diff --git a/.codeqlmanifest.json b/.codeqlmanifest.json index 4efe12f6d2b3..97e6df87f14e 100644 --- a/.codeqlmanifest.json +++ b/.codeqlmanifest.json @@ -1,4 +1,5 @@ -{ "provide": [ "*/ql/src/qlpack.yml", +{ "provide": [ "ruby/.codeqlmanifest.json", + "*/ql/src/qlpack.yml", "*/ql/lib/qlpack.yml", "*/ql/test/qlpack.yml", "cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/tainted/qlpack.yml", diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8f9ffdde4de4..ff73bcb4e7b0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,9 +1,14 @@ { "extensions": [ + "rust-lang.rust", + "bungcip.better-toml", "github.vscode-codeql", "slevesque.vscode-zipexplorer" ], "settings": { + "files.watcherExclude": { + "**/target/**": true + }, "codeQL.runningQueries.memory": 2048 } } diff --git a/.github/actions/fetch-codeql/action.yml b/.github/actions/fetch-codeql/action.yml new file mode 100644 index 000000000000..de6d50dc12fe --- /dev/null +++ b/.github/actions/fetch-codeql/action.yml @@ -0,0 +1,14 @@ +name: Fetch CodeQL +description: Fetches the latest version of CodeQL +runs: + using: composite + steps: + - name: Fetch CodeQL + shell: bash + run: | + LATEST=$(gh release list --repo https://github.com/github/codeql-cli-binaries | cut -f 1 | grep -v beta | sort --version-sort | tail -1) + gh release download --repo https://github.com/github/codeql-cli-binaries --pattern codeql-linux64.zip "$LATEST" + unzip -q codeql-linux64.zip + echo "${{ github.workspace }}/codeql" >> $GITHUB_PATH + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000000..613f92871464 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "ruby/node-types" + schedule: + interval: "daily" + - package-ecosystem: "cargo" + directory: "ruby/generator" + schedule: + interval: "daily" + - package-ecosystem: "cargo" + directory: "ruby/extractor" + schedule: + interval: "daily" + - package-ecosystem: "cargo" + directory: "ruby/autobuilder" + schedule: + interval: "daily" diff --git a/.github/workflows/qhelp-pr-preview.yml b/.github/workflows/qhelp-pr-preview.yml new file mode 100644 index 000000000000..7e2ea6a10f8f --- /dev/null +++ b/.github/workflows/qhelp-pr-preview.yml @@ -0,0 +1,39 @@ +name: Query help preview + +on: + pull_request: + branches: + - main + - 'rc/*' + paths: + - "ruby/**/*.qhelp" + +jobs: + qhelp: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 2 + - name: Determine changed files + id: changes + run: | + echo -n "::set-output name=qhelp_files::" + (git diff --name-only --diff-filter=ACMRT HEAD~1 HEAD | grep .qhelp$ | grep -v .inc.qhelp; + git diff --name-only --diff-filter=ACMRT HEAD~1 HEAD | grep .inc.qhelp$ | xargs -d '\n' -rn1 basename | xargs -d '\n' -rn1 git grep -l) | + sort -u | xargs -d '\n' -n1 printf "'%s' " + + - uses: ./.github/actions/fetch-codeql + + - name: QHelp preview + if: ${{ steps.changes.outputs.qhelp_files }} + run: | + ( echo "QHelp previews:"; + for path in ${{ steps.changes.outputs.qhelp_files }} ; do + echo "
${path}" + echo + codeql generate query-help --format=markdown ${path} + echo "
" + done) | gh pr comment "${{ github.event.pull_request.number }}" -F - + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/ruby-build.yml b/.github/workflows/ruby-build.yml new file mode 100644 index 000000000000..4361ce5a1cc6 --- /dev/null +++ b/.github/workflows/ruby-build.yml @@ -0,0 +1,232 @@ +name: "Ruby: Build" + +on: + push: + paths: + - 'ruby/**' + branches: + - main + - 'rc/*' + pull_request: + paths: + - 'ruby/**' + branches: + - main + - 'rc/*' + workflow_dispatch: + inputs: + tag: + description: "Version tag to create" + required: false + +env: + CARGO_TERM_COLOR: always + +defaults: + run: + working-directory: ruby + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + - name: Install GNU tar + if: runner.os == 'macOS' + run: | + brew install gnu-tar + echo "/usr/local/opt/gnu-tar/libexec/gnubin" >> $GITHUB_PATH + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + ruby/target + key: ${{ runner.os }}-rust-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Check formatting + run: cargo fmt --all -- --check + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose + - name: Release build + run: cargo build --release + - name: Generate dbscheme + if: ${{ matrix.os == 'ubuntu-latest' }} + run: target/release/ruby-generator --dbscheme ql/lib/ruby.dbscheme --library ql/lib/codeql/ruby/ast/internal/TreeSitter.qll + - uses: actions/upload-artifact@v2 + if: ${{ matrix.os == 'ubuntu-latest' }} + with: + name: ruby.dbscheme + path: ruby/ql/lib/ruby.dbscheme + - uses: actions/upload-artifact@v2 + if: ${{ matrix.os == 'ubuntu-latest' }} + with: + name: TreeSitter.qll + path: ruby/ql/lib/codeql/ruby/ast/internal/TreeSitter.qll + - uses: actions/upload-artifact@v2 + with: + name: extractor-${{ matrix.os }} + path: | + ruby/target/release/ruby-autobuilder + ruby/target/release/ruby-autobuilder.exe + ruby/target/release/ruby-extractor + ruby/target/release/ruby-extractor.exe + retention-days: 1 + compile-queries: + runs-on: ubuntu-latest + env: + CODEQL_THREADS: 4 # TODO: remove this once it's set by the CLI + steps: + - uses: actions/checkout@v2 + - name: Fetch CodeQL + run: | + LATEST=$(gh release list --repo https://github.com/github/codeql-cli-binaries | cut -f 1 | grep -v beta | sort --version-sort | tail -1) + gh release download --repo https://github.com/github/codeql-cli-binaries --pattern codeql-linux64.zip "$LATEST" + unzip -q codeql-linux64.zip + env: + GITHUB_TOKEN: ${{ github.token }} + - name: Build Query Pack + run: | + codeql/codeql pack create ql/lib --output target/packs + codeql/codeql pack install ql/src + codeql/codeql pack create ql/src --output target/packs + PACK_FOLDER=$(readlink -f target/packs/codeql/ruby-queries/*) + codeql/codeql generate query-help --format=sarifv2.1.0 --output="${PACK_FOLDER}/rules.sarif" ql/src + (cd ql/src; find queries \( -name '*.qhelp' -o -name '*.rb' -o -name '*.erb' \) -exec bash -c 'mkdir -p "'"${PACK_FOLDER}"'/$(dirname "{}")"' \; -exec cp "{}" "${PACK_FOLDER}/{}" \;) + - name: Compile with previous CodeQL versions + run: | + for version in $(gh release list --repo https://github.com/github/codeql-cli-binaries | cut -f 1 | sort --version-sort | tail -3 | head -2); do + rm -f codeql-linux64.zip + gh release download --repo https://github.com/github/codeql-cli-binaries --pattern codeql-linux64.zip "$version" + rm -rf codeql; unzip -q codeql-linux64.zip + codeql/codeql query compile target/packs/* + done + env: + GITHUB_TOKEN: ${{ github.token }} + - uses: actions/upload-artifact@v2 + with: + name: codeql-ruby-queries + path: | + ruby/target/packs/* + retention-days: 1 + + package: + runs-on: ubuntu-latest + needs: [build, compile-queries] + steps: + - uses: actions/checkout@v2 + - uses: actions/download-artifact@v2 + with: + name: ruby.dbscheme + path: ruby/ruby + - uses: actions/download-artifact@v2 + with: + name: extractor-ubuntu-latest + path: ruby/linux64 + - uses: actions/download-artifact@v2 + with: + name: extractor-windows-latest + path: ruby/win64 + - uses: actions/download-artifact@v2 + with: + name: extractor-macos-latest + path: ruby/osx64 + - run: | + mkdir -p ruby + cp -r codeql-extractor.yml tools ql/lib/ruby.dbscheme.stats ruby/ + mkdir -p ruby/tools/{linux64,osx64,win64} + cp linux64/ruby-autobuilder ruby/tools/linux64/autobuilder + cp osx64/ruby-autobuilder ruby/tools/osx64/autobuilder + cp win64/ruby-autobuilder.exe ruby/tools/win64/autobuilder.exe + cp linux64/ruby-extractor ruby/tools/linux64/extractor + cp osx64/ruby-extractor ruby/tools/osx64/extractor + cp win64/ruby-extractor.exe ruby/tools/win64/extractor.exe + chmod +x ruby/tools/{linux64,osx64}/{autobuilder,extractor} + zip -rq codeql-ruby.zip ruby + - uses: actions/upload-artifact@v2 + with: + name: codeql-ruby-pack + path: ruby/codeql-ruby.zip + retention-days: 1 + - uses: actions/download-artifact@v2 + with: + name: codeql-ruby-queries + path: ruby/qlpacks + - run: | + echo '{ + "provide": [ + "ruby/codeql-extractor.yml", + "qlpacks/*/*/*/qlpack.yml" + ] + }' > .codeqlmanifest.json + zip -rq codeql-ruby-bundle.zip .codeqlmanifest.json ruby qlpacks + - uses: actions/upload-artifact@v2 + with: + name: codeql-ruby-bundle + path: ruby/codeql-ruby-bundle.zip + retention-days: 1 + + test: + defaults: + run: + working-directory: ${{ github.workspace }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + runs-on: ${{ matrix.os }} + needs: [package] + steps: + - uses: actions/checkout@v2 + with: + repository: Shopify/example-ruby-app + ref: 67a0decc5eb550f3a9228eda53925c3afd40dfe9 + - name: Fetch CodeQL + shell: bash + run: | + LATEST=$(gh release list --repo https://github.com/github/codeql-cli-binaries | cut -f 1 | grep -v beta | sort --version-sort | tail -1) + gh release download --repo https://github.com/github/codeql-cli-binaries --pattern codeql.zip "$LATEST" + unzip -q codeql.zip + env: + GITHUB_TOKEN: ${{ github.token }} + working-directory: ${{ runner.temp }} + - name: Download Ruby bundle + uses: actions/download-artifact@v2 + with: + name: codeql-ruby-bundle + path: ${{ runner.temp }} + - name: Unzip Ruby bundle + shell: bash + run: unzip -q -d "${{ runner.temp }}/ruby-bundle" "${{ runner.temp }}/codeql-ruby-bundle.zip" + - name: Prepare test files + shell: bash + run: | + echo "import ruby select count(File f)" > "test.ql" + echo "| 4 |" > "test.expected" + echo 'name: sample-tests + version: 0.0.0 + dependencies: + codeql/ruby-all: 0.0.1 + extractor: ruby + tests: . + ' > qlpack.yml + - name: Run QL test + shell: bash + run: | + "${{ runner.temp }}/codeql/codeql" test run --search-path "${{ runner.temp }}/ruby-bundle" --additional-packs "${{ runner.temp }}/ruby-bundle" . + - name: Create database + shell: bash + run: | + "${{ runner.temp }}/codeql/codeql" database create --search-path "${{ runner.temp }}/ruby-bundle" --language ruby --source-root . ../database + - name: Analyze database + shell: bash + run: | + "${{ runner.temp }}/codeql/codeql" database analyze --search-path "${{ runner.temp }}/ruby-bundle" --format=sarifv2.1.0 --output=out.sarif ../database ruby-code-scanning.qls diff --git a/.github/workflows/ruby-dataset-measure.yml b/.github/workflows/ruby-dataset-measure.yml new file mode 100644 index 000000000000..39e5a8486d60 --- /dev/null +++ b/.github/workflows/ruby-dataset-measure.yml @@ -0,0 +1,71 @@ +name: "Ruby: Collect database stats" + +on: + push: + branches: + - main + - 'rc/*' + paths: + - ruby/ql/lib/ruby.dbscheme + pull_request: + branches: + - main + - 'rc/*' + paths: + - ruby/ql/lib/ruby.dbscheme + workflow_dispatch: + +jobs: + measure: + env: + CODEQL_THREADS: 4 # TODO: remove this once it's set by the CLI + strategy: + fail-fast: false + matrix: + repo: [rails/rails, discourse/discourse, spree/spree] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: ./.github/actions/fetch-codeql + + - uses: ./ruby/actions/create-extractor-pack + + - 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 "${{ github.workspace }}/ruby" \ + --threads 4 \ + --language ruby --source-root "${{ github.workspace }}/repo" \ + "${{ runner.temp }}/database" + - name: Measure database + run: | + mkdir -p "stats/${{ matrix.repo }}" + codeql dataset measure --threads 4 --output "stats/${{ matrix.repo }}/stats.xml" "${{ runner.temp }}/database/db-ruby" + - 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' | sort | xargs python ruby/scripts/merge_stats.py --output ruby/ql/lib/ruby.dbscheme.stats --normalise ruby_tokeninfo + - uses: actions/upload-artifact@v2 + with: + name: ruby.dbscheme.stats + path: ruby/ql/lib/ruby.dbscheme.stats diff --git a/.github/workflows/ruby-qltest.yml b/.github/workflows/ruby-qltest.yml new file mode 100644 index 000000000000..fad38bf689e9 --- /dev/null +++ b/.github/workflows/ruby-qltest.yml @@ -0,0 +1,48 @@ +name: "Ruby: Run QL Tests" + +on: + push: + paths: + - 'ruby/**' + branches: + - main + - 'rc/*' + pull_request: + paths: + - 'ruby/**' + branches: + - main + - 'rc/*' + +env: + CARGO_TERM_COLOR: always + +defaults: + run: + working-directory: ruby + +jobs: + qltest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ./.github/actions/fetch-codeql + - uses: ./ruby/actions/create-extractor-pack + - 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 }}/ruby" --additional-packs "${{ github.workspace }}" --consistency-queries ql/consistency-queries ql/test + env: + GITHUB_TOKEN: ${{ github.token }} + - name: Check QL formatting + run: find ql "(" -name "*.ql" -or -name "*.qll" ")" -print0 | xargs -0 codeql query format --check-only + - name: Check QL compilation + run: | + codeql query compile --check-only --threads=4 --warnings=error --search-path "${{ github.workspace }}/ruby" --additional-packs "${{ github.workspace }}" "ql/src" "ql/examples" + env: + GITHUB_TOKEN: ${{ github.token }} + - name: Check DB upgrade scripts + run: | + echo >empty.trap + codeql dataset import -S ql/lib/upgrades/initial/ruby.dbscheme testdb empty.trap + codeql dataset upgrade testdb --additional-packs ql/lib/upgrades + diff -q testdb/ruby.dbscheme ql/lib/ruby.dbscheme diff --git a/.github/workflows/sync-files.yml b/.github/workflows/sync-files.yml new file mode 100644 index 000000000000..e41f4b75dc6c --- /dev/null +++ b/.github/workflows/sync-files.yml @@ -0,0 +1,20 @@ +name: Check synchronized files + +on: + push: + branches: + - main + - 'rc/*' + pull_request: + branches: + - main + - 'rc/*' + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Check synchronized files + run: python config/sync-files.py + diff --git a/CODEOWNERS b/CODEOWNERS index 89529f95924a..d5f3362a7ca6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,6 +3,7 @@ /java/ @github/codeql-java /javascript/ @github/codeql-javascript /python/ @github/codeql-python +/ruby/ @github/codeql-ruby # Make @xcorail (GitHub Security Lab) a code owner for experimental queries so he gets pinged when we promote a query out of experimental /cpp/**/experimental/**/* @github/codeql-c-analysis @xcorail @@ -10,6 +11,7 @@ /java/**/experimental/**/* @github/codeql-java @xcorail /javascript/**/experimental/**/* @github/codeql-javascript @xcorail /python/**/experimental/**/* @github/codeql-python @xcorail +/ruby/**/experimental/**/* @github/codeql-ruby @xcorail # Notify members of codeql-go about PRs to the shared data-flow library files /java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @github/codeql-java @github/codeql-go @@ -22,4 +24,4 @@ /docs/codeql-cli/ @github/codeql-cli-reviewers /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 \ No newline at end of file +/docs/query-*-style-guide.md @github/codeql-analysis-reviewers diff --git a/config/identical-files.json b/config/identical-files.json index 74ef7b82323b..f3907eb154bc 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -24,14 +24,16 @@ "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll", "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll", "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll", - "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll" + "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll", + "ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll" ], "DataFlow Java/C++/C#/Python Common": [ "java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll", "cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll", "cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll", "csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll", - "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll" + "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll", + "ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplCommon.qll" ], "TaintTracking::Configuration Java/C++/C#/Python": [ "cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTrackingImpl.qll", @@ -49,18 +51,21 @@ "python/ql/lib/semmle/python/dataflow/new/internal/tainttracking1/TaintTrackingImpl.qll", "python/ql/lib/semmle/python/dataflow/new/internal/tainttracking2/TaintTrackingImpl.qll", "python/ql/lib/semmle/python/dataflow/new/internal/tainttracking3/TaintTrackingImpl.qll", - "python/ql/lib/semmle/python/dataflow/new/internal/tainttracking4/TaintTrackingImpl.qll" + "python/ql/lib/semmle/python/dataflow/new/internal/tainttracking4/TaintTrackingImpl.qll", + "ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTrackingImpl.qll" ], "DataFlow Java/C++/C#/Python Consistency checks": [ "java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplConsistency.qll", "cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplConsistency.qll", "cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplConsistency.qll", "csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplConsistency.qll", - "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplConsistency.qll" + "python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplConsistency.qll", + "ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplConsistency.qll" ], "DataFlow Java/C# Flow Summaries": [ "java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll", - "csharp/ql/lib/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll" + "csharp/ql/lib/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll", + "ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll" ], "SsaReadPosition Java/C#": [ "java/ql/lib/semmle/code/java/dataflow/internal/rangeanalysis/SsaReadPositionCommon.qll", @@ -368,7 +373,8 @@ "Inline Test Expectations": [ "cpp/ql/test/TestUtilities/InlineExpectationsTest.qll", "java/ql/test/TestUtilities/InlineExpectationsTest.qll", - "python/ql/test/TestUtilities/InlineExpectationsTest.qll" + "python/ql/test/TestUtilities/InlineExpectationsTest.qll", + "ruby/ql/test/TestUtilities/InlineExpectationsTest.qll" ], "C++ ExternalAPIs": [ "cpp/ql/src/Security/CWE/CWE-020/ExternalAPIs.qll", @@ -440,7 +446,8 @@ "csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll", "csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplCommon.qll", "csharp/ql/lib/semmle/code/csharp/dataflow/internal/basessa/SsaImplCommon.qll", - "csharp/ql/lib/semmle/code/cil/internal/SsaImplCommon.qll" + "csharp/ql/lib/semmle/code/cil/internal/SsaImplCommon.qll", + "ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll" ], "CryptoAlgorithms Python/JS": [ "javascript/ql/lib/semmle/javascript/security/CryptoAlgorithms.qll", @@ -460,6 +467,15 @@ ], "ReDoS Polynomial Python/JS": [ "javascript/ql/lib/semmle/javascript/security/performance/SuperlinearBackTracking.qll", - "python/ql/lib/semmle/python/security/performance/SuperlinearBackTracking.qll" + "python/ql/lib/semmle/python/security/performance/SuperlinearBackTracking.qll", + "ruby/ql/lib/codeql/ruby/regexp/SuperlinearBackTracking.qll" + ], + "CFG": [ + "csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImplShared.qll", + "ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplShared.qll" + ], + "TypeTracker": [ + "python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll", + "ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll" ] -} \ No newline at end of file +} diff --git a/docs/codeql/query-help/codeql-cwe-coverage.rst b/docs/codeql/query-help/codeql-cwe-coverage.rst index b333053a1c80..089d620f40e7 100644 --- a/docs/codeql/query-help/codeql-cwe-coverage.rst +++ b/docs/codeql/query-help/codeql-cwe-coverage.rst @@ -33,3 +33,4 @@ Note that the CWE coverage includes both "`supported queries ` - :doc:`CodeQL query help for JavaScript ` - :doc:`CodeQL query help for Python ` +- :doc:`CodeQL query help for Ruby ` .. pull-quote:: Information @@ -33,5 +34,6 @@ For a full list of the CWEs covered by these queries, see ":doc:`CodeQL CWE cove java javascript python + ruby codeql-cwe-coverage diff --git a/docs/codeql/query-help/ruby-cwe.md b/docs/codeql/query-help/ruby-cwe.md new file mode 100644 index 000000000000..61f49069fce2 --- /dev/null +++ b/docs/codeql/query-help/ruby-cwe.md @@ -0,0 +1,8 @@ +# CWE coverage for Ruby + +An overview of CWE coverage for Ruby in the latest release of CodeQL. + +## Overview + + + diff --git a/docs/codeql/query-help/ruby.rst b/docs/codeql/query-help/ruby.rst new file mode 100644 index 000000000000..3ce1304ba761 --- /dev/null +++ b/docs/codeql/query-help/ruby.rst @@ -0,0 +1,8 @@ +CodeQL query help for Ruby +============================ + +.. include:: ../reusables/query-help-overview.rst + +For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository `__. + +.. include:: toc-ruby.rst diff --git a/ruby/.codeqlmanifest.json b/ruby/.codeqlmanifest.json new file mode 100644 index 000000000000..7effe0a77c5f --- /dev/null +++ b/ruby/.codeqlmanifest.json @@ -0,0 +1,10 @@ +{ + "provide": [ + "ql/lib/qlpack.yml", + "ql/src/qlpack.yml", + "ql/consistency-queries/qlpack.yml", + "ql/test/qlpack.yml", + "ql/examples/qlpack.yml", + "extractor-pack/codeql-extractor.yml" + ] +} diff --git a/ruby/.gitattributes b/ruby/.gitattributes new file mode 100644 index 000000000000..f4c10fb0a918 --- /dev/null +++ b/ruby/.gitattributes @@ -0,0 +1 @@ +Cargo.lock -diff -whitespace diff --git a/ruby/.gitignore b/ruby/.gitignore new file mode 100644 index 000000000000..2b3f9bb1af23 --- /dev/null +++ b/ruby/.gitignore @@ -0,0 +1,8 @@ +/target +extractor-pack +.vscode/launch.json +.cache +ql/test/**/*.testproj +ql/test/**/*.actual +ql/test/**/CONSISTENCY +.codeql diff --git a/ruby/.vscode/tasks.json b/ruby/.vscode/tasks.json new file mode 100644 index 000000000000..c28cd789fcae --- /dev/null +++ b/ruby/.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/ruby/Cargo.lock b/ruby/Cargo.lock new file mode 100644 index 000000000000..f6bfc9f2a599 --- /dev/null +++ b/ruby/Cargo.lock @@ -0,0 +1,662 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[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 = "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 = "ruby-autobuilder" +version = "0.1.0" + +[[package]] +name = "ruby-extractor" +version = "0.1.0" +dependencies = [ + "clap", + "flate2", + "node-types", + "num_cpus", + "rayon", + "regex", + "tracing", + "tracing-subscriber", + "tree-sitter", + "tree-sitter-embedded-template", + "tree-sitter-ruby", +] + +[[package]] +name = "ruby-generator" +version = "0.1.0" +dependencies = [ + "clap", + "node-types", + "tracing", + "tracing-subscriber", + "tree-sitter-embedded-template", + "tree-sitter-ruby", +] + +[[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-embedded-template" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193fe1e3b39f71785d1890e4ac7bedd9be3d797f5db6363884415804fd516d93" +dependencies = [ + "cc", + "tree-sitter", +] + +[[package]] +name = "tree-sitter-ruby" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b8d9d12b864a5f587052046be0bb8c7ae005df605b3150224472c41705268d" +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/ruby/Cargo.toml b/ruby/Cargo.toml new file mode 100644 index 000000000000..7acbd36afa6f --- /dev/null +++ b/ruby/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] +members = [ + "autobuilder", + "extractor", + "generator", + "node-types", +] diff --git a/ruby/Makefile b/ruby/Makefile new file mode 100644 index 000000000000..02d7c92a46e0 --- /dev/null +++ b/ruby/Makefile @@ -0,0 +1,71 @@ +all: extractor dbscheme + +ifeq ($(OS),Windows_NT) +EXE = .exe +CODEQL_PLATFORM = win64 +else +EXE = +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Linux) +CODEQL_PLATFORM = linux64 +endif +ifeq ($(UNAME_S),Darwin) +CODEQL_PLATFORM = osx64 +endif +endif + +FILES=codeql-extractor.yml\ + tools/qltest.cmd\ + tools/index-files.sh\ + tools/index-files.cmd\ + tools/autobuild.sh\ + tools/qltest.sh\ + tools/autobuild.cmd\ + ql/lib/ruby.dbscheme.stats\ + ql/lib/ruby.dbscheme + +BIN_FILES=target/release/ruby-extractor$(EXE) target/release/ruby-autobuilder$(EXE) + +extractor-common: + rm -rf build + mkdir build + mkdir build/codeql-extractor-ruby + cp codeql-extractor.yml ql/lib/ruby.dbscheme ql/lib/ruby.dbscheme.stats build/codeql-extractor-ruby + cp -r tools build/codeql-extractor-ruby/ + +.PHONY: tools +tools: $(BIN_FILES) + rm -rf tools/bin + mkdir tools/bin + cp -r target/release/ruby-autobuilder$(EXE) tools/bin/autobuilder$(EXE) + cp -r target/release/ruby-extractor$(EXE) tools/bin/extractor$(EXE) + +target/release/%$(EXE): + cargo build --release --bin $(basename $(notdir $@)) + +dbscheme: + cargo build --bin ruby-generator + cargo run -p ruby-generator -- --dbscheme ql/lib/ruby.dbscheme --library ql/lib/codeql/ruby/ast/internal/TreeSitter.qll + codeql query format -i ql/lib/codeql/ruby/ast/internal/TreeSitter.qll + +.PHONY: extractor +extractor: $(FILES) $(BIN_FILES) + rm -rf extractor-pack + mkdir extractor-pack + mkdir extractor-pack/tools + mkdir extractor-pack/tools/$(CODEQL_PLATFORM) + cp codeql-extractor.yml extractor-pack/codeql-extractor.yml + cp tools/qltest.cmd extractor-pack/tools/qltest.cmd + cp tools/index-files.sh extractor-pack/tools/index-files.sh + cp tools/index-files.cmd extractor-pack/tools/index-files.cmd + cp tools/autobuild.sh extractor-pack/tools/autobuild.sh + cp tools/qltest.sh extractor-pack/tools/qltest.sh + cp tools/autobuild.cmd extractor-pack/tools/autobuild.cmd + cp ql/lib/ruby.dbscheme.stats extractor-pack/ruby.dbscheme.stats + cp ql/lib/ruby.dbscheme extractor-pack/ruby.dbscheme + cp target/release/ruby-extractor$(EXE) extractor-pack/tools/$(CODEQL_PLATFORM)/extractor$(EXE) + cp target/release/ruby-autobuilder$(EXE) extractor-pack/tools/$(CODEQL_PLATFORM)/autobuilder$(EXE) + +test: extractor dbscheme + codeql pack install ql/test + codeql test run --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --search-path . --consistency-queries ql/consistency-queries ql/test diff --git a/ruby/README.md b/ruby/README.md new file mode 100644 index 000000000000..2e816a76ddb2 --- /dev/null +++ b/ruby/README.md @@ -0,0 +1,47 @@ +# Ruby analysis support for CodeQL + +Under development. + +## Building the tools from source + +[Install Rust](https://www.rust-lang.org/tools/install), then run: + +```bash +cargo build --release +``` + +## Generating the database schema and QL library + +The generated `ql/lib/ruby.dbscheme` and `ql/lib/codeql/ruby/ast/internal/TreeSitter.qll` files are included in the repository, but they can be re-generated as follows: + +```bash +# Run the generator +cargo run --release -p ruby-generator -- --dbscheme ql/lib/ruby.dbscheme --library ql/lib/codeql/ruby/ast/internal/TreeSitter.qll +# Then auto-format the QL library +codeql query format -i ql/lib/codeql/ruby/ast/internal/TreeSitter.qll +``` + +## Building a CodeQL database for a Ruby program + +First, get an extractor pack. There are two options: + +1. Either download the latest `codeql-ruby-pack` from Actions and unzip it twice, or +2. Run `scripts/create-extractor-pack.sh` (Linux/Mac) or `scripts\create-extractor-pack.ps1` (Windows PowerShell) and the pack will be created in the `extractor-pack` directory. + +Then run + +```bash +codeql database create -l ruby -s --search-path +``` + +## Running qltests + +Run + +```bash +codeql test run --search-path +``` + +## Writing database upgrade scripts + +See [this guide](doc/prepare-db-upgrade.md). diff --git a/ruby/actions/create-extractor-pack/action.yml b/ruby/actions/create-extractor-pack/action.yml new file mode 100644 index 000000000000..75faf327c131 --- /dev/null +++ b/ruby/actions/create-extractor-pack/action.yml @@ -0,0 +1,16 @@ +name: Build Ruby CodeQL pack +description: Builds the Ruby CodeQL pack +runs: + using: composite + steps: + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + ruby/target + key: ${{ runner.os }}-qltest-cargo-${{ hashFiles('ruby/**/Cargo.lock') }} + - name: Build Extractor + shell: bash + run: scripts/create-extractor-pack.sh + working-directory: ruby diff --git a/ruby/autobuilder/Cargo.toml b/ruby/autobuilder/Cargo.toml new file mode 100644 index 000000000000..c77b6895dff8 --- /dev/null +++ b/ruby/autobuilder/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "ruby-autobuilder" +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] diff --git a/ruby/autobuilder/src/main.rs b/ruby/autobuilder/src/main.rs new file mode 100644 index 000000000000..89dab018d401 --- /dev/null +++ b/ruby/autobuilder/src/main.rs @@ -0,0 +1,39 @@ +use std::env; +use std::path::PathBuf; +use std::process::Command; + +fn main() -> std::io::Result<()> { + let dist = env::var("CODEQL_DIST").expect("CODEQL_DIST not set"); + let db = env::var("CODEQL_EXTRACTOR_RUBY_WIP_DATABASE") + .expect("CODEQL_EXTRACTOR_RUBY_WIP_DATABASE not set"); + let codeql = if env::consts::OS == "windows" { + "codeql.exe" + } else { + "codeql" + }; + let codeql: PathBuf = [&dist, codeql].iter().collect(); + let mut cmd = Command::new(codeql); + cmd.arg("database") + .arg("index-files") + .arg("--include-extension=.rb") + .arg("--include-extension=.erb") + .arg("--include-extension=.gemspec") + .arg("--include=**/Gemfile") + .arg("--size-limit=5m") + .arg("--language=ruby") + .arg("--working-dir=.") + .arg(db); + + for line in env::var("LGTM_INDEX_FILTERS") + .unwrap_or_default() + .split("\n") + { + if line.starts_with("include:") { + cmd.arg("--include").arg(&line[8..]); + } else if line.starts_with("exclude:") { + cmd.arg("--exclude").arg(&line[8..]); + } + } + let exit = &cmd.spawn()?.wait()?; + std::process::exit(exit.code().unwrap_or(1)) +} diff --git a/ruby/codeql-extractor.yml b/ruby/codeql-extractor.yml new file mode 100644 index 000000000000..534e91f74ac6 --- /dev/null +++ b/ruby/codeql-extractor.yml @@ -0,0 +1,14 @@ +name: "ruby" +display_name: "Ruby" +version: 0.1.0 +column_kind: "utf8" +legacy_qltest_extraction: true +file_types: + - name: ruby + display_name: Ruby files + extensions: + - .rb + - name: erb + display_name: Ruby templates + extensions: + - .erb diff --git a/ruby/codeql-ruby.code-workspace b/ruby/codeql-ruby.code-workspace new file mode 100644 index 000000000000..7610040e0989 --- /dev/null +++ b/ruby/codeql-ruby.code-workspace @@ -0,0 +1,14 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "editor.formatOnSave": true, + "files.eol": "\n", + "files.exclude": { + "codeql": true + } + } +} \ No newline at end of file diff --git a/ruby/doc/prepare-db-upgrade.md b/ruby/doc/prepare-db-upgrade.md new file mode 100644 index 000000000000..239eef3cd275 --- /dev/null +++ b/ruby/doc/prepare-db-upgrade.md @@ -0,0 +1,91 @@ +# Upgrading the Ruby database schema + +The schema (`ql/lib/ruby.dbscheme`) is automatically generated from tree-sitter's `node-types.json`. When the tree-sitter grammar changes, the database schema is likely to change as well, and we need to write an upgrade script. This document explains how to do that. + +## Process Overview + + 1. Commit the change to `ruby.dbscheme` (along with any library updates required to work with the change). + 2. Run `scripts/prepare-db-upgrade.sh`. + 3. Fill in the details in `upgrade.properties`, and add any required upgrade queries. + +It may be helpful to look at some of the existing upgrade scripts, to see how they work. + +## Details + +### Generating a Ruby database upgrade script + +Schema changes need to be accompanied by scripts that allow us to upgrade databases that were generated with older versions of the tools, so they can use new query functionality (albeit with possibly degraded results). + +#### The easy (mostly automatic) way + +The easy way to generate an upgrade script is to run the `scripts/prepare-db-upgrade.sh` script. This will generate a skeleton upgrade directory, leaving you to fill out the details in the `upgrade.properties` file. + +#### upgrade.properties + +It will look something like: + +``` +description: what it does +compatibility: partial +some_relation.rel: run some_relation.qlo +``` + +The `description` field is a textual description of the aim of the upgrade. + +The `compatibility` field takes one of four values: + + * **full**: results from the upgraded snapshot will be identical to results from a snapshot built with the new version of the toolchain. + + * **backwards**: the step is safe and preserves the meaning of the old database, but new features may not work correctly on the upgraded snapshot. + + * **partial**: the step is safe and preserves the meaning of the old database, but you would get better results if you rebuilt the snapshot with the new version of the toolchain. + + * **breaking**: the step is unsafe and will prevent certain queries from working. + +The `some_relation.rel` line(s) are the actions required to do the database upgrade. Do a diff on the the new vs old `.dbscheme` file to get an idea of what they have to achieve. Sometimes you won't need any upgrade commands – this happens when the dbscheme has changed in "cosmetic" ways, for example by adding/removing comments or changing union type relationships, but still retains the same on-disk format for all tables; the purpose of the upgrade script is then to document the fact that it's safe to replace the old dbscheme with the new one. + +Some typical upgrade commands look like this: + +``` +// Delete a relation that has been replaced in the new scheme +obsolete.rel: delete + +// Create a new version of a table by applying a simple RA expression to an +// existing table. The example duplicates the 'id' column of input.rel as +// the last column of etended.rel, perhaps to record our best guess at +// newly-populated "source declaration" information. +extended.rel: reorder input.rel (int id, string name, int parent) id name parent id + +// Create relationname.rel by running relationname.qlo and writing the query +// results as a .rel file. The query file should be named relationname.ql and +// should be placed in the upgrade directory. It should avoid using the default +// QLL library, and will run in the context of the *old* dbscheme. +relationname.rel: run relationname.qlo +``` + +#### Testing your upgrade script + +Upgrade scripts can be a little bit fiddly, so it's essential that you test them. You might do so as follows: + + 1. Create a snapshot of your favourite project using the old version of the code. + + 2. Switch to the new version of the code. + + 3. Try to run some queries that will depend on your upgrade script working correctly. + + 4. Observe the upgrade being performed in the query server log. + + 5. Verify that your queries produced sensible results. + +#### Doing the upgrade manually + +To create the upgrade directory manually, without using `scripts/prepare-db-upgrade.sh`: + +1. Get a hash of the old `.dbscheme` file, from just before your changes. You can do this by checking out the code prior to your changes and running `git hash-object ql/lib/ruby.dbscheme` + +2. Go back to your branch and create an upgrade directory with that hash as its name, for example: `mkdir ql/lib/upgrades/454f1e15151422355049dc4f1f0486a03baeffef` + +3. Copy the old `.dbscheme` file to that directory, using the name old.dbscheme. + `cp ql/lib/ruby.dbscheme ql/lib/upgrades/454f1e15151422355049dc4f1f0486a03baeffef/old.dbscheme` + +4. Put a copy of your new `.dbscheme` file in that directory and create an `upgrade.properties` file (as described above). diff --git a/ruby/extractor/Cargo.toml b/ruby/extractor/Cargo.toml new file mode 100644 index 000000000000..beb1ac2345fd --- /dev/null +++ b/ruby/extractor/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "ruby-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-embedded-template = "0.19" +tree-sitter-ruby = "0.19" +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/ruby/extractor/src/extractor.rs b/ruby/extractor/src/extractor.rs new file mode 100644 index 000000000000..b1e7750b2d05 --- /dev/null +++ b/ruby/extractor/src/extractor.rs @@ -0,0 +1,844 @@ +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: &Vec, + 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: &source, + trap_writer: 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 { + match c { + '&' => true, + '{' => true, + '}' => true, + '"' => true, + '@' => true, + '#' => true, + _ => false, + } + } + + 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 Vec, + /// 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())); + return 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::new(); + all_args.push(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: &Vec, + child_nodes: &Vec, + 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::new(); + args.push(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; + } + match &self.schema.get(single_type).unwrap().kind { + EntryKind::Union { members } => { + 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: &Vec, 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<'a>(source: &Vec, 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: &String, max_size: usize) -> &str { + if string.len() <= max_size { + return string; + } + let p = string.as_ptr(); + let mut index = max_size; + // We want to clip the string at [max_size]; however, the character at that position + // may span several bytes. We need to find the first byte of the character. In UTF-8 + // encoded data any byte that matches the bit pattern 10XXXXXX is not a start byte. + // Therefore we decrement the index as long as there are bytes matching this pattern. + // This ensures we cut the string at the border between one character and another. + while index > 0 && unsafe { (*p.offset(index as isize) & 0b11000000) == 0b10000000 } { + 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/ruby/extractor/src/main.rs b/ruby/extractor/src/main.rs new file mode 100644 index 000000000000..d9132b1952e4 --- /dev/null +++ b/ruby/extractor/src/main.rs @@ -0,0 +1,300 @@ +mod extractor; + +extern crate num_cpus; + +use clap; +use flate2::write::GzEncoder; +use rayon::prelude::*; +use std::fs; +use std::io::{BufRead, BufWriter}; +use std::path::{Path, PathBuf}; +use tree_sitter::{Language, Parser, Range}; + +enum TrapCompression { + None, + Gzip, +} + +impl TrapCompression { + fn from_env() -> TrapCompression { + match std::env::var("CODEQL_RUBY_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("-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("Ruby extractor") + .version("1.0") + .author("GitHub") + .about("CodeQL Ruby 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_ruby::language(); + let erb = tree_sitter_embedded_template::language(); + // Look up tree-sitter kind ids now, to avoid string comparisons when scanning ERB files. + let erb_directive_id = erb.id_for_node_kind("directive", true); + let erb_output_directive_id = erb.id_for_node_kind("output_directive", true); + let erb_code_id = erb.id_for_node_kind("code", true); + let schema = node_types::read_node_types_str("ruby", tree_sitter_ruby::NODE_TYPES)?; + let erb_schema = + node_types::read_node_types_str("erb", tree_sitter_embedded_template::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 mut source = std::fs::read(&path)?; + let code_ranges; + let mut trap_writer = extractor::new_trap_writer(); + if path.extension().map_or(false, |x| x == "erb") { + tracing::info!("scanning: {}", path.display()); + extractor::extract( + erb, + "erb", + &erb_schema, + &mut trap_writer, + &path, + &source, + &[], + )?; + + let (ranges, line_breaks) = scan_erb( + erb, + &source, + erb_directive_id, + erb_output_directive_id, + erb_code_id, + ); + for i in line_breaks { + if i < source.len() { + source[i] = b'\n'; + } + } + code_ranges = ranges; + } else { + code_ranges = vec![]; + } + extractor::extract( + language, + "ruby", + &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: &PathBuf, + 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 scan_erb( + erb: Language, + source: &Vec, + directive_id: u16, + output_directive_id: u16, + code_id: u16, +) -> (Vec, Vec) { + let mut parser = Parser::new(); + parser.set_language(erb).unwrap(); + let tree = parser.parse(&source, None).expect("Failed to parse file"); + let mut result = Vec::new(); + let mut line_breaks = vec![]; + + for n in tree.root_node().children(&mut tree.walk()) { + let kind_id = n.kind_id(); + if kind_id == directive_id || kind_id == output_directive_id { + for c in n.children(&mut tree.walk()) { + if c.kind_id() == code_id { + let mut range = c.range(); + if range.end_byte < source.len() { + line_breaks.push(range.end_byte); + range.end_byte += 1; + range.end_point.column += 1; + } + result.push(range); + } + } + } + } + if result.len() == 0 { + let root = tree.root_node(); + // Add an empty range at the end of the file + result.push(Range { + start_byte: root.end_byte(), + end_byte: root.end_byte(), + start_point: root.end_position(), + end_point: root.end_position(), + }); + } + (result, line_breaks) +} + +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/ruby/generator/Cargo.toml b/ruby/generator/Cargo.toml new file mode 100644 index 000000000000..33fa1af7d145 --- /dev/null +++ b/ruby/generator/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ruby-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-embedded-template = "0.19" +tree-sitter-ruby = "0.19" diff --git a/ruby/generator/src/dbscheme.rs b/ruby/generator/src/dbscheme.rs new file mode 100644 index 000000000000..ec39fd993e07 --- /dev/null +++ b/ruby/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)?; + } + write!(f, "]\n")?; + } + + write!(f, "{}(\n", 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, ",")?; + } + write!(f, "\n")?; + } + 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/ruby/generator/src/language.rs b/ruby/generator/src/language.rs new file mode 100644 index 000000000000..f0b0ed1790f2 --- /dev/null +++ b/ruby/generator/src/language.rs @@ -0,0 +1,4 @@ +pub struct Language { + pub name: String, + pub node_types: &'static str, +} diff --git a/ruby/generator/src/main.rs b/ruby/generator/src/main.rs new file mode 100644 index 000000000000..13bf4f085280 --- /dev/null +++ b/ruby/generator/src/main.rs @@ -0,0 +1,672 @@ +mod dbscheme; +mod language; +mod ql; +mod ql_gen; + +use clap; +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::AtType(&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::AtType(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 { + 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::AtType(&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<'a>( + nodes: &'a node_types::NodeTypeMap, +) -> (Vec>, Set<&'a str>, Map<&'a 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 { + 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: &name, + columns: vec![dbscheme::Column { + db_type: dbscheme::DbColumnType::Int, + name: "id", + unique: true, + ql_type: ql::Type::AtType(&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::AtType("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::AtType(ast_node_name), + ql_type_is_ref: true, + }, + dbscheme::Column { + db_type: dbscheme::DbColumnType::Int, + name: "parent", + unique: false, + ql_type: ql::Type::AtType(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::AtType(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::AtType("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: name, + column: "kind", + branches: 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::AtType("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::AtType("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::AtType("location_default"), + ql_type_is_ref: false, + }, + dbscheme::Column { + unique: false, + db_type: dbscheme::DbColumnType::Int, + name: "file", + ql_type: ql::Type::AtType("file"), + ql_type_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::AtType("container"), + ql_type_is_ref: true, + }, + dbscheme::Column { + unique: true, + db_type: dbscheme::DbColumnType::Int, + name: "child", + ql_type: ql::Type::AtType("container"), + ql_type_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::AtType("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::AtType("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("Ruby dbscheme generator") + .version("1.0") + .author("GitHub") + .about("CodeQL Ruby 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: "Ruby".to_owned(), + node_types: tree_sitter_ruby::NODE_TYPES, + }, + Language { + name: "Erb".to_owned(), + node_types: tree_sitter_embedded_template::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/ruby/generator/src/ql.rs b/ruby/generator/src/ql.rs new file mode 100644 index 000000000000..b6b4f15f4ef3 --- /dev/null +++ b/ruby/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)?; + } + write!(f, " {{ \n")?; + + if let Some(charpred) = &self.characteristic_predicate { + write!( + f, + " {}\n", + Predicate { + qldoc: None, + name: self.name.clone(), + overridden: false, + return_type: None, + formal_parameters: vec![], + body: charpred.clone(), + } + )?; + } + + for predicate in &self.predicates { + write!(f, " {}\n", 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)?; + } + write!(f, "module {} {{ \n", self.name)?; + for decl in &self.body { + write!(f, " {}\n", 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. + AtType(&'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::AtType(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.len() > 0 { + 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/ruby/generator/src/ql_gen.rs b/ruby/generator/src/ql_gen.rs new file mode 100644 index 000000000000..a0f8b9c8b3e3 --- /dev/null +++ b/ruby/generator/src/ql_gen.rs @@ -0,0 +1,602 @@ +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::Pred("getAPrimaryQlClass", vec![])), + second_expr: Some(Box::new(ql::Expression::String(","))), + }), + ), + }; + ql::Class { + qldoc: Some(String::from("The base class for all AST nodes")), + name: "AstNode", + is_abstract: false, + supertypes: vec![ql::Type::AtType(ast_node)].into_iter().collect(), + 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::Pred("getValue", vec![])), + ), + }; + ql::Class { + qldoc: Some(String::from("A token.")), + name: "Token", + is_abstract: false, + supertypes: vec![ql::Type::AtType(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<'a>(db_name: &'a str) -> ql::Class<'a> { + 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::AtType(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: qldoc, + name: 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<'a>(class_name: &'a str) -> ql::Predicate<'a> { + 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<'a>(def_table: &'a str, arity: usize) -> ql::Predicate<'a> { + 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<'a>(def_table: &'a str) -> ql::Predicate<'a> { + 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.len() == 0 { + "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<'a>(nodes: &'a 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::AtType(&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::AtType(&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::AtType(&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/ruby/node-types/Cargo.toml b/ruby/node-types/Cargo.toml new file mode 100644 index 000000000000..c751d7360d60 --- /dev/null +++ b/ruby/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/ruby/node-types/src/lib.rs b/ruby/node-types/src/lib.rs new file mode 100644 index 000000000000..0f1de0156923 --- /dev/null +++ b/ruby/node-types/src/lib.rs @@ -0,0 +1,442 @@ +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 = 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 = 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: &Vec) -> Set { + let iter = node_types.iter().map(convert_type).collect(); + std::collections::BTreeSet::from(iter) +} + +pub fn convert_nodes(prefix: &str, nodes: &Vec) -> NodeTypeMap { + let mut entries = NodeTypeMap::new(); + let mut token_kinds = Set::new(); + + // First, find all the token kinds + for node in nodes { + if node.subtypes.is_none() { + if node.fields.as_ref().map_or(0, |x| x.len()) == 0 && node.children.is_none() { + let type_name = TypeName { + kind: node.kind.clone(), + named: node.named, + }; + token_kinds.insert(type_name); + } + } + } + + 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 counter = 0; + let mut field_token_ints: BTreeMap = BTreeMap::new(); + for t in converted_types { + 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)); + counter += 1; + } + 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: [&'static 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!("ruby", to_snake_case("Ruby")); + assert_eq!("erb", to_snake_case("ERB")); + assert_eq!("embedded_template", to_snake_case("EmbeddedTemplate")); +} diff --git a/ruby/ql/consistency-queries/AstConsistency.ql b/ruby/ql/consistency-queries/AstConsistency.ql new file mode 100644 index 000000000000..8a5ebcdcda7f --- /dev/null +++ b/ruby/ql/consistency-queries/AstConsistency.ql @@ -0,0 +1,25 @@ +import codeql.ruby.AST +import codeql.ruby.ast.internal.Synthesis + +query predicate missingParent(AstNode node, string cls) { + not exists(node.getParent()) and + node.getLocation().getFile().getExtension() != "erb" and + not node instanceof Toplevel and + cls = node.getPrimaryQlClasses() +} + +pragma[noinline] +private AstNode parent(AstNode child, int desugarLevel) { + result = child.getParent() and + desugarLevel = desugarLevel(result) +} + +query predicate multipleParents(AstNode node, AstNode parent, string cls) { + parent = node.getParent() and + cls = parent.getPrimaryQlClasses() and + exists(AstNode one, AstNode two, int desugarLevel | + one = parent(node, desugarLevel) and + two = parent(node, desugarLevel) and + one != two + ) +} diff --git a/ruby/ql/consistency-queries/CfgConsistency.ql b/ruby/ql/consistency-queries/CfgConsistency.ql new file mode 100644 index 000000000000..c2aaaad0ac10 --- /dev/null +++ b/ruby/ql/consistency-queries/CfgConsistency.ql @@ -0,0 +1 @@ +import codeql.ruby.controlflow.internal.ControlFlowGraphImplShared::Consistency diff --git a/ruby/ql/consistency-queries/DataFlowConsistency.ql b/ruby/ql/consistency-queries/DataFlowConsistency.ql new file mode 100644 index 000000000000..f5bc9552ab6c --- /dev/null +++ b/ruby/ql/consistency-queries/DataFlowConsistency.ql @@ -0,0 +1 @@ +import codeql.ruby.dataflow.internal.DataFlowImplConsistency::Consistency diff --git a/ruby/ql/consistency-queries/SsaConsistency.ql b/ruby/ql/consistency-queries/SsaConsistency.ql new file mode 100644 index 000000000000..79289273f958 --- /dev/null +++ b/ruby/ql/consistency-queries/SsaConsistency.ql @@ -0,0 +1,22 @@ +import ruby +import codeql.ruby.dataflow.SSA +import codeql.ruby.controlflow.ControlFlowGraph + +query predicate nonUniqueDef(CfgNode read, Ssa::Definition def) { + read = def.getARead() and + exists(Ssa::Definition other | read = other.getARead() and other != def) +} + +query predicate readWithoutDef(LocalVariableReadAccess read) { + exists(CfgNode node | + node = read.getAControlFlowNode() and + not node = any(Ssa::Definition def).getARead() + ) +} + +query predicate deadDef(Ssa::Definition def, LocalVariable v) { + v = def.getSourceVariable() and + not v.isCaptured() and + not exists(def.getARead()) and + not def = any(Ssa::PhiNode phi).getAnInput() +} diff --git a/ruby/ql/consistency-queries/VariablesConsistency.ql b/ruby/ql/consistency-queries/VariablesConsistency.ql new file mode 100644 index 000000000000..ed2183340d9c --- /dev/null +++ b/ruby/ql/consistency-queries/VariablesConsistency.ql @@ -0,0 +1,6 @@ +import codeql.ruby.ast.Variable + +query predicate ambiguousVariable(VariableAccess access, Variable variable) { + access.getVariable() = variable and + count(access.getVariable()) > 1 +} diff --git a/ruby/ql/consistency-queries/qlpack.yml b/ruby/ql/consistency-queries/qlpack.yml new file mode 100644 index 000000000000..fa76023b6461 --- /dev/null +++ b/ruby/ql/consistency-queries/qlpack.yml @@ -0,0 +1,5 @@ +name: codeql/ruby-consistency-queries +version: 0.0.1 +dependencies: + codeql/ruby-all: 0.0.1 + diff --git a/ruby/ql/docs/experimental.md b/ruby/ql/docs/experimental.md new file mode 100644 index 000000000000..2a86f2093530 --- /dev/null +++ b/ruby/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/ruby/ql/examples/qlpack.lock.yml b/ruby/ql/examples/qlpack.lock.yml new file mode 100644 index 000000000000..06dd07fc7dc7 --- /dev/null +++ b/ruby/ql/examples/qlpack.lock.yml @@ -0,0 +1,4 @@ +--- +dependencies: {} +compiled: false +lockVersion: 1.0.0 diff --git a/ruby/ql/examples/qlpack.yml b/ruby/ql/examples/qlpack.yml new file mode 100644 index 000000000000..87a6ffae9c18 --- /dev/null +++ b/ruby/ql/examples/qlpack.yml @@ -0,0 +1,4 @@ +name: codeql/ruby-examples +version: 0.0.2 +dependencies: + codeql/ruby-all: ^0.0.2 diff --git a/ruby/ql/examples/queries.xml b/ruby/ql/examples/queries.xml new file mode 100644 index 000000000000..a7ce9735ef42 --- /dev/null +++ b/ruby/ql/examples/queries.xml @@ -0,0 +1 @@ + diff --git a/ruby/ql/examples/snippets/emptythen.ql b/ruby/ql/examples/snippets/emptythen.ql new file mode 100644 index 000000000000..531556fc7fa5 --- /dev/null +++ b/ruby/ql/examples/snippets/emptythen.ql @@ -0,0 +1,18 @@ +/** + * @name If statements with empty then branch + * @description Finds 'if' statements where the 'then' branch is + * an empty block statement + * @id ruby/examples/emptythen + * @tags if + * then + * empty + * conditional + * branch + * statement + */ + +import ruby + +from IfExpr i +where not exists(i.getThen().getAChild()) +select i diff --git a/ruby/ql/lib/codeql/IDEContextual.qll b/ruby/ql/lib/codeql/IDEContextual.qll new file mode 100644 index 000000000000..0e58b1d878be --- /dev/null +++ b/ruby/ql/lib/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/ruby/ql/lib/codeql/Locations.qll b/ruby/ql/lib/codeql/Locations.qll new file mode 100644 index 000000000000..bd43633d49a3 --- /dev/null +++ b/ruby/ql/lib/codeql/Locations.qll @@ -0,0 +1,66 @@ +/** 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://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). + */ +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://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). + */ + 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() + ) + } + + /** Holds if this location starts strictly before the specified location. */ + pragma[inline] + predicate strictlyBefore(Location other) { + this.getStartLine() < other.getStartLine() + or + this.getStartLine() = other.getStartLine() and this.getStartColumn() < other.getStartColumn() + } +} + +/** An entity representing an empty location. */ +class EmptyLocation extends Location { + EmptyLocation() { this.hasLocationInfo("", 0, 0, 0, 0) } +} diff --git a/ruby/ql/lib/codeql/files/FileSystem.qll b/ruby/ql/lib/codeql/files/FileSystem.qll new file mode 100644 index 000000000000..e8b6a8ff6911 --- /dev/null +++ b/ruby/ql/lib/codeql/files/FileSystem.qll @@ -0,0 +1,173 @@ +/** Provides classes for working with files and folders. */ + +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 = getAChildContainer() } + + /** Gets a sub-folder in this container. */ + Folder getAFolder() { result = 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 = 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 = getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3) } + + /** Gets the file in this container that has the given `baseName`, if any. */ + File getFile(string baseName) { + result = getAFile() and + result.getBaseName() = baseName + } + + /** Gets the sub-folder in this container that has the given `baseName`, if any. */ + Folder getFolder(string baseName) { + result = getAFolder() and + result.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 = 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 = getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) } + + /** + * Gets a URL representing the location of this container. + * + * For more information see https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/#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 = 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://" + 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" } + + /** Holds if this file was extracted from ordinary source code. */ + predicate fromSource() { any() } +} diff --git a/ruby/ql/lib/codeql/ruby/AST.qll b/ruby/ql/lib/codeql/ruby/AST.qll new file mode 100644 index 000000000000..2d006b6312ae --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/AST.qll @@ -0,0 +1,141 @@ +import codeql.Locations +import ast.Call +import ast.Control +import ast.Constant +import ast.Erb +import ast.Expr +import ast.Literal +import ast.Method +import ast.Module +import ast.Parameter +import ast.Operation +import ast.Pattern +import ast.Scope +import ast.Statement +import ast.Variable +private import ast.internal.AST +private import ast.internal.Scope +private import ast.internal.Synthesis +private import ast.internal.TreeSitter + +/** + * A node in the abstract syntax tree. This class is the base class for all Ruby + * program elements. + */ +class AstNode extends TAstNode { + /** + * Gets the name of a primary CodeQL class to which this node belongs. + * + * This predicate always has a result. If no primary class can be + * determined, the result is `"???"`. If multiple primary classes match, + * this predicate can have multiple results. + */ + string getAPrimaryQlClass() { result = "???" } + + /** + * Gets a comma-separated list of the names of the primary CodeQL classes to + * which this element belongs. + */ + final string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") } + + /** Gets the enclosing module, if any. */ + ModuleBase getEnclosingModule() { + exists(Scope::Range s | + s = scopeOf(toGeneratedInclSynth(this)) and + toGeneratedInclSynth(result) = s.getEnclosingModule() + ) + } + + /** Gets the enclosing method, if any. */ + MethodBase getEnclosingMethod() { + exists(Scope::Range s | + s = scopeOf(toGeneratedInclSynth(this)) and + toGeneratedInclSynth(result) = s.getEnclosingMethod() + ) + } + + /** Gets a textual representation of this node. */ + cached + string toString() { none() } + + /** Gets the location of this node. */ + Location getLocation() { result = getLocation(this) } + + /** Gets the file of this node. */ + final File getFile() { result = this.getLocation().getFile() } + + /** Gets a child node of this `AstNode`. */ + final AstNode getAChild() { result = this.getAChild(_) } + + /** Gets the parent of this `AstNode`, if this node is not a root node. */ + final 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 = "getDesugared" and + result = this.getDesugared() + } + + /** + * Holds if this node was synthesized to represent an implicit AST node not + * present in the source code. In the following example method call, the + * receiver is an implicit `self` reference, for which there is a synthesized + * `Self` node. + * + * ```rb + * foo(123) + * ``` + */ + final predicate isSynthesized() { this = getSynthChild(_, _) } + + /** + * Gets the desugared version of this AST node, if any. + * + * For example, the desugared version of + * + * ```rb + * x += y + * ``` + * + * is + * + * ```rb + * x = x + y + * ``` + * + * when `x` is a variable. Whenever an AST node can be desugared, + * then the desugared version is used in the control-flow graph. + */ + final AstNode getDesugared() { result = getSynthChild(this, -1) } +} + +/** A Ruby source file */ +class RubyFile extends File { + RubyFile() { ruby_ast_node_parent(_, this, _) } + + /** Gets a token in this file. */ + private Ruby::Token getAToken() { result.getLocation().getFile() = this } + + /** Holds if `line` contains a token. */ + private predicate line(int line, boolean comment) { + exists(Ruby::Token token, Location l | + token = this.getAToken() and + l = token.getLocation() and + line in [l.getStartLine() .. l.getEndLine()] and + if token instanceof @ruby_token_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)) } +} diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll new file mode 100644 index 000000000000..4645879d55ae --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -0,0 +1,391 @@ +/** + * Provides an implementation of _API graphs_, which are an abstract representation of the API + * surface used and/or defined by a code base. + * + * The nodes of the API graph represent definitions and uses of API components. The edges are + * directed and labeled; they specify how the components represented by nodes relate to each other. + */ + +private import ruby +import codeql.ruby.DataFlow +import codeql.ruby.typetracking.TypeTracker +import codeql.ruby.ast.internal.Module +private import codeql.ruby.controlflow.CfgNodes + +/** + * Provides classes and predicates for working with APIs used in a database. + */ +module API { + /** + * An abstract representation of a definition or use of an API component such as a Ruby module, + * or the result of a method call. + */ + class Node extends Impl::TApiNode { + /** + * Gets a data-flow node corresponding to a use of the API component represented by this node. + * + * For example, `Kernel.format "%s world!", "Hello"` is a use of the return of the `format` function of + * the `Kernel` module. + * + * This includes indirect uses found via data flow. + */ + DataFlow::Node getAUse() { + exists(DataFlow::LocalSourceNode src | Impl::use(this, src) | + Impl::trackUseNode(src).flowsTo(result) + ) + } + + /** + * Gets an immediate use of the API component represented by this node. + * + * Unlike `getAUse()`, this predicate only gets the immediate references, not the indirect uses + * found via data flow. + */ + DataFlow::LocalSourceNode getAnImmediateUse() { Impl::use(this, result) } + + /** + * Gets a call to a method on the receiver represented by this API component. + */ + DataFlow::CallNode getAMethodCall(string method) { + result = getReturn(method).getAnImmediateUse() + } + + /** + * Gets a node representing member `m` of this API component. + * + * For example, a member can be: + * + * - A submodule of a module + * - An attribute of an object + */ + bindingset[m] + bindingset[result] + Node getMember(string m) { result = getASuccessor(Label::member(m)) } + + /** + * Gets a node representing a member of this API component where the name of the member is + * not known statically. + */ + Node getUnknownMember() { result = getASuccessor(Label::unknownMember()) } + + /** + * Gets a node representing a member of this API component where the name of the member may + * or may not be known statically. + */ + Node getAMember() { + result = getASuccessor(Label::member(_)) or + result = getUnknownMember() + } + + /** + * Gets a node representing an instance of this API component, that is, an object whose + * constructor is the function represented by this node. + * + * For example, if this node represents a use of some class `A`, then there might be a node + * representing instances of `A`, typically corresponding to expressions `new A()` at the + * source level. + * + * This predicate may have multiple results when there are multiple constructor calls invoking this API component. + * Consider using `getAnInstantiation()` if there is a need to distinguish between individual constructor calls. + */ + Node getInstance() { result = getASuccessor(Label::instance()) } + + /** + * Gets a node representing the result of calling a method on the receiver represented by this node. + */ + Node getReturn(string method) { result = getASuccessor(Label::return(method)) } + + /** + * Gets a `new` call to the function represented by this API component. + */ + DataFlow::Node getAnInstantiation() { result = getInstance().getAnImmediateUse() } + + /** + * Gets a node representing a subclass of the class represented by this node. + */ + Node getASubclass() { result = getASuccessor(Label::subclass()) } + + /** + * Gets a string representation of the lexicographically least among all shortest access paths + * from the root to this node. + */ + string getPath() { result = min(string p | p = getAPath(Impl::distanceFromRoot(this)) | p) } + + /** + * Gets a node such that there is an edge in the API graph between this node and the other + * one, and that edge is labeled with `lbl`. + */ + Node getASuccessor(string lbl) { Impl::edge(this, lbl, result) } + + /** + * Gets a node such that there is an edge in the API graph between that other node and + * this one, and that edge is labeled with `lbl` + */ + Node getAPredecessor(string lbl) { this = result.getASuccessor(lbl) } + + /** + * Gets a node such that there is an edge in the API graph between this node and the other + * one. + */ + Node getAPredecessor() { result = getAPredecessor(_) } + + /** + * Gets a node such that there is an edge in the API graph between that other node and + * this one. + */ + Node getASuccessor() { result = getASuccessor(_) } + + /** + * Gets the data-flow node that gives rise to this node, if any. + */ + DataFlow::Node getInducingNode() { this = Impl::MkUse(result) } + + /** Gets the location of this node. */ + Location getLocation() { + result = this.getInducingNode().getLocation() + or + // For nodes that do not have a meaningful location, `path` is the empty string and all other + // parameters are zero. + not exists(getInducingNode()) and + result instanceof EmptyLocation + } + + /** + * Gets a textual representation of this element. + */ + abstract string toString(); + + /** + * Gets a path of the given `length` from the root to this node. + */ + private string getAPath(int length) { + this instanceof Impl::MkRoot and + length = 0 and + result = "" + or + exists(Node pred, string lbl, string predpath | + Impl::edge(pred, lbl, this) and + lbl != "" and + predpath = pred.getAPath(length - 1) and + exists(string dot | if length = 1 then dot = "" else dot = "." | + result = predpath + dot + lbl and + // avoid producing strings longer than 1MB + result.length() < 1000 * 1000 + ) + ) and + length in [1 .. Impl::distanceFromRoot(this)] + } + + /** Gets the shortest distance from the root to this node in the API graph. */ + int getDepth() { result = Impl::distanceFromRoot(this) } + } + + /** The root node of an API graph. */ + class Root extends Node, Impl::MkRoot { + override string toString() { result = "root" } + } + + /** A node corresponding to the use of an API component. */ + class Use extends Node, Impl::MkUse { + override string toString() { + exists(string type | this = Impl::MkUse(_) and type = "Use " | + result = type + getPath() + or + not exists(this.getPath()) and result = type + "with no path" + ) + } + } + + /** Gets the root node. */ + Root root() { any() } + + /** + * Gets a node corresponding to a top-level member `m` (typically a module). + * + * This is equivalent to `root().getAMember("m")`. + * + * Note: You should only use this predicate for top level modules or classes. If you want nodes corresponding to a nested module or class, + * you should use `.getMember` on the parent module/class. For example, for nodes corresponding to the class `Gem::Version`, + * use `getTopLevelMember("Gem").getMember("Version")`. + */ + Node getTopLevelMember(string m) { result = root().getMember(m) } + + /** + * Provides the actual implementation of API graphs, cached for performance. + * + * Ideally, we'd like nodes to correspond to (global) access paths, with edge labels + * corresponding to extending the access path by one element. We also want to be able to map + * nodes to their definitions and uses in the data-flow graph, and this should happen modulo + * (inter-procedural) data flow. + * + * This, however, is not easy to implement, since access paths can have unbounded length + * and we need some way of recognizing cycles to avoid non-termination. Unfortunately, expressing + * a condition like "this node hasn't been involved in constructing any predecessor of + * this node in the API graph" without negative recursion is tricky. + * + * So instead most nodes are directly associated with a data-flow node, representing + * either a use or a definition of an API component. This ensures that we only have a finite + * number of nodes. However, we can now have multiple nodes with the same access + * path, which are essentially indistinguishable for a client of the API. + * + * On the other hand, a single node can have multiple access paths (which is, of + * course, unavoidable). We pick as canonical the alphabetically least access path with + * shortest length. + */ + cached + private module Impl { + cached + newtype TApiNode = + /** The root of the API graph. */ + MkRoot() or + /** A use of an API member at the node `nd`. */ + MkUse(DataFlow::Node nd) { use(_, _, nd) } + + private string resolveTopLevel(ConstantReadAccess read) { + TResolved(result) = resolveScopeExpr(read) and + not result.matches("%::%") + } + + /** + * Holds if `ref` is a use of a node that should have an incoming edge from `base` labeled + * `lbl` in the API graph. + */ + cached + predicate use(TApiNode base, string lbl, DataFlow::Node ref) { + base = MkRoot() and + exists(string name, ExprNodes::ConstantAccessCfgNode access, ConstantReadAccess read | + access = ref.asExpr() and + lbl = Label::member(read.getName()) and + read = access.getExpr() + | + name = resolveTopLevel(read) + or + name = read.getName() and + not exists(resolveTopLevel(read)) and + not exists(read.getScopeExpr()) + ) + or + exists(ExprCfgNode node | + // First, we find a predecessor of the node `ref` that we want to determine. The predecessor + // is any node that is a type-tracked use of a data flow node (`src`), which is itself a + // reference to the API node `base`. Thus, `pred` and `src` both represent uses of `base`. + // + // Once we have identified the predecessor, we define its relation to the successor `ref` as + // well as the label on the edge from `pred` to `ref`. This label describes the nature of + // the relationship between `pred` and `ref`. + useExpr(node, base) + | + // // Referring to an attribute on a node that is a use of `base`: + // pred = `Rails` part of `Rails::Whatever` + // lbl = `Whatever` + // ref = `Rails::Whatever` + exists(ExprNodes::ConstantAccessCfgNode c, ConstantReadAccess read | + not exists(resolveTopLevel(read)) and + node = c.getScopeExpr() and + lbl = Label::member(read.getName()) and + ref.asExpr() = c and + read = c.getExpr() + ) + or + // Calling a method on a node that is a use of `base` + exists(ExprNodes::MethodCallCfgNode call, string name | + node = call.getReceiver() and + name = call.getExpr().getMethodName() and + lbl = Label::return(name) and + name != "new" and + ref.asExpr() = call + ) + or + // Calling the `new` method on a node that is a use of `base`, which creates a new instance + exists(ExprNodes::MethodCallCfgNode call | + node = call.getReceiver() and + lbl = Label::instance() and + call.getExpr().getMethodName() = "new" and + ref.asExpr() = call + ) + ) + } + + pragma[nomagic] + private predicate useExpr(ExprCfgNode node, TApiNode base) { + exists(DataFlow::LocalSourceNode src, DataFlow::LocalSourceNode pred | + use(base, src) and + pred = trackUseNode(src) and + pred.flowsTo(any(DataFlow::ExprNode n | n.getExprNode() = node)) + ) + } + + /** + * Holds if `ref` is a use of node `nd`. + */ + cached + predicate use(TApiNode nd, DataFlow::Node ref) { nd = MkUse(ref) } + + /** + * Gets a data-flow node to which `src`, which is a use of an API-graph node, flows. + * + * The flow from `src` to that node may be inter-procedural. + */ + private DataFlow::LocalSourceNode trackUseNode(DataFlow::Node src, TypeTracker t) { + // Declaring `src` to be a `LocalSourceNode` currently causes a redundant check in the + // recursive case, so instead we check it explicitly here. + src instanceof DataFlow::LocalSourceNode and + t.start() and + use(_, src) and + result = src + or + exists(TypeTracker t2 | result = trackUseNode(src, t2).track(t2, t)) + } + + /** + * Gets a data-flow node to which `src`, which is a use of an API-graph node, flows. + * + * The flow from `src` to that node may be inter-procedural. + */ + cached + DataFlow::LocalSourceNode trackUseNode(DataFlow::LocalSourceNode src) { + result = trackUseNode(src, TypeTracker::end()) + } + + /** + * Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`. + */ + cached + predicate edge(TApiNode pred, string lbl, TApiNode succ) { + /* Every node that is a use of an API component is itself added to the API graph. */ + exists(DataFlow::LocalSourceNode ref | + use(pred, lbl, ref) and + succ = MkUse(ref) + ) + } + + /** + * Holds if there is an edge from `pred` to `succ` in the API graph. + */ + private predicate edge(TApiNode pred, TApiNode succ) { edge(pred, _, succ) } + + /** Gets the shortest distance from the root to `nd` in the API graph. */ + cached + int distanceFromRoot(TApiNode nd) = shortestDistances(MkRoot/0, edge/2)(_, nd, result) + } +} + +private module Label { + /** Gets the `member` edge label for member `m`. */ + bindingset[m] + bindingset[result] + string member(string m) { result = "getMember(\"" + m + "\")" } + + /** Gets the `member` edge label for the unknown member. */ + string unknownMember() { result = "getUnknownMember()" } + + /** Gets the `instance` edge label. */ + string instance() { result = "instance" } + + /** Gets the `return` edge label. */ + bindingset[m] + bindingset[result] + string return(string m) { result = "getReturn(\"" + m + "\")" } + + string subclass() { result = "getASubclass()" } +} diff --git a/ruby/ql/lib/codeql/ruby/CFG.qll b/ruby/ql/lib/codeql/ruby/CFG.qll new file mode 100644 index 000000000000..77507b05a7f1 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/CFG.qll @@ -0,0 +1,5 @@ +/** Provides classes representing the control flow graph. */ + +import controlflow.ControlFlowGraph +import controlflow.CfgNodes as CfgNodes +import controlflow.BasicBlocks diff --git a/ruby/ql/lib/codeql/ruby/Concepts.qll b/ruby/ql/lib/codeql/ruby/Concepts.qll new file mode 100644 index 000000000000..79483cb0d0f5 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/Concepts.qll @@ -0,0 +1,505 @@ +/** + * Provides abstract classes representing generic concepts such as file system + * access or system command execution, for which individual framework libraries + * provide concrete subclasses. + */ + +private import codeql.ruby.AST +private import codeql.ruby.CFG +private import codeql.ruby.DataFlow +private import codeql.ruby.Frameworks +private import codeql.ruby.dataflow.RemoteFlowSources +private import codeql.ruby.ApiGraphs + +/** + * A data-flow node that executes SQL statements. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `SqlExecution::Range` instead. + */ +class SqlExecution extends DataFlow::Node instanceof SqlExecution::Range { + /** Gets the argument that specifies the SQL statements to be executed. */ + DataFlow::Node getSql() { result = super.getSql() } +} + +/** Provides a class for modeling new SQL execution APIs. */ +module SqlExecution { + /** + * A data-flow node that executes SQL statements. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `SqlExecution` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets the argument that specifies the SQL statements to be executed. */ + abstract DataFlow::Node getSql(); + } +} + +/** + * A data flow node that performs a file system access, including reading and writing data, + * creating and deleting files and folders, checking and updating permissions, and so on. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `FileSystemAccess::Range` instead. + */ +class FileSystemAccess extends DataFlow::Node instanceof FileSystemAccess::Range { + /** Gets an argument to this file system access that is interpreted as a path. */ + DataFlow::Node getAPathArgument() { result = super.getAPathArgument() } +} + +/** Provides a class for modeling new file system access APIs. */ +module FileSystemAccess { + /** + * A data-flow node that performs a file system access, including reading and writing data, + * creating and deleting files and folders, checking and updating permissions, and so on. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `FileSystemAccess` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets an argument to this file system access that is interpreted as a path. */ + abstract DataFlow::Node getAPathArgument(); + } +} + +/** + * A data flow node that reads data from the file system. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `FileSystemReadAccess::Range` instead. + */ +class FileSystemReadAccess extends FileSystemAccess instanceof FileSystemReadAccess::Range { + /** + * Gets a node that represents data read from the file system access. + */ + DataFlow::Node getADataNode() { result = FileSystemReadAccess::Range.super.getADataNode() } +} + +/** Provides a class for modeling new file system reads. */ +module FileSystemReadAccess { + /** + * A data flow node that reads data from the file system. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `FileSystemReadAccess` instead. + */ + abstract class Range extends FileSystemAccess::Range { + /** + * Gets a node that represents data read from the file system. + */ + abstract DataFlow::Node getADataNode(); + } +} + +/** + * A data flow node that sets the permissions for one or more files. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `FileSystemPermissionModification::Range` instead. + */ +class FileSystemPermissionModification extends DataFlow::Node instanceof FileSystemPermissionModification::Range { + /** + * Gets an argument to this permission modification that is interpreted as a + * set of permissions. + */ + DataFlow::Node getAPermissionNode() { result = super.getAPermissionNode() } +} + +/** Provides a class for modeling new file system permission modifications. */ +module FileSystemPermissionModification { + /** + * A data-flow node that sets permissions for a one or more files. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `FileSystemPermissionModification` instead. + */ + abstract class Range extends DataFlow::Node { + /** + * Gets an argument to this permission modification that is interpreted as a + * set of permissions. + */ + abstract DataFlow::Node getAPermissionNode(); + } +} + +/** + * A data flow node that contains a file name or an array of file names from the local file system. + */ +abstract class FileNameSource extends DataFlow::Node { } + +/** + * A data-flow node that escapes meta-characters, which could be used to prevent + * injection attacks. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `Escaping::Range` instead. + */ +class Escaping extends DataFlow::Node instanceof Escaping::Range { + Escaping() { + // escapes that don't have _both_ input/output defined are not valid + exists(super.getAnInput()) and + exists(super.getOutput()) + } + + /** Gets an input that will be escaped. */ + DataFlow::Node getAnInput() { result = super.getAnInput() } + + /** Gets the output that contains the escaped data. */ + DataFlow::Node getOutput() { result = super.getOutput() } + + /** + * Gets the context that this function escapes for, such as `html`, or `url`. + */ + string getKind() { result = super.getKind() } +} + +/** Provides a class for modeling new escaping APIs. */ +module Escaping { + /** + * A data-flow node that escapes meta-characters, which could be used to prevent + * injection attacks. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `Escaping` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets an input that will be escaped. */ + abstract DataFlow::Node getAnInput(); + + /** Gets the output that contains the escaped data. */ + abstract DataFlow::Node getOutput(); + + /** + * Gets the context that this function escapes for. + * + * While kinds are represented as strings, this should not be relied upon. Use the + * predicates in the `Escaping` module, such as `getHtmlKind`. + */ + abstract string getKind(); + } + + /** Gets the escape-kind for escaping a string so it can safely be included in HTML. */ + string getHtmlKind() { result = "html" } +} + +/** + * An escape of a string so it can be safely included in + * the body of an HTML element, for example, replacing `{}` in + * `

{}

`. + */ +class HtmlEscaping extends Escaping { + HtmlEscaping() { super.getKind() = Escaping::getHtmlKind() } +} + +/** Provides classes for modeling HTTP-related APIs. */ +module HTTP { + /** Provides classes for modeling HTTP servers. */ + module Server { + /** + * A data-flow node that sets up a route on a server. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `RouteSetup::Range` instead. + */ + class RouteSetup extends DataFlow::Node instanceof RouteSetup::Range { + /** Gets the URL pattern for this route, if it can be statically determined. */ + string getUrlPattern() { result = super.getUrlPattern() } + + /** + * Gets a function that will handle incoming requests for this route, if any. + * + * NOTE: This will be modified in the near future to have a `RequestHandler` result, instead of a `Method`. + */ + Method getARequestHandler() { result = super.getARequestHandler() } + + /** + * Gets a parameter that will receive parts of the url when handling incoming + * requests for this route, if any. These automatically become a `RemoteFlowSource`. + */ + Parameter getARoutedParameter() { result = super.getARoutedParameter() } + + /** Gets a string that identifies the framework used for this route setup. */ + string getFramework() { result = super.getFramework() } + } + + /** Provides a class for modeling new HTTP routing APIs. */ + module RouteSetup { + /** + * A data-flow node that sets up a route on a server. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `RouteSetup` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets the argument used to set the URL pattern. */ + abstract DataFlow::Node getUrlPatternArg(); + + /** Gets the URL pattern for this route, if it can be statically determined. */ + string getUrlPattern() { + exists(CfgNodes::ExprNodes::StringlikeLiteralCfgNode strNode | + this.getUrlPatternArg().getALocalSource() = DataFlow::exprNode(strNode) and + result = strNode.getExpr().getValueText() + ) + } + + /** + * Gets a function that will handle incoming requests for this route, if any. + * + * NOTE: This will be modified in the near future to have a `RequestHandler` result, instead of a `Method`. + */ + abstract Method getARequestHandler(); + + /** + * Gets a parameter that will receive parts of the url when handling incoming + * requests for this route, if any. These automatically become a `RemoteFlowSource`. + */ + abstract Parameter getARoutedParameter(); + + /** Gets a string that identifies the framework used for this route setup. */ + abstract string getFramework(); + } + } + + /** + * A function that will handle incoming HTTP requests. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `RequestHandler::Range` instead. + */ + class RequestHandler extends Method instanceof RequestHandler::Range { + /** + * Gets a parameter that could receive parts of the url when handling incoming + * requests, if any. These automatically become a `RemoteFlowSource`. + */ + Parameter getARoutedParameter() { result = super.getARoutedParameter() } + + /** Gets a string that identifies the framework used for this route setup. */ + string getFramework() { result = super.getFramework() } + } + + /** Provides a class for modeling new HTTP request handlers. */ + module RequestHandler { + /** + * A function that will handle incoming HTTP requests. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `RequestHandler` instead. + * + * Only extend this class if you can't provide a `RouteSetup`, since we handle that case automatically. + */ + abstract class Range extends Method { + /** + * Gets a parameter that could receive parts of the url when handling incoming + * requests, if any. These automatically become a `RemoteFlowSource`. + */ + abstract Parameter getARoutedParameter(); + + /** Gets a string that identifies the framework used for this request handler. */ + abstract string getFramework(); + } + } + + private class RequestHandlerFromRouteSetup extends RequestHandler::Range { + RouteSetup rs; + + RequestHandlerFromRouteSetup() { this = rs.getARequestHandler() } + + override Parameter getARoutedParameter() { + result = rs.getARoutedParameter() and + result = this.getAParameter() + } + + override string getFramework() { result = rs.getFramework() } + } + + /** A parameter that will receive parts of the url when handling an incoming request. */ + private class RoutedParameter extends RemoteFlowSource::Range, DataFlow::ParameterNode { + RequestHandler handler; + + RoutedParameter() { this.getParameter() = handler.getARoutedParameter() } + + override string getSourceType() { result = handler.getFramework() + " RoutedParameter" } + } + + /** + * A data-flow node that creates a HTTP response on a server. + * + * Note: we don't require that this response must be sent to a client (a kind of + * "if a tree falls in a forest and nobody hears it" situation). + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `HttpResponse::Range` instead. + */ + class HttpResponse extends DataFlow::Node instanceof HttpResponse::Range { + /** Gets the data-flow node that specifies the body of this HTTP response. */ + DataFlow::Node getBody() { result = super.getBody() } + + /** Gets the mimetype of this HTTP response, if it can be statically determined. */ + string getMimetype() { result = super.getMimetype() } + } + + /** Provides a class for modeling new HTTP response APIs. */ + module HttpResponse { + /** + * A data-flow node that creates a HTTP response on a server. + * + * Note: we don't require that this response must be sent to a client (a kind of + * "if a tree falls in a forest and nobody hears it" situation). + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `HttpResponse` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets the data-flow node that specifies the body of this HTTP response. */ + abstract DataFlow::Node getBody(); + + /** Gets the data-flow node that specifies the content-type/mimetype of this HTTP response, if any. */ + abstract DataFlow::Node getMimetypeOrContentTypeArg(); + + /** Gets the default mimetype that should be used if `getMimetypeOrContentTypeArg` has no results. */ + abstract string getMimetypeDefault(); + + /** Gets the mimetype of this HTTP response, if it can be statically determined. */ + string getMimetype() { + exists(CfgNodes::ExprNodes::StringlikeLiteralCfgNode strNode | + this.getMimetypeOrContentTypeArg().getALocalSource() = DataFlow::exprNode(strNode) and + result = strNode.getExpr().getValueText().splitAt(";", 0) + ) + or + not exists(this.getMimetypeOrContentTypeArg()) and + result = this.getMimetypeDefault() + } + } + } + + /** + * A data-flow node that creates a HTTP redirect response on a server. + * + * Note: we don't require that this redirect must be sent to a client (a kind of + * "if a tree falls in a forest and nobody hears it" situation). + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `HttpRedirectResponse::Range` instead. + */ + class HttpRedirectResponse extends HttpResponse instanceof HttpRedirectResponse::Range { + /** Gets the data-flow node that specifies the location of this HTTP redirect response. */ + DataFlow::Node getRedirectLocation() { result = super.getRedirectLocation() } + } + + /** Provides a class for modeling new HTTP redirect response APIs. */ + module HttpRedirectResponse { + /** + * A data-flow node that creates a HTTP redirect response on a server. + * + * Note: we don't require that this redirect must be sent to a client (a kind of + * "if a tree falls in a forest and nobody hears it" situation). + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `HttpResponse` instead. + */ + abstract class Range extends HTTP::Server::HttpResponse::Range { + /** Gets the data-flow node that specifies the location of this HTTP redirect response. */ + abstract DataFlow::Node getRedirectLocation(); + } + } + } + + /** Provides classes for modeling HTTP clients. */ + module Client { + /** + * A method call that makes an outgoing HTTP request. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `Request::Range` instead. + */ + class Request extends MethodCall instanceof Request::Range { + /** Gets a node which returns the body of the response */ + DataFlow::Node getResponseBody() { result = super.getResponseBody() } + + /** Gets a string that identifies the framework used for this request. */ + string getFramework() { result = super.getFramework() } + } + + /** Provides a class for modeling new HTTP requests. */ + module Request { + /** + * A method call that makes an outgoing HTTP request. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `Request` instead. + */ + abstract class Range extends MethodCall { + /** Gets a node which returns the body of the response */ + abstract DataFlow::Node getResponseBody(); + + /** Gets a string that identifies the framework used for this request. */ + abstract string getFramework(); + } + } + + /** The response body from an outgoing HTTP request, considered as a remote flow source */ + private class RequestResponseBody extends RemoteFlowSource::Range, DataFlow::Node { + Request request; + + RequestResponseBody() { this = request.getResponseBody() } + + override string getSourceType() { result = request.getFramework() } + } + } +} + +/** + * A data flow node that executes an operating system command, + * for instance by spawning a new process. + */ +class SystemCommandExecution extends DataFlow::Node instanceof SystemCommandExecution::Range { + /** Holds if a shell interprets `arg`. */ + predicate isShellInterpreted(DataFlow::Node arg) { super.isShellInterpreted(arg) } + + /** Gets an argument to this execution that specifies the command or an argument to it. */ + DataFlow::Node getAnArgument() { result = super.getAnArgument() } +} + +/** Provides a class for modeling new operating system command APIs. */ +module SystemCommandExecution { + /** + * A data flow node that executes an operating system command, for instance by spawning a new + * process. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `SystemCommandExecution` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets an argument to this execution that specifies the command or an argument to it. */ + abstract DataFlow::Node getAnArgument(); + + /** Holds if a shell interprets `arg`. */ + predicate isShellInterpreted(DataFlow::Node arg) { none() } + } +} + +/** + * A data-flow node that dynamically executes Ruby code. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `CodeExecution::Range` instead. + */ +class CodeExecution extends DataFlow::Node instanceof CodeExecution::Range { + /** Gets the argument that specifies the code to be executed. */ + DataFlow::Node getCode() { result = super.getCode() } +} + +/** Provides a class for modeling new dynamic code execution APIs. */ +module CodeExecution { + /** + * A data-flow node that dynamically executes Ruby code. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `CodeExecution` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets the argument that specifies the code to be executed. */ + abstract DataFlow::Node getCode(); + } +} diff --git a/ruby/ql/lib/codeql/ruby/DataFlow.qll b/ruby/ql/lib/codeql/ruby/DataFlow.qll new file mode 100644 index 000000000000..e7645ce0c109 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/DataFlow.qll @@ -0,0 +1,7 @@ +/** + * Provides classes for performing local (intra-procedural) and + * global (inter-procedural) data flow analyses. + */ +module DataFlow { + import codeql.ruby.dataflow.internal.DataFlowImpl +} diff --git a/ruby/ql/lib/codeql/ruby/Diagnostics.qll b/ruby/ql/lib/codeql/ruby/Diagnostics.qll new file mode 100644 index 000000000000..b8995c01bc27 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/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/ruby/ql/lib/codeql/ruby/Frameworks.qll b/ruby/ql/lib/codeql/ruby/Frameworks.qll new file mode 100644 index 000000000000..8ce52df8458a --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/Frameworks.qll @@ -0,0 +1,10 @@ +/** + * Helper file that imports all framework modeling. + */ + +private import codeql.ruby.frameworks.ActionController +private import codeql.ruby.frameworks.ActiveRecord +private import codeql.ruby.frameworks.ActionView +private import codeql.ruby.frameworks.StandardLibrary +private import codeql.ruby.frameworks.Files +private import codeql.ruby.frameworks.HTTPClients diff --git a/ruby/ql/lib/codeql/ruby/TaintTracking.qll b/ruby/ql/lib/codeql/ruby/TaintTracking.qll new file mode 100755 index 000000000000..e443b2942731 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/TaintTracking.qll @@ -0,0 +1,7 @@ +/** + * Provides classes for performing local (intra-procedural) and + * global (inter-procedural) taint-tracking analyses. + */ +module TaintTracking { + import codeql.ruby.dataflow.internal.tainttracking1.TaintTrackingImpl +} diff --git a/ruby/ql/lib/codeql/ruby/ast/Call.qll b/ruby/ql/lib/codeql/ruby/ast/Call.qll new file mode 100644 index 000000000000..8f98cf0574ce --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/Call.qll @@ -0,0 +1,199 @@ +private import codeql.ruby.AST +private import internal.AST +private import internal.Call +private import internal.TreeSitter +private import codeql.ruby.dataflow.internal.DataFlowDispatch +private import codeql.ruby.dataflow.internal.DataFlowImplCommon + +/** + * A call. + */ +class Call extends Expr instanceof CallImpl { + override string getAPrimaryQlClass() { result = "Call" } + + /** + * Gets the `n`th argument of this method call. In the following example, the + * result for n=0 is the `IntegerLiteral` 0, while for n=1 the result is a + * `Pair` (whose `getKey` returns the `SymbolLiteral` for `bar`, and + * `getValue` returns the `IntegerLiteral` 1). Keyword arguments like this + * can be accessed more naturally using the + * `getKeywordArgument(string keyword)` predicate. + * ```rb + * foo(0, bar: 1) + * yield 0, bar: 1 + * ``` + */ + final Expr getArgument(int n) { result = super.getArgumentImpl(n) } + + /** + * Gets an argument of this method call. + */ + final Expr getAnArgument() { result = this.getArgument(_) } + + /** + * Gets the value of the keyword argument whose key is `keyword`, if any. For + * example, the result for `getKeywordArgument("qux")` in the following + * example is the `IntegerLiteral` 123. + * ```rb + * foo :bar "baz", qux: 123 + * ``` + */ + final Expr getKeywordArgument(string keyword) { + exists(Pair p | + p = this.getAnArgument() and + p.getKey().(SymbolLiteral).getValueText() = keyword and + result = p.getValue() + ) + } + + /** + * Gets the number of arguments of this method call. + */ + final int getNumberOfArguments() { result = super.getNumberOfArgumentsImpl() } + + /** Gets a potential target of this call, if any. */ + final Callable getATarget() { + exists(DataFlowCall c | this = c.asCall().getExpr() | + TCfgScope(result) = [viableCallable(c), viableCallableLambda(c, _)] + ) + } + + override AstNode getAChild(string pred) { + result = Expr.super.getAChild(pred) + or + pred = "getArgument" and result = this.getArgument(_) + } +} + +/** + * A method call. + */ +class MethodCall extends Call instanceof MethodCallImpl { + override string getAPrimaryQlClass() { result = "MethodCall" } + + /** + * Gets the receiver of this call, if any. For example: + * + * ```rb + * foo.bar + * Baz::qux + * corge() + * ``` + * + * The result for the call to `bar` is the `Expr` for `foo`; the result for + * the call to `qux` is the `Expr` for `Baz`; for the call to `corge` there + * is no result. + */ + final Expr getReceiver() { result = super.getReceiverImpl() } + + /** + * Gets the name of the method being called. For example, in: + * + * ```rb + * foo.bar x, y + * ``` + * + * the result is `"bar"`. + */ + final string getMethodName() { result = super.getMethodNameImpl() } + + /** + * Gets the block of this method call, if any. + * ```rb + * foo.each { |x| puts x } + * ``` + */ + final Block getBlock() { result = super.getBlockImpl() } + + override string toString() { result = "call to " + this.getMethodName() } + + override AstNode getAChild(string pred) { + result = Call.super.getAChild(pred) + or + pred = "getReceiver" and result = this.getReceiver() + or + pred = "getBlock" and result = this.getBlock() + } +} + +/** + * A call to a setter method. + * ```rb + * self.foo = 10 + * a[0] = 10 + * ``` + */ +class SetterMethodCall extends MethodCall, TMethodCallSynth { + SetterMethodCall() { this = TMethodCallSynth(_, _, _, true, _) } + + final override string getAPrimaryQlClass() { result = "SetterMethodCall" } +} + +/** + * An element reference; a call to the `[]` method. + * ```rb + * a[0] + * ``` + */ +class ElementReference extends MethodCall instanceof ElementReferenceImpl { + final override string getAPrimaryQlClass() { result = "ElementReference" } + + final override string toString() { result = "...[...]" } +} + +/** + * A call to `yield`. + * ```rb + * yield x, y + * ``` + */ +class YieldCall extends Call instanceof YieldCallImpl { + final override string getAPrimaryQlClass() { result = "YieldCall" } + + final override string toString() { result = "yield ..." } +} + +/** + * A call to `super`. + * ```rb + * class Foo < Bar + * def baz + * super + * end + * end + * ``` + */ +class SuperCall extends MethodCall instanceof SuperCallImpl { + final override string getAPrimaryQlClass() { result = "SuperCall" } +} + +/** + * A block argument in a method call. + * ```rb + * foo(&block) + * ``` + */ +class BlockArgument extends Expr, TBlockArgument { + private Ruby::BlockArgument g; + + BlockArgument() { this = TBlockArgument(g) } + + final override string getAPrimaryQlClass() { result = "BlockArgument" } + + /** + * Gets the underlying expression representing the block. In the following + * example, the result is the `Expr` for `bar`: + * ```rb + * foo(&bar) + * ``` + */ + final Expr getValue() { toGenerated(result) = g.getChild() } + + final override string toString() { result = "&..." } + + final override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getValue" and result = this.getValue() + } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/Constant.qll b/ruby/ql/lib/codeql/ruby/ast/Constant.qll new file mode 100644 index 000000000000..268ed20f1516 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/Constant.qll @@ -0,0 +1,179 @@ +private import codeql.ruby.AST +private import internal.AST +private import internal.Module +private import internal.Variable +private import internal.TreeSitter + +/** An access to a constant. */ +class ConstantAccess extends Expr, TConstantAccess { + /** Gets the name of the constant being accessed. */ + string getName() { none() } + + /** Holds if the name of the constant being accessed is `name`. */ + final predicate hasName(string name) { this.getName() = name } + + /** + * Gets the expression used in the access's scope resolution operation, if + * any. In the following example, the result is the `Call` expression for + * `foo()`. + * + * ```rb + * foo()::MESSAGE + * ``` + * + * However, there is no result for the following example, since there is no + * scope resolution operation. + * + * ```rb + * MESSAGE + * ``` + */ + Expr getScopeExpr() { none() } + + /** + * Holds if the access uses the scope resolution operator to refer to the + * global scope, as in this example: + * + * ```rb + * ::MESSAGE + * ``` + */ + predicate hasGlobalScope() { none() } + + override string toString() { result = this.getName() } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getScopeExpr" and result = this.getScopeExpr() + } +} + +private class TokenConstantAccess extends ConstantAccess, TTokenConstantAccess { + private Ruby::Constant g; + + TokenConstantAccess() { this = TTokenConstantAccess(g) } + + final override string getName() { result = g.getValue() } +} + +private class ScopeResolutionConstantAccess extends ConstantAccess, TScopeResolutionConstantAccess { + private Ruby::ScopeResolution g; + private Ruby::Constant constant; + + ScopeResolutionConstantAccess() { this = TScopeResolutionConstantAccess(g, constant) } + + final override string getName() { result = constant.getValue() } + + final override Expr getScopeExpr() { toGenerated(result) = g.getScope() } + + final override predicate hasGlobalScope() { not exists(g.getScope()) } +} + +private class ConstantReadAccessSynth extends ConstantAccess, TConstantReadAccessSynth { + private string value; + + ConstantReadAccessSynth() { this = TConstantReadAccessSynth(_, _, value) } + + final override string getName() { + if this.hasGlobalScope() then result = value.suffix(2) else result = value + } + + final override Expr getScopeExpr() { synthChild(this, 0, result) } + + final override predicate hasGlobalScope() { value.matches("::%") } +} + +/** + * A use (read) of a constant. + * + * For example, the right-hand side of the assignment in: + * + * ```rb + * x = Foo + * ``` + * + * Or the superclass `Bar` in this example: + * + * ```rb + * class Foo < Bar + * end + * ``` + */ +class ConstantReadAccess extends ConstantAccess { + ConstantReadAccess() { + not this instanceof ConstantWriteAccess + or + // `X` in `X ||= 10` is considered both a read and a write + this = any(AssignOperation a).getLeftOperand() + or + this instanceof TConstantReadAccessSynth + } + + /** + * Gets the value being read, if any. For example, in + * + * ```rb + * module M + * CONST = "const" + * end + * + * puts M::CONST + * ``` + * + * the value being read at `M::CONST` is `"const"`. + */ + Expr getValue() { + not exists(this.getScopeExpr()) and + result = lookupConst(this.getEnclosingModule+().getModule(), this.getName()) and + // For now, we restrict the scope of top-level declarations to their file. + // This may remove some plausible targets, but also removes a lot of + // implausible targets + if result.getEnclosingModule() instanceof Toplevel + then result.getFile() = this.getFile() + else any() + or + this.hasGlobalScope() and + result = lookupConst(TResolved("Object"), this.getName()) + or + result = lookupConst(resolveScopeExpr(this.getScopeExpr()), this.getName()) + } + + final override string getAPrimaryQlClass() { result = "ConstantReadAccess" } +} + +/** + * A definition of a constant. + * + * Examples: + * + * ```rb + * Foo = 1 # defines constant Foo as an integer + * M::Foo = 1 # defines constant Foo as an integer in module M + * + * class Bar; end # defines constant Bar as a class + * class M::Bar; end # defines constant Bar as a class in module M + * + * module Baz; end # defines constant Baz as a module + * module M::Baz; end # defines constant Baz as a module in module M + * ``` + */ +class ConstantWriteAccess extends ConstantAccess { + ConstantWriteAccess() { + explicitAssignmentNode(toGenerated(this), _) or this instanceof TNamespace + } + + override string getAPrimaryQlClass() { result = "ConstantWriteAccess" } +} + +/** + * A definition of a constant via assignment. For example, the left-hand + * operand in the following example: + * + * ```rb + * MAX_SIZE = 100 + * ``` + */ +class ConstantAssignment extends ConstantWriteAccess, LhsExpr { + override string getAPrimaryQlClass() { result = "ConstantAssignment" } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/Control.qll b/ruby/ql/lib/codeql/ruby/ast/Control.qll new file mode 100644 index 000000000000..33f52c024133 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/Control.qll @@ -0,0 +1,611 @@ +private import codeql.ruby.AST +private import internal.AST +private import internal.TreeSitter + +/** + * A control expression that can be any of the following: + * - `case` + * - `if`/`unless` (including expression-modifier variants) + * - ternary-if (`?:`) + * - `while`/`until` (including expression-modifier variants) + * - `for` + */ +class ControlExpr extends Expr, TControlExpr { } + +/** + * A conditional expression: `if`/`unless` (including expression-modifier + * variants), and ternary-if (`?:`) expressions. + */ +class ConditionalExpr extends ControlExpr, TConditionalExpr { + /** + * Gets the condition expression. For example, the result is `foo` in the + * following: + * ```rb + * if foo + * bar = 1 + * end + * ``` + */ + Expr getCondition() { none() } + + /** + * Gets the branch of this conditional expression that is taken when the + * condition evaluates to `cond`, if any. + */ + Stmt getBranch(boolean cond) { none() } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getCondition" and result = this.getCondition() + or + pred = "getBranch" and result = this.getBranch(_) + } +} + +/** + * An `if` or `elsif` expression. + * ```rb + * if x + * a += 1 + * elsif y + * a += 2 + * end + * ``` + */ +class IfExpr extends ConditionalExpr, TIfExpr { + final override string getAPrimaryQlClass() { result = "IfExpr" } + + /** Holds if this is an `elsif` expression. */ + predicate isElsif() { none() } + + /** Gets the 'then' branch of this `if`/`elsif` expression. */ + Stmt getThen() { none() } + + /** + * Gets the `elsif`/`else` branch of this `if`/`elsif` expression, if any. In + * the following example, the result is a `StmtSequence` containing `b`. + * ```rb + * if foo + * a + * else + * b + * end + * ``` + * But there is no result for the following: + * ```rb + * if foo + * a + * end + * ``` + * There can be at most one result, since `elsif` branches nest. In the + * following example, `ifExpr.getElse()` returns an `ElsifExpr`, and the + * `else` branch is nested inside that. To get the `StmtSequence` for the + * `else` branch, i.e. the one containing `c`, use + * `getElse().(ElsifExpr).getElse()`. + * ```rb + * if foo + * a + * elsif bar + * b + * else + * c + * end + * ``` + */ + Stmt getElse() { none() } + + final override Stmt getBranch(boolean cond) { + cond = true and result = this.getThen() + or + cond = false and result = this.getElse() + } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getThen" and result = this.getThen() + or + pred = "getElse" and result = this.getElse() + } +} + +private class If extends IfExpr, TIf { + private Ruby::If g; + + If() { this = TIf(g) } + + final override Expr getCondition() { toGenerated(result) = g.getCondition() } + + final override Stmt getThen() { toGenerated(result) = g.getConsequence() } + + final override Stmt getElse() { toGenerated(result) = g.getAlternative() } + + final override string toString() { result = "if ..." } +} + +private class Elsif extends IfExpr, TElsif { + private Ruby::Elsif g; + + Elsif() { this = TElsif(g) } + + final override predicate isElsif() { any() } + + final override Expr getCondition() { toGenerated(result) = g.getCondition() } + + final override Stmt getThen() { toGenerated(result) = g.getConsequence() } + + final override Stmt getElse() { toGenerated(result) = g.getAlternative() } + + final override string toString() { result = "elsif ..." } +} + +/** + * An `unless` expression. + * ```rb + * unless x == 0 + * y /= x + * end + * ``` + */ +class UnlessExpr extends ConditionalExpr, TUnlessExpr { + private Ruby::Unless g; + + UnlessExpr() { this = TUnlessExpr(g) } + + final override string getAPrimaryQlClass() { result = "UnlessExpr" } + + final override Expr getCondition() { toGenerated(result) = g.getCondition() } + + /** + * Gets the 'then' branch of this `unless` expression. In the following + * example, the result is the `StmtSequence` containing `foo`. + * ```rb + * unless a == b then + * foo + * else + * bar + * end + * ``` + */ + final Stmt getThen() { toGenerated(result) = g.getConsequence() } + + /** + * Gets the 'else' branch of this `unless` expression. In the following + * example, the result is the `StmtSequence` containing `bar`. + * ```rb + * unless a == b then + * foo + * else + * bar + * end + * ``` + */ + final Stmt getElse() { toGenerated(result) = g.getAlternative() } + + final override Expr getBranch(boolean cond) { + cond = false and result = getThen() + or + cond = true and result = getElse() + } + + final override string toString() { result = "unless ..." } + + override AstNode getAChild(string pred) { + result = ConditionalExpr.super.getAChild(pred) + or + pred = "getThen" and result = this.getThen() + or + pred = "getElse" and result = this.getElse() + } +} + +/** + * An expression modified using `if`. + * ```rb + * foo if bar + * ``` + */ +class IfModifierExpr extends ConditionalExpr, TIfModifierExpr { + private Ruby::IfModifier g; + + IfModifierExpr() { this = TIfModifierExpr(g) } + + final override string getAPrimaryQlClass() { result = "IfModifierExpr" } + + final override Expr getCondition() { toGenerated(result) = g.getCondition() } + + final override Stmt getBranch(boolean cond) { cond = true and result = this.getBody() } + + /** + * Gets the statement that is conditionally evaluated. In the following + * example, the result is the `Expr` for `foo`. + * ```rb + * foo if bar + * ``` + */ + final Stmt getBody() { toGenerated(result) = g.getBody() } + + final override string toString() { result = "... if ..." } + + override AstNode getAChild(string pred) { + result = ConditionalExpr.super.getAChild(pred) + or + pred = "getBody" and result = this.getBody() + } +} + +/** + * An expression modified using `unless`. + * ```rb + * y /= x unless x == 0 + * ``` + */ +class UnlessModifierExpr extends ConditionalExpr, TUnlessModifierExpr { + private Ruby::UnlessModifier g; + + UnlessModifierExpr() { this = TUnlessModifierExpr(g) } + + final override string getAPrimaryQlClass() { result = "UnlessModifierExpr" } + + final override Expr getCondition() { toGenerated(result) = g.getCondition() } + + final override Stmt getBranch(boolean cond) { cond = false and result = this.getBody() } + + /** + * Gets the statement that is conditionally evaluated. In the following + * example, the result is the `Expr` for `foo`. + * ```rb + * foo unless bar + * ``` + */ + final Stmt getBody() { toGenerated(result) = g.getBody() } + + final override string toString() { result = "... unless ..." } + + override AstNode getAChild(string pred) { + result = ConditionalExpr.super.getAChild(pred) + or + pred = "getBody" and result = this.getBody() + } +} + +/** + * A conditional expression using the ternary (`?:`) operator. + * ```rb + * (a > b) ? a : b + * ``` + */ +class TernaryIfExpr extends ConditionalExpr, TTernaryIfExpr { + private Ruby::Conditional g; + + TernaryIfExpr() { this = TTernaryIfExpr(g) } + + final override string getAPrimaryQlClass() { result = "TernaryIfExpr" } + + final override Expr getCondition() { toGenerated(result) = g.getCondition() } + + /** Gets the 'then' branch of this ternary if expression. */ + final Stmt getThen() { toGenerated(result) = g.getConsequence() } + + /** Gets the 'else' branch of this ternary if expression. */ + final Stmt getElse() { toGenerated(result) = g.getAlternative() } + + final override Stmt getBranch(boolean cond) { + cond = true and result = getThen() + or + cond = false and result = getElse() + } + + final override string toString() { result = "... ? ... : ..." } + + override AstNode getAChild(string pred) { + result = ConditionalExpr.super.getAChild(pred) + or + pred = "getThen" and result = this.getThen() + or + pred = "getElse" and result = this.getElse() + } +} + +class CaseExpr extends ControlExpr, TCaseExpr { + private Ruby::Case g; + + CaseExpr() { this = TCaseExpr(g) } + + final override string getAPrimaryQlClass() { result = "CaseExpr" } + + /** + * Gets the expression being compared, if any. For example, `foo` in the following example. + * ```rb + * case foo + * when 0 + * puts 'zero' + * when 1 + * puts 'one' + * end + * ``` + * There is no result for the following example: + * ```rb + * case + * when a then 0 + * when b then 1 + * else 2 + * end + * ``` + */ + final Expr getValue() { toGenerated(result) = g.getValue() } + + /** + * Gets the `n`th branch of this case expression, either a `WhenExpr` or a + * `StmtSequence`. + */ + final Expr getBranch(int n) { toGenerated(result) = g.getChild(n) } + + /** + * Gets a branch of this case expression, either a `WhenExpr` or an + * `ElseExpr`. + */ + final Expr getABranch() { result = this.getBranch(_) } + + /** Gets a `when` branch of this case expression. */ + final WhenExpr getAWhenBranch() { result = getABranch() } + + /** Gets the `else` branch of this case expression, if any. */ + final StmtSequence getElseBranch() { result = getABranch() } + + /** + * Gets the number of branches of this case expression. + */ + final int getNumberOfBranches() { result = count(this.getBranch(_)) } + + final override string toString() { result = "case ..." } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getValue" and result = this.getValue() + or + pred = "getBranch" and result = this.getBranch(_) + } +} + +/** + * A `when` branch of a `case` expression. + * ```rb + * case + * when a > b then x + * end + * ``` + */ +class WhenExpr extends Expr, TWhenExpr { + private Ruby::When g; + + WhenExpr() { this = TWhenExpr(g) } + + final override string getAPrimaryQlClass() { result = "WhenExpr" } + + /** Gets the body of this case-when expression. */ + final Stmt getBody() { toGenerated(result) = g.getBody() } + + /** + * Gets the `n`th pattern (or condition) in this case-when expression. In the + * following example, the 0th pattern is `x`, the 1st pattern is `y`, and the + * 2nd pattern is `z`. + * ```rb + * case foo + * when x, y, z + * puts 'x/y/z' + * end + * ``` + */ + final Expr getPattern(int n) { toGenerated(result) = g.getPattern(n).getChild() } + + /** + * Gets a pattern (or condition) in this case-when expression. + */ + final Expr getAPattern() { result = this.getPattern(_) } + + /** + * Gets the number of patterns in this case-when expression. + */ + final int getNumberOfPatterns() { result = count(this.getPattern(_)) } + + final override string toString() { result = "when ..." } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getBody" and result = this.getBody() + or + pred = "getPattern" and result = this.getPattern(_) + } +} + +/** + * A loop. That is, a `for` loop, a `while` or `until` loop, or their + * expression-modifier variants. + */ +class Loop extends ControlExpr, TLoop { + /** Gets the body of this loop. */ + Stmt getBody() { none() } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getBody" and result = this.getBody() + } +} + +/** + * A loop using a condition expression. That is, a `while` or `until` loop, or + * their expression-modifier variants. + */ +class ConditionalLoop extends Loop, TConditionalLoop { + /** Gets the condition expression of this loop. */ + Expr getCondition() { none() } + + override AstNode getAChild(string pred) { + result = Loop.super.getAChild(pred) + or + pred = "getCondition" and result = this.getCondition() + } + + /** Holds if the loop body is entered when the condition is `condValue`. */ + predicate entersLoopWhenConditionIs(boolean condValue) { none() } +} + +/** + * A `while` loop. + * ```rb + * while a < b + * p a + * a += 2 + * end + * ``` + */ +class WhileExpr extends ConditionalLoop, TWhileExpr { + private Ruby::While g; + + WhileExpr() { this = TWhileExpr(g) } + + final override string getAPrimaryQlClass() { result = "WhileExpr" } + + /** Gets the body of this `while` loop. */ + final override Stmt getBody() { toGenerated(result) = g.getBody() } + + final override Expr getCondition() { toGenerated(result) = g.getCondition() } + + /** + * Holds if the loop body is entered when the condition is `condValue`. For + * `while` loops, this holds when `condValue` is true. + */ + final override predicate entersLoopWhenConditionIs(boolean condValue) { condValue = true } + + final override string toString() { result = "while ..." } +} + +/** + * An `until` loop. + * ```rb + * until a >= b + * p a + * a += 1 + * end + * ``` + */ +class UntilExpr extends ConditionalLoop, TUntilExpr { + private Ruby::Until g; + + UntilExpr() { this = TUntilExpr(g) } + + final override string getAPrimaryQlClass() { result = "UntilExpr" } + + /** Gets the body of this `until` loop. */ + final override Stmt getBody() { toGenerated(result) = g.getBody() } + + final override Expr getCondition() { toGenerated(result) = g.getCondition() } + + /** + * Holds if the loop body is entered when the condition is `condValue`. For + * `until` loops, this holds when `condValue` is false. + */ + final override predicate entersLoopWhenConditionIs(boolean condValue) { condValue = false } + + final override string toString() { result = "until ..." } +} + +/** + * An expression looped using the `while` modifier. + * ```rb + * foo while bar + * ``` + */ +class WhileModifierExpr extends ConditionalLoop, TWhileModifierExpr { + private Ruby::WhileModifier g; + + WhileModifierExpr() { this = TWhileModifierExpr(g) } + + final override Stmt getBody() { toGenerated(result) = g.getBody() } + + final override Expr getCondition() { toGenerated(result) = g.getCondition() } + + /** + * Holds if the loop body is entered when the condition is `condValue`. For + * `while`-modifier loops, this holds when `condValue` is true. + */ + final override predicate entersLoopWhenConditionIs(boolean condValue) { condValue = true } + + final override string getAPrimaryQlClass() { result = "WhileModifierExpr" } + + final override string toString() { result = "... while ..." } +} + +/** + * An expression looped using the `until` modifier. + * ```rb + * foo until bar + * ``` + */ +class UntilModifierExpr extends ConditionalLoop, TUntilModifierExpr { + private Ruby::UntilModifier g; + + UntilModifierExpr() { this = TUntilModifierExpr(g) } + + final override Stmt getBody() { toGenerated(result) = g.getBody() } + + final override Expr getCondition() { toGenerated(result) = g.getCondition() } + + /** + * Holds if the loop body is entered when the condition is `condValue`. For + * `until`-modifier loops, this holds when `condValue` is false. + */ + final override predicate entersLoopWhenConditionIs(boolean condValue) { condValue = false } + + final override string getAPrimaryQlClass() { result = "UntilModifierExpr" } + + final override string toString() { result = "... until ..." } +} + +/** + * A `for` loop. + * ```rb + * for val in 1..n + * sum += val + * end + * ``` + */ +class ForExpr extends Loop, TForExpr { + private Ruby::For g; + + ForExpr() { this = TForExpr(g) } + + final override string getAPrimaryQlClass() { result = "ForExpr" } + + /** Gets the body of this `for` loop. */ + final override Stmt getBody() { toGenerated(result) = g.getBody() } + + /** Gets the pattern representing the iteration argument. */ + final Pattern getPattern() { toGenerated(result) = g.getPattern() } + + /** + * Gets the value being iterated over. In the following example, the result + * is the expression `1..10`: + * ```rb + * for n in 1..10 do + * puts n + * end + * ``` + */ + final Expr getValue() { toGenerated(result) = g.getValue().getChild() } + + final override string toString() { result = "for ... in ..." } + + override AstNode getAChild(string pred) { + result = Loop.super.getAChild(pred) + or + pred = "getPattern" and result = this.getPattern() + or + pred = "getValue" and result = this.getValue() + } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/Erb.qll b/ruby/ql/lib/codeql/ruby/ast/Erb.qll new file mode 100644 index 000000000000..0c10d764590d --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/Erb.qll @@ -0,0 +1,267 @@ +private import codeql.Locations +private import codeql.ruby.AST +private import internal.Erb +private import internal.TreeSitter + +/** + * A node in the ERB abstract syntax tree. This class is the base class for all + * ERB elements. + */ +class ErbAstNode extends TAstNode { + /** Gets a textual representation of this node. */ + cached + string toString() { none() } + + /** Gets the location of this node. */ + Location getLocation() { result = getLocation(this) } + + /** + * Gets the name of a primary CodeQL class to which this node belongs. + * + * This predicate always has a result. If no primary class can be + * determined, the result is `"???"`. If multiple primary classes match, + * this predicate can have multiple results. + */ + string getAPrimaryQlClass() { result = "???" } +} + +/** + * An ERB template. This can contain multiple directives to be executed when + * the template is compiled. + */ +class ErbTemplate extends TTemplate, ErbAstNode { + private Erb::Template g; + + ErbTemplate() { this = TTemplate(g) } + + override string toString() { result = "erb template" } + + final override string getAPrimaryQlClass() { result = "ErbTemplate" } + + ErbAstNode getAChildNode() { toGenerated(result) = g.getChild(_) } +} + +// Truncate the token string value to 32 char max +bindingset[val] +private string displayToken(string val) { + val.length() <= 32 and result = val + or + val.length() > 32 and result = val.prefix(29) + "..." +} + +/** + * An ERB token. This could be embedded code, a comment, or arbitrary text. + */ +class ErbToken extends TTokenNode, ErbAstNode { + override string toString() { result = displayToken(this.getValue()) } + + /** Gets the string value of this token. */ + string getValue() { exists(Erb::Token g | this = fromGenerated(g) | result = g.getValue()) } + + override string getAPrimaryQlClass() { result = "ErbToken" } +} + +/** + * An ERB token appearing within a comment directive. + */ +class ErbComment extends ErbToken { + private Erb::Comment g; + + ErbComment() { this = TComment(g) } + + override string getValue() { result = g.getValue() } + + final override string getAPrimaryQlClass() { result = "ErbComment" } +} + +/** + * An ERB token appearing within a code directive. This will typically be + * interpreted as Ruby code or a GraphQL query, depending on context. + */ +class ErbCode extends ErbToken { + private Erb::Code g; + + ErbCode() { this = TCode(g) } + + override string getValue() { result = g.getValue() } + + final override string getAPrimaryQlClass() { result = "ErbCode" } +} + +bindingset[line, col] +private predicate locationIncludesPosition(Location loc, int line, int col) { + // position between start and end line, exclusive + line > loc.getStartLine() and + line < loc.getEndLine() + or + // position on start line, multi line location + line = loc.getStartLine() and + not loc.getStartLine() = loc.getEndLine() and + col >= loc.getStartColumn() + or + // position on end line, multi line location + line = loc.getEndLine() and + not loc.getStartLine() = loc.getEndLine() and + col <= loc.getEndColumn() + or + // single line location, position between start and end column + line = loc.getStartLine() and + loc.getStartLine() = loc.getEndLine() and + col >= loc.getStartColumn() and + col <= loc.getEndColumn() +} + +/** + * A directive in an ERB template. + */ +class ErbDirective extends TDirectiveNode, ErbAstNode { + private predicate containsStartOf(Location loc) { + loc.getFile() = this.getLocation().getFile() and + locationIncludesPosition(this.getLocation(), loc.getStartLine(), loc.getStartColumn()) + } + + private predicate containsStmtStart(Stmt s) { + this.containsStartOf(s.getLocation()) and + // `Toplevel` statements are not contained within individual directives, + // though their start location may appear within a directive location + not s instanceof Toplevel + } + + /** + * Gets a statement that starts in directive that is not a child of any other + * statement starting in this directive. + */ + Stmt getAChildStmt() { + this.containsStmtStart(result) and + not this.containsStmtStart(result.getParent()) + } + + /** + * Gets the last child statement in this directive. + * See `getAChildStmt` for more details. + */ + Stmt getTerminalStmt() { + result = this.getAChildStmt() and + forall(Stmt s | s = this.getAChildStmt() and not s = result | + s.getLocation().strictlyBefore(result.getLocation()) + ) + } + + /** Gets the child token of this directive. */ + ErbToken getToken() { + exists(Erb::Directive g | this = fromGenerated(g) | toGenerated(result) = g.getChild()) + } + + override string toString() { result = "erb directive" } + + override string getAPrimaryQlClass() { result = "ErbDirective" } +} + +/** + * A comment directive in an ERB template. + * ```erb + * <%#= 2 + 2 %> + * <%# for x in xs do %> + * ``` + */ +class ErbCommentDirective extends ErbDirective { + private Erb::CommentDirective g; + + ErbCommentDirective() { this = TCommentDirective(g) } + + override ErbComment getToken() { toGenerated(result) = g.getChild() } + + final override string toString() { result = "<%#" + this.getToken().toString() + "%>" } + + final override string getAPrimaryQlClass() { result = "ErbCommentDirective" } +} + +/** + * A GraphQL directive in an ERB template. + * ```erb + * <%graphql + * fragment Foo on Bar { + * some { + * queryText + * moreProperties + * } + * } + * %> + * ``` + */ +class ErbGraphqlDirective extends ErbDirective { + private Erb::GraphqlDirective g; + + ErbGraphqlDirective() { this = TGraphqlDirective(g) } + + override ErbCode getToken() { toGenerated(result) = g.getChild() } + + final override string toString() { result = "<%graphql" + this.getToken().toString() + "%>" } + + final override string getAPrimaryQlClass() { result = "ErbGraphqlDirective" } +} + +/** + * An output directive in an ERB template. + * ```erb + * <%= + * fragment Foo on Bar { + * some { + * queryText + * moreProperties + * } + * } + * %> + * ``` + */ +class ErbOutputDirective extends ErbDirective { + private Erb::OutputDirective g; + + ErbOutputDirective() { this = TOutputDirective(g) } + + override ErbCode getToken() { toGenerated(result) = g.getChild() } + + final override string toString() { result = "<%=" + this.getToken().toString() + "%>" } + + final override string getAPrimaryQlClass() { result = "ErbOutputDirective" } +} + +/** + * An execution directive in an ERB template. + * This code will be executed as Ruby, but not rendered. + * ```erb + * <% books = author.books + * for book in books do %> + * ``` + */ +class ErbExecutionDirective extends ErbDirective { + private Erb::Directive g; + + ErbExecutionDirective() { this = TDirective(g) } + + final override string toString() { result = "<%" + this.getToken().toString() + "%>" } + + final override string getAPrimaryQlClass() { result = "ErbExecutionDirective" } +} + +/** + * A `File` containing an Embedded Ruby template. + * This is typically a file containing snippets of Ruby code that can be + * evaluated to create a compiled version of the file. + */ +class ErbFile extends File { + private ErbTemplate template; + + ErbFile() { this = template.getLocation().getFile() } + + /** + * Holds if the file represents a partial to be rendered in the context of + * another template. + */ + predicate isPartial() { this.getStem().charAt(0) = "_" } + + /** + * Gets the erb template contained within this file. + */ + ErbTemplate getTemplate() { result = template } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/Expr.qll b/ruby/ql/lib/codeql/ruby/ast/Expr.qll new file mode 100644 index 000000000000..6a2a85ffee90 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/Expr.qll @@ -0,0 +1,450 @@ +private import codeql.ruby.AST +private import internal.AST +private import internal.TreeSitter + +/** + * An expression. + * + * This is the root QL class for all expressions. + */ +class Expr extends Stmt, TExpr { } + +/** + * A reference to the current object. For example: + * - `self == other` + * - `self.method_name` + * - `def self.method_name ... end` + * + * This also includes implicit references to the current object in method + * calls. For example, the method call `foo(123)` has an implicit `self` + * receiver, and is equivalent to the explicit `self.foo(123)`. + */ +class Self extends Expr, TSelf { + final override string getAPrimaryQlClass() { result = "Self" } + + final override string toString() { result = "self" } +} + +/** + * A sequence of expressions in the right-hand side of an assignment or + * a `return`, `break` or `next` statement. + * ```rb + * x = 1, *items, 3, *more + * return 1, 2 + * next *list + * break **map + * return 1, 2, *items, k: 5, **map + * ``` + */ +class ArgumentList extends Expr, TArgumentList { + private Ruby::AstNode g; + + ArgumentList() { this = TArgumentList(g) } + + /** Gets the `i`th element in this argument list. */ + Expr getElement(int i) { + toGenerated(result) in [ + g.(Ruby::ArgumentList).getChild(i), g.(Ruby::RightAssignmentList).getChild(i) + ] + } + + final override string getAPrimaryQlClass() { result = "ArgumentList" } + + final override string toString() { result = "..., ..." } + + final override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getElement" and result = this.getElement(_) + } +} + +/** A sequence of expressions. */ +class StmtSequence extends Expr, TStmtSequence { + override string getAPrimaryQlClass() { result = "StmtSequence" } + + /** Gets the `n`th statement in this sequence. */ + Stmt getStmt(int n) { none() } + + /** Gets a statement in this sequence. */ + final Stmt getAStmt() { result = this.getStmt(_) } + + /** Gets the last statement in this sequence, if any. */ + final Stmt getLastStmt() { result = this.getStmt(this.getNumberOfStatements() - 1) } + + /** Gets the number of statements in this sequence. */ + final int getNumberOfStatements() { result = count(this.getAStmt()) } + + /** Holds if this sequence has no statements. */ + final predicate isEmpty() { this.getNumberOfStatements() = 0 } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getStmt" and result = this.getStmt(_) + } +} + +private class StmtSequenceSynth extends StmtSequence, TStmtSequenceSynth { + final override Stmt getStmt(int n) { synthChild(this, n, result) } + + final override string toString() { result = "..." } +} + +private class Then extends StmtSequence, TThen { + private Ruby::Then g; + + Then() { this = TThen(g) } + + override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) } + + final override string toString() { result = "then ..." } +} + +private class Else extends StmtSequence, TElse { + private Ruby::Else g; + + Else() { this = TElse(g) } + + override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) } + + final override string toString() { result = "else ..." } +} + +private class Do extends StmtSequence, TDo { + private Ruby::Do g; + + Do() { this = TDo(g) } + + override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) } + + final override string toString() { result = "do ..." } +} + +private class Ensure extends StmtSequence, TEnsure { + private Ruby::Ensure g; + + Ensure() { this = TEnsure(g) } + + override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) } + + final override string toString() { result = "ensure ..." } +} + +/** + * A sequence of statements representing the body of a method, class, module, + * or do-block. That is, any body that may also include rescue/ensure/else + * statements. + */ +class BodyStmt extends StmtSequence, TBodyStmt { + // Not defined by dispatch, as it should not be exposed + private Ruby::AstNode getChild(int i) { + result = any(Ruby::Method g | this = TMethod(g)).getChild(i) + or + result = any(Ruby::SingletonMethod g | this = TSingletonMethod(g)).getChild(i) + or + exists(Ruby::Lambda g | this = TLambda(g) | + result = g.getBody().(Ruby::DoBlock).getChild(i) or + result = g.getBody().(Ruby::Block).getChild(i) + ) + or + result = any(Ruby::DoBlock g | this = TDoBlock(g)).getChild(i) + or + result = any(Ruby::Program g | this = TToplevel(g)).getChild(i) and + not result instanceof Ruby::BeginBlock + or + result = any(Ruby::Class g | this = TClassDeclaration(g)).getChild(i) + or + result = any(Ruby::SingletonClass g | this = TSingletonClass(g)).getChild(i) + or + result = any(Ruby::Module g | this = TModuleDeclaration(g)).getChild(i) + or + result = any(Ruby::Begin g | this = TBeginExpr(g)).getChild(i) + } + + final override Stmt getStmt(int n) { + result = + rank[n + 1](AstNode node, int i | + toGenerated(node) = this.getChild(i) and + not node instanceof Else and + not node instanceof RescueClause and + not node instanceof Ensure + | + node order by i + ) + } + + /** Gets the `n`th rescue clause in this block. */ + final RescueClause getRescue(int n) { + result = + rank[n + 1](RescueClause node, int i | toGenerated(node) = getChild(i) | node order by i) + } + + /** Gets a rescue clause in this block. */ + final RescueClause getARescue() { result = this.getRescue(_) } + + /** Gets the `else` clause in this block, if any. */ + final StmtSequence getElse() { result = unique(Else s | toGenerated(s) = getChild(_)) } + + /** Gets the `ensure` clause in this block, if any. */ + final StmtSequence getEnsure() { result = unique(Ensure s | toGenerated(s) = getChild(_)) } + + final predicate hasEnsure() { exists(this.getEnsure()) } + + override AstNode getAChild(string pred) { + result = StmtSequence.super.getAChild(pred) + or + pred = "getRescue" and result = this.getRescue(_) + or + pred = "getElse" and result = this.getElse() + or + pred = "getEnsure" and result = this.getEnsure() + } +} + +/** + * A parenthesized expression sequence, typically containing a single expression: + * ```rb + * (x + 1) + * ``` + * However, they can also contain multiple expressions (the value of the parenthesized + * expression is the last expression): + * ```rb + * (foo; bar) + * ``` + * or even an empty sequence (value is `nil`): + * ```rb + * () + * ``` + */ +class ParenthesizedExpr extends StmtSequence, TParenthesizedExpr { + private Ruby::ParenthesizedStatements g; + + ParenthesizedExpr() { this = TParenthesizedExpr(g) } + + final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) } + + final override string getAPrimaryQlClass() { result = "ParenthesizedExpr" } + + final override string toString() { result = "( ... )" } +} + +/** + * A pair expression. For example, in a hash: + * ```rb + * { foo: bar } + * ``` + * Or a keyword argument: + * ```rb + * baz(qux: 1) + * ``` + */ +class Pair extends Expr, TPair { + private Ruby::Pair g; + + Pair() { this = TPair(g) } + + final override string getAPrimaryQlClass() { result = "Pair" } + + /** + * Gets the key expression of this pair. For example, the `SymbolLiteral` + * representing the keyword `foo` in the following example: + * ```rb + * bar(foo: 123) + * ``` + * Or the `StringLiteral` for `'foo'` in the following hash pair: + * ```rb + * { 'foo' => 123 } + * ``` + */ + final Expr getKey() { toGenerated(result) = g.getKey() } + + /** + * Gets the value expression of this pair. For example, the `InteralLiteral` + * 123 in the following hash pair: + * ```rb + * { 'foo' => 123 } + * ``` + */ + final Expr getValue() { toGenerated(result) = g.getValue() } + + final override string toString() { result = "Pair" } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getKey" and result = this.getKey() + or + pred = "getValue" and result = this.getValue() + } +} + +/** + * A rescue clause. For example: + * ```rb + * begin + * write_file + * rescue StandardError => msg + * puts msg + * end + */ +class RescueClause extends Expr, TRescueClause { + private Ruby::Rescue g; + + RescueClause() { this = TRescueClause(g) } + + final override string getAPrimaryQlClass() { result = "RescueClause" } + + /** + * Gets the `n`th exception to match, if any. For example `FirstError` or `SecondError` in: + * ```rb + * begin + * do_something + * rescue FirstError, SecondError => e + * handle_error(e) + * end + * ``` + */ + final Expr getException(int n) { toGenerated(result) = g.getExceptions().getChild(n) } + + /** + * Gets an exception to match, if any. For example `FirstError` or `SecondError` in: + * ```rb + * begin + * do_something + * rescue FirstError, SecondError => e + * handle_error(e) + * end + * ``` + */ + final Expr getAnException() { result = this.getException(_) } + + /** + * Gets the variable to which to assign the matched exception, if any. + * For example `err` in: + * ```rb + * begin + * do_something + * rescue StandardError => err + * handle_error(err) + * end + * ``` + */ + final LhsExpr getVariableExpr() { toGenerated(result) = g.getVariable().getChild() } + + /** + * Gets the exception handler body. + */ + final StmtSequence getBody() { toGenerated(result) = g.getBody() } + + final override string toString() { result = "rescue ..." } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getException" and result = this.getException(_) + or + pred = "getVariableExpr" and result = this.getVariableExpr() + or + pred = "getBody" and result = this.getBody() + } +} + +/** + * An expression with a `rescue` modifier. For example: + * ```rb + * contents = read_file rescue "" + * ``` + */ +class RescueModifierExpr extends Expr, TRescueModifierExpr { + private Ruby::RescueModifier g; + + RescueModifierExpr() { this = TRescueModifierExpr(g) } + + final override string getAPrimaryQlClass() { result = "RescueModifierExpr" } + + /** + * Gets the body of this `RescueModifierExpr`. + * ```rb + * body rescue handler + * ``` + */ + final Stmt getBody() { toGenerated(result) = g.getBody() } + + /** + * Gets the exception handler of this `RescueModifierExpr`. + * ```rb + * body rescue handler + * ``` + */ + final Stmt getHandler() { toGenerated(result) = g.getHandler() } + + final override string toString() { result = "... rescue ..." } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getBody" and result = this.getBody() + or + pred = "getHandler" and result = this.getHandler() + } +} + +/** + * A concatenation of string literals. + * + * ```rb + * "foo" "bar" "baz" + * ``` + */ +class StringConcatenation extends Expr, TStringConcatenation { + private Ruby::ChainedString g; + + StringConcatenation() { this = TStringConcatenation(g) } + + final override string getAPrimaryQlClass() { result = "StringConcatenation" } + + /** Gets the `n`th string literal in this concatenation. */ + final StringLiteral getString(int n) { toGenerated(result) = g.getChild(n) } + + /** Gets a string literal in this concatenation. */ + final StringLiteral getAString() { result = this.getString(_) } + + /** Gets the number of string literals in this concatenation. */ + final int getNumberOfStrings() { result = count(this.getString(_)) } + + /** + * Gets the result of concatenating all the string literals, if and only if + * they do not contain any interpolations. + * + * For the following example, the result is `"foobar"`: + * + * ```rb + * "foo" 'bar' + * ``` + * + * And for the following example, where one of the string literals includes + * an interpolation, there is no result: + * + * ```rb + * "foo" "bar#{ n }" + * ``` + */ + final string getConcatenatedValueText() { + forall(StringLiteral c | c = this.getString(_) | exists(c.getValueText())) and + result = + concat(string valueText, int i | + valueText = this.getString(i).getValueText() + | + valueText order by i + ) + } + + final override string toString() { result = "\"...\" \"...\"" } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getString" and result = this.getString(_) + } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/Literal.qll b/ruby/ql/lib/codeql/ruby/ast/Literal.qll new file mode 100644 index 000000000000..c158faf2b4b2 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/Literal.qll @@ -0,0 +1,885 @@ +private import codeql.ruby.AST +private import codeql.ruby.regexp.RegExpTreeView as RETV +private import internal.AST +private import internal.Scope +private import internal.TreeSitter + +/** + * A literal. + * + * This is the QL root class for all literals. + */ +class Literal extends Expr, TLiteral { + /** + * Gets the source text for this literal, if this is a simple literal. + * + * For complex literals, such as arrays, hashes, and strings with + * interpolations, this predicate has no result. + */ + string getValueText() { none() } +} + +/** + * A numeric literal, i.e. an integer, floating-point, rational, or complex + * value. + * + * ```rb + * 123 + * 0xff + * 3.14159 + * 1.0E2 + * 7r + * 1i + * ``` + */ +class NumericLiteral extends Literal, TNumericLiteral { } + +/** + * An integer literal. + * + * ```rb + * 123 + * 0xff + * ``` + */ +class IntegerLiteral extends NumericLiteral, TIntegerLiteral { + /** Gets the numerical value of this integer literal. */ + int getValue() { none() } + + final override string toString() { result = this.getValueText() } + + final override string getAPrimaryQlClass() { result = "IntegerLiteral" } +} + +private class IntegerLiteralReal extends IntegerLiteral, TIntegerLiteralReal { + private Ruby::Integer g; + + IntegerLiteralReal() { this = TIntegerLiteralReal(g) } + + final override string getValueText() { result = g.getValue() } + + final override int getValue() { + exists(string s, string values, string str | + s = this.getValueText().toLowerCase() and + ( + s.matches("0b%") and + values = "01" and + str = s.suffix(2) + or + s.matches("0x%") and + values = "0123456789abcdef" and + str = s.suffix(2) + or + s.charAt(0) = "0" and + not s.charAt(1) = ["b", "x", "o"] and + values = "01234567" and + str = s.suffix(1) + or + s.matches("0o%") and + values = "01234567" and + str = s.suffix(2) + or + s.charAt(0) != "0" and values = "0123456789" and str = s + ) + | + result = + sum(int index, string c, int v, int exp | + c = str.replaceAll("_", "").charAt(index) and + v = values.indexOf(c.toLowerCase()) and + exp = str.replaceAll("_", "").length() - index - 1 + | + v * values.length().pow(exp) + ) + ) + } +} + +private class IntegerLiteralSynth extends IntegerLiteral, TIntegerLiteralSynth { + private int value; + + IntegerLiteralSynth() { this = TIntegerLiteralSynth(_, _, value) } + + final override string getValueText() { result = value.toString() } + + final override int getValue() { result = value } +} + +/** + * A floating-point literal. + * + * ```rb + * 1.3 + * 2.7e+5 + * ``` + */ +class FloatLiteral extends NumericLiteral, TFloatLiteral { + private Ruby::Float g; + + FloatLiteral() { this = TFloatLiteral(g) } + + final override string getValueText() { result = g.getValue() } + + final override string toString() { result = this.getValueText() } + + final override string getAPrimaryQlClass() { result = "FloatLiteral" } +} + +/** + * A rational literal. + * + * ```rb + * 123r + * ``` + */ +class RationalLiteral extends NumericLiteral, TRationalLiteral { + private Ruby::Rational g; + + RationalLiteral() { this = TRationalLiteral(g) } + + final override string getValueText() { result = g.getChild().(Ruby::Token).getValue() + "r" } + + final override string toString() { result = this.getValueText() } + + final override string getAPrimaryQlClass() { result = "RationalLiteral" } +} + +/** + * A complex literal. + * + * ```rb + * 1i + * ``` + */ +class ComplexLiteral extends NumericLiteral, TComplexLiteral { + private Ruby::Complex g; + + ComplexLiteral() { this = TComplexLiteral(g) } + + final override string getValueText() { result = g.getValue() } + + final override string toString() { result = this.getValueText() } + + final override string getAPrimaryQlClass() { result = "ComplexLiteral" } +} + +/** A `nil` literal. */ +class NilLiteral extends Literal, TNilLiteral { + private Ruby::Nil g; + + NilLiteral() { this = TNilLiteral(g) } + + final override string getValueText() { result = g.getValue() } + + final override string toString() { result = this.getValueText() } + + final override string getAPrimaryQlClass() { result = "NilLiteral" } +} + +/** + * A Boolean literal. + * ```rb + * true + * false + * TRUE + * FALSE + * ``` + */ +class BooleanLiteral extends Literal, TBooleanLiteral { + final override string getAPrimaryQlClass() { result = "BooleanLiteral" } + + final override string toString() { result = this.getValueText() } + + /** Holds if the Boolean literal is `true` or `TRUE`. */ + predicate isTrue() { none() } + + /** Holds if the Boolean literal is `false` or `FALSE`. */ + predicate isFalse() { none() } +} + +private class TrueLiteral extends BooleanLiteral, TTrueLiteral { + private Ruby::True g; + + TrueLiteral() { this = TTrueLiteral(g) } + + final override string getValueText() { result = g.getValue() } + + final override predicate isTrue() { any() } +} + +private class FalseLiteral extends BooleanLiteral, TFalseLiteral { + private Ruby::False g; + + FalseLiteral() { this = TFalseLiteral(g) } + + final override string getValueText() { result = g.getValue() } + + final override predicate isFalse() { any() } +} + +/** + * The base class for a component of a string: `StringTextComponent`, + * `StringEscapeSequenceComponent`, or `StringInterpolationComponent`. + */ +class StringComponent extends AstNode, TStringComponent { + /** + * Gets the source text for this string component. Has no result if this is + * a `StringInterpolationComponent`. + */ + string getValueText() { none() } +} + +/** + * A component of a string (or string-like) literal that is simply text. + * + * For example, the following string literals all contain `StringTextComponent` + * components whose `getValueText()` returns `"foo"`: + * + * ```rb + * 'foo' + * "#{ bar() }foo" + * "foo#{ bar() } baz" + * ``` + */ +class StringTextComponent extends StringComponent, TStringTextComponent { + private Ruby::Token g; + + StringTextComponent() { this = TStringTextComponent(g) } + + final override string toString() { result = g.getValue() } + + final override string getValueText() { result = g.getValue() } + + final override string getAPrimaryQlClass() { result = "StringTextComponent" } +} + +/** + * An escape sequence component of a string or string-like literal. + */ +class StringEscapeSequenceComponent extends StringComponent, TStringEscapeSequenceComponent { + private Ruby::EscapeSequence g; + + StringEscapeSequenceComponent() { this = TStringEscapeSequenceComponent(g) } + + final override string toString() { result = g.getValue() } + + final override string getValueText() { result = g.getValue() } + + final override string getAPrimaryQlClass() { result = "StringEscapeSequenceComponent" } +} + +/** + * An interpolation expression component of a string or string-like literal. + */ +class StringInterpolationComponent extends StringComponent, StmtSequence, + TStringInterpolationComponent { + private Ruby::Interpolation g; + + StringInterpolationComponent() { this = TStringInterpolationComponent(g) } + + final override string toString() { result = "#{...}" } + + final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) } + + final override string getValueText() { none() } + + final override string getAPrimaryQlClass() { result = "StringInterpolationComponent" } +} + +/** + * A string, symbol, regexp, or subshell literal. + */ +class StringlikeLiteral extends Literal, TStringlikeLiteral { + /** + * Gets the `n`th component of this string or string-like literal. The result + * will be one of `StringTextComponent`, `StringInterpolationComponent`, and + * `StringEscapeSequenceComponent`. + * + * In the following example, the result for `n = 0` is the + * `StringTextComponent` for `foo_`, and the result for `n = 1` is the + * `StringInterpolationComponent` for `Time.now`. + * + * ```rb + * "foo_#{ Time.now }" + * ``` + */ + StringComponent getComponent(int n) { none() } + + /** + * Gets the number of components in this string or string-like literal. + * + * For the empty string `""`, the result is 0. + * + * For the string `"foo"`, the result is 1: there is a single + * `StringTextComponent`. + * + * For the following example, the result is 3: there is a + * `StringTextComponent` for the substring `"foo_"`; a + * `StringEscapeSequenceComponent` for the escaped quote; and a + * `StringInterpolationComponent` for the interpolation. + * + * ```rb + * "foo\"#{bar}" + * ``` + */ + final int getNumberOfComponents() { result = count(this.getComponent(_)) } + + private string getStartDelimiter() { + this instanceof TStringLiteral and + result = "\"" + or + this instanceof TRegExpLiteral and + result = "/" + or + this instanceof TSimpleSymbolLiteral and + result = ":" + or + this instanceof TComplexSymbolLiteral and + result = ":\"" + or + this instanceof THashKeySymbolLiteral and + result = "" + or + this instanceof TSubshellLiteral and + result = "`" + or + this instanceof THereDoc and + result = "" + } + + private string getEndDelimiter() { + this instanceof TStringLiteral and + result = "\"" + or + this instanceof TRegExpLiteral and + result = "/" + or + this instanceof TSimpleSymbolLiteral and + result = "" + or + this instanceof TComplexSymbolLiteral and + result = "\"" + or + this instanceof THashKeySymbolLiteral and + result = "" + or + this instanceof TSubshellLiteral and + result = "`" + or + this instanceof THereDoc and + result = "" + } + + override string getValueText() { + // 0 components should result in the empty string + // if there are any interpolations, there should be no result + // otherwise, concatenate all the components + forall(StringComponent c | c = this.getComponent(_) | + not c instanceof StringInterpolationComponent + ) and + result = + concat(StringComponent c, int i | c = this.getComponent(i) | c.getValueText() order by i) + } + + override string toString() { + exists(string full, string summary | + full = + concat(StringComponent c, int i, string s | + c = this.getComponent(i) and + ( + s = toGenerated(c).(Ruby::Token).getValue() + or + not toGenerated(c) instanceof Ruby::Token and + s = "#{...}" + ) + | + s order by i + ) and + ( + // summary should be 32 chars max (incl. ellipsis) + full.length() > 32 and summary = full.substring(0, 29) + "..." + or + full.length() <= 32 and summary = full + ) and + result = this.getStartDelimiter() + summary + this.getEndDelimiter() + ) + } + + final override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getComponent" and result = this.getComponent(_) + } +} + +/** + * A string literal. + * + * ```rb + * 'hello' + * "hello, #{name}" + * ``` + */ +class StringLiteral extends StringlikeLiteral, TStringLiteral { + final override string getAPrimaryQlClass() { result = "StringLiteral" } +} + +private class RegularStringLiteral extends StringLiteral, TRegularStringLiteral { + private Ruby::String g; + + RegularStringLiteral() { this = TRegularStringLiteral(g) } + + final override StringComponent getComponent(int n) { toGenerated(result) = g.getChild(n) } +} + +private class BareStringLiteral extends StringLiteral, TBareStringLiteral { + private Ruby::BareString g; + + BareStringLiteral() { this = TBareStringLiteral(g) } + + final override StringComponent getComponent(int n) { toGenerated(result) = g.getChild(n) } +} + +/** + * A regular expression literal. + * + * ```rb + * /[a-z]+/ + * ``` + */ +class RegExpLiteral extends StringlikeLiteral, TRegExpLiteral { + private Ruby::Regex g; + + RegExpLiteral() { this = TRegExpLiteral(g) } + + final override string getAPrimaryQlClass() { result = "RegExpLiteral" } + + final override StringComponent getComponent(int i) { toGenerated(result) = g.getChild(i) } + + /** + * Gets the regexp flags as a string. + * + * ```rb + * /foo/ # => "" + * /foo/i # => "i" + * /foo/imxo # => "imxo" + */ + final string getFlagString() { + // For `/foo/i`, there should be an `/i` token in the database with `this` + // as its parents. Strip the delimiter, which can vary. + result = + max(Ruby::Token t | t.getParent() = g | t.getValue().suffix(1) order by t.getParentIndex()) + } + + /** + * Holds if the regexp was specified using the `i` flag to indicate case + * insensitivity, as in the following example: + * + * ```rb + * /foo/i + * ``` + */ + final predicate hasCaseInsensitiveFlag() { this.getFlagString().charAt(_) = "i" } + + /** + * Holds if the regex was specified using the `m` flag to indicate multiline + * mode. For example: + * + * ```rb + * /foo/m + * ``` + */ + final predicate hasMultilineFlag() { this.getFlagString().charAt(_) = "m" } + + /** + * Holds if the regex was specified using the `x` flag to indicate + * 'free-spacing' mode (also known as 'extended' mode), meaning that + * whitespace and comments in the pattern are ignored. For example: + * + * ```rb + * %r{ + * [a-zA-Z_] # starts with a letter or underscore + * \w* # and then zero or more letters/digits/underscores + * }/x + * ``` + */ + final predicate hasFreeSpacingFlag() { this.getFlagString().charAt(_) = "x" } + + /** Returns the root node of the parse tree of this regular expression. */ + final RETV::RegExpTerm getParsed() { result = RETV::getParsedRegExp(this) } +} + +/** + * A symbol literal. + * + * ```rb + * :foo + * :"foo bar" + * :"foo bar #{baz}" + * ``` + */ +class SymbolLiteral extends StringlikeLiteral, TSymbolLiteral { + final override string getAPrimaryQlClass() { + not this instanceof MethodName and result = "SymbolLiteral" + } +} + +private class SimpleSymbolLiteral extends SymbolLiteral, TSimpleSymbolLiteral { + private Ruby::SimpleSymbol g; + + SimpleSymbolLiteral() { this = TSimpleSymbolLiteral(g) } + + // Tree-sitter gives us value text including the colon, which we skip. + final override string getValueText() { result = g.getValue().suffix(1) } + + final override string toString() { result = g.getValue() } +} + +private class ComplexSymbolLiteral extends SymbolLiteral, TComplexSymbolLiteral { } + +private class DelimitedSymbolLiteral extends ComplexSymbolLiteral, TDelimitedSymbolLiteral { + private Ruby::DelimitedSymbol g; + + DelimitedSymbolLiteral() { this = TDelimitedSymbolLiteral(g) } + + final override StringComponent getComponent(int i) { toGenerated(result) = g.getChild(i) } +} + +private class BareSymbolLiteral extends ComplexSymbolLiteral, TBareSymbolLiteral { + private Ruby::BareSymbol g; + + BareSymbolLiteral() { this = TBareSymbolLiteral(g) } + + final override StringComponent getComponent(int i) { toGenerated(result) = g.getChild(i) } +} + +private class HashKeySymbolLiteral extends SymbolLiteral, THashKeySymbolLiteral { + private Ruby::HashKeySymbol g; + + HashKeySymbolLiteral() { this = THashKeySymbolLiteral(g) } + + final override string getValueText() { result = g.getValue() } + + final override string toString() { result = ":" + this.getValueText() } +} + +/** + * A subshell literal. + * + * ```rb + * `ls -l` + * %x(/bin/sh foo.sh) + * ``` + */ +class SubshellLiteral extends StringlikeLiteral, TSubshellLiteral { + private Ruby::Subshell g; + + SubshellLiteral() { this = TSubshellLiteral(g) } + + final override string getAPrimaryQlClass() { result = "SubshellLiteral" } + + final override StringComponent getComponent(int i) { toGenerated(result) = g.getChild(i) } +} + +/** + * A character literal. + * + * ```rb + * ?a + * ?\u{61} + * ``` + */ +class CharacterLiteral extends Literal, TCharacterLiteral { + private Ruby::Character g; + + CharacterLiteral() { this = TCharacterLiteral(g) } + + final override string getValueText() { result = g.getValue() } + + final override string toString() { result = g.getValue() } + + final override string getAPrimaryQlClass() { result = "CharacterLiteral" } +} + +/** + * A "here document". For example: + * ```rb + * query = < 21 + * SQL + * ``` + */ +class HereDoc extends StringlikeLiteral, THereDoc { + private Ruby::HeredocBeginning g; + + HereDoc() { this = THereDoc(g) } + + final override string getAPrimaryQlClass() { result = "HereDoc" } + + /** + * Holds if this here document is executed in a subshell. + * ```rb + * <<`COMMAND` + * echo "Hello world!" + * COMMAND + * ``` + */ + final predicate isSubShell() { getQuoteStyle() = "`" } + + /** + * Gets the quotation mark (`"`, `'` or `` ` ``) that surrounds the here document identifier, if any. + * ```rb + * <<"IDENTIFIER" + * <<'IDENTIFIER' + * <<`IDENTIFIER` + * ``` + */ + final string getQuoteStyle() { + exists(string s | + s = g.getValue() and + s.charAt(s.length() - 1) = result and + result = ["'", "`", "\""] + ) + } + + /** + * Gets the indentation modifier (`-` or `~`) of the here document identifier, if any. + * ```rb + * <<~IDENTIFIER + * <<-IDENTIFIER + * < (x) { x + 1 } + * ``` + */ +class Lambda extends Callable, BodyStmt, TLambda { + private Ruby::Lambda g; + + Lambda() { this = TLambda(g) } + + final override string getAPrimaryQlClass() { result = "Lambda" } + + final override Parameter getParameter(int n) { + toGenerated(result) = g.getParameters().getChild(n) + } + + final override string toString() { result = "-> { ... }" } + + final override AstNode getAChild(string pred) { + result = Callable.super.getAChild(pred) + or + result = BodyStmt.super.getAChild(pred) + } +} + +/** A block. */ +class Block extends Callable, StmtSequence, Scope, TBlock { + override AstNode getAChild(string pred) { + result = Callable.super.getAChild(pred) + or + result = StmtSequence.super.getAChild(pred) + } +} + +/** A block enclosed within `do` and `end`. */ +class DoBlock extends Block, BodyStmt, TDoBlock { + private Ruby::DoBlock g; + + DoBlock() { this = TDoBlock(g) } + + final override Parameter getParameter(int n) { + toGenerated(result) = g.getParameters().getChild(n) + } + + final override string toString() { result = "do ... end" } + + final override AstNode getAChild(string pred) { + result = Block.super.getAChild(pred) + or + result = BodyStmt.super.getAChild(pred) + } + + final override string getAPrimaryQlClass() { result = "DoBlock" } +} + +/** + * A block defined using curly braces, e.g. in the following code: + * ```rb + * names.each { |name| puts name } + * ``` + */ +class BraceBlock extends Block, TBraceBlock { + private Ruby::Block g; + + BraceBlock() { this = TBraceBlock(g) } + + final override Parameter getParameter(int n) { + toGenerated(result) = g.getParameters().getChild(n) + } + + final override Stmt getStmt(int i) { toGenerated(result) = g.getChild(i) } + + final override string toString() { result = "{ ... }" } + + final override string getAPrimaryQlClass() { result = "BraceBlock" } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/Module.qll b/ruby/ql/lib/codeql/ruby/ast/Module.qll new file mode 100644 index 000000000000..125c2a6ce184 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/Module.qll @@ -0,0 +1,365 @@ +private import codeql.ruby.AST +private import codeql.ruby.ast.Constant +private import internal.AST +private import internal.Module +private import internal.TreeSitter + +/** + * A representation of a run-time `module` or `class` value. + */ +class Module extends TModule { + /** Gets a declaration of this module, if any. */ + ModuleBase getADeclaration() { result.getModule() = this } + + /** Gets the super class of this module, if any. */ + Module getSuperClass() { result = getSuperClass(this) } + + /** Gets a `prepend`ed module. */ + Module getAPrependedModule() { result = getAPrependedModule(this) } + + /** Gets an `include`d module. */ + Module getAnIncludedModule() { result = getAnIncludedModule(this) } + + /** Holds if this module is a class. */ + pragma[noinline] + predicate isClass() { this.getADeclaration() instanceof ClassDeclaration } + + /** Gets a textual representation of this module. */ + string toString() { + this = TResolved(result) + or + exists(Namespace n | this = TUnresolved(n) and result = "...::" + n.toString()) + } + + /** Gets the location of this module. */ + Location getLocation() { + exists(Namespace n | this = TUnresolved(n) and result = n.getLocation()) + or + result = + min(Namespace n, string qName, Location loc, int weight | + this = TResolved(qName) and + qName = namespaceDeclaration(n) and + loc = n.getLocation() and + if exists(loc.getFile().getRelativePath()) then weight = 0 else weight = 1 + | + loc + order by + weight, count(n.getAStmt()) desc, loc.getFile().getAbsolutePath(), loc.getStartLine(), + loc.getStartColumn() + ) + } +} + +/** + * The base class for classes, singleton classes, and modules. + */ +class ModuleBase extends BodyStmt, Scope, TModuleBase { + /** Gets a method defined in this module/class. */ + MethodBase getAMethod() { result = this.getAStmt() } + + /** Gets the method named `name` in this module/class, if any. */ + MethodBase getMethod(string name) { result = this.getAMethod() and result.getName() = name } + + /** Gets a class defined in this module/class. */ + ClassDeclaration getAClass() { result = this.getAStmt() } + + /** Gets the class named `name` in this module/class, if any. */ + ClassDeclaration getClass(string name) { result = this.getAClass() and result.getName() = name } + + /** Gets a module defined in this module/class. */ + ModuleDeclaration getAModule() { result = this.getAStmt() } + + /** Gets the module named `name` in this module/class, if any. */ + ModuleDeclaration getModule(string name) { + result = this.getAModule() and result.getName() = name + } + + /** + * Gets the value of the constant named `name`, if any. + * + * For example, the value of `CONST` is `"const"` in + * ```rb + * module M + * CONST = "const" + * end + * ``` + */ + Expr getConstant(string name) { + exists(AssignExpr ae, ConstantWriteAccess w | + ae = this.getAStmt() and + w = ae.getLeftOperand() and + w.getName() = name and + not exists(w.getScopeExpr()) and + result = ae.getRightOperand() + ) + } + + /** Gets the representation of the run-time value of this module or class. */ + Module getModule() { none() } +} + +/** + * A Ruby source file. + * + * ```rb + * def main + * puts "hello world!" + * end + * main + * ``` + */ +class Toplevel extends ModuleBase, TToplevel { + private Ruby::Program g; + + Toplevel() { this = TToplevel(g) } + + final override string getAPrimaryQlClass() { result = "Toplevel" } + + /** + * Gets the `n`th `BEGIN` block. + */ + final BeginBlock getBeginBlock(int n) { + toGenerated(result) = rank[n + 1](int i, Ruby::BeginBlock b | b = g.getChild(i) | b order by i) + } + + /** + * Gets a `BEGIN` block. + */ + final BeginBlock getABeginBlock() { result = getBeginBlock(_) } + + final override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getBeginBlock" and result = this.getBeginBlock(_) + } + + final override Module getModule() { result = TResolved("Object") } + + final override string toString() { result = g.getLocation().getFile().getBaseName() } +} + +/** + * A class or module definition. + * + * ```rb + * class Foo + * def bar + * end + * end + * module Bar + * class Baz + * end + * end + * ``` + */ +class Namespace extends ModuleBase, ConstantWriteAccess, TNamespace { + override string getAPrimaryQlClass() { result = "Namespace" } + + /** + * Gets the name of the module/class. In the following example, the result is + * `"Foo"`. + * ```rb + * class Foo + * end + * ``` + * + * N.B. in the following example, where the module/class name uses the scope + * resolution operator, the result is the name being resolved, i.e. `"Bar"`. + * Use `getScopeExpr` to get the `Foo` for `Foo`. + * ```rb + * module Foo::Bar + * end + * ``` + */ + override string getName() { none() } + + /** + * Gets the scope expression used in the module/class name's scope resolution + * operation, if any. + * + * In the following example, the result is the `Expr` for `Foo`. + * + * ```rb + * module Foo::Bar + * end + * ``` + * + * However, there is no result for the following example, since there is no + * scope resolution operation. + * + * ```rb + * module Baz + * end + * ``` + */ + override Expr getScopeExpr() { none() } + + /** + * Holds if the module/class name uses the scope resolution operator to access the + * global scope, as in this example: + * + * ```rb + * class ::Foo + * end + * ``` + */ + override predicate hasGlobalScope() { none() } + + final override Module getModule() { + result = any(string qName | qName = namespaceDeclaration(this) | TResolved(qName)) + or + result = TUnresolved(this) + } + + override AstNode getAChild(string pred) { + result = ModuleBase.super.getAChild(pred) or + result = ConstantWriteAccess.super.getAChild(pred) + } + + final override string toString() { result = ConstantWriteAccess.super.toString() } +} + +/** + * A class definition. + * + * ```rb + * class Foo + * def bar + * end + * end + * ``` + */ +class ClassDeclaration extends Namespace, TClassDeclaration { + private Ruby::Class g; + + ClassDeclaration() { this = TClassDeclaration(g) } + + final override string getAPrimaryQlClass() { result = "ClassDeclaration" } + + /** + * Gets the `Expr` used as the superclass in the class definition, if any. + * + * In the following example, the result is a `ConstantReadAccess`. + * ```rb + * class Foo < Bar + * end + * ``` + * + * In the following example, where the superclass is a call expression, the + * result is a `Call`. + * ```rb + * class C < foo() + * end + * ``` + */ + final Expr getSuperclassExpr() { toGenerated(result) = g.getSuperclass().getChild() } + + final override string getName() { + result = g.getName().(Ruby::Token).getValue() or + result = g.getName().(Ruby::ScopeResolution).getName().(Ruby::Token).getValue() + } + + final override Expr getScopeExpr() { + toGenerated(result) = g.getName().(Ruby::ScopeResolution).getScope() + } + + final override predicate hasGlobalScope() { + exists(Ruby::ScopeResolution sr | + sr = g.getName() and + not exists(sr.getScope()) + ) + } + + final override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getSuperclassExpr" and result = this.getSuperclassExpr() + } +} + +/** + * A definition of a singleton class on an object. + * + * ```rb + * class << foo + * def bar + * p 'bar' + * end + * end + * ``` + */ +class SingletonClass extends ModuleBase, TSingletonClass { + private Ruby::SingletonClass g; + + SingletonClass() { this = TSingletonClass(g) } + + final override string getAPrimaryQlClass() { result = "ClassDeclaration" } + + /** + * Gets the expression resulting in the object on which the singleton class + * is defined. In the following example, the result is the `Expr` for `foo`: + * + * ```rb + * class << foo + * end + * ``` + */ + final Expr getValue() { toGenerated(result) = g.getValue() } + + final override string toString() { result = "class << ..." } + + final override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getValue" and result = this.getValue() + } +} + +/** + * A module definition. + * + * ```rb + * module Foo + * class Bar + * end + * end + * ``` + * + * N.B. this class represents a single instance of a module definition. In the + * following example, classes `Bar` and `Baz` are both defined in the module + * `Foo`, but in two syntactically distinct definitions, meaning that there + * will be two instances of `ModuleDeclaration` in the database. + * + * ```rb + * module Foo + * class Bar; end + * end + * + * module Foo + * class Baz; end + * end + * ``` + */ +class ModuleDeclaration extends Namespace, TModuleDeclaration { + private Ruby::Module g; + + ModuleDeclaration() { this = TModuleDeclaration(g) } + + final override string getAPrimaryQlClass() { result = "ModuleDeclaration" } + + final override string getName() { + result = g.getName().(Ruby::Token).getValue() or + result = g.getName().(Ruby::ScopeResolution).getName().(Ruby::Token).getValue() + } + + final override Expr getScopeExpr() { + toGenerated(result) = g.getName().(Ruby::ScopeResolution).getScope() + } + + final override predicate hasGlobalScope() { + exists(Ruby::ScopeResolution sr | + sr = g.getName() and + not exists(sr.getScope()) + ) + } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/Operation.qll b/ruby/ql/lib/codeql/ruby/ast/Operation.qll new file mode 100644 index 000000000000..236439700ce3 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/Operation.qll @@ -0,0 +1,620 @@ +private import codeql.ruby.AST +private import internal.AST +private import internal.TreeSitter +private import internal.Operation + +/** + * An operation. + * + * This is the QL root class for all operations. + */ +class Operation extends Expr instanceof OperationImpl { + /** Gets the operator of this operation. */ + final string getOperator() { result = super.getOperatorImpl() } + + /** Gets an operand of this operation. */ + final Expr getAnOperand() { result = super.getAnOperandImpl() } + + override AstNode getAChild(string pred) { + result = Expr.super.getAChild(pred) + or + pred = "getAnOperand" and result = this.getAnOperand() + } +} + +/** A unary operation. */ +class UnaryOperation extends Operation, MethodCall instanceof UnaryOperationImpl { + /** Gets the operand of this unary operation. */ + final Expr getOperand() { result = super.getOperandImpl() } + + final override AstNode getAChild(string pred) { + result = Operation.super.getAChild(pred) + or + result = MethodCall.super.getAChild(pred) + or + pred = "getOperand" and result = this.getOperand() + } + + final override string toString() { result = this.getOperator() + " ..." } +} + +/** A unary logical operation. */ +class UnaryLogicalOperation extends UnaryOperation, TUnaryLogicalOperation { } + +/** + * A logical NOT operation, using either `!` or `not`. + * ```rb + * !x.nil? + * not params.empty? + * ``` + */ +class NotExpr extends UnaryLogicalOperation, TNotExpr { + final override string getAPrimaryQlClass() { result = "NotExpr" } +} + +/** A unary arithmetic operation. */ +class UnaryArithmeticOperation extends UnaryOperation, TUnaryArithmeticOperation { } + +/** + * A unary plus expression. + * ```rb + * + a + * ``` + */ +class UnaryPlusExpr extends UnaryArithmeticOperation, TUnaryPlusExpr { + final override string getAPrimaryQlClass() { result = "UnaryPlusExpr" } +} + +/** + * A unary minus expression. + * ```rb + * - a + * ``` + */ +class UnaryMinusExpr extends UnaryArithmeticOperation, TUnaryMinusExpr { + final override string getAPrimaryQlClass() { result = "UnaryMinusExpr" } +} + +/** + * A splat expression. + * ```rb + * foo(*args) + * ``` + */ +class SplatExpr extends UnaryOperation, TSplatExpr { + final override string getAPrimaryQlClass() { result = "SplatExpr" } +} + +/** + * A hash-splat (or 'double-splat') expression. + * ```rb + * foo(**options) + * ``` + */ +class HashSplatExpr extends UnaryOperation, THashSplatExpr { + private Ruby::HashSplatArgument g; + + HashSplatExpr() { this = THashSplatExpr(g) } + + final override string getAPrimaryQlClass() { result = "HashSplatExpr" } +} + +/** A unary bitwise operation. */ +class UnaryBitwiseOperation extends UnaryOperation, TUnaryBitwiseOperation { } + +/** + * A complement (bitwise NOT) expression. + * ```rb + * ~x + * ``` + */ +class ComplementExpr extends UnaryBitwiseOperation, TComplementExpr { + final override string getAPrimaryQlClass() { result = "ComplementExpr" } +} + +/** + * A call to the special `defined?` operator. + * ```rb + * defined? some_method + * ``` + */ +class DefinedExpr extends UnaryOperation, TDefinedExpr { + final override string getAPrimaryQlClass() { result = "DefinedExpr" } +} + +/** A binary operation. */ +class BinaryOperation extends Operation, MethodCall instanceof BinaryOperationImpl { + final override string toString() { result = "... " + this.getOperator() + " ..." } + + override AstNode getAChild(string pred) { + result = Operation.super.getAChild(pred) + or + result = MethodCall.super.getAChild(pred) + or + pred = "getLeftOperand" and result = this.getLeftOperand() + or + pred = "getRightOperand" and result = this.getRightOperand() + } + + /** Gets the left operand of this binary operation. */ + final Stmt getLeftOperand() { result = super.getLeftOperandImpl() } + + /** Gets the right operand of this binary operation. */ + final Stmt getRightOperand() { result = super.getRightOperandImpl() } +} + +/** + * A binary arithmetic operation. + */ +class BinaryArithmeticOperation extends BinaryOperation, TBinaryArithmeticOperation { } + +/** + * An add expression. + * ```rb + * x + 1 + * ``` + */ +class AddExpr extends BinaryArithmeticOperation, TAddExpr { + final override string getAPrimaryQlClass() { result = "AddExpr" } +} + +/** + * A subtract expression. + * ```rb + * x - 3 + * ``` + */ +class SubExpr extends BinaryArithmeticOperation, TSubExpr { + final override string getAPrimaryQlClass() { result = "SubExpr" } +} + +/** + * A multiply expression. + * ```rb + * x * 10 + * ``` + */ +class MulExpr extends BinaryArithmeticOperation, TMulExpr { + final override string getAPrimaryQlClass() { result = "MulExpr" } +} + +/** + * A divide expression. + * ```rb + * x / y + * ``` + */ +class DivExpr extends BinaryArithmeticOperation, TDivExpr { + final override string getAPrimaryQlClass() { result = "DivExpr" } +} + +/** + * A modulo expression. + * ```rb + * x % 2 + * ``` + */ +class ModuloExpr extends BinaryArithmeticOperation, TModuloExpr { + final override string getAPrimaryQlClass() { result = "ModuloExpr" } +} + +/** + * An exponent expression. + * ```rb + * x ** 2 + * ``` + */ +class ExponentExpr extends BinaryArithmeticOperation, TExponentExpr { + final override string getAPrimaryQlClass() { result = "ExponentExpr" } +} + +/** + * A binary logical operation. + */ +class BinaryLogicalOperation extends BinaryOperation, TBinaryLogicalOperation { } + +/** + * A logical AND operation, using either `and` or `&&`. + * ```rb + * x and y + * a && b + * ``` + */ +class LogicalAndExpr extends BinaryLogicalOperation, TLogicalAndExpr { + final override string getAPrimaryQlClass() { result = "LogicalAndExpr" } +} + +/** + * A logical OR operation, using either `or` or `||`. + * ```rb + * x or y + * a || b + * ``` + */ +class LogicalOrExpr extends BinaryLogicalOperation, TLogicalOrExpr { + final override string getAPrimaryQlClass() { result = "LogicalOrExpr" } +} + +/** + * A binary bitwise operation. + */ +class BinaryBitwiseOperation extends BinaryOperation, TBinaryBitwiseOperation { } + +/** + * A left-shift operation. + * ```rb + * x << n + * ``` + */ +class LShiftExpr extends BinaryBitwiseOperation, TLShiftExpr { + final override string getAPrimaryQlClass() { result = "LShiftExpr" } +} + +/** + * A right-shift operation. + * ```rb + * x >> n + * ``` + */ +class RShiftExpr extends BinaryBitwiseOperation, TRShiftExpr { + final override string getAPrimaryQlClass() { result = "RShiftExpr" } +} + +/** + * A bitwise AND operation. + * ```rb + * x & 0xff + * ``` + */ +class BitwiseAndExpr extends BinaryBitwiseOperation, TBitwiseAndExpr { + final override string getAPrimaryQlClass() { result = "BitwiseAndExpr" } +} + +/** + * A bitwise OR operation. + * ```rb + * x | 0x01 + * ``` + */ +class BitwiseOrExpr extends BinaryBitwiseOperation, TBitwiseOrExpr { + final override string getAPrimaryQlClass() { result = "BitwiseOrExpr" } +} + +/** + * An XOR (exclusive OR) operation. + * ```rb + * x ^ y + * ``` + */ +class BitwiseXorExpr extends BinaryBitwiseOperation, TBitwiseXorExpr { + final override string getAPrimaryQlClass() { result = "BitwiseXorExpr" } +} + +/** + * A comparison operation. That is, either an equality operation or a + * relational operation. + */ +class ComparisonOperation extends BinaryOperation, TComparisonOperation { } + +/** + * An equality operation. + */ +class EqualityOperation extends ComparisonOperation, TEqualityOperation { } + +/** + * An equals expression. + * ```rb + * x == y + * ``` + */ +class EqExpr extends EqualityOperation, TEqExpr { + final override string getAPrimaryQlClass() { result = "EqExpr" } +} + +/** + * A not-equals expression. + * ```rb + * x != y + * ``` + */ +class NEExpr extends EqualityOperation, TNEExpr { + final override string getAPrimaryQlClass() { result = "NEExpr" } +} + +/** + * A case-equality (or 'threequals') expression. + * ```rb + * String === "foo" + * ``` + */ +class CaseEqExpr extends EqualityOperation, TCaseEqExpr { + final override string getAPrimaryQlClass() { result = "CaseEqExpr" } +} + +/** + * A relational operation, that is, one of `<=`, `<`, `>`, or `>=`. + */ +class RelationalOperation extends ComparisonOperation, TRelationalOperation { + /** Gets the greater operand. */ + Expr getGreaterOperand() { none() } + + /** Gets the lesser operand. */ + Expr getLesserOperand() { none() } + + final override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getGreaterOperand" and result = this.getGreaterOperand() + or + pred = "getLesserOperand" and result = this.getLesserOperand() + } +} + +/** + * A greater-than expression. + * ```rb + * x > 0 + * ``` + */ +class GTExpr extends RelationalOperation, TGTExpr { + final override string getAPrimaryQlClass() { result = "GTExpr" } + + final override Expr getGreaterOperand() { result = this.getLeftOperand() } + + final override Expr getLesserOperand() { result = this.getRightOperand() } +} + +/** + * A greater-than-or-equal expression. + * ```rb + * x >= 0 + * ``` + */ +class GEExpr extends RelationalOperation, TGEExpr { + final override string getAPrimaryQlClass() { result = "GEExpr" } + + final override Expr getGreaterOperand() { result = this.getLeftOperand() } + + final override Expr getLesserOperand() { result = this.getRightOperand() } +} + +/** + * A less-than expression. + * ```rb + * x < 10 + * ``` + */ +class LTExpr extends RelationalOperation, TLTExpr { + final override string getAPrimaryQlClass() { result = "LTExpr" } + + final override Expr getGreaterOperand() { result = this.getRightOperand() } + + final override Expr getLesserOperand() { result = this.getLeftOperand() } +} + +/** + * A less-than-or-equal expression. + * ```rb + * x <= 10 + * ``` + */ +class LEExpr extends RelationalOperation, TLEExpr { + final override string getAPrimaryQlClass() { result = "LEExpr" } + + final override Expr getGreaterOperand() { result = this.getRightOperand() } + + final override Expr getLesserOperand() { result = this.getLeftOperand() } +} + +/** + * A three-way comparison ('spaceship') expression. + * ```rb + * a <=> b + * ``` + */ +class SpaceshipExpr extends BinaryOperation, TSpaceshipExpr { + final override string getAPrimaryQlClass() { result = "SpaceshipExpr" } +} + +/** + * A regexp match expression. + * ```rb + * input =~ /\d/ + * ``` + */ +class RegExpMatchExpr extends BinaryOperation, TRegExpMatchExpr { + final override string getAPrimaryQlClass() { result = "RegExpMatchExpr" } +} + +/** + * A regexp-doesn't-match expression. + * ```rb + * input !~ /\d/ + * ``` + */ +class NoRegExpMatchExpr extends BinaryOperation, TNoRegExpMatchExpr { + final override string getAPrimaryQlClass() { result = "NoRegExpMatchExpr" } +} + +/** + * A binary assignment operation, including `=`, `+=`, `&=`, etc. + * + * This is a QL base class for all assignments. + */ +class Assignment extends Operation instanceof AssignmentImpl { + /** Gets the left hand side of this assignment. */ + final Pattern getLeftOperand() { result = super.getLeftOperandImpl() } + + /** Gets the right hand side of this assignment. */ + final Expr getRightOperand() { result = super.getRightOperandImpl() } + + final override string toString() { result = "... " + this.getOperator() + " ..." } + + override AstNode getAChild(string pred) { + result = Operation.super.getAChild(pred) + or + pred = "getLeftOperand" and result = getLeftOperand() + or + pred = "getRightOperand" and result = getRightOperand() + } +} + +/** + * An assignment operation with the operator `=`. + * ```rb + * x = 123 + * ``` + */ +class AssignExpr extends Assignment, TAssignExpr { + final override string getAPrimaryQlClass() { result = "AssignExpr" } +} + +/** + * A binary assignment operation other than `=`. + */ +class AssignOperation extends Assignment instanceof AssignOperationImpl { } + +/** + * An arithmetic assignment operation: `+=`, `-=`, `*=`, `/=`, `**=`, and `%=`. + */ +class AssignArithmeticOperation extends AssignOperation, TAssignArithmeticOperation { } + +/** + * A `+=` assignment expression. + * ```rb + * x += 1 + * ``` + */ +class AssignAddExpr extends AssignArithmeticOperation, TAssignAddExpr { + final override string getAPrimaryQlClass() { result = "AssignAddExpr" } +} + +/** + * A `-=` assignment expression. + * ```rb + * x -= 3 + * ``` + */ +class AssignSubExpr extends AssignArithmeticOperation, TAssignSubExpr { + final override string getAPrimaryQlClass() { result = "AssignSubExpr" } +} + +/** + * A `*=` assignment expression. + * ```rb + * x *= 10 + * ``` + */ +class AssignMulExpr extends AssignArithmeticOperation, TAssignMulExpr { + final override string getAPrimaryQlClass() { result = "AssignMulExpr" } +} + +/** + * A `/=` assignment expression. + * ```rb + * x /= y + * ``` + */ +class AssignDivExpr extends AssignArithmeticOperation, TAssignDivExpr { + final override string getAPrimaryQlClass() { result = "AssignDivExpr" } +} + +/** + * A `%=` assignment expression. + * ```rb + * x %= 4 + * ``` + */ +class AssignModuloExpr extends AssignArithmeticOperation, TAssignModuloExpr { + final override string getAPrimaryQlClass() { result = "AssignModuloExpr" } +} + +/** + * A `**=` assignment expression. + * ```rb + * x **= 2 + * ``` + */ +class AssignExponentExpr extends AssignArithmeticOperation, TAssignExponentExpr { + final override string getAPrimaryQlClass() { result = "AssignExponentExpr" } +} + +/** + * A logical assignment operation: `&&=` and `||=`. + */ +class AssignLogicalOperation extends AssignOperation, TAssignLogicalOperation { } + +/** + * A logical AND assignment operation. + * ```rb + * x &&= y.even? + * ``` + */ +class AssignLogicalAndExpr extends AssignLogicalOperation, TAssignLogicalAndExpr { + final override string getAPrimaryQlClass() { result = "AssignLogicalAndExpr" } +} + +/** + * A logical OR assignment operation. + * ```rb + * x ||= y + * ``` + */ +class AssignLogicalOrExpr extends AssignLogicalOperation, TAssignLogicalOrExpr { + final override string getAPrimaryQlClass() { result = "AssignLogicalOrExpr" } +} + +/** + * A bitwise assignment operation: `<<=`, `>>=`, `&=`, `|=` and `^=`. + */ +class AssignBitwiseOperation extends AssignOperation, TAssignBitwiseOperation { } + +/** + * A left-shift assignment operation. + * ```rb + * x <<= 3 + * ``` + */ +class AssignLShiftExpr extends AssignBitwiseOperation, TAssignLShiftExpr { + final override string getAPrimaryQlClass() { result = "AssignLShiftExpr" } +} + +/** + * A right-shift assignment operation. + * ```rb + * x >>= 3 + * ``` + */ +class AssignRShiftExpr extends AssignBitwiseOperation, TAssignRShiftExpr { + final override string getAPrimaryQlClass() { result = "AssignRShiftExpr" } +} + +/** + * A bitwise AND assignment operation. + * ```rb + * x &= 0xff + * ``` + */ +class AssignBitwiseAndExpr extends AssignBitwiseOperation, TAssignBitwiseAndExpr { + final override string getAPrimaryQlClass() { result = "AssignBitwiseAndExpr" } +} + +/** + * A bitwise OR assignment operation. + * ```rb + * x |= 0x01 + * ``` + */ +class AssignBitwiseOrExpr extends AssignBitwiseOperation, TAssignBitwiseOrExpr { + final override string getAPrimaryQlClass() { result = "AssignBitwiseOrExpr" } +} + +/** + * An XOR (exclusive OR) assignment operation. + * ```rb + * x ^= y + * ``` + */ +class AssignBitwiseXorExpr extends AssignBitwiseOperation, TAssignBitwiseXorExpr { + final override string getAPrimaryQlClass() { result = "AssignBitwiseXorExpr" } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/Parameter.qll b/ruby/ql/lib/codeql/ruby/ast/Parameter.qll new file mode 100644 index 000000000000..cb7c70c82a53 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/Parameter.qll @@ -0,0 +1,235 @@ +private import codeql.ruby.AST +private import internal.AST +private import internal.Variable +private import internal.Parameter +private import internal.TreeSitter + +/** A parameter. */ +class Parameter extends AstNode, TParameter { + /** Gets the callable that this parameter belongs to. */ + final Callable getCallable() { result.getAParameter() = this } + + /** Gets the zero-based position of this parameter. */ + final int getPosition() { this = any(Callable c).getParameter(result) } + + /** Gets a variable introduced by this parameter. */ + LocalVariable getAVariable() { none() } + + /** Gets the variable named `name` introduced by this parameter. */ + final LocalVariable getVariable(string name) { + result = this.getAVariable() and + result.getName() = name + } +} + +/** + * A parameter defined using a pattern. + * + * This includes both simple parameters and tuple parameters. + */ +class PatternParameter extends Parameter, Pattern, TPatternParameter { + override LocalVariable getAVariable() { result = Pattern.super.getAVariable() } +} + +/** A parameter defined using a tuple pattern. */ +class TuplePatternParameter extends PatternParameter, TuplePattern, TTuplePatternParameter { + final override LocalVariable getAVariable() { result = TuplePattern.super.getAVariable() } + + final override string getAPrimaryQlClass() { result = "TuplePatternParameter" } + + override AstNode getAChild(string pred) { result = TuplePattern.super.getAChild(pred) } +} + +/** A named parameter. */ +class NamedParameter extends Parameter, TNamedParameter { + /** Gets the name of this parameter. */ + string getName() { none() } + + /** Holds if the name of this parameter is `name`. */ + final predicate hasName(string name) { this.getName() = name } + + /** Gets the variable introduced by this parameter. */ + LocalVariable getVariable() { none() } + + override LocalVariable getAVariable() { result = this.getVariable() } + + /** Gets an access to this parameter. */ + final VariableAccess getAnAccess() { result = this.getVariable().getAnAccess() } + + /** Gets the access that defines the underlying local variable. */ + final VariableAccess getDefiningAccess() { result = this.getVariable().getDefiningAccess() } + + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getDefiningAccess" and + result = this.getDefiningAccess() + } +} + +/** A simple (normal) parameter. */ +class SimpleParameter extends NamedParameter, PatternParameter, VariablePattern, TSimpleParameter { + private Ruby::Identifier g; + + SimpleParameter() { this = TSimpleParameter(g) } + + final override string getName() { result = g.getValue() } + + final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g) } + + final override LocalVariable getAVariable() { result = this.getVariable() } + + final override string getAPrimaryQlClass() { result = "SimpleParameter" } + + final override string toString() { result = this.getName() } +} + +/** + * A parameter that is a block. For example, `&bar` in the following code: + * ```rb + * def foo(&bar) + * bar.call if block_given? + * end + * ``` + */ +class BlockParameter extends NamedParameter, TBlockParameter { + private Ruby::BlockParameter g; + + BlockParameter() { this = TBlockParameter(g) } + + final override string getName() { result = g.getName().getValue() } + + final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) } + + final override string toString() { result = "&" + this.getName() } + + final override string getAPrimaryQlClass() { result = "BlockParameter" } +} + +/** + * A hash-splat (or double-splat) parameter. For example, `**options` in the + * following code: + * ```rb + * def foo(bar, **options) + * ... + * end + * ``` + */ +class HashSplatParameter extends NamedParameter, THashSplatParameter { + private Ruby::HashSplatParameter g; + + HashSplatParameter() { this = THashSplatParameter(g) } + + final override string getAPrimaryQlClass() { result = "HashSplatParameter" } + + final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) } + + final override string toString() { result = "**" + this.getName() } + + final override string getName() { result = g.getName().getValue() } +} + +/** + * A keyword parameter, including a default value if the parameter is optional. + * For example, in the following example, `foo` is a keyword parameter with a + * default value of `0`, and `bar` is a mandatory keyword parameter with no + * default value mandatory parameter). + * ```rb + * def f(foo: 0, bar:) + * foo * 10 + bar + * end + * ``` + */ +class KeywordParameter extends NamedParameter, TKeywordParameter { + private Ruby::KeywordParameter g; + + KeywordParameter() { this = TKeywordParameter(g) } + + final override string getAPrimaryQlClass() { result = "KeywordParameter" } + + final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) } + + /** + * Gets the default value, i.e. the value assigned to the parameter when one + * is not provided by the caller. If the parameter is mandatory and does not + * have a default value, this predicate has no result. + */ + final Expr getDefaultValue() { toGenerated(result) = g.getValue() } + + /** + * Holds if the parameter is optional. That is, there is a default value that + * is used when the caller omits this parameter. + */ + final predicate isOptional() { exists(this.getDefaultValue()) } + + final override string toString() { result = this.getName() } + + final override string getName() { result = g.getName().getValue() } + + final override Location getLocation() { result = g.getName().getLocation() } + + final override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getDefaultValue" and result = this.getDefaultValue() + } +} + +/** + * An optional parameter. For example, the parameter `name` in the following + * code: + * ```rb + * def say_hello(name = 'Anon') + * puts "hello #{name}" + * end + * ``` + */ +class OptionalParameter extends NamedParameter, TOptionalParameter { + private Ruby::OptionalParameter g; + + OptionalParameter() { this = TOptionalParameter(g) } + + final override string getAPrimaryQlClass() { result = "OptionalParameter" } + + /** + * Gets the default value, i.e. the value assigned to the parameter when one + * is not provided by the caller. + */ + final Expr getDefaultValue() { toGenerated(result) = g.getValue() } + + final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) } + + final override string toString() { result = this.getName() } + + final override string getName() { result = g.getName().getValue() } + + final override Location getLocation() { result = g.getName().getLocation() } + + final override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getDefaultValue" and result = this.getDefaultValue() + } +} + +/** + * A splat parameter. For example, `*values` in the following code: + * ```rb + * def foo(bar, *values) + * ... + * end + * ``` + */ +class SplatParameter extends NamedParameter, TSplatParameter { + private Ruby::SplatParameter g; + + SplatParameter() { this = TSplatParameter(g) } + + final override string getAPrimaryQlClass() { result = "SplatParameter" } + + final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) } + + final override string toString() { result = "*" + this.getName() } + + final override string getName() { result = g.getName().getValue() } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/Pattern.qll b/ruby/ql/lib/codeql/ruby/ast/Pattern.qll new file mode 100644 index 000000000000..7275894b57d6 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/Pattern.qll @@ -0,0 +1,96 @@ +private import codeql.ruby.AST +private import codeql.Locations +private import internal.AST +private import internal.Pattern +private import internal.TreeSitter +private import internal.Variable + +/** A pattern. */ +class Pattern extends AstNode { + Pattern() { + explicitAssignmentNode(toGenerated(this), _) + or + implicitAssignmentNode(toGenerated(this)) + or + implicitParameterAssignmentNode(toGenerated(this), _) + or + this = getSynthChild(any(AssignExpr ae), 0) + } + + /** Gets a variable used in (or introduced by) this pattern. */ + Variable getAVariable() { none() } +} + +private class LhsExpr_ = + TVariableAccess or TTokenConstantAccess or TScopeResolutionConstantAccess or TMethodCall or + TSimpleParameter; + +/** + * A "left-hand-side" expression. An `LhsExpr` can occur on the left-hand side of + * operator assignments (`AssignOperation`), in patterns (`Pattern`) on the left-hand side of + * an assignment (`AssignExpr`) or for loop (`ForExpr`), and as the exception + * variable of a `rescue` clause (`RescueClause`). + * + * An `LhsExpr` can be a simple variable, a constant, a call, or an element reference: + * ```rb + * var = 1 + * var += 1 + * E = 1 + * foo.bar = 1 + * foo[0] = 1 + * rescue E => var + * ``` + */ +class LhsExpr extends Pattern, LhsExpr_, Expr { + override Variable getAVariable() { result = this.(VariableAccess).getVariable() } +} + +private class TVariablePattern = TVariableAccess or TSimpleParameter; + +/** A simple variable pattern. */ +class VariablePattern extends Pattern, LhsExpr, TVariablePattern { } + +/** + * A tuple pattern. + * + * This includes both tuple patterns in parameters and assignments. Example patterns: + * ```rb + * a, self.b = value + * (a, b), c[3] = value + * a, b, *rest, c, d = value + * ``` + */ +class TuplePattern extends Pattern, TTuplePattern { + override string getAPrimaryQlClass() { result = "TuplePattern" } + + private TuplePatternImpl getImpl() { result = toGenerated(this) } + + private Ruby::AstNode getChild(int i) { result = this.getImpl().getChildNode(i) } + + /** Gets the `i`th pattern in this tuple pattern. */ + final Pattern getElement(int i) { + exists(Ruby::AstNode c | c = this.getChild(i) | + toGenerated(result) = c.(Ruby::RestAssignment).getChild() + or + toGenerated(result) = c + ) + } + + /** Gets a sub pattern in this tuple pattern. */ + final Pattern getAnElement() { result = this.getElement(_) } + + /** + * Gets the index of the pattern with the `*` marker on it, if it exists. + * In the example below the index is `2`. + * ```rb + * a, b, *rest, c, d = value + * ``` + */ + final int getRestIndex() { result = this.getImpl().getRestIndex() } + + override Variable getAVariable() { result = this.getElement(_).getAVariable() } + + override string toString() { result = "(..., ...)" } + + override AstNode getAChild(string pred) { pred = "getElement" and result = getElement(_) } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/Scope.qll b/ruby/ql/lib/codeql/ruby/ast/Scope.qll new file mode 100644 index 000000000000..45fb00ae731a --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/Scope.qll @@ -0,0 +1,22 @@ +private import codeql.ruby.AST +private import internal.AST +private import internal.Scope +private import internal.TreeSitter + +class Scope extends AstNode, TScopeType { + private Scope::Range range; + + Scope() { range = toGenerated(this) } + + /** Gets the scope in which this scope is nested, if any. */ + Scope getOuterScope() { toGenerated(result) = range.getOuterScope() } + + /** Gets a variable that is declared in this scope. */ + final Variable getAVariable() { result.getDeclaringScope() = this } + + /** Gets the variable declared in this scope with the given name, if any. */ + final Variable getVariable(string name) { + result = this.getAVariable() and + result.getName() = name + } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/Statement.qll b/ruby/ql/lib/codeql/ruby/ast/Statement.qll new file mode 100644 index 000000000000..e3d77c2010c6 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/Statement.qll @@ -0,0 +1,248 @@ +private import codeql.ruby.AST +private import codeql.ruby.CFG +private import internal.AST +private import internal.TreeSitter +private import internal.Variable +private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl + +/** + * A statement. + * + * This is the root QL class for all statements. + */ +class Stmt extends AstNode, TStmt { + /** Gets a control-flow node for this statement, if any. */ + CfgNodes::AstCfgNode getAControlFlowNode() { result.getNode() = this } + + /** Gets the control-flow scope of this statement, if any. */ + CfgScope getCfgScope() { result = getCfgScope(this) } + + /** Gets the enclosing callable, if any. */ + Callable getEnclosingCallable() { result = this.getCfgScope() } +} + +/** + * An empty statement (`;`). + */ +class EmptyStmt extends Stmt, TEmptyStmt { + final override string getAPrimaryQlClass() { result = "EmptyStmt" } + + final override string toString() { result = ";" } +} + +/** + * A `begin` statement. + * ```rb + * begin + * puts "hello world" + * end + * ``` + */ +class BeginExpr extends BodyStmt, TBeginExpr { + final override string getAPrimaryQlClass() { result = "BeginExpr" } + + final override string toString() { result = "begin ... " } +} + +/** + * A `BEGIN` block. + * ```rb + * BEGIN { puts "starting ..." } + * ``` + */ +class BeginBlock extends StmtSequence, TBeginBlock { + private Ruby::BeginBlock g; + + BeginBlock() { this = TBeginBlock(g) } + + final override string getAPrimaryQlClass() { result = "BeginBlock" } + + final override string toString() { result = "BEGIN { ... }" } + + final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) } +} + +/** + * An `END` block. + * ```rb + * END { puts "shutting down" } + * ``` + */ +class EndBlock extends StmtSequence, TEndBlock { + private Ruby::EndBlock g; + + EndBlock() { this = TEndBlock(g) } + + final override string getAPrimaryQlClass() { result = "EndBlock" } + + final override string toString() { result = "END { ... }" } + + final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) } +} + +/** + * An `undef` statement. For example: + * ```rb + * - undef method_name + * - undef &&, :method_name + * - undef :"method_#{ name }" + * ``` + */ +class UndefStmt extends Stmt, TUndefStmt { + private Ruby::Undef g; + + UndefStmt() { this = TUndefStmt(g) } + + /** Gets the `n`th method name to undefine. */ + final MethodName getMethodName(int n) { toGenerated(result) = g.getChild(n) } + + /** Gets a method name to undefine. */ + final MethodName getAMethodName() { result = getMethodName(_) } + + final override string getAPrimaryQlClass() { result = "UndefStmt" } + + final override string toString() { result = "undef ..." } + + final override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getMethodName" and result = this.getMethodName(_) + } +} + +/** + * An `alias` statement. For example: + * ```rb + * - alias alias_name method_name + * - alias foo :method_name + * - alias bar :"method_#{ name }" + * ``` + */ +class AliasStmt extends Stmt, TAliasStmt { + private Ruby::Alias g; + + AliasStmt() { this = TAliasStmt(g) } + + /** Gets the new method name. */ + final MethodName getNewName() { toGenerated(result) = g.getName() } + + /** Gets the original method name. */ + final MethodName getOldName() { toGenerated(result) = g.getAlias() } + + final override string getAPrimaryQlClass() { result = "AliasStmt" } + + final override string toString() { result = "alias ..." } + + final override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getNewName" and result = this.getNewName() + or + pred = "getOldName" and result = this.getOldName() + } +} + +/** + * A statement that may return a value: `return`, `break` and `next`. + * + * ```rb + * return + * return value + * break + * break value + * next + * next value + * ``` + */ +class ReturningStmt extends Stmt, TReturningStmt { + private Ruby::ArgumentList getArgumentList() { + result = any(Ruby::Return g | this = TReturnStmt(g)).getChild() + or + result = any(Ruby::Break g | this = TBreakStmt(g)).getChild() + or + result = any(Ruby::Next g | this = TNextStmt(g)).getChild() + } + + /** Gets the returned value, if any. */ + final Expr getValue() { + toGenerated(result) = + any(Ruby::AstNode res | + exists(Ruby::ArgumentList a, int c | + a = this.getArgumentList() and c = count(a.getChild(_)) + | + res = a.getChild(0) and c = 1 + or + res = a and c > 1 + ) + ) + } + + final override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "getValue" and result = this.getValue() + } +} + +/** + * A `return` statement. + * ```rb + * return + * return value + * ``` + */ +class ReturnStmt extends ReturningStmt, TReturnStmt { + final override string getAPrimaryQlClass() { result = "ReturnStmt" } + + final override string toString() { result = "return" } +} + +/** + * A `break` statement. + * ```rb + * break + * break value + * ``` + */ +class BreakStmt extends ReturningStmt, TBreakStmt { + final override string getAPrimaryQlClass() { result = "BreakStmt" } + + final override string toString() { result = "break" } +} + +/** + * A `next` statement. + * ```rb + * next + * next value + * ``` + */ +class NextStmt extends ReturningStmt, TNextStmt { + final override string getAPrimaryQlClass() { result = "NextStmt" } + + final override string toString() { result = "next" } +} + +/** + * A `redo` statement. + * ```rb + * redo + * ``` + */ +class RedoStmt extends Stmt, TRedoStmt { + final override string getAPrimaryQlClass() { result = "RedoStmt" } + + final override string toString() { result = "redo" } +} + +/** + * A `retry` statement. + * ```rb + * retry + * ``` + */ +class RetryStmt extends Stmt, TRetryStmt { + final override string getAPrimaryQlClass() { result = "RetryStmt" } + + final override string toString() { result = "retry" } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/Variable.qll b/ruby/ql/lib/codeql/ruby/ast/Variable.qll new file mode 100644 index 000000000000..b16d046d8868 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/Variable.qll @@ -0,0 +1,187 @@ +/** Provides classes for modeling program variables. */ + +private import codeql.ruby.AST +private import codeql.Locations +private import internal.AST +private import internal.TreeSitter +private import internal.Variable + +/** A variable declared in a scope. */ +class Variable instanceof VariableImpl { + /** Gets the name of this variable. */ + final string getName() { result = super.getNameImpl() } + + /** Holds if the name of this variable is `name`. */ + final predicate hasName(string name) { this.getName() = name } + + /** Gets a textual representation of this variable. */ + final string toString() { result = this.getName() } + + /** Gets the location of this variable. */ + final Location getLocation() { result = super.getLocationImpl() } + + /** Gets the scope this variable is declared in. */ + final Scope getDeclaringScope() { + toGenerated(result) = this.(VariableReal).getDeclaringScopeImpl() + } + + /** Gets an access to this variable. */ + VariableAccess getAnAccess() { result.getVariable() = this } +} + +/** A local variable. */ +class LocalVariable extends Variable, TLocalVariable { + override LocalVariableAccess getAnAccess() { result.getVariable() = this } + + /** Gets the access where this local variable is first introduced. */ + VariableAccess getDefiningAccess() { result = this.(LocalVariableReal).getDefiningAccessImpl() } + + /** + * Holds if this variable is captured. For example in + * + * ```rb + * def m x + * x.times do |y| + * puts x + * end + * puts x + * end + * ``` + * + * `x` is a captured variable, whereas `y` is not. + */ + predicate isCaptured() { this.getAnAccess().isCapturedAccess() } +} + +/** A global variable. */ +class GlobalVariable extends Variable instanceof GlobalVariableImpl { + final override GlobalVariableAccess getAnAccess() { result.getVariable() = this } +} + +/** An instance variable. */ +class InstanceVariable extends Variable instanceof InstanceVariableImpl { + /** Holds is this variable is a class instance variable. */ + final predicate isClassInstanceVariable() { super.isClassInstanceVariable() } + + final override InstanceVariableAccess getAnAccess() { result.getVariable() = this } +} + +/** A class variable. */ +class ClassVariable extends Variable instanceof ClassVariableImpl { + final override ClassVariableAccess getAnAccess() { result.getVariable() = this } +} + +/** An access to a variable. */ +class VariableAccess extends Expr instanceof VariableAccessImpl { + /** Gets the variable this identifier refers to. */ + final Variable getVariable() { result = super.getVariableImpl() } + + /** + * Holds if this access is a write access belonging to the explicit + * assignment `assignment`. For example, in + * + * ```rb + * a, b = foo + * ``` + * + * both `a` and `b` are write accesses belonging to the same assignment. + */ + predicate isExplicitWrite(AstNode assignment) { + explicitWriteAccess(toGenerated(this), toGenerated(assignment)) + or + this = assignment.(AssignExpr).getLeftOperand() + } + + /** + * Holds if this access is a write access belonging to an implicit assignment. + * For example, in + * + * ```rb + * def m elements + * for e in elements do + * puts e + * end + * end + * ``` + * + * the access to `elements` in the parameter list is an implicit assignment, + * as is the first access to `e`. + */ + predicate isImplicitWrite() { implicitWriteAccess(toGenerated(this)) } + + final override string toString() { result = VariableAccessImpl.super.toString() } +} + +/** An access to a variable where the value is updated. */ +class VariableWriteAccess extends VariableAccess { + VariableWriteAccess() { + this.isExplicitWrite(_) or + this.isImplicitWrite() + } +} + +/** An access to a variable where the value is read. */ +class VariableReadAccess extends VariableAccess { + VariableReadAccess() { not this instanceof VariableWriteAccess } +} + +/** An access to a local variable. */ +class LocalVariableAccess extends VariableAccess instanceof LocalVariableAccessImpl { + final override string getAPrimaryQlClass() { result = "LocalVariableAccess" } + + /** + * Holds if this access is a captured variable access. For example in + * + * ```rb + * def m x + * x.times do |y| + * puts x + * end + * puts x + * end + * ``` + * + * the access to `x` in the first `puts x` is a captured access, while + * the access to `x` in the second `puts x` is not. + */ + final predicate isCapturedAccess() { isCapturedAccess(this) } +} + +/** An access to a local variable where the value is updated. */ +class LocalVariableWriteAccess extends LocalVariableAccess, VariableWriteAccess { } + +/** An access to a local variable where the value is read. */ +class LocalVariableReadAccess extends LocalVariableAccess, VariableReadAccess { } + +/** An access to a global variable. */ +class GlobalVariableAccess extends VariableAccess instanceof GlobalVariableAccessImpl { + final override string getAPrimaryQlClass() { result = "GlobalVariableAccess" } +} + +/** An access to a global variable where the value is updated. */ +class GlobalVariableWriteAccess extends GlobalVariableAccess, VariableWriteAccess { } + +/** An access to a global variable where the value is read. */ +class GlobalVariableReadAccess extends GlobalVariableAccess, VariableReadAccess { } + +/** An access to an instance variable. */ +class InstanceVariableAccess extends VariableAccess instanceof InstanceVariableAccessImpl { + final override string getAPrimaryQlClass() { result = "InstanceVariableAccess" } +} + +/** An access to an instance variable where the value is updated. */ +class InstanceVariableWriteAccess extends InstanceVariableAccess, VariableWriteAccess { } + +/** An access to an instance variable where the value is read. */ +class InstanceVariableReadAccess extends InstanceVariableAccess, VariableReadAccess { } + +/** An access to a class variable. */ +class ClassVariableAccess extends VariableAccess instanceof ClassVariableAccessRealImpl { + final override string getAPrimaryQlClass() { result = "ClassVariableAccess" } +} + +/** An access to a class variable where the value is updated. */ +class ClassVariableWriteAccess extends ClassVariableAccess, VariableWriteAccess { } + +/** An access to a class variable where the value is read. */ +class ClassVariableReadAccess extends ClassVariableAccess, VariableReadAccess { } diff --git a/ruby/ql/lib/codeql/ruby/ast/internal/AST.qll b/ruby/ql/lib/codeql/ruby/ast/internal/AST.qll new file mode 100644 index 000000000000..28bd75e88b26 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/internal/AST.qll @@ -0,0 +1,699 @@ +import codeql.Locations +private import TreeSitter +private import codeql.ruby.ast.internal.Call +private import codeql.ruby.ast.internal.Parameter +private import codeql.ruby.ast.internal.Variable +private import codeql.ruby.AST as AST +private import Synthesis + +module MethodName { + predicate range(Ruby::UnderscoreMethodName g) { + exists(Ruby::Undef u | u.getChild(_) = g) + or + exists(Ruby::Alias a | a.getName() = g or a.getAlias() = g) + } + + class Token = + @ruby_setter or @ruby_token_class_variable or @ruby_token_constant or + @ruby_token_global_variable or @ruby_token_identifier or @ruby_token_instance_variable or + @ruby_token_operator; +} + +private predicate mkSynthChild(SynthKind kind, AST::AstNode parent, int i) { + any(Synthesis s).child(parent, i, SynthChild(kind)) +} + +cached +private module Cached { + cached + newtype TAstNode = + TAddExprReal(Ruby::Binary g) { g instanceof @ruby_binary_plus } or + TAddExprSynth(AST::AstNode parent, int i) { mkSynthChild(AddExprKind(), parent, i) } or + TAliasStmt(Ruby::Alias g) or + TArgumentList(Ruby::AstNode g) { + ( + g.getParent() instanceof Ruby::Break or + g.getParent() instanceof Ruby::Return or + g.getParent() instanceof Ruby::Next or + g.getParent() instanceof Ruby::Assignment or + g.getParent() instanceof Ruby::OperatorAssignment + ) and + ( + strictcount(g.(Ruby::ArgumentList).getChild(_)) > 1 + or + g instanceof Ruby::RightAssignmentList + ) + } or + TAssignAddExpr(Ruby::OperatorAssignment g) { g instanceof @ruby_operator_assignment_plusequal } or + TAssignBitwiseAndExpr(Ruby::OperatorAssignment g) { + g instanceof @ruby_operator_assignment_ampersandequal + } or + TAssignBitwiseOrExpr(Ruby::OperatorAssignment g) { + g instanceof @ruby_operator_assignment_pipeequal + } or + TAssignBitwiseXorExpr(Ruby::OperatorAssignment g) { + g instanceof @ruby_operator_assignment_caretequal + } or + TAssignDivExpr(Ruby::OperatorAssignment g) { g instanceof @ruby_operator_assignment_slashequal } or + TAssignExponentExpr(Ruby::OperatorAssignment g) { + g instanceof @ruby_operator_assignment_starstarequal + } or + TAssignExprReal(Ruby::Assignment g) or + TAssignExprSynth(AST::AstNode parent, int i) { mkSynthChild(AssignExprKind(), parent, i) } or + TAssignLShiftExpr(Ruby::OperatorAssignment g) { + g instanceof @ruby_operator_assignment_langlelangleequal + } or + TAssignLogicalAndExpr(Ruby::OperatorAssignment g) { + g instanceof @ruby_operator_assignment_ampersandampersandequal + } or + TAssignLogicalOrExpr(Ruby::OperatorAssignment g) { + g instanceof @ruby_operator_assignment_pipepipeequal + } or + TAssignModuloExpr(Ruby::OperatorAssignment g) { + g instanceof @ruby_operator_assignment_percentequal + } or + TAssignMulExpr(Ruby::OperatorAssignment g) { g instanceof @ruby_operator_assignment_starequal } or + TAssignRShiftExpr(Ruby::OperatorAssignment g) { + g instanceof @ruby_operator_assignment_ranglerangleequal + } or + TAssignSubExpr(Ruby::OperatorAssignment g) { g instanceof @ruby_operator_assignment_minusequal } or + TBareStringLiteral(Ruby::BareString g) or + TBareSymbolLiteral(Ruby::BareSymbol g) or + TBeginBlock(Ruby::BeginBlock g) or + TBeginExpr(Ruby::Begin g) or + TBitwiseAndExprReal(Ruby::Binary g) { g instanceof @ruby_binary_ampersand } or + TBitwiseAndExprSynth(AST::AstNode parent, int i) { + mkSynthChild(BitwiseAndExprKind(), parent, i) + } or + TBitwiseOrExprReal(Ruby::Binary g) { g instanceof @ruby_binary_pipe } or + TBitwiseOrExprSynth(AST::AstNode parent, int i) { mkSynthChild(BitwiseOrExprKind(), parent, i) } or + TBitwiseXorExprReal(Ruby::Binary g) { g instanceof @ruby_binary_caret } or + TBitwiseXorExprSynth(AST::AstNode parent, int i) { + mkSynthChild(BitwiseXorExprKind(), parent, i) + } or + TBlockArgument(Ruby::BlockArgument g) or + TBlockParameter(Ruby::BlockParameter g) or + TBraceBlock(Ruby::Block g) { not g.getParent() instanceof Ruby::Lambda } or + TBreakStmt(Ruby::Break g) or + TCaseEqExpr(Ruby::Binary g) { g instanceof @ruby_binary_equalequalequal } or + TCaseExpr(Ruby::Case g) or + TCharacterLiteral(Ruby::Character g) or + TClassDeclaration(Ruby::Class g) or + TClassVariableAccessReal(Ruby::ClassVariable g, AST::ClassVariable v) { + ClassVariableAccess::range(g, v) + } or + TClassVariableAccessSynth(AST::AstNode parent, int i, AST::ClassVariable v) { + mkSynthChild(ClassVariableAccessKind(v), parent, i) + } or + TComplementExpr(Ruby::Unary g) { g instanceof @ruby_unary_tilde } or + TComplexLiteral(Ruby::Complex g) or + TConstantReadAccessSynth(AST::AstNode parent, int i, string value) { + mkSynthChild(ConstantReadAccessKind(value), parent, i) + } or + TDefinedExpr(Ruby::Unary g) { g instanceof @ruby_unary_definedquestion } or + TDelimitedSymbolLiteral(Ruby::DelimitedSymbol g) or + TDestructuredLeftAssignment(Ruby::DestructuredLeftAssignment g) { + not strictcount(int i | exists(g.getParent().(Ruby::LeftAssignmentList).getChild(i))) = 1 + } or + TDivExprReal(Ruby::Binary g) { g instanceof @ruby_binary_slash } or + TDivExprSynth(AST::AstNode parent, int i) { mkSynthChild(DivExprKind(), parent, i) } or + TDo(Ruby::Do g) or + TDoBlock(Ruby::DoBlock g) { not g.getParent() instanceof Ruby::Lambda } or + TElementReference(Ruby::ElementReference g) or + TElse(Ruby::Else g) or + TElsif(Ruby::Elsif g) or + TEmptyStmt(Ruby::EmptyStatement g) or + TEndBlock(Ruby::EndBlock g) or + TEnsure(Ruby::Ensure g) or + TEqExpr(Ruby::Binary g) { g instanceof @ruby_binary_equalequal } or + TExponentExprReal(Ruby::Binary g) { g instanceof @ruby_binary_starstar } or + TExponentExprSynth(AST::AstNode parent, int i) { mkSynthChild(ExponentExprKind(), parent, i) } or + TFalseLiteral(Ruby::False g) or + TFloatLiteral(Ruby::Float g) { not any(Ruby::Rational r).getChild() = g } or + TForExpr(Ruby::For g) or + TForIn(Ruby::In g) or // TODO REMOVE + TGEExpr(Ruby::Binary g) { g instanceof @ruby_binary_rangleequal } or + TGTExpr(Ruby::Binary g) { g instanceof @ruby_binary_rangle } or + TGlobalVariableAccessReal(Ruby::GlobalVariable g, AST::GlobalVariable v) { + GlobalVariableAccess::range(g, v) + } or + TGlobalVariableAccessSynth(AST::AstNode parent, int i, AST::GlobalVariable v) { + mkSynthChild(GlobalVariableAccessKind(v), parent, i) + } or + THashKeySymbolLiteral(Ruby::HashKeySymbol g) or + THashLiteral(Ruby::Hash g) or + THashSplatExpr(Ruby::HashSplatArgument g) or + THashSplatParameter(Ruby::HashSplatParameter g) or + THereDoc(Ruby::HeredocBeginning g) or + TIdentifierMethodCall(Ruby::Identifier g) { isIdentifierMethodCall(g) } or + TIf(Ruby::If g) or + TIfModifierExpr(Ruby::IfModifier g) or + TInstanceVariableAccessReal(Ruby::InstanceVariable g, AST::InstanceVariable v) { + InstanceVariableAccess::range(g, v) + } or + TInstanceVariableAccessSynth(AST::AstNode parent, int i, AST::InstanceVariable v) { + mkSynthChild(InstanceVariableAccessKind(v), parent, i) + } or + TIntegerLiteralReal(Ruby::Integer g) { not any(Ruby::Rational r).getChild() = g } or + TIntegerLiteralSynth(AST::AstNode parent, int i, int value) { + mkSynthChild(IntegerLiteralKind(value), parent, i) + } or + TKeywordParameter(Ruby::KeywordParameter g) or + TLEExpr(Ruby::Binary g) { g instanceof @ruby_binary_langleequal } or + TLShiftExprReal(Ruby::Binary g) { g instanceof @ruby_binary_langlelangle } or + TLShiftExprSynth(AST::AstNode parent, int i) { mkSynthChild(LShiftExprKind(), parent, i) } or + TLTExpr(Ruby::Binary g) { g instanceof @ruby_binary_langle } or + TLambda(Ruby::Lambda g) or + TLeftAssignmentList(Ruby::LeftAssignmentList g) or + TLocalVariableAccessReal(Ruby::Identifier g, AST::LocalVariable v) { + LocalVariableAccess::range(g, v) + } or + TLocalVariableAccessSynth(AST::AstNode parent, int i, AST::LocalVariable v) { + mkSynthChild(LocalVariableAccessRealKind(v), parent, i) + or + mkSynthChild(LocalVariableAccessSynthKind(v), parent, i) + } or + TLogicalAndExprReal(Ruby::Binary g) { + g instanceof @ruby_binary_and or g instanceof @ruby_binary_ampersandampersand + } or + TLogicalAndExprSynth(AST::AstNode parent, int i) { + mkSynthChild(LogicalAndExprKind(), parent, i) + } or + TLogicalOrExprReal(Ruby::Binary g) { + g instanceof @ruby_binary_or or g instanceof @ruby_binary_pipepipe + } or + TLogicalOrExprSynth(AST::AstNode parent, int i) { mkSynthChild(LogicalOrExprKind(), parent, i) } or + TMethod(Ruby::Method g) or + TMethodCallSynth(AST::AstNode parent, int i, string name, boolean setter, int arity) { + mkSynthChild(MethodCallKind(name, setter, arity), parent, i) + } or + TModuleDeclaration(Ruby::Module g) or + TModuloExprReal(Ruby::Binary g) { g instanceof @ruby_binary_percent } or + TModuloExprSynth(AST::AstNode parent, int i) { mkSynthChild(ModuloExprKind(), parent, i) } or + TMulExprReal(Ruby::Binary g) { g instanceof @ruby_binary_star } or + TMulExprSynth(AST::AstNode parent, int i) { mkSynthChild(MulExprKind(), parent, i) } or + TNEExpr(Ruby::Binary g) { g instanceof @ruby_binary_bangequal } or + TNextStmt(Ruby::Next g) or + TNilLiteral(Ruby::Nil g) or + TNoRegExpMatchExpr(Ruby::Binary g) { g instanceof @ruby_binary_bangtilde } or + TNotExpr(Ruby::Unary g) { g instanceof @ruby_unary_bang or g instanceof @ruby_unary_not } or + TOptionalParameter(Ruby::OptionalParameter g) or + TPair(Ruby::Pair g) or + TParenthesizedExpr(Ruby::ParenthesizedStatements g) or + TRShiftExprReal(Ruby::Binary g) { g instanceof @ruby_binary_ranglerangle } or + TRShiftExprSynth(AST::AstNode parent, int i) { mkSynthChild(RShiftExprKind(), parent, i) } or + TRangeLiteralReal(Ruby::Range g) or + TRangeLiteralSynth(AST::AstNode parent, int i, boolean inclusive) { + mkSynthChild(RangeLiteralKind(inclusive), parent, i) + } or + TRationalLiteral(Ruby::Rational g) or + TRedoStmt(Ruby::Redo g) or + TRegExpLiteral(Ruby::Regex g) or + TRegExpMatchExpr(Ruby::Binary g) { g instanceof @ruby_binary_equaltilde } or + TRegularArrayLiteral(Ruby::Array g) or + TRegularMethodCall(Ruby::Call g) { isRegularMethodCall(g) } or + TRegularStringLiteral(Ruby::String g) or + TRegularSuperCall(Ruby::Call g) { g.getMethod() instanceof Ruby::Super } or + TRescueClause(Ruby::Rescue g) or + TRescueModifierExpr(Ruby::RescueModifier g) or + TRetryStmt(Ruby::Retry g) or + TReturnStmt(Ruby::Return g) or + TScopeResolutionConstantAccess(Ruby::ScopeResolution g, Ruby::Constant constant) { + constant = g.getName() and + ( + // A tree-sitter `scope_resolution` node with a `constant` name field is a + // read of that constant in any context where an identifier would be a + // vcall. + vcall(g) + or + explicitAssignmentNode(g, _) + ) + } or + TScopeResolutionMethodCall(Ruby::ScopeResolution g, Ruby::Identifier i) { + isScopeResolutionMethodCall(g, i) + } or + TSelfReal(Ruby::Self g) or + TSelfSynth(AST::AstNode parent, int i) { mkSynthChild(SelfKind(), parent, i) } or + TSimpleParameter(Ruby::Identifier g) { g instanceof Parameter::Range } or + TSimpleSymbolLiteral(Ruby::SimpleSymbol g) or + TSingletonClass(Ruby::SingletonClass g) or + TSingletonMethod(Ruby::SingletonMethod g) or + TSpaceshipExpr(Ruby::Binary g) { g instanceof @ruby_binary_langleequalrangle } or + TSplatExprReal(Ruby::SplatArgument g) or + TSplatExprSynth(AST::AstNode parent, int i) { mkSynthChild(SplatExprKind(), parent, i) } or + TSplatParameter(Ruby::SplatParameter g) or + TStmtSequenceSynth(AST::AstNode parent, int i) { mkSynthChild(StmtSequenceKind(), parent, i) } or + TStringArrayLiteral(Ruby::StringArray g) or + TStringConcatenation(Ruby::ChainedString g) or + TStringEscapeSequenceComponent(Ruby::EscapeSequence g) or + TStringInterpolationComponent(Ruby::Interpolation g) or + TStringTextComponent(Ruby::Token g) { + g instanceof Ruby::StringContent or g instanceof Ruby::HeredocContent + } or + TSubExprReal(Ruby::Binary g) { g instanceof @ruby_binary_minus } or + TSubExprSynth(AST::AstNode parent, int i) { mkSynthChild(SubExprKind(), parent, i) } or + TSubshellLiteral(Ruby::Subshell g) or + TSymbolArrayLiteral(Ruby::SymbolArray g) or + TTernaryIfExpr(Ruby::Conditional g) or + TThen(Ruby::Then g) or + TTokenConstantAccess(Ruby::Constant g) { + // A tree-sitter `constant` token is a read of that constant in any context + // where an identifier would be a vcall. + vcall(g) + or + explicitAssignmentNode(g, _) + } or + TTokenMethodName(MethodName::Token g) { MethodName::range(g) } or + TTokenSuperCall(Ruby::Super g) { vcall(g) } or + TToplevel(Ruby::Program g) or + TTrueLiteral(Ruby::True g) or + TTuplePatternParameter(Ruby::DestructuredParameter g) or + TUnaryMinusExpr(Ruby::Unary g) { g instanceof @ruby_unary_minus } or + TUnaryPlusExpr(Ruby::Unary g) { g instanceof @ruby_unary_plus } or + TUndefStmt(Ruby::Undef g) or + TUnlessExpr(Ruby::Unless g) or + TUnlessModifierExpr(Ruby::UnlessModifier g) or + TUntilExpr(Ruby::Until g) or + TUntilModifierExpr(Ruby::UntilModifier g) or + TWhenExpr(Ruby::When g) or + TWhileExpr(Ruby::While g) or + TWhileModifierExpr(Ruby::WhileModifier g) or + TYieldCall(Ruby::Yield g) + + /** + * Gets the underlying TreeSitter entity for a given AST node. This does not + * include synthesized AST nodes, because they are not the primary AST node + * for any given generated node. + */ + cached + Ruby::AstNode toGenerated(AST::AstNode n) { + n = TAddExprReal(result) or + n = TAliasStmt(result) or + n = TArgumentList(result) or + n = TAssignAddExpr(result) or + n = TAssignBitwiseAndExpr(result) or + n = TAssignBitwiseOrExpr(result) or + n = TAssignBitwiseXorExpr(result) or + n = TAssignDivExpr(result) or + n = TAssignExponentExpr(result) or + n = TAssignExprReal(result) or + n = TAssignLShiftExpr(result) or + n = TAssignLogicalAndExpr(result) or + n = TAssignLogicalOrExpr(result) or + n = TAssignModuloExpr(result) or + n = TAssignMulExpr(result) or + n = TAssignRShiftExpr(result) or + n = TAssignSubExpr(result) or + n = TBareStringLiteral(result) or + n = TBareSymbolLiteral(result) or + n = TBeginBlock(result) or + n = TBeginExpr(result) or + n = TBitwiseAndExprReal(result) or + n = TBitwiseOrExprReal(result) or + n = TBitwiseXorExprReal(result) or + n = TBlockArgument(result) or + n = TBlockParameter(result) or + n = TBraceBlock(result) or + n = TBreakStmt(result) or + n = TCaseEqExpr(result) or + n = TCaseExpr(result) or + n = TCharacterLiteral(result) or + n = TClassDeclaration(result) or + n = TClassVariableAccessReal(result, _) or + n = TComplementExpr(result) or + n = TComplexLiteral(result) or + n = TDefinedExpr(result) or + n = TDelimitedSymbolLiteral(result) or + n = TDestructuredLeftAssignment(result) or + n = TDivExprReal(result) or + n = TDo(result) or + n = TDoBlock(result) or + n = TElementReference(result) or + n = TElse(result) or + n = TElsif(result) or + n = TEmptyStmt(result) or + n = TEndBlock(result) or + n = TEnsure(result) or + n = TEqExpr(result) or + n = TExponentExprReal(result) or + n = TFalseLiteral(result) or + n = TFloatLiteral(result) or + n = TForExpr(result) or + n = TForIn(result) or // TODO REMOVE + n = TGEExpr(result) or + n = TGTExpr(result) or + n = TGlobalVariableAccessReal(result, _) or + n = THashKeySymbolLiteral(result) or + n = THashLiteral(result) or + n = THashSplatExpr(result) or + n = THashSplatParameter(result) or + n = THereDoc(result) or + n = TIdentifierMethodCall(result) or + n = TIf(result) or + n = TIfModifierExpr(result) or + n = TInstanceVariableAccessReal(result, _) or + n = TIntegerLiteralReal(result) or + n = TKeywordParameter(result) or + n = TLEExpr(result) or + n = TLShiftExprReal(result) or + n = TLTExpr(result) or + n = TLambda(result) or + n = TLeftAssignmentList(result) or + n = TLocalVariableAccessReal(result, _) or + n = TLogicalAndExprReal(result) or + n = TLogicalOrExprReal(result) or + n = TMethod(result) or + n = TModuleDeclaration(result) or + n = TModuloExprReal(result) or + n = TMulExprReal(result) or + n = TNEExpr(result) or + n = TNextStmt(result) or + n = TNilLiteral(result) or + n = TNoRegExpMatchExpr(result) or + n = TNotExpr(result) or + n = TOptionalParameter(result) or + n = TPair(result) or + n = TParenthesizedExpr(result) or + n = TRShiftExprReal(result) or + n = TRangeLiteralReal(result) or + n = TRationalLiteral(result) or + n = TRedoStmt(result) or + n = TRegExpLiteral(result) or + n = TRegExpMatchExpr(result) or + n = TRegularArrayLiteral(result) or + n = TRegularMethodCall(result) or + n = TRegularStringLiteral(result) or + n = TRegularSuperCall(result) or + n = TRescueClause(result) or + n = TRescueModifierExpr(result) or + n = TRetryStmt(result) or + n = TReturnStmt(result) or + n = TScopeResolutionConstantAccess(result, _) or + n = TScopeResolutionMethodCall(result, _) or + n = TSelfReal(result) or + n = TSimpleParameter(result) or + n = TSimpleSymbolLiteral(result) or + n = TSingletonClass(result) or + n = TSingletonMethod(result) or + n = TSpaceshipExpr(result) or + n = TSplatExprReal(result) or + n = TSplatParameter(result) or + n = TStringArrayLiteral(result) or + n = TStringConcatenation(result) or + n = TStringEscapeSequenceComponent(result) or + n = TStringInterpolationComponent(result) or + n = TStringTextComponent(result) or + n = TSubExprReal(result) or + n = TSubshellLiteral(result) or + n = TSymbolArrayLiteral(result) or + n = TTernaryIfExpr(result) or + n = TThen(result) or + n = TTokenConstantAccess(result) or + n = TTokenMethodName(result) or + n = TTokenSuperCall(result) or + n = TToplevel(result) or + n = TTrueLiteral(result) or + n = TTuplePatternParameter(result) or + n = TUnaryMinusExpr(result) or + n = TUnaryPlusExpr(result) or + n = TUndefStmt(result) or + n = TUnlessExpr(result) or + n = TUnlessModifierExpr(result) or + n = TUntilExpr(result) or + n = TUntilModifierExpr(result) or + n = TWhenExpr(result) or + n = TWhileExpr(result) or + n = TWhileModifierExpr(result) or + n = TYieldCall(result) + } + + /** Gets the `i`th synthesized child of `parent`. */ + cached + AST::AstNode getSynthChild(AST::AstNode parent, int i) { + result = TAddExprSynth(parent, i) + or + result = TAssignExprSynth(parent, i) + or + result = TBitwiseAndExprSynth(parent, i) + or + result = TBitwiseOrExprSynth(parent, i) + or + result = TBitwiseXorExprSynth(parent, i) + or + result = TClassVariableAccessSynth(parent, i, _) + or + result = TConstantReadAccessSynth(parent, i, _) + or + result = TDivExprSynth(parent, i) + or + result = TExponentExprSynth(parent, i) + or + result = TGlobalVariableAccessSynth(parent, i, _) + or + result = TInstanceVariableAccessSynth(parent, i, _) + or + result = TIntegerLiteralSynth(parent, i, _) + or + result = TLShiftExprSynth(parent, i) + or + result = TLocalVariableAccessSynth(parent, i, _) + or + result = TLogicalAndExprSynth(parent, i) + or + result = TLogicalOrExprSynth(parent, i) + or + result = TMethodCallSynth(parent, i, _, _, _) + or + result = TModuloExprSynth(parent, i) + or + result = TMulExprSynth(parent, i) + or + result = TRangeLiteralSynth(parent, i, _) + or + result = TRShiftExprSynth(parent, i) + or + result = TSelfSynth(parent, i) + or + result = TSplatExprSynth(parent, i) + or + result = TStmtSequenceSynth(parent, i) + or + result = TSubExprSynth(parent, i) + } + + /** + * Holds if the `i`th child of `parent` is `child`. Either `parent` or + * `child` (or both) is a synthesized node. + */ + cached + predicate synthChild(AST::AstNode parent, int i, AST::AstNode child) { + child = getSynthChild(parent, i) + or + any(Synthesis s).child(parent, i, RealChild(child)) + } + + /** + * Like `toGenerated`, but also returns generated nodes for synthesized AST + * nodes. + */ + cached + Ruby::AstNode toGeneratedInclSynth(AST::AstNode n) { + result = toGenerated(n) + or + not exists(toGenerated(n)) and + exists(AST::AstNode parent | + synthChild(parent, _, n) and + result = toGeneratedInclSynth(parent) + ) + } + + cached + Location getLocation(AST::AstNode n) { + synthLocation(n, result) + or + n.isSynthesized() and + not synthLocation(n, _) and + result = getLocation(n.getParent()) + or + result = toGenerated(n).getLocation() + } +} + +import Cached + +TAstNode fromGenerated(Ruby::AstNode n) { n = toGenerated(result) } + +class TCall = TMethodCall or TYieldCall; + +class TMethodCall = + TMethodCallSynth or TIdentifierMethodCall or TScopeResolutionMethodCall or TRegularMethodCall or + TElementReference or TSuperCall or TUnaryOperation or TBinaryOperation; + +class TSuperCall = TTokenSuperCall or TRegularSuperCall; + +class TConstantAccess = + TTokenConstantAccess or TScopeResolutionConstantAccess or TNamespace or TConstantReadAccessSynth; + +class TControlExpr = TConditionalExpr or TCaseExpr or TLoop; + +class TConditionalExpr = + TIfExpr or TUnlessExpr or TIfModifierExpr or TUnlessModifierExpr or TTernaryIfExpr; + +class TIfExpr = TIf or TElsif; + +class TConditionalLoop = TWhileExpr or TUntilExpr or TWhileModifierExpr or TUntilModifierExpr; + +class TLoop = TConditionalLoop or TForExpr; + +class TSelf = TSelfReal or TSelfSynth; + +class TExpr = + TSelf or TArgumentList or TRescueClause or TRescueModifierExpr or TPair or TStringConcatenation or + TCall or TBlockArgument or TConstantAccess or TControlExpr or TWhenExpr or TLiteral or + TCallable or TVariableAccess or TStmtSequence or TOperation or TSimpleParameter; + +class TSplatExpr = TSplatExprReal or TSplatExprSynth; + +class TStmtSequence = + TBeginBlock or TEndBlock or TThen or TElse or TDo or TEnsure or TStringInterpolationComponent or + TBlock or TBodyStmt or TParenthesizedExpr or TStmtSequenceSynth; + +class TBodyStmt = TBeginExpr or TModuleBase or TMethod or TLambda or TDoBlock or TSingletonMethod; + +class TLiteral = + TNumericLiteral or TNilLiteral or TBooleanLiteral or TStringlikeLiteral or TCharacterLiteral or + TArrayLiteral or THashLiteral or TRangeLiteral or TTokenMethodName; + +class TNumericLiteral = TIntegerLiteral or TFloatLiteral or TRationalLiteral or TComplexLiteral; + +class TIntegerLiteral = TIntegerLiteralReal or TIntegerLiteralSynth; + +class TBooleanLiteral = TTrueLiteral or TFalseLiteral; + +class TStringComponent = + TStringTextComponent or TStringEscapeSequenceComponent or TStringInterpolationComponent; + +class TStringlikeLiteral = + TStringLiteral or TRegExpLiteral or TSymbolLiteral or TSubshellLiteral or THereDoc; + +class TStringLiteral = TRegularStringLiteral or TBareStringLiteral; + +class TSymbolLiteral = TSimpleSymbolLiteral or TComplexSymbolLiteral or THashKeySymbolLiteral; + +class TComplexSymbolLiteral = TDelimitedSymbolLiteral or TBareSymbolLiteral; + +class TArrayLiteral = TRegularArrayLiteral or TStringArrayLiteral or TSymbolArrayLiteral; + +class TCallable = TMethodBase or TLambda or TBlock; + +class TMethodBase = TMethod or TSingletonMethod; + +class TBlock = TDoBlock or TBraceBlock; + +class TModuleBase = TToplevel or TNamespace or TSingletonClass; + +class TNamespace = TClassDeclaration or TModuleDeclaration; + +class TOperation = TUnaryOperation or TBinaryOperation or TAssignment; + +class TUnaryOperation = + TUnaryLogicalOperation or TUnaryArithmeticOperation or TUnaryBitwiseOperation or TDefinedExpr or + TSplatExpr or THashSplatExpr; + +class TUnaryLogicalOperation = TNotExpr; + +class TUnaryArithmeticOperation = TUnaryPlusExpr or TUnaryMinusExpr; + +class TUnaryBitwiseOperation = TComplementExpr; + +class TBinaryOperation = + TBinaryArithmeticOperation or TBinaryLogicalOperation or TBinaryBitwiseOperation or + TComparisonOperation or TSpaceshipExpr or TRegExpMatchExpr or TNoRegExpMatchExpr; + +class TBinaryArithmeticOperation = + TAddExpr or TSubExpr or TMulExpr or TDivExpr or TModuloExpr or TExponentExpr; + +class TAddExpr = TAddExprReal or TAddExprSynth; + +class TSubExpr = TSubExprReal or TSubExprSynth; + +class TMulExpr = TMulExprReal or TMulExprSynth; + +class TDivExpr = TDivExprReal or TDivExprSynth; + +class TModuloExpr = TModuloExprReal or TModuloExprSynth; + +class TExponentExpr = TExponentExprReal or TExponentExprSynth; + +class TBinaryLogicalOperation = TLogicalAndExpr or TLogicalOrExpr; + +class TLogicalAndExpr = TLogicalAndExprReal or TLogicalAndExprSynth; + +class TLogicalOrExpr = TLogicalOrExprReal or TLogicalOrExprSynth; + +class TBinaryBitwiseOperation = + TLShiftExpr or TRShiftExpr or TBitwiseAndExpr or TBitwiseOrExpr or TBitwiseXorExpr; + +class TLShiftExpr = TLShiftExprReal or TLShiftExprSynth; + +class TRangeLiteral = TRangeLiteralReal or TRangeLiteralSynth; + +class TRShiftExpr = TRShiftExprReal or TRShiftExprSynth; + +class TBitwiseAndExpr = TBitwiseAndExprReal or TBitwiseAndExprSynth; + +class TBitwiseOrExpr = TBitwiseOrExprReal or TBitwiseOrExprSynth; + +class TBitwiseXorExpr = TBitwiseXorExprReal or TBitwiseXorExprSynth; + +class TComparisonOperation = TEqualityOperation or TRelationalOperation; + +class TEqualityOperation = TEqExpr or TNEExpr or TCaseEqExpr; + +class TRelationalOperation = TGTExpr or TGEExpr or TLTExpr or TLEExpr; + +class TAssignExpr = TAssignExprReal or TAssignExprSynth; + +class TAssignment = TAssignExpr or TAssignOperation; + +class TAssignOperation = + TAssignArithmeticOperation or TAssignLogicalOperation or TAssignBitwiseOperation; + +class TAssignArithmeticOperation = + TAssignAddExpr or TAssignSubExpr or TAssignMulExpr or TAssignDivExpr or TAssignModuloExpr or + TAssignExponentExpr; + +class TAssignLogicalOperation = TAssignLogicalAndExpr or TAssignLogicalOrExpr; + +class TAssignBitwiseOperation = + TAssignLShiftExpr or TAssignRShiftExpr or TAssignBitwiseAndExpr or TAssignBitwiseOrExpr or + TAssignBitwiseXorExpr; + +class TStmt = + TEmptyStmt or TBodyStmt or TStmtSequence or TUndefStmt or TAliasStmt or TReturningStmt or + TRedoStmt or TRetryStmt or TExpr; + +class TReturningStmt = TReturnStmt or TBreakStmt or TNextStmt; + +class TParameter = + TPatternParameter or TBlockParameter or THashSplatParameter or TKeywordParameter or + TOptionalParameter or TSplatParameter; + +class TPatternParameter = TSimpleParameter or TTuplePatternParameter; + +class TNamedParameter = + TSimpleParameter or TBlockParameter or THashSplatParameter or TKeywordParameter or + TOptionalParameter or TSplatParameter; + +class TTuplePattern = TTuplePatternParameter or TDestructuredLeftAssignment or TLeftAssignmentList; + +class TVariableAccess = + TLocalVariableAccess or TGlobalVariableAccess or TInstanceVariableAccess or TClassVariableAccess; + +class TLocalVariableAccess = TLocalVariableAccessReal or TLocalVariableAccessSynth; + +class TGlobalVariableAccess = TGlobalVariableAccessReal or TGlobalVariableAccessSynth; + +class TInstanceVariableAccess = TInstanceVariableAccessReal or TInstanceVariableAccessSynth; + +class TClassVariableAccess = TClassVariableAccessReal or TClassVariableAccessSynth; diff --git a/ruby/ql/lib/codeql/ruby/ast/internal/Call.qll b/ruby/ql/lib/codeql/ruby/ast/internal/Call.qll new file mode 100644 index 000000000000..43681e1d58fd --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/internal/Call.qll @@ -0,0 +1,186 @@ +private import TreeSitter +private import Variable +private import codeql.ruby.AST +private import codeql.ruby.ast.internal.AST + +predicate isIdentifierMethodCall(Ruby::Identifier g) { vcall(g) and not access(g, _) } + +predicate isRegularMethodCall(Ruby::Call g) { not g.getMethod() instanceof Ruby::Super } + +predicate isScopeResolutionMethodCall(Ruby::ScopeResolution g, Ruby::Identifier i) { + i = g.getName() and + not exists(Ruby::Call c | c.getMethod() = g) +} + +abstract class CallImpl extends Expr, TCall { + abstract AstNode getArgumentImpl(int n); + + /** + * It is not possible to define this predicate as + * + * ```ql + * result = count(this.getArgumentImpl(_)) + * ``` + * + * since that will result in a non-monotonicity error. + */ + abstract int getNumberOfArgumentsImpl(); +} + +abstract class MethodCallImpl extends CallImpl, TMethodCall { + abstract AstNode getReceiverImpl(); + + abstract string getMethodNameImpl(); + + abstract Block getBlockImpl(); +} + +class MethodCallSynth extends MethodCallImpl, TMethodCallSynth { + final override string getMethodNameImpl() { + exists(boolean setter, string name | this = TMethodCallSynth(_, _, name, setter, _) | + setter = true and result = name + "=" + or + setter = false and result = name + ) + } + + final override AstNode getReceiverImpl() { synthChild(this, 0, result) } + + final override AstNode getArgumentImpl(int n) { synthChild(this, n + 1, result) and n >= 0 } + + final override int getNumberOfArgumentsImpl() { this = TMethodCallSynth(_, _, _, _, result) } + + final override Block getBlockImpl() { none() } +} + +class IdentifierMethodCall extends MethodCallImpl, TIdentifierMethodCall { + private Ruby::Identifier g; + + IdentifierMethodCall() { this = TIdentifierMethodCall(g) } + + final override string getMethodNameImpl() { result = g.getValue() } + + final override AstNode getReceiverImpl() { result = TSelfSynth(this, 0) } + + final override Expr getArgumentImpl(int n) { none() } + + final override int getNumberOfArgumentsImpl() { result = 0 } + + final override Block getBlockImpl() { none() } +} + +class ScopeResolutionMethodCall extends MethodCallImpl, TScopeResolutionMethodCall { + private Ruby::ScopeResolution g; + private Ruby::Identifier i; + + ScopeResolutionMethodCall() { this = TScopeResolutionMethodCall(g, i) } + + final override string getMethodNameImpl() { result = i.getValue() } + + final override Expr getReceiverImpl() { toGenerated(result) = g.getScope() } + + final override Expr getArgumentImpl(int n) { none() } + + final override int getNumberOfArgumentsImpl() { result = 0 } + + final override Block getBlockImpl() { none() } +} + +class RegularMethodCall extends MethodCallImpl, TRegularMethodCall { + private Ruby::Call g; + + RegularMethodCall() { this = TRegularMethodCall(g) } + + final override Expr getReceiverImpl() { + toGenerated(result) = g.getReceiver() + or + not exists(g.getReceiver()) and + toGenerated(result) = g.getMethod().(Ruby::ScopeResolution).getScope() + or + result = TSelfSynth(this, 0) + } + + final override string getMethodNameImpl() { + isRegularMethodCall(g) and + ( + result = "call" and g.getMethod() instanceof Ruby::ArgumentList + or + result = g.getMethod().(Ruby::Token).getValue() + or + result = g.getMethod().(Ruby::ScopeResolution).getName().(Ruby::Token).getValue() + ) + } + + final override Expr getArgumentImpl(int n) { + toGenerated(result) = g.getArguments().getChild(n) + or + toGenerated(result) = g.getMethod().(Ruby::ArgumentList).getChild(n) + } + + final override int getNumberOfArgumentsImpl() { + result = + count(g.getArguments().getChild(_)) + count(g.getMethod().(Ruby::ArgumentList).getChild(_)) + } + + final override Block getBlockImpl() { toGenerated(result) = g.getBlock() } +} + +class ElementReferenceImpl extends MethodCallImpl, TElementReference { + private Ruby::ElementReference g; + + ElementReferenceImpl() { this = TElementReference(g) } + + final override Expr getReceiverImpl() { toGenerated(result) = g.getObject() } + + final override Expr getArgumentImpl(int n) { toGenerated(result) = g.getChild(n) } + + final override int getNumberOfArgumentsImpl() { result = count(g.getChild(_)) } + + final override string getMethodNameImpl() { result = "[]" } + + final override Block getBlockImpl() { none() } +} + +abstract class SuperCallImpl extends MethodCallImpl, TSuperCall { } + +class TokenSuperCall extends SuperCallImpl, TTokenSuperCall { + private Ruby::Super g; + + TokenSuperCall() { this = TTokenSuperCall(g) } + + final override string getMethodNameImpl() { result = g.getValue() } + + final override Expr getReceiverImpl() { none() } + + final override Expr getArgumentImpl(int n) { none() } + + final override int getNumberOfArgumentsImpl() { result = 0 } + + final override Block getBlockImpl() { none() } +} + +class RegularSuperCall extends SuperCallImpl, TRegularSuperCall { + private Ruby::Call g; + + RegularSuperCall() { this = TRegularSuperCall(g) } + + final override string getMethodNameImpl() { result = g.getMethod().(Ruby::Super).getValue() } + + final override Expr getReceiverImpl() { none() } + + final override Expr getArgumentImpl(int n) { toGenerated(result) = g.getArguments().getChild(n) } + + final override int getNumberOfArgumentsImpl() { result = count(g.getArguments().getChild(_)) } + + final override Block getBlockImpl() { toGenerated(result) = g.getBlock() } +} + +class YieldCallImpl extends CallImpl, TYieldCall { + Ruby::Yield g; + + YieldCallImpl() { this = TYieldCall(g) } + + final override Expr getArgumentImpl(int n) { toGenerated(result) = g.getChild().getChild(n) } + + final override int getNumberOfArgumentsImpl() { result = count(g.getChild().getChild(_)) } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/internal/Erb.qll b/ruby/ql/lib/codeql/ruby/ast/internal/Erb.qll new file mode 100644 index 000000000000..7a69bf5b783d --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/internal/Erb.qll @@ -0,0 +1,43 @@ +import codeql.Locations +private import TreeSitter +private import codeql.ruby.ast.Erb + +cached +private module Cached { + cached + newtype TAstNode = + TCommentDirective(Erb::CommentDirective g) or + TDirective(Erb::Directive g) or + TGraphqlDirective(Erb::GraphqlDirective g) or + TOutputDirective(Erb::OutputDirective g) or + TTemplate(Erb::Template g) or + TToken(Erb::Token g) or + TComment(Erb::Comment g) or + TCode(Erb::Code g) + + /** + * Gets the underlying TreeSitter entity for a given erb AST node. + */ + cached + Erb::AstNode toGenerated(ErbAstNode n) { + n = TCommentDirective(result) or + n = TDirective(result) or + n = TGraphqlDirective(result) or + n = TOutputDirective(result) or + n = TTemplate(result) or + n = TToken(result) or + n = TComment(result) or + n = TCode(result) + } + + cached + Location getLocation(ErbAstNode n) { result = toGenerated(n).getLocation() } +} + +import Cached + +TAstNode fromGenerated(Erb::AstNode n) { n = toGenerated(result) } + +class TDirectiveNode = TCommentDirective or TDirective or TGraphqlDirective or TOutputDirective; + +class TTokenNode = TToken or TComment or TCode; diff --git a/ruby/ql/lib/codeql/ruby/ast/internal/Module.qll b/ruby/ql/lib/codeql/ruby/ast/internal/Module.qll new file mode 100644 index 000000000000..247573b59e57 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/internal/Module.qll @@ -0,0 +1,409 @@ +private import codeql.Locations +private import codeql.ruby.AST +private import codeql.ruby.ast.Call +private import codeql.ruby.ast.Constant +private import codeql.ruby.ast.Expr +private import codeql.ruby.ast.Module +private import codeql.ruby.ast.Operation +private import codeql.ruby.ast.Scope + +// Names of built-in modules and classes +private string builtin() { + result = + [ + "Object", "Kernel", "BasicObject", "Class", "Module", "NilClass", "FalseClass", "TrueClass", + "Numeric", "Integer", "Float", "Rational", "Complex", "Array", "Hash", "Symbol", "Proc" + ] +} + +cached +private module Cached { + cached + newtype TModule = + TResolved(string qName) { + qName = builtin() + or + qName = namespaceDeclaration(_) + } or + TUnresolved(Namespace n) { not exists(namespaceDeclaration(n)) } + + cached + string namespaceDeclaration(Namespace n) { + isToplevel(n) and result = n.getName() + or + not isToplevel(n) and + not exists(n.getScopeExpr()) and + result = scopeAppend(namespaceDeclaration(n.getEnclosingModule()), n.getName()) + or + exists(string container | + TResolved(container) = resolveScopeExpr(n.getScopeExpr()) and + result = scopeAppend(container, n.getName()) + ) + } + + cached + Module getSuperClass(Module cls) { + cls = TResolved("Object") and result = TResolved("BasicObject") + or + cls = TResolved(["Module", "Numeric", "Array", "Hash", "FalseClass", "TrueClass", "NilClass"]) and + result = TResolved("Object") + or + cls = TResolved(["Integer", "Float", "Rational", "Complex"]) and + result = TResolved("Numeric") + or + cls = TResolved("Class") and + result = TResolved("Module") + or + not cls = TResolved(builtin()) and + ( + exists(ClassDeclaration d | + d = cls.getADeclaration() and + result = resolveScopeExpr(d.getSuperclassExpr()) + ) + or + result = TResolved("Object") and + forex(ClassDeclaration d | d = cls.getADeclaration() | + not exists(resolveScopeExpr(d.getSuperclassExpr())) + ) + ) + } + + cached + Module getAnIncludedModule(Module m) { + m = TResolved("Object") and result = TResolved("Kernel") + or + exists(IncludeOrPrependCall c | + c.getMethodName() = "include" and + ( + m = resolveScopeExpr(c.getReceiver()) + or + m = enclosingModule(c).getModule() and + c.getReceiver() instanceof Self + ) and + result = resolveScopeExpr(c.getAnArgument()) + ) + } + + cached + Module getAPrependedModule(Module m) { + exists(IncludeOrPrependCall c | + c.getMethodName() = "prepend" and + ( + m = resolveScopeExpr(c.getReceiver()) + or + m = enclosingModule(c).getModule() and + c.getReceiver() instanceof Self + ) and + result = resolveScopeExpr(c.getAnArgument()) + ) + } + + /** + * Resolve class or module read access to a qualified module name. + */ + cached + TResolved resolveScopeExpr(ConstantReadAccess r) { + exists(string qname | qname = resolveConstant(r) and result = TResolved(qname)) + } + + /** + * Resolve constant access (class, module or otherwise) to a qualified module name. + * `resolveScopeExpr/1` picks the best (lowest priority number) result of + * `resolveScopeExpr/2` that resolves to a constant definition. If the constant + * definition is a Namespace then it is returned, if it's a constant assignment then + * the right-hand side of the assignment is resolved. + */ + cached + string resolveConstant(ConstantReadAccess r) { + exists(string qname | + qname = + min(string qn, int p | + isDefinedConstant(qn) and + qn = resolveScopeExpr(r, p) and + // prevent classes/modules that contain/extend themselves + not exists(ConstantWriteAccess w | qn = constantDefinition0(w) | + r = w.getScopeExpr() + or + r = w.(ClassDeclaration).getSuperclassExpr() + ) + | + qn order by p + ) + | + result = qname + or + exists(ConstantAssignment a | + qname = constantDefinition0(a) and + result = resolveConstant(a.getParent().(Assignment).getRightOperand()) + ) + ) + } + + cached + Method lookupMethod(Module m, string name) { TMethod(result) = lookupMethodOrConst(m, name) } + + cached + Expr lookupConst(Module m, string name) { + TExpr(result) = lookupMethodOrConst(m, name) + or + exists(AssignExpr ae, ConstantWriteAccess w | + w = ae.getLeftOperand() and + w.getName() = name and + m = resolveScopeExpr(w.getScopeExpr()) and + result = ae.getRightOperand() + ) + } +} + +import Cached + +private predicate isToplevel(ConstantAccess n) { + not exists(n.getScopeExpr()) and + ( + n.hasGlobalScope() + or + n.getEnclosingModule() instanceof Toplevel + ) +} + +private predicate isDefinedConstant(string qualifiedModuleName) { + qualifiedModuleName = [builtin(), constantDefinition0(_)] +} + +private int maxDepth() { result = 1 + max(int level | exists(enclosing(_, level))) } + +private ModuleBase enclosing(ModuleBase m, int level) { + result = m and level = 0 + or + result = enclosing(m.getEnclosingModule(), level - 1) +} + +pragma[noinline] +private Namespace enclosingNameSpaceConstantReadAccess( + ConstantReadAccess c, int priority, string name +) { + result = enclosing(c.getEnclosingModule(), priority) and + name = c.getName() +} + +/** + * Resolve constant read access (typically a scope expression) to a qualified name. The + * `priority` value indicates the precedence of the solution with respect to the lookup order. + * A constant name without scope specifier is resolved against its enclosing modules (inner-most first); + * if the constant is not found in any of the enclosing modules, then the constant will be resolved + * with respect to the ancestors (prepends, includes, super classes, and their ancestors) of the + * directly enclosing module. + */ +private string resolveScopeExpr(ConstantReadAccess c, int priority) { + c.hasGlobalScope() and result = c.getName() and priority = 0 + or + exists(string name | + result = qualifiedModuleName(resolveScopeExprConstantReadAccess(c, priority, name), name) + ) + or + not exists(c.getScopeExpr()) and + not c.hasGlobalScope() and + ( + exists(string name | + exists(Namespace n | + n = enclosingNameSpaceConstantReadAccess(c, priority, name) and + result = qualifiedModuleName(constantDefinition0(n), name) + ) + or + result = + qualifiedModuleName(ancestors(qualifiedModuleNameConstantReadAccess(c, name), + priority - maxDepth()), name) + ) + or + priority = maxDepth() + 4 and + qualifiedModuleNameConstantReadAccess(c, result) != "BasicObject" + ) +} + +pragma[nomagic] +private string resolveScopeExprConstantReadAccess(ConstantReadAccess c, int priority, string name) { + result = resolveScopeExpr(c.getScopeExpr(), priority) and + name = c.getName() +} + +bindingset[qualifier, name] +private string scopeAppend(string qualifier, string name) { + if qualifier = "Object" then result = name else result = qualifier + "::" + name +} + +private string qualifiedModuleName(ModuleBase m) { + result = "Object" and m instanceof Toplevel + or + result = constantDefinition0(m) +} + +pragma[noinline] +private string qualifiedModuleNameConstantWriteAccess(ConstantWriteAccess c, string name) { + result = qualifiedModuleName(c.getEnclosingModule()) and + name = c.getName() +} + +pragma[noinline] +private string qualifiedModuleNameConstantReadAccess(ConstantReadAccess c, string name) { + result = qualifiedModuleName(c.getEnclosingModule()) and + name = c.getName() +} + +/** + * Get a qualified name for a constant definition. May return multiple qualified + * names because we over-approximate when resolving scope resolutions and ignore + * lookup order precedence. Taking lookup order into account here would lead to + * non-monotonic recursion. + */ +private string constantDefinition0(ConstantWriteAccess c) { + c.hasGlobalScope() and result = c.getName() + or + result = scopeAppend(resolveScopeExpr(c.getScopeExpr(), _), c.getName()) + or + not exists(c.getScopeExpr()) and + not c.hasGlobalScope() and + exists(string name | result = scopeAppend(qualifiedModuleNameConstantWriteAccess(c, name), name)) +} + +/** + * The qualified names of the ancestors of a class/module. The ancestors should be an ordered list + * of the ancestores of `prepend`ed modules, the module itself , the ancestors or `include`d modules + * and the ancestors of the super class. The priority value only distinguishes the kind of ancestor, + * it does not order the ancestors within a group of the same kind. This is an over-approximation, however, + * computing the precise order is tricky because it depends on the evaluation/file loading order. + */ +// TODO: the order of super classes can be determined more precisely even without knowing the evaluation +// order, so we should be able to make this more precise. +private string ancestors(string qname, int priority) { + result = ancestors(prepends(qname), _) and priority = 0 + or + result = qname and priority = 1 and isDefinedConstant(qname) + or + result = ancestors(includes(qname), _) and priority = 2 + or + result = ancestors(superclass(qname), _) and priority = 3 +} + +private class IncludeOrPrependCall extends MethodCall { + IncludeOrPrependCall() { this.getMethodName() = ["include", "prepend"] } + + string getAModule() { result = resolveScopeExpr(this.getAnArgument(), _) } + + string getTarget() { + result = resolveScopeExpr(this.getReceiver(), _) + or + result = qualifiedModuleName(enclosingModule(this)) and + ( + this.getReceiver() instanceof Self + or + not exists(this.getReceiver()) + ) + } +} + +/** + * A variant of AstNode::getEnclosingModule that excludes + * results that are enclosed in a block. This is a bit wrong because + * it could lead to false negatives. However, `include` statements in + * blocks are very rare in normal code. The majority of cases are in calls + * to methods like `module_eval` and `Rspec.describe` / `Rspec.context`. These + * methods evaluate the block in the context of some other module/class instead of + * the enclosing one. + */ +private ModuleBase enclosingModule(AstNode node) { result = parent*(node).getParent() } + +private AstNode parent(AstNode n) { + result = n.getParent() and + not result instanceof ModuleBase and + not result instanceof Block +} + +private string prepends(string qname) { + exists(IncludeOrPrependCall m | + m.getMethodName() = "prepend" and + qname = m.getTarget() and + result = m.getAModule() + ) +} + +private string includes(string qname) { + qname = "Object" and + result = "Kernel" + or + exists(IncludeOrPrependCall m | + m.getMethodName() = "include" and + qname = m.getTarget() and + result = m.getAModule() + ) +} + +private Expr superexpr(string qname) { + exists(ClassDeclaration c | qname = constantDefinition0(c) and result = c.getSuperclassExpr()) +} + +private string superclass(string qname) { + qname = "Object" and result = "BasicObject" + or + result = resolveScopeExpr(superexpr(qname), _) +} + +private string qualifiedModuleName(string container, string name) { + isDefinedConstant(result) and + ( + container = result.regexpCapture("(.+)::([^:]+)", 1) and + name = result.regexpCapture("(.+)::([^:]+)", 2) + or + container = "Object" and name = result + ) +} + +private Module getAncestors(Module m) { + result = m or + result = getAncestors(m.getAnIncludedModule()) or + result = getAncestors(m.getAPrependedModule()) +} + +private newtype TMethodOrExpr = + TMethod(Method m) or + TExpr(Expr e) + +private TMethodOrExpr getMethodOrConst(TModule owner, string name) { + exists(ModuleBase m | m.getModule() = owner | + result = TMethod(m.getMethod(name)) + or + result = TExpr(m.getConstant(name)) + ) +} + +module ExposedForTestingOnly { + Method getMethod(TModule owner, string name) { TMethod(result) = getMethodOrConst(owner, name) } + + Expr getConst(TModule owner, string name) { TExpr(result) = getMethodOrConst(owner, name) } +} + +private TMethodOrExpr lookupMethodOrConst0(Module m, string name) { + result = lookupMethodOrConst0(m.getAPrependedModule(), name) + or + not exists(getMethodOrConst(getAncestors(m.getAPrependedModule()), name)) and + ( + result = getMethodOrConst(m, name) + or + not exists(getMethodOrConst(m, name)) and + result = lookupMethodOrConst0(m.getAnIncludedModule(), name) + ) +} + +private AstNode getNode(TMethodOrExpr e) { e = TMethod(result) or e = TExpr(result) } + +private TMethodOrExpr lookupMethodOrConst(Module m, string name) { + result = lookupMethodOrConst0(m, name) + or + not exists(lookupMethodOrConst0(m, name)) and + result = lookupMethodOrConst(m.getSuperClass(), name) and + // For now, we restrict the scope of top-level declarations to their file. + // This may remove some plausible targets, but also removes a lot of + // implausible targets + if getNode(result).getEnclosingModule() instanceof Toplevel + then getNode(result).getFile() = m.getADeclaration().getFile() + else any() +} diff --git a/ruby/ql/lib/codeql/ruby/ast/internal/Operation.qll b/ruby/ql/lib/codeql/ruby/ast/internal/Operation.qll new file mode 100644 index 000000000000..3571c97e9dc7 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/internal/Operation.qll @@ -0,0 +1,198 @@ +private import codeql.ruby.AST +private import AST +private import TreeSitter +private import Call + +abstract class OperationImpl extends Expr, TOperation { + abstract string getOperatorImpl(); + + abstract Expr getAnOperandImpl(); +} + +abstract class UnaryOperationImpl extends OperationImpl, MethodCallImpl, TUnaryOperation { + abstract Expr getOperandImpl(); + + final override Expr getAnOperandImpl() { result = this.getOperandImpl() } + + final override string getMethodNameImpl() { result = this.getOperatorImpl() } + + final override AstNode getReceiverImpl() { result = this.getOperandImpl() } + + final override Expr getArgumentImpl(int n) { none() } + + final override int getNumberOfArgumentsImpl() { result = 0 } + + final override Block getBlockImpl() { none() } +} + +class UnaryOperationGenerated extends UnaryOperationImpl { + private Ruby::Unary g; + + UnaryOperationGenerated() { g = toGenerated(this) } + + final override Expr getOperandImpl() { toGenerated(result) = g.getOperand() } + + final override string getOperatorImpl() { result = g.getOperator() } +} + +class SplatExprReal extends UnaryOperationImpl, TSplatExprReal { + private Ruby::SplatArgument g; + + SplatExprReal() { this = TSplatExprReal(g) } + + final override string getOperatorImpl() { result = "*" } + + final override Expr getOperandImpl() { toGenerated(result) = g.getChild() } +} + +class SplatExprSynth extends UnaryOperationImpl, TSplatExprSynth { + final override string getOperatorImpl() { result = "*" } + + final override Expr getOperandImpl() { synthChild(this, 0, result) } +} + +class HashSplatExprImpl extends UnaryOperationImpl, THashSplatExpr { + private Ruby::HashSplatArgument g; + + HashSplatExprImpl() { this = THashSplatExpr(g) } + + final override Expr getOperandImpl() { toGenerated(result) = g.getChild() } + + final override string getOperatorImpl() { result = "**" } +} + +abstract class BinaryOperationImpl extends OperationImpl, MethodCallImpl, TBinaryOperation { + abstract Stmt getLeftOperandImpl(); + + abstract Stmt getRightOperandImpl(); + + final override Expr getAnOperandImpl() { + result = this.getLeftOperandImpl() + or + result = this.getRightOperandImpl() + } + + final override string getMethodNameImpl() { result = this.getOperatorImpl() } + + final override AstNode getReceiverImpl() { result = this.getLeftOperandImpl() } + + final override Expr getArgumentImpl(int n) { n = 0 and result = this.getRightOperandImpl() } + + final override int getNumberOfArgumentsImpl() { result = 1 } + + final override Block getBlockImpl() { none() } +} + +class BinaryOperationReal extends BinaryOperationImpl { + private Ruby::Binary g; + + BinaryOperationReal() { g = toGenerated(this) } + + final override string getOperatorImpl() { result = g.getOperator() } + + final override Stmt getLeftOperandImpl() { toGenerated(result) = g.getLeft() } + + final override Stmt getRightOperandImpl() { toGenerated(result) = g.getRight() } +} + +abstract class BinaryOperationSynth extends BinaryOperationImpl { + final override Stmt getLeftOperandImpl() { synthChild(this, 0, result) } + + final override Stmt getRightOperandImpl() { synthChild(this, 1, result) } +} + +class AddExprSynth extends BinaryOperationSynth, TAddExprSynth { + final override string getOperatorImpl() { result = "+" } +} + +class SubExprSynth extends BinaryOperationSynth, TSubExprSynth { + final override string getOperatorImpl() { result = "-" } +} + +class MulExprSynth extends BinaryOperationSynth, TMulExprSynth { + final override string getOperatorImpl() { result = "*" } +} + +class DivExprSynth extends BinaryOperationSynth, TDivExprSynth { + final override string getOperatorImpl() { result = "/" } +} + +class ModuloExprSynth extends BinaryOperationSynth, TModuloExprSynth { + final override string getOperatorImpl() { result = "%" } +} + +class ExponentExprSynth extends BinaryOperationSynth, TExponentExprSynth { + final override string getOperatorImpl() { result = "**" } +} + +class LogicalAndExprSynth extends BinaryOperationSynth, TLogicalAndExprSynth { + final override string getOperatorImpl() { result = "&&" } +} + +class LogicalOrExprSynth extends BinaryOperationSynth, TLogicalOrExprSynth { + final override string getOperatorImpl() { result = "||" } +} + +class LShiftExprSynth extends BinaryOperationSynth, TLShiftExprSynth { + final override string getOperatorImpl() { result = "<<" } +} + +class RShiftExprSynth extends BinaryOperationSynth, TRShiftExprSynth { + final override string getOperatorImpl() { result = ">>" } +} + +class BitwiseAndSynthExpr extends BinaryOperationSynth, TBitwiseAndExprSynth { + final override string getOperatorImpl() { result = "&" } +} + +class BitwiseOrSynthExpr extends BinaryOperationSynth, TBitwiseOrExprSynth { + final override string getOperatorImpl() { result = "|" } +} + +class BitwiseXorSynthExpr extends BinaryOperationSynth, TBitwiseXorExprSynth { + final override string getOperatorImpl() { result = "^" } +} + +abstract class AssignmentImpl extends OperationImpl, TAssignment { + abstract Pattern getLeftOperandImpl(); + + abstract Expr getRightOperandImpl(); + + final override Expr getAnOperandImpl() { + result = this.getLeftOperandImpl() + or + result = this.getRightOperandImpl() + } +} + +class AssignExprReal extends AssignmentImpl, TAssignExprReal { + private Ruby::Assignment g; + + AssignExprReal() { this = TAssignExprReal(g) } + + final override string getOperatorImpl() { result = "=" } + + final override Pattern getLeftOperandImpl() { toGenerated(result) = g.getLeft() } + + final override Expr getRightOperandImpl() { toGenerated(result) = g.getRight() } +} + +class AssignExprSynth extends AssignmentImpl, TAssignExprSynth { + final override string getOperatorImpl() { result = "=" } + + final override Pattern getLeftOperandImpl() { synthChild(this, 0, result) } + + final override Expr getRightOperandImpl() { synthChild(this, 1, result) } +} + +class AssignOperationImpl extends AssignmentImpl, TAssignOperation { + Ruby::OperatorAssignment g; + + AssignOperationImpl() { g = toGenerated(this) } + + final override string getOperatorImpl() { result = g.getOperator() } + + final override Pattern getLeftOperandImpl() { toGenerated(result) = g.getLeft() } + + final override Expr getRightOperandImpl() { toGenerated(result) = g.getRight() } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/internal/Parameter.qll b/ruby/ql/lib/codeql/ruby/ast/internal/Parameter.qll new file mode 100644 index 000000000000..f888d89c1ace --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/internal/Parameter.qll @@ -0,0 +1,19 @@ +private import codeql.ruby.AST +private import AST +private import TreeSitter + +module Parameter { + class Range extends Ruby::AstNode { + private int pos; + + Range() { + this = any(Ruby::BlockParameters bp).getChild(pos) + or + this = any(Ruby::MethodParameters mp).getChild(pos) + or + this = any(Ruby::LambdaParameters lp).getChild(pos) + } + + int getPosition() { result = pos } + } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/internal/Pattern.qll b/ruby/ql/lib/codeql/ruby/ast/internal/Pattern.qll new file mode 100644 index 000000000000..ce18e77f2225 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/internal/Pattern.qll @@ -0,0 +1,32 @@ +private import codeql.ruby.AST +private import AST +private import TreeSitter + +abstract class TuplePatternImpl extends Ruby::AstNode { + abstract Ruby::AstNode getChildNode(int i); + + final int getRestIndex() { + result = unique(int i | this.getChildNode(i) instanceof Ruby::RestAssignment) + } +} + +class TuplePatternParameterImpl extends TuplePatternImpl, Ruby::DestructuredParameter { + override Ruby::AstNode getChildNode(int i) { result = this.getChild(i) } +} + +class DestructuredLeftAssignmentImpl extends TuplePatternImpl, Ruby::DestructuredLeftAssignment { + override Ruby::AstNode getChildNode(int i) { result = this.getChild(i) } +} + +class LeftAssignmentListImpl extends TuplePatternImpl, Ruby::LeftAssignmentList { + override Ruby::AstNode getChildNode(int i) { + this = + any(Ruby::LeftAssignmentList lal | + if + strictcount(int j | exists(lal.getChild(j))) = 1 and + lal.getChild(0) instanceof Ruby::DestructuredLeftAssignment + then result = lal.getChild(0).(Ruby::DestructuredLeftAssignment).getChild(i) + else result = lal.getChild(i) + ) + } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/internal/Scope.qll b/ruby/ql/lib/codeql/ruby/ast/internal/Scope.qll new file mode 100644 index 000000000000..1cc64fac8850 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/internal/Scope.qll @@ -0,0 +1,109 @@ +private import TreeSitter +private import codeql.ruby.ast.Scope +private import codeql.ruby.ast.internal.AST +private import codeql.ruby.ast.internal.Parameter + +class TScopeType = TMethodBase or TModuleLike or TBlockLike; + +private class TBlockLike = TDoBlock or TLambda or TBlock or TEndBlock; + +private class TModuleLike = TToplevel or TModuleDeclaration or TClassDeclaration or TSingletonClass; + +module Scope { + class TypeRange = Callable::TypeRange or ModuleBase::TypeRange or @ruby_end_block; + + class Range extends Ruby::AstNode, TypeRange { + Range() { not this = any(Ruby::Lambda l).getBody() } + + ModuleBase::Range getEnclosingModule() { + result = this + or + not this instanceof ModuleBase::Range and result = this.getOuterScope().getEnclosingModule() + } + + MethodBase::Range getEnclosingMethod() { + result = this + or + not this instanceof MethodBase::Range and + not this instanceof ModuleBase::Range and + result = this.getOuterScope().getEnclosingMethod() + } + + Range getOuterScope() { result = scopeOf(this) } + } +} + +module MethodBase { + class TypeRange = @ruby_method or @ruby_singleton_method; + + class Range extends Scope::Range, TypeRange { } +} + +module Callable { + class TypeRange = MethodBase::TypeRange or @ruby_do_block or @ruby_lambda or @ruby_block; + + class Range extends Scope::Range, TypeRange { + Parameter::Range getParameter(int i) { + result = this.(Ruby::Method).getParameters().getChild(i) or + result = this.(Ruby::SingletonMethod).getParameters().getChild(i) or + result = this.(Ruby::DoBlock).getParameters().getChild(i) or + result = this.(Ruby::Lambda).getParameters().getChild(i) or + result = this.(Ruby::Block).getParameters().getChild(i) + } + } +} + +module ModuleBase { + class TypeRange = @ruby_program or @ruby_module or @ruby_class or @ruby_singleton_class; + + class Range extends Scope::Range, TypeRange { } +} + +pragma[noinline] +private predicate rankHeredocBody(File f, Ruby::HeredocBody b, int i) { + b = + rank[i](Ruby::HeredocBody b0 | + f = b0.getLocation().getFile() + | + b0 order by b0.getLocation().getStartLine(), b0.getLocation().getStartColumn() + ) +} + +Ruby::HeredocBody getHereDocBody(Ruby::HeredocBeginning g) { + exists(int i, File f | + g = + rank[i](Ruby::HeredocBeginning b | + f = b.getLocation().getFile() + | + b order by b.getLocation().getStartLine(), b.getLocation().getStartColumn() + ) and + rankHeredocBody(f, result, i) + ) +} + +private Ruby::AstNode parentOf(Ruby::AstNode n) { + n = getHereDocBody(result) + or + exists(Ruby::AstNode parent | parent = n.getParent() | + if + n = + [ + parent.(Ruby::Module).getName(), parent.(Ruby::Class).getName(), + parent.(Ruby::Class).getSuperclass(), parent.(Ruby::SingletonClass).getValue(), + parent.(Ruby::Method).getName(), parent.(Ruby::SingletonMethod).getName(), + parent.(Ruby::SingletonMethod).getObject() + ] + then result = parent.getParent() + else result = parent + ) +} + +/** Gets the enclosing scope of a node */ +cached +Scope::Range scopeOf(Ruby::AstNode n) { + exists(Ruby::AstNode p | p = parentOf(n) | + p = result + or + not p instanceof Scope::Range and result = scopeOf(p) + ) +} diff --git a/ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qll b/ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qll new file mode 100644 index 000000000000..a8673050148f --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qll @@ -0,0 +1,797 @@ +/** Provides predicates for synthesizing AST nodes. */ + +private import AST +private import TreeSitter +private import codeql.ruby.ast.internal.Call +private import codeql.ruby.ast.internal.Variable +private import codeql.ruby.ast.internal.Pattern +private import codeql.ruby.AST + +/** A synthesized AST node kind. */ +newtype SynthKind = + AddExprKind() or + AssignExprKind() or + BitwiseAndExprKind() or + BitwiseOrExprKind() or + BitwiseXorExprKind() or + ClassVariableAccessKind(ClassVariable v) or + DivExprKind() or + ExponentExprKind() or + GlobalVariableAccessKind(GlobalVariable v) or + InstanceVariableAccessKind(InstanceVariable v) or + IntegerLiteralKind(int i) { i in [-1000 .. 1000] } or + LShiftExprKind() or + LocalVariableAccessRealKind(LocalVariableReal v) or + LocalVariableAccessSynthKind(TLocalVariableSynth v) or + LogicalAndExprKind() or + LogicalOrExprKind() or + MethodCallKind(string name, boolean setter, int arity) { + any(Synthesis s).methodCall(name, setter, arity) + } or + ModuloExprKind() or + MulExprKind() or + RangeLiteralKind(boolean inclusive) { inclusive in [false, true] } or + RShiftExprKind() or + SplatExprKind() or + StmtSequenceKind() or + SelfKind() or + SubExprKind() or + ConstantReadAccessKind(string value) { any(Synthesis s).constantReadAccess(value) } + +/** + * An AST child. + * + * Either a new synthesized node or a reference to an existing node. + */ +newtype Child = + SynthChild(SynthKind k) or + RealChild(AstNode n) + +private newtype TSynthesis = MkSynthesis() + +/** A class used for synthesizing AST nodes. */ +class Synthesis extends TSynthesis { + /** + * Holds if a node should be synthesized as the `i`th child of `parent`, or if + * a non-synthesized node should be the `i`th child of synthesized node `parent`. + * + * `i = -1` is used to represent that the synthesized node is a desugared version + * of its parent. + */ + predicate child(AstNode parent, int i, Child child) { none() } + + /** + * Holds if synthesized node `n` should have location `l`. Synthesized nodes for + * which this predicate does not hold, inherit their location (recursively) from + * their parent node. + */ + predicate location(AstNode n, Location l) { none() } + + /** + * Holds if a local variable, identified by `i`, should be synthesized for AST + * node `n`. + */ + predicate localVariable(AstNode n, int i) { none() } + + /** + * Holds if a method call to `name` with arity `arity` is needed. + */ + predicate methodCall(string name, boolean setter, int arity) { none() } + + /** + * Holds if a constant read access of `name` is needed. + */ + predicate constantReadAccess(string name) { none() } + + /** + * Holds if `n` should be excluded from `ControlFlowTree` in the CFG construction. + */ + predicate excludeFromControlFlowTree(AstNode n) { none() } + + final string toString() { none() } +} + +private class Desugared extends AstNode { + Desugared() { this = any(AstNode sugar).getDesugared() } + + AstNode getADescendant() { result = this.getAChild*() } +} + +/** + * Gets the desugaring level of `n`. That is, the number of desugaring + * transformations required before the context in which `n` occurs is + * fully desugared. + */ +int desugarLevel(AstNode n) { result = count(Desugared desugared | n = desugared.getADescendant()) } + +/** + * Use this predicate in `Synthesis::child` to generate an assignment of `value` to + * synthesized variable `v`, where the assignment is a child of `assignParent` at + * index `assignIndex`. + */ +bindingset[v, assignParent, assignIndex, value] +private predicate assign( + AstNode parent, int i, Child child, TLocalVariableSynth v, AstNode assignParent, int assignIndex, + AstNode value +) { + parent = assignParent and + i = assignIndex and + child = SynthChild(AssignExprKind()) + or + parent = TAssignExprSynth(assignParent, assignIndex) and + ( + i = 0 and + child = SynthChild(LocalVariableAccessSynthKind(v)) + or + i = 1 and + child = RealChild(value) + ) +} + +/** Holds if synthesized node `n` should have location `l`. */ +predicate synthLocation(AstNode n, Location l) { + n.isSynthesized() and any(Synthesis s).location(n, l) +} + +private predicate hasLocation(AstNode n, Location l) { + l = toGenerated(n).getLocation() + or + synthLocation(n, l) +} + +private module ImplicitSelfSynthesis { + pragma[nomagic] + private predicate identifierMethodCallSelfSynthesis(AstNode mc, int i, Child child) { + child = SynthChild(SelfKind()) and + mc = TIdentifierMethodCall(_) and + i = 0 + } + + private class IdentifierMethodCallSelfSynthesis extends Synthesis { + final override predicate child(AstNode parent, int i, Child child) { + identifierMethodCallSelfSynthesis(parent, i, child) + } + } + + pragma[nomagic] + private predicate regularMethodCallSelfSynthesis(TRegularMethodCall mc, int i, Child child) { + exists(Ruby::AstNode g | + mc = TRegularMethodCall(g) and + // If there's no explicit receiver (or scope resolution that acts like a + // receiver), then the receiver is implicitly `self`. N.B. `::Foo()` is + // not valid Ruby. + not exists(g.(Ruby::Call).getReceiver()) and + not exists(g.(Ruby::Call).getMethod().(Ruby::ScopeResolution).getScope()) + ) and + child = SynthChild(SelfKind()) and + i = 0 + } + + private class RegularMethodCallSelfSynthesis extends Synthesis { + final override predicate child(AstNode parent, int i, Child child) { + regularMethodCallSelfSynthesis(parent, i, child) + } + } +} + +private module SetterDesugar { + /** An assignment where the left-hand side is a method call. */ + private class SetterAssignExpr extends AssignExpr { + private MethodCall mc; + + pragma[nomagic] + SetterAssignExpr() { mc = this.getLeftOperand() } + + MethodCall getMethodCall() { result = mc } + + pragma[nomagic] + MethodCallKind getCallKind(boolean setter, int arity) { + result = MethodCallKind(mc.getMethodName(), setter, arity) + } + + pragma[nomagic] + Expr getReceiver() { result = mc.getReceiver() } + + pragma[nomagic] + Expr getArgument(int i) { result = mc.getArgument(i) } + + pragma[nomagic] + int getNumberOfArguments() { result = mc.getNumberOfArguments() } + + pragma[nomagic] + Location getMethodCallLocation() { hasLocation(mc, result) } + } + + pragma[nomagic] + private predicate setterMethodCallSynthesis(AstNode parent, int i, Child child) { + exists(SetterAssignExpr sae | + parent = sae and + i = -1 and + child = SynthChild(StmtSequenceKind()) + or + exists(AstNode seq | seq = TStmtSequenceSynth(sae, -1) | + parent = seq and + i = 0 and + child = SynthChild(sae.getCallKind(true, sae.getNumberOfArguments() + 1)) + or + exists(AstNode call | call = TMethodCallSynth(seq, 0, _, _, _) | + parent = call and + i = 0 and + child = RealChild(sae.getReceiver()) + or + parent = call and + child = RealChild(sae.getArgument(i - 1)) + or + exists(int valueIndex | valueIndex = sae.getNumberOfArguments() + 1 | + parent = call and + i = valueIndex and + child = SynthChild(AssignExprKind()) + or + parent = TAssignExprSynth(call, valueIndex) and + ( + i = 0 and + child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(sae, 0))) + or + i = 1 and + child = RealChild(sae.getRightOperand()) + ) + ) + ) + or + parent = seq and + i = 1 and + child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(sae, 0))) + ) + ) + } + + /** + * ```rb + * x.foo = y + * ``` + * + * desugars to + * + * ```rb + * x.foo=(__synth_0 = y); + * __synth_0; + * ``` + */ + private class SetterMethodCallSynthesis extends Synthesis { + final override predicate child(AstNode parent, int i, Child child) { + setterMethodCallSynthesis(parent, i, child) + } + + final override predicate location(AstNode n, Location l) { + exists(SetterAssignExpr sae, StmtSequence seq | + seq = sae.getDesugared() and + l = sae.getMethodCallLocation() and + n = seq.getAStmt() + ) + } + + final override predicate excludeFromControlFlowTree(AstNode n) { + n = any(SetterAssignExpr sae).getMethodCall() + } + + final override predicate localVariable(AstNode n, int i) { + n instanceof SetterAssignExpr and + i = 0 + } + + final override predicate methodCall(string name, boolean setter, int arity) { + exists(SetterAssignExpr sae | + name = sae.getMethodCall().getMethodName() and + setter = true and + arity = sae.getNumberOfArguments() + 1 + ) + } + } +} + +private module AssignOperationDesugar { + /** + * Gets the operator kind to synthesize for operator assignment `ao`. + */ + private SynthKind getKind(AssignOperation ao) { + ao instanceof AssignAddExpr and result = AddExprKind() + or + ao instanceof AssignSubExpr and result = SubExprKind() + or + ao instanceof AssignMulExpr and result = MulExprKind() + or + ao instanceof AssignDivExpr and result = DivExprKind() + or + ao instanceof AssignModuloExpr and result = ModuloExprKind() + or + ao instanceof AssignExponentExpr and result = ExponentExprKind() + or + ao instanceof AssignLogicalAndExpr and result = LogicalAndExprKind() + or + ao instanceof AssignLogicalOrExpr and result = LogicalOrExprKind() + or + ao instanceof AssignLShiftExpr and result = LShiftExprKind() + or + ao instanceof AssignRShiftExpr and result = RShiftExprKind() + or + ao instanceof AssignBitwiseAndExpr and result = BitwiseAndExprKind() + or + ao instanceof AssignBitwiseOrExpr and result = BitwiseOrExprKind() + or + ao instanceof AssignBitwiseXorExpr and result = BitwiseXorExprKind() + } + + private Location getAssignOperationLocation(AssignOperation ao) { + exists(Ruby::OperatorAssignment g, Ruby::Token op | + g = toGenerated(ao) and + op.getParent() = g and + op.getParentIndex() = 1 and + result = op.getLocation() + ) + } + + /** An assignment operation where the left-hand side is a variable. */ + private class VariableAssignOperation extends AssignOperation { + private Variable v; + + pragma[nomagic] + VariableAssignOperation() { v = this.getLeftOperand().(VariableAccess).getVariable() } + + pragma[nomagic] + SynthKind getVariableAccessKind() { + result in [ + LocalVariableAccessRealKind(v).(SynthKind), InstanceVariableAccessKind(v), + ClassVariableAccessKind(v), GlobalVariableAccessKind(v) + ] + } + } + + pragma[nomagic] + private predicate variableAssignOperationSynthesis(AstNode parent, int i, Child child) { + exists(VariableAssignOperation vao | + parent = vao and + i = -1 and + child = SynthChild(AssignExprKind()) + or + exists(AstNode assign | assign = TAssignExprSynth(vao, -1) | + parent = assign and + i = 0 and + child = RealChild(vao.getLeftOperand()) + or + parent = assign and + i = 1 and + child = SynthChild(getKind(vao)) + or + parent = getSynthChild(assign, 1) and + ( + i = 0 and + child = SynthChild(vao.getVariableAccessKind()) + or + i = 1 and + child = RealChild(vao.getRightOperand()) + ) + ) + ) + } + + /** + * ```rb + * x += y + * ``` + * + * desugars to + * + * ```rb + * x = x + y + * ``` + * + * when `x` is a variable. + */ + private class VariableAssignOperationSynthesis extends Synthesis { + final override predicate child(AstNode parent, int i, Child child) { + variableAssignOperationSynthesis(parent, i, child) + } + + final override predicate location(AstNode n, Location l) { + exists(VariableAssignOperation vao, BinaryOperation bo | + bo = vao.getDesugared().(AssignExpr).getRightOperand() + | + n = bo and + l = getAssignOperationLocation(vao) + or + n = bo.getLeftOperand() and + hasLocation(vao.getLeftOperand(), l) + ) + } + } + + /** An assignment operation where the left-hand side is a method call. */ + private class SetterAssignOperation extends AssignOperation { + private MethodCall mc; + + pragma[nomagic] + SetterAssignOperation() { mc = this.getLeftOperand() } + + MethodCall getMethodCall() { result = mc } + + pragma[nomagic] + MethodCallKind getCallKind(boolean setter, int arity) { + result = MethodCallKind(mc.getMethodName(), setter, arity) + } + + pragma[nomagic] + Expr getReceiver() { result = mc.getReceiver() } + + pragma[nomagic] + Expr getArgument(int i) { result = mc.getArgument(i) } + + pragma[nomagic] + int getNumberOfArguments() { result = mc.getNumberOfArguments() } + + pragma[nomagic] + Location getMethodCallLocation() { hasLocation(mc, result) } + } + + pragma[nomagic] + private predicate methodCallAssignOperationSynthesis(AstNode parent, int i, Child child) { + exists(SetterAssignOperation sao | + parent = sao and + i = -1 and + child = SynthChild(StmtSequenceKind()) + or + exists(AstNode seq | seq = TStmtSequenceSynth(sao, -1) | + // `__synth__0 = foo` + assign(parent, i, child, TLocalVariableSynth(sao, 0), seq, 0, sao.getReceiver()) + or + // `__synth__1 = bar` + exists(Expr arg, int j | arg = sao.getArgument(j - 1) | + assign(parent, i, child, TLocalVariableSynth(sao, j), seq, j, arg) + ) + or + // `__synth__2 = __synth__0.[](__synth__1) + y` + exists(int opAssignIndex | opAssignIndex = sao.getNumberOfArguments() + 1 | + parent = seq and + i = opAssignIndex and + child = SynthChild(AssignExprKind()) + or + exists(AstNode assign | assign = TAssignExprSynth(seq, opAssignIndex) | + parent = assign and + i = 0 and + child = + SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(sao, opAssignIndex))) + or + parent = assign and + i = 1 and + child = SynthChild(getKind(sao)) + or + // `__synth__0.[](__synth__1) + y` + exists(AstNode op | op = getSynthChild(assign, 1) | + parent = op and + i = 0 and + child = SynthChild(sao.getCallKind(false, sao.getNumberOfArguments())) + or + parent = TMethodCallSynth(op, 0, _, _, _) and + child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(sao, i))) and + i in [0 .. sao.getNumberOfArguments()] + or + parent = op and + i = 1 and + child = RealChild(sao.getRightOperand()) + ) + ) + or + // `__synth__0.[]=(__synth__1, __synth__2);` + parent = seq and + i = opAssignIndex + 1 and + child = SynthChild(sao.getCallKind(true, opAssignIndex)) + or + exists(AstNode setter | setter = TMethodCallSynth(seq, opAssignIndex + 1, _, _, _) | + parent = setter and + child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(sao, i))) and + i in [0 .. sao.getNumberOfArguments()] + or + parent = setter and + i = opAssignIndex + 1 and + child = + SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(sao, opAssignIndex))) + ) + or + parent = seq and + i = opAssignIndex + 2 and + child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(sao, opAssignIndex))) + ) + ) + ) + } + + /** + * ```rb + * foo[bar] += y + * ``` + * + * desugars to + * + * ```rb + * __synth__0 = foo; + * __synth__1 = bar; + * __synth__2 = __synth__0.[](__synth__1) + y; + * __synth__0.[]=(__synth__1, __synth__2); + * __synth__2; + * ``` + */ + private class MethodCallAssignOperationSynthesis extends Synthesis { + final override predicate child(AstNode parent, int i, Child child) { + methodCallAssignOperationSynthesis(parent, i, child) + } + + final override predicate location(AstNode n, Location l) { + exists(SetterAssignOperation sao, StmtSequence seq | seq = sao.getDesugared() | + n = seq.getStmt(0) and + hasLocation(sao.getReceiver(), l) + or + exists(int i | + n = seq.getStmt(i + 1) and + hasLocation(sao.getArgument(i), l) + ) + or + exists(AssignExpr ae, int opAssignIndex | + opAssignIndex = sao.getNumberOfArguments() + 1 and + ae = seq.getStmt(opAssignIndex) + | + l = getAssignOperationLocation(sao) and + n = ae + or + exists(BinaryOperation bo | bo = ae.getRightOperand() | + n = bo.getLeftOperand() and + l = sao.getMethodCallLocation() + or + exists(MethodCall mc | mc = bo.getLeftOperand() | + n = mc.getReceiver() and + hasLocation(sao.getReceiver(), l) + or + exists(int i | + n = mc.getArgument(i) and + hasLocation(sao.getArgument(i), l) + ) + ) + ) + or + exists(MethodCall mc | mc = seq.getStmt(opAssignIndex + 1) | + n = mc and + l = sao.getMethodCallLocation() + or + n = mc.getReceiver() and + hasLocation(sao.getReceiver(), l) + or + exists(int i | n = mc.getArgument(i) | + hasLocation(sao.getArgument(i), l) + or + i = opAssignIndex and + l = getAssignOperationLocation(sao) + ) + ) + or + n = seq.getStmt(opAssignIndex + 2) and + l = getAssignOperationLocation(sao) + ) + ) + } + + final override predicate localVariable(AstNode n, int i) { + n = any(SetterAssignOperation sao | i in [0 .. sao.getNumberOfArguments() + 1]) + } + + final override predicate methodCall(string name, boolean setter, int arity) { + exists(SetterAssignOperation sao | name = sao.getMethodCall().getMethodName() | + setter = false and + arity = sao.getNumberOfArguments() + or + setter = true and + arity = sao.getNumberOfArguments() + 1 + ) + } + + final override predicate excludeFromControlFlowTree(AstNode n) { + n = any(SetterAssignOperation sao).getMethodCall() + } + } +} + +private module CompoundAssignDesugar { + /** An assignment where the left-hand side is a tuple pattern. */ + private class TupleAssignExpr extends AssignExpr { + private TuplePattern tp; + + pragma[nomagic] + TupleAssignExpr() { tp = this.getLeftOperand() } + + TuplePattern getTuplePattern() { result = tp } + + pragma[nomagic] + Pattern getElement(int i) { result = tp.getElement(i) } + + pragma[nomagic] + int getNumberOfElements() { + toGenerated(tp) = any(TuplePatternImpl impl | result = count(impl.getChildNode(_))) + } + + pragma[nomagic] + int getRestIndexOrNumberOfElements() { + result = tp.getRestIndex() + or + toGenerated(tp) = any(TuplePatternImpl impl | not exists(impl.getRestIndex())) and + result = this.getNumberOfElements() + } + } + + pragma[nomagic] + private predicate compoundAssignSynthesis(AstNode parent, int i, Child child) { + exists(TupleAssignExpr tae | + parent = tae and + i = -1 and + child = SynthChild(StmtSequenceKind()) + or + exists(AstNode seq | seq = TStmtSequenceSynth(tae, -1) | + parent = seq and + i = 0 and + child = SynthChild(AssignExprKind()) + or + exists(AstNode assign | assign = TAssignExprSynth(seq, 0) | + parent = assign and + i = 0 and + child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(tae, 0))) + or + parent = assign and + i = 1 and + child = SynthChild(SplatExprKind()) + or + parent = TSplatExprSynth(assign, 1) and + i = 0 and + child = RealChild(tae.getRightOperand()) + ) + or + exists(Pattern p, int j, int restIndex | + p = tae.getElement(j) and + restIndex = tae.getRestIndexOrNumberOfElements() + | + parent = seq and + i = j + 1 and + child = SynthChild(AssignExprKind()) + or + exists(AstNode assign | assign = TAssignExprSynth(seq, j + 1) | + parent = assign and + i = 0 and + child = RealChild(p) + or + parent = assign and + i = 1 and + child = SynthChild(MethodCallKind("[]", false, 1)) + or + parent = TMethodCallSynth(assign, 1, _, _, _) and + i = 0 and + child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(tae, 0))) + or + j < restIndex and + parent = TMethodCallSynth(assign, 1, _, _, _) and + i = 1 and + child = SynthChild(IntegerLiteralKind(j)) + or + j = restIndex and + ( + parent = TMethodCallSynth(assign, 1, _, _, _) and + i = 1 and + child = SynthChild(RangeLiteralKind(true)) + or + exists(AstNode call | + call = TMethodCallSynth(assign, 1, _, _, _) and + parent = TRangeLiteralSynth(call, 1, _) + | + i = 0 and + child = SynthChild(IntegerLiteralKind(j)) + or + i = 1 and + child = SynthChild(IntegerLiteralKind(restIndex - tae.getNumberOfElements())) + ) + ) + or + j > restIndex and + parent = TMethodCallSynth(assign, 1, _, _, _) and + i = 1 and + child = SynthChild(IntegerLiteralKind(j - tae.getNumberOfElements())) + ) + ) + ) + ) + } + + /** + * ```rb + * x, *y, z = w + * ``` + * desugars to + * + * ```rb + * __synth__0 = *w; + * x = __synth__0[0]; + * y = __synth__0[1..-2]; + * z = __synth__0[-1]; + * ``` + */ + private class CompoundAssignSynthesis extends Synthesis { + final override predicate child(AstNode parent, int i, Child child) { + compoundAssignSynthesis(parent, i, child) + } + + final override predicate location(AstNode n, Location l) { + exists(TupleAssignExpr tae, StmtSequence seq | seq = tae.getDesugared() | + n = seq.getStmt(0) and + hasLocation(tae.getRightOperand(), l) + or + exists(Pattern p, int j | + p = tae.getElement(j) and + n = seq.getStmt(j + 1) and + hasLocation(p, l) + ) + ) + } + + final override predicate localVariable(AstNode n, int i) { + n instanceof TupleAssignExpr and + i = 0 + } + + final override predicate methodCall(string name, boolean setter, int arity) { + name = "[]" and + setter = false and + arity = 1 + } + + final override predicate excludeFromControlFlowTree(AstNode n) { + n = any(TupleAssignExpr tae).getTuplePattern() + } + } +} + +private module ArrayLiteralDesugar { + pragma[nomagic] + private predicate arrayLiteralSynthesis(AstNode parent, int i, Child child) { + exists(ArrayLiteral al | + parent = al and + i = -1 and + child = SynthChild(MethodCallKind("[]", false, al.getNumberOfElements() + 1)) + or + exists(AstNode mc | mc = TMethodCallSynth(al, -1, _, _, _) | + parent = mc and + i = 0 and + child = SynthChild(ConstantReadAccessKind("::Array")) + or + parent = mc and + child = RealChild(al.getElement(i - 1)) + ) + ) + } + + /** + * ```rb + * [1, 2, 3] + * ``` + * desugars to + * + * ```rb + * ::Array.[](1, 2, 3) + * ``` + */ + private class CompoundAssignSynthesis extends Synthesis { + final override predicate child(AstNode parent, int i, Child child) { + arrayLiteralSynthesis(parent, i, child) + } + + final override predicate methodCall(string name, boolean setter, int arity) { + name = "[]" and + setter = false and + arity = any(ArrayLiteral al).getNumberOfElements() + 1 + } + + final override predicate constantReadAccess(string name) { name = "::Array" } + } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/internal/TreeSitter.qll b/ruby/ql/lib/codeql/ruby/ast/internal/TreeSitter.qll new file mode 100644 index 000000000000..140335142d93 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/internal/TreeSitter.qll @@ -0,0 +1,1988 @@ +/* + * CodeQL library for Ruby + * Automatically generated from the tree-sitter grammar; do not edit + */ + +private import codeql.files.FileSystem +private import codeql.Locations + +module Ruby { + /** The base class for all AST nodes */ + class AstNode extends @ruby_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() { ruby_ast_node_parent(this, result, _) } + + /** Gets the index of this node among the children of its parent. */ + int getParentIndex() { ruby_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(getAPrimaryQlClass(), ",") } + } + + /** A token. */ + class Token extends @ruby_token, AstNode { + /** Gets the value of this token. */ + string getValue() { ruby_tokeninfo(this, _, result, _) } + + /** Gets the location of this token. */ + override Location getLocation() { ruby_tokeninfo(this, _, _, result) } + + /** Gets a string representation of this element. */ + override string toString() { result = getValue() } + + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Token" } + } + + /** A reserved word. */ + class ReservedWord extends @ruby_reserved_word, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "ReservedWord" } + } + + class UnderscoreArg extends @ruby_underscore_arg, AstNode { } + + class UnderscoreLhs extends @ruby_underscore_lhs, AstNode { } + + class UnderscoreMethodName extends @ruby_underscore_method_name, AstNode { } + + class UnderscorePrimary extends @ruby_underscore_primary, AstNode { } + + class UnderscoreStatement extends @ruby_underscore_statement, AstNode { } + + class UnderscoreVariable extends @ruby_underscore_variable, AstNode { } + + /** A class representing `alias` nodes. */ + class Alias extends @ruby_alias, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Alias" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_alias_def(this, _, _, result) } + + /** Gets the node corresponding to the field `alias`. */ + UnderscoreMethodName getAlias() { ruby_alias_def(this, result, _, _) } + + /** Gets the node corresponding to the field `name`. */ + UnderscoreMethodName getName() { ruby_alias_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_alias_def(this, result, _, _) or ruby_alias_def(this, _, result, _) + } + } + + /** A class representing `argument_list` nodes. */ + class ArgumentList extends @ruby_argument_list, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "ArgumentList" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_argument_list_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_argument_list_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_argument_list_child(this, _, result) } + } + + /** A class representing `array` nodes. */ + class Array extends @ruby_array, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Array" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_array_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_array_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_array_child(this, _, result) } + } + + /** A class representing `assignment` nodes. */ + class Assignment extends @ruby_assignment, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Assignment" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_assignment_def(this, _, _, result) } + + /** Gets the node corresponding to the field `left`. */ + AstNode getLeft() { ruby_assignment_def(this, result, _, _) } + + /** Gets the node corresponding to the field `right`. */ + AstNode getRight() { ruby_assignment_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_assignment_def(this, result, _, _) or ruby_assignment_def(this, _, result, _) + } + } + + /** A class representing `bare_string` nodes. */ + class BareString extends @ruby_bare_string, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "BareString" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_bare_string_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_bare_string_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_bare_string_child(this, _, result) } + } + + /** A class representing `bare_symbol` nodes. */ + class BareSymbol extends @ruby_bare_symbol, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "BareSymbol" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_bare_symbol_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_bare_symbol_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_bare_symbol_child(this, _, result) } + } + + /** A class representing `begin` nodes. */ + class Begin extends @ruby_begin, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Begin" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_begin_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_begin_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_begin_child(this, _, result) } + } + + /** A class representing `begin_block` nodes. */ + class BeginBlock extends @ruby_begin_block, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "BeginBlock" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_begin_block_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_begin_block_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_begin_block_child(this, _, result) } + } + + /** A class representing `binary` nodes. */ + class Binary extends @ruby_binary, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Binary" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_binary_def(this, _, _, _, result) } + + /** Gets the node corresponding to the field `left`. */ + AstNode getLeft() { ruby_binary_def(this, result, _, _, _) } + + /** Gets the node corresponding to the field `operator`. */ + string getOperator() { + exists(int value | ruby_binary_def(this, _, value, _, _) | + result = "!=" and value = 0 + or + result = "!~" and value = 1 + or + result = "%" and value = 2 + or + result = "&" and value = 3 + or + result = "&&" and value = 4 + or + result = "*" and value = 5 + or + result = "**" and value = 6 + or + result = "+" and value = 7 + or + result = "-" and value = 8 + or + result = "/" and value = 9 + or + result = "<" and value = 10 + or + result = "<<" and value = 11 + or + result = "<=" and value = 12 + or + result = "<=>" and value = 13 + or + result = "==" and value = 14 + or + result = "===" and value = 15 + or + result = "=~" and value = 16 + or + result = ">" and value = 17 + or + result = ">=" and value = 18 + or + result = ">>" and value = 19 + or + result = "^" and value = 20 + or + result = "and" and value = 21 + or + result = "or" and value = 22 + or + result = "|" and value = 23 + or + result = "||" and value = 24 + ) + } + + /** Gets the node corresponding to the field `right`. */ + AstNode getRight() { ruby_binary_def(this, _, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_binary_def(this, result, _, _, _) or ruby_binary_def(this, _, _, result, _) + } + } + + /** A class representing `block` nodes. */ + class Block extends @ruby_block, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Block" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_block_def(this, result) } + + /** Gets the node corresponding to the field `parameters`. */ + BlockParameters getParameters() { ruby_block_parameters(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_block_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_block_parameters(this, result) or ruby_block_child(this, _, result) + } + } + + /** A class representing `block_argument` nodes. */ + class BlockArgument extends @ruby_block_argument, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "BlockArgument" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_block_argument_def(this, _, result) } + + /** Gets the child of this node. */ + UnderscoreArg getChild() { ruby_block_argument_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_block_argument_def(this, result, _) } + } + + /** A class representing `block_parameter` nodes. */ + class BlockParameter extends @ruby_block_parameter, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "BlockParameter" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_block_parameter_def(this, _, result) } + + /** Gets the node corresponding to the field `name`. */ + Identifier getName() { ruby_block_parameter_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_block_parameter_def(this, result, _) } + } + + /** A class representing `block_parameters` nodes. */ + class BlockParameters extends @ruby_block_parameters, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "BlockParameters" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_block_parameters_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_block_parameters_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_block_parameters_child(this, _, result) } + } + + /** A class representing `break` nodes. */ + class Break extends @ruby_break, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Break" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_break_def(this, result) } + + /** Gets the child of this node. */ + ArgumentList getChild() { ruby_break_child(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_break_child(this, result) } + } + + /** A class representing `call` nodes. */ + class Call extends @ruby_call, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Call" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_call_def(this, _, result) } + + /** Gets the node corresponding to the field `arguments`. */ + ArgumentList getArguments() { ruby_call_arguments(this, result) } + + /** Gets the node corresponding to the field `block`. */ + AstNode getBlock() { ruby_call_block(this, result) } + + /** Gets the node corresponding to the field `method`. */ + AstNode getMethod() { ruby_call_def(this, result, _) } + + /** Gets the node corresponding to the field `receiver`. */ + AstNode getReceiver() { ruby_call_receiver(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_call_arguments(this, result) or + ruby_call_block(this, result) or + ruby_call_def(this, result, _) or + ruby_call_receiver(this, result) + } + } + + /** A class representing `case` nodes. */ + class Case extends @ruby_case__, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Case" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_case_def(this, result) } + + /** Gets the node corresponding to the field `value`. */ + UnderscoreStatement getValue() { ruby_case_value(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_case_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_case_value(this, result) or ruby_case_child(this, _, result) + } + } + + /** A class representing `chained_string` nodes. */ + class ChainedString extends @ruby_chained_string, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "ChainedString" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_chained_string_def(this, result) } + + /** Gets the `i`th child of this node. */ + String getChild(int i) { ruby_chained_string_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_chained_string_child(this, _, result) } + } + + /** A class representing `character` tokens. */ + class Character extends @ruby_token_character, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Character" } + } + + /** A class representing `class` nodes. */ + class Class extends @ruby_class, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Class" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_class_def(this, _, result) } + + /** Gets the node corresponding to the field `name`. */ + AstNode getName() { ruby_class_def(this, result, _) } + + /** Gets the node corresponding to the field `superclass`. */ + Superclass getSuperclass() { ruby_class_superclass(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_class_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_class_def(this, result, _) or + ruby_class_superclass(this, result) or + ruby_class_child(this, _, result) + } + } + + /** A class representing `class_variable` tokens. */ + class ClassVariable extends @ruby_token_class_variable, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "ClassVariable" } + } + + /** A class representing `comment` tokens. */ + class Comment extends @ruby_token_comment, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Comment" } + } + + /** A class representing `complex` tokens. */ + class Complex extends @ruby_token_complex, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Complex" } + } + + /** A class representing `conditional` nodes. */ + class Conditional extends @ruby_conditional, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Conditional" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_conditional_def(this, _, _, _, result) } + + /** Gets the node corresponding to the field `alternative`. */ + UnderscoreArg getAlternative() { ruby_conditional_def(this, result, _, _, _) } + + /** Gets the node corresponding to the field `condition`. */ + UnderscoreArg getCondition() { ruby_conditional_def(this, _, result, _, _) } + + /** Gets the node corresponding to the field `consequence`. */ + UnderscoreArg getConsequence() { ruby_conditional_def(this, _, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_conditional_def(this, result, _, _, _) or + ruby_conditional_def(this, _, result, _, _) or + ruby_conditional_def(this, _, _, result, _) + } + } + + /** A class representing `constant` tokens. */ + class Constant extends @ruby_token_constant, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Constant" } + } + + /** A class representing `delimited_symbol` nodes. */ + class DelimitedSymbol extends @ruby_delimited_symbol, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "DelimitedSymbol" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_delimited_symbol_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_delimited_symbol_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_delimited_symbol_child(this, _, result) } + } + + /** A class representing `destructured_left_assignment` nodes. */ + class DestructuredLeftAssignment extends @ruby_destructured_left_assignment, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "DestructuredLeftAssignment" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_destructured_left_assignment_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_destructured_left_assignment_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_destructured_left_assignment_child(this, _, result) } + } + + /** A class representing `destructured_parameter` nodes. */ + class DestructuredParameter extends @ruby_destructured_parameter, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "DestructuredParameter" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_destructured_parameter_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_destructured_parameter_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_destructured_parameter_child(this, _, result) } + } + + /** A class representing `do` nodes. */ + class Do extends @ruby_do, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Do" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_do_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_do_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_do_child(this, _, result) } + } + + /** A class representing `do_block` nodes. */ + class DoBlock extends @ruby_do_block, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "DoBlock" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_do_block_def(this, result) } + + /** Gets the node corresponding to the field `parameters`. */ + BlockParameters getParameters() { ruby_do_block_parameters(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_do_block_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_do_block_parameters(this, result) or ruby_do_block_child(this, _, result) + } + } + + /** A class representing `element_reference` nodes. */ + class ElementReference extends @ruby_element_reference, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "ElementReference" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_element_reference_def(this, _, result) } + + /** Gets the node corresponding to the field `object`. */ + UnderscorePrimary getObject() { ruby_element_reference_def(this, result, _) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_element_reference_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_element_reference_def(this, result, _) or ruby_element_reference_child(this, _, result) + } + } + + /** A class representing `else` nodes. */ + class Else extends @ruby_else, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Else" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_else_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_else_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_else_child(this, _, result) } + } + + /** A class representing `elsif` nodes. */ + class Elsif extends @ruby_elsif, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Elsif" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_elsif_def(this, _, result) } + + /** Gets the node corresponding to the field `alternative`. */ + AstNode getAlternative() { ruby_elsif_alternative(this, result) } + + /** Gets the node corresponding to the field `condition`. */ + UnderscoreStatement getCondition() { ruby_elsif_def(this, result, _) } + + /** Gets the node corresponding to the field `consequence`. */ + Then getConsequence() { ruby_elsif_consequence(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_elsif_alternative(this, result) or + ruby_elsif_def(this, result, _) or + ruby_elsif_consequence(this, result) + } + } + + /** A class representing `empty_statement` tokens. */ + class EmptyStatement extends @ruby_token_empty_statement, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "EmptyStatement" } + } + + /** A class representing `end_block` nodes. */ + class EndBlock extends @ruby_end_block, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "EndBlock" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_end_block_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_end_block_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_end_block_child(this, _, result) } + } + + /** A class representing `ensure` nodes. */ + class Ensure extends @ruby_ensure, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Ensure" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_ensure_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_ensure_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_ensure_child(this, _, result) } + } + + /** A class representing `escape_sequence` tokens. */ + class EscapeSequence extends @ruby_token_escape_sequence, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "EscapeSequence" } + } + + /** A class representing `exception_variable` nodes. */ + class ExceptionVariable extends @ruby_exception_variable, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "ExceptionVariable" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_exception_variable_def(this, _, result) } + + /** Gets the child of this node. */ + UnderscoreLhs getChild() { ruby_exception_variable_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_exception_variable_def(this, result, _) } + } + + /** A class representing `exceptions` nodes. */ + class Exceptions extends @ruby_exceptions, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Exceptions" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_exceptions_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_exceptions_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_exceptions_child(this, _, result) } + } + + /** A class representing `false` tokens. */ + class False extends @ruby_token_false, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "False" } + } + + /** A class representing `float` tokens. */ + class Float extends @ruby_token_float, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Float" } + } + + /** A class representing `for` nodes. */ + class For extends @ruby_for, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "For" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_for_def(this, _, _, _, result) } + + /** Gets the node corresponding to the field `body`. */ + Do getBody() { ruby_for_def(this, result, _, _, _) } + + /** Gets the node corresponding to the field `pattern`. */ + AstNode getPattern() { ruby_for_def(this, _, result, _, _) } + + /** Gets the node corresponding to the field `value`. */ + In getValue() { ruby_for_def(this, _, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_for_def(this, result, _, _, _) or + ruby_for_def(this, _, result, _, _) or + ruby_for_def(this, _, _, result, _) + } + } + + /** A class representing `global_variable` tokens. */ + class GlobalVariable extends @ruby_token_global_variable, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "GlobalVariable" } + } + + /** A class representing `hash` nodes. */ + class Hash extends @ruby_hash, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Hash" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_hash_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_hash_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_hash_child(this, _, result) } + } + + /** A class representing `hash_key_symbol` tokens. */ + class HashKeySymbol extends @ruby_token_hash_key_symbol, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "HashKeySymbol" } + } + + /** A class representing `hash_splat_argument` nodes. */ + class HashSplatArgument extends @ruby_hash_splat_argument, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "HashSplatArgument" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_hash_splat_argument_def(this, _, result) } + + /** Gets the child of this node. */ + UnderscoreArg getChild() { ruby_hash_splat_argument_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_hash_splat_argument_def(this, result, _) } + } + + /** A class representing `hash_splat_parameter` nodes. */ + class HashSplatParameter extends @ruby_hash_splat_parameter, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "HashSplatParameter" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_hash_splat_parameter_def(this, result) } + + /** Gets the node corresponding to the field `name`. */ + Identifier getName() { ruby_hash_splat_parameter_name(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_hash_splat_parameter_name(this, result) } + } + + /** A class representing `heredoc_beginning` tokens. */ + class HeredocBeginning extends @ruby_token_heredoc_beginning, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "HeredocBeginning" } + } + + /** A class representing `heredoc_body` nodes. */ + class HeredocBody extends @ruby_heredoc_body, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "HeredocBody" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_heredoc_body_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_heredoc_body_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_heredoc_body_child(this, _, result) } + } + + /** A class representing `heredoc_content` tokens. */ + class HeredocContent extends @ruby_token_heredoc_content, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "HeredocContent" } + } + + /** A class representing `heredoc_end` tokens. */ + class HeredocEnd extends @ruby_token_heredoc_end, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "HeredocEnd" } + } + + /** A class representing `identifier` tokens. */ + class Identifier extends @ruby_token_identifier, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Identifier" } + } + + /** A class representing `if` nodes. */ + class If extends @ruby_if, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "If" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_if_def(this, _, result) } + + /** Gets the node corresponding to the field `alternative`. */ + AstNode getAlternative() { ruby_if_alternative(this, result) } + + /** Gets the node corresponding to the field `condition`. */ + UnderscoreStatement getCondition() { ruby_if_def(this, result, _) } + + /** Gets the node corresponding to the field `consequence`. */ + Then getConsequence() { ruby_if_consequence(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_if_alternative(this, result) or + ruby_if_def(this, result, _) or + ruby_if_consequence(this, result) + } + } + + /** A class representing `if_modifier` nodes. */ + class IfModifier extends @ruby_if_modifier, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "IfModifier" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_if_modifier_def(this, _, _, result) } + + /** Gets the node corresponding to the field `body`. */ + UnderscoreStatement getBody() { ruby_if_modifier_def(this, result, _, _) } + + /** Gets the node corresponding to the field `condition`. */ + AstNode getCondition() { ruby_if_modifier_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_if_modifier_def(this, result, _, _) or ruby_if_modifier_def(this, _, result, _) + } + } + + /** A class representing `in` nodes. */ + class In extends @ruby_in, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "In" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_in_def(this, _, result) } + + /** Gets the child of this node. */ + UnderscoreArg getChild() { ruby_in_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_in_def(this, result, _) } + } + + /** A class representing `instance_variable` tokens. */ + class InstanceVariable extends @ruby_token_instance_variable, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "InstanceVariable" } + } + + /** A class representing `integer` tokens. */ + class Integer extends @ruby_token_integer, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Integer" } + } + + /** A class representing `interpolation` nodes. */ + class Interpolation extends @ruby_interpolation, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Interpolation" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_interpolation_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_interpolation_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_interpolation_child(this, _, result) } + } + + /** A class representing `keyword_parameter` nodes. */ + class KeywordParameter extends @ruby_keyword_parameter, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "KeywordParameter" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_keyword_parameter_def(this, _, result) } + + /** Gets the node corresponding to the field `name`. */ + Identifier getName() { ruby_keyword_parameter_def(this, result, _) } + + /** Gets the node corresponding to the field `value`. */ + UnderscoreArg getValue() { ruby_keyword_parameter_value(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_keyword_parameter_def(this, result, _) or ruby_keyword_parameter_value(this, result) + } + } + + /** A class representing `lambda` nodes. */ + class Lambda extends @ruby_lambda, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Lambda" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_lambda_def(this, _, result) } + + /** Gets the node corresponding to the field `body`. */ + AstNode getBody() { ruby_lambda_def(this, result, _) } + + /** Gets the node corresponding to the field `parameters`. */ + LambdaParameters getParameters() { ruby_lambda_parameters(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_lambda_def(this, result, _) or ruby_lambda_parameters(this, result) + } + } + + /** A class representing `lambda_parameters` nodes. */ + class LambdaParameters extends @ruby_lambda_parameters, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "LambdaParameters" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_lambda_parameters_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_lambda_parameters_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_lambda_parameters_child(this, _, result) } + } + + /** A class representing `left_assignment_list` nodes. */ + class LeftAssignmentList extends @ruby_left_assignment_list, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "LeftAssignmentList" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_left_assignment_list_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_left_assignment_list_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_left_assignment_list_child(this, _, result) } + } + + /** A class representing `method` nodes. */ + class Method extends @ruby_method, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Method" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_method_def(this, _, result) } + + /** Gets the node corresponding to the field `name`. */ + UnderscoreMethodName getName() { ruby_method_def(this, result, _) } + + /** Gets the node corresponding to the field `parameters`. */ + MethodParameters getParameters() { ruby_method_parameters(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_method_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_method_def(this, result, _) or + ruby_method_parameters(this, result) or + ruby_method_child(this, _, result) + } + } + + /** A class representing `method_parameters` nodes. */ + class MethodParameters extends @ruby_method_parameters, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "MethodParameters" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_method_parameters_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_method_parameters_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_method_parameters_child(this, _, result) } + } + + /** A class representing `module` nodes. */ + class Module extends @ruby_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() { ruby_module_def(this, _, result) } + + /** Gets the node corresponding to the field `name`. */ + AstNode getName() { ruby_module_def(this, result, _) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_module_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_module_def(this, result, _) or ruby_module_child(this, _, result) + } + } + + /** A class representing `next` nodes. */ + class Next extends @ruby_next, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Next" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_next_def(this, result) } + + /** Gets the child of this node. */ + ArgumentList getChild() { ruby_next_child(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_next_child(this, result) } + } + + /** A class representing `nil` tokens. */ + class Nil extends @ruby_token_nil, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Nil" } + } + + /** A class representing `operator` tokens. */ + class Operator extends @ruby_token_operator, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Operator" } + } + + /** A class representing `operator_assignment` nodes. */ + class OperatorAssignment extends @ruby_operator_assignment, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "OperatorAssignment" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_operator_assignment_def(this, _, _, _, result) } + + /** Gets the node corresponding to the field `left`. */ + UnderscoreLhs getLeft() { ruby_operator_assignment_def(this, result, _, _, _) } + + /** Gets the node corresponding to the field `operator`. */ + string getOperator() { + exists(int value | ruby_operator_assignment_def(this, _, value, _, _) | + result = "%=" and value = 0 + or + result = "&&=" and value = 1 + or + result = "&=" and value = 2 + or + result = "**=" and value = 3 + or + result = "*=" and value = 4 + or + result = "+=" and value = 5 + or + result = "-=" and value = 6 + or + result = "/=" and value = 7 + or + result = "<<=" and value = 8 + or + result = ">>=" and value = 9 + or + result = "^=" and value = 10 + or + result = "|=" and value = 11 + or + result = "||=" and value = 12 + ) + } + + /** Gets the node corresponding to the field `right`. */ + AstNode getRight() { ruby_operator_assignment_def(this, _, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_operator_assignment_def(this, result, _, _, _) or + ruby_operator_assignment_def(this, _, _, result, _) + } + } + + /** A class representing `optional_parameter` nodes. */ + class OptionalParameter extends @ruby_optional_parameter, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "OptionalParameter" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_optional_parameter_def(this, _, _, result) } + + /** Gets the node corresponding to the field `name`. */ + Identifier getName() { ruby_optional_parameter_def(this, result, _, _) } + + /** Gets the node corresponding to the field `value`. */ + UnderscoreArg getValue() { ruby_optional_parameter_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_optional_parameter_def(this, result, _, _) or + ruby_optional_parameter_def(this, _, result, _) + } + } + + /** A class representing `pair` nodes. */ + class Pair extends @ruby_pair, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Pair" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_pair_def(this, _, _, result) } + + /** Gets the node corresponding to the field `key`. */ + AstNode getKey() { ruby_pair_def(this, result, _, _) } + + /** Gets the node corresponding to the field `value`. */ + UnderscoreArg getValue() { ruby_pair_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_pair_def(this, result, _, _) or ruby_pair_def(this, _, result, _) + } + } + + /** A class representing `parenthesized_statements` nodes. */ + class ParenthesizedStatements extends @ruby_parenthesized_statements, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "ParenthesizedStatements" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_parenthesized_statements_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_parenthesized_statements_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_parenthesized_statements_child(this, _, result) } + } + + /** A class representing `pattern` nodes. */ + class Pattern extends @ruby_pattern, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Pattern" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_pattern_def(this, _, result) } + + /** Gets the child of this node. */ + AstNode getChild() { ruby_pattern_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_pattern_def(this, result, _) } + } + + /** A class representing `program` nodes. */ + class Program extends @ruby_program, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Program" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_program_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_program_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_program_child(this, _, result) } + } + + /** A class representing `range` nodes. */ + class Range extends @ruby_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() { ruby_range_def(this, _, result) } + + /** Gets the node corresponding to the field `begin`. */ + UnderscoreArg getBegin() { ruby_range_begin(this, result) } + + /** Gets the node corresponding to the field `end`. */ + UnderscoreArg getEnd() { ruby_range_end(this, result) } + + /** Gets the node corresponding to the field `operator`. */ + string getOperator() { + exists(int value | ruby_range_def(this, value, _) | + result = ".." and value = 0 + or + result = "..." and value = 1 + ) + } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_range_begin(this, result) or ruby_range_end(this, result) + } + } + + /** A class representing `rational` nodes. */ + class Rational extends @ruby_rational, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Rational" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_rational_def(this, _, result) } + + /** Gets the child of this node. */ + AstNode getChild() { ruby_rational_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_rational_def(this, result, _) } + } + + /** A class representing `redo` nodes. */ + class Redo extends @ruby_redo, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Redo" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_redo_def(this, result) } + + /** Gets the child of this node. */ + ArgumentList getChild() { ruby_redo_child(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_redo_child(this, result) } + } + + /** A class representing `regex` nodes. */ + class Regex extends @ruby_regex, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Regex" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_regex_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_regex_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_regex_child(this, _, result) } + } + + /** A class representing `rescue` nodes. */ + class Rescue extends @ruby_rescue, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Rescue" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_rescue_def(this, result) } + + /** Gets the node corresponding to the field `body`. */ + Then getBody() { ruby_rescue_body(this, result) } + + /** Gets the node corresponding to the field `exceptions`. */ + Exceptions getExceptions() { ruby_rescue_exceptions(this, result) } + + /** Gets the node corresponding to the field `variable`. */ + ExceptionVariable getVariable() { ruby_rescue_variable(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_rescue_body(this, result) or + ruby_rescue_exceptions(this, result) or + ruby_rescue_variable(this, result) + } + } + + /** A class representing `rescue_modifier` nodes. */ + class RescueModifier extends @ruby_rescue_modifier, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "RescueModifier" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_rescue_modifier_def(this, _, _, result) } + + /** Gets the node corresponding to the field `body`. */ + UnderscoreStatement getBody() { ruby_rescue_modifier_def(this, result, _, _) } + + /** Gets the node corresponding to the field `handler`. */ + AstNode getHandler() { ruby_rescue_modifier_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_rescue_modifier_def(this, result, _, _) or ruby_rescue_modifier_def(this, _, result, _) + } + } + + /** A class representing `rest_assignment` nodes. */ + class RestAssignment extends @ruby_rest_assignment, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "RestAssignment" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_rest_assignment_def(this, result) } + + /** Gets the child of this node. */ + UnderscoreLhs getChild() { ruby_rest_assignment_child(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_rest_assignment_child(this, result) } + } + + /** A class representing `retry` nodes. */ + class Retry extends @ruby_retry, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Retry" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_retry_def(this, result) } + + /** Gets the child of this node. */ + ArgumentList getChild() { ruby_retry_child(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_retry_child(this, result) } + } + + /** A class representing `return` nodes. */ + class Return extends @ruby_return, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Return" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_return_def(this, result) } + + /** Gets the child of this node. */ + ArgumentList getChild() { ruby_return_child(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_return_child(this, result) } + } + + /** A class representing `right_assignment_list` nodes. */ + class RightAssignmentList extends @ruby_right_assignment_list, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "RightAssignmentList" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_right_assignment_list_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_right_assignment_list_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_right_assignment_list_child(this, _, result) } + } + + /** A class representing `scope_resolution` nodes. */ + class ScopeResolution extends @ruby_scope_resolution, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "ScopeResolution" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_scope_resolution_def(this, _, result) } + + /** Gets the node corresponding to the field `name`. */ + AstNode getName() { ruby_scope_resolution_def(this, result, _) } + + /** Gets the node corresponding to the field `scope`. */ + UnderscorePrimary getScope() { ruby_scope_resolution_scope(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_scope_resolution_def(this, result, _) or ruby_scope_resolution_scope(this, result) + } + } + + /** A class representing `self` tokens. */ + class Self extends @ruby_token_self, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Self" } + } + + /** A class representing `setter` nodes. */ + class Setter extends @ruby_setter, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Setter" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_setter_def(this, _, result) } + + /** Gets the node corresponding to the field `name`. */ + Identifier getName() { ruby_setter_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_setter_def(this, result, _) } + } + + /** A class representing `simple_symbol` tokens. */ + class SimpleSymbol extends @ruby_token_simple_symbol, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "SimpleSymbol" } + } + + /** A class representing `singleton_class` nodes. */ + class SingletonClass extends @ruby_singleton_class, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "SingletonClass" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_singleton_class_def(this, _, result) } + + /** Gets the node corresponding to the field `value`. */ + UnderscoreArg getValue() { ruby_singleton_class_def(this, result, _) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_singleton_class_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_singleton_class_def(this, result, _) or ruby_singleton_class_child(this, _, result) + } + } + + /** A class representing `singleton_method` nodes. */ + class SingletonMethod extends @ruby_singleton_method, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "SingletonMethod" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_singleton_method_def(this, _, _, result) } + + /** Gets the node corresponding to the field `name`. */ + UnderscoreMethodName getName() { ruby_singleton_method_def(this, result, _, _) } + + /** Gets the node corresponding to the field `object`. */ + AstNode getObject() { ruby_singleton_method_def(this, _, result, _) } + + /** Gets the node corresponding to the field `parameters`. */ + MethodParameters getParameters() { ruby_singleton_method_parameters(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_singleton_method_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_singleton_method_def(this, result, _, _) or + ruby_singleton_method_def(this, _, result, _) or + ruby_singleton_method_parameters(this, result) or + ruby_singleton_method_child(this, _, result) + } + } + + /** A class representing `splat_argument` nodes. */ + class SplatArgument extends @ruby_splat_argument, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "SplatArgument" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_splat_argument_def(this, _, result) } + + /** Gets the child of this node. */ + UnderscoreArg getChild() { ruby_splat_argument_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_splat_argument_def(this, result, _) } + } + + /** A class representing `splat_parameter` nodes. */ + class SplatParameter extends @ruby_splat_parameter, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "SplatParameter" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_splat_parameter_def(this, result) } + + /** Gets the node corresponding to the field `name`. */ + Identifier getName() { ruby_splat_parameter_name(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_splat_parameter_name(this, result) } + } + + /** A class representing `string` nodes. */ + class String extends @ruby_string__, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "String" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_string_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_string_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_string_child(this, _, result) } + } + + /** A class representing `string_array` nodes. */ + class StringArray extends @ruby_string_array, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "StringArray" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_string_array_def(this, result) } + + /** Gets the `i`th child of this node. */ + BareString getChild(int i) { ruby_string_array_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_string_array_child(this, _, result) } + } + + /** A class representing `string_content` tokens. */ + class StringContent extends @ruby_token_string_content, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "StringContent" } + } + + /** A class representing `subshell` nodes. */ + class Subshell extends @ruby_subshell, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Subshell" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_subshell_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_subshell_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_subshell_child(this, _, result) } + } + + /** A class representing `super` tokens. */ + class Super extends @ruby_token_super, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Super" } + } + + /** A class representing `superclass` nodes. */ + class Superclass extends @ruby_superclass, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Superclass" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_superclass_def(this, _, result) } + + /** Gets the child of this node. */ + AstNode getChild() { ruby_superclass_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_superclass_def(this, result, _) } + } + + /** A class representing `symbol_array` nodes. */ + class SymbolArray extends @ruby_symbol_array, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "SymbolArray" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_symbol_array_def(this, result) } + + /** Gets the `i`th child of this node. */ + BareSymbol getChild(int i) { ruby_symbol_array_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_symbol_array_child(this, _, result) } + } + + /** A class representing `then` nodes. */ + class Then extends @ruby_then, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Then" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_then_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { ruby_then_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_then_child(this, _, result) } + } + + /** A class representing `true` tokens. */ + class True extends @ruby_token_true, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "True" } + } + + /** A class representing `unary` nodes. */ + class Unary extends @ruby_unary, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Unary" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_unary_def(this, _, _, result) } + + /** Gets the node corresponding to the field `operand`. */ + AstNode getOperand() { ruby_unary_def(this, result, _, _) } + + /** Gets the node corresponding to the field `operator`. */ + string getOperator() { + exists(int value | ruby_unary_def(this, _, value, _) | + result = "!" and value = 0 + or + result = "+" and value = 1 + or + result = "-" and value = 2 + or + result = "defined?" and value = 3 + or + result = "not" and value = 4 + or + result = "~" and value = 5 + ) + } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_unary_def(this, result, _, _) } + } + + /** A class representing `undef` nodes. */ + class Undef extends @ruby_undef, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Undef" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_undef_def(this, result) } + + /** Gets the `i`th child of this node. */ + UnderscoreMethodName getChild(int i) { ruby_undef_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_undef_child(this, _, result) } + } + + /** A class representing `uninterpreted` tokens. */ + class Uninterpreted extends @ruby_token_uninterpreted, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Uninterpreted" } + } + + /** A class representing `unless` nodes. */ + class Unless extends @ruby_unless, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Unless" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_unless_def(this, _, result) } + + /** Gets the node corresponding to the field `alternative`. */ + AstNode getAlternative() { ruby_unless_alternative(this, result) } + + /** Gets the node corresponding to the field `condition`. */ + UnderscoreStatement getCondition() { ruby_unless_def(this, result, _) } + + /** Gets the node corresponding to the field `consequence`. */ + Then getConsequence() { ruby_unless_consequence(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_unless_alternative(this, result) or + ruby_unless_def(this, result, _) or + ruby_unless_consequence(this, result) + } + } + + /** A class representing `unless_modifier` nodes. */ + class UnlessModifier extends @ruby_unless_modifier, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "UnlessModifier" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_unless_modifier_def(this, _, _, result) } + + /** Gets the node corresponding to the field `body`. */ + UnderscoreStatement getBody() { ruby_unless_modifier_def(this, result, _, _) } + + /** Gets the node corresponding to the field `condition`. */ + AstNode getCondition() { ruby_unless_modifier_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_unless_modifier_def(this, result, _, _) or ruby_unless_modifier_def(this, _, result, _) + } + } + + /** A class representing `until` nodes. */ + class Until extends @ruby_until, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Until" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_until_def(this, _, _, result) } + + /** Gets the node corresponding to the field `body`. */ + Do getBody() { ruby_until_def(this, result, _, _) } + + /** Gets the node corresponding to the field `condition`. */ + UnderscoreStatement getCondition() { ruby_until_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_until_def(this, result, _, _) or ruby_until_def(this, _, result, _) + } + } + + /** A class representing `until_modifier` nodes. */ + class UntilModifier extends @ruby_until_modifier, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "UntilModifier" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_until_modifier_def(this, _, _, result) } + + /** Gets the node corresponding to the field `body`. */ + UnderscoreStatement getBody() { ruby_until_modifier_def(this, result, _, _) } + + /** Gets the node corresponding to the field `condition`. */ + AstNode getCondition() { ruby_until_modifier_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_until_modifier_def(this, result, _, _) or ruby_until_modifier_def(this, _, result, _) + } + } + + /** A class representing `when` nodes. */ + class When extends @ruby_when, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "When" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_when_def(this, result) } + + /** Gets the node corresponding to the field `body`. */ + Then getBody() { ruby_when_body(this, result) } + + /** Gets the node corresponding to the field `pattern`. */ + Pattern getPattern(int i) { ruby_when_pattern(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_when_body(this, result) or ruby_when_pattern(this, _, result) + } + } + + /** A class representing `while` nodes. */ + class While extends @ruby_while, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "While" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_while_def(this, _, _, result) } + + /** Gets the node corresponding to the field `body`. */ + Do getBody() { ruby_while_def(this, result, _, _) } + + /** Gets the node corresponding to the field `condition`. */ + UnderscoreStatement getCondition() { ruby_while_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_while_def(this, result, _, _) or ruby_while_def(this, _, result, _) + } + } + + /** A class representing `while_modifier` nodes. */ + class WhileModifier extends @ruby_while_modifier, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "WhileModifier" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_while_modifier_def(this, _, _, result) } + + /** Gets the node corresponding to the field `body`. */ + UnderscoreStatement getBody() { ruby_while_modifier_def(this, result, _, _) } + + /** Gets the node corresponding to the field `condition`. */ + AstNode getCondition() { ruby_while_modifier_def(this, _, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { + ruby_while_modifier_def(this, result, _, _) or ruby_while_modifier_def(this, _, result, _) + } + } + + /** A class representing `yield` nodes. */ + class Yield extends @ruby_yield, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Yield" } + + /** Gets the location of this element. */ + override Location getLocation() { ruby_yield_def(this, result) } + + /** Gets the child of this node. */ + ArgumentList getChild() { ruby_yield_child(this, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { ruby_yield_child(this, result) } + } +} + +module Erb { + /** The base class for all AST nodes */ + class AstNode extends @erb_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() { erb_ast_node_parent(this, result, _) } + + /** Gets the index of this node among the children of its parent. */ + int getParentIndex() { erb_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(getAPrimaryQlClass(), ",") } + } + + /** A token. */ + class Token extends @erb_token, AstNode { + /** Gets the value of this token. */ + string getValue() { erb_tokeninfo(this, _, result, _) } + + /** Gets the location of this token. */ + override Location getLocation() { erb_tokeninfo(this, _, _, result) } + + /** Gets a string representation of this element. */ + override string toString() { result = getValue() } + + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Token" } + } + + /** A reserved word. */ + class ReservedWord extends @erb_reserved_word, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "ReservedWord" } + } + + /** A class representing `code` tokens. */ + class Code extends @erb_token_code, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Code" } + } + + /** A class representing `comment` tokens. */ + class Comment extends @erb_token_comment, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Comment" } + } + + /** A class representing `comment_directive` nodes. */ + class CommentDirective extends @erb_comment_directive, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "CommentDirective" } + + /** Gets the location of this element. */ + override Location getLocation() { erb_comment_directive_def(this, _, result) } + + /** Gets the child of this node. */ + Comment getChild() { erb_comment_directive_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { erb_comment_directive_def(this, result, _) } + } + + /** A class representing `content` tokens. */ + class Content extends @erb_token_content, Token { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Content" } + } + + /** A class representing `directive` nodes. */ + class Directive extends @erb_directive, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Directive" } + + /** Gets the location of this element. */ + override Location getLocation() { erb_directive_def(this, _, result) } + + /** Gets the child of this node. */ + Code getChild() { erb_directive_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { erb_directive_def(this, result, _) } + } + + /** A class representing `graphql_directive` nodes. */ + class GraphqlDirective extends @erb_graphql_directive, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "GraphqlDirective" } + + /** Gets the location of this element. */ + override Location getLocation() { erb_graphql_directive_def(this, _, result) } + + /** Gets the child of this node. */ + Code getChild() { erb_graphql_directive_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { erb_graphql_directive_def(this, result, _) } + } + + /** A class representing `output_directive` nodes. */ + class OutputDirective extends @erb_output_directive, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "OutputDirective" } + + /** Gets the location of this element. */ + override Location getLocation() { erb_output_directive_def(this, _, result) } + + /** Gets the child of this node. */ + Code getChild() { erb_output_directive_def(this, result, _) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { erb_output_directive_def(this, result, _) } + } + + /** A class representing `template` nodes. */ + class Template extends @erb_template, AstNode { + /** Gets the name of the primary QL class for this element. */ + override string getAPrimaryQlClass() { result = "Template" } + + /** Gets the location of this element. */ + override Location getLocation() { erb_template_def(this, result) } + + /** Gets the `i`th child of this node. */ + AstNode getChild(int i) { erb_template_child(this, i, result) } + + /** Gets a field or child node of this node. */ + override AstNode getAFieldOrChild() { erb_template_child(this, _, result) } + } +} diff --git a/ruby/ql/lib/codeql/ruby/ast/internal/Variable.qll b/ruby/ql/lib/codeql/ruby/ast/internal/Variable.qll new file mode 100644 index 000000000000..3394ef0665a9 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/ast/internal/Variable.qll @@ -0,0 +1,604 @@ +private import TreeSitter +private import codeql.Locations +private import codeql.ruby.AST +private import codeql.ruby.ast.internal.AST +private import codeql.ruby.ast.internal.Parameter +private import codeql.ruby.ast.internal.Scope +private import codeql.ruby.ast.internal.Synthesis + +/** + * Holds if `n` is in the left-hand-side of an explicit assignment `assignment`. + */ +predicate explicitAssignmentNode(Ruby::AstNode n, Ruby::AstNode assignment) { + n = assignment.(Ruby::Assignment).getLeft() + or + n = assignment.(Ruby::OperatorAssignment).getLeft() + or + exists(Ruby::AstNode parent | + parent = n.getParent() and + explicitAssignmentNode(parent, assignment) + | + parent instanceof Ruby::DestructuredLeftAssignment + or + parent instanceof Ruby::LeftAssignmentList + or + parent instanceof Ruby::RestAssignment + ) +} + +/** Holds if `n` is inside an implicit assignment. */ +predicate implicitAssignmentNode(Ruby::AstNode n) { + n = any(Ruby::ExceptionVariable ev).getChild() + or + n = any(Ruby::For for).getPattern() + or + implicitAssignmentNode(n.getParent()) +} + +/** Holds if `n` is inside a parameter. */ +predicate implicitParameterAssignmentNode(Ruby::AstNode n, Callable::Range c) { + n = c.getParameter(_) + or + implicitParameterAssignmentNode(n.getParent().(Ruby::DestructuredParameter), c) +} + +private predicate instanceVariableAccess( + Ruby::InstanceVariable var, string name, Scope::Range scope, boolean instance +) { + name = var.getValue() and + scope = enclosingModuleOrClass(var) and + if hasEnclosingMethod(var) then instance = true else instance = false +} + +private predicate classVariableAccess(Ruby::ClassVariable var, string name, Scope::Range scope) { + name = var.getValue() and + scope = enclosingModuleOrClass(var) +} + +private predicate hasEnclosingMethod(Ruby::AstNode node) { + exists(Scope::Range s | scopeOf(node) = s and exists(s.getEnclosingMethod())) +} + +private ModuleBase::Range enclosingModuleOrClass(Ruby::AstNode node) { + exists(Scope::Range s | scopeOf(node) = s and result = s.getEnclosingModule()) +} + +private predicate parameterAssignment(Callable::Range scope, string name, Ruby::Identifier i) { + implicitParameterAssignmentNode(i, scope) and + name = i.getValue() +} + +/** Holds if `scope` defines `name` in its parameter declaration at `i`. */ +private predicate scopeDefinesParameterVariable( + Callable::Range scope, string name, Ruby::Identifier i +) { + // In case of overlapping parameter names (e.g. `_`), only the first + // parameter will give rise to a variable + i = + min(Ruby::Identifier other | + parameterAssignment(scope, name, other) + | + other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn() + ) + or + exists(Parameter::Range p | + p = scope.getParameter(_) and + name = i.getValue() + | + i = p.(Ruby::BlockParameter).getName() or + i = p.(Ruby::HashSplatParameter).getName() or + i = p.(Ruby::KeywordParameter).getName() or + i = p.(Ruby::OptionalParameter).getName() or + i = p.(Ruby::SplatParameter).getName() + ) +} + +/** Holds if `name` is assigned in `scope` at `i`. */ +private predicate scopeAssigns(Scope::Range scope, string name, Ruby::Identifier i) { + (explicitAssignmentNode(i, _) or implicitAssignmentNode(i)) and + name = i.getValue() and + scope = scopeOf(i) +} + +cached +private module Cached { + cached + newtype TVariable = + TGlobalVariable(string name) { name = any(Ruby::GlobalVariable var).getValue() } or + TClassVariable(Scope::Range scope, string name, Ruby::AstNode decl) { + decl = + min(Ruby::ClassVariable other | + classVariableAccess(other, name, scope) + | + other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn() + ) + } or + TInstanceVariable(Scope::Range scope, string name, boolean instance, Ruby::AstNode decl) { + decl = + min(Ruby::InstanceVariable other | + instanceVariableAccess(other, name, scope, instance) + | + other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn() + ) + } or + TLocalVariableReal(Scope::Range scope, string name, Ruby::Identifier i) { + scopeDefinesParameterVariable(scope, name, i) + or + i = + min(Ruby::Identifier other | + scopeAssigns(scope, name, other) + | + other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn() + ) and + not scopeDefinesParameterVariable(scope, name, _) and + not inherits(scope, name, _) + } or + TLocalVariableSynth(AstNode n, int i) { any(Synthesis s).localVariable(n, i) } + + // Db types that can be vcalls + private class VcallToken = + @ruby_scope_resolution or @ruby_token_constant or @ruby_token_identifier or @ruby_token_super; + + /** + * Holds if `i` is an `identifier` node occurring in the context where it + * should be considered a VCALL. VCALL is the term that MRI/Ripper uses + * internally when there's an identifier without arguments or parentheses, + * i.e. it *might* be a method call, but it might also be a variable access, + * depending on the bindings in the current scope. + * ```rb + * foo # in MRI this is a VCALL, and the predicate should hold for this + * bar() # in MRI this would be an FCALL. Tree-sitter gives us a `call` node, + * # and the `method` field will be an `identifier`, but this predicate + * # will not hold for that identifier. + * ``` + */ + cached + predicate vcall(VcallToken i) { + i = any(Ruby::ArgumentList x).getChild(_) + or + i = any(Ruby::Array x).getChild(_) + or + i = any(Ruby::Assignment x).getRight() + or + i = any(Ruby::Begin x).getChild(_) + or + i = any(Ruby::BeginBlock x).getChild(_) + or + i = any(Ruby::Binary x).getLeft() + or + i = any(Ruby::Binary x).getRight() + or + i = any(Ruby::Block x).getChild(_) + or + i = any(Ruby::BlockArgument x).getChild() + or + i = any(Ruby::Call x).getReceiver() + or + i = any(Ruby::Case x).getValue() + or + i = any(Ruby::Class x).getChild(_) + or + i = any(Ruby::Conditional x).getCondition() + or + i = any(Ruby::Conditional x).getConsequence() + or + i = any(Ruby::Conditional x).getAlternative() + or + i = any(Ruby::Do x).getChild(_) + or + i = any(Ruby::DoBlock x).getChild(_) + or + i = any(Ruby::ElementReference x).getChild(_) + or + i = any(Ruby::ElementReference x).getObject() + or + i = any(Ruby::Else x).getChild(_) + or + i = any(Ruby::Elsif x).getCondition() + or + i = any(Ruby::EndBlock x).getChild(_) + or + i = any(Ruby::Ensure x).getChild(_) + or + i = any(Ruby::Exceptions x).getChild(_) + or + i = any(Ruby::HashSplatArgument x).getChild() + or + i = any(Ruby::If x).getCondition() + or + i = any(Ruby::IfModifier x).getCondition() + or + i = any(Ruby::IfModifier x).getBody() + or + i = any(Ruby::In x).getChild() + or + i = any(Ruby::Interpolation x).getChild(_) + or + i = any(Ruby::KeywordParameter x).getValue() + or + i = any(Ruby::Method x).getChild(_) + or + i = any(Ruby::Module x).getChild(_) + or + i = any(Ruby::OperatorAssignment x).getRight() + or + i = any(Ruby::OptionalParameter x).getValue() + or + i = any(Ruby::Pair x).getKey() + or + i = any(Ruby::Pair x).getValue() + or + i = any(Ruby::ParenthesizedStatements x).getChild(_) + or + i = any(Ruby::Pattern x).getChild() + or + i = any(Ruby::Program x).getChild(_) + or + i = any(Ruby::Range x).getBegin() + or + i = any(Ruby::Range x).getEnd() + or + i = any(Ruby::RescueModifier x).getBody() + or + i = any(Ruby::RescueModifier x).getHandler() + or + i = any(Ruby::RightAssignmentList x).getChild(_) + or + i = any(Ruby::ScopeResolution x).getScope() + or + i = any(Ruby::SingletonClass x).getValue() + or + i = any(Ruby::SingletonClass x).getChild(_) + or + i = any(Ruby::SingletonMethod x).getChild(_) + or + i = any(Ruby::SingletonMethod x).getObject() + or + i = any(Ruby::SplatArgument x).getChild() + or + i = any(Ruby::Superclass x).getChild() + or + i = any(Ruby::Then x).getChild(_) + or + i = any(Ruby::Unary x).getOperand() + or + i = any(Ruby::Unless x).getCondition() + or + i = any(Ruby::UnlessModifier x).getCondition() + or + i = any(Ruby::UnlessModifier x).getBody() + or + i = any(Ruby::Until x).getCondition() + or + i = any(Ruby::UntilModifier x).getCondition() + or + i = any(Ruby::UntilModifier x).getBody() + or + i = any(Ruby::While x).getCondition() + or + i = any(Ruby::WhileModifier x).getCondition() + or + i = any(Ruby::WhileModifier x).getBody() + } + + cached + predicate access(Ruby::Identifier access, VariableReal variable) { + exists(string name | + variable.getNameImpl() = name and + name = access.getValue() + | + variable.getDeclaringScopeImpl() = scopeOf(access) and + not access.getLocation().strictlyBefore(variable.getLocationImpl()) and + // In case of overlapping parameter names, later parameters should not + // be considered accesses to the first parameter + if parameterAssignment(_, _, access) + then scopeDefinesParameterVariable(_, _, access) + else any() + or + exists(Scope::Range declScope | + variable.getDeclaringScopeImpl() = declScope and + inherits(scopeOf(access), name, declScope) + ) + ) + } + + private class Access extends Ruby::Token { + Access() { + access(this, _) or + this instanceof Ruby::GlobalVariable or + this instanceof Ruby::InstanceVariable or + this instanceof Ruby::ClassVariable + } + } + + cached + predicate explicitWriteAccess(Access access, Ruby::AstNode assignment) { + explicitAssignmentNode(access, assignment) + } + + cached + predicate implicitWriteAccess(Access access) { + implicitAssignmentNode(access) + or + scopeDefinesParameterVariable(_, _, access) + } + + cached + predicate isCapturedAccess(LocalVariableAccess access) { + toGenerated(access.getVariable().getDeclaringScope()) != scopeOf(toGenerated(access)) + } + + cached + predicate instanceVariableAccess(Ruby::InstanceVariable var, InstanceVariable v) { + exists(string name, Scope::Range scope, boolean instance | + v = TInstanceVariable(scope, name, instance, _) and + instanceVariableAccess(var, name, scope, instance) + ) + } + + cached + predicate classVariableAccess(Ruby::ClassVariable var, ClassVariable variable) { + exists(Scope::Range scope, string name | + variable = TClassVariable(scope, name, _) and + classVariableAccess(var, name, scope) + ) + } +} + +import Cached + +/** Holds if this scope inherits `name` from an outer scope `outer`. */ +private predicate inherits(Scope::Range scope, string name, Scope::Range outer) { + (scope instanceof Ruby::Block or scope instanceof Ruby::DoBlock) and + not scopeDefinesParameterVariable(scope, name, _) and + ( + outer = scope.getOuterScope() and + ( + scopeDefinesParameterVariable(outer, name, _) + or + exists(Ruby::Identifier i | + scopeAssigns(outer, name, i) and + i.getLocation().strictlyBefore(scope.getLocation()) + ) + ) + or + inherits(scope.getOuterScope(), name, outer) + ) +} + +abstract class VariableImpl extends TVariable { + abstract string getNameImpl(); + + final string toString() { result = this.getNameImpl() } + + abstract Location getLocationImpl(); +} + +class TVariableReal = TGlobalVariable or TClassVariable or TInstanceVariable or TLocalVariableReal; + +class TLocalVariable = TLocalVariableReal or TLocalVariableSynth; + +/** + * This class only exists to avoid negative recursion warnings. Ideally, + * we would use `VariableImpl` directly, but that results in incorrect + * negative recursion warnings. Adding new root-defs for the predicates + * below works around this. + */ +abstract class VariableReal extends TVariableReal { + abstract string getNameImpl(); + + abstract Location getLocationImpl(); + + abstract Scope::Range getDeclaringScopeImpl(); + + final string toString() { result = this.getNameImpl() } +} + +// Convert extensions of `VariableReal` into extensions of `VariableImpl` +private class VariableRealAdapter extends VariableImpl, TVariableReal instanceof VariableReal { + final override string getNameImpl() { result = VariableReal.super.getNameImpl() } + + final override Location getLocationImpl() { result = VariableReal.super.getLocationImpl() } +} + +class LocalVariableReal extends VariableReal, TLocalVariableReal { + private Scope::Range scope; + private string name; + private Ruby::Identifier i; + + LocalVariableReal() { this = TLocalVariableReal(scope, name, i) } + + final override string getNameImpl() { result = name } + + final override Location getLocationImpl() { result = i.getLocation() } + + final override Scope::Range getDeclaringScopeImpl() { result = scope } + + final VariableAccess getDefiningAccessImpl() { toGenerated(result) = i } +} + +class LocalVariableSynth extends VariableImpl, TLocalVariableSynth { + private AstNode n; + private int i; + + LocalVariableSynth() { this = TLocalVariableSynth(n, i) } + + final override string getNameImpl() { + exists(int level | level = desugarLevel(n) | + if level > 0 then result = "__synth__" + i + "__" + level else result = "__synth__" + i + ) + } + + final override Location getLocationImpl() { result = n.getLocation() } +} + +class GlobalVariableImpl extends VariableReal, TGlobalVariable { + private string name; + + GlobalVariableImpl() { this = TGlobalVariable(name) } + + final override string getNameImpl() { result = name } + + final override Location getLocationImpl() { none() } + + final override Scope::Range getDeclaringScopeImpl() { none() } +} + +class InstanceVariableImpl extends VariableReal, TInstanceVariable { + private ModuleBase::Range scope; + private boolean instance; + private string name; + private Ruby::AstNode decl; + + InstanceVariableImpl() { this = TInstanceVariable(scope, name, instance, decl) } + + final override string getNameImpl() { result = name } + + final predicate isClassInstanceVariable() { instance = false } + + final override Location getLocationImpl() { result = decl.getLocation() } + + final override Scope::Range getDeclaringScopeImpl() { result = scope } +} + +class ClassVariableImpl extends VariableReal, TClassVariable { + private ModuleBase::Range scope; + private string name; + private Ruby::AstNode decl; + + ClassVariableImpl() { this = TClassVariable(scope, name, decl) } + + final override string getNameImpl() { result = name } + + final override Location getLocationImpl() { result = decl.getLocation() } + + final override Scope::Range getDeclaringScopeImpl() { result = scope } +} + +abstract class VariableAccessImpl extends Expr, TVariableAccess { + abstract VariableImpl getVariableImpl(); +} + +module LocalVariableAccess { + predicate range(Ruby::Identifier id, LocalVariable v) { + access(id, v) and + ( + explicitWriteAccess(id, _) + or + implicitWriteAccess(id) + or + vcall(id) + ) + } +} + +class TVariableAccessReal = + TLocalVariableAccessReal or TGlobalVariableAccess or TInstanceVariableAccess or + TClassVariableAccess; + +abstract class LocalVariableAccessImpl extends VariableAccessImpl, TLocalVariableAccess { } + +private class LocalVariableAccessReal extends LocalVariableAccessImpl, TLocalVariableAccessReal { + private Ruby::Identifier g; + private LocalVariable v; + + LocalVariableAccessReal() { this = TLocalVariableAccessReal(g, v) } + + final override LocalVariable getVariableImpl() { result = v } + + final override string toString() { result = g.getValue() } +} + +private class LocalVariableAccessSynth extends LocalVariableAccessImpl, TLocalVariableAccessSynth { + private LocalVariable v; + + LocalVariableAccessSynth() { this = TLocalVariableAccessSynth(_, _, v) } + + final override LocalVariable getVariableImpl() { result = v } + + final override string toString() { result = v.getName() } +} + +module GlobalVariableAccess { + predicate range(Ruby::GlobalVariable n, GlobalVariableImpl v) { n.getValue() = v.getNameImpl() } +} + +abstract class GlobalVariableAccessImpl extends VariableAccessImpl, TGlobalVariableAccess { } + +private class GlobalVariableAccessReal extends GlobalVariableAccessImpl, TGlobalVariableAccessReal { + private Ruby::GlobalVariable g; + private GlobalVariable v; + + GlobalVariableAccessReal() { this = TGlobalVariableAccessReal(g, v) } + + final override GlobalVariable getVariableImpl() { result = v } + + final override string toString() { result = g.getValue() } +} + +private class GlobalVariableAccessSynth extends GlobalVariableAccessImpl, TGlobalVariableAccessSynth { + private GlobalVariable v; + + GlobalVariableAccessSynth() { this = TGlobalVariableAccessSynth(_, _, v) } + + final override GlobalVariable getVariableImpl() { result = v } + + final override string toString() { result = v.getName() } +} + +module InstanceVariableAccess { + predicate range(Ruby::InstanceVariable n, InstanceVariable v) { instanceVariableAccess(n, v) } +} + +abstract class InstanceVariableAccessImpl extends VariableAccessImpl, TInstanceVariableAccess { } + +private class InstanceVariableAccessReal extends InstanceVariableAccessImpl, + TInstanceVariableAccessReal { + private Ruby::InstanceVariable g; + private InstanceVariable v; + + InstanceVariableAccessReal() { this = TInstanceVariableAccessReal(g, v) } + + final override InstanceVariable getVariableImpl() { result = v } + + final override string toString() { result = g.getValue() } +} + +private class InstanceVariableAccessSynth extends InstanceVariableAccessImpl, + TInstanceVariableAccessSynth { + private InstanceVariable v; + + InstanceVariableAccessSynth() { this = TInstanceVariableAccessSynth(_, _, v) } + + final override InstanceVariable getVariableImpl() { result = v } + + final override string toString() { result = v.getName() } +} + +module ClassVariableAccess { + predicate range(Ruby::ClassVariable n, ClassVariable v) { classVariableAccess(n, v) } +} + +abstract class ClassVariableAccessRealImpl extends VariableAccessImpl, TClassVariableAccess { } + +private class ClassVariableAccessReal extends ClassVariableAccessRealImpl, TClassVariableAccessReal { + private Ruby::ClassVariable g; + private ClassVariable v; + + ClassVariableAccessReal() { this = TClassVariableAccessReal(g, v) } + + final override ClassVariable getVariableImpl() { result = v } + + final override string toString() { result = g.getValue() } +} + +private class ClassVariableAccessSynth extends ClassVariableAccessRealImpl, + TClassVariableAccessSynth { + private ClassVariable v; + + ClassVariableAccessSynth() { this = TClassVariableAccessSynth(_, _, v) } + + final override ClassVariable getVariableImpl() { result = v } + + final override string toString() { result = v.getName() } +} diff --git a/ruby/ql/lib/codeql/ruby/controlflow/BasicBlocks.qll b/ruby/ql/lib/codeql/ruby/controlflow/BasicBlocks.qll new file mode 100644 index 000000000000..42aef4b794b4 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/controlflow/BasicBlocks.qll @@ -0,0 +1,414 @@ +/** Provides classes representing basic blocks. */ + +private import codeql.Locations +private import codeql.ruby.AST +private import codeql.ruby.ast.internal.AST +private import codeql.ruby.ast.internal.TreeSitter +private import codeql.ruby.controlflow.ControlFlowGraph +private import internal.ControlFlowGraphImpl +private import CfgNodes +private import SuccessorTypes + +/** + * A basic block, that is, a maximal straight-line sequence of control flow nodes + * without branches or joins. + */ +class BasicBlock extends TBasicBlockStart { + /** Gets the scope of this basic block. */ + CfgScope getScope() { result = this.getAPredecessor().getScope() } + + /** Gets an immediate successor of this basic block, if any. */ + BasicBlock getASuccessor() { result = this.getASuccessor(_) } + + /** Gets an immediate successor of this basic block of a given type, if any. */ + BasicBlock getASuccessor(SuccessorType t) { + result.getFirstNode() = this.getLastNode().getASuccessor(t) + } + + /** Gets an immediate predecessor of this basic block, if any. */ + BasicBlock getAPredecessor() { result.getASuccessor() = this } + + /** Gets an immediate predecessor of this basic block of a given type, if any. */ + BasicBlock getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this } + + /** Gets the control flow node at a specific (zero-indexed) position in this basic block. */ + CfgNode getNode(int pos) { bbIndex(this.getFirstNode(), result, pos) } + + /** Gets a control flow node in this basic block. */ + CfgNode getANode() { result = this.getNode(_) } + + /** Gets the first control flow node in this basic block. */ + CfgNode getFirstNode() { this = TBasicBlockStart(result) } + + /** Gets the last control flow node in this basic block. */ + CfgNode getLastNode() { result = this.getNode(this.length() - 1) } + + /** Gets the length of this basic block. */ + int length() { result = strictcount(this.getANode()) } + + /** + * Holds if this basic block immediately dominates basic block `bb`. + * + * That is, all paths reaching basic block `bb` from some entry point + * basic block must go through this basic block (which is an immediate + * predecessor of `bb`). + * + * Example: + * + * ```rb + * def m b + * if b + * return 0 + * end + * return 1 + * end + * ``` + * + * The basic block starting on line 2 immediately dominates the + * basic block on line 5 (all paths from the entry point of `m` + * to `return 1` must go through the `if` block). + */ + predicate immediatelyDominates(BasicBlock bb) { bbIDominates(this, bb) } + + /** + * Holds if this basic block strictly dominates basic block `bb`. + * + * That is, all paths reaching basic block `bb` from some entry point + * basic block must go through this basic block (which must be different + * from `bb`). + * + * Example: + * + * ```rb + * def m b + * if b + * return 0 + * end + * return 1 + * end + * ``` + * + * The basic block starting on line 2 strictly dominates the + * basic block on line 5 (all paths from the entry point of `m` + * to `return 1` must go through the `if` block). + */ + predicate strictlyDominates(BasicBlock bb) { bbIDominates+(this, bb) } + + /** + * Holds if this basic block dominates basic block `bb`. + * + * That is, all paths reaching basic block `bb` from some entry point + * basic block must go through this basic block. + * + * Example: + * + * ```rb + * def m b + * if b + * return 0 + * end + * return 1 + * end + * ``` + * + * The basic block starting on line 2 dominates the basic + * basic block on line 5 (all paths from the entry point of `m` + * to `return 1` must go through the `if` block). + */ + predicate dominates(BasicBlock bb) { + bb = this or + this.strictlyDominates(bb) + } + + /** + * Holds if `df` is in the dominance frontier of this basic block. + * That is, this basic block dominates a predecessor of `df`, but + * does not dominate `df` itself. + * + * Example: + * + * ```rb + * def m x + * if x < 0 + * x = -x + * if x > 10 + * x = x - 1 + * end + * end + * puts x + * end + * ``` + * + * The basic block on line 8 is in the dominance frontier + * of the basic block starting on line 3 because that block + * dominates the basic block on line 4, which is a predecessor of + * `puts x`. Also, the basic block starting on line 3 does not + * dominate the basic block on line 8. + */ + predicate inDominanceFrontier(BasicBlock df) { + this.dominatesPredecessor(df) and + not strictlyDominates(df) + } + + /** + * Holds if this basic block dominates a predecessor of `df`. + */ + private predicate dominatesPredecessor(BasicBlock df) { this.dominates(df.getAPredecessor()) } + + /** + * Gets the basic block that immediately dominates this basic block, if any. + * + * That is, all paths reaching this basic block from some entry point + * basic block must go through the result, which is an immediate basic block + * predecessor of this basic block. + * + * Example: + * + * ```rb + * def m b + * if b + * return 0 + * end + * return 1 + * end + * ``` + * + * The basic block starting on line 2 is an immediate dominator of + * the basic block on line 5 (all paths from the entry point of `m` + * to `return 1` must go through the `if` block, and the `if` block + * is an immediate predecessor of `return 1`). + */ + BasicBlock getImmediateDominator() { bbIDominates(result, this) } + + /** + * Holds if this basic block strictly post-dominates basic block `bb`. + * + * That is, all paths reaching a normal exit point basic block from basic + * block `bb` must go through this basic block (which must be different + * from `bb`). + * + * Example: + * + * ```rb + * def m b + * if b + * puts "b" + * end + * puts "m" + * end + * ``` + * + * The basic block on line 5 strictly post-dominates the basic block on + * line 3 (all paths to the exit point of `m` from `puts "b"` must go + * through `puts "m"`). + */ + predicate strictlyPostDominates(BasicBlock bb) { bbIPostDominates+(this, bb) } + + /** + * Holds if this basic block post-dominates basic block `bb`. + * + * That is, all paths reaching a normal exit point basic block from basic + * block `bb` must go through this basic block. + * + * Example: + * + * ```rb + * def m b + * if b + * puts "b" + * end + * puts "m" + * end + * ``` + * + * The basic block on line 5 post-dominates the basic block on line 3 + * (all paths to the exit point of `m` from `puts "b"` must go through + * `puts "m"`). + */ + predicate postDominates(BasicBlock bb) { + this.strictlyPostDominates(bb) or + this = bb + } + + /** Holds if this basic block is in a loop in the control flow graph. */ + predicate inLoop() { this.getASuccessor+() = this } + + /** Gets a textual representation of this basic block. */ + string toString() { result = this.getFirstNode().toString() } + + /** Gets the location of this basic block. */ + Location getLocation() { result = this.getFirstNode().getLocation() } +} + +cached +private module Cached { + /** Internal representation of basic blocks. */ + cached + newtype TBasicBlock = TBasicBlockStart(CfgNode cfn) { startsBB(cfn) } + + /** Holds if `cfn` starts a new basic block. */ + private predicate startsBB(CfgNode cfn) { + not exists(cfn.getAPredecessor()) and exists(cfn.getASuccessor()) + or + cfn.isJoin() + or + cfn.getAPredecessor().isBranch() + } + + /** + * Holds if `succ` is a control flow successor of `pred` within + * the same basic block. + */ + private predicate intraBBSucc(CfgNode pred, CfgNode succ) { + succ = pred.getASuccessor() and + not startsBB(succ) + } + + /** + * Holds if `cfn` is the `i`th node in basic block `bb`. + * + * In other words, `i` is the shortest distance from a node `bb` + * that starts a basic block to `cfn` along the `intraBBSucc` relation. + */ + cached + predicate bbIndex(CfgNode bbStart, CfgNode cfn, int i) = + shortestDistances(startsBB/1, intraBBSucc/2)(bbStart, cfn, i) + + /** + * Holds if the first node of basic block `succ` is a control flow + * successor of the last node of basic block `pred`. + */ + private predicate succBB(BasicBlock pred, BasicBlock succ) { succ = pred.getASuccessor() } + + /** Holds if `dom` is an immediate dominator of `bb`. */ + cached + predicate bbIDominates(BasicBlock dom, BasicBlock bb) = + idominance(entryBB/1, succBB/2)(_, dom, bb) + + /** Holds if `pred` is a basic block predecessor of `succ`. */ + private predicate predBB(BasicBlock succ, BasicBlock pred) { succBB(pred, succ) } + + /** Holds if `bb` is an exit basic block that represents normal exit. */ + private predicate normalExitBB(BasicBlock bb) { bb.getANode().(AnnotatedExitNode).isNormal() } + + /** Holds if `dom` is an immediate post-dominator of `bb`. */ + cached + predicate bbIPostDominates(BasicBlock dom, BasicBlock bb) = + idominance(normalExitBB/1, predBB/2)(_, dom, bb) + + /** + * Gets the `i`th predecessor of join block `jb`, with respect to some + * arbitrary order. + */ + cached + JoinBlockPredecessor getJoinBlockPredecessor(JoinBlock jb, int i) { + result = + rank[i + 1](JoinBlockPredecessor jbp | + jbp = jb.getAPredecessor() + | + jbp order by JoinBlockPredecessors::getId(jbp), JoinBlockPredecessors::getSplitString(jbp) + ) + } +} + +private import Cached + +/** Holds if `bb` is an entry basic block. */ +private predicate entryBB(BasicBlock bb) { bb.getFirstNode() instanceof EntryNode } + +/** + * An entry basic block, that is, a basic block whose first node is + * an entry node. + */ +class EntryBasicBlock extends BasicBlock { + EntryBasicBlock() { entryBB(this) } + + override CfgScope getScope() { this.getFirstNode() = TEntryNode(result) } +} + +/** + * An annotated exit basic block, that is, a basic block whose last node is + * an annotated exit node. + */ +class AnnotatedExitBasicBlock extends BasicBlock { + private boolean normal; + + AnnotatedExitBasicBlock() { + exists(AnnotatedExitNode n | + n = this.getANode() and + if n.isNormal() then normal = true else normal = false + ) + } + + /** Holds if this block represent a normal exit. */ + final predicate isNormal() { normal = true } +} + +/** + * An exit basic block, that is, a basic block whose last node is + * an exit node. + */ +class ExitBasicBlock extends BasicBlock { + ExitBasicBlock() { this.getLastNode() instanceof ExitNode } +} + +private module JoinBlockPredecessors { + private predicate id(Ruby::AstNode x, Ruby::AstNode y) { x = y } + + private predicate idOf(Ruby::AstNode x, int y) = equivalenceRelation(id/2)(x, y) + + int getId(JoinBlockPredecessor jbp) { + idOf(toGeneratedInclSynth(jbp.getFirstNode().(AstCfgNode).getNode()), result) + or + idOf(toGeneratedInclSynth(jbp.(EntryBasicBlock).getScope()), result) + } + + string getSplitString(JoinBlockPredecessor jbp) { + result = jbp.getFirstNode().(AstCfgNode).getSplitsString() + or + not exists(jbp.getFirstNode().(AstCfgNode).getSplitsString()) and + result = "" + } +} + +/** A basic block with more than one predecessor. */ +class JoinBlock extends BasicBlock { + JoinBlock() { getFirstNode().isJoin() } + + /** + * Gets the `i`th predecessor of this join block, with respect to some + * arbitrary order. + */ + JoinBlockPredecessor getJoinBlockPredecessor(int i) { result = getJoinBlockPredecessor(this, i) } +} + +/** A basic block that is an immediate predecessor of a join block. */ +class JoinBlockPredecessor extends BasicBlock { + JoinBlockPredecessor() { this.getASuccessor() instanceof JoinBlock } +} + +/** A basic block that terminates in a condition, splitting the subsequent control flow. */ +class ConditionBlock extends BasicBlock { + ConditionBlock() { this.getLastNode().isCondition() } + + /** + * Holds if basic block `succ` is immediately controlled by this basic + * block with conditional value `s`. That is, `succ` is an immediate + * successor of this block, and `succ` can only be reached from + * the callable entry point by going via the `s` edge out of this basic block. + */ + pragma[nomagic] + predicate immediatelyControls(BasicBlock succ, BooleanSuccessor s) { + succ = this.getASuccessor(s) and + forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != this | succ.dominates(pred)) + } + + /** + * Holds if basic block `controlled` is controlled by this basic block with + * conditional value `s`. That is, `controlled` can only be reached from + * the callable entry point by going via the `s` edge out of this basic block. + */ + predicate controls(BasicBlock controlled, BooleanSuccessor s) { + exists(BasicBlock succ | this.immediatelyControls(succ, s) | succ.dominates(controlled)) + } +} diff --git a/ruby/ql/lib/codeql/ruby/controlflow/CfgNodes.qll b/ruby/ql/lib/codeql/ruby/controlflow/CfgNodes.qll new file mode 100644 index 000000000000..157a95f15fec --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/controlflow/CfgNodes.qll @@ -0,0 +1,418 @@ +/** Provides classes representing nodes in a control flow graph. */ + +private import codeql.ruby.AST +private import codeql.ruby.controlflow.BasicBlocks +private import ControlFlowGraph +private import internal.ControlFlowGraphImpl +private import internal.Splitting + +/** An entry node for a given scope. */ +class EntryNode extends CfgNode, TEntryNode { + private CfgScope scope; + + EntryNode() { this = TEntryNode(scope) } + + final override EntryBasicBlock getBasicBlock() { result = CfgNode.super.getBasicBlock() } + + final override Location getLocation() { result = scope.getLocation() } + + final override string toString() { result = "enter " + scope } +} + +/** An exit node for a given scope, annotated with the type of exit. */ +class AnnotatedExitNode extends CfgNode, TAnnotatedExitNode { + private CfgScope scope; + private boolean normal; + + AnnotatedExitNode() { this = TAnnotatedExitNode(scope, normal) } + + /** Holds if this node represent a normal exit. */ + final predicate isNormal() { normal = true } + + final override AnnotatedExitBasicBlock getBasicBlock() { result = CfgNode.super.getBasicBlock() } + + final override Location getLocation() { result = scope.getLocation() } + + final override string toString() { + exists(string s | + normal = true and s = "normal" + or + normal = false and s = "abnormal" + | + result = "exit " + scope + " (" + s + ")" + ) + } +} + +/** An exit node for a given scope. */ +class ExitNode extends CfgNode, TExitNode { + private CfgScope scope; + + ExitNode() { this = TExitNode(scope) } + + final override Location getLocation() { result = scope.getLocation() } + + final override string toString() { result = "exit " + scope } +} + +/** + * A node for an AST node. + * + * Each AST node maps to zero or more `AstCfgNode`s: zero when the node in unreachable + * (dead) code or not important for control flow, and multiple when there are different + * splits for the AST node. + */ +class AstCfgNode extends CfgNode, TElementNode { + private Splits splits; + private AstNode n; + + AstCfgNode() { this = TElementNode(n, splits) } + + final override AstNode getNode() { result = n } + + override Location getLocation() { result = n.getLocation() } + + final override string toString() { + exists(string s | s = n.(AstNode).toString() | + result = "[" + this.getSplitsString() + "] " + s + or + not exists(this.getSplitsString()) and result = s + ) + } + + /** Gets a comma-separated list of strings for each split in this node, if any. */ + final string getSplitsString() { + result = splits.toString() and + result != "" + } + + /** Gets a split for this control flow node, if any. */ + final Split getASplit() { result = splits.getASplit() } +} + +/** A control-flow node that wraps an AST expression. */ +class ExprCfgNode extends AstCfgNode { + Expr e; + + ExprCfgNode() { e = this.getNode() } + + /** Gets the underlying expression. */ + Expr getExpr() { result = e } +} + +/** A control-flow node that wraps a return-like statement. */ +class ReturningCfgNode extends AstCfgNode { + ReturningStmt s; + + ReturningCfgNode() { s = this.getNode() } + + /** Gets the node of the returned value, if any. */ + ExprCfgNode getReturnedValueNode() { + result = this.getAPredecessor() and + result.getNode() = s.getValue() + } +} + +/** A control-flow node that wraps a `StringComponent` AST expression. */ +class StringComponentCfgNode extends AstCfgNode { + StringComponentCfgNode() { this.getNode() instanceof StringComponent } +} + +private Expr desugar(Expr n) { + result = n.getDesugared() + or + not exists(n.getDesugared()) and + result = n +} + +/** + * A class for mapping parent-child AST nodes to parent-child CFG nodes. + */ +abstract private class ExprChildMapping extends Expr { + /** + * Holds if `child` is a (possibly nested) child of this expression + * for which we would like to find a matching CFG child. + */ + abstract predicate relevantChild(Expr child); + + pragma[nomagic] + private predicate reachesBasicBlock(Expr child, CfgNode cfn, BasicBlock bb) { + this.relevantChild(child) and + cfn = this.getAControlFlowNode() and + bb.getANode() = cfn + or + exists(BasicBlock mid | + this.reachesBasicBlock(child, cfn, mid) and + bb = mid.getAPredecessor() and + not mid.getANode().getNode() = child + ) + } + + /** + * Holds if there is a control-flow path from `cfn` to `cfnChild`, where `cfn` + * is a control-flow node for this expression, and `cfnChild` is a control-flow + * node for `child`. + * + * The path never escapes the syntactic scope of this expression. + */ + cached + predicate hasCfgChild(Expr child, CfgNode cfn, CfgNode cfnChild) { + this.reachesBasicBlock(child, cfn, cfnChild.getBasicBlock()) and + cfnChild = desugar(child).getAControlFlowNode() + } +} + +/** Provides classes for control-flow nodes that wrap AST expressions. */ +module ExprNodes { + // TODO: Add more classes + private class AssignExprChildMapping extends ExprChildMapping, AssignExpr { + override predicate relevantChild(Expr e) { e = this.getAnOperand() } + } + + /** A control-flow node that wraps an `AssignExpr` AST expression. */ + class AssignExprCfgNode extends ExprCfgNode { + override AssignExprChildMapping e; + + final override AssignExpr getExpr() { result = ExprCfgNode.super.getExpr() } + + /** Gets the LHS of this assignment. */ + final ExprCfgNode getLhs() { e.hasCfgChild(e.getLeftOperand(), this, result) } + + /** Gets the RHS of this assignment. */ + final ExprCfgNode getRhs() { e.hasCfgChild(e.getRightOperand(), this, result) } + } + + private class OperationExprChildMapping extends ExprChildMapping, Operation { + override predicate relevantChild(Expr e) { e = this.getAnOperand() } + } + + /** A control-flow node that wraps an `Operation` AST expression. */ + class OperationCfgNode extends ExprCfgNode { + override OperationExprChildMapping e; + + override Operation getExpr() { result = super.getExpr() } + + /** Gets an operand of this operation. */ + final ExprCfgNode getAnOperand() { e.hasCfgChild(e.getAnOperand(), this, result) } + } + + /** A control-flow node that wraps a `BinaryOperation` AST expression. */ + class BinaryOperationCfgNode extends OperationCfgNode { + private BinaryOperation bo; + + BinaryOperationCfgNode() { e = bo } + + override BinaryOperation getExpr() { result = super.getExpr() } + + /** Gets the left operand of this binary operation. */ + final ExprCfgNode getLeftOperand() { e.hasCfgChild(bo.getLeftOperand(), this, result) } + + /** Gets the right operand of this binary operation. */ + final ExprCfgNode getRightOperand() { e.hasCfgChild(bo.getRightOperand(), this, result) } + } + + private class BlockArgumentChildMapping extends ExprChildMapping, BlockArgument { + override predicate relevantChild(Expr e) { e = this.getValue() } + } + + /** A control-flow node that wraps a `BlockArgument` AST expression. */ + class BlockArgumentCfgNode extends ExprCfgNode { + override BlockArgumentChildMapping e; + + final override BlockArgument getExpr() { result = ExprCfgNode.super.getExpr() } + + /** Gets the value of this block argument. */ + final ExprCfgNode getValue() { e.hasCfgChild(e.getValue(), this, result) } + } + + private class CallExprChildMapping extends ExprChildMapping, Call { + override predicate relevantChild(Expr e) { + e = [this.getAnArgument(), this.(MethodCall).getReceiver(), this.(MethodCall).getBlock()] + } + } + + /** A control-flow node that wraps a `Call` AST expression. */ + class CallCfgNode extends ExprCfgNode { + override CallExprChildMapping e; + + override Call getExpr() { result = super.getExpr() } + + /** Gets the `n`th argument of this call. */ + final ExprCfgNode getArgument(int n) { e.hasCfgChild(e.getArgument(n), this, result) } + + /** Gets the the keyword argument whose key is `keyword` of this call. */ + final ExprCfgNode getKeywordArgument(string keyword) { + e.hasCfgChild(e.getKeywordArgument(keyword), this, result) + } + + /** Gets the number of arguments of this call. */ + final int getNumberOfArguments() { result = e.getNumberOfArguments() } + + /** Gets the receiver of this call. */ + final ExprCfgNode getReceiver() { e.hasCfgChild(e.(MethodCall).getReceiver(), this, result) } + + /** Gets the block of this call. */ + final ExprCfgNode getBlock() { e.hasCfgChild(e.(MethodCall).getBlock(), this, result) } + } + + private class CaseExprChildMapping extends ExprChildMapping, CaseExpr { + override predicate relevantChild(Expr e) { e = this.getValue() or e = this.getBranch(_) } + } + + /** A control-flow node that wraps a `MethodCall` AST expression. */ + class MethodCallCfgNode extends CallCfgNode { + MethodCallCfgNode() { super.getExpr() instanceof MethodCall } + + override MethodCall getExpr() { result = super.getExpr() } + } + + /** A control-flow node that wraps a `CaseExpr` AST expression. */ + class CaseExprCfgNode extends ExprCfgNode { + override CaseExprChildMapping e; + + final override CaseExpr getExpr() { result = ExprCfgNode.super.getExpr() } + + /** Gets the expression being compared, if any. */ + final ExprCfgNode getValue() { e.hasCfgChild(e.getValue(), this, result) } + + /** + * Gets the `n`th branch of this case expression. + */ + final ExprCfgNode getBranch(int n) { e.hasCfgChild(e.getBranch(n), this, result) } + } + + private class ConditionalExprChildMapping extends ExprChildMapping, ConditionalExpr { + override predicate relevantChild(Expr e) { e = this.getCondition() or e = this.getBranch(_) } + } + + /** A control-flow node that wraps a `ConditionalExpr` AST expression. */ + class ConditionalExprCfgNode extends ExprCfgNode { + override ConditionalExprChildMapping e; + + final override ConditionalExpr getExpr() { result = ExprCfgNode.super.getExpr() } + + /** Gets the condition expression. */ + final ExprCfgNode getCondition() { e.hasCfgChild(e.getCondition(), this, result) } + + /** + * Gets the branch of this conditional expression that is taken when the condition + * evaluates to cond, if any. + */ + final ExprCfgNode getBranch(boolean cond) { e.hasCfgChild(e.getBranch(cond), this, result) } + } + + private class ConstantAccessChildMapping extends ExprChildMapping, ConstantAccess { + override predicate relevantChild(Expr e) { e = this.getScopeExpr() } + } + + /** A control-flow node that wraps a `ConditionalExpr` AST expression. */ + class ConstantAccessCfgNode extends ExprCfgNode { + override ConstantAccessChildMapping e; + + final override ConstantAccess getExpr() { result = super.getExpr() } + + /** Gets the scope expression. */ + final ExprCfgNode getScopeExpr() { e.hasCfgChild(e.getScopeExpr(), this, result) } + } + + private class StmtSequenceChildMapping extends ExprChildMapping, StmtSequence { + override predicate relevantChild(Expr e) { e = this.getLastStmt() } + } + + /** A control-flow node that wraps a `StmtSequence` AST expression. */ + class StmtSequenceCfgNode extends ExprCfgNode { + override StmtSequenceChildMapping e; + + final override StmtSequence getExpr() { result = ExprCfgNode.super.getExpr() } + + /** Gets the last statement in this sequence, if any. */ + final ExprCfgNode getLastStmt() { e.hasCfgChild(e.getLastStmt(), this, result) } + } + + private class ForExprChildMapping extends ExprChildMapping, ForExpr { + override predicate relevantChild(Expr e) { e = this.getValue() } + } + + /** A control-flow node that wraps a `ForExpr` AST expression. */ + class ForExprCfgNode extends ExprCfgNode { + override ForExprChildMapping e; + + final override ForExpr getExpr() { result = ExprCfgNode.super.getExpr() } + + /** Gets the value being iterated over. */ + final ExprCfgNode getValue() { e.hasCfgChild(e.getValue(), this, result) } + } + + /** A control-flow node that wraps a `ParenthesizedExpr` AST expression. */ + class ParenthesizedExprCfgNode extends StmtSequenceCfgNode { + ParenthesizedExprCfgNode() { this.getExpr() instanceof ParenthesizedExpr } + } + + /** A control-flow node that wraps a `VariableReadAccess` AST expression. */ + class VariableReadAccessCfgNode extends ExprCfgNode { + override VariableReadAccess e; + + final override VariableReadAccess getExpr() { result = ExprCfgNode.super.getExpr() } + } + + /** A control-flow node that wraps a `InstanceVariableWriteAccess` AST expression. */ + class InstanceVariableWriteAccessCfgNode extends ExprCfgNode { + override InstanceVariableWriteAccess e; + + final override InstanceVariableWriteAccess getExpr() { result = ExprCfgNode.super.getExpr() } + } + + /** A control-flow node that wraps a `StringInterpolationComponent` AST expression. */ + class StringInterpolationComponentCfgNode extends StmtSequenceCfgNode { + StringInterpolationComponentCfgNode() { this.getNode() instanceof StringInterpolationComponent } + } + + private class StringlikeLiteralChildMapping extends ExprChildMapping, StringlikeLiteral { + override predicate relevantChild(Expr e) { e = this.getComponent(_) } + } + + /** A control-flow node that wraps a `StringlikeLiteral` AST expression. */ + class StringlikeLiteralCfgNode extends ExprCfgNode { + override StringlikeLiteralChildMapping e; + + final override StringlikeLiteral getExpr() { result = super.getExpr() } + + /** Gets a component of this `StringlikeLiteral` */ + StringComponentCfgNode getAComponent() { e.hasCfgChild(e.getComponent(_), this, result) } + } + + /** A control-flow node that wraps a `StringLiteral` AST expression. */ + class StringLiteralCfgNode extends ExprCfgNode { + override StringLiteral e; + + final override StringLiteral getExpr() { result = super.getExpr() } + } + + /** A control-flow node that wraps a `RegExpLiteral` AST expression. */ + class RegExpLiteralCfgNode extends ExprCfgNode { + override RegExpLiteral e; + + final override RegExpLiteral getExpr() { result = super.getExpr() } + } + + /** A control-flow node that wraps a `ComparisonOperation` AST expression. */ + class ComparisonOperationCfgNode extends BinaryOperationCfgNode { + ComparisonOperationCfgNode() { e instanceof ComparisonOperation } + + override ComparisonOperation getExpr() { result = super.getExpr() } + } + + /** A control-flow node that wraps a `RelationalOperation` AST expression. */ + class RelationalOperationCfgNode extends ComparisonOperationCfgNode { + RelationalOperationCfgNode() { e instanceof RelationalOperation } + + final override RelationalOperation getExpr() { result = super.getExpr() } + } + + /** A control-flow node that wraps an `ElementReference` AST expression. */ + class ElementReferenceCfgNode extends MethodCallCfgNode { + ElementReferenceCfgNode() { e instanceof ElementReference } + + final override ElementReference getExpr() { result = super.getExpr() } + } +} diff --git a/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph.qll b/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph.qll new file mode 100644 index 000000000000..aad9daa48276 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph.qll @@ -0,0 +1,341 @@ +/** Provides classes representing the control flow graph. */ + +private import codeql.Locations +private import codeql.ruby.AST +private import codeql.ruby.controlflow.BasicBlocks +private import SuccessorTypes +private import internal.ControlFlowGraphImpl +private import internal.Splitting +private import internal.Completion + +/** An AST node with an associated control-flow graph. */ +class CfgScope extends Scope instanceof CfgScope::Range_ { + /** Gets the CFG scope that this scope is nested under, if any. */ + final CfgScope getOuterCfgScope() { + exists(AstNode parent | + parent = this.getParent() and + result = getCfgScope(parent) + ) + } +} + +/** + * A control flow node. + * + * A control flow node is a node in the control flow graph (CFG). There is a + * many-to-one relationship between CFG nodes and AST nodes. + * + * Only nodes that can be reached from an entry point are included in the CFG. + */ +class CfgNode extends TNode { + /** Gets a textual representation of this control flow node. */ + string toString() { none() } + + /** Gets the AST node that this node corresponds to, if any. */ + AstNode getNode() { none() } + + /** Gets the location of this control flow node. */ + Location getLocation() { none() } + + /** Gets the file of this control flow node. */ + final File getFile() { result = this.getLocation().getFile() } + + /** Holds if this control flow node has conditional successors. */ + final predicate isCondition() { exists(this.getASuccessor(any(BooleanSuccessor bs))) } + + /** Gets the scope of this node. */ + final CfgScope getScope() { result = this.getBasicBlock().getScope() } + + /** Gets the basic block that this control flow node belongs to. */ + BasicBlock getBasicBlock() { result.getANode() = this } + + /** Gets a successor node of a given type, if any. */ + final CfgNode getASuccessor(SuccessorType t) { result = getASuccessor(this, t) } + + /** Gets an immediate successor, if any. */ + final CfgNode getASuccessor() { result = this.getASuccessor(_) } + + /** Gets an immediate predecessor node of a given flow type, if any. */ + final CfgNode getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this } + + /** Gets an immediate predecessor, if any. */ + final CfgNode getAPredecessor() { result = this.getAPredecessor(_) } + + /** Holds if this node has more than one predecessor. */ + final predicate isJoin() { strictcount(this.getAPredecessor()) > 1 } + + /** Holds if this node has more than one successor. */ + final predicate isBranch() { strictcount(this.getASuccessor()) > 1 } +} + +/** The type of a control flow successor. */ +class SuccessorType extends TSuccessorType { + /** Gets a textual representation of successor type. */ + string toString() { none() } +} + +/** Provides different types of control flow successor types. */ +module SuccessorTypes { + /** A normal control flow successor. */ + class NormalSuccessor extends SuccessorType, TSuccessorSuccessor { + final override string toString() { result = "successor" } + } + + /** + * A conditional control flow successor. Either a Boolean successor (`BooleanSuccessor`), + * an emptiness successor (`EmptinessSuccessor`), or a matching successor + * (`MatchingSuccessor`) + */ + class ConditionalSuccessor extends SuccessorType { + boolean value; + + ConditionalSuccessor() { + this = TBooleanSuccessor(value) or + this = TEmptinessSuccessor(value) or + this = TMatchingSuccessor(value) + } + + /** Gets the Boolean value of this successor. */ + final boolean getValue() { result = value } + + override string toString() { result = getValue().toString() } + } + + /** + * A Boolean control flow successor. + * + * For example, in + * + * ```rb + * if x >= 0 + * puts "positive" + * else + * puts "negative" + * end + * ``` + * + * `x >= 0` has both a `true` successor and a `false` successor. + */ + class BooleanSuccessor extends ConditionalSuccessor, TBooleanSuccessor { } + + /** + * An emptiness control flow successor. + * + * For example, this program fragment: + * + * ```rb + * for arg in args do + * puts arg + * end + * puts "done"; + * ``` + * + * has a control flow graph containing emptiness successors: + * + * ``` + * args + * | + * for------<----- + * / \ \ + * / \ | + * / \ | + * / \ | + * empty non-empty | + * | \ | + * puts "done" \ | + * arg | + * | | + * puts arg | + * \___/ + * ``` + */ + class EmptinessSuccessor extends ConditionalSuccessor, TEmptinessSuccessor { + override string toString() { if value = true then result = "empty" else result = "non-empty" } + } + + /** + * A matching control flow successor. + * + * For example, this program fragment: + * + * ```rb + * case x + * when 1 then puts "one" + * else puts "not one" + * end + * ``` + * + * has a control flow graph containing matching successors: + * + * ``` + * x + * | + * 1 + * / \ + * / \ + * / \ + * / \ + * match non-match + * | | + * puts "one" puts "not one" + * ``` + */ + class MatchingSuccessor extends ConditionalSuccessor, TMatchingSuccessor { + override string toString() { if value = true then result = "match" else result = "no-match" } + } + + /** + * A `return` control flow successor. + * + * Example: + * + * ```rb + * def sum(x,y) + * return x + y + * end + * ``` + * + * The exit node of `sum` is a `return` successor of the `return x + y` + * statement. + */ + class ReturnSuccessor extends SuccessorType, TReturnSuccessor { + final override string toString() { result = "return" } + } + + /** + * A `break` control flow successor. + * + * Example: + * + * ```rb + * def m + * while x >= 0 + * x -= 1 + * if num > 100 + * break + * end + * end + * puts "done" + * end + * ``` + * + * The node `puts "done"` is `break` successor of the node `break`. + */ + class BreakSuccessor extends SuccessorType, TBreakSuccessor { + final override string toString() { result = "break" } + } + + /** + * A `next` control flow successor. + * + * Example: + * + * ```rb + * def m + * while x >= 0 + * x -= 1 + * if num > 100 + * next + * end + * end + * puts "done" + * end + * ``` + * + * The node `x >= 0` is `next` successor of the node `next`. + */ + class NextSuccessor extends SuccessorType, TNextSuccessor { + final override string toString() { result = "next" } + } + + /** + * A `redo` control flow successor. + * + * Example: + * + * Example: + * + * ```rb + * def m + * while x >= 0 + * x -= 1 + * if num > 100 + * redo + * end + * end + * puts "done" + * end + * ``` + * + * The node `x -= 1` is `redo` successor of the node `redo`. + */ + class RedoSuccessor extends SuccessorType, TRedoSuccessor { + final override string toString() { result = "redo" } + } + + /** + * A `retry` control flow successor. + * + * Example: + * + * Example: + * + * ```rb + * def m + * begin + * puts "Retry" + * raise + * rescue + * retry + * end + * end + * ``` + * + * The node `puts "Retry"` is `retry` successor of the node `retry`. + */ + class RetrySuccessor extends SuccessorType, TRetrySuccessor { + final override string toString() { result = "retry" } + } + + /** + * An exceptional control flow successor. + * + * Example: + * + * ```rb + * def m x + * if x > 2 + * raise "x > 2" + * end + * puts "x <= 2" + * end + * ``` + * + * The exit node of `m` is an exceptional successor of the node + * `raise "x > 2"`. + */ + class RaiseSuccessor extends SuccessorType, TRaiseSuccessor { + final override string toString() { result = "raise" } + } + + /** + * An exit control flow successor. + * + * Example: + * + * ```rb + * def m x + * if x > 2 + * exit 1 + * end + * puts "x <= 2" + * end + * ``` + * + * The exit node of `m` is an exit successor of the node + * `exit 1`. + */ + class ExitSuccessor extends SuccessorType, TExitSuccessor { + final override string toString() { result = "exit" } + } +} diff --git a/ruby/ql/lib/codeql/ruby/controlflow/internal/Completion.qll b/ruby/ql/lib/codeql/ruby/controlflow/internal/Completion.qll new file mode 100644 index 000000000000..e7f64d1318e6 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/controlflow/internal/Completion.qll @@ -0,0 +1,507 @@ +/** + * Provides classes representing control flow completions. + * + * A completion represents how a statement or expression terminates. + */ + +private import codeql.ruby.AST +private import codeql.ruby.ast.internal.AST +private import codeql.ruby.controlflow.ControlFlowGraph +private import ControlFlowGraphImpl +private import NonReturning +private import SuccessorTypes + +private newtype TCompletion = + TSimpleCompletion() or + TBooleanCompletion(boolean b) { b in [false, true] } or + TEmptinessCompletion(boolean isEmpty) { isEmpty in [false, true] } or + TMatchingCompletion(boolean isMatch) { isMatch in [false, true] } or + TReturnCompletion() or + TBreakCompletion() or + TNextCompletion() or + TRedoCompletion() or + TRetryCompletion() or + TRaiseCompletion() or // TODO: Add exception type? + TExitCompletion() or + TNestedCompletion(Completion inner, Completion outer, int nestLevel) { + inner = TBreakCompletion() and + outer instanceof NonNestedNormalCompletion and + nestLevel = 0 + or + inner instanceof NormalCompletion and + nestedEnsureCompletion(outer, nestLevel) + } + +pragma[noinline] +private predicate nestedEnsureCompletion(Completion outer, int nestLevel) { + ( + outer = TReturnCompletion() + or + outer = TBreakCompletion() + or + outer = TNextCompletion() + or + outer = TRedoCompletion() + or + outer = TRetryCompletion() + or + outer = TRaiseCompletion() + or + outer = TExitCompletion() + ) and + nestLevel = any(Trees::BodyStmtTree t).getNestLevel() +} + +pragma[noinline] +private predicate completionIsValidForStmt(AstNode n, Completion c) { + n = TForIn(_) and + c instanceof EmptinessCompletion + or + n instanceof BreakStmt and + c = TBreakCompletion() + or + n instanceof NextStmt and + c = TNextCompletion() + or + n instanceof RedoStmt and + c = TRedoCompletion() + or + n instanceof ReturnStmt and + c = TReturnCompletion() +} + +/** + * Holds if `c` happens in an exception-aware context, that is, it may be + * `rescue`d or `ensure`d. In such cases, we assume that the target of `c` + * may raise an exception (in addition to evaluating normally). + */ +private predicate mayRaise(Call c) { + exists(Trees::BodyStmtTree bst | c = bst.getBodyChild(_, true).getAChild*() | + exists(bst.getARescue()) + or + exists(bst.getEnsure()) + ) +} + +/** A completion of a statement or an expression. */ +abstract class Completion extends TCompletion { + /** Holds if this completion is valid for node `n`. */ + predicate isValidFor(AstNode n) { + this = n.(NonReturningCall).getACompletion() + or + completionIsValidForStmt(n, this) + or + mustHaveBooleanCompletion(n) and + ( + exists(boolean value | isBooleanConstant(n, value) | this = TBooleanCompletion(value)) + or + not isBooleanConstant(n, _) and + this = TBooleanCompletion(_) + ) + or + mustHaveMatchingCompletion(n) and + this = TMatchingCompletion(_) + or + n = any(RescueModifierExpr parent).getBody() and this = TRaiseCompletion() + or + mayRaise(n) and + this = TRaiseCompletion() + or + not n instanceof NonReturningCall and + not completionIsValidForStmt(n, _) and + not mustHaveBooleanCompletion(n) and + not mustHaveMatchingCompletion(n) and + this = TSimpleCompletion() + } + + /** + * Holds if this completion will continue a loop when it is the completion + * of a loop body. + */ + predicate continuesLoop() { + this instanceof NormalCompletion or + this instanceof NextCompletion + } + + /** + * Gets the inner completion. This is either the inner completion, + * when the completion is nested, or the completion itself. + */ + Completion getInnerCompletion() { result = this } + + /** + * Gets the outer completion. This is either the outer completion, + * when the completion is nested, or the completion itself. + */ + Completion getOuterCompletion() { result = this } + + /** Gets a successor type that matches this completion. */ + abstract SuccessorType getAMatchingSuccessorType(); + + /** Gets a textual representation of this completion. */ + abstract string toString(); +} + +/** Holds if node `n` has the Boolean constant value `value`. */ +private predicate isBooleanConstant(AstNode n, boolean value) { + mustHaveBooleanCompletion(n) and + ( + n.(BooleanLiteral).isTrue() and + value = true + or + n.(BooleanLiteral).isFalse() and + value = false + ) +} + +/** + * Holds if a normal completion of `n` must be a Boolean completion. + */ +private predicate mustHaveBooleanCompletion(AstNode n) { + inBooleanContext(n) and + not n instanceof NonReturningCall +} + +/** + * Holds if `n` is used in a Boolean context. That is, the value + * that `n` evaluates to determines a true/false branch successor. + */ +private predicate inBooleanContext(AstNode n) { + exists(ConditionalExpr i | + n = i.getCondition() + or + inBooleanContext(i) and + n = i.getBranch(_) + ) + or + n = any(ConditionalLoop parent).getCondition() + or + exists(LogicalAndExpr parent | + n = parent.getLeftOperand() + or + inBooleanContext(parent) and + n = parent.getRightOperand() + ) + or + exists(LogicalOrExpr parent | + n = parent.getLeftOperand() + or + inBooleanContext(parent) and + n = parent.getRightOperand() + ) + or + n = any(NotExpr parent | inBooleanContext(parent)).getOperand() + or + n = any(StmtSequence parent | inBooleanContext(parent)).getLastStmt() + or + exists(CaseExpr c, WhenExpr w | + not exists(c.getValue()) and + c.getAWhenBranch() = w and + w.getPattern(_) = n + ) +} + +/** + * Holds if a normal completion of `n` must be a matching completion. + */ +private predicate mustHaveMatchingCompletion(AstNode n) { + inMatchingContext(n) and + not n instanceof NonReturningCall +} + +/** + * Holds if `n` is used in a matching context. That is, whether or + * not the value of `n` matches, determines the successor. + */ +private predicate inMatchingContext(AstNode n) { + n = any(RescueClause r).getException(_) + or + exists(CaseExpr c, WhenExpr w | + exists(c.getValue()) and + c.getAWhenBranch() = w and + w.getPattern(_) = n + ) + or + n.(Trees::DefaultValueParameterTree).hasDefaultValue() +} + +/** + * A completion that represents normal evaluation of a statement or an + * expression. + */ +abstract class NormalCompletion extends Completion { } + +abstract private class NonNestedNormalCompletion extends NormalCompletion { } + +/** A simple (normal) completion. */ +class SimpleCompletion extends NonNestedNormalCompletion, TSimpleCompletion { + override NormalSuccessor getAMatchingSuccessorType() { any() } + + override string toString() { result = "simple" } +} + +/** + * A completion that represents evaluation of an expression, whose value determines + * the successor. Either a Boolean completion (`BooleanCompletion`), an emptiness + * completion (`EmptinessCompletion`), or a matching completion (`MatchingCompletion`). + */ +abstract class ConditionalCompletion extends NonNestedNormalCompletion { + boolean value; + + bindingset[value] + ConditionalCompletion() { any() } + + /** Gets the Boolean value of this conditional completion. */ + final boolean getValue() { result = value } +} + +/** + * A completion that represents evaluation of an expression + * with a Boolean value. + */ +class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion { + BooleanCompletion() { this = TBooleanCompletion(value) } + + /** Gets the dual Boolean completion. */ + BooleanCompletion getDual() { result = TBooleanCompletion(value.booleanNot()) } + + override BooleanSuccessor getAMatchingSuccessorType() { result.getValue() = value } + + override string toString() { result = value.toString() } +} + +/** A Boolean `true` completion. */ +class TrueCompletion extends BooleanCompletion { + TrueCompletion() { this.getValue() = true } +} + +/** A Boolean `false` completion. */ +class FalseCompletion extends BooleanCompletion { + FalseCompletion() { this.getValue() = false } +} + +/** + * A completion that represents evaluation of an emptiness test, for example + * a test in a `for in` statement. + */ +class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion { + EmptinessCompletion() { this = TEmptinessCompletion(value) } + + override EmptinessSuccessor getAMatchingSuccessorType() { result.getValue() = value } + + override string toString() { if value = true then result = "empty" else result = "non-empty" } +} + +/** + * A completion that represents evaluation of a matching test, for example + * a test in a `rescue` statement. + */ +class MatchingCompletion extends ConditionalCompletion, TMatchingCompletion { + MatchingCompletion() { this = TMatchingCompletion(value) } + + override MatchingSuccessor getAMatchingSuccessorType() { result.getValue() = value } + + override string toString() { if value = true then result = "match" else result = "no-match" } +} + +/** + * A completion that represents evaluation of a statement or an + * expression resulting in a return. + */ +class ReturnCompletion extends Completion { + ReturnCompletion() { + this = TReturnCompletion() or + this = TNestedCompletion(_, TReturnCompletion(), _) + } + + override ReturnSuccessor getAMatchingSuccessorType() { any() } + + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TReturnCompletion() and result = "return" + } +} + +/** + * A completion that represents evaluation of a statement or an + * expression resulting in a break from a loop. + */ +class BreakCompletion extends Completion { + BreakCompletion() { + this = TBreakCompletion() or + this = TNestedCompletion(_, TBreakCompletion(), _) + } + + override BreakSuccessor getAMatchingSuccessorType() { any() } + + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TBreakCompletion() and result = "break" + } +} + +/** + * A completion that represents evaluation of a statement or an + * expression resulting in a continuation of a loop. + */ +class NextCompletion extends Completion { + NextCompletion() { + this = TNextCompletion() or + this = TNestedCompletion(_, TNextCompletion(), _) + } + + override NextSuccessor getAMatchingSuccessorType() { any() } + + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TNextCompletion() and result = "next" + } +} + +/** + * A completion that represents evaluation of a statement or an + * expression resulting in a redo of a loop iteration. + */ +class RedoCompletion extends Completion { + RedoCompletion() { + this = TRedoCompletion() or + this = TNestedCompletion(_, TRedoCompletion(), _) + } + + override RedoSuccessor getAMatchingSuccessorType() { any() } + + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TRedoCompletion() and result = "redo" + } +} + +/** + * A completion that represents evaluation of a statement or an + * expression resulting in a retry. + */ +class RetryCompletion extends Completion { + RetryCompletion() { + this = TRetryCompletion() or + this = TNestedCompletion(_, TRetryCompletion(), _) + } + + override RetrySuccessor getAMatchingSuccessorType() { any() } + + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TRetryCompletion() and result = "retry" + } +} + +/** + * A completion that represents evaluation of a statement or an + * expression resulting in a thrown exception. + */ +class RaiseCompletion extends Completion { + RaiseCompletion() { + this = TRaiseCompletion() or + this = TNestedCompletion(_, TRaiseCompletion(), _) + } + + override RaiseSuccessor getAMatchingSuccessorType() { any() } + + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TRaiseCompletion() and result = "raise" + } +} + +/** + * A completion that represents evaluation of a statement or an + * expression resulting in an abort/exit. + */ +class ExitCompletion extends Completion { + ExitCompletion() { + this = TExitCompletion() or + this = TNestedCompletion(_, TExitCompletion(), _) + } + + override ExitSuccessor getAMatchingSuccessorType() { any() } + + override string toString() { + // `NestedCompletion` defines `toString()` for the other case + this = TExitCompletion() and result = "exit" + } +} + +/** + * A nested completion. For example, in + * + * ```rb + * def m + * while x >= 0 + * x -= 1 + * if num > 100 + * break + * end + * end + * puts "done" + * end + * ``` + * + * the `while` loop can have a nested completion where the inner completion + * is a `break` and the outer completion is a simple successor. + */ +abstract class NestedCompletion extends Completion, TNestedCompletion { + Completion inner; + Completion outer; + int nestLevel; + + NestedCompletion() { this = TNestedCompletion(inner, outer, nestLevel) } + + /** Gets a completion that is compatible with the inner completion. */ + abstract Completion getAnInnerCompatibleCompletion(); + + /** Gets the level of this nested completion. */ + final int getNestLevel() { result = nestLevel } + + override string toString() { result = outer + " [" + inner + "] (" + nestLevel + ")" } +} + +class NestedBreakCompletion extends NormalCompletion, NestedCompletion { + NestedBreakCompletion() { + inner = TBreakCompletion() and + outer instanceof NonNestedNormalCompletion + } + + override BreakCompletion getInnerCompletion() { result = inner } + + override NonNestedNormalCompletion getOuterCompletion() { result = outer } + + override Completion getAnInnerCompatibleCompletion() { + result = inner and + outer = TSimpleCompletion() + or + result = TNestedCompletion(outer, inner, _) + } + + override SuccessorType getAMatchingSuccessorType() { + outer instanceof SimpleCompletion and + result instanceof BreakSuccessor + or + result = outer.(ConditionalCompletion).getAMatchingSuccessorType() + } +} + +class NestedEnsureCompletion extends NestedCompletion { + NestedEnsureCompletion() { + inner instanceof NormalCompletion and + nestedEnsureCompletion(outer, nestLevel) + } + + override NormalCompletion getInnerCompletion() { result = inner } + + override Completion getOuterCompletion() { result = outer } + + override Completion getAnInnerCompatibleCompletion() { + result.getOuterCompletion() = this.getInnerCompletion() + } + + override SuccessorType getAMatchingSuccessorType() { none() } +} diff --git a/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImpl.qll b/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImpl.qll new file mode 100644 index 000000000000..5bfea3aca7b9 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImpl.qll @@ -0,0 +1,1164 @@ +/** + * Provides auxiliary classes and predicates used to construct the basic successor + * relation on control flow elements. + * + * The implementation is centered around the concept of a _completion_, which + * models how the execution of a statement or expression terminates. + * Completions are represented as an algebraic data type `Completion` defined in + * `Completion.qll`. + * + * The CFG is built by structural recursion over the AST. To achieve this the + * CFG edges related to a given AST node, `n`, are divided into three categories: + * + * 1. The in-going edge that points to the first CFG node to execute when + * `n` is going to be executed. + * 2. The out-going edges for control flow leaving `n` that are going to some + * other node in the surrounding context of `n`. + * 3. The edges that have both of their end-points entirely within the AST + * node and its children. + * + * The edges in (1) and (2) are inherently non-local and are therefore + * initially calculated as half-edges, that is, the single node, `k`, of the + * edge contained within `n`, by the predicates `k = first(n)` and `k = last(n, _)`, + * respectively. The edges in (3) can then be enumerated directly by the predicate + * `succ` by calling `first` and `last` recursively on the children of `n` and + * connecting the end-points. This yields the entire CFG, since all edges are in + * (3) for _some_ AST node. + * + * The second parameter of `last` is the completion, which is necessary to distinguish + * the out-going edges from `n`. Note that the completion changes as the calculation of + * `last` proceeds outward through the AST; for example, a `BreakCompletion` is + * caught up by its surrounding loop and turned into a `NormalCompletion`. + */ + +private import codeql.ruby.AST +private import codeql.ruby.ast.internal.AST as ASTInternal +private import codeql.ruby.ast.internal.Scope +private import codeql.ruby.ast.Scope +private import codeql.ruby.ast.internal.TreeSitter +private import codeql.ruby.ast.internal.Variable +private import codeql.ruby.controlflow.ControlFlowGraph +private import Completion +import ControlFlowGraphImplShared + +module CfgScope { + abstract class Range_ extends AstNode { + abstract predicate entry(AstNode first); + + abstract predicate exit(AstNode last, Completion c); + } + + private class ToplevelScope extends Range_, Toplevel { + final override predicate entry(AstNode first) { first(this, first) } + + final override predicate exit(AstNode last, Completion c) { last(this, last, c) } + } + + private class EndBlockScope extends Range_, EndBlock { + final override predicate entry(AstNode first) { + first(this.(Trees::EndBlockTree).getBodyChild(0, _), first) + } + + final override predicate exit(AstNode last, Completion c) { + last(this.(Trees::EndBlockTree).getLastBodyChild(), last, c) + } + } + + private class BodyStmtCallableScope extends Range_, ASTInternal::TBodyStmt, Callable { + final override predicate entry(AstNode first) { this.(Trees::BodyStmtTree).firstInner(first) } + + final override predicate exit(AstNode last, Completion c) { + this.(Trees::BodyStmtTree).lastInner(last, c) + } + } + + private class BraceBlockScope extends Range_, BraceBlock { + final override predicate entry(AstNode first) { + first(this.(Trees::BraceBlockTree).getBodyChild(0, _), first) + } + + final override predicate exit(AstNode last, Completion c) { + last(this.(Trees::BraceBlockTree).getLastBodyChild(), last, c) + } + } +} + +/** Holds if `first` is first executed when entering `scope`. */ +pragma[nomagic] +predicate succEntry(CfgScope::Range_ scope, AstNode first) { scope.entry(first) } + +/** Holds if `last` with completion `c` can exit `scope`. */ +pragma[nomagic] +predicate succExit(CfgScope::Range_ scope, AstNode last, Completion c) { scope.exit(last, c) } + +// TODO: remove this class; it should be replaced with an implicit non AST node +private class ForIn extends AstNode, ASTInternal::TForIn { + final override string toString() { result = "In" } +} + +// TODO: remove this class; it should be replaced with an implicit non AST node +private class ForRange extends ForExpr { + override AstNode getAChild(string pred) { + result = super.getAChild(pred) + or + pred = "" and + result = this.getIn() + } + + ForIn getIn() { + result = ASTInternal::TForIn(ASTInternal::toGenerated(this).(Ruby::For).getValue()) + } +} + +/** Defines the CFG by dispatch on the various AST types. */ +module Trees { + private class AliasStmtTree extends StandardPreOrderTree, AliasStmt { + final override ControlFlowTree getChildElement(int i) { + result = this.getNewName() and i = 0 + or + result = this.getOldName() and i = 1 + } + } + + private class ArgumentListTree extends StandardTree, ArgumentList { + final override ControlFlowTree getChildElement(int i) { result = this.getElement(i) } + + final override predicate first(AstNode first) { first(this.getFirstChildElement(), first) } + + final override predicate last(AstNode last, Completion c) { + last(this.getLastChildElement(), last, c) + } + } + + private class AssignExprTree extends StandardPostOrderTree, AssignExpr { + AssignExprTree() { + exists(Expr left | left = this.getLeftOperand() | + left instanceof VariableAccess or + left instanceof ConstantAccess + ) + } + + final override ControlFlowTree getChildElement(int i) { + result = this.getLeftOperand() and i = 0 + or + result = this.getRightOperand() and i = 1 + } + } + + private class BeginTree extends BodyStmtTree, BeginExpr { + final override predicate first(AstNode first) { this.firstInner(first) } + + final override predicate last(AstNode last, Completion c) { this.lastInner(last, c) } + + final override predicate propagatesAbnormal(AstNode child) { none() } + } + + private class BlockArgumentTree extends StandardPostOrderTree, BlockArgument { + final override ControlFlowTree getChildElement(int i) { result = this.getValue() and i = 0 } + } + + abstract private class NonDefaultValueParameterTree extends ControlFlowTree, NamedParameter { + final override predicate first(AstNode first) { + this.getDefiningAccess().(ControlFlowTree).first(first) + } + + final override predicate last(AstNode last, Completion c) { + this.getDefiningAccess().(ControlFlowTree).last(last, c) + } + + override predicate propagatesAbnormal(AstNode child) { + this.getDefiningAccess().(ControlFlowTree).propagatesAbnormal(child) + } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { none() } + } + + private class BlockParameterTree extends NonDefaultValueParameterTree, BlockParameter { } + + abstract class BodyStmtTree extends StmtSequenceTree, BodyStmt { + override predicate first(AstNode first) { first = this } + + predicate firstInner(AstNode first) { + first(this.getBodyChild(0, _), first) + or + not exists(this.getBodyChild(_, _)) and + ( + first(this.getRescue(_), first) + or + not exists(this.getRescue(_)) and + first(this.getEnsure(), first) + ) + } + + predicate lastInner(AstNode last, Completion c) { + exists(boolean ensurable | last = this.getAnEnsurePredecessor(c, ensurable) | + not this.hasEnsure() + or + ensurable = false + ) + or + // If the body completes normally, take the completion from the `ensure` block + this.lastEnsure(last, c, any(NormalCompletion nc), _) + or + // If the `ensure` block completes normally, it inherits any non-normal + // completion from the body + c = + any(NestedEnsureCompletion nec | + this.lastEnsure(last, nec.getAnInnerCompatibleCompletion(), nec.getOuterCompletion(), + nec.getNestLevel()) + ) + or + not exists(this.getBodyChild(_, _)) and + not exists(this.getRescue(_)) and + this.lastEnsure0(last, c) + or + last([this.getEnsure(), this.getBodyChild(_, false)], last, c) and + not c instanceof NormalCompletion + } + + override predicate succ(AstNode pred, AstNode succ, Completion c) { + // Normal left-to-right evaluation in the body + exists(int i | + last(this.getBodyChild(i, _), pred, c) and + first(this.getBodyChild(i + 1, _), succ) and + c instanceof NormalCompletion + ) + or + // Exceptional flow from body to first `rescue` + this.lastBody(pred, c, true) and + first(this.getRescue(0), succ) and + c instanceof RaiseCompletion + or + // Flow from one `rescue` clause to the next when there is no match + exists(RescueTree rescue, int i | rescue = this.getRescue(i) | + rescue.lastNoMatch(pred, c) and + first(this.getRescue(i + 1), succ) + ) + or + // Flow from body to `else` block when no exception + this.lastBody(pred, c, _) and + first(this.getElse(), succ) and + c instanceof NormalCompletion + or + // Flow into `ensure` block + pred = getAnEnsurePredecessor(c, true) and + first(this.getEnsure(), succ) + } + + /** + * Gets a last element from this block that may finish with completion `c`, such + * that control may be transferred to the `ensure` block (if it exists), but only + * if `ensurable = true`. + */ + pragma[nomagic] + private AstNode getAnEnsurePredecessor(Completion c, boolean ensurable) { + this.lastBody(result, c, ensurable) and + ( + // Any non-throw completion will always continue directly to the `ensure` block, + // unless there is an `else` block + not c instanceof RaiseCompletion and + not exists(this.getElse()) + or + // Any completion will continue to the `ensure` block when there are no `rescue` + // blocks + not exists(this.getRescue(_)) + ) + or + // Last element from any matching `rescue` block continues to the `ensure` block + this.getRescue(_).(RescueTree).lastMatch(result, c) and + ensurable = true + or + // If the last `rescue` block does not match, continue to the `ensure` block + exists(int lst, MatchingCompletion mc | + this.getRescue(lst).(RescueTree).lastNoMatch(result, mc) and + mc.getValue() = false and + not exists(this.getRescue(lst + 1)) and + c = + any(NestedEnsureCompletion nec | + nec.getOuterCompletion() instanceof RaiseCompletion and + nec.getInnerCompletion() = mc and + nec.getNestLevel() = 0 + ) and + ensurable = true + ) + or + // Last element of `else` block continues to the `ensure` block + last(this.getElse(), result, c) and + ensurable = true + } + + pragma[nomagic] + private predicate lastEnsure0(AstNode last, Completion c) { last(this.getEnsure(), last, c) } + + /** + * Gets a descendant that belongs to the `ensure` block of this block, if any. + * Nested `ensure` blocks are not included. + */ + pragma[nomagic] + AstNode getAnEnsureDescendant() { + result = this.getEnsure() + or + exists(AstNode mid | + mid = this.getAnEnsureDescendant() and + result = mid.getAChild() and + getCfgScope(result) = getCfgScope(mid) and + not exists(BodyStmt nestedBlock | + result = nestedBlock.getEnsure() and + nestedBlock != this + ) + ) + } + + /** + * Holds if `innerBlock` has an `ensure` block and is immediately nested inside the + * `ensure` block of this block. + */ + private predicate nestedEnsure(BodyStmtTree innerBlock) { + exists(StmtSequence innerEnsure | + innerEnsure = this.getAnEnsureDescendant().getAChild() and + getCfgScope(innerEnsure) = getCfgScope(this) and + innerEnsure = innerBlock.(BodyStmt).getEnsure() + ) + } + + /** + * Gets the `ensure`-nesting level of this block. That is, the number of `ensure` + * blocks that this block is nested under. + */ + int getNestLevel() { result = count(BodyStmtTree outer | outer.nestedEnsure+(this)) } + + pragma[nomagic] + private predicate lastEnsure( + AstNode last, NormalCompletion ensure, Completion outer, int nestLevel + ) { + this.lastEnsure0(last, ensure) and + exists( + this.getAnEnsurePredecessor(any(Completion c0 | outer = c0.getOuterCompletion()), true) + ) and + nestLevel = this.getNestLevel() + } + + /** + * Holds if `last` is a last element in the body of this block. `ensurable` + * indicates whether `last` may be a predecessor of an `ensure` block. + */ + pragma[nomagic] + private predicate lastBody(AstNode last, Completion c, boolean ensurable) { + exists(boolean rescuable | + if c instanceof RaiseCompletion then ensurable = rescuable else ensurable = true + | + last(this.getBodyChild(_, rescuable), last, c) and + not c instanceof NormalCompletion + or + exists(int lst | + last(this.getBodyChild(lst, rescuable), last, c) and + not exists(this.getBodyChild(lst + 1, _)) + ) + ) + } + } + + private class BooleanLiteralTree extends LeafTree, BooleanLiteral { } + + class BraceBlockTree extends StmtSequenceTree, BraceBlock { + final override predicate propagatesAbnormal(AstNode child) { none() } + + final override AstNode getBodyChild(int i, boolean rescuable) { + result = this.getParameter(i) and rescuable = false + or + result = StmtSequenceTree.super.getBodyChild(i - this.getNumberOfParameters(), rescuable) + } + + override predicate first(AstNode first) { first = this } + + override predicate succ(AstNode pred, AstNode succ, Completion c) { + // Normal left-to-right evaluation in the body + exists(int i | + last(this.getBodyChild(i, _), pred, c) and + first(this.getBodyChild(i + 1, _), succ) and + c instanceof NormalCompletion + ) + } + } + + private class CallTree extends StandardPostOrderTree, Call { + CallTree() { + // Logical operations are handled separately + not this instanceof UnaryLogicalOperation and + not this instanceof BinaryLogicalOperation + } + + override ControlFlowTree getChildElement(int i) { result = this.getArgument(i) } + } + + private class CaseTree extends PreOrderTree, CaseExpr { + final override predicate propagatesAbnormal(AstNode child) { + child = this.getValue() or child = this.getABranch() + } + + final override predicate last(AstNode last, Completion c) { + last(this.getValue(), last, c) and not exists(this.getABranch()) + or + last(this.getAWhenBranch().getBody(), last, c) + or + exists(int i, ControlFlowTree lastBranch | + lastBranch = this.getBranch(i) and + not exists(this.getBranch(i + 1)) and + last(lastBranch, last, c) + ) + } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + exists(AstNode next | + pred = this and + first(next, succ) and + c instanceof SimpleCompletion + | + next = this.getValue() + or + not exists(this.getValue()) and + next = this.getBranch(0) + ) + or + last(this.getValue(), pred, c) and + first(this.getBranch(0), succ) and + c instanceof SimpleCompletion + or + exists(int i, WhenTree branch | branch = this.getBranch(i) | + last(branch.getLastPattern(), pred, c) and + first(this.getBranch(i + 1), succ) and + c.(ConditionalCompletion).getValue() = false + ) + } + } + + private class CharacterTree extends LeafTree, CharacterLiteral { } + + private class ClassDeclarationTree extends NamespaceTree, ClassDeclaration { + /** Gets the `i`th child in the body of this block. */ + final override AstNode getBodyChild(int i, boolean rescuable) { + result = this.getScopeExpr() and i = 0 and rescuable = false + or + result = this.getSuperclassExpr() and + i = count(this.getScopeExpr()) and + rescuable = true + or + result = + super + .getBodyChild(i - count(this.getScopeExpr()) - count(this.getSuperclassExpr()), + rescuable) + } + } + + private class ClassVariableTree extends LeafTree, ClassVariableAccess { } + + private class ConditionalExprTree extends PostOrderTree, ConditionalExpr { + final override predicate propagatesAbnormal(AstNode child) { + child = this.getCondition() or child = this.getBranch(_) + } + + final override predicate first(AstNode first) { first(this.getCondition(), first) } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + exists(boolean b | + last(this.getCondition(), pred, c) and + b = c.(BooleanCompletion).getValue() + | + first(this.getBranch(b), succ) + or + not exists(this.getBranch(b)) and + succ = this + ) + or + last(this.getBranch(_), pred, c) and + succ = this and + c instanceof NormalCompletion + } + } + + private class ConditionalLoopTree extends PostOrderTree, ConditionalLoop { + final override predicate propagatesAbnormal(AstNode child) { child = this.getCondition() } + + final override predicate first(AstNode first) { first(this.getCondition(), first) } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + last(this.getCondition(), pred, c) and + this.entersLoopWhenConditionIs(c.(BooleanCompletion).getValue()) and + first(this.getBody(), succ) + or + last(this.getBody(), pred, c) and + first(this.getCondition(), succ) and + c.continuesLoop() + or + last(this.getBody(), pred, c) and + first(this.getBody(), succ) and + c instanceof RedoCompletion + or + succ = this and + ( + last(this.getCondition(), pred, c) and + this.entersLoopWhenConditionIs(c.(BooleanCompletion).getValue().booleanNot()) + or + last(this.getBody(), pred, c) and + not c.continuesLoop() and + not c instanceof BreakCompletion and + not c instanceof RedoCompletion + or + last(this.getBody(), pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion()) + ) + } + } + + private class ConstantAccessTree extends PostOrderTree, ConstantAccess { + ConstantAccessTree() { + not this instanceof ClassDeclaration and + not this instanceof ModuleDeclaration + } + + final override predicate propagatesAbnormal(AstNode child) { child = this.getScopeExpr() } + + final override predicate first(AstNode first) { + first(this.getScopeExpr(), first) + or + not exists(this.getScopeExpr()) and + first = this + } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + last(this.getScopeExpr(), pred, c) and + succ = this and + c instanceof NormalCompletion + } + } + + /** A parameter that may have a default value. */ + abstract class DefaultValueParameterTree extends ControlFlowTree { + abstract Expr getDefaultValueExpr(); + + abstract AstNode getAccessNode(); + + predicate hasDefaultValue() { exists(this.getDefaultValueExpr()) } + + final override predicate propagatesAbnormal(AstNode child) { + child = this.getDefaultValueExpr() or child = this.getAccessNode() + } + + final override predicate first(AstNode first) { first = this.getAccessNode() } + + final override predicate last(AstNode last, Completion c) { + last(this.getDefaultValueExpr(), last, c) and + c instanceof NormalCompletion + or + last = this.getAccessNode() and + ( + not this.hasDefaultValue() and + c instanceof SimpleCompletion + or + this.hasDefaultValue() and + c.(MatchingCompletion).getValue() = true + ) + } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + pred = this.getAccessNode() and + first(this.getDefaultValueExpr(), succ) and + c.(MatchingCompletion).getValue() = false + } + } + + private class DesugaredTree extends ControlFlowTree { + ControlFlowTree desugared; + + DesugaredTree() { desugared = this.getDesugared() } + + final override predicate propagatesAbnormal(AstNode child) { + desugared.propagatesAbnormal(child) + } + + final override predicate first(AstNode first) { desugared.first(first) } + + final override predicate last(AstNode last, Completion c) { desugared.last(last, c) } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { none() } + } + + private class DoBlockTree extends BodyStmtTree, DoBlock { + /** Gets the `i`th child in the body of this block. */ + final override AstNode getBodyChild(int i, boolean rescuable) { + result = this.getParameter(i) and rescuable = false + or + result = BodyStmtTree.super.getBodyChild(i - this.getNumberOfParameters(), rescuable) + } + + override predicate propagatesAbnormal(AstNode child) { none() } + } + + private class EmptyStatementTree extends LeafTree, EmptyStmt { } + + class EndBlockTree extends StmtSequenceTree, EndBlock { + override predicate first(AstNode first) { first = this } + + override predicate succ(AstNode pred, AstNode succ, Completion c) { + // Normal left-to-right evaluation in the body + exists(int i | + last(this.getBodyChild(i, _), pred, c) and + first(this.getBodyChild(i + 1, _), succ) and + c instanceof NormalCompletion + ) + } + } + + private class ForInTree extends LeafTree, ForIn { } + + /** + * Control flow of a for-in loop + * + * For example, this program fragment: + * + * ```rb + * for arg in args do + * puts arg + * end + * puts "done"; + * ``` + * + * has the following control flow graph: + * + * ``` + * args + * | + * in------<----- + * / \ \ + * / \ | + * / \ | + * / \ | + * empty non-empty | + * | \ | + * for \ | + * | arg | + * | | | + * puts "done" puts arg | + * \___/ + * ``` + */ + private class ForTree extends PostOrderTree, ForRange { + final override predicate propagatesAbnormal(AstNode child) { + child = this.getPattern() or child = this.getValue() + } + + final override predicate first(AstNode first) { first(this.getValue(), first) } + + /** + * for pattern in array do body end + * ``` + * array +-> in +--[non empty]--> pattern -> body -> in + * |--[empty]--> for + * ``` + */ + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + last(this.getValue(), pred, c) and + first(this.getIn(), succ) and + c instanceof SimpleCompletion + or + last(this.getIn(), pred, c) and + first(this.getPattern(), succ) and + c.(EmptinessCompletion).getValue() = false + or + last(this.getPattern(), pred, c) and + first(this.getBody(), succ) and + c instanceof NormalCompletion + or + last(this.getBody(), pred, c) and + first(this.getIn(), succ) and + c.continuesLoop() + or + last(this.getBody(), pred, c) and + first(this.getBody(), succ) and + c instanceof RedoCompletion + or + succ = this and + ( + last(this.getIn(), pred, c) and + c.(EmptinessCompletion).getValue() = true + or + last(this.getBody(), pred, c) and + not c.continuesLoop() and + not c instanceof BreakCompletion and + not c instanceof RedoCompletion + or + last(this.getBody(), pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion()) + ) + } + } + + private class GlobalVariableTree extends LeafTree, GlobalVariableAccess { } + + private class HashLiteralTree extends StandardPostOrderTree, HashLiteral { + final override ControlFlowTree getChildElement(int i) { result = this.getElement(i) } + } + + private class HashSplatParameterTree extends NonDefaultValueParameterTree, HashSplatParameter { } + + private class HereDocTree extends StandardPreOrderTree, HereDoc { + final override ControlFlowTree getChildElement(int i) { result = this.getComponent(i) } + } + + private class InstanceVariableTree extends LeafTree, InstanceVariableAccess { } + + private class KeywordParameterTree extends DefaultValueParameterTree, KeywordParameter { + final override Expr getDefaultValueExpr() { result = this.getDefaultValue() } + + final override AstNode getAccessNode() { result = this.getDefiningAccess() } + } + + private class LambdaTree extends BodyStmtTree, Lambda { + final override predicate propagatesAbnormal(AstNode child) { none() } + + /** Gets the `i`th child in the body of this block. */ + final override AstNode getBodyChild(int i, boolean rescuable) { + result = this.getParameter(i) and rescuable = false + or + result = BodyStmtTree.super.getBodyChild(i - this.getNumberOfParameters(), rescuable) + } + } + + private class LocalVariableAccessTree extends LeafTree, LocalVariableAccess { } + + private class LogicalAndTree extends PostOrderTree, LogicalAndExpr { + final override predicate propagatesAbnormal(AstNode child) { child = this.getAnOperand() } + + final override predicate first(AstNode first) { first(this.getLeftOperand(), first) } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + last(this.getLeftOperand(), pred, c) and + c instanceof TrueCompletion and + first(this.getRightOperand(), succ) + or + last(this.getLeftOperand(), pred, c) and + c instanceof FalseCompletion and + succ = this + or + last(this.getRightOperand(), pred, c) and + c instanceof NormalCompletion and + succ = this + } + } + + private class LogicalNotTree extends PostOrderTree, NotExpr { + final override predicate propagatesAbnormal(AstNode child) { child = this.getOperand() } + + final override predicate first(AstNode first) { first(this.getOperand(), first) } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + succ = this and + last(this.getOperand(), pred, c) and + c instanceof NormalCompletion + } + } + + private class LogicalOrTree extends PostOrderTree, LogicalOrExpr { + final override predicate propagatesAbnormal(AstNode child) { child = this.getAnOperand() } + + final override predicate first(AstNode first) { first(this.getLeftOperand(), first) } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + last(this.getLeftOperand(), pred, c) and + c instanceof FalseCompletion and + first(this.getRightOperand(), succ) + or + last(this.getLeftOperand(), pred, c) and + c instanceof TrueCompletion and + succ = this + or + last(this.getRightOperand(), pred, c) and + c instanceof NormalCompletion and + succ = this + } + } + + private class MethodCallTree extends CallTree, MethodCall { + final override ControlFlowTree getChildElement(int i) { + result = this.getReceiver() and i = 0 + or + result = this.getArgument(i - 1) + or + result = this.getBlock() and i = 1 + this.getNumberOfArguments() + } + } + + private class MethodNameTree extends LeafTree, MethodName, ASTInternal::TTokenMethodName { } + + private class MethodTree extends BodyStmtTree, Method { + final override predicate propagatesAbnormal(AstNode child) { none() } + + /** Gets the `i`th child in the body of this block. */ + final override AstNode getBodyChild(int i, boolean rescuable) { + result = this.getParameter(i) and rescuable = false + or + result = BodyStmtTree.super.getBodyChild(i - this.getNumberOfParameters(), rescuable) + } + } + + private class ModuleDeclarationTree extends NamespaceTree, ModuleDeclaration { + /** Gets the `i`th child in the body of this block. */ + final override AstNode getBodyChild(int i, boolean rescuable) { + result = this.getScopeExpr() and i = 0 and rescuable = false + or + result = NamespaceTree.super.getBodyChild(i - count(this.getScopeExpr()), rescuable) + } + } + + private class NamespaceTree extends BodyStmtTree, Namespace { + final override predicate first(AstNode first) { + this.firstInner(first) + or + not exists(this.getAChild(_)) and + first = this + } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + BodyStmtTree.super.succ(pred, succ, c) + or + succ = this and + this.lastInner(pred, c) + } + } + + private class NilTree extends LeafTree, NilLiteral { } + + private class NumericLiteralTree extends LeafTree, NumericLiteral { } + + private class OptionalParameterTree extends DefaultValueParameterTree, OptionalParameter { + final override Expr getDefaultValueExpr() { result = this.getDefaultValue() } + + final override AstNode getAccessNode() { result = this.getDefiningAccess() } + } + + private class PairTree extends StandardPostOrderTree, Pair { + final override ControlFlowTree getChildElement(int i) { + result = this.getKey() and i = 0 + or + result = this.getValue() and i = 1 + } + } + + private class RangeLiteralTree extends StandardPostOrderTree, RangeLiteral { + final override ControlFlowTree getChildElement(int i) { + result = this.getBegin() and i = 0 + or + result = this.getEnd() and i = 1 + } + } + + private class RedoStmtTree extends LeafTree, RedoStmt { } + + private class RescueModifierTree extends PreOrderTree, RescueModifierExpr { + final override predicate propagatesAbnormal(AstNode child) { child = this.getHandler() } + + final override predicate last(AstNode last, Completion c) { + last(this.getBody(), last, c) and + not c instanceof RaiseCompletion + or + last(this.getHandler(), last, c) + } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + pred = this and + first(this.getBody(), succ) and + c instanceof SimpleCompletion + or + last(this.getBody(), pred, c) and + c instanceof RaiseCompletion and + first(this.getHandler(), succ) + } + } + + private class RescueTree extends PreOrderTree, RescueClause { + final override predicate propagatesAbnormal(AstNode child) { child = this.getAnException() } + + private Expr getLastException() { + exists(int i | result = this.getException(i) and not exists(this.getException(i + 1))) + } + + predicate lastMatch(AstNode last, Completion c) { + last(this.getBody(), last, c) + or + not exists(this.getBody()) and + ( + last(this.getVariableExpr(), last, c) + or + not exists(this.getVariableExpr()) and + ( + last(this.getAnException(), last, c) and + c.(MatchingCompletion).getValue() = true + or + not exists(this.getAnException()) and + last = this and + c.isValidFor(this) + ) + ) + } + + predicate lastNoMatch(AstNode last, Completion c) { + last(this.getLastException(), last, c) and + c.(MatchingCompletion).getValue() = false + } + + final override predicate last(AstNode last, Completion c) { + this.lastNoMatch(last, c) + or + this.lastMatch(last, c) + } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + exists(AstNode next | + pred = this and + first(next, succ) and + c instanceof SimpleCompletion + | + next = this.getException(0) + or + not exists(this.getException(0)) and + ( + next = this.getVariableExpr() + or + not exists(this.getVariableExpr()) and + next = this.getBody() + ) + ) + or + exists(AstNode next | + last(this.getAnException(), pred, c) and + first(next, succ) and + c.(MatchingCompletion).getValue() = true + | + next = this.getVariableExpr() + or + not exists(this.getVariableExpr()) and + next = this.getBody() + ) + or + exists(int i | + last(this.getException(i), pred, c) and + c.(MatchingCompletion).getValue() = false and + first(this.getException(i + 1), succ) + ) + or + last(this.getVariableExpr(), pred, c) and + first(this.getBody(), succ) and + c instanceof NormalCompletion + } + } + + private class RetryStmtTree extends LeafTree, RetryStmt { } + + private class ReturningStmtTree extends StandardPostOrderTree, ReturningStmt { + final override ControlFlowTree getChildElement(int i) { result = this.getValue() and i = 0 } + } + + private class SelfTree extends LeafTree, Self { } + + private class SimpleParameterTree extends NonDefaultValueParameterTree, SimpleParameter { } + + // Corner case: For duplicated '_' parameters, only the first occurence has a defining + // access. For subsequent parameters we simply include the parameter itself in the CFG + private class SimpleParameterTreeDupUnderscore extends LeafTree, SimpleParameter { + SimpleParameterTreeDupUnderscore() { not exists(this.getDefiningAccess()) } + } + + private class SingletonClassTree extends BodyStmtTree, SingletonClass { + final override predicate first(AstNode first) { + this.firstInner(first) + or + not exists(this.getAChild(_)) and + first = this + } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + BodyStmtTree.super.succ(pred, succ, c) + or + succ = this and + this.lastInner(pred, c) + } + + /** Gets the `i`th child in the body of this block. */ + final override AstNode getBodyChild(int i, boolean rescuable) { + ( + result = this.getValue() and i = 0 and rescuable = false + or + result = BodyStmtTree.super.getBodyChild(i - 1, rescuable) + ) + } + } + + private class SingletonMethodTree extends BodyStmtTree, SingletonMethod { + final override predicate propagatesAbnormal(AstNode child) { none() } + + /** Gets the `i`th child in the body of this block. */ + final override AstNode getBodyChild(int i, boolean rescuable) { + result = this.getParameter(i) and rescuable = false + or + result = BodyStmtTree.super.getBodyChild(i - this.getNumberOfParameters(), rescuable) + } + + override predicate first(AstNode first) { first(this.getObject(), first) } + + override predicate succ(AstNode pred, AstNode succ, Completion c) { + BodyStmtTree.super.succ(pred, succ, c) + or + last(this.getObject(), pred, c) and + succ = this and + c instanceof NormalCompletion + } + } + + private class SplatParameterTree extends NonDefaultValueParameterTree, SplatParameter { } + + class StmtSequenceTree extends PostOrderTree, StmtSequence { + override predicate propagatesAbnormal(AstNode child) { child = this.getAStmt() } + + override predicate first(AstNode first) { first(this.getStmt(0), first) } + + /** Gets the `i`th child in the body of this body statement. */ + AstNode getBodyChild(int i, boolean rescuable) { + result = this.getStmt(i) and + rescuable = true + } + + final AstNode getLastBodyChild() { + exists(int i | + result = this.getBodyChild(i, _) and + not exists(this.getBodyChild(i + 1, _)) + ) + } + + override predicate succ(AstNode pred, AstNode succ, Completion c) { + // Normal left-to-right evaluation in the body + exists(int i | + last(this.getBodyChild(i, _), pred, c) and + first(this.getBodyChild(i + 1, _), succ) and + c instanceof NormalCompletion + ) + or + succ = this and + last(this.getLastBodyChild(), pred, c) and + c instanceof NormalCompletion + } + } + + private class StringConcatenationTree extends StandardTree, StringConcatenation { + final override ControlFlowTree getChildElement(int i) { result = this.getString(i) } + + final override predicate first(AstNode first) { first(this.getFirstChildElement(), first) } + + final override predicate last(AstNode last, Completion c) { + last(this.getLastChildElement(), last, c) + } + } + + private class StringlikeLiteralTree extends StandardPostOrderTree, StringlikeLiteral { + StringlikeLiteralTree() { not this instanceof HereDoc } + + final override ControlFlowTree getChildElement(int i) { result = this.getComponent(i) } + } + + private class ToplevelTree extends BodyStmtTree, Toplevel { + final override AstNode getBodyChild(int i, boolean rescuable) { + result = this.getBeginBlock(i) and rescuable = true + or + result = BodyStmtTree.super.getBodyChild(i - count(this.getABeginBlock()), rescuable) + } + + final override predicate first(AstNode first) { this.firstInner(first) } + + final override predicate last(AstNode last, Completion c) { this.lastInner(last, c) } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + BodyStmtTree.super.succ(pred, succ, c) + } + } + + private class TuplePatternTree extends StandardPostOrderTree, TuplePattern { + final override ControlFlowTree getChildElement(int i) { result = this.getElement(i) } + } + + private class UndefStmtTree extends StandardPreOrderTree, UndefStmt { + final override ControlFlowTree getChildElement(int i) { result = this.getMethodName(i) } + } + + private class WhenTree extends PreOrderTree, WhenExpr { + final override predicate propagatesAbnormal(AstNode child) { child = this.getAPattern() } + + final Expr getLastPattern() { + exists(int i | + result = this.getPattern(i) and + not exists(this.getPattern(i + 1)) + ) + } + + final override predicate last(AstNode last, Completion c) { + last(this.getLastPattern(), last, c) and + c.(ConditionalCompletion).getValue() = false + or + last(this.getBody(), last, c) + } + + final override predicate succ(AstNode pred, AstNode succ, Completion c) { + pred = this and + first(this.getPattern(0), succ) and + c instanceof SimpleCompletion + or + exists(int i, Expr p, boolean b | + p = this.getPattern(i) and + last(p, pred, c) and + b = c.(ConditionalCompletion).getValue() + | + b = true and + first(this.getBody(), succ) + or + b = false and + first(this.getPattern(i + 1), succ) + ) + } + } +} + +private Scope parent(Scope n) { + result = n.getOuterScope() and + not n instanceof CfgScope::Range_ +} + +/** Gets the CFG scope of node `n`. */ +pragma[inline] +CfgScope getCfgScope(AstNode n) { + exists(AstNode n0 | + pragma[only_bind_into](n0) = n and + pragma[only_bind_into](result) = getCfgScopeImpl(n0) + ) +} + +cached +private module Cached { + /** Gets the CFG scope of node `n`. */ + cached + CfgScope getCfgScopeImpl(AstNode n) { + forceCachingInSameStage() and + result = parent*(ASTInternal::fromGenerated(scopeOf(ASTInternal::toGeneratedInclSynth(n)))) + } + + cached + newtype TSuccessorType = + TSuccessorSuccessor() or + TBooleanSuccessor(boolean b) { b in [false, true] } or + TEmptinessSuccessor(boolean isEmpty) { isEmpty in [false, true] } or + TMatchingSuccessor(boolean isMatch) { isMatch in [false, true] } or + TReturnSuccessor() or + TBreakSuccessor() or + TNextSuccessor() or + TRedoSuccessor() or + TRetrySuccessor() or + TRaiseSuccessor() or // TODO: Add exception type? + TExitSuccessor() +} + +import Cached diff --git a/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplShared.qll b/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplShared.qll new file mode 100644 index 000000000000..050a93847290 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplShared.qll @@ -0,0 +1,945 @@ +/** Provides language-independent definitions for AST-to-CFG construction. */ + +private import ControlFlowGraphImplSpecific + +/** An element with associated control flow. */ +abstract class ControlFlowTree extends ControlFlowTreeBase { + /** Holds if `first` is the first element executed within this element. */ + pragma[nomagic] + abstract predicate first(ControlFlowElement first); + + /** + * Holds if `last` with completion `c` is a potential last element executed + * within this element. + */ + pragma[nomagic] + abstract predicate last(ControlFlowElement last, Completion c); + + /** Holds if abnormal execution of `child` should propagate upwards. */ + abstract predicate propagatesAbnormal(ControlFlowElement child); + + /** + * Holds if `succ` is a control flow successor for `pred`, given that `pred` + * finishes with completion `c`. + */ + pragma[nomagic] + abstract predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c); +} + +/** + * Holds if `first` is the first element executed within control flow + * element `cft`. + */ +predicate first(ControlFlowTree cft, ControlFlowElement first) { cft.first(first) } + +/** + * Holds if `last` with completion `c` is a potential last element executed + * within control flow element `cft`. + */ +predicate last(ControlFlowTree cft, ControlFlowElement last, Completion c) { + cft.last(last, c) + or + exists(ControlFlowElement cfe | + cft.propagatesAbnormal(cfe) and + last(cfe, last, c) and + not completionIsNormal(c) + ) +} + +/** + * Holds if `succ` is a control flow successor for `pred`, given that `pred` + * finishes with completion `c`. + */ +pragma[nomagic] +predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + any(ControlFlowTree cft).succ(pred, succ, c) +} + +/** An element that is executed in pre-order. */ +abstract class PreOrderTree extends ControlFlowTree { + final override predicate first(ControlFlowElement first) { first = this } +} + +/** An element that is executed in post-order. */ +abstract class PostOrderTree extends ControlFlowTree { + override predicate last(ControlFlowElement last, Completion c) { + last = this and + completionIsValidFor(c, last) + } +} + +/** + * An element where the children are evaluated following a standard left-to-right + * evaluation. The actual evaluation order is determined by the predicate + * `getChildElement()`. + */ +abstract class StandardTree extends ControlFlowTree { + /** Gets the `i`th child element, in order of evaluation. */ + abstract ControlFlowElement getChildElement(int i); + + private ControlFlowElement getChildElementRanked(int i) { + result = + rank[i + 1](ControlFlowElement child, int j | + child = this.getChildElement(j) + | + child order by j + ) + } + + /** Gets the first child node of this element. */ + final ControlFlowElement getFirstChildElement() { result = this.getChildElementRanked(0) } + + /** Gets the last child node of this node. */ + final ControlFlowElement getLastChildElement() { + exists(int last | + result = this.getChildElementRanked(last) and + not exists(this.getChildElementRanked(last + 1)) + ) + } + + /** Holds if this element has no children. */ + predicate isLeafElement() { not exists(this.getFirstChildElement()) } + + override predicate propagatesAbnormal(ControlFlowElement child) { + child = this.getChildElement(_) + } + + pragma[nomagic] + override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + exists(int i | + last(this.getChildElementRanked(i), pred, c) and + completionIsNormal(c) and + first(this.getChildElementRanked(i + 1), succ) + ) + } +} + +/** A standard element that is executed in pre-order. */ +abstract class StandardPreOrderTree extends StandardTree, PreOrderTree { + override predicate last(ControlFlowElement last, Completion c) { + last(this.getLastChildElement(), last, c) + or + this.isLeafElement() and + completionIsValidFor(c, this) and + last = this + } + + override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + StandardTree.super.succ(pred, succ, c) + or + pred = this and + first(this.getFirstChildElement(), succ) and + completionIsSimple(c) + } +} + +/** A standard element that is executed in post-order. */ +abstract class StandardPostOrderTree extends StandardTree, PostOrderTree { + override predicate first(ControlFlowElement first) { + first(this.getFirstChildElement(), first) + or + not exists(this.getFirstChildElement()) and + first = this + } + + override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + StandardTree.super.succ(pred, succ, c) + or + last(this.getLastChildElement(), pred, c) and + succ = this and + completionIsNormal(c) + } +} + +/** An element that is a leaf in the control flow graph. */ +abstract class LeafTree extends PreOrderTree, PostOrderTree { + override predicate propagatesAbnormal(ControlFlowElement child) { none() } + + override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { none() } +} + +/** + * Holds if split kinds `sk1` and `sk2` may overlap. That is, they may apply + * to at least one common AST node inside `scope`. + */ +private predicate overlapping(CfgScope scope, SplitKind sk1, SplitKind sk2) { + exists(ControlFlowElement e | + sk1.appliesTo(e) and + sk2.appliesTo(e) and + scope = getCfgScope(e) + ) +} + +/** + * A split kind. Each control flow node can have at most one split of a + * given kind. + */ +abstract class SplitKind extends SplitKindBase { + /** Gets a split of this kind. */ + SplitImpl getASplit() { result.getKind() = this } + + /** Holds if some split of this kind applies to AST node `n`. */ + predicate appliesTo(ControlFlowElement n) { this.getASplit().appliesTo(n) } + + /** + * Gets a unique integer representing this split kind. The integer is used + * to represent sets of splits as ordered lists. + */ + abstract int getListOrder(); + + /** Gets the rank of this split kind among all overlapping kinds for `c`. */ + private int getRank(CfgScope scope) { + this = rank[result](SplitKind sk | overlapping(scope, this, sk) | sk order by sk.getListOrder()) + } + + /** + * Holds if this split kind is enabled for AST node `n`. For performance reasons, + * the number of splits is restricted by the `maxSplits()` predicate. + */ + predicate isEnabled(ControlFlowElement n) { + this.appliesTo(n) and + this.getRank(getCfgScope(n)) <= maxSplits() + } + + /** + * Gets the rank of this split kind among all the split kinds that apply to + * AST node `n`. The rank is based on the order defined by `getListOrder()`. + */ + int getListRank(ControlFlowElement n) { + this.isEnabled(n) and + this = rank[result](SplitKind sk | sk.appliesTo(n) | sk order by sk.getListOrder()) + } + + /** Gets a textual representation of this split kind. */ + abstract string toString(); +} + +/** Provides the interface for implementing an entity to split on. */ +abstract class SplitImpl extends Split { + /** Gets the kind of this split. */ + abstract SplitKind getKind(); + + /** + * Holds if this split is entered when control passes from `pred` to `succ` with + * completion `c`. + * + * Invariant: `hasEntry(pred, succ, c) implies succ(pred, succ, c)`. + */ + abstract predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c); + + /** + * Holds if this split is entered when control passes from `scope` to the entry point + * `first`. + * + * Invariant: `hasEntryScope(scope, first) implies scopeFirst(scope, first)`. + */ + abstract predicate hasEntryScope(CfgScope scope, ControlFlowElement first); + + /** + * Holds if this split is left when control passes from `pred` to `succ` with + * completion `c`. + * + * Invariant: `hasExit(pred, succ, c) implies succ(pred, succ, c)`. + */ + abstract predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c); + + /** + * Holds if this split is left when control passes from `last` out of the enclosing + * scope `scope` with completion `c`. + * + * Invariant: `hasExitScope(scope, last, c) implies scopeLast(scope, last, c)` + */ + abstract predicate hasExitScope(CfgScope scope, ControlFlowElement last, Completion c); + + /** + * Holds if this split is maintained when control passes from `pred` to `succ` with + * completion `c`. + * + * Invariant: `hasSuccessor(pred, succ, c) implies succ(pred, succ, c)` + */ + abstract predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c); + + /** Holds if this split applies to control flow element `cfe`. */ + final predicate appliesTo(ControlFlowElement cfe) { + this.hasEntry(_, cfe, _) + or + this.hasEntryScope(_, cfe) + or + exists(ControlFlowElement pred | this.appliesTo(pred) | this.hasSuccessor(pred, cfe, _)) + } + + /** The `succ` relation restricted to predecessors `pred` that this split applies to. */ + pragma[noinline] + final predicate appliesSucc(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + this.appliesTo(pred) and + succ(pred, succ, c) + } +} + +/** + * A set of control flow node splits. The set is represented by a list of splits, + * ordered by ascending rank. + */ +class Splits extends TSplits { + /** Gets a textual representation of this set of splits. */ + string toString() { result = splitsToString(this) } + + /** Gets a split belonging to this set of splits. */ + SplitImpl getASplit() { + exists(SplitImpl head, Splits tail | this = TSplitsCons(head, tail) | + result = head + or + result = tail.getASplit() + ) + } +} + +private predicate succEntrySplitsFromRank( + CfgScope pred, ControlFlowElement succ, Splits splits, int rnk +) { + splits = TSplitsNil() and + scopeFirst(pred, succ) and + rnk = 0 + or + exists(SplitImpl head, Splits tail | succEntrySplitsCons(pred, succ, head, tail, rnk) | + splits = TSplitsCons(head, tail) + ) +} + +private predicate succEntrySplitsCons( + CfgScope pred, ControlFlowElement succ, SplitImpl head, Splits tail, int rnk +) { + succEntrySplitsFromRank(pred, succ, tail, rnk - 1) and + head.hasEntryScope(pred, succ) and + rnk = head.getKind().getListRank(succ) +} + +/** + * Holds if `succ` with splits `succSplits` is the first element that is executed + * when entering callable `pred`. + */ +pragma[noinline] +private predicate succEntrySplits( + CfgScope pred, ControlFlowElement succ, Splits succSplits, SuccessorType t +) { + exists(int rnk | + scopeFirst(pred, succ) and + successorTypeIsSimple(t) and + succEntrySplitsFromRank(pred, succ, succSplits, rnk) + | + rnk = 0 and + not any(SplitImpl split).hasEntryScope(pred, succ) + or + rnk = max(SplitImpl split | split.hasEntryScope(pred, succ) | split.getKind().getListRank(succ)) + ) +} + +/** + * Holds if `pred` with splits `predSplits` can exit the enclosing callable + * `succ` with type `t`. + */ +private predicate succExitSplits( + ControlFlowElement pred, Splits predSplits, CfgScope succ, SuccessorType t +) { + exists(Reachability::SameSplitsBlock b, Completion c | pred = b.getAnElement() | + b.isReachable(predSplits) and + t = getAMatchingSuccessorType(c) and + scopeLast(succ, pred, c) and + forall(SplitImpl predSplit | predSplit = predSplits.getASplit() | + predSplit.hasExitScope(succ, pred, c) + ) + ) +} + +/** + * Provides a predicate for the successor relation with split information, + * as well as logic used to construct the type `TSplits` representing sets + * of splits. Only sets of splits that can be reached are constructed, hence + * the predicates are mutually recursive. + * + * For the successor relation + * + * ```ql + * succSplits(ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, Completion c) + * ``` + * + * the following invariants are maintained: + * + * 1. `pred` is reachable with split set `predSplits`. + * 2. For all `split` in `predSplits`: + * - If `split.hasSuccessor(pred, succ, c)` then `split` in `succSplits`. + * 3. For all `split` in `predSplits`: + * - If `split.hasExit(pred, succ, c)` and not `split.hasEntry(pred, succ, c)` then + * `split` not in `succSplits`. + * 4. For all `split` with kind not in `predSplits`: + * - If `split.hasEntry(pred, succ, c)` then `split` in `succSplits`. + * 5. For all `split` in `succSplits`: + * - `split.hasSuccessor(pred, succ, c)` and `split` in `predSplits`, or + * - `split.hasEntry(pred, succ, c)`. + * + * The algorithm divides into four cases: + * + * 1. The set of splits for the successor is the same as the set of splits + * for the predecessor: + * a) The successor is in the same `SameSplitsBlock` as the predecessor. + * b) The successor is *not* in the same `SameSplitsBlock` as the predecessor. + * 2. The set of splits for the successor is different from the set of splits + * for the predecessor: + * a) The set of splits for the successor is *maybe* non-empty. + * b) The set of splits for the successor is *always* empty. + * + * Only case 2a may introduce new sets of splits, so only predicates from + * this case are used in the definition of `TSplits`. + * + * The predicates in this module are named after the cases above. + */ +private module SuccSplits { + private predicate succInvariant1( + Reachability::SameSplitsBlock b, ControlFlowElement pred, Splits predSplits, + ControlFlowElement succ, Completion c + ) { + pred = b.getAnElement() and + b.isReachable(predSplits) and + succ(pred, succ, c) + } + + private predicate case1b0( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c + ) { + exists(Reachability::SameSplitsBlock b | + // Invariant 1 + succInvariant1(b, pred, predSplits, succ, c) + | + (succ = b.getAnElement() implies succ = b) and + // Invariant 4 + not exists(SplitImpl split | split.hasEntry(pred, succ, c)) + ) + } + + /** + * Case 1b. + * + * Invariants 1 and 4 hold in the base case, and invariants 2, 3, and 5 are + * maintained for all splits in `predSplits` (= `succSplits`), except + * possibly for the splits in `except`. + * + * The predicate is written using explicit recursion, as opposed to a `forall`, + * to avoid negative recursion. + */ + private predicate case1bForall( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, Splits except + ) { + case1b0(pred, predSplits, succ, c) and + except = predSplits + or + exists(SplitImpl split | + case1bForallCons(pred, predSplits, succ, c, split, except) and + split.hasSuccessor(pred, succ, c) + ) + } + + pragma[noinline] + private predicate case1bForallCons( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, + SplitImpl exceptHead, Splits exceptTail + ) { + case1bForall(pred, predSplits, succ, c, TSplitsCons(exceptHead, exceptTail)) + } + + private predicate case1( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c + ) { + // Case 1a + exists(Reachability::SameSplitsBlock b | succInvariant1(b, pred, predSplits, succ, c) | + succ = b.getAnElement() and + not succ = b + ) + or + // Case 1b + case1bForall(pred, predSplits, succ, c, TSplitsNil()) + } + + pragma[noinline] + private SplitImpl succInvariant1GetASplit( + Reachability::SameSplitsBlock b, ControlFlowElement pred, Splits predSplits, + ControlFlowElement succ, Completion c + ) { + succInvariant1(b, pred, predSplits, succ, c) and + result = predSplits.getASplit() + } + + private predicate case2aux( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c + ) { + exists(Reachability::SameSplitsBlock b | + succInvariant1(b, pred, predSplits, succ, c) and + (succ = b.getAnElement() implies succ = b) + | + succInvariant1GetASplit(b, pred, predSplits, succ, c).hasExit(pred, succ, c) + or + any(SplitImpl split).hasEntry(pred, succ, c) + ) + } + + /** + * Holds if `succSplits` should not inherit a split of kind `sk` from + * `predSplits`, except possibly because of a split in `except`. + * + * The predicate is written using explicit recursion, as opposed to a `forall`, + * to avoid negative recursion. + */ + private predicate case2aNoneInheritedOfKindForall( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, SplitKind sk, + Splits except + ) { + case2aux(pred, predSplits, succ, c) and + sk.appliesTo(succ) and + except = predSplits + or + exists(Splits mid, SplitImpl split | + case2aNoneInheritedOfKindForall(pred, predSplits, succ, c, sk, mid) and + mid = TSplitsCons(split, except) + | + split.getKind() = any(SplitKind sk0 | sk0 != sk and sk0.appliesTo(succ)) + or + split.hasExit(pred, succ, c) + ) + } + + pragma[nomagic] + private predicate entryOfKind( + ControlFlowElement pred, ControlFlowElement succ, Completion c, SplitImpl split, SplitKind sk + ) { + split.hasEntry(pred, succ, c) and + sk = split.getKind() + } + + /** Holds if `succSplits` should not have a split of kind `sk`. */ + pragma[nomagic] + private predicate case2aNoneOfKind( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, SplitKind sk + ) { + // None inherited from predecessor + case2aNoneInheritedOfKindForall(pred, predSplits, succ, c, sk, TSplitsNil()) and + // None newly entered into + not entryOfKind(pred, succ, c, _, sk) + } + + /** Holds if `succSplits` should not have a split of kind `sk` at rank `rnk`. */ + pragma[nomagic] + private predicate case2aNoneAtRank( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, int rnk + ) { + exists(SplitKind sk | case2aNoneOfKind(pred, predSplits, succ, c, sk) | + rnk = sk.getListRank(succ) + ) + } + + pragma[nomagic] + private SplitImpl case2auxGetAPredecessorSplit( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c + ) { + case2aux(pred, predSplits, succ, c) and + result = predSplits.getASplit() + } + + /** Gets a split that should be in `succSplits`. */ + pragma[nomagic] + private SplitImpl case2aSome( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, SplitKind sk + ) { + ( + // Inherited from predecessor + result = case2auxGetAPredecessorSplit(pred, predSplits, succ, c) and + result.hasSuccessor(pred, succ, c) + or + // Newly entered into + exists(SplitKind sk0 | + case2aNoneInheritedOfKindForall(pred, predSplits, succ, c, sk0, TSplitsNil()) + | + entryOfKind(pred, succ, c, result, sk0) + ) + ) and + sk = result.getKind() + } + + /** Gets a split that should be in `succSplits` at rank `rnk`. */ + pragma[nomagic] + SplitImpl case2aSomeAtRank( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, int rnk + ) { + exists(SplitKind sk | result = case2aSome(pred, predSplits, succ, c, sk) | + rnk = sk.getListRank(succ) + ) + } + + /** + * Case 2a. + * + * As opposed to the other cases, in this case we need to construct a new set + * of splits `succSplits`. Since this involves constructing the very IPA type, + * we cannot recurse directly over the structure of `succSplits`. Instead, we + * recurse over the ranks of all splits that *might* be in `succSplits`. + * + * - Invariant 1 holds in the base case, + * - invariant 2 holds for all splits with rank at least `rnk`, + * - invariant 3 holds for all splits in `predSplits`, + * - invariant 4 holds for all splits in `succSplits` with rank at least `rnk`, + * and + * - invariant 4 holds for all splits in `succSplits` with rank at least `rnk`. + */ + predicate case2aFromRank( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, + Completion c, int rnk + ) { + case2aux(pred, predSplits, succ, c) and + succSplits = TSplitsNil() and + rnk = max(any(SplitKind sk).getListRank(succ)) + 1 + or + case2aFromRank(pred, predSplits, succ, succSplits, c, rnk + 1) and + case2aNoneAtRank(pred, predSplits, succ, c, rnk) + or + exists(Splits mid, SplitImpl split | split = case2aCons(pred, predSplits, succ, mid, c, rnk) | + succSplits = TSplitsCons(split, mid) + ) + } + + pragma[noinline] + private SplitImpl case2aCons( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, + Completion c, int rnk + ) { + case2aFromRank(pred, predSplits, succ, succSplits, c, rnk + 1) and + result = case2aSomeAtRank(pred, predSplits, succ, c, rnk) + } + + /** + * Case 2b. + * + * Invariants 1, 4, and 5 hold in the base case, and invariants 2 and 3 are + * maintained for all splits in `predSplits`, except possibly for the splits + * in `except`. + * + * The predicate is written using explicit recursion, as opposed to a `forall`, + * to avoid negative recursion. + */ + private predicate case2bForall( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, Splits except + ) { + // Invariant 1 + case2aux(pred, predSplits, succ, c) and + // Invariants 4 and 5 + not any(SplitKind sk).appliesTo(succ) and + except = predSplits + or + exists(SplitImpl split | case2bForallCons(pred, predSplits, succ, c, split, except) | + // Invariants 2 and 3 + split.hasExit(pred, succ, c) + ) + } + + pragma[noinline] + private predicate case2bForallCons( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, + SplitImpl exceptHead, Splits exceptTail + ) { + case2bForall(pred, predSplits, succ, c, TSplitsCons(exceptHead, exceptTail)) + } + + private predicate case2( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, + Completion c + ) { + case2aFromRank(pred, predSplits, succ, succSplits, c, 1) + or + case2bForall(pred, predSplits, succ, c, TSplitsNil()) and + succSplits = TSplitsNil() + } + + /** + * Holds if `succ` with splits `succSplits` is a successor of type `t` for `pred` + * with splits `predSplits`. + */ + predicate succSplits( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, + Completion c + ) { + case1(pred, predSplits, succ, c) and + succSplits = predSplits + or + case2(pred, predSplits, succ, succSplits, c) + } +} + +import SuccSplits + +/** Provides logic for calculating reachable control flow nodes. */ +private module Reachability { + /** + * Holds if `cfe` is a control flow element where the set of possible splits may + * be different from the set of possible splits for one of `cfe`'s predecessors. + * That is, `cfe` starts a new block of elements with the same set of splits. + */ + private predicate startsSplits(ControlFlowElement cfe) { + scopeFirst(_, cfe) + or + exists(SplitImpl s | + s.hasEntry(_, cfe, _) + or + s.hasExit(_, cfe, _) + ) + or + exists(ControlFlowElement pred, SplitImpl split, Completion c | succ(pred, cfe, c) | + split.appliesTo(pred) and + not split.hasSuccessor(pred, cfe, c) + ) + } + + private predicate intraSplitsSucc(ControlFlowElement pred, ControlFlowElement succ) { + succ(pred, succ, _) and + not startsSplits(succ) + } + + private predicate splitsBlockContains(ControlFlowElement start, ControlFlowElement cfe) = + fastTC(intraSplitsSucc/2)(start, cfe) + + /** + * A block of control flow elements where the set of splits is guaranteed + * to remain unchanged, represented by the first element in the block. + */ + class SameSplitsBlock extends ControlFlowElement { + SameSplitsBlock() { startsSplits(this) } + + /** Gets a control flow element in this block. */ + ControlFlowElement getAnElement() { + splitsBlockContains(this, result) + or + result = this + } + + pragma[noinline] + private SameSplitsBlock getASuccessor(Splits predSplits, Splits succSplits) { + exists(ControlFlowElement pred | pred = this.getAnElement() | + succSplits(pred, predSplits, result, succSplits, _) + ) + } + + /** + * Holds if the elements of this block are reachable from a callable entry + * point, with the splits `splits`. + */ + predicate isReachable(Splits splits) { + // Base case + succEntrySplits(_, this, splits, _) + or + // Recursive case + exists(SameSplitsBlock pred, Splits predSplits | pred.isReachable(predSplits) | + this = pred.getASuccessor(predSplits, splits) + ) + } + } +} + +cached +private module Cached { + /** + * If needed, call this predicate from `ControlFlowGraphImplSpecific.qll` in order to + * force a stage-dependency on the `ControlFlowGraphImplShared.qll` stage and therby + * collapsing the two stages. + */ + cached + predicate forceCachingInSameStage() { any() } + + cached + newtype TSplits = + TSplitsNil() or + TSplitsCons(SplitImpl head, Splits tail) { + exists( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, int rnk + | + case2aFromRank(pred, predSplits, succ, tail, c, rnk + 1) and + head = case2aSomeAtRank(pred, predSplits, succ, c, rnk) + ) + or + succEntrySplitsCons(_, _, head, tail, _) + } + + cached + string splitsToString(Splits splits) { + splits = TSplitsNil() and + result = "" + or + exists(SplitImpl head, Splits tail, string headString, string tailString | + splits = TSplitsCons(head, tail) + | + headString = head.toString() and + tailString = tail.toString() and + if tailString = "" + then result = headString + else + if headString = "" + then result = tailString + else result = headString + ", " + tailString + ) + } + + /** + * Internal representation of control flow nodes in the control flow graph. + * The control flow graph is pruned for unreachable nodes. + */ + cached + newtype TNode = + TEntryNode(CfgScope scope) { succEntrySplits(scope, _, _, _) } or + TAnnotatedExitNode(CfgScope scope, boolean normal) { + exists(Reachability::SameSplitsBlock b, SuccessorType t | b.isReachable(_) | + succExitSplits(b.getAnElement(), _, scope, t) and + if isAbnormalExitType(t) then normal = false else normal = true + ) + } or + TExitNode(CfgScope scope) { + exists(Reachability::SameSplitsBlock b | b.isReachable(_) | + succExitSplits(b.getAnElement(), _, scope, _) + ) + } or + TElementNode(ControlFlowElement cfe, Splits splits) { + exists(Reachability::SameSplitsBlock b | b.isReachable(splits) | cfe = b.getAnElement()) + } + + /** Gets a successor node of a given flow type, if any. */ + cached + TNode getASuccessor(TNode pred, SuccessorType t) { + // Callable entry node -> callable body + exists(ControlFlowElement succElement, Splits succSplits, CfgScope scope | + result = TElementNode(succElement, succSplits) and + pred = TEntryNode(scope) and + succEntrySplits(scope, succElement, succSplits, t) + ) + or + exists(ControlFlowElement predElement, Splits predSplits | + pred = TElementNode(predElement, predSplits) + | + // Element node -> callable exit (annotated) + exists(CfgScope scope, boolean normal | + result = TAnnotatedExitNode(scope, normal) and + succExitSplits(predElement, predSplits, scope, t) and + if isAbnormalExitType(t) then normal = false else normal = true + ) + or + // Element node -> element node + exists(ControlFlowElement succElement, Splits succSplits, Completion c | + result = TElementNode(succElement, succSplits) + | + succSplits(predElement, predSplits, succElement, succSplits, c) and + t = getAMatchingSuccessorType(c) + ) + ) + or + // Callable exit (annotated) -> callable exit + exists(CfgScope scope | + pred = TAnnotatedExitNode(scope, _) and + result = TExitNode(scope) and + successorTypeIsSimple(t) + ) + } + + /** + * Gets a first control flow element executed within `cfe`. + */ + cached + ControlFlowElement getAControlFlowEntryNode(ControlFlowElement cfe) { first(cfe, result) } + + /** + * Gets a potential last control flow element executed within `cfe`. + */ + cached + ControlFlowElement getAControlFlowExitNode(ControlFlowElement cfe) { last(cfe, result, _) } +} + +import Cached + +/** + * Import this module into a `.ql` file of `@kind graph` to render a CFG. The + * graph is restricted to nodes from `RelevantNode`. + */ +module TestOutput { + abstract class RelevantNode extends Node { } + + query predicate nodes(RelevantNode n, string attr, string val) { + attr = "semmle.order" and + val = + any(int i | + n = + rank[i](RelevantNode p, Location l | + l = p.getLocation() + | + p + order by + l.getFile().getBaseName(), l.getFile().getAbsolutePath(), l.getStartLine(), + l.getStartColumn() + ) + ).toString() + } + + query predicate edges(RelevantNode pred, RelevantNode succ, string attr, string val) { + exists(SuccessorType t | succ = getASuccessor(pred, t) | + attr = "semmle.label" and + if successorTypeIsSimple(t) then val = "" else val = t.toString() + ) + } +} + +/** Provides a set of splitting-related consistency queries. */ +module Consistency { + query predicate nonUniqueSetRepresentation(Splits s1, Splits s2) { + forex(Split s | s = s1.getASplit() | s = s2.getASplit()) and + forex(Split s | s = s2.getASplit() | s = s1.getASplit()) and + s1 != s2 + } + + query predicate breakInvariant2( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, + SplitImpl split, Completion c + ) { + succSplits(pred, predSplits, succ, succSplits, c) and + split = predSplits.getASplit() and + split.hasSuccessor(pred, succ, c) and + not split = succSplits.getASplit() + } + + query predicate breakInvariant3( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, + SplitImpl split, Completion c + ) { + succSplits(pred, predSplits, succ, succSplits, c) and + split = predSplits.getASplit() and + split.hasExit(pred, succ, c) and + not split.hasEntry(pred, succ, c) and + split = succSplits.getASplit() + } + + query predicate breakInvariant4( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, + SplitImpl split, Completion c + ) { + succSplits(pred, predSplits, succ, succSplits, c) and + split.hasEntry(pred, succ, c) and + not split.getKind() = predSplits.getASplit().getKind() and + not split = succSplits.getASplit() + } + + query predicate breakInvariant5( + ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, + SplitImpl split, Completion c + ) { + succSplits(pred, predSplits, succ, succSplits, c) and + split = succSplits.getASplit() and + not (split.hasSuccessor(pred, succ, c) and split = predSplits.getASplit()) and + not split.hasEntry(pred, succ, c) + } + + query predicate multipleSuccessors(Node node, SuccessorType t, Node successor) { + not node instanceof TEntryNode and + strictcount(getASuccessor(node, t)) > 1 and + successor = getASuccessor(node, t) + } +} diff --git a/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplSpecific.qll b/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplSpecific.qll new file mode 100644 index 000000000000..2d018ff616a8 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplSpecific.qll @@ -0,0 +1,74 @@ +private import ruby as rb +private import ControlFlowGraphImpl as Impl +private import Completion as Comp +private import codeql.ruby.ast.internal.Synthesis +private import Splitting as Splitting +private import codeql.ruby.CFG as CFG + +/** The base class for `ControlFlowTree`. */ +class ControlFlowTreeBase extends rb::AstNode { + ControlFlowTreeBase() { not any(Synthesis s).excludeFromControlFlowTree(this) } +} + +class ControlFlowElement = rb::AstNode; + +class Completion = Comp::Completion; + +/** + * Hold if `c` represents normal evaluation of a statement or an + * expression. + */ +predicate completionIsNormal(Completion c) { c instanceof Comp::NormalCompletion } + +/** + * Hold if `c` represents simple (normal) evaluation of a statement or an + * expression. + */ +predicate completionIsSimple(Completion c) { c instanceof Comp::SimpleCompletion } + +/** Holds if `c` is a valid completion for `e`. */ +predicate completionIsValidFor(Completion c, ControlFlowElement e) { c.isValidFor(e) } + +class CfgScope = CFG::CfgScope; + +predicate getCfgScope = Impl::getCfgScope/1; + +/** Holds if `first` is first executed when entering `scope`. */ +predicate scopeFirst(CfgScope scope, ControlFlowElement first) { + scope.(Impl::CfgScope::Range_).entry(first) +} + +/** Holds if `scope` is exited when `last` finishes with completion `c`. */ +predicate scopeLast(CfgScope scope, ControlFlowElement last, Completion c) { + scope.(Impl::CfgScope::Range_).exit(last, c) +} + +/** The maximum number of splits allowed for a given node. */ +int maxSplits() { result = 5 } + +class SplitKindBase = Splitting::TSplitKind; + +class Split = Splitting::Split; + +class SuccessorType = CFG::SuccessorType; + +/** Gets a successor type that matches completion `c`. */ +SuccessorType getAMatchingSuccessorType(Completion c) { result = c.getAMatchingSuccessorType() } + +/** + * Hold if `c` represents simple (normal) evaluation of a statement or an + * expression. + */ +predicate successorTypeIsSimple(SuccessorType t) { + t instanceof CFG::SuccessorTypes::NormalSuccessor +} + +/** Holds if `t` is an abnormal exit type out of a CFG scope. */ +predicate isAbnormalExitType(SuccessorType t) { + t instanceof CFG::SuccessorTypes::RaiseSuccessor or + t instanceof CFG::SuccessorTypes::ExitSuccessor +} + +class Location = rb::Location; + +class Node = CFG::CfgNode; diff --git a/ruby/ql/lib/codeql/ruby/controlflow/internal/NonReturning.qll b/ruby/ql/lib/codeql/ruby/controlflow/internal/NonReturning.qll new file mode 100644 index 000000000000..e1927a0b1c97 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/controlflow/internal/NonReturning.qll @@ -0,0 +1,22 @@ +/** Provides a simple analysis for identifying calls that will not return. */ + +private import codeql.ruby.AST +private import Completion + +/** A call that definitely does not return (conservative analysis). */ +abstract class NonReturningCall extends MethodCall { + /** Gets a valid completion for this non-returning call. */ + abstract Completion getACompletion(); +} + +private class RaiseCall extends NonReturningCall { + RaiseCall() { this.getMethodName() = "raise" } + + override RaiseCompletion getACompletion() { not result instanceof NestedCompletion } +} + +private class ExitCall extends NonReturningCall { + ExitCall() { this.getMethodName() in ["abort", "exit"] } + + override ExitCompletion getACompletion() { not result instanceof NestedCompletion } +} diff --git a/ruby/ql/lib/codeql/ruby/controlflow/internal/Splitting.qll b/ruby/ql/lib/codeql/ruby/controlflow/internal/Splitting.qll new file mode 100644 index 000000000000..dd360fe83710 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/controlflow/internal/Splitting.qll @@ -0,0 +1,336 @@ +/** + * Provides classes and predicates relevant for splitting the control flow graph. + */ + +private import codeql.ruby.AST +private import Completion +private import ControlFlowGraphImpl +private import SuccessorTypes +private import codeql.ruby.controlflow.ControlFlowGraph + +cached +private module Cached { + cached + newtype TSplitKind = + TConditionalCompletionSplitKind() { forceCachingInSameStage() } or + TEnsureSplitKind(int nestLevel) { nestLevel = any(Trees::BodyStmtTree t).getNestLevel() } + + cached + newtype TSplit = + TConditionalCompletionSplit(ConditionalCompletion c) or + TEnsureSplit(EnsureSplitting::EnsureSplitType type, int nestLevel) { + nestLevel = any(Trees::BodyStmtTree t).getNestLevel() + } +} + +import Cached + +/** A split for a control flow node. */ +class Split extends TSplit { + /** Gets a textual representation of this split. */ + string toString() { none() } +} + +private module ConditionalCompletionSplitting { + /** + * A split for conditional completions. For example, in + * + * ```rb + * def method x + * if x < 2 and x > 0 + * puts "x is 1" + * end + * end + * ``` + * + * we record whether `x < 2` and `x > 0` evaluate to `true` or `false`, and + * restrict the edges out of `x < 2 and x > 0` accordingly. + */ + class ConditionalCompletionSplit extends Split, TConditionalCompletionSplit { + ConditionalCompletion completion; + + ConditionalCompletionSplit() { this = TConditionalCompletionSplit(completion) } + + override string toString() { result = completion.toString() } + } + + private class ConditionalCompletionSplitKind extends SplitKind, TConditionalCompletionSplitKind { + override int getListOrder() { result = 0 } + + override predicate isEnabled(AstNode n) { this.appliesTo(n) } + + override string toString() { result = "ConditionalCompletion" } + } + + int getNextListOrder() { result = 1 } + + private class ConditionalCompletionSplitImpl extends SplitImpl, ConditionalCompletionSplit { + override ConditionalCompletionSplitKind getKind() { any() } + + override predicate hasEntry(AstNode pred, AstNode succ, Completion c) { + succ(pred, succ, c) and + last(succ, _, completion) and + ( + last(succ.(NotExpr).getOperand(), pred, c) and + completion.(BooleanCompletion).getDual() = c + or + last(succ.(LogicalAndExpr).getAnOperand(), pred, c) and + completion = c + or + last(succ.(LogicalOrExpr).getAnOperand(), pred, c) and + completion = c + or + last(succ.(StmtSequence).getLastStmt(), pred, c) and + completion = c + or + last(succ.(ConditionalExpr).getBranch(_), pred, c) and + completion = c + ) + } + + override predicate hasEntryScope(CfgScope scope, AstNode succ) { none() } + + override predicate hasExit(AstNode pred, AstNode succ, Completion c) { + this.appliesTo(pred) and + succ(pred, succ, c) and + if c instanceof ConditionalCompletion then completion = c else any() + } + + override predicate hasExitScope(CfgScope scope, AstNode last, Completion c) { + this.appliesTo(last) and + succExit(scope, last, c) and + if c instanceof ConditionalCompletion then completion = c else any() + } + + override predicate hasSuccessor(AstNode pred, AstNode succ, Completion c) { none() } + } +} + +module EnsureSplitting { + /** + * The type of a split `ensure` node. + * + * The type represents one of the possible ways of entering an `ensure` + * block. For example, if a block ends with a `return` statement, then + * the `ensure` block must end with a `return` as well (provided that + * the `ensure` block executes normally). + */ + class EnsureSplitType extends SuccessorType { + EnsureSplitType() { not this instanceof ConditionalSuccessor } + + /** Holds if this split type matches entry into an `ensure` block with completion `c`. */ + predicate isSplitForEntryCompletion(Completion c) { + if c instanceof NormalCompletion + then + // If the entry into the `ensure` block completes with any normal completion, + // it simply means normal execution after the `ensure` block + this instanceof NormalSuccessor + else this = c.getAMatchingSuccessorType() + } + } + + /** A node that belongs to an `ensure` block. */ + private class EnsureNode extends AstNode { + private Trees::BodyStmtTree block; + + EnsureNode() { this = block.getAnEnsureDescendant() } + + int getNestLevel() { result = block.getNestLevel() } + + /** Holds if this node is the entry node in the `ensure` block it belongs to. */ + predicate isEntryNode() { first(block.getEnsure(), this) } + } + + /** + * A split for nodes belonging to an `ensure` block, which determines how to + * continue execution after leaving the `ensure` block. For example, in + * + * ```rb + * begin + * if x + * raise "Exception" + * end + * ensure + * puts "Ensure" + * end + * ``` + * + * all control flow nodes in the `ensure` block have two splits: one representing + * normal execution of the body (when `x` evaluates to `true`), and one representing + * exceptional execution of the body (when `x` evaluates to `false`). + */ + class EnsureSplit extends Split, TEnsureSplit { + private EnsureSplitType type; + private int nestLevel; + + EnsureSplit() { this = TEnsureSplit(type, nestLevel) } + + /** + * Gets the type of this `ensure` split, that is, how to continue execution after the + * `ensure` block. + */ + EnsureSplitType getType() { result = type } + + /** Gets the nesting level. */ + int getNestLevel() { result = nestLevel } + + override string toString() { + if type instanceof NormalSuccessor + then result = "" + else + if nestLevel > 0 + then result = "ensure(" + nestLevel + "): " + type.toString() + else result = "ensure: " + type.toString() + } + } + + private int getListOrder(EnsureSplitKind kind) { + result = ConditionalCompletionSplitting::getNextListOrder() + kind.getNestLevel() + } + + int getNextListOrder() { + result = max([getListOrder(_) + 1, ConditionalCompletionSplitting::getNextListOrder()]) + } + + private class EnsureSplitKind extends SplitKind, TEnsureSplitKind { + private int nestLevel; + + EnsureSplitKind() { this = TEnsureSplitKind(nestLevel) } + + /** Gets the nesting level. */ + int getNestLevel() { result = nestLevel } + + override int getListOrder() { result = getListOrder(this) } + + override string toString() { result = "ensure (" + nestLevel + ")" } + } + + pragma[noinline] + private predicate hasEntry0(AstNode pred, EnsureNode succ, int nestLevel, Completion c) { + succ.isEntryNode() and + nestLevel = succ.getNestLevel() and + succ(pred, succ, c) + } + + private class EnsureSplitImpl extends SplitImpl, EnsureSplit { + override EnsureSplitKind getKind() { result.getNestLevel() = this.getNestLevel() } + + override predicate hasEntry(AstNode pred, AstNode succ, Completion c) { + hasEntry0(pred, succ, this.getNestLevel(), c) and + this.getType().isSplitForEntryCompletion(c) + } + + override predicate hasEntryScope(CfgScope scope, AstNode first) { none() } + + /** + * Holds if this split applies to `pred`, where `pred` is a valid predecessor. + */ + private predicate appliesToPredecessor(AstNode pred) { + this.appliesTo(pred) and + (succ(pred, _, _) or succExit(_, pred, _)) + } + + pragma[noinline] + private predicate exit0(AstNode pred, Trees::BodyStmtTree block, int nestLevel, Completion c) { + this.appliesToPredecessor(pred) and + nestLevel = block.getNestLevel() and + block.lastInner(pred, c) + } + + /** + * Holds if `pred` may exit this split with completion `c`. The Boolean + * `inherited` indicates whether `c` is an inherited completion from the + * body. + */ + private predicate exit(Trees::BodyStmtTree block, AstNode pred, Completion c, boolean inherited) { + exists(EnsureSplitType type | + exit0(pred, block, this.getNestLevel(), c) and + type = this.getType() + | + if last(block.getEnsure(), pred, c) + then + // `ensure` block can itself exit with completion `c`: either `c` must + // match this split, `c` must be an abnormal completion, or this split + // does not require another completion to be recovered + inherited = false and + ( + type = c.getAMatchingSuccessorType() + or + not c instanceof NormalCompletion + or + type instanceof NormalSuccessor + ) + else ( + // `ensure` block can exit with inherited completion `c`, which must + // match this split + inherited = true and + type = c.getAMatchingSuccessorType() and + not type instanceof NormalSuccessor + ) + ) + or + // If this split is normal, and an outer split can exit based on an inherited + // completion, we need to exit this split as well. For example, in + // + // ```rb + // def m(b1, b2) + // if b1 + // return + // end + // ensure + // begin + // if b2 + // raise "Exception" + // end + // ensure + // puts "inner ensure" + // end + // end + // ``` + // + // if the outer split for `puts "inner ensure"` is `return` and the inner split + // is "normal" (corresponding to `b1 = true` and `b2 = false`), then the inner + // split must be able to exit with a `return` completion. + this.appliesToPredecessor(pred) and + exists(EnsureSplitImpl outer | + outer.getNestLevel() = this.getNestLevel() - 1 and + outer.exit(_, pred, c, inherited) and + this.getType() instanceof NormalSuccessor and + inherited = true + ) + } + + override predicate hasExit(AstNode pred, AstNode succ, Completion c) { + succ(pred, succ, c) and + ( + exit(_, pred, c, _) + or + exit(_, pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _) + ) + } + + override predicate hasExitScope(CfgScope scope, AstNode last, Completion c) { + succExit(scope, last, c) and + ( + exit(_, last, c, _) + or + exit(_, last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _) + ) + } + + override predicate hasSuccessor(AstNode pred, AstNode succ, Completion c) { + this.appliesToPredecessor(pred) and + succ(pred, succ, c) and + succ = + any(EnsureNode en | + if en.isEntryNode() + then + // entering a nested `ensure` block + en.getNestLevel() > this.getNestLevel() + else + // staying in the same (possibly nested) `ensure` block as `pred` + en.getNestLevel() >= this.getNestLevel() + ) + } + } +} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/BarrierGuards.qll b/ruby/ql/lib/codeql/ruby/dataflow/BarrierGuards.qll new file mode 100644 index 000000000000..0c0ca749eacd --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/BarrierGuards.qll @@ -0,0 +1,75 @@ +/** Provides commonly used barriers to dataflow. */ + +private import ruby +private import codeql.ruby.DataFlow +private import codeql.ruby.CFG + +/** + * A validation of value by comparing with a constant string value, for example + * in: + * + * ```rb + * dir = params[:order] + * dir = "DESC" unless dir == "ASC" + * User.order("name #{dir}") + * ``` + * + * the equality operation guards against `dir` taking arbitrary values when used + * in the `order` call. + */ +class StringConstCompare extends DataFlow::BarrierGuard, + CfgNodes::ExprNodes::ComparisonOperationCfgNode { + private CfgNode checkedNode; + // The value of the condition that results in the node being validated. + private boolean checkedBranch; + + StringConstCompare() { + exists(CfgNodes::ExprNodes::StringLiteralCfgNode strLitNode | + this.getExpr() instanceof EqExpr and checkedBranch = true + or + this.getExpr() instanceof CaseEqExpr and checkedBranch = true + or + this.getExpr() instanceof NEExpr and checkedBranch = false + | + this.getLeftOperand() = strLitNode and this.getRightOperand() = checkedNode + or + this.getLeftOperand() = checkedNode and this.getRightOperand() = strLitNode + ) + } + + override predicate checks(CfgNode expr, boolean branch) { + expr = checkedNode and branch = checkedBranch + } +} + +/** + * A validation of a value by checking for inclusion in an array of string + * literal values, for example in: + * + * ```rb + * name = params[:user_name] + * if %w(alice bob charlie).include? name + * User.find_by("username = #{name}") + * end + * ``` + * + * the `include?` call guards against `name` taking arbitrary values when used + * in the `find_by` call. + */ +// +class StringConstArrayInclusionCall extends DataFlow::BarrierGuard, + CfgNodes::ExprNodes::MethodCallCfgNode { + private CfgNode checkedNode; + + StringConstArrayInclusionCall() { + exists(ArrayLiteral aLit | + this.getExpr().getMethodName() = "include?" and + this.getExpr().getReceiver() = aLit + | + forall(Expr elem | elem = aLit.getAnElement() | elem instanceof StringLiteral) and + this.getArgument(0) = checkedNode + ) + } + + override predicate checks(CfgNode expr, boolean branch) { expr = checkedNode and branch = true } +} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll b/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll new file mode 100644 index 000000000000..ddd44329317a --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll @@ -0,0 +1,125 @@ +/** Provides classes and predicates for defining flow summaries. */ + +import ruby +import codeql.ruby.DataFlow +private import internal.FlowSummaryImpl as Impl +private import internal.DataFlowDispatch + +// import all instances below +private module Summaries { } + +class SummaryComponent = Impl::Public::SummaryComponent; + +/** Provides predicates for constructing summary components. */ +module SummaryComponent { + private import Impl::Public::SummaryComponent as SC + + predicate parameter = SC::parameter/1; + + predicate argument = SC::argument/1; + + predicate content = SC::content/1; + + /** Gets a summary component that represents a qualifier. */ + SummaryComponent qualifier() { result = argument(-1) } + + /** Gets a summary component that represents a block argument. */ + SummaryComponent block() { result = argument(-2) } + + /** Gets a summary component that represents the return value of a call. */ + SummaryComponent return() { result = SC::return(any(NormalReturnKind rk)) } +} + +class SummaryComponentStack = Impl::Public::SummaryComponentStack; + +/** Provides predicates for constructing stacks of summary components. */ +module SummaryComponentStack { + private import Impl::Public::SummaryComponentStack as SCS + + predicate singleton = SCS::singleton/1; + + predicate push = SCS::push/2; + + predicate argument = SCS::argument/1; + + /** Gets a singleton stack representing a qualifier. */ + SummaryComponentStack qualifier() { result = singleton(SummaryComponent::qualifier()) } + + /** Gets a singleton stack representing a block argument. */ + SummaryComponentStack block() { result = singleton(SummaryComponent::block()) } + + /** Gets a singleton stack representing the return value of a call. */ + SummaryComponentStack return() { result = singleton(SummaryComponent::return()) } +} + +/** A callable with a flow summary, identified by a unique string. */ +abstract class SummarizedCallable extends LibraryCallable { + bindingset[this] + SummarizedCallable() { any() } + + /** + * Holds if data may flow from `input` to `output` through this callable. + * + * `preservesValue` indicates whether this is a value-preserving step + * or a taint-step. + * + * Input specifications are restricted to stacks that end with + * `SummaryComponent::argument(_)`, preceded by zero or more + * `SummaryComponent::return()` or `SummaryComponent::content(_)` components. + * + * Output specifications are restricted to stacks that end with + * `SummaryComponent::return()` or `SummaryComponent::argument(_)`. + * + * Output stacks ending with `SummaryComponent::return()` can be preceded by zero + * or more `SummaryComponent::content(_)` components. + * + * Output stacks ending with `SummaryComponent::argument(_)` can be preceded by an + * optional `SummaryComponent::parameter(_)` component, which in turn can be preceded + * by zero or more `SummaryComponent::content(_)` components. + */ + pragma[nomagic] + predicate propagatesFlow( + SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + ) { + none() + } + + /** + * Same as + * + * ```ql + * propagatesFlow( + * SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + * ) + * ``` + * + * but uses an external (string) representation of the input and output stacks. + */ + pragma[nomagic] + predicate propagatesFlowExt(string input, string output, boolean preservesValue) { none() } + + /** + * Holds if values stored inside `content` are cleared on objects passed as + * the `i`th argument to this callable. + */ + pragma[nomagic] + predicate clearsContent(int i, DataFlow::Content content) { none() } +} + +private class SummarizedCallableAdapter extends Impl::Public::SummarizedCallable { + private SummarizedCallable sc; + + SummarizedCallableAdapter() { this = TLibraryCallable(sc) } + + final override predicate propagatesFlow( + SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + ) { + sc.propagatesFlow(input, output, preservesValue) + } + + final override predicate clearsContent(int i, DataFlow::Content content) { + sc.clearsContent(i, content) + } +} + +class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack; diff --git a/ruby/ql/lib/codeql/ruby/dataflow/RemoteFlowSources.qll b/ruby/ql/lib/codeql/ruby/dataflow/RemoteFlowSources.qll new file mode 100644 index 000000000000..617bfd8678ed --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/RemoteFlowSources.qll @@ -0,0 +1,37 @@ +/** + * Provides an extension point for for modeling user-controlled data. + * Such data is often used as data-flow sources in security queries. + */ + +private import codeql.ruby.dataflow.internal.DataFlowPublic as DataFlow +// Need to import since frameworks can extend `RemoteFlowSource::Range` +private import codeql.ruby.Frameworks + +/** + * A data flow source of remote user input. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `RemoteFlowSource::Range` instead. + */ +class RemoteFlowSource extends DataFlow::Node { + RemoteFlowSource::Range self; + + RemoteFlowSource() { this = self } + + /** Gets a string that describes the type of this remote flow source. */ + string getSourceType() { result = self.getSourceType() } +} + +/** Provides a class for modeling new sources of remote user input. */ +module RemoteFlowSource { + /** + * A data flow source of remote user input. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `RemoteFlowSource` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets a string that describes the type of this remote flow source. */ + abstract string getSourceType(); + } +} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll b/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll new file mode 100644 index 000000000000..dedfcd4e3dee --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll @@ -0,0 +1,385 @@ +/** + * Provides the module `Ssa` for working with static single assignment (SSA) form. + */ + +/** + * Provides classes for working with static single assignment (SSA) form. + */ +module Ssa { + private import codeql.Locations + private import codeql.ruby.CFG + private import codeql.ruby.ast.Variable + private import internal.SsaImplCommon as SsaImplCommon + private import internal.SsaImpl as SsaImpl + private import CfgNodes::ExprNodes + + /** A static single assignment (SSA) definition. */ + class Definition extends SsaImplCommon::Definition { + /** + * Gets the control flow node of this SSA definition, if any. Phi nodes are + * examples of SSA definitions without a control flow node, as they are + * modelled at index `-1` in the relevant basic block. + */ + final CfgNode getControlFlowNode() { + exists(BasicBlock bb, int i | this.definesAt(_, bb, i) | result = bb.getNode(i)) + } + + /** + * Gets a control-flow node that reads the value of this SSA definition. + * + * Example: + * + * ```rb + * def m b # defines b_0 + * i = 0 # defines i_0 + * puts i # reads i_0 + * puts i + 1 # reads i_0 + * if b # reads b_0 + * i = 1 # defines i_1 + * puts i # reads i_1 + * puts i + 1 # reads i_1 + * else + * i = 2 # defines i_2 + * puts i # reads i_2 + * puts i + 1 # reads i_2 + * end + * # defines i_3 = phi(i_1, i_2) + * puts i # reads i3 + * end + * ``` + */ + final VariableReadAccessCfgNode getARead() { result = SsaImpl::getARead(this) } + + /** + * Gets a first control-flow node that reads the value of this SSA definition. + * That is, a read that can be reached from this definition without passing + * through other reads. + * + * Example: + * + * ```rb + * def m b # defines b_0 + * i = 0 # defines i_0 + * puts i # first read of i_0 + * puts i + 1 + * if b # first read of b_0 + * i = 1 # defines i_1 + * puts i # first read of i_1 + * puts i + 1 + * else + * i = 2 # defines i_2 + * puts i # first read of i_2 + * puts i + 1 + * end + * # defines i_3 = phi(i_1, i_2) + * puts i # first read of i3 + * end + * ``` + */ + final VariableReadAccessCfgNode getAFirstRead() { SsaImpl::firstRead(this, result) } + + /** + * Gets a last control-flow node that reads the value of this SSA definition. + * That is, a read that can reach the end of the enclosing CFG scope, or another + * SSA definition for the source variable, without passing through any other read. + * + * Example: + * + * ```rb + * def m b # defines b_0 + * i = 0 # defines i_0 + * puts i + * puts i + 1 # last read of i_0 + * if b # last read of b_0 + * i = 1 # defines i_1 + * puts i + * puts i + 1 # last read of i_1 + * else + * i = 2 # defines i_2 + * puts i + * puts i + 1 # last read of i_2 + * end + * # defines i_3 = phi(i_1, i_2) + * puts i # last read of i3 + * end + * ``` + */ + final VariableReadAccessCfgNode getALastRead() { SsaImpl::lastRead(this, result) } + + /** + * Holds if `read1` and `read2` are adjacent reads of this SSA definition. + * That is, `read2` can be reached from `read1` without passing through + * another read. + * + * Example: + * + * ```rb + * def m b + * i = 0 # defines i_0 + * puts i # reads i_0 (read1) + * puts i + 1 # reads i_0 (read2) + * if b + * i = 1 # defines i_1 + * puts i # reads i_1 (read1) + * puts i + 1 # reads i_1 (read2) + * else + * i = 2 # defines i_2 + * puts i # reads i_2 (read1) + * puts i + 1 # reads i_2 (read2) + * end + * puts i + * end + * ``` + */ + final predicate hasAdjacentReads( + VariableReadAccessCfgNode read1, VariableReadAccessCfgNode read2 + ) { + SsaImpl::adjacentReadPair(this, read1, read2) + } + + /** + * Gets an SSA definition whose value can flow to this one in one step. This + * includes inputs to phi nodes and the prior definitions of uncertain writes. + */ + private Definition getAPhiInputOrPriorDefinition() { + result = this.(PhiNode).getAnInput() or + result = this.(CapturedCallDefinition).getPriorDefinition() + } + + /** + * Gets a definition that ultimately defines this SSA definition and is + * not itself a phi node. + * + * Example: + * + * ```rb + * def m b + * i = 0 # defines i_0 + * puts i + * puts i + 1 + * if b + * i = 1 # defines i_1 + * puts i + * puts i + 1 + * else + * i = 2 # defines i_2 + * puts i + * puts i + 1 + * end + * # defines i_3 = phi(i_1, i_2); ultimate definitions are i_1 and i_2 + * puts i + * end + * ``` + */ + final Definition getAnUltimateDefinition() { + result = this.getAPhiInputOrPriorDefinition*() and + not result instanceof PhiNode + } + + override string toString() { result = this.getControlFlowNode().toString() } + + /** Gets the location of this SSA definition. */ + Location getLocation() { result = this.getControlFlowNode().getLocation() } + } + + /** + * An SSA definition that corresponds to a write. For example `x = 10` in + * + * ```rb + * x = 10 + * puts x + * ``` + */ + class WriteDefinition extends Definition, SsaImplCommon::WriteDefinition { + private VariableWriteAccess write; + + WriteDefinition() { + exists(BasicBlock bb, int i, Variable v | + this.definesAt(v, bb, i) and + SsaImpl::variableWriteActual(bb, i, v, write) + ) + } + + /** Gets the underlying write access. */ + final VariableWriteAccess getWriteAccess() { result = write } + + /** + * Holds if this SSA definition represents a direct assignment of `value` + * to the underlying variable. + */ + predicate assigns(CfgNodes::ExprCfgNode value) { + exists(CfgNodes::ExprNodes::AssignExprCfgNode a, BasicBlock bb, int i | + this.definesAt(_, bb, i) and + a = bb.getNode(i) and + value = a.getRhs() + ) + } + + final override string toString() { result = Definition.super.toString() } + + final override Location getLocation() { result = this.getControlFlowNode().getLocation() } + } + + /** + * An SSA definition inserted at the beginning of a scope to represent an + * uninitialized local variable. For example, in + * + * ```rb + * def m + * x = 10 if b + * puts x + * end + * ``` + * + * since the assignment to `x` is conditional, an unitialized definition for + * `x` is inserted at the start of `m`. + */ + class UninitializedDefinition extends Definition, SsaImplCommon::WriteDefinition { + UninitializedDefinition() { + exists(BasicBlock bb, int i, Variable v | + this.definesAt(v, bb, i) and + SsaImpl::uninitializedWrite(bb, i, v) + ) + } + + final override string toString() { result = "" } + + final override Location getLocation() { result = this.getBasicBlock().getLocation() } + } + + /** + * An SSA definition inserted at the beginning of a scope to represent a + * captured local variable. For example, in + * + * ```rb + * def m x + * y = 0 + * x.times do |x| + * y += x + * end + * return y + * end + * ``` + * + * an entry definition for `y` is inserted at the start of the `do` block. + */ + class CapturedEntryDefinition extends Definition, SsaImplCommon::WriteDefinition { + CapturedEntryDefinition() { + exists(BasicBlock bb, int i, Variable v | + this.definesAt(v, bb, i) and + SsaImpl::capturedEntryWrite(bb, i, v) + ) + } + + final override string toString() { result = "" } + + override Location getLocation() { result = this.getBasicBlock().getLocation() } + } + + /** + * An SSA definition inserted at a call that may update the value of a captured + * variable. For example, in + * + * ```rb + * def m x + * y = 0 + * x.times do |x| + * y += x + * end + * return y + * end + * ``` + * + * a definition for `y` is inserted at the call to `times`. + */ + class CapturedCallDefinition extends Definition, SsaImplCommon::UncertainWriteDefinition { + CapturedCallDefinition() { + exists(Variable v, BasicBlock bb, int i | + this.definesAt(v, bb, i) and + SsaImpl::capturedCallWrite(bb, i, v) + ) + } + + /** + * Gets the immediately preceding definition. Since this update is uncertain, + * the value from the preceding definition might still be valid. + */ + final Definition getPriorDefinition() { result = SsaImpl::uncertainWriteDefinitionInput(this) } + + override string toString() { result = this.getControlFlowNode().toString() } + } + + /** + * A phi node. For example, in + * + * ```rb + * if b + * x = 0 + * else + * x = 1 + * end + * puts x + * ``` + * + * a phi node for `x` is inserted just before the call `puts x`. + */ + class PhiNode extends Definition, SsaImplCommon::PhiNode { + /** + * Gets an input of this phi node. + * + * Example: + * + * ```rb + * def m b + * i = 0 # defines i_0 + * puts i + * puts i + 1 + * if b + * i = 1 # defines i_1 + * puts i + * puts i + 1 + * else + * i = 2 # defines i_2 + * puts i + * puts i + 1 + * end + * # defines i_3 = phi(i_1, i_2); inputs are i_1 and i_2 + * puts i + * end + * ``` + */ + final Definition getAnInput() { this.hasInputFromBlock(result, _) } + + /** Holds if `inp` is an input to this phi node along the edge originating in `bb`. */ + predicate hasInputFromBlock(Definition inp, BasicBlock bb) { + inp = SsaImpl::phiHasInputFromBlock(this, bb) + } + + private string getSplitString() { + result = this.getBasicBlock().getFirstNode().(CfgNodes::AstCfgNode).getSplitsString() + } + + override string toString() { + exists(string prefix | + prefix = "[" + this.getSplitString() + "] " + or + not exists(this.getSplitString()) and + prefix = "" + | + result = prefix + "phi" + ) + } + + /* + * The location of a phi node is the same as the location of the first node + * in the basic block in which it is defined. + * + * Strictly speaking, the node is *before* the first node, but such a location + * does not exist in the source program. + */ + + final override Location getLocation() { + result = this.getBasicBlock().getFirstNode().getLocation() + } + } +} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll new file mode 100644 index 000000000000..6bb71afcbfca --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll @@ -0,0 +1,446 @@ +private import ruby +private import codeql.ruby.CFG +private import DataFlowPrivate +private import codeql.ruby.typetracking.TypeTracker +private import codeql.ruby.ast.internal.Module +private import FlowSummaryImpl as FlowSummaryImpl +private import codeql.ruby.dataflow.FlowSummary + +newtype TReturnKind = + TNormalReturnKind() or + TBreakReturnKind() + +/** + * Gets a node that can read the value returned from `call` with return kind + * `kind`. + */ +OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) } + +/** + * A return kind. A return kind describes how a value can be returned + * from a callable. + */ +abstract class ReturnKind extends TReturnKind { + /** Gets a textual representation of this position. */ + abstract string toString(); +} + +/** + * A value returned from a callable using a `return` statement or an expression + * body, that is, a "normal" return. + */ +class NormalReturnKind extends ReturnKind, TNormalReturnKind { + override string toString() { result = "return" } +} + +/** + * A value returned from a callable using a `break` statement. + */ +class BreakReturnKind extends ReturnKind, TBreakReturnKind { + override string toString() { result = "break" } +} + +/** A callable defined in library code, identified by a unique string. */ +abstract class LibraryCallable extends string { + bindingset[this] + LibraryCallable() { any() } + + /** Gets a call to this library callable. */ + abstract Call getACall(); +} + +/** + * A callable. This includes callables from source code, as well as callables + * defined in library code. + */ +class DataFlowCallable extends TDataFlowCallable { + /** Gets the underlying source code callable, if any. */ + Callable asCallable() { this = TCfgScope(result) } + + /** Gets the underlying library callable, if any. */ + LibraryCallable asLibraryCallable() { this = TLibraryCallable(result) } + + /** Gets a textual representation of this callable. */ + string toString() { result = [this.asCallable().toString(), this.asLibraryCallable()] } + + /** Gets the location of this callable. */ + Location getLocation() { result = this.asCallable().getLocation() } +} + +/** + * A call. This includes calls from source code, as well as call(back)s + * inside library callables with a flow summary. + */ +class DataFlowCall extends TDataFlowCall { + /** Gets the enclosing callable. */ + DataFlowCallable getEnclosingCallable() { none() } + + /** Gets the underlying source code call, if any. */ + CfgNodes::ExprNodes::CallCfgNode asCall() { none() } + + /** Gets a textual representation of this call. */ + string toString() { none() } + + /** Gets the location of this call. */ + Location getLocation() { none() } +} + +/** + * A synthesized call inside a callable with a flow summary. + * + * For example, in + * ```rb + * ints.each do |i| + * puts i + * end + * ``` + * + * there is a call to the block argument inside `each`. + */ +class SummaryCall extends DataFlowCall, TSummaryCall { + private FlowSummaryImpl::Public::SummarizedCallable c; + private DataFlow::Node receiver; + + SummaryCall() { this = TSummaryCall(c, receiver) } + + /** Gets the data flow node that this call targets. */ + DataFlow::Node getReceiver() { result = receiver } + + override DataFlowCallable getEnclosingCallable() { result = c } + + override string toString() { result = "[summary] call to " + receiver + " in " + c } + + override Location getLocation() { result = c.getLocation() } +} + +private class NormalCall extends DataFlowCall, TNormalCall { + private CfgNodes::ExprNodes::CallCfgNode c; + + NormalCall() { this = TNormalCall(c) } + + override CfgNodes::ExprNodes::CallCfgNode asCall() { result = c } + + override DataFlowCallable getEnclosingCallable() { result = TCfgScope(c.getScope()) } + + override string toString() { result = c.toString() } + + override Location getLocation() { result = c.getLocation() } +} + +pragma[nomagic] +private predicate methodCall( + CfgNodes::ExprNodes::CallCfgNode call, DataFlow::LocalSourceNode sourceNode, string method +) { + exists(DataFlow::Node nodeTo | + method = call.getExpr().(MethodCall).getMethodName() and + nodeTo.asExpr() = call.getReceiver() and + sourceNode.flowsTo(nodeTo) + ) +} + +private Block yieldCall(CfgNodes::ExprNodes::CallCfgNode call) { + call.getExpr() instanceof YieldCall and + exists(BlockParameterNode node | + node = trackBlock(result) and + node.getMethod() = call.getExpr().getEnclosingMethod() + ) +} + +pragma[nomagic] +private predicate superCall(CfgNodes::ExprNodes::CallCfgNode call, Module superClass, string method) { + call.getExpr() instanceof SuperCall and + exists(Module tp | + tp = call.getExpr().getEnclosingModule().getModule() and + superClass = tp.getSuperClass() and + method = call.getExpr().getEnclosingMethod().getName() + ) +} + +pragma[nomagic] +private predicate instanceMethodCall(CfgNodes::ExprNodes::CallCfgNode call, Module tp, string method) { + exists(DataFlow::LocalSourceNode sourceNode | + methodCall(call, sourceNode, method) and + sourceNode = trackInstance(tp) + ) +} + +cached +private module Cached { + cached + newtype TDataFlowCallable = + TCfgScope(CfgScope scope) or + TLibraryCallable(LibraryCallable callable) + + cached + newtype TDataFlowCall = + TNormalCall(CfgNodes::ExprNodes::CallCfgNode c) or + TSummaryCall(FlowSummaryImpl::Public::SummarizedCallable c, DataFlow::Node receiver) { + FlowSummaryImpl::Private::summaryCallbackRange(c, receiver) + } + + cached + CfgScope getTarget(CfgNodes::ExprNodes::CallCfgNode call) { + // Temporarily disable operation resolution (due to bad performance) + not call.getExpr() instanceof Operation and + ( + exists(string method | + exists(Module tp | + instanceMethodCall(call, tp, method) and + result = lookupMethod(tp, method) and + if result.(Method).isPrivate() + then + exists(Self self | + self = call.getReceiver().getExpr() and + pragma[only_bind_out](self.getEnclosingModule().getModule().getSuperClass*()) = + pragma[only_bind_out](result.getEnclosingModule().getModule()) + ) and + // For now, we restrict the scope of top-level declarations to their file. + // This may remove some plausible targets, but also removes a lot of + // implausible targets + if result.getEnclosingModule() instanceof Toplevel + then result.getFile() = call.getFile() + else any() + else any() + ) + or + exists(DataFlow::LocalSourceNode sourceNode | + methodCall(call, sourceNode, method) and + sourceNode = trackSingletonMethod(result, method) + ) + ) + or + exists(Module superClass, string method | + superCall(call, superClass, method) and + result = lookupMethod(superClass, method) + ) + or + result = yieldCall(call) + ) + } +} + +import Cached + +private DataFlow::LocalSourceNode trackInstance(Module tp, TypeTracker t) { + t.start() and + ( + result.asExpr().getExpr() instanceof NilLiteral and tp = TResolved("NilClass") + or + result.asExpr().getExpr().(BooleanLiteral).isFalse() and tp = TResolved("FalseClass") + or + result.asExpr().getExpr().(BooleanLiteral).isTrue() and tp = TResolved("TrueClass") + or + result.asExpr().getExpr() instanceof IntegerLiteral and tp = TResolved("Integer") + or + result.asExpr().getExpr() instanceof FloatLiteral and tp = TResolved("Float") + or + result.asExpr().getExpr() instanceof RationalLiteral and tp = TResolved("Rational") + or + result.asExpr().getExpr() instanceof ComplexLiteral and tp = TResolved("Complex") + or + result.asExpr().getExpr() instanceof StringlikeLiteral and tp = TResolved("String") + or + exists(ConstantReadAccess array, MethodCall mc | + result.asExpr().getExpr() = mc and + mc.getMethodName() = "[]" and + mc.getReceiver() = array and + array.getName() = "Array" and + array.hasGlobalScope() and + tp = TResolved("Array") + ) + or + result.asExpr().getExpr() instanceof HashLiteral and tp = TResolved("Hash") + or + result.asExpr().getExpr() instanceof MethodBase and tp = TResolved("Symbol") + or + result.asParameter() instanceof BlockParameter and tp = TResolved("Proc") + or + result.asExpr().getExpr() instanceof Lambda and tp = TResolved("Proc") + or + exists(CfgNodes::ExprNodes::CallCfgNode call, DataFlow::Node nodeTo | + call.getExpr().(MethodCall).getMethodName() = "new" and + nodeTo.asExpr() = call.getReceiver() and + trackModule(tp).flowsTo(nodeTo) and + result.asExpr() = call + ) + or + // `self` in method + exists(Self self, Method enclosing | + self = result.asExpr().getExpr() and + enclosing = self.getEnclosingMethod() and + tp = enclosing.getEnclosingModule().getModule() and + not self.getEnclosingModule().getEnclosingMethod() = enclosing + ) + or + // `self` in singleton method + exists(Self self, MethodBase enclosing | + self = result.asExpr().getExpr() and + flowsToSingletonMethodObject(trackInstance(tp), enclosing) and + enclosing = self.getEnclosingMethod() and + not self.getEnclosingModule().getEnclosingMethod() = enclosing + ) + or + // `self` in top-level + exists(Self self, Toplevel enclosing | + self = result.asExpr().getExpr() and + enclosing = self.getEnclosingModule() and + tp = TResolved("Object") and + not self.getEnclosingMethod().getEnclosingModule() = enclosing + ) + or + // a module or class + exists(Module m | + result = trackModule(m) and + if m.isClass() then tp = TResolved("Class") else tp = TResolved("Module") + ) + ) + or + exists(TypeTracker t2, StepSummary summary | + result = trackInstanceRec(tp, t2, summary) and t = t2.append(summary) + ) +} + +pragma[nomagic] +private DataFlow::LocalSourceNode trackInstanceRec(Module tp, TypeTracker t, StepSummary summary) { + StepSummary::step(trackInstance(tp, t), result, summary) +} + +private DataFlow::LocalSourceNode trackInstance(Module tp) { + result = trackInstance(tp, TypeTracker::end()) +} + +private DataFlow::LocalSourceNode trackBlock(Block block, TypeTracker t) { + t.start() and result.asExpr().getExpr() = block + or + exists(TypeTracker t2, StepSummary summary | + result = trackBlockRec(block, t2, summary) and t = t2.append(summary) + ) +} + +pragma[nomagic] +private DataFlow::LocalSourceNode trackBlockRec(Block block, TypeTracker t, StepSummary summary) { + StepSummary::step(trackBlock(block, t), result, summary) +} + +private DataFlow::LocalSourceNode trackBlock(Block block) { + result = trackBlock(block, TypeTracker::end()) +} + +private predicate singletonMethod(MethodBase method, Expr object) { + object = method.(SingletonMethod).getObject() + or + exists(SingletonClass cls | + object = cls.getValue() and method instanceof Method and method = cls.getAMethod() + ) +} + +pragma[nomagic] +private predicate flowsToSingletonMethodObject(DataFlow::LocalSourceNode nodeFrom, MethodBase method) { + exists(DataFlow::LocalSourceNode nodeTo | + nodeFrom.flowsTo(nodeTo) and + singletonMethod(method, nodeTo.asExpr().getExpr()) + ) +} + +pragma[nomagic] +private predicate moduleFlowsToSingletonMethodObject(Module m, MethodBase method) { + flowsToSingletonMethodObject(trackModule(m), method) +} + +pragma[nomagic] +private DataFlow::LocalSourceNode trackSingletonMethod0(MethodBase method, TypeTracker t) { + t.start() and + ( + flowsToSingletonMethodObject(result, method) + or + exists(Module m | result = trackModule(m) and moduleFlowsToSingletonMethodObject(m, method)) + ) + or + exists(TypeTracker t2, StepSummary summary | + result = trackSingletonMethod0Rec(method, t2, summary) and t = t2.append(summary) + ) +} + +pragma[nomagic] +private DataFlow::LocalSourceNode trackSingletonMethod0Rec( + MethodBase method, TypeTracker t, StepSummary summary +) { + StepSummary::step(trackSingletonMethod0(method, t), result, summary) +} + +pragma[nomagic] +private DataFlow::LocalSourceNode trackSingletonMethod(MethodBase m, string name) { + result = trackSingletonMethod0(m, TypeTracker::end()) and + name = m.getName() +} + +private DataFlow::Node selfInModule(Module tp) { + exists(Self self, ModuleBase enclosing | + self = result.asExpr().getExpr() and + enclosing = self.getEnclosingModule() and + tp = enclosing.getModule() and + not self.getEnclosingMethod().getEnclosingModule() = enclosing + ) +} + +private DataFlow::LocalSourceNode trackModule(Module tp, TypeTracker t) { + t.start() and + ( + // ConstantReadAccess to Module + resolveScopeExpr(result.asExpr().getExpr()) = tp + or + // `self` reference to Module + result = selfInModule(tp) + ) + or + exists(TypeTracker t2, StepSummary summary | + result = trackModuleRec(tp, t2, summary) and t = t2.append(summary) + ) +} + +pragma[nomagic] +private DataFlow::LocalSourceNode trackModuleRec(Module tp, TypeTracker t, StepSummary summary) { + StepSummary::step(trackModule(tp, t), result, summary) +} + +private DataFlow::LocalSourceNode trackModule(Module tp) { + result = trackModule(tp, TypeTracker::end()) +} + +/** Gets a viable run-time target for the call `call`. */ +DataFlowCallable viableCallable(DataFlowCall call) { + result = TCfgScope(getTarget(call.asCall())) and + not call.asCall().getExpr() instanceof YieldCall // handled by `lambdaCreation`/`lambdaCall` + or + exists(LibraryCallable callable | + result = TLibraryCallable(callable) and + call.asCall().getExpr() = callable.getACall() + ) +} + +/** + * Holds if the set of viable implementations that can be called by `call` + * might be improved by knowing the call context. This is the case if the + * qualifier accesses a parameter of the enclosing callable `c` (including + * the implicit `self` parameter). + */ +predicate mayBenefitFromCallContext(DataFlowCall call, DataFlowCallable c) { none() } + +/** + * Gets a viable dispatch target of `call` in the context `ctx`. This is + * restricted to those `call`s for which a context might make a difference. + */ +DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) { none() } + +/** + * Holds if `e` is an `ExprNode` that may be returned by a call to `c`. + */ +predicate exprNodeReturnedFrom(DataFlow::ExprNode e, Callable c) { + exists(ReturnNode r | + r.getEnclosingCallable().asCallable() = c and + ( + r.(ExplicitReturnNode).getReturningNode().getReturnedValueNode() = e.asExpr() or + r.(ExprReturnNode) = e + ) + ) +} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll new file mode 100644 index 000000000000..0c99a25ccc4b --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll @@ -0,0 +1,4555 @@ +/** + * Provides an implementation of global (interprocedural) data flow. This file + * re-exports the local (intraprocedural) data flow analysis from + * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed + * through the `Configuration` class. This file exists in several identical + * copies, allowing queries to use multiple `Configuration` classes that depend + * on each other without introducing mutual recursion among those configurations. + */ + +private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public + +/** + * A configuration of interprocedural data flow analysis. This defines + * sources, sinks, and any other configurable aspect of the analysis. Each + * use of the global data flow library must define its own unique extension + * of this abstract class. To create a configuration, extend this class with + * a subclass whose characteristic predicate is a unique singleton string. + * For example, write + * + * ```ql + * class MyAnalysisConfiguration extends DataFlow::Configuration { + * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } + * // Override `isSource` and `isSink`. + * // Optionally override `isBarrier`. + * // Optionally override `isAdditionalFlowStep`. + * } + * ``` + * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and + * the edges are those data-flow steps that preserve the value of the node + * along with any additional edges defined by `isAdditionalFlowStep`. + * Specifying nodes in `isBarrier` will remove those nodes from the graph, and + * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going + * and/or out-going edges from those nodes, respectively. + * + * Then, to query whether there is flow between some `source` and `sink`, + * write + * + * ```ql + * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) + * ``` + * + * Multiple configurations can coexist, but two classes extending + * `DataFlow::Configuration` should never depend on each other. One of them + * should instead depend on a `DataFlow2::Configuration`, a + * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. + */ +abstract class Configuration extends string { + bindingset[this] + Configuration() { any() } + + /** + * Holds if `source` is a relevant data flow source. + */ + abstract predicate isSource(Node source); + + /** + * Holds if `sink` is a relevant data flow sink. + */ + abstract predicate isSink(Node sink); + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + predicate isBarrier(Node node) { none() } + + /** Holds if data flow into `node` is prohibited. */ + predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + predicate isBarrierOut(Node node) { none() } + + /** Holds if data flow through nodes guarded by `guard` is prohibited. */ + predicate isBarrierGuard(BarrierGuard guard) { none() } + + /** + * Holds if the additional flow step from `node1` to `node2` must be taken + * into account in the analysis. + */ + predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Holds if an arbitrary number of implicit read steps of content `c` may be + * taken at `node`. + */ + predicate allowImplicitRead(Node node, Content c) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + int fieldFlowBranchLimit() { result = 2 } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + */ + predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink) { flowsTo(source, sink, _, _, this) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowTo(Node sink) { hasFlow(_, sink) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } + + /** + * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` + * measured in approximate number of interprocedural steps. + */ + int explorationLimit() { none() } + + /** + * Holds if there is a partial data flow path from `source` to `node`. The + * approximate distance between `node` and the closest source is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards sink definitions. + * + * This predicate is intended for data-flow exploration and debugging and may + * perform poorly if the number of sources is too big and/or the exploration + * limit is set too high without using barriers. + * + * This predicate is disabled (has no results) by default. Override + * `explorationLimit()` with a suitable number to enable this predicate. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + */ + final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { + partialFlow(source, node, this) and + dist = node.getSourceDistance() + } + + /** + * Holds if there is a partial data flow path from `node` to `sink`. The + * approximate distance between `node` and the closest sink is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards source definitions. + * + * This predicate is intended for data-flow exploration and debugging and may + * perform poorly if the number of sinks is too big and/or the exploration + * limit is set too high without using barriers. + * + * This predicate is disabled (has no results) by default. Override + * `explorationLimit()` with a suitable number to enable this predicate. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + * + * Note that reverse flow has slightly lower precision than the corresponding + * forward flow, as reverse flow disregards type pruning among other features. + */ + final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { + revPartialFlow(node, sink, this) and + dist = node.getSinkDistance() + } +} + +/** + * This class exists to prevent mutual recursion between the user-overridden + * member predicates of `Configuration` and the rest of the data-flow library. + * Good performance cannot be guaranteed in the presence of such recursion, so + * it should be replaced by using more than one copy of the data flow library. + */ +abstract private class ConfigurationRecursionPrevention extends Configuration { + bindingset[this] + ConfigurationRecursionPrevention() { any() } + + override predicate hasFlow(Node source, Node sink) { + strictcount(Node n | this.isSource(n)) < 0 + or + strictcount(Node n | this.isSink(n)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 + or + super.hasFlow(source, sink) + } +} + +private newtype TNodeEx = + TNodeNormal(Node n) or + TNodeImplicitRead(Node n, boolean hasRead) { + any(Configuration c).allowImplicitRead(n, _) and hasRead = [false, true] + } + +private class NodeEx extends TNodeEx { + string toString() { + result = this.asNode().toString() + or + exists(Node n | this.isImplicitReadNode(n, _) | result = n.toString() + " [Ext]") + } + + Node asNode() { this = TNodeNormal(result) } + + predicate isImplicitReadNode(Node n, boolean hasRead) { this = TNodeImplicitRead(n, hasRead) } + + Node projectToNode() { this = TNodeNormal(result) or this = TNodeImplicitRead(result, _) } + + pragma[nomagic] + private DataFlowCallable getEnclosingCallable0() { + nodeEnclosingCallable(this.projectToNode(), result) + } + + pragma[inline] + DataFlowCallable getEnclosingCallable() { + pragma[only_bind_out](this).getEnclosingCallable0() = pragma[only_bind_into](result) + } + + pragma[nomagic] + private DataFlowType getDataFlowType0() { nodeDataFlowType(this.asNode(), result) } + + pragma[inline] + DataFlowType getDataFlowType() { + pragma[only_bind_out](this).getDataFlowType0() = pragma[only_bind_into](result) + } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.projectToNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +private class ArgNodeEx extends NodeEx { + ArgNodeEx() { this.asNode() instanceof ArgNode } +} + +private class ParamNodeEx extends NodeEx { + ParamNodeEx() { this.asNode() instanceof ParamNode } + + predicate isParameterOf(DataFlowCallable c, int i) { + this.asNode().(ParamNode).isParameterOf(c, i) + } + + int getPosition() { this.isParameterOf(_, result) } +} + +private class RetNodeEx extends NodeEx { + RetNodeEx() { this.asNode() instanceof ReturnNodeExt } + + ReturnPosition getReturnPosition() { result = getReturnPosition(this.asNode()) } + + ReturnKindExt getKind() { result = this.asNode().(ReturnNodeExt).getKind() } +} + +private predicate inBarrier(NodeEx node, Configuration config) { + exists(Node n | + node.asNode() = n and + config.isBarrierIn(n) and + config.isSource(n) + ) +} + +private predicate outBarrier(NodeEx node, Configuration config) { + exists(Node n | + node.asNode() = n and + config.isBarrierOut(n) and + config.isSink(n) + ) +} + +private predicate fullBarrier(NodeEx node, Configuration config) { + exists(Node n | node.asNode() = n | + config.isBarrier(n) + or + config.isBarrierIn(n) and + not config.isSource(n) + or + config.isBarrierOut(n) and + not config.isSink(n) + or + exists(BarrierGuard g | + config.isBarrierGuard(g) and + n = g.getAGuardedNode() + ) + ) +} + +pragma[nomagic] +private predicate sourceNode(NodeEx node, Configuration config) { config.isSource(node.asNode()) } + +pragma[nomagic] +private predicate sinkNode(NodeEx node, Configuration config) { config.isSink(node.asNode()) } + +/** + * Holds if data can flow in one local step from `node1` to `node2`. + */ +private predicate localFlowStep(NodeEx node1, NodeEx node2, Configuration config) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + simpleLocalFlowStepExt(n1, n2) and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + not fullBarrier(node1, config) and + not fullBarrier(node2, config) + ) + or + exists(Node n | + config.allowImplicitRead(n, _) and + node1.asNode() = n and + node2.isImplicitReadNode(n, false) + ) +} + +/** + * Holds if the additional step from `node1` to `node2` does not jump between callables. + */ +private predicate additionalLocalFlowStep(NodeEx node1, NodeEx node2, Configuration config) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + config.isAdditionalFlowStep(n1, n2) and + getNodeEnclosingCallable(n1) = getNodeEnclosingCallable(n2) and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + not fullBarrier(node1, config) and + not fullBarrier(node2, config) + ) + or + exists(Node n | + config.allowImplicitRead(n, _) and + node1.isImplicitReadNode(n, true) and + node2.asNode() = n + ) +} + +/** + * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. + */ +private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + jumpStepCached(n1, n2) and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + not fullBarrier(node1, config) and + not fullBarrier(node2, config) + ) +} + +/** + * Holds if the additional step from `node1` to `node2` jumps between callables. + */ +private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration config) { + exists(Node n1, Node n2 | + node1.asNode() = n1 and + node2.asNode() = n2 and + config.isAdditionalFlowStep(n1, n2) and + getNodeEnclosingCallable(n1) != getNodeEnclosingCallable(n2) and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + not fullBarrier(node1, config) and + not fullBarrier(node2, config) + ) +} + +private predicate read(NodeEx node1, Content c, NodeEx node2, Configuration config) { + read(node1.asNode(), c, node2.asNode()) + or + exists(Node n | + node2.isImplicitReadNode(n, true) and + node1.isImplicitReadNode(n, _) and + config.allowImplicitRead(n, c) + ) +} + +private predicate store( + NodeEx node1, TypedContent tc, NodeEx node2, DataFlowType contentType, Configuration config +) { + store(node1.asNode(), tc, node2.asNode(), contentType) and + read(_, tc.getContent(), _, config) +} + +pragma[nomagic] +private predicate viableReturnPosOutEx(DataFlowCall call, ReturnPosition pos, NodeEx out) { + viableReturnPosOut(call, pos, out.asNode()) +} + +pragma[nomagic] +private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx arg) { + viableParamArg(call, p.asNode(), arg.asNode()) +} + +/** + * Holds if field flow should be used for the given configuration. + */ +private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } + +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + class Cc = boolean; + + /* Begin: Stage 1 logic. */ + /** + * Holds if `node` is reachable from a source in the configuration `config`. + * + * The Boolean `cc` records whether the node is reached through an + * argument in a call. + */ + predicate fwdFlow(NodeEx node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + sourceNode(node, config) and + cc = false + or + exists(NodeEx mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(NodeEx mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(NodeEx mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(NodeEx mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(NodeEx mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _, config) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(NodeEx arg | + fwdFlow(arg, _, config) and + viableParamArgEx(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(NodeEx node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, NodeEx node, Cc cc, Configuration config) { + exists(NodeEx mid | + fwdFlow(mid, cc, config) and + read(mid, c, node, config) + ) + } + + /** + * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. + */ + pragma[nomagic] + private predicate fwdFlowConsCand(Content c, Configuration config) { + exists(NodeEx mid, NodeEx node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, _, config) and + store(mid, tc, node, _, config) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(RetNodeEx ret | + fwdFlow(ret, cc, config) and + ret.getReturnPosition() = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, NodeEx out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOutEx(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, NodeEx out, Configuration config) { + fwdFlowOut(call, out, true, config) + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { + exists(ArgNodeEx arg | + fwdFlow(arg, cc, config) and + viableParamArgEx(call, _, arg) + ) + } + + /** + * Holds if `node` is part of a path from a source to a sink in the + * configuration `config`. + * + * The Boolean `toReturn` records whether the node must be returned from + * the enclosing callable in order to reach a sink. + */ + pragma[nomagic] + predicate revFlow(NodeEx node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + sinkNode(node, config) and + toReturn = false + or + exists(NodeEx mid | + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) + ) + or + exists(NodeEx mid | + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) + ) + or + exists(NodeEx mid | + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false + ) + or + exists(NodeEx mid | + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) + ) + or + // read + exists(NodeEx mid, Content c | + read(node, c, mid, config) and + fwdFlowConsCand(c, pragma[only_bind_into](config)) and + revFlow(mid, toReturn, pragma[only_bind_into](config)) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + revFlowOut(pos, config) and + node.(RetNodeEx).getReturnPosition() = pos and + toReturn = true + ) + } + + /** + * Holds if `c` is the target of a read in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowConsCand(Content c, Configuration config) { + exists(NodeEx mid, NodeEx node | + fwdFlow(node, pragma[only_bind_into](config)) and + read(node, c, mid, config) and + fwdFlowConsCand(c, pragma[only_bind_into](config)) and + revFlow(pragma[only_bind_into](mid), _, pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate revFlowStore(Content c, NodeEx node, boolean toReturn, Configuration config) { + exists(NodeEx mid, TypedContent tc | + revFlow(mid, toReturn, pragma[only_bind_into](config)) and + fwdFlowConsCand(c, pragma[only_bind_into](config)) and + store(node, tc, mid, _, config) and + c = tc.getContent() + ) + } + + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } + + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and + viableReturnPosOutEx(call, pos, out) + } + + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, NodeEx out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } + + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config + ) { + viableParamArgEx(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgNodeEx arg, boolean toReturn, Configuration config + ) { + exists(ParamNodeEx p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgNodeEx arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(NodeEx out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, + Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and + revFlow(node2, pragma[only_bind_into](config)) and + store(node1, tc, node2, contentType, config) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(NodeEx n1, Content c, NodeEx n2, Configuration config) { + revFlowIsReadAndStored(c, pragma[only_bind_into](config)) and + revFlow(n2, pragma[only_bind_into](config)) and + read(n1, c, n2, pragma[only_bind_into](config)) + } + + pragma[nomagic] + predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand(NodeEx node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(RetNodeEx ret | + throughFlowNodeCand(ret, config) and + callable = ret.getEnclosingCallable() and + kind = ret.getKind() + ) + } + + /** + * Holds if flow may enter through `p` and reach a return node making `p` a + * candidate for the origin of a summary. + */ + predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = p.getPosition() + ) + } + + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNodeEx arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(NodeEx node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(NodeEx n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(NodeEx node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(NodeEx n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ +} + +pragma[noinline] +private predicate localFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { + Stage1::revFlow(node2, config) and + localFlowStep(node1, node2, config) +} + +pragma[noinline] +private predicate additionalLocalFlowStepNodeCand1(NodeEx node1, NodeEx node2, Configuration config) { + Stage1::revFlow(node2, config) and + additionalLocalFlowStep(node1, node2, config) +} + +pragma[nomagic] +private predicate viableReturnPosOutNodeCand1( + DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config +) { + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) +} + +/** + * Holds if data can flow out of `call` from `ret` to `out`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. + */ +pragma[nomagic] +private predicate flowOutOfCallNodeCand1( + DataFlowCall call, RetNodeEx ret, NodeEx out, Configuration config +) { + viableReturnPosOutNodeCand1(call, ret.getReturnPosition(), out, config) and + Stage1::revFlow(ret, config) and + not outBarrier(ret, config) and + not inBarrier(out, config) +} + +pragma[nomagic] +private predicate viableParamArgNodeCand1( + DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config +) { + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) +} + +/** + * Holds if data can flow into `call` and that this step is part of a + * path from a source to a sink. + */ +pragma[nomagic] +private predicate flowIntoCallNodeCand1( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, Configuration config +) { + viableParamArgNodeCand1(call, p, arg, config) and + Stage1::revFlow(p, config) and + not outBarrier(arg, config) and + not inBarrier(p, config) +} + +/** + * Gets the amount of forward branching on the origin of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ +private int branch(NodeEx n1, Configuration conf) { + result = + strictcount(NodeEx n | + flowOutOfCallNodeCand1(_, n1, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) + ) +} + +/** + * Gets the amount of backward branching on the target of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ +private int join(NodeEx n2, Configuration conf) { + result = + strictcount(NodeEx n | + flowOutOfCallNodeCand1(_, n, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) + ) +} + +/** + * Holds if data can flow out of `call` from `ret` to `out`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. The + * `allowsFieldFlow` flag indicates whether the branching is within the limit + * specified by the configuration. + */ +pragma[nomagic] +private predicate flowOutOfCallNodeCand1( + DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config +) { + flowOutOfCallNodeCand1(call, ret, out, config) and + exists(int b, int j | + b = branch(ret, config) and + j = join(out, config) and + if b.minimum(j) <= config.fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) +} + +/** + * Holds if data can flow into `call` and that this step is part of a + * path from a source to a sink. The `allowsFieldFlow` flag indicates whether + * the branching is within the limit specified by the configuration. + */ +pragma[nomagic] +private predicate flowIntoCallNodeCand1( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config +) { + flowIntoCallNodeCand1(call, arg, p, config) and + exists(int b, int j | + b = branch(arg, config) and + j = join(p, config) and + if b.minimum(j) <= config.fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) +} + +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(NodeEx node) { PrevStage::revFlow(node, _) and exists(result) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccNone() { result instanceof CallContextAny } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + checkCallContextCall(outercc, call, c) and + if recordDataFlowCallSiteDispatch(call, c) + then result = TSpecificCall(call) + else result = TSomeCall() + } + + bindingset[call, c, innercc] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { + checkCallContextReturn(innercc, c, call) and + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() } + + private predicate localStep( + NodeEx node1, NodeEx node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(NodeEx node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, ret.getEnclosingCallable(), _, + pragma[only_bind_into](config)) + } + + /** + * Holds if `node` is reachable with access path `ap` from a source in the + * configuration `config`. + * + * The call context `cc` records whether the node is reached through an + * argument in a call, and if so, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + sourceNode(node, config) and + cc = ccNone() and + argAp = apNone() and + ap = getApNil(node) + or + exists(NodeEx mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil + ) + or + exists(NodeEx mid | + fwdFlow(mid, _, _, ap, pragma[only_bind_into](config)) and + flowCand(node, _, pragma[only_bind_into](config)) and + jumpStep(mid, node, config) and + cc = ccNone() and + argAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(mid, _, _, nil, pragma[only_bind_into](config)) and + flowCand(node, _, pragma[only_bind_into](config)) and + additionalJumpStep(mid, node, config) and + cc = ccNone() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + } + + pragma[nomagic] + private predicate fwdFlowStore( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } + + /** + * Holds if forward flow with access path `tail` reaches a store of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(TypedContent tc | + fwdFlowStore(_, tail, tc, _, _, _, config) and + tc.getContent() = c and + cons = apCons(tc, tail) + ) + } + + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, NodeEx node1, NodeEx node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParamNodeEx p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgNodeEx arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutNotFromArg( + NodeEx out, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists( + DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + ccOut = getCallContextReturn(inner, call, innercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, NodeEx out, Ap argAp, Ap ap, Configuration config + ) { + exists(RetNodeEx ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` + * and data might flow through the target callable and back out at `call`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered( + DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParamNodeEx p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config + ) { + fwdFlowStore(node1, ap1, tc, node2, _, _, config) and + ap2 = apCons(tc, ap1) and + fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config) + } + + private predicate readStepFwd( + NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config + ) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(pragma[only_bind_into](call), pragma[only_bind_into](cc), + pragma[only_bind_into](argAp), pragma[only_bind_into](argAp0), + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + + /** + * Holds if `node` with access path `ap` is part of a path from a source to a + * sink in the configuration `config`. + * + * The Boolean `toReturn` records whether the node must be returned from the + * enclosing callable in order to reach a sink, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + sinkNode(node, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(NodeEx mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, pragma[only_bind_into](config)) and + ap instanceof ApNil + ) + or + exists(NodeEx mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and + additionalJumpStep(node, mid, config) and + revFlow(pragma[only_bind_into](mid), _, _, nil, pragma[only_bind_into](config)) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(NodeEx mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, NodeEx node, TypedContent tc, NodeEx mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } + + /** + * Holds if reverse flow with access path `tail` reaches a read of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(NodeEx mid, Ap tail0 | + revFlow(mid, _, _, tail, config) and + tail = pragma[only_bind_into](tail0) and + readStepFwd(_, cons, c, mid, tail0, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, RetNodeEx ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(NodeEx out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInNotToReturn( + ArgNodeEx arg, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgNodeEx arg, Ap returnAp, Ap ap, Configuration config + ) { + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. + */ + pragma[nomagic] + private predicate revFlowIsReturned( + DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + exists(RetNodeEx ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, + Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType, config) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, + pragma[only_bind_into](config)) + ) + } + + predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParamNodeEx p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) { + exists(RetNodeEx ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(pragma[only_bind_into](ret), true, apSome(_), pragma[only_bind_into](ap0), + pragma[only_bind_into](config)) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.getPosition() = pos and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNodeEx arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(NodeEx node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(NodeEx n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(NodeEx node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(NodeEx n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} + +pragma[nomagic] +private predicate flowOutOfCallNodeCand2( + DataFlowCall call, RetNodeEx node1, NodeEx node2, boolean allowsFieldFlow, Configuration config +) { + flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and + Stage2::revFlow(node2, pragma[only_bind_into](config)) and + Stage2::revFlow(node1, pragma[only_bind_into](config)) +} + +pragma[nomagic] +private predicate flowIntoCallNodeCand2( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, + Configuration config +) { + flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and + Stage2::revFlow(node2, pragma[only_bind_into](config)) and + Stage2::revFlow(node1, pragma[only_bind_into](config)) +} + +private module LocalFlowBigStep { + /** + * A node where some checking is required, and hence the big-step relation + * is not allowed to step over. + */ + private class FlowCheckNode extends NodeEx { + FlowCheckNode() { + castNode(this.asNode()) or + clearsContentCached(this.asNode(), _) + } + } + + /** + * Holds if `node` can be the first node in a maximal subsequence of local + * flow steps in a dataflow path. + */ + predicate localFlowEntry(NodeEx node, Configuration config) { + Stage2::revFlow(node, config) and + ( + sourceNode(node, config) or + jumpStep(_, node, config) or + additionalJumpStep(_, node, config) or + node instanceof ParamNodeEx or + node.asNode() instanceof OutNodeExt or + store(_, _, node, _, config) or + read(_, _, node, config) or + node instanceof FlowCheckNode + ) + } + + /** + * Holds if `node` can be the last node in a maximal subsequence of local + * flow steps in a dataflow path. + */ + private predicate localFlowExit(NodeEx node, Configuration config) { + exists(NodeEx next | Stage2::revFlow(next, config) | + jumpStep(node, next, config) or + additionalJumpStep(node, next, config) or + flowIntoCallNodeCand1(_, node, next, config) or + flowOutOfCallNodeCand1(_, node, next, config) or + store(node, _, next, _, config) or + read(node, _, next, config) + ) + or + node instanceof FlowCheckNode + or + sinkNode(node, config) + } + + pragma[noinline] + private predicate additionalLocalFlowStepNodeCand2( + NodeEx node1, NodeEx node2, Configuration config + ) { + additionalLocalFlowStepNodeCand1(node1, node2, config) and + Stage2::revFlow(node1, _, _, false, pragma[only_bind_into](config)) and + Stage2::revFlow(node2, _, _, false, pragma[only_bind_into](config)) + } + + /** + * Holds if the local path from `node1` to `node2` is a prefix of a maximal + * subsequence of local flow steps in a dataflow path. + * + * This is the transitive closure of `[additional]localFlowStep` beginning + * at `localFlowEntry`. + */ + pragma[nomagic] + private predicate localFlowStepPlus( + NodeEx node1, NodeEx node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc + ) { + not isUnreachableInCallCached(node2.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and + ( + localFlowEntry(node1, pragma[only_bind_into](config)) and + ( + localFlowStepNodeCand1(node1, node2, config) and + preservesValue = true and + t = node1.getDataFlowType() // irrelevant dummy value + or + additionalLocalFlowStepNodeCand2(node1, node2, config) and + preservesValue = false and + t = node2.getDataFlowType() + ) and + node1 != node2 and + cc.relevantFor(node1.getEnclosingCallable()) and + not isUnreachableInCallCached(node1.asNode(), cc.(LocalCallContextSpecificCall).getCall()) and + Stage2::revFlow(node2, pragma[only_bind_into](config)) + or + exists(NodeEx mid | + localFlowStepPlus(node1, mid, preservesValue, t, pragma[only_bind_into](config), cc) and + localFlowStepNodeCand1(mid, node2, config) and + not mid instanceof FlowCheckNode and + Stage2::revFlow(node2, pragma[only_bind_into](config)) + ) + or + exists(NodeEx mid | + localFlowStepPlus(node1, mid, _, _, pragma[only_bind_into](config), cc) and + additionalLocalFlowStepNodeCand2(mid, node2, config) and + not mid instanceof FlowCheckNode and + preservesValue = false and + t = node2.getDataFlowType() and + Stage2::revFlow(node2, pragma[only_bind_into](config)) + ) + ) + } + + /** + * Holds if `node1` can step to `node2` in one or more local steps and this + * path can occur as a maximal subsequence of local steps in a dataflow path. + */ + pragma[nomagic] + predicate localFlowBigStep( + NodeEx node1, NodeEx node2, boolean preservesValue, AccessPathFrontNil apf, + Configuration config, LocalCallContext callContext + ) { + localFlowStepPlus(node1, node2, preservesValue, apf.getType(), config, callContext) and + localFlowExit(node2, config) + } +} + +private import LocalFlowBigStep + +private module Stage3 { + module PrevStage = Stage2; + + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathFront; + + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TFrontNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccNone() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c, innercc] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() } + + bindingset[node, cc, config] + private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() } + + private predicate localStep( + NodeEx node1, NodeEx node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + pragma[nomagic] + private predicate clear(NodeEx node, Ap ap) { ap.isClearedAt(node.asNode()) } + + pragma[nomagic] + private predicate castingNodeEx(NodeEx node) { node.asNode() instanceof CastingNode } + + bindingset[node, ap] + private predicate filter(NodeEx node, Ap ap) { + not clear(node, ap) and + if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), ap.getType()) else any() + } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { + // We need to typecheck stores here, since reverse flow through a getter + // might have a different type here compared to inside the getter. + compatibleTypes(ap.getType(), contentType) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(NodeEx node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + bindingset[result, apa] + private ApApprox unbindApa(ApApprox apa) { + exists(ApApprox apa0 | + apa = pragma[only_bind_into](apa0) and result = pragma[only_bind_into](apa0) + ) + } + + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, ret.getEnclosingCallable(), _, + pragma[only_bind_into](config)) + } + + /** + * Holds if `node` is reachable with access path `ap` from a source in the + * configuration `config`. + * + * The call context `cc` records whether the node is reached through an + * argument in a call, and if so, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindApa(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + sourceNode(node, config) and + cc = ccNone() and + argAp = apNone() and + ap = getApNil(node) + or + exists(NodeEx mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil + ) + or + exists(NodeEx mid | + fwdFlow(mid, _, _, ap, pragma[only_bind_into](config)) and + flowCand(node, _, pragma[only_bind_into](config)) and + jumpStep(mid, node, config) and + cc = ccNone() and + argAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(mid, _, _, nil, pragma[only_bind_into](config)) and + flowCand(node, _, pragma[only_bind_into](config)) and + additionalJumpStep(mid, node, config) and + cc = ccNone() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + } + + pragma[nomagic] + private predicate fwdFlowStore( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, unbindApa(getApprox(ap1)), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } + + /** + * Holds if forward flow with access path `tail` reaches a store of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(TypedContent tc | + fwdFlowStore(_, tail, tc, _, _, _, config) and + tc.getContent() = c and + cons = apCons(tc, tail) + ) + } + + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, NodeEx node1, NodeEx node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParamNodeEx p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgNodeEx arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutNotFromArg( + NodeEx out, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists( + DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + ccOut = getCallContextReturn(inner, call, innercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, NodeEx out, Ap argAp, Ap ap, Configuration config + ) { + exists(RetNodeEx ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` + * and data might flow through the target callable and back out at `call`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered( + DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParamNodeEx p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config + ) { + fwdFlowStore(node1, ap1, tc, node2, _, _, config) and + ap2 = apCons(tc, ap1) and + fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config) + } + + private predicate readStepFwd( + NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config + ) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(pragma[only_bind_into](call), pragma[only_bind_into](cc), + pragma[only_bind_into](argAp), pragma[only_bind_into](argAp0), + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + + /** + * Holds if `node` with access path `ap` is part of a path from a source to a + * sink in the configuration `config`. + * + * The Boolean `toReturn` records whether the node must be returned from the + * enclosing callable in order to reach a sink, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + sinkNode(node, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(NodeEx mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, pragma[only_bind_into](config)) and + ap instanceof ApNil + ) + or + exists(NodeEx mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and + additionalJumpStep(node, mid, config) and + revFlow(pragma[only_bind_into](mid), _, _, nil, pragma[only_bind_into](config)) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(NodeEx mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, NodeEx node, TypedContent tc, NodeEx mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } + + /** + * Holds if reverse flow with access path `tail` reaches a read of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(NodeEx mid, Ap tail0 | + revFlow(mid, _, _, tail, config) and + tail = pragma[only_bind_into](tail0) and + readStepFwd(_, cons, c, mid, tail0, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, RetNodeEx ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(NodeEx out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInNotToReturn( + ArgNodeEx arg, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgNodeEx arg, Ap returnAp, Ap ap, Configuration config + ) { + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. + */ + pragma[nomagic] + private predicate revFlowIsReturned( + DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + exists(RetNodeEx ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, + Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType, config) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, + pragma[only_bind_into](config)) + ) + } + + predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParamNodeEx p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) { + exists(RetNodeEx ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(pragma[only_bind_into](ret), true, apSome(_), pragma[only_bind_into](ap0), + pragma[only_bind_into](config)) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.getPosition() = pos and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNodeEx arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(NodeEx node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(NodeEx n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(NodeEx node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(NodeEx n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ +} + +/** + * Holds if `argApf` is recorded as the summary context for flow reaching `node` + * and remains relevant for the following pruning stage. + */ +private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) { + exists(AccessPathFront apf | + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config) + ) +} + +/** + * Holds if a length 2 access path approximation with the head `tc` is expected + * to be expensive. + */ +private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { + exists(int tails, int nodes, int apLimit, int tupleLimit | + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and + nodes = + strictcount(NodeEx n | + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) + or + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) + ) and + accessPathApproxCostLimits(apLimit, tupleLimit) and + apLimit < tails and + tupleLimit < (tails - 1) * nodes + ) +} + +private newtype TAccessPathApprox = + TNil(DataFlowType t) or + TConsNil(TypedContent tc, DataFlowType t) { + Stage3::consCand(tc, TFrontNil(t), _) and + not expensiveLen2unfolding(tc, _) + } or + TConsCons(TypedContent tc1, TypedContent tc2, int len) { + Stage3::consCand(tc1, TFrontHead(tc2), _) and + len in [2 .. accessPathLimit()] and + not expensiveLen2unfolding(tc1, _) + } or + TCons1(TypedContent tc, int len) { + len in [1 .. accessPathLimit()] and + expensiveLen2unfolding(tc, _) + } + +/** + * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only + * the first two elements of the list and its length are tracked. If data flows + * from a source to a given node with a given `AccessPathApprox`, this indicates + * the sequence of dereference operations needed to get from the value in the node + * to the tracked object. The final type indicates the type of the tracked object. + */ +abstract private class AccessPathApprox extends TAccessPathApprox { + abstract string toString(); + + abstract TypedContent getHead(); + + abstract int len(); + + abstract DataFlowType getType(); + + abstract AccessPathFront getFront(); + + /** Gets the access path obtained by popping `head` from this path, if any. */ + abstract AccessPathApprox pop(TypedContent head); +} + +private class AccessPathApproxNil extends AccessPathApprox, TNil { + private DataFlowType t; + + AccessPathApproxNil() { this = TNil(t) } + + override string toString() { result = concat(": " + ppReprType(t)) } + + override TypedContent getHead() { none() } + + override int len() { result = 0 } + + override DataFlowType getType() { result = t } + + override AccessPathFront getFront() { result = TFrontNil(t) } + + override AccessPathApprox pop(TypedContent head) { none() } +} + +abstract private class AccessPathApproxCons extends AccessPathApprox { } + +private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { + private TypedContent tc; + private DataFlowType t; + + AccessPathApproxConsNil() { this = TConsNil(tc, t) } + + override string toString() { + // The `concat` becomes "" if `ppReprType` has no result. + result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) + } + + override TypedContent getHead() { result = tc } + + override int len() { result = 1 } + + override DataFlowType getType() { result = tc.getContainerType() } + + override AccessPathFront getFront() { result = TFrontHead(tc) } + + override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } +} + +private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { + private TypedContent tc1; + private TypedContent tc2; + private int len; + + AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } + + override string toString() { + if len = 2 + then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" + else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" + } + + override TypedContent getHead() { result = tc1 } + + override int len() { result = len } + + override DataFlowType getType() { result = tc1.getContainerType() } + + override AccessPathFront getFront() { result = TFrontHead(tc1) } + + override AccessPathApprox pop(TypedContent head) { + head = tc1 and + ( + result = TConsCons(tc2, _, len - 1) + or + len = 2 and + result = TConsNil(tc2, _) + or + result = TCons1(tc2, len - 1) + ) + } +} + +private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { + private TypedContent tc; + private int len; + + AccessPathApproxCons1() { this = TCons1(tc, len) } + + override string toString() { + if len = 1 + then result = "[" + tc.toString() + "]" + else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + } + + override TypedContent getHead() { result = tc } + + override int len() { result = len } + + override DataFlowType getType() { result = tc.getContainerType() } + + override AccessPathFront getFront() { result = TFrontHead(tc) } + + override AccessPathApprox pop(TypedContent head) { + head = tc and + ( + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | + result = TConsCons(tc2, _, len - 1) + or + len = 2 and + result = TConsNil(tc2, _) + or + result = TCons1(tc2, len - 1) + ) + or + exists(DataFlowType t | + len = 1 and + Stage3::consCand(tc, TFrontNil(t), _) and + result = TNil(t) + ) + ) + } +} + +/** Gets the access path obtained by popping `tc` from `ap`, if any. */ +private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } + +/** Gets the access path obtained by pushing `tc` onto `ap`. */ +private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } + +private newtype TAccessPathApproxOption = + TAccessPathApproxNone() or + TAccessPathApproxSome(AccessPathApprox apa) + +private class AccessPathApproxOption extends TAccessPathApproxOption { + string toString() { + this = TAccessPathApproxNone() and result = "" + or + this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) + } +} + +private module Stage4 { + module PrevStage = Stage3; + + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(NodeEx node) { + PrevStage::revFlow(node, _) and result = TNil(node.getDataFlowType()) + } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccNone() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + checkCallContextCall(outercc, call, c) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c, innercc] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { + checkCallContextReturn(innercc, c, call) and + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = + getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), + node.getEnclosingCallable()) + } + + private predicate localStep( + NodeEx node1, NodeEx node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, RetNodeEx node1, NodeEx node2, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and + PrevStage::revFlow(node1, _, _, _, pragma[only_bind_into](config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgNodeEx node1, ParamNodeEx node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and + PrevStage::revFlow(node1, _, _, _, pragma[only_bind_into](config)) + } + + bindingset[node, ap] + private predicate filter(NodeEx node, Ap ap) { any() } + + // Type checking is not necessary here as it has already been done in stage 3. + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(NodeEx node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + bindingset[result, apa] + private ApApprox unbindApa(ApApprox apa) { + exists(ApApprox apa0 | + apa = pragma[only_bind_into](apa0) and result = pragma[only_bind_into](apa0) + ) + } + + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, RetNodeEx ret, NodeEx out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, ret.getEnclosingCallable(), _, + pragma[only_bind_into](config)) + } + + /** + * Holds if `node` is reachable with access path `ap` from a source in the + * configuration `config`. + * + * The call context `cc` records whether the node is reached through an + * argument in a call, and if so, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindApa(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + sourceNode(node, config) and + cc = ccNone() and + argAp = apNone() and + ap = getApNil(node) + or + exists(NodeEx mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil + ) + or + exists(NodeEx mid | + fwdFlow(mid, _, _, ap, pragma[only_bind_into](config)) and + flowCand(node, _, pragma[only_bind_into](config)) and + jumpStep(mid, node, config) and + cc = ccNone() and + argAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(mid, _, _, nil, pragma[only_bind_into](config)) and + flowCand(node, _, pragma[only_bind_into](config)) and + additionalJumpStep(mid, node, config) and + cc = ccNone() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + } + + pragma[nomagic] + private predicate fwdFlowStore( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, unbindApa(getApprox(ap1)), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } + + /** + * Holds if forward flow with access path `tail` reaches a store of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(TypedContent tc | + fwdFlowStore(_, tail, tc, _, _, _, config) and + tc.getContent() = c and + cons = apCons(tc, tail) + ) + } + + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, NodeEx node1, NodeEx node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParamNodeEx p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgNodeEx arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutNotFromArg( + NodeEx out, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists( + DataFlowCall call, RetNodeEx ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + ccOut = getCallContextReturn(inner, call, innercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, NodeEx out, Ap argAp, Ap ap, Configuration config + ) { + exists(RetNodeEx ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` + * and data might flow through the target callable and back out at `call`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered( + DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParamNodeEx p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, Ap ap2, Configuration config + ) { + fwdFlowStore(node1, ap1, tc, node2, _, _, config) and + ap2 = apCons(tc, ap1) and + fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config) + } + + private predicate readStepFwd( + NodeEx n1, Ap ap1, Content c, NodeEx n2, Ap ap2, Configuration config + ) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(pragma[only_bind_into](call), pragma[only_bind_into](cc), + pragma[only_bind_into](argAp), pragma[only_bind_into](argAp0), + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + + /** + * Holds if `node` with access path `ap` is part of a path from a source to a + * sink in the configuration `config`. + * + * The Boolean `toReturn` records whether the node must be returned from the + * enclosing callable in order to reach a sink, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + NodeEx node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + sinkNode(node, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(NodeEx mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, pragma[only_bind_into](config)) and + ap instanceof ApNil + ) + or + exists(NodeEx mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(NodeEx mid, ApNil nil | + fwdFlow(node, _, _, ap, pragma[only_bind_into](config)) and + additionalJumpStep(node, mid, config) and + revFlow(pragma[only_bind_into](mid), _, _, nil, pragma[only_bind_into](config)) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(NodeEx mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, NodeEx node, TypedContent tc, NodeEx mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } + + /** + * Holds if reverse flow with access path `tail` reaches a read of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(NodeEx mid, Ap tail0 | + revFlow(mid, _, _, tail, config) and + tail = pragma[only_bind_into](tail0) and + readStepFwd(_, cons, c, mid, tail0, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, RetNodeEx ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(NodeEx out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInNotToReturn( + ArgNodeEx arg, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgNodeEx arg, Ap returnAp, Ap ap, Configuration config + ) { + exists(ParamNodeEx p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. + */ + pragma[nomagic] + private predicate revFlowIsReturned( + DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + exists(RetNodeEx ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + NodeEx node1, Ap ap1, TypedContent tc, NodeEx node2, DataFlowType contentType, + Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType, config) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(NodeEx node1, Content c, NodeEx node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, pragma[only_bind_into](ap2), pragma[only_bind_into](config)) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, pragma[only_bind_into](ap2), _, _, _, _, _, + pragma[only_bind_into](config)) + ) + } + + predicate revFlow(NodeEx node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParamNodeEx p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParamNodeEx p, DataFlowCallable c, Ap ap, Configuration config) { + exists(RetNodeEx ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(pragma[only_bind_into](ret), true, apSome(_), pragma[only_bind_into](ap0), + pragma[only_bind_into](config)) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.getPosition() = pos and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNodeEx arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(NodeEx node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(NodeEx n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(NodeEx node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(NodeEx n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ +} + +bindingset[conf, result] +private Configuration unbindConf(Configuration conf) { + exists(Configuration c | result = pragma[only_bind_into](c) and conf = pragma[only_bind_into](c)) +} + +private predicate nodeMayUseSummary(NodeEx n, AccessPathApprox apa, Configuration config) { + exists(DataFlowCallable c, AccessPathApprox apa0 | + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and + n.getEnclosingCallable() = c + ) +} + +private newtype TSummaryCtx = + TSummaryCtxNone() or + TSummaryCtxSome(ParamNodeEx p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } + +/** + * A context for generating flow summaries. This represents flow entry through + * a specific parameter with an access path of a specific shape. + * + * Summaries are only created for parameters that may flow through. + */ +abstract private class SummaryCtx extends TSummaryCtx { + abstract string toString(); +} + +/** A summary context from which no flow summary can be generated. */ +private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { + override string toString() { result = "" } +} + +/** A summary context from which a flow summary can be generated. */ +private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { + private ParamNodeEx p; + private AccessPath ap; + + SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } + + int getParameterPos() { p.isParameterOf(_, result) } + + override string toString() { result = p + ": " + ap } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +/** + * Gets the number of length 2 access path approximations that correspond to `apa`. + */ +private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { + exists(TypedContent tc, int len | + tc = apa.getHead() and + len = apa.len() and + result = + strictcount(AccessPathFront apf | + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + config) + ) + ) +} + +private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { + result = + strictcount(NodeEx n | + Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config) + ) +} + +/** + * Holds if a length 2 access path approximation matching `apa` is expected + * to be expensive. + */ +private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { + exists(int aps, int nodes, int apLimit, int tupleLimit | + aps = count1to2unfold(apa, config) and + nodes = countNodesUsingAccessPath(apa, config) and + accessPathCostLimits(apLimit, tupleLimit) and + apLimit < aps and + tupleLimit < (aps - 1) * nodes + ) +} + +private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { + exists(TypedContent head | + apa.pop(head) = result and + Stage4::consCand(head, result, config) + ) +} + +/** + * Holds with `unfold = false` if a precise head-tail representation of `apa` is + * expected to be expensive. Holds with `unfold = true` otherwise. + */ +private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { + exists(int aps, int nodes, int apLimit, int tupleLimit | + aps = countPotentialAps(apa, config) and + nodes = countNodesUsingAccessPath(apa, config) and + accessPathCostLimits(apLimit, tupleLimit) and + if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true + ) +} + +/** + * Gets the number of `AccessPath`s that correspond to `apa`. + */ +private int countAps(AccessPathApprox apa, Configuration config) { + evalUnfold(apa, false, config) and + result = 1 and + (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) + or + evalUnfold(apa, false, config) and + result = count1to2unfold(apa, config) and + not expensiveLen1to2unfolding(apa, config) + or + evalUnfold(apa, true, config) and + result = countPotentialAps(apa, config) +} + +/** + * Gets the number of `AccessPath`s that would correspond to `apa` assuming + * that it is expanded to a precise head-tail representation. + */ +language[monotonicAggregates] +private int countPotentialAps(AccessPathApprox apa, Configuration config) { + apa instanceof AccessPathApproxNil and result = 1 + or + result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) +} + +private newtype TAccessPath = + TAccessPathNil(DataFlowType t) or + TAccessPathCons(TypedContent head, AccessPath tail) { + exists(AccessPathApproxCons apa | + not evalUnfold(apa, false, _) and + head = apa.getHead() and + tail.getApprox() = getATail(apa, _) + ) + } or + TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { + exists(AccessPathApproxCons apa | + evalUnfold(apa, false, _) and + not expensiveLen1to2unfolding(apa, _) and + apa.len() = len and + head1 = apa.getHead() and + head2 = getATail(apa, _).getHead() + ) + } or + TAccessPathCons1(TypedContent head, int len) { + exists(AccessPathApproxCons apa | + evalUnfold(apa, false, _) and + expensiveLen1to2unfolding(apa, _) and + apa.len() = len and + head = apa.getHead() + ) + } + +private newtype TPathNode = + TPathNodeMid(NodeEx node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { + // A PathNode is introduced by a source ... + Stage4::revFlow(node, config) and + sourceNode(node, config) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + ap = TAccessPathNil(node.getDataFlowType()) + or + // ... or a step from an existing PathNode to another node. + exists(PathNodeMid mid | + pathStep(mid, node, cc, sc, ap) and + pragma[only_bind_into](config) = mid.getConfiguration() and + Stage4::revFlow(node, _, _, ap.getApprox(), pragma[only_bind_into](config)) + ) + } or + TPathNodeSink(NodeEx node, Configuration config) { + sinkNode(node, pragma[only_bind_into](config)) and + Stage4::revFlow(node, pragma[only_bind_into](config)) and + ( + // A sink that is also a source ... + sourceNode(node, config) + or + // ... or a sink that can be reached from a source + exists(PathNodeMid mid | + pathStep(mid, node, _, _, TAccessPathNil(_)) and + pragma[only_bind_into](config) = mid.getConfiguration() + ) + ) + } + +/** + * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a + * source to a given node with a given `AccessPath`, this indicates the sequence + * of dereference operations needed to get from the value in the node to the + * tracked object. The final type indicates the type of the tracked object. + */ +abstract private class AccessPath extends TAccessPath { + /** Gets the head of this access path, if any. */ + abstract TypedContent getHead(); + + /** Gets the tail of this access path, if any. */ + abstract AccessPath getTail(); + + /** Gets the front of this access path. */ + abstract AccessPathFront getFront(); + + /** Gets the approximation of this access path. */ + abstract AccessPathApprox getApprox(); + + /** Gets the length of this access path. */ + abstract int length(); + + /** Gets a textual representation of this access path. */ + abstract string toString(); + + /** Gets the access path obtained by popping `tc` from this access path, if any. */ + final AccessPath pop(TypedContent tc) { + result = this.getTail() and + tc = this.getHead() + } + + /** Gets the access path obtained by pushing `tc` onto this access path. */ + final AccessPath push(TypedContent tc) { this = result.pop(tc) } +} + +private class AccessPathNil extends AccessPath, TAccessPathNil { + private DataFlowType t; + + AccessPathNil() { this = TAccessPathNil(t) } + + DataFlowType getType() { result = t } + + override TypedContent getHead() { none() } + + override AccessPath getTail() { none() } + + override AccessPathFrontNil getFront() { result = TFrontNil(t) } + + override AccessPathApproxNil getApprox() { result = TNil(t) } + + override int length() { result = 0 } + + override string toString() { result = concat(": " + ppReprType(t)) } +} + +private class AccessPathCons extends AccessPath, TAccessPathCons { + private TypedContent head; + private AccessPath tail; + + AccessPathCons() { this = TAccessPathCons(head, tail) } + + override TypedContent getHead() { result = head } + + override AccessPath getTail() { result = tail } + + override AccessPathFrontHead getFront() { result = TFrontHead(head) } + + override AccessPathApproxCons getApprox() { + result = TConsNil(head, tail.(AccessPathNil).getType()) + or + result = TConsCons(head, tail.getHead(), this.length()) + or + result = TCons1(head, this.length()) + } + + override int length() { result = 1 + tail.length() } + + private string toStringImpl(boolean needsSuffix) { + exists(DataFlowType t | + tail = TAccessPathNil(t) and + needsSuffix = false and + result = head.toString() + "]" + concat(" : " + ppReprType(t)) + ) + or + result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) + or + exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | + result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true + or + result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false + ) + or + exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | + result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true + or + result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false + ) + } + + override string toString() { + result = "[" + this.toStringImpl(true) + length().toString() + ")]" + or + result = "[" + this.toStringImpl(false) + } +} + +private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { + private TypedContent head1; + private TypedContent head2; + private int len; + + AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } + + override TypedContent getHead() { result = head1 } + + override AccessPath getTail() { + Stage4::consCand(head1, result.getApprox(), _) and + result.getHead() = head2 and + result.length() = len - 1 + } + + override AccessPathFrontHead getFront() { result = TFrontHead(head1) } + + override AccessPathApproxCons getApprox() { + result = TConsCons(head1, head2, len) or + result = TCons1(head1, len) + } + + override int length() { result = len } + + override string toString() { + if len = 2 + then result = "[" + head1.toString() + ", " + head2.toString() + "]" + else + result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" + } +} + +private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { + private TypedContent head; + private int len; + + AccessPathCons1() { this = TAccessPathCons1(head, len) } + + override TypedContent getHead() { result = head } + + override AccessPath getTail() { + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 + } + + override AccessPathFrontHead getFront() { result = TFrontHead(head) } + + override AccessPathApproxCons getApprox() { result = TCons1(head, len) } + + override int length() { result = len } + + override string toString() { + if len = 1 + then result = "[" + head.toString() + "]" + else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" + } +} + +/** + * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. + * Only those `PathNode`s that are reachable from a source are generated. + */ +class PathNode extends TPathNode { + /** Gets a textual representation of this element. */ + string toString() { none() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { none() } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + none() + } + + /** Gets the underlying `Node`. */ + final Node getNode() { this.(PathNodeImpl).getNodeEx().projectToNode() = result } + + /** Gets the associated configuration. */ + Configuration getConfiguration() { none() } + + private PathNode getASuccessorIfHidden() { + this.(PathNodeImpl).isHidden() and + result = this.(PathNodeImpl).getASuccessorImpl() + } + + /** Gets a successor of this node, if any. */ + final PathNode getASuccessor() { + result = this.(PathNodeImpl).getASuccessorImpl().getASuccessorIfHidden*() and + not this.(PathNodeImpl).isHidden() and + not result.(PathNodeImpl).isHidden() + } + + /** Holds if this node is a source. */ + predicate isSource() { none() } +} + +abstract private class PathNodeImpl extends PathNode { + abstract PathNode getASuccessorImpl(); + + abstract NodeEx getNodeEx(); + + predicate isHidden() { + hiddenNode(this.getNodeEx().asNode()) and + not this.isSource() and + not this instanceof PathNodeSink + or + this.getNodeEx() instanceof TNodeImplicitRead + } + + private string ppAp() { + this instanceof PathNodeSink and result = "" + or + exists(string s | s = this.(PathNodeMid).getAp().toString() | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + } + + override string toString() { result = this.getNodeEx().toString() + ppAp() } + + override string toStringWithContext() { result = this.getNodeEx().toString() + ppAp() + ppCtx() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +/** Holds if `n` can reach a sink. */ +private predicate directReach(PathNode n) { + n instanceof PathNodeSink or directReach(n.getASuccessor()) +} + +/** Holds if `n` can reach a sink or is used in a subpath. */ +private predicate reach(PathNode n) { directReach(n) or Subpaths::retReach(n) } + +/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */ +private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and directReach(n2) } + +private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2) + +/** + * Provides the query predicates needed to include a graph in a path-problem query. + */ +module PathGraph { + /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ + query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b and reach(b) } + + /** Holds if `n` is a node in the graph of data flow path explanations. */ + query predicate nodes(PathNode n, string key, string val) { + reach(n) and key = "semmle.label" and val = n.toString() + } + + query predicate subpaths = Subpaths::subpaths/4; +} + +/** + * An intermediate flow graph node. This is a triple consisting of a `Node`, + * a `CallContext`, and a `Configuration`. + */ +private class PathNodeMid extends PathNodeImpl, TPathNodeMid { + NodeEx node; + CallContext cc; + SummaryCtx sc; + AccessPath ap; + Configuration config; + + PathNodeMid() { this = TPathNodeMid(node, cc, sc, ap, config) } + + override NodeEx getNodeEx() { result = node } + + CallContext getCallContext() { result = cc } + + SummaryCtx getSummaryCtx() { result = sc } + + AccessPath getAp() { result = ap } + + override Configuration getConfiguration() { result = config } + + private PathNodeMid getSuccMid() { + pathStep(this, result.getNodeEx(), result.getCallContext(), result.getSummaryCtx(), + result.getAp()) and + result.getConfiguration() = unbindConf(this.getConfiguration()) + } + + override PathNodeImpl getASuccessorImpl() { + // an intermediate step to another intermediate node + result = getSuccMid() + or + // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges + exists(PathNodeMid mid, PathNodeSink sink | + mid = getSuccMid() and + mid.getNodeEx() = sink.getNodeEx() and + mid.getAp() instanceof AccessPathNil and + sink.getConfiguration() = unbindConf(mid.getConfiguration()) and + result = sink + ) + } + + override predicate isSource() { + sourceNode(node, config) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + ap instanceof AccessPathNil + } +} + +/** + * A flow graph node corresponding to a sink. This is disjoint from the + * intermediate nodes in order to uniquely correspond to a given sink by + * excluding the `CallContext`. + */ +private class PathNodeSink extends PathNodeImpl, TPathNodeSink { + NodeEx node; + Configuration config; + + PathNodeSink() { this = TPathNodeSink(node, config) } + + override NodeEx getNodeEx() { result = node } + + override Configuration getConfiguration() { result = config } + + override PathNode getASuccessorImpl() { none() } + + override predicate isSource() { sourceNode(node, config) } +} + +/** + * Holds if data may flow from `mid` to `node`. The last step in or out of + * a callable is recorded by `cc`. + */ +private predicate pathStep( + PathNodeMid mid, NodeEx node, CallContext cc, SummaryCtx sc, AccessPath ap +) { + exists(AccessPath ap0, NodeEx midnode, Configuration conf, LocalCallContext localCC | + midnode = mid.getNodeEx() and + conf = mid.getConfiguration() and + cc = mid.getCallContext() and + sc = mid.getSummaryCtx() and + localCC = + getLocalCallContext(pragma[only_bind_into](pragma[only_bind_out](cc)), + midnode.getEnclosingCallable()) and + ap0 = mid.getAp() + | + localFlowBigStep(midnode, node, true, _, conf, localCC) and + ap = ap0 + or + localFlowBigStep(midnode, node, false, ap.getFront(), conf, localCC) and + ap0 instanceof AccessPathNil + ) + or + jumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + ap = mid.getAp() + or + additionalJumpStep(mid.getNodeEx(), node, mid.getConfiguration()) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + mid.getAp() instanceof AccessPathNil and + ap = TAccessPathNil(node.getDataFlowType()) + or + exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and + sc = mid.getSummaryCtx() + or + exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and + sc = mid.getSummaryCtx() + or + pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp() + or + pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone + or + pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() +} + +pragma[nomagic] +private predicate pathReadStep( + PathNodeMid mid, NodeEx node, AccessPath ap0, TypedContent tc, CallContext cc +) { + ap0 = mid.getAp() and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNodeEx(), tc.getContent(), node, mid.getConfiguration()) and + cc = mid.getCallContext() +} + +pragma[nomagic] +private predicate pathStoreStep( + PathNodeMid mid, NodeEx node, AccessPath ap0, TypedContent tc, CallContext cc +) { + ap0 = mid.getAp() and + Stage4::storeStepCand(mid.getNodeEx(), _, tc, node, _, mid.getConfiguration()) and + cc = mid.getCallContext() +} + +private predicate pathOutOfCallable0( + PathNodeMid mid, ReturnPosition pos, CallContext innercc, AccessPathApprox apa, + Configuration config +) { + pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and + innercc = mid.getCallContext() and + innercc instanceof CallContextNoCall and + apa = mid.getAp().getApprox() and + config = mid.getConfiguration() +} + +pragma[nomagic] +private predicate pathOutOfCallable1( + PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, AccessPathApprox apa, + Configuration config +) { + exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | + pathOutOfCallable0(mid, pos, innercc, apa, config) and + c = pos.getCallable() and + kind = pos.getKind() and + resolveReturn(innercc, c, call) + | + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + ) +} + +pragma[noinline] +private NodeEx getAnOutNodeFlow( + ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config +) { + result.asNode() = kind.getAnOutNode(call) and + Stage4::revFlow(result, _, _, apa, config) +} + +/** + * Holds if data may flow from `mid` to `out`. The last step of this path + * is a return from a callable and is recorded by `cc`, if needed. + */ +pragma[noinline] +private predicate pathOutOfCallable(PathNodeMid mid, NodeEx out, CallContext cc) { + exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | + pathOutOfCallable1(mid, call, kind, cc, apa, config) and + out = getAnOutNodeFlow(kind, call, apa, config) + ) +} + +/** + * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. + */ +pragma[noinline] +private predicate pathIntoArg( + PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa +) { + exists(ArgNode arg | + arg = mid.getNodeEx().asNode() and + cc = mid.getCallContext() and + arg.argumentOf(call, i) and + ap = mid.getAp() and + apa = ap.getApprox() + ) +} + +pragma[noinline] +private predicate parameterCand( + DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config +) { + exists(ParamNodeEx p | + Stage4::revFlow(p, _, _, apa, config) and + p.isParameterOf(callable, i) + ) +} + +pragma[nomagic] +private predicate pathIntoCallable0( + PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call, + AccessPath ap +) { + exists(AccessPathApprox apa | + pathIntoArg(mid, i, outercc, call, ap, apa) and + callable = resolveCall(call, outercc) and + parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration()) + ) +} + +/** + * Holds if data may flow from `mid` to `p` through `call`. The contexts + * before and after entering the callable are `outercc` and `innercc`, + * respectively. + */ +private predicate pathIntoCallable( + PathNodeMid mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + DataFlowCall call +) { + exists(int i, DataFlowCallable callable, AccessPath ap | + pathIntoCallable0(mid, callable, i, outercc, call, ap) and + p.isParameterOf(callable, i) and + ( + sc = TSummaryCtxSome(p, ap) + or + not exists(TSummaryCtxSome(p, ap)) and + sc = TSummaryCtxNone() + ) + | + if recordDataFlowCallSite(call, callable) + then innercc = TSpecificCall(call) + else innercc = TSomeCall() + ) +} + +/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ +pragma[nomagic] +private predicate paramFlowsThrough( + ReturnKindExt kind, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, AccessPathApprox apa, + Configuration config +) { + exists(PathNodeMid mid, RetNodeEx ret, int pos | + mid.getNodeEx() = ret and + kind = ret.getKind() and + cc = mid.getCallContext() and + sc = mid.getSummaryCtx() and + config = mid.getConfiguration() and + ap = mid.getAp() and + apa = ap.getApprox() and + pos = sc.getParameterPos() and + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) +} + +pragma[nomagic] +private predicate pathThroughCallable0( + DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap, + AccessPathApprox apa +) { + exists(CallContext innercc, SummaryCtx sc | + pathIntoCallable(mid, _, cc, innercc, sc, call) and + paramFlowsThrough(kind, innercc, sc, ap, apa, unbindConf(mid.getConfiguration())) + ) +} + +/** + * Holds if data may flow from `mid` through a callable to the node `out`. + * The context `cc` is restored to its value prior to entering the callable. + */ +pragma[noinline] +private predicate pathThroughCallable(PathNodeMid mid, NodeEx out, CallContext cc, AccessPath ap) { + exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa | + pathThroughCallable0(call, mid, kind, cc, ap, apa) and + out = getAnOutNodeFlow(kind, call, apa, unbindConf(mid.getConfiguration())) + ) +} + +private module Subpaths { + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by + * `kind`, `sc`, `apout`, and `innercc`. + */ + pragma[nomagic] + private predicate subpaths01( + PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, + NodeEx out, AccessPath apout + ) { + pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and + pathIntoCallable(arg, par, _, innercc, sc, _) and + paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, + unbindConf(arg.getConfiguration())) + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple and `ret` is determined by + * `kind`, `sc`, `apout`, and `innercc`. + */ + pragma[nomagic] + private predicate subpaths02( + PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, + NodeEx out, AccessPath apout + ) { + subpaths01(arg, par, sc, innercc, kind, out, apout) and + out.asNode() = kind.getAnOutNode(_) + } + + pragma[nomagic] + private Configuration getPathNodeConf(PathNode n) { result = n.getConfiguration() } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple. + */ + pragma[nomagic] + private predicate subpaths03( + PathNode arg, ParamNodeEx par, PathNodeMid ret, NodeEx out, AccessPath apout + ) { + exists(SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind, RetNodeEx retnode | + subpaths02(arg, par, sc, innercc, kind, out, apout) and + ret.getNodeEx() = retnode and + kind = retnode.getKind() and + innercc = ret.getCallContext() and + sc = ret.getSummaryCtx() and + ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and + apout = ret.getAp() and + not ret.isHidden() + ) + } + + /** + * Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through + * a subpath between `par` and `ret` with the connecting edges `arg -> par` and + * `ret -> out` is summarized as the edge `arg -> out`. + */ + predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) { + exists(ParamNodeEx p, NodeEx o, AccessPath apout | + pragma[only_bind_into](arg).getASuccessor() = par and + pragma[only_bind_into](arg).getASuccessor() = out and + subpaths03(arg, p, ret, o, apout) and + par.getNodeEx() = p and + out.getNodeEx() = o and + out.getAp() = apout + ) + } + + /** + * Holds if `n` can reach a return node in a summarized subpath. + */ + predicate retReach(PathNode n) { + subpaths(_, _, n, _) + or + exists(PathNode mid | + retReach(mid) and + n.getASuccessor() = mid and + not subpaths(_, mid, _, _) + ) + } +} + +/** + * Holds if data can flow (inter-procedurally) from `source` to `sink`. + * + * Will only have results if `configuration` has non-empty sources and + * sinks. + */ +private predicate flowsTo( + PathNode flowsource, PathNodeSink flowsink, Node source, Node sink, Configuration configuration +) { + flowsource.isSource() and + flowsource.getConfiguration() = configuration and + flowsource.(PathNodeImpl).getNodeEx().asNode() = source and + (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and + flowsink.getNodeEx().asNode() = sink +} + +/** + * Holds if data can flow (inter-procedurally) from `source` to `sink`. + * + * Will only have results if `configuration` has non-empty sources and + * sinks. + */ +predicate flowsTo(Node source, Node sink, Configuration configuration) { + flowsTo(_, _, source, sink, configuration) +} + +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0)) and + fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(NodeEx n0 | exists(PathNodeImpl pn | pn.getNodeEx() = n0 and reach(pn))) and + fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and + tuples = count(PathNode pn | reach(pn)) +} + +/** + * INTERNAL: Only for debugging. + * + * Calculates per-stage metrics for data flow. + */ +predicate stageStats( + int n, string stage, int nodes, int fields, int conscand, int tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + +private module FlowExploration { + private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { + exists(NodeEx node1, NodeEx node2 | + jumpStep(node1, node2, config) + or + additionalJumpStep(node1, node2, config) + or + // flow into callable + viableParamArgEx(_, node2, node1) + or + // flow out of a callable + viableReturnPosOutEx(_, node1.(RetNodeEx).getReturnPosition(), node2) + | + c1 = node1.getEnclosingCallable() and + c2 = node2.getEnclosingCallable() and + c1 != c2 + ) + } + + private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { + exists(Node n | config.isSource(n) and c = getNodeEnclosingCallable(n)) + or + exists(DataFlowCallable mid | + interestingCallableSrc(mid, config) and callableStep(mid, c, config) + ) + } + + private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { + exists(Node n | config.isSink(n) and c = getNodeEnclosingCallable(n)) + or + exists(DataFlowCallable mid | + interestingCallableSink(mid, config) and callableStep(c, mid, config) + ) + } + + private newtype TCallableExt = + TCallable(DataFlowCallable c, Configuration config) { + interestingCallableSrc(c, config) or + interestingCallableSink(c, config) + } or + TCallableSrc() or + TCallableSink() + + private predicate callableExtSrc(TCallableSrc src) { any() } + + private predicate callableExtSink(TCallableSink sink) { any() } + + private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { + exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | + callableStep(c1, c2, config) and + ce1 = TCallable(c1, pragma[only_bind_into](config)) and + ce2 = TCallable(c2, pragma[only_bind_into](config)) + ) + or + exists(Node n, Configuration config | + ce1 = TCallableSrc() and + config.isSource(n) and + ce2 = TCallable(getNodeEnclosingCallable(n), config) + ) + or + exists(Node n, Configuration config | + ce2 = TCallableSink() and + config.isSink(n) and + ce1 = TCallable(getNodeEnclosingCallable(n), config) + ) + } + + private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { + callableExtStepFwd(ce2, ce1) + } + + private int distSrcExt(TCallableExt c) = + shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) + + private int distSinkExt(TCallableExt c) = + shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) + + private int distSrc(DataFlowCallable c, Configuration config) { + result = distSrcExt(TCallable(c, config)) - 1 + } + + private int distSink(DataFlowCallable c, Configuration config) { + result = distSinkExt(TCallable(c, config)) - 1 + } + + private newtype TPartialAccessPath = + TPartialNil(DataFlowType t) or + TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } + + /** + * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first + * element of the list and its length are tracked. If data flows from a source to + * a given node with a given `AccessPath`, this indicates the sequence of + * dereference operations needed to get from the value in the node to the + * tracked object. The final type indicates the type of the tracked object. + */ + private class PartialAccessPath extends TPartialAccessPath { + abstract string toString(); + + TypedContent getHead() { this = TPartialCons(result, _) } + + int len() { + this = TPartialNil(_) and result = 0 + or + this = TPartialCons(_, result) + } + + DataFlowType getType() { + this = TPartialNil(result) + or + exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) + } + } + + private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { + override string toString() { + exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) + } + } + + private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { + override string toString() { + exists(TypedContent tc, int len | this = TPartialCons(tc, len) | + if len = 1 + then result = "[" + tc.toString() + "]" + else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + ) + } + } + + private newtype TRevPartialAccessPath = + TRevPartialNil() or + TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } + + /** + * Conceptually a list of `Content`s, but only the first + * element of the list and its length are tracked. + */ + private class RevPartialAccessPath extends TRevPartialAccessPath { + abstract string toString(); + + Content getHead() { this = TRevPartialCons(result, _) } + + int len() { + this = TRevPartialNil() and result = 0 + or + this = TRevPartialCons(_, result) + } + } + + private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { + override string toString() { result = "" } + } + + private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { + override string toString() { + exists(Content c, int len | this = TRevPartialCons(c, len) | + if len = 1 + then result = "[" + c.toString() + "]" + else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" + ) + } + } + + private newtype TSummaryCtx1 = + TSummaryCtx1None() or + TSummaryCtx1Param(ParamNodeEx p) + + private newtype TSummaryCtx2 = + TSummaryCtx2None() or + TSummaryCtx2Some(PartialAccessPath ap) + + private newtype TRevSummaryCtx1 = + TRevSummaryCtx1None() or + TRevSummaryCtx1Some(ReturnPosition pos) + + private newtype TRevSummaryCtx2 = + TRevSummaryCtx2None() or + TRevSummaryCtx2Some(RevPartialAccessPath ap) + + private newtype TPartialPathNode = + TPartialPathNodeFwd( + NodeEx node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, + Configuration config + ) { + sourceNode(node, config) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + ap = TPartialNil(node.getDataFlowType()) and + not fullBarrier(node, config) and + exists(config.explorationLimit()) + or + partialPathNodeMk0(node, cc, sc1, sc2, ap, config) and + distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() + } or + TPartialPathNodeRev( + NodeEx node, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, RevPartialAccessPath ap, + Configuration config + ) { + sinkNode(node, config) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + ap = TRevPartialNil() and + not fullBarrier(node, config) and + exists(config.explorationLimit()) + or + exists(PartialPathNodeRev mid | + revPartialPathStep(mid, node, sc1, sc2, ap, config) and + not clearsContentCached(node.asNode(), ap.getHead()) and + not fullBarrier(node, config) and + distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() + ) + } + + pragma[nomagic] + private predicate partialPathNodeMk0( + NodeEx node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, + Configuration config + ) { + exists(PartialPathNodeFwd mid | + partialPathStep(mid, node, cc, sc1, sc2, ap, config) and + not fullBarrier(node, config) and + not clearsContentCached(node.asNode(), ap.getHead().getContent()) and + if node.asNode() instanceof CastingNode + then compatibleTypes(node.getDataFlowType(), ap.getType()) + else any() + ) + } + + /** + * A `Node` augmented with a call context, an access path, and a configuration. + */ + class PartialPathNode extends TPartialPathNode { + /** Gets a textual representation of this element. */ + string toString() { result = this.getNodeEx().toString() + this.ppAp() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { + result = this.getNodeEx().toString() + this.ppAp() + this.ppCtx() + } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.getNodeEx().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets the underlying `Node`. */ + final Node getNode() { this.getNodeEx().projectToNode() = result } + + private NodeEx getNodeEx() { + result = this.(PartialPathNodeFwd).getNodeEx() or + result = this.(PartialPathNodeRev).getNodeEx() + } + + /** Gets the associated configuration. */ + Configuration getConfiguration() { none() } + + /** Gets a successor of this node, if any. */ + PartialPathNode getASuccessor() { none() } + + /** + * Gets the approximate distance to the nearest source measured in number + * of interprocedural steps. + */ + int getSourceDistance() { + result = distSrc(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) + } + + /** + * Gets the approximate distance to the nearest sink measured in number + * of interprocedural steps. + */ + int getSinkDistance() { + result = distSink(this.getNodeEx().getEnclosingCallable(), this.getConfiguration()) + } + + private string ppAp() { + exists(string s | + s = this.(PartialPathNodeFwd).getAp().toString() or + s = this.(PartialPathNodeRev).getAp().toString() + | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" + } + + /** Holds if this is a source in a forward-flow path. */ + predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } + + /** Holds if this is a sink in a reverse-flow path. */ + predicate isRevSink() { this.(PartialPathNodeRev).isSink() } + } + + /** + * Provides the query predicates needed to include a graph in a path-problem query. + */ + module PartialPathGraph { + /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ + query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } + } + + private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { + NodeEx node; + CallContext cc; + TSummaryCtx1 sc1; + TSummaryCtx2 sc2; + PartialAccessPath ap; + Configuration config; + + PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, cc, sc1, sc2, ap, config) } + + NodeEx getNodeEx() { result = node } + + CallContext getCallContext() { result = cc } + + TSummaryCtx1 getSummaryCtx1() { result = sc1 } + + TSummaryCtx2 getSummaryCtx2() { result = sc2 } + + PartialAccessPath getAp() { result = ap } + + override Configuration getConfiguration() { result = config } + + override PartialPathNodeFwd getASuccessor() { + partialPathStep(this, result.getNodeEx(), result.getCallContext(), result.getSummaryCtx1(), + result.getSummaryCtx2(), result.getAp(), result.getConfiguration()) + } + + predicate isSource() { + sourceNode(node, config) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + ap instanceof TPartialNil + } + } + + private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { + NodeEx node; + TRevSummaryCtx1 sc1; + TRevSummaryCtx2 sc2; + RevPartialAccessPath ap; + Configuration config; + + PartialPathNodeRev() { this = TPartialPathNodeRev(node, sc1, sc2, ap, config) } + + NodeEx getNodeEx() { result = node } + + TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } + + TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } + + RevPartialAccessPath getAp() { result = ap } + + override Configuration getConfiguration() { result = config } + + override PartialPathNodeRev getASuccessor() { + revPartialPathStep(result, this.getNodeEx(), this.getSummaryCtx1(), this.getSummaryCtx2(), + this.getAp(), this.getConfiguration()) + } + + predicate isSink() { + sinkNode(node, config) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + ap = TRevPartialNil() + } + } + + private predicate partialPathStep( + PartialPathNodeFwd mid, NodeEx node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + PartialAccessPath ap, Configuration config + ) { + not isUnreachableInCallCached(node.asNode(), cc.(CallContextSpecificCall).getCall()) and + ( + localFlowStep(mid.getNodeEx(), node, config) and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + ap = mid.getAp() and + config = mid.getConfiguration() + or + additionalLocalFlowStep(mid.getNodeEx(), node, config) and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) and + config = mid.getConfiguration() + ) + or + jumpStep(mid.getNodeEx(), node, config) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + ap = mid.getAp() and + config = mid.getConfiguration() + or + additionalJumpStep(mid.getNodeEx(), node, config) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(node.getDataFlowType()) and + config = mid.getConfiguration() + or + partialPathStoreStep(mid, _, _, node, ap) and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + config = mid.getConfiguration() + or + exists(PartialAccessPath ap0, TypedContent tc | + partialPathReadStep(mid, ap0, tc, node, cc, config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + apConsFwd(ap, tc, ap0, config) + ) + or + partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) + or + partialPathOutOfCallable(mid, node, cc, ap, config) and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() + or + partialPathThroughCallable(mid, node, cc, ap, config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() + } + + bindingset[result, i] + private int unbindInt(int i) { i <= result and i >= result } + + pragma[inline] + private predicate partialPathStoreStep( + PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, NodeEx node, + PartialAccessPath ap2 + ) { + exists(NodeEx midNode, DataFlowType contentType | + midNode = mid.getNodeEx() and + ap1 = mid.getAp() and + store(midNode, tc, node, contentType, mid.getConfiguration()) and + ap2.getHead() = tc and + ap2.len() = unbindInt(ap1.len() + 1) and + compatibleTypes(ap1.getType(), contentType) + ) + } + + pragma[nomagic] + private predicate apConsFwd( + PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config + ) { + exists(PartialPathNodeFwd mid | + partialPathStoreStep(mid, ap1, tc, _, ap2) and + config = mid.getConfiguration() + ) + } + + pragma[nomagic] + private predicate partialPathReadStep( + PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, NodeEx node, CallContext cc, + Configuration config + ) { + exists(NodeEx midNode | + midNode = mid.getNodeEx() and + ap = mid.getAp() and + read(midNode, tc.getContent(), node, pragma[only_bind_into](config)) and + ap.getHead() = tc and + pragma[only_bind_into](config) = mid.getConfiguration() and + cc = mid.getCallContext() + ) + } + + private predicate partialPathOutOfCallable0( + PartialPathNodeFwd mid, ReturnPosition pos, CallContext innercc, PartialAccessPath ap, + Configuration config + ) { + pos = mid.getNodeEx().(RetNodeEx).getReturnPosition() and + innercc = mid.getCallContext() and + innercc instanceof CallContextNoCall and + ap = mid.getAp() and + config = mid.getConfiguration() + } + + pragma[nomagic] + private predicate partialPathOutOfCallable1( + PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, + PartialAccessPath ap, Configuration config + ) { + exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | + partialPathOutOfCallable0(mid, pos, innercc, ap, config) and + c = pos.getCallable() and + kind = pos.getKind() and + resolveReturn(innercc, c, call) + | + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + ) + } + + private predicate partialPathOutOfCallable( + PartialPathNodeFwd mid, NodeEx out, CallContext cc, PartialAccessPath ap, Configuration config + ) { + exists(ReturnKindExt kind, DataFlowCall call | + partialPathOutOfCallable1(mid, call, kind, cc, ap, config) + | + out.asNode() = kind.getAnOutNode(call) + ) + } + + pragma[noinline] + private predicate partialPathIntoArg( + PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, + Configuration config + ) { + exists(ArgNode arg | + arg = mid.getNodeEx().asNode() and + cc = mid.getCallContext() and + arg.argumentOf(call, i) and + ap = mid.getAp() and + config = mid.getConfiguration() + ) + } + + pragma[nomagic] + private predicate partialPathIntoCallable0( + PartialPathNodeFwd mid, DataFlowCallable callable, int i, CallContext outercc, + DataFlowCall call, PartialAccessPath ap, Configuration config + ) { + partialPathIntoArg(mid, i, outercc, call, ap, config) and + callable = resolveCall(call, outercc) + } + + private predicate partialPathIntoCallable( + PartialPathNodeFwd mid, ParamNodeEx p, CallContext outercc, CallContextCall innercc, + TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, + Configuration config + ) { + exists(int i, DataFlowCallable callable | + partialPathIntoCallable0(mid, callable, i, outercc, call, ap, config) and + p.isParameterOf(callable, i) and + sc1 = TSummaryCtx1Param(p) and + sc2 = TSummaryCtx2Some(ap) + | + if recordDataFlowCallSite(call, callable) + then innercc = TSpecificCall(call) + else innercc = TSomeCall() + ) + } + + pragma[nomagic] + private predicate paramFlowsThroughInPartialPath( + ReturnKindExt kind, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + PartialAccessPath ap, Configuration config + ) { + exists(PartialPathNodeFwd mid, RetNodeEx ret | + mid.getNodeEx() = ret and + kind = ret.getKind() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + config = mid.getConfiguration() and + ap = mid.getAp() + ) + } + + pragma[noinline] + private predicate partialPathThroughCallable0( + DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, + PartialAccessPath ap, Configuration config + ) { + exists(CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + partialPathIntoCallable(mid, _, cc, innercc, sc1, sc2, call, _, config) and + paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) + ) + } + + private predicate partialPathThroughCallable( + PartialPathNodeFwd mid, NodeEx out, CallContext cc, PartialAccessPath ap, Configuration config + ) { + exists(DataFlowCall call, ReturnKindExt kind | + partialPathThroughCallable0(call, mid, kind, cc, ap, config) and + out.asNode() = kind.getAnOutNode(call) + ) + } + + private predicate revPartialPathStep( + PartialPathNodeRev mid, NodeEx node, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, + RevPartialAccessPath ap, Configuration config + ) { + localFlowStep(node, mid.getNodeEx(), config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + ap = mid.getAp() and + config = mid.getConfiguration() + or + additionalLocalFlowStep(node, mid.getNodeEx(), config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() and + config = mid.getConfiguration() + or + jumpStep(node, mid.getNodeEx(), config) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + ap = mid.getAp() and + config = mid.getConfiguration() + or + additionalJumpStep(node, mid.getNodeEx(), config) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() and + config = mid.getConfiguration() + or + revPartialPathReadStep(mid, _, _, node, ap) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + config = mid.getConfiguration() + or + exists(RevPartialAccessPath ap0, Content c | + revPartialPathStoreStep(mid, ap0, c, node, config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + apConsRev(ap, c, ap0, config) + ) + or + exists(ParamNodeEx p | + mid.getNodeEx() = p and + viableParamArgEx(_, p, node) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + ap = mid.getAp() and + config = mid.getConfiguration() + ) + or + exists(ReturnPosition pos | + revPartialPathIntoReturn(mid, pos, sc1, sc2, _, ap, config) and + pos = getReturnPosition(node.asNode()) + ) + or + revPartialPathThroughCallable(mid, node, ap, config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() + } + + pragma[inline] + private predicate revPartialPathReadStep( + PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, NodeEx node, + RevPartialAccessPath ap2 + ) { + exists(NodeEx midNode | + midNode = mid.getNodeEx() and + ap1 = mid.getAp() and + read(node, c, midNode, mid.getConfiguration()) and + ap2.getHead() = c and + ap2.len() = unbindInt(ap1.len() + 1) + ) + } + + pragma[nomagic] + private predicate apConsRev( + RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config + ) { + exists(PartialPathNodeRev mid | + revPartialPathReadStep(mid, ap1, c, _, ap2) and + config = mid.getConfiguration() + ) + } + + pragma[nomagic] + private predicate revPartialPathStoreStep( + PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, NodeEx node, Configuration config + ) { + exists(NodeEx midNode, TypedContent tc | + midNode = mid.getNodeEx() and + ap = mid.getAp() and + store(node, tc, midNode, _, config) and + ap.getHead() = c and + config = mid.getConfiguration() and + tc.getContent() = c + ) + } + + pragma[nomagic] + private predicate revPartialPathIntoReturn( + PartialPathNodeRev mid, ReturnPosition pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, + DataFlowCall call, RevPartialAccessPath ap, Configuration config + ) { + exists(NodeEx out | + mid.getNodeEx() = out and + viableReturnPosOutEx(call, pos, out) and + sc1 = TRevSummaryCtx1Some(pos) and + sc2 = TRevSummaryCtx2Some(ap) and + ap = mid.getAp() and + config = mid.getConfiguration() + ) + } + + pragma[nomagic] + private predicate revPartialPathFlowsThrough( + int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, + Configuration config + ) { + exists(PartialPathNodeRev mid, ParamNodeEx p | + mid.getNodeEx() = p and + p.getPosition() = pos and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + ap = mid.getAp() and + config = mid.getConfiguration() + ) + } + + pragma[nomagic] + private predicate revPartialPathThroughCallable0( + DataFlowCall call, PartialPathNodeRev mid, int pos, RevPartialAccessPath ap, + Configuration config + ) { + exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2 | + revPartialPathIntoReturn(mid, _, sc1, sc2, call, _, config) and + revPartialPathFlowsThrough(pos, sc1, sc2, ap, config) + ) + } + + pragma[nomagic] + private predicate revPartialPathThroughCallable( + PartialPathNodeRev mid, ArgNodeEx node, RevPartialAccessPath ap, Configuration config + ) { + exists(DataFlowCall call, int pos | + revPartialPathThroughCallable0(call, mid, pos, ap, config) and + node.asNode().(ArgNode).argumentOf(call, pos) + ) + } +} + +import FlowExploration + +private predicate partialFlow( + PartialPathNode source, PartialPathNode node, Configuration configuration +) { + source.getConfiguration() = configuration and + source.isFwdSource() and + node = source.getASuccessor+() +} + +private predicate revPartialFlow( + PartialPathNode node, PartialPathNode sink, Configuration configuration +) { + sink.getConfiguration() = configuration and + sink.isRevSink() and + node.getASuccessor+() = sink +} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplCommon.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplCommon.qll new file mode 100644 index 000000000000..f588a25a1761 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplCommon.qll @@ -0,0 +1,1287 @@ +private import DataFlowImplSpecific::Private +private import DataFlowImplSpecific::Public +import Cached + +/** + * The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion. + * + * `apLimit` bounds the acceptable fan-out, and `tupleLimit` bounds the + * estimated per-`AccessPathFront` tuple cost. Access paths exceeding both of + * these limits are represented with lower precision during pruning. + */ +predicate accessPathApproxCostLimits(int apLimit, int tupleLimit) { + apLimit = 10 and + tupleLimit = 10000 +} + +/** + * The cost limits for the `AccessPathApprox` to `AccessPath` expansion. + * + * `apLimit` bounds the acceptable fan-out, and `tupleLimit` bounds the + * estimated per-`AccessPathApprox` tuple cost. Access paths exceeding both of + * these limits are represented with lower precision. + */ +predicate accessPathCostLimits(int apLimit, int tupleLimit) { + apLimit = 5 and + tupleLimit = 1000 +} + +/** + * Provides a simple data-flow analysis for resolving lambda calls. The analysis + * currently excludes read-steps, store-steps, and flow-through. + * + * The analysis uses non-linear recursion: When computing a flow path in or out + * of a call, we use the results of the analysis recursively to resolve lambda + * calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly. + */ +private module LambdaFlow { + private predicate viableParamNonLambda(DataFlowCall call, int i, ParamNode p) { + p.isParameterOf(viableCallable(call), i) + } + + private predicate viableParamLambda(DataFlowCall call, int i, ParamNode p) { + p.isParameterOf(viableCallableLambda(call, _), i) + } + + private predicate viableParamArgNonLambda(DataFlowCall call, ParamNode p, ArgNode arg) { + exists(int i | + viableParamNonLambda(call, i, p) and + arg.argumentOf(call, i) + ) + } + + private predicate viableParamArgLambda(DataFlowCall call, ParamNode p, ArgNode arg) { + exists(int i | + viableParamLambda(call, i, p) and + arg.argumentOf(call, i) + ) + } + + private newtype TReturnPositionSimple = + TReturnPositionSimple0(DataFlowCallable c, ReturnKind kind) { + exists(ReturnNode ret | + c = getNodeEnclosingCallable(ret) and + kind = ret.getKind() + ) + } + + pragma[noinline] + private TReturnPositionSimple getReturnPositionSimple(ReturnNode ret, ReturnKind kind) { + result = TReturnPositionSimple0(getNodeEnclosingCallable(ret), kind) + } + + pragma[nomagic] + private TReturnPositionSimple viableReturnPosNonLambda(DataFlowCall call, ReturnKind kind) { + result = TReturnPositionSimple0(viableCallable(call), kind) + } + + pragma[nomagic] + private TReturnPositionSimple viableReturnPosLambda( + DataFlowCall call, DataFlowCallOption lastCall, ReturnKind kind + ) { + result = TReturnPositionSimple0(viableCallableLambda(call, lastCall), kind) + } + + private predicate viableReturnPosOutNonLambda( + DataFlowCall call, TReturnPositionSimple pos, OutNode out + ) { + exists(ReturnKind kind | + pos = viableReturnPosNonLambda(call, kind) and + out = getAnOutNode(call, kind) + ) + } + + private predicate viableReturnPosOutLambda( + DataFlowCall call, DataFlowCallOption lastCall, TReturnPositionSimple pos, OutNode out + ) { + exists(ReturnKind kind | + pos = viableReturnPosLambda(call, lastCall, kind) and + out = getAnOutNode(call, kind) + ) + } + + /** + * Holds if data can flow (inter-procedurally) from `node` (of type `t`) to + * the lambda call `lambdaCall`. + * + * The parameter `toReturn` indicates whether the path from `node` to + * `lambdaCall` goes through a return, and `toJump` whether the path goes + * through a jump step. + * + * The call context `lastCall` records the last call on the path from `node` + * to `lambdaCall`, if any. That is, `lastCall` is able to target the enclosing + * callable of `lambdaCall`. + */ + pragma[nomagic] + predicate revLambdaFlow( + DataFlowCall lambdaCall, LambdaCallKind kind, Node node, DataFlowType t, boolean toReturn, + boolean toJump, DataFlowCallOption lastCall + ) { + revLambdaFlow0(lambdaCall, kind, node, t, toReturn, toJump, lastCall) and + if castNode(node) or node instanceof ArgNode or node instanceof ReturnNode + then compatibleTypes(t, getNodeDataFlowType(node)) + else any() + } + + pragma[nomagic] + predicate revLambdaFlow0( + DataFlowCall lambdaCall, LambdaCallKind kind, Node node, DataFlowType t, boolean toReturn, + boolean toJump, DataFlowCallOption lastCall + ) { + lambdaCall(lambdaCall, kind, node) and + t = getNodeDataFlowType(node) and + toReturn = false and + toJump = false and + lastCall = TDataFlowCallNone() + or + // local flow + exists(Node mid, DataFlowType t0 | + revLambdaFlow(lambdaCall, kind, mid, t0, toReturn, toJump, lastCall) + | + simpleLocalFlowStep(node, mid) and + t = t0 + or + exists(boolean preservesValue | + additionalLambdaFlowStep(node, mid, preservesValue) and + getNodeEnclosingCallable(node) = getNodeEnclosingCallable(mid) + | + preservesValue = false and + t = getNodeDataFlowType(node) + or + preservesValue = true and + t = t0 + ) + ) + or + // jump step + exists(Node mid, DataFlowType t0 | + revLambdaFlow(lambdaCall, kind, mid, t0, _, _, _) and + toReturn = false and + toJump = true and + lastCall = TDataFlowCallNone() + | + jumpStepCached(node, mid) and + t = t0 + or + exists(boolean preservesValue | + additionalLambdaFlowStep(node, mid, preservesValue) and + getNodeEnclosingCallable(node) != getNodeEnclosingCallable(mid) + | + preservesValue = false and + t = getNodeDataFlowType(node) + or + preservesValue = true and + t = t0 + ) + ) + or + // flow into a callable + exists(ParamNode p, DataFlowCallOption lastCall0, DataFlowCall call | + revLambdaFlowIn(lambdaCall, kind, p, t, toJump, lastCall0) and + ( + if lastCall0 = TDataFlowCallNone() and toJump = false + then lastCall = TDataFlowCallSome(call) + else lastCall = lastCall0 + ) and + toReturn = false + | + viableParamArgNonLambda(call, p, node) + or + viableParamArgLambda(call, p, node) // non-linear recursion + ) + or + // flow out of a callable + exists(TReturnPositionSimple pos | + revLambdaFlowOut(lambdaCall, kind, pos, t, toJump, lastCall) and + getReturnPositionSimple(node, node.(ReturnNode).getKind()) = pos and + toReturn = true + ) + } + + pragma[nomagic] + predicate revLambdaFlowOutLambdaCall( + DataFlowCall lambdaCall, LambdaCallKind kind, OutNode out, DataFlowType t, boolean toJump, + DataFlowCall call, DataFlowCallOption lastCall + ) { + revLambdaFlow(lambdaCall, kind, out, t, _, toJump, lastCall) and + exists(ReturnKindExt rk | + out = rk.getAnOutNode(call) and + lambdaCall(call, _, _) + ) + } + + pragma[nomagic] + predicate revLambdaFlowOut( + DataFlowCall lambdaCall, LambdaCallKind kind, TReturnPositionSimple pos, DataFlowType t, + boolean toJump, DataFlowCallOption lastCall + ) { + exists(DataFlowCall call, OutNode out | + revLambdaFlow(lambdaCall, kind, out, t, _, toJump, lastCall) and + viableReturnPosOutNonLambda(call, pos, out) + or + // non-linear recursion + revLambdaFlowOutLambdaCall(lambdaCall, kind, out, t, toJump, call, lastCall) and + viableReturnPosOutLambda(call, _, pos, out) + ) + } + + pragma[nomagic] + predicate revLambdaFlowIn( + DataFlowCall lambdaCall, LambdaCallKind kind, ParamNode p, DataFlowType t, boolean toJump, + DataFlowCallOption lastCall + ) { + revLambdaFlow(lambdaCall, kind, p, t, false, toJump, lastCall) + } +} + +private DataFlowCallable viableCallableExt(DataFlowCall call) { + result = viableCallable(call) + or + result = viableCallableLambda(call, _) +} + +cached +private module Cached { + /** + * If needed, call this predicate from `DataFlowImplSpecific.qll` in order to + * force a stage-dependency on the `DataFlowImplCommon.qll` stage and therby + * collapsing the two stages. + */ + cached + predicate forceCachingInSameStage() { any() } + + cached + predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = n.getEnclosingCallable() } + + cached + predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) { + c = call.getEnclosingCallable() + } + + cached + predicate nodeDataFlowType(Node n, DataFlowType t) { t = getNodeType(n) } + + cached + predicate jumpStepCached(Node node1, Node node2) { jumpStep(node1, node2) } + + cached + predicate clearsContentCached(Node n, Content c) { clearsContent(n, c) } + + cached + predicate isUnreachableInCallCached(Node n, DataFlowCall call) { isUnreachableInCall(n, call) } + + cached + predicate outNodeExt(Node n) { + n instanceof OutNode + or + n.(PostUpdateNode).getPreUpdateNode() instanceof ArgNode + } + + cached + predicate hiddenNode(Node n) { nodeIsHidden(n) } + + cached + OutNodeExt getAnOutNodeExt(DataFlowCall call, ReturnKindExt k) { + result = getAnOutNode(call, k.(ValueReturnKind).getKind()) + or + exists(ArgNode arg | + result.(PostUpdateNode).getPreUpdateNode() = arg and + arg.argumentOf(call, k.(ParamUpdateReturnKind).getPosition()) + ) + } + + cached + predicate returnNodeExt(Node n, ReturnKindExt k) { + k = TValueReturn(n.(ReturnNode).getKind()) + or + exists(ParamNode p, int pos | + parameterValueFlowsToPreUpdate(p, n) and + p.isParameterOf(_, pos) and + k = TParamUpdate(pos) + ) + } + + cached + predicate castNode(Node n) { n instanceof CastNode } + + cached + predicate castingNode(Node n) { + castNode(n) or + n instanceof ParamNode or + n instanceof OutNodeExt or + // For reads, `x.f`, we want to check that the tracked type after the read (which + // is obtained by popping the head of the access path stack) is compatible with + // the type of `x.f`. + read(_, _, n) + } + + cached + predicate parameterNode(Node n, DataFlowCallable c, int i) { + n.(ParameterNode).isParameterOf(c, i) + } + + cached + predicate argumentNode(Node n, DataFlowCall call, int pos) { + n.(ArgumentNode).argumentOf(call, pos) + } + + /** + * Gets a viable target for the lambda call `call`. + * + * `lastCall` records the call required to reach `call` in order for the result + * to be a viable target, if any. + */ + cached + DataFlowCallable viableCallableLambda(DataFlowCall call, DataFlowCallOption lastCall) { + exists(Node creation, LambdaCallKind kind | + LambdaFlow::revLambdaFlow(call, kind, creation, _, _, _, lastCall) and + lambdaCreation(creation, kind, result) + ) + } + + /** + * Holds if `p` is the `i`th parameter of a viable dispatch target of `call`. + * The instance parameter is considered to have index `-1`. + */ + pragma[nomagic] + private predicate viableParam(DataFlowCall call, int i, ParamNode p) { + p.isParameterOf(viableCallableExt(call), i) + } + + /** + * Holds if `arg` is a possible argument to `p` in `call`, taking virtual + * dispatch into account. + */ + cached + predicate viableParamArg(DataFlowCall call, ParamNode p, ArgNode arg) { + exists(int i | + viableParam(call, i, p) and + arg.argumentOf(call, i) and + compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(p)) + ) + } + + pragma[nomagic] + private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { + viableCallableExt(call) = result.getCallable() and + kind = result.getKind() + } + + /** + * Holds if a value at return position `pos` can be returned to `out` via `call`, + * taking virtual dispatch into account. + */ + cached + predicate viableReturnPosOut(DataFlowCall call, ReturnPosition pos, Node out) { + exists(ReturnKindExt kind | + pos = viableReturnPos(call, kind) and + out = kind.getAnOutNode(call) + ) + } + + /** Provides predicates for calculating flow-through summaries. */ + private module FlowThrough { + /** + * The first flow-through approximation: + * + * - Input access paths are abstracted with a Boolean parameter + * that indicates (non-)emptiness. + */ + private module Cand { + /** + * Holds if `p` can flow to `node` in the same callable using only + * value-preserving steps. + * + * `read` indicates whether it is contents of `p` that can flow to `node`. + */ + pragma[nomagic] + private predicate parameterValueFlowCand(ParamNode p, Node node, boolean read) { + p = node and + read = false + or + // local flow + exists(Node mid | + parameterValueFlowCand(p, mid, read) and + simpleLocalFlowStep(mid, node) + ) + or + // read + exists(Node mid | + parameterValueFlowCand(p, mid, false) and + read(mid, _, node) and + read = true + ) + or + // flow through: no prior read + exists(ArgNode arg | + parameterValueFlowArgCand(p, arg, false) and + argumentValueFlowsThroughCand(arg, node, read) + ) + or + // flow through: no read inside method + exists(ArgNode arg | + parameterValueFlowArgCand(p, arg, read) and + argumentValueFlowsThroughCand(arg, node, false) + ) + } + + pragma[nomagic] + private predicate parameterValueFlowArgCand(ParamNode p, ArgNode arg, boolean read) { + parameterValueFlowCand(p, arg, read) + } + + pragma[nomagic] + predicate parameterValueFlowsToPreUpdateCand(ParamNode p, PostUpdateNode n) { + parameterValueFlowCand(p, n.getPreUpdateNode(), false) + } + + /** + * Holds if `p` can flow to a return node of kind `kind` in the same + * callable using only value-preserving steps, not taking call contexts + * into account. + * + * `read` indicates whether it is contents of `p` that can flow to the return + * node. + */ + predicate parameterValueFlowReturnCand(ParamNode p, ReturnKind kind, boolean read) { + exists(ReturnNode ret | + parameterValueFlowCand(p, ret, read) and + kind = ret.getKind() + ) + } + + pragma[nomagic] + private predicate argumentValueFlowsThroughCand0( + DataFlowCall call, ArgNode arg, ReturnKind kind, boolean read + ) { + exists(ParamNode param | viableParamArg(call, param, arg) | + parameterValueFlowReturnCand(param, kind, read) + ) + } + + /** + * Holds if `arg` flows to `out` through a call using only value-preserving steps, + * not taking call contexts into account. + * + * `read` indicates whether it is contents of `arg` that can flow to `out`. + */ + predicate argumentValueFlowsThroughCand(ArgNode arg, Node out, boolean read) { + exists(DataFlowCall call, ReturnKind kind | + argumentValueFlowsThroughCand0(call, arg, kind, read) and + out = getAnOutNode(call, kind) + ) + } + + predicate cand(ParamNode p, Node n) { + parameterValueFlowCand(p, n, _) and + ( + parameterValueFlowReturnCand(p, _, _) + or + parameterValueFlowsToPreUpdateCand(p, _) + ) + } + } + + /** + * The final flow-through calculation: + * + * - Calculated flow is either value-preserving (`read = TReadStepTypesNone()`) + * or summarized as a single read step with before and after types recorded + * in the `ReadStepTypesOption` parameter. + * - Types are checked using the `compatibleTypes()` relation. + */ + private module Final { + /** + * Holds if `p` can flow to `node` in the same callable using only + * value-preserving steps and possibly a single read step, not taking + * call contexts into account. + * + * If a read step was taken, then `read` captures the `Content`, the + * container type, and the content type. + */ + predicate parameterValueFlow(ParamNode p, Node node, ReadStepTypesOption read) { + parameterValueFlow0(p, node, read) and + if node instanceof CastingNode + then + // normal flow through + read = TReadStepTypesNone() and + compatibleTypes(getNodeDataFlowType(p), getNodeDataFlowType(node)) + or + // getter + compatibleTypes(read.getContentType(), getNodeDataFlowType(node)) + else any() + } + + pragma[nomagic] + private predicate parameterValueFlow0(ParamNode p, Node node, ReadStepTypesOption read) { + p = node and + Cand::cand(p, _) and + read = TReadStepTypesNone() + or + // local flow + exists(Node mid | + parameterValueFlow(p, mid, read) and + simpleLocalFlowStep(mid, node) + ) + or + // read + exists(Node mid | + parameterValueFlow(p, mid, TReadStepTypesNone()) and + readStepWithTypes(mid, read.getContainerType(), read.getContent(), node, + read.getContentType()) and + Cand::parameterValueFlowReturnCand(p, _, true) and + compatibleTypes(getNodeDataFlowType(p), read.getContainerType()) + ) + or + parameterValueFlow0_0(TReadStepTypesNone(), p, node, read) + } + + pragma[nomagic] + private predicate parameterValueFlow0_0( + ReadStepTypesOption mustBeNone, ParamNode p, Node node, ReadStepTypesOption read + ) { + // flow through: no prior read + exists(ArgNode arg | + parameterValueFlowArg(p, arg, mustBeNone) and + argumentValueFlowsThrough(arg, read, node) + ) + or + // flow through: no read inside method + exists(ArgNode arg | + parameterValueFlowArg(p, arg, read) and + argumentValueFlowsThrough(arg, mustBeNone, node) + ) + } + + pragma[nomagic] + private predicate parameterValueFlowArg(ParamNode p, ArgNode arg, ReadStepTypesOption read) { + parameterValueFlow(p, arg, read) and + Cand::argumentValueFlowsThroughCand(arg, _, _) + } + + pragma[nomagic] + private predicate argumentValueFlowsThrough0( + DataFlowCall call, ArgNode arg, ReturnKind kind, ReadStepTypesOption read + ) { + exists(ParamNode param | viableParamArg(call, param, arg) | + parameterValueFlowReturn(param, kind, read) + ) + } + + /** + * Holds if `arg` flows to `out` through a call using only + * value-preserving steps and possibly a single read step, not taking + * call contexts into account. + * + * If a read step was taken, then `read` captures the `Content`, the + * container type, and the content type. + */ + pragma[nomagic] + predicate argumentValueFlowsThrough(ArgNode arg, ReadStepTypesOption read, Node out) { + exists(DataFlowCall call, ReturnKind kind | + argumentValueFlowsThrough0(call, arg, kind, read) and + out = getAnOutNode(call, kind) + | + // normal flow through + read = TReadStepTypesNone() and + compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(out)) + or + // getter + compatibleTypes(getNodeDataFlowType(arg), read.getContainerType()) and + compatibleTypes(read.getContentType(), getNodeDataFlowType(out)) + ) + } + + /** + * Holds if `arg` flows to `out` through a call using only + * value-preserving steps and a single read step, not taking call + * contexts into account, thus representing a getter-step. + */ + predicate getterStep(ArgNode arg, Content c, Node out) { + argumentValueFlowsThrough(arg, TReadStepTypesSome(_, c, _), out) + } + + /** + * Holds if `p` can flow to a return node of kind `kind` in the same + * callable using only value-preserving steps and possibly a single read + * step. + * + * If a read step was taken, then `read` captures the `Content`, the + * container type, and the content type. + */ + private predicate parameterValueFlowReturn( + ParamNode p, ReturnKind kind, ReadStepTypesOption read + ) { + exists(ReturnNode ret | + parameterValueFlow(p, ret, read) and + kind = ret.getKind() + ) + } + } + + import Final + } + + import FlowThrough + + cached + private module DispatchWithCallContext { + /** + * Holds if the set of viable implementations that can be called by `call` + * might be improved by knowing the call context. + */ + pragma[nomagic] + private predicate mayBenefitFromCallContextExt(DataFlowCall call, DataFlowCallable callable) { + mayBenefitFromCallContext(call, callable) + or + callEnclosingCallable(call, callable) and + exists(viableCallableLambda(call, TDataFlowCallSome(_))) + } + + /** + * Gets a viable dispatch target of `call` in the context `ctx`. This is + * restricted to those `call`s for which a context might make a difference. + */ + pragma[nomagic] + private DataFlowCallable viableImplInCallContextExt(DataFlowCall call, DataFlowCall ctx) { + result = viableImplInCallContext(call, ctx) + or + result = viableCallableLambda(call, TDataFlowCallSome(ctx)) + or + exists(DataFlowCallable enclosing | + mayBenefitFromCallContextExt(call, enclosing) and + enclosing = viableCallableExt(ctx) and + result = viableCallableLambda(call, TDataFlowCallNone()) + ) + } + + /** + * Holds if the call context `ctx` reduces the set of viable run-time + * dispatch targets of call `call` in `c`. + */ + cached + predicate reducedViableImplInCallContext(DataFlowCall call, DataFlowCallable c, DataFlowCall ctx) { + exists(int tgts, int ctxtgts | + mayBenefitFromCallContextExt(call, c) and + c = viableCallableExt(ctx) and + ctxtgts = count(viableImplInCallContextExt(call, ctx)) and + tgts = strictcount(viableCallableExt(call)) and + ctxtgts < tgts + ) + } + + /** + * Gets a viable run-time dispatch target for the call `call` in the + * context `ctx`. This is restricted to those calls for which a context + * makes a difference. + */ + cached + DataFlowCallable prunedViableImplInCallContext(DataFlowCall call, DataFlowCall ctx) { + result = viableImplInCallContextExt(call, ctx) and + reducedViableImplInCallContext(call, _, ctx) + } + + /** + * Holds if flow returning from callable `c` to call `call` might return + * further and if this path restricts the set of call sites that can be + * returned to. + */ + cached + predicate reducedViableImplInReturn(DataFlowCallable c, DataFlowCall call) { + exists(int tgts, int ctxtgts | + mayBenefitFromCallContextExt(call, _) and + c = viableCallableExt(call) and + ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContextExt(call, ctx)) and + tgts = strictcount(DataFlowCall ctx | callEnclosingCallable(call, viableCallableExt(ctx))) and + ctxtgts < tgts + ) + } + + /** + * Gets a viable run-time dispatch target for the call `call` in the + * context `ctx`. This is restricted to those calls and results for which + * the return flow from the result to `call` restricts the possible context + * `ctx`. + */ + cached + DataFlowCallable prunedViableImplInCallContextReverse(DataFlowCall call, DataFlowCall ctx) { + result = viableImplInCallContextExt(call, ctx) and + reducedViableImplInReturn(result, call) + } + } + + import DispatchWithCallContext + + /** + * Holds if `p` can flow to the pre-update node associated with post-update + * node `n`, in the same callable, using only value-preserving steps. + */ + private predicate parameterValueFlowsToPreUpdate(ParamNode p, PostUpdateNode n) { + parameterValueFlow(p, n.getPreUpdateNode(), TReadStepTypesNone()) + } + + private predicate store( + Node node1, Content c, Node node2, DataFlowType contentType, DataFlowType containerType + ) { + storeStep(node1, c, node2) and + contentType = getNodeDataFlowType(node1) and + containerType = getNodeDataFlowType(node2) + or + exists(Node n1, Node n2 | + n1 = node1.(PostUpdateNode).getPreUpdateNode() and + n2 = node2.(PostUpdateNode).getPreUpdateNode() + | + argumentValueFlowsThrough(n2, TReadStepTypesSome(containerType, c, contentType), n1) + or + read(n2, c, n1) and + contentType = getNodeDataFlowType(n1) and + containerType = getNodeDataFlowType(n2) + ) + } + + cached + predicate read(Node node1, Content c, Node node2) { readStep(node1, c, node2) } + + /** + * Holds if data can flow from `node1` to `node2` via a direct assignment to + * `f`. + * + * This includes reverse steps through reads when the result of the read has + * been stored into, in order to handle cases like `x.f1.f2 = y`. + */ + cached + predicate store(Node node1, TypedContent tc, Node node2, DataFlowType contentType) { + store(node1, tc.getContent(), node2, contentType, tc.getContainerType()) + } + + /** + * Holds if data can flow from `fromNode` to `toNode` because they are the post-update + * nodes of some function output and input respectively, where the output and input + * are aliases. A typical example is a function returning `this`, implementing a fluent + * interface. + */ + private predicate reverseStepThroughInputOutputAlias( + PostUpdateNode fromNode, PostUpdateNode toNode + ) { + exists(Node fromPre, Node toPre | + fromPre = fromNode.getPreUpdateNode() and + toPre = toNode.getPreUpdateNode() + | + exists(DataFlowCall c | + // Does the language-specific simpleLocalFlowStep already model flow + // from function input to output? + fromPre = getAnOutNode(c, _) and + toPre.(ArgNode).argumentOf(c, _) and + simpleLocalFlowStep(toPre.(ArgNode), fromPre) + ) + or + argumentValueFlowsThrough(toPre, TReadStepTypesNone(), fromPre) + ) + } + + cached + predicate simpleLocalFlowStepExt(Node node1, Node node2) { + simpleLocalFlowStep(node1, node2) or + reverseStepThroughInputOutputAlias(node1, node2) + } + + /** + * Holds if the call context `call` improves virtual dispatch in `callable`. + */ + cached + predicate recordDataFlowCallSiteDispatch(DataFlowCall call, DataFlowCallable callable) { + reducedViableImplInCallContext(_, callable, call) + } + + /** + * Holds if the call context `call` allows us to prune unreachable nodes in `callable`. + */ + cached + predicate recordDataFlowCallSiteUnreachable(DataFlowCall call, DataFlowCallable callable) { + exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call)) + } + + cached + newtype TCallContext = + TAnyCallContext() or + TSpecificCall(DataFlowCall call) { recordDataFlowCallSite(call, _) } or + TSomeCall() or + TReturn(DataFlowCallable c, DataFlowCall call) { reducedViableImplInReturn(c, call) } + + cached + newtype TReturnPosition = + TReturnPosition0(DataFlowCallable c, ReturnKindExt kind) { + exists(ReturnNodeExt ret | + c = returnNodeGetEnclosingCallable(ret) and + kind = ret.getKind() + ) + } + + cached + newtype TLocalFlowCallContext = + TAnyLocalCall() or + TSpecificLocalCall(DataFlowCall call) { isUnreachableInCallCached(_, call) } + + cached + newtype TReturnKindExt = + TValueReturn(ReturnKind kind) or + TParamUpdate(int pos) { exists(ParamNode p | p.isParameterOf(_, pos)) } + + cached + newtype TBooleanOption = + TBooleanNone() or + TBooleanSome(boolean b) { b = true or b = false } + + cached + newtype TDataFlowCallOption = + TDataFlowCallNone() or + TDataFlowCallSome(DataFlowCall call) + + cached + newtype TTypedContent = MkTypedContent(Content c, DataFlowType t) { store(_, c, _, _, t) } + + cached + newtype TAccessPathFront = + TFrontNil(DataFlowType t) or + TFrontHead(TypedContent tc) + + cached + newtype TAccessPathFrontOption = + TAccessPathFrontNone() or + TAccessPathFrontSome(AccessPathFront apf) +} + +/** + * Holds if the call context `call` either improves virtual dispatch in + * `callable` or if it allows us to prune unreachable nodes in `callable`. + */ +predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) { + recordDataFlowCallSiteDispatch(call, callable) or + recordDataFlowCallSiteUnreachable(call, callable) +} + +/** + * A `Node` at which a cast can occur such that the type should be checked. + */ +class CastingNode extends Node { + CastingNode() { castingNode(this) } +} + +private predicate readStepWithTypes( + Node n1, DataFlowType container, Content c, Node n2, DataFlowType content +) { + read(n1, c, n2) and + container = getNodeDataFlowType(n1) and + content = getNodeDataFlowType(n2) +} + +private newtype TReadStepTypesOption = + TReadStepTypesNone() or + TReadStepTypesSome(DataFlowType container, Content c, DataFlowType content) { + readStepWithTypes(_, container, c, _, content) + } + +private class ReadStepTypesOption extends TReadStepTypesOption { + predicate isSome() { this instanceof TReadStepTypesSome } + + DataFlowType getContainerType() { this = TReadStepTypesSome(result, _, _) } + + Content getContent() { this = TReadStepTypesSome(_, result, _) } + + DataFlowType getContentType() { this = TReadStepTypesSome(_, _, result) } + + string toString() { if this.isSome() then result = "Some(..)" else result = "None()" } +} + +/** + * A call context to restrict the targets of virtual dispatch, prune local flow, + * and match the call sites of flow into a method with flow out of a method. + * + * There are four cases: + * - `TAnyCallContext()` : No restrictions on method flow. + * - `TSpecificCall(DataFlowCall call)` : Flow entered through the + * given `call`. This call improves the set of viable + * dispatch targets for at least one method call in the current callable + * or helps prune unreachable nodes in the current callable. + * - `TSomeCall()` : Flow entered through a parameter. The + * originating call does not improve the set of dispatch targets for any + * method call in the current callable and was therefore not recorded. + * - `TReturn(Callable c, DataFlowCall call)` : Flow reached `call` from `c` and + * this dispatch target of `call` implies a reduced set of dispatch origins + * to which data may flow if it should reach a `return` statement. + */ +abstract class CallContext extends TCallContext { + abstract string toString(); + + /** Holds if this call context is relevant for `callable`. */ + abstract predicate relevantFor(DataFlowCallable callable); +} + +abstract class CallContextNoCall extends CallContext { } + +class CallContextAny extends CallContextNoCall, TAnyCallContext { + override string toString() { result = "CcAny" } + + override predicate relevantFor(DataFlowCallable callable) { any() } +} + +abstract class CallContextCall extends CallContext { + /** Holds if this call context may be `call`. */ + bindingset[call] + abstract predicate matchesCall(DataFlowCall call); +} + +class CallContextSpecificCall extends CallContextCall, TSpecificCall { + override string toString() { + exists(DataFlowCall call | this = TSpecificCall(call) | result = "CcCall(" + call + ")") + } + + override predicate relevantFor(DataFlowCallable callable) { + recordDataFlowCallSite(getCall(), callable) + } + + override predicate matchesCall(DataFlowCall call) { call = this.getCall() } + + DataFlowCall getCall() { this = TSpecificCall(result) } +} + +class CallContextSomeCall extends CallContextCall, TSomeCall { + override string toString() { result = "CcSomeCall" } + + override predicate relevantFor(DataFlowCallable callable) { + exists(ParamNode p | getNodeEnclosingCallable(p) = callable) + } + + override predicate matchesCall(DataFlowCall call) { any() } +} + +class CallContextReturn extends CallContextNoCall, TReturn { + override string toString() { + exists(DataFlowCall call | this = TReturn(_, call) | result = "CcReturn(" + call + ")") + } + + override predicate relevantFor(DataFlowCallable callable) { + exists(DataFlowCall call | this = TReturn(_, call) and callEnclosingCallable(call, callable)) + } +} + +/** + * A call context that is relevant for pruning local flow. + */ +abstract class LocalCallContext extends TLocalFlowCallContext { + abstract string toString(); + + /** Holds if this call context is relevant for `callable`. */ + abstract predicate relevantFor(DataFlowCallable callable); +} + +class LocalCallContextAny extends LocalCallContext, TAnyLocalCall { + override string toString() { result = "LocalCcAny" } + + override predicate relevantFor(DataFlowCallable callable) { any() } +} + +class LocalCallContextSpecificCall extends LocalCallContext, TSpecificLocalCall { + LocalCallContextSpecificCall() { this = TSpecificLocalCall(call) } + + DataFlowCall call; + + DataFlowCall getCall() { result = call } + + override string toString() { result = "LocalCcCall(" + call + ")" } + + override predicate relevantFor(DataFlowCallable callable) { relevantLocalCCtx(call, callable) } +} + +private predicate relevantLocalCCtx(DataFlowCall call, DataFlowCallable callable) { + exists(Node n | getNodeEnclosingCallable(n) = callable and isUnreachableInCallCached(n, call)) +} + +/** + * Gets the local call context given the call context and the callable that + * the contexts apply to. + */ +LocalCallContext getLocalCallContext(CallContext ctx, DataFlowCallable callable) { + ctx.relevantFor(callable) and + if relevantLocalCCtx(ctx.(CallContextSpecificCall).getCall(), callable) + then result.(LocalCallContextSpecificCall).getCall() = ctx.(CallContextSpecificCall).getCall() + else result instanceof LocalCallContextAny +} + +/** + * The value of a parameter at function entry, viewed as a node in a data + * flow graph. + */ +class ParamNode extends Node { + ParamNode() { parameterNode(this, _, _) } + + /** + * Holds if this node is the parameter of callable `c` at the specified + * (zero-based) position. + */ + predicate isParameterOf(DataFlowCallable c, int i) { parameterNode(this, c, i) } +} + +/** A data-flow node that represents a call argument. */ +class ArgNode extends Node { + ArgNode() { argumentNode(this, _, _) } + + /** Holds if this argument occurs at the given position in the given call. */ + final predicate argumentOf(DataFlowCall call, int pos) { argumentNode(this, call, pos) } +} + +/** + * A node from which flow can return to the caller. This is either a regular + * `ReturnNode` or a `PostUpdateNode` corresponding to the value of a parameter. + */ +class ReturnNodeExt extends Node { + ReturnNodeExt() { returnNodeExt(this, _) } + + /** Gets the kind of this returned value. */ + ReturnKindExt getKind() { returnNodeExt(this, result) } +} + +/** + * A node to which data can flow from a call. Either an ordinary out node + * or a post-update node associated with a call argument. + */ +class OutNodeExt extends Node { + OutNodeExt() { outNodeExt(this) } +} + +/** + * An extended return kind. A return kind describes how data can be returned + * from a callable. This can either be through a returned value or an updated + * parameter. + */ +abstract class ReturnKindExt extends TReturnKindExt { + /** Gets a textual representation of this return kind. */ + abstract string toString(); + + /** Gets a node corresponding to data flow out of `call`. */ + final OutNodeExt getAnOutNode(DataFlowCall call) { result = getAnOutNodeExt(call, this) } +} + +class ValueReturnKind extends ReturnKindExt, TValueReturn { + private ReturnKind kind; + + ValueReturnKind() { this = TValueReturn(kind) } + + ReturnKind getKind() { result = kind } + + override string toString() { result = kind.toString() } +} + +class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate { + private int pos; + + ParamUpdateReturnKind() { this = TParamUpdate(pos) } + + int getPosition() { result = pos } + + override string toString() { result = "param update " + pos } +} + +/** A callable tagged with a relevant return kind. */ +class ReturnPosition extends TReturnPosition0 { + private DataFlowCallable c; + private ReturnKindExt kind; + + ReturnPosition() { this = TReturnPosition0(c, kind) } + + /** Gets the callable. */ + DataFlowCallable getCallable() { result = c } + + /** Gets the return kind. */ + ReturnKindExt getKind() { result = kind } + + /** Gets a textual representation of this return position. */ + string toString() { result = "[" + kind + "] " + c } +} + +/** + * Gets the enclosing callable of `n`. Unlike `n.getEnclosingCallable()`, this + * predicate ensures that joins go from `n` to the result instead of the other + * way around. + */ +pragma[inline] +DataFlowCallable getNodeEnclosingCallable(Node n) { + nodeEnclosingCallable(pragma[only_bind_out](n), pragma[only_bind_into](result)) +} + +/** Gets the type of `n` used for type pruning. */ +pragma[inline] +DataFlowType getNodeDataFlowType(Node n) { + nodeDataFlowType(pragma[only_bind_out](n), pragma[only_bind_into](result)) +} + +pragma[noinline] +private DataFlowCallable returnNodeGetEnclosingCallable(ReturnNodeExt ret) { + result = getNodeEnclosingCallable(ret) +} + +pragma[noinline] +private ReturnPosition getReturnPosition0(ReturnNodeExt ret, ReturnKindExt kind) { + result.getCallable() = returnNodeGetEnclosingCallable(ret) and + kind = result.getKind() +} + +pragma[noinline] +ReturnPosition getReturnPosition(ReturnNodeExt ret) { + result = getReturnPosition0(ret, ret.getKind()) +} + +/** + * Checks whether `inner` can return to `call` in the call context `innercc`. + * Assumes a context of `inner = viableCallableExt(call)`. + */ +bindingset[innercc, inner, call] +predicate checkCallContextReturn(CallContext innercc, DataFlowCallable inner, DataFlowCall call) { + innercc instanceof CallContextAny + or + exists(DataFlowCallable c0, DataFlowCall call0 | + callEnclosingCallable(call0, inner) and + innercc = TReturn(c0, call0) and + c0 = prunedViableImplInCallContextReverse(call0, call) + ) +} + +/** + * Checks whether `call` can resolve to `calltarget` in the call context `cc`. + * Assumes a context of `calltarget = viableCallableExt(call)`. + */ +bindingset[cc, call, calltarget] +predicate checkCallContextCall(CallContext cc, DataFlowCall call, DataFlowCallable calltarget) { + exists(DataFlowCall ctx | cc = TSpecificCall(ctx) | + if reducedViableImplInCallContext(call, _, ctx) + then calltarget = prunedViableImplInCallContext(call, ctx) + else any() + ) + or + cc instanceof CallContextSomeCall + or + cc instanceof CallContextAny + or + cc instanceof CallContextReturn +} + +/** + * Resolves a return from `callable` in `cc` to `call`. This is equivalent to + * `callable = viableCallableExt(call) and checkCallContextReturn(cc, callable, call)`. + */ +bindingset[cc, callable] +predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall call) { + cc instanceof CallContextAny and callable = viableCallableExt(call) + or + exists(DataFlowCallable c0, DataFlowCall call0 | + callEnclosingCallable(call0, callable) and + cc = TReturn(c0, call0) and + c0 = prunedViableImplInCallContextReverse(call0, call) + ) +} + +/** + * Resolves a call from `call` in `cc` to `result`. This is equivalent to + * `result = viableCallableExt(call) and checkCallContextCall(cc, call, result)`. + */ +bindingset[call, cc] +DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) { + exists(DataFlowCall ctx | cc = TSpecificCall(ctx) | + if reducedViableImplInCallContext(call, _, ctx) + then result = prunedViableImplInCallContext(call, ctx) + else result = viableCallableExt(call) + ) + or + result = viableCallableExt(call) and cc instanceof CallContextSomeCall + or + result = viableCallableExt(call) and cc instanceof CallContextAny + or + result = viableCallableExt(call) and cc instanceof CallContextReturn +} + +/** An optional Boolean value. */ +class BooleanOption extends TBooleanOption { + string toString() { + this = TBooleanNone() and result = "" + or + this = TBooleanSome(any(boolean b | result = b.toString())) + } +} + +/** An optional `DataFlowCall`. */ +class DataFlowCallOption extends TDataFlowCallOption { + string toString() { + this = TDataFlowCallNone() and + result = "(none)" + or + exists(DataFlowCall call | + this = TDataFlowCallSome(call) and + result = call.toString() + ) + } +} + +/** Content tagged with the type of a containing object. */ +class TypedContent extends MkTypedContent { + private Content c; + private DataFlowType t; + + TypedContent() { this = MkTypedContent(c, t) } + + /** Gets the content. */ + Content getContent() { result = c } + + /** Gets the container type. */ + DataFlowType getContainerType() { result = t } + + /** Gets a textual representation of this content. */ + string toString() { result = c.toString() } +} + +/** + * The front of an access path. This is either a head or a nil. + */ +abstract class AccessPathFront extends TAccessPathFront { + abstract string toString(); + + abstract DataFlowType getType(); + + abstract boolean toBoolNonEmpty(); + + TypedContent getHead() { this = TFrontHead(result) } + + predicate isClearedAt(Node n) { clearsContentCached(n, getHead().getContent()) } +} + +class AccessPathFrontNil extends AccessPathFront, TFrontNil { + private DataFlowType t; + + AccessPathFrontNil() { this = TFrontNil(t) } + + override string toString() { result = ppReprType(t) } + + override DataFlowType getType() { result = t } + + override boolean toBoolNonEmpty() { result = false } +} + +class AccessPathFrontHead extends AccessPathFront, TFrontHead { + private TypedContent tc; + + AccessPathFrontHead() { this = TFrontHead(tc) } + + override string toString() { result = tc.toString() } + + override DataFlowType getType() { result = tc.getContainerType() } + + override boolean toBoolNonEmpty() { result = true } +} + +/** An optional access path front. */ +class AccessPathFrontOption extends TAccessPathFrontOption { + string toString() { + this = TAccessPathFrontNone() and result = "" + or + this = TAccessPathFrontSome(any(AccessPathFront apf | result = apf.toString())) + } +} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplConsistency.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplConsistency.qll new file mode 100644 index 000000000000..a55e65a81f69 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplConsistency.qll @@ -0,0 +1,181 @@ +/** + * Provides consistency queries for checking invariants in the language-specific + * data-flow classes and predicates. + */ + +private import DataFlowImplSpecific::Private +private import DataFlowImplSpecific::Public +private import tainttracking1.TaintTrackingParameter::Private +private import tainttracking1.TaintTrackingParameter::Public + +module Consistency { + private class RelevantNode extends Node { + RelevantNode() { + this instanceof ArgumentNode or + this instanceof ParameterNode or + this instanceof ReturnNode or + this = getAnOutNode(_, _) or + simpleLocalFlowStep(this, _) or + simpleLocalFlowStep(_, this) or + jumpStep(this, _) or + jumpStep(_, this) or + storeStep(this, _, _) or + storeStep(_, _, this) or + readStep(this, _, _) or + readStep(_, _, this) or + defaultAdditionalTaintStep(this, _) or + defaultAdditionalTaintStep(_, this) + } + } + + query predicate uniqueEnclosingCallable(Node n, string msg) { + exists(int c | + n instanceof RelevantNode and + c = count(n.getEnclosingCallable()) and + c != 1 and + msg = "Node should have one enclosing callable but has " + c + "." + ) + } + + query predicate uniqueType(Node n, string msg) { + exists(int c | + n instanceof RelevantNode and + c = count(getNodeType(n)) and + c != 1 and + msg = "Node should have one type but has " + c + "." + ) + } + + query predicate uniqueNodeLocation(Node n, string msg) { + exists(int c | + c = + count(string filepath, int startline, int startcolumn, int endline, int endcolumn | + n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + ) and + c != 1 and + msg = "Node should have one location but has " + c + "." + ) + } + + query predicate missingLocation(string msg) { + exists(int c | + c = + strictcount(Node n | + not exists(string filepath, int startline, int startcolumn, int endline, int endcolumn | + n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + ) + ) and + msg = "Nodes without location: " + c + ) + } + + query predicate uniqueNodeToString(Node n, string msg) { + exists(int c | + c = count(n.toString()) and + c != 1 and + msg = "Node should have one toString but has " + c + "." + ) + } + + query predicate missingToString(string msg) { + exists(int c | + c = strictcount(Node n | not exists(n.toString())) and + msg = "Nodes without toString: " + c + ) + } + + query predicate parameterCallable(ParameterNode p, string msg) { + exists(DataFlowCallable c | p.isParameterOf(c, _) and c != p.getEnclosingCallable()) and + msg = "Callable mismatch for parameter." + } + + query predicate localFlowIsLocal(Node n1, Node n2, string msg) { + simpleLocalFlowStep(n1, n2) and + n1.getEnclosingCallable() != n2.getEnclosingCallable() and + msg = "Local flow step does not preserve enclosing callable." + } + + private DataFlowType typeRepr() { result = getNodeType(_) } + + query predicate compatibleTypesReflexive(DataFlowType t, string msg) { + t = typeRepr() and + not compatibleTypes(t, t) and + msg = "Type compatibility predicate is not reflexive." + } + + query predicate unreachableNodeCCtx(Node n, DataFlowCall call, string msg) { + isUnreachableInCall(n, call) and + exists(DataFlowCallable c | + c = n.getEnclosingCallable() and + not viableCallable(call) = c + ) and + msg = "Call context for isUnreachableInCall is inconsistent with call graph." + } + + query predicate localCallNodes(DataFlowCall call, Node n, string msg) { + ( + n = getAnOutNode(call, _) and + msg = "OutNode and call does not share enclosing callable." + or + n.(ArgumentNode).argumentOf(call, _) and + msg = "ArgumentNode and call does not share enclosing callable." + ) and + n.getEnclosingCallable() != call.getEnclosingCallable() + } + + // This predicate helps the compiler forget that in some languages + // it is impossible for a result of `getPreUpdateNode` to be an + // instance of `PostUpdateNode`. + private Node getPre(PostUpdateNode n) { + result = n.getPreUpdateNode() + or + none() + } + + query predicate postIsNotPre(PostUpdateNode n, string msg) { + getPre(n) = n and + msg = "PostUpdateNode should not equal its pre-update node." + } + + query predicate postHasUniquePre(PostUpdateNode n, string msg) { + exists(int c | + c = count(n.getPreUpdateNode()) and + c != 1 and + msg = "PostUpdateNode should have one pre-update node but has " + c + "." + ) + } + + query predicate uniquePostUpdate(Node n, string msg) { + 1 < strictcount(PostUpdateNode post | post.getPreUpdateNode() = n) and + msg = "Node has multiple PostUpdateNodes." + } + + query predicate postIsInSameCallable(PostUpdateNode n, string msg) { + n.getEnclosingCallable() != n.getPreUpdateNode().getEnclosingCallable() and + msg = "PostUpdateNode does not share callable with its pre-update node." + } + + private predicate hasPost(Node n) { exists(PostUpdateNode post | post.getPreUpdateNode() = n) } + + query predicate reverseRead(Node n, string msg) { + exists(Node n2 | readStep(n, _, n2) and hasPost(n2) and not hasPost(n)) and + msg = "Origin of readStep is missing a PostUpdateNode." + } + + query predicate argHasPostUpdate(ArgumentNode n, string msg) { + not hasPost(n) and + not isImmutableOrUnobservable(n) and + msg = "ArgumentNode is missing PostUpdateNode." + } + + // This predicate helps the compiler forget that in some languages + // it is impossible for a `PostUpdateNode` to be the target of + // `simpleLocalFlowStep`. + private predicate isPostUpdateNode(Node n) { n instanceof PostUpdateNode or none() } + + query predicate postWithInFlow(Node n, string msg) { + isPostUpdateNode(n) and + simpleLocalFlowStep(_, n) and + msg = "PostUpdateNode should not be the target of local flow." + } +} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplSpecific.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplSpecific.qll new file mode 100644 index 000000000000..e78a0814a148 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplSpecific.qll @@ -0,0 +1,11 @@ +/** + * Provides Ruby-specific definitions for use in the data flow library. + */ +module Private { + import DataFlowPrivate + import DataFlowDispatch +} + +module Public { + import DataFlowPublic +} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll new file mode 100644 index 000000000000..90392f2eadf7 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll @@ -0,0 +1,705 @@ +private import ruby +private import codeql.ruby.CFG +private import codeql.ruby.dataflow.SSA +private import DataFlowPublic +private import DataFlowDispatch +private import SsaImpl as SsaImpl +private import FlowSummaryImpl as FlowSummaryImpl + +abstract class NodeImpl extends Node { + /** Do not call: use `getEnclosingCallable()` instead. */ + abstract CfgScope getCfgScope(); + + /** Do not call: use `getLocation()` instead. */ + abstract Location getLocationImpl(); + + /** Do not call: use `toString()` instead. */ + abstract string toStringImpl(); +} + +private class ExprNodeImpl extends ExprNode, NodeImpl { + override CfgScope getCfgScope() { result = this.getExprNode().getExpr().getCfgScope() } + + override Location getLocationImpl() { result = this.getExprNode().getLocation() } + + override string toStringImpl() { result = this.getExprNode().toString() } +} + +/** Provides predicates related to local data flow. */ +module LocalFlow { + private import codeql.ruby.dataflow.internal.SsaImpl + + /** + * Holds if `nodeFrom` is a last node referencing SSA definition `def`, which + * can reach `next`. + */ + private predicate localFlowSsaInput(Node nodeFrom, Ssa::Definition def, Ssa::Definition next) { + exists(BasicBlock bb, int i | lastRefBeforeRedef(def, bb, i, next) | + def = nodeFrom.(SsaDefinitionNode).getDefinition() and + def.definesAt(_, bb, i) + or + exists(CfgNodes::ExprCfgNode e | + e = nodeFrom.asExpr() and + e = bb.getNode(i) and + e.getExpr() instanceof VariableReadAccess + ) + ) + } + + /** + * Holds if there is a local flow step from `nodeFrom` to `nodeTo` involving + * SSA definition `def. + */ + predicate localSsaFlowStep(Ssa::Definition def, Node nodeFrom, Node nodeTo) { + // Flow from parameter into SSA definition + exists(BasicBlock bb, int i | + bb.getNode(i).getNode() = + nodeFrom.(ParameterNode).getParameter().(NamedParameter).getDefiningAccess() and + nodeTo.(SsaDefinitionNode).getDefinition().definesAt(_, bb, i) + ) + or + // Flow from assignment into SSA definition + def.(Ssa::WriteDefinition).assigns(nodeFrom.asExpr()) and + nodeTo.(SsaDefinitionNode).getDefinition() = def + or + // Flow from SSA definition to first read + def = nodeFrom.(SsaDefinitionNode).getDefinition() and + nodeTo.asExpr() = def.getAFirstRead() + or + // Flow from read to next read + exists( + CfgNodes::ExprNodes::VariableReadAccessCfgNode read1, + CfgNodes::ExprNodes::VariableReadAccessCfgNode read2 + | + def.hasAdjacentReads(read1, read2) and + nodeTo.asExpr() = read2 + | + nodeFrom.asExpr() = read1 + or + read1 = nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr() + ) + or + // Flow into phi node + exists(Ssa::PhiNode phi | + localFlowSsaInput(nodeFrom, def, phi) and + phi = nodeTo.(SsaDefinitionNode).getDefinition() and + def = phi.getAnInput() + ) + // TODO + // or + // // Flow into uncertain SSA definition + // exists(LocalFlow::UncertainExplicitSsaDefinition uncertain | + // localFlowSsaInput(nodeFrom, def, uncertain) and + // uncertain = nodeTo.(SsaDefinitionNode).getDefinition() and + // def = uncertain.getPriorDefinition() + // ) + } +} + +/** An argument of a call (including qualifier arguments). */ +private class Argument extends Expr { + private Call call; + private int arg; + + Argument() { this = call.getArgument(arg) } + + /** Holds if this expression is the `i`th argument of `c`. */ + predicate isArgumentOf(Expr c, int i) { c = call and i = arg } +} + +/** A collection of cached types and predicates to be evaluated in the same stage. */ +cached +private module Cached { + cached + newtype TNode = + TExprNode(CfgNodes::ExprCfgNode n) or + TReturningNode(CfgNodes::ReturningCfgNode n) or + TSsaDefinitionNode(Ssa::Definition def) or + TNormalParameterNode(Parameter p) { not p instanceof BlockParameter } or + TSelfParameterNode(MethodBase m) or + TBlockParameterNode(MethodBase m) or + TExprPostUpdateNode(CfgNodes::ExprCfgNode n) { + exists(AstNode node | node = n.getNode() | + node instanceof Argument and + not node instanceof BlockArgument + or + n = any(CfgNodes::ExprNodes::CallCfgNode call).getReceiver() + ) + } or + TSummaryNode( + FlowSummaryImpl::Public::SummarizedCallable c, + FlowSummaryImpl::Private::SummaryNodeState state + ) { + FlowSummaryImpl::Private::summaryNodeRange(c, state) + } or + TSummaryParameterNode(FlowSummaryImpl::Public::SummarizedCallable c, int i) { + FlowSummaryImpl::Private::summaryParameterNodeRange(c, i) + } + + class TParameterNode = + TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or TSummaryParameterNode; + + /** + * This is the local flow predicate that is used as a building block in global + * data flow. It excludes SSA flow through instance fields, as flow through fields + * is handled by the global data-flow library, but includes various other steps + * that are only relevant for global flow. + */ + cached + predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) { + exists(Ssa::Definition def | LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo)) + or + nodeTo.(ParameterNode).getParameter().(OptionalParameter).getDefaultValue() = + nodeFrom.asExpr().getExpr() + or + nodeTo.(ParameterNode).getParameter().(KeywordParameter).getDefaultValue() = + nodeFrom.asExpr().getExpr() + or + nodeFrom.(SelfParameterNode).getMethod() = nodeTo.asExpr().getExpr().getEnclosingCallable() and + nodeTo.asExpr().getExpr() instanceof Self + or + nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::AssignExprCfgNode).getRhs() + or + nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::BlockArgumentCfgNode).getValue() + or + nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::StmtSequenceCfgNode).getLastStmt() + or + nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::ConditionalExprCfgNode).getBranch(_) + or + nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::CaseExprCfgNode).getBranch(_) + or + exists(CfgNodes::ExprCfgNode exprTo, ReturningStatementNode n | + nodeFrom = n and + exprTo = nodeTo.asExpr() and + n.getReturningNode().getNode() instanceof BreakStmt and + exprTo.getNode() instanceof Loop and + nodeTo.asExpr().getAPredecessor(any(SuccessorTypes::BreakSuccessor s)) = n.getReturningNode() + ) + or + nodeFrom.asExpr() = nodeTo.(ReturningStatementNode).getReturningNode().getReturnedValueNode() + or + nodeTo.asExpr() = + any(CfgNodes::ExprNodes::ForExprCfgNode for | + exists(SuccessorType s | + not s instanceof SuccessorTypes::BreakSuccessor and + exists(for.getAPredecessor(s)) + ) and + nodeFrom.asExpr() = for.getValue() + ) + or + FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true) + } + + cached + predicate isLocalSourceNode(Node n) { not simpleLocalFlowStep+(any(ExprNode e), n) } + + cached + newtype TContent = TTodoContent() // stub +} + +import Cached + +/** Holds if `n` should be hidden from path explanations. */ +predicate nodeIsHidden(Node n) { + exists(Ssa::Definition def | def = n.(SsaDefinitionNode).getDefinition() | + def instanceof Ssa::PhiNode + ) + or + n instanceof SummaryNode + or + n instanceof SummaryParameterNode +} + +/** An SSA definition, viewed as a node in a data flow graph. */ +class SsaDefinitionNode extends NodeImpl, TSsaDefinitionNode { + Ssa::Definition def; + + SsaDefinitionNode() { this = TSsaDefinitionNode(def) } + + /** Gets the underlying SSA definition. */ + Ssa::Definition getDefinition() { result = def } + + override CfgScope getCfgScope() { result = def.getBasicBlock().getScope() } + + override Location getLocationImpl() { result = def.getLocation() } + + override string toStringImpl() { result = def.toString() } +} + +/** + * A value returning statement, viewed as a node in a data flow graph. + * + * Note that because of control-flow splitting, one `ReturningStmt` may correspond + * to multiple `ReturningStatementNode`s, just like it may correspond to multiple + * `ControlFlow::Node`s. + */ +class ReturningStatementNode extends NodeImpl, TReturningNode { + private CfgNodes::ReturningCfgNode n; + + ReturningStatementNode() { this = TReturningNode(n) } + + /** Gets the expression corresponding to this node. */ + CfgNodes::ReturningCfgNode getReturningNode() { result = n } + + override CfgScope getCfgScope() { result = n.getScope() } + + override Location getLocationImpl() { result = n.getLocation() } + + override string toStringImpl() { result = n.toString() } +} + +private module ParameterNodes { + abstract class ParameterNodeImpl extends ParameterNode, NodeImpl { + abstract predicate isSourceParameterOf(Callable c, int i); + + override predicate isParameterOf(DataFlowCallable c, int i) { + this.isSourceParameterOf(c.asCallable(), i) + } + } + + /** + * The value of a normal parameter at function entry, viewed as a node in a data + * flow graph. + */ + class NormalParameterNode extends ParameterNodeImpl, TNormalParameterNode { + private Parameter parameter; + + NormalParameterNode() { this = TNormalParameterNode(parameter) } + + override Parameter getParameter() { result = parameter } + + override predicate isSourceParameterOf(Callable c, int i) { c.getParameter(i) = parameter } + + override CfgScope getCfgScope() { result = parameter.getCallable() } + + override Location getLocationImpl() { result = parameter.getLocation() } + + override string toStringImpl() { result = parameter.toString() } + } + + /** + * The value of the `self` parameter at function entry, viewed as a node in a data + * flow graph. + */ + class SelfParameterNode extends ParameterNodeImpl, TSelfParameterNode { + private MethodBase method; + + SelfParameterNode() { this = TSelfParameterNode(method) } + + final MethodBase getMethod() { result = method } + + override predicate isSourceParameterOf(Callable c, int i) { method = c and i = -1 } + + override CfgScope getCfgScope() { result = method } + + override Location getLocationImpl() { result = method.getLocation() } + + override string toStringImpl() { result = "self in " + method.toString() } + } + + /** + * The value of a block parameter at function entry, viewed as a node in a data + * flow graph. + */ + class BlockParameterNode extends ParameterNodeImpl, TBlockParameterNode { + private MethodBase method; + + BlockParameterNode() { this = TBlockParameterNode(method) } + + final MethodBase getMethod() { result = method } + + override Parameter getParameter() { + result = method.getAParameter() and result instanceof BlockParameter + } + + override predicate isSourceParameterOf(Callable c, int i) { c = method and i = -2 } + + override CfgScope getCfgScope() { result = method } + + override Location getLocationImpl() { + result = getParameter().getLocation() + or + not exists(getParameter()) and result = method.getLocation() + } + + override string toStringImpl() { + result = getParameter().toString() + or + not exists(getParameter()) and result = "&block" + } + } + + /** A parameter for a library callable with a flow summary. */ + class SummaryParameterNode extends ParameterNodeImpl, TSummaryParameterNode { + private FlowSummaryImpl::Public::SummarizedCallable sc; + private int pos; + + SummaryParameterNode() { this = TSummaryParameterNode(sc, pos) } + + override predicate isSourceParameterOf(Callable c, int i) { none() } + + override predicate isParameterOf(DataFlowCallable c, int i) { sc = c and i = pos } + + override CfgScope getCfgScope() { none() } + + override DataFlowCallable getEnclosingCallable() { result = sc } + + override Location getLocationImpl() { none() } + + override string toStringImpl() { result = "parameter " + pos + " of " + sc } + } +} + +import ParameterNodes + +/** A data-flow node used to model flow summaries. */ +private class SummaryNode extends NodeImpl, TSummaryNode { + private FlowSummaryImpl::Public::SummarizedCallable c; + private FlowSummaryImpl::Private::SummaryNodeState state; + + SummaryNode() { this = TSummaryNode(c, state) } + + override CfgScope getCfgScope() { none() } + + override DataFlowCallable getEnclosingCallable() { result = c } + + override Location getLocationImpl() { none() } + + override string toStringImpl() { result = "[summary] " + state + " in " + c } +} + +/** A data-flow node that represents a call argument. */ +abstract class ArgumentNode extends Node { + /** Holds if this argument occurs at the given position in the given call. */ + predicate argumentOf(DataFlowCall call, int pos) { this.sourceArgumentOf(call.asCall(), pos) } + + abstract predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, int pos); + + /** Gets the call in which this node is an argument. */ + final DataFlowCall getCall() { this.argumentOf(result, _) } +} + +private module ArgumentNodes { + /** A data-flow node that represents an explicit call argument. */ + class ExplicitArgumentNode extends ArgumentNode { + ExplicitArgumentNode() { + this.asExpr().getExpr() instanceof Argument and + not this.asExpr().getExpr() instanceof BlockArgument + } + + override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, int pos) { + this.asExpr() = call.getArgument(pos) + } + } + + /** A data-flow node that represents the `self` argument of a call. */ + class SelfArgumentNode extends ArgumentNode { + SelfArgumentNode() { this.asExpr() = any(CfgNodes::ExprNodes::CallCfgNode call).getReceiver() } + + override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, int pos) { + this.asExpr() = call.getReceiver() and + pos = -1 + } + } + + /** A data-flow node that represents a block argument. */ + class BlockArgumentNode extends ArgumentNode { + BlockArgumentNode() { + this.asExpr().getExpr() instanceof BlockArgument or + exists(CfgNodes::ExprNodes::CallCfgNode c | c.getBlock() = this.asExpr()) + } + + override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, int pos) { + pos = -2 and + ( + this.asExpr() = call.getBlock() + or + exists(CfgNodes::ExprCfgNode arg, int n | + arg = call.getArgument(n) and + this.asExpr() = arg and + arg.getExpr() instanceof BlockArgument + ) + ) + } + } + + private class SummaryArgumentNode extends SummaryNode, ArgumentNode { + SummaryArgumentNode() { FlowSummaryImpl::Private::summaryArgumentNode(_, this, _) } + + override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, int pos) { none() } + + override predicate argumentOf(DataFlowCall call, int pos) { + FlowSummaryImpl::Private::summaryArgumentNode(call, this, pos) + } + } +} + +import ArgumentNodes + +/** A data-flow node that represents a value returned by a callable. */ +abstract class ReturnNode extends Node { + /** Gets the kind of this return node. */ + abstract ReturnKind getKind(); +} + +private module ReturnNodes { + private predicate isValid(CfgNodes::ReturningCfgNode node) { + exists(ReturningStmt stmt, Callable scope | + stmt = node.getNode() and + scope = node.getScope() + | + stmt instanceof ReturnStmt and + (scope instanceof Method or scope instanceof SingletonMethod or scope instanceof Lambda) + or + stmt instanceof NextStmt and + (scope instanceof Block or scope instanceof Lambda) + or + stmt instanceof BreakStmt and + (scope instanceof Block or scope instanceof Lambda) + ) + } + + /** + * A data-flow node that represents an expression returned by a callable, + * either using an explict `return` statement or as the expression of a method body. + */ + class ExplicitReturnNode extends ReturnNode, ReturningStatementNode { + private CfgNodes::ReturningCfgNode n; + + ExplicitReturnNode() { + isValid(this.getReturningNode()) and + n.getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and + n.getScope() instanceof Callable + } + + override ReturnKind getKind() { + if n.getNode() instanceof BreakStmt + then result instanceof BreakReturnKind + else result instanceof NormalReturnKind + } + } + + class ExprReturnNode extends ReturnNode, ExprNode { + ExprReturnNode() { + this.getExprNode().getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and + this.(NodeImpl).getCfgScope() instanceof Callable + } + + override ReturnKind getKind() { result instanceof NormalReturnKind } + } + + private class SummaryReturnNode extends SummaryNode, ReturnNode { + private ReturnKind rk; + + SummaryReturnNode() { FlowSummaryImpl::Private::summaryReturnNode(this, rk) } + + override ReturnKind getKind() { result = rk } + } +} + +import ReturnNodes + +/** A data-flow node that represents the output of a call. */ +abstract class OutNode extends Node { + /** Gets the underlying call, where this node is a corresponding output of kind `kind`. */ + abstract DataFlowCall getCall(ReturnKind kind); +} + +private module OutNodes { + /** + * A data-flow node that reads a value returned directly by a callable, + * either via a call or a `yield` of a block. + */ + class ExprOutNode extends OutNode, ExprNode { + private DataFlowCall call; + + ExprOutNode() { call.asCall() = this.getExprNode() } + + override DataFlowCall getCall(ReturnKind kind) { + result = call and + kind instanceof NormalReturnKind + } + } + + private class SummaryOutNode extends SummaryNode, OutNode { + SummaryOutNode() { FlowSummaryImpl::Private::summaryOutNode(_, this, _) } + + override DataFlowCall getCall(ReturnKind kind) { + FlowSummaryImpl::Private::summaryOutNode(result, this, kind) + } + } +} + +import OutNodes + +predicate jumpStep(Node pred, Node succ) { + SsaImpl::captureFlowIn(pred.(SsaDefinitionNode).getDefinition(), + succ.(SsaDefinitionNode).getDefinition()) + or + SsaImpl::captureFlowOut(pred.(SsaDefinitionNode).getDefinition(), + succ.(SsaDefinitionNode).getDefinition()) + or + exists(Self s, Method m | + s = succ.asExpr().getExpr() and + pred.(SelfParameterNode).getMethod() = m and + m = s.getEnclosingMethod() and + m != s.getEnclosingCallable() + ) + or + succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr() +} + +predicate storeStep(Node node1, Content c, Node node2) { + FlowSummaryImpl::Private::Steps::summaryStoreStep(node1, c, node2) +} + +predicate readStep(Node node1, Content c, Node node2) { + FlowSummaryImpl::Private::Steps::summaryReadStep(node1, c, node2) +} + +/** + * Holds if values stored inside content `c` are cleared at node `n`. For example, + * any value stored inside `f` is cleared at the pre-update node associated with `x` + * in `x.f = newValue`. + */ +predicate clearsContent(Node n, Content c) { + storeStep(_, c, n) + or + FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c) +} + +private newtype TDataFlowType = TTodoDataFlowType() + +class DataFlowType extends TDataFlowType { + string toString() { result = "" } +} + +/** Gets the type of `n` used for type pruning. */ +DataFlowType getNodeType(NodeImpl n) { any() } + +/** Gets a string representation of a `DataFlowType`. */ +string ppReprType(DataFlowType t) { result = t.toString() } + +/** + * Holds if `t1` and `t2` are compatible, that is, whether data can flow from + * a node of type `t1` to a node of type `t2`. + */ +pragma[inline] +predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { any() } + +/** + * A node associated with an object after an operation that might have + * changed its state. + * + * This can be either the argument to a callable after the callable returns + * (which might have mutated the argument), or the qualifier of a field after + * an update to the field. + * + * Nodes corresponding to AST elements, for example `ExprNode`, usually refer + * to the value before the update. + */ +abstract class PostUpdateNode extends Node { + /** Gets the node before the state update. */ + abstract Node getPreUpdateNode(); +} + +private module PostUpdateNodes { + class ExprPostUpdateNode extends PostUpdateNode, NodeImpl, TExprPostUpdateNode { + private CfgNodes::ExprCfgNode e; + + ExprPostUpdateNode() { this = TExprPostUpdateNode(e) } + + override ExprNode getPreUpdateNode() { e = result.getExprNode() } + + override CfgScope getCfgScope() { result = e.getExpr().getCfgScope() } + + override Location getLocationImpl() { result = e.getLocation() } + + override string toStringImpl() { result = "[post] " + e.toString() } + } + + private class SummaryPostUpdateNode extends SummaryNode, PostUpdateNode { + private Node pre; + + SummaryPostUpdateNode() { FlowSummaryImpl::Private::summaryPostUpdateNode(this, pre) } + + override Node getPreUpdateNode() { result = pre } + } +} + +private import PostUpdateNodes + +/** A node that performs a type cast. */ +class CastNode extends Node { + CastNode() { none() } +} + +class DataFlowExpr = CfgNodes::ExprCfgNode; + +int accessPathLimit() { result = 5 } + +/** The unit type. */ +private newtype TUnit = TMkUnit() + +/** The trivial type with a single element. */ +class Unit extends TUnit { + /** Gets a textual representation of this element. */ + string toString() { result = "unit" } +} + +/** + * Holds if `n` does not require a `PostUpdateNode` as it either cannot be + * modified or its modification cannot be observed, for example if it is a + * freshly created object that is not saved in a variable. + * + * This predicate is only used for consistency checks. + */ +predicate isImmutableOrUnobservable(Node n) { n instanceof BlockArgumentNode } + +/** + * Holds if the node `n` is unreachable when the call context is `call`. + */ +predicate isUnreachableInCall(Node n, DataFlowCall call) { none() } + +newtype LambdaCallKind = + TYieldCallKind() or + TLambdaCallKind() + +/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */ +predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { + kind = TYieldCallKind() and + creation.asExpr().getExpr() = c.asCallable().(Block) + or + kind = TLambdaCallKind() and + ( + creation.asExpr().getExpr() = c.asCallable().(Lambda) + or + creation.asExpr() = + any(CfgNodes::ExprNodes::MethodCallCfgNode mc | + c.asCallable() = mc.getBlock().getExpr() and + mc.getExpr().getMethodName() = "lambda" + ) + ) +} + +/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */ +predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { + kind = TYieldCallKind() and + receiver.(BlockParameterNode).getMethod() = + call.asCall().getExpr().(YieldCall).getEnclosingMethod() + or + kind = TLambdaCallKind() and + call.asCall() = + any(CfgNodes::ExprNodes::MethodCallCfgNode mc | + receiver.asExpr() = mc.getReceiver() and + mc.getExpr().getMethodName() = "call" + ) + or + receiver = call.(SummaryCall).getReceiver() and + if receiver.(ParameterNode).isParameterOf(_, -2) + then kind = TYieldCallKind() + else kind = TLambdaCallKind() +} + +/** Extra data-flow steps needed for lambda flow analysis. */ +predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll new file mode 100644 index 000000000000..6c73df7c2300 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll @@ -0,0 +1,216 @@ +private import ruby +private import DataFlowDispatch +private import DataFlowPrivate +private import codeql.ruby.CFG +private import codeql.ruby.typetracking.TypeTracker +private import codeql.ruby.dataflow.SSA +private import FlowSummaryImpl as FlowSummaryImpl + +/** + * An element, viewed as a node in a data flow graph. Either an expression + * (`ExprNode`) or a parameter (`ParameterNode`). + */ +class Node extends TNode { + /** Gets the expression corresponding to this node, if any. */ + CfgNodes::ExprCfgNode asExpr() { result = this.(ExprNode).getExprNode() } + + /** Gets the parameter corresponding to this node, if any. */ + Parameter asParameter() { result = this.(ParameterNode).getParameter() } + + /** Gets a textual representation of this node. */ + // TODO: cache + final string toString() { result = this.(NodeImpl).toStringImpl() } + + /** Gets the location of this node. */ + // TODO: cache + final Location getLocation() { result = this.(NodeImpl).getLocationImpl() } + + DataFlowCallable getEnclosingCallable() { result = TCfgScope(this.(NodeImpl).getCfgScope()) } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** + * Gets a local source node from which data may flow to this node in zero or more local data-flow steps. + */ + LocalSourceNode getALocalSource() { result.flowsTo(this) } +} + +/** A data-flow node corresponding to a call in the control-flow graph. */ +class CallNode extends LocalSourceNode { + private CfgNodes::ExprNodes::CallCfgNode node; + + CallNode() { node = this.asExpr() } + + /** Gets the data-flow node corresponding to the receiver of the call corresponding to this data-flow node */ + Node getReceiver() { result.asExpr() = node.getReceiver() } + + /** Gets the data-flow node corresponding to the `n`th argument of the call corresponding to this data-flow node */ + Node getArgument(int n) { result.asExpr() = node.getArgument(n) } + + /** Gets the data-flow node corresponding to the named argument of the call corresponding to this data-flow node */ + Node getKeywordArgument(string name) { result.asExpr() = node.getKeywordArgument(name) } +} + +/** + * An expression, viewed as a node in a data flow graph. + * + * Note that because of control-flow splitting, one `Expr` may correspond + * to multiple `ExprNode`s, just like it may correspond to multiple + * `ControlFlow::Node`s. + */ +class ExprNode extends Node, TExprNode { + private CfgNodes::ExprCfgNode n; + + ExprNode() { this = TExprNode(n) } + + /** Gets the expression corresponding to this node. */ + CfgNodes::ExprCfgNode getExprNode() { result = n } +} + +/** + * The value of a parameter at function entry, viewed as a node in a data + * flow graph. + */ +class ParameterNode extends Node, TParameterNode { + /** Gets the parameter corresponding to this node, if any. */ + Parameter getParameter() { none() } + + /** + * Holds if this node is the parameter of callable `c` at the specified + * (zero-based) position. + */ + predicate isParameterOf(DataFlowCallable c, int i) { none() } +} + +/** + * A data-flow node that is a source of local flow. + */ +class LocalSourceNode extends Node { + LocalSourceNode() { isLocalSourceNode(this) } + + /** Holds if this `LocalSourceNode` can flow to `nodeTo` in one or more local flow steps. */ + pragma[inline] + predicate flowsTo(Node nodeTo) { hasLocalSource(nodeTo, this) } + + /** + * Gets a node that this node may flow to using one heap and/or interprocedural step. + * + * See `TypeTracker` for more details about how to use this. + */ + pragma[inline] + LocalSourceNode track(TypeTracker t2, TypeTracker t) { t = t2.step(this, result) } +} + +predicate hasLocalSource(Node sink, Node source) { + // Declaring `source` to be a `SourceNode` currently causes a redundant check in the + // recursive case, so instead we check it explicitly here. + source = sink and + source instanceof LocalSourceNode + or + exists(Node mid | + hasLocalSource(mid, source) and + simpleLocalFlowStep(mid, sink) + ) +} + +/** Gets a node corresponding to expression `e`. */ +ExprNode exprNode(CfgNodes::ExprCfgNode e) { result.getExprNode() = e } + +/** + * Gets the node corresponding to the value of parameter `p` at function entry. + */ +ParameterNode parameterNode(Parameter p) { result.getParameter() = p } + +/** + * Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local + * (intra-procedural) step. + */ +predicate localFlowStep(Node nodeFrom, Node nodeTo) { + simpleLocalFlowStep(nodeFrom, nodeTo) + or + // Simple flow through library code is included in the exposed local + // step relation, even though flow is technically inter-procedural + FlowSummaryImpl::Private::Steps::summaryThroughStep(nodeFrom, nodeTo, true) +} + +/** + * Holds if data flows from `source` to `sink` in zero or more local + * (intra-procedural) steps. + */ +predicate localFlow(Node source, Node sink) { localFlowStep*(source, sink) } + +/** + * Holds if data can flow from `e1` to `e2` in zero or more + * local (intra-procedural) steps. + */ +predicate localExprFlow(CfgNodes::ExprCfgNode e1, CfgNodes::ExprCfgNode e2) { + localFlow(exprNode(e1), exprNode(e2)) +} + +/** + * A reference contained in an object. This is either a field, a property, + * or an element in a collection. + */ +class Content extends TContent { + /** Gets a textual representation of this content. */ + string toString() { none() } + + /** Gets the location of this content. */ + Location getLocation() { none() } +} + +/** + * A guard that validates some expression. + * + * To use this in a configuration, extend the class and provide a + * characteristic predicate precisely specifying the guard, and override + * `checks` to specify what is being validated and in which branch. + * + * It is important that all extending classes in scope are disjoint. + */ +abstract class BarrierGuard extends CfgNodes::ExprCfgNode { + private ConditionBlock conditionBlock; + + BarrierGuard() { this = conditionBlock.getLastNode() } + + /** Holds if this guard controls block `b` upon evaluating to `branch`. */ + private predicate controlsBlock(BasicBlock bb, boolean branch) { + exists(SuccessorTypes::BooleanSuccessor s | s.getValue() = branch | + conditionBlock.controls(bb, s) + ) + } + + /** + * Holds if this guard validates `expr` upon evaluating to `branch`. + * For example, the following code validates `foo` when the condition + * `foo == "foo"` is true. + * ```ruby + * if foo == "foo" + * do_something + * else + * do_something_else + * end + * ``` + */ + abstract predicate checks(CfgNode expr, boolean branch); + + final Node getAGuardedNode() { + exists(boolean branch, CfgNodes::ExprCfgNode testedNode, Ssa::Definition def | + def.getARead() = testedNode and + def.getARead() = result.asExpr() and + this.checks(testedNode, branch) and + this.controlsBlock(result.asExpr().getBasicBlock(), branch) + ) + } +} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll new file mode 100644 index 000000000000..523516e60f80 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll @@ -0,0 +1,827 @@ +/** + * Provides classes and predicates for defining flow summaries. + * + * The definitions in this file are language-independent, and language-specific + * definitions are passed in via the `DataFlowImplSpecific` and + * `FlowSummaryImplSpecific` modules. + */ + +private import FlowSummaryImplSpecific +private import DataFlowImplSpecific::Private +private import DataFlowImplSpecific::Public +private import DataFlowImplCommon + +/** Provides classes and predicates for defining flow summaries. */ +module Public { + private import Private + + /** + * A component used in a flow summary. + * + * Either a parameter or an argument at a given position, a specific + * content type, or a return kind. + */ + class SummaryComponent extends TSummaryComponent { + /** Gets a textual representation of this summary component. */ + string toString() { + exists(Content c | this = TContentSummaryComponent(c) and result = c.toString()) + or + exists(int i | this = TParameterSummaryComponent(i) and result = "parameter " + i) + or + exists(int i | this = TArgumentSummaryComponent(i) and result = "argument " + i) + or + exists(ReturnKind rk | this = TReturnSummaryComponent(rk) and result = "return (" + rk + ")") + } + } + + /** Provides predicates for constructing summary components. */ + module SummaryComponent { + /** Gets a summary component for content `c`. */ + SummaryComponent content(Content c) { result = TContentSummaryComponent(c) } + + /** Gets a summary component for parameter `i`. */ + SummaryComponent parameter(int i) { result = TParameterSummaryComponent(i) } + + /** Gets a summary component for argument `i`. */ + SummaryComponent argument(int i) { result = TArgumentSummaryComponent(i) } + + /** Gets a summary component for a return of kind `rk`. */ + SummaryComponent return(ReturnKind rk) { result = TReturnSummaryComponent(rk) } + } + + /** + * A (non-empty) stack of summary components. + * + * A stack is used to represent where data is read from (input) or where it + * is written to (output). For example, an input stack `[Field f, Argument 0]` + * means that data is read from field `f` from the `0`th argument, while an + * output stack `[Field g, Return]` means that data is written to the field + * `g` of the returned object. + */ + class SummaryComponentStack extends TSummaryComponentStack { + /** Gets the head of this stack. */ + SummaryComponent head() { + this = TSingletonSummaryComponentStack(result) or + this = TConsSummaryComponentStack(result, _) + } + + /** Gets the tail of this stack, if any. */ + SummaryComponentStack tail() { this = TConsSummaryComponentStack(_, result) } + + /** Gets the length of this stack. */ + int length() { + this = TSingletonSummaryComponentStack(_) and result = 1 + or + result = 1 + this.tail().length() + } + + /** Gets the stack obtained by dropping the first `i` elements, if any. */ + SummaryComponentStack drop(int i) { + i = 0 and result = this + or + result = this.tail().drop(i - 1) + } + + /** Holds if this stack contains summary component `c`. */ + predicate contains(SummaryComponent c) { c = this.drop(_).head() } + + /** Gets a textual representation of this stack. */ + string toString() { + exists(SummaryComponent head, SummaryComponentStack tail | + head = this.head() and + tail = this.tail() and + result = head + " of " + tail + ) + or + exists(SummaryComponent c | + this = TSingletonSummaryComponentStack(c) and + result = c.toString() + ) + } + } + + /** Provides predicates for constructing stacks of summary components. */ + module SummaryComponentStack { + /** Gets a singleton stack containing `c`. */ + SummaryComponentStack singleton(SummaryComponent c) { + result = TSingletonSummaryComponentStack(c) + } + + /** + * Gets the stack obtained by pushing `head` onto `tail`. + * + * Make sure to override `RequiredSummaryComponentStack::required()` in order + * to ensure that the constructed stack exists. + */ + SummaryComponentStack push(SummaryComponent head, SummaryComponentStack tail) { + result = TConsSummaryComponentStack(head, tail) + } + + /** Gets a singleton stack for argument `i`. */ + SummaryComponentStack argument(int i) { result = singleton(SummaryComponent::argument(i)) } + + /** Gets a singleton stack representing a return of kind `rk`. */ + SummaryComponentStack return(ReturnKind rk) { result = singleton(SummaryComponent::return(rk)) } + } + + /** + * A class that exists for QL technical reasons only (the IPA type used + * to represent component stacks needs to be bounded). + */ + abstract class RequiredSummaryComponentStack extends SummaryComponentStack { + /** + * Holds if the stack obtained by pushing `head` onto `tail` is required. + */ + abstract predicate required(SummaryComponent c); + } + + /** A callable with a flow summary. */ + abstract class SummarizedCallable extends DataFlowCallable { + /** + * Holds if data may flow from `input` to `output` through this callable. + * + * `preservesValue` indicates whether this is a value-preserving step + * or a taint-step. + * + * Input specifications are restricted to stacks that end with + * `SummaryComponent::argument(_)`, preceded by zero or more + * `SummaryComponent::return(_)` or `SummaryComponent::content(_)` components. + * + * Output specifications are restricted to stacks that end with + * `SummaryComponent::return(_)` or `SummaryComponent::argument(_)`. + * + * Output stacks ending with `SummaryComponent::return(_)` can be preceded by zero + * or more `SummaryComponent::content(_)` components. + * + * Output stacks ending with `SummaryComponent::argument(_)` can be preceded by an + * optional `SummaryComponent::parameter(_)` component, which in turn can be preceded + * by zero or more `SummaryComponent::content(_)` components. + */ + pragma[nomagic] + predicate propagatesFlow( + SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + ) { + none() + } + + /** + * Holds if values stored inside `content` are cleared on objects passed as + * the `i`th argument to this callable. + */ + pragma[nomagic] + predicate clearsContent(int i, Content content) { none() } + } +} + +/** + * Provides predicates for compiling flow summaries down to atomic local steps, + * read steps, and store steps. + */ +module Private { + private import Public + + newtype TSummaryComponent = + TContentSummaryComponent(Content c) or + TParameterSummaryComponent(int i) { parameterPosition(i) } or + TArgumentSummaryComponent(int i) { parameterPosition(i) } or + TReturnSummaryComponent(ReturnKind rk) + + newtype TSummaryComponentStack = + TSingletonSummaryComponentStack(SummaryComponent c) or + TConsSummaryComponentStack(SummaryComponent head, SummaryComponentStack tail) { + tail.(RequiredSummaryComponentStack).required(head) + } + + pragma[nomagic] + private predicate summary( + SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output, + boolean preservesValue + ) { + c.propagatesFlow(input, output, preservesValue) + } + + private newtype TSummaryNodeState = + TSummaryNodeInputState(SummaryComponentStack s) { + exists(SummaryComponentStack input | + summary(_, input, _, _) and + s = input.drop(_) + ) + } or + TSummaryNodeOutputState(SummaryComponentStack s) { + exists(SummaryComponentStack output | + summary(_, _, output, _) and + s = output.drop(_) + ) + } + + /** + * A state used to break up (complex) flow summaries into atomic flow steps. + * For a flow summary + * + * ```ql + * propagatesFlow( + * SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + * ) + * ``` + * + * the following states are used: + * + * - `TSummaryNodeInputState(SummaryComponentStack s)`: + * this state represents that the components in `s` _have been read_ from the + * input. + * - `TSummaryNodeOutputState(SummaryComponentStack s)`: + * this state represents that the components in `s` _remain to be written_ to + * the output. + */ + class SummaryNodeState extends TSummaryNodeState { + /** Holds if this state is a valid input state for `c`. */ + pragma[nomagic] + predicate isInputState(SummarizedCallable c, SummaryComponentStack s) { + this = TSummaryNodeInputState(s) and + exists(SummaryComponentStack input | + summary(c, input, _, _) and + s = input.drop(_) + ) + } + + /** Holds if this state is a valid output state for `c`. */ + pragma[nomagic] + predicate isOutputState(SummarizedCallable c, SummaryComponentStack s) { + this = TSummaryNodeOutputState(s) and + exists(SummaryComponentStack output | + summary(c, _, output, _) and + s = output.drop(_) + ) + } + + /** Gets a textual representation of this state. */ + string toString() { + exists(SummaryComponentStack s | + this = TSummaryNodeInputState(s) and + result = "read: " + s + ) + or + exists(SummaryComponentStack s | + this = TSummaryNodeOutputState(s) and + result = "to write: " + s + ) + } + } + + /** + * Holds if `state` represents having read the `i`th argument for `c`. In this case + * we are not synthesizing a data-flow node, but instead assume that a relevant + * parameter node already exists. + */ + private predicate parameterReadState(SummarizedCallable c, SummaryNodeState state, int i) { + state.isInputState(c, SummaryComponentStack::argument(i)) + } + + /** + * Holds if a synthesized summary node is needed for the state `state` in summarized + * callable `c`. + */ + predicate summaryNodeRange(SummarizedCallable c, SummaryNodeState state) { + state.isInputState(c, _) and + not parameterReadState(c, state, _) + or + state.isOutputState(c, _) + } + + pragma[noinline] + private Node summaryNodeInputState(SummarizedCallable c, SummaryComponentStack s) { + exists(SummaryNodeState state | state.isInputState(c, s) | + result = summaryNode(c, state) + or + exists(int i | + parameterReadState(c, state, i) and + result.(ParamNode).isParameterOf(c, i) + ) + ) + } + + pragma[noinline] + private Node summaryNodeOutputState(SummarizedCallable c, SummaryComponentStack s) { + exists(SummaryNodeState state | + state.isOutputState(c, s) and + result = summaryNode(c, state) + ) + } + + /** + * Holds if a write targets `post`, which is a post-update node for the `i`th + * parameter of `c`. + */ + private predicate isParameterPostUpdate(Node post, SummarizedCallable c, int i) { + post = summaryNodeOutputState(c, SummaryComponentStack::argument(i)) + } + + /** Holds if a parameter node is required for the `i`th parameter of `c`. */ + predicate summaryParameterNodeRange(SummarizedCallable c, int i) { + parameterReadState(c, _, i) + or + isParameterPostUpdate(_, c, i) + } + + private predicate callbackOutput( + SummarizedCallable c, SummaryComponentStack s, Node receiver, ReturnKind rk + ) { + any(SummaryNodeState state).isInputState(c, s) and + s.head() = TReturnSummaryComponent(rk) and + receiver = summaryNodeInputState(c, s.drop(1)) + } + + private Node pre(Node post) { + summaryPostUpdateNode(post, result) + or + not summaryPostUpdateNode(post, _) and + result = post + } + + private predicate callbackInput( + SummarizedCallable c, SummaryComponentStack s, Node receiver, int i + ) { + any(SummaryNodeState state).isOutputState(c, s) and + s.head() = TParameterSummaryComponent(i) and + receiver = pre(summaryNodeOutputState(c, s.drop(1))) + } + + /** Holds if a call targeting `receiver` should be synthesized inside `c`. */ + predicate summaryCallbackRange(SummarizedCallable c, Node receiver) { + callbackOutput(c, _, receiver, _) + or + callbackInput(c, _, receiver, _) + } + + /** + * Gets the type of synthesized summary node `n`. + * + * The type is computed based on the language-specific predicates + * `getContentType()`, `getReturnType()`, `getCallbackParameterType()`, and + * `getCallbackReturnType()`. + */ + DataFlowType summaryNodeType(Node n) { + exists(Node pre | + summaryPostUpdateNode(n, pre) and + result = getNodeType(pre) + ) + or + exists(SummarizedCallable c, SummaryComponentStack s, SummaryComponent head | head = s.head() | + n = summaryNodeInputState(c, s) and + ( + exists(Content cont | + head = TContentSummaryComponent(cont) and result = getContentType(cont) + ) + or + exists(ReturnKind rk | + head = TReturnSummaryComponent(rk) and + result = + getCallbackReturnType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c), + s.drop(1))), rk) + ) + ) + or + n = summaryNodeOutputState(c, s) and + ( + exists(Content cont | + head = TContentSummaryComponent(cont) and result = getContentType(cont) + ) + or + s.length() = 1 and + exists(ReturnKind rk | + head = TReturnSummaryComponent(rk) and + result = getReturnType(c, rk) + ) + or + exists(int i | head = TParameterSummaryComponent(i) | + result = + getCallbackParameterType(getNodeType(summaryNodeOutputState(pragma[only_bind_out](c), + s.drop(1))), i) + ) + ) + ) + } + + /** Holds if summary node `out` contains output of kind `rk` from call `c`. */ + predicate summaryOutNode(DataFlowCall c, Node out, ReturnKind rk) { + exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver | + callbackOutput(callable, s, receiver, rk) and + out = summaryNodeInputState(callable, s) and + c = summaryDataFlowCall(receiver) + ) + } + + /** Holds if summary node `arg` is the `i`th argument of call `c`. */ + predicate summaryArgumentNode(DataFlowCall c, Node arg, int i) { + exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver | + callbackInput(callable, s, receiver, i) and + arg = summaryNodeOutputState(callable, s) and + c = summaryDataFlowCall(receiver) + ) + } + + /** Holds if summary node `post` is a post-update node with pre-update node `pre`. */ + predicate summaryPostUpdateNode(Node post, ParamNode pre) { + exists(SummarizedCallable c, int i | + isParameterPostUpdate(post, c, i) and + pre.isParameterOf(c, i) + ) + } + + /** Holds if summary node `ret` is a return node of kind `rk`. */ + predicate summaryReturnNode(Node ret, ReturnKind rk) { + exists(SummarizedCallable callable, SummaryComponentStack s | + ret = summaryNodeOutputState(callable, s) and + s = TSingletonSummaryComponentStack(TReturnSummaryComponent(rk)) + ) + } + + /** Provides a compilation of flow summaries to atomic data-flow steps. */ + module Steps { + /** + * Holds if there is a local step from `pred` to `succ`, which is synthesized + * from a flow summary. + */ + predicate summaryLocalStep(Node pred, Node succ, boolean preservesValue) { + exists( + SummarizedCallable c, SummaryComponentStack inputContents, + SummaryComponentStack outputContents + | + summary(c, inputContents, outputContents, preservesValue) and + pred = summaryNodeInputState(c, inputContents) and + succ = summaryNodeOutputState(c, outputContents) + | + preservesValue = true + or + preservesValue = false and not summary(c, inputContents, outputContents, true) + ) + or + // If flow through a method updates a parameter from some input A, and that + // parameter also is returned through B, then we'd like a combined flow from A + // to B as well. As an example, this simplifies modeling of fluent methods: + // for `StringBuilder.append(x)` with a specified value flow from qualifier to + // return value and taint flow from argument 0 to the qualifier, then this + // allows us to infer taint flow from argument 0 to the return value. + summaryPostUpdateNode(pred, succ) and preservesValue = true + } + + /** + * Holds if there is a read step of content `c` from `pred` to `succ`, which + * is synthesized from a flow summary. + */ + predicate summaryReadStep(Node pred, Content c, Node succ) { + exists(SummarizedCallable sc, SummaryComponentStack s | + pred = summaryNodeInputState(sc, s.drop(1)) and + succ = summaryNodeInputState(sc, s) and + SummaryComponent::content(c) = s.head() + ) + } + + /** + * Holds if there is a store step of content `c` from `pred` to `succ`, which + * is synthesized from a flow summary. + */ + predicate summaryStoreStep(Node pred, Content c, Node succ) { + exists(SummarizedCallable sc, SummaryComponentStack s | + pred = summaryNodeOutputState(sc, s) and + succ = summaryNodeOutputState(sc, s.drop(1)) and + SummaryComponent::content(c) = s.head() + ) + } + + /** + * Holds if values stored inside content `c` are cleared when passed as + * input of type `input` in `call`. + */ + predicate summaryClearsContent(ArgNode arg, Content c) { + exists(DataFlowCall call, int i | + viableCallable(call).(SummarizedCallable).clearsContent(i, c) and + arg.argumentOf(call, i) + ) + } + + pragma[nomagic] + private ParamNode summaryArgParam(ArgNode arg, ReturnKindExt rk, OutNodeExt out) { + exists(DataFlowCall call, int pos, SummarizedCallable callable | + arg.argumentOf(call, pos) and + viableCallable(call) = callable and + result.isParameterOf(callable, pos) and + out = rk.getAnOutNode(call) + ) + } + + /** + * Holds if `arg` flows to `out` using a simple flow summary, that is, a flow + * summary without reads and stores. + * + * NOTE: This step should not be used in global data-flow/taint-tracking, but may + * be useful to include in the exposed local data-flow/taint-tracking relations. + */ + predicate summaryThroughStep(ArgNode arg, Node out, boolean preservesValue) { + exists(ReturnKindExt rk, ReturnNodeExt ret | + summaryLocalStep(summaryArgParam(arg, rk, out), ret, preservesValue) and + ret.getKind() = rk + ) + } + + /** + * Holds if there is a read(+taint) of `c` from `arg` to `out` using a + * flow summary. + * + * NOTE: This step should not be used in global data-flow/taint-tracking, but may + * be useful to include in the exposed local data-flow/taint-tracking relations. + */ + predicate summaryGetterStep(ArgNode arg, Content c, Node out) { + exists(ReturnKindExt rk, Node mid, ReturnNodeExt ret | + summaryReadStep(summaryArgParam(arg, rk, out), c, mid) and + summaryLocalStep(mid, ret, _) and + ret.getKind() = rk + ) + } + + /** + * Holds if there is a (taint+)store of `arg` into content `c` of `out` using a + * flow summary. + * + * NOTE: This step should not be used in global data-flow/taint-tracking, but may + * be useful to include in the exposed local data-flow/taint-tracking relations. + */ + predicate summarySetterStep(ArgNode arg, Content c, Node out) { + exists(ReturnKindExt rk, Node mid, ReturnNodeExt ret | + summaryLocalStep(summaryArgParam(arg, rk, out), mid, _) and + summaryStoreStep(mid, c, ret) and + ret.getKind() = rk + ) + } + + /** + * Holds if data is written into content `c` of argument `arg` using a flow summary. + * + * Depending on the type of `c`, this predicate may be relevant to include in the + * definition of `clearsContent()`. + */ + predicate summaryStoresIntoArg(Content c, Node arg) { + exists(ParamUpdateReturnKind rk, ReturnNodeExt ret, PostUpdateNode out | + exists(DataFlowCall call, SummarizedCallable callable | + getNodeEnclosingCallable(ret) = callable and + viableCallable(call) = callable and + summaryStoreStep(_, c, ret) and + ret.getKind() = pragma[only_bind_into](rk) and + out = rk.getAnOutNode(call) and + arg = out.getPreUpdateNode() + ) + ) + } + } + + /** + * Provides a means of translating externally (e.g., CSV) defined flow + * summaries into a `SummarizedCallable`s. + */ + module External { + /** Holds if `spec` is a relevant external specification. */ + private predicate relevantSpec(string spec) { + summaryElement(_, spec, _, _) or + summaryElement(_, _, spec, _) or + sourceElement(_, spec, _) or + sinkElement(_, spec, _) + } + + /** Holds if the `n`th component of specification `s` is `c`. */ + predicate specSplit(string s, string c, int n) { relevantSpec(s) and s.splitAt(" of ", n) = c } + + /** Holds if specification `s` has length `len`. */ + predicate specLength(string s, int len) { len = 1 + max(int n | specSplit(s, _, n)) } + + /** Gets the last component of specification `s`. */ + string specLast(string s) { + exists(int len | + specLength(s, len) and + specSplit(s, result, len - 1) + ) + } + + /** Holds if specification component `c` parses as parameter `n`. */ + predicate parseParam(string c, int n) { + specSplit(_, c, _) and + ( + c.regexpCapture("Parameter\\[([-0-9]+)\\]", 1).toInt() = n + or + exists(int n1, int n2 | + c.regexpCapture("Parameter\\[([-0-9]+)\\.\\.([0-9]+)\\]", 1).toInt() = n1 and + c.regexpCapture("Parameter\\[([-0-9]+)\\.\\.([0-9]+)\\]", 2).toInt() = n2 and + n = [n1 .. n2] + ) + ) + } + + /** Holds if specification component `c` parses as argument `n`. */ + predicate parseArg(string c, int n) { + specSplit(_, c, _) and + ( + c.regexpCapture("Argument\\[([-0-9]+)\\]", 1).toInt() = n + or + exists(int n1, int n2 | + c.regexpCapture("Argument\\[([-0-9]+)\\.\\.([0-9]+)\\]", 1).toInt() = n1 and + c.regexpCapture("Argument\\[([-0-9]+)\\.\\.([0-9]+)\\]", 2).toInt() = n2 and + n = [n1 .. n2] + ) + ) + } + + private SummaryComponent interpretComponent(string c) { + specSplit(_, c, _) and + ( + exists(int pos | parseArg(c, pos) and result = SummaryComponent::argument(pos)) + or + exists(int pos | parseParam(c, pos) and result = SummaryComponent::parameter(pos)) + or + c = "ReturnValue" and result = SummaryComponent::return(getReturnValueKind()) + or + result = interpretComponentSpecific(c) + ) + } + + /** + * Holds if `spec` specifies summary component stack `stack`. + */ + predicate interpretSpec(string spec, SummaryComponentStack stack) { + interpretSpec(spec, 0, stack) + } + + private predicate interpretSpec(string spec, int idx, SummaryComponentStack stack) { + exists(string c | + relevantSpec(spec) and + specLength(spec, idx + 1) and + specSplit(spec, c, idx) and + stack = SummaryComponentStack::singleton(interpretComponent(c)) + ) + or + exists(SummaryComponent head, SummaryComponentStack tail | + interpretSpec(spec, idx, head, tail) and + stack = SummaryComponentStack::push(head, tail) + ) + } + + private predicate interpretSpec( + string output, int idx, SummaryComponent head, SummaryComponentStack tail + ) { + exists(string c | + interpretSpec(output, idx + 1, tail) and + specSplit(output, c, idx) and + head = interpretComponent(c) + ) + } + + private class MkStack extends RequiredSummaryComponentStack { + MkStack() { interpretSpec(_, _, _, this) } + + override predicate required(SummaryComponent c) { interpretSpec(_, _, c, this) } + } + + private class SummarizedCallableExternal extends SummarizedCallable { + SummarizedCallableExternal() { summaryElement(this, _, _, _) } + + override predicate propagatesFlow( + SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + ) { + exists(string inSpec, string outSpec, string kind | + summaryElement(this, inSpec, outSpec, kind) and + interpretSpec(inSpec, input) and + interpretSpec(outSpec, output) + | + kind = "value" and preservesValue = true + or + kind = "taint" and preservesValue = false + ) + } + } + + /** Holds if component `c` of specification `spec` cannot be parsed. */ + predicate invalidSpecComponent(string spec, string c) { + specSplit(spec, c, _) and + not exists(interpretComponent(c)) + } + + private predicate inputNeedsReference(string c) { + c = "Argument" or + parseArg(c, _) + } + + private predicate outputNeedsReference(string c) { + c = "Argument" or + parseArg(c, _) or + c = "ReturnValue" + } + + private predicate sourceElementRef(InterpretNode ref, string output, string kind) { + exists(SourceOrSinkElement e | + sourceElement(e, output, kind) and + if outputNeedsReference(specLast(output)) + then e = ref.getCallTarget() + else e = ref.asElement() + ) + } + + private predicate sinkElementRef(InterpretNode ref, string input, string kind) { + exists(SourceOrSinkElement e | + sinkElement(e, input, kind) and + if inputNeedsReference(specLast(input)) + then e = ref.getCallTarget() + else e = ref.asElement() + ) + } + + private predicate interpretOutput(string output, int idx, InterpretNode ref, InterpretNode node) { + sourceElementRef(ref, output, _) and + specLength(output, idx) and + node = ref + or + exists(InterpretNode mid, string c | + interpretOutput(output, idx + 1, ref, mid) and + specSplit(output, c, idx) + | + exists(int pos | + node.asNode().(PostUpdateNode).getPreUpdateNode().(ArgNode).argumentOf(mid.asCall(), pos) + | + c = "Argument" or parseArg(c, pos) + ) + or + exists(int pos | node.asNode().(ParamNode).isParameterOf(mid.asCallable(), pos) | + c = "Parameter" or parseParam(c, pos) + ) + or + c = "ReturnValue" and + node.asNode() = getAnOutNodeExt(mid.asCall(), TValueReturn(getReturnValueKind())) + or + interpretOutputSpecific(c, mid, node) + ) + } + + private predicate interpretInput(string input, int idx, InterpretNode ref, InterpretNode node) { + sinkElementRef(ref, input, _) and + specLength(input, idx) and + node = ref + or + exists(InterpretNode mid, string c | + interpretInput(input, idx + 1, ref, mid) and + specSplit(input, c, idx) + | + exists(int pos | node.asNode().(ArgNode).argumentOf(mid.asCall(), pos) | + c = "Argument" or parseArg(c, pos) + ) + or + exists(ReturnNodeExt ret | + c = "ReturnValue" and + ret = node.asNode() and + ret.getKind().(ValueReturnKind).getKind() = getReturnValueKind() and + mid.asCallable() = getNodeEnclosingCallable(ret) + ) + or + interpretInputSpecific(c, mid, node) + ) + } + + /** + * Holds if `node` is specified as a source with the given kind in a CSV flow + * model. + */ + predicate isSourceNode(InterpretNode node, string kind) { + exists(InterpretNode ref, string output | + sourceElementRef(ref, output, kind) and + interpretOutput(output, 0, ref, node) + ) + } + + /** + * Holds if `node` is specified as a sink with the given kind in a CSV flow + * model. + */ + predicate isSinkNode(InterpretNode node, string kind) { + exists(InterpretNode ref, string input | + sinkElementRef(ref, input, kind) and + interpretInput(input, 0, ref, node) + ) + } + } + + /** Provides a query predicate for outputting a set of relevant flow summaries. */ + module TestOutput { + /** A flow summary to include in the `summary/3` query predicate. */ + abstract class RelevantSummarizedCallable extends SummarizedCallable { + /** Gets the string representation of this callable used by `summary/3`. */ + string getFullString() { result = this.toString() } + } + + /** A query predicate for outputting flow summaries in QL tests. */ + query predicate summary(string callable, string flow, boolean preservesValue) { + exists( + RelevantSummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output + | + callable = c.getFullString() and + c.propagatesFlow(input, output, preservesValue) and + flow = input + " -> " + output + ) + } + } +} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImplSpecific.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImplSpecific.qll new file mode 100644 index 000000000000..2b7fe072ddd5 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImplSpecific.qll @@ -0,0 +1,114 @@ +/** + * Provides Ruby specific classes and predicates for defining flow summaries. + */ + +private import ruby +private import DataFlowDispatch +private import DataFlowPrivate +private import DataFlowPublic +private import DataFlowImplCommon +private import FlowSummaryImpl::Private +private import FlowSummaryImpl::Public +private import codeql.ruby.dataflow.FlowSummary as FlowSummary + +/** Holds is `i` is a valid parameter position. */ +predicate parameterPosition(int i) { i in [-2 .. 10] } + +/** Gets the synthesized summary data-flow node for the given values. */ +Node summaryNode(SummarizedCallable c, SummaryNodeState state) { result = TSummaryNode(c, state) } + +/** Gets the synthesized data-flow call for `receiver`. */ +SummaryCall summaryDataFlowCall(Node receiver) { receiver = result.getReceiver() } + +/** Gets the type of content `c`. */ +DataFlowType getContentType(Content c) { any() } + +/** Gets the return type of kind `rk` for callable `c`. */ +bindingset[c, rk] +DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) { any() } + +/** + * Gets the type of the `i`th parameter in a synthesized call that targets a + * callback of type `t`. + */ +bindingset[t, i] +DataFlowType getCallbackParameterType(DataFlowType t, int i) { any() } + +/** + * Gets the return type of kind `rk` in a synthesized call that targets a + * callback of type `t`. + */ +DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) { any() } + +/** + * Holds if an external flow summary exists for `c` with input specification + * `input`, output specification `output`, and kind `kind`. + */ +predicate summaryElement(DataFlowCallable c, string input, string output, string kind) { + exists(FlowSummary::SummarizedCallable sc, boolean preservesValue | + sc.propagatesFlowExt(input, output, preservesValue) and + c.asLibraryCallable() = sc and + if preservesValue = true then kind = "value" else kind = "taint" + ) +} + +/** + * Gets the summary component for specification component `c`, if any. + * + * This covers all the Ruby-specific components of a flow summary, and + * is currently restricted to `"BlockArgument"`. + */ +SummaryComponent interpretComponentSpecific(string c) { + c = "BlockArgument" and + result = FlowSummary::SummaryComponent::block() +} + +/** Gets the return kind corresponding to specification `"ReturnValue"`. */ +NormalReturnKind getReturnValueKind() { any() } + +/** + * All definitions in this module are required by the shared implementation + * (for source/sink interpretation), but they are unused for Ruby, where + * we rely on API graphs instead. + */ +private module UnusedSourceSinkInterpretation { + /** + * Holds if an external source specification exists for `e` with output specification + * `output` and kind `kind`. + */ + predicate sourceElement(AstNode n, string output, string kind) { none() } + + /** + * Holds if an external sink specification exists for `n` with input specification + * `input` and kind `kind`. + */ + predicate sinkElement(AstNode n, string input, string kind) { none() } + + class SourceOrSinkElement = AstNode; + + /** An entity used to interpret a source/sink specification. */ + class InterpretNode extends AstNode { + /** Gets the element that this node corresponds to, if any. */ + SourceOrSinkElement asElement() { none() } + + /** Gets the data-flow node that this node corresponds to, if any. */ + Node asNode() { none() } + + /** Gets the call that this node corresponds to, if any. */ + DataFlowCall asCall() { none() } + + /** Gets the callable that this node corresponds to, if any. */ + DataFlowCallable asCallable() { none() } + + /** Gets the target of this call, if any. */ + Callable getCallTarget() { none() } + } + + /** Provides additional sink specification logic. */ + predicate interpretOutputSpecific(string c, InterpretNode mid, InterpretNode node) { none() } + + /** Provides additional source specification logic. */ + predicate interpretInputSpecific(string c, InterpretNode mid, InterpretNode node) { none() } +} + +import UnusedSourceSinkInterpretation diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll new file mode 100644 index 000000000000..54269c5cb591 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll @@ -0,0 +1,289 @@ +private import SsaImplCommon +private import codeql.ruby.AST +private import codeql.ruby.CFG +private import codeql.ruby.ast.Variable +private import CfgNodes::ExprNodes + +/** Holds if `v` is uninitialized at index `i` in entry block `bb`. */ +predicate uninitializedWrite(EntryBasicBlock bb, int i, LocalVariable v) { + v.getDeclaringScope() = bb.getScope() and + i = -1 +} + +/** Holds if `bb` contains a caputured read of variable `v`. */ +pragma[noinline] +private predicate hasCapturedVariableRead(BasicBlock bb, LocalVariable v) { + exists(LocalVariableReadAccess read | + read = bb.getANode().getNode() and + read.isCapturedAccess() and + read.getVariable() = v + ) +} + +/** + * Holds if an entry definition is needed for captured variable `v` at index + * `i` in entry block `bb`. + */ +predicate capturedEntryWrite(EntryBasicBlock bb, int i, LocalVariable v) { + hasCapturedVariableRead(bb.getASuccessor*(), v) and + i = -1 +} + +/** Holds if `bb` contains a caputured write to variable `v`. */ +pragma[noinline] +private predicate writesCapturedVariable(BasicBlock bb, LocalVariable v) { + exists(LocalVariableWriteAccess write | + write = bb.getANode().getNode() and + write.isCapturedAccess() and + write.getVariable() = v + ) +} + +/** + * Holds if a pseudo read of captured variable `v` should be inserted + * at index `i` in exit block `bb`. + */ +private predicate capturedExitRead(AnnotatedExitBasicBlock bb, int i, LocalVariable v) { + bb.isNormal() and + writesCapturedVariable(bb.getAPredecessor*(), v) and + i = bb.length() +} + +private CfgScope getCaptureOuterCfgScope(CfgScope scope) { + result = scope.getOuterCfgScope() and + ( + scope instanceof Block + or + scope instanceof Lambda + ) +} + +/** Holds if captured variable `v` is read inside `scope`. */ +pragma[noinline] +private predicate hasCapturedRead(Variable v, CfgScope scope) { + any(LocalVariableReadAccess read | + read.getVariable() = v and scope = getCaptureOuterCfgScope*(read.getCfgScope()) + ).isCapturedAccess() +} + +pragma[noinline] +private predicate hasVariableWriteWithCapturedRead(BasicBlock bb, LocalVariable v, CfgScope scope) { + hasCapturedRead(v, scope) and + exists(VariableWriteAccess write | + write = bb.getANode().getNode() and + write.getVariable() = v and + bb.getScope() = scope.getOuterCfgScope() + ) +} + +/** + * Holds if the call at index `i` in basic block `bb` may reach a callable + * that reads captured variable `v`. + */ +private predicate capturedCallRead(BasicBlock bb, int i, LocalVariable v) { + exists(CfgScope scope | + hasVariableWriteWithCapturedRead(bb.getAPredecessor*(), v, scope) and + bb.getNode(i).getNode() instanceof Call + | + not scope instanceof Block + or + // If the read happens inside a block, we restrict to the call that + // contains the block + scope = any(MethodCall c | bb.getNode(i) = c.getAControlFlowNode()).getBlock() + ) +} + +/** Holds if captured variable `v` is written inside `scope`. */ +pragma[noinline] +private predicate hasCapturedWrite(Variable v, CfgScope scope) { + any(LocalVariableWriteAccess write | + write.getVariable() = v and scope = getCaptureOuterCfgScope*(write.getCfgScope()) + ).isCapturedAccess() +} + +/** Holds if `v` is read at index `i` in basic block `bb`. */ +private predicate variableReadActual(BasicBlock bb, int i, LocalVariable v) { + exists(VariableReadAccess read | + read.getVariable() = v and + read = bb.getNode(i).getNode() + ) +} + +predicate variableRead(BasicBlock bb, int i, LocalVariable v, boolean certain) { + variableReadActual(bb, i, v) and + certain = true + or + capturedCallRead(bb, i, v) and + certain = false + or + capturedExitRead(bb, i, v) and + certain = false +} + +pragma[noinline] +private predicate hasVariableReadWithCapturedWrite(BasicBlock bb, LocalVariable v, CfgScope scope) { + hasCapturedWrite(v, scope) and + exists(VariableReadAccess read | + read = bb.getANode().getNode() and + read.getVariable() = v and + bb.getScope() = scope.getOuterCfgScope() + ) +} + +cached +private module Cached { + /** + * Holds if the call at index `i` in basic block `bb` may reach a callable + * that writes captured variable `v`. + */ + cached + predicate capturedCallWrite(BasicBlock bb, int i, LocalVariable v) { + exists(CfgScope scope | + hasVariableReadWithCapturedWrite(bb.getASuccessor*(), v, scope) and + bb.getNode(i).getNode() instanceof Call + | + not scope instanceof Block + or + // If the write happens inside a block, we restrict to the call that + // contains the block + scope = any(MethodCall c | bb.getNode(i) = c.getAControlFlowNode()).getBlock() + ) + } + + /** + * Holds if `v` is written at index `i` in basic block `bb`, and the corresponding + * AST write access is `write`. + */ + cached + predicate variableWriteActual(BasicBlock bb, int i, LocalVariable v, VariableWriteAccess write) { + exists(AstNode n | + write.getVariable() = v and + n = bb.getNode(i).getNode() + | + write.isExplicitWrite(n) + or + write.isImplicitWrite() and + n = write + ) + } + + cached + VariableReadAccessCfgNode getARead(Definition def) { + exists(LocalVariable v, BasicBlock bb, int i | + ssaDefReachesRead(v, def, bb, i) and + variableReadActual(bb, i, v) and + result = bb.getNode(i) + ) + } + + /** + * Holds if there is flow for a captured variable from the enclosing scope into a block. + * ```rb + * foo = 0 + * bar { + * puts foo + * } + * ``` + */ + cached + predicate captureFlowIn(Definition def, Definition entry) { + exists(LocalVariable v, BasicBlock bb, int i | + ssaDefReachesRead(v, def, bb, i) and + capturedCallRead(bb, i, v) and + exists(BasicBlock bb2, int i2 | + capturedEntryWrite(bb2, i2, v) and + entry.definesAt(v, bb2, i2) + ) + ) + } + + /** + * Holds if there is outgoing flow for a captured variable that is updated in a block. + * ```rb + * foo = 0 + * bar { + * foo += 10 + * } + * puts foo + * ``` + */ + cached + predicate captureFlowOut(Definition def, Definition exit) { + exists(LocalVariable v, BasicBlock bb, int i | + ssaDefReachesRead(v, def, bb, i) and + capturedExitRead(bb, i, v) and + exists(BasicBlock bb2, int i2 | + capturedCallWrite(bb2, i2, v) and + exit.definesAt(v, bb2, i2) + ) + ) + } + + cached + Definition phiHasInputFromBlock(PhiNode phi, BasicBlock bb) { + phiHasInputFromBlock(phi, result, bb) + } + + /** + * Holds if the value defined at SSA definition `def` can reach a read at `read`, + * without passing through any other non-pseudo read. + */ + cached + predicate firstRead(Definition def, VariableReadAccessCfgNode read) { + exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 | + def.definesAt(_, bb1, i1) and + adjacentDefNoUncertainReads(def, bb1, i1, bb2, i2) and + read = bb2.getNode(i2) + ) + } + + /** + * Holds if the read at `read2` is a read of the same SSA definition `def` + * as the read at `read1`, and `read2` can be reached from `read1` without + * passing through another non-pseudo read. + */ + cached + predicate adjacentReadPair( + Definition def, VariableReadAccessCfgNode read1, VariableReadAccessCfgNode read2 + ) { + exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 | + read1 = bb1.getNode(i1) and + variableReadActual(bb1, i1, _) and + adjacentDefNoUncertainReads(def, bb1, i1, bb2, i2) and + read2 = bb2.getNode(i2) + ) + } + + /** + * Holds if the read of `def` at `read` may be a last read. That is, `read` + * can either reach another definition of the underlying source variable or + * the end of the CFG scope, without passing through another non-pseudo read. + */ + cached + predicate lastRead(Definition def, VariableReadAccessCfgNode read) { + exists(BasicBlock bb, int i | + lastRefNoUncertainReads(def, bb, i) and + variableReadActual(bb, i, _) and + read = bb.getNode(i) + ) + } + + /** + * Holds if the reference to `def` at index `i` in basic block `bb` can reach + * another definition `next` of the same underlying source variable, without + * passing through another write or non-pseudo read. + * + * The reference is either a read of `def` or `def` itself. + */ + cached + predicate lastRefBeforeRedef(Definition def, BasicBlock bb, int i, Definition next) { + lastRefRedefNoUncertainReads(def, bb, i, next) + } + + cached + Definition uncertainWriteDefinitionInput(UncertainWriteDefinition def) { + uncertainWriteDefinitionInput(def, result) + } +} + +import Cached diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll new file mode 100644 index 000000000000..884f4406d01a --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll @@ -0,0 +1,637 @@ +/** + * Provides a language-independent implementation of static single assignment + * (SSA) form. + */ + +private import SsaImplSpecific + +private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { getABasicBlockSuccessor(result) = bb } + +/** + * Liveness analysis (based on source variables) to restrict the size of the + * SSA representation. + */ +private module Liveness { + /** + * A classification of variable references into reads (of a given kind) and + * (certain or uncertain) writes. + */ + private newtype TRefKind = + Read(boolean certain) { certain in [false, true] } or + Write(boolean certain) { certain in [false, true] } + + private class RefKind extends TRefKind { + string toString() { + exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")") + or + exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")") + } + + int getOrder() { + this = Read(_) and + result = 0 + or + this = Write(_) and + result = 1 + } + } + + /** + * Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`. + */ + private predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) { + exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain)) + or + exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain)) + } + + private newtype OrderedRefIndex = + MkOrderedRefIndex(int i, int tag) { + exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder()) + } + + private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) { + ref(bb, i, v, k) and + result = MkOrderedRefIndex(i, ord) and + ord = k.getOrder() + } + + /** + * Gets the (1-based) rank of the reference to `v` at the `i`th node of + * basic block `bb`, which has the given reference kind `k`. + * + * Reads are considered before writes when they happen at the same index. + */ + private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) { + refOrd(bb, i, v, k, _) = + rank[result](int j, int ord, OrderedRefIndex res | + res = refOrd(bb, j, v, _, ord) + | + res order by j, ord + ) + } + + private int maxRefRank(BasicBlock bb, SourceVariable v) { + result = refRank(bb, _, v, _) and + not result + 1 = refRank(bb, _, v, _) + } + + /** + * Gets the (1-based) rank of the first reference to `v` inside basic block `bb` + * that is either a read or a certain write. + */ + private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) { + result = + min(int r, RefKind k | + r = refRank(bb, _, v, k) and + k != Write(false) + | + r + ) + } + + /** + * Holds if source variable `v` is live at the beginning of basic block `bb`. + */ + predicate liveAtEntry(BasicBlock bb, SourceVariable v) { + // The first read or certain write to `v` inside `bb` is a read + refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v) + or + // There is no certain write to `v` inside `bb`, but `v` is live at entry + // to a successor basic block of `bb` + not exists(firstReadOrCertainWrite(bb, v)) and + liveAtExit(bb, v) + } + + /** + * Holds if source variable `v` is live at the end of basic block `bb`. + */ + predicate liveAtExit(BasicBlock bb, SourceVariable v) { + liveAtEntry(getABasicBlockSuccessor(bb), v) + } + + /** + * Holds if variable `v` is live in basic block `bb` at index `i`. + * The rank of `i` is `rnk` as defined by `refRank()`. + */ + private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) { + exists(RefKind kind | rnk = refRank(bb, i, v, kind) | + rnk = maxRefRank(bb, v) and + liveAtExit(bb, v) + or + ref(bb, i, v, kind) and + kind = Read(_) + or + exists(RefKind nextKind | + liveAtRank(bb, _, v, rnk + 1) and + rnk + 1 = refRank(bb, _, v, nextKind) and + nextKind != Write(true) + ) + ) + } + + /** + * Holds if variable `v` is live after the (certain or uncertain) write at + * index `i` inside basic block `bb`. + */ + predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) { + exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk)) + } +} + +private import Liveness + +/** Holds if `bb1` strictly dominates `bb2`. */ +private predicate strictlyDominates(BasicBlock bb1, BasicBlock bb2) { + bb1 = getImmediateBasicBlockDominator+(bb2) +} + +/** Holds if `bb1` dominates a predecessor of `bb2`. */ +private predicate dominatesPredecessor(BasicBlock bb1, BasicBlock bb2) { + exists(BasicBlock pred | pred = getABasicBlockPredecessor(bb2) | + bb1 = pred + or + strictlyDominates(bb1, pred) + ) +} + +/** Holds if `df` is in the dominance frontier of `bb`. */ +private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) { + dominatesPredecessor(bb, df) and + not strictlyDominates(bb, df) +} + +/** + * Holds if `bb` is in the dominance frontier of a block containing a + * definition of `v`. + */ +pragma[noinline] +private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) { + exists(BasicBlock defbb, Definition def | + def.definesAt(v, defbb, _) and + inDominanceFrontier(defbb, bb) + ) +} + +cached +newtype TDefinition = + TWriteDef(SourceVariable v, BasicBlock bb, int i) { + variableWrite(bb, i, v, _) and + liveAfterWrite(bb, i, v) + } or + TPhiNode(SourceVariable v, BasicBlock bb) { + inDefDominanceFrontier(bb, v) and + liveAtEntry(bb, v) + } + +private module SsaDefReaches { + newtype TSsaRefKind = + SsaRead() or + SsaDef() + + /** + * A classification of SSA variable references into reads and definitions. + */ + class SsaRefKind extends TSsaRefKind { + string toString() { + this = SsaRead() and + result = "SsaRead" + or + this = SsaDef() and + result = "SsaDef" + } + + int getOrder() { + this = SsaRead() and + result = 0 + or + this = SsaDef() and + result = 1 + } + } + + /** + * Holds if the `i`th node of basic block `bb` is a reference to `v`, + * either a read (when `k` is `SsaRead()`) or an SSA definition (when `k` + * is `SsaDef()`). + * + * Unlike `Liveness::ref`, this includes `phi` nodes. + */ + predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { + variableRead(bb, i, v, _) and + k = SsaRead() + or + exists(Definition def | def.definesAt(v, bb, i)) and + k = SsaDef() + } + + private newtype OrderedSsaRefIndex = + MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) } + + private OrderedSsaRefIndex ssaRefOrd(BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord) { + ssaRef(bb, i, v, k) and + result = MkOrderedSsaRefIndex(i, k) and + ord = k.getOrder() + } + + /** + * Gets the (1-based) rank of the reference to `v` at the `i`th node of basic + * block `bb`, which has the given reference kind `k`. + * + * For example, if `bb` is a basic block with a phi node for `v` (considered + * to be at index -1), reads `v` at node 2, and defines it at node 5, we have: + * + * ```ql + * ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node + * ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2 + * ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5 + * ``` + * + * Reads are considered before writes when they happen at the same index. + */ + int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { + ssaRefOrd(bb, i, v, k, _) = + rank[result](int j, int ord, OrderedSsaRefIndex res | + res = ssaRefOrd(bb, j, v, _, ord) + | + res order by j, ord + ) + } + + int maxSsaRefRank(BasicBlock bb, SourceVariable v) { + result = ssaRefRank(bb, _, v, _) and + not result + 1 = ssaRefRank(bb, _, v, _) + } + + /** + * Holds if the SSA definition `def` reaches rank index `rnk` in its own + * basic block `bb`. + */ + predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) { + exists(int i | + rnk = ssaRefRank(bb, i, v, SsaDef()) and + def.definesAt(v, bb, i) + ) + or + ssaDefReachesRank(bb, def, rnk - 1, v) and + rnk = ssaRefRank(bb, _, v, SsaRead()) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches index `i` in the same + * basic block `bb`, without crossing another SSA definition of `v`. + */ + predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) { + exists(int rnk | + ssaDefReachesRank(bb, def, rnk, v) and + rnk = ssaRefRank(bb, i, v, SsaRead()) + ) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches uncertain SSA definition + * `redef` in the same basic block, without crossing another SSA definition of `v`. + */ + predicate ssaDefReachesUncertainDefWithinBlock( + SourceVariable v, Definition def, UncertainWriteDefinition redef + ) { + exists(BasicBlock bb, int rnk, int i | + ssaDefReachesRank(bb, def, rnk, v) and + rnk = ssaRefRank(bb, i, v, SsaDef()) - 1 and + redef.definesAt(v, bb, i) + ) + } + + /** + * Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`. + */ + int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) { + v = def.getSourceVariable() and + result = ssaRefRank(bb, i, v, k) and + ( + ssaDefReachesRead(_, def, bb, i) + or + def.definesAt(_, bb, i) + ) + } + + /** + * Holds if the reference to `def` at index `i` in basic block `bb` is the + * last reference to `v` inside `bb`. + */ + pragma[noinline] + predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) { + ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) + } + + predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v) { + exists(ssaDefRank(def, v, bb, _, _)) + } + + pragma[noinline] + private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) { + ssaDefReachesEndOfBlock(bb, def, _) and + not defOccursInBlock(_, bb, def.getSourceVariable()) + } + + /** + * Holds if `def` is accessed in basic block `bb1` (either a read or a write), + * `bb2` is a transitive successor of `bb1`, `def` is live at the end of `bb1`, + * and the underlying variable for `def` is neither read nor written in any block + * on the path between `bb1` and `bb2`. + */ + predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) { + defOccursInBlock(def, bb1, _) and + bb2 = getABasicBlockSuccessor(bb1) + or + exists(BasicBlock mid | + varBlockReaches(def, bb1, mid) and + ssaDefReachesThroughBlock(def, mid) and + bb2 = getABasicBlockSuccessor(mid) + ) + } + + /** + * Holds if `def` is accessed in basic block `bb1` (either a read or a write), + * `def` is read at index `i2` in basic block `bb2`, `bb2` is in a transitive + * successor block of `bb1`, and `def` is neither read nor written in any block + * on a path between `bb1` and `bb2`. + */ + predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) { + varBlockReaches(def, bb1, bb2) and + ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1 + } +} + +private import SsaDefReaches + +pragma[nomagic] +predicate liveThrough(BasicBlock bb, SourceVariable v) { + liveAtExit(bb, v) and + not ssaRef(bb, _, v, SsaDef()) +} + +/** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if the SSA definition of `v` at `def` reaches the end of basic + * block `bb`, at which point it is still live, without crossing another + * SSA definition of `v`. + */ +pragma[nomagic] +predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) { + exists(int last | last = maxSsaRefRank(bb, v) | + ssaDefReachesRank(bb, def, last, v) and + liveAtExit(bb, v) + ) + or + // The construction of SSA form ensures that each read of a variable is + // dominated by its definition. An SSA definition therefore reaches a + // control flow node if it is the _closest_ SSA definition that dominates + // the node. If two definitions dominate a node then one must dominate the + // other, so therefore the definition of _closest_ is given by the dominator + // tree. Thus, reaching definitions can be calculated in terms of dominance. + ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and + liveThrough(bb, pragma[only_bind_into](v)) +} + +/** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`. + */ +pragma[nomagic] +predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) { + exists(SourceVariable v, BasicBlock bbDef | + phi.definesAt(v, bbDef, _) and + getABasicBlockPredecessor(bbDef) = bb and + ssaDefReachesEndOfBlock(bb, inp, v) + ) +} + +/** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if the SSA definition of `v` at `def` reaches a read at index `i` in + * basic block `bb`, without crossing another SSA definition of `v`. The read + * is of kind `rk`. + */ +pragma[nomagic] +predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) { + ssaDefReachesReadWithinBlock(v, def, bb, i) + or + variableRead(bb, i, v, _) and + ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and + not ssaDefReachesReadWithinBlock(v, _, bb, i) +} + +/** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read + * or a write), `def` is read at index `i2` in basic block `bb2`, and there is a + * path between them without any read of `def`. + */ +pragma[nomagic] +predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { + exists(int rnk | + rnk = ssaDefRank(def, _, bb1, i1, _) and + rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaRead()) and + variableRead(bb1, i2, _, _) and + bb2 = bb1 + ) + or + lastSsaRef(def, _, bb1, i1) and + defAdjacentRead(def, bb1, bb2, i2) +} + +pragma[noinline] +private predicate adjacentDefRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v +) { + adjacentDefRead(def, bb1, i1, bb2, i2) and + v = def.getSourceVariable() +} + +private predicate adjacentDefReachesRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 +) { + exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) | + ssaRef(bb1, i1, v, SsaDef()) + or + variableRead(bb1, i1, v, true) + ) + or + exists(BasicBlock bb3, int i3 | + adjacentDefReachesRead(def, bb1, i1, bb3, i3) and + variableRead(bb3, i3, _, false) and + adjacentDefRead(def, bb3, i3, bb2, i2) + ) +} + +/** + * NB: If this predicate is exposed, it should be cached. + * + * Same as `adjacentDefRead`, but ignores uncertain reads. + */ +pragma[nomagic] +predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { + adjacentDefReachesRead(def, bb1, i1, bb2, i2) and + variableRead(bb2, i2, _, true) +} + +/** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if the node at index `i` in `bb` is a last reference to SSA definition + * `def`. The reference is last because it can reach another write `next`, + * without passing through another read or write. + */ +pragma[nomagic] +predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) { + exists(SourceVariable v | + // Next reference to `v` inside `bb` is a write + exists(int rnk, int j | + rnk = ssaDefRank(def, v, bb, i, _) and + next.definesAt(v, bb, j) and + rnk + 1 = ssaRefRank(bb, j, v, SsaDef()) + ) + or + // Can reach a write using one or more steps + lastSsaRef(def, v, bb, i) and + exists(BasicBlock bb2 | + varBlockReaches(def, bb, bb2) and + 1 = ssaDefRank(next, v, bb2, _, SsaDef()) + ) + ) +} + +/** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if `inp` is an immediately preceding definition of uncertain definition + * `def`. Since `def` is uncertain, the value from the preceding definition might + * still be valid. + */ +pragma[nomagic] +predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) { + lastRefRedef(inp, _, _, def) +} + +private predicate adjacentDefReachesUncertainRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 +) { + adjacentDefReachesRead(def, bb1, i1, bb2, i2) and + variableRead(bb2, i2, _, false) +} + +/** + * NB: If this predicate is exposed, it should be cached. + * + * Same as `lastRefRedef`, but ignores uncertain reads. + */ +pragma[nomagic] +predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) { + lastRefRedef(def, bb, i, next) and + not variableRead(bb, i, def.getSourceVariable(), false) + or + exists(BasicBlock bb0, int i0 | + lastRefRedef(def, bb0, i0, next) and + adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) + ) +} + +/** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if the node at index `i` in `bb` is a last reference to SSA + * definition `def`. + * + * That is, the node can reach the end of the enclosing callable, or another + * SSA definition for the underlying source variable, without passing through + * another read. + */ +pragma[nomagic] +predicate lastRef(Definition def, BasicBlock bb, int i) { + lastRefRedef(def, bb, i, _) + or + lastSsaRef(def, _, bb, i) and + ( + // Can reach exit directly + bb instanceof ExitBasicBlock + or + // Can reach a block using one or more steps, where `def` is no longer live + exists(BasicBlock bb2 | varBlockReaches(def, bb, bb2) | + not defOccursInBlock(def, bb2, _) and + not ssaDefReachesEndOfBlock(bb2, def, _) + ) + ) +} + +/** + * NB: If this predicate is exposed, it should be cached. + * + * Same as `lastRefRedef`, but ignores uncertain reads. + */ +pragma[nomagic] +predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) { + lastRef(def, bb, i) and + not variableRead(bb, i, def.getSourceVariable(), false) + or + exists(BasicBlock bb0, int i0 | + lastRef(def, bb0, i0) and + adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) + ) +} + +/** A static single assignment (SSA) definition. */ +class Definition extends TDefinition { + /** Gets the source variable underlying this SSA definition. */ + SourceVariable getSourceVariable() { this.definesAt(result, _, _) } + + /** + * Holds if this SSA definition defines `v` at index `i` in basic block `bb`. + * Phi nodes are considered to be at index `-1`, while normal variable writes + * are at the index of the control flow node they wrap. + */ + final predicate definesAt(SourceVariable v, BasicBlock bb, int i) { + this = TWriteDef(v, bb, i) + or + this = TPhiNode(v, bb) and i = -1 + } + + /** Gets the basic block to which this SSA definition belongs. */ + final BasicBlock getBasicBlock() { this.definesAt(_, result, _) } + + /** Gets a textual representation of this SSA definition. */ + string toString() { none() } +} + +/** An SSA definition that corresponds to a write. */ +class WriteDefinition extends Definition, TWriteDef { + private SourceVariable v; + private BasicBlock bb; + private int i; + + WriteDefinition() { this = TWriteDef(v, bb, i) } + + override string toString() { result = "WriteDef" } +} + +/** A phi node. */ +class PhiNode extends Definition, TPhiNode { + override string toString() { result = "Phi" } +} + +/** + * An SSA definition that represents an uncertain update of the underlying + * source variable. + */ +class UncertainWriteDefinition extends WriteDefinition { + UncertainWriteDefinition() { + exists(SourceVariable v, BasicBlock bb, int i | + this.definesAt(v, bb, i) and + variableWrite(bb, i, v, false) + ) + } +} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplSpecific.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplSpecific.qll new file mode 100644 index 000000000000..76646f17e8d5 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplSpecific.qll @@ -0,0 +1,34 @@ +/** Provides the Ruby specific parameters for `SsaImplCommon.qll`. */ + +private import SsaImpl as SsaImpl +private import codeql.ruby.AST +private import codeql.ruby.ast.Parameter +private import codeql.ruby.ast.Variable +private import codeql.ruby.controlflow.BasicBlocks as BasicBlocks +private import codeql.ruby.controlflow.ControlFlowGraph + +class BasicBlock = BasicBlocks::BasicBlock; + +BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result = bb.getImmediateDominator() } + +BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() } + +class ExitBasicBlock = BasicBlocks::ExitBasicBlock; + +class SourceVariable = LocalVariable; + +predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { + ( + SsaImpl::uninitializedWrite(bb, i, v) + or + SsaImpl::capturedEntryWrite(bb, i, v) + or + SsaImpl::variableWriteActual(bb, i, v, _) + ) and + certain = true + or + SsaImpl::capturedCallWrite(bb, i, v) and + certain = false +} + +predicate variableRead = SsaImpl::variableRead/4; diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/TaintTrackingPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/TaintTrackingPrivate.qll new file mode 100755 index 000000000000..86c8ffb7f50a --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/TaintTrackingPrivate.qll @@ -0,0 +1,41 @@ +private import ruby +private import TaintTrackingPublic +private import codeql.ruby.CFG +private import codeql.ruby.DataFlow +private import FlowSummaryImpl as FlowSummaryImpl + +/** + * Holds if `node` should be a sanitizer in all global taint flow configurations + * but not in local taint. + */ +predicate defaultTaintSanitizer(DataFlow::Node node) { none() } + +/** + * Holds if default `TaintTracking::Configuration`s should allow implicit reads + * of `c` at sinks and inputs to additional taint steps. + */ +bindingset[node] +predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::Content c) { none() } + +/** + * Holds if the additional step from `nodeFrom` to `nodeTo` should be included + * in all global taint flow configurations. + */ +cached +predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + // operation involving `nodeFrom` + exists(CfgNodes::ExprNodes::OperationCfgNode op | + op = nodeTo.asExpr() and + op.getAnOperand() = nodeFrom.asExpr() and + not op.getExpr() instanceof AssignExpr + ) + or + // string interpolation of `nodeFrom` into `nodeTo` + nodeFrom.asExpr() = + nodeTo.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent() + or + // element reference from nodeFrom + nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::ElementReferenceCfgNode).getReceiver() + or + FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, false) +} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/TaintTrackingPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/TaintTrackingPublic.qll new file mode 100755 index 000000000000..3fe5659bdc70 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/TaintTrackingPublic.qll @@ -0,0 +1,31 @@ +private import ruby +private import TaintTrackingPrivate +private import codeql.ruby.CFG +private import codeql.ruby.DataFlow +private import FlowSummaryImpl as FlowSummaryImpl + +/** + * Holds if taint propagates from `source` to `sink` in zero or more local + * (intra-procedural) steps. + */ +predicate localTaint(DataFlow::Node source, DataFlow::Node sink) { localTaintStep*(source, sink) } + +/** + * Holds if taint can flow from `e1` to `e2` in zero or more + * local (intra-procedural) steps. + */ +predicate localExprTaint(CfgNodes::ExprCfgNode e1, CfgNodes::ExprCfgNode e2) { + localTaint(DataFlow::exprNode(e1), DataFlow::exprNode(e2)) +} + +/** + * Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local + * (intra-procedural) step. + */ +predicate localTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + defaultAdditionalTaintStep(nodeFrom, nodeTo) + or + // Simple flow through library code is included in the exposed local + // step relation, even though flow is technically inter-procedural + FlowSummaryImpl::Private::Steps::summaryThroughStep(nodeFrom, nodeTo, false) +} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTrackingImpl.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTrackingImpl.qll new file mode 100644 index 000000000000..f4f73b8247c5 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTrackingImpl.qll @@ -0,0 +1,120 @@ +/** + * Provides an implementation of global (interprocedural) taint tracking. + * This file re-exports the local (intraprocedural) taint-tracking analysis + * from `TaintTrackingParameter::Public` and adds a global analysis, mainly + * exposed through the `Configuration` class. For some languages, this file + * exists in several identical copies, allowing queries to use multiple + * `Configuration` classes that depend on each other without introducing + * mutual recursion among those configurations. + */ + +import TaintTrackingParameter::Public +private import TaintTrackingParameter::Private + +/** + * A configuration of interprocedural taint tracking analysis. This defines + * sources, sinks, and any other configurable aspect of the analysis. Each + * use of the taint tracking library must define its own unique extension of + * this abstract class. + * + * A taint-tracking configuration is a special data flow configuration + * (`DataFlow::Configuration`) that allows for flow through nodes that do not + * necessarily preserve values but are still relevant from a taint tracking + * perspective. (For example, string concatenation, where one of the operands + * is tainted.) + * + * To create a configuration, extend this class with a subclass whose + * characteristic predicate is a unique singleton string. For example, write + * + * ```ql + * class MyAnalysisConfiguration extends TaintTracking::Configuration { + * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } + * // Override `isSource` and `isSink`. + * // Optionally override `isSanitizer`. + * // Optionally override `isSanitizerIn`. + * // Optionally override `isSanitizerOut`. + * // Optionally override `isSanitizerGuard`. + * // Optionally override `isAdditionalTaintStep`. + * } + * ``` + * + * Then, to query whether there is flow between some `source` and `sink`, + * write + * + * ```ql + * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) + * ``` + * + * Multiple configurations can coexist, but it is unsupported to depend on + * another `TaintTracking::Configuration` or a `DataFlow::Configuration` in the + * overridden predicates that define sources, sinks, or additional steps. + * Instead, the dependency should go to a `TaintTracking2::Configuration` or a + * `DataFlow2::Configuration`, `DataFlow3::Configuration`, etc. + */ +abstract class Configuration extends DataFlow::Configuration { + bindingset[this] + Configuration() { any() } + + /** + * Holds if `source` is a relevant taint source. + * + * The smaller this predicate is, the faster `hasFlow()` will converge. + */ + // overridden to provide taint-tracking specific qldoc + abstract override predicate isSource(DataFlow::Node source); + + /** + * Holds if `sink` is a relevant taint sink. + * + * The smaller this predicate is, the faster `hasFlow()` will converge. + */ + // overridden to provide taint-tracking specific qldoc + abstract override predicate isSink(DataFlow::Node sink); + + /** Holds if the node `node` is a taint sanitizer. */ + predicate isSanitizer(DataFlow::Node node) { none() } + + final override predicate isBarrier(DataFlow::Node node) { + isSanitizer(node) or + defaultTaintSanitizer(node) + } + + /** Holds if taint propagation into `node` is prohibited. */ + predicate isSanitizerIn(DataFlow::Node node) { none() } + + final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) } + + /** Holds if taint propagation out of `node` is prohibited. */ + predicate isSanitizerOut(DataFlow::Node node) { none() } + + final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) } + + /** Holds if taint propagation through nodes guarded by `guard` is prohibited. */ + predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() } + + final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) } + + /** + * Holds if the additional taint propagation step from `node1` to `node2` + * must be taken into account in the analysis. + */ + predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() } + + final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + isAdditionalTaintStep(node1, node2) or + defaultAdditionalTaintStep(node1, node2) + } + + override predicate allowImplicitRead(DataFlow::Node node, DataFlow::Content c) { + (this.isSink(node) or this.isAdditionalTaintStep(node, _)) and + defaultImplicitTaintRead(node, c) + } + + /** + * Holds if taint may flow from `source` to `sink` for this configuration. + */ + // overridden to provide taint-tracking specific qldoc + override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) { + super.hasFlow(source, sink) + } +} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTrackingParameter.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTrackingParameter.qll new file mode 100644 index 000000000000..ce6f5ed1c480 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTrackingParameter.qll @@ -0,0 +1,6 @@ +import codeql.ruby.dataflow.internal.TaintTrackingPublic as Public + +module Private { + import codeql.ruby.DataFlow::DataFlow as DataFlow + import codeql.ruby.dataflow.internal.TaintTrackingPrivate +} diff --git a/ruby/ql/lib/codeql/ruby/filters/GeneratedCode.qll b/ruby/ql/lib/codeql/ruby/filters/GeneratedCode.qll new file mode 100644 index 000000000000..18d12be3aac8 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/filters/GeneratedCode.qll @@ -0,0 +1,43 @@ +/** Provides classes for detecting generated code. */ + +private import ruby +private import codeql.ruby.ast.internal.TreeSitter + +/** A source file that contains generated code. */ +abstract class GeneratedCodeFile extends RubyFile { } + +/** A file contining comments suggesting it contains generated code. */ +class GeneratedCommentFile extends GeneratedCodeFile { + GeneratedCommentFile() { this = any(GeneratedCodeComment c).getLocation().getFile() } +} + +/** A comment line that indicates generated code. */ +abstract class GeneratedCodeComment extends Ruby::Comment { } + +/** + * A generic comment line that suggests that the file is generated. + */ +class GenericGeneratedCodeComment extends GeneratedCodeComment { + GenericGeneratedCodeComment() { + exists(string line, string entity, string was, string automatically | line = getValue() | + entity = "file|class|art[ei]fact|module|script" and + was = "was|is|has been" and + automatically = "automatically |mechanically |auto[- ]?" and + line.regexpMatch("(?i).*\\bThis (" + entity + ") (" + was + ") (" + automatically + + ")?generated\\b.*") + ) + } +} + +/** A comment warning against modifications. */ +class DontModifyMarkerComment extends GeneratedCodeComment { + DontModifyMarkerComment() { + exists(string line | line = getValue() | + line.regexpMatch("(?i).*\\bGenerated by\\b.*\\bDo not edit\\b.*") or + line.regexpMatch("(?i).*\\bAny modifications to this file will be lost\\b.*") + ) + } +} + +/** Holds if `file` looks like it contains generated code. */ +predicate isGeneratedCode(GeneratedCodeFile file) { any() } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll new file mode 100644 index 000000000000..7ed83f4f75fb --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll @@ -0,0 +1,244 @@ +private import codeql.ruby.AST +private import codeql.ruby.Concepts +private import codeql.ruby.controlflow.CfgNodes +private import codeql.ruby.DataFlow +private import codeql.ruby.dataflow.RemoteFlowSources +private import codeql.ruby.ast.internal.Module +private import ActionView + +private class ActionControllerBaseAccess extends ConstantReadAccess { + ActionControllerBaseAccess() { + this.getName() = "Base" and + this.getScopeExpr().(ConstantAccess).getName() = "ActionController" + } +} + +// ApplicationController extends ActionController::Base, but we +// treat it separately in case the ApplicationController definition +// is not in the database +private class ApplicationControllerAccess extends ConstantReadAccess { + ApplicationControllerAccess() { this.getName() = "ApplicationController" } +} + +/** + * A `ClassDeclaration` for a class that extends `ActionController::Base`. + * For example, + * + * ```rb + * class FooController < ActionController::Base + * def delete_handler + * uid = params[:id] + * User.delete_all("id = ?", uid) + * end + * end + * ``` + */ +class ActionControllerControllerClass extends ClassDeclaration { + ActionControllerControllerClass() { + // class FooController < ActionController::Base + this.getSuperclassExpr() instanceof ActionControllerBaseAccess + or + // class FooController < ApplicationController + this.getSuperclassExpr() instanceof ApplicationControllerAccess + or + // class BarController < FooController + exists(ActionControllerControllerClass other | + other.getModule() = resolveScopeExpr(this.getSuperclassExpr()) + ) + } + + /** + * Gets a `ActionControllerActionMethod` defined in this class. + */ + ActionControllerActionMethod getAnAction() { result = this.getAMethod() } +} + +/** + * An instance method defined within an `ActionController` controller class. + * This may be the target of a route handler, if such a route is defined. + */ +class ActionControllerActionMethod extends Method, HTTP::Server::RequestHandler::Range { + private ActionControllerControllerClass controllerClass; + + ActionControllerActionMethod() { this = controllerClass.getAMethod() } + + /** + * Establishes a mapping between a method within the file + * `app/controllers/_controller.rb` and the + * corresponding template file at + * `app/views//.html.erb`. + */ + ErbFile getDefaultTemplateFile() { + controllerTemplatesFolder(this.getControllerClass(), result.getParentContainer()) and + result.getBaseName() = this.getName() + ".html.erb" + } + + // params come from `params` method rather than a method parameter + override Parameter getARoutedParameter() { none() } + + override string getFramework() { result = "ActionController" } + + /** Gets a call to render from within this method. */ + RenderCall getARenderCall() { result.getParent+() = this } + + // TODO: model the implicit render call when a path through the method does + // not end at an explicit render or redirect + /** Gets the controller class containing this method. */ + ActionControllerControllerClass getControllerClass() { result = controllerClass } +} + +// A method call with a `self` receiver from within a controller class +private class ActionControllerContextCall extends MethodCall { + private ActionControllerControllerClass controllerClass; + + ActionControllerContextCall() { + this.getReceiver() instanceof Self and + this.getEnclosingModule() = controllerClass + } + + ActionControllerControllerClass getControllerClass() { result = controllerClass } +} + +/** + * A call to the `params` method to fetch the request parameters. + */ +abstract class ParamsCall extends MethodCall { + ParamsCall() { this.getMethodName() = "params" } +} + +/** + * A `RemoteFlowSource::Range` to represent accessing the + * ActionController parameters available via the `params` method. + */ +class ParamsSource extends RemoteFlowSource::Range { + ParamsCall call; + + ParamsSource() { this.asExpr().getExpr() = call } + + override string getSourceType() { result = "ActionController::Metal#params" } +} + +// A call to `params` from within a controller. +private class ActionControllerParamsCall extends ActionControllerContextCall, ParamsCall { } + +// A call to `render` from within a controller. +private class ActionControllerRenderCall extends ActionControllerContextCall, RenderCall { } + +// A call to `render_to` from within a controller. +private class ActionControllerRenderToCall extends ActionControllerContextCall, RenderToCall { } + +// A call to `html_safe` from within a controller. +private class ActionControllerHtmlSafeCall extends HtmlSafeCall { + ActionControllerHtmlSafeCall() { + this.getEnclosingModule() instanceof ActionControllerControllerClass + } +} + +// A call to `html_escape` from within a controller. +private class ActionControllerHtmlEscapeCall extends HtmlEscapeCall { + ActionControllerHtmlEscapeCall() { + this.getEnclosingModule() instanceof ActionControllerControllerClass + } +} + +/** + * A call to the `redirect_to` method, used in an action to redirect to a + * specific URL/path or to a different action in this controller. + */ +class RedirectToCall extends ActionControllerContextCall { + RedirectToCall() { this.getMethodName() = "redirect_to" } + + /** Gets the `Expr` representing the URL to redirect to, if any */ + Expr getRedirectUrl() { result = this.getArgument(0) } + + /** Gets the `ActionControllerActionMethod` to redirect to, if any */ + ActionControllerActionMethod getRedirectActionMethod() { + exists(string methodName | + methodName = this.getKeywordArgument("action").(StringlikeLiteral).getValueText() and + methodName = result.getName() and + result.getEnclosingModule() = this.getControllerClass() + ) + } +} + +/** + * A call to the `redirect_to` method, as an `HttpRedirectResponse`. + */ +class ActionControllerRedirectResponse extends HTTP::Server::HttpRedirectResponse::Range { + RedirectToCall redirectToCall; + + ActionControllerRedirectResponse() { this.asExpr().getExpr() = redirectToCall } + + override DataFlow::Node getBody() { none() } + + override DataFlow::Node getMimetypeOrContentTypeArg() { none() } + + override string getMimetypeDefault() { none() } + + override DataFlow::Node getRedirectLocation() { + result.asExpr().getExpr() = redirectToCall.getRedirectUrl() + } +} + +/** + * A method in an `ActionController` class that is accessible from within a + * Rails view as a helper method. For instance, in: + * + * ```rb + * class FooController < ActionController::Base + * helper_method :logged_in? + * def logged_in? + * @current_user != nil + * end + * end + * ``` + * + * the `logged_in?` method is a helper method. + * See also https://api.rubyonrails.org/classes/AbstractController/Helpers/ClassMethods.html#method-i-helper_method + */ +class ActionControllerHelperMethod extends Method { + private ActionControllerControllerClass controllerClass; + + ActionControllerHelperMethod() { + this.getEnclosingModule() = controllerClass and + exists(MethodCall helperMethodMarker | + helperMethodMarker.getMethodName() = "helper_method" and + helperMethodMarker.getAnArgument().(StringlikeLiteral).getValueText() = this.getName() and + helperMethodMarker.getEnclosingModule() = controllerClass + ) + } + + /** Gets the class containing this helper method. */ + ActionControllerControllerClass getControllerClass() { result = controllerClass } +} + +/** + * Gets an `ActionControllerControllerClass` associated with the given `ErbFile` + * according to Rails path conventions. + * For instance, a template file at `app/views/foo/bar/baz.html.erb` will be + * mapped to a controller class in `app/controllers/foo/bar/baz_controller.rb`, + * if such a controller class exists. + */ +ActionControllerControllerClass getAssociatedControllerClass(ErbFile f) { + controllerTemplatesFolder(result, f.getParentContainer()) +} + +/** + * Holds if `templatesFolder` is in the correct location to contain template + * files "belonging" to the given `ActionControllerControllerClass`, according + * to Rails conventions. + * + * In particular, this means that an action method in `cls` will by default + * render a correspondingly named template file within `templatesFolder`. + */ +predicate controllerTemplatesFolder(ActionControllerControllerClass cls, Folder templatesFolder) { + exists(string templatesPath, string sourcePrefix, string subPath, string controllerPath | + controllerPath = cls.getLocation().getFile().getRelativePath() and + templatesPath = templatesFolder.getRelativePath() and + // `sourcePrefix` is either a prefix path ending in a slash, or empty if + // the rails app is at the source root + sourcePrefix = [controllerPath.regexpCapture("^(.*/)app/controllers/(?:.*?)/(?:[^/]*)$", 1), ""] and + controllerPath = sourcePrefix + "app/controllers/" + subPath + "_controller.rb" and + templatesPath = sourcePrefix + "app/views/" + subPath + ) +} diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActionView.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActionView.qll new file mode 100644 index 000000000000..dd95ff99896b --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActionView.qll @@ -0,0 +1,156 @@ +private import codeql.ruby.AST +private import codeql.ruby.Concepts +private import codeql.ruby.controlflow.CfgNodes +private import codeql.ruby.DataFlow +private import codeql.ruby.dataflow.RemoteFlowSources +private import codeql.ruby.ast.internal.Module +private import ActionController + +predicate inActionViewContext(AstNode n) { + // Within a template + n.getLocation().getFile() instanceof ErbFile +} + +/** + * A method call on a string to mark it as HTML safe for Rails. + * Strings marked as such will not be automatically escaped when inserted into + * HTML. + */ +abstract class HtmlSafeCall extends MethodCall { + HtmlSafeCall() { this.getMethodName() = "html_safe" } +} + +// A call to `html_safe` from within a template. +private class ActionViewHtmlSafeCall extends HtmlSafeCall { + ActionViewHtmlSafeCall() { inActionViewContext(this) } +} + +/** + * A call to a method named "html_escape", "html_escape_once", or "h". + */ +abstract class HtmlEscapeCall extends MethodCall { + // "h" is aliased to "html_escape" in ActiveSupport + HtmlEscapeCall() { this.getMethodName() = ["html_escape", "html_escape_once", "h"] } +} + +class RailsHtmlEscaping extends Escaping::Range, DataFlow::CallNode { + RailsHtmlEscaping() { this.asExpr().getExpr() instanceof HtmlEscapeCall } + + override DataFlow::Node getAnInput() { result = this.getArgument(0) } + + override DataFlow::Node getOutput() { result = this } + + override string getKind() { result = Escaping::getHtmlKind() } +} + +// A call to `html_escape` from within a template. +private class ActionViewHtmlEscapeCall extends HtmlEscapeCall { + ActionViewHtmlEscapeCall() { inActionViewContext(this) } +} + +// A call in a context where some commonly used `ActionView` methods are available. +private class ActionViewContextCall extends MethodCall { + ActionViewContextCall() { + this.getReceiver() instanceof Self and + inActionViewContext(this) + } + + predicate isInErbFile() { this.getLocation().getFile() instanceof ErbFile } +} + +/** A call to the `raw` method to output a value without HTML escaping. */ +class RawCall extends ActionViewContextCall { + RawCall() { this.getMethodName() = "raw" } +} + +// A call to the `params` method within the context of a template. +private class ActionViewParamsCall extends ActionViewContextCall, ParamsCall { } + +/** + * A call to a `render` method that will populate the response body with the + * rendered content. + */ +abstract class RenderCall extends MethodCall { + RenderCall() { this.getMethodName() = "render" } + + private string getWorkingDirectory() { + result = this.getLocation().getFile().getParentContainer().getAbsolutePath() + } + + bindingset[templatePath] + private string templatePathPattern(string templatePath) { + exists(string basename, string relativeRoot | + // everything after the final slash, or the whole string if there is no slash + basename = templatePath.regexpCapture("^(?:.*/)?([^/]*)$", 1) and + // everything up to and including the final slash + relativeRoot = templatePath.regexpCapture("^(.*/)?(?:[^/]*?)$", 1) + | + ( + // path relative to /app/views/ + result = "%/app/views/" + relativeRoot + "%" + basename + "%" + or + // relative to file containing call + result = this.getWorkingDirectory() + "%" + templatePath + "%" + ) + ) + } + + private string getTemplatePathPatterns() { + exists(string templatePath | + exists(Expr arg | + // TODO: support other ways of specifying paths (e.g. `file`) + arg = this.getKeywordArgument("partial") or + arg = this.getKeywordArgument("template") or + arg = this.getKeywordArgument("action") or + arg = this.getArgument(0) + | + templatePath = arg.(StringlikeLiteral).getValueText() + ) + | + result = this.templatePathPattern(templatePath) + ) + } + + /** + * Get the template file to be rendered by this call, if any. + */ + ErbFile getTemplateFile() { result.getAbsolutePath().matches(this.getTemplatePathPatterns()) } + + /** + * Get the local variables passed as context to the renderer + */ + HashLiteral getLocals() { result = this.getKeywordArgument("locals") } + // TODO: implicit renders in controller actions +} + +// A call to the `render` method within the context of a template. +private class ActionViewRenderCall extends RenderCall, ActionViewContextCall { } + +/** + * A render call that does not automatically set the HTTP response body. + */ +abstract class RenderToCall extends MethodCall { + RenderToCall() { this.getMethodName() = ["render_to_body", "render_to_string"] } +} + +// A call to `render_to` from within a template. +private class ActionViewRenderToCall extends ActionViewContextCall, RenderToCall { } + +/** + * A call to the ActionView `link_to` helper method. + * + * This generates an HTML anchor tag. The method is not designed to expect + * user-input, so provided paths are not automatically HTML escaped. + */ +class LinkToCall extends ActionViewContextCall { + LinkToCall() { this.getMethodName() = "link_to" } + + Expr getPathArgument() { + // When `link_to` is called with a block, it uses the first argument as the + // path, and otherwise the second argument. + exists(this.getBlock()) and result = this.getArgument(0) + or + not exists(this.getBlock()) and result = this.getArgument(1) + } +} +// TODO: model flow in/out of template files properly, diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll new file mode 100644 index 000000000000..de032e21c49f --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll @@ -0,0 +1,157 @@ +private import codeql.ruby.AST +private import codeql.ruby.Concepts +private import codeql.ruby.controlflow.CfgNodes +private import codeql.ruby.DataFlow +private import codeql.ruby.ast.internal.Module + +private class ActiveRecordBaseAccess extends ConstantReadAccess { + ActiveRecordBaseAccess() { + this.getName() = "Base" and + this.getScopeExpr().(ConstantAccess).getName() = "ActiveRecord" + } +} + +// ApplicationRecord extends ActiveRecord::Base, but we +// treat it separately in case the ApplicationRecord definition +// is not in the database +private class ApplicationRecordAccess extends ConstantReadAccess { + ApplicationRecordAccess() { this.getName() = "ApplicationRecord" } +} + +/** + * A `ClassDeclaration` for a class that extends `ActiveRecord::Base`. For example, + * + * ```rb + * class UserGroup < ActiveRecord::Base + * has_many :users + * end + * ``` + */ +class ActiveRecordModelClass extends ClassDeclaration { + ActiveRecordModelClass() { + // class Foo < ActiveRecord::Base + this.getSuperclassExpr() instanceof ActiveRecordBaseAccess + or + // class Foo < ApplicationRecord + this.getSuperclassExpr() instanceof ApplicationRecordAccess + or + // class Bar < Foo + exists(ActiveRecordModelClass other | + other.getModule() = resolveScopeExpr(this.getSuperclassExpr()) + ) + } +} + +/** A class method call whose receiver is an `ActiveRecordModelClass`. */ +class ActiveRecordModelClassMethodCall extends MethodCall { + private ActiveRecordModelClass recvCls; + + ActiveRecordModelClassMethodCall() { + // e.g. Foo.where(...) + recvCls.getModule() = resolveScopeExpr(this.getReceiver()) + or + // e.g. Foo.joins(:bars).where(...) + recvCls = this.getReceiver().(ActiveRecordModelClassMethodCall).getReceiverClass() + or + // e.g. self.where(...) within an ActiveRecordModelClass + this.getReceiver() instanceof Self and + this.getEnclosingModule() = recvCls + } + + /** The `ActiveRecordModelClass` of the receiver of this method. */ + ActiveRecordModelClass getReceiverClass() { result = recvCls } +} + +private Expr sqlFragmentArgument(MethodCall call) { + exists(string methodName | + methodName = call.getMethodName() and + ( + methodName = + [ + "delete_all", "destroy_all", "exists?", "find_by", "find_by_sql", "from", "group", + "having", "joins", "lock", "not", "order", "pluck", "where" + ] and + result = call.getArgument(0) + or + methodName = "calculate" and result = call.getArgument(1) + or + // This format was supported until Rails 2.3.8 + methodName = ["all", "find", "first", "last"] and + result = call.getKeywordArgument("conditions") + ) + ) +} + +// An expression that, if tainted by unsanitized input, should not be used as +// part of an argument to an SQL executing method +private predicate unsafeSqlExpr(Expr sqlFragmentExpr) { + // Literals containing an interpolated value + exists(StringInterpolationComponent interpolated | + interpolated = sqlFragmentExpr.(StringlikeLiteral).getComponent(_) + ) + or + // String concatenations + sqlFragmentExpr instanceof AddExpr + or + // Variable reads + sqlFragmentExpr instanceof VariableReadAccess + or + // Method call + sqlFragmentExpr instanceof MethodCall +} + +/** + * A method call that may result in executing unintended user-controlled SQL + * queries if the `getSqlFragmentSinkArgument()` expression is tainted by + * unsanitized user-controlled input. For example, supposing that `User` is an + * `ActiveRecord` model class, then + * + * ```rb + * User.where("name = '#{user_name}'") + * ``` + * + * may be unsafe if `user_name` is from unsanitized user input, as a value such + * as `"') OR 1=1 --"` could result in the application looking up all users + * rather than just one with a matching name. + */ +class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMethodCall { + // The SQL fragment argument itself + private Expr sqlFragmentExpr; + + // TODO: `find` with `lock:` option also takes an SQL fragment + PotentiallyUnsafeSqlExecutingMethodCall() { + exists(Expr arg | + arg = sqlFragmentArgument(this) and + unsafeSqlExpr(sqlFragmentExpr) and + ( + sqlFragmentExpr = arg + or + sqlFragmentExpr = arg.(ArrayLiteral).getElement(0) + ) and + // Check that method has not been overridden + not exists(SingletonMethod m | + m.getName() = this.getMethodName() and + m.getOuterScope() = this.getReceiverClass() + ) + ) + } + + Expr getSqlFragmentSinkArgument() { result = sqlFragmentExpr } +} + +/** + * An `SqlExecution::Range` for an argument to a + * `PotentiallyUnsafeSqlExecutingMethodCall` that may be vulnerable to being + * controlled by user input. + */ +class ActiveRecordSqlExecutionRange extends SqlExecution::Range { + ActiveRecordSqlExecutionRange() { + exists(PotentiallyUnsafeSqlExecutingMethodCall mc | + this.asExpr().getNode() = mc.getSqlFragmentSinkArgument() + ) + } + + override DataFlow::Node getSql() { result = this } +} +// TODO: model `ActiveRecord` sanitizers +// https://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Files.qll b/ruby/ql/lib/codeql/ruby/frameworks/Files.qll new file mode 100644 index 000000000000..4e0d10d74474 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/Files.qll @@ -0,0 +1,302 @@ +/** + * Provides classes for working with file system libraries. + */ + +private import ruby +private import codeql.ruby.Concepts +private import codeql.ruby.ApiGraphs +private import codeql.ruby.DataFlow +private import codeql.ruby.frameworks.StandardLibrary + +private DataFlow::Node ioInstanceInstantiation() { + result = API::getTopLevelMember("IO").getAnInstantiation() or + result = API::getTopLevelMember("IO").getAMethodCall(["for_fd", "open", "try_convert"]) +} + +private DataFlow::Node ioInstance() { + result = ioInstanceInstantiation() + or + exists(DataFlow::Node inst | + inst = ioInstance() and + inst.(DataFlow::LocalSourceNode).flowsTo(result) + ) +} + +// Match some simple cases where a path argument specifies a shell command to +// be executed. For example, the `"|date"` argument in `IO.read("|date")`, which +// will execute a shell command and read its output rather than reading from the +// filesystem. +private predicate pathArgSpawnsSubprocess(Expr arg) { + arg.(StringlikeLiteral).getValueText().charAt(0) = "|" +} + +private DataFlow::Node fileInstanceInstantiation() { + result = API::getTopLevelMember("File").getAnInstantiation() + or + result = API::getTopLevelMember("File").getAMethodCall("open") + or + // Calls to `Kernel.open` can yield `File` instances + exists(KernelMethodCall c | + c = result.asExpr().getExpr() and + c.getMethodName() = "open" and + // Assume that calls that don't invoke shell commands will instead open + // a file. + not pathArgSpawnsSubprocess(c.getArgument(0)) + ) +} + +private DataFlow::Node fileInstance() { + result = fileInstanceInstantiation() + or + exists(DataFlow::Node inst | + inst = fileInstance() and + inst.(DataFlow::LocalSourceNode).flowsTo(result) + ) +} + +private string ioFileReaderClassMethodName() { + result = ["binread", "foreach", "read", "readlines", "try_convert"] +} + +private string ioFileReaderInstanceMethodName() { + result = + [ + "getbyte", "getc", "gets", "pread", "read", "read_nonblock", "readbyte", "readchar", + "readline", "readlines", "readpartial", "sysread" + ] +} + +private string ioFileReaderMethodName(boolean classMethodCall) { + classMethodCall = true and result = ioFileReaderClassMethodName() + or + classMethodCall = false and result = ioFileReaderInstanceMethodName() +} + +/** + * Classes and predicates for modeling the core `IO` module. + */ +module IO { + /** + * An instance of the `IO` class, for example in + * + * ```rb + * rand = IO.new(IO.sysopen("/dev/random", "r"), "r") + * rand_data = rand.read(32) + * ``` + * + * there are 3 `IOInstance`s - the call to `IO.new`, the assignment + * `rand = ...`, and the read access to `rand` on the second line. + */ + class IOInstance extends DataFlow::Node { + IOInstance() { + this = ioInstance() or + this = fileInstance() + } + } + + // "Direct" `IO` instances, i.e. cases where there is no more specific + // subtype such as `File` + private class IOInstanceStrict extends IOInstance { + IOInstanceStrict() { this = ioInstance() } + } + + /** + * A `DataFlow::CallNode` that reads data using the `IO` class. For example, + * the `IO.read call in: + * + * ```rb + * IO.read("|date") + * ``` + * + * returns the output of the `date` shell command, invoked as a subprocess. + * + * This class includes reads both from shell commands and reads from the + * filesystem. For working with filesystem accesses specifically, see + * `IOFileReader` or the `FileSystemReadAccess` concept. + */ + class IOReader extends DataFlow::CallNode { + private boolean classMethodCall; + private string api; + + IOReader() { + // Class methods + api = ["File", "IO"] and + classMethodCall = true and + this = API::getTopLevelMember(api).getAMethodCall(ioFileReaderMethodName(classMethodCall)) + or + // IO instance methods + classMethodCall = false and + api = "IO" and + exists(IOInstanceStrict ii | + this.getReceiver() = ii and + this.asExpr().getExpr().(MethodCall).getMethodName() = + ioFileReaderMethodName(classMethodCall) + ) + or + // File instance methods + classMethodCall = false and + api = "File" and + exists(File::FileInstance fi | + this.getReceiver() = fi and + this.asExpr().getExpr().(MethodCall).getMethodName() = + ioFileReaderMethodName(classMethodCall) + ) + // TODO: enumeration style methods such as `each`, `foreach`, etc. + } + + /** + * Returns the most specific core class used for this read, `IO` or `File` + */ + string getAPI() { result = api } + + predicate isClassMethodCall() { classMethodCall = true } + } + + /** + * A `DataFlow::CallNode` that reads data from the filesystem using the `IO` + * class. For example, the `IO.read call in: + * + * ```rb + * IO.read("foo.txt") + * ``` + * + * reads the file `foo.txt` and returns its contents as a string. + */ + class IOFileReader extends IOReader, FileSystemReadAccess::Range { + IOFileReader() { + this.getAPI() = "File" + or + this.isClassMethodCall() and + // Assume that calls that don't invoke shell commands will instead + // read from a file. + not pathArgSpawnsSubprocess(this.getArgument(0).asExpr().getExpr()) + } + + // TODO: can we infer a path argument for instance method calls? + // e.g. by tracing back to the instantiation of that instance + override DataFlow::Node getAPathArgument() { + result = this.getArgument(0) and this.isClassMethodCall() + } + + // This class represents calls that return data + override DataFlow::Node getADataNode() { result = this } + } +} + +/** + * Classes and predicates for modeling the core `File` module. + * + * Because `File` is a subclass of `IO`, all `FileInstance`s and + * `FileModuleReader`s are also `IOInstance`s and `IOModuleReader`s + * respectively. + */ +module File { + /** + * An instance of the `File` class, for example in + * + * ```rb + * f = File.new("foo.txt") + * puts f.read() + * ``` + * + * there are 3 `FileInstance`s - the call to `File.new`, the assignment + * `f = ...`, and the read access to `f` on the second line. + */ + class FileInstance extends IO::IOInstance { + FileInstance() { this = fileInstance() } + } + + /** + * A read using the `File` module, e.g. the `f.read` call in + * + * ```rb + * f = File.new("foo.txt") + * puts f.read() + * ``` + */ + class FileModuleReader extends IO::IOFileReader { + FileModuleReader() { this.getAPI() = "File" } + } + + /** + * A call to a `File` method that may return one or more filenames. + */ + class FileModuleFilenameSource extends FileNameSource, DataFlow::CallNode { + FileModuleFilenameSource() { + // Class methods + this = + API::getTopLevelMember("File") + .getAMethodCall([ + "absolute_path", "basename", "expand_path", "join", "path", "readlink", + "realdirpath", "realpath" + ]) + or + // Instance methods + exists(FileInstance fi | + this.getReceiver() = fi and + this.asExpr().getExpr().(MethodCall).getMethodName() = ["path", "to_path"] + ) + } + } + + private class FileModulePermissionModification extends FileSystemPermissionModification::Range, + DataFlow::CallNode { + private DataFlow::Node permissionArg; + + FileModulePermissionModification() { + exists(string methodName | this = API::getTopLevelMember("File").getAMethodCall(methodName) | + methodName in ["chmod", "lchmod"] and permissionArg = this.getArgument(0) + or + methodName = "mkfifo" and permissionArg = this.getArgument(1) + or + methodName in ["new", "open"] and permissionArg = this.getArgument(2) + // TODO: defaults for optional args? This may depend on the umask + ) + } + + override DataFlow::Node getAPermissionNode() { result = permissionArg } + } +} + +/** + * Classes and predicates for modeling the `FileUtils` module from the standard + * library. + */ +module FileUtils { + /** + * A call to a FileUtils method that may return one or more filenames. + */ + class FileUtilsFilenameSource extends FileNameSource { + FileUtilsFilenameSource() { + // Note that many methods in FileUtils accept a `noop` option that will + // perform a dry run of the command. This means that, for instance, `rm` + // and similar methods may not actually delete/unlink a file when called. + this = + API::getTopLevelMember("FileUtils") + .getAMethodCall([ + "chmod", "chmod_R", "chown", "chown_R", "getwd", "makedirs", "mkdir", "mkdir_p", + "mkpath", "remove", "remove_dir", "remove_entry", "rm", "rm_f", "rm_r", "rm_rf", + "rmdir", "rmtree", "safe_unlink", "touch" + ]) + } + } + + private class FileUtilsPermissionModification extends FileSystemPermissionModification::Range, + DataFlow::CallNode { + private DataFlow::Node permissionArg; + + FileUtilsPermissionModification() { + exists(string methodName | + this = API::getTopLevelMember("FileUtils").getAMethodCall(methodName) + | + methodName in ["chmod", "chmod_R"] and permissionArg = this.getArgument(0) + or + methodName in ["install", "makedirs", "mkdir", "mkdir_p", "mkpath"] and + permissionArg = this.getKeywordArgument("mode") + // TODO: defaults for optional args? This may depend on the umask + ) + } + + override DataFlow::Node getAPermissionNode() { result = permissionArg } + } +} diff --git a/ruby/ql/lib/codeql/ruby/frameworks/HTTPClients.qll b/ruby/ql/lib/codeql/ruby/frameworks/HTTPClients.qll new file mode 100644 index 000000000000..6c41f4661afd --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/HTTPClients.qll @@ -0,0 +1,8 @@ +/** + * Helper file that imports all HTTP clients. + */ + +private import codeql.ruby.frameworks.http_clients.NetHTTP +private import codeql.ruby.frameworks.http_clients.Excon +private import codeql.ruby.frameworks.http_clients.Faraday +private import codeql.ruby.frameworks.http_clients.RestClient diff --git a/ruby/ql/lib/codeql/ruby/frameworks/StandardLibrary.qll b/ruby/ql/lib/codeql/ruby/frameworks/StandardLibrary.qll new file mode 100644 index 000000000000..197f2062e0d4 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/StandardLibrary.qll @@ -0,0 +1,277 @@ +private import codeql.ruby.AST +private import codeql.ruby.Concepts +private import codeql.ruby.DataFlow +private import codeql.ruby.ApiGraphs + +/** + * The `Kernel` module is included by the `Object` class, so its methods are available + * in every Ruby object. In addition, its module methods can be called by + * providing a specific receiver as in `Kernel.exit`. + */ +class KernelMethodCall extends MethodCall { + KernelMethodCall() { + this = API::getTopLevelMember("Kernel").getAMethodCall(_).asExpr().getExpr() + or + // we assume that if there's no obvious target for this method call + // and the method name matches a Kernel method, then it is a Kernel method call. + // TODO: ApiGraphs should ideally handle this case + not exists(this.(Call).getATarget()) and + ( + this.getReceiver() instanceof Self and isPrivateKernelMethod(this.getMethodName()) + or + isPublicKernelMethod(this.getMethodName()) + ) + } +} + +/** + * Public methods in the `Kernel` module. These can be invoked on any object via the usual dot syntax. + * ```ruby + * arr = [] + * arr.send("push", 5) # => [5] + * ``` + */ +private predicate isPublicKernelMethod(string method) { + method in ["class", "clone", "frozen?", "tap", "then", "yield_self", "send"] +} + +/** + * Private methods in the `Kernel` module. + * These can be be invoked on `self`, on `Kernel`, or using a low-level primitive like `send` or `instance_eval`. + * ```ruby + * puts "hello world" + * Kernel.puts "hello world" + * 5.instance_eval { puts "hello world" } + * 5.send("puts", "hello world") + * ``` + */ +private predicate isPrivateKernelMethod(string method) { + method in [ + "Array", "Complex", "Float", "Hash", "Integer", "Rational", "String", "__callee__", "__dir__", + "__method__", "`", "abort", "at_exit", "autoload", "autoload?", "binding", "block_given?", + "callcc", "caller", "caller_locations", "catch", "chomp", "chop", "eval", "exec", "exit", + "exit!", "fail", "fork", "format", "gets", "global_variables", "gsub", "iterator?", "lambda", + "load", "local_variables", "loop", "open", "p", "pp", "print", "printf", "proc", "putc", + "puts", "raise", "rand", "readline", "readlines", "require", "require_relative", "select", + "set_trace_func", "sleep", "spawn", "sprintf", "srand", "sub", "syscall", "system", "test", + "throw", "trace_var", "trap", "untrace_var", "warn" + ] +} + +/** + * A system command executed via subshell literal syntax. + * E.g. + * ```ruby + * `cat foo.txt` + * %x(cat foo.txt) + * %x[cat foo.txt] + * %x{cat foo.txt} + * %x/cat foo.txt/ + * ``` + */ +class SubshellLiteralExecution extends SystemCommandExecution::Range { + SubshellLiteral literal; + + SubshellLiteralExecution() { this.asExpr().getExpr() = literal } + + override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = literal.getComponent(_) } + + override predicate isShellInterpreted(DataFlow::Node arg) { arg = getAnArgument() } +} + +/** + * A system command executed via shell heredoc syntax. + * E.g. + * ```ruby + * <<`EOF` + * cat foo.text + * EOF + * ``` + */ +class SubshellHeredocExecution extends SystemCommandExecution::Range { + HereDoc heredoc; + + SubshellHeredocExecution() { this.asExpr().getExpr() = heredoc and heredoc.isSubShell() } + + override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = heredoc.getComponent(_) } + + override predicate isShellInterpreted(DataFlow::Node arg) { arg = getAnArgument() } +} + +/** + * A system command executed via the `Kernel.system` method. + * `Kernel.system` accepts three argument forms: + * - A single string. If it contains no shell meta characters, keywords or + * builtins, it is executed directly in a subprocess. + * Otherwise, it is executed in a subshell. + * ```ruby + * system("cat foo.txt | tail") + * ``` + * - A command and one or more arguments. + * The command is executed in a subprocess. + * ```ruby + * system("cat", "foo.txt") + * ``` + * - An array containing the command name and argv[0], followed by zero or more arguments. + * The command is executed in a subprocess. + * ```ruby + * system(["cat", "cat"], "foo.txt") + * ``` + * In addition, `Kernel.system` accepts an optional environment hash as the + * first argument and an optional options hash as the last argument. + * We don't yet distinguish between these arguments and the command arguments. + * ```ruby + * system({"FOO" => "BAR"}, "cat foo.txt | tail", {unsetenv_others: true}) + * ``` + * Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-system + */ +class KernelSystemCall extends SystemCommandExecution::Range { + KernelMethodCall methodCall; + + KernelSystemCall() { + methodCall.getMethodName() = "system" and + this.asExpr().getExpr() = methodCall + } + + override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() } + + override predicate isShellInterpreted(DataFlow::Node arg) { + // Kernel.system invokes a subshell if you provide a single string as argument + methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument() + } +} + +/** + * A system command executed via the `Kernel.exec` method. + * `Kernel.exec` takes the same argument forms as `Kernel.system`. See `KernelSystemCall` for details. + * Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-exec + */ +class KernelExecCall extends SystemCommandExecution::Range { + KernelMethodCall methodCall; + + KernelExecCall() { + methodCall.getMethodName() = "exec" and + this.asExpr().getExpr() = methodCall + } + + override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() } + + override predicate isShellInterpreted(DataFlow::Node arg) { + // Kernel.exec invokes a subshell if you provide a single string as argument + methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument() + } +} + +/** + * A system command executed via the `Kernel.spawn` method. + * `Kernel.spawn` takes the same argument forms as `Kernel.system`. + * See `KernelSystemCall` for details. + * Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-spawn + * TODO: document and handle the env and option arguments. + * ``` + * spawn([env,] command... [,options]) -> pid + * ``` + */ +class KernelSpawnCall extends SystemCommandExecution::Range { + KernelMethodCall methodCall; + + KernelSpawnCall() { + methodCall.getMethodName() = "spawn" and + this.asExpr().getExpr() = methodCall + } + + override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() } + + override predicate isShellInterpreted(DataFlow::Node arg) { + // Kernel.spawn invokes a subshell if you provide a single string as argument + methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument() + } +} + +/** + * A system command executed via one of the `Open3` methods. + * These methods take the same argument forms as `Kernel.system`. + * See `KernelSystemCall` for details. + */ +class Open3Call extends SystemCommandExecution::Range { + MethodCall methodCall; + + Open3Call() { + this.asExpr().getExpr() = methodCall and + this = + API::getTopLevelMember("Open3") + .getAMethodCall(["popen3", "popen2", "popen2e", "capture3", "capture2", "capture2e"]) + } + + override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() } + + override predicate isShellInterpreted(DataFlow::Node arg) { + // These Open3 methods invoke a subshell if you provide a single string as argument + methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument() + } +} + +/** + * A pipeline of system commands constructed via one of the `Open3` methods. + * These methods accept a variable argument list of commands. + * Commands can be in any form supported by `Kernel.system`. See `KernelSystemCall` for details. + * ```ruby + * Open3.pipeline("cat foo.txt", "tail") + * Open3.pipeline(["cat", "foo.txt"], "tail") + * Open3.pipeline([{}, "cat", "foo.txt"], "tail") + * Open3.pipeline([["cat", "cat"], "foo.txt"], "tail") + */ +class Open3PipelineCall extends SystemCommandExecution::Range { + MethodCall methodCall; + + Open3PipelineCall() { + this.asExpr().getExpr() = methodCall and + this = + API::getTopLevelMember("Open3") + .getAMethodCall(["pipeline_rw", "pipeline_r", "pipeline_w", "pipeline_start", "pipeline"]) + } + + override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() } + + override predicate isShellInterpreted(DataFlow::Node arg) { + // A command in the pipeline is executed in a subshell if it is given as a single string argument. + arg.asExpr().getExpr() instanceof StringlikeLiteral and + arg.asExpr().getExpr() = methodCall.getAnArgument() + } +} + +/** + * A call to `Kernel.eval`, which executes its argument as Ruby code. + * ```ruby + * a = 1 + * Kernel.eval("a = 2") + * a # => 2 + * ``` + */ +class EvalCallCodeExecution extends CodeExecution::Range { + KernelMethodCall methodCall; + + EvalCallCodeExecution() { + this.asExpr().getExpr() = methodCall and methodCall.getMethodName() = "eval" + } + + override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getAnArgument() } +} + +/** + * A call to `Kernel#send`, which executes its arguments as a Ruby method call. + * ```ruby + * arr = [] + * arr.send("push", 1) + * arr # => [1] + * ``` + */ +class SendCallCodeExecution extends CodeExecution::Range { + KernelMethodCall methodCall; + + SendCallCodeExecution() { + this.asExpr().getExpr() = methodCall and methodCall.getMethodName() = "send" + } + + override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getAnArgument() } +} diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Excon.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Excon.qll new file mode 100644 index 000000000000..825f5b82e975 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Excon.qll @@ -0,0 +1,46 @@ +private import ruby +private import codeql.ruby.Concepts +private import codeql.ruby.ApiGraphs + +/** + * A call that makes an HTTP request using `Excon`. + * ```ruby + * # one-off request + * Excon.get("http://example.com").body + * + * # connection re-use + * connection = Excon.new("http://example.com") + * connection.get(path: "/").body + * connection.request(method: :get, path: "/") + * ``` + * + * TODO: pipelining, streaming responses + * https://github.com/excon/excon/blob/master/README.md + */ +class ExconHTTPRequest extends HTTP::Client::Request::Range { + DataFlow::Node request; + DataFlow::CallNode responseBody; + + ExconHTTPRequest() { + exists(API::Node requestNode | request = requestNode.getAnImmediateUse() | + requestNode = + [ + // one-off requests + API::getTopLevelMember("Excon"), + // connection re-use + API::getTopLevelMember("Excon").getInstance() + ] + .getReturn([ + // Excon#request exists but Excon.request doesn't. + // This shouldn't be a problem - in real code the latter would raise NoMethodError anyway. + "get", "head", "delete", "options", "post", "put", "patch", "trace", "request" + ]) and + responseBody = requestNode.getAMethodCall("body") and + this = request.asExpr().getExpr() + ) + } + + override DataFlow::Node getResponseBody() { result = responseBody } + + override string getFramework() { result = "Excon" } +} diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Faraday.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Faraday.qll new file mode 100644 index 000000000000..21f3ba52b786 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/Faraday.qll @@ -0,0 +1,38 @@ +private import ruby +private import codeql.ruby.Concepts +private import codeql.ruby.ApiGraphs + +/** + * A call that makes an HTTP request using `Faraday`. + * ```ruby + * # one-off request + * Faraday.get("http://example.com").body + * + * # connection re-use + * connection = Faraday.new("http://example.com") + * connection.get("/").body + * ``` + */ +class FaradayHTTPRequest extends HTTP::Client::Request::Range { + DataFlow::Node request; + DataFlow::CallNode responseBody; + + FaradayHTTPRequest() { + exists(API::Node requestNode | + requestNode = + [ + // one-off requests + API::getTopLevelMember("Faraday"), + // connection re-use + API::getTopLevelMember("Faraday").getInstance() + ].getReturn(["get", "head", "delete", "post", "put", "patch", "trace"]) and + responseBody = requestNode.getAMethodCall("body") and + request = requestNode.getAnImmediateUse() and + this = request.asExpr().getExpr() + ) + } + + override DataFlow::Node getResponseBody() { result = responseBody } + + override string getFramework() { result = "Faraday" } +} diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/NetHTTP.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/NetHTTP.qll new file mode 100644 index 000000000000..9b99cd36bda6 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/NetHTTP.qll @@ -0,0 +1,54 @@ +private import codeql.ruby.AST +private import codeql.ruby.Concepts +private import codeql.ruby.dataflow.RemoteFlowSources +private import codeql.ruby.ApiGraphs +private import codeql.ruby.dataflow.internal.DataFlowPublic + +/** + * A `Net::HTTP` call which initiates an HTTP request. + * ```ruby + * Net::HTTP.get("http://example.com/") + * Net::HTTP.post("http://example.com/", "some_data") + * req = Net::HTTP.new("example.com") + * response = req.get("/") + * ``` + */ +class NetHTTPRequest extends HTTP::Client::Request::Range { + private DataFlow::CallNode request; + private DataFlow::Node responseBody; + + NetHTTPRequest() { + exists(API::Node requestNode, string method | + request = requestNode.getAnImmediateUse() and + this = request.asExpr().getExpr() + | + // Net::HTTP.get(...) + method = "get" and + requestNode = API::getTopLevelMember("Net").getMember("HTTP").getReturn(method) and + responseBody = request + or + // Net::HTTP.post(...).body + method in ["post", "post_form"] and + requestNode = API::getTopLevelMember("Net").getMember("HTTP").getReturn(method) and + responseBody = requestNode.getAMethodCall(["body", "read_body", "entity"]) + or + // Net::HTTP.new(..).get(..).body + method in [ + "get", "get2", "request_get", "head", "head2", "request_head", "delete", "put", "patch", + "post", "post2", "request_post", "request" + ] and + requestNode = API::getTopLevelMember("Net").getMember("HTTP").getInstance().getReturn(method) and + responseBody = requestNode.getAMethodCall(["body", "read_body", "entity"]) + ) + } + + /** + * Gets the node representing the URL of the request. + * Currently unused, but may be useful in future, e.g. to filter out certain requests. + */ + DataFlow::Node getURLArgument() { result = request.getArgument(0) } + + override DataFlow::Node getResponseBody() { result = responseBody } + + override string getFramework() { result = "Net::HTTP" } +} diff --git a/ruby/ql/lib/codeql/ruby/frameworks/http_clients/RestClient.qll b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/RestClient.qll new file mode 100644 index 000000000000..e088cb1c5f8f --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/http_clients/RestClient.qll @@ -0,0 +1,29 @@ +private import ruby +private import codeql.ruby.Concepts +private import codeql.ruby.ApiGraphs + +/** + * A call that makes an HTTP request using `RestClient`. + * ```ruby + * RestClient.get("http://example.com").body + * ``` + */ +class RestClientHTTPRequest extends HTTP::Client::Request::Range { + DataFlow::Node request; + DataFlow::CallNode responseBody; + + RestClientHTTPRequest() { + exists(API::Node requestNode | + requestNode = + API::getTopLevelMember("RestClient") + .getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and + request = requestNode.getAnImmediateUse() and + responseBody = requestNode.getAMethodCall("body") and + this = request.asExpr().getExpr() + ) + } + + override DataFlow::Node getResponseBody() { result = responseBody } + + override string getFramework() { result = "RestClient" } +} diff --git a/ruby/ql/lib/codeql/ruby/printAst.qll b/ruby/ql/lib/codeql/ruby/printAst.qll new file mode 100644 index 000000000000..0b5604dc670b --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/printAst.qll @@ -0,0 +1,203 @@ +/** + * Provides queries to pretty-print a Ruby abstract syntax tree as a graph. + * + * 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. + */ + +private import AST +private import codeql.ruby.regexp.RegExpTreeView as RETV + +/** Holds if `n` appears in the desugaring of some other node. */ +predicate isDesugared(AstNode n) { + n = any(AstNode sugar).getDesugared() + or + isDesugared(n.getParent()) +} + +/** + * 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 isDesugared(n) + or + not n.isSynthesized() + or + n.isSynthesized() and + not n = any(AstNode sugar).getDesugared() and + exists(AstNode parent | + parent = n.getParent() and + not parent.isSynthesized() and + not n = parent.getDesugared() + ) + } + + predicate shouldPrintAstEdge(AstNode parent, string edgeName, AstNode child) { + child = parent.getAChild(edgeName) and + not child = parent.getDesugared() + } +} + +private predicate shouldPrintNode(AstNode n) { + any(PrintAstConfiguration config).shouldPrintNode(n) +} + +private predicate shouldPrintAstEdge(AstNode parent, string edgeName, AstNode child) { + any(PrintAstConfiguration config).shouldPrintAstEdge(parent, edgeName, child) +} + +newtype TPrintNode = + TPrintRegularAstNode(AstNode n) { shouldPrintNode(n) } or + TPrintRegExpNode(RETV::RegExpTerm term) { + exists(RegExpLiteral literal | + shouldPrintNode(literal) and + term.getRootTerm() = literal.getParsed() + ) + } + +/** + * A node in the output tree. + */ +class PrintAstNode extends TPrintNode { + /** Gets a textual representation of this node in the PrintAst output tree. */ + string toString() { none() } + + /** + * Gets the child node with name `edgeName`. Typically this is the name of the + * predicate used to access the child. + */ + PrintAstNode getChild(string edgeName) { none() } + + /** Gets a child of this node. */ + final PrintAstNode getAChild() { result = getChild(_) } + + /** Gets the parent of this node, if any. */ + final PrintAstNode getParent() { result.getAChild() = this } + + /** + * Holds if this node 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://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + none() + } + + /** Gets a value used to order this node amongst its siblings. */ + int getOrder() { none() } + + /** + * Gets the value of the property of this node, where the name of the property + * is `key`. + */ + final string getProperty(string key) { + key = "semmle.label" and + result = this.toString() + or + key = "semmle.order" and result = this.getOrder().toString() + } +} + +/** An `AstNode` in the output tree. */ +class PrintRegularAstNode extends PrintAstNode, TPrintRegularAstNode { + AstNode astNode; + + PrintRegularAstNode() { this = TPrintRegularAstNode(astNode) } + + override string toString() { + result = "[" + concat(astNode.getAPrimaryQlClass(), ", ") + "] " + astNode.toString() + } + + override PrintAstNode getChild(string edgeName) { + exists(AstNode child | shouldPrintAstEdge(astNode, edgeName, child) | + result = TPrintRegularAstNode(child) + ) + or + // If this AST node is a regexp literal, add the parsed regexp tree as a + // child. + exists(RETV::RegExpTerm t | t = astNode.(RegExpLiteral).getParsed() | + result = TPrintRegExpNode(t) and edgeName = "getParsed" + ) + } + + override int getOrder() { + this = + rank[result](PrintRegularAstNode p, Location l, File f | + l = p.getLocation() and + f = l.getFile() + | + p order by f.getBaseName(), f.getAbsolutePath(), l.getStartLine(), l.getStartColumn() + ) + } + + /** Gets the location of this node. */ + Location getLocation() { result = astNode.getLocation() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + astNode.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +/** A parsed regexp node in the output tree. */ +class PrintRegExpNode extends PrintAstNode, TPrintRegExpNode { + RETV::RegExpTerm regexNode; + + PrintRegExpNode() { this = TPrintRegExpNode(regexNode) } + + override string toString() { + result = "[" + concat(regexNode.getAPrimaryQlClass(), ", ") + "] " + regexNode.toString() + } + + override PrintAstNode getChild(string edgeName) { + // Use the child index as an edge name. + exists(int i | result = TPrintRegExpNode(regexNode.getChild(i)) and edgeName = i.toString()) + } + + override int getOrder() { exists(RETV::RegExpTerm p | p.getChild(result) = regexNode) } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + regexNode.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +/** + * 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/ruby/ql/lib/codeql/ruby/regexp/ExponentialBackTracking.qll b/ruby/ql/lib/codeql/ruby/regexp/ExponentialBackTracking.qll new file mode 100644 index 000000000000..a805366bab85 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/regexp/ExponentialBackTracking.qll @@ -0,0 +1,343 @@ +private import ReDoSUtil +private import RegExpTreeView +private import codeql.Locations + +/* + * This query implements the analysis described in the following two papers: + * + * James Kirrage, Asiri Rathnayake, Hayo Thielecke: Static Analysis for + * Regular Expression Denial-of-Service Attacks. NSS 2013. + * (http://www.cs.bham.ac.uk/~hxt/research/reg-exp-sec.pdf) + * Asiri Rathnayake, Hayo Thielecke: Static Analysis for Regular Expression + * Exponential Runtime via Substructural Logics. 2014. + * (https://www.cs.bham.ac.uk/~hxt/research/redos_full.pdf) + * + * The basic idea is to search for overlapping cycles in the NFA, that is, + * states `q` such that there are two distinct paths from `q` to itself + * that consume the same word `w`. + * + * For any such state `q`, an attack string can be constructed as follows: + * concatenate a prefix `v` that takes the NFA to `q` with `n` copies of + * the word `w` that leads back to `q` along two different paths, followed + * by a suffix `x` that is _not_ accepted in state `q`. A backtracking + * implementation will need to explore at least 2^n different ways of going + * from `q` back to itself while trying to match the `n` copies of `w` + * before finally giving up. + * + * Now in order to identify overlapping cycles, all we have to do is find + * pumpable forks, that is, states `q` that can transition to two different + * states `r1` and `r2` on the same input symbol `c`, such that there are + * paths from both `r1` and `r2` to `q` that consume the same word. The latter + * condition is equivalent to saying that `(q, q)` is reachable from `(r1, r2)` + * in the product NFA. + * + * This is what the query does. It makes a simple attempt to construct a + * prefix `v` leading into `q`, but only to improve the alert message. + * And the query tries to prove the existence of a suffix that ensures + * rejection. This check might fail, which can cause false positives. + * + * Finally, sometimes it depends on the translation whether the NFA generated + * for a regular expression has a pumpable fork or not. We implement one + * particular translation, which may result in false positives or negatives + * relative to some particular JavaScript engine. + * + * More precisely, the query constructs an NFA from a regular expression `r` + * as follows: + * + * * Every sub-term `t` gives rise to an NFA state `Match(t,i)`, representing + * the state of the automaton before attempting to match the `i`th character in `t`. + * * There is one accepting state `Accept(r)`. + * * There is a special `AcceptAnySuffix(r)` state, which accepts any suffix string + * by using an epsilon transition to `Accept(r)` and an any transition to itself. + * * Transitions between states may be labelled with epsilon, or an abstract + * input symbol. + * * Each abstract input symbol represents a set of concrete input characters: + * either a single character, a set of characters represented by a + * character class, or the set of all characters. + * * The product automaton is constructed lazily, starting with pair states + * `(q, q)` where `q` is a fork, and proceding along an over-approximate + * step relation. + * * The over-approximate step relation allows transitions along pairs of + * abstract input symbols where the symbols have overlap in the characters they accept. + * * Once a trace of pairs of abstract input symbols that leads from a fork + * back to itself has been identified, we attempt to construct a concrete + * string corresponding to it, which may fail. + * * Lastly we ensure that any state reached by repeating `n` copies of `w` has + * a suffix `x` (possible empty) that is most likely __not__ accepted. + */ + +/** + * Holds if state `s` might be inside a backtracking repetition. + */ +pragma[noinline] +private predicate stateInsideBacktracking(State s) { + s.getRepr().getParent*() instanceof MaybeBacktrackingRepetition +} + +/** + * A infinitely repeating quantifier that might backtrack. + */ +private class MaybeBacktrackingRepetition extends InfiniteRepetitionQuantifier { + MaybeBacktrackingRepetition() { + exists(RegExpTerm child | + child instanceof RegExpAlt or + child instanceof RegExpQuantifier + | + child.getParent+() = this + ) + } +} + +/** + * A state in the product automaton. + * + * We lazily only construct those states that we are actually + * going to need: `(q, q)` for every fork state `q`, and any + * pair of states that can be reached from a pair that we have + * already constructed. To cut down on the number of states, + * we only represent states `(q1, q2)` where `q1` is lexicographically + * no bigger than `q2`. + * + * States are only constructed if both states in the pair are + * inside a repetition that might backtrack. + */ +private newtype TStatePair = + MkStatePair(State q1, State q2) { + isFork(q1, _, _, _, _) and q2 = q1 + or + (step(_, _, _, q1, q2) or step(_, _, _, q2, q1)) and + rankState(q1) <= rankState(q2) + } + +/** + * Gets a unique number for a `state`. + * Is used to create an ordering of states, where states with the same `toString()` will be ordered differently. + */ +private int rankState(State state) { + state = + rank[result](State s, Location l | + l = s.getRepr().getLocation() + | + s order by l.getStartLine(), l.getStartColumn(), s.toString() + ) +} + +/** + * A state in the product automaton. + */ +private class StatePair extends TStatePair { + State q1; + State q2; + + StatePair() { this = MkStatePair(q1, q2) } + + /** Gets a textual representation of this element. */ + string toString() { result = "(" + q1 + ", " + q2 + ")" } + + /** Gets the first component of the state pair. */ + State getLeft() { result = q1 } + + /** Gets the second component of the state pair. */ + State getRight() { result = q2 } +} + +/** + * Holds for all constructed state pairs. + * + * Used in `statePairDist` + */ +private predicate isStatePair(StatePair p) { any() } + +/** + * Holds if there are transitions from the components of `q` to the corresponding + * components of `r`. + * + * Used in `statePairDist` + */ +private predicate delta2(StatePair q, StatePair r) { step(q, _, _, r) } + +/** + * Gets the minimum length of a path from `q` to `r` in the + * product automaton. + */ +private int statePairDist(StatePair q, StatePair r) = + shortestDistances(isStatePair/1, delta2/2)(q, r, result) + +/** + * Holds if there are transitions from `q` to `r1` and from `q` to `r2` + * labelled with `s1` and `s2`, respectively, where `s1` and `s2` do not + * trivially have an empty intersection. + * + * This predicate only holds for states associated with regular expressions + * that have at least one repetition quantifier in them (otherwise the + * expression cannot be vulnerable to ReDoS attacks anyway). + */ +pragma[noopt] +private predicate isFork(State q, InputSymbol s1, InputSymbol s2, State r1, State r2) { + stateInsideBacktracking(q) and + exists(State q1, State q2 | + q1 = epsilonSucc*(q) and + delta(q1, s1, r1) and + q2 = epsilonSucc*(q) and + delta(q2, s2, r2) and + // Use pragma[noopt] to prevent intersect(s1,s2) from being the starting point of the join. + // From (s1,s2) it would find a huge number of intermediate state pairs (q1,q2) originating from different literals, + // and discover at the end that no `q` can reach both `q1` and `q2` by epsilon transitions. + exists(intersect(s1, s2)) + | + s1 != s2 + or + r1 != r2 + or + r1 = r2 and q1 != q2 + or + // If q can reach itself by epsilon transitions, then there are two distinct paths to the q1/q2 state: + // one that uses the loop and one that doesn't. The engine will separately attempt to match with each path, + // despite ending in the same state. The "fork" thus arises from the choice of whether to use the loop or not. + // To avoid every state in the loop becoming a fork state, + // we arbitrarily pick the InfiniteRepetitionQuantifier state as the canonical fork state for the loop + // (every epsilon-loop must contain such a state). + // + // We additionally require that the there exists another InfiniteRepetitionQuantifier `mid` on the path from `q` to itself. + // This is done to avoid flagging regular expressions such as `/(a?)*b/` - that only has polynomial runtime, and is detected by `js/polynomial-redos`. + // The below code is therefore a heuritic, that only flags regular expressions such as `/(a*)*b/`, + // and does not flag regular expressions such as `/(a?b?)c/`, but the latter pattern is not used frequently. + r1 = r2 and + q1 = q2 and + epsilonSucc+(q) = q and + exists(RegExpTerm term | term = q.getRepr() | term instanceof InfiniteRepetitionQuantifier) and + // One of the mid states is an infinite quantifier itself + exists(State mid, RegExpTerm term | + mid = epsilonSucc+(q) and + term = mid.getRepr() and + term instanceof InfiniteRepetitionQuantifier and + q = epsilonSucc+(mid) and + not mid = q + ) + ) and + stateInsideBacktracking(r1) and + stateInsideBacktracking(r2) +} + +/** + * Gets the state pair `(q1, q2)` or `(q2, q1)`; note that only + * one or the other is defined. + */ +private StatePair mkStatePair(State q1, State q2) { + result = MkStatePair(q1, q2) or result = MkStatePair(q2, q1) +} + +/** + * Holds if there are transitions from the components of `q` to the corresponding + * components of `r` labelled with `s1` and `s2`, respectively. + */ +private predicate step(StatePair q, InputSymbol s1, InputSymbol s2, StatePair r) { + exists(State r1, State r2 | step(q, s1, s2, r1, r2) and r = mkStatePair(r1, r2)) +} + +/** + * Holds if there are transitions from the components of `q` to `r1` and `r2` + * labelled with `s1` and `s2`, respectively. + * + * We only consider transitions where the resulting states `(r1, r2)` are both + * inside a repetition that might backtrack. + */ +pragma[noopt] +private predicate step(StatePair q, InputSymbol s1, InputSymbol s2, State r1, State r2) { + exists(State q1, State q2 | q.getLeft() = q1 and q.getRight() = q2 | + deltaClosed(q1, s1, r1) and + deltaClosed(q2, s2, r2) and + // use noopt to force the join on `intersect` to happen last. + exists(intersect(s1, s2)) + ) and + stateInsideBacktracking(r1) and + stateInsideBacktracking(r2) +} + +private newtype TTrace = + Nil() or + Step(InputSymbol s1, InputSymbol s2, TTrace t) { + exists(StatePair p | + isReachableFromFork(_, p, t, _) and + step(p, s1, s2, _) + ) + or + t = Nil() and isFork(_, s1, s2, _, _) + } + +/** + * A list of pairs of input symbols that describe a path in the product automaton + * starting from some fork state. + */ +private class Trace extends TTrace { + /** Gets a textual representation of this element. */ + string toString() { + this = Nil() and result = "Nil()" + or + exists(InputSymbol s1, InputSymbol s2, Trace t | this = Step(s1, s2, t) | + result = "Step(" + s1 + ", " + s2 + ", " + t + ")" + ) + } +} + +/** + * Gets a string corresponding to the trace `t`. + */ +private string concretise(Trace t) { + t = Nil() and result = "" + or + exists(InputSymbol s1, InputSymbol s2, Trace rest | t = Step(s1, s2, rest) | + result = concretise(rest) + intersect(s1, s2) + ) +} + +/** + * Holds if `r` is reachable from `(fork, fork)` under input `w`, and there is + * a path from `r` back to `(fork, fork)` with `rem` steps. + */ +private predicate isReachableFromFork(State fork, StatePair r, Trace w, int rem) { + // base case + exists(InputSymbol s1, InputSymbol s2, State q1, State q2 | + isFork(fork, s1, s2, q1, q2) and + r = MkStatePair(q1, q2) and + w = Step(s1, s2, Nil()) and + rem = statePairDist(r, MkStatePair(fork, fork)) + ) + or + // recursive case + exists(StatePair p, Trace v, InputSymbol s1, InputSymbol s2 | + isReachableFromFork(fork, p, v, rem + 1) and + step(p, s1, s2, r) and + w = Step(s1, s2, v) and + rem >= statePairDist(r, MkStatePair(fork, fork)) + ) +} + +/** + * Gets a state in the product automaton from which `(fork, fork)` is + * reachable in zero or more epsilon transitions. + */ +private StatePair getAForkPair(State fork) { + isFork(fork, _, _, _, _) and + result = MkStatePair(epsilonPred*(fork), epsilonPred*(fork)) +} + +/** + * Holds if `fork` is a pumpable fork with word `w`. + */ +private predicate isPumpable(State fork, string w) { + exists(StatePair q, Trace t | + isReachableFromFork(fork, q, t, _) and + q = getAForkPair(fork) and + w = concretise(t) + ) +} + +/** + * An instantiation of `ReDoSConfiguration` for exponential backtracking. + */ +class ExponentialReDoSConfiguration extends ReDoSConfiguration { + ExponentialReDoSConfiguration() { this = "ExponentialReDoSConfiguration" } + + override predicate isReDoSCandidate(State state, string pump) { isPumpable(state, pump) } +} diff --git a/ruby/ql/lib/codeql/ruby/regexp/ParseRegExp.qll b/ruby/ql/lib/codeql/ruby/regexp/ParseRegExp.qll new file mode 100644 index 000000000000..da7a79173074 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/regexp/ParseRegExp.qll @@ -0,0 +1,891 @@ +/** + * Library for parsing for Ruby regular expressions. + * + * N.B. does not yet handle stripping whitespace and comments in regexes with + * the `x` (free-spacing) flag. + */ + +private import codeql.ruby.ast.Literal as AST +private import codeql.Locations + +class RegExp extends AST::RegExpLiteral { + /** + * Helper predicate for `charSetStart(int start, int end)`. + * + * In order to identify left brackets ('[') which actually start a character class, + * we perform a left to right scan of the string. + * + * To avoid negative recursion we return a boolean. See `escaping`, + * the helper for `escapingChar`, for a clean use of this pattern. + * + * result is true for those start chars that actually mark a start of a char set. + */ + boolean charSetStart(int pos) { + exists(int index | + // is opening bracket + this.charSetDelimiter(index, pos) = true and + ( + // if this is the first bracket, `pos` starts a char set + index = 1 and result = true + or + // if the previous char set delimiter was not a closing bracket, `pos` does + // not start a char set. This is needed to handle cases such as `[[]` (a + // char set that matches the `[` char) + index > 1 and + not this.charSetDelimiter(index - 1, _) = false and + result = false + or + // special handling of cases such as `[][]` (the character-set of the characters `]` and `[`). + exists(int prevClosingBracketPos | + // previous bracket is a closing bracket + this.charSetDelimiter(index - 1, prevClosingBracketPos) = false and + if + // check if the character that comes before the previous closing bracket + // is an opening bracket (taking `^` into account) + // check if the character that comes before the previous closing bracket + // is an opening bracket (taking `^` into account) + exists(int posBeforePrevClosingBracket | + if this.getChar(prevClosingBracketPos - 1) = "^" + then posBeforePrevClosingBracket = prevClosingBracketPos - 2 + else posBeforePrevClosingBracket = prevClosingBracketPos - 1 + | + this.charSetDelimiter(index - 2, posBeforePrevClosingBracket) = true + ) + then + // brackets without anything in between is not valid character ranges, so + // the first closing bracket in `[]]` and `[^]]` does not count, + // + // and we should _not_ mark the second opening bracket in `[][]` and `[^][]` + // as starting a new char set. ^ ^ + exists(int posBeforePrevClosingBracket | + this.charSetDelimiter(index - 2, posBeforePrevClosingBracket) = true + | + result = this.charSetStart(posBeforePrevClosingBracket).booleanNot() + ) + else + // if not, `pos` does in fact mark a real start of a character range + result = true + ) + ) + ) + } + + /** + * Helper predicate for chars that could be character-set delimiters. + * Holds if the (non-escaped) char at `pos` in the string, is the (one-based) `index` occurrence of a bracket (`[` or `]`) in the string. + * Result if `true` is the char is `[`, and `false` if the char is `]`. + */ + boolean charSetDelimiter(int index, int pos) { + pos = + rank[index](int p | + (this.nonEscapedCharAt(p) = "[" or this.nonEscapedCharAt(p) = "]") and + // Brackets that art part of POSIX expressions should not count as + // char-set delimiters. + not exists(int x, int y | + this.posixStyleNamedCharacterProperty(x, y, _) and pos >= x and pos < y + ) + ) and + ( + this.nonEscapedCharAt(pos) = "[" and result = true + or + this.nonEscapedCharAt(pos) = "]" and result = false + ) + } + + predicate charSetStart(int start, int end) { + this.charSetStart(start) = true and + ( + this.getChar(start + 1) = "^" and end = start + 2 + or + not this.getChar(start + 1) = "^" and end = start + 1 + ) + } + + /** Whether there is a character class, between start (inclusive) and end (exclusive) */ + predicate charSet(int start, int end) { + exists(int innerStart, int innerEnd | + this.charSetStart(start, innerStart) and + not this.charSetStart(_, start) + | + end = innerEnd + 1 and + innerEnd = + min(int e | + e > innerStart and + this.nonEscapedCharAt(e) = "]" and + not exists(int x, int y | + this.posixStyleNamedCharacterProperty(x, y, _) and e >= x and e < y + ) + | + e + ) + ) + } + + predicate charSetToken(int charsetStart, int index, int tokenStart, int tokenEnd) { + tokenStart = + rank[index](int start, int end | this.charSetToken(charsetStart, start, end) | start) and + this.charSetToken(charsetStart, tokenStart, tokenEnd) + } + + /** Either a char or a - */ + predicate charSetToken(int charsetStart, int start, int end) { + this.charSetStart(charsetStart, start) and + ( + this.escapedCharacter(start, end) + or + this.namedCharacterProperty(start, end, _) + or + exists(this.nonEscapedCharAt(start)) and end = start + 1 + ) + or + this.charSetToken(charsetStart, _, start) and + ( + this.escapedCharacter(start, end) + or + this.namedCharacterProperty(start, end, _) + or + exists(this.nonEscapedCharAt(start)) and + end = start + 1 and + not this.getChar(start) = "]" + ) + } + + predicate charSetChild(int charsetStart, int start, int end) { + this.charSetToken(charsetStart, start, end) and + not exists(int rangeStart, int rangeEnd | + this.charRange(charsetStart, rangeStart, _, _, rangeEnd) and + rangeStart <= start and + rangeEnd >= end + ) + or + this.charRange(charsetStart, start, _, _, end) + } + + predicate charRange(int charsetStart, int start, int lowerEnd, int upperStart, int end) { + exists(int index | + this.charRangeEnd(charsetStart, index) = true and + this.charSetToken(charsetStart, index - 2, start, lowerEnd) and + this.charSetToken(charsetStart, index, upperStart, end) + ) + } + + private boolean charRangeEnd(int charsetStart, int index) { + this.charSetToken(charsetStart, index, _, _) and + ( + index in [1, 2] and result = false + or + index > 2 and + exists(int connectorStart | + this.charSetToken(charsetStart, index - 1, connectorStart, _) and + this.nonEscapedCharAt(connectorStart) = "-" and + result = + this.charRangeEnd(charsetStart, index - 2) + .booleanNot() + .booleanAnd(this.charRangeEnd(charsetStart, index - 1).booleanNot()) + ) + or + not exists(int connectorStart | + this.charSetToken(charsetStart, index - 1, connectorStart, _) and + this.nonEscapedCharAt(connectorStart) = "-" + ) and + result = false + ) + } + + predicate escapingChar(int pos) { this.escaping(pos) = true } + + private boolean escaping(int pos) { + pos = -1 and result = false + or + this.getChar(pos) = "\\" and result = this.escaping(pos - 1).booleanNot() + or + this.getChar(pos) != "\\" and result = false + } + + /** Gets the text of this regex */ + string getText() { result = this.getValueText() } + + string getChar(int i) { result = this.getText().charAt(i) } + + string nonEscapedCharAt(int i) { + result = this.getText().charAt(i) and + not exists(int x, int y | this.escapedCharacter(x, y) and i in [x .. y - 1]) + } + + private predicate isOptionDivider(int i) { this.nonEscapedCharAt(i) = "|" } + + private predicate isGroupEnd(int i) { this.nonEscapedCharAt(i) = ")" and not this.inCharSet(i) } + + private predicate isGroupStart(int i) { this.nonEscapedCharAt(i) = "(" and not this.inCharSet(i) } + + predicate failedToParse(int i) { + exists(this.getChar(i)) and + not exists(int start, int end | + this.topLevel(start, end) and + start <= i and + end > i + ) + } + + /** Matches named character properties such as `\p{Word}` and `[[:digit:]]` */ + predicate namedCharacterProperty(int start, int end, string name) { + pStyleNamedCharacterProperty(start, end, name) or + posixStyleNamedCharacterProperty(start, end, name) + } + + /** Gets the name of the character property in start,end */ + string getCharacterPropertyName(int start, int end) { + this.namedCharacterProperty(start, end, result) + } + + /** Matches a POSIX bracket expression such as `[:alnum:]` within a character class. */ + private predicate posixStyleNamedCharacterProperty(int start, int end, string name) { + this.getChar(start) = "[" and + this.getChar(start + 1) = ":" and + end = + min(int e | + e > start and + this.getChar(e - 2) = ":" and + this.getChar(e - 1) = "]" + | + e + ) and + exists(int nameStart | + this.getChar(start + 2) = "^" and nameStart = start + 3 + or + not this.getChar(start + 2) = "^" and nameStart = start + 2 + | + name = this.getText().substring(nameStart, end - 2) + ) + } + + /** + * Matches named character properties. For example: + * - `\p{Space}` + * - `\P{Digit}` upper-case P means inverted + * - `\p{^Word}` caret also means inverted + * + * These can occur both inside and outside of character classes. + */ + private predicate pStyleNamedCharacterProperty(int start, int end, string name) { + this.escapingChar(start) and + this.getChar(start + 1) in ["p", "P"] and + this.getChar(start + 2) = "{" and + this.getChar(end - 1) = "}" and + end > start and + not exists(int i | start + 2 < i and i < end - 1 | this.getChar(i) = "}") and + exists(int nameStart | + this.getChar(start + 3) = "^" and nameStart = start + 4 + or + not this.getChar(start + 3) = "^" and nameStart = start + 3 + | + name = this.getText().substring(nameStart, end - 1) + ) + } + + /** + * Holds if the named character property is inverted. Examples for which it holds: + * - `\P{Digit}` upper-case P means inverted + * - `\p{^Word}` caret also means inverted + * - `[[:^digit:]]` + * + * Examples for which it doesn't hold: + * - `\p{Word}` + * - `\P{^Space}` - upper-case P and caret cancel each other out + * - `[[:alnum:]]` + */ + predicate namedCharacterPropertyIsInverted(int start, int end) { + this.pStyleNamedCharacterProperty(start, end, _) and + exists(boolean upperP, boolean caret | + (if this.getChar(start + 1) = "P" then upperP = true else upperP = false) and + (if this.getChar(start + 3) = "^" then caret = true else caret = false) + | + upperP.booleanXor(caret) = true + ) + or + this.posixStyleNamedCharacterProperty(start, end, _) and + this.getChar(start + 3) = "^" + } + + predicate escapedCharacter(int start, int end) { + this.escapingChar(start) and + not this.numberedBackreference(start, _, _) and + not this.namedBackreference(start, _, _) and + not this.pStyleNamedCharacterProperty(start, _, _) and + ( + // hex char \xhh + this.getChar(start + 1) = "x" and end = start + 4 + or + // wide hex char \uhhhh + this.getChar(start + 1) = "u" and end = start + 6 + or + // escape not handled above; update when adding a new case + not this.getChar(start + 1) in ["x", "u"] and + not exists(this.getChar(start + 1).toInt()) and + end = start + 2 + ) + } + + predicate inCharSet(int index) { + exists(int x, int y | this.charSet(x, y) and index in [x + 1 .. y - 2]) + } + + predicate inPosixBracket(int index) { + exists(int x, int y | + this.posixStyleNamedCharacterProperty(x, y, _) and index in [x + 1 .. y - 2] + ) + } + + /** 'Simple' characters are any that don't alter the parsing of the regex. */ + private predicate simpleCharacter(int start, int end) { + end = start + 1 and + not this.charSet(start, _) and + not this.charSet(_, start + 1) and + not exists(int x, int y | + this.posixStyleNamedCharacterProperty(x, y, _) and + start >= x and + end <= y + ) and + exists(string c | c = this.getChar(start) | + exists(int x, int y, int z | + this.charSet(x, z) and + this.charSetStart(x, y) + | + start = y + or + start = z - 2 + or + start > y and start < z - 2 and not this.charRange(_, _, start, end, _) + ) + or + not this.inCharSet(start) and + not c = "(" and + not c = "[" and + not c = ")" and + not c = "|" and + not this.qualifier(start, _, _, _) + ) + } + + predicate character(int start, int end) { + ( + this.simpleCharacter(start, end) and + not exists(int x, int y | this.escapedCharacter(x, y) and x <= start and y >= end) + or + this.escapedCharacter(start, end) + ) and + not exists(int x, int y | this.groupStart(x, y) and x <= start and y >= end) and + not exists(int x, int y | this.backreference(x, y) and x <= start and y >= end) and + not exists(int x, int y | + this.pStyleNamedCharacterProperty(x, y, _) and x <= start and y >= end + ) + } + + predicate normalCharacter(int start, int end) { + this.character(start, end) and + not this.specialCharacter(start, end, _) + } + + predicate specialCharacter(int start, int end, string char) { + this.character(start, end) and + not this.inCharSet(start) and + ( + end = start + 1 and + char = this.getChar(start) and + (char = "$" or char = "^" or char = ".") + or + end = start + 2 and + this.escapingChar(start) and + char = this.getText().substring(start, end) and + char = ["\\A", "\\Z", "\\z"] + ) + } + + /** Whether the text in the range `start,end` is a group */ + predicate group(int start, int end) { + this.groupContents(start, end, _, _) + or + this.emptyGroup(start, end) + } + + /** Gets the number of the group in start,end */ + int getGroupNumber(int start, int end) { + this.group(start, end) and + result = + count(int i | this.group(i, _) and i < start and not this.nonCapturingGroupStart(i, _)) + 1 + } + + /** Gets the name, if it has one, of the group in start,end */ + string getGroupName(int start, int end) { + this.group(start, end) and + exists(int nameEnd | + this.namedGroupStart(start, nameEnd) and + result = this.getText().substring(start + 4, nameEnd - 1) + ) + } + + /** Whether the text in the range start, end is a group and can match the empty string. */ + predicate zeroWidthMatch(int start, int end) { + this.emptyGroup(start, end) + or + this.negativeAssertionGroup(start, end) + or + this.positiveLookaheadAssertionGroup(start, end) + or + this.positiveLookbehindAssertionGroup(start, end) + } + + predicate emptyGroup(int start, int end) { + exists(int endm1 | end = endm1 + 1 | + this.groupStart(start, endm1) and + this.isGroupEnd(endm1) + ) + } + + private predicate emptyMatchAtStartGroup(int start, int end) { + this.emptyGroup(start, end) + or + this.negativeAssertionGroup(start, end) + or + this.positiveLookaheadAssertionGroup(start, end) + } + + private predicate emptyMatchAtEndGroup(int start, int end) { + this.emptyGroup(start, end) + or + this.negativeAssertionGroup(start, end) + or + this.positiveLookbehindAssertionGroup(start, end) + } + + private predicate negativeAssertionGroup(int start, int end) { + exists(int inStart | + this.negativeLookaheadAssertionStart(start, inStart) + or + this.negativeLookbehindAssertionStart(start, inStart) + | + this.groupContents(start, end, inStart, _) + ) + } + + predicate negativeLookaheadAssertionGroup(int start, int end) { + exists(int inStart | this.negativeLookaheadAssertionStart(start, inStart) | + this.groupContents(start, end, inStart, _) + ) + } + + predicate negativeLookbehindAssertionGroup(int start, int end) { + exists(int inStart | this.negativeLookbehindAssertionStart(start, inStart) | + this.groupContents(start, end, inStart, _) + ) + } + + predicate positiveLookaheadAssertionGroup(int start, int end) { + exists(int inStart | this.lookaheadAssertionStart(start, inStart) | + this.groupContents(start, end, inStart, _) + ) + } + + predicate positiveLookbehindAssertionGroup(int start, int end) { + exists(int inStart | this.lookbehindAssertionStart(start, inStart) | + this.groupContents(start, end, inStart, _) + ) + } + + private predicate groupStart(int start, int end) { + this.nonCapturingGroupStart(start, end) + or + this.namedGroupStart(start, end) + or + this.lookaheadAssertionStart(start, end) + or + this.negativeLookaheadAssertionStart(start, end) + or + this.lookbehindAssertionStart(start, end) + or + this.negativeLookbehindAssertionStart(start, end) + or + this.commentGroupStart(start, end) + or + this.simpleGroupStart(start, end) + } + + /** Matches the start of a non-capturing group, e.g. `(?:` */ + private predicate nonCapturingGroupStart(int start, int end) { + this.isGroupStart(start) and + this.getChar(start + 1) = "?" and + this.getChar(start + 2) = ":" and + end = start + 3 + } + + /** Matches the start of a simple group, e.g. `(a+)`. */ + private predicate simpleGroupStart(int start, int end) { + this.isGroupStart(start) and + this.getChar(start + 1) != "?" and + end = start + 1 + } + + /** + * Matches the start of a named group, such as: + * - `(?\w+)` + * - `(?'name'\w+)` + */ + private predicate namedGroupStart(int start, int end) { + this.isGroupStart(start) and + this.getChar(start + 1) = "?" and + ( + this.getChar(start + 2) = "<" and + not this.getChar(start + 3) = "=" and // (?<=foo) is a positive lookbehind assertion + not this.getChar(start + 3) = "!" and // (? start + 3 and this.getChar(i) = ">") and + end = nameEnd + 1 + ) + or + this.getChar(start + 2) = "'" and + exists(int nameEnd | + nameEnd = min(int i | i > start + 2 and this.getChar(i) = "'") and end = nameEnd + 1 + ) + ) + } + + /** Matches the start of a positive lookahead assertion, i.e. `(?=`. */ + private predicate lookaheadAssertionStart(int start, int end) { + this.isGroupStart(start) and + this.getChar(start + 1) = "?" and + this.getChar(start + 2) = "=" and + end = start + 3 + } + + /** Matches the start of a negative lookahead assertion, i.e. `(?!`. */ + private predicate negativeLookaheadAssertionStart(int start, int end) { + this.isGroupStart(start) and + this.getChar(start + 1) = "?" and + this.getChar(start + 2) = "!" and + end = start + 3 + } + + /** Matches the start of a positive lookbehind assertion, i.e. `(?<=`. */ + private predicate lookbehindAssertionStart(int start, int end) { + this.isGroupStart(start) and + this.getChar(start + 1) = "?" and + this.getChar(start + 2) = "<" and + this.getChar(start + 3) = "=" and + end = start + 4 + } + + /** Matches the start of a negative lookbehind assertion, i.e. `(?`. */ + predicate namedBackreference(int start, int end, string name) { + this.escapingChar(start) and + this.getChar(start + 1) = "k" and + this.getChar(start + 2) = "<" and + exists(int nameEnd | nameEnd = min(int i | i > start + 3 and this.getChar(i) = ">") | + end = nameEnd + 1 and + name = this.getText().substring(start + 3, nameEnd) + ) + } + + /** Matches a numbered backreference, e.g. `\1`. */ + predicate numberedBackreference(int start, int end, int value) { + this.escapingChar(start) and + not this.getChar(start + 1) = "0" and + exists(string text, string svalue, int len | + end = start + len and + text = this.getText() and + len in [2 .. 3] + | + svalue = text.substring(start + 1, start + len) and + value = svalue.toInt() and + not exists(text.substring(start + 1, start + len + 1).toInt()) and + value > 0 + ) + } + + /** Whether the text in the range `start,end` is a back reference */ + predicate backreference(int start, int end) { + this.numberedBackreference(start, end, _) + or + this.namedBackreference(start, end, _) + } + + /** Gets the number of the back reference in start,end */ + int getBackRefNumber(int start, int end) { this.numberedBackreference(start, end, result) } + + /** Gets the name, if it has one, of the back reference in start,end */ + string getBackRefName(int start, int end) { this.namedBackreference(start, end, result) } + + private predicate baseItem(int start, int end) { + this.character(start, end) and + not exists(int x, int y | this.charSet(x, y) and x <= start and y >= end) + or + this.group(start, end) + or + this.charSet(start, end) + or + this.backreference(start, end) + or + this.pStyleNamedCharacterProperty(start, end, _) + } + + private predicate qualifier(int start, int end, boolean maybeEmpty, boolean mayRepeatForever) { + this.shortQualifier(start, end, maybeEmpty, mayRepeatForever) and + not this.getChar(end) = "?" + or + exists(int shortEnd | this.shortQualifier(start, shortEnd, maybeEmpty, mayRepeatForever) | + if this.getChar(shortEnd) = "?" then end = shortEnd + 1 else end = shortEnd + ) + } + + private predicate shortQualifier(int start, int end, boolean maybeEmpty, boolean mayRepeatForever) { + ( + this.getChar(start) = "+" and maybeEmpty = false and mayRepeatForever = true + or + this.getChar(start) = "*" and maybeEmpty = true and mayRepeatForever = true + or + this.getChar(start) = "?" and maybeEmpty = true and mayRepeatForever = false + ) and + end = start + 1 + or + exists(string lower, string upper | + this.multiples(start, end, lower, upper) and + (if lower = "" or lower.toInt() = 0 then maybeEmpty = true else maybeEmpty = false) and + if upper = "" then mayRepeatForever = true else mayRepeatForever = false + ) + } + + predicate multiples(int start, int end, string lower, string upper) { + exists(string text, string match, string inner | + text = this.getText() and + end = start + match.length() and + inner = match.substring(1, match.length() - 1) + | + match = text.regexpFind("\\{[0-9]+\\}", _, start) and + lower = inner and + upper = lower + or + match = text.regexpFind("\\{[0-9]*,[0-9]*\\}", _, start) and + exists(int commaIndex | + commaIndex = inner.indexOf(",") and + lower = inner.prefix(commaIndex) and + upper = inner.suffix(commaIndex + 1) + ) + ) + } + + /** + * Whether the text in the range start,end is a qualified item, where item is a character, + * a character set or a group. + */ + predicate qualifiedItem(int start, int end, boolean maybeEmpty, boolean mayRepeatForever) { + this.qualifiedPart(start, _, end, maybeEmpty, mayRepeatForever) + } + + predicate qualifiedPart( + int start, int partEnd, int end, boolean maybeEmpty, boolean mayRepeatForever + ) { + this.baseItem(start, partEnd) and + this.qualifier(partEnd, end, maybeEmpty, mayRepeatForever) + } + + predicate item(int start, int end) { + this.qualifiedItem(start, end, _, _) + or + this.baseItem(start, end) and not this.qualifier(end, _, _, _) + } + + private predicate subsequence(int start, int end) { + ( + start = 0 or + this.groupStart(_, start) or + this.isOptionDivider(start - 1) + ) and + this.item(start, end) + or + exists(int mid | + this.subsequence(start, mid) and + this.item(mid, end) + ) + } + + /** + * Whether the text in the range start,end is a sequence of 1 or more items, where an item is a character, + * a character set or a group. + */ + predicate sequence(int start, int end) { + this.sequenceOrQualified(start, end) and + not this.qualifiedItem(start, end, _, _) + } + + private predicate sequenceOrQualified(int start, int end) { + this.subsequence(start, end) and + not this.itemStart(end) + } + + private predicate itemStart(int start) { + this.character(start, _) or + this.isGroupStart(start) or + this.charSet(start, _) or + this.backreference(start, _) or + this.namedCharacterProperty(start, _, _) + } + + private predicate itemEnd(int end) { + this.character(_, end) + or + exists(int endm1 | this.isGroupEnd(endm1) and end = endm1 + 1) + or + this.charSet(_, end) + or + this.qualifier(_, end, _, _) + } + + private predicate topLevel(int start, int end) { + this.subalternation(start, end, _) and + not this.isOptionDivider(end) + } + + private predicate subalternation(int start, int end, int itemStart) { + this.sequenceOrQualified(start, end) and + not this.isOptionDivider(start - 1) and + itemStart = start + or + start = end and + not this.itemEnd(start) and + this.isOptionDivider(end) and + itemStart = start + or + exists(int mid | + this.subalternation(start, mid, _) and + this.isOptionDivider(mid) and + itemStart = mid + 1 + | + this.sequenceOrQualified(itemStart, end) + or + not this.itemStart(end) and end = itemStart + ) + } + + /** + * Whether the text in the range start,end is an alternation + */ + predicate alternation(int start, int end) { + this.topLevel(start, end) and + exists(int less | this.subalternation(start, less, _) and less < end) + } + + /** + * Whether the text in the range start,end is an alternation and the text in partStart, partEnd is one of the + * options in that alternation. + */ + predicate alternationOption(int start, int end, int partStart, int partEnd) { + this.alternation(start, end) and + this.subalternation(start, partEnd, partStart) + } + + /** A part of the regex that may match the start of the string. */ + private predicate firstPart(int start, int end) { + start = 0 and end = this.getText().length() + or + exists(int x | this.firstPart(x, end) | + this.emptyMatchAtStartGroup(x, start) + or + this.qualifiedItem(x, start, true, _) + or + // ^ and \A match the start of the string + this.specialCharacter(x, start, ["^", "\\A"]) + ) + or + exists(int y | this.firstPart(start, y) | + this.item(start, end) + or + this.qualifiedPart(start, end, y, _, _) + ) + or + exists(int x, int y | this.firstPart(x, y) | + this.groupContents(x, y, start, end) + or + this.alternationOption(x, y, start, end) + ) + } + + /** A part of the regex that may match the end of the string. */ + private predicate lastPart(int start, int end) { + start = 0 and end = this.getText().length() + or + exists(int y | this.lastPart(start, y) | + this.emptyMatchAtEndGroup(end, y) + or + this.qualifiedItem(end, y, true, _) + or + // $, \Z, and \z match the end of the string. + this.specialCharacter(end, y, ["$", "\\Z", "\\z"]) + ) + or + exists(int x | + this.lastPart(x, end) and + this.item(start, end) + ) + or + exists(int y | this.lastPart(start, y) | this.qualifiedPart(start, end, y, _, _)) + or + exists(int x, int y | this.lastPart(x, y) | + this.groupContents(x, y, start, end) + or + this.alternationOption(x, y, start, end) + ) + } + + /** + * Whether the item at [start, end) is one of the first items + * to be matched. + */ + predicate firstItem(int start, int end) { + ( + this.character(start, end) + or + this.qualifiedItem(start, end, _, _) + or + this.charSet(start, end) + ) and + this.firstPart(start, end) + } + + /** + * Whether the item at [start, end) is one of the last items + * to be matched. + */ + predicate lastItem(int start, int end) { + ( + this.character(start, end) + or + this.qualifiedItem(start, end, _, _) + or + this.charSet(start, end) + ) and + this.lastPart(start, end) + } +} diff --git a/ruby/ql/lib/codeql/ruby/regexp/PolynomialReDoSCustomizations.qll b/ruby/ql/lib/codeql/ruby/regexp/PolynomialReDoSCustomizations.qll new file mode 100644 index 000000000000..3d3655ad3a98 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/regexp/PolynomialReDoSCustomizations.qll @@ -0,0 +1,131 @@ +/** + * Provides default sources, sinks and sanitizers for reasoning about + * polynomial regular expression denial-of-service attacks, as well + * as extension points for adding your own. + */ + +private import codeql.ruby.AST as AST +private import codeql.ruby.CFG +private import codeql.ruby.DataFlow +private import codeql.ruby.dataflow.RemoteFlowSources +private import codeql.ruby.regexp.ParseRegExp as RegExp +private import codeql.ruby.regexp.RegExpTreeView +private import codeql.ruby.regexp.SuperlinearBackTracking + +module PolynomialReDoS { + /** + * A data flow source node for polynomial regular expression denial-of-service vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink node for polynomial regular expression denial-of-service vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { + /** Gets the regex that is being executed by this node. */ + abstract RegExpTerm getRegExp(); + + /** Gets the node to highlight in the alert message. */ + DataFlow::Node getHighlight() { result = this } + } + + /** + * A sanitizer for polynomial regular expression denial-of-service vulnerabilities. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * A sanitizer guard for polynomial regular expression denial of service + * vulnerabilities. + */ + abstract class SanitizerGuard extends DataFlow::BarrierGuard { } + + /** + * A source of remote user input, considered as a flow source. + */ + class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { } + + /** + * Gets the AST of a regular expression object that can flow to `node`. + */ + RegExpTerm getRegExpObjectFromNode(DataFlow::Node node) { + exists(DataFlow::LocalSourceNode regexp | + regexp.flowsTo(node) and + result = regexp.asExpr().(CfgNodes::ExprNodes::RegExpLiteralCfgNode).getExpr().getParsed() + ) + } + + /** + * A regexp match against a superlinear backtracking term, seen as a sink for + * polynomial regular expression denial-of-service vulnerabilities. + */ + class PolynomialBackTrackingTermMatch extends Sink { + PolynomialBackTrackingTerm term; + DataFlow::ExprNode matchNode; + + PolynomialBackTrackingTermMatch() { + exists(DataFlow::Node regexp | + term.getRootTerm() = getRegExpObjectFromNode(regexp) and + ( + // `=~` or `!~` + exists(CfgNodes::ExprNodes::BinaryOperationCfgNode op | + matchNode.asExpr() = op and + ( + op.getExpr() instanceof AST::RegExpMatchExpr or + op.getExpr() instanceof AST::NoRegExpMatchExpr + ) and + ( + this.asExpr() = op.getLeftOperand() and regexp.asExpr() = op.getRightOperand() + or + this.asExpr() = op.getRightOperand() and regexp.asExpr() = op.getLeftOperand() + ) + ) + or + // Any of the methods on `String` that take a regexp. + exists(CfgNodes::ExprNodes::MethodCallCfgNode call | + matchNode.asExpr() = call and + call.getExpr().getMethodName() = + [ + "[]", "gsub", "gsub!", "index", "match", "match?", "partition", "rindex", + "rpartition", "scan", "slice!", "split", "sub", "sub!" + ] and + this.asExpr() = call.getReceiver() and + regexp.asExpr() = call.getArgument(0) + ) + or + // A call to `match` or `match?` where the regexp is the receiver. + exists(CfgNodes::ExprNodes::MethodCallCfgNode call | + matchNode.asExpr() = call and + call.getExpr().getMethodName() = ["match", "match?"] and + regexp.asExpr() = call.getReceiver() and + this.asExpr() = call.getArgument(0) + ) + ) + ) + } + + override RegExpTerm getRegExp() { result = term } + + override DataFlow::Node getHighlight() { result = matchNode } + } + + /** + * A check on the length of a string, seen as a sanitizer guard. + */ + class LengthGuard extends SanitizerGuard, CfgNodes::ExprNodes::RelationalOperationCfgNode { + private DataFlow::Node input; + + LengthGuard() { + exists(DataFlow::CallNode length, DataFlow::ExprNode operand | + length.asExpr().getExpr().(AST::MethodCall).getMethodName() = "length" and + length.getReceiver() = input and + length.flowsTo(operand) and + operand.getExprNode() = this.getAnOperand() + ) + } + + override predicate checks(CfgNode node, boolean branch) { + node = input.asExpr() and branch = true + } + } +} diff --git a/ruby/ql/lib/codeql/ruby/regexp/PolynomialReDoSQuery.qll b/ruby/ql/lib/codeql/ruby/regexp/PolynomialReDoSQuery.qll new file mode 100644 index 000000000000..db7269d7fdb1 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/regexp/PolynomialReDoSQuery.qll @@ -0,0 +1,37 @@ +/** + * Provides a taint tracking configuration for reasoning about polynomial + * regular expression denial-of-service attacks. + * + * Note, for performance reasons: only import this file if `Configuration` is + * needed. Otherwise, `PolynomialReDoSCustomizations` should be imported + * instead. + */ + +private import codeql.ruby.DataFlow +private import codeql.ruby.TaintTracking + +/** + * Provides a taint-tracking configuration for detecting polynomial regular + * expression denial of service vulnerabilities. + */ +module PolynomialReDoS { + import PolynomialReDoSCustomizations::PolynomialReDoS + + /** + * A taint-tracking configuration for detecting polynomial regular expression + * denial of service vulnerabilities. + */ + class Configuration extends TaintTracking::Configuration { + Configuration() { this = "PolynomialReDoS" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer } + + override predicate isSanitizerGuard(DataFlow::BarrierGuard node) { + node instanceof SanitizerGuard + } + } +} diff --git a/ruby/ql/lib/codeql/ruby/regexp/ReDoSUtil.qll b/ruby/ql/lib/codeql/ruby/regexp/ReDoSUtil.qll new file mode 100644 index 000000000000..496983ea8495 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/regexp/ReDoSUtil.qll @@ -0,0 +1,1186 @@ +/** + * Provides classes for working with regular expressions that can + * perform backtracking in superlinear/exponential time. + * + * This module contains a number of utility predicates for compiling a regular expression into a NFA and reasoning about this NFA. + * + * The `ReDoSConfiguration` contains a `isReDoSCandidate` predicate that is used to + * to determine which states the prefix/suffix search should happen on. + * There is only meant to exist one `ReDoSConfiguration` at a time. + * + * The predicate `hasReDoSResult` outputs a de-duplicated set of + * states that will cause backtracking (a rejecting suffix exists). + */ + +import RegExpTreeView +private import codeql.Locations + +/** + * A configuration for which parts of a regular expression should be considered relevant for + * the different predicates in `ReDoS.qll`. + * Used to adjust the computations for either superlinear or exponential backtracking. + */ +abstract class ReDoSConfiguration extends string { + bindingset[this] + ReDoSConfiguration() { any() } + + /** + * Holds if `state` with the pump string `pump` is a candidate for a + * ReDoS vulnerable state. + * This is used to determine which states are considered for the prefix/suffix construction. + */ + abstract predicate isReDoSCandidate(State state, string pump); +} + +/** + * Holds if repeating `pump' starting at `state` is a candidate for causing backtracking. + * No check whether a rejected suffix exists has been made. + */ +private predicate isReDoSCandidate(State state, string pump) { + any(ReDoSConfiguration conf).isReDoSCandidate(state, pump) and + ( + not any(ReDoSConfiguration conf).isReDoSCandidate(epsilonSucc+(state), _) + or + epsilonSucc+(state) = state and + state = + max(State s, Location l | + s = epsilonSucc+(state) and + l = s.getRepr().getLocation() and + any(ReDoSConfiguration conf).isReDoSCandidate(s, _) and + s.getRepr() instanceof InfiniteRepetitionQuantifier + | + s order by l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine() + ) + ) +} + +/** + * Gets the char after `c` (from a simplified ASCII table). + */ +private string nextChar(string c) { exists(int code | code = ascii(c) | code + 1 = ascii(result)) } + +/** + * Gets an approximation for the ASCII code for `char`. + * Only the easily printable chars are included (so no newline, tab, null, etc). + */ +private int ascii(string char) { + char = + rank[result](string c | + c = + "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + .charAt(_) + ) +} + +/** + * Holds if `t` matches at least an epsilon symbol. + * + * That is, this term does not restrict the language of the enclosing regular expression. + * + * This is implemented as an under-approximation, and this predicate does not hold for sub-patterns in particular. + */ +predicate matchesEpsilon(RegExpTerm t) { + t instanceof RegExpStar + or + t instanceof RegExpOpt + or + t.(RegExpRange).getLowerBound() = 0 + or + exists(RegExpTerm child | + child = t.getAChild() and + matchesEpsilon(child) + | + t instanceof RegExpAlt or + t instanceof RegExpGroup or + t instanceof RegExpPlus or + t instanceof RegExpRange + ) + or + matchesEpsilon(t.(RegExpBackRef).getGroup()) + or + forex(RegExpTerm child | child = t.(RegExpSequence).getAChild() | matchesEpsilon(child)) +} + +/** + * A lookahead/lookbehind that matches the empty string. + */ +class EmptyPositiveSubPatttern extends RegExpSubPattern { + EmptyPositiveSubPatttern() { + ( + this instanceof RegExpPositiveLookahead + or + this instanceof RegExpPositiveLookbehind + ) and + matchesEpsilon(this.getOperand()) + } +} + +/** + * A branch in a disjunction that is the root node in a literal, or a literal + * whose root node is not a disjunction. + */ +class RegExpRoot extends RegExpTerm { + RegExpParent parent; + + RegExpRoot() { + exists(RegExpAlt alt | + alt.isRootTerm() and + this = alt.getAChild() and + parent = alt.getParent() + ) + or + this.isRootTerm() and + not this instanceof RegExpAlt and + parent = this.getParent() + } + + /** + * Holds if this root term is relevant to the ReDoS analysis. + */ + predicate isRelevant() { + // there is at least one repetition + getRoot(any(InfiniteRepetitionQuantifier q)) = this and + // there are no lookbehinds + not exists(RegExpLookbehind lbh | getRoot(lbh) = this) and + // is actually used as a RegExp + isUsedAsRegExp() //and + // // pragmatic performance optimization: ignore minified files. + // not getRootTerm().getParent().(Expr).getTopLevel().isMinified() + } +} + +/** + * A constant in a regular expression that represents valid Unicode character(s). + */ +private class RegexpCharacterConstant extends RegExpConstant { + RegexpCharacterConstant() { this.isCharacter() } +} + +/** + * Holds if `term` is the chosen canonical representative for all terms with string representation `str`. + * + * Using canonical representatives gives a huge performance boost when working with tuples containing multiple `InputSymbol`s. + * The number of `InputSymbol`s is decreased by 3 orders of magnitude or more in some larger benchmarks. + */ +private predicate isCanonicalTerm(RegExpTerm term, string str) { + term = + rank[1](RegExpTerm t, Location loc, File file | + loc = t.getLocation() and + file = t.getFile() and + str = t.getRawValue() + | + t order by t.getFile().getRelativePath(), loc.getStartLine(), loc.getStartColumn() + ) +} + +/** + * An abstract input symbol, representing a set of concrete characters. + */ +private newtype TInputSymbol = + /** An input symbol corresponding to character `c`. */ + Char(string c) { + c = any(RegexpCharacterConstant cc | getRoot(cc).isRelevant()).getValue().charAt(_) + } or + /** + * An input symbol representing all characters matched by + * a (non-universal) character class that has string representation `charClassString`. + */ + CharClass(string charClassString) { + exists(RegExpTerm term | term.getRawValue() = charClassString | getRoot(term).isRelevant()) and + exists(RegExpTerm recc | isCanonicalTerm(recc, charClassString) | + recc instanceof RegExpCharacterClass and + not recc.(RegExpCharacterClass).isUniversalClass() + or + recc instanceof RegExpCharacterClassEscape + or + recc instanceof RegExpNamedCharacterProperty + ) + } or + /** An input symbol representing all characters matched by `.`. */ + Dot() or + /** An input symbol representing all characters. */ + Any() or + /** An epsilon transition in the automaton. */ + Epsilon() + +/** + * Gets the canonical CharClass for `term`. + */ +CharClass getCanonicalCharClass(RegExpTerm term) { + exists(string str | isCanonicalTerm(term, str) | result = CharClass(str)) +} + +/** + * Holds if `a` and `b` are input symbols from the same regexp. + */ +private predicate sharesRoot(TInputSymbol a, TInputSymbol b) { + exists(RegExpRoot root | + belongsTo(a, root) and + belongsTo(b, root) + ) +} + +/** + * Holds if the `a` is an input symbol from a regexp that has root `root`. + */ +private predicate belongsTo(TInputSymbol a, RegExpRoot root) { + exists(State s | getRoot(s.getRepr()) = root | + delta(s, a, _) + or + delta(_, a, s) + ) +} + +/** + * An abstract input symbol, representing a set of concrete characters. + */ +class InputSymbol extends TInputSymbol { + InputSymbol() { not this instanceof Epsilon } + + /** + * Gets a string representation of this input symbol. + */ + string toString() { + this = Char(result) + or + this = CharClass(result) + or + this = Dot() and result = "." + or + this = Any() and result = "[^]" + } +} + +/** + * An abstract input symbol that represents a character class. + */ +abstract private class CharacterClass extends InputSymbol { + /** + * Gets a character that is relevant for intersection-tests involving this + * character class. + * + * Specifically, this is any of the characters mentioned explicitly in the + * character class, offset by one if it is inverted. For character class escapes, + * the result is as if the class had been written out as a series of intervals. + * + * This set is large enough to ensure that for any two intersecting character + * classes, one contains a relevant character from the other. + */ + abstract string getARelevantChar(); + + /** + * Holds if this character class matches `char`. + */ + bindingset[char] + abstract predicate matches(string char); + + /** + * Gets a character matched by this character class. + */ + string choose() { result = getARelevantChar() and matches(result) } +} + +/** + * Provides implementations for `CharacterClass`. + */ +private module CharacterClasses { + /** + * Holds if the character class `cc` has a child (constant or range) that matches `char`. + */ + pragma[noinline] + predicate hasChildThatMatches(RegExpCharacterClass cc, string char) { + exists(getCanonicalCharClass(cc)) and + exists(RegExpTerm child | child = cc.getAChild() | + char = child.(RegexpCharacterConstant).getValue() + or + rangeMatchesOnLetterOrDigits(child, char) + or + not rangeMatchesOnLetterOrDigits(child, _) and + char = getARelevantChar() and + exists(string lo, string hi | child.(RegExpCharacterRange).isRange(lo, hi) | + lo <= char and + char <= hi + ) + or + exists(RegExpCharacterClassEscape escape | escape = child | + escape.getValue() = escape.getValue().toLowerCase() and + classEscapeMatches(escape.getValue(), char) + or + char = getARelevantChar() and + escape.getValue() = escape.getValue().toUpperCase() and + not classEscapeMatches(escape.getValue().toLowerCase(), char) + ) + or + exists(RegExpNamedCharacterProperty charProp | charProp = child | + not charProp.isInverted() and + namedCharacterPropertyMatches(charProp.getName(), char) + or + char = getARelevantChar() and + charProp.isInverted() and + not namedCharacterPropertyMatches(charProp.getName(), char) + ) + ) + } + + /** + * Holds if `range` is a range on lower-case, upper-case, or digits, and matches `char`. + * This predicate is used to restrict the searchspace for ranges by only joining `getAnyPossiblyMatchedChar` + * on a few ranges. + */ + private predicate rangeMatchesOnLetterOrDigits(RegExpCharacterRange range, string char) { + exists(string lo, string hi | + range.isRange(lo, hi) and lo = lowercaseLetter() and hi = lowercaseLetter() + | + lo <= char and + char <= hi and + char = lowercaseLetter() + ) + or + exists(string lo, string hi | + range.isRange(lo, hi) and lo = upperCaseLetter() and hi = upperCaseLetter() + | + lo <= char and + char <= hi and + char = upperCaseLetter() + ) + or + exists(string lo, string hi | range.isRange(lo, hi) and lo = digit() and hi = digit() | + lo <= char and + char <= hi and + char = digit() + ) + } + + private string lowercaseLetter() { result = "abdcefghijklmnopqrstuvwxyz".charAt(_) } + + private string upperCaseLetter() { result = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".charAt(_) } + + private string digit() { result = [0 .. 9].toString() } + + /** + * Gets a char that could be matched by a regular expression. + * Includes all printable ascii chars, all constants mentioned in a regexp, and all chars matches by the regexp `/\s|\d|\w/`. + */ + string getARelevantChar() { + exists(ascii(result)) + or + exists(RegexpCharacterConstant c | result = c.getValue().charAt(_)) + or + classEscapeMatches(_, result) + } + + /** + * Gets a char that is mentioned in the character class `c`. + */ + private string getAMentionedChar(RegExpCharacterClass c) { + exists(RegExpTerm child | child = c.getAChild() | + result = child.(RegexpCharacterConstant).getValue() + or + child.(RegExpCharacterRange).isRange(result, _) + or + child.(RegExpCharacterRange).isRange(_, result) + or + exists(RegExpCharacterClassEscape escape | child = escape | + result = min(string s | classEscapeMatches(escape.getValue().toLowerCase(), s)) + or + result = max(string s | classEscapeMatches(escape.getValue().toLowerCase(), s)) + ) + or + exists(RegExpNamedCharacterProperty charProp | child = charProp | + result = min(string s | namedCharacterPropertyMatches(charProp.getName(), s)) + or + result = max(string s | namedCharacterPropertyMatches(charProp.getName(), s)) + ) + ) + } + + /** + * An implementation of `CharacterClass` for positive (non inverted) character classes. + */ + private class PositiveCharacterClass extends CharacterClass { + RegExpCharacterClass cc; + + PositiveCharacterClass() { this = getCanonicalCharClass(cc) and not cc.isInverted() } + + override string getARelevantChar() { result = getAMentionedChar(cc) } + + override predicate matches(string char) { hasChildThatMatches(cc, char) } + } + + /** + * An implementation of `CharacterClass` for inverted character classes. + */ + private class InvertedCharacterClass extends CharacterClass { + RegExpCharacterClass cc; + + InvertedCharacterClass() { this = getCanonicalCharClass(cc) and cc.isInverted() } + + override string getARelevantChar() { + result = nextChar(getAMentionedChar(cc)) or + nextChar(result) = getAMentionedChar(cc) + } + + bindingset[char] + override predicate matches(string char) { not hasChildThatMatches(cc, char) } + } + + /** + * Holds if the character class escape `clazz` (\d, \s, or \w) matches `char`. + */ + pragma[noinline] + private predicate classEscapeMatches(string clazz, string char) { + clazz = "d" and + char = "0123456789".charAt(_) + or + clazz = "s" and + char = [" ", "\t", "\r", "\n", 11.toUnicode(), 12.toUnicode()] // 11.toUnicode() = \v, 12.toUnicode() = \f' + or + clazz = "w" and + char = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_".charAt(_) + } + + /** + * Holds if the named character property (e.g. from a POSIX bracket + * expression) `propName` matches `char`. For example, it holds when `name` is + * `"word"` and `char` is `"a"`. + * + * TODO: expand to cover more properties. + */ + private predicate namedCharacterPropertyMatches(string propName, string char) { + propName = ["digit", "Digit"] and + char = "0123456789".charAt(_) + or + propName = ["space", "Space"] and + ( + char = [" ", "\t", "\r", "\n"] + or + char = getARelevantChar() and + char.regexpMatch("\\u000b|\\u000c") // \v|\f (vertical tab | form feed) + ) + or + propName = ["word", "Word"] and + char = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_".charAt(_) + } + + /** + * An implementation of `CharacterClass` for \d, \s, and \w. + */ + private class PositiveCharacterClassEscape extends CharacterClass { + RegExpCharacterClassEscape cc; + + PositiveCharacterClassEscape() { + this = getCanonicalCharClass(cc) and cc.getValue() = ["d", "s", "w"] + } + + override string getARelevantChar() { + cc.getValue() = "d" and + result = ["0", "9"] + or + cc.getValue() = "s" and + result = [" "] + or + cc.getValue() = "w" and + result = ["a", "Z", "_", "0", "9"] + } + + override predicate matches(string char) { classEscapeMatches(cc.getValue(), char) } + + override string choose() { + cc.getValue() = "d" and + result = "9" + or + cc.getValue() = "s" and + result = [" "] + or + cc.getValue() = "w" and + result = "a" + } + } + + /** + * An implementation of `CharacterClass` for \D, \S, and \W. + */ + private class NegativeCharacterClassEscape extends CharacterClass { + RegExpCharacterClassEscape cc; + + NegativeCharacterClassEscape() { + this = getCanonicalCharClass(cc) and cc.getValue() = ["D", "S", "W"] + } + + override string getARelevantChar() { + cc.getValue() = "D" and + result = ["a", "Z", "!"] + or + cc.getValue() = "S" and + result = ["a", "9", "!"] + or + cc.getValue() = "W" and + result = [" ", "!"] + } + + bindingset[char] + override predicate matches(string char) { + not classEscapeMatches(cc.getValue().toLowerCase(), char) + } + } + + /** + * An implementation of `NamedCharacterProperty` for positive (non-inverted) + * character properties. + */ + private class PositiveNamedCharacterProperty extends CharacterClass { + RegExpNamedCharacterProperty cp; + + PositiveNamedCharacterProperty() { this = getCanonicalCharClass(cp) and not cp.isInverted() } + + override string getARelevantChar() { + exists(string lowerName | lowerName = cp.getName().toLowerCase() | + lowerName = "digit" and + result = ["0", "9"] + or + lowerName = "space" and + result = [" "] + or + lowerName = "word" and + result = ["a", "Z", "_", "0", "9"] + ) + } + + override predicate matches(string char) { namedCharacterPropertyMatches(cp.getName(), char) } + + override string choose() { + exists(string lowerName | lowerName = cp.getName().toLowerCase() | + lowerName = "digit" and + result = "9" + or + lowerName = "space" and + result = " " + or + lowerName = "word" and + result = "a" + ) + } + } + + private class InvertedNamedCharacterProperty extends CharacterClass { + RegExpNamedCharacterProperty cp; + + InvertedNamedCharacterProperty() { this = getCanonicalCharClass(cp) and cp.isInverted() } + + override string getARelevantChar() { + exists(string lowerName | lowerName = cp.getName().toLowerCase() | + lowerName = "digit" and + result = ["a", "Z", "!"] + or + lowerName = "space" and + result = ["a", "9", "!"] + or + lowerName = "word" and + result = [" ", "!"] + ) + } + + bindingset[char] + override predicate matches(string char) { + not namedCharacterPropertyMatches(cp.getName(), char) + } + } +} + +private class EdgeLabel extends TInputSymbol { + string toString() { + this = Epsilon() and result = "" + or + exists(InputSymbol s | this = s and result = s.toString()) + } +} + +/** + * Gets the state before matching `t`. + */ +pragma[inline] +private State before(RegExpTerm t) { result = Match(t, 0) } + +/** + * Gets a state the NFA may be in after matching `t`. + */ +private State after(RegExpTerm t) { + exists(RegExpAlt alt | t = alt.getAChild() | result = after(alt)) + or + exists(RegExpSequence seq, int i | t = seq.getChild(i) | + result = before(seq.getChild(i + 1)) + or + i + 1 = seq.getNumChild() and result = after(seq) + ) + or + exists(RegExpGroup grp | t = grp.getAChild() | result = after(grp)) + or + exists(RegExpStar star | t = star.getAChild() | result = before(star)) + or + exists(RegExpPlus plus | t = plus.getAChild() | + result = before(plus) or + result = after(plus) + ) + or + exists(RegExpOpt opt | t = opt.getAChild() | result = after(opt)) + or + exists(RegExpRoot root | t = root | result = AcceptAnySuffix(root)) +} + +/** + * Holds if the NFA has a transition from `q1` to `q2` labelled with `lbl`. + */ +predicate delta(State q1, EdgeLabel lbl, State q2) { + exists(RegexpCharacterConstant s, int i | + q1 = Match(s, i) and + lbl = Char(s.getValue().charAt(i)) and + ( + q2 = Match(s, i + 1) + or + s.getValue().length() = i + 1 and + q2 = after(s) + ) + ) + or + exists(RegExpDot dot | q1 = before(dot) and q2 = after(dot) | + if dot.getLiteral().isDotAll() then lbl = Any() else lbl = Dot() + ) + or + exists(RegExpCharacterClass cc | + cc.isUniversalClass() and q1 = before(cc) and lbl = Any() and q2 = after(cc) + or + q1 = before(cc) and + lbl = CharClass(cc.getRawValue()) and + q2 = after(cc) + ) + or + exists(RegExpCharacterClassEscape cc | + q1 = before(cc) and + lbl = CharClass(cc.getRawValue()) and + q2 = after(cc) + ) + or + exists(RegExpNamedCharacterProperty cp | + q1 = before(cp) and + lbl = CharClass(cp.getRawValue()) and + q2 = after(cp) + ) + or + exists(RegExpAlt alt | lbl = Epsilon() | q1 = before(alt) and q2 = before(alt.getAChild())) + or + exists(RegExpSequence seq | lbl = Epsilon() | q1 = before(seq) and q2 = before(seq.getChild(0))) + or + exists(RegExpGroup grp | lbl = Epsilon() | q1 = before(grp) and q2 = before(grp.getChild(0))) + or + exists(RegExpStar star | lbl = Epsilon() | + q1 = before(star) and q2 = before(star.getChild(0)) + or + q1 = before(star) and q2 = after(star) + ) + or + exists(RegExpPlus plus | lbl = Epsilon() | q1 = before(plus) and q2 = before(plus.getChild(0))) + or + exists(RegExpOpt opt | lbl = Epsilon() | + q1 = before(opt) and q2 = before(opt.getChild(0)) + or + q1 = before(opt) and q2 = after(opt) + ) + or + exists(RegExpRoot root | q1 = AcceptAnySuffix(root) | + lbl = Any() and q2 = q1 + or + lbl = Epsilon() and q2 = Accept(root) + ) + or + exists(RegExpRoot root | q1 = Match(root, 0) | lbl = Any() and q2 = q1) + or + exists(RegExpDollar dollar | q1 = before(dollar) | + lbl = Epsilon() and q2 = Accept(getRoot(dollar)) + ) + or + exists(EmptyPositiveSubPatttern empty | q1 = before(empty) | + lbl = Epsilon() and q2 = after(empty) + ) +} + +/** + * Gets a state that `q` has an epsilon transition to. + */ +State epsilonSucc(State q) { delta(q, Epsilon(), result) } + +/** + * Gets a state that has an epsilon transition to `q`. + */ +State epsilonPred(State q) { q = epsilonSucc(result) } + +/** + * Holds if there is a state `q` that can be reached from `q1` + * along epsilon edges, such that there is a transition from + * `q` to `q2` that consumes symbol `s`. + */ +predicate deltaClosed(State q1, InputSymbol s, State q2) { delta(epsilonSucc*(q1), s, q2) } + +/** + * Gets the root containing the given term, that is, the root of the literal, + * or a branch of the root disjunction. + */ +RegExpRoot getRoot(RegExpTerm term) { + result = term or + result = getRoot(term.getParent()) +} + +private newtype TState = + Match(RegExpTerm t, int i) { + getRoot(t).isRelevant() and + ( + i = 0 + or + exists(t.(RegexpCharacterConstant).getValue().charAt(i)) + ) + } or + Accept(RegExpRoot l) { l.isRelevant() } or + AcceptAnySuffix(RegExpRoot l) { l.isRelevant() } + +/** + * Gets a state that is about to match the regular expression `t`. + */ +State mkMatch(RegExpTerm t) { result = Match(t, 0) } + +/** + * A state in the NFA corresponding to a regular expression. + * + * Each regular expression literal `l` has one accepting state + * `Accept(l)`, one state that accepts all suffixes `AcceptAnySuffix(l)`, + * and a state `Match(t, i)` for every subterm `t`, + * which represents the state of the NFA before starting to + * match `t`, or the `i`th character in `t` if `t` is a constant. + */ +class State extends TState { + RegExpTerm repr; + + State() { + this = Match(repr, _) or + this = Accept(repr) or + this = AcceptAnySuffix(repr) + } + + /** + * Gets a string representation for this state in a regular expression. + */ + string toString() { + exists(int i | this = Match(repr, i) | result = "Match(" + repr + "," + i + ")") + or + this instanceof Accept and + result = "Accept(" + repr + ")" + or + this instanceof AcceptAnySuffix and + result = "AcceptAny(" + repr + ")" + } + + /** + * Gets the location for this state. + */ + Location getLocation() { result = repr.getLocation() } + + /** + * Gets the term represented by this state. + */ + RegExpTerm getRepr() { result = repr } +} + +/** + * Gets the minimum char that is matched by both the character classes `c` and `d`. + */ +private string getMinOverlapBetweenCharacterClasses(CharacterClass c, CharacterClass d) { + result = min(getAOverlapBetweenCharacterClasses(c, d)) +} + +/** + * Gets a char that is matched by both the character classes `c` and `d`. + * And `c` and `d` is not the same character class. + */ +private string getAOverlapBetweenCharacterClasses(CharacterClass c, CharacterClass d) { + sharesRoot(c, d) and + result = [c.getARelevantChar(), d.getARelevantChar()] and + c.matches(result) and + d.matches(result) and + not c = d +} + +/** + * Gets a character that is represented by both `c` and `d`. + */ +string intersect(InputSymbol c, InputSymbol d) { + (sharesRoot(c, d) or [c, d] = Any()) and + ( + c = Char(result) and + d = getAnInputSymbolMatching(result) + or + result = getMinOverlapBetweenCharacterClasses(c, d) + or + result = c.(CharacterClass).choose() and + ( + d = c + or + d = Dot() and + not (result = "\n" or result = "\r") + or + d = Any() + ) + or + (c = Dot() or c = Any()) and + (d = Dot() or d = Any()) and + result = "a" + ) + or + result = intersect(d, c) +} + +/** + * Gets a symbol that matches `char`. + */ +bindingset[char] +InputSymbol getAnInputSymbolMatching(string char) { + result = Char(char) + or + result.(CharacterClass).matches(char) + or + result = Dot() and + not (char = "\n" or char = "\r") + or + result = Any() +} + +/** + * Predicates for constructing a prefix string that leads to a given state. + */ +private module PrefixConstruction { + /** + * Holds if `state` starts the string matched by the regular expression. + */ + private predicate isStartState(State state) { + state instanceof StateInPumpableRegexp and + ( + state = Match(any(RegExpRoot r), _) + or + exists(RegExpCaret car | state = after(car)) + ) + } + + /** + * Holds if `state` is the textually last start state for the regular expression. + */ + private predicate lastStartState(State state) { + exists(RegExpRoot root | + state = + max(State s, Location l | + isStartState(s) and getRoot(s.getRepr()) = root and l = s.getRepr().getLocation() + | + s + order by + l.getStartLine(), l.getStartColumn(), s.getRepr().toString(), l.getEndColumn(), + l.getEndLine() + ) + ) + } + + /** + * Holds if there exists any transition (Epsilon() or other) from `a` to `b`. + */ + private predicate existsTransition(State a, State b) { delta(a, _, b) } + + /** + * Gets the minimum number of transitions it takes to reach `state` from the `start` state. + */ + int prefixLength(State start, State state) = + shortestDistances(lastStartState/1, existsTransition/2)(start, state, result) + + /** + * Gets the minimum number of transitions it takes to reach `state` from the start state. + */ + private int lengthFromStart(State state) { result = prefixLength(_, state) } + + /** + * Gets a string for which the regular expression will reach `state`. + * + * Has at most one result for any given `state`. + * This predicate will not always have a result even if there is a ReDoS issue in + * the regular expression. + */ + string prefix(State state) { + lastStartState(state) and + result = "" + or + // the search stops past the last redos candidate state. + lengthFromStart(state) <= max(lengthFromStart(any(State s | isReDoSCandidate(s, _)))) and + exists(State prev | + // select a unique predecessor (by an arbitrary measure) + prev = + min(State s, Location loc | + lengthFromStart(s) = lengthFromStart(state) - 1 and + loc = s.getRepr().getLocation() and + delta(s, _, state) + | + s + order by + loc.getStartLine(), loc.getStartColumn(), loc.getEndLine(), loc.getEndColumn(), + s.getRepr().toString() + ) + | + // greedy search for the shortest prefix + result = prefix(prev) and delta(prev, Epsilon(), state) + or + not delta(prev, Epsilon(), state) and + result = prefix(prev) + getCanonicalEdgeChar(prev, state) + ) + } + + /** + * Gets a canonical char for which there exists a transition from `prev` to `next` in the NFA. + */ + private string getCanonicalEdgeChar(State prev, State next) { + result = + min(string c | delta(prev, any(InputSymbol symbol | c = intersect(Any(), symbol)), next)) + } + + /** + * A state within a regular expression that has a pumpable state. + */ + class StateInPumpableRegexp extends State { + pragma[noinline] + StateInPumpableRegexp() { + exists(State s | isReDoSCandidate(s, _) | getRoot(s.getRepr()) = getRoot(this.getRepr())) + } + } +} + +/** + * Predicates for testing the presence of a rejecting suffix. + * + * These predicates are used to ensure that the all states reached from the fork + * by repeating `w` have a rejecting suffix. + * + * For example, a regexp like `/^(a+)+/` will accept any string as long the prefix is + * some number of `"a"`s, and it is therefore not possible to construct a rejecting suffix. + * + * A regexp like `/(a+)+$/` or `/(a+)+b/` trivially has a rejecting suffix, + * as the suffix "X" will cause both the regular expressions to be rejected. + * + * The string `w` is repeated any number of times because it needs to be + * infinitely repeatedable for the attack to work. + * For the regular expression `/((ab)+)*abab/` the accepting state is not reachable from the fork + * using epsilon transitions. But any attempt at repeating `w` will end in a state that accepts all suffixes. + */ +private module SuffixConstruction { + import PrefixConstruction + + /** + * Holds if all states reachable from `fork` by repeating `w` + * are likely rejectable by appending some suffix. + */ + predicate reachesOnlyRejectableSuffixes(State fork, string w) { + isReDoSCandidate(fork, w) and + forex(State next | next = process(fork, w, w.length() - 1) | isLikelyRejectable(next)) + } + + /** + * Holds if there likely exists a suffix starting from `s` that leads to the regular expression being rejected. + * This predicate might find impossible suffixes when searching for suffixes of length > 1, which can cause FPs. + */ + pragma[noinline] + private predicate isLikelyRejectable(StateInPumpableRegexp s) { + // exists a reject edge with some char. + hasRejectEdge(s) + or + hasEdgeToLikelyRejectable(s) + or + // stopping here is rejection + isRejectState(s) + } + + /** + * Holds if `s` is not an accept state, and there is no epsilon transition to an accept state. + */ + predicate isRejectState(StateInPumpableRegexp s) { not epsilonSucc*(s) = Accept(_) } + + /** + * Holds if there is likely a non-empty suffix leading to rejection starting in `s`. + */ + pragma[noopt] + predicate hasEdgeToLikelyRejectable(StateInPumpableRegexp s) { + // all edges (at least one) with some char leads to another state that is rejectable. + // the `next` states might not share a common suffix, which can cause FPs. + exists(string char | char = hasEdgeToLikelyRejectableHelper(s) | + // noopt to force `hasEdgeToLikelyRejectableHelper` to be first in the join-order. + exists(State next | deltaClosedChar(s, char, next) | isLikelyRejectable(next)) and + forall(State next | deltaClosedChar(s, char, next) | isLikelyRejectable(next)) + ) + } + + /** + * Gets a char for there exists a transition away from `s`, + * and `s` has not been found to be rejectable by `hasRejectEdge` or `isRejectState`. + */ + pragma[noinline] + private string hasEdgeToLikelyRejectableHelper(StateInPumpableRegexp s) { + not hasRejectEdge(s) and + not isRejectState(s) and + deltaClosedChar(s, result, _) + } + + /** + * Holds if there is a state `next` that can be reached from `prev` + * along epsilon edges, such that there is a transition from + * `prev` to `next` that the character symbol `char`. + */ + predicate deltaClosedChar(StateInPumpableRegexp prev, string char, StateInPumpableRegexp next) { + deltaClosed(prev, getAnInputSymbolMatchingRelevant(char), next) + } + + pragma[noinline] + InputSymbol getAnInputSymbolMatchingRelevant(string char) { + char = relevant(_) and + result = getAnInputSymbolMatching(char) + } + + /** + * Gets a char used for finding possible suffixes inside `root`. + */ + pragma[noinline] + private string relevant(RegExpRoot root) { + exists(ascii(result)) + or + exists(InputSymbol s | belongsTo(s, root) | result = intersect(s, _)) + or + // The characters from `hasSimpleRejectEdge`. Only `\n` is really needed (as `\n` is not in the `ascii` relation). + // The three chars must be kept in sync with `hasSimpleRejectEdge`. + result = ["|", "\n", "Z"] + } + + /** + * Holds if there exists a `char` such that there is no edge from `s` labeled `char` in our NFA. + * The NFA does not model reject states, so the above is the same as saying there is a reject edge. + */ + private predicate hasRejectEdge(State s) { + hasSimpleRejectEdge(s) + or + not hasSimpleRejectEdge(s) and + exists(string char | char = relevant(getRoot(s.getRepr())) | not deltaClosedChar(s, char, _)) + } + + /** + * Holds if there is no edge from `s` labeled with "|", "\n", or "Z" in our NFA. + * This predicate is used as a cheap pre-processing to speed up `hasRejectEdge`. + */ + private predicate hasSimpleRejectEdge(State s) { + // The three chars were chosen arbitrarily. The three chars must be kept in sync with `relevant`. + exists(string char | char = ["|", "\n", "Z"] | not deltaClosedChar(s, char, _)) + } + + /** + * Gets a state that can be reached from pumpable `fork` consuming all + * chars in `w` any number of times followed by the first `i+1` characters of `w`. + */ + pragma[noopt] + private State process(State fork, string w, int i) { + exists(State prev | prev = getProcessPrevious(fork, i, w) | + exists(string char, InputSymbol sym | + char = w.charAt(i) and + deltaClosed(prev, sym, result) and + // noopt to prevent joining `prev` with all possible `chars` that could transition away from `prev`. + // Instead only join with the set of `chars` where a relevant `InputSymbol` has already been found. + sym = getAProcessInputSymbol(char) + ) + ) + } + + /** + * Gets a state that can be reached from pumpable `fork` consuming all + * chars in `w` any number of times followed by the first `i` characters of `w`. + */ + private State getProcessPrevious(State fork, int i, string w) { + isReDoSCandidate(fork, w) and + ( + i = 0 and result = fork + or + result = process(fork, w, i - 1) + or + // repeat until fixpoint + i = 0 and + result = process(fork, w, w.length() - 1) + ) + } + + /** + * Gets an InputSymbol that matches `char`. + * The predicate is specialized to only have a result for the `char`s that are relevant for the `process` predicate. + */ + private InputSymbol getAProcessInputSymbol(string char) { + char = getAProcessChar() and + result = getAnInputSymbolMatching(char) + } + + /** + * Gets a `char` that occurs in a `pump` string. + */ + private string getAProcessChar() { result = any(string s | isReDoSCandidate(_, s)).charAt(_) } +} + +/** + * Gets the result of backslash-escaping newlines, carriage-returns and + * backslashes in `s`. + */ +bindingset[s] +private string escape(string s) { + result = + s.replaceAll("\\", "\\\\") + .replaceAll("\n", "\\n") + .replaceAll("\r", "\\r") + .replaceAll("\t", "\\t") +} + +/** + * Gets `str` with the last `i` characters moved to the front. + * + * We use this to adjust the pump string to match with the beginning of + * a RegExpTerm, so it doesn't start in the middle of a constant. + */ +bindingset[str, i] +private string rotate(string str, int i) { + result = str.suffix(str.length() - i) + str.prefix(str.length() - i) +} + +/** + * Holds if `term` may cause superlinear backtracking on strings containing many repetitions of `pump`. + * Gets the shortest string that causes superlinear backtracking. + */ +private predicate isReDoSAttackable(RegExpTerm term, string pump, State s) { + exists(int i, string c | s = Match(term, i) | + c = + min(string w | + any(ReDoSConfiguration conf).isReDoSCandidate(s, w) and + SuffixConstruction::reachesOnlyRejectableSuffixes(s, w) + | + w order by w.length(), w + ) and + pump = escape(rotate(c, i)) + ) +} + +/** + * Holds if the state `s` (represented by the term `t`) can have backtracking with repetitions of `pump`. + * + * `prefixMsg` contains a friendly message for a prefix that reaches `s` (or `prefixMsg` is the empty string if the prefix is empty or if no prefix could be found). + */ +predicate hasReDoSResult(RegExpTerm t, string pump, State s, string prefixMsg) { + not t.getRegExp().hasFreeSpacingFlag() and // exclude free-spacing mode regexes + isReDoSAttackable(t, pump, s) and + ( + prefixMsg = "starting with '" + escape(PrefixConstruction::prefix(s)) + "' and " and + not PrefixConstruction::prefix(s) = "" + or + PrefixConstruction::prefix(s) = "" and prefixMsg = "" + or + not exists(PrefixConstruction::prefix(s)) and prefixMsg = "" + ) +} diff --git a/ruby/ql/lib/codeql/ruby/regexp/RegExpTreeView.qll b/ruby/ql/lib/codeql/ruby/regexp/RegExpTreeView.qll new file mode 100644 index 000000000000..11fd0836ce1d --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/regexp/RegExpTreeView.qll @@ -0,0 +1,724 @@ +private import codeql.ruby.ast.Literal as AST +private import codeql.Locations +private import ParseRegExp + +/** + * An element containing a regular expression term, that is, either + * a string literal (parsed as a regular expression) + * or another regular expression term. + */ +class RegExpParent extends TRegExpParent { + string toString() { result = "RegExpParent" } + + RegExpTerm getChild(int i) { none() } + + RegExpTerm getAChild() { result = getChild(_) } + + int getNumChild() { result = count(getAChild()) } + + /** + * Gets the name of a primary CodeQL class to which this regular + * expression term belongs. + */ + string getAPrimaryQlClass() { result = "RegExpParent" } + + /** + * Gets a comma-separated list of the names of the primary CodeQL classes to + * which this regular expression term belongs. + */ + final string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") } +} + +class RegExpLiteral extends TRegExpLiteral, RegExpParent { + RegExp re; + + RegExpLiteral() { this = TRegExpLiteral(re) } + + override RegExpTerm getChild(int i) { i = 0 and result.getRegExp() = re and result.isRootTerm() } + + predicate isDotAll() { re.hasMultilineFlag() } + + override string getAPrimaryQlClass() { result = "RegExpLiteral" } +} + +class RegExpTerm extends RegExpParent { + RegExp re; + int start; + int end; + + RegExpTerm() { + this = TRegExpAlt(re, start, end) + or + this = TRegExpBackRef(re, start, end) + or + this = TRegExpCharacterClass(re, start, end) + or + this = TRegExpCharacterRange(re, start, end) + or + this = TRegExpNormalChar(re, start, end) + or + this = TRegExpGroup(re, start, end) + or + this = TRegExpQuantifier(re, start, end) + or + this = TRegExpSequence(re, start, end) and + exists(seqChild(re, start, end, 1)) // if a sequence does not have more than one element, it should be treated as that element instead. + or + this = TRegExpSpecialChar(re, start, end) + or + this = TRegExpNamedCharacterProperty(re, start, end) + } + + RegExpTerm getRootTerm() { + this.isRootTerm() and result = this + or + result = getParent().(RegExpTerm).getRootTerm() + } + + predicate isUsedAsRegExp() { any() } + + predicate isRootTerm() { start = 0 and end = re.getText().length() } + + override RegExpTerm getChild(int i) { + result = this.(RegExpAlt).getChild(i) + or + result = this.(RegExpBackRef).getChild(i) + or + result = this.(RegExpCharacterClass).getChild(i) + or + result = this.(RegExpCharacterRange).getChild(i) + or + result = this.(RegExpNormalChar).getChild(i) + or + result = this.(RegExpGroup).getChild(i) + or + result = this.(RegExpQuantifier).getChild(i) + or + result = this.(RegExpSequence).getChild(i) + or + result = this.(RegExpSpecialChar).getChild(i) + or + result = this.(RegExpNamedCharacterProperty).getChild(i) + } + + RegExpParent getParent() { result.getAChild() = this } + + RegExp getRegExp() { result = re } + + int getStart() { result = start } + + int getEnd() { result = end } + + override string toString() { result = re.getText().substring(start, end) } + + override string getAPrimaryQlClass() { result = "RegExpTerm" } + + Location getLocation() { result = re.getLocation() } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + exists(int re_start, int re_end | + re.getComponent(0).getLocation().hasLocationInfo(filepath, startline, re_start, _, _) and + re.getComponent(re.getNumberOfComponents() - 1) + .getLocation() + .hasLocationInfo(filepath, _, _, endline, re_end) + | + startcolumn = re_start + start and + endcolumn = re_start + end - 1 + ) + } + + File getFile() { result = this.getLocation().getFile() } + + string getRawValue() { result = this.toString() } + + RegExpLiteral getLiteral() { result = TRegExpLiteral(re) } + + /** Gets the regular expression term that is matched (textually) before this one, if any. */ + RegExpTerm getPredecessor() { + exists(RegExpTerm parent | parent = getParent() | + result = parent.(RegExpSequence).previousElement(this) + or + not exists(parent.(RegExpSequence).previousElement(this)) and + not parent instanceof RegExpSubPattern and + result = parent.getPredecessor() + ) + } + + /** Gets the regular expression term that is matched (textually) after this one, if any. */ + RegExpTerm getSuccessor() { + exists(RegExpTerm parent | parent = getParent() | + result = parent.(RegExpSequence).nextElement(this) + or + not exists(parent.(RegExpSequence).nextElement(this)) and + not parent instanceof RegExpSubPattern and + result = parent.getSuccessor() + ) + } +} + +newtype TRegExpParent = + TRegExpLiteral(RegExp re) or + TRegExpQuantifier(RegExp re, int start, int end) { re.qualifiedItem(start, end, _, _) } or + TRegExpSequence(RegExp re, int start, int end) { re.sequence(start, end) } or + TRegExpAlt(RegExp re, int start, int end) { re.alternation(start, end) } or + TRegExpCharacterClass(RegExp re, int start, int end) { re.charSet(start, end) } or + TRegExpCharacterRange(RegExp re, int start, int end) { re.charRange(_, start, _, _, end) } or + TRegExpGroup(RegExp re, int start, int end) { re.group(start, end) } or + TRegExpSpecialChar(RegExp re, int start, int end) { re.specialCharacter(start, end, _) } or + TRegExpNormalChar(RegExp re, int start, int end) { re.normalCharacter(start, end) } or + TRegExpBackRef(RegExp re, int start, int end) { re.backreference(start, end) } or + TRegExpNamedCharacterProperty(RegExp re, int start, int end) { + re.namedCharacterProperty(start, end, _) + } + +class RegExpQuantifier extends RegExpTerm, TRegExpQuantifier { + int part_end; + boolean maybe_empty; + boolean may_repeat_forever; + + RegExpQuantifier() { + this = TRegExpQuantifier(re, start, end) and + re.qualifiedPart(start, part_end, end, maybe_empty, may_repeat_forever) + } + + override RegExpTerm getChild(int i) { + i = 0 and + result.getRegExp() = re and + result.getStart() = start and + result.getEnd() = part_end + } + + predicate mayRepeatForever() { may_repeat_forever = true } + + string getQualifier() { result = re.getText().substring(part_end, end) } + + override string getAPrimaryQlClass() { result = "RegExpQuantifier" } +} + +class InfiniteRepetitionQuantifier extends RegExpQuantifier { + InfiniteRepetitionQuantifier() { this.mayRepeatForever() } + + override string getAPrimaryQlClass() { result = "InfiniteRepetitionQuantifier" } +} + +class RegExpStar extends InfiniteRepetitionQuantifier { + RegExpStar() { this.getQualifier().charAt(0) = "*" } + + override string getAPrimaryQlClass() { result = "RegExpStar" } +} + +class RegExpPlus extends InfiniteRepetitionQuantifier { + RegExpPlus() { this.getQualifier().charAt(0) = "+" } + + override string getAPrimaryQlClass() { result = "RegExpPlus" } +} + +class RegExpOpt extends RegExpQuantifier { + RegExpOpt() { this.getQualifier().charAt(0) = "?" } + + override string getAPrimaryQlClass() { result = "RegExpOpt" } +} + +class RegExpRange extends RegExpQuantifier { + string upper; + string lower; + + RegExpRange() { re.multiples(part_end, end, lower, upper) } + + string getUpper() { result = upper } + + string getLower() { result = lower } + + /** + * Gets the upper bound of the range, if any. + * + * If there is no upper bound, any number of repetitions is allowed. + * For a term of the form `r{lo}`, both the lower and the upper bound + * are `lo`. + */ + int getUpperBound() { result = this.getUpper().toInt() } + + /** Gets the lower bound of the range. */ + int getLowerBound() { result = this.getLower().toInt() } + + override string getAPrimaryQlClass() { result = "RegExpRange" } +} + +class RegExpSequence extends RegExpTerm, TRegExpSequence { + RegExpSequence() { + this = TRegExpSequence(re, start, end) and + exists(seqChild(re, start, end, 1)) // if a sequence does not have more than one element, it should be treated as that element instead. + } + + override RegExpTerm getChild(int i) { result = seqChild(re, start, end, i) } + + /** Gets the element preceding `element` in this sequence. */ + RegExpTerm previousElement(RegExpTerm element) { element = nextElement(result) } + + /** Gets the element following `element` in this sequence. */ + RegExpTerm nextElement(RegExpTerm element) { + exists(int i | + element = this.getChild(i) and + result = this.getChild(i + 1) + ) + } + + override string getAPrimaryQlClass() { result = "RegExpSequence" } +} + +pragma[nomagic] +private int seqChildEnd(RegExp re, int start, int end, int i) { + result = seqChild(re, start, end, i).getEnd() +} + +// moved out so we can use it in the charpred +private RegExpTerm seqChild(RegExp re, int start, int end, int i) { + re.sequence(start, end) and + ( + i = 0 and + result.getRegExp() = re and + result.getStart() = start and + exists(int itemEnd | + re.item(start, itemEnd) and + result.getEnd() = itemEnd + ) + or + i > 0 and + result.getRegExp() = re and + exists(int itemStart | itemStart = seqChildEnd(re, start, end, i - 1) | + result.getStart() = itemStart and + re.item(itemStart, result.getEnd()) + ) + ) +} + +class RegExpAlt extends RegExpTerm, TRegExpAlt { + RegExpAlt() { this = TRegExpAlt(re, start, end) } + + override RegExpTerm getChild(int i) { + i = 0 and + result.getRegExp() = re and + result.getStart() = start and + exists(int part_end | + re.alternationOption(start, end, start, part_end) and + result.getEnd() = part_end + ) + or + i > 0 and + result.getRegExp() = re and + exists(int part_start | + part_start = this.getChild(i - 1).getEnd() + 1 // allow for the | + | + result.getStart() = part_start and + re.alternationOption(start, end, part_start, result.getEnd()) + ) + } + + override string getAPrimaryQlClass() { result = "RegExpAlt" } +} + +class RegExpEscape extends RegExpNormalChar { + RegExpEscape() { re.escapedCharacter(start, end) } + + /** + * Gets the name of the escaped; for example, `w` for `\w`. + * TODO: Handle named escapes. + */ + override string getValue() { + this.isIdentityEscape() and result = this.getUnescaped() + or + this.getUnescaped() = "n" and result = "\n" + or + this.getUnescaped() = "r" and result = "\r" + or + this.getUnescaped() = "t" and result = "\t" + or + isUnicode() and + result = getUnicode() + } + + predicate isIdentityEscape() { not this.getUnescaped() in ["n", "r", "t"] } + + /** + * Gets the text for this escape. That is e.g. "\w". + */ + private string getText() { result = re.getText().substring(start, end) } + + /** + * Holds if this is a unicode escape. + */ + private predicate isUnicode() { getText().prefix(2) = ["\\u", "\\U"] } + + /** + * Gets the unicode char for this escape. + * E.g. for `\u0061` this returns "a". + */ + private string getUnicode() { + exists(int codepoint | codepoint = sum(getHexValueFromUnicode(_)) | + result = codepoint.toUnicode() + ) + } + + /** + * Gets int value for the `index`th char in the hex number of the unicode escape. + * E.g. for `\u0061` and `index = 2` this returns 96 (the number `6` interpreted as hex). + */ + private int getHexValueFromUnicode(int index) { + isUnicode() and + exists(string hex, string char | hex = getText().suffix(2) | + char = hex.charAt(index) and + result = 16.pow(hex.length() - index - 1) * toHex(char) + ) + } + + string getUnescaped() { result = this.getText().suffix(1) } + + override string getAPrimaryQlClass() { result = "RegExpEscape" } +} + +/** + * Gets the hex number for the `hex` char. + */ +private int toHex(string hex) { + hex = [0 .. 9].toString() and + result = hex.toInt() + or + result = 10 and hex = ["a", "A"] + or + result = 11 and hex = ["b", "B"] + or + result = 12 and hex = ["c", "C"] + or + result = 13 and hex = ["d", "D"] + or + result = 14 and hex = ["e", "E"] + or + result = 15 and hex = ["f", "F"] +} + +/** + * A character class escape in a regular expression. + * That is, an escaped character that denotes multiple characters. + * + * Examples: + * + * ``` + * \w + * \S + * ``` + */ +class RegExpCharacterClassEscape extends RegExpEscape { + RegExpCharacterClassEscape() { this.getValue() in ["d", "D", "s", "S", "w", "W", "h", "H"] } + + /** Gets the name of the character class; for example, `w` for `\w`. */ + // override string getValue() { result = value } + override RegExpTerm getChild(int i) { none() } + + override string getAPrimaryQlClass() { result = "RegExpCharacterClassEscape" } +} + +/** + * A character class. + * + * Examples: + * + * ```rb + * /[a-fA-F0-9]/ + * /[^abc]/ + * ``` + */ +class RegExpCharacterClass extends RegExpTerm, TRegExpCharacterClass { + RegExpCharacterClass() { this = TRegExpCharacterClass(re, start, end) } + + predicate isInverted() { re.getChar(start + 1) = "^" } + + predicate isUniversalClass() { + // [^] + isInverted() and not exists(getAChild()) + or + // [\w\W] and similar + not isInverted() and + exists(string cce1, string cce2 | + cce1 = getAChild().(RegExpCharacterClassEscape).getValue() and + cce2 = getAChild().(RegExpCharacterClassEscape).getValue() + | + cce1 != cce2 and cce1.toLowerCase() = cce2.toLowerCase() + ) + } + + override RegExpTerm getChild(int i) { + i = 0 and + result.getRegExp() = re and + exists(int itemStart, int itemEnd | + result.getStart() = itemStart and + re.charSetStart(start, itemStart) and + re.charSetChild(start, itemStart, itemEnd) and + result.getEnd() = itemEnd + ) + or + i > 0 and + result.getRegExp() = re and + exists(int itemStart | itemStart = this.getChild(i - 1).getEnd() | + result.getStart() = itemStart and + re.charSetChild(start, itemStart, result.getEnd()) + ) + } + + override string getAPrimaryQlClass() { result = "RegExpCharacterClass" } +} + +class RegExpCharacterRange extends RegExpTerm, TRegExpCharacterRange { + int lower_end; + int upper_start; + + RegExpCharacterRange() { + this = TRegExpCharacterRange(re, start, end) and + re.charRange(_, start, lower_end, upper_start, end) + } + + predicate isRange(string lo, string hi) { + lo = re.getText().substring(start, lower_end) and + hi = re.getText().substring(upper_start, end) + } + + override RegExpTerm getChild(int i) { + i = 0 and + result.getRegExp() = re and + result.getStart() = start and + result.getEnd() = lower_end + or + i = 1 and + result.getRegExp() = re and + result.getStart() = upper_start and + result.getEnd() = end + } + + override string getAPrimaryQlClass() { result = "RegExpCharacterRange" } +} + +class RegExpNormalChar extends RegExpTerm, TRegExpNormalChar { + RegExpNormalChar() { this = TRegExpNormalChar(re, start, end) } + + predicate isCharacter() { any() } + + string getValue() { result = re.getText().substring(start, end) } + + override RegExpTerm getChild(int i) { none() } + + override string getAPrimaryQlClass() { result = "RegExpNormalChar" } +} + +class RegExpConstant extends RegExpTerm { + string value; + + RegExpConstant() { + this = TRegExpNormalChar(re, start, end) and + not this instanceof RegExpCharacterClassEscape and + // exclude chars in qualifiers + // TODO: push this into regex library + not exists(int qstart, int qend | re.qualifiedPart(_, qstart, qend, _, _) | + qstart <= start and end <= qend + ) and + value = this.(RegExpNormalChar).getValue() + or + this = TRegExpSpecialChar(re, start, end) and + re.inCharSet(start) and + value = this.(RegExpSpecialChar).getChar() + } + + predicate isCharacter() { any() } + + string getValue() { result = value } + + override RegExpTerm getChild(int i) { none() } + + override string getAPrimaryQlClass() { result = "RegExpConstant" } +} + +class RegExpGroup extends RegExpTerm, TRegExpGroup { + RegExpGroup() { this = TRegExpGroup(re, start, end) } + + /** + * Gets the index of this capture group within the enclosing regular + * expression literal. + * + * For example, in the regular expression `/((a?).)(?:b)/`, the + * group `((a?).)` has index 1, the group `(a?)` nested inside it + * has index 2, and the group `(?:b)` has no index, since it is + * not a capture group. + */ + int getNumber() { result = re.getGroupNumber(start, end) } + + /** Holds if this is a named capture group. */ + predicate isNamed() { exists(this.getName()) } + + /** Gets the name of this capture group, if any. */ + string getName() { result = re.getGroupName(start, end) } + + predicate isCharacter() { any() } + + string getValue() { result = re.getText().substring(start, end) } + + override RegExpTerm getChild(int i) { + result.getRegExp() = re and + i = 0 and + re.groupContents(start, end, result.getStart(), result.getEnd()) + } + + override string getAPrimaryQlClass() { result = "RegExpGroup" } +} + +class RegExpSpecialChar extends RegExpTerm, TRegExpSpecialChar { + string char; + + RegExpSpecialChar() { + this = TRegExpSpecialChar(re, start, end) and + re.specialCharacter(start, end, char) + } + + predicate isCharacter() { any() } + + string getChar() { result = char } + + override RegExpTerm getChild(int i) { none() } + + override string getAPrimaryQlClass() { result = "RegExpSpecialChar" } +} + +class RegExpDot extends RegExpSpecialChar { + RegExpDot() { this.getChar() = "." } + + override string getAPrimaryQlClass() { result = "RegExpDot" } +} + +class RegExpDollar extends RegExpSpecialChar { + RegExpDollar() { this.getChar() = ["$", "\\Z", "\\z"] } + + override string getAPrimaryQlClass() { result = "RegExpDollar" } +} + +class RegExpCaret extends RegExpSpecialChar { + RegExpCaret() { this.getChar() = ["^", "\\A"] } + + override string getAPrimaryQlClass() { result = "RegExpCaret" } +} + +class RegExpZeroWidthMatch extends RegExpGroup { + RegExpZeroWidthMatch() { re.zeroWidthMatch(start, end) } + + override predicate isCharacter() { any() } + + override RegExpTerm getChild(int i) { none() } + + override string getAPrimaryQlClass() { result = "RegExpZeroWidthMatch" } +} + +/** + * A zero-width lookahead or lookbehind assertion. + * + * Examples: + * + * ``` + * (?=\w) + * (?!\n) + * (?<=\.) + * (? (d,e,f)` in the product automaton + * iff there exists three transitions in the NFA `a->d, b->e, c->f` where those three + * transitions all match a shared character `char`. (see `getAThreewayIntersect`) + * + * We start a search in the product automaton at `(pivot, pivot, succ)`, + * and search for a series of transitions (a `Trace`), such that we end + * at `(pivot, succ, succ)` (see `isReachableFromStartTuple`). + * + * For example, consider the regular expression `/^\d*5\w*$/`. + * The search will start at the tuple `(\d*, \d*, \w*)` and search + * for a path to `(\d*, \w*, \w*)`. + * This path exists, and consists of a single transition in the product automaton, + * where the three corresponding NFA edges all match the character `"5"`. + * + * The start-state in the NFA has an any-transition to itself, this allows us to + * flag regular expressions such as `/a*$/` - which does not have a start anchor - + * and can thus start matching anywhere. + * + * The implementation is not perfect. + * It has the same suffix detection issue as the `js/redos` query, which can cause false positives. + * It also doesn't find all transitions in the product automaton, which can cause false negatives. + */ + +/** + * An instantiaion of `ReDoSConfiguration` for superlinear ReDoS. + */ +class SuperLinearReDoSConfiguration extends ReDoSConfiguration { + SuperLinearReDoSConfiguration() { this = "SuperLinearReDoSConfiguration" } + + override predicate isReDoSCandidate(State state, string pump) { isPumpable(_, state, pump) } +} + +/** + * Gets any root (start) state of a regular expression. + */ +private State getRootState() { result = mkMatch(any(RegExpRoot r)) } + +private newtype TStateTuple = + MkStateTuple(State q1, State q2, State q3) { + // starts at (pivot, pivot, succ) + isStartLoops(q1, q3) and q1 = q2 + or + step(_, _, _, _, q1, q2, q3) and FeasibleTuple::isFeasibleTuple(q1, q2, q3) + } + +/** + * A state in the product automaton. + * The product automaton contains 3-tuples of states. + * + * We lazily only construct those states that we are actually + * going to need. + * Either a start state `(pivot, pivot, succ)`, or a state + * where there exists a transition from an already existing state. + * + * The exponential variant of this query (`js/redos`) uses an optimization + * trick where `q1 <= q2`. This trick cannot be used here as the order + * of the elements matter. + */ +class StateTuple extends TStateTuple { + State q1; + State q2; + State q3; + + StateTuple() { this = MkStateTuple(q1, q2, q3) } + + /** + * Gest a string repesentation of this tuple. + */ + string toString() { result = "(" + q1 + ", " + q2 + ", " + q3 + ")" } + + /** + * Holds if this tuple is `(r1, r2, r3)`. + */ + pragma[noinline] + predicate isTuple(State r1, State r2, State r3) { r1 = q1 and r2 = q2 and r3 = q3 } +} + +/** + * A module for determining feasible tuples for the product automaton. + * + * The implementation is split into many predicates for performance reasons. + */ +private module FeasibleTuple { + /** + * Holds if the tuple `(r1, r2, r3)` might be on path from a start-state to an end-state in the product automaton. + */ + pragma[inline] + predicate isFeasibleTuple(State r1, State r2, State r3) { + // The first element is either inside a repetition (or the start state itself) + isRepetitionOrStart(r1) and + // The last element is inside a repetition + stateInsideRepetition(r3) and + // The states are reachable in the NFA in the order r1 -> r2 -> r3 + delta+(r1) = r2 and + delta+(r2) = r3 and + // The first element can reach a beginning (the "pivot" state in a `(pivot, succ)` pair). + canReachABeginning(r1) and + // The last element can reach a target (the "succ" state in a `(pivot, succ)` pair). + canReachATarget(r3) + } + + /** + * Holds if `s` is either inside a repetition, or is the start state (which is a repetition). + */ + pragma[noinline] + private predicate isRepetitionOrStart(State s) { stateInsideRepetition(s) or s = getRootState() } + + /** + * Holds if state `s` might be inside a backtracking repetition. + */ + pragma[noinline] + private predicate stateInsideRepetition(State s) { + s.getRepr().getParent*() instanceof InfiniteRepetitionQuantifier + } + + /** + * Holds if there exists a path in the NFA from `s` to a "pivot" state + * (from a `(pivot, succ)` pair that starts the search). + */ + pragma[noinline] + private predicate canReachABeginning(State s) { + delta+(s) = any(State pivot | isStartLoops(pivot, _)) + } + + /** + * Holds if there exists a path in the NFA from `s` to a "succ" state + * (from a `(pivot, succ)` pair that starts the search). + */ + pragma[noinline] + private predicate canReachATarget(State s) { delta+(s) = any(State succ | isStartLoops(_, succ)) } +} + +/** + * Holds if `pivot` and `succ` are a pair of loops that could be the beginning of a quadratic blowup. + * + * There is a slight implementation difference compared to the paper: this predicate requires that `pivot != succ`. + * The case where `pivot = succ` causes exponential backtracking and is handled by the `js/redos` query. + */ +predicate isStartLoops(State pivot, State succ) { + pivot != succ and + succ.getRepr() instanceof InfiniteRepetitionQuantifier and + delta+(pivot) = succ and + ( + pivot.getRepr() instanceof InfiniteRepetitionQuantifier + or + pivot = mkMatch(any(RegExpRoot root)) + ) +} + +/** + * Gets a state for which there exists a transition in the NFA from `s'. + */ +State delta(State s) { delta(s, _, result) } + +/** + * Holds if there are transitions from the components of `q` to the corresponding + * components of `r` labelled with `s1`, `s2`, and `s3`, respectively. + */ +pragma[noinline] +predicate step(StateTuple q, InputSymbol s1, InputSymbol s2, InputSymbol s3, StateTuple r) { + exists(State r1, State r2, State r3 | + step(q, s1, s2, s3, r1, r2, r3) and r = MkStateTuple(r1, r2, r3) + ) +} + +/** + * Holds if there are transitions from the components of `q` to `r1`, `r2`, and `r3 + * labelled with `s1`, `s2`, and `s3`, respectively. + */ +pragma[noopt] +predicate step( + StateTuple q, InputSymbol s1, InputSymbol s2, InputSymbol s3, State r1, State r2, State r3 +) { + exists(State q1, State q2, State q3 | q.isTuple(q1, q2, q3) | + deltaClosed(q1, s1, r1) and + deltaClosed(q2, s2, r2) and + deltaClosed(q3, s3, r3) and + // use noopt to force the join on `getAThreewayIntersect` to happen last. + exists(getAThreewayIntersect(s1, s2, s3)) + ) +} + +/** + * Gets a char that is matched by all the edges `s1`, `s2`, and `s3`. + * + * The result is not complete, and might miss some combination of edges that share some character. + */ +pragma[noinline] +string getAThreewayIntersect(InputSymbol s1, InputSymbol s2, InputSymbol s3) { + result = minAndMaxIntersect(s1, s2) and result = [intersect(s2, s3), intersect(s1, s3)] + or + result = minAndMaxIntersect(s1, s3) and result = [intersect(s2, s3), intersect(s1, s2)] + or + result = minAndMaxIntersect(s2, s3) and result = [intersect(s1, s2), intersect(s1, s3)] +} + +/** + * Gets the minimum and maximum characters that intersect between `a` and `b`. + * This predicate is used to limit the size of `getAThreewayIntersect`. + */ +pragma[noinline] +string minAndMaxIntersect(InputSymbol a, InputSymbol b) { + result = [min(intersect(a, b)), max(intersect(a, b))] +} + +private newtype TTrace = + Nil() or + Step(InputSymbol s1, InputSymbol s2, InputSymbol s3, TTrace t) { + exists(StateTuple p | + isReachableFromStartTuple(_, _, p, t, _) and + step(p, s1, s2, s3, _) + ) + or + exists(State pivot, State succ | isStartLoops(pivot, succ) | + t = Nil() and step(MkStateTuple(pivot, pivot, succ), s1, s2, s3, _) + ) + } + +/** + * A list of tuples of input symbols that describe a path in the product automaton + * starting from some start state. + */ +class Trace extends TTrace { + /** + * Gets a string representation of this Trace that can be used for debug purposes. + */ + string toString() { + this = Nil() and result = "Nil()" + or + exists(InputSymbol s1, InputSymbol s2, InputSymbol s3, Trace t | this = Step(s1, s2, s3, t) | + result = "Step(" + s1 + ", " + s2 + ", " + s3 + ", " + t + ")" + ) + } +} + +/** + * Gets a string corresponding to the trace `t`. + */ +string concretise(Trace t) { + t = Nil() and result = "" + or + exists(InputSymbol s1, InputSymbol s2, InputSymbol s3, Trace rest | t = Step(s1, s2, s3, rest) | + result = concretise(rest) + getAThreewayIntersect(s1, s2, s3) + ) +} + +/** + * Holds if there exists a transition from `r` to `q` in the product automaton. + * Notice that the arguments are flipped, and thus the direction is backwards. + */ +pragma[noinline] +predicate tupleDeltaBackwards(StateTuple q, StateTuple r) { step(r, _, _, _, q) } + +/** + * Holds if `tuple` is an end state in our search. + * That means there exists a pair of loops `(pivot, succ)` such that `tuple = (pivot, succ, succ)`. + */ +predicate isEndTuple(StateTuple tuple) { tuple = getAnEndTuple(_, _) } + +/** + * Gets the minimum length of a path from `r` to some an end state `end`. + * + * The implementation searches backwards from the end-tuple. + * This approach was chosen because it is way more efficient if the first predicate given to `shortestDistances` is small. + * The `end` argument must always be an end state. + */ +int distBackFromEnd(StateTuple r, StateTuple end) = + shortestDistances(isEndTuple/1, tupleDeltaBackwards/2)(end, r, result) + +/** + * Holds if there exists a pair of repetitions `(pivot, succ)` in the regular expression such that: + * `tuple` is reachable from `(pivot, pivot, succ)` in the product automaton, + * and there is a distance of `dist` from `tuple` to the nearest end-tuple `(pivot, succ, succ)`, + * and a path from a start-state to `tuple` follows the transitions in `trace`. + */ +predicate isReachableFromStartTuple(State pivot, State succ, StateTuple tuple, Trace trace, int dist) { + // base case. The first step is inlined to start the search after all possible 1-steps, and not just the ones with the shortest path. + exists(InputSymbol s1, InputSymbol s2, InputSymbol s3, State q1, State q2, State q3 | + isStartLoops(pivot, succ) and + step(MkStateTuple(pivot, pivot, succ), s1, s2, s3, tuple) and + tuple = MkStateTuple(q1, q2, q3) and + trace = Step(s1, s2, s3, Nil()) and + dist = distBackFromEnd(tuple, MkStateTuple(pivot, succ, succ)) + ) + or + // recursive case + exists(StateTuple p, Trace v, InputSymbol s1, InputSymbol s2, InputSymbol s3 | + isReachableFromStartTuple(pivot, succ, p, v, dist + 1) and + dist = isReachableFromStartTupleHelper(pivot, succ, tuple, p, s1, s2, s3) and + trace = Step(s1, s2, s3, v) + ) +} + +/** + * Helper predicate for the recursive case in `isReachableFromStartTuple`. + */ +pragma[noinline] +private int isReachableFromStartTupleHelper( + State pivot, State succ, StateTuple r, StateTuple p, InputSymbol s1, InputSymbol s2, + InputSymbol s3 +) { + result = distBackFromEnd(r, MkStateTuple(pivot, succ, succ)) and + step(p, s1, s2, s3, r) +} + +/** + * Gets the tuple `(pivot, succ, succ)` from the product automaton. + */ +StateTuple getAnEndTuple(State pivot, State succ) { + isStartLoops(pivot, succ) and + result = MkStateTuple(pivot, succ, succ) +} + +/** + * Holds if matching repetitions of `pump` can: + * 1) Transition from `pivot` back to `pivot`. + * 2) Transition from `pivot` to `succ`. + * 3) Transition from `succ` to `succ`. + * + * From theorem 3 in the paper linked in the top of this file we can therefore conclude that + * the regular expression has polynomial backtracking - if a rejecting suffix exists. + * + * This predicate is used by `SuperLinearReDoSConfiguration`, and the final results are + * available in the `hasReDoSResult` predicate. + */ +predicate isPumpable(State pivot, State succ, string pump) { + exists(StateTuple q, Trace t | + isReachableFromStartTuple(pivot, succ, q, t, _) and + q = getAnEndTuple(pivot, succ) and + pump = concretise(t) + ) +} + +/** + * Holds if repetitions of `pump` at `t` will cause polynomial backtracking. + */ +predicate polynimalReDoS(RegExpTerm t, string pump, string prefixMsg, RegExpTerm prev) { + exists(State s, State pivot | + hasReDoSResult(t, pump, s, prefixMsg) and + isPumpable(pivot, s, _) and + prev = pivot.getRepr() + ) +} + +/** + * Gets a message for why `term` can cause polynomial backtracking. + */ +string getReasonString(RegExpTerm term, string pump, string prefixMsg, RegExpTerm prev) { + polynimalReDoS(term, pump, prefixMsg, prev) and + result = + "Strings " + prefixMsg + "with many repetitions of '" + pump + + "' can start matching anywhere after the start of the preceeding " + prev +} + +/** + * A term that may cause a regular expression engine to perform a + * polynomial number of match attempts, relative to the input length. + */ +class PolynomialBackTrackingTerm extends InfiniteRepetitionQuantifier { + string reason; + string pump; + string prefixMsg; + RegExpTerm prev; + + PolynomialBackTrackingTerm() { + reason = getReasonString(this, pump, prefixMsg, prev) and + // there might be many reasons for this term to have polynomial backtracking - we pick the shortest one. + reason = min(string msg | msg = getReasonString(this, _, _, _) | msg order by msg.length(), msg) + } + + /** + * Holds if all non-empty successors to the polynomial backtracking term matches the end of the line. + */ + predicate isAtEndLine() { + forall(RegExpTerm succ | this.getSuccessor+() = succ and not matchesEpsilon(succ) | + succ instanceof RegExpDollar + ) + } + + /** + * Gets the string that should be repeated to cause this regular expression to perform polynomially. + */ + string getPumpString() { result = pump } + + /** + * Gets a message for which prefix a matching string must start with for this term to cause polynomial backtracking. + */ + string getPrefixMessage() { result = prefixMsg } + + /** + * Gets a predecessor to `this`, which also loops on the pump string, and thereby causes polynomial backtracking. + */ + RegExpTerm getPreviousLoop() { result = prev } + + /** + * Gets the reason for the number of match attempts. + */ + string getReason() { result = reason } +} diff --git a/ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll new file mode 100644 index 000000000000..4baceba42dbf --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll @@ -0,0 +1,40 @@ +private import ruby +private import codeql.ruby.DataFlow +private import codeql.ruby.Concepts +private import codeql.ruby.Frameworks +private import codeql.ruby.dataflow.RemoteFlowSources +private import codeql.ruby.dataflow.BarrierGuards + +/** + * Provides default sources, sinks and sanitizers for detecting + * "Code injection" vulnerabilities, as well as extension points for + * adding your own. + */ +module CodeInjection { + /** + * A data flow source for "Code injection" vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for "Code injection" vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { } + + /** + * A sanitizer guard for "Code injection" vulnerabilities. + */ + abstract class SanitizerGuard extends DataFlow::BarrierGuard { } + + /** + * A source of remote user input, considered as a flow source. + */ + class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { } + + /** + * A call that evaluates its arguments as Ruby code, considered as a flow sink. + */ + class CodeExecutionAsSink extends Sink { + CodeExecutionAsSink() { this = any(CodeExecution c).getCode() } + } +} diff --git a/ruby/ql/lib/codeql/ruby/security/CodeInjectionQuery.qll b/ruby/ql/lib/codeql/ruby/security/CodeInjectionQuery.qll new file mode 100644 index 000000000000..95e08a82dc3d --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/security/CodeInjectionQuery.qll @@ -0,0 +1,29 @@ +/** + * Provides a taint-tracking configuration for detecting "Code injection" vulnerabilities. + * + * Note, for performance reasons: only import this file if `Configuration` is needed, + * otherwise `CodeInjectionCustomizations` should be imported instead. + */ + +import codeql.ruby.DataFlow::DataFlow::PathGraph +import codeql.ruby.DataFlow +import codeql.ruby.TaintTracking +import CodeInjectionCustomizations::CodeInjection +import codeql.ruby.dataflow.BarrierGuards + +/** + * A taint-tracking configuration for detecting "Code injection" vulnerabilities. + */ +class Configuration extends TaintTracking::Configuration { + Configuration() { this = "CodeInjection" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { + guard instanceof SanitizerGuard or + guard instanceof StringConstCompare or + guard instanceof StringConstArrayInclusionCall + } +} diff --git a/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll new file mode 100644 index 000000000000..b39455195bef --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll @@ -0,0 +1,54 @@ +/** + * Provides default sources, sinks and sanitizers for reasoning about + * command-injection vulnerabilities, as well as extension points for + * adding your own. + */ + +private import codeql.ruby.DataFlow +private import codeql.ruby.dataflow.RemoteFlowSources +private import codeql.ruby.Concepts +private import codeql.ruby.Frameworks +private import codeql.ruby.ApiGraphs + +module CommandInjection { + /** + * A data flow source for command-injection vulnerabilities. + */ + abstract class Source extends DataFlow::Node { + /** Gets a string that describes the type of this remote flow source. */ + abstract string getSourceType(); + } + + /** + * A data flow sink for command-injection vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { } + + /** + * A sanitizer for command-injection vulnerabilities. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** A source of remote user input, considered as a flow source for command injection. */ + class RemoteFlowSourceAsSource extends Source { + RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource } + + override string getSourceType() { result = "a user-provided value" } + } + + /** + * A command argument to a function that initiates an operating system command. + */ + class SystemCommandExecutionSink extends Sink { + SystemCommandExecutionSink() { exists(SystemCommandExecution c | c.isShellInterpreted(this)) } + } + + /** + * A call to `Shellwords.escape` or `Shellwords.shellescape` sanitizes its input. + */ + class ShellwordsEscapeAsSanitizer extends Sanitizer { + ShellwordsEscapeAsSanitizer() { + this = API::getTopLevelMember("Shellwords").getAMethodCall(["escape", "shellescape"]) + } + } +} diff --git a/ruby/ql/lib/codeql/ruby/security/CommandInjectionQuery.qll b/ruby/ql/lib/codeql/ruby/security/CommandInjectionQuery.qll new file mode 100644 index 000000000000..25460ad65dfd --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/security/CommandInjectionQuery.qll @@ -0,0 +1,32 @@ +/** + * Provides a taint tracking configuration for reasoning about + * command-injection vulnerabilities (CWE-078). + * + * Note, for performance reasons: only import this file if + * `CommandInjection::Configuration` is needed, otherwise + * `CommandInjectionCustomizations` should be imported instead. + */ + +import ruby +import codeql.ruby.TaintTracking +import CommandInjectionCustomizations::CommandInjection +import codeql.ruby.DataFlow +import codeql.ruby.dataflow.BarrierGuards + +/** + * A taint-tracking configuration for reasoning about command-injection vulnerabilities. + */ +class Configuration extends TaintTracking::Configuration { + Configuration() { this = "CommandInjection" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer } + + override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { + guard instanceof StringConstCompare or + guard instanceof StringConstArrayInclusionCall + } +} diff --git a/ruby/ql/lib/codeql/ruby/security/ReflectedXSSCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/ReflectedXSSCustomizations.qll new file mode 100644 index 000000000000..11402bb28503 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/security/ReflectedXSSCustomizations.qll @@ -0,0 +1,200 @@ +private import ruby +private import codeql.ruby.DataFlow +private import codeql.ruby.CFG +private import codeql.ruby.Concepts +private import codeql.ruby.Frameworks +private import codeql.ruby.frameworks.ActionController +private import codeql.ruby.frameworks.ActionView +private import codeql.ruby.dataflow.RemoteFlowSources +private import codeql.ruby.dataflow.BarrierGuards +import codeql.ruby.dataflow.internal.DataFlowDispatch +private import codeql.ruby.typetracking.TypeTracker + +/** + * Provides default sources, sinks and sanitizers for detecting + * "reflected server-side cross-site scripting" + * vulnerabilities, as well as extension points for adding your own. + */ +module ReflectedXSS { + /** + * A data flow source for "reflected server-side cross-site scripting" vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for "reflected server-side cross-site scripting" vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { } + + /** + * A sanitizer for "reflected server-side cross-site scripting" vulnerabilities. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * A sanitizer guard for "reflected server-side cross-site scripting" vulnerabilities. + */ + abstract class SanitizerGuard extends DataFlow::BarrierGuard { } + + /** + * A source of remote user input, considered as a flow source. + */ + class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { } + + private class ErbOutputMethodCallArgumentNode extends DataFlow::Node { + private MethodCall call; + + ErbOutputMethodCallArgumentNode() { + exists(ErbOutputDirective d | + call = d.getTerminalStmt() and + this.asExpr().getExpr() = call.getAnArgument() + ) + } + + MethodCall getCall() { result = call } + } + + /** + * An `html_safe` call marking the output as not requiring HTML escaping, + * considered as a flow sink. + */ + class HtmlSafeCallAsSink extends Sink { + HtmlSafeCallAsSink() { + exists(HtmlSafeCall c, ErbOutputDirective d | + this.asExpr().getExpr() = c.getReceiver() and + c = d.getTerminalStmt() + ) + } + } + + /** + * An argument to a call to the `raw` method, considered as a flow sink. + */ + class RawCallArgumentAsSink extends Sink, ErbOutputMethodCallArgumentNode { + RawCallArgumentAsSink() { this.getCall() instanceof RawCall } + } + + /** + * A argument to a call to the `link_to` method, which does not expect + * unsanitized user-input, considered as a flow sink. + */ + class LinkToCallArgumentAsSink extends Sink, ErbOutputMethodCallArgumentNode { + LinkToCallArgumentAsSink() { + this.asExpr().getExpr() = this.getCall().(LinkToCall).getPathArgument() + } + } + + /** + * An HTML escaping, considered as a sanitizer. + */ + class HtmlEscapingAsSanitizer extends Sanitizer { + HtmlEscapingAsSanitizer() { this = any(HtmlEscaping esc).getOutput() } + } + + /** + * A comparison with a constant string, considered as a sanitizer-guard. + */ + class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { } + + /** + * An inclusion check against an array of constant strings, considered as a sanitizer-guard. + */ + class StringConstArrayInclusionCallAsSanitizerGuard extends SanitizerGuard, + StringConstArrayInclusionCall { } + + /** + * A `VariableWriteAccessCfgNode` that is not succeeded (locally) by another + * write to that variable. + */ + private class FinalInstanceVarWrite extends CfgNodes::ExprNodes::InstanceVariableWriteAccessCfgNode { + private InstanceVariable var; + + FinalInstanceVarWrite() { + var = this.getExpr().getVariable() and + not exists(CfgNodes::ExprNodes::InstanceVariableWriteAccessCfgNode succWrite | + succWrite.getExpr().getVariable() = var + | + succWrite = this.getASuccessor+() + ) + } + + InstanceVariable getVariable() { result = var } + + AssignExpr getAnAssignExpr() { result.getLeftOperand() = this.getExpr() } + } + + /** + * An additional step that is taint-preserving in the context of reflected XSS. + */ + predicate isAdditionalXSSTaintStep(DataFlow::Node node1, DataFlow::Node node2) { + // node1 is a `locals` argument to a render call... + exists(RenderCall call, Pair kvPair, string hashKey | + call.getLocals().getAKeyValuePair() = kvPair and + kvPair.getValue() = node1.asExpr().getExpr() and + kvPair.getKey().(StringlikeLiteral).getValueText() = hashKey and + // `node2` appears in the rendered template file + call.getTemplateFile() = node2.getLocation().getFile() and + ( + // node2 is an element reference against `local_assigns` + exists( + CfgNodes::ExprNodes::ElementReferenceCfgNode refNode, DataFlow::Node argNode, + CfgNodes::ExprNodes::StringlikeLiteralCfgNode strNode + | + refNode = node2.asExpr() and + argNode.asExpr() = refNode.getArgument(0) and + refNode.getReceiver().getExpr().(MethodCall).getMethodName() = "local_assigns" and + argNode.getALocalSource() = DataFlow::exprNode(strNode) and + strNode.getExpr().getValueText() = hashKey + ) + or + // ...node2 is a "method call" to a "method" with `hashKey` as its name + // TODO: This may be a variable read in reality that we interpret as a method call + exists(MethodCall varAcc | + varAcc = node2.asExpr().(CfgNodes::ExprNodes::MethodCallCfgNode).getExpr() and + varAcc.getMethodName() = hashKey + ) + ) + ) + or + // instance variables in the controller + exists( + ActionControllerActionMethod action, VariableReadAccess viewVarRead, AssignExpr ae, + FinalInstanceVarWrite controllerVarWrite + | + viewVarRead = node2.asExpr().(CfgNodes::ExprNodes::VariableReadAccessCfgNode).getExpr() and + action.getDefaultTemplateFile() = viewVarRead.getLocation().getFile() and + // match read to write on variable name + viewVarRead.getVariable().getName() = controllerVarWrite.getVariable().getName() and + // propagate taint from assignment RHS expr to variable read access in view + ae = controllerVarWrite.getAnAssignExpr() and + node1.asExpr().getExpr() = ae.getRightOperand() and + ae.getParent+() = action + ) + or + // flow from template into controller helper method + exists( + ErbFile template, ActionControllerHelperMethod helperMethod, + CfgNodes::ExprNodes::MethodCallCfgNode helperMethodCall, int argIdx + | + template = node1.getLocation().getFile() and + helperMethod.getName() = helperMethodCall.getExpr().getMethodName() and + helperMethod.getControllerClass() = getAssociatedControllerClass(template) and + helperMethodCall.getArgument(argIdx) = node1.asExpr() and + helperMethod.getParameter(argIdx) = node2.asExpr().getExpr() + ) + or + // flow out of controller helper method into template + exists( + ErbFile template, ActionControllerHelperMethod helperMethod, + CfgNodes::ExprNodes::MethodCallCfgNode helperMethodCall + | + template = node2.getLocation().getFile() and + helperMethod.getName() = helperMethodCall.getExpr().getMethodName() and + helperMethod.getControllerClass() = getAssociatedControllerClass(template) and + // `node1` is an expr node that may be returned by the helper method + exprNodeReturnedFrom(node1, helperMethod) and + // `node2` is a call to the helper method + node2.asExpr() = helperMethodCall + ) + } +} diff --git a/ruby/ql/lib/codeql/ruby/security/ReflectedXSSQuery.qll b/ruby/ql/lib/codeql/ruby/security/ReflectedXSSQuery.qll new file mode 100644 index 000000000000..182501d65645 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/security/ReflectedXSSQuery.qll @@ -0,0 +1,39 @@ +/** + * Provides a taint-tracking configuration for detecting "reflected server-side cross-site scripting" vulnerabilities. + * + * Note, for performance reasons: only import this file if + * `ReflectedXSS::Configuration` is needed, otherwise + * `ReflectedXSSCustomizations` should be imported instead. + */ + +private import ruby +import codeql.ruby.DataFlow +import codeql.ruby.TaintTracking + +/** + * Provides a taint-tracking configuration for detecting "reflected server-side cross-site scripting" vulnerabilities. + */ +module ReflectedXSS { + import ReflectedXSSCustomizations::ReflectedXSS + + /** + * A taint-tracking configuration for detecting "reflected server-side cross-site scripting" vulnerabilities. + */ + class Configuration extends TaintTracking::Configuration { + Configuration() { this = "ReflectedXSS" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer } + + override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { + guard instanceof SanitizerGuard + } + + override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { + isAdditionalXSSTaintStep(node1, node2) + } + } +} diff --git a/ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationCustomizations.qll new file mode 100644 index 000000000000..84e61bd1ece8 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationCustomizations.qll @@ -0,0 +1,83 @@ +/** + * Provides default sources, sinks and sanitizers for reasoning about unsafe + * deserialization, as well as extension points for adding your own. + */ + +private import ruby +private import codeql.ruby.ApiGraphs +private import codeql.ruby.CFG +private import codeql.ruby.DataFlow +private import codeql.ruby.dataflow.RemoteFlowSources + +module UnsafeDeserialization { + /** + * A data flow source for unsafe deserialization vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for unsafe deserialization vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { } + + /** + * A sanitizer for unsafe deserialization vulnerabilities. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * Additional taint steps for "unsafe deserialization" vulnerabilities. + */ + predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) { + base64DecodeTaintStep(fromNode, toNode) + } + + /** A source of remote user input, considered as a flow source for unsafe deserialization. */ + class RemoteFlowSourceAsSource extends Source { + RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource } + } + + /** + * An argument in a call to `Marshal.load` or `Marshal.restore`, considered a + * sink for unsafe deserialization. + */ + class MarshalLoadOrRestoreArgument extends Sink { + MarshalLoadOrRestoreArgument() { + this = API::getTopLevelMember("Marshal").getAMethodCall(["load", "restore"]).getArgument(0) + } + } + + /** + * An argument in a call to `YAML.load`, considered a sink for unsafe + * deserialization. + */ + class YamlLoadArgument extends Sink { + YamlLoadArgument() { + this = API::getTopLevelMember("YAML").getAMethodCall("load").getArgument(0) + } + } + + /** + * An argument in a call to `JSON.load` or `JSON.restore`, considered a sink + * for unsafe deserialization. + */ + class JsonLoadArgument extends Sink { + JsonLoadArgument() { + this = API::getTopLevelMember("JSON").getAMethodCall(["load", "restore"]).getArgument(0) + } + } + + /** + * `Base64.decode64` propagates taint from its argument to its return value. + */ + predicate base64DecodeTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) { + exists(DataFlow::CallNode callNode | + callNode = + API::getTopLevelMember("Base64") + .getAMethodCall(["decode64", "strict_decode64", "urlsafe_decode64"]) + | + fromNode = callNode.getArgument(0) and + toNode = callNode + ) + } +} diff --git a/ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationQuery.qll b/ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationQuery.qll new file mode 100644 index 000000000000..d08b73da9361 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationQuery.qll @@ -0,0 +1,34 @@ +/** + * Provides a taint-tracking configuration for reasoning about unsafe deserialization. + * + * Note, for performance reasons: only import this file if + * `UnsafeDeserialization::Configuration` is needed, otherwise + * `UnsafeDeserializationCustomizations` should be imported instead. + */ + +private import ruby +private import codeql.ruby.DataFlow +private import codeql.ruby.TaintTracking +import UnsafeDeserializationCustomizations + +/** + * A taint-tracking configuration for reasoning about unsafe deserialization. + */ +class Configuration extends TaintTracking::Configuration { + Configuration() { this = "UnsafeDeserialization" } + + override predicate isSource(DataFlow::Node source) { + source instanceof UnsafeDeserialization::Source + } + + override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeDeserialization::Sink } + + override predicate isSanitizer(DataFlow::Node node) { + super.isSanitizer(node) or + node instanceof UnsafeDeserialization::Sanitizer + } + + override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) { + UnsafeDeserialization::isAdditionalTaintStep(fromNode, toNode) + } +} diff --git a/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll new file mode 100644 index 000000000000..caaf22640188 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll @@ -0,0 +1,127 @@ +/** + * Provides default sources, sinks and sanitizers for detecting "URL + * redirection" vulnerabilities, as well as extension points for adding your + * own. + */ + +private import ruby +private import codeql.ruby.DataFlow +private import codeql.ruby.Concepts +private import codeql.ruby.dataflow.RemoteFlowSources +private import codeql.ruby.dataflow.BarrierGuards + +/** + * Provides default sources, sinks and sanitizers for detecting + * "URL redirection" vulnerabilities, as well as extension points for + * adding your own. + */ +module UrlRedirect { + /** + * A data flow source for "URL redirection" vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for "URL redirection" vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { } + + /** + * A sanitizer for "URL redirection" vulnerabilities. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * A sanitizer guard for "URL redirection" vulnerabilities. + */ + abstract class SanitizerGuard extends DataFlow::BarrierGuard { } + + /** + * Additional taint steps for "URL redirection" vulnerabilities. + */ + predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { + taintStepViaMethodCallReturnValue(node1, node2) + } + + /** + * A source of remote user input, considered as a flow source. + */ + class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { } + + /** + * A HTTP redirect response, considered as a flow sink. + */ + class RedirectLocationAsSink extends Sink { + RedirectLocationAsSink() { + exists(HTTP::Server::HttpRedirectResponse e | + this = e.getRedirectLocation() and + // As a rough heuristic, assume that methods with these names are handlers for POST/PUT/PATCH/DELETE requests, + // which are not as vulnerable to URL redirection because browsers will not initiate them from clicking a link. + not this.getEnclosingCallable() + .asCallable() + .(Method) + .getName() + .regexpMatch(".*(create|update|destroy).*") + ) + } + } + + /** + * A comparison with a constant string, considered as a sanitizer-guard. + */ + class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { } + + /** + * Some methods will propagate taint to their return values. + * Here we cover a few common ones related to `ActionController::Parameters`. + * TODO: use ApiGraphs or something to restrict these method calls to the correct receiver, rather + * than matching on method name alone. + */ + predicate taintStepViaMethodCallReturnValue(DataFlow::Node node1, DataFlow::Node node2) { + exists(MethodCall m | m = node2.asExpr().getExpr() | + m.getReceiver() = node1.asExpr().getExpr() and + (actionControllerTaintedMethod(m) or hashTaintedMethod(m)) + ) + } + + /** + * String interpolation is considered safe, provided the string is prefixed by a non-tainted value. + * In most cases this will prevent the tainted value from controlling e.g. the host of the URL. + * + * For example: + * + * ```ruby + * redirect_to "/users/#{params[:key]}" # safe + * redirect_to "#{params[:key]}/users" # unsafe + * ``` + * + * There are prefixed interpolations that are not safe, e.g. + * + * ```ruby + * redirect_to "foo#{params[:key]}/users" # => "foo-malicious-site.com/users" + * ``` + * + * We currently don't catch these cases. + */ + class StringInterpolationAsSanitizer extends Sanitizer { + StringInterpolationAsSanitizer() { + exists(StringlikeLiteral str, int n | str.getComponent(n) = this.asExpr().getExpr() and n > 0) + } + } + + /** + * These methods return a new `ActionController::Parameters` or a `Hash` containing a subset of + * the original values. This may still contain user input, so the results are tainted. + * TODO: flesh this out to cover the whole API. + */ + predicate actionControllerTaintedMethod(MethodCall m) { + m.getMethodName() in ["to_unsafe_hash", "to_unsafe_h", "permit", "require"] + } + + /** + * These `Hash` methods preserve taint because they return a new hash which may still contain keys + * with user input. + * TODO: flesh this out to cover the whole API. + */ + predicate hashTaintedMethod(MethodCall m) { m.getMethodName() in ["merge", "fetch"] } +} diff --git a/ruby/ql/lib/codeql/ruby/security/UrlRedirectQuery.qll b/ruby/ql/lib/codeql/ruby/security/UrlRedirectQuery.qll new file mode 100644 index 000000000000..5a984d1fd6e0 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/security/UrlRedirectQuery.qll @@ -0,0 +1,34 @@ +/** + * Provides a taint-tracking configuration for detecting "URL redirection" vulnerabilities. + * + * Note, for performance reasons: only import this file if `Configuration` is needed, + * otherwise `UrlRedirectCustomizations` should be imported instead. + */ + +private import ruby +import codeql.ruby.DataFlow::DataFlow::PathGraph +import codeql.ruby.DataFlow +import codeql.ruby.TaintTracking +import UrlRedirectCustomizations +import UrlRedirectCustomizations::UrlRedirect + +/** + * A taint-tracking configuration for detecting "URL redirection" vulnerabilities. + */ +class Configuration extends TaintTracking::Configuration { + Configuration() { this = "UrlRedirect" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer } + + override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { + guard instanceof SanitizerGuard + } + + override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { + UrlRedirect::isAdditionalTaintStep(node1, node2) + } +} diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll new file mode 100644 index 000000000000..6ced6a8206e8 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll @@ -0,0 +1,470 @@ +/** Step Summaries and Type Tracking */ + +private import TypeTrackerSpecific + +/** + * Any string that may appear as the name of a piece of content. This will usually include things like: + * - Attribute names (in Python) + * - Property names (in JavaScript) + * + * In general, this can also be used to model things like stores to specific list indices. To ensure + * correctness, it is important that + * + * - different types of content do not have overlapping names, and + * - the empty string `""` is not a valid piece of content, as it is used to indicate the absence of + * content instead. + */ +class ContentName extends string { + ContentName() { this = getPossibleContentName() } +} + +/** Either a content name, or the empty string (representing no content). */ +class OptionalContentName extends string { + OptionalContentName() { this instanceof ContentName or this = "" } +} + +cached +private module Cached { + /** + * A description of a step on an inter-procedural data flow path. + */ + cached + newtype TStepSummary = + LevelStep() or + CallStep() or + ReturnStep() or + StoreStep(ContentName content) or + LoadStep(ContentName content) + + /** Gets the summary resulting from appending `step` to type-tracking summary `tt`. */ + cached + TypeTracker append(TypeTracker tt, StepSummary step) { + exists(Boolean hasCall, OptionalContentName content | tt = MkTypeTracker(hasCall, content) | + step = LevelStep() and result = tt + or + step = CallStep() and result = MkTypeTracker(true, content) + or + step = ReturnStep() and hasCall = false and result = tt + or + step = LoadStep(content) and result = MkTypeTracker(hasCall, "") + or + exists(string p | step = StoreStep(p) and content = "" and result = MkTypeTracker(hasCall, p)) + ) + } + + /** + * Gets the summary that corresponds to having taken a forwards + * heap and/or intra-procedural step from `nodeFrom` to `nodeTo`. + * + * Steps contained in this predicate should _not_ depend on the call graph. + */ + cached + predicate stepNoCall(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { + exists(Node mid | nodeFrom.flowsTo(mid) and smallstepNoCall(mid, nodeTo, summary)) + } + + /** + * Gets the summary that corresponds to having taken a forwards + * inter-procedural step from `nodeFrom` to `nodeTo`. + */ + cached + predicate stepCall(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { + exists(Node mid | nodeFrom.flowsTo(mid) and smallstepCall(mid, nodeTo, summary)) + } +} + +private import Cached + +/** + * INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead. + * + * A description of a step on an inter-procedural data flow path. + */ +class StepSummary extends TStepSummary { + /** Gets a textual representation of this step summary. */ + string toString() { + this instanceof LevelStep and result = "level" + or + this instanceof CallStep and result = "call" + or + this instanceof ReturnStep and result = "return" + or + exists(string content | this = StoreStep(content) | result = "store " + content) + or + exists(string content | this = LoadStep(content) | result = "load " + content) + } +} + +pragma[noinline] +private predicate smallstepNoCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { + jumpStep(nodeFrom, nodeTo) and + summary = LevelStep() + or + exists(string content | + StepSummary::localSourceStoreStep(nodeFrom, nodeTo, content) and + summary = StoreStep(content) + or + basicLoadStep(nodeFrom, nodeTo, content) and summary = LoadStep(content) + ) +} + +pragma[noinline] +private predicate smallstepCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { + callStep(nodeFrom, nodeTo) and summary = CallStep() + or + returnStep(nodeFrom, nodeTo) and + summary = ReturnStep() +} + +/** Provides predicates for updating step summaries (`StepSummary`s). */ +module StepSummary { + /** + * Gets the summary that corresponds to having taken a forwards + * heap and/or inter-procedural step from `nodeFrom` to `nodeTo`. + * + * This predicate is inlined, which enables better join-orders when + * the call graph construction and type tracking are mutually recursive. + * In such cases, non-linear recursion involving `step` will be limited + * to non-linear recursion for the parts of `step` that involve the + * call graph. + */ + pragma[inline] + predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { + stepNoCall(nodeFrom, nodeTo, summary) + or + stepCall(nodeFrom, nodeTo, summary) + } + + /** + * Gets the summary that corresponds to having taken a forwards + * local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`. + * + * Unlike `StepSummary::step`, this predicate does not compress + * type-preserving steps. + */ + pragma[inline] + predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { + smallstepNoCall(nodeFrom, nodeTo, summary) + or + smallstepCall(nodeFrom, nodeTo, summary) + } + + /** + * Holds if `nodeFrom` is being written to the `content` content of the object in `nodeTo`. + * + * Note that `nodeTo` will always be a local source node that flows to the place where the content + * is written in `basicStoreStep`. This may lead to the flow of information going "back in time" + * from the point of view of the execution of the program. + * + * For instance, if we interpret attribute writes in Python as writing to content with the same + * name as the attribute and consider the following snippet + * + * ```python + * def foo(y): + * x = Foo() + * bar(x) + * x.attr = y + * baz(x) + * + * def bar(x): + * z = x.attr + * ``` + * for the attribute write `x.attr = y`, we will have `content` being the literal string `"attr"`, + * `nodeFrom` will be `y`, and `nodeTo` will be the object `Foo()` created on the first line of the + * function. This means we will track the fact that `x.attr` can have the type of `y` into the + * assignment to `z` inside `bar`, even though this attribute write happens _after_ `bar` is called. + */ + predicate localSourceStoreStep(Node nodeFrom, TypeTrackingNode nodeTo, string content) { + exists(Node obj | nodeTo.flowsTo(obj) and basicStoreStep(nodeFrom, obj, content)) + } +} + +private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalContentName content) + +/** + * Summary of the steps needed to track a value to a given dataflow node. + * + * This can be used to track objects that implement a certain API in order to + * recognize calls to that API. Note that type-tracking does not by itself provide a + * source/sink relation, that is, it may determine that a node has a given type, + * but it won't determine where that type came from. + * + * It is recommended that all uses of this type are written in the following form, + * for tracking some type `myType`: + * ```ql + * DataFlow::TypeTrackingNode myType(DataFlow::TypeTracker t) { + * t.start() and + * result = < source of myType > + * or + * exists (DataFlow::TypeTracker t2 | + * result = myType(t2).track(t2, t) + * ) + * } + * + * DataFlow::Node myType() { myType(DataFlow::TypeTracker::end()).flowsTo(result) } + * ``` + * + * Instead of `result = myType(t2).track(t2, t)`, you can also use the equivalent + * `t = t2.step(myType(t2), result)`. If you additionally want to track individual + * intra-procedural steps, use `t = t2.smallstep(myCallback(t2), result)`. + */ +class TypeTracker extends TTypeTracker { + Boolean hasCall; + OptionalContentName content; + + TypeTracker() { this = MkTypeTracker(hasCall, content) } + + /** Gets the summary resulting from appending `step` to this type-tracking summary. */ + TypeTracker append(StepSummary step) { result = append(this, step) } + + /** Gets a textual representation of this summary. */ + string toString() { + exists(string withCall, string withContent | + (if hasCall = true then withCall = "with" else withCall = "without") and + (if content != "" then withContent = " with content " + content else withContent = "") and + result = "type tracker " + withCall + " call steps" + withContent + ) + } + + /** + * Holds if this is the starting point of type tracking. + */ + predicate start() { hasCall = false and content = "" } + + /** + * Holds if this is the starting point of type tracking, and the value starts in the content named `contentName`. + * The type tracking only ends after the content has been loaded. + */ + predicate startInContent(ContentName contentName) { hasCall = false and content = contentName } + + /** + * Holds if this is the starting point of type tracking + * when tracking a parameter into a call, but not out of it. + */ + predicate call() { hasCall = true and content = "" } + + /** + * Holds if this is the end point of type tracking. + */ + predicate end() { content = "" } + + /** + * INTERNAL. DO NOT USE. + * + * Holds if this type has been tracked into a call. + */ + boolean hasCall() { result = hasCall } + + /** + * INTERNAL. DO NOT USE. + * + * Gets the content associated with this type tracker. + */ + string getContent() { result = content } + + /** + * Gets a type tracker that starts where this one has left off to allow continued + * tracking. + * + * This predicate is only defined if the type is not associated to a piece of content. + */ + TypeTracker continue() { content = "" and result = this } + + /** + * Gets the summary that corresponds to having taken a forwards + * heap and/or inter-procedural step from `nodeFrom` to `nodeTo`. + */ + pragma[inline] + TypeTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) { + exists(StepSummary summary | + StepSummary::step(nodeFrom, pragma[only_bind_out](nodeTo), pragma[only_bind_into](summary)) and + result = this.append(pragma[only_bind_into](summary)) + ) + } + + /** + * Gets the summary that corresponds to having taken a forwards + * local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`. + * + * Unlike `TypeTracker::step`, this predicate exposes all edges + * in the flow graph, and not just the edges between `Node`s. + * It may therefore be less performant. + * + * Type tracking predicates using small steps typically take the following form: + * ```ql + * DataFlow::Node myType(DataFlow::TypeTracker t) { + * t.start() and + * result = < source of myType > + * or + * exists (DataFlow::TypeTracker t2 | + * t = t2.smallstep(myType(t2), result) + * ) + * } + * + * DataFlow::Node myType() { + * result = myType(DataFlow::TypeTracker::end()) + * } + * ``` + */ + pragma[inline] + TypeTracker smallstep(Node nodeFrom, Node nodeTo) { + exists(StepSummary summary | + StepSummary::smallstep(nodeFrom, nodeTo, summary) and + result = this.append(summary) + ) + or + simpleLocalFlowStep(nodeFrom, nodeTo) and + result = this + } +} + +/** Provides predicates for implementing custom `TypeTracker`s. */ +module TypeTracker { + /** + * Gets a valid end point of type tracking. + */ + TypeTracker end() { result.end() } +} + +private newtype TTypeBackTracker = MkTypeBackTracker(Boolean hasReturn, OptionalContentName content) + +/** + * Summary of the steps needed to back-track a use of a value to a given dataflow node. + * + * This can for example be used to track callbacks that are passed to a certain API, + * so we can model specific parameters of that callback as having a certain type. + * + * Note that type back-tracking does not provide a source/sink relation, that is, + * it may determine that a node will be used in an API call somewhere, but it won't + * determine exactly where that use was, or the path that led to the use. + * + * It is recommended that all uses of this type are written in the following form, + * for back-tracking some callback type `myCallback`: + * + * ```ql + * DataFlow::TypeTrackingNode myCallback(DataFlow::TypeBackTracker t) { + * t.start() and + * result = (< some API call >).getArgument(< n >).getALocalSource() + * or + * exists (DataFlow::TypeBackTracker t2 | + * result = myCallback(t2).backtrack(t2, t) + * ) + * } + * + * DataFlow::TypeTrackingNode myCallback() { result = myCallback(DataFlow::TypeBackTracker::end()) } + * ``` + * + * Instead of `result = myCallback(t2).backtrack(t2, t)`, you can also use the equivalent + * `t2 = t.step(result, myCallback(t2))`. If you additionally want to track individual + * intra-procedural steps, use `t2 = t.smallstep(result, myCallback(t2))`. + */ +class TypeBackTracker extends TTypeBackTracker { + Boolean hasReturn; + string content; + + TypeBackTracker() { this = MkTypeBackTracker(hasReturn, content) } + + /** Gets the summary resulting from prepending `step` to this type-tracking summary. */ + TypeBackTracker prepend(StepSummary step) { + step = LevelStep() and result = this + or + step = CallStep() and hasReturn = false and result = this + or + step = ReturnStep() and result = MkTypeBackTracker(true, content) + or + exists(string p | + step = LoadStep(p) and content = "" and result = MkTypeBackTracker(hasReturn, p) + ) + or + step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, "") + } + + /** Gets a textual representation of this summary. */ + string toString() { + exists(string withReturn, string withContent | + (if hasReturn = true then withReturn = "with" else withReturn = "without") and + (if content != "" then withContent = " with content " + content else withContent = "") and + result = "type back-tracker " + withReturn + " return steps" + withContent + ) + } + + /** + * Holds if this is the starting point of type tracking. + */ + predicate start() { hasReturn = false and content = "" } + + /** + * Holds if this is the end point of type tracking. + */ + predicate end() { content = "" } + + /** + * INTERNAL. DO NOT USE. + * + * Holds if this type has been back-tracked into a call through return edge. + */ + boolean hasReturn() { result = hasReturn } + + /** + * Gets a type tracker that starts where this one has left off to allow continued + * tracking. + * + * This predicate is only defined if the type has not been tracked into a piece of content. + */ + TypeBackTracker continue() { content = "" and result = this } + + /** + * Gets the summary that corresponds to having taken a backwards + * heap and/or inter-procedural step from `nodeTo` to `nodeFrom`. + */ + pragma[inline] + TypeBackTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) { + exists(StepSummary summary | + StepSummary::step(pragma[only_bind_out](nodeFrom), nodeTo, pragma[only_bind_into](summary)) and + this = result.prepend(pragma[only_bind_into](summary)) + ) + } + + /** + * Gets the summary that corresponds to having taken a backwards + * local, heap and/or inter-procedural step from `nodeTo` to `nodeFrom`. + * + * Unlike `TypeBackTracker::step`, this predicate exposes all edges + * in the flowgraph, and not just the edges between + * `TypeTrackingNode`s. It may therefore be less performant. + * + * Type tracking predicates using small steps typically take the following form: + * ```ql + * DataFlow::Node myType(DataFlow::TypeBackTracker t) { + * t.start() and + * result = < some API call >.getArgument(< n >) + * or + * exists (DataFlow::TypeBackTracker t2 | + * t = t2.smallstep(result, myType(t2)) + * ) + * } + * + * DataFlow::Node myType() { + * result = myType(DataFlow::TypeBackTracker::end()) + * } + * ``` + */ + pragma[inline] + TypeBackTracker smallstep(Node nodeFrom, Node nodeTo) { + exists(StepSummary summary | + StepSummary::smallstep(nodeFrom, nodeTo, summary) and + this = result.prepend(summary) + ) + or + simpleLocalFlowStep(nodeFrom, nodeTo) and + this = result + } +} + +/** Provides predicates for implementing custom `TypeBackTracker`s. */ +module TypeBackTracker { + /** + * Gets a valid end point of type back-tracking. + */ + TypeBackTracker end() { result.end() } +} diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll new file mode 100644 index 000000000000..108a48196e49 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -0,0 +1,130 @@ +private import codeql.ruby.AST as AST +private import codeql.ruby.CFG as CFG +private import CFG::CfgNodes +private import codeql.ruby.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon +private import codeql.ruby.dataflow.internal.DataFlowPublic as DataFlowPublic +private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate +private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatch +private import codeql.ruby.dataflow.internal.SsaImpl as SsaImpl + +class Node = DataFlowPublic::Node; + +class TypeTrackingNode = DataFlowPublic::LocalSourceNode; + +predicate simpleLocalFlowStep = DataFlowPrivate::simpleLocalFlowStep/2; + +predicate jumpStep = DataFlowPrivate::jumpStep/2; + +/** + * Gets the name of a possible piece of content. This will usually include things like + * + * - Attribute names (in Python) + * - Property names (in JavaScript) + */ +string getPossibleContentName() { result = getSetterCallAttributeName(_) } + +/** + * Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call. + * + * Flow into summarized library methods is not included, as that will lead to negative + * recursion (or, at best, terrible performance), since identifying calls to library + * methods is done using API graphs (which uses type tracking). + */ +predicate callStep(DataFlowPrivate::ArgumentNode nodeFrom, DataFlowPublic::ParameterNode nodeTo) { + exists(ExprNodes::CallCfgNode call, CFG::CfgScope callable, int i | + DataFlowDispatch::getTarget(call) = callable and + nodeFrom.sourceArgumentOf(call, i) and + nodeTo.(DataFlowPrivate::ParameterNodeImpl).isSourceParameterOf(callable, i) + ) +} + +/** + * Holds if `nodeFrom` steps to `nodeTo` by being returned from a call. + * + * Flow out of summarized library methods is not included, as that will lead to negative + * recursion (or, at best, terrible performance), since identifying calls to library + * methods is done using API graphs (which uses type tracking). + */ +predicate returnStep(DataFlowPrivate::ReturnNode nodeFrom, Node nodeTo) { + exists(ExprNodes::CallCfgNode call | + nodeFrom.(DataFlowPrivate::NodeImpl).getCfgScope() = DataFlowDispatch::getTarget(call) and + nodeTo.asExpr().getNode() = call.getNode() + ) +} + +/** + * Holds if `nodeFrom` is being written to the `content` content of the object + * in `nodeTo`. + * + * Note that the choice of `nodeTo` does not have to make sense + * "chronologically". All we care about is whether the `content` content of + * `nodeTo` can have a specific type, and the assumption is that if a specific + * type appears here, then any access of that particular content can yield + * something of that particular type. + * + * Thus, in an example such as + * + * ```rb + * def foo(y) + * x = Foo.new + * bar(x) + * x.content = y + * baz(x) + * end + * + * def bar(x) + * z = x.content + * end + * ``` + * for the content write `x.content = y`, we will have `content` being the + * literal string `"content"`, `nodeFrom` will be `y`, and `nodeTo` will be the + * `Foo` object created on the first line of the function. This means we will + * track the fact that `x.content` can have the type of `y` into the assignment + * to `z` inside `bar`, even though this content write happens _after_ `bar` is + * called. + */ +predicate basicStoreStep(Node nodeFrom, DataFlowPublic::LocalSourceNode nodeTo, string content) { + // TODO: support SetterMethodCall inside TuplePattern + exists(ExprNodes::MethodCallCfgNode call | + content = getSetterCallAttributeName(call.getExpr()) and + nodeTo.(DataFlowPublic::ExprNode).getExprNode() = call.getReceiver() and + call.getExpr() instanceof AST::SetterMethodCall and + call.getArgument(call.getNumberOfArguments() - 1) = + nodeFrom.(DataFlowPublic::ExprNode).getExprNode() + ) +} + +/** + * Returns the name of the attribute being set by the setter method call, i.e. + * the name of the setter method without the trailing `=`. In the following + * example, the result is `"bar"`. + * + * ```rb + * foo.bar = 1 + * ``` + */ +private string getSetterCallAttributeName(AST::SetterMethodCall call) { + // TODO: this should be exposed in `SetterMethodCall` + exists(string setterName | + setterName = call.getMethodName() and result = setterName.prefix(setterName.length() - 1) + ) +} + +/** + * Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`. + */ +predicate basicLoadStep(Node nodeFrom, Node nodeTo, string content) { + exists(ExprNodes::MethodCallCfgNode call | + call.getExpr().getNumberOfArguments() = 0 and + content = call.getExpr().(AST::MethodCall).getMethodName() and + nodeFrom.asExpr() = call.getReceiver() and + nodeTo.asExpr() = call + ) +} + +/** + * A utility class that is equivalent to `boolean` but does not require type joining. + */ +class Boolean extends boolean { + Boolean() { this = true or this = false } +} diff --git a/ruby/ql/lib/qlpack.lock.yml b/ruby/ql/lib/qlpack.lock.yml new file mode 100644 index 000000000000..06dd07fc7dc7 --- /dev/null +++ b/ruby/ql/lib/qlpack.lock.yml @@ -0,0 +1,4 @@ +--- +dependencies: {} +compiled: false +lockVersion: 1.0.0 diff --git a/ruby/ql/lib/qlpack.yml b/ruby/ql/lib/qlpack.yml new file mode 100644 index 000000000000..91f40532fc95 --- /dev/null +++ b/ruby/ql/lib/qlpack.yml @@ -0,0 +1,6 @@ +name: codeql/ruby-all +version: 0.0.2 +extractor: ruby +dbscheme: ruby.dbscheme +upgrades: upgrades +library: true diff --git a/ruby/ql/lib/ruby.dbscheme b/ruby/ql/lib/ruby.dbscheme new file mode 100644 index 000000000000..31a238d080f3 --- /dev/null +++ b/ruby/ql/lib/ruby.dbscheme @@ -0,0 +1,1316 @@ +// CodeQL database schema for Ruby +// 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 +; + + +@ruby_underscore_arg = @ruby_assignment | @ruby_binary | @ruby_conditional | @ruby_operator_assignment | @ruby_range | @ruby_unary | @ruby_underscore_primary + +@ruby_underscore_lhs = @ruby_call | @ruby_element_reference | @ruby_scope_resolution | @ruby_token_false | @ruby_token_nil | @ruby_token_true | @ruby_underscore_variable + +@ruby_underscore_method_name = @ruby_delimited_symbol | @ruby_setter | @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_operator | @ruby_token_simple_symbol + +@ruby_underscore_primary = @ruby_array | @ruby_begin | @ruby_break | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_delimited_symbol | @ruby_for | @ruby_hash | @ruby_if | @ruby_lambda | @ruby_method | @ruby_module | @ruby_next | @ruby_parenthesized_statements | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_retry | @ruby_return | @ruby_singleton_class | @ruby_singleton_method | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_symbol_array | @ruby_token_character | @ruby_token_complex | @ruby_token_float | @ruby_token_heredoc_beginning | @ruby_token_integer | @ruby_token_simple_symbol | @ruby_unary | @ruby_underscore_lhs | @ruby_unless | @ruby_until | @ruby_while | @ruby_yield + +@ruby_underscore_statement = @ruby_alias | @ruby_assignment | @ruby_begin_block | @ruby_binary | @ruby_break | @ruby_call | @ruby_end_block | @ruby_if_modifier | @ruby_next | @ruby_operator_assignment | @ruby_rescue_modifier | @ruby_return | @ruby_unary | @ruby_undef | @ruby_underscore_arg | @ruby_unless_modifier | @ruby_until_modifier | @ruby_while_modifier | @ruby_yield + +@ruby_underscore_variable = @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_self | @ruby_token_super + +ruby_alias_def( + unique int id: @ruby_alias, + int alias: @ruby_underscore_method_name ref, + int name: @ruby_underscore_method_name ref, + int loc: @location ref +); + +@ruby_argument_list_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_argument_list, index] +ruby_argument_list_child( + int ruby_argument_list: @ruby_argument_list ref, + int index: int ref, + unique int child: @ruby_argument_list_child_type ref +); + +ruby_argument_list_def( + unique int id: @ruby_argument_list, + int loc: @location ref +); + +@ruby_array_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_array, index] +ruby_array_child( + int ruby_array: @ruby_array ref, + int index: int ref, + unique int child: @ruby_array_child_type ref +); + +ruby_array_def( + unique int id: @ruby_array, + int loc: @location ref +); + +@ruby_assignment_left_type = @ruby_left_assignment_list | @ruby_underscore_lhs + +@ruby_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_right_assignment_list | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +ruby_assignment_def( + unique int id: @ruby_assignment, + int left: @ruby_assignment_left_type ref, + int right: @ruby_assignment_right_type ref, + int loc: @location ref +); + +@ruby_bare_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_bare_string, index] +ruby_bare_string_child( + int ruby_bare_string: @ruby_bare_string ref, + int index: int ref, + unique int child: @ruby_bare_string_child_type ref +); + +ruby_bare_string_def( + unique int id: @ruby_bare_string, + int loc: @location ref +); + +@ruby_bare_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_bare_symbol, index] +ruby_bare_symbol_child( + int ruby_bare_symbol: @ruby_bare_symbol ref, + int index: int ref, + unique int child: @ruby_bare_symbol_child_type ref +); + +ruby_bare_symbol_def( + unique int id: @ruby_bare_symbol, + int loc: @location ref +); + +@ruby_begin_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_begin, index] +ruby_begin_child( + int ruby_begin: @ruby_begin ref, + int index: int ref, + unique int child: @ruby_begin_child_type ref +); + +ruby_begin_def( + unique int id: @ruby_begin, + int loc: @location ref +); + +@ruby_begin_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_begin_block, index] +ruby_begin_block_child( + int ruby_begin_block: @ruby_begin_block ref, + int index: int ref, + unique int child: @ruby_begin_block_child_type ref +); + +ruby_begin_block_def( + unique int id: @ruby_begin_block, + int loc: @location ref +); + +@ruby_binary_left_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +case @ruby_binary.operator of + 0 = @ruby_binary_bangequal +| 1 = @ruby_binary_bangtilde +| 2 = @ruby_binary_percent +| 3 = @ruby_binary_ampersand +| 4 = @ruby_binary_ampersandampersand +| 5 = @ruby_binary_star +| 6 = @ruby_binary_starstar +| 7 = @ruby_binary_plus +| 8 = @ruby_binary_minus +| 9 = @ruby_binary_slash +| 10 = @ruby_binary_langle +| 11 = @ruby_binary_langlelangle +| 12 = @ruby_binary_langleequal +| 13 = @ruby_binary_langleequalrangle +| 14 = @ruby_binary_equalequal +| 15 = @ruby_binary_equalequalequal +| 16 = @ruby_binary_equaltilde +| 17 = @ruby_binary_rangle +| 18 = @ruby_binary_rangleequal +| 19 = @ruby_binary_ranglerangle +| 20 = @ruby_binary_caret +| 21 = @ruby_binary_and +| 22 = @ruby_binary_or +| 23 = @ruby_binary_pipe +| 24 = @ruby_binary_pipepipe +; + + +@ruby_binary_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_binary_def( + unique int id: @ruby_binary, + int left: @ruby_binary_left_type ref, + int operator: int ref, + int right: @ruby_binary_right_type ref, + int loc: @location ref +); + +ruby_block_parameters( + unique int ruby_block: @ruby_block ref, + unique int parameters: @ruby_block_parameters ref +); + +@ruby_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_block, index] +ruby_block_child( + int ruby_block: @ruby_block ref, + int index: int ref, + unique int child: @ruby_block_child_type ref +); + +ruby_block_def( + unique int id: @ruby_block, + int loc: @location ref +); + +ruby_block_argument_def( + unique int id: @ruby_block_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_block_parameter_def( + unique int id: @ruby_block_parameter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_block_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_block_parameters, index] +ruby_block_parameters_child( + int ruby_block_parameters: @ruby_block_parameters ref, + int index: int ref, + unique int child: @ruby_block_parameters_child_type ref +); + +ruby_block_parameters_def( + unique int id: @ruby_block_parameters, + int loc: @location ref +); + +ruby_break_child( + unique int ruby_break: @ruby_break ref, + unique int child: @ruby_argument_list ref +); + +ruby_break_def( + unique int id: @ruby_break, + int loc: @location ref +); + +ruby_call_arguments( + unique int ruby_call: @ruby_call ref, + unique int arguments: @ruby_argument_list ref +); + +@ruby_call_block_type = @ruby_block | @ruby_do_block + +ruby_call_block( + unique int ruby_call: @ruby_call ref, + unique int block: @ruby_call_block_type ref +); + +@ruby_call_method_type = @ruby_argument_list | @ruby_scope_resolution | @ruby_token_operator | @ruby_underscore_variable + +@ruby_call_receiver_type = @ruby_call | @ruby_underscore_primary + +ruby_call_receiver( + unique int ruby_call: @ruby_call ref, + unique int receiver: @ruby_call_receiver_type ref +); + +ruby_call_def( + unique int id: @ruby_call, + int method: @ruby_call_method_type ref, + int loc: @location ref +); + +ruby_case_value( + unique int ruby_case__: @ruby_case__ ref, + unique int value: @ruby_underscore_statement ref +); + +@ruby_case_child_type = @ruby_else | @ruby_when + +#keyset[ruby_case__, index] +ruby_case_child( + int ruby_case__: @ruby_case__ ref, + int index: int ref, + unique int child: @ruby_case_child_type ref +); + +ruby_case_def( + unique int id: @ruby_case__, + int loc: @location ref +); + +#keyset[ruby_chained_string, index] +ruby_chained_string_child( + int ruby_chained_string: @ruby_chained_string ref, + int index: int ref, + unique int child: @ruby_string__ ref +); + +ruby_chained_string_def( + unique int id: @ruby_chained_string, + int loc: @location ref +); + +@ruby_class_name_type = @ruby_scope_resolution | @ruby_token_constant + +ruby_class_superclass( + unique int ruby_class: @ruby_class ref, + unique int superclass: @ruby_superclass ref +); + +@ruby_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_class, index] +ruby_class_child( + int ruby_class: @ruby_class ref, + int index: int ref, + unique int child: @ruby_class_child_type ref +); + +ruby_class_def( + unique int id: @ruby_class, + int name: @ruby_class_name_type ref, + int loc: @location ref +); + +ruby_conditional_def( + unique int id: @ruby_conditional, + int alternative: @ruby_underscore_arg ref, + int condition: @ruby_underscore_arg ref, + int consequence: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_delimited_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_delimited_symbol, index] +ruby_delimited_symbol_child( + int ruby_delimited_symbol: @ruby_delimited_symbol ref, + int index: int ref, + unique int child: @ruby_delimited_symbol_child_type ref +); + +ruby_delimited_symbol_def( + unique int id: @ruby_delimited_symbol, + int loc: @location ref +); + +@ruby_destructured_left_assignment_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs + +#keyset[ruby_destructured_left_assignment, index] +ruby_destructured_left_assignment_child( + int ruby_destructured_left_assignment: @ruby_destructured_left_assignment ref, + int index: int ref, + unique int child: @ruby_destructured_left_assignment_child_type ref +); + +ruby_destructured_left_assignment_def( + unique int id: @ruby_destructured_left_assignment, + int loc: @location ref +); + +@ruby_destructured_parameter_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_destructured_parameter, index] +ruby_destructured_parameter_child( + int ruby_destructured_parameter: @ruby_destructured_parameter ref, + int index: int ref, + unique int child: @ruby_destructured_parameter_child_type ref +); + +ruby_destructured_parameter_def( + unique int id: @ruby_destructured_parameter, + int loc: @location ref +); + +@ruby_do_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_do, index] +ruby_do_child( + int ruby_do: @ruby_do ref, + int index: int ref, + unique int child: @ruby_do_child_type ref +); + +ruby_do_def( + unique int id: @ruby_do, + int loc: @location ref +); + +ruby_do_block_parameters( + unique int ruby_do_block: @ruby_do_block ref, + unique int parameters: @ruby_block_parameters ref +); + +@ruby_do_block_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_do_block, index] +ruby_do_block_child( + int ruby_do_block: @ruby_do_block ref, + int index: int ref, + unique int child: @ruby_do_block_child_type ref +); + +ruby_do_block_def( + unique int id: @ruby_do_block, + int loc: @location ref +); + +@ruby_element_reference_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_element_reference, index] +ruby_element_reference_child( + int ruby_element_reference: @ruby_element_reference ref, + int index: int ref, + unique int child: @ruby_element_reference_child_type ref +); + +ruby_element_reference_def( + unique int id: @ruby_element_reference, + int object: @ruby_underscore_primary ref, + int loc: @location ref +); + +@ruby_else_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_else, index] +ruby_else_child( + int ruby_else: @ruby_else ref, + int index: int ref, + unique int child: @ruby_else_child_type ref +); + +ruby_else_def( + unique int id: @ruby_else, + int loc: @location ref +); + +@ruby_elsif_alternative_type = @ruby_else | @ruby_elsif + +ruby_elsif_alternative( + unique int ruby_elsif: @ruby_elsif ref, + unique int alternative: @ruby_elsif_alternative_type ref +); + +ruby_elsif_consequence( + unique int ruby_elsif: @ruby_elsif ref, + unique int consequence: @ruby_then ref +); + +ruby_elsif_def( + unique int id: @ruby_elsif, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_end_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_end_block, index] +ruby_end_block_child( + int ruby_end_block: @ruby_end_block ref, + int index: int ref, + unique int child: @ruby_end_block_child_type ref +); + +ruby_end_block_def( + unique int id: @ruby_end_block, + int loc: @location ref +); + +@ruby_ensure_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_ensure, index] +ruby_ensure_child( + int ruby_ensure: @ruby_ensure ref, + int index: int ref, + unique int child: @ruby_ensure_child_type ref +); + +ruby_ensure_def( + unique int id: @ruby_ensure, + int loc: @location ref +); + +ruby_exception_variable_def( + unique int id: @ruby_exception_variable, + int child: @ruby_underscore_lhs ref, + int loc: @location ref +); + +@ruby_exceptions_child_type = @ruby_splat_argument | @ruby_underscore_arg + +#keyset[ruby_exceptions, index] +ruby_exceptions_child( + int ruby_exceptions: @ruby_exceptions ref, + int index: int ref, + unique int child: @ruby_exceptions_child_type ref +); + +ruby_exceptions_def( + unique int id: @ruby_exceptions, + int loc: @location ref +); + +@ruby_for_pattern_type = @ruby_left_assignment_list | @ruby_underscore_lhs + +ruby_for_def( + unique int id: @ruby_for, + int body: @ruby_do ref, + int pattern: @ruby_for_pattern_type ref, + int value: @ruby_in ref, + int loc: @location ref +); + +@ruby_hash_child_type = @ruby_hash_splat_argument | @ruby_pair + +#keyset[ruby_hash, index] +ruby_hash_child( + int ruby_hash: @ruby_hash ref, + int index: int ref, + unique int child: @ruby_hash_child_type ref +); + +ruby_hash_def( + unique int id: @ruby_hash, + int loc: @location ref +); + +ruby_hash_splat_argument_def( + unique int id: @ruby_hash_splat_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_hash_splat_parameter_name( + unique int ruby_hash_splat_parameter: @ruby_hash_splat_parameter ref, + unique int name: @ruby_token_identifier ref +); + +ruby_hash_splat_parameter_def( + unique int id: @ruby_hash_splat_parameter, + int loc: @location ref +); + +@ruby_heredoc_body_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_heredoc_content | @ruby_token_heredoc_end + +#keyset[ruby_heredoc_body, index] +ruby_heredoc_body_child( + int ruby_heredoc_body: @ruby_heredoc_body ref, + int index: int ref, + unique int child: @ruby_heredoc_body_child_type ref +); + +ruby_heredoc_body_def( + unique int id: @ruby_heredoc_body, + int loc: @location ref +); + +@ruby_if_alternative_type = @ruby_else | @ruby_elsif + +ruby_if_alternative( + unique int ruby_if: @ruby_if ref, + unique int alternative: @ruby_if_alternative_type ref +); + +ruby_if_consequence( + unique int ruby_if: @ruby_if ref, + unique int consequence: @ruby_then ref +); + +ruby_if_def( + unique int id: @ruby_if, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_if_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_if_modifier_def( + unique int id: @ruby_if_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_if_modifier_condition_type ref, + int loc: @location ref +); + +ruby_in_def( + unique int id: @ruby_in, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_interpolation_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_interpolation, index] +ruby_interpolation_child( + int ruby_interpolation: @ruby_interpolation ref, + int index: int ref, + unique int child: @ruby_interpolation_child_type ref +); + +ruby_interpolation_def( + unique int id: @ruby_interpolation, + int loc: @location ref +); + +ruby_keyword_parameter_value( + unique int ruby_keyword_parameter: @ruby_keyword_parameter ref, + unique int value: @ruby_underscore_arg ref +); + +ruby_keyword_parameter_def( + unique int id: @ruby_keyword_parameter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_lambda_body_type = @ruby_block | @ruby_do_block + +ruby_lambda_parameters( + unique int ruby_lambda: @ruby_lambda ref, + unique int parameters: @ruby_lambda_parameters ref +); + +ruby_lambda_def( + unique int id: @ruby_lambda, + int body: @ruby_lambda_body_type ref, + int loc: @location ref +); + +@ruby_lambda_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_lambda_parameters, index] +ruby_lambda_parameters_child( + int ruby_lambda_parameters: @ruby_lambda_parameters ref, + int index: int ref, + unique int child: @ruby_lambda_parameters_child_type ref +); + +ruby_lambda_parameters_def( + unique int id: @ruby_lambda_parameters, + int loc: @location ref +); + +@ruby_left_assignment_list_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs + +#keyset[ruby_left_assignment_list, index] +ruby_left_assignment_list_child( + int ruby_left_assignment_list: @ruby_left_assignment_list ref, + int index: int ref, + unique int child: @ruby_left_assignment_list_child_type ref +); + +ruby_left_assignment_list_def( + unique int id: @ruby_left_assignment_list, + int loc: @location ref +); + +ruby_method_parameters( + unique int ruby_method: @ruby_method ref, + unique int parameters: @ruby_method_parameters ref +); + +@ruby_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_method, index] +ruby_method_child( + int ruby_method: @ruby_method ref, + int index: int ref, + unique int child: @ruby_method_child_type ref +); + +ruby_method_def( + unique int id: @ruby_method, + int name: @ruby_underscore_method_name ref, + int loc: @location ref +); + +@ruby_method_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_method_parameters, index] +ruby_method_parameters_child( + int ruby_method_parameters: @ruby_method_parameters ref, + int index: int ref, + unique int child: @ruby_method_parameters_child_type ref +); + +ruby_method_parameters_def( + unique int id: @ruby_method_parameters, + int loc: @location ref +); + +@ruby_module_name_type = @ruby_scope_resolution | @ruby_token_constant + +@ruby_module_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_module, index] +ruby_module_child( + int ruby_module: @ruby_module ref, + int index: int ref, + unique int child: @ruby_module_child_type ref +); + +ruby_module_def( + unique int id: @ruby_module, + int name: @ruby_module_name_type ref, + int loc: @location ref +); + +ruby_next_child( + unique int ruby_next: @ruby_next ref, + unique int child: @ruby_argument_list ref +); + +ruby_next_def( + unique int id: @ruby_next, + int loc: @location ref +); + +case @ruby_operator_assignment.operator of + 0 = @ruby_operator_assignment_percentequal +| 1 = @ruby_operator_assignment_ampersandampersandequal +| 2 = @ruby_operator_assignment_ampersandequal +| 3 = @ruby_operator_assignment_starstarequal +| 4 = @ruby_operator_assignment_starequal +| 5 = @ruby_operator_assignment_plusequal +| 6 = @ruby_operator_assignment_minusequal +| 7 = @ruby_operator_assignment_slashequal +| 8 = @ruby_operator_assignment_langlelangleequal +| 9 = @ruby_operator_assignment_ranglerangleequal +| 10 = @ruby_operator_assignment_caretequal +| 11 = @ruby_operator_assignment_pipeequal +| 12 = @ruby_operator_assignment_pipepipeequal +; + + +@ruby_operator_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_operator_assignment_def( + unique int id: @ruby_operator_assignment, + int left: @ruby_underscore_lhs ref, + int operator: int ref, + int right: @ruby_operator_assignment_right_type ref, + int loc: @location ref +); + +ruby_optional_parameter_def( + unique int id: @ruby_optional_parameter, + int name: @ruby_token_identifier ref, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_pair_key_type = @ruby_string__ | @ruby_token_hash_key_symbol | @ruby_underscore_arg + +ruby_pair_def( + unique int id: @ruby_pair, + int key__: @ruby_pair_key_type ref, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_parenthesized_statements_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_parenthesized_statements, index] +ruby_parenthesized_statements_child( + int ruby_parenthesized_statements: @ruby_parenthesized_statements ref, + int index: int ref, + unique int child: @ruby_parenthesized_statements_child_type ref +); + +ruby_parenthesized_statements_def( + unique int id: @ruby_parenthesized_statements, + int loc: @location ref +); + +@ruby_pattern_child_type = @ruby_splat_argument | @ruby_underscore_arg + +ruby_pattern_def( + unique int id: @ruby_pattern, + int child: @ruby_pattern_child_type ref, + int loc: @location ref +); + +@ruby_program_child_type = @ruby_token_empty_statement | @ruby_token_uninterpreted | @ruby_underscore_statement + +#keyset[ruby_program, index] +ruby_program_child( + int ruby_program: @ruby_program ref, + int index: int ref, + unique int child: @ruby_program_child_type ref +); + +ruby_program_def( + unique int id: @ruby_program, + int loc: @location ref +); + +ruby_range_begin( + unique int ruby_range: @ruby_range ref, + unique int begin: @ruby_underscore_arg ref +); + +ruby_range_end( + unique int ruby_range: @ruby_range ref, + unique int end: @ruby_underscore_arg ref +); + +case @ruby_range.operator of + 0 = @ruby_range_dotdot +| 1 = @ruby_range_dotdotdot +; + + +ruby_range_def( + unique int id: @ruby_range, + int operator: int ref, + int loc: @location ref +); + +@ruby_rational_child_type = @ruby_token_float | @ruby_token_integer + +ruby_rational_def( + unique int id: @ruby_rational, + int child: @ruby_rational_child_type ref, + int loc: @location ref +); + +ruby_redo_child( + unique int ruby_redo: @ruby_redo ref, + unique int child: @ruby_argument_list ref +); + +ruby_redo_def( + unique int id: @ruby_redo, + int loc: @location ref +); + +@ruby_regex_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_regex, index] +ruby_regex_child( + int ruby_regex: @ruby_regex ref, + int index: int ref, + unique int child: @ruby_regex_child_type ref +); + +ruby_regex_def( + unique int id: @ruby_regex, + int loc: @location ref +); + +ruby_rescue_body( + unique int ruby_rescue: @ruby_rescue ref, + unique int body: @ruby_then ref +); + +ruby_rescue_exceptions( + unique int ruby_rescue: @ruby_rescue ref, + unique int exceptions: @ruby_exceptions ref +); + +ruby_rescue_variable( + unique int ruby_rescue: @ruby_rescue ref, + unique int variable: @ruby_exception_variable ref +); + +ruby_rescue_def( + unique int id: @ruby_rescue, + int loc: @location ref +); + +@ruby_rescue_modifier_handler_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_rescue_modifier_def( + unique int id: @ruby_rescue_modifier, + int body: @ruby_underscore_statement ref, + int handler: @ruby_rescue_modifier_handler_type ref, + int loc: @location ref +); + +ruby_rest_assignment_child( + unique int ruby_rest_assignment: @ruby_rest_assignment ref, + unique int child: @ruby_underscore_lhs ref +); + +ruby_rest_assignment_def( + unique int id: @ruby_rest_assignment, + int loc: @location ref +); + +ruby_retry_child( + unique int ruby_retry: @ruby_retry ref, + unique int child: @ruby_argument_list ref +); + +ruby_retry_def( + unique int id: @ruby_retry, + int loc: @location ref +); + +ruby_return_child( + unique int ruby_return: @ruby_return ref, + unique int child: @ruby_argument_list ref +); + +ruby_return_def( + unique int id: @ruby_return, + int loc: @location ref +); + +@ruby_right_assignment_list_child_type = @ruby_splat_argument | @ruby_underscore_arg + +#keyset[ruby_right_assignment_list, index] +ruby_right_assignment_list_child( + int ruby_right_assignment_list: @ruby_right_assignment_list ref, + int index: int ref, + unique int child: @ruby_right_assignment_list_child_type ref +); + +ruby_right_assignment_list_def( + unique int id: @ruby_right_assignment_list, + int loc: @location ref +); + +@ruby_scope_resolution_name_type = @ruby_token_constant | @ruby_token_identifier + +ruby_scope_resolution_scope( + unique int ruby_scope_resolution: @ruby_scope_resolution ref, + unique int scope: @ruby_underscore_primary ref +); + +ruby_scope_resolution_def( + unique int id: @ruby_scope_resolution, + int name: @ruby_scope_resolution_name_type ref, + int loc: @location ref +); + +ruby_setter_def( + unique int id: @ruby_setter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_singleton_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_singleton_class, index] +ruby_singleton_class_child( + int ruby_singleton_class: @ruby_singleton_class ref, + int index: int ref, + unique int child: @ruby_singleton_class_child_type ref +); + +ruby_singleton_class_def( + unique int id: @ruby_singleton_class, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_singleton_method_object_type = @ruby_underscore_arg | @ruby_underscore_variable + +ruby_singleton_method_parameters( + unique int ruby_singleton_method: @ruby_singleton_method ref, + unique int parameters: @ruby_method_parameters ref +); + +@ruby_singleton_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_singleton_method, index] +ruby_singleton_method_child( + int ruby_singleton_method: @ruby_singleton_method ref, + int index: int ref, + unique int child: @ruby_singleton_method_child_type ref +); + +ruby_singleton_method_def( + unique int id: @ruby_singleton_method, + int name: @ruby_underscore_method_name ref, + int object: @ruby_singleton_method_object_type ref, + int loc: @location ref +); + +ruby_splat_argument_def( + unique int id: @ruby_splat_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_splat_parameter_name( + unique int ruby_splat_parameter: @ruby_splat_parameter ref, + unique int name: @ruby_token_identifier ref +); + +ruby_splat_parameter_def( + unique int id: @ruby_splat_parameter, + int loc: @location ref +); + +@ruby_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_string__, index] +ruby_string_child( + int ruby_string__: @ruby_string__ ref, + int index: int ref, + unique int child: @ruby_string_child_type ref +); + +ruby_string_def( + unique int id: @ruby_string__, + int loc: @location ref +); + +#keyset[ruby_string_array, index] +ruby_string_array_child( + int ruby_string_array: @ruby_string_array ref, + int index: int ref, + unique int child: @ruby_bare_string ref +); + +ruby_string_array_def( + unique int id: @ruby_string_array, + int loc: @location ref +); + +@ruby_subshell_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_subshell, index] +ruby_subshell_child( + int ruby_subshell: @ruby_subshell ref, + int index: int ref, + unique int child: @ruby_subshell_child_type ref +); + +ruby_subshell_def( + unique int id: @ruby_subshell, + int loc: @location ref +); + +@ruby_superclass_child_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_superclass_def( + unique int id: @ruby_superclass, + int child: @ruby_superclass_child_type ref, + int loc: @location ref +); + +#keyset[ruby_symbol_array, index] +ruby_symbol_array_child( + int ruby_symbol_array: @ruby_symbol_array ref, + int index: int ref, + unique int child: @ruby_bare_symbol ref +); + +ruby_symbol_array_def( + unique int id: @ruby_symbol_array, + int loc: @location ref +); + +@ruby_then_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_then, index] +ruby_then_child( + int ruby_then: @ruby_then ref, + int index: int ref, + unique int child: @ruby_then_child_type ref +); + +ruby_then_def( + unique int id: @ruby_then, + int loc: @location ref +); + +@ruby_unary_operand_type = @ruby_break | @ruby_call | @ruby_next | @ruby_parenthesized_statements | @ruby_return | @ruby_token_float | @ruby_token_integer | @ruby_underscore_arg | @ruby_yield + +case @ruby_unary.operator of + 0 = @ruby_unary_bang +| 1 = @ruby_unary_plus +| 2 = @ruby_unary_minus +| 3 = @ruby_unary_definedquestion +| 4 = @ruby_unary_not +| 5 = @ruby_unary_tilde +; + + +ruby_unary_def( + unique int id: @ruby_unary, + int operand: @ruby_unary_operand_type ref, + int operator: int ref, + int loc: @location ref +); + +#keyset[ruby_undef, index] +ruby_undef_child( + int ruby_undef: @ruby_undef ref, + int index: int ref, + unique int child: @ruby_underscore_method_name ref +); + +ruby_undef_def( + unique int id: @ruby_undef, + int loc: @location ref +); + +@ruby_unless_alternative_type = @ruby_else | @ruby_elsif + +ruby_unless_alternative( + unique int ruby_unless: @ruby_unless ref, + unique int alternative: @ruby_unless_alternative_type ref +); + +ruby_unless_consequence( + unique int ruby_unless: @ruby_unless ref, + unique int consequence: @ruby_then ref +); + +ruby_unless_def( + unique int id: @ruby_unless, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_unless_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_unless_modifier_def( + unique int id: @ruby_unless_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_unless_modifier_condition_type ref, + int loc: @location ref +); + +ruby_until_def( + unique int id: @ruby_until, + int body: @ruby_do ref, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_until_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_until_modifier_def( + unique int id: @ruby_until_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_until_modifier_condition_type ref, + int loc: @location ref +); + +ruby_when_body( + unique int ruby_when: @ruby_when ref, + unique int body: @ruby_then ref +); + +#keyset[ruby_when, index] +ruby_when_pattern( + int ruby_when: @ruby_when ref, + int index: int ref, + unique int pattern: @ruby_pattern ref +); + +ruby_when_def( + unique int id: @ruby_when, + int loc: @location ref +); + +ruby_while_def( + unique int id: @ruby_while, + int body: @ruby_do ref, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_while_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_while_modifier_def( + unique int id: @ruby_while_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_while_modifier_condition_type ref, + int loc: @location ref +); + +ruby_yield_child( + unique int ruby_yield: @ruby_yield ref, + unique int child: @ruby_argument_list ref +); + +ruby_yield_def( + unique int id: @ruby_yield, + int loc: @location ref +); + +ruby_tokeninfo( + unique int id: @ruby_token, + int kind: int ref, + string value: string ref, + int loc: @location ref +); + +case @ruby_token.kind of + 0 = @ruby_reserved_word +| 1 = @ruby_token_character +| 2 = @ruby_token_class_variable +| 3 = @ruby_token_comment +| 4 = @ruby_token_complex +| 5 = @ruby_token_constant +| 6 = @ruby_token_empty_statement +| 7 = @ruby_token_escape_sequence +| 8 = @ruby_token_false +| 9 = @ruby_token_float +| 10 = @ruby_token_global_variable +| 11 = @ruby_token_hash_key_symbol +| 12 = @ruby_token_heredoc_beginning +| 13 = @ruby_token_heredoc_content +| 14 = @ruby_token_heredoc_end +| 15 = @ruby_token_identifier +| 16 = @ruby_token_instance_variable +| 17 = @ruby_token_integer +| 18 = @ruby_token_nil +| 19 = @ruby_token_operator +| 20 = @ruby_token_self +| 21 = @ruby_token_simple_symbol +| 22 = @ruby_token_string_content +| 23 = @ruby_token_super +| 24 = @ruby_token_true +| 25 = @ruby_token_uninterpreted +; + + +@ruby_ast_node = @ruby_alias | @ruby_argument_list | @ruby_array | @ruby_assignment | @ruby_bare_string | @ruby_bare_symbol | @ruby_begin | @ruby_begin_block | @ruby_binary | @ruby_block | @ruby_block_argument | @ruby_block_parameter | @ruby_block_parameters | @ruby_break | @ruby_call | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_conditional | @ruby_delimited_symbol | @ruby_destructured_left_assignment | @ruby_destructured_parameter | @ruby_do | @ruby_do_block | @ruby_element_reference | @ruby_else | @ruby_elsif | @ruby_end_block | @ruby_ensure | @ruby_exception_variable | @ruby_exceptions | @ruby_for | @ruby_hash | @ruby_hash_splat_argument | @ruby_hash_splat_parameter | @ruby_heredoc_body | @ruby_if | @ruby_if_modifier | @ruby_in | @ruby_interpolation | @ruby_keyword_parameter | @ruby_lambda | @ruby_lambda_parameters | @ruby_left_assignment_list | @ruby_method | @ruby_method_parameters | @ruby_module | @ruby_next | @ruby_operator_assignment | @ruby_optional_parameter | @ruby_pair | @ruby_parenthesized_statements | @ruby_pattern | @ruby_program | @ruby_range | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_rescue | @ruby_rescue_modifier | @ruby_rest_assignment | @ruby_retry | @ruby_return | @ruby_right_assignment_list | @ruby_scope_resolution | @ruby_setter | @ruby_singleton_class | @ruby_singleton_method | @ruby_splat_argument | @ruby_splat_parameter | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_superclass | @ruby_symbol_array | @ruby_then | @ruby_token | @ruby_unary | @ruby_undef | @ruby_unless | @ruby_unless_modifier | @ruby_until | @ruby_until_modifier | @ruby_when | @ruby_while | @ruby_while_modifier | @ruby_yield + +@ruby_ast_node_parent = @file | @ruby_ast_node + +#keyset[parent, parent_index] +ruby_ast_node_parent( + int child: @ruby_ast_node ref, + int parent: @ruby_ast_node_parent ref, + int parent_index: int ref +); + +erb_comment_directive_def( + unique int id: @erb_comment_directive, + int child: @erb_token_comment ref, + int loc: @location ref +); + +erb_directive_def( + unique int id: @erb_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +erb_graphql_directive_def( + unique int id: @erb_graphql_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +erb_output_directive_def( + unique int id: @erb_output_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +@erb_template_child_type = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_token_content + +#keyset[erb_template, index] +erb_template_child( + int erb_template: @erb_template ref, + int index: int ref, + unique int child: @erb_template_child_type ref +); + +erb_template_def( + unique int id: @erb_template, + int loc: @location ref +); + +erb_tokeninfo( + unique int id: @erb_token, + int kind: int ref, + string value: string ref, + int loc: @location ref +); + +case @erb_token.kind of + 0 = @erb_reserved_word +| 1 = @erb_token_code +| 2 = @erb_token_comment +| 3 = @erb_token_content +; + + +@erb_ast_node = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_template | @erb_token + +@erb_ast_node_parent = @erb_ast_node | @file + +#keyset[parent, parent_index] +erb_ast_node_parent( + int child: @erb_ast_node ref, + int parent: @erb_ast_node_parent ref, + int parent_index: int ref +); + diff --git a/ruby/ql/lib/ruby.dbscheme.stats b/ruby/ql/lib/ruby.dbscheme.stats new file mode 100644 index 000000000000..ce3429f6f9d0 --- /dev/null +++ b/ruby/ql/lib/ruby.dbscheme.stats @@ -0,0 +1,25377 @@ + + + @diagnostic_debug + 0 + + + @diagnostic_error + 79 + + + @diagnostic_info + 0 + + + @diagnostic_warning + 0 + + + @erb_comment_directive + 190 + + + @erb_directive + 7623 + + + @erb_graphql_directive + 0 + + + @erb_output_directive + 13897 + + + @erb_reserved_word + 43424 + + + @erb_template + 1414 + + + @erb_token_code + 21521 + + + @erb_token_comment + 190 + + + @erb_token_content + 13381 + + + @file + 5508 + + + @folder + 1546 + + + @location_default + 2824141 + + + @ruby_alias + 408 + + + @ruby_argument_list + 224649 + + + @ruby_array + 10872 + + + @ruby_assignment + 41647 + + + @ruby_bare_string + 3117 + + + @ruby_bare_symbol + 689 + + + @ruby_begin + 641 + + + @ruby_begin_block + 0 + + + @ruby_binary_ampersand + 44 + + + @ruby_binary_ampersandampersand + 2861 + + + @ruby_binary_and + 70 + + + @ruby_binary_bangequal + 514 + + + @ruby_binary_bangtilde + 38 + + + @ruby_binary_caret + 29 + + + @ruby_binary_equalequal + 2574 + + + @ruby_binary_equalequalequal + 181 + + + @ruby_binary_equaltilde + 261 + + + @ruby_binary_langle + 437 + + + @ruby_binary_langleequal + 93 + + + @ruby_binary_langleequalrangle + 85 + + + @ruby_binary_langlelangle + 3389 + + + @ruby_binary_minus + 664 + + + @ruby_binary_or + 3 + + + @ruby_binary_percent + 141 + + + @ruby_binary_pipe + 43 + + + @ruby_binary_pipepipe + 2611 + + + @ruby_binary_plus + 1537 + + + @ruby_binary_rangle + 795 + + + @ruby_binary_rangleequal + 138 + + + @ruby_binary_ranglerangle + 6 + + + @ruby_binary_slash + 125 + + + @ruby_binary_star + 374 + + + @ruby_binary_starstar + 34 + + + @ruby_block + 24292 + + + @ruby_block_argument + 1944 + + + @ruby_block_parameter + 775 + + + @ruby_block_parameters + 7492 + + + @ruby_break + 220 + + + @ruby_call + 315429 + + + @ruby_case__ + 389 + + + @ruby_chained_string + 288 + + + @ruby_class + 5395 + + + @ruby_conditional + 1150 + + + @ruby_delimited_symbol + 394 + + + @ruby_destructured_left_assignment + 1 + + + @ruby_destructured_parameter + 64 + + + @ruby_do + 120 + + + @ruby_do_block + 42966 + + + @ruby_element_reference + 26560 + + + @ruby_else + 2225 + + + @ruby_elsif + 507 + + + @ruby_end_block + 0 + + + @ruby_ensure + 1209 + + + @ruby_exception_variable + 327 + + + @ruby_exceptions + 453 + + + @ruby_for + 1 + + + @ruby_hash + 8332 + + + @ruby_hash_splat_argument + 426 + + + @ruby_hash_splat_parameter + 432 + + + @ruby_heredoc_body + 1697 + + + @ruby_if + 5974 + + + @ruby_if_modifier + 4425 + + + @ruby_in + 1 + + + @ruby_interpolation + 12372 + + + @ruby_keyword_parameter + 1151 + + + @ruby_lambda + 711 + + + @ruby_lambda_parameters + 202 + + + @ruby_left_assignment_list + 816 + + + @ruby_method + 31633 + + + @ruby_method_parameters + 9291 + + + @ruby_module + 4650 + + + @ruby_next + 662 + + + @ruby_operator_assignment_ampersandampersandequal + 5 + + + @ruby_operator_assignment_ampersandequal + 5 + + + @ruby_operator_assignment_caretequal + 0 + + + @ruby_operator_assignment_langlelangleequal + 0 + + + @ruby_operator_assignment_minusequal + 64 + + + @ruby_operator_assignment_percentequal + 6 + + + @ruby_operator_assignment_pipeequal + 45 + + + @ruby_operator_assignment_pipepipeequal + 1502 + + + @ruby_operator_assignment_plusequal + 517 + + + @ruby_operator_assignment_ranglerangleequal + 0 + + + @ruby_operator_assignment_slashequal + 3 + + + @ruby_operator_assignment_starequal + 2 + + + @ruby_operator_assignment_starstarequal + 0 + + + @ruby_optional_parameter + 2105 + + + @ruby_pair + 69682 + + + @ruby_parenthesized_statements + 1758 + + + @ruby_pattern + 1227 + + + @ruby_program + 5504 + + + @ruby_range_dotdot + 449 + + + @ruby_range_dotdotdot + 128 + + + @ruby_rational + 4 + + + @ruby_redo + 0 + + + @ruby_regex + 4131 + + + @ruby_rescue + 668 + + + @ruby_rescue_modifier + 184 + + + @ruby_reserved_word + 1062916 + + + @ruby_rest_assignment + 18 + + + @ruby_retry + 11 + + + @ruby_return + 2742 + + + @ruby_right_assignment_list + 439 + + + @ruby_scope_resolution + 23999 + + + @ruby_setter + 194 + + + @ruby_singleton_class + 198 + + + @ruby_singleton_method + 2123 + + + @ruby_splat_argument + 694 + + + @ruby_splat_parameter + 943 + + + @ruby_string__ + 96217 + + + @ruby_string_array + 971 + + + @ruby_subshell + 134 + + + @ruby_superclass + 4270 + + + @ruby_symbol_array + 140 + + + @ruby_then + 7955 + + + @ruby_token_character + 10 + + + @ruby_token_class_variable + 236 + + + @ruby_token_comment + 57737 + + + @ruby_token_complex + 0 + + + @ruby_token_constant + 90663 + + + @ruby_token_empty_statement + 0 + + + @ruby_token_escape_sequence + 20870 + + + @ruby_token_false + 5412 + + + @ruby_token_float + 1823 + + + @ruby_token_global_variable + 749 + + + @ruby_token_hash_key_symbol + 68006 + + + @ruby_token_heredoc_beginning + 1697 + + + @ruby_token_heredoc_content + 3981 + + + @ruby_token_heredoc_end + 1697 + + + @ruby_token_identifier + 476086 + + + @ruby_token_instance_variable + 26195 + + + @ruby_token_integer + 34101 + + + @ruby_token_nil + 4221 + + + @ruby_token_operator + 198 + + + @ruby_token_self + 4212 + + + @ruby_token_simple_symbol + 79911 + + + @ruby_token_string_content + 120621 + + + @ruby_token_super + 1608 + + + @ruby_token_true + 7403 + + + @ruby_token_uninterpreted + 0 + + + @ruby_unary_bang + 1814 + + + @ruby_unary_definedquestion + 245 + + + @ruby_unary_minus + 673 + + + @ruby_unary_not + 10 + + + @ruby_unary_plus + 450 + + + @ruby_unary_tilde + 4 + + + @ruby_undef + 13 + + + @ruby_unless + 471 + + + @ruby_unless_modifier + 1440 + + + @ruby_until + 16 + + + @ruby_until_modifier + 12 + + + @ruby_when + 1021 + + + @ruby_while + 109 + + + @ruby_while_modifier + 9 + + + @ruby_yield + 772 + + + + containerparent + 7048 + + + parent + 1546 + + + child + 7048 + + + + + parent + child + + + 12 + + + 1 + 2 + 596 + + + 2 + 3 + 289 + + + 3 + 4 + 119 + + + 4 + 5 + 166 + + + 5 + 6 + 73 + + + 6 + 9 + 129 + + + 9 + 20 + 116 + + + 22 + 114 + 55 + + + + + + + child + parent + + + 12 + + + 1 + 2 + 7048 + + + + + + + + + diagnostics + 79 + + + id + 79 + + + severity + 3 + + + error_tag + 3 + + + error_message + 12 + + + full_error_message + 73 + + + location + 79 + + + + + id + severity + + + 12 + + + 1 + 2 + 79 + + + + + + + id + error_tag + + + 12 + + + 1 + 2 + 79 + + + + + + + id + error_message + + + 12 + + + 1 + 2 + 79 + + + + + + + id + full_error_message + + + 12 + + + 1 + 2 + 79 + + + + + + + id + location + + + 12 + + + 1 + 2 + 79 + + + + + + + severity + id + + + 12 + + + 26 + 27 + 3 + + + + + + + severity + error_tag + + + 12 + + + 1 + 2 + 3 + + + + + + + severity + error_message + + + 12 + + + 4 + 5 + 3 + + + + + + + severity + full_error_message + + + 12 + + + 24 + 25 + 3 + + + + + + + severity + location + + + 12 + + + 26 + 27 + 3 + + + + + + + error_tag + id + + + 12 + + + 26 + 27 + 3 + + + + + + + error_tag + severity + + + 12 + + + 1 + 2 + 3 + + + + + + + error_tag + error_message + + + 12 + + + 4 + 5 + 3 + + + + + + + error_tag + full_error_message + + + 12 + + + 24 + 25 + 3 + + + + + + + error_tag + location + + + 12 + + + 26 + 27 + 3 + + + + + + + error_message + id + + + 12 + + + 1 + 2 + 9 + + + 23 + 24 + 3 + + + + + + + error_message + severity + + + 12 + + + 1 + 2 + 12 + + + + + + + error_message + error_tag + + + 12 + + + 1 + 2 + 12 + + + + + + + error_message + full_error_message + + + 12 + + + 1 + 2 + 9 + + + 21 + 22 + 3 + + + + + + + error_message + location + + + 12 + + + 1 + 2 + 9 + + + 23 + 24 + 3 + + + + + + + full_error_message + id + + + 12 + + + 1 + 2 + 67 + + + 2 + 3 + 6 + + + + + + + full_error_message + severity + + + 12 + + + 1 + 2 + 73 + + + + + + + full_error_message + error_tag + + + 12 + + + 1 + 2 + 73 + + + + + + + full_error_message + error_message + + + 12 + + + 1 + 2 + 73 + + + + + + + full_error_message + location + + + 12 + + + 1 + 2 + 67 + + + 2 + 3 + 6 + + + + + + + location + id + + + 12 + + + 1 + 2 + 79 + + + + + + + location + severity + + + 12 + + + 1 + 2 + 79 + + + + + + + location + error_tag + + + 12 + + + 1 + 2 + 79 + + + + + + + location + error_message + + + 12 + + + 1 + 2 + 79 + + + + + + + location + full_error_message + + + 12 + + + 1 + 2 + 79 + + + + + + + + + erb_ast_node_parent + 101645 + + + child + 101645 + + + parent + 24514 + + + parent_index + 621 + + + + + child + parent + + + 12 + + + 1 + 2 + 101645 + + + + + + + child + parent_index + + + 12 + + + 1 + 2 + 101645 + + + + + + + parent + child + + + 12 + + + 1 + 3 + 1466 + + + 3 + 4 + 21755 + + + 4 + 203 + 1291 + + + + + + + parent + parent_index + + + 12 + + + 1 + 3 + 1466 + + + 3 + 4 + 21755 + + + 4 + 203 + 1291 + + + + + + + parent_index + child + + + 12 + + + 2 + 3 + 73 + + + 3 + 4 + 67 + + + 4 + 5 + 83 + + + 5 + 6 + 58 + + + 6 + 11 + 49 + + + 11 + 20 + 49 + + + 20 + 36 + 49 + + + 38 + 66 + 49 + + + 68 + 114 + 49 + + + 116 + 254 + 49 + + + 257 + 7972 + 43 + + + + + + + parent_index + parent + + + 12 + + + 2 + 3 + 73 + + + 3 + 4 + 67 + + + 4 + 5 + 83 + + + 5 + 6 + 58 + + + 6 + 11 + 49 + + + 11 + 20 + 49 + + + 20 + 36 + 49 + + + 38 + 66 + 49 + + + 68 + 114 + 49 + + + 116 + 254 + 49 + + + 257 + 7972 + 43 + + + + + + + + + erb_comment_directive_def + 190 + + + id + 190 + + + child + 190 + + + loc + 190 + + + + + id + child + + + 12 + + + 1 + 2 + 190 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 190 + + + + + + + child + id + + + 12 + + + 1 + 2 + 190 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 190 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 190 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 190 + + + + + + + + + erb_directive_def + 7623 + + + id + 7623 + + + child + 7623 + + + loc + 7623 + + + + + id + child + + + 12 + + + 1 + 2 + 7623 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 7623 + + + + + + + child + id + + + 12 + + + 1 + 2 + 7623 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 7623 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 7623 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 7623 + + + + + + + + + erb_graphql_directive_def + 0 + + + id + 0 + + + child + 0 + + + loc + 0 + + + + + id + child + + + 12 + + + 1 + 2 + 1 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 1 + + + + + + + child + id + + + 12 + + + + + + child + loc + + + 12 + + + + + + loc + id + + + 12 + + + + + + loc + child + + + 12 + + + + + + + + erb_output_directive_def + 13897 + + + id + 13897 + + + child + 13897 + + + loc + 13897 + + + + + id + child + + + 12 + + + 1 + 2 + 13897 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 13897 + + + + + + + child + id + + + 12 + + + 1 + 2 + 13897 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 13897 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 13897 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 13897 + + + + + + + + + erb_template_child + 35093 + + + erb_template + 1387 + + + index + 621 + + + child + 35093 + + + + + erb_template + index + + + 12 + + + 1 + 4 + 95 + + + 4 + 7 + 116 + + + 7 + 9 + 110 + + + 9 + 12 + 107 + + + 12 + 13 + 39 + + + 13 + 14 + 126 + + + 14 + 18 + 119 + + + 18 + 21 + 107 + + + 21 + 27 + 123 + + + 27 + 33 + 104 + + + 33 + 43 + 104 + + + 44 + 61 + 110 + + + 61 + 111 + 104 + + + 129 + 203 + 15 + + + + + + + erb_template + child + + + 12 + + + 1 + 4 + 95 + + + 4 + 7 + 116 + + + 7 + 9 + 110 + + + 9 + 12 + 107 + + + 12 + 13 + 39 + + + 13 + 14 + 126 + + + 14 + 18 + 119 + + + 18 + 21 + 107 + + + 21 + 27 + 123 + + + 27 + 33 + 104 + + + 33 + 43 + 104 + + + 44 + 61 + 110 + + + 61 + 111 + 104 + + + 129 + 203 + 15 + + + + + + + index + erb_template + + + 12 + + + 2 + 3 + 73 + + + 3 + 4 + 67 + + + 4 + 5 + 83 + + + 5 + 6 + 58 + + + 6 + 11 + 49 + + + 11 + 20 + 49 + + + 20 + 36 + 49 + + + 38 + 66 + 49 + + + 68 + 114 + 49 + + + 116 + 254 + 49 + + + 257 + 452 + 43 + + + + + + + index + child + + + 12 + + + 2 + 3 + 73 + + + 3 + 4 + 67 + + + 4 + 5 + 83 + + + 5 + 6 + 58 + + + 6 + 11 + 49 + + + 11 + 20 + 49 + + + 20 + 36 + 49 + + + 38 + 66 + 49 + + + 68 + 114 + 49 + + + 116 + 254 + 49 + + + 257 + 452 + 43 + + + + + + + child + erb_template + + + 12 + + + 1 + 2 + 35093 + + + + + + + child + index + + + 12 + + + 1 + 2 + 35093 + + + + + + + + + erb_template_def + 1414 + + + id + 1414 + + + loc + 1414 + + + + + id + loc + + + 12 + + + 1 + 2 + 1414 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1414 + + + + + + + + + erb_tokeninfo + 78518 + + + id + 78518 + + + kind + 12 + + + value + 20005 + + + loc + 78518 + + + + + id + kind + + + 12 + + + 1 + 2 + 78518 + + + + + + + id + value + + + 12 + + + 1 + 2 + 78518 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 78518 + + + + + + + kind + id + + + 12 + + + 62 + 63 + 3 + + + 4351 + 4352 + 3 + + + 6998 + 6999 + 3 + + + 14120 + 14121 + 3 + + + + + + + kind + value + + + 12 + + + 27 + 28 + 3 + + + 80 + 81 + 3 + + + 2394 + 2395 + 3 + + + 4004 + 4005 + 3 + + + + + + + kind + loc + + + 12 + + + 62 + 63 + 3 + + + 4351 + 4352 + 3 + + + 6998 + 6999 + 3 + + + 14120 + 14121 + 3 + + + + + + + value + id + + + 12 + + + 1 + 2 + 15936 + + + 2 + 3 + 2589 + + + 3 + 7054 + 1479 + + + + + + + value + kind + + + 12 + + + 1 + 2 + 20005 + + + + + + + value + loc + + + 12 + + + 1 + 2 + 15936 + + + 2 + 3 + 2589 + + + 3 + 7054 + 1479 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 78518 + + + + + + + loc + kind + + + 12 + + + 1 + 2 + 78518 + + + + + + + loc + value + + + 12 + + + 1 + 2 + 78518 + + + + + + + + + files + 5508 + + + id + 5508 + + + name + 5508 + + + + + id + name + + + 12 + + + 1 + 2 + 5508 + + + + + + + name + id + + + 12 + + + 1 + 2 + 5508 + + + + + + + + + folders + 1546 + + + id + 1546 + + + name + 1546 + + + + + id + name + + + 12 + + + 1 + 2 + 1546 + + + + + + + name + id + + + 12 + + + 1 + 2 + 1546 + + + + + + + + + locations_default + 2824141 + + + id + 2824141 + + + file + 5508 + + + start_line + 4554 + + + start_column + 1005 + + + end_line + 4554 + + + end_column + 1005 + + + + + id + file + + + 12 + + + 1 + 2 + 2824141 + + + + + + + id + start_line + + + 12 + + + 1 + 2 + 2824141 + + + + + + + id + start_column + + + 12 + + + 1 + 2 + 2824141 + + + + + + + id + end_line + + + 12 + + + 1 + 2 + 2824141 + + + + + + + id + end_column + + + 12 + + + 1 + 2 + 2824141 + + + + + + + file + id + + + 12 + + + 1 + 40 + 439 + + + 40 + 66 + 415 + + + 66 + 95 + 418 + + + 95 + 128 + 418 + + + 128 + 159 + 415 + + + 159 + 204 + 415 + + + 204 + 254 + 421 + + + 254 + 325 + 415 + + + 325 + 406 + 415 + + + 406 + 528 + 415 + + + 528 + 720 + 415 + + + 722 + 1225 + 415 + + + 1236 + 4316 + 415 + + + 4698 + 15687 + 73 + + + + + + + file + start_line + + + 12 + + + 1 + 6 + 436 + + + 6 + 9 + 402 + + + 9 + 11 + 384 + + + 11 + 15 + 507 + + + 15 + 19 + 439 + + + 19 + 24 + 467 + + + 24 + 30 + 418 + + + 30 + 38 + 476 + + + 38 + 48 + 442 + + + 48 + 64 + 418 + + + 64 + 95 + 415 + + + 95 + 184 + 418 + + + 184 + 1241 + 279 + + + + + + + file + start_column + + + 12 + + + 1 + 19 + 415 + + + 19 + 30 + 455 + + + 30 + 41 + 445 + + + 41 + 49 + 455 + + + 49 + 55 + 436 + + + 55 + 62 + 427 + + + 62 + 69 + 467 + + + 69 + 77 + 470 + + + 77 + 85 + 442 + + + 85 + 93 + 439 + + + 93 + 107 + 424 + + + 107 + 131 + 424 + + + 131 + 241 + 202 + + + + + + + file + end_line + + + 12 + + + 1 + 6 + 393 + + + 6 + 9 + 418 + + + 9 + 11 + 365 + + + 11 + 14 + 402 + + + 14 + 18 + 467 + + + 18 + 23 + 495 + + + 23 + 29 + 461 + + + 29 + 37 + 445 + + + 37 + 46 + 449 + + + 46 + 61 + 418 + + + 61 + 87 + 424 + + + 87 + 162 + 415 + + + 162 + 1241 + 350 + + + + + + + file + end_column + + + 12 + + + 1 + 23 + 433 + + + 23 + 34 + 433 + + + 34 + 45 + 449 + + + 45 + 53 + 464 + + + 53 + 59 + 427 + + + 59 + 67 + 473 + + + 67 + 74 + 449 + + + 74 + 81 + 442 + + + 81 + 89 + 467 + + + 89 + 98 + 445 + + + 98 + 112 + 436 + + + 112 + 138 + 427 + + + 138 + 246 + 156 + + + + + + + start_line + id + + + 12 + + + 1 + 8 + 329 + + + 8 + 11 + 344 + + + 11 + 21 + 421 + + + 21 + 34 + 350 + + + 34 + 66 + 350 + + + 66 + 104 + 353 + + + 105 + 148 + 350 + + + 148 + 200 + 344 + + + 200 + 258 + 347 + + + 258 + 384 + 347 + + + 385 + 803 + 344 + + + 812 + 2042 + 344 + + + 2046 + 14720 + 325 + + + + + + + start_line + file + + + 12 + + + 1 + 2 + 987 + + + 2 + 3 + 473 + + + 3 + 6 + 273 + + + 6 + 9 + 322 + + + 9 + 14 + 344 + + + 14 + 19 + 415 + + + 19 + 26 + 381 + + + 26 + 40 + 350 + + + 40 + 85 + 353 + + + 85 + 223 + 344 + + + 224 + 1791 + 307 + + + + + + + start_line + start_column + + + 12 + + + 1 + 5 + 319 + + + 5 + 8 + 369 + + + 8 + 14 + 335 + + + 14 + 20 + 375 + + + 20 + 32 + 347 + + + 32 + 44 + 344 + + + 44 + 55 + 344 + + + 55 + 64 + 350 + + + 64 + 74 + 381 + + + 74 + 85 + 350 + + + 85 + 102 + 344 + + + 102 + 122 + 347 + + + 122 + 229 + 344 + + + + + + + start_line + end_line + + + 12 + + + 1 + 2 + 1242 + + + 2 + 3 + 765 + + + 3 + 4 + 384 + + + 4 + 5 + 359 + + + 5 + 6 + 264 + + + 6 + 8 + 412 + + + 8 + 11 + 353 + + + 11 + 17 + 350 + + + 17 + 32 + 344 + + + 32 + 274 + 76 + + + + + + + start_line + end_column + + + 12 + + + 1 + 6 + 335 + + + 6 + 9 + 399 + + + 9 + 15 + 384 + + + 15 + 23 + 350 + + + 23 + 37 + 362 + + + 37 + 50 + 353 + + + 50 + 60 + 350 + + + 60 + 70 + 362 + + + 70 + 80 + 375 + + + 80 + 92 + 350 + + + 92 + 111 + 350 + + + 111 + 145 + 347 + + + 145 + 236 + 230 + + + + + + + start_column + id + + + 12 + + + 1 + 2 + 70 + + + 2 + 3 + 61 + + + 3 + 6 + 76 + + + 6 + 13 + 76 + + + 13 + 25 + 79 + + + 25 + 45 + 76 + + + 46 + 87 + 76 + + + 88 + 208 + 76 + + + 214 + 576 + 76 + + + 628 + 2031 + 76 + + + 2076 + 5049 + 76 + + + 5223 + 11579 + 76 + + + 11885 + 17346 + 76 + + + 17792 + 47966 + 24 + + + + + + + start_column + file + + + 12 + + + 1 + 2 + 98 + + + 2 + 3 + 61 + + + 3 + 6 + 79 + + + 6 + 11 + 79 + + + 11 + 20 + 79 + + + 21 + 35 + 76 + + + 36 + 76 + 76 + + + 76 + 183 + 76 + + + 184 + 397 + 76 + + + 412 + 777 + 76 + + + 783 + 1156 + 76 + + + 1188 + 1382 + 76 + + + 1383 + 1791 + 67 + + + + + + + start_column + start_line + + + 12 + + + 1 + 2 + 98 + + + 2 + 3 + 55 + + + 3 + 6 + 83 + + + 6 + 13 + 86 + + + 13 + 23 + 76 + + + 23 + 40 + 83 + + + 41 + 84 + 76 + + + 86 + 162 + 76 + + + 163 + 303 + 76 + + + 308 + 548 + 76 + + + 560 + 783 + 76 + + + 800 + 900 + 76 + + + 905 + 1265 + 61 + + + + + + + start_column + end_line + + + 12 + + + 1 + 2 + 95 + + + 2 + 3 + 55 + + + 3 + 6 + 83 + + + 6 + 13 + 92 + + + 13 + 23 + 79 + + + 23 + 41 + 76 + + + 41 + 86 + 79 + + + 87 + 164 + 76 + + + 166 + 309 + 76 + + + 309 + 566 + 76 + + + 567 + 811 + 76 + + + 833 + 920 + 76 + + + 920 + 1265 + 58 + + + + + + + start_column + end_column + + + 12 + + + 1 + 2 + 86 + + + 2 + 3 + 61 + + + 3 + 5 + 86 + + + 5 + 9 + 79 + + + 9 + 13 + 76 + + + 13 + 19 + 79 + + + 19 + 28 + 79 + + + 29 + 48 + 76 + + + 48 + 69 + 76 + + + 70 + 101 + 76 + + + 101 + 131 + 76 + + + 131 + 150 + 79 + + + 150 + 196 + 67 + + + + + + + end_line + id + + + 12 + + + 1 + 6 + 307 + + + 6 + 12 + 384 + + + 12 + 21 + 405 + + + 21 + 33 + 347 + + + 33 + 67 + 344 + + + 67 + 103 + 347 + + + 103 + 145 + 347 + + + 145 + 200 + 347 + + + 200 + 256 + 344 + + + 256 + 379 + 347 + + + 379 + 759 + 344 + + + 761 + 1942 + 344 + + + 2002 + 14495 + 341 + + + + + + + end_line + file + + + 12 + + + 1 + 2 + 987 + + + 2 + 3 + 473 + + + 3 + 6 + 273 + + + 6 + 9 + 322 + + + 9 + 14 + 344 + + + 14 + 19 + 415 + + + 19 + 26 + 378 + + + 26 + 40 + 353 + + + 40 + 85 + 353 + + + 85 + 225 + 344 + + + 225 + 1627 + 307 + + + + + + + end_line + start_line + + + 12 + + + 1 + 2 + 1245 + + + 2 + 3 + 688 + + + 3 + 4 + 369 + + + 4 + 5 + 335 + + + 5 + 6 + 267 + + + 6 + 7 + 249 + + + 7 + 9 + 347 + + + 9 + 14 + 369 + + + 14 + 23 + 365 + + + 23 + 40 + 316 + + + + + + + end_line + start_column + + + 12 + + + 1 + 5 + 316 + + + 5 + 9 + 405 + + + 9 + 15 + 390 + + + 15 + 22 + 344 + + + 22 + 36 + 356 + + + 36 + 47 + 344 + + + 47 + 58 + 372 + + + 58 + 67 + 356 + + + 67 + 77 + 362 + + + 77 + 90 + 356 + + + 90 + 108 + 362 + + + 108 + 139 + 347 + + + 139 + 225 + 236 + + + + + + + end_line + end_column + + + 12 + + + 1 + 5 + 319 + + + 5 + 8 + 362 + + + 8 + 14 + 329 + + + 14 + 20 + 384 + + + 20 + 33 + 359 + + + 33 + 47 + 347 + + + 47 + 58 + 369 + + + 58 + 68 + 369 + + + 68 + 77 + 350 + + + 77 + 89 + 359 + + + 89 + 108 + 356 + + + 108 + 131 + 344 + + + 131 + 236 + 301 + + + + + + + end_column + id + + + 12 + + + 1 + 2 + 39 + + + 2 + 4 + 83 + + + 4 + 10 + 86 + + + 10 + 20 + 76 + + + 20 + 31 + 76 + + + 32 + 65 + 76 + + + 65 + 141 + 76 + + + 147 + 343 + 79 + + + 351 + 1101 + 76 + + + 1142 + 3007 + 76 + + + 3214 + 7188 + 76 + + + 7472 + 12484 + 76 + + + 12543 + 14150 + 76 + + + 14175 + 23048 + 24 + + + + + + + end_column + file + + + 12 + + + 1 + 2 + 86 + + + 2 + 3 + 46 + + + 3 + 5 + 73 + + + 5 + 10 + 79 + + + 10 + 16 + 76 + + + 16 + 32 + 79 + + + 32 + 60 + 79 + + + 62 + 126 + 76 + + + 139 + 319 + 76 + + + 338 + 683 + 76 + + + 697 + 1105 + 76 + + + 1109 + 1381 + 76 + + + 1381 + 1480 + 76 + + + 1515 + 1654 + 21 + + + + + + + end_column + start_line + + + 12 + + + 1 + 2 + 86 + + + 2 + 4 + 79 + + + 4 + 8 + 76 + + + 8 + 15 + 83 + + + 15 + 24 + 79 + + + 24 + 45 + 76 + + + 46 + 93 + 76 + + + 93 + 171 + 79 + + + 181 + 338 + 76 + + + 338 + 587 + 76 + + + 596 + 823 + 76 + + + 838 + 933 + 76 + + + 934 + 1246 + 58 + + + + + + + end_column + start_column + + + 12 + + + 1 + 2 + 43 + + + 2 + 4 + 86 + + + 4 + 8 + 83 + + + 8 + 13 + 79 + + + 13 + 19 + 86 + + + 19 + 36 + 76 + + + 36 + 59 + 79 + + + 59 + 69 + 79 + + + 69 + 76 + 79 + + + 77 + 85 + 83 + + + 85 + 97 + 76 + + + 97 + 107 + 89 + + + 107 + 147 + 61 + + + + + + + end_column + end_line + + + 12 + + + 1 + 2 + 86 + + + 2 + 3 + 46 + + + 3 + 5 + 76 + + + 5 + 12 + 92 + + + 12 + 20 + 79 + + + 21 + 36 + 76 + + + 36 + 71 + 76 + + + 76 + 151 + 79 + + + 152 + 286 + 76 + + + 287 + 511 + 76 + + + 514 + 716 + 76 + + + 720 + 905 + 76 + + + 906 + 1150 + 76 + + + 1169 + 1204 + 6 + + + + + + + + + ruby_alias_def + 408 + + + id + 408 + + + alias + 408 + + + name + 408 + + + loc + 408 + + + + + id + alias + + + 12 + + + 1 + 2 + 408 + + + + + + + id + name + + + 12 + + + 1 + 2 + 408 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 408 + + + + + + + alias + id + + + 12 + + + 1 + 2 + 408 + + + + + + + alias + name + + + 12 + + + 1 + 2 + 408 + + + + + + + alias + loc + + + 12 + + + 1 + 2 + 408 + + + + + + + name + id + + + 12 + + + 1 + 2 + 408 + + + + + + + name + alias + + + 12 + + + 1 + 2 + 408 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 408 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 408 + + + + + + + loc + alias + + + 12 + + + 1 + 2 + 408 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 408 + + + + + + + + + ruby_argument_list_child + 287421 + + + ruby_argument_list + 224587 + + + index + 98 + + + child + 287421 + + + + + ruby_argument_list + index + + + 12 + + + 1 + 2 + 183694 + + + 2 + 3 + 27328 + + + 3 + 33 + 13565 + + + + + + + ruby_argument_list + child + + + 12 + + + 1 + 2 + 183694 + + + 2 + 3 + 27328 + + + 3 + 33 + 13565 + + + + + + + index + ruby_argument_list + + + 12 + + + 1 + 2 + 33 + + + 2 + 3 + 9 + + + 4 + 5 + 3 + + + 6 + 7 + 6 + + + 7 + 9 + 6 + + + 10 + 14 + 6 + + + 17 + 20 + 6 + + + 28 + 48 + 6 + + + 92 + 208 + 6 + + + 604 + 1639 + 6 + + + 4411 + 13298 + 6 + + + 73027 + 73028 + 3 + + + + + + + index + child + + + 12 + + + 1 + 2 + 33 + + + 2 + 3 + 9 + + + 4 + 5 + 3 + + + 6 + 7 + 6 + + + 7 + 9 + 6 + + + 10 + 14 + 6 + + + 17 + 20 + 6 + + + 28 + 48 + 6 + + + 92 + 208 + 6 + + + 604 + 1639 + 6 + + + 4411 + 13298 + 6 + + + 73027 + 73028 + 3 + + + + + + + child + ruby_argument_list + + + 12 + + + 1 + 2 + 287421 + + + + + + + child + index + + + 12 + + + 1 + 2 + 287421 + + + + + + + + + ruby_argument_list_def + 224649 + + + id + 224649 + + + loc + 224649 + + + + + id + loc + + + 12 + + + 1 + 2 + 224649 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 224649 + + + + + + + + + ruby_array_child + 20608 + + + ruby_array + 9202 + + + index + 96 + + + child + 20608 + + + + + ruby_array + index + + + 12 + + + 1 + 2 + 3031 + + + 2 + 3 + 4007 + + + 3 + 4 + 1333 + + + 4 + 10 + 713 + + + 10 + 95 + 116 + + + + + + + ruby_array + child + + + 12 + + + 1 + 2 + 3031 + + + 2 + 3 + 4007 + + + 3 + 4 + 1333 + + + 4 + 10 + 713 + + + 10 + 95 + 116 + + + + + + + index + ruby_array + + + 12 + + + 1 + 2 + 10 + + + 2 + 3 + 27 + + + 3 + 4 + 13 + + + 4 + 6 + 8 + + + 6 + 14 + 8 + + + 14 + 25 + 8 + + + 25 + 46 + 8 + + + 58 + 450 + 8 + + + 811 + 8987 + 4 + + + + + + + index + child + + + 12 + + + 1 + 2 + 10 + + + 2 + 3 + 27 + + + 3 + 4 + 13 + + + 4 + 6 + 8 + + + 6 + 14 + 8 + + + 14 + 25 + 8 + + + 25 + 46 + 8 + + + 58 + 450 + 8 + + + 811 + 8987 + 4 + + + + + + + child + ruby_array + + + 12 + + + 1 + 2 + 20608 + + + + + + + child + index + + + 12 + + + 1 + 2 + 20608 + + + + + + + + + ruby_array_def + 10872 + + + id + 10872 + + + loc + 10872 + + + + + id + loc + + + 12 + + + 1 + 2 + 10872 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 10872 + + + + + + + + + ruby_assignment_def + 41647 + + + id + 41647 + + + left + 41647 + + + right + 41647 + + + loc + 41647 + + + + + id + left + + + 12 + + + 1 + 2 + 41647 + + + + + + + id + right + + + 12 + + + 1 + 2 + 41647 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 41647 + + + + + + + left + id + + + 12 + + + 1 + 2 + 41647 + + + + + + + left + right + + + 12 + + + 1 + 2 + 41647 + + + + + + + left + loc + + + 12 + + + 1 + 2 + 41647 + + + + + + + right + id + + + 12 + + + 1 + 2 + 41647 + + + + + + + right + left + + + 12 + + + 1 + 2 + 41647 + + + + + + + right + loc + + + 12 + + + 1 + 2 + 41647 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 41647 + + + + + + + loc + left + + + 12 + + + 1 + 2 + 41647 + + + + + + + loc + right + + + 12 + + + 1 + 2 + 41647 + + + + + + + + + ruby_ast_node_parent + 2810806 + + + child + 2810806 + + + parent + 921530 + + + parent_index + 541 + + + + + child + parent + + + 12 + + + 1 + 2 + 2810806 + + + + + + + child + parent_index + + + 12 + + + 1 + 2 + 2810806 + + + + + + + parent + child + + + 12 + + + 1 + 2 + 98040 + + + 2 + 3 + 130895 + + + 3 + 4 + 508697 + + + 4 + 5 + 116185 + + + 5 + 177 + 67711 + + + + + + + parent + parent_index + + + 12 + + + 1 + 2 + 98040 + + + 2 + 3 + 130895 + + + 3 + 4 + 508697 + + + 4 + 5 + 116185 + + + 5 + 177 + 67711 + + + + + + + parent_index + child + + + 12 + + + 1 + 2 + 126 + + + 2 + 3 + 58 + + + 3 + 4 + 3 + + + 4 + 5 + 49 + + + 5 + 8 + 49 + + + 8 + 15 + 43 + + + 15 + 28 + 43 + + + 28 + 57 + 43 + + + 62 + 147 + 43 + + + 167 + 803 + 43 + + + 1055 + 299646 + 39 + + + + + + + parent_index + parent + + + 12 + + + 1 + 2 + 126 + + + 2 + 3 + 58 + + + 3 + 4 + 3 + + + 4 + 5 + 49 + + + 5 + 8 + 49 + + + 8 + 15 + 43 + + + 15 + 28 + 43 + + + 28 + 57 + 43 + + + 62 + 147 + 43 + + + 167 + 803 + 43 + + + 1055 + 299646 + 39 + + + + + + + + + ruby_bare_string_child + 3129 + + + ruby_bare_string + 3117 + + + index + 2 + + + child + 3129 + + + + + ruby_bare_string + index + + + 12 + + + 1 + 2 + 3105 + + + 2 + 3 + 12 + + + + + + + ruby_bare_string + child + + + 12 + + + 1 + 2 + 3105 + + + 2 + 3 + 12 + + + + + + + index + ruby_bare_string + + + 12 + + + 12 + 13 + 1 + + + 3044 + 3045 + 1 + + + + + + + index + child + + + 12 + + + 12 + 13 + 1 + + + 3044 + 3045 + 1 + + + + + + + child + ruby_bare_string + + + 12 + + + 1 + 2 + 3129 + + + + + + + child + index + + + 12 + + + 1 + 2 + 3129 + + + + + + + + + ruby_bare_string_def + 3117 + + + id + 3117 + + + loc + 3117 + + + + + id + loc + + + 12 + + + 1 + 2 + 3117 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 3117 + + + + + + + + + ruby_bare_symbol_child + 689 + + + ruby_bare_symbol + 689 + + + index + 1 + + + child + 689 + + + + + ruby_bare_symbol + index + + + 12 + + + 1 + 2 + 689 + + + + + + + ruby_bare_symbol + child + + + 12 + + + 1 + 2 + 689 + + + + + + + index + ruby_bare_symbol + + + 12 + + + 689 + 690 + 1 + + + + + + + index + child + + + 12 + + + 689 + 690 + 1 + + + + + + + child + ruby_bare_symbol + + + 12 + + + 1 + 2 + 689 + + + + + + + child + index + + + 12 + + + 1 + 2 + 689 + + + + + + + + + ruby_bare_symbol_def + 689 + + + id + 689 + + + loc + 689 + + + + + id + loc + + + 12 + + + 1 + 2 + 689 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 689 + + + + + + + + + ruby_begin_block_child + 0 + + + ruby_begin_block + 0 + + + index + 0 + + + child + 0 + + + + + ruby_begin_block + index + + + 12 + + + + + + ruby_begin_block + child + + + 12 + + + + + + index + ruby_begin_block + + + 12 + + + + + + index + child + + + 12 + + + + + + child + ruby_begin_block + + + 12 + + + 1 + 2 + 1 + + + + + + + child + index + + + 12 + + + 1 + 2 + 1 + + + + + + + + + ruby_begin_block_def + 0 + + + id + 0 + + + loc + 0 + + + + + id + loc + + + 12 + + + 1 + 2 + 1 + + + + + + + loc + id + + + 12 + + + + + + + + ruby_begin_child + 2205 + + + ruby_begin + 641 + + + index + 34 + + + child + 2205 + + + + + ruby_begin + index + + + 12 + + + 1 + 2 + 35 + + + 2 + 3 + 294 + + + 3 + 4 + 127 + + + 4 + 5 + 71 + + + 5 + 7 + 57 + + + 7 + 14 + 50 + + + 14 + 35 + 7 + + + + + + + ruby_begin + child + + + 12 + + + 1 + 2 + 35 + + + 2 + 3 + 294 + + + 3 + 4 + 127 + + + 4 + 5 + 71 + + + 5 + 7 + 57 + + + 7 + 14 + 50 + + + 14 + 35 + 7 + + + + + + + index + ruby_begin + + + 12 + + + 1 + 2 + 6 + + + 4 + 5 + 8 + + + 5 + 6 + 5 + + + 6 + 13 + 3 + + + 13 + 24 + 3 + + + 30 + 58 + 3 + + + 78 + 186 + 3 + + + 312 + 642 + 3 + + + + + + + index + child + + + 12 + + + 1 + 2 + 6 + + + 4 + 5 + 8 + + + 5 + 6 + 5 + + + 6 + 13 + 3 + + + 13 + 24 + 3 + + + 30 + 58 + 3 + + + 78 + 186 + 3 + + + 312 + 642 + 3 + + + + + + + child + ruby_begin + + + 12 + + + 1 + 2 + 2205 + + + + + + + child + index + + + 12 + + + 1 + 2 + 2205 + + + + + + + + + ruby_begin_def + 641 + + + id + 641 + + + loc + 641 + + + + + id + loc + + + 12 + + + 1 + 2 + 641 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 641 + + + + + + + + + ruby_binary_def + 14413 + + + id + 14413 + + + left + 14413 + + + operator + 23 + + + right + 14413 + + + loc + 14413 + + + + + id + left + + + 12 + + + 1 + 2 + 14413 + + + + + + + id + operator + + + 12 + + + 1 + 2 + 14413 + + + + + + + id + right + + + 12 + + + 1 + 2 + 14413 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 14413 + + + + + + + left + id + + + 12 + + + 1 + 2 + 14413 + + + + + + + left + operator + + + 12 + + + 1 + 2 + 14413 + + + + + + + left + right + + + 12 + + + 1 + 2 + 14413 + + + + + + + left + loc + + + 12 + + + 1 + 2 + 14413 + + + + + + + operator + id + + + 12 + + + 1 + 2 + 2 + + + 9 + 17 + 2 + + + 22 + 39 + 2 + + + 43 + 94 + 2 + + + 115 + 125 + 2 + + + 125 + 139 + 2 + + + 261 + 321 + 2 + + + 437 + 515 + 2 + + + 664 + 796 + 2 + + + 1238 + 1414 + 2 + + + 2574 + 2612 + 2 + + + 2861 + 2862 + 1 + + + + + + + operator + left + + + 12 + + + 1 + 2 + 2 + + + 9 + 17 + 2 + + + 22 + 39 + 2 + + + 43 + 94 + 2 + + + 115 + 125 + 2 + + + 125 + 139 + 2 + + + 261 + 321 + 2 + + + 437 + 515 + 2 + + + 664 + 796 + 2 + + + 1238 + 1414 + 2 + + + 2574 + 2612 + 2 + + + 2861 + 2862 + 1 + + + + + + + operator + right + + + 12 + + + 1 + 2 + 2 + + + 9 + 17 + 2 + + + 22 + 39 + 2 + + + 43 + 94 + 2 + + + 115 + 125 + 2 + + + 125 + 139 + 2 + + + 261 + 321 + 2 + + + 437 + 515 + 2 + + + 664 + 796 + 2 + + + 1238 + 1414 + 2 + + + 2574 + 2612 + 2 + + + 2861 + 2862 + 1 + + + + + + + operator + loc + + + 12 + + + 1 + 2 + 2 + + + 9 + 17 + 2 + + + 22 + 39 + 2 + + + 43 + 94 + 2 + + + 115 + 125 + 2 + + + 125 + 139 + 2 + + + 261 + 321 + 2 + + + 437 + 515 + 2 + + + 664 + 796 + 2 + + + 1238 + 1414 + 2 + + + 2574 + 2612 + 2 + + + 2861 + 2862 + 1 + + + + + + + right + id + + + 12 + + + 1 + 2 + 14413 + + + + + + + right + left + + + 12 + + + 1 + 2 + 14413 + + + + + + + right + operator + + + 12 + + + 1 + 2 + 14413 + + + + + + + right + loc + + + 12 + + + 1 + 2 + 14413 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 14413 + + + + + + + loc + left + + + 12 + + + 1 + 2 + 14413 + + + + + + + loc + operator + + + 12 + + + 1 + 2 + 14413 + + + + + + + loc + right + + + 12 + + + 1 + 2 + 14413 + + + + + + + + + ruby_block_argument_def + 1944 + + + id + 1944 + + + child + 1944 + + + loc + 1944 + + + + + id + child + + + 12 + + + 1 + 2 + 1944 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 1944 + + + + + + + child + id + + + 12 + + + 1 + 2 + 1944 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 1944 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1944 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 1944 + + + + + + + + + ruby_block_child + 24274 + + + ruby_block + 24249 + + + index + 12 + + + child + 24274 + + + + + ruby_block + index + + + 12 + + + 1 + 2 + 24231 + + + 2 + 5 + 18 + + + + + + + ruby_block + child + + + 12 + + + 1 + 2 + 24231 + + + 2 + 5 + 18 + + + + + + + index + ruby_block + + + 12 + + + 1 + 2 + 6 + + + 6 + 7 + 3 + + + 7885 + 7886 + 3 + + + + + + + index + child + + + 12 + + + 1 + 2 + 6 + + + 6 + 7 + 3 + + + 7885 + 7886 + 3 + + + + + + + child + ruby_block + + + 12 + + + 1 + 2 + 24274 + + + + + + + child + index + + + 12 + + + 1 + 2 + 24274 + + + + + + + + + ruby_block_def + 24292 + + + id + 24292 + + + loc + 24292 + + + + + id + loc + + + 12 + + + 1 + 2 + 24292 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 24292 + + + + + + + + + ruby_block_parameter_def + 775 + + + id + 775 + + + name + 775 + + + loc + 775 + + + + + id + name + + + 12 + + + 1 + 2 + 775 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 775 + + + + + + + name + id + + + 12 + + + 1 + 2 + 775 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 775 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 775 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 775 + + + + + + + + + ruby_block_parameters + 2711 + + + ruby_block + 2711 + + + parameters + 2711 + + + + + ruby_block + parameters + + + 12 + + + 1 + 2 + 2711 + + + + + + + parameters + ruby_block + + + 12 + + + 1 + 2 + 2711 + + + + + + + + + ruby_block_parameters_child + 8728 + + + ruby_block_parameters + 7492 + + + index + 5 + + + child + 8728 + + + + + ruby_block_parameters + index + + + 12 + + + 1 + 2 + 6401 + + + 2 + 3 + 988 + + + 3 + 6 + 102 + + + + + + + ruby_block_parameters + child + + + 12 + + + 1 + 2 + 6401 + + + 2 + 3 + 988 + + + 3 + 6 + 102 + + + + + + + index + ruby_block_parameters + + + 12 + + + 9 + 10 + 1 + + + 33 + 34 + 1 + + + 100 + 101 + 1 + + + 1065 + 1066 + 1 + + + 7316 + 7317 + 1 + + + + + + + index + child + + + 12 + + + 9 + 10 + 1 + + + 33 + 34 + 1 + + + 100 + 101 + 1 + + + 1065 + 1066 + 1 + + + 7316 + 7317 + 1 + + + + + + + child + ruby_block_parameters + + + 12 + + + 1 + 2 + 8728 + + + + + + + child + index + + + 12 + + + 1 + 2 + 8728 + + + + + + + + + ruby_block_parameters_def + 7492 + + + id + 7492 + + + loc + 7492 + + + + + id + loc + + + 12 + + + 1 + 2 + 7492 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 7492 + + + + + + + + + ruby_break_child + 9 + + + ruby_break + 9 + + + child + 9 + + + + + ruby_break + child + + + 12 + + + 1 + 2 + 9 + + + + + + + child + ruby_break + + + 12 + + + 1 + 2 + 9 + + + + + + + + + ruby_break_def + 220 + + + id + 220 + + + loc + 220 + + + + + id + loc + + + 12 + + + 1 + 2 + 220 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 220 + + + + + + + + + ruby_call_arguments + 223852 + + + ruby_call + 223852 + + + arguments + 223852 + + + + + ruby_call + arguments + + + 12 + + + 1 + 2 + 223852 + + + + + + + arguments + ruby_call + + + 12 + + + 1 + 2 + 223852 + + + + + + + + + ruby_call_block + 66671 + + + ruby_call + 66671 + + + block + 66671 + + + + + ruby_call + block + + + 12 + + + 1 + 2 + 66671 + + + + + + + block + ruby_call + + + 12 + + + 1 + 2 + 66671 + + + + + + + + + ruby_call_def + 315429 + + + id + 315429 + + + method + 315429 + + + loc + 315429 + + + + + id + method + + + 12 + + + 1 + 2 + 315429 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 315429 + + + + + + + method + id + + + 12 + + + 1 + 2 + 315429 + + + + + + + method + loc + + + 12 + + + 1 + 2 + 315429 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 315429 + + + + + + + loc + method + + + 12 + + + 1 + 2 + 315429 + + + + + + + + + ruby_call_receiver + 173313 + + + ruby_call + 173313 + + + receiver + 173313 + + + + + ruby_call + receiver + + + 12 + + + 1 + 2 + 173313 + + + + + + + receiver + ruby_call + + + 12 + + + 1 + 2 + 173313 + + + + + + + + + ruby_case_child + 1307 + + + ruby_case__ + 389 + + + index + 22 + + + child + 1307 + + + + + ruby_case__ + index + + + 12 + + + 1 + 2 + 12 + + + 2 + 3 + 103 + + + 3 + 4 + 159 + + + 4 + 5 + 61 + + + 5 + 7 + 31 + + + 7 + 23 + 20 + + + + + + + ruby_case__ + child + + + 12 + + + 1 + 2 + 12 + + + 2 + 3 + 103 + + + 3 + 4 + 159 + + + 4 + 5 + 61 + + + 5 + 7 + 31 + + + 7 + 23 + 20 + + + + + + + index + ruby_case__ + + + 12 + + + 1 + 2 + 8 + + + 2 + 3 + 2 + + + 3 + 5 + 2 + + + 8 + 11 + 2 + + + 13 + 21 + 2 + + + 30 + 52 + 2 + + + 111 + 268 + 2 + + + 368 + 381 + 2 + + + + + + + index + child + + + 12 + + + 1 + 2 + 8 + + + 2 + 3 + 2 + + + 3 + 5 + 2 + + + 8 + 11 + 2 + + + 13 + 21 + 2 + + + 30 + 52 + 2 + + + 111 + 268 + 2 + + + 368 + 381 + 2 + + + + + + + child + ruby_case__ + + + 12 + + + 1 + 2 + 1307 + + + + + + + child + index + + + 12 + + + 1 + 2 + 1307 + + + + + + + + + ruby_case_def + 389 + + + id + 389 + + + loc + 389 + + + + + id + loc + + + 12 + + + 1 + 2 + 389 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 389 + + + + + + + + + ruby_case_value + 376 + + + ruby_case__ + 376 + + + value + 376 + + + + + ruby_case__ + value + + + 12 + + + 1 + 2 + 376 + + + + + + + value + ruby_case__ + + + 12 + + + 1 + 2 + 376 + + + + + + + + + ruby_chained_string_child + 1094 + + + ruby_chained_string + 288 + + + index + 12 + + + child + 1094 + + + + + ruby_chained_string + index + + + 12 + + + 2 + 3 + 98 + + + 3 + 4 + 63 + + + 4 + 5 + 44 + + + 5 + 6 + 40 + + + 6 + 8 + 21 + + + 8 + 13 + 20 + + + + + + + ruby_chained_string + child + + + 12 + + + 2 + 3 + 98 + + + 3 + 4 + 63 + + + 4 + 5 + 44 + + + 5 + 6 + 40 + + + 6 + 8 + 21 + + + 8 + 13 + 20 + + + + + + + index + ruby_chained_string + + + 12 + + + 2 + 3 + 1 + + + 4 + 5 + 1 + + + 7 + 8 + 1 + + + 8 + 9 + 1 + + + 20 + 21 + 1 + + + 32 + 33 + 1 + + + 41 + 42 + 1 + + + 81 + 82 + 1 + + + 124 + 125 + 1 + + + 186 + 187 + 1 + + + 282 + 283 + 2 + + + + + + + index + child + + + 12 + + + 2 + 3 + 1 + + + 4 + 5 + 1 + + + 7 + 8 + 1 + + + 8 + 9 + 1 + + + 20 + 21 + 1 + + + 32 + 33 + 1 + + + 41 + 42 + 1 + + + 81 + 82 + 1 + + + 124 + 125 + 1 + + + 186 + 187 + 1 + + + 282 + 283 + 2 + + + + + + + child + ruby_chained_string + + + 12 + + + 1 + 2 + 1094 + + + + + + + child + index + + + 12 + + + 1 + 2 + 1094 + + + + + + + + + ruby_chained_string_def + 288 + + + id + 288 + + + loc + 288 + + + + + id + loc + + + 12 + + + 1 + 2 + 288 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 288 + + + + + + + + + ruby_class_child + 42235 + + + ruby_class + 4859 + + + index + 318 + + + child + 42235 + + + + + ruby_class + index + + + 12 + + + 1 + 2 + 1052 + + + 2 + 3 + 746 + + + 3 + 4 + 500 + + + 4 + 5 + 402 + + + 5 + 6 + 309 + + + 6 + 7 + 263 + + + 7 + 9 + 363 + + + 9 + 13 + 417 + + + 13 + 21 + 378 + + + 21 + 78 + 366 + + + 80 + 312 + 57 + + + + + + + ruby_class + child + + + 12 + + + 1 + 2 + 1052 + + + 2 + 3 + 746 + + + 3 + 4 + 500 + + + 4 + 5 + 402 + + + 5 + 6 + 309 + + + 6 + 7 + 263 + + + 7 + 9 + 363 + + + 9 + 13 + 417 + + + 13 + 21 + 378 + + + 21 + 78 + 366 + + + 80 + 312 + 57 + + + + + + + index + ruby_class + + + 12 + + + 1 + 3 + 13 + + + 3 + 4 + 30 + + + 4 + 5 + 28 + + + 5 + 7 + 24 + + + 7 + 8 + 24 + + + 8 + 11 + 25 + + + 11 + 15 + 25 + + + 16 + 27 + 27 + + + 27 + 42 + 24 + + + 44 + 71 + 24 + + + 71 + 144 + 24 + + + 150 + 444 + 24 + + + 466 + 4746 + 19 + + + + + + + index + child + + + 12 + + + 1 + 3 + 13 + + + 3 + 4 + 30 + + + 4 + 5 + 28 + + + 5 + 7 + 24 + + + 7 + 8 + 24 + + + 8 + 11 + 25 + + + 11 + 15 + 25 + + + 16 + 27 + 27 + + + 27 + 42 + 24 + + + 44 + 71 + 24 + + + 71 + 144 + 24 + + + 150 + 444 + 24 + + + 466 + 4746 + 19 + + + + + + + child + ruby_class + + + 12 + + + 1 + 2 + 42235 + + + + + + + child + index + + + 12 + + + 1 + 2 + 42235 + + + + + + + + + ruby_class_def + 5395 + + + id + 5395 + + + name + 5395 + + + loc + 5395 + + + + + id + name + + + 12 + + + 1 + 2 + 5395 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 5395 + + + + + + + name + id + + + 12 + + + 1 + 2 + 5395 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 5395 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 5395 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 5395 + + + + + + + + + ruby_class_superclass + 4270 + + + ruby_class + 4270 + + + superclass + 4270 + + + + + ruby_class + superclass + + + 12 + + + 1 + 2 + 4270 + + + + + + + superclass + ruby_class + + + 12 + + + 1 + 2 + 4270 + + + + + + + + + ruby_conditional_def + 1150 + + + id + 1150 + + + alternative + 1150 + + + condition + 1150 + + + consequence + 1150 + + + loc + 1150 + + + + + id + alternative + + + 12 + + + 1 + 2 + 1150 + + + + + + + id + condition + + + 12 + + + 1 + 2 + 1150 + + + + + + + id + consequence + + + 12 + + + 1 + 2 + 1150 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 1150 + + + + + + + alternative + id + + + 12 + + + 1 + 2 + 1150 + + + + + + + alternative + condition + + + 12 + + + 1 + 2 + 1150 + + + + + + + alternative + consequence + + + 12 + + + 1 + 2 + 1150 + + + + + + + alternative + loc + + + 12 + + + 1 + 2 + 1150 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 1150 + + + + + + + condition + alternative + + + 12 + + + 1 + 2 + 1150 + + + + + + + condition + consequence + + + 12 + + + 1 + 2 + 1150 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 1150 + + + + + + + consequence + id + + + 12 + + + 1 + 2 + 1150 + + + + + + + consequence + alternative + + + 12 + + + 1 + 2 + 1150 + + + + + + + consequence + condition + + + 12 + + + 1 + 2 + 1150 + + + + + + + consequence + loc + + + 12 + + + 1 + 2 + 1150 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1150 + + + + + + + loc + alternative + + + 12 + + + 1 + 2 + 1150 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 1150 + + + + + + + loc + consequence + + + 12 + + + 1 + 2 + 1150 + + + + + + + + + ruby_delimited_symbol_child + 542 + + + ruby_delimited_symbol + 394 + + + index + 8 + + + child + 542 + + + + + ruby_delimited_symbol + index + + + 12 + + + 1 + 2 + 298 + + + 2 + 3 + 73 + + + 3 + 9 + 22 + + + + + + + ruby_delimited_symbol + child + + + 12 + + + 1 + 2 + 298 + + + 2 + 3 + 73 + + + 3 + 9 + 22 + + + + + + + index + ruby_delimited_symbol + + + 12 + + + 1 + 2 + 1 + + + 3 + 4 + 1 + + + 5 + 6 + 1 + + + 8 + 9 + 1 + + + 12 + 13 + 1 + + + 22 + 23 + 1 + + + 94 + 95 + 1 + + + 385 + 386 + 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 + + + 385 + 386 + 1 + + + + + + + child + ruby_delimited_symbol + + + 12 + + + 1 + 2 + 542 + + + + + + + child + index + + + 12 + + + 1 + 2 + 542 + + + + + + + + + ruby_delimited_symbol_def + 394 + + + id + 394 + + + loc + 394 + + + + + id + loc + + + 12 + + + 1 + 2 + 394 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 394 + + + + + + + + + ruby_destructured_left_assignment_child + 2 + + + ruby_destructured_left_assignment + 1 + + + index + 2 + + + child + 2 + + + + + ruby_destructured_left_assignment + index + + + 12 + + + 2 + 3 + 1 + + + + + + + ruby_destructured_left_assignment + child + + + 12 + + + 2 + 3 + 1 + + + + + + + index + ruby_destructured_left_assignment + + + 12 + + + 1 + 2 + 2 + + + + + + + index + child + + + 12 + + + 1 + 2 + 2 + + + + + + + child + ruby_destructured_left_assignment + + + 12 + + + 1 + 2 + 2 + + + + + + + child + index + + + 12 + + + 1 + 2 + 2 + + + + + + + + + ruby_destructured_left_assignment_def + 1 + + + id + 1 + + + loc + 1 + + + + + id + loc + + + 12 + + + 1 + 2 + 1 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1 + + + + + + + + + ruby_destructured_parameter_child + 132 + + + ruby_destructured_parameter + 64 + + + index + 4 + + + child + 132 + + + + + ruby_destructured_parameter + index + + + 12 + + + 1 + 2 + 3 + + + 2 + 3 + 56 + + + 3 + 5 + 5 + + + + + + + ruby_destructured_parameter + child + + + 12 + + + 1 + 2 + 3 + + + 2 + 3 + 56 + + + 3 + 5 + 5 + + + + + + + index + ruby_destructured_parameter + + + 12 + + + 1 + 2 + 1 + + + 5 + 6 + 1 + + + 60 + 61 + 1 + + + 63 + 64 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 1 + + + 5 + 6 + 1 + + + 60 + 61 + 1 + + + 63 + 64 + 1 + + + + + + + child + ruby_destructured_parameter + + + 12 + + + 1 + 2 + 132 + + + + + + + child + index + + + 12 + + + 1 + 2 + 132 + + + + + + + + + ruby_destructured_parameter_def + 64 + + + id + 64 + + + loc + 64 + + + + + id + loc + + + 12 + + + 1 + 2 + 64 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 64 + + + + + + + + + ruby_do_block_child + 128622 + + + ruby_do_block + 42951 + + + index + 221 + + + child + 128622 + + + + + ruby_do_block + index + + + 12 + + + 1 + 2 + 13848 + + + 2 + 3 + 11545 + + + 3 + 4 + 6873 + + + 4 + 5 + 3795 + + + 5 + 7 + 3509 + + + 7 + 24 + 3235 + + + 24 + 73 + 144 + + + + + + + ruby_do_block + child + + + 12 + + + 1 + 2 + 13848 + + + 2 + 3 + 11545 + + + 3 + 4 + 6873 + + + 4 + 5 + 3795 + + + 5 + 7 + 3509 + + + 7 + 24 + 3235 + + + 24 + 73 + 144 + + + + + + + index + ruby_do_block + + + 12 + + + 1 + 2 + 21 + + + 2 + 3 + 49 + + + 4 + 5 + 3 + + + 5 + 6 + 18 + + + 6 + 10 + 18 + + + 11 + 18 + 18 + + + 19 + 38 + 18 + + + 47 + 91 + 18 + + + 102 + 239 + 18 + + + 307 + 1100 + 18 + + + 1535 + 13967 + 18 + + + + + + + index + child + + + 12 + + + 1 + 2 + 21 + + + 2 + 3 + 49 + + + 4 + 5 + 3 + + + 5 + 6 + 18 + + + 6 + 10 + 18 + + + 11 + 18 + 18 + + + 19 + 38 + 18 + + + 47 + 91 + 18 + + + 102 + 239 + 18 + + + 307 + 1100 + 18 + + + 1535 + 13967 + 18 + + + + + + + child + ruby_do_block + + + 12 + + + 1 + 2 + 128622 + + + + + + + child + index + + + 12 + + + 1 + 2 + 128622 + + + + + + + + + ruby_do_block_def + 42966 + + + id + 42966 + + + loc + 42966 + + + + + id + loc + + + 12 + + + 1 + 2 + 42966 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 42966 + + + + + + + + + ruby_do_block_parameters + 4780 + + + ruby_do_block + 4780 + + + parameters + 4780 + + + + + ruby_do_block + parameters + + + 12 + + + 1 + 2 + 4780 + + + + + + + parameters + ruby_do_block + + + 12 + + + 1 + 2 + 4780 + + + + + + + + + ruby_do_child + 276 + + + ruby_do + 117 + + + index + 18 + + + child + 276 + + + + + ruby_do + index + + + 12 + + + 1 + 2 + 36 + + + 2 + 3 + 48 + + + 3 + 4 + 17 + + + 4 + 6 + 10 + + + 6 + 19 + 6 + + + + + + + ruby_do + child + + + 12 + + + 1 + 2 + 36 + + + 2 + 3 + 48 + + + 3 + 4 + 17 + + + 4 + 6 + 10 + + + 6 + 19 + 6 + + + + + + + index + ruby_do + + + 12 + + + 1 + 2 + 9 + + + 2 + 3 + 3 + + + 6 + 7 + 1 + + + 8 + 9 + 1 + + + 16 + 17 + 1 + + + 33 + 34 + 1 + + + 81 + 82 + 1 + + + 117 + 118 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 9 + + + 2 + 3 + 3 + + + 6 + 7 + 1 + + + 8 + 9 + 1 + + + 16 + 17 + 1 + + + 33 + 34 + 1 + + + 81 + 82 + 1 + + + 117 + 118 + 1 + + + + + + + child + ruby_do + + + 12 + + + 1 + 2 + 276 + + + + + + + child + index + + + 12 + + + 1 + 2 + 276 + + + + + + + + + ruby_do_def + 120 + + + id + 120 + + + loc + 120 + + + + + id + loc + + + 12 + + + 1 + 2 + 120 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 120 + + + + + + + + + ruby_element_reference_child + 26610 + + + ruby_element_reference + 26558 + + + index + 2 + + + child + 26610 + + + + + ruby_element_reference + index + + + 12 + + + 1 + 2 + 26506 + + + 2 + 3 + 52 + + + + + + + ruby_element_reference + child + + + 12 + + + 1 + 2 + 26506 + + + 2 + 3 + 52 + + + + + + + index + ruby_element_reference + + + 12 + + + 52 + 53 + 1 + + + 26558 + 26559 + 1 + + + + + + + index + child + + + 12 + + + 52 + 53 + 1 + + + 26558 + 26559 + 1 + + + + + + + child + ruby_element_reference + + + 12 + + + 1 + 2 + 26610 + + + + + + + child + index + + + 12 + + + 1 + 2 + 26610 + + + + + + + + + ruby_element_reference_def + 26560 + + + id + 26560 + + + object + 26560 + + + loc + 26560 + + + + + id + object + + + 12 + + + 1 + 2 + 26560 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 26560 + + + + + + + object + id + + + 12 + + + 1 + 2 + 26560 + + + + + + + object + loc + + + 12 + + + 1 + 2 + 26560 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 26560 + + + + + + + loc + object + + + 12 + + + 1 + 2 + 26560 + + + + + + + + + ruby_else_child + 2830 + + + ruby_else + 2222 + + + index + 11 + + + child + 2830 + + + + + ruby_else + index + + + 12 + + + 1 + 2 + 1868 + + + 2 + 3 + 222 + + + 3 + 12 + 131 + + + + + + + ruby_else + child + + + 12 + + + 1 + 2 + 1868 + + + 2 + 3 + 222 + + + 3 + 12 + 131 + + + + + + + index + ruby_else + + + 12 + + + 1 + 2 + 1 + + + 3 + 4 + 1 + + + 4 + 5 + 1 + + + 6 + 7 + 1 + + + 9 + 10 + 1 + + + 16 + 17 + 1 + + + 28 + 29 + 1 + + + 54 + 55 + 1 + + + 128 + 129 + 1 + + + 345 + 346 + 1 + + + 2170 + 2171 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 1 + + + 3 + 4 + 1 + + + 4 + 5 + 1 + + + 6 + 7 + 1 + + + 9 + 10 + 1 + + + 16 + 17 + 1 + + + 28 + 29 + 1 + + + 54 + 55 + 1 + + + 128 + 129 + 1 + + + 345 + 346 + 1 + + + 2170 + 2171 + 1 + + + + + + + child + ruby_else + + + 12 + + + 1 + 2 + 2830 + + + + + + + child + index + + + 12 + + + 1 + 2 + 2830 + + + + + + + + + ruby_else_def + 2225 + + + id + 2225 + + + loc + 2225 + + + + + id + loc + + + 12 + + + 1 + 2 + 2225 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 2225 + + + + + + + + + ruby_elsif_alternative + 280 + + + ruby_elsif + 280 + + + alternative + 280 + + + + + ruby_elsif + alternative + + + 12 + + + 1 + 2 + 280 + + + + + + + alternative + ruby_elsif + + + 12 + + + 1 + 2 + 280 + + + + + + + + + ruby_elsif_consequence + 506 + + + ruby_elsif + 506 + + + consequence + 506 + + + + + ruby_elsif + consequence + + + 12 + + + 1 + 2 + 506 + + + + + + + consequence + ruby_elsif + + + 12 + + + 1 + 2 + 506 + + + + + + + + + ruby_elsif_def + 507 + + + id + 507 + + + condition + 507 + + + loc + 507 + + + + + id + condition + + + 12 + + + 1 + 2 + 507 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 507 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 507 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 507 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 507 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 507 + + + + + + + + + ruby_end_block_child + 0 + + + ruby_end_block + 0 + + + index + 0 + + + child + 0 + + + + + ruby_end_block + index + + + 12 + + + + + + ruby_end_block + child + + + 12 + + + + + + index + ruby_end_block + + + 12 + + + + + + index + child + + + 12 + + + + + + child + ruby_end_block + + + 12 + + + 1 + 2 + 1 + + + + + + + child + index + + + 12 + + + 1 + 2 + 1 + + + + + + + + + ruby_end_block_def + 0 + + + id + 0 + + + loc + 0 + + + + + id + loc + + + 12 + + + 1 + 2 + 1 + + + + + + + loc + id + + + 12 + + + + + + + + ruby_ensure_child + 1628 + + + ruby_ensure + 1209 + + + index + 16 + + + child + 1628 + + + + + ruby_ensure + index + + + 12 + + + 1 + 2 + 940 + + + 2 + 3 + 175 + + + 3 + 9 + 92 + + + 16 + 17 + 2 + + + + + + + ruby_ensure + child + + + 12 + + + 1 + 2 + 940 + + + 2 + 3 + 175 + + + 3 + 9 + 92 + + + 16 + 17 + 2 + + + + + + + index + ruby_ensure + + + 12 + + + 2 + 3 + 8 + + + 5 + 6 + 2 + + + 6 + 7 + 2 + + + 16 + 17 + 1 + + + 92 + 93 + 1 + + + 263 + 264 + 1 + + + 1181 + 1182 + 1 + + + + + + + index + child + + + 12 + + + 2 + 3 + 8 + + + 5 + 6 + 2 + + + 6 + 7 + 2 + + + 16 + 17 + 1 + + + 92 + 93 + 1 + + + 263 + 264 + 1 + + + 1181 + 1182 + 1 + + + + + + + child + ruby_ensure + + + 12 + + + 1 + 2 + 1628 + + + + + + + child + index + + + 12 + + + 1 + 2 + 1628 + + + + + + + + + ruby_ensure_def + 1209 + + + id + 1209 + + + loc + 1209 + + + + + id + loc + + + 12 + + + 1 + 2 + 1209 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1209 + + + + + + + + + ruby_exception_variable_def + 327 + + + id + 327 + + + child + 327 + + + loc + 327 + + + + + id + child + + + 12 + + + 1 + 2 + 327 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 327 + + + + + + + child + id + + + 12 + + + 1 + 2 + 327 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 327 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 327 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 327 + + + + + + + + + ruby_exceptions_child + 530 + + + ruby_exceptions + 453 + + + index + 8 + + + child + 530 + + + + + ruby_exceptions + index + + + 12 + + + 1 + 2 + 405 + + + 2 + 3 + 33 + + + 3 + 9 + 15 + + + + + + + ruby_exceptions + child + + + 12 + + + 1 + 2 + 405 + + + 2 + 3 + 33 + + + 3 + 9 + 15 + + + + + + + index + ruby_exceptions + + + 12 + + + 2 + 3 + 3 + + + 3 + 4 + 1 + + + 5 + 6 + 1 + + + 15 + 16 + 1 + + + 48 + 49 + 1 + + + 453 + 454 + 1 + + + + + + + index + child + + + 12 + + + 2 + 3 + 3 + + + 3 + 4 + 1 + + + 5 + 6 + 1 + + + 15 + 16 + 1 + + + 48 + 49 + 1 + + + 453 + 454 + 1 + + + + + + + child + ruby_exceptions + + + 12 + + + 1 + 2 + 530 + + + + + + + child + index + + + 12 + + + 1 + 2 + 530 + + + + + + + + + ruby_exceptions_def + 453 + + + id + 453 + + + loc + 453 + + + + + id + loc + + + 12 + + + 1 + 2 + 453 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 453 + + + + + + + + + ruby_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 + + + + + + + + + ruby_hash_child + 15172 + + + ruby_hash + 6815 + + + index + 111 + + + child + 15172 + + + + + ruby_hash + index + + + 12 + + + 1 + 2 + 3526 + + + 2 + 3 + 1654 + + + 3 + 4 + 733 + + + 4 + 6 + 554 + + + 6 + 112 + 348 + + + + + + + ruby_hash + child + + + 12 + + + 1 + 2 + 3526 + + + 2 + 3 + 1654 + + + 3 + 4 + 733 + + + 4 + 6 + 554 + + + 6 + 112 + 348 + + + + + + + index + ruby_hash + + + 12 + + + 1 + 2 + 61 + + + 2 + 12 + 9 + + + 12 + 16 + 8 + + + 18 + 26 + 10 + + + 26 + 46 + 9 + + + 49 + 349 + 9 + + + 541 + 6816 + 5 + + + + + + + index + child + + + 12 + + + 1 + 2 + 61 + + + 2 + 12 + 9 + + + 12 + 16 + 8 + + + 18 + 26 + 10 + + + 26 + 46 + 9 + + + 49 + 349 + 9 + + + 541 + 6816 + 5 + + + + + + + child + ruby_hash + + + 12 + + + 1 + 2 + 15172 + + + + + + + child + index + + + 12 + + + 1 + 2 + 15172 + + + + + + + + + ruby_hash_def + 8332 + + + id + 8332 + + + loc + 8332 + + + + + id + loc + + + 12 + + + 1 + 2 + 8332 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 8332 + + + + + + + + + ruby_hash_splat_argument_def + 426 + + + id + 426 + + + child + 426 + + + loc + 426 + + + + + id + child + + + 12 + + + 1 + 2 + 426 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 426 + + + + + + + child + id + + + 12 + + + 1 + 2 + 426 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 426 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 426 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 426 + + + + + + + + + ruby_hash_splat_parameter_def + 432 + + + id + 432 + + + loc + 432 + + + + + id + loc + + + 12 + + + 1 + 2 + 432 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 432 + + + + + + + + + ruby_hash_splat_parameter_name + 361 + + + ruby_hash_splat_parameter + 361 + + + name + 361 + + + + + ruby_hash_splat_parameter + name + + + 12 + + + 1 + 2 + 361 + + + + + + + name + ruby_hash_splat_parameter + + + 12 + + + 1 + 2 + 361 + + + + + + + + + ruby_heredoc_body_child + 8041 + + + ruby_heredoc_body + 1697 + + + index + 72 + + + child + 8041 + + + + + ruby_heredoc_body + index + + + 12 + + + 2 + 3 + 924 + + + 4 + 5 + 208 + + + 5 + 6 + 1 + + + 6 + 7 + 256 + + + 7 + 9 + 109 + + + 10 + 15 + 138 + + + 16 + 73 + 61 + + + + + + + ruby_heredoc_body + child + + + 12 + + + 2 + 3 + 924 + + + 4 + 5 + 208 + + + 5 + 6 + 1 + + + 6 + 7 + 256 + + + 7 + 9 + 109 + + + 10 + 15 + 138 + + + 16 + 73 + 61 + + + + + + + index + ruby_heredoc_body + + + 12 + + + 1 + 2 + 19 + + + 2 + 3 + 9 + + + 4 + 5 + 4 + + + 5 + 8 + 5 + + + 8 + 9 + 3 + + + 10 + 13 + 6 + + + 14 + 24 + 6 + + + 29 + 62 + 6 + + + 89 + 200 + 6 + + + 303 + 774 + 6 + + + 1697 + 1698 + 2 + + + + + + + index + child + + + 12 + + + 1 + 2 + 19 + + + 2 + 3 + 9 + + + 4 + 5 + 4 + + + 5 + 8 + 5 + + + 8 + 9 + 3 + + + 10 + 13 + 6 + + + 14 + 24 + 6 + + + 29 + 62 + 6 + + + 89 + 200 + 6 + + + 303 + 774 + 6 + + + 1697 + 1698 + 2 + + + + + + + child + ruby_heredoc_body + + + 12 + + + 1 + 2 + 8041 + + + + + + + child + index + + + 12 + + + 1 + 2 + 8041 + + + + + + + + + ruby_heredoc_body_def + 1697 + + + id + 1697 + + + loc + 1697 + + + + + id + loc + + + 12 + + + 1 + 2 + 1697 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1697 + + + + + + + + + ruby_if_alternative + 2067 + + + ruby_if + 2067 + + + alternative + 2067 + + + + + ruby_if + alternative + + + 12 + + + 1 + 2 + 2067 + + + + + + + alternative + ruby_if + + + 12 + + + 1 + 2 + 2067 + + + + + + + + + ruby_if_consequence + 5954 + + + ruby_if + 5954 + + + consequence + 5954 + + + + + ruby_if + consequence + + + 12 + + + 1 + 2 + 5954 + + + + + + + consequence + ruby_if + + + 12 + + + 1 + 2 + 5954 + + + + + + + + + ruby_if_def + 5974 + + + id + 5974 + + + condition + 5974 + + + loc + 5974 + + + + + id + condition + + + 12 + + + 1 + 2 + 5974 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 5974 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 5974 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 5974 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 5974 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 5974 + + + + + + + + + ruby_if_modifier_def + 4425 + + + id + 4425 + + + body + 4425 + + + condition + 4425 + + + loc + 4425 + + + + + id + body + + + 12 + + + 1 + 2 + 4425 + + + + + + + id + condition + + + 12 + + + 1 + 2 + 4425 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 4425 + + + + + + + body + id + + + 12 + + + 1 + 2 + 4425 + + + + + + + body + condition + + + 12 + + + 1 + 2 + 4425 + + + + + + + body + loc + + + 12 + + + 1 + 2 + 4425 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 4425 + + + + + + + condition + body + + + 12 + + + 1 + 2 + 4425 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 4425 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 4425 + + + + + + + loc + body + + + 12 + + + 1 + 2 + 4425 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 4425 + + + + + + + + + ruby_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 + + + + + + + + + ruby_interpolation_child + 12372 + + + ruby_interpolation + 12372 + + + index + 1 + + + child + 12372 + + + + + ruby_interpolation + index + + + 12 + + + 1 + 2 + 12372 + + + + + + + ruby_interpolation + child + + + 12 + + + 1 + 2 + 12372 + + + + + + + index + ruby_interpolation + + + 12 + + + 12372 + 12373 + 1 + + + + + + + index + child + + + 12 + + + 12372 + 12373 + 1 + + + + + + + child + ruby_interpolation + + + 12 + + + 1 + 2 + 12372 + + + + + + + child + index + + + 12 + + + 1 + 2 + 12372 + + + + + + + + + ruby_interpolation_def + 12372 + + + id + 12372 + + + loc + 12372 + + + + + id + loc + + + 12 + + + 1 + 2 + 12372 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 12372 + + + + + + + + + ruby_keyword_parameter_def + 1151 + + + id + 1151 + + + name + 1151 + + + loc + 1151 + + + + + id + name + + + 12 + + + 1 + 2 + 1151 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 1151 + + + + + + + name + id + + + 12 + + + 1 + 2 + 1151 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 1151 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1151 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 1151 + + + + + + + + + ruby_keyword_parameter_value + 855 + + + ruby_keyword_parameter + 855 + + + value + 855 + + + + + ruby_keyword_parameter + value + + + 12 + + + 1 + 2 + 855 + + + + + + + value + ruby_keyword_parameter + + + 12 + + + 1 + 2 + 855 + + + + + + + + + ruby_lambda_def + 711 + + + id + 711 + + + body + 711 + + + loc + 711 + + + + + id + body + + + 12 + + + 1 + 2 + 711 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 711 + + + + + + + body + id + + + 12 + + + 1 + 2 + 711 + + + + + + + body + loc + + + 12 + + + 1 + 2 + 711 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 711 + + + + + + + loc + body + + + 12 + + + 1 + 2 + 711 + + + + + + + + + ruby_lambda_parameters + 202 + + + ruby_lambda + 202 + + + parameters + 202 + + + + + ruby_lambda + parameters + + + 12 + + + 1 + 2 + 202 + + + + + + + parameters + ruby_lambda + + + 12 + + + 1 + 2 + 202 + + + + + + + + + ruby_lambda_parameters_child + 260 + + + ruby_lambda_parameters + 197 + + + index + 4 + + + child + 260 + + + + + ruby_lambda_parameters + index + + + 12 + + + 1 + 2 + 157 + + + 2 + 3 + 23 + + + 3 + 5 + 16 + + + + + + + ruby_lambda_parameters + child + + + 12 + + + 1 + 2 + 157 + + + 2 + 3 + 23 + + + 3 + 5 + 16 + + + + + + + index + ruby_lambda_parameters + + + 12 + + + 6 + 7 + 1 + + + 16 + 17 + 1 + + + 39 + 40 + 1 + + + 193 + 194 + 1 + + + + + + + index + child + + + 12 + + + 6 + 7 + 1 + + + 16 + 17 + 1 + + + 39 + 40 + 1 + + + 193 + 194 + 1 + + + + + + + child + ruby_lambda_parameters + + + 12 + + + 1 + 2 + 260 + + + + + + + child + index + + + 12 + + + 1 + 2 + 260 + + + + + + + + + ruby_lambda_parameters_def + 202 + + + id + 202 + + + loc + 202 + + + + + id + loc + + + 12 + + + 1 + 2 + 202 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 202 + + + + + + + + + ruby_left_assignment_list_child + 1819 + + + ruby_left_assignment_list + 816 + + + index + 9 + + + child + 1819 + + + + + ruby_left_assignment_list + index + + + 12 + + + 1 + 2 + 2 + + + 2 + 3 + 664 + + + 3 + 4 + 128 + + + 4 + 10 + 21 + + + + + + + ruby_left_assignment_list + child + + + 12 + + + 1 + 2 + 2 + + + 2 + 3 + 664 + + + 3 + 4 + 128 + + + 4 + 10 + 21 + + + + + + + index + ruby_left_assignment_list + + + 12 + + + 1 + 2 + 1 + + + 2 + 3 + 1 + + + 3 + 4 + 2 + + + 9 + 10 + 1 + + + 21 + 22 + 1 + + + 146 + 147 + 1 + + + 795 + 796 + 1 + + + 797 + 798 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 1 + + + 2 + 3 + 1 + + + 3 + 4 + 2 + + + 9 + 10 + 1 + + + 21 + 22 + 1 + + + 146 + 147 + 1 + + + 795 + 796 + 1 + + + 797 + 798 + 1 + + + + + + + child + ruby_left_assignment_list + + + 12 + + + 1 + 2 + 1819 + + + + + + + child + index + + + 12 + + + 1 + 2 + 1819 + + + + + + + + + ruby_left_assignment_list_def + 816 + + + id + 816 + + + loc + 816 + + + + + id + loc + + + 12 + + + 1 + 2 + 816 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 816 + + + + + + + + + ruby_method_child + 84921 + + + ruby_method + 31332 + + + index + 77 + + + child + 84921 + + + + + ruby_method + index + + + 12 + + + 1 + 2 + 14132 + + + 2 + 3 + 5765 + + + 3 + 4 + 4064 + + + 4 + 5 + 2521 + + + 5 + 7 + 2615 + + + 7 + 77 + 2232 + + + + + + + ruby_method + child + + + 12 + + + 1 + 2 + 14132 + + + 2 + 3 + 5765 + + + 3 + 4 + 4064 + + + 4 + 5 + 2521 + + + 5 + 7 + 2615 + + + 7 + 77 + 2232 + + + + + + + index + ruby_method + + + 12 + + + 1 + 2 + 7 + + + 2 + 4 + 2 + + + 4 + 5 + 9 + + + 5 + 6 + 9 + + + 6 + 7 + 9 + + + 9 + 12 + 4 + + + 13 + 19 + 6 + + + 20 + 37 + 6 + + + 44 + 113 + 6 + + + 146 + 399 + 6 + + + 508 + 2181 + 6 + + + 3184 + 30596 + 6 + + + + + + + index + child + + + 12 + + + 1 + 2 + 7 + + + 2 + 4 + 2 + + + 4 + 5 + 9 + + + 5 + 6 + 9 + + + 6 + 7 + 9 + + + 9 + 12 + 4 + + + 13 + 19 + 6 + + + 20 + 37 + 6 + + + 44 + 113 + 6 + + + 146 + 399 + 6 + + + 508 + 2181 + 6 + + + 3184 + 30596 + 6 + + + + + + + child + ruby_method + + + 12 + + + 1 + 2 + 84921 + + + + + + + child + index + + + 12 + + + 1 + 2 + 84921 + + + + + + + + + ruby_method_def + 31633 + + + id + 31633 + + + name + 31633 + + + loc + 31633 + + + + + id + name + + + 12 + + + 1 + 2 + 31633 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 31633 + + + + + + + name + id + + + 12 + + + 1 + 2 + 31633 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 31633 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 31633 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 31633 + + + + + + + + + ruby_method_parameters + 8741 + + + ruby_method + 8741 + + + parameters + 8741 + + + + + ruby_method + parameters + + + 12 + + + 1 + 2 + 8741 + + + + + + + parameters + ruby_method + + + 12 + + + 1 + 2 + 8741 + + + + + + + + + ruby_method_parameters_child + 15231 + + + ruby_method_parameters + 9202 + + + index + 11 + + + child + 15231 + + + + + ruby_method_parameters + index + + + 12 + + + 1 + 2 + 5533 + + + 2 + 3 + 2258 + + + 3 + 4 + 885 + + + 4 + 12 + 525 + + + + + + + ruby_method_parameters + child + + + 12 + + + 1 + 2 + 5533 + + + 2 + 3 + 2258 + + + 3 + 4 + 885 + + + 4 + 12 + 525 + + + + + + + index + ruby_method_parameters + + + 12 + + + 3 + 4 + 1 + + + 5 + 6 + 1 + + + 9 + 10 + 1 + + + 26 + 27 + 1 + + + 44 + 45 + 1 + + + 103 + 104 + 1 + + + 223 + 224 + 1 + + + 513 + 514 + 1 + + + 1378 + 1379 + 1 + + + 3583 + 3584 + 1 + + + 8986 + 8987 + 1 + + + + + + + index + child + + + 12 + + + 3 + 4 + 1 + + + 5 + 6 + 1 + + + 9 + 10 + 1 + + + 26 + 27 + 1 + + + 44 + 45 + 1 + + + 103 + 104 + 1 + + + 223 + 224 + 1 + + + 513 + 514 + 1 + + + 1378 + 1379 + 1 + + + 3583 + 3584 + 1 + + + 8986 + 8987 + 1 + + + + + + + child + ruby_method_parameters + + + 12 + + + 1 + 2 + 15231 + + + + + + + child + index + + + 12 + + + 1 + 2 + 15231 + + + + + + + + + ruby_method_parameters_def + 9291 + + + id + 9291 + + + loc + 9291 + + + + + id + loc + + + 12 + + + 1 + 2 + 9291 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 9291 + + + + + + + + + ruby_module_child + 10026 + + + ruby_module + 3395 + + + index + 126 + + + child + 10026 + + + + + ruby_module + index + + + 12 + + + 1 + 2 + 2407 + + + 2 + 3 + 287 + + + 3 + 5 + 243 + + + 5 + 11 + 270 + + + 11 + 125 + 186 + + + + + + + ruby_module + child + + + 12 + + + 1 + 2 + 2407 + + + 2 + 3 + 287 + + + 3 + 5 + 243 + + + 5 + 11 + 270 + + + 11 + 125 + 186 + + + + + + + index + ruby_module + + + 12 + + + 1 + 2 + 11 + + + 2 + 3 + 11 + + + 3 + 4 + 4 + + + 4 + 5 + 21 + + + 5 + 7 + 10 + + + 7 + 10 + 10 + + + 10 + 16 + 11 + + + 16 + 23 + 11 + + + 25 + 45 + 10 + + + 50 + 107 + 10 + + + 123 + 374 + 10 + + + 446 + 3317 + 5 + + + + + + + index + child + + + 12 + + + 1 + 2 + 11 + + + 2 + 3 + 11 + + + 3 + 4 + 4 + + + 4 + 5 + 21 + + + 5 + 7 + 10 + + + 7 + 10 + 10 + + + 10 + 16 + 11 + + + 16 + 23 + 11 + + + 25 + 45 + 10 + + + 50 + 107 + 10 + + + 123 + 374 + 10 + + + 446 + 3317 + 5 + + + + + + + child + ruby_module + + + 12 + + + 1 + 2 + 10026 + + + + + + + child + index + + + 12 + + + 1 + 2 + 10026 + + + + + + + + + ruby_module_def + 4650 + + + id + 4650 + + + name + 4650 + + + loc + 4650 + + + + + id + name + + + 12 + + + 1 + 2 + 4650 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 4650 + + + + + + + name + id + + + 12 + + + 1 + 2 + 4650 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 4650 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 4650 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 4650 + + + + + + + + + ruby_next_child + 15 + + + ruby_next + 15 + + + child + 15 + + + + + ruby_next + child + + + 12 + + + 1 + 2 + 15 + + + + + + + child + ruby_next + + + 12 + + + 1 + 2 + 15 + + + + + + + + + ruby_next_def + 662 + + + id + 662 + + + loc + 662 + + + + + id + loc + + + 12 + + + 1 + 2 + 662 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 662 + + + + + + + + + ruby_operator_assignment_def + 2098 + + + id + 2098 + + + left + 2098 + + + operator + 6 + + + right + 2098 + + + loc + 2098 + + + + + id + left + + + 12 + + + 1 + 2 + 2098 + + + + + + + id + operator + + + 12 + + + 1 + 2 + 2098 + + + + + + + id + right + + + 12 + + + 1 + 2 + 2098 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 2098 + + + + + + + left + id + + + 12 + + + 1 + 2 + 2098 + + + + + + + left + operator + + + 12 + + + 1 + 2 + 2098 + + + + + + + left + right + + + 12 + + + 1 + 2 + 2098 + + + + + + + left + loc + + + 12 + + + 1 + 2 + 2098 + + + + + + + operator + id + + + 12 + + + 1 + 2 + 1 + + + 5 + 6 + 1 + + + 9 + 10 + 1 + + + 64 + 65 + 1 + + + 517 + 518 + 1 + + + 1502 + 1503 + 1 + + + + + + + operator + left + + + 12 + + + 1 + 2 + 1 + + + 5 + 6 + 1 + + + 9 + 10 + 1 + + + 64 + 65 + 1 + + + 517 + 518 + 1 + + + 1502 + 1503 + 1 + + + + + + + operator + right + + + 12 + + + 1 + 2 + 1 + + + 5 + 6 + 1 + + + 9 + 10 + 1 + + + 64 + 65 + 1 + + + 517 + 518 + 1 + + + 1502 + 1503 + 1 + + + + + + + operator + loc + + + 12 + + + 1 + 2 + 1 + + + 5 + 6 + 1 + + + 9 + 10 + 1 + + + 64 + 65 + 1 + + + 517 + 518 + 1 + + + 1502 + 1503 + 1 + + + + + + + right + id + + + 12 + + + 1 + 2 + 2098 + + + + + + + right + left + + + 12 + + + 1 + 2 + 2098 + + + + + + + right + operator + + + 12 + + + 1 + 2 + 2098 + + + + + + + right + loc + + + 12 + + + 1 + 2 + 2098 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 2098 + + + + + + + loc + left + + + 12 + + + 1 + 2 + 2098 + + + + + + + loc + operator + + + 12 + + + 1 + 2 + 2098 + + + + + + + loc + right + + + 12 + + + 1 + 2 + 2098 + + + + + + + + + ruby_optional_parameter_def + 2105 + + + id + 2105 + + + name + 2105 + + + value + 2105 + + + loc + 2105 + + + + + id + name + + + 12 + + + 1 + 2 + 2105 + + + + + + + id + value + + + 12 + + + 1 + 2 + 2105 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 2105 + + + + + + + name + id + + + 12 + + + 1 + 2 + 2105 + + + + + + + name + value + + + 12 + + + 1 + 2 + 2105 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 2105 + + + + + + + value + id + + + 12 + + + 1 + 2 + 2105 + + + + + + + value + name + + + 12 + + + 1 + 2 + 2105 + + + + + + + value + loc + + + 12 + + + 1 + 2 + 2105 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 2105 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 2105 + + + + + + + loc + value + + + 12 + + + 1 + 2 + 2105 + + + + + + + + + ruby_pair_def + 69682 + + + id + 69682 + + + key__ + 69682 + + + value + 69682 + + + loc + 69682 + + + + + id + key__ + + + 12 + + + 1 + 2 + 69682 + + + + + + + id + value + + + 12 + + + 1 + 2 + 69682 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 69682 + + + + + + + key__ + id + + + 12 + + + 1 + 2 + 69682 + + + + + + + key__ + value + + + 12 + + + 1 + 2 + 69682 + + + + + + + key__ + loc + + + 12 + + + 1 + 2 + 69682 + + + + + + + value + id + + + 12 + + + 1 + 2 + 69682 + + + + + + + value + key__ + + + 12 + + + 1 + 2 + 69682 + + + + + + + value + loc + + + 12 + + + 1 + 2 + 69682 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 69682 + + + + + + + loc + key__ + + + 12 + + + 1 + 2 + 69682 + + + + + + + loc + value + + + 12 + + + 1 + 2 + 69682 + + + + + + + + + ruby_parenthesized_statements_child + 1759 + + + ruby_parenthesized_statements + 1758 + + + index + 2 + + + child + 1759 + + + + + ruby_parenthesized_statements + index + + + 12 + + + 1 + 2 + 1757 + + + 2 + 3 + 1 + + + + + + + ruby_parenthesized_statements + child + + + 12 + + + 1 + 2 + 1757 + + + 2 + 3 + 1 + + + + + + + index + ruby_parenthesized_statements + + + 12 + + + 1 + 2 + 1 + + + 1758 + 1759 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 1 + + + 1758 + 1759 + 1 + + + + + + + child + ruby_parenthesized_statements + + + 12 + + + 1 + 2 + 1759 + + + + + + + child + index + + + 12 + + + 1 + 2 + 1759 + + + + + + + + + ruby_parenthesized_statements_def + 1758 + + + id + 1758 + + + loc + 1758 + + + + + id + loc + + + 12 + + + 1 + 2 + 1758 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1758 + + + + + + + + + ruby_pattern_def + 1227 + + + id + 1227 + + + child + 1227 + + + loc + 1227 + + + + + id + child + + + 12 + + + 1 + 2 + 1227 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 1227 + + + + + + + child + id + + + 12 + + + 1 + 2 + 1227 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 1227 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1227 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 1227 + + + + + + + + + ruby_program_child + 13685 + + + ruby_program + 5437 + + + index + 132 + + + child + 13685 + + + + + ruby_program + index + + + 12 + + + 1 + 2 + 2364 + + + 2 + 3 + 1792 + + + 3 + 4 + 482 + + + 4 + 7 + 455 + + + 7 + 44 + 341 + + + + + + + ruby_program + child + + + 12 + + + 1 + 2 + 2364 + + + 2 + 3 + 1792 + + + 3 + 4 + 482 + + + 4 + 7 + 455 + + + 7 + 44 + 341 + + + + + + + index + ruby_program + + + 12 + + + 1 + 2 + 30 + + + 2 + 3 + 9 + + + 4 + 5 + 15 + + + 5 + 7 + 6 + + + 8 + 11 + 9 + + + 13 + 15 + 9 + + + 16 + 25 + 9 + + + 31 + 43 + 9 + + + 50 + 77 + 9 + + + 92 + 147 + 9 + + + 191 + 417 + 9 + + + 999 + 1769 + 6 + + + + + + + index + child + + + 12 + + + 1 + 2 + 30 + + + 2 + 3 + 9 + + + 4 + 5 + 15 + + + 5 + 7 + 6 + + + 8 + 11 + 9 + + + 13 + 15 + 9 + + + 16 + 25 + 9 + + + 31 + 43 + 9 + + + 50 + 77 + 9 + + + 92 + 147 + 9 + + + 191 + 417 + 9 + + + 999 + 1769 + 6 + + + + + + + child + ruby_program + + + 12 + + + 1 + 2 + 13685 + + + + + + + child + index + + + 12 + + + 1 + 2 + 13685 + + + + + + + + + ruby_program_def + 5504 + + + id + 5504 + + + loc + 5504 + + + + + id + loc + + + 12 + + + 1 + 2 + 5504 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 5504 + + + + + + + + + ruby_range_begin + 570 + + + ruby_range + 570 + + + begin + 570 + + + + + ruby_range + begin + + + 12 + + + 1 + 2 + 570 + + + + + + + begin + ruby_range + + + 12 + + + 1 + 2 + 570 + + + + + + + + + ruby_range_def + 577 + + + id + 577 + + + operator + 2 + + + loc + 577 + + + + + id + operator + + + 12 + + + 1 + 2 + 577 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 577 + + + + + + + operator + id + + + 12 + + + 125 + 126 + 1 + + + 439 + 440 + 1 + + + + + + + operator + loc + + + 12 + + + 125 + 126 + 1 + + + 439 + 440 + 1 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 577 + + + + + + + loc + operator + + + 12 + + + 1 + 2 + 577 + + + + + + + + + ruby_range_end + 484 + + + ruby_range + 484 + + + end + 484 + + + + + ruby_range + end + + + 12 + + + 1 + 2 + 484 + + + + + + + end + ruby_range + + + 12 + + + 1 + 2 + 484 + + + + + + + + + ruby_rational_def + 4 + + + id + 4 + + + child + 4 + + + loc + 4 + + + + + id + child + + + 12 + + + 1 + 2 + 4 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 4 + + + + + + + child + id + + + 12 + + + 1 + 2 + 4 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 4 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 4 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 4 + + + + + + + + + ruby_redo_child + 0 + + + ruby_redo + 0 + + + child + 0 + + + + + ruby_redo + child + + + 12 + + + 1 + 2 + 1 + + + + + + + child + ruby_redo + + + 12 + + + 1 + 2 + 1 + + + + + + + + + ruby_redo_def + 0 + + + id + 0 + + + loc + 0 + + + + + id + loc + + + 12 + + + 1 + 2 + 1 + + + + + + + loc + id + + + 12 + + + + + + + + ruby_regex_child + 14014 + + + ruby_regex + 4126 + + + index + 44 + + + child + 14014 + + + + + ruby_regex + index + + + 12 + + + 1 + 2 + 2110 + + + 2 + 3 + 225 + + + 3 + 4 + 539 + + + 4 + 5 + 156 + + + 5 + 6 + 362 + + + 6 + 8 + 318 + + + 8 + 15 + 313 + + + 15 + 44 + 99 + + + + + + + ruby_regex + child + + + 12 + + + 1 + 2 + 2110 + + + 2 + 3 + 225 + + + 3 + 4 + 539 + + + 4 + 5 + 156 + + + 5 + 6 + 362 + + + 6 + 8 + 318 + + + 8 + 15 + 313 + + + 15 + 44 + 99 + + + + + + + index + ruby_regex + + + 12 + + + 2 + 3 + 4 + + + 4 + 7 + 3 + + + 7 + 11 + 3 + + + 12 + 17 + 3 + + + 17 + 18 + 2 + + + 20 + 22 + 3 + + + 23 + 25 + 2 + + + 25 + 31 + 3 + + + 32 + 40 + 3 + + + 57 + 98 + 3 + + + 108 + 169 + 3 + + + 231 + 343 + 3 + + + 403 + 715 + 3 + + + 1068 + 1749 + 3 + + + 1968 + 4030 + 2 + + + + + + + index + child + + + 12 + + + 2 + 3 + 4 + + + 4 + 7 + 3 + + + 7 + 11 + 3 + + + 12 + 17 + 3 + + + 17 + 18 + 2 + + + 20 + 22 + 3 + + + 23 + 25 + 2 + + + 25 + 31 + 3 + + + 32 + 40 + 3 + + + 57 + 98 + 3 + + + 108 + 169 + 3 + + + 231 + 343 + 3 + + + 403 + 715 + 3 + + + 1068 + 1749 + 3 + + + 1968 + 4030 + 2 + + + + + + + child + ruby_regex + + + 12 + + + 1 + 2 + 14014 + + + + + + + child + index + + + 12 + + + 1 + 2 + 14014 + + + + + + + + + ruby_regex_def + 4131 + + + id + 4131 + + + loc + 4131 + + + + + id + loc + + + 12 + + + 1 + 2 + 4131 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 4131 + + + + + + + + + ruby_rescue_body + 573 + + + ruby_rescue + 573 + + + body + 573 + + + + + ruby_rescue + body + + + 12 + + + 1 + 2 + 573 + + + + + + + body + ruby_rescue + + + 12 + + + 1 + 2 + 573 + + + + + + + + + ruby_rescue_def + 668 + + + id + 668 + + + loc + 668 + + + + + id + loc + + + 12 + + + 1 + 2 + 668 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 668 + + + + + + + + + ruby_rescue_exceptions + 453 + + + ruby_rescue + 453 + + + exceptions + 453 + + + + + ruby_rescue + exceptions + + + 12 + + + 1 + 2 + 453 + + + + + + + exceptions + ruby_rescue + + + 12 + + + 1 + 2 + 453 + + + + + + + + + ruby_rescue_modifier_def + 184 + + + id + 184 + + + body + 184 + + + handler + 184 + + + loc + 184 + + + + + id + body + + + 12 + + + 1 + 2 + 184 + + + + + + + id + handler + + + 12 + + + 1 + 2 + 184 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 184 + + + + + + + body + id + + + 12 + + + 1 + 2 + 184 + + + + + + + body + handler + + + 12 + + + 1 + 2 + 184 + + + + + + + body + loc + + + 12 + + + 1 + 2 + 184 + + + + + + + handler + id + + + 12 + + + 1 + 2 + 184 + + + + + + + handler + body + + + 12 + + + 1 + 2 + 184 + + + + + + + handler + loc + + + 12 + + + 1 + 2 + 184 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 184 + + + + + + + loc + body + + + 12 + + + 1 + 2 + 184 + + + + + + + loc + handler + + + 12 + + + 1 + 2 + 184 + + + + + + + + + ruby_rescue_variable + 327 + + + ruby_rescue + 327 + + + variable + 327 + + + + + ruby_rescue + variable + + + 12 + + + 1 + 2 + 327 + + + + + + + variable + ruby_rescue + + + 12 + + + 1 + 2 + 327 + + + + + + + + + ruby_rest_assignment_child + 7 + + + ruby_rest_assignment + 7 + + + child + 7 + + + + + ruby_rest_assignment + child + + + 12 + + + 1 + 2 + 7 + + + + + + + child + ruby_rest_assignment + + + 12 + + + 1 + 2 + 7 + + + + + + + + + ruby_rest_assignment_def + 18 + + + id + 18 + + + loc + 18 + + + + + id + loc + + + 12 + + + 1 + 2 + 18 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 18 + + + + + + + + + ruby_retry_child + 0 + + + ruby_retry + 0 + + + child + 0 + + + + + ruby_retry + child + + + 12 + + + 1 + 2 + 1 + + + + + + + child + ruby_retry + + + 12 + + + 1 + 2 + 1 + + + + + + + + + ruby_retry_def + 11 + + + id + 11 + + + loc + 11 + + + + + id + loc + + + 12 + + + 1 + 2 + 11 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 11 + + + + + + + + + ruby_return_child + 1731 + + + ruby_return + 1731 + + + child + 1731 + + + + + ruby_return + child + + + 12 + + + 1 + 2 + 1731 + + + + + + + child + ruby_return + + + 12 + + + 1 + 2 + 1731 + + + + + + + + + ruby_return_def + 2742 + + + id + 2742 + + + loc + 2742 + + + + + id + loc + + + 12 + + + 1 + 2 + 2742 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 2742 + + + + + + + + + ruby_right_assignment_list_child + 938 + + + ruby_right_assignment_list + 439 + + + index + 5 + + + child + 938 + + + + + ruby_right_assignment_list + index + + + 12 + + + 2 + 3 + 392 + + + 3 + 4 + 36 + + + 4 + 6 + 10 + + + + + + + ruby_right_assignment_list + child + + + 12 + + + 2 + 3 + 392 + + + 3 + 4 + 36 + + + 4 + 6 + 10 + + + + + + + index + ruby_right_assignment_list + + + 12 + + + 2 + 3 + 1 + + + 10 + 11 + 1 + + + 46 + 47 + 1 + + + 429 + 430 + 2 + + + + + + + index + child + + + 12 + + + 2 + 3 + 1 + + + 10 + 11 + 1 + + + 46 + 47 + 1 + + + 429 + 430 + 2 + + + + + + + child + ruby_right_assignment_list + + + 12 + + + 1 + 2 + 938 + + + + + + + child + index + + + 12 + + + 1 + 2 + 938 + + + + + + + + + ruby_right_assignment_list_def + 439 + + + id + 439 + + + loc + 439 + + + + + id + loc + + + 12 + + + 1 + 2 + 439 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 439 + + + + + + + + + ruby_scope_resolution_def + 23999 + + + id + 23999 + + + name + 23999 + + + loc + 23999 + + + + + id + name + + + 12 + + + 1 + 2 + 23999 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 23999 + + + + + + + name + id + + + 12 + + + 1 + 2 + 23999 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 23999 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 23999 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 23999 + + + + + + + + + ruby_scope_resolution_scope + 23310 + + + ruby_scope_resolution + 23310 + + + scope + 23310 + + + + + ruby_scope_resolution + scope + + + 12 + + + 1 + 2 + 23310 + + + + + + + scope + ruby_scope_resolution + + + 12 + + + 1 + 2 + 23310 + + + + + + + + + ruby_setter_def + 194 + + + id + 194 + + + name + 194 + + + loc + 194 + + + + + id + name + + + 12 + + + 1 + 2 + 194 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 194 + + + + + + + name + id + + + 12 + + + 1 + 2 + 194 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 194 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 194 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 194 + + + + + + + + + ruby_singleton_class_child + 760 + + + ruby_singleton_class + 198 + + + index + 24 + + + child + 760 + + + + + ruby_singleton_class + index + + + 12 + + + 1 + 2 + 93 + + + 2 + 3 + 23 + + + 3 + 4 + 11 + + + 4 + 5 + 15 + + + 5 + 6 + 10 + + + 6 + 8 + 17 + + + 8 + 13 + 15 + + + 13 + 25 + 12 + + + + + + + ruby_singleton_class + child + + + 12 + + + 1 + 2 + 93 + + + 2 + 3 + 23 + + + 3 + 4 + 11 + + + 4 + 5 + 15 + + + 5 + 6 + 10 + + + 6 + 8 + 17 + + + 8 + 13 + 15 + + + 13 + 25 + 12 + + + + + + + index + ruby_singleton_class + + + 12 + + + 1 + 2 + 2 + + + 2 + 3 + 3 + + + 3 + 5 + 2 + + + 7 + 8 + 1 + + + 8 + 9 + 2 + + + 10 + 13 + 2 + + + 17 + 19 + 2 + + + 20 + 24 + 2 + + + 27 + 35 + 2 + + + 44 + 55 + 2 + + + 69 + 81 + 2 + + + 103 + 195 + 2 + + + + + + + index + child + + + 12 + + + 1 + 2 + 2 + + + 2 + 3 + 3 + + + 3 + 5 + 2 + + + 7 + 8 + 1 + + + 8 + 9 + 2 + + + 10 + 13 + 2 + + + 17 + 19 + 2 + + + 20 + 24 + 2 + + + 27 + 35 + 2 + + + 44 + 55 + 2 + + + 69 + 81 + 2 + + + 103 + 195 + 2 + + + + + + + child + ruby_singleton_class + + + 12 + + + 1 + 2 + 760 + + + + + + + child + index + + + 12 + + + 1 + 2 + 760 + + + + + + + + + ruby_singleton_class_def + 198 + + + id + 198 + + + value + 198 + + + loc + 198 + + + + + id + value + + + 12 + + + 1 + 2 + 198 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 198 + + + + + + + value + id + + + 12 + + + 1 + 2 + 198 + + + + + + + value + loc + + + 12 + + + 1 + 2 + 198 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 198 + + + + + + + loc + value + + + 12 + + + 1 + 2 + 198 + + + + + + + + + ruby_singleton_method_child + 5181 + + + ruby_singleton_method + 2123 + + + index + 28 + + + child + 5181 + + + + + ruby_singleton_method + index + + + 12 + + + 1 + 2 + 1207 + + + 2 + 3 + 318 + + + 3 + 4 + 185 + + + 4 + 5 + 131 + + + 5 + 8 + 166 + + + 8 + 29 + 116 + + + + + + + ruby_singleton_method + child + + + 12 + + + 1 + 2 + 1207 + + + 2 + 3 + 318 + + + 3 + 4 + 185 + + + 4 + 5 + 131 + + + 5 + 8 + 166 + + + 8 + 29 + 116 + + + + + + + index + ruby_singleton_method + + + 12 + + + 1 + 2 + 1 + + + 3 + 4 + 2 + + + 4 + 5 + 2 + + + 6 + 7 + 4 + + + 7 + 9 + 2 + + + 11 + 16 + 2 + + + 21 + 26 + 2 + + + 30 + 37 + 2 + + + 47 + 63 + 2 + + + 84 + 117 + 2 + + + 148 + 201 + 2 + + + 282 + 414 + 2 + + + 598 + 917 + 2 + + + 2123 + 2124 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 1 + + + 3 + 4 + 2 + + + 4 + 5 + 2 + + + 6 + 7 + 4 + + + 7 + 9 + 2 + + + 11 + 16 + 2 + + + 21 + 26 + 2 + + + 30 + 37 + 2 + + + 47 + 63 + 2 + + + 84 + 117 + 2 + + + 148 + 201 + 2 + + + 282 + 414 + 2 + + + 598 + 917 + 2 + + + 2123 + 2124 + 1 + + + + + + + child + ruby_singleton_method + + + 12 + + + 1 + 2 + 5181 + + + + + + + child + index + + + 12 + + + 1 + 2 + 5181 + + + + + + + + + ruby_singleton_method_def + 2123 + + + id + 2123 + + + name + 2123 + + + object + 2123 + + + loc + 2123 + + + + + id + name + + + 12 + + + 1 + 2 + 2123 + + + + + + + id + object + + + 12 + + + 1 + 2 + 2123 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 2123 + + + + + + + name + id + + + 12 + + + 1 + 2 + 2123 + + + + + + + name + object + + + 12 + + + 1 + 2 + 2123 + + + + + + + name + loc + + + 12 + + + 1 + 2 + 2123 + + + + + + + object + id + + + 12 + + + 1 + 2 + 2123 + + + + + + + object + name + + + 12 + + + 1 + 2 + 2123 + + + + + + + object + loc + + + 12 + + + 1 + 2 + 2123 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 2123 + + + + + + + loc + name + + + 12 + + + 1 + 2 + 2123 + + + + + + + loc + object + + + 12 + + + 1 + 2 + 2123 + + + + + + + + + ruby_singleton_method_parameters + 1327 + + + ruby_singleton_method + 1327 + + + parameters + 1327 + + + + + ruby_singleton_method + parameters + + + 12 + + + 1 + 2 + 1327 + + + + + + + parameters + ruby_singleton_method + + + 12 + + + 1 + 2 + 1327 + + + + + + + + + ruby_splat_argument_def + 694 + + + id + 694 + + + child + 694 + + + loc + 694 + + + + + id + child + + + 12 + + + 1 + 2 + 694 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 694 + + + + + + + child + id + + + 12 + + + 1 + 2 + 694 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 694 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 694 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 694 + + + + + + + + + ruby_splat_parameter_def + 943 + + + id + 943 + + + loc + 943 + + + + + id + loc + + + 12 + + + 1 + 2 + 943 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 943 + + + + + + + + + ruby_splat_parameter_name + 767 + + + ruby_splat_parameter + 767 + + + name + 767 + + + + + ruby_splat_parameter + name + + + 12 + + + 1 + 2 + 767 + + + + + + + name + ruby_splat_parameter + + + 12 + + + 1 + 2 + 767 + + + + + + + + + ruby_string_array_child + 3117 + + + ruby_string_array + 965 + + + index + 76 + + + child + 3117 + + + + + ruby_string_array + index + + + 12 + + + 1 + 2 + 204 + + + 2 + 3 + 317 + + + 3 + 4 + 245 + + + 4 + 5 + 68 + + + 5 + 9 + 81 + + + 9 + 76 + 47 + + + + + + + ruby_string_array + child + + + 12 + + + 1 + 2 + 204 + + + 2 + 3 + 317 + + + 3 + 4 + 245 + + + 4 + 5 + 68 + + + 5 + 9 + 81 + + + 9 + 76 + 47 + + + + + + + index + ruby_string_array + + + 12 + + + 1 + 2 + 30 + + + 2 + 3 + 1 + + + 3 + 4 + 6 + + + 4 + 5 + 13 + + + 5 + 7 + 4 + + + 7 + 13 + 5 + + + 13 + 37 + 6 + + + 41 + 127 + 6 + + + 193 + 944 + 4 + + + + + + + index + child + + + 12 + + + 1 + 2 + 30 + + + 2 + 3 + 1 + + + 3 + 4 + 6 + + + 4 + 5 + 13 + + + 5 + 7 + 4 + + + 7 + 13 + 5 + + + 13 + 37 + 6 + + + 41 + 127 + 6 + + + 193 + 944 + 4 + + + + + + + child + ruby_string_array + + + 12 + + + 1 + 2 + 3117 + + + + + + + child + index + + + 12 + + + 1 + 2 + 3117 + + + + + + + + + ruby_string_array_def + 971 + + + id + 971 + + + loc + 971 + + + + + id + loc + + + 12 + + + 1 + 2 + 971 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 971 + + + + + + + + + ruby_string_child + 130102 + + + ruby_string__ + 94415 + + + index + 126 + + + child + 130102 + + + + + ruby_string__ + index + + + 12 + + + 1 + 2 + 87263 + + + 2 + 65 + 7081 + + + 65 + 125 + 70 + + + + + + + ruby_string__ + child + + + 12 + + + 1 + 2 + 87263 + + + 2 + 65 + 7081 + + + 65 + 125 + 70 + + + + + + + index + ruby_string__ + + + 12 + + + 1 + 19 + 4 + + + 61 + 62 + 13 + + + 62 + 63 + 37 + + + 64 + 82 + 8 + + + 142 + 146 + 10 + + + 146 + 195 + 10 + + + 195 + 217 + 10 + + + 220 + 365 + 10 + + + 394 + 480 + 10 + + + 485 + 3524 + 10 + + + 6984 + 92195 + 2 + + + + + + + index + child + + + 12 + + + 1 + 19 + 4 + + + 61 + 62 + 13 + + + 62 + 63 + 37 + + + 64 + 82 + 8 + + + 142 + 146 + 10 + + + 146 + 195 + 10 + + + 195 + 217 + 10 + + + 220 + 365 + 10 + + + 394 + 480 + 10 + + + 485 + 3524 + 10 + + + 6984 + 92195 + 2 + + + + + + + child + ruby_string__ + + + 12 + + + 1 + 2 + 130102 + + + + + + + child + index + + + 12 + + + 1 + 2 + 130102 + + + + + + + + + ruby_string_def + 96217 + + + id + 96217 + + + loc + 96217 + + + + + id + loc + + + 12 + + + 1 + 2 + 96217 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 96217 + + + + + + + + + ruby_subshell_child + 210 + + + ruby_subshell + 134 + + + index + 11 + + + child + 210 + + + + + ruby_subshell + index + + + 12 + + + 1 + 2 + 101 + + + 2 + 3 + 17 + + + 3 + 6 + 9 + + + 6 + 12 + 6 + + + + + + + ruby_subshell + child + + + 12 + + + 1 + 2 + 101 + + + 2 + 3 + 17 + + + 3 + 6 + 9 + + + 6 + 12 + 6 + + + + + + + index + ruby_subshell + + + 12 + + + 1 + 2 + 4 + + + 2 + 3 + 1 + + + 6 + 7 + 1 + + + 7 + 8 + 1 + + + 9 + 10 + 1 + + + 15 + 16 + 1 + + + 32 + 33 + 1 + + + 131 + 132 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 4 + + + 2 + 3 + 1 + + + 6 + 7 + 1 + + + 7 + 8 + 1 + + + 9 + 10 + 1 + + + 15 + 16 + 1 + + + 32 + 33 + 1 + + + 131 + 132 + 1 + + + + + + + child + ruby_subshell + + + 12 + + + 1 + 2 + 210 + + + + + + + child + index + + + 12 + + + 1 + 2 + 210 + + + + + + + + + ruby_subshell_def + 134 + + + id + 134 + + + loc + 134 + + + + + id + loc + + + 12 + + + 1 + 2 + 134 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 134 + + + + + + + + + ruby_superclass_def + 4270 + + + id + 4270 + + + child + 4270 + + + loc + 4270 + + + + + id + child + + + 12 + + + 1 + 2 + 4270 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 4270 + + + + + + + child + id + + + 12 + + + 1 + 2 + 4270 + + + + + + + child + loc + + + 12 + + + 1 + 2 + 4270 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 4270 + + + + + + + loc + child + + + 12 + + + 1 + 2 + 4270 + + + + + + + + + ruby_symbol_array_child + 689 + + + ruby_symbol_array + 140 + + + index + 32 + + + child + 689 + + + + + ruby_symbol_array + index + + + 12 + + + 1 + 2 + 53 + + + 2 + 3 + 25 + + + 3 + 4 + 13 + + + 4 + 6 + 7 + + + 6 + 7 + 8 + + + 7 + 10 + 12 + + + 10 + 16 + 11 + + + 16 + 33 + 11 + + + + + + + ruby_symbol_array + child + + + 12 + + + 1 + 2 + 53 + + + 2 + 3 + 25 + + + 3 + 4 + 13 + + + 4 + 6 + 7 + + + 6 + 7 + 8 + + + 7 + 10 + 12 + + + 10 + 16 + 11 + + + 16 + 33 + 11 + + + + + + + index + ruby_symbol_array + + + 12 + + + 1 + 2 + 4 + + + 2 + 3 + 4 + + + 3 + 4 + 3 + + + 4 + 6 + 2 + + + 6 + 8 + 2 + + + 10 + 12 + 2 + + + 14 + 18 + 2 + + + 18 + 21 + 2 + + + 21 + 23 + 2 + + + 27 + 30 + 2 + + + 34 + 43 + 2 + + + 43 + 50 + 2 + + + 62 + 88 + 2 + + + 140 + 141 + 1 + + + + + + + index + child + + + 12 + + + 1 + 2 + 4 + + + 2 + 3 + 4 + + + 3 + 4 + 3 + + + 4 + 6 + 2 + + + 6 + 8 + 2 + + + 10 + 12 + 2 + + + 14 + 18 + 2 + + + 18 + 21 + 2 + + + 21 + 23 + 2 + + + 27 + 30 + 2 + + + 34 + 43 + 2 + + + 43 + 50 + 2 + + + 62 + 88 + 2 + + + 140 + 141 + 1 + + + + + + + child + ruby_symbol_array + + + 12 + + + 1 + 2 + 689 + + + + + + + child + index + + + 12 + + + 1 + 2 + 689 + + + + + + + + + ruby_symbol_array_def + 140 + + + id + 140 + + + loc + 140 + + + + + id + loc + + + 12 + + + 1 + 2 + 140 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 140 + + + + + + + + + ruby_then_child + 13464 + + + ruby_then + 7955 + + + index + 35 + + + child + 13464 + + + + + ruby_then + index + + + 12 + + + 1 + 2 + 4951 + + + 2 + 3 + 1806 + + + 3 + 4 + 658 + + + 4 + 36 + 540 + + + + + + + ruby_then + child + + + 12 + + + 1 + 2 + 4951 + + + 2 + 3 + 1806 + + + 3 + 4 + 658 + + + 4 + 36 + 540 + + + + + + + index + ruby_then + + + 12 + + + 1 + 2 + 13 + + + 2 + 3 + 1 + + + 4 + 5 + 3 + + + 5 + 6 + 3 + + + 7 + 11 + 3 + + + 12 + 28 + 3 + + + 43 + 92 + 3 + + + 156 + 541 + 3 + + + 1198 + 7956 + 3 + + + + + + + index + child + + + 12 + + + 1 + 2 + 13 + + + 2 + 3 + 1 + + + 4 + 5 + 3 + + + 5 + 6 + 3 + + + 7 + 11 + 3 + + + 12 + 28 + 3 + + + 43 + 92 + 3 + + + 156 + 541 + 3 + + + 1198 + 7956 + 3 + + + + + + + child + ruby_then + + + 12 + + + 1 + 2 + 13464 + + + + + + + child + index + + + 12 + + + 1 + 2 + 13464 + + + + + + + + + ruby_then_def + 7955 + + + id + 7955 + + + loc + 7955 + + + + + id + loc + + + 12 + + + 1 + 2 + 7955 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 7955 + + + + + + + + + ruby_tokeninfo + 1894753 + + + id + 1894753 + + + kind + 23 + + + value + 85301 + + + loc + 1894720 + + + + + id + kind + + + 12 + + + 1 + 2 + 1894753 + + + + + + + id + value + + + 12 + + + 1 + 2 + 1894753 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 1894753 + + + + + + + kind + id + + + 12 + + + 1 + 34 + 2 + + + 139 + 215 + 2 + + + 444 + 1556 + 2 + + + 1697 + 1698 + 2 + + + 3861 + 3982 + 2 + + + 4065 + 5413 + 2 + + + 7403 + 9303 + 2 + + + 13214 + 16454 + 2 + + + 23074 + 52448 + 2 + + + 52634 + 75797 + 2 + + + 89822 + 476087 + 2 + + + 1055423 + 1055424 + 1 + + + + + + + kind + value + + + 12 + + + 1 + 2 + 6 + + + 5 + 33 + 2 + + + 44 + 55 + 2 + + + 61 + 121 + 2 + + + 123 + 137 + 2 + + + 555 + 1703 + 2 + + + 2935 + 3607 + 2 + + + 4405 + 7397 + 2 + + + 9607 + 17996 + 2 + + + 41927 + 41928 + 1 + + + + + + + kind + loc + + + 12 + + + 1 + 34 + 2 + + + 139 + 215 + 2 + + + 444 + 1556 + 2 + + + 1697 + 1698 + 2 + + + 3861 + 3982 + 2 + + + 4065 + 5413 + 2 + + + 7403 + 9303 + 2 + + + 13214 + 16454 + 2 + + + 23074 + 52448 + 2 + + + 52634 + 75797 + 2 + + + 89822 + 476087 + 2 + + + 1055423 + 1055424 + 1 + + + + + + + value + id + + + 12 + + + 1 + 2 + 50427 + + + 2 + 3 + 12515 + + + 3 + 4 + 6013 + + + 4 + 7 + 7139 + + + 7 + 26 + 6436 + + + 26 + 174539 + 2771 + + + + + + + value + kind + + + 12 + + + 1 + 2 + 80971 + + + 2 + 5 + 4330 + + + + + + + value + loc + + + 12 + + + 1 + 2 + 50428 + + + 2 + 3 + 12514 + + + 3 + 4 + 6013 + + + 4 + 7 + 7139 + + + 7 + 26 + 6436 + + + 26 + 174539 + 2771 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1894687 + + + 2 + 3 + 33 + + + + + + + loc + kind + + + 12 + + + 1 + 2 + 1894687 + + + 2 + 3 + 33 + + + + + + + loc + value + + + 12 + + + 1 + 2 + 1894720 + + + + + + + + + ruby_unary_def + 2599 + + + id + 2599 + + + operand + 2599 + + + operator + 5 + + + loc + 2599 + + + + + id + operand + + + 12 + + + 1 + 2 + 2599 + + + + + + + id + operator + + + 12 + + + 1 + 2 + 2599 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 2599 + + + + + + + operand + id + + + 12 + + + 1 + 2 + 2599 + + + + + + + operand + operator + + + 12 + + + 1 + 2 + 2599 + + + + + + + operand + loc + + + 12 + + + 1 + 2 + 2599 + + + + + + + operator + id + + + 12 + + + 10 + 11 + 1 + + + 82 + 83 + 1 + + + 139 + 140 + 1 + + + 554 + 555 + 1 + + + 1814 + 1815 + 1 + + + + + + + operator + operand + + + 12 + + + 10 + 11 + 1 + + + 82 + 83 + 1 + + + 139 + 140 + 1 + + + 554 + 555 + 1 + + + 1814 + 1815 + 1 + + + + + + + operator + loc + + + 12 + + + 10 + 11 + 1 + + + 82 + 83 + 1 + + + 139 + 140 + 1 + + + 554 + 555 + 1 + + + 1814 + 1815 + 1 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 2599 + + + + + + + loc + operand + + + 12 + + + 1 + 2 + 2599 + + + + + + + loc + operator + + + 12 + + + 1 + 2 + 2599 + + + + + + + + + ruby_undef_child + 13 + + + ruby_undef + 13 + + + index + 1 + + + child + 13 + + + + + ruby_undef + index + + + 12 + + + 1 + 2 + 13 + + + + + + + ruby_undef + child + + + 12 + + + 1 + 2 + 13 + + + + + + + index + ruby_undef + + + 12 + + + 13 + 14 + 1 + + + + + + + index + child + + + 12 + + + 13 + 14 + 1 + + + + + + + child + ruby_undef + + + 12 + + + 1 + 2 + 13 + + + + + + + child + index + + + 12 + + + 1 + 2 + 13 + + + + + + + + + ruby_undef_def + 13 + + + id + 13 + + + loc + 13 + + + + + id + loc + + + 12 + + + 1 + 2 + 13 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 13 + + + + + + + + + ruby_unless_alternative + 14 + + + ruby_unless + 14 + + + alternative + 14 + + + + + ruby_unless + alternative + + + 12 + + + 1 + 2 + 14 + + + + + + + alternative + ruby_unless + + + 12 + + + 1 + 2 + 14 + + + + + + + + + ruby_unless_consequence + 471 + + + ruby_unless + 471 + + + consequence + 471 + + + + + ruby_unless + consequence + + + 12 + + + 1 + 2 + 471 + + + + + + + consequence + ruby_unless + + + 12 + + + 1 + 2 + 471 + + + + + + + + + ruby_unless_def + 471 + + + id + 471 + + + condition + 471 + + + loc + 471 + + + + + id + condition + + + 12 + + + 1 + 2 + 471 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 471 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 471 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 471 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 471 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 471 + + + + + + + + + ruby_unless_modifier_def + 1440 + + + id + 1440 + + + body + 1440 + + + condition + 1440 + + + loc + 1440 + + + + + id + body + + + 12 + + + 1 + 2 + 1440 + + + + + + + id + condition + + + 12 + + + 1 + 2 + 1440 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 1440 + + + + + + + body + id + + + 12 + + + 1 + 2 + 1440 + + + + + + + body + condition + + + 12 + + + 1 + 2 + 1440 + + + + + + + body + loc + + + 12 + + + 1 + 2 + 1440 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 1440 + + + + + + + condition + body + + + 12 + + + 1 + 2 + 1440 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 1440 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1440 + + + + + + + loc + body + + + 12 + + + 1 + 2 + 1440 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 1440 + + + + + + + + + ruby_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 + + + + + + + + + ruby_until_modifier_def + 12 + + + id + 12 + + + body + 12 + + + condition + 12 + + + loc + 12 + + + + + id + body + + + 12 + + + 1 + 2 + 12 + + + + + + + id + condition + + + 12 + + + 1 + 2 + 12 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 12 + + + + + + + body + id + + + 12 + + + 1 + 2 + 12 + + + + + + + body + condition + + + 12 + + + 1 + 2 + 12 + + + + + + + body + loc + + + 12 + + + 1 + 2 + 12 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 12 + + + + + + + condition + body + + + 12 + + + 1 + 2 + 12 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 12 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 12 + + + + + + + loc + body + + + 12 + + + 1 + 2 + 12 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 12 + + + + + + + + + ruby_when_body + 1010 + + + ruby_when + 1010 + + + body + 1010 + + + + + ruby_when + body + + + 12 + + + 1 + 2 + 1010 + + + + + + + body + ruby_when + + + 12 + + + 1 + 2 + 1010 + + + + + + + + + ruby_when_def + 1021 + + + id + 1021 + + + loc + 1021 + + + + + id + loc + + + 12 + + + 1 + 2 + 1021 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 1021 + + + + + + + + + ruby_when_pattern + 1227 + + + ruby_when + 1021 + + + index + 14 + + + pattern + 1227 + + + + + ruby_when + index + + + 12 + + + 1 + 2 + 891 + + + 2 + 3 + 97 + + + 3 + 15 + 31 + + + + + + + ruby_when + pattern + + + 12 + + + 1 + 2 + 891 + + + 2 + 3 + 97 + + + 3 + 15 + 31 + + + + + + + index + ruby_when + + + 12 + + + 2 + 3 + 4 + + + 3 + 4 + 4 + + + 6 + 7 + 1 + + + 7 + 8 + 1 + + + 12 + 13 + 1 + + + 31 + 32 + 1 + + + 126 + 127 + 1 + + + 997 + 998 + 1 + + + + + + + index + pattern + + + 12 + + + 2 + 3 + 4 + + + 3 + 4 + 4 + + + 6 + 7 + 1 + + + 7 + 8 + 1 + + + 12 + 13 + 1 + + + 31 + 32 + 1 + + + 126 + 127 + 1 + + + 997 + 998 + 1 + + + + + + + pattern + ruby_when + + + 12 + + + 1 + 2 + 1227 + + + + + + + pattern + index + + + 12 + + + 1 + 2 + 1227 + + + + + + + + + ruby_while_def + 109 + + + id + 109 + + + body + 109 + + + condition + 109 + + + loc + 109 + + + + + id + body + + + 12 + + + 1 + 2 + 109 + + + + + + + id + condition + + + 12 + + + 1 + 2 + 109 + + + + + + + id + loc + + + 12 + + + 1 + 2 + 109 + + + + + + + body + id + + + 12 + + + 1 + 2 + 109 + + + + + + + body + condition + + + 12 + + + 1 + 2 + 109 + + + + + + + body + loc + + + 12 + + + 1 + 2 + 109 + + + + + + + condition + id + + + 12 + + + 1 + 2 + 109 + + + + + + + condition + body + + + 12 + + + 1 + 2 + 109 + + + + + + + condition + loc + + + 12 + + + 1 + 2 + 109 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 109 + + + + + + + loc + body + + + 12 + + + 1 + 2 + 109 + + + + + + + loc + condition + + + 12 + + + 1 + 2 + 109 + + + + + + + + + ruby_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 + + + + + + + + + ruby_yield_child + 359 + + + ruby_yield + 359 + + + child + 359 + + + + + ruby_yield + child + + + 12 + + + 1 + 2 + 359 + + + + + + + child + ruby_yield + + + 12 + + + 1 + 2 + 359 + + + + + + + + + ruby_yield_def + 772 + + + id + 772 + + + loc + 772 + + + + + id + loc + + + 12 + + + 1 + 2 + 772 + + + + + + + loc + id + + + 12 + + + 1 + 2 + 772 + + + + + + + + + sourceLocationPrefix + 3 + + + prefix + 3 + + + + + + diff --git a/ruby/ql/lib/ruby.qll b/ruby/ql/lib/ruby.qll new file mode 100644 index 000000000000..18468c9f8cfb --- /dev/null +++ b/ruby/ql/lib/ruby.qll @@ -0,0 +1 @@ +import codeql.ruby.AST diff --git a/ruby/ql/lib/upgrades/09a494ce67d8141f28d6411f89b9ff7bdad440f3/old.dbscheme b/ruby/ql/lib/upgrades/09a494ce67d8141f28d6411f89b9ff7bdad440f3/old.dbscheme new file mode 100644 index 000000000000..09a494ce67d8 --- /dev/null +++ b/ruby/ql/lib/upgrades/09a494ce67d8141f28d6411f89b9ff7bdad440f3/old.dbscheme @@ -0,0 +1,1333 @@ +// CodeQL database schema for Ruby +// 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 +); + +@sourceline = @file + +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref +); + +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref +); + +folders( + unique int id: @folder, + string name: string ref, + string simple: 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 +; + + +@ruby_underscore_arg = @ruby_assignment | @ruby_binary | @ruby_conditional | @ruby_operator_assignment | @ruby_range | @ruby_unary | @ruby_underscore_primary + +@ruby_underscore_lhs = @ruby_call | @ruby_element_reference | @ruby_scope_resolution | @ruby_token_false | @ruby_token_nil | @ruby_token_true | @ruby_underscore_variable + +@ruby_underscore_method_name = @ruby_delimited_symbol | @ruby_setter | @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_operator | @ruby_token_simple_symbol + +@ruby_underscore_primary = @ruby_array | @ruby_begin | @ruby_break | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_delimited_symbol | @ruby_for | @ruby_hash | @ruby_if | @ruby_lambda | @ruby_method | @ruby_module | @ruby_next | @ruby_parenthesized_statements | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_retry | @ruby_return | @ruby_singleton_class | @ruby_singleton_method | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_symbol_array | @ruby_token_character | @ruby_token_complex | @ruby_token_float | @ruby_token_heredoc_beginning | @ruby_token_integer | @ruby_token_simple_symbol | @ruby_unary | @ruby_underscore_lhs | @ruby_unless | @ruby_until | @ruby_while | @ruby_yield + +@ruby_underscore_statement = @ruby_alias | @ruby_assignment | @ruby_begin_block | @ruby_binary | @ruby_break | @ruby_call | @ruby_end_block | @ruby_if_modifier | @ruby_next | @ruby_operator_assignment | @ruby_rescue_modifier | @ruby_return | @ruby_unary | @ruby_undef | @ruby_underscore_arg | @ruby_unless_modifier | @ruby_until_modifier | @ruby_while_modifier | @ruby_yield + +@ruby_underscore_variable = @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_self | @ruby_token_super + +ruby_alias_def( + unique int id: @ruby_alias, + int alias: @ruby_underscore_method_name ref, + int name: @ruby_underscore_method_name ref, + int loc: @location ref +); + +@ruby_argument_list_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_argument_list, index] +ruby_argument_list_child( + int ruby_argument_list: @ruby_argument_list ref, + int index: int ref, + unique int child: @ruby_argument_list_child_type ref +); + +ruby_argument_list_def( + unique int id: @ruby_argument_list, + int loc: @location ref +); + +@ruby_array_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_array, index] +ruby_array_child( + int ruby_array: @ruby_array ref, + int index: int ref, + unique int child: @ruby_array_child_type ref +); + +ruby_array_def( + unique int id: @ruby_array, + int loc: @location ref +); + +@ruby_assignment_left_type = @ruby_left_assignment_list | @ruby_underscore_lhs + +@ruby_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_right_assignment_list | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +ruby_assignment_def( + unique int id: @ruby_assignment, + int left: @ruby_assignment_left_type ref, + int right: @ruby_assignment_right_type ref, + int loc: @location ref +); + +@ruby_bare_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_bare_string, index] +ruby_bare_string_child( + int ruby_bare_string: @ruby_bare_string ref, + int index: int ref, + unique int child: @ruby_bare_string_child_type ref +); + +ruby_bare_string_def( + unique int id: @ruby_bare_string, + int loc: @location ref +); + +@ruby_bare_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_bare_symbol, index] +ruby_bare_symbol_child( + int ruby_bare_symbol: @ruby_bare_symbol ref, + int index: int ref, + unique int child: @ruby_bare_symbol_child_type ref +); + +ruby_bare_symbol_def( + unique int id: @ruby_bare_symbol, + int loc: @location ref +); + +@ruby_begin_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_begin, index] +ruby_begin_child( + int ruby_begin: @ruby_begin ref, + int index: int ref, + unique int child: @ruby_begin_child_type ref +); + +ruby_begin_def( + unique int id: @ruby_begin, + int loc: @location ref +); + +@ruby_begin_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_begin_block, index] +ruby_begin_block_child( + int ruby_begin_block: @ruby_begin_block ref, + int index: int ref, + unique int child: @ruby_begin_block_child_type ref +); + +ruby_begin_block_def( + unique int id: @ruby_begin_block, + int loc: @location ref +); + +@ruby_binary_left_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +case @ruby_binary.operator of + 0 = @ruby_binary_bangequal +| 1 = @ruby_binary_bangtilde +| 2 = @ruby_binary_percent +| 3 = @ruby_binary_ampersand +| 4 = @ruby_binary_ampersandampersand +| 5 = @ruby_binary_star +| 6 = @ruby_binary_starstar +| 7 = @ruby_binary_plus +| 8 = @ruby_binary_minus +| 9 = @ruby_binary_slash +| 10 = @ruby_binary_langle +| 11 = @ruby_binary_langlelangle +| 12 = @ruby_binary_langleequal +| 13 = @ruby_binary_langleequalrangle +| 14 = @ruby_binary_equalequal +| 15 = @ruby_binary_equalequalequal +| 16 = @ruby_binary_equaltilde +| 17 = @ruby_binary_rangle +| 18 = @ruby_binary_rangleequal +| 19 = @ruby_binary_ranglerangle +| 20 = @ruby_binary_caret +| 21 = @ruby_binary_and +| 22 = @ruby_binary_or +| 23 = @ruby_binary_pipe +| 24 = @ruby_binary_pipepipe +; + + +@ruby_binary_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_binary_def( + unique int id: @ruby_binary, + int left: @ruby_binary_left_type ref, + int operator: int ref, + int right: @ruby_binary_right_type ref, + int loc: @location ref +); + +ruby_block_parameters( + unique int ruby_block: @ruby_block ref, + unique int parameters: @ruby_block_parameters ref +); + +@ruby_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_block, index] +ruby_block_child( + int ruby_block: @ruby_block ref, + int index: int ref, + unique int child: @ruby_block_child_type ref +); + +ruby_block_def( + unique int id: @ruby_block, + int loc: @location ref +); + +ruby_block_argument_def( + unique int id: @ruby_block_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_block_parameter_def( + unique int id: @ruby_block_parameter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_block_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_block_parameters, index] +ruby_block_parameters_child( + int ruby_block_parameters: @ruby_block_parameters ref, + int index: int ref, + unique int child: @ruby_block_parameters_child_type ref +); + +ruby_block_parameters_def( + unique int id: @ruby_block_parameters, + int loc: @location ref +); + +ruby_break_child( + unique int ruby_break: @ruby_break ref, + unique int child: @ruby_argument_list ref +); + +ruby_break_def( + unique int id: @ruby_break, + int loc: @location ref +); + +ruby_call_arguments( + unique int ruby_call: @ruby_call ref, + unique int arguments: @ruby_argument_list ref +); + +@ruby_call_block_type = @ruby_block | @ruby_do_block + +ruby_call_block( + unique int ruby_call: @ruby_call ref, + unique int block: @ruby_call_block_type ref +); + +@ruby_call_method_type = @ruby_argument_list | @ruby_scope_resolution | @ruby_token_operator | @ruby_underscore_variable + +@ruby_call_receiver_type = @ruby_call | @ruby_underscore_primary + +ruby_call_receiver( + unique int ruby_call: @ruby_call ref, + unique int receiver: @ruby_call_receiver_type ref +); + +ruby_call_def( + unique int id: @ruby_call, + int method: @ruby_call_method_type ref, + int loc: @location ref +); + +ruby_case_value( + unique int ruby_case__: @ruby_case__ ref, + unique int value: @ruby_underscore_statement ref +); + +@ruby_case_child_type = @ruby_else | @ruby_when + +#keyset[ruby_case__, index] +ruby_case_child( + int ruby_case__: @ruby_case__ ref, + int index: int ref, + unique int child: @ruby_case_child_type ref +); + +ruby_case_def( + unique int id: @ruby_case__, + int loc: @location ref +); + +#keyset[ruby_chained_string, index] +ruby_chained_string_child( + int ruby_chained_string: @ruby_chained_string ref, + int index: int ref, + unique int child: @ruby_string__ ref +); + +ruby_chained_string_def( + unique int id: @ruby_chained_string, + int loc: @location ref +); + +@ruby_class_name_type = @ruby_scope_resolution | @ruby_token_constant + +ruby_class_superclass( + unique int ruby_class: @ruby_class ref, + unique int superclass: @ruby_superclass ref +); + +@ruby_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_class, index] +ruby_class_child( + int ruby_class: @ruby_class ref, + int index: int ref, + unique int child: @ruby_class_child_type ref +); + +ruby_class_def( + unique int id: @ruby_class, + int name: @ruby_class_name_type ref, + int loc: @location ref +); + +ruby_conditional_def( + unique int id: @ruby_conditional, + int alternative: @ruby_underscore_arg ref, + int condition: @ruby_underscore_arg ref, + int consequence: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_delimited_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_delimited_symbol, index] +ruby_delimited_symbol_child( + int ruby_delimited_symbol: @ruby_delimited_symbol ref, + int index: int ref, + unique int child: @ruby_delimited_symbol_child_type ref +); + +ruby_delimited_symbol_def( + unique int id: @ruby_delimited_symbol, + int loc: @location ref +); + +@ruby_destructured_left_assignment_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs + +#keyset[ruby_destructured_left_assignment, index] +ruby_destructured_left_assignment_child( + int ruby_destructured_left_assignment: @ruby_destructured_left_assignment ref, + int index: int ref, + unique int child: @ruby_destructured_left_assignment_child_type ref +); + +ruby_destructured_left_assignment_def( + unique int id: @ruby_destructured_left_assignment, + int loc: @location ref +); + +@ruby_destructured_parameter_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_destructured_parameter, index] +ruby_destructured_parameter_child( + int ruby_destructured_parameter: @ruby_destructured_parameter ref, + int index: int ref, + unique int child: @ruby_destructured_parameter_child_type ref +); + +ruby_destructured_parameter_def( + unique int id: @ruby_destructured_parameter, + int loc: @location ref +); + +@ruby_do_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_do, index] +ruby_do_child( + int ruby_do: @ruby_do ref, + int index: int ref, + unique int child: @ruby_do_child_type ref +); + +ruby_do_def( + unique int id: @ruby_do, + int loc: @location ref +); + +ruby_do_block_parameters( + unique int ruby_do_block: @ruby_do_block ref, + unique int parameters: @ruby_block_parameters ref +); + +@ruby_do_block_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_do_block, index] +ruby_do_block_child( + int ruby_do_block: @ruby_do_block ref, + int index: int ref, + unique int child: @ruby_do_block_child_type ref +); + +ruby_do_block_def( + unique int id: @ruby_do_block, + int loc: @location ref +); + +@ruby_element_reference_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_element_reference, index] +ruby_element_reference_child( + int ruby_element_reference: @ruby_element_reference ref, + int index: int ref, + unique int child: @ruby_element_reference_child_type ref +); + +ruby_element_reference_def( + unique int id: @ruby_element_reference, + int object: @ruby_underscore_primary ref, + int loc: @location ref +); + +@ruby_else_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_else, index] +ruby_else_child( + int ruby_else: @ruby_else ref, + int index: int ref, + unique int child: @ruby_else_child_type ref +); + +ruby_else_def( + unique int id: @ruby_else, + int loc: @location ref +); + +@ruby_elsif_alternative_type = @ruby_else | @ruby_elsif + +ruby_elsif_alternative( + unique int ruby_elsif: @ruby_elsif ref, + unique int alternative: @ruby_elsif_alternative_type ref +); + +ruby_elsif_consequence( + unique int ruby_elsif: @ruby_elsif ref, + unique int consequence: @ruby_then ref +); + +ruby_elsif_def( + unique int id: @ruby_elsif, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_end_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_end_block, index] +ruby_end_block_child( + int ruby_end_block: @ruby_end_block ref, + int index: int ref, + unique int child: @ruby_end_block_child_type ref +); + +ruby_end_block_def( + unique int id: @ruby_end_block, + int loc: @location ref +); + +@ruby_ensure_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_ensure, index] +ruby_ensure_child( + int ruby_ensure: @ruby_ensure ref, + int index: int ref, + unique int child: @ruby_ensure_child_type ref +); + +ruby_ensure_def( + unique int id: @ruby_ensure, + int loc: @location ref +); + +ruby_exception_variable_def( + unique int id: @ruby_exception_variable, + int child: @ruby_underscore_lhs ref, + int loc: @location ref +); + +@ruby_exceptions_child_type = @ruby_splat_argument | @ruby_underscore_arg + +#keyset[ruby_exceptions, index] +ruby_exceptions_child( + int ruby_exceptions: @ruby_exceptions ref, + int index: int ref, + unique int child: @ruby_exceptions_child_type ref +); + +ruby_exceptions_def( + unique int id: @ruby_exceptions, + int loc: @location ref +); + +@ruby_for_pattern_type = @ruby_left_assignment_list | @ruby_underscore_lhs + +ruby_for_def( + unique int id: @ruby_for, + int body: @ruby_do ref, + int pattern: @ruby_for_pattern_type ref, + int value: @ruby_in ref, + int loc: @location ref +); + +@ruby_hash_child_type = @ruby_hash_splat_argument | @ruby_pair + +#keyset[ruby_hash, index] +ruby_hash_child( + int ruby_hash: @ruby_hash ref, + int index: int ref, + unique int child: @ruby_hash_child_type ref +); + +ruby_hash_def( + unique int id: @ruby_hash, + int loc: @location ref +); + +ruby_hash_splat_argument_def( + unique int id: @ruby_hash_splat_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_hash_splat_parameter_name( + unique int ruby_hash_splat_parameter: @ruby_hash_splat_parameter ref, + unique int name: @ruby_token_identifier ref +); + +ruby_hash_splat_parameter_def( + unique int id: @ruby_hash_splat_parameter, + int loc: @location ref +); + +@ruby_heredoc_body_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_heredoc_content | @ruby_token_heredoc_end + +#keyset[ruby_heredoc_body, index] +ruby_heredoc_body_child( + int ruby_heredoc_body: @ruby_heredoc_body ref, + int index: int ref, + unique int child: @ruby_heredoc_body_child_type ref +); + +ruby_heredoc_body_def( + unique int id: @ruby_heredoc_body, + int loc: @location ref +); + +@ruby_if_alternative_type = @ruby_else | @ruby_elsif + +ruby_if_alternative( + unique int ruby_if: @ruby_if ref, + unique int alternative: @ruby_if_alternative_type ref +); + +ruby_if_consequence( + unique int ruby_if: @ruby_if ref, + unique int consequence: @ruby_then ref +); + +ruby_if_def( + unique int id: @ruby_if, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_if_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_if_modifier_def( + unique int id: @ruby_if_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_if_modifier_condition_type ref, + int loc: @location ref +); + +ruby_in_def( + unique int id: @ruby_in, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_interpolation_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_interpolation, index] +ruby_interpolation_child( + int ruby_interpolation: @ruby_interpolation ref, + int index: int ref, + unique int child: @ruby_interpolation_child_type ref +); + +ruby_interpolation_def( + unique int id: @ruby_interpolation, + int loc: @location ref +); + +ruby_keyword_parameter_value( + unique int ruby_keyword_parameter: @ruby_keyword_parameter ref, + unique int value: @ruby_underscore_arg ref +); + +ruby_keyword_parameter_def( + unique int id: @ruby_keyword_parameter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_lambda_body_type = @ruby_block | @ruby_do_block + +ruby_lambda_parameters( + unique int ruby_lambda: @ruby_lambda ref, + unique int parameters: @ruby_lambda_parameters ref +); + +ruby_lambda_def( + unique int id: @ruby_lambda, + int body: @ruby_lambda_body_type ref, + int loc: @location ref +); + +@ruby_lambda_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_lambda_parameters, index] +ruby_lambda_parameters_child( + int ruby_lambda_parameters: @ruby_lambda_parameters ref, + int index: int ref, + unique int child: @ruby_lambda_parameters_child_type ref +); + +ruby_lambda_parameters_def( + unique int id: @ruby_lambda_parameters, + int loc: @location ref +); + +@ruby_left_assignment_list_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs + +#keyset[ruby_left_assignment_list, index] +ruby_left_assignment_list_child( + int ruby_left_assignment_list: @ruby_left_assignment_list ref, + int index: int ref, + unique int child: @ruby_left_assignment_list_child_type ref +); + +ruby_left_assignment_list_def( + unique int id: @ruby_left_assignment_list, + int loc: @location ref +); + +ruby_method_parameters( + unique int ruby_method: @ruby_method ref, + unique int parameters: @ruby_method_parameters ref +); + +@ruby_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_method, index] +ruby_method_child( + int ruby_method: @ruby_method ref, + int index: int ref, + unique int child: @ruby_method_child_type ref +); + +ruby_method_def( + unique int id: @ruby_method, + int name: @ruby_underscore_method_name ref, + int loc: @location ref +); + +@ruby_method_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_method_parameters, index] +ruby_method_parameters_child( + int ruby_method_parameters: @ruby_method_parameters ref, + int index: int ref, + unique int child: @ruby_method_parameters_child_type ref +); + +ruby_method_parameters_def( + unique int id: @ruby_method_parameters, + int loc: @location ref +); + +@ruby_module_name_type = @ruby_scope_resolution | @ruby_token_constant + +@ruby_module_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_module, index] +ruby_module_child( + int ruby_module: @ruby_module ref, + int index: int ref, + unique int child: @ruby_module_child_type ref +); + +ruby_module_def( + unique int id: @ruby_module, + int name: @ruby_module_name_type ref, + int loc: @location ref +); + +ruby_next_child( + unique int ruby_next: @ruby_next ref, + unique int child: @ruby_argument_list ref +); + +ruby_next_def( + unique int id: @ruby_next, + int loc: @location ref +); + +case @ruby_operator_assignment.operator of + 0 = @ruby_operator_assignment_percentequal +| 1 = @ruby_operator_assignment_ampersandampersandequal +| 2 = @ruby_operator_assignment_ampersandequal +| 3 = @ruby_operator_assignment_starstarequal +| 4 = @ruby_operator_assignment_starequal +| 5 = @ruby_operator_assignment_plusequal +| 6 = @ruby_operator_assignment_minusequal +| 7 = @ruby_operator_assignment_slashequal +| 8 = @ruby_operator_assignment_langlelangleequal +| 9 = @ruby_operator_assignment_ranglerangleequal +| 10 = @ruby_operator_assignment_caretequal +| 11 = @ruby_operator_assignment_pipeequal +| 12 = @ruby_operator_assignment_pipepipeequal +; + + +@ruby_operator_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_operator_assignment_def( + unique int id: @ruby_operator_assignment, + int left: @ruby_underscore_lhs ref, + int operator: int ref, + int right: @ruby_operator_assignment_right_type ref, + int loc: @location ref +); + +ruby_optional_parameter_def( + unique int id: @ruby_optional_parameter, + int name: @ruby_token_identifier ref, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_pair_key_type = @ruby_string__ | @ruby_token_hash_key_symbol | @ruby_underscore_arg + +ruby_pair_def( + unique int id: @ruby_pair, + int key__: @ruby_pair_key_type ref, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_parenthesized_statements_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_parenthesized_statements, index] +ruby_parenthesized_statements_child( + int ruby_parenthesized_statements: @ruby_parenthesized_statements ref, + int index: int ref, + unique int child: @ruby_parenthesized_statements_child_type ref +); + +ruby_parenthesized_statements_def( + unique int id: @ruby_parenthesized_statements, + int loc: @location ref +); + +@ruby_pattern_child_type = @ruby_splat_argument | @ruby_underscore_arg + +ruby_pattern_def( + unique int id: @ruby_pattern, + int child: @ruby_pattern_child_type ref, + int loc: @location ref +); + +@ruby_program_child_type = @ruby_token_empty_statement | @ruby_token_uninterpreted | @ruby_underscore_statement + +#keyset[ruby_program, index] +ruby_program_child( + int ruby_program: @ruby_program ref, + int index: int ref, + unique int child: @ruby_program_child_type ref +); + +ruby_program_def( + unique int id: @ruby_program, + int loc: @location ref +); + +ruby_range_begin( + unique int ruby_range: @ruby_range ref, + unique int begin: @ruby_underscore_arg ref +); + +ruby_range_end( + unique int ruby_range: @ruby_range ref, + unique int end: @ruby_underscore_arg ref +); + +case @ruby_range.operator of + 0 = @ruby_range_dotdot +| 1 = @ruby_range_dotdotdot +; + + +ruby_range_def( + unique int id: @ruby_range, + int operator: int ref, + int loc: @location ref +); + +@ruby_rational_child_type = @ruby_token_float | @ruby_token_integer + +ruby_rational_def( + unique int id: @ruby_rational, + int child: @ruby_rational_child_type ref, + int loc: @location ref +); + +ruby_redo_child( + unique int ruby_redo: @ruby_redo ref, + unique int child: @ruby_argument_list ref +); + +ruby_redo_def( + unique int id: @ruby_redo, + int loc: @location ref +); + +@ruby_regex_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_regex, index] +ruby_regex_child( + int ruby_regex: @ruby_regex ref, + int index: int ref, + unique int child: @ruby_regex_child_type ref +); + +ruby_regex_def( + unique int id: @ruby_regex, + int loc: @location ref +); + +ruby_rescue_body( + unique int ruby_rescue: @ruby_rescue ref, + unique int body: @ruby_then ref +); + +ruby_rescue_exceptions( + unique int ruby_rescue: @ruby_rescue ref, + unique int exceptions: @ruby_exceptions ref +); + +ruby_rescue_variable( + unique int ruby_rescue: @ruby_rescue ref, + unique int variable: @ruby_exception_variable ref +); + +ruby_rescue_def( + unique int id: @ruby_rescue, + int loc: @location ref +); + +@ruby_rescue_modifier_handler_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_rescue_modifier_def( + unique int id: @ruby_rescue_modifier, + int body: @ruby_underscore_statement ref, + int handler: @ruby_rescue_modifier_handler_type ref, + int loc: @location ref +); + +ruby_rest_assignment_child( + unique int ruby_rest_assignment: @ruby_rest_assignment ref, + unique int child: @ruby_underscore_lhs ref +); + +ruby_rest_assignment_def( + unique int id: @ruby_rest_assignment, + int loc: @location ref +); + +ruby_retry_child( + unique int ruby_retry: @ruby_retry ref, + unique int child: @ruby_argument_list ref +); + +ruby_retry_def( + unique int id: @ruby_retry, + int loc: @location ref +); + +ruby_return_child( + unique int ruby_return: @ruby_return ref, + unique int child: @ruby_argument_list ref +); + +ruby_return_def( + unique int id: @ruby_return, + int loc: @location ref +); + +@ruby_right_assignment_list_child_type = @ruby_splat_argument | @ruby_underscore_arg + +#keyset[ruby_right_assignment_list, index] +ruby_right_assignment_list_child( + int ruby_right_assignment_list: @ruby_right_assignment_list ref, + int index: int ref, + unique int child: @ruby_right_assignment_list_child_type ref +); + +ruby_right_assignment_list_def( + unique int id: @ruby_right_assignment_list, + int loc: @location ref +); + +@ruby_scope_resolution_name_type = @ruby_token_constant | @ruby_token_identifier + +ruby_scope_resolution_scope( + unique int ruby_scope_resolution: @ruby_scope_resolution ref, + unique int scope: @ruby_underscore_primary ref +); + +ruby_scope_resolution_def( + unique int id: @ruby_scope_resolution, + int name: @ruby_scope_resolution_name_type ref, + int loc: @location ref +); + +ruby_setter_def( + unique int id: @ruby_setter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_singleton_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_singleton_class, index] +ruby_singleton_class_child( + int ruby_singleton_class: @ruby_singleton_class ref, + int index: int ref, + unique int child: @ruby_singleton_class_child_type ref +); + +ruby_singleton_class_def( + unique int id: @ruby_singleton_class, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_singleton_method_object_type = @ruby_underscore_arg | @ruby_underscore_variable + +ruby_singleton_method_parameters( + unique int ruby_singleton_method: @ruby_singleton_method ref, + unique int parameters: @ruby_method_parameters ref +); + +@ruby_singleton_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_singleton_method, index] +ruby_singleton_method_child( + int ruby_singleton_method: @ruby_singleton_method ref, + int index: int ref, + unique int child: @ruby_singleton_method_child_type ref +); + +ruby_singleton_method_def( + unique int id: @ruby_singleton_method, + int name: @ruby_underscore_method_name ref, + int object: @ruby_singleton_method_object_type ref, + int loc: @location ref +); + +ruby_splat_argument_def( + unique int id: @ruby_splat_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_splat_parameter_name( + unique int ruby_splat_parameter: @ruby_splat_parameter ref, + unique int name: @ruby_token_identifier ref +); + +ruby_splat_parameter_def( + unique int id: @ruby_splat_parameter, + int loc: @location ref +); + +@ruby_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_string__, index] +ruby_string_child( + int ruby_string__: @ruby_string__ ref, + int index: int ref, + unique int child: @ruby_string_child_type ref +); + +ruby_string_def( + unique int id: @ruby_string__, + int loc: @location ref +); + +#keyset[ruby_string_array, index] +ruby_string_array_child( + int ruby_string_array: @ruby_string_array ref, + int index: int ref, + unique int child: @ruby_bare_string ref +); + +ruby_string_array_def( + unique int id: @ruby_string_array, + int loc: @location ref +); + +@ruby_subshell_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_subshell, index] +ruby_subshell_child( + int ruby_subshell: @ruby_subshell ref, + int index: int ref, + unique int child: @ruby_subshell_child_type ref +); + +ruby_subshell_def( + unique int id: @ruby_subshell, + int loc: @location ref +); + +@ruby_superclass_child_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_superclass_def( + unique int id: @ruby_superclass, + int child: @ruby_superclass_child_type ref, + int loc: @location ref +); + +#keyset[ruby_symbol_array, index] +ruby_symbol_array_child( + int ruby_symbol_array: @ruby_symbol_array ref, + int index: int ref, + unique int child: @ruby_bare_symbol ref +); + +ruby_symbol_array_def( + unique int id: @ruby_symbol_array, + int loc: @location ref +); + +@ruby_then_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_then, index] +ruby_then_child( + int ruby_then: @ruby_then ref, + int index: int ref, + unique int child: @ruby_then_child_type ref +); + +ruby_then_def( + unique int id: @ruby_then, + int loc: @location ref +); + +@ruby_unary_operand_type = @ruby_break | @ruby_call | @ruby_next | @ruby_parenthesized_statements | @ruby_return | @ruby_token_float | @ruby_token_integer | @ruby_underscore_arg | @ruby_yield + +case @ruby_unary.operator of + 0 = @ruby_unary_bang +| 1 = @ruby_unary_plus +| 2 = @ruby_unary_minus +| 3 = @ruby_unary_definedquestion +| 4 = @ruby_unary_not +| 5 = @ruby_unary_tilde +; + + +ruby_unary_def( + unique int id: @ruby_unary, + int operand: @ruby_unary_operand_type ref, + int operator: int ref, + int loc: @location ref +); + +#keyset[ruby_undef, index] +ruby_undef_child( + int ruby_undef: @ruby_undef ref, + int index: int ref, + unique int child: @ruby_underscore_method_name ref +); + +ruby_undef_def( + unique int id: @ruby_undef, + int loc: @location ref +); + +@ruby_unless_alternative_type = @ruby_else | @ruby_elsif + +ruby_unless_alternative( + unique int ruby_unless: @ruby_unless ref, + unique int alternative: @ruby_unless_alternative_type ref +); + +ruby_unless_consequence( + unique int ruby_unless: @ruby_unless ref, + unique int consequence: @ruby_then ref +); + +ruby_unless_def( + unique int id: @ruby_unless, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_unless_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_unless_modifier_def( + unique int id: @ruby_unless_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_unless_modifier_condition_type ref, + int loc: @location ref +); + +ruby_until_def( + unique int id: @ruby_until, + int body: @ruby_do ref, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_until_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_until_modifier_def( + unique int id: @ruby_until_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_until_modifier_condition_type ref, + int loc: @location ref +); + +ruby_when_body( + unique int ruby_when: @ruby_when ref, + unique int body: @ruby_then ref +); + +#keyset[ruby_when, index] +ruby_when_pattern( + int ruby_when: @ruby_when ref, + int index: int ref, + unique int pattern: @ruby_pattern ref +); + +ruby_when_def( + unique int id: @ruby_when, + int loc: @location ref +); + +ruby_while_def( + unique int id: @ruby_while, + int body: @ruby_do ref, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_while_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_while_modifier_def( + unique int id: @ruby_while_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_while_modifier_condition_type ref, + int loc: @location ref +); + +ruby_yield_child( + unique int ruby_yield: @ruby_yield ref, + unique int child: @ruby_argument_list ref +); + +ruby_yield_def( + unique int id: @ruby_yield, + int loc: @location ref +); + +ruby_tokeninfo( + unique int id: @ruby_token, + int kind: int ref, + int file: @file ref, + int idx: int ref, + string value: string ref, + int loc: @location ref +); + +case @ruby_token.kind of + 0 = @ruby_reserved_word +| 1 = @ruby_token_character +| 2 = @ruby_token_class_variable +| 3 = @ruby_token_comment +| 4 = @ruby_token_complex +| 5 = @ruby_token_constant +| 6 = @ruby_token_empty_statement +| 7 = @ruby_token_escape_sequence +| 8 = @ruby_token_false +| 9 = @ruby_token_float +| 10 = @ruby_token_global_variable +| 11 = @ruby_token_hash_key_symbol +| 12 = @ruby_token_heredoc_beginning +| 13 = @ruby_token_heredoc_content +| 14 = @ruby_token_heredoc_end +| 15 = @ruby_token_identifier +| 16 = @ruby_token_instance_variable +| 17 = @ruby_token_integer +| 18 = @ruby_token_nil +| 19 = @ruby_token_operator +| 20 = @ruby_token_self +| 21 = @ruby_token_simple_symbol +| 22 = @ruby_token_string_content +| 23 = @ruby_token_super +| 24 = @ruby_token_true +| 25 = @ruby_token_uninterpreted +; + + +@ruby_ast_node = @ruby_alias | @ruby_argument_list | @ruby_array | @ruby_assignment | @ruby_bare_string | @ruby_bare_symbol | @ruby_begin | @ruby_begin_block | @ruby_binary | @ruby_block | @ruby_block_argument | @ruby_block_parameter | @ruby_block_parameters | @ruby_break | @ruby_call | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_conditional | @ruby_delimited_symbol | @ruby_destructured_left_assignment | @ruby_destructured_parameter | @ruby_do | @ruby_do_block | @ruby_element_reference | @ruby_else | @ruby_elsif | @ruby_end_block | @ruby_ensure | @ruby_exception_variable | @ruby_exceptions | @ruby_for | @ruby_hash | @ruby_hash_splat_argument | @ruby_hash_splat_parameter | @ruby_heredoc_body | @ruby_if | @ruby_if_modifier | @ruby_in | @ruby_interpolation | @ruby_keyword_parameter | @ruby_lambda | @ruby_lambda_parameters | @ruby_left_assignment_list | @ruby_method | @ruby_method_parameters | @ruby_module | @ruby_next | @ruby_operator_assignment | @ruby_optional_parameter | @ruby_pair | @ruby_parenthesized_statements | @ruby_pattern | @ruby_program | @ruby_range | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_rescue | @ruby_rescue_modifier | @ruby_rest_assignment | @ruby_retry | @ruby_return | @ruby_right_assignment_list | @ruby_scope_resolution | @ruby_setter | @ruby_singleton_class | @ruby_singleton_method | @ruby_splat_argument | @ruby_splat_parameter | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_superclass | @ruby_symbol_array | @ruby_then | @ruby_token | @ruby_unary | @ruby_undef | @ruby_unless | @ruby_unless_modifier | @ruby_until | @ruby_until_modifier | @ruby_when | @ruby_while | @ruby_while_modifier | @ruby_yield + +@ruby_ast_node_parent = @file | @ruby_ast_node + +#keyset[parent, parent_index] +ruby_ast_node_parent( + int child: @ruby_ast_node ref, + int parent: @ruby_ast_node_parent ref, + int parent_index: int ref +); + +erb_comment_directive_def( + unique int id: @erb_comment_directive, + int child: @erb_token_comment ref, + int loc: @location ref +); + +erb_directive_def( + unique int id: @erb_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +erb_graphql_directive_def( + unique int id: @erb_graphql_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +erb_output_directive_def( + unique int id: @erb_output_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +@erb_template_child_type = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_token_content + +#keyset[erb_template, index] +erb_template_child( + int erb_template: @erb_template ref, + int index: int ref, + unique int child: @erb_template_child_type ref +); + +erb_template_def( + unique int id: @erb_template, + int loc: @location ref +); + +erb_tokeninfo( + unique int id: @erb_token, + int kind: int ref, + int file: @file ref, + int idx: int ref, + string value: string ref, + int loc: @location ref +); + +case @erb_token.kind of + 0 = @erb_reserved_word +| 1 = @erb_token_code +| 2 = @erb_token_comment +| 3 = @erb_token_content +; + + +@erb_ast_node = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_template | @erb_token + +@erb_ast_node_parent = @erb_ast_node | @file + +#keyset[parent, parent_index] +erb_ast_node_parent( + int child: @erb_ast_node ref, + int parent: @erb_ast_node_parent ref, + int parent_index: int ref +); + diff --git a/ruby/ql/lib/upgrades/09a494ce67d8141f28d6411f89b9ff7bdad440f3/ruby.dbscheme b/ruby/ql/lib/upgrades/09a494ce67d8141f28d6411f89b9ff7bdad440f3/ruby.dbscheme new file mode 100644 index 000000000000..30e1075bbdc9 --- /dev/null +++ b/ruby/ql/lib/upgrades/09a494ce67d8141f28d6411f89b9ff7bdad440f3/ruby.dbscheme @@ -0,0 +1,1329 @@ +// CodeQL database schema for Ruby +// 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 +); + +@sourceline = @file + +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref +); + +files( + unique int id: @file, + string name: string ref +); + +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 +; + + +@ruby_underscore_arg = @ruby_assignment | @ruby_binary | @ruby_conditional | @ruby_operator_assignment | @ruby_range | @ruby_unary | @ruby_underscore_primary + +@ruby_underscore_lhs = @ruby_call | @ruby_element_reference | @ruby_scope_resolution | @ruby_token_false | @ruby_token_nil | @ruby_token_true | @ruby_underscore_variable + +@ruby_underscore_method_name = @ruby_delimited_symbol | @ruby_setter | @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_operator | @ruby_token_simple_symbol + +@ruby_underscore_primary = @ruby_array | @ruby_begin | @ruby_break | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_delimited_symbol | @ruby_for | @ruby_hash | @ruby_if | @ruby_lambda | @ruby_method | @ruby_module | @ruby_next | @ruby_parenthesized_statements | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_retry | @ruby_return | @ruby_singleton_class | @ruby_singleton_method | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_symbol_array | @ruby_token_character | @ruby_token_complex | @ruby_token_float | @ruby_token_heredoc_beginning | @ruby_token_integer | @ruby_token_simple_symbol | @ruby_unary | @ruby_underscore_lhs | @ruby_unless | @ruby_until | @ruby_while | @ruby_yield + +@ruby_underscore_statement = @ruby_alias | @ruby_assignment | @ruby_begin_block | @ruby_binary | @ruby_break | @ruby_call | @ruby_end_block | @ruby_if_modifier | @ruby_next | @ruby_operator_assignment | @ruby_rescue_modifier | @ruby_return | @ruby_unary | @ruby_undef | @ruby_underscore_arg | @ruby_unless_modifier | @ruby_until_modifier | @ruby_while_modifier | @ruby_yield + +@ruby_underscore_variable = @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_self | @ruby_token_super + +ruby_alias_def( + unique int id: @ruby_alias, + int alias: @ruby_underscore_method_name ref, + int name: @ruby_underscore_method_name ref, + int loc: @location ref +); + +@ruby_argument_list_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_argument_list, index] +ruby_argument_list_child( + int ruby_argument_list: @ruby_argument_list ref, + int index: int ref, + unique int child: @ruby_argument_list_child_type ref +); + +ruby_argument_list_def( + unique int id: @ruby_argument_list, + int loc: @location ref +); + +@ruby_array_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_array, index] +ruby_array_child( + int ruby_array: @ruby_array ref, + int index: int ref, + unique int child: @ruby_array_child_type ref +); + +ruby_array_def( + unique int id: @ruby_array, + int loc: @location ref +); + +@ruby_assignment_left_type = @ruby_left_assignment_list | @ruby_underscore_lhs + +@ruby_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_right_assignment_list | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +ruby_assignment_def( + unique int id: @ruby_assignment, + int left: @ruby_assignment_left_type ref, + int right: @ruby_assignment_right_type ref, + int loc: @location ref +); + +@ruby_bare_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_bare_string, index] +ruby_bare_string_child( + int ruby_bare_string: @ruby_bare_string ref, + int index: int ref, + unique int child: @ruby_bare_string_child_type ref +); + +ruby_bare_string_def( + unique int id: @ruby_bare_string, + int loc: @location ref +); + +@ruby_bare_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_bare_symbol, index] +ruby_bare_symbol_child( + int ruby_bare_symbol: @ruby_bare_symbol ref, + int index: int ref, + unique int child: @ruby_bare_symbol_child_type ref +); + +ruby_bare_symbol_def( + unique int id: @ruby_bare_symbol, + int loc: @location ref +); + +@ruby_begin_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_begin, index] +ruby_begin_child( + int ruby_begin: @ruby_begin ref, + int index: int ref, + unique int child: @ruby_begin_child_type ref +); + +ruby_begin_def( + unique int id: @ruby_begin, + int loc: @location ref +); + +@ruby_begin_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_begin_block, index] +ruby_begin_block_child( + int ruby_begin_block: @ruby_begin_block ref, + int index: int ref, + unique int child: @ruby_begin_block_child_type ref +); + +ruby_begin_block_def( + unique int id: @ruby_begin_block, + int loc: @location ref +); + +@ruby_binary_left_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +case @ruby_binary.operator of + 0 = @ruby_binary_bangequal +| 1 = @ruby_binary_bangtilde +| 2 = @ruby_binary_percent +| 3 = @ruby_binary_ampersand +| 4 = @ruby_binary_ampersandampersand +| 5 = @ruby_binary_star +| 6 = @ruby_binary_starstar +| 7 = @ruby_binary_plus +| 8 = @ruby_binary_minus +| 9 = @ruby_binary_slash +| 10 = @ruby_binary_langle +| 11 = @ruby_binary_langlelangle +| 12 = @ruby_binary_langleequal +| 13 = @ruby_binary_langleequalrangle +| 14 = @ruby_binary_equalequal +| 15 = @ruby_binary_equalequalequal +| 16 = @ruby_binary_equaltilde +| 17 = @ruby_binary_rangle +| 18 = @ruby_binary_rangleequal +| 19 = @ruby_binary_ranglerangle +| 20 = @ruby_binary_caret +| 21 = @ruby_binary_and +| 22 = @ruby_binary_or +| 23 = @ruby_binary_pipe +| 24 = @ruby_binary_pipepipe +; + + +@ruby_binary_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_binary_def( + unique int id: @ruby_binary, + int left: @ruby_binary_left_type ref, + int operator: int ref, + int right: @ruby_binary_right_type ref, + int loc: @location ref +); + +ruby_block_parameters( + unique int ruby_block: @ruby_block ref, + unique int parameters: @ruby_block_parameters ref +); + +@ruby_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_block, index] +ruby_block_child( + int ruby_block: @ruby_block ref, + int index: int ref, + unique int child: @ruby_block_child_type ref +); + +ruby_block_def( + unique int id: @ruby_block, + int loc: @location ref +); + +ruby_block_argument_def( + unique int id: @ruby_block_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_block_parameter_def( + unique int id: @ruby_block_parameter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_block_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_block_parameters, index] +ruby_block_parameters_child( + int ruby_block_parameters: @ruby_block_parameters ref, + int index: int ref, + unique int child: @ruby_block_parameters_child_type ref +); + +ruby_block_parameters_def( + unique int id: @ruby_block_parameters, + int loc: @location ref +); + +ruby_break_child( + unique int ruby_break: @ruby_break ref, + unique int child: @ruby_argument_list ref +); + +ruby_break_def( + unique int id: @ruby_break, + int loc: @location ref +); + +ruby_call_arguments( + unique int ruby_call: @ruby_call ref, + unique int arguments: @ruby_argument_list ref +); + +@ruby_call_block_type = @ruby_block | @ruby_do_block + +ruby_call_block( + unique int ruby_call: @ruby_call ref, + unique int block: @ruby_call_block_type ref +); + +@ruby_call_method_type = @ruby_argument_list | @ruby_scope_resolution | @ruby_token_operator | @ruby_underscore_variable + +@ruby_call_receiver_type = @ruby_call | @ruby_underscore_primary + +ruby_call_receiver( + unique int ruby_call: @ruby_call ref, + unique int receiver: @ruby_call_receiver_type ref +); + +ruby_call_def( + unique int id: @ruby_call, + int method: @ruby_call_method_type ref, + int loc: @location ref +); + +ruby_case_value( + unique int ruby_case__: @ruby_case__ ref, + unique int value: @ruby_underscore_statement ref +); + +@ruby_case_child_type = @ruby_else | @ruby_when + +#keyset[ruby_case__, index] +ruby_case_child( + int ruby_case__: @ruby_case__ ref, + int index: int ref, + unique int child: @ruby_case_child_type ref +); + +ruby_case_def( + unique int id: @ruby_case__, + int loc: @location ref +); + +#keyset[ruby_chained_string, index] +ruby_chained_string_child( + int ruby_chained_string: @ruby_chained_string ref, + int index: int ref, + unique int child: @ruby_string__ ref +); + +ruby_chained_string_def( + unique int id: @ruby_chained_string, + int loc: @location ref +); + +@ruby_class_name_type = @ruby_scope_resolution | @ruby_token_constant + +ruby_class_superclass( + unique int ruby_class: @ruby_class ref, + unique int superclass: @ruby_superclass ref +); + +@ruby_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_class, index] +ruby_class_child( + int ruby_class: @ruby_class ref, + int index: int ref, + unique int child: @ruby_class_child_type ref +); + +ruby_class_def( + unique int id: @ruby_class, + int name: @ruby_class_name_type ref, + int loc: @location ref +); + +ruby_conditional_def( + unique int id: @ruby_conditional, + int alternative: @ruby_underscore_arg ref, + int condition: @ruby_underscore_arg ref, + int consequence: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_delimited_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_delimited_symbol, index] +ruby_delimited_symbol_child( + int ruby_delimited_symbol: @ruby_delimited_symbol ref, + int index: int ref, + unique int child: @ruby_delimited_symbol_child_type ref +); + +ruby_delimited_symbol_def( + unique int id: @ruby_delimited_symbol, + int loc: @location ref +); + +@ruby_destructured_left_assignment_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs + +#keyset[ruby_destructured_left_assignment, index] +ruby_destructured_left_assignment_child( + int ruby_destructured_left_assignment: @ruby_destructured_left_assignment ref, + int index: int ref, + unique int child: @ruby_destructured_left_assignment_child_type ref +); + +ruby_destructured_left_assignment_def( + unique int id: @ruby_destructured_left_assignment, + int loc: @location ref +); + +@ruby_destructured_parameter_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_destructured_parameter, index] +ruby_destructured_parameter_child( + int ruby_destructured_parameter: @ruby_destructured_parameter ref, + int index: int ref, + unique int child: @ruby_destructured_parameter_child_type ref +); + +ruby_destructured_parameter_def( + unique int id: @ruby_destructured_parameter, + int loc: @location ref +); + +@ruby_do_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_do, index] +ruby_do_child( + int ruby_do: @ruby_do ref, + int index: int ref, + unique int child: @ruby_do_child_type ref +); + +ruby_do_def( + unique int id: @ruby_do, + int loc: @location ref +); + +ruby_do_block_parameters( + unique int ruby_do_block: @ruby_do_block ref, + unique int parameters: @ruby_block_parameters ref +); + +@ruby_do_block_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_do_block, index] +ruby_do_block_child( + int ruby_do_block: @ruby_do_block ref, + int index: int ref, + unique int child: @ruby_do_block_child_type ref +); + +ruby_do_block_def( + unique int id: @ruby_do_block, + int loc: @location ref +); + +@ruby_element_reference_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_element_reference, index] +ruby_element_reference_child( + int ruby_element_reference: @ruby_element_reference ref, + int index: int ref, + unique int child: @ruby_element_reference_child_type ref +); + +ruby_element_reference_def( + unique int id: @ruby_element_reference, + int object: @ruby_underscore_primary ref, + int loc: @location ref +); + +@ruby_else_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_else, index] +ruby_else_child( + int ruby_else: @ruby_else ref, + int index: int ref, + unique int child: @ruby_else_child_type ref +); + +ruby_else_def( + unique int id: @ruby_else, + int loc: @location ref +); + +@ruby_elsif_alternative_type = @ruby_else | @ruby_elsif + +ruby_elsif_alternative( + unique int ruby_elsif: @ruby_elsif ref, + unique int alternative: @ruby_elsif_alternative_type ref +); + +ruby_elsif_consequence( + unique int ruby_elsif: @ruby_elsif ref, + unique int consequence: @ruby_then ref +); + +ruby_elsif_def( + unique int id: @ruby_elsif, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_end_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_end_block, index] +ruby_end_block_child( + int ruby_end_block: @ruby_end_block ref, + int index: int ref, + unique int child: @ruby_end_block_child_type ref +); + +ruby_end_block_def( + unique int id: @ruby_end_block, + int loc: @location ref +); + +@ruby_ensure_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_ensure, index] +ruby_ensure_child( + int ruby_ensure: @ruby_ensure ref, + int index: int ref, + unique int child: @ruby_ensure_child_type ref +); + +ruby_ensure_def( + unique int id: @ruby_ensure, + int loc: @location ref +); + +ruby_exception_variable_def( + unique int id: @ruby_exception_variable, + int child: @ruby_underscore_lhs ref, + int loc: @location ref +); + +@ruby_exceptions_child_type = @ruby_splat_argument | @ruby_underscore_arg + +#keyset[ruby_exceptions, index] +ruby_exceptions_child( + int ruby_exceptions: @ruby_exceptions ref, + int index: int ref, + unique int child: @ruby_exceptions_child_type ref +); + +ruby_exceptions_def( + unique int id: @ruby_exceptions, + int loc: @location ref +); + +@ruby_for_pattern_type = @ruby_left_assignment_list | @ruby_underscore_lhs + +ruby_for_def( + unique int id: @ruby_for, + int body: @ruby_do ref, + int pattern: @ruby_for_pattern_type ref, + int value: @ruby_in ref, + int loc: @location ref +); + +@ruby_hash_child_type = @ruby_hash_splat_argument | @ruby_pair + +#keyset[ruby_hash, index] +ruby_hash_child( + int ruby_hash: @ruby_hash ref, + int index: int ref, + unique int child: @ruby_hash_child_type ref +); + +ruby_hash_def( + unique int id: @ruby_hash, + int loc: @location ref +); + +ruby_hash_splat_argument_def( + unique int id: @ruby_hash_splat_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_hash_splat_parameter_name( + unique int ruby_hash_splat_parameter: @ruby_hash_splat_parameter ref, + unique int name: @ruby_token_identifier ref +); + +ruby_hash_splat_parameter_def( + unique int id: @ruby_hash_splat_parameter, + int loc: @location ref +); + +@ruby_heredoc_body_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_heredoc_content | @ruby_token_heredoc_end + +#keyset[ruby_heredoc_body, index] +ruby_heredoc_body_child( + int ruby_heredoc_body: @ruby_heredoc_body ref, + int index: int ref, + unique int child: @ruby_heredoc_body_child_type ref +); + +ruby_heredoc_body_def( + unique int id: @ruby_heredoc_body, + int loc: @location ref +); + +@ruby_if_alternative_type = @ruby_else | @ruby_elsif + +ruby_if_alternative( + unique int ruby_if: @ruby_if ref, + unique int alternative: @ruby_if_alternative_type ref +); + +ruby_if_consequence( + unique int ruby_if: @ruby_if ref, + unique int consequence: @ruby_then ref +); + +ruby_if_def( + unique int id: @ruby_if, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_if_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_if_modifier_def( + unique int id: @ruby_if_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_if_modifier_condition_type ref, + int loc: @location ref +); + +ruby_in_def( + unique int id: @ruby_in, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_interpolation_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_interpolation, index] +ruby_interpolation_child( + int ruby_interpolation: @ruby_interpolation ref, + int index: int ref, + unique int child: @ruby_interpolation_child_type ref +); + +ruby_interpolation_def( + unique int id: @ruby_interpolation, + int loc: @location ref +); + +ruby_keyword_parameter_value( + unique int ruby_keyword_parameter: @ruby_keyword_parameter ref, + unique int value: @ruby_underscore_arg ref +); + +ruby_keyword_parameter_def( + unique int id: @ruby_keyword_parameter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_lambda_body_type = @ruby_block | @ruby_do_block + +ruby_lambda_parameters( + unique int ruby_lambda: @ruby_lambda ref, + unique int parameters: @ruby_lambda_parameters ref +); + +ruby_lambda_def( + unique int id: @ruby_lambda, + int body: @ruby_lambda_body_type ref, + int loc: @location ref +); + +@ruby_lambda_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_lambda_parameters, index] +ruby_lambda_parameters_child( + int ruby_lambda_parameters: @ruby_lambda_parameters ref, + int index: int ref, + unique int child: @ruby_lambda_parameters_child_type ref +); + +ruby_lambda_parameters_def( + unique int id: @ruby_lambda_parameters, + int loc: @location ref +); + +@ruby_left_assignment_list_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs + +#keyset[ruby_left_assignment_list, index] +ruby_left_assignment_list_child( + int ruby_left_assignment_list: @ruby_left_assignment_list ref, + int index: int ref, + unique int child: @ruby_left_assignment_list_child_type ref +); + +ruby_left_assignment_list_def( + unique int id: @ruby_left_assignment_list, + int loc: @location ref +); + +ruby_method_parameters( + unique int ruby_method: @ruby_method ref, + unique int parameters: @ruby_method_parameters ref +); + +@ruby_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_method, index] +ruby_method_child( + int ruby_method: @ruby_method ref, + int index: int ref, + unique int child: @ruby_method_child_type ref +); + +ruby_method_def( + unique int id: @ruby_method, + int name: @ruby_underscore_method_name ref, + int loc: @location ref +); + +@ruby_method_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_method_parameters, index] +ruby_method_parameters_child( + int ruby_method_parameters: @ruby_method_parameters ref, + int index: int ref, + unique int child: @ruby_method_parameters_child_type ref +); + +ruby_method_parameters_def( + unique int id: @ruby_method_parameters, + int loc: @location ref +); + +@ruby_module_name_type = @ruby_scope_resolution | @ruby_token_constant + +@ruby_module_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_module, index] +ruby_module_child( + int ruby_module: @ruby_module ref, + int index: int ref, + unique int child: @ruby_module_child_type ref +); + +ruby_module_def( + unique int id: @ruby_module, + int name: @ruby_module_name_type ref, + int loc: @location ref +); + +ruby_next_child( + unique int ruby_next: @ruby_next ref, + unique int child: @ruby_argument_list ref +); + +ruby_next_def( + unique int id: @ruby_next, + int loc: @location ref +); + +case @ruby_operator_assignment.operator of + 0 = @ruby_operator_assignment_percentequal +| 1 = @ruby_operator_assignment_ampersandampersandequal +| 2 = @ruby_operator_assignment_ampersandequal +| 3 = @ruby_operator_assignment_starstarequal +| 4 = @ruby_operator_assignment_starequal +| 5 = @ruby_operator_assignment_plusequal +| 6 = @ruby_operator_assignment_minusequal +| 7 = @ruby_operator_assignment_slashequal +| 8 = @ruby_operator_assignment_langlelangleequal +| 9 = @ruby_operator_assignment_ranglerangleequal +| 10 = @ruby_operator_assignment_caretequal +| 11 = @ruby_operator_assignment_pipeequal +| 12 = @ruby_operator_assignment_pipepipeequal +; + + +@ruby_operator_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_operator_assignment_def( + unique int id: @ruby_operator_assignment, + int left: @ruby_underscore_lhs ref, + int operator: int ref, + int right: @ruby_operator_assignment_right_type ref, + int loc: @location ref +); + +ruby_optional_parameter_def( + unique int id: @ruby_optional_parameter, + int name: @ruby_token_identifier ref, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_pair_key_type = @ruby_string__ | @ruby_token_hash_key_symbol | @ruby_underscore_arg + +ruby_pair_def( + unique int id: @ruby_pair, + int key__: @ruby_pair_key_type ref, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_parenthesized_statements_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_parenthesized_statements, index] +ruby_parenthesized_statements_child( + int ruby_parenthesized_statements: @ruby_parenthesized_statements ref, + int index: int ref, + unique int child: @ruby_parenthesized_statements_child_type ref +); + +ruby_parenthesized_statements_def( + unique int id: @ruby_parenthesized_statements, + int loc: @location ref +); + +@ruby_pattern_child_type = @ruby_splat_argument | @ruby_underscore_arg + +ruby_pattern_def( + unique int id: @ruby_pattern, + int child: @ruby_pattern_child_type ref, + int loc: @location ref +); + +@ruby_program_child_type = @ruby_token_empty_statement | @ruby_token_uninterpreted | @ruby_underscore_statement + +#keyset[ruby_program, index] +ruby_program_child( + int ruby_program: @ruby_program ref, + int index: int ref, + unique int child: @ruby_program_child_type ref +); + +ruby_program_def( + unique int id: @ruby_program, + int loc: @location ref +); + +ruby_range_begin( + unique int ruby_range: @ruby_range ref, + unique int begin: @ruby_underscore_arg ref +); + +ruby_range_end( + unique int ruby_range: @ruby_range ref, + unique int end: @ruby_underscore_arg ref +); + +case @ruby_range.operator of + 0 = @ruby_range_dotdot +| 1 = @ruby_range_dotdotdot +; + + +ruby_range_def( + unique int id: @ruby_range, + int operator: int ref, + int loc: @location ref +); + +@ruby_rational_child_type = @ruby_token_float | @ruby_token_integer + +ruby_rational_def( + unique int id: @ruby_rational, + int child: @ruby_rational_child_type ref, + int loc: @location ref +); + +ruby_redo_child( + unique int ruby_redo: @ruby_redo ref, + unique int child: @ruby_argument_list ref +); + +ruby_redo_def( + unique int id: @ruby_redo, + int loc: @location ref +); + +@ruby_regex_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_regex, index] +ruby_regex_child( + int ruby_regex: @ruby_regex ref, + int index: int ref, + unique int child: @ruby_regex_child_type ref +); + +ruby_regex_def( + unique int id: @ruby_regex, + int loc: @location ref +); + +ruby_rescue_body( + unique int ruby_rescue: @ruby_rescue ref, + unique int body: @ruby_then ref +); + +ruby_rescue_exceptions( + unique int ruby_rescue: @ruby_rescue ref, + unique int exceptions: @ruby_exceptions ref +); + +ruby_rescue_variable( + unique int ruby_rescue: @ruby_rescue ref, + unique int variable: @ruby_exception_variable ref +); + +ruby_rescue_def( + unique int id: @ruby_rescue, + int loc: @location ref +); + +@ruby_rescue_modifier_handler_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_rescue_modifier_def( + unique int id: @ruby_rescue_modifier, + int body: @ruby_underscore_statement ref, + int handler: @ruby_rescue_modifier_handler_type ref, + int loc: @location ref +); + +ruby_rest_assignment_child( + unique int ruby_rest_assignment: @ruby_rest_assignment ref, + unique int child: @ruby_underscore_lhs ref +); + +ruby_rest_assignment_def( + unique int id: @ruby_rest_assignment, + int loc: @location ref +); + +ruby_retry_child( + unique int ruby_retry: @ruby_retry ref, + unique int child: @ruby_argument_list ref +); + +ruby_retry_def( + unique int id: @ruby_retry, + int loc: @location ref +); + +ruby_return_child( + unique int ruby_return: @ruby_return ref, + unique int child: @ruby_argument_list ref +); + +ruby_return_def( + unique int id: @ruby_return, + int loc: @location ref +); + +@ruby_right_assignment_list_child_type = @ruby_splat_argument | @ruby_underscore_arg + +#keyset[ruby_right_assignment_list, index] +ruby_right_assignment_list_child( + int ruby_right_assignment_list: @ruby_right_assignment_list ref, + int index: int ref, + unique int child: @ruby_right_assignment_list_child_type ref +); + +ruby_right_assignment_list_def( + unique int id: @ruby_right_assignment_list, + int loc: @location ref +); + +@ruby_scope_resolution_name_type = @ruby_token_constant | @ruby_token_identifier + +ruby_scope_resolution_scope( + unique int ruby_scope_resolution: @ruby_scope_resolution ref, + unique int scope: @ruby_underscore_primary ref +); + +ruby_scope_resolution_def( + unique int id: @ruby_scope_resolution, + int name: @ruby_scope_resolution_name_type ref, + int loc: @location ref +); + +ruby_setter_def( + unique int id: @ruby_setter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_singleton_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_singleton_class, index] +ruby_singleton_class_child( + int ruby_singleton_class: @ruby_singleton_class ref, + int index: int ref, + unique int child: @ruby_singleton_class_child_type ref +); + +ruby_singleton_class_def( + unique int id: @ruby_singleton_class, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_singleton_method_object_type = @ruby_underscore_arg | @ruby_underscore_variable + +ruby_singleton_method_parameters( + unique int ruby_singleton_method: @ruby_singleton_method ref, + unique int parameters: @ruby_method_parameters ref +); + +@ruby_singleton_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_singleton_method, index] +ruby_singleton_method_child( + int ruby_singleton_method: @ruby_singleton_method ref, + int index: int ref, + unique int child: @ruby_singleton_method_child_type ref +); + +ruby_singleton_method_def( + unique int id: @ruby_singleton_method, + int name: @ruby_underscore_method_name ref, + int object: @ruby_singleton_method_object_type ref, + int loc: @location ref +); + +ruby_splat_argument_def( + unique int id: @ruby_splat_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_splat_parameter_name( + unique int ruby_splat_parameter: @ruby_splat_parameter ref, + unique int name: @ruby_token_identifier ref +); + +ruby_splat_parameter_def( + unique int id: @ruby_splat_parameter, + int loc: @location ref +); + +@ruby_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_string__, index] +ruby_string_child( + int ruby_string__: @ruby_string__ ref, + int index: int ref, + unique int child: @ruby_string_child_type ref +); + +ruby_string_def( + unique int id: @ruby_string__, + int loc: @location ref +); + +#keyset[ruby_string_array, index] +ruby_string_array_child( + int ruby_string_array: @ruby_string_array ref, + int index: int ref, + unique int child: @ruby_bare_string ref +); + +ruby_string_array_def( + unique int id: @ruby_string_array, + int loc: @location ref +); + +@ruby_subshell_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_subshell, index] +ruby_subshell_child( + int ruby_subshell: @ruby_subshell ref, + int index: int ref, + unique int child: @ruby_subshell_child_type ref +); + +ruby_subshell_def( + unique int id: @ruby_subshell, + int loc: @location ref +); + +@ruby_superclass_child_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_superclass_def( + unique int id: @ruby_superclass, + int child: @ruby_superclass_child_type ref, + int loc: @location ref +); + +#keyset[ruby_symbol_array, index] +ruby_symbol_array_child( + int ruby_symbol_array: @ruby_symbol_array ref, + int index: int ref, + unique int child: @ruby_bare_symbol ref +); + +ruby_symbol_array_def( + unique int id: @ruby_symbol_array, + int loc: @location ref +); + +@ruby_then_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_then, index] +ruby_then_child( + int ruby_then: @ruby_then ref, + int index: int ref, + unique int child: @ruby_then_child_type ref +); + +ruby_then_def( + unique int id: @ruby_then, + int loc: @location ref +); + +@ruby_unary_operand_type = @ruby_break | @ruby_call | @ruby_next | @ruby_parenthesized_statements | @ruby_return | @ruby_token_float | @ruby_token_integer | @ruby_underscore_arg | @ruby_yield + +case @ruby_unary.operator of + 0 = @ruby_unary_bang +| 1 = @ruby_unary_plus +| 2 = @ruby_unary_minus +| 3 = @ruby_unary_definedquestion +| 4 = @ruby_unary_not +| 5 = @ruby_unary_tilde +; + + +ruby_unary_def( + unique int id: @ruby_unary, + int operand: @ruby_unary_operand_type ref, + int operator: int ref, + int loc: @location ref +); + +#keyset[ruby_undef, index] +ruby_undef_child( + int ruby_undef: @ruby_undef ref, + int index: int ref, + unique int child: @ruby_underscore_method_name ref +); + +ruby_undef_def( + unique int id: @ruby_undef, + int loc: @location ref +); + +@ruby_unless_alternative_type = @ruby_else | @ruby_elsif + +ruby_unless_alternative( + unique int ruby_unless: @ruby_unless ref, + unique int alternative: @ruby_unless_alternative_type ref +); + +ruby_unless_consequence( + unique int ruby_unless: @ruby_unless ref, + unique int consequence: @ruby_then ref +); + +ruby_unless_def( + unique int id: @ruby_unless, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_unless_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_unless_modifier_def( + unique int id: @ruby_unless_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_unless_modifier_condition_type ref, + int loc: @location ref +); + +ruby_until_def( + unique int id: @ruby_until, + int body: @ruby_do ref, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_until_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_until_modifier_def( + unique int id: @ruby_until_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_until_modifier_condition_type ref, + int loc: @location ref +); + +ruby_when_body( + unique int ruby_when: @ruby_when ref, + unique int body: @ruby_then ref +); + +#keyset[ruby_when, index] +ruby_when_pattern( + int ruby_when: @ruby_when ref, + int index: int ref, + unique int pattern: @ruby_pattern ref +); + +ruby_when_def( + unique int id: @ruby_when, + int loc: @location ref +); + +ruby_while_def( + unique int id: @ruby_while, + int body: @ruby_do ref, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_while_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_while_modifier_def( + unique int id: @ruby_while_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_while_modifier_condition_type ref, + int loc: @location ref +); + +ruby_yield_child( + unique int ruby_yield: @ruby_yield ref, + unique int child: @ruby_argument_list ref +); + +ruby_yield_def( + unique int id: @ruby_yield, + int loc: @location ref +); + +ruby_tokeninfo( + unique int id: @ruby_token, + int kind: int ref, + int file: @file ref, + int idx: int ref, + string value: string ref, + int loc: @location ref +); + +case @ruby_token.kind of + 0 = @ruby_reserved_word +| 1 = @ruby_token_character +| 2 = @ruby_token_class_variable +| 3 = @ruby_token_comment +| 4 = @ruby_token_complex +| 5 = @ruby_token_constant +| 6 = @ruby_token_empty_statement +| 7 = @ruby_token_escape_sequence +| 8 = @ruby_token_false +| 9 = @ruby_token_float +| 10 = @ruby_token_global_variable +| 11 = @ruby_token_hash_key_symbol +| 12 = @ruby_token_heredoc_beginning +| 13 = @ruby_token_heredoc_content +| 14 = @ruby_token_heredoc_end +| 15 = @ruby_token_identifier +| 16 = @ruby_token_instance_variable +| 17 = @ruby_token_integer +| 18 = @ruby_token_nil +| 19 = @ruby_token_operator +| 20 = @ruby_token_self +| 21 = @ruby_token_simple_symbol +| 22 = @ruby_token_string_content +| 23 = @ruby_token_super +| 24 = @ruby_token_true +| 25 = @ruby_token_uninterpreted +; + + +@ruby_ast_node = @ruby_alias | @ruby_argument_list | @ruby_array | @ruby_assignment | @ruby_bare_string | @ruby_bare_symbol | @ruby_begin | @ruby_begin_block | @ruby_binary | @ruby_block | @ruby_block_argument | @ruby_block_parameter | @ruby_block_parameters | @ruby_break | @ruby_call | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_conditional | @ruby_delimited_symbol | @ruby_destructured_left_assignment | @ruby_destructured_parameter | @ruby_do | @ruby_do_block | @ruby_element_reference | @ruby_else | @ruby_elsif | @ruby_end_block | @ruby_ensure | @ruby_exception_variable | @ruby_exceptions | @ruby_for | @ruby_hash | @ruby_hash_splat_argument | @ruby_hash_splat_parameter | @ruby_heredoc_body | @ruby_if | @ruby_if_modifier | @ruby_in | @ruby_interpolation | @ruby_keyword_parameter | @ruby_lambda | @ruby_lambda_parameters | @ruby_left_assignment_list | @ruby_method | @ruby_method_parameters | @ruby_module | @ruby_next | @ruby_operator_assignment | @ruby_optional_parameter | @ruby_pair | @ruby_parenthesized_statements | @ruby_pattern | @ruby_program | @ruby_range | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_rescue | @ruby_rescue_modifier | @ruby_rest_assignment | @ruby_retry | @ruby_return | @ruby_right_assignment_list | @ruby_scope_resolution | @ruby_setter | @ruby_singleton_class | @ruby_singleton_method | @ruby_splat_argument | @ruby_splat_parameter | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_superclass | @ruby_symbol_array | @ruby_then | @ruby_token | @ruby_unary | @ruby_undef | @ruby_unless | @ruby_unless_modifier | @ruby_until | @ruby_until_modifier | @ruby_when | @ruby_while | @ruby_while_modifier | @ruby_yield + +@ruby_ast_node_parent = @file | @ruby_ast_node + +#keyset[parent, parent_index] +ruby_ast_node_parent( + int child: @ruby_ast_node ref, + int parent: @ruby_ast_node_parent ref, + int parent_index: int ref +); + +erb_comment_directive_def( + unique int id: @erb_comment_directive, + int child: @erb_token_comment ref, + int loc: @location ref +); + +erb_directive_def( + unique int id: @erb_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +erb_graphql_directive_def( + unique int id: @erb_graphql_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +erb_output_directive_def( + unique int id: @erb_output_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +@erb_template_child_type = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_token_content + +#keyset[erb_template, index] +erb_template_child( + int erb_template: @erb_template ref, + int index: int ref, + unique int child: @erb_template_child_type ref +); + +erb_template_def( + unique int id: @erb_template, + int loc: @location ref +); + +erb_tokeninfo( + unique int id: @erb_token, + int kind: int ref, + int file: @file ref, + int idx: int ref, + string value: string ref, + int loc: @location ref +); + +case @erb_token.kind of + 0 = @erb_reserved_word +| 1 = @erb_token_code +| 2 = @erb_token_comment +| 3 = @erb_token_content +; + + +@erb_ast_node = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_template | @erb_token + +@erb_ast_node_parent = @erb_ast_node | @file + +#keyset[parent, parent_index] +erb_ast_node_parent( + int child: @erb_ast_node ref, + int parent: @erb_ast_node_parent ref, + int parent_index: int ref +); + diff --git a/ruby/ql/lib/upgrades/09a494ce67d8141f28d6411f89b9ff7bdad440f3/upgrade.properties b/ruby/ql/lib/upgrades/09a494ce67d8141f28d6411f89b9ff7bdad440f3/upgrade.properties new file mode 100644 index 000000000000..71f3ee178e5f --- /dev/null +++ b/ruby/ql/lib/upgrades/09a494ce67d8141f28d6411f89b9ff7bdad440f3/upgrade.properties @@ -0,0 +1,4 @@ +description: Removed unused column from the `folders` and `files` relations +compatibility: full +files.rel: reorder files.rel (int id, string name, string simple, string ext, int fromSource) id name +folders.rel: reorder folders.rel (int id, string name, string simple) id name diff --git a/ruby/ql/lib/upgrades/30e1075bbdc9ce935dbe28dc7175489fe8e69a4c/old.dbscheme b/ruby/ql/lib/upgrades/30e1075bbdc9ce935dbe28dc7175489fe8e69a4c/old.dbscheme new file mode 100644 index 000000000000..30e1075bbdc9 --- /dev/null +++ b/ruby/ql/lib/upgrades/30e1075bbdc9ce935dbe28dc7175489fe8e69a4c/old.dbscheme @@ -0,0 +1,1329 @@ +// CodeQL database schema for Ruby +// 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 +); + +@sourceline = @file + +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref +); + +files( + unique int id: @file, + string name: string ref +); + +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 +; + + +@ruby_underscore_arg = @ruby_assignment | @ruby_binary | @ruby_conditional | @ruby_operator_assignment | @ruby_range | @ruby_unary | @ruby_underscore_primary + +@ruby_underscore_lhs = @ruby_call | @ruby_element_reference | @ruby_scope_resolution | @ruby_token_false | @ruby_token_nil | @ruby_token_true | @ruby_underscore_variable + +@ruby_underscore_method_name = @ruby_delimited_symbol | @ruby_setter | @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_operator | @ruby_token_simple_symbol + +@ruby_underscore_primary = @ruby_array | @ruby_begin | @ruby_break | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_delimited_symbol | @ruby_for | @ruby_hash | @ruby_if | @ruby_lambda | @ruby_method | @ruby_module | @ruby_next | @ruby_parenthesized_statements | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_retry | @ruby_return | @ruby_singleton_class | @ruby_singleton_method | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_symbol_array | @ruby_token_character | @ruby_token_complex | @ruby_token_float | @ruby_token_heredoc_beginning | @ruby_token_integer | @ruby_token_simple_symbol | @ruby_unary | @ruby_underscore_lhs | @ruby_unless | @ruby_until | @ruby_while | @ruby_yield + +@ruby_underscore_statement = @ruby_alias | @ruby_assignment | @ruby_begin_block | @ruby_binary | @ruby_break | @ruby_call | @ruby_end_block | @ruby_if_modifier | @ruby_next | @ruby_operator_assignment | @ruby_rescue_modifier | @ruby_return | @ruby_unary | @ruby_undef | @ruby_underscore_arg | @ruby_unless_modifier | @ruby_until_modifier | @ruby_while_modifier | @ruby_yield + +@ruby_underscore_variable = @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_self | @ruby_token_super + +ruby_alias_def( + unique int id: @ruby_alias, + int alias: @ruby_underscore_method_name ref, + int name: @ruby_underscore_method_name ref, + int loc: @location ref +); + +@ruby_argument_list_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_argument_list, index] +ruby_argument_list_child( + int ruby_argument_list: @ruby_argument_list ref, + int index: int ref, + unique int child: @ruby_argument_list_child_type ref +); + +ruby_argument_list_def( + unique int id: @ruby_argument_list, + int loc: @location ref +); + +@ruby_array_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_array, index] +ruby_array_child( + int ruby_array: @ruby_array ref, + int index: int ref, + unique int child: @ruby_array_child_type ref +); + +ruby_array_def( + unique int id: @ruby_array, + int loc: @location ref +); + +@ruby_assignment_left_type = @ruby_left_assignment_list | @ruby_underscore_lhs + +@ruby_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_right_assignment_list | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +ruby_assignment_def( + unique int id: @ruby_assignment, + int left: @ruby_assignment_left_type ref, + int right: @ruby_assignment_right_type ref, + int loc: @location ref +); + +@ruby_bare_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_bare_string, index] +ruby_bare_string_child( + int ruby_bare_string: @ruby_bare_string ref, + int index: int ref, + unique int child: @ruby_bare_string_child_type ref +); + +ruby_bare_string_def( + unique int id: @ruby_bare_string, + int loc: @location ref +); + +@ruby_bare_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_bare_symbol, index] +ruby_bare_symbol_child( + int ruby_bare_symbol: @ruby_bare_symbol ref, + int index: int ref, + unique int child: @ruby_bare_symbol_child_type ref +); + +ruby_bare_symbol_def( + unique int id: @ruby_bare_symbol, + int loc: @location ref +); + +@ruby_begin_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_begin, index] +ruby_begin_child( + int ruby_begin: @ruby_begin ref, + int index: int ref, + unique int child: @ruby_begin_child_type ref +); + +ruby_begin_def( + unique int id: @ruby_begin, + int loc: @location ref +); + +@ruby_begin_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_begin_block, index] +ruby_begin_block_child( + int ruby_begin_block: @ruby_begin_block ref, + int index: int ref, + unique int child: @ruby_begin_block_child_type ref +); + +ruby_begin_block_def( + unique int id: @ruby_begin_block, + int loc: @location ref +); + +@ruby_binary_left_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +case @ruby_binary.operator of + 0 = @ruby_binary_bangequal +| 1 = @ruby_binary_bangtilde +| 2 = @ruby_binary_percent +| 3 = @ruby_binary_ampersand +| 4 = @ruby_binary_ampersandampersand +| 5 = @ruby_binary_star +| 6 = @ruby_binary_starstar +| 7 = @ruby_binary_plus +| 8 = @ruby_binary_minus +| 9 = @ruby_binary_slash +| 10 = @ruby_binary_langle +| 11 = @ruby_binary_langlelangle +| 12 = @ruby_binary_langleequal +| 13 = @ruby_binary_langleequalrangle +| 14 = @ruby_binary_equalequal +| 15 = @ruby_binary_equalequalequal +| 16 = @ruby_binary_equaltilde +| 17 = @ruby_binary_rangle +| 18 = @ruby_binary_rangleequal +| 19 = @ruby_binary_ranglerangle +| 20 = @ruby_binary_caret +| 21 = @ruby_binary_and +| 22 = @ruby_binary_or +| 23 = @ruby_binary_pipe +| 24 = @ruby_binary_pipepipe +; + + +@ruby_binary_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_binary_def( + unique int id: @ruby_binary, + int left: @ruby_binary_left_type ref, + int operator: int ref, + int right: @ruby_binary_right_type ref, + int loc: @location ref +); + +ruby_block_parameters( + unique int ruby_block: @ruby_block ref, + unique int parameters: @ruby_block_parameters ref +); + +@ruby_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_block, index] +ruby_block_child( + int ruby_block: @ruby_block ref, + int index: int ref, + unique int child: @ruby_block_child_type ref +); + +ruby_block_def( + unique int id: @ruby_block, + int loc: @location ref +); + +ruby_block_argument_def( + unique int id: @ruby_block_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_block_parameter_def( + unique int id: @ruby_block_parameter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_block_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_block_parameters, index] +ruby_block_parameters_child( + int ruby_block_parameters: @ruby_block_parameters ref, + int index: int ref, + unique int child: @ruby_block_parameters_child_type ref +); + +ruby_block_parameters_def( + unique int id: @ruby_block_parameters, + int loc: @location ref +); + +ruby_break_child( + unique int ruby_break: @ruby_break ref, + unique int child: @ruby_argument_list ref +); + +ruby_break_def( + unique int id: @ruby_break, + int loc: @location ref +); + +ruby_call_arguments( + unique int ruby_call: @ruby_call ref, + unique int arguments: @ruby_argument_list ref +); + +@ruby_call_block_type = @ruby_block | @ruby_do_block + +ruby_call_block( + unique int ruby_call: @ruby_call ref, + unique int block: @ruby_call_block_type ref +); + +@ruby_call_method_type = @ruby_argument_list | @ruby_scope_resolution | @ruby_token_operator | @ruby_underscore_variable + +@ruby_call_receiver_type = @ruby_call | @ruby_underscore_primary + +ruby_call_receiver( + unique int ruby_call: @ruby_call ref, + unique int receiver: @ruby_call_receiver_type ref +); + +ruby_call_def( + unique int id: @ruby_call, + int method: @ruby_call_method_type ref, + int loc: @location ref +); + +ruby_case_value( + unique int ruby_case__: @ruby_case__ ref, + unique int value: @ruby_underscore_statement ref +); + +@ruby_case_child_type = @ruby_else | @ruby_when + +#keyset[ruby_case__, index] +ruby_case_child( + int ruby_case__: @ruby_case__ ref, + int index: int ref, + unique int child: @ruby_case_child_type ref +); + +ruby_case_def( + unique int id: @ruby_case__, + int loc: @location ref +); + +#keyset[ruby_chained_string, index] +ruby_chained_string_child( + int ruby_chained_string: @ruby_chained_string ref, + int index: int ref, + unique int child: @ruby_string__ ref +); + +ruby_chained_string_def( + unique int id: @ruby_chained_string, + int loc: @location ref +); + +@ruby_class_name_type = @ruby_scope_resolution | @ruby_token_constant + +ruby_class_superclass( + unique int ruby_class: @ruby_class ref, + unique int superclass: @ruby_superclass ref +); + +@ruby_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_class, index] +ruby_class_child( + int ruby_class: @ruby_class ref, + int index: int ref, + unique int child: @ruby_class_child_type ref +); + +ruby_class_def( + unique int id: @ruby_class, + int name: @ruby_class_name_type ref, + int loc: @location ref +); + +ruby_conditional_def( + unique int id: @ruby_conditional, + int alternative: @ruby_underscore_arg ref, + int condition: @ruby_underscore_arg ref, + int consequence: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_delimited_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_delimited_symbol, index] +ruby_delimited_symbol_child( + int ruby_delimited_symbol: @ruby_delimited_symbol ref, + int index: int ref, + unique int child: @ruby_delimited_symbol_child_type ref +); + +ruby_delimited_symbol_def( + unique int id: @ruby_delimited_symbol, + int loc: @location ref +); + +@ruby_destructured_left_assignment_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs + +#keyset[ruby_destructured_left_assignment, index] +ruby_destructured_left_assignment_child( + int ruby_destructured_left_assignment: @ruby_destructured_left_assignment ref, + int index: int ref, + unique int child: @ruby_destructured_left_assignment_child_type ref +); + +ruby_destructured_left_assignment_def( + unique int id: @ruby_destructured_left_assignment, + int loc: @location ref +); + +@ruby_destructured_parameter_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_destructured_parameter, index] +ruby_destructured_parameter_child( + int ruby_destructured_parameter: @ruby_destructured_parameter ref, + int index: int ref, + unique int child: @ruby_destructured_parameter_child_type ref +); + +ruby_destructured_parameter_def( + unique int id: @ruby_destructured_parameter, + int loc: @location ref +); + +@ruby_do_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_do, index] +ruby_do_child( + int ruby_do: @ruby_do ref, + int index: int ref, + unique int child: @ruby_do_child_type ref +); + +ruby_do_def( + unique int id: @ruby_do, + int loc: @location ref +); + +ruby_do_block_parameters( + unique int ruby_do_block: @ruby_do_block ref, + unique int parameters: @ruby_block_parameters ref +); + +@ruby_do_block_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_do_block, index] +ruby_do_block_child( + int ruby_do_block: @ruby_do_block ref, + int index: int ref, + unique int child: @ruby_do_block_child_type ref +); + +ruby_do_block_def( + unique int id: @ruby_do_block, + int loc: @location ref +); + +@ruby_element_reference_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_element_reference, index] +ruby_element_reference_child( + int ruby_element_reference: @ruby_element_reference ref, + int index: int ref, + unique int child: @ruby_element_reference_child_type ref +); + +ruby_element_reference_def( + unique int id: @ruby_element_reference, + int object: @ruby_underscore_primary ref, + int loc: @location ref +); + +@ruby_else_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_else, index] +ruby_else_child( + int ruby_else: @ruby_else ref, + int index: int ref, + unique int child: @ruby_else_child_type ref +); + +ruby_else_def( + unique int id: @ruby_else, + int loc: @location ref +); + +@ruby_elsif_alternative_type = @ruby_else | @ruby_elsif + +ruby_elsif_alternative( + unique int ruby_elsif: @ruby_elsif ref, + unique int alternative: @ruby_elsif_alternative_type ref +); + +ruby_elsif_consequence( + unique int ruby_elsif: @ruby_elsif ref, + unique int consequence: @ruby_then ref +); + +ruby_elsif_def( + unique int id: @ruby_elsif, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_end_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_end_block, index] +ruby_end_block_child( + int ruby_end_block: @ruby_end_block ref, + int index: int ref, + unique int child: @ruby_end_block_child_type ref +); + +ruby_end_block_def( + unique int id: @ruby_end_block, + int loc: @location ref +); + +@ruby_ensure_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_ensure, index] +ruby_ensure_child( + int ruby_ensure: @ruby_ensure ref, + int index: int ref, + unique int child: @ruby_ensure_child_type ref +); + +ruby_ensure_def( + unique int id: @ruby_ensure, + int loc: @location ref +); + +ruby_exception_variable_def( + unique int id: @ruby_exception_variable, + int child: @ruby_underscore_lhs ref, + int loc: @location ref +); + +@ruby_exceptions_child_type = @ruby_splat_argument | @ruby_underscore_arg + +#keyset[ruby_exceptions, index] +ruby_exceptions_child( + int ruby_exceptions: @ruby_exceptions ref, + int index: int ref, + unique int child: @ruby_exceptions_child_type ref +); + +ruby_exceptions_def( + unique int id: @ruby_exceptions, + int loc: @location ref +); + +@ruby_for_pattern_type = @ruby_left_assignment_list | @ruby_underscore_lhs + +ruby_for_def( + unique int id: @ruby_for, + int body: @ruby_do ref, + int pattern: @ruby_for_pattern_type ref, + int value: @ruby_in ref, + int loc: @location ref +); + +@ruby_hash_child_type = @ruby_hash_splat_argument | @ruby_pair + +#keyset[ruby_hash, index] +ruby_hash_child( + int ruby_hash: @ruby_hash ref, + int index: int ref, + unique int child: @ruby_hash_child_type ref +); + +ruby_hash_def( + unique int id: @ruby_hash, + int loc: @location ref +); + +ruby_hash_splat_argument_def( + unique int id: @ruby_hash_splat_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_hash_splat_parameter_name( + unique int ruby_hash_splat_parameter: @ruby_hash_splat_parameter ref, + unique int name: @ruby_token_identifier ref +); + +ruby_hash_splat_parameter_def( + unique int id: @ruby_hash_splat_parameter, + int loc: @location ref +); + +@ruby_heredoc_body_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_heredoc_content | @ruby_token_heredoc_end + +#keyset[ruby_heredoc_body, index] +ruby_heredoc_body_child( + int ruby_heredoc_body: @ruby_heredoc_body ref, + int index: int ref, + unique int child: @ruby_heredoc_body_child_type ref +); + +ruby_heredoc_body_def( + unique int id: @ruby_heredoc_body, + int loc: @location ref +); + +@ruby_if_alternative_type = @ruby_else | @ruby_elsif + +ruby_if_alternative( + unique int ruby_if: @ruby_if ref, + unique int alternative: @ruby_if_alternative_type ref +); + +ruby_if_consequence( + unique int ruby_if: @ruby_if ref, + unique int consequence: @ruby_then ref +); + +ruby_if_def( + unique int id: @ruby_if, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_if_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_if_modifier_def( + unique int id: @ruby_if_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_if_modifier_condition_type ref, + int loc: @location ref +); + +ruby_in_def( + unique int id: @ruby_in, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_interpolation_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_interpolation, index] +ruby_interpolation_child( + int ruby_interpolation: @ruby_interpolation ref, + int index: int ref, + unique int child: @ruby_interpolation_child_type ref +); + +ruby_interpolation_def( + unique int id: @ruby_interpolation, + int loc: @location ref +); + +ruby_keyword_parameter_value( + unique int ruby_keyword_parameter: @ruby_keyword_parameter ref, + unique int value: @ruby_underscore_arg ref +); + +ruby_keyword_parameter_def( + unique int id: @ruby_keyword_parameter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_lambda_body_type = @ruby_block | @ruby_do_block + +ruby_lambda_parameters( + unique int ruby_lambda: @ruby_lambda ref, + unique int parameters: @ruby_lambda_parameters ref +); + +ruby_lambda_def( + unique int id: @ruby_lambda, + int body: @ruby_lambda_body_type ref, + int loc: @location ref +); + +@ruby_lambda_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_lambda_parameters, index] +ruby_lambda_parameters_child( + int ruby_lambda_parameters: @ruby_lambda_parameters ref, + int index: int ref, + unique int child: @ruby_lambda_parameters_child_type ref +); + +ruby_lambda_parameters_def( + unique int id: @ruby_lambda_parameters, + int loc: @location ref +); + +@ruby_left_assignment_list_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs + +#keyset[ruby_left_assignment_list, index] +ruby_left_assignment_list_child( + int ruby_left_assignment_list: @ruby_left_assignment_list ref, + int index: int ref, + unique int child: @ruby_left_assignment_list_child_type ref +); + +ruby_left_assignment_list_def( + unique int id: @ruby_left_assignment_list, + int loc: @location ref +); + +ruby_method_parameters( + unique int ruby_method: @ruby_method ref, + unique int parameters: @ruby_method_parameters ref +); + +@ruby_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_method, index] +ruby_method_child( + int ruby_method: @ruby_method ref, + int index: int ref, + unique int child: @ruby_method_child_type ref +); + +ruby_method_def( + unique int id: @ruby_method, + int name: @ruby_underscore_method_name ref, + int loc: @location ref +); + +@ruby_method_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_method_parameters, index] +ruby_method_parameters_child( + int ruby_method_parameters: @ruby_method_parameters ref, + int index: int ref, + unique int child: @ruby_method_parameters_child_type ref +); + +ruby_method_parameters_def( + unique int id: @ruby_method_parameters, + int loc: @location ref +); + +@ruby_module_name_type = @ruby_scope_resolution | @ruby_token_constant + +@ruby_module_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_module, index] +ruby_module_child( + int ruby_module: @ruby_module ref, + int index: int ref, + unique int child: @ruby_module_child_type ref +); + +ruby_module_def( + unique int id: @ruby_module, + int name: @ruby_module_name_type ref, + int loc: @location ref +); + +ruby_next_child( + unique int ruby_next: @ruby_next ref, + unique int child: @ruby_argument_list ref +); + +ruby_next_def( + unique int id: @ruby_next, + int loc: @location ref +); + +case @ruby_operator_assignment.operator of + 0 = @ruby_operator_assignment_percentequal +| 1 = @ruby_operator_assignment_ampersandampersandequal +| 2 = @ruby_operator_assignment_ampersandequal +| 3 = @ruby_operator_assignment_starstarequal +| 4 = @ruby_operator_assignment_starequal +| 5 = @ruby_operator_assignment_plusequal +| 6 = @ruby_operator_assignment_minusequal +| 7 = @ruby_operator_assignment_slashequal +| 8 = @ruby_operator_assignment_langlelangleequal +| 9 = @ruby_operator_assignment_ranglerangleequal +| 10 = @ruby_operator_assignment_caretequal +| 11 = @ruby_operator_assignment_pipeequal +| 12 = @ruby_operator_assignment_pipepipeequal +; + + +@ruby_operator_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_operator_assignment_def( + unique int id: @ruby_operator_assignment, + int left: @ruby_underscore_lhs ref, + int operator: int ref, + int right: @ruby_operator_assignment_right_type ref, + int loc: @location ref +); + +ruby_optional_parameter_def( + unique int id: @ruby_optional_parameter, + int name: @ruby_token_identifier ref, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_pair_key_type = @ruby_string__ | @ruby_token_hash_key_symbol | @ruby_underscore_arg + +ruby_pair_def( + unique int id: @ruby_pair, + int key__: @ruby_pair_key_type ref, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_parenthesized_statements_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_parenthesized_statements, index] +ruby_parenthesized_statements_child( + int ruby_parenthesized_statements: @ruby_parenthesized_statements ref, + int index: int ref, + unique int child: @ruby_parenthesized_statements_child_type ref +); + +ruby_parenthesized_statements_def( + unique int id: @ruby_parenthesized_statements, + int loc: @location ref +); + +@ruby_pattern_child_type = @ruby_splat_argument | @ruby_underscore_arg + +ruby_pattern_def( + unique int id: @ruby_pattern, + int child: @ruby_pattern_child_type ref, + int loc: @location ref +); + +@ruby_program_child_type = @ruby_token_empty_statement | @ruby_token_uninterpreted | @ruby_underscore_statement + +#keyset[ruby_program, index] +ruby_program_child( + int ruby_program: @ruby_program ref, + int index: int ref, + unique int child: @ruby_program_child_type ref +); + +ruby_program_def( + unique int id: @ruby_program, + int loc: @location ref +); + +ruby_range_begin( + unique int ruby_range: @ruby_range ref, + unique int begin: @ruby_underscore_arg ref +); + +ruby_range_end( + unique int ruby_range: @ruby_range ref, + unique int end: @ruby_underscore_arg ref +); + +case @ruby_range.operator of + 0 = @ruby_range_dotdot +| 1 = @ruby_range_dotdotdot +; + + +ruby_range_def( + unique int id: @ruby_range, + int operator: int ref, + int loc: @location ref +); + +@ruby_rational_child_type = @ruby_token_float | @ruby_token_integer + +ruby_rational_def( + unique int id: @ruby_rational, + int child: @ruby_rational_child_type ref, + int loc: @location ref +); + +ruby_redo_child( + unique int ruby_redo: @ruby_redo ref, + unique int child: @ruby_argument_list ref +); + +ruby_redo_def( + unique int id: @ruby_redo, + int loc: @location ref +); + +@ruby_regex_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_regex, index] +ruby_regex_child( + int ruby_regex: @ruby_regex ref, + int index: int ref, + unique int child: @ruby_regex_child_type ref +); + +ruby_regex_def( + unique int id: @ruby_regex, + int loc: @location ref +); + +ruby_rescue_body( + unique int ruby_rescue: @ruby_rescue ref, + unique int body: @ruby_then ref +); + +ruby_rescue_exceptions( + unique int ruby_rescue: @ruby_rescue ref, + unique int exceptions: @ruby_exceptions ref +); + +ruby_rescue_variable( + unique int ruby_rescue: @ruby_rescue ref, + unique int variable: @ruby_exception_variable ref +); + +ruby_rescue_def( + unique int id: @ruby_rescue, + int loc: @location ref +); + +@ruby_rescue_modifier_handler_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_rescue_modifier_def( + unique int id: @ruby_rescue_modifier, + int body: @ruby_underscore_statement ref, + int handler: @ruby_rescue_modifier_handler_type ref, + int loc: @location ref +); + +ruby_rest_assignment_child( + unique int ruby_rest_assignment: @ruby_rest_assignment ref, + unique int child: @ruby_underscore_lhs ref +); + +ruby_rest_assignment_def( + unique int id: @ruby_rest_assignment, + int loc: @location ref +); + +ruby_retry_child( + unique int ruby_retry: @ruby_retry ref, + unique int child: @ruby_argument_list ref +); + +ruby_retry_def( + unique int id: @ruby_retry, + int loc: @location ref +); + +ruby_return_child( + unique int ruby_return: @ruby_return ref, + unique int child: @ruby_argument_list ref +); + +ruby_return_def( + unique int id: @ruby_return, + int loc: @location ref +); + +@ruby_right_assignment_list_child_type = @ruby_splat_argument | @ruby_underscore_arg + +#keyset[ruby_right_assignment_list, index] +ruby_right_assignment_list_child( + int ruby_right_assignment_list: @ruby_right_assignment_list ref, + int index: int ref, + unique int child: @ruby_right_assignment_list_child_type ref +); + +ruby_right_assignment_list_def( + unique int id: @ruby_right_assignment_list, + int loc: @location ref +); + +@ruby_scope_resolution_name_type = @ruby_token_constant | @ruby_token_identifier + +ruby_scope_resolution_scope( + unique int ruby_scope_resolution: @ruby_scope_resolution ref, + unique int scope: @ruby_underscore_primary ref +); + +ruby_scope_resolution_def( + unique int id: @ruby_scope_resolution, + int name: @ruby_scope_resolution_name_type ref, + int loc: @location ref +); + +ruby_setter_def( + unique int id: @ruby_setter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_singleton_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_singleton_class, index] +ruby_singleton_class_child( + int ruby_singleton_class: @ruby_singleton_class ref, + int index: int ref, + unique int child: @ruby_singleton_class_child_type ref +); + +ruby_singleton_class_def( + unique int id: @ruby_singleton_class, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_singleton_method_object_type = @ruby_underscore_arg | @ruby_underscore_variable + +ruby_singleton_method_parameters( + unique int ruby_singleton_method: @ruby_singleton_method ref, + unique int parameters: @ruby_method_parameters ref +); + +@ruby_singleton_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_singleton_method, index] +ruby_singleton_method_child( + int ruby_singleton_method: @ruby_singleton_method ref, + int index: int ref, + unique int child: @ruby_singleton_method_child_type ref +); + +ruby_singleton_method_def( + unique int id: @ruby_singleton_method, + int name: @ruby_underscore_method_name ref, + int object: @ruby_singleton_method_object_type ref, + int loc: @location ref +); + +ruby_splat_argument_def( + unique int id: @ruby_splat_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_splat_parameter_name( + unique int ruby_splat_parameter: @ruby_splat_parameter ref, + unique int name: @ruby_token_identifier ref +); + +ruby_splat_parameter_def( + unique int id: @ruby_splat_parameter, + int loc: @location ref +); + +@ruby_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_string__, index] +ruby_string_child( + int ruby_string__: @ruby_string__ ref, + int index: int ref, + unique int child: @ruby_string_child_type ref +); + +ruby_string_def( + unique int id: @ruby_string__, + int loc: @location ref +); + +#keyset[ruby_string_array, index] +ruby_string_array_child( + int ruby_string_array: @ruby_string_array ref, + int index: int ref, + unique int child: @ruby_bare_string ref +); + +ruby_string_array_def( + unique int id: @ruby_string_array, + int loc: @location ref +); + +@ruby_subshell_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_subshell, index] +ruby_subshell_child( + int ruby_subshell: @ruby_subshell ref, + int index: int ref, + unique int child: @ruby_subshell_child_type ref +); + +ruby_subshell_def( + unique int id: @ruby_subshell, + int loc: @location ref +); + +@ruby_superclass_child_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_superclass_def( + unique int id: @ruby_superclass, + int child: @ruby_superclass_child_type ref, + int loc: @location ref +); + +#keyset[ruby_symbol_array, index] +ruby_symbol_array_child( + int ruby_symbol_array: @ruby_symbol_array ref, + int index: int ref, + unique int child: @ruby_bare_symbol ref +); + +ruby_symbol_array_def( + unique int id: @ruby_symbol_array, + int loc: @location ref +); + +@ruby_then_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_then, index] +ruby_then_child( + int ruby_then: @ruby_then ref, + int index: int ref, + unique int child: @ruby_then_child_type ref +); + +ruby_then_def( + unique int id: @ruby_then, + int loc: @location ref +); + +@ruby_unary_operand_type = @ruby_break | @ruby_call | @ruby_next | @ruby_parenthesized_statements | @ruby_return | @ruby_token_float | @ruby_token_integer | @ruby_underscore_arg | @ruby_yield + +case @ruby_unary.operator of + 0 = @ruby_unary_bang +| 1 = @ruby_unary_plus +| 2 = @ruby_unary_minus +| 3 = @ruby_unary_definedquestion +| 4 = @ruby_unary_not +| 5 = @ruby_unary_tilde +; + + +ruby_unary_def( + unique int id: @ruby_unary, + int operand: @ruby_unary_operand_type ref, + int operator: int ref, + int loc: @location ref +); + +#keyset[ruby_undef, index] +ruby_undef_child( + int ruby_undef: @ruby_undef ref, + int index: int ref, + unique int child: @ruby_underscore_method_name ref +); + +ruby_undef_def( + unique int id: @ruby_undef, + int loc: @location ref +); + +@ruby_unless_alternative_type = @ruby_else | @ruby_elsif + +ruby_unless_alternative( + unique int ruby_unless: @ruby_unless ref, + unique int alternative: @ruby_unless_alternative_type ref +); + +ruby_unless_consequence( + unique int ruby_unless: @ruby_unless ref, + unique int consequence: @ruby_then ref +); + +ruby_unless_def( + unique int id: @ruby_unless, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_unless_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_unless_modifier_def( + unique int id: @ruby_unless_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_unless_modifier_condition_type ref, + int loc: @location ref +); + +ruby_until_def( + unique int id: @ruby_until, + int body: @ruby_do ref, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_until_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_until_modifier_def( + unique int id: @ruby_until_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_until_modifier_condition_type ref, + int loc: @location ref +); + +ruby_when_body( + unique int ruby_when: @ruby_when ref, + unique int body: @ruby_then ref +); + +#keyset[ruby_when, index] +ruby_when_pattern( + int ruby_when: @ruby_when ref, + int index: int ref, + unique int pattern: @ruby_pattern ref +); + +ruby_when_def( + unique int id: @ruby_when, + int loc: @location ref +); + +ruby_while_def( + unique int id: @ruby_while, + int body: @ruby_do ref, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_while_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_while_modifier_def( + unique int id: @ruby_while_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_while_modifier_condition_type ref, + int loc: @location ref +); + +ruby_yield_child( + unique int ruby_yield: @ruby_yield ref, + unique int child: @ruby_argument_list ref +); + +ruby_yield_def( + unique int id: @ruby_yield, + int loc: @location ref +); + +ruby_tokeninfo( + unique int id: @ruby_token, + int kind: int ref, + int file: @file ref, + int idx: int ref, + string value: string ref, + int loc: @location ref +); + +case @ruby_token.kind of + 0 = @ruby_reserved_word +| 1 = @ruby_token_character +| 2 = @ruby_token_class_variable +| 3 = @ruby_token_comment +| 4 = @ruby_token_complex +| 5 = @ruby_token_constant +| 6 = @ruby_token_empty_statement +| 7 = @ruby_token_escape_sequence +| 8 = @ruby_token_false +| 9 = @ruby_token_float +| 10 = @ruby_token_global_variable +| 11 = @ruby_token_hash_key_symbol +| 12 = @ruby_token_heredoc_beginning +| 13 = @ruby_token_heredoc_content +| 14 = @ruby_token_heredoc_end +| 15 = @ruby_token_identifier +| 16 = @ruby_token_instance_variable +| 17 = @ruby_token_integer +| 18 = @ruby_token_nil +| 19 = @ruby_token_operator +| 20 = @ruby_token_self +| 21 = @ruby_token_simple_symbol +| 22 = @ruby_token_string_content +| 23 = @ruby_token_super +| 24 = @ruby_token_true +| 25 = @ruby_token_uninterpreted +; + + +@ruby_ast_node = @ruby_alias | @ruby_argument_list | @ruby_array | @ruby_assignment | @ruby_bare_string | @ruby_bare_symbol | @ruby_begin | @ruby_begin_block | @ruby_binary | @ruby_block | @ruby_block_argument | @ruby_block_parameter | @ruby_block_parameters | @ruby_break | @ruby_call | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_conditional | @ruby_delimited_symbol | @ruby_destructured_left_assignment | @ruby_destructured_parameter | @ruby_do | @ruby_do_block | @ruby_element_reference | @ruby_else | @ruby_elsif | @ruby_end_block | @ruby_ensure | @ruby_exception_variable | @ruby_exceptions | @ruby_for | @ruby_hash | @ruby_hash_splat_argument | @ruby_hash_splat_parameter | @ruby_heredoc_body | @ruby_if | @ruby_if_modifier | @ruby_in | @ruby_interpolation | @ruby_keyword_parameter | @ruby_lambda | @ruby_lambda_parameters | @ruby_left_assignment_list | @ruby_method | @ruby_method_parameters | @ruby_module | @ruby_next | @ruby_operator_assignment | @ruby_optional_parameter | @ruby_pair | @ruby_parenthesized_statements | @ruby_pattern | @ruby_program | @ruby_range | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_rescue | @ruby_rescue_modifier | @ruby_rest_assignment | @ruby_retry | @ruby_return | @ruby_right_assignment_list | @ruby_scope_resolution | @ruby_setter | @ruby_singleton_class | @ruby_singleton_method | @ruby_splat_argument | @ruby_splat_parameter | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_superclass | @ruby_symbol_array | @ruby_then | @ruby_token | @ruby_unary | @ruby_undef | @ruby_unless | @ruby_unless_modifier | @ruby_until | @ruby_until_modifier | @ruby_when | @ruby_while | @ruby_while_modifier | @ruby_yield + +@ruby_ast_node_parent = @file | @ruby_ast_node + +#keyset[parent, parent_index] +ruby_ast_node_parent( + int child: @ruby_ast_node ref, + int parent: @ruby_ast_node_parent ref, + int parent_index: int ref +); + +erb_comment_directive_def( + unique int id: @erb_comment_directive, + int child: @erb_token_comment ref, + int loc: @location ref +); + +erb_directive_def( + unique int id: @erb_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +erb_graphql_directive_def( + unique int id: @erb_graphql_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +erb_output_directive_def( + unique int id: @erb_output_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +@erb_template_child_type = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_token_content + +#keyset[erb_template, index] +erb_template_child( + int erb_template: @erb_template ref, + int index: int ref, + unique int child: @erb_template_child_type ref +); + +erb_template_def( + unique int id: @erb_template, + int loc: @location ref +); + +erb_tokeninfo( + unique int id: @erb_token, + int kind: int ref, + int file: @file ref, + int idx: int ref, + string value: string ref, + int loc: @location ref +); + +case @erb_token.kind of + 0 = @erb_reserved_word +| 1 = @erb_token_code +| 2 = @erb_token_comment +| 3 = @erb_token_content +; + + +@erb_ast_node = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_template | @erb_token + +@erb_ast_node_parent = @erb_ast_node | @file + +#keyset[parent, parent_index] +erb_ast_node_parent( + int child: @erb_ast_node ref, + int parent: @erb_ast_node_parent ref, + int parent_index: int ref +); + diff --git a/ruby/ql/lib/upgrades/30e1075bbdc9ce935dbe28dc7175489fe8e69a4c/ruby.dbscheme b/ruby/ql/lib/upgrades/30e1075bbdc9ce935dbe28dc7175489fe8e69a4c/ruby.dbscheme new file mode 100644 index 000000000000..b5aef9c93ae6 --- /dev/null +++ b/ruby/ql/lib/upgrades/30e1075bbdc9ce935dbe28dc7175489fe8e69a4c/ruby.dbscheme @@ -0,0 +1,1320 @@ +// CodeQL database schema for Ruby +// 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 +; + + +@ruby_underscore_arg = @ruby_assignment | @ruby_binary | @ruby_conditional | @ruby_operator_assignment | @ruby_range | @ruby_unary | @ruby_underscore_primary + +@ruby_underscore_lhs = @ruby_call | @ruby_element_reference | @ruby_scope_resolution | @ruby_token_false | @ruby_token_nil | @ruby_token_true | @ruby_underscore_variable + +@ruby_underscore_method_name = @ruby_delimited_symbol | @ruby_setter | @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_operator | @ruby_token_simple_symbol + +@ruby_underscore_primary = @ruby_array | @ruby_begin | @ruby_break | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_delimited_symbol | @ruby_for | @ruby_hash | @ruby_if | @ruby_lambda | @ruby_method | @ruby_module | @ruby_next | @ruby_parenthesized_statements | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_retry | @ruby_return | @ruby_singleton_class | @ruby_singleton_method | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_symbol_array | @ruby_token_character | @ruby_token_complex | @ruby_token_float | @ruby_token_heredoc_beginning | @ruby_token_integer | @ruby_token_simple_symbol | @ruby_unary | @ruby_underscore_lhs | @ruby_unless | @ruby_until | @ruby_while | @ruby_yield + +@ruby_underscore_statement = @ruby_alias | @ruby_assignment | @ruby_begin_block | @ruby_binary | @ruby_break | @ruby_call | @ruby_end_block | @ruby_if_modifier | @ruby_next | @ruby_operator_assignment | @ruby_rescue_modifier | @ruby_return | @ruby_unary | @ruby_undef | @ruby_underscore_arg | @ruby_unless_modifier | @ruby_until_modifier | @ruby_while_modifier | @ruby_yield + +@ruby_underscore_variable = @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_self | @ruby_token_super + +ruby_alias_def( + unique int id: @ruby_alias, + int alias: @ruby_underscore_method_name ref, + int name: @ruby_underscore_method_name ref, + int loc: @location ref +); + +@ruby_argument_list_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_argument_list, index] +ruby_argument_list_child( + int ruby_argument_list: @ruby_argument_list ref, + int index: int ref, + unique int child: @ruby_argument_list_child_type ref +); + +ruby_argument_list_def( + unique int id: @ruby_argument_list, + int loc: @location ref +); + +@ruby_array_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_array, index] +ruby_array_child( + int ruby_array: @ruby_array ref, + int index: int ref, + unique int child: @ruby_array_child_type ref +); + +ruby_array_def( + unique int id: @ruby_array, + int loc: @location ref +); + +@ruby_assignment_left_type = @ruby_left_assignment_list | @ruby_underscore_lhs + +@ruby_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_right_assignment_list | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +ruby_assignment_def( + unique int id: @ruby_assignment, + int left: @ruby_assignment_left_type ref, + int right: @ruby_assignment_right_type ref, + int loc: @location ref +); + +@ruby_bare_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_bare_string, index] +ruby_bare_string_child( + int ruby_bare_string: @ruby_bare_string ref, + int index: int ref, + unique int child: @ruby_bare_string_child_type ref +); + +ruby_bare_string_def( + unique int id: @ruby_bare_string, + int loc: @location ref +); + +@ruby_bare_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_bare_symbol, index] +ruby_bare_symbol_child( + int ruby_bare_symbol: @ruby_bare_symbol ref, + int index: int ref, + unique int child: @ruby_bare_symbol_child_type ref +); + +ruby_bare_symbol_def( + unique int id: @ruby_bare_symbol, + int loc: @location ref +); + +@ruby_begin_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_begin, index] +ruby_begin_child( + int ruby_begin: @ruby_begin ref, + int index: int ref, + unique int child: @ruby_begin_child_type ref +); + +ruby_begin_def( + unique int id: @ruby_begin, + int loc: @location ref +); + +@ruby_begin_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_begin_block, index] +ruby_begin_block_child( + int ruby_begin_block: @ruby_begin_block ref, + int index: int ref, + unique int child: @ruby_begin_block_child_type ref +); + +ruby_begin_block_def( + unique int id: @ruby_begin_block, + int loc: @location ref +); + +@ruby_binary_left_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +case @ruby_binary.operator of + 0 = @ruby_binary_bangequal +| 1 = @ruby_binary_bangtilde +| 2 = @ruby_binary_percent +| 3 = @ruby_binary_ampersand +| 4 = @ruby_binary_ampersandampersand +| 5 = @ruby_binary_star +| 6 = @ruby_binary_starstar +| 7 = @ruby_binary_plus +| 8 = @ruby_binary_minus +| 9 = @ruby_binary_slash +| 10 = @ruby_binary_langle +| 11 = @ruby_binary_langlelangle +| 12 = @ruby_binary_langleequal +| 13 = @ruby_binary_langleequalrangle +| 14 = @ruby_binary_equalequal +| 15 = @ruby_binary_equalequalequal +| 16 = @ruby_binary_equaltilde +| 17 = @ruby_binary_rangle +| 18 = @ruby_binary_rangleequal +| 19 = @ruby_binary_ranglerangle +| 20 = @ruby_binary_caret +| 21 = @ruby_binary_and +| 22 = @ruby_binary_or +| 23 = @ruby_binary_pipe +| 24 = @ruby_binary_pipepipe +; + + +@ruby_binary_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_binary_def( + unique int id: @ruby_binary, + int left: @ruby_binary_left_type ref, + int operator: int ref, + int right: @ruby_binary_right_type ref, + int loc: @location ref +); + +ruby_block_parameters( + unique int ruby_block: @ruby_block ref, + unique int parameters: @ruby_block_parameters ref +); + +@ruby_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_block, index] +ruby_block_child( + int ruby_block: @ruby_block ref, + int index: int ref, + unique int child: @ruby_block_child_type ref +); + +ruby_block_def( + unique int id: @ruby_block, + int loc: @location ref +); + +ruby_block_argument_def( + unique int id: @ruby_block_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_block_parameter_def( + unique int id: @ruby_block_parameter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_block_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_block_parameters, index] +ruby_block_parameters_child( + int ruby_block_parameters: @ruby_block_parameters ref, + int index: int ref, + unique int child: @ruby_block_parameters_child_type ref +); + +ruby_block_parameters_def( + unique int id: @ruby_block_parameters, + int loc: @location ref +); + +ruby_break_child( + unique int ruby_break: @ruby_break ref, + unique int child: @ruby_argument_list ref +); + +ruby_break_def( + unique int id: @ruby_break, + int loc: @location ref +); + +ruby_call_arguments( + unique int ruby_call: @ruby_call ref, + unique int arguments: @ruby_argument_list ref +); + +@ruby_call_block_type = @ruby_block | @ruby_do_block + +ruby_call_block( + unique int ruby_call: @ruby_call ref, + unique int block: @ruby_call_block_type ref +); + +@ruby_call_method_type = @ruby_argument_list | @ruby_scope_resolution | @ruby_token_operator | @ruby_underscore_variable + +@ruby_call_receiver_type = @ruby_call | @ruby_underscore_primary + +ruby_call_receiver( + unique int ruby_call: @ruby_call ref, + unique int receiver: @ruby_call_receiver_type ref +); + +ruby_call_def( + unique int id: @ruby_call, + int method: @ruby_call_method_type ref, + int loc: @location ref +); + +ruby_case_value( + unique int ruby_case__: @ruby_case__ ref, + unique int value: @ruby_underscore_statement ref +); + +@ruby_case_child_type = @ruby_else | @ruby_when + +#keyset[ruby_case__, index] +ruby_case_child( + int ruby_case__: @ruby_case__ ref, + int index: int ref, + unique int child: @ruby_case_child_type ref +); + +ruby_case_def( + unique int id: @ruby_case__, + int loc: @location ref +); + +#keyset[ruby_chained_string, index] +ruby_chained_string_child( + int ruby_chained_string: @ruby_chained_string ref, + int index: int ref, + unique int child: @ruby_string__ ref +); + +ruby_chained_string_def( + unique int id: @ruby_chained_string, + int loc: @location ref +); + +@ruby_class_name_type = @ruby_scope_resolution | @ruby_token_constant + +ruby_class_superclass( + unique int ruby_class: @ruby_class ref, + unique int superclass: @ruby_superclass ref +); + +@ruby_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_class, index] +ruby_class_child( + int ruby_class: @ruby_class ref, + int index: int ref, + unique int child: @ruby_class_child_type ref +); + +ruby_class_def( + unique int id: @ruby_class, + int name: @ruby_class_name_type ref, + int loc: @location ref +); + +ruby_conditional_def( + unique int id: @ruby_conditional, + int alternative: @ruby_underscore_arg ref, + int condition: @ruby_underscore_arg ref, + int consequence: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_delimited_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_delimited_symbol, index] +ruby_delimited_symbol_child( + int ruby_delimited_symbol: @ruby_delimited_symbol ref, + int index: int ref, + unique int child: @ruby_delimited_symbol_child_type ref +); + +ruby_delimited_symbol_def( + unique int id: @ruby_delimited_symbol, + int loc: @location ref +); + +@ruby_destructured_left_assignment_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs + +#keyset[ruby_destructured_left_assignment, index] +ruby_destructured_left_assignment_child( + int ruby_destructured_left_assignment: @ruby_destructured_left_assignment ref, + int index: int ref, + unique int child: @ruby_destructured_left_assignment_child_type ref +); + +ruby_destructured_left_assignment_def( + unique int id: @ruby_destructured_left_assignment, + int loc: @location ref +); + +@ruby_destructured_parameter_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_destructured_parameter, index] +ruby_destructured_parameter_child( + int ruby_destructured_parameter: @ruby_destructured_parameter ref, + int index: int ref, + unique int child: @ruby_destructured_parameter_child_type ref +); + +ruby_destructured_parameter_def( + unique int id: @ruby_destructured_parameter, + int loc: @location ref +); + +@ruby_do_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_do, index] +ruby_do_child( + int ruby_do: @ruby_do ref, + int index: int ref, + unique int child: @ruby_do_child_type ref +); + +ruby_do_def( + unique int id: @ruby_do, + int loc: @location ref +); + +ruby_do_block_parameters( + unique int ruby_do_block: @ruby_do_block ref, + unique int parameters: @ruby_block_parameters ref +); + +@ruby_do_block_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_do_block, index] +ruby_do_block_child( + int ruby_do_block: @ruby_do_block ref, + int index: int ref, + unique int child: @ruby_do_block_child_type ref +); + +ruby_do_block_def( + unique int id: @ruby_do_block, + int loc: @location ref +); + +@ruby_element_reference_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_element_reference, index] +ruby_element_reference_child( + int ruby_element_reference: @ruby_element_reference ref, + int index: int ref, + unique int child: @ruby_element_reference_child_type ref +); + +ruby_element_reference_def( + unique int id: @ruby_element_reference, + int object: @ruby_underscore_primary ref, + int loc: @location ref +); + +@ruby_else_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_else, index] +ruby_else_child( + int ruby_else: @ruby_else ref, + int index: int ref, + unique int child: @ruby_else_child_type ref +); + +ruby_else_def( + unique int id: @ruby_else, + int loc: @location ref +); + +@ruby_elsif_alternative_type = @ruby_else | @ruby_elsif + +ruby_elsif_alternative( + unique int ruby_elsif: @ruby_elsif ref, + unique int alternative: @ruby_elsif_alternative_type ref +); + +ruby_elsif_consequence( + unique int ruby_elsif: @ruby_elsif ref, + unique int consequence: @ruby_then ref +); + +ruby_elsif_def( + unique int id: @ruby_elsif, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_end_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_end_block, index] +ruby_end_block_child( + int ruby_end_block: @ruby_end_block ref, + int index: int ref, + unique int child: @ruby_end_block_child_type ref +); + +ruby_end_block_def( + unique int id: @ruby_end_block, + int loc: @location ref +); + +@ruby_ensure_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_ensure, index] +ruby_ensure_child( + int ruby_ensure: @ruby_ensure ref, + int index: int ref, + unique int child: @ruby_ensure_child_type ref +); + +ruby_ensure_def( + unique int id: @ruby_ensure, + int loc: @location ref +); + +ruby_exception_variable_def( + unique int id: @ruby_exception_variable, + int child: @ruby_underscore_lhs ref, + int loc: @location ref +); + +@ruby_exceptions_child_type = @ruby_splat_argument | @ruby_underscore_arg + +#keyset[ruby_exceptions, index] +ruby_exceptions_child( + int ruby_exceptions: @ruby_exceptions ref, + int index: int ref, + unique int child: @ruby_exceptions_child_type ref +); + +ruby_exceptions_def( + unique int id: @ruby_exceptions, + int loc: @location ref +); + +@ruby_for_pattern_type = @ruby_left_assignment_list | @ruby_underscore_lhs + +ruby_for_def( + unique int id: @ruby_for, + int body: @ruby_do ref, + int pattern: @ruby_for_pattern_type ref, + int value: @ruby_in ref, + int loc: @location ref +); + +@ruby_hash_child_type = @ruby_hash_splat_argument | @ruby_pair + +#keyset[ruby_hash, index] +ruby_hash_child( + int ruby_hash: @ruby_hash ref, + int index: int ref, + unique int child: @ruby_hash_child_type ref +); + +ruby_hash_def( + unique int id: @ruby_hash, + int loc: @location ref +); + +ruby_hash_splat_argument_def( + unique int id: @ruby_hash_splat_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_hash_splat_parameter_name( + unique int ruby_hash_splat_parameter: @ruby_hash_splat_parameter ref, + unique int name: @ruby_token_identifier ref +); + +ruby_hash_splat_parameter_def( + unique int id: @ruby_hash_splat_parameter, + int loc: @location ref +); + +@ruby_heredoc_body_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_heredoc_content | @ruby_token_heredoc_end + +#keyset[ruby_heredoc_body, index] +ruby_heredoc_body_child( + int ruby_heredoc_body: @ruby_heredoc_body ref, + int index: int ref, + unique int child: @ruby_heredoc_body_child_type ref +); + +ruby_heredoc_body_def( + unique int id: @ruby_heredoc_body, + int loc: @location ref +); + +@ruby_if_alternative_type = @ruby_else | @ruby_elsif + +ruby_if_alternative( + unique int ruby_if: @ruby_if ref, + unique int alternative: @ruby_if_alternative_type ref +); + +ruby_if_consequence( + unique int ruby_if: @ruby_if ref, + unique int consequence: @ruby_then ref +); + +ruby_if_def( + unique int id: @ruby_if, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_if_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_if_modifier_def( + unique int id: @ruby_if_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_if_modifier_condition_type ref, + int loc: @location ref +); + +ruby_in_def( + unique int id: @ruby_in, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_interpolation_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_interpolation, index] +ruby_interpolation_child( + int ruby_interpolation: @ruby_interpolation ref, + int index: int ref, + unique int child: @ruby_interpolation_child_type ref +); + +ruby_interpolation_def( + unique int id: @ruby_interpolation, + int loc: @location ref +); + +ruby_keyword_parameter_value( + unique int ruby_keyword_parameter: @ruby_keyword_parameter ref, + unique int value: @ruby_underscore_arg ref +); + +ruby_keyword_parameter_def( + unique int id: @ruby_keyword_parameter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_lambda_body_type = @ruby_block | @ruby_do_block + +ruby_lambda_parameters( + unique int ruby_lambda: @ruby_lambda ref, + unique int parameters: @ruby_lambda_parameters ref +); + +ruby_lambda_def( + unique int id: @ruby_lambda, + int body: @ruby_lambda_body_type ref, + int loc: @location ref +); + +@ruby_lambda_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_lambda_parameters, index] +ruby_lambda_parameters_child( + int ruby_lambda_parameters: @ruby_lambda_parameters ref, + int index: int ref, + unique int child: @ruby_lambda_parameters_child_type ref +); + +ruby_lambda_parameters_def( + unique int id: @ruby_lambda_parameters, + int loc: @location ref +); + +@ruby_left_assignment_list_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs + +#keyset[ruby_left_assignment_list, index] +ruby_left_assignment_list_child( + int ruby_left_assignment_list: @ruby_left_assignment_list ref, + int index: int ref, + unique int child: @ruby_left_assignment_list_child_type ref +); + +ruby_left_assignment_list_def( + unique int id: @ruby_left_assignment_list, + int loc: @location ref +); + +ruby_method_parameters( + unique int ruby_method: @ruby_method ref, + unique int parameters: @ruby_method_parameters ref +); + +@ruby_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_method, index] +ruby_method_child( + int ruby_method: @ruby_method ref, + int index: int ref, + unique int child: @ruby_method_child_type ref +); + +ruby_method_def( + unique int id: @ruby_method, + int name: @ruby_underscore_method_name ref, + int loc: @location ref +); + +@ruby_method_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_method_parameters, index] +ruby_method_parameters_child( + int ruby_method_parameters: @ruby_method_parameters ref, + int index: int ref, + unique int child: @ruby_method_parameters_child_type ref +); + +ruby_method_parameters_def( + unique int id: @ruby_method_parameters, + int loc: @location ref +); + +@ruby_module_name_type = @ruby_scope_resolution | @ruby_token_constant + +@ruby_module_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_module, index] +ruby_module_child( + int ruby_module: @ruby_module ref, + int index: int ref, + unique int child: @ruby_module_child_type ref +); + +ruby_module_def( + unique int id: @ruby_module, + int name: @ruby_module_name_type ref, + int loc: @location ref +); + +ruby_next_child( + unique int ruby_next: @ruby_next ref, + unique int child: @ruby_argument_list ref +); + +ruby_next_def( + unique int id: @ruby_next, + int loc: @location ref +); + +case @ruby_operator_assignment.operator of + 0 = @ruby_operator_assignment_percentequal +| 1 = @ruby_operator_assignment_ampersandampersandequal +| 2 = @ruby_operator_assignment_ampersandequal +| 3 = @ruby_operator_assignment_starstarequal +| 4 = @ruby_operator_assignment_starequal +| 5 = @ruby_operator_assignment_plusequal +| 6 = @ruby_operator_assignment_minusequal +| 7 = @ruby_operator_assignment_slashequal +| 8 = @ruby_operator_assignment_langlelangleequal +| 9 = @ruby_operator_assignment_ranglerangleequal +| 10 = @ruby_operator_assignment_caretequal +| 11 = @ruby_operator_assignment_pipeequal +| 12 = @ruby_operator_assignment_pipepipeequal +; + + +@ruby_operator_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_operator_assignment_def( + unique int id: @ruby_operator_assignment, + int left: @ruby_underscore_lhs ref, + int operator: int ref, + int right: @ruby_operator_assignment_right_type ref, + int loc: @location ref +); + +ruby_optional_parameter_def( + unique int id: @ruby_optional_parameter, + int name: @ruby_token_identifier ref, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_pair_key_type = @ruby_string__ | @ruby_token_hash_key_symbol | @ruby_underscore_arg + +ruby_pair_def( + unique int id: @ruby_pair, + int key__: @ruby_pair_key_type ref, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_parenthesized_statements_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_parenthesized_statements, index] +ruby_parenthesized_statements_child( + int ruby_parenthesized_statements: @ruby_parenthesized_statements ref, + int index: int ref, + unique int child: @ruby_parenthesized_statements_child_type ref +); + +ruby_parenthesized_statements_def( + unique int id: @ruby_parenthesized_statements, + int loc: @location ref +); + +@ruby_pattern_child_type = @ruby_splat_argument | @ruby_underscore_arg + +ruby_pattern_def( + unique int id: @ruby_pattern, + int child: @ruby_pattern_child_type ref, + int loc: @location ref +); + +@ruby_program_child_type = @ruby_token_empty_statement | @ruby_token_uninterpreted | @ruby_underscore_statement + +#keyset[ruby_program, index] +ruby_program_child( + int ruby_program: @ruby_program ref, + int index: int ref, + unique int child: @ruby_program_child_type ref +); + +ruby_program_def( + unique int id: @ruby_program, + int loc: @location ref +); + +ruby_range_begin( + unique int ruby_range: @ruby_range ref, + unique int begin: @ruby_underscore_arg ref +); + +ruby_range_end( + unique int ruby_range: @ruby_range ref, + unique int end: @ruby_underscore_arg ref +); + +case @ruby_range.operator of + 0 = @ruby_range_dotdot +| 1 = @ruby_range_dotdotdot +; + + +ruby_range_def( + unique int id: @ruby_range, + int operator: int ref, + int loc: @location ref +); + +@ruby_rational_child_type = @ruby_token_float | @ruby_token_integer + +ruby_rational_def( + unique int id: @ruby_rational, + int child: @ruby_rational_child_type ref, + int loc: @location ref +); + +ruby_redo_child( + unique int ruby_redo: @ruby_redo ref, + unique int child: @ruby_argument_list ref +); + +ruby_redo_def( + unique int id: @ruby_redo, + int loc: @location ref +); + +@ruby_regex_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_regex, index] +ruby_regex_child( + int ruby_regex: @ruby_regex ref, + int index: int ref, + unique int child: @ruby_regex_child_type ref +); + +ruby_regex_def( + unique int id: @ruby_regex, + int loc: @location ref +); + +ruby_rescue_body( + unique int ruby_rescue: @ruby_rescue ref, + unique int body: @ruby_then ref +); + +ruby_rescue_exceptions( + unique int ruby_rescue: @ruby_rescue ref, + unique int exceptions: @ruby_exceptions ref +); + +ruby_rescue_variable( + unique int ruby_rescue: @ruby_rescue ref, + unique int variable: @ruby_exception_variable ref +); + +ruby_rescue_def( + unique int id: @ruby_rescue, + int loc: @location ref +); + +@ruby_rescue_modifier_handler_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_rescue_modifier_def( + unique int id: @ruby_rescue_modifier, + int body: @ruby_underscore_statement ref, + int handler: @ruby_rescue_modifier_handler_type ref, + int loc: @location ref +); + +ruby_rest_assignment_child( + unique int ruby_rest_assignment: @ruby_rest_assignment ref, + unique int child: @ruby_underscore_lhs ref +); + +ruby_rest_assignment_def( + unique int id: @ruby_rest_assignment, + int loc: @location ref +); + +ruby_retry_child( + unique int ruby_retry: @ruby_retry ref, + unique int child: @ruby_argument_list ref +); + +ruby_retry_def( + unique int id: @ruby_retry, + int loc: @location ref +); + +ruby_return_child( + unique int ruby_return: @ruby_return ref, + unique int child: @ruby_argument_list ref +); + +ruby_return_def( + unique int id: @ruby_return, + int loc: @location ref +); + +@ruby_right_assignment_list_child_type = @ruby_splat_argument | @ruby_underscore_arg + +#keyset[ruby_right_assignment_list, index] +ruby_right_assignment_list_child( + int ruby_right_assignment_list: @ruby_right_assignment_list ref, + int index: int ref, + unique int child: @ruby_right_assignment_list_child_type ref +); + +ruby_right_assignment_list_def( + unique int id: @ruby_right_assignment_list, + int loc: @location ref +); + +@ruby_scope_resolution_name_type = @ruby_token_constant | @ruby_token_identifier + +ruby_scope_resolution_scope( + unique int ruby_scope_resolution: @ruby_scope_resolution ref, + unique int scope: @ruby_underscore_primary ref +); + +ruby_scope_resolution_def( + unique int id: @ruby_scope_resolution, + int name: @ruby_scope_resolution_name_type ref, + int loc: @location ref +); + +ruby_setter_def( + unique int id: @ruby_setter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_singleton_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_singleton_class, index] +ruby_singleton_class_child( + int ruby_singleton_class: @ruby_singleton_class ref, + int index: int ref, + unique int child: @ruby_singleton_class_child_type ref +); + +ruby_singleton_class_def( + unique int id: @ruby_singleton_class, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_singleton_method_object_type = @ruby_underscore_arg | @ruby_underscore_variable + +ruby_singleton_method_parameters( + unique int ruby_singleton_method: @ruby_singleton_method ref, + unique int parameters: @ruby_method_parameters ref +); + +@ruby_singleton_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_singleton_method, index] +ruby_singleton_method_child( + int ruby_singleton_method: @ruby_singleton_method ref, + int index: int ref, + unique int child: @ruby_singleton_method_child_type ref +); + +ruby_singleton_method_def( + unique int id: @ruby_singleton_method, + int name: @ruby_underscore_method_name ref, + int object: @ruby_singleton_method_object_type ref, + int loc: @location ref +); + +ruby_splat_argument_def( + unique int id: @ruby_splat_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_splat_parameter_name( + unique int ruby_splat_parameter: @ruby_splat_parameter ref, + unique int name: @ruby_token_identifier ref +); + +ruby_splat_parameter_def( + unique int id: @ruby_splat_parameter, + int loc: @location ref +); + +@ruby_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_string__, index] +ruby_string_child( + int ruby_string__: @ruby_string__ ref, + int index: int ref, + unique int child: @ruby_string_child_type ref +); + +ruby_string_def( + unique int id: @ruby_string__, + int loc: @location ref +); + +#keyset[ruby_string_array, index] +ruby_string_array_child( + int ruby_string_array: @ruby_string_array ref, + int index: int ref, + unique int child: @ruby_bare_string ref +); + +ruby_string_array_def( + unique int id: @ruby_string_array, + int loc: @location ref +); + +@ruby_subshell_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_subshell, index] +ruby_subshell_child( + int ruby_subshell: @ruby_subshell ref, + int index: int ref, + unique int child: @ruby_subshell_child_type ref +); + +ruby_subshell_def( + unique int id: @ruby_subshell, + int loc: @location ref +); + +@ruby_superclass_child_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_superclass_def( + unique int id: @ruby_superclass, + int child: @ruby_superclass_child_type ref, + int loc: @location ref +); + +#keyset[ruby_symbol_array, index] +ruby_symbol_array_child( + int ruby_symbol_array: @ruby_symbol_array ref, + int index: int ref, + unique int child: @ruby_bare_symbol ref +); + +ruby_symbol_array_def( + unique int id: @ruby_symbol_array, + int loc: @location ref +); + +@ruby_then_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_then, index] +ruby_then_child( + int ruby_then: @ruby_then ref, + int index: int ref, + unique int child: @ruby_then_child_type ref +); + +ruby_then_def( + unique int id: @ruby_then, + int loc: @location ref +); + +@ruby_unary_operand_type = @ruby_break | @ruby_call | @ruby_next | @ruby_parenthesized_statements | @ruby_return | @ruby_token_float | @ruby_token_integer | @ruby_underscore_arg | @ruby_yield + +case @ruby_unary.operator of + 0 = @ruby_unary_bang +| 1 = @ruby_unary_plus +| 2 = @ruby_unary_minus +| 3 = @ruby_unary_definedquestion +| 4 = @ruby_unary_not +| 5 = @ruby_unary_tilde +; + + +ruby_unary_def( + unique int id: @ruby_unary, + int operand: @ruby_unary_operand_type ref, + int operator: int ref, + int loc: @location ref +); + +#keyset[ruby_undef, index] +ruby_undef_child( + int ruby_undef: @ruby_undef ref, + int index: int ref, + unique int child: @ruby_underscore_method_name ref +); + +ruby_undef_def( + unique int id: @ruby_undef, + int loc: @location ref +); + +@ruby_unless_alternative_type = @ruby_else | @ruby_elsif + +ruby_unless_alternative( + unique int ruby_unless: @ruby_unless ref, + unique int alternative: @ruby_unless_alternative_type ref +); + +ruby_unless_consequence( + unique int ruby_unless: @ruby_unless ref, + unique int consequence: @ruby_then ref +); + +ruby_unless_def( + unique int id: @ruby_unless, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_unless_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_unless_modifier_def( + unique int id: @ruby_unless_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_unless_modifier_condition_type ref, + int loc: @location ref +); + +ruby_until_def( + unique int id: @ruby_until, + int body: @ruby_do ref, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_until_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_until_modifier_def( + unique int id: @ruby_until_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_until_modifier_condition_type ref, + int loc: @location ref +); + +ruby_when_body( + unique int ruby_when: @ruby_when ref, + unique int body: @ruby_then ref +); + +#keyset[ruby_when, index] +ruby_when_pattern( + int ruby_when: @ruby_when ref, + int index: int ref, + unique int pattern: @ruby_pattern ref +); + +ruby_when_def( + unique int id: @ruby_when, + int loc: @location ref +); + +ruby_while_def( + unique int id: @ruby_while, + int body: @ruby_do ref, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_while_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_while_modifier_def( + unique int id: @ruby_while_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_while_modifier_condition_type ref, + int loc: @location ref +); + +ruby_yield_child( + unique int ruby_yield: @ruby_yield ref, + unique int child: @ruby_argument_list ref +); + +ruby_yield_def( + unique int id: @ruby_yield, + int loc: @location ref +); + +ruby_tokeninfo( + unique int id: @ruby_token, + int kind: int ref, + int file: @file ref, + int idx: int ref, + string value: string ref, + int loc: @location ref +); + +case @ruby_token.kind of + 0 = @ruby_reserved_word +| 1 = @ruby_token_character +| 2 = @ruby_token_class_variable +| 3 = @ruby_token_comment +| 4 = @ruby_token_complex +| 5 = @ruby_token_constant +| 6 = @ruby_token_empty_statement +| 7 = @ruby_token_escape_sequence +| 8 = @ruby_token_false +| 9 = @ruby_token_float +| 10 = @ruby_token_global_variable +| 11 = @ruby_token_hash_key_symbol +| 12 = @ruby_token_heredoc_beginning +| 13 = @ruby_token_heredoc_content +| 14 = @ruby_token_heredoc_end +| 15 = @ruby_token_identifier +| 16 = @ruby_token_instance_variable +| 17 = @ruby_token_integer +| 18 = @ruby_token_nil +| 19 = @ruby_token_operator +| 20 = @ruby_token_self +| 21 = @ruby_token_simple_symbol +| 22 = @ruby_token_string_content +| 23 = @ruby_token_super +| 24 = @ruby_token_true +| 25 = @ruby_token_uninterpreted +; + + +@ruby_ast_node = @ruby_alias | @ruby_argument_list | @ruby_array | @ruby_assignment | @ruby_bare_string | @ruby_bare_symbol | @ruby_begin | @ruby_begin_block | @ruby_binary | @ruby_block | @ruby_block_argument | @ruby_block_parameter | @ruby_block_parameters | @ruby_break | @ruby_call | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_conditional | @ruby_delimited_symbol | @ruby_destructured_left_assignment | @ruby_destructured_parameter | @ruby_do | @ruby_do_block | @ruby_element_reference | @ruby_else | @ruby_elsif | @ruby_end_block | @ruby_ensure | @ruby_exception_variable | @ruby_exceptions | @ruby_for | @ruby_hash | @ruby_hash_splat_argument | @ruby_hash_splat_parameter | @ruby_heredoc_body | @ruby_if | @ruby_if_modifier | @ruby_in | @ruby_interpolation | @ruby_keyword_parameter | @ruby_lambda | @ruby_lambda_parameters | @ruby_left_assignment_list | @ruby_method | @ruby_method_parameters | @ruby_module | @ruby_next | @ruby_operator_assignment | @ruby_optional_parameter | @ruby_pair | @ruby_parenthesized_statements | @ruby_pattern | @ruby_program | @ruby_range | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_rescue | @ruby_rescue_modifier | @ruby_rest_assignment | @ruby_retry | @ruby_return | @ruby_right_assignment_list | @ruby_scope_resolution | @ruby_setter | @ruby_singleton_class | @ruby_singleton_method | @ruby_splat_argument | @ruby_splat_parameter | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_superclass | @ruby_symbol_array | @ruby_then | @ruby_token | @ruby_unary | @ruby_undef | @ruby_unless | @ruby_unless_modifier | @ruby_until | @ruby_until_modifier | @ruby_when | @ruby_while | @ruby_while_modifier | @ruby_yield + +@ruby_ast_node_parent = @file | @ruby_ast_node + +#keyset[parent, parent_index] +ruby_ast_node_parent( + int child: @ruby_ast_node ref, + int parent: @ruby_ast_node_parent ref, + int parent_index: int ref +); + +erb_comment_directive_def( + unique int id: @erb_comment_directive, + int child: @erb_token_comment ref, + int loc: @location ref +); + +erb_directive_def( + unique int id: @erb_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +erb_graphql_directive_def( + unique int id: @erb_graphql_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +erb_output_directive_def( + unique int id: @erb_output_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +@erb_template_child_type = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_token_content + +#keyset[erb_template, index] +erb_template_child( + int erb_template: @erb_template ref, + int index: int ref, + unique int child: @erb_template_child_type ref +); + +erb_template_def( + unique int id: @erb_template, + int loc: @location ref +); + +erb_tokeninfo( + unique int id: @erb_token, + int kind: int ref, + int file: @file ref, + int idx: int ref, + string value: string ref, + int loc: @location ref +); + +case @erb_token.kind of + 0 = @erb_reserved_word +| 1 = @erb_token_code +| 2 = @erb_token_comment +| 3 = @erb_token_content +; + + +@erb_ast_node = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_template | @erb_token + +@erb_ast_node_parent = @erb_ast_node | @file + +#keyset[parent, parent_index] +erb_ast_node_parent( + int child: @erb_ast_node ref, + int parent: @erb_ast_node_parent ref, + int parent_index: int ref +); + diff --git a/ruby/ql/lib/upgrades/30e1075bbdc9ce935dbe28dc7175489fe8e69a4c/upgrade.properties b/ruby/ql/lib/upgrades/30e1075bbdc9ce935dbe28dc7175489fe8e69a4c/upgrade.properties new file mode 100644 index 000000000000..c10884c8b1e6 --- /dev/null +++ b/ruby/ql/lib/upgrades/30e1075bbdc9ce935dbe28dc7175489fe8e69a4c/upgrade.properties @@ -0,0 +1,2 @@ +description: Removed unused `numlines` relation +compatibility: full diff --git a/ruby/ql/lib/upgrades/40be81bc2086eb0368f33c770e0a84817bb340c3/old.dbscheme b/ruby/ql/lib/upgrades/40be81bc2086eb0368f33c770e0a84817bb340c3/old.dbscheme new file mode 100644 index 000000000000..40be81bc2086 --- /dev/null +++ b/ruby/ql/lib/upgrades/40be81bc2086eb0368f33c770e0a84817bb340c3/old.dbscheme @@ -0,0 +1,1267 @@ +// CodeQL database schema for Ruby +// 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 +); + +@sourceline = @file + +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref +); + +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref +); + +folders( + unique int id: @folder, + string name: string ref, + string simple: string ref +); + +@container = @file | @folder + +containerparent( + int parent: @container ref, + unique int child: @container ref +); + +sourceLocationPrefix( + string prefix: string ref +); + +@underscore_arg = @assignment | @binary | @conditional | @operator_assignment | @range | @unary | @underscore_primary + +@underscore_lhs = @call | @element_reference | @scope_resolution | @token_false | @token_nil | @token_true | @underscore_variable + +@underscore_method_name = @delimited_symbol | @setter | @token_class_variable | @token_constant | @token_global_variable | @token_identifier | @token_instance_variable | @token_operator | @token_simple_symbol + +@underscore_primary = @array | @begin | @break | @case__ | @chained_string | @class | @delimited_symbol | @for | @hash | @if | @lambda | @method | @module | @next | @parenthesized_statements | @rational | @redo | @regex | @retry | @return | @singleton_class | @singleton_method | @string__ | @string_array | @subshell | @symbol_array | @token_character | @token_complex | @token_float | @token_heredoc_beginning | @token_integer | @token_simple_symbol | @unary | @underscore_lhs | @unless | @until | @while | @yield + +@underscore_statement = @alias | @assignment | @begin_block | @binary | @break | @call | @end_block | @if_modifier | @next | @operator_assignment | @rescue_modifier | @return | @unary | @undef | @underscore_arg | @unless_modifier | @until_modifier | @while_modifier | @yield + +@underscore_variable = @token_class_variable | @token_constant | @token_global_variable | @token_identifier | @token_instance_variable | @token_self | @token_super + +alias_def( + unique int id: @alias, + int alias: @underscore_method_name ref, + int name: @underscore_method_name ref, + int loc: @location ref +); + +@argument_list_child_type = @block_argument | @break | @call | @hash_splat_argument | @next | @pair | @return | @splat_argument | @underscore_arg | @yield + +#keyset[argument_list, index] +argument_list_child( + int argument_list: @argument_list ref, + int index: int ref, + unique int child: @argument_list_child_type ref +); + +argument_list_def( + unique int id: @argument_list, + int loc: @location ref +); + +@array_child_type = @block_argument | @break | @call | @hash_splat_argument | @next | @pair | @return | @splat_argument | @underscore_arg | @yield + +#keyset[array, index] +array_child( + int array: @array ref, + int index: int ref, + unique int child: @array_child_type ref +); + +array_def( + unique int id: @array, + int loc: @location ref +); + +@assignment_left_type = @left_assignment_list | @underscore_lhs + +@assignment_right_type = @break | @call | @next | @return | @right_assignment_list | @splat_argument | @underscore_arg | @yield + +assignment_def( + unique int id: @assignment, + int left: @assignment_left_type ref, + int right: @assignment_right_type ref, + int loc: @location ref +); + +@bare_string_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[bare_string, index] +bare_string_child( + int bare_string: @bare_string ref, + int index: int ref, + unique int child: @bare_string_child_type ref +); + +bare_string_def( + unique int id: @bare_string, + int loc: @location ref +); + +@bare_symbol_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[bare_symbol, index] +bare_symbol_child( + int bare_symbol: @bare_symbol ref, + int index: int ref, + unique int child: @bare_symbol_child_type ref +); + +bare_symbol_def( + unique int id: @bare_symbol, + int loc: @location ref +); + +@begin_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[begin, index] +begin_child( + int begin: @begin ref, + int index: int ref, + unique int child: @begin_child_type ref +); + +begin_def( + unique int id: @begin, + int loc: @location ref +); + +@begin_block_child_type = @token_empty_statement | @underscore_statement + +#keyset[begin_block, index] +begin_block_child( + int begin_block: @begin_block ref, + int index: int ref, + unique int child: @begin_block_child_type ref +); + +begin_block_def( + unique int id: @begin_block, + int loc: @location ref +); + +@binary_left_type = @break | @call | @next | @return | @underscore_arg | @yield + +case @binary.operator of + 0 = @binary_bangequal +| 1 = @binary_bangtilde +| 2 = @binary_percent +| 3 = @binary_ampersand +| 4 = @binary_ampersandampersand +| 5 = @binary_star +| 6 = @binary_starstar +| 7 = @binary_plus +| 8 = @binary_minus +| 9 = @binary_slash +| 10 = @binary_langle +| 11 = @binary_langlelangle +| 12 = @binary_langleequal +| 13 = @binary_langleequalrangle +| 14 = @binary_equalequal +| 15 = @binary_equalequalequal +| 16 = @binary_equaltilde +| 17 = @binary_rangle +| 18 = @binary_rangleequal +| 19 = @binary_ranglerangle +| 20 = @binary_caret +| 21 = @binary_and +| 22 = @binary_or +| 23 = @binary_pipe +| 24 = @binary_pipepipe +; + + +@binary_right_type = @break | @call | @next | @return | @underscore_arg | @yield + +binary_def( + unique int id: @binary, + int left: @binary_left_type ref, + int operator: int ref, + int right: @binary_right_type ref, + int loc: @location ref +); + +block_parameters( + unique int block: @block ref, + unique int parameters: @block_parameters ref +); + +@block_child_type = @token_empty_statement | @underscore_statement + +#keyset[block, index] +block_child( + int block: @block ref, + int index: int ref, + unique int child: @block_child_type ref +); + +block_def( + unique int id: @block, + int loc: @location ref +); + +block_argument_def( + unique int id: @block_argument, + int child: @underscore_arg ref, + int loc: @location ref +); + +block_parameter_def( + unique int id: @block_parameter, + int name: @token_identifier ref, + int loc: @location ref +); + +@block_parameters_child_type = @block_parameter | @destructured_parameter | @hash_splat_parameter | @keyword_parameter | @optional_parameter | @splat_parameter | @token_identifier + +#keyset[block_parameters, index] +block_parameters_child( + int block_parameters: @block_parameters ref, + int index: int ref, + unique int child: @block_parameters_child_type ref +); + +block_parameters_def( + unique int id: @block_parameters, + int loc: @location ref +); + +break_child( + unique int break: @break ref, + unique int child: @argument_list ref +); + +break_def( + unique int id: @break, + int loc: @location ref +); + +call_arguments( + unique int call: @call ref, + unique int arguments: @argument_list ref +); + +@call_block_type = @block | @do_block + +call_block( + unique int call: @call ref, + unique int block: @call_block_type ref +); + +@call_method_type = @argument_list | @scope_resolution | @token_operator | @underscore_variable + +@call_receiver_type = @call | @underscore_primary + +call_receiver( + unique int call: @call ref, + unique int receiver: @call_receiver_type ref +); + +call_def( + unique int id: @call, + int method: @call_method_type ref, + int loc: @location ref +); + +case_value( + unique int case__: @case__ ref, + unique int value: @underscore_statement ref +); + +@case_child_type = @else | @when + +#keyset[case__, index] +case_child( + int case__: @case__ ref, + int index: int ref, + unique int child: @case_child_type ref +); + +case_def( + unique int id: @case__, + int loc: @location ref +); + +#keyset[chained_string, index] +chained_string_child( + int chained_string: @chained_string ref, + int index: int ref, + unique int child: @string__ ref +); + +chained_string_def( + unique int id: @chained_string, + int loc: @location ref +); + +@class_name_type = @scope_resolution | @token_constant + +class_superclass( + unique int class: @class ref, + unique int superclass: @superclass ref +); + +@class_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[class, index] +class_child( + int class: @class ref, + int index: int ref, + unique int child: @class_child_type ref +); + +class_def( + unique int id: @class, + int name: @class_name_type ref, + int loc: @location ref +); + +conditional_def( + unique int id: @conditional, + int alternative: @underscore_arg ref, + int condition: @underscore_arg ref, + int consequence: @underscore_arg ref, + int loc: @location ref +); + +@delimited_symbol_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[delimited_symbol, index] +delimited_symbol_child( + int delimited_symbol: @delimited_symbol ref, + int index: int ref, + unique int child: @delimited_symbol_child_type ref +); + +delimited_symbol_def( + unique int id: @delimited_symbol, + int loc: @location ref +); + +@destructured_left_assignment_child_type = @destructured_left_assignment | @rest_assignment | @underscore_lhs + +#keyset[destructured_left_assignment, index] +destructured_left_assignment_child( + int destructured_left_assignment: @destructured_left_assignment ref, + int index: int ref, + unique int child: @destructured_left_assignment_child_type ref +); + +destructured_left_assignment_def( + unique int id: @destructured_left_assignment, + int loc: @location ref +); + +@destructured_parameter_child_type = @block_parameter | @destructured_parameter | @hash_splat_parameter | @keyword_parameter | @optional_parameter | @splat_parameter | @token_identifier + +#keyset[destructured_parameter, index] +destructured_parameter_child( + int destructured_parameter: @destructured_parameter ref, + int index: int ref, + unique int child: @destructured_parameter_child_type ref +); + +destructured_parameter_def( + unique int id: @destructured_parameter, + int loc: @location ref +); + +@do_child_type = @token_empty_statement | @underscore_statement + +#keyset[do, index] +do_child( + int do: @do ref, + int index: int ref, + unique int child: @do_child_type ref +); + +do_def( + unique int id: @do, + int loc: @location ref +); + +do_block_parameters( + unique int do_block: @do_block ref, + unique int parameters: @block_parameters ref +); + +@do_block_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[do_block, index] +do_block_child( + int do_block: @do_block ref, + int index: int ref, + unique int child: @do_block_child_type ref +); + +do_block_def( + unique int id: @do_block, + int loc: @location ref +); + +@element_reference_child_type = @block_argument | @break | @call | @hash_splat_argument | @next | @pair | @return | @splat_argument | @underscore_arg | @yield + +#keyset[element_reference, index] +element_reference_child( + int element_reference: @element_reference ref, + int index: int ref, + unique int child: @element_reference_child_type ref +); + +element_reference_def( + unique int id: @element_reference, + int object: @underscore_primary ref, + int loc: @location ref +); + +@else_child_type = @token_empty_statement | @underscore_statement + +#keyset[else, index] +else_child( + int else: @else ref, + int index: int ref, + unique int child: @else_child_type ref +); + +else_def( + unique int id: @else, + int loc: @location ref +); + +@elsif_alternative_type = @else | @elsif + +elsif_alternative( + unique int elsif: @elsif ref, + unique int alternative: @elsif_alternative_type ref +); + +elsif_consequence( + unique int elsif: @elsif ref, + unique int consequence: @then ref +); + +elsif_def( + unique int id: @elsif, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@end_block_child_type = @token_empty_statement | @underscore_statement + +#keyset[end_block, index] +end_block_child( + int end_block: @end_block ref, + int index: int ref, + unique int child: @end_block_child_type ref +); + +end_block_def( + unique int id: @end_block, + int loc: @location ref +); + +@ensure_child_type = @token_empty_statement | @underscore_statement + +#keyset[ensure, index] +ensure_child( + int ensure: @ensure ref, + int index: int ref, + unique int child: @ensure_child_type ref +); + +ensure_def( + unique int id: @ensure, + int loc: @location ref +); + +exception_variable_def( + unique int id: @exception_variable, + int child: @underscore_lhs ref, + int loc: @location ref +); + +@exceptions_child_type = @splat_argument | @underscore_arg + +#keyset[exceptions, index] +exceptions_child( + int exceptions: @exceptions ref, + int index: int ref, + unique int child: @exceptions_child_type ref +); + +exceptions_def( + unique int id: @exceptions, + int loc: @location ref +); + +@for_pattern_type = @left_assignment_list | @underscore_lhs + +for_def( + unique int id: @for, + int body: @do ref, + int pattern: @for_pattern_type ref, + int value: @in ref, + int loc: @location ref +); + +@hash_child_type = @hash_splat_argument | @pair + +#keyset[hash, index] +hash_child( + int hash: @hash ref, + int index: int ref, + unique int child: @hash_child_type ref +); + +hash_def( + unique int id: @hash, + int loc: @location ref +); + +hash_splat_argument_def( + unique int id: @hash_splat_argument, + int child: @underscore_arg ref, + int loc: @location ref +); + +hash_splat_parameter_name( + unique int hash_splat_parameter: @hash_splat_parameter ref, + unique int name: @token_identifier ref +); + +hash_splat_parameter_def( + unique int id: @hash_splat_parameter, + int loc: @location ref +); + +@heredoc_body_child_type = @interpolation | @token_escape_sequence | @token_heredoc_content | @token_heredoc_end + +#keyset[heredoc_body, index] +heredoc_body_child( + int heredoc_body: @heredoc_body ref, + int index: int ref, + unique int child: @heredoc_body_child_type ref +); + +heredoc_body_def( + unique int id: @heredoc_body, + int loc: @location ref +); + +@if_alternative_type = @else | @elsif + +if_alternative( + unique int if: @if ref, + unique int alternative: @if_alternative_type ref +); + +if_consequence( + unique int if: @if ref, + unique int consequence: @then ref +); + +if_def( + unique int id: @if, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@if_modifier_condition_type = @break | @call | @next | @return | @underscore_arg | @yield + +if_modifier_def( + unique int id: @if_modifier, + int body: @underscore_statement ref, + int condition: @if_modifier_condition_type ref, + int loc: @location ref +); + +in_def( + unique int id: @in, + int child: @underscore_arg ref, + int loc: @location ref +); + +@interpolation_child_type = @token_empty_statement | @underscore_statement + +#keyset[interpolation, index] +interpolation_child( + int interpolation: @interpolation ref, + int index: int ref, + unique int child: @interpolation_child_type ref +); + +interpolation_def( + unique int id: @interpolation, + int loc: @location ref +); + +keyword_parameter_value( + unique int keyword_parameter: @keyword_parameter ref, + unique int value: @underscore_arg ref +); + +keyword_parameter_def( + unique int id: @keyword_parameter, + int name: @token_identifier ref, + int loc: @location ref +); + +@lambda_body_type = @block | @do_block + +lambda_parameters( + unique int lambda: @lambda ref, + unique int parameters: @lambda_parameters ref +); + +lambda_def( + unique int id: @lambda, + int body: @lambda_body_type ref, + int loc: @location ref +); + +@lambda_parameters_child_type = @block_parameter | @destructured_parameter | @hash_splat_parameter | @keyword_parameter | @optional_parameter | @splat_parameter | @token_identifier + +#keyset[lambda_parameters, index] +lambda_parameters_child( + int lambda_parameters: @lambda_parameters ref, + int index: int ref, + unique int child: @lambda_parameters_child_type ref +); + +lambda_parameters_def( + unique int id: @lambda_parameters, + int loc: @location ref +); + +@left_assignment_list_child_type = @destructured_left_assignment | @rest_assignment | @underscore_lhs + +#keyset[left_assignment_list, index] +left_assignment_list_child( + int left_assignment_list: @left_assignment_list ref, + int index: int ref, + unique int child: @left_assignment_list_child_type ref +); + +left_assignment_list_def( + unique int id: @left_assignment_list, + int loc: @location ref +); + +method_parameters( + unique int method: @method ref, + unique int parameters: @method_parameters ref +); + +@method_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[method, index] +method_child( + int method: @method ref, + int index: int ref, + unique int child: @method_child_type ref +); + +method_def( + unique int id: @method, + int name: @underscore_method_name ref, + int loc: @location ref +); + +@method_parameters_child_type = @block_parameter | @destructured_parameter | @hash_splat_parameter | @keyword_parameter | @optional_parameter | @splat_parameter | @token_identifier + +#keyset[method_parameters, index] +method_parameters_child( + int method_parameters: @method_parameters ref, + int index: int ref, + unique int child: @method_parameters_child_type ref +); + +method_parameters_def( + unique int id: @method_parameters, + int loc: @location ref +); + +@module_name_type = @scope_resolution | @token_constant + +@module_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[module, index] +module_child( + int module: @module ref, + int index: int ref, + unique int child: @module_child_type ref +); + +module_def( + unique int id: @module, + int name: @module_name_type ref, + int loc: @location ref +); + +next_child( + unique int next: @next ref, + unique int child: @argument_list ref +); + +next_def( + unique int id: @next, + int loc: @location ref +); + +case @operator_assignment.operator of + 0 = @operator_assignment_percentequal +| 1 = @operator_assignment_ampersandampersandequal +| 2 = @operator_assignment_ampersandequal +| 3 = @operator_assignment_starstarequal +| 4 = @operator_assignment_starequal +| 5 = @operator_assignment_plusequal +| 6 = @operator_assignment_minusequal +| 7 = @operator_assignment_slashequal +| 8 = @operator_assignment_langlelangleequal +| 9 = @operator_assignment_ranglerangleequal +| 10 = @operator_assignment_caretequal +| 11 = @operator_assignment_pipeequal +| 12 = @operator_assignment_pipepipeequal +; + + +@operator_assignment_right_type = @break | @call | @next | @return | @underscore_arg | @yield + +operator_assignment_def( + unique int id: @operator_assignment, + int left: @underscore_lhs ref, + int operator: int ref, + int right: @operator_assignment_right_type ref, + int loc: @location ref +); + +optional_parameter_def( + unique int id: @optional_parameter, + int name: @token_identifier ref, + int value: @underscore_arg ref, + int loc: @location ref +); + +@pair_key_type = @string__ | @token_hash_key_symbol | @underscore_arg + +pair_def( + unique int id: @pair, + int key__: @pair_key_type ref, + int value: @underscore_arg ref, + int loc: @location ref +); + +@parenthesized_statements_child_type = @token_empty_statement | @underscore_statement + +#keyset[parenthesized_statements, index] +parenthesized_statements_child( + int parenthesized_statements: @parenthesized_statements ref, + int index: int ref, + unique int child: @parenthesized_statements_child_type ref +); + +parenthesized_statements_def( + unique int id: @parenthesized_statements, + int loc: @location ref +); + +@pattern_child_type = @splat_argument | @underscore_arg + +pattern_def( + unique int id: @pattern, + int child: @pattern_child_type ref, + int loc: @location ref +); + +@program_child_type = @token_empty_statement | @token_uninterpreted | @underscore_statement + +#keyset[program, index] +program_child( + int program: @program ref, + int index: int ref, + unique int child: @program_child_type ref +); + +program_def( + unique int id: @program, + int loc: @location ref +); + +range_begin( + unique int range: @range ref, + unique int begin: @underscore_arg ref +); + +range_end( + unique int range: @range ref, + unique int end: @underscore_arg ref +); + +case @range.operator of + 0 = @range_dotdot +| 1 = @range_dotdotdot +; + + +range_def( + unique int id: @range, + int operator: int ref, + int loc: @location ref +); + +@rational_child_type = @token_float | @token_integer + +rational_def( + unique int id: @rational, + int child: @rational_child_type ref, + int loc: @location ref +); + +redo_child( + unique int redo: @redo ref, + unique int child: @argument_list ref +); + +redo_def( + unique int id: @redo, + int loc: @location ref +); + +@regex_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[regex, index] +regex_child( + int regex: @regex ref, + int index: int ref, + unique int child: @regex_child_type ref +); + +regex_def( + unique int id: @regex, + int loc: @location ref +); + +rescue_body( + unique int rescue: @rescue ref, + unique int body: @then ref +); + +rescue_exceptions( + unique int rescue: @rescue ref, + unique int exceptions: @exceptions ref +); + +rescue_variable( + unique int rescue: @rescue ref, + unique int variable: @exception_variable ref +); + +rescue_def( + unique int id: @rescue, + int loc: @location ref +); + +@rescue_modifier_handler_type = @break | @call | @next | @return | @underscore_arg | @yield + +rescue_modifier_def( + unique int id: @rescue_modifier, + int body: @underscore_statement ref, + int handler: @rescue_modifier_handler_type ref, + int loc: @location ref +); + +rest_assignment_child( + unique int rest_assignment: @rest_assignment ref, + unique int child: @underscore_lhs ref +); + +rest_assignment_def( + unique int id: @rest_assignment, + int loc: @location ref +); + +retry_child( + unique int retry: @retry ref, + unique int child: @argument_list ref +); + +retry_def( + unique int id: @retry, + int loc: @location ref +); + +return_child( + unique int return: @return ref, + unique int child: @argument_list ref +); + +return_def( + unique int id: @return, + int loc: @location ref +); + +@right_assignment_list_child_type = @splat_argument | @underscore_arg + +#keyset[right_assignment_list, index] +right_assignment_list_child( + int right_assignment_list: @right_assignment_list ref, + int index: int ref, + unique int child: @right_assignment_list_child_type ref +); + +right_assignment_list_def( + unique int id: @right_assignment_list, + int loc: @location ref +); + +@scope_resolution_name_type = @token_constant | @token_identifier + +scope_resolution_scope( + unique int scope_resolution: @scope_resolution ref, + unique int scope: @underscore_primary ref +); + +scope_resolution_def( + unique int id: @scope_resolution, + int name: @scope_resolution_name_type ref, + int loc: @location ref +); + +setter_def( + unique int id: @setter, + int name: @token_identifier ref, + int loc: @location ref +); + +@singleton_class_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[singleton_class, index] +singleton_class_child( + int singleton_class: @singleton_class ref, + int index: int ref, + unique int child: @singleton_class_child_type ref +); + +singleton_class_def( + unique int id: @singleton_class, + int value: @underscore_arg ref, + int loc: @location ref +); + +@singleton_method_object_type = @underscore_arg | @underscore_variable + +singleton_method_parameters( + unique int singleton_method: @singleton_method ref, + unique int parameters: @method_parameters ref +); + +@singleton_method_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[singleton_method, index] +singleton_method_child( + int singleton_method: @singleton_method ref, + int index: int ref, + unique int child: @singleton_method_child_type ref +); + +singleton_method_def( + unique int id: @singleton_method, + int name: @underscore_method_name ref, + int object: @singleton_method_object_type ref, + int loc: @location ref +); + +splat_argument_def( + unique int id: @splat_argument, + int child: @underscore_arg ref, + int loc: @location ref +); + +splat_parameter_name( + unique int splat_parameter: @splat_parameter ref, + unique int name: @token_identifier ref +); + +splat_parameter_def( + unique int id: @splat_parameter, + int loc: @location ref +); + +@string_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[string__, index] +string_child( + int string__: @string__ ref, + int index: int ref, + unique int child: @string_child_type ref +); + +string_def( + unique int id: @string__, + int loc: @location ref +); + +#keyset[string_array, index] +string_array_child( + int string_array: @string_array ref, + int index: int ref, + unique int child: @bare_string ref +); + +string_array_def( + unique int id: @string_array, + int loc: @location ref +); + +@subshell_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[subshell, index] +subshell_child( + int subshell: @subshell ref, + int index: int ref, + unique int child: @subshell_child_type ref +); + +subshell_def( + unique int id: @subshell, + int loc: @location ref +); + +@superclass_child_type = @break | @call | @next | @return | @underscore_arg | @yield + +superclass_def( + unique int id: @superclass, + int child: @superclass_child_type ref, + int loc: @location ref +); + +#keyset[symbol_array, index] +symbol_array_child( + int symbol_array: @symbol_array ref, + int index: int ref, + unique int child: @bare_symbol ref +); + +symbol_array_def( + unique int id: @symbol_array, + int loc: @location ref +); + +@then_child_type = @token_empty_statement | @underscore_statement + +#keyset[then, index] +then_child( + int then: @then ref, + int index: int ref, + unique int child: @then_child_type ref +); + +then_def( + unique int id: @then, + int loc: @location ref +); + +@unary_operand_type = @break | @call | @next | @parenthesized_statements | @return | @token_float | @token_integer | @underscore_arg | @yield + +case @unary.operator of + 0 = @unary_bang +| 1 = @unary_plus +| 2 = @unary_minus +| 3 = @unary_definedquestion +| 4 = @unary_not +| 5 = @unary_tilde +; + + +unary_def( + unique int id: @unary, + int operand: @unary_operand_type ref, + int operator: int ref, + int loc: @location ref +); + +#keyset[undef, index] +undef_child( + int undef: @undef ref, + int index: int ref, + unique int child: @underscore_method_name ref +); + +undef_def( + unique int id: @undef, + int loc: @location ref +); + +@unless_alternative_type = @else | @elsif + +unless_alternative( + unique int unless: @unless ref, + unique int alternative: @unless_alternative_type ref +); + +unless_consequence( + unique int unless: @unless ref, + unique int consequence: @then ref +); + +unless_def( + unique int id: @unless, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@unless_modifier_condition_type = @break | @call | @next | @return | @underscore_arg | @yield + +unless_modifier_def( + unique int id: @unless_modifier, + int body: @underscore_statement ref, + int condition: @unless_modifier_condition_type ref, + int loc: @location ref +); + +until_def( + unique int id: @until, + int body: @do ref, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@until_modifier_condition_type = @break | @call | @next | @return | @underscore_arg | @yield + +until_modifier_def( + unique int id: @until_modifier, + int body: @underscore_statement ref, + int condition: @until_modifier_condition_type ref, + int loc: @location ref +); + +when_body( + unique int when: @when ref, + unique int body: @then ref +); + +#keyset[when, index] +when_pattern( + int when: @when ref, + int index: int ref, + unique int pattern: @pattern ref +); + +when_def( + unique int id: @when, + int loc: @location ref +); + +while_def( + unique int id: @while, + int body: @do ref, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@while_modifier_condition_type = @break | @call | @next | @return | @underscore_arg | @yield + +while_modifier_def( + unique int id: @while_modifier, + int body: @underscore_statement ref, + int condition: @while_modifier_condition_type ref, + int loc: @location ref +); + +yield_child( + unique int yield: @yield ref, + unique int child: @argument_list ref +); + +yield_def( + unique int id: @yield, + int loc: @location ref +); + +tokeninfo( + unique int id: @token, + int kind: int ref, + int file: @file ref, + int idx: int ref, + string value: string ref, + int loc: @location ref +); + +case @token.kind of + 0 = @reserved_word +| 1 = @token_character +| 2 = @token_class_variable +| 3 = @token_comment +| 4 = @token_complex +| 5 = @token_constant +| 6 = @token_empty_statement +| 7 = @token_escape_sequence +| 8 = @token_false +| 9 = @token_float +| 10 = @token_global_variable +| 11 = @token_hash_key_symbol +| 12 = @token_heredoc_beginning +| 13 = @token_heredoc_content +| 14 = @token_heredoc_end +| 15 = @token_identifier +| 16 = @token_instance_variable +| 17 = @token_integer +| 18 = @token_nil +| 19 = @token_operator +| 20 = @token_self +| 21 = @token_simple_symbol +| 22 = @token_string_content +| 23 = @token_super +| 24 = @token_true +| 25 = @token_uninterpreted +; + + +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 +; + + +@ast_node = @alias | @argument_list | @array | @assignment | @bare_string | @bare_symbol | @begin | @begin_block | @binary | @block | @block_argument | @block_parameter | @block_parameters | @break | @call | @case__ | @chained_string | @class | @conditional | @delimited_symbol | @destructured_left_assignment | @destructured_parameter | @do | @do_block | @element_reference | @else | @elsif | @end_block | @ensure | @exception_variable | @exceptions | @for | @hash | @hash_splat_argument | @hash_splat_parameter | @heredoc_body | @if | @if_modifier | @in | @interpolation | @keyword_parameter | @lambda | @lambda_parameters | @left_assignment_list | @method | @method_parameters | @module | @next | @operator_assignment | @optional_parameter | @pair | @parenthesized_statements | @pattern | @program | @range | @rational | @redo | @regex | @rescue | @rescue_modifier | @rest_assignment | @retry | @return | @right_assignment_list | @scope_resolution | @setter | @singleton_class | @singleton_method | @splat_argument | @splat_parameter | @string__ | @string_array | @subshell | @superclass | @symbol_array | @then | @token | @unary | @undef | @unless | @unless_modifier | @until | @until_modifier | @when | @while | @while_modifier | @yield + +@ast_node_parent = @ast_node | @file + +#keyset[parent, parent_index] +ast_node_parent( + int child: @ast_node ref, + int parent: @ast_node_parent ref, + int parent_index: int ref +); + diff --git a/ruby/ql/lib/upgrades/40be81bc2086eb0368f33c770e0a84817bb340c3/ruby.dbscheme b/ruby/ql/lib/upgrades/40be81bc2086eb0368f33c770e0a84817bb340c3/ruby.dbscheme new file mode 100644 index 000000000000..09a494ce67d8 --- /dev/null +++ b/ruby/ql/lib/upgrades/40be81bc2086eb0368f33c770e0a84817bb340c3/ruby.dbscheme @@ -0,0 +1,1333 @@ +// CodeQL database schema for Ruby +// 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 +); + +@sourceline = @file + +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref +); + +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref +); + +folders( + unique int id: @folder, + string name: string ref, + string simple: 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 +; + + +@ruby_underscore_arg = @ruby_assignment | @ruby_binary | @ruby_conditional | @ruby_operator_assignment | @ruby_range | @ruby_unary | @ruby_underscore_primary + +@ruby_underscore_lhs = @ruby_call | @ruby_element_reference | @ruby_scope_resolution | @ruby_token_false | @ruby_token_nil | @ruby_token_true | @ruby_underscore_variable + +@ruby_underscore_method_name = @ruby_delimited_symbol | @ruby_setter | @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_operator | @ruby_token_simple_symbol + +@ruby_underscore_primary = @ruby_array | @ruby_begin | @ruby_break | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_delimited_symbol | @ruby_for | @ruby_hash | @ruby_if | @ruby_lambda | @ruby_method | @ruby_module | @ruby_next | @ruby_parenthesized_statements | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_retry | @ruby_return | @ruby_singleton_class | @ruby_singleton_method | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_symbol_array | @ruby_token_character | @ruby_token_complex | @ruby_token_float | @ruby_token_heredoc_beginning | @ruby_token_integer | @ruby_token_simple_symbol | @ruby_unary | @ruby_underscore_lhs | @ruby_unless | @ruby_until | @ruby_while | @ruby_yield + +@ruby_underscore_statement = @ruby_alias | @ruby_assignment | @ruby_begin_block | @ruby_binary | @ruby_break | @ruby_call | @ruby_end_block | @ruby_if_modifier | @ruby_next | @ruby_operator_assignment | @ruby_rescue_modifier | @ruby_return | @ruby_unary | @ruby_undef | @ruby_underscore_arg | @ruby_unless_modifier | @ruby_until_modifier | @ruby_while_modifier | @ruby_yield + +@ruby_underscore_variable = @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_self | @ruby_token_super + +ruby_alias_def( + unique int id: @ruby_alias, + int alias: @ruby_underscore_method_name ref, + int name: @ruby_underscore_method_name ref, + int loc: @location ref +); + +@ruby_argument_list_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_argument_list, index] +ruby_argument_list_child( + int ruby_argument_list: @ruby_argument_list ref, + int index: int ref, + unique int child: @ruby_argument_list_child_type ref +); + +ruby_argument_list_def( + unique int id: @ruby_argument_list, + int loc: @location ref +); + +@ruby_array_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_array, index] +ruby_array_child( + int ruby_array: @ruby_array ref, + int index: int ref, + unique int child: @ruby_array_child_type ref +); + +ruby_array_def( + unique int id: @ruby_array, + int loc: @location ref +); + +@ruby_assignment_left_type = @ruby_left_assignment_list | @ruby_underscore_lhs + +@ruby_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_right_assignment_list | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +ruby_assignment_def( + unique int id: @ruby_assignment, + int left: @ruby_assignment_left_type ref, + int right: @ruby_assignment_right_type ref, + int loc: @location ref +); + +@ruby_bare_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_bare_string, index] +ruby_bare_string_child( + int ruby_bare_string: @ruby_bare_string ref, + int index: int ref, + unique int child: @ruby_bare_string_child_type ref +); + +ruby_bare_string_def( + unique int id: @ruby_bare_string, + int loc: @location ref +); + +@ruby_bare_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_bare_symbol, index] +ruby_bare_symbol_child( + int ruby_bare_symbol: @ruby_bare_symbol ref, + int index: int ref, + unique int child: @ruby_bare_symbol_child_type ref +); + +ruby_bare_symbol_def( + unique int id: @ruby_bare_symbol, + int loc: @location ref +); + +@ruby_begin_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_begin, index] +ruby_begin_child( + int ruby_begin: @ruby_begin ref, + int index: int ref, + unique int child: @ruby_begin_child_type ref +); + +ruby_begin_def( + unique int id: @ruby_begin, + int loc: @location ref +); + +@ruby_begin_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_begin_block, index] +ruby_begin_block_child( + int ruby_begin_block: @ruby_begin_block ref, + int index: int ref, + unique int child: @ruby_begin_block_child_type ref +); + +ruby_begin_block_def( + unique int id: @ruby_begin_block, + int loc: @location ref +); + +@ruby_binary_left_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +case @ruby_binary.operator of + 0 = @ruby_binary_bangequal +| 1 = @ruby_binary_bangtilde +| 2 = @ruby_binary_percent +| 3 = @ruby_binary_ampersand +| 4 = @ruby_binary_ampersandampersand +| 5 = @ruby_binary_star +| 6 = @ruby_binary_starstar +| 7 = @ruby_binary_plus +| 8 = @ruby_binary_minus +| 9 = @ruby_binary_slash +| 10 = @ruby_binary_langle +| 11 = @ruby_binary_langlelangle +| 12 = @ruby_binary_langleequal +| 13 = @ruby_binary_langleequalrangle +| 14 = @ruby_binary_equalequal +| 15 = @ruby_binary_equalequalequal +| 16 = @ruby_binary_equaltilde +| 17 = @ruby_binary_rangle +| 18 = @ruby_binary_rangleequal +| 19 = @ruby_binary_ranglerangle +| 20 = @ruby_binary_caret +| 21 = @ruby_binary_and +| 22 = @ruby_binary_or +| 23 = @ruby_binary_pipe +| 24 = @ruby_binary_pipepipe +; + + +@ruby_binary_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_binary_def( + unique int id: @ruby_binary, + int left: @ruby_binary_left_type ref, + int operator: int ref, + int right: @ruby_binary_right_type ref, + int loc: @location ref +); + +ruby_block_parameters( + unique int ruby_block: @ruby_block ref, + unique int parameters: @ruby_block_parameters ref +); + +@ruby_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_block, index] +ruby_block_child( + int ruby_block: @ruby_block ref, + int index: int ref, + unique int child: @ruby_block_child_type ref +); + +ruby_block_def( + unique int id: @ruby_block, + int loc: @location ref +); + +ruby_block_argument_def( + unique int id: @ruby_block_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_block_parameter_def( + unique int id: @ruby_block_parameter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_block_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_block_parameters, index] +ruby_block_parameters_child( + int ruby_block_parameters: @ruby_block_parameters ref, + int index: int ref, + unique int child: @ruby_block_parameters_child_type ref +); + +ruby_block_parameters_def( + unique int id: @ruby_block_parameters, + int loc: @location ref +); + +ruby_break_child( + unique int ruby_break: @ruby_break ref, + unique int child: @ruby_argument_list ref +); + +ruby_break_def( + unique int id: @ruby_break, + int loc: @location ref +); + +ruby_call_arguments( + unique int ruby_call: @ruby_call ref, + unique int arguments: @ruby_argument_list ref +); + +@ruby_call_block_type = @ruby_block | @ruby_do_block + +ruby_call_block( + unique int ruby_call: @ruby_call ref, + unique int block: @ruby_call_block_type ref +); + +@ruby_call_method_type = @ruby_argument_list | @ruby_scope_resolution | @ruby_token_operator | @ruby_underscore_variable + +@ruby_call_receiver_type = @ruby_call | @ruby_underscore_primary + +ruby_call_receiver( + unique int ruby_call: @ruby_call ref, + unique int receiver: @ruby_call_receiver_type ref +); + +ruby_call_def( + unique int id: @ruby_call, + int method: @ruby_call_method_type ref, + int loc: @location ref +); + +ruby_case_value( + unique int ruby_case__: @ruby_case__ ref, + unique int value: @ruby_underscore_statement ref +); + +@ruby_case_child_type = @ruby_else | @ruby_when + +#keyset[ruby_case__, index] +ruby_case_child( + int ruby_case__: @ruby_case__ ref, + int index: int ref, + unique int child: @ruby_case_child_type ref +); + +ruby_case_def( + unique int id: @ruby_case__, + int loc: @location ref +); + +#keyset[ruby_chained_string, index] +ruby_chained_string_child( + int ruby_chained_string: @ruby_chained_string ref, + int index: int ref, + unique int child: @ruby_string__ ref +); + +ruby_chained_string_def( + unique int id: @ruby_chained_string, + int loc: @location ref +); + +@ruby_class_name_type = @ruby_scope_resolution | @ruby_token_constant + +ruby_class_superclass( + unique int ruby_class: @ruby_class ref, + unique int superclass: @ruby_superclass ref +); + +@ruby_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_class, index] +ruby_class_child( + int ruby_class: @ruby_class ref, + int index: int ref, + unique int child: @ruby_class_child_type ref +); + +ruby_class_def( + unique int id: @ruby_class, + int name: @ruby_class_name_type ref, + int loc: @location ref +); + +ruby_conditional_def( + unique int id: @ruby_conditional, + int alternative: @ruby_underscore_arg ref, + int condition: @ruby_underscore_arg ref, + int consequence: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_delimited_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_delimited_symbol, index] +ruby_delimited_symbol_child( + int ruby_delimited_symbol: @ruby_delimited_symbol ref, + int index: int ref, + unique int child: @ruby_delimited_symbol_child_type ref +); + +ruby_delimited_symbol_def( + unique int id: @ruby_delimited_symbol, + int loc: @location ref +); + +@ruby_destructured_left_assignment_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs + +#keyset[ruby_destructured_left_assignment, index] +ruby_destructured_left_assignment_child( + int ruby_destructured_left_assignment: @ruby_destructured_left_assignment ref, + int index: int ref, + unique int child: @ruby_destructured_left_assignment_child_type ref +); + +ruby_destructured_left_assignment_def( + unique int id: @ruby_destructured_left_assignment, + int loc: @location ref +); + +@ruby_destructured_parameter_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_destructured_parameter, index] +ruby_destructured_parameter_child( + int ruby_destructured_parameter: @ruby_destructured_parameter ref, + int index: int ref, + unique int child: @ruby_destructured_parameter_child_type ref +); + +ruby_destructured_parameter_def( + unique int id: @ruby_destructured_parameter, + int loc: @location ref +); + +@ruby_do_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_do, index] +ruby_do_child( + int ruby_do: @ruby_do ref, + int index: int ref, + unique int child: @ruby_do_child_type ref +); + +ruby_do_def( + unique int id: @ruby_do, + int loc: @location ref +); + +ruby_do_block_parameters( + unique int ruby_do_block: @ruby_do_block ref, + unique int parameters: @ruby_block_parameters ref +); + +@ruby_do_block_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_do_block, index] +ruby_do_block_child( + int ruby_do_block: @ruby_do_block ref, + int index: int ref, + unique int child: @ruby_do_block_child_type ref +); + +ruby_do_block_def( + unique int id: @ruby_do_block, + int loc: @location ref +); + +@ruby_element_reference_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_element_reference, index] +ruby_element_reference_child( + int ruby_element_reference: @ruby_element_reference ref, + int index: int ref, + unique int child: @ruby_element_reference_child_type ref +); + +ruby_element_reference_def( + unique int id: @ruby_element_reference, + int object: @ruby_underscore_primary ref, + int loc: @location ref +); + +@ruby_else_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_else, index] +ruby_else_child( + int ruby_else: @ruby_else ref, + int index: int ref, + unique int child: @ruby_else_child_type ref +); + +ruby_else_def( + unique int id: @ruby_else, + int loc: @location ref +); + +@ruby_elsif_alternative_type = @ruby_else | @ruby_elsif + +ruby_elsif_alternative( + unique int ruby_elsif: @ruby_elsif ref, + unique int alternative: @ruby_elsif_alternative_type ref +); + +ruby_elsif_consequence( + unique int ruby_elsif: @ruby_elsif ref, + unique int consequence: @ruby_then ref +); + +ruby_elsif_def( + unique int id: @ruby_elsif, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_end_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_end_block, index] +ruby_end_block_child( + int ruby_end_block: @ruby_end_block ref, + int index: int ref, + unique int child: @ruby_end_block_child_type ref +); + +ruby_end_block_def( + unique int id: @ruby_end_block, + int loc: @location ref +); + +@ruby_ensure_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_ensure, index] +ruby_ensure_child( + int ruby_ensure: @ruby_ensure ref, + int index: int ref, + unique int child: @ruby_ensure_child_type ref +); + +ruby_ensure_def( + unique int id: @ruby_ensure, + int loc: @location ref +); + +ruby_exception_variable_def( + unique int id: @ruby_exception_variable, + int child: @ruby_underscore_lhs ref, + int loc: @location ref +); + +@ruby_exceptions_child_type = @ruby_splat_argument | @ruby_underscore_arg + +#keyset[ruby_exceptions, index] +ruby_exceptions_child( + int ruby_exceptions: @ruby_exceptions ref, + int index: int ref, + unique int child: @ruby_exceptions_child_type ref +); + +ruby_exceptions_def( + unique int id: @ruby_exceptions, + int loc: @location ref +); + +@ruby_for_pattern_type = @ruby_left_assignment_list | @ruby_underscore_lhs + +ruby_for_def( + unique int id: @ruby_for, + int body: @ruby_do ref, + int pattern: @ruby_for_pattern_type ref, + int value: @ruby_in ref, + int loc: @location ref +); + +@ruby_hash_child_type = @ruby_hash_splat_argument | @ruby_pair + +#keyset[ruby_hash, index] +ruby_hash_child( + int ruby_hash: @ruby_hash ref, + int index: int ref, + unique int child: @ruby_hash_child_type ref +); + +ruby_hash_def( + unique int id: @ruby_hash, + int loc: @location ref +); + +ruby_hash_splat_argument_def( + unique int id: @ruby_hash_splat_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_hash_splat_parameter_name( + unique int ruby_hash_splat_parameter: @ruby_hash_splat_parameter ref, + unique int name: @ruby_token_identifier ref +); + +ruby_hash_splat_parameter_def( + unique int id: @ruby_hash_splat_parameter, + int loc: @location ref +); + +@ruby_heredoc_body_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_heredoc_content | @ruby_token_heredoc_end + +#keyset[ruby_heredoc_body, index] +ruby_heredoc_body_child( + int ruby_heredoc_body: @ruby_heredoc_body ref, + int index: int ref, + unique int child: @ruby_heredoc_body_child_type ref +); + +ruby_heredoc_body_def( + unique int id: @ruby_heredoc_body, + int loc: @location ref +); + +@ruby_if_alternative_type = @ruby_else | @ruby_elsif + +ruby_if_alternative( + unique int ruby_if: @ruby_if ref, + unique int alternative: @ruby_if_alternative_type ref +); + +ruby_if_consequence( + unique int ruby_if: @ruby_if ref, + unique int consequence: @ruby_then ref +); + +ruby_if_def( + unique int id: @ruby_if, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_if_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_if_modifier_def( + unique int id: @ruby_if_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_if_modifier_condition_type ref, + int loc: @location ref +); + +ruby_in_def( + unique int id: @ruby_in, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_interpolation_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_interpolation, index] +ruby_interpolation_child( + int ruby_interpolation: @ruby_interpolation ref, + int index: int ref, + unique int child: @ruby_interpolation_child_type ref +); + +ruby_interpolation_def( + unique int id: @ruby_interpolation, + int loc: @location ref +); + +ruby_keyword_parameter_value( + unique int ruby_keyword_parameter: @ruby_keyword_parameter ref, + unique int value: @ruby_underscore_arg ref +); + +ruby_keyword_parameter_def( + unique int id: @ruby_keyword_parameter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_lambda_body_type = @ruby_block | @ruby_do_block + +ruby_lambda_parameters( + unique int ruby_lambda: @ruby_lambda ref, + unique int parameters: @ruby_lambda_parameters ref +); + +ruby_lambda_def( + unique int id: @ruby_lambda, + int body: @ruby_lambda_body_type ref, + int loc: @location ref +); + +@ruby_lambda_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_lambda_parameters, index] +ruby_lambda_parameters_child( + int ruby_lambda_parameters: @ruby_lambda_parameters ref, + int index: int ref, + unique int child: @ruby_lambda_parameters_child_type ref +); + +ruby_lambda_parameters_def( + unique int id: @ruby_lambda_parameters, + int loc: @location ref +); + +@ruby_left_assignment_list_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs + +#keyset[ruby_left_assignment_list, index] +ruby_left_assignment_list_child( + int ruby_left_assignment_list: @ruby_left_assignment_list ref, + int index: int ref, + unique int child: @ruby_left_assignment_list_child_type ref +); + +ruby_left_assignment_list_def( + unique int id: @ruby_left_assignment_list, + int loc: @location ref +); + +ruby_method_parameters( + unique int ruby_method: @ruby_method ref, + unique int parameters: @ruby_method_parameters ref +); + +@ruby_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_method, index] +ruby_method_child( + int ruby_method: @ruby_method ref, + int index: int ref, + unique int child: @ruby_method_child_type ref +); + +ruby_method_def( + unique int id: @ruby_method, + int name: @ruby_underscore_method_name ref, + int loc: @location ref +); + +@ruby_method_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_method_parameters, index] +ruby_method_parameters_child( + int ruby_method_parameters: @ruby_method_parameters ref, + int index: int ref, + unique int child: @ruby_method_parameters_child_type ref +); + +ruby_method_parameters_def( + unique int id: @ruby_method_parameters, + int loc: @location ref +); + +@ruby_module_name_type = @ruby_scope_resolution | @ruby_token_constant + +@ruby_module_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_module, index] +ruby_module_child( + int ruby_module: @ruby_module ref, + int index: int ref, + unique int child: @ruby_module_child_type ref +); + +ruby_module_def( + unique int id: @ruby_module, + int name: @ruby_module_name_type ref, + int loc: @location ref +); + +ruby_next_child( + unique int ruby_next: @ruby_next ref, + unique int child: @ruby_argument_list ref +); + +ruby_next_def( + unique int id: @ruby_next, + int loc: @location ref +); + +case @ruby_operator_assignment.operator of + 0 = @ruby_operator_assignment_percentequal +| 1 = @ruby_operator_assignment_ampersandampersandequal +| 2 = @ruby_operator_assignment_ampersandequal +| 3 = @ruby_operator_assignment_starstarequal +| 4 = @ruby_operator_assignment_starequal +| 5 = @ruby_operator_assignment_plusequal +| 6 = @ruby_operator_assignment_minusequal +| 7 = @ruby_operator_assignment_slashequal +| 8 = @ruby_operator_assignment_langlelangleequal +| 9 = @ruby_operator_assignment_ranglerangleequal +| 10 = @ruby_operator_assignment_caretequal +| 11 = @ruby_operator_assignment_pipeequal +| 12 = @ruby_operator_assignment_pipepipeequal +; + + +@ruby_operator_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_operator_assignment_def( + unique int id: @ruby_operator_assignment, + int left: @ruby_underscore_lhs ref, + int operator: int ref, + int right: @ruby_operator_assignment_right_type ref, + int loc: @location ref +); + +ruby_optional_parameter_def( + unique int id: @ruby_optional_parameter, + int name: @ruby_token_identifier ref, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_pair_key_type = @ruby_string__ | @ruby_token_hash_key_symbol | @ruby_underscore_arg + +ruby_pair_def( + unique int id: @ruby_pair, + int key__: @ruby_pair_key_type ref, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_parenthesized_statements_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_parenthesized_statements, index] +ruby_parenthesized_statements_child( + int ruby_parenthesized_statements: @ruby_parenthesized_statements ref, + int index: int ref, + unique int child: @ruby_parenthesized_statements_child_type ref +); + +ruby_parenthesized_statements_def( + unique int id: @ruby_parenthesized_statements, + int loc: @location ref +); + +@ruby_pattern_child_type = @ruby_splat_argument | @ruby_underscore_arg + +ruby_pattern_def( + unique int id: @ruby_pattern, + int child: @ruby_pattern_child_type ref, + int loc: @location ref +); + +@ruby_program_child_type = @ruby_token_empty_statement | @ruby_token_uninterpreted | @ruby_underscore_statement + +#keyset[ruby_program, index] +ruby_program_child( + int ruby_program: @ruby_program ref, + int index: int ref, + unique int child: @ruby_program_child_type ref +); + +ruby_program_def( + unique int id: @ruby_program, + int loc: @location ref +); + +ruby_range_begin( + unique int ruby_range: @ruby_range ref, + unique int begin: @ruby_underscore_arg ref +); + +ruby_range_end( + unique int ruby_range: @ruby_range ref, + unique int end: @ruby_underscore_arg ref +); + +case @ruby_range.operator of + 0 = @ruby_range_dotdot +| 1 = @ruby_range_dotdotdot +; + + +ruby_range_def( + unique int id: @ruby_range, + int operator: int ref, + int loc: @location ref +); + +@ruby_rational_child_type = @ruby_token_float | @ruby_token_integer + +ruby_rational_def( + unique int id: @ruby_rational, + int child: @ruby_rational_child_type ref, + int loc: @location ref +); + +ruby_redo_child( + unique int ruby_redo: @ruby_redo ref, + unique int child: @ruby_argument_list ref +); + +ruby_redo_def( + unique int id: @ruby_redo, + int loc: @location ref +); + +@ruby_regex_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_regex, index] +ruby_regex_child( + int ruby_regex: @ruby_regex ref, + int index: int ref, + unique int child: @ruby_regex_child_type ref +); + +ruby_regex_def( + unique int id: @ruby_regex, + int loc: @location ref +); + +ruby_rescue_body( + unique int ruby_rescue: @ruby_rescue ref, + unique int body: @ruby_then ref +); + +ruby_rescue_exceptions( + unique int ruby_rescue: @ruby_rescue ref, + unique int exceptions: @ruby_exceptions ref +); + +ruby_rescue_variable( + unique int ruby_rescue: @ruby_rescue ref, + unique int variable: @ruby_exception_variable ref +); + +ruby_rescue_def( + unique int id: @ruby_rescue, + int loc: @location ref +); + +@ruby_rescue_modifier_handler_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_rescue_modifier_def( + unique int id: @ruby_rescue_modifier, + int body: @ruby_underscore_statement ref, + int handler: @ruby_rescue_modifier_handler_type ref, + int loc: @location ref +); + +ruby_rest_assignment_child( + unique int ruby_rest_assignment: @ruby_rest_assignment ref, + unique int child: @ruby_underscore_lhs ref +); + +ruby_rest_assignment_def( + unique int id: @ruby_rest_assignment, + int loc: @location ref +); + +ruby_retry_child( + unique int ruby_retry: @ruby_retry ref, + unique int child: @ruby_argument_list ref +); + +ruby_retry_def( + unique int id: @ruby_retry, + int loc: @location ref +); + +ruby_return_child( + unique int ruby_return: @ruby_return ref, + unique int child: @ruby_argument_list ref +); + +ruby_return_def( + unique int id: @ruby_return, + int loc: @location ref +); + +@ruby_right_assignment_list_child_type = @ruby_splat_argument | @ruby_underscore_arg + +#keyset[ruby_right_assignment_list, index] +ruby_right_assignment_list_child( + int ruby_right_assignment_list: @ruby_right_assignment_list ref, + int index: int ref, + unique int child: @ruby_right_assignment_list_child_type ref +); + +ruby_right_assignment_list_def( + unique int id: @ruby_right_assignment_list, + int loc: @location ref +); + +@ruby_scope_resolution_name_type = @ruby_token_constant | @ruby_token_identifier + +ruby_scope_resolution_scope( + unique int ruby_scope_resolution: @ruby_scope_resolution ref, + unique int scope: @ruby_underscore_primary ref +); + +ruby_scope_resolution_def( + unique int id: @ruby_scope_resolution, + int name: @ruby_scope_resolution_name_type ref, + int loc: @location ref +); + +ruby_setter_def( + unique int id: @ruby_setter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_singleton_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_singleton_class, index] +ruby_singleton_class_child( + int ruby_singleton_class: @ruby_singleton_class ref, + int index: int ref, + unique int child: @ruby_singleton_class_child_type ref +); + +ruby_singleton_class_def( + unique int id: @ruby_singleton_class, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_singleton_method_object_type = @ruby_underscore_arg | @ruby_underscore_variable + +ruby_singleton_method_parameters( + unique int ruby_singleton_method: @ruby_singleton_method ref, + unique int parameters: @ruby_method_parameters ref +); + +@ruby_singleton_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_singleton_method, index] +ruby_singleton_method_child( + int ruby_singleton_method: @ruby_singleton_method ref, + int index: int ref, + unique int child: @ruby_singleton_method_child_type ref +); + +ruby_singleton_method_def( + unique int id: @ruby_singleton_method, + int name: @ruby_underscore_method_name ref, + int object: @ruby_singleton_method_object_type ref, + int loc: @location ref +); + +ruby_splat_argument_def( + unique int id: @ruby_splat_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_splat_parameter_name( + unique int ruby_splat_parameter: @ruby_splat_parameter ref, + unique int name: @ruby_token_identifier ref +); + +ruby_splat_parameter_def( + unique int id: @ruby_splat_parameter, + int loc: @location ref +); + +@ruby_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_string__, index] +ruby_string_child( + int ruby_string__: @ruby_string__ ref, + int index: int ref, + unique int child: @ruby_string_child_type ref +); + +ruby_string_def( + unique int id: @ruby_string__, + int loc: @location ref +); + +#keyset[ruby_string_array, index] +ruby_string_array_child( + int ruby_string_array: @ruby_string_array ref, + int index: int ref, + unique int child: @ruby_bare_string ref +); + +ruby_string_array_def( + unique int id: @ruby_string_array, + int loc: @location ref +); + +@ruby_subshell_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_subshell, index] +ruby_subshell_child( + int ruby_subshell: @ruby_subshell ref, + int index: int ref, + unique int child: @ruby_subshell_child_type ref +); + +ruby_subshell_def( + unique int id: @ruby_subshell, + int loc: @location ref +); + +@ruby_superclass_child_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_superclass_def( + unique int id: @ruby_superclass, + int child: @ruby_superclass_child_type ref, + int loc: @location ref +); + +#keyset[ruby_symbol_array, index] +ruby_symbol_array_child( + int ruby_symbol_array: @ruby_symbol_array ref, + int index: int ref, + unique int child: @ruby_bare_symbol ref +); + +ruby_symbol_array_def( + unique int id: @ruby_symbol_array, + int loc: @location ref +); + +@ruby_then_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_then, index] +ruby_then_child( + int ruby_then: @ruby_then ref, + int index: int ref, + unique int child: @ruby_then_child_type ref +); + +ruby_then_def( + unique int id: @ruby_then, + int loc: @location ref +); + +@ruby_unary_operand_type = @ruby_break | @ruby_call | @ruby_next | @ruby_parenthesized_statements | @ruby_return | @ruby_token_float | @ruby_token_integer | @ruby_underscore_arg | @ruby_yield + +case @ruby_unary.operator of + 0 = @ruby_unary_bang +| 1 = @ruby_unary_plus +| 2 = @ruby_unary_minus +| 3 = @ruby_unary_definedquestion +| 4 = @ruby_unary_not +| 5 = @ruby_unary_tilde +; + + +ruby_unary_def( + unique int id: @ruby_unary, + int operand: @ruby_unary_operand_type ref, + int operator: int ref, + int loc: @location ref +); + +#keyset[ruby_undef, index] +ruby_undef_child( + int ruby_undef: @ruby_undef ref, + int index: int ref, + unique int child: @ruby_underscore_method_name ref +); + +ruby_undef_def( + unique int id: @ruby_undef, + int loc: @location ref +); + +@ruby_unless_alternative_type = @ruby_else | @ruby_elsif + +ruby_unless_alternative( + unique int ruby_unless: @ruby_unless ref, + unique int alternative: @ruby_unless_alternative_type ref +); + +ruby_unless_consequence( + unique int ruby_unless: @ruby_unless ref, + unique int consequence: @ruby_then ref +); + +ruby_unless_def( + unique int id: @ruby_unless, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_unless_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_unless_modifier_def( + unique int id: @ruby_unless_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_unless_modifier_condition_type ref, + int loc: @location ref +); + +ruby_until_def( + unique int id: @ruby_until, + int body: @ruby_do ref, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_until_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_until_modifier_def( + unique int id: @ruby_until_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_until_modifier_condition_type ref, + int loc: @location ref +); + +ruby_when_body( + unique int ruby_when: @ruby_when ref, + unique int body: @ruby_then ref +); + +#keyset[ruby_when, index] +ruby_when_pattern( + int ruby_when: @ruby_when ref, + int index: int ref, + unique int pattern: @ruby_pattern ref +); + +ruby_when_def( + unique int id: @ruby_when, + int loc: @location ref +); + +ruby_while_def( + unique int id: @ruby_while, + int body: @ruby_do ref, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_while_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_while_modifier_def( + unique int id: @ruby_while_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_while_modifier_condition_type ref, + int loc: @location ref +); + +ruby_yield_child( + unique int ruby_yield: @ruby_yield ref, + unique int child: @ruby_argument_list ref +); + +ruby_yield_def( + unique int id: @ruby_yield, + int loc: @location ref +); + +ruby_tokeninfo( + unique int id: @ruby_token, + int kind: int ref, + int file: @file ref, + int idx: int ref, + string value: string ref, + int loc: @location ref +); + +case @ruby_token.kind of + 0 = @ruby_reserved_word +| 1 = @ruby_token_character +| 2 = @ruby_token_class_variable +| 3 = @ruby_token_comment +| 4 = @ruby_token_complex +| 5 = @ruby_token_constant +| 6 = @ruby_token_empty_statement +| 7 = @ruby_token_escape_sequence +| 8 = @ruby_token_false +| 9 = @ruby_token_float +| 10 = @ruby_token_global_variable +| 11 = @ruby_token_hash_key_symbol +| 12 = @ruby_token_heredoc_beginning +| 13 = @ruby_token_heredoc_content +| 14 = @ruby_token_heredoc_end +| 15 = @ruby_token_identifier +| 16 = @ruby_token_instance_variable +| 17 = @ruby_token_integer +| 18 = @ruby_token_nil +| 19 = @ruby_token_operator +| 20 = @ruby_token_self +| 21 = @ruby_token_simple_symbol +| 22 = @ruby_token_string_content +| 23 = @ruby_token_super +| 24 = @ruby_token_true +| 25 = @ruby_token_uninterpreted +; + + +@ruby_ast_node = @ruby_alias | @ruby_argument_list | @ruby_array | @ruby_assignment | @ruby_bare_string | @ruby_bare_symbol | @ruby_begin | @ruby_begin_block | @ruby_binary | @ruby_block | @ruby_block_argument | @ruby_block_parameter | @ruby_block_parameters | @ruby_break | @ruby_call | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_conditional | @ruby_delimited_symbol | @ruby_destructured_left_assignment | @ruby_destructured_parameter | @ruby_do | @ruby_do_block | @ruby_element_reference | @ruby_else | @ruby_elsif | @ruby_end_block | @ruby_ensure | @ruby_exception_variable | @ruby_exceptions | @ruby_for | @ruby_hash | @ruby_hash_splat_argument | @ruby_hash_splat_parameter | @ruby_heredoc_body | @ruby_if | @ruby_if_modifier | @ruby_in | @ruby_interpolation | @ruby_keyword_parameter | @ruby_lambda | @ruby_lambda_parameters | @ruby_left_assignment_list | @ruby_method | @ruby_method_parameters | @ruby_module | @ruby_next | @ruby_operator_assignment | @ruby_optional_parameter | @ruby_pair | @ruby_parenthesized_statements | @ruby_pattern | @ruby_program | @ruby_range | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_rescue | @ruby_rescue_modifier | @ruby_rest_assignment | @ruby_retry | @ruby_return | @ruby_right_assignment_list | @ruby_scope_resolution | @ruby_setter | @ruby_singleton_class | @ruby_singleton_method | @ruby_splat_argument | @ruby_splat_parameter | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_superclass | @ruby_symbol_array | @ruby_then | @ruby_token | @ruby_unary | @ruby_undef | @ruby_unless | @ruby_unless_modifier | @ruby_until | @ruby_until_modifier | @ruby_when | @ruby_while | @ruby_while_modifier | @ruby_yield + +@ruby_ast_node_parent = @file | @ruby_ast_node + +#keyset[parent, parent_index] +ruby_ast_node_parent( + int child: @ruby_ast_node ref, + int parent: @ruby_ast_node_parent ref, + int parent_index: int ref +); + +erb_comment_directive_def( + unique int id: @erb_comment_directive, + int child: @erb_token_comment ref, + int loc: @location ref +); + +erb_directive_def( + unique int id: @erb_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +erb_graphql_directive_def( + unique int id: @erb_graphql_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +erb_output_directive_def( + unique int id: @erb_output_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +@erb_template_child_type = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_token_content + +#keyset[erb_template, index] +erb_template_child( + int erb_template: @erb_template ref, + int index: int ref, + unique int child: @erb_template_child_type ref +); + +erb_template_def( + unique int id: @erb_template, + int loc: @location ref +); + +erb_tokeninfo( + unique int id: @erb_token, + int kind: int ref, + int file: @file ref, + int idx: int ref, + string value: string ref, + int loc: @location ref +); + +case @erb_token.kind of + 0 = @erb_reserved_word +| 1 = @erb_token_code +| 2 = @erb_token_comment +| 3 = @erb_token_content +; + + +@erb_ast_node = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_template | @erb_token + +@erb_ast_node_parent = @erb_ast_node | @file + +#keyset[parent, parent_index] +erb_ast_node_parent( + int child: @erb_ast_node ref, + int parent: @erb_ast_node_parent ref, + int parent_index: int ref +); + diff --git a/ruby/ql/lib/upgrades/40be81bc2086eb0368f33c770e0a84817bb340c3/ruby.dbscheme.stats b/ruby/ql/lib/upgrades/40be81bc2086eb0368f33c770e0a84817bb340c3/ruby.dbscheme.stats new file mode 100644 index 000000000000..665de5190c26 --- /dev/null +++ b/ruby/ql/lib/upgrades/40be81bc2086eb0368f33c770e0a84817bb340c3/ruby.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/ruby/ql/lib/upgrades/40be81bc2086eb0368f33c770e0a84817bb340c3/upgrade.properties b/ruby/ql/lib/upgrades/40be81bc2086eb0368f33c770e0a84817bb340c3/upgrade.properties new file mode 100644 index 000000000000..2c4e8cc41e80 --- /dev/null +++ b/ruby/ql/lib/upgrades/40be81bc2086eb0368f33c770e0a84817bb340c3/upgrade.properties @@ -0,0 +1,165 @@ +description: Add ERB tables and rename Ruby tables +compatibility: backwards +ruby_alias_def.rel: reorder alias_def.rel ( int id, int alias, int name, int loc ) id alias name loc +ruby_argument_list_child.rel: reorder argument_list_child.rel ( int ruby_argument_list, int index, int child ) ruby_argument_list index child +ruby_argument_list_def.rel: reorder argument_list_def.rel ( int id, int loc ) id loc +ruby_array_child.rel: reorder array_child.rel ( int ruby_array, int index, int child ) ruby_array index child +ruby_array_def.rel: reorder array_def.rel ( int id, int loc ) id loc +ruby_assignment_def.rel: reorder assignment_def.rel ( int id, int left, int right, int loc ) id left right loc +ruby_bare_string_child.rel: reorder bare_string_child.rel ( int ruby_bare_string, int index, int child ) ruby_bare_string index child +ruby_bare_string_def.rel: reorder bare_string_def.rel ( int id, int loc ) id loc +ruby_bare_symbol_child.rel: reorder bare_symbol_child.rel ( int ruby_bare_symbol, int index, int child ) ruby_bare_symbol index child +ruby_bare_symbol_def.rel: reorder bare_symbol_def.rel ( int id, int loc ) id loc +ruby_begin_child.rel: reorder begin_child.rel ( int ruby_begin, int index, int child ) ruby_begin index child +ruby_begin_def.rel: reorder begin_def.rel ( int id, int loc ) id loc +ruby_begin_block_child.rel: reorder begin_block_child.rel ( int ruby_begin_block, int index, int child ) ruby_begin_block index child +ruby_begin_block_def.rel: reorder begin_block_def.rel ( int id, int loc ) id loc +ruby_binary_def.rel: reorder binary_def.rel ( int id, int left, int operator, int right, int loc ) id left operator right loc +ruby_block_parameters.rel: reorder block_parameters.rel ( int ruby_block, int parameters ) ruby_block parameters +ruby_block_child.rel: reorder block_child.rel ( int ruby_block, int index, int child ) ruby_block index child +ruby_block_def.rel: reorder block_def.rel ( int id, int loc ) id loc +ruby_block_argument_def.rel: reorder block_argument_def.rel ( int id, int child, int loc ) id child loc +ruby_block_parameter_def.rel: reorder block_parameter_def.rel ( int id, int name, int loc ) id name loc +ruby_block_parameters_child.rel: reorder block_parameters_child.rel ( int ruby_block_parameters, int index, int child ) ruby_block_parameters index child +ruby_block_parameters_def.rel: reorder block_parameters_def.rel ( int id, int loc ) id loc +ruby_break_child.rel: reorder break_child.rel ( int ruby_break, int child ) ruby_break child +ruby_break_def.rel: reorder break_def.rel ( int id, int loc ) id loc +ruby_call_arguments.rel: reorder call_arguments.rel ( int ruby_call, int arguments ) ruby_call arguments +ruby_call_block.rel: reorder call_block.rel ( int ruby_call, int block ) ruby_call block +ruby_call_receiver.rel: reorder call_receiver.rel ( int ruby_call, int receiver ) ruby_call receiver +ruby_call_def.rel: reorder call_def.rel ( int id, int method, int loc ) id method loc +ruby_case_value.rel: reorder case_value.rel ( int ruby_case__, int value ) ruby_case__ value +ruby_case_child.rel: reorder case_child.rel ( int ruby_case__, int index, int child ) ruby_case__ index child +ruby_case_def.rel: reorder case_def.rel ( int id, int loc ) id loc +ruby_chained_string_child.rel: reorder chained_string_child.rel ( int ruby_chained_string, int index, int child ) ruby_chained_string index child +ruby_chained_string_def.rel: reorder chained_string_def.rel ( int id, int loc ) id loc +ruby_class_superclass.rel: reorder class_superclass.rel ( int ruby_class, int superclass ) ruby_class superclass +ruby_class_child.rel: reorder class_child.rel ( int ruby_class, int index, int child ) ruby_class index child +ruby_class_def.rel: reorder class_def.rel ( int id, int name, int loc ) id name loc +ruby_conditional_def.rel: reorder conditional_def.rel ( int id, int alternative, int condition, int consequence, int loc ) id alternative condition consequence loc +ruby_delimited_symbol_child.rel: reorder delimited_symbol_child.rel ( int ruby_delimited_symbol, int index, int child ) ruby_delimited_symbol index child +ruby_delimited_symbol_def.rel: reorder delimited_symbol_def.rel ( int id, int loc ) id loc +ruby_destructured_left_assignment_child.rel: reorder destructured_left_assignment_child.rel ( int ruby_destructured_left_assignment, int index, int child ) ruby_destructured_left_assignment index child +ruby_destructured_left_assignment_def.rel: reorder destructured_left_assignment_def.rel ( int id, int loc ) id loc +ruby_destructured_parameter_child.rel: reorder destructured_parameter_child.rel ( int ruby_destructured_parameter, int index, int child ) ruby_destructured_parameter index child +ruby_destructured_parameter_def.rel: reorder destructured_parameter_def.rel ( int id, int loc ) id loc +ruby_do_child.rel: reorder do_child.rel ( int ruby_do, int index, int child ) ruby_do index child +ruby_do_def.rel: reorder do_def.rel ( int id, int loc ) id loc +ruby_do_block_parameters.rel: reorder do_block_parameters.rel ( int ruby_do_block, int parameters ) ruby_do_block parameters +ruby_do_block_child.rel: reorder do_block_child.rel ( int ruby_do_block, int index, int child ) ruby_do_block index child +ruby_do_block_def.rel: reorder do_block_def.rel ( int id, int loc ) id loc +ruby_element_reference_child.rel: reorder element_reference_child.rel ( int ruby_element_reference, int index, int child ) ruby_element_reference index child +ruby_element_reference_def.rel: reorder element_reference_def.rel ( int id, int object, int loc ) id object loc +ruby_else_child.rel: reorder else_child.rel ( int ruby_else, int index, int child ) ruby_else index child +ruby_else_def.rel: reorder else_def.rel ( int id, int loc ) id loc +ruby_elsif_alternative.rel: reorder elsif_alternative.rel ( int ruby_elsif, int alternative ) ruby_elsif alternative +ruby_elsif_consequence.rel: reorder elsif_consequence.rel ( int ruby_elsif, int consequence ) ruby_elsif consequence +ruby_elsif_def.rel: reorder elsif_def.rel ( int id, int condition, int loc ) id condition loc +ruby_end_block_child.rel: reorder end_block_child.rel ( int ruby_end_block, int index, int child ) ruby_end_block index child +ruby_end_block_def.rel: reorder end_block_def.rel ( int id, int loc ) id loc +ruby_ensure_child.rel: reorder ensure_child.rel ( int ruby_ensure, int index, int child ) ruby_ensure index child +ruby_ensure_def.rel: reorder ensure_def.rel ( int id, int loc ) id loc +ruby_exception_variable_def.rel: reorder exception_variable_def.rel ( int id, int child, int loc ) id child loc +ruby_exceptions_child.rel: reorder exceptions_child.rel ( int ruby_exceptions, int index, int child ) ruby_exceptions index child +ruby_exceptions_def.rel: reorder exceptions_def.rel ( int id, int loc ) id loc +ruby_for_def.rel: reorder for_def.rel ( int id, int body, int pattern, int value, int loc ) id body pattern value loc +ruby_hash_child.rel: reorder hash_child.rel ( int ruby_hash, int index, int child ) ruby_hash index child +ruby_hash_def.rel: reorder hash_def.rel ( int id, int loc ) id loc +ruby_hash_splat_argument_def.rel: reorder hash_splat_argument_def.rel ( int id, int child, int loc ) id child loc +ruby_hash_splat_parameter_name.rel: reorder hash_splat_parameter_name.rel ( int ruby_hash_splat_parameter, int name ) ruby_hash_splat_parameter name +ruby_hash_splat_parameter_def.rel: reorder hash_splat_parameter_def.rel ( int id, int loc ) id loc +ruby_heredoc_body_child.rel: reorder heredoc_body_child.rel ( int ruby_heredoc_body, int index, int child ) ruby_heredoc_body index child +ruby_heredoc_body_def.rel: reorder heredoc_body_def.rel ( int id, int loc ) id loc +ruby_if_alternative.rel: reorder if_alternative.rel ( int ruby_if, int alternative ) ruby_if alternative +ruby_if_consequence.rel: reorder if_consequence.rel ( int ruby_if, int consequence ) ruby_if consequence +ruby_if_def.rel: reorder if_def.rel ( int id, int condition, int loc ) id condition loc +ruby_if_modifier_def.rel: reorder if_modifier_def.rel ( int id, int body, int condition, int loc ) id body condition loc +ruby_in_def.rel: reorder in_def.rel ( int id, int child, int loc ) id child loc +ruby_interpolation_child.rel: reorder interpolation_child.rel ( int ruby_interpolation, int index, int child ) ruby_interpolation index child +ruby_interpolation_def.rel: reorder interpolation_def.rel ( int id, int loc ) id loc +ruby_keyword_parameter_value.rel: reorder keyword_parameter_value.rel ( int ruby_keyword_parameter, int value ) ruby_keyword_parameter value +ruby_keyword_parameter_def.rel: reorder keyword_parameter_def.rel ( int id, int name, int loc ) id name loc +ruby_lambda_parameters.rel: reorder lambda_parameters.rel ( int ruby_lambda, int parameters ) ruby_lambda parameters +ruby_lambda_def.rel: reorder lambda_def.rel ( int id, int body, int loc ) id body loc +ruby_lambda_parameters_child.rel: reorder lambda_parameters_child.rel ( int ruby_lambda_parameters, int index, int child ) ruby_lambda_parameters index child +ruby_lambda_parameters_def.rel: reorder lambda_parameters_def.rel ( int id, int loc ) id loc +ruby_left_assignment_list_child.rel: reorder left_assignment_list_child.rel ( int ruby_left_assignment_list, int index, int child ) ruby_left_assignment_list index child +ruby_left_assignment_list_def.rel: reorder left_assignment_list_def.rel ( int id, int loc ) id loc +ruby_method_parameters.rel: reorder method_parameters.rel ( int ruby_method, int parameters ) ruby_method parameters +ruby_method_child.rel: reorder method_child.rel ( int ruby_method, int index, int child ) ruby_method index child +ruby_method_def.rel: reorder method_def.rel ( int id, int name, int loc ) id name loc +ruby_method_parameters_child.rel: reorder method_parameters_child.rel ( int ruby_method_parameters, int index, int child ) ruby_method_parameters index child +ruby_method_parameters_def.rel: reorder method_parameters_def.rel ( int id, int loc ) id loc +ruby_module_child.rel: reorder module_child.rel ( int ruby_module, int index, int child ) ruby_module index child +ruby_module_def.rel: reorder module_def.rel ( int id, int name, int loc ) id name loc +ruby_next_child.rel: reorder next_child.rel ( int ruby_next, int child ) ruby_next child +ruby_next_def.rel: reorder next_def.rel ( int id, int loc ) id loc +ruby_operator_assignment_def.rel: reorder operator_assignment_def.rel ( int id, int left, int operator, int right, int loc ) id left operator right loc +ruby_optional_parameter_def.rel: reorder optional_parameter_def.rel ( int id, int name, int value, int loc ) id name value loc +ruby_pair_def.rel: reorder pair_def.rel ( int id, int key__, int value, int loc ) id key__ value loc +ruby_parenthesized_statements_child.rel: reorder parenthesized_statements_child.rel ( int ruby_parenthesized_statements, int index, int child ) ruby_parenthesized_statements index child +ruby_parenthesized_statements_def.rel: reorder parenthesized_statements_def.rel ( int id, int loc ) id loc +ruby_pattern_def.rel: reorder pattern_def.rel ( int id, int child, int loc ) id child loc +ruby_program_child.rel: reorder program_child.rel ( int ruby_program, int index, int child ) ruby_program index child +ruby_program_def.rel: reorder program_def.rel ( int id, int loc ) id loc +ruby_range_begin.rel: reorder range_begin.rel ( int ruby_range, int begin ) ruby_range begin +ruby_range_end.rel: reorder range_end.rel ( int ruby_range, int end ) ruby_range end +ruby_range_def.rel: reorder range_def.rel ( int id, int operator, int loc ) id operator loc +ruby_rational_def.rel: reorder rational_def.rel ( int id, int child, int loc ) id child loc +ruby_redo_child.rel: reorder redo_child.rel ( int ruby_redo, int child ) ruby_redo child +ruby_redo_def.rel: reorder redo_def.rel ( int id, int loc ) id loc +ruby_regex_child.rel: reorder regex_child.rel ( int ruby_regex, int index, int child ) ruby_regex index child +ruby_regex_def.rel: reorder regex_def.rel ( int id, int loc ) id loc +ruby_rescue_body.rel: reorder rescue_body.rel ( int ruby_rescue, int body ) ruby_rescue body +ruby_rescue_exceptions.rel: reorder rescue_exceptions.rel ( int ruby_rescue, int exceptions ) ruby_rescue exceptions +ruby_rescue_variable.rel: reorder rescue_variable.rel ( int ruby_rescue, int variable ) ruby_rescue variable +ruby_rescue_def.rel: reorder rescue_def.rel ( int id, int loc ) id loc +ruby_rescue_modifier_def.rel: reorder rescue_modifier_def.rel ( int id, int body, int handler, int loc ) id body handler loc +ruby_rest_assignment_child.rel: reorder rest_assignment_child.rel ( int ruby_rest_assignment, int child ) ruby_rest_assignment child +ruby_rest_assignment_def.rel: reorder rest_assignment_def.rel ( int id, int loc ) id loc +ruby_retry_child.rel: reorder retry_child.rel ( int ruby_retry, int child ) ruby_retry child +ruby_retry_def.rel: reorder retry_def.rel ( int id, int loc ) id loc +ruby_return_child.rel: reorder return_child.rel ( int ruby_return, int child ) ruby_return child +ruby_return_def.rel: reorder return_def.rel ( int id, int loc ) id loc +ruby_right_assignment_list_child.rel: reorder right_assignment_list_child.rel ( int ruby_right_assignment_list, int index, int child ) ruby_right_assignment_list index child +ruby_right_assignment_list_def.rel: reorder right_assignment_list_def.rel ( int id, int loc ) id loc +ruby_scope_resolution_scope.rel: reorder scope_resolution_scope.rel ( int ruby_scope_resolution, int scope ) ruby_scope_resolution scope +ruby_scope_resolution_def.rel: reorder scope_resolution_def.rel ( int id, int name, int loc ) id name loc +ruby_setter_def.rel: reorder setter_def.rel ( int id, int name, int loc ) id name loc +ruby_singleton_class_child.rel: reorder singleton_class_child.rel ( int ruby_singleton_class, int index, int child ) ruby_singleton_class index child +ruby_singleton_class_def.rel: reorder singleton_class_def.rel ( int id, int value, int loc ) id value loc +ruby_singleton_method_parameters.rel: reorder singleton_method_parameters.rel ( int ruby_singleton_method, int parameters ) ruby_singleton_method parameters +ruby_singleton_method_child.rel: reorder singleton_method_child.rel ( int ruby_singleton_method, int index, int child ) ruby_singleton_method index child +ruby_singleton_method_def.rel: reorder singleton_method_def.rel ( int id, int name, int object, int loc ) id name object loc +ruby_splat_argument_def.rel: reorder splat_argument_def.rel ( int id, int child, int loc ) id child loc +ruby_splat_parameter_name.rel: reorder splat_parameter_name.rel ( int ruby_splat_parameter, int name ) ruby_splat_parameter name +ruby_splat_parameter_def.rel: reorder splat_parameter_def.rel ( int id, int loc ) id loc +ruby_string_child.rel: reorder string_child.rel ( int ruby_string__, int index, int child ) ruby_string__ index child +ruby_string_def.rel: reorder string_def.rel ( int id, int loc ) id loc +ruby_string_array_child.rel: reorder string_array_child.rel ( int ruby_string_array, int index, int child ) ruby_string_array index child +ruby_string_array_def.rel: reorder string_array_def.rel ( int id, int loc ) id loc +ruby_subshell_child.rel: reorder subshell_child.rel ( int ruby_subshell, int index, int child ) ruby_subshell index child +ruby_subshell_def.rel: reorder subshell_def.rel ( int id, int loc ) id loc +ruby_superclass_def.rel: reorder superclass_def.rel ( int id, int child, int loc ) id child loc +ruby_symbol_array_child.rel: reorder symbol_array_child.rel ( int ruby_symbol_array, int index, int child ) ruby_symbol_array index child +ruby_symbol_array_def.rel: reorder symbol_array_def.rel ( int id, int loc ) id loc +ruby_then_child.rel: reorder then_child.rel ( int ruby_then, int index, int child ) ruby_then index child +ruby_then_def.rel: reorder then_def.rel ( int id, int loc ) id loc +ruby_unary_def.rel: reorder unary_def.rel ( int id, int operand, int operator, int loc ) id operand operator loc +ruby_undef_child.rel: reorder undef_child.rel ( int ruby_undef, int index, int child ) ruby_undef index child +ruby_undef_def.rel: reorder undef_def.rel ( int id, int loc ) id loc +ruby_unless_alternative.rel: reorder unless_alternative.rel ( int ruby_unless, int alternative ) ruby_unless alternative +ruby_unless_consequence.rel: reorder unless_consequence.rel ( int ruby_unless, int consequence ) ruby_unless consequence +ruby_unless_def.rel: reorder unless_def.rel ( int id, int condition, int loc ) id condition loc +ruby_unless_modifier_def.rel: reorder unless_modifier_def.rel ( int id, int body, int condition, int loc ) id body condition loc +ruby_until_def.rel: reorder until_def.rel ( int id, int body, int condition, int loc ) id body condition loc +ruby_until_modifier_def.rel: reorder until_modifier_def.rel ( int id, int body, int condition, int loc ) id body condition loc +ruby_when_body.rel: reorder when_body.rel ( int ruby_when, int body ) ruby_when body +ruby_when_pattern.rel: reorder when_pattern.rel ( int ruby_when, int index, int pattern ) ruby_when index pattern +ruby_when_def.rel: reorder when_def.rel ( int id, int loc ) id loc +ruby_while_def.rel: reorder while_def.rel ( int id, int body, int condition, int loc ) id body condition loc +ruby_while_modifier_def.rel: reorder while_modifier_def.rel ( int id, int body, int condition, int loc ) id body condition loc +ruby_yield_child.rel: reorder yield_child.rel ( int ruby_yield, int child ) ruby_yield child +ruby_yield_def.rel: reorder yield_def.rel ( int id, int loc ) id loc +ruby_tokeninfo.rel: reorder tokeninfo.rel ( int id, int kind, int file, int idx, string value, int loc ) id kind file idx value loc +ruby_ast_node_parent.rel: reorder ast_node_parent.rel ( int child, int parent, int parent_index) child parent parent_index diff --git a/ruby/ql/lib/upgrades/8725deeb2fa6627c45235f18b7c121c35498dac7/old.dbscheme b/ruby/ql/lib/upgrades/8725deeb2fa6627c45235f18b7c121c35498dac7/old.dbscheme new file mode 100644 index 000000000000..8725deeb2fa6 --- /dev/null +++ b/ruby/ql/lib/upgrades/8725deeb2fa6627c45235f18b7c121c35498dac7/old.dbscheme @@ -0,0 +1,1250 @@ +// CodeQL database schema for Ruby +// 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 +); + +@sourceline = @file + +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref +); + +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref +); + +folders( + unique int id: @folder, + string name: string ref, + string simple: string ref +); + +@container = @file | @folder + +containerparent( + int parent: @container ref, + unique int child: @container ref +); + +sourceLocationPrefix( + string prefix: string ref +); + +@underscore_arg = @assignment | @binary | @conditional | @operator_assignment | @range | @unary | @underscore_primary + +@underscore_lhs = @call | @element_reference | @scope_resolution | @token_false | @token_nil | @token_true | @underscore_variable + +@underscore_method_name = @delimited_symbol | @setter | @token_class_variable | @token_constant | @token_global_variable | @token_identifier | @token_instance_variable | @token_operator | @token_simple_symbol + +@underscore_primary = @array | @begin | @break | @case__ | @chained_string | @class | @delimited_symbol | @for | @hash | @if | @lambda | @method | @module | @next | @parenthesized_statements | @rational | @redo | @regex | @retry | @return | @singleton_class | @singleton_method | @string__ | @string_array | @subshell | @symbol_array | @token_character | @token_complex | @token_float | @token_heredoc_beginning | @token_integer | @token_simple_symbol | @unary | @underscore_lhs | @unless | @until | @while | @yield + +@underscore_statement = @alias | @assignment | @begin_block | @binary | @break | @call | @end_block | @if_modifier | @next | @operator_assignment | @rescue_modifier | @return | @unary | @undef | @underscore_arg | @unless_modifier | @until_modifier | @while_modifier | @yield + +@underscore_variable = @token_class_variable | @token_constant | @token_global_variable | @token_identifier | @token_instance_variable | @token_self | @token_super + +alias_def( + unique int id: @alias, + int alias: @underscore_method_name ref, + int name: @underscore_method_name ref, + int loc: @location ref +); + +@argument_list_child_type = @block_argument | @break | @call | @hash_splat_argument | @next | @pair | @return | @splat_argument | @underscore_arg | @yield + +#keyset[argument_list, index] +argument_list_child( + int argument_list: @argument_list ref, + int index: int ref, + unique int child: @argument_list_child_type ref +); + +argument_list_def( + unique int id: @argument_list, + int loc: @location ref +); + +@array_child_type = @block_argument | @break | @call | @hash_splat_argument | @next | @pair | @return | @splat_argument | @underscore_arg | @yield + +#keyset[array, index] +array_child( + int array: @array ref, + int index: int ref, + unique int child: @array_child_type ref +); + +array_def( + unique int id: @array, + int loc: @location ref +); + +@assignment_left_type = @left_assignment_list | @underscore_lhs + +@assignment_right_type = @break | @call | @next | @return | @right_assignment_list | @splat_argument | @underscore_arg | @yield + +assignment_def( + unique int id: @assignment, + int left: @assignment_left_type ref, + int right: @assignment_right_type ref, + int loc: @location ref +); + +@bare_string_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[bare_string, index] +bare_string_child( + int bare_string: @bare_string ref, + int index: int ref, + unique int child: @bare_string_child_type ref +); + +bare_string_def( + unique int id: @bare_string, + int loc: @location ref +); + +@bare_symbol_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[bare_symbol, index] +bare_symbol_child( + int bare_symbol: @bare_symbol ref, + int index: int ref, + unique int child: @bare_symbol_child_type ref +); + +bare_symbol_def( + unique int id: @bare_symbol, + int loc: @location ref +); + +@begin_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[begin, index] +begin_child( + int begin: @begin ref, + int index: int ref, + unique int child: @begin_child_type ref +); + +begin_def( + unique int id: @begin, + int loc: @location ref +); + +@begin_block_child_type = @token_empty_statement | @underscore_statement + +#keyset[begin_block, index] +begin_block_child( + int begin_block: @begin_block ref, + int index: int ref, + unique int child: @begin_block_child_type ref +); + +begin_block_def( + unique int id: @begin_block, + int loc: @location ref +); + +@binary_left_type = @break | @call | @next | @return | @underscore_arg | @yield + +case @binary.operator of + 0 = @binary_bangequal +| 1 = @binary_bangtilde +| 2 = @binary_percent +| 3 = @binary_ampersand +| 4 = @binary_ampersandampersand +| 5 = @binary_star +| 6 = @binary_starstar +| 7 = @binary_plus +| 8 = @binary_minus +| 9 = @binary_slash +| 10 = @binary_langle +| 11 = @binary_langlelangle +| 12 = @binary_langleequal +| 13 = @binary_langleequalrangle +| 14 = @binary_equalequal +| 15 = @binary_equalequalequal +| 16 = @binary_equaltilde +| 17 = @binary_rangle +| 18 = @binary_rangleequal +| 19 = @binary_ranglerangle +| 20 = @binary_caret +| 21 = @binary_and +| 22 = @binary_or +| 23 = @binary_pipe +| 24 = @binary_pipepipe +; + + +@binary_right_type = @break | @call | @next | @return | @underscore_arg | @yield + +binary_def( + unique int id: @binary, + int left: @binary_left_type ref, + int operator: int ref, + int right: @binary_right_type ref, + int loc: @location ref +); + +block_parameters( + unique int block: @block ref, + unique int parameters: @block_parameters ref +); + +@block_child_type = @token_empty_statement | @underscore_statement + +#keyset[block, index] +block_child( + int block: @block ref, + int index: int ref, + unique int child: @block_child_type ref +); + +block_def( + unique int id: @block, + int loc: @location ref +); + +block_argument_def( + unique int id: @block_argument, + int child: @underscore_arg ref, + int loc: @location ref +); + +block_parameter_def( + unique int id: @block_parameter, + int name: @token_identifier ref, + int loc: @location ref +); + +@block_parameters_child_type = @block_parameter | @destructured_parameter | @hash_splat_parameter | @keyword_parameter | @optional_parameter | @splat_parameter | @token_identifier + +#keyset[block_parameters, index] +block_parameters_child( + int block_parameters: @block_parameters ref, + int index: int ref, + unique int child: @block_parameters_child_type ref +); + +block_parameters_def( + unique int id: @block_parameters, + int loc: @location ref +); + +break_child( + unique int break: @break ref, + unique int child: @argument_list ref +); + +break_def( + unique int id: @break, + int loc: @location ref +); + +call_arguments( + unique int call: @call ref, + unique int arguments: @argument_list ref +); + +@call_block_type = @block | @do_block + +call_block( + unique int call: @call ref, + unique int block: @call_block_type ref +); + +@call_method_type = @argument_list | @scope_resolution | @token_operator | @underscore_variable + +@call_receiver_type = @call | @underscore_primary + +call_receiver( + unique int call: @call ref, + unique int receiver: @call_receiver_type ref +); + +call_def( + unique int id: @call, + int method: @call_method_type ref, + int loc: @location ref +); + +case_value( + unique int case__: @case__ ref, + unique int value: @underscore_statement ref +); + +@case_child_type = @else | @when + +#keyset[case__, index] +case_child( + int case__: @case__ ref, + int index: int ref, + unique int child: @case_child_type ref +); + +case_def( + unique int id: @case__, + int loc: @location ref +); + +#keyset[chained_string, index] +chained_string_child( + int chained_string: @chained_string ref, + int index: int ref, + unique int child: @string__ ref +); + +chained_string_def( + unique int id: @chained_string, + int loc: @location ref +); + +@class_name_type = @scope_resolution | @token_constant + +class_superclass( + unique int class: @class ref, + unique int superclass: @superclass ref +); + +@class_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[class, index] +class_child( + int class: @class ref, + int index: int ref, + unique int child: @class_child_type ref +); + +class_def( + unique int id: @class, + int name: @class_name_type ref, + int loc: @location ref +); + +conditional_def( + unique int id: @conditional, + int alternative: @underscore_arg ref, + int condition: @underscore_arg ref, + int consequence: @underscore_arg ref, + int loc: @location ref +); + +@delimited_symbol_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[delimited_symbol, index] +delimited_symbol_child( + int delimited_symbol: @delimited_symbol ref, + int index: int ref, + unique int child: @delimited_symbol_child_type ref +); + +delimited_symbol_def( + unique int id: @delimited_symbol, + int loc: @location ref +); + +@destructured_left_assignment_child_type = @destructured_left_assignment | @rest_assignment | @underscore_lhs + +#keyset[destructured_left_assignment, index] +destructured_left_assignment_child( + int destructured_left_assignment: @destructured_left_assignment ref, + int index: int ref, + unique int child: @destructured_left_assignment_child_type ref +); + +destructured_left_assignment_def( + unique int id: @destructured_left_assignment, + int loc: @location ref +); + +@destructured_parameter_child_type = @block_parameter | @destructured_parameter | @hash_splat_parameter | @keyword_parameter | @optional_parameter | @splat_parameter | @token_identifier + +#keyset[destructured_parameter, index] +destructured_parameter_child( + int destructured_parameter: @destructured_parameter ref, + int index: int ref, + unique int child: @destructured_parameter_child_type ref +); + +destructured_parameter_def( + unique int id: @destructured_parameter, + int loc: @location ref +); + +@do_child_type = @token_empty_statement | @underscore_statement + +#keyset[do, index] +do_child( + int do: @do ref, + int index: int ref, + unique int child: @do_child_type ref +); + +do_def( + unique int id: @do, + int loc: @location ref +); + +do_block_parameters( + unique int do_block: @do_block ref, + unique int parameters: @block_parameters ref +); + +@do_block_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[do_block, index] +do_block_child( + int do_block: @do_block ref, + int index: int ref, + unique int child: @do_block_child_type ref +); + +do_block_def( + unique int id: @do_block, + int loc: @location ref +); + +@element_reference_child_type = @block_argument | @break | @call | @hash_splat_argument | @next | @pair | @return | @splat_argument | @underscore_arg | @yield + +#keyset[element_reference, index] +element_reference_child( + int element_reference: @element_reference ref, + int index: int ref, + unique int child: @element_reference_child_type ref +); + +element_reference_def( + unique int id: @element_reference, + int object: @underscore_primary ref, + int loc: @location ref +); + +@else_child_type = @token_empty_statement | @underscore_statement + +#keyset[else, index] +else_child( + int else: @else ref, + int index: int ref, + unique int child: @else_child_type ref +); + +else_def( + unique int id: @else, + int loc: @location ref +); + +@elsif_alternative_type = @else | @elsif + +elsif_alternative( + unique int elsif: @elsif ref, + unique int alternative: @elsif_alternative_type ref +); + +elsif_consequence( + unique int elsif: @elsif ref, + unique int consequence: @then ref +); + +elsif_def( + unique int id: @elsif, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@end_block_child_type = @token_empty_statement | @underscore_statement + +#keyset[end_block, index] +end_block_child( + int end_block: @end_block ref, + int index: int ref, + unique int child: @end_block_child_type ref +); + +end_block_def( + unique int id: @end_block, + int loc: @location ref +); + +@ensure_child_type = @token_empty_statement | @underscore_statement + +#keyset[ensure, index] +ensure_child( + int ensure: @ensure ref, + int index: int ref, + unique int child: @ensure_child_type ref +); + +ensure_def( + unique int id: @ensure, + int loc: @location ref +); + +exception_variable_def( + unique int id: @exception_variable, + int child: @underscore_lhs ref, + int loc: @location ref +); + +@exceptions_child_type = @splat_argument | @underscore_arg + +#keyset[exceptions, index] +exceptions_child( + int exceptions: @exceptions ref, + int index: int ref, + unique int child: @exceptions_child_type ref +); + +exceptions_def( + unique int id: @exceptions, + int loc: @location ref +); + +@for_pattern_type = @left_assignment_list | @underscore_lhs + +for_def( + unique int id: @for, + int body: @do ref, + int pattern: @for_pattern_type ref, + int value: @in ref, + int loc: @location ref +); + +@hash_child_type = @hash_splat_argument | @pair + +#keyset[hash, index] +hash_child( + int hash: @hash ref, + int index: int ref, + unique int child: @hash_child_type ref +); + +hash_def( + unique int id: @hash, + int loc: @location ref +); + +hash_splat_argument_def( + unique int id: @hash_splat_argument, + int child: @underscore_arg ref, + int loc: @location ref +); + +hash_splat_parameter_name( + unique int hash_splat_parameter: @hash_splat_parameter ref, + unique int name: @token_identifier ref +); + +hash_splat_parameter_def( + unique int id: @hash_splat_parameter, + int loc: @location ref +); + +@heredoc_body_child_type = @interpolation | @token_escape_sequence | @token_heredoc_content | @token_heredoc_end + +#keyset[heredoc_body, index] +heredoc_body_child( + int heredoc_body: @heredoc_body ref, + int index: int ref, + unique int child: @heredoc_body_child_type ref +); + +heredoc_body_def( + unique int id: @heredoc_body, + int loc: @location ref +); + +@if_alternative_type = @else | @elsif + +if_alternative( + unique int if: @if ref, + unique int alternative: @if_alternative_type ref +); + +if_consequence( + unique int if: @if ref, + unique int consequence: @then ref +); + +if_def( + unique int id: @if, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@if_modifier_condition_type = @break | @call | @next | @return | @underscore_arg | @yield + +if_modifier_def( + unique int id: @if_modifier, + int body: @underscore_statement ref, + int condition: @if_modifier_condition_type ref, + int loc: @location ref +); + +in_def( + unique int id: @in, + int child: @underscore_arg ref, + int loc: @location ref +); + +@interpolation_child_type = @token_empty_statement | @underscore_statement + +#keyset[interpolation, index] +interpolation_child( + int interpolation: @interpolation ref, + int index: int ref, + unique int child: @interpolation_child_type ref +); + +interpolation_def( + unique int id: @interpolation, + int loc: @location ref +); + +keyword_parameter_value( + unique int keyword_parameter: @keyword_parameter ref, + unique int value: @underscore_arg ref +); + +keyword_parameter_def( + unique int id: @keyword_parameter, + int name: @token_identifier ref, + int loc: @location ref +); + +@lambda_body_type = @block | @do_block + +lambda_parameters( + unique int lambda: @lambda ref, + unique int parameters: @lambda_parameters ref +); + +lambda_def( + unique int id: @lambda, + int body: @lambda_body_type ref, + int loc: @location ref +); + +@lambda_parameters_child_type = @block_parameter | @destructured_parameter | @hash_splat_parameter | @keyword_parameter | @optional_parameter | @splat_parameter | @token_identifier + +#keyset[lambda_parameters, index] +lambda_parameters_child( + int lambda_parameters: @lambda_parameters ref, + int index: int ref, + unique int child: @lambda_parameters_child_type ref +); + +lambda_parameters_def( + unique int id: @lambda_parameters, + int loc: @location ref +); + +@left_assignment_list_child_type = @destructured_left_assignment | @rest_assignment | @underscore_lhs + +#keyset[left_assignment_list, index] +left_assignment_list_child( + int left_assignment_list: @left_assignment_list ref, + int index: int ref, + unique int child: @left_assignment_list_child_type ref +); + +left_assignment_list_def( + unique int id: @left_assignment_list, + int loc: @location ref +); + +method_parameters( + unique int method: @method ref, + unique int parameters: @method_parameters ref +); + +@method_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[method, index] +method_child( + int method: @method ref, + int index: int ref, + unique int child: @method_child_type ref +); + +method_def( + unique int id: @method, + int name: @underscore_method_name ref, + int loc: @location ref +); + +@method_parameters_child_type = @block_parameter | @destructured_parameter | @hash_splat_parameter | @keyword_parameter | @optional_parameter | @splat_parameter | @token_identifier + +#keyset[method_parameters, index] +method_parameters_child( + int method_parameters: @method_parameters ref, + int index: int ref, + unique int child: @method_parameters_child_type ref +); + +method_parameters_def( + unique int id: @method_parameters, + int loc: @location ref +); + +@module_name_type = @scope_resolution | @token_constant + +@module_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[module, index] +module_child( + int module: @module ref, + int index: int ref, + unique int child: @module_child_type ref +); + +module_def( + unique int id: @module, + int name: @module_name_type ref, + int loc: @location ref +); + +next_child( + unique int next: @next ref, + unique int child: @argument_list ref +); + +next_def( + unique int id: @next, + int loc: @location ref +); + +case @operator_assignment.operator of + 0 = @operator_assignment_percentequal +| 1 = @operator_assignment_ampersandampersandequal +| 2 = @operator_assignment_ampersandequal +| 3 = @operator_assignment_starstarequal +| 4 = @operator_assignment_starequal +| 5 = @operator_assignment_plusequal +| 6 = @operator_assignment_minusequal +| 7 = @operator_assignment_slashequal +| 8 = @operator_assignment_langlelangleequal +| 9 = @operator_assignment_ranglerangleequal +| 10 = @operator_assignment_caretequal +| 11 = @operator_assignment_pipeequal +| 12 = @operator_assignment_pipepipeequal +; + + +@operator_assignment_right_type = @break | @call | @next | @return | @underscore_arg | @yield + +operator_assignment_def( + unique int id: @operator_assignment, + int left: @underscore_lhs ref, + int operator: int ref, + int right: @operator_assignment_right_type ref, + int loc: @location ref +); + +optional_parameter_def( + unique int id: @optional_parameter, + int name: @token_identifier ref, + int value: @underscore_arg ref, + int loc: @location ref +); + +@pair_key_type = @string__ | @token_hash_key_symbol | @underscore_arg + +pair_def( + unique int id: @pair, + int key__: @pair_key_type ref, + int value: @underscore_arg ref, + int loc: @location ref +); + +@parenthesized_statements_child_type = @token_empty_statement | @underscore_statement + +#keyset[parenthesized_statements, index] +parenthesized_statements_child( + int parenthesized_statements: @parenthesized_statements ref, + int index: int ref, + unique int child: @parenthesized_statements_child_type ref +); + +parenthesized_statements_def( + unique int id: @parenthesized_statements, + int loc: @location ref +); + +@pattern_child_type = @splat_argument | @underscore_arg + +pattern_def( + unique int id: @pattern, + int child: @pattern_child_type ref, + int loc: @location ref +); + +@program_child_type = @token_empty_statement | @token_uninterpreted | @underscore_statement + +#keyset[program, index] +program_child( + int program: @program ref, + int index: int ref, + unique int child: @program_child_type ref +); + +program_def( + unique int id: @program, + int loc: @location ref +); + +range_begin( + unique int range: @range ref, + unique int begin: @underscore_arg ref +); + +range_end( + unique int range: @range ref, + unique int end: @underscore_arg ref +); + +case @range.operator of + 0 = @range_dotdot +| 1 = @range_dotdotdot +; + + +range_def( + unique int id: @range, + int operator: int ref, + int loc: @location ref +); + +@rational_child_type = @token_float | @token_integer + +rational_def( + unique int id: @rational, + int child: @rational_child_type ref, + int loc: @location ref +); + +redo_child( + unique int redo: @redo ref, + unique int child: @argument_list ref +); + +redo_def( + unique int id: @redo, + int loc: @location ref +); + +@regex_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[regex, index] +regex_child( + int regex: @regex ref, + int index: int ref, + unique int child: @regex_child_type ref +); + +regex_def( + unique int id: @regex, + int loc: @location ref +); + +rescue_body( + unique int rescue: @rescue ref, + unique int body: @then ref +); + +rescue_exceptions( + unique int rescue: @rescue ref, + unique int exceptions: @exceptions ref +); + +rescue_variable( + unique int rescue: @rescue ref, + unique int variable: @exception_variable ref +); + +rescue_def( + unique int id: @rescue, + int loc: @location ref +); + +@rescue_modifier_handler_type = @break | @call | @next | @return | @underscore_arg | @yield + +rescue_modifier_def( + unique int id: @rescue_modifier, + int body: @underscore_statement ref, + int handler: @rescue_modifier_handler_type ref, + int loc: @location ref +); + +rest_assignment_child( + unique int rest_assignment: @rest_assignment ref, + unique int child: @underscore_lhs ref +); + +rest_assignment_def( + unique int id: @rest_assignment, + int loc: @location ref +); + +retry_child( + unique int retry: @retry ref, + unique int child: @argument_list ref +); + +retry_def( + unique int id: @retry, + int loc: @location ref +); + +return_child( + unique int return: @return ref, + unique int child: @argument_list ref +); + +return_def( + unique int id: @return, + int loc: @location ref +); + +@right_assignment_list_child_type = @splat_argument | @underscore_arg + +#keyset[right_assignment_list, index] +right_assignment_list_child( + int right_assignment_list: @right_assignment_list ref, + int index: int ref, + unique int child: @right_assignment_list_child_type ref +); + +right_assignment_list_def( + unique int id: @right_assignment_list, + int loc: @location ref +); + +@scope_resolution_name_type = @token_constant | @token_identifier + +scope_resolution_scope( + unique int scope_resolution: @scope_resolution ref, + unique int scope: @underscore_primary ref +); + +scope_resolution_def( + unique int id: @scope_resolution, + int name: @scope_resolution_name_type ref, + int loc: @location ref +); + +setter_def( + unique int id: @setter, + int name: @token_identifier ref, + int loc: @location ref +); + +@singleton_class_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[singleton_class, index] +singleton_class_child( + int singleton_class: @singleton_class ref, + int index: int ref, + unique int child: @singleton_class_child_type ref +); + +singleton_class_def( + unique int id: @singleton_class, + int value: @underscore_arg ref, + int loc: @location ref +); + +@singleton_method_object_type = @underscore_arg | @underscore_variable + +singleton_method_parameters( + unique int singleton_method: @singleton_method ref, + unique int parameters: @method_parameters ref +); + +@singleton_method_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[singleton_method, index] +singleton_method_child( + int singleton_method: @singleton_method ref, + int index: int ref, + unique int child: @singleton_method_child_type ref +); + +singleton_method_def( + unique int id: @singleton_method, + int name: @underscore_method_name ref, + int object: @singleton_method_object_type ref, + int loc: @location ref +); + +splat_argument_def( + unique int id: @splat_argument, + int child: @underscore_arg ref, + int loc: @location ref +); + +splat_parameter_name( + unique int splat_parameter: @splat_parameter ref, + unique int name: @token_identifier ref +); + +splat_parameter_def( + unique int id: @splat_parameter, + int loc: @location ref +); + +@string_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[string__, index] +string_child( + int string__: @string__ ref, + int index: int ref, + unique int child: @string_child_type ref +); + +string_def( + unique int id: @string__, + int loc: @location ref +); + +#keyset[string_array, index] +string_array_child( + int string_array: @string_array ref, + int index: int ref, + unique int child: @bare_string ref +); + +string_array_def( + unique int id: @string_array, + int loc: @location ref +); + +@subshell_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[subshell, index] +subshell_child( + int subshell: @subshell ref, + int index: int ref, + unique int child: @subshell_child_type ref +); + +subshell_def( + unique int id: @subshell, + int loc: @location ref +); + +@superclass_child_type = @break | @call | @next | @return | @underscore_arg | @yield + +superclass_def( + unique int id: @superclass, + int child: @superclass_child_type ref, + int loc: @location ref +); + +#keyset[symbol_array, index] +symbol_array_child( + int symbol_array: @symbol_array ref, + int index: int ref, + unique int child: @bare_symbol ref +); + +symbol_array_def( + unique int id: @symbol_array, + int loc: @location ref +); + +@then_child_type = @token_empty_statement | @underscore_statement + +#keyset[then, index] +then_child( + int then: @then ref, + int index: int ref, + unique int child: @then_child_type ref +); + +then_def( + unique int id: @then, + int loc: @location ref +); + +@unary_operand_type = @break | @call | @next | @parenthesized_statements | @return | @token_float | @token_integer | @underscore_arg | @yield + +case @unary.operator of + 0 = @unary_bang +| 1 = @unary_plus +| 2 = @unary_minus +| 3 = @unary_definedquestion +| 4 = @unary_not +| 5 = @unary_tilde +; + + +unary_def( + unique int id: @unary, + int operand: @unary_operand_type ref, + int operator: int ref, + int loc: @location ref +); + +#keyset[undef, index] +undef_child( + int undef: @undef ref, + int index: int ref, + unique int child: @underscore_method_name ref +); + +undef_def( + unique int id: @undef, + int loc: @location ref +); + +@unless_alternative_type = @else | @elsif + +unless_alternative( + unique int unless: @unless ref, + unique int alternative: @unless_alternative_type ref +); + +unless_consequence( + unique int unless: @unless ref, + unique int consequence: @then ref +); + +unless_def( + unique int id: @unless, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@unless_modifier_condition_type = @break | @call | @next | @return | @underscore_arg | @yield + +unless_modifier_def( + unique int id: @unless_modifier, + int body: @underscore_statement ref, + int condition: @unless_modifier_condition_type ref, + int loc: @location ref +); + +until_def( + unique int id: @until, + int body: @do ref, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@until_modifier_condition_type = @break | @call | @next | @return | @underscore_arg | @yield + +until_modifier_def( + unique int id: @until_modifier, + int body: @underscore_statement ref, + int condition: @until_modifier_condition_type ref, + int loc: @location ref +); + +when_body( + unique int when: @when ref, + unique int body: @then ref +); + +#keyset[when, index] +when_pattern( + int when: @when ref, + int index: int ref, + unique int pattern: @pattern ref +); + +when_def( + unique int id: @when, + int loc: @location ref +); + +while_def( + unique int id: @while, + int body: @do ref, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@while_modifier_condition_type = @break | @call | @next | @return | @underscore_arg | @yield + +while_modifier_def( + unique int id: @while_modifier, + int body: @underscore_statement ref, + int condition: @while_modifier_condition_type ref, + int loc: @location ref +); + +yield_child( + unique int yield: @yield ref, + unique int child: @argument_list ref +); + +yield_def( + unique int id: @yield, + int loc: @location ref +); + +tokeninfo( + unique int id: @token, + int kind: int ref, + int file: @file ref, + int idx: int ref, + string value: string ref, + int loc: @location ref +); + +case @token.kind of + 0 = @reserved_word +| 1 = @token_character +| 2 = @token_class_variable +| 3 = @token_comment +| 4 = @token_complex +| 5 = @token_constant +| 6 = @token_empty_statement +| 7 = @token_escape_sequence +| 8 = @token_false +| 9 = @token_float +| 10 = @token_global_variable +| 11 = @token_hash_key_symbol +| 12 = @token_heredoc_beginning +| 13 = @token_heredoc_content +| 14 = @token_heredoc_end +| 15 = @token_identifier +| 16 = @token_instance_variable +| 17 = @token_integer +| 18 = @token_nil +| 19 = @token_operator +| 20 = @token_self +| 21 = @token_simple_symbol +| 22 = @token_string_content +| 23 = @token_super +| 24 = @token_true +| 25 = @token_uninterpreted +; + + +@ast_node = @alias | @argument_list | @array | @assignment | @bare_string | @bare_symbol | @begin | @begin_block | @binary | @block | @block_argument | @block_parameter | @block_parameters | @break | @call | @case__ | @chained_string | @class | @conditional | @delimited_symbol | @destructured_left_assignment | @destructured_parameter | @do | @do_block | @element_reference | @else | @elsif | @end_block | @ensure | @exception_variable | @exceptions | @for | @hash | @hash_splat_argument | @hash_splat_parameter | @heredoc_body | @if | @if_modifier | @in | @interpolation | @keyword_parameter | @lambda | @lambda_parameters | @left_assignment_list | @method | @method_parameters | @module | @next | @operator_assignment | @optional_parameter | @pair | @parenthesized_statements | @pattern | @program | @range | @rational | @redo | @regex | @rescue | @rescue_modifier | @rest_assignment | @retry | @return | @right_assignment_list | @scope_resolution | @setter | @singleton_class | @singleton_method | @splat_argument | @splat_parameter | @string__ | @string_array | @subshell | @superclass | @symbol_array | @then | @token | @unary | @undef | @unless | @unless_modifier | @until | @until_modifier | @when | @while | @while_modifier | @yield + +@ast_node_parent = @ast_node | @file + +#keyset[parent, parent_index] +ast_node_parent( + int child: @ast_node ref, + int parent: @ast_node_parent ref, + int parent_index: int ref +); + diff --git a/ruby/ql/lib/upgrades/8725deeb2fa6627c45235f18b7c121c35498dac7/ruby.dbscheme b/ruby/ql/lib/upgrades/8725deeb2fa6627c45235f18b7c121c35498dac7/ruby.dbscheme new file mode 100644 index 000000000000..40be81bc2086 --- /dev/null +++ b/ruby/ql/lib/upgrades/8725deeb2fa6627c45235f18b7c121c35498dac7/ruby.dbscheme @@ -0,0 +1,1267 @@ +// CodeQL database schema for Ruby +// 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 +); + +@sourceline = @file + +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref +); + +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref +); + +folders( + unique int id: @folder, + string name: string ref, + string simple: string ref +); + +@container = @file | @folder + +containerparent( + int parent: @container ref, + unique int child: @container ref +); + +sourceLocationPrefix( + string prefix: string ref +); + +@underscore_arg = @assignment | @binary | @conditional | @operator_assignment | @range | @unary | @underscore_primary + +@underscore_lhs = @call | @element_reference | @scope_resolution | @token_false | @token_nil | @token_true | @underscore_variable + +@underscore_method_name = @delimited_symbol | @setter | @token_class_variable | @token_constant | @token_global_variable | @token_identifier | @token_instance_variable | @token_operator | @token_simple_symbol + +@underscore_primary = @array | @begin | @break | @case__ | @chained_string | @class | @delimited_symbol | @for | @hash | @if | @lambda | @method | @module | @next | @parenthesized_statements | @rational | @redo | @regex | @retry | @return | @singleton_class | @singleton_method | @string__ | @string_array | @subshell | @symbol_array | @token_character | @token_complex | @token_float | @token_heredoc_beginning | @token_integer | @token_simple_symbol | @unary | @underscore_lhs | @unless | @until | @while | @yield + +@underscore_statement = @alias | @assignment | @begin_block | @binary | @break | @call | @end_block | @if_modifier | @next | @operator_assignment | @rescue_modifier | @return | @unary | @undef | @underscore_arg | @unless_modifier | @until_modifier | @while_modifier | @yield + +@underscore_variable = @token_class_variable | @token_constant | @token_global_variable | @token_identifier | @token_instance_variable | @token_self | @token_super + +alias_def( + unique int id: @alias, + int alias: @underscore_method_name ref, + int name: @underscore_method_name ref, + int loc: @location ref +); + +@argument_list_child_type = @block_argument | @break | @call | @hash_splat_argument | @next | @pair | @return | @splat_argument | @underscore_arg | @yield + +#keyset[argument_list, index] +argument_list_child( + int argument_list: @argument_list ref, + int index: int ref, + unique int child: @argument_list_child_type ref +); + +argument_list_def( + unique int id: @argument_list, + int loc: @location ref +); + +@array_child_type = @block_argument | @break | @call | @hash_splat_argument | @next | @pair | @return | @splat_argument | @underscore_arg | @yield + +#keyset[array, index] +array_child( + int array: @array ref, + int index: int ref, + unique int child: @array_child_type ref +); + +array_def( + unique int id: @array, + int loc: @location ref +); + +@assignment_left_type = @left_assignment_list | @underscore_lhs + +@assignment_right_type = @break | @call | @next | @return | @right_assignment_list | @splat_argument | @underscore_arg | @yield + +assignment_def( + unique int id: @assignment, + int left: @assignment_left_type ref, + int right: @assignment_right_type ref, + int loc: @location ref +); + +@bare_string_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[bare_string, index] +bare_string_child( + int bare_string: @bare_string ref, + int index: int ref, + unique int child: @bare_string_child_type ref +); + +bare_string_def( + unique int id: @bare_string, + int loc: @location ref +); + +@bare_symbol_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[bare_symbol, index] +bare_symbol_child( + int bare_symbol: @bare_symbol ref, + int index: int ref, + unique int child: @bare_symbol_child_type ref +); + +bare_symbol_def( + unique int id: @bare_symbol, + int loc: @location ref +); + +@begin_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[begin, index] +begin_child( + int begin: @begin ref, + int index: int ref, + unique int child: @begin_child_type ref +); + +begin_def( + unique int id: @begin, + int loc: @location ref +); + +@begin_block_child_type = @token_empty_statement | @underscore_statement + +#keyset[begin_block, index] +begin_block_child( + int begin_block: @begin_block ref, + int index: int ref, + unique int child: @begin_block_child_type ref +); + +begin_block_def( + unique int id: @begin_block, + int loc: @location ref +); + +@binary_left_type = @break | @call | @next | @return | @underscore_arg | @yield + +case @binary.operator of + 0 = @binary_bangequal +| 1 = @binary_bangtilde +| 2 = @binary_percent +| 3 = @binary_ampersand +| 4 = @binary_ampersandampersand +| 5 = @binary_star +| 6 = @binary_starstar +| 7 = @binary_plus +| 8 = @binary_minus +| 9 = @binary_slash +| 10 = @binary_langle +| 11 = @binary_langlelangle +| 12 = @binary_langleequal +| 13 = @binary_langleequalrangle +| 14 = @binary_equalequal +| 15 = @binary_equalequalequal +| 16 = @binary_equaltilde +| 17 = @binary_rangle +| 18 = @binary_rangleequal +| 19 = @binary_ranglerangle +| 20 = @binary_caret +| 21 = @binary_and +| 22 = @binary_or +| 23 = @binary_pipe +| 24 = @binary_pipepipe +; + + +@binary_right_type = @break | @call | @next | @return | @underscore_arg | @yield + +binary_def( + unique int id: @binary, + int left: @binary_left_type ref, + int operator: int ref, + int right: @binary_right_type ref, + int loc: @location ref +); + +block_parameters( + unique int block: @block ref, + unique int parameters: @block_parameters ref +); + +@block_child_type = @token_empty_statement | @underscore_statement + +#keyset[block, index] +block_child( + int block: @block ref, + int index: int ref, + unique int child: @block_child_type ref +); + +block_def( + unique int id: @block, + int loc: @location ref +); + +block_argument_def( + unique int id: @block_argument, + int child: @underscore_arg ref, + int loc: @location ref +); + +block_parameter_def( + unique int id: @block_parameter, + int name: @token_identifier ref, + int loc: @location ref +); + +@block_parameters_child_type = @block_parameter | @destructured_parameter | @hash_splat_parameter | @keyword_parameter | @optional_parameter | @splat_parameter | @token_identifier + +#keyset[block_parameters, index] +block_parameters_child( + int block_parameters: @block_parameters ref, + int index: int ref, + unique int child: @block_parameters_child_type ref +); + +block_parameters_def( + unique int id: @block_parameters, + int loc: @location ref +); + +break_child( + unique int break: @break ref, + unique int child: @argument_list ref +); + +break_def( + unique int id: @break, + int loc: @location ref +); + +call_arguments( + unique int call: @call ref, + unique int arguments: @argument_list ref +); + +@call_block_type = @block | @do_block + +call_block( + unique int call: @call ref, + unique int block: @call_block_type ref +); + +@call_method_type = @argument_list | @scope_resolution | @token_operator | @underscore_variable + +@call_receiver_type = @call | @underscore_primary + +call_receiver( + unique int call: @call ref, + unique int receiver: @call_receiver_type ref +); + +call_def( + unique int id: @call, + int method: @call_method_type ref, + int loc: @location ref +); + +case_value( + unique int case__: @case__ ref, + unique int value: @underscore_statement ref +); + +@case_child_type = @else | @when + +#keyset[case__, index] +case_child( + int case__: @case__ ref, + int index: int ref, + unique int child: @case_child_type ref +); + +case_def( + unique int id: @case__, + int loc: @location ref +); + +#keyset[chained_string, index] +chained_string_child( + int chained_string: @chained_string ref, + int index: int ref, + unique int child: @string__ ref +); + +chained_string_def( + unique int id: @chained_string, + int loc: @location ref +); + +@class_name_type = @scope_resolution | @token_constant + +class_superclass( + unique int class: @class ref, + unique int superclass: @superclass ref +); + +@class_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[class, index] +class_child( + int class: @class ref, + int index: int ref, + unique int child: @class_child_type ref +); + +class_def( + unique int id: @class, + int name: @class_name_type ref, + int loc: @location ref +); + +conditional_def( + unique int id: @conditional, + int alternative: @underscore_arg ref, + int condition: @underscore_arg ref, + int consequence: @underscore_arg ref, + int loc: @location ref +); + +@delimited_symbol_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[delimited_symbol, index] +delimited_symbol_child( + int delimited_symbol: @delimited_symbol ref, + int index: int ref, + unique int child: @delimited_symbol_child_type ref +); + +delimited_symbol_def( + unique int id: @delimited_symbol, + int loc: @location ref +); + +@destructured_left_assignment_child_type = @destructured_left_assignment | @rest_assignment | @underscore_lhs + +#keyset[destructured_left_assignment, index] +destructured_left_assignment_child( + int destructured_left_assignment: @destructured_left_assignment ref, + int index: int ref, + unique int child: @destructured_left_assignment_child_type ref +); + +destructured_left_assignment_def( + unique int id: @destructured_left_assignment, + int loc: @location ref +); + +@destructured_parameter_child_type = @block_parameter | @destructured_parameter | @hash_splat_parameter | @keyword_parameter | @optional_parameter | @splat_parameter | @token_identifier + +#keyset[destructured_parameter, index] +destructured_parameter_child( + int destructured_parameter: @destructured_parameter ref, + int index: int ref, + unique int child: @destructured_parameter_child_type ref +); + +destructured_parameter_def( + unique int id: @destructured_parameter, + int loc: @location ref +); + +@do_child_type = @token_empty_statement | @underscore_statement + +#keyset[do, index] +do_child( + int do: @do ref, + int index: int ref, + unique int child: @do_child_type ref +); + +do_def( + unique int id: @do, + int loc: @location ref +); + +do_block_parameters( + unique int do_block: @do_block ref, + unique int parameters: @block_parameters ref +); + +@do_block_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[do_block, index] +do_block_child( + int do_block: @do_block ref, + int index: int ref, + unique int child: @do_block_child_type ref +); + +do_block_def( + unique int id: @do_block, + int loc: @location ref +); + +@element_reference_child_type = @block_argument | @break | @call | @hash_splat_argument | @next | @pair | @return | @splat_argument | @underscore_arg | @yield + +#keyset[element_reference, index] +element_reference_child( + int element_reference: @element_reference ref, + int index: int ref, + unique int child: @element_reference_child_type ref +); + +element_reference_def( + unique int id: @element_reference, + int object: @underscore_primary ref, + int loc: @location ref +); + +@else_child_type = @token_empty_statement | @underscore_statement + +#keyset[else, index] +else_child( + int else: @else ref, + int index: int ref, + unique int child: @else_child_type ref +); + +else_def( + unique int id: @else, + int loc: @location ref +); + +@elsif_alternative_type = @else | @elsif + +elsif_alternative( + unique int elsif: @elsif ref, + unique int alternative: @elsif_alternative_type ref +); + +elsif_consequence( + unique int elsif: @elsif ref, + unique int consequence: @then ref +); + +elsif_def( + unique int id: @elsif, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@end_block_child_type = @token_empty_statement | @underscore_statement + +#keyset[end_block, index] +end_block_child( + int end_block: @end_block ref, + int index: int ref, + unique int child: @end_block_child_type ref +); + +end_block_def( + unique int id: @end_block, + int loc: @location ref +); + +@ensure_child_type = @token_empty_statement | @underscore_statement + +#keyset[ensure, index] +ensure_child( + int ensure: @ensure ref, + int index: int ref, + unique int child: @ensure_child_type ref +); + +ensure_def( + unique int id: @ensure, + int loc: @location ref +); + +exception_variable_def( + unique int id: @exception_variable, + int child: @underscore_lhs ref, + int loc: @location ref +); + +@exceptions_child_type = @splat_argument | @underscore_arg + +#keyset[exceptions, index] +exceptions_child( + int exceptions: @exceptions ref, + int index: int ref, + unique int child: @exceptions_child_type ref +); + +exceptions_def( + unique int id: @exceptions, + int loc: @location ref +); + +@for_pattern_type = @left_assignment_list | @underscore_lhs + +for_def( + unique int id: @for, + int body: @do ref, + int pattern: @for_pattern_type ref, + int value: @in ref, + int loc: @location ref +); + +@hash_child_type = @hash_splat_argument | @pair + +#keyset[hash, index] +hash_child( + int hash: @hash ref, + int index: int ref, + unique int child: @hash_child_type ref +); + +hash_def( + unique int id: @hash, + int loc: @location ref +); + +hash_splat_argument_def( + unique int id: @hash_splat_argument, + int child: @underscore_arg ref, + int loc: @location ref +); + +hash_splat_parameter_name( + unique int hash_splat_parameter: @hash_splat_parameter ref, + unique int name: @token_identifier ref +); + +hash_splat_parameter_def( + unique int id: @hash_splat_parameter, + int loc: @location ref +); + +@heredoc_body_child_type = @interpolation | @token_escape_sequence | @token_heredoc_content | @token_heredoc_end + +#keyset[heredoc_body, index] +heredoc_body_child( + int heredoc_body: @heredoc_body ref, + int index: int ref, + unique int child: @heredoc_body_child_type ref +); + +heredoc_body_def( + unique int id: @heredoc_body, + int loc: @location ref +); + +@if_alternative_type = @else | @elsif + +if_alternative( + unique int if: @if ref, + unique int alternative: @if_alternative_type ref +); + +if_consequence( + unique int if: @if ref, + unique int consequence: @then ref +); + +if_def( + unique int id: @if, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@if_modifier_condition_type = @break | @call | @next | @return | @underscore_arg | @yield + +if_modifier_def( + unique int id: @if_modifier, + int body: @underscore_statement ref, + int condition: @if_modifier_condition_type ref, + int loc: @location ref +); + +in_def( + unique int id: @in, + int child: @underscore_arg ref, + int loc: @location ref +); + +@interpolation_child_type = @token_empty_statement | @underscore_statement + +#keyset[interpolation, index] +interpolation_child( + int interpolation: @interpolation ref, + int index: int ref, + unique int child: @interpolation_child_type ref +); + +interpolation_def( + unique int id: @interpolation, + int loc: @location ref +); + +keyword_parameter_value( + unique int keyword_parameter: @keyword_parameter ref, + unique int value: @underscore_arg ref +); + +keyword_parameter_def( + unique int id: @keyword_parameter, + int name: @token_identifier ref, + int loc: @location ref +); + +@lambda_body_type = @block | @do_block + +lambda_parameters( + unique int lambda: @lambda ref, + unique int parameters: @lambda_parameters ref +); + +lambda_def( + unique int id: @lambda, + int body: @lambda_body_type ref, + int loc: @location ref +); + +@lambda_parameters_child_type = @block_parameter | @destructured_parameter | @hash_splat_parameter | @keyword_parameter | @optional_parameter | @splat_parameter | @token_identifier + +#keyset[lambda_parameters, index] +lambda_parameters_child( + int lambda_parameters: @lambda_parameters ref, + int index: int ref, + unique int child: @lambda_parameters_child_type ref +); + +lambda_parameters_def( + unique int id: @lambda_parameters, + int loc: @location ref +); + +@left_assignment_list_child_type = @destructured_left_assignment | @rest_assignment | @underscore_lhs + +#keyset[left_assignment_list, index] +left_assignment_list_child( + int left_assignment_list: @left_assignment_list ref, + int index: int ref, + unique int child: @left_assignment_list_child_type ref +); + +left_assignment_list_def( + unique int id: @left_assignment_list, + int loc: @location ref +); + +method_parameters( + unique int method: @method ref, + unique int parameters: @method_parameters ref +); + +@method_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[method, index] +method_child( + int method: @method ref, + int index: int ref, + unique int child: @method_child_type ref +); + +method_def( + unique int id: @method, + int name: @underscore_method_name ref, + int loc: @location ref +); + +@method_parameters_child_type = @block_parameter | @destructured_parameter | @hash_splat_parameter | @keyword_parameter | @optional_parameter | @splat_parameter | @token_identifier + +#keyset[method_parameters, index] +method_parameters_child( + int method_parameters: @method_parameters ref, + int index: int ref, + unique int child: @method_parameters_child_type ref +); + +method_parameters_def( + unique int id: @method_parameters, + int loc: @location ref +); + +@module_name_type = @scope_resolution | @token_constant + +@module_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[module, index] +module_child( + int module: @module ref, + int index: int ref, + unique int child: @module_child_type ref +); + +module_def( + unique int id: @module, + int name: @module_name_type ref, + int loc: @location ref +); + +next_child( + unique int next: @next ref, + unique int child: @argument_list ref +); + +next_def( + unique int id: @next, + int loc: @location ref +); + +case @operator_assignment.operator of + 0 = @operator_assignment_percentequal +| 1 = @operator_assignment_ampersandampersandequal +| 2 = @operator_assignment_ampersandequal +| 3 = @operator_assignment_starstarequal +| 4 = @operator_assignment_starequal +| 5 = @operator_assignment_plusequal +| 6 = @operator_assignment_minusequal +| 7 = @operator_assignment_slashequal +| 8 = @operator_assignment_langlelangleequal +| 9 = @operator_assignment_ranglerangleequal +| 10 = @operator_assignment_caretequal +| 11 = @operator_assignment_pipeequal +| 12 = @operator_assignment_pipepipeequal +; + + +@operator_assignment_right_type = @break | @call | @next | @return | @underscore_arg | @yield + +operator_assignment_def( + unique int id: @operator_assignment, + int left: @underscore_lhs ref, + int operator: int ref, + int right: @operator_assignment_right_type ref, + int loc: @location ref +); + +optional_parameter_def( + unique int id: @optional_parameter, + int name: @token_identifier ref, + int value: @underscore_arg ref, + int loc: @location ref +); + +@pair_key_type = @string__ | @token_hash_key_symbol | @underscore_arg + +pair_def( + unique int id: @pair, + int key__: @pair_key_type ref, + int value: @underscore_arg ref, + int loc: @location ref +); + +@parenthesized_statements_child_type = @token_empty_statement | @underscore_statement + +#keyset[parenthesized_statements, index] +parenthesized_statements_child( + int parenthesized_statements: @parenthesized_statements ref, + int index: int ref, + unique int child: @parenthesized_statements_child_type ref +); + +parenthesized_statements_def( + unique int id: @parenthesized_statements, + int loc: @location ref +); + +@pattern_child_type = @splat_argument | @underscore_arg + +pattern_def( + unique int id: @pattern, + int child: @pattern_child_type ref, + int loc: @location ref +); + +@program_child_type = @token_empty_statement | @token_uninterpreted | @underscore_statement + +#keyset[program, index] +program_child( + int program: @program ref, + int index: int ref, + unique int child: @program_child_type ref +); + +program_def( + unique int id: @program, + int loc: @location ref +); + +range_begin( + unique int range: @range ref, + unique int begin: @underscore_arg ref +); + +range_end( + unique int range: @range ref, + unique int end: @underscore_arg ref +); + +case @range.operator of + 0 = @range_dotdot +| 1 = @range_dotdotdot +; + + +range_def( + unique int id: @range, + int operator: int ref, + int loc: @location ref +); + +@rational_child_type = @token_float | @token_integer + +rational_def( + unique int id: @rational, + int child: @rational_child_type ref, + int loc: @location ref +); + +redo_child( + unique int redo: @redo ref, + unique int child: @argument_list ref +); + +redo_def( + unique int id: @redo, + int loc: @location ref +); + +@regex_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[regex, index] +regex_child( + int regex: @regex ref, + int index: int ref, + unique int child: @regex_child_type ref +); + +regex_def( + unique int id: @regex, + int loc: @location ref +); + +rescue_body( + unique int rescue: @rescue ref, + unique int body: @then ref +); + +rescue_exceptions( + unique int rescue: @rescue ref, + unique int exceptions: @exceptions ref +); + +rescue_variable( + unique int rescue: @rescue ref, + unique int variable: @exception_variable ref +); + +rescue_def( + unique int id: @rescue, + int loc: @location ref +); + +@rescue_modifier_handler_type = @break | @call | @next | @return | @underscore_arg | @yield + +rescue_modifier_def( + unique int id: @rescue_modifier, + int body: @underscore_statement ref, + int handler: @rescue_modifier_handler_type ref, + int loc: @location ref +); + +rest_assignment_child( + unique int rest_assignment: @rest_assignment ref, + unique int child: @underscore_lhs ref +); + +rest_assignment_def( + unique int id: @rest_assignment, + int loc: @location ref +); + +retry_child( + unique int retry: @retry ref, + unique int child: @argument_list ref +); + +retry_def( + unique int id: @retry, + int loc: @location ref +); + +return_child( + unique int return: @return ref, + unique int child: @argument_list ref +); + +return_def( + unique int id: @return, + int loc: @location ref +); + +@right_assignment_list_child_type = @splat_argument | @underscore_arg + +#keyset[right_assignment_list, index] +right_assignment_list_child( + int right_assignment_list: @right_assignment_list ref, + int index: int ref, + unique int child: @right_assignment_list_child_type ref +); + +right_assignment_list_def( + unique int id: @right_assignment_list, + int loc: @location ref +); + +@scope_resolution_name_type = @token_constant | @token_identifier + +scope_resolution_scope( + unique int scope_resolution: @scope_resolution ref, + unique int scope: @underscore_primary ref +); + +scope_resolution_def( + unique int id: @scope_resolution, + int name: @scope_resolution_name_type ref, + int loc: @location ref +); + +setter_def( + unique int id: @setter, + int name: @token_identifier ref, + int loc: @location ref +); + +@singleton_class_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[singleton_class, index] +singleton_class_child( + int singleton_class: @singleton_class ref, + int index: int ref, + unique int child: @singleton_class_child_type ref +); + +singleton_class_def( + unique int id: @singleton_class, + int value: @underscore_arg ref, + int loc: @location ref +); + +@singleton_method_object_type = @underscore_arg | @underscore_variable + +singleton_method_parameters( + unique int singleton_method: @singleton_method ref, + unique int parameters: @method_parameters ref +); + +@singleton_method_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[singleton_method, index] +singleton_method_child( + int singleton_method: @singleton_method ref, + int index: int ref, + unique int child: @singleton_method_child_type ref +); + +singleton_method_def( + unique int id: @singleton_method, + int name: @underscore_method_name ref, + int object: @singleton_method_object_type ref, + int loc: @location ref +); + +splat_argument_def( + unique int id: @splat_argument, + int child: @underscore_arg ref, + int loc: @location ref +); + +splat_parameter_name( + unique int splat_parameter: @splat_parameter ref, + unique int name: @token_identifier ref +); + +splat_parameter_def( + unique int id: @splat_parameter, + int loc: @location ref +); + +@string_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[string__, index] +string_child( + int string__: @string__ ref, + int index: int ref, + unique int child: @string_child_type ref +); + +string_def( + unique int id: @string__, + int loc: @location ref +); + +#keyset[string_array, index] +string_array_child( + int string_array: @string_array ref, + int index: int ref, + unique int child: @bare_string ref +); + +string_array_def( + unique int id: @string_array, + int loc: @location ref +); + +@subshell_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[subshell, index] +subshell_child( + int subshell: @subshell ref, + int index: int ref, + unique int child: @subshell_child_type ref +); + +subshell_def( + unique int id: @subshell, + int loc: @location ref +); + +@superclass_child_type = @break | @call | @next | @return | @underscore_arg | @yield + +superclass_def( + unique int id: @superclass, + int child: @superclass_child_type ref, + int loc: @location ref +); + +#keyset[symbol_array, index] +symbol_array_child( + int symbol_array: @symbol_array ref, + int index: int ref, + unique int child: @bare_symbol ref +); + +symbol_array_def( + unique int id: @symbol_array, + int loc: @location ref +); + +@then_child_type = @token_empty_statement | @underscore_statement + +#keyset[then, index] +then_child( + int then: @then ref, + int index: int ref, + unique int child: @then_child_type ref +); + +then_def( + unique int id: @then, + int loc: @location ref +); + +@unary_operand_type = @break | @call | @next | @parenthesized_statements | @return | @token_float | @token_integer | @underscore_arg | @yield + +case @unary.operator of + 0 = @unary_bang +| 1 = @unary_plus +| 2 = @unary_minus +| 3 = @unary_definedquestion +| 4 = @unary_not +| 5 = @unary_tilde +; + + +unary_def( + unique int id: @unary, + int operand: @unary_operand_type ref, + int operator: int ref, + int loc: @location ref +); + +#keyset[undef, index] +undef_child( + int undef: @undef ref, + int index: int ref, + unique int child: @underscore_method_name ref +); + +undef_def( + unique int id: @undef, + int loc: @location ref +); + +@unless_alternative_type = @else | @elsif + +unless_alternative( + unique int unless: @unless ref, + unique int alternative: @unless_alternative_type ref +); + +unless_consequence( + unique int unless: @unless ref, + unique int consequence: @then ref +); + +unless_def( + unique int id: @unless, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@unless_modifier_condition_type = @break | @call | @next | @return | @underscore_arg | @yield + +unless_modifier_def( + unique int id: @unless_modifier, + int body: @underscore_statement ref, + int condition: @unless_modifier_condition_type ref, + int loc: @location ref +); + +until_def( + unique int id: @until, + int body: @do ref, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@until_modifier_condition_type = @break | @call | @next | @return | @underscore_arg | @yield + +until_modifier_def( + unique int id: @until_modifier, + int body: @underscore_statement ref, + int condition: @until_modifier_condition_type ref, + int loc: @location ref +); + +when_body( + unique int when: @when ref, + unique int body: @then ref +); + +#keyset[when, index] +when_pattern( + int when: @when ref, + int index: int ref, + unique int pattern: @pattern ref +); + +when_def( + unique int id: @when, + int loc: @location ref +); + +while_def( + unique int id: @while, + int body: @do ref, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@while_modifier_condition_type = @break | @call | @next | @return | @underscore_arg | @yield + +while_modifier_def( + unique int id: @while_modifier, + int body: @underscore_statement ref, + int condition: @while_modifier_condition_type ref, + int loc: @location ref +); + +yield_child( + unique int yield: @yield ref, + unique int child: @argument_list ref +); + +yield_def( + unique int id: @yield, + int loc: @location ref +); + +tokeninfo( + unique int id: @token, + int kind: int ref, + int file: @file ref, + int idx: int ref, + string value: string ref, + int loc: @location ref +); + +case @token.kind of + 0 = @reserved_word +| 1 = @token_character +| 2 = @token_class_variable +| 3 = @token_comment +| 4 = @token_complex +| 5 = @token_constant +| 6 = @token_empty_statement +| 7 = @token_escape_sequence +| 8 = @token_false +| 9 = @token_float +| 10 = @token_global_variable +| 11 = @token_hash_key_symbol +| 12 = @token_heredoc_beginning +| 13 = @token_heredoc_content +| 14 = @token_heredoc_end +| 15 = @token_identifier +| 16 = @token_instance_variable +| 17 = @token_integer +| 18 = @token_nil +| 19 = @token_operator +| 20 = @token_self +| 21 = @token_simple_symbol +| 22 = @token_string_content +| 23 = @token_super +| 24 = @token_true +| 25 = @token_uninterpreted +; + + +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 +; + + +@ast_node = @alias | @argument_list | @array | @assignment | @bare_string | @bare_symbol | @begin | @begin_block | @binary | @block | @block_argument | @block_parameter | @block_parameters | @break | @call | @case__ | @chained_string | @class | @conditional | @delimited_symbol | @destructured_left_assignment | @destructured_parameter | @do | @do_block | @element_reference | @else | @elsif | @end_block | @ensure | @exception_variable | @exceptions | @for | @hash | @hash_splat_argument | @hash_splat_parameter | @heredoc_body | @if | @if_modifier | @in | @interpolation | @keyword_parameter | @lambda | @lambda_parameters | @left_assignment_list | @method | @method_parameters | @module | @next | @operator_assignment | @optional_parameter | @pair | @parenthesized_statements | @pattern | @program | @range | @rational | @redo | @regex | @rescue | @rescue_modifier | @rest_assignment | @retry | @return | @right_assignment_list | @scope_resolution | @setter | @singleton_class | @singleton_method | @splat_argument | @splat_parameter | @string__ | @string_array | @subshell | @superclass | @symbol_array | @then | @token | @unary | @undef | @unless | @unless_modifier | @until | @until_modifier | @when | @while | @while_modifier | @yield + +@ast_node_parent = @ast_node | @file + +#keyset[parent, parent_index] +ast_node_parent( + int child: @ast_node ref, + int parent: @ast_node_parent ref, + int parent_index: int ref +); + diff --git a/ruby/ql/lib/upgrades/8725deeb2fa6627c45235f18b7c121c35498dac7/upgrade.properties b/ruby/ql/lib/upgrades/8725deeb2fa6627c45235f18b7c121c35498dac7/upgrade.properties new file mode 100644 index 000000000000..2261e7e5111e --- /dev/null +++ b/ruby/ql/lib/upgrades/8725deeb2fa6627c45235f18b7c121c35498dac7/upgrade.properties @@ -0,0 +1,2 @@ +description: Create an empty diagnostics table +compatibility: backwards diff --git a/ruby/ql/lib/upgrades/b5aef9c93ae64f848017d2dcb760eed916ab0cdd/old.dbscheme b/ruby/ql/lib/upgrades/b5aef9c93ae64f848017d2dcb760eed916ab0cdd/old.dbscheme new file mode 100644 index 000000000000..b5aef9c93ae6 --- /dev/null +++ b/ruby/ql/lib/upgrades/b5aef9c93ae64f848017d2dcb760eed916ab0cdd/old.dbscheme @@ -0,0 +1,1320 @@ +// CodeQL database schema for Ruby +// 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 +; + + +@ruby_underscore_arg = @ruby_assignment | @ruby_binary | @ruby_conditional | @ruby_operator_assignment | @ruby_range | @ruby_unary | @ruby_underscore_primary + +@ruby_underscore_lhs = @ruby_call | @ruby_element_reference | @ruby_scope_resolution | @ruby_token_false | @ruby_token_nil | @ruby_token_true | @ruby_underscore_variable + +@ruby_underscore_method_name = @ruby_delimited_symbol | @ruby_setter | @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_operator | @ruby_token_simple_symbol + +@ruby_underscore_primary = @ruby_array | @ruby_begin | @ruby_break | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_delimited_symbol | @ruby_for | @ruby_hash | @ruby_if | @ruby_lambda | @ruby_method | @ruby_module | @ruby_next | @ruby_parenthesized_statements | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_retry | @ruby_return | @ruby_singleton_class | @ruby_singleton_method | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_symbol_array | @ruby_token_character | @ruby_token_complex | @ruby_token_float | @ruby_token_heredoc_beginning | @ruby_token_integer | @ruby_token_simple_symbol | @ruby_unary | @ruby_underscore_lhs | @ruby_unless | @ruby_until | @ruby_while | @ruby_yield + +@ruby_underscore_statement = @ruby_alias | @ruby_assignment | @ruby_begin_block | @ruby_binary | @ruby_break | @ruby_call | @ruby_end_block | @ruby_if_modifier | @ruby_next | @ruby_operator_assignment | @ruby_rescue_modifier | @ruby_return | @ruby_unary | @ruby_undef | @ruby_underscore_arg | @ruby_unless_modifier | @ruby_until_modifier | @ruby_while_modifier | @ruby_yield + +@ruby_underscore_variable = @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_self | @ruby_token_super + +ruby_alias_def( + unique int id: @ruby_alias, + int alias: @ruby_underscore_method_name ref, + int name: @ruby_underscore_method_name ref, + int loc: @location ref +); + +@ruby_argument_list_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_argument_list, index] +ruby_argument_list_child( + int ruby_argument_list: @ruby_argument_list ref, + int index: int ref, + unique int child: @ruby_argument_list_child_type ref +); + +ruby_argument_list_def( + unique int id: @ruby_argument_list, + int loc: @location ref +); + +@ruby_array_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_array, index] +ruby_array_child( + int ruby_array: @ruby_array ref, + int index: int ref, + unique int child: @ruby_array_child_type ref +); + +ruby_array_def( + unique int id: @ruby_array, + int loc: @location ref +); + +@ruby_assignment_left_type = @ruby_left_assignment_list | @ruby_underscore_lhs + +@ruby_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_right_assignment_list | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +ruby_assignment_def( + unique int id: @ruby_assignment, + int left: @ruby_assignment_left_type ref, + int right: @ruby_assignment_right_type ref, + int loc: @location ref +); + +@ruby_bare_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_bare_string, index] +ruby_bare_string_child( + int ruby_bare_string: @ruby_bare_string ref, + int index: int ref, + unique int child: @ruby_bare_string_child_type ref +); + +ruby_bare_string_def( + unique int id: @ruby_bare_string, + int loc: @location ref +); + +@ruby_bare_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_bare_symbol, index] +ruby_bare_symbol_child( + int ruby_bare_symbol: @ruby_bare_symbol ref, + int index: int ref, + unique int child: @ruby_bare_symbol_child_type ref +); + +ruby_bare_symbol_def( + unique int id: @ruby_bare_symbol, + int loc: @location ref +); + +@ruby_begin_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_begin, index] +ruby_begin_child( + int ruby_begin: @ruby_begin ref, + int index: int ref, + unique int child: @ruby_begin_child_type ref +); + +ruby_begin_def( + unique int id: @ruby_begin, + int loc: @location ref +); + +@ruby_begin_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_begin_block, index] +ruby_begin_block_child( + int ruby_begin_block: @ruby_begin_block ref, + int index: int ref, + unique int child: @ruby_begin_block_child_type ref +); + +ruby_begin_block_def( + unique int id: @ruby_begin_block, + int loc: @location ref +); + +@ruby_binary_left_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +case @ruby_binary.operator of + 0 = @ruby_binary_bangequal +| 1 = @ruby_binary_bangtilde +| 2 = @ruby_binary_percent +| 3 = @ruby_binary_ampersand +| 4 = @ruby_binary_ampersandampersand +| 5 = @ruby_binary_star +| 6 = @ruby_binary_starstar +| 7 = @ruby_binary_plus +| 8 = @ruby_binary_minus +| 9 = @ruby_binary_slash +| 10 = @ruby_binary_langle +| 11 = @ruby_binary_langlelangle +| 12 = @ruby_binary_langleequal +| 13 = @ruby_binary_langleequalrangle +| 14 = @ruby_binary_equalequal +| 15 = @ruby_binary_equalequalequal +| 16 = @ruby_binary_equaltilde +| 17 = @ruby_binary_rangle +| 18 = @ruby_binary_rangleequal +| 19 = @ruby_binary_ranglerangle +| 20 = @ruby_binary_caret +| 21 = @ruby_binary_and +| 22 = @ruby_binary_or +| 23 = @ruby_binary_pipe +| 24 = @ruby_binary_pipepipe +; + + +@ruby_binary_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_binary_def( + unique int id: @ruby_binary, + int left: @ruby_binary_left_type ref, + int operator: int ref, + int right: @ruby_binary_right_type ref, + int loc: @location ref +); + +ruby_block_parameters( + unique int ruby_block: @ruby_block ref, + unique int parameters: @ruby_block_parameters ref +); + +@ruby_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_block, index] +ruby_block_child( + int ruby_block: @ruby_block ref, + int index: int ref, + unique int child: @ruby_block_child_type ref +); + +ruby_block_def( + unique int id: @ruby_block, + int loc: @location ref +); + +ruby_block_argument_def( + unique int id: @ruby_block_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_block_parameter_def( + unique int id: @ruby_block_parameter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_block_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_block_parameters, index] +ruby_block_parameters_child( + int ruby_block_parameters: @ruby_block_parameters ref, + int index: int ref, + unique int child: @ruby_block_parameters_child_type ref +); + +ruby_block_parameters_def( + unique int id: @ruby_block_parameters, + int loc: @location ref +); + +ruby_break_child( + unique int ruby_break: @ruby_break ref, + unique int child: @ruby_argument_list ref +); + +ruby_break_def( + unique int id: @ruby_break, + int loc: @location ref +); + +ruby_call_arguments( + unique int ruby_call: @ruby_call ref, + unique int arguments: @ruby_argument_list ref +); + +@ruby_call_block_type = @ruby_block | @ruby_do_block + +ruby_call_block( + unique int ruby_call: @ruby_call ref, + unique int block: @ruby_call_block_type ref +); + +@ruby_call_method_type = @ruby_argument_list | @ruby_scope_resolution | @ruby_token_operator | @ruby_underscore_variable + +@ruby_call_receiver_type = @ruby_call | @ruby_underscore_primary + +ruby_call_receiver( + unique int ruby_call: @ruby_call ref, + unique int receiver: @ruby_call_receiver_type ref +); + +ruby_call_def( + unique int id: @ruby_call, + int method: @ruby_call_method_type ref, + int loc: @location ref +); + +ruby_case_value( + unique int ruby_case__: @ruby_case__ ref, + unique int value: @ruby_underscore_statement ref +); + +@ruby_case_child_type = @ruby_else | @ruby_when + +#keyset[ruby_case__, index] +ruby_case_child( + int ruby_case__: @ruby_case__ ref, + int index: int ref, + unique int child: @ruby_case_child_type ref +); + +ruby_case_def( + unique int id: @ruby_case__, + int loc: @location ref +); + +#keyset[ruby_chained_string, index] +ruby_chained_string_child( + int ruby_chained_string: @ruby_chained_string ref, + int index: int ref, + unique int child: @ruby_string__ ref +); + +ruby_chained_string_def( + unique int id: @ruby_chained_string, + int loc: @location ref +); + +@ruby_class_name_type = @ruby_scope_resolution | @ruby_token_constant + +ruby_class_superclass( + unique int ruby_class: @ruby_class ref, + unique int superclass: @ruby_superclass ref +); + +@ruby_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_class, index] +ruby_class_child( + int ruby_class: @ruby_class ref, + int index: int ref, + unique int child: @ruby_class_child_type ref +); + +ruby_class_def( + unique int id: @ruby_class, + int name: @ruby_class_name_type ref, + int loc: @location ref +); + +ruby_conditional_def( + unique int id: @ruby_conditional, + int alternative: @ruby_underscore_arg ref, + int condition: @ruby_underscore_arg ref, + int consequence: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_delimited_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_delimited_symbol, index] +ruby_delimited_symbol_child( + int ruby_delimited_symbol: @ruby_delimited_symbol ref, + int index: int ref, + unique int child: @ruby_delimited_symbol_child_type ref +); + +ruby_delimited_symbol_def( + unique int id: @ruby_delimited_symbol, + int loc: @location ref +); + +@ruby_destructured_left_assignment_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs + +#keyset[ruby_destructured_left_assignment, index] +ruby_destructured_left_assignment_child( + int ruby_destructured_left_assignment: @ruby_destructured_left_assignment ref, + int index: int ref, + unique int child: @ruby_destructured_left_assignment_child_type ref +); + +ruby_destructured_left_assignment_def( + unique int id: @ruby_destructured_left_assignment, + int loc: @location ref +); + +@ruby_destructured_parameter_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_destructured_parameter, index] +ruby_destructured_parameter_child( + int ruby_destructured_parameter: @ruby_destructured_parameter ref, + int index: int ref, + unique int child: @ruby_destructured_parameter_child_type ref +); + +ruby_destructured_parameter_def( + unique int id: @ruby_destructured_parameter, + int loc: @location ref +); + +@ruby_do_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_do, index] +ruby_do_child( + int ruby_do: @ruby_do ref, + int index: int ref, + unique int child: @ruby_do_child_type ref +); + +ruby_do_def( + unique int id: @ruby_do, + int loc: @location ref +); + +ruby_do_block_parameters( + unique int ruby_do_block: @ruby_do_block ref, + unique int parameters: @ruby_block_parameters ref +); + +@ruby_do_block_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_do_block, index] +ruby_do_block_child( + int ruby_do_block: @ruby_do_block ref, + int index: int ref, + unique int child: @ruby_do_block_child_type ref +); + +ruby_do_block_def( + unique int id: @ruby_do_block, + int loc: @location ref +); + +@ruby_element_reference_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_element_reference, index] +ruby_element_reference_child( + int ruby_element_reference: @ruby_element_reference ref, + int index: int ref, + unique int child: @ruby_element_reference_child_type ref +); + +ruby_element_reference_def( + unique int id: @ruby_element_reference, + int object: @ruby_underscore_primary ref, + int loc: @location ref +); + +@ruby_else_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_else, index] +ruby_else_child( + int ruby_else: @ruby_else ref, + int index: int ref, + unique int child: @ruby_else_child_type ref +); + +ruby_else_def( + unique int id: @ruby_else, + int loc: @location ref +); + +@ruby_elsif_alternative_type = @ruby_else | @ruby_elsif + +ruby_elsif_alternative( + unique int ruby_elsif: @ruby_elsif ref, + unique int alternative: @ruby_elsif_alternative_type ref +); + +ruby_elsif_consequence( + unique int ruby_elsif: @ruby_elsif ref, + unique int consequence: @ruby_then ref +); + +ruby_elsif_def( + unique int id: @ruby_elsif, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_end_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_end_block, index] +ruby_end_block_child( + int ruby_end_block: @ruby_end_block ref, + int index: int ref, + unique int child: @ruby_end_block_child_type ref +); + +ruby_end_block_def( + unique int id: @ruby_end_block, + int loc: @location ref +); + +@ruby_ensure_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_ensure, index] +ruby_ensure_child( + int ruby_ensure: @ruby_ensure ref, + int index: int ref, + unique int child: @ruby_ensure_child_type ref +); + +ruby_ensure_def( + unique int id: @ruby_ensure, + int loc: @location ref +); + +ruby_exception_variable_def( + unique int id: @ruby_exception_variable, + int child: @ruby_underscore_lhs ref, + int loc: @location ref +); + +@ruby_exceptions_child_type = @ruby_splat_argument | @ruby_underscore_arg + +#keyset[ruby_exceptions, index] +ruby_exceptions_child( + int ruby_exceptions: @ruby_exceptions ref, + int index: int ref, + unique int child: @ruby_exceptions_child_type ref +); + +ruby_exceptions_def( + unique int id: @ruby_exceptions, + int loc: @location ref +); + +@ruby_for_pattern_type = @ruby_left_assignment_list | @ruby_underscore_lhs + +ruby_for_def( + unique int id: @ruby_for, + int body: @ruby_do ref, + int pattern: @ruby_for_pattern_type ref, + int value: @ruby_in ref, + int loc: @location ref +); + +@ruby_hash_child_type = @ruby_hash_splat_argument | @ruby_pair + +#keyset[ruby_hash, index] +ruby_hash_child( + int ruby_hash: @ruby_hash ref, + int index: int ref, + unique int child: @ruby_hash_child_type ref +); + +ruby_hash_def( + unique int id: @ruby_hash, + int loc: @location ref +); + +ruby_hash_splat_argument_def( + unique int id: @ruby_hash_splat_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_hash_splat_parameter_name( + unique int ruby_hash_splat_parameter: @ruby_hash_splat_parameter ref, + unique int name: @ruby_token_identifier ref +); + +ruby_hash_splat_parameter_def( + unique int id: @ruby_hash_splat_parameter, + int loc: @location ref +); + +@ruby_heredoc_body_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_heredoc_content | @ruby_token_heredoc_end + +#keyset[ruby_heredoc_body, index] +ruby_heredoc_body_child( + int ruby_heredoc_body: @ruby_heredoc_body ref, + int index: int ref, + unique int child: @ruby_heredoc_body_child_type ref +); + +ruby_heredoc_body_def( + unique int id: @ruby_heredoc_body, + int loc: @location ref +); + +@ruby_if_alternative_type = @ruby_else | @ruby_elsif + +ruby_if_alternative( + unique int ruby_if: @ruby_if ref, + unique int alternative: @ruby_if_alternative_type ref +); + +ruby_if_consequence( + unique int ruby_if: @ruby_if ref, + unique int consequence: @ruby_then ref +); + +ruby_if_def( + unique int id: @ruby_if, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_if_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_if_modifier_def( + unique int id: @ruby_if_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_if_modifier_condition_type ref, + int loc: @location ref +); + +ruby_in_def( + unique int id: @ruby_in, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_interpolation_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_interpolation, index] +ruby_interpolation_child( + int ruby_interpolation: @ruby_interpolation ref, + int index: int ref, + unique int child: @ruby_interpolation_child_type ref +); + +ruby_interpolation_def( + unique int id: @ruby_interpolation, + int loc: @location ref +); + +ruby_keyword_parameter_value( + unique int ruby_keyword_parameter: @ruby_keyword_parameter ref, + unique int value: @ruby_underscore_arg ref +); + +ruby_keyword_parameter_def( + unique int id: @ruby_keyword_parameter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_lambda_body_type = @ruby_block | @ruby_do_block + +ruby_lambda_parameters( + unique int ruby_lambda: @ruby_lambda ref, + unique int parameters: @ruby_lambda_parameters ref +); + +ruby_lambda_def( + unique int id: @ruby_lambda, + int body: @ruby_lambda_body_type ref, + int loc: @location ref +); + +@ruby_lambda_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_lambda_parameters, index] +ruby_lambda_parameters_child( + int ruby_lambda_parameters: @ruby_lambda_parameters ref, + int index: int ref, + unique int child: @ruby_lambda_parameters_child_type ref +); + +ruby_lambda_parameters_def( + unique int id: @ruby_lambda_parameters, + int loc: @location ref +); + +@ruby_left_assignment_list_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs + +#keyset[ruby_left_assignment_list, index] +ruby_left_assignment_list_child( + int ruby_left_assignment_list: @ruby_left_assignment_list ref, + int index: int ref, + unique int child: @ruby_left_assignment_list_child_type ref +); + +ruby_left_assignment_list_def( + unique int id: @ruby_left_assignment_list, + int loc: @location ref +); + +ruby_method_parameters( + unique int ruby_method: @ruby_method ref, + unique int parameters: @ruby_method_parameters ref +); + +@ruby_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_method, index] +ruby_method_child( + int ruby_method: @ruby_method ref, + int index: int ref, + unique int child: @ruby_method_child_type ref +); + +ruby_method_def( + unique int id: @ruby_method, + int name: @ruby_underscore_method_name ref, + int loc: @location ref +); + +@ruby_method_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_method_parameters, index] +ruby_method_parameters_child( + int ruby_method_parameters: @ruby_method_parameters ref, + int index: int ref, + unique int child: @ruby_method_parameters_child_type ref +); + +ruby_method_parameters_def( + unique int id: @ruby_method_parameters, + int loc: @location ref +); + +@ruby_module_name_type = @ruby_scope_resolution | @ruby_token_constant + +@ruby_module_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_module, index] +ruby_module_child( + int ruby_module: @ruby_module ref, + int index: int ref, + unique int child: @ruby_module_child_type ref +); + +ruby_module_def( + unique int id: @ruby_module, + int name: @ruby_module_name_type ref, + int loc: @location ref +); + +ruby_next_child( + unique int ruby_next: @ruby_next ref, + unique int child: @ruby_argument_list ref +); + +ruby_next_def( + unique int id: @ruby_next, + int loc: @location ref +); + +case @ruby_operator_assignment.operator of + 0 = @ruby_operator_assignment_percentequal +| 1 = @ruby_operator_assignment_ampersandampersandequal +| 2 = @ruby_operator_assignment_ampersandequal +| 3 = @ruby_operator_assignment_starstarequal +| 4 = @ruby_operator_assignment_starequal +| 5 = @ruby_operator_assignment_plusequal +| 6 = @ruby_operator_assignment_minusequal +| 7 = @ruby_operator_assignment_slashequal +| 8 = @ruby_operator_assignment_langlelangleequal +| 9 = @ruby_operator_assignment_ranglerangleequal +| 10 = @ruby_operator_assignment_caretequal +| 11 = @ruby_operator_assignment_pipeequal +| 12 = @ruby_operator_assignment_pipepipeequal +; + + +@ruby_operator_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_operator_assignment_def( + unique int id: @ruby_operator_assignment, + int left: @ruby_underscore_lhs ref, + int operator: int ref, + int right: @ruby_operator_assignment_right_type ref, + int loc: @location ref +); + +ruby_optional_parameter_def( + unique int id: @ruby_optional_parameter, + int name: @ruby_token_identifier ref, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_pair_key_type = @ruby_string__ | @ruby_token_hash_key_symbol | @ruby_underscore_arg + +ruby_pair_def( + unique int id: @ruby_pair, + int key__: @ruby_pair_key_type ref, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_parenthesized_statements_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_parenthesized_statements, index] +ruby_parenthesized_statements_child( + int ruby_parenthesized_statements: @ruby_parenthesized_statements ref, + int index: int ref, + unique int child: @ruby_parenthesized_statements_child_type ref +); + +ruby_parenthesized_statements_def( + unique int id: @ruby_parenthesized_statements, + int loc: @location ref +); + +@ruby_pattern_child_type = @ruby_splat_argument | @ruby_underscore_arg + +ruby_pattern_def( + unique int id: @ruby_pattern, + int child: @ruby_pattern_child_type ref, + int loc: @location ref +); + +@ruby_program_child_type = @ruby_token_empty_statement | @ruby_token_uninterpreted | @ruby_underscore_statement + +#keyset[ruby_program, index] +ruby_program_child( + int ruby_program: @ruby_program ref, + int index: int ref, + unique int child: @ruby_program_child_type ref +); + +ruby_program_def( + unique int id: @ruby_program, + int loc: @location ref +); + +ruby_range_begin( + unique int ruby_range: @ruby_range ref, + unique int begin: @ruby_underscore_arg ref +); + +ruby_range_end( + unique int ruby_range: @ruby_range ref, + unique int end: @ruby_underscore_arg ref +); + +case @ruby_range.operator of + 0 = @ruby_range_dotdot +| 1 = @ruby_range_dotdotdot +; + + +ruby_range_def( + unique int id: @ruby_range, + int operator: int ref, + int loc: @location ref +); + +@ruby_rational_child_type = @ruby_token_float | @ruby_token_integer + +ruby_rational_def( + unique int id: @ruby_rational, + int child: @ruby_rational_child_type ref, + int loc: @location ref +); + +ruby_redo_child( + unique int ruby_redo: @ruby_redo ref, + unique int child: @ruby_argument_list ref +); + +ruby_redo_def( + unique int id: @ruby_redo, + int loc: @location ref +); + +@ruby_regex_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_regex, index] +ruby_regex_child( + int ruby_regex: @ruby_regex ref, + int index: int ref, + unique int child: @ruby_regex_child_type ref +); + +ruby_regex_def( + unique int id: @ruby_regex, + int loc: @location ref +); + +ruby_rescue_body( + unique int ruby_rescue: @ruby_rescue ref, + unique int body: @ruby_then ref +); + +ruby_rescue_exceptions( + unique int ruby_rescue: @ruby_rescue ref, + unique int exceptions: @ruby_exceptions ref +); + +ruby_rescue_variable( + unique int ruby_rescue: @ruby_rescue ref, + unique int variable: @ruby_exception_variable ref +); + +ruby_rescue_def( + unique int id: @ruby_rescue, + int loc: @location ref +); + +@ruby_rescue_modifier_handler_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_rescue_modifier_def( + unique int id: @ruby_rescue_modifier, + int body: @ruby_underscore_statement ref, + int handler: @ruby_rescue_modifier_handler_type ref, + int loc: @location ref +); + +ruby_rest_assignment_child( + unique int ruby_rest_assignment: @ruby_rest_assignment ref, + unique int child: @ruby_underscore_lhs ref +); + +ruby_rest_assignment_def( + unique int id: @ruby_rest_assignment, + int loc: @location ref +); + +ruby_retry_child( + unique int ruby_retry: @ruby_retry ref, + unique int child: @ruby_argument_list ref +); + +ruby_retry_def( + unique int id: @ruby_retry, + int loc: @location ref +); + +ruby_return_child( + unique int ruby_return: @ruby_return ref, + unique int child: @ruby_argument_list ref +); + +ruby_return_def( + unique int id: @ruby_return, + int loc: @location ref +); + +@ruby_right_assignment_list_child_type = @ruby_splat_argument | @ruby_underscore_arg + +#keyset[ruby_right_assignment_list, index] +ruby_right_assignment_list_child( + int ruby_right_assignment_list: @ruby_right_assignment_list ref, + int index: int ref, + unique int child: @ruby_right_assignment_list_child_type ref +); + +ruby_right_assignment_list_def( + unique int id: @ruby_right_assignment_list, + int loc: @location ref +); + +@ruby_scope_resolution_name_type = @ruby_token_constant | @ruby_token_identifier + +ruby_scope_resolution_scope( + unique int ruby_scope_resolution: @ruby_scope_resolution ref, + unique int scope: @ruby_underscore_primary ref +); + +ruby_scope_resolution_def( + unique int id: @ruby_scope_resolution, + int name: @ruby_scope_resolution_name_type ref, + int loc: @location ref +); + +ruby_setter_def( + unique int id: @ruby_setter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_singleton_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_singleton_class, index] +ruby_singleton_class_child( + int ruby_singleton_class: @ruby_singleton_class ref, + int index: int ref, + unique int child: @ruby_singleton_class_child_type ref +); + +ruby_singleton_class_def( + unique int id: @ruby_singleton_class, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_singleton_method_object_type = @ruby_underscore_arg | @ruby_underscore_variable + +ruby_singleton_method_parameters( + unique int ruby_singleton_method: @ruby_singleton_method ref, + unique int parameters: @ruby_method_parameters ref +); + +@ruby_singleton_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_singleton_method, index] +ruby_singleton_method_child( + int ruby_singleton_method: @ruby_singleton_method ref, + int index: int ref, + unique int child: @ruby_singleton_method_child_type ref +); + +ruby_singleton_method_def( + unique int id: @ruby_singleton_method, + int name: @ruby_underscore_method_name ref, + int object: @ruby_singleton_method_object_type ref, + int loc: @location ref +); + +ruby_splat_argument_def( + unique int id: @ruby_splat_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_splat_parameter_name( + unique int ruby_splat_parameter: @ruby_splat_parameter ref, + unique int name: @ruby_token_identifier ref +); + +ruby_splat_parameter_def( + unique int id: @ruby_splat_parameter, + int loc: @location ref +); + +@ruby_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_string__, index] +ruby_string_child( + int ruby_string__: @ruby_string__ ref, + int index: int ref, + unique int child: @ruby_string_child_type ref +); + +ruby_string_def( + unique int id: @ruby_string__, + int loc: @location ref +); + +#keyset[ruby_string_array, index] +ruby_string_array_child( + int ruby_string_array: @ruby_string_array ref, + int index: int ref, + unique int child: @ruby_bare_string ref +); + +ruby_string_array_def( + unique int id: @ruby_string_array, + int loc: @location ref +); + +@ruby_subshell_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_subshell, index] +ruby_subshell_child( + int ruby_subshell: @ruby_subshell ref, + int index: int ref, + unique int child: @ruby_subshell_child_type ref +); + +ruby_subshell_def( + unique int id: @ruby_subshell, + int loc: @location ref +); + +@ruby_superclass_child_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_superclass_def( + unique int id: @ruby_superclass, + int child: @ruby_superclass_child_type ref, + int loc: @location ref +); + +#keyset[ruby_symbol_array, index] +ruby_symbol_array_child( + int ruby_symbol_array: @ruby_symbol_array ref, + int index: int ref, + unique int child: @ruby_bare_symbol ref +); + +ruby_symbol_array_def( + unique int id: @ruby_symbol_array, + int loc: @location ref +); + +@ruby_then_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_then, index] +ruby_then_child( + int ruby_then: @ruby_then ref, + int index: int ref, + unique int child: @ruby_then_child_type ref +); + +ruby_then_def( + unique int id: @ruby_then, + int loc: @location ref +); + +@ruby_unary_operand_type = @ruby_break | @ruby_call | @ruby_next | @ruby_parenthesized_statements | @ruby_return | @ruby_token_float | @ruby_token_integer | @ruby_underscore_arg | @ruby_yield + +case @ruby_unary.operator of + 0 = @ruby_unary_bang +| 1 = @ruby_unary_plus +| 2 = @ruby_unary_minus +| 3 = @ruby_unary_definedquestion +| 4 = @ruby_unary_not +| 5 = @ruby_unary_tilde +; + + +ruby_unary_def( + unique int id: @ruby_unary, + int operand: @ruby_unary_operand_type ref, + int operator: int ref, + int loc: @location ref +); + +#keyset[ruby_undef, index] +ruby_undef_child( + int ruby_undef: @ruby_undef ref, + int index: int ref, + unique int child: @ruby_underscore_method_name ref +); + +ruby_undef_def( + unique int id: @ruby_undef, + int loc: @location ref +); + +@ruby_unless_alternative_type = @ruby_else | @ruby_elsif + +ruby_unless_alternative( + unique int ruby_unless: @ruby_unless ref, + unique int alternative: @ruby_unless_alternative_type ref +); + +ruby_unless_consequence( + unique int ruby_unless: @ruby_unless ref, + unique int consequence: @ruby_then ref +); + +ruby_unless_def( + unique int id: @ruby_unless, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_unless_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_unless_modifier_def( + unique int id: @ruby_unless_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_unless_modifier_condition_type ref, + int loc: @location ref +); + +ruby_until_def( + unique int id: @ruby_until, + int body: @ruby_do ref, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_until_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_until_modifier_def( + unique int id: @ruby_until_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_until_modifier_condition_type ref, + int loc: @location ref +); + +ruby_when_body( + unique int ruby_when: @ruby_when ref, + unique int body: @ruby_then ref +); + +#keyset[ruby_when, index] +ruby_when_pattern( + int ruby_when: @ruby_when ref, + int index: int ref, + unique int pattern: @ruby_pattern ref +); + +ruby_when_def( + unique int id: @ruby_when, + int loc: @location ref +); + +ruby_while_def( + unique int id: @ruby_while, + int body: @ruby_do ref, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_while_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_while_modifier_def( + unique int id: @ruby_while_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_while_modifier_condition_type ref, + int loc: @location ref +); + +ruby_yield_child( + unique int ruby_yield: @ruby_yield ref, + unique int child: @ruby_argument_list ref +); + +ruby_yield_def( + unique int id: @ruby_yield, + int loc: @location ref +); + +ruby_tokeninfo( + unique int id: @ruby_token, + int kind: int ref, + int file: @file ref, + int idx: int ref, + string value: string ref, + int loc: @location ref +); + +case @ruby_token.kind of + 0 = @ruby_reserved_word +| 1 = @ruby_token_character +| 2 = @ruby_token_class_variable +| 3 = @ruby_token_comment +| 4 = @ruby_token_complex +| 5 = @ruby_token_constant +| 6 = @ruby_token_empty_statement +| 7 = @ruby_token_escape_sequence +| 8 = @ruby_token_false +| 9 = @ruby_token_float +| 10 = @ruby_token_global_variable +| 11 = @ruby_token_hash_key_symbol +| 12 = @ruby_token_heredoc_beginning +| 13 = @ruby_token_heredoc_content +| 14 = @ruby_token_heredoc_end +| 15 = @ruby_token_identifier +| 16 = @ruby_token_instance_variable +| 17 = @ruby_token_integer +| 18 = @ruby_token_nil +| 19 = @ruby_token_operator +| 20 = @ruby_token_self +| 21 = @ruby_token_simple_symbol +| 22 = @ruby_token_string_content +| 23 = @ruby_token_super +| 24 = @ruby_token_true +| 25 = @ruby_token_uninterpreted +; + + +@ruby_ast_node = @ruby_alias | @ruby_argument_list | @ruby_array | @ruby_assignment | @ruby_bare_string | @ruby_bare_symbol | @ruby_begin | @ruby_begin_block | @ruby_binary | @ruby_block | @ruby_block_argument | @ruby_block_parameter | @ruby_block_parameters | @ruby_break | @ruby_call | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_conditional | @ruby_delimited_symbol | @ruby_destructured_left_assignment | @ruby_destructured_parameter | @ruby_do | @ruby_do_block | @ruby_element_reference | @ruby_else | @ruby_elsif | @ruby_end_block | @ruby_ensure | @ruby_exception_variable | @ruby_exceptions | @ruby_for | @ruby_hash | @ruby_hash_splat_argument | @ruby_hash_splat_parameter | @ruby_heredoc_body | @ruby_if | @ruby_if_modifier | @ruby_in | @ruby_interpolation | @ruby_keyword_parameter | @ruby_lambda | @ruby_lambda_parameters | @ruby_left_assignment_list | @ruby_method | @ruby_method_parameters | @ruby_module | @ruby_next | @ruby_operator_assignment | @ruby_optional_parameter | @ruby_pair | @ruby_parenthesized_statements | @ruby_pattern | @ruby_program | @ruby_range | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_rescue | @ruby_rescue_modifier | @ruby_rest_assignment | @ruby_retry | @ruby_return | @ruby_right_assignment_list | @ruby_scope_resolution | @ruby_setter | @ruby_singleton_class | @ruby_singleton_method | @ruby_splat_argument | @ruby_splat_parameter | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_superclass | @ruby_symbol_array | @ruby_then | @ruby_token | @ruby_unary | @ruby_undef | @ruby_unless | @ruby_unless_modifier | @ruby_until | @ruby_until_modifier | @ruby_when | @ruby_while | @ruby_while_modifier | @ruby_yield + +@ruby_ast_node_parent = @file | @ruby_ast_node + +#keyset[parent, parent_index] +ruby_ast_node_parent( + int child: @ruby_ast_node ref, + int parent: @ruby_ast_node_parent ref, + int parent_index: int ref +); + +erb_comment_directive_def( + unique int id: @erb_comment_directive, + int child: @erb_token_comment ref, + int loc: @location ref +); + +erb_directive_def( + unique int id: @erb_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +erb_graphql_directive_def( + unique int id: @erb_graphql_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +erb_output_directive_def( + unique int id: @erb_output_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +@erb_template_child_type = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_token_content + +#keyset[erb_template, index] +erb_template_child( + int erb_template: @erb_template ref, + int index: int ref, + unique int child: @erb_template_child_type ref +); + +erb_template_def( + unique int id: @erb_template, + int loc: @location ref +); + +erb_tokeninfo( + unique int id: @erb_token, + int kind: int ref, + int file: @file ref, + int idx: int ref, + string value: string ref, + int loc: @location ref +); + +case @erb_token.kind of + 0 = @erb_reserved_word +| 1 = @erb_token_code +| 2 = @erb_token_comment +| 3 = @erb_token_content +; + + +@erb_ast_node = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_template | @erb_token + +@erb_ast_node_parent = @erb_ast_node | @file + +#keyset[parent, parent_index] +erb_ast_node_parent( + int child: @erb_ast_node ref, + int parent: @erb_ast_node_parent ref, + int parent_index: int ref +); + diff --git a/ruby/ql/lib/upgrades/b5aef9c93ae64f848017d2dcb760eed916ab0cdd/ruby.dbscheme b/ruby/ql/lib/upgrades/b5aef9c93ae64f848017d2dcb760eed916ab0cdd/ruby.dbscheme new file mode 100644 index 000000000000..31a238d080f3 --- /dev/null +++ b/ruby/ql/lib/upgrades/b5aef9c93ae64f848017d2dcb760eed916ab0cdd/ruby.dbscheme @@ -0,0 +1,1316 @@ +// CodeQL database schema for Ruby +// 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 +; + + +@ruby_underscore_arg = @ruby_assignment | @ruby_binary | @ruby_conditional | @ruby_operator_assignment | @ruby_range | @ruby_unary | @ruby_underscore_primary + +@ruby_underscore_lhs = @ruby_call | @ruby_element_reference | @ruby_scope_resolution | @ruby_token_false | @ruby_token_nil | @ruby_token_true | @ruby_underscore_variable + +@ruby_underscore_method_name = @ruby_delimited_symbol | @ruby_setter | @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_operator | @ruby_token_simple_symbol + +@ruby_underscore_primary = @ruby_array | @ruby_begin | @ruby_break | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_delimited_symbol | @ruby_for | @ruby_hash | @ruby_if | @ruby_lambda | @ruby_method | @ruby_module | @ruby_next | @ruby_parenthesized_statements | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_retry | @ruby_return | @ruby_singleton_class | @ruby_singleton_method | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_symbol_array | @ruby_token_character | @ruby_token_complex | @ruby_token_float | @ruby_token_heredoc_beginning | @ruby_token_integer | @ruby_token_simple_symbol | @ruby_unary | @ruby_underscore_lhs | @ruby_unless | @ruby_until | @ruby_while | @ruby_yield + +@ruby_underscore_statement = @ruby_alias | @ruby_assignment | @ruby_begin_block | @ruby_binary | @ruby_break | @ruby_call | @ruby_end_block | @ruby_if_modifier | @ruby_next | @ruby_operator_assignment | @ruby_rescue_modifier | @ruby_return | @ruby_unary | @ruby_undef | @ruby_underscore_arg | @ruby_unless_modifier | @ruby_until_modifier | @ruby_while_modifier | @ruby_yield + +@ruby_underscore_variable = @ruby_token_class_variable | @ruby_token_constant | @ruby_token_global_variable | @ruby_token_identifier | @ruby_token_instance_variable | @ruby_token_self | @ruby_token_super + +ruby_alias_def( + unique int id: @ruby_alias, + int alias: @ruby_underscore_method_name ref, + int name: @ruby_underscore_method_name ref, + int loc: @location ref +); + +@ruby_argument_list_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_argument_list, index] +ruby_argument_list_child( + int ruby_argument_list: @ruby_argument_list ref, + int index: int ref, + unique int child: @ruby_argument_list_child_type ref +); + +ruby_argument_list_def( + unique int id: @ruby_argument_list, + int loc: @location ref +); + +@ruby_array_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_array, index] +ruby_array_child( + int ruby_array: @ruby_array ref, + int index: int ref, + unique int child: @ruby_array_child_type ref +); + +ruby_array_def( + unique int id: @ruby_array, + int loc: @location ref +); + +@ruby_assignment_left_type = @ruby_left_assignment_list | @ruby_underscore_lhs + +@ruby_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_right_assignment_list | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +ruby_assignment_def( + unique int id: @ruby_assignment, + int left: @ruby_assignment_left_type ref, + int right: @ruby_assignment_right_type ref, + int loc: @location ref +); + +@ruby_bare_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_bare_string, index] +ruby_bare_string_child( + int ruby_bare_string: @ruby_bare_string ref, + int index: int ref, + unique int child: @ruby_bare_string_child_type ref +); + +ruby_bare_string_def( + unique int id: @ruby_bare_string, + int loc: @location ref +); + +@ruby_bare_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_bare_symbol, index] +ruby_bare_symbol_child( + int ruby_bare_symbol: @ruby_bare_symbol ref, + int index: int ref, + unique int child: @ruby_bare_symbol_child_type ref +); + +ruby_bare_symbol_def( + unique int id: @ruby_bare_symbol, + int loc: @location ref +); + +@ruby_begin_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_begin, index] +ruby_begin_child( + int ruby_begin: @ruby_begin ref, + int index: int ref, + unique int child: @ruby_begin_child_type ref +); + +ruby_begin_def( + unique int id: @ruby_begin, + int loc: @location ref +); + +@ruby_begin_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_begin_block, index] +ruby_begin_block_child( + int ruby_begin_block: @ruby_begin_block ref, + int index: int ref, + unique int child: @ruby_begin_block_child_type ref +); + +ruby_begin_block_def( + unique int id: @ruby_begin_block, + int loc: @location ref +); + +@ruby_binary_left_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +case @ruby_binary.operator of + 0 = @ruby_binary_bangequal +| 1 = @ruby_binary_bangtilde +| 2 = @ruby_binary_percent +| 3 = @ruby_binary_ampersand +| 4 = @ruby_binary_ampersandampersand +| 5 = @ruby_binary_star +| 6 = @ruby_binary_starstar +| 7 = @ruby_binary_plus +| 8 = @ruby_binary_minus +| 9 = @ruby_binary_slash +| 10 = @ruby_binary_langle +| 11 = @ruby_binary_langlelangle +| 12 = @ruby_binary_langleequal +| 13 = @ruby_binary_langleequalrangle +| 14 = @ruby_binary_equalequal +| 15 = @ruby_binary_equalequalequal +| 16 = @ruby_binary_equaltilde +| 17 = @ruby_binary_rangle +| 18 = @ruby_binary_rangleequal +| 19 = @ruby_binary_ranglerangle +| 20 = @ruby_binary_caret +| 21 = @ruby_binary_and +| 22 = @ruby_binary_or +| 23 = @ruby_binary_pipe +| 24 = @ruby_binary_pipepipe +; + + +@ruby_binary_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_binary_def( + unique int id: @ruby_binary, + int left: @ruby_binary_left_type ref, + int operator: int ref, + int right: @ruby_binary_right_type ref, + int loc: @location ref +); + +ruby_block_parameters( + unique int ruby_block: @ruby_block ref, + unique int parameters: @ruby_block_parameters ref +); + +@ruby_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_block, index] +ruby_block_child( + int ruby_block: @ruby_block ref, + int index: int ref, + unique int child: @ruby_block_child_type ref +); + +ruby_block_def( + unique int id: @ruby_block, + int loc: @location ref +); + +ruby_block_argument_def( + unique int id: @ruby_block_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_block_parameter_def( + unique int id: @ruby_block_parameter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_block_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_block_parameters, index] +ruby_block_parameters_child( + int ruby_block_parameters: @ruby_block_parameters ref, + int index: int ref, + unique int child: @ruby_block_parameters_child_type ref +); + +ruby_block_parameters_def( + unique int id: @ruby_block_parameters, + int loc: @location ref +); + +ruby_break_child( + unique int ruby_break: @ruby_break ref, + unique int child: @ruby_argument_list ref +); + +ruby_break_def( + unique int id: @ruby_break, + int loc: @location ref +); + +ruby_call_arguments( + unique int ruby_call: @ruby_call ref, + unique int arguments: @ruby_argument_list ref +); + +@ruby_call_block_type = @ruby_block | @ruby_do_block + +ruby_call_block( + unique int ruby_call: @ruby_call ref, + unique int block: @ruby_call_block_type ref +); + +@ruby_call_method_type = @ruby_argument_list | @ruby_scope_resolution | @ruby_token_operator | @ruby_underscore_variable + +@ruby_call_receiver_type = @ruby_call | @ruby_underscore_primary + +ruby_call_receiver( + unique int ruby_call: @ruby_call ref, + unique int receiver: @ruby_call_receiver_type ref +); + +ruby_call_def( + unique int id: @ruby_call, + int method: @ruby_call_method_type ref, + int loc: @location ref +); + +ruby_case_value( + unique int ruby_case__: @ruby_case__ ref, + unique int value: @ruby_underscore_statement ref +); + +@ruby_case_child_type = @ruby_else | @ruby_when + +#keyset[ruby_case__, index] +ruby_case_child( + int ruby_case__: @ruby_case__ ref, + int index: int ref, + unique int child: @ruby_case_child_type ref +); + +ruby_case_def( + unique int id: @ruby_case__, + int loc: @location ref +); + +#keyset[ruby_chained_string, index] +ruby_chained_string_child( + int ruby_chained_string: @ruby_chained_string ref, + int index: int ref, + unique int child: @ruby_string__ ref +); + +ruby_chained_string_def( + unique int id: @ruby_chained_string, + int loc: @location ref +); + +@ruby_class_name_type = @ruby_scope_resolution | @ruby_token_constant + +ruby_class_superclass( + unique int ruby_class: @ruby_class ref, + unique int superclass: @ruby_superclass ref +); + +@ruby_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_class, index] +ruby_class_child( + int ruby_class: @ruby_class ref, + int index: int ref, + unique int child: @ruby_class_child_type ref +); + +ruby_class_def( + unique int id: @ruby_class, + int name: @ruby_class_name_type ref, + int loc: @location ref +); + +ruby_conditional_def( + unique int id: @ruby_conditional, + int alternative: @ruby_underscore_arg ref, + int condition: @ruby_underscore_arg ref, + int consequence: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_delimited_symbol_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_delimited_symbol, index] +ruby_delimited_symbol_child( + int ruby_delimited_symbol: @ruby_delimited_symbol ref, + int index: int ref, + unique int child: @ruby_delimited_symbol_child_type ref +); + +ruby_delimited_symbol_def( + unique int id: @ruby_delimited_symbol, + int loc: @location ref +); + +@ruby_destructured_left_assignment_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs + +#keyset[ruby_destructured_left_assignment, index] +ruby_destructured_left_assignment_child( + int ruby_destructured_left_assignment: @ruby_destructured_left_assignment ref, + int index: int ref, + unique int child: @ruby_destructured_left_assignment_child_type ref +); + +ruby_destructured_left_assignment_def( + unique int id: @ruby_destructured_left_assignment, + int loc: @location ref +); + +@ruby_destructured_parameter_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_destructured_parameter, index] +ruby_destructured_parameter_child( + int ruby_destructured_parameter: @ruby_destructured_parameter ref, + int index: int ref, + unique int child: @ruby_destructured_parameter_child_type ref +); + +ruby_destructured_parameter_def( + unique int id: @ruby_destructured_parameter, + int loc: @location ref +); + +@ruby_do_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_do, index] +ruby_do_child( + int ruby_do: @ruby_do ref, + int index: int ref, + unique int child: @ruby_do_child_type ref +); + +ruby_do_def( + unique int id: @ruby_do, + int loc: @location ref +); + +ruby_do_block_parameters( + unique int ruby_do_block: @ruby_do_block ref, + unique int parameters: @ruby_block_parameters ref +); + +@ruby_do_block_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_do_block, index] +ruby_do_block_child( + int ruby_do_block: @ruby_do_block ref, + int index: int ref, + unique int child: @ruby_do_block_child_type ref +); + +ruby_do_block_def( + unique int id: @ruby_do_block, + int loc: @location ref +); + +@ruby_element_reference_child_type = @ruby_block_argument | @ruby_break | @ruby_call | @ruby_hash_splat_argument | @ruby_next | @ruby_pair | @ruby_return | @ruby_splat_argument | @ruby_underscore_arg | @ruby_yield + +#keyset[ruby_element_reference, index] +ruby_element_reference_child( + int ruby_element_reference: @ruby_element_reference ref, + int index: int ref, + unique int child: @ruby_element_reference_child_type ref +); + +ruby_element_reference_def( + unique int id: @ruby_element_reference, + int object: @ruby_underscore_primary ref, + int loc: @location ref +); + +@ruby_else_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_else, index] +ruby_else_child( + int ruby_else: @ruby_else ref, + int index: int ref, + unique int child: @ruby_else_child_type ref +); + +ruby_else_def( + unique int id: @ruby_else, + int loc: @location ref +); + +@ruby_elsif_alternative_type = @ruby_else | @ruby_elsif + +ruby_elsif_alternative( + unique int ruby_elsif: @ruby_elsif ref, + unique int alternative: @ruby_elsif_alternative_type ref +); + +ruby_elsif_consequence( + unique int ruby_elsif: @ruby_elsif ref, + unique int consequence: @ruby_then ref +); + +ruby_elsif_def( + unique int id: @ruby_elsif, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_end_block_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_end_block, index] +ruby_end_block_child( + int ruby_end_block: @ruby_end_block ref, + int index: int ref, + unique int child: @ruby_end_block_child_type ref +); + +ruby_end_block_def( + unique int id: @ruby_end_block, + int loc: @location ref +); + +@ruby_ensure_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_ensure, index] +ruby_ensure_child( + int ruby_ensure: @ruby_ensure ref, + int index: int ref, + unique int child: @ruby_ensure_child_type ref +); + +ruby_ensure_def( + unique int id: @ruby_ensure, + int loc: @location ref +); + +ruby_exception_variable_def( + unique int id: @ruby_exception_variable, + int child: @ruby_underscore_lhs ref, + int loc: @location ref +); + +@ruby_exceptions_child_type = @ruby_splat_argument | @ruby_underscore_arg + +#keyset[ruby_exceptions, index] +ruby_exceptions_child( + int ruby_exceptions: @ruby_exceptions ref, + int index: int ref, + unique int child: @ruby_exceptions_child_type ref +); + +ruby_exceptions_def( + unique int id: @ruby_exceptions, + int loc: @location ref +); + +@ruby_for_pattern_type = @ruby_left_assignment_list | @ruby_underscore_lhs + +ruby_for_def( + unique int id: @ruby_for, + int body: @ruby_do ref, + int pattern: @ruby_for_pattern_type ref, + int value: @ruby_in ref, + int loc: @location ref +); + +@ruby_hash_child_type = @ruby_hash_splat_argument | @ruby_pair + +#keyset[ruby_hash, index] +ruby_hash_child( + int ruby_hash: @ruby_hash ref, + int index: int ref, + unique int child: @ruby_hash_child_type ref +); + +ruby_hash_def( + unique int id: @ruby_hash, + int loc: @location ref +); + +ruby_hash_splat_argument_def( + unique int id: @ruby_hash_splat_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_hash_splat_parameter_name( + unique int ruby_hash_splat_parameter: @ruby_hash_splat_parameter ref, + unique int name: @ruby_token_identifier ref +); + +ruby_hash_splat_parameter_def( + unique int id: @ruby_hash_splat_parameter, + int loc: @location ref +); + +@ruby_heredoc_body_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_heredoc_content | @ruby_token_heredoc_end + +#keyset[ruby_heredoc_body, index] +ruby_heredoc_body_child( + int ruby_heredoc_body: @ruby_heredoc_body ref, + int index: int ref, + unique int child: @ruby_heredoc_body_child_type ref +); + +ruby_heredoc_body_def( + unique int id: @ruby_heredoc_body, + int loc: @location ref +); + +@ruby_if_alternative_type = @ruby_else | @ruby_elsif + +ruby_if_alternative( + unique int ruby_if: @ruby_if ref, + unique int alternative: @ruby_if_alternative_type ref +); + +ruby_if_consequence( + unique int ruby_if: @ruby_if ref, + unique int consequence: @ruby_then ref +); + +ruby_if_def( + unique int id: @ruby_if, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_if_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_if_modifier_def( + unique int id: @ruby_if_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_if_modifier_condition_type ref, + int loc: @location ref +); + +ruby_in_def( + unique int id: @ruby_in, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_interpolation_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_interpolation, index] +ruby_interpolation_child( + int ruby_interpolation: @ruby_interpolation ref, + int index: int ref, + unique int child: @ruby_interpolation_child_type ref +); + +ruby_interpolation_def( + unique int id: @ruby_interpolation, + int loc: @location ref +); + +ruby_keyword_parameter_value( + unique int ruby_keyword_parameter: @ruby_keyword_parameter ref, + unique int value: @ruby_underscore_arg ref +); + +ruby_keyword_parameter_def( + unique int id: @ruby_keyword_parameter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_lambda_body_type = @ruby_block | @ruby_do_block + +ruby_lambda_parameters( + unique int ruby_lambda: @ruby_lambda ref, + unique int parameters: @ruby_lambda_parameters ref +); + +ruby_lambda_def( + unique int id: @ruby_lambda, + int body: @ruby_lambda_body_type ref, + int loc: @location ref +); + +@ruby_lambda_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_lambda_parameters, index] +ruby_lambda_parameters_child( + int ruby_lambda_parameters: @ruby_lambda_parameters ref, + int index: int ref, + unique int child: @ruby_lambda_parameters_child_type ref +); + +ruby_lambda_parameters_def( + unique int id: @ruby_lambda_parameters, + int loc: @location ref +); + +@ruby_left_assignment_list_child_type = @ruby_destructured_left_assignment | @ruby_rest_assignment | @ruby_underscore_lhs + +#keyset[ruby_left_assignment_list, index] +ruby_left_assignment_list_child( + int ruby_left_assignment_list: @ruby_left_assignment_list ref, + int index: int ref, + unique int child: @ruby_left_assignment_list_child_type ref +); + +ruby_left_assignment_list_def( + unique int id: @ruby_left_assignment_list, + int loc: @location ref +); + +ruby_method_parameters( + unique int ruby_method: @ruby_method ref, + unique int parameters: @ruby_method_parameters ref +); + +@ruby_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_method, index] +ruby_method_child( + int ruby_method: @ruby_method ref, + int index: int ref, + unique int child: @ruby_method_child_type ref +); + +ruby_method_def( + unique int id: @ruby_method, + int name: @ruby_underscore_method_name ref, + int loc: @location ref +); + +@ruby_method_parameters_child_type = @ruby_block_parameter | @ruby_destructured_parameter | @ruby_hash_splat_parameter | @ruby_keyword_parameter | @ruby_optional_parameter | @ruby_splat_parameter | @ruby_token_identifier + +#keyset[ruby_method_parameters, index] +ruby_method_parameters_child( + int ruby_method_parameters: @ruby_method_parameters ref, + int index: int ref, + unique int child: @ruby_method_parameters_child_type ref +); + +ruby_method_parameters_def( + unique int id: @ruby_method_parameters, + int loc: @location ref +); + +@ruby_module_name_type = @ruby_scope_resolution | @ruby_token_constant + +@ruby_module_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_module, index] +ruby_module_child( + int ruby_module: @ruby_module ref, + int index: int ref, + unique int child: @ruby_module_child_type ref +); + +ruby_module_def( + unique int id: @ruby_module, + int name: @ruby_module_name_type ref, + int loc: @location ref +); + +ruby_next_child( + unique int ruby_next: @ruby_next ref, + unique int child: @ruby_argument_list ref +); + +ruby_next_def( + unique int id: @ruby_next, + int loc: @location ref +); + +case @ruby_operator_assignment.operator of + 0 = @ruby_operator_assignment_percentequal +| 1 = @ruby_operator_assignment_ampersandampersandequal +| 2 = @ruby_operator_assignment_ampersandequal +| 3 = @ruby_operator_assignment_starstarequal +| 4 = @ruby_operator_assignment_starequal +| 5 = @ruby_operator_assignment_plusequal +| 6 = @ruby_operator_assignment_minusequal +| 7 = @ruby_operator_assignment_slashequal +| 8 = @ruby_operator_assignment_langlelangleequal +| 9 = @ruby_operator_assignment_ranglerangleequal +| 10 = @ruby_operator_assignment_caretequal +| 11 = @ruby_operator_assignment_pipeequal +| 12 = @ruby_operator_assignment_pipepipeequal +; + + +@ruby_operator_assignment_right_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_operator_assignment_def( + unique int id: @ruby_operator_assignment, + int left: @ruby_underscore_lhs ref, + int operator: int ref, + int right: @ruby_operator_assignment_right_type ref, + int loc: @location ref +); + +ruby_optional_parameter_def( + unique int id: @ruby_optional_parameter, + int name: @ruby_token_identifier ref, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_pair_key_type = @ruby_string__ | @ruby_token_hash_key_symbol | @ruby_underscore_arg + +ruby_pair_def( + unique int id: @ruby_pair, + int key__: @ruby_pair_key_type ref, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_parenthesized_statements_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_parenthesized_statements, index] +ruby_parenthesized_statements_child( + int ruby_parenthesized_statements: @ruby_parenthesized_statements ref, + int index: int ref, + unique int child: @ruby_parenthesized_statements_child_type ref +); + +ruby_parenthesized_statements_def( + unique int id: @ruby_parenthesized_statements, + int loc: @location ref +); + +@ruby_pattern_child_type = @ruby_splat_argument | @ruby_underscore_arg + +ruby_pattern_def( + unique int id: @ruby_pattern, + int child: @ruby_pattern_child_type ref, + int loc: @location ref +); + +@ruby_program_child_type = @ruby_token_empty_statement | @ruby_token_uninterpreted | @ruby_underscore_statement + +#keyset[ruby_program, index] +ruby_program_child( + int ruby_program: @ruby_program ref, + int index: int ref, + unique int child: @ruby_program_child_type ref +); + +ruby_program_def( + unique int id: @ruby_program, + int loc: @location ref +); + +ruby_range_begin( + unique int ruby_range: @ruby_range ref, + unique int begin: @ruby_underscore_arg ref +); + +ruby_range_end( + unique int ruby_range: @ruby_range ref, + unique int end: @ruby_underscore_arg ref +); + +case @ruby_range.operator of + 0 = @ruby_range_dotdot +| 1 = @ruby_range_dotdotdot +; + + +ruby_range_def( + unique int id: @ruby_range, + int operator: int ref, + int loc: @location ref +); + +@ruby_rational_child_type = @ruby_token_float | @ruby_token_integer + +ruby_rational_def( + unique int id: @ruby_rational, + int child: @ruby_rational_child_type ref, + int loc: @location ref +); + +ruby_redo_child( + unique int ruby_redo: @ruby_redo ref, + unique int child: @ruby_argument_list ref +); + +ruby_redo_def( + unique int id: @ruby_redo, + int loc: @location ref +); + +@ruby_regex_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_regex, index] +ruby_regex_child( + int ruby_regex: @ruby_regex ref, + int index: int ref, + unique int child: @ruby_regex_child_type ref +); + +ruby_regex_def( + unique int id: @ruby_regex, + int loc: @location ref +); + +ruby_rescue_body( + unique int ruby_rescue: @ruby_rescue ref, + unique int body: @ruby_then ref +); + +ruby_rescue_exceptions( + unique int ruby_rescue: @ruby_rescue ref, + unique int exceptions: @ruby_exceptions ref +); + +ruby_rescue_variable( + unique int ruby_rescue: @ruby_rescue ref, + unique int variable: @ruby_exception_variable ref +); + +ruby_rescue_def( + unique int id: @ruby_rescue, + int loc: @location ref +); + +@ruby_rescue_modifier_handler_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_rescue_modifier_def( + unique int id: @ruby_rescue_modifier, + int body: @ruby_underscore_statement ref, + int handler: @ruby_rescue_modifier_handler_type ref, + int loc: @location ref +); + +ruby_rest_assignment_child( + unique int ruby_rest_assignment: @ruby_rest_assignment ref, + unique int child: @ruby_underscore_lhs ref +); + +ruby_rest_assignment_def( + unique int id: @ruby_rest_assignment, + int loc: @location ref +); + +ruby_retry_child( + unique int ruby_retry: @ruby_retry ref, + unique int child: @ruby_argument_list ref +); + +ruby_retry_def( + unique int id: @ruby_retry, + int loc: @location ref +); + +ruby_return_child( + unique int ruby_return: @ruby_return ref, + unique int child: @ruby_argument_list ref +); + +ruby_return_def( + unique int id: @ruby_return, + int loc: @location ref +); + +@ruby_right_assignment_list_child_type = @ruby_splat_argument | @ruby_underscore_arg + +#keyset[ruby_right_assignment_list, index] +ruby_right_assignment_list_child( + int ruby_right_assignment_list: @ruby_right_assignment_list ref, + int index: int ref, + unique int child: @ruby_right_assignment_list_child_type ref +); + +ruby_right_assignment_list_def( + unique int id: @ruby_right_assignment_list, + int loc: @location ref +); + +@ruby_scope_resolution_name_type = @ruby_token_constant | @ruby_token_identifier + +ruby_scope_resolution_scope( + unique int ruby_scope_resolution: @ruby_scope_resolution ref, + unique int scope: @ruby_underscore_primary ref +); + +ruby_scope_resolution_def( + unique int id: @ruby_scope_resolution, + int name: @ruby_scope_resolution_name_type ref, + int loc: @location ref +); + +ruby_setter_def( + unique int id: @ruby_setter, + int name: @ruby_token_identifier ref, + int loc: @location ref +); + +@ruby_singleton_class_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_singleton_class, index] +ruby_singleton_class_child( + int ruby_singleton_class: @ruby_singleton_class ref, + int index: int ref, + unique int child: @ruby_singleton_class_child_type ref +); + +ruby_singleton_class_def( + unique int id: @ruby_singleton_class, + int value: @ruby_underscore_arg ref, + int loc: @location ref +); + +@ruby_singleton_method_object_type = @ruby_underscore_arg | @ruby_underscore_variable + +ruby_singleton_method_parameters( + unique int ruby_singleton_method: @ruby_singleton_method ref, + unique int parameters: @ruby_method_parameters ref +); + +@ruby_singleton_method_child_type = @ruby_else | @ruby_ensure | @ruby_rescue | @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_singleton_method, index] +ruby_singleton_method_child( + int ruby_singleton_method: @ruby_singleton_method ref, + int index: int ref, + unique int child: @ruby_singleton_method_child_type ref +); + +ruby_singleton_method_def( + unique int id: @ruby_singleton_method, + int name: @ruby_underscore_method_name ref, + int object: @ruby_singleton_method_object_type ref, + int loc: @location ref +); + +ruby_splat_argument_def( + unique int id: @ruby_splat_argument, + int child: @ruby_underscore_arg ref, + int loc: @location ref +); + +ruby_splat_parameter_name( + unique int ruby_splat_parameter: @ruby_splat_parameter ref, + unique int name: @ruby_token_identifier ref +); + +ruby_splat_parameter_def( + unique int id: @ruby_splat_parameter, + int loc: @location ref +); + +@ruby_string_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_string__, index] +ruby_string_child( + int ruby_string__: @ruby_string__ ref, + int index: int ref, + unique int child: @ruby_string_child_type ref +); + +ruby_string_def( + unique int id: @ruby_string__, + int loc: @location ref +); + +#keyset[ruby_string_array, index] +ruby_string_array_child( + int ruby_string_array: @ruby_string_array ref, + int index: int ref, + unique int child: @ruby_bare_string ref +); + +ruby_string_array_def( + unique int id: @ruby_string_array, + int loc: @location ref +); + +@ruby_subshell_child_type = @ruby_interpolation | @ruby_token_escape_sequence | @ruby_token_string_content + +#keyset[ruby_subshell, index] +ruby_subshell_child( + int ruby_subshell: @ruby_subshell ref, + int index: int ref, + unique int child: @ruby_subshell_child_type ref +); + +ruby_subshell_def( + unique int id: @ruby_subshell, + int loc: @location ref +); + +@ruby_superclass_child_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_superclass_def( + unique int id: @ruby_superclass, + int child: @ruby_superclass_child_type ref, + int loc: @location ref +); + +#keyset[ruby_symbol_array, index] +ruby_symbol_array_child( + int ruby_symbol_array: @ruby_symbol_array ref, + int index: int ref, + unique int child: @ruby_bare_symbol ref +); + +ruby_symbol_array_def( + unique int id: @ruby_symbol_array, + int loc: @location ref +); + +@ruby_then_child_type = @ruby_token_empty_statement | @ruby_underscore_statement + +#keyset[ruby_then, index] +ruby_then_child( + int ruby_then: @ruby_then ref, + int index: int ref, + unique int child: @ruby_then_child_type ref +); + +ruby_then_def( + unique int id: @ruby_then, + int loc: @location ref +); + +@ruby_unary_operand_type = @ruby_break | @ruby_call | @ruby_next | @ruby_parenthesized_statements | @ruby_return | @ruby_token_float | @ruby_token_integer | @ruby_underscore_arg | @ruby_yield + +case @ruby_unary.operator of + 0 = @ruby_unary_bang +| 1 = @ruby_unary_plus +| 2 = @ruby_unary_minus +| 3 = @ruby_unary_definedquestion +| 4 = @ruby_unary_not +| 5 = @ruby_unary_tilde +; + + +ruby_unary_def( + unique int id: @ruby_unary, + int operand: @ruby_unary_operand_type ref, + int operator: int ref, + int loc: @location ref +); + +#keyset[ruby_undef, index] +ruby_undef_child( + int ruby_undef: @ruby_undef ref, + int index: int ref, + unique int child: @ruby_underscore_method_name ref +); + +ruby_undef_def( + unique int id: @ruby_undef, + int loc: @location ref +); + +@ruby_unless_alternative_type = @ruby_else | @ruby_elsif + +ruby_unless_alternative( + unique int ruby_unless: @ruby_unless ref, + unique int alternative: @ruby_unless_alternative_type ref +); + +ruby_unless_consequence( + unique int ruby_unless: @ruby_unless ref, + unique int consequence: @ruby_then ref +); + +ruby_unless_def( + unique int id: @ruby_unless, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_unless_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_unless_modifier_def( + unique int id: @ruby_unless_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_unless_modifier_condition_type ref, + int loc: @location ref +); + +ruby_until_def( + unique int id: @ruby_until, + int body: @ruby_do ref, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_until_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_until_modifier_def( + unique int id: @ruby_until_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_until_modifier_condition_type ref, + int loc: @location ref +); + +ruby_when_body( + unique int ruby_when: @ruby_when ref, + unique int body: @ruby_then ref +); + +#keyset[ruby_when, index] +ruby_when_pattern( + int ruby_when: @ruby_when ref, + int index: int ref, + unique int pattern: @ruby_pattern ref +); + +ruby_when_def( + unique int id: @ruby_when, + int loc: @location ref +); + +ruby_while_def( + unique int id: @ruby_while, + int body: @ruby_do ref, + int condition: @ruby_underscore_statement ref, + int loc: @location ref +); + +@ruby_while_modifier_condition_type = @ruby_break | @ruby_call | @ruby_next | @ruby_return | @ruby_underscore_arg | @ruby_yield + +ruby_while_modifier_def( + unique int id: @ruby_while_modifier, + int body: @ruby_underscore_statement ref, + int condition: @ruby_while_modifier_condition_type ref, + int loc: @location ref +); + +ruby_yield_child( + unique int ruby_yield: @ruby_yield ref, + unique int child: @ruby_argument_list ref +); + +ruby_yield_def( + unique int id: @ruby_yield, + int loc: @location ref +); + +ruby_tokeninfo( + unique int id: @ruby_token, + int kind: int ref, + string value: string ref, + int loc: @location ref +); + +case @ruby_token.kind of + 0 = @ruby_reserved_word +| 1 = @ruby_token_character +| 2 = @ruby_token_class_variable +| 3 = @ruby_token_comment +| 4 = @ruby_token_complex +| 5 = @ruby_token_constant +| 6 = @ruby_token_empty_statement +| 7 = @ruby_token_escape_sequence +| 8 = @ruby_token_false +| 9 = @ruby_token_float +| 10 = @ruby_token_global_variable +| 11 = @ruby_token_hash_key_symbol +| 12 = @ruby_token_heredoc_beginning +| 13 = @ruby_token_heredoc_content +| 14 = @ruby_token_heredoc_end +| 15 = @ruby_token_identifier +| 16 = @ruby_token_instance_variable +| 17 = @ruby_token_integer +| 18 = @ruby_token_nil +| 19 = @ruby_token_operator +| 20 = @ruby_token_self +| 21 = @ruby_token_simple_symbol +| 22 = @ruby_token_string_content +| 23 = @ruby_token_super +| 24 = @ruby_token_true +| 25 = @ruby_token_uninterpreted +; + + +@ruby_ast_node = @ruby_alias | @ruby_argument_list | @ruby_array | @ruby_assignment | @ruby_bare_string | @ruby_bare_symbol | @ruby_begin | @ruby_begin_block | @ruby_binary | @ruby_block | @ruby_block_argument | @ruby_block_parameter | @ruby_block_parameters | @ruby_break | @ruby_call | @ruby_case__ | @ruby_chained_string | @ruby_class | @ruby_conditional | @ruby_delimited_symbol | @ruby_destructured_left_assignment | @ruby_destructured_parameter | @ruby_do | @ruby_do_block | @ruby_element_reference | @ruby_else | @ruby_elsif | @ruby_end_block | @ruby_ensure | @ruby_exception_variable | @ruby_exceptions | @ruby_for | @ruby_hash | @ruby_hash_splat_argument | @ruby_hash_splat_parameter | @ruby_heredoc_body | @ruby_if | @ruby_if_modifier | @ruby_in | @ruby_interpolation | @ruby_keyword_parameter | @ruby_lambda | @ruby_lambda_parameters | @ruby_left_assignment_list | @ruby_method | @ruby_method_parameters | @ruby_module | @ruby_next | @ruby_operator_assignment | @ruby_optional_parameter | @ruby_pair | @ruby_parenthesized_statements | @ruby_pattern | @ruby_program | @ruby_range | @ruby_rational | @ruby_redo | @ruby_regex | @ruby_rescue | @ruby_rescue_modifier | @ruby_rest_assignment | @ruby_retry | @ruby_return | @ruby_right_assignment_list | @ruby_scope_resolution | @ruby_setter | @ruby_singleton_class | @ruby_singleton_method | @ruby_splat_argument | @ruby_splat_parameter | @ruby_string__ | @ruby_string_array | @ruby_subshell | @ruby_superclass | @ruby_symbol_array | @ruby_then | @ruby_token | @ruby_unary | @ruby_undef | @ruby_unless | @ruby_unless_modifier | @ruby_until | @ruby_until_modifier | @ruby_when | @ruby_while | @ruby_while_modifier | @ruby_yield + +@ruby_ast_node_parent = @file | @ruby_ast_node + +#keyset[parent, parent_index] +ruby_ast_node_parent( + int child: @ruby_ast_node ref, + int parent: @ruby_ast_node_parent ref, + int parent_index: int ref +); + +erb_comment_directive_def( + unique int id: @erb_comment_directive, + int child: @erb_token_comment ref, + int loc: @location ref +); + +erb_directive_def( + unique int id: @erb_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +erb_graphql_directive_def( + unique int id: @erb_graphql_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +erb_output_directive_def( + unique int id: @erb_output_directive, + int child: @erb_token_code ref, + int loc: @location ref +); + +@erb_template_child_type = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_token_content + +#keyset[erb_template, index] +erb_template_child( + int erb_template: @erb_template ref, + int index: int ref, + unique int child: @erb_template_child_type ref +); + +erb_template_def( + unique int id: @erb_template, + int loc: @location ref +); + +erb_tokeninfo( + unique int id: @erb_token, + int kind: int ref, + string value: string ref, + int loc: @location ref +); + +case @erb_token.kind of + 0 = @erb_reserved_word +| 1 = @erb_token_code +| 2 = @erb_token_comment +| 3 = @erb_token_content +; + + +@erb_ast_node = @erb_comment_directive | @erb_directive | @erb_graphql_directive | @erb_output_directive | @erb_template | @erb_token + +@erb_ast_node_parent = @erb_ast_node | @file + +#keyset[parent, parent_index] +erb_ast_node_parent( + int child: @erb_ast_node ref, + int parent: @erb_ast_node_parent ref, + int parent_index: int ref +); + diff --git a/ruby/ql/lib/upgrades/b5aef9c93ae64f848017d2dcb760eed916ab0cdd/upgrade.properties b/ruby/ql/lib/upgrades/b5aef9c93ae64f848017d2dcb760eed916ab0cdd/upgrade.properties new file mode 100644 index 000000000000..b3c3c7ccf638 --- /dev/null +++ b/ruby/ql/lib/upgrades/b5aef9c93ae64f848017d2dcb760eed916ab0cdd/upgrade.properties @@ -0,0 +1,4 @@ +description: Removed unused columns from the `*_tokeninfo` relations +compatibility: full +ruby_tokeninfo.rel: reorder ruby_tokeninfo.rel (int id, int kind, int file, int idx, string value, int loc) id kind value loc +erb_tokeninfo.rel: reorder erb_tokeninfo.rel (int id, int kind, int file, int idx, string value, int loc) id kind value loc diff --git a/ruby/ql/lib/upgrades/initial/ruby.dbscheme b/ruby/ql/lib/upgrades/initial/ruby.dbscheme new file mode 100644 index 000000000000..8725deeb2fa6 --- /dev/null +++ b/ruby/ql/lib/upgrades/initial/ruby.dbscheme @@ -0,0 +1,1250 @@ +// CodeQL database schema for Ruby +// 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 +); + +@sourceline = @file + +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref +); + +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref +); + +folders( + unique int id: @folder, + string name: string ref, + string simple: string ref +); + +@container = @file | @folder + +containerparent( + int parent: @container ref, + unique int child: @container ref +); + +sourceLocationPrefix( + string prefix: string ref +); + +@underscore_arg = @assignment | @binary | @conditional | @operator_assignment | @range | @unary | @underscore_primary + +@underscore_lhs = @call | @element_reference | @scope_resolution | @token_false | @token_nil | @token_true | @underscore_variable + +@underscore_method_name = @delimited_symbol | @setter | @token_class_variable | @token_constant | @token_global_variable | @token_identifier | @token_instance_variable | @token_operator | @token_simple_symbol + +@underscore_primary = @array | @begin | @break | @case__ | @chained_string | @class | @delimited_symbol | @for | @hash | @if | @lambda | @method | @module | @next | @parenthesized_statements | @rational | @redo | @regex | @retry | @return | @singleton_class | @singleton_method | @string__ | @string_array | @subshell | @symbol_array | @token_character | @token_complex | @token_float | @token_heredoc_beginning | @token_integer | @token_simple_symbol | @unary | @underscore_lhs | @unless | @until | @while | @yield + +@underscore_statement = @alias | @assignment | @begin_block | @binary | @break | @call | @end_block | @if_modifier | @next | @operator_assignment | @rescue_modifier | @return | @unary | @undef | @underscore_arg | @unless_modifier | @until_modifier | @while_modifier | @yield + +@underscore_variable = @token_class_variable | @token_constant | @token_global_variable | @token_identifier | @token_instance_variable | @token_self | @token_super + +alias_def( + unique int id: @alias, + int alias: @underscore_method_name ref, + int name: @underscore_method_name ref, + int loc: @location ref +); + +@argument_list_child_type = @block_argument | @break | @call | @hash_splat_argument | @next | @pair | @return | @splat_argument | @underscore_arg | @yield + +#keyset[argument_list, index] +argument_list_child( + int argument_list: @argument_list ref, + int index: int ref, + unique int child: @argument_list_child_type ref +); + +argument_list_def( + unique int id: @argument_list, + int loc: @location ref +); + +@array_child_type = @block_argument | @break | @call | @hash_splat_argument | @next | @pair | @return | @splat_argument | @underscore_arg | @yield + +#keyset[array, index] +array_child( + int array: @array ref, + int index: int ref, + unique int child: @array_child_type ref +); + +array_def( + unique int id: @array, + int loc: @location ref +); + +@assignment_left_type = @left_assignment_list | @underscore_lhs + +@assignment_right_type = @break | @call | @next | @return | @right_assignment_list | @splat_argument | @underscore_arg | @yield + +assignment_def( + unique int id: @assignment, + int left: @assignment_left_type ref, + int right: @assignment_right_type ref, + int loc: @location ref +); + +@bare_string_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[bare_string, index] +bare_string_child( + int bare_string: @bare_string ref, + int index: int ref, + unique int child: @bare_string_child_type ref +); + +bare_string_def( + unique int id: @bare_string, + int loc: @location ref +); + +@bare_symbol_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[bare_symbol, index] +bare_symbol_child( + int bare_symbol: @bare_symbol ref, + int index: int ref, + unique int child: @bare_symbol_child_type ref +); + +bare_symbol_def( + unique int id: @bare_symbol, + int loc: @location ref +); + +@begin_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[begin, index] +begin_child( + int begin: @begin ref, + int index: int ref, + unique int child: @begin_child_type ref +); + +begin_def( + unique int id: @begin, + int loc: @location ref +); + +@begin_block_child_type = @token_empty_statement | @underscore_statement + +#keyset[begin_block, index] +begin_block_child( + int begin_block: @begin_block ref, + int index: int ref, + unique int child: @begin_block_child_type ref +); + +begin_block_def( + unique int id: @begin_block, + int loc: @location ref +); + +@binary_left_type = @break | @call | @next | @return | @underscore_arg | @yield + +case @binary.operator of + 0 = @binary_bangequal +| 1 = @binary_bangtilde +| 2 = @binary_percent +| 3 = @binary_ampersand +| 4 = @binary_ampersandampersand +| 5 = @binary_star +| 6 = @binary_starstar +| 7 = @binary_plus +| 8 = @binary_minus +| 9 = @binary_slash +| 10 = @binary_langle +| 11 = @binary_langlelangle +| 12 = @binary_langleequal +| 13 = @binary_langleequalrangle +| 14 = @binary_equalequal +| 15 = @binary_equalequalequal +| 16 = @binary_equaltilde +| 17 = @binary_rangle +| 18 = @binary_rangleequal +| 19 = @binary_ranglerangle +| 20 = @binary_caret +| 21 = @binary_and +| 22 = @binary_or +| 23 = @binary_pipe +| 24 = @binary_pipepipe +; + + +@binary_right_type = @break | @call | @next | @return | @underscore_arg | @yield + +binary_def( + unique int id: @binary, + int left: @binary_left_type ref, + int operator: int ref, + int right: @binary_right_type ref, + int loc: @location ref +); + +block_parameters( + unique int block: @block ref, + unique int parameters: @block_parameters ref +); + +@block_child_type = @token_empty_statement | @underscore_statement + +#keyset[block, index] +block_child( + int block: @block ref, + int index: int ref, + unique int child: @block_child_type ref +); + +block_def( + unique int id: @block, + int loc: @location ref +); + +block_argument_def( + unique int id: @block_argument, + int child: @underscore_arg ref, + int loc: @location ref +); + +block_parameter_def( + unique int id: @block_parameter, + int name: @token_identifier ref, + int loc: @location ref +); + +@block_parameters_child_type = @block_parameter | @destructured_parameter | @hash_splat_parameter | @keyword_parameter | @optional_parameter | @splat_parameter | @token_identifier + +#keyset[block_parameters, index] +block_parameters_child( + int block_parameters: @block_parameters ref, + int index: int ref, + unique int child: @block_parameters_child_type ref +); + +block_parameters_def( + unique int id: @block_parameters, + int loc: @location ref +); + +break_child( + unique int break: @break ref, + unique int child: @argument_list ref +); + +break_def( + unique int id: @break, + int loc: @location ref +); + +call_arguments( + unique int call: @call ref, + unique int arguments: @argument_list ref +); + +@call_block_type = @block | @do_block + +call_block( + unique int call: @call ref, + unique int block: @call_block_type ref +); + +@call_method_type = @argument_list | @scope_resolution | @token_operator | @underscore_variable + +@call_receiver_type = @call | @underscore_primary + +call_receiver( + unique int call: @call ref, + unique int receiver: @call_receiver_type ref +); + +call_def( + unique int id: @call, + int method: @call_method_type ref, + int loc: @location ref +); + +case_value( + unique int case__: @case__ ref, + unique int value: @underscore_statement ref +); + +@case_child_type = @else | @when + +#keyset[case__, index] +case_child( + int case__: @case__ ref, + int index: int ref, + unique int child: @case_child_type ref +); + +case_def( + unique int id: @case__, + int loc: @location ref +); + +#keyset[chained_string, index] +chained_string_child( + int chained_string: @chained_string ref, + int index: int ref, + unique int child: @string__ ref +); + +chained_string_def( + unique int id: @chained_string, + int loc: @location ref +); + +@class_name_type = @scope_resolution | @token_constant + +class_superclass( + unique int class: @class ref, + unique int superclass: @superclass ref +); + +@class_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[class, index] +class_child( + int class: @class ref, + int index: int ref, + unique int child: @class_child_type ref +); + +class_def( + unique int id: @class, + int name: @class_name_type ref, + int loc: @location ref +); + +conditional_def( + unique int id: @conditional, + int alternative: @underscore_arg ref, + int condition: @underscore_arg ref, + int consequence: @underscore_arg ref, + int loc: @location ref +); + +@delimited_symbol_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[delimited_symbol, index] +delimited_symbol_child( + int delimited_symbol: @delimited_symbol ref, + int index: int ref, + unique int child: @delimited_symbol_child_type ref +); + +delimited_symbol_def( + unique int id: @delimited_symbol, + int loc: @location ref +); + +@destructured_left_assignment_child_type = @destructured_left_assignment | @rest_assignment | @underscore_lhs + +#keyset[destructured_left_assignment, index] +destructured_left_assignment_child( + int destructured_left_assignment: @destructured_left_assignment ref, + int index: int ref, + unique int child: @destructured_left_assignment_child_type ref +); + +destructured_left_assignment_def( + unique int id: @destructured_left_assignment, + int loc: @location ref +); + +@destructured_parameter_child_type = @block_parameter | @destructured_parameter | @hash_splat_parameter | @keyword_parameter | @optional_parameter | @splat_parameter | @token_identifier + +#keyset[destructured_parameter, index] +destructured_parameter_child( + int destructured_parameter: @destructured_parameter ref, + int index: int ref, + unique int child: @destructured_parameter_child_type ref +); + +destructured_parameter_def( + unique int id: @destructured_parameter, + int loc: @location ref +); + +@do_child_type = @token_empty_statement | @underscore_statement + +#keyset[do, index] +do_child( + int do: @do ref, + int index: int ref, + unique int child: @do_child_type ref +); + +do_def( + unique int id: @do, + int loc: @location ref +); + +do_block_parameters( + unique int do_block: @do_block ref, + unique int parameters: @block_parameters ref +); + +@do_block_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[do_block, index] +do_block_child( + int do_block: @do_block ref, + int index: int ref, + unique int child: @do_block_child_type ref +); + +do_block_def( + unique int id: @do_block, + int loc: @location ref +); + +@element_reference_child_type = @block_argument | @break | @call | @hash_splat_argument | @next | @pair | @return | @splat_argument | @underscore_arg | @yield + +#keyset[element_reference, index] +element_reference_child( + int element_reference: @element_reference ref, + int index: int ref, + unique int child: @element_reference_child_type ref +); + +element_reference_def( + unique int id: @element_reference, + int object: @underscore_primary ref, + int loc: @location ref +); + +@else_child_type = @token_empty_statement | @underscore_statement + +#keyset[else, index] +else_child( + int else: @else ref, + int index: int ref, + unique int child: @else_child_type ref +); + +else_def( + unique int id: @else, + int loc: @location ref +); + +@elsif_alternative_type = @else | @elsif + +elsif_alternative( + unique int elsif: @elsif ref, + unique int alternative: @elsif_alternative_type ref +); + +elsif_consequence( + unique int elsif: @elsif ref, + unique int consequence: @then ref +); + +elsif_def( + unique int id: @elsif, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@end_block_child_type = @token_empty_statement | @underscore_statement + +#keyset[end_block, index] +end_block_child( + int end_block: @end_block ref, + int index: int ref, + unique int child: @end_block_child_type ref +); + +end_block_def( + unique int id: @end_block, + int loc: @location ref +); + +@ensure_child_type = @token_empty_statement | @underscore_statement + +#keyset[ensure, index] +ensure_child( + int ensure: @ensure ref, + int index: int ref, + unique int child: @ensure_child_type ref +); + +ensure_def( + unique int id: @ensure, + int loc: @location ref +); + +exception_variable_def( + unique int id: @exception_variable, + int child: @underscore_lhs ref, + int loc: @location ref +); + +@exceptions_child_type = @splat_argument | @underscore_arg + +#keyset[exceptions, index] +exceptions_child( + int exceptions: @exceptions ref, + int index: int ref, + unique int child: @exceptions_child_type ref +); + +exceptions_def( + unique int id: @exceptions, + int loc: @location ref +); + +@for_pattern_type = @left_assignment_list | @underscore_lhs + +for_def( + unique int id: @for, + int body: @do ref, + int pattern: @for_pattern_type ref, + int value: @in ref, + int loc: @location ref +); + +@hash_child_type = @hash_splat_argument | @pair + +#keyset[hash, index] +hash_child( + int hash: @hash ref, + int index: int ref, + unique int child: @hash_child_type ref +); + +hash_def( + unique int id: @hash, + int loc: @location ref +); + +hash_splat_argument_def( + unique int id: @hash_splat_argument, + int child: @underscore_arg ref, + int loc: @location ref +); + +hash_splat_parameter_name( + unique int hash_splat_parameter: @hash_splat_parameter ref, + unique int name: @token_identifier ref +); + +hash_splat_parameter_def( + unique int id: @hash_splat_parameter, + int loc: @location ref +); + +@heredoc_body_child_type = @interpolation | @token_escape_sequence | @token_heredoc_content | @token_heredoc_end + +#keyset[heredoc_body, index] +heredoc_body_child( + int heredoc_body: @heredoc_body ref, + int index: int ref, + unique int child: @heredoc_body_child_type ref +); + +heredoc_body_def( + unique int id: @heredoc_body, + int loc: @location ref +); + +@if_alternative_type = @else | @elsif + +if_alternative( + unique int if: @if ref, + unique int alternative: @if_alternative_type ref +); + +if_consequence( + unique int if: @if ref, + unique int consequence: @then ref +); + +if_def( + unique int id: @if, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@if_modifier_condition_type = @break | @call | @next | @return | @underscore_arg | @yield + +if_modifier_def( + unique int id: @if_modifier, + int body: @underscore_statement ref, + int condition: @if_modifier_condition_type ref, + int loc: @location ref +); + +in_def( + unique int id: @in, + int child: @underscore_arg ref, + int loc: @location ref +); + +@interpolation_child_type = @token_empty_statement | @underscore_statement + +#keyset[interpolation, index] +interpolation_child( + int interpolation: @interpolation ref, + int index: int ref, + unique int child: @interpolation_child_type ref +); + +interpolation_def( + unique int id: @interpolation, + int loc: @location ref +); + +keyword_parameter_value( + unique int keyword_parameter: @keyword_parameter ref, + unique int value: @underscore_arg ref +); + +keyword_parameter_def( + unique int id: @keyword_parameter, + int name: @token_identifier ref, + int loc: @location ref +); + +@lambda_body_type = @block | @do_block + +lambda_parameters( + unique int lambda: @lambda ref, + unique int parameters: @lambda_parameters ref +); + +lambda_def( + unique int id: @lambda, + int body: @lambda_body_type ref, + int loc: @location ref +); + +@lambda_parameters_child_type = @block_parameter | @destructured_parameter | @hash_splat_parameter | @keyword_parameter | @optional_parameter | @splat_parameter | @token_identifier + +#keyset[lambda_parameters, index] +lambda_parameters_child( + int lambda_parameters: @lambda_parameters ref, + int index: int ref, + unique int child: @lambda_parameters_child_type ref +); + +lambda_parameters_def( + unique int id: @lambda_parameters, + int loc: @location ref +); + +@left_assignment_list_child_type = @destructured_left_assignment | @rest_assignment | @underscore_lhs + +#keyset[left_assignment_list, index] +left_assignment_list_child( + int left_assignment_list: @left_assignment_list ref, + int index: int ref, + unique int child: @left_assignment_list_child_type ref +); + +left_assignment_list_def( + unique int id: @left_assignment_list, + int loc: @location ref +); + +method_parameters( + unique int method: @method ref, + unique int parameters: @method_parameters ref +); + +@method_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[method, index] +method_child( + int method: @method ref, + int index: int ref, + unique int child: @method_child_type ref +); + +method_def( + unique int id: @method, + int name: @underscore_method_name ref, + int loc: @location ref +); + +@method_parameters_child_type = @block_parameter | @destructured_parameter | @hash_splat_parameter | @keyword_parameter | @optional_parameter | @splat_parameter | @token_identifier + +#keyset[method_parameters, index] +method_parameters_child( + int method_parameters: @method_parameters ref, + int index: int ref, + unique int child: @method_parameters_child_type ref +); + +method_parameters_def( + unique int id: @method_parameters, + int loc: @location ref +); + +@module_name_type = @scope_resolution | @token_constant + +@module_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[module, index] +module_child( + int module: @module ref, + int index: int ref, + unique int child: @module_child_type ref +); + +module_def( + unique int id: @module, + int name: @module_name_type ref, + int loc: @location ref +); + +next_child( + unique int next: @next ref, + unique int child: @argument_list ref +); + +next_def( + unique int id: @next, + int loc: @location ref +); + +case @operator_assignment.operator of + 0 = @operator_assignment_percentequal +| 1 = @operator_assignment_ampersandampersandequal +| 2 = @operator_assignment_ampersandequal +| 3 = @operator_assignment_starstarequal +| 4 = @operator_assignment_starequal +| 5 = @operator_assignment_plusequal +| 6 = @operator_assignment_minusequal +| 7 = @operator_assignment_slashequal +| 8 = @operator_assignment_langlelangleequal +| 9 = @operator_assignment_ranglerangleequal +| 10 = @operator_assignment_caretequal +| 11 = @operator_assignment_pipeequal +| 12 = @operator_assignment_pipepipeequal +; + + +@operator_assignment_right_type = @break | @call | @next | @return | @underscore_arg | @yield + +operator_assignment_def( + unique int id: @operator_assignment, + int left: @underscore_lhs ref, + int operator: int ref, + int right: @operator_assignment_right_type ref, + int loc: @location ref +); + +optional_parameter_def( + unique int id: @optional_parameter, + int name: @token_identifier ref, + int value: @underscore_arg ref, + int loc: @location ref +); + +@pair_key_type = @string__ | @token_hash_key_symbol | @underscore_arg + +pair_def( + unique int id: @pair, + int key__: @pair_key_type ref, + int value: @underscore_arg ref, + int loc: @location ref +); + +@parenthesized_statements_child_type = @token_empty_statement | @underscore_statement + +#keyset[parenthesized_statements, index] +parenthesized_statements_child( + int parenthesized_statements: @parenthesized_statements ref, + int index: int ref, + unique int child: @parenthesized_statements_child_type ref +); + +parenthesized_statements_def( + unique int id: @parenthesized_statements, + int loc: @location ref +); + +@pattern_child_type = @splat_argument | @underscore_arg + +pattern_def( + unique int id: @pattern, + int child: @pattern_child_type ref, + int loc: @location ref +); + +@program_child_type = @token_empty_statement | @token_uninterpreted | @underscore_statement + +#keyset[program, index] +program_child( + int program: @program ref, + int index: int ref, + unique int child: @program_child_type ref +); + +program_def( + unique int id: @program, + int loc: @location ref +); + +range_begin( + unique int range: @range ref, + unique int begin: @underscore_arg ref +); + +range_end( + unique int range: @range ref, + unique int end: @underscore_arg ref +); + +case @range.operator of + 0 = @range_dotdot +| 1 = @range_dotdotdot +; + + +range_def( + unique int id: @range, + int operator: int ref, + int loc: @location ref +); + +@rational_child_type = @token_float | @token_integer + +rational_def( + unique int id: @rational, + int child: @rational_child_type ref, + int loc: @location ref +); + +redo_child( + unique int redo: @redo ref, + unique int child: @argument_list ref +); + +redo_def( + unique int id: @redo, + int loc: @location ref +); + +@regex_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[regex, index] +regex_child( + int regex: @regex ref, + int index: int ref, + unique int child: @regex_child_type ref +); + +regex_def( + unique int id: @regex, + int loc: @location ref +); + +rescue_body( + unique int rescue: @rescue ref, + unique int body: @then ref +); + +rescue_exceptions( + unique int rescue: @rescue ref, + unique int exceptions: @exceptions ref +); + +rescue_variable( + unique int rescue: @rescue ref, + unique int variable: @exception_variable ref +); + +rescue_def( + unique int id: @rescue, + int loc: @location ref +); + +@rescue_modifier_handler_type = @break | @call | @next | @return | @underscore_arg | @yield + +rescue_modifier_def( + unique int id: @rescue_modifier, + int body: @underscore_statement ref, + int handler: @rescue_modifier_handler_type ref, + int loc: @location ref +); + +rest_assignment_child( + unique int rest_assignment: @rest_assignment ref, + unique int child: @underscore_lhs ref +); + +rest_assignment_def( + unique int id: @rest_assignment, + int loc: @location ref +); + +retry_child( + unique int retry: @retry ref, + unique int child: @argument_list ref +); + +retry_def( + unique int id: @retry, + int loc: @location ref +); + +return_child( + unique int return: @return ref, + unique int child: @argument_list ref +); + +return_def( + unique int id: @return, + int loc: @location ref +); + +@right_assignment_list_child_type = @splat_argument | @underscore_arg + +#keyset[right_assignment_list, index] +right_assignment_list_child( + int right_assignment_list: @right_assignment_list ref, + int index: int ref, + unique int child: @right_assignment_list_child_type ref +); + +right_assignment_list_def( + unique int id: @right_assignment_list, + int loc: @location ref +); + +@scope_resolution_name_type = @token_constant | @token_identifier + +scope_resolution_scope( + unique int scope_resolution: @scope_resolution ref, + unique int scope: @underscore_primary ref +); + +scope_resolution_def( + unique int id: @scope_resolution, + int name: @scope_resolution_name_type ref, + int loc: @location ref +); + +setter_def( + unique int id: @setter, + int name: @token_identifier ref, + int loc: @location ref +); + +@singleton_class_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[singleton_class, index] +singleton_class_child( + int singleton_class: @singleton_class ref, + int index: int ref, + unique int child: @singleton_class_child_type ref +); + +singleton_class_def( + unique int id: @singleton_class, + int value: @underscore_arg ref, + int loc: @location ref +); + +@singleton_method_object_type = @underscore_arg | @underscore_variable + +singleton_method_parameters( + unique int singleton_method: @singleton_method ref, + unique int parameters: @method_parameters ref +); + +@singleton_method_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement + +#keyset[singleton_method, index] +singleton_method_child( + int singleton_method: @singleton_method ref, + int index: int ref, + unique int child: @singleton_method_child_type ref +); + +singleton_method_def( + unique int id: @singleton_method, + int name: @underscore_method_name ref, + int object: @singleton_method_object_type ref, + int loc: @location ref +); + +splat_argument_def( + unique int id: @splat_argument, + int child: @underscore_arg ref, + int loc: @location ref +); + +splat_parameter_name( + unique int splat_parameter: @splat_parameter ref, + unique int name: @token_identifier ref +); + +splat_parameter_def( + unique int id: @splat_parameter, + int loc: @location ref +); + +@string_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[string__, index] +string_child( + int string__: @string__ ref, + int index: int ref, + unique int child: @string_child_type ref +); + +string_def( + unique int id: @string__, + int loc: @location ref +); + +#keyset[string_array, index] +string_array_child( + int string_array: @string_array ref, + int index: int ref, + unique int child: @bare_string ref +); + +string_array_def( + unique int id: @string_array, + int loc: @location ref +); + +@subshell_child_type = @interpolation | @token_escape_sequence | @token_string_content + +#keyset[subshell, index] +subshell_child( + int subshell: @subshell ref, + int index: int ref, + unique int child: @subshell_child_type ref +); + +subshell_def( + unique int id: @subshell, + int loc: @location ref +); + +@superclass_child_type = @break | @call | @next | @return | @underscore_arg | @yield + +superclass_def( + unique int id: @superclass, + int child: @superclass_child_type ref, + int loc: @location ref +); + +#keyset[symbol_array, index] +symbol_array_child( + int symbol_array: @symbol_array ref, + int index: int ref, + unique int child: @bare_symbol ref +); + +symbol_array_def( + unique int id: @symbol_array, + int loc: @location ref +); + +@then_child_type = @token_empty_statement | @underscore_statement + +#keyset[then, index] +then_child( + int then: @then ref, + int index: int ref, + unique int child: @then_child_type ref +); + +then_def( + unique int id: @then, + int loc: @location ref +); + +@unary_operand_type = @break | @call | @next | @parenthesized_statements | @return | @token_float | @token_integer | @underscore_arg | @yield + +case @unary.operator of + 0 = @unary_bang +| 1 = @unary_plus +| 2 = @unary_minus +| 3 = @unary_definedquestion +| 4 = @unary_not +| 5 = @unary_tilde +; + + +unary_def( + unique int id: @unary, + int operand: @unary_operand_type ref, + int operator: int ref, + int loc: @location ref +); + +#keyset[undef, index] +undef_child( + int undef: @undef ref, + int index: int ref, + unique int child: @underscore_method_name ref +); + +undef_def( + unique int id: @undef, + int loc: @location ref +); + +@unless_alternative_type = @else | @elsif + +unless_alternative( + unique int unless: @unless ref, + unique int alternative: @unless_alternative_type ref +); + +unless_consequence( + unique int unless: @unless ref, + unique int consequence: @then ref +); + +unless_def( + unique int id: @unless, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@unless_modifier_condition_type = @break | @call | @next | @return | @underscore_arg | @yield + +unless_modifier_def( + unique int id: @unless_modifier, + int body: @underscore_statement ref, + int condition: @unless_modifier_condition_type ref, + int loc: @location ref +); + +until_def( + unique int id: @until, + int body: @do ref, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@until_modifier_condition_type = @break | @call | @next | @return | @underscore_arg | @yield + +until_modifier_def( + unique int id: @until_modifier, + int body: @underscore_statement ref, + int condition: @until_modifier_condition_type ref, + int loc: @location ref +); + +when_body( + unique int when: @when ref, + unique int body: @then ref +); + +#keyset[when, index] +when_pattern( + int when: @when ref, + int index: int ref, + unique int pattern: @pattern ref +); + +when_def( + unique int id: @when, + int loc: @location ref +); + +while_def( + unique int id: @while, + int body: @do ref, + int condition: @underscore_statement ref, + int loc: @location ref +); + +@while_modifier_condition_type = @break | @call | @next | @return | @underscore_arg | @yield + +while_modifier_def( + unique int id: @while_modifier, + int body: @underscore_statement ref, + int condition: @while_modifier_condition_type ref, + int loc: @location ref +); + +yield_child( + unique int yield: @yield ref, + unique int child: @argument_list ref +); + +yield_def( + unique int id: @yield, + int loc: @location ref +); + +tokeninfo( + unique int id: @token, + int kind: int ref, + int file: @file ref, + int idx: int ref, + string value: string ref, + int loc: @location ref +); + +case @token.kind of + 0 = @reserved_word +| 1 = @token_character +| 2 = @token_class_variable +| 3 = @token_comment +| 4 = @token_complex +| 5 = @token_constant +| 6 = @token_empty_statement +| 7 = @token_escape_sequence +| 8 = @token_false +| 9 = @token_float +| 10 = @token_global_variable +| 11 = @token_hash_key_symbol +| 12 = @token_heredoc_beginning +| 13 = @token_heredoc_content +| 14 = @token_heredoc_end +| 15 = @token_identifier +| 16 = @token_instance_variable +| 17 = @token_integer +| 18 = @token_nil +| 19 = @token_operator +| 20 = @token_self +| 21 = @token_simple_symbol +| 22 = @token_string_content +| 23 = @token_super +| 24 = @token_true +| 25 = @token_uninterpreted +; + + +@ast_node = @alias | @argument_list | @array | @assignment | @bare_string | @bare_symbol | @begin | @begin_block | @binary | @block | @block_argument | @block_parameter | @block_parameters | @break | @call | @case__ | @chained_string | @class | @conditional | @delimited_symbol | @destructured_left_assignment | @destructured_parameter | @do | @do_block | @element_reference | @else | @elsif | @end_block | @ensure | @exception_variable | @exceptions | @for | @hash | @hash_splat_argument | @hash_splat_parameter | @heredoc_body | @if | @if_modifier | @in | @interpolation | @keyword_parameter | @lambda | @lambda_parameters | @left_assignment_list | @method | @method_parameters | @module | @next | @operator_assignment | @optional_parameter | @pair | @parenthesized_statements | @pattern | @program | @range | @rational | @redo | @regex | @rescue | @rescue_modifier | @rest_assignment | @retry | @return | @right_assignment_list | @scope_resolution | @setter | @singleton_class | @singleton_method | @splat_argument | @splat_parameter | @string__ | @string_array | @subshell | @superclass | @symbol_array | @then | @token | @unary | @undef | @unless | @unless_modifier | @until | @until_modifier | @when | @while | @while_modifier | @yield + +@ast_node_parent = @ast_node | @file + +#keyset[parent, parent_index] +ast_node_parent( + int child: @ast_node ref, + int parent: @ast_node_parent ref, + int parent_index: int ref +); + diff --git a/ruby/ql/src/AlertSuppression.ql b/ruby/ql/src/AlertSuppression.ql new file mode 100644 index 000000000000..0ea3c64d7479 --- /dev/null +++ b/ruby/ql/src/AlertSuppression.ql @@ -0,0 +1,82 @@ +/** + * @name Alert suppression + * @description Generates information about alert suppressions. + * @kind alert-suppression + * @id rb/alert-suppression + */ + +import ruby +import codeql.ruby.ast.internal.TreeSitter + +/** + * An alert suppression comment. + */ +class SuppressionComment extends Ruby::Comment { + string annotation; + + SuppressionComment() { + // suppression comments must be single-line + this.getLocation().getStartLine() = this.getLocation().getEndLine() and + exists(string text | text = commentText(this) | + // match `lgtm[...]` anywhere in the comment + annotation = text.regexpFind("(?i)\\blgtm\\s*\\[[^\\]]*\\]", _, _) + or + // match `lgtm` at the start of the comment and after semicolon + annotation = text.regexpFind("(?i)(?<=^|;)\\s*lgtm(?!\\B|\\s*\\[)", _, _).trim() + ) + } + + /** + * Gets the text of this suppression comment. + */ + string getText() { result = commentText(this) } + + /** Gets the suppression annotation in this comment. */ + string getAnnotation() { result = annotation } + + /** + * Holds if this comment applies to the range from column `startcolumn` of line `startline` + * to column `endcolumn` of line `endline` in file `filepath`. + */ + predicate covers(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + this.getLocation().hasLocationInfo(filepath, startline, _, endline, endcolumn) and + startcolumn = 1 + } + + /** Gets the scope of this suppression. */ + SuppressionScope getScope() { this = result.getSuppressionComment() } +} + +private string commentText(Ruby::Comment comment) { result = comment.getValue().suffix(1) } + +/** + * The scope of an alert suppression comment. + */ +class SuppressionScope extends @ruby_token_comment { + SuppressionScope() { this instanceof SuppressionComment } + + /** Gets a suppression comment with this scope. */ + SuppressionComment getSuppressionComment() { result = this } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.(SuppressionComment).covers(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets a textual representation of this element. */ + string toString() { result = "suppression range" } +} + +from SuppressionComment c +select c, // suppression comment + c.getText(), // text of suppression comment (excluding delimiters) + c.getAnnotation(), // text of suppression annotation + c.getScope() // scope of suppression diff --git a/ruby/ql/src/codeql-suites/ruby-code-scanning.qls b/ruby/ql/src/codeql-suites/ruby-code-scanning.qls new file mode 100644 index 000000000000..cf9f7605b2b2 --- /dev/null +++ b/ruby/ql/src/codeql-suites/ruby-code-scanning.qls @@ -0,0 +1,5 @@ +- description: Standard Code Scanning queries for Ruby +- queries: . +- apply: code-scanning-selectors.yml + from: codeql/suite-helpers + diff --git a/ruby/ql/src/codeql-suites/ruby-lgtm-full.qls b/ruby/ql/src/codeql-suites/ruby-lgtm-full.qls new file mode 100644 index 000000000000..11fe09a96819 --- /dev/null +++ b/ruby/ql/src/codeql-suites/ruby-lgtm-full.qls @@ -0,0 +1,12 @@ +- description: Standard LGTM queries for Ruby, including ones not displayed by default +- queries: . +- apply: lgtm-selectors.yml + from: codeql/suite-helpers +# These are only for IDE use. +- exclude: + tags contain: + - ide-contextual-queries/local-definitions + - ide-contextual-queries/local-references +- include: + id: rb/lines-of-code-in-files + diff --git a/ruby/ql/src/codeql-suites/ruby-lgtm.qls b/ruby/ql/src/codeql-suites/ruby-lgtm.qls new file mode 100644 index 000000000000..d13847da7e0e --- /dev/null +++ b/ruby/ql/src/codeql-suites/ruby-lgtm.qls @@ -0,0 +1,4 @@ +- description: Standard LGTM queries for Ruby +- apply: codeql-suites/ruby-lgtm-full.qls +- apply: lgtm-displayed-only.yml + from: codeql/suite-helpers diff --git a/ruby/ql/src/codeql-suites/ruby-security-and-quality.qls b/ruby/ql/src/codeql-suites/ruby-security-and-quality.qls new file mode 100644 index 000000000000..588a074cb508 --- /dev/null +++ b/ruby/ql/src/codeql-suites/ruby-security-and-quality.qls @@ -0,0 +1,4 @@ +- description: Security-and-quality queries for Ruby +- queries: . +- apply: security-and-quality-selectors.yml + from: codeql/suite-helpers diff --git a/ruby/ql/src/codeql-suites/ruby-security-extended.qls b/ruby/ql/src/codeql-suites/ruby-security-extended.qls new file mode 100644 index 000000000000..250341643eab --- /dev/null +++ b/ruby/ql/src/codeql-suites/ruby-security-extended.qls @@ -0,0 +1,5 @@ +- description: Security-extended queries for Ruby +- queries: . +- apply: security-extended-selectors.yml + from: codeql/suite-helpers + diff --git a/ruby/ql/src/experimental/README.md b/ruby/ql/src/experimental/README.md new file mode 100644 index 000000000000..ed83d8d4ab0e --- /dev/null +++ b/ruby/ql/src/experimental/README.md @@ -0,0 +1 @@ +This directory contains [experimental](../../docs/experimental.md) CodeQL queries and libraries. diff --git a/ruby/ql/src/experimental/performance/UseDetect.ql b/ruby/ql/src/experimental/performance/UseDetect.ql new file mode 100644 index 000000000000..f5fcf6df4fba --- /dev/null +++ b/ruby/ql/src/experimental/performance/UseDetect.ql @@ -0,0 +1,64 @@ +/** + * @name Use detect + * @description Use 'detect' instead of 'select' followed by 'first' or 'last'. + * @kind problem + * @problem.severity warning + * @id rb/use-detect + * @tags performance rubocop + * @precision high + */ + +// This is an implementation of the Rubocop rule +// https://github.com/rubocop/rubocop-performance/blob/master/lib/rubocop/cop/performance/detect.rb +import ruby +import codeql.ruby.dataflow.SSA + +/** A call that extracts the first or last element of a list. */ +class EndCall extends MethodCall { + string detect; + + EndCall() { + detect = "detect" and + ( + this.getMethodName() = "first" and + this.getNumberOfArguments() = 0 + or + this.getNumberOfArguments() = 1 and + this.getArgument(0).(IntegerLiteral).getValueText() = "0" + ) + or + detect = "reverse_detect" and + ( + this.getMethodName() = "last" and + this.getNumberOfArguments() = 0 + or + this.getNumberOfArguments() = 1 and + this.getArgument(0).(UnaryMinusExpr).getOperand().(IntegerLiteral).getValueText() = "1" + ) + } + + string detectCall() { result = detect } +} + +Expr getUniqueRead(Expr e) { + exists(AssignExpr ae | + e = ae.getRightOperand() and + forex(Ssa::WriteDefinition def | def.getWriteAccess() = ae.getLeftOperand() | + strictcount(def.getARead()) = 1 and + not def = any(Ssa::PhiNode phi).getAnInput() and + def.getARead() = result.getAControlFlowNode() + ) + ) +} + +class SelectBlock extends MethodCall { + SelectBlock() { + this.getMethodName() in ["select", "filter", "find_all"] and + exists(this.getBlock()) + } +} + +from EndCall call, SelectBlock selectBlock +where getUniqueRead*(selectBlock) = call.getReceiver() +select call, "Replace this call and $@ with '" + call.detectCall() + "'.", selectBlock, + "'select' call" diff --git a/ruby/ql/src/filters/ClassifyFiles.ql b/ruby/ql/src/filters/ClassifyFiles.ql new file mode 100644 index 000000000000..d194523e09d9 --- /dev/null +++ b/ruby/ql/src/filters/ClassifyFiles.ql @@ -0,0 +1,20 @@ +/** + * @name Classify files + * @description This query produces a list of all files in a database + * that are classified as generated code or test code. + * + * Used by LGTM. + * @kind file-classifier + * @id rb/file-classifier + */ + +import ruby +import codeql.ruby.filters.GeneratedCode + +predicate classify(File f, string category) { + f instanceof GeneratedCodeFile and category = "generated" +} + +from File f, string category +where classify(f, category) +select f, category diff --git a/ruby/ql/src/ide-contextual-queries/localDefinitions.ql b/ruby/ql/src/ide-contextual-queries/localDefinitions.ql new file mode 100644 index 000000000000..81c5e449bb1c --- /dev/null +++ b/ruby/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 ruby/ide-jump-to-definition + * @tags ide-contextual-queries/local-definitions + */ + +import codeql.IDEContextual +import codeql.ruby.AST + +external string selectedSourceFile(); + +from AstNode e, Variable def, string kind +where + e = def.getAnAccess() and + kind = "local variable" and + e.getLocation().getFile() = getFileBySourceArchiveName(selectedSourceFile()) +select e, def, kind diff --git a/ruby/ql/src/ide-contextual-queries/localReferences.ql b/ruby/ql/src/ide-contextual-queries/localReferences.ql new file mode 100644 index 000000000000..713b363e60fd --- /dev/null +++ b/ruby/ql/src/ide-contextual-queries/localReferences.ql @@ -0,0 +1,21 @@ +/** + * @name Find-references links + * @description Generates use-definition pairs that provide the data + * for find-references in the code viewer. + * @kind definitions + * @id ruby/ide-find-references + * @tags ide-contextual-queries/local-references + */ + +import codeql.IDEContextual +import codeql.ruby.AST +import codeql.ruby.ast.Variable + +external string selectedSourceFile(); + +from AstNode e, Variable def, string kind +where + e = def.getAnAccess() and + kind = "local variable" and + def.getLocation().getFile() = getFileBySourceArchiveName(selectedSourceFile()) +select e, def, kind diff --git a/ruby/ql/src/ide-contextual-queries/printAst.ql b/ruby/ql/src/ide-contextual-queries/printAst.ql new file mode 100644 index 000000000000..cd5b9a4a3b27 --- /dev/null +++ b/ruby/ql/src/ide-contextual-queries/printAst.ql @@ -0,0 +1,27 @@ +/** + * @name Print AST + * @description Produces a representation of a file's Abstract Syntax Tree. + * This query is used by the VS Code extension. + * @id ruby/print-ast + * @kind graph + * @tags ide-contextual-queries/print-ast + */ + +private import codeql.IDEContextual +private import codeql.ruby.AST +private import codeql.ruby.printAst + +/** + * 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/ruby/ql/src/qlpack.lock.yml b/ruby/ql/src/qlpack.lock.yml new file mode 100644 index 000000000000..0bef0f691a9d --- /dev/null +++ b/ruby/ql/src/qlpack.lock.yml @@ -0,0 +1,6 @@ +--- +dependencies: + codeql/suite-helpers: + version: 0.0.2 +compiled: false +lockVersion: 1.0.0 diff --git a/ruby/ql/src/qlpack.yml b/ruby/ql/src/qlpack.yml new file mode 100644 index 000000000000..db2a2ebf5526 --- /dev/null +++ b/ruby/ql/src/qlpack.yml @@ -0,0 +1,6 @@ +name: codeql/ruby-queries +version: 0.0.2 +suites: codeql-suites +dependencies: + codeql/ruby-all: ^0.0.2 + codeql/suite-helpers: ^0.0.2 diff --git a/ruby/ql/src/queries.xml b/ruby/ql/src/queries.xml new file mode 100644 index 000000000000..a7ce9735ef42 --- /dev/null +++ b/ruby/ql/src/queries.xml @@ -0,0 +1 @@ + diff --git a/ruby/ql/src/queries/analysis/Definitions.ql b/ruby/ql/src/queries/analysis/Definitions.ql new file mode 100644 index 000000000000..ebdb02f7dd7e --- /dev/null +++ b/ruby/ql/src/queries/analysis/Definitions.ql @@ -0,0 +1,107 @@ +/** + * @name Definitions + * @description Jump to definition helper query. + * @kind definitions + * @id rb/jump-to-definition + */ + +/* + * TODO: + * - should `Foo.new` point to `Foo#initialize`? + */ + +import ruby +import codeql.ruby.ast.internal.Module +import codeql.ruby.dataflow.SSA + +from DefLoc loc, Expr src, Expr target, string kind +where + ConstantDefLoc(src, target) = loc and kind = "constant" + or + MethodLoc(src, target) = loc and kind = "method" + or + LocalVariableLoc(src, target) = loc and kind = "variable" + or + InstanceVariableLoc(src, target) = loc and kind = "instance variable" + or + ClassVariableLoc(src, target) = loc and kind = "class variable" +select src, target, kind + +/** + * Definition location info for different identifiers. + * Each branch holds two values that are subclasses of `Expr`. + * The first is the "source" - some usage of an identifier. + * The second is the "target" - the definition of that identifier. + */ +newtype DefLoc = + /** A constant, module or class. */ + ConstantDefLoc(ConstantReadAccess read, ConstantWriteAccess write) { write = definitionOf(read) } or + /** A method call. */ + MethodLoc(MethodCall call, Method meth) { meth = call.getATarget() } or + /** A local variable. */ + LocalVariableLoc(VariableReadAccess read, VariableWriteAccess write) { + exists(Ssa::WriteDefinition w | + write = w.getWriteAccess() and + read = w.getARead().getExpr() and + not read.isSynthesized() + ) + } or + /** An instance variable */ + InstanceVariableLoc(InstanceVariableReadAccess read, InstanceVariableWriteAccess write) { + /* + * We consider instance variables to be "defined" in the initialize method of their enclosing class. + * If that method doesn't exist, we won't provide any jump-to-def information for the instance variable. + */ + + exists(Method m | + m.getAChild+() = write and + m.getName() = "initialize" and + write.getVariable() = read.getVariable() + ) + } or + /** A class variable */ + ClassVariableLoc(ClassVariableReadAccess read, ClassVariableWriteAccess write) { + read.getVariable() = write.getVariable() and + not exists(MethodBase m | m.getAChild+() = write) + } + +/** + * Gets the fully qualified name for a constant, based on the context in which it is defined. + * + * For example, given + * ```ruby + * module Foo + * module Bar + * class Baz + * end + * end + * end + * ``` + * + * the constant `Baz` has the fully qualified name `Foo::Bar::Baz`. + */ +string constantQualifiedName(ConstantWriteAccess w) { + /* get the qualified name for the parent module, then append w */ + exists(ConstantWriteAccess parent | parent = w.getEnclosingModule() | + result = constantQualifiedName(parent) + "::" + w.getName() + ) + or + /* base case - there's no parent module */ + not exists(ConstantWriteAccess parent | parent = w.getEnclosingModule()) and + result = w.getName() +} + +/** + * Gets the constant write that defines the given constant. + * Modules often don't have a unique definition, as they are opened multiple times in different + * files. In these cases we arbitrarily pick the definition with the lexicographically least + * location. + */ +ConstantWriteAccess definitionOf(ConstantReadAccess r) { + result = + min(ConstantWriteAccess w | + constantQualifiedName(w) = resolveConstant(r) + | + w order by w.getLocation().toString() + ) +} diff --git a/ruby/ql/src/queries/diagnostics/ExtractionErrors.ql b/ruby/ql/src/queries/diagnostics/ExtractionErrors.ql new file mode 100644 index 000000000000..5c55d9843377 --- /dev/null +++ b/ruby/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 rb/diagnostics/extraction-errors + */ + +import ruby +import codeql.ruby.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/ruby/ql/src/queries/diagnostics/SuccessfullyExtractedFiles.ql b/ruby/ql/src/queries/diagnostics/SuccessfullyExtractedFiles.ql new file mode 100644 index 000000000000..74f95763d8a5 --- /dev/null +++ b/ruby/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 rb/diagnostics/successfully-extracted-files + */ + +import ruby +import codeql.ruby.Diagnostics + +from File f +where + not exists(ExtractionError e | e.getLocation().getFile() = f) and + exists(f.getRelativePath()) +select f, "" diff --git a/ruby/ql/src/queries/metrics/FLines.ql b/ruby/ql/src/queries/metrics/FLines.ql new file mode 100644 index 000000000000..97c319fbf73a --- /dev/null +++ b/ruby/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 rb/lines-per-file + */ + +import ruby + +from RubyFile f, int n +where n = f.getNumberOfLines() +select f, n order by n desc diff --git a/ruby/ql/src/queries/metrics/FLinesOfCode.ql b/ruby/ql/src/queries/metrics/FLinesOfCode.ql new file mode 100644 index 000000000000..0c1d15960ccc --- /dev/null +++ b/ruby/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 rb/lines-of-code-in-files + */ + +import ruby + +from RubyFile f, int n +where n = f.getNumberOfLinesOfCode() +select f, n order by n desc diff --git a/ruby/ql/src/queries/metrics/FLinesOfComments.ql b/ruby/ql/src/queries/metrics/FLinesOfComments.ql new file mode 100644 index 000000000000..8af882f13d18 --- /dev/null +++ b/ruby/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 rb/lines-of-comments-in-files + */ + +import ruby + +from RubyFile f, int n +where n = f.getNumberOfLinesOfComments() +select f, n order by n desc diff --git a/ruby/ql/src/queries/security/cwe-078/CommandInjection.qhelp b/ruby/ql/src/queries/security/cwe-078/CommandInjection.qhelp new file mode 100644 index 000000000000..d01465d1dae5 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-078/CommandInjection.qhelp @@ -0,0 +1,44 @@ + + + +

Code that passes user input directly to +Kernel.system, Kernel.exec, or some other library +routine that executes a command, allows the user to execute malicious +code.

+ +
+ + +

If possible, use hard-coded string literals to specify the command to run +or library to load. Instead of passing the user input directly to the +process or library function, examine the user input and then choose +among hard-coded string literals.

+ +

If the applicable libraries or commands cannot be determined at +compile time, then add code to verify that the user input string is +safe before using it.

+ +
+ + +

The following example shows code that takes a shell script that can be changed +maliciously by a user, and passes it straight to Kernel.system +without examining it first.

+ + + +
+ + +
  • +OWASP: +Command Injection. +
  • + + + +
    +
    diff --git a/ruby/ql/src/queries/security/cwe-078/CommandInjection.ql b/ruby/ql/src/queries/security/cwe-078/CommandInjection.ql new file mode 100644 index 000000000000..4c2dda966b98 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-078/CommandInjection.ql @@ -0,0 +1,25 @@ +/** + * @name Uncontrolled command line + * @description Using externally controlled strings in a command line may allow a malicious + * user to change the meaning of the command. + * @kind path-problem + * @problem.severity error + * @security-severity 9.8 + * @precision high + * @id rb/command-line-injection + * @tags correctness + * security + * external/cwe/cwe-078 + * external/cwe/cwe-088 + */ + +import ruby +import codeql.ruby.security.CommandInjectionQuery +import DataFlow::PathGraph + +from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Source sourceNode +where + config.hasFlowPath(source, sink) and + sourceNode = source.getNode() +select sink.getNode(), source, sink, "This command depends on $@.", sourceNode, + sourceNode.getSourceType() diff --git a/ruby/ql/src/queries/security/cwe-078/examples/command_injection.rb b/ruby/ql/src/queries/security/cwe-078/examples/command_injection.rb new file mode 100644 index 000000000000..e5d433634646 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-078/examples/command_injection.rb @@ -0,0 +1,6 @@ +class UsersController < ActionController::Base + def create + command = params[:command] + system(command) # BAD + end +end diff --git a/ruby/ql/src/queries/security/cwe-079/ReflectedXSS.qhelp b/ruby/ql/src/queries/security/cwe-079/ReflectedXSS.qhelp new file mode 100644 index 000000000000..6a33f8b3e065 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-079/ReflectedXSS.qhelp @@ -0,0 +1,55 @@ + + + + +

    + Directly writing user input (for example, an HTTP request parameter) to a webpage, + without properly sanitizing the input first, allows for a cross-site scripting + vulnerability. +

    +
    + + +

    + To guard against cross-site scripting, escape user input before writing it + to the page. Some frameworks, such as Rails, perform this escaping + implicitly and by default. +

    + +

    + Take care when using methods such as html_safe or + raw. They can be used to emit a string without escaping + it, and should only be used when the string has already been manually + escaped (for example, with the Rails html_escape method), or when + the content is otherwise guaranteed to be safe (such as a hard-coded string). +

    +
    + + +

    + The following example is safe because the + params[:user_name] content within the output tags will be + HTML-escaped automatically before being emitted. +

    + + +

    + However, the following example is unsafe because user-controlled input is + emitted without escaping, since it is marked as html_safe. +

    + +
    + + +
  • + OWASP: + XSS + Ruby on Rails Cheatsheet. +
  • +
  • + Wikipedia: Cross-site scripting. +
  • +
    +
    diff --git a/ruby/ql/src/queries/security/cwe-079/ReflectedXSS.ql b/ruby/ql/src/queries/security/cwe-079/ReflectedXSS.ql new file mode 100644 index 000000000000..2061ae8ec0c5 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-079/ReflectedXSS.ql @@ -0,0 +1,23 @@ +/** + * @name Reflected server-side cross-site scripting + * @description Writing user input directly to a web page + * allows for a cross-site scripting vulnerability. + * @kind path-problem + * @problem.severity error + * @sub-severity high + * @precision high + * @id rb/reflected-xss + * @tags security + * external/cwe/cwe-079 + * external/cwe/cwe-116 + */ + +import ruby +import codeql.ruby.security.ReflectedXSSQuery +import codeql.ruby.DataFlow +import DataFlow::PathGraph + +from ReflectedXSS::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink +where config.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@.", + source.getNode(), "a user-provided value" diff --git a/ruby/ql/src/queries/security/cwe-079/examples/reflective_xss.html.erb b/ruby/ql/src/queries/security/cwe-079/examples/reflective_xss.html.erb new file mode 100644 index 000000000000..b1501af1f0d3 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-079/examples/reflective_xss.html.erb @@ -0,0 +1 @@ +

    Hello <%= params[:user_name].html_safe %>!

    diff --git a/ruby/ql/src/queries/security/cwe-079/examples/safe.html.erb b/ruby/ql/src/queries/security/cwe-079/examples/safe.html.erb new file mode 100644 index 000000000000..5e247b25b7eb --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-079/examples/safe.html.erb @@ -0,0 +1 @@ +

    Hello <%= params[:user_name] %>!

    diff --git a/ruby/ql/src/queries/security/cwe-089/SqlInjection.qhelp b/ruby/ql/src/queries/security/cwe-089/SqlInjection.qhelp new file mode 100644 index 000000000000..b477a49ca690 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-089/SqlInjection.qhelp @@ -0,0 +1,64 @@ + + + + +

    +If a database query (such as a SQL or NoSQL query) is built from +user-provided data without sufficient sanitization, a malicious user +may be able to run malicious database queries. +

    +
    + + +

    +Most database connector libraries offer a way of safely embedding +untrusted data into a query by means of query parameters or +prepared statements. +

    +
    + + +

    +In the following Rails example, an ActionController class +has a text_bio method to handle requests to fetch a biography +for a specified user. +

    + +

    +The user is specified using a parameter, user_name provided by +the client. This value is accessible using the params method. +

    + +

    +The method illustrates three different ways to construct and execute an SQL +query to find the user by name. +

    + +

    +In the first case, the parameter user_name is inserted into an +SQL fragment using string interpolation. The parameter is user-supplied and +is not sanitized. An attacker could use this to construct SQL queries that +were not intended to be executed here. +

    + +

    +The second case uses string concatenation and is vulnerable in the same way +that the first case is. +

    + +

    +In the third case, the name is passed in a hash instead. +ActiveRecord will construct a parameterized SQL query that is not +vulnerable to SQL injection attacks. +

    + + +
    + + +
  • Wikipedia: SQL injection.
  • +
  • OWASP: SQL Injection Prevention Cheat Sheet.
  • +
    +
    diff --git a/ruby/ql/src/queries/security/cwe-089/SqlInjection.ql b/ruby/ql/src/queries/security/cwe-089/SqlInjection.ql new file mode 100644 index 000000000000..de795e34e712 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-089/SqlInjection.ql @@ -0,0 +1,39 @@ +/** + * @name SQL query built from user-controlled sources + * @description Building a SQL query from user-controlled sources is vulnerable to insertion of + * malicious SQL code by the user. + * @kind path-problem + * @problem.severity error + * @security-severity 8.8 + * @precision high + * @id rb/sql-injection + * @tags security + * external/cwe/cwe-089 + * external/owasp/owasp-a1 + */ + +import ruby +import codeql.ruby.Concepts +import codeql.ruby.DataFlow +import codeql.ruby.dataflow.BarrierGuards +import codeql.ruby.dataflow.RemoteFlowSources +import codeql.ruby.TaintTracking +import DataFlow::PathGraph + +class SQLInjectionConfiguration extends TaintTracking::Configuration { + SQLInjectionConfiguration() { this = "SQLInjectionConfiguration" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { sink instanceof SqlExecution } + + override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { + guard instanceof StringConstCompare or + guard instanceof StringConstArrayInclusionCall + } +} + +from SQLInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink +where config.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "This SQL query depends on $@.", source.getNode(), + "a user-provided value" diff --git a/ruby/ql/src/queries/security/cwe-089/examples/SqlInjection.rb b/ruby/ql/src/queries/security/cwe-089/examples/SqlInjection.rb new file mode 100644 index 000000000000..24d6b2ad59b6 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-089/examples/SqlInjection.rb @@ -0,0 +1,15 @@ +class UserController < ActionController::Base + def text_bio + # BAD -- Using string interpolation + user = User.find_by "name = '#{params[:user_name]}'" + + # BAD -- Using string concatenation + find_str = "name = '" + params[:user_name] + "'" + user = User.find_by(find_str) + + # GOOD -- Using a hash to parameterize arguments + user = User.find_by name: params[:user_name] + + render plain: user&.text_bio + end +end diff --git a/ruby/ql/src/queries/security/cwe-094/CodeInjection.qhelp b/ruby/ql/src/queries/security/cwe-094/CodeInjection.qhelp new file mode 100644 index 000000000000..216b045e4b7a --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-094/CodeInjection.qhelp @@ -0,0 +1,46 @@ + + + + +

    +Directly evaluating user input (for example, an HTTP request parameter) as code without first +sanitizing the input allows an attacker arbitrary code execution. This can occur when user +input is passed to code that interprets it as an expression to be +evaluated, using methods such as Kernel.eval or Kernel.send. +

    +
    + + +

    +Avoid including user input in any expression that may be dynamically evaluated. If user input must +be included, use context-specific escaping before including it. +It is important that the correct escaping is used for the type of evaluation that will occur. +

    +
    + + +

    +The following example shows two functions setting a name from a request. +The first function uses eval to execute the set_name method. +This is dangerous as it can allow a malicious user to execute arbitrary code on the server. +For example, the user could supply the value "' + exec('rm -rf') + '" +to destroy the server's file system. +The second function calls the set_name method directly and is thus safe. + +

    + + +
    + + +
  • +OWASP: +Code Injection. +
  • +
  • +Wikipedia: Code Injection. +
  • +
    +
    diff --git a/ruby/ql/src/queries/security/cwe-094/CodeInjection.ql b/ruby/ql/src/queries/security/cwe-094/CodeInjection.ql new file mode 100644 index 000000000000..60e8e32c2f69 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-094/CodeInjection.ql @@ -0,0 +1,27 @@ +/** + * @name Code injection + * @description Interpreting unsanitized user input as code allows a malicious user to perform arbitrary + * code execution. + * @kind path-problem + * @problem.severity error + * @security-severity 9.3 + * @sub-severity high + * @precision high + * @id rb/code-injection + * @tags security + * external/owasp/owasp-a1 + * external/cwe/cwe-094 + * external/cwe/cwe-095 + * external/cwe/cwe-116 + */ + +import ruby +import codeql.ruby.security.CodeInjectionQuery +import DataFlow::PathGraph + +from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Source sourceNode +where + config.hasFlowPath(source, sink) and + sourceNode = source.getNode() +select sink.getNode(), source, sink, "This code execution depends on $@.", sourceNode, + "a user-provided value" diff --git a/ruby/ql/src/queries/security/cwe-094/examples/code_injection.rb b/ruby/ql/src/queries/security/cwe-094/examples/code_injection.rb new file mode 100644 index 000000000000..3ff3cf6cca6a --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-094/examples/code_injection.rb @@ -0,0 +1,17 @@ +class UsersController < ActionController::Base + # BAD - Allow user to define code to be run. + def create_bad + first_name = params[:first_name] + eval("set_name(#{first_name})") + end + + # GOOD - Call code directly + def create_good + first_name = params[:first_name] + set_name(first_name) + end + + def set_name(name) + @name = name + end +end diff --git a/ruby/ql/src/queries/security/cwe-1333/PolynomialReDoS.qhelp b/ruby/ql/src/queries/security/cwe-1333/PolynomialReDoS.qhelp new file mode 100644 index 000000000000..0a93a3a245c3 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-1333/PolynomialReDoS.qhelp @@ -0,0 +1,113 @@ + + + + + + + +

    + + Consider this use of a regular expression, which removes + all leading and trailing whitespace in a string: + +

    + + + text.gsub!(/^\s+|\s+$/, '') # BAD + + +

    + + The sub-expression "\s+$" will match the + whitespace characters in text from left to + right, but it can start matching anywhere within a + whitespace sequence. This is problematic for strings + that do not end with a whitespace + character. Such a string will force the regular + expression engine to process each whitespace sequence + once per whitespace character in the sequence. + +

    + +

    + + This ultimately means that the time cost of trimming a + string is quadratic in the length of the string. So a + string like "a b" will take milliseconds to + process, but a similar string with a million spaces + instead of just one will take several minutes. + +

    + +

    + + Avoid this problem by rewriting the regular expression + to not contain the ambiguity about when to start + matching whitespace sequences. For instance, by using a + negative look-behind + (/^\s+|(?<!\s)\s+$/), or just by using + the built-in strip method (text.strip!). + +

    + +

    + + Note that the sub-expression "^\s+" is + not problematic as the ^ + anchor restricts when that sub-expression can start + matching, and as the regular expression engine matches + from left to right. + +

    + +
    + + + +

    + + As a similar, but slightly subtler problem, consider the + regular expression that matches lines with numbers, possibly written + using scientific notation: +

    + + + /^0\.\d+E?\d+$/ # BAD + + +

    + + The problem with this regular expression is in the + sub-expression \d+E?\d+ because the second + \d+ can start matching digits anywhere + after the first match of the first \d+ if + there is no E in the input string. + +

    + +

    + + This is problematic for strings that do + not end with a digit. Such a string + will force the regular expression engine to process each + digit sequence once per digit in the sequence, again + leading to a quadratic time complexity. + +

    + +

    + + To make the processing faster, the regular expression + should be rewritten such that the two \d+ + sub-expressions do not have overlapping matches: + /^0\.\d+(E\d+)?$/. + +

    + +
    + + + +
    diff --git a/ruby/ql/src/queries/security/cwe-1333/PolynomialReDoS.ql b/ruby/ql/src/queries/security/cwe-1333/PolynomialReDoS.ql new file mode 100644 index 000000000000..9ee914c3bf0d --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-1333/PolynomialReDoS.ql @@ -0,0 +1,31 @@ +/** + * @name Polynomial regular expression used on uncontrolled data + * @description A regular expression that can require polynomial time + * to match may be vulnerable to denial-of-service attacks. + * @kind path-problem + * @problem.severity warning + * @security-severity 7.5 + * @precision high + * @id rb/polynomial-redos + * @tags security + * external/cwe/cwe-1333 + * external/cwe/cwe-730 + * external/cwe/cwe-400 + */ + +import DataFlow::PathGraph +import codeql.ruby.DataFlow +import codeql.ruby.regexp.PolynomialReDoSQuery +import codeql.ruby.regexp.SuperlinearBackTracking + +from + PolynomialReDoS::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, + PolynomialReDoS::Sink sinkNode, PolynomialBackTrackingTerm regexp +where + config.hasFlowPath(source, sink) and + sinkNode = sink.getNode() and + regexp = sinkNode.getRegExp() +select sinkNode.getHighlight(), source, sink, + "This $@ that depends on $@ may run slow on strings " + regexp.getPrefixMessage() + + "with many repetitions of '" + regexp.getPumpString() + "'.", regexp, "regular expression", + source.getNode(), "a user-provided value" diff --git a/ruby/ql/src/queries/security/cwe-1333/ReDoS.qhelp b/ruby/ql/src/queries/security/cwe-1333/ReDoS.qhelp new file mode 100644 index 000000000000..4c85702a0d3e --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-1333/ReDoS.qhelp @@ -0,0 +1,28 @@ + + + + +

    Consider this regular expression:

    + + /^_(__|.)+_$/ + +

    + Its sub-expression "(__|.)+?" can match the string + "__" either by the first alternative "__" to the + left of the "|" operator, or by two repetitions of the second + alternative "." to the right. Thus, a string consisting of an + odd number of underscores followed by some other character will cause the + regular expression engine to run for an exponential amount of time before + rejecting the input. +

    +

    + This problem can be avoided by rewriting the regular expression to remove + the ambiguity between the two branches of the alternative inside the + repetition: +

    + + /^_(__|[^_])+_$/ + +
    + +
    diff --git a/ruby/ql/src/queries/security/cwe-1333/ReDoS.ql b/ruby/ql/src/queries/security/cwe-1333/ReDoS.ql new file mode 100644 index 000000000000..234772240e3c --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-1333/ReDoS.ql @@ -0,0 +1,25 @@ +/** + * @name Inefficient regular expression + * @description A regular expression that requires exponential time to match certain inputs + * can be a performance bottleneck, and may be vulnerable to denial-of-service + * attacks. + * @kind problem + * @problem.severity error + * @security-severity 7.5 + * @precision high + * @id rb/redos + * @tags security + * external/cwe/cwe-1333 + * external/cwe/cwe-730 + * external/cwe/cwe-400 + */ + +import codeql.ruby.regexp.ExponentialBackTracking +import codeql.ruby.regexp.ReDoSUtil +import codeql.ruby.regexp.RegExpTreeView + +from RegExpTerm t, string pump, State s, string prefixMsg +where hasReDoSResult(t, pump, s, prefixMsg) +select t, + "This part of the regular expression may cause exponential backtracking on strings " + prefixMsg + + "containing many repetitions of '" + pump + "'." diff --git a/ruby/ql/src/queries/security/cwe-1333/ReDoSIntroduction.inc.qhelp b/ruby/ql/src/queries/security/cwe-1333/ReDoSIntroduction.inc.qhelp new file mode 100644 index 000000000000..fd3c39bfaa7d --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-1333/ReDoSIntroduction.inc.qhelp @@ -0,0 +1,37 @@ + + + +

    + Some regular expressions take a long time to match certain input strings + to the point where the time it takes to match a string of length n + is proportional to nk or even 2n. + Such regular expressions can negatively affect performance, or even allow + a malicious user to perform a Denial of Service ("DoS") attack by crafting + an expensive input string for the regular expression to match. +

    +

    + The regular expression engine used by the Ruby interpreter (MRI) uses + backtracking non-deterministic finite automata to implement regular + expression matching. While this approach is space-efficient and allows + supporting advanced features like capture groups, it is not time-efficient + in general. The worst-case time complexity of such an automaton can be + polynomial or even exponential, meaning that for strings of a certain + shape, increasing the input length by ten characters may make the + automaton about 1000 times slower. +

    +

    + Typically, a regular expression is affected by this problem if it contains + a repetition of the form r* or r+ where the + sub-expression r is ambiguous in the sense that it can match + some string in multiple ways. More information about the precise + circumstances can be found in the references. +

    +
    + +

    + Modify the regular expression to remove the ambiguity, or ensure that the + strings matched with the regular expression are short enough that the + time-complexity does not matter. +

    +
    +
    \ No newline at end of file diff --git a/ruby/ql/src/queries/security/cwe-1333/ReDoSReferences.inc.qhelp b/ruby/ql/src/queries/security/cwe-1333/ReDoSReferences.inc.qhelp new file mode 100644 index 000000000000..d8bb9eb9ee01 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-1333/ReDoSReferences.inc.qhelp @@ -0,0 +1,13 @@ + + + +
  • OWASP: + Regular expression Denial of Service - ReDoS. +
  • +
  • Wikipedia: ReDoS.
  • +
  • Wikipedia: Time complexity.
  • +
  • James Kirrage, Asiri Rathnayake, Hayo Thielecke: + Static Analysis for Regular Expression Denial-of-Service Attack. +
  • +
    +
    diff --git a/ruby/ql/src/queries/security/cwe-502/UnsafeDeserialization.qhelp b/ruby/ql/src/queries/security/cwe-502/UnsafeDeserialization.qhelp new file mode 100644 index 000000000000..0230c924958a --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-502/UnsafeDeserialization.qhelp @@ -0,0 +1,57 @@ + + + + +

    +Deserializing untrusted data using any method that allows the construction of +arbitrary objects is easily exploitable and, in many cases, allows an attacker +to execute arbitrary code. +

    +
    + + +

    +Avoid deserialization of untrusted data if possible. If the architecture permits +it, use serialization formats that cannot represent arbitarary objects. For +libraries that support it, such as the Ruby standard library's JSON +module, ensure that the parser is configured to disable +deserialization of arbitrary objects. +

    +
    + + +

    +The following example calls the Marshal.load, JSON.load, and +YAML.load methods on data from an HTTP request. Since these methods +are capable of deserializing to arbitrary objects, this is inherently unsafe. +

    + +

    +Using JSON.parse and YAML.safe_load instead, as in the +following example, removes the vulnerability. Note that there is no safe way to +deserialize untrusted data using Marshal. +

    + +
    + + + +
  • +OWASP vulnerability description: +deserialization of untrusted data. +
  • +
  • +Ruby documentation: guidance on deserializing objects safely. +
  • +
  • +Ruby documentation: security guidance on the Marshal library. +
  • +
  • +Ruby documentation: security guidance on JSON.load. +
  • +
  • +Ruby documentation: security guidance on the YAML library. +
  • +
    + +
    diff --git a/ruby/ql/src/queries/security/cwe-502/UnsafeDeserialization.ql b/ruby/ql/src/queries/security/cwe-502/UnsafeDeserialization.ql new file mode 100644 index 000000000000..0df3b7c8d679 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-502/UnsafeDeserialization.ql @@ -0,0 +1,21 @@ +/** + * @name Deserialization of user-controlled data + * @description Deserializing user-controlled data may allow attackers to + * execute arbitrary code. + * @kind path-problem + * @problem.severity warning + * @security-severity 9.8 + * @precision high + * @id rb/unsafe-deserialization + * @tags security + * external/cwe/cwe-502 + */ + +import ruby +import DataFlow::PathGraph +import codeql.ruby.DataFlow +import codeql.ruby.security.UnsafeDeserializationQuery + +from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "Unsafe deserialization of $@.", source.getNode(), "user input" diff --git a/ruby/ql/src/queries/security/cwe-502/examples/UnsafeDeserializationBad.rb b/ruby/ql/src/queries/security/cwe-502/examples/UnsafeDeserializationBad.rb new file mode 100644 index 000000000000..008b705b6426 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-502/examples/UnsafeDeserializationBad.rb @@ -0,0 +1,20 @@ +require 'json' +require 'yaml' + +class UserController < ActionController::Base + def marshal_example + data = Base64.decode64 params[:data] + object = Marshal.load data + # ... + end + + def json_example + object = JSON.load params[:json] + # ... + end + + def yaml_example + object = YAML.load params[:yaml] + # ... + end +end \ No newline at end of file diff --git a/ruby/ql/src/queries/security/cwe-502/examples/UnsafeDeserializationGood.rb b/ruby/ql/src/queries/security/cwe-502/examples/UnsafeDeserializationGood.rb new file mode 100644 index 000000000000..b86bd9590d6f --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-502/examples/UnsafeDeserializationGood.rb @@ -0,0 +1,13 @@ +require 'json' + +class UserController < ActionController::Base + def safe_json_example + object = JSON.parse params[:json] + # ... + end + + def safe_yaml_example + object = YAML.safe_load params[:yaml] + # ... + end +end \ No newline at end of file diff --git a/ruby/ql/src/queries/security/cwe-601/UrlRedirect.qhelp b/ruby/ql/src/queries/security/cwe-601/UrlRedirect.qhelp new file mode 100644 index 000000000000..307580d9cc10 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-601/UrlRedirect.qhelp @@ -0,0 +1,44 @@ + + + + +

    +Directly incorporating user input into a URL redirect request without validating the input +can facilitate phishing attacks. In these attacks, unsuspecting users can be redirected to a +malicious site that looks very similar to the real site they intend to visit, but which is +controlled by the attacker. +

    +
    + + +

    +To guard against untrusted URL redirection, it is advisable to avoid putting user input +directly into a redirect URL. Instead, maintain a list of authorized +redirects on the server; then choose from that list based on the user input provided. +

    +
    + + +

    +The following example shows an HTTP request parameter being used directly in a URL redirect +without validating the input, which facilitates phishing attacks: +

    + + + +

    +One way to remedy the problem is to validate the user input against a known fixed string +before doing the redirection: +

    + + +
    + + +
  • OWASP: + XSS Unvalidated Redirects and Forwards Cheat Sheet.
  • +
  • Rails Guides: + Redirection and Files.
  • +
    + +
    diff --git a/ruby/ql/src/queries/security/cwe-601/UrlRedirect.ql b/ruby/ql/src/queries/security/cwe-601/UrlRedirect.ql new file mode 100644 index 000000000000..aeaa4c29dc54 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-601/UrlRedirect.ql @@ -0,0 +1,22 @@ +/** + * @name URL redirection from remote source + * @description URL redirection based on unvalidated user input + * may cause redirection to malicious web sites. + * @kind path-problem + * @problem.severity error + * @security-severity 6.1 + * @sub-severity low + * @id rb/url-redirection + * @tags security + * external/cwe/cwe-601 + * @precision high + */ + +import ruby +import codeql.ruby.security.UrlRedirectQuery +import codeql.ruby.DataFlow::DataFlow::PathGraph + +from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink +where config.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "Untrusted URL redirection due to $@.", source.getNode(), + "a user-provided value" diff --git a/ruby/ql/src/queries/security/cwe-601/examples/redirect_bad.rb b/ruby/ql/src/queries/security/cwe-601/examples/redirect_bad.rb new file mode 100644 index 000000000000..f0a32bac6030 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-601/examples/redirect_bad.rb @@ -0,0 +1,5 @@ +class HelloController < ActionController::Base + def hello + redirect_to params[:url] + end +end diff --git a/ruby/ql/src/queries/security/cwe-601/examples/redirect_good.rb b/ruby/ql/src/queries/security/cwe-601/examples/redirect_good.rb new file mode 100644 index 000000000000..f5241f486593 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-601/examples/redirect_good.rb @@ -0,0 +1,11 @@ +class HelloController < ActionController::Base + VALID_REDIRECT = "http://cwe.mitre.org/data/definitions/601.html" + + def hello + if params[:url] == VALID_REDIRECT + redirect_to params[:url] + else + # error + end + end +end diff --git a/ruby/ql/src/queries/security/cwe-732/WeakFilePermissions.qhelp b/ruby/ql/src/queries/security/cwe-732/WeakFilePermissions.qhelp new file mode 100644 index 000000000000..a5f8997e911b --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-732/WeakFilePermissions.qhelp @@ -0,0 +1,26 @@ + + + + +

    +When creating a file, POSIX systems allow permissions to be specified +for owner, group and others separately. Permissions should be kept as +strict as possible, preventing access to the files contents by other users. +

    + +
    + + +

    +Restrict the file permissions of files to prevent any but the owner being able to read or write to that file +

    +
    + + +
  • +Wikipedia: +File system permissions. +
  • +
    + +
    diff --git a/ruby/ql/src/queries/security/cwe-732/WeakFilePermissions.ql b/ruby/ql/src/queries/security/cwe-732/WeakFilePermissions.ql new file mode 100644 index 000000000000..793eafe04bdb --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-732/WeakFilePermissions.ql @@ -0,0 +1,64 @@ +/** + * @name Overly permissive file permissions + * @description Allowing files to be readable or writable by users other than the owner may allow sensitive information to be accessed. + * @kind path-problem + * @problem.severity warning + * @security-severity 7.8 + * @id rb/overly-permissive-file + * @tags external/cwe/cwe-732 + * security + * @precision low + */ + +import ruby +import codeql.ruby.Concepts +import codeql.ruby.DataFlow +import DataFlow::PathGraph +import codeql.ruby.ApiGraphs + +bindingset[p] +int world_permission(int p) { result = p.bitAnd(7) } + +// 70 oct = 56 dec +bindingset[p] +int group_permission(int p) { result = p.bitAnd(56) } + +bindingset[p] +string access(int p) { + p.bitAnd(2) != 0 and result = "writable" + or + p.bitAnd(4) != 0 and result = "readable" +} + +/** An expression specifing a file permission that allows group/others read or write access */ +class PermissivePermissionsExpr extends Expr { + // TODO: non-literal expressions? + PermissivePermissionsExpr() { + exists(int perm, string acc | + perm = this.(IntegerLiteral).getValue() and + (acc = access(world_permission(perm)) or acc = access(group_permission(perm))) + ) + or + // adding/setting read or write permissions for all/group/other + this.(StringLiteral).getValueText().regexpMatch(".*[ago][^-=+]*[+=][xXst]*[rw].*") + } +} + +class PermissivePermissionsConfig extends DataFlow::Configuration { + PermissivePermissionsConfig() { this = "PermissivePermissionsConfig" } + + override predicate isSource(DataFlow::Node source) { + exists(PermissivePermissionsExpr ppe | source.asExpr().getExpr() = ppe) + } + + override predicate isSink(DataFlow::Node sink) { + exists(FileSystemPermissionModification mod | mod.getAPermissionNode() = sink) + } +} + +from + DataFlow::PathNode source, DataFlow::PathNode sink, PermissivePermissionsConfig conf, + FileSystemPermissionModification mod +where conf.hasFlowPath(source, sink) and mod.getAPermissionNode() = sink.getNode() +select source.getNode(), source, sink, "Overly permissive mask in $@ sets file to $@.", mod, + mod.toString(), source.getNode(), source.getNode().toString() diff --git a/ruby/ql/src/queries/security/cwe-798/HardcodedCredentials.qhelp b/ruby/ql/src/queries/security/cwe-798/HardcodedCredentials.qhelp new file mode 100644 index 000000000000..65122bd2d19f --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-798/HardcodedCredentials.qhelp @@ -0,0 +1,79 @@ + + + + +

    +Including unencrypted hard-coded inbound or outbound authentication credentials within source code +or configuration files is dangerous because the credentials may be easily discovered. +

    +

    +Source or configuration files containing hard-coded credentials may be visible to an attacker. For +example, the source code may be open source, or it may be leaked or accidentally revealed. +

    +

    +For inbound authentication, hard-coded credentials may allow unauthorized access to the system. This +is particularly problematic if the credential is hard-coded in the source code, because it cannot be +disabled easily. For outbound authentication, the hard-coded credentials may provide an attacker with +privileged information or unauthorized access to some other system. +

    + +
    + + +

    +Remove hard-coded credentials, such as user names, passwords and certificates, from source code, +placing them in configuration files or other data stores if necessary. If possible, store +configuration files including credential data separately from the source code, in a secure location +with restricted access. +

    + +

    +For outbound authentication details, consider encrypting the credentials or the enclosing data +stores or configuration files, and using permissions to restrict access. +

    + +

    +For inbound authentication details, consider hashing passwords using standard library functions +where possible. For example, OpenSSL::KDF.pbkdf2_hmac. +

    + +
    + + +

    +The following examples shows different types of inbound and outbound authentication. +

    + +

    +In the first case, RackAppBad, we accept a password from a remote user, and compare +it against a plaintext string literal. If an attacker acquires the source code they can observe +the password, and can log in to the system. Furthermore, if such an intrusion was discovered, the +application would need to be rewritten and redeployed in order to change the password. +

    + +

    +In the second case, RackAppGood, the password is compared to a hashed and salted +password stored in a configuration file, using OpenSSL::KDF.pbkdf2_hmac. +In this case, access to the source code or the assembly would not reveal the password to an +attacker. Even access to the configuration file containing the password hash and salt would be of +little value to an attacker, as it is usually extremely difficult to reverse engineer the password +from the hash and salt. In a real application care should be taken to make the string comparison +of the hashed input against the hashed password take close to constant time, as this will make +timing attacks more difficult. +

    + + + +
    + + +
  • +OWASP: +XSS +Use of hard-coded password. +
  • + +
    +
    diff --git a/ruby/ql/src/queries/security/cwe-798/HardcodedCredentials.ql b/ruby/ql/src/queries/security/cwe-798/HardcodedCredentials.ql new file mode 100644 index 000000000000..c887793031dc --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-798/HardcodedCredentials.ql @@ -0,0 +1,155 @@ +/** + * @name Hard-coded credentials + * @description Credentials are hard coded in the source code of the application. + * @kind path-problem + * @problem.severity error + * @security-severity 9.8 + * @precision high + * @id rb/hardcoded-credentials + * @tags security + * external/cwe/cwe-259 + * external/cwe/cwe-321 + * external/cwe/cwe-798 + */ + +import ruby +import codeql.ruby.DataFlow +import DataFlow::PathGraph +import codeql.ruby.TaintTracking +import codeql.ruby.controlflow.CfgNodes + +bindingset[char, fraction] +predicate fewer_characters_than(StringLiteral str, string char, float fraction) { + exists(string text, int chars | + text = str.getValueText() and + chars = count(int i | text.charAt(i) = char) + | + /* Allow one character */ + chars = 1 or + chars < text.length() * fraction + ) +} + +predicate possible_reflective_name(string name) { + // TODO: implement this? + none() +} + +int char_count(StringLiteral str) { result = count(string c | c = str.getValueText().charAt(_)) } + +predicate capitalized_word(StringLiteral str) { str.getValueText().regexpMatch("[A-Z][a-z]+") } + +predicate format_string(StringLiteral str) { str.getValueText().matches("%{%}%") } + +predicate maybeCredential(Expr e) { + /* A string that is not too short and unlikely to be text or an identifier. */ + exists(StringLiteral str | str = e | + /* At least 10 characters */ + str.getValueText().length() > 9 and + /* Not too much whitespace */ + fewer_characters_than(str, " ", 0.05) and + /* or underscores */ + fewer_characters_than(str, "_", 0.2) and + /* Not too repetitive */ + exists(int chars | chars = char_count(str) | + chars > 15 or + chars * 3 > str.getValueText().length() * 2 + ) and + not possible_reflective_name(str.getValueText()) and + not capitalized_word(str) and + not format_string(str) + ) + or + /* Or, an integer with over 32 bits */ + exists(IntegerLiteral lit | lit = e | + not exists(lit.getValue()) and + /* Not a set of flags or round number */ + not lit.getValueText().matches("%00%") + ) +} + +class HardcodedValueSource extends DataFlow::Node { + HardcodedValueSource() { maybeCredential(this.asExpr().getExpr()) } +} + +/** + * Gets a regular expression for matching names of locations (variables, parameters, keys) that + * indicate the value being held is a credential. + */ +private string getACredentialRegExp() { + result = "(?i).*pass(wd|word|code|phrase)(?!.*question).*" or + result = "(?i).*(puid|username|userid).*" or + result = "(?i).*(cert)(?!.*(format|name)).*" +} + +bindingset[name] +private predicate maybeCredentialName(string name) { + name.regexpMatch(getACredentialRegExp()) and + not name.suffix(name.length() - 4) = "file" +} + +// Positional parameter +private DataFlow::Node credentialParameter() { + exists(Method m, NamedParameter p, int idx | + result.asParameter() = p and + p = m.getParameter(idx) and + maybeCredentialName(p.getName()) + ) +} + +// Keyword argument +private Expr credentialKeywordArgument() { + exists(MethodCall mc, string argKey | + result = mc.getKeywordArgument(argKey) and + maybeCredentialName(argKey) + ) +} + +// An equality check against a credential value +private Expr credentialComparison() { + exists(EqualityOperation op, VariableReadAccess vra | + maybeCredentialName(vra.getVariable().getName()) and + ( + op.getLeftOperand() = result and + op.getRightOperand() = vra + or + op.getLeftOperand() = vra and op.getRightOperand() = result + ) + ) +} + +private predicate isCredentialSink(DataFlow::Node node) { + node = credentialParameter() + or + node.asExpr().getExpr() = credentialKeywordArgument() + or + node.asExpr().getExpr() = credentialComparison() +} + +class CredentialSink extends DataFlow::Node { + CredentialSink() { isCredentialSink(this) } +} + +class HardcodedCredentialsConfiguration extends DataFlow::Configuration { + HardcodedCredentialsConfiguration() { this = "HardcodedCredentialsConfiguration" } + + override predicate isSource(DataFlow::Node source) { source instanceof HardcodedValueSource } + + override predicate isSink(DataFlow::Node sink) { sink instanceof CredentialSink } + + override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + exists(ExprNodes::BinaryOperationCfgNode binop | + ( + binop.getLeftOperand() = node1.asExpr() or + binop.getRightOperand() = node1.asExpr() + ) and + binop = node2.asExpr() and + // string concatenation + binop.getExpr() instanceof AddExpr + ) + } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, HardcodedCredentialsConfiguration conf +where conf.hasFlowPath(source, sink) +select source.getNode(), source, sink, "Use of $@.", source.getNode(), "hardcoded credentials" diff --git a/ruby/ql/src/queries/security/cwe-798/HardcodedCredentials.rb b/ruby/ql/src/queries/security/cwe-798/HardcodedCredentials.rb new file mode 100644 index 000000000000..66f9e6bcbb18 --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-798/HardcodedCredentials.rb @@ -0,0 +1,40 @@ +require 'rack' +require 'yaml' +require 'openssl' + +class RackAppBad + def call(env) + req = Rack::Request.new(env) + password = req.params['password'] + + # BAD: Inbound authentication made by comparison to string literal + if password == 'myPa55word' + [200, {'Content-type' => 'text/plain'}, ['OK']] + else + [403, {'Content-type' => 'text/plain'}, ['Permission denied']] + end + end +end + +class RackAppGood + def call(env) + req = Rack::Request.new(env) + password = req.params['password'] + + config_file = YAML.load_file('config.yml') + hashed_password = config_file['hashed_password'] + salt = [config_file['salt']].pack('H*') + + #GOOD: Inbound authentication made by comparing to a hash password from a config file. + hash = OpenSSL::Digest::SHA256.new + dk = OpenSSL::KDF.pbkdf2_hmac( + password, salt: salt, hash: hash, iterations: 100_000, length: hash.digest_length + ) + hashed_input = dk.unpack('H*').first + if hashed_password == hashed_input + [200, {'Content-type' => 'text/plain'}, ['OK']] + else + [403, {'Content-type' => 'text/plain'}, ['Permission denied']] + end + end +end diff --git a/ruby/ql/src/queries/summary/LinesOfCode.ql b/ruby/ql/src/queries/summary/LinesOfCode.ql new file mode 100644 index 000000000000..f727cf504d9e --- /dev/null +++ b/ruby/ql/src/queries/summary/LinesOfCode.ql @@ -0,0 +1,15 @@ +/** + * @id rb/summary/lines-of-code + * @name Total lines of Ruby code in the database + * @description The total number of lines of Ruby 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 ruby + +select sum(RubyFile f | exists(f.getRelativePath()) | f.getNumberOfLinesOfCode()) diff --git a/ruby/ql/src/queries/summary/LinesOfUserCode.ql b/ruby/ql/src/queries/summary/LinesOfUserCode.ql new file mode 100644 index 000000000000..19f4f46fb8d7 --- /dev/null +++ b/ruby/ql/src/queries/summary/LinesOfUserCode.ql @@ -0,0 +1,19 @@ +/** + * @id rb/summary/lines-of-user-code + * @name Total Lines of user written Ruby code in the database + * @description The total number of lines of Ruby 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 ruby + +select sum(RubyFile f | + f.fromSource() and + exists(f.getRelativePath()) and + not f.getAbsolutePath().matches("%/vendor/%") + | + f.getNumberOfLinesOfCode() + ) diff --git a/ruby/ql/src/queries/summary/NumberOfFilesExtractedWithErrors.ql b/ruby/ql/src/queries/summary/NumberOfFilesExtractedWithErrors.ql new file mode 100644 index 000000000000..1a68d2c57e6a --- /dev/null +++ b/ruby/ql/src/queries/summary/NumberOfFilesExtractedWithErrors.ql @@ -0,0 +1,15 @@ +/** + * @id rb/summary/number-of-files-extracted-with-errors + * @name Total number of files that were extracted with errors + * @description The total number of Ruby code files that we extracted, but where + * at least one extraction error occurred in the process. + * @kind metric + * @tags summary + */ + +import ruby +import codeql.ruby.Diagnostics + +select count(File f | + exists(ExtractionError e | e.getLocation().getFile() = f) and exists(f.getRelativePath()) + ) diff --git a/ruby/ql/src/queries/summary/NumberOfSuccessfullyExtractedFiles.ql b/ruby/ql/src/queries/summary/NumberOfSuccessfullyExtractedFiles.ql new file mode 100644 index 000000000000..356989935e12 --- /dev/null +++ b/ruby/ql/src/queries/summary/NumberOfSuccessfullyExtractedFiles.ql @@ -0,0 +1,15 @@ +/** + * @id rb/summary/number-of-successfully-extracted-files + * @name Total number of files that were extracted without error + * @description The total number of Ruby code files that we extracted without + * encountering any extraction errors + * @kind metric + * @tags summary + */ + +import ruby +import codeql.ruby.Diagnostics + +select count(File f | + not exists(ExtractionError e | e.getLocation().getFile() = f) and exists(f.getRelativePath()) + ) diff --git a/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql b/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql new file mode 100644 index 000000000000..5ce06a0c1824 --- /dev/null +++ b/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql @@ -0,0 +1,28 @@ +/** + * @name Useless assignment to local variable + * @description An assignment to a local variable that is not used later on, or whose value is always + * overwritten, has no effect. + * @kind problem + * @problem.severity warning + * @id rb/useless-assignment-to-local + * @tags maintainability + * external/cwe/cwe-563 + * @precision low + */ + +import ruby +import codeql.ruby.dataflow.SSA + +class RelevantLocalVariableWriteAccess extends LocalVariableWriteAccess { + RelevantLocalVariableWriteAccess() { + not this.getVariable().getName().charAt(0) = "_" and + not this = any(Parameter p).getAVariable().getDefiningAccess() + } +} + +from RelevantLocalVariableWriteAccess write, LocalVariable v +where + v = write.getVariable() and + exists(write.getAControlFlowNode()) and + not exists(Ssa::WriteDefinition def | def.getWriteAccess() = write) +select write, "This assignment to $@ is useless, since its value is never read.", v, v.getName() diff --git a/ruby/ql/src/queries/variables/UninitializedLocal.ql b/ruby/ql/src/queries/variables/UninitializedLocal.ql new file mode 100644 index 000000000000..ef134eddd707 --- /dev/null +++ b/ruby/ql/src/queries/variables/UninitializedLocal.ql @@ -0,0 +1,32 @@ +/** + * @name Potentially uninitialized local variable + * @description Using a local variable before it is initialized gives the variable a default + * 'nil' value. + * @kind problem + * @problem.severity error + * @id rb/uninitialized-local-variable + * @tags reliability + * correctness + * @precision low + */ + +import ruby +import codeql.ruby.dataflow.SSA + +class RelevantLocalVariableReadAccess extends LocalVariableReadAccess { + RelevantLocalVariableReadAccess() { + not exists(MethodCall c | + c.getReceiver() = this and + c.getMethodName() = "nil?" + ) + } +} + +from RelevantLocalVariableReadAccess read, LocalVariable v +where + v = read.getVariable() and + exists(Ssa::Definition def | + def.getAnUltimateDefinition() instanceof Ssa::UninitializedDefinition and + read = def.getARead().getExpr() + ) +select read, "Local variable $@ may be used before it is initialized.", v, v.getName() diff --git a/ruby/ql/src/queries/variables/UnusedParameter.ql b/ruby/ql/src/queries/variables/UnusedParameter.ql new file mode 100644 index 000000000000..1aa1a6bc4626 --- /dev/null +++ b/ruby/ql/src/queries/variables/UnusedParameter.ql @@ -0,0 +1,27 @@ +/** + * @name Unused parameter. + * @description A parameter that is not used later on, or whose value is always overwritten, + * can be removed. + * @kind problem + * @problem.severity warning + * @id rb/unused-parameter + * @tags maintainability + * external/cwe/cwe-563 + * @precision low + */ + +import ruby +import codeql.ruby.dataflow.SSA + +class RelevantParameterVariable extends LocalVariable { + RelevantParameterVariable() { + exists(Parameter p | + this = p.getAVariable() and + not this.getName().charAt(0) = "_" + ) + } +} + +from RelevantParameterVariable v +where not exists(Ssa::WriteDefinition def | def.getWriteAccess() = v.getDefiningAccess()) +select v, "Unused parameter." diff --git a/ruby/ql/test/TestUtilities/InlineExpectationsTest.qll b/ruby/ql/test/TestUtilities/InlineExpectationsTest.qll new file mode 100644 index 000000000000..d351bac89a82 --- /dev/null +++ b/ruby/ql/test/TestUtilities/InlineExpectationsTest.qll @@ -0,0 +1,346 @@ +/** + * Provides a library for writing QL tests whose success or failure is based on expected results + * embedded in the test source code as comments, rather than the contents of an `.expected` file + * (in that the `.expected` file should always be empty). + * + * To add this framework to a new language: + * - Add a file `InlineExpectationsTestPrivate.qll` that defines a `LineComment` class. This class + * must support a `getContents` method that returns the contents of the given comment, _excluding_ + * the comment indicator itself. It should also define `toString` and `getLocation` as usual. + * + * To create a new inline expectations test: + * - Declare a class that extends `InlineExpectationsTest`. In the characteristic predicate of the + * new class, bind `this` to a unique string (usually the name of the test). + * - Override the `hasActualResult()` predicate to produce the actual results of the query. For each + * result, specify a `Location`, a text description of the element for which the result was + * reported, a short string to serve as the tag to identify expected results for this test, and the + * expected value of the result. + * - Override `getARelevantTag()` to return the set of tags that can be produced by + * `hasActualResult()`. Often this is just a single tag. + * + * Example: + * ```ql + * class ConstantValueTest extends InlineExpectationsTest { + * ConstantValueTest() { this = "ConstantValueTest" } + * + * override string getARelevantTag() { + * // We only use one tag for this test. + * result = "const" + * } + * + * override predicate hasActualResult( + * Location location, string element, string tag, string value + * ) { + * exists(Expr e | + * tag = "const" and // The tag for this test. + * value = e.getValue() and // The expected value. Will only hold for constant expressions. + * location = e.getLocation() and // The location of the result to be reported. + * element = e.toString() // The display text for the result. + * ) + * } + * } + * ``` + * + * There is no need to write a `select` clause or query predicate. All of the differences between + * expected results and actual results will be reported in the `failures()` query predicate. + * + * To annotate the test source code with an expected result, place a comment starting with a `$` on the + * same line as the expected result, with text of the following format as the body of the comment: + * + * `tag=expected-value` + * + * Where `tag` is the value of the `tag` parameter from `hasActualResult()`, and `expected-value` is + * the value of the `value` parameter from `hasActualResult()`. The `=expected-value` portion may be + * omitted, in which case `expected-value` is treated as the empty string. Multiple expectations may + * be placed in the same comment. Any actual result that + * appears on a line that does not contain a matching expected result comment will be reported with + * a message of the form "Unexpected result: tag=value". Any expected result comment for which there + * is no matching actual result will be reported with a message of the form + * "Missing result: tag=expected-value". + * + * Example: + * ```cpp + * int i = x + 5; // $const=5 + * int j = y + (7 - 3) // $const=7 const=3 const=4 // The result of the subtraction is a constant. + * ``` + * + * For tests that contain known missing and spurious results, it is possible to further + * annotate that a particular expected result is known to be spurious, or that a particular + * missing result is known to be missing: + * + * `$ SPURIOUS: tag=expected-value` // Spurious result + * `$ MISSING: tag=expected-value` // Missing result + * + * A spurious expectation is treated as any other expected result, except that if there is no + * matching actual result, the message will be of the form "Fixed spurious result: tag=value". A + * missing expectation is treated as if there were no expected result, except that if a + * matching expected result is found, the message will be of the form + * "Fixed missing result: tag=value". + * + * A single line can contain all the expected, spurious and missing results of that line. For instance: + * `$ tag1=value1 SPURIOUS: tag2=value2 MISSING: tag3=value3`. + * + * If the same result value is expected for two or more tags on the same line, there is a shorthand + * notation available: + * + * `tag1,tag2=expected-value` + * + * is equivalent to: + * + * `tag1=expected-value tag2=expected-value` + */ + +private import InlineExpectationsTestPrivate + +/** + * Base class for tests with inline expectations. The test extends this class to provide the actual + * results of the query, which are then compared with the expected results in comments to produce a + * list of failure messages that point out where the actual results differ from the expected + * results. + */ +abstract class InlineExpectationsTest extends string { + bindingset[this] + InlineExpectationsTest() { any() } + + /** + * Returns all tags that can be generated by this test. Most tests will only ever produce a single + * tag. Any expected result comments for a tag that is not returned by the `getARelevantTag()` + * predicate for an active test will be ignored. This makes it possible to write multiple tests in + * different `.ql` files that all query the same source code. + */ + abstract string getARelevantTag(); + + /** + * Returns the actual results of the query that is being tested. Each result consist of the + * following values: + * - `location` - The source code location of the result. Any expected result comment must appear + * on the start line of this location. + * - `element` - Display text for the element on which the result is reported. + * - `tag` - The tag that marks this result as coming from this test. This must be one of the tags + * returned by `getARelevantTag()`. + * - `value` - The value of the result, which will be matched against the value associated with + * `tag` in any expected result comment on that line. + */ + abstract predicate hasActualResult(Location location, string element, string tag, string value); + + final predicate hasFailureMessage(FailureLocatable element, string message) { + exists(ActualResult actualResult | + actualResult.getTest() = this and + element = actualResult and + ( + exists(FalseNegativeExpectation falseNegative | + falseNegative.matchesActualResult(actualResult) and + message = "Fixed missing result:" + falseNegative.getExpectationText() + ) + or + not exists(ValidExpectation expectation | expectation.matchesActualResult(actualResult)) and + message = "Unexpected result: " + actualResult.getExpectationText() + ) + ) + or + exists(ValidExpectation expectation | + not exists(ActualResult actualResult | expectation.matchesActualResult(actualResult)) and + expectation.getTag() = getARelevantTag() and + element = expectation and + ( + expectation instanceof GoodExpectation and + message = "Missing result:" + expectation.getExpectationText() + or + expectation instanceof FalsePositiveExpectation and + message = "Fixed spurious result:" + expectation.getExpectationText() + ) + ) + or + exists(InvalidExpectation expectation | + element = expectation and + message = "Invalid expectation syntax: " + expectation.getExpectation() + ) + } +} + +/** + * RegEx pattern to match a comment containing one or more expected results. The comment must have + * `$` as its first non-whitespace character. Any subsequent character + * is treated as part of the expected results, except that the comment may contain a `//` sequence + * to treat the remainder of the line as a regular (non-interpreted) comment. + */ +private string expectationCommentPattern() { result = "\\s*\\$((?:[^/]|/[^/])*)(?://.*)?" } + +/** + * The possible columns in an expectation comment. The `TDefaultColumn` branch represents the first + * column in a comment. This column is not precedeeded by a name. `TNamedColumn(name)` represents a + * column containing expected results preceeded by the string `name:`. + */ +private newtype TColumn = + TDefaultColumn() or + TNamedColumn(string name) { name = ["MISSING", "SPURIOUS"] } + +bindingset[start, content] +private int getEndOfColumnPosition(int start, string content) { + result = + min(string name, int cand | + exists(TNamedColumn(name)) and + cand = content.indexOf(name + ":") and + cand >= start + | + cand + ) + or + not exists(string name | + exists(TNamedColumn(name)) and + content.indexOf(name + ":") >= start + ) and + result = content.length() +} + +private predicate getAnExpectation( + LineComment comment, TColumn column, string expectation, string tags, string value +) { + exists(string content | + content = comment.getContents().regexpCapture(expectationCommentPattern(), 1) and + ( + column = TDefaultColumn() and + exists(int end | + end = getEndOfColumnPosition(0, content) and + expectation = content.prefix(end).regexpFind(expectationPattern(), _, _).trim() + ) + or + exists(string name, int start, int end | + column = TNamedColumn(name) and + start = content.indexOf(name + ":") + name.length() + 1 and + end = getEndOfColumnPosition(start, content) and + expectation = content.substring(start, end).regexpFind(expectationPattern(), _, _).trim() + ) + ) + ) and + tags = expectation.regexpCapture(expectationPattern(), 1) and + if exists(expectation.regexpCapture(expectationPattern(), 2)) + then value = expectation.regexpCapture(expectationPattern(), 2) + else value = "" +} + +private string getColumnString(TColumn column) { + column = TDefaultColumn() and result = "" + or + column = TNamedColumn(result) +} + +/** + * RegEx pattern to match a single expected result, not including the leading `$`. It consists of one or + * more comma-separated tags containing only letters, digits, `-` and `_` (note that the first character + * must not be a digit), optionally followed by `=` and the expected value. + */ +private string expectationPattern() { + exists(string tag, string tags, string value | + tag = "[A-Za-z-_][A-Za-z-_0-9]*" and + tags = "((?:" + tag + ")(?:\\s*,\\s*" + tag + ")*)" and + // In Python, we allow both `"` and `'` for strings, as well as the prefixes `bru`. + // For example, `b"foo"`. + value = "((?:[bru]*\"[^\"]*\"|[bru]*'[^']*'|\\S+)*)" and + result = tags + "(?:=" + value + ")?" + ) +} + +private newtype TFailureLocatable = + TActualResult( + InlineExpectationsTest test, Location location, string element, string tag, string value + ) { + test.hasActualResult(location, element, tag, value) + } or + TValidExpectation(LineComment comment, string tag, string value, string knownFailure) { + exists(TColumn column, string tags | + getAnExpectation(comment, column, _, tags, value) and + tag = tags.splitAt(",") and + knownFailure = getColumnString(column) + ) + } or + TInvalidExpectation(LineComment comment, string expectation) { + getAnExpectation(comment, _, expectation, _, _) and + not expectation.regexpMatch(expectationPattern()) + } + +class FailureLocatable extends TFailureLocatable { + string toString() { none() } + + Location getLocation() { none() } + + final string getExpectationText() { result = getTag() + "=" + getValue() } + + string getTag() { none() } + + string getValue() { none() } +} + +class ActualResult extends FailureLocatable, TActualResult { + InlineExpectationsTest test; + Location location; + string element; + string tag; + string value; + + ActualResult() { this = TActualResult(test, location, element, tag, value) } + + override string toString() { result = element } + + override Location getLocation() { result = location } + + InlineExpectationsTest getTest() { result = test } + + override string getTag() { result = tag } + + override string getValue() { result = value } +} + +abstract private class Expectation extends FailureLocatable { + LineComment comment; + + override string toString() { result = comment.toString() } + + override Location getLocation() { result = comment.getLocation() } +} + +private class ValidExpectation extends Expectation, TValidExpectation { + string tag; + string value; + string knownFailure; + + ValidExpectation() { this = TValidExpectation(comment, tag, value, knownFailure) } + + override string getTag() { result = tag } + + override string getValue() { result = value } + + string getKnownFailure() { result = knownFailure } + + predicate matchesActualResult(ActualResult actualResult) { + getLocation().getStartLine() = actualResult.getLocation().getStartLine() and + getLocation().getFile() = actualResult.getLocation().getFile() and + getTag() = actualResult.getTag() and + getValue() = actualResult.getValue() + } +} + +/* Note: These next three classes correspond to all the possible values of type `TColumn`. */ +class GoodExpectation extends ValidExpectation { + GoodExpectation() { getKnownFailure() = "" } +} + +class FalsePositiveExpectation extends ValidExpectation { + FalsePositiveExpectation() { getKnownFailure() = "SPURIOUS" } +} + +class FalseNegativeExpectation extends ValidExpectation { + FalseNegativeExpectation() { getKnownFailure() = "MISSING" } +} + +class InvalidExpectation extends Expectation, TInvalidExpectation { + string expectation; + + InvalidExpectation() { this = TInvalidExpectation(comment, expectation) } + + string getExpectation() { result = expectation } +} + +query predicate failures(FailureLocatable element, string message) { + exists(InlineExpectationsTest test | test.hasFailureMessage(element, message)) +} diff --git a/ruby/ql/test/TestUtilities/InlineExpectationsTestPrivate.qll b/ruby/ql/test/TestUtilities/InlineExpectationsTestPrivate.qll new file mode 100644 index 000000000000..cb40a07ea04e --- /dev/null +++ b/ruby/ql/test/TestUtilities/InlineExpectationsTestPrivate.qll @@ -0,0 +1,9 @@ +import ruby +import codeql.ruby.ast.internal.TreeSitter + +/** + * A class representing line comments in Ruby. + */ +class LineComment extends Ruby::Comment { + string getContents() { result = this.getValue().suffix(1) } +} diff --git a/ruby/ql/test/library-tests/ast/Ast.expected b/ruby/ql/test/library-tests/ast/Ast.expected new file mode 100644 index 000000000000..afe1c82a40b5 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/Ast.expected @@ -0,0 +1,2186 @@ +gems/Gemfile: +# 1| [Toplevel] Gemfile +# 1| getStmt: [MethodCall] call to source +# 1| getReceiver: [Self] self +# 1| getArgument: [StringLiteral] "https://rubygems.org" +# 1| getComponent: [StringTextComponent] https://rubygems.org +# 3| getStmt: [MethodCall] call to gem +# 3| getReceiver: [Self] self +# 3| getArgument: [StringLiteral] "foo_gem" +# 3| getComponent: [StringTextComponent] foo_gem +# 3| getArgument: [StringLiteral] "~> 2.0" +# 3| getComponent: [StringTextComponent] ~> 2.0 +# 5| getStmt: [MethodCall] call to source +# 5| getReceiver: [Self] self +# 5| getArgument: [StringLiteral] "https://gems.example.com" +# 5| getComponent: [StringTextComponent] https://gems.example.com +# 5| getBlock: [DoBlock] do ... end +# 6| getStmt: [MethodCall] call to gem +# 6| getReceiver: [Self] self +# 6| getArgument: [StringLiteral] "my_gem" +# 6| getComponent: [StringTextComponent] my_gem +# 6| getArgument: [StringLiteral] "1.0" +# 6| getComponent: [StringTextComponent] 1.0 +# 7| getStmt: [MethodCall] call to gem +# 7| getReceiver: [Self] self +# 7| getArgument: [StringLiteral] "another_gem" +# 7| getComponent: [StringTextComponent] another_gem +# 7| getArgument: [StringLiteral] "3.1.4" +# 7| getComponent: [StringTextComponent] 3.1.4 +calls/calls.rb: +# 1| [Toplevel] calls.rb +# 2| getStmt: [MethodCall] call to foo +# 2| getReceiver: [Self] self +# 5| getStmt: [MethodCall] call to bar +# 5| getReceiver: [ConstantReadAccess] Foo +# 8| getStmt: [MethodCall] call to bar +# 8| getReceiver: [Self] self +# 11| getStmt: [MethodCall] call to bar +# 11| getReceiver: [IntegerLiteral] 123 +# 14| getStmt: [MethodCall] call to foo +# 14| getReceiver: [Self] self +# 14| getArgument: [IntegerLiteral] 0 +# 14| getArgument: [IntegerLiteral] 1 +# 14| getArgument: [IntegerLiteral] 2 +# 17| getStmt: [MethodCall] call to foo +# 17| getReceiver: [Self] self +# 17| getBlock: [BraceBlock] { ... } +# 17| getParameter: [SimpleParameter] x +# 17| getDefiningAccess: [LocalVariableAccess] x +# 17| getStmt: [AddExpr] ... + ... +# 17| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 17| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 20| getStmt: [MethodCall] call to foo +# 20| getReceiver: [Self] self +# 20| getBlock: [DoBlock] do ... end +# 20| getParameter: [SimpleParameter] x +# 20| getDefiningAccess: [LocalVariableAccess] x +# 21| getStmt: [AddExpr] ... + ... +# 21| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 21| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 25| getStmt: [MethodCall] call to bar +# 25| getReceiver: [IntegerLiteral] 123 +# 25| getArgument: [StringLiteral] "foo" +# 25| getComponent: [StringTextComponent] foo +# 25| getBlock: [DoBlock] do ... end +# 25| getParameter: [SimpleParameter] x +# 25| getDefiningAccess: [LocalVariableAccess] x +# 26| getStmt: [AddExpr] ... + ... +# 26| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 26| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 30| getStmt: [Method] method_that_yields +# 31| getStmt: [YieldCall] yield ... +# 35| getStmt: [Method] another_method_that_yields +# 36| getStmt: [YieldCall] yield ... +# 36| getArgument: [IntegerLiteral] 100 +# 36| getArgument: [IntegerLiteral] 200 +# 46| getStmt: [MethodCall] call to foo +# 46| getReceiver: [Self] self +# 47| getStmt: [MethodCall] call to foo +# 47| getReceiver: [ConstantReadAccess] X +# 50| getStmt: [ParenthesizedExpr] ( ... ) +# 50| getStmt: [MethodCall] call to foo +# 50| getReceiver: [Self] self +# 51| getStmt: [ParenthesizedExpr] ( ... ) +# 51| getStmt: [MethodCall] call to foo +# 51| getReceiver: [ConstantReadAccess] X +# 54| getStmt: [MethodCall] call to some_func +# 54| getReceiver: [Self] self +# 54| getArgument: [MethodCall] call to foo +# 54| getReceiver: [Self] self +# 55| getStmt: [MethodCall] call to some_func +# 55| getReceiver: [Self] self +# 55| getArgument: [MethodCall] call to foo +# 55| getReceiver: [ConstantReadAccess] X +# 58| getStmt: [ArrayLiteral] [...] +# 58| getElement: [MethodCall] call to foo +# 58| getReceiver: [Self] self +# 59| getStmt: [ArrayLiteral] [...] +# 59| getElement: [MethodCall] call to foo +# 59| getReceiver: [ConstantReadAccess] X +# 62| getStmt: [AssignExpr] ... = ... +# 62| getAnOperand/getLeftOperand: [LocalVariableAccess] var1 +# 62| getAnOperand/getRightOperand: [MethodCall] call to foo +# 62| getReceiver: [Self] self +# 63| getStmt: [AssignExpr] ... = ... +# 63| getAnOperand/getLeftOperand: [LocalVariableAccess] var1 +# 63| getAnOperand/getRightOperand: [MethodCall] call to foo +# 63| getReceiver: [ConstantReadAccess] X +# 66| getStmt: [AssignAddExpr] ... += ... +# 66| getAnOperand/getLeftOperand: [LocalVariableAccess] var1 +# 66| getAnOperand/getRightOperand: [MethodCall] call to bar +# 66| getReceiver: [Self] self +# 67| getStmt: [AssignAddExpr] ... += ... +# 67| getAnOperand/getLeftOperand: [LocalVariableAccess] var1 +# 67| getAnOperand/getRightOperand: [MethodCall] call to bar +# 67| getReceiver: [ConstantReadAccess] X +# 70| getStmt: [AssignExpr] ... = ... +# 70| getAnOperand/getLeftOperand: [LocalVariableAccess] var1 +# 70| getAnOperand/getRightOperand: [ArgumentList] ..., ... +# 70| getElement: [MethodCall] call to foo +# 70| getReceiver: [Self] self +# 70| getElement: [MethodCall] call to bar +# 70| getReceiver: [ConstantReadAccess] X +# 73| getStmt: [BeginExpr] begin ... +# 74| getStmt: [MethodCall] call to foo +# 74| getReceiver: [Self] self +# 75| getStmt: [MethodCall] call to foo +# 75| getReceiver: [ConstantReadAccess] X +# 79| getBeginBlock: [BeginBlock] BEGIN { ... } +# 79| getStmt: [MethodCall] call to foo +# 79| getReceiver: [Self] self +# 79| getStmt: [MethodCall] call to bar +# 79| getReceiver: [ConstantReadAccess] X +# 82| getStmt: [EndBlock] END { ... } +# 82| getStmt: [MethodCall] call to foo +# 82| getReceiver: [Self] self +# 82| getStmt: [MethodCall] call to bar +# 82| getReceiver: [ConstantReadAccess] X +# 85| getStmt: [AddExpr] ... + ... +# 85| getAnOperand/getLeftOperand/getReceiver: [MethodCall] call to foo +# 85| getReceiver: [Self] self +# 85| getAnOperand/getArgument/getRightOperand: [MethodCall] call to bar +# 85| getReceiver: [ConstantReadAccess] X +# 88| getStmt: [NotExpr] ! ... +# 88| getAnOperand/getOperand/getReceiver: [MethodCall] call to foo +# 88| getReceiver: [Self] self +# 89| getStmt: [ComplementExpr] ~ ... +# 89| getAnOperand/getOperand/getReceiver: [MethodCall] call to bar +# 89| getReceiver: [ConstantReadAccess] X +# 92| getStmt: [MethodCall] call to foo +# 92| getReceiver: [Self] self +# 92| getBlock: [BraceBlock] { ... } +# 92| getStmt: [MethodCall] call to bar +# 92| getReceiver: [Self] self +# 92| getStmt: [MethodCall] call to baz +# 92| getReceiver: [ConstantReadAccess] X +# 95| getStmt: [MethodCall] call to foo +# 95| getReceiver: [Self] self +# 95| getBlock: [DoBlock] do ... end +# 96| getStmt: [MethodCall] call to bar +# 96| getReceiver: [Self] self +# 97| getStmt: [MethodCall] call to baz +# 97| getReceiver: [ConstantReadAccess] X +# 101| getStmt: [MethodCall] call to bar +# 101| getReceiver: [MethodCall] call to foo +# 101| getReceiver: [Self] self +# 102| getStmt: [MethodCall] call to baz +# 102| getReceiver: [MethodCall] call to bar +# 102| getReceiver: [Self] self +# 106| getStmt: [CaseExpr] case ... +# 106| getValue: [MethodCall] call to foo +# 106| getReceiver: [Self] self +# 107| getBranch: [WhenExpr] when ... +# 107| getPattern: [MethodCall] call to bar +# 107| getReceiver: [Self] self +# 107| getBody: [StmtSequence] then ... +# 108| getStmt: [MethodCall] call to baz +# 108| getReceiver: [Self] self +# 110| getStmt: [CaseExpr] case ... +# 110| getValue: [MethodCall] call to foo +# 110| getReceiver: [ConstantReadAccess] X +# 111| getBranch: [WhenExpr] when ... +# 111| getPattern: [MethodCall] call to bar +# 111| getReceiver: [ConstantReadAccess] X +# 111| getBody: [StmtSequence] then ... +# 112| getStmt: [MethodCall] call to baz +# 112| getReceiver: [ConstantReadAccess] X +# 116| getStmt: [ClassDeclaration] MyClass +# 117| getStmt: [MethodCall] call to foo +# 117| getReceiver: [Self] self +# 118| getStmt: [MethodCall] call to bar +# 118| getReceiver: [ConstantReadAccess] X +# 122| getStmt: [ClassDeclaration] MyClass +# 122| getSuperclassExpr: [MethodCall] call to foo +# 122| getReceiver: [Self] self +# 124| getStmt: [ClassDeclaration] MyClass2 +# 124| getSuperclassExpr: [MethodCall] call to foo +# 124| getReceiver: [ConstantReadAccess] X +# 128| getStmt: [ClassDeclaration] class << ... +# 128| getValue: [MethodCall] call to foo +# 128| getReceiver: [Self] self +# 129| getStmt: [MethodCall] call to bar +# 129| getReceiver: [Self] self +# 131| getStmt: [ClassDeclaration] class << ... +# 131| getValue: [MethodCall] call to foo +# 131| getReceiver: [ConstantReadAccess] X +# 132| getStmt: [MethodCall] call to bar +# 132| getReceiver: [ConstantReadAccess] X +# 136| getStmt: [Method] some_method +# 137| getStmt: [MethodCall] call to foo +# 137| getReceiver: [Self] self +# 138| getStmt: [MethodCall] call to bar +# 138| getReceiver: [ConstantReadAccess] X +# 142| getStmt: [SingletonMethod] some_method +# 142| getObject: [MethodCall] call to foo +# 142| getReceiver: [Self] self +# 143| getStmt: [MethodCall] call to bar +# 143| getReceiver: [Self] self +# 144| getStmt: [MethodCall] call to baz +# 144| getReceiver: [ConstantReadAccess] X +# 148| getStmt: [Method] method_with_keyword_param +# 148| getParameter: [KeywordParameter] keyword +# 148| getDefiningAccess: [LocalVariableAccess] keyword +# 148| getDefaultValue: [MethodCall] call to foo +# 148| getReceiver: [Self] self +# 150| getStmt: [Method] method_with_keyword_param2 +# 150| getParameter: [KeywordParameter] keyword +# 150| getDefiningAccess: [LocalVariableAccess] keyword +# 150| getDefaultValue: [MethodCall] call to foo +# 150| getReceiver: [ConstantReadAccess] X +# 154| getStmt: [Method] method_with_optional_param +# 154| getParameter: [OptionalParameter] param +# 154| getDefiningAccess: [LocalVariableAccess] param +# 154| getDefaultValue: [MethodCall] call to foo +# 154| getReceiver: [Self] self +# 156| getStmt: [Method] method_with_optional_param2 +# 156| getParameter: [OptionalParameter] param +# 156| getDefiningAccess: [LocalVariableAccess] param +# 156| getDefaultValue: [MethodCall] call to foo +# 156| getReceiver: [ConstantReadAccess] X +# 160| getStmt: [ModuleDeclaration] SomeModule +# 161| getStmt: [MethodCall] call to foo +# 161| getReceiver: [Self] self +# 162| getStmt: [MethodCall] call to bar +# 162| getReceiver: [ConstantReadAccess] X +# 166| getStmt: [TernaryIfExpr] ... ? ... : ... +# 166| getCondition: [MethodCall] call to foo +# 166| getReceiver: [Self] self +# 166| getBranch/getThen: [MethodCall] call to bar +# 166| getReceiver: [Self] self +# 166| getBranch/getElse: [MethodCall] call to baz +# 166| getReceiver: [Self] self +# 167| getStmt: [TernaryIfExpr] ... ? ... : ... +# 167| getCondition: [MethodCall] call to foo +# 167| getReceiver: [ConstantReadAccess] X +# 167| getBranch/getThen: [MethodCall] call to bar +# 167| getReceiver: [ConstantReadAccess] X +# 167| getBranch/getElse: [MethodCall] call to baz +# 167| getReceiver: [ConstantReadAccess] X +# 170| getStmt: [IfExpr] if ... +# 170| getCondition: [MethodCall] call to foo +# 170| getReceiver: [Self] self +# 170| getBranch/getThen: [StmtSequence] then ... +# 171| getStmt: [MethodCall] call to wibble +# 171| getReceiver: [Self] self +# 172| getBranch/getElse: [IfExpr] elsif ... +# 172| getCondition: [MethodCall] call to bar +# 172| getReceiver: [Self] self +# 172| getBranch/getThen: [StmtSequence] then ... +# 173| getStmt: [MethodCall] call to wobble +# 173| getReceiver: [Self] self +# 174| getBranch/getElse: [StmtSequence] else ... +# 175| getStmt: [MethodCall] call to wabble +# 175| getReceiver: [Self] self +# 177| getStmt: [IfExpr] if ... +# 177| getCondition: [MethodCall] call to foo +# 177| getReceiver: [ConstantReadAccess] X +# 177| getBranch/getThen: [StmtSequence] then ... +# 178| getStmt: [MethodCall] call to wibble +# 178| getReceiver: [ConstantReadAccess] X +# 179| getBranch/getElse: [IfExpr] elsif ... +# 179| getCondition: [MethodCall] call to bar +# 179| getReceiver: [ConstantReadAccess] X +# 179| getBranch/getThen: [StmtSequence] then ... +# 180| getStmt: [MethodCall] call to wobble +# 180| getReceiver: [ConstantReadAccess] X +# 181| getBranch/getElse: [StmtSequence] else ... +# 182| getStmt: [MethodCall] call to wabble +# 182| getReceiver: [ConstantReadAccess] X +# 186| getStmt: [IfModifierExpr] ... if ... +# 186| getBody/getBranch: [MethodCall] call to bar +# 186| getReceiver: [Self] self +# 186| getCondition: [MethodCall] call to foo +# 186| getReceiver: [Self] self +# 187| getStmt: [IfModifierExpr] ... if ... +# 187| getBody/getBranch: [MethodCall] call to bar +# 187| getReceiver: [ConstantReadAccess] X +# 187| getCondition: [MethodCall] call to foo +# 187| getReceiver: [ConstantReadAccess] X +# 190| getStmt: [UnlessExpr] unless ... +# 190| getCondition: [MethodCall] call to foo +# 190| getReceiver: [Self] self +# 190| getBranch/getThen: [StmtSequence] then ... +# 191| getStmt: [MethodCall] call to bar +# 191| getReceiver: [Self] self +# 193| getStmt: [UnlessExpr] unless ... +# 193| getCondition: [MethodCall] call to foo +# 193| getReceiver: [ConstantReadAccess] X +# 193| getBranch/getThen: [StmtSequence] then ... +# 194| getStmt: [MethodCall] call to bar +# 194| getReceiver: [ConstantReadAccess] X +# 198| getStmt: [UnlessModifierExpr] ... unless ... +# 198| getBody/getBranch: [MethodCall] call to bar +# 198| getReceiver: [Self] self +# 198| getCondition: [MethodCall] call to foo +# 198| getReceiver: [Self] self +# 199| getStmt: [UnlessModifierExpr] ... unless ... +# 199| getBody/getBranch: [MethodCall] call to bar +# 199| getReceiver: [ConstantReadAccess] X +# 199| getCondition: [MethodCall] call to foo +# 199| getReceiver: [ConstantReadAccess] X +# 202| getStmt: [WhileExpr] while ... +# 202| getCondition: [MethodCall] call to foo +# 202| getReceiver: [Self] self +# 202| getBody: [StmtSequence] do ... +# 203| getStmt: [MethodCall] call to bar +# 203| getReceiver: [Self] self +# 205| getStmt: [WhileExpr] while ... +# 205| getCondition: [MethodCall] call to foo +# 205| getReceiver: [ConstantReadAccess] X +# 205| getBody: [StmtSequence] do ... +# 206| getStmt: [MethodCall] call to bar +# 206| getReceiver: [ConstantReadAccess] X +# 210| getStmt: [WhileModifierExpr] ... while ... +# 210| getBody: [MethodCall] call to bar +# 210| getReceiver: [Self] self +# 210| getCondition: [MethodCall] call to foo +# 210| getReceiver: [Self] self +# 211| getStmt: [WhileModifierExpr] ... while ... +# 211| getBody: [MethodCall] call to bar +# 211| getReceiver: [ConstantReadAccess] X +# 211| getCondition: [MethodCall] call to foo +# 211| getReceiver: [ConstantReadAccess] X +# 214| getStmt: [UntilExpr] until ... +# 214| getCondition: [MethodCall] call to foo +# 214| getReceiver: [Self] self +# 214| getBody: [StmtSequence] do ... +# 215| getStmt: [MethodCall] call to bar +# 215| getReceiver: [Self] self +# 217| getStmt: [UntilExpr] until ... +# 217| getCondition: [MethodCall] call to foo +# 217| getReceiver: [ConstantReadAccess] X +# 217| getBody: [StmtSequence] do ... +# 218| getStmt: [MethodCall] call to bar +# 218| getReceiver: [ConstantReadAccess] X +# 222| getStmt: [UntilModifierExpr] ... until ... +# 222| getBody: [MethodCall] call to bar +# 222| getReceiver: [Self] self +# 222| getCondition: [MethodCall] call to foo +# 222| getReceiver: [Self] self +# 223| getStmt: [UntilModifierExpr] ... until ... +# 223| getBody: [MethodCall] call to bar +# 223| getReceiver: [ConstantReadAccess] X +# 223| getCondition: [MethodCall] call to foo +# 223| getReceiver: [ConstantReadAccess] X +# 226| getStmt: [ForExpr] for ... in ... +# 226| getPattern: [LocalVariableAccess] x +# 226| : [???] In +# 226| getValue: [MethodCall] call to bar +# 226| getReceiver: [Self] self +# 226| getBody: [StmtSequence] do ... +# 227| getStmt: [MethodCall] call to baz +# 227| getReceiver: [Self] self +# 229| getStmt: [ForExpr] for ... in ... +# 229| getPattern: [LocalVariableAccess] x +# 229| : [???] In +# 229| getValue: [MethodCall] call to bar +# 229| getReceiver: [ConstantReadAccess] X +# 229| getBody: [StmtSequence] do ... +# 230| getStmt: [MethodCall] call to baz +# 230| getReceiver: [ConstantReadAccess] X +# 234| getStmt: [ElementReference] ...[...] +# 234| getReceiver: [MethodCall] call to foo +# 234| getReceiver: [Self] self +# 234| getArgument: [MethodCall] call to bar +# 234| getReceiver: [Self] self +# 235| getStmt: [ElementReference] ...[...] +# 235| getReceiver: [MethodCall] call to foo +# 235| getReceiver: [ConstantReadAccess] X +# 235| getArgument: [MethodCall] call to bar +# 235| getReceiver: [ConstantReadAccess] X +# 238| getStmt: [StringLiteral] "foo-#{...}-#{...}" +# 238| getComponent: [StringTextComponent] foo- +# 238| getComponent: [StringInterpolationComponent] #{...} +# 238| getStmt: [MethodCall] call to bar +# 238| getReceiver: [Self] self +# 238| getComponent: [StringTextComponent] - +# 238| getComponent: [StringInterpolationComponent] #{...} +# 238| getStmt: [MethodCall] call to baz +# 238| getReceiver: [ConstantReadAccess] X +# 241| getStmt: [ConstantReadAccess] Bar +# 241| getScopeExpr: [MethodCall] call to foo +# 241| getReceiver: [Self] self +# 242| getStmt: [ConstantReadAccess] Bar +# 242| getScopeExpr: [MethodCall] call to foo +# 242| getReceiver: [ConstantReadAccess] X +# 245| getStmt: [RangeLiteral] _ .. _ +# 245| getBegin: [MethodCall] call to foo +# 245| getReceiver: [Self] self +# 245| getEnd: [MethodCall] call to bar +# 245| getReceiver: [Self] self +# 246| getStmt: [RangeLiteral] _ .. _ +# 246| getBegin: [MethodCall] call to foo +# 246| getReceiver: [ConstantReadAccess] X +# 246| getEnd: [MethodCall] call to bar +# 246| getReceiver: [ConstantReadAccess] X +# 249| getStmt: [HashLiteral] {...} +# 249| getElement: [Pair] Pair +# 249| getKey: [MethodCall] call to foo +# 249| getReceiver: [Self] self +# 249| getValue: [MethodCall] call to bar +# 249| getReceiver: [Self] self +# 249| getElement: [Pair] Pair +# 249| getKey: [MethodCall] call to foo +# 249| getReceiver: [ConstantReadAccess] X +# 249| getValue: [MethodCall] call to bar +# 249| getReceiver: [ConstantReadAccess] X +# 252| getStmt: [BeginExpr] begin ... +# 253| getRescue: [RescueClause] rescue ... +# 253| getException: [MethodCall] call to foo +# 253| getReceiver: [Self] self +# 254| getEnsure: [StmtSequence] ensure ... +# 254| getStmt: [MethodCall] call to bar +# 254| getReceiver: [Self] self +# 256| getStmt: [BeginExpr] begin ... +# 257| getRescue: [RescueClause] rescue ... +# 257| getException: [MethodCall] call to foo +# 257| getReceiver: [ConstantReadAccess] X +# 258| getEnsure: [StmtSequence] ensure ... +# 258| getStmt: [MethodCall] call to bar +# 258| getReceiver: [ConstantReadAccess] X +# 262| getStmt: [RescueModifierExpr] ... rescue ... +# 262| getBody: [MethodCall] call to foo +# 262| getReceiver: [Self] self +# 262| getHandler: [MethodCall] call to bar +# 262| getReceiver: [Self] self +# 263| getStmt: [RescueModifierExpr] ... rescue ... +# 263| getBody: [MethodCall] call to foo +# 263| getReceiver: [ConstantReadAccess] X +# 263| getHandler: [MethodCall] call to bar +# 263| getReceiver: [ConstantReadAccess] X +# 266| getStmt: [MethodCall] call to foo +# 266| getReceiver: [Self] self +# 266| getArgument: [BlockArgument] &... +# 266| getValue: [MethodCall] call to bar +# 266| getReceiver: [Self] self +# 267| getStmt: [MethodCall] call to foo +# 267| getReceiver: [Self] self +# 267| getArgument: [BlockArgument] &... +# 267| getValue: [MethodCall] call to bar +# 267| getReceiver: [ConstantReadAccess] X +# 270| getStmt: [MethodCall] call to foo +# 270| getReceiver: [Self] self +# 270| getArgument: [SplatExpr] * ... +# 270| getAnOperand/getOperand/getReceiver: [MethodCall] call to bar +# 270| getReceiver: [Self] self +# 271| getStmt: [MethodCall] call to foo +# 271| getReceiver: [Self] self +# 271| getArgument: [SplatExpr] * ... +# 271| getAnOperand/getOperand/getReceiver: [MethodCall] call to bar +# 271| getReceiver: [ConstantReadAccess] X +# 274| getStmt: [MethodCall] call to foo +# 274| getReceiver: [Self] self +# 274| getArgument: [HashSplatExpr] ** ... +# 274| getAnOperand/getOperand/getReceiver: [MethodCall] call to bar +# 274| getReceiver: [Self] self +# 275| getStmt: [MethodCall] call to foo +# 275| getReceiver: [Self] self +# 275| getArgument: [HashSplatExpr] ** ... +# 275| getAnOperand/getOperand/getReceiver: [MethodCall] call to bar +# 275| getReceiver: [ConstantReadAccess] X +# 278| getStmt: [MethodCall] call to foo +# 278| getReceiver: [Self] self +# 278| getArgument: [Pair] Pair +# 278| getKey: [SymbolLiteral] :blah +# 278| getValue: [MethodCall] call to bar +# 278| getReceiver: [Self] self +# 279| getStmt: [MethodCall] call to foo +# 279| getReceiver: [Self] self +# 279| getArgument: [Pair] Pair +# 279| getKey: [SymbolLiteral] :blah +# 279| getValue: [MethodCall] call to bar +# 279| getReceiver: [ConstantReadAccess] X +# 284| getStmt: [ClassDeclaration] MyClass +# 285| getStmt: [Method] my_method +# 286| getStmt: [SuperCall] call to super +# 287| getStmt: [SuperCall] call to super +# 288| getStmt: [SuperCall] call to super +# 288| getArgument: [StringLiteral] "blah" +# 288| getComponent: [StringTextComponent] blah +# 289| getStmt: [SuperCall] call to super +# 289| getArgument: [IntegerLiteral] 1 +# 289| getArgument: [IntegerLiteral] 2 +# 289| getArgument: [IntegerLiteral] 3 +# 290| getStmt: [SuperCall] call to super +# 290| getBlock: [BraceBlock] { ... } +# 290| getParameter: [SimpleParameter] x +# 290| getDefiningAccess: [LocalVariableAccess] x +# 290| getStmt: [AddExpr] ... + ... +# 290| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 290| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 291| getStmt: [SuperCall] call to super +# 291| getBlock: [DoBlock] do ... end +# 291| getParameter: [SimpleParameter] x +# 291| getDefiningAccess: [LocalVariableAccess] x +# 291| getStmt: [MulExpr] ... * ... +# 291| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 291| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2 +# 292| getStmt: [SuperCall] call to super +# 292| getArgument: [IntegerLiteral] 4 +# 292| getArgument: [IntegerLiteral] 5 +# 292| getBlock: [BraceBlock] { ... } +# 292| getParameter: [SimpleParameter] x +# 292| getDefiningAccess: [LocalVariableAccess] x +# 292| getStmt: [AddExpr] ... + ... +# 292| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 292| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 100 +# 293| getStmt: [SuperCall] call to super +# 293| getArgument: [IntegerLiteral] 6 +# 293| getArgument: [IntegerLiteral] 7 +# 293| getBlock: [DoBlock] do ... end +# 293| getParameter: [SimpleParameter] x +# 293| getDefiningAccess: [LocalVariableAccess] x +# 293| getStmt: [AddExpr] ... + ... +# 293| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 293| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 200 +# 301| getStmt: [ClassDeclaration] AnotherClass +# 302| getStmt: [Method] another_method +# 303| getStmt: [MethodCall] call to super +# 303| getReceiver: [MethodCall] call to foo +# 303| getReceiver: [Self] self +# 304| getStmt: [MethodCall] call to super +# 304| getReceiver: [Self] self +# 305| getStmt: [MethodCall] call to super +# 305| getReceiver: [SuperCall] call to super +# 310| getStmt: [MethodCall] call to call +# 310| getReceiver: [MethodCall] call to foo +# 310| getReceiver: [Self] self +# 311| getStmt: [MethodCall] call to call +# 311| getReceiver: [MethodCall] call to foo +# 311| getReceiver: [Self] self +# 311| getArgument: [IntegerLiteral] 1 +# 314| getStmt: [AssignExpr] ... = ... +# 314| getAnOperand/getLeftOperand: [MethodCall] call to foo +# 314| getReceiver: [Self] self +# 314| getAnOperand/getRightOperand: [IntegerLiteral] 10 +# 315| getStmt: [AssignExpr] ... = ... +# 315| getAnOperand/getLeftOperand: [ElementReference] ...[...] +# 315| getReceiver: [MethodCall] call to foo +# 315| getReceiver: [Self] self +# 315| getArgument: [IntegerLiteral] 0 +# 315| getAnOperand/getRightOperand: [IntegerLiteral] 10 +# 316| getStmt: [AssignExpr] ... = ... +# 316| getLeftOperand: [TuplePattern] (..., ...) +# 316| getElement: [MethodCall] call to foo +# 316| getReceiver: [Self] self +# 316| getElement: [MethodCall] call to bar +# 316| getReceiver: [Self] self +# 316| getElement: [ElementReference] ...[...] +# 316| getReceiver: [MethodCall] call to foo +# 316| getReceiver: [Self] self +# 316| getArgument: [IntegerLiteral] 4 +# 316| getAnOperand/getRightOperand: [ArrayLiteral] [...] +# 316| getElement: [IntegerLiteral] 1 +# 316| getElement: [IntegerLiteral] 2 +# 316| getElement: [IntegerLiteral] 3 +# 316| getElement: [IntegerLiteral] 4 +# 317| getStmt: [AssignExpr] ... = ... +# 317| getLeftOperand: [TuplePattern] (..., ...) +# 317| getElement: [LocalVariableAccess] a +# 317| getElement: [ElementReference] ...[...] +# 317| getReceiver: [MethodCall] call to foo +# 317| getReceiver: [Self] self +# 317| getArgument: [IntegerLiteral] 5 +# 317| getAnOperand/getRightOperand: [ArrayLiteral] [...] +# 317| getElement: [IntegerLiteral] 1 +# 317| getElement: [IntegerLiteral] 2 +# 317| getElement: [IntegerLiteral] 3 +# 318| getStmt: [AssignAddExpr] ... += ... +# 318| getAnOperand/getLeftOperand: [MethodCall] call to count +# 318| getReceiver: [Self] self +# 318| getAnOperand/getRightOperand: [IntegerLiteral] 1 +# 319| getStmt: [AssignAddExpr] ... += ... +# 319| getAnOperand/getLeftOperand: [ElementReference] ...[...] +# 319| getReceiver: [MethodCall] call to foo +# 319| getReceiver: [Self] self +# 319| getArgument: [IntegerLiteral] 0 +# 319| getAnOperand/getRightOperand: [IntegerLiteral] 1 +# 320| getStmt: [AssignMulExpr] ... *= ... +# 320| getAnOperand/getLeftOperand: [ElementReference] ...[...] +# 320| getReceiver: [MethodCall] call to bar +# 320| getReceiver: [MethodCall] call to foo +# 320| getReceiver: [Self] self +# 320| getArgument: [IntegerLiteral] 0 +# 320| getArgument: [MethodCall] call to baz +# 320| getReceiver: [MethodCall] call to foo +# 320| getReceiver: [Self] self +# 320| getArgument: [AddExpr] ... + ... +# 320| getAnOperand/getLeftOperand/getReceiver: [MethodCall] call to boo +# 320| getReceiver: [MethodCall] call to foo +# 320| getReceiver: [Self] self +# 320| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 320| getAnOperand/getRightOperand: [IntegerLiteral] 2 +control/cases.rb: +# 1| [Toplevel] cases.rb +# 2| getStmt: [AssignExpr] ... = ... +# 2| getAnOperand/getLeftOperand: [LocalVariableAccess] a +# 2| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 3| getStmt: [AssignExpr] ... = ... +# 3| getAnOperand/getLeftOperand: [LocalVariableAccess] b +# 3| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 4| getStmt: [AssignExpr] ... = ... +# 4| getAnOperand/getLeftOperand: [LocalVariableAccess] c +# 4| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 5| getStmt: [AssignExpr] ... = ... +# 5| getAnOperand/getLeftOperand: [LocalVariableAccess] d +# 5| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 8| getStmt: [CaseExpr] case ... +# 8| getValue: [LocalVariableAccess] a +# 9| getBranch: [WhenExpr] when ... +# 9| getPattern: [LocalVariableAccess] b +# 9| getBody: [StmtSequence] then ... +# 10| getStmt: [IntegerLiteral] 100 +# 11| getBranch: [WhenExpr] when ... +# 11| getPattern: [LocalVariableAccess] c +# 11| getPattern: [LocalVariableAccess] d +# 11| getBody: [StmtSequence] then ... +# 12| getStmt: [IntegerLiteral] 200 +# 13| getBranch: [StmtSequence] else ... +# 14| getStmt: [IntegerLiteral] 300 +# 18| getStmt: [CaseExpr] case ... +# 19| getBranch: [WhenExpr] when ... +# 19| getPattern: [GTExpr] ... > ... +# 19| getAnOperand/getGreaterOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 19| getAnOperand/getArgument/getLesserOperand/getRightOperand: [LocalVariableAccess] b +# 19| getBody: [StmtSequence] then ... +# 19| getStmt: [IntegerLiteral] 10 +# 20| getBranch: [WhenExpr] when ... +# 20| getPattern: [EqExpr] ... == ... +# 20| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 20| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] b +# 20| getBody: [StmtSequence] then ... +# 20| getStmt: [IntegerLiteral] 20 +# 21| getBranch: [WhenExpr] when ... +# 21| getPattern: [LTExpr] ... < ... +# 21| getAnOperand/getLeftOperand/getLesserOperand/getReceiver: [LocalVariableAccess] a +# 21| getAnOperand/getArgument/getGreaterOperand/getRightOperand: [LocalVariableAccess] b +# 21| getBody: [StmtSequence] then ... +# 21| getStmt: [IntegerLiteral] 30 +modules/classes.rb: +# 2| [Toplevel] classes.rb +# 3| getStmt: [ClassDeclaration] Foo +# 7| getStmt: [ClassDeclaration] Bar +# 7| getSuperclassExpr: [ConstantReadAccess] BaseClass +# 11| getStmt: [ClassDeclaration] Baz +# 11| getSuperclassExpr: [MethodCall] call to superclass_for +# 11| getReceiver: [Self] self +# 11| getArgument: [SymbolLiteral] :baz +# 15| getStmt: [ModuleDeclaration] MyModule +# 16| getStmt: [ClassDeclaration] MyClass +# 16| getScopeExpr: [ConstantReadAccess] MyModule +# 20| getStmt: [ClassDeclaration] Wibble +# 21| getStmt: [Method] method_a +# 22| getStmt: [MethodCall] call to puts +# 22| getReceiver: [Self] self +# 22| getArgument: [StringLiteral] "a" +# 22| getComponent: [StringTextComponent] a +# 25| getStmt: [Method] method_b +# 26| getStmt: [MethodCall] call to puts +# 26| getReceiver: [Self] self +# 26| getArgument: [StringLiteral] "b" +# 26| getComponent: [StringTextComponent] b +# 29| getStmt: [MethodCall] call to some_method_call +# 29| getReceiver: [Self] self +# 30| getStmt: [AssignExpr] ... = ... +# 30| getAnOperand/getLeftOperand: [GlobalVariableAccess] $global_var +# 30| getAnOperand/getRightOperand: [IntegerLiteral] 123 +# 32| getStmt: [ClassDeclaration] ClassInWibble +# 35| getStmt: [ModuleDeclaration] ModuleInWibble +# 40| getStmt: [AssignExpr] ... = ... +# 40| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 40| getAnOperand/getRightOperand: [StringLiteral] "hello" +# 40| getComponent: [StringTextComponent] hello +# 41| getStmt: [ClassDeclaration] class << ... +# 41| getValue: [LocalVariableAccess] x +# 42| getStmt: [Method] length +# 43| getStmt: [MulExpr] ... * ... +# 43| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 100 +# 43| getAnOperand/getArgument/getRightOperand: [SuperCall] call to super +# 46| getStmt: [Method] wibble +# 47| getStmt: [MethodCall] call to puts +# 47| getReceiver: [Self] self +# 47| getArgument: [StringLiteral] "wibble" +# 47| getComponent: [StringTextComponent] wibble +# 50| getStmt: [MethodCall] call to another_method_call +# 50| getReceiver: [Self] self +# 51| getStmt: [AssignExpr] ... = ... +# 51| getAnOperand/getLeftOperand: [GlobalVariableAccess] $global_var2 +# 51| getAnOperand/getRightOperand: [IntegerLiteral] 456 +# 55| getStmt: [ClassDeclaration] MyClassInGlobalScope +control/conditionals.rb: +# 1| [Toplevel] conditionals.rb +# 2| getStmt: [AssignExpr] ... = ... +# 2| getAnOperand/getLeftOperand: [LocalVariableAccess] a +# 2| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 3| getStmt: [AssignExpr] ... = ... +# 3| getAnOperand/getLeftOperand: [LocalVariableAccess] b +# 3| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 4| getStmt: [AssignExpr] ... = ... +# 4| getAnOperand/getLeftOperand: [LocalVariableAccess] c +# 4| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 5| getStmt: [AssignExpr] ... = ... +# 5| getAnOperand/getLeftOperand: [LocalVariableAccess] d +# 5| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 6| getStmt: [AssignExpr] ... = ... +# 6| getAnOperand/getLeftOperand: [LocalVariableAccess] e +# 6| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 7| getStmt: [AssignExpr] ... = ... +# 7| getAnOperand/getLeftOperand: [LocalVariableAccess] f +# 7| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 10| getStmt: [IfExpr] if ... +# 10| getCondition: [GTExpr] ... > ... +# 10| getAnOperand/getGreaterOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 10| getAnOperand/getArgument/getLesserOperand/getRightOperand: [LocalVariableAccess] b +# 10| getBranch/getThen: [StmtSequence] then ... +# 11| getStmt: [LocalVariableAccess] c +# 15| getStmt: [IfExpr] if ... +# 15| getCondition: [EqExpr] ... == ... +# 15| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 15| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] b +# 15| getBranch/getThen: [StmtSequence] then ... +# 16| getStmt: [LocalVariableAccess] c +# 17| getBranch/getElse: [StmtSequence] else ... +# 18| getStmt: [LocalVariableAccess] d +# 22| getStmt: [IfExpr] if ... +# 22| getCondition: [EqExpr] ... == ... +# 22| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 22| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 0 +# 22| getBranch/getThen: [StmtSequence] then ... +# 23| getStmt: [LocalVariableAccess] c +# 24| getBranch/getElse: [IfExpr] elsif ... +# 24| getCondition: [EqExpr] ... == ... +# 24| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 24| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 24| getBranch/getThen: [StmtSequence] then ... +# 25| getStmt: [LocalVariableAccess] d +# 26| getBranch/getElse: [IfExpr] elsif ... +# 26| getCondition: [EqExpr] ... == ... +# 26| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 26| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2 +# 26| getBranch/getThen: [StmtSequence] then ... +# 27| getStmt: [LocalVariableAccess] e +# 28| getBranch/getElse: [StmtSequence] else ... +# 29| getStmt: [LocalVariableAccess] f +# 33| getStmt: [IfExpr] if ... +# 33| getCondition: [EqExpr] ... == ... +# 33| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 33| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 0 +# 33| getBranch/getThen: [StmtSequence] then ... +# 34| getStmt: [LocalVariableAccess] b +# 35| getBranch/getElse: [IfExpr] elsif ... +# 35| getCondition: [EqExpr] ... == ... +# 35| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 35| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 35| getBranch/getThen: [StmtSequence] then ... +# 36| getStmt: [LocalVariableAccess] c +# 40| getStmt: [UnlessExpr] unless ... +# 40| getCondition: [GTExpr] ... > ... +# 40| getAnOperand/getGreaterOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 40| getAnOperand/getArgument/getLesserOperand/getRightOperand: [LocalVariableAccess] b +# 40| getBranch/getThen: [StmtSequence] then ... +# 41| getStmt: [LocalVariableAccess] c +# 45| getStmt: [UnlessExpr] unless ... +# 45| getCondition: [EqExpr] ... == ... +# 45| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 45| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] b +# 45| getBranch/getThen: [StmtSequence] then ... +# 46| getStmt: [LocalVariableAccess] c +# 47| getBranch/getElse: [StmtSequence] else ... +# 48| getStmt: [LocalVariableAccess] d +# 52| getStmt: [IfModifierExpr] ... if ... +# 52| getBody/getBranch: [AssignExpr] ... = ... +# 52| getAnOperand/getLeftOperand: [LocalVariableAccess] a +# 52| getAnOperand/getRightOperand: [LocalVariableAccess] b +# 52| getCondition: [GTExpr] ... > ... +# 52| getAnOperand/getGreaterOperand/getLeftOperand/getReceiver: [LocalVariableAccess] c +# 52| getAnOperand/getArgument/getLesserOperand/getRightOperand: [LocalVariableAccess] d +# 55| getStmt: [UnlessModifierExpr] ... unless ... +# 55| getBody/getBranch: [AssignExpr] ... = ... +# 55| getAnOperand/getLeftOperand: [LocalVariableAccess] a +# 55| getAnOperand/getRightOperand: [LocalVariableAccess] b +# 55| getCondition: [LTExpr] ... < ... +# 55| getAnOperand/getLeftOperand/getLesserOperand/getReceiver: [LocalVariableAccess] c +# 55| getAnOperand/getArgument/getGreaterOperand/getRightOperand: [LocalVariableAccess] d +# 58| getStmt: [AssignExpr] ... = ... +# 58| getAnOperand/getLeftOperand: [LocalVariableAccess] a +# 58| getAnOperand/getRightOperand: [TernaryIfExpr] ... ? ... : ... +# 58| getCondition: [GTExpr] ... > ... +# 58| getAnOperand/getGreaterOperand/getLeftOperand/getReceiver: [LocalVariableAccess] b +# 58| getAnOperand/getArgument/getLesserOperand/getRightOperand: [LocalVariableAccess] c +# 58| getBranch/getThen: [AddExpr] ... + ... +# 58| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] d +# 58| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 58| getBranch/getElse: [SubExpr] ... - ... +# 58| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] e +# 58| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2 +# 61| getStmt: [IfExpr] if ... +# 61| getCondition: [GTExpr] ... > ... +# 61| getAnOperand/getGreaterOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 61| getAnOperand/getArgument/getLesserOperand/getRightOperand: [LocalVariableAccess] b +# 61| getBranch/getThen: [StmtSequence] then ... +# 62| getStmt: [LocalVariableAccess] c +# 63| getBranch/getElse: [StmtSequence] else ... +# 67| getStmt: [IfExpr] if ... +# 67| getCondition: [GTExpr] ... > ... +# 67| getAnOperand/getGreaterOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 67| getAnOperand/getArgument/getLesserOperand/getRightOperand: [LocalVariableAccess] b +# 67| getBranch/getThen: [StmtSequence] then ... +# 68| getBranch/getElse: [StmtSequence] else ... +# 69| getStmt: [LocalVariableAccess] c +constants/constants.rb: +# 1| [Toplevel] constants.rb +# 1| getStmt: [ModuleDeclaration] ModuleA +# 2| getStmt: [ClassDeclaration] ClassA +# 3| getStmt: [AssignExpr] ... = ... +# 3| getAnOperand/getLeftOperand: [ConstantAssignment] CONST_A +# 3| getAnOperand/getRightOperand: [StringLiteral] "const_a" +# 3| getComponent: [StringTextComponent] const_a +# 6| getStmt: [AssignExpr] ... = ... +# 6| getAnOperand/getLeftOperand: [ConstantAssignment] CONST_B +# 6| getAnOperand/getRightOperand: [StringLiteral] "const_b" +# 6| getComponent: [StringTextComponent] const_b +# 8| getStmt: [ModuleDeclaration] ModuleB +# 9| getStmt: [ClassDeclaration] ClassB +# 9| getSuperclassExpr: [ConstantReadAccess] Base +# 12| getStmt: [ClassDeclaration] ClassC +# 12| getSuperclassExpr: [ConstantReadAccess] Z +# 12| getScopeExpr: [ConstantReadAccess] Y +# 12| getScopeExpr: [ConstantReadAccess] X +# 17| getStmt: [AssignExpr] ... = ... +# 17| getAnOperand/getLeftOperand: [ConstantAssignment] GREETING +# 17| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 17| getAnOperand/getLeftOperand/getReceiver: [AddExpr] ... + ... +# 17| getAnOperand/getLeftOperand/getReceiver: [StringLiteral] "Hello" +# 17| getComponent: [StringTextComponent] Hello +# 17| getAnOperand/getArgument/getRightOperand: [ConstantReadAccess] CONST_A +# 17| getScopeExpr: [ConstantReadAccess] ClassA +# 17| getScopeExpr: [ConstantReadAccess] ModuleA +# 17| getAnOperand/getArgument/getRightOperand: [ConstantReadAccess] CONST_B +# 17| getScopeExpr: [ConstantReadAccess] ModuleA +# 19| getStmt: [Method] foo +# 20| getStmt: [AssignExpr] ... = ... +# 20| getAnOperand/getLeftOperand: [ConstantAssignment] Names +# 20| getAnOperand/getRightOperand: [ArrayLiteral] [...] +# 20| getElement: [StringLiteral] "Vera" +# 20| getComponent: [StringTextComponent] Vera +# 20| getElement: [StringLiteral] "Chuck" +# 20| getComponent: [StringTextComponent] Chuck +# 20| getElement: [StringLiteral] "Dave" +# 20| getComponent: [StringTextComponent] Dave +# 22| getStmt: [MethodCall] call to each +# 22| getReceiver: [ConstantReadAccess] Names +# 22| getBlock: [DoBlock] do ... end +# 22| getParameter: [SimpleParameter] name +# 22| getDefiningAccess: [LocalVariableAccess] name +# 23| getStmt: [MethodCall] call to puts +# 23| getReceiver: [Self] self +# 23| getArgument: [StringLiteral] "#{...} #{...}" +# 23| getComponent: [StringInterpolationComponent] #{...} +# 23| getStmt: [ConstantReadAccess] GREETING +# 23| getComponent: [StringTextComponent] +# 23| getComponent: [StringInterpolationComponent] #{...} +# 23| getStmt: [LocalVariableAccess] name +# 28| getStmt: [MethodCall] call to Array +# 28| getReceiver: [Self] self +# 28| getArgument: [StringLiteral] "foo" +# 28| getComponent: [StringTextComponent] foo +# 31| getStmt: [ClassDeclaration] ClassD +# 31| getScopeExpr: [ConstantReadAccess] ModuleA +# 31| getSuperclassExpr: [ConstantReadAccess] ClassA +# 31| getScopeExpr: [ConstantReadAccess] ModuleA +# 34| getStmt: [ModuleDeclaration] ModuleC +# 34| getScopeExpr: [ConstantReadAccess] ModuleA +# 37| getStmt: [AssignExpr] ... = ... +# 37| getAnOperand/getLeftOperand: [ConstantAssignment] MAX_SIZE +# 37| getScopeExpr: [ConstantReadAccess] ModuleB +# 37| getScopeExpr: [ConstantReadAccess] ModuleA +# 37| getAnOperand/getRightOperand: [IntegerLiteral] 1024 +# 39| getStmt: [MethodCall] call to puts +# 39| getReceiver: [Self] self +# 39| getArgument: [ConstantReadAccess] MAX_SIZE +# 39| getScopeExpr: [ConstantReadAccess] ModuleB +# 39| getScopeExpr: [ConstantReadAccess] ModuleA +# 41| getStmt: [MethodCall] call to puts +# 41| getReceiver: [Self] self +# 41| getArgument: [ConstantReadAccess] GREETING +# 42| getStmt: [MethodCall] call to puts +# 42| getReceiver: [Self] self +# 42| getArgument: [ConstantReadAccess] GREETING +literals/literals.rb: +# 1| [Toplevel] literals.rb +# 2| getStmt: [NilLiteral] nil +# 3| getStmt: [NilLiteral] NIL +# 4| getStmt: [BooleanLiteral] false +# 5| getStmt: [BooleanLiteral] FALSE +# 6| getStmt: [BooleanLiteral] true +# 7| getStmt: [BooleanLiteral] TRUE +# 10| getStmt: [IntegerLiteral] 1234 +# 11| getStmt: [IntegerLiteral] 5_678 +# 12| getStmt: [IntegerLiteral] 0 +# 13| getStmt: [IntegerLiteral] 0d900 +# 16| getStmt: [IntegerLiteral] 0x1234 +# 17| getStmt: [IntegerLiteral] 0xdeadbeef +# 18| getStmt: [IntegerLiteral] 0xF00D_face +# 21| getStmt: [IntegerLiteral] 0123 +# 22| getStmt: [IntegerLiteral] 0o234 +# 23| getStmt: [IntegerLiteral] 0O45_6 +# 26| getStmt: [IntegerLiteral] 0b10010100 +# 27| getStmt: [IntegerLiteral] 0B011_01101 +# 30| getStmt: [FloatLiteral] 12.34 +# 31| getStmt: [FloatLiteral] 1234e-2 +# 32| getStmt: [FloatLiteral] 1.234E1 +# 35| getStmt: [RationalLiteral] 23r +# 36| getStmt: [RationalLiteral] 9.85r +# 39| getStmt: [ComplexLiteral] 2i +# 46| getStmt: [StringLiteral] "" +# 47| getStmt: [StringLiteral] "" +# 48| getStmt: [StringLiteral] "hello" +# 48| getComponent: [StringTextComponent] hello +# 49| getStmt: [StringLiteral] "goodbye" +# 49| getComponent: [StringTextComponent] goodbye +# 50| getStmt: [StringLiteral] "string with escaped \" quote" +# 50| getComponent: [StringTextComponent] string with escaped +# 50| getComponent: [StringEscapeSequenceComponent] \" +# 50| getComponent: [StringTextComponent] quote +# 51| getStmt: [StringLiteral] "string with " quote" +# 51| getComponent: [StringTextComponent] string with " quote +# 52| getStmt: [StringLiteral] "foo bar baz" +# 52| getComponent: [StringTextComponent] foo bar baz +# 53| getStmt: [StringLiteral] "foo bar baz" +# 53| getComponent: [StringTextComponent] foo bar baz +# 54| getStmt: [StringLiteral] "foo ' bar " baz'" +# 54| getComponent: [StringTextComponent] foo ' bar " baz' +# 55| getStmt: [StringLiteral] "FOO ' BAR " BAZ'" +# 55| getComponent: [StringTextComponent] FOO ' BAR " BAZ' +# 56| getStmt: [StringLiteral] "foo\ bar" +# 56| getComponent: [StringTextComponent] foo\ bar +# 57| getStmt: [StringLiteral] "foo\ bar" +# 57| getComponent: [StringTextComponent] foo +# 57| getComponent: [StringEscapeSequenceComponent] \ +# 57| getComponent: [StringTextComponent] bar +# 58| getStmt: [StringLiteral] "2 + 2 = #{...}" +# 58| getComponent: [StringTextComponent] 2 + 2 = +# 58| getComponent: [StringInterpolationComponent] #{...} +# 58| getStmt: [AddExpr] ... + ... +# 58| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 2 +# 58| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2 +# 59| getStmt: [StringLiteral] "3 + 4 = #{...}" +# 59| getComponent: [StringTextComponent] 3 + 4 = +# 59| getComponent: [StringInterpolationComponent] #{...} +# 59| getStmt: [AddExpr] ... + ... +# 59| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 3 +# 59| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 4 +# 60| getStmt: [StringLiteral] "2 + 2 = #{ 2 + 2 }" +# 60| getComponent: [StringTextComponent] 2 + 2 = #{ 2 + 2 } +# 61| getStmt: [StringLiteral] "3 + 4 = #{ 3 + 4 }" +# 61| getComponent: [StringTextComponent] 3 + 4 = #{ 3 + 4 } +# 62| getStmt: [StringConcatenation] "..." "..." +# 62| getString: [StringLiteral] "foo" +# 62| getComponent: [StringTextComponent] foo +# 62| getString: [StringLiteral] "bar" +# 62| getComponent: [StringTextComponent] bar +# 62| getString: [StringLiteral] "baz" +# 62| getComponent: [StringTextComponent] baz +# 63| getStmt: [StringConcatenation] "..." "..." +# 63| getString: [StringLiteral] "foo" +# 63| getComponent: [StringTextComponent] foo +# 63| getString: [StringLiteral] "bar" +# 63| getComponent: [StringTextComponent] bar +# 63| getString: [StringLiteral] "baz" +# 63| getComponent: [StringTextComponent] baz +# 64| getStmt: [StringConcatenation] "..." "..." +# 64| getString: [StringLiteral] "foo" +# 64| getComponent: [StringTextComponent] foo +# 64| getString: [StringLiteral] "bar#{...}" +# 64| getComponent: [StringTextComponent] bar +# 64| getComponent: [StringInterpolationComponent] #{...} +# 64| getStmt: [MulExpr] ... * ... +# 64| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 1 +# 64| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 64| getString: [StringLiteral] "baz" +# 64| getComponent: [StringTextComponent] baz +# 65| getStmt: [StringLiteral] "foo #{...} qux" +# 65| getComponent: [StringTextComponent] foo +# 65| getComponent: [StringInterpolationComponent] #{...} +# 65| getStmt: [StringLiteral] "bar #{...} baz" +# 65| getComponent: [StringTextComponent] bar +# 65| getComponent: [StringInterpolationComponent] #{...} +# 65| getStmt: [AddExpr] ... + ... +# 65| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 2 +# 65| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 3 +# 65| getComponent: [StringTextComponent] baz +# 65| getComponent: [StringTextComponent] qux +# 66| getStmt: [StringLiteral] "foo #{...}" +# 66| getComponent: [StringTextComponent] foo +# 66| getComponent: [StringInterpolationComponent] #{...} +# 66| getStmt: [MethodCall] call to blah +# 66| getReceiver: [Self] self +# 66| getStmt: [AddExpr] ... + ... +# 66| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 1 +# 66| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 9 +# 69| getStmt: [CharacterLiteral] ?x +# 70| getStmt: [CharacterLiteral] ?\n +# 71| getStmt: [CharacterLiteral] ?\s +# 72| getStmt: [CharacterLiteral] ?\\ +# 73| getStmt: [CharacterLiteral] ?\u{58} +# 74| getStmt: [CharacterLiteral] ?\C-a +# 75| getStmt: [CharacterLiteral] ?\M-a +# 76| getStmt: [CharacterLiteral] ?\M-\C-a +# 77| getStmt: [CharacterLiteral] ?\C-\M-a +# 80| getStmt: [SymbolLiteral] :"" +# 81| getStmt: [SymbolLiteral] :hello +# 82| getStmt: [SymbolLiteral] :"foo bar" +# 82| getComponent: [StringTextComponent] foo bar +# 83| getStmt: [SymbolLiteral] :"bar baz" +# 83| getComponent: [StringTextComponent] bar baz +# 84| getStmt: [HashLiteral] {...} +# 84| getElement: [Pair] Pair +# 84| getKey: [SymbolLiteral] :foo +# 84| getValue: [StringLiteral] "bar" +# 84| getComponent: [StringTextComponent] bar +# 85| getStmt: [SymbolLiteral] :"wibble" +# 85| getComponent: [StringTextComponent] wibble +# 86| getStmt: [SymbolLiteral] :"wibble wobble" +# 86| getComponent: [StringTextComponent] wibble wobble +# 87| getStmt: [SymbolLiteral] :"foo_#{...}" +# 87| getComponent: [StringTextComponent] foo_ +# 87| getComponent: [StringInterpolationComponent] #{...} +# 87| getStmt: [AddExpr] ... + ... +# 87| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 2 +# 87| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2 +# 88| getStmt: [SymbolLiteral] :"foo_#{ 1 + 1 }" +# 88| getComponent: [StringTextComponent] foo_#{ 1 + 1 } +# 89| getStmt: [SymbolLiteral] :"foo_#{ 3 - 2 }" +# 89| getComponent: [StringTextComponent] foo_#{ 3 - 2 } +# 92| getStmt: [ArrayLiteral] [...] +# 93| getStmt: [ArrayLiteral] [...] +# 93| getElement: [IntegerLiteral] 1 +# 93| getElement: [IntegerLiteral] 2 +# 93| getElement: [IntegerLiteral] 3 +# 94| getStmt: [ArrayLiteral] [...] +# 94| getElement: [IntegerLiteral] 4 +# 94| getElement: [IntegerLiteral] 5 +# 94| getElement: [DivExpr] ... / ... +# 94| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 12 +# 94| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2 +# 95| getStmt: [ArrayLiteral] [...] +# 95| getElement: [IntegerLiteral] 7 +# 95| getElement: [ArrayLiteral] [...] +# 95| getElement: [IntegerLiteral] 8 +# 95| getElement: [IntegerLiteral] 9 +# 98| getStmt: [ArrayLiteral] %w(...) +# 99| getStmt: [ArrayLiteral] %w(...) +# 99| getElement: [StringLiteral] "foo" +# 99| getComponent: [StringTextComponent] foo +# 99| getElement: [StringLiteral] "bar" +# 99| getComponent: [StringTextComponent] bar +# 99| getElement: [StringLiteral] "baz" +# 99| getComponent: [StringTextComponent] baz +# 100| getStmt: [ArrayLiteral] %w(...) +# 100| getElement: [StringLiteral] "foo" +# 100| getComponent: [StringTextComponent] foo +# 100| getElement: [StringLiteral] "bar" +# 100| getComponent: [StringTextComponent] bar +# 100| getElement: [StringLiteral] "baz" +# 100| getComponent: [StringTextComponent] baz +# 101| getStmt: [ArrayLiteral] %w(...) +# 101| getElement: [StringLiteral] "foo" +# 101| getComponent: [StringTextComponent] foo +# 101| getElement: [StringLiteral] "bar#{...}" +# 101| getComponent: [StringTextComponent] bar +# 101| getComponent: [StringInterpolationComponent] #{...} +# 101| getStmt: [AddExpr] ... + ... +# 101| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 1 +# 101| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 101| getElement: [StringLiteral] "baz" +# 101| getComponent: [StringTextComponent] baz +# 102| getStmt: [ArrayLiteral] %w(...) +# 102| getElement: [StringLiteral] "foo" +# 102| getComponent: [StringTextComponent] foo +# 102| getElement: [StringLiteral] "bar#{1+1}" +# 102| getComponent: [StringTextComponent] bar#{1+1} +# 102| getElement: [StringLiteral] "baz" +# 102| getComponent: [StringTextComponent] baz +# 105| getStmt: [ArrayLiteral] %i(...) +# 106| getStmt: [ArrayLiteral] %i(...) +# 106| getElement: [SymbolLiteral] :"foo" +# 106| getComponent: [StringTextComponent] foo +# 106| getElement: [SymbolLiteral] :"bar" +# 106| getComponent: [StringTextComponent] bar +# 106| getElement: [SymbolLiteral] :"baz" +# 106| getComponent: [StringTextComponent] baz +# 107| getStmt: [ArrayLiteral] %i(...) +# 107| getElement: [SymbolLiteral] :"foo" +# 107| getComponent: [StringTextComponent] foo +# 107| getElement: [SymbolLiteral] :"bar" +# 107| getComponent: [StringTextComponent] bar +# 107| getElement: [SymbolLiteral] :"baz" +# 107| getComponent: [StringTextComponent] baz +# 108| getStmt: [ArrayLiteral] %i(...) +# 108| getElement: [SymbolLiteral] :"foo" +# 108| getComponent: [StringTextComponent] foo +# 108| getElement: [SymbolLiteral] :"bar#{...}" +# 108| getComponent: [StringTextComponent] bar +# 108| getComponent: [StringInterpolationComponent] #{...} +# 108| getStmt: [AddExpr] ... + ... +# 108| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 2 +# 108| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 4 +# 108| getElement: [SymbolLiteral] :"baz" +# 108| getComponent: [StringTextComponent] baz +# 109| getStmt: [ArrayLiteral] %i(...) +# 109| getElement: [SymbolLiteral] :"foo" +# 109| getComponent: [StringTextComponent] foo +# 109| getElement: [SymbolLiteral] :"bar#{" +# 109| getComponent: [StringTextComponent] bar#{ +# 109| getElement: [SymbolLiteral] :"2" +# 109| getComponent: [StringTextComponent] 2 +# 109| getElement: [SymbolLiteral] :"+" +# 109| getComponent: [StringTextComponent] + +# 109| getElement: [SymbolLiteral] :"4" +# 109| getComponent: [StringTextComponent] 4 +# 109| getElement: [SymbolLiteral] :"}" +# 109| getComponent: [StringTextComponent] } +# 109| getElement: [SymbolLiteral] :"baz" +# 109| getComponent: [StringTextComponent] baz +# 112| getStmt: [HashLiteral] {...} +# 113| getStmt: [HashLiteral] {...} +# 113| getElement: [Pair] Pair +# 113| getKey: [SymbolLiteral] :foo +# 113| getValue: [IntegerLiteral] 1 +# 113| getElement: [Pair] Pair +# 113| getKey: [SymbolLiteral] :bar +# 113| getValue: [IntegerLiteral] 2 +# 113| getElement: [Pair] Pair +# 113| getKey: [StringLiteral] "baz" +# 113| getComponent: [StringTextComponent] baz +# 113| getValue: [IntegerLiteral] 3 +# 114| getStmt: [HashLiteral] {...} +# 114| getElement: [Pair] Pair +# 114| getKey: [SymbolLiteral] :foo +# 114| getValue: [IntegerLiteral] 7 +# 114| getElement: [HashSplatExpr] ** ... +# 114| getAnOperand/getOperand/getReceiver: [MethodCall] call to bar +# 114| getReceiver: [Self] self +# 117| getStmt: [ParenthesizedExpr] ( ... ) +# 117| getStmt: [RangeLiteral] _ .. _ +# 117| getBegin: [IntegerLiteral] 1 +# 117| getEnd: [IntegerLiteral] 10 +# 118| getStmt: [ParenthesizedExpr] ( ... ) +# 118| getStmt: [RangeLiteral] _ ... _ +# 118| getBegin: [IntegerLiteral] 1 +# 118| getEnd: [IntegerLiteral] 10 +# 119| getStmt: [ParenthesizedExpr] ( ... ) +# 119| getStmt: [RangeLiteral] _ .. _ +# 119| getBegin: [IntegerLiteral] 1 +# 119| getEnd: [IntegerLiteral] 0 +# 120| getStmt: [ParenthesizedExpr] ( ... ) +# 120| getStmt: [RangeLiteral] _ .. _ +# 120| getBegin: [MethodCall] call to start +# 120| getReceiver: [Self] self +# 120| getEnd: [AddExpr] ... + ... +# 120| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 2 +# 120| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 3 +# 121| getStmt: [ParenthesizedExpr] ( ... ) +# 121| getStmt: [RangeLiteral] _ .. _ +# 121| getBegin: [IntegerLiteral] 1 +# 122| getStmt: [ParenthesizedExpr] ( ... ) +# 122| getStmt: [RangeLiteral] _ .. _ +# 122| getEnd: [IntegerLiteral] 1 +# 123| getStmt: [ParenthesizedExpr] ( ... ) +# 123| getStmt: [SubExpr] ... - ... +# 123| getAnOperand/getLeftOperand/getReceiver: [RangeLiteral] _ .. _ +# 123| getBegin: [IntegerLiteral] 0 +# 123| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 126| getStmt: [SubshellLiteral] `ls -l` +# 126| getComponent: [StringTextComponent] ls -l +# 127| getStmt: [SubshellLiteral] `ls -l` +# 127| getComponent: [StringTextComponent] ls -l +# 128| getStmt: [SubshellLiteral] `du -d #{...}` +# 128| getComponent: [StringTextComponent] du -d +# 128| getComponent: [StringInterpolationComponent] #{...} +# 128| getStmt: [AddExpr] ... + ... +# 128| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 1 +# 128| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 129| getStmt: [SubshellLiteral] `du -d #{...}` +# 129| getComponent: [StringTextComponent] du -d +# 129| getComponent: [StringInterpolationComponent] #{...} +# 129| getStmt: [SubExpr] ... - ... +# 129| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 5 +# 129| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 4 +# 132| getStmt: [RegExpLiteral] // +# 133| getStmt: [RegExpLiteral] /foo/ +# 133| getParsed: [RegExpSequence] foo +# 133| 0: [RegExpConstant, RegExpNormalChar] f +# 133| 1: [RegExpConstant, RegExpNormalChar] o +# 133| 2: [RegExpConstant, RegExpNormalChar] o +# 133| getComponent: [StringTextComponent] foo +# 134| getStmt: [RegExpLiteral] /foo/ +# 134| getParsed: [RegExpSequence] foo +# 134| 0: [RegExpConstant, RegExpNormalChar] f +# 134| 1: [RegExpConstant, RegExpNormalChar] o +# 134| 2: [RegExpConstant, RegExpNormalChar] o +# 134| getComponent: [StringTextComponent] foo +# 135| getStmt: [RegExpLiteral] /foo+\sbar\S/ +# 135| getParsed: [RegExpSequence] foo+\sbar\S +# 135| 0: [RegExpConstant, RegExpNormalChar] f +# 135| 1: [RegExpConstant, RegExpNormalChar] o +# 135| 2: [RegExpPlus] o+ +# 135| 0: [RegExpConstant, RegExpNormalChar] o +# 135| 3: [RegExpCharacterClassEscape] \s +# 135| 4: [RegExpConstant, RegExpNormalChar] b +# 135| 5: [RegExpConstant, RegExpNormalChar] a +# 135| 6: [RegExpConstant, RegExpNormalChar] r +# 135| 7: [RegExpCharacterClassEscape] \S +# 135| getComponent: [StringTextComponent] foo+ +# 135| getComponent: [StringEscapeSequenceComponent] \s +# 135| getComponent: [StringTextComponent] bar +# 135| getComponent: [StringEscapeSequenceComponent] \S +# 136| getStmt: [RegExpLiteral] /foo#{...}bar/ +# 136| getComponent: [StringTextComponent] foo +# 136| getComponent: [StringInterpolationComponent] #{...} +# 136| getStmt: [AddExpr] ... + ... +# 136| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 1 +# 136| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 136| getComponent: [StringTextComponent] bar +# 137| getStmt: [RegExpLiteral] /foo/ +# 137| getParsed: [RegExpSequence] foo +# 137| 0: [RegExpConstant, RegExpNormalChar] f +# 137| 1: [RegExpConstant, RegExpNormalChar] o +# 137| 2: [RegExpConstant, RegExpNormalChar] o +# 137| getComponent: [StringTextComponent] foo +# 138| getStmt: [RegExpLiteral] // +# 139| getStmt: [RegExpLiteral] /foo/ +# 139| getParsed: [RegExpSequence] foo +# 139| 0: [RegExpConstant, RegExpNormalChar] f +# 139| 1: [RegExpConstant, RegExpNormalChar] o +# 139| 2: [RegExpConstant, RegExpNormalChar] o +# 139| getComponent: [StringTextComponent] foo +# 140| getStmt: [RegExpLiteral] /foo/ +# 140| getParsed: [RegExpSequence] foo +# 140| 0: [RegExpConstant, RegExpNormalChar] f +# 140| 1: [RegExpConstant, RegExpNormalChar] o +# 140| 2: [RegExpConstant, RegExpNormalChar] o +# 140| getComponent: [StringTextComponent] foo +# 141| getStmt: [RegExpLiteral] /foo+\sbar\S/ +# 141| getParsed: [RegExpSequence] foo+\sbar\S +# 141| 0: [RegExpConstant, RegExpNormalChar] f +# 141| 1: [RegExpConstant, RegExpNormalChar] o +# 141| 2: [RegExpPlus] o+ +# 141| 0: [RegExpConstant, RegExpNormalChar] o +# 141| 3: [RegExpCharacterClassEscape] \s +# 141| 4: [RegExpConstant, RegExpNormalChar] b +# 141| 5: [RegExpConstant, RegExpNormalChar] a +# 141| 6: [RegExpConstant, RegExpNormalChar] r +# 141| 7: [RegExpCharacterClassEscape] \S +# 141| getComponent: [StringTextComponent] foo+ +# 141| getComponent: [StringEscapeSequenceComponent] \s +# 141| getComponent: [StringTextComponent] bar +# 141| getComponent: [StringEscapeSequenceComponent] \S +# 142| getStmt: [RegExpLiteral] /foo#{...}bar/ +# 142| getComponent: [StringTextComponent] foo +# 142| getComponent: [StringInterpolationComponent] #{...} +# 142| getStmt: [AddExpr] ... + ... +# 142| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 1 +# 142| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 142| getComponent: [StringTextComponent] bar +# 143| getStmt: [RegExpLiteral] /foo/ +# 143| getParsed: [RegExpSequence] foo +# 143| 0: [RegExpConstant, RegExpNormalChar] f +# 143| 1: [RegExpConstant, RegExpNormalChar] o +# 143| 2: [RegExpConstant, RegExpNormalChar] o +# 143| getComponent: [StringTextComponent] foo +# 146| getStmt: [StringLiteral] "abcdefghijklmnopqrstuvwxyzabcdef" +# 146| getComponent: [StringTextComponent] abcdefghijklmnopqrstuvwxyzabcdef +# 147| getStmt: [StringLiteral] "foobarfoobarfoobarfoobarfooba..." +# 147| getComponent: [StringTextComponent] foobarfoobarfoobarfoobarfoobarfoo +# 148| getStmt: [StringLiteral] "foobar\\foobar\\foobar\\fooba..." +# 148| getComponent: [StringTextComponent] foobar +# 148| getComponent: [StringEscapeSequenceComponent] \\ +# 148| getComponent: [StringTextComponent] foobar +# 148| getComponent: [StringEscapeSequenceComponent] \\ +# 148| getComponent: [StringTextComponent] foobar +# 148| getComponent: [StringEscapeSequenceComponent] \\ +# 148| getComponent: [StringTextComponent] foobar +# 148| getComponent: [StringEscapeSequenceComponent] \\ +# 148| getComponent: [StringTextComponent] foobar +# 151| getStmt: [MethodCall] call to run_sql +# 151| getReceiver: [Self] self +# 151| getArgument: [HereDoc] <: [???] In +# 9| getValue: [RangeLiteral] _ .. _ +# 9| getBegin: [IntegerLiteral] 1 +# 9| getEnd: [IntegerLiteral] 10 +# 9| getBody: [StmtSequence] do ... +# 10| getStmt: [AssignAddExpr] ... += ... +# 10| getAnOperand/getLeftOperand: [LocalVariableAccess] sum +# 10| getAnOperand/getRightOperand: [LocalVariableAccess] n +# 11| getStmt: [AssignExpr] ... = ... +# 11| getAnOperand/getLeftOperand: [LocalVariableAccess] foo +# 11| getAnOperand/getRightOperand: [LocalVariableAccess] n +# 16| getStmt: [ForExpr] for ... in ... +# 16| getPattern: [LocalVariableAccess] n +# 16| : [???] In +# 16| getValue: [RangeLiteral] _ .. _ +# 16| getBegin: [IntegerLiteral] 1 +# 16| getEnd: [IntegerLiteral] 10 +# 16| getBody: [StmtSequence] do ... +# 17| getStmt: [AssignAddExpr] ... += ... +# 17| getAnOperand/getLeftOperand: [LocalVariableAccess] sum +# 17| getAnOperand/getRightOperand: [LocalVariableAccess] n +# 18| getStmt: [AssignSubExpr] ... -= ... +# 18| getAnOperand/getLeftOperand: [LocalVariableAccess] foo +# 18| getAnOperand/getRightOperand: [LocalVariableAccess] n +# 22| getStmt: [ForExpr] for ... in ... +# 22| getPattern: [TuplePattern] (..., ...) +# 22| getElement: [LocalVariableAccess] key +# 22| getElement: [LocalVariableAccess] value +# 22| : [???] In +# 22| getValue: [HashLiteral] {...} +# 22| getElement: [Pair] Pair +# 22| getKey: [SymbolLiteral] :foo +# 22| getValue: [IntegerLiteral] 0 +# 22| getElement: [Pair] Pair +# 22| getKey: [SymbolLiteral] :bar +# 22| getValue: [IntegerLiteral] 1 +# 22| getBody: [StmtSequence] do ... +# 23| getStmt: [AssignAddExpr] ... += ... +# 23| getAnOperand/getLeftOperand: [LocalVariableAccess] sum +# 23| getAnOperand/getRightOperand: [LocalVariableAccess] value +# 24| getStmt: [AssignMulExpr] ... *= ... +# 24| getAnOperand/getLeftOperand: [LocalVariableAccess] foo +# 24| getAnOperand/getRightOperand: [LocalVariableAccess] value +# 28| getStmt: [ForExpr] for ... in ... +# 28| getPattern: [TuplePattern] (..., ...) +# 28| getElement: [LocalVariableAccess] key +# 28| getElement: [LocalVariableAccess] value +# 28| : [???] In +# 28| getValue: [HashLiteral] {...} +# 28| getElement: [Pair] Pair +# 28| getKey: [SymbolLiteral] :foo +# 28| getValue: [IntegerLiteral] 0 +# 28| getElement: [Pair] Pair +# 28| getKey: [SymbolLiteral] :bar +# 28| getValue: [IntegerLiteral] 1 +# 28| getBody: [StmtSequence] do ... +# 29| getStmt: [AssignAddExpr] ... += ... +# 29| getAnOperand/getLeftOperand: [LocalVariableAccess] sum +# 29| getAnOperand/getRightOperand: [LocalVariableAccess] value +# 30| getStmt: [AssignDivExpr] ... /= ... +# 30| getAnOperand/getLeftOperand: [LocalVariableAccess] foo +# 30| getAnOperand/getRightOperand: [LocalVariableAccess] value +# 31| getStmt: [BreakStmt] break +# 35| getStmt: [WhileExpr] while ... +# 35| getCondition: [LTExpr] ... < ... +# 35| getAnOperand/getLeftOperand/getLesserOperand/getReceiver: [LocalVariableAccess] x +# 35| getAnOperand/getArgument/getGreaterOperand/getRightOperand: [LocalVariableAccess] y +# 35| getBody: [StmtSequence] do ... +# 36| getStmt: [AssignAddExpr] ... += ... +# 36| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 36| getAnOperand/getRightOperand: [IntegerLiteral] 1 +# 37| getStmt: [AssignAddExpr] ... += ... +# 37| getAnOperand/getLeftOperand: [LocalVariableAccess] z +# 37| getAnOperand/getRightOperand: [IntegerLiteral] 1 +# 38| getStmt: [NextStmt] next +# 42| getStmt: [WhileExpr] while ... +# 42| getCondition: [LTExpr] ... < ... +# 42| getAnOperand/getLeftOperand/getLesserOperand/getReceiver: [LocalVariableAccess] x +# 42| getAnOperand/getArgument/getGreaterOperand/getRightOperand: [LocalVariableAccess] y +# 42| getBody: [StmtSequence] do ... +# 43| getStmt: [AssignAddExpr] ... += ... +# 43| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 43| getAnOperand/getRightOperand: [IntegerLiteral] 1 +# 44| getStmt: [AssignAddExpr] ... += ... +# 44| getAnOperand/getLeftOperand: [LocalVariableAccess] z +# 44| getAnOperand/getRightOperand: [IntegerLiteral] 2 +# 48| getStmt: [WhileModifierExpr] ... while ... +# 48| getBody: [AssignAddExpr] ... += ... +# 48| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 48| getAnOperand/getRightOperand: [IntegerLiteral] 1 +# 48| getCondition: [GEExpr] ... >= ... +# 48| getAnOperand/getGreaterOperand/getLeftOperand/getReceiver: [LocalVariableAccess] y +# 48| getAnOperand/getArgument/getLesserOperand/getRightOperand: [LocalVariableAccess] x +# 51| getStmt: [UntilExpr] until ... +# 51| getCondition: [EqExpr] ... == ... +# 51| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 51| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] y +# 51| getBody: [StmtSequence] do ... +# 52| getStmt: [AssignAddExpr] ... += ... +# 52| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 52| getAnOperand/getRightOperand: [IntegerLiteral] 1 +# 53| getStmt: [AssignSubExpr] ... -= ... +# 53| getAnOperand/getLeftOperand: [LocalVariableAccess] z +# 53| getAnOperand/getRightOperand: [IntegerLiteral] 1 +# 57| getStmt: [UntilExpr] until ... +# 57| getCondition: [GTExpr] ... > ... +# 57| getAnOperand/getGreaterOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 57| getAnOperand/getArgument/getLesserOperand/getRightOperand: [LocalVariableAccess] y +# 57| getBody: [StmtSequence] do ... +# 58| getStmt: [AssignAddExpr] ... += ... +# 58| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 58| getAnOperand/getRightOperand: [IntegerLiteral] 1 +# 59| getStmt: [AssignSubExpr] ... -= ... +# 59| getAnOperand/getLeftOperand: [LocalVariableAccess] z +# 59| getAnOperand/getRightOperand: [IntegerLiteral] 4 +# 63| getStmt: [UntilModifierExpr] ... until ... +# 63| getBody: [AssignSubExpr] ... -= ... +# 63| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 63| getAnOperand/getRightOperand: [IntegerLiteral] 1 +# 63| getCondition: [EqExpr] ... == ... +# 63| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 63| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 0 +# 66| getStmt: [WhileExpr] while ... +# 66| getCondition: [LTExpr] ... < ... +# 66| getAnOperand/getLeftOperand/getLesserOperand/getReceiver: [LocalVariableAccess] x +# 66| getAnOperand/getArgument/getGreaterOperand/getRightOperand: [LocalVariableAccess] y +# 66| getBody: [StmtSequence] do ... +misc/misc.erb: +# 2| [Toplevel] misc.erb +# 2| getStmt: [MethodCall] call to require_asset +# 2| getReceiver: [Self] self +# 2| getArgument: [StringLiteral] "main_include_admin.js" +# 2| getComponent: [StringTextComponent] main_include_admin.js +misc/misc.rb: +# 1| [Toplevel] misc.rb +# 1| getStmt: [AssignExpr] ... = ... +# 1| getAnOperand/getLeftOperand: [LocalVariableAccess] bar +# 1| getAnOperand/getRightOperand: [StringLiteral] "bar" +# 1| getComponent: [StringTextComponent] bar +# 3| getStmt: [UndefStmt] undef ... +# 3| getMethodName: [MethodName] foo +# 3| getMethodName: [MethodName] :foo +# 3| getMethodName: [MethodName] foo= +# 3| getMethodName: [MethodName] [] +# 3| getMethodName: [MethodName] []= +# 4| getStmt: [UndefStmt] undef ... +# 4| getMethodName: [MethodName] :"foo_#{...}" +# 4| getComponent: [StringTextComponent] foo_ +# 4| getComponent: [StringInterpolationComponent] #{...} +# 4| getStmt: [LocalVariableAccess] bar +# 5| getStmt: [UndefStmt] undef ... +# 5| getMethodName: [MethodName] nil +# 5| getMethodName: [MethodName] true +# 5| getMethodName: [MethodName] false +# 5| getMethodName: [MethodName] super +# 5| getMethodName: [MethodName] self +# 7| getStmt: [AliasStmt] alias ... +# 7| getNewName: [MethodName] new +# 7| getOldName: [MethodName] :old +# 8| getStmt: [AliasStmt] alias ... +# 8| getNewName: [MethodName] foo= +# 8| getOldName: [MethodName] []= +# 9| getStmt: [AliasStmt] alias ... +# 9| getNewName: [MethodName] super +# 9| getOldName: [MethodName] self +# 10| getStmt: [AliasStmt] alias ... +# 10| getNewName: [MethodName] :"\n#{...}" +# 10| getComponent: [StringEscapeSequenceComponent] \n +# 10| getComponent: [StringInterpolationComponent] #{...} +# 10| getStmt: [LocalVariableAccess] bar +# 10| getOldName: [MethodName] :"foo" +# 10| getComponent: [StringTextComponent] foo +modules/modules.rb: +# 1| [Toplevel] modules.rb +# 1| getStmt: [ModuleDeclaration] Empty +# 4| getStmt: [ModuleDeclaration] Foo +# 5| getStmt: [ModuleDeclaration] Bar +# 6| getStmt: [ClassDeclaration] ClassInFooBar +# 9| getStmt: [Method] method_in_foo_bar +# 12| getStmt: [MethodCall] call to puts +# 12| getReceiver: [Self] self +# 12| getArgument: [StringLiteral] "module Foo::Bar" +# 12| getComponent: [StringTextComponent] module Foo::Bar +# 13| getStmt: [AssignExpr] ... = ... +# 13| getAnOperand/getLeftOperand: [GlobalVariableAccess] $global_var +# 13| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 16| getStmt: [Method] method_in_foo +# 19| getStmt: [ClassDeclaration] ClassInFoo +# 22| getStmt: [MethodCall] call to puts +# 22| getReceiver: [Self] self +# 22| getArgument: [StringLiteral] "module Foo" +# 22| getComponent: [StringTextComponent] module Foo +# 23| getStmt: [AssignExpr] ... = ... +# 23| getAnOperand/getLeftOperand: [GlobalVariableAccess] $global_var +# 23| getAnOperand/getRightOperand: [IntegerLiteral] 1 +# 26| getStmt: [ModuleDeclaration] Foo +# 27| getStmt: [Method] method_in_another_definition_of_foo +# 30| getStmt: [ClassDeclaration] ClassInAnotherDefinitionOfFoo +# 33| getStmt: [MethodCall] call to puts +# 33| getReceiver: [Self] self +# 33| getArgument: [StringLiteral] "module Foo again" +# 33| getComponent: [StringTextComponent] module Foo again +# 34| getStmt: [AssignExpr] ... = ... +# 34| getAnOperand/getLeftOperand: [GlobalVariableAccess] $global_var +# 34| getAnOperand/getRightOperand: [IntegerLiteral] 2 +# 37| getStmt: [ModuleDeclaration] Bar +# 38| getStmt: [Method] method_a +# 41| getStmt: [Method] method_b +# 44| getStmt: [MethodCall] call to puts +# 44| getReceiver: [Self] self +# 44| getArgument: [StringLiteral] "module Bar" +# 44| getComponent: [StringTextComponent] module Bar +# 45| getStmt: [AssignExpr] ... = ... +# 45| getAnOperand/getLeftOperand: [GlobalVariableAccess] $global_var +# 45| getAnOperand/getRightOperand: [IntegerLiteral] 3 +# 48| getStmt: [ModuleDeclaration] Bar +# 48| getScopeExpr: [ConstantReadAccess] Foo +# 49| getStmt: [ClassDeclaration] ClassInAnotherDefinitionOfFooBar +# 52| getStmt: [Method] method_in_another_definition_of_foo_bar +# 55| getStmt: [MethodCall] call to puts +# 55| getReceiver: [Self] self +# 55| getArgument: [StringLiteral] "module Foo::Bar again" +# 55| getComponent: [StringTextComponent] module Foo::Bar again +# 56| getStmt: [AssignExpr] ... = ... +# 56| getAnOperand/getLeftOperand: [GlobalVariableAccess] $global_var +# 56| getAnOperand/getRightOperand: [IntegerLiteral] 4 +# 60| getStmt: [ModuleDeclaration] MyModuleInGlobalScope +# 63| getStmt: [ModuleDeclaration] Test +# 65| getStmt: [ModuleDeclaration] Foo1 +# 66| getStmt: [ClassDeclaration] Bar +# 66| getScopeExpr: [ConstantReadAccess] Foo1 +# 70| getStmt: [ModuleDeclaration] Foo2 +# 71| getStmt: [ModuleDeclaration] Foo2 +# 72| getStmt: [ClassDeclaration] Bar +# 72| getScopeExpr: [ConstantReadAccess] Foo2 +# 76| getStmt: [ModuleDeclaration] Foo3 +# 77| getStmt: [AssignExpr] ... = ... +# 77| getAnOperand/getLeftOperand: [ConstantAssignment] Foo3 +# 77| getAnOperand/getRightOperand: [ConstantReadAccess] Object +# 78| getStmt: [ClassDeclaration] Bar +# 78| getScopeExpr: [ConstantReadAccess] Foo3 +# 83| getStmt: [ModuleDeclaration] Other +# 84| getStmt: [ModuleDeclaration] Foo1 +# 88| getStmt: [ModuleDeclaration] IncludeTest +# 89| getStmt: [MethodCall] call to include +# 89| getReceiver: [Self] self +# 89| getArgument: [ConstantReadAccess] Test +# 90| getStmt: [MethodCall] call to module_eval +# 90| getReceiver: [ConstantReadAccess] Object +# 90| getBlock: [BraceBlock] { ... } +# 90| getStmt: [MethodCall] call to prepend +# 90| getReceiver: [Self] self +# 90| getArgument: [ConstantReadAccess] Other +# 91| getStmt: [ModuleDeclaration] Y +# 91| getScopeExpr: [ConstantReadAccess] Foo1 +# 95| getStmt: [ModuleDeclaration] IncludeTest2 +# 96| getStmt: [MethodCall] call to include +# 96| getReceiver: [Self] self +# 96| getArgument: [ConstantReadAccess] Test +# 97| getStmt: [ModuleDeclaration] Z +# 97| getScopeExpr: [ConstantReadAccess] Foo1 +# 101| getStmt: [ModuleDeclaration] PrependTest +# 102| getStmt: [MethodCall] call to prepend +# 102| getReceiver: [Self] self +# 102| getArgument: [ConstantReadAccess] Test +# 103| getStmt: [ModuleDeclaration] Y +# 103| getScopeExpr: [ConstantReadAccess] Foo2 +# 107| getStmt: [ModuleDeclaration] MM +# 108| getStmt: [ModuleDeclaration] MM +# 108| getScopeExpr: [ConstantReadAccess] MM +# 112| getStmt: [ClassDeclaration] YY +# 115| getStmt: [ModuleDeclaration] XX +# 116| getStmt: [ClassDeclaration] YY +# 116| getSuperclassExpr: [ConstantReadAccess] YY +# 120| getStmt: [ModuleDeclaration] Baz +# 120| getScopeExpr: [ConstantReadAccess] Bar +# 120| getScopeExpr: [ConstantReadAccess] Foo1 +# 120| getScopeExpr: [ConstantReadAccess] Test +operations/operations.rb: +# 1| [Toplevel] operations.rb +# 3| getStmt: [AssignExpr] ... = ... +# 3| getAnOperand/getLeftOperand: [LocalVariableAccess] a +# 3| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 4| getStmt: [AssignExpr] ... = ... +# 4| getAnOperand/getLeftOperand: [LocalVariableAccess] b +# 4| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 5| getStmt: [AssignExpr] ... = ... +# 5| getAnOperand/getLeftOperand: [LocalVariableAccess] bar +# 5| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 6| getStmt: [AssignExpr] ... = ... +# 6| getAnOperand/getLeftOperand: [LocalVariableAccess] base +# 6| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 7| getStmt: [AssignExpr] ... = ... +# 7| getAnOperand/getLeftOperand: [LocalVariableAccess] baz +# 7| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 8| getStmt: [AssignExpr] ... = ... +# 8| getAnOperand/getLeftOperand: [LocalVariableAccess] foo +# 8| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 9| getStmt: [AssignExpr] ... = ... +# 9| getAnOperand/getLeftOperand: [LocalVariableAccess] handle +# 9| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 10| getStmt: [AssignExpr] ... = ... +# 10| getAnOperand/getLeftOperand: [LocalVariableAccess] m +# 10| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 11| getStmt: [AssignExpr] ... = ... +# 11| getAnOperand/getLeftOperand: [LocalVariableAccess] mask +# 11| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 12| getStmt: [AssignExpr] ... = ... +# 12| getAnOperand/getLeftOperand: [LocalVariableAccess] n +# 12| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 13| getStmt: [AssignExpr] ... = ... +# 13| getAnOperand/getLeftOperand: [LocalVariableAccess] name +# 13| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 14| getStmt: [AssignExpr] ... = ... +# 14| getAnOperand/getLeftOperand: [LocalVariableAccess] num +# 14| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 15| getStmt: [AssignExpr] ... = ... +# 15| getAnOperand/getLeftOperand: [LocalVariableAccess] power +# 15| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 16| getStmt: [AssignExpr] ... = ... +# 16| getAnOperand/getLeftOperand: [LocalVariableAccess] qux +# 16| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 17| getStmt: [AssignExpr] ... = ... +# 17| getAnOperand/getLeftOperand: [LocalVariableAccess] w +# 17| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 18| getStmt: [AssignExpr] ... = ... +# 18| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 18| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 19| getStmt: [AssignExpr] ... = ... +# 19| getAnOperand/getLeftOperand: [LocalVariableAccess] y +# 19| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 20| getStmt: [AssignExpr] ... = ... +# 20| getAnOperand/getLeftOperand: [LocalVariableAccess] z +# 20| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 23| getStmt: [NotExpr] ! ... +# 23| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] a +# 24| getStmt: [NotExpr] not ... +# 24| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] b +# 25| getStmt: [UnaryPlusExpr] + ... +# 25| getAnOperand/getOperand/getReceiver: [IntegerLiteral] 14 +# 26| getStmt: [UnaryMinusExpr] - ... +# 26| getAnOperand/getOperand/getReceiver: [IntegerLiteral] 7 +# 27| getStmt: [ComplementExpr] ~ ... +# 27| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] x +# 28| getStmt: [DefinedExpr] defined? ... +# 28| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] foo +# 29| getStmt: [Method] foo +# 29| getStmt: [ReturnStmt] return +# 29| getValue: [ArgumentList] ..., ... +# 29| getElement: [IntegerLiteral] 1 +# 29| getElement: [SplatExpr] * ... +# 29| getAnOperand/getOperand/getReceiver: [ArrayLiteral] [...] +# 29| getElement: [IntegerLiteral] 2 +# 29| getElement: [Pair] Pair +# 29| getKey: [SymbolLiteral] :a +# 29| getValue: [IntegerLiteral] 3 +# 29| getElement: [HashSplatExpr] ** ... +# 29| getAnOperand/getOperand/getReceiver: [HashLiteral] {...} +# 29| getElement: [Pair] Pair +# 29| getKey: [SymbolLiteral] :b +# 29| getValue: [IntegerLiteral] 4 +# 29| getElement: [Pair] Pair +# 29| getKey: [SymbolLiteral] :c +# 29| getValue: [IntegerLiteral] 5 +# 32| getStmt: [AddExpr] ... + ... +# 32| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] w +# 32| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 234 +# 33| getStmt: [SubExpr] ... - ... +# 33| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 33| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 17 +# 34| getStmt: [MulExpr] ... * ... +# 34| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] y +# 34| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 10 +# 35| getStmt: [DivExpr] ... / ... +# 35| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] z +# 35| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2 +# 36| getStmt: [ModuloExpr] ... % ... +# 36| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] num +# 36| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2 +# 37| getStmt: [ExponentExpr] ... ** ... +# 37| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] base +# 37| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] power +# 40| getStmt: [LogicalAndExpr] ... && ... +# 40| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] foo +# 40| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] bar +# 41| getStmt: [LogicalAndExpr] ... and ... +# 41| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] baz +# 41| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] qux +# 42| getStmt: [LogicalOrExpr] ... or ... +# 42| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 42| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] b +# 43| getStmt: [LogicalOrExpr] ... || ... +# 43| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 43| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] y +# 46| getStmt: [LShiftExpr] ... << ... +# 46| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 46| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 3 +# 47| getStmt: [RShiftExpr] ... >> ... +# 47| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] y +# 47| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 16 +# 48| getStmt: [BitwiseAndExpr] ... & ... +# 48| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] foo +# 48| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 0xff +# 49| getStmt: [BitwiseOrExpr] ... | ... +# 49| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] bar +# 49| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 0x02 +# 50| getStmt: [BitwiseXorExpr] ... ^ ... +# 50| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] baz +# 50| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] qux +# 53| getStmt: [EqExpr] ... == ... +# 53| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 53| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] y +# 54| getStmt: [NEExpr] ... != ... +# 54| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 54| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 123 +# 55| getStmt: [CaseEqExpr] ... === ... +# 55| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] m +# 55| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] n +# 58| getStmt: [GTExpr] ... > ... +# 58| getAnOperand/getGreaterOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 58| getAnOperand/getArgument/getLesserOperand/getRightOperand: [IntegerLiteral] 0 +# 59| getStmt: [GEExpr] ... >= ... +# 59| getAnOperand/getGreaterOperand/getLeftOperand/getReceiver: [LocalVariableAccess] y +# 59| getAnOperand/getArgument/getLesserOperand/getRightOperand: [IntegerLiteral] 100 +# 60| getStmt: [LTExpr] ... < ... +# 60| getAnOperand/getLeftOperand/getLesserOperand/getReceiver: [LocalVariableAccess] a +# 60| getAnOperand/getArgument/getGreaterOperand/getRightOperand: [LocalVariableAccess] b +# 61| getStmt: [LEExpr] ... <= ... +# 61| getAnOperand/getLeftOperand/getLesserOperand/getReceiver: [IntegerLiteral] 7 +# 61| getAnOperand/getArgument/getGreaterOperand/getRightOperand: [LocalVariableAccess] foo +# 64| getStmt: [SpaceshipExpr] ... <=> ... +# 64| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 64| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] b +# 65| getStmt: [RegExpMatchExpr] ... =~ ... +# 65| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] name +# 65| getAnOperand/getArgument/getRightOperand: [RegExpLiteral] /foo.*/ +# 65| getParsed: [RegExpSequence] foo.* +# 65| 0: [RegExpConstant, RegExpNormalChar] f +# 65| 1: [RegExpConstant, RegExpNormalChar] o +# 65| 2: [RegExpConstant, RegExpNormalChar] o +# 65| 3: [RegExpStar] .* +# 65| 0: [RegExpDot] . +# 65| getComponent: [StringTextComponent] foo.* +# 66| getStmt: [NoRegExpMatchExpr] ... !~ ... +# 66| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] handle +# 66| getAnOperand/getArgument/getRightOperand: [RegExpLiteral] /.*bar/ +# 66| getParsed: [RegExpSequence] .*bar +# 66| 0: [RegExpStar] .* +# 66| 0: [RegExpDot] . +# 66| 1: [RegExpConstant, RegExpNormalChar] b +# 66| 2: [RegExpConstant, RegExpNormalChar] a +# 66| 3: [RegExpConstant, RegExpNormalChar] r +# 66| getComponent: [StringTextComponent] .*bar +# 69| getStmt: [AssignAddExpr] ... += ... +# 69| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 69| getAnOperand/getRightOperand: [IntegerLiteral] 128 +# 70| getStmt: [AssignSubExpr] ... -= ... +# 70| getAnOperand/getLeftOperand: [LocalVariableAccess] y +# 70| getAnOperand/getRightOperand: [IntegerLiteral] 32 +# 71| getStmt: [AssignMulExpr] ... *= ... +# 71| getAnOperand/getLeftOperand: [LocalVariableAccess] a +# 71| getAnOperand/getRightOperand: [IntegerLiteral] 12 +# 72| getStmt: [AssignDivExpr] ... /= ... +# 72| getAnOperand/getLeftOperand: [LocalVariableAccess] b +# 72| getAnOperand/getRightOperand: [IntegerLiteral] 4 +# 73| getStmt: [AssignModuloExpr] ... %= ... +# 73| getAnOperand/getLeftOperand: [LocalVariableAccess] z +# 73| getAnOperand/getRightOperand: [IntegerLiteral] 2 +# 74| getStmt: [AssignExponentExpr] ... **= ... +# 74| getAnOperand/getLeftOperand: [LocalVariableAccess] foo +# 74| getAnOperand/getRightOperand: [LocalVariableAccess] bar +# 77| getStmt: [AssignLogicalAndExpr] ... &&= ... +# 77| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 77| getAnOperand/getRightOperand: [LocalVariableAccess] y +# 78| getStmt: [AssignLogicalOrExpr] ... ||= ... +# 78| getAnOperand/getLeftOperand: [LocalVariableAccess] a +# 78| getAnOperand/getRightOperand: [LocalVariableAccess] b +# 81| getStmt: [AssignLShiftExpr] ... <<= ... +# 81| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 81| getAnOperand/getRightOperand: [IntegerLiteral] 2 +# 82| getStmt: [AssignRShiftExpr] ... >>= ... +# 82| getAnOperand/getLeftOperand: [LocalVariableAccess] y +# 82| getAnOperand/getRightOperand: [IntegerLiteral] 3 +# 83| getStmt: [AssignBitwiseAndExpr] ... &= ... +# 83| getAnOperand/getLeftOperand: [LocalVariableAccess] foo +# 83| getAnOperand/getRightOperand: [LocalVariableAccess] mask +# 84| getStmt: [AssignBitwiseOrExpr] ... |= ... +# 84| getAnOperand/getLeftOperand: [LocalVariableAccess] bar +# 84| getAnOperand/getRightOperand: [IntegerLiteral] 0x01 +# 85| getStmt: [AssignBitwiseXorExpr] ... ^= ... +# 85| getAnOperand/getLeftOperand: [LocalVariableAccess] baz +# 85| getAnOperand/getRightOperand: [LocalVariableAccess] qux +# 87| getStmt: [ClassDeclaration] X +# 88| getStmt: [AssignExpr] ... = ... +# 88| getAnOperand/getLeftOperand: [InstanceVariableAccess] @x +# 88| getAnOperand/getRightOperand: [IntegerLiteral] 1 +# 89| getStmt: [AssignAddExpr] ... += ... +# 89| getAnOperand/getLeftOperand: [InstanceVariableAccess] @x +# 89| getAnOperand/getRightOperand: [IntegerLiteral] 2 +# 91| getStmt: [AssignExpr] ... = ... +# 91| getAnOperand/getLeftOperand: [ClassVariableAccess] @@y +# 91| getAnOperand/getRightOperand: [IntegerLiteral] 3 +# 92| getStmt: [AssignDivExpr] ... /= ... +# 92| getAnOperand/getLeftOperand: [ClassVariableAccess] @@y +# 92| getAnOperand/getRightOperand: [IntegerLiteral] 4 +# 95| getStmt: [AssignExpr] ... = ... +# 95| getAnOperand/getLeftOperand: [GlobalVariableAccess] $global_var +# 95| getAnOperand/getRightOperand: [IntegerLiteral] 5 +# 96| getStmt: [AssignMulExpr] ... *= ... +# 96| getAnOperand/getLeftOperand: [GlobalVariableAccess] $global_var +# 96| getAnOperand/getRightOperand: [IntegerLiteral] 6 +params/params.rb: +# 1| [Toplevel] params.rb +# 4| getStmt: [Method] identifier_method_params +# 4| getParameter: [SimpleParameter] foo +# 4| getDefiningAccess: [LocalVariableAccess] foo +# 4| getParameter: [SimpleParameter] bar +# 4| getDefiningAccess: [LocalVariableAccess] bar +# 4| getParameter: [SimpleParameter] baz +# 4| getDefiningAccess: [LocalVariableAccess] baz +# 8| getStmt: [AssignExpr] ... = ... +# 8| getAnOperand/getLeftOperand: [LocalVariableAccess] hash +# 8| getAnOperand/getRightOperand: [HashLiteral] {...} +# 9| getStmt: [MethodCall] call to each +# 9| getReceiver: [LocalVariableAccess] hash +# 9| getBlock: [DoBlock] do ... end +# 9| getParameter: [SimpleParameter] key +# 9| getDefiningAccess: [LocalVariableAccess] key +# 9| getParameter: [SimpleParameter] value +# 9| getDefiningAccess: [LocalVariableAccess] value +# 10| getStmt: [MethodCall] call to puts +# 10| getReceiver: [Self] self +# 10| getArgument: [StringLiteral] "#{...} -> #{...}" +# 10| getComponent: [StringInterpolationComponent] #{...} +# 10| getStmt: [LocalVariableAccess] key +# 10| getComponent: [StringTextComponent] -> +# 10| getComponent: [StringInterpolationComponent] #{...} +# 10| getStmt: [LocalVariableAccess] value +# 14| getStmt: [AssignExpr] ... = ... +# 14| getAnOperand/getLeftOperand: [LocalVariableAccess] sum +# 14| getAnOperand/getRightOperand: [Lambda] -> { ... } +# 14| getParameter: [SimpleParameter] foo +# 14| getDefiningAccess: [LocalVariableAccess] foo +# 14| getParameter: [SimpleParameter] bar +# 14| getDefiningAccess: [LocalVariableAccess] bar +# 14| getStmt: [AddExpr] ... + ... +# 14| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] foo +# 14| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] bar +# 17| getStmt: [Method] destructured_method_param +# 17| getParameter: [TuplePatternParameter] (..., ...) +# 17| getElement: [LocalVariableAccess] a +# 17| getElement: [LocalVariableAccess] b +# 17| getElement: [LocalVariableAccess] c +# 21| getStmt: [AssignExpr] ... = ... +# 21| getAnOperand/getLeftOperand: [LocalVariableAccess] array +# 21| getAnOperand/getRightOperand: [ArrayLiteral] [...] +# 22| getStmt: [MethodCall] call to each +# 22| getReceiver: [LocalVariableAccess] array +# 22| getBlock: [BraceBlock] { ... } +# 22| getParameter: [TuplePatternParameter] (..., ...) +# 22| getElement: [LocalVariableAccess] a +# 22| getElement: [LocalVariableAccess] b +# 22| getStmt: [MethodCall] call to puts +# 22| getReceiver: [Self] self +# 22| getArgument: [AddExpr] ... + ... +# 22| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 22| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] b +# 25| getStmt: [AssignExpr] ... = ... +# 25| getAnOperand/getLeftOperand: [LocalVariableAccess] sum_four_values +# 25| getAnOperand/getRightOperand: [Lambda] -> { ... } +# 25| getParameter: [TuplePatternParameter] (..., ...) +# 25| getElement: [LocalVariableAccess] first +# 25| getElement: [LocalVariableAccess] second +# 25| getParameter: [TuplePatternParameter] (..., ...) +# 25| getElement: [LocalVariableAccess] third +# 25| getElement: [LocalVariableAccess] fourth +# 26| getStmt: [AddExpr] ... + ... +# 26| getAnOperand/getLeftOperand/getReceiver: [AddExpr] ... + ... +# 26| getAnOperand/getLeftOperand/getReceiver: [AddExpr] ... + ... +# 26| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] first +# 26| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] second +# 26| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] third +# 26| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] fourth +# 30| getStmt: [Method] method_with_splat +# 30| getParameter: [SimpleParameter] wibble +# 30| getDefiningAccess: [LocalVariableAccess] wibble +# 30| getParameter: [SplatParameter] *splat +# 30| getDefiningAccess: [LocalVariableAccess] splat +# 30| getParameter: [HashSplatParameter] **double_splat +# 30| getDefiningAccess: [LocalVariableAccess] double_splat +# 34| getStmt: [MethodCall] call to each +# 34| getReceiver: [LocalVariableAccess] array +# 34| getBlock: [DoBlock] do ... end +# 34| getParameter: [SimpleParameter] val +# 34| getDefiningAccess: [LocalVariableAccess] val +# 34| getParameter: [SplatParameter] *splat +# 34| getDefiningAccess: [LocalVariableAccess] splat +# 34| getParameter: [HashSplatParameter] **double_splat +# 34| getDefiningAccess: [LocalVariableAccess] double_splat +# 38| getStmt: [AssignExpr] ... = ... +# 38| getAnOperand/getLeftOperand: [LocalVariableAccess] lambda_with_splats +# 38| getAnOperand/getRightOperand: [Lambda] -> { ... } +# 38| getParameter: [SimpleParameter] x +# 38| getDefiningAccess: [LocalVariableAccess] x +# 38| getParameter: [SplatParameter] *blah +# 38| getDefiningAccess: [LocalVariableAccess] blah +# 38| getParameter: [HashSplatParameter] **wibble +# 38| getDefiningAccess: [LocalVariableAccess] wibble +# 41| getStmt: [Method] method_with_keyword_params +# 41| getParameter: [SimpleParameter] x +# 41| getDefiningAccess: [LocalVariableAccess] x +# 41| getParameter: [KeywordParameter] foo +# 41| getDefiningAccess: [LocalVariableAccess] foo +# 41| getParameter: [KeywordParameter] bar +# 41| getDefiningAccess: [LocalVariableAccess] bar +# 41| getDefaultValue: [IntegerLiteral] 7 +# 42| getStmt: [AddExpr] ... + ... +# 42| getAnOperand/getLeftOperand/getReceiver: [AddExpr] ... + ... +# 42| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 42| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] foo +# 42| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] bar +# 46| getStmt: [Method] use_block_with_keyword +# 46| getParameter: [BlockParameter] &block +# 46| getDefiningAccess: [LocalVariableAccess] block +# 47| getStmt: [MethodCall] call to puts +# 47| getReceiver: [Self] self +# 47| getArgument: [MethodCall] call to call +# 47| getReceiver: [LocalVariableAccess] block +# 47| getArgument: [Pair] Pair +# 47| getKey: [SymbolLiteral] :bar +# 47| getValue: [IntegerLiteral] 2 +# 47| getArgument: [Pair] Pair +# 47| getKey: [SymbolLiteral] :foo +# 47| getValue: [IntegerLiteral] 3 +# 49| getStmt: [MethodCall] call to use_block_with_keyword +# 49| getReceiver: [Self] self +# 49| getBlock: [DoBlock] do ... end +# 49| getParameter: [KeywordParameter] xx +# 49| getDefiningAccess: [LocalVariableAccess] xx +# 49| getParameter: [KeywordParameter] yy +# 49| getDefiningAccess: [LocalVariableAccess] yy +# 49| getDefaultValue: [IntegerLiteral] 100 +# 50| getStmt: [AddExpr] ... + ... +# 50| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] xx +# 50| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] yy +# 53| getStmt: [AssignExpr] ... = ... +# 53| getAnOperand/getLeftOperand: [LocalVariableAccess] lambda_with_keyword_params +# 53| getAnOperand/getRightOperand: [Lambda] -> { ... } +# 53| getParameter: [SimpleParameter] x +# 53| getDefiningAccess: [LocalVariableAccess] x +# 53| getParameter: [KeywordParameter] y +# 53| getDefiningAccess: [LocalVariableAccess] y +# 53| getParameter: [KeywordParameter] z +# 53| getDefiningAccess: [LocalVariableAccess] z +# 53| getDefaultValue: [IntegerLiteral] 3 +# 54| getStmt: [AddExpr] ... + ... +# 54| getAnOperand/getLeftOperand/getReceiver: [AddExpr] ... + ... +# 54| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 54| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] y +# 54| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] z +# 58| getStmt: [Method] method_with_optional_params +# 58| getParameter: [SimpleParameter] val1 +# 58| getDefiningAccess: [LocalVariableAccess] val1 +# 58| getParameter: [OptionalParameter] val2 +# 58| getDefiningAccess: [LocalVariableAccess] val2 +# 58| getDefaultValue: [IntegerLiteral] 0 +# 58| getParameter: [OptionalParameter] val3 +# 58| getDefiningAccess: [LocalVariableAccess] val3 +# 58| getDefaultValue: [IntegerLiteral] 100 +# 62| getStmt: [Method] use_block_with_optional +# 62| getParameter: [BlockParameter] &block +# 62| getDefiningAccess: [LocalVariableAccess] block +# 63| getStmt: [MethodCall] call to call +# 63| getReceiver: [LocalVariableAccess] block +# 63| getArgument: [StringLiteral] "Zeus" +# 63| getComponent: [StringTextComponent] Zeus +# 65| getStmt: [MethodCall] call to use_block_with_optional +# 65| getReceiver: [Self] self +# 65| getBlock: [DoBlock] do ... end +# 65| getParameter: [SimpleParameter] name +# 65| getDefiningAccess: [LocalVariableAccess] name +# 65| getParameter: [OptionalParameter] age +# 65| getDefiningAccess: [LocalVariableAccess] age +# 65| getDefaultValue: [IntegerLiteral] 99 +# 66| getStmt: [MethodCall] call to puts +# 66| getReceiver: [Self] self +# 66| getArgument: [StringLiteral] "#{...} is #{...} years old" +# 66| getComponent: [StringInterpolationComponent] #{...} +# 66| getStmt: [LocalVariableAccess] name +# 66| getComponent: [StringTextComponent] is +# 66| getComponent: [StringInterpolationComponent] #{...} +# 66| getStmt: [LocalVariableAccess] age +# 66| getComponent: [StringTextComponent] years old +# 70| getStmt: [AssignExpr] ... = ... +# 70| getAnOperand/getLeftOperand: [LocalVariableAccess] lambda_with_optional_params +# 70| getAnOperand/getRightOperand: [Lambda] -> { ... } +# 70| getParameter: [SimpleParameter] a +# 70| getDefiningAccess: [LocalVariableAccess] a +# 70| getParameter: [OptionalParameter] b +# 70| getDefiningAccess: [LocalVariableAccess] b +# 70| getDefaultValue: [IntegerLiteral] 1000 +# 70| getParameter: [OptionalParameter] c +# 70| getDefiningAccess: [LocalVariableAccess] c +# 70| getDefaultValue: [IntegerLiteral] 20 +# 70| getStmt: [AddExpr] ... + ... +# 70| getAnOperand/getLeftOperand/getReceiver: [AddExpr] ... + ... +# 70| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 70| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] b +# 70| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] c +erb/template.html.erb: +# 19| [Toplevel] template.html.erb +# 19| getStmt: [StringLiteral] "hello world" +# 19| getComponent: [StringTextComponent] hello world +# 25| getStmt: [AssignExpr] ... = ... +# 25| getAnOperand/getLeftOperand: [LocalVariableAccess] xs +# 25| getAnOperand/getRightOperand: [StringLiteral] "" +# 27| getStmt: [ForExpr] for ... in ... +# 27| getPattern: [LocalVariableAccess] x +# 27| : [???] In +# 27| getValue: [ArrayLiteral] [...] +# 27| getElement: [StringLiteral] "foo" +# 27| getComponent: [StringTextComponent] foo +# 27| getElement: [StringLiteral] "bar" +# 27| getComponent: [StringTextComponent] bar +# 27| getElement: [StringLiteral] "baz" +# 27| getComponent: [StringTextComponent] baz +# 27| getBody: [StmtSequence] do ... +# 28| getStmt: [AssignAddExpr] ... += ... +# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] xs +# 28| getAnOperand/getRightOperand: [LocalVariableAccess] x +# 29| getStmt: [LocalVariableAccess] xs +gems/test.gemspec: +# 1| [Toplevel] test.gemspec +# 1| getStmt: [MethodCall] call to new +# 1| getReceiver: [ConstantReadAccess] Specification +# 1| getScopeExpr: [ConstantReadAccess] Gem +# 1| getBlock: [DoBlock] do ... end +# 1| getParameter: [SimpleParameter] s +# 1| getDefiningAccess: [LocalVariableAccess] s +# 2| getStmt: [AssignExpr] ... = ... +# 2| getAnOperand/getLeftOperand: [MethodCall] call to name +# 2| getReceiver: [LocalVariableAccess] s +# 2| getAnOperand/getRightOperand: [StringLiteral] "test" +# 2| getComponent: [StringTextComponent] test +# 3| getStmt: [AssignExpr] ... = ... +# 3| getAnOperand/getLeftOperand: [MethodCall] call to version +# 3| getReceiver: [LocalVariableAccess] s +# 3| getAnOperand/getRightOperand: [StringLiteral] "0.0.0" +# 3| getComponent: [StringTextComponent] 0.0.0 +# 4| getStmt: [AssignExpr] ... = ... +# 4| getAnOperand/getLeftOperand: [MethodCall] call to summary +# 4| getReceiver: [LocalVariableAccess] s +# 4| getAnOperand/getRightOperand: [StringLiteral] "foo!" +# 4| getComponent: [StringTextComponent] foo! +# 5| getStmt: [AssignExpr] ... = ... +# 5| getAnOperand/getLeftOperand: [MethodCall] call to description +# 5| getReceiver: [LocalVariableAccess] s +# 5| getAnOperand/getRightOperand: [StringLiteral] "A test" +# 5| getComponent: [StringTextComponent] A test +# 6| getStmt: [AssignExpr] ... = ... +# 6| getAnOperand/getLeftOperand: [MethodCall] call to authors +# 6| getReceiver: [LocalVariableAccess] s +# 6| getAnOperand/getRightOperand: [ArrayLiteral] [...] +# 6| getElement: [StringLiteral] "Mona Lisa" +# 6| getComponent: [StringTextComponent] Mona Lisa +# 7| getStmt: [AssignExpr] ... = ... +# 7| getAnOperand/getLeftOperand: [MethodCall] call to email +# 7| getReceiver: [LocalVariableAccess] s +# 7| getAnOperand/getRightOperand: [StringLiteral] "mona@example.com" +# 7| getComponent: [StringTextComponent] mona@example.com +# 8| getStmt: [AssignExpr] ... = ... +# 8| getAnOperand/getLeftOperand: [MethodCall] call to files +# 8| getReceiver: [LocalVariableAccess] s +# 8| getAnOperand/getRightOperand: [ArrayLiteral] [...] +# 8| getElement: [StringLiteral] "lib/test.rb" +# 8| getComponent: [StringTextComponent] lib/test.rb +# 9| getStmt: [AssignExpr] ... = ... +# 9| getAnOperand/getLeftOperand: [MethodCall] call to homepage +# 9| getReceiver: [LocalVariableAccess] s +# 9| getAnOperand/getRightOperand: [StringLiteral] "https://github.com/github/cod..." +# 9| getComponent: [StringTextComponent] https://github.com/github/codeql-ruby +gems/lib/test.rb: +# 1| [Toplevel] test.rb +# 1| getStmt: [ClassDeclaration] Foo +# 2| getStmt: [SingletonMethod] greet +# 2| getObject: [Self] self +# 3| getStmt: [MethodCall] call to puts +# 3| getReceiver: [Self] self +# 3| getArgument: [StringLiteral] "Hello" +# 3| getComponent: [StringTextComponent] Hello +modules/toplevel.rb: +# 1| [Toplevel] toplevel.rb +# 1| getStmt: [MethodCall] call to puts +# 1| getReceiver: [Self] self +# 1| getArgument: [StringLiteral] "world" +# 1| getComponent: [StringTextComponent] world +# 3| getStmt: [EndBlock] END { ... } +# 3| getStmt: [MethodCall] call to puts +# 3| getReceiver: [Self] self +# 3| getArgument: [StringLiteral] "!!!" +# 3| getComponent: [StringTextComponent] !!! +# 5| getBeginBlock: [BeginBlock] BEGIN { ... } +# 5| getStmt: [MethodCall] call to puts +# 5| getReceiver: [Self] self +# 5| getArgument: [StringLiteral] "hello" +# 5| getComponent: [StringTextComponent] hello diff --git a/ruby/ql/test/library-tests/ast/Ast.ql b/ruby/ql/test/library-tests/ast/Ast.ql new file mode 100644 index 000000000000..2e80254f0b08 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/Ast.ql @@ -0,0 +1,5 @@ +/** + * @kind graph + */ + +import codeql.ruby.printAst diff --git a/ruby/ql/test/library-tests/ast/AstDesugar.expected b/ruby/ql/test/library-tests/ast/AstDesugar.expected new file mode 100644 index 000000000000..c6a4bf898914 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/AstDesugar.expected @@ -0,0 +1,631 @@ +calls/calls.rb: +# 58| [ArrayLiteral] [...] +# 58| getDesugared: [MethodCall] call to [] +# 58| getReceiver: [ConstantReadAccess] Array +# 58| getArgument: [MethodCall] call to foo +# 58| getReceiver: [Self] self +# 59| [ArrayLiteral] [...] +# 59| getDesugared: [MethodCall] call to [] +# 59| getReceiver: [ConstantReadAccess] Array +# 59| getArgument: [MethodCall] call to foo +# 59| getReceiver: [ConstantReadAccess] X +# 66| [AssignAddExpr] ... += ... +# 66| getDesugared: [AssignExpr] ... = ... +# 66| getAnOperand/getLeftOperand: [LocalVariableAccess] var1 +# 66| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 66| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] var1 +# 66| getAnOperand/getArgument/getRightOperand: [MethodCall] call to bar +# 66| getReceiver: [Self] self +# 67| [AssignAddExpr] ... += ... +# 67| getDesugared: [AssignExpr] ... = ... +# 67| getAnOperand/getLeftOperand: [LocalVariableAccess] var1 +# 67| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 67| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] var1 +# 67| getAnOperand/getArgument/getRightOperand: [MethodCall] call to bar +# 67| getReceiver: [ConstantReadAccess] X +# 314| [AssignExpr] ... = ... +# 314| getDesugared: [StmtSequence] ... +# 314| getStmt: [SetterMethodCall] call to foo= +# 314| getReceiver: [Self] self +# 314| getArgument: [AssignExpr] ... = ... +# 314| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0 +# 314| getAnOperand/getRightOperand: [IntegerLiteral] 10 +# 314| getStmt: [LocalVariableAccess] __synth__0 +# 315| [AssignExpr] ... = ... +# 315| getDesugared: [StmtSequence] ... +# 315| getStmt: [SetterMethodCall] call to []= +# 315| getReceiver: [MethodCall] call to foo +# 315| getReceiver: [Self] self +# 315| getArgument: [AssignExpr] ... = ... +# 315| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0 +# 315| getAnOperand/getRightOperand: [IntegerLiteral] 10 +# 315| getArgument: [IntegerLiteral] 0 +# 315| getStmt: [LocalVariableAccess] __synth__0 +# 316| [AssignExpr] ... = ... +# 316| getDesugared: [StmtSequence] ... +# 316| getStmt: [AssignExpr] ... = ... +# 316| getAnOperand/getLeftOperand: [MethodCall] call to foo +# 316| getDesugared: [StmtSequence] ... +# 316| getStmt: [SetterMethodCall] call to foo= +# 316| getReceiver: [Self] self +# 316| getArgument: [AssignExpr] ... = ... +# 316| getAnOperand/getRightOperand: [MethodCall] call to [] +# 316| getArgument: [IntegerLiteral] 0 +# 316| getReceiver: [LocalVariableAccess] __synth__0 +# 316| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1 +# 316| getStmt: [LocalVariableAccess] __synth__0__1 +# 316| getStmt: [AssignExpr] ... = ... +# 316| getAnOperand/getLeftOperand: [MethodCall] call to bar +# 316| getDesugared: [StmtSequence] ... +# 316| getStmt: [SetterMethodCall] call to bar= +# 316| getReceiver: [Self] self +# 316| getArgument: [AssignExpr] ... = ... +# 316| getAnOperand/getRightOperand: [MethodCall] call to [] +# 316| getArgument: [RangeLiteral] _ .. _ +# 316| getBegin: [IntegerLiteral] 1 +# 316| getEnd: [IntegerLiteral] -2 +# 316| getReceiver: [LocalVariableAccess] __synth__0 +# 316| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1 +# 316| getStmt: [LocalVariableAccess] __synth__0__1 +# 316| getStmt: [AssignExpr] ... = ... +# 316| getAnOperand/getLeftOperand: [ElementReference] ...[...] +# 316| getDesugared: [StmtSequence] ... +# 316| getStmt: [SetterMethodCall] call to []= +# 316| getReceiver: [MethodCall] call to foo +# 316| getReceiver: [Self] self +# 316| getArgument: [AssignExpr] ... = ... +# 316| getAnOperand/getRightOperand: [MethodCall] call to [] +# 316| getArgument: [IntegerLiteral] -1 +# 316| getReceiver: [LocalVariableAccess] __synth__0 +# 316| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1 +# 316| getArgument: [IntegerLiteral] 4 +# 316| getStmt: [LocalVariableAccess] __synth__0__1 +# 316| getStmt: [AssignExpr] ... = ... +# 316| getAnOperand/getRightOperand: [SplatExpr] * ... +# 316| getAnOperand/getOperand/getReceiver: [ArrayLiteral] [...] +# 316| getDesugared: [MethodCall] call to [] +# 316| getReceiver: [ConstantReadAccess] Array +# 316| getArgument: [IntegerLiteral] 1 +# 316| getArgument: [IntegerLiteral] 2 +# 316| getArgument: [IntegerLiteral] 3 +# 316| getArgument: [IntegerLiteral] 4 +# 316| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0 +# 317| [AssignExpr] ... = ... +# 317| getDesugared: [StmtSequence] ... +# 317| getStmt: [AssignExpr] ... = ... +# 317| getAnOperand/getLeftOperand: [LocalVariableAccess] a +# 317| getAnOperand/getRightOperand: [MethodCall] call to [] +# 317| getArgument: [IntegerLiteral] 0 +# 317| getReceiver: [LocalVariableAccess] __synth__0 +# 317| getStmt: [AssignExpr] ... = ... +# 317| getAnOperand/getLeftOperand: [ElementReference] ...[...] +# 317| getDesugared: [StmtSequence] ... +# 317| getStmt: [SetterMethodCall] call to []= +# 317| getReceiver: [MethodCall] call to foo +# 317| getReceiver: [Self] self +# 317| getArgument: [AssignExpr] ... = ... +# 317| getAnOperand/getRightOperand: [MethodCall] call to [] +# 317| getArgument: [RangeLiteral] _ .. _ +# 317| getBegin: [IntegerLiteral] 1 +# 317| getEnd: [IntegerLiteral] -1 +# 317| getReceiver: [LocalVariableAccess] __synth__0 +# 317| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1 +# 317| getArgument: [IntegerLiteral] 5 +# 317| getStmt: [LocalVariableAccess] __synth__0__1 +# 317| getStmt: [AssignExpr] ... = ... +# 317| getAnOperand/getRightOperand: [SplatExpr] * ... +# 317| getAnOperand/getOperand/getReceiver: [ArrayLiteral] [...] +# 317| getDesugared: [MethodCall] call to [] +# 317| getReceiver: [ConstantReadAccess] Array +# 317| getArgument: [IntegerLiteral] 1 +# 317| getArgument: [IntegerLiteral] 2 +# 317| getArgument: [IntegerLiteral] 3 +# 317| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0 +# 318| [AssignAddExpr] ... += ... +# 318| getDesugared: [StmtSequence] ... +# 318| getStmt: [AssignExpr] ... = ... +# 318| getAnOperand/getRightOperand: [Self] self +# 318| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0 +# 318| getStmt: [SetterMethodCall] call to count= +# 318| getReceiver: [LocalVariableAccess] __synth__0 +# 318| getArgument: [LocalVariableAccess] __synth__1 +# 318| getStmt: [AssignExpr] ... = ... +# 318| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 318| getAnOperand/getLeftOperand/getReceiver: [MethodCall] call to count +# 318| getReceiver: [LocalVariableAccess] __synth__0 +# 318| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 318| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__1 +# 318| getStmt: [LocalVariableAccess] __synth__1 +# 319| [AssignAddExpr] ... += ... +# 319| getDesugared: [StmtSequence] ... +# 319| getStmt: [AssignExpr] ... = ... +# 319| getAnOperand/getRightOperand: [MethodCall] call to foo +# 319| getReceiver: [Self] self +# 319| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0 +# 319| getStmt: [SetterMethodCall] call to []= +# 319| getReceiver: [LocalVariableAccess] __synth__0 +# 319| getArgument: [LocalVariableAccess] __synth__1 +# 319| getArgument: [LocalVariableAccess] __synth__2 +# 319| getStmt: [AssignExpr] ... = ... +# 319| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 319| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__1 +# 319| getStmt: [AssignExpr] ... = ... +# 319| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 319| getAnOperand/getLeftOperand/getReceiver: [MethodCall] call to [] +# 319| getReceiver: [LocalVariableAccess] __synth__0 +# 319| getArgument: [LocalVariableAccess] __synth__1 +# 319| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 319| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__2 +# 319| getStmt: [LocalVariableAccess] __synth__2 +# 320| [AssignMulExpr] ... *= ... +# 320| getDesugared: [StmtSequence] ... +# 320| getStmt: [AssignExpr] ... = ... +# 320| getAnOperand/getRightOperand: [MethodCall] call to bar +# 320| getReceiver: [MethodCall] call to foo +# 320| getReceiver: [Self] self +# 320| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0 +# 320| getStmt: [SetterMethodCall] call to []= +# 320| getReceiver: [LocalVariableAccess] __synth__0 +# 320| getArgument: [LocalVariableAccess] __synth__1 +# 320| getArgument: [LocalVariableAccess] __synth__2 +# 320| getArgument: [LocalVariableAccess] __synth__3 +# 320| getArgument: [LocalVariableAccess] __synth__4 +# 320| getStmt: [AssignExpr] ... = ... +# 320| getAnOperand/getRightOperand: [IntegerLiteral] 0 +# 320| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__1 +# 320| getStmt: [AssignExpr] ... = ... +# 320| getAnOperand/getRightOperand: [MethodCall] call to baz +# 320| getReceiver: [MethodCall] call to foo +# 320| getReceiver: [Self] self +# 320| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__2 +# 320| getStmt: [AssignExpr] ... = ... +# 320| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 320| getAnOperand/getLeftOperand/getReceiver: [MethodCall] call to boo +# 320| getReceiver: [MethodCall] call to foo +# 320| getReceiver: [Self] self +# 320| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 320| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__3 +# 320| getStmt: [AssignExpr] ... = ... +# 320| getAnOperand/getRightOperand: [MulExpr] ... * ... +# 320| getAnOperand/getLeftOperand/getReceiver: [MethodCall] call to [] +# 320| getReceiver: [LocalVariableAccess] __synth__0 +# 320| getArgument: [LocalVariableAccess] __synth__1 +# 320| getArgument: [LocalVariableAccess] __synth__2 +# 320| getArgument: [LocalVariableAccess] __synth__3 +# 320| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2 +# 320| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__4 +# 320| getStmt: [LocalVariableAccess] __synth__4 +constants/constants.rb: +# 20| [ArrayLiteral] [...] +# 20| getDesugared: [MethodCall] call to [] +# 20| getReceiver: [ConstantReadAccess] Array +# 20| getArgument: [StringLiteral] "Vera" +# 20| getComponent: [StringTextComponent] Vera +# 20| getArgument: [StringLiteral] "Chuck" +# 20| getComponent: [StringTextComponent] Chuck +# 20| getArgument: [StringLiteral] "Dave" +# 20| getComponent: [StringTextComponent] Dave +literals/literals.rb: +# 92| [ArrayLiteral] [...] +# 92| getDesugared: [MethodCall] call to [] +# 92| getReceiver: [ConstantReadAccess] Array +# 93| [ArrayLiteral] [...] +# 93| getDesugared: [MethodCall] call to [] +# 93| getReceiver: [ConstantReadAccess] Array +# 93| getArgument: [IntegerLiteral] 1 +# 93| getArgument: [IntegerLiteral] 2 +# 93| getArgument: [IntegerLiteral] 3 +# 94| [ArrayLiteral] [...] +# 94| getDesugared: [MethodCall] call to [] +# 94| getReceiver: [ConstantReadAccess] Array +# 94| getArgument: [IntegerLiteral] 4 +# 94| getArgument: [IntegerLiteral] 5 +# 94| getArgument: [DivExpr] ... / ... +# 94| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 12 +# 94| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2 +# 95| [ArrayLiteral] [...] +# 95| getDesugared: [MethodCall] call to [] +# 95| getReceiver: [ConstantReadAccess] Array +# 95| getArgument: [IntegerLiteral] 7 +# 95| getArgument: [ArrayLiteral] [...] +# 95| getDesugared: [MethodCall] call to [] +# 95| getReceiver: [ConstantReadAccess] Array +# 95| getArgument: [IntegerLiteral] 8 +# 95| getArgument: [IntegerLiteral] 9 +# 98| [ArrayLiteral] %w(...) +# 98| getDesugared: [MethodCall] call to [] +# 98| getReceiver: [ConstantReadAccess] Array +# 99| [ArrayLiteral] %w(...) +# 99| getDesugared: [MethodCall] call to [] +# 99| getReceiver: [ConstantReadAccess] Array +# 99| getArgument: [StringLiteral] "foo" +# 99| getComponent: [StringTextComponent] foo +# 99| getArgument: [StringLiteral] "bar" +# 99| getComponent: [StringTextComponent] bar +# 99| getArgument: [StringLiteral] "baz" +# 99| getComponent: [StringTextComponent] baz +# 100| [ArrayLiteral] %w(...) +# 100| getDesugared: [MethodCall] call to [] +# 100| getReceiver: [ConstantReadAccess] Array +# 100| getArgument: [StringLiteral] "foo" +# 100| getComponent: [StringTextComponent] foo +# 100| getArgument: [StringLiteral] "bar" +# 100| getComponent: [StringTextComponent] bar +# 100| getArgument: [StringLiteral] "baz" +# 100| getComponent: [StringTextComponent] baz +# 101| [ArrayLiteral] %w(...) +# 101| getDesugared: [MethodCall] call to [] +# 101| getReceiver: [ConstantReadAccess] Array +# 101| getArgument: [StringLiteral] "foo" +# 101| getComponent: [StringTextComponent] foo +# 101| getArgument: [StringLiteral] "bar#{...}" +# 101| getComponent: [StringTextComponent] bar +# 101| getComponent: [StringInterpolationComponent] #{...} +# 101| getStmt: [AddExpr] ... + ... +# 101| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 1 +# 101| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 101| getArgument: [StringLiteral] "baz" +# 101| getComponent: [StringTextComponent] baz +# 102| [ArrayLiteral] %w(...) +# 102| getDesugared: [MethodCall] call to [] +# 102| getReceiver: [ConstantReadAccess] Array +# 102| getArgument: [StringLiteral] "foo" +# 102| getComponent: [StringTextComponent] foo +# 102| getArgument: [StringLiteral] "bar#{1+1}" +# 102| getComponent: [StringTextComponent] bar#{1+1} +# 102| getArgument: [StringLiteral] "baz" +# 102| getComponent: [StringTextComponent] baz +# 105| [ArrayLiteral] %i(...) +# 105| getDesugared: [MethodCall] call to [] +# 105| getReceiver: [ConstantReadAccess] Array +# 106| [ArrayLiteral] %i(...) +# 106| getDesugared: [MethodCall] call to [] +# 106| getReceiver: [ConstantReadAccess] Array +# 106| getArgument: [SymbolLiteral] :"foo" +# 106| getComponent: [StringTextComponent] foo +# 106| getArgument: [SymbolLiteral] :"bar" +# 106| getComponent: [StringTextComponent] bar +# 106| getArgument: [SymbolLiteral] :"baz" +# 106| getComponent: [StringTextComponent] baz +# 107| [ArrayLiteral] %i(...) +# 107| getDesugared: [MethodCall] call to [] +# 107| getReceiver: [ConstantReadAccess] Array +# 107| getArgument: [SymbolLiteral] :"foo" +# 107| getComponent: [StringTextComponent] foo +# 107| getArgument: [SymbolLiteral] :"bar" +# 107| getComponent: [StringTextComponent] bar +# 107| getArgument: [SymbolLiteral] :"baz" +# 107| getComponent: [StringTextComponent] baz +# 108| [ArrayLiteral] %i(...) +# 108| getDesugared: [MethodCall] call to [] +# 108| getReceiver: [ConstantReadAccess] Array +# 108| getArgument: [SymbolLiteral] :"foo" +# 108| getComponent: [StringTextComponent] foo +# 108| getArgument: [SymbolLiteral] :"bar#{...}" +# 108| getComponent: [StringTextComponent] bar +# 108| getComponent: [StringInterpolationComponent] #{...} +# 108| getStmt: [AddExpr] ... + ... +# 108| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 2 +# 108| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 4 +# 108| getArgument: [SymbolLiteral] :"baz" +# 108| getComponent: [StringTextComponent] baz +# 109| [ArrayLiteral] %i(...) +# 109| getDesugared: [MethodCall] call to [] +# 109| getReceiver: [ConstantReadAccess] Array +# 109| getArgument: [SymbolLiteral] :"foo" +# 109| getComponent: [StringTextComponent] foo +# 109| getArgument: [SymbolLiteral] :"bar#{" +# 109| getComponent: [StringTextComponent] bar#{ +# 109| getArgument: [SymbolLiteral] :"2" +# 109| getComponent: [StringTextComponent] 2 +# 109| getArgument: [SymbolLiteral] :"+" +# 109| getComponent: [StringTextComponent] + +# 109| getArgument: [SymbolLiteral] :"4" +# 109| getComponent: [StringTextComponent] 4 +# 109| getArgument: [SymbolLiteral] :"}" +# 109| getComponent: [StringTextComponent] } +# 109| getArgument: [SymbolLiteral] :"baz" +# 109| getComponent: [StringTextComponent] baz +control/loops.rb: +# 10| [AssignAddExpr] ... += ... +# 10| getDesugared: [AssignExpr] ... = ... +# 10| getAnOperand/getLeftOperand: [LocalVariableAccess] sum +# 10| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 10| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] sum +# 10| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] n +# 17| [AssignAddExpr] ... += ... +# 17| getDesugared: [AssignExpr] ... = ... +# 17| getAnOperand/getLeftOperand: [LocalVariableAccess] sum +# 17| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 17| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] sum +# 17| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] n +# 18| [AssignSubExpr] ... -= ... +# 18| getDesugared: [AssignExpr] ... = ... +# 18| getAnOperand/getLeftOperand: [LocalVariableAccess] foo +# 18| getAnOperand/getRightOperand: [SubExpr] ... - ... +# 18| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] foo +# 18| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] n +# 23| [AssignAddExpr] ... += ... +# 23| getDesugared: [AssignExpr] ... = ... +# 23| getAnOperand/getLeftOperand: [LocalVariableAccess] sum +# 23| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 23| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] sum +# 23| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] value +# 24| [AssignMulExpr] ... *= ... +# 24| getDesugared: [AssignExpr] ... = ... +# 24| getAnOperand/getLeftOperand: [LocalVariableAccess] foo +# 24| getAnOperand/getRightOperand: [MulExpr] ... * ... +# 24| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] foo +# 24| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] value +# 29| [AssignAddExpr] ... += ... +# 29| getDesugared: [AssignExpr] ... = ... +# 29| getAnOperand/getLeftOperand: [LocalVariableAccess] sum +# 29| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 29| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] sum +# 29| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] value +# 30| [AssignDivExpr] ... /= ... +# 30| getDesugared: [AssignExpr] ... = ... +# 30| getAnOperand/getLeftOperand: [LocalVariableAccess] foo +# 30| getAnOperand/getRightOperand: [DivExpr] ... / ... +# 30| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] foo +# 30| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] value +# 36| [AssignAddExpr] ... += ... +# 36| getDesugared: [AssignExpr] ... = ... +# 36| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 36| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 36| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 36| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 37| [AssignAddExpr] ... += ... +# 37| getDesugared: [AssignExpr] ... = ... +# 37| getAnOperand/getLeftOperand: [LocalVariableAccess] z +# 37| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 37| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] z +# 37| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 43| [AssignAddExpr] ... += ... +# 43| getDesugared: [AssignExpr] ... = ... +# 43| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 43| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 43| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 43| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 44| [AssignAddExpr] ... += ... +# 44| getDesugared: [AssignExpr] ... = ... +# 44| getAnOperand/getLeftOperand: [LocalVariableAccess] z +# 44| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 44| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] z +# 44| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2 +# 48| [AssignAddExpr] ... += ... +# 48| getDesugared: [AssignExpr] ... = ... +# 48| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 48| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 48| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 48| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 52| [AssignAddExpr] ... += ... +# 52| getDesugared: [AssignExpr] ... = ... +# 52| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 52| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 52| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 52| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 53| [AssignSubExpr] ... -= ... +# 53| getDesugared: [AssignExpr] ... = ... +# 53| getAnOperand/getLeftOperand: [LocalVariableAccess] z +# 53| getAnOperand/getRightOperand: [SubExpr] ... - ... +# 53| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] z +# 53| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 58| [AssignAddExpr] ... += ... +# 58| getDesugared: [AssignExpr] ... = ... +# 58| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 58| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 58| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 58| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +# 59| [AssignSubExpr] ... -= ... +# 59| getDesugared: [AssignExpr] ... = ... +# 59| getAnOperand/getLeftOperand: [LocalVariableAccess] z +# 59| getAnOperand/getRightOperand: [SubExpr] ... - ... +# 59| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] z +# 59| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 4 +# 63| [AssignSubExpr] ... -= ... +# 63| getDesugared: [AssignExpr] ... = ... +# 63| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 63| getAnOperand/getRightOperand: [SubExpr] ... - ... +# 63| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 63| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1 +operations/operations.rb: +# 29| [ArrayLiteral] [...] +# 29| getDesugared: [MethodCall] call to [] +# 29| getReceiver: [ConstantReadAccess] Array +# 29| getArgument: [IntegerLiteral] 2 +# 69| [AssignAddExpr] ... += ... +# 69| getDesugared: [AssignExpr] ... = ... +# 69| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 69| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 69| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 69| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 128 +# 70| [AssignSubExpr] ... -= ... +# 70| getDesugared: [AssignExpr] ... = ... +# 70| getAnOperand/getLeftOperand: [LocalVariableAccess] y +# 70| getAnOperand/getRightOperand: [SubExpr] ... - ... +# 70| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] y +# 70| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 32 +# 71| [AssignMulExpr] ... *= ... +# 71| getDesugared: [AssignExpr] ... = ... +# 71| getAnOperand/getLeftOperand: [LocalVariableAccess] a +# 71| getAnOperand/getRightOperand: [MulExpr] ... * ... +# 71| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 71| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 12 +# 72| [AssignDivExpr] ... /= ... +# 72| getDesugared: [AssignExpr] ... = ... +# 72| getAnOperand/getLeftOperand: [LocalVariableAccess] b +# 72| getAnOperand/getRightOperand: [DivExpr] ... / ... +# 72| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] b +# 72| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 4 +# 73| [AssignModuloExpr] ... %= ... +# 73| getDesugared: [AssignExpr] ... = ... +# 73| getAnOperand/getLeftOperand: [LocalVariableAccess] z +# 73| getAnOperand/getRightOperand: [ModuloExpr] ... % ... +# 73| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] z +# 73| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2 +# 74| [AssignExponentExpr] ... **= ... +# 74| getDesugared: [AssignExpr] ... = ... +# 74| getAnOperand/getLeftOperand: [LocalVariableAccess] foo +# 74| getAnOperand/getRightOperand: [ExponentExpr] ... ** ... +# 74| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] foo +# 74| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] bar +# 77| [AssignLogicalAndExpr] ... &&= ... +# 77| getDesugared: [AssignExpr] ... = ... +# 77| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 77| getAnOperand/getRightOperand: [LogicalAndExpr] ... && ... +# 77| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 77| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] y +# 78| [AssignLogicalOrExpr] ... ||= ... +# 78| getDesugared: [AssignExpr] ... = ... +# 78| getAnOperand/getLeftOperand: [LocalVariableAccess] a +# 78| getAnOperand/getRightOperand: [LogicalOrExpr] ... || ... +# 78| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] a +# 78| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] b +# 81| [AssignLShiftExpr] ... <<= ... +# 81| getDesugared: [AssignExpr] ... = ... +# 81| getAnOperand/getLeftOperand: [LocalVariableAccess] x +# 81| getAnOperand/getRightOperand: [LShiftExpr] ... << ... +# 81| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x +# 81| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2 +# 82| [AssignRShiftExpr] ... >>= ... +# 82| getDesugared: [AssignExpr] ... = ... +# 82| getAnOperand/getLeftOperand: [LocalVariableAccess] y +# 82| getAnOperand/getRightOperand: [RShiftExpr] ... >> ... +# 82| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] y +# 82| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 3 +# 83| [AssignBitwiseAndExpr] ... &= ... +# 83| getDesugared: [AssignExpr] ... = ... +# 83| getAnOperand/getLeftOperand: [LocalVariableAccess] foo +# 83| getAnOperand/getRightOperand: [BitwiseAndExpr] ... & ... +# 83| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] foo +# 83| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] mask +# 84| [AssignBitwiseOrExpr] ... |= ... +# 84| getDesugared: [AssignExpr] ... = ... +# 84| getAnOperand/getLeftOperand: [LocalVariableAccess] bar +# 84| getAnOperand/getRightOperand: [BitwiseOrExpr] ... | ... +# 84| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] bar +# 84| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 0x01 +# 85| [AssignBitwiseXorExpr] ... ^= ... +# 85| getDesugared: [AssignExpr] ... = ... +# 85| getAnOperand/getLeftOperand: [LocalVariableAccess] baz +# 85| getAnOperand/getRightOperand: [BitwiseXorExpr] ... ^ ... +# 85| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] baz +# 85| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] qux +# 89| [AssignAddExpr] ... += ... +# 89| getDesugared: [AssignExpr] ... = ... +# 89| getAnOperand/getLeftOperand: [InstanceVariableAccess] @x +# 89| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 89| getAnOperand/getLeftOperand/getReceiver: [InstanceVariableAccess] @x +# 89| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2 +# 92| [AssignDivExpr] ... /= ... +# 92| getDesugared: [AssignExpr] ... = ... +# 92| getAnOperand/getLeftOperand: [ClassVariableAccess] @@y +# 92| getAnOperand/getRightOperand: [DivExpr] ... / ... +# 92| getAnOperand/getLeftOperand/getReceiver: [ClassVariableAccess] @@y +# 92| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 4 +# 96| [AssignMulExpr] ... *= ... +# 96| getDesugared: [AssignExpr] ... = ... +# 96| getAnOperand/getLeftOperand: [GlobalVariableAccess] $global_var +# 96| getAnOperand/getRightOperand: [MulExpr] ... * ... +# 96| getAnOperand/getLeftOperand/getReceiver: [GlobalVariableAccess] $global_var +# 96| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 6 +params/params.rb: +# 21| [ArrayLiteral] [...] +# 21| getDesugared: [MethodCall] call to [] +# 21| getReceiver: [ConstantReadAccess] Array +erb/template.html.erb: +# 27| [ArrayLiteral] [...] +# 27| getDesugared: [MethodCall] call to [] +# 27| getReceiver: [ConstantReadAccess] Array +# 27| getArgument: [StringLiteral] "foo" +# 27| getComponent: [StringTextComponent] foo +# 27| getArgument: [StringLiteral] "bar" +# 27| getComponent: [StringTextComponent] bar +# 27| getArgument: [StringLiteral] "baz" +# 27| getComponent: [StringTextComponent] baz +# 28| [AssignAddExpr] ... += ... +# 28| getDesugared: [AssignExpr] ... = ... +# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] xs +# 28| getAnOperand/getRightOperand: [AddExpr] ... + ... +# 28| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] xs +# 28| getAnOperand/getArgument/getRightOperand: [LocalVariableAccess] x +gems/test.gemspec: +# 2| [AssignExpr] ... = ... +# 2| getDesugared: [StmtSequence] ... +# 2| getStmt: [SetterMethodCall] call to name= +# 2| getReceiver: [LocalVariableAccess] s +# 2| getArgument: [AssignExpr] ... = ... +# 2| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0 +# 2| getAnOperand/getRightOperand: [StringLiteral] "test" +# 2| getComponent: [StringTextComponent] test +# 2| getStmt: [LocalVariableAccess] __synth__0 +# 3| [AssignExpr] ... = ... +# 3| getDesugared: [StmtSequence] ... +# 3| getStmt: [SetterMethodCall] call to version= +# 3| getReceiver: [LocalVariableAccess] s +# 3| getArgument: [AssignExpr] ... = ... +# 3| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0 +# 3| getAnOperand/getRightOperand: [StringLiteral] "0.0.0" +# 3| getComponent: [StringTextComponent] 0.0.0 +# 3| getStmt: [LocalVariableAccess] __synth__0 +# 4| [AssignExpr] ... = ... +# 4| getDesugared: [StmtSequence] ... +# 4| getStmt: [SetterMethodCall] call to summary= +# 4| getReceiver: [LocalVariableAccess] s +# 4| getArgument: [AssignExpr] ... = ... +# 4| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0 +# 4| getAnOperand/getRightOperand: [StringLiteral] "foo!" +# 4| getComponent: [StringTextComponent] foo! +# 4| getStmt: [LocalVariableAccess] __synth__0 +# 5| [AssignExpr] ... = ... +# 5| getDesugared: [StmtSequence] ... +# 5| getStmt: [SetterMethodCall] call to description= +# 5| getReceiver: [LocalVariableAccess] s +# 5| getArgument: [AssignExpr] ... = ... +# 5| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0 +# 5| getAnOperand/getRightOperand: [StringLiteral] "A test" +# 5| getComponent: [StringTextComponent] A test +# 5| getStmt: [LocalVariableAccess] __synth__0 +# 6| [AssignExpr] ... = ... +# 6| getDesugared: [StmtSequence] ... +# 6| getStmt: [SetterMethodCall] call to authors= +# 6| getReceiver: [LocalVariableAccess] s +# 6| getArgument: [AssignExpr] ... = ... +# 6| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0 +# 6| getAnOperand/getRightOperand: [ArrayLiteral] [...] +# 6| getDesugared: [MethodCall] call to [] +# 6| getReceiver: [ConstantReadAccess] Array +# 6| getArgument: [StringLiteral] "Mona Lisa" +# 6| getComponent: [StringTextComponent] Mona Lisa +# 6| getStmt: [LocalVariableAccess] __synth__0 +# 7| [AssignExpr] ... = ... +# 7| getDesugared: [StmtSequence] ... +# 7| getStmt: [SetterMethodCall] call to email= +# 7| getReceiver: [LocalVariableAccess] s +# 7| getArgument: [AssignExpr] ... = ... +# 7| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0 +# 7| getAnOperand/getRightOperand: [StringLiteral] "mona@example.com" +# 7| getComponent: [StringTextComponent] mona@example.com +# 7| getStmt: [LocalVariableAccess] __synth__0 +# 8| [AssignExpr] ... = ... +# 8| getDesugared: [StmtSequence] ... +# 8| getStmt: [SetterMethodCall] call to files= +# 8| getReceiver: [LocalVariableAccess] s +# 8| getArgument: [AssignExpr] ... = ... +# 8| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0 +# 8| getAnOperand/getRightOperand: [ArrayLiteral] [...] +# 8| getDesugared: [MethodCall] call to [] +# 8| getReceiver: [ConstantReadAccess] Array +# 8| getArgument: [StringLiteral] "lib/test.rb" +# 8| getComponent: [StringTextComponent] lib/test.rb +# 8| getStmt: [LocalVariableAccess] __synth__0 +# 9| [AssignExpr] ... = ... +# 9| getDesugared: [StmtSequence] ... +# 9| getStmt: [SetterMethodCall] call to homepage= +# 9| getReceiver: [LocalVariableAccess] s +# 9| getArgument: [AssignExpr] ... = ... +# 9| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0 +# 9| getAnOperand/getRightOperand: [StringLiteral] "https://github.com/github/cod..." +# 9| getComponent: [StringTextComponent] https://github.com/github/codeql-ruby +# 9| getStmt: [LocalVariableAccess] __synth__0 diff --git a/ruby/ql/test/library-tests/ast/AstDesugar.ql b/ruby/ql/test/library-tests/ast/AstDesugar.ql new file mode 100644 index 000000000000..ab7036adc486 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/AstDesugar.ql @@ -0,0 +1,23 @@ +/** + * @kind graph + */ + +import codeql.ruby.AST +import codeql.ruby.printAst +import codeql.ruby.ast.internal.Synthesis + +class DesugarPrintAstConfiguration extends PrintAstConfiguration { + override predicate shouldPrintNode(AstNode n) { + isDesugared(n) + or + exists(n.getDesugared()) + } + + override predicate shouldPrintAstEdge(AstNode parent, string edgeName, AstNode child) { + super.shouldPrintAstEdge(parent, edgeName, child) and + desugarLevel(parent) = desugarLevel(child) + or + child = parent.getDesugared() and + edgeName = "getDesugared" + } +} diff --git a/ruby/ql/test/library-tests/ast/calls/arguments.expected b/ruby/ql/test/library-tests/ast/calls/arguments.expected new file mode 100644 index 000000000000..526de133d0b1 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/calls/arguments.expected @@ -0,0 +1,17 @@ +blockArguments +| calls.rb:266:5:266:8 | &... | calls.rb:266:6:266:8 | call to bar | +| calls.rb:267:5:267:11 | &... | calls.rb:267:6:267:11 | call to bar | +splatExpr +| calls.rb:270:5:270:8 | * ... | calls.rb:270:6:270:8 | call to bar | +| calls.rb:271:5:271:11 | * ... | calls.rb:271:6:271:11 | call to bar | +| calls.rb:316:31:316:42 | * ... | calls.rb:316:31:316:42 | [...] | +| calls.rb:317:14:317:22 | * ... | calls.rb:317:14:317:22 | [...] | +hashSplatExpr +| calls.rb:274:5:274:9 | ** ... | calls.rb:274:7:274:9 | call to bar | +| calls.rb:275:5:275:12 | ** ... | calls.rb:275:7:275:12 | call to bar | +keywordArguments +| calls.rb:278:5:278:13 | Pair | calls.rb:278:5:278:8 | :blah | calls.rb:278:11:278:13 | call to bar | +| calls.rb:279:5:279:16 | Pair | calls.rb:279:5:279:8 | :blah | calls.rb:279:11:279:16 | call to bar | +keywordArgumentsByKeyword +| calls.rb:278:1:278:14 | call to foo | blah | calls.rb:278:11:278:13 | call to bar | +| calls.rb:279:1:279:17 | call to foo | blah | calls.rb:279:11:279:16 | call to bar | diff --git a/ruby/ql/test/library-tests/ast/calls/arguments.ql b/ruby/ql/test/library-tests/ast/calls/arguments.ql new file mode 100644 index 000000000000..abe4ffa49339 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/calls/arguments.ql @@ -0,0 +1,15 @@ +import ruby + +query predicate blockArguments(BlockArgument a, Expr e) { e = a.getValue() } + +query predicate splatExpr(SplatExpr a, Expr e) { e = a.getOperand() } + +query predicate hashSplatExpr(HashSplatExpr a, Expr e) { e = a.getOperand() } + +query predicate keywordArguments(Pair a, Expr key, Expr value) { + exists(Call c | c.getAnArgument() = a and key = a.getKey() and value = a.getValue()) +} + +query predicate keywordArgumentsByKeyword(Call c, string keyword, Expr value) { + c.getKeywordArgument(keyword) = value +} diff --git a/ruby/ql/test/library-tests/ast/calls/calls.expected b/ruby/ql/test/library-tests/ast/calls/calls.expected new file mode 100644 index 000000000000..1dd34f354622 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/calls/calls.expected @@ -0,0 +1,365 @@ +callsWithNoReceiverArgumentsOrBlock +| calls.rb:31:3:31:7 | yield ... | (none) | +| calls.rb:286:5:286:9 | call to super | super | +| calls.rb:287:5:287:11 | call to super | super | +| calls.rb:305:5:305:9 | call to super | super | +callsWithArguments +| calls.rb:14:1:14:11 | call to foo | foo | 0 | calls.rb:14:5:14:5 | 0 | +| calls.rb:14:1:14:11 | call to foo | foo | 1 | calls.rb:14:8:14:8 | 1 | +| calls.rb:14:1:14:11 | call to foo | foo | 2 | calls.rb:14:11:14:11 | 2 | +| calls.rb:17:11:17:15 | ... + ... | + | 0 | calls.rb:17:15:17:15 | 1 | +| calls.rb:21:3:21:7 | ... + ... | + | 0 | calls.rb:21:7:21:7 | 1 | +| calls.rb:25:1:27:3 | call to bar | bar | 0 | calls.rb:25:9:25:13 | "foo" | +| calls.rb:26:3:26:7 | ... + ... | + | 0 | calls.rb:26:7:26:7 | 1 | +| calls.rb:36:3:36:16 | yield ... | (none) | 0 | calls.rb:36:9:36:11 | 100 | +| calls.rb:36:3:36:16 | yield ... | (none) | 1 | calls.rb:36:14:36:16 | 200 | +| calls.rb:54:1:54:14 | call to some_func | some_func | 0 | calls.rb:54:11:54:13 | call to foo | +| calls.rb:55:1:55:17 | call to some_func | some_func | 0 | calls.rb:55:11:55:16 | call to foo | +| calls.rb:58:1:58:5 | call to [] | [] | 0 | calls.rb:58:2:58:4 | call to foo | +| calls.rb:59:1:59:8 | call to [] | [] | 0 | calls.rb:59:2:59:7 | call to foo | +| calls.rb:66:6:66:7 | ... + ... | + | 0 | calls.rb:66:9:66:11 | call to bar | +| calls.rb:67:6:67:7 | ... + ... | + | 0 | calls.rb:67:9:67:14 | call to bar | +| calls.rb:85:1:85:12 | ... + ... | + | 0 | calls.rb:85:7:85:12 | call to bar | +| calls.rb:234:1:234:8 | ...[...] | [] | 0 | calls.rb:234:5:234:7 | call to bar | +| calls.rb:235:1:235:14 | ...[...] | [] | 0 | calls.rb:235:8:235:13 | call to bar | +| calls.rb:266:1:266:9 | call to foo | foo | 0 | calls.rb:266:5:266:8 | &... | +| calls.rb:267:1:267:12 | call to foo | foo | 0 | calls.rb:267:5:267:11 | &... | +| calls.rb:270:1:270:9 | call to foo | foo | 0 | calls.rb:270:5:270:8 | * ... | +| calls.rb:271:1:271:12 | call to foo | foo | 0 | calls.rb:271:5:271:11 | * ... | +| calls.rb:274:1:274:10 | call to foo | foo | 0 | calls.rb:274:5:274:9 | ** ... | +| calls.rb:275:1:275:13 | call to foo | foo | 0 | calls.rb:275:5:275:12 | ** ... | +| calls.rb:278:1:278:14 | call to foo | foo | 0 | calls.rb:278:5:278:13 | Pair | +| calls.rb:279:1:279:17 | call to foo | foo | 0 | calls.rb:279:5:279:16 | Pair | +| calls.rb:288:5:288:16 | call to super | super | 0 | calls.rb:288:11:288:16 | "blah" | +| calls.rb:289:5:289:17 | call to super | super | 0 | calls.rb:289:11:289:11 | 1 | +| calls.rb:289:5:289:17 | call to super | super | 1 | calls.rb:289:14:289:14 | 2 | +| calls.rb:289:5:289:17 | call to super | super | 2 | calls.rb:289:17:289:17 | 3 | +| calls.rb:290:17:290:21 | ... + ... | + | 0 | calls.rb:290:21:290:21 | 1 | +| calls.rb:291:18:291:22 | ... * ... | * | 0 | calls.rb:291:22:291:22 | 2 | +| calls.rb:292:5:292:30 | call to super | super | 0 | calls.rb:292:11:292:11 | 4 | +| calls.rb:292:5:292:30 | call to super | super | 1 | calls.rb:292:14:292:14 | 5 | +| calls.rb:292:22:292:28 | ... + ... | + | 0 | calls.rb:292:26:292:28 | 100 | +| calls.rb:293:5:293:33 | call to super | super | 0 | calls.rb:293:11:293:11 | 6 | +| calls.rb:293:5:293:33 | call to super | super | 1 | calls.rb:293:14:293:14 | 7 | +| calls.rb:293:23:293:29 | ... + ... | + | 0 | calls.rb:293:27:293:29 | 200 | +| calls.rb:311:1:311:7 | call to call | call | 0 | calls.rb:311:6:311:6 | 1 | +| calls.rb:314:1:314:8 | call to foo= | foo= | 0 | calls.rb:314:1:314:8 | ... = ... | +| calls.rb:315:1:315:6 | ...[...] | [] | 0 | calls.rb:315:5:315:5 | 0 | +| calls.rb:315:1:315:6 | call to []= | []= | 0 | calls.rb:315:5:315:5 | 0 | +| calls.rb:315:1:315:6 | call to []= | []= | 1 | calls.rb:315:1:315:6 | ... = ... | +| calls.rb:316:1:316:8 | call to [] | [] | 0 | calls.rb:316:1:316:8 | 0 | +| calls.rb:316:1:316:8 | call to foo= | foo= | 0 | calls.rb:316:1:316:8 | ... = ... | +| calls.rb:316:12:316:19 | call to [] | [] | 0 | calls.rb:316:12:316:19 | _ .. _ | +| calls.rb:316:12:316:19 | call to bar= | bar= | 0 | calls.rb:316:12:316:19 | ... = ... | +| calls.rb:316:22:316:27 | ...[...] | [] | 0 | calls.rb:316:26:316:26 | 4 | +| calls.rb:316:22:316:27 | call to [] | [] | 0 | calls.rb:316:22:316:27 | -1 | +| calls.rb:316:22:316:27 | call to []= | []= | 0 | calls.rb:316:26:316:26 | 4 | +| calls.rb:316:22:316:27 | call to []= | []= | 1 | calls.rb:316:22:316:27 | ... = ... | +| calls.rb:316:31:316:42 | call to [] | [] | 0 | calls.rb:316:32:316:32 | 1 | +| calls.rb:316:31:316:42 | call to [] | [] | 1 | calls.rb:316:35:316:35 | 2 | +| calls.rb:316:31:316:42 | call to [] | [] | 2 | calls.rb:316:38:316:38 | 3 | +| calls.rb:316:31:316:42 | call to [] | [] | 3 | calls.rb:316:41:316:41 | 4 | +| calls.rb:317:1:317:1 | call to [] | [] | 0 | calls.rb:317:1:317:1 | 0 | +| calls.rb:317:5:317:10 | ...[...] | [] | 0 | calls.rb:317:9:317:9 | 5 | +| calls.rb:317:5:317:10 | call to [] | [] | 0 | calls.rb:317:5:317:10 | _ .. _ | +| calls.rb:317:5:317:10 | call to []= | []= | 0 | calls.rb:317:9:317:9 | 5 | +| calls.rb:317:5:317:10 | call to []= | []= | 1 | calls.rb:317:5:317:10 | ... = ... | +| calls.rb:317:14:317:22 | call to [] | [] | 0 | calls.rb:317:15:317:15 | 1 | +| calls.rb:317:14:317:22 | call to [] | [] | 1 | calls.rb:317:18:317:18 | 2 | +| calls.rb:317:14:317:22 | call to [] | [] | 2 | calls.rb:317:21:317:21 | 3 | +| calls.rb:318:1:318:10 | call to count= | count= | 1 | calls.rb:318:12:318:13 | __synth__1 | +| calls.rb:318:12:318:13 | ... + ... | + | 0 | calls.rb:318:15:318:15 | 1 | +| calls.rb:319:1:319:6 | ...[...] | [] | 0 | calls.rb:319:5:319:5 | 0 | +| calls.rb:319:1:319:6 | call to [] | [] | 0 | calls.rb:319:5:319:5 | __synth__1 | +| calls.rb:319:1:319:6 | call to []= | []= | 0 | calls.rb:319:5:319:5 | __synth__1 | +| calls.rb:319:1:319:6 | call to []= | []= | 2 | calls.rb:319:8:319:9 | __synth__2 | +| calls.rb:319:8:319:9 | ... + ... | + | 0 | calls.rb:319:11:319:11 | 1 | +| calls.rb:320:1:320:32 | ...[...] | [] | 0 | calls.rb:320:9:320:9 | 0 | +| calls.rb:320:1:320:32 | ...[...] | [] | 1 | calls.rb:320:12:320:18 | call to baz | +| calls.rb:320:1:320:32 | ...[...] | [] | 2 | calls.rb:320:21:320:31 | ... + ... | +| calls.rb:320:1:320:32 | call to [] | [] | 0 | calls.rb:320:9:320:9 | __synth__1 | +| calls.rb:320:1:320:32 | call to [] | [] | 1 | calls.rb:320:12:320:18 | __synth__2 | +| calls.rb:320:1:320:32 | call to [] | [] | 2 | calls.rb:320:21:320:31 | __synth__3 | +| calls.rb:320:1:320:32 | call to []= | []= | 0 | calls.rb:320:9:320:9 | __synth__1 | +| calls.rb:320:1:320:32 | call to []= | []= | 1 | calls.rb:320:12:320:18 | __synth__2 | +| calls.rb:320:1:320:32 | call to []= | []= | 2 | calls.rb:320:21:320:31 | __synth__3 | +| calls.rb:320:1:320:32 | call to []= | []= | 4 | calls.rb:320:34:320:35 | __synth__4 | +| calls.rb:320:21:320:31 | ... + ... | + | 0 | calls.rb:320:31:320:31 | 1 | +| calls.rb:320:34:320:35 | ... * ... | * | 0 | calls.rb:320:37:320:37 | 2 | +callsWithReceiver +| calls.rb:2:1:2:5 | call to foo | calls.rb:2:1:2:5 | self | +| calls.rb:5:1:5:10 | call to bar | calls.rb:5:1:5:3 | Foo | +| calls.rb:8:1:8:7 | call to bar | calls.rb:8:1:8:7 | self | +| calls.rb:11:1:11:7 | call to bar | calls.rb:11:1:11:3 | 123 | +| calls.rb:14:1:14:11 | call to foo | calls.rb:14:1:14:11 | self | +| calls.rb:17:1:17:17 | call to foo | calls.rb:17:1:17:17 | self | +| calls.rb:17:11:17:15 | ... + ... | calls.rb:17:11:17:11 | x | +| calls.rb:20:1:22:3 | call to foo | calls.rb:20:1:22:3 | self | +| calls.rb:21:3:21:7 | ... + ... | calls.rb:21:3:21:3 | x | +| calls.rb:25:1:27:3 | call to bar | calls.rb:25:1:25:3 | 123 | +| calls.rb:26:3:26:7 | ... + ... | calls.rb:26:3:26:3 | x | +| calls.rb:46:1:46:3 | call to foo | calls.rb:46:1:46:3 | self | +| calls.rb:47:1:47:6 | call to foo | calls.rb:47:1:47:1 | X | +| calls.rb:50:2:50:4 | call to foo | calls.rb:50:2:50:4 | self | +| calls.rb:51:2:51:7 | call to foo | calls.rb:51:2:51:2 | X | +| calls.rb:54:1:54:14 | call to some_func | calls.rb:54:1:54:14 | self | +| calls.rb:54:11:54:13 | call to foo | calls.rb:54:11:54:13 | self | +| calls.rb:55:1:55:17 | call to some_func | calls.rb:55:1:55:17 | self | +| calls.rb:55:11:55:16 | call to foo | calls.rb:55:11:55:11 | X | +| calls.rb:58:1:58:5 | call to [] | calls.rb:58:1:58:5 | Array | +| calls.rb:58:2:58:4 | call to foo | calls.rb:58:2:58:4 | self | +| calls.rb:59:1:59:8 | call to [] | calls.rb:59:1:59:8 | Array | +| calls.rb:59:2:59:7 | call to foo | calls.rb:59:2:59:2 | X | +| calls.rb:62:8:62:10 | call to foo | calls.rb:62:8:62:10 | self | +| calls.rb:63:8:63:13 | call to foo | calls.rb:63:8:63:8 | X | +| calls.rb:66:6:66:7 | ... + ... | calls.rb:66:1:66:4 | var1 | +| calls.rb:66:9:66:11 | call to bar | calls.rb:66:9:66:11 | self | +| calls.rb:67:6:67:7 | ... + ... | calls.rb:67:1:67:4 | var1 | +| calls.rb:67:9:67:14 | call to bar | calls.rb:67:9:67:9 | X | +| calls.rb:70:8:70:10 | call to foo | calls.rb:70:8:70:10 | self | +| calls.rb:70:13:70:18 | call to bar | calls.rb:70:13:70:13 | X | +| calls.rb:74:3:74:5 | call to foo | calls.rb:74:3:74:5 | self | +| calls.rb:75:3:75:8 | call to foo | calls.rb:75:3:75:3 | X | +| calls.rb:79:9:79:11 | call to foo | calls.rb:79:9:79:11 | self | +| calls.rb:79:14:79:19 | call to bar | calls.rb:79:14:79:14 | X | +| calls.rb:82:7:82:9 | call to foo | calls.rb:82:7:82:9 | self | +| calls.rb:82:12:82:17 | call to bar | calls.rb:82:12:82:12 | X | +| calls.rb:85:1:85:3 | call to foo | calls.rb:85:1:85:3 | self | +| calls.rb:85:1:85:12 | ... + ... | calls.rb:85:1:85:3 | call to foo | +| calls.rb:85:7:85:12 | call to bar | calls.rb:85:7:85:7 | X | +| calls.rb:88:1:88:4 | ! ... | calls.rb:88:2:88:4 | call to foo | +| calls.rb:88:2:88:4 | call to foo | calls.rb:88:2:88:4 | self | +| calls.rb:89:1:89:7 | ~ ... | calls.rb:89:2:89:7 | call to bar | +| calls.rb:89:2:89:7 | call to bar | calls.rb:89:2:89:2 | X | +| calls.rb:92:1:92:21 | call to foo | calls.rb:92:1:92:21 | self | +| calls.rb:92:9:92:11 | call to bar | calls.rb:92:9:92:11 | self | +| calls.rb:92:14:92:19 | call to baz | calls.rb:92:14:92:14 | X | +| calls.rb:95:1:98:3 | call to foo | calls.rb:95:1:98:3 | self | +| calls.rb:96:3:96:5 | call to bar | calls.rb:96:3:96:5 | self | +| calls.rb:97:3:97:8 | call to baz | calls.rb:97:3:97:3 | X | +| calls.rb:101:1:101:3 | call to foo | calls.rb:101:1:101:3 | self | +| calls.rb:101:1:101:9 | call to bar | calls.rb:101:1:101:3 | call to foo | +| calls.rb:102:1:102:3 | call to bar | calls.rb:102:1:102:3 | self | +| calls.rb:102:1:102:9 | call to baz | calls.rb:102:1:102:3 | call to bar | +| calls.rb:106:6:106:8 | call to foo | calls.rb:106:6:106:8 | self | +| calls.rb:107:6:107:8 | call to bar | calls.rb:107:6:107:8 | self | +| calls.rb:108:3:108:5 | call to baz | calls.rb:108:3:108:5 | self | +| calls.rb:110:6:110:11 | call to foo | calls.rb:110:6:110:6 | X | +| calls.rb:111:6:111:11 | call to bar | calls.rb:111:6:111:6 | X | +| calls.rb:112:3:112:8 | call to baz | calls.rb:112:3:112:3 | X | +| calls.rb:117:3:117:5 | call to foo | calls.rb:117:3:117:5 | self | +| calls.rb:118:3:118:8 | call to bar | calls.rb:118:3:118:3 | X | +| calls.rb:122:17:122:19 | call to foo | calls.rb:122:17:122:19 | self | +| calls.rb:124:18:124:23 | call to foo | calls.rb:124:18:124:18 | X | +| calls.rb:128:10:128:12 | call to foo | calls.rb:128:10:128:12 | self | +| calls.rb:129:3:129:5 | call to bar | calls.rb:129:3:129:5 | self | +| calls.rb:131:10:131:15 | call to foo | calls.rb:131:10:131:10 | X | +| calls.rb:132:3:132:8 | call to bar | calls.rb:132:3:132:3 | X | +| calls.rb:137:3:137:5 | call to foo | calls.rb:137:3:137:5 | self | +| calls.rb:138:3:138:8 | call to bar | calls.rb:138:3:138:3 | X | +| calls.rb:142:5:142:7 | call to foo | calls.rb:142:5:142:7 | self | +| calls.rb:143:3:143:5 | call to bar | calls.rb:143:3:143:5 | self | +| calls.rb:144:3:144:8 | call to baz | calls.rb:144:3:144:3 | X | +| calls.rb:148:40:148:42 | call to foo | calls.rb:148:40:148:42 | self | +| calls.rb:150:41:150:46 | call to foo | calls.rb:150:41:150:41 | X | +| calls.rb:154:40:154:42 | call to foo | calls.rb:154:40:154:42 | self | +| calls.rb:156:41:156:46 | call to foo | calls.rb:156:41:156:41 | X | +| calls.rb:161:3:161:5 | call to foo | calls.rb:161:3:161:5 | self | +| calls.rb:162:3:162:8 | call to bar | calls.rb:162:3:162:3 | X | +| calls.rb:166:1:166:3 | call to foo | calls.rb:166:1:166:3 | self | +| calls.rb:166:7:166:9 | call to bar | calls.rb:166:7:166:9 | self | +| calls.rb:166:13:166:15 | call to baz | calls.rb:166:13:166:15 | self | +| calls.rb:167:1:167:6 | call to foo | calls.rb:167:1:167:1 | X | +| calls.rb:167:10:167:15 | call to bar | calls.rb:167:10:167:10 | X | +| calls.rb:167:19:167:24 | call to baz | calls.rb:167:19:167:19 | X | +| calls.rb:170:4:170:6 | call to foo | calls.rb:170:4:170:6 | self | +| calls.rb:171:3:171:8 | call to wibble | calls.rb:171:3:171:8 | self | +| calls.rb:172:7:172:9 | call to bar | calls.rb:172:7:172:9 | self | +| calls.rb:173:3:173:8 | call to wobble | calls.rb:173:3:173:8 | self | +| calls.rb:175:3:175:8 | call to wabble | calls.rb:175:3:175:8 | self | +| calls.rb:177:4:177:9 | call to foo | calls.rb:177:4:177:4 | X | +| calls.rb:178:3:178:11 | call to wibble | calls.rb:178:3:178:3 | X | +| calls.rb:179:7:179:12 | call to bar | calls.rb:179:7:179:7 | X | +| calls.rb:180:3:180:11 | call to wobble | calls.rb:180:3:180:3 | X | +| calls.rb:182:3:182:11 | call to wabble | calls.rb:182:3:182:3 | X | +| calls.rb:186:1:186:3 | call to bar | calls.rb:186:1:186:3 | self | +| calls.rb:186:8:186:10 | call to foo | calls.rb:186:8:186:10 | self | +| calls.rb:187:1:187:6 | call to bar | calls.rb:187:1:187:1 | X | +| calls.rb:187:11:187:16 | call to foo | calls.rb:187:11:187:11 | X | +| calls.rb:190:8:190:10 | call to foo | calls.rb:190:8:190:10 | self | +| calls.rb:191:3:191:5 | call to bar | calls.rb:191:3:191:5 | self | +| calls.rb:193:8:193:13 | call to foo | calls.rb:193:8:193:8 | X | +| calls.rb:194:3:194:8 | call to bar | calls.rb:194:3:194:3 | X | +| calls.rb:198:1:198:3 | call to bar | calls.rb:198:1:198:3 | self | +| calls.rb:198:12:198:14 | call to foo | calls.rb:198:12:198:14 | self | +| calls.rb:199:1:199:6 | call to bar | calls.rb:199:1:199:1 | X | +| calls.rb:199:15:199:20 | call to foo | calls.rb:199:15:199:15 | X | +| calls.rb:202:7:202:9 | call to foo | calls.rb:202:7:202:9 | self | +| calls.rb:203:3:203:5 | call to bar | calls.rb:203:3:203:5 | self | +| calls.rb:205:7:205:12 | call to foo | calls.rb:205:7:205:7 | X | +| calls.rb:206:3:206:8 | call to bar | calls.rb:206:3:206:3 | X | +| calls.rb:210:1:210:3 | call to bar | calls.rb:210:1:210:3 | self | +| calls.rb:210:11:210:13 | call to foo | calls.rb:210:11:210:13 | self | +| calls.rb:211:1:211:6 | call to bar | calls.rb:211:1:211:1 | X | +| calls.rb:211:14:211:19 | call to foo | calls.rb:211:14:211:14 | X | +| calls.rb:214:7:214:9 | call to foo | calls.rb:214:7:214:9 | self | +| calls.rb:215:3:215:5 | call to bar | calls.rb:215:3:215:5 | self | +| calls.rb:217:7:217:12 | call to foo | calls.rb:217:7:217:7 | X | +| calls.rb:218:3:218:8 | call to bar | calls.rb:218:3:218:3 | X | +| calls.rb:222:1:222:3 | call to bar | calls.rb:222:1:222:3 | self | +| calls.rb:222:11:222:13 | call to foo | calls.rb:222:11:222:13 | self | +| calls.rb:223:1:223:6 | call to bar | calls.rb:223:1:223:1 | X | +| calls.rb:223:14:223:19 | call to foo | calls.rb:223:14:223:14 | X | +| calls.rb:226:10:226:12 | call to bar | calls.rb:226:10:226:12 | self | +| calls.rb:227:3:227:5 | call to baz | calls.rb:227:3:227:5 | self | +| calls.rb:229:10:229:15 | call to bar | calls.rb:229:10:229:10 | X | +| calls.rb:230:3:230:8 | call to baz | calls.rb:230:3:230:3 | X | +| calls.rb:234:1:234:3 | call to foo | calls.rb:234:1:234:3 | self | +| calls.rb:234:1:234:8 | ...[...] | calls.rb:234:1:234:3 | call to foo | +| calls.rb:234:5:234:7 | call to bar | calls.rb:234:5:234:7 | self | +| calls.rb:235:1:235:6 | call to foo | calls.rb:235:1:235:1 | X | +| calls.rb:235:1:235:14 | ...[...] | calls.rb:235:1:235:6 | call to foo | +| calls.rb:235:8:235:13 | call to bar | calls.rb:235:8:235:8 | X | +| calls.rb:238:8:238:10 | call to bar | calls.rb:238:8:238:10 | self | +| calls.rb:238:15:238:20 | call to baz | calls.rb:238:15:238:15 | X | +| calls.rb:241:1:241:3 | call to foo | calls.rb:241:1:241:3 | self | +| calls.rb:242:1:242:6 | call to foo | calls.rb:242:1:242:1 | X | +| calls.rb:245:1:245:3 | call to foo | calls.rb:245:1:245:3 | self | +| calls.rb:245:6:245:8 | call to bar | calls.rb:245:6:245:8 | self | +| calls.rb:246:1:246:6 | call to foo | calls.rb:246:1:246:1 | X | +| calls.rb:246:9:246:14 | call to bar | calls.rb:246:9:246:9 | X | +| calls.rb:249:3:249:5 | call to foo | calls.rb:249:3:249:5 | self | +| calls.rb:249:10:249:12 | call to bar | calls.rb:249:10:249:12 | self | +| calls.rb:249:15:249:20 | call to foo | calls.rb:249:15:249:15 | X | +| calls.rb:249:25:249:30 | call to bar | calls.rb:249:25:249:25 | X | +| calls.rb:253:8:253:10 | call to foo | calls.rb:253:8:253:10 | self | +| calls.rb:254:8:254:10 | call to bar | calls.rb:254:8:254:10 | self | +| calls.rb:257:8:257:13 | call to foo | calls.rb:257:8:257:8 | X | +| calls.rb:258:8:258:13 | call to bar | calls.rb:258:8:258:8 | X | +| calls.rb:262:1:262:3 | call to foo | calls.rb:262:1:262:3 | self | +| calls.rb:262:12:262:14 | call to bar | calls.rb:262:12:262:14 | self | +| calls.rb:263:1:263:6 | call to foo | calls.rb:263:1:263:1 | X | +| calls.rb:263:15:263:20 | call to bar | calls.rb:263:15:263:15 | X | +| calls.rb:266:1:266:9 | call to foo | calls.rb:266:1:266:9 | self | +| calls.rb:266:6:266:8 | call to bar | calls.rb:266:6:266:8 | self | +| calls.rb:267:1:267:12 | call to foo | calls.rb:267:1:267:12 | self | +| calls.rb:267:6:267:11 | call to bar | calls.rb:267:6:267:6 | X | +| calls.rb:270:1:270:9 | call to foo | calls.rb:270:1:270:9 | self | +| calls.rb:270:5:270:8 | * ... | calls.rb:270:6:270:8 | call to bar | +| calls.rb:270:6:270:8 | call to bar | calls.rb:270:6:270:8 | self | +| calls.rb:271:1:271:12 | call to foo | calls.rb:271:1:271:12 | self | +| calls.rb:271:5:271:11 | * ... | calls.rb:271:6:271:11 | call to bar | +| calls.rb:271:6:271:11 | call to bar | calls.rb:271:6:271:6 | X | +| calls.rb:274:1:274:10 | call to foo | calls.rb:274:1:274:10 | self | +| calls.rb:274:5:274:9 | ** ... | calls.rb:274:7:274:9 | call to bar | +| calls.rb:274:7:274:9 | call to bar | calls.rb:274:7:274:9 | self | +| calls.rb:275:1:275:13 | call to foo | calls.rb:275:1:275:13 | self | +| calls.rb:275:5:275:12 | ** ... | calls.rb:275:7:275:12 | call to bar | +| calls.rb:275:7:275:12 | call to bar | calls.rb:275:7:275:7 | X | +| calls.rb:278:1:278:14 | call to foo | calls.rb:278:1:278:14 | self | +| calls.rb:278:11:278:13 | call to bar | calls.rb:278:11:278:13 | self | +| calls.rb:279:1:279:17 | call to foo | calls.rb:279:1:279:17 | self | +| calls.rb:279:11:279:16 | call to bar | calls.rb:279:11:279:11 | X | +| calls.rb:290:17:290:21 | ... + ... | calls.rb:290:17:290:17 | x | +| calls.rb:291:18:291:22 | ... * ... | calls.rb:291:18:291:18 | x | +| calls.rb:292:22:292:28 | ... + ... | calls.rb:292:22:292:22 | x | +| calls.rb:293:23:293:29 | ... + ... | calls.rb:293:23:293:23 | x | +| calls.rb:303:5:303:7 | call to foo | calls.rb:303:5:303:7 | self | +| calls.rb:303:5:303:13 | call to super | calls.rb:303:5:303:7 | call to foo | +| calls.rb:304:5:304:14 | call to super | calls.rb:304:5:304:8 | self | +| calls.rb:305:5:305:15 | call to super | calls.rb:305:5:305:9 | call to super | +| calls.rb:310:1:310:3 | call to foo | calls.rb:310:1:310:3 | self | +| calls.rb:310:1:310:6 | call to call | calls.rb:310:1:310:3 | call to foo | +| calls.rb:311:1:311:3 | call to foo | calls.rb:311:1:311:3 | self | +| calls.rb:311:1:311:7 | call to call | calls.rb:311:1:311:3 | call to foo | +| calls.rb:314:1:314:8 | call to foo | calls.rb:314:1:314:4 | self | +| calls.rb:314:1:314:8 | call to foo= | calls.rb:314:1:314:4 | self | +| calls.rb:315:1:315:3 | call to foo | calls.rb:315:1:315:3 | self | +| calls.rb:315:1:315:6 | ...[...] | calls.rb:315:1:315:3 | call to foo | +| calls.rb:315:1:315:6 | call to []= | calls.rb:315:1:315:3 | call to foo | +| calls.rb:316:1:316:8 | call to [] | calls.rb:316:1:316:8 | __synth__0 | +| calls.rb:316:1:316:8 | call to foo | calls.rb:316:1:316:4 | self | +| calls.rb:316:1:316:8 | call to foo= | calls.rb:316:1:316:4 | self | +| calls.rb:316:12:316:19 | call to [] | calls.rb:316:12:316:19 | __synth__0 | +| calls.rb:316:12:316:19 | call to bar | calls.rb:316:12:316:15 | self | +| calls.rb:316:12:316:19 | call to bar= | calls.rb:316:12:316:15 | self | +| calls.rb:316:22:316:24 | call to foo | calls.rb:316:22:316:24 | self | +| calls.rb:316:22:316:27 | ...[...] | calls.rb:316:22:316:24 | call to foo | +| calls.rb:316:22:316:27 | call to [] | calls.rb:316:22:316:27 | __synth__0 | +| calls.rb:316:22:316:27 | call to []= | calls.rb:316:22:316:24 | call to foo | +| calls.rb:316:31:316:42 | * ... | calls.rb:316:31:316:42 | [...] | +| calls.rb:316:31:316:42 | call to [] | calls.rb:316:31:316:42 | Array | +| calls.rb:317:1:317:1 | call to [] | calls.rb:317:1:317:1 | __synth__0 | +| calls.rb:317:5:317:7 | call to foo | calls.rb:317:5:317:7 | self | +| calls.rb:317:5:317:10 | ...[...] | calls.rb:317:5:317:7 | call to foo | +| calls.rb:317:5:317:10 | call to [] | calls.rb:317:5:317:10 | __synth__0 | +| calls.rb:317:5:317:10 | call to []= | calls.rb:317:5:317:7 | call to foo | +| calls.rb:317:14:317:22 | * ... | calls.rb:317:14:317:22 | [...] | +| calls.rb:317:14:317:22 | call to [] | calls.rb:317:14:317:22 | Array | +| calls.rb:318:1:318:10 | call to count | calls.rb:318:1:318:4 | __synth__0 | +| calls.rb:318:1:318:10 | call to count | calls.rb:318:1:318:4 | self | +| calls.rb:318:1:318:10 | call to count= | calls.rb:318:1:318:4 | __synth__0 | +| calls.rb:318:12:318:13 | ... + ... | calls.rb:318:1:318:10 | call to count | +| calls.rb:319:1:319:3 | call to foo | calls.rb:319:1:319:3 | self | +| calls.rb:319:1:319:6 | ...[...] | calls.rb:319:1:319:3 | call to foo | +| calls.rb:319:1:319:6 | call to [] | calls.rb:319:1:319:3 | __synth__0 | +| calls.rb:319:1:319:6 | call to []= | calls.rb:319:1:319:3 | __synth__0 | +| calls.rb:319:8:319:9 | ... + ... | calls.rb:319:1:319:6 | call to [] | +| calls.rb:320:1:320:3 | call to foo | calls.rb:320:1:320:3 | self | +| calls.rb:320:1:320:7 | call to bar | calls.rb:320:1:320:3 | call to foo | +| calls.rb:320:1:320:32 | ...[...] | calls.rb:320:1:320:7 | call to bar | +| calls.rb:320:1:320:32 | call to [] | calls.rb:320:1:320:7 | __synth__0 | +| calls.rb:320:1:320:32 | call to []= | calls.rb:320:1:320:7 | __synth__0 | +| calls.rb:320:12:320:14 | call to foo | calls.rb:320:12:320:14 | self | +| calls.rb:320:12:320:18 | call to baz | calls.rb:320:12:320:14 | call to foo | +| calls.rb:320:21:320:23 | call to foo | calls.rb:320:21:320:23 | self | +| calls.rb:320:21:320:27 | call to boo | calls.rb:320:21:320:23 | call to foo | +| calls.rb:320:21:320:31 | ... + ... | calls.rb:320:21:320:27 | call to boo | +| calls.rb:320:34:320:35 | ... * ... | calls.rb:320:1:320:32 | call to [] | +callsWithBlock +| calls.rb:17:1:17:17 | call to foo | calls.rb:17:5:17:17 | { ... } | +| calls.rb:20:1:22:3 | call to foo | calls.rb:20:5:22:3 | do ... end | +| calls.rb:25:1:27:3 | call to bar | calls.rb:25:16:27:3 | do ... end | +| calls.rb:92:1:92:21 | call to foo | calls.rb:92:7:92:21 | { ... } | +| calls.rb:95:1:98:3 | call to foo | calls.rb:95:7:98:3 | do ... end | +| calls.rb:290:5:290:23 | call to super | calls.rb:290:11:290:23 | { ... } | +| calls.rb:291:5:291:26 | call to super | calls.rb:291:11:291:26 | do ... end | +| calls.rb:292:5:292:30 | call to super | calls.rb:292:16:292:30 | { ... } | +| calls.rb:293:5:293:33 | call to super | calls.rb:293:16:293:33 | do ... end | +yieldCalls +| calls.rb:31:3:31:7 | yield ... | +| calls.rb:36:3:36:16 | yield ... | +superCalls +| calls.rb:286:5:286:9 | call to super | +| calls.rb:287:5:287:11 | call to super | +| calls.rb:288:5:288:16 | call to super | +| calls.rb:289:5:289:17 | call to super | +| calls.rb:290:5:290:23 | call to super | +| calls.rb:291:5:291:26 | call to super | +| calls.rb:292:5:292:30 | call to super | +| calls.rb:293:5:293:33 | call to super | +| calls.rb:305:5:305:9 | call to super | +superCallsWithArguments +| calls.rb:288:5:288:16 | call to super | 0 | calls.rb:288:11:288:16 | "blah" | +| calls.rb:289:5:289:17 | call to super | 0 | calls.rb:289:11:289:11 | 1 | +| calls.rb:289:5:289:17 | call to super | 1 | calls.rb:289:14:289:14 | 2 | +| calls.rb:289:5:289:17 | call to super | 2 | calls.rb:289:17:289:17 | 3 | +| calls.rb:292:5:292:30 | call to super | 0 | calls.rb:292:11:292:11 | 4 | +| calls.rb:292:5:292:30 | call to super | 1 | calls.rb:292:14:292:14 | 5 | +| calls.rb:293:5:293:33 | call to super | 0 | calls.rb:293:11:293:11 | 6 | +| calls.rb:293:5:293:33 | call to super | 1 | calls.rb:293:14:293:14 | 7 | +superCallsWithBlock +| calls.rb:290:5:290:23 | call to super | calls.rb:290:11:290:23 | { ... } | +| calls.rb:291:5:291:26 | call to super | calls.rb:291:11:291:26 | do ... end | +| calls.rb:292:5:292:30 | call to super | calls.rb:292:16:292:30 | { ... } | +| calls.rb:293:5:293:33 | call to super | calls.rb:293:16:293:33 | do ... end | +setterCalls +| calls.rb:314:1:314:8 | call to foo= | +| calls.rb:315:1:315:6 | call to []= | +| calls.rb:316:1:316:8 | call to foo= | +| calls.rb:316:12:316:19 | call to bar= | +| calls.rb:316:22:316:27 | call to []= | +| calls.rb:317:5:317:10 | call to []= | +| calls.rb:318:1:318:10 | call to count= | +| calls.rb:319:1:319:6 | call to []= | +| calls.rb:320:1:320:32 | call to []= | diff --git a/ruby/ql/test/library-tests/ast/calls/calls.ql b/ruby/ql/test/library-tests/ast/calls/calls.ql new file mode 100644 index 000000000000..4d244c2cf4cb --- /dev/null +++ b/ruby/ql/test/library-tests/ast/calls/calls.ql @@ -0,0 +1,33 @@ +import ruby + +private string getMethodName(Call c) { + result = c.(MethodCall).getMethodName() + or + not c instanceof MethodCall and result = "(none)" +} + +query predicate callsWithNoReceiverArgumentsOrBlock(Call c, string name) { + name = getMethodName(c) and + not exists(c.(MethodCall).getReceiver()) and + not exists(c.getAnArgument()) and + not exists(c.(MethodCall).getBlock()) +} + +query predicate callsWithArguments(Call c, string name, int n, Expr argN) { + name = getMethodName(c) and + argN = c.getArgument(n) +} + +query predicate callsWithReceiver(MethodCall c, Expr rcv) { rcv = c.getReceiver() } + +query predicate callsWithBlock(MethodCall c, Block b) { b = c.getBlock() } + +query predicate yieldCalls(YieldCall c) { any() } + +query predicate superCalls(SuperCall c) { any() } + +query predicate superCallsWithArguments(SuperCall c, int n, Expr argN) { argN = c.getArgument(n) } + +query predicate superCallsWithBlock(SuperCall c, Block b) { b = c.getBlock() } + +query predicate setterCalls(SetterMethodCall c) { any() } diff --git a/ruby/ql/test/library-tests/ast/calls/calls.rb b/ruby/ql/test/library-tests/ast/calls/calls.rb new file mode 100644 index 000000000000..1362b043e8a1 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/calls/calls.rb @@ -0,0 +1,320 @@ +# call with no receiver, arguments, or block +foo() + +# call whose name is a scope resolution +Foo::bar() + +# call whose name is a global scope resolution +::bar() + +# call with a receiver, no arguments or block +123.bar + +# call with arguments +foo 0, 1, 2 + +# call with curly brace block +foo { |x| x + 1 } + +# call with do block +foo do |x| + x + 1 +end + +# call with receiver, arguments, and a block +123.bar('foo') do |x| + x + 1 +end + +# a yield call +def method_that_yields + yield +end + +# a yield call with arguments +def another_method_that_yields + yield 100, 200 +end + +# ------------------------------------------------------------------------------ +# Calls without parentheses or arguments are parsed by tree-sitter simply as +# `identifier` nodes (or `scope_resolution` nodes whose `name` field is an +# `identifier), so here we test that our AST library correctly represents them +# as calls in all the following contexts. + +# root level (child of program) +foo +X::foo + +# in a parenthesized statement +(foo) +(X::foo) + +# in an argument list +some_func(foo) +some_func(X::foo) + +# in an array +[foo] +[X::foo] + +# RHS of an assignment +var1 = foo +var1 = X::foo + +# RHS an operator assignment +var1 += bar +var1 += X::bar + +# RHS assignment list +var1 = foo, X::bar + +# in a begin-end block +begin + foo + X::foo +end + +# in a BEGIN block +BEGIN { foo; X::bar } + +# in an END block +END { foo; X::bar } + +# both operands of a binary operation +foo + X::bar + +# unary operand +!foo +~X::bar + +# in a curly brace block +foo() { bar; X::baz } + +# in a do-end block +foo() do + bar + X::baz +end + +# the receiver in a call can itself be a call +foo.bar() +bar.baz() + +# the value for a case expr +# and the when pattern and body +case foo +when bar + baz +end +case X::foo +when X::bar + X::baz +end + +# in a class definition +class MyClass + foo + X::bar +end + +# in a superclass +class MyClass < foo +end +class MyClass2 < X::foo +end + +# in a singleton class value or body +class << foo + bar +end +class << X::foo + X::bar +end + +# in a method body +def some_method + foo + X::bar +end + +# in a singleton method object or body +def foo.some_method + bar + X::baz +end + +# in the default value for a keyword parameter +def method_with_keyword_param(keyword: foo) +end +def method_with_keyword_param2(keyword: X::foo) +end + +# in the default value for an optional parameter +def method_with_optional_param(param = foo) +end +def method_with_optional_param2(param = X::foo) +end + +# in a module +module SomeModule + foo + X::bar +end + +# ternary if: condition, consequence, and alternative can all be calls +foo ? bar : baz +X::foo ? X::bar : X::baz + +# if/elsif/else conditions and bodies +if foo + wibble +elsif bar + wobble +else + wabble +end +if X::foo + X::wibble +elsif X::bar + X::wobble +else + X::wabble +end + +# if-modifier condition/body +bar if foo +X::bar if X::foo + +# unless condition/body +unless foo + bar +end +unless X::foo + X::bar +end + +# unless-modifier condition/body +bar unless foo +X::bar unless X::foo + +# while loop condition/body +while foo do + bar +end +while X::foo do + X::bar +end + +# while-modifier loop condition/body +bar while foo +X::bar while X::foo + +# until loop condition/body +until foo do + bar +end +until X::foo do + X::bar +end + +# until-modifier loop condition/body +bar until foo +X::bar until X::foo + +# the collection being iterated over in a for loop, and the body +for x in bar + baz +end +for x in X::bar + X::baz +end + +# in an array indexing operation, both the object and the index can be calls +foo[bar] +X::foo[X::bar] + +# interpolation +"foo-#{bar}-#{X::baz}" + +# the scope in a scope resolution +foo::Bar +X::foo::Bar + +# in a range +foo..bar +X::foo..X::bar + +# the key/value in a hash pair +{ foo => bar, X::foo => X::bar } + +# rescue exceptions and ensure +begin +rescue foo +ensure bar +end +begin +rescue X::foo +ensure X::bar +end + +# rescue-modifier body and handler +foo rescue bar +X::foo rescue X::bar + +# block argument +foo(&bar) +foo(&X::bar) + +# splat argument +foo(*bar) +foo(*X::bar) + +# hash-splat argument +foo(**bar) +foo(**X::bar) + +# the value in a keyword argument +foo(blah: bar) +foo(blah: X::bar) + +# ------------------------------------------------------------------------------ +# calls to `super` + +class MyClass + def my_method + super + super() + super 'blah' + super 1, 2, 3 + super { |x| x + 1 } + super do |x| x * 2 end + super 4, 5 { |x| x + 100 } + super 6, 7 do |x| x + 200 end + end +end + +# ------------------------------------------------------------------------------ +# calls to methods simply named `super`, i.e. *not* calls to the same method in +# a parent classs, so these should be Call but not SuperCall + +class AnotherClass + def another_method + foo.super + self.super + super.super # we expect the receiver to be a SuperCall, while the outer call should not (it's just a regular Call) + end +end + +# calls without method name +foo.() +foo.(1) + +# setter calls +self.foo = 10 +foo[0] = 10 +self.foo, *self.bar, foo[4] = [1, 2, 3, 4] +a, *foo[5] = [1, 2, 3] +self.count += 1 +foo[0] += 1 +foo.bar[0, foo.baz, foo.boo + 1] *= 2 diff --git a/ruby/ql/test/library-tests/ast/constants/constants.expected b/ruby/ql/test/library-tests/ast/constants/constants.expected new file mode 100644 index 000000000000..9e7b8cd6c066 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/constants/constants.expected @@ -0,0 +1,57 @@ +constantAccess +| constants.rb:1:1:15:3 | ModuleA | write | ModuleA | ModuleDeclaration | +| constants.rb:2:5:4:7 | ClassA | write | ClassA | ClassDeclaration | +| constants.rb:3:9:3:15 | CONST_A | write | CONST_A | ConstantAssignment | +| constants.rb:6:5:6:11 | CONST_B | write | CONST_B | ConstantAssignment | +| constants.rb:8:5:14:7 | ModuleB | write | ModuleB | ModuleDeclaration | +| constants.rb:9:9:10:11 | ClassB | write | ClassB | ClassDeclaration | +| constants.rb:9:24:9:27 | Base | read | Base | ConstantReadAccess | +| constants.rb:12:9:13:11 | ClassC | write | ClassC | ClassDeclaration | +| constants.rb:12:24:12:24 | X | read | X | ConstantReadAccess | +| constants.rb:12:24:12:27 | Y | read | Y | ConstantReadAccess | +| constants.rb:12:24:12:30 | Z | read | Z | ConstantReadAccess | +| constants.rb:17:1:17:8 | GREETING | write | GREETING | ConstantAssignment | +| constants.rb:17:22:17:28 | ModuleA | read | ModuleA | ConstantReadAccess | +| constants.rb:17:22:17:36 | ClassA | read | ClassA | ConstantReadAccess | +| constants.rb:17:22:17:45 | CONST_A | read | CONST_A | ConstantReadAccess | +| constants.rb:17:49:17:55 | ModuleA | read | ModuleA | ConstantReadAccess | +| constants.rb:17:49:17:64 | CONST_B | read | CONST_B | ConstantReadAccess | +| constants.rb:20:5:20:9 | Names | write | Names | ConstantAssignment | +| constants.rb:20:13:20:37 | Array | read | Array | ConstantReadAccess | +| constants.rb:22:5:22:9 | Names | read | Names | ConstantReadAccess | +| constants.rb:23:18:23:25 | GREETING | read | GREETING | ConstantReadAccess | +| constants.rb:31:1:32:3 | ClassD | write | ClassD | ClassDeclaration | +| constants.rb:31:7:31:13 | ModuleA | read | ModuleA | ConstantReadAccess | +| constants.rb:31:25:31:31 | ModuleA | read | ModuleA | ConstantReadAccess | +| constants.rb:31:25:31:39 | ClassA | read | ClassA | ConstantReadAccess | +| constants.rb:34:1:35:3 | ModuleC | write | ModuleC | ModuleDeclaration | +| constants.rb:34:8:34:14 | ModuleA | read | ModuleA | ConstantReadAccess | +| constants.rb:37:1:37:7 | ModuleA | read | ModuleA | ConstantReadAccess | +| constants.rb:37:1:37:16 | ModuleB | read | ModuleB | ConstantReadAccess | +| constants.rb:37:1:37:26 | MAX_SIZE | write | MAX_SIZE | ConstantAssignment | +| constants.rb:39:6:39:12 | ModuleA | read | ModuleA | ConstantReadAccess | +| constants.rb:39:6:39:21 | ModuleB | read | ModuleB | ConstantReadAccess | +| constants.rb:39:6:39:31 | MAX_SIZE | read | MAX_SIZE | ConstantReadAccess | +| constants.rb:41:6:41:13 | GREETING | read | GREETING | ConstantReadAccess | +| constants.rb:42:6:42:15 | GREETING | read | GREETING | ConstantReadAccess | +getConst +| constants.rb:1:1:15:3 | ModuleA | CONST_B | constants.rb:6:15:6:23 | "const_b" | +| constants.rb:2:5:4:7 | ModuleA::ClassA | CONST_A | constants.rb:3:19:3:27 | "const_a" | +| file://:0:0:0:0 | Object | GREETING | constants.rb:17:12:17:64 | ... + ... | +lookupConst +| constants.rb:1:1:15:3 | ModuleA | CONST_B | constants.rb:6:15:6:23 | "const_b" | +| constants.rb:2:5:4:7 | ModuleA::ClassA | CONST_A | constants.rb:3:19:3:27 | "const_a" | +| constants.rb:2:5:4:7 | ModuleA::ClassA | GREETING | constants.rb:17:12:17:64 | ... + ... | +| constants.rb:8:5:14:7 | ModuleA::ModuleB | MAX_SIZE | constants.rb:37:30:37:33 | 1024 | +| constants.rb:9:9:10:11 | ModuleA::ModuleB::ClassB | GREETING | constants.rb:17:12:17:64 | ... + ... | +| constants.rb:12:9:13:11 | ModuleA::ModuleB::ClassC | GREETING | constants.rb:17:12:17:64 | ... + ... | +| constants.rb:31:1:32:3 | ModuleA::ClassD | CONST_A | constants.rb:3:19:3:27 | "const_a" | +| constants.rb:31:1:32:3 | ModuleA::ClassD | GREETING | constants.rb:17:12:17:64 | ... + ... | +| file://:0:0:0:0 | Object | GREETING | constants.rb:17:12:17:64 | ... + ... | +constantValue +| constants.rb:17:22:17:45 | CONST_A | constants.rb:3:19:3:27 | "const_a" | +| constants.rb:17:49:17:64 | CONST_B | constants.rb:6:15:6:23 | "const_b" | +| constants.rb:23:18:23:25 | GREETING | constants.rb:17:12:17:64 | ... + ... | +| constants.rb:39:6:39:31 | MAX_SIZE | constants.rb:37:30:37:33 | 1024 | +| constants.rb:41:6:41:13 | GREETING | constants.rb:17:12:17:64 | ... + ... | +| constants.rb:42:6:42:15 | GREETING | constants.rb:17:12:17:64 | ... + ... | diff --git a/ruby/ql/test/library-tests/ast/constants/constants.ql b/ruby/ql/test/library-tests/ast/constants/constants.ql new file mode 100644 index 000000000000..5ab151d603cd --- /dev/null +++ b/ruby/ql/test/library-tests/ast/constants/constants.ql @@ -0,0 +1,18 @@ +import ruby +import codeql.ruby.ast.internal.Module as M + +query predicate constantAccess(ConstantAccess a, string kind, string name, string cls) { + ( + a instanceof ConstantReadAccess and kind = "read" + or + a instanceof ConstantWriteAccess and kind = "write" + ) and + name = a.getName() and + cls = a.getAPrimaryQlClass() +} + +query Expr getConst(Module m, string name) { result = M::ExposedForTestingOnly::getConst(m, name) } + +query Expr lookupConst(Module m, string name) { result = M::lookupConst(m, name) } + +query predicate constantValue(ConstantReadAccess a, Expr e) { e = a.getValue() } diff --git a/ruby/ql/test/library-tests/ast/constants/constants.rb b/ruby/ql/test/library-tests/ast/constants/constants.rb new file mode 100644 index 000000000000..00acddef9d1d --- /dev/null +++ b/ruby/ql/test/library-tests/ast/constants/constants.rb @@ -0,0 +1,42 @@ +module ModuleA + class ClassA + CONST_A = "const_a" + end + + CONST_B = "const_b" + + module ModuleB + class ClassB < Base + end + + class ClassC < X::Y::Z + end + end +end + +GREETING = 'Hello' + ModuleA::ClassA::CONST_A + ModuleA::CONST_B + +def foo + Names = ['Vera', 'Chuck', 'Dave'] + + Names.each do |name| + puts "#{ GREETING } #{ name }" + end + + # A call to Kernel::Array; despite beginning with an upper-case character, + # we don't consider this to be a constant access. + Array('foo') +end + +class ModuleA::ClassD < ModuleA::ClassA +end + +module ModuleA::ModuleC +end + +ModuleA::ModuleB::MAX_SIZE = 1024 + +puts ModuleA::ModuleB::MAX_SIZE + +puts GREETING +puts ::GREETING diff --git a/ruby/ql/test/library-tests/ast/control/CaseExpr.expected b/ruby/ql/test/library-tests/ast/control/CaseExpr.expected new file mode 100644 index 000000000000..ff2b223690a6 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/control/CaseExpr.expected @@ -0,0 +1,22 @@ +caseValues +| cases.rb:8:1:15:3 | case ... | cases.rb:8:6:8:6 | a | +caseNoValues +| cases.rb:18:1:22:3 | case ... | +caseElseBranches +| cases.rb:8:1:15:3 | case ... | cases.rb:13:1:14:7 | else ... | +caseNoElseBranches +| cases.rb:18:1:22:3 | case ... | +caseWhenBranches +| cases.rb:8:1:15:3 | case ... | cases.rb:9:1:10:7 | when ... | 0 | cases.rb:9:6:9:6 | b | cases.rb:9:7:10:7 | then ... | +| cases.rb:8:1:15:3 | case ... | cases.rb:11:1:12:7 | when ... | 0 | cases.rb:11:6:11:6 | c | cases.rb:11:10:12:7 | then ... | +| cases.rb:8:1:15:3 | case ... | cases.rb:11:1:12:7 | when ... | 1 | cases.rb:11:9:11:9 | d | cases.rb:11:10:12:7 | then ... | +| cases.rb:18:1:22:3 | case ... | cases.rb:19:1:19:19 | when ... | 0 | cases.rb:19:6:19:10 | ... > ... | cases.rb:19:13:19:19 | then ... | +| cases.rb:18:1:22:3 | case ... | cases.rb:20:1:20:19 | when ... | 0 | cases.rb:20:6:20:11 | ... == ... | cases.rb:20:13:20:19 | then ... | +| cases.rb:18:1:22:3 | case ... | cases.rb:21:1:21:19 | when ... | 0 | cases.rb:21:6:21:10 | ... < ... | cases.rb:21:13:21:19 | then ... | +caseAllBranches +| cases.rb:8:1:15:3 | case ... | 0 | cases.rb:9:1:10:7 | when ... | +| cases.rb:8:1:15:3 | case ... | 1 | cases.rb:11:1:12:7 | when ... | +| cases.rb:8:1:15:3 | case ... | 2 | cases.rb:13:1:14:7 | else ... | +| cases.rb:18:1:22:3 | case ... | 0 | cases.rb:19:1:19:19 | when ... | +| cases.rb:18:1:22:3 | case ... | 1 | cases.rb:20:1:20:19 | when ... | +| cases.rb:18:1:22:3 | case ... | 2 | cases.rb:21:1:21:19 | when ... | diff --git a/ruby/ql/test/library-tests/ast/control/CaseExpr.ql b/ruby/ql/test/library-tests/ast/control/CaseExpr.ql new file mode 100644 index 000000000000..eaef9359f554 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/control/CaseExpr.ql @@ -0,0 +1,19 @@ +import ruby + +query predicate caseValues(CaseExpr c, Expr value) { value = c.getValue() } + +query predicate caseNoValues(CaseExpr c) { not exists(c.getValue()) } + +query predicate caseElseBranches(CaseExpr c, StmtSequence elseBranch) { + elseBranch = c.getElseBranch() +} + +query predicate caseNoElseBranches(CaseExpr c) { not exists(c.getElseBranch()) } + +query predicate caseWhenBranches(CaseExpr c, WhenExpr when, int pIndex, Expr p, StmtSequence body) { + when = c.getAWhenBranch() and + p = when.getPattern(pIndex) and + body = when.getBody() +} + +query predicate caseAllBranches(CaseExpr c, int n, Expr branch) { branch = c.getBranch(n) } diff --git a/ruby/ql/test/library-tests/ast/control/ConditionalExpr.expected b/ruby/ql/test/library-tests/ast/control/ConditionalExpr.expected new file mode 100644 index 000000000000..3d78655dbbbd --- /dev/null +++ b/ruby/ql/test/library-tests/ast/control/ConditionalExpr.expected @@ -0,0 +1,43 @@ +conditionalExprs +| conditionals.rb:10:1:12:3 | if ... | IfExpr | conditionals.rb:10:4:10:8 | ... > ... | conditionals.rb:10:10:11:5 | then ... | true | +| conditionals.rb:15:1:19:3 | if ... | IfExpr | conditionals.rb:15:4:15:9 | ... == ... | conditionals.rb:15:10:16:5 | then ... | true | +| conditionals.rb:15:1:19:3 | if ... | IfExpr | conditionals.rb:15:4:15:9 | ... == ... | conditionals.rb:17:1:18:5 | else ... | false | +| conditionals.rb:22:1:30:3 | if ... | IfExpr | conditionals.rb:22:4:22:9 | ... == ... | conditionals.rb:22:11:23:5 | then ... | true | +| conditionals.rb:22:1:30:3 | if ... | IfExpr | conditionals.rb:22:4:22:9 | ... == ... | conditionals.rb:24:1:29:5 | elsif ... | false | +| conditionals.rb:24:1:29:5 | elsif ... | IfExpr | conditionals.rb:24:7:24:12 | ... == ... | conditionals.rb:24:14:25:5 | then ... | true | +| conditionals.rb:24:1:29:5 | elsif ... | IfExpr | conditionals.rb:24:7:24:12 | ... == ... | conditionals.rb:26:1:29:5 | elsif ... | false | +| conditionals.rb:26:1:29:5 | elsif ... | IfExpr | conditionals.rb:26:7:26:12 | ... == ... | conditionals.rb:26:14:27:5 | then ... | true | +| conditionals.rb:26:1:29:5 | elsif ... | IfExpr | conditionals.rb:26:7:26:12 | ... == ... | conditionals.rb:28:1:29:5 | else ... | false | +| conditionals.rb:33:1:37:3 | if ... | IfExpr | conditionals.rb:33:4:33:9 | ... == ... | conditionals.rb:33:10:34:5 | then ... | true | +| conditionals.rb:33:1:37:3 | if ... | IfExpr | conditionals.rb:33:4:33:9 | ... == ... | conditionals.rb:35:1:36:5 | elsif ... | false | +| conditionals.rb:35:1:36:5 | elsif ... | IfExpr | conditionals.rb:35:7:35:12 | ... == ... | conditionals.rb:35:13:36:5 | then ... | true | +| conditionals.rb:40:1:42:3 | unless ... | UnlessExpr | conditionals.rb:40:8:40:12 | ... > ... | conditionals.rb:40:14:41:5 | then ... | false | +| conditionals.rb:45:1:49:3 | unless ... | UnlessExpr | conditionals.rb:45:8:45:13 | ... == ... | conditionals.rb:45:14:46:5 | then ... | false | +| conditionals.rb:45:1:49:3 | unless ... | UnlessExpr | conditionals.rb:45:8:45:13 | ... == ... | conditionals.rb:47:1:48:5 | else ... | true | +| conditionals.rb:52:1:52:14 | ... if ... | IfModifierExpr | conditionals.rb:52:10:52:14 | ... > ... | conditionals.rb:52:1:52:5 | ... = ... | true | +| conditionals.rb:55:1:55:18 | ... unless ... | UnlessModifierExpr | conditionals.rb:55:14:55:18 | ... < ... | conditionals.rb:55:1:55:5 | ... = ... | false | +| conditionals.rb:58:5:58:25 | ... ? ... : ... | TernaryIfExpr | conditionals.rb:58:5:58:9 | ... > ... | conditionals.rb:58:13:58:17 | ... + ... | true | +| conditionals.rb:58:5:58:25 | ... ? ... : ... | TernaryIfExpr | conditionals.rb:58:5:58:9 | ... > ... | conditionals.rb:58:21:58:25 | ... - ... | false | +| conditionals.rb:61:1:64:3 | if ... | IfExpr | conditionals.rb:61:4:61:8 | ... > ... | conditionals.rb:61:10:62:5 | then ... | true | +| conditionals.rb:61:1:64:3 | if ... | IfExpr | conditionals.rb:61:4:61:8 | ... > ... | conditionals.rb:63:1:63:4 | else ... | false | +| conditionals.rb:67:1:70:3 | if ... | IfExpr | conditionals.rb:67:4:67:8 | ... > ... | conditionals.rb:67:10:67:13 | then ... | true | +| conditionals.rb:67:1:70:3 | if ... | IfExpr | conditionals.rb:67:4:67:8 | ... > ... | conditionals.rb:68:1:69:5 | else ... | false | +ifExprs +| conditionals.rb:10:1:12:3 | if ... | IfExpr | conditionals.rb:10:4:10:8 | ... > ... | conditionals.rb:10:10:11:5 | then ... | (none) | false | +| conditionals.rb:15:1:19:3 | if ... | IfExpr | conditionals.rb:15:4:15:9 | ... == ... | conditionals.rb:15:10:16:5 | then ... | else ... | false | +| conditionals.rb:22:1:30:3 | if ... | IfExpr | conditionals.rb:22:4:22:9 | ... == ... | conditionals.rb:22:11:23:5 | then ... | elsif ... | false | +| conditionals.rb:24:1:29:5 | elsif ... | IfExpr | conditionals.rb:24:7:24:12 | ... == ... | conditionals.rb:24:14:25:5 | then ... | elsif ... | true | +| conditionals.rb:26:1:29:5 | elsif ... | IfExpr | conditionals.rb:26:7:26:12 | ... == ... | conditionals.rb:26:14:27:5 | then ... | else ... | true | +| conditionals.rb:33:1:37:3 | if ... | IfExpr | conditionals.rb:33:4:33:9 | ... == ... | conditionals.rb:33:10:34:5 | then ... | elsif ... | false | +| conditionals.rb:35:1:36:5 | elsif ... | IfExpr | conditionals.rb:35:7:35:12 | ... == ... | conditionals.rb:35:13:36:5 | then ... | (none) | true | +| conditionals.rb:61:1:64:3 | if ... | IfExpr | conditionals.rb:61:4:61:8 | ... > ... | conditionals.rb:61:10:62:5 | then ... | else ... | false | +| conditionals.rb:67:1:70:3 | if ... | IfExpr | conditionals.rb:67:4:67:8 | ... > ... | conditionals.rb:67:10:67:13 | then ... | else ... | false | +unlessExprs +| conditionals.rb:40:1:42:3 | unless ... | UnlessExpr | conditionals.rb:40:8:40:12 | ... > ... | conditionals.rb:40:14:41:5 | then ... | (none) | +| conditionals.rb:45:1:49:3 | unless ... | UnlessExpr | conditionals.rb:45:8:45:13 | ... == ... | conditionals.rb:45:14:46:5 | then ... | else ... | +ifModifierExprs +| conditionals.rb:52:1:52:14 | ... if ... | IfModifierExpr | conditionals.rb:52:10:52:14 | ... > ... | conditionals.rb:52:1:52:5 | ... = ... | +unlessModifierExprs +| conditionals.rb:55:1:55:18 | ... unless ... | UnlessModifierExpr | conditionals.rb:55:14:55:18 | ... < ... | conditionals.rb:55:1:55:5 | ... = ... | +ternaryIfExprs +| conditionals.rb:58:5:58:25 | ... ? ... : ... | TernaryIfExpr | conditionals.rb:58:5:58:9 | ... > ... | conditionals.rb:58:13:58:17 | ... + ... | conditionals.rb:58:21:58:25 | ... - ... | diff --git a/ruby/ql/test/library-tests/ast/control/ConditionalExpr.ql b/ruby/ql/test/library-tests/ast/control/ConditionalExpr.ql new file mode 100644 index 000000000000..bd0e5fbe6234 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/control/ConditionalExpr.ql @@ -0,0 +1,49 @@ +import ruby + +query predicate conditionalExprs( + ConditionalExpr e, string pClass, Expr cond, Expr branch, boolean branchCond +) { + pClass = e.getAPrimaryQlClass() and + cond = e.getCondition() and + branch = e.getBranch(branchCond) +} + +query predicate ifExprs( + IfExpr e, string pClass, Expr cond, StmtSequence thenExpr, string elseStr, boolean isElsif +) { + pClass = e.getAPrimaryQlClass() and + cond = e.getCondition() and + thenExpr = e.getThen() and + (if exists(e.getElse()) then elseStr = e.getElse().toString() else elseStr = "(none)") and + if e.isElsif() then isElsif = true else isElsif = false +} + +query predicate unlessExprs( + UnlessExpr e, string pClass, Expr cond, StmtSequence thenExpr, string elseStr +) { + pClass = e.getAPrimaryQlClass() and + cond = e.getCondition() and + thenExpr = e.getThen() and + if exists(e.getElse()) then elseStr = e.getElse().toString() else elseStr = "(none)" +} + +query predicate ifModifierExprs(IfModifierExpr e, string pClass, Expr cond, Expr expr) { + pClass = e.getAPrimaryQlClass() and + cond = e.getCondition() and + expr = e.getBody() +} + +query predicate unlessModifierExprs(UnlessModifierExpr e, string pClass, Expr cond, Expr expr) { + pClass = e.getAPrimaryQlClass() and + cond = e.getCondition() and + expr = e.getBody() +} + +query predicate ternaryIfExprs( + TernaryIfExpr e, string pClass, Expr cond, Expr thenExpr, Expr elseExpr +) { + pClass = e.getAPrimaryQlClass() and + cond = e.getCondition() and + thenExpr = e.getThen() and + elseExpr = e.getElse() +} diff --git a/ruby/ql/test/library-tests/ast/control/ControlExpr.expected b/ruby/ql/test/library-tests/ast/control/ControlExpr.expected new file mode 100644 index 000000000000..9088fd6ed142 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/control/ControlExpr.expected @@ -0,0 +1,27 @@ +| cases.rb:8:1:15:3 | case ... | CaseExpr | +| cases.rb:18:1:22:3 | case ... | CaseExpr | +| conditionals.rb:10:1:12:3 | if ... | IfExpr | +| conditionals.rb:15:1:19:3 | if ... | IfExpr | +| conditionals.rb:22:1:30:3 | if ... | IfExpr | +| conditionals.rb:24:1:29:5 | elsif ... | IfExpr | +| conditionals.rb:26:1:29:5 | elsif ... | IfExpr | +| conditionals.rb:33:1:37:3 | if ... | IfExpr | +| conditionals.rb:35:1:36:5 | elsif ... | IfExpr | +| conditionals.rb:40:1:42:3 | unless ... | UnlessExpr | +| conditionals.rb:45:1:49:3 | unless ... | UnlessExpr | +| conditionals.rb:52:1:52:14 | ... if ... | IfModifierExpr | +| conditionals.rb:55:1:55:18 | ... unless ... | UnlessModifierExpr | +| conditionals.rb:58:5:58:25 | ... ? ... : ... | TernaryIfExpr | +| conditionals.rb:61:1:64:3 | if ... | IfExpr | +| conditionals.rb:67:1:70:3 | if ... | IfExpr | +| loops.rb:9:1:12:3 | for ... in ... | ForExpr | +| loops.rb:16:1:19:3 | for ... in ... | ForExpr | +| loops.rb:22:1:25:3 | for ... in ... | ForExpr | +| loops.rb:28:1:32:3 | for ... in ... | ForExpr | +| loops.rb:35:1:39:3 | while ... | WhileExpr | +| loops.rb:42:1:45:3 | while ... | WhileExpr | +| loops.rb:48:1:48:19 | ... while ... | WhileModifierExpr | +| loops.rb:51:1:54:3 | until ... | UntilExpr | +| loops.rb:57:1:60:3 | until ... | UntilExpr | +| loops.rb:63:1:63:19 | ... until ... | UntilModifierExpr | +| loops.rb:66:1:67:3 | while ... | WhileExpr | diff --git a/ruby/ql/test/library-tests/ast/control/ControlExpr.ql b/ruby/ql/test/library-tests/ast/control/ControlExpr.ql new file mode 100644 index 000000000000..ad802180efbe --- /dev/null +++ b/ruby/ql/test/library-tests/ast/control/ControlExpr.ql @@ -0,0 +1,3 @@ +import ruby + +query predicate controlExprs(ControlExpr c, string pClass) { pClass = c.getAPrimaryQlClass() } diff --git a/ruby/ql/test/library-tests/ast/control/Loop.expected b/ruby/ql/test/library-tests/ast/control/Loop.expected new file mode 100644 index 000000000000..02bde8f9e368 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/control/Loop.expected @@ -0,0 +1,50 @@ +loops +| loops.rb:9:1:12:3 | for ... in ... | ForExpr | loops.rb:9:15:12:3 | do ... | StmtSequence | +| loops.rb:16:1:19:3 | for ... in ... | ForExpr | loops.rb:16:15:19:3 | do ... | StmtSequence | +| loops.rb:22:1:25:3 | for ... in ... | ForExpr | loops.rb:22:35:25:3 | do ... | StmtSequence | +| loops.rb:28:1:32:3 | for ... in ... | ForExpr | loops.rb:28:37:32:3 | do ... | StmtSequence | +| loops.rb:35:1:39:3 | while ... | WhileExpr | loops.rb:35:12:39:3 | do ... | StmtSequence | +| loops.rb:42:1:45:3 | while ... | WhileExpr | loops.rb:42:13:45:3 | do ... | StmtSequence | +| loops.rb:48:1:48:19 | ... while ... | WhileModifierExpr | loops.rb:48:1:48:6 | ... += ... | AssignAddExpr | +| loops.rb:51:1:54:3 | until ... | UntilExpr | loops.rb:51:13:54:3 | do ... | StmtSequence | +| loops.rb:57:1:60:3 | until ... | UntilExpr | loops.rb:57:13:60:3 | do ... | StmtSequence | +| loops.rb:63:1:63:19 | ... until ... | UntilModifierExpr | loops.rb:63:1:63:6 | ... -= ... | AssignSubExpr | +| loops.rb:66:1:67:3 | while ... | WhileExpr | loops.rb:66:13:67:3 | do ... | StmtSequence | +conditionalLoops +| loops.rb:35:1:39:3 | while ... | WhileExpr | loops.rb:35:7:35:11 | ... < ... | loops.rb:35:12:39:3 | do ... | StmtSequence | +| loops.rb:42:1:45:3 | while ... | WhileExpr | loops.rb:42:7:42:11 | ... < ... | loops.rb:42:13:45:3 | do ... | StmtSequence | +| loops.rb:48:1:48:19 | ... while ... | WhileModifierExpr | loops.rb:48:14:48:19 | ... >= ... | loops.rb:48:1:48:6 | ... += ... | AssignAddExpr | +| loops.rb:51:1:54:3 | until ... | UntilExpr | loops.rb:51:7:51:12 | ... == ... | loops.rb:51:13:54:3 | do ... | StmtSequence | +| loops.rb:57:1:60:3 | until ... | UntilExpr | loops.rb:57:7:57:11 | ... > ... | loops.rb:57:13:60:3 | do ... | StmtSequence | +| loops.rb:63:1:63:19 | ... until ... | UntilModifierExpr | loops.rb:63:14:63:19 | ... == ... | loops.rb:63:1:63:6 | ... -= ... | AssignSubExpr | +| loops.rb:66:1:67:3 | while ... | WhileExpr | loops.rb:66:7:66:11 | ... < ... | loops.rb:66:13:67:3 | do ... | StmtSequence | +forExprs +| loops.rb:9:1:12:3 | for ... in ... | loops.rb:9:5:9:5 | n | loops.rb:9:15:12:3 | do ... | 0 | loops.rb:10:5:10:12 | ... += ... | +| loops.rb:9:1:12:3 | for ... in ... | loops.rb:9:5:9:5 | n | loops.rb:9:15:12:3 | do ... | 1 | loops.rb:11:5:11:11 | ... = ... | +| loops.rb:16:1:19:3 | for ... in ... | loops.rb:16:5:16:5 | n | loops.rb:16:15:19:3 | do ... | 0 | loops.rb:17:5:17:12 | ... += ... | +| loops.rb:16:1:19:3 | for ... in ... | loops.rb:16:5:16:5 | n | loops.rb:16:15:19:3 | do ... | 1 | loops.rb:18:5:18:12 | ... -= ... | +| loops.rb:22:1:25:3 | for ... in ... | loops.rb:22:5:22:14 | (..., ...) | loops.rb:22:35:25:3 | do ... | 0 | loops.rb:23:3:23:14 | ... += ... | +| loops.rb:22:1:25:3 | for ... in ... | loops.rb:22:5:22:14 | (..., ...) | loops.rb:22:35:25:3 | do ... | 1 | loops.rb:24:3:24:14 | ... *= ... | +| loops.rb:28:1:32:3 | for ... in ... | loops.rb:28:5:28:16 | (..., ...) | loops.rb:28:37:32:3 | do ... | 0 | loops.rb:29:3:29:14 | ... += ... | +| loops.rb:28:1:32:3 | for ... in ... | loops.rb:28:5:28:16 | (..., ...) | loops.rb:28:37:32:3 | do ... | 1 | loops.rb:30:3:30:14 | ... /= ... | +| loops.rb:28:1:32:3 | for ... in ... | loops.rb:28:5:28:16 | (..., ...) | loops.rb:28:37:32:3 | do ... | 2 | loops.rb:31:3:31:7 | break | +forExprsTuplePatterns +| loops.rb:22:1:25:3 | for ... in ... | loops.rb:22:5:22:14 | (..., ...) | 0 | loops.rb:22:5:22:7 | key | +| loops.rb:22:1:25:3 | for ... in ... | loops.rb:22:5:22:14 | (..., ...) | 1 | loops.rb:22:10:22:14 | value | +| loops.rb:28:1:32:3 | for ... in ... | loops.rb:28:5:28:16 | (..., ...) | 0 | loops.rb:28:6:28:8 | key | +| loops.rb:28:1:32:3 | for ... in ... | loops.rb:28:5:28:16 | (..., ...) | 1 | loops.rb:28:11:28:15 | value | +whileExprs +| loops.rb:35:1:39:3 | while ... | loops.rb:35:7:35:11 | ... < ... | loops.rb:35:12:39:3 | do ... | 0 | loops.rb:36:3:36:8 | ... += ... | +| loops.rb:35:1:39:3 | while ... | loops.rb:35:7:35:11 | ... < ... | loops.rb:35:12:39:3 | do ... | 1 | loops.rb:37:3:37:8 | ... += ... | +| loops.rb:35:1:39:3 | while ... | loops.rb:35:7:35:11 | ... < ... | loops.rb:35:12:39:3 | do ... | 2 | loops.rb:38:3:38:6 | next | +| loops.rb:42:1:45:3 | while ... | loops.rb:42:7:42:11 | ... < ... | loops.rb:42:13:45:3 | do ... | 0 | loops.rb:43:3:43:8 | ... += ... | +| loops.rb:42:1:45:3 | while ... | loops.rb:42:7:42:11 | ... < ... | loops.rb:42:13:45:3 | do ... | 1 | loops.rb:44:3:44:8 | ... += ... | +whileModifierExprs +| loops.rb:48:1:48:19 | ... while ... | loops.rb:48:14:48:19 | ... >= ... | loops.rb:48:1:48:6 | ... += ... | +untilExprs +| loops.rb:51:1:54:3 | until ... | loops.rb:51:7:51:12 | ... == ... | loops.rb:51:13:54:3 | do ... | 0 | loops.rb:52:3:52:8 | ... += ... | +| loops.rb:51:1:54:3 | until ... | loops.rb:51:7:51:12 | ... == ... | loops.rb:51:13:54:3 | do ... | 1 | loops.rb:53:3:53:8 | ... -= ... | +| loops.rb:57:1:60:3 | until ... | loops.rb:57:7:57:11 | ... > ... | loops.rb:57:13:60:3 | do ... | 0 | loops.rb:58:3:58:8 | ... += ... | +| loops.rb:57:1:60:3 | until ... | loops.rb:57:7:57:11 | ... > ... | loops.rb:57:13:60:3 | do ... | 1 | loops.rb:59:3:59:8 | ... -= ... | +untilModifierExprs +| loops.rb:63:1:63:19 | ... until ... | loops.rb:63:14:63:19 | ... == ... | loops.rb:63:1:63:6 | ... -= ... | diff --git a/ruby/ql/test/library-tests/ast/control/Loop.ql b/ruby/ql/test/library-tests/ast/control/Loop.ql new file mode 100644 index 000000000000..d5275b46e333 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/control/Loop.ql @@ -0,0 +1,47 @@ +import ruby + +query predicate loops(Loop l, string lClass, Expr body, string bodyClass) { + l.getBody() = body and lClass = l.getAPrimaryQlClass() and bodyClass = body.getAPrimaryQlClass() +} + +query predicate conditionalLoops( + ConditionalLoop l, string lClass, Expr cond, Expr body, string bodyClass +) { + l.getBody() = body and + lClass = l.getAPrimaryQlClass() and + bodyClass = body.getAPrimaryQlClass() and + cond = l.getCondition() +} + +query predicate forExprs(ForExpr f, Pattern p, StmtSequence body, int i, Stmt bodyChild) { + p = f.getPattern() and + body = f.getBody() and + bodyChild = body.getStmt(i) +} + +query predicate forExprsTuplePatterns(ForExpr f, TuplePattern tp, int i, Pattern cp) { + tp = f.getPattern() and + cp = tp.getElement(i) +} + +query predicate whileExprs(WhileExpr e, Expr cond, StmtSequence body, int i, Stmt bodyChild) { + cond = e.getCondition() and + body = e.getBody() and + bodyChild = body.getStmt(i) +} + +query predicate whileModifierExprs(WhileModifierExpr e, Expr cond, Expr body) { + cond = e.getCondition() and + body = e.getBody() +} + +query predicate untilExprs(UntilExpr e, Expr cond, StmtSequence body, int i, Stmt bodyChild) { + cond = e.getCondition() and + body = e.getBody() and + bodyChild = body.getStmt(i) +} + +query predicate untilModifierExprs(UntilModifierExpr e, Expr cond, Expr body) { + cond = e.getCondition() and + body = e.getBody() +} diff --git a/ruby/ql/test/library-tests/ast/control/cases.rb b/ruby/ql/test/library-tests/ast/control/cases.rb new file mode 100644 index 000000000000..38a74da81d6a --- /dev/null +++ b/ruby/ql/test/library-tests/ast/control/cases.rb @@ -0,0 +1,22 @@ +# Define some variables used below +a = 0 +b = 0 +c = 0 +d = 0 + +# A case expr with a value and an else branch +case a +when b + 100 +when c, d + 200 +else + 300 +end + +# A case expr without a value or else branch +case +when a > b then 10 +when a == b then 20 +when a < b then 30 +end \ No newline at end of file diff --git a/ruby/ql/test/library-tests/ast/control/conditionals.rb b/ruby/ql/test/library-tests/ast/control/conditionals.rb new file mode 100644 index 000000000000..85e008f5c1d5 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/control/conditionals.rb @@ -0,0 +1,70 @@ +# Define some variables used below +a = 0 +b = 0 +c = 0 +d = 0 +e = 0 +f = 0 + +# If expr with no else +if a > b then + c +end + +# If expr with single else +if a == b + c +else + d +end + +# If expr with multiple nested elsif branches +if a == 0 then + c +elsif a == 1 then + d +elsif a == 2 then + e +else + f +end + +# If expr with elsif and then no else +if a == 0 + b +elsif a == 1 + c +end + +# Unless expr with no else +unless a > b then + c +end + +# Unless expr with else +unless a == b + c +else + d +end + +# If-modified expr +a = b if c > d + +# Unless-modified expr +a = b unless c < d + +# Ternary if expr +a = b > c ? d + 1 : e - 2 + +# If expr with empty else (treated as no else) +if a > b then + c +else +end + +# If expr with empty then (treated as no then) +if a > b then +else + c +end \ No newline at end of file diff --git a/ruby/ql/test/library-tests/ast/control/loops.rb b/ruby/ql/test/library-tests/ast/control/loops.rb new file mode 100644 index 000000000000..b00a61e348b6 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/control/loops.rb @@ -0,0 +1,67 @@ +# Define some variables used below. +foo = 0 +sum = 0 +x = 0 +y = 0 +z = 0 + +# For loop with a single variable as the iteration argument +for n in 1..10 + sum += n + foo = n +end + +# For loop with a single variable and a trailing comma as the iteration +# argument +for n in 1..10 + sum += n + foo -= n +end + +# For loop with a tuple pattern as the iteration argument +for key, value in {foo: 0, bar: 1} + sum += value + foo *= value +end + +# Same, but with parentheses around the pattern +for (key, value) in {foo: 0, bar: 1} + sum += value + foo /= value + break +end + +# While loop +while x < y + x += 1 + z += 1 + next +end + +# While loop with `do` keyword +while x < y do + x += 1 + z += 2 +end + +# While-modified expression +x += 1 while y >= x + +# Until loop +until x == y + x += 1 + z -= 1 +end + +# Until loop with `do` keyword +until x > y do + x += 1 + z -= 4 +end + +# Until-modified expression +x -= 1 until x == 0 + +# While loop with empty `do` block +while x < y do +end diff --git a/ruby/ql/test/library-tests/ast/erb/Erb.expected b/ruby/ql/test/library-tests/ast/erb/Erb.expected new file mode 100644 index 000000000000..089e9408e552 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/erb/Erb.expected @@ -0,0 +1,130 @@ +erbFiles +| template.html.erb:0:0:0:0 | template.html.erb | +erbAstNodes +| template.html.erb:1:1:1:9 | <%graphql | +| template.html.erb:1:1:17:2 | <%graphql\n fragment Foo on Bar {\n ...%> | +| template.html.erb:1:1:32:6 | erb template | +| template.html.erb:1:10:16:4 | \n fragment Foo on Bar {\n ... | +| template.html.erb:1:10:16:4 | \n fragment Foo on Bar {\n ... | +| template.html.erb:17:1:17:2 | %> | +| template.html.erb:17:3:19:3 | \n\n<%= | +| template.html.erb:17:3:19:20 | <%= "hello world" %> | +| template.html.erb:19:4:19:18 | "hello world" | +| template.html.erb:19:4:19:18 | "hello world" | +| template.html.erb:19:19:19:20 | %> | +| template.html.erb:19:21:21:3 | \n\n<%# | +| template.html.erb:19:21:21:31 | <%#= "this is commented out" %> | +| template.html.erb:21:4:21:29 | = "this is commented out" | +| template.html.erb:21:4:21:29 | = "this is commented out" | +| template.html.erb:21:30:21:31 | %> | +| template.html.erb:21:32:23:3 | \n\n<%# | +| template.html.erb:21:32:23:35 | <%# "this is also commented out" %> | +| template.html.erb:23:4:23:33 | "this is also commented out" | +| template.html.erb:23:4:23:33 | "this is also commented out" | +| template.html.erb:23:34:23:35 | %> | +| template.html.erb:23:36:25:2 | \n\n<% | +| template.html.erb:23:36:25:13 | <% xs = "" %> | +| template.html.erb:25:3:25:11 | xs = "" | +| template.html.erb:25:3:25:11 | xs = "" | +| template.html.erb:25:12:25:13 | %> | +| template.html.erb:25:14:27:2 | \n
      \n | +| template.html.erb:27:3:27:4 | <% | +| template.html.erb:27:3:27:41 | <% for x in ["foo", "bar", "baz...%> | +| template.html.erb:27:5:27:39 | for x in ["foo", "bar", "baz... | +| template.html.erb:27:5:27:39 | for x in ["foo", "bar", "baz... | +| template.html.erb:27:40:27:41 | %> | +| template.html.erb:27:42:28:6 | \n
    • | +| template.html.erb:28:7:28:9 | <%= | +| template.html.erb:28:7:30:12 | <%= xs += x\n xs\n %> | +| template.html.erb:28:10:30:10 | xs += x\n xs\n | +| template.html.erb:28:10:30:10 | xs += x\n xs\n | +| template.html.erb:30:11:30:12 | %> | +| template.html.erb:30:13:31:2 |
    • \n | +| template.html.erb:31:3:31:4 | <% | +| template.html.erb:31:3:31:11 | <% end %> | +| template.html.erb:31:5:31:9 | end | +| template.html.erb:31:5:31:9 | end | +| template.html.erb:31:10:31:11 | %> | +| template.html.erb:31:12:32:6 | \n
    \n | +erbTemplates +| template.html.erb:1:1:32:6 | erb template | +erbDirectives +| template.html.erb:1:1:17:2 | <%graphql\n fragment Foo on Bar {\n ...%> | +| template.html.erb:17:3:19:20 | <%= "hello world" %> | +| template.html.erb:19:21:21:31 | <%#= "this is commented out" %> | +| template.html.erb:21:32:23:35 | <%# "this is also commented out" %> | +| template.html.erb:23:36:25:13 | <% xs = "" %> | +| template.html.erb:27:3:27:41 | <% for x in ["foo", "bar", "baz...%> | +| template.html.erb:28:7:30:12 | <%= xs += x\n xs\n %> | +| template.html.erb:31:3:31:11 | <% end %> | +erbCommentDirectives +| template.html.erb:19:21:21:31 | <%#= "this is commented out" %> | +| template.html.erb:21:32:23:35 | <%# "this is also commented out" %> | +erbGraphqlDirectives +| template.html.erb:1:1:17:2 | <%graphql\n fragment Foo on Bar {\n ...%> | +erbOutputDirectives +| template.html.erb:17:3:19:20 | <%= "hello world" %> | +| template.html.erb:28:7:30:12 | <%= xs += x\n xs\n %> | +erbExecutionDirectives +| template.html.erb:23:36:25:13 | <% xs = "" %> | +| template.html.erb:27:3:27:41 | <% for x in ["foo", "bar", "baz...%> | +| template.html.erb:31:3:31:11 | <% end %> | +childStmts +| template.html.erb:17:3:19:20 | <%= "hello world" %> | template.html.erb:19:5:19:17 | "hello world" | +| template.html.erb:23:36:25:13 | <% xs = "" %> | template.html.erb:25:4:25:10 | ... = ... | +| template.html.erb:27:3:27:41 | <% for x in ["foo", "bar", "baz...%> | template.html.erb:27:6:31:8 | for ... in ... | +| template.html.erb:28:7:30:12 | <%= xs += x\n xs\n %> | template.html.erb:28:11:28:17 | ... += ... | +| template.html.erb:28:7:30:12 | <%= xs += x\n xs\n %> | template.html.erb:29:11:29:12 | xs | +terminalStatements +| template.html.erb:17:3:19:20 | <%= "hello world" %> | template.html.erb:19:5:19:17 | "hello world" | +| template.html.erb:23:36:25:13 | <% xs = "" %> | template.html.erb:25:4:25:10 | ... = ... | +| template.html.erb:27:3:27:41 | <% for x in ["foo", "bar", "baz...%> | template.html.erb:27:6:31:8 | for ... in ... | +| template.html.erb:28:7:30:12 | <%= xs += x\n xs\n %> | template.html.erb:29:11:29:12 | xs | +primaryQlClasses +| template.html.erb:1:1:1:9 | <%graphql | ErbToken | +| template.html.erb:1:1:17:2 | <%graphql\n fragment Foo on Bar {\n ...%> | ErbGraphqlDirective | +| template.html.erb:1:1:32:6 | erb template | ErbTemplate | +| template.html.erb:1:10:16:4 | \n fragment Foo on Bar {\n ... | ErbCode | +| template.html.erb:1:10:16:4 | \n fragment Foo on Bar {\n ... | ErbToken | +| template.html.erb:17:1:17:2 | %> | ErbToken | +| template.html.erb:17:3:19:3 | \n\n<%= | ErbToken | +| template.html.erb:17:3:19:20 | <%= "hello world" %> | ErbOutputDirective | +| template.html.erb:19:4:19:18 | "hello world" | ErbCode | +| template.html.erb:19:4:19:18 | "hello world" | ErbToken | +| template.html.erb:19:19:19:20 | %> | ErbToken | +| template.html.erb:19:21:21:3 | \n\n<%# | ErbToken | +| template.html.erb:19:21:21:31 | <%#= "this is commented out" %> | ErbCommentDirective | +| template.html.erb:21:4:21:29 | = "this is commented out" | ErbComment | +| template.html.erb:21:4:21:29 | = "this is commented out" | ErbToken | +| template.html.erb:21:30:21:31 | %> | ErbToken | +| template.html.erb:21:32:23:3 | \n\n<%# | ErbToken | +| template.html.erb:21:32:23:35 | <%# "this is also commented out" %> | ErbCommentDirective | +| template.html.erb:23:4:23:33 | "this is also commented out" | ErbComment | +| template.html.erb:23:4:23:33 | "this is also commented out" | ErbToken | +| template.html.erb:23:34:23:35 | %> | ErbToken | +| template.html.erb:23:36:25:2 | \n\n<% | ErbToken | +| template.html.erb:23:36:25:13 | <% xs = "" %> | ErbExecutionDirective | +| template.html.erb:25:3:25:11 | xs = "" | ErbCode | +| template.html.erb:25:3:25:11 | xs = "" | ErbToken | +| template.html.erb:25:12:25:13 | %> | ErbToken | +| template.html.erb:25:14:27:2 | \n
      \n | ErbToken | +| template.html.erb:27:3:27:4 | <% | ErbToken | +| template.html.erb:27:3:27:41 | <% for x in ["foo", "bar", "baz...%> | ErbExecutionDirective | +| template.html.erb:27:5:27:39 | for x in ["foo", "bar", "baz... | ErbCode | +| template.html.erb:27:5:27:39 | for x in ["foo", "bar", "baz... | ErbToken | +| template.html.erb:27:40:27:41 | %> | ErbToken | +| template.html.erb:27:42:28:6 | \n
    • | ErbToken | +| template.html.erb:28:7:28:9 | <%= | ErbToken | +| template.html.erb:28:7:30:12 | <%= xs += x\n xs\n %> | ErbOutputDirective | +| template.html.erb:28:10:30:10 | xs += x\n xs\n | ErbCode | +| template.html.erb:28:10:30:10 | xs += x\n xs\n | ErbToken | +| template.html.erb:30:11:30:12 | %> | ErbToken | +| template.html.erb:30:13:31:2 |
    • \n | ErbToken | +| template.html.erb:31:3:31:4 | <% | ErbToken | +| template.html.erb:31:3:31:11 | <% end %> | ErbExecutionDirective | +| template.html.erb:31:5:31:9 | end | ErbCode | +| template.html.erb:31:5:31:9 | end | ErbToken | +| template.html.erb:31:10:31:11 | %> | ErbToken | +| template.html.erb:31:12:32:6 | \n
    \n | ErbToken | +erbFileTemplates +| template.html.erb:0:0:0:0 | template.html.erb | template.html.erb:1:1:32:6 | erb template | diff --git a/ruby/ql/test/library-tests/ast/erb/Erb.ql b/ruby/ql/test/library-tests/ast/erb/Erb.ql new file mode 100644 index 000000000000..afdc1f2b70e1 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/erb/Erb.ql @@ -0,0 +1,25 @@ +import ruby + +query predicate erbFiles(ErbFile f) { any() } + +query predicate erbAstNodes(ErbAstNode n) { any() } + +query predicate erbTemplates(ErbTemplate t) { any() } + +query predicate erbDirectives(ErbDirective d) { any() } + +query predicate erbCommentDirectives(ErbCommentDirective d) { any() } + +query predicate erbGraphqlDirectives(ErbGraphqlDirective d) { any() } + +query predicate erbOutputDirectives(ErbOutputDirective d) { any() } + +query predicate erbExecutionDirectives(ErbExecutionDirective d) { any() } + +query predicate childStmts(ErbDirective d, Stmt s) { s = d.getAChildStmt() } + +query predicate terminalStatements(ErbDirective d, Stmt s) { s = d.getTerminalStmt() } + +query predicate primaryQlClasses(ErbAstNode n, string cls) { cls = n.getAPrimaryQlClass() } + +query predicate erbFileTemplates(ErbFile f, ErbTemplate t) { t = f.getTemplate() } diff --git a/ruby/ql/test/library-tests/ast/erb/template.html.erb b/ruby/ql/test/library-tests/ast/erb/template.html.erb new file mode 100644 index 000000000000..f77d311a1b87 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/erb/template.html.erb @@ -0,0 +1,32 @@ +<%graphql + fragment Foo on Bar { + some { + queryText + moreProperties + } + } + + fragment AnotherFragment on SomethingElse { + other { + things + } + here { + etc + } + } +%> + +<%= "hello world" %> + +<%#= "this is commented out" %> + +<%# "this is also commented out" %> + +<% xs = "" %> +
      + <% for x in ["foo", "bar", "baz"] do %> +
    • <%= xs += x + xs + %>
    • + <% end %> +
    diff --git a/ruby/ql/test/library-tests/ast/gems/Gemfile b/ruby/ql/test/library-tests/ast/gems/Gemfile new file mode 100644 index 000000000000..c5adf783a395 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/gems/Gemfile @@ -0,0 +1,9 @@ +source 'https://rubygems.org' + +gem 'foo_gem', '~> 2.0' + +source 'https://gems.example.com' do + gem 'my_gem', '1.0' + gem 'another_gem', '3.1.4' +end + diff --git a/ruby/ql/test/library-tests/ast/gems/lib/test.rb b/ruby/ql/test/library-tests/ast/gems/lib/test.rb new file mode 100644 index 000000000000..9bc0deccf870 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/gems/lib/test.rb @@ -0,0 +1,5 @@ +class Foo + def self.greet + puts "Hello" + end +end diff --git a/ruby/ql/test/library-tests/ast/gems/test.expected b/ruby/ql/test/library-tests/ast/gems/test.expected new file mode 100644 index 000000000000..71ed228b3db3 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/gems/test.expected @@ -0,0 +1,25 @@ +| Gemfile:1:1:1:29 | call to source | +| Gemfile:3:1:3:23 | call to gem | +| Gemfile:5:1:8:3 | call to source | +| Gemfile:6:3:6:21 | call to gem | +| Gemfile:7:3:7:28 | call to gem | +| lib/test.rb:3:5:3:16 | call to puts | +| test.gemspec:1:1:10:3 | call to new | +| test.gemspec:2:3:2:8 | call to name | +| test.gemspec:2:3:2:8 | call to name= | +| test.gemspec:3:3:3:11 | call to version | +| test.gemspec:3:3:3:11 | call to version= | +| test.gemspec:4:3:4:11 | call to summary | +| test.gemspec:4:3:4:11 | call to summary= | +| test.gemspec:5:3:5:15 | call to description | +| test.gemspec:5:3:5:15 | call to description= | +| test.gemspec:6:3:6:11 | call to authors | +| test.gemspec:6:3:6:11 | call to authors= | +| test.gemspec:6:19:6:31 | call to [] | +| test.gemspec:7:3:7:9 | call to email | +| test.gemspec:7:3:7:9 | call to email= | +| test.gemspec:8:3:8:9 | call to files | +| test.gemspec:8:3:8:9 | call to files= | +| test.gemspec:8:19:8:33 | call to [] | +| test.gemspec:9:3:9:12 | call to homepage | +| test.gemspec:9:3:9:12 | call to homepage= | diff --git a/ruby/ql/test/library-tests/ast/gems/test.gemspec b/ruby/ql/test/library-tests/ast/gems/test.gemspec new file mode 100644 index 000000000000..e96a530a2dca --- /dev/null +++ b/ruby/ql/test/library-tests/ast/gems/test.gemspec @@ -0,0 +1,10 @@ +Gem::Specification.new do |s| + s.name = 'test' + s.version = '0.0.0' + s.summary = "foo!" + s.description = "A test" + s.authors = ["Mona Lisa"] + s.email = 'mona@example.com' + s.files = ["lib/test.rb"] + s.homepage = 'https://github.com/github/codeql-ruby' +end diff --git a/ruby/ql/test/library-tests/ast/gems/test.ql b/ruby/ql/test/library-tests/ast/gems/test.ql new file mode 100644 index 000000000000..0d7277ed6bd8 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/gems/test.ql @@ -0,0 +1,4 @@ +import ruby + +// Just enough to test that we extracted the Gemfile and the .gemspec file. +select any(Call c) diff --git a/ruby/ql/test/library-tests/ast/literals/literals.expected b/ruby/ql/test/library-tests/ast/literals/literals.expected new file mode 100644 index 000000000000..1dd2ffceb55a --- /dev/null +++ b/ruby/ql/test/library-tests/ast/literals/literals.expected @@ -0,0 +1,791 @@ +allLiterals +| literals.rb:2:1:2:3 | nil | NilLiteral | nil | +| literals.rb:3:1:3:3 | NIL | NilLiteral | NIL | +| literals.rb:4:1:4:5 | false | BooleanLiteral | false | +| literals.rb:5:1:5:5 | FALSE | BooleanLiteral | FALSE | +| literals.rb:6:1:6:4 | true | BooleanLiteral | true | +| literals.rb:7:1:7:4 | TRUE | BooleanLiteral | TRUE | +| literals.rb:10:1:10:4 | 1234 | IntegerLiteral | 1234 | +| literals.rb:11:1:11:5 | 5_678 | IntegerLiteral | 5_678 | +| literals.rb:12:1:12:1 | 0 | IntegerLiteral | 0 | +| literals.rb:13:1:13:5 | 0d900 | IntegerLiteral | 0d900 | +| literals.rb:16:1:16:6 | 0x1234 | IntegerLiteral | 0x1234 | +| literals.rb:17:1:17:10 | 0xdeadbeef | IntegerLiteral | 0xdeadbeef | +| literals.rb:18:1:18:11 | 0xF00D_face | IntegerLiteral | 0xF00D_face | +| literals.rb:21:1:21:4 | 0123 | IntegerLiteral | 0123 | +| literals.rb:22:1:22:5 | 0o234 | IntegerLiteral | 0o234 | +| literals.rb:23:1:23:6 | 0O45_6 | IntegerLiteral | 0O45_6 | +| literals.rb:26:1:26:10 | 0b10010100 | IntegerLiteral | 0b10010100 | +| literals.rb:27:1:27:11 | 0B011_01101 | IntegerLiteral | 0B011_01101 | +| literals.rb:30:1:30:5 | 12.34 | FloatLiteral | 12.34 | +| literals.rb:31:1:31:7 | 1234e-2 | FloatLiteral | 1234e-2 | +| literals.rb:32:1:32:7 | 1.234E1 | FloatLiteral | 1.234E1 | +| literals.rb:35:1:35:3 | 23r | RationalLiteral | 23r | +| literals.rb:36:1:36:5 | 9.85r | RationalLiteral | 9.85r | +| literals.rb:39:1:39:2 | 2i | ComplexLiteral | 2i | +| literals.rb:46:1:46:2 | "" | StringLiteral | | +| literals.rb:47:1:47:2 | "" | StringLiteral | | +| literals.rb:48:1:48:7 | "hello" | StringLiteral | hello | +| literals.rb:49:1:49:9 | "goodbye" | StringLiteral | goodbye | +| literals.rb:50:1:50:30 | "string with escaped \\" quote" | StringLiteral | string with escaped \\" quote | +| literals.rb:51:1:51:21 | "string with " quote" | StringLiteral | string with " quote | +| literals.rb:52:1:52:14 | "foo bar baz" | StringLiteral | foo bar baz | +| literals.rb:53:1:53:15 | "foo bar baz" | StringLiteral | foo bar baz | +| literals.rb:54:1:54:20 | "foo ' bar " baz'" | StringLiteral | foo ' bar " baz' | +| literals.rb:55:1:55:20 | "FOO ' BAR " BAZ'" | StringLiteral | FOO ' BAR " BAZ' | +| literals.rb:56:1:56:12 | "foo\\ bar" | StringLiteral | foo\\ bar | +| literals.rb:57:1:57:12 | "foo\\ bar" | StringLiteral | foo\\ bar | +| literals.rb:58:1:58:20 | "2 + 2 = #{...}" | StringLiteral | | +| literals.rb:58:13:58:13 | 2 | IntegerLiteral | 2 | +| literals.rb:58:17:58:17 | 2 | IntegerLiteral | 2 | +| literals.rb:59:1:59:22 | "3 + 4 = #{...}" | StringLiteral | | +| literals.rb:59:15:59:15 | 3 | IntegerLiteral | 3 | +| literals.rb:59:19:59:19 | 4 | IntegerLiteral | 4 | +| literals.rb:60:1:60:20 | "2 + 2 = #{ 2 + 2 }" | StringLiteral | 2 + 2 = #{ 2 + 2 } | +| literals.rb:61:1:61:22 | "3 + 4 = #{ 3 + 4 }" | StringLiteral | 3 + 4 = #{ 3 + 4 } | +| literals.rb:62:1:62:5 | "foo" | StringLiteral | foo | +| literals.rb:62:7:62:11 | "bar" | StringLiteral | bar | +| literals.rb:62:13:62:17 | "baz" | StringLiteral | baz | +| literals.rb:63:1:63:7 | "foo" | StringLiteral | foo | +| literals.rb:63:9:63:13 | "bar" | StringLiteral | bar | +| literals.rb:63:15:63:19 | "baz" | StringLiteral | baz | +| literals.rb:64:1:64:5 | "foo" | StringLiteral | foo | +| literals.rb:64:7:64:21 | "bar#{...}" | StringLiteral | | +| literals.rb:64:14:64:14 | 1 | IntegerLiteral | 1 | +| literals.rb:64:18:64:18 | 1 | IntegerLiteral | 1 | +| literals.rb:64:23:64:27 | "baz" | StringLiteral | baz | +| literals.rb:65:1:65:35 | "foo #{...} qux" | StringLiteral | | +| literals.rb:65:9:65:28 | "bar #{...} baz" | StringLiteral | | +| literals.rb:65:17:65:17 | 2 | IntegerLiteral | 2 | +| literals.rb:65:21:65:21 | 3 | IntegerLiteral | 3 | +| literals.rb:66:1:66:22 | "foo #{...}" | StringLiteral | | +| literals.rb:66:17:66:17 | 1 | IntegerLiteral | 1 | +| literals.rb:66:19:66:19 | 9 | IntegerLiteral | 9 | +| literals.rb:69:1:69:2 | ?x | CharacterLiteral | ?x | +| literals.rb:70:1:70:3 | ?\\n | CharacterLiteral | ?\\n | +| literals.rb:71:1:71:3 | ?\\s | CharacterLiteral | ?\\s | +| literals.rb:72:1:72:3 | ?\\\\ | CharacterLiteral | ?\\\\ | +| literals.rb:73:1:73:7 | ?\\u{58} | CharacterLiteral | ?\\u{58} | +| literals.rb:74:1:74:5 | ?\\C-a | CharacterLiteral | ?\\C-a | +| literals.rb:75:1:75:5 | ?\\M-a | CharacterLiteral | ?\\M-a | +| literals.rb:76:1:76:8 | ?\\M-\\C-a | CharacterLiteral | ?\\M-\\C-a | +| literals.rb:77:1:77:8 | ?\\C-\\M-a | CharacterLiteral | ?\\C-\\M-a | +| literals.rb:80:1:80:3 | :"" | SymbolLiteral | | +| literals.rb:81:1:81:6 | :hello | SymbolLiteral | hello | +| literals.rb:82:1:82:10 | :"foo bar" | SymbolLiteral | foo bar | +| literals.rb:83:1:83:10 | :"bar baz" | SymbolLiteral | bar baz | +| literals.rb:84:1:84:14 | {...} | HashLiteral | | +| literals.rb:84:3:84:5 | :foo | SymbolLiteral | foo | +| literals.rb:84:8:84:12 | "bar" | StringLiteral | bar | +| literals.rb:85:1:85:10 | :"wibble" | SymbolLiteral | wibble | +| literals.rb:86:1:86:17 | :"wibble wobble" | SymbolLiteral | wibble wobble | +| literals.rb:87:1:87:16 | :"foo_#{...}" | SymbolLiteral | | +| literals.rb:87:10:87:10 | 2 | IntegerLiteral | 2 | +| literals.rb:87:14:87:14 | 2 | IntegerLiteral | 2 | +| literals.rb:88:1:88:17 | :"foo_#{ 1 + 1 }" | SymbolLiteral | foo_#{ 1 + 1 } | +| literals.rb:89:1:89:18 | :"foo_#{ 3 - 2 }" | SymbolLiteral | foo_#{ 3 - 2 } | +| literals.rb:92:1:92:2 | [...] | ArrayLiteral | | +| literals.rb:93:1:93:9 | [...] | ArrayLiteral | | +| literals.rb:93:2:93:2 | 1 | IntegerLiteral | 1 | +| literals.rb:93:5:93:5 | 2 | IntegerLiteral | 2 | +| literals.rb:93:8:93:8 | 3 | IntegerLiteral | 3 | +| literals.rb:94:1:94:14 | [...] | ArrayLiteral | | +| literals.rb:94:2:94:2 | 4 | IntegerLiteral | 4 | +| literals.rb:94:5:94:5 | 5 | IntegerLiteral | 5 | +| literals.rb:94:8:94:9 | 12 | IntegerLiteral | 12 | +| literals.rb:94:13:94:13 | 2 | IntegerLiteral | 2 | +| literals.rb:95:1:95:11 | [...] | ArrayLiteral | | +| literals.rb:95:2:95:2 | 7 | IntegerLiteral | 7 | +| literals.rb:95:5:95:10 | [...] | ArrayLiteral | | +| literals.rb:95:6:95:6 | 8 | IntegerLiteral | 8 | +| literals.rb:95:9:95:9 | 9 | IntegerLiteral | 9 | +| literals.rb:98:1:98:4 | %w(...) | ArrayLiteral | | +| literals.rb:99:1:99:15 | %w(...) | ArrayLiteral | | +| literals.rb:99:4:99:6 | "foo" | StringLiteral | foo | +| literals.rb:99:8:99:10 | "bar" | StringLiteral | bar | +| literals.rb:99:12:99:14 | "baz" | StringLiteral | baz | +| literals.rb:100:1:100:15 | %w(...) | ArrayLiteral | | +| literals.rb:100:4:100:6 | "foo" | StringLiteral | foo | +| literals.rb:100:8:100:10 | "bar" | StringLiteral | bar | +| literals.rb:100:12:100:14 | "baz" | StringLiteral | baz | +| literals.rb:101:1:101:21 | %w(...) | ArrayLiteral | | +| literals.rb:101:4:101:6 | "foo" | StringLiteral | foo | +| literals.rb:101:8:101:16 | "bar#{...}" | StringLiteral | | +| literals.rb:101:13:101:13 | 1 | IntegerLiteral | 1 | +| literals.rb:101:15:101:15 | 1 | IntegerLiteral | 1 | +| literals.rb:101:18:101:20 | "baz" | StringLiteral | baz | +| literals.rb:102:1:102:21 | %w(...) | ArrayLiteral | | +| literals.rb:102:4:102:6 | "foo" | StringLiteral | foo | +| literals.rb:102:8:102:16 | "bar#{1+1}" | StringLiteral | bar#{1+1} | +| literals.rb:102:18:102:20 | "baz" | StringLiteral | baz | +| literals.rb:105:1:105:4 | %i(...) | ArrayLiteral | | +| literals.rb:106:1:106:15 | %i(...) | ArrayLiteral | | +| literals.rb:106:4:106:6 | :"foo" | SymbolLiteral | foo | +| literals.rb:106:8:106:10 | :"bar" | SymbolLiteral | bar | +| literals.rb:106:12:106:14 | :"baz" | SymbolLiteral | baz | +| literals.rb:107:1:107:15 | %i(...) | ArrayLiteral | | +| literals.rb:107:4:107:6 | :"foo" | SymbolLiteral | foo | +| literals.rb:107:8:107:10 | :"bar" | SymbolLiteral | bar | +| literals.rb:107:12:107:14 | :"baz" | SymbolLiteral | baz | +| literals.rb:108:1:108:25 | %i(...) | ArrayLiteral | | +| literals.rb:108:4:108:6 | :"foo" | SymbolLiteral | foo | +| literals.rb:108:8:108:20 | :"bar#{...}" | SymbolLiteral | | +| literals.rb:108:14:108:14 | 2 | IntegerLiteral | 2 | +| literals.rb:108:18:108:18 | 4 | IntegerLiteral | 4 | +| literals.rb:108:22:108:24 | :"baz" | SymbolLiteral | baz | +| literals.rb:109:1:109:25 | %i(...) | ArrayLiteral | | +| literals.rb:109:4:109:6 | :"foo" | SymbolLiteral | foo | +| literals.rb:109:8:109:12 | :"bar#{" | SymbolLiteral | bar#{ | +| literals.rb:109:14:109:14 | :"2" | SymbolLiteral | 2 | +| literals.rb:109:16:109:16 | :"+" | SymbolLiteral | + | +| literals.rb:109:18:109:18 | :"4" | SymbolLiteral | 4 | +| literals.rb:109:20:109:20 | :"}" | SymbolLiteral | } | +| literals.rb:109:22:109:24 | :"baz" | SymbolLiteral | baz | +| literals.rb:112:1:112:2 | {...} | HashLiteral | | +| literals.rb:113:1:113:33 | {...} | HashLiteral | | +| literals.rb:113:3:113:5 | :foo | SymbolLiteral | foo | +| literals.rb:113:8:113:8 | 1 | IntegerLiteral | 1 | +| literals.rb:113:11:113:14 | :bar | SymbolLiteral | bar | +| literals.rb:113:19:113:19 | 2 | IntegerLiteral | 2 | +| literals.rb:113:22:113:26 | "baz" | StringLiteral | baz | +| literals.rb:113:31:113:31 | 3 | IntegerLiteral | 3 | +| literals.rb:114:1:114:17 | {...} | HashLiteral | | +| literals.rb:114:3:114:5 | :foo | SymbolLiteral | foo | +| literals.rb:114:8:114:8 | 7 | IntegerLiteral | 7 | +| literals.rb:117:2:117:2 | 1 | IntegerLiteral | 1 | +| literals.rb:117:2:117:6 | _ .. _ | RangeLiteral | | +| literals.rb:117:5:117:6 | 10 | IntegerLiteral | 10 | +| literals.rb:118:2:118:2 | 1 | IntegerLiteral | 1 | +| literals.rb:118:2:118:7 | _ ... _ | RangeLiteral | | +| literals.rb:118:6:118:7 | 10 | IntegerLiteral | 10 | +| literals.rb:119:2:119:2 | 1 | IntegerLiteral | 1 | +| literals.rb:119:2:119:7 | _ .. _ | RangeLiteral | | +| literals.rb:119:7:119:7 | 0 | IntegerLiteral | 0 | +| literals.rb:120:2:120:11 | _ .. _ | RangeLiteral | | +| literals.rb:120:9:120:9 | 2 | IntegerLiteral | 2 | +| literals.rb:120:11:120:11 | 3 | IntegerLiteral | 3 | +| literals.rb:121:2:121:2 | 1 | IntegerLiteral | 1 | +| literals.rb:121:2:121:4 | _ .. _ | RangeLiteral | | +| literals.rb:122:2:122:4 | _ .. _ | RangeLiteral | | +| literals.rb:122:4:122:4 | 1 | IntegerLiteral | 1 | +| literals.rb:123:2:123:2 | 0 | IntegerLiteral | 0 | +| literals.rb:123:2:123:4 | _ .. _ | RangeLiteral | | +| literals.rb:123:6:123:6 | 1 | IntegerLiteral | 1 | +| literals.rb:126:1:126:7 | `ls -l` | SubshellLiteral | ls -l | +| literals.rb:127:1:127:9 | `ls -l` | SubshellLiteral | ls -l | +| literals.rb:128:1:128:18 | `du -d #{...}` | SubshellLiteral | | +| literals.rb:128:11:128:11 | 1 | IntegerLiteral | 1 | +| literals.rb:128:15:128:15 | 1 | IntegerLiteral | 1 | +| literals.rb:129:1:129:20 | `du -d #{...}` | SubshellLiteral | | +| literals.rb:129:13:129:13 | 5 | IntegerLiteral | 5 | +| literals.rb:129:17:129:17 | 4 | IntegerLiteral | 4 | +| literals.rb:132:1:132:2 | // | RegExpLiteral | | +| literals.rb:133:1:133:5 | /foo/ | RegExpLiteral | foo | +| literals.rb:134:1:134:6 | /foo/ | RegExpLiteral | foo | +| literals.rb:135:1:135:13 | /foo+\\sbar\\S/ | RegExpLiteral | foo+\\sbar\\S | +| literals.rb:136:1:136:18 | /foo#{...}bar/ | RegExpLiteral | | +| literals.rb:136:8:136:8 | 1 | IntegerLiteral | 1 | +| literals.rb:136:12:136:12 | 1 | IntegerLiteral | 1 | +| literals.rb:137:1:137:8 | /foo/ | RegExpLiteral | foo | +| literals.rb:138:1:138:4 | // | RegExpLiteral | | +| literals.rb:139:1:139:7 | /foo/ | RegExpLiteral | foo | +| literals.rb:140:1:140:8 | /foo/ | RegExpLiteral | foo | +| literals.rb:141:1:141:15 | /foo+\\sbar\\S/ | RegExpLiteral | foo+\\sbar\\S | +| literals.rb:142:1:142:20 | /foo#{...}bar/ | RegExpLiteral | | +| literals.rb:142:10:142:10 | 1 | IntegerLiteral | 1 | +| literals.rb:142:14:142:14 | 1 | IntegerLiteral | 1 | +| literals.rb:143:1:143:10 | /foo/ | RegExpLiteral | foo | +| literals.rb:146:1:146:34 | "abcdefghijklmnopqrstuvwxyzabcdef" | StringLiteral | abcdefghijklmnopqrstuvwxyzabcdef | +| literals.rb:147:1:147:35 | "foobarfoobarfoobarfoobarfooba..." | StringLiteral | foobarfoobarfoobarfoobarfoobarfoo | +| literals.rb:148:1:148:40 | "foobar\\\\foobar\\\\foobar\\\\fooba..." | StringLiteral | foobar\\\\foobar\\\\foobar\\\\foobar\\\\foobar | +| literals.rb:151:9:151:13 | < | +| literals.rb:158:11:158:16 | <<-BLA | HereDoc | \nsome text\\nand some more\n | +| literals.rb:163:9:163:19 | <<~SQUIGGLY | HereDoc | \n indented stuff\n | +| literals.rb:167:9:167:15 | <<"DOC" | HereDoc | | +| literals.rb:172:9:172:15 | <<'DOC' | HereDoc | | +| literals.rb:176:10:176:19 | <<`SCRIPT` | HereDoc | \n cat file.txt\n | +stringlikeLiterals +| literals.rb:46:1:46:2 | "" | | +| literals.rb:47:1:47:2 | "" | | +| literals.rb:48:1:48:7 | "hello" | hello | +| literals.rb:49:1:49:9 | "goodbye" | goodbye | +| literals.rb:50:1:50:30 | "string with escaped \\" quote" | string with escaped \\" quote | +| literals.rb:51:1:51:21 | "string with " quote" | string with " quote | +| literals.rb:52:1:52:14 | "foo bar baz" | foo bar baz | +| literals.rb:53:1:53:15 | "foo bar baz" | foo bar baz | +| literals.rb:54:1:54:20 | "foo ' bar " baz'" | foo ' bar " baz' | +| literals.rb:55:1:55:20 | "FOO ' BAR " BAZ'" | FOO ' BAR " BAZ' | +| literals.rb:56:1:56:12 | "foo\\ bar" | foo\\ bar | +| literals.rb:57:1:57:12 | "foo\\ bar" | foo\\ bar | +| literals.rb:58:1:58:20 | "2 + 2 = #{...}" | | +| literals.rb:59:1:59:22 | "3 + 4 = #{...}" | | +| literals.rb:60:1:60:20 | "2 + 2 = #{ 2 + 2 }" | 2 + 2 = #{ 2 + 2 } | +| literals.rb:61:1:61:22 | "3 + 4 = #{ 3 + 4 }" | 3 + 4 = #{ 3 + 4 } | +| literals.rb:62:1:62:5 | "foo" | foo | +| literals.rb:62:7:62:11 | "bar" | bar | +| literals.rb:62:13:62:17 | "baz" | baz | +| literals.rb:63:1:63:7 | "foo" | foo | +| literals.rb:63:9:63:13 | "bar" | bar | +| literals.rb:63:15:63:19 | "baz" | baz | +| literals.rb:64:1:64:5 | "foo" | foo | +| literals.rb:64:7:64:21 | "bar#{...}" | | +| literals.rb:64:23:64:27 | "baz" | baz | +| literals.rb:65:1:65:35 | "foo #{...} qux" | | +| literals.rb:65:9:65:28 | "bar #{...} baz" | | +| literals.rb:66:1:66:22 | "foo #{...}" | | +| literals.rb:80:1:80:3 | :"" | | +| literals.rb:81:1:81:6 | :hello | hello | +| literals.rb:82:1:82:10 | :"foo bar" | foo bar | +| literals.rb:83:1:83:10 | :"bar baz" | bar baz | +| literals.rb:84:3:84:5 | :foo | foo | +| literals.rb:84:8:84:12 | "bar" | bar | +| literals.rb:85:1:85:10 | :"wibble" | wibble | +| literals.rb:86:1:86:17 | :"wibble wobble" | wibble wobble | +| literals.rb:87:1:87:16 | :"foo_#{...}" | | +| literals.rb:88:1:88:17 | :"foo_#{ 1 + 1 }" | foo_#{ 1 + 1 } | +| literals.rb:89:1:89:18 | :"foo_#{ 3 - 2 }" | foo_#{ 3 - 2 } | +| literals.rb:99:4:99:6 | "foo" | foo | +| literals.rb:99:8:99:10 | "bar" | bar | +| literals.rb:99:12:99:14 | "baz" | baz | +| literals.rb:100:4:100:6 | "foo" | foo | +| literals.rb:100:8:100:10 | "bar" | bar | +| literals.rb:100:12:100:14 | "baz" | baz | +| literals.rb:101:4:101:6 | "foo" | foo | +| literals.rb:101:8:101:16 | "bar#{...}" | | +| literals.rb:101:18:101:20 | "baz" | baz | +| literals.rb:102:4:102:6 | "foo" | foo | +| literals.rb:102:8:102:16 | "bar#{1+1}" | bar#{1+1} | +| literals.rb:102:18:102:20 | "baz" | baz | +| literals.rb:106:4:106:6 | :"foo" | foo | +| literals.rb:106:8:106:10 | :"bar" | bar | +| literals.rb:106:12:106:14 | :"baz" | baz | +| literals.rb:107:4:107:6 | :"foo" | foo | +| literals.rb:107:8:107:10 | :"bar" | bar | +| literals.rb:107:12:107:14 | :"baz" | baz | +| literals.rb:108:4:108:6 | :"foo" | foo | +| literals.rb:108:8:108:20 | :"bar#{...}" | | +| literals.rb:108:22:108:24 | :"baz" | baz | +| literals.rb:109:4:109:6 | :"foo" | foo | +| literals.rb:109:8:109:12 | :"bar#{" | bar#{ | +| literals.rb:109:14:109:14 | :"2" | 2 | +| literals.rb:109:16:109:16 | :"+" | + | +| literals.rb:109:18:109:18 | :"4" | 4 | +| literals.rb:109:20:109:20 | :"}" | } | +| literals.rb:109:22:109:24 | :"baz" | baz | +| literals.rb:113:3:113:5 | :foo | foo | +| literals.rb:113:11:113:14 | :bar | bar | +| literals.rb:113:22:113:26 | "baz" | baz | +| literals.rb:114:3:114:5 | :foo | foo | +| literals.rb:126:1:126:7 | `ls -l` | ls -l | +| literals.rb:127:1:127:9 | `ls -l` | ls -l | +| literals.rb:128:1:128:18 | `du -d #{...}` | | +| literals.rb:129:1:129:20 | `du -d #{...}` | | +| literals.rb:132:1:132:2 | // | | +| literals.rb:133:1:133:5 | /foo/ | foo | +| literals.rb:134:1:134:6 | /foo/ | foo | +| literals.rb:135:1:135:13 | /foo+\\sbar\\S/ | foo+\\sbar\\S | +| literals.rb:136:1:136:18 | /foo#{...}bar/ | | +| literals.rb:137:1:137:8 | /foo/ | foo | +| literals.rb:138:1:138:4 | // | | +| literals.rb:139:1:139:7 | /foo/ | foo | +| literals.rb:140:1:140:8 | /foo/ | foo | +| literals.rb:141:1:141:15 | /foo+\\sbar\\S/ | foo+\\sbar\\S | +| literals.rb:142:1:142:20 | /foo#{...}bar/ | | +| literals.rb:143:1:143:10 | /foo/ | foo | +| literals.rb:146:1:146:34 | "abcdefghijklmnopqrstuvwxyzabcdef" | abcdefghijklmnopqrstuvwxyzabcdef | +| literals.rb:147:1:147:35 | "foobarfoobarfoobarfoobarfooba..." | foobarfoobarfoobarfoobarfoobarfoo | +| literals.rb:148:1:148:40 | "foobar\\\\foobar\\\\foobar\\\\fooba..." | foobar\\\\foobar\\\\foobar\\\\foobar\\\\foobar | +| literals.rb:151:9:151:13 | < | +| literals.rb:158:11:158:16 | <<-BLA | \nsome text\\nand some more\n | +| literals.rb:163:9:163:19 | <<~SQUIGGLY | \n indented stuff\n | +| literals.rb:167:9:167:15 | <<"DOC" | | +| literals.rb:172:9:172:15 | <<'DOC' | | +| literals.rb:176:10:176:19 | <<`SCRIPT` | \n cat file.txt\n | +stringLiterals +| literals.rb:46:1:46:2 | "" | | +| literals.rb:47:1:47:2 | "" | | +| literals.rb:48:1:48:7 | "hello" | hello | +| literals.rb:49:1:49:9 | "goodbye" | goodbye | +| literals.rb:50:1:50:30 | "string with escaped \\" quote" | string with escaped \\" quote | +| literals.rb:51:1:51:21 | "string with " quote" | string with " quote | +| literals.rb:52:1:52:14 | "foo bar baz" | foo bar baz | +| literals.rb:53:1:53:15 | "foo bar baz" | foo bar baz | +| literals.rb:54:1:54:20 | "foo ' bar " baz'" | foo ' bar " baz' | +| literals.rb:55:1:55:20 | "FOO ' BAR " BAZ'" | FOO ' BAR " BAZ' | +| literals.rb:56:1:56:12 | "foo\\ bar" | foo\\ bar | +| literals.rb:57:1:57:12 | "foo\\ bar" | foo\\ bar | +| literals.rb:58:1:58:20 | "2 + 2 = #{...}" | | +| literals.rb:59:1:59:22 | "3 + 4 = #{...}" | | +| literals.rb:60:1:60:20 | "2 + 2 = #{ 2 + 2 }" | 2 + 2 = #{ 2 + 2 } | +| literals.rb:61:1:61:22 | "3 + 4 = #{ 3 + 4 }" | 3 + 4 = #{ 3 + 4 } | +| literals.rb:62:1:62:5 | "foo" | foo | +| literals.rb:62:7:62:11 | "bar" | bar | +| literals.rb:62:13:62:17 | "baz" | baz | +| literals.rb:63:1:63:7 | "foo" | foo | +| literals.rb:63:9:63:13 | "bar" | bar | +| literals.rb:63:15:63:19 | "baz" | baz | +| literals.rb:64:1:64:5 | "foo" | foo | +| literals.rb:64:7:64:21 | "bar#{...}" | | +| literals.rb:64:23:64:27 | "baz" | baz | +| literals.rb:65:1:65:35 | "foo #{...} qux" | | +| literals.rb:65:9:65:28 | "bar #{...} baz" | | +| literals.rb:66:1:66:22 | "foo #{...}" | | +| literals.rb:84:8:84:12 | "bar" | bar | +| literals.rb:99:4:99:6 | "foo" | foo | +| literals.rb:99:8:99:10 | "bar" | bar | +| literals.rb:99:12:99:14 | "baz" | baz | +| literals.rb:100:4:100:6 | "foo" | foo | +| literals.rb:100:8:100:10 | "bar" | bar | +| literals.rb:100:12:100:14 | "baz" | baz | +| literals.rb:101:4:101:6 | "foo" | foo | +| literals.rb:101:8:101:16 | "bar#{...}" | | +| literals.rb:101:18:101:20 | "baz" | baz | +| literals.rb:102:4:102:6 | "foo" | foo | +| literals.rb:102:8:102:16 | "bar#{1+1}" | bar#{1+1} | +| literals.rb:102:18:102:20 | "baz" | baz | +| literals.rb:113:22:113:26 | "baz" | baz | +| literals.rb:146:1:146:34 | "abcdefghijklmnopqrstuvwxyzabcdef" | abcdefghijklmnopqrstuvwxyzabcdef | +| literals.rb:147:1:147:35 | "foobarfoobarfoobarfoobarfooba..." | foobarfoobarfoobarfoobarfoobarfoo | +| literals.rb:148:1:148:40 | "foobar\\\\foobar\\\\foobar\\\\fooba..." | foobar\\\\foobar\\\\foobar\\\\foobar\\\\foobar | +regExpLiterals +| literals.rb:132:1:132:2 | // | | | +| literals.rb:133:1:133:5 | /foo/ | foo | | +| literals.rb:134:1:134:6 | /foo/ | foo | i | +| literals.rb:135:1:135:13 | /foo+\\sbar\\S/ | foo+\\sbar\\S | | +| literals.rb:136:1:136:18 | /foo#{...}bar/ | | | +| literals.rb:137:1:137:8 | /foo/ | foo | oxm | +| literals.rb:138:1:138:4 | // | | | +| literals.rb:139:1:139:7 | /foo/ | foo | | +| literals.rb:140:1:140:8 | /foo/ | foo | i | +| literals.rb:141:1:141:15 | /foo+\\sbar\\S/ | foo+\\sbar\\S | | +| literals.rb:142:1:142:20 | /foo#{...}bar/ | | | +| literals.rb:143:1:143:10 | /foo/ | foo | mxo | +symbolLiterals +| literals.rb:80:1:80:3 | :"" | | +| literals.rb:81:1:81:6 | :hello | hello | +| literals.rb:82:1:82:10 | :"foo bar" | foo bar | +| literals.rb:83:1:83:10 | :"bar baz" | bar baz | +| literals.rb:84:3:84:5 | :foo | foo | +| literals.rb:85:1:85:10 | :"wibble" | wibble | +| literals.rb:86:1:86:17 | :"wibble wobble" | wibble wobble | +| literals.rb:87:1:87:16 | :"foo_#{...}" | | +| literals.rb:88:1:88:17 | :"foo_#{ 1 + 1 }" | foo_#{ 1 + 1 } | +| literals.rb:89:1:89:18 | :"foo_#{ 3 - 2 }" | foo_#{ 3 - 2 } | +| literals.rb:106:4:106:6 | :"foo" | foo | +| literals.rb:106:8:106:10 | :"bar" | bar | +| literals.rb:106:12:106:14 | :"baz" | baz | +| literals.rb:107:4:107:6 | :"foo" | foo | +| literals.rb:107:8:107:10 | :"bar" | bar | +| literals.rb:107:12:107:14 | :"baz" | baz | +| literals.rb:108:4:108:6 | :"foo" | foo | +| literals.rb:108:8:108:20 | :"bar#{...}" | | +| literals.rb:108:22:108:24 | :"baz" | baz | +| literals.rb:109:4:109:6 | :"foo" | foo | +| literals.rb:109:8:109:12 | :"bar#{" | bar#{ | +| literals.rb:109:14:109:14 | :"2" | 2 | +| literals.rb:109:16:109:16 | :"+" | + | +| literals.rb:109:18:109:18 | :"4" | 4 | +| literals.rb:109:20:109:20 | :"}" | } | +| literals.rb:109:22:109:24 | :"baz" | baz | +| literals.rb:113:3:113:5 | :foo | foo | +| literals.rb:113:11:113:14 | :bar | bar | +| literals.rb:114:3:114:5 | :foo | foo | +subshellLiterals +| literals.rb:126:1:126:7 | `ls -l` | ls -l | +| literals.rb:127:1:127:9 | `ls -l` | ls -l | +| literals.rb:128:1:128:18 | `du -d #{...}` | | +| literals.rb:129:1:129:20 | `du -d #{...}` | | +stringComponents +| literals.rb:48:1:48:7 | "hello" | StringLiteral | 0 | literals.rb:48:2:48:6 | hello | StringTextComponent | +| literals.rb:49:1:49:9 | "goodbye" | StringLiteral | 0 | literals.rb:49:2:49:8 | goodbye | StringTextComponent | +| literals.rb:50:1:50:30 | "string with escaped \\" quote" | StringLiteral | 0 | literals.rb:50:2:50:21 | string with escaped | StringTextComponent | +| literals.rb:50:1:50:30 | "string with escaped \\" quote" | StringLiteral | 1 | literals.rb:50:22:50:23 | \\" | StringEscapeSequenceComponent | +| literals.rb:50:1:50:30 | "string with escaped \\" quote" | StringLiteral | 2 | literals.rb:50:24:50:29 | quote | StringTextComponent | +| literals.rb:51:1:51:21 | "string with " quote" | StringLiteral | 0 | literals.rb:51:2:51:20 | string with " quote | StringTextComponent | +| literals.rb:52:1:52:14 | "foo bar baz" | StringLiteral | 0 | literals.rb:52:3:52:13 | foo bar baz | StringTextComponent | +| literals.rb:53:1:53:15 | "foo bar baz" | StringLiteral | 0 | literals.rb:53:4:53:14 | foo bar baz | StringTextComponent | +| literals.rb:54:1:54:20 | "foo ' bar " baz'" | StringLiteral | 0 | literals.rb:54:4:54:19 | foo ' bar " baz' | StringTextComponent | +| literals.rb:55:1:55:20 | "FOO ' BAR " BAZ'" | StringLiteral | 0 | literals.rb:55:4:55:19 | FOO ' BAR " BAZ' | StringTextComponent | +| literals.rb:56:1:56:12 | "foo\\ bar" | StringLiteral | 0 | literals.rb:56:4:56:11 | foo\\ bar | StringTextComponent | +| literals.rb:57:1:57:12 | "foo\\ bar" | StringLiteral | 0 | literals.rb:57:4:57:6 | foo | StringTextComponent | +| literals.rb:57:1:57:12 | "foo\\ bar" | StringLiteral | 1 | literals.rb:57:7:57:8 | \\ | StringEscapeSequenceComponent | +| literals.rb:57:1:57:12 | "foo\\ bar" | StringLiteral | 2 | literals.rb:57:9:57:11 | bar | StringTextComponent | +| literals.rb:58:1:58:20 | "2 + 2 = #{...}" | StringLiteral | 0 | literals.rb:58:2:58:9 | 2 + 2 = | StringTextComponent | +| literals.rb:58:1:58:20 | "2 + 2 = #{...}" | StringLiteral | 1 | literals.rb:58:10:58:19 | #{...} | StringInterpolationComponent | +| literals.rb:59:1:59:22 | "3 + 4 = #{...}" | StringLiteral | 0 | literals.rb:59:4:59:11 | 3 + 4 = | StringTextComponent | +| literals.rb:59:1:59:22 | "3 + 4 = #{...}" | StringLiteral | 1 | literals.rb:59:12:59:21 | #{...} | StringInterpolationComponent | +| literals.rb:60:1:60:20 | "2 + 2 = #{ 2 + 2 }" | StringLiteral | 0 | literals.rb:60:2:60:19 | 2 + 2 = #{ 2 + 2 } | StringTextComponent | +| literals.rb:61:1:61:22 | "3 + 4 = #{ 3 + 4 }" | StringLiteral | 0 | literals.rb:61:4:61:21 | 3 + 4 = #{ 3 + 4 } | StringTextComponent | +| literals.rb:62:1:62:5 | "foo" | StringLiteral | 0 | literals.rb:62:2:62:4 | foo | StringTextComponent | +| literals.rb:62:7:62:11 | "bar" | StringLiteral | 0 | literals.rb:62:8:62:10 | bar | StringTextComponent | +| literals.rb:62:13:62:17 | "baz" | StringLiteral | 0 | literals.rb:62:14:62:16 | baz | StringTextComponent | +| literals.rb:63:1:63:7 | "foo" | StringLiteral | 0 | literals.rb:63:4:63:6 | foo | StringTextComponent | +| literals.rb:63:9:63:13 | "bar" | StringLiteral | 0 | literals.rb:63:10:63:12 | bar | StringTextComponent | +| literals.rb:63:15:63:19 | "baz" | StringLiteral | 0 | literals.rb:63:16:63:18 | baz | StringTextComponent | +| literals.rb:64:1:64:5 | "foo" | StringLiteral | 0 | literals.rb:64:2:64:4 | foo | StringTextComponent | +| literals.rb:64:7:64:21 | "bar#{...}" | StringLiteral | 0 | literals.rb:64:8:64:10 | bar | StringTextComponent | +| literals.rb:64:7:64:21 | "bar#{...}" | StringLiteral | 1 | literals.rb:64:11:64:20 | #{...} | StringInterpolationComponent | +| literals.rb:64:23:64:27 | "baz" | StringLiteral | 0 | literals.rb:64:24:64:26 | baz | StringTextComponent | +| literals.rb:65:1:65:35 | "foo #{...} qux" | StringLiteral | 0 | literals.rb:65:2:65:5 | foo | StringTextComponent | +| literals.rb:65:1:65:35 | "foo #{...} qux" | StringLiteral | 1 | literals.rb:65:6:65:30 | #{...} | StringInterpolationComponent | +| literals.rb:65:1:65:35 | "foo #{...} qux" | StringLiteral | 2 | literals.rb:65:31:65:34 | qux | StringTextComponent | +| literals.rb:65:9:65:28 | "bar #{...} baz" | StringLiteral | 0 | literals.rb:65:10:65:13 | bar | StringTextComponent | +| literals.rb:65:9:65:28 | "bar #{...} baz" | StringLiteral | 1 | literals.rb:65:14:65:23 | #{...} | StringInterpolationComponent | +| literals.rb:65:9:65:28 | "bar #{...} baz" | StringLiteral | 2 | literals.rb:65:24:65:27 | baz | StringTextComponent | +| literals.rb:66:1:66:22 | "foo #{...}" | StringLiteral | 0 | literals.rb:66:2:66:5 | foo | StringTextComponent | +| literals.rb:66:1:66:22 | "foo #{...}" | StringLiteral | 1 | literals.rb:66:6:66:21 | #{...} | StringInterpolationComponent | +| literals.rb:82:1:82:10 | :"foo bar" | SymbolLiteral | 0 | literals.rb:82:3:82:9 | foo bar | StringTextComponent | +| literals.rb:83:1:83:10 | :"bar baz" | SymbolLiteral | 0 | literals.rb:83:3:83:9 | bar baz | StringTextComponent | +| literals.rb:84:8:84:12 | "bar" | StringLiteral | 0 | literals.rb:84:9:84:11 | bar | StringTextComponent | +| literals.rb:85:1:85:10 | :"wibble" | SymbolLiteral | 0 | literals.rb:85:4:85:9 | wibble | StringTextComponent | +| literals.rb:86:1:86:17 | :"wibble wobble" | SymbolLiteral | 0 | literals.rb:86:4:86:16 | wibble wobble | StringTextComponent | +| literals.rb:87:1:87:16 | :"foo_#{...}" | SymbolLiteral | 0 | literals.rb:87:3:87:6 | foo_ | StringTextComponent | +| literals.rb:87:1:87:16 | :"foo_#{...}" | SymbolLiteral | 1 | literals.rb:87:7:87:15 | #{...} | StringInterpolationComponent | +| literals.rb:88:1:88:17 | :"foo_#{ 1 + 1 }" | SymbolLiteral | 0 | literals.rb:88:3:88:16 | foo_#{ 1 + 1 } | StringTextComponent | +| literals.rb:89:1:89:18 | :"foo_#{ 3 - 2 }" | SymbolLiteral | 0 | literals.rb:89:4:89:17 | foo_#{ 3 - 2 } | StringTextComponent | +| literals.rb:99:4:99:6 | "foo" | StringLiteral | 0 | literals.rb:99:4:99:6 | foo | StringTextComponent | +| literals.rb:99:8:99:10 | "bar" | StringLiteral | 0 | literals.rb:99:8:99:10 | bar | StringTextComponent | +| literals.rb:99:12:99:14 | "baz" | StringLiteral | 0 | literals.rb:99:12:99:14 | baz | StringTextComponent | +| literals.rb:100:4:100:6 | "foo" | StringLiteral | 0 | literals.rb:100:4:100:6 | foo | StringTextComponent | +| literals.rb:100:8:100:10 | "bar" | StringLiteral | 0 | literals.rb:100:8:100:10 | bar | StringTextComponent | +| literals.rb:100:12:100:14 | "baz" | StringLiteral | 0 | literals.rb:100:12:100:14 | baz | StringTextComponent | +| literals.rb:101:4:101:6 | "foo" | StringLiteral | 0 | literals.rb:101:4:101:6 | foo | StringTextComponent | +| literals.rb:101:8:101:16 | "bar#{...}" | StringLiteral | 0 | literals.rb:101:8:101:10 | bar | StringTextComponent | +| literals.rb:101:8:101:16 | "bar#{...}" | StringLiteral | 1 | literals.rb:101:11:101:16 | #{...} | StringInterpolationComponent | +| literals.rb:101:18:101:20 | "baz" | StringLiteral | 0 | literals.rb:101:18:101:20 | baz | StringTextComponent | +| literals.rb:102:4:102:6 | "foo" | StringLiteral | 0 | literals.rb:102:4:102:6 | foo | StringTextComponent | +| literals.rb:102:8:102:16 | "bar#{1+1}" | StringLiteral | 0 | literals.rb:102:8:102:16 | bar#{1+1} | StringTextComponent | +| literals.rb:102:18:102:20 | "baz" | StringLiteral | 0 | literals.rb:102:18:102:20 | baz | StringTextComponent | +| literals.rb:106:4:106:6 | :"foo" | SymbolLiteral | 0 | literals.rb:106:4:106:6 | foo | StringTextComponent | +| literals.rb:106:8:106:10 | :"bar" | SymbolLiteral | 0 | literals.rb:106:8:106:10 | bar | StringTextComponent | +| literals.rb:106:12:106:14 | :"baz" | SymbolLiteral | 0 | literals.rb:106:12:106:14 | baz | StringTextComponent | +| literals.rb:107:4:107:6 | :"foo" | SymbolLiteral | 0 | literals.rb:107:4:107:6 | foo | StringTextComponent | +| literals.rb:107:8:107:10 | :"bar" | SymbolLiteral | 0 | literals.rb:107:8:107:10 | bar | StringTextComponent | +| literals.rb:107:12:107:14 | :"baz" | SymbolLiteral | 0 | literals.rb:107:12:107:14 | baz | StringTextComponent | +| literals.rb:108:4:108:6 | :"foo" | SymbolLiteral | 0 | literals.rb:108:4:108:6 | foo | StringTextComponent | +| literals.rb:108:8:108:20 | :"bar#{...}" | SymbolLiteral | 0 | literals.rb:108:8:108:10 | bar | StringTextComponent | +| literals.rb:108:8:108:20 | :"bar#{...}" | SymbolLiteral | 1 | literals.rb:108:11:108:20 | #{...} | StringInterpolationComponent | +| literals.rb:108:22:108:24 | :"baz" | SymbolLiteral | 0 | literals.rb:108:22:108:24 | baz | StringTextComponent | +| literals.rb:109:4:109:6 | :"foo" | SymbolLiteral | 0 | literals.rb:109:4:109:6 | foo | StringTextComponent | +| literals.rb:109:8:109:12 | :"bar#{" | SymbolLiteral | 0 | literals.rb:109:8:109:12 | bar#{ | StringTextComponent | +| literals.rb:109:14:109:14 | :"2" | SymbolLiteral | 0 | literals.rb:109:14:109:14 | 2 | StringTextComponent | +| literals.rb:109:16:109:16 | :"+" | SymbolLiteral | 0 | literals.rb:109:16:109:16 | + | StringTextComponent | +| literals.rb:109:18:109:18 | :"4" | SymbolLiteral | 0 | literals.rb:109:18:109:18 | 4 | StringTextComponent | +| literals.rb:109:20:109:20 | :"}" | SymbolLiteral | 0 | literals.rb:109:20:109:20 | } | StringTextComponent | +| literals.rb:109:22:109:24 | :"baz" | SymbolLiteral | 0 | literals.rb:109:22:109:24 | baz | StringTextComponent | +| literals.rb:113:22:113:26 | "baz" | StringLiteral | 0 | literals.rb:113:23:113:25 | baz | StringTextComponent | +| literals.rb:126:1:126:7 | `ls -l` | SubshellLiteral | 0 | literals.rb:126:2:126:6 | ls -l | StringTextComponent | +| literals.rb:127:1:127:9 | `ls -l` | SubshellLiteral | 0 | literals.rb:127:4:127:8 | ls -l | StringTextComponent | +| literals.rb:128:1:128:18 | `du -d #{...}` | SubshellLiteral | 0 | literals.rb:128:2:128:7 | du -d | StringTextComponent | +| literals.rb:128:1:128:18 | `du -d #{...}` | SubshellLiteral | 1 | literals.rb:128:8:128:17 | #{...} | StringInterpolationComponent | +| literals.rb:129:1:129:20 | `du -d #{...}` | SubshellLiteral | 0 | literals.rb:129:4:129:9 | du -d | StringTextComponent | +| literals.rb:129:1:129:20 | `du -d #{...}` | SubshellLiteral | 1 | literals.rb:129:10:129:19 | #{...} | StringInterpolationComponent | +| literals.rb:133:1:133:5 | /foo/ | RegExpLiteral | 0 | literals.rb:133:2:133:4 | foo | StringTextComponent | +| literals.rb:134:1:134:6 | /foo/ | RegExpLiteral | 0 | literals.rb:134:2:134:4 | foo | StringTextComponent | +| literals.rb:135:1:135:13 | /foo+\\sbar\\S/ | RegExpLiteral | 0 | literals.rb:135:2:135:5 | foo+ | StringTextComponent | +| literals.rb:135:1:135:13 | /foo+\\sbar\\S/ | RegExpLiteral | 1 | literals.rb:135:6:135:7 | \\s | StringEscapeSequenceComponent | +| literals.rb:135:1:135:13 | /foo+\\sbar\\S/ | RegExpLiteral | 2 | literals.rb:135:8:135:10 | bar | StringTextComponent | +| literals.rb:135:1:135:13 | /foo+\\sbar\\S/ | RegExpLiteral | 3 | literals.rb:135:11:135:12 | \\S | StringEscapeSequenceComponent | +| literals.rb:136:1:136:18 | /foo#{...}bar/ | RegExpLiteral | 0 | literals.rb:136:2:136:4 | foo | StringTextComponent | +| literals.rb:136:1:136:18 | /foo#{...}bar/ | RegExpLiteral | 1 | literals.rb:136:5:136:14 | #{...} | StringInterpolationComponent | +| literals.rb:136:1:136:18 | /foo#{...}bar/ | RegExpLiteral | 2 | literals.rb:136:15:136:17 | bar | StringTextComponent | +| literals.rb:137:1:137:8 | /foo/ | RegExpLiteral | 0 | literals.rb:137:2:137:4 | foo | StringTextComponent | +| literals.rb:139:1:139:7 | /foo/ | RegExpLiteral | 0 | literals.rb:139:4:139:6 | foo | StringTextComponent | +| literals.rb:140:1:140:8 | /foo/ | RegExpLiteral | 0 | literals.rb:140:4:140:6 | foo | StringTextComponent | +| literals.rb:141:1:141:15 | /foo+\\sbar\\S/ | RegExpLiteral | 0 | literals.rb:141:4:141:7 | foo+ | StringTextComponent | +| literals.rb:141:1:141:15 | /foo+\\sbar\\S/ | RegExpLiteral | 1 | literals.rb:141:8:141:9 | \\s | StringEscapeSequenceComponent | +| literals.rb:141:1:141:15 | /foo+\\sbar\\S/ | RegExpLiteral | 2 | literals.rb:141:10:141:12 | bar | StringTextComponent | +| literals.rb:141:1:141:15 | /foo+\\sbar\\S/ | RegExpLiteral | 3 | literals.rb:141:13:141:14 | \\S | StringEscapeSequenceComponent | +| literals.rb:142:1:142:20 | /foo#{...}bar/ | RegExpLiteral | 0 | literals.rb:142:4:142:6 | foo | StringTextComponent | +| literals.rb:142:1:142:20 | /foo#{...}bar/ | RegExpLiteral | 1 | literals.rb:142:7:142:16 | #{...} | StringInterpolationComponent | +| literals.rb:142:1:142:20 | /foo#{...}bar/ | RegExpLiteral | 2 | literals.rb:142:17:142:19 | bar | StringTextComponent | +| literals.rb:143:1:143:10 | /foo/ | RegExpLiteral | 0 | literals.rb:143:4:143:6 | foo | StringTextComponent | +| literals.rb:146:1:146:34 | "abcdefghijklmnopqrstuvwxyzabcdef" | StringLiteral | 0 | literals.rb:146:2:146:33 | abcdefghijklmnopqrstuvwxyzabcdef | StringTextComponent | +| literals.rb:147:1:147:35 | "foobarfoobarfoobarfoobarfooba..." | StringLiteral | 0 | literals.rb:147:2:147:34 | foobarfoobarfoobarfoobarfoobarfoo | StringTextComponent | +| literals.rb:148:1:148:40 | "foobar\\\\foobar\\\\foobar\\\\fooba..." | StringLiteral | 0 | literals.rb:148:2:148:7 | foobar | StringTextComponent | +| literals.rb:148:1:148:40 | "foobar\\\\foobar\\\\foobar\\\\fooba..." | StringLiteral | 1 | literals.rb:148:8:148:9 | \\\\ | StringEscapeSequenceComponent | +| literals.rb:148:1:148:40 | "foobar\\\\foobar\\\\foobar\\\\fooba..." | StringLiteral | 2 | literals.rb:148:10:148:15 | foobar | StringTextComponent | +| literals.rb:148:1:148:40 | "foobar\\\\foobar\\\\foobar\\\\fooba..." | StringLiteral | 3 | literals.rb:148:16:148:17 | \\\\ | StringEscapeSequenceComponent | +| literals.rb:148:1:148:40 | "foobar\\\\foobar\\\\foobar\\\\fooba..." | StringLiteral | 4 | literals.rb:148:18:148:23 | foobar | StringTextComponent | +| literals.rb:148:1:148:40 | "foobar\\\\foobar\\\\foobar\\\\fooba..." | StringLiteral | 5 | literals.rb:148:24:148:25 | \\\\ | StringEscapeSequenceComponent | +| literals.rb:148:1:148:40 | "foobar\\\\foobar\\\\foobar\\\\fooba..." | StringLiteral | 6 | literals.rb:148:26:148:31 | foobar | StringTextComponent | +| literals.rb:148:1:148:40 | "foobar\\\\foobar\\\\foobar\\\\fooba..." | StringLiteral | 7 | literals.rb:148:32:148:33 | \\\\ | StringEscapeSequenceComponent | +| literals.rb:148:1:148:40 | "foobar\\\\foobar\\\\foobar\\\\fooba..." | StringLiteral | 8 | literals.rb:148:34:148:39 | foobar | StringTextComponent | +| literals.rb:151:9:151:13 | < | 0 | literals.rb:64:1:64:5 | "foo" | +| literals.rb:64:1:64:27 | "..." "..." | | 1 | literals.rb:64:7:64:21 | "bar#{...}" | +| literals.rb:64:1:64:27 | "..." "..." | | 2 | literals.rb:64:23:64:27 | "baz" | +arrayLiterals +| literals.rb:92:1:92:2 | [...] | 0 | +| literals.rb:93:1:93:9 | [...] | 3 | +| literals.rb:94:1:94:14 | [...] | 3 | +| literals.rb:95:1:95:11 | [...] | 2 | +| literals.rb:95:5:95:10 | [...] | 2 | +| literals.rb:98:1:98:4 | %w(...) | 0 | +| literals.rb:99:1:99:15 | %w(...) | 3 | +| literals.rb:100:1:100:15 | %w(...) | 3 | +| literals.rb:101:1:101:21 | %w(...) | 3 | +| literals.rb:102:1:102:21 | %w(...) | 3 | +| literals.rb:105:1:105:4 | %i(...) | 0 | +| literals.rb:106:1:106:15 | %i(...) | 3 | +| literals.rb:107:1:107:15 | %i(...) | 3 | +| literals.rb:108:1:108:25 | %i(...) | 3 | +| literals.rb:109:1:109:25 | %i(...) | 7 | +arrayLiteralElements +| literals.rb:93:1:93:9 | [...] | 0 | literals.rb:93:2:93:2 | 1 | IntegerLiteral | +| literals.rb:93:1:93:9 | [...] | 1 | literals.rb:93:5:93:5 | 2 | IntegerLiteral | +| literals.rb:93:1:93:9 | [...] | 2 | literals.rb:93:8:93:8 | 3 | IntegerLiteral | +| literals.rb:94:1:94:14 | [...] | 0 | literals.rb:94:2:94:2 | 4 | IntegerLiteral | +| literals.rb:94:1:94:14 | [...] | 1 | literals.rb:94:5:94:5 | 5 | IntegerLiteral | +| literals.rb:94:1:94:14 | [...] | 2 | literals.rb:94:8:94:13 | ... / ... | DivExpr | +| literals.rb:95:1:95:11 | [...] | 0 | literals.rb:95:2:95:2 | 7 | IntegerLiteral | +| literals.rb:95:1:95:11 | [...] | 1 | literals.rb:95:5:95:10 | [...] | ArrayLiteral | +| literals.rb:95:5:95:10 | [...] | 0 | literals.rb:95:6:95:6 | 8 | IntegerLiteral | +| literals.rb:95:5:95:10 | [...] | 1 | literals.rb:95:9:95:9 | 9 | IntegerLiteral | +| literals.rb:99:1:99:15 | %w(...) | 0 | literals.rb:99:4:99:6 | "foo" | StringLiteral | +| literals.rb:99:1:99:15 | %w(...) | 1 | literals.rb:99:8:99:10 | "bar" | StringLiteral | +| literals.rb:99:1:99:15 | %w(...) | 2 | literals.rb:99:12:99:14 | "baz" | StringLiteral | +| literals.rb:100:1:100:15 | %w(...) | 0 | literals.rb:100:4:100:6 | "foo" | StringLiteral | +| literals.rb:100:1:100:15 | %w(...) | 1 | literals.rb:100:8:100:10 | "bar" | StringLiteral | +| literals.rb:100:1:100:15 | %w(...) | 2 | literals.rb:100:12:100:14 | "baz" | StringLiteral | +| literals.rb:101:1:101:21 | %w(...) | 0 | literals.rb:101:4:101:6 | "foo" | StringLiteral | +| literals.rb:101:1:101:21 | %w(...) | 1 | literals.rb:101:8:101:16 | "bar#{...}" | StringLiteral | +| literals.rb:101:1:101:21 | %w(...) | 2 | literals.rb:101:18:101:20 | "baz" | StringLiteral | +| literals.rb:102:1:102:21 | %w(...) | 0 | literals.rb:102:4:102:6 | "foo" | StringLiteral | +| literals.rb:102:1:102:21 | %w(...) | 1 | literals.rb:102:8:102:16 | "bar#{1+1}" | StringLiteral | +| literals.rb:102:1:102:21 | %w(...) | 2 | literals.rb:102:18:102:20 | "baz" | StringLiteral | +| literals.rb:106:1:106:15 | %i(...) | 0 | literals.rb:106:4:106:6 | :"foo" | SymbolLiteral | +| literals.rb:106:1:106:15 | %i(...) | 1 | literals.rb:106:8:106:10 | :"bar" | SymbolLiteral | +| literals.rb:106:1:106:15 | %i(...) | 2 | literals.rb:106:12:106:14 | :"baz" | SymbolLiteral | +| literals.rb:107:1:107:15 | %i(...) | 0 | literals.rb:107:4:107:6 | :"foo" | SymbolLiteral | +| literals.rb:107:1:107:15 | %i(...) | 1 | literals.rb:107:8:107:10 | :"bar" | SymbolLiteral | +| literals.rb:107:1:107:15 | %i(...) | 2 | literals.rb:107:12:107:14 | :"baz" | SymbolLiteral | +| literals.rb:108:1:108:25 | %i(...) | 0 | literals.rb:108:4:108:6 | :"foo" | SymbolLiteral | +| literals.rb:108:1:108:25 | %i(...) | 1 | literals.rb:108:8:108:20 | :"bar#{...}" | SymbolLiteral | +| literals.rb:108:1:108:25 | %i(...) | 2 | literals.rb:108:22:108:24 | :"baz" | SymbolLiteral | +| literals.rb:109:1:109:25 | %i(...) | 0 | literals.rb:109:4:109:6 | :"foo" | SymbolLiteral | +| literals.rb:109:1:109:25 | %i(...) | 1 | literals.rb:109:8:109:12 | :"bar#{" | SymbolLiteral | +| literals.rb:109:1:109:25 | %i(...) | 2 | literals.rb:109:14:109:14 | :"2" | SymbolLiteral | +| literals.rb:109:1:109:25 | %i(...) | 3 | literals.rb:109:16:109:16 | :"+" | SymbolLiteral | +| literals.rb:109:1:109:25 | %i(...) | 4 | literals.rb:109:18:109:18 | :"4" | SymbolLiteral | +| literals.rb:109:1:109:25 | %i(...) | 5 | literals.rb:109:20:109:20 | :"}" | SymbolLiteral | +| literals.rb:109:1:109:25 | %i(...) | 6 | literals.rb:109:22:109:24 | :"baz" | SymbolLiteral | +hashLiterals +| literals.rb:84:1:84:14 | {...} | 1 | +| literals.rb:112:1:112:2 | {...} | 0 | +| literals.rb:113:1:113:33 | {...} | 3 | +| literals.rb:114:1:114:17 | {...} | 2 | +hashLiteralElements +| literals.rb:84:1:84:14 | {...} | 0 | literals.rb:84:3:84:12 | Pair | Pair | +| literals.rb:113:1:113:33 | {...} | 0 | literals.rb:113:3:113:8 | Pair | Pair | +| literals.rb:113:1:113:33 | {...} | 1 | literals.rb:113:11:113:19 | Pair | Pair | +| literals.rb:113:1:113:33 | {...} | 2 | literals.rb:113:22:113:31 | Pair | Pair | +| literals.rb:114:1:114:17 | {...} | 0 | literals.rb:114:3:114:8 | Pair | Pair | +| literals.rb:114:1:114:17 | {...} | 1 | literals.rb:114:11:114:15 | ** ... | HashSplatExpr | +hashLiteralKeyValuePairs +| literals.rb:84:1:84:14 | {...} | literals.rb:84:3:84:12 | Pair | literals.rb:84:3:84:5 | :foo | literals.rb:84:8:84:12 | "bar" | +| literals.rb:113:1:113:33 | {...} | literals.rb:113:3:113:8 | Pair | literals.rb:113:3:113:5 | :foo | literals.rb:113:8:113:8 | 1 | +| literals.rb:113:1:113:33 | {...} | literals.rb:113:11:113:19 | Pair | literals.rb:113:11:113:14 | :bar | literals.rb:113:19:113:19 | 2 | +| literals.rb:113:1:113:33 | {...} | literals.rb:113:22:113:31 | Pair | literals.rb:113:22:113:26 | "baz" | literals.rb:113:31:113:31 | 3 | +| literals.rb:114:1:114:17 | {...} | literals.rb:114:3:114:8 | Pair | literals.rb:114:3:114:5 | :foo | literals.rb:114:8:114:8 | 7 | +finiteRangeLiterals +| literals.rb:117:2:117:6 | _ .. _ | literals.rb:117:2:117:2 | 1 | literals.rb:117:5:117:6 | 10 | +| literals.rb:118:2:118:7 | _ ... _ | literals.rb:118:2:118:2 | 1 | literals.rb:118:6:118:7 | 10 | +| literals.rb:119:2:119:7 | _ .. _ | literals.rb:119:2:119:2 | 1 | literals.rb:119:7:119:7 | 0 | +| literals.rb:120:2:120:11 | _ .. _ | literals.rb:120:2:120:6 | call to start | literals.rb:120:9:120:11 | ... + ... | +beginlessRangeLiterals +| literals.rb:122:2:122:4 | _ .. _ | literals.rb:122:4:122:4 | 1 | +endlessRangeLiterals +| literals.rb:121:2:121:4 | _ .. _ | literals.rb:121:2:121:2 | 1 | +| literals.rb:123:2:123:4 | _ .. _ | literals.rb:123:2:123:2 | 0 | +inclusiveRangeLiterals +| literals.rb:117:2:117:6 | _ .. _ | +| literals.rb:119:2:119:7 | _ .. _ | +| literals.rb:120:2:120:11 | _ .. _ | +| literals.rb:121:2:121:4 | _ .. _ | +| literals.rb:122:2:122:4 | _ .. _ | +| literals.rb:123:2:123:4 | _ .. _ | +exclusiveRangeLiterals +| literals.rb:118:2:118:7 | _ ... _ | +numericLiterals +| literals.rb:10:1:10:4 | 1234 | IntegerLiteral | 1234 | +| literals.rb:11:1:11:5 | 5_678 | IntegerLiteral | 5_678 | +| literals.rb:12:1:12:1 | 0 | IntegerLiteral | 0 | +| literals.rb:13:1:13:5 | 0d900 | IntegerLiteral | 0d900 | +| literals.rb:16:1:16:6 | 0x1234 | IntegerLiteral | 0x1234 | +| literals.rb:17:1:17:10 | 0xdeadbeef | IntegerLiteral | 0xdeadbeef | +| literals.rb:18:1:18:11 | 0xF00D_face | IntegerLiteral | 0xF00D_face | +| literals.rb:21:1:21:4 | 0123 | IntegerLiteral | 0123 | +| literals.rb:22:1:22:5 | 0o234 | IntegerLiteral | 0o234 | +| literals.rb:23:1:23:6 | 0O45_6 | IntegerLiteral | 0O45_6 | +| literals.rb:26:1:26:10 | 0b10010100 | IntegerLiteral | 0b10010100 | +| literals.rb:27:1:27:11 | 0B011_01101 | IntegerLiteral | 0B011_01101 | +| literals.rb:30:1:30:5 | 12.34 | FloatLiteral | 12.34 | +| literals.rb:31:1:31:7 | 1234e-2 | FloatLiteral | 1234e-2 | +| literals.rb:32:1:32:7 | 1.234E1 | FloatLiteral | 1.234E1 | +| literals.rb:35:1:35:3 | 23r | RationalLiteral | 23r | +| literals.rb:36:1:36:5 | 9.85r | RationalLiteral | 9.85r | +| literals.rb:39:1:39:2 | 2i | ComplexLiteral | 2i | +| literals.rb:58:13:58:13 | 2 | IntegerLiteral | 2 | +| literals.rb:58:17:58:17 | 2 | IntegerLiteral | 2 | +| literals.rb:59:15:59:15 | 3 | IntegerLiteral | 3 | +| literals.rb:59:19:59:19 | 4 | IntegerLiteral | 4 | +| literals.rb:64:14:64:14 | 1 | IntegerLiteral | 1 | +| literals.rb:64:18:64:18 | 1 | IntegerLiteral | 1 | +| literals.rb:65:17:65:17 | 2 | IntegerLiteral | 2 | +| literals.rb:65:21:65:21 | 3 | IntegerLiteral | 3 | +| literals.rb:66:17:66:17 | 1 | IntegerLiteral | 1 | +| literals.rb:66:19:66:19 | 9 | IntegerLiteral | 9 | +| literals.rb:87:10:87:10 | 2 | IntegerLiteral | 2 | +| literals.rb:87:14:87:14 | 2 | IntegerLiteral | 2 | +| literals.rb:93:2:93:2 | 1 | IntegerLiteral | 1 | +| literals.rb:93:5:93:5 | 2 | IntegerLiteral | 2 | +| literals.rb:93:8:93:8 | 3 | IntegerLiteral | 3 | +| literals.rb:94:2:94:2 | 4 | IntegerLiteral | 4 | +| literals.rb:94:5:94:5 | 5 | IntegerLiteral | 5 | +| literals.rb:94:8:94:9 | 12 | IntegerLiteral | 12 | +| literals.rb:94:13:94:13 | 2 | IntegerLiteral | 2 | +| literals.rb:95:2:95:2 | 7 | IntegerLiteral | 7 | +| literals.rb:95:6:95:6 | 8 | IntegerLiteral | 8 | +| literals.rb:95:9:95:9 | 9 | IntegerLiteral | 9 | +| literals.rb:101:13:101:13 | 1 | IntegerLiteral | 1 | +| literals.rb:101:15:101:15 | 1 | IntegerLiteral | 1 | +| literals.rb:108:14:108:14 | 2 | IntegerLiteral | 2 | +| literals.rb:108:18:108:18 | 4 | IntegerLiteral | 4 | +| literals.rb:113:8:113:8 | 1 | IntegerLiteral | 1 | +| literals.rb:113:19:113:19 | 2 | IntegerLiteral | 2 | +| literals.rb:113:31:113:31 | 3 | IntegerLiteral | 3 | +| literals.rb:114:8:114:8 | 7 | IntegerLiteral | 7 | +| literals.rb:117:2:117:2 | 1 | IntegerLiteral | 1 | +| literals.rb:117:5:117:6 | 10 | IntegerLiteral | 10 | +| literals.rb:118:2:118:2 | 1 | IntegerLiteral | 1 | +| literals.rb:118:6:118:7 | 10 | IntegerLiteral | 10 | +| literals.rb:119:2:119:2 | 1 | IntegerLiteral | 1 | +| literals.rb:119:7:119:7 | 0 | IntegerLiteral | 0 | +| literals.rb:120:9:120:9 | 2 | IntegerLiteral | 2 | +| literals.rb:120:11:120:11 | 3 | IntegerLiteral | 3 | +| literals.rb:121:2:121:2 | 1 | IntegerLiteral | 1 | +| literals.rb:122:4:122:4 | 1 | IntegerLiteral | 1 | +| literals.rb:123:2:123:2 | 0 | IntegerLiteral | 0 | +| literals.rb:123:6:123:6 | 1 | IntegerLiteral | 1 | +| literals.rb:128:11:128:11 | 1 | IntegerLiteral | 1 | +| literals.rb:128:15:128:15 | 1 | IntegerLiteral | 1 | +| literals.rb:129:13:129:13 | 5 | IntegerLiteral | 5 | +| literals.rb:129:17:129:17 | 4 | IntegerLiteral | 4 | +| literals.rb:136:8:136:8 | 1 | IntegerLiteral | 1 | +| literals.rb:136:12:136:12 | 1 | IntegerLiteral | 1 | +| literals.rb:142:10:142:10 | 1 | IntegerLiteral | 1 | +| literals.rb:142:14:142:14 | 1 | IntegerLiteral | 1 | +integerLiterals +| literals.rb:10:1:10:4 | 1234 | IntegerLiteral | 1234 | +| literals.rb:11:1:11:5 | 5_678 | IntegerLiteral | 5_678 | +| literals.rb:12:1:12:1 | 0 | IntegerLiteral | 0 | +| literals.rb:13:1:13:5 | 0d900 | IntegerLiteral | 0d900 | +| literals.rb:16:1:16:6 | 0x1234 | IntegerLiteral | 0x1234 | +| literals.rb:17:1:17:10 | 0xdeadbeef | IntegerLiteral | 0xdeadbeef | +| literals.rb:18:1:18:11 | 0xF00D_face | IntegerLiteral | 0xF00D_face | +| literals.rb:21:1:21:4 | 0123 | IntegerLiteral | 0123 | +| literals.rb:22:1:22:5 | 0o234 | IntegerLiteral | 0o234 | +| literals.rb:23:1:23:6 | 0O45_6 | IntegerLiteral | 0O45_6 | +| literals.rb:26:1:26:10 | 0b10010100 | IntegerLiteral | 0b10010100 | +| literals.rb:27:1:27:11 | 0B011_01101 | IntegerLiteral | 0B011_01101 | +| literals.rb:58:13:58:13 | 2 | IntegerLiteral | 2 | +| literals.rb:58:17:58:17 | 2 | IntegerLiteral | 2 | +| literals.rb:59:15:59:15 | 3 | IntegerLiteral | 3 | +| literals.rb:59:19:59:19 | 4 | IntegerLiteral | 4 | +| literals.rb:64:14:64:14 | 1 | IntegerLiteral | 1 | +| literals.rb:64:18:64:18 | 1 | IntegerLiteral | 1 | +| literals.rb:65:17:65:17 | 2 | IntegerLiteral | 2 | +| literals.rb:65:21:65:21 | 3 | IntegerLiteral | 3 | +| literals.rb:66:17:66:17 | 1 | IntegerLiteral | 1 | +| literals.rb:66:19:66:19 | 9 | IntegerLiteral | 9 | +| literals.rb:87:10:87:10 | 2 | IntegerLiteral | 2 | +| literals.rb:87:14:87:14 | 2 | IntegerLiteral | 2 | +| literals.rb:93:2:93:2 | 1 | IntegerLiteral | 1 | +| literals.rb:93:5:93:5 | 2 | IntegerLiteral | 2 | +| literals.rb:93:8:93:8 | 3 | IntegerLiteral | 3 | +| literals.rb:94:2:94:2 | 4 | IntegerLiteral | 4 | +| literals.rb:94:5:94:5 | 5 | IntegerLiteral | 5 | +| literals.rb:94:8:94:9 | 12 | IntegerLiteral | 12 | +| literals.rb:94:13:94:13 | 2 | IntegerLiteral | 2 | +| literals.rb:95:2:95:2 | 7 | IntegerLiteral | 7 | +| literals.rb:95:6:95:6 | 8 | IntegerLiteral | 8 | +| literals.rb:95:9:95:9 | 9 | IntegerLiteral | 9 | +| literals.rb:101:13:101:13 | 1 | IntegerLiteral | 1 | +| literals.rb:101:15:101:15 | 1 | IntegerLiteral | 1 | +| literals.rb:108:14:108:14 | 2 | IntegerLiteral | 2 | +| literals.rb:108:18:108:18 | 4 | IntegerLiteral | 4 | +| literals.rb:113:8:113:8 | 1 | IntegerLiteral | 1 | +| literals.rb:113:19:113:19 | 2 | IntegerLiteral | 2 | +| literals.rb:113:31:113:31 | 3 | IntegerLiteral | 3 | +| literals.rb:114:8:114:8 | 7 | IntegerLiteral | 7 | +| literals.rb:117:2:117:2 | 1 | IntegerLiteral | 1 | +| literals.rb:117:5:117:6 | 10 | IntegerLiteral | 10 | +| literals.rb:118:2:118:2 | 1 | IntegerLiteral | 1 | +| literals.rb:118:6:118:7 | 10 | IntegerLiteral | 10 | +| literals.rb:119:2:119:2 | 1 | IntegerLiteral | 1 | +| literals.rb:119:7:119:7 | 0 | IntegerLiteral | 0 | +| literals.rb:120:9:120:9 | 2 | IntegerLiteral | 2 | +| literals.rb:120:11:120:11 | 3 | IntegerLiteral | 3 | +| literals.rb:121:2:121:2 | 1 | IntegerLiteral | 1 | +| literals.rb:122:4:122:4 | 1 | IntegerLiteral | 1 | +| literals.rb:123:2:123:2 | 0 | IntegerLiteral | 0 | +| literals.rb:123:6:123:6 | 1 | IntegerLiteral | 1 | +| literals.rb:128:11:128:11 | 1 | IntegerLiteral | 1 | +| literals.rb:128:15:128:15 | 1 | IntegerLiteral | 1 | +| literals.rb:129:13:129:13 | 5 | IntegerLiteral | 5 | +| literals.rb:129:17:129:17 | 4 | IntegerLiteral | 4 | +| literals.rb:136:8:136:8 | 1 | IntegerLiteral | 1 | +| literals.rb:136:12:136:12 | 1 | IntegerLiteral | 1 | +| literals.rb:142:10:142:10 | 1 | IntegerLiteral | 1 | +| literals.rb:142:14:142:14 | 1 | IntegerLiteral | 1 | +floatLiterals +| literals.rb:30:1:30:5 | 12.34 | FloatLiteral | 12.34 | +| literals.rb:31:1:31:7 | 1234e-2 | FloatLiteral | 1234e-2 | +| literals.rb:32:1:32:7 | 1.234E1 | FloatLiteral | 1.234E1 | +rationalLiterals +| literals.rb:35:1:35:3 | 23r | RationalLiteral | 23r | +| literals.rb:36:1:36:5 | 9.85r | RationalLiteral | 9.85r | +complexLiterals +| literals.rb:39:1:39:2 | 2i | ComplexLiteral | 2i | diff --git a/ruby/ql/test/library-tests/ast/literals/literals.ql b/ruby/ql/test/library-tests/ast/literals/literals.ql new file mode 100644 index 000000000000..52469a35e502 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/literals/literals.ql @@ -0,0 +1,110 @@ +import ruby + +query predicate allLiterals(Literal l, string pClass, string valueText) { + pClass = l.getAPrimaryQlClass() and + ( + valueText = l.getValueText() + or + not exists(l.getValueText()) and valueText = "" + ) +} + +query predicate stringlikeLiterals(StringlikeLiteral l, string valueText) { + valueText = l.getValueText() + or + not exists(l.getValueText()) and valueText = "" +} + +query predicate stringLiterals(StringLiteral l, string valueText) { + stringlikeLiterals(l, valueText) +} + +query predicate regExpLiterals(RegExpLiteral l, string valueText, string flags) { + stringlikeLiterals(l, valueText) and flags = l.getFlagString() +} + +query predicate symbolLiterals(SymbolLiteral l, string valueText) { + stringlikeLiterals(l, valueText) +} + +query predicate subshellLiterals(SubshellLiteral l, string valueText) { + stringlikeLiterals(l, valueText) +} + +query predicate stringComponents( + StringlikeLiteral l, string lClass, int i, StringComponent c, string cClass +) { + lClass = l.getAPrimaryQlClass() and + c = l.getComponent(i) and + cClass = c.getAPrimaryQlClass() +} + +query predicate stringInterpolations(StringInterpolationComponent c, int i, Expr e, string eClass) { + e = c.getStmt(i) and eClass = e.getAPrimaryQlClass() +} + +query predicate concatenatedStrings(StringConcatenation sc, string valueText, int n, StringLiteral l) { + l = sc.getString(n) and + ( + valueText = sc.getConcatenatedValueText() + or + not exists(sc.getConcatenatedValueText()) and valueText = "" + ) +} + +query predicate arrayLiterals(ArrayLiteral l, int numElements) { + numElements = l.getNumberOfElements() +} + +query predicate arrayLiteralElements(ArrayLiteral l, int n, Expr elementN, string pClass) { + elementN = l.getElement(n) and pClass = elementN.getAPrimaryQlClass() +} + +query predicate hashLiterals(HashLiteral l, int numElements) { + numElements = l.getNumberOfElements() +} + +query predicate hashLiteralElements(HashLiteral l, int n, Expr elementN, string pClass) { + elementN = l.getElement(n) and pClass = elementN.getAPrimaryQlClass() +} + +query predicate hashLiteralKeyValuePairs(HashLiteral l, Pair p, Expr k, Expr v) { + p = l.getAKeyValuePair() and k = p.getKey() and v = p.getValue() +} + +query predicate finiteRangeLiterals(RangeLiteral l, Expr begin, Expr end) { + begin = l.getBegin() and + end = l.getEnd() +} + +query predicate beginlessRangeLiterals(RangeLiteral l, Expr end) { + not exists(l.getBegin()) and end = l.getEnd() +} + +query predicate endlessRangeLiterals(RangeLiteral l, Expr begin) { + begin = l.getBegin() and not exists(l.getEnd()) +} + +query predicate inclusiveRangeLiterals(RangeLiteral l) { l.isInclusive() } + +query predicate exclusiveRangeLiterals(RangeLiteral l) { l.isExclusive() } + +query predicate numericLiterals(NumericLiteral l, string pClass, string valueText) { + allLiterals(l, pClass, valueText) +} + +query predicate integerLiterals(IntegerLiteral l, string pClass, string valueText) { + allLiterals(l, pClass, valueText) +} + +query predicate floatLiterals(FloatLiteral l, string pClass, string valueText) { + allLiterals(l, pClass, valueText) +} + +query predicate rationalLiterals(RationalLiteral l, string pClass, string valueText) { + allLiterals(l, pClass, valueText) +} + +query predicate complexLiterals(ComplexLiteral l, string pClass, string valueText) { + allLiterals(l, pClass, valueText) +} diff --git a/ruby/ql/test/library-tests/ast/literals/literals.rb b/ruby/ql/test/library-tests/ast/literals/literals.rb new file mode 100644 index 000000000000..a264a6d0003d --- /dev/null +++ b/ruby/ql/test/library-tests/ast/literals/literals.rb @@ -0,0 +1,179 @@ +# boolean values and nil +nil +NIL +false +FALSE +true +TRUE + +# decimal integers +1234 +5_678 +0 +0d900 + +# hexadecimal integers +0x1234 +0xdeadbeef +0xF00D_face + +# octal integers +0123 +0o234 +0O45_6 + +# binary integers +0b10010100 +0B011_01101 + +# floating-point numbers +12.34 +1234e-2 +1.234E1 + +# rational numbers +23r +9.85r + +# imaginary/complex numbers +2i +#3.14i # BAD: parse error + +# imaginary & rational +#1.2ri # BAD: parse error + +# strings +"" +'' +"hello" +'goodbye' +"string with escaped \" quote" +'string with " quote' +%(foo bar baz) +%q +%q(foo ' bar " baz') +%Q(FOO ' BAR " BAZ') +%q(foo\ bar) # "foo\\ bar" +%Q(foo\ bar) # "foo bar" +"2 + 2 = #{ 2 + 2 }" # interpolation +%Q(3 + 4 = #{ 3 + 4 }) # interpolation +'2 + 2 = #{ 2 + 2 }' # no interpolation +%q(3 + 4 = #{ 3 + 4 }) # no interpolation +"foo" 'bar' "baz" # concatenated +%q{foo} "bar" 'baz' # concatenated +"foo" "bar#{ 1 * 1 }" 'baz' # concatenated, interpolation +"foo #{ "bar #{ 2 + 3 } baz" } qux" # interpolation containing string containing interpolation +"foo #{ blah(); 1+9 }" # multiple statements in interpolation + +# characters +?x +?\n +?\s +?\\ +?\u{58} +?\C-a +?\M-a +?\M-\C-a +?\C-\M-a + +# symbols +:"" +:hello +:"foo bar" +:'bar baz' +{ foo: "bar" } +%s(wibble) +%s[wibble wobble] +:"foo_#{ 2 + 2}" # interpolation +:'foo_#{ 1 + 1 }' # no interpolation +%s(foo_#{ 3 - 2 }) # no interpolation + +# arrays +[] +[1, 2, 3] +[4, 5, 12 / 2] +[7, [8, 9]] + +# arrays of strings +%w() +%w(foo bar baz) +%w!foo bar baz! +%W[foo bar#{1+1} baz] # interpolation +%w[foo bar#{1+1} baz] # no interpolation + +# arrays of symbols +%i() +%i(foo bar baz) +%i@foo bar baz@ +%I(foo bar#{ 2 + 4 } baz) # interpolation +%i(foo bar#{ 2 + 4 } baz) # no interpolation + +# hashes +{} +{ foo: 1, :bar => 2, 'baz' => 3 } +{ foo: 7, **bar } # hash-splat argument + +# ranges +(1..10) +(1...10) +(1 .. 0) +(start..2+3) +(1..) # 1 to infinity +(..1) # -infinity to 1 +(0..-1) # BAD: parsed as binary with minus endless range on the LHS + +# subshell +`ls -l` +%x(ls -l) +`du -d #{ 1 + 1 }` # interpolation +%x@du -d #{ 5 - 4 }@ # interpolation + +# regular expressions +// +/foo/ +/foo/i +/foo+\sbar\S/ +/foo#{ 1 + 1 }bar/ # interpolation +/foo/oxm +%r[] +%r(foo) +%r:foo:i +%r{foo+\sbar\S} +%r{foo#{ 1 + 1 }bar} # interpolation +%r:foo:mxo + +# long strings +'abcdefghijklmnopqrstuvwxyzabcdef' # 32 chars, should not be truncated +'foobarfoobarfoobarfoobarfoobarfoo' # 33 chars, should be truncated +"foobar\\foobar\\foobar\\foobar\\foobar" # several short components, but long enough overall to be truncated + +# here documents +run_sql(< diff --git a/ruby/ql/test/library-tests/ast/misc/misc.expected b/ruby/ql/test/library-tests/ast/misc/misc.expected new file mode 100644 index 000000000000..45235cd57871 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/misc/misc.expected @@ -0,0 +1,21 @@ +undef +| misc.rb:3:1:3:30 | undef ... | 0 | misc.rb:3:7:3:9 | foo | foo | MethodName | +| misc.rb:3:1:3:30 | undef ... | 1 | misc.rb:3:12:3:15 | :foo | foo | MethodName | +| misc.rb:3:1:3:30 | undef ... | 2 | misc.rb:3:18:3:21 | foo= | foo= | MethodName | +| misc.rb:3:1:3:30 | undef ... | 3 | misc.rb:3:24:3:25 | [] | [] | MethodName | +| misc.rb:3:1:3:30 | undef ... | 4 | misc.rb:3:28:3:30 | []= | []= | MethodName | +| misc.rb:4:1:4:19 | undef ... | 0 | misc.rb:4:7:4:19 | :"foo_#{...}" | (none) | MethodName | +| misc.rb:5:1:5:35 | undef ... | 0 | misc.rb:5:7:5:9 | nil | nil | MethodName | +| misc.rb:5:1:5:35 | undef ... | 1 | misc.rb:5:12:5:15 | true | true | MethodName | +| misc.rb:5:1:5:35 | undef ... | 2 | misc.rb:5:18:5:22 | false | false | MethodName | +| misc.rb:5:1:5:35 | undef ... | 3 | misc.rb:5:25:5:29 | super | super | MethodName | +| misc.rb:5:1:5:35 | undef ... | 4 | misc.rb:5:32:5:35 | self | self | MethodName | +alias +| misc.rb:7:1:7:14 | alias ... | new | misc.rb:7:7:7:9 | new | new | MethodName | +| misc.rb:7:1:7:14 | alias ... | old | misc.rb:7:11:7:14 | :old | old | MethodName | +| misc.rb:8:1:8:14 | alias ... | new | misc.rb:8:7:8:10 | foo= | foo= | MethodName | +| misc.rb:8:1:8:14 | alias ... | old | misc.rb:8:12:8:14 | []= | []= | MethodName | +| misc.rb:9:1:9:16 | alias ... | new | misc.rb:9:7:9:11 | super | super | MethodName | +| misc.rb:9:1:9:16 | alias ... | old | misc.rb:9:13:9:16 | self | self | MethodName | +| misc.rb:10:1:10:24 | alias ... | new | misc.rb:10:7:10:17 | :"\\n#{...}" | (none) | MethodName | +| misc.rb:10:1:10:24 | alias ... | old | misc.rb:10:19:10:24 | :"foo" | foo | MethodName | diff --git a/ruby/ql/test/library-tests/ast/misc/misc.ql b/ruby/ql/test/library-tests/ast/misc/misc.ql new file mode 100644 index 000000000000..788a8593cdda --- /dev/null +++ b/ruby/ql/test/library-tests/ast/misc/misc.ql @@ -0,0 +1,23 @@ +import ruby + +private string getValueText(MethodName m) { + result = m.getValueText() + or + not exists(m.getValueText()) and result = "(none)" +} + +query predicate undef(UndefStmt u, int i, MethodName m, string name, string pClass) { + pClass = m.getAPrimaryQlClass() and + u.getMethodName(i) = m and + name = getValueText(m) +} + +query predicate alias(AliasStmt a, string prop, MethodName m, string name, string pClass) { + pClass = m.getAPrimaryQlClass() and + name = getValueText(m) and + ( + a.getOldName() = m and prop = "old" + or + a.getNewName() = m and prop = "new" + ) +} diff --git a/ruby/ql/test/library-tests/ast/misc/misc.rb b/ruby/ql/test/library-tests/ast/misc/misc.rb new file mode 100644 index 000000000000..6f7fabec245a --- /dev/null +++ b/ruby/ql/test/library-tests/ast/misc/misc.rb @@ -0,0 +1,10 @@ +bar = "bar" + +undef foo, :foo, foo=, [], []= +undef :"foo_#{bar}" +undef nil, true, false, super, self + +alias new :old +alias foo= []= +alias super self +alias :"\n#{bar}" :"foo" diff --git a/ruby/ql/test/library-tests/ast/modules/classes.expected b/ruby/ql/test/library-tests/ast/modules/classes.expected new file mode 100644 index 000000000000..e81722ecfa2e --- /dev/null +++ b/ruby/ql/test/library-tests/ast/modules/classes.expected @@ -0,0 +1,42 @@ +classes +| classes.rb:3:1:4:3 | Foo | ClassDeclaration | Foo | +| classes.rb:7:1:8:3 | Bar | ClassDeclaration | Bar | +| classes.rb:11:1:12:3 | Baz | ClassDeclaration | Baz | +| classes.rb:16:1:17:3 | MyClass | ClassDeclaration | MyClass | +| classes.rb:20:1:37:3 | Wibble | ClassDeclaration | Wibble | +| classes.rb:32:3:33:5 | ClassInWibble | ClassDeclaration | ClassInWibble | +| classes.rb:55:1:56:3 | MyClassInGlobalScope | ClassDeclaration | MyClassInGlobalScope | +| modules.rb:6:5:7:7 | ClassInFooBar | ClassDeclaration | ClassInFooBar | +| modules.rb:19:3:20:5 | ClassInFoo | ClassDeclaration | ClassInFoo | +| modules.rb:30:3:31:5 | ClassInAnotherDefinitionOfFoo | ClassDeclaration | ClassInAnotherDefinitionOfFoo | +| modules.rb:49:3:50:5 | ClassInAnotherDefinitionOfFooBar | ClassDeclaration | ClassInAnotherDefinitionOfFooBar | +| modules.rb:66:5:67:7 | Bar | ClassDeclaration | Bar | +| modules.rb:72:5:73:7 | Bar | ClassDeclaration | Bar | +| modules.rb:78:5:79:7 | Bar | ClassDeclaration | Bar | +| modules.rb:112:1:113:3 | YY | ClassDeclaration | YY | +| modules.rb:116:7:117:9 | YY | ClassDeclaration | YY | +classesWithNameScopeExprs +| classes.rb:16:1:17:3 | MyClass | classes.rb:16:7:16:14 | MyModule | +| modules.rb:66:5:67:7 | Bar | modules.rb:66:11:66:14 | Foo1 | +| modules.rb:72:5:73:7 | Bar | modules.rb:72:11:72:14 | Foo2 | +| modules.rb:78:5:79:7 | Bar | modules.rb:78:11:78:14 | Foo3 | +classesWithGlobalNameScopeExprs +| classes.rb:55:1:56:3 | MyClassInGlobalScope | +exprsInClasses +| classes.rb:20:1:37:3 | Wibble | 0 | classes.rb:21:3:23:5 | method_a | Method | +| classes.rb:20:1:37:3 | Wibble | 1 | classes.rb:25:3:27:5 | method_b | Method | +| classes.rb:20:1:37:3 | Wibble | 2 | classes.rb:29:3:29:20 | call to some_method_call | MethodCall | +| classes.rb:20:1:37:3 | Wibble | 3 | classes.rb:30:3:30:19 | ... = ... | AssignExpr | +| classes.rb:20:1:37:3 | Wibble | 4 | classes.rb:32:3:33:5 | ClassInWibble | ClassDeclaration | +| classes.rb:20:1:37:3 | Wibble | 5 | classes.rb:35:3:36:5 | ModuleInWibble | ModuleDeclaration | +methodsInClasses +| classes.rb:20:1:37:3 | Wibble | classes.rb:21:3:23:5 | method_a | method_a | +| classes.rb:20:1:37:3 | Wibble | classes.rb:25:3:27:5 | method_b | method_b | +classesInClasses +| classes.rb:20:1:37:3 | Wibble | classes.rb:32:3:33:5 | ClassInWibble | ClassInWibble | +modulesInClasses +| classes.rb:20:1:37:3 | Wibble | classes.rb:35:3:36:5 | ModuleInWibble | ModuleInWibble | +classesWithASuperclass +| classes.rb:7:1:8:3 | Bar | classes.rb:7:13:7:21 | BaseClass | +| classes.rb:11:1:12:3 | Baz | classes.rb:11:13:11:32 | call to superclass_for | +| modules.rb:116:7:117:9 | YY | modules.rb:116:18:116:19 | YY | diff --git a/ruby/ql/test/library-tests/ast/modules/classes.ql b/ruby/ql/test/library-tests/ast/modules/classes.ql new file mode 100644 index 000000000000..a4b392a32cba --- /dev/null +++ b/ruby/ql/test/library-tests/ast/modules/classes.ql @@ -0,0 +1,29 @@ +import ruby + +query predicate classes(ClassDeclaration c, string pClass, string name) { + pClass = c.getAPrimaryQlClass() and name = c.getName() +} + +query predicate classesWithNameScopeExprs(ClassDeclaration c, Expr se) { se = c.getScopeExpr() } + +query predicate classesWithGlobalNameScopeExprs(ClassDeclaration c) { c.hasGlobalScope() } + +query predicate exprsInClasses(ClassDeclaration c, int i, Expr e, string eClass) { + e = c.getStmt(i) and eClass = e.getAPrimaryQlClass() +} + +query predicate methodsInClasses(ClassDeclaration c, Method m, string name) { + m = c.getMethod(name) +} + +query predicate classesInClasses(ClassDeclaration c, ClassDeclaration child, string name) { + child = c.getClass(name) +} + +query predicate modulesInClasses(ClassDeclaration c, ModuleDeclaration m, string name) { + m = c.getModule(name) +} + +query predicate classesWithASuperclass(ClassDeclaration c, Expr scExpr) { + scExpr = c.getSuperclassExpr() +} diff --git a/ruby/ql/test/library-tests/ast/modules/classes.rb b/ruby/ql/test/library-tests/ast/modules/classes.rb new file mode 100644 index 000000000000..ce05212ada1a --- /dev/null +++ b/ruby/ql/test/library-tests/ast/modules/classes.rb @@ -0,0 +1,56 @@ + +# a class with no superclass specified +class Foo +end + +# a class where the superclass is a constant +class Bar < BaseClass +end + +# a class where the superclass is a call expression +class Baz < superclass_for(:baz) +end + +# a class where the name is a scope resolution +module MyModule; end +class MyModule::MyClass +end + +# a class with various expressions +class Wibble + def method_a + puts 'a' + end + + def method_b + puts 'b' + end + + some_method_call() + $global_var = 123 + + class ClassInWibble + end + + module ModuleInWibble + end +end + +# a singleton class with some methods and some other arbitrary expressions +x = 'hello' +class << x + def length + 100 * super + end + + def wibble + puts 'wibble' + end + + another_method_call + $global_var2 = 456 +end + +# a class where the name is a scope resolution using the global scope +class ::MyClassInGlobalScope +end \ No newline at end of file diff --git a/ruby/ql/test/library-tests/ast/modules/module_base.expected b/ruby/ql/test/library-tests/ast/modules/module_base.expected new file mode 100644 index 000000000000..109ffa5cf751 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/modules/module_base.expected @@ -0,0 +1,102 @@ +moduleBases +| classes.rb:2:1:56:3 | classes.rb | Toplevel | +| classes.rb:3:1:4:3 | Foo | ClassDeclaration | +| classes.rb:7:1:8:3 | Bar | ClassDeclaration | +| classes.rb:11:1:12:3 | Baz | ClassDeclaration | +| classes.rb:15:1:15:20 | MyModule | ModuleDeclaration | +| classes.rb:16:1:17:3 | MyClass | ClassDeclaration | +| classes.rb:20:1:37:3 | Wibble | ClassDeclaration | +| classes.rb:32:3:33:5 | ClassInWibble | ClassDeclaration | +| classes.rb:35:3:36:5 | ModuleInWibble | ModuleDeclaration | +| classes.rb:41:1:52:3 | class << ... | ClassDeclaration | +| classes.rb:55:1:56:3 | MyClassInGlobalScope | ClassDeclaration | +| modules.rb:1:1:2:3 | Empty | ModuleDeclaration | +| modules.rb:1:1:122:1 | modules.rb | Toplevel | +| modules.rb:4:1:24:3 | Foo | ModuleDeclaration | +| modules.rb:5:3:14:5 | Bar | ModuleDeclaration | +| modules.rb:6:5:7:7 | ClassInFooBar | ClassDeclaration | +| modules.rb:19:3:20:5 | ClassInFoo | ClassDeclaration | +| modules.rb:26:1:35:3 | Foo | ModuleDeclaration | +| modules.rb:30:3:31:5 | ClassInAnotherDefinitionOfFoo | ClassDeclaration | +| modules.rb:37:1:46:3 | Bar | ModuleDeclaration | +| modules.rb:48:1:57:3 | Bar | ModuleDeclaration | +| modules.rb:49:3:50:5 | ClassInAnotherDefinitionOfFooBar | ClassDeclaration | +| modules.rb:60:1:61:3 | MyModuleInGlobalScope | ModuleDeclaration | +| modules.rb:63:1:81:3 | Test | ModuleDeclaration | +| modules.rb:65:3:68:5 | Foo1 | ModuleDeclaration | +| modules.rb:66:5:67:7 | Bar | ClassDeclaration | +| modules.rb:70:3:74:5 | Foo2 | ModuleDeclaration | +| modules.rb:71:5:71:19 | Foo2 | ModuleDeclaration | +| modules.rb:72:5:73:7 | Bar | ClassDeclaration | +| modules.rb:76:3:80:5 | Foo3 | ModuleDeclaration | +| modules.rb:78:5:79:7 | Bar | ClassDeclaration | +| modules.rb:83:1:86:3 | Other | ModuleDeclaration | +| modules.rb:84:3:85:5 | Foo1 | ModuleDeclaration | +| modules.rb:88:1:93:3 | IncludeTest | ModuleDeclaration | +| modules.rb:91:3:92:5 | Y | ModuleDeclaration | +| modules.rb:95:1:99:3 | IncludeTest2 | ModuleDeclaration | +| modules.rb:97:3:98:5 | Z | ModuleDeclaration | +| modules.rb:101:1:105:3 | PrependTest | ModuleDeclaration | +| modules.rb:103:3:104:5 | Y | ModuleDeclaration | +| modules.rb:107:1:110:3 | MM | ModuleDeclaration | +| modules.rb:108:3:109:5 | MM | ModuleDeclaration | +| modules.rb:112:1:113:3 | YY | ClassDeclaration | +| modules.rb:115:1:118:3 | XX | ModuleDeclaration | +| modules.rb:116:7:117:9 | YY | ClassDeclaration | +| modules.rb:120:1:121:3 | Baz | ModuleDeclaration | +| toplevel.rb:1:1:5:23 | toplevel.rb | Toplevel | +moduleBaseClasses +| classes.rb:2:1:56:3 | classes.rb | classes.rb:3:1:4:3 | Foo | +| classes.rb:2:1:56:3 | classes.rb | classes.rb:7:1:8:3 | Bar | +| classes.rb:2:1:56:3 | classes.rb | classes.rb:11:1:12:3 | Baz | +| classes.rb:2:1:56:3 | classes.rb | classes.rb:16:1:17:3 | MyClass | +| classes.rb:2:1:56:3 | classes.rb | classes.rb:20:1:37:3 | Wibble | +| classes.rb:2:1:56:3 | classes.rb | classes.rb:55:1:56:3 | MyClassInGlobalScope | +| classes.rb:20:1:37:3 | Wibble | classes.rb:32:3:33:5 | ClassInWibble | +| modules.rb:1:1:122:1 | modules.rb | modules.rb:112:1:113:3 | YY | +| modules.rb:4:1:24:3 | Foo | modules.rb:19:3:20:5 | ClassInFoo | +| modules.rb:5:3:14:5 | Bar | modules.rb:6:5:7:7 | ClassInFooBar | +| modules.rb:26:1:35:3 | Foo | modules.rb:30:3:31:5 | ClassInAnotherDefinitionOfFoo | +| modules.rb:48:1:57:3 | Bar | modules.rb:49:3:50:5 | ClassInAnotherDefinitionOfFooBar | +| modules.rb:65:3:68:5 | Foo1 | modules.rb:66:5:67:7 | Bar | +| modules.rb:70:3:74:5 | Foo2 | modules.rb:72:5:73:7 | Bar | +| modules.rb:76:3:80:5 | Foo3 | modules.rb:78:5:79:7 | Bar | +| modules.rb:115:1:118:3 | XX | modules.rb:116:7:117:9 | YY | +moduleBaseMethods +| classes.rb:20:1:37:3 | Wibble | classes.rb:21:3:23:5 | method_a | +| classes.rb:20:1:37:3 | Wibble | classes.rb:25:3:27:5 | method_b | +| classes.rb:41:1:52:3 | class << ... | classes.rb:42:3:44:5 | length | +| classes.rb:41:1:52:3 | class << ... | classes.rb:46:3:48:5 | wibble | +| modules.rb:4:1:24:3 | Foo | modules.rb:16:3:17:5 | method_in_foo | +| modules.rb:5:3:14:5 | Bar | modules.rb:9:5:10:7 | method_in_foo_bar | +| modules.rb:26:1:35:3 | Foo | modules.rb:27:3:28:5 | method_in_another_definition_of_foo | +| modules.rb:37:1:46:3 | Bar | modules.rb:38:3:39:5 | method_a | +| modules.rb:37:1:46:3 | Bar | modules.rb:41:3:42:5 | method_b | +| modules.rb:48:1:57:3 | Bar | modules.rb:52:3:53:5 | method_in_another_definition_of_foo_bar | +moduleBaseModules +| classes.rb:2:1:56:3 | classes.rb | classes.rb:15:1:15:20 | MyModule | +| classes.rb:20:1:37:3 | Wibble | classes.rb:35:3:36:5 | ModuleInWibble | +| modules.rb:1:1:122:1 | modules.rb | modules.rb:1:1:2:3 | Empty | +| modules.rb:1:1:122:1 | modules.rb | modules.rb:4:1:24:3 | Foo | +| modules.rb:1:1:122:1 | modules.rb | modules.rb:26:1:35:3 | Foo | +| modules.rb:1:1:122:1 | modules.rb | modules.rb:37:1:46:3 | Bar | +| modules.rb:1:1:122:1 | modules.rb | modules.rb:48:1:57:3 | Bar | +| modules.rb:1:1:122:1 | modules.rb | modules.rb:60:1:61:3 | MyModuleInGlobalScope | +| modules.rb:1:1:122:1 | modules.rb | modules.rb:63:1:81:3 | Test | +| modules.rb:1:1:122:1 | modules.rb | modules.rb:83:1:86:3 | Other | +| modules.rb:1:1:122:1 | modules.rb | modules.rb:88:1:93:3 | IncludeTest | +| modules.rb:1:1:122:1 | modules.rb | modules.rb:95:1:99:3 | IncludeTest2 | +| modules.rb:1:1:122:1 | modules.rb | modules.rb:101:1:105:3 | PrependTest | +| modules.rb:1:1:122:1 | modules.rb | modules.rb:107:1:110:3 | MM | +| modules.rb:1:1:122:1 | modules.rb | modules.rb:115:1:118:3 | XX | +| modules.rb:1:1:122:1 | modules.rb | modules.rb:120:1:121:3 | Baz | +| modules.rb:4:1:24:3 | Foo | modules.rb:5:3:14:5 | Bar | +| modules.rb:63:1:81:3 | Test | modules.rb:65:3:68:5 | Foo1 | +| modules.rb:63:1:81:3 | Test | modules.rb:70:3:74:5 | Foo2 | +| modules.rb:63:1:81:3 | Test | modules.rb:76:3:80:5 | Foo3 | +| modules.rb:70:3:74:5 | Foo2 | modules.rb:71:5:71:19 | Foo2 | +| modules.rb:83:1:86:3 | Other | modules.rb:84:3:85:5 | Foo1 | +| modules.rb:88:1:93:3 | IncludeTest | modules.rb:91:3:92:5 | Y | +| modules.rb:95:1:99:3 | IncludeTest2 | modules.rb:97:3:98:5 | Z | +| modules.rb:101:1:105:3 | PrependTest | modules.rb:103:3:104:5 | Y | +| modules.rb:107:1:110:3 | MM | modules.rb:108:3:109:5 | MM | diff --git a/ruby/ql/test/library-tests/ast/modules/module_base.ql b/ruby/ql/test/library-tests/ast/modules/module_base.ql new file mode 100644 index 000000000000..e95faa629699 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/modules/module_base.ql @@ -0,0 +1,9 @@ +import ruby + +query predicate moduleBases(ModuleBase mb, string pClass) { pClass = mb.getAPrimaryQlClass() } + +query predicate moduleBaseClasses(ModuleBase mb, ClassDeclaration c) { c = mb.getAClass() } + +query predicate moduleBaseMethods(ModuleBase mb, Method m) { m = mb.getAMethod() } + +query predicate moduleBaseModules(ModuleBase mb, ModuleDeclaration m) { m = mb.getAModule() } diff --git a/ruby/ql/test/library-tests/ast/modules/modules.expected b/ruby/ql/test/library-tests/ast/modules/modules.expected new file mode 100644 index 000000000000..61352f7c78e3 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/modules/modules.expected @@ -0,0 +1,103 @@ +modules +| classes.rb:15:1:15:20 | MyModule | ModuleDeclaration | MyModule | +| classes.rb:35:3:36:5 | ModuleInWibble | ModuleDeclaration | ModuleInWibble | +| modules.rb:1:1:2:3 | Empty | ModuleDeclaration | Empty | +| modules.rb:4:1:24:3 | Foo | ModuleDeclaration | Foo | +| modules.rb:5:3:14:5 | Bar | ModuleDeclaration | Bar | +| modules.rb:26:1:35:3 | Foo | ModuleDeclaration | Foo | +| modules.rb:37:1:46:3 | Bar | ModuleDeclaration | Bar | +| modules.rb:48:1:57:3 | Bar | ModuleDeclaration | Bar | +| modules.rb:60:1:61:3 | MyModuleInGlobalScope | ModuleDeclaration | MyModuleInGlobalScope | +| modules.rb:63:1:81:3 | Test | ModuleDeclaration | Test | +| modules.rb:65:3:68:5 | Foo1 | ModuleDeclaration | Foo1 | +| modules.rb:70:3:74:5 | Foo2 | ModuleDeclaration | Foo2 | +| modules.rb:71:5:71:19 | Foo2 | ModuleDeclaration | Foo2 | +| modules.rb:76:3:80:5 | Foo3 | ModuleDeclaration | Foo3 | +| modules.rb:83:1:86:3 | Other | ModuleDeclaration | Other | +| modules.rb:84:3:85:5 | Foo1 | ModuleDeclaration | Foo1 | +| modules.rb:88:1:93:3 | IncludeTest | ModuleDeclaration | IncludeTest | +| modules.rb:91:3:92:5 | Y | ModuleDeclaration | Y | +| modules.rb:95:1:99:3 | IncludeTest2 | ModuleDeclaration | IncludeTest2 | +| modules.rb:97:3:98:5 | Z | ModuleDeclaration | Z | +| modules.rb:101:1:105:3 | PrependTest | ModuleDeclaration | PrependTest | +| modules.rb:103:3:104:5 | Y | ModuleDeclaration | Y | +| modules.rb:107:1:110:3 | MM | ModuleDeclaration | MM | +| modules.rb:108:3:109:5 | MM | ModuleDeclaration | MM | +| modules.rb:115:1:118:3 | XX | ModuleDeclaration | XX | +| modules.rb:120:1:121:3 | Baz | ModuleDeclaration | Baz | +modulesWithScopeExprs +| modules.rb:48:1:57:3 | Bar | modules.rb:48:8:48:10 | Foo | +| modules.rb:91:3:92:5 | Y | modules.rb:91:10:91:13 | Foo1 | +| modules.rb:97:3:98:5 | Z | modules.rb:97:10:97:13 | Foo1 | +| modules.rb:103:3:104:5 | Y | modules.rb:103:10:103:13 | Foo2 | +| modules.rb:108:3:109:5 | MM | modules.rb:108:10:108:11 | MM | +| modules.rb:120:1:121:3 | Baz | modules.rb:120:8:120:22 | Bar | +modulesWithGlobalNameScopeExprs +| modules.rb:60:1:61:3 | MyModuleInGlobalScope | +exprsInModules +| modules.rb:4:1:24:3 | Foo | 0 | modules.rb:5:3:14:5 | Bar | ModuleDeclaration | +| modules.rb:4:1:24:3 | Foo | 1 | modules.rb:16:3:17:5 | method_in_foo | Method | +| modules.rb:4:1:24:3 | Foo | 2 | modules.rb:19:3:20:5 | ClassInFoo | ClassDeclaration | +| modules.rb:4:1:24:3 | Foo | 3 | modules.rb:22:3:22:19 | call to puts | MethodCall | +| modules.rb:4:1:24:3 | Foo | 4 | modules.rb:23:3:23:17 | ... = ... | AssignExpr | +| modules.rb:5:3:14:5 | Bar | 0 | modules.rb:6:5:7:7 | ClassInFooBar | ClassDeclaration | +| modules.rb:5:3:14:5 | Bar | 1 | modules.rb:9:5:10:7 | method_in_foo_bar | Method | +| modules.rb:5:3:14:5 | Bar | 2 | modules.rb:12:5:12:26 | call to puts | MethodCall | +| modules.rb:5:3:14:5 | Bar | 3 | modules.rb:13:5:13:19 | ... = ... | AssignExpr | +| modules.rb:26:1:35:3 | Foo | 0 | modules.rb:27:3:28:5 | method_in_another_definition_of_foo | Method | +| modules.rb:26:1:35:3 | Foo | 1 | modules.rb:30:3:31:5 | ClassInAnotherDefinitionOfFoo | ClassDeclaration | +| modules.rb:26:1:35:3 | Foo | 2 | modules.rb:33:3:33:25 | call to puts | MethodCall | +| modules.rb:26:1:35:3 | Foo | 3 | modules.rb:34:3:34:17 | ... = ... | AssignExpr | +| modules.rb:37:1:46:3 | Bar | 0 | modules.rb:38:3:39:5 | method_a | Method | +| modules.rb:37:1:46:3 | Bar | 1 | modules.rb:41:3:42:5 | method_b | Method | +| modules.rb:37:1:46:3 | Bar | 2 | modules.rb:44:3:44:19 | call to puts | MethodCall | +| modules.rb:37:1:46:3 | Bar | 3 | modules.rb:45:3:45:17 | ... = ... | AssignExpr | +| modules.rb:48:1:57:3 | Bar | 0 | modules.rb:49:3:50:5 | ClassInAnotherDefinitionOfFooBar | ClassDeclaration | +| modules.rb:48:1:57:3 | Bar | 1 | modules.rb:52:3:53:5 | method_in_another_definition_of_foo_bar | Method | +| modules.rb:48:1:57:3 | Bar | 2 | modules.rb:55:3:55:30 | call to puts | MethodCall | +| modules.rb:48:1:57:3 | Bar | 3 | modules.rb:56:3:56:17 | ... = ... | AssignExpr | +| modules.rb:63:1:81:3 | Test | 0 | modules.rb:65:3:68:5 | Foo1 | ModuleDeclaration | +| modules.rb:63:1:81:3 | Test | 1 | modules.rb:70:3:74:5 | Foo2 | ModuleDeclaration | +| modules.rb:63:1:81:3 | Test | 2 | modules.rb:76:3:80:5 | Foo3 | ModuleDeclaration | +| modules.rb:65:3:68:5 | Foo1 | 0 | modules.rb:66:5:67:7 | Bar | ClassDeclaration | +| modules.rb:70:3:74:5 | Foo2 | 0 | modules.rb:71:5:71:19 | Foo2 | ModuleDeclaration | +| modules.rb:70:3:74:5 | Foo2 | 1 | modules.rb:72:5:73:7 | Bar | ClassDeclaration | +| modules.rb:76:3:80:5 | Foo3 | 0 | modules.rb:77:5:77:17 | ... = ... | AssignExpr | +| modules.rb:76:3:80:5 | Foo3 | 1 | modules.rb:78:5:79:7 | Bar | ClassDeclaration | +| modules.rb:83:1:86:3 | Other | 0 | modules.rb:84:3:85:5 | Foo1 | ModuleDeclaration | +| modules.rb:88:1:93:3 | IncludeTest | 0 | modules.rb:89:3:89:16 | call to include | MethodCall | +| modules.rb:88:1:93:3 | IncludeTest | 1 | modules.rb:90:3:90:38 | call to module_eval | MethodCall | +| modules.rb:88:1:93:3 | IncludeTest | 2 | modules.rb:91:3:92:5 | Y | ModuleDeclaration | +| modules.rb:95:1:99:3 | IncludeTest2 | 0 | modules.rb:96:3:96:14 | call to include | MethodCall | +| modules.rb:95:1:99:3 | IncludeTest2 | 1 | modules.rb:97:3:98:5 | Z | ModuleDeclaration | +| modules.rb:101:1:105:3 | PrependTest | 0 | modules.rb:102:3:102:16 | call to prepend | MethodCall | +| modules.rb:101:1:105:3 | PrependTest | 1 | modules.rb:103:3:104:5 | Y | ModuleDeclaration | +| modules.rb:107:1:110:3 | MM | 0 | modules.rb:108:3:109:5 | MM | ModuleDeclaration | +| modules.rb:115:1:118:3 | XX | 0 | modules.rb:116:7:117:9 | YY | ClassDeclaration | +methodsInModules +| modules.rb:4:1:24:3 | Foo | modules.rb:16:3:17:5 | method_in_foo | method_in_foo | +| modules.rb:5:3:14:5 | Bar | modules.rb:9:5:10:7 | method_in_foo_bar | method_in_foo_bar | +| modules.rb:26:1:35:3 | Foo | modules.rb:27:3:28:5 | method_in_another_definition_of_foo | method_in_another_definition_of_foo | +| modules.rb:37:1:46:3 | Bar | modules.rb:38:3:39:5 | method_a | method_a | +| modules.rb:37:1:46:3 | Bar | modules.rb:41:3:42:5 | method_b | method_b | +| modules.rb:48:1:57:3 | Bar | modules.rb:52:3:53:5 | method_in_another_definition_of_foo_bar | method_in_another_definition_of_foo_bar | +classesInModules +| modules.rb:4:1:24:3 | Foo | modules.rb:19:3:20:5 | ClassInFoo | ClassInFoo | +| modules.rb:5:3:14:5 | Bar | modules.rb:6:5:7:7 | ClassInFooBar | ClassInFooBar | +| modules.rb:26:1:35:3 | Foo | modules.rb:30:3:31:5 | ClassInAnotherDefinitionOfFoo | ClassInAnotherDefinitionOfFoo | +| modules.rb:48:1:57:3 | Bar | modules.rb:49:3:50:5 | ClassInAnotherDefinitionOfFooBar | ClassInAnotherDefinitionOfFooBar | +| modules.rb:65:3:68:5 | Foo1 | modules.rb:66:5:67:7 | Bar | Bar | +| modules.rb:70:3:74:5 | Foo2 | modules.rb:72:5:73:7 | Bar | Bar | +| modules.rb:76:3:80:5 | Foo3 | modules.rb:78:5:79:7 | Bar | Bar | +| modules.rb:115:1:118:3 | XX | modules.rb:116:7:117:9 | YY | YY | +modulesInModules +| modules.rb:4:1:24:3 | Foo | modules.rb:5:3:14:5 | Bar | Bar | +| modules.rb:63:1:81:3 | Test | modules.rb:65:3:68:5 | Foo1 | Foo1 | +| modules.rb:63:1:81:3 | Test | modules.rb:70:3:74:5 | Foo2 | Foo2 | +| modules.rb:63:1:81:3 | Test | modules.rb:76:3:80:5 | Foo3 | Foo3 | +| modules.rb:70:3:74:5 | Foo2 | modules.rb:71:5:71:19 | Foo2 | Foo2 | +| modules.rb:83:1:86:3 | Other | modules.rb:84:3:85:5 | Foo1 | Foo1 | +| modules.rb:88:1:93:3 | IncludeTest | modules.rb:91:3:92:5 | Y | Y | +| modules.rb:95:1:99:3 | IncludeTest2 | modules.rb:97:3:98:5 | Z | Z | +| modules.rb:101:1:105:3 | PrependTest | modules.rb:103:3:104:5 | Y | Y | +| modules.rb:107:1:110:3 | MM | modules.rb:108:3:109:5 | MM | MM | diff --git a/ruby/ql/test/library-tests/ast/modules/modules.ql b/ruby/ql/test/library-tests/ast/modules/modules.ql new file mode 100644 index 000000000000..9ed9c7b4fa26 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/modules/modules.ql @@ -0,0 +1,25 @@ +import ruby + +query predicate modules(ModuleDeclaration m, string pClass, string name) { + pClass = m.getAPrimaryQlClass() and name = m.getName() +} + +query predicate modulesWithScopeExprs(ModuleDeclaration m, Expr se) { se = m.getScopeExpr() } + +query predicate modulesWithGlobalNameScopeExprs(ModuleDeclaration m) { m.hasGlobalScope() } + +query predicate exprsInModules(ModuleDeclaration m, int i, Expr e, string eClass) { + e = m.getStmt(i) and eClass = e.getAPrimaryQlClass() +} + +query predicate methodsInModules(ModuleDeclaration mod, Method method, string name) { + method = mod.getMethod(name) +} + +query predicate classesInModules(ModuleDeclaration mod, ClassDeclaration klass, string name) { + klass = mod.getClass(name) +} + +query predicate modulesInModules(ModuleDeclaration mod, ModuleDeclaration child, string name) { + child = mod.getModule(name) +} diff --git a/ruby/ql/test/library-tests/ast/modules/modules.rb b/ruby/ql/test/library-tests/ast/modules/modules.rb new file mode 100644 index 000000000000..8287b0a1bc40 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/modules/modules.rb @@ -0,0 +1,122 @@ +module Empty +end + +module Foo + module Bar + class ClassInFooBar + end + + def method_in_foo_bar + end + + puts 'module Foo::Bar' + $global_var = 0 + end + + def method_in_foo + end + + class ClassInFoo + end + + puts 'module Foo' + $global_var = 1 +end + +module Foo + def method_in_another_definition_of_foo + end + + class ClassInAnotherDefinitionOfFoo + end + + puts 'module Foo again' + $global_var = 2 +end + +module Bar + def method_a + end + + def method_b + end + + puts 'module Bar' + $global_var = 3 +end + +module Foo::Bar + class ClassInAnotherDefinitionOfFooBar + end + + def method_in_another_definition_of_foo_bar + end + + puts 'module Foo::Bar again' + $global_var = 4 +end + +# a module where the name is a scope resolution using the global scope +module ::MyModuleInGlobalScope +end + +module Test + + module Foo1 + class Foo1::Bar + end + end + + module Foo2 + module Foo2 end + class Foo2::Bar + end + end + + module Foo3 + Foo3 = Object + class Foo3::Bar + end + end +end + +module Other + module Foo1 + end +end + +module IncludeTest + include ::Test + Object.module_eval { prepend Other } + module Foo1::Y + end +end + +module IncludeTest2 + include Test + module Foo1::Z + end +end + +module PrependTest + prepend ::Test + module Foo2::Y + end +end + +module MM + module MM::MM + end +end + +class YY +end + +module XX + class YY < YY + end +end + +module Test::Foo1::Bar::Baz +end + diff --git a/ruby/ql/test/library-tests/ast/modules/singleton_classes.expected b/ruby/ql/test/library-tests/ast/modules/singleton_classes.expected new file mode 100644 index 000000000000..f1c13b476d67 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/modules/singleton_classes.expected @@ -0,0 +1,10 @@ +singletonClasses +| classes.rb:41:1:52:3 | class << ... | ClassDeclaration | classes.rb:41:10:41:10 | x | +exprsInSingletonClasses +| classes.rb:41:1:52:3 | class << ... | 0 | classes.rb:42:3:44:5 | length | Method | +| classes.rb:41:1:52:3 | class << ... | 1 | classes.rb:46:3:48:5 | wibble | Method | +| classes.rb:41:1:52:3 | class << ... | 2 | classes.rb:50:3:50:21 | call to another_method_call | MethodCall | +| classes.rb:41:1:52:3 | class << ... | 3 | classes.rb:51:3:51:20 | ... = ... | AssignExpr | +methodsInSingletonClasses +| classes.rb:41:1:52:3 | class << ... | classes.rb:42:3:44:5 | length | +| classes.rb:41:1:52:3 | class << ... | classes.rb:46:3:48:5 | wibble | diff --git a/ruby/ql/test/library-tests/ast/modules/singleton_classes.ql b/ruby/ql/test/library-tests/ast/modules/singleton_classes.ql new file mode 100644 index 000000000000..d02dd8cb68c0 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/modules/singleton_classes.ql @@ -0,0 +1,11 @@ +import ruby + +query predicate singletonClasses(SingletonClass sc, string pClass, Expr value) { + pClass = sc.getAPrimaryQlClass() and value = sc.getValue() +} + +query predicate exprsInSingletonClasses(SingletonClass sc, int i, Expr e, string eClass) { + e = sc.getStmt(i) and eClass = e.getAPrimaryQlClass() +} + +query predicate methodsInSingletonClasses(SingletonClass sc, Method m) { m = sc.getAMethod() } diff --git a/ruby/ql/test/library-tests/ast/modules/toplevel.expected b/ruby/ql/test/library-tests/ast/modules/toplevel.expected new file mode 100644 index 000000000000..c15df4682994 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/modules/toplevel.expected @@ -0,0 +1,8 @@ +toplevel +| classes.rb:2:1:56:3 | classes.rb | Toplevel | +| modules.rb:1:1:122:1 | modules.rb | Toplevel | +| toplevel.rb:1:1:5:23 | toplevel.rb | Toplevel | +beginBlocks +| toplevel.rb:1:1:5:23 | toplevel.rb | 0 | toplevel.rb:5:1:5:22 | BEGIN { ... } | +endBlocks +| toplevel.rb:1:1:5:23 | toplevel.rb | toplevel.rb:3:1:3:18 | END { ... } | diff --git a/ruby/ql/test/library-tests/ast/modules/toplevel.ql b/ruby/ql/test/library-tests/ast/modules/toplevel.ql new file mode 100644 index 000000000000..90afb185ff65 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/modules/toplevel.ql @@ -0,0 +1,9 @@ +import ruby + +query predicate toplevel(Toplevel m, string pClass) { pClass = m.getAPrimaryQlClass() } + +query predicate beginBlocks(Toplevel m, int i, BeginBlock b) { b = m.getBeginBlock(i) } + +query predicate endBlocks(Toplevel m, EndBlock b) { + b.getLocation().getFile() = m.getLocation().getFile() +} diff --git a/ruby/ql/test/library-tests/ast/modules/toplevel.rb b/ruby/ql/test/library-tests/ast/modules/toplevel.rb new file mode 100644 index 000000000000..461053925312 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/modules/toplevel.rb @@ -0,0 +1,5 @@ +puts "world" + +END { puts "!!!" } + +BEGIN { puts "hello" } diff --git a/ruby/ql/test/library-tests/ast/operations/assignment.expected b/ruby/ql/test/library-tests/ast/operations/assignment.expected new file mode 100644 index 000000000000..7a536f183f7a --- /dev/null +++ b/ruby/ql/test/library-tests/ast/operations/assignment.expected @@ -0,0 +1,90 @@ +assignments +| operations.rb:3:1:3:5 | ... = ... | = | operations.rb:3:1:3:1 | a | operations.rb:3:5:3:5 | 0 | AssignExpr | +| operations.rb:4:1:4:5 | ... = ... | = | operations.rb:4:1:4:1 | b | operations.rb:4:5:4:5 | 0 | AssignExpr | +| operations.rb:5:1:5:7 | ... = ... | = | operations.rb:5:1:5:3 | bar | operations.rb:5:7:5:7 | 0 | AssignExpr | +| operations.rb:6:1:6:8 | ... = ... | = | operations.rb:6:1:6:4 | base | operations.rb:6:8:6:8 | 0 | AssignExpr | +| operations.rb:7:1:7:7 | ... = ... | = | operations.rb:7:1:7:3 | baz | operations.rb:7:7:7:7 | 0 | AssignExpr | +| operations.rb:8:1:8:7 | ... = ... | = | operations.rb:8:1:8:3 | foo | operations.rb:8:7:8:7 | 0 | AssignExpr | +| operations.rb:9:1:9:10 | ... = ... | = | operations.rb:9:1:9:6 | handle | operations.rb:9:10:9:10 | 0 | AssignExpr | +| operations.rb:10:1:10:5 | ... = ... | = | operations.rb:10:1:10:1 | m | operations.rb:10:5:10:5 | 0 | AssignExpr | +| operations.rb:11:1:11:8 | ... = ... | = | operations.rb:11:1:11:4 | mask | operations.rb:11:8:11:8 | 0 | AssignExpr | +| operations.rb:12:1:12:5 | ... = ... | = | operations.rb:12:1:12:1 | n | operations.rb:12:5:12:5 | 0 | AssignExpr | +| operations.rb:13:1:13:8 | ... = ... | = | operations.rb:13:1:13:4 | name | operations.rb:13:8:13:8 | 0 | AssignExpr | +| operations.rb:14:1:14:7 | ... = ... | = | operations.rb:14:1:14:3 | num | operations.rb:14:7:14:7 | 0 | AssignExpr | +| operations.rb:15:1:15:9 | ... = ... | = | operations.rb:15:1:15:5 | power | operations.rb:15:9:15:9 | 0 | AssignExpr | +| operations.rb:16:1:16:7 | ... = ... | = | operations.rb:16:1:16:3 | qux | operations.rb:16:7:16:7 | 0 | AssignExpr | +| operations.rb:17:1:17:5 | ... = ... | = | operations.rb:17:1:17:1 | w | operations.rb:17:5:17:5 | 0 | AssignExpr | +| operations.rb:18:1:18:5 | ... = ... | = | operations.rb:18:1:18:1 | x | operations.rb:18:5:18:5 | 0 | AssignExpr | +| operations.rb:19:1:19:5 | ... = ... | = | operations.rb:19:1:19:1 | y | operations.rb:19:5:19:5 | 0 | AssignExpr | +| operations.rb:20:1:20:5 | ... = ... | = | operations.rb:20:1:20:1 | z | operations.rb:20:5:20:5 | 0 | AssignExpr | +| operations.rb:69:1:69:8 | ... += ... | += | operations.rb:69:1:69:1 | x | operations.rb:69:6:69:8 | 128 | AssignAddExpr | +| operations.rb:69:1:69:8 | ... = ... | = | operations.rb:69:1:69:1 | x | operations.rb:69:3:69:4 | ... + ... | AssignExpr | +| operations.rb:70:1:70:7 | ... -= ... | -= | operations.rb:70:1:70:1 | y | operations.rb:70:6:70:7 | 32 | AssignSubExpr | +| operations.rb:70:1:70:7 | ... = ... | = | operations.rb:70:1:70:1 | y | operations.rb:70:3:70:4 | ... - ... | AssignExpr | +| operations.rb:71:1:71:7 | ... *= ... | *= | operations.rb:71:1:71:1 | a | operations.rb:71:6:71:7 | 12 | AssignMulExpr | +| operations.rb:71:1:71:7 | ... = ... | = | operations.rb:71:1:71:1 | a | operations.rb:71:3:71:4 | ... * ... | AssignExpr | +| operations.rb:72:1:72:6 | ... /= ... | /= | operations.rb:72:1:72:1 | b | operations.rb:72:6:72:6 | 4 | AssignDivExpr | +| operations.rb:72:1:72:6 | ... = ... | = | operations.rb:72:1:72:1 | b | operations.rb:72:3:72:4 | ... / ... | AssignExpr | +| operations.rb:73:1:73:6 | ... %= ... | %= | operations.rb:73:1:73:1 | z | operations.rb:73:6:73:6 | 2 | AssignModuloExpr | +| operations.rb:73:1:73:6 | ... = ... | = | operations.rb:73:1:73:1 | z | operations.rb:73:3:73:4 | ... % ... | AssignExpr | +| operations.rb:74:1:74:11 | ... **= ... | **= | operations.rb:74:1:74:3 | foo | operations.rb:74:9:74:11 | bar | AssignExponentExpr | +| operations.rb:74:1:74:11 | ... = ... | = | operations.rb:74:1:74:3 | foo | operations.rb:74:5:74:7 | ... ** ... | AssignExpr | +| operations.rb:77:2:77:8 | ... &&= ... | &&= | operations.rb:77:2:77:2 | x | operations.rb:77:8:77:8 | y | AssignLogicalAndExpr | +| operations.rb:77:2:77:8 | ... = ... | = | operations.rb:77:2:77:2 | x | operations.rb:77:4:77:6 | ... && ... | AssignExpr | +| operations.rb:78:2:78:8 | ... = ... | = | operations.rb:78:2:78:2 | a | operations.rb:78:4:78:6 | ... \|\| ... | AssignExpr | +| operations.rb:78:2:78:8 | ... \|\|= ... | \|\|= | operations.rb:78:2:78:2 | a | operations.rb:78:8:78:8 | b | AssignLogicalOrExpr | +| operations.rb:81:2:81:8 | ... <<= ... | <<= | operations.rb:81:2:81:2 | x | operations.rb:81:8:81:8 | 2 | AssignLShiftExpr | +| operations.rb:81:2:81:8 | ... = ... | = | operations.rb:81:2:81:2 | x | operations.rb:81:4:81:6 | ... << ... | AssignExpr | +| operations.rb:82:2:82:8 | ... = ... | = | operations.rb:82:2:82:2 | y | operations.rb:82:4:82:6 | ... >> ... | AssignExpr | +| operations.rb:82:2:82:8 | ... >>= ... | >>= | operations.rb:82:2:82:2 | y | operations.rb:82:8:82:8 | 3 | AssignRShiftExpr | +| operations.rb:83:2:83:12 | ... &= ... | &= | operations.rb:83:2:83:4 | foo | operations.rb:83:9:83:12 | mask | AssignBitwiseAndExpr | +| operations.rb:83:2:83:12 | ... = ... | = | operations.rb:83:2:83:4 | foo | operations.rb:83:6:83:7 | ... & ... | AssignExpr | +| operations.rb:84:2:84:12 | ... = ... | = | operations.rb:84:2:84:4 | bar | operations.rb:84:6:84:7 | ... \| ... | AssignExpr | +| operations.rb:84:2:84:12 | ... \|= ... | \|= | operations.rb:84:2:84:4 | bar | operations.rb:84:9:84:12 | 0x01 | AssignBitwiseOrExpr | +| operations.rb:85:2:85:11 | ... = ... | = | operations.rb:85:2:85:4 | baz | operations.rb:85:6:85:7 | ... ^ ... | AssignExpr | +| operations.rb:85:2:85:11 | ... ^= ... | ^= | operations.rb:85:2:85:4 | baz | operations.rb:85:9:85:11 | qux | AssignBitwiseXorExpr | +| operations.rb:88:3:88:8 | ... = ... | = | operations.rb:88:3:88:4 | @x | operations.rb:88:8:88:8 | 1 | AssignExpr | +| operations.rb:89:3:89:9 | ... += ... | += | operations.rb:89:3:89:4 | @x | operations.rb:89:9:89:9 | 2 | AssignAddExpr | +| operations.rb:89:3:89:9 | ... = ... | = | operations.rb:89:3:89:4 | @x | operations.rb:89:6:89:7 | ... + ... | AssignExpr | +| operations.rb:91:3:91:9 | ... = ... | = | operations.rb:91:3:91:5 | @@y | operations.rb:91:9:91:9 | 3 | AssignExpr | +| operations.rb:92:3:92:10 | ... /= ... | /= | operations.rb:92:3:92:5 | @@y | operations.rb:92:10:92:10 | 4 | AssignDivExpr | +| operations.rb:92:3:92:10 | ... = ... | = | operations.rb:92:3:92:5 | @@y | operations.rb:92:7:92:8 | ... / ... | AssignExpr | +| operations.rb:95:1:95:15 | ... = ... | = | operations.rb:95:1:95:11 | $global_var | operations.rb:95:15:95:15 | 5 | AssignExpr | +| operations.rb:96:1:96:16 | ... *= ... | *= | operations.rb:96:1:96:11 | $global_var | operations.rb:96:16:96:16 | 6 | AssignMulExpr | +| operations.rb:96:1:96:16 | ... = ... | = | operations.rb:96:1:96:11 | $global_var | operations.rb:96:13:96:14 | ... * ... | AssignExpr | +assignOperations +| operations.rb:69:1:69:8 | ... += ... | += | operations.rb:69:1:69:1 | x | operations.rb:69:6:69:8 | 128 | AssignAddExpr | +| operations.rb:70:1:70:7 | ... -= ... | -= | operations.rb:70:1:70:1 | y | operations.rb:70:6:70:7 | 32 | AssignSubExpr | +| operations.rb:71:1:71:7 | ... *= ... | *= | operations.rb:71:1:71:1 | a | operations.rb:71:6:71:7 | 12 | AssignMulExpr | +| operations.rb:72:1:72:6 | ... /= ... | /= | operations.rb:72:1:72:1 | b | operations.rb:72:6:72:6 | 4 | AssignDivExpr | +| operations.rb:73:1:73:6 | ... %= ... | %= | operations.rb:73:1:73:1 | z | operations.rb:73:6:73:6 | 2 | AssignModuloExpr | +| operations.rb:74:1:74:11 | ... **= ... | **= | operations.rb:74:1:74:3 | foo | operations.rb:74:9:74:11 | bar | AssignExponentExpr | +| operations.rb:77:2:77:8 | ... &&= ... | &&= | operations.rb:77:2:77:2 | x | operations.rb:77:8:77:8 | y | AssignLogicalAndExpr | +| operations.rb:78:2:78:8 | ... \|\|= ... | \|\|= | operations.rb:78:2:78:2 | a | operations.rb:78:8:78:8 | b | AssignLogicalOrExpr | +| operations.rb:81:2:81:8 | ... <<= ... | <<= | operations.rb:81:2:81:2 | x | operations.rb:81:8:81:8 | 2 | AssignLShiftExpr | +| operations.rb:82:2:82:8 | ... >>= ... | >>= | operations.rb:82:2:82:2 | y | operations.rb:82:8:82:8 | 3 | AssignRShiftExpr | +| operations.rb:83:2:83:12 | ... &= ... | &= | operations.rb:83:2:83:4 | foo | operations.rb:83:9:83:12 | mask | AssignBitwiseAndExpr | +| operations.rb:84:2:84:12 | ... \|= ... | \|= | operations.rb:84:2:84:4 | bar | operations.rb:84:9:84:12 | 0x01 | AssignBitwiseOrExpr | +| operations.rb:85:2:85:11 | ... ^= ... | ^= | operations.rb:85:2:85:4 | baz | operations.rb:85:9:85:11 | qux | AssignBitwiseXorExpr | +| operations.rb:89:3:89:9 | ... += ... | += | operations.rb:89:3:89:4 | @x | operations.rb:89:9:89:9 | 2 | AssignAddExpr | +| operations.rb:92:3:92:10 | ... /= ... | /= | operations.rb:92:3:92:5 | @@y | operations.rb:92:10:92:10 | 4 | AssignDivExpr | +| operations.rb:96:1:96:16 | ... *= ... | *= | operations.rb:96:1:96:11 | $global_var | operations.rb:96:16:96:16 | 6 | AssignMulExpr | +assignArithmeticOperations +| operations.rb:69:1:69:8 | ... += ... | += | operations.rb:69:1:69:1 | x | operations.rb:69:6:69:8 | 128 | AssignAddExpr | +| operations.rb:70:1:70:7 | ... -= ... | -= | operations.rb:70:1:70:1 | y | operations.rb:70:6:70:7 | 32 | AssignSubExpr | +| operations.rb:71:1:71:7 | ... *= ... | *= | operations.rb:71:1:71:1 | a | operations.rb:71:6:71:7 | 12 | AssignMulExpr | +| operations.rb:72:1:72:6 | ... /= ... | /= | operations.rb:72:1:72:1 | b | operations.rb:72:6:72:6 | 4 | AssignDivExpr | +| operations.rb:73:1:73:6 | ... %= ... | %= | operations.rb:73:1:73:1 | z | operations.rb:73:6:73:6 | 2 | AssignModuloExpr | +| operations.rb:74:1:74:11 | ... **= ... | **= | operations.rb:74:1:74:3 | foo | operations.rb:74:9:74:11 | bar | AssignExponentExpr | +| operations.rb:89:3:89:9 | ... += ... | += | operations.rb:89:3:89:4 | @x | operations.rb:89:9:89:9 | 2 | AssignAddExpr | +| operations.rb:92:3:92:10 | ... /= ... | /= | operations.rb:92:3:92:5 | @@y | operations.rb:92:10:92:10 | 4 | AssignDivExpr | +| operations.rb:96:1:96:16 | ... *= ... | *= | operations.rb:96:1:96:11 | $global_var | operations.rb:96:16:96:16 | 6 | AssignMulExpr | +assignLogicalOperations +| operations.rb:77:2:77:8 | ... &&= ... | &&= | operations.rb:77:2:77:2 | x | operations.rb:77:8:77:8 | y | AssignLogicalAndExpr | +| operations.rb:78:2:78:8 | ... \|\|= ... | \|\|= | operations.rb:78:2:78:2 | a | operations.rb:78:8:78:8 | b | AssignLogicalOrExpr | +assignBitwiseOperations +| operations.rb:81:2:81:8 | ... <<= ... | <<= | operations.rb:81:2:81:2 | x | operations.rb:81:8:81:8 | 2 | AssignLShiftExpr | +| operations.rb:82:2:82:8 | ... >>= ... | >>= | operations.rb:82:2:82:2 | y | operations.rb:82:8:82:8 | 3 | AssignRShiftExpr | +| operations.rb:83:2:83:12 | ... &= ... | &= | operations.rb:83:2:83:4 | foo | operations.rb:83:9:83:12 | mask | AssignBitwiseAndExpr | +| operations.rb:84:2:84:12 | ... \|= ... | \|= | operations.rb:84:2:84:4 | bar | operations.rb:84:9:84:12 | 0x01 | AssignBitwiseOrExpr | +| operations.rb:85:2:85:11 | ... ^= ... | ^= | operations.rb:85:2:85:4 | baz | operations.rb:85:9:85:11 | qux | AssignBitwiseXorExpr | diff --git a/ruby/ql/test/library-tests/ast/operations/assignment.ql b/ruby/ql/test/library-tests/ast/operations/assignment.ql new file mode 100644 index 000000000000..3ff3b3f76843 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/operations/assignment.ql @@ -0,0 +1,35 @@ +import ruby + +query predicate assignments(Assignment a, string operator, Expr left, Expr right, string pClass) { + operator = a.getOperator() and + left = a.getLeftOperand() and + right = a.getRightOperand() and + pClass = a.getAPrimaryQlClass() +} + +query predicate assignOperations( + AssignOperation o, string operator, Expr left, Expr right, string pClass +) { + operator = o.getOperator() and + left = o.getLeftOperand() and + right = o.getRightOperand() and + pClass = o.getAPrimaryQlClass() +} + +query predicate assignArithmeticOperations( + AssignArithmeticOperation o, string operator, Expr left, Expr right, string pClass +) { + assignOperations(o, operator, left, right, pClass) +} + +query predicate assignLogicalOperations( + AssignLogicalOperation o, string operator, Expr left, Expr right, string pClass +) { + assignOperations(o, operator, left, right, pClass) +} + +query predicate assignBitwiseOperations( + AssignBitwiseOperation o, string operator, Expr left, Expr right, string pClass +) { + assignOperations(o, operator, left, right, pClass) +} diff --git a/ruby/ql/test/library-tests/ast/operations/binary.expected b/ruby/ql/test/library-tests/ast/operations/binary.expected new file mode 100644 index 000000000000..236b0c3b5745 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/operations/binary.expected @@ -0,0 +1,99 @@ +binaryOperations +| operations.rb:32:1:32:7 | ... + ... | + | operations.rb:32:1:32:1 | w | operations.rb:32:5:32:7 | 234 | AddExpr | +| operations.rb:33:1:33:6 | ... - ... | - | operations.rb:33:1:33:1 | x | operations.rb:33:5:33:6 | 17 | SubExpr | +| operations.rb:34:1:34:6 | ... * ... | * | operations.rb:34:1:34:1 | y | operations.rb:34:5:34:6 | 10 | MulExpr | +| operations.rb:35:1:35:5 | ... / ... | / | operations.rb:35:1:35:1 | z | operations.rb:35:5:35:5 | 2 | DivExpr | +| operations.rb:36:1:36:7 | ... % ... | % | operations.rb:36:1:36:3 | num | operations.rb:36:7:36:7 | 2 | ModuloExpr | +| operations.rb:37:1:37:13 | ... ** ... | ** | operations.rb:37:1:37:4 | base | operations.rb:37:9:37:13 | power | ExponentExpr | +| operations.rb:40:1:40:10 | ... && ... | && | operations.rb:40:1:40:3 | foo | operations.rb:40:8:40:10 | bar | LogicalAndExpr | +| operations.rb:41:1:41:11 | ... and ... | and | operations.rb:41:1:41:3 | baz | operations.rb:41:9:41:11 | qux | LogicalAndExpr | +| operations.rb:42:1:42:6 | ... or ... | or | operations.rb:42:1:42:1 | a | operations.rb:42:6:42:6 | b | LogicalOrExpr | +| operations.rb:43:1:43:6 | ... \|\| ... | \|\| | operations.rb:43:1:43:1 | x | operations.rb:43:6:43:6 | y | LogicalOrExpr | +| operations.rb:46:1:46:6 | ... << ... | << | operations.rb:46:1:46:1 | x | operations.rb:46:6:46:6 | 3 | LShiftExpr | +| operations.rb:47:1:47:7 | ... >> ... | >> | operations.rb:47:1:47:1 | y | operations.rb:47:6:47:7 | 16 | RShiftExpr | +| operations.rb:48:1:48:10 | ... & ... | & | operations.rb:48:1:48:3 | foo | operations.rb:48:7:48:10 | 0xff | BitwiseAndExpr | +| operations.rb:49:1:49:10 | ... \| ... | \| | operations.rb:49:1:49:3 | bar | operations.rb:49:7:49:10 | 0x02 | BitwiseOrExpr | +| operations.rb:50:1:50:9 | ... ^ ... | ^ | operations.rb:50:1:50:3 | baz | operations.rb:50:7:50:9 | qux | BitwiseXorExpr | +| operations.rb:53:1:53:6 | ... == ... | == | operations.rb:53:1:53:1 | x | operations.rb:53:6:53:6 | y | EqExpr | +| operations.rb:54:1:54:8 | ... != ... | != | operations.rb:54:1:54:1 | a | operations.rb:54:6:54:8 | 123 | NEExpr | +| operations.rb:55:1:55:7 | ... === ... | === | operations.rb:55:1:55:1 | m | operations.rb:55:7:55:7 | n | CaseEqExpr | +| operations.rb:58:1:58:5 | ... > ... | > | operations.rb:58:1:58:1 | x | operations.rb:58:5:58:5 | 0 | GTExpr | +| operations.rb:59:1:59:8 | ... >= ... | >= | operations.rb:59:1:59:1 | y | operations.rb:59:6:59:8 | 100 | GEExpr | +| operations.rb:60:1:60:5 | ... < ... | < | operations.rb:60:1:60:1 | a | operations.rb:60:5:60:5 | b | LTExpr | +| operations.rb:61:1:61:8 | ... <= ... | <= | operations.rb:61:1:61:1 | 7 | operations.rb:61:6:61:8 | foo | LEExpr | +| operations.rb:64:1:64:7 | ... <=> ... | <=> | operations.rb:64:1:64:1 | a | operations.rb:64:7:64:7 | b | SpaceshipExpr | +| operations.rb:65:1:65:15 | ... =~ ... | =~ | operations.rb:65:1:65:4 | name | operations.rb:65:9:65:15 | /foo.*/ | RegExpMatchExpr | +| operations.rb:66:1:66:17 | ... !~ ... | !~ | operations.rb:66:1:66:6 | handle | operations.rb:66:11:66:17 | /.*bar/ | NoRegExpMatchExpr | +| operations.rb:69:3:69:4 | ... + ... | + | operations.rb:69:1:69:1 | x | operations.rb:69:6:69:8 | 128 | AddExpr | +| operations.rb:70:3:70:4 | ... - ... | - | operations.rb:70:1:70:1 | y | operations.rb:70:6:70:7 | 32 | SubExpr | +| operations.rb:71:3:71:4 | ... * ... | * | operations.rb:71:1:71:1 | a | operations.rb:71:6:71:7 | 12 | MulExpr | +| operations.rb:72:3:72:4 | ... / ... | / | operations.rb:72:1:72:1 | b | operations.rb:72:6:72:6 | 4 | DivExpr | +| operations.rb:73:3:73:4 | ... % ... | % | operations.rb:73:1:73:1 | z | operations.rb:73:6:73:6 | 2 | ModuloExpr | +| operations.rb:74:5:74:7 | ... ** ... | ** | operations.rb:74:1:74:3 | foo | operations.rb:74:9:74:11 | bar | ExponentExpr | +| operations.rb:77:4:77:6 | ... && ... | && | operations.rb:77:2:77:2 | x | operations.rb:77:8:77:8 | y | LogicalAndExpr | +| operations.rb:78:4:78:6 | ... \|\| ... | \|\| | operations.rb:78:2:78:2 | a | operations.rb:78:8:78:8 | b | LogicalOrExpr | +| operations.rb:81:4:81:6 | ... << ... | << | operations.rb:81:2:81:2 | x | operations.rb:81:8:81:8 | 2 | LShiftExpr | +| operations.rb:82:4:82:6 | ... >> ... | >> | operations.rb:82:2:82:2 | y | operations.rb:82:8:82:8 | 3 | RShiftExpr | +| operations.rb:83:6:83:7 | ... & ... | & | operations.rb:83:2:83:4 | foo | operations.rb:83:9:83:12 | mask | BitwiseAndExpr | +| operations.rb:84:6:84:7 | ... \| ... | \| | operations.rb:84:2:84:4 | bar | operations.rb:84:9:84:12 | 0x01 | BitwiseOrExpr | +| operations.rb:85:6:85:7 | ... ^ ... | ^ | operations.rb:85:2:85:4 | baz | operations.rb:85:9:85:11 | qux | BitwiseXorExpr | +| operations.rb:89:6:89:7 | ... + ... | + | operations.rb:89:3:89:4 | @x | operations.rb:89:9:89:9 | 2 | AddExpr | +| operations.rb:92:7:92:8 | ... / ... | / | operations.rb:92:3:92:5 | @@y | operations.rb:92:10:92:10 | 4 | DivExpr | +| operations.rb:96:13:96:14 | ... * ... | * | operations.rb:96:1:96:11 | $global_var | operations.rb:96:16:96:16 | 6 | MulExpr | +binaryArithmeticOperations +| operations.rb:32:1:32:7 | ... + ... | + | operations.rb:32:1:32:1 | w | operations.rb:32:5:32:7 | 234 | AddExpr | +| operations.rb:33:1:33:6 | ... - ... | - | operations.rb:33:1:33:1 | x | operations.rb:33:5:33:6 | 17 | SubExpr | +| operations.rb:34:1:34:6 | ... * ... | * | operations.rb:34:1:34:1 | y | operations.rb:34:5:34:6 | 10 | MulExpr | +| operations.rb:35:1:35:5 | ... / ... | / | operations.rb:35:1:35:1 | z | operations.rb:35:5:35:5 | 2 | DivExpr | +| operations.rb:36:1:36:7 | ... % ... | % | operations.rb:36:1:36:3 | num | operations.rb:36:7:36:7 | 2 | ModuloExpr | +| operations.rb:37:1:37:13 | ... ** ... | ** | operations.rb:37:1:37:4 | base | operations.rb:37:9:37:13 | power | ExponentExpr | +| operations.rb:69:3:69:4 | ... + ... | + | operations.rb:69:1:69:1 | x | operations.rb:69:6:69:8 | 128 | AddExpr | +| operations.rb:70:3:70:4 | ... - ... | - | operations.rb:70:1:70:1 | y | operations.rb:70:6:70:7 | 32 | SubExpr | +| operations.rb:71:3:71:4 | ... * ... | * | operations.rb:71:1:71:1 | a | operations.rb:71:6:71:7 | 12 | MulExpr | +| operations.rb:72:3:72:4 | ... / ... | / | operations.rb:72:1:72:1 | b | operations.rb:72:6:72:6 | 4 | DivExpr | +| operations.rb:73:3:73:4 | ... % ... | % | operations.rb:73:1:73:1 | z | operations.rb:73:6:73:6 | 2 | ModuloExpr | +| operations.rb:74:5:74:7 | ... ** ... | ** | operations.rb:74:1:74:3 | foo | operations.rb:74:9:74:11 | bar | ExponentExpr | +| operations.rb:89:6:89:7 | ... + ... | + | operations.rb:89:3:89:4 | @x | operations.rb:89:9:89:9 | 2 | AddExpr | +| operations.rb:92:7:92:8 | ... / ... | / | operations.rb:92:3:92:5 | @@y | operations.rb:92:10:92:10 | 4 | DivExpr | +| operations.rb:96:13:96:14 | ... * ... | * | operations.rb:96:1:96:11 | $global_var | operations.rb:96:16:96:16 | 6 | MulExpr | +binaryLogicalOperations +| operations.rb:40:1:40:10 | ... && ... | && | operations.rb:40:1:40:3 | foo | operations.rb:40:8:40:10 | bar | LogicalAndExpr | +| operations.rb:41:1:41:11 | ... and ... | and | operations.rb:41:1:41:3 | baz | operations.rb:41:9:41:11 | qux | LogicalAndExpr | +| operations.rb:42:1:42:6 | ... or ... | or | operations.rb:42:1:42:1 | a | operations.rb:42:6:42:6 | b | LogicalOrExpr | +| operations.rb:43:1:43:6 | ... \|\| ... | \|\| | operations.rb:43:1:43:1 | x | operations.rb:43:6:43:6 | y | LogicalOrExpr | +| operations.rb:77:4:77:6 | ... && ... | && | operations.rb:77:2:77:2 | x | operations.rb:77:8:77:8 | y | LogicalAndExpr | +| operations.rb:78:4:78:6 | ... \|\| ... | \|\| | operations.rb:78:2:78:2 | a | operations.rb:78:8:78:8 | b | LogicalOrExpr | +binaryBitwiseOperations +| operations.rb:46:1:46:6 | ... << ... | << | operations.rb:46:1:46:1 | x | operations.rb:46:6:46:6 | 3 | LShiftExpr | +| operations.rb:47:1:47:7 | ... >> ... | >> | operations.rb:47:1:47:1 | y | operations.rb:47:6:47:7 | 16 | RShiftExpr | +| operations.rb:48:1:48:10 | ... & ... | & | operations.rb:48:1:48:3 | foo | operations.rb:48:7:48:10 | 0xff | BitwiseAndExpr | +| operations.rb:49:1:49:10 | ... \| ... | \| | operations.rb:49:1:49:3 | bar | operations.rb:49:7:49:10 | 0x02 | BitwiseOrExpr | +| operations.rb:50:1:50:9 | ... ^ ... | ^ | operations.rb:50:1:50:3 | baz | operations.rb:50:7:50:9 | qux | BitwiseXorExpr | +| operations.rb:81:4:81:6 | ... << ... | << | operations.rb:81:2:81:2 | x | operations.rb:81:8:81:8 | 2 | LShiftExpr | +| operations.rb:82:4:82:6 | ... >> ... | >> | operations.rb:82:2:82:2 | y | operations.rb:82:8:82:8 | 3 | RShiftExpr | +| operations.rb:83:6:83:7 | ... & ... | & | operations.rb:83:2:83:4 | foo | operations.rb:83:9:83:12 | mask | BitwiseAndExpr | +| operations.rb:84:6:84:7 | ... \| ... | \| | operations.rb:84:2:84:4 | bar | operations.rb:84:9:84:12 | 0x01 | BitwiseOrExpr | +| operations.rb:85:6:85:7 | ... ^ ... | ^ | operations.rb:85:2:85:4 | baz | operations.rb:85:9:85:11 | qux | BitwiseXorExpr | +comparisonOperations +| operations.rb:53:1:53:6 | ... == ... | == | operations.rb:53:1:53:1 | x | operations.rb:53:6:53:6 | y | EqExpr | +| operations.rb:54:1:54:8 | ... != ... | != | operations.rb:54:1:54:1 | a | operations.rb:54:6:54:8 | 123 | NEExpr | +| operations.rb:55:1:55:7 | ... === ... | === | operations.rb:55:1:55:1 | m | operations.rb:55:7:55:7 | n | CaseEqExpr | +| operations.rb:58:1:58:5 | ... > ... | > | operations.rb:58:1:58:1 | x | operations.rb:58:5:58:5 | 0 | GTExpr | +| operations.rb:59:1:59:8 | ... >= ... | >= | operations.rb:59:1:59:1 | y | operations.rb:59:6:59:8 | 100 | GEExpr | +| operations.rb:60:1:60:5 | ... < ... | < | operations.rb:60:1:60:1 | a | operations.rb:60:5:60:5 | b | LTExpr | +| operations.rb:61:1:61:8 | ... <= ... | <= | operations.rb:61:1:61:1 | 7 | operations.rb:61:6:61:8 | foo | LEExpr | +equalityOperations +| operations.rb:53:1:53:6 | ... == ... | == | operations.rb:53:1:53:1 | x | operations.rb:53:6:53:6 | y | EqExpr | +| operations.rb:54:1:54:8 | ... != ... | != | operations.rb:54:1:54:1 | a | operations.rb:54:6:54:8 | 123 | NEExpr | +| operations.rb:55:1:55:7 | ... === ... | === | operations.rb:55:1:55:1 | m | operations.rb:55:7:55:7 | n | CaseEqExpr | +relationalOperations +| operations.rb:58:1:58:5 | ... > ... | > | operations.rb:58:5:58:5 | 0 | operations.rb:58:1:58:1 | x | GTExpr | +| operations.rb:59:1:59:8 | ... >= ... | >= | operations.rb:59:6:59:8 | 100 | operations.rb:59:1:59:1 | y | GEExpr | +| operations.rb:60:1:60:5 | ... < ... | < | operations.rb:60:1:60:1 | a | operations.rb:60:5:60:5 | b | LTExpr | +| operations.rb:61:1:61:8 | ... <= ... | <= | operations.rb:61:1:61:1 | 7 | operations.rb:61:6:61:8 | foo | LEExpr | +spaceshipExprs +| operations.rb:64:1:64:7 | ... <=> ... | <=> | operations.rb:64:1:64:1 | a | operations.rb:64:7:64:7 | b | SpaceshipExpr | +regExpMatchExprs +| operations.rb:65:1:65:15 | ... =~ ... | =~ | operations.rb:65:1:65:4 | name | operations.rb:65:9:65:15 | /foo.*/ | RegExpMatchExpr | +noRegExpMatchExprs +| operations.rb:66:1:66:17 | ... !~ ... | !~ | operations.rb:66:1:66:6 | handle | operations.rb:66:11:66:17 | /.*bar/ | NoRegExpMatchExpr | diff --git a/ruby/ql/test/library-tests/ast/operations/binary.ql b/ruby/ql/test/library-tests/ast/operations/binary.ql new file mode 100644 index 000000000000..a2900adca956 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/operations/binary.ql @@ -0,0 +1,67 @@ +import ruby + +query predicate binaryOperations( + BinaryOperation o, string operator, Expr left, Expr right, string pClass +) { + operator = o.getOperator() and + left = o.getLeftOperand() and + right = o.getRightOperand() and + pClass = o.getAPrimaryQlClass() +} + +query predicate binaryArithmeticOperations( + BinaryArithmeticOperation o, string operator, Expr left, Expr right, string pClass +) { + binaryOperations(o, operator, left, right, pClass) +} + +query predicate binaryLogicalOperations( + BinaryLogicalOperation o, string operator, Expr left, Expr right, string pClass +) { + binaryOperations(o, operator, left, right, pClass) +} + +query predicate binaryBitwiseOperations( + BinaryBitwiseOperation o, string operator, Expr left, Expr right, string pClass +) { + binaryOperations(o, operator, left, right, pClass) +} + +query predicate comparisonOperations( + ComparisonOperation o, string operator, Expr left, Expr right, string pClass +) { + binaryOperations(o, operator, left, right, pClass) +} + +query predicate equalityOperations( + EqualityOperation o, string operator, Expr left, Expr right, string pClass +) { + binaryOperations(o, operator, left, right, pClass) +} + +query predicate relationalOperations( + RelationalOperation o, string operator, Expr lesser, Expr greater, string pClass +) { + operator = o.getOperator() and + lesser = o.getLesserOperand() and + greater = o.getGreaterOperand() and + pClass = o.getAPrimaryQlClass() +} + +query predicate spaceshipExprs( + SpaceshipExpr e, string operator, Expr left, Expr right, string pClass +) { + binaryOperations(e, operator, left, right, pClass) +} + +query predicate regExpMatchExprs( + RegExpMatchExpr e, string operator, Expr left, Expr right, string pClass +) { + binaryOperations(e, operator, left, right, pClass) +} + +query predicate noRegExpMatchExprs( + NoRegExpMatchExpr e, string operator, Expr left, Expr right, string pClass +) { + binaryOperations(e, operator, left, right, pClass) +} diff --git a/ruby/ql/test/library-tests/ast/operations/operation.expected b/ruby/ql/test/library-tests/ast/operations/operation.expected new file mode 100644 index 000000000000..76dbae694bea --- /dev/null +++ b/ruby/ql/test/library-tests/ast/operations/operation.expected @@ -0,0 +1,196 @@ +| operations.rb:3:1:3:5 | ... = ... | = | operations.rb:3:1:3:1 | a | AssignExpr | +| operations.rb:3:1:3:5 | ... = ... | = | operations.rb:3:5:3:5 | 0 | AssignExpr | +| operations.rb:4:1:4:5 | ... = ... | = | operations.rb:4:1:4:1 | b | AssignExpr | +| operations.rb:4:1:4:5 | ... = ... | = | operations.rb:4:5:4:5 | 0 | AssignExpr | +| operations.rb:5:1:5:7 | ... = ... | = | operations.rb:5:1:5:3 | bar | AssignExpr | +| operations.rb:5:1:5:7 | ... = ... | = | operations.rb:5:7:5:7 | 0 | AssignExpr | +| operations.rb:6:1:6:8 | ... = ... | = | operations.rb:6:1:6:4 | base | AssignExpr | +| operations.rb:6:1:6:8 | ... = ... | = | operations.rb:6:8:6:8 | 0 | AssignExpr | +| operations.rb:7:1:7:7 | ... = ... | = | operations.rb:7:1:7:3 | baz | AssignExpr | +| operations.rb:7:1:7:7 | ... = ... | = | operations.rb:7:7:7:7 | 0 | AssignExpr | +| operations.rb:8:1:8:7 | ... = ... | = | operations.rb:8:1:8:3 | foo | AssignExpr | +| operations.rb:8:1:8:7 | ... = ... | = | operations.rb:8:7:8:7 | 0 | AssignExpr | +| operations.rb:9:1:9:10 | ... = ... | = | operations.rb:9:1:9:6 | handle | AssignExpr | +| operations.rb:9:1:9:10 | ... = ... | = | operations.rb:9:10:9:10 | 0 | AssignExpr | +| operations.rb:10:1:10:5 | ... = ... | = | operations.rb:10:1:10:1 | m | AssignExpr | +| operations.rb:10:1:10:5 | ... = ... | = | operations.rb:10:5:10:5 | 0 | AssignExpr | +| operations.rb:11:1:11:8 | ... = ... | = | operations.rb:11:1:11:4 | mask | AssignExpr | +| operations.rb:11:1:11:8 | ... = ... | = | operations.rb:11:8:11:8 | 0 | AssignExpr | +| operations.rb:12:1:12:5 | ... = ... | = | operations.rb:12:1:12:1 | n | AssignExpr | +| operations.rb:12:1:12:5 | ... = ... | = | operations.rb:12:5:12:5 | 0 | AssignExpr | +| operations.rb:13:1:13:8 | ... = ... | = | operations.rb:13:1:13:4 | name | AssignExpr | +| operations.rb:13:1:13:8 | ... = ... | = | operations.rb:13:8:13:8 | 0 | AssignExpr | +| operations.rb:14:1:14:7 | ... = ... | = | operations.rb:14:1:14:3 | num | AssignExpr | +| operations.rb:14:1:14:7 | ... = ... | = | operations.rb:14:7:14:7 | 0 | AssignExpr | +| operations.rb:15:1:15:9 | ... = ... | = | operations.rb:15:1:15:5 | power | AssignExpr | +| operations.rb:15:1:15:9 | ... = ... | = | operations.rb:15:9:15:9 | 0 | AssignExpr | +| operations.rb:16:1:16:7 | ... = ... | = | operations.rb:16:1:16:3 | qux | AssignExpr | +| operations.rb:16:1:16:7 | ... = ... | = | operations.rb:16:7:16:7 | 0 | AssignExpr | +| operations.rb:17:1:17:5 | ... = ... | = | operations.rb:17:1:17:1 | w | AssignExpr | +| operations.rb:17:1:17:5 | ... = ... | = | operations.rb:17:5:17:5 | 0 | AssignExpr | +| operations.rb:18:1:18:5 | ... = ... | = | operations.rb:18:1:18:1 | x | AssignExpr | +| operations.rb:18:1:18:5 | ... = ... | = | operations.rb:18:5:18:5 | 0 | AssignExpr | +| operations.rb:19:1:19:5 | ... = ... | = | operations.rb:19:1:19:1 | y | AssignExpr | +| operations.rb:19:1:19:5 | ... = ... | = | operations.rb:19:5:19:5 | 0 | AssignExpr | +| operations.rb:20:1:20:5 | ... = ... | = | operations.rb:20:1:20:1 | z | AssignExpr | +| operations.rb:20:1:20:5 | ... = ... | = | operations.rb:20:5:20:5 | 0 | AssignExpr | +| operations.rb:23:1:23:2 | ! ... | ! | operations.rb:23:2:23:2 | a | NotExpr | +| operations.rb:24:1:24:5 | not ... | not | operations.rb:24:5:24:5 | b | NotExpr | +| operations.rb:25:1:25:3 | + ... | + | operations.rb:25:2:25:3 | 14 | UnaryPlusExpr | +| operations.rb:26:1:26:2 | - ... | - | operations.rb:26:2:26:2 | 7 | UnaryMinusExpr | +| operations.rb:27:1:27:2 | ~ ... | ~ | operations.rb:27:2:27:2 | x | ComplementExpr | +| operations.rb:28:1:28:12 | defined? ... | defined? | operations.rb:28:10:28:12 | foo | DefinedExpr | +| operations.rb:29:20:29:23 | * ... | * | operations.rb:29:21:29:23 | [...] | SplatExpr | +| operations.rb:29:31:29:42 | ** ... | ** | operations.rb:29:33:29:42 | {...} | HashSplatExpr | +| operations.rb:32:1:32:7 | ... + ... | + | operations.rb:32:1:32:1 | w | AddExpr | +| operations.rb:32:1:32:7 | ... + ... | + | operations.rb:32:5:32:7 | 234 | AddExpr | +| operations.rb:33:1:33:6 | ... - ... | - | operations.rb:33:1:33:1 | x | SubExpr | +| operations.rb:33:1:33:6 | ... - ... | - | operations.rb:33:5:33:6 | 17 | SubExpr | +| operations.rb:34:1:34:6 | ... * ... | * | operations.rb:34:1:34:1 | y | MulExpr | +| operations.rb:34:1:34:6 | ... * ... | * | operations.rb:34:5:34:6 | 10 | MulExpr | +| operations.rb:35:1:35:5 | ... / ... | / | operations.rb:35:1:35:1 | z | DivExpr | +| operations.rb:35:1:35:5 | ... / ... | / | operations.rb:35:5:35:5 | 2 | DivExpr | +| operations.rb:36:1:36:7 | ... % ... | % | operations.rb:36:1:36:3 | num | ModuloExpr | +| operations.rb:36:1:36:7 | ... % ... | % | operations.rb:36:7:36:7 | 2 | ModuloExpr | +| operations.rb:37:1:37:13 | ... ** ... | ** | operations.rb:37:1:37:4 | base | ExponentExpr | +| operations.rb:37:1:37:13 | ... ** ... | ** | operations.rb:37:9:37:13 | power | ExponentExpr | +| operations.rb:40:1:40:10 | ... && ... | && | operations.rb:40:1:40:3 | foo | LogicalAndExpr | +| operations.rb:40:1:40:10 | ... && ... | && | operations.rb:40:8:40:10 | bar | LogicalAndExpr | +| operations.rb:41:1:41:11 | ... and ... | and | operations.rb:41:1:41:3 | baz | LogicalAndExpr | +| operations.rb:41:1:41:11 | ... and ... | and | operations.rb:41:9:41:11 | qux | LogicalAndExpr | +| operations.rb:42:1:42:6 | ... or ... | or | operations.rb:42:1:42:1 | a | LogicalOrExpr | +| operations.rb:42:1:42:6 | ... or ... | or | operations.rb:42:6:42:6 | b | LogicalOrExpr | +| operations.rb:43:1:43:6 | ... \|\| ... | \|\| | operations.rb:43:1:43:1 | x | LogicalOrExpr | +| operations.rb:43:1:43:6 | ... \|\| ... | \|\| | operations.rb:43:6:43:6 | y | LogicalOrExpr | +| operations.rb:46:1:46:6 | ... << ... | << | operations.rb:46:1:46:1 | x | LShiftExpr | +| operations.rb:46:1:46:6 | ... << ... | << | operations.rb:46:6:46:6 | 3 | LShiftExpr | +| operations.rb:47:1:47:7 | ... >> ... | >> | operations.rb:47:1:47:1 | y | RShiftExpr | +| operations.rb:47:1:47:7 | ... >> ... | >> | operations.rb:47:6:47:7 | 16 | RShiftExpr | +| operations.rb:48:1:48:10 | ... & ... | & | operations.rb:48:1:48:3 | foo | BitwiseAndExpr | +| operations.rb:48:1:48:10 | ... & ... | & | operations.rb:48:7:48:10 | 0xff | BitwiseAndExpr | +| operations.rb:49:1:49:10 | ... \| ... | \| | operations.rb:49:1:49:3 | bar | BitwiseOrExpr | +| operations.rb:49:1:49:10 | ... \| ... | \| | operations.rb:49:7:49:10 | 0x02 | BitwiseOrExpr | +| operations.rb:50:1:50:9 | ... ^ ... | ^ | operations.rb:50:1:50:3 | baz | BitwiseXorExpr | +| operations.rb:50:1:50:9 | ... ^ ... | ^ | operations.rb:50:7:50:9 | qux | BitwiseXorExpr | +| operations.rb:53:1:53:6 | ... == ... | == | operations.rb:53:1:53:1 | x | EqExpr | +| operations.rb:53:1:53:6 | ... == ... | == | operations.rb:53:6:53:6 | y | EqExpr | +| operations.rb:54:1:54:8 | ... != ... | != | operations.rb:54:1:54:1 | a | NEExpr | +| operations.rb:54:1:54:8 | ... != ... | != | operations.rb:54:6:54:8 | 123 | NEExpr | +| operations.rb:55:1:55:7 | ... === ... | === | operations.rb:55:1:55:1 | m | CaseEqExpr | +| operations.rb:55:1:55:7 | ... === ... | === | operations.rb:55:7:55:7 | n | CaseEqExpr | +| operations.rb:58:1:58:5 | ... > ... | > | operations.rb:58:1:58:1 | x | GTExpr | +| operations.rb:58:1:58:5 | ... > ... | > | operations.rb:58:5:58:5 | 0 | GTExpr | +| operations.rb:59:1:59:8 | ... >= ... | >= | operations.rb:59:1:59:1 | y | GEExpr | +| operations.rb:59:1:59:8 | ... >= ... | >= | operations.rb:59:6:59:8 | 100 | GEExpr | +| operations.rb:60:1:60:5 | ... < ... | < | operations.rb:60:1:60:1 | a | LTExpr | +| operations.rb:60:1:60:5 | ... < ... | < | operations.rb:60:5:60:5 | b | LTExpr | +| operations.rb:61:1:61:8 | ... <= ... | <= | operations.rb:61:1:61:1 | 7 | LEExpr | +| operations.rb:61:1:61:8 | ... <= ... | <= | operations.rb:61:6:61:8 | foo | LEExpr | +| operations.rb:64:1:64:7 | ... <=> ... | <=> | operations.rb:64:1:64:1 | a | SpaceshipExpr | +| operations.rb:64:1:64:7 | ... <=> ... | <=> | operations.rb:64:7:64:7 | b | SpaceshipExpr | +| operations.rb:65:1:65:15 | ... =~ ... | =~ | operations.rb:65:1:65:4 | name | RegExpMatchExpr | +| operations.rb:65:1:65:15 | ... =~ ... | =~ | operations.rb:65:9:65:15 | /foo.*/ | RegExpMatchExpr | +| operations.rb:66:1:66:17 | ... !~ ... | !~ | operations.rb:66:1:66:6 | handle | NoRegExpMatchExpr | +| operations.rb:66:1:66:17 | ... !~ ... | !~ | operations.rb:66:11:66:17 | /.*bar/ | NoRegExpMatchExpr | +| operations.rb:69:1:69:8 | ... += ... | += | operations.rb:69:1:69:1 | x | AssignAddExpr | +| operations.rb:69:1:69:8 | ... += ... | += | operations.rb:69:6:69:8 | 128 | AssignAddExpr | +| operations.rb:69:1:69:8 | ... = ... | = | operations.rb:69:1:69:1 | x | AssignExpr | +| operations.rb:69:1:69:8 | ... = ... | = | operations.rb:69:3:69:4 | ... + ... | AssignExpr | +| operations.rb:69:3:69:4 | ... + ... | + | operations.rb:69:1:69:1 | x | AddExpr | +| operations.rb:69:3:69:4 | ... + ... | + | operations.rb:69:6:69:8 | 128 | AddExpr | +| operations.rb:70:1:70:7 | ... -= ... | -= | operations.rb:70:1:70:1 | y | AssignSubExpr | +| operations.rb:70:1:70:7 | ... -= ... | -= | operations.rb:70:6:70:7 | 32 | AssignSubExpr | +| operations.rb:70:1:70:7 | ... = ... | = | operations.rb:70:1:70:1 | y | AssignExpr | +| operations.rb:70:1:70:7 | ... = ... | = | operations.rb:70:3:70:4 | ... - ... | AssignExpr | +| operations.rb:70:3:70:4 | ... - ... | - | operations.rb:70:1:70:1 | y | SubExpr | +| operations.rb:70:3:70:4 | ... - ... | - | operations.rb:70:6:70:7 | 32 | SubExpr | +| operations.rb:71:1:71:7 | ... *= ... | *= | operations.rb:71:1:71:1 | a | AssignMulExpr | +| operations.rb:71:1:71:7 | ... *= ... | *= | operations.rb:71:6:71:7 | 12 | AssignMulExpr | +| operations.rb:71:1:71:7 | ... = ... | = | operations.rb:71:1:71:1 | a | AssignExpr | +| operations.rb:71:1:71:7 | ... = ... | = | operations.rb:71:3:71:4 | ... * ... | AssignExpr | +| operations.rb:71:3:71:4 | ... * ... | * | operations.rb:71:1:71:1 | a | MulExpr | +| operations.rb:71:3:71:4 | ... * ... | * | operations.rb:71:6:71:7 | 12 | MulExpr | +| operations.rb:72:1:72:6 | ... /= ... | /= | operations.rb:72:1:72:1 | b | AssignDivExpr | +| operations.rb:72:1:72:6 | ... /= ... | /= | operations.rb:72:6:72:6 | 4 | AssignDivExpr | +| operations.rb:72:1:72:6 | ... = ... | = | operations.rb:72:1:72:1 | b | AssignExpr | +| operations.rb:72:1:72:6 | ... = ... | = | operations.rb:72:3:72:4 | ... / ... | AssignExpr | +| operations.rb:72:3:72:4 | ... / ... | / | operations.rb:72:1:72:1 | b | DivExpr | +| operations.rb:72:3:72:4 | ... / ... | / | operations.rb:72:6:72:6 | 4 | DivExpr | +| operations.rb:73:1:73:6 | ... %= ... | %= | operations.rb:73:1:73:1 | z | AssignModuloExpr | +| operations.rb:73:1:73:6 | ... %= ... | %= | operations.rb:73:6:73:6 | 2 | AssignModuloExpr | +| operations.rb:73:1:73:6 | ... = ... | = | operations.rb:73:1:73:1 | z | AssignExpr | +| operations.rb:73:1:73:6 | ... = ... | = | operations.rb:73:3:73:4 | ... % ... | AssignExpr | +| operations.rb:73:3:73:4 | ... % ... | % | operations.rb:73:1:73:1 | z | ModuloExpr | +| operations.rb:73:3:73:4 | ... % ... | % | operations.rb:73:6:73:6 | 2 | ModuloExpr | +| operations.rb:74:1:74:11 | ... **= ... | **= | operations.rb:74:1:74:3 | foo | AssignExponentExpr | +| operations.rb:74:1:74:11 | ... **= ... | **= | operations.rb:74:9:74:11 | bar | AssignExponentExpr | +| operations.rb:74:1:74:11 | ... = ... | = | operations.rb:74:1:74:3 | foo | AssignExpr | +| operations.rb:74:1:74:11 | ... = ... | = | operations.rb:74:5:74:7 | ... ** ... | AssignExpr | +| operations.rb:74:5:74:7 | ... ** ... | ** | operations.rb:74:1:74:3 | foo | ExponentExpr | +| operations.rb:74:5:74:7 | ... ** ... | ** | operations.rb:74:9:74:11 | bar | ExponentExpr | +| operations.rb:77:2:77:8 | ... &&= ... | &&= | operations.rb:77:2:77:2 | x | AssignLogicalAndExpr | +| operations.rb:77:2:77:8 | ... &&= ... | &&= | operations.rb:77:8:77:8 | y | AssignLogicalAndExpr | +| operations.rb:77:2:77:8 | ... = ... | = | operations.rb:77:2:77:2 | x | AssignExpr | +| operations.rb:77:2:77:8 | ... = ... | = | operations.rb:77:4:77:6 | ... && ... | AssignExpr | +| operations.rb:77:4:77:6 | ... && ... | && | operations.rb:77:2:77:2 | x | LogicalAndExpr | +| operations.rb:77:4:77:6 | ... && ... | && | operations.rb:77:8:77:8 | y | LogicalAndExpr | +| operations.rb:78:2:78:8 | ... = ... | = | operations.rb:78:2:78:2 | a | AssignExpr | +| operations.rb:78:2:78:8 | ... = ... | = | operations.rb:78:4:78:6 | ... \|\| ... | AssignExpr | +| operations.rb:78:2:78:8 | ... \|\|= ... | \|\|= | operations.rb:78:2:78:2 | a | AssignLogicalOrExpr | +| operations.rb:78:2:78:8 | ... \|\|= ... | \|\|= | operations.rb:78:8:78:8 | b | AssignLogicalOrExpr | +| operations.rb:78:4:78:6 | ... \|\| ... | \|\| | operations.rb:78:2:78:2 | a | LogicalOrExpr | +| operations.rb:78:4:78:6 | ... \|\| ... | \|\| | operations.rb:78:8:78:8 | b | LogicalOrExpr | +| operations.rb:81:2:81:8 | ... <<= ... | <<= | operations.rb:81:2:81:2 | x | AssignLShiftExpr | +| operations.rb:81:2:81:8 | ... <<= ... | <<= | operations.rb:81:8:81:8 | 2 | AssignLShiftExpr | +| operations.rb:81:2:81:8 | ... = ... | = | operations.rb:81:2:81:2 | x | AssignExpr | +| operations.rb:81:2:81:8 | ... = ... | = | operations.rb:81:4:81:6 | ... << ... | AssignExpr | +| operations.rb:81:4:81:6 | ... << ... | << | operations.rb:81:2:81:2 | x | LShiftExpr | +| operations.rb:81:4:81:6 | ... << ... | << | operations.rb:81:8:81:8 | 2 | LShiftExpr | +| operations.rb:82:2:82:8 | ... = ... | = | operations.rb:82:2:82:2 | y | AssignExpr | +| operations.rb:82:2:82:8 | ... = ... | = | operations.rb:82:4:82:6 | ... >> ... | AssignExpr | +| operations.rb:82:2:82:8 | ... >>= ... | >>= | operations.rb:82:2:82:2 | y | AssignRShiftExpr | +| operations.rb:82:2:82:8 | ... >>= ... | >>= | operations.rb:82:8:82:8 | 3 | AssignRShiftExpr | +| operations.rb:82:4:82:6 | ... >> ... | >> | operations.rb:82:2:82:2 | y | RShiftExpr | +| operations.rb:82:4:82:6 | ... >> ... | >> | operations.rb:82:8:82:8 | 3 | RShiftExpr | +| operations.rb:83:2:83:12 | ... &= ... | &= | operations.rb:83:2:83:4 | foo | AssignBitwiseAndExpr | +| operations.rb:83:2:83:12 | ... &= ... | &= | operations.rb:83:9:83:12 | mask | AssignBitwiseAndExpr | +| operations.rb:83:2:83:12 | ... = ... | = | operations.rb:83:2:83:4 | foo | AssignExpr | +| operations.rb:83:2:83:12 | ... = ... | = | operations.rb:83:6:83:7 | ... & ... | AssignExpr | +| operations.rb:83:6:83:7 | ... & ... | & | operations.rb:83:2:83:4 | foo | BitwiseAndExpr | +| operations.rb:83:6:83:7 | ... & ... | & | operations.rb:83:9:83:12 | mask | BitwiseAndExpr | +| operations.rb:84:2:84:12 | ... = ... | = | operations.rb:84:2:84:4 | bar | AssignExpr | +| operations.rb:84:2:84:12 | ... = ... | = | operations.rb:84:6:84:7 | ... \| ... | AssignExpr | +| operations.rb:84:2:84:12 | ... \|= ... | \|= | operations.rb:84:2:84:4 | bar | AssignBitwiseOrExpr | +| operations.rb:84:2:84:12 | ... \|= ... | \|= | operations.rb:84:9:84:12 | 0x01 | AssignBitwiseOrExpr | +| operations.rb:84:6:84:7 | ... \| ... | \| | operations.rb:84:2:84:4 | bar | BitwiseOrExpr | +| operations.rb:84:6:84:7 | ... \| ... | \| | operations.rb:84:9:84:12 | 0x01 | BitwiseOrExpr | +| operations.rb:85:2:85:11 | ... = ... | = | operations.rb:85:2:85:4 | baz | AssignExpr | +| operations.rb:85:2:85:11 | ... = ... | = | operations.rb:85:6:85:7 | ... ^ ... | AssignExpr | +| operations.rb:85:2:85:11 | ... ^= ... | ^= | operations.rb:85:2:85:4 | baz | AssignBitwiseXorExpr | +| operations.rb:85:2:85:11 | ... ^= ... | ^= | operations.rb:85:9:85:11 | qux | AssignBitwiseXorExpr | +| operations.rb:85:6:85:7 | ... ^ ... | ^ | operations.rb:85:2:85:4 | baz | BitwiseXorExpr | +| operations.rb:85:6:85:7 | ... ^ ... | ^ | operations.rb:85:9:85:11 | qux | BitwiseXorExpr | +| operations.rb:88:3:88:8 | ... = ... | = | operations.rb:88:3:88:4 | @x | AssignExpr | +| operations.rb:88:3:88:8 | ... = ... | = | operations.rb:88:8:88:8 | 1 | AssignExpr | +| operations.rb:89:3:89:9 | ... += ... | += | operations.rb:89:3:89:4 | @x | AssignAddExpr | +| operations.rb:89:3:89:9 | ... += ... | += | operations.rb:89:9:89:9 | 2 | AssignAddExpr | +| operations.rb:89:3:89:9 | ... = ... | = | operations.rb:89:3:89:4 | @x | AssignExpr | +| operations.rb:89:3:89:9 | ... = ... | = | operations.rb:89:6:89:7 | ... + ... | AssignExpr | +| operations.rb:89:6:89:7 | ... + ... | + | operations.rb:89:3:89:4 | @x | AddExpr | +| operations.rb:89:6:89:7 | ... + ... | + | operations.rb:89:9:89:9 | 2 | AddExpr | +| operations.rb:91:3:91:9 | ... = ... | = | operations.rb:91:3:91:5 | @@y | AssignExpr | +| operations.rb:91:3:91:9 | ... = ... | = | operations.rb:91:9:91:9 | 3 | AssignExpr | +| operations.rb:92:3:92:10 | ... /= ... | /= | operations.rb:92:3:92:5 | @@y | AssignDivExpr | +| operations.rb:92:3:92:10 | ... /= ... | /= | operations.rb:92:10:92:10 | 4 | AssignDivExpr | +| operations.rb:92:3:92:10 | ... = ... | = | operations.rb:92:3:92:5 | @@y | AssignExpr | +| operations.rb:92:3:92:10 | ... = ... | = | operations.rb:92:7:92:8 | ... / ... | AssignExpr | +| operations.rb:92:7:92:8 | ... / ... | / | operations.rb:92:3:92:5 | @@y | DivExpr | +| operations.rb:92:7:92:8 | ... / ... | / | operations.rb:92:10:92:10 | 4 | DivExpr | +| operations.rb:95:1:95:15 | ... = ... | = | operations.rb:95:1:95:11 | $global_var | AssignExpr | +| operations.rb:95:1:95:15 | ... = ... | = | operations.rb:95:15:95:15 | 5 | AssignExpr | +| operations.rb:96:1:96:16 | ... *= ... | *= | operations.rb:96:1:96:11 | $global_var | AssignMulExpr | +| operations.rb:96:1:96:16 | ... *= ... | *= | operations.rb:96:16:96:16 | 6 | AssignMulExpr | +| operations.rb:96:1:96:16 | ... = ... | = | operations.rb:96:1:96:11 | $global_var | AssignExpr | +| operations.rb:96:1:96:16 | ... = ... | = | operations.rb:96:13:96:14 | ... * ... | AssignExpr | +| operations.rb:96:13:96:14 | ... * ... | * | operations.rb:96:1:96:11 | $global_var | MulExpr | +| operations.rb:96:13:96:14 | ... * ... | * | operations.rb:96:16:96:16 | 6 | MulExpr | diff --git a/ruby/ql/test/library-tests/ast/operations/operation.ql b/ruby/ql/test/library-tests/ast/operations/operation.ql new file mode 100644 index 000000000000..2a1db9fd3df6 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/operations/operation.ql @@ -0,0 +1,8 @@ +import ruby + +from Operation o, string operator, Expr operand, string pClass +where + operator = o.getOperator() and + operand = o.getAnOperand() and + pClass = o.getAPrimaryQlClass() +select o, operator, operand, pClass diff --git a/ruby/ql/test/library-tests/ast/operations/operations.rb b/ruby/ql/test/library-tests/ast/operations/operations.rb new file mode 100644 index 000000000000..fcbc4551bde3 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/operations/operations.rb @@ -0,0 +1,96 @@ +# Start with assignments to all the identifiers used below, so that they are +# interpreted as variables. +a = 0 +b = 0 +bar = 0 +base = 0 +baz = 0 +foo = 0 +handle = 0 +m = 0 +mask = 0 +n = 0 +name = 0 +num = 0 +power = 0 +qux = 0 +w = 0 +x = 0 +y = 0 +z = 0 + +# Unary operations +!a +not b ++14 +-7 +~x +defined? foo +def foo; return 1, *[2], a:3, **{b:4, c:5} end + +# Binary arithmetic operations +w + 234 +x - 17 +y * 10 +z / 2 +num % 2 +base ** power + +# Binary logical operations +foo && bar +baz and qux +a or b +x || y + +# Binary bitwise operations +x << 3 +y >> 16 +foo & 0xff +bar | 0x02 +baz ^ qux + +# Equality operations +x == y +a != 123 +m === n + +# Relational operations +x > 0 +y >= 100 +a < b +7 <= foo + +# Misc binary operations +a <=> b +name =~ /foo.*/ +handle !~ /.*bar/ + +# Arithmetic assign operations +x += 128 +y -= 32 +a *= 12 +b /= 4 +z %= 2 +foo **= bar + +# Logical assign operations + x &&= y + a ||= b + + # Bitwise assign operations + x <<= 2 + y >>= 3 + foo &= mask + bar |= 0x01 + baz ^= qux + +class X + @x = 1 + @x += 2 + + @@y = 3 + @@y /= 4 +end + +$global_var = 5 +$global_var *= 6 diff --git a/ruby/ql/test/library-tests/ast/operations/unary.expected b/ruby/ql/test/library-tests/ast/operations/unary.expected new file mode 100644 index 000000000000..0190e9bcf567 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/operations/unary.expected @@ -0,0 +1,17 @@ +unaryOperations +| operations.rb:23:1:23:2 | ! ... | ! | operations.rb:23:2:23:2 | a | NotExpr | +| operations.rb:24:1:24:5 | not ... | not | operations.rb:24:5:24:5 | b | NotExpr | +| operations.rb:25:1:25:3 | + ... | + | operations.rb:25:2:25:3 | 14 | UnaryPlusExpr | +| operations.rb:26:1:26:2 | - ... | - | operations.rb:26:2:26:2 | 7 | UnaryMinusExpr | +| operations.rb:27:1:27:2 | ~ ... | ~ | operations.rb:27:2:27:2 | x | ComplementExpr | +| operations.rb:28:1:28:12 | defined? ... | defined? | operations.rb:28:10:28:12 | foo | DefinedExpr | +| operations.rb:29:20:29:23 | * ... | * | operations.rb:29:21:29:23 | [...] | SplatExpr | +| operations.rb:29:31:29:42 | ** ... | ** | operations.rb:29:33:29:42 | {...} | HashSplatExpr | +unaryLogicalOperations +| operations.rb:23:1:23:2 | ! ... | ! | operations.rb:23:2:23:2 | a | NotExpr | +| operations.rb:24:1:24:5 | not ... | not | operations.rb:24:5:24:5 | b | NotExpr | +unaryArithmeticOperations +| operations.rb:25:1:25:3 | + ... | + | operations.rb:25:2:25:3 | 14 | UnaryPlusExpr | +| operations.rb:26:1:26:2 | - ... | - | operations.rb:26:2:26:2 | 7 | UnaryMinusExpr | +unaryBitwiseOperations +| operations.rb:27:1:27:2 | ~ ... | ~ | operations.rb:27:2:27:2 | x | ComplementExpr | diff --git a/ruby/ql/test/library-tests/ast/operations/unary.ql b/ruby/ql/test/library-tests/ast/operations/unary.ql new file mode 100644 index 000000000000..f94e1def2d62 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/operations/unary.ql @@ -0,0 +1,25 @@ +import ruby + +query predicate unaryOperations(UnaryOperation o, string operator, Expr operand, string pClass) { + operator = o.getOperator() and + operand = o.getOperand() and + pClass = o.getAPrimaryQlClass() +} + +query predicate unaryLogicalOperations( + UnaryLogicalOperation o, string operator, Expr operand, string pClass +) { + unaryOperations(o, operator, operand, pClass) +} + +query predicate unaryArithmeticOperations( + UnaryArithmeticOperation o, string operator, Expr operand, string pClass +) { + unaryOperations(o, operator, operand, pClass) +} + +query predicate unaryBitwiseOperations( + UnaryBitwiseOperation o, string operator, Expr operand, string pClass +) { + unaryOperations(o, operator, operand, pClass) +} diff --git a/ruby/ql/test/library-tests/ast/params/params.expected b/ruby/ql/test/library-tests/ast/params/params.expected new file mode 100644 index 000000000000..5ae4fc32b38c --- /dev/null +++ b/ruby/ql/test/library-tests/ast/params/params.expected @@ -0,0 +1,149 @@ +idParams +| params.rb:4:30:4:32 | foo | foo | +| params.rb:4:35:4:37 | bar | bar | +| params.rb:4:40:4:42 | baz | baz | +| params.rb:9:15:9:17 | key | key | +| params.rb:9:20:9:24 | value | value | +| params.rb:14:11:14:13 | foo | foo | +| params.rb:14:16:14:18 | bar | bar | +| params.rb:30:23:30:28 | wibble | wibble | +| params.rb:30:31:30:36 | *splat | splat | +| params.rb:30:39:30:52 | **double_splat | double_splat | +| params.rb:34:16:34:18 | val | val | +| params.rb:34:21:34:26 | *splat | splat | +| params.rb:34:29:34:42 | **double_splat | double_splat | +| params.rb:38:26:38:26 | x | x | +| params.rb:38:29:38:33 | *blah | blah | +| params.rb:38:36:38:43 | **wibble | wibble | +| params.rb:41:32:41:32 | x | x | +| params.rb:41:35:41:37 | foo | foo | +| params.rb:41:41:41:43 | bar | bar | +| params.rb:46:28:46:33 | &block | block | +| params.rb:49:28:49:29 | xx | xx | +| params.rb:49:33:49:34 | yy | yy | +| params.rb:53:34:53:34 | x | x | +| params.rb:53:37:53:37 | y | y | +| params.rb:53:41:53:41 | z | z | +| params.rb:58:33:58:36 | val1 | val1 | +| params.rb:58:39:58:42 | val2 | val2 | +| params.rb:58:49:58:52 | val3 | val3 | +| params.rb:62:29:62:34 | &block | block | +| params.rb:65:29:65:32 | name | name | +| params.rb:65:35:65:37 | age | age | +| params.rb:70:35:70:35 | a | a | +| params.rb:70:38:70:38 | b | b | +| params.rb:70:48:70:48 | c | c | +blockParams +| params.rb:46:28:46:33 | &block | block | +| params.rb:62:29:62:34 | &block | block | +patternParams +| params.rb:17:31:17:39 | (..., ...) | params.rb:17:32:17:32 | a | 0 | +| params.rb:17:31:17:39 | (..., ...) | params.rb:17:35:17:35 | b | 1 | +| params.rb:17:31:17:39 | (..., ...) | params.rb:17:38:17:38 | c | 2 | +| params.rb:22:15:22:20 | (..., ...) | params.rb:22:16:22:16 | a | 0 | +| params.rb:22:15:22:20 | (..., ...) | params.rb:22:19:22:19 | b | 1 | +| params.rb:25:23:25:37 | (..., ...) | params.rb:25:24:25:28 | first | 0 | +| params.rb:25:23:25:37 | (..., ...) | params.rb:25:31:25:36 | second | 1 | +| params.rb:25:40:25:54 | (..., ...) | params.rb:25:41:25:45 | third | 0 | +| params.rb:25:40:25:54 | (..., ...) | params.rb:25:48:25:53 | fourth | 1 | +splatParams +| params.rb:30:31:30:36 | *splat | splat | +| params.rb:34:21:34:26 | *splat | splat | +| params.rb:38:29:38:33 | *blah | blah | +hashSplatParams +| params.rb:30:39:30:52 | **double_splat | double_splat | +| params.rb:34:29:34:42 | **double_splat | double_splat | +| params.rb:38:36:38:43 | **wibble | wibble | +keywordParams +| params.rb:41:35:41:37 | foo | foo | (none) | +| params.rb:41:41:41:43 | bar | bar | 7 | +| params.rb:49:28:49:29 | xx | xx | (none) | +| params.rb:49:33:49:34 | yy | yy | 100 | +| params.rb:53:37:53:37 | y | y | (none) | +| params.rb:53:41:53:41 | z | z | 3 | +optionalParams +| params.rb:58:39:58:42 | val2 | val2 | params.rb:58:46:58:46 | 0 | +| params.rb:58:49:58:52 | val3 | val3 | params.rb:58:56:58:58 | 100 | +| params.rb:65:35:65:37 | age | age | params.rb:65:41:65:42 | 99 | +| params.rb:70:38:70:38 | b | b | params.rb:70:42:70:45 | 1000 | +| params.rb:70:48:70:48 | c | c | params.rb:70:52:70:53 | 20 | +paramsInMethods +| params.rb:4:1:5:3 | identifier_method_params | 0 | params.rb:4:30:4:32 | foo | SimpleParameter | +| params.rb:4:1:5:3 | identifier_method_params | 1 | params.rb:4:35:4:37 | bar | SimpleParameter | +| params.rb:4:1:5:3 | identifier_method_params | 2 | params.rb:4:40:4:42 | baz | SimpleParameter | +| params.rb:17:1:18:3 | destructured_method_param | 0 | params.rb:17:31:17:39 | (..., ...) | TuplePatternParameter | +| params.rb:30:1:31:3 | method_with_splat | 0 | params.rb:30:23:30:28 | wibble | SimpleParameter | +| params.rb:30:1:31:3 | method_with_splat | 1 | params.rb:30:31:30:36 | *splat | SplatParameter | +| params.rb:30:1:31:3 | method_with_splat | 2 | params.rb:30:39:30:52 | **double_splat | HashSplatParameter | +| params.rb:41:1:43:3 | method_with_keyword_params | 0 | params.rb:41:32:41:32 | x | SimpleParameter | +| params.rb:41:1:43:3 | method_with_keyword_params | 1 | params.rb:41:35:41:37 | foo | KeywordParameter | +| params.rb:41:1:43:3 | method_with_keyword_params | 2 | params.rb:41:41:41:43 | bar | KeywordParameter | +| params.rb:46:1:48:3 | use_block_with_keyword | 0 | params.rb:46:28:46:33 | &block | BlockParameter | +| params.rb:58:1:59:3 | method_with_optional_params | 0 | params.rb:58:33:58:36 | val1 | SimpleParameter | +| params.rb:58:1:59:3 | method_with_optional_params | 1 | params.rb:58:39:58:42 | val2 | OptionalParameter | +| params.rb:58:1:59:3 | method_with_optional_params | 2 | params.rb:58:49:58:52 | val3 | OptionalParameter | +| params.rb:62:1:64:3 | use_block_with_optional | 0 | params.rb:62:29:62:34 | &block | BlockParameter | +paramsInBlocks +| params.rb:9:11:11:3 | do ... end | 0 | params.rb:9:15:9:17 | key | SimpleParameter | +| params.rb:9:11:11:3 | do ... end | 1 | params.rb:9:20:9:24 | value | SimpleParameter | +| params.rb:22:12:22:32 | { ... } | 0 | params.rb:22:15:22:20 | (..., ...) | TuplePatternParameter | +| params.rb:34:12:35:3 | do ... end | 0 | params.rb:34:16:34:18 | val | SimpleParameter | +| params.rb:34:12:35:3 | do ... end | 1 | params.rb:34:21:34:26 | *splat | SplatParameter | +| params.rb:34:12:35:3 | do ... end | 2 | params.rb:34:29:34:42 | **double_splat | HashSplatParameter | +| params.rb:49:24:51:3 | do ... end | 0 | params.rb:49:28:49:29 | xx | KeywordParameter | +| params.rb:49:24:51:3 | do ... end | 1 | params.rb:49:33:49:34 | yy | KeywordParameter | +| params.rb:65:25:67:3 | do ... end | 0 | params.rb:65:29:65:32 | name | SimpleParameter | +| params.rb:65:25:67:3 | do ... end | 1 | params.rb:65:35:65:37 | age | OptionalParameter | +paramsInLambdas +| params.rb:14:7:14:33 | -> { ... } | 0 | params.rb:14:11:14:13 | foo | SimpleParameter | +| params.rb:14:7:14:33 | -> { ... } | 1 | params.rb:14:16:14:18 | bar | SimpleParameter | +| params.rb:25:19:27:1 | -> { ... } | 0 | params.rb:25:23:25:37 | (..., ...) | TuplePatternParameter | +| params.rb:25:19:27:1 | -> { ... } | 1 | params.rb:25:40:25:54 | (..., ...) | TuplePatternParameter | +| params.rb:38:22:38:47 | -> { ... } | 0 | params.rb:38:26:38:26 | x | SimpleParameter | +| params.rb:38:22:38:47 | -> { ... } | 1 | params.rb:38:29:38:33 | *blah | SplatParameter | +| params.rb:38:22:38:47 | -> { ... } | 2 | params.rb:38:36:38:43 | **wibble | HashSplatParameter | +| params.rb:53:30:55:1 | -> { ... } | 0 | params.rb:53:34:53:34 | x | SimpleParameter | +| params.rb:53:30:55:1 | -> { ... } | 1 | params.rb:53:37:53:37 | y | KeywordParameter | +| params.rb:53:30:55:1 | -> { ... } | 2 | params.rb:53:41:53:41 | z | KeywordParameter | +| params.rb:70:31:70:64 | -> { ... } | 0 | params.rb:70:35:70:35 | a | SimpleParameter | +| params.rb:70:31:70:64 | -> { ... } | 1 | params.rb:70:38:70:38 | b | OptionalParameter | +| params.rb:70:31:70:64 | -> { ... } | 2 | params.rb:70:48:70:48 | c | OptionalParameter | +params +| params.rb:4:30:4:32 | foo | 0 | SimpleParameter | +| params.rb:4:35:4:37 | bar | 1 | SimpleParameter | +| params.rb:4:40:4:42 | baz | 2 | SimpleParameter | +| params.rb:9:15:9:17 | key | 0 | SimpleParameter | +| params.rb:9:20:9:24 | value | 1 | SimpleParameter | +| params.rb:14:11:14:13 | foo | 0 | SimpleParameter | +| params.rb:14:16:14:18 | bar | 1 | SimpleParameter | +| params.rb:17:31:17:39 | (..., ...) | 0 | TuplePatternParameter | +| params.rb:22:15:22:20 | (..., ...) | 0 | TuplePatternParameter | +| params.rb:25:23:25:37 | (..., ...) | 0 | TuplePatternParameter | +| params.rb:25:40:25:54 | (..., ...) | 1 | TuplePatternParameter | +| params.rb:30:23:30:28 | wibble | 0 | SimpleParameter | +| params.rb:30:31:30:36 | *splat | 1 | SplatParameter | +| params.rb:30:39:30:52 | **double_splat | 2 | HashSplatParameter | +| params.rb:34:16:34:18 | val | 0 | SimpleParameter | +| params.rb:34:21:34:26 | *splat | 1 | SplatParameter | +| params.rb:34:29:34:42 | **double_splat | 2 | HashSplatParameter | +| params.rb:38:26:38:26 | x | 0 | SimpleParameter | +| params.rb:38:29:38:33 | *blah | 1 | SplatParameter | +| params.rb:38:36:38:43 | **wibble | 2 | HashSplatParameter | +| params.rb:41:32:41:32 | x | 0 | SimpleParameter | +| params.rb:41:35:41:37 | foo | 1 | KeywordParameter | +| params.rb:41:41:41:43 | bar | 2 | KeywordParameter | +| params.rb:46:28:46:33 | &block | 0 | BlockParameter | +| params.rb:49:28:49:29 | xx | 0 | KeywordParameter | +| params.rb:49:33:49:34 | yy | 1 | KeywordParameter | +| params.rb:53:34:53:34 | x | 0 | SimpleParameter | +| params.rb:53:37:53:37 | y | 1 | KeywordParameter | +| params.rb:53:41:53:41 | z | 2 | KeywordParameter | +| params.rb:58:33:58:36 | val1 | 0 | SimpleParameter | +| params.rb:58:39:58:42 | val2 | 1 | OptionalParameter | +| params.rb:58:49:58:52 | val3 | 2 | OptionalParameter | +| params.rb:62:29:62:34 | &block | 0 | BlockParameter | +| params.rb:65:29:65:32 | name | 0 | SimpleParameter | +| params.rb:65:35:65:37 | age | 1 | OptionalParameter | +| params.rb:70:35:70:35 | a | 0 | SimpleParameter | +| params.rb:70:38:70:38 | b | 1 | OptionalParameter | +| params.rb:70:48:70:48 | c | 2 | OptionalParameter | diff --git a/ruby/ql/test/library-tests/ast/params/params.ql b/ruby/ql/test/library-tests/ast/params/params.ql new file mode 100644 index 000000000000..eb77ac0e4182 --- /dev/null +++ b/ruby/ql/test/library-tests/ast/params/params.ql @@ -0,0 +1,48 @@ +import ruby + +//////////////////////////////////////////////////////////////////////////////// +// Query predicates for various types of parameter +query predicate idParams(NamedParameter np, string name) { name = np.getName() } + +query predicate blockParams(BlockParameter bp, string name) { name = bp.getName() } + +query predicate patternParams(TuplePatternParameter tpp, Pattern child, int childIndex) { + tpp.getElement(childIndex) = child +} + +query predicate splatParams(SplatParameter sp, string name) { name = sp.getName() } + +query predicate hashSplatParams(HashSplatParameter hsp, string name) { name = hsp.getName() } + +query predicate keywordParams(KeywordParameter kp, string name, string defaultValueStr) { + name = kp.getName() and + if kp.isOptional() + then defaultValueStr = kp.getDefaultValue().toString() + else defaultValueStr = "(none)" +} + +query predicate optionalParams(OptionalParameter op, string name, AstNode defaultValue) { + name = op.getName() and + defaultValue = op.getDefaultValue() +} + +//////////////////////////////////////////////////////////////////////////////// +// Query predicates for various contexts of parameters +query predicate paramsInMethods(Method m, int i, Parameter p, string pClass) { + p = m.getParameter(i) and pClass = p.getAPrimaryQlClass() +} + +query predicate paramsInBlocks(Block b, int i, Parameter p, string pClass) { + p = b.getParameter(i) and pClass = p.getAPrimaryQlClass() +} + +query predicate paramsInLambdas(Lambda l, int i, Parameter p, string pClass) { + p = l.getParameter(i) and pClass = p.getAPrimaryQlClass() +} + +//////////////////////////////////////////////////////////////////////////////// +// General query selecting all parameters +query predicate params(Parameter p, int i, string pClass) { + i = p.getPosition() and + pClass = p.getAPrimaryQlClass() +} diff --git a/ruby/ql/test/library-tests/ast/params/params.rb b/ruby/ql/test/library-tests/ast/params/params.rb new file mode 100644 index 000000000000..5946b265c84c --- /dev/null +++ b/ruby/ql/test/library-tests/ast/params/params.rb @@ -0,0 +1,70 @@ +# Tests for the different kinds and contexts of parameters. + +# Method containing identifier parameters +def identifier_method_params(foo, bar, baz) +end + +# Block containing identifier parameters +hash = {} +hash.each do |key, value| + puts "#{key} -> #{value}" +end + +# Lambda containing identifier parameters +sum = -> (foo, bar) { foo + bar } + +# Method containing destructured parameters +def destructured_method_param((a, b, c)) +end + +# Block containing destructured parameters +array = [] +array.each { |(a, b)| puts a+b } + +# Lambda containing destructured parameters +sum_four_values = -> ((first, second), (third, fourth)) { + first + second + third + fourth +} + +# Method containing splat and hash-splat params +def method_with_splat(wibble, *splat, **double_splat) +end + +# Block with splat and hash-splat parameter +array.each do |val, *splat, **double_splat| +end + +# Lambda with splat and hash-splat +lambda_with_splats = -> (x, *blah, **wibble) {} + +# Method containing keyword parameters +def method_with_keyword_params(x, foo:, bar: 7) + x + foo + bar +end + +# Block with keyword parameters +def use_block_with_keyword(&block) + puts(block.call bar: 2, foo: 3) +end +use_block_with_keyword do |xx:, yy: 100| + xx + yy +end + +lambda_with_keyword_params = -> (x, y:, z: 3) { + x + y + z +} + +# Method containing optional parameters +def method_with_optional_params(val1, val2 = 0, val3 = 100) +end + +# Block containing optional parameter +def use_block_with_optional(&block) + block.call 'Zeus' +end +use_block_with_optional do |name, age = 99| + puts "#{name} is #{age} years old" +end + +# Lambda containing optional parameters +lambda_with_optional_params = -> (a, b = 1000, c = 20) { a+b+c } \ No newline at end of file diff --git a/ruby/ql/test/library-tests/controlflow/graph/Cfg.expected b/ruby/ql/test/library-tests/controlflow/graph/Cfg.expected new file mode 100644 index 000000000000..3897a300f4e0 --- /dev/null +++ b/ruby/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -0,0 +1,5160 @@ +break_ensure.rb: +# 1| enter m1 +#-----| -> elements + +# 1| enter break_ensure.rb +#-----| -> m1 + +# 1| m1 +#-----| -> m2 + +# 1| exit m1 + +# 1| exit break_ensure.rb + +# 1| exit m1 (normal) +#-----| -> exit m1 + +# 1| exit break_ensure.rb (normal) +#-----| -> exit break_ensure.rb + +# 1| elements +#-----| -> elements + +# 2| for ... in ... +#-----| -> elements + +# 2| element +#-----| -> element + +# 2| In +#-----| empty -> for ... in ... +#-----| non-empty -> element + +# 2| elements +#-----| -> In + +# 2| do ... +#-----| -> In + +# 3| if ... +#-----| -> do ... + +# 3| ... > ... +#-----| raise -> for ... in ... +#-----| true -> break +#-----| false -> if ... + +# 3| element +#-----| -> 0 + +# 3| 0 +#-----| -> ... > ... + +# 4| break +#-----| break -> for ... in ... + +# 7| ensure ... +#-----| -> exit m1 (normal) + +# 8| if ... +#-----| -> ensure ... + +# 8| call to nil? +#-----| false -> if ... +#-----| true -> self + +# 8| elements +#-----| -> call to nil? + +# 8| then ... +#-----| -> if ... + +# 9| call to puts +#-----| -> then ... + +# 9| self +#-----| -> "elements nil" + +# 9| "elements nil" +#-----| -> call to puts + +# 13| enter m2 +#-----| -> elements + +# 13| m2 +#-----| -> m3 + +# 13| exit m2 + +# 13| exit m2 (normal) +#-----| -> exit m2 + +# 13| elements +#-----| -> elements + +# 14| for ... in ... +#-----| -> exit m2 (normal) + +# 14| element +#-----| -> element + +# 14| In +#-----| empty -> for ... in ... +#-----| non-empty -> element + +# 14| elements +#-----| -> In + +# 14| do ... +#-----| -> In + +# 16| if ... +#-----| -> elements + +# 16| ... > ... +#-----| true -> break +#-----| false -> if ... +#-----| raise -> [ensure: raise] elements + +# 16| element +#-----| -> 0 + +# 16| 0 +#-----| -> ... > ... + +# 17| break +#-----| break -> [ensure: break] elements + +# 19| ensure ... +#-----| -> do ... + +# 19| [ensure: break] ensure ... +#-----| break -> for ... in ... + +# 19| [ensure: raise] ensure ... +#-----| raise -> for ... in ... + +# 20| if ... +#-----| -> ensure ... + +# 20| [ensure: break] if ... +#-----| -> [ensure: break] ensure ... + +# 20| [ensure: raise] if ... +#-----| -> [ensure: raise] ensure ... + +# 20| call to nil? +#-----| false -> if ... +#-----| true -> self + +# 20| [ensure: break] call to nil? +#-----| false -> [ensure: break] if ... +#-----| true -> [ensure: break] self + +# 20| [ensure: raise] call to nil? +#-----| false -> [ensure: raise] if ... +#-----| true -> [ensure: raise] self + +# 20| elements +#-----| -> call to nil? + +# 20| [ensure: break] elements +#-----| -> [ensure: break] call to nil? + +# 20| [ensure: raise] elements +#-----| -> [ensure: raise] call to nil? + +# 20| then ... +#-----| -> if ... + +# 20| [ensure: break] then ... +#-----| -> [ensure: break] if ... + +# 20| [ensure: raise] then ... +#-----| -> [ensure: raise] if ... + +# 21| call to puts +#-----| -> then ... + +# 21| [ensure: break] call to puts +#-----| -> [ensure: break] then ... + +# 21| [ensure: raise] call to puts +#-----| -> [ensure: raise] then ... + +# 21| self +#-----| -> "elements nil" + +# 21| [ensure: break] self +#-----| -> [ensure: break] "elements nil" + +# 21| [ensure: raise] self +#-----| -> [ensure: raise] "elements nil" + +# 21| "elements nil" +#-----| -> call to puts + +# 21| [ensure: break] "elements nil" +#-----| -> [ensure: break] call to puts + +# 21| [ensure: raise] "elements nil" +#-----| -> [ensure: raise] call to puts + +# 27| enter m3 +#-----| -> elements + +# 27| m3 +#-----| -> m4 + +# 27| exit m3 + +# 27| exit m3 (abnormal) +#-----| -> exit m3 + +# 27| exit m3 (normal) +#-----| -> exit m3 + +# 27| elements +#-----| -> elements + +# 29| if ... +#-----| -> elements + +# 29| call to nil? +#-----| false -> if ... +#-----| true -> return +#-----| raise -> [ensure: raise] elements + +# 29| elements +#-----| -> call to nil? + +# 30| return +#-----| return -> [ensure: return] elements + +# 32| ensure ... +#-----| -> self + +# 32| [ensure: raise] ensure ... +#-----| raise -> exit m3 (abnormal) + +# 32| [ensure: return] ensure ... +#-----| return -> exit m3 (normal) + +# 33| for ... in ... +#-----| -> ensure ... + +# 33| [ensure: raise] for ... in ... +#-----| -> [ensure: raise] ensure ... + +# 33| [ensure: return] for ... in ... +#-----| -> [ensure: return] ensure ... + +# 33| element +#-----| -> self + +# 33| [ensure: raise] element +#-----| -> [ensure: raise] self + +# 33| [ensure: return] element +#-----| -> [ensure: return] self + +# 33| In +#-----| empty -> for ... in ... +#-----| non-empty -> element + +# 33| [ensure: raise] In +#-----| empty -> [ensure: raise] for ... in ... +#-----| non-empty -> [ensure: raise] element + +# 33| [ensure: return] In +#-----| empty -> [ensure: return] for ... in ... +#-----| non-empty -> [ensure: return] element + +# 33| elements +#-----| -> In + +# 33| [ensure: raise] elements +#-----| -> [ensure: raise] In + +# 33| [ensure: return] elements +#-----| -> [ensure: return] In + +# 33| do ... +#-----| -> In + +# 33| [ensure: raise] do ... +#-----| -> [ensure: raise] In + +# 33| [ensure: return] do ... +#-----| -> [ensure: return] In + +# 35| if ... +#-----| -> do ... + +# 35| [ensure: raise] if ... +#-----| -> [ensure: raise] do ... + +# 35| [ensure: return] if ... +#-----| -> [ensure: return] do ... + +# 35| ... > ... +#-----| true -> break +#-----| false -> if ... + +# 35| [ensure: raise] ... > ... +#-----| true -> [ensure: raise] break +#-----| false -> [ensure: raise] if ... + +# 35| [ensure: return] ... > ... +#-----| true -> [ensure: return] break +#-----| false -> [ensure: return] if ... + +# 35| call to x +#-----| -> 0 + +# 35| [ensure: raise] call to x +#-----| -> [ensure: raise] 0 + +# 35| [ensure: return] call to x +#-----| -> [ensure: return] 0 + +# 35| self +#-----| -> call to x + +# 35| [ensure: raise] self +#-----| -> [ensure: raise] call to x + +# 35| [ensure: return] self +#-----| -> [ensure: return] call to x + +# 35| 0 +#-----| -> ... > ... + +# 35| [ensure: raise] 0 +#-----| -> [ensure: raise] ... > ... + +# 35| [ensure: return] 0 +#-----| -> [ensure: return] ... > ... + +# 36| break +#-----| break -> for ... in ... + +# 36| [ensure: raise] break +#-----| break -> [ensure: raise] for ... in ... + +# 36| [ensure: return] break +#-----| break -> [ensure: return] for ... in ... + +# 41| call to puts +#-----| -> exit m3 (normal) + +# 41| self +#-----| -> "Done" + +# 41| "Done" +#-----| -> call to puts + +# 44| enter m4 +#-----| -> elements + +# 44| m4 +#-----| -> exit break_ensure.rb (normal) + +# 44| exit m4 + +# 44| exit m4 (normal) +#-----| -> exit m4 + +# 44| elements +#-----| -> elements + +# 45| for ... in ... +#-----| -> exit m4 (normal) + +# 45| element +#-----| -> element + +# 45| In +#-----| empty -> for ... in ... +#-----| non-empty -> element + +# 45| elements +#-----| -> In + +# 45| do ... +#-----| -> In + +# 47| if ... +#-----| -> element + +# 47| ... > ... +#-----| false -> if ... +#-----| raise -> [ensure: raise] element +#-----| true -> self + +# 47| element +#-----| -> 1 + +# 47| 1 +#-----| -> ... > ... + +# 48| call to raise +#-----| raise -> [ensure: raise] element + +# 48| self +#-----| -> "" + +# 48| "" +#-----| -> call to raise + +# 50| ensure ... +#-----| -> do ... + +# 50| [ensure: raise] ensure ... +#-----| raise -> for ... in ... + +# 51| if ... +#-----| -> ensure ... + +# 51| [ensure: raise] if ... +#-----| -> [ensure: raise] ensure ... + +# 51| ... > ... +#-----| false -> if ... +#-----| true -> 10 + +# 51| [ensure: raise] ... > ... +#-----| false -> [ensure: raise] if ... +#-----| true -> [ensure: raise] 10 + +# 51| element +#-----| -> 0 + +# 51| [ensure: raise] element +#-----| -> [ensure: raise] 0 + +# 51| 0 +#-----| -> ... > ... + +# 51| [ensure: raise] 0 +#-----| -> [ensure: raise] ... > ... + +# 52| break +#-----| break -> for ... in ... + +# 52| [ensure: raise] break +#-----| break -> for ... in ... + +# 52| 10 +#-----| -> break + +# 52| [ensure: raise] 10 +#-----| -> [ensure: raise] break + +case.rb: +# 1| enter if_in_case +#-----| -> case ... + +# 1| enter case.rb +#-----| -> if_in_case + +# 1| if_in_case +#-----| -> exit case.rb (normal) + +# 1| exit if_in_case + +# 1| exit case.rb + +# 1| exit if_in_case (normal) +#-----| -> exit if_in_case + +# 1| exit case.rb (normal) +#-----| -> exit case.rb + +# 2| case ... +#-----| -> self + +# 2| call to x1 +#-----| -> when ... + +# 2| self +#-----| -> call to x1 + +# 3| when ... +#-----| -> 1 + +# 3| 1 +#-----| no-match -> when ... +#-----| match -> self + +# 3| then ... +#-----| -> exit if_in_case (normal) + +# 3| ( ... ) +#-----| -> then ... + +# 3| if ... +#-----| -> ( ... ) + +# 3| call to x2 +#-----| false -> if ... +#-----| true -> self + +# 3| self +#-----| -> call to x2 + +# 3| then ... +#-----| -> if ... + +# 3| call to puts +#-----| -> then ... + +# 3| self +#-----| -> "x2" + +# 3| "x2" +#-----| -> call to puts + +# 4| when ... +#-----| -> 2 + +# 4| 2 +#-----| match -> self +#-----| no-match -> exit if_in_case (normal) + +# 4| then ... +#-----| -> exit if_in_case (normal) + +# 4| call to puts +#-----| -> then ... + +# 4| self +#-----| -> "2" + +# 4| "2" +#-----| -> call to puts + +cfg.html.erb: +# 5| enter cfg.html.erb +#-----| -> @title + +# 5| @title +#-----| -> self + +# 5| exit cfg.html.erb + +# 5| exit cfg.html.erb (normal) +#-----| -> exit cfg.html.erb + +# 6| call to stylesheet_link_tag +#-----| -> self + +# 6| self +#-----| -> "application" + +# 6| "application" +#-----| -> :media + +# 6| Pair +#-----| -> call to stylesheet_link_tag + +# 6| :media +#-----| -> "all" + +# 6| "all" +#-----| -> Pair + +# 12| call to link_to +#-----| -> self + +# 12| self +#-----| -> "A" + +# 12| "A" +#-----| -> self + +# 12| call to a +#-----| -> :id + +# 12| self +#-----| -> call to a + +# 12| Pair +#-----| -> call to link_to + +# 12| :id +#-----| -> "a" + +# 12| "a" +#-----| -> Pair + +# 15| call to link_to +#-----| -> self + +# 15| self +#-----| -> "B" + +# 15| "B" +#-----| -> self + +# 15| call to a +#-----| -> call to link_to + +# 15| self +#-----| -> call to a + +# 16| call to link_to +#-----| -> self + +# 16| self +#-----| -> "C" + +# 16| "C" +#-----| -> self + +# 16| call to b +#-----| -> call to link_to + +# 16| self +#-----| -> call to b + +# 18| if ... +#-----| -> self + +# 18| call to admin? +#-----| true -> self +#-----| false -> self + +# 18| self +#-----| -> call to admin? + +# 18| then ... +#-----| -> if ... + +# 19| call to link_to +#-----| -> then ... + +# 19| self +#-----| -> "D" + +# 19| "D" +#-----| -> self + +# 19| call to d +#-----| -> call to link_to + +# 19| self +#-----| -> call to d + +# 20| else ... +#-----| -> if ... + +# 21| call to link_to +#-----| -> else ... + +# 21| self +#-----| -> "E" + +# 21| "E" +#-----| -> self + +# 21| call to e +#-----| -> call to link_to + +# 21| self +#-----| -> call to e + +# 29| call to each +#-----| -> exit cfg.html.erb (normal) + +# 29| call to collection +#-----| -> do ... end + +# 29| self +#-----| -> call to collection + +# 29| enter do ... end +#-----| -> key + +# 29| do ... end +#-----| -> call to each + +# 29| exit do ... end + +# 29| exit do ... end (normal) +#-----| -> exit do ... end + +# 29| key +#-----| -> value + +# 29| value +#-----| -> key + +# 30| key +#-----| -> value + +# 30| value +#-----| -> exit do ... end (normal) + +cfg.rb: +# 1| enter cfg.rb +#-----| -> self + +# 1| bar +#-----| -> alias ... + +# 1| exit cfg.rb + +# 1| exit cfg.rb (normal) +#-----| -> exit cfg.rb + +# 3| alias ... +#-----| -> foo + +# 3| foo +#-----| -> bar + +# 3| bar +#-----| -> b + +# 5| ... = ... +#-----| -> Array + +# 5| b +#-----| -> 42 + +# 5| 42 +#-----| -> ... = ... + +# 7| call to [] +#-----| -> Array + +# 7| Array +#-----| -> b + +# 7| :"one#{...}" +#-----| -> :"another" + +# 7| #{...} +#-----| -> :"one#{...}" + +# 7| b +#-----| -> #{...} + +# 7| :"another" +#-----| -> call to [] + +# 9| call to [] +#-----| -> self + +# 9| Array +#-----| -> b + +# 9| "one#{...}" +#-----| -> "another" + +# 9| #{...} +#-----| -> "one#{...}" + +# 9| b +#-----| -> #{...} + +# 9| "another" +#-----| -> call to [] + +# 12| call to puts +#-----| -> END { ... } + +# 12| self +#-----| -> 4 + +# 12| 4 +#-----| -> call to puts + +# 15| BEGIN { ... } +#-----| -> bar + +# 16| call to puts +#-----| -> BEGIN { ... } + +# 16| self +#-----| -> "hello" + +# 16| "hello" +#-----| -> call to puts + +# 19| enter END { ... } +#-----| -> self + +# 19| END { ... } +#-----| -> 41 + +# 19| exit END { ... } + +# 19| exit END { ... } (normal) +#-----| -> exit END { ... } + +# 20| call to puts +#-----| -> exit END { ... } (normal) + +# 20| self +#-----| -> "world" + +# 20| "world" +#-----| -> call to puts + +# 23| ... + ... +#-----| -> 2 + +# 23| 41 +#-----| -> 1 + +# 23| 1 +#-----| -> ... + ... + +# 25| 2 +#-----| -> { ... } + +# 25| call to times +#-----| -> self + +# 25| enter { ... } +#-----| -> x + +# 25| { ... } +#-----| -> call to times + +# 25| exit { ... } + +# 25| exit { ... } (normal) +#-----| -> exit { ... } + +# 25| x +#-----| -> self + +# 25| call to puts +#-----| -> exit { ... } (normal) + +# 25| self +#-----| -> x + +# 25| x +#-----| -> call to puts + +# 27| call to puts +#-----| -> Proc + +# 27| self +#-----| -> :puts + +# 27| &... +#-----| -> call to puts + +# 27| :puts +#-----| -> &... + +# 29| call to new +#-----| -> true + +# 29| Proc +#-----| -> { ... } + +# 29| enter { ... } +#-----| -> x + +# 29| { ... } +#-----| -> call to new + +# 29| exit { ... } + +# 29| exit { ... } (normal) +#-----| -> exit { ... } + +# 29| x +#-----| -> x + +# 29| call to call +#-----| -> exit { ... } (normal) + +# 29| x +#-----| -> call to call + +# 31| while ... +#-----| -> false + +# 31| true +#-----| true -> 1 + +# 32| break +#-----| break -> while ... + +# 32| 1 +#-----| -> break + +# 35| if ... +#-----| -> self + +# 35| false +#-----| false -> if ... + +# 39| call to puts +#-----| -> case ... + +# 39| self +#-----| -> 42 + +# 39| 42 +#-----| -> call to puts + +# 41| case ... +#-----| -> 10 + +# 41| 10 +#-----| -> when ... + +# 42| when ... +#-----| -> 1 + +# 42| 1 +#-----| no-match -> when ... +#-----| match -> self + +# 42| then ... +#-----| -> case ... + +# 42| call to puts +#-----| -> then ... + +# 42| self +#-----| -> "one" + +# 42| "one" +#-----| -> call to puts + +# 43| when ... +#-----| -> 2 + +# 43| 2 +#-----| no-match -> 3 +#-----| match -> self + +# 43| 3 +#-----| no-match -> 4 +#-----| match -> self + +# 43| 4 +#-----| match -> self +#-----| no-match -> self + +# 43| then ... +#-----| -> case ... + +# 43| call to puts +#-----| -> then ... + +# 43| self +#-----| -> "some" + +# 43| "some" +#-----| -> call to puts + +# 44| else ... +#-----| -> case ... + +# 44| call to puts +#-----| -> else ... + +# 44| self +#-----| -> "many" + +# 44| "many" +#-----| -> call to puts + +# 47| case ... +#-----| -> when ... + +# 48| when ... +#-----| -> b + +# 48| ... == ... +#-----| false -> when ... +#-----| true -> self + +# 48| b +#-----| -> 1 + +# 48| 1 +#-----| -> ... == ... + +# 48| then ... +#-----| -> chained + +# 48| call to puts +#-----| -> then ... + +# 48| self +#-----| -> "one" + +# 48| "one" +#-----| -> call to puts + +# 49| when ... +#-----| -> b + +# 49| ... == ... +#-----| false -> b +#-----| true -> self + +# 49| b +#-----| -> 0 + +# 49| 0 +#-----| -> ... == ... + +# 49| ... > ... +#-----| false -> chained +#-----| true -> self + +# 49| b +#-----| -> 1 + +# 49| 1 +#-----| -> ... > ... + +# 49| then ... +#-----| -> chained + +# 49| call to puts +#-----| -> then ... + +# 49| self +#-----| -> "some" + +# 49| "some" +#-----| -> call to puts + +# 52| ... = ... +#-----| -> character + +# 52| chained +#-----| -> "a" + +# 52| "a" +#-----| -> chained + +# 52| "#{...}" +#-----| -> "string" + +# 52| #{...} +#-----| -> "#{...}" + +# 52| chained +#-----| -> #{...} + +# 52| "string" +#-----| -> ... = ... + +# 54| ... = ... +#-----| -> Object + +# 54| character +#-----| -> ?\x40 + +# 54| ?\x40 +#-----| -> ... = ... + +# 58| Silly +#-----| -> x + +# 58| Object +#-----| -> complex + +# 59| ... = ... +#-----| -> conditional + +# 59| complex +#-----| -> 10-2i + +# 59| 10-2i +#-----| -> ... = ... + +# 60| ... = ... +#-----| -> C + +# 60| conditional +#-----| -> self + +# 60| ... < ... +#-----| true -> "hello" +#-----| false -> "bye" + +# 60| ... ? ... : ... +#-----| -> ... = ... + +# 60| call to b +#-----| -> 10 + +# 60| self +#-----| -> call to b + +# 60| 10 +#-----| -> ... < ... + +# 60| "hello" +#-----| -> ... ? ... : ... + +# 60| "bye" +#-----| -> ... ? ... : ... + +# 61| ... = ... +#-----| -> __synth__0 + +# 61| C +#-----| -> "constant" + +# 61| "constant" +#-----| -> ... = ... + +# 62| ... +#-----| -> pattern + +# 62| x +#-----| -> __synth__0 + +# 62| ... = ... +#-----| -> __synth__0__1 + +# 62| call to [] +#-----| -> ... = ... + +# 62| 0 +#-----| -> call to [] + +# 62| __synth__0 +#-----| -> 0 + +# 62| call to [] +#-----| -> * ... + +# 62| ... +#-----| -> ... + +# 62| 1 +#-----| -> call to [] + +# 62| __synth__0 +#-----| -> 1 + +# 62| ... = ... +#-----| -> y + +# 62| * ... +#-----| -> ... = ... + +# 62| __synth__0__1 +#-----| -> __synth__0 + +# 62| y +#-----| -> __synth__0__1 + +# 62| ... = ... +#-----| -> z + +# 62| call to [] +#-----| -> ... = ... + +# 62| 0 +#-----| -> call to [] + +# 62| __synth__0__1 +#-----| -> 0 + +# 62| z +#-----| -> __synth__0__1 + +# 62| ... = ... +#-----| -> ... + +# 62| call to [] +#-----| -> ... = ... + +# 62| 1 +#-----| -> call to [] + +# 62| __synth__0__1 +#-----| -> 1 + +# 62| call to [] +#-----| -> * ... + +# 62| ... = ... +#-----| -> x + +# 62| Array +#-----| -> 1 + +# 62| * ... +#-----| -> ... = ... + +# 62| __synth__0 +#-----| -> Array + +# 62| 1 +#-----| -> Array + +# 62| call to [] +#-----| -> call to [] + +# 62| Array +#-----| -> 2 + +# 62| 2 +#-----| -> 3 + +# 62| 3 +#-----| -> call to [] + +# 63| enter pattern +#-----| -> a + +# 63| pattern +#-----| -> items + +# 63| exit pattern + +# 63| exit pattern (normal) +#-----| -> exit pattern + +# 63| (..., ...) +#-----| -> self + +# 63| a +#-----| -> b + +# 63| b +#-----| -> (..., ...) + +# 64| call to puts +#-----| -> self + +# 64| self +#-----| -> a + +# 64| a +#-----| -> call to puts + +# 65| call to puts +#-----| -> exit pattern (normal) + +# 65| self +#-----| -> b + +# 65| b +#-----| -> call to puts + +# 67| ... = ... +#-----| -> self + +# 67| items +#-----| -> Array + +# 67| call to [] +#-----| -> ... = ... + +# 67| Array +#-----| -> 1 + +# 67| 1 +#-----| -> 2 + +# 67| 2 +#-----| -> 3 + +# 67| 3 +#-----| -> call to [] + +# 68| call to puts +#-----| -> print + +# 68| self +#-----| -> items + +# 68| ...[...] +#-----| -> call to puts + +# 68| items +#-----| -> 2 + +# 68| 2 +#-----| -> ...[...] + +# 69| enter print +#-----| -> self + +# 69| print +#-----| -> Silly + +# 69| exit print + +# 69| exit print (normal) +#-----| -> exit print + +# 70| call to puts +#-----| -> exit print (normal) + +# 70| self +#-----| -> "silly" + +# 70| "silly" +#-----| -> call to puts + +# 74| ... = ... +#-----| -> x + +# 74| x +#-----| -> 42 + +# 74| 42 +#-----| -> ... = ... + +# 75| if ... +#-----| -> ; + +# 75| ... < ... +#-----| true -> 0 +#-----| false -> x + +# 75| x +#-----| -> 0 + +# 75| 0 +#-----| -> ... < ... + +# 75| then ... +#-----| -> if ... + +# 75| 0 +#-----| -> then ... + +# 75| elsif ... +#-----| -> if ... + +# 75| ... > ... +#-----| true -> 10 +#-----| false -> x + +# 75| x +#-----| -> 10 + +# 75| 10 +#-----| -> ... > ... + +# 75| then ... +#-----| -> elsif ... + +# 75| 10 +#-----| -> then ... + +# 75| else ... +#-----| -> elsif ... + +# 75| x +#-----| -> else ... + +# 78| ; +#-----| -> self + +# 82| else ... +#-----| -> self + +# 83| call to puts +#-----| -> else ... + +# 83| self +#-----| -> "ok" + +# 83| "ok" +#-----| -> call to puts + +# 84| ensure ... +#-----| -> escape + +# 85| call to puts +#-----| -> ensure ... + +# 85| self +#-----| -> "end" + +# 85| "end" +#-----| -> call to puts + +# 88| ... = ... +#-----| -> Array + +# 88| escape +#-----| -> x + +# 88| "\u1234#{...}\n" +#-----| -> ... = ... + +# 88| #{...} +#-----| -> "\u1234#{...}\n" + +# 88| x +#-----| -> #{...} + +# 90| for ... in ... +#-----| -> $global + +# 90| x +#-----| -> x + +# 90| In +#-----| empty -> for ... in ... +#-----| non-empty -> x + +# 90| call to [] +#-----| -> In + +# 90| Array +#-----| -> 1.4 + +# 90| 1.4 +#-----| -> 2.5 + +# 90| 2.5 +#-----| -> 3.4e5 + +# 90| 3.4e5 +#-----| -> call to [] + +# 90| do ... +#-----| -> In + +# 91| if ... +#-----| -> self + +# 91| ... > ... +#-----| false -> if ... +#-----| true -> next + +# 91| x +#-----| -> 3 + +# 91| 3 +#-----| -> ... > ... + +# 91| next +#-----| next -> In + +# 92| call to puts +#-----| -> do ... + +# 92| self +#-----| -> x + +# 92| x +#-----| -> call to puts + +# 95| ... = ... +#-----| -> map1 + +# 95| $global +#-----| -> 42 + +# 95| 42 +#-----| -> ... = ... + +# 97| ... = ... +#-----| -> map2 + +# 97| map1 +#-----| -> "a" + +# 97| {...} +#-----| -> ... = ... + +# 97| Pair +#-----| -> "c" + +# 97| "a" +#-----| -> "b" + +# 97| "b" +#-----| -> Pair + +# 97| Pair +#-----| -> :e + +# 97| "c" +#-----| -> "d" + +# 97| "d" +#-----| -> Pair + +# 97| Pair +#-----| -> {...} + +# 97| :e +#-----| -> "f" + +# 97| "f" +#-----| -> Pair + +# 98| ... = ... +#-----| -> parameters + +# 98| map2 +#-----| -> map1 + +# 98| {...} +#-----| -> ... = ... + +# 98| ** ... +#-----| -> "x" + +# 98| map1 +#-----| -> ** ... + +# 98| Pair +#-----| -> map1 + +# 98| "x" +#-----| -> "y" + +# 98| "y" +#-----| -> Pair + +# 98| ** ... +#-----| -> {...} + +# 98| map1 +#-----| -> ** ... + +# 101| enter parameters +#-----| -> value + +# 101| parameters +#-----| -> type + +# 101| exit parameters + +# 101| exit parameters (normal) +#-----| -> exit parameters + +# 101| value +#-----| no-match -> 42 +#-----| match -> key + +# 101| 42 +#-----| -> key + +# 101| key +#-----| -> kwargs + +# 101| kwargs +#-----| -> self + +# 102| call to puts +#-----| -> kwargs + +# 102| self +#-----| -> value + +# 102| value +#-----| -> call to puts + +# 103| return +#-----| return -> exit parameters (normal) + +# 103| ...[...] +#-----| -> return + +# 103| kwargs +#-----| -> key + +# 103| key +#-----| -> ...[...] + +# 106| ... = ... +#-----| -> table + +# 106| type +#-----| -> "healthy" + +# 106| "healthy" +#-----| -> ... = ... + +# 107| ... = ... +#-----| -> self + +# 107| table +#-----| -> "food" + +# 107| "food" +#-----| -> ... = ... + +# 108| call to puts +#-----| -> b + +# 108| self +#-----| -> < call to puts + +# 108| < table + +# 109| #{...} +#-----| -> type + +# 109| table +#-----| -> #{...} + +# 110| #{...} +#-----| -> ( ... ) + +# 110| type +#-----| -> #{...} + +# 113| ... if ... +#-----| -> @field + +# 113| call to puts +#-----| -> ... if ... + +# 113| self +#-----| -> "hi" + +# 113| "hi" +#-----| -> call to puts + +# 113| ... > ... +#-----| false -> ... if ... +#-----| true -> self + +# 113| b +#-----| -> 10 + +# 113| 10 +#-----| -> ... > ... + +# 115| C +#-----| -> swap + +# 116| ... = ... +#-----| -> @@static_field + +# 116| @field +#-----| -> 42 + +# 116| 42 +#-----| -> ... = ... + +# 117| ... = ... +#-----| -> C + +# 117| @@static_field +#-----| -> 10 + +# 117| 10 +#-----| -> ... = ... + +# 120| ... = ... +#-----| -> nothing + +# 120| swap +#-----| -> -> { ... } + +# 120| enter -> { ... } +#-----| -> x + +# 120| -> { ... } +#-----| -> ... = ... + +# 120| exit -> { ... } + +# 120| exit -> { ... } (normal) +#-----| -> exit -> { ... } + +# 120| (..., ...) +#-----| -> Array + +# 120| x +#-----| -> y + +# 120| y +#-----| -> (..., ...) + +# 120| call to [] +#-----| -> exit -> { ... } (normal) + +# 120| Array +#-----| -> y + +# 120| y +#-----| -> x + +# 120| x +#-----| -> call to [] + +# 122| M +#-----| -> EmptyClass + +# 123| ... = ... +#-----| -> some + +# 123| nothing +#-----| -> nil + +# 123| nil +#-----| -> ... = ... + +# 124| ... = ... +#-----| -> some + +# 124| some +#-----| -> 2 + +# 124| 2 +#-----| -> ... = ... + +# 125| some +#-----| -> some + +# 125| ... = ... +#-----| -> last + +# 125| some +#-----| -> 10 + +# 125| ... + ... +#-----| -> ... = ... + +# 125| 10 +#-----| -> ... + ... + +# 126| ... = ... +#-----| -> range + +# 126| last +#-----| -> 2 + +# 126| ( ... ) +#-----| -> ... = ... + +# 126| 2 +#-----| -> 4 + +# 126| 4 +#-----| -> 7 + +# 126| 7 +#-----| -> ( ... ) + +# 127| ... = ... +#-----| -> half + +# 127| range +#-----| -> 0 + +# 127| 0 +#-----| -> 9 + +# 127| _ .. _ +#-----| -> ... = ... + +# 127| 9 +#-----| -> _ .. _ + +# 128| ... = ... +#-----| -> regex + +# 128| half +#-----| -> 1 + +# 128| ... + ... +#-----| -> ... = ... + +# 128| ... / ... +#-----| -> 1 + +# 128| 1 +#-----| -> 3r + +# 128| 3r +#-----| -> ... / ... + +# 128| ... / ... +#-----| -> ... + ... + +# 128| 1 +#-----| -> 6r + +# 128| 6r +#-----| -> ... / ... + +# 129| ... = ... +#-----| -> Constant + +# 129| regex +#-----| -> range + +# 129| /hello\s+[#{...}]/ +#-----| -> ... = ... + +# 129| #{...} +#-----| -> /hello\s+[#{...}]/ + +# 129| range +#-----| -> #{...} + +# 130| ... = ... +#-----| -> M + +# 130| Constant +#-----| -> 5 + +# 130| 5 +#-----| -> ... = ... + +# 133| EmptyClass +#-----| -> EmptyModule + +# 134| EmptyModule +#-----| -> ... rescue ... + +# 136| ... rescue ... +#-----| -> 1 + +# 136| ... / ... +#-----| raise -> self +#-----| -> __synth__0 + +# 136| 1 +#-----| -> 0 + +# 136| 0 +#-----| -> ... / ... + +# 136| call to puts +#-----| -> __synth__0 + +# 136| self +#-----| -> "div by zero" + +# 136| "div by zero" +#-----| -> call to puts + +# 138| ... +#-----| -> M + +# 138| init +#-----| -> __synth__0 + +# 138| ... = ... +#-----| -> last + +# 138| call to [] +#-----| -> ... = ... + +# 138| _ .. _ +#-----| -> call to [] + +# 138| __synth__0 +#-----| -> 0 + +# 138| 0 +#-----| -> -2 + +# 138| -2 +#-----| -> _ .. _ + +# 138| last +#-----| -> __synth__0 + +# 138| ... = ... +#-----| -> ... + +# 138| call to [] +#-----| -> ... = ... + +# 138| -1 +#-----| -> call to [] + +# 138| __synth__0 +#-----| -> -1 + +# 138| 1 +#-----| -> 2 + +# 138| ... = ... +#-----| -> init + +# 138| * ... +#-----| -> ... = ... + +# 138| __synth__0 +#-----| -> 1 + +# 138| 2 +#-----| -> 3 + +# 138| 3 +#-----| -> * ... + +# 140| M +#-----| -> Constant + +# 140| Constant +#-----| -> M + +# 141| call to itself +#-----| -> Constant + +# 141| M +#-----| -> call to itself + +# 141| Constant +#-----| -> Silly + +# 143| class << ... +#-----| -> silly + +# 143| call to itself +#-----| -> setter= + +# 143| Silly +#-----| -> call to itself + +# 144| setter= +#-----| -> print + +# 145| enter print +#-----| -> self + +# 145| print +#-----| -> class << ... + +# 145| exit print + +# 145| exit print (normal) +#-----| -> exit print + +# 146| call to puts +#-----| -> self + +# 146| self +#-----| -> "singleton" + +# 146| "singleton" +#-----| -> call to puts + +# 147| call to puts +#-----| -> exit print (normal) + +# 147| self +#-----| -> call to super + +# 147| call to print +#-----| -> call to puts + +# 147| call to super +#-----| -> call to print + +# 151| ... = ... +#-----| -> silly + +# 151| silly +#-----| -> Silly + +# 151| call to new +#-----| -> ... = ... + +# 151| Silly +#-----| -> call to new + +# 152| enter method +#-----| -> x + +# 152| method +#-----| -> two_parameters + +# 152| exit method + +# 152| exit method (normal) +#-----| -> exit method + +# 152| silly +#-----| -> method + +# 152| x +#-----| -> self + +# 153| call to puts +#-----| -> exit method (normal) + +# 153| self +#-----| -> x + +# 153| x +#-----| -> call to puts + +# 156| enter two_parameters +#-----| -> a + +# 156| two_parameters +#-----| -> self + +# 156| exit two_parameters + +# 156| exit two_parameters (normal) +#-----| -> exit two_parameters + +# 156| a +#-----| -> b + +# 156| b +#-----| -> exit two_parameters (normal) + +# 158| call to two_parameters +#-----| -> scriptfile + +# 158| self +#-----| -> Array + +# 158| * ... +#-----| -> call to two_parameters + +# 158| call to [] +#-----| -> * ... + +# 158| Array +#-----| -> 1 + +# 158| 1 +#-----| -> 2 + +# 158| 2 +#-----| -> call to [] + +# 160| ... = ... +#-----| -> symbol + +# 160| scriptfile +#-----| -> self + +# 160| `cat "#{...}"` +#-----| -> ... = ... + +# 160| #{...} +#-----| -> `cat "#{...}"` + +# 160| call to __FILE__ +#-----| -> #{...} + +# 160| self +#-----| -> call to __FILE__ + +# 162| ... = ... +#-----| -> delimited_symbol + +# 162| symbol +#-----| -> :hello + +# 162| :hello +#-----| -> ... = ... + +# 164| ... = ... +#-----| -> x + +# 164| delimited_symbol +#-----| -> 12 + +# 164| :"goodbye-#{...}" +#-----| -> ... = ... + +# 164| #{...} +#-----| -> :"goodbye-#{...}" + +# 164| ... + ... +#-----| -> #{...} + +# 164| 12 +#-----| -> 13 + +# 164| 13 +#-----| -> ... + ... + +# 166| ... = ... +#-----| -> x + +# 166| x +#-----| -> true + +# 166| true +#-----| -> ... = ... + +# 167| ... = ... +#-----| -> x + +# 167| x +#-----| -> true + +# 167| ! ... +#-----| -> ... = ... + +# 167| true +#-----| -> ! ... + +# 168| ... = ... +#-----| -> undef ... + +# 168| x +#-----| -> 42 + +# 168| - ... +#-----| -> ... = ... + +# 168| 42 +#-----| -> - ... + +# 170| undef ... +#-----| -> two_parameters + +# 170| two_parameters +#-----| -> x + +# 172| unless ... +#-----| -> x + +# 172| ... == ... +#-----| false -> self +#-----| true -> self + +# 172| x +#-----| -> 10 + +# 172| 10 +#-----| -> ... == ... + +# 172| then ... +#-----| -> unless ... + +# 172| call to puts +#-----| -> then ... + +# 172| self +#-----| -> "hi" + +# 172| "hi" +#-----| -> call to puts + +# 172| else ... +#-----| -> unless ... + +# 172| call to puts +#-----| -> else ... + +# 172| self +#-----| -> "bye" + +# 172| "bye" +#-----| -> call to puts + +# 174| call to puts +#-----| -> ... unless ... + +# 174| ... unless ... +#-----| -> x + +# 174| self +#-----| -> "hi" + +# 174| "hi" +#-----| -> call to puts + +# 174| ... == ... +#-----| true -> ... unless ... +#-----| false -> self + +# 174| x +#-----| -> 0 + +# 174| 0 +#-----| -> ... == ... + +# 176| until ... +#-----| -> i + +# 176| ... > ... +#-----| true -> until ... +#-----| false -> x + +# 176| x +#-----| -> 10 + +# 176| 10 +#-----| -> ... > ... + +# 176| do ... +#-----| -> x + +# 176| x +#-----| -> x + +# 176| ... = ... +#-----| -> self + +# 176| x +#-----| -> 10 + +# 176| ... + ... +#-----| -> ... = ... + +# 176| 10 +#-----| -> ... + ... + +# 176| call to puts +#-----| -> do ... + +# 176| self +#-----| -> "hello" + +# 176| "hello" +#-----| -> call to puts + +# 178| ... = ... +#-----| -> i + +# 178| i +#-----| -> 0 + +# 178| 0 +#-----| -> ... = ... + +# 179| ( ... ) +#-----| -> i + +# 179| ... until ... +#-----| -> x + +# 179| call to puts +#-----| -> i + +# 179| self +#-----| -> "hello" + +# 179| "hello" +#-----| -> call to puts + +# 179| i +#-----| -> i + +# 179| ... = ... +#-----| -> ( ... ) + +# 179| i +#-----| -> 1 + +# 179| ... + ... +#-----| -> ... = ... + +# 179| 1 +#-----| -> ... + ... + +# 179| ... == ... +#-----| true -> ... until ... +#-----| false -> self + +# 179| i +#-----| -> 10 + +# 179| 10 +#-----| -> ... == ... + +# 181| ... = ... +#-----| -> x + +# 181| x +#-----| -> 0 + +# 181| 0 +#-----| -> ... = ... + +# 182| while ... +#-----| -> i + +# 182| ... < ... +#-----| false -> while ... +#-----| true -> x + +# 182| x +#-----| -> 10 + +# 182| 10 +#-----| -> ... < ... + +# 182| do ... +#-----| -> x + +# 183| x +#-----| -> x + +# 183| ... = ... +#-----| -> x + +# 183| x +#-----| -> 1 + +# 183| ... + ... +#-----| -> ... = ... + +# 183| 1 +#-----| -> ... + ... + +# 184| if ... +#-----| -> self + +# 184| ... == ... +#-----| false -> if ... +#-----| true -> redo + +# 184| x +#-----| -> 5 + +# 184| 5 +#-----| -> ... == ... + +# 184| redo +#-----| redo -> x + +# 185| call to puts +#-----| -> do ... + +# 185| self +#-----| -> x + +# 185| x +#-----| -> call to puts + +# 188| ( ... ) +#-----| -> i + +# 188| ... while ... +#-----| -> run_block + +# 188| call to puts +#-----| -> i + +# 188| self +#-----| -> "hello" + +# 188| "hello" +#-----| -> call to puts + +# 188| i +#-----| -> i + +# 188| ... = ... +#-----| -> ( ... ) + +# 188| i +#-----| -> 1 + +# 188| ... - ... +#-----| -> ... = ... + +# 188| 1 +#-----| -> ... - ... + +# 188| ... != ... +#-----| false -> ... while ... +#-----| true -> self + +# 188| i +#-----| -> 0 + +# 188| 0 +#-----| -> ... != ... + +# 190| enter run_block +#-----| -> 42 + +# 190| run_block +#-----| -> self + +# 190| exit run_block + +# 190| exit run_block (normal) +#-----| -> exit run_block + +# 191| yield ... +#-----| -> exit run_block (normal) + +# 191| 42 +#-----| -> yield ... + +# 194| call to run_block +#-----| -> exit cfg.rb (normal) + +# 194| self +#-----| -> { ... } + +# 194| enter { ... } +#-----| -> x + +# 194| { ... } +#-----| -> call to run_block + +# 194| exit { ... } + +# 194| exit { ... } (normal) +#-----| -> exit { ... } + +# 194| x +#-----| -> self + +# 194| call to puts +#-----| -> exit { ... } (normal) + +# 194| self +#-----| -> x + +# 194| x +#-----| -> call to puts + +desugar.rb: +# 1| enter m1 +#-----| -> x + +# 1| enter desugar.rb +#-----| -> m1 + +# 1| m1 +#-----| -> m2 + +# 1| exit m1 + +# 1| exit desugar.rb + +# 1| exit m1 (normal) +#-----| -> exit m1 + +# 1| exit desugar.rb (normal) +#-----| -> exit desugar.rb + +# 1| x +#-----| -> x + +# 2| x +#-----| -> x + +# 2| ... = ... +#-----| -> exit m1 (normal) + +# 2| x +#-----| -> 1 + +# 2| ... + ... +#-----| -> ... = ... + +# 2| 1 +#-----| -> ... + ... + +# 5| enter m2 +#-----| -> x + +# 5| m2 +#-----| -> m3 + +# 5| exit m2 + +# 5| exit m2 (normal) +#-----| -> exit m2 + +# 5| x +#-----| -> x + +# 6| call to foo +#-----| -> __synth__0 + +# 6| x +#-----| -> call to foo + +# 6| ... +#-----| -> exit m2 (normal) + +# 6| call to count= +#-----| -> __synth__0 + +# 6| __synth__0 +#-----| -> ... + +# 6| ... = ... +#-----| -> call to count= + +# 6| __synth__0 +#-----| -> 1 + +# 6| 1 +#-----| -> ... = ... + +# 9| enter m3 +#-----| -> x + +# 9| m3 +#-----| -> m4 + +# 9| exit m3 + +# 9| exit m3 (normal) +#-----| -> exit m3 + +# 9| x +#-----| -> x + +# 10| call to foo +#-----| -> 0 + +# 10| x +#-----| -> call to foo + +# 10| ... +#-----| -> exit m3 (normal) + +# 10| call to []= +#-----| -> __synth__0 + +# 10| __synth__0 +#-----| -> ... + +# 10| ... = ... +#-----| -> call to []= + +# 10| __synth__0 +#-----| -> 1 + +# 10| 0 +#-----| -> __synth__0 + +# 10| 1 +#-----| -> ... = ... + +# 13| enter m4 +#-----| -> x + +# 13| m4 +#-----| -> m5 + +# 13| exit m4 + +# 13| exit m4 (normal) +#-----| -> exit m4 + +# 13| x +#-----| -> __synth__0 + +# 14| call to foo +#-----| -> ... = ... + +# 14| x +#-----| -> call to foo + +# 14| ... +#-----| -> exit m4 (normal) + +# 14| ... = ... +#-----| -> __synth__1 + +# 14| call to count= +#-----| -> __synth__1 + +# 14| __synth__0 +#-----| -> x + +# 14| __synth__0 +#-----| -> __synth__1 + +# 14| call to count +#-----| -> 1 + +# 14| __synth__0 +#-----| -> call to count + +# 14| ... = ... +#-----| -> __synth__0 + +# 14| __synth__1 +#-----| -> ... + +# 14| ... + ... +#-----| -> ... = ... + +# 14| __synth__1 +#-----| -> __synth__0 + +# 14| __synth__1 +#-----| -> call to count= + +# 14| 1 +#-----| -> ... + ... + +# 17| enter m5 +#-----| -> x + +# 17| m5 +#-----| -> m6 + +# 17| exit m5 + +# 17| exit m5 (normal) +#-----| -> exit m5 + +# 17| x +#-----| -> y + +# 17| y +#-----| -> __synth__0 + +# 18| call to foo +#-----| -> ... = ... + +# 18| x +#-----| -> call to foo + +# 18| ... +#-----| -> exit m5 (normal) + +# 18| ... = ... +#-----| -> __synth__1 + +# 18| call to []= +#-----| -> __synth__4 + +# 18| __synth__0 +#-----| -> x + +# 18| __synth__0 +#-----| -> __synth__1 + +# 18| call to [] +#-----| -> 1 + +# 18| __synth__0 +#-----| -> __synth__1 + +# 18| 0 +#-----| -> ... = ... + +# 18| ... = ... +#-----| -> __synth__2 + +# 18| __synth__1 +#-----| -> 0 + +# 18| __synth__1 +#-----| -> __synth__2 + +# 18| __synth__1 +#-----| -> __synth__2 + +# 18| call to bar +#-----| -> ... = ... + +# 18| y +#-----| -> call to bar + +# 18| ... = ... +#-----| -> __synth__3 + +# 18| __synth__2 +#-----| -> y + +# 18| __synth__2 +#-----| -> __synth__3 + +# 18| __synth__2 +#-----| -> __synth__3 + +# 18| ... + ... +#-----| -> ... = ... + +# 18| call to baz +#-----| -> 3 + +# 18| x +#-----| -> call to baz + +# 18| ... = ... +#-----| -> __synth__4 + +# 18| __synth__3 +#-----| -> x + +# 18| __synth__3 +#-----| -> __synth__4 + +# 18| __synth__3 +#-----| -> call to [] + +# 18| 3 +#-----| -> ... + ... + +# 18| ... = ... +#-----| -> __synth__0 + +# 18| __synth__4 +#-----| -> ... + +# 18| ... + ... +#-----| -> ... = ... + +# 18| __synth__4 +#-----| -> __synth__0 + +# 18| __synth__4 +#-----| -> call to []= + +# 18| 1 +#-----| -> ... + ... + +# 21| enter m6 +#-----| -> __synth__0 + +# 21| m6 +#-----| -> m7 + +# 21| exit m6 + +# 21| exit m6 (normal) +#-----| -> exit m6 + +# 22| x +#-----| -> __synth__0 + +# 22| ... +#-----| -> exit m6 (normal) + +# 22| ... = ... +#-----| -> y + +# 22| call to [] +#-----| -> ... = ... + +# 22| 0 +#-----| -> call to [] + +# 22| __synth__0 +#-----| -> 0 + +# 22| y +#-----| -> __synth__0 + +# 22| ... = ... +#-----| -> self + +# 22| call to [] +#-----| -> ... = ... + +# 22| _ .. _ +#-----| -> call to [] + +# 22| __synth__0 +#-----| -> 1 + +# 22| 1 +#-----| -> -2 + +# 22| -2 +#-----| -> _ .. _ + +# 22| call to z +#-----| -> __synth__0__1 + +# 22| self +#-----| -> call to z + +# 22| call to [] +#-----| -> ... = ... + +# 22| ... +#-----| -> ... + +# 22| -1 +#-----| -> call to [] + +# 22| __synth__0 +#-----| -> -1 + +# 22| call to bar= +#-----| -> __synth__0__1 + +# 22| __synth__0__1 +#-----| -> ... + +# 22| ... = ... +#-----| -> call to bar= + +# 22| __synth__0__1 +#-----| -> __synth__0 + +# 22| call to [] +#-----| -> * ... + +# 22| ... = ... +#-----| -> x + +# 22| Array +#-----| -> 1 + +# 22| * ... +#-----| -> ... = ... + +# 22| __synth__0 +#-----| -> Array + +# 22| 1 +#-----| -> 2 + +# 22| 2 +#-----| -> 3 + +# 22| 3 +#-----| -> 4 + +# 22| 4 +#-----| -> call to [] + +# 25| enter m7 +#-----| -> __synth__0 + +# 25| m7 +#-----| -> @x + +# 25| exit m7 + +# 25| exit m7 (normal) +#-----| -> exit m7 + +# 26| x +#-----| -> __synth__0 + +# 26| ... +#-----| -> exit m7 (normal) + +# 26| ... = ... +#-----| -> __synth__0__1 + +# 26| call to [] +#-----| -> ... = ... + +# 26| 0 +#-----| -> call to [] + +# 26| __synth__0 +#-----| -> 0 + +# 26| call to [] +#-----| -> * ... + +# 26| ... +#-----| -> ... + +# 26| 1 +#-----| -> call to [] + +# 26| __synth__0 +#-----| -> 1 + +# 26| ... = ... +#-----| -> y + +# 26| * ... +#-----| -> ... = ... + +# 26| __synth__0__1 +#-----| -> __synth__0 + +# 26| y +#-----| -> __synth__0__1 + +# 26| ... = ... +#-----| -> z + +# 26| call to [] +#-----| -> ... = ... + +# 26| 0 +#-----| -> call to [] + +# 26| __synth__0__1 +#-----| -> 0 + +# 26| z +#-----| -> __synth__0__1 + +# 26| ... = ... +#-----| -> ... + +# 26| call to [] +#-----| -> ... = ... + +# 26| 1 +#-----| -> call to [] + +# 26| __synth__0__1 +#-----| -> 1 + +# 26| call to [] +#-----| -> * ... + +# 26| ... = ... +#-----| -> x + +# 26| Array +#-----| -> 1 + +# 26| * ... +#-----| -> ... = ... + +# 26| __synth__0 +#-----| -> Array + +# 26| 1 +#-----| -> Array + +# 26| call to [] +#-----| -> call to [] + +# 26| Array +#-----| -> 2 + +# 26| 2 +#-----| -> 3 + +# 26| 3 +#-----| -> call to [] + +# 29| X +#-----| -> $global_var + +# 30| ... = ... +#-----| -> @x + +# 30| @x +#-----| -> 1 + +# 30| 1 +#-----| -> ... = ... + +# 31| @x +#-----| -> @x + +# 31| ... = ... +#-----| -> @@y + +# 31| @x +#-----| -> 2 + +# 31| ... + ... +#-----| -> ... = ... + +# 31| 2 +#-----| -> ... + ... + +# 33| ... = ... +#-----| -> @@y + +# 33| @@y +#-----| -> 3 + +# 33| 3 +#-----| -> ... = ... + +# 34| @@y +#-----| -> @@y + +# 34| ... = ... +#-----| -> X + +# 34| @@y +#-----| -> 4 + +# 34| ... / ... +#-----| -> ... = ... + +# 34| 4 +#-----| -> ... / ... + +# 37| ... = ... +#-----| -> $global_var + +# 37| $global_var +#-----| -> 5 + +# 37| 5 +#-----| -> ... = ... + +# 38| $global_var +#-----| -> $global_var + +# 38| ... = ... +#-----| -> exit desugar.rb (normal) + +# 38| $global_var +#-----| -> 6 + +# 38| ... * ... +#-----| -> ... = ... + +# 38| 6 +#-----| -> ... * ... + +exit.rb: +# 1| enter m1 +#-----| -> x + +# 1| enter exit.rb +#-----| -> m1 + +# 1| m1 +#-----| -> m2 + +# 1| exit m1 + +# 1| exit exit.rb + +# 1| exit m1 (abnormal) +#-----| -> exit m1 + +# 1| exit m1 (normal) +#-----| -> exit m1 + +# 1| exit exit.rb (normal) +#-----| -> exit exit.rb + +# 1| x +#-----| -> x + +# 2| if ... +#-----| -> self + +# 2| ... > ... +#-----| false -> if ... +#-----| true -> self + +# 2| x +#-----| -> 2 + +# 2| 2 +#-----| -> ... > ... + +# 3| call to exit +#-----| exit -> exit m1 (abnormal) + +# 3| self +#-----| -> 1 + +# 3| 1 +#-----| -> call to exit + +# 5| call to puts +#-----| -> exit m1 (normal) + +# 5| self +#-----| -> "x <= 2" + +# 5| "x <= 2" +#-----| -> call to puts + +# 8| enter m2 +#-----| -> x + +# 8| m2 +#-----| -> exit exit.rb (normal) + +# 8| exit m2 + +# 8| exit m2 (abnormal) +#-----| -> exit m2 + +# 8| exit m2 (normal) +#-----| -> exit m2 + +# 8| x +#-----| -> x + +# 9| if ... +#-----| -> self + +# 9| ... > ... +#-----| false -> if ... +#-----| true -> self + +# 9| x +#-----| -> 2 + +# 9| 2 +#-----| -> ... > ... + +# 10| call to abort +#-----| exit -> exit m2 (abnormal) + +# 10| self +#-----| -> "abort!" + +# 10| "abort!" +#-----| -> call to abort + +# 12| call to puts +#-----| -> exit m2 (normal) + +# 12| self +#-----| -> "x <= 2" + +# 12| "x <= 2" +#-----| -> call to puts + +heredoc.rb: +# 1| enter double_heredoc +#-----| -> self + +# 1| enter heredoc.rb +#-----| -> double_heredoc + +# 1| double_heredoc +#-----| -> exit heredoc.rb (normal) + +# 1| exit double_heredoc + +# 1| exit heredoc.rb + +# 1| exit double_heredoc (normal) +#-----| -> exit double_heredoc + +# 1| exit heredoc.rb (normal) +#-----| -> exit heredoc.rb + +# 2| call to puts +#-----| -> exit double_heredoc (normal) + +# 2| self +#-----| -> < < call to puts + +ifs.rb: +# 1| enter m1 +#-----| -> x + +# 1| enter ifs.rb +#-----| -> m1 + +# 1| m1 +#-----| -> m2 + +# 1| exit m1 + +# 1| exit ifs.rb + +# 1| exit m1 (normal) +#-----| -> exit m1 + +# 1| exit ifs.rb (normal) +#-----| -> exit ifs.rb + +# 1| x +#-----| -> x + +# 2| if ... +#-----| -> exit m1 (normal) + +# 2| ... > ... +#-----| false -> x +#-----| true -> self + +# 2| x +#-----| -> 2 + +# 2| 2 +#-----| -> ... > ... + +# 2| then ... +#-----| -> if ... + +# 3| call to puts +#-----| -> then ... + +# 3| self +#-----| -> "x is greater than 2" + +# 3| "x is greater than 2" +#-----| -> call to puts + +# 4| elsif ... +#-----| -> if ... + +# 4| ... <= ... +#-----| false -> [false] ... and ... +#-----| true -> x + +# 4| [false] ... and ... +#-----| false -> self + +# 4| [true] ... and ... +#-----| true -> self + +# 4| [false] ... and ... +#-----| false -> [false] ... and ... + +# 4| [true] ... and ... +#-----| true -> x + +# 4| x +#-----| -> 2 + +# 4| 2 +#-----| -> ... <= ... + +# 4| ... > ... +#-----| false -> [false] ... and ... +#-----| true -> [true] ... and ... + +# 4| x +#-----| -> 0 + +# 4| 0 +#-----| -> ... > ... + +# 4| [false] ! ... +#-----| false -> [false] ... and ... + +# 4| [true] ! ... +#-----| true -> [true] ... and ... + +# 4| [false] ( ... ) +#-----| false -> [true] ! ... + +# 4| [true] ( ... ) +#-----| true -> [false] ! ... + +# 4| ... == ... +#-----| false -> [false] ( ... ) +#-----| true -> [true] ( ... ) + +# 4| x +#-----| -> 5 + +# 4| 5 +#-----| -> ... == ... + +# 4| then ... +#-----| -> elsif ... + +# 5| call to puts +#-----| -> then ... + +# 5| self +#-----| -> "x is 1" + +# 5| "x is 1" +#-----| -> call to puts + +# 6| else ... +#-----| -> elsif ... + +# 7| call to puts +#-----| -> else ... + +# 7| self +#-----| -> "I can't guess the number" + +# 7| "I can't guess the number" +#-----| -> call to puts + +# 11| enter m2 +#-----| -> b + +# 11| m2 +#-----| -> m3 + +# 11| exit m2 + +# 11| exit m2 (normal) +#-----| -> exit m2 + +# 11| b +#-----| -> b + +# 12| if ... +#-----| -> 1 + +# 12| b +#-----| false -> if ... +#-----| true -> 0 + +# 13| return +#-----| return -> exit m2 (normal) + +# 13| 0 +#-----| -> return + +# 15| return +#-----| return -> exit m2 (normal) + +# 15| 1 +#-----| -> return + +# 18| enter m3 +#-----| -> x + +# 18| m3 +#-----| -> m4 + +# 18| exit m3 + +# 18| exit m3 (normal) +#-----| -> exit m3 + +# 18| x +#-----| -> x + +# 19| if ... +#-----| -> self + +# 19| ... < ... +#-----| false -> if ... +#-----| true -> x + +# 19| x +#-----| -> 0 + +# 19| 0 +#-----| -> ... < ... + +# 19| then ... +#-----| -> if ... + +# 20| ... = ... +#-----| -> x + +# 20| x +#-----| -> x + +# 20| - ... +#-----| -> ... = ... + +# 20| x +#-----| -> - ... + +# 21| if ... +#-----| -> then ... + +# 21| ... > ... +#-----| false -> if ... +#-----| true -> x + +# 21| x +#-----| -> 10 + +# 21| 10 +#-----| -> ... > ... + +# 21| then ... +#-----| -> if ... + +# 22| ... = ... +#-----| -> then ... + +# 22| x +#-----| -> x + +# 22| ... - ... +#-----| -> ... = ... + +# 22| x +#-----| -> 1 + +# 22| 1 +#-----| -> ... - ... + +# 25| call to puts +#-----| -> exit m3 (normal) + +# 25| self +#-----| -> x + +# 25| x +#-----| -> call to puts + +# 28| enter m4 +#-----| -> b1 + +# 28| m4 +#-----| -> m5 + +# 28| exit m4 + +# 28| exit m4 (normal) +#-----| -> exit m4 + +# 28| b1 +#-----| -> b2 + +# 28| b2 +#-----| -> b3 + +# 28| b3 +#-----| -> b1 + +# 29| return +#-----| return -> exit m4 (normal) + +# 29| [false] ( ... ) +#-----| false -> "!b2 || !b3" + +# 29| [true] ( ... ) +#-----| true -> "b2 || b3" + +# 29| ... ? ... : ... +#-----| -> return + +# 29| [false] ... ? ... : ... +#-----| false -> [false] ( ... ) + +# 29| [true] ... ? ... : ... +#-----| true -> [true] ( ... ) + +# 29| b1 +#-----| true -> b2 +#-----| false -> b3 + +# 29| b2 +#-----| false -> [false] ... ? ... : ... +#-----| true -> [true] ... ? ... : ... + +# 29| b3 +#-----| false -> [false] ... ? ... : ... +#-----| true -> [true] ... ? ... : ... + +# 29| "b2 || b3" +#-----| -> ... ? ... : ... + +# 29| "!b2 || !b3" +#-----| -> ... ? ... : ... + +# 32| enter m5 +#-----| -> b1 + +# 32| m5 +#-----| -> 1 + +# 32| exit m5 + +# 32| exit m5 (normal) +#-----| -> exit m5 + +# 32| b1 +#-----| -> b2 + +# 32| b2 +#-----| -> b3 + +# 32| b3 +#-----| -> b4 + +# 32| b4 +#-----| -> b5 + +# 32| b5 +#-----| -> b1 + +# 33| if ... +#-----| -> exit m5 (normal) + +# 33| [false] ( ... ) +#-----| false -> "!b2 || !b4 || !b5" + +# 33| [true] ( ... ) +#-----| true -> "b2 || b4 || b5" + +# 33| [false] if ... +#-----| false -> [false] ( ... ) + +# 33| [true] if ... +#-----| true -> [true] ( ... ) + +# 33| b1 +#-----| true -> b2 +#-----| false -> b3 + +# 33| [false] then ... +#-----| false -> [false] if ... + +# 33| [true] then ... +#-----| true -> [true] if ... + +# 33| b2 +#-----| false -> [false] then ... +#-----| true -> [true] then ... + +# 33| [false] elsif ... +#-----| false -> [false] if ... + +# 33| [true] elsif ... +#-----| true -> [true] if ... + +# 33| b3 +#-----| true -> b4 +#-----| false -> b5 + +# 33| [false] then ... +#-----| false -> [false] elsif ... + +# 33| [true] then ... +#-----| true -> [true] elsif ... + +# 33| b4 +#-----| false -> [false] then ... +#-----| true -> [true] then ... + +# 33| [false] else ... +#-----| false -> [false] elsif ... + +# 33| [true] else ... +#-----| true -> [true] elsif ... + +# 33| b5 +#-----| false -> [false] else ... +#-----| true -> [true] else ... + +# 33| then ... +#-----| -> if ... + +# 33| "b2 || b4 || b5" +#-----| -> then ... + +# 33| else ... +#-----| -> if ... + +# 33| "!b2 || !b4 || !b5" +#-----| -> else ... + +# 36| enter conditional_method_def +#-----| -> self + +# 36| conditional_method_def +#-----| -> ... unless ... + +# 36| ... unless ... +#-----| -> constant_condition + +# 36| exit conditional_method_def + +# 36| exit conditional_method_def (normal) +#-----| -> exit conditional_method_def + +# 37| call to puts +#-----| -> exit conditional_method_def (normal) + +# 37| self +#-----| -> "bla" + +# 37| "bla" +#-----| -> call to puts + +# 38| ... == ... +#-----| false -> conditional_method_def +#-----| true -> ... unless ... + +# 38| 1 +#-----| -> 2 + +# 38| 2 +#-----| -> ... == ... + +# 40| enter constant_condition +#-----| -> true + +# 40| constant_condition +#-----| -> empty_else + +# 40| exit constant_condition + +# 40| exit constant_condition (normal) +#-----| -> exit constant_condition + +# 41| if ... +#-----| -> exit constant_condition (normal) + +# 41| [false] ! ... +#-----| false -> if ... + +# 41| true +#-----| true -> [false] ! ... + +# 46| enter empty_else +#-----| -> b + +# 46| empty_else +#-----| -> exit ifs.rb (normal) + +# 46| exit empty_else + +# 46| exit empty_else (normal) +#-----| -> exit empty_else + +# 46| b +#-----| -> b + +# 47| if ... +#-----| -> self + +# 47| b +#-----| true -> self + +# 47| then ... +#-----| -> if ... + +# 48| call to puts +#-----| -> then ... + +# 48| self +#-----| -> "true" + +# 48| "true" +#-----| -> call to puts + +# 51| call to puts +#-----| -> exit empty_else (normal) + +# 51| self +#-----| -> "done" + +# 51| "done" +#-----| -> call to puts + +loops.rb: +# 1| enter m1 +#-----| -> x + +# 1| enter loops.rb +#-----| -> m1 + +# 1| m1 +#-----| -> m2 + +# 1| exit m1 + +# 1| exit loops.rb + +# 1| exit m1 (normal) +#-----| -> exit m1 + +# 1| exit loops.rb (normal) +#-----| -> exit loops.rb + +# 1| x +#-----| -> x + +# 2| while ... +#-----| -> exit m1 (normal) + +# 2| ... >= ... +#-----| false -> while ... +#-----| true -> self + +# 2| x +#-----| -> 0 + +# 2| 0 +#-----| -> ... >= ... + +# 2| do ... +#-----| -> x + +# 3| call to puts +#-----| -> x + +# 3| self +#-----| -> x + +# 3| x +#-----| -> call to puts + +# 4| x +#-----| -> x + +# 4| ... = ... +#-----| -> do ... + +# 4| x +#-----| -> 1 + +# 4| ... - ... +#-----| -> ... = ... + +# 4| 1 +#-----| -> ... - ... + +# 8| enter m2 +#-----| -> x + +# 8| m2 +#-----| -> m3 + +# 8| exit m2 + +# 8| exit m2 (normal) +#-----| -> exit m2 + +# 8| x +#-----| -> x + +# 9| while ... +#-----| -> self + +# 9| ... >= ... +#-----| false -> while ... +#-----| true -> self + +# 9| x +#-----| -> 0 + +# 9| 0 +#-----| -> ... >= ... + +# 9| do ... +#-----| -> x + +# 10| call to puts +#-----| -> x + +# 10| self +#-----| -> x + +# 10| x +#-----| -> call to puts + +# 11| x +#-----| -> x + +# 11| ... = ... +#-----| -> x + +# 11| x +#-----| -> 1 + +# 11| ... - ... +#-----| -> ... = ... + +# 11| 1 +#-----| -> ... - ... + +# 12| if ... +#-----| -> self + +# 12| ... > ... +#-----| true -> break +#-----| false -> x + +# 12| x +#-----| -> 100 + +# 12| 100 +#-----| -> ... > ... + +# 13| break +#-----| break -> while ... + +# 14| elsif ... +#-----| -> if ... + +# 14| ... > ... +#-----| true -> next +#-----| false -> x + +# 14| x +#-----| -> 50 + +# 14| 50 +#-----| -> ... > ... + +# 15| next +#-----| next -> x + +# 16| elsif ... +#-----| -> elsif ... + +# 16| ... > ... +#-----| false -> elsif ... +#-----| true -> redo + +# 16| x +#-----| -> 10 + +# 16| 10 +#-----| -> ... > ... + +# 17| redo +#-----| redo -> self + +# 19| call to puts +#-----| -> do ... + +# 19| self +#-----| -> "Iter" + +# 19| "Iter" +#-----| -> call to puts + +# 21| call to puts +#-----| -> exit m2 (normal) + +# 21| self +#-----| -> "Done" + +# 21| "Done" +#-----| -> call to puts + +# 24| enter m3 +#-----| -> Array + +# 24| m3 +#-----| -> m4 + +# 24| exit m3 + +# 24| exit m3 (normal) +#-----| -> exit m3 + +# 25| call to each +#-----| -> exit m3 (normal) + +# 25| call to [] +#-----| -> do ... end + +# 25| Array +#-----| -> 1 + +# 25| 1 +#-----| -> 2 + +# 25| 2 +#-----| -> 3 + +# 25| 3 +#-----| -> call to [] + +# 25| enter do ... end +#-----| -> x + +# 25| do ... end +#-----| -> call to each + +# 25| exit do ... end + +# 25| exit do ... end (normal) +#-----| -> exit do ... end + +# 25| x +#-----| -> self + +# 26| call to puts +#-----| -> exit do ... end (normal) + +# 26| self +#-----| -> x + +# 26| x +#-----| -> call to puts + +# 30| enter m4 +#-----| -> x + +# 30| m4 +#-----| -> exit loops.rb (normal) + +# 30| exit m4 + +# 30| exit m4 (normal) +#-----| -> exit m4 + +# 30| x +#-----| -> y + +# 30| y +#-----| -> x + +# 31| while ... +#-----| -> exit m4 (normal) + +# 31| ... < ... +#-----| false -> while ... + +# 31| x +#-----| -> y + +# 31| y +#-----| -> ... < ... + +raise.rb: +# 1| enter raise.rb +#-----| -> Exception + +# 1| ExceptionA +#-----| -> Exception + +# 1| exit raise.rb + +# 1| exit raise.rb (normal) +#-----| -> exit raise.rb + +# 1| Exception +#-----| -> ExceptionA + +# 4| ExceptionB +#-----| -> m1 + +# 4| Exception +#-----| -> ExceptionB + +# 7| enter m1 +#-----| -> x + +# 7| m1 +#-----| -> m2 + +# 7| exit m1 + +# 7| exit m1 (abnormal) +#-----| -> exit m1 + +# 7| exit m1 (normal) +#-----| -> exit m1 + +# 7| x +#-----| -> x + +# 8| if ... +#-----| -> self + +# 8| ... > ... +#-----| false -> if ... +#-----| true -> self + +# 8| x +#-----| -> 2 + +# 8| 2 +#-----| -> ... > ... + +# 9| call to raise +#-----| raise -> exit m1 (abnormal) + +# 9| self +#-----| -> "x > 2" + +# 9| "x > 2" +#-----| -> call to raise + +# 11| call to puts +#-----| -> exit m1 (normal) + +# 11| self +#-----| -> "x <= 2" + +# 11| "x <= 2" +#-----| -> call to puts + +# 14| enter m2 +#-----| -> b + +# 14| m2 +#-----| -> m3 + +# 14| exit m2 + +# 14| exit m2 (abnormal) +#-----| -> exit m2 + +# 14| exit m2 (normal) +#-----| -> exit m2 + +# 14| b +#-----| -> b + +# 16| if ... +#-----| -> self + +# 16| b +#-----| false -> if ... +#-----| true -> self + +# 17| call to raise +#-----| raise -> rescue ... + +# 17| self +#-----| -> ExceptionA + +# 17| ExceptionA +#-----| -> call to raise + +# 19| rescue ... +#-----| -> ExceptionA + +# 19| ExceptionA +#-----| match -> self +#-----| raise -> exit m2 (abnormal) + +# 19| then ... +#-----| -> self + +# 20| call to puts +#-----| -> then ... + +# 20| self +#-----| -> "Rescued" + +# 20| "Rescued" +#-----| -> call to puts + +# 22| call to puts +#-----| -> exit m2 (normal) + +# 22| self +#-----| -> "End m2" + +# 22| "End m2" +#-----| -> call to puts + +# 25| enter m3 +#-----| -> b + +# 25| m3 +#-----| -> m4 + +# 25| exit m3 + +# 25| exit m3 (normal) +#-----| -> exit m3 + +# 25| b +#-----| -> b + +# 27| if ... +#-----| -> self + +# 27| b +#-----| false -> if ... +#-----| true -> self + +# 28| call to raise +#-----| raise -> rescue ... + +# 28| self +#-----| -> ExceptionA + +# 28| ExceptionA +#-----| -> call to raise + +# 30| rescue ... +#-----| -> self + +# 30| then ... +#-----| -> self + +# 31| call to puts +#-----| -> then ... + +# 31| self +#-----| -> "Rescued" + +# 31| "Rescued" +#-----| -> call to puts + +# 33| call to puts +#-----| -> exit m3 (normal) + +# 33| self +#-----| -> "End m3" + +# 33| "End m3" +#-----| -> call to puts + +# 36| enter m4 +#-----| -> b + +# 36| m4 +#-----| -> m5 + +# 36| exit m4 + +# 36| exit m4 (normal) +#-----| -> exit m4 + +# 36| b +#-----| -> b + +# 38| if ... +#-----| -> self + +# 38| b +#-----| false -> if ... +#-----| true -> self + +# 39| call to raise +#-----| raise -> rescue ... + +# 39| self +#-----| -> ExceptionA + +# 39| ExceptionA +#-----| -> call to raise + +# 41| rescue ... +#-----| -> e + +# 41| e +#-----| -> self + +# 41| then ... +#-----| -> self + +# 42| call to puts +#-----| -> then ... + +# 42| self +#-----| -> "Rescued {e}" + +# 42| "Rescued {e}" +#-----| -> call to puts + +# 44| call to puts +#-----| -> exit m4 (normal) + +# 44| self +#-----| -> "End m4" + +# 44| "End m4" +#-----| -> call to puts + +# 47| enter m5 +#-----| -> b + +# 47| m5 +#-----| -> m6 + +# 47| exit m5 + +# 47| exit m5 (normal) +#-----| -> exit m5 + +# 47| b +#-----| -> b + +# 49| if ... +#-----| -> self + +# 49| b +#-----| false -> if ... +#-----| true -> self + +# 50| call to raise +#-----| raise -> rescue ... + +# 50| self +#-----| -> ExceptionA + +# 50| ExceptionA +#-----| -> call to raise + +# 52| rescue ... +#-----| -> e + +# 52| e +#-----| -> self + +# 54| call to puts +#-----| -> exit m5 (normal) + +# 54| self +#-----| -> "End m5" + +# 54| "End m5" +#-----| -> call to puts + +# 57| enter m6 +#-----| -> b + +# 57| m6 +#-----| -> m7 + +# 57| exit m6 + +# 57| exit m6 (abnormal) +#-----| -> exit m6 + +# 57| exit m6 (normal) +#-----| -> exit m6 + +# 57| b +#-----| -> b + +# 59| if ... +#-----| -> self + +# 59| b +#-----| false -> if ... +#-----| true -> self + +# 60| call to raise +#-----| raise -> rescue ... + +# 60| self +#-----| -> ExceptionA + +# 60| ExceptionA +#-----| -> call to raise + +# 62| rescue ... +#-----| -> ExceptionA + +# 62| ExceptionA +#-----| no-match -> ExceptionB +#-----| match -> e + +# 62| ExceptionB +#-----| match -> e +#-----| raise -> exit m6 (abnormal) + +# 62| e +#-----| -> self + +# 62| then ... +#-----| -> self + +# 63| call to puts +#-----| -> then ... + +# 63| self +#-----| -> "Rescued {e}" + +# 63| "Rescued {e}" +#-----| -> call to puts + +# 65| call to puts +#-----| -> exit m6 (normal) + +# 65| self +#-----| -> "End m6" + +# 65| "End m6" +#-----| -> call to puts + +# 68| enter m7 +#-----| -> x + +# 68| m7 +#-----| -> m8 + +# 68| exit m7 + +# 68| exit m7 (abnormal) +#-----| -> exit m7 + +# 68| exit m7 (normal) +#-----| -> exit m7 + +# 68| x +#-----| -> x + +# 69| if ... +#-----| -> self + +# 69| ... > ... +#-----| false -> x +#-----| true -> self +#-----| raise -> [ensure: raise] self + +# 69| x +#-----| -> 2 + +# 69| 2 +#-----| -> ... > ... + +# 70| call to raise +#-----| raise -> [ensure: raise] self + +# 70| self +#-----| -> "x > 2" + +# 70| "x > 2" +#-----| -> call to raise + +# 71| elsif ... +#-----| -> if ... + +# 71| ... < ... +#-----| false -> elsif ... +#-----| true -> "x < 0" +#-----| raise -> [ensure: raise] self + +# 71| x +#-----| -> 0 + +# 71| 0 +#-----| -> ... < ... + +# 72| return +#-----| return -> [ensure: return] self + +# 72| "x < 0" +#-----| -> return + +# 74| call to puts +#-----| -> self +#-----| raise -> [ensure: raise] self + +# 74| self +#-----| -> "0 <= x <= 2" + +# 74| "0 <= x <= 2" +#-----| -> call to puts + +# 75| ensure ... +#-----| -> exit m7 (normal) + +# 75| [ensure: raise] ensure ... +#-----| raise -> exit m7 (abnormal) + +# 75| [ensure: return] ensure ... +#-----| return -> exit m7 (normal) + +# 76| call to puts +#-----| -> ensure ... + +# 76| [ensure: raise] call to puts +#-----| -> [ensure: raise] ensure ... + +# 76| [ensure: return] call to puts +#-----| -> [ensure: return] ensure ... + +# 76| self +#-----| -> "ensure" + +# 76| [ensure: raise] self +#-----| -> [ensure: raise] "ensure" + +# 76| [ensure: return] self +#-----| -> [ensure: return] "ensure" + +# 76| "ensure" +#-----| -> call to puts + +# 76| [ensure: raise] "ensure" +#-----| -> [ensure: raise] call to puts + +# 76| [ensure: return] "ensure" +#-----| -> [ensure: return] call to puts + +# 79| enter m8 +#-----| -> x + +# 79| m8 +#-----| -> m9 + +# 79| exit m8 + +# 79| exit m8 (abnormal) +#-----| -> exit m8 + +# 79| exit m8 (normal) +#-----| -> exit m8 + +# 79| x +#-----| -> self + +# 80| call to puts +#-----| -> x + +# 80| self +#-----| -> "Begin m8" + +# 80| "Begin m8" +#-----| -> call to puts + +# 82| if ... +#-----| -> self + +# 82| ... > ... +#-----| false -> x +#-----| true -> self +#-----| raise -> [ensure: raise] self + +# 82| x +#-----| -> 2 + +# 82| 2 +#-----| -> ... > ... + +# 83| call to raise +#-----| raise -> [ensure: raise] self + +# 83| self +#-----| -> "x > 2" + +# 83| "x > 2" +#-----| -> call to raise + +# 84| elsif ... +#-----| -> if ... + +# 84| ... < ... +#-----| false -> elsif ... +#-----| true -> "x < 0" +#-----| raise -> [ensure: raise] self + +# 84| x +#-----| -> 0 + +# 84| 0 +#-----| -> ... < ... + +# 85| return +#-----| return -> [ensure: return] self + +# 85| "x < 0" +#-----| -> return + +# 87| call to puts +#-----| -> self +#-----| raise -> [ensure: raise] self + +# 87| self +#-----| -> "0 <= x <= 2" + +# 87| "0 <= x <= 2" +#-----| -> call to puts + +# 88| ensure ... +#-----| -> self + +# 88| [ensure: raise] ensure ... +#-----| raise -> exit m8 (abnormal) + +# 88| [ensure: return] ensure ... +#-----| return -> exit m8 (normal) + +# 89| call to puts +#-----| -> ensure ... + +# 89| [ensure: raise] call to puts +#-----| -> [ensure: raise] ensure ... + +# 89| [ensure: return] call to puts +#-----| -> [ensure: return] ensure ... + +# 89| self +#-----| -> "ensure" + +# 89| [ensure: raise] self +#-----| -> [ensure: raise] "ensure" + +# 89| [ensure: return] self +#-----| -> [ensure: return] "ensure" + +# 89| "ensure" +#-----| -> call to puts + +# 89| [ensure: raise] "ensure" +#-----| -> [ensure: raise] call to puts + +# 89| [ensure: return] "ensure" +#-----| -> [ensure: return] call to puts + +# 91| call to puts +#-----| -> exit m8 (normal) + +# 91| self +#-----| -> "End m8" + +# 91| "End m8" +#-----| -> call to puts + +# 94| enter m9 +#-----| -> x + +# 94| m9 +#-----| -> m10 + +# 94| exit m9 + +# 94| exit m9 (abnormal) +#-----| -> exit m9 + +# 94| exit m9 (normal) +#-----| -> exit m9 + +# 94| x +#-----| -> b1 + +# 94| b1 +#-----| -> b2 + +# 94| b2 +#-----| -> self + +# 95| call to puts +#-----| -> x +#-----| raise -> [ensure: raise] self + +# 95| self +#-----| -> "Begin m9" + +# 95| "Begin m9" +#-----| -> call to puts + +# 97| if ... +#-----| -> self + +# 97| ... > ... +#-----| false -> x +#-----| true -> self +#-----| raise -> [ensure: raise] self + +# 97| x +#-----| -> 2 + +# 97| 2 +#-----| -> ... > ... + +# 98| call to raise +#-----| raise -> [ensure: raise] self + +# 98| self +#-----| -> "x > 2" + +# 98| "x > 2" +#-----| -> call to raise + +# 99| elsif ... +#-----| -> if ... + +# 99| ... < ... +#-----| false -> elsif ... +#-----| true -> "x < 0" +#-----| raise -> [ensure: raise] self + +# 99| x +#-----| -> 0 + +# 99| 0 +#-----| -> ... < ... + +# 100| return +#-----| return -> [ensure: return] self + +# 100| "x < 0" +#-----| -> return + +# 102| call to puts +#-----| -> self +#-----| raise -> [ensure: raise] self + +# 102| self +#-----| -> "0 <= x <= 2" + +# 102| "0 <= x <= 2" +#-----| -> call to puts + +# 103| ensure ... +#-----| -> self + +# 103| [ensure: raise] ensure ... +#-----| raise -> [ensure: raise] self + +# 103| [ensure: return] ensure ... +#-----| return -> [ensure: return] self + +# 104| call to puts +#-----| -> b1 +#-----| raise -> [ensure: raise] self + +# 104| [ensure: raise] call to puts +#-----| -> [ensure: raise] b1 +#-----| raise -> [ensure: raise] self + +# 104| [ensure: return] call to puts +#-----| -> [ensure: return] b1 +#-----| raise -> [ensure: raise] self + +# 104| self +#-----| -> "outer ensure" + +# 104| [ensure: raise] self +#-----| -> [ensure: raise] "outer ensure" + +# 104| [ensure: return] self +#-----| -> [ensure: return] "outer ensure" + +# 104| "outer ensure" +#-----| -> call to puts + +# 104| [ensure: raise] "outer ensure" +#-----| -> [ensure: raise] call to puts + +# 104| [ensure: return] "outer ensure" +#-----| -> [ensure: return] call to puts + +# 106| if ... +#-----| -> self + +# 106| [ensure: raise] if ... +#-----| -> [ensure: raise] self + +# 106| [ensure: return] if ... +#-----| -> [ensure: return] self + +# 106| b1 +#-----| false -> if ... +#-----| true -> self + +# 106| [ensure: raise] b1 +#-----| false -> [ensure: raise] if ... +#-----| true -> [ensure: raise] self + +# 106| [ensure: return] b1 +#-----| false -> [ensure: return] if ... +#-----| true -> [ensure: return] self + +# 107| call to raise +#-----| raise -> [ensure(1): raise] self + +# 107| [ensure: raise] call to raise +#-----| raise -> [ensure: raise, ensure(1): raise] self + +# 107| [ensure: return] call to raise +#-----| raise -> [ensure: return, ensure(1): raise] self + +# 107| self +#-----| -> "b1 is true" + +# 107| [ensure: raise] self +#-----| -> [ensure: raise] "b1 is true" + +# 107| [ensure: return] self +#-----| -> [ensure: return] "b1 is true" + +# 107| "b1 is true" +#-----| -> call to raise + +# 107| [ensure: raise] "b1 is true" +#-----| -> [ensure: raise] call to raise + +# 107| [ensure: return] "b1 is true" +#-----| -> [ensure: return] call to raise + +# 109| ensure ... +#-----| -> ensure ... + +# 109| [ensure(1): raise] ensure ... +#-----| raise -> [ensure: raise] self + +# 109| [ensure: raise] ensure ... +#-----| -> [ensure: raise] ensure ... + +# 109| [ensure: raise, ensure(1): raise] ensure ... +#-----| raise -> [ensure: raise] self + +# 109| [ensure: return] ensure ... +#-----| -> [ensure: return] ensure ... + +# 109| [ensure: return, ensure(1): raise] ensure ... +#-----| raise -> [ensure: raise] self + +# 110| call to puts +#-----| -> ensure ... +#-----| raise -> [ensure: raise] self + +# 110| [ensure(1): raise] call to puts +#-----| -> [ensure(1): raise] ensure ... +#-----| raise -> [ensure: raise] self + +# 110| [ensure: raise] call to puts +#-----| -> [ensure: raise] ensure ... +#-----| raise -> [ensure: raise] self + +# 110| [ensure: raise, ensure(1): raise] call to puts +#-----| -> [ensure: raise, ensure(1): raise] ensure ... +#-----| raise -> [ensure: raise] self + +# 110| [ensure: return] call to puts +#-----| -> [ensure: return] ensure ... +#-----| raise -> [ensure: raise] self + +# 110| [ensure: return, ensure(1): raise] call to puts +#-----| -> [ensure: return, ensure(1): raise] ensure ... +#-----| raise -> [ensure: raise] self + +# 110| self +#-----| -> "inner ensure" + +# 110| [ensure(1): raise] self +#-----| -> [ensure(1): raise] "inner ensure" + +# 110| [ensure: raise] self +#-----| -> [ensure: raise] "inner ensure" + +# 110| [ensure: raise, ensure(1): raise] self +#-----| -> [ensure: raise, ensure(1): raise] "inner ensure" + +# 110| [ensure: return] self +#-----| -> [ensure: return] "inner ensure" + +# 110| [ensure: return, ensure(1): raise] self +#-----| -> [ensure: return, ensure(1): raise] "inner ensure" + +# 110| "inner ensure" +#-----| -> call to puts + +# 110| [ensure(1): raise] "inner ensure" +#-----| -> [ensure(1): raise] call to puts + +# 110| [ensure: raise] "inner ensure" +#-----| -> [ensure: raise] call to puts + +# 110| [ensure: raise, ensure(1): raise] "inner ensure" +#-----| -> [ensure: raise, ensure(1): raise] call to puts + +# 110| [ensure: return] "inner ensure" +#-----| -> [ensure: return] call to puts + +# 110| [ensure: return, ensure(1): raise] "inner ensure" +#-----| -> [ensure: return, ensure(1): raise] call to puts + +# 113| call to puts +#-----| -> self +#-----| raise -> [ensure: raise] self + +# 113| self +#-----| -> "End m9" + +# 113| "End m9" +#-----| -> call to puts + +# 114| ensure ... +#-----| -> exit m9 (normal) + +# 114| [ensure: raise] ensure ... +#-----| raise -> exit m9 (abnormal) + +# 114| [ensure: return] ensure ... +#-----| return -> exit m9 (normal) + +# 115| call to puts +#-----| -> b2 + +# 115| [ensure: raise] call to puts +#-----| -> [ensure: raise] b2 + +# 115| [ensure: return] call to puts +#-----| -> [ensure: return] b2 + +# 115| self +#-----| -> "method ensure" + +# 115| [ensure: raise] self +#-----| -> [ensure: raise] "method ensure" + +# 115| [ensure: return] self +#-----| -> [ensure: return] "method ensure" + +# 115| "method ensure" +#-----| -> call to puts + +# 115| [ensure: raise] "method ensure" +#-----| -> [ensure: raise] call to puts + +# 115| [ensure: return] "method ensure" +#-----| -> [ensure: return] call to puts + +# 116| if ... +#-----| -> ensure ... + +# 116| [ensure: raise] if ... +#-----| -> [ensure: raise] ensure ... + +# 116| [ensure: return] if ... +#-----| -> [ensure: return] ensure ... + +# 116| b2 +#-----| false -> if ... +#-----| true -> self + +# 116| [ensure: raise] b2 +#-----| false -> [ensure: raise] if ... +#-----| true -> [ensure: raise] self + +# 116| [ensure: return] b2 +#-----| false -> [ensure: return] if ... +#-----| true -> [ensure: return] self + +# 117| call to raise +#-----| raise -> exit m9 (abnormal) + +# 117| [ensure: raise] call to raise +#-----| raise -> exit m9 (abnormal) + +# 117| [ensure: return] call to raise +#-----| raise -> exit m9 (abnormal) + +# 117| self +#-----| -> "b2 is true" + +# 117| [ensure: raise] self +#-----| -> [ensure: raise] "b2 is true" + +# 117| [ensure: return] self +#-----| -> [ensure: return] "b2 is true" + +# 117| "b2 is true" +#-----| -> call to raise + +# 117| [ensure: raise] "b2 is true" +#-----| -> [ensure: raise] call to raise + +# 117| [ensure: return] "b2 is true" +#-----| -> [ensure: return] call to raise + +# 121| enter m10 +#-----| -> p + +# 121| m10 +#-----| -> m11 + +# 121| exit m10 + +# 121| exit m10 (abnormal) +#-----| -> exit m10 + +# 121| exit m10 (normal) +#-----| -> exit m10 + +# 121| p +#-----| no-match -> self +#-----| match -> self + +# 121| call to raise +#-----| raise -> exit m10 (abnormal) + +# 121| self +#-----| -> "Exception" + +# 121| "Exception" +#-----| -> call to raise + +# 124| ensure ... +#-----| -> exit m10 (normal) + +# 125| call to puts +#-----| -> ensure ... + +# 125| self +#-----| -> "Will not get executed if p is..." + +# 125| "Will not get executed if p is..." +#-----| -> call to puts + +# 128| enter m11 +#-----| -> b + +# 128| m11 +#-----| -> m12 + +# 128| exit m11 + +# 128| exit m11 (abnormal) +#-----| -> exit m11 + +# 128| exit m11 (normal) +#-----| -> exit m11 + +# 128| b +#-----| -> b + +# 130| if ... +#-----| -> self + +# 130| b +#-----| false -> if ... +#-----| true -> self + +# 131| call to raise +#-----| raise -> rescue ... + +# 131| self +#-----| -> ExceptionA + +# 131| ExceptionA +#-----| -> call to raise + +# 133| rescue ... +#-----| -> ExceptionA + +# 133| ExceptionA +#-----| no-match -> rescue ... +#-----| match -> self + +# 134| rescue ... +#-----| -> ExceptionB + +# 134| ExceptionB +#-----| match -> self +#-----| raise -> [ensure: raise] self + +# 134| then ... +#-----| -> self + +# 135| call to puts +#-----| -> then ... + +# 135| self +#-----| -> "ExceptionB" + +# 135| "ExceptionB" +#-----| -> call to puts + +# 136| ensure ... +#-----| -> self + +# 136| [ensure: raise] ensure ... +#-----| raise -> exit m11 (abnormal) + +# 137| call to puts +#-----| -> ensure ... + +# 137| [ensure: raise] call to puts +#-----| -> [ensure: raise] ensure ... + +# 137| self +#-----| -> "Ensure" + +# 137| [ensure: raise] self +#-----| -> [ensure: raise] "Ensure" + +# 137| "Ensure" +#-----| -> call to puts + +# 137| [ensure: raise] "Ensure" +#-----| -> [ensure: raise] call to puts + +# 139| call to puts +#-----| -> exit m11 (normal) + +# 139| self +#-----| -> "End m11" + +# 139| "End m11" +#-----| -> call to puts + +# 142| enter m12 +#-----| -> b + +# 142| m12 +#-----| -> m13 + +# 142| exit m12 + +# 142| exit m12 (normal) +#-----| -> exit m12 + +# 142| b +#-----| -> b + +# 143| if ... +#-----| -> 3 + +# 143| b +#-----| false -> if ... +#-----| true -> self + +# 144| call to raise +#-----| raise -> [ensure: raise] 3 + +# 144| self +#-----| -> "" + +# 144| "" +#-----| -> call to raise + +# 147| return +#-----| return -> exit m12 (normal) + +# 147| [ensure: raise] return +#-----| return -> exit m12 (normal) + +# 147| 3 +#-----| -> return + +# 147| [ensure: raise] 3 +#-----| -> [ensure: raise] return + +# 150| m13 +#-----| -> m14 + +# 154| enter m14 +#-----| -> element + +# 154| m14 +#-----| -> m15 + +# 154| exit m14 + +# 154| exit m14 (normal) +#-----| -> exit m14 + +# 154| element +#-----| -> element + +# 155| call to each +#-----| -> exit m14 (normal) + +# 155| element +#-----| -> { ... } + +# 155| enter { ... } +#-----| -> elem + +# 155| { ... } +#-----| -> call to each + +# 155| exit { ... } + +# 155| exit { ... } (abnormal) +#-----| -> exit { ... } + +# 155| exit { ... } (normal) +#-----| -> exit { ... } + +# 155| elem +#-----| -> element + +# 155| ... if ... +#-----| -> exit { ... } (normal) + +# 155| call to raise +#-----| raise -> exit { ... } (abnormal) + +# 155| self +#-----| -> "" + +# 155| "" +#-----| -> call to raise + +# 155| call to nil? +#-----| false -> ... if ... +#-----| true -> self + +# 155| element +#-----| -> call to nil? + +# 158| enter m15 +#-----| -> self + +# 158| m15 +#-----| -> self + +# 158| exit m15 + +# 158| exit m15 (normal) +#-----| -> exit m15 + +# 159| call to foo +#-----| -> exit m15 (normal) + +# 159| self +#-----| -> do ... end + +# 159| enter do ... end +#-----| -> self + +# 159| do ... end +#-----| -> call to foo + +# 159| exit do ... end + +# 159| exit do ... end (normal) +#-----| -> exit do ... end + +# 160| call to bar +#-----| -> exit do ... end (normal) + +# 160| self +#-----| -> -> { ... } + +# 160| enter -> { ... } +#-----| -> x + +# 160| -> { ... } +#-----| -> call to bar + +# 160| exit -> { ... } + +# 160| exit -> { ... } (abnormal) +#-----| -> exit -> { ... } + +# 160| exit -> { ... } (normal) +#-----| -> exit -> { ... } + +# 160| x +#-----| -> x + +# 161| call to raise +#-----| raise -> exit -> { ... } (abnormal) + +# 161| ... unless ... +#-----| -> exit -> { ... } (normal) + +# 161| self +#-----| -> "" + +# 161| "" +#-----| -> call to raise + +# 161| x +#-----| true -> ... unless ... +#-----| false -> self + +# 166| C +#-----| -> exit raise.rb (normal) + +# 167| enter m +#-----| -> self + +# 167| m +#-----| -> C + +# 167| exit m + +# 167| exit m (abnormal) +#-----| -> exit m + +# 167| self +#-----| -> m + +# 168| call to raise +#-----| raise -> exit m (abnormal) + +# 168| self +#-----| -> "" + +# 168| "" +#-----| -> call to raise diff --git a/ruby/ql/test/library-tests/controlflow/graph/Cfg.ql b/ruby/ql/test/library-tests/controlflow/graph/Cfg.ql new file mode 100644 index 000000000000..b4a5bbe946f4 --- /dev/null +++ b/ruby/ql/test/library-tests/controlflow/graph/Cfg.ql @@ -0,0 +1,10 @@ +/** + * @kind graph + */ + +import codeql.ruby.CFG +import codeql.ruby.controlflow.internal.ControlFlowGraphImpl::TestOutput + +class MyRelevantNode extends RelevantNode { + MyRelevantNode() { exists(this) } +} diff --git a/ruby/ql/test/library-tests/controlflow/graph/break_ensure.rb b/ruby/ql/test/library-tests/controlflow/graph/break_ensure.rb new file mode 100644 index 000000000000..4ee587049f3c --- /dev/null +++ b/ruby/ql/test/library-tests/controlflow/graph/break_ensure.rb @@ -0,0 +1,56 @@ +def m1 elements + for element in elements do + if element > 0 then + break + end + end +ensure + if elements.nil? then + puts "elements nil" + end +end + +def m2 elements + for element in elements do + begin + if element > 0 then + break + end + ensure + if elements.nil? then + puts "elements nil" + end + end + end +end + +def m3 elements + begin + if elements.nil? then + return + end + ensure + for element in elements do + begin + if x > 0 then + break + end + end + end + end + puts "Done" +end + +def m4 elements + for element in elements do + begin + if element > 1 then + raise "" + end + ensure + if element > 0 then + break 10; + end + end + end +end diff --git a/ruby/ql/test/library-tests/controlflow/graph/case.rb b/ruby/ql/test/library-tests/controlflow/graph/case.rb new file mode 100644 index 000000000000..97de48a9d42a --- /dev/null +++ b/ruby/ql/test/library-tests/controlflow/graph/case.rb @@ -0,0 +1,6 @@ +def if_in_case + case x1 + when 1 then (if x2 then puts "x2" end) + when 2 then puts "2" + end +end diff --git a/ruby/ql/test/library-tests/controlflow/graph/cfg.html.erb b/ruby/ql/test/library-tests/controlflow/graph/cfg.html.erb new file mode 100644 index 000000000000..aea9d7d73e37 --- /dev/null +++ b/ruby/ql/test/library-tests/controlflow/graph/cfg.html.erb @@ -0,0 +1,34 @@ + + + + + <%= @title %> + <%= stylesheet_link_tag "application", :media => "all" %> + + + +
    +
    + <%= link_to "A", a, id: "a" %> + +
    +
    + +
    + <% collection.each do |key, value| %> +
    <%= value %>
    + <% end %> +
    + + \ No newline at end of file diff --git a/ruby/ql/test/library-tests/controlflow/graph/cfg.rb b/ruby/ql/test/library-tests/controlflow/graph/cfg.rb new file mode 100644 index 000000000000..aa43ec6c9aec --- /dev/null +++ b/ruby/ql/test/library-tests/controlflow/graph/cfg.rb @@ -0,0 +1,199 @@ +def bar; end + +alias foo bar + +b = 42 + +%I(one#{ b } another) # bare symbol + +%W(one#{ b } another) # bare string + +begin + puts 4 +end + +BEGIN { + puts "hello" +} + +END { + puts "world" +} + +41 + 1 + +2.times { |x| puts x } + +puts &:puts + +Proc.new { |&x| x.call } + +while true + break 1 +end + +if false + puts "impossible" +end + +self.puts 42 + +case 10 + when 1 then puts "one" + when 2, 3, 4 then puts "some" + else puts "many" +end + +case + when b == 1 then puts "one" + when b == 0, b > 1 then puts "some" +end + +chained = "a" "#{chained}" "string" + +character = ?\x40 + + +# this is a class +class Silly < Object + complex = 10-2i + conditional = b < 10 ? "hello" : "bye" + C = "constant" + (x, (y, z)) = [1, [2, 3]] + def pattern( (a,b) ) + puts a + puts b + end + items = [1, 2, 3] + puts items[2] + def print() + puts "silly" + end +end + +x = 42 +if x < 0 then 0 elsif x > 10 then 10 else x end + +begin + ; # empty statement +rescue Exception, Exception2 => e + puts "oops" + retry +else + puts "ok" +ensure + puts "end" +end + +escape = "\u1234#{x}\n" + +for x in [1.4, 2.5, 3.4e5] do + if x > 3 then next end + puts x +end + +$global = 42 + +map1 = { 'a' => 'b', 'c': 'd', e: 'f' } +map2 = { **map1, 'x' => 'y', **map1} + + +def parameters(value = 42, key:, **kwargs) + puts value + return kwargs[key] +end + +type = "healthy" +table = "food" +puts (< 10 + +class C + @field = 42 + @@static_field = 10 +end + +swap = ->((x, y)) { [y, x] } + +module M + nothing = nil + some = 2 + some += 10 + last = (2; 4; 7) + range = 0..9 + half = 1/3r + 1/6r + regex = /hello\s+[#{range}]/ + Constant = 5 +end + +class EmptyClass; end +module EmptyModule; end + +1/0 rescue puts "div by zero" + +(*init, last) = 1, 2, 3 + +M::Constant +M.itself::Constant + +class << Silly.itself + def setter=() end + def print() + puts "singleton" + puts super.print() + end +end + +silly = Silly.new +def silly.method(*x) + puts x +end + +def two_parameters (a,b) end + +two_parameters(*[1,2]) + +scriptfile = `cat "#{__FILE__}"` + +symbol = :hello + +delimited_symbol = :"goodbye-#{ 12 + 13 }" + +x = true +x = ! true +x = - 42 + +undef two_parameters + +unless x == 10 then puts "hi" else puts "bye" end + +puts "hi" unless x == 0 + +until x > 10 do x += 10; puts "hello" end + +i = 0 +(puts "hello"; i += 1) until i == 10 + +x = 0 +while x < 10 do + x += 1 + if x == 5 then redo end + puts x +end + +(puts "hello"; i -= 1) while i != 0 + +def run_block + yield 42 +end + +run_block { |x|puts x } + +__END__ + +Some ignored nonsense + diff --git a/ruby/ql/test/library-tests/controlflow/graph/desugar.rb b/ruby/ql/test/library-tests/controlflow/graph/desugar.rb new file mode 100644 index 000000000000..68b36508d33b --- /dev/null +++ b/ruby/ql/test/library-tests/controlflow/graph/desugar.rb @@ -0,0 +1,38 @@ +def m1 x + x += 1 +end + +def m2 x + x.foo.count = 1 +end + +def m3 x + x.foo[0] = 1 +end + +def m4 x + x.foo.count += 1 +end + +def m5 x, y + x.foo[0, y.bar, x.baz + 3] += 1 +end + +def m6 + x, *y, z.bar = [1, 2, 3, 4] +end + +def m7 + x, (y, z) = [1, [2, 3]] +end + +class X + @x = 1 + @x += 2 + + @@y = 3 + @@y /= 4 +end + +$global_var = 5 +$global_var *= 6 diff --git a/ruby/ql/test/library-tests/controlflow/graph/exit.rb b/ruby/ql/test/library-tests/controlflow/graph/exit.rb new file mode 100644 index 000000000000..b67fa9e1532a --- /dev/null +++ b/ruby/ql/test/library-tests/controlflow/graph/exit.rb @@ -0,0 +1,13 @@ +def m1 x + if x > 2 + exit 1 + end + puts "x <= 2" +end + +def m2 x + if x > 2 + abort "abort!" + end + puts "x <= 2" +end diff --git a/ruby/ql/test/library-tests/controlflow/graph/heredoc.rb b/ruby/ql/test/library-tests/controlflow/graph/heredoc.rb new file mode 100644 index 000000000000..09e1b771ea81 --- /dev/null +++ b/ruby/ql/test/library-tests/controlflow/graph/heredoc.rb @@ -0,0 +1,7 @@ +def double_heredoc + puts(< 2 + puts "x is greater than 2" + elsif x <= 2 and x > 0 and !(x == 5) + puts "x is 1" + else + puts "I can't guess the number" + end +end + +def m2 b + if b + return 0 + end + return 1 +end + +def m3 x + if x < 0 + x = -x + if x > 10 + x = x - 1 + end + end + puts x +end + +def m4 (b1, b2, b3) + return (b1 ? b2 : b3) ? "b2 || b3" : "!b2 || !b3" +end + +def m5 (b1, b2, b3, b4, b5) + if (if b1 then b2 elsif b3 then b4 else b5 end) then "b2 || b4 || b5" else "!b2 || !b4 || !b5" end +end + +def conditional_method_def() + puts "bla" +end unless 1 == 2 + +def constant_condition() + if !true + puts "Impossible" + end +end + +def empty_else b + if b then + puts "true" + else + end + puts "done" +end \ No newline at end of file diff --git a/ruby/ql/test/library-tests/controlflow/graph/loops.rb b/ruby/ql/test/library-tests/controlflow/graph/loops.rb new file mode 100644 index 000000000000..b3f1c60557ac --- /dev/null +++ b/ruby/ql/test/library-tests/controlflow/graph/loops.rb @@ -0,0 +1,33 @@ +def m1 x + while x >= 0 + puts x + x -= 1 + end +end + +def m2 x + while x >= 0 + puts x + x -= 1 + if x > 100 + break + elsif x > 50 + next + elsif x > 10 + redo + end + puts "Iter" + end + puts "Done" +end + +def m3 + [1,2,3].each do |x| + puts x + end +end + +def m4(x, y) + while x < y do + end +end diff --git a/ruby/ql/test/library-tests/controlflow/graph/raise.rb b/ruby/ql/test/library-tests/controlflow/graph/raise.rb new file mode 100644 index 000000000000..e5f0c0e50f5c --- /dev/null +++ b/ruby/ql/test/library-tests/controlflow/graph/raise.rb @@ -0,0 +1,170 @@ +class ExceptionA < Exception +end + +class ExceptionB < Exception +end + +def m1 x + if x > 2 + raise "x > 2" + end + puts "x <= 2" +end + +def m2 b + begin + if b + raise ExceptionA + end + rescue ExceptionA + puts "Rescued" + end + puts "End m2" +end + +def m3 b + begin + if b + raise ExceptionA + end + rescue + puts "Rescued" + end + puts "End m3" +end + +def m4 b + begin + if b + raise ExceptionA + end + rescue => e + puts "Rescued {e}" + end + puts "End m4" +end + +def m5 b + begin + if b + raise ExceptionA + end + rescue => e + end + puts "End m5" +end + +def m6 b + begin + if b + raise ExceptionA + end + rescue ExceptionA, ExceptionB => e + puts "Rescued {e}" + end + puts "End m6" +end + +def m7 x + if x > 2 + raise "x > 2" + elsif x < 0 + return "x < 0" + end + puts "0 <= x <= 2" +ensure + puts "ensure" +end + +def m8 x + puts "Begin m8" + begin + if x > 2 + raise "x > 2" + elsif x < 0 + return "x < 0" + end + puts "0 <= x <= 2" + ensure + puts "ensure" + end + puts "End m8" +end + +def m9(x, b1, b2) + puts "Begin m9" + begin + if x > 2 + raise "x > 2" + elsif x < 0 + return "x < 0" + end + puts "0 <= x <= 2" + ensure + puts "outer ensure" + begin + if b1 + raise "b1 is true" + end + ensure + puts "inner ensure" + end + end + puts "End m9" +ensure + puts "method ensure" + if b2 + raise "b2 is true" + end +end + +def m10(p = (raise "Exception")) +rescue + puts "Will not get executed if p is not supplied" +ensure + puts "Will not get executed if p is not supplied" +end + +def m11 b + begin + if b + raise ExceptionA + end + rescue ExceptionA + rescue ExceptionB + puts "ExceptionB" + ensure + puts "Ensure" + end + puts "End m11" +end + +def m12 b + if b + raise "" + end +ensure + return 3 +end + +def m13 +ensure +end + +def m14 element + element.each { |elem| raise "" if element.nil? } +end + +def m15 + foo do + bar ->(x) do + raise "" unless x + end + end +end + +class C + def self.m() + raise "" + end +end diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb b/ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb new file mode 100644 index 000000000000..752444ad2a7b --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb @@ -0,0 +1,31 @@ +MyModule #$ use=getMember("MyModule") +print MyModule.foo #$ use=getMember("MyModule").getReturn("foo") +Kernel.print(e) #$ use=getMember("Kernel").getReturn("print") +Object::Kernel #$ use=getMember("Kernel") +Object::Kernel.print(e) #$ use=getMember("Kernel").getReturn("print") +begin + print MyModule.bar #$ use=getMember("MyModule").getReturn("bar") + raise AttributeError #$ use=getMember("AttributeError") +rescue AttributeError => e #$ use=getMember("AttributeError") + Kernel.print(e) #$ use=getMember("Kernel").getReturn("print") +end +Unknown.new.run #$ use=getMember("Unknown").instance.getReturn("run") +Foo::Bar::Baz #$ use=getMember("Foo").getMember("Bar").getMember("Baz") + +Const = [1, 2, 3] #$ use=getMember("Array").getReturn("[]") +Const.each do |c| #$ use=getMember("Const").getReturn("each") + puts c +end + +foo = Foo #$ use=getMember("Foo") +foo::Bar::Baz #$ use=getMember("Foo").getMember("Bar").getMember("Baz") + +FooAlias = Foo #$ use=getMember("Foo") +FooAlias::Bar::Baz #$ use=getMember("Foo").getMember("Bar").getMember("Baz") + +module Outer + module Inner + end +end + +Outer::Inner.foo #$ use=getMember("Outer").getMember("Inner").getReturn("foo") diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/use.expected b/ruby/ql/test/library-tests/dataflow/api-graphs/use.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/use.ql b/ruby/ql/test/library-tests/dataflow/api-graphs/use.ql new file mode 100644 index 000000000000..0ca7454109ab --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/use.ql @@ -0,0 +1,35 @@ +import ruby +import codeql.ruby.DataFlow +import TestUtilities.InlineExpectationsTest +import codeql.ruby.ApiGraphs + +class ApiUseTest extends InlineExpectationsTest { + ApiUseTest() { this = "ApiUseTest" } + + override string getARelevantTag() { result = "use" } + + private predicate relevantNode(API::Node a, DataFlow::Node n, Location l) { + n = a.getAUse() and + l = n.getLocation() + } + + override predicate hasActualResult(Location location, string element, string tag, string value) { + exists(API::Node a, DataFlow::Node n | relevantNode(a, n, location) | + tag = "use" and + // Only report the longest path on this line: + value = + max(API::Node a2, Location l2, DataFlow::Node n2 | + relevantNode(a2, n2, l2) and + l2.getFile() = location.getFile() and + l2.getStartLine() = location.getStartLine() + | + a2.getPath() + order by + size(n2.asExpr().getExpr()), a2.getPath().length() desc, a2.getPath() desc + ) and + element = n.toString() + ) + } +} + +private int size(AstNode n) { not n instanceof StmtSequence and result = count(n.getAChild*()) } diff --git a/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.expected b/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.expected new file mode 100644 index 000000000000..e0940df3d033 --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.expected @@ -0,0 +1,5 @@ +| barrier-guards.rb:3:4:3:15 | ... == ... | barrier-guards.rb:4:5:4:7 | foo | barrier-guards.rb:3:4:3:6 | foo | true | +| barrier-guards.rb:9:4:9:24 | call to include? | barrier-guards.rb:10:5:10:7 | foo | barrier-guards.rb:9:21:9:23 | foo | true | +| barrier-guards.rb:15:4:15:15 | ... != ... | barrier-guards.rb:18:5:18:7 | foo | barrier-guards.rb:15:4:15:6 | foo | false | +| barrier-guards.rb:21:8:21:19 | ... == ... | barrier-guards.rb:24:5:24:7 | foo | barrier-guards.rb:21:8:21:10 | foo | true | +| barrier-guards.rb:27:8:27:19 | ... != ... | barrier-guards.rb:28:5:28:7 | foo | barrier-guards.rb:27:8:27:10 | foo | false | diff --git a/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.ql b/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.ql new file mode 100644 index 000000000000..d56f6986c8d9 --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.ql @@ -0,0 +1,7 @@ +import codeql.ruby.dataflow.internal.DataFlowPublic +import codeql.ruby.dataflow.BarrierGuards +import codeql.ruby.controlflow.CfgNodes + +from BarrierGuard g, boolean branch, ExprCfgNode expr +where g.checks(expr, branch) +select g, g.getAGuardedNode(), expr, branch diff --git a/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.rb b/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.rb new file mode 100644 index 000000000000..2a26bbb3ae0f --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.rb @@ -0,0 +1,33 @@ +foo = "foo" + +if foo == "foo" + foo +else + foo +end + +if ["foo"].include?(foo) + foo +else + foo +end + +if foo != "foo" + foo +else + foo +end + +unless foo == "foo" + foo +else + foo +end + +unless foo != "foo" + foo +else + foo +end + +foo diff --git a/ruby/ql/test/library-tests/dataflow/call-sensitivity/call-sensitivity.expected b/ruby/ql/test/library-tests/dataflow/call-sensitivity/call-sensitivity.expected new file mode 100644 index 000000000000..ed1dcf79a3c1 --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/call-sensitivity/call-sensitivity.expected @@ -0,0 +1,36 @@ +edges +| call_sensitivity.rb:7:13:7:13 | x : | call_sensitivity.rb:8:11:8:11 | x : | +| call_sensitivity.rb:8:11:8:11 | x : | call_sensitivity.rb:15:20:15:20 | x : | +| call_sensitivity.rb:15:9:15:15 | "taint" : | call_sensitivity.rb:7:13:7:13 | x : | +| call_sensitivity.rb:15:20:15:20 | x : | call_sensitivity.rb:15:28:15:28 | x | +| call_sensitivity.rb:17:27:17:27 | x : | call_sensitivity.rb:18:17:18:17 | x : | +| call_sensitivity.rb:17:27:17:27 | x : | call_sensitivity.rb:18:17:18:17 | x : | +| call_sensitivity.rb:18:17:18:17 | x : | call_sensitivity.rb:27:17:27:17 | x : | +| call_sensitivity.rb:18:17:18:17 | x : | call_sensitivity.rb:36:23:36:23 | x : | +| call_sensitivity.rb:27:17:27:17 | x : | call_sensitivity.rb:27:27:27:27 | x | +| call_sensitivity.rb:28:25:28:31 | "taint" : | call_sensitivity.rb:17:27:17:27 | x : | +| call_sensitivity.rb:36:23:36:23 | x : | call_sensitivity.rb:36:31:36:31 | x | +| call_sensitivity.rb:37:25:37:31 | "taint" : | call_sensitivity.rb:17:27:17:27 | x : | +nodes +| call_sensitivity.rb:5:6:5:12 | "taint" | semmle.label | "taint" | +| call_sensitivity.rb:7:13:7:13 | x : | semmle.label | x : | +| call_sensitivity.rb:8:11:8:11 | x : | semmle.label | x : | +| call_sensitivity.rb:15:9:15:15 | "taint" : | semmle.label | "taint" : | +| call_sensitivity.rb:15:20:15:20 | x : | semmle.label | x : | +| call_sensitivity.rb:15:28:15:28 | x | semmle.label | x | +| call_sensitivity.rb:17:27:17:27 | x : | semmle.label | x : | +| call_sensitivity.rb:17:27:17:27 | x : | semmle.label | x : | +| call_sensitivity.rb:18:17:18:17 | x : | semmle.label | x : | +| call_sensitivity.rb:18:17:18:17 | x : | semmle.label | x : | +| call_sensitivity.rb:27:17:27:17 | x : | semmle.label | x : | +| call_sensitivity.rb:27:27:27:27 | x | semmle.label | x | +| call_sensitivity.rb:28:25:28:31 | "taint" : | semmle.label | "taint" : | +| call_sensitivity.rb:36:23:36:23 | x : | semmle.label | x : | +| call_sensitivity.rb:36:31:36:31 | x | semmle.label | x | +| call_sensitivity.rb:37:25:37:31 | "taint" : | semmle.label | "taint" : | +subpaths +#select +| call_sensitivity.rb:5:6:5:12 | "taint" | call_sensitivity.rb:5:6:5:12 | "taint" | call_sensitivity.rb:5:6:5:12 | "taint" | $@ | call_sensitivity.rb:5:6:5:12 | "taint" | "taint" | +| call_sensitivity.rb:15:28:15:28 | x | call_sensitivity.rb:15:9:15:15 | "taint" : | call_sensitivity.rb:15:28:15:28 | x | $@ | call_sensitivity.rb:15:9:15:15 | "taint" : | "taint" : | +| call_sensitivity.rb:27:27:27:27 | x | call_sensitivity.rb:28:25:28:31 | "taint" : | call_sensitivity.rb:27:27:27:27 | x | $@ | call_sensitivity.rb:28:25:28:31 | "taint" : | "taint" : | +| call_sensitivity.rb:36:31:36:31 | x | call_sensitivity.rb:37:25:37:31 | "taint" : | call_sensitivity.rb:36:31:36:31 | x | $@ | call_sensitivity.rb:37:25:37:31 | "taint" : | "taint" : | diff --git a/ruby/ql/test/library-tests/dataflow/call-sensitivity/call-sensitivity.ql b/ruby/ql/test/library-tests/dataflow/call-sensitivity/call-sensitivity.ql new file mode 100644 index 000000000000..f6974ed0fca2 --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/call-sensitivity/call-sensitivity.ql @@ -0,0 +1,26 @@ +/** + * @kind path-problem + */ + +import ruby +import codeql.ruby.DataFlow +import DataFlow::PathGraph + +class Conf extends DataFlow::Configuration { + Conf() { this = "Conf" } + + override predicate isSource(DataFlow::Node src) { + src.asExpr().getExpr().(StringLiteral).getValueText() = "taint" + } + + override predicate isSink(DataFlow::Node sink) { + exists(MethodCall mc | + mc.getMethodName() = "sink" and + mc.getAnArgument() = sink.asExpr().getExpr() + ) + } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, Conf conf +where conf.hasFlowPath(source, sink) +select sink, source, sink, "$@", source, source.toString() diff --git a/ruby/ql/test/library-tests/dataflow/call-sensitivity/call_sensitivity.rb b/ruby/ql/test/library-tests/dataflow/call-sensitivity/call_sensitivity.rb new file mode 100644 index 000000000000..e6c7117d8597 --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/call-sensitivity/call_sensitivity.rb @@ -0,0 +1,38 @@ +def sink s + puts s +end + +sink "taint" + +def yielder x + yield x +end + +yielder "no taint" { |x| sink x } # no flow + +yielder "taint" { |x| puts x } # no flow + +yielder "taint" { |x| sink x } # flow + +def apply_lambda (lambda, x) + lambda.call(x) +end + +my_lambda = -> (x) { sink x } +apply_lambda(my_lambda, "no taint") # no flow + +my_lambda = -> (x) { puts x } +apply_lambda(my_lambda, "taint") # no flow + +my_lambda = -> (x) { sink x } +apply_lambda(my_lambda, "taint") # flow + +my_lambda = lambda { |x| sink x } +apply_lambda(my_lambda, "no taint") # no flow + +my_lambda = lambda { |x| puts x } +apply_lambda(my_lambda, "taint") # no flow + +my_lambda = lambda { |x| sink x } +apply_lambda(my_lambda, "taint") # flow + diff --git a/ruby/ql/test/library-tests/dataflow/local/DataflowStep.expected b/ruby/ql/test/library-tests/dataflow/local/DataflowStep.expected new file mode 100644 index 000000000000..a1d85c6f80be --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/local/DataflowStep.expected @@ -0,0 +1,58 @@ +| local_dataflow.rb:1:1:7:3 | self in foo | local_dataflow.rb:3:8:3:10 | self | +| local_dataflow.rb:1:9:1:9 | a | local_dataflow.rb:1:9:1:9 | a | +| local_dataflow.rb:1:9:1:9 | a | local_dataflow.rb:2:7:2:7 | a | +| local_dataflow.rb:2:3:2:7 | ... = ... | local_dataflow.rb:3:13:3:13 | b | +| local_dataflow.rb:2:7:2:7 | a | local_dataflow.rb:2:3:2:7 | ... = ... | +| local_dataflow.rb:2:7:2:7 | a | local_dataflow.rb:2:3:2:7 | ... = ... | +| local_dataflow.rb:2:7:2:7 | a | local_dataflow.rb:3:10:3:10 | a | +| local_dataflow.rb:3:7:3:14 | ( ... ) | local_dataflow.rb:3:3:3:14 | ... = ... | +| local_dataflow.rb:3:10:3:10 | [post] a | local_dataflow.rb:4:11:4:11 | a | +| local_dataflow.rb:3:10:3:10 | a | local_dataflow.rb:4:11:4:11 | a | +| local_dataflow.rb:3:13:3:13 | b | local_dataflow.rb:3:7:3:14 | ( ... ) | +| local_dataflow.rb:3:13:3:13 | b | local_dataflow.rb:6:13:6:13 | b | +| local_dataflow.rb:4:7:4:11 | ... = ... | local_dataflow.rb:4:3:4:11 | ... = ... | +| local_dataflow.rb:4:11:4:11 | a | local_dataflow.rb:4:7:4:11 | ... = ... | +| local_dataflow.rb:4:11:4:11 | a | local_dataflow.rb:5:12:5:12 | a | +| local_dataflow.rb:5:7:5:13 | ( ... ) | local_dataflow.rb:5:3:5:13 | ... = ... | +| local_dataflow.rb:5:8:5:12 | ... = ... | local_dataflow.rb:5:7:5:13 | ( ... ) | +| local_dataflow.rb:5:12:5:12 | a | local_dataflow.rb:5:8:5:12 | ... = ... | +| local_dataflow.rb:5:12:5:12 | a | local_dataflow.rb:6:8:6:8 | a | +| local_dataflow.rb:6:7:6:14 | ( ... ) | local_dataflow.rb:6:3:6:14 | ... = ... | +| local_dataflow.rb:6:8:6:13 | ... = ... | local_dataflow.rb:6:7:6:14 | ( ... ) | +| local_dataflow.rb:6:10:6:11 | ... + ... | local_dataflow.rb:6:8:6:13 | ... = ... | +| local_dataflow.rb:9:1:9:15 | ... = ... | local_dataflow.rb:10:14:10:18 | array | +| local_dataflow.rb:9:9:9:15 | call to [] | local_dataflow.rb:9:1:9:15 | ... = ... | +| local_dataflow.rb:9:9:9:15 | call to [] | local_dataflow.rb:9:1:9:15 | ... = ... | +| local_dataflow.rb:10:5:13:3 | for ... in ... | local_dataflow.rb:10:1:13:3 | ... = ... | +| local_dataflow.rb:10:9:10:9 | x | local_dataflow.rb:12:5:12:5 | x | +| local_dataflow.rb:10:14:10:18 | array | local_dataflow.rb:10:5:13:3 | for ... in ... | +| local_dataflow.rb:10:14:10:18 | array | local_dataflow.rb:15:10:15:14 | array | +| local_dataflow.rb:12:3:12:5 | call to p | local_dataflow.rb:10:19:13:3 | do ... | +| local_dataflow.rb:15:10:15:14 | array | local_dataflow.rb:15:1:17:3 | for ... in ... | +| local_dataflow.rb:15:10:15:14 | array | local_dataflow.rb:19:10:19:14 | array | +| local_dataflow.rb:16:3:16:10 | break | local_dataflow.rb:15:1:17:3 | for ... in ... | +| local_dataflow.rb:16:9:16:10 | 10 | local_dataflow.rb:16:3:16:10 | break | +| local_dataflow.rb:19:5:19:5 | x | local_dataflow.rb:20:6:20:6 | x | +| local_dataflow.rb:19:10:19:14 | array | local_dataflow.rb:19:1:21:3 | for ... in ... | +| local_dataflow.rb:20:3:20:25 | if ... | local_dataflow.rb:19:16:21:3 | do ... | +| local_dataflow.rb:20:17:20:21 | break | local_dataflow.rb:19:1:21:3 | for ... in ... | +| local_dataflow.rb:24:2:24:8 | break | local_dataflow.rb:23:1:25:3 | while ... | +| local_dataflow.rb:24:8:24:8 | 5 | local_dataflow.rb:24:2:24:8 | break | +| local_dataflow.rb:28:5:28:26 | M | local_dataflow.rb:28:1:28:26 | ... = ... | +| local_dataflow.rb:28:15:28:22 | "module" | local_dataflow.rb:28:5:28:26 | M | +| local_dataflow.rb:30:5:30:24 | C | local_dataflow.rb:30:1:30:24 | ... = ... | +| local_dataflow.rb:30:14:30:20 | "class" | local_dataflow.rb:30:5:30:24 | C | +| local_dataflow.rb:32:5:32:25 | bar | local_dataflow.rb:32:1:32:25 | ... = ... | +| local_dataflow.rb:32:5:32:25 | bar | local_dataflow.rb:32:1:32:25 | ... = ... | +| local_dataflow.rb:34:7:34:7 | x | local_dataflow.rb:34:7:34:7 | x | +| local_dataflow.rb:34:7:34:7 | x | local_dataflow.rb:35:6:35:6 | x | +| local_dataflow.rb:36:13:36:13 | 7 | local_dataflow.rb:36:6:36:13 | return | +| local_dataflow.rb:41:7:41:7 | x | local_dataflow.rb:41:7:41:7 | x | +| local_dataflow.rb:41:7:41:7 | x | local_dataflow.rb:42:6:42:6 | x | +| local_dataflow.rb:43:13:43:13 | 7 | local_dataflow.rb:43:6:43:13 | return | +| local_dataflow.rb:45:10:45:10 | 6 | local_dataflow.rb:45:3:45:10 | return | +| local_dataflow.rb:49:3:53:3 | | local_dataflow.rb:50:18:50:18 | x | +| local_dataflow.rb:50:8:50:13 | "next" | local_dataflow.rb:50:3:50:13 | next | +| local_dataflow.rb:50:18:50:18 | [post] x | local_dataflow.rb:51:20:51:20 | x | +| local_dataflow.rb:50:18:50:18 | x | local_dataflow.rb:51:20:51:20 | x | +| local_dataflow.rb:51:9:51:15 | "break" | local_dataflow.rb:51:3:51:15 | break | diff --git a/ruby/ql/test/library-tests/dataflow/local/DataflowStep.ql b/ruby/ql/test/library-tests/dataflow/local/DataflowStep.ql new file mode 100644 index 000000000000..872625f39a73 --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/local/DataflowStep.ql @@ -0,0 +1,6 @@ +import ruby +import codeql.ruby.DataFlow + +from DataFlow::Node pred, DataFlow::Node succ +where DataFlow::localFlowStep(pred, succ) +select pred, succ diff --git a/ruby/ql/test/library-tests/dataflow/local/ReturnNodes.expected b/ruby/ql/test/library-tests/dataflow/local/ReturnNodes.expected new file mode 100644 index 000000000000..8a3e009f694c --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/local/ReturnNodes.expected @@ -0,0 +1,9 @@ +| local_dataflow.rb:6:3:6:14 | ... = ... | +| local_dataflow.rb:32:14:32:21 | "method" | +| local_dataflow.rb:36:6:36:13 | return | +| local_dataflow.rb:38:3:38:13 | "reachable" | +| local_dataflow.rb:43:6:43:13 | return | +| local_dataflow.rb:45:3:45:10 | return | +| local_dataflow.rb:50:3:50:13 | next | +| local_dataflow.rb:51:3:51:15 | break | +| local_dataflow.rb:52:3:52:10 | "normal" | diff --git a/ruby/ql/test/library-tests/dataflow/local/ReturnNodes.ql b/ruby/ql/test/library-tests/dataflow/local/ReturnNodes.ql new file mode 100644 index 000000000000..44c17517874b --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/local/ReturnNodes.ql @@ -0,0 +1,4 @@ +import ruby +import codeql.ruby.dataflow.internal.DataFlowPrivate + +select any(ReturnNode node) diff --git a/ruby/ql/test/library-tests/dataflow/local/local_dataflow.rb b/ruby/ql/test/library-tests/dataflow/local/local_dataflow.rb new file mode 100644 index 000000000000..27fc5f4d841e --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/local/local_dataflow.rb @@ -0,0 +1,53 @@ +def foo(a) + b = a + c = (p a; b) + d = c = a + d = (c = a) + e = (a += b) +end + +array = [1,2,3] +y = for x in array +do + p x +end + +for x in array do + break 10 +end + +for x in array do + if x > 1 then break end +end + +while true + break 5 +end + +# string flows to x +x = module M; "module" end +# string flows to x +x = class C; "class" end +# string does not flow to x because "def" evaluates to a method symbol +x = def bar; "method" end + +def m x + if x == 4 + return 7 + end + "reachable" +end + +def m x + if x == 4 + return 7 + end + return 6 + "unreachable" +end + +m do + next "next" if x < 4 + break "break" if x < 9 + "normal" +end diff --git a/ruby/ql/test/library-tests/dataflow/summaries/Summaries.expected b/ruby/ql/test/library-tests/dataflow/summaries/Summaries.expected new file mode 100644 index 000000000000..a35369384329 --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/summaries/Summaries.expected @@ -0,0 +1,35 @@ +edges +| summaries.rb:1:11:1:26 | call to identity : | summaries.rb:2:6:2:12 | tainted | +| summaries.rb:1:11:1:26 | call to identity : | summaries.rb:4:24:4:30 | tainted : | +| summaries.rb:1:11:1:26 | call to identity : | summaries.rb:16:36:16:42 | tainted : | +| summaries.rb:1:20:1:26 | "taint" : | summaries.rb:1:11:1:26 | call to identity : | +| summaries.rb:4:12:7:3 | call to apply_block : | summaries.rb:9:6:9:13 | tainted2 | +| summaries.rb:4:24:4:30 | tainted : | summaries.rb:4:12:7:3 | call to apply_block : | +| summaries.rb:4:24:4:30 | tainted : | summaries.rb:4:36:4:36 | x : | +| summaries.rb:4:36:4:36 | x : | summaries.rb:5:8:5:8 | x | +| summaries.rb:11:17:11:17 | x : | summaries.rb:12:8:12:8 | x | +| summaries.rb:16:12:16:43 | call to apply_lambda : | summaries.rb:18:6:18:13 | tainted3 | +| summaries.rb:16:36:16:42 | tainted : | summaries.rb:11:17:11:17 | x : | +| summaries.rb:16:36:16:42 | tainted : | summaries.rb:16:12:16:43 | call to apply_lambda : | +nodes +| summaries.rb:1:11:1:26 | call to identity : | semmle.label | call to identity : | +| summaries.rb:1:20:1:26 | "taint" : | semmle.label | "taint" : | +| summaries.rb:2:6:2:12 | tainted | semmle.label | tainted | +| summaries.rb:4:12:7:3 | call to apply_block : | semmle.label | call to apply_block : | +| summaries.rb:4:24:4:30 | tainted : | semmle.label | tainted : | +| summaries.rb:4:36:4:36 | x : | semmle.label | x : | +| summaries.rb:5:8:5:8 | x | semmle.label | x | +| summaries.rb:9:6:9:13 | tainted2 | semmle.label | tainted2 | +| summaries.rb:11:17:11:17 | x : | semmle.label | x : | +| summaries.rb:12:8:12:8 | x | semmle.label | x | +| summaries.rb:16:12:16:43 | call to apply_lambda : | semmle.label | call to apply_lambda : | +| summaries.rb:16:36:16:42 | tainted : | semmle.label | tainted : | +| summaries.rb:18:6:18:13 | tainted3 | semmle.label | tainted3 | +subpaths +invalidSpecComponent +#select +| summaries.rb:2:6:2:12 | tainted | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:2:6:2:12 | tainted | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : | +| summaries.rb:5:8:5:8 | x | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:5:8:5:8 | x | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : | +| summaries.rb:9:6:9:13 | tainted2 | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:9:6:9:13 | tainted2 | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : | +| summaries.rb:12:8:12:8 | x | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:12:8:12:8 | x | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : | +| summaries.rb:18:6:18:13 | tainted3 | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:18:6:18:13 | tainted3 | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : | diff --git a/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql b/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql new file mode 100644 index 000000000000..6d9db3f5c825 --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql @@ -0,0 +1,77 @@ +/** + * @kind path-problem + */ + +import ruby +import codeql.ruby.dataflow.FlowSummary +import DataFlow::PathGraph +import codeql.ruby.TaintTracking +import codeql.ruby.dataflow.internal.FlowSummaryImpl + +query predicate invalidSpecComponent(SummarizedCallable sc, string s, string c) { + (sc.propagatesFlowExt(s, _, _) or sc.propagatesFlowExt(_, s, _)) and + Private::External::invalidSpecComponent(s, c) +} + +private class SummarizedCallableIdentity extends SummarizedCallable { + SummarizedCallableIdentity() { this = "identity" } + + override MethodCall getACall() { result.getMethodName() = this } + + override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + input = "Argument[0]" and + output = "ReturnValue" and + preservesValue = true + } +} + +private class SummarizedCallableApplyBlock extends SummarizedCallable { + SummarizedCallableApplyBlock() { this = "apply_block" } + + override MethodCall getACall() { result.getMethodName() = this } + + override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + input = "Argument[0]" and + output = "Parameter[0] of BlockArgument" and + preservesValue = true + or + input = "ReturnValue of BlockArgument" and + output = "ReturnValue" and + preservesValue = true + } +} + +private class SummarizedCallableApplyLambda extends SummarizedCallable { + SummarizedCallableApplyLambda() { this = "apply_lambda" } + + override MethodCall getACall() { result.getMethodName() = this } + + override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + input = "Argument[1]" and + output = "Parameter[0] of Argument[0]" and + preservesValue = true + or + input = "ReturnValue of Argument[0]" and + output = "ReturnValue" and + preservesValue = true + } +} + +class Conf extends TaintTracking::Configuration { + Conf() { this = "FlowSummaries" } + + override predicate isSource(DataFlow::Node src) { + src.asExpr().getExpr().(StringLiteral).getValueText() = "taint" + } + + override predicate isSink(DataFlow::Node sink) { + exists(MethodCall mc | + mc.getMethodName() = "sink" and + mc.getAnArgument() = sink.asExpr().getExpr() + ) + } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, Conf conf +where conf.hasFlowPath(source, sink) +select sink, source, sink, "$@", source, source.toString() diff --git a/ruby/ql/test/library-tests/dataflow/summaries/summaries.rb b/ruby/ql/test/library-tests/dataflow/summaries/summaries.rb new file mode 100644 index 000000000000..08851ae2936a --- /dev/null +++ b/ruby/ql/test/library-tests/dataflow/summaries/summaries.rb @@ -0,0 +1,18 @@ +tainted = identity "taint" +sink tainted + +tainted2 = apply_block tainted do |x| + sink x + x +end + +sink tainted2 + +my_lambda = -> (x) { + sink x + x +} + +tainted3 = apply_lambda(my_lambda, tainted) + +sink(tainted3) diff --git a/ruby/ql/test/library-tests/frameworks/ActionController.expected b/ruby/ql/test/library-tests/frameworks/ActionController.expected new file mode 100644 index 000000000000..60225b6d02bd --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/ActionController.expected @@ -0,0 +1,59 @@ +actionControllerControllerClasses +| ActiveRecordInjection.rb:27:1:58:3 | FooController | +| ActiveRecordInjection.rb:60:1:90:3 | BarController | +| ActiveRecordInjection.rb:92:1:96:3 | BazController | +| app/controllers/foo/bars_controller.rb:1:1:20:3 | BarsController | +actionControllerActionMethods +| ActiveRecordInjection.rb:32:3:57:5 | some_request_handler | +| ActiveRecordInjection.rb:61:3:69:5 | some_other_request_handler | +| ActiveRecordInjection.rb:71:3:89:5 | safe_paths | +| ActiveRecordInjection.rb:93:3:95:5 | yet_another_handler | +| app/controllers/foo/bars_controller.rb:3:3:5:5 | index | +| app/controllers/foo/bars_controller.rb:7:3:13:5 | show_debug | +| app/controllers/foo/bars_controller.rb:15:3:19:5 | show | +paramsCalls +| ActiveRecordInjection.rb:35:30:35:35 | call to params | +| ActiveRecordInjection.rb:39:30:39:35 | call to params | +| ActiveRecordInjection.rb:43:32:43:37 | call to params | +| ActiveRecordInjection.rb:48:21:48:26 | call to params | +| ActiveRecordInjection.rb:54:34:54:39 | call to params | +| ActiveRecordInjection.rb:56:23:56:28 | call to params | +| ActiveRecordInjection.rb:56:38:56:43 | call to params | +| ActiveRecordInjection.rb:62:10:62:15 | call to params | +| ActiveRecordInjection.rb:72:11:72:16 | call to params | +| ActiveRecordInjection.rb:77:12:77:17 | call to params | +| ActiveRecordInjection.rb:83:12:83:17 | call to params | +| ActiveRecordInjection.rb:88:15:88:20 | call to params | +| ActiveRecordInjection.rb:94:22:94:27 | call to params | +| app/controllers/foo/bars_controller.rb:8:21:8:26 | call to params | +| app/controllers/foo/bars_controller.rb:9:10:9:15 | call to params | +| app/controllers/foo/bars_controller.rb:16:21:16:26 | call to params | +| app/controllers/foo/bars_controller.rb:17:10:17:15 | call to params | +| app/views/foo/bars/show.html.erb:5:9:5:14 | call to params | +paramsSources +| ActiveRecordInjection.rb:35:30:35:35 | call to params | +| ActiveRecordInjection.rb:39:30:39:35 | call to params | +| ActiveRecordInjection.rb:43:32:43:37 | call to params | +| ActiveRecordInjection.rb:48:21:48:26 | call to params | +| ActiveRecordInjection.rb:54:34:54:39 | call to params | +| ActiveRecordInjection.rb:56:23:56:28 | call to params | +| ActiveRecordInjection.rb:56:38:56:43 | call to params | +| ActiveRecordInjection.rb:62:10:62:15 | call to params | +| ActiveRecordInjection.rb:72:11:72:16 | call to params | +| ActiveRecordInjection.rb:77:12:77:17 | call to params | +| ActiveRecordInjection.rb:83:12:83:17 | call to params | +| ActiveRecordInjection.rb:88:15:88:20 | call to params | +| ActiveRecordInjection.rb:94:22:94:27 | call to params | +| app/controllers/foo/bars_controller.rb:8:21:8:26 | call to params | +| app/controllers/foo/bars_controller.rb:9:10:9:15 | call to params | +| app/controllers/foo/bars_controller.rb:16:21:16:26 | call to params | +| app/controllers/foo/bars_controller.rb:17:10:17:15 | call to params | +| app/views/foo/bars/show.html.erb:5:9:5:14 | call to params | +redirectToCalls +| app/controllers/foo/bars_controller.rb:12:5:12:30 | call to redirect_to | +actionControllerHelperMethods +getAssociatedControllerClasses +| app/controllers/foo/bars_controller.rb:1:1:20:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb | +| app/controllers/foo/bars_controller.rb:1:1:20:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb | +controllerTemplatesFolders +| app/controllers/foo/bars_controller.rb:1:1:20:3 | BarsController | folder://app/views/foo/bars | app/views/foo/bars | diff --git a/ruby/ql/test/library-tests/frameworks/ActionController.ql b/ruby/ql/test/library-tests/frameworks/ActionController.ql new file mode 100644 index 000000000000..9d70ce46cbf9 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/ActionController.ql @@ -0,0 +1,23 @@ +private import ruby +private import codeql.ruby.frameworks.ActionController +private import codeql.ruby.frameworks.ActionView + +query predicate actionControllerControllerClasses(ActionControllerControllerClass cls) { any() } + +query predicate actionControllerActionMethods(ActionControllerActionMethod m) { any() } + +query predicate paramsCalls(ParamsCall c) { any() } + +query predicate paramsSources(ParamsSource src) { any() } + +query predicate redirectToCalls(RedirectToCall c) { any() } + +query predicate actionControllerHelperMethods(ActionControllerHelperMethod m) { any() } + +query predicate getAssociatedControllerClasses(ActionControllerControllerClass cls, ErbFile f) { + cls = getAssociatedControllerClass(f) +} + +query predicate controllerTemplatesFolders(ActionControllerControllerClass cls, Folder f) { + controllerTemplatesFolder(cls, f) +} diff --git a/ruby/ql/test/library-tests/frameworks/ActionView.expected b/ruby/ql/test/library-tests/frameworks/ActionView.expected new file mode 100644 index 000000000000..959c9cc0ab26 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/ActionView.expected @@ -0,0 +1,21 @@ +htmlSafeCalls +| app/views/foo/bars/show.html.erb:23:3:23:25 | call to html_safe | +| app/views/foo/bars/show.html.erb:27:3:27:25 | call to html_safe | +rawCalls +| app/views/foo/bars/_widget.html.erb:1:5:1:21 | call to raw | +| app/views/foo/bars/_widget.html.erb:2:5:2:20 | call to raw | +| app/views/foo/bars/_widget.html.erb:3:5:3:29 | call to raw | +| app/views/foo/bars/show.html.erb:1:14:1:29 | call to raw | +| app/views/foo/bars/show.html.erb:2:5:2:21 | call to raw | +| app/views/foo/bars/show.html.erb:3:5:3:20 | call to raw | +| app/views/foo/bars/show.html.erb:4:5:4:29 | call to raw | +| app/views/foo/bars/show.html.erb:5:5:5:21 | call to raw | +| app/views/foo/bars/show.html.erb:7:5:7:19 | call to raw | +renderCalls +| app/controllers/foo/bars_controller.rb:4:5:4:37 | call to render | +| app/controllers/foo/bars_controller.rb:18:5:18:76 | call to render | +| app/views/foo/bars/show.html.erb:31:5:31:89 | call to render | +renderToCalls +| app/controllers/foo/bars_controller.rb:10:16:10:97 | call to render_to_string | +linkToCalls +| app/views/foo/bars/show.html.erb:33:5:33:41 | call to link_to | diff --git a/ruby/ql/test/library-tests/frameworks/ActionView.ql b/ruby/ql/test/library-tests/frameworks/ActionView.ql new file mode 100644 index 000000000000..da48ed87e8a0 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/ActionView.ql @@ -0,0 +1,12 @@ +import codeql.ruby.frameworks.ActionController +import codeql.ruby.frameworks.ActionView + +query predicate htmlSafeCalls(HtmlSafeCall c) { any() } + +query predicate rawCalls(RawCall c) { any() } + +query predicate renderCalls(RenderCall c) { any() } + +query predicate renderToCalls(RenderToCall c) { any() } + +query predicate linkToCalls(LinkToCall c) { any() } diff --git a/ruby/ql/test/library-tests/frameworks/ActiveRecord.expected b/ruby/ql/test/library-tests/frameworks/ActiveRecord.expected new file mode 100644 index 000000000000..8ec8abee1854 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/ActiveRecord.expected @@ -0,0 +1,46 @@ +activeRecordModelClasses +| ActiveRecordInjection.rb:1:1:3:3 | UserGroup | +| ActiveRecordInjection.rb:5:1:17:3 | User | +| ActiveRecordInjection.rb:19:1:25:3 | Admin | +activeRecordSqlExecutionRanges +| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | +| ActiveRecordInjection.rb:23:17:23:25 | condition | +| ActiveRecordInjection.rb:35:30:35:44 | ...[...] | +| ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" | +| ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" | +| ActiveRecordInjection.rb:47:16:47:21 | <<-SQL | +| ActiveRecordInjection.rb:54:20:54:47 | "user.id = '#{...}'" | +| ActiveRecordInjection.rb:68:21:68:33 | ... + ... | +| ActiveRecordInjection.rb:75:16:75:28 | "name #{...}" | +| ActiveRecordInjection.rb:80:20:80:39 | "username = #{...}" | +activeRecordModelClassMethodCalls +| ActiveRecordInjection.rb:2:3:2:17 | call to has_many | +| ActiveRecordInjection.rb:6:3:6:24 | call to belongs_to | +| ActiveRecordInjection.rb:10:5:10:68 | call to find | +| ActiveRecordInjection.rb:15:5:15:40 | call to find_by | +| ActiveRecordInjection.rb:15:5:15:46 | call to users | +| ActiveRecordInjection.rb:23:5:23:26 | call to destroy_all | +| ActiveRecordInjection.rb:35:5:35:45 | call to calculate | +| ActiveRecordInjection.rb:39:5:39:44 | call to delete_all | +| ActiveRecordInjection.rb:43:5:43:47 | call to destroy_all | +| ActiveRecordInjection.rb:47:5:47:35 | call to where | +| ActiveRecordInjection.rb:54:5:54:14 | call to where | +| ActiveRecordInjection.rb:54:5:54:48 | call to not | +| ActiveRecordInjection.rb:56:5:56:51 | call to authenticate | +| ActiveRecordInjection.rb:68:5:68:34 | call to delete_all | +| ActiveRecordInjection.rb:75:5:75:29 | call to order | +| ActiveRecordInjection.rb:80:7:80:40 | call to find_by | +| ActiveRecordInjection.rb:85:5:85:33 | call to find_by | +| ActiveRecordInjection.rb:88:5:88:34 | call to find | +| ActiveRecordInjection.rb:94:5:94:46 | call to delete_all | +potentiallyUnsafeSqlExecutingMethodCall +| ActiveRecordInjection.rb:10:5:10:68 | call to find | +| ActiveRecordInjection.rb:23:5:23:26 | call to destroy_all | +| ActiveRecordInjection.rb:35:5:35:45 | call to calculate | +| ActiveRecordInjection.rb:39:5:39:44 | call to delete_all | +| ActiveRecordInjection.rb:43:5:43:47 | call to destroy_all | +| ActiveRecordInjection.rb:47:5:47:35 | call to where | +| ActiveRecordInjection.rb:54:5:54:48 | call to not | +| ActiveRecordInjection.rb:68:5:68:34 | call to delete_all | +| ActiveRecordInjection.rb:75:5:75:29 | call to order | +| ActiveRecordInjection.rb:80:7:80:40 | call to find_by | diff --git a/ruby/ql/test/library-tests/frameworks/ActiveRecord.ql b/ruby/ql/test/library-tests/frameworks/ActiveRecord.ql new file mode 100644 index 000000000000..f277f8e368ab --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/ActiveRecord.ql @@ -0,0 +1,12 @@ +import codeql.ruby.controlflow.CfgNodes +import codeql.ruby.frameworks.ActiveRecord + +query predicate activeRecordModelClasses(ActiveRecordModelClass cls) { any() } + +query predicate activeRecordSqlExecutionRanges(ActiveRecordSqlExecutionRange range) { any() } + +query predicate activeRecordModelClassMethodCalls(ActiveRecordModelClassMethodCall call) { any() } + +query predicate potentiallyUnsafeSqlExecutingMethodCall(PotentiallyUnsafeSqlExecutingMethodCall call) { + any() +} diff --git a/ruby/ql/test/library-tests/frameworks/ActiveRecordInjection.rb b/ruby/ql/test/library-tests/frameworks/ActiveRecordInjection.rb new file mode 100644 index 000000000000..0eaf15935374 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/ActiveRecordInjection.rb @@ -0,0 +1,96 @@ +class UserGroup < ActiveRecord::Base + has_many :users +end + +class User < ApplicationRecord + belongs_to :user_group + + def self.authenticate(name, pass) + # BAD: possible untrusted input interpolated into SQL fragment + find(:first, :conditions => "name='#{name}' and pass='#{pass}'") + end + + def self.from(user_group_id) + # GOOD: `find_by` with hash argument + UserGroup.find_by(id: user_group_id).users + end +end + +class Admin < User + def self.delete_all(condition = nil) + # BAD: `delete_all` overrides an ActiveRecord method, but doesn't perform + # any validation before passing its arguments on to another ActiveRecord method + destroy_all(condition) + end +end + +class FooController < ActionController::Base + + MAX_USER_ID = 100_000 + + # A string tainted by user input is inserted into an SQL query + def some_request_handler + # BAD: executes `SELECT AVG(#{params[:column]}) FROM "users"` + # where `params[:column]` is unsanitized + User.calculate(:average, params[:column]) + + # BAD: executes `DELETE FROM "users" WHERE (id = '#{params[:id]}')` + # where `params[:id]` is unsanitized + User.delete_all("id = '#{params[:id]}'") + + # BAD: executes `SELECT "users".* FROM "users" WHERE (id = '#{params[:id]}')` + # where `params[:id]` is unsanitized + User.destroy_all(["id = '#{params[:id]}'"]) + + # BAD: executes `SELECT "users".* FROM "users" WHERE id BETWEEN '#{params[:min_id]}' AND 100000` + # where `params[:min_id]` is unsanitized + User.where(<<-SQL, MAX_USER_ID) + id BETWEEN '#{params[:min_id]}' AND ? + SQL + + # BAD: chained method case + # executes `SELECT "users".* FROM "users" WHERE (NOT (user_id = 'params[:id]'))` + # where `params[:id]` is unsanitized + User.where.not("user.id = '#{params[:id]}'") + + User.authenticate(params[:name], params[:pass]) + end +end + +class BarController < ApplicationController + def some_other_request_handler + ps = params + uid = ps[:id] + uidEq = "= '#{uid}'" + + # BAD: executes `DELETE FROM "users" WHERE (id = #{uid})` + # where `uid` is unsantized + User.delete_all("id " + uidEq) + end + + def safe_paths + dir = params[:order] + # GOOD: barrier guard prevents taint flow + dir = "DESC" unless dir == "ASC" + User.order("name #{dir}") + + name = params[:user_name] + # GOOD: barrier guard prevents taint flow + if %w(alice bob charlie).include? name + User.find_by("username = #{name}") + end + + name = params[:user_name] + # GOOD: hash arguments are sanitized by ActiveRecord + User.find_by(user_name: name) + + # OK: `find` method is overridden in `User` + User.find(params[:user_group]) + end +end + +class BazController < BarController + def yet_another_handler + Admin.delete_all(params[:admin_condition]) + end +end diff --git a/ruby/ql/test/library-tests/frameworks/CommandExecution.rb b/ruby/ql/test/library-tests/frameworks/CommandExecution.rb new file mode 100644 index 000000000000..73b9944775e5 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/CommandExecution.rb @@ -0,0 +1,90 @@ +`echo foo` +%x(echo foo) +%x{echo foo} +%x[echo foo] +%x/echo foo/ + +system("echo foo") +system("echo", "foo") +system(["echo", "echo"], "foo") + +system({"FOO" => "BAR"}, "echo foo") +system({"FOO" => "BAR"}, "echo", "foo") +system({"FOO" => "BAR"}, ["echo", "echo"], "foo") + +system("echo foo", unsetenv_others: true) +system("echo", "foo", unsetenv_others: true) +system(["echo", "echo"], "foo", unsetenv_others: true) + +system({"FOO" => "BAR"}, "echo foo", unsetenv_others: true) +system({"FOO" => "BAR"}, "echo", "foo", unsetenv_others: true) +system({"FOO" => "BAR"}, ["echo", "echo"], "foo", unsetenv_others: true) + +exec("echo foo") +exec("echo", "foo") +exec(["echo", "echo"], "foo") + +exec({"FOO" => "BAR"}, "echo foo") +exec({"FOO" => "BAR"}, "echo", "foo") +exec({"FOO" => "BAR"}, ["echo", "echo"], "foo") + +exec("echo foo", unsetenv_others: true) +exec("echo", "foo", unsetenv_others: true) +exec(["echo", "echo"], "foo", unsetenv_others: true) + +exec({"FOO" => "BAR"}, "echo foo", unsetenv_others: true) +exec({"FOO" => "BAR"}, "echo", "foo", unsetenv_others: true) +exec({"FOO" => "BAR"}, ["echo", "echo"], "foo", unsetenv_others: true) + +spawn("echo foo") +spawn("echo", "foo") +spawn(["echo", "echo"], "foo") + +spawn({"FOO" => "BAR"}, "echo foo") +spawn({"FOO" => "BAR"}, "echo", "foo") +spawn({"FOO" => "BAR"}, ["echo", "echo"], "foo") + +spawn("echo foo", unsetenv_others: true) +spawn("echo", "foo", unsetenv_others: true) +spawn(["echo", "echo"], "foo", unsetenv_others: true) + +spawn({"FOO" => "BAR"}, "echo foo", unsetenv_others: true) +spawn({"FOO" => "BAR"}, "echo", "foo", unsetenv_others: true) +spawn({"FOO" => "BAR"}, ["echo", "echo"], "foo", unsetenv_others: true) + +Open3.popen3("echo foo") +Open3.popen2("echo foo") +Open3.popen2e("echo foo") +Open3.capture3("echo foo") +Open3.capture2("echo foo") +Open3.capture2e("echo foo") +Open3.pipeline_rw("echo foo", "grep bar") +Open3.pipeline_r("echo foo", "grep bar") +Open3.pipeline_w("echo foo", "grep bar") +Open3.pipeline_start("echo foo", "grep bar") +Open3.pipeline("echo foo", "grep bar") + +<<`EOF` +echo foo +EOF + +module MockSystem + def system(*args) + args + end + + def self.system(*args) + args + end +end + +class Foo + include MockSystem + + def run + system("ls") + MockSystem.system("ls") + end +end + +UnknownModule.system("ls") diff --git a/ruby/ql/test/library-tests/frameworks/Eval.rb b/ruby/ql/test/library-tests/frameworks/Eval.rb new file mode 100644 index 000000000000..5f7237265ac1 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/Eval.rb @@ -0,0 +1,23 @@ +# Uses of eval and send + +eval("raise \"error\"") +send("raise", "error") + +a = [] +a.send("raise", "error") + +class Foo + def eval(x) + x + 1 + end + + def send(*args) + 2 + end + + def run + eval("exit 1") + end +end + +Foo.new.send("exit", 1) diff --git a/ruby/ql/test/library-tests/frameworks/Files.expected b/ruby/ql/test/library-tests/frameworks/Files.expected new file mode 100644 index 000000000000..3c094b666269 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/Files.expected @@ -0,0 +1,60 @@ +fileInstances +| Files.rb:2:1:2:30 | ... = ... | +| Files.rb:2:1:2:30 | ... = ... | +| Files.rb:2:12:2:30 | call to new | +| Files.rb:3:1:3:21 | ... = ... | +| Files.rb:3:1:3:21 | ... = ... | +| Files.rb:3:14:3:21 | foo_file | +| Files.rb:4:1:4:8 | foo_file | +| Files.rb:7:13:7:22 | foo_file_2 | +| Files.rb:10:6:10:13 | foo_file | +| Files.rb:11:6:11:13 | foo_file | +| Files.rb:23:1:23:33 | ... = ... | +| Files.rb:23:19:23:33 | call to open | +| Files.rb:24:1:24:40 | ... = ... | +| Files.rb:24:19:24:40 | call to open | +ioInstances +| Files.rb:2:1:2:30 | ... = ... | +| Files.rb:2:1:2:30 | ... = ... | +| Files.rb:2:12:2:30 | call to new | +| Files.rb:3:1:3:21 | ... = ... | +| Files.rb:3:1:3:21 | ... = ... | +| Files.rb:3:14:3:21 | foo_file | +| Files.rb:4:1:4:8 | foo_file | +| Files.rb:7:13:7:22 | foo_file_2 | +| Files.rb:10:6:10:13 | foo_file | +| Files.rb:11:6:11:13 | foo_file | +| Files.rb:17:1:17:50 | ... = ... | +| Files.rb:17:1:17:50 | ... = ... | +| Files.rb:17:8:17:50 | call to new | +| Files.rb:18:1:18:13 | ... = ... | +| Files.rb:18:10:18:13 | rand | +| Files.rb:20:13:20:16 | rand | +| Files.rb:23:1:23:33 | ... = ... | +| Files.rb:23:19:23:33 | call to open | +| Files.rb:24:1:24:40 | ... = ... | +| Files.rb:24:19:24:40 | call to open | +| Files.rb:35:1:35:56 | ... = ... | +| Files.rb:35:13:35:56 | call to open | +fileReaders +| Files.rb:7:13:7:32 | call to readlines | +ioReaders +| Files.rb:7:13:7:32 | call to readlines | File | +| Files.rb:20:13:20:25 | call to read | IO | +| Files.rb:29:12:29:29 | call to read | IO | +| Files.rb:32:8:32:23 | call to read | IO | +ioFileReaders +| Files.rb:7:13:7:32 | call to readlines | File | +| Files.rb:29:12:29:29 | call to read | IO | +fileModuleFilenameSources +| Files.rb:10:6:10:18 | call to path | +| Files.rb:11:6:11:21 | call to to_path | +fileUtilsFilenameSources +| Files.rb:14:8:14:43 | call to makedirs | +fileSystemReadAccesses +| Files.rb:7:13:7:32 | call to readlines | +| Files.rb:29:12:29:29 | call to read | +fileNameSources +| Files.rb:10:6:10:18 | call to path | +| Files.rb:11:6:11:21 | call to to_path | +| Files.rb:14:8:14:43 | call to makedirs | diff --git a/ruby/ql/test/library-tests/frameworks/Files.ql b/ruby/ql/test/library-tests/frameworks/Files.ql new file mode 100644 index 000000000000..fc2fffe92164 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/Files.ql @@ -0,0 +1,21 @@ +private import ruby +private import codeql.ruby.frameworks.Files +private import codeql.ruby.Concepts + +query predicate fileInstances(File::FileInstance i) { any() } + +query predicate ioInstances(IO::IOInstance i) { any() } + +query predicate fileReaders(File::FileModuleReader r) { any() } + +query predicate ioReaders(IO::IOReader r, string api) { api = r.getAPI() } + +query predicate ioFileReaders(IO::IOFileReader r, string api) { api = r.getAPI() } + +query predicate fileModuleFilenameSources(File::FileModuleFilenameSource s) { any() } + +query predicate fileUtilsFilenameSources(FileUtils::FileUtilsFilenameSource s) { any() } + +query predicate fileSystemReadAccesses(FileSystemReadAccess a) { any() } + +query predicate fileNameSources(FileNameSource s) { any() } diff --git a/ruby/ql/test/library-tests/frameworks/Files.rb b/ruby/ql/test/library-tests/frameworks/Files.rb new file mode 100644 index 000000000000..8050a97cde69 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/Files.rb @@ -0,0 +1,35 @@ +# `foo_file` is a `File` instance +foo_file = File.new("foo.txt") +foo_file_2 = foo_file +foo_file + +# File read access +foo_lines = foo_file_2.readlines + +# `fp` is a file path +fp = foo_file.path +fp = foo_file.to_path + +# `FileUtils.makedirs` returns an array of file names +dirs = FileUtils.makedirs(["dir1", "dir2"]) + +# `rand` is an `IO` instance +rand = IO.new(IO.sysopen("/dev/random", "r"), "r") +rand_2 = rand + +rand_data = rand.read(32) + +# `foo_file_kernel` is a `File` instance +foo_file_kernel = open("foo.txt") +foo_file_kernel = Kernel.open("foo.txt") + +foo_command_kernel = open("|ls") + +# `IO.read("foo.txt")` reads from a file +foo_text = IO.read("foo.txt") + +# `IO.read("|date")` does not read from a file +date = IO.read("|date") + +# `rand_open` is an `IO` instance +rand_open = IO.open(IO.sysopen("/dev/random", "r"), "r") diff --git a/ruby/ql/test/library-tests/frameworks/StandardLibrary.expected b/ruby/ql/test/library-tests/frameworks/StandardLibrary.expected new file mode 100644 index 000000000000..d6fbf3a44fc1 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/StandardLibrary.expected @@ -0,0 +1,65 @@ +subshellLiteralExecutions +| CommandExecution.rb:1:1:1:10 | `echo foo` | +| CommandExecution.rb:2:1:2:12 | `echo foo` | +| CommandExecution.rb:3:1:3:12 | `echo foo` | +| CommandExecution.rb:4:1:4:12 | `echo foo` | +| CommandExecution.rb:5:1:5:12 | `echo foo` | +subshellHeredocExecutions +| CommandExecution.rb:67:1:67:7 | <<`EOF` | +kernelSystemCallExecutions +| CommandExecution.rb:7:1:7:18 | call to system | +| CommandExecution.rb:8:1:8:21 | call to system | +| CommandExecution.rb:9:1:9:31 | call to system | +| CommandExecution.rb:11:1:11:36 | call to system | +| CommandExecution.rb:12:1:12:39 | call to system | +| CommandExecution.rb:13:1:13:49 | call to system | +| CommandExecution.rb:15:1:15:41 | call to system | +| CommandExecution.rb:16:1:16:44 | call to system | +| CommandExecution.rb:17:1:17:54 | call to system | +| CommandExecution.rb:19:1:19:59 | call to system | +| CommandExecution.rb:20:1:20:62 | call to system | +| CommandExecution.rb:21:1:21:72 | call to system | +kernelExecCallExecutions +| CommandExecution.rb:23:1:23:16 | call to exec | +| CommandExecution.rb:24:1:24:19 | call to exec | +| CommandExecution.rb:25:1:25:29 | call to exec | +| CommandExecution.rb:27:1:27:34 | call to exec | +| CommandExecution.rb:28:1:28:37 | call to exec | +| CommandExecution.rb:29:1:29:47 | call to exec | +| CommandExecution.rb:31:1:31:39 | call to exec | +| CommandExecution.rb:32:1:32:42 | call to exec | +| CommandExecution.rb:33:1:33:52 | call to exec | +| CommandExecution.rb:35:1:35:57 | call to exec | +| CommandExecution.rb:36:1:36:60 | call to exec | +| CommandExecution.rb:37:1:37:70 | call to exec | +kernelSpawnCallExecutions +| CommandExecution.rb:39:1:39:17 | call to spawn | +| CommandExecution.rb:40:1:40:20 | call to spawn | +| CommandExecution.rb:41:1:41:30 | call to spawn | +| CommandExecution.rb:43:1:43:35 | call to spawn | +| CommandExecution.rb:44:1:44:38 | call to spawn | +| CommandExecution.rb:45:1:45:48 | call to spawn | +| CommandExecution.rb:47:1:47:40 | call to spawn | +| CommandExecution.rb:48:1:48:43 | call to spawn | +| CommandExecution.rb:49:1:49:53 | call to spawn | +| CommandExecution.rb:51:1:51:58 | call to spawn | +| CommandExecution.rb:52:1:52:61 | call to spawn | +| CommandExecution.rb:53:1:53:71 | call to spawn | +open3CallExecutions +| CommandExecution.rb:55:1:55:24 | call to popen3 | +| CommandExecution.rb:56:1:56:24 | call to popen2 | +| CommandExecution.rb:57:1:57:25 | call to popen2e | +| CommandExecution.rb:58:1:58:26 | call to capture3 | +| CommandExecution.rb:59:1:59:26 | call to capture2 | +| CommandExecution.rb:60:1:60:27 | call to capture2e | +open3PipelineCallExecutions +| CommandExecution.rb:61:1:61:41 | call to pipeline_rw | +| CommandExecution.rb:62:1:62:40 | call to pipeline_r | +| CommandExecution.rb:63:1:63:40 | call to pipeline_w | +| CommandExecution.rb:64:1:64:44 | call to pipeline_start | +| CommandExecution.rb:65:1:65:38 | call to pipeline | +evalCallCodeExecutions +| Eval.rb:3:1:3:23 | call to eval | +sendCallCodeExecutions +| Eval.rb:4:1:4:22 | call to send | +| Eval.rb:7:1:7:24 | call to send | diff --git a/ruby/ql/test/library-tests/frameworks/StandardLibrary.ql b/ruby/ql/test/library-tests/frameworks/StandardLibrary.ql new file mode 100644 index 000000000000..ca04ebb48266 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/StandardLibrary.ql @@ -0,0 +1,19 @@ +import codeql.ruby.frameworks.StandardLibrary + +query predicate subshellLiteralExecutions(SubshellLiteralExecution e) { any() } + +query predicate subshellHeredocExecutions(SubshellHeredocExecution e) { any() } + +query predicate kernelSystemCallExecutions(KernelSystemCall c) { any() } + +query predicate kernelExecCallExecutions(KernelExecCall c) { any() } + +query predicate kernelSpawnCallExecutions(KernelSpawnCall c) { any() } + +query predicate open3CallExecutions(Open3Call c) { any() } + +query predicate open3PipelineCallExecutions(Open3PipelineCall c) { any() } + +query predicate evalCallCodeExecutions(EvalCallCodeExecution e) { any() } + +query predicate sendCallCodeExecutions(SendCallCodeExecution e) { any() } diff --git a/ruby/ql/test/library-tests/frameworks/app/components/DummyComponent.rb b/ruby/ql/test/library-tests/frameworks/app/components/DummyComponent.rb new file mode 100644 index 000000000000..80d6a602d686 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/app/components/DummyComponent.rb @@ -0,0 +1,2 @@ +class DummyComponent < ViewComponent::Base +end diff --git a/ruby/ql/test/library-tests/frameworks/app/controllers/foo/bars_controller.rb b/ruby/ql/test/library-tests/frameworks/app/controllers/foo/bars_controller.rb new file mode 100644 index 000000000000..6655cbcd4c7d --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/app/controllers/foo/bars_controller.rb @@ -0,0 +1,20 @@ +class BarsController < ApplicationController + + def index + render template: "foo/bars/index" + end + + def show_debug + @user_website = params[:website] + dt = params[:text] + rendered = render_to_string "foo/bars/show", locals: { display_text: dt, safe_text: "hello" } + puts rendered + redirect_to action: "show" + end + + def show + @user_website = params[:website] + dt = params[:text] + render "foo/bars/show", locals: { display_text: dt, safe_text: "hello" } + end +end diff --git a/ruby/ql/test/library-tests/frameworks/app/views/foo/bars/_widget.html.erb b/ruby/ql/test/library-tests/frameworks/app/views/foo/bars/_widget.html.erb new file mode 100644 index 000000000000..dda3813363ac --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/app/views/foo/bars/_widget.html.erb @@ -0,0 +1,4 @@ +<%= raw @display_text %> +<%= raw display_text %> +<%= raw locals[:display_text] %> +<%= @display_text %> diff --git a/ruby/ql/test/library-tests/frameworks/app/views/foo/bars/show.html.erb b/ruby/ql/test/library-tests/frameworks/app/views/foo/bars/show.html.erb new file mode 100644 index 000000000000..a86fabf719ce --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/app/views/foo/bars/show.html.erb @@ -0,0 +1,33 @@ +
    website +<%= raw @display_text %> +<%= raw display_text %> +<%= raw locals[:display_text] %> +<%= raw params[:text] %> +<% key = :display_text %> +<%= raw locals[key] %> + +
      +<% key = [:display_text, :safe_text] do +
    • <%= raw locals[key] %>
    • +<% end %> +
    + +<%= @display_text %> + +<%= + full_text = prefix + locals[:display_text] + full_text +%> + +<%= + @display_text.html_safe +%> + +<%= + @display_text.html_safe + @display_text +%> + +<%= render partial: 'foo/bars/widget', locals: { display_text: "widget_" + display_text } %> + +<%= link_to "some website", @user_website %> diff --git a/ruby/ql/test/library-tests/frameworks/http_clients/Excon.expected b/ruby/ql/test/library-tests/frameworks/http_clients/Excon.expected new file mode 100644 index 000000000000..e25ae0aff638 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/http_clients/Excon.expected @@ -0,0 +1,10 @@ +| Excon.rb:3:9:3:40 | call to get | Excon.rb:4:1:4:10 | call to body | +| Excon.rb:6:9:6:60 | call to post | Excon.rb:7:1:7:10 | call to body | +| Excon.rb:9:9:9:59 | call to put | Excon.rb:10:1:10:10 | call to body | +| Excon.rb:12:9:12:61 | call to patch | Excon.rb:13:1:13:10 | call to body | +| Excon.rb:15:9:15:43 | call to delete | Excon.rb:16:1:16:10 | call to body | +| Excon.rb:18:9:18:41 | call to head | Excon.rb:19:1:19:10 | call to body | +| Excon.rb:21:9:21:44 | call to options | Excon.rb:22:1:22:10 | call to body | +| Excon.rb:24:9:24:42 | call to trace | Excon.rb:25:1:25:10 | call to body | +| Excon.rb:28:9:28:33 | call to get | Excon.rb:29:1:29:10 | call to body | +| Excon.rb:31:10:31:38 | call to post | Excon.rb:32:1:32:11 | call to body | diff --git a/ruby/ql/test/library-tests/frameworks/http_clients/Excon.ql b/ruby/ql/test/library-tests/frameworks/http_clients/Excon.ql new file mode 100644 index 000000000000..1b89d77e2209 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/http_clients/Excon.ql @@ -0,0 +1,4 @@ +import codeql.ruby.frameworks.http_clients.Excon +import codeql.ruby.DataFlow + +query DataFlow::Node exconHTTPRequests(ExconHTTPRequest e) { result = e.getResponseBody() } diff --git a/ruby/ql/test/library-tests/frameworks/http_clients/Excon.rb b/ruby/ql/test/library-tests/frameworks/http_clients/Excon.rb new file mode 100644 index 000000000000..55166eeca174 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/http_clients/Excon.rb @@ -0,0 +1,32 @@ +require "excon" + +resp1 = Excon.get("http://example.com/") +resp1.body + +resp2 = Excon.post("http://example.com/", body: "some_data") +resp2.body + +resp3 = Excon.put("http://example.com/", body: "some_data") +resp3.body + +resp4 = Excon.patch("http://example.com/", body: "some_data") +resp4.body + +resp5 = Excon.delete("http://example.com/") +resp5.body + +resp6 = Excon.head("http://example.com/") +resp6.body + +resp7 = Excon.options("http://example.com/") +resp7.body + +resp8 = Excon.trace("http://example.com/") +resp8.body + +connection = Excon.new("http://example.com") +resp9 = connection.get(path: "/") +resp9.body + +resp10 = connection.post(path: "/foo") +resp10.body \ No newline at end of file diff --git a/ruby/ql/test/library-tests/frameworks/http_clients/Faraday.expected b/ruby/ql/test/library-tests/frameworks/http_clients/Faraday.expected new file mode 100644 index 000000000000..2013b49dd4ab --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/http_clients/Faraday.expected @@ -0,0 +1,9 @@ +| Faraday.rb:3:9:3:42 | call to get | Faraday.rb:4:1:4:10 | call to body | +| Faraday.rb:6:9:6:62 | call to post | Faraday.rb:7:1:7:10 | call to body | +| Faraday.rb:9:9:9:61 | call to put | Faraday.rb:10:1:10:10 | call to body | +| Faraday.rb:12:9:12:63 | call to patch | Faraday.rb:13:1:13:10 | call to body | +| Faraday.rb:15:9:15:45 | call to delete | Faraday.rb:16:1:16:10 | call to body | +| Faraday.rb:18:9:18:43 | call to head | Faraday.rb:19:1:19:10 | call to body | +| Faraday.rb:24:9:24:44 | call to trace | Faraday.rb:25:1:25:10 | call to body | +| Faraday.rb:28:9:28:27 | call to get | Faraday.rb:29:1:29:10 | call to body | +| Faraday.rb:31:10:31:46 | call to post | Faraday.rb:32:1:32:11 | call to body | diff --git a/ruby/ql/test/library-tests/frameworks/http_clients/Faraday.ql b/ruby/ql/test/library-tests/frameworks/http_clients/Faraday.ql new file mode 100644 index 000000000000..705c226429e6 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/http_clients/Faraday.ql @@ -0,0 +1,4 @@ +import codeql.ruby.frameworks.http_clients.Faraday +import codeql.ruby.DataFlow + +query DataFlow::Node faradayHTTPRequests(FaradayHTTPRequest e) { result = e.getResponseBody() } diff --git a/ruby/ql/test/library-tests/frameworks/http_clients/Faraday.rb b/ruby/ql/test/library-tests/frameworks/http_clients/Faraday.rb new file mode 100644 index 000000000000..ee8cccb44b01 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/http_clients/Faraday.rb @@ -0,0 +1,32 @@ +require "faraday" + +resp1 = Faraday.get("http://example.com/") +resp1.body + +resp2 = Faraday.post("http://example.com/", body: "some_data") +resp2.body + +resp3 = Faraday.put("http://example.com/", body: "some_data") +resp3.body + +resp4 = Faraday.patch("http://example.com/", body: "some_data") +resp4.body + +resp5 = Faraday.delete("http://example.com/") +resp5.body + +resp6 = Faraday.head("http://example.com/") +resp6.body + +resp7 = Faraday.options("http://example.com/") +resp7.body + +resp8 = Faraday.trace("http://example.com/") +resp8.body + +connection = Faraday.new("http://example.com") +resp9 = connection.get("/") +resp9.body + +resp10 = connection.post("/foo", some: "data") +resp10.body \ No newline at end of file diff --git a/ruby/ql/test/library-tests/frameworks/http_clients/NetHTTP.expected b/ruby/ql/test/library-tests/frameworks/http_clients/NetHTTP.expected new file mode 100644 index 000000000000..256ed046e166 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/http_clients/NetHTTP.expected @@ -0,0 +1,8 @@ +| NetHTTP.rb:4:1:4:18 | call to get | NetHTTP.rb:4:1:4:18 | call to get | +| NetHTTP.rb:6:8:6:50 | call to post | NetHTTP.rb:7:1:7:9 | call to body | +| NetHTTP.rb:6:8:6:50 | call to post | NetHTTP.rb:8:1:8:14 | call to read_body | +| NetHTTP.rb:6:8:6:50 | call to post | NetHTTP.rb:9:1:9:11 | call to entity | +| NetHTTP.rb:13:6:13:17 | call to get | NetHTTP.rb:18:1:18:7 | call to body | +| NetHTTP.rb:14:6:14:18 | call to post | NetHTTP.rb:19:1:19:12 | call to read_body | +| NetHTTP.rb:15:6:15:17 | call to put | NetHTTP.rb:20:1:20:9 | call to entity | +| NetHTTP.rb:24:3:24:33 | call to get | NetHTTP.rb:27:1:27:28 | call to body | diff --git a/ruby/ql/test/library-tests/frameworks/http_clients/NetHTTP.ql b/ruby/ql/test/library-tests/frameworks/http_clients/NetHTTP.ql new file mode 100644 index 000000000000..b6ce25645f34 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/http_clients/NetHTTP.ql @@ -0,0 +1,4 @@ +import codeql.ruby.frameworks.http_clients.NetHTTP +import codeql.ruby.DataFlow + +query DataFlow::Node netHTTPRequests(NetHTTPRequest e) { result = e.getResponseBody() } diff --git a/ruby/ql/test/library-tests/frameworks/http_clients/NetHTTP.rb b/ruby/ql/test/library-tests/frameworks/http_clients/NetHTTP.rb new file mode 100644 index 000000000000..12333be9f4ec --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/http_clients/NetHTTP.rb @@ -0,0 +1,27 @@ +require "net/http" + +uri = URI.parse("https://example.com") +Net::HTTP.get(uri) + +resp = Net::HTTP.post(URI.parse(uri), "some_body") +resp.body +resp.read_body +resp.entity + +req = Net::HTTP.new("https://example.com") + +r1 = req.get("/") +r2 = req.post("/") +r3 = req.put("/") +r4 = req.patch("/") + +r1.body +r2.read_body +r3.entity +r4.foo + +def get(domain, path) + Net::HTTP.new(domain).get(path) +end + +get("example.com", "/").body diff --git a/ruby/ql/test/library-tests/frameworks/http_clients/RestClient.expected b/ruby/ql/test/library-tests/frameworks/http_clients/RestClient.expected new file mode 100644 index 000000000000..d39b89fbdcbf --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/http_clients/RestClient.expected @@ -0,0 +1,7 @@ +| RestClient.rb:3:9:3:45 | call to get | RestClient.rb:4:1:4:10 | call to body | +| RestClient.rb:6:9:6:59 | call to post | RestClient.rb:7:1:7:10 | call to body | +| RestClient.rb:9:9:9:58 | call to put | RestClient.rb:10:1:10:10 | call to body | +| RestClient.rb:12:9:12:60 | call to patch | RestClient.rb:13:1:13:10 | call to body | +| RestClient.rb:15:9:15:47 | call to delete | RestClient.rb:16:1:16:10 | call to body | +| RestClient.rb:18:9:18:45 | call to head | RestClient.rb:19:1:19:10 | call to body | +| RestClient.rb:21:9:21:48 | call to options | RestClient.rb:22:1:22:10 | call to body | diff --git a/ruby/ql/test/library-tests/frameworks/http_clients/RestClient.ql b/ruby/ql/test/library-tests/frameworks/http_clients/RestClient.ql new file mode 100644 index 000000000000..9d35545e6b9d --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/http_clients/RestClient.ql @@ -0,0 +1,6 @@ +import codeql.ruby.frameworks.http_clients.RestClient +import codeql.ruby.DataFlow + +query DataFlow::Node restClientHTTPRequests(RestClientHTTPRequest e) { + result = e.getResponseBody() +} diff --git a/ruby/ql/test/library-tests/frameworks/http_clients/RestClient.rb b/ruby/ql/test/library-tests/frameworks/http_clients/RestClient.rb new file mode 100644 index 000000000000..61d8caa43d50 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/http_clients/RestClient.rb @@ -0,0 +1,22 @@ +require "rest-client" + +resp1 = RestClient.get("http://example.com/") +resp1.body + +resp2 = RestClient.post("http://example.com", some: "data") +resp2.body + +resp3 = RestClient.put("http://example.com", some: "data") +resp3.body + +resp4 = RestClient.patch("http://example.com", some: "data") +resp4.body + +resp5 = RestClient.delete("http://example.com") +resp5.body + +resp6 = RestClient.head("http://example.com") +resp6.body + +resp7 = RestClient.options("http://example.com") +resp7.body \ No newline at end of file diff --git a/ruby/ql/test/library-tests/modules/ancestors.expected b/ruby/ql/test/library-tests/modules/ancestors.expected new file mode 100644 index 000000000000..a9c235619fd5 --- /dev/null +++ b/ruby/ql/test/library-tests/modules/ancestors.expected @@ -0,0 +1,154 @@ +calls.rb: +# 102| Hash +#-----| super -> Object + +# 77| Integer +#-----| super -> Numeric + +# 86| Kernel + +# 90| Module +#-----| super -> Object + +# 97| Object +#-----| include -> Kernel +#-----| super -> BasicObject + +# 106| Array +#-----| super -> Object + +#-----| BasicObject + +#-----| Class +#-----| super -> Module + +#-----| Complex +#-----| super -> Numeric + +#-----| FalseClass +#-----| super -> Object + +#-----| Float +#-----| super -> Numeric + +#-----| NilClass +#-----| super -> Object + +#-----| Numeric +#-----| super -> Object + +#-----| Proc + +#-----| Rational +#-----| super -> Numeric + +#-----| Symbol + +#-----| TrueClass +#-----| super -> Object + +# 15| M + +private.rb: +# 1| C +#-----| super -> Object +#-----| include -> M + +calls.rb: +# 51| D +#-----| super -> C + +# 82| String +#-----| super -> Object + +# 144| S +#-----| super -> Object + +# 150| A +#-----| super -> S + +# 155| B +#-----| super -> S + +hello.rb: +# 1| EnglishWords + +# 11| Greeting +#-----| super -> Object +#-----| include -> EnglishWords + +# 18| HelloWorld +#-----| super -> Greeting + +modules.rb: +# 1| Empty + +# 4| Foo + +# 37| Bar +#-----| super -> Object + +# 60| MyModuleInGlobalScope + +# 63| Test + +# 83| Other + +# 88| IncludeTest +#-----| include -> Test + +# 95| IncludeTest2 +#-----| include -> Test + +# 101| PrependTest +#-----| prepend -> Test + +# 107| MM + +# 112| YY +#-----| super -> Object + +# 115| XX + +# 5| Foo::Bar + +# 19| Foo::ClassInFoo +#-----| super -> Object + +# 30| Foo::ClassInAnotherDefinitionOfFoo +#-----| super -> Object + +# 116| XX::YY +#-----| super -> YY + +# 65| Test::Foo1 + +# 70| Test::Foo2 + +# 76| Test::Foo3 + +# 84| Other::Foo1 + +# 6| Foo::Bar::ClassInFooBar +#-----| super -> Object + +# 71| Test::Foo2::Foo2 + +# 108| MM::MM + +# 49| Foo::Bar::ClassInAnotherDefinitionOfFooBar +#-----| super -> Object + +# 66| Test::Foo1::Bar +#-----| super -> Object + +# 91| Test::Foo1::Y + +# 97| Test::Foo1::Z + +# 103| Test::Foo2::Y + +# 72| Test::Foo2::Foo2::Bar +#-----| super -> Object + +# 120| Test::Foo1::Bar::Baz diff --git a/ruby/ql/test/library-tests/modules/ancestors.ql b/ruby/ql/test/library-tests/modules/ancestors.ql new file mode 100644 index 000000000000..897ddf94f351 --- /dev/null +++ b/ruby/ql/test/library-tests/modules/ancestors.ql @@ -0,0 +1,21 @@ +/** + * @kind graph + * @id rb/test/ancestors + */ + +import ruby + +query predicate nodes(Module node, string key, string value) { + key = "semmle.label" and value = node.toString() +} + +query predicate edges(Module source, Module target, string key, string value) { + key = "semmle.label" and + ( + target = source.getSuperClass() and value = "super" + or + target = source.getAPrependedModule() and value = "prepend" + or + target = source.getAnIncludedModule() and value = "include" + ) +} diff --git a/ruby/ql/test/library-tests/modules/callgraph.expected b/ruby/ql/test/library-tests/modules/callgraph.expected new file mode 100644 index 000000000000..4d5a29cce356 --- /dev/null +++ b/ruby/ql/test/library-tests/modules/callgraph.expected @@ -0,0 +1,129 @@ +getTarget +| calls.rb:2:5:2:14 | call to puts | calls.rb:87:5:87:17 | puts | +| calls.rb:5:1:5:3 | call to foo | calls.rb:1:1:3:3 | foo | +| calls.rb:5:1:5:3 | call to foo | calls.rb:71:1:75:3 | foo | +| calls.rb:8:5:8:14 | call to puts | calls.rb:87:5:87:17 | puts | +| calls.rb:11:1:11:8 | call to bar | calls.rb:7:1:9:3 | bar | +| calls.rb:13:1:13:8 | call to foo | calls.rb:1:1:3:3 | foo | +| calls.rb:13:1:13:8 | call to foo | calls.rb:71:1:75:3 | foo | +| calls.rb:22:5:22:15 | call to singleton_m | calls.rb:17:5:17:29 | singleton_m | +| calls.rb:23:5:23:20 | call to singleton_m | calls.rb:17:5:17:29 | singleton_m | +| calls.rb:27:1:27:13 | call to singleton_m | calls.rb:17:5:17:29 | singleton_m | +| calls.rb:30:5:30:13 | call to include | calls.rb:92:5:92:20 | include | +| calls.rb:38:9:38:18 | call to instance_m | calls.rb:16:5:16:23 | instance_m | +| calls.rb:39:9:39:23 | call to instance_m | calls.rb:16:5:16:23 | instance_m | +| calls.rb:46:5:46:9 | call to new | calls.rb:99:5:99:16 | new | +| calls.rb:47:1:47:5 | call to baz | calls.rb:37:5:43:7 | baz | +| calls.rb:49:1:49:12 | call to instance_m | calls.rb:16:5:16:23 | instance_m | +| calls.rb:53:9:53:13 | call to super | calls.rb:37:5:43:7 | baz | +| calls.rb:57:5:57:9 | call to new | calls.rb:99:5:99:16 | new | +| calls.rb:58:1:58:5 | call to baz | calls.rb:52:5:54:7 | baz | +| calls.rb:60:1:60:12 | call to instance_m | calls.rb:16:5:16:23 | instance_m | +| calls.rb:63:5:63:16 | call to bit_length | calls.rb:78:5:78:23 | bit_length | +| calls.rb:64:5:64:16 | call to bit_length | calls.rb:78:5:78:23 | bit_length | +| calls.rb:68:5:68:11 | yield ... | calls.rb:74:16:74:29 | { ... } | +| calls.rb:68:5:68:11 | yield ... | calls.rb:141:10:141:28 | { ... } | +| calls.rb:72:11:72:18 | call to new | calls.rb:99:5:99:16 | new | +| calls.rb:73:5:73:10 | ...[...] | calls.rb:103:5:103:15 | [] | +| calls.rb:74:5:74:29 | call to call_block | calls.rb:67:1:69:3 | call_block | +| calls.rb:74:22:74:27 | ...[...] | calls.rb:103:5:103:15 | [] | +| calls.rb:98:5:98:18 | call to include | calls.rb:92:5:92:20 | include | +| calls.rb:112:15:112:25 | call to length | calls.rb:108:3:108:17 | length | +| calls.rb:113:9:113:24 | yield ... | calls.rb:129:23:129:62 | { ... } | +| calls.rb:113:9:113:24 | yield ... | calls.rb:131:17:131:35 | { ... } | +| calls.rb:113:9:113:24 | yield ... | calls.rb:133:17:133:40 | { ... } | +| calls.rb:113:9:113:24 | yield ... | calls.rb:135:18:135:37 | { ... } | +| calls.rb:113:18:113:24 | ...[...] | calls.rb:107:3:107:13 | [] | +| calls.rb:120:5:120:20 | yield ... | calls.rb:123:7:123:30 | { ... } | +| calls.rb:123:1:123:30 | call to funny | calls.rb:119:1:121:3 | funny | +| calls.rb:123:13:123:29 | call to puts | calls.rb:87:5:87:17 | puts | +| calls.rb:123:18:123:29 | call to capitalize | calls.rb:83:5:83:23 | capitalize | +| calls.rb:125:1:125:14 | call to capitalize | calls.rb:83:5:83:23 | capitalize | +| calls.rb:126:1:126:12 | call to bit_length | calls.rb:78:5:78:23 | bit_length | +| calls.rb:127:1:127:5 | call to abs | calls.rb:79:5:79:16 | abs | +| calls.rb:129:1:129:62 | call to foreach | calls.rb:110:3:116:5 | foreach | +| calls.rb:129:32:129:61 | call to puts | calls.rb:87:5:87:17 | puts | +| calls.rb:131:1:131:35 | call to foreach | calls.rb:110:3:116:5 | foreach | +| calls.rb:131:23:131:34 | call to bit_length | calls.rb:78:5:78:23 | bit_length | +| calls.rb:133:1:133:40 | call to foreach | calls.rb:110:3:116:5 | foreach | +| calls.rb:133:23:133:39 | call to puts | calls.rb:87:5:87:17 | puts | +| calls.rb:135:1:135:37 | call to foreach | calls.rb:110:3:116:5 | foreach | +| calls.rb:135:27:135:36 | call to puts | calls.rb:87:5:87:17 | puts | +| calls.rb:138:5:138:17 | call to call_block | calls.rb:67:1:69:3 | call_block | +| calls.rb:141:1:141:28 | call to indirect | calls.rb:137:1:139:3 | indirect | +| calls.rb:141:16:141:27 | call to bit_length | calls.rb:78:5:78:23 | bit_length | +| calls.rb:146:9:146:17 | call to to_s | calls.rb:151:5:152:7 | to_s | +| calls.rb:146:9:146:17 | call to to_s | calls.rb:156:5:157:7 | to_s | +| calls.rb:160:1:160:5 | call to new | calls.rb:99:5:99:16 | new | +| calls.rb:160:1:160:14 | call to s_method | calls.rb:145:5:147:7 | s_method | +| calls.rb:161:1:161:5 | call to new | calls.rb:99:5:99:16 | new | +| calls.rb:161:1:161:14 | call to s_method | calls.rb:145:5:147:7 | s_method | +| calls.rb:162:1:162:5 | call to new | calls.rb:99:5:99:16 | new | +| calls.rb:162:1:162:14 | call to s_method | calls.rb:145:5:147:7 | s_method | +| calls.rb:167:1:167:15 | call to private_on_main | calls.rb:164:1:165:3 | private_on_main | +| hello.rb:12:5:12:24 | call to include | calls.rb:92:5:92:20 | include | +| hello.rb:14:16:14:20 | call to hello | hello.rb:2:5:4:7 | hello | +| hello.rb:20:16:20:20 | call to super | hello.rb:13:5:15:7 | message | +| hello.rb:20:30:20:34 | call to world | hello.rb:5:5:7:7 | world | +| modules.rb:12:5:12:26 | call to puts | calls.rb:87:5:87:17 | puts | +| modules.rb:22:3:22:19 | call to puts | calls.rb:87:5:87:17 | puts | +| modules.rb:33:3:33:25 | call to puts | calls.rb:87:5:87:17 | puts | +| modules.rb:44:3:44:19 | call to puts | calls.rb:87:5:87:17 | puts | +| modules.rb:55:3:55:30 | call to puts | calls.rb:87:5:87:17 | puts | +| modules.rb:89:3:89:16 | call to include | calls.rb:92:5:92:20 | include | +| modules.rb:90:3:90:38 | call to module_eval | calls.rb:91:5:91:24 | module_eval | +| modules.rb:90:24:90:36 | call to prepend | calls.rb:93:5:93:20 | prepend | +| modules.rb:96:3:96:14 | call to include | calls.rb:92:5:92:20 | include | +| modules.rb:102:3:102:16 | call to prepend | calls.rb:93:5:93:20 | prepend | +| private.rb:2:3:3:5 | call to private | calls.rb:94:5:94:20 | private | +| private.rb:10:3:10:19 | call to private | calls.rb:94:5:94:20 | private | +| private.rb:12:3:12:9 | call to private | calls.rb:94:5:94:20 | private | +| private.rb:24:1:24:5 | call to new | calls.rb:99:5:99:16 | new | +| private.rb:25:1:25:5 | call to new | calls.rb:99:5:99:16 | new | +| private.rb:26:1:26:5 | call to new | calls.rb:99:5:99:16 | new | +| private.rb:27:1:27:5 | call to new | calls.rb:99:5:99:16 | new | +| private.rb:28:1:28:5 | call to new | calls.rb:99:5:99:16 | new | +| private.rb:28:1:28:12 | call to public | private.rb:5:3:6:5 | public | +| private.rb:30:1:30:15 | call to private_on_main | private.rb:21:1:22:3 | private_on_main | +unresolvedCall +| calls.rb:19:5:19:14 | call to instance_m | +| calls.rb:20:5:20:19 | call to instance_m | +| calls.rb:26:1:26:12 | call to instance_m | +| calls.rb:31:5:31:14 | call to instance_m | +| calls.rb:32:5:32:19 | call to instance_m | +| calls.rb:34:5:34:15 | call to singleton_m | +| calls.rb:35:5:35:20 | call to singleton_m | +| calls.rb:41:9:41:19 | call to singleton_m | +| calls.rb:42:9:42:24 | call to singleton_m | +| calls.rb:48:1:48:13 | call to singleton_m | +| calls.rb:59:1:59:13 | call to singleton_m | +| calls.rb:112:11:112:25 | ... < ... | +| calls.rb:114:11:114:12 | ... + ... | +| calls.rb:129:1:129:13 | call to [] | +| calls.rb:129:48:129:59 | call to capitalize | +| calls.rb:131:1:131:7 | call to [] | +| calls.rb:133:1:133:7 | call to [] | +| calls.rb:133:28:133:39 | call to capitalize | +| calls.rb:135:1:135:8 | call to [] | +| calls.rb:135:4:135:5 | - ... | +| calls.rb:135:32:135:36 | call to abs | +| hello.rb:20:16:20:26 | ... + ... | +| hello.rb:20:16:20:34 | ... + ... | +| hello.rb:20:16:20:40 | ... + ... | +| private.rb:24:1:24:14 | call to private1 | +| private.rb:25:1:25:14 | call to private2 | +| private.rb:26:1:26:14 | call to private3 | +| private.rb:27:1:27:14 | call to private4 | +privateMethod +| calls.rb:1:1:3:3 | foo | +| calls.rb:62:1:65:3 | optional_arg | +| calls.rb:67:1:69:3 | call_block | +| calls.rb:71:1:75:3 | foo | +| calls.rb:119:1:121:3 | funny | +| calls.rb:137:1:139:3 | indirect | +| calls.rb:164:1:165:3 | private_on_main | +| private.rb:2:11:3:5 | private1 | +| private.rb:8:3:9:5 | private2 | +| private.rb:14:3:15:5 | private3 | +| private.rb:17:3:18:5 | private4 | +| private.rb:21:1:22:3 | private_on_main | diff --git a/ruby/ql/test/library-tests/modules/callgraph.ql b/ruby/ql/test/library-tests/modules/callgraph.ql new file mode 100644 index 000000000000..bc8a9d6f9f9c --- /dev/null +++ b/ruby/ql/test/library-tests/modules/callgraph.ql @@ -0,0 +1,7 @@ +import ruby + +query Callable getTarget(Call call) { result = call.getATarget() } + +query predicate unresolvedCall(Call call) { not exists(call.getATarget()) } + +query predicate privateMethod(Method m) { m.isPrivate() } diff --git a/ruby/ql/test/library-tests/modules/calls.rb b/ruby/ql/test/library-tests/modules/calls.rb new file mode 100644 index 000000000000..d5c9c774f06b --- /dev/null +++ b/ruby/ql/test/library-tests/modules/calls.rb @@ -0,0 +1,167 @@ +def foo + puts "foo" +end + +foo + +def self.bar + puts "bar" +end + +self.bar + +self.foo + +module M + def instance_m; end + def self.singleton_m; end + + instance_m # NoMethodError + self.instance_m # NoMethodError + + singleton_m + self.singleton_m +end + +M.instance_m # NoMethodError +M.singleton_m + +class C + include M + instance_m # NoMethodError + self.instance_m # NoMethodError + + singleton_m # NoMethodError + self.singleton_m # NoMethodError + + def baz + instance_m + self.instance_m + + singleton_m # NoMethodError + self.singleton_m # NoMethodError + end +end + +c = C.new +c.baz +c.singleton_m # NoMethodError +c.instance_m + +class D < C + def baz + super + end +end + +d = D.new +d.baz +d.singleton_m # NoMethodError +d.instance_m + +def optional_arg(a = 4, b: 5) + a.bit_length + b.bit_length +end + +def call_block + yield 1 +end + +def foo() + var = Hash.new + var[1] + call_block { |x| var[x] } +end + +class Integer + def bit_length; end + def abs; end +end + +class String + def capitalize; end +end + +module Kernel + def puts; end +end + +class Module + def module_eval; end + def include; end + def prepend; end + def private; end +end + +class Object < Module + include Kernel + def new; end +end + +class Hash + def []; end +end + +class Array + def []; end + def length; end + + def foreach &body + x = 0 + while x < self.length + yield x, self[x] + x += 1 + end + end +end + +def funny + yield "prefix: " +end + +funny { |i| puts i.capitalize} + +"a".capitalize +1.bit_length +1.abs + +["a","b","c"].foreach { |i, v| puts "#{i} -> #{v.capitalize}"} # TODO should resolve to String.capitalize + +[1,2,3].foreach { |i| i.bit_length} + +[1,2,3].foreach { |i| puts i.capitalize} # NoMethodError + +[1,-2,3].foreach { |_, v| puts v.abs} # TODO should resolve to Integer.abs + +def indirect &b + call_block &b +end + +indirect { |i| i.bit_length} + + +class S + def s_method + self.to_s + end +end + +class A < S + def to_s + end +end + +class B < S + def to_s + end +end + +S.new.s_method +A.new.s_method +B.new.s_method + +def private_on_main +end + +private_on_main diff --git a/ruby/ql/test/library-tests/modules/hello.rb b/ruby/ql/test/library-tests/modules/hello.rb new file mode 100644 index 000000000000..1cfa0f7e4f30 --- /dev/null +++ b/ruby/ql/test/library-tests/modules/hello.rb @@ -0,0 +1,22 @@ +module EnglishWords + def hello + return "hello" + end + def world + return "world" + end +end + + +class Greeting + include EnglishWords + def message + return hello + end +end + +class HelloWorld < Greeting + def message + return super + " " + world + "!" + end +end \ No newline at end of file diff --git a/ruby/ql/test/library-tests/modules/methods.expected b/ruby/ql/test/library-tests/modules/methods.expected new file mode 100644 index 000000000000..20e2b1ee2421 --- /dev/null +++ b/ruby/ql/test/library-tests/modules/methods.expected @@ -0,0 +1,223 @@ +getMethod +| calls.rb:15:1:24:3 | M | instance_m | calls.rb:16:5:16:23 | instance_m | +| calls.rb:51:1:55:3 | D | baz | calls.rb:52:5:54:7 | baz | +| calls.rb:77:1:80:3 | Integer | abs | calls.rb:79:5:79:16 | abs | +| calls.rb:77:1:80:3 | Integer | bit_length | calls.rb:78:5:78:23 | bit_length | +| calls.rb:82:1:84:3 | String | capitalize | calls.rb:83:5:83:23 | capitalize | +| calls.rb:86:1:88:3 | Kernel | puts | calls.rb:87:5:87:17 | puts | +| calls.rb:90:1:95:3 | Module | include | calls.rb:92:5:92:20 | include | +| calls.rb:90:1:95:3 | Module | module_eval | calls.rb:91:5:91:24 | module_eval | +| calls.rb:90:1:95:3 | Module | prepend | calls.rb:93:5:93:20 | prepend | +| calls.rb:90:1:95:3 | Module | private | calls.rb:94:5:94:20 | private | +| calls.rb:97:1:100:3 | Object | call_block | calls.rb:67:1:69:3 | call_block | +| calls.rb:97:1:100:3 | Object | foo | calls.rb:1:1:3:3 | foo | +| calls.rb:97:1:100:3 | Object | foo | calls.rb:71:1:75:3 | foo | +| calls.rb:97:1:100:3 | Object | funny | calls.rb:119:1:121:3 | funny | +| calls.rb:97:1:100:3 | Object | indirect | calls.rb:137:1:139:3 | indirect | +| calls.rb:97:1:100:3 | Object | new | calls.rb:99:5:99:16 | new | +| calls.rb:97:1:100:3 | Object | optional_arg | calls.rb:62:1:65:3 | optional_arg | +| calls.rb:97:1:100:3 | Object | private_on_main | calls.rb:164:1:165:3 | private_on_main | +| calls.rb:97:1:100:3 | Object | private_on_main | private.rb:21:1:22:3 | private_on_main | +| calls.rb:102:1:104:3 | Hash | [] | calls.rb:103:5:103:15 | [] | +| calls.rb:106:1:117:3 | Array | [] | calls.rb:107:3:107:13 | [] | +| calls.rb:106:1:117:3 | Array | foreach | calls.rb:110:3:116:5 | foreach | +| calls.rb:106:1:117:3 | Array | length | calls.rb:108:3:108:17 | length | +| calls.rb:144:1:148:3 | S | s_method | calls.rb:145:5:147:7 | s_method | +| calls.rb:150:1:153:3 | A | to_s | calls.rb:151:5:152:7 | to_s | +| calls.rb:155:1:158:3 | B | to_s | calls.rb:156:5:157:7 | to_s | +| hello.rb:1:1:8:3 | EnglishWords | hello | hello.rb:2:5:4:7 | hello | +| hello.rb:1:1:8:3 | EnglishWords | world | hello.rb:5:5:7:7 | world | +| hello.rb:11:1:16:3 | Greeting | message | hello.rb:13:5:15:7 | message | +| hello.rb:18:1:22:3 | HelloWorld | message | hello.rb:19:5:21:7 | message | +| modules.rb:4:1:24:3 | Foo | method_in_another_definition_of_foo | modules.rb:27:3:28:5 | method_in_another_definition_of_foo | +| modules.rb:4:1:24:3 | Foo | method_in_foo | modules.rb:16:3:17:5 | method_in_foo | +| modules.rb:5:3:14:5 | Foo::Bar | method_in_another_definition_of_foo_bar | modules.rb:52:3:53:5 | method_in_another_definition_of_foo_bar | +| modules.rb:5:3:14:5 | Foo::Bar | method_in_foo_bar | modules.rb:9:5:10:7 | method_in_foo_bar | +| modules.rb:37:1:46:3 | Bar | method_a | modules.rb:38:3:39:5 | method_a | +| modules.rb:37:1:46:3 | Bar | method_b | modules.rb:41:3:42:5 | method_b | +| private.rb:1:1:19:3 | C | baz | calls.rb:37:5:43:7 | baz | +| private.rb:1:1:19:3 | C | private2 | private.rb:8:3:9:5 | private2 | +| private.rb:1:1:19:3 | C | private3 | private.rb:14:3:15:5 | private3 | +| private.rb:1:1:19:3 | C | private4 | private.rb:17:3:18:5 | private4 | +| private.rb:1:1:19:3 | C | public | private.rb:5:3:6:5 | public | +lookupMethod +| calls.rb:15:1:24:3 | M | instance_m | calls.rb:16:5:16:23 | instance_m | +| calls.rb:51:1:55:3 | D | baz | calls.rb:52:5:54:7 | baz | +| calls.rb:51:1:55:3 | D | call_block | calls.rb:67:1:69:3 | call_block | +| calls.rb:51:1:55:3 | D | foo | calls.rb:1:1:3:3 | foo | +| calls.rb:51:1:55:3 | D | foo | calls.rb:71:1:75:3 | foo | +| calls.rb:51:1:55:3 | D | funny | calls.rb:119:1:121:3 | funny | +| calls.rb:51:1:55:3 | D | indirect | calls.rb:137:1:139:3 | indirect | +| calls.rb:51:1:55:3 | D | instance_m | calls.rb:16:5:16:23 | instance_m | +| calls.rb:51:1:55:3 | D | new | calls.rb:99:5:99:16 | new | +| calls.rb:51:1:55:3 | D | optional_arg | calls.rb:62:1:65:3 | optional_arg | +| calls.rb:51:1:55:3 | D | private2 | private.rb:8:3:9:5 | private2 | +| calls.rb:51:1:55:3 | D | private3 | private.rb:14:3:15:5 | private3 | +| calls.rb:51:1:55:3 | D | private4 | private.rb:17:3:18:5 | private4 | +| calls.rb:51:1:55:3 | D | private_on_main | calls.rb:164:1:165:3 | private_on_main | +| calls.rb:51:1:55:3 | D | public | private.rb:5:3:6:5 | public | +| calls.rb:51:1:55:3 | D | puts | calls.rb:87:5:87:17 | puts | +| calls.rb:77:1:80:3 | Integer | abs | calls.rb:79:5:79:16 | abs | +| calls.rb:77:1:80:3 | Integer | bit_length | calls.rb:78:5:78:23 | bit_length | +| calls.rb:77:1:80:3 | Integer | new | calls.rb:99:5:99:16 | new | +| calls.rb:77:1:80:3 | Integer | puts | calls.rb:87:5:87:17 | puts | +| calls.rb:82:1:84:3 | String | call_block | calls.rb:67:1:69:3 | call_block | +| calls.rb:82:1:84:3 | String | capitalize | calls.rb:83:5:83:23 | capitalize | +| calls.rb:82:1:84:3 | String | foo | calls.rb:1:1:3:3 | foo | +| calls.rb:82:1:84:3 | String | foo | calls.rb:71:1:75:3 | foo | +| calls.rb:82:1:84:3 | String | funny | calls.rb:119:1:121:3 | funny | +| calls.rb:82:1:84:3 | String | indirect | calls.rb:137:1:139:3 | indirect | +| calls.rb:82:1:84:3 | String | new | calls.rb:99:5:99:16 | new | +| calls.rb:82:1:84:3 | String | optional_arg | calls.rb:62:1:65:3 | optional_arg | +| calls.rb:82:1:84:3 | String | private_on_main | calls.rb:164:1:165:3 | private_on_main | +| calls.rb:82:1:84:3 | String | puts | calls.rb:87:5:87:17 | puts | +| calls.rb:86:1:88:3 | Kernel | puts | calls.rb:87:5:87:17 | puts | +| calls.rb:90:1:95:3 | Module | call_block | calls.rb:67:1:69:3 | call_block | +| calls.rb:90:1:95:3 | Module | foo | calls.rb:1:1:3:3 | foo | +| calls.rb:90:1:95:3 | Module | foo | calls.rb:71:1:75:3 | foo | +| calls.rb:90:1:95:3 | Module | funny | calls.rb:119:1:121:3 | funny | +| calls.rb:90:1:95:3 | Module | include | calls.rb:92:5:92:20 | include | +| calls.rb:90:1:95:3 | Module | indirect | calls.rb:137:1:139:3 | indirect | +| calls.rb:90:1:95:3 | Module | module_eval | calls.rb:91:5:91:24 | module_eval | +| calls.rb:90:1:95:3 | Module | new | calls.rb:99:5:99:16 | new | +| calls.rb:90:1:95:3 | Module | optional_arg | calls.rb:62:1:65:3 | optional_arg | +| calls.rb:90:1:95:3 | Module | prepend | calls.rb:93:5:93:20 | prepend | +| calls.rb:90:1:95:3 | Module | private | calls.rb:94:5:94:20 | private | +| calls.rb:90:1:95:3 | Module | private_on_main | calls.rb:164:1:165:3 | private_on_main | +| calls.rb:90:1:95:3 | Module | puts | calls.rb:87:5:87:17 | puts | +| calls.rb:97:1:100:3 | Object | call_block | calls.rb:67:1:69:3 | call_block | +| calls.rb:97:1:100:3 | Object | foo | calls.rb:1:1:3:3 | foo | +| calls.rb:97:1:100:3 | Object | foo | calls.rb:71:1:75:3 | foo | +| calls.rb:97:1:100:3 | Object | funny | calls.rb:119:1:121:3 | funny | +| calls.rb:97:1:100:3 | Object | indirect | calls.rb:137:1:139:3 | indirect | +| calls.rb:97:1:100:3 | Object | new | calls.rb:99:5:99:16 | new | +| calls.rb:97:1:100:3 | Object | optional_arg | calls.rb:62:1:65:3 | optional_arg | +| calls.rb:97:1:100:3 | Object | private_on_main | calls.rb:164:1:165:3 | private_on_main | +| calls.rb:97:1:100:3 | Object | private_on_main | private.rb:21:1:22:3 | private_on_main | +| calls.rb:97:1:100:3 | Object | puts | calls.rb:87:5:87:17 | puts | +| calls.rb:102:1:104:3 | Hash | [] | calls.rb:103:5:103:15 | [] | +| calls.rb:102:1:104:3 | Hash | call_block | calls.rb:67:1:69:3 | call_block | +| calls.rb:102:1:104:3 | Hash | foo | calls.rb:1:1:3:3 | foo | +| calls.rb:102:1:104:3 | Hash | foo | calls.rb:71:1:75:3 | foo | +| calls.rb:102:1:104:3 | Hash | funny | calls.rb:119:1:121:3 | funny | +| calls.rb:102:1:104:3 | Hash | indirect | calls.rb:137:1:139:3 | indirect | +| calls.rb:102:1:104:3 | Hash | new | calls.rb:99:5:99:16 | new | +| calls.rb:102:1:104:3 | Hash | optional_arg | calls.rb:62:1:65:3 | optional_arg | +| calls.rb:102:1:104:3 | Hash | private_on_main | calls.rb:164:1:165:3 | private_on_main | +| calls.rb:102:1:104:3 | Hash | puts | calls.rb:87:5:87:17 | puts | +| calls.rb:106:1:117:3 | Array | [] | calls.rb:107:3:107:13 | [] | +| calls.rb:106:1:117:3 | Array | call_block | calls.rb:67:1:69:3 | call_block | +| calls.rb:106:1:117:3 | Array | foo | calls.rb:1:1:3:3 | foo | +| calls.rb:106:1:117:3 | Array | foo | calls.rb:71:1:75:3 | foo | +| calls.rb:106:1:117:3 | Array | foreach | calls.rb:110:3:116:5 | foreach | +| calls.rb:106:1:117:3 | Array | funny | calls.rb:119:1:121:3 | funny | +| calls.rb:106:1:117:3 | Array | indirect | calls.rb:137:1:139:3 | indirect | +| calls.rb:106:1:117:3 | Array | length | calls.rb:108:3:108:17 | length | +| calls.rb:106:1:117:3 | Array | new | calls.rb:99:5:99:16 | new | +| calls.rb:106:1:117:3 | Array | optional_arg | calls.rb:62:1:65:3 | optional_arg | +| calls.rb:106:1:117:3 | Array | private_on_main | calls.rb:164:1:165:3 | private_on_main | +| calls.rb:106:1:117:3 | Array | puts | calls.rb:87:5:87:17 | puts | +| calls.rb:144:1:148:3 | S | call_block | calls.rb:67:1:69:3 | call_block | +| calls.rb:144:1:148:3 | S | foo | calls.rb:1:1:3:3 | foo | +| calls.rb:144:1:148:3 | S | foo | calls.rb:71:1:75:3 | foo | +| calls.rb:144:1:148:3 | S | funny | calls.rb:119:1:121:3 | funny | +| calls.rb:144:1:148:3 | S | indirect | calls.rb:137:1:139:3 | indirect | +| calls.rb:144:1:148:3 | S | new | calls.rb:99:5:99:16 | new | +| calls.rb:144:1:148:3 | S | optional_arg | calls.rb:62:1:65:3 | optional_arg | +| calls.rb:144:1:148:3 | S | private_on_main | calls.rb:164:1:165:3 | private_on_main | +| calls.rb:144:1:148:3 | S | puts | calls.rb:87:5:87:17 | puts | +| calls.rb:144:1:148:3 | S | s_method | calls.rb:145:5:147:7 | s_method | +| calls.rb:150:1:153:3 | A | call_block | calls.rb:67:1:69:3 | call_block | +| calls.rb:150:1:153:3 | A | foo | calls.rb:1:1:3:3 | foo | +| calls.rb:150:1:153:3 | A | foo | calls.rb:71:1:75:3 | foo | +| calls.rb:150:1:153:3 | A | funny | calls.rb:119:1:121:3 | funny | +| calls.rb:150:1:153:3 | A | indirect | calls.rb:137:1:139:3 | indirect | +| calls.rb:150:1:153:3 | A | new | calls.rb:99:5:99:16 | new | +| calls.rb:150:1:153:3 | A | optional_arg | calls.rb:62:1:65:3 | optional_arg | +| calls.rb:150:1:153:3 | A | private_on_main | calls.rb:164:1:165:3 | private_on_main | +| calls.rb:150:1:153:3 | A | puts | calls.rb:87:5:87:17 | puts | +| calls.rb:150:1:153:3 | A | s_method | calls.rb:145:5:147:7 | s_method | +| calls.rb:150:1:153:3 | A | to_s | calls.rb:151:5:152:7 | to_s | +| calls.rb:155:1:158:3 | B | call_block | calls.rb:67:1:69:3 | call_block | +| calls.rb:155:1:158:3 | B | foo | calls.rb:1:1:3:3 | foo | +| calls.rb:155:1:158:3 | B | foo | calls.rb:71:1:75:3 | foo | +| calls.rb:155:1:158:3 | B | funny | calls.rb:119:1:121:3 | funny | +| calls.rb:155:1:158:3 | B | indirect | calls.rb:137:1:139:3 | indirect | +| calls.rb:155:1:158:3 | B | new | calls.rb:99:5:99:16 | new | +| calls.rb:155:1:158:3 | B | optional_arg | calls.rb:62:1:65:3 | optional_arg | +| calls.rb:155:1:158:3 | B | private_on_main | calls.rb:164:1:165:3 | private_on_main | +| calls.rb:155:1:158:3 | B | puts | calls.rb:87:5:87:17 | puts | +| calls.rb:155:1:158:3 | B | s_method | calls.rb:145:5:147:7 | s_method | +| calls.rb:155:1:158:3 | B | to_s | calls.rb:156:5:157:7 | to_s | +| file://:0:0:0:0 | Class | include | calls.rb:92:5:92:20 | include | +| file://:0:0:0:0 | Class | module_eval | calls.rb:91:5:91:24 | module_eval | +| file://:0:0:0:0 | Class | new | calls.rb:99:5:99:16 | new | +| file://:0:0:0:0 | Class | prepend | calls.rb:93:5:93:20 | prepend | +| file://:0:0:0:0 | Class | private | calls.rb:94:5:94:20 | private | +| file://:0:0:0:0 | Class | puts | calls.rb:87:5:87:17 | puts | +| file://:0:0:0:0 | Complex | new | calls.rb:99:5:99:16 | new | +| file://:0:0:0:0 | Complex | puts | calls.rb:87:5:87:17 | puts | +| file://:0:0:0:0 | FalseClass | new | calls.rb:99:5:99:16 | new | +| file://:0:0:0:0 | FalseClass | puts | calls.rb:87:5:87:17 | puts | +| file://:0:0:0:0 | Float | new | calls.rb:99:5:99:16 | new | +| file://:0:0:0:0 | Float | puts | calls.rb:87:5:87:17 | puts | +| file://:0:0:0:0 | NilClass | new | calls.rb:99:5:99:16 | new | +| file://:0:0:0:0 | NilClass | puts | calls.rb:87:5:87:17 | puts | +| file://:0:0:0:0 | Numeric | new | calls.rb:99:5:99:16 | new | +| file://:0:0:0:0 | Numeric | puts | calls.rb:87:5:87:17 | puts | +| file://:0:0:0:0 | Rational | new | calls.rb:99:5:99:16 | new | +| file://:0:0:0:0 | Rational | puts | calls.rb:87:5:87:17 | puts | +| file://:0:0:0:0 | TrueClass | new | calls.rb:99:5:99:16 | new | +| file://:0:0:0:0 | TrueClass | puts | calls.rb:87:5:87:17 | puts | +| hello.rb:1:1:8:3 | EnglishWords | hello | hello.rb:2:5:4:7 | hello | +| hello.rb:1:1:8:3 | EnglishWords | world | hello.rb:5:5:7:7 | world | +| hello.rb:11:1:16:3 | Greeting | hello | hello.rb:2:5:4:7 | hello | +| hello.rb:11:1:16:3 | Greeting | message | hello.rb:13:5:15:7 | message | +| hello.rb:11:1:16:3 | Greeting | new | calls.rb:99:5:99:16 | new | +| hello.rb:11:1:16:3 | Greeting | puts | calls.rb:87:5:87:17 | puts | +| hello.rb:11:1:16:3 | Greeting | world | hello.rb:5:5:7:7 | world | +| hello.rb:18:1:22:3 | HelloWorld | hello | hello.rb:2:5:4:7 | hello | +| hello.rb:18:1:22:3 | HelloWorld | message | hello.rb:19:5:21:7 | message | +| hello.rb:18:1:22:3 | HelloWorld | new | calls.rb:99:5:99:16 | new | +| hello.rb:18:1:22:3 | HelloWorld | puts | calls.rb:87:5:87:17 | puts | +| hello.rb:18:1:22:3 | HelloWorld | world | hello.rb:5:5:7:7 | world | +| modules.rb:4:1:24:3 | Foo | method_in_another_definition_of_foo | modules.rb:27:3:28:5 | method_in_another_definition_of_foo | +| modules.rb:4:1:24:3 | Foo | method_in_foo | modules.rb:16:3:17:5 | method_in_foo | +| modules.rb:5:3:14:5 | Foo::Bar | method_in_another_definition_of_foo_bar | modules.rb:52:3:53:5 | method_in_another_definition_of_foo_bar | +| modules.rb:5:3:14:5 | Foo::Bar | method_in_foo_bar | modules.rb:9:5:10:7 | method_in_foo_bar | +| modules.rb:6:5:7:7 | Foo::Bar::ClassInFooBar | new | calls.rb:99:5:99:16 | new | +| modules.rb:6:5:7:7 | Foo::Bar::ClassInFooBar | puts | calls.rb:87:5:87:17 | puts | +| modules.rb:19:3:20:5 | Foo::ClassInFoo | new | calls.rb:99:5:99:16 | new | +| modules.rb:19:3:20:5 | Foo::ClassInFoo | puts | calls.rb:87:5:87:17 | puts | +| modules.rb:30:3:31:5 | Foo::ClassInAnotherDefinitionOfFoo | new | calls.rb:99:5:99:16 | new | +| modules.rb:30:3:31:5 | Foo::ClassInAnotherDefinitionOfFoo | puts | calls.rb:87:5:87:17 | puts | +| modules.rb:37:1:46:3 | Bar | method_a | modules.rb:38:3:39:5 | method_a | +| modules.rb:37:1:46:3 | Bar | method_b | modules.rb:41:3:42:5 | method_b | +| modules.rb:37:1:46:3 | Bar | new | calls.rb:99:5:99:16 | new | +| modules.rb:37:1:46:3 | Bar | puts | calls.rb:87:5:87:17 | puts | +| modules.rb:49:3:50:5 | Foo::Bar::ClassInAnotherDefinitionOfFooBar | new | calls.rb:99:5:99:16 | new | +| modules.rb:49:3:50:5 | Foo::Bar::ClassInAnotherDefinitionOfFooBar | puts | calls.rb:87:5:87:17 | puts | +| modules.rb:66:5:67:7 | Test::Foo1::Bar | new | calls.rb:99:5:99:16 | new | +| modules.rb:66:5:67:7 | Test::Foo1::Bar | puts | calls.rb:87:5:87:17 | puts | +| modules.rb:72:5:73:7 | Test::Foo2::Foo2::Bar | new | calls.rb:99:5:99:16 | new | +| modules.rb:72:5:73:7 | Test::Foo2::Foo2::Bar | puts | calls.rb:87:5:87:17 | puts | +| modules.rb:112:1:113:3 | YY | new | calls.rb:99:5:99:16 | new | +| modules.rb:112:1:113:3 | YY | puts | calls.rb:87:5:87:17 | puts | +| modules.rb:116:7:117:9 | XX::YY | new | calls.rb:99:5:99:16 | new | +| modules.rb:116:7:117:9 | XX::YY | puts | calls.rb:87:5:87:17 | puts | +| private.rb:1:1:19:3 | C | baz | calls.rb:37:5:43:7 | baz | +| private.rb:1:1:19:3 | C | call_block | calls.rb:67:1:69:3 | call_block | +| private.rb:1:1:19:3 | C | foo | calls.rb:1:1:3:3 | foo | +| private.rb:1:1:19:3 | C | foo | calls.rb:71:1:75:3 | foo | +| private.rb:1:1:19:3 | C | funny | calls.rb:119:1:121:3 | funny | +| private.rb:1:1:19:3 | C | indirect | calls.rb:137:1:139:3 | indirect | +| private.rb:1:1:19:3 | C | instance_m | calls.rb:16:5:16:23 | instance_m | +| private.rb:1:1:19:3 | C | new | calls.rb:99:5:99:16 | new | +| private.rb:1:1:19:3 | C | optional_arg | calls.rb:62:1:65:3 | optional_arg | +| private.rb:1:1:19:3 | C | private2 | private.rb:8:3:9:5 | private2 | +| private.rb:1:1:19:3 | C | private3 | private.rb:14:3:15:5 | private3 | +| private.rb:1:1:19:3 | C | private4 | private.rb:17:3:18:5 | private4 | +| private.rb:1:1:19:3 | C | private_on_main | calls.rb:164:1:165:3 | private_on_main | +| private.rb:1:1:19:3 | C | private_on_main | private.rb:21:1:22:3 | private_on_main | +| private.rb:1:1:19:3 | C | public | private.rb:5:3:6:5 | public | +| private.rb:1:1:19:3 | C | puts | calls.rb:87:5:87:17 | puts | diff --git a/ruby/ql/test/library-tests/modules/methods.ql b/ruby/ql/test/library-tests/modules/methods.ql new file mode 100644 index 000000000000..e92ce65e6842 --- /dev/null +++ b/ruby/ql/test/library-tests/modules/methods.ql @@ -0,0 +1,8 @@ +import ruby +import codeql.ruby.ast.internal.Module as M + +query MethodBase getMethod(Module m, string name) { + result = M::ExposedForTestingOnly::getMethod(m, name) +} + +query MethodBase lookupMethod(Module m, string name) { result = M::lookupMethod(m, name) } diff --git a/ruby/ql/test/library-tests/modules/modules.expected b/ruby/ql/test/library-tests/modules/modules.expected new file mode 100644 index 000000000000..b7402d830032 --- /dev/null +++ b/ruby/ql/test/library-tests/modules/modules.expected @@ -0,0 +1,152 @@ +getModule +| calls.rb:15:1:24:3 | M | +| calls.rb:51:1:55:3 | D | +| calls.rb:77:1:80:3 | Integer | +| calls.rb:82:1:84:3 | String | +| calls.rb:86:1:88:3 | Kernel | +| calls.rb:90:1:95:3 | Module | +| calls.rb:97:1:100:3 | Object | +| calls.rb:102:1:104:3 | Hash | +| calls.rb:106:1:117:3 | Array | +| calls.rb:144:1:148:3 | S | +| calls.rb:150:1:153:3 | A | +| calls.rb:155:1:158:3 | B | +| file://:0:0:0:0 | BasicObject | +| file://:0:0:0:0 | Class | +| file://:0:0:0:0 | Complex | +| file://:0:0:0:0 | FalseClass | +| file://:0:0:0:0 | Float | +| file://:0:0:0:0 | NilClass | +| file://:0:0:0:0 | Numeric | +| file://:0:0:0:0 | Proc | +| file://:0:0:0:0 | Rational | +| file://:0:0:0:0 | Symbol | +| file://:0:0:0:0 | TrueClass | +| hello.rb:1:1:8:3 | EnglishWords | +| hello.rb:11:1:16:3 | Greeting | +| hello.rb:18:1:22:3 | HelloWorld | +| modules.rb:1:1:2:3 | Empty | +| modules.rb:4:1:24:3 | Foo | +| modules.rb:5:3:14:5 | Foo::Bar | +| modules.rb:6:5:7:7 | Foo::Bar::ClassInFooBar | +| modules.rb:19:3:20:5 | Foo::ClassInFoo | +| modules.rb:30:3:31:5 | Foo::ClassInAnotherDefinitionOfFoo | +| modules.rb:37:1:46:3 | Bar | +| modules.rb:49:3:50:5 | Foo::Bar::ClassInAnotherDefinitionOfFooBar | +| modules.rb:60:1:61:3 | MyModuleInGlobalScope | +| modules.rb:63:1:81:3 | Test | +| modules.rb:65:3:68:5 | Test::Foo1 | +| modules.rb:66:5:67:7 | Test::Foo1::Bar | +| modules.rb:70:3:74:5 | Test::Foo2 | +| modules.rb:71:5:71:19 | Test::Foo2::Foo2 | +| modules.rb:72:5:73:7 | Test::Foo2::Foo2::Bar | +| modules.rb:76:3:80:5 | Test::Foo3 | +| modules.rb:83:1:86:3 | Other | +| modules.rb:84:3:85:5 | Other::Foo1 | +| modules.rb:88:1:93:3 | IncludeTest | +| modules.rb:91:3:92:5 | Test::Foo1::Y | +| modules.rb:95:1:99:3 | IncludeTest2 | +| modules.rb:97:3:98:5 | Test::Foo1::Z | +| modules.rb:101:1:105:3 | PrependTest | +| modules.rb:103:3:104:5 | Test::Foo2::Y | +| modules.rb:107:1:110:3 | MM | +| modules.rb:108:3:109:5 | MM::MM | +| modules.rb:112:1:113:3 | YY | +| modules.rb:115:1:118:3 | XX | +| modules.rb:116:7:117:9 | XX::YY | +| modules.rb:120:1:121:3 | Test::Foo1::Bar::Baz | +| private.rb:1:1:19:3 | C | +getADeclaration +| calls.rb:15:1:24:3 | M | calls.rb:15:1:24:3 | M | +| calls.rb:51:1:55:3 | D | calls.rb:51:1:55:3 | D | +| calls.rb:77:1:80:3 | Integer | calls.rb:77:1:80:3 | Integer | +| calls.rb:82:1:84:3 | String | calls.rb:82:1:84:3 | String | +| calls.rb:86:1:88:3 | Kernel | calls.rb:86:1:88:3 | Kernel | +| calls.rb:90:1:95:3 | Module | calls.rb:90:1:95:3 | Module | +| calls.rb:97:1:100:3 | Object | calls.rb:1:1:167:16 | calls.rb | +| calls.rb:97:1:100:3 | Object | calls.rb:97:1:100:3 | Object | +| calls.rb:97:1:100:3 | Object | hello.rb:1:1:22:3 | hello.rb | +| calls.rb:97:1:100:3 | Object | modules.rb:1:1:122:1 | modules.rb | +| calls.rb:97:1:100:3 | Object | private.rb:1:1:30:15 | private.rb | +| calls.rb:102:1:104:3 | Hash | calls.rb:102:1:104:3 | Hash | +| calls.rb:106:1:117:3 | Array | calls.rb:106:1:117:3 | Array | +| calls.rb:144:1:148:3 | S | calls.rb:144:1:148:3 | S | +| calls.rb:150:1:153:3 | A | calls.rb:150:1:153:3 | A | +| calls.rb:155:1:158:3 | B | calls.rb:155:1:158:3 | B | +| hello.rb:1:1:8:3 | EnglishWords | hello.rb:1:1:8:3 | EnglishWords | +| hello.rb:11:1:16:3 | Greeting | hello.rb:11:1:16:3 | Greeting | +| hello.rb:18:1:22:3 | HelloWorld | hello.rb:18:1:22:3 | HelloWorld | +| modules.rb:1:1:2:3 | Empty | modules.rb:1:1:2:3 | Empty | +| modules.rb:4:1:24:3 | Foo | modules.rb:4:1:24:3 | Foo | +| modules.rb:4:1:24:3 | Foo | modules.rb:26:1:35:3 | Foo | +| modules.rb:5:3:14:5 | Foo::Bar | modules.rb:5:3:14:5 | Bar | +| modules.rb:5:3:14:5 | Foo::Bar | modules.rb:48:1:57:3 | Bar | +| modules.rb:6:5:7:7 | Foo::Bar::ClassInFooBar | modules.rb:6:5:7:7 | ClassInFooBar | +| modules.rb:19:3:20:5 | Foo::ClassInFoo | modules.rb:19:3:20:5 | ClassInFoo | +| modules.rb:30:3:31:5 | Foo::ClassInAnotherDefinitionOfFoo | modules.rb:30:3:31:5 | ClassInAnotherDefinitionOfFoo | +| modules.rb:37:1:46:3 | Bar | modules.rb:37:1:46:3 | Bar | +| modules.rb:37:1:46:3 | Bar | modules.rb:78:5:79:7 | Bar | +| modules.rb:49:3:50:5 | Foo::Bar::ClassInAnotherDefinitionOfFooBar | modules.rb:49:3:50:5 | ClassInAnotherDefinitionOfFooBar | +| modules.rb:60:1:61:3 | MyModuleInGlobalScope | modules.rb:60:1:61:3 | MyModuleInGlobalScope | +| modules.rb:63:1:81:3 | Test | modules.rb:63:1:81:3 | Test | +| modules.rb:65:3:68:5 | Test::Foo1 | modules.rb:65:3:68:5 | Foo1 | +| modules.rb:66:5:67:7 | Test::Foo1::Bar | modules.rb:66:5:67:7 | Bar | +| modules.rb:70:3:74:5 | Test::Foo2 | modules.rb:70:3:74:5 | Foo2 | +| modules.rb:71:5:71:19 | Test::Foo2::Foo2 | modules.rb:71:5:71:19 | Foo2 | +| modules.rb:72:5:73:7 | Test::Foo2::Foo2::Bar | modules.rb:72:5:73:7 | Bar | +| modules.rb:76:3:80:5 | Test::Foo3 | modules.rb:76:3:80:5 | Foo3 | +| modules.rb:83:1:86:3 | Other | modules.rb:83:1:86:3 | Other | +| modules.rb:84:3:85:5 | Other::Foo1 | modules.rb:84:3:85:5 | Foo1 | +| modules.rb:88:1:93:3 | IncludeTest | modules.rb:88:1:93:3 | IncludeTest | +| modules.rb:91:3:92:5 | Test::Foo1::Y | modules.rb:91:3:92:5 | Y | +| modules.rb:95:1:99:3 | IncludeTest2 | modules.rb:95:1:99:3 | IncludeTest2 | +| modules.rb:97:3:98:5 | Test::Foo1::Z | modules.rb:97:3:98:5 | Z | +| modules.rb:101:1:105:3 | PrependTest | modules.rb:101:1:105:3 | PrependTest | +| modules.rb:103:3:104:5 | Test::Foo2::Y | modules.rb:103:3:104:5 | Y | +| modules.rb:107:1:110:3 | MM | modules.rb:107:1:110:3 | MM | +| modules.rb:108:3:109:5 | MM::MM | modules.rb:108:3:109:5 | MM | +| modules.rb:112:1:113:3 | YY | modules.rb:112:1:113:3 | YY | +| modules.rb:115:1:118:3 | XX | modules.rb:115:1:118:3 | XX | +| modules.rb:116:7:117:9 | XX::YY | modules.rb:116:7:117:9 | YY | +| modules.rb:120:1:121:3 | Test::Foo1::Bar::Baz | modules.rb:120:1:121:3 | Baz | +| private.rb:1:1:19:3 | C | calls.rb:29:1:44:3 | C | +| private.rb:1:1:19:3 | C | private.rb:1:1:19:3 | C | +getSuperClass +| calls.rb:51:1:55:3 | D | private.rb:1:1:19:3 | C | +| calls.rb:77:1:80:3 | Integer | file://:0:0:0:0 | Numeric | +| calls.rb:82:1:84:3 | String | calls.rb:97:1:100:3 | Object | +| calls.rb:90:1:95:3 | Module | calls.rb:97:1:100:3 | Object | +| calls.rb:97:1:100:3 | Object | file://:0:0:0:0 | BasicObject | +| calls.rb:102:1:104:3 | Hash | calls.rb:97:1:100:3 | Object | +| calls.rb:106:1:117:3 | Array | calls.rb:97:1:100:3 | Object | +| calls.rb:144:1:148:3 | S | calls.rb:97:1:100:3 | Object | +| calls.rb:150:1:153:3 | A | calls.rb:144:1:148:3 | S | +| calls.rb:155:1:158:3 | B | calls.rb:144:1:148:3 | S | +| file://:0:0:0:0 | Class | calls.rb:90:1:95:3 | Module | +| file://:0:0:0:0 | Complex | file://:0:0:0:0 | Numeric | +| file://:0:0:0:0 | FalseClass | calls.rb:97:1:100:3 | Object | +| file://:0:0:0:0 | Float | file://:0:0:0:0 | Numeric | +| file://:0:0:0:0 | NilClass | calls.rb:97:1:100:3 | Object | +| file://:0:0:0:0 | Numeric | calls.rb:97:1:100:3 | Object | +| file://:0:0:0:0 | Rational | file://:0:0:0:0 | Numeric | +| file://:0:0:0:0 | TrueClass | calls.rb:97:1:100:3 | Object | +| hello.rb:11:1:16:3 | Greeting | calls.rb:97:1:100:3 | Object | +| hello.rb:18:1:22:3 | HelloWorld | hello.rb:11:1:16:3 | Greeting | +| modules.rb:6:5:7:7 | Foo::Bar::ClassInFooBar | calls.rb:97:1:100:3 | Object | +| modules.rb:19:3:20:5 | Foo::ClassInFoo | calls.rb:97:1:100:3 | Object | +| modules.rb:30:3:31:5 | Foo::ClassInAnotherDefinitionOfFoo | calls.rb:97:1:100:3 | Object | +| modules.rb:37:1:46:3 | Bar | calls.rb:97:1:100:3 | Object | +| modules.rb:49:3:50:5 | Foo::Bar::ClassInAnotherDefinitionOfFooBar | calls.rb:97:1:100:3 | Object | +| modules.rb:66:5:67:7 | Test::Foo1::Bar | calls.rb:97:1:100:3 | Object | +| modules.rb:72:5:73:7 | Test::Foo2::Foo2::Bar | calls.rb:97:1:100:3 | Object | +| modules.rb:112:1:113:3 | YY | calls.rb:97:1:100:3 | Object | +| modules.rb:116:7:117:9 | XX::YY | modules.rb:112:1:113:3 | YY | +| private.rb:1:1:19:3 | C | calls.rb:97:1:100:3 | Object | +getAPrependedModule +| modules.rb:101:1:105:3 | PrependTest | modules.rb:63:1:81:3 | Test | +getAnIncludedModule +| calls.rb:97:1:100:3 | Object | calls.rb:86:1:88:3 | Kernel | +| hello.rb:11:1:16:3 | Greeting | hello.rb:1:1:8:3 | EnglishWords | +| modules.rb:88:1:93:3 | IncludeTest | modules.rb:63:1:81:3 | Test | +| modules.rb:95:1:99:3 | IncludeTest2 | modules.rb:63:1:81:3 | Test | +| private.rb:1:1:19:3 | C | calls.rb:15:1:24:3 | M | diff --git a/ruby/ql/test/library-tests/modules/modules.ql b/ruby/ql/test/library-tests/modules/modules.ql new file mode 100644 index 000000000000..a29d2e937565 --- /dev/null +++ b/ruby/ql/test/library-tests/modules/modules.ql @@ -0,0 +1,11 @@ +import ruby + +query Module getModule() { any() } + +query ModuleBase getADeclaration(Module m) { result = m.getADeclaration() } + +query Module getSuperClass(Module m) { result = m.getSuperClass() } + +query Module getAPrependedModule(Module m) { result = m.getAPrependedModule() } + +query Module getAnIncludedModule(Module m) { result = m.getAnIncludedModule() } diff --git a/ruby/ql/test/library-tests/modules/modules.rb b/ruby/ql/test/library-tests/modules/modules.rb new file mode 100644 index 000000000000..8287b0a1bc40 --- /dev/null +++ b/ruby/ql/test/library-tests/modules/modules.rb @@ -0,0 +1,122 @@ +module Empty +end + +module Foo + module Bar + class ClassInFooBar + end + + def method_in_foo_bar + end + + puts 'module Foo::Bar' + $global_var = 0 + end + + def method_in_foo + end + + class ClassInFoo + end + + puts 'module Foo' + $global_var = 1 +end + +module Foo + def method_in_another_definition_of_foo + end + + class ClassInAnotherDefinitionOfFoo + end + + puts 'module Foo again' + $global_var = 2 +end + +module Bar + def method_a + end + + def method_b + end + + puts 'module Bar' + $global_var = 3 +end + +module Foo::Bar + class ClassInAnotherDefinitionOfFooBar + end + + def method_in_another_definition_of_foo_bar + end + + puts 'module Foo::Bar again' + $global_var = 4 +end + +# a module where the name is a scope resolution using the global scope +module ::MyModuleInGlobalScope +end + +module Test + + module Foo1 + class Foo1::Bar + end + end + + module Foo2 + module Foo2 end + class Foo2::Bar + end + end + + module Foo3 + Foo3 = Object + class Foo3::Bar + end + end +end + +module Other + module Foo1 + end +end + +module IncludeTest + include ::Test + Object.module_eval { prepend Other } + module Foo1::Y + end +end + +module IncludeTest2 + include Test + module Foo1::Z + end +end + +module PrependTest + prepend ::Test + module Foo2::Y + end +end + +module MM + module MM::MM + end +end + +class YY +end + +module XX + class YY < YY + end +end + +module Test::Foo1::Bar::Baz +end + diff --git a/ruby/ql/test/library-tests/modules/private.rb b/ruby/ql/test/library-tests/modules/private.rb new file mode 100644 index 000000000000..5c1d666be3a6 --- /dev/null +++ b/ruby/ql/test/library-tests/modules/private.rb @@ -0,0 +1,30 @@ +class C + private def private1 + end + + def public + end + + def private2 + end + private :private2 + + private + + def private3 + end + + def private4 + end +end + +def private_on_main +end + +C.new.private1 +C.new.private2 +C.new.private3 +C.new.private4 +C.new.public + +private_on_main \ No newline at end of file diff --git a/ruby/ql/test/library-tests/modules/superclasses.expected b/ruby/ql/test/library-tests/modules/superclasses.expected new file mode 100644 index 000000000000..b1948a9976ea --- /dev/null +++ b/ruby/ql/test/library-tests/modules/superclasses.expected @@ -0,0 +1,148 @@ +calls.rb: +# 102| Hash +#-----| -> Object + +# 77| Integer +#-----| -> Numeric + +# 86| Kernel + +# 90| Module +#-----| -> Object + +# 97| Object +#-----| -> BasicObject + +# 106| Array +#-----| -> Object + +#-----| BasicObject + +#-----| Class +#-----| -> Module + +#-----| Complex +#-----| -> Numeric + +#-----| FalseClass +#-----| -> Object + +#-----| Float +#-----| -> Numeric + +#-----| NilClass +#-----| -> Object + +#-----| Numeric +#-----| -> Object + +#-----| Proc + +#-----| Rational +#-----| -> Numeric + +#-----| Symbol + +#-----| TrueClass +#-----| -> Object + +# 15| M + +private.rb: +# 1| C +#-----| -> Object + +calls.rb: +# 51| D +#-----| -> C + +# 82| String +#-----| -> Object + +# 144| S +#-----| -> Object + +# 150| A +#-----| -> S + +# 155| B +#-----| -> S + +hello.rb: +# 1| EnglishWords + +# 11| Greeting +#-----| -> Object + +# 18| HelloWorld +#-----| -> Greeting + +modules.rb: +# 1| Empty + +# 4| Foo + +# 37| Bar +#-----| -> Object + +# 60| MyModuleInGlobalScope + +# 63| Test + +# 83| Other + +# 88| IncludeTest + +# 95| IncludeTest2 + +# 101| PrependTest + +# 107| MM + +# 112| YY +#-----| -> Object + +# 115| XX + +# 5| Foo::Bar + +# 19| Foo::ClassInFoo +#-----| -> Object + +# 30| Foo::ClassInAnotherDefinitionOfFoo +#-----| -> Object + +# 116| XX::YY +#-----| -> YY + +# 65| Test::Foo1 + +# 70| Test::Foo2 + +# 76| Test::Foo3 + +# 84| Other::Foo1 + +# 6| Foo::Bar::ClassInFooBar +#-----| -> Object + +# 71| Test::Foo2::Foo2 + +# 108| MM::MM + +# 49| Foo::Bar::ClassInAnotherDefinitionOfFooBar +#-----| -> Object + +# 66| Test::Foo1::Bar +#-----| -> Object + +# 91| Test::Foo1::Y + +# 97| Test::Foo1::Z + +# 103| Test::Foo2::Y + +# 72| Test::Foo2::Foo2::Bar +#-----| -> Object + +# 120| Test::Foo1::Bar::Baz diff --git a/ruby/ql/test/library-tests/modules/superclasses.ql b/ruby/ql/test/library-tests/modules/superclasses.ql new file mode 100644 index 000000000000..d2141aa38ff6 --- /dev/null +++ b/ruby/ql/test/library-tests/modules/superclasses.ql @@ -0,0 +1,12 @@ +/** + * @kind graph + * @id rb/test/supertypes + */ + +import ruby + +query predicate nodes(Module node, string key, string value) { + key = "semmle.label" and value = node.toString() +} + +query predicate edges(Module source, Module target) { target = source.getSuperClass() } diff --git a/ruby/ql/test/library-tests/regexp/parse.expected b/ruby/ql/test/library-tests/regexp/parse.expected new file mode 100644 index 000000000000..130d828a5d57 --- /dev/null +++ b/ruby/ql/test/library-tests/regexp/parse.expected @@ -0,0 +1,556 @@ +regexp.rb: +# 5| [RegExpConstant, RegExpNormalChar] a + +# 5| [RegExpSequence] abc +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a +#-----| 1 -> [RegExpConstant, RegExpNormalChar] b +#-----| 2 -> [RegExpConstant, RegExpNormalChar] c + +# 5| [RegExpConstant, RegExpNormalChar] b + +# 5| [RegExpConstant, RegExpNormalChar] c + +# 8| [RegExpStar] a* +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a + +# 8| [RegExpConstant, RegExpNormalChar] a + +# 8| [RegExpSequence] a*b+c?d +#-----| 0 -> [RegExpStar] a* +#-----| 1 -> [RegExpPlus] b+ +#-----| 2 -> [RegExpOpt] c? +#-----| 3 -> [RegExpConstant, RegExpNormalChar] d + +# 8| [RegExpPlus] b+ +#-----| 0 -> [RegExpConstant, RegExpNormalChar] b + +# 8| [RegExpConstant, RegExpNormalChar] b + +# 8| [RegExpOpt] c? +#-----| 0 -> [RegExpConstant, RegExpNormalChar] c + +# 8| [RegExpConstant, RegExpNormalChar] c + +# 8| [RegExpConstant, RegExpNormalChar] d + +# 9| [RegExpRange] a{4,8} +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a + +# 9| [RegExpConstant, RegExpNormalChar] a + +# 9| [RegExpNormalChar] 4 + +# 9| [RegExpNormalChar] , + +# 9| [RegExpNormalChar] 8 + +# 9| [RegExpNormalChar] } + +# 10| [RegExpRange] a{,8} +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a + +# 10| [RegExpConstant, RegExpNormalChar] a + +# 10| [RegExpNormalChar] , + +# 10| [RegExpNormalChar] 8 + +# 10| [RegExpNormalChar] } + +# 11| [InfiniteRepetitionQuantifier, RegExpRange] a{3,} +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a + +# 11| [RegExpConstant, RegExpNormalChar] a + +# 11| [RegExpNormalChar] 3 + +# 11| [RegExpNormalChar] , + +# 11| [RegExpNormalChar] } + +# 12| [RegExpRange] a{7} +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a + +# 12| [RegExpConstant, RegExpNormalChar] a + +# 12| [RegExpNormalChar] 7 + +# 12| [RegExpNormalChar] } + +# 15| [RegExpAlt] foo|bar +#-----| 0 -> [RegExpSequence] foo +#-----| 1 -> [RegExpSequence] bar + +# 15| [RegExpConstant, RegExpNormalChar] f + +# 15| [RegExpSequence] foo +#-----| 0 -> [RegExpConstant, RegExpNormalChar] f +#-----| 1 -> [RegExpConstant, RegExpNormalChar] o +#-----| 2 -> [RegExpConstant, RegExpNormalChar] o + +# 15| [RegExpConstant, RegExpNormalChar] o + +# 15| [RegExpConstant, RegExpNormalChar] o + +# 15| [RegExpConstant, RegExpNormalChar] b + +# 15| [RegExpSequence] bar +#-----| 0 -> [RegExpConstant, RegExpNormalChar] b +#-----| 1 -> [RegExpConstant, RegExpNormalChar] a +#-----| 2 -> [RegExpConstant, RegExpNormalChar] r + +# 15| [RegExpConstant, RegExpNormalChar] a + +# 15| [RegExpConstant, RegExpNormalChar] r + +# 18| [RegExpCharacterClass] [abc] +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a +#-----| 1 -> [RegExpConstant, RegExpNormalChar] b +#-----| 2 -> [RegExpConstant, RegExpNormalChar] c + +# 18| [RegExpConstant, RegExpNormalChar] a + +# 18| [RegExpConstant, RegExpNormalChar] b + +# 18| [RegExpConstant, RegExpNormalChar] c + +# 19| [RegExpCharacterClass] [a-fA-F0-9_] +#-----| 0 -> [RegExpCharacterRange] a-f +#-----| 1 -> [RegExpCharacterRange] A-F +#-----| 2 -> [RegExpCharacterRange] 0-9 +#-----| 3 -> [RegExpConstant, RegExpNormalChar] _ + +# 19| [RegExpCharacterRange] a-f +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a +#-----| 1 -> [RegExpConstant, RegExpNormalChar] f + +# 19| [RegExpConstant, RegExpNormalChar] a + +# 19| [RegExpConstant, RegExpNormalChar] f + +# 19| [RegExpCharacterRange] A-F +#-----| 0 -> [RegExpConstant, RegExpNormalChar] A +#-----| 1 -> [RegExpConstant, RegExpNormalChar] F + +# 19| [RegExpConstant, RegExpNormalChar] A + +# 19| [RegExpConstant, RegExpNormalChar] F + +# 19| [RegExpCharacterRange] 0-9 +#-----| 0 -> [RegExpConstant, RegExpNormalChar] 0 +#-----| 1 -> [RegExpConstant, RegExpNormalChar] 9 + +# 19| [RegExpConstant, RegExpNormalChar] 0 + +# 19| [RegExpConstant, RegExpNormalChar] 9 + +# 19| [RegExpConstant, RegExpNormalChar] _ + +# 20| [RegExpCaret] \A + +# 20| [RegExpSequence] \A[+-]?\d+ +#-----| 0 -> [RegExpCaret] \A +#-----| 1 -> [RegExpOpt] [+-]? +#-----| 2 -> [RegExpPlus] \d+ + +# 20| [RegExpCharacterClass] [+-] +#-----| 0 -> [RegExpConstant, RegExpNormalChar] + +#-----| 1 -> [RegExpConstant, RegExpNormalChar] - + +# 20| [RegExpOpt] [+-]? +#-----| 0 -> [RegExpCharacterClass] [+-] + +# 20| [RegExpConstant, RegExpNormalChar] + + +# 20| [RegExpConstant, RegExpNormalChar] - + +# 20| [RegExpPlus] \d+ +#-----| 0 -> [RegExpCharacterClassEscape] \d + +# 20| [RegExpCharacterClassEscape] \d + +# 21| [RegExpCharacterClass] [\w] +#-----| 0 -> [RegExpCharacterClassEscape] \w + +# 21| [RegExpPlus] [\w]+ +#-----| 0 -> [RegExpCharacterClass] [\w] + +# 21| [RegExpCharacterClassEscape] \w + +# 22| [RegExpConstant, RegExpEscape] \[ + +# 22| [RegExpSequence] \[\][123] +#-----| 0 -> [RegExpConstant, RegExpEscape] \[ +#-----| 1 -> [RegExpConstant, RegExpEscape] \] +#-----| 2 -> [RegExpCharacterClass] [123] + +# 22| [RegExpConstant, RegExpEscape] \] + +# 22| [RegExpCharacterClass] [123] +#-----| 0 -> [RegExpConstant, RegExpNormalChar] 1 +#-----| 1 -> [RegExpConstant, RegExpNormalChar] 2 +#-----| 2 -> [RegExpConstant, RegExpNormalChar] 3 + +# 22| [RegExpConstant, RegExpNormalChar] 1 + +# 22| [RegExpConstant, RegExpNormalChar] 2 + +# 22| [RegExpConstant, RegExpNormalChar] 3 + +# 23| [RegExpCharacterClass] [^A-Z] +#-----| 0 -> [RegExpCharacterRange] A-Z + +# 23| [RegExpCharacterRange] A-Z +#-----| 0 -> [RegExpConstant, RegExpNormalChar] A +#-----| 1 -> [RegExpConstant, RegExpNormalChar] Z + +# 23| [RegExpConstant, RegExpNormalChar] A + +# 23| [RegExpConstant, RegExpNormalChar] Z + +# 24| [RegExpCharacterClass] []] +#-----| 0 -> [RegExpConstant, RegExpNormalChar] ] + +# 24| [RegExpConstant, RegExpNormalChar] ] + +# 25| [RegExpCharacterClass] [^]] +#-----| 0 -> [RegExpConstant, RegExpNormalChar] ] + +# 25| [RegExpConstant, RegExpNormalChar] ] + +# 26| [RegExpCharacterClass] [^-] +#-----| 0 -> [RegExpConstant, RegExpNormalChar] - + +# 26| [RegExpConstant, RegExpNormalChar] - + +# 29| [RegExpCharacterClass] [[a-f] +#-----| 0 -> [RegExpConstant, RegExpNormalChar] [ +#-----| 1 -> [RegExpCharacterRange] a-f + +# 29| [RegExpSequence] [[a-f]A-F] +#-----| 0 -> [RegExpCharacterClass] [[a-f] +#-----| 1 -> [RegExpConstant, RegExpNormalChar] A +#-----| 2 -> [RegExpConstant, RegExpNormalChar] - +#-----| 3 -> [RegExpConstant, RegExpNormalChar] F +#-----| 4 -> [RegExpConstant, RegExpNormalChar] ] + +# 29| [RegExpConstant, RegExpNormalChar] [ + +# 29| [RegExpCharacterRange] a-f +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a +#-----| 1 -> [RegExpConstant, RegExpNormalChar] f + +# 29| [RegExpConstant, RegExpNormalChar] a + +# 29| [RegExpConstant, RegExpNormalChar] f + +# 29| [RegExpConstant, RegExpNormalChar] A + +# 29| [RegExpConstant, RegExpNormalChar] - + +# 29| [RegExpConstant, RegExpNormalChar] F + +# 29| [RegExpConstant, RegExpNormalChar] ] + +# 32| [RegExpStar] .* +#-----| 0 -> [RegExpDot] . + +# 32| [RegExpDot] . + +# 33| [RegExpStar] .* +#-----| 0 -> [RegExpDot] . + +# 33| [RegExpDot] . + +# 34| [RegExpPlus] \w+ +#-----| 0 -> [RegExpCharacterClassEscape] \w + +# 34| [RegExpCharacterClassEscape] \w + +# 34| [RegExpSequence] \w+\W +#-----| 0 -> [RegExpPlus] \w+ +#-----| 1 -> [RegExpCharacterClassEscape] \W + +# 34| [RegExpCharacterClassEscape] \W + +# 35| [RegExpCharacterClassEscape] \s + +# 35| [RegExpSequence] \s\S +#-----| 0 -> [RegExpCharacterClassEscape] \s +#-----| 1 -> [RegExpCharacterClassEscape] \S + +# 35| [RegExpCharacterClassEscape] \S + +# 36| [RegExpCharacterClassEscape] \d + +# 36| [RegExpSequence] \d\D +#-----| 0 -> [RegExpCharacterClassEscape] \d +#-----| 1 -> [RegExpCharacterClassEscape] \D + +# 36| [RegExpCharacterClassEscape] \D + +# 37| [RegExpCharacterClassEscape] \h + +# 37| [RegExpSequence] \h\H +#-----| 0 -> [RegExpCharacterClassEscape] \h +#-----| 1 -> [RegExpCharacterClassEscape] \H + +# 37| [RegExpCharacterClassEscape] \H + +# 38| [RegExpConstant, RegExpEscape] \n + +# 38| [RegExpSequence] \n\r\t +#-----| 0 -> [RegExpConstant, RegExpEscape] \n +#-----| 1 -> [RegExpConstant, RegExpEscape] \r +#-----| 2 -> [RegExpConstant, RegExpEscape] \t + +# 38| [RegExpConstant, RegExpEscape] \r + +# 38| [RegExpConstant, RegExpEscape] \t + +# 41| [RegExpStar] (foo)* +#-----| 0 -> [RegExpGroup] (foo) + +# 41| [RegExpGroup] (foo) +#-----| 0 -> [RegExpSequence] foo + +# 41| [RegExpSequence] (foo)*bar +#-----| 0 -> [RegExpStar] (foo)* +#-----| 1 -> [RegExpConstant, RegExpNormalChar] b +#-----| 2 -> [RegExpConstant, RegExpNormalChar] a +#-----| 3 -> [RegExpConstant, RegExpNormalChar] r + +# 41| [RegExpConstant, RegExpNormalChar] f + +# 41| [RegExpSequence] foo +#-----| 0 -> [RegExpConstant, RegExpNormalChar] f +#-----| 1 -> [RegExpConstant, RegExpNormalChar] o +#-----| 2 -> [RegExpConstant, RegExpNormalChar] o + +# 41| [RegExpConstant, RegExpNormalChar] o + +# 41| [RegExpConstant, RegExpNormalChar] o + +# 41| [RegExpConstant, RegExpNormalChar] b + +# 41| [RegExpConstant, RegExpNormalChar] a + +# 41| [RegExpConstant, RegExpNormalChar] r + +# 42| [RegExpConstant, RegExpNormalChar] f + +# 42| [RegExpSequence] fo(o|b)ar +#-----| 0 -> [RegExpConstant, RegExpNormalChar] f +#-----| 1 -> [RegExpConstant, RegExpNormalChar] o +#-----| 2 -> [RegExpGroup] (o|b) +#-----| 3 -> [RegExpConstant, RegExpNormalChar] a +#-----| 4 -> [RegExpConstant, RegExpNormalChar] r + +# 42| [RegExpConstant, RegExpNormalChar] o + +# 42| [RegExpGroup] (o|b) +#-----| 0 -> [RegExpAlt] o|b + +# 42| [RegExpAlt] o|b +#-----| 0 -> [RegExpConstant, RegExpNormalChar] o +#-----| 1 -> [RegExpConstant, RegExpNormalChar] b + +# 42| [RegExpConstant, RegExpNormalChar] o + +# 42| [RegExpConstant, RegExpNormalChar] b + +# 42| [RegExpConstant, RegExpNormalChar] a + +# 42| [RegExpConstant, RegExpNormalChar] r + +# 43| [RegExpGroup] (a|b|cd) +#-----| 0 -> [RegExpAlt] a|b|cd + +# 43| [RegExpSequence] (a|b|cd)e +#-----| 0 -> [RegExpGroup] (a|b|cd) +#-----| 1 -> [RegExpConstant, RegExpNormalChar] e + +# 43| [RegExpAlt] a|b|cd +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a +#-----| 1 -> [RegExpConstant, RegExpNormalChar] b +#-----| 2 -> [RegExpSequence] cd + +# 43| [RegExpConstant, RegExpNormalChar] a + +# 43| [RegExpConstant, RegExpNormalChar] b + +# 43| [RegExpConstant, RegExpNormalChar] c + +# 43| [RegExpSequence] cd +#-----| 0 -> [RegExpConstant, RegExpNormalChar] c +#-----| 1 -> [RegExpConstant, RegExpNormalChar] d + +# 43| [RegExpConstant, RegExpNormalChar] d + +# 43| [RegExpConstant, RegExpNormalChar] e + +# 44| [RegExpGroup] (?::+) +#-----| 0 -> [RegExpPlus] :+ + +# 44| [RegExpSequence] (?::+)\w +#-----| 0 -> [RegExpGroup] (?::+) +#-----| 1 -> [RegExpCharacterClassEscape] \w + +# 44| [RegExpPlus] :+ +#-----| 0 -> [RegExpConstant, RegExpNormalChar] : + +# 44| [RegExpConstant, RegExpNormalChar] : + +# 44| [RegExpCharacterClassEscape] \w + +# 47| [RegExpGroup] (?\w+) +#-----| 0 -> [RegExpPlus] \w+ + +# 47| [RegExpPlus] \w+ +#-----| 0 -> [RegExpCharacterClassEscape] \w + +# 47| [RegExpCharacterClassEscape] \w + +# 48| [RegExpGroup] (?'foo'fo+) +#-----| 0 -> [RegExpSequence] fo+ + +# 48| [RegExpConstant, RegExpNormalChar] f + +# 48| [RegExpSequence] fo+ +#-----| 0 -> [RegExpConstant, RegExpNormalChar] f +#-----| 1 -> [RegExpPlus] o+ + +# 48| [RegExpPlus] o+ +#-----| 0 -> [RegExpConstant, RegExpNormalChar] o + +# 48| [RegExpConstant, RegExpNormalChar] o + +# 51| [RegExpGroup] (a+) +#-----| 0 -> [RegExpPlus] a+ + +# 51| [RegExpSequence] (a+)b+\1 +#-----| 0 -> [RegExpGroup] (a+) +#-----| 1 -> [RegExpPlus] b+ +#-----| 2 -> [RegExpBackRef] \1 + +# 51| [RegExpPlus] a+ +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a + +# 51| [RegExpConstant, RegExpNormalChar] a + +# 51| [RegExpPlus] b+ +#-----| 0 -> [RegExpConstant, RegExpNormalChar] b + +# 51| [RegExpConstant, RegExpNormalChar] b + +# 51| [RegExpBackRef] \1 + +# 52| [RegExpGroup] (?q+) +#-----| 0 -> [RegExpPlus] q+ + +# 52| [RegExpSequence] (?q+)\s+\k+ +#-----| 0 -> [RegExpGroup] (?q+) +#-----| 1 -> [RegExpPlus] \s+ +#-----| 2 -> [RegExpPlus] \k+ + +# 52| [RegExpPlus] q+ +#-----| 0 -> [RegExpConstant, RegExpNormalChar] q + +# 52| [RegExpConstant, RegExpNormalChar] q + +# 52| [RegExpPlus] \s+ +#-----| 0 -> [RegExpCharacterClassEscape] \s + +# 52| [RegExpCharacterClassEscape] \s + +# 52| [RegExpBackRef] \k + +# 52| [RegExpPlus] \k+ +#-----| 0 -> [RegExpBackRef] \k + +# 55| [RegExpNamedCharacterProperty] \p{Word} + +# 55| [RegExpStar] \p{Word}* +#-----| 0 -> [RegExpNamedCharacterProperty] \p{Word} + +# 56| [RegExpNamedCharacterProperty] \P{Digit} + +# 56| [RegExpPlus] \P{Digit}+ +#-----| 0 -> [RegExpNamedCharacterProperty] \P{Digit} + +# 57| [RegExpNamedCharacterProperty] \p{^Alnum} + +# 57| [RegExpRange] \p{^Alnum}{2,3} +#-----| 0 -> [RegExpNamedCharacterProperty] \p{^Alnum} + +# 57| [RegExpNormalChar] 2 + +# 57| [RegExpNormalChar] , + +# 57| [RegExpNormalChar] 3 + +# 57| [RegExpNormalChar] } + +# 58| [RegExpCharacterClass] [a-f\p{Digit}] +#-----| 0 -> [RegExpCharacterRange] a-f +#-----| 1 -> [RegExpNamedCharacterProperty] \p{Digit} + +# 58| [RegExpPlus] [a-f\p{Digit}]+ +#-----| 0 -> [RegExpCharacterClass] [a-f\p{Digit}] + +# 58| [RegExpCharacterRange] a-f +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a +#-----| 1 -> [RegExpConstant, RegExpNormalChar] f + +# 58| [RegExpConstant, RegExpNormalChar] a + +# 58| [RegExpConstant, RegExpNormalChar] f + +# 58| [RegExpNamedCharacterProperty] \p{Digit} + +# 61| [RegExpCharacterClass] [[:alpha:]] +#-----| 0 -> [RegExpNamedCharacterProperty] [:alpha:] + +# 61| [RegExpSequence] [[:alpha:]][[:digit:]] +#-----| 0 -> [RegExpCharacterClass] [[:alpha:]] +#-----| 1 -> [RegExpCharacterClass] [[:digit:]] + +# 61| [RegExpNamedCharacterProperty] [:alpha:] + +# 61| [RegExpCharacterClass] [[:digit:]] +#-----| 0 -> [RegExpNamedCharacterProperty] [:digit:] + +# 61| [RegExpNamedCharacterProperty] [:digit:] + +# 64| [RegExpCharacterClass] [[:alpha:][:digit:]] +#-----| 0 -> [RegExpNamedCharacterProperty] [:alpha:] +#-----| 1 -> [RegExpNamedCharacterProperty] [:digit:] + +# 64| [RegExpNamedCharacterProperty] [:alpha:] + +# 64| [RegExpNamedCharacterProperty] [:digit:] + +# 67| [RegExpCharacterClass] [A-F[:digit:]a-f] +#-----| 0 -> [RegExpCharacterRange] A-F +#-----| 1 -> [RegExpNamedCharacterProperty] [:digit:] +#-----| 2 -> [RegExpCharacterRange] a-f + +# 67| [RegExpCharacterRange] A-F +#-----| 0 -> [RegExpConstant, RegExpNormalChar] A +#-----| 1 -> [RegExpConstant, RegExpNormalChar] F + +# 67| [RegExpConstant, RegExpNormalChar] A + +# 67| [RegExpConstant, RegExpNormalChar] F + +# 67| [RegExpNamedCharacterProperty] [:digit:] + +# 67| [RegExpCharacterRange] a-f +#-----| 0 -> [RegExpConstant, RegExpNormalChar] a +#-----| 1 -> [RegExpConstant, RegExpNormalChar] f + +# 67| [RegExpConstant, RegExpNormalChar] a + +# 67| [RegExpConstant, RegExpNormalChar] f + +# 70| [RegExpNamedCharacterProperty] [:digit:] diff --git a/ruby/ql/test/library-tests/regexp/parse.ql b/ruby/ql/test/library-tests/regexp/parse.ql new file mode 100644 index 000000000000..e18c383c4d8d --- /dev/null +++ b/ruby/ql/test/library-tests/regexp/parse.ql @@ -0,0 +1,27 @@ +/** + * @kind graph + */ + +import codeql.Locations +import codeql.ruby.regexp.RegExpTreeView as RETV + +query predicate nodes(RETV::RegExpTerm n, string attr, string val) { + attr = "semmle.label" and + val = "[" + concat(n.getAPrimaryQlClass(), ", ") + "] " + n.toString() + or + attr = "semmle.order" and + val = + any(int i | + n = + rank[i](RETV::RegExpTerm t, string fp, int sl, int sc | + t.hasLocationInfo(fp, sl, sc, _, _) + | + t order by fp, sl, sc + ) + ).toString() +} + +query predicate edges(RETV::RegExpTerm pred, RETV::RegExpTerm succ, string attr, string val) { + attr in ["semmle.label", "semmle.order"] and + val = any(int i | succ = pred.getChild(i)).toString() +} diff --git a/ruby/ql/test/library-tests/regexp/regexp.rb b/ruby/ql/test/library-tests/regexp/regexp.rb new file mode 100644 index 000000000000..469813207d97 --- /dev/null +++ b/ruby/ql/test/library-tests/regexp/regexp.rb @@ -0,0 +1,70 @@ +# Empty +// + +# Basic sequence +/abc/ + +# Repetition +/a*b+c?d/ +/a{4,8}/ +/a{,8}/ +/a{3,}/ +/a{7}/ + +# Alternation +/foo|bar/ + +# Character classes +/[abc]/ +/[a-fA-F0-9_]/ +/\A[+-]?\d+/ +/[\w]+/ +/\[\][123]/ +/[^A-Z]/ +/[]]/ # MRI gives a warning, but accepts this as matching ']' +/[^]]/ # MRI gives a warning, but accepts this as matching anything except ']' +/[^-]/ + +# Nested character classes +/[[a-f]A-F]/ # BAD - not parsed correctly + +# Meta-character classes +/.*/ +/.*/m +/\w+\W/ +/\s\S/ +/\d\D/ +/\h\H/ +/\n\r\t/ + +# Groups +/(foo)*bar/ +/fo(o|b)ar/ +/(a|b|cd)e/ +/(?::+)\w/ # Non-capturing group matching colons + +# Named groups +/(?\w+)/ +/(?'foo'fo+)/ + +# Backreferences +/(a+)b+\1/ +/(?q+)\s+\k+/ + +# Named character properties using the p-style syntax +/\p{Word}*/ +/\P{Digit}+/ +/\p{^Alnum}{2,3}/ +/[a-f\p{Digit}]+/ # Also valid inside character classes + +# Two separate character classes, each containing a single POSIX bracket expression +/[[:alpha:]][[:digit:]]/ + +# A single character class containing two POSIX bracket expressions +/[[:alpha:][:digit:]]/ + +# A single character class containing two ranges and one POSIX bracket expression +/[A-F[:digit:]a-f]/ + +# *Not* a POSIX bracket expression; just a regular character class. +/[:digit:]/ diff --git a/ruby/ql/test/library-tests/variables/class_variables.rb b/ruby/ql/test/library-tests/variables/class_variables.rb new file mode 100644 index 000000000000..dd32df7ea143 --- /dev/null +++ b/ruby/ql/test/library-tests/variables/class_variables.rb @@ -0,0 +1,29 @@ +@@x = 42 + +p @@x + +def print + p @@x +end + +class X + def b + p @@x + end + def self.s + p @@x + end +end + +class Y < BasicObject + @@x = 10 +end + +module M + @@x = 12 +end + +module N + include M + p @@x +end diff --git a/ruby/ql/test/library-tests/variables/instance_variables.rb b/ruby/ql/test/library-tests/variables/instance_variables.rb new file mode 100644 index 000000000000..d35b6b15af9e --- /dev/null +++ b/ruby/ql/test/library-tests/variables/instance_variables.rb @@ -0,0 +1,44 @@ +@top = 1 + +def foo + @foo = 10 +end + +def print_foo + puts @foo +end + +puts @top + +class X + @x = 10 + def m() + @y = 7 + end +end + +module M + @m = 10 + def n() + @n = 7 + end +end + +puts { + @x = 100 +} + +def bar + 1.times { @x = 200 } +end + +class C + @x = 42 + def x + def y + @x = 10 + end + y + p @x + end + end \ No newline at end of file diff --git a/ruby/ql/test/library-tests/variables/nested_scopes.rb b/ruby/ql/test/library-tests/variables/nested_scopes.rb new file mode 100644 index 000000000000..db0311fe7d26 --- /dev/null +++ b/ruby/ql/test/library-tests/variables/nested_scopes.rb @@ -0,0 +1,42 @@ +def a + puts "method a" +end +class C + a = 5 + module M + a = 4 + module N + a = 3 + class D + a = 2 + def show_a + a = 1 + puts a + a.times do |a| + a.times do | x; a| + a = 6 + a.times { |x| puts a } + end + end + end + def show_a2 a + puts a + end + puts a + end + def self.show + puts a # not a variable, but a call to a() + end + class << self + a = 10 + puts a + end + puts a + end + puts a + end + puts a +end +d = C::M::N::D.new +d.show_a + diff --git a/ruby/ql/test/library-tests/variables/parameter.expected b/ruby/ql/test/library-tests/variables/parameter.expected new file mode 100644 index 000000000000..6e21d46b2121 --- /dev/null +++ b/ruby/ql/test/library-tests/variables/parameter.expected @@ -0,0 +1,44 @@ +parameterVariable +| nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:23:15:23 | a | +| nested_scopes.rb:16:26:16:26 | x | nested_scopes.rb:16:26:16:26 | x | +| nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:16:29:16:29 | a | +| nested_scopes.rb:18:26:18:26 | x | nested_scopes.rb:18:26:18:26 | x | +| nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:21:22:21 | a | +| parameters.rb:1:14:1:14 | x | parameters.rb:1:14:1:14 | x | +| parameters.rb:1:18:1:18 | y | parameters.rb:1:18:1:18 | y | +| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | +| parameters.rb:7:25:7:31 | *pizzas | parameters.rb:7:26:7:31 | pizzas | +| parameters.rb:15:15:15:19 | **map | parameters.rb:15:17:15:19 | map | +| parameters.rb:16:16:16:18 | key | parameters.rb:16:16:16:18 | key | +| parameters.rb:16:21:16:25 | value | parameters.rb:16:21:16:25 | value | +| parameters.rb:21:16:21:21 | &block | parameters.rb:21:17:21:21 | block | +| parameters.rb:25:15:25:18 | name | parameters.rb:25:15:25:18 | name | +| parameters.rb:25:33:25:36 | size | parameters.rb:25:33:25:36 | size | +| parameters.rb:30:15:30:19 | first | parameters.rb:30:15:30:19 | first | +| parameters.rb:30:24:30:29 | middle | parameters.rb:30:24:30:29 | middle | +| parameters.rb:30:36:30:39 | last | parameters.rb:30:36:30:39 | last | +| parameters.rb:35:11:35:11 | a | parameters.rb:35:11:35:11 | a | +| parameters.rb:40:12:40:12 | d | parameters.rb:40:12:40:12 | d | +| parameters.rb:45:20:45:20 | _ | parameters.rb:45:20:45:20 | _ | +| parameters.rb:49:12:49:16 | (..., ...) | parameters.rb:49:13:49:13 | a | +| parameters.rb:49:12:49:16 | (..., ...) | parameters.rb:49:15:49:15 | b | +| parameters.rb:54:14:54:14 | y | parameters.rb:54:14:54:14 | y | +| scopes.rb:2:14:2:14 | x | scopes.rb:2:14:2:14 | x | +| scopes.rb:9:14:9:14 | x | scopes.rb:9:14:9:14 | x | +| ssa.rb:1:7:1:7 | b | ssa.rb:1:7:1:7 | b | +| ssa.rb:18:8:18:8 | x | ssa.rb:18:8:18:8 | x | +| ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | +| ssa.rb:33:20:33:20 | x | ssa.rb:33:20:33:20 | x | +| ssa.rb:44:8:44:8 | b | ssa.rb:44:8:44:8 | b | +| ssa.rb:49:9:49:9 | x | ssa.rb:49:9:49:9 | x | +| ssa.rb:53:8:53:10 | foo | ssa.rb:53:8:53:10 | foo | +| ssa.rb:64:8:64:8 | a | ssa.rb:64:8:64:8 | a | +| ssa.rb:66:15:66:15 | a | ssa.rb:66:15:66:15 | a | +parameterNoVariable +| parameters.rb:45:22:45:22 | _ | +parameterVariableNoAccess +| nested_scopes.rb:16:26:16:26 | x | nested_scopes.rb:16:26:16:26 | x | +| nested_scopes.rb:18:26:18:26 | x | nested_scopes.rb:18:26:18:26 | x | +| scopes.rb:2:14:2:14 | x | scopes.rb:2:14:2:14 | x | +| scopes.rb:9:14:9:14 | x | scopes.rb:9:14:9:14 | x | +| ssa.rb:49:9:49:9 | x | ssa.rb:49:9:49:9 | x | diff --git a/ruby/ql/test/library-tests/variables/parameter.ql b/ruby/ql/test/library-tests/variables/parameter.ql new file mode 100644 index 000000000000..93c3972c391a --- /dev/null +++ b/ruby/ql/test/library-tests/variables/parameter.ql @@ -0,0 +1,10 @@ +import codeql.ruby.ast.Variable +import codeql.ruby.ast.Parameter + +query predicate parameterVariable(Parameter p, Variable v) { v = p.getAVariable() } + +query predicate parameterNoVariable(Parameter p) { not exists(p.getAVariable()) } + +query predicate parameterVariableNoAccess(Parameter p, Variable v) { + v = p.getAVariable() and strictcount(v.getAnAccess()) = 1 +} diff --git a/ruby/ql/test/library-tests/variables/parameters.rb b/ruby/ql/test/library-tests/variables/parameters.rb new file mode 100644 index 000000000000..99aff4df5aee --- /dev/null +++ b/ruby/ql/test/library-tests/variables/parameters.rb @@ -0,0 +1,58 @@ +1.times do | x ; y| + y = 5 + puts x + puts y +end + +def order_pizza(client, *pizzas) + if pizzas.count == 1 + puts "1 pizza for #{client}!" + else + puts "#{ pizzas.count} pizzas for #{client}!" + end +end + +def print_map(**map) + map.each do |key, value| + puts "#{key} = #{value}" + end +end + +def call_block(&block) + block.call +end + +def opt_param(name = 'unknown', size = name.length) + puts name + puts size +end + +def key_param(first: , middle: '', last:) + puts "#{first} #{middle} #{last}" +end + +b = 2 +def multi(a = (b = 5)) + # `a` is a parameter and `b` is a new variable + puts "#{a} #{b}" +end + +def multi2(d: e = 4) + # `d` is a parameter and `e` is a local variable + puts "#{d} #{e}" +end + +def dup_underscore(_,_) + puts _ # binds to the first _ +end + +def tuples((a,b)) + puts "#{a} #{b}" +end + +x = 10 +1.times do | y = (x = 1)| + puts x + puts y +end + diff --git a/ruby/ql/test/library-tests/variables/scopes.rb b/ruby/ql/test/library-tests/variables/scopes.rb new file mode 100644 index 000000000000..db55da3b77f2 --- /dev/null +++ b/ruby/ql/test/library-tests/variables/scopes.rb @@ -0,0 +1,49 @@ +def a ; "x" end +1.times do | x | + puts a # not a local variable + a = 3 + puts a # local variable +end +a = 6 +puts a +1.times do | x | + puts a # local variable from top-level + a += 3 + puts a # local variable from top-level + a, b, (c, d) = [4, 5, [6, 7]] + puts a # local variable from top-level + puts b # new local variable + puts c # new local variable + puts d # new local variable +end + +# new global variable +$global = 42 + +# use of a pre-defined global variable +script = $0 + +class A; end +x = A +module x::B + x = 1 +end +class << x + x = 2 +end +class x::C < x + x = 3 +end +def x.foo + x = 4 +end + +module M + var = 1 + foo = <<-EOF + #{var} + #{fun} + #{var2 = 10} + #{var2} + EOF +end diff --git a/ruby/ql/test/library-tests/variables/ssa.expected b/ruby/ql/test/library-tests/variables/ssa.expected new file mode 100644 index 000000000000..af235db7e3d6 --- /dev/null +++ b/ruby/ql/test/library-tests/variables/ssa.expected @@ -0,0 +1,360 @@ +definition +| nested_scopes.rb:5:3:5:7 | ... = ... | nested_scopes.rb:5:3:5:3 | a | +| nested_scopes.rb:7:5:7:9 | ... = ... | nested_scopes.rb:7:5:7:5 | a | +| nested_scopes.rb:9:7:9:11 | ... = ... | nested_scopes.rb:9:7:9:7 | a | +| nested_scopes.rb:11:9:11:13 | ... = ... | nested_scopes.rb:11:9:11:9 | a | +| nested_scopes.rb:13:11:13:15 | ... = ... | nested_scopes.rb:13:11:13:11 | a | +| nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:23:15:23 | a | +| nested_scopes.rb:17:15:17:19 | ... = ... | nested_scopes.rb:16:29:16:29 | a | +| nested_scopes.rb:18:23:18:36 | | nested_scopes.rb:16:29:16:29 | a | +| nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:21:22:21 | a | +| nested_scopes.rb:31:11:31:16 | ... = ... | nested_scopes.rb:31:11:31:11 | a | +| nested_scopes.rb:40:1:40:18 | ... = ... | nested_scopes.rb:40:1:40:1 | d | +| parameters.rb:1:14:1:14 | x | parameters.rb:1:14:1:14 | x | +| parameters.rb:2:4:2:8 | ... = ... | parameters.rb:1:18:1:18 | y | +| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | +| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | +| parameters.rb:15:17:15:19 | map | parameters.rb:15:17:15:19 | map | +| parameters.rb:16:16:16:18 | key | parameters.rb:16:16:16:18 | key | +| parameters.rb:16:21:16:25 | value | parameters.rb:16:21:16:25 | value | +| parameters.rb:21:17:21:21 | block | parameters.rb:21:17:21:21 | block | +| parameters.rb:25:15:25:18 | name | parameters.rb:25:15:25:18 | name | +| parameters.rb:25:33:25:36 | size | parameters.rb:25:33:25:36 | size | +| parameters.rb:30:15:30:19 | first | parameters.rb:30:15:30:19 | first | +| parameters.rb:30:24:30:29 | middle | parameters.rb:30:24:30:29 | middle | +| parameters.rb:30:36:30:39 | last | parameters.rb:30:36:30:39 | last | +| parameters.rb:35:1:38:3 | | parameters.rb:35:16:35:16 | b | +| parameters.rb:35:11:35:11 | a | parameters.rb:35:11:35:11 | a | +| parameters.rb:35:16:35:20 | ... = ... | parameters.rb:35:16:35:16 | b | +| parameters.rb:37:3:37:18 | phi | parameters.rb:35:16:35:16 | b | +| parameters.rb:40:1:43:3 | | parameters.rb:40:15:40:15 | e | +| parameters.rb:40:12:40:12 | d | parameters.rb:40:12:40:12 | d | +| parameters.rb:40:15:40:19 | ... = ... | parameters.rb:40:15:40:15 | e | +| parameters.rb:42:3:42:18 | phi | parameters.rb:40:15:40:15 | e | +| parameters.rb:45:20:45:20 | _ | parameters.rb:45:20:45:20 | _ | +| parameters.rb:49:13:49:13 | a | parameters.rb:49:13:49:13 | a | +| parameters.rb:49:15:49:15 | b | parameters.rb:49:15:49:15 | b | +| parameters.rb:53:1:53:6 | ... = ... | parameters.rb:53:1:53:1 | x | +| parameters.rb:54:9:57:3 | | parameters.rb:53:1:53:1 | x | +| parameters.rb:54:14:54:14 | y | parameters.rb:54:14:54:14 | y | +| parameters.rb:54:19:54:23 | ... = ... | parameters.rb:53:1:53:1 | x | +| parameters.rb:55:4:55:9 | phi | parameters.rb:53:1:53:1 | x | +| scopes.rb:4:4:4:8 | ... = ... | scopes.rb:4:4:4:4 | a | +| scopes.rb:7:1:7:5 | ... = ... | scopes.rb:7:1:7:1 | a | +| scopes.rb:9:9:18:3 | | scopes.rb:7:1:7:1 | a | +| scopes.rb:11:4:11:9 | ... = ... | scopes.rb:7:1:7:1 | a | +| scopes.rb:13:4:13:4 | ... = ... | scopes.rb:7:1:7:1 | a | +| scopes.rb:13:7:13:7 | ... = ... | scopes.rb:13:7:13:7 | b | +| scopes.rb:13:10:13:15 | ... = ... | scopes.rb:13:10:13:15 | __synth__0__1 | +| scopes.rb:13:11:13:11 | ... = ... | scopes.rb:13:11:13:11 | c | +| scopes.rb:13:14:13:14 | ... = ... | scopes.rb:13:14:13:14 | d | +| scopes.rb:13:19:13:32 | ... = ... | scopes.rb:13:4:13:32 | __synth__0 | +| scopes.rb:27:1:27:5 | ... = ... | scopes.rb:27:1:27:1 | x | +| scopes.rb:42:2:42:9 | ... = ... | scopes.rb:42:2:42:4 | var | +| scopes.rb:46:5:46:13 | ... = ... | scopes.rb:46:5:46:8 | var2 | +| ssa.rb:1:7:1:7 | b | ssa.rb:1:7:1:7 | b | +| ssa.rb:2:3:2:7 | ... = ... | ssa.rb:2:3:2:3 | i | +| ssa.rb:5:3:13:5 | phi | ssa.rb:2:3:2:3 | i | +| ssa.rb:6:5:6:9 | ... = ... | ssa.rb:2:3:2:3 | i | +| ssa.rb:10:5:10:9 | ... = ... | ssa.rb:2:3:2:3 | i | +| ssa.rb:18:8:18:8 | x | ssa.rb:18:8:18:8 | x | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | +| ssa.rb:21:5:21:10 | ... = ... | ssa.rb:18:8:18:8 | x | +| ssa.rb:25:1:30:3 | | ssa.rb:26:7:26:10 | elem | +| ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | +| ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | +| ssa.rb:26:12:26:22 | phi | ssa.rb:26:7:26:10 | elem | +| ssa.rb:33:20:33:20 | x | ssa.rb:33:20:33:20 | x | +| ssa.rb:40:3:40:9 | ... = ... | ssa.rb:40:3:40:4 | m3 | +| ssa.rb:44:1:47:3 | | ssa.rb:45:3:45:3 | x | +| ssa.rb:44:8:44:8 | b | ssa.rb:44:8:44:8 | b | +| ssa.rb:45:3:45:7 | ... = ... | ssa.rb:45:3:45:3 | x | +| ssa.rb:45:3:45:12 | phi | ssa.rb:45:3:45:3 | x | +| ssa.rb:49:1:51:3 | | ssa.rb:49:14:49:14 | y | +| ssa.rb:49:14:49:19 | ... = ... | ssa.rb:49:14:49:14 | y | +| ssa.rb:50:3:50:8 | phi | ssa.rb:49:14:49:14 | y | +| ssa.rb:53:8:53:10 | foo | ssa.rb:53:8:53:10 | foo | +| ssa.rb:54:3:54:11 | ... = ... | ssa.rb:54:3:54:3 | x | +| ssa.rb:59:3:59:8 | ... = ... | ssa.rb:59:3:59:3 | x | +| ssa.rb:60:3:60:9 | ... = ... | ssa.rb:59:3:59:3 | x | +| ssa.rb:64:8:64:8 | a | ssa.rb:64:8:64:8 | a | +| ssa.rb:65:3:65:15 | ... = ... | ssa.rb:65:3:65:10 | captured | +| ssa.rb:66:3:70:5 | call to times | ssa.rb:65:3:65:10 | captured | +| ssa.rb:66:11:70:5 | | ssa.rb:65:3:65:10 | captured | +| ssa.rb:66:15:66:15 | a | ssa.rb:66:15:66:15 | a | +| ssa.rb:69:5:69:17 | ... = ... | ssa.rb:65:3:65:10 | captured | +| ssa.rb:75:3:75:14 | ... = ... | ssa.rb:75:3:75:10 | captured | +| ssa.rb:76:7:78:5 | | ssa.rb:75:3:75:10 | captured | +| ssa.rb:82:3:82:14 | ... = ... | ssa.rb:82:3:82:10 | captured | +| ssa.rb:84:10:86:8 | | ssa.rb:82:3:82:10 | captured | +read +| nested_scopes.rb:5:3:5:7 | ... = ... | nested_scopes.rb:5:3:5:3 | a | nested_scopes.rb:38:8:38:8 | a | +| nested_scopes.rb:7:5:7:9 | ... = ... | nested_scopes.rb:7:5:7:5 | a | nested_scopes.rb:36:10:36:10 | a | +| nested_scopes.rb:9:7:9:11 | ... = ... | nested_scopes.rb:9:7:9:7 | a | nested_scopes.rb:34:12:34:12 | a | +| nested_scopes.rb:11:9:11:13 | ... = ... | nested_scopes.rb:11:9:11:9 | a | nested_scopes.rb:25:14:25:14 | a | +| nested_scopes.rb:13:11:13:15 | ... = ... | nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:14:16:14:16 | a | +| nested_scopes.rb:13:11:13:15 | ... = ... | nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:15:11:15:11 | a | +| nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:16:13:16:13 | a | +| nested_scopes.rb:17:15:17:19 | ... = ... | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:15:18:15 | a | +| nested_scopes.rb:18:23:18:36 | | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:34:18:34 | a | +| nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:23:16:23:16 | a | +| nested_scopes.rb:31:11:31:16 | ... = ... | nested_scopes.rb:31:11:31:11 | a | nested_scopes.rb:32:16:32:16 | a | +| nested_scopes.rb:40:1:40:18 | ... = ... | nested_scopes.rb:40:1:40:1 | d | nested_scopes.rb:41:1:41:1 | d | +| parameters.rb:1:14:1:14 | x | parameters.rb:1:14:1:14 | x | parameters.rb:3:9:3:9 | x | +| parameters.rb:2:4:2:8 | ... = ... | parameters.rb:1:18:1:18 | y | parameters.rb:4:9:4:9 | y | +| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | parameters.rb:9:25:9:30 | client | +| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | parameters.rb:11:41:11:46 | client | +| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:8:6:8:11 | pizzas | +| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:11:14:11:19 | pizzas | +| parameters.rb:15:17:15:19 | map | parameters.rb:15:17:15:19 | map | parameters.rb:16:3:16:5 | map | +| parameters.rb:16:16:16:18 | key | parameters.rb:16:16:16:18 | key | parameters.rb:17:13:17:15 | key | +| parameters.rb:16:21:16:25 | value | parameters.rb:16:21:16:25 | value | parameters.rb:17:22:17:26 | value | +| parameters.rb:21:17:21:21 | block | parameters.rb:21:17:21:21 | block | parameters.rb:22:3:22:7 | block | +| parameters.rb:25:15:25:18 | name | parameters.rb:25:15:25:18 | name | parameters.rb:25:40:25:43 | name | +| parameters.rb:25:15:25:18 | name | parameters.rb:25:15:25:18 | name | parameters.rb:26:8:26:11 | name | +| parameters.rb:25:33:25:36 | size | parameters.rb:25:33:25:36 | size | parameters.rb:27:8:27:11 | size | +| parameters.rb:30:15:30:19 | first | parameters.rb:30:15:30:19 | first | parameters.rb:31:11:31:15 | first | +| parameters.rb:30:24:30:29 | middle | parameters.rb:30:24:30:29 | middle | parameters.rb:31:20:31:25 | middle | +| parameters.rb:30:36:30:39 | last | parameters.rb:30:36:30:39 | last | parameters.rb:31:30:31:33 | last | +| parameters.rb:35:11:35:11 | a | parameters.rb:35:11:35:11 | a | parameters.rb:37:11:37:11 | a | +| parameters.rb:37:3:37:18 | phi | parameters.rb:35:16:35:16 | b | parameters.rb:37:16:37:16 | b | +| parameters.rb:40:12:40:12 | d | parameters.rb:40:12:40:12 | d | parameters.rb:42:11:42:11 | d | +| parameters.rb:42:3:42:18 | phi | parameters.rb:40:15:40:15 | e | parameters.rb:42:16:42:16 | e | +| parameters.rb:45:20:45:20 | _ | parameters.rb:45:20:45:20 | _ | parameters.rb:46:8:46:8 | _ | +| parameters.rb:49:13:49:13 | a | parameters.rb:49:13:49:13 | a | parameters.rb:50:11:50:11 | a | +| parameters.rb:49:15:49:15 | b | parameters.rb:49:15:49:15 | b | parameters.rb:50:16:50:16 | b | +| parameters.rb:54:14:54:14 | y | parameters.rb:54:14:54:14 | y | parameters.rb:56:9:56:9 | y | +| parameters.rb:55:4:55:9 | phi | parameters.rb:53:1:53:1 | x | parameters.rb:55:9:55:9 | x | +| scopes.rb:4:4:4:8 | ... = ... | scopes.rb:4:4:4:4 | a | scopes.rb:5:9:5:9 | a | +| scopes.rb:7:1:7:5 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:8:6:8:6 | a | +| scopes.rb:9:9:18:3 | | scopes.rb:7:1:7:1 | a | scopes.rb:10:9:10:9 | a | +| scopes.rb:9:9:18:3 | | scopes.rb:7:1:7:1 | a | scopes.rb:11:4:11:4 | a | +| scopes.rb:11:4:11:9 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:12:9:12:9 | a | +| scopes.rb:13:4:13:4 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:14:9:14:9 | a | +| scopes.rb:13:7:13:7 | ... = ... | scopes.rb:13:7:13:7 | b | scopes.rb:15:9:15:9 | b | +| scopes.rb:13:10:13:15 | ... = ... | scopes.rb:13:10:13:15 | __synth__0__1 | scopes.rb:13:11:13:11 | __synth__0__1 | +| scopes.rb:13:10:13:15 | ... = ... | scopes.rb:13:10:13:15 | __synth__0__1 | scopes.rb:13:14:13:14 | __synth__0__1 | +| scopes.rb:13:11:13:11 | ... = ... | scopes.rb:13:11:13:11 | c | scopes.rb:16:9:16:9 | c | +| scopes.rb:13:14:13:14 | ... = ... | scopes.rb:13:14:13:14 | d | scopes.rb:17:9:17:9 | d | +| scopes.rb:13:19:13:32 | ... = ... | scopes.rb:13:4:13:32 | __synth__0 | scopes.rb:13:4:13:4 | __synth__0 | +| scopes.rb:13:19:13:32 | ... = ... | scopes.rb:13:4:13:32 | __synth__0 | scopes.rb:13:7:13:7 | __synth__0 | +| scopes.rb:13:19:13:32 | ... = ... | scopes.rb:13:4:13:32 | __synth__0 | scopes.rb:13:10:13:15 | __synth__0 | +| scopes.rb:27:1:27:5 | ... = ... | scopes.rb:27:1:27:1 | x | scopes.rb:28:8:28:8 | x | +| scopes.rb:27:1:27:5 | ... = ... | scopes.rb:27:1:27:1 | x | scopes.rb:31:10:31:10 | x | +| scopes.rb:27:1:27:5 | ... = ... | scopes.rb:27:1:27:1 | x | scopes.rb:34:7:34:7 | x | +| scopes.rb:27:1:27:5 | ... = ... | scopes.rb:27:1:27:1 | x | scopes.rb:34:14:34:14 | x | +| scopes.rb:27:1:27:5 | ... = ... | scopes.rb:27:1:27:1 | x | scopes.rb:37:5:37:5 | x | +| scopes.rb:42:2:42:9 | ... = ... | scopes.rb:42:2:42:4 | var | scopes.rb:44:5:44:7 | var | +| scopes.rb:46:5:46:13 | ... = ... | scopes.rb:46:5:46:8 | var2 | scopes.rb:47:5:47:8 | var2 | +| ssa.rb:1:7:1:7 | b | ssa.rb:1:7:1:7 | b | ssa.rb:5:6:5:6 | b | +| ssa.rb:2:3:2:7 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:3:8:3:8 | i | +| ssa.rb:2:3:2:7 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:4:8:4:8 | i | +| ssa.rb:5:3:13:5 | phi | ssa.rb:2:3:2:3 | i | ssa.rb:15:8:15:8 | i | +| ssa.rb:6:5:6:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:7:10:7:10 | i | +| ssa.rb:6:5:6:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:8:10:8:10 | i | +| ssa.rb:10:5:10:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:11:10:11:10 | i | +| ssa.rb:10:5:10:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:12:10:12:10 | i | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:19:9:19:9 | x | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:20:10:20:10 | x | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:21:5:21:5 | x | +| ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | ssa.rb:26:15:26:22 | elements | +| ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | ssa.rb:27:10:27:13 | elem | +| ssa.rb:26:12:26:22 | phi | ssa.rb:26:7:26:10 | elem | ssa.rb:29:8:29:11 | elem | +| ssa.rb:33:20:33:20 | x | ssa.rb:33:20:33:20 | x | ssa.rb:34:10:34:10 | x | +| ssa.rb:40:3:40:9 | ... = ... | ssa.rb:40:3:40:4 | m3 | ssa.rb:41:8:41:9 | m3 | +| ssa.rb:44:8:44:8 | b | ssa.rb:44:8:44:8 | b | ssa.rb:45:12:45:12 | b | +| ssa.rb:45:3:45:12 | phi | ssa.rb:45:3:45:3 | x | ssa.rb:46:8:46:8 | x | +| ssa.rb:50:3:50:8 | phi | ssa.rb:49:14:49:14 | y | ssa.rb:50:8:50:8 | y | +| ssa.rb:53:8:53:10 | foo | ssa.rb:53:8:53:10 | foo | ssa.rb:54:7:54:9 | foo | +| ssa.rb:54:3:54:11 | ... = ... | ssa.rb:54:3:54:3 | x | ssa.rb:55:8:55:8 | x | +| ssa.rb:59:3:59:8 | ... = ... | ssa.rb:59:3:59:3 | x | ssa.rb:60:3:60:3 | x | +| ssa.rb:60:3:60:9 | ... = ... | ssa.rb:59:3:59:3 | x | ssa.rb:61:8:61:8 | x | +| ssa.rb:64:8:64:8 | a | ssa.rb:64:8:64:8 | a | ssa.rb:66:3:66:3 | a | +| ssa.rb:66:3:70:5 | call to times | ssa.rb:65:3:65:10 | captured | ssa.rb:71:8:71:15 | captured | +| ssa.rb:66:11:70:5 | | ssa.rb:65:3:65:10 | captured | ssa.rb:68:10:68:17 | captured | +| ssa.rb:66:11:70:5 | | ssa.rb:65:3:65:10 | captured | ssa.rb:69:5:69:12 | captured | +| ssa.rb:66:15:66:15 | a | ssa.rb:66:15:66:15 | a | ssa.rb:67:10:67:10 | a | +| ssa.rb:76:7:78:5 | | ssa.rb:75:3:75:10 | captured | ssa.rb:77:15:77:22 | captured | +| ssa.rb:84:10:86:8 | | ssa.rb:82:3:82:10 | captured | ssa.rb:85:15:85:22 | captured | +firstRead +| nested_scopes.rb:5:3:5:7 | ... = ... | nested_scopes.rb:5:3:5:3 | a | nested_scopes.rb:38:8:38:8 | a | +| nested_scopes.rb:7:5:7:9 | ... = ... | nested_scopes.rb:7:5:7:5 | a | nested_scopes.rb:36:10:36:10 | a | +| nested_scopes.rb:9:7:9:11 | ... = ... | nested_scopes.rb:9:7:9:7 | a | nested_scopes.rb:34:12:34:12 | a | +| nested_scopes.rb:11:9:11:13 | ... = ... | nested_scopes.rb:11:9:11:9 | a | nested_scopes.rb:25:14:25:14 | a | +| nested_scopes.rb:13:11:13:15 | ... = ... | nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:14:16:14:16 | a | +| nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:16:13:16:13 | a | +| nested_scopes.rb:17:15:17:19 | ... = ... | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:15:18:15 | a | +| nested_scopes.rb:18:23:18:36 | | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:34:18:34 | a | +| nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:23:16:23:16 | a | +| nested_scopes.rb:31:11:31:16 | ... = ... | nested_scopes.rb:31:11:31:11 | a | nested_scopes.rb:32:16:32:16 | a | +| nested_scopes.rb:40:1:40:18 | ... = ... | nested_scopes.rb:40:1:40:1 | d | nested_scopes.rb:41:1:41:1 | d | +| parameters.rb:1:14:1:14 | x | parameters.rb:1:14:1:14 | x | parameters.rb:3:9:3:9 | x | +| parameters.rb:2:4:2:8 | ... = ... | parameters.rb:1:18:1:18 | y | parameters.rb:4:9:4:9 | y | +| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | parameters.rb:9:25:9:30 | client | +| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | parameters.rb:11:41:11:46 | client | +| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:8:6:8:11 | pizzas | +| parameters.rb:15:17:15:19 | map | parameters.rb:15:17:15:19 | map | parameters.rb:16:3:16:5 | map | +| parameters.rb:16:16:16:18 | key | parameters.rb:16:16:16:18 | key | parameters.rb:17:13:17:15 | key | +| parameters.rb:16:21:16:25 | value | parameters.rb:16:21:16:25 | value | parameters.rb:17:22:17:26 | value | +| parameters.rb:21:17:21:21 | block | parameters.rb:21:17:21:21 | block | parameters.rb:22:3:22:7 | block | +| parameters.rb:25:15:25:18 | name | parameters.rb:25:15:25:18 | name | parameters.rb:25:40:25:43 | name | +| parameters.rb:25:15:25:18 | name | parameters.rb:25:15:25:18 | name | parameters.rb:26:8:26:11 | name | +| parameters.rb:25:33:25:36 | size | parameters.rb:25:33:25:36 | size | parameters.rb:27:8:27:11 | size | +| parameters.rb:30:15:30:19 | first | parameters.rb:30:15:30:19 | first | parameters.rb:31:11:31:15 | first | +| parameters.rb:30:24:30:29 | middle | parameters.rb:30:24:30:29 | middle | parameters.rb:31:20:31:25 | middle | +| parameters.rb:30:36:30:39 | last | parameters.rb:30:36:30:39 | last | parameters.rb:31:30:31:33 | last | +| parameters.rb:35:11:35:11 | a | parameters.rb:35:11:35:11 | a | parameters.rb:37:11:37:11 | a | +| parameters.rb:37:3:37:18 | phi | parameters.rb:35:16:35:16 | b | parameters.rb:37:16:37:16 | b | +| parameters.rb:40:12:40:12 | d | parameters.rb:40:12:40:12 | d | parameters.rb:42:11:42:11 | d | +| parameters.rb:42:3:42:18 | phi | parameters.rb:40:15:40:15 | e | parameters.rb:42:16:42:16 | e | +| parameters.rb:45:20:45:20 | _ | parameters.rb:45:20:45:20 | _ | parameters.rb:46:8:46:8 | _ | +| parameters.rb:49:13:49:13 | a | parameters.rb:49:13:49:13 | a | parameters.rb:50:11:50:11 | a | +| parameters.rb:49:15:49:15 | b | parameters.rb:49:15:49:15 | b | parameters.rb:50:16:50:16 | b | +| parameters.rb:54:14:54:14 | y | parameters.rb:54:14:54:14 | y | parameters.rb:56:9:56:9 | y | +| parameters.rb:55:4:55:9 | phi | parameters.rb:53:1:53:1 | x | parameters.rb:55:9:55:9 | x | +| scopes.rb:4:4:4:8 | ... = ... | scopes.rb:4:4:4:4 | a | scopes.rb:5:9:5:9 | a | +| scopes.rb:7:1:7:5 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:8:6:8:6 | a | +| scopes.rb:9:9:18:3 | | scopes.rb:7:1:7:1 | a | scopes.rb:10:9:10:9 | a | +| scopes.rb:11:4:11:9 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:12:9:12:9 | a | +| scopes.rb:13:4:13:4 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:14:9:14:9 | a | +| scopes.rb:13:7:13:7 | ... = ... | scopes.rb:13:7:13:7 | b | scopes.rb:15:9:15:9 | b | +| scopes.rb:13:10:13:15 | ... = ... | scopes.rb:13:10:13:15 | __synth__0__1 | scopes.rb:13:11:13:11 | __synth__0__1 | +| scopes.rb:13:11:13:11 | ... = ... | scopes.rb:13:11:13:11 | c | scopes.rb:16:9:16:9 | c | +| scopes.rb:13:14:13:14 | ... = ... | scopes.rb:13:14:13:14 | d | scopes.rb:17:9:17:9 | d | +| scopes.rb:13:19:13:32 | ... = ... | scopes.rb:13:4:13:32 | __synth__0 | scopes.rb:13:4:13:4 | __synth__0 | +| scopes.rb:27:1:27:5 | ... = ... | scopes.rb:27:1:27:1 | x | scopes.rb:28:8:28:8 | x | +| scopes.rb:42:2:42:9 | ... = ... | scopes.rb:42:2:42:4 | var | scopes.rb:44:5:44:7 | var | +| scopes.rb:46:5:46:13 | ... = ... | scopes.rb:46:5:46:8 | var2 | scopes.rb:47:5:47:8 | var2 | +| ssa.rb:1:7:1:7 | b | ssa.rb:1:7:1:7 | b | ssa.rb:5:6:5:6 | b | +| ssa.rb:2:3:2:7 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:3:8:3:8 | i | +| ssa.rb:5:3:13:5 | phi | ssa.rb:2:3:2:3 | i | ssa.rb:15:8:15:8 | i | +| ssa.rb:6:5:6:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:7:10:7:10 | i | +| ssa.rb:10:5:10:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:11:10:11:10 | i | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:19:9:19:9 | x | +| ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | ssa.rb:26:15:26:22 | elements | +| ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | ssa.rb:27:10:27:13 | elem | +| ssa.rb:26:12:26:22 | phi | ssa.rb:26:7:26:10 | elem | ssa.rb:29:8:29:11 | elem | +| ssa.rb:33:20:33:20 | x | ssa.rb:33:20:33:20 | x | ssa.rb:34:10:34:10 | x | +| ssa.rb:40:3:40:9 | ... = ... | ssa.rb:40:3:40:4 | m3 | ssa.rb:41:8:41:9 | m3 | +| ssa.rb:44:8:44:8 | b | ssa.rb:44:8:44:8 | b | ssa.rb:45:12:45:12 | b | +| ssa.rb:45:3:45:12 | phi | ssa.rb:45:3:45:3 | x | ssa.rb:46:8:46:8 | x | +| ssa.rb:50:3:50:8 | phi | ssa.rb:49:14:49:14 | y | ssa.rb:50:8:50:8 | y | +| ssa.rb:53:8:53:10 | foo | ssa.rb:53:8:53:10 | foo | ssa.rb:54:7:54:9 | foo | +| ssa.rb:54:3:54:11 | ... = ... | ssa.rb:54:3:54:3 | x | ssa.rb:55:8:55:8 | x | +| ssa.rb:59:3:59:8 | ... = ... | ssa.rb:59:3:59:3 | x | ssa.rb:60:3:60:3 | x | +| ssa.rb:60:3:60:9 | ... = ... | ssa.rb:59:3:59:3 | x | ssa.rb:61:8:61:8 | x | +| ssa.rb:64:8:64:8 | a | ssa.rb:64:8:64:8 | a | ssa.rb:66:3:66:3 | a | +| ssa.rb:66:3:70:5 | call to times | ssa.rb:65:3:65:10 | captured | ssa.rb:71:8:71:15 | captured | +| ssa.rb:66:11:70:5 | | ssa.rb:65:3:65:10 | captured | ssa.rb:68:10:68:17 | captured | +| ssa.rb:66:15:66:15 | a | ssa.rb:66:15:66:15 | a | ssa.rb:67:10:67:10 | a | +| ssa.rb:76:7:78:5 | | ssa.rb:75:3:75:10 | captured | ssa.rb:77:15:77:22 | captured | +| ssa.rb:84:10:86:8 | | ssa.rb:82:3:82:10 | captured | ssa.rb:85:15:85:22 | captured | +lastRead +| nested_scopes.rb:5:3:5:7 | ... = ... | nested_scopes.rb:5:3:5:3 | a | nested_scopes.rb:38:8:38:8 | a | +| nested_scopes.rb:7:5:7:9 | ... = ... | nested_scopes.rb:7:5:7:5 | a | nested_scopes.rb:36:10:36:10 | a | +| nested_scopes.rb:9:7:9:11 | ... = ... | nested_scopes.rb:9:7:9:7 | a | nested_scopes.rb:34:12:34:12 | a | +| nested_scopes.rb:11:9:11:13 | ... = ... | nested_scopes.rb:11:9:11:9 | a | nested_scopes.rb:25:14:25:14 | a | +| nested_scopes.rb:13:11:13:15 | ... = ... | nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:15:11:15:11 | a | +| nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:16:13:16:13 | a | +| nested_scopes.rb:17:15:17:19 | ... = ... | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:15:18:15 | a | +| nested_scopes.rb:18:23:18:36 | | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:34:18:34 | a | +| nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:23:16:23:16 | a | +| nested_scopes.rb:31:11:31:16 | ... = ... | nested_scopes.rb:31:11:31:11 | a | nested_scopes.rb:32:16:32:16 | a | +| nested_scopes.rb:40:1:40:18 | ... = ... | nested_scopes.rb:40:1:40:1 | d | nested_scopes.rb:41:1:41:1 | d | +| parameters.rb:1:14:1:14 | x | parameters.rb:1:14:1:14 | x | parameters.rb:3:9:3:9 | x | +| parameters.rb:2:4:2:8 | ... = ... | parameters.rb:1:18:1:18 | y | parameters.rb:4:9:4:9 | y | +| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | parameters.rb:9:25:9:30 | client | +| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | parameters.rb:11:41:11:46 | client | +| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:8:6:8:11 | pizzas | +| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:11:14:11:19 | pizzas | +| parameters.rb:15:17:15:19 | map | parameters.rb:15:17:15:19 | map | parameters.rb:16:3:16:5 | map | +| parameters.rb:16:16:16:18 | key | parameters.rb:16:16:16:18 | key | parameters.rb:17:13:17:15 | key | +| parameters.rb:16:21:16:25 | value | parameters.rb:16:21:16:25 | value | parameters.rb:17:22:17:26 | value | +| parameters.rb:21:17:21:21 | block | parameters.rb:21:17:21:21 | block | parameters.rb:22:3:22:7 | block | +| parameters.rb:25:15:25:18 | name | parameters.rb:25:15:25:18 | name | parameters.rb:26:8:26:11 | name | +| parameters.rb:25:33:25:36 | size | parameters.rb:25:33:25:36 | size | parameters.rb:27:8:27:11 | size | +| parameters.rb:30:15:30:19 | first | parameters.rb:30:15:30:19 | first | parameters.rb:31:11:31:15 | first | +| parameters.rb:30:24:30:29 | middle | parameters.rb:30:24:30:29 | middle | parameters.rb:31:20:31:25 | middle | +| parameters.rb:30:36:30:39 | last | parameters.rb:30:36:30:39 | last | parameters.rb:31:30:31:33 | last | +| parameters.rb:35:11:35:11 | a | parameters.rb:35:11:35:11 | a | parameters.rb:37:11:37:11 | a | +| parameters.rb:37:3:37:18 | phi | parameters.rb:35:16:35:16 | b | parameters.rb:37:16:37:16 | b | +| parameters.rb:40:12:40:12 | d | parameters.rb:40:12:40:12 | d | parameters.rb:42:11:42:11 | d | +| parameters.rb:42:3:42:18 | phi | parameters.rb:40:15:40:15 | e | parameters.rb:42:16:42:16 | e | +| parameters.rb:45:20:45:20 | _ | parameters.rb:45:20:45:20 | _ | parameters.rb:46:8:46:8 | _ | +| parameters.rb:49:13:49:13 | a | parameters.rb:49:13:49:13 | a | parameters.rb:50:11:50:11 | a | +| parameters.rb:49:15:49:15 | b | parameters.rb:49:15:49:15 | b | parameters.rb:50:16:50:16 | b | +| parameters.rb:54:14:54:14 | y | parameters.rb:54:14:54:14 | y | parameters.rb:56:9:56:9 | y | +| parameters.rb:55:4:55:9 | phi | parameters.rb:53:1:53:1 | x | parameters.rb:55:9:55:9 | x | +| scopes.rb:4:4:4:8 | ... = ... | scopes.rb:4:4:4:4 | a | scopes.rb:5:9:5:9 | a | +| scopes.rb:7:1:7:5 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:8:6:8:6 | a | +| scopes.rb:9:9:18:3 | | scopes.rb:7:1:7:1 | a | scopes.rb:11:4:11:4 | a | +| scopes.rb:11:4:11:9 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:12:9:12:9 | a | +| scopes.rb:13:4:13:4 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:14:9:14:9 | a | +| scopes.rb:13:7:13:7 | ... = ... | scopes.rb:13:7:13:7 | b | scopes.rb:15:9:15:9 | b | +| scopes.rb:13:10:13:15 | ... = ... | scopes.rb:13:10:13:15 | __synth__0__1 | scopes.rb:13:14:13:14 | __synth__0__1 | +| scopes.rb:13:11:13:11 | ... = ... | scopes.rb:13:11:13:11 | c | scopes.rb:16:9:16:9 | c | +| scopes.rb:13:14:13:14 | ... = ... | scopes.rb:13:14:13:14 | d | scopes.rb:17:9:17:9 | d | +| scopes.rb:13:19:13:32 | ... = ... | scopes.rb:13:4:13:32 | __synth__0 | scopes.rb:13:10:13:15 | __synth__0 | +| scopes.rb:27:1:27:5 | ... = ... | scopes.rb:27:1:27:1 | x | scopes.rb:37:5:37:5 | x | +| scopes.rb:42:2:42:9 | ... = ... | scopes.rb:42:2:42:4 | var | scopes.rb:44:5:44:7 | var | +| scopes.rb:46:5:46:13 | ... = ... | scopes.rb:46:5:46:8 | var2 | scopes.rb:47:5:47:8 | var2 | +| ssa.rb:1:7:1:7 | b | ssa.rb:1:7:1:7 | b | ssa.rb:5:6:5:6 | b | +| ssa.rb:2:3:2:7 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:4:8:4:8 | i | +| ssa.rb:5:3:13:5 | phi | ssa.rb:2:3:2:3 | i | ssa.rb:15:8:15:8 | i | +| ssa.rb:6:5:6:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:8:10:8:10 | i | +| ssa.rb:10:5:10:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:12:10:12:10 | i | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:19:9:19:9 | x | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:21:5:21:5 | x | +| ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | ssa.rb:26:15:26:22 | elements | +| ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | ssa.rb:27:10:27:13 | elem | +| ssa.rb:26:12:26:22 | phi | ssa.rb:26:7:26:10 | elem | ssa.rb:29:8:29:11 | elem | +| ssa.rb:33:20:33:20 | x | ssa.rb:33:20:33:20 | x | ssa.rb:34:10:34:10 | x | +| ssa.rb:40:3:40:9 | ... = ... | ssa.rb:40:3:40:4 | m3 | ssa.rb:41:8:41:9 | m3 | +| ssa.rb:44:8:44:8 | b | ssa.rb:44:8:44:8 | b | ssa.rb:45:12:45:12 | b | +| ssa.rb:45:3:45:12 | phi | ssa.rb:45:3:45:3 | x | ssa.rb:46:8:46:8 | x | +| ssa.rb:50:3:50:8 | phi | ssa.rb:49:14:49:14 | y | ssa.rb:50:8:50:8 | y | +| ssa.rb:53:8:53:10 | foo | ssa.rb:53:8:53:10 | foo | ssa.rb:54:7:54:9 | foo | +| ssa.rb:54:3:54:11 | ... = ... | ssa.rb:54:3:54:3 | x | ssa.rb:55:8:55:8 | x | +| ssa.rb:59:3:59:8 | ... = ... | ssa.rb:59:3:59:3 | x | ssa.rb:60:3:60:3 | x | +| ssa.rb:60:3:60:9 | ... = ... | ssa.rb:59:3:59:3 | x | ssa.rb:61:8:61:8 | x | +| ssa.rb:64:8:64:8 | a | ssa.rb:64:8:64:8 | a | ssa.rb:66:3:66:3 | a | +| ssa.rb:66:3:70:5 | call to times | ssa.rb:65:3:65:10 | captured | ssa.rb:71:8:71:15 | captured | +| ssa.rb:66:11:70:5 | | ssa.rb:65:3:65:10 | captured | ssa.rb:69:5:69:12 | captured | +| ssa.rb:66:15:66:15 | a | ssa.rb:66:15:66:15 | a | ssa.rb:67:10:67:10 | a | +| ssa.rb:76:7:78:5 | | ssa.rb:75:3:75:10 | captured | ssa.rb:77:15:77:22 | captured | +| ssa.rb:84:10:86:8 | | ssa.rb:82:3:82:10 | captured | ssa.rb:85:15:85:22 | captured | +adjacentReads +| nested_scopes.rb:13:11:13:15 | ... = ... | nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:14:16:14:16 | a | nested_scopes.rb:15:11:15:11 | a | +| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:8:6:8:11 | pizzas | parameters.rb:11:14:11:19 | pizzas | +| parameters.rb:25:15:25:18 | name | parameters.rb:25:15:25:18 | name | parameters.rb:25:40:25:43 | name | parameters.rb:26:8:26:11 | name | +| scopes.rb:9:9:18:3 | | scopes.rb:7:1:7:1 | a | scopes.rb:10:9:10:9 | a | scopes.rb:11:4:11:4 | a | +| scopes.rb:13:10:13:15 | ... = ... | scopes.rb:13:10:13:15 | __synth__0__1 | scopes.rb:13:11:13:11 | __synth__0__1 | scopes.rb:13:14:13:14 | __synth__0__1 | +| scopes.rb:13:19:13:32 | ... = ... | scopes.rb:13:4:13:32 | __synth__0 | scopes.rb:13:4:13:4 | __synth__0 | scopes.rb:13:7:13:7 | __synth__0 | +| scopes.rb:13:19:13:32 | ... = ... | scopes.rb:13:4:13:32 | __synth__0 | scopes.rb:13:7:13:7 | __synth__0 | scopes.rb:13:10:13:15 | __synth__0 | +| scopes.rb:27:1:27:5 | ... = ... | scopes.rb:27:1:27:1 | x | scopes.rb:28:8:28:8 | x | scopes.rb:31:10:31:10 | x | +| scopes.rb:27:1:27:5 | ... = ... | scopes.rb:27:1:27:1 | x | scopes.rb:31:10:31:10 | x | scopes.rb:34:7:34:7 | x | +| scopes.rb:27:1:27:5 | ... = ... | scopes.rb:27:1:27:1 | x | scopes.rb:34:7:34:7 | x | scopes.rb:34:14:34:14 | x | +| scopes.rb:27:1:27:5 | ... = ... | scopes.rb:27:1:27:1 | x | scopes.rb:34:14:34:14 | x | scopes.rb:37:5:37:5 | x | +| ssa.rb:2:3:2:7 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:3:8:3:8 | i | ssa.rb:4:8:4:8 | i | +| ssa.rb:6:5:6:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:7:10:7:10 | i | ssa.rb:8:10:8:10 | i | +| ssa.rb:10:5:10:9 | ... = ... | ssa.rb:2:3:2:3 | i | ssa.rb:11:10:11:10 | i | ssa.rb:12:10:12:10 | i | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:19:9:19:9 | x | ssa.rb:20:10:20:10 | x | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:20:10:20:10 | x | ssa.rb:21:5:21:5 | x | +| ssa.rb:66:11:70:5 | | ssa.rb:65:3:65:10 | captured | ssa.rb:68:10:68:17 | captured | ssa.rb:69:5:69:12 | captured | +phi +| parameters.rb:37:3:37:18 | phi | parameters.rb:35:16:35:16 | b | parameters.rb:35:1:38:3 | | +| parameters.rb:37:3:37:18 | phi | parameters.rb:35:16:35:16 | b | parameters.rb:35:16:35:20 | ... = ... | +| parameters.rb:42:3:42:18 | phi | parameters.rb:40:15:40:15 | e | parameters.rb:40:1:43:3 | | +| parameters.rb:42:3:42:18 | phi | parameters.rb:40:15:40:15 | e | parameters.rb:40:15:40:19 | ... = ... | +| parameters.rb:55:4:55:9 | phi | parameters.rb:53:1:53:1 | x | parameters.rb:54:9:57:3 | | +| parameters.rb:55:4:55:9 | phi | parameters.rb:53:1:53:1 | x | parameters.rb:54:19:54:23 | ... = ... | +| ssa.rb:5:3:13:5 | phi | ssa.rb:2:3:2:3 | i | ssa.rb:6:5:6:9 | ... = ... | +| ssa.rb:5:3:13:5 | phi | ssa.rb:2:3:2:3 | i | ssa.rb:10:5:10:9 | ... = ... | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:18:8:18:8 | x | +| ssa.rb:19:9:19:9 | phi | ssa.rb:18:8:18:8 | x | ssa.rb:21:5:21:10 | ... = ... | +| ssa.rb:26:12:26:22 | phi | ssa.rb:26:7:26:10 | elem | ssa.rb:25:1:30:3 | | +| ssa.rb:26:12:26:22 | phi | ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | +| ssa.rb:45:3:45:12 | phi | ssa.rb:45:3:45:3 | x | ssa.rb:44:1:47:3 | | +| ssa.rb:45:3:45:12 | phi | ssa.rb:45:3:45:3 | x | ssa.rb:45:3:45:7 | ... = ... | +| ssa.rb:50:3:50:8 | phi | ssa.rb:49:14:49:14 | y | ssa.rb:49:1:51:3 | | +| ssa.rb:50:3:50:8 | phi | ssa.rb:49:14:49:14 | y | ssa.rb:49:14:49:19 | ... = ... | diff --git a/ruby/ql/test/library-tests/variables/ssa.ql b/ruby/ql/test/library-tests/variables/ssa.ql new file mode 100644 index 000000000000..df2f9cd91e57 --- /dev/null +++ b/ruby/ql/test/library-tests/variables/ssa.ql @@ -0,0 +1,27 @@ +import codeql.ruby.AST +import codeql.ruby.CFG +import codeql.ruby.ast.Variable +import codeql.ruby.dataflow.SSA + +query predicate definition(Ssa::Definition def, Variable v) { def.getSourceVariable() = v } + +query predicate read(Ssa::Definition def, Variable v, CfgNode read) { + def.getSourceVariable() = v and read = def.getARead() +} + +query predicate firstRead(Ssa::Definition def, Variable v, CfgNode read) { + def.getSourceVariable() = v and read = def.getAFirstRead() +} + +query predicate lastRead(Ssa::Definition def, Variable v, CfgNode read) { + def.getSourceVariable() = v and read = def.getALastRead() +} + +query predicate adjacentReads(Ssa::Definition def, Variable v, CfgNode read1, CfgNode read2) { + def.getSourceVariable() = v and + def.hasAdjacentReads(read1, read2) +} + +query predicate phi(Ssa::PhiNode phi, Variable v, Ssa::Definition input) { + phi.getSourceVariable() = v and input = phi.getAnInput() +} diff --git a/ruby/ql/test/library-tests/variables/ssa.rb b/ruby/ql/test/library-tests/variables/ssa.rb new file mode 100644 index 000000000000..bb13dd6c4c98 --- /dev/null +++ b/ruby/ql/test/library-tests/variables/ssa.rb @@ -0,0 +1,88 @@ +def m b # defines b_0 + i = 0 # defines i_0 + puts i # reads i_0 (first read) + puts i + 1 # reads i_0 (last read) + if b # reads b_0 + i = 1 # defines i_1 + puts i # reads i_1 (first read) + puts i + 1 # reads i_1 (last read) + else + i = 2 # defines i_2 + puts i # reads i_2 (first read) + puts i + 1 # reads i_2 (last read) + end + # defines i_3 = phi(i_1, i_2) + puts i # reads i3 (first read and last read) +end + +def m1 x + while x >= 0 + puts x + x -= 1 + end +end + +def m2 elements + for elem in elements do + puts elem + end + puts elem +end + +def m3 + [1,2,3].each do |x| + puts x + end +end + +def m4 + puts m3 + m3 = 10 + puts m3 + 1 +end + +def m5 b + x = 0 if b + puts x +end + +def m6 (x = (y = 10)) + puts y +end + +def m7 foo + x = foo.x + puts x +end + +def m8 + x = 10 + x += 10 + puts x +end + +def m9 a + captured = 10 + a.times do |a| + puts a + puts captured + captured += 1 + end + puts captured +end + +def m10 + captured = 0 + foo do + bar(baz: captured) + end +end + +def m11 + captured = 0 + foo do + bar do + puts captured + end + end +end \ No newline at end of file diff --git a/ruby/ql/test/library-tests/variables/varaccess.expected b/ruby/ql/test/library-tests/variables/varaccess.expected new file mode 100644 index 000000000000..b3b03c798af3 --- /dev/null +++ b/ruby/ql/test/library-tests/variables/varaccess.expected @@ -0,0 +1,389 @@ +variableAccess +| class_variables.rb:1:1:1:3 | @@x | class_variables.rb:1:1:1:3 | @@x | class_variables.rb:1:1:29:4 | class_variables.rb | +| class_variables.rb:3:3:3:5 | @@x | class_variables.rb:1:1:1:3 | @@x | class_variables.rb:1:1:29:4 | class_variables.rb | +| class_variables.rb:6:4:6:6 | @@x | class_variables.rb:1:1:1:3 | @@x | class_variables.rb:1:1:29:4 | class_variables.rb | +| class_variables.rb:11:7:11:9 | @@x | class_variables.rb:11:7:11:9 | @@x | class_variables.rb:9:1:16:3 | X | +| class_variables.rb:14:6:14:8 | @@x | class_variables.rb:11:7:11:9 | @@x | class_variables.rb:9:1:16:3 | X | +| class_variables.rb:19:3:19:5 | @@x | class_variables.rb:19:3:19:5 | @@x | class_variables.rb:18:1:20:3 | Y | +| class_variables.rb:23:3:23:5 | @@x | class_variables.rb:23:3:23:5 | @@x | class_variables.rb:22:1:24:3 | M | +| class_variables.rb:28:5:28:7 | @@x | class_variables.rb:28:5:28:7 | @@x | class_variables.rb:26:1:29:3 | N | +| instance_variables.rb:1:1:1:4 | @top | instance_variables.rb:1:1:1:4 | @top | instance_variables.rb:1:1:44:4 | instance_variables.rb | +| instance_variables.rb:4:3:4:6 | @foo | instance_variables.rb:4:3:4:6 | @foo | instance_variables.rb:1:1:44:4 | instance_variables.rb | +| instance_variables.rb:8:8:8:11 | @foo | instance_variables.rb:4:3:4:6 | @foo | instance_variables.rb:1:1:44:4 | instance_variables.rb | +| instance_variables.rb:11:6:11:9 | @top | instance_variables.rb:1:1:1:4 | @top | instance_variables.rb:1:1:44:4 | instance_variables.rb | +| instance_variables.rb:14:3:14:4 | @x | instance_variables.rb:14:3:14:4 | @x | instance_variables.rb:13:1:18:3 | X | +| instance_variables.rb:16:5:16:6 | @y | instance_variables.rb:16:5:16:6 | @y | instance_variables.rb:13:1:18:3 | X | +| instance_variables.rb:21:2:21:3 | @m | instance_variables.rb:21:2:21:3 | @m | instance_variables.rb:20:1:25:3 | M | +| instance_variables.rb:23:4:23:5 | @n | instance_variables.rb:23:4:23:5 | @n | instance_variables.rb:20:1:25:3 | M | +| instance_variables.rb:28:3:28:4 | @x | instance_variables.rb:28:3:28:4 | @x | instance_variables.rb:1:1:44:4 | instance_variables.rb | +| instance_variables.rb:32:12:32:13 | @x | instance_variables.rb:32:12:32:13 | @x | instance_variables.rb:1:1:44:4 | instance_variables.rb | +| instance_variables.rb:36:3:36:4 | @x | instance_variables.rb:36:3:36:4 | @x | instance_variables.rb:35:1:44:4 | C | +| instance_variables.rb:39:6:39:7 | @x | instance_variables.rb:39:6:39:7 | @x | instance_variables.rb:35:1:44:4 | C | +| instance_variables.rb:42:6:42:7 | @x | instance_variables.rb:39:6:39:7 | @x | instance_variables.rb:35:1:44:4 | C | +| nested_scopes.rb:5:3:5:3 | a | nested_scopes.rb:5:3:5:3 | a | nested_scopes.rb:4:1:39:3 | C | +| nested_scopes.rb:7:5:7:5 | a | nested_scopes.rb:7:5:7:5 | a | nested_scopes.rb:6:3:37:5 | M | +| nested_scopes.rb:9:7:9:7 | a | nested_scopes.rb:9:7:9:7 | a | nested_scopes.rb:8:5:35:7 | N | +| nested_scopes.rb:11:9:11:9 | a | nested_scopes.rb:11:9:11:9 | a | nested_scopes.rb:10:7:26:9 | D | +| nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:12:9:21:11 | show_a | +| nested_scopes.rb:14:16:14:16 | a | nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:12:9:21:11 | show_a | +| nested_scopes.rb:15:11:15:11 | a | nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:12:9:21:11 | show_a | +| nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:19:20:13 | do ... end | +| nested_scopes.rb:16:13:16:13 | a | nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:19:20:13 | do ... end | +| nested_scopes.rb:16:26:16:26 | x | nested_scopes.rb:16:26:16:26 | x | nested_scopes.rb:16:21:19:15 | do ... end | +| nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:16:21:19:15 | do ... end | +| nested_scopes.rb:17:15:17:15 | a | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:16:21:19:15 | do ... end | +| nested_scopes.rb:18:15:18:15 | a | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:16:21:19:15 | do ... end | +| nested_scopes.rb:18:26:18:26 | x | nested_scopes.rb:18:26:18:26 | x | nested_scopes.rb:18:23:18:36 | { ... } | +| nested_scopes.rb:18:34:18:34 | a | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:16:21:19:15 | do ... end | +| nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:9:24:11 | show_a2 | +| nested_scopes.rb:23:16:23:16 | a | nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:9:24:11 | show_a2 | +| nested_scopes.rb:25:14:25:14 | a | nested_scopes.rb:11:9:11:9 | a | nested_scopes.rb:10:7:26:9 | D | +| nested_scopes.rb:31:11:31:11 | a | nested_scopes.rb:31:11:31:11 | a | nested_scopes.rb:30:7:33:9 | class << ... | +| nested_scopes.rb:32:16:32:16 | a | nested_scopes.rb:31:11:31:11 | a | nested_scopes.rb:30:7:33:9 | class << ... | +| nested_scopes.rb:34:12:34:12 | a | nested_scopes.rb:9:7:9:7 | a | nested_scopes.rb:8:5:35:7 | N | +| nested_scopes.rb:36:10:36:10 | a | nested_scopes.rb:7:5:7:5 | a | nested_scopes.rb:6:3:37:5 | M | +| nested_scopes.rb:38:8:38:8 | a | nested_scopes.rb:5:3:5:3 | a | nested_scopes.rb:4:1:39:3 | C | +| nested_scopes.rb:40:1:40:1 | d | nested_scopes.rb:40:1:40:1 | d | nested_scopes.rb:1:1:42:1 | nested_scopes.rb | +| nested_scopes.rb:41:1:41:1 | d | nested_scopes.rb:40:1:40:1 | d | nested_scopes.rb:1:1:42:1 | nested_scopes.rb | +| parameters.rb:1:14:1:14 | x | parameters.rb:1:14:1:14 | x | parameters.rb:1:9:5:3 | do ... end | +| parameters.rb:1:18:1:18 | y | parameters.rb:1:18:1:18 | y | parameters.rb:1:9:5:3 | do ... end | +| parameters.rb:2:4:2:4 | y | parameters.rb:1:18:1:18 | y | parameters.rb:1:9:5:3 | do ... end | +| parameters.rb:3:9:3:9 | x | parameters.rb:1:14:1:14 | x | parameters.rb:1:9:5:3 | do ... end | +| parameters.rb:4:9:4:9 | y | parameters.rb:1:18:1:18 | y | parameters.rb:1:9:5:3 | do ... end | +| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | parameters.rb:7:1:13:3 | order_pizza | +| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:1:13:3 | order_pizza | +| parameters.rb:8:6:8:11 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:1:13:3 | order_pizza | +| parameters.rb:9:25:9:30 | client | parameters.rb:7:17:7:22 | client | parameters.rb:7:1:13:3 | order_pizza | +| parameters.rb:11:14:11:19 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:1:13:3 | order_pizza | +| parameters.rb:11:41:11:46 | client | parameters.rb:7:17:7:22 | client | parameters.rb:7:1:13:3 | order_pizza | +| parameters.rb:15:17:15:19 | map | parameters.rb:15:17:15:19 | map | parameters.rb:15:1:19:3 | print_map | +| parameters.rb:16:3:16:5 | map | parameters.rb:15:17:15:19 | map | parameters.rb:15:1:19:3 | print_map | +| parameters.rb:16:16:16:18 | key | parameters.rb:16:16:16:18 | key | parameters.rb:16:12:18:5 | do ... end | +| parameters.rb:16:21:16:25 | value | parameters.rb:16:21:16:25 | value | parameters.rb:16:12:18:5 | do ... end | +| parameters.rb:17:13:17:15 | key | parameters.rb:16:16:16:18 | key | parameters.rb:16:12:18:5 | do ... end | +| parameters.rb:17:22:17:26 | value | parameters.rb:16:21:16:25 | value | parameters.rb:16:12:18:5 | do ... end | +| parameters.rb:21:17:21:21 | block | parameters.rb:21:17:21:21 | block | parameters.rb:21:1:23:3 | call_block | +| parameters.rb:22:3:22:7 | block | parameters.rb:21:17:21:21 | block | parameters.rb:21:1:23:3 | call_block | +| parameters.rb:25:15:25:18 | name | parameters.rb:25:15:25:18 | name | parameters.rb:25:1:28:3 | opt_param | +| parameters.rb:25:33:25:36 | size | parameters.rb:25:33:25:36 | size | parameters.rb:25:1:28:3 | opt_param | +| parameters.rb:25:40:25:43 | name | parameters.rb:25:15:25:18 | name | parameters.rb:25:1:28:3 | opt_param | +| parameters.rb:26:8:26:11 | name | parameters.rb:25:15:25:18 | name | parameters.rb:25:1:28:3 | opt_param | +| parameters.rb:27:8:27:11 | size | parameters.rb:25:33:25:36 | size | parameters.rb:25:1:28:3 | opt_param | +| parameters.rb:30:15:30:19 | first | parameters.rb:30:15:30:19 | first | parameters.rb:30:1:32:3 | key_param | +| parameters.rb:30:24:30:29 | middle | parameters.rb:30:24:30:29 | middle | parameters.rb:30:1:32:3 | key_param | +| parameters.rb:30:36:30:39 | last | parameters.rb:30:36:30:39 | last | parameters.rb:30:1:32:3 | key_param | +| parameters.rb:31:11:31:15 | first | parameters.rb:30:15:30:19 | first | parameters.rb:30:1:32:3 | key_param | +| parameters.rb:31:20:31:25 | middle | parameters.rb:30:24:30:29 | middle | parameters.rb:30:1:32:3 | key_param | +| parameters.rb:31:30:31:33 | last | parameters.rb:30:36:30:39 | last | parameters.rb:30:1:32:3 | key_param | +| parameters.rb:34:1:34:1 | b | parameters.rb:34:1:34:1 | b | parameters.rb:1:1:58:1 | parameters.rb | +| parameters.rb:35:11:35:11 | a | parameters.rb:35:11:35:11 | a | parameters.rb:35:1:38:3 | multi | +| parameters.rb:35:16:35:16 | b | parameters.rb:35:16:35:16 | b | parameters.rb:35:1:38:3 | multi | +| parameters.rb:37:11:37:11 | a | parameters.rb:35:11:35:11 | a | parameters.rb:35:1:38:3 | multi | +| parameters.rb:37:16:37:16 | b | parameters.rb:35:16:35:16 | b | parameters.rb:35:1:38:3 | multi | +| parameters.rb:40:12:40:12 | d | parameters.rb:40:12:40:12 | d | parameters.rb:40:1:43:3 | multi2 | +| parameters.rb:40:15:40:15 | e | parameters.rb:40:15:40:15 | e | parameters.rb:40:1:43:3 | multi2 | +| parameters.rb:42:11:42:11 | d | parameters.rb:40:12:40:12 | d | parameters.rb:40:1:43:3 | multi2 | +| parameters.rb:42:16:42:16 | e | parameters.rb:40:15:40:15 | e | parameters.rb:40:1:43:3 | multi2 | +| parameters.rb:45:20:45:20 | _ | parameters.rb:45:20:45:20 | _ | parameters.rb:45:1:47:3 | dup_underscore | +| parameters.rb:46:8:46:8 | _ | parameters.rb:45:20:45:20 | _ | parameters.rb:45:1:47:3 | dup_underscore | +| parameters.rb:49:13:49:13 | a | parameters.rb:49:13:49:13 | a | parameters.rb:49:1:51:3 | tuples | +| parameters.rb:49:15:49:15 | b | parameters.rb:49:15:49:15 | b | parameters.rb:49:1:51:3 | tuples | +| parameters.rb:50:11:50:11 | a | parameters.rb:49:13:49:13 | a | parameters.rb:49:1:51:3 | tuples | +| parameters.rb:50:16:50:16 | b | parameters.rb:49:15:49:15 | b | parameters.rb:49:1:51:3 | tuples | +| parameters.rb:53:1:53:1 | x | parameters.rb:53:1:53:1 | x | parameters.rb:1:1:58:1 | parameters.rb | +| parameters.rb:54:14:54:14 | y | parameters.rb:54:14:54:14 | y | parameters.rb:54:9:57:3 | do ... end | +| parameters.rb:54:19:54:19 | x | parameters.rb:53:1:53:1 | x | parameters.rb:1:1:58:1 | parameters.rb | +| parameters.rb:55:9:55:9 | x | parameters.rb:53:1:53:1 | x | parameters.rb:1:1:58:1 | parameters.rb | +| parameters.rb:56:9:56:9 | y | parameters.rb:54:14:54:14 | y | parameters.rb:54:9:57:3 | do ... end | +| scopes.rb:2:14:2:14 | x | scopes.rb:2:14:2:14 | x | scopes.rb:2:9:6:3 | do ... end | +| scopes.rb:4:4:4:4 | a | scopes.rb:4:4:4:4 | a | scopes.rb:2:9:6:3 | do ... end | +| scopes.rb:5:9:5:9 | a | scopes.rb:4:4:4:4 | a | scopes.rb:2:9:6:3 | do ... end | +| scopes.rb:7:1:7:1 | a | scopes.rb:7:1:7:1 | a | scopes.rb:1:1:49:4 | scopes.rb | +| scopes.rb:8:6:8:6 | a | scopes.rb:7:1:7:1 | a | scopes.rb:1:1:49:4 | scopes.rb | +| scopes.rb:9:14:9:14 | x | scopes.rb:9:14:9:14 | x | scopes.rb:9:9:18:3 | do ... end | +| scopes.rb:10:9:10:9 | a | scopes.rb:7:1:7:1 | a | scopes.rb:1:1:49:4 | scopes.rb | +| scopes.rb:11:4:11:4 | a | scopes.rb:7:1:7:1 | a | scopes.rb:1:1:49:4 | scopes.rb | +| scopes.rb:11:4:11:4 | a | scopes.rb:7:1:7:1 | a | scopes.rb:1:1:49:4 | scopes.rb | +| scopes.rb:12:9:12:9 | a | scopes.rb:7:1:7:1 | a | scopes.rb:1:1:49:4 | scopes.rb | +| scopes.rb:13:4:13:4 | a | scopes.rb:7:1:7:1 | a | scopes.rb:1:1:49:4 | scopes.rb | +| scopes.rb:13:7:13:7 | b | scopes.rb:13:7:13:7 | b | scopes.rb:9:9:18:3 | do ... end | +| scopes.rb:13:11:13:11 | c | scopes.rb:13:11:13:11 | c | scopes.rb:9:9:18:3 | do ... end | +| scopes.rb:13:14:13:14 | d | scopes.rb:13:14:13:14 | d | scopes.rb:9:9:18:3 | do ... end | +| scopes.rb:14:9:14:9 | a | scopes.rb:7:1:7:1 | a | scopes.rb:1:1:49:4 | scopes.rb | +| scopes.rb:15:9:15:9 | b | scopes.rb:13:7:13:7 | b | scopes.rb:9:9:18:3 | do ... end | +| scopes.rb:16:9:16:9 | c | scopes.rb:13:11:13:11 | c | scopes.rb:9:9:18:3 | do ... end | +| scopes.rb:17:9:17:9 | d | scopes.rb:13:14:13:14 | d | scopes.rb:9:9:18:3 | do ... end | +| scopes.rb:24:1:24:6 | script | scopes.rb:24:1:24:6 | script | scopes.rb:1:1:49:4 | scopes.rb | +| scopes.rb:27:1:27:1 | x | scopes.rb:27:1:27:1 | x | scopes.rb:1:1:49:4 | scopes.rb | +| scopes.rb:28:8:28:8 | x | scopes.rb:27:1:27:1 | x | scopes.rb:1:1:49:4 | scopes.rb | +| scopes.rb:29:3:29:3 | x | scopes.rb:29:3:29:3 | x | scopes.rb:28:1:30:3 | B | +| scopes.rb:31:10:31:10 | x | scopes.rb:27:1:27:1 | x | scopes.rb:1:1:49:4 | scopes.rb | +| scopes.rb:32:3:32:3 | x | scopes.rb:32:3:32:3 | x | scopes.rb:31:1:33:3 | class << ... | +| scopes.rb:34:7:34:7 | x | scopes.rb:27:1:27:1 | x | scopes.rb:1:1:49:4 | scopes.rb | +| scopes.rb:34:14:34:14 | x | scopes.rb:27:1:27:1 | x | scopes.rb:1:1:49:4 | scopes.rb | +| scopes.rb:35:3:35:3 | x | scopes.rb:35:3:35:3 | x | scopes.rb:34:1:36:3 | C | +| scopes.rb:37:5:37:5 | x | scopes.rb:27:1:27:1 | x | scopes.rb:1:1:49:4 | scopes.rb | +| scopes.rb:38:3:38:3 | x | scopes.rb:38:3:38:3 | x | scopes.rb:37:1:39:3 | foo | +| scopes.rb:42:2:42:4 | var | scopes.rb:42:2:42:4 | var | scopes.rb:41:1:49:3 | M | +| scopes.rb:43:2:43:4 | foo | scopes.rb:43:2:43:4 | foo | scopes.rb:41:1:49:3 | M | +| scopes.rb:44:5:44:7 | var | scopes.rb:42:2:42:4 | var | scopes.rb:41:1:49:3 | M | +| scopes.rb:46:5:46:8 | var2 | scopes.rb:46:5:46:8 | var2 | scopes.rb:41:1:49:3 | M | +| scopes.rb:47:5:47:8 | var2 | scopes.rb:46:5:46:8 | var2 | scopes.rb:41:1:49:3 | M | +| ssa.rb:1:7:1:7 | b | ssa.rb:1:7:1:7 | b | ssa.rb:1:1:16:3 | m | +| ssa.rb:2:3:2:3 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | m | +| ssa.rb:3:8:3:8 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | m | +| ssa.rb:4:8:4:8 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | m | +| ssa.rb:5:6:5:6 | b | ssa.rb:1:7:1:7 | b | ssa.rb:1:1:16:3 | m | +| ssa.rb:6:5:6:5 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | m | +| ssa.rb:7:10:7:10 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | m | +| ssa.rb:8:10:8:10 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | m | +| ssa.rb:10:5:10:5 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | m | +| ssa.rb:11:10:11:10 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | m | +| ssa.rb:12:10:12:10 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | m | +| ssa.rb:15:8:15:8 | i | ssa.rb:2:3:2:3 | i | ssa.rb:1:1:16:3 | m | +| ssa.rb:18:8:18:8 | x | ssa.rb:18:8:18:8 | x | ssa.rb:18:1:23:3 | m1 | +| ssa.rb:19:9:19:9 | x | ssa.rb:18:8:18:8 | x | ssa.rb:18:1:23:3 | m1 | +| ssa.rb:20:10:20:10 | x | ssa.rb:18:8:18:8 | x | ssa.rb:18:1:23:3 | m1 | +| ssa.rb:21:5:21:5 | x | ssa.rb:18:8:18:8 | x | ssa.rb:18:1:23:3 | m1 | +| ssa.rb:21:5:21:5 | x | ssa.rb:18:8:18:8 | x | ssa.rb:18:1:23:3 | m1 | +| ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | ssa.rb:25:1:30:3 | m2 | +| ssa.rb:26:7:26:10 | elem | ssa.rb:26:7:26:10 | elem | ssa.rb:25:1:30:3 | m2 | +| ssa.rb:26:15:26:22 | elements | ssa.rb:25:8:25:15 | elements | ssa.rb:25:1:30:3 | m2 | +| ssa.rb:27:10:27:13 | elem | ssa.rb:26:7:26:10 | elem | ssa.rb:25:1:30:3 | m2 | +| ssa.rb:29:8:29:11 | elem | ssa.rb:26:7:26:10 | elem | ssa.rb:25:1:30:3 | m2 | +| ssa.rb:33:20:33:20 | x | ssa.rb:33:20:33:20 | x | ssa.rb:33:16:35:5 | do ... end | +| ssa.rb:34:10:34:10 | x | ssa.rb:33:20:33:20 | x | ssa.rb:33:16:35:5 | do ... end | +| ssa.rb:40:3:40:4 | m3 | ssa.rb:40:3:40:4 | m3 | ssa.rb:38:1:42:3 | m4 | +| ssa.rb:41:8:41:9 | m3 | ssa.rb:40:3:40:4 | m3 | ssa.rb:38:1:42:3 | m4 | +| ssa.rb:44:8:44:8 | b | ssa.rb:44:8:44:8 | b | ssa.rb:44:1:47:3 | m5 | +| ssa.rb:45:3:45:3 | x | ssa.rb:45:3:45:3 | x | ssa.rb:44:1:47:3 | m5 | +| ssa.rb:45:12:45:12 | b | ssa.rb:44:8:44:8 | b | ssa.rb:44:1:47:3 | m5 | +| ssa.rb:46:8:46:8 | x | ssa.rb:45:3:45:3 | x | ssa.rb:44:1:47:3 | m5 | +| ssa.rb:49:9:49:9 | x | ssa.rb:49:9:49:9 | x | ssa.rb:49:1:51:3 | m6 | +| ssa.rb:49:14:49:14 | y | ssa.rb:49:14:49:14 | y | ssa.rb:49:1:51:3 | m6 | +| ssa.rb:50:8:50:8 | y | ssa.rb:49:14:49:14 | y | ssa.rb:49:1:51:3 | m6 | +| ssa.rb:53:8:53:10 | foo | ssa.rb:53:8:53:10 | foo | ssa.rb:53:1:56:3 | m7 | +| ssa.rb:54:3:54:3 | x | ssa.rb:54:3:54:3 | x | ssa.rb:53:1:56:3 | m7 | +| ssa.rb:54:7:54:9 | foo | ssa.rb:53:8:53:10 | foo | ssa.rb:53:1:56:3 | m7 | +| ssa.rb:55:8:55:8 | x | ssa.rb:54:3:54:3 | x | ssa.rb:53:1:56:3 | m7 | +| ssa.rb:59:3:59:3 | x | ssa.rb:59:3:59:3 | x | ssa.rb:58:1:62:3 | m8 | +| ssa.rb:60:3:60:3 | x | ssa.rb:59:3:59:3 | x | ssa.rb:58:1:62:3 | m8 | +| ssa.rb:60:3:60:3 | x | ssa.rb:59:3:59:3 | x | ssa.rb:58:1:62:3 | m8 | +| ssa.rb:61:8:61:8 | x | ssa.rb:59:3:59:3 | x | ssa.rb:58:1:62:3 | m8 | +| ssa.rb:64:8:64:8 | a | ssa.rb:64:8:64:8 | a | ssa.rb:64:1:72:3 | m9 | +| ssa.rb:65:3:65:10 | captured | ssa.rb:65:3:65:10 | captured | ssa.rb:64:1:72:3 | m9 | +| ssa.rb:66:3:66:3 | a | ssa.rb:64:8:64:8 | a | ssa.rb:64:1:72:3 | m9 | +| ssa.rb:66:15:66:15 | a | ssa.rb:66:15:66:15 | a | ssa.rb:66:11:70:5 | do ... end | +| ssa.rb:67:10:67:10 | a | ssa.rb:66:15:66:15 | a | ssa.rb:66:11:70:5 | do ... end | +| ssa.rb:68:10:68:17 | captured | ssa.rb:65:3:65:10 | captured | ssa.rb:64:1:72:3 | m9 | +| ssa.rb:69:5:69:12 | captured | ssa.rb:65:3:65:10 | captured | ssa.rb:64:1:72:3 | m9 | +| ssa.rb:69:5:69:12 | captured | ssa.rb:65:3:65:10 | captured | ssa.rb:64:1:72:3 | m9 | +| ssa.rb:71:8:71:15 | captured | ssa.rb:65:3:65:10 | captured | ssa.rb:64:1:72:3 | m9 | +| ssa.rb:75:3:75:10 | captured | ssa.rb:75:3:75:10 | captured | ssa.rb:74:1:79:3 | m10 | +| ssa.rb:77:15:77:22 | captured | ssa.rb:75:3:75:10 | captured | ssa.rb:74:1:79:3 | m10 | +| ssa.rb:82:3:82:10 | captured | ssa.rb:82:3:82:10 | captured | ssa.rb:81:1:88:3 | m11 | +| ssa.rb:85:15:85:22 | captured | ssa.rb:82:3:82:10 | captured | ssa.rb:81:1:88:3 | m11 | +explicitWrite +| class_variables.rb:1:1:1:3 | @@x | class_variables.rb:1:1:1:8 | ... = ... | +| class_variables.rb:19:3:19:5 | @@x | class_variables.rb:19:3:19:10 | ... = ... | +| class_variables.rb:23:3:23:5 | @@x | class_variables.rb:23:3:23:10 | ... = ... | +| instance_variables.rb:1:1:1:4 | @top | instance_variables.rb:1:1:1:8 | ... = ... | +| instance_variables.rb:4:3:4:6 | @foo | instance_variables.rb:4:3:4:11 | ... = ... | +| instance_variables.rb:14:3:14:4 | @x | instance_variables.rb:14:3:14:9 | ... = ... | +| instance_variables.rb:16:5:16:6 | @y | instance_variables.rb:16:5:16:10 | ... = ... | +| instance_variables.rb:21:2:21:3 | @m | instance_variables.rb:21:2:21:8 | ... = ... | +| instance_variables.rb:23:4:23:5 | @n | instance_variables.rb:23:4:23:9 | ... = ... | +| instance_variables.rb:28:3:28:4 | @x | instance_variables.rb:28:3:28:10 | ... = ... | +| instance_variables.rb:32:12:32:13 | @x | instance_variables.rb:32:12:32:19 | ... = ... | +| instance_variables.rb:36:3:36:4 | @x | instance_variables.rb:36:3:36:9 | ... = ... | +| instance_variables.rb:39:6:39:7 | @x | instance_variables.rb:39:6:39:12 | ... = ... | +| nested_scopes.rb:5:3:5:3 | a | nested_scopes.rb:5:3:5:7 | ... = ... | +| nested_scopes.rb:7:5:7:5 | a | nested_scopes.rb:7:5:7:9 | ... = ... | +| nested_scopes.rb:9:7:9:7 | a | nested_scopes.rb:9:7:9:11 | ... = ... | +| nested_scopes.rb:11:9:11:9 | a | nested_scopes.rb:11:9:11:13 | ... = ... | +| nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:13:11:13:15 | ... = ... | +| nested_scopes.rb:17:15:17:15 | a | nested_scopes.rb:17:15:17:19 | ... = ... | +| nested_scopes.rb:31:11:31:11 | a | nested_scopes.rb:31:11:31:16 | ... = ... | +| nested_scopes.rb:40:1:40:1 | d | nested_scopes.rb:40:1:40:18 | ... = ... | +| parameters.rb:2:4:2:4 | y | parameters.rb:2:4:2:8 | ... = ... | +| parameters.rb:34:1:34:1 | b | parameters.rb:34:1:34:5 | ... = ... | +| parameters.rb:35:16:35:16 | b | parameters.rb:35:16:35:20 | ... = ... | +| parameters.rb:40:15:40:15 | e | parameters.rb:40:15:40:19 | ... = ... | +| parameters.rb:53:1:53:1 | x | parameters.rb:53:1:53:6 | ... = ... | +| parameters.rb:54:19:54:19 | x | parameters.rb:54:19:54:23 | ... = ... | +| scopes.rb:4:4:4:4 | a | scopes.rb:4:4:4:8 | ... = ... | +| scopes.rb:7:1:7:1 | a | scopes.rb:7:1:7:5 | ... = ... | +| scopes.rb:11:4:11:4 | a | scopes.rb:11:4:11:9 | ... += ... | +| scopes.rb:11:4:11:4 | a | scopes.rb:11:4:11:9 | ... = ... | +| scopes.rb:13:4:13:4 | a | scopes.rb:13:4:13:4 | ... = ... | +| scopes.rb:13:4:13:4 | a | scopes.rb:13:4:13:32 | ... = ... | +| scopes.rb:13:7:13:7 | b | scopes.rb:13:4:13:32 | ... = ... | +| scopes.rb:13:7:13:7 | b | scopes.rb:13:7:13:7 | ... = ... | +| scopes.rb:13:10:13:15 | __synth__0__1 | scopes.rb:13:10:13:15 | ... = ... | +| scopes.rb:13:11:13:11 | c | scopes.rb:13:4:13:32 | ... = ... | +| scopes.rb:13:11:13:11 | c | scopes.rb:13:11:13:11 | ... = ... | +| scopes.rb:13:14:13:14 | d | scopes.rb:13:4:13:32 | ... = ... | +| scopes.rb:13:14:13:14 | d | scopes.rb:13:14:13:14 | ... = ... | +| scopes.rb:13:19:13:32 | __synth__0 | scopes.rb:13:19:13:32 | ... = ... | +| scopes.rb:21:1:21:7 | $global | scopes.rb:21:1:21:12 | ... = ... | +| scopes.rb:24:1:24:6 | script | scopes.rb:24:1:24:11 | ... = ... | +| scopes.rb:27:1:27:1 | x | scopes.rb:27:1:27:5 | ... = ... | +| scopes.rb:29:3:29:3 | x | scopes.rb:29:3:29:7 | ... = ... | +| scopes.rb:32:3:32:3 | x | scopes.rb:32:3:32:7 | ... = ... | +| scopes.rb:35:3:35:3 | x | scopes.rb:35:3:35:7 | ... = ... | +| scopes.rb:38:3:38:3 | x | scopes.rb:38:3:38:7 | ... = ... | +| scopes.rb:42:2:42:4 | var | scopes.rb:42:2:42:9 | ... = ... | +| scopes.rb:43:2:43:4 | foo | scopes.rb:43:2:43:13 | ... = ... | +| scopes.rb:46:5:46:8 | var2 | scopes.rb:46:5:46:13 | ... = ... | +| ssa.rb:2:3:2:3 | i | ssa.rb:2:3:2:7 | ... = ... | +| ssa.rb:6:5:6:5 | i | ssa.rb:6:5:6:9 | ... = ... | +| ssa.rb:10:5:10:5 | i | ssa.rb:10:5:10:9 | ... = ... | +| ssa.rb:21:5:21:5 | x | ssa.rb:21:5:21:10 | ... -= ... | +| ssa.rb:21:5:21:5 | x | ssa.rb:21:5:21:10 | ... = ... | +| ssa.rb:40:3:40:4 | m3 | ssa.rb:40:3:40:9 | ... = ... | +| ssa.rb:45:3:45:3 | x | ssa.rb:45:3:45:7 | ... = ... | +| ssa.rb:49:14:49:14 | y | ssa.rb:49:14:49:19 | ... = ... | +| ssa.rb:54:3:54:3 | x | ssa.rb:54:3:54:11 | ... = ... | +| ssa.rb:59:3:59:3 | x | ssa.rb:59:3:59:8 | ... = ... | +| ssa.rb:60:3:60:3 | x | ssa.rb:60:3:60:9 | ... += ... | +| ssa.rb:60:3:60:3 | x | ssa.rb:60:3:60:9 | ... = ... | +| ssa.rb:65:3:65:10 | captured | ssa.rb:65:3:65:15 | ... = ... | +| ssa.rb:69:5:69:12 | captured | ssa.rb:69:5:69:17 | ... += ... | +| ssa.rb:69:5:69:12 | captured | ssa.rb:69:5:69:17 | ... = ... | +| ssa.rb:75:3:75:10 | captured | ssa.rb:75:3:75:14 | ... = ... | +| ssa.rb:82:3:82:10 | captured | ssa.rb:82:3:82:14 | ... = ... | +implicitWrite +| nested_scopes.rb:15:23:15:23 | a | +| nested_scopes.rb:16:26:16:26 | x | +| nested_scopes.rb:16:29:16:29 | a | +| nested_scopes.rb:18:26:18:26 | x | +| nested_scopes.rb:22:21:22:21 | a | +| parameters.rb:1:14:1:14 | x | +| parameters.rb:1:18:1:18 | y | +| parameters.rb:7:17:7:22 | client | +| parameters.rb:7:26:7:31 | pizzas | +| parameters.rb:15:17:15:19 | map | +| parameters.rb:16:16:16:18 | key | +| parameters.rb:16:21:16:25 | value | +| parameters.rb:21:17:21:21 | block | +| parameters.rb:25:15:25:18 | name | +| parameters.rb:25:33:25:36 | size | +| parameters.rb:30:15:30:19 | first | +| parameters.rb:30:24:30:29 | middle | +| parameters.rb:30:36:30:39 | last | +| parameters.rb:35:11:35:11 | a | +| parameters.rb:40:12:40:12 | d | +| parameters.rb:45:20:45:20 | _ | +| parameters.rb:49:13:49:13 | a | +| parameters.rb:49:15:49:15 | b | +| parameters.rb:54:14:54:14 | y | +| scopes.rb:2:14:2:14 | x | +| scopes.rb:9:14:9:14 | x | +| ssa.rb:1:7:1:7 | b | +| ssa.rb:18:8:18:8 | x | +| ssa.rb:25:8:25:15 | elements | +| ssa.rb:26:7:26:10 | elem | +| ssa.rb:33:20:33:20 | x | +| ssa.rb:44:8:44:8 | b | +| ssa.rb:49:9:49:9 | x | +| ssa.rb:53:8:53:10 | foo | +| ssa.rb:64:8:64:8 | a | +| ssa.rb:66:15:66:15 | a | +readAccess +| class_variables.rb:3:3:3:5 | @@x | +| class_variables.rb:6:4:6:6 | @@x | +| class_variables.rb:11:7:11:9 | @@x | +| class_variables.rb:14:6:14:8 | @@x | +| class_variables.rb:28:5:28:7 | @@x | +| instance_variables.rb:8:8:8:11 | @foo | +| instance_variables.rb:11:6:11:9 | @top | +| instance_variables.rb:42:6:42:7 | @x | +| nested_scopes.rb:14:16:14:16 | a | +| nested_scopes.rb:15:11:15:11 | a | +| nested_scopes.rb:16:13:16:13 | a | +| nested_scopes.rb:18:15:18:15 | a | +| nested_scopes.rb:18:34:18:34 | a | +| nested_scopes.rb:23:16:23:16 | a | +| nested_scopes.rb:25:14:25:14 | a | +| nested_scopes.rb:32:16:32:16 | a | +| nested_scopes.rb:34:12:34:12 | a | +| nested_scopes.rb:36:10:36:10 | a | +| nested_scopes.rb:38:8:38:8 | a | +| nested_scopes.rb:41:1:41:1 | d | +| parameters.rb:3:9:3:9 | x | +| parameters.rb:4:9:4:9 | y | +| parameters.rb:8:6:8:11 | pizzas | +| parameters.rb:9:25:9:30 | client | +| parameters.rb:11:14:11:19 | pizzas | +| parameters.rb:11:41:11:46 | client | +| parameters.rb:16:3:16:5 | map | +| parameters.rb:17:13:17:15 | key | +| parameters.rb:17:22:17:26 | value | +| parameters.rb:22:3:22:7 | block | +| parameters.rb:25:40:25:43 | name | +| parameters.rb:26:8:26:11 | name | +| parameters.rb:27:8:27:11 | size | +| parameters.rb:31:11:31:15 | first | +| parameters.rb:31:20:31:25 | middle | +| parameters.rb:31:30:31:33 | last | +| parameters.rb:37:11:37:11 | a | +| parameters.rb:37:16:37:16 | b | +| parameters.rb:42:11:42:11 | d | +| parameters.rb:42:16:42:16 | e | +| parameters.rb:46:8:46:8 | _ | +| parameters.rb:50:11:50:11 | a | +| parameters.rb:50:16:50:16 | b | +| parameters.rb:55:9:55:9 | x | +| parameters.rb:56:9:56:9 | y | +| scopes.rb:5:9:5:9 | a | +| scopes.rb:8:6:8:6 | a | +| scopes.rb:10:9:10:9 | a | +| scopes.rb:11:4:11:4 | a | +| scopes.rb:12:9:12:9 | a | +| scopes.rb:13:4:13:4 | __synth__0 | +| scopes.rb:13:7:13:7 | __synth__0 | +| scopes.rb:13:10:13:15 | __synth__0 | +| scopes.rb:13:11:13:11 | __synth__0__1 | +| scopes.rb:13:14:13:14 | __synth__0__1 | +| scopes.rb:14:9:14:9 | a | +| scopes.rb:15:9:15:9 | b | +| scopes.rb:16:9:16:9 | c | +| scopes.rb:17:9:17:9 | d | +| scopes.rb:24:10:24:11 | $0 | +| scopes.rb:28:8:28:8 | x | +| scopes.rb:31:10:31:10 | x | +| scopes.rb:34:7:34:7 | x | +| scopes.rb:34:14:34:14 | x | +| scopes.rb:37:5:37:5 | x | +| scopes.rb:44:5:44:7 | var | +| scopes.rb:47:5:47:8 | var2 | +| ssa.rb:3:8:3:8 | i | +| ssa.rb:4:8:4:8 | i | +| ssa.rb:5:6:5:6 | b | +| ssa.rb:7:10:7:10 | i | +| ssa.rb:8:10:8:10 | i | +| ssa.rb:11:10:11:10 | i | +| ssa.rb:12:10:12:10 | i | +| ssa.rb:15:8:15:8 | i | +| ssa.rb:19:9:19:9 | x | +| ssa.rb:20:10:20:10 | x | +| ssa.rb:21:5:21:5 | x | +| ssa.rb:26:15:26:22 | elements | +| ssa.rb:27:10:27:13 | elem | +| ssa.rb:29:8:29:11 | elem | +| ssa.rb:34:10:34:10 | x | +| ssa.rb:41:8:41:9 | m3 | +| ssa.rb:45:12:45:12 | b | +| ssa.rb:46:8:46:8 | x | +| ssa.rb:50:8:50:8 | y | +| ssa.rb:54:7:54:9 | foo | +| ssa.rb:55:8:55:8 | x | +| ssa.rb:60:3:60:3 | x | +| ssa.rb:61:8:61:8 | x | +| ssa.rb:66:3:66:3 | a | +| ssa.rb:67:10:67:10 | a | +| ssa.rb:68:10:68:17 | captured | +| ssa.rb:69:5:69:12 | captured | +| ssa.rb:71:8:71:15 | captured | +| ssa.rb:77:15:77:22 | captured | +| ssa.rb:85:15:85:22 | captured | diff --git a/ruby/ql/test/library-tests/variables/varaccess.ql b/ruby/ql/test/library-tests/variables/varaccess.ql new file mode 100644 index 000000000000..18cc26c1d249 --- /dev/null +++ b/ruby/ql/test/library-tests/variables/varaccess.ql @@ -0,0 +1,15 @@ +import codeql.ruby.AST +import codeql.ruby.ast.Variable + +query predicate variableAccess(VariableAccess access, Variable variable, Scope scope) { + variable = access.getVariable() and + scope = variable.getDeclaringScope() +} + +query predicate explicitWrite(VariableWriteAccess write, AstNode assignment) { + write.isExplicitWrite(assignment) +} + +query predicate implicitWrite(VariableWriteAccess write) { write.isImplicitWrite() } + +query predicate readAccess(VariableReadAccess read) { any() } diff --git a/ruby/ql/test/library-tests/variables/variable.expected b/ruby/ql/test/library-tests/variables/variable.expected new file mode 100644 index 000000000000..d7c3a54270fb --- /dev/null +++ b/ruby/ql/test/library-tests/variables/variable.expected @@ -0,0 +1,89 @@ +| class_variables.rb:1:1:1:3 | @@x | +| class_variables.rb:11:7:11:9 | @@x | +| class_variables.rb:19:3:19:5 | @@x | +| class_variables.rb:23:3:23:5 | @@x | +| class_variables.rb:28:5:28:7 | @@x | +| file://:0:0:0:0 | $0 | +| file://:0:0:0:0 | $global | +| instance_variables.rb:1:1:1:4 | @top | +| instance_variables.rb:4:3:4:6 | @foo | +| instance_variables.rb:14:3:14:4 | @x | +| instance_variables.rb:16:5:16:6 | @y | +| instance_variables.rb:21:2:21:3 | @m | +| instance_variables.rb:23:4:23:5 | @n | +| instance_variables.rb:28:3:28:4 | @x | +| instance_variables.rb:32:12:32:13 | @x | +| instance_variables.rb:36:3:36:4 | @x | +| instance_variables.rb:39:6:39:7 | @x | +| nested_scopes.rb:5:3:5:3 | a | +| nested_scopes.rb:7:5:7:5 | a | +| nested_scopes.rb:9:7:9:7 | a | +| nested_scopes.rb:11:9:11:9 | a | +| nested_scopes.rb:13:11:13:11 | a | +| nested_scopes.rb:15:23:15:23 | a | +| nested_scopes.rb:16:26:16:26 | x | +| nested_scopes.rb:16:29:16:29 | a | +| nested_scopes.rb:18:26:18:26 | x | +| nested_scopes.rb:22:21:22:21 | a | +| nested_scopes.rb:31:11:31:11 | a | +| nested_scopes.rb:40:1:40:1 | d | +| parameters.rb:1:14:1:14 | x | +| parameters.rb:1:18:1:18 | y | +| parameters.rb:7:17:7:22 | client | +| parameters.rb:7:26:7:31 | pizzas | +| parameters.rb:15:17:15:19 | map | +| parameters.rb:16:16:16:18 | key | +| parameters.rb:16:21:16:25 | value | +| parameters.rb:21:17:21:21 | block | +| parameters.rb:25:15:25:18 | name | +| parameters.rb:25:33:25:36 | size | +| parameters.rb:30:15:30:19 | first | +| parameters.rb:30:24:30:29 | middle | +| parameters.rb:30:36:30:39 | last | +| parameters.rb:34:1:34:1 | b | +| parameters.rb:35:11:35:11 | a | +| parameters.rb:35:16:35:16 | b | +| parameters.rb:40:12:40:12 | d | +| parameters.rb:40:15:40:15 | e | +| parameters.rb:45:20:45:20 | _ | +| parameters.rb:49:13:49:13 | a | +| parameters.rb:49:15:49:15 | b | +| parameters.rb:53:1:53:1 | x | +| parameters.rb:54:14:54:14 | y | +| scopes.rb:2:14:2:14 | x | +| scopes.rb:4:4:4:4 | a | +| scopes.rb:7:1:7:1 | a | +| scopes.rb:9:14:9:14 | x | +| scopes.rb:13:4:13:32 | __synth__0 | +| scopes.rb:13:7:13:7 | b | +| scopes.rb:13:10:13:15 | __synth__0__1 | +| scopes.rb:13:11:13:11 | c | +| scopes.rb:13:14:13:14 | d | +| scopes.rb:24:1:24:6 | script | +| scopes.rb:27:1:27:1 | x | +| scopes.rb:29:3:29:3 | x | +| scopes.rb:32:3:32:3 | x | +| scopes.rb:35:3:35:3 | x | +| scopes.rb:38:3:38:3 | x | +| scopes.rb:42:2:42:4 | var | +| scopes.rb:43:2:43:4 | foo | +| scopes.rb:46:5:46:8 | var2 | +| ssa.rb:1:7:1:7 | b | +| ssa.rb:2:3:2:3 | i | +| ssa.rb:18:8:18:8 | x | +| ssa.rb:25:8:25:15 | elements | +| ssa.rb:26:7:26:10 | elem | +| ssa.rb:33:20:33:20 | x | +| ssa.rb:40:3:40:4 | m3 | +| ssa.rb:44:8:44:8 | b | +| ssa.rb:45:3:45:3 | x | +| ssa.rb:49:9:49:9 | x | +| ssa.rb:49:14:49:14 | y | +| ssa.rb:53:8:53:10 | foo | +| ssa.rb:54:3:54:3 | x | +| ssa.rb:59:3:59:3 | x | +| ssa.rb:64:8:64:8 | a | +| ssa.rb:65:3:65:10 | captured | +| ssa.rb:66:15:66:15 | a | +| ssa.rb:75:3:75:10 | captured | +| ssa.rb:82:3:82:10 | captured | diff --git a/ruby/ql/test/library-tests/variables/variable.ql b/ruby/ql/test/library-tests/variables/variable.ql new file mode 100644 index 000000000000..7a87b119bece --- /dev/null +++ b/ruby/ql/test/library-tests/variables/variable.ql @@ -0,0 +1,3 @@ +import codeql.ruby.ast.Variable + +query predicate variable(Variable v) { any() } diff --git a/ruby/ql/test/library-tests/variables/varscopes.expected b/ruby/ql/test/library-tests/variables/varscopes.expected new file mode 100644 index 000000000000..090243e57a80 --- /dev/null +++ b/ruby/ql/test/library-tests/variables/varscopes.expected @@ -0,0 +1,75 @@ +| class_variables.rb:1:1:29:4 | class_variables.rb | +| class_variables.rb:5:1:7:3 | print | +| class_variables.rb:9:1:16:3 | X | +| class_variables.rb:10:3:12:5 | b | +| class_variables.rb:13:3:15:5 | s | +| class_variables.rb:18:1:20:3 | Y | +| class_variables.rb:22:1:24:3 | M | +| class_variables.rb:26:1:29:3 | N | +| instance_variables.rb:1:1:44:4 | instance_variables.rb | +| instance_variables.rb:3:1:5:3 | foo | +| instance_variables.rb:7:1:9:3 | print_foo | +| instance_variables.rb:13:1:18:3 | X | +| instance_variables.rb:15:3:17:5 | m | +| instance_variables.rb:20:1:25:3 | M | +| instance_variables.rb:22:2:24:4 | n | +| instance_variables.rb:27:6:29:1 | { ... } | +| instance_variables.rb:31:1:33:3 | bar | +| instance_variables.rb:32:10:32:21 | { ... } | +| instance_variables.rb:35:1:44:4 | C | +| instance_variables.rb:37:3:43:5 | x | +| instance_variables.rb:38:4:40:6 | y | +| nested_scopes.rb:1:1:3:3 | a | +| nested_scopes.rb:1:1:42:1 | nested_scopes.rb | +| nested_scopes.rb:4:1:39:3 | C | +| nested_scopes.rb:6:3:37:5 | M | +| nested_scopes.rb:8:5:35:7 | N | +| nested_scopes.rb:10:7:26:9 | D | +| nested_scopes.rb:12:9:21:11 | show_a | +| nested_scopes.rb:15:19:20:13 | do ... end | +| nested_scopes.rb:16:21:19:15 | do ... end | +| nested_scopes.rb:18:23:18:36 | { ... } | +| nested_scopes.rb:22:9:24:11 | show_a2 | +| nested_scopes.rb:27:7:29:9 | show | +| nested_scopes.rb:30:7:33:9 | class << ... | +| parameters.rb:1:1:58:1 | parameters.rb | +| parameters.rb:1:9:5:3 | do ... end | +| parameters.rb:7:1:13:3 | order_pizza | +| parameters.rb:15:1:19:3 | print_map | +| parameters.rb:16:12:18:5 | do ... end | +| parameters.rb:21:1:23:3 | call_block | +| parameters.rb:25:1:28:3 | opt_param | +| parameters.rb:30:1:32:3 | key_param | +| parameters.rb:35:1:38:3 | multi | +| parameters.rb:40:1:43:3 | multi2 | +| parameters.rb:45:1:47:3 | dup_underscore | +| parameters.rb:49:1:51:3 | tuples | +| parameters.rb:54:9:57:3 | do ... end | +| scopes.rb:1:1:1:15 | a | +| scopes.rb:1:1:49:4 | scopes.rb | +| scopes.rb:2:9:6:3 | do ... end | +| scopes.rb:9:9:18:3 | do ... end | +| scopes.rb:26:1:26:12 | A | +| scopes.rb:28:1:30:3 | B | +| scopes.rb:31:1:33:3 | class << ... | +| scopes.rb:34:1:36:3 | C | +| scopes.rb:37:1:39:3 | foo | +| scopes.rb:41:1:49:3 | M | +| ssa.rb:1:1:16:3 | m | +| ssa.rb:1:1:88:3 | ssa.rb | +| ssa.rb:18:1:23:3 | m1 | +| ssa.rb:25:1:30:3 | m2 | +| ssa.rb:32:1:36:3 | m3 | +| ssa.rb:33:16:35:5 | do ... end | +| ssa.rb:38:1:42:3 | m4 | +| ssa.rb:44:1:47:3 | m5 | +| ssa.rb:49:1:51:3 | m6 | +| ssa.rb:53:1:56:3 | m7 | +| ssa.rb:58:1:62:3 | m8 | +| ssa.rb:64:1:72:3 | m9 | +| ssa.rb:66:11:70:5 | do ... end | +| ssa.rb:74:1:79:3 | m10 | +| ssa.rb:76:7:78:5 | do ... end | +| ssa.rb:81:1:88:3 | m11 | +| ssa.rb:83:7:87:5 | do ... end | +| ssa.rb:84:10:86:8 | do ... end | diff --git a/ruby/ql/test/library-tests/variables/varscopes.ql b/ruby/ql/test/library-tests/variables/varscopes.ql new file mode 100644 index 000000000000..2f3aa1b2e6c7 --- /dev/null +++ b/ruby/ql/test/library-tests/variables/varscopes.ql @@ -0,0 +1,3 @@ +import codeql.ruby.ast.Scope + +select any(Scope x) diff --git a/ruby/ql/test/qlpack.lock.yml b/ruby/ql/test/qlpack.lock.yml new file mode 100644 index 000000000000..0bef0f691a9d --- /dev/null +++ b/ruby/ql/test/qlpack.lock.yml @@ -0,0 +1,6 @@ +--- +dependencies: + codeql/suite-helpers: + version: 0.0.2 +compiled: false +lockVersion: 1.0.0 diff --git a/ruby/ql/test/qlpack.yml b/ruby/ql/test/qlpack.yml new file mode 100644 index 000000000000..91111a5ffa33 --- /dev/null +++ b/ruby/ql/test/qlpack.yml @@ -0,0 +1,8 @@ +name: codeql/ruby-tests +version: 0.0.2 +dependencies: + codeql/ruby-queries: ^0.0.2 + codeql/ruby-examples: ^0.0.2 + codeql/ruby-all: ^0.0.2 +extractor: ruby +tests: . diff --git a/ruby/ql/test/query-tests/AlertSuppression/.gitattributes b/ruby/ql/test/query-tests/AlertSuppression/.gitattributes new file mode 100644 index 000000000000..7ed66a396cfd --- /dev/null +++ b/ruby/ql/test/query-tests/AlertSuppression/.gitattributes @@ -0,0 +1 @@ +TestWindows.java eol=crlf diff --git a/ruby/ql/test/query-tests/AlertSuppression/AlertSuppression.expected b/ruby/ql/test/query-tests/AlertSuppression/AlertSuppression.expected new file mode 100644 index 000000000000..840b13f61bce --- /dev/null +++ b/ruby/ql/test/query-tests/AlertSuppression/AlertSuppression.expected @@ -0,0 +1,48 @@ +| Test.rb:1:16:1:21 | # lgtm | lgtm | lgtm | Test.rb:1:1:1:21 | suppression range | +| Test.rb:2:1:2:32 | # lgtm[rb/confusing-method-name] | lgtm[rb/confusing-method-name] | lgtm[rb/confusing-method-name] | Test.rb:2:1:2:32 | suppression range | +| Test.rb:3:1:3:65 | # lgtm[rb/confusing-method-name, rb/non-short-circuit-evaluation] | lgtm[rb/confusing-method-name, rb/non-short-circuit-evaluation] | lgtm[rb/confusing-method-name, rb/non-short-circuit-evaluation] | Test.rb:3:1:3:65 | suppression range | +| Test.rb:4:1:4:23 | # lgtm[@tag:exceptions] | lgtm[@tag:exceptions] | lgtm[@tag:exceptions] | Test.rb:4:1:4:23 | suppression range | +| Test.rb:5:1:5:48 | # lgtm[@tag:exceptions,rb/confusing-method-name] | lgtm[@tag:exceptions,rb/confusing-method-name] | lgtm[@tag:exceptions,rb/confusing-method-name] | Test.rb:5:1:5:48 | suppression range | +| Test.rb:6:1:6:27 | # lgtm[@expires:2017-06-11] | lgtm[@expires:2017-06-11] | lgtm[@expires:2017-06-11] | Test.rb:6:1:6:27 | suppression range | +| Test.rb:7:1:7:78 | # lgtm[rb/confusing-method-name] does not seem confusing despite alert by lgtm | lgtm[rb/confusing-method-name] does not seem confusing despite alert by lgtm | lgtm[rb/confusing-method-name] | Test.rb:7:1:7:78 | suppression range | +| Test.rb:8:1:8:17 | # lgtm: blah blah | lgtm: blah blah | lgtm | Test.rb:8:1:8:17 | suppression range | +| Test.rb:9:1:9:31 | # lgtm blah blah #falsepositive | lgtm blah blah #falsepositive | lgtm | Test.rb:9:1:9:31 | suppression range | +| Test.rb:10:1:10:33 | #lgtm [rb/confusing-method-name] | lgtm [rb/confusing-method-name] | lgtm [rb/confusing-method-name] | Test.rb:10:1:10:33 | suppression range | +| Test.rb:11:1:11:8 | # lgtm[] | lgtm[] | lgtm[] | Test.rb:11:1:11:8 | suppression range | +| Test.rb:13:1:13:5 | #lgtm | lgtm | lgtm | Test.rb:13:1:13:5 | suppression range | +| Test.rb:14:1:14:6 | #\tlgtm | \tlgtm | lgtm | Test.rb:14:1:14:6 | suppression range | +| Test.rb:15:1:15:33 | # lgtm\t[rb/confusing-method-name] | lgtm\t[rb/confusing-method-name] | lgtm\t[rb/confusing-method-name] | Test.rb:15:1:15:33 | suppression range | +| Test.rb:18:1:18:11 | # foo; lgtm | foo; lgtm | lgtm | Test.rb:18:1:18:11 | suppression range | +| Test.rb:19:1:19:37 | # foo; lgtm[rb/confusing-method-name] | foo; lgtm[rb/confusing-method-name] | lgtm[rb/confusing-method-name] | Test.rb:19:1:19:37 | suppression range | +| Test.rb:21:1:21:36 | # foo lgtm[rb/confusing-method-name] | foo lgtm[rb/confusing-method-name] | lgtm[rb/confusing-method-name] | Test.rb:21:1:21:36 | suppression range | +| Test.rb:23:1:23:40 | # foo lgtm[rb/confusing-method-name] bar | foo lgtm[rb/confusing-method-name] bar | lgtm[rb/confusing-method-name] | Test.rb:23:1:23:40 | suppression range | +| Test.rb:24:1:24:7 | # LGTM! | LGTM! | LGTM | Test.rb:24:1:24:7 | suppression range | +| Test.rb:25:1:25:32 | # LGTM[rb/confusing-method-name] | LGTM[rb/confusing-method-name] | LGTM[rb/confusing-method-name] | Test.rb:25:1:25:32 | suppression range | +| Test.rb:26:1:26:73 | #lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation] | lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation] | lgtm[rb/confusing-method-name] | Test.rb:26:1:26:73 | suppression range | +| Test.rb:26:1:26:73 | #lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation] | lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation] | lgtm[rb/non-short-circuit-evaluation] | Test.rb:26:1:26:73 | suppression range | +| Test.rb:27:1:27:37 | #lgtm[rb/confusing-method-name]; lgtm | lgtm[rb/confusing-method-name]; lgtm | lgtm | Test.rb:27:1:27:37 | suppression range | +| Test.rb:27:1:27:37 | #lgtm[rb/confusing-method-name]; lgtm | lgtm[rb/confusing-method-name]; lgtm | lgtm[rb/confusing-method-name] | Test.rb:27:1:27:37 | suppression range | +| TestWindows.rb:1:23:1:29 | # lgtm\r | lgtm\r | lgtm | TestWindows.rb:1:1:1:29 | suppression range | +| TestWindows.rb:2:1:2:33 | # lgtm[rb/confusing-method-name]\r | lgtm[rb/confusing-method-name]\r | lgtm[rb/confusing-method-name] | TestWindows.rb:2:1:2:33 | suppression range | +| TestWindows.rb:3:1:3:66 | # lgtm[rb/confusing-method-name, rb/non-short-circuit-evaluation]\r | lgtm[rb/confusing-method-name, rb/non-short-circuit-evaluation]\r | lgtm[rb/confusing-method-name, rb/non-short-circuit-evaluation] | TestWindows.rb:3:1:3:66 | suppression range | +| TestWindows.rb:4:1:4:24 | # lgtm[@tag:exceptions]\r | lgtm[@tag:exceptions]\r | lgtm[@tag:exceptions] | TestWindows.rb:4:1:4:24 | suppression range | +| TestWindows.rb:5:1:5:49 | # lgtm[@tag:exceptions,rb/confusing-method-name]\r | lgtm[@tag:exceptions,rb/confusing-method-name]\r | lgtm[@tag:exceptions,rb/confusing-method-name] | TestWindows.rb:5:1:5:49 | suppression range | +| TestWindows.rb:6:1:6:28 | # lgtm[@expires:2017-06-11]\r | lgtm[@expires:2017-06-11]\r | lgtm[@expires:2017-06-11] | TestWindows.rb:6:1:6:28 | suppression range | +| TestWindows.rb:7:1:7:79 | # lgtm[rb/confusing-method-name] does not seem confusing despite alert by lgtm\r | lgtm[rb/confusing-method-name] does not seem confusing despite alert by lgtm\r | lgtm[rb/confusing-method-name] | TestWindows.rb:7:1:7:79 | suppression range | +| TestWindows.rb:8:1:8:18 | # lgtm: blah blah\r | lgtm: blah blah\r | lgtm | TestWindows.rb:8:1:8:18 | suppression range | +| TestWindows.rb:9:1:9:32 | # lgtm blah blah #falsepositive\r | lgtm blah blah #falsepositive\r | lgtm | TestWindows.rb:9:1:9:32 | suppression range | +| TestWindows.rb:10:1:10:34 | #lgtm [rb/confusing-method-name]\r | lgtm [rb/confusing-method-name]\r | lgtm [rb/confusing-method-name] | TestWindows.rb:10:1:10:34 | suppression range | +| TestWindows.rb:11:1:11:9 | # lgtm[]\r | lgtm[]\r | lgtm[] | TestWindows.rb:11:1:11:9 | suppression range | +| TestWindows.rb:13:1:13:6 | #lgtm\r | lgtm\r | lgtm | TestWindows.rb:13:1:13:6 | suppression range | +| TestWindows.rb:14:1:14:7 | #\tlgtm\r | \tlgtm\r | lgtm | TestWindows.rb:14:1:14:7 | suppression range | +| TestWindows.rb:15:1:15:34 | # lgtm\t[rb/confusing-method-name]\r | lgtm\t[rb/confusing-method-name]\r | lgtm\t[rb/confusing-method-name] | TestWindows.rb:15:1:15:34 | suppression range | +| TestWindows.rb:18:1:18:12 | # foo; lgtm\r | foo; lgtm\r | lgtm | TestWindows.rb:18:1:18:12 | suppression range | +| TestWindows.rb:19:1:19:38 | # foo; lgtm[rb/confusing-method-name]\r | foo; lgtm[rb/confusing-method-name]\r | lgtm[rb/confusing-method-name] | TestWindows.rb:19:1:19:38 | suppression range | +| TestWindows.rb:21:1:21:37 | # foo lgtm[rb/confusing-method-name]\r | foo lgtm[rb/confusing-method-name]\r | lgtm[rb/confusing-method-name] | TestWindows.rb:21:1:21:37 | suppression range | +| TestWindows.rb:23:1:23:41 | # foo lgtm[rb/confusing-method-name] bar\r | foo lgtm[rb/confusing-method-name] bar\r | lgtm[rb/confusing-method-name] | TestWindows.rb:23:1:23:41 | suppression range | +| TestWindows.rb:24:1:24:8 | # LGTM!\r | LGTM!\r | LGTM | TestWindows.rb:24:1:24:8 | suppression range | +| TestWindows.rb:25:1:25:33 | # LGTM[rb/confusing-method-name]\r | LGTM[rb/confusing-method-name]\r | LGTM[rb/confusing-method-name] | TestWindows.rb:25:1:25:33 | suppression range | +| TestWindows.rb:26:1:26:74 | #lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation]\r | lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation]\r | lgtm[rb/confusing-method-name] | TestWindows.rb:26:1:26:74 | suppression range | +| TestWindows.rb:26:1:26:74 | #lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation]\r | lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation]\r | lgtm[rb/non-short-circuit-evaluation] | TestWindows.rb:26:1:26:74 | suppression range | +| TestWindows.rb:27:1:27:38 | #lgtm[rb/confusing-method-name]; lgtm\r | lgtm[rb/confusing-method-name]; lgtm\r | lgtm | TestWindows.rb:27:1:27:38 | suppression range | +| TestWindows.rb:27:1:27:38 | #lgtm[rb/confusing-method-name]; lgtm\r | lgtm[rb/confusing-method-name]; lgtm\r | lgtm[rb/confusing-method-name] | TestWindows.rb:27:1:27:38 | suppression range | diff --git a/ruby/ql/test/query-tests/AlertSuppression/AlertSuppression.qlref b/ruby/ql/test/query-tests/AlertSuppression/AlertSuppression.qlref new file mode 100644 index 000000000000..9d7833eccae7 --- /dev/null +++ b/ruby/ql/test/query-tests/AlertSuppression/AlertSuppression.qlref @@ -0,0 +1 @@ +AlertSuppression.ql diff --git a/ruby/ql/test/query-tests/AlertSuppression/Test.rb b/ruby/ql/test/query-tests/AlertSuppression/Test.rb new file mode 100644 index 000000000000..028d5b87aa73 --- /dev/null +++ b/ruby/ql/test/query-tests/AlertSuppression/Test.rb @@ -0,0 +1,28 @@ +class Test end # lgtm +# lgtm[rb/confusing-method-name] +# lgtm[rb/confusing-method-name, rb/non-short-circuit-evaluation] +# lgtm[@tag:exceptions] +# lgtm[@tag:exceptions,rb/confusing-method-name] +# lgtm[@expires:2017-06-11] +# lgtm[rb/confusing-method-name] does not seem confusing despite alert by lgtm +# lgtm: blah blah +# lgtm blah blah #falsepositive +#lgtm [rb/confusing-method-name] +# lgtm[] +# lgtmfoo +#lgtm +# lgtm +# lgtm [rb/confusing-method-name] +# foolgtm[rb/confusing-method-name] +# foolgtm +# foo; lgtm +# foo; lgtm[rb/confusing-method-name] +# foo lgtm +# foo lgtm[rb/confusing-method-name] +# foo lgtm bar +# foo lgtm[rb/confusing-method-name] bar +# LGTM! +# LGTM[rb/confusing-method-name] +#lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation] +#lgtm[rb/confusing-method-name]; lgtm + diff --git a/ruby/ql/test/query-tests/AlertSuppression/TestWindows.rb b/ruby/ql/test/query-tests/AlertSuppression/TestWindows.rb new file mode 100644 index 000000000000..1e69a66ee117 --- /dev/null +++ b/ruby/ql/test/query-tests/AlertSuppression/TestWindows.rb @@ -0,0 +1,28 @@ +class TestWindows end # lgtm +# lgtm[rb/confusing-method-name] +# lgtm[rb/confusing-method-name, rb/non-short-circuit-evaluation] +# lgtm[@tag:exceptions] +# lgtm[@tag:exceptions,rb/confusing-method-name] +# lgtm[@expires:2017-06-11] +# lgtm[rb/confusing-method-name] does not seem confusing despite alert by lgtm +# lgtm: blah blah +# lgtm blah blah #falsepositive +#lgtm [rb/confusing-method-name] +# lgtm[] +# lgtmfoo +#lgtm +# lgtm +# lgtm [rb/confusing-method-name] +# foolgtm[rb/confusing-method-name] +# foolgtm +# foo; lgtm +# foo; lgtm[rb/confusing-method-name] +# foo lgtm +# foo lgtm[rb/confusing-method-name] +# foo lgtm bar +# foo lgtm[rb/confusing-method-name] bar +# LGTM! +# LGTM[rb/confusing-method-name] +#lgtm[rb/confusing-method-name] and lgtm[rb/non-short-circuit-evaluation] +#lgtm[rb/confusing-method-name]; lgtm + diff --git a/ruby/ql/test/query-tests/analysis/Definitions.expected b/ruby/ql/test/query-tests/analysis/Definitions.expected new file mode 100644 index 000000000000..47f0b049d08c --- /dev/null +++ b/ruby/ql/test/query-tests/analysis/Definitions.expected @@ -0,0 +1,14 @@ +| Definitions.rb:6:7:6:21 | call to g | Definitions.rb:9:5:11:7 | g | method | +| Definitions.rb:6:9:6:21 | SOME_CONSTANT | Definitions.rb:2:3:2:15 | SOME_CONSTANT | constant | +| Definitions.rb:10:7:10:7 | x | Definitions.rb:9:11:9:11 | x | variable | +| Definitions.rb:14:7:14:7 | call to f | Definitions.rb:5:5:7:7 | f | method | +| Definitions.rb:23:5:23:7 | @@a | Definitions.rb:20:3:20:5 | @@a | class variable | +| Definitions.rb:32:7:32:7 | y | Definitions.rb:31:10:31:10 | y | variable | +| Definitions.rb:36:7:36:7 | A | Definitions.rb:1:1:17:3 | A | constant | +| Definitions.rb:36:7:36:10 | B | Definitions.rb:4:3:16:5 | B | constant | +| Definitions.rb:36:7:36:18 | call to g | Definitions.rb:9:5:11:7 | g | method | +| Definitions.rb:36:18:36:18 | y | Definitions.rb:35:11:35:11 | y | variable | +| Definitions.rb:39:7:39:8 | @e | Definitions.rb:30:7:30:8 | @e | instance variable | +| Definitions.rb:41:7:41:9 | @@b | Definitions.rb:27:5:27:7 | @@b | class variable | +| Definitions.rb:46:1:46:1 | C | Definitions.rb:19:1:44:3 | C | constant | +| Definitions.rb:46:1:46:4 | D | Definitions.rb:26:3:43:5 | D | constant | diff --git a/ruby/ql/test/query-tests/analysis/Definitions.qlref b/ruby/ql/test/query-tests/analysis/Definitions.qlref new file mode 100644 index 000000000000..a8620aaeec6b --- /dev/null +++ b/ruby/ql/test/query-tests/analysis/Definitions.qlref @@ -0,0 +1 @@ +queries/analysis/Definitions.ql diff --git a/ruby/ql/test/query-tests/analysis/Definitions.rb b/ruby/ql/test/query-tests/analysis/Definitions.rb new file mode 100644 index 000000000000..90b59be5acfe --- /dev/null +++ b/ruby/ql/test/query-tests/analysis/Definitions.rb @@ -0,0 +1,46 @@ +module A + SOME_CONSTANT = 1 + + class B + def f + g SOME_CONSTANT + end + + def g x + x + end + + def h + f + end + end +end + +module C + @@a = 1 + + def self.a + @@a + end + + class D + @@b = 2 + + def initialize + @e = 1 + x, y = [1, 2] + y + end + + def h y + A::B.new.g y + UnknownClass.some_method + @f = 2 + @e + @f + @@b + end + end +end + +C::D.new diff --git a/ruby/ql/test/query-tests/diagnostics/ExtractionErrors.expected b/ruby/ql/test/query-tests/diagnostics/ExtractionErrors.expected new file mode 100644 index 000000000000..22431857197d --- /dev/null +++ b/ruby/ql/test/query-tests/diagnostics/ExtractionErrors.expected @@ -0,0 +1,5 @@ +| src/not_ruby.rb:5:25:5:26 | parse error | Extraction failed in src/not_ruby.rb with error parse error | 2 | +| src/unsupported_feature.rb:2:18:2:20 | parse error | Extraction failed in src/unsupported_feature.rb with error parse error | 2 | +| src/unsupported_feature.rb:3:13:3:15 | parse error | Extraction failed in src/unsupported_feature.rb with error parse error | 2 | +| src/unsupported_feature.rb:6:15:6:17 | parse error | Extraction failed in src/unsupported_feature.rb with error parse error | 2 | +| src/unsupported_feature.rb:7:20:7:22 | parse error | Extraction failed in src/unsupported_feature.rb with error parse error | 2 | diff --git a/ruby/ql/test/query-tests/diagnostics/ExtractionErrors.qlref b/ruby/ql/test/query-tests/diagnostics/ExtractionErrors.qlref new file mode 100644 index 000000000000..ffbdb0a7b1b5 --- /dev/null +++ b/ruby/ql/test/query-tests/diagnostics/ExtractionErrors.qlref @@ -0,0 +1 @@ +queries/diagnostics/ExtractionErrors.ql \ No newline at end of file diff --git a/ruby/ql/test/query-tests/diagnostics/NumberOfFilesExtractedWithErrors.expected b/ruby/ql/test/query-tests/diagnostics/NumberOfFilesExtractedWithErrors.expected new file mode 100644 index 000000000000..a882f18620ba --- /dev/null +++ b/ruby/ql/test/query-tests/diagnostics/NumberOfFilesExtractedWithErrors.expected @@ -0,0 +1 @@ +| 2 | diff --git a/ruby/ql/test/query-tests/diagnostics/NumberOfFilesExtractedWithErrors.qlref b/ruby/ql/test/query-tests/diagnostics/NumberOfFilesExtractedWithErrors.qlref new file mode 100644 index 000000000000..17823cc88374 --- /dev/null +++ b/ruby/ql/test/query-tests/diagnostics/NumberOfFilesExtractedWithErrors.qlref @@ -0,0 +1 @@ +queries/summary/NumberOfFilesExtractedWithErrors.ql \ No newline at end of file diff --git a/ruby/ql/test/query-tests/diagnostics/NumberOfSuccessfullyExtractedFiles.expected b/ruby/ql/test/query-tests/diagnostics/NumberOfSuccessfullyExtractedFiles.expected new file mode 100644 index 000000000000..7621cebdd5fb --- /dev/null +++ b/ruby/ql/test/query-tests/diagnostics/NumberOfSuccessfullyExtractedFiles.expected @@ -0,0 +1 @@ +| 3 | diff --git a/ruby/ql/test/query-tests/diagnostics/NumberOfSuccessfullyExtractedFiles.qlref b/ruby/ql/test/query-tests/diagnostics/NumberOfSuccessfullyExtractedFiles.qlref new file mode 100644 index 000000000000..5f6eda05206f --- /dev/null +++ b/ruby/ql/test/query-tests/diagnostics/NumberOfSuccessfullyExtractedFiles.qlref @@ -0,0 +1 @@ +queries/summary/NumberOfSuccessfullyExtractedFiles.ql \ No newline at end of file diff --git a/ruby/ql/test/query-tests/diagnostics/SuccessfullyExtractedFiles.expected b/ruby/ql/test/query-tests/diagnostics/SuccessfullyExtractedFiles.expected new file mode 100644 index 000000000000..ccb442a930a7 --- /dev/null +++ b/ruby/ql/test/query-tests/diagnostics/SuccessfullyExtractedFiles.expected @@ -0,0 +1,3 @@ +| src/bar.erb:0:0:0:0 | src/bar.erb | | +| src/foo.rb:0:0:0:0 | src/foo.rb | | +| src/vendor/cache/lib.rb:0:0:0:0 | src/vendor/cache/lib.rb | | diff --git a/ruby/ql/test/query-tests/diagnostics/SuccessfullyExtractedFiles.qlref b/ruby/ql/test/query-tests/diagnostics/SuccessfullyExtractedFiles.qlref new file mode 100644 index 000000000000..6dc0952ba3a0 --- /dev/null +++ b/ruby/ql/test/query-tests/diagnostics/SuccessfullyExtractedFiles.qlref @@ -0,0 +1 @@ +queries/diagnostics/SuccessfullyExtractedFiles.ql \ No newline at end of file diff --git a/ruby/ql/test/query-tests/diagnostics/src/bar.erb b/ruby/ql/test/query-tests/diagnostics/src/bar.erb new file mode 100644 index 000000000000..5bd37fdef28f --- /dev/null +++ b/ruby/ql/test/query-tests/diagnostics/src/bar.erb @@ -0,0 +1,2 @@ +<%# comment -%> +blah diff --git a/ruby/ql/test/query-tests/diagnostics/src/foo.rb b/ruby/ql/test/query-tests/diagnostics/src/foo.rb new file mode 100644 index 000000000000..7b831d230edb --- /dev/null +++ b/ruby/ql/test/query-tests/diagnostics/src/foo.rb @@ -0,0 +1,9 @@ +# comment + +def hello + p "hello world" +end + +# another one + +hello diff --git a/ruby/ql/test/query-tests/diagnostics/src/not_ruby.rb b/ruby/ql/test/query-tests/diagnostics/src/not_ruby.rb new file mode 100755 index 000000000000..51128f4f7fe3 --- /dev/null +++ b/ruby/ql/test/query-tests/diagnostics/src/not_ruby.rb @@ -0,0 +1,5 @@ +#!/bin/bash + +# This is a bash script +export FOO="$(whereis ls)" +exec "$FOO" "$(dirname "$0")" diff --git a/ruby/ql/test/query-tests/diagnostics/src/unsupported_feature.rb b/ruby/ql/test/query-tests/diagnostics/src/unsupported_feature.rb new file mode 100644 index 000000000000..c5a39a3619d9 --- /dev/null +++ b/ruby/ql/test/query-tests/diagnostics/src/unsupported_feature.rb @@ -0,0 +1,17 @@ +class Foo + def initialize(...) + do_init(...) + end + + def do_init(...) + really_do_init(...) + end + + def really_do_init(bar, baz:, &block) + puts bar + puts baz + block.call + end +end + +Foo.new("hello", baz: "world") { || puts "!" } diff --git a/ruby/ql/test/query-tests/diagnostics/src/vendor/cache/lib.rb b/ruby/ql/test/query-tests/diagnostics/src/vendor/cache/lib.rb new file mode 100644 index 000000000000..f75db7941dba --- /dev/null +++ b/ruby/ql/test/query-tests/diagnostics/src/vendor/cache/lib.rb @@ -0,0 +1,9 @@ +# comment + +def hello + p "hello lib" +end + +# another one + +hello diff --git a/ruby/ql/test/query-tests/metrics/FLines/Empty.rb b/ruby/ql/test/query-tests/metrics/FLines/Empty.rb new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/ruby/ql/test/query-tests/metrics/FLines/FLines.expected b/ruby/ql/test/query-tests/metrics/FLines/FLines.expected new file mode 100644 index 000000000000..e988ecc8ff01 --- /dev/null +++ b/ruby/ql/test/query-tests/metrics/FLines/FLines.expected @@ -0,0 +1,2 @@ +| FLines.rb:0:0:0:0 | FLines.rb | 34 | +| Empty.rb:0:0:0:0 | Empty.rb | 0 | diff --git a/ruby/ql/test/query-tests/metrics/FLines/FLines.qlref b/ruby/ql/test/query-tests/metrics/FLines/FLines.qlref new file mode 100644 index 000000000000..315464375988 --- /dev/null +++ b/ruby/ql/test/query-tests/metrics/FLines/FLines.qlref @@ -0,0 +1 @@ +queries/metrics/FLines.ql \ No newline at end of file diff --git a/ruby/ql/test/query-tests/metrics/FLines/FLines.rb b/ruby/ql/test/query-tests/metrics/FLines/FLines.rb new file mode 100644 index 000000000000..5383f87951b4 --- /dev/null +++ b/ruby/ql/test/query-tests/metrics/FLines/FLines.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +=begin +some preprocessing here +and here +=end + +class FLinesTest + + def foo(bar) + # This is a comment + # and another + + some_string = <<-ESCAPE +hello world +multiple +lines + +how many lines of code in this heredoc? +# 9 lines total + +ESCAPE + + some_other_string = "line 1 + line" + bar + + p some_string + p some_other_string + + some_string + some_other_string + end + + +end diff --git a/ruby/ql/test/query-tests/metrics/FLines/FLinesOfCode.expected b/ruby/ql/test/query-tests/metrics/FLines/FLinesOfCode.expected new file mode 100644 index 000000000000..b55005190915 --- /dev/null +++ b/ruby/ql/test/query-tests/metrics/FLines/FLinesOfCode.expected @@ -0,0 +1,2 @@ +| FLines.rb:0:0:0:0 | FLines.rb | 18 | +| Empty.rb:0:0:0:0 | Empty.rb | 0 | diff --git a/ruby/ql/test/query-tests/metrics/FLines/FLinesOfCode.qlref b/ruby/ql/test/query-tests/metrics/FLines/FLinesOfCode.qlref new file mode 100644 index 000000000000..cb4dd2b8767c --- /dev/null +++ b/ruby/ql/test/query-tests/metrics/FLines/FLinesOfCode.qlref @@ -0,0 +1 @@ +queries/metrics/FLinesOfCode.ql \ No newline at end of file diff --git a/ruby/ql/test/query-tests/metrics/FLines/FLinesOfComments.expected b/ruby/ql/test/query-tests/metrics/FLines/FLinesOfComments.expected new file mode 100644 index 000000000000..cf90251a4f5c --- /dev/null +++ b/ruby/ql/test/query-tests/metrics/FLines/FLinesOfComments.expected @@ -0,0 +1,2 @@ +| FLines.rb:0:0:0:0 | FLines.rb | 7 | +| Empty.rb:0:0:0:0 | Empty.rb | 0 | diff --git a/ruby/ql/test/query-tests/metrics/FLines/FLinesOfComments.qlref b/ruby/ql/test/query-tests/metrics/FLines/FLinesOfComments.qlref new file mode 100644 index 000000000000..766ae4bcc590 --- /dev/null +++ b/ruby/ql/test/query-tests/metrics/FLines/FLinesOfComments.qlref @@ -0,0 +1 @@ +queries/metrics/FLinesOfComments.ql \ No newline at end of file diff --git a/ruby/ql/test/query-tests/performance/UseDetect/UseDetect.expected b/ruby/ql/test/query-tests/performance/UseDetect/UseDetect.expected new file mode 100644 index 000000000000..d8a5ec8dd41d --- /dev/null +++ b/ruby/ql/test/query-tests/performance/UseDetect/UseDetect.expected @@ -0,0 +1,7 @@ +| UseDetect.rb:5:9:5:36 | call to first | Replace this call and $@ with 'detect'. | UseDetect.rb:5:9:5:30 | call to select | 'select' call | +| UseDetect.rb:6:9:6:35 | call to last | Replace this call and $@ with 'reverse_detect'. | UseDetect.rb:6:9:6:30 | call to select | 'select' call | +| UseDetect.rb:7:9:7:33 | ...[...] | Replace this call and $@ with 'detect'. | UseDetect.rb:7:9:7:30 | call to select | 'select' call | +| UseDetect.rb:8:9:8:34 | ...[...] | Replace this call and $@ with 'reverse_detect'. | UseDetect.rb:8:9:8:30 | call to select | 'select' call | +| UseDetect.rb:9:9:9:36 | call to first | Replace this call and $@ with 'detect'. | UseDetect.rb:9:9:9:30 | call to filter | 'select' call | +| UseDetect.rb:10:9:10:37 | call to last | Replace this call and $@ with 'reverse_detect'. | UseDetect.rb:10:9:10:32 | call to find_all | 'select' call | +| UseDetect.rb:12:9:12:24 | call to first | Replace this call and $@ with 'detect'. | UseDetect.rb:11:22:11:43 | call to select | 'select' call | diff --git a/ruby/ql/test/query-tests/performance/UseDetect/UseDetect.qlref b/ruby/ql/test/query-tests/performance/UseDetect/UseDetect.qlref new file mode 100644 index 000000000000..f2a94b28c407 --- /dev/null +++ b/ruby/ql/test/query-tests/performance/UseDetect/UseDetect.qlref @@ -0,0 +1 @@ +experimental/performance/UseDetect.ql diff --git a/ruby/ql/test/query-tests/performance/UseDetect/UseDetect.rb b/ruby/ql/test/query-tests/performance/UseDetect/UseDetect.rb new file mode 100644 index 000000000000..e1d2d9b91ba0 --- /dev/null +++ b/ruby/ql/test/query-tests/performance/UseDetect/UseDetect.rb @@ -0,0 +1,21 @@ + +class DetectTest + def test + # These are bad + [].select { |i| true }.first + [].select { |i| true }.last + [].select { |i| true }[0] + [].select { |i| true }[-1] + [].filter { |i| true }.first + [].find_all { |i| true }.last + selection1 = [].select { |i| true } + selection1.first + + # These are good + [].select("").first # Selecting a string + [].select { |i| true }[1] # Selecting the second element + selection2 = [].select { |i| true } + selection2.first # Selection used elsewhere + selection3 = selection2 + end +end diff --git a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection.expected b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection.expected new file mode 100644 index 000000000000..d3338f6cd564 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection.expected @@ -0,0 +1,33 @@ +edges +| CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:7:10:7:15 | #{...} | +| CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:8:16:8:18 | cmd | +| CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:10:14:10:16 | cmd | +| CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:11:17:11:22 | #{...} | +| CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:13:9:13:14 | #{...} | +| CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:29:19:29:24 | #{...} | +| CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:33:24:33:36 | "echo #{...}" | +| CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:34:39:34:51 | "grep #{...}" | +| CommandInjection.rb:46:15:46:20 | call to params : | CommandInjection.rb:50:24:50:36 | "echo #{...}" | +nodes +| CommandInjection.rb:6:15:6:20 | call to params : | semmle.label | call to params : | +| CommandInjection.rb:7:10:7:15 | #{...} | semmle.label | #{...} | +| CommandInjection.rb:8:16:8:18 | cmd | semmle.label | cmd | +| CommandInjection.rb:10:14:10:16 | cmd | semmle.label | cmd | +| CommandInjection.rb:11:17:11:22 | #{...} | semmle.label | #{...} | +| CommandInjection.rb:13:9:13:14 | #{...} | semmle.label | #{...} | +| CommandInjection.rb:29:19:29:24 | #{...} | semmle.label | #{...} | +| CommandInjection.rb:33:24:33:36 | "echo #{...}" | semmle.label | "echo #{...}" | +| CommandInjection.rb:34:39:34:51 | "grep #{...}" | semmle.label | "grep #{...}" | +| CommandInjection.rb:46:15:46:20 | call to params : | semmle.label | call to params : | +| CommandInjection.rb:50:24:50:36 | "echo #{...}" | semmle.label | "echo #{...}" | +subpaths +#select +| CommandInjection.rb:7:10:7:15 | #{...} | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:7:10:7:15 | #{...} | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value | +| CommandInjection.rb:8:16:8:18 | cmd | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:8:16:8:18 | cmd | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value | +| CommandInjection.rb:10:14:10:16 | cmd | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:10:14:10:16 | cmd | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value | +| CommandInjection.rb:11:17:11:22 | #{...} | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:11:17:11:22 | #{...} | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value | +| CommandInjection.rb:13:9:13:14 | #{...} | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:13:9:13:14 | #{...} | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value | +| CommandInjection.rb:29:19:29:24 | #{...} | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:29:19:29:24 | #{...} | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value | +| CommandInjection.rb:33:24:33:36 | "echo #{...}" | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:33:24:33:36 | "echo #{...}" | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value | +| CommandInjection.rb:34:39:34:51 | "grep #{...}" | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:34:39:34:51 | "grep #{...}" | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value | +| CommandInjection.rb:50:24:50:36 | "echo #{...}" | CommandInjection.rb:46:15:46:20 | call to params : | CommandInjection.rb:50:24:50:36 | "echo #{...}" | This command depends on $@. | CommandInjection.rb:46:15:46:20 | call to params | a user-provided value | diff --git a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection.qlref b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection.qlref new file mode 100644 index 000000000000..b9c6fe1b90a8 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection.qlref @@ -0,0 +1 @@ +queries/security/cwe-078/CommandInjection.ql diff --git a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection.rb b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection.rb new file mode 100644 index 000000000000..f91d36f71bd3 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection.rb @@ -0,0 +1,52 @@ +require "shellwords" +require "open3" + +class UsersController < ActionController::Base + def create + cmd = params[:cmd] + `#{cmd}` + system(cmd) + system("echo", cmd) # OK, because cmd is not shell interpreted + exec(cmd) + %x(echo #{cmd}) + result = <<`EOF` + #{cmd} +EOF + + safe_cmd_1 = Shellwords.escape(cmd) + `echo #{safe_cmd_1}` + + safe_cmd_2 = Shellwords.shellescape(cmd) + `echo #{safe_cmd_2}` + + if cmd == "some constant" + `echo #{cmd}` + end + + if %w(foo bar).include? cmd + `echo #{cmd}` + else + `echo #{cmd}` + end + + # Open3 methods + Open3.capture2("echo #{cmd}") + Open3.pipeline("cat foo.txt", "grep #{cmd}") + Open3.pipeline(["echo", cmd], "tail") # OK, because cmd is not shell interpreted + end + + def show + `ls` + system("ls") + exec("ls") + %x(ls) + end + + def index + cmd = params[:key] + if %w(foo bar).include? cmd + `echo #{cmd}` + end + Open3.capture2("echo #{cmd}") + end +end diff --git a/ruby/ql/test/query-tests/security/cwe-079/ReflectedXSS.expected b/ruby/ql/test/query-tests/security/cwe-079/ReflectedXSS.expected new file mode 100644 index 000000000000..9f9f21e346d3 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-079/ReflectedXSS.expected @@ -0,0 +1,60 @@ +edges +| app/controllers/foo/bars_controller.rb:9:12:9:17 | call to params : | app/controllers/foo/bars_controller.rb:9:12:9:29 | ...[...] : | +| app/controllers/foo/bars_controller.rb:9:12:9:29 | ...[...] : | app/views/foo/bars/show.html.erb:47:5:47:13 | call to user_name | +| app/controllers/foo/bars_controller.rb:13:5:13:37 | ... = ... : | app/views/foo/bars/show.html.erb:51:5:51:18 | call to user_name_memo | +| app/controllers/foo/bars_controller.rb:13:20:13:25 | call to params : | app/controllers/foo/bars_controller.rb:13:5:13:37 | ... = ... : | +| app/controllers/foo/bars_controller.rb:17:21:17:26 | call to params : | app/controllers/foo/bars_controller.rb:17:21:17:36 | ...[...] : | +| app/controllers/foo/bars_controller.rb:17:21:17:36 | ...[...] : | app/views/foo/bars/show.html.erb:2:18:2:30 | @user_website | +| app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params : | app/controllers/foo/bars_controller.rb:19:22:19:23 | dt : | +| app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params : | app/controllers/foo/bars_controller.rb:23:53:23:54 | dt : | +| app/controllers/foo/bars_controller.rb:19:22:19:23 | dt : | app/views/foo/bars/show.html.erb:41:3:41:16 | @instance_text | +| app/controllers/foo/bars_controller.rb:23:53:23:54 | dt : | app/views/foo/bars/show.html.erb:5:9:5:20 | call to display_text | +| app/controllers/foo/bars_controller.rb:23:53:23:54 | dt : | app/views/foo/bars/show.html.erb:8:9:8:36 | ...[...] | +| app/controllers/foo/bars_controller.rb:23:53:23:54 | dt : | app/views/foo/bars/show.html.erb:12:9:12:26 | ...[...] | +| app/controllers/foo/bars_controller.rb:23:53:23:54 | dt : | app/views/foo/bars/show.html.erb:36:3:36:14 | call to display_text | +| app/controllers/foo/bars_controller.rb:23:53:23:54 | dt : | app/views/foo/bars/show.html.erb:44:76:44:87 | call to display_text : | +| app/views/foo/bars/show.html.erb:44:64:44:87 | ... + ... : | app/views/foo/bars/_widget.html.erb:5:9:5:20 | call to display_text | +| app/views/foo/bars/show.html.erb:44:64:44:87 | ... + ... : | app/views/foo/bars/_widget.html.erb:8:9:8:36 | ...[...] | +| app/views/foo/bars/show.html.erb:44:76:44:87 | call to display_text : | app/views/foo/bars/show.html.erb:44:64:44:87 | ... + ... : | +| app/views/foo/bars/show.html.erb:54:29:54:34 | call to params : | app/views/foo/bars/show.html.erb:54:29:54:44 | ...[...] | +| app/views/foo/bars/show.html.erb:57:13:57:18 | call to params : | app/views/foo/bars/show.html.erb:57:13:57:28 | ...[...] | +nodes +| app/controllers/foo/bars_controller.rb:9:12:9:17 | call to params : | semmle.label | call to params : | +| app/controllers/foo/bars_controller.rb:9:12:9:29 | ...[...] : | semmle.label | ...[...] : | +| app/controllers/foo/bars_controller.rb:13:5:13:37 | ... = ... : | semmle.label | ... = ... : | +| app/controllers/foo/bars_controller.rb:13:20:13:25 | call to params : | semmle.label | call to params : | +| app/controllers/foo/bars_controller.rb:17:21:17:26 | call to params : | semmle.label | call to params : | +| app/controllers/foo/bars_controller.rb:17:21:17:36 | ...[...] : | semmle.label | ...[...] : | +| app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params : | semmle.label | call to params : | +| app/controllers/foo/bars_controller.rb:19:22:19:23 | dt : | semmle.label | dt : | +| app/controllers/foo/bars_controller.rb:23:53:23:54 | dt : | semmle.label | dt : | +| app/views/foo/bars/_widget.html.erb:5:9:5:20 | call to display_text | semmle.label | call to display_text | +| app/views/foo/bars/_widget.html.erb:8:9:8:36 | ...[...] | semmle.label | ...[...] | +| app/views/foo/bars/show.html.erb:2:18:2:30 | @user_website | semmle.label | @user_website | +| app/views/foo/bars/show.html.erb:5:9:5:20 | call to display_text | semmle.label | call to display_text | +| app/views/foo/bars/show.html.erb:8:9:8:36 | ...[...] | semmle.label | ...[...] | +| app/views/foo/bars/show.html.erb:12:9:12:26 | ...[...] | semmle.label | ...[...] | +| app/views/foo/bars/show.html.erb:36:3:36:14 | call to display_text | semmle.label | call to display_text | +| app/views/foo/bars/show.html.erb:41:3:41:16 | @instance_text | semmle.label | @instance_text | +| app/views/foo/bars/show.html.erb:44:64:44:87 | ... + ... : | semmle.label | ... + ... : | +| app/views/foo/bars/show.html.erb:44:76:44:87 | call to display_text : | semmle.label | call to display_text : | +| app/views/foo/bars/show.html.erb:47:5:47:13 | call to user_name | semmle.label | call to user_name | +| app/views/foo/bars/show.html.erb:51:5:51:18 | call to user_name_memo | semmle.label | call to user_name_memo | +| app/views/foo/bars/show.html.erb:54:29:54:34 | call to params : | semmle.label | call to params : | +| app/views/foo/bars/show.html.erb:54:29:54:44 | ...[...] | semmle.label | ...[...] | +| app/views/foo/bars/show.html.erb:57:13:57:18 | call to params : | semmle.label | call to params : | +| app/views/foo/bars/show.html.erb:57:13:57:28 | ...[...] | semmle.label | ...[...] | +subpaths +#select +| app/views/foo/bars/_widget.html.erb:5:9:5:20 | call to display_text | app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params : | app/views/foo/bars/_widget.html.erb:5:9:5:20 | call to display_text | Cross-site scripting vulnerability due to $@. | app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params | a user-provided value | +| app/views/foo/bars/_widget.html.erb:8:9:8:36 | ...[...] | app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params : | app/views/foo/bars/_widget.html.erb:8:9:8:36 | ...[...] | Cross-site scripting vulnerability due to $@. | app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params | a user-provided value | +| app/views/foo/bars/show.html.erb:2:18:2:30 | @user_website | app/controllers/foo/bars_controller.rb:17:21:17:26 | call to params : | app/views/foo/bars/show.html.erb:2:18:2:30 | @user_website | Cross-site scripting vulnerability due to $@. | app/controllers/foo/bars_controller.rb:17:21:17:26 | call to params | a user-provided value | +| app/views/foo/bars/show.html.erb:5:9:5:20 | call to display_text | app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params : | app/views/foo/bars/show.html.erb:5:9:5:20 | call to display_text | Cross-site scripting vulnerability due to $@. | app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params | a user-provided value | +| app/views/foo/bars/show.html.erb:8:9:8:36 | ...[...] | app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params : | app/views/foo/bars/show.html.erb:8:9:8:36 | ...[...] | Cross-site scripting vulnerability due to $@. | app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params | a user-provided value | +| app/views/foo/bars/show.html.erb:12:9:12:26 | ...[...] | app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params : | app/views/foo/bars/show.html.erb:12:9:12:26 | ...[...] | Cross-site scripting vulnerability due to $@. | app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params | a user-provided value | +| app/views/foo/bars/show.html.erb:36:3:36:14 | call to display_text | app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params : | app/views/foo/bars/show.html.erb:36:3:36:14 | call to display_text | Cross-site scripting vulnerability due to $@. | app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params | a user-provided value | +| app/views/foo/bars/show.html.erb:41:3:41:16 | @instance_text | app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params : | app/views/foo/bars/show.html.erb:41:3:41:16 | @instance_text | Cross-site scripting vulnerability due to $@. | app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params | a user-provided value | +| app/views/foo/bars/show.html.erb:47:5:47:13 | call to user_name | app/controllers/foo/bars_controller.rb:9:12:9:17 | call to params : | app/views/foo/bars/show.html.erb:47:5:47:13 | call to user_name | Cross-site scripting vulnerability due to $@. | app/controllers/foo/bars_controller.rb:9:12:9:17 | call to params | a user-provided value | +| app/views/foo/bars/show.html.erb:51:5:51:18 | call to user_name_memo | app/controllers/foo/bars_controller.rb:13:20:13:25 | call to params : | app/views/foo/bars/show.html.erb:51:5:51:18 | call to user_name_memo | Cross-site scripting vulnerability due to $@. | app/controllers/foo/bars_controller.rb:13:20:13:25 | call to params | a user-provided value | +| app/views/foo/bars/show.html.erb:54:29:54:44 | ...[...] | app/views/foo/bars/show.html.erb:54:29:54:34 | call to params : | app/views/foo/bars/show.html.erb:54:29:54:44 | ...[...] | Cross-site scripting vulnerability due to $@. | app/views/foo/bars/show.html.erb:54:29:54:34 | call to params | a user-provided value | +| app/views/foo/bars/show.html.erb:57:13:57:28 | ...[...] | app/views/foo/bars/show.html.erb:57:13:57:18 | call to params : | app/views/foo/bars/show.html.erb:57:13:57:28 | ...[...] | Cross-site scripting vulnerability due to $@. | app/views/foo/bars/show.html.erb:57:13:57:18 | call to params | a user-provided value | diff --git a/ruby/ql/test/query-tests/security/cwe-079/ReflectedXSS.qlref b/ruby/ql/test/query-tests/security/cwe-079/ReflectedXSS.qlref new file mode 100644 index 000000000000..af140959abb2 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-079/ReflectedXSS.qlref @@ -0,0 +1 @@ +queries/security/cwe-079/ReflectedXSS.ql diff --git a/ruby/ql/test/query-tests/security/cwe-079/app/controllers/foo/bars_controller.rb b/ruby/ql/test/query-tests/security/cwe-079/app/controllers/foo/bars_controller.rb new file mode 100644 index 000000000000..56df7b4d3dde --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-079/app/controllers/foo/bars_controller.rb @@ -0,0 +1,25 @@ +class BarsController < ApplicationController + helper_method :user_name, :user_name_memo + + def index + render template: "foo/bars/index" + end + + def user_name + return params[:user_name] + end + + def user_name_memo + @user_name ||= params[:user_name] + end + + def show + @user_website = params[:website] + dt = params[:text] + @instance_text = dt + @safe_foo = params[:text] + @safe_foo = "safe_foo" + @html_escaped = ERB::Util.html_escape(params[:text]) + render "foo/bars/show", locals: { display_text: dt, safe_text: "hello" } + end +end diff --git a/ruby/ql/test/query-tests/security/cwe-079/app/views/foo/bars/_widget.html.erb b/ruby/ql/test/query-tests/security/cwe-079/app/views/foo/bars/_widget.html.erb new file mode 100644 index 000000000000..54b52afe8c7f --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-079/app/views/foo/bars/_widget.html.erb @@ -0,0 +1,11 @@ +<%# BAD: A local rendered raw as an instance variable %> +<%= raw @display_text %> + +<%# BAD: A local rendered raw as a local variable %> +<%= raw display_text %> + +<%# BAD: A local rendered raw via the local_assigns hash %> +<%= raw local_assigns[:display_text] %> + +<%# GOOD: A local rendered with default escaping via the local_assigns hash %> +<%= local_assigns[:display_text] %> diff --git a/ruby/ql/test/query-tests/security/cwe-079/app/views/foo/bars/show.html.erb b/ruby/ql/test/query-tests/security/cwe-079/app/views/foo/bars/show.html.erb new file mode 100644 index 000000000000..0df7af110396 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-079/app/views/foo/bars/show.html.erb @@ -0,0 +1,71 @@ +<%# BAD: An instance variable rendered without escaping %> +website + +<%# BAD: A local rendered raw as a local variable %> +<%= raw display_text %> + +<%# BAD: A local rendered raw via the local_assigns hash %> +<%= raw local_assigns[:display_text] %> + +<% key = :display_text %> +<%# BAD: A local rendered raw via the locals_assigns hash %> +<%= raw local_assigns[key] %> + +
      +<% for key in [:display_text, :safe_text] do %> + <%# BAD: A local rendered raw via the locals hash %> + <%# TODO: we miss that `key` can take `:display_text` as a value here %> +
    • <%= raw local_assigns[key] %>
    • +<% end %> +
    + +<%# GOOD: A local rendered with default escaping via the local_assigns hash %> +<%= local_assigns[display_text] %> + +<%# GOOD: default escaping of rendered text %> +<%= + full_text = prefix + local_assigns[:display_text] + full_text +%> + +<%# GOOD: default escaping of rendered text (from instance var) %> +<%= @instance_text %> + +<%# BAD: html_safe marks string as not requiring HTML escaping %> +<%= + display_text.html_safe +%> + +<%# BAD: html_safe marks string as not requiring HTML escaping %> +<%= + @instance_text.html_safe +%> + +<%= render partial: 'foo/bars/widget', locals: { display_text: "widget_" + display_text } %> + +<%# BAD: user_name is a helper method that returns unsanitized user-input %> +<%= user_name.html_safe %> + +<%# BAD: user_name_memo is a helper method that returns unsanitized user-input %> +<%# TODO: we miss this because the return value from user_name_memo is not properly linked to this call %> +<%= user_name_memo.html_safe %> + +<%# BAD: unsanitized user-input should not be passed to link_to as the URL %> +<%= link_to "user website", params[:website] %> + +<%# BAD: unsanitized user-input should not be passed to link_to as the URL %> +<%= link_to params[:website], class: "user-link" do %> + user website +<% end %> + +<%# GOOD: @safe_foo is a hardcoded string here at runtime %> +<%= @safe_foo.html_safe %> + +<%# GOOD: @html_escaped is manually escaped in the controller %> +<%= @html_escaped.html_safe %> + +<%# GOOD: @html_escaped is manually escaped in the controller %> +<%= + html_escaped_in_template = h params[:text] + html_escaped_in_template.html_safe +%> diff --git a/ruby/ql/test/query-tests/security/cwe-089/ActiveRecordInjection.rb b/ruby/ql/test/query-tests/security/cwe-089/ActiveRecordInjection.rb new file mode 100644 index 000000000000..2044d44b83e1 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-089/ActiveRecordInjection.rb @@ -0,0 +1,103 @@ +class UserGroup < ActiveRecord::Base + has_many :users +end + +class User < ApplicationRecord + belongs_to :user_group + + def self.authenticate(name, pass) + # BAD: possible untrusted input interpolated into SQL fragment + find(:first, :conditions => "name='#{name}' and pass='#{pass}'") + end + + def self.from(user_group_id) + # GOOD: `find_by` with hash argument + UserGroup.find_by(id: user_group_id).users + end +end + +class Admin < User + def self.delete_all(condition = nil) + # BAD: `delete_all` overrides an ActiveRecord method, but doesn't perform + # any validation before passing its arguments on to another ActiveRecord method + destroy_all(condition) + end +end + +class FooController < ActionController::Base + + MAX_USER_ID = 100_000 + + # A string tainted by user input is inserted into an SQL query + def some_request_handler + # BAD: executes `SELECT AVG(#{params[:column]}) FROM "users"` + # where `params[:column]` is unsanitized + User.calculate(:average, params[:column]) + + # BAD: executes `DELETE FROM "users" WHERE (id = '#{params[:id]}')` + # where `params[:id]` is unsanitized + User.delete_all("id = '#{params[:id]}'") + + # BAD: executes `SELECT "users".* FROM "users" WHERE (id = '#{params[:id]}')` + # where `params[:id]` is unsanitized + User.destroy_all(["id = '#{params[:id]}'"]) + + # BAD: executes `SELECT "users".* FROM "users" WHERE id BETWEEN '#{params[:min_id]}' AND 100000` + # where `params[:min_id]` is unsanitized + User.where(<<-SQL, MAX_USER_ID) + id BETWEEN '#{params[:min_id]}' AND ? + SQL + + # BAD: chained method case + # executes `SELECT "users".* FROM "users" WHERE (NOT (user_id = 'params[:id]'))` + # where `params[:id]` is unsanitized + User.where.not("user.id = '#{params[:id]}'") + + User.authenticate(params[:name], params[:pass]) + end +end + +class BarController < ApplicationController + def some_other_request_handler + ps = params + uid = ps[:id] + uidEq = "= '#{uid}'" + + # BAD: executes `DELETE FROM "users" WHERE (id = #{uid})` + # where `uid` is unsantized + User.delete_all("id " + uidEq) + end + + def safe_paths + dir = params[:order] + # GOOD: barrier guard prevents taint flow + if dir == "ASC" + User.order("name #{dir}") + else + dir = "DESC" + User.order("name #{dir}") + end + # TODO: a more idiomatic form of this guard is the following: + # dir = "DESC" unless dir == "ASC" + # but our taint tracking can't (yet) handle that properly + + name = params[:user_name] + # GOOD: barrier guard prevents taint flow + if %w(alice bob charlie).include? name + User.find_by("username = #{name}") + end + + name = params[:user_name] + # GOOD: hash arguments are sanitized by ActiveRecord + User.find_by(user_name: name) + + # OK: `find` method is overridden in `User` + User.find(params[:user_group]) + end +end + +class BazController < BarController + def yet_another_handler + Admin.delete_all(params[:admin_condition]) + end +end diff --git a/ruby/ql/test/query-tests/security/cwe-089/SqlInjection.expected b/ruby/ql/test/query-tests/security/cwe-089/SqlInjection.expected new file mode 100644 index 000000000000..383a6ef2a94c --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-089/SqlInjection.expected @@ -0,0 +1,51 @@ +edges +| ActiveRecordInjection.rb:8:25:8:28 | name : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | +| ActiveRecordInjection.rb:8:31:8:34 | pass : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | +| ActiveRecordInjection.rb:20:23:20:31 | condition : | ActiveRecordInjection.rb:23:17:23:25 | condition | +| ActiveRecordInjection.rb:35:30:35:35 | call to params : | ActiveRecordInjection.rb:35:30:35:44 | ...[...] | +| ActiveRecordInjection.rb:39:30:39:35 | call to params : | ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" | +| ActiveRecordInjection.rb:43:32:43:37 | call to params : | ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" | +| ActiveRecordInjection.rb:48:21:48:26 | call to params : | ActiveRecordInjection.rb:47:16:47:21 | <<-SQL | +| ActiveRecordInjection.rb:54:34:54:39 | call to params : | ActiveRecordInjection.rb:54:20:54:47 | "user.id = '#{...}'" | +| ActiveRecordInjection.rb:56:23:56:28 | call to params : | ActiveRecordInjection.rb:56:23:56:35 | ...[...] : | +| ActiveRecordInjection.rb:56:23:56:35 | ...[...] : | ActiveRecordInjection.rb:8:25:8:28 | name : | +| ActiveRecordInjection.rb:56:38:56:43 | call to params : | ActiveRecordInjection.rb:56:38:56:50 | ...[...] : | +| ActiveRecordInjection.rb:56:38:56:50 | ...[...] : | ActiveRecordInjection.rb:8:31:8:34 | pass : | +| ActiveRecordInjection.rb:62:10:62:15 | call to params : | ActiveRecordInjection.rb:68:21:68:33 | ... + ... | +| ActiveRecordInjection.rb:101:22:101:27 | call to params : | ActiveRecordInjection.rb:101:22:101:45 | ...[...] : | +| ActiveRecordInjection.rb:101:22:101:45 | ...[...] : | ActiveRecordInjection.rb:20:23:20:31 | condition : | +nodes +| ActiveRecordInjection.rb:8:25:8:28 | name : | semmle.label | name : | +| ActiveRecordInjection.rb:8:31:8:34 | pass : | semmle.label | pass : | +| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | semmle.label | "name='#{...}' and pass='#{...}'" | +| ActiveRecordInjection.rb:20:23:20:31 | condition : | semmle.label | condition : | +| ActiveRecordInjection.rb:23:17:23:25 | condition | semmle.label | condition | +| ActiveRecordInjection.rb:35:30:35:35 | call to params : | semmle.label | call to params : | +| ActiveRecordInjection.rb:35:30:35:44 | ...[...] | semmle.label | ...[...] | +| ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" | semmle.label | "id = '#{...}'" | +| ActiveRecordInjection.rb:39:30:39:35 | call to params : | semmle.label | call to params : | +| ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" | semmle.label | "id = '#{...}'" | +| ActiveRecordInjection.rb:43:32:43:37 | call to params : | semmle.label | call to params : | +| ActiveRecordInjection.rb:47:16:47:21 | <<-SQL | semmle.label | <<-SQL | +| ActiveRecordInjection.rb:48:21:48:26 | call to params : | semmle.label | call to params : | +| ActiveRecordInjection.rb:54:20:54:47 | "user.id = '#{...}'" | semmle.label | "user.id = '#{...}'" | +| ActiveRecordInjection.rb:54:34:54:39 | call to params : | semmle.label | call to params : | +| ActiveRecordInjection.rb:56:23:56:28 | call to params : | semmle.label | call to params : | +| ActiveRecordInjection.rb:56:23:56:35 | ...[...] : | semmle.label | ...[...] : | +| ActiveRecordInjection.rb:56:38:56:43 | call to params : | semmle.label | call to params : | +| ActiveRecordInjection.rb:56:38:56:50 | ...[...] : | semmle.label | ...[...] : | +| ActiveRecordInjection.rb:62:10:62:15 | call to params : | semmle.label | call to params : | +| ActiveRecordInjection.rb:68:21:68:33 | ... + ... | semmle.label | ... + ... | +| ActiveRecordInjection.rb:101:22:101:27 | call to params : | semmle.label | call to params : | +| ActiveRecordInjection.rb:101:22:101:45 | ...[...] : | semmle.label | ...[...] : | +subpaths +#select +| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | ActiveRecordInjection.rb:56:23:56:28 | call to params : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:56:23:56:28 | call to params | a user-provided value | +| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | ActiveRecordInjection.rb:56:38:56:43 | call to params : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:56:38:56:43 | call to params | a user-provided value | +| ActiveRecordInjection.rb:23:17:23:25 | condition | ActiveRecordInjection.rb:101:22:101:27 | call to params : | ActiveRecordInjection.rb:23:17:23:25 | condition | This SQL query depends on $@. | ActiveRecordInjection.rb:101:22:101:27 | call to params | a user-provided value | +| ActiveRecordInjection.rb:35:30:35:44 | ...[...] | ActiveRecordInjection.rb:35:30:35:35 | call to params : | ActiveRecordInjection.rb:35:30:35:44 | ...[...] | This SQL query depends on $@. | ActiveRecordInjection.rb:35:30:35:35 | call to params | a user-provided value | +| ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" | ActiveRecordInjection.rb:39:30:39:35 | call to params : | ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:39:30:39:35 | call to params | a user-provided value | +| ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" | ActiveRecordInjection.rb:43:32:43:37 | call to params : | ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:43:32:43:37 | call to params | a user-provided value | +| ActiveRecordInjection.rb:47:16:47:21 | <<-SQL | ActiveRecordInjection.rb:48:21:48:26 | call to params : | ActiveRecordInjection.rb:47:16:47:21 | <<-SQL | This SQL query depends on $@. | ActiveRecordInjection.rb:48:21:48:26 | call to params | a user-provided value | +| ActiveRecordInjection.rb:54:20:54:47 | "user.id = '#{...}'" | ActiveRecordInjection.rb:54:34:54:39 | call to params : | ActiveRecordInjection.rb:54:20:54:47 | "user.id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:54:34:54:39 | call to params | a user-provided value | +| ActiveRecordInjection.rb:68:21:68:33 | ... + ... | ActiveRecordInjection.rb:62:10:62:15 | call to params : | ActiveRecordInjection.rb:68:21:68:33 | ... + ... | This SQL query depends on $@. | ActiveRecordInjection.rb:62:10:62:15 | call to params | a user-provided value | diff --git a/ruby/ql/test/query-tests/security/cwe-089/SqlInjection.qlref b/ruby/ql/test/query-tests/security/cwe-089/SqlInjection.qlref new file mode 100644 index 000000000000..bcb55c8510f1 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-089/SqlInjection.qlref @@ -0,0 +1 @@ +queries/security/cwe-089/SqlInjection.ql diff --git a/ruby/ql/test/query-tests/security/cwe-094/CodeInjection.expected b/ruby/ql/test/query-tests/security/cwe-094/CodeInjection.expected new file mode 100644 index 000000000000..47df28e98d10 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-094/CodeInjection.expected @@ -0,0 +1,10 @@ +edges +| CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:6:10:6:13 | code | +nodes +| CodeInjection.rb:3:12:3:17 | call to params : | semmle.label | call to params : | +| CodeInjection.rb:6:10:6:13 | code | semmle.label | code | +| CodeInjection.rb:9:10:9:15 | call to params | semmle.label | call to params | +subpaths +#select +| CodeInjection.rb:6:10:6:13 | code | CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:6:10:6:13 | code | This code execution depends on $@. | CodeInjection.rb:3:12:3:17 | call to params | a user-provided value | +| CodeInjection.rb:9:10:9:15 | call to params | CodeInjection.rb:9:10:9:15 | call to params | CodeInjection.rb:9:10:9:15 | call to params | This code execution depends on $@. | CodeInjection.rb:9:10:9:15 | call to params | a user-provided value | diff --git a/ruby/ql/test/query-tests/security/cwe-094/CodeInjection.qlref b/ruby/ql/test/query-tests/security/cwe-094/CodeInjection.qlref new file mode 100644 index 000000000000..6dcbcb4448ed --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-094/CodeInjection.qlref @@ -0,0 +1 @@ +queries/security/cwe-094/CodeInjection.ql diff --git a/ruby/ql/test/query-tests/security/cwe-094/CodeInjection.rb b/ruby/ql/test/query-tests/security/cwe-094/CodeInjection.rb new file mode 100644 index 000000000000..a738a9f22658 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-094/CodeInjection.rb @@ -0,0 +1,29 @@ +class UsersController < ActionController::Base + def create + code = params[:code] + + # BAD + eval(code) + + # BAD + eval(params) + + # GOOD + Foo.new.bar(code) + end + + def update + # GOOD + eval("foo") + end +end + +class Foo + def eval(x) + true + end + + def bar(x) + eval(x) + end +end diff --git a/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/ANodeBlog-LICENSE b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/ANodeBlog-LICENSE new file mode 100644 index 000000000000..8dada3edaf50 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/ANodeBlog-LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/CodeMirror-LICENSE b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/CodeMirror-LICENSE new file mode 100644 index 000000000000..ff7db4b99f55 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/CodeMirror-LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (C) 2017 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/Prism-LICENSE b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/Prism-LICENSE new file mode 100644 index 000000000000..528949f42d9e --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/Prism-LICENSE @@ -0,0 +1,21 @@ +MIT LICENSE + +Copyright (c) 2012 Lea Verou + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/Prototype.js-LICENSE b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/Prototype.js-LICENSE new file mode 100644 index 000000000000..05789cf01905 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/Prototype.js-LICENSE @@ -0,0 +1,16 @@ +Copyright (c) 2005-2010 Sam Stephenson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/ReDoS.expected b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/ReDoS.expected new file mode 100644 index 000000000000..7cb5759e733e --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/ReDoS.expected @@ -0,0 +1,93 @@ +| tst.rb:4:14:4:28 | (?:__\|[\\s\\S])+? | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '__'. | +| tst.rb:4:38:4:54 | (?:\\*\\*\|[\\s\\S])+? | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '**'. | +| tst.rb:19:20:19:39 | (?:[^"\\\\]\|\\\\\\\\\|\\\\.)+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\\\\\\\'. | +| tst.rb:19:43:19:62 | (?:[^'\\\\]\|\\\\\\\\\|\\\\.)+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\\\\\\\'. | +| tst.rb:19:67:19:86 | (?:[^)\\\\]\|\\\\\\\\\|\\\\.)+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\\\\\\\'. | +| tst.rb:31:50:31:51 | .* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\|\|\\n'. | +| tst.rb:36:19:36:28 | (\\\\\\/\|.)*? | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\\\/'. | +| tst.rb:41:23:41:24 | .* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '#'. | +| tst.rb:47:27:47:29 | .*? | This part of the regular expression may cause exponential backtracking on strings starting with '"' and containing many repetitions of '""'. | +| tst.rb:47:33:47:35 | .*? | This part of the regular expression may cause exponential backtracking on strings starting with ''' and containing many repetitions of ''''. | +| tst.rb:52:33:52:35 | .*? | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of ']['. | +| tst.rb:52:66:52:68 | .*? | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of ']['. | +| tst.rb:58:11:58:16 | [a-z]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:59:11:59:16 | [a-z]* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:60:39:60:50 | [a-zA-Z0-9]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | +| tst.rb:61:12:61:19 | ([a-z])+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'aa'. | +| tst.rb:66:12:66:27 | [\\w#:.~>+()\\s-]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\t'. | +| tst.rb:66:34:66:36 | .*? | This part of the regular expression may cause exponential backtracking on strings starting with '[' and containing many repetitions of ']['. | +| tst.rb:71:15:71:22 | (\\\\?.)*? | This part of the regular expression may cause exponential backtracking on strings starting with '"' and containing many repetitions of '\\\\a'. | +| tst.rb:74:10:74:17 | (b\|a?b)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'b'. | +| tst.rb:77:10:77:17 | (a\|aa?)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:83:10:83:16 | (.\|\\n)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\n'. | +| tst.rb:95:11:95:24 | ([\\S\\s]\|[^a])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '`'. | +| tst.rb:101:11:101:19 | (.\|[^a])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '`'. | +| tst.rb:107:11:107:19 | (b\|[^a])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'b'. | +| tst.rb:110:11:110:19 | (G\|[^a])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'G'. | +| tst.rb:113:11:113:23 | ([0-9]\|[^a])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | +| tst.rb:125:11:125:24 | ([a-z]\|[d-h])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'd'. | +| tst.rb:128:11:128:26 | ([^a-z]\|[^0-9])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '/'. | +| tst.rb:131:11:131:21 | (\\d\|[0-9])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | +| tst.rb:134:11:134:18 | (\\s\|\\s)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of ' '. | +| tst.rb:137:11:137:17 | (\\w\|G)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'G'. | +| tst.rb:143:11:143:18 | (\\d\|\\w)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | +| tst.rb:146:11:146:17 | (\\d\|5)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '5'. | +| tst.rb:155:11:155:20 | (\\f\|[\\f])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'f'. | +| tst.rb:158:11:158:18 | (\\W\|\\D)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of ' '. | +| tst.rb:161:11:161:18 | (\\S\|\\w)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | +| tst.rb:164:11:164:20 | (\\S\|[\\w])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | +| tst.rb:167:11:167:23 | (1s\|[\\da-z])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '1s'. | +| tst.rb:170:11:170:19 | (0\|[\\d])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | +| tst.rb:173:12:173:16 | [\\d]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | +| tst.rb:185:12:185:17 | [^>a]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '='. | +| tst.rb:188:13:188:15 | \\s* | This part of the regular expression may cause exponential backtracking on strings starting with '\\n' and containing many repetitions of '\\n'. | +| tst.rb:191:14:191:16 | \\s+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of ' '. | +| tst.rb:194:64:194:75 | [ a-zA-Z{}]+ | This part of the regular expression may cause exponential backtracking on strings starting with '{[A(A)A:' and containing many repetitions of ' A:'. | +| tst.rb:194:77:194:78 | ,? | This part of the regular expression may cause exponential backtracking on strings starting with '{[A(A)A: ' and containing many repetitions of ',A: '. | +| tst.rb:197:11:197:12 | a+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:197:14:197:15 | b+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'b'. | +| tst.rb:200:12:200:18 | (a+a?)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:200:13:200:14 | a+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:203:11:203:12 | a+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:209:11:209:12 | a+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:215:11:215:13 | \\n+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\n'. | +| tst.rb:218:11:218:15 | [^X]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'W'. | +| tst.rb:221:16:221:16 | b | This part of the regular expression may cause exponential backtracking on strings starting with 'W' and containing many repetitions of 'bW'. | +| tst.rb:227:16:227:16 | b | This part of the regular expression may cause exponential backtracking on strings starting with 'W' and containing many repetitions of 'bW'. | +| tst.rb:239:13:239:13 | b | This part of the regular expression may cause exponential backtracking on strings starting with 'a' and containing many repetitions of 'ba'. | +| tst.rb:245:11:245:17 | [\\n\\s]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\n'. | +| tst.rb:254:11:254:13 | \\w* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz'. | +| tst.rb:254:23:254:25 | \\w* | This part of the regular expression may cause exponential backtracking on strings starting with 'foobarbaz' and containing many repetitions of 'foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz'. | +| tst.rb:254:35:254:37 | \\w* | This part of the regular expression may cause exponential backtracking on strings starting with 'foobarbazfoobarbaz' and containing many repetitions of 'foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz'. | +| tst.rb:254:47:254:49 | \\w* | This part of the regular expression may cause exponential backtracking on strings starting with 'foobarbazfoobarbazfoobarbaz' and containing many repetitions of 'foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz'. | +| tst.rb:257:10:257:112 | (.thisisagoddamnlongstringforstresstestingthequery\|\\sthisisagoddamnlongstringforstresstestingthequery)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of ' thisisagoddamnlongstringforstresstestingthequery'. | +| tst.rb:260:10:260:73 | (thisisagoddamnlongstringforstresstestingthequery\|this\\w+query)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'thisisagoddamnlongstringforstresstestingthequery'. | +| tst.rb:260:64:260:66 | \\w+ | This part of the regular expression may cause exponential backtracking on strings starting with 'this' and containing many repetitions of 'aquerythis'. | +| tst.rb:272:17:272:18 | b+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'b'. | +| tst.rb:275:34:275:36 | \\s* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '"" a='. | +| tst.rb:281:12:281:13 | a+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:284:12:284:13 | a+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:290:12:290:13 | a+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:293:21:293:22 | a+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:299:86:299:87 | e+ | This part of the regular expression may cause exponential backtracking on strings starting with ';00000000000000' and containing many repetitions of 'e'. | +| tst.rb:302:14:302:15 | c+ | This part of the regular expression may cause exponential backtracking on strings starting with 'ab' and containing many repetitions of 'c'. | +| tst.rb:305:14:305:16 | \\s+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of ' '. | +| tst.rb:308:12:308:21 | ([^\\/]\|X)+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'X'. | +| tst.rb:311:16:311:20 | [^Y]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'Xx'. | +| tst.rb:314:11:314:12 | a* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:317:14:317:19 | [\\w-]* | This part of the regular expression may cause exponential backtracking on strings starting with 'foo' and containing many repetitions of '-'. | +| tst.rb:320:11:320:15 | (ab)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'ab'. | +| tst.rb:323:10:323:16 | (a?a?)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:332:10:332:18 | (?:a\|a?)+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:338:13:338:41 | (([a-c]\|[c-d])T(e?e?e?e?\|X))+ | This part of the regular expression may cause exponential backtracking on strings starting with 'PRE' and containing many repetitions of 'cTX'. | +| tst.rb:341:12:341:15 | (a)+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'aa'. | +| tst.rb:344:12:344:13 | b+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'bb'. | +| tst.rb:350:11:350:12 | a* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:351:11:351:12 | a+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:352:11:352:12 | a* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:353:11:353:12 | a+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | +| tst.rb:360:11:360:26 | ((?:a{\|-)\|\\w\\{)+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a{'. | +| tst.rb:361:11:361:29 | ((?:a{0\|-)\|\\w\\{\\d)+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a{0'. | +| tst.rb:362:11:362:31 | ((?:a{0,\|-)\|\\w\\{\\d,)+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a{0,'. | +| tst.rb:363:11:363:34 | ((?:a{0,2\|-)\|\\w\\{\\d,\\d)+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a{0,2'. | +| tst.rb:369:12:369:22 | (\\u0061\|a)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | diff --git a/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/ReDoS.qlref b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/ReDoS.qlref new file mode 100644 index 000000000000..7f4557181d7c --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/ReDoS.qlref @@ -0,0 +1 @@ +queries/security/cwe-1333/ReDoS.ql diff --git a/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/brace-expansion-LICENSE b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/brace-expansion-LICENSE new file mode 100644 index 000000000000..de3226673c38 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/brace-expansion-LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013 Julian Gruber + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/jest-LICENSE b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/jest-LICENSE new file mode 100644 index 000000000000..10e779c44ac3 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/jest-LICENSE @@ -0,0 +1,23 @@ +MIT License + +For Jest software + +Copyright (c) 2014-present, Facebook, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/knockout-LICENSE b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/knockout-LICENSE new file mode 100644 index 000000000000..08a07b1ae8d4 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/knockout-LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) - http://www.opensource.org/licenses/mit-license.php + +Copyright (c) Steven Sanderson, the Knockout.js team, and other contributors +http://knockoutjs.com/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/marked-LICENSE b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/marked-LICENSE new file mode 100644 index 000000000000..64b41a0e4637 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/marked-LICENSE @@ -0,0 +1,43 @@ +# License information + +## Contribution License Agreement + +If you contribute code to this project, you are implicitly allowing your code +to be distributed under the MIT license. You are also implicitly verifying that +all code is your original work. `` + +## Marked + +Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## Markdown + +Copyright © 2004, John Gruber +http://daringfireball.net/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name “Markdown” nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +This software is provided by the copyright holders and contributors “as is” and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright owner or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage. diff --git a/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/tst.rb b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/tst.rb new file mode 100644 index 000000000000..01f8e8a83bc5 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-1333-exponential-redos/tst.rb @@ -0,0 +1,372 @@ +# NOT GOOD; attack: "_" + "__".repeat(100) +# Adapted from marked (https://github.com/markedjs/marked), which is licensed +# under the MIT license; see file marked-LICENSE. +bad1 = /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/ + +# GOOD +# Adapted from marked (https://github.com/markedjs/marked), which is licensed +# under the MIT license; see file marked-LICENSE. +good1 = /^\b_((?:__|[^_])+?)_\b|^\*((?:\*\*|[^*])+?)\*(?!\*)/ + +# GOOD - there is no witness in the end that could cause the regexp to not match +# Adapted from brace-expansion (https://github.com/juliangruber/brace-expansion), +# which is licensed under the MIT license; see file brace-expansion-LICENSE. +good2 = /(.*,)+.+/ + +# NOT GOOD; attack: " '" + "\\\\".repeat(100) +# Adapted from CodeMirror (https://github.com/codemirror/codemirror), +# which is licensed under the MIT license; see file CodeMirror-LICENSE. +bad2 = /^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/ + +# GOOD +# Adapted from lulucms2 (https://github.com/yiifans/lulucms2). +good2 = /\(\*(?:[\s\S]*?\(\*[\s\S]*?\*\))*[\s\S]*?\*\)/ + +# GOOD +# Adapted from jest (https://github.com/facebook/jest), which is licensed +# under the MIT license; see file jest-LICENSE. +good3 = /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/ + +# NOT GOOD, variant of good3; attack: "a|\n:|\n" + "||\n".repeat(100) +bad4 = /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)a/ + +# NOT GOOD; attack: "/" + "\\/a".repeat(100) +# Adapted from ANodeBlog (https://github.com/gefangshuai/ANodeBlog), +# which is licensed under the Apache License 2.0; see file ANodeBlog-LICENSE. +bad5 = /\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/ + +# NOT GOOD; attack: "##".repeat(100) + "\na" +# Adapted from CodeMirror (https://github.com/codemirror/codemirror), +# which is licensed under the MIT license; see file CodeMirror-LICENSE. +bad6 = /^([\s\[\{\(]|#.*)*$/ + +# GOOD +good4 = /(\r\n|\r|\n)+/ + +# BAD - PoC: `node -e "/((?:[^\"\']|\".*?\"|\'.*?\')*?)([(,)]|$)/.test(\"'''''''''''''''''''''''''''''''''''''''''''''\\\"\");"`. It's complicated though, because the regexp still matches something, it just matches the empty-string after the attack string. +actuallyBad = /((?:[^"']|".*?"|'.*?')*?)([(,)]|$)/ + +# NOT GOOD; attack: "a" + "[]".repeat(100) + ".b\n" +# Adapted from Knockout (https://github.com/knockout/knockout), which is +# licensed under the MIT license; see file knockout-LICENSE +bad6 = /^[\_$a-z][\_$a-z0-9]*(\[.*?\])*(\.[\_$a-z][\_$a-z0-9]*(\[.*?\])*)*$/i + +# GOOD +good6 = /(a|.)*/ + +# Testing the NFA - only some of the below are detected. +bad7 = /^([a-z]+)+$/ +bad8 = /^([a-z]*)*$/ +bad9 = /^([a-zA-Z0-9])(([\\.-]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$/ +bad10 = /^(([a-z])+.)+[A-Z]([a-z])+$/ + +# NOT GOOD; attack: "[" + "][".repeat(100) + "]!" +# Adapted from Prototype.js (https://github.com/prototypejs/prototype), which +# is licensed under the MIT license; see file Prototype.js-LICENSE. +bad11 = /(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/ + +# NOT GOOD; attack: "'" + "\\a".repeat(100) + '"' +# Adapted from Prism (https://github.com/PrismJS/prism), which is licensed +# under the MIT license; see file Prism-LICENSE. +bad12 = /("|')(\\?.)*?\1/ + +# NOT GOOD +bad13 = /(b|a?b)*c/ + +# NOT GOOD +bad15 = /(a|aa?)*b/ + +# GOOD +good7 = /(.|\n)*!/ + +# NOT GOOD; attack: "\n".repeat(100) + "." +bad16 = /(.|\n)*!/m + +# GOOD +good8 = /([\w.]+)*/ + +# BAD - we don't yet parse regexps constructed from strings +bad17 = Regexp.new '(a|aa?)*b' + +# GOOD - not used as regexp +good9 = '(a|aa?)*b' + +# NOT GOOD +bad18 = /(([\S\s]|[^a])*)"/ + +# GOOD - there is no witness in the end that could cause the regexp to not match +good10 = /([^"']+)*/ + +# NOT GOOD +bad20 = /((.|[^a])*)"/ + +# GOOD +good10 = /((a|[^a])*)"/ + +# NOT GOOD +bad21 = /((b|[^a])*)"/ + +# NOT GOOD +bad22 = /((G|[^a])*)"/ + +# NOT GOOD +bad23 = /(([0-9]|[^a])*)"/ + +# BAD - missing result +bad24 = /(?:=(?:([!#\$%&'\*\+\-\.\^_`\|~0-9A-Za-z]+)|"((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"])*)"))?/ + +# BAD - missing result +bad25 = /"((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"])*)"/ + +# GOOD +bad26 = /"((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"\\])*)"/ + +# NOT GOOD +bad27 = /(([a-z]|[d-h])*)"/ + +# NOT GOOD +bad27 = /(([^a-z]|[^0-9])*)"/ + +# NOT GOOD +bad28 = /((\d|[0-9])*)"/ + +# NOT GOOD +bad29 = /((\s|\s)*)"/ + +# NOT GOOD +bad30 = /((\w|G)*)"/ + +# GOOD +good11 = /((\s|\d)*)"/ + +# NOT GOOD +bad31 = /((\d|\w)*)"/ + +# NOT GOOD +bad32 = /((\d|5)*)"/ + +# BAD - \f is not handled correctly +bad33 = /((\s|[\f])*)"/ + +# BAD - \v is not handled correctly +bad34 = /((\s|[\v]|\\v)*)"/ + +# NOT GOOD +bad35 = /((\f|[\f])*)"/ + +# NOT GOOD +bad36 = /((\W|\D)*)"/ + +# NOT GOOD +bad37 = /((\S|\w)*)"/ + +# NOT GOOD +bad38 = /((\S|[\w])*)"/ + +# NOT GOOD +bad39 = /((1s|[\da-z])*)"/ + +# NOT GOOD +bad40 = /((0|[\d])*)"/ + +# NOT GOOD +bad41 = /(([\d]+)*)"/ + +# GOOD - there is no witness in the end that could cause the regexp to not match +good12 = /(\d+(X\d+)?)+/ + +# GOOD - there is no witness in the end that could cause the regexp to not match +good13 = /([0-9]+(X[0-9]*)?)*/ + +# GOOD +good15 = /^([^>]+)*(>|$)/ + +# NOT GOOD +bad43 = /^([^>a]+)*(>|$)/ + +# NOT GOOD +bad44 = /(\n\s*)+$/ + +# NOT GOOD +bad45 = /^(?:\s+|#.*|\(\?#[^)]*\))*(?:[?*+]|{\d+(?:,\d*)?})/ + +# NOT GOOD +bad46 = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)((\s*([a-zA-Z]+)\: ?([ a-zA-Z{}]+),?)+)*\s*\]\}/ + +# NOT GOOD +bad47 = /(a+|b+|c+)*c/ + +# NOT GOOD +bad48 = /(((a+a?)*)+b+)/ + +# NOT GOOD +bad49 = /(a+)+bbbb/ + +# GOOD +good16 = /(a+)+aaaaa*a+/ + +# NOT GOOD +bad50 = /(a+)+aaaaa$/ + +# GOOD +good17 = /(\n+)+\n\n/ + +# NOT GOOD +bad51 = /(\n+)+\n\n$/ + +# NOT GOOD +bad52 = /([^X]+)*$/ + +# NOT GOOD +bad53 = /(([^X]b)+)*$/ + +# GOOD +good18 = /(([^X]b)+)*($|[^X]b)/ + +# NOT GOOD +bad54 = /(([^X]b)+)*($|[^X]c)/ + +# GOOD +good20 = /((ab)+)*ababab/ + +# GOOD +good21 = /((ab)+)*abab(ab)*(ab)+/ + +# GOOD +good22 = /((ab)+)*/ + +# NOT GOOD +bad55 = /((ab)+)*$/ + +# GOOD +good23 = /((ab)+)*[a1][b1][a2][b2][a3][b3]/ + +# NOT GOOD +bad56 = /([\n\s]+)*(.)/ + +# GOOD - any witness passes through the accept state. +good24 = /(A*A*X)*/ + +# GOOD +good26 = /([^\\\]]+)*/ + +# NOT GOOD +bad59 = /(\w*foobarbaz\w*foobarbaz\w*foobarbaz\w*foobarbaz\s*foobarbaz\d*foobarbaz\w*)+-/ + +# NOT GOOD +bad60 = /(.thisisagoddamnlongstringforstresstestingthequery|\sthisisagoddamnlongstringforstresstestingthequery)*-/ + +# NOT GOOD +bad61 = /(thisisagoddamnlongstringforstresstestingthequery|this\w+query)*-/ + +# GOOD +good27 = /(thisisagoddamnlongstringforstresstestingthequery|imanotherbutunrelatedstringcomparedtotheotherstring)*-/ + +# GOOD +#good28 = /foo([\uDC66\uDC67]|[\uDC68\uDC69])*foo/ + +# GOOD +#good29 = /foo((\uDC66|\uDC67)|(\uDC68|\uDC69))*foo/ + +# NOT GOOD (but cannot currently construct a prefix) +bad62 = /a{2,3}(b+)+X/ + +# NOT GOOD (and a good prefix test) +bad63 = /^<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/ + +# GOOD +good30 = /(a+)*[\S\s][\S\s][\S\s]?/ + +# GOOD - but we fail to see that repeating the attack string ends in the "accept any" state (due to not parsing the range `[^]{2,3}`). +good31 = /(a+)*[\S\s]{2,3}/ + +# GOOD - but we spuriously conclude that a rejecting suffix exists (due to not parsing the range `[^]{2,}` when constructing the NFA). +good32 = /(a+)*([\S\s]{2,}|X)$/ + +# GOOD +good33 = /(a+)*([\S\s]*|X)$/ + +# NOT GOOD +bad64 = /((a+)*$|[\S\s]+)/ + +# GOOD - but still flagged. The only change compared to the above is the order of alternatives, which we don't model. +good34 = /([\S\s]+|(a+)*$)/ + +# GOOD +good35 = /((;|^)a+)+$/ + +# NOT GOOD (a good prefix test) +bad65 = /(^|;)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(e+)+f/ + +# NOT GOOD +bad66 = /^ab(c+)+$/ + +# NOT GOOD +bad67 = /(\d(\s+)*){20}/ + +# GOOD - but we spuriously conclude that a rejecting suffix exists. +good36 = /(([^\/]|X)+)(\/[\S\s]*)*$/ + +# GOOD - but we spuriously conclude that a rejecting suffix exists. +good37 = /^((x([^Y]+)?)*(Y|$))/ + +# NOT GOOD +bad68 = /(a*)+b/ + +# NOT GOOD +bad69 = /foo([\w-]*)+bar/ + +# NOT GOOD +bad70 = /((ab)*)+c/ + +# NOT GOOD +bad71 = /(a?a?)*b/ + +# GOOD +good38 = /(a?)*b/ + +# NOT GOOD - but not detected +bad72 = /(c?a?)*b/ + +# NOT GOOD +bad73 = /(?:a|a?)+b/ + +# NOT GOOD - but not detected. +bad74 = /(a?b?)*$/ + +# NOT GOOD +bad76 = /PRE(([a-c]|[c-d])T(e?e?e?e?|X))+(cTcT|cTXcTX$)/ + +# NOT GOOD - but not detected +bad77 = /^((a)+\w)+$/ + +# NOT GOOD +bad78 = /^(b+.)+$/ + +# GOOD +good39 = /a*b/ + +# All 4 bad combinations of nested * and + +bad79 = /(a*)*b/ +bad80 = /(a+)*b/ +bad81 = /(a*)+b/ +bad82 = /(a+)+b/ + +# GOOD +good40 = /(a|b)+/ +good41 = /(?:[\s;,"'<>(){}|\[\]@=+*]|:(?![\/\\]))+/ + +# NOT GOOD +bad83 = /^((?:a{|-)|\w\{)+X$/ +bad84 = /^((?:a{0|-)|\w\{\d)+X$/ +bad85 = /^((?:a{0,|-)|\w\{\d,)+X$/ +bad86 = /^((?:a{0,2|-)|\w\{\d,\d)+X$/ + +# GOOD: +good42 = /^((?:a{0,2}|-)|\w\{\d,\d\})+X$/ + +# NOT GOOD +bad87 = /^X(\u0061|a)*Y$/ + +# GOOD +good43 = /^X(\u0061|b)+Y$/ \ No newline at end of file diff --git a/ruby/ql/test/query-tests/security/cwe-1333-polynomial-redos/PolynomialReDoS.expected b/ruby/ql/test/query-tests/security/cwe-1333-polynomial-redos/PolynomialReDoS.expected new file mode 100644 index 000000000000..938758f9db1b --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-1333-polynomial-redos/PolynomialReDoS.expected @@ -0,0 +1,62 @@ +edges +| PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:10:5:10:8 | name | +| PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:11:5:11:8 | name | +| PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:12:5:12:8 | name | +| PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:13:5:13:8 | name | +| PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:14:5:14:8 | name | +| PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:15:5:15:8 | name | +| PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:16:5:16:8 | name | +| PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:17:5:17:8 | name | +| PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:18:5:18:8 | name | +| PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:19:5:19:8 | name | +| PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:20:5:20:8 | name | +| PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:21:5:21:8 | name | +| PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:22:5:22:8 | name | +| PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:23:17:23:20 | name | +| PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:24:18:24:21 | name | +| PolynomialReDoS.rb:27:9:27:14 | call to params : | PolynomialReDoS.rb:28:5:28:5 | a | +| PolynomialReDoS.rb:29:9:29:14 | call to params : | PolynomialReDoS.rb:30:5:30:5 | b | +| PolynomialReDoS.rb:31:9:31:14 | call to params : | PolynomialReDoS.rb:32:5:32:5 | c | +nodes +| PolynomialReDoS.rb:4:12:4:17 | call to params : | semmle.label | call to params : | +| PolynomialReDoS.rb:10:5:10:8 | name | semmle.label | name | +| PolynomialReDoS.rb:11:5:11:8 | name | semmle.label | name | +| PolynomialReDoS.rb:12:5:12:8 | name | semmle.label | name | +| PolynomialReDoS.rb:13:5:13:8 | name | semmle.label | name | +| PolynomialReDoS.rb:14:5:14:8 | name | semmle.label | name | +| PolynomialReDoS.rb:15:5:15:8 | name | semmle.label | name | +| PolynomialReDoS.rb:16:5:16:8 | name | semmle.label | name | +| PolynomialReDoS.rb:17:5:17:8 | name | semmle.label | name | +| PolynomialReDoS.rb:18:5:18:8 | name | semmle.label | name | +| PolynomialReDoS.rb:19:5:19:8 | name | semmle.label | name | +| PolynomialReDoS.rb:20:5:20:8 | name | semmle.label | name | +| PolynomialReDoS.rb:21:5:21:8 | name | semmle.label | name | +| PolynomialReDoS.rb:22:5:22:8 | name | semmle.label | name | +| PolynomialReDoS.rb:23:17:23:20 | name | semmle.label | name | +| PolynomialReDoS.rb:24:18:24:21 | name | semmle.label | name | +| PolynomialReDoS.rb:27:9:27:14 | call to params : | semmle.label | call to params : | +| PolynomialReDoS.rb:28:5:28:5 | a | semmle.label | a | +| PolynomialReDoS.rb:29:9:29:14 | call to params : | semmle.label | call to params : | +| PolynomialReDoS.rb:30:5:30:5 | b | semmle.label | b | +| PolynomialReDoS.rb:31:9:31:14 | call to params : | semmle.label | call to params : | +| PolynomialReDoS.rb:32:5:32:5 | c | semmle.label | c | +subpaths +#select +| PolynomialReDoS.rb:10:5:10:17 | ... =~ ... | PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:10:5:10:8 | name | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:4:12:4:17 | call to params | a user-provided value | +| PolynomialReDoS.rb:11:5:11:17 | ... !~ ... | PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:11:5:11:8 | name | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:4:12:4:17 | call to params | a user-provided value | +| PolynomialReDoS.rb:12:5:12:15 | ...[...] | PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:12:5:12:8 | name | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:4:12:4:17 | call to params | a user-provided value | +| PolynomialReDoS.rb:13:5:13:23 | call to gsub | PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:13:5:13:8 | name | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:4:12:4:17 | call to params | a user-provided value | +| PolynomialReDoS.rb:14:5:14:20 | call to index | PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:14:5:14:8 | name | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:4:12:4:17 | call to params | a user-provided value | +| PolynomialReDoS.rb:15:5:15:20 | call to match | PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:15:5:15:8 | name | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:4:12:4:17 | call to params | a user-provided value | +| PolynomialReDoS.rb:16:5:16:21 | call to match? | PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:16:5:16:8 | name | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:4:12:4:17 | call to params | a user-provided value | +| PolynomialReDoS.rb:17:5:17:24 | call to partition | PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:17:5:17:8 | name | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:4:12:4:17 | call to params | a user-provided value | +| PolynomialReDoS.rb:18:5:18:21 | call to rindex | PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:18:5:18:8 | name | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:4:12:4:17 | call to params | a user-provided value | +| PolynomialReDoS.rb:19:5:19:25 | call to rpartition | PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:19:5:19:8 | name | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:4:12:4:17 | call to params | a user-provided value | +| PolynomialReDoS.rb:20:5:20:19 | call to scan | PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:20:5:20:8 | name | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:4:12:4:17 | call to params | a user-provided value | +| PolynomialReDoS.rb:21:5:21:20 | call to split | PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:21:5:21:8 | name | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:4:12:4:17 | call to params | a user-provided value | +| PolynomialReDoS.rb:22:5:22:22 | call to sub | PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:22:5:22:8 | name | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:4:12:4:17 | call to params | a user-provided value | +| PolynomialReDoS.rb:23:5:23:20 | call to match | PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:23:17:23:20 | name | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:4:12:4:17 | call to params | a user-provided value | +| PolynomialReDoS.rb:24:5:24:21 | call to match? | PolynomialReDoS.rb:4:12:4:17 | call to params : | PolynomialReDoS.rb:24:18:24:21 | name | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:4:12:4:17 | call to params | a user-provided value | +| PolynomialReDoS.rb:28:5:28:21 | call to gsub! | PolynomialReDoS.rb:27:9:27:14 | call to params : | PolynomialReDoS.rb:28:5:28:5 | a | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:27:9:27:14 | call to params | a user-provided value | +| PolynomialReDoS.rb:30:5:30:18 | call to slice! | PolynomialReDoS.rb:29:9:29:14 | call to params : | PolynomialReDoS.rb:30:5:30:5 | b | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:29:9:29:14 | call to params | a user-provided value | +| PolynomialReDoS.rb:32:5:32:20 | call to sub! | PolynomialReDoS.rb:31:9:31:14 | call to params : | PolynomialReDoS.rb:32:5:32:5 | c | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:7:19:7:21 | \\s+ | regular expression | PolynomialReDoS.rb:31:9:31:14 | call to params | a user-provided value | diff --git a/ruby/ql/test/query-tests/security/cwe-1333-polynomial-redos/PolynomialReDoS.qlref b/ruby/ql/test/query-tests/security/cwe-1333-polynomial-redos/PolynomialReDoS.qlref new file mode 100644 index 000000000000..5807dc56fa07 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-1333-polynomial-redos/PolynomialReDoS.qlref @@ -0,0 +1 @@ +queries/security/cwe-1333/PolynomialReDoS.ql diff --git a/ruby/ql/test/query-tests/security/cwe-1333-polynomial-redos/PolynomialReDoS.rb b/ruby/ql/test/query-tests/security/cwe-1333-polynomial-redos/PolynomialReDoS.rb new file mode 100644 index 000000000000..55db95555841 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-1333-polynomial-redos/PolynomialReDoS.rb @@ -0,0 +1,42 @@ +class FooController < ActionController::Base + def some_request_handler + # A source for the data-flow query (i.e. a remote flow source) + name = params[:name] + + # A vulnerable regex + regex = /^\s+|\s+$/ + + # Various sinks that match the source against the regex + name =~ regex # NOT GOOD + name !~ regex # NOT GOOD + name[regex] # NOT GOOD + name.gsub regex, '' # NOT GOOD + name.index regex # NOT GOOD + name.match regex # NOT GOOD + name.match? regex # NOT GOOD + name.partition regex # NOT GOOD + name.rindex regex # NOT GOOD + name.rpartition regex # NOT GOOD + name.scan regex # NOT GOOD + name.split regex # NOT GOOD + name.sub regex, '' # NOT GOOD + regex.match name # NOT GOOD + regex.match? name # NOT GOOD + + # Destructive variants + a = params[:b] + a.gsub! regex, '' # NOT GOOD + b = params[:a] + b.slice! regex # NOT GOOD + c = params[:c] + c.sub! regex, '' # NOT GOOD + + # GOOD - guarded by a string length check + if name.length < 1024 + name.gsub regex, '' + end + + # GOOD - regex does not suffer from polynomial backtracking (regression test) + params[:foo] =~ /\A[bc].*\Z/ + end +end diff --git a/ruby/ql/test/query-tests/security/cwe-502/UnsafeDeserialization.expected b/ruby/ql/test/query-tests/security/cwe-502/UnsafeDeserialization.expected new file mode 100644 index 000000000000..054ec8bfa661 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-502/UnsafeDeserialization.expected @@ -0,0 +1,24 @@ +edges +| UnsafeDeserialization.rb:8:39:8:44 | call to params : | UnsafeDeserialization.rb:9:27:9:41 | serialized_data | +| UnsafeDeserialization.rb:14:39:14:44 | call to params : | UnsafeDeserialization.rb:15:30:15:44 | serialized_data | +| UnsafeDeserialization.rb:20:17:20:22 | call to params : | UnsafeDeserialization.rb:21:24:21:32 | json_data | +| UnsafeDeserialization.rb:26:17:26:22 | call to params : | UnsafeDeserialization.rb:27:27:27:35 | json_data | +| UnsafeDeserialization.rb:38:17:38:22 | call to params : | UnsafeDeserialization.rb:39:24:39:32 | yaml_data | +nodes +| UnsafeDeserialization.rb:8:39:8:44 | call to params : | semmle.label | call to params : | +| UnsafeDeserialization.rb:9:27:9:41 | serialized_data | semmle.label | serialized_data | +| UnsafeDeserialization.rb:14:39:14:44 | call to params : | semmle.label | call to params : | +| UnsafeDeserialization.rb:15:30:15:44 | serialized_data | semmle.label | serialized_data | +| UnsafeDeserialization.rb:20:17:20:22 | call to params : | semmle.label | call to params : | +| UnsafeDeserialization.rb:21:24:21:32 | json_data | semmle.label | json_data | +| UnsafeDeserialization.rb:26:17:26:22 | call to params : | semmle.label | call to params : | +| UnsafeDeserialization.rb:27:27:27:35 | json_data | semmle.label | json_data | +| UnsafeDeserialization.rb:38:17:38:22 | call to params : | semmle.label | call to params : | +| UnsafeDeserialization.rb:39:24:39:32 | yaml_data | semmle.label | yaml_data | +subpaths +#select +| UnsafeDeserialization.rb:9:27:9:41 | serialized_data | UnsafeDeserialization.rb:8:39:8:44 | call to params : | UnsafeDeserialization.rb:9:27:9:41 | serialized_data | Unsafe deserialization of $@. | UnsafeDeserialization.rb:8:39:8:44 | call to params | user input | +| UnsafeDeserialization.rb:15:30:15:44 | serialized_data | UnsafeDeserialization.rb:14:39:14:44 | call to params : | UnsafeDeserialization.rb:15:30:15:44 | serialized_data | Unsafe deserialization of $@. | UnsafeDeserialization.rb:14:39:14:44 | call to params | user input | +| UnsafeDeserialization.rb:21:24:21:32 | json_data | UnsafeDeserialization.rb:20:17:20:22 | call to params : | UnsafeDeserialization.rb:21:24:21:32 | json_data | Unsafe deserialization of $@. | UnsafeDeserialization.rb:20:17:20:22 | call to params | user input | +| UnsafeDeserialization.rb:27:27:27:35 | json_data | UnsafeDeserialization.rb:26:17:26:22 | call to params : | UnsafeDeserialization.rb:27:27:27:35 | json_data | Unsafe deserialization of $@. | UnsafeDeserialization.rb:26:17:26:22 | call to params | user input | +| UnsafeDeserialization.rb:39:24:39:32 | yaml_data | UnsafeDeserialization.rb:38:17:38:22 | call to params : | UnsafeDeserialization.rb:39:24:39:32 | yaml_data | Unsafe deserialization of $@. | UnsafeDeserialization.rb:38:17:38:22 | call to params | user input | diff --git a/ruby/ql/test/query-tests/security/cwe-502/UnsafeDeserialization.qlref b/ruby/ql/test/query-tests/security/cwe-502/UnsafeDeserialization.qlref new file mode 100644 index 000000000000..55f7c440b46e --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-502/UnsafeDeserialization.qlref @@ -0,0 +1 @@ +queries/security/cwe-502/UnsafeDeserialization.ql diff --git a/ruby/ql/test/query-tests/security/cwe-502/UnsafeDeserialization.rb b/ruby/ql/test/query-tests/security/cwe-502/UnsafeDeserialization.rb new file mode 100644 index 000000000000..dc0659b11af1 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-502/UnsafeDeserialization.rb @@ -0,0 +1,47 @@ +require "base64" +require "json" +require "yaml" + +class UsersController < ActionController::Base + # BAD + def route0 + serialized_data = Base64.decode64 params[:key] + object = Marshal.load serialized_data + end + + # BAD + def route1 + serialized_data = Base64.decode64 params[:key] + object = Marshal.restore serialized_data + end + + # BAD + def route2 + json_data = params[:key] + object = JSON.load json_data + end + + # BAD + def route3 + json_data = params[:key] + object = JSON.restore json_data + end + + # GOOD - JSON.parse is safe to use on untrusted data + def route4 + json_data = params[:key] + object = JSON.parse json_data + end + + # BAD + def route5 + yaml_data = params[:key] + object = YAML.load yaml_data + end + + # GOOD + def route6 + yaml_data = params[:key] + object = YAML.safe_load yaml_data + end +end diff --git a/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.expected b/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.expected new file mode 100644 index 000000000000..2684c3ff1805 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.expected @@ -0,0 +1,31 @@ +edges +| UrlRedirect.rb:9:17:9:22 | call to params : | UrlRedirect.rb:9:17:9:28 | ...[...] | +| UrlRedirect.rb:14:17:14:22 | call to params : | UrlRedirect.rb:14:17:14:43 | call to fetch | +| UrlRedirect.rb:19:17:19:22 | call to params : | UrlRedirect.rb:19:17:19:37 | call to to_unsafe_hash | +| UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:24:17:24:37 | call to filter_params | +| UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:56:21:56:32 | input_params : | +| UrlRedirect.rb:34:20:34:25 | call to params : | UrlRedirect.rb:34:17:34:37 | "#{...}/foo" | +| UrlRedirect.rb:56:21:56:32 | input_params : | UrlRedirect.rb:57:5:57:29 | call to permit : | +nodes +| UrlRedirect.rb:4:17:4:22 | call to params | semmle.label | call to params | +| UrlRedirect.rb:9:17:9:22 | call to params : | semmle.label | call to params : | +| UrlRedirect.rb:9:17:9:28 | ...[...] | semmle.label | ...[...] | +| UrlRedirect.rb:14:17:14:22 | call to params : | semmle.label | call to params : | +| UrlRedirect.rb:14:17:14:43 | call to fetch | semmle.label | call to fetch | +| UrlRedirect.rb:19:17:19:22 | call to params : | semmle.label | call to params : | +| UrlRedirect.rb:19:17:19:37 | call to to_unsafe_hash | semmle.label | call to to_unsafe_hash | +| UrlRedirect.rb:24:17:24:37 | call to filter_params | semmle.label | call to filter_params | +| UrlRedirect.rb:24:31:24:36 | call to params : | semmle.label | call to params : | +| UrlRedirect.rb:34:17:34:37 | "#{...}/foo" | semmle.label | "#{...}/foo" | +| UrlRedirect.rb:34:20:34:25 | call to params : | semmle.label | call to params : | +| UrlRedirect.rb:56:21:56:32 | input_params : | semmle.label | input_params : | +| UrlRedirect.rb:57:5:57:29 | call to permit : | semmle.label | call to permit : | +subpaths +| UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:56:21:56:32 | input_params : | UrlRedirect.rb:57:5:57:29 | call to permit : | UrlRedirect.rb:24:17:24:37 | call to filter_params : | +#select +| UrlRedirect.rb:4:17:4:22 | call to params | UrlRedirect.rb:4:17:4:22 | call to params | UrlRedirect.rb:4:17:4:22 | call to params | Untrusted URL redirection due to $@. | UrlRedirect.rb:4:17:4:22 | call to params | a user-provided value | +| UrlRedirect.rb:9:17:9:28 | ...[...] | UrlRedirect.rb:9:17:9:22 | call to params : | UrlRedirect.rb:9:17:9:28 | ...[...] | Untrusted URL redirection due to $@. | UrlRedirect.rb:9:17:9:22 | call to params | a user-provided value | +| UrlRedirect.rb:14:17:14:43 | call to fetch | UrlRedirect.rb:14:17:14:22 | call to params : | UrlRedirect.rb:14:17:14:43 | call to fetch | Untrusted URL redirection due to $@. | UrlRedirect.rb:14:17:14:22 | call to params | a user-provided value | +| UrlRedirect.rb:19:17:19:37 | call to to_unsafe_hash | UrlRedirect.rb:19:17:19:22 | call to params : | UrlRedirect.rb:19:17:19:37 | call to to_unsafe_hash | Untrusted URL redirection due to $@. | UrlRedirect.rb:19:17:19:22 | call to params | a user-provided value | +| UrlRedirect.rb:24:17:24:37 | call to filter_params | UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:24:17:24:37 | call to filter_params | Untrusted URL redirection due to $@. | UrlRedirect.rb:24:31:24:36 | call to params | a user-provided value | +| UrlRedirect.rb:34:17:34:37 | "#{...}/foo" | UrlRedirect.rb:34:20:34:25 | call to params : | UrlRedirect.rb:34:17:34:37 | "#{...}/foo" | Untrusted URL redirection due to $@. | UrlRedirect.rb:34:20:34:25 | call to params | a user-provided value | diff --git a/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.qlref b/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.qlref new file mode 100644 index 000000000000..422dc00837aa --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.qlref @@ -0,0 +1 @@ +queries/security/cwe-601/UrlRedirect.ql diff --git a/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.rb b/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.rb new file mode 100644 index 000000000000..c5bfc6d0093c --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-601/UrlRedirect.rb @@ -0,0 +1,59 @@ +class UsersController < ActionController::Base + # BAD + def route1 + redirect_to params + end + + # BAD + def route2 + redirect_to params[:key] + end + + # BAD + def route3 + redirect_to params.fetch(:specific_arg) + end + + # BAD + def route4 + redirect_to params.to_unsafe_hash + end + + # BAD + def route5 + redirect_to filter_params(params) + end + + # GOOD + def route6 + redirect_to "/foo/#{params[:key]}" + end + + # BAD + def route7 + redirect_to "#{params[:key]}/foo" + end + + # GOOD + def route8 + key = params[:key] + if key == "foo" + redirect_to key + else + redirect_to "/default" + end + end + + # GOOD + # Technically vulnerable, but we assume this is a handler for a POST request, + # so can't be triggered by following a link. + def create + redirect_to params[:key] + end + + private + + def filter_params(input_params) + input_params.permit(:key) + end +end diff --git a/ruby/ql/test/query-tests/security/cwe-732/FilePermissions.rb b/ruby/ql/test/query-tests/security/cwe-732/FilePermissions.rb new file mode 100644 index 000000000000..305bdb2d1470 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-732/FilePermissions.rb @@ -0,0 +1,73 @@ +require "fileutils" + +def run_chmod_1(filename) + # BAD: sets file as world writable + FileUtils.chmod 0222, filename + # BAD: sets file as world writable + FileUtils.chmod 0622, filename + # BAD: sets file as world readable + FileUtils.chmod 0755, filename + # BAD: sets file as world readable + writable + FileUtils.chmod 0777, filename +end + +module DummyModule + def chmod(mode, list, options = {} ) + list + end +end + +def run_chmod_2(filename) + foo = File + bar = foo + baz = DummyModule + # GOOD: DummyModule is not a known class that performs file permission modifications + baz.chmod 0755, filename + baz = bar + # BAD: sets file as world readable + baz.chmod 0755, filename +end + +def run_chmod_3(filename) + # TODO: we currently miss this + foo = FileUtils + bar, baz = foo, 7 + # BAD: sets file as world readable + bar.chmod 0755, filename +end + +def run_chmod_4(filename) + # GOOD: no group/world access + FileUtils.chmod 0700, filename + # GOOD: group/world execute bit only + FileUtils.chmod 0711, filename + # GOOD: world execute bit only + FileUtils.chmod 0701, filename + # GOOD: group execute bit only + FileUtils.chmod 0710, filename +end + +def run_chmod_5(filename) + perm = 0777 + # BAD: sets world rwx + FileUtils.chmod perm, filename + perm2 = perm + # BAD: sets world rwx + FileUtils.chmod perm2, filename + + perm = "u=wrx,g=rwx,o=x" + perm2 = perm + # BAD: sets group rwx + FileUtils.chmod perm2, filename + # BAD: sets file as world readable + FileUtils.chmod "u=rwx,o+r", filename + # GOOD: sets file as group/world unreadable + FileUtils.chmod "u=rwx,go-r", filename + # BAD: sets group/world as +rw + FileUtils.chmod "a+rw", filename +end + +def run_chmod_R(filename) + # BAD: sets file as world readable + FileUtils.chmod_R 0755, filename +end diff --git a/ruby/ql/test/query-tests/security/cwe-732/WeakFilePermissions.expected b/ruby/ql/test/query-tests/security/cwe-732/WeakFilePermissions.expected new file mode 100644 index 000000000000..f7335edd5e01 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-732/WeakFilePermissions.expected @@ -0,0 +1,31 @@ +edges +| FilePermissions.rb:51:10:51:13 | 0777 : | FilePermissions.rb:53:19:53:22 | perm | +| FilePermissions.rb:51:10:51:13 | 0777 : | FilePermissions.rb:56:19:56:23 | perm2 | +| FilePermissions.rb:58:10:58:26 | "u=wrx,g=rwx,o=x" : | FilePermissions.rb:61:19:61:23 | perm2 | +nodes +| FilePermissions.rb:5:19:5:22 | 0222 | semmle.label | 0222 | +| FilePermissions.rb:7:19:7:22 | 0622 | semmle.label | 0622 | +| FilePermissions.rb:9:19:9:22 | 0755 | semmle.label | 0755 | +| FilePermissions.rb:11:19:11:22 | 0777 | semmle.label | 0777 | +| FilePermissions.rb:28:13:28:16 | 0755 | semmle.label | 0755 | +| FilePermissions.rb:51:10:51:13 | 0777 : | semmle.label | 0777 : | +| FilePermissions.rb:53:19:53:22 | perm | semmle.label | perm | +| FilePermissions.rb:56:19:56:23 | perm2 | semmle.label | perm2 | +| FilePermissions.rb:58:10:58:26 | "u=wrx,g=rwx,o=x" : | semmle.label | "u=wrx,g=rwx,o=x" : | +| FilePermissions.rb:61:19:61:23 | perm2 | semmle.label | perm2 | +| FilePermissions.rb:63:19:63:29 | "u=rwx,o+r" | semmle.label | "u=rwx,o+r" | +| FilePermissions.rb:67:19:67:24 | "a+rw" | semmle.label | "a+rw" | +| FilePermissions.rb:72:21:72:24 | 0755 | semmle.label | 0755 | +subpaths +#select +| FilePermissions.rb:5:19:5:22 | 0222 | FilePermissions.rb:5:19:5:22 | 0222 | FilePermissions.rb:5:19:5:22 | 0222 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:5:3:5:32 | call to chmod | call to chmod | FilePermissions.rb:5:19:5:22 | 0222 | 0222 | +| FilePermissions.rb:7:19:7:22 | 0622 | FilePermissions.rb:7:19:7:22 | 0622 | FilePermissions.rb:7:19:7:22 | 0622 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:7:3:7:32 | call to chmod | call to chmod | FilePermissions.rb:7:19:7:22 | 0622 | 0622 | +| FilePermissions.rb:9:19:9:22 | 0755 | FilePermissions.rb:9:19:9:22 | 0755 | FilePermissions.rb:9:19:9:22 | 0755 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:9:3:9:32 | call to chmod | call to chmod | FilePermissions.rb:9:19:9:22 | 0755 | 0755 | +| FilePermissions.rb:11:19:11:22 | 0777 | FilePermissions.rb:11:19:11:22 | 0777 | FilePermissions.rb:11:19:11:22 | 0777 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:11:3:11:32 | call to chmod | call to chmod | FilePermissions.rb:11:19:11:22 | 0777 | 0777 | +| FilePermissions.rb:28:13:28:16 | 0755 | FilePermissions.rb:28:13:28:16 | 0755 | FilePermissions.rb:28:13:28:16 | 0755 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:28:3:28:26 | call to chmod | call to chmod | FilePermissions.rb:28:13:28:16 | 0755 | 0755 | +| FilePermissions.rb:51:10:51:13 | 0777 | FilePermissions.rb:51:10:51:13 | 0777 : | FilePermissions.rb:53:19:53:22 | perm | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:53:3:53:32 | call to chmod | call to chmod | FilePermissions.rb:51:10:51:13 | 0777 | 0777 | +| FilePermissions.rb:51:10:51:13 | 0777 | FilePermissions.rb:51:10:51:13 | 0777 : | FilePermissions.rb:56:19:56:23 | perm2 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:56:3:56:33 | call to chmod | call to chmod | FilePermissions.rb:51:10:51:13 | 0777 | 0777 | +| FilePermissions.rb:58:10:58:26 | "u=wrx,g=rwx,o=x" | FilePermissions.rb:58:10:58:26 | "u=wrx,g=rwx,o=x" : | FilePermissions.rb:61:19:61:23 | perm2 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:61:3:61:33 | call to chmod | call to chmod | FilePermissions.rb:58:10:58:26 | "u=wrx,g=rwx,o=x" | "u=wrx,g=rwx,o=x" | +| FilePermissions.rb:63:19:63:29 | "u=rwx,o+r" | FilePermissions.rb:63:19:63:29 | "u=rwx,o+r" | FilePermissions.rb:63:19:63:29 | "u=rwx,o+r" | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:63:3:63:39 | call to chmod | call to chmod | FilePermissions.rb:63:19:63:29 | "u=rwx,o+r" | "u=rwx,o+r" | +| FilePermissions.rb:67:19:67:24 | "a+rw" | FilePermissions.rb:67:19:67:24 | "a+rw" | FilePermissions.rb:67:19:67:24 | "a+rw" | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:67:3:67:34 | call to chmod | call to chmod | FilePermissions.rb:67:19:67:24 | "a+rw" | "a+rw" | +| FilePermissions.rb:72:21:72:24 | 0755 | FilePermissions.rb:72:21:72:24 | 0755 | FilePermissions.rb:72:21:72:24 | 0755 | Overly permissive mask in $@ sets file to $@. | FilePermissions.rb:72:3:72:34 | call to chmod_R | call to chmod_R | FilePermissions.rb:72:21:72:24 | 0755 | 0755 | diff --git a/ruby/ql/test/query-tests/security/cwe-732/WeakFilePermissions.qlref b/ruby/ql/test/query-tests/security/cwe-732/WeakFilePermissions.qlref new file mode 100644 index 000000000000..bf19b31509d5 --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-732/WeakFilePermissions.qlref @@ -0,0 +1 @@ +queries/security/cwe-732/WeakFilePermissions.ql diff --git a/ruby/ql/test/query-tests/security/cwe-798/HardcodedCredentials.expected b/ruby/ql/test/query-tests/security/cwe-798/HardcodedCredentials.expected new file mode 100644 index 000000000000..2d64f6ca816c --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-798/HardcodedCredentials.expected @@ -0,0 +1,31 @@ +edges +| HardcodedCredentials.rb:12:19:12:64 | "4NQX/CqB5Ae98zFUmwj1DMpF7azsh..." : | HardcodedCredentials.rb:1:23:1:30 | password | +| HardcodedCredentials.rb:18:19:18:72 | ... + ... : | HardcodedCredentials.rb:1:23:1:30 | password | +| HardcodedCredentials.rb:18:27:18:72 | "ogH6qSYWGdbR/2WOGYa7eZ/tObL+G..." : | HardcodedCredentials.rb:18:19:18:72 | ... + ... : | +| HardcodedCredentials.rb:20:11:20:76 | "3jOe7sXKX6Tx52qHWUVqh2t9LNsE+..." : | HardcodedCredentials.rb:23:19:23:20 | pw : | +| HardcodedCredentials.rb:21:12:21:37 | "4fQuzXef4f2yow8KWvIJTA==" : | HardcodedCredentials.rb:23:19:23:20 | pw : | +| HardcodedCredentials.rb:23:19:23:20 | pw : | HardcodedCredentials.rb:1:23:1:30 | password | +| HardcodedCredentials.rb:38:40:38:85 | "kdW/xVhiv6y1fQQNevDpUaq+2rfPK..." : | HardcodedCredentials.rb:31:18:31:23 | passwd | +nodes +| HardcodedCredentials.rb:1:23:1:30 | password | semmle.label | password | +| HardcodedCredentials.rb:4:20:4:65 | "xwjVWdfzfRlbcgKkbSfG/xSrUeHYq..." | semmle.label | "xwjVWdfzfRlbcgKkbSfG/xSrUeHYq..." | +| HardcodedCredentials.rb:8:30:8:75 | "X6BLgRWSAtAWG/GaHS+WGGW2K7zZF..." | semmle.label | "X6BLgRWSAtAWG/GaHS+WGGW2K7zZF..." | +| HardcodedCredentials.rb:12:19:12:64 | "4NQX/CqB5Ae98zFUmwj1DMpF7azsh..." : | semmle.label | "4NQX/CqB5Ae98zFUmwj1DMpF7azsh..." : | +| HardcodedCredentials.rb:15:30:15:75 | "WLC17dLQ9P8YlQvqm77qplOMm5pd1..." | semmle.label | "WLC17dLQ9P8YlQvqm77qplOMm5pd1..." | +| HardcodedCredentials.rb:18:19:18:72 | ... + ... : | semmle.label | ... + ... : | +| HardcodedCredentials.rb:18:27:18:72 | "ogH6qSYWGdbR/2WOGYa7eZ/tObL+G..." : | semmle.label | "ogH6qSYWGdbR/2WOGYa7eZ/tObL+G..." : | +| HardcodedCredentials.rb:20:11:20:76 | "3jOe7sXKX6Tx52qHWUVqh2t9LNsE+..." : | semmle.label | "3jOe7sXKX6Tx52qHWUVqh2t9LNsE+..." : | +| HardcodedCredentials.rb:21:12:21:37 | "4fQuzXef4f2yow8KWvIJTA==" : | semmle.label | "4fQuzXef4f2yow8KWvIJTA==" : | +| HardcodedCredentials.rb:23:19:23:20 | pw : | semmle.label | pw : | +| HardcodedCredentials.rb:31:18:31:23 | passwd | semmle.label | passwd | +| HardcodedCredentials.rb:38:40:38:85 | "kdW/xVhiv6y1fQQNevDpUaq+2rfPK..." : | semmle.label | "kdW/xVhiv6y1fQQNevDpUaq+2rfPK..." : | +subpaths +#select +| HardcodedCredentials.rb:4:20:4:65 | "xwjVWdfzfRlbcgKkbSfG/xSrUeHYq..." | HardcodedCredentials.rb:4:20:4:65 | "xwjVWdfzfRlbcgKkbSfG/xSrUeHYq..." | HardcodedCredentials.rb:4:20:4:65 | "xwjVWdfzfRlbcgKkbSfG/xSrUeHYq..." | Use of $@. | HardcodedCredentials.rb:4:20:4:65 | "xwjVWdfzfRlbcgKkbSfG/xSrUeHYq..." | hardcoded credentials | +| HardcodedCredentials.rb:8:30:8:75 | "X6BLgRWSAtAWG/GaHS+WGGW2K7zZF..." | HardcodedCredentials.rb:8:30:8:75 | "X6BLgRWSAtAWG/GaHS+WGGW2K7zZF..." | HardcodedCredentials.rb:8:30:8:75 | "X6BLgRWSAtAWG/GaHS+WGGW2K7zZF..." | Use of $@. | HardcodedCredentials.rb:8:30:8:75 | "X6BLgRWSAtAWG/GaHS+WGGW2K7zZF..." | hardcoded credentials | +| HardcodedCredentials.rb:12:19:12:64 | "4NQX/CqB5Ae98zFUmwj1DMpF7azsh..." | HardcodedCredentials.rb:12:19:12:64 | "4NQX/CqB5Ae98zFUmwj1DMpF7azsh..." : | HardcodedCredentials.rb:1:23:1:30 | password | Use of $@. | HardcodedCredentials.rb:12:19:12:64 | "4NQX/CqB5Ae98zFUmwj1DMpF7azsh..." | hardcoded credentials | +| HardcodedCredentials.rb:15:30:15:75 | "WLC17dLQ9P8YlQvqm77qplOMm5pd1..." | HardcodedCredentials.rb:15:30:15:75 | "WLC17dLQ9P8YlQvqm77qplOMm5pd1..." | HardcodedCredentials.rb:15:30:15:75 | "WLC17dLQ9P8YlQvqm77qplOMm5pd1..." | Use of $@. | HardcodedCredentials.rb:15:30:15:75 | "WLC17dLQ9P8YlQvqm77qplOMm5pd1..." | hardcoded credentials | +| HardcodedCredentials.rb:18:27:18:72 | "ogH6qSYWGdbR/2WOGYa7eZ/tObL+G..." | HardcodedCredentials.rb:18:27:18:72 | "ogH6qSYWGdbR/2WOGYa7eZ/tObL+G..." : | HardcodedCredentials.rb:1:23:1:30 | password | Use of $@. | HardcodedCredentials.rb:18:27:18:72 | "ogH6qSYWGdbR/2WOGYa7eZ/tObL+G..." | hardcoded credentials | +| HardcodedCredentials.rb:20:11:20:76 | "3jOe7sXKX6Tx52qHWUVqh2t9LNsE+..." | HardcodedCredentials.rb:20:11:20:76 | "3jOe7sXKX6Tx52qHWUVqh2t9LNsE+..." : | HardcodedCredentials.rb:1:23:1:30 | password | Use of $@. | HardcodedCredentials.rb:20:11:20:76 | "3jOe7sXKX6Tx52qHWUVqh2t9LNsE+..." | hardcoded credentials | +| HardcodedCredentials.rb:21:12:21:37 | "4fQuzXef4f2yow8KWvIJTA==" | HardcodedCredentials.rb:21:12:21:37 | "4fQuzXef4f2yow8KWvIJTA==" : | HardcodedCredentials.rb:1:23:1:30 | password | Use of $@. | HardcodedCredentials.rb:21:12:21:37 | "4fQuzXef4f2yow8KWvIJTA==" | hardcoded credentials | +| HardcodedCredentials.rb:38:40:38:85 | "kdW/xVhiv6y1fQQNevDpUaq+2rfPK..." | HardcodedCredentials.rb:38:40:38:85 | "kdW/xVhiv6y1fQQNevDpUaq+2rfPK..." : | HardcodedCredentials.rb:31:18:31:23 | passwd | Use of $@. | HardcodedCredentials.rb:38:40:38:85 | "kdW/xVhiv6y1fQQNevDpUaq+2rfPK..." | hardcoded credentials | diff --git a/ruby/ql/test/query-tests/security/cwe-798/HardcodedCredentials.qlref b/ruby/ql/test/query-tests/security/cwe-798/HardcodedCredentials.qlref new file mode 100644 index 000000000000..e65b7754872d --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-798/HardcodedCredentials.qlref @@ -0,0 +1 @@ +queries/security/cwe-798/HardcodedCredentials.ql diff --git a/ruby/ql/test/query-tests/security/cwe-798/HardcodedCredentials.rb b/ruby/ql/test/query-tests/security/cwe-798/HardcodedCredentials.rb new file mode 100644 index 000000000000..0d9fad1c9b9b --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-798/HardcodedCredentials.rb @@ -0,0 +1,41 @@ +def authenticate(uid, password, cert: nil) + if cert != nil then + # comparison with hardcoded credential + return cert == "xwjVWdfzfRlbcgKkbSfG/xSrUeHYqxPgz9WKN3Yow1o=" + end + + # comparison with hardcoded credential + uid == 123 and password == "X6BLgRWSAtAWG/GaHS+WGGW2K7zZFTAjJ54fGSudHJk=" +end + +# call with hardcoded credential as argument +authenticate(123, "4NQX/CqB5Ae98zFUmwj1DMpF7azshxSvb0Jo4gIFmIQ=") + +# call with hardcoded credential as argument +authenticate(456, nil, cert: "WLC17dLQ9P8YlQvqm77qplOMm5pd1q25Q2onWqu78JI=") + +# concatenation involving literal +authenticate(789, "pw:" + "ogH6qSYWGdbR/2WOGYa7eZ/tObL+GtqDPx6q37BTTRQ=") + +pw_left = "3jOe7sXKX6Tx52qHWUVqh2t9LNsE+ZXFj2qw6asRARTV2deAXFKkMTVOoaFYom1Q" +pw_right = "4fQuzXef4f2yow8KWvIJTA==" +pw = pw_left + pw_right +authenticate(999, pw) + +passwd = gets.chomp +# call with hardcoded credential-like value, but not to a potential credential sink (should not be flagged) +authenticate("gowLsSGfPbh/ZS60k+LQQBhcq1tsh/YgbvNmDauQr5Q=", passwd) + +module Passwords + class KnownPasswords + def include?(passwd) + passwd == "foo" + end + end +end + +# Call to object method +Passwords::KnownPasswords.new.include?("kdW/xVhiv6y1fQQNevDpUaq+2rfPKfh+teE/45zS7bc=") + +# Call to unrelated method with same name (should not be flagged) +"foobar".include?("foo") diff --git a/ruby/ql/test/query-tests/summary/LinesOfCode.expected b/ruby/ql/test/query-tests/summary/LinesOfCode.expected new file mode 100644 index 000000000000..f17cce5aa70a --- /dev/null +++ b/ruby/ql/test/query-tests/summary/LinesOfCode.expected @@ -0,0 +1 @@ +| 9 | diff --git a/ruby/ql/test/query-tests/summary/LinesOfCode.qlref b/ruby/ql/test/query-tests/summary/LinesOfCode.qlref new file mode 100644 index 000000000000..84278cfc96b7 --- /dev/null +++ b/ruby/ql/test/query-tests/summary/LinesOfCode.qlref @@ -0,0 +1 @@ +queries/summary/LinesOfCode.ql \ No newline at end of file diff --git a/ruby/ql/test/query-tests/summary/LinesOfUserCode.expected b/ruby/ql/test/query-tests/summary/LinesOfUserCode.expected new file mode 100644 index 000000000000..69f8503f9661 --- /dev/null +++ b/ruby/ql/test/query-tests/summary/LinesOfUserCode.expected @@ -0,0 +1 @@ +| 5 | diff --git a/ruby/ql/test/query-tests/summary/LinesOfUserCode.qlref b/ruby/ql/test/query-tests/summary/LinesOfUserCode.qlref new file mode 100644 index 000000000000..4114db632a26 --- /dev/null +++ b/ruby/ql/test/query-tests/summary/LinesOfUserCode.qlref @@ -0,0 +1 @@ +queries/summary/LinesOfUserCode.ql \ No newline at end of file diff --git a/ruby/ql/test/query-tests/summary/src/foo.rb b/ruby/ql/test/query-tests/summary/src/foo.rb new file mode 100644 index 000000000000..2f0f2acc09a9 --- /dev/null +++ b/ruby/ql/test/query-tests/summary/src/foo.rb @@ -0,0 +1,11 @@ +# comment + +def hello + p "hello foo" +end + +# another one + +hello + +p "more code here" diff --git a/ruby/ql/test/query-tests/summary/src/vendor/cache/lib.rb b/ruby/ql/test/query-tests/summary/src/vendor/cache/lib.rb new file mode 100644 index 000000000000..f75db7941dba --- /dev/null +++ b/ruby/ql/test/query-tests/summary/src/vendor/cache/lib.rb @@ -0,0 +1,9 @@ +# comment + +def hello + p "hello lib" +end + +# another one + +hello diff --git a/ruby/scripts/create-extractor-pack.ps1 b/ruby/scripts/create-extractor-pack.ps1 new file mode 100644 index 000000000000..43546aa90568 --- /dev/null +++ b/ruby/scripts/create-extractor-pack.ps1 @@ -0,0 +1,12 @@ +cargo build --release + +cargo run --release -p ruby-generator -- --dbscheme ql/lib/ruby.dbscheme --library ql/lib/codeql/ruby/ast/internal/TreeSitter.qll +codeql query format -i ql\lib\codeql/ruby\ast\internal\TreeSitter.qll + +rm -Recurse -Force extractor-pack +mkdir extractor-pack | Out-Null +cp codeql-extractor.yml, ql\lib\ruby.dbscheme, ql\lib\ruby.dbscheme.stats extractor-pack +cp -Recurse tools extractor-pack +mkdir extractor-pack\tools\win64 | Out-Null +cp target\release\ruby-extractor.exe extractor-pack\tools\win64\extractor.exe +cp target\release\ruby-autobuilder.exe extractor-pack\tools\win64\autobuilder.exe diff --git a/ruby/scripts/create-extractor-pack.sh b/ruby/scripts/create-extractor-pack.sh new file mode 100755 index 000000000000..756358902af4 --- /dev/null +++ b/ruby/scripts/create-extractor-pack.sh @@ -0,0 +1,23 @@ +#!/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 ruby-generator -- --dbscheme ql/lib/ruby.dbscheme --library ql/lib/codeql/ruby/ast/internal/TreeSitter.qll +codeql query format -i ql/lib/codeql/ruby/ast/internal/TreeSitter.qll + +rm -rf extractor-pack +mkdir -p extractor-pack +cp -r codeql-extractor.yml tools ql/lib/ruby.dbscheme ql/lib/ruby.dbscheme.stats extractor-pack/ +mkdir -p extractor-pack/tools/${platform} +cp target/release/ruby-extractor extractor-pack/tools/${platform}/extractor +cp target/release/ruby-autobuilder extractor-pack/tools/${platform}/autobuilder diff --git a/ruby/scripts/merge_stats.py b/ruby/scripts/merge_stats.py new file mode 100644 index 000000000000..d2cbe0a3e644 --- /dev/null +++ b/ruby/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/ruby/scripts/prepare-db-upgrade.sh b/ruby/scripts/prepare-db-upgrade.sh new file mode 100755 index 000000000000..4cb1957ef85c --- /dev/null +++ b/ruby/scripts/prepare-db-upgrade.sh @@ -0,0 +1,106 @@ +#!/bin/sh +# +# Prepare the upgrade script directory for a Ruby database schema upgrade. + +set -e +set -u + +app_name="$(basename "$0")" + +usage() +{ + exit_code="$1" + shift + + cat >&2 <]" + +--prev-hash + Hash/branch to use to get SHA1 for previous DB scheme. + Default: origin/main + +Must be run within the git repo needing an update. +EOF + exit "${exit_code}" +} + +prev_hash="origin/main" + +while [ $# -gt 0 ]; do + case "$1" in + -x) + set -x + ;; + -h | --help) + usage 0 + ;; + --prev-hash) + if [ $# -eq 1 ]; then + usage 2 "--prev-hash requires Commit/Branch option" + fi + shift + prev_hash="$1" + ;; + --) + shift + break + ;; + -*) + usage 2 "Unrecognised option: $1" + ;; + *) + break + ;; + esac + shift +done + +if [ $# -gt 0 ]; then + usage 2 "Unrecognised operand: $1" +fi + +scheme_file="ql/lib/ruby.dbscheme" +upgrade_root="ql/lib/upgrades" + +check_hash_valid() +{ + if [ ${#2} -ne 40 ]; then + echo "Did not get expected $1 hash: $2" >&2 + exit 2 + fi +} + +# Get the hash of the previous and current DB Schema files +prev_hash="$(git show "${prev_hash}:${scheme_file}" | git hash-object --stdin)" +check_hash_valid previous "${prev_hash}" +current_hash="$(git hash-object "${scheme_file}")" +check_hash_valid current "${current_hash}" +if [ "${current_hash}" = "${prev_hash}" ]; then + echo "No work to be done." + exit +fi + +# Copy current and new dbscheme into the upgrade dir +upgradedir="${upgrade_root}/${prev_hash}" +mkdir -p "${upgradedir}" + +cp "${scheme_file}" "${upgradedir}" +git cat-file blob "${prev_hash}" > "${upgradedir}/old.dbscheme" + +# Create the template upgrade.properties file. +cat < "${upgradedir}/upgrade.properties" +description: +compatibility: full|backwards|partial|breaking +EOF + +# Tell user what we've done +cat <