mirror of
https://github.com/github/codeql.git
synced 2026-04-28 10:15:14 +02:00
Merge branch 'main' into fix-join-order-in-in-def-dominance-frontier
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
14
.github/actions/fetch-codeql/action.yml
vendored
Normal file
14
.github/actions/fetch-codeql/action.yml
vendored
Normal file
@@ -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 }}
|
||||
18
.github/dependabot.yml
vendored
Normal file
18
.github/dependabot.yml
vendored
Normal file
@@ -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"
|
||||
4
.github/labeler.yml
vendored
4
.github/labeler.yml
vendored
@@ -18,6 +18,10 @@ Python:
|
||||
- python/**/*
|
||||
- change-notes/**/*python*
|
||||
|
||||
Ruby:
|
||||
- ruby/**/*
|
||||
- change-notes/**/*ruby*
|
||||
|
||||
documentation:
|
||||
- "**/*.qhelp"
|
||||
- "**/*.md"
|
||||
|
||||
39
.github/workflows/qhelp-pr-preview.yml
vendored
Normal file
39
.github/workflows/qhelp-pr-preview.yml
vendored
Normal file
@@ -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 "<details> <summary>${path}</summary>"
|
||||
echo
|
||||
codeql generate query-help --format=markdown ${path}
|
||||
echo "</details>"
|
||||
done) | gh pr comment "${{ github.event.pull_request.number }}" -F -
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
232
.github/workflows/ruby-build.yml
vendored
Normal file
232
.github/workflows/ruby-build.yml
vendored
Normal file
@@ -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
|
||||
71
.github/workflows/ruby-dataset-measure.yml
vendored
Normal file
71
.github/workflows/ruby-dataset-measure.yml
vendored
Normal file
@@ -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
|
||||
48
.github/workflows/ruby-qltest.yml
vendored
Normal file
48
.github/workflows/ruby-qltest.yml
vendored
Normal file
@@ -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
|
||||
20
.github/workflows/sync-files.yml
vendored
Normal file
20
.github/workflows/sync-files.yml
vendored
Normal file
@@ -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
|
||||
|
||||
@@ -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
|
||||
/docs/query-*-style-guide.md @github/codeql-analysis-reviewers
|
||||
|
||||
@@ -24,14 +24,17 @@
|
||||
"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",
|
||||
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl2.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 +52,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",
|
||||
@@ -369,7 +375,8 @@
|
||||
"cpp/ql/test/TestUtilities/InlineExpectationsTest.qll",
|
||||
"csharp/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",
|
||||
@@ -441,7 +448,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",
|
||||
@@ -461,13 +469,23 @@
|
||||
],
|
||||
"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"
|
||||
],
|
||||
"CodeQL Tutorial": [
|
||||
"cpp/ql/lib/tutorial.qll",
|
||||
"csharp/ql/lib/tutorial.qll",
|
||||
"java/ql/lib/tutorial.qll",
|
||||
"javascript/ql/lib/tutorial.qll",
|
||||
"python/ql/lib/tutorial.qll"
|
||||
"python/ql/lib/tutorial.qll",
|
||||
"ruby/ql/lib/tutorial.qll"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,5 +33,6 @@ Note that the CWE coverage includes both "`supported queries <https://github.com
|
||||
java-cwe
|
||||
javascript-cwe
|
||||
python-cwe
|
||||
ruby-cwe
|
||||
|
||||
.. include:: ../reusables/ruby-beta-note.rst
|
||||
|
||||
@@ -9,6 +9,7 @@ View the query help for the queries included in the ``code-scanning``, ``securit
|
||||
- :doc:`CodeQL query help for Java <java>`
|
||||
- :doc:`CodeQL query help for JavaScript <javascript>`
|
||||
- :doc:`CodeQL query help for Python <python>`
|
||||
- :doc:`CodeQL query help for Ruby <ruby>`
|
||||
|
||||
.. pull-quote:: Information
|
||||
|
||||
@@ -34,5 +35,6 @@ For a full list of the CWEs covered by these queries, see ":doc:`CodeQL CWE cove
|
||||
java
|
||||
javascript
|
||||
python
|
||||
ruby
|
||||
codeql-cwe-coverage
|
||||
|
||||
|
||||
8
docs/codeql/query-help/ruby-cwe.md
Normal file
8
docs/codeql/query-help/ruby-cwe.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# CWE coverage for Ruby
|
||||
|
||||
An overview of CWE coverage for Ruby in the latest release of CodeQL.
|
||||
|
||||
## Overview
|
||||
|
||||
<!-- autogenerated CWE coverage table will be added below -->
|
||||
|
||||
8
docs/codeql/query-help/ruby.rst
Normal file
8
docs/codeql/query-help/ruby.rst
Normal file
@@ -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 <https://github.com/github/codeql/tree/main/ruby/ql/examples>`__.
|
||||
|
||||
.. include:: toc-ruby.rst
|
||||
2
java/change-notes/2021-10-20-more-specific-types.md
Normal file
2
java/change-notes/2021-10-20-more-specific-types.md
Normal file
@@ -0,0 +1,2 @@
|
||||
lgtm,codescanning
|
||||
* Some uses of `RefType` in the support for 'generics' and 'imports' now use the more specific `ClassOrInterface` instead
|
||||
@@ -26,11 +26,11 @@ jakarta.ws.rs.container,,9,,,,,,,,,,,,,,,,,,,,,9,,
|
||||
jakarta.ws.rs.core,2,,149,,,,,,,,,,,,,,,2,,,,,,94,55
|
||||
java.beans,,,1,,,,,,,,,,,,,,,,,,,,,1,
|
||||
java.io,3,,27,,3,,,,,,,,,,,,,,,,,,,26,1
|
||||
java.lang,,,50,,,,,,,,,,,,,,,,,,,,,41,9
|
||||
java.lang,,,51,,,,,,,,,,,,,,,,,,,,,41,10
|
||||
java.net,10,3,7,,,,,,,,,,,10,,,,,,,,,3,7,
|
||||
java.nio,10,,4,,10,,,,,,,,,,,,,,,,,,,4,
|
||||
java.sql,7,,,,,,,,,,,,,,,7,,,,,,,,,
|
||||
java.util,,,417,,,,,,,,,,,,,,,,,,,,,15,402
|
||||
java.util,,,420,,,,,,,,,,,,,,,,,,,,,15,405
|
||||
javax.faces.context,2,7,,,,,,,,,,,,,,,,,,,2,,7,,
|
||||
javax.json,,,123,,,,,,,,,,,,,,,,,,,,,100,23
|
||||
javax.management.remote,2,,,,,,,,,2,,,,,,,,,,,,,,,
|
||||
|
||||
|
@@ -15,9 +15,9 @@ Java framework & library support
|
||||
`Apache HttpComponents <https://hc.apache.org/>`_,"``org.apache.hc.core5.*``, ``org.apache.http``",5,136,28,,,3,,,,25
|
||||
`Google Guava <https://guava.dev/>`_,``com.google.common.*``,,728,6,,6,,,,,
|
||||
`JSON-java <https://github.com/stleary/JSON-java>`_,``org.json``,,236,,,,,,,,
|
||||
Java Standard Library,``java.*``,3,506,30,13,,,7,,,10
|
||||
Java Standard Library,``java.*``,3,510,30,13,,,7,,,10
|
||||
Java extensions,"``javax.*``, ``jakarta.*``",54,552,32,,,4,,1,1,2
|
||||
`Spring <https://spring.io/>`_,``org.springframework.*``,29,469,91,,,,19,14,,29
|
||||
Others,"``cn.hutool.core.codec``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.fasterxml.jackson.core``, ``com.fasterxml.jackson.databind``, ``com.opensymphony.xwork2.ognl``, ``com.unboundid.ldap.sdk``, ``flexjson``, ``groovy.lang``, ``groovy.util``, ``jodd.json``, ``net.sf.saxon.s9api``, ``ognl``, ``org.apache.commons.codec``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.ognl``, ``org.apache.directory.ldap.client.api``, ``org.apache.ibatis.jdbc``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.codehaus.groovy.control``, ``org.dom4j``, ``org.hibernate``, ``org.jooq``, ``org.mvel2``, ``org.xml.sax``, ``org.xmlpull.v1``, ``play.mvc``",7,28,151,,,,14,18,,
|
||||
Totals,,143,5257,408,13,6,10,107,33,1,66
|
||||
Totals,,143,5261,408,13,6,10,107,33,1,66
|
||||
|
||||
|
||||
@@ -393,7 +393,7 @@ typeVars(
|
||||
string nodeName: string ref,
|
||||
int pos: int ref,
|
||||
int kind: int ref, // deprecated
|
||||
int parentid: @typeorcallable ref
|
||||
int parentid: @classorinterfaceorcallable ref
|
||||
);
|
||||
|
||||
wildcards(
|
||||
@@ -414,7 +414,7 @@ typeBounds(
|
||||
typeArgs(
|
||||
int argumentid: @reftype ref,
|
||||
int pos: int ref,
|
||||
int parentid: @typeorcallable ref
|
||||
int parentid: @classorinterfaceorcallable ref
|
||||
);
|
||||
|
||||
isParameterized(
|
||||
@@ -487,7 +487,7 @@ hasModifier(
|
||||
|
||||
imports(
|
||||
unique int id: @import,
|
||||
int holder: @typeorpackage ref,
|
||||
int holder: @classorinterfaceorpackage ref,
|
||||
string name: string ref,
|
||||
int kind: int ref
|
||||
);
|
||||
@@ -857,10 +857,9 @@ javadocText(
|
||||
@javadocParent = @javadoc | @javadocTag;
|
||||
@javadocElement = @javadocTag | @javadocText;
|
||||
|
||||
@typeorpackage = @type | @package;
|
||||
|
||||
@typeorcallable = @type | @callable;
|
||||
@classorinterface = @interface | @class;
|
||||
@classorinterfaceorpackage = @classorinterface | @package;
|
||||
@classorinterfaceorcallable = @classorinterface | @callable;
|
||||
@boundedtype = @typevariable | @wildcard;
|
||||
@reftype = @classorinterface | @array | @boundedtype;
|
||||
@classorarray = @class | @array;
|
||||
|
||||
@@ -38,7 +38,7 @@ import Type
|
||||
*
|
||||
* For example, `X` in `class X<T> { }`.
|
||||
*/
|
||||
class GenericType extends RefType {
|
||||
class GenericType extends ClassOrInterface {
|
||||
GenericType() { typeVars(_, _, _, _, this) }
|
||||
|
||||
/**
|
||||
@@ -139,7 +139,7 @@ abstract class BoundedType extends RefType, @boundedtype {
|
||||
*/
|
||||
class TypeVariable extends BoundedType, @typevariable {
|
||||
/** Gets the generic type that is parameterized by this type parameter, if any. */
|
||||
RefType getGenericType() { typeVars(this, _, _, _, result) }
|
||||
GenericType getGenericType() { typeVars(this, _, _, _, result) }
|
||||
|
||||
/** Gets the generic callable that is parameterized by this type parameter, if any. */
|
||||
GenericCallable getGenericCallable() { typeVars(this, _, _, _, result) }
|
||||
@@ -321,7 +321,7 @@ class TypeBound extends @typebound {
|
||||
* For example, `List<Number>` is a parameterization of
|
||||
* the generic type `List<E>`, where `E` is a type parameter.
|
||||
*/
|
||||
class ParameterizedType extends RefType {
|
||||
class ParameterizedType extends ClassOrInterface {
|
||||
ParameterizedType() {
|
||||
typeArgs(_, _, this) or
|
||||
typeVars(_, _, _, _, this)
|
||||
@@ -367,7 +367,9 @@ class ParameterizedType extends RefType {
|
||||
}
|
||||
|
||||
/** Holds if this type originates from source code. */
|
||||
override predicate fromSource() { typeVars(_, _, _, _, this) and RefType.super.fromSource() }
|
||||
override predicate fromSource() {
|
||||
typeVars(_, _, _, _, this) and ClassOrInterface.super.fromSource()
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ParameterizedType" }
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ class ImportType extends Import {
|
||||
ImportType() { imports(this, _, _, 1) }
|
||||
|
||||
/** Gets the imported type. */
|
||||
RefType getImportedType() { imports(this, result, _, _) }
|
||||
ClassOrInterface getImportedType() { imports(this, result, _, _) }
|
||||
|
||||
override string toString() { result = "import " + this.getImportedType().toString() }
|
||||
|
||||
@@ -44,7 +44,7 @@ class ImportOnDemandFromType extends Import {
|
||||
ImportOnDemandFromType() { imports(this, _, _, 2) }
|
||||
|
||||
/** Gets the type from which accessible nested types are imported. */
|
||||
RefType getTypeHoldingImport() { imports(this, result, _, _) }
|
||||
ClassOrInterface getTypeHoldingImport() { imports(this, result, _, _) }
|
||||
|
||||
/** Gets an imported type. */
|
||||
NestedType getAnImport() { result.getEnclosingType() = this.getTypeHoldingImport() }
|
||||
@@ -87,7 +87,7 @@ class ImportStaticOnDemand extends Import {
|
||||
ImportStaticOnDemand() { imports(this, _, _, 4) }
|
||||
|
||||
/** Gets the type from which accessible static members are imported. */
|
||||
RefType getTypeHoldingImport() { imports(this, result, _, _) }
|
||||
ClassOrInterface getTypeHoldingImport() { imports(this, result, _, _) }
|
||||
|
||||
/** Gets an imported type. */
|
||||
NestedType getATypeImport() { result.getEnclosingType() = this.getTypeHoldingImport() }
|
||||
@@ -118,7 +118,7 @@ class ImportStaticTypeMember extends Import {
|
||||
ImportStaticTypeMember() { imports(this, _, _, 5) }
|
||||
|
||||
/** Gets the type from which static members with a given name are imported. */
|
||||
RefType getTypeHoldingImport() { imports(this, result, _, _) }
|
||||
ClassOrInterface getTypeHoldingImport() { imports(this, result, _, _) }
|
||||
|
||||
/** Gets the name of the imported member(s). */
|
||||
override string getName() { imports(this, _, result, _) }
|
||||
|
||||
@@ -0,0 +1,983 @@
|
||||
/**
|
||||
* An invocation of the compiler. Note that more than one file may be
|
||||
* compiled per invocation. For example, this command compiles three
|
||||
* source files:
|
||||
*
|
||||
* javac A.java B.java C.java
|
||||
*
|
||||
* The `id` simply identifies the invocation, while `cwd` is the working
|
||||
* directory from which the compiler was invoked.
|
||||
*/
|
||||
compilations(
|
||||
/**
|
||||
* An invocation of the compiler. Note that more than one file may
|
||||
* be compiled per invocation. For example, this command compiles
|
||||
* three source files:
|
||||
*
|
||||
* javac A.java B.java C.java
|
||||
*/
|
||||
unique int id : @compilation,
|
||||
string cwd : string ref
|
||||
);
|
||||
|
||||
/**
|
||||
* The arguments that were passed to the extractor for a compiler
|
||||
* invocation. If `id` is for the compiler invocation
|
||||
*
|
||||
* javac A.java B.java C.java
|
||||
*
|
||||
* then typically there will be rows for
|
||||
*
|
||||
* num | arg
|
||||
* --- | ---
|
||||
* 0 | *path to extractor*
|
||||
* 1 | `--javac-args`
|
||||
* 2 | A.java
|
||||
* 3 | B.java
|
||||
* 4 | C.java
|
||||
*/
|
||||
#keyset[id, num]
|
||||
compilation_args(
|
||||
int id : @compilation ref,
|
||||
int num : int ref,
|
||||
string arg : string ref
|
||||
);
|
||||
|
||||
/**
|
||||
* The source files that are compiled by a compiler invocation.
|
||||
* If `id` is for the compiler invocation
|
||||
*
|
||||
* javac A.java B.java C.java
|
||||
*
|
||||
* then there will be rows for
|
||||
*
|
||||
* num | arg
|
||||
* --- | ---
|
||||
* 0 | A.java
|
||||
* 1 | B.java
|
||||
* 2 | C.java
|
||||
*/
|
||||
#keyset[id, num]
|
||||
compilation_compiling_files(
|
||||
int id : @compilation ref,
|
||||
int num : int ref,
|
||||
int file : @file ref
|
||||
);
|
||||
|
||||
/**
|
||||
* The time taken by the extractor for a compiler invocation.
|
||||
*
|
||||
* For each file `num`, there will be rows for
|
||||
*
|
||||
* kind | seconds
|
||||
* ---- | ---
|
||||
* 1 | CPU seconds used by the extractor frontend
|
||||
* 2 | Elapsed seconds during the extractor frontend
|
||||
* 3 | CPU seconds used by the extractor backend
|
||||
* 4 | Elapsed seconds during the extractor backend
|
||||
*/
|
||||
#keyset[id, num, kind]
|
||||
compilation_time(
|
||||
int id : @compilation ref,
|
||||
int num : int ref,
|
||||
/* kind:
|
||||
1 = frontend_cpu_seconds
|
||||
2 = frontend_elapsed_seconds
|
||||
3 = extractor_cpu_seconds
|
||||
4 = extractor_elapsed_seconds
|
||||
*/
|
||||
int kind : int ref,
|
||||
float seconds : float ref
|
||||
);
|
||||
|
||||
/**
|
||||
* An error or warning generated by the extractor.
|
||||
* The diagnostic message `diagnostic` was generated during compiler
|
||||
* invocation `compilation`, and is the `file_number_diagnostic_number`th
|
||||
* message generated while extracting the `file_number`th file of that
|
||||
* invocation.
|
||||
*/
|
||||
#keyset[compilation, file_number, file_number_diagnostic_number]
|
||||
diagnostic_for(
|
||||
unique int diagnostic : @diagnostic ref,
|
||||
int compilation : @compilation ref,
|
||||
int file_number : int ref,
|
||||
int file_number_diagnostic_number : int ref
|
||||
);
|
||||
|
||||
/**
|
||||
* If extraction was successful, then `cpu_seconds` and
|
||||
* `elapsed_seconds` are the CPU time and elapsed time (respectively)
|
||||
* that extraction took for compiler invocation `id`.
|
||||
*/
|
||||
compilation_finished(
|
||||
unique int id : @compilation ref,
|
||||
float cpu_seconds : float ref,
|
||||
float elapsed_seconds : float ref
|
||||
);
|
||||
|
||||
diagnostics(
|
||||
unique int id: @diagnostic,
|
||||
int severity: int ref,
|
||||
string error_tag: string ref,
|
||||
string error_message: string ref,
|
||||
string full_error_message: string ref,
|
||||
int location: @location_default ref
|
||||
);
|
||||
|
||||
/*
|
||||
* External artifacts
|
||||
*/
|
||||
|
||||
externalData(
|
||||
int id : @externalDataElement,
|
||||
string path : string ref,
|
||||
int column: int ref,
|
||||
string value : string ref
|
||||
);
|
||||
|
||||
snapshotDate(
|
||||
unique date snapshotDate : date ref
|
||||
);
|
||||
|
||||
sourceLocationPrefix(
|
||||
string prefix : string ref
|
||||
);
|
||||
|
||||
/*
|
||||
* Duplicate code
|
||||
*/
|
||||
|
||||
duplicateCode(
|
||||
unique int id : @duplication,
|
||||
string relativePath : string ref,
|
||||
int equivClass : int ref
|
||||
);
|
||||
|
||||
similarCode(
|
||||
unique int id : @similarity,
|
||||
string relativePath : string ref,
|
||||
int equivClass : int ref
|
||||
);
|
||||
|
||||
@duplication_or_similarity = @duplication | @similarity
|
||||
|
||||
tokens(
|
||||
int id : @duplication_or_similarity ref,
|
||||
int offset : int ref,
|
||||
int beginLine : int ref,
|
||||
int beginColumn : int ref,
|
||||
int endLine : int ref,
|
||||
int endColumn : int ref
|
||||
);
|
||||
|
||||
/*
|
||||
* SMAP
|
||||
*/
|
||||
|
||||
smap_header(
|
||||
int outputFileId: @file ref,
|
||||
string outputFilename: string ref,
|
||||
string defaultStratum: string ref
|
||||
);
|
||||
|
||||
smap_files(
|
||||
int outputFileId: @file ref,
|
||||
string stratum: string ref,
|
||||
int inputFileNum: int ref,
|
||||
string inputFileName: string ref,
|
||||
int inputFileId: @file ref
|
||||
);
|
||||
|
||||
smap_lines(
|
||||
int outputFileId: @file ref,
|
||||
string stratum: string ref,
|
||||
int inputFileNum: int ref,
|
||||
int inputStartLine: int ref,
|
||||
int inputLineCount: int ref,
|
||||
int outputStartLine: int ref,
|
||||
int outputLineIncrement: int ref
|
||||
);
|
||||
|
||||
/*
|
||||
* Locations and files
|
||||
*/
|
||||
|
||||
@location = @location_default ;
|
||||
|
||||
locations_default(
|
||||
unique int id: @location_default,
|
||||
int file: @file ref,
|
||||
int beginLine: int ref,
|
||||
int beginColumn: int ref,
|
||||
int endLine: int ref,
|
||||
int endColumn: int ref
|
||||
);
|
||||
|
||||
hasLocation(
|
||||
int locatableid: @locatable ref,
|
||||
int id: @location ref
|
||||
);
|
||||
|
||||
@sourceline = @locatable ;
|
||||
|
||||
#keyset[element_id]
|
||||
numlines(
|
||||
int element_id: @sourceline ref,
|
||||
int num_lines: int ref,
|
||||
int num_code: int ref,
|
||||
int num_comment: int ref
|
||||
);
|
||||
|
||||
files(
|
||||
unique int id: @file,
|
||||
string name: string ref
|
||||
);
|
||||
|
||||
folders(
|
||||
unique int id: @folder,
|
||||
string name: string ref
|
||||
);
|
||||
|
||||
@container = @folder | @file
|
||||
|
||||
containerparent(
|
||||
int parent: @container ref,
|
||||
unique int child: @container ref
|
||||
);
|
||||
|
||||
/*
|
||||
* Java
|
||||
*/
|
||||
|
||||
cupackage(
|
||||
unique int id: @file ref,
|
||||
int packageid: @package ref
|
||||
);
|
||||
|
||||
#keyset[fileid,keyName]
|
||||
jarManifestMain(
|
||||
int fileid: @file ref,
|
||||
string keyName: string ref,
|
||||
string value: string ref
|
||||
);
|
||||
|
||||
#keyset[fileid,entryName,keyName]
|
||||
jarManifestEntries(
|
||||
int fileid: @file ref,
|
||||
string entryName: string ref,
|
||||
string keyName: string ref,
|
||||
string value: string ref
|
||||
);
|
||||
|
||||
packages(
|
||||
unique int id: @package,
|
||||
string nodeName: string ref
|
||||
);
|
||||
|
||||
primitives(
|
||||
unique int id: @primitive,
|
||||
string nodeName: string ref
|
||||
);
|
||||
|
||||
modifiers(
|
||||
unique int id: @modifier,
|
||||
string nodeName: string ref
|
||||
);
|
||||
|
||||
classes(
|
||||
unique int id: @class,
|
||||
string nodeName: string ref,
|
||||
int parentid: @package ref,
|
||||
int sourceid: @class ref
|
||||
);
|
||||
|
||||
isRecord(
|
||||
unique int id: @class ref
|
||||
);
|
||||
|
||||
interfaces(
|
||||
unique int id: @interface,
|
||||
string nodeName: string ref,
|
||||
int parentid: @package ref,
|
||||
int sourceid: @interface ref
|
||||
);
|
||||
|
||||
fielddecls(
|
||||
unique int id: @fielddecl,
|
||||
int parentid: @reftype ref
|
||||
);
|
||||
|
||||
#keyset[fieldId] #keyset[fieldDeclId,pos]
|
||||
fieldDeclaredIn(
|
||||
int fieldId: @field ref,
|
||||
int fieldDeclId: @fielddecl ref,
|
||||
int pos: int ref
|
||||
);
|
||||
|
||||
fields(
|
||||
unique int id: @field,
|
||||
string nodeName: string ref,
|
||||
int typeid: @type ref,
|
||||
int parentid: @reftype ref,
|
||||
int sourceid: @field ref
|
||||
);
|
||||
|
||||
constrs(
|
||||
unique int id: @constructor,
|
||||
string nodeName: string ref,
|
||||
string signature: string ref,
|
||||
int typeid: @type ref,
|
||||
int parentid: @reftype ref,
|
||||
int sourceid: @constructor ref
|
||||
);
|
||||
|
||||
methods(
|
||||
unique int id: @method,
|
||||
string nodeName: string ref,
|
||||
string signature: string ref,
|
||||
int typeid: @type ref,
|
||||
int parentid: @reftype ref,
|
||||
int sourceid: @method ref
|
||||
);
|
||||
|
||||
#keyset[parentid,pos]
|
||||
params(
|
||||
unique int id: @param,
|
||||
int typeid: @type ref,
|
||||
int pos: int ref,
|
||||
int parentid: @callable ref,
|
||||
int sourceid: @param ref
|
||||
);
|
||||
|
||||
paramName(
|
||||
unique int id: @param ref,
|
||||
string nodeName: string ref
|
||||
);
|
||||
|
||||
isVarargsParam(
|
||||
int param: @param ref
|
||||
);
|
||||
|
||||
exceptions(
|
||||
unique int id: @exception,
|
||||
int typeid: @type ref,
|
||||
int parentid: @callable ref
|
||||
);
|
||||
|
||||
isAnnotType(
|
||||
int interfaceid: @interface ref
|
||||
);
|
||||
|
||||
isAnnotElem(
|
||||
int methodid: @method ref
|
||||
);
|
||||
|
||||
annotValue(
|
||||
int parentid: @annotation ref,
|
||||
int id2: @method ref,
|
||||
unique int value: @expr ref
|
||||
);
|
||||
|
||||
isEnumType(
|
||||
int classid: @class ref
|
||||
);
|
||||
|
||||
isEnumConst(
|
||||
int fieldid: @field ref
|
||||
);
|
||||
|
||||
#keyset[parentid,pos]
|
||||
typeVars(
|
||||
unique int id: @typevariable,
|
||||
string nodeName: string ref,
|
||||
int pos: int ref,
|
||||
int kind: int ref, // deprecated
|
||||
int parentid: @typeorcallable ref
|
||||
);
|
||||
|
||||
wildcards(
|
||||
unique int id: @wildcard,
|
||||
string nodeName: string ref,
|
||||
int kind: int ref
|
||||
);
|
||||
|
||||
#keyset[parentid,pos]
|
||||
typeBounds(
|
||||
unique int id: @typebound,
|
||||
int typeid: @reftype ref,
|
||||
int pos: int ref,
|
||||
int parentid: @boundedtype ref
|
||||
);
|
||||
|
||||
#keyset[parentid,pos]
|
||||
typeArgs(
|
||||
int argumentid: @reftype ref,
|
||||
int pos: int ref,
|
||||
int parentid: @typeorcallable ref
|
||||
);
|
||||
|
||||
isParameterized(
|
||||
int memberid: @member ref
|
||||
);
|
||||
|
||||
isRaw(
|
||||
int memberid: @member ref
|
||||
);
|
||||
|
||||
erasure(
|
||||
unique int memberid: @member ref,
|
||||
int erasureid: @member ref
|
||||
);
|
||||
|
||||
#keyset[classid] #keyset[parent]
|
||||
isAnonymClass(
|
||||
int classid: @class ref,
|
||||
int parent: @classinstancexpr ref
|
||||
);
|
||||
|
||||
#keyset[typeid] #keyset[parent]
|
||||
isLocalClassOrInterface(
|
||||
int typeid: @classorinterface ref,
|
||||
int parent: @localtypedeclstmt ref
|
||||
);
|
||||
|
||||
isDefConstr(
|
||||
int constructorid: @constructor ref
|
||||
);
|
||||
|
||||
#keyset[exprId]
|
||||
lambdaKind(
|
||||
int exprId: @lambdaexpr ref,
|
||||
int bodyKind: int ref
|
||||
);
|
||||
|
||||
arrays(
|
||||
unique int id: @array,
|
||||
string nodeName: string ref,
|
||||
int elementtypeid: @type ref,
|
||||
int dimension: int ref,
|
||||
int componenttypeid: @type ref
|
||||
);
|
||||
|
||||
enclInReftype(
|
||||
unique int child: @reftype ref,
|
||||
int parent: @reftype ref
|
||||
);
|
||||
|
||||
extendsReftype(
|
||||
int id1: @reftype ref,
|
||||
int id2: @classorinterface ref
|
||||
);
|
||||
|
||||
implInterface(
|
||||
int id1: @classorarray ref,
|
||||
int id2: @interface ref
|
||||
);
|
||||
|
||||
permits(
|
||||
int id1: @classorinterface ref,
|
||||
int id2: @classorinterface ref
|
||||
);
|
||||
|
||||
hasModifier(
|
||||
int id1: @modifiable ref,
|
||||
int id2: @modifier ref
|
||||
);
|
||||
|
||||
imports(
|
||||
unique int id: @import,
|
||||
int holder: @typeorpackage ref,
|
||||
string name: string ref,
|
||||
int kind: int ref
|
||||
);
|
||||
|
||||
#keyset[parent,idx]
|
||||
stmts(
|
||||
unique int id: @stmt,
|
||||
int kind: int ref,
|
||||
int parent: @stmtparent ref,
|
||||
int idx: int ref,
|
||||
int bodydecl: @callable ref
|
||||
);
|
||||
|
||||
@stmtparent = @callable | @stmt | @switchexpr;
|
||||
|
||||
case @stmt.kind of
|
||||
0 = @block
|
||||
| 1 = @ifstmt
|
||||
| 2 = @forstmt
|
||||
| 3 = @enhancedforstmt
|
||||
| 4 = @whilestmt
|
||||
| 5 = @dostmt
|
||||
| 6 = @trystmt
|
||||
| 7 = @switchstmt
|
||||
| 8 = @synchronizedstmt
|
||||
| 9 = @returnstmt
|
||||
| 10 = @throwstmt
|
||||
| 11 = @breakstmt
|
||||
| 12 = @continuestmt
|
||||
| 13 = @emptystmt
|
||||
| 14 = @exprstmt
|
||||
| 15 = @labeledstmt
|
||||
| 16 = @assertstmt
|
||||
| 17 = @localvariabledeclstmt
|
||||
| 18 = @localtypedeclstmt
|
||||
| 19 = @constructorinvocationstmt
|
||||
| 20 = @superconstructorinvocationstmt
|
||||
| 21 = @case
|
||||
| 22 = @catchclause
|
||||
| 23 = @yieldstmt
|
||||
;
|
||||
|
||||
#keyset[parent,idx]
|
||||
exprs(
|
||||
unique int id: @expr,
|
||||
int kind: int ref,
|
||||
int typeid: @type ref,
|
||||
int parent: @exprparent ref,
|
||||
int idx: int ref
|
||||
);
|
||||
|
||||
callableEnclosingExpr(
|
||||
unique int id: @expr ref,
|
||||
int callable_id: @callable ref
|
||||
);
|
||||
|
||||
statementEnclosingExpr(
|
||||
unique int id: @expr ref,
|
||||
int statement_id: @stmt ref
|
||||
);
|
||||
|
||||
isParenthesized(
|
||||
unique int id: @expr ref,
|
||||
int parentheses: int ref
|
||||
);
|
||||
|
||||
case @expr.kind of
|
||||
1 = @arrayaccess
|
||||
| 2 = @arraycreationexpr
|
||||
| 3 = @arrayinit
|
||||
| 4 = @assignexpr
|
||||
| 5 = @assignaddexpr
|
||||
| 6 = @assignsubexpr
|
||||
| 7 = @assignmulexpr
|
||||
| 8 = @assigndivexpr
|
||||
| 9 = @assignremexpr
|
||||
| 10 = @assignandexpr
|
||||
| 11 = @assignorexpr
|
||||
| 12 = @assignxorexpr
|
||||
| 13 = @assignlshiftexpr
|
||||
| 14 = @assignrshiftexpr
|
||||
| 15 = @assignurshiftexpr
|
||||
| 16 = @booleanliteral
|
||||
| 17 = @integerliteral
|
||||
| 18 = @longliteral
|
||||
| 19 = @floatingpointliteral
|
||||
| 20 = @doubleliteral
|
||||
| 21 = @characterliteral
|
||||
| 22 = @stringliteral
|
||||
| 23 = @nullliteral
|
||||
| 24 = @mulexpr
|
||||
| 25 = @divexpr
|
||||
| 26 = @remexpr
|
||||
| 27 = @addexpr
|
||||
| 28 = @subexpr
|
||||
| 29 = @lshiftexpr
|
||||
| 30 = @rshiftexpr
|
||||
| 31 = @urshiftexpr
|
||||
| 32 = @andbitexpr
|
||||
| 33 = @orbitexpr
|
||||
| 34 = @xorbitexpr
|
||||
| 35 = @andlogicalexpr
|
||||
| 36 = @orlogicalexpr
|
||||
| 37 = @ltexpr
|
||||
| 38 = @gtexpr
|
||||
| 39 = @leexpr
|
||||
| 40 = @geexpr
|
||||
| 41 = @eqexpr
|
||||
| 42 = @neexpr
|
||||
| 43 = @postincexpr
|
||||
| 44 = @postdecexpr
|
||||
| 45 = @preincexpr
|
||||
| 46 = @predecexpr
|
||||
| 47 = @minusexpr
|
||||
| 48 = @plusexpr
|
||||
| 49 = @bitnotexpr
|
||||
| 50 = @lognotexpr
|
||||
| 51 = @castexpr
|
||||
| 52 = @newexpr
|
||||
| 53 = @conditionalexpr
|
||||
| 54 = @parexpr // deprecated
|
||||
| 55 = @instanceofexpr
|
||||
| 56 = @localvariabledeclexpr
|
||||
| 57 = @typeliteral
|
||||
| 58 = @thisaccess
|
||||
| 59 = @superaccess
|
||||
| 60 = @varaccess
|
||||
| 61 = @methodaccess
|
||||
| 62 = @unannotatedtypeaccess
|
||||
| 63 = @arraytypeaccess
|
||||
| 64 = @packageaccess
|
||||
| 65 = @wildcardtypeaccess
|
||||
| 66 = @declannotation
|
||||
| 67 = @uniontypeaccess
|
||||
| 68 = @lambdaexpr
|
||||
| 69 = @memberref
|
||||
| 70 = @annotatedtypeaccess
|
||||
| 71 = @typeannotation
|
||||
| 72 = @intersectiontypeaccess
|
||||
| 73 = @switchexpr
|
||||
;
|
||||
|
||||
@classinstancexpr = @newexpr | @lambdaexpr | @memberref
|
||||
|
||||
@annotation = @declannotation | @typeannotation
|
||||
@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess
|
||||
|
||||
@assignment = @assignexpr
|
||||
| @assignop;
|
||||
|
||||
@unaryassignment = @postincexpr
|
||||
| @postdecexpr
|
||||
| @preincexpr
|
||||
| @predecexpr;
|
||||
|
||||
@assignop = @assignaddexpr
|
||||
| @assignsubexpr
|
||||
| @assignmulexpr
|
||||
| @assigndivexpr
|
||||
| @assignremexpr
|
||||
| @assignandexpr
|
||||
| @assignorexpr
|
||||
| @assignxorexpr
|
||||
| @assignlshiftexpr
|
||||
| @assignrshiftexpr
|
||||
| @assignurshiftexpr;
|
||||
|
||||
@literal = @booleanliteral
|
||||
| @integerliteral
|
||||
| @longliteral
|
||||
| @floatingpointliteral
|
||||
| @doubleliteral
|
||||
| @characterliteral
|
||||
| @stringliteral
|
||||
| @nullliteral;
|
||||
|
||||
@binaryexpr = @mulexpr
|
||||
| @divexpr
|
||||
| @remexpr
|
||||
| @addexpr
|
||||
| @subexpr
|
||||
| @lshiftexpr
|
||||
| @rshiftexpr
|
||||
| @urshiftexpr
|
||||
| @andbitexpr
|
||||
| @orbitexpr
|
||||
| @xorbitexpr
|
||||
| @andlogicalexpr
|
||||
| @orlogicalexpr
|
||||
| @ltexpr
|
||||
| @gtexpr
|
||||
| @leexpr
|
||||
| @geexpr
|
||||
| @eqexpr
|
||||
| @neexpr;
|
||||
|
||||
@unaryexpr = @postincexpr
|
||||
| @postdecexpr
|
||||
| @preincexpr
|
||||
| @predecexpr
|
||||
| @minusexpr
|
||||
| @plusexpr
|
||||
| @bitnotexpr
|
||||
| @lognotexpr;
|
||||
|
||||
@caller = @classinstancexpr
|
||||
| @methodaccess
|
||||
| @constructorinvocationstmt
|
||||
| @superconstructorinvocationstmt;
|
||||
|
||||
callableBinding(
|
||||
unique int callerid: @caller ref,
|
||||
int callee: @callable ref
|
||||
);
|
||||
|
||||
memberRefBinding(
|
||||
unique int id: @expr ref,
|
||||
int callable: @callable ref
|
||||
);
|
||||
|
||||
@exprparent = @stmt | @expr | @callable | @field | @fielddecl | @class | @interface | @param | @localvar | @typevariable;
|
||||
|
||||
variableBinding(
|
||||
unique int expr: @varaccess ref,
|
||||
int variable: @variable ref
|
||||
);
|
||||
|
||||
@variable = @localscopevariable | @field;
|
||||
|
||||
@localscopevariable = @localvar | @param;
|
||||
|
||||
localvars(
|
||||
unique int id: @localvar,
|
||||
string nodeName: string ref,
|
||||
int typeid: @type ref,
|
||||
int parentid: @localvariabledeclexpr ref
|
||||
);
|
||||
|
||||
@namedexprorstmt = @breakstmt
|
||||
| @continuestmt
|
||||
| @labeledstmt
|
||||
| @literal;
|
||||
|
||||
namestrings(
|
||||
string name: string ref,
|
||||
string value: string ref,
|
||||
unique int parent: @namedexprorstmt ref
|
||||
);
|
||||
|
||||
/*
|
||||
* Modules
|
||||
*/
|
||||
|
||||
#keyset[name]
|
||||
modules(
|
||||
unique int id: @module,
|
||||
string name: string ref
|
||||
);
|
||||
|
||||
isOpen(
|
||||
int id: @module ref
|
||||
);
|
||||
|
||||
#keyset[fileId]
|
||||
cumodule(
|
||||
int fileId: @file ref,
|
||||
int moduleId: @module ref
|
||||
);
|
||||
|
||||
@directive = @requires
|
||||
| @exports
|
||||
| @opens
|
||||
| @uses
|
||||
| @provides
|
||||
|
||||
#keyset[directive]
|
||||
directives(
|
||||
int id: @module ref,
|
||||
int directive: @directive ref
|
||||
);
|
||||
|
||||
requires(
|
||||
unique int id: @requires,
|
||||
int target: @module ref
|
||||
);
|
||||
|
||||
isTransitive(
|
||||
int id: @requires ref
|
||||
);
|
||||
|
||||
isStatic(
|
||||
int id: @requires ref
|
||||
);
|
||||
|
||||
exports(
|
||||
unique int id: @exports,
|
||||
int target: @package ref
|
||||
);
|
||||
|
||||
exportsTo(
|
||||
int id: @exports ref,
|
||||
int target: @module ref
|
||||
);
|
||||
|
||||
opens(
|
||||
unique int id: @opens,
|
||||
int target: @package ref
|
||||
);
|
||||
|
||||
opensTo(
|
||||
int id: @opens ref,
|
||||
int target: @module ref
|
||||
);
|
||||
|
||||
uses(
|
||||
unique int id: @uses,
|
||||
string serviceInterface: string ref
|
||||
);
|
||||
|
||||
provides(
|
||||
unique int id: @provides,
|
||||
string serviceInterface: string ref
|
||||
);
|
||||
|
||||
providesWith(
|
||||
int id: @provides ref,
|
||||
string serviceImpl: string ref
|
||||
);
|
||||
|
||||
/*
|
||||
* Javadoc
|
||||
*/
|
||||
|
||||
javadoc(
|
||||
unique int id: @javadoc
|
||||
);
|
||||
|
||||
isNormalComment(
|
||||
int commentid : @javadoc ref
|
||||
);
|
||||
|
||||
isEolComment(
|
||||
int commentid : @javadoc ref
|
||||
);
|
||||
|
||||
hasJavadoc(
|
||||
int documentableid: @member ref,
|
||||
int javadocid: @javadoc ref
|
||||
);
|
||||
|
||||
#keyset[parentid,idx]
|
||||
javadocTag(
|
||||
unique int id: @javadocTag,
|
||||
string name: string ref,
|
||||
int parentid: @javadocParent ref,
|
||||
int idx: int ref
|
||||
);
|
||||
|
||||
#keyset[parentid,idx]
|
||||
javadocText(
|
||||
unique int id: @javadocText,
|
||||
string text: string ref,
|
||||
int parentid: @javadocParent ref,
|
||||
int idx: int ref
|
||||
);
|
||||
|
||||
@javadocParent = @javadoc | @javadocTag;
|
||||
@javadocElement = @javadocTag | @javadocText;
|
||||
|
||||
@typeorpackage = @type | @package;
|
||||
|
||||
@typeorcallable = @type | @callable;
|
||||
@classorinterface = @interface | @class;
|
||||
@boundedtype = @typevariable | @wildcard;
|
||||
@reftype = @classorinterface | @array | @boundedtype;
|
||||
@classorarray = @class | @array;
|
||||
@type = @primitive | @reftype;
|
||||
@callable = @method | @constructor;
|
||||
@element = @file | @package | @primitive | @class | @interface | @method | @constructor | @modifier | @param | @exception | @field |
|
||||
@annotation | @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl;
|
||||
|
||||
@modifiable = @member_modifiable| @param | @localvar ;
|
||||
|
||||
@member_modifiable = @class | @interface | @method | @constructor | @field ;
|
||||
|
||||
@member = @method | @constructor | @field | @reftype ;
|
||||
|
||||
@locatable = @file | @class | @interface | @fielddecl | @field | @constructor | @method | @param | @exception
|
||||
| @boundedtype | @typebound | @array | @primitive
|
||||
| @import | @stmt | @expr | @localvar | @javadoc | @javadocTag | @javadocText
|
||||
| @xmllocatable;
|
||||
|
||||
@top = @element | @locatable | @folder;
|
||||
|
||||
/*
|
||||
* XML Files
|
||||
*/
|
||||
|
||||
xmlEncoding(
|
||||
unique int id: @file ref,
|
||||
string encoding: string ref
|
||||
);
|
||||
|
||||
xmlDTDs(
|
||||
unique int id: @xmldtd,
|
||||
string root: string ref,
|
||||
string publicId: string ref,
|
||||
string systemId: string ref,
|
||||
int fileid: @file ref
|
||||
);
|
||||
|
||||
xmlElements(
|
||||
unique int id: @xmlelement,
|
||||
string name: string ref,
|
||||
int parentid: @xmlparent ref,
|
||||
int idx: int ref,
|
||||
int fileid: @file ref
|
||||
);
|
||||
|
||||
xmlAttrs(
|
||||
unique int id: @xmlattribute,
|
||||
int elementid: @xmlelement ref,
|
||||
string name: string ref,
|
||||
string value: string ref,
|
||||
int idx: int ref,
|
||||
int fileid: @file ref
|
||||
);
|
||||
|
||||
xmlNs(
|
||||
int id: @xmlnamespace,
|
||||
string prefixName: string ref,
|
||||
string URI: string ref,
|
||||
int fileid: @file ref
|
||||
);
|
||||
|
||||
xmlHasNs(
|
||||
int elementId: @xmlnamespaceable ref,
|
||||
int nsId: @xmlnamespace ref,
|
||||
int fileid: @file ref
|
||||
);
|
||||
|
||||
xmlComments(
|
||||
unique int id: @xmlcomment,
|
||||
string text: string ref,
|
||||
int parentid: @xmlparent ref,
|
||||
int fileid: @file ref
|
||||
);
|
||||
|
||||
xmlChars(
|
||||
unique int id: @xmlcharacters,
|
||||
string text: string ref,
|
||||
int parentid: @xmlparent ref,
|
||||
int idx: int ref,
|
||||
int isCDATA: int ref,
|
||||
int fileid: @file ref
|
||||
);
|
||||
|
||||
@xmlparent = @file | @xmlelement;
|
||||
@xmlnamespaceable = @xmlelement | @xmlattribute;
|
||||
|
||||
xmllocations(
|
||||
int xmlElement: @xmllocatable ref,
|
||||
int location: @location_default ref
|
||||
);
|
||||
|
||||
@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
|
||||
|
||||
/*
|
||||
* configuration files with key value pairs
|
||||
*/
|
||||
|
||||
configs(
|
||||
unique int id: @config
|
||||
);
|
||||
|
||||
configNames(
|
||||
unique int id: @configName,
|
||||
int config: @config ref,
|
||||
string name: string ref
|
||||
);
|
||||
|
||||
configValues(
|
||||
unique int id: @configValue,
|
||||
int config: @config ref,
|
||||
string value: string ref
|
||||
);
|
||||
|
||||
configLocations(
|
||||
int locatable: @configLocatable ref,
|
||||
int location: @location_default ref
|
||||
);
|
||||
|
||||
@configLocatable = @config | @configName | @configValue;
|
||||
982
java/upgrades/017ac1ed2df1eaa5d8c4ae1849261c82392209d4/semmlecode.dbscheme
Executable file
982
java/upgrades/017ac1ed2df1eaa5d8c4ae1849261c82392209d4/semmlecode.dbscheme
Executable file
@@ -0,0 +1,982 @@
|
||||
/**
|
||||
* An invocation of the compiler. Note that more than one file may be
|
||||
* compiled per invocation. For example, this command compiles three
|
||||
* source files:
|
||||
*
|
||||
* javac A.java B.java C.java
|
||||
*
|
||||
* The `id` simply identifies the invocation, while `cwd` is the working
|
||||
* directory from which the compiler was invoked.
|
||||
*/
|
||||
compilations(
|
||||
/**
|
||||
* An invocation of the compiler. Note that more than one file may
|
||||
* be compiled per invocation. For example, this command compiles
|
||||
* three source files:
|
||||
*
|
||||
* javac A.java B.java C.java
|
||||
*/
|
||||
unique int id : @compilation,
|
||||
string cwd : string ref
|
||||
);
|
||||
|
||||
/**
|
||||
* The arguments that were passed to the extractor for a compiler
|
||||
* invocation. If `id` is for the compiler invocation
|
||||
*
|
||||
* javac A.java B.java C.java
|
||||
*
|
||||
* then typically there will be rows for
|
||||
*
|
||||
* num | arg
|
||||
* --- | ---
|
||||
* 0 | *path to extractor*
|
||||
* 1 | `--javac-args`
|
||||
* 2 | A.java
|
||||
* 3 | B.java
|
||||
* 4 | C.java
|
||||
*/
|
||||
#keyset[id, num]
|
||||
compilation_args(
|
||||
int id : @compilation ref,
|
||||
int num : int ref,
|
||||
string arg : string ref
|
||||
);
|
||||
|
||||
/**
|
||||
* The source files that are compiled by a compiler invocation.
|
||||
* If `id` is for the compiler invocation
|
||||
*
|
||||
* javac A.java B.java C.java
|
||||
*
|
||||
* then there will be rows for
|
||||
*
|
||||
* num | arg
|
||||
* --- | ---
|
||||
* 0 | A.java
|
||||
* 1 | B.java
|
||||
* 2 | C.java
|
||||
*/
|
||||
#keyset[id, num]
|
||||
compilation_compiling_files(
|
||||
int id : @compilation ref,
|
||||
int num : int ref,
|
||||
int file : @file ref
|
||||
);
|
||||
|
||||
/**
|
||||
* The time taken by the extractor for a compiler invocation.
|
||||
*
|
||||
* For each file `num`, there will be rows for
|
||||
*
|
||||
* kind | seconds
|
||||
* ---- | ---
|
||||
* 1 | CPU seconds used by the extractor frontend
|
||||
* 2 | Elapsed seconds during the extractor frontend
|
||||
* 3 | CPU seconds used by the extractor backend
|
||||
* 4 | Elapsed seconds during the extractor backend
|
||||
*/
|
||||
#keyset[id, num, kind]
|
||||
compilation_time(
|
||||
int id : @compilation ref,
|
||||
int num : int ref,
|
||||
/* kind:
|
||||
1 = frontend_cpu_seconds
|
||||
2 = frontend_elapsed_seconds
|
||||
3 = extractor_cpu_seconds
|
||||
4 = extractor_elapsed_seconds
|
||||
*/
|
||||
int kind : int ref,
|
||||
float seconds : float ref
|
||||
);
|
||||
|
||||
/**
|
||||
* An error or warning generated by the extractor.
|
||||
* The diagnostic message `diagnostic` was generated during compiler
|
||||
* invocation `compilation`, and is the `file_number_diagnostic_number`th
|
||||
* message generated while extracting the `file_number`th file of that
|
||||
* invocation.
|
||||
*/
|
||||
#keyset[compilation, file_number, file_number_diagnostic_number]
|
||||
diagnostic_for(
|
||||
unique int diagnostic : @diagnostic ref,
|
||||
int compilation : @compilation ref,
|
||||
int file_number : int ref,
|
||||
int file_number_diagnostic_number : int ref
|
||||
);
|
||||
|
||||
/**
|
||||
* If extraction was successful, then `cpu_seconds` and
|
||||
* `elapsed_seconds` are the CPU time and elapsed time (respectively)
|
||||
* that extraction took for compiler invocation `id`.
|
||||
*/
|
||||
compilation_finished(
|
||||
unique int id : @compilation ref,
|
||||
float cpu_seconds : float ref,
|
||||
float elapsed_seconds : float ref
|
||||
);
|
||||
|
||||
diagnostics(
|
||||
unique int id: @diagnostic,
|
||||
int severity: int ref,
|
||||
string error_tag: string ref,
|
||||
string error_message: string ref,
|
||||
string full_error_message: string ref,
|
||||
int location: @location_default ref
|
||||
);
|
||||
|
||||
/*
|
||||
* External artifacts
|
||||
*/
|
||||
|
||||
externalData(
|
||||
int id : @externalDataElement,
|
||||
string path : string ref,
|
||||
int column: int ref,
|
||||
string value : string ref
|
||||
);
|
||||
|
||||
snapshotDate(
|
||||
unique date snapshotDate : date ref
|
||||
);
|
||||
|
||||
sourceLocationPrefix(
|
||||
string prefix : string ref
|
||||
);
|
||||
|
||||
/*
|
||||
* Duplicate code
|
||||
*/
|
||||
|
||||
duplicateCode(
|
||||
unique int id : @duplication,
|
||||
string relativePath : string ref,
|
||||
int equivClass : int ref
|
||||
);
|
||||
|
||||
similarCode(
|
||||
unique int id : @similarity,
|
||||
string relativePath : string ref,
|
||||
int equivClass : int ref
|
||||
);
|
||||
|
||||
@duplication_or_similarity = @duplication | @similarity
|
||||
|
||||
tokens(
|
||||
int id : @duplication_or_similarity ref,
|
||||
int offset : int ref,
|
||||
int beginLine : int ref,
|
||||
int beginColumn : int ref,
|
||||
int endLine : int ref,
|
||||
int endColumn : int ref
|
||||
);
|
||||
|
||||
/*
|
||||
* SMAP
|
||||
*/
|
||||
|
||||
smap_header(
|
||||
int outputFileId: @file ref,
|
||||
string outputFilename: string ref,
|
||||
string defaultStratum: string ref
|
||||
);
|
||||
|
||||
smap_files(
|
||||
int outputFileId: @file ref,
|
||||
string stratum: string ref,
|
||||
int inputFileNum: int ref,
|
||||
string inputFileName: string ref,
|
||||
int inputFileId: @file ref
|
||||
);
|
||||
|
||||
smap_lines(
|
||||
int outputFileId: @file ref,
|
||||
string stratum: string ref,
|
||||
int inputFileNum: int ref,
|
||||
int inputStartLine: int ref,
|
||||
int inputLineCount: int ref,
|
||||
int outputStartLine: int ref,
|
||||
int outputLineIncrement: int ref
|
||||
);
|
||||
|
||||
/*
|
||||
* Locations and files
|
||||
*/
|
||||
|
||||
@location = @location_default ;
|
||||
|
||||
locations_default(
|
||||
unique int id: @location_default,
|
||||
int file: @file ref,
|
||||
int beginLine: int ref,
|
||||
int beginColumn: int ref,
|
||||
int endLine: int ref,
|
||||
int endColumn: int ref
|
||||
);
|
||||
|
||||
hasLocation(
|
||||
int locatableid: @locatable ref,
|
||||
int id: @location ref
|
||||
);
|
||||
|
||||
@sourceline = @locatable ;
|
||||
|
||||
#keyset[element_id]
|
||||
numlines(
|
||||
int element_id: @sourceline ref,
|
||||
int num_lines: int ref,
|
||||
int num_code: int ref,
|
||||
int num_comment: int ref
|
||||
);
|
||||
|
||||
files(
|
||||
unique int id: @file,
|
||||
string name: string ref
|
||||
);
|
||||
|
||||
folders(
|
||||
unique int id: @folder,
|
||||
string name: string ref
|
||||
);
|
||||
|
||||
@container = @folder | @file
|
||||
|
||||
containerparent(
|
||||
int parent: @container ref,
|
||||
unique int child: @container ref
|
||||
);
|
||||
|
||||
/*
|
||||
* Java
|
||||
*/
|
||||
|
||||
cupackage(
|
||||
unique int id: @file ref,
|
||||
int packageid: @package ref
|
||||
);
|
||||
|
||||
#keyset[fileid,keyName]
|
||||
jarManifestMain(
|
||||
int fileid: @file ref,
|
||||
string keyName: string ref,
|
||||
string value: string ref
|
||||
);
|
||||
|
||||
#keyset[fileid,entryName,keyName]
|
||||
jarManifestEntries(
|
||||
int fileid: @file ref,
|
||||
string entryName: string ref,
|
||||
string keyName: string ref,
|
||||
string value: string ref
|
||||
);
|
||||
|
||||
packages(
|
||||
unique int id: @package,
|
||||
string nodeName: string ref
|
||||
);
|
||||
|
||||
primitives(
|
||||
unique int id: @primitive,
|
||||
string nodeName: string ref
|
||||
);
|
||||
|
||||
modifiers(
|
||||
unique int id: @modifier,
|
||||
string nodeName: string ref
|
||||
);
|
||||
|
||||
classes(
|
||||
unique int id: @class,
|
||||
string nodeName: string ref,
|
||||
int parentid: @package ref,
|
||||
int sourceid: @class ref
|
||||
);
|
||||
|
||||
isRecord(
|
||||
unique int id: @class ref
|
||||
);
|
||||
|
||||
interfaces(
|
||||
unique int id: @interface,
|
||||
string nodeName: string ref,
|
||||
int parentid: @package ref,
|
||||
int sourceid: @interface ref
|
||||
);
|
||||
|
||||
fielddecls(
|
||||
unique int id: @fielddecl,
|
||||
int parentid: @reftype ref
|
||||
);
|
||||
|
||||
#keyset[fieldId] #keyset[fieldDeclId,pos]
|
||||
fieldDeclaredIn(
|
||||
int fieldId: @field ref,
|
||||
int fieldDeclId: @fielddecl ref,
|
||||
int pos: int ref
|
||||
);
|
||||
|
||||
fields(
|
||||
unique int id: @field,
|
||||
string nodeName: string ref,
|
||||
int typeid: @type ref,
|
||||
int parentid: @reftype ref,
|
||||
int sourceid: @field ref
|
||||
);
|
||||
|
||||
constrs(
|
||||
unique int id: @constructor,
|
||||
string nodeName: string ref,
|
||||
string signature: string ref,
|
||||
int typeid: @type ref,
|
||||
int parentid: @reftype ref,
|
||||
int sourceid: @constructor ref
|
||||
);
|
||||
|
||||
methods(
|
||||
unique int id: @method,
|
||||
string nodeName: string ref,
|
||||
string signature: string ref,
|
||||
int typeid: @type ref,
|
||||
int parentid: @reftype ref,
|
||||
int sourceid: @method ref
|
||||
);
|
||||
|
||||
#keyset[parentid,pos]
|
||||
params(
|
||||
unique int id: @param,
|
||||
int typeid: @type ref,
|
||||
int pos: int ref,
|
||||
int parentid: @callable ref,
|
||||
int sourceid: @param ref
|
||||
);
|
||||
|
||||
paramName(
|
||||
unique int id: @param ref,
|
||||
string nodeName: string ref
|
||||
);
|
||||
|
||||
isVarargsParam(
|
||||
int param: @param ref
|
||||
);
|
||||
|
||||
exceptions(
|
||||
unique int id: @exception,
|
||||
int typeid: @type ref,
|
||||
int parentid: @callable ref
|
||||
);
|
||||
|
||||
isAnnotType(
|
||||
int interfaceid: @interface ref
|
||||
);
|
||||
|
||||
isAnnotElem(
|
||||
int methodid: @method ref
|
||||
);
|
||||
|
||||
annotValue(
|
||||
int parentid: @annotation ref,
|
||||
int id2: @method ref,
|
||||
unique int value: @expr ref
|
||||
);
|
||||
|
||||
isEnumType(
|
||||
int classid: @class ref
|
||||
);
|
||||
|
||||
isEnumConst(
|
||||
int fieldid: @field ref
|
||||
);
|
||||
|
||||
#keyset[parentid,pos]
|
||||
typeVars(
|
||||
unique int id: @typevariable,
|
||||
string nodeName: string ref,
|
||||
int pos: int ref,
|
||||
int kind: int ref, // deprecated
|
||||
int parentid: @classorinterfaceorcallable ref
|
||||
);
|
||||
|
||||
wildcards(
|
||||
unique int id: @wildcard,
|
||||
string nodeName: string ref,
|
||||
int kind: int ref
|
||||
);
|
||||
|
||||
#keyset[parentid,pos]
|
||||
typeBounds(
|
||||
unique int id: @typebound,
|
||||
int typeid: @reftype ref,
|
||||
int pos: int ref,
|
||||
int parentid: @boundedtype ref
|
||||
);
|
||||
|
||||
#keyset[parentid,pos]
|
||||
typeArgs(
|
||||
int argumentid: @reftype ref,
|
||||
int pos: int ref,
|
||||
int parentid: @classorinterfaceorcallable ref
|
||||
);
|
||||
|
||||
isParameterized(
|
||||
int memberid: @member ref
|
||||
);
|
||||
|
||||
isRaw(
|
||||
int memberid: @member ref
|
||||
);
|
||||
|
||||
erasure(
|
||||
unique int memberid: @member ref,
|
||||
int erasureid: @member ref
|
||||
);
|
||||
|
||||
#keyset[classid] #keyset[parent]
|
||||
isAnonymClass(
|
||||
int classid: @class ref,
|
||||
int parent: @classinstancexpr ref
|
||||
);
|
||||
|
||||
#keyset[typeid] #keyset[parent]
|
||||
isLocalClassOrInterface(
|
||||
int typeid: @classorinterface ref,
|
||||
int parent: @localtypedeclstmt ref
|
||||
);
|
||||
|
||||
isDefConstr(
|
||||
int constructorid: @constructor ref
|
||||
);
|
||||
|
||||
#keyset[exprId]
|
||||
lambdaKind(
|
||||
int exprId: @lambdaexpr ref,
|
||||
int bodyKind: int ref
|
||||
);
|
||||
|
||||
arrays(
|
||||
unique int id: @array,
|
||||
string nodeName: string ref,
|
||||
int elementtypeid: @type ref,
|
||||
int dimension: int ref,
|
||||
int componenttypeid: @type ref
|
||||
);
|
||||
|
||||
enclInReftype(
|
||||
unique int child: @reftype ref,
|
||||
int parent: @reftype ref
|
||||
);
|
||||
|
||||
extendsReftype(
|
||||
int id1: @reftype ref,
|
||||
int id2: @classorinterface ref
|
||||
);
|
||||
|
||||
implInterface(
|
||||
int id1: @classorarray ref,
|
||||
int id2: @interface ref
|
||||
);
|
||||
|
||||
permits(
|
||||
int id1: @classorinterface ref,
|
||||
int id2: @classorinterface ref
|
||||
);
|
||||
|
||||
hasModifier(
|
||||
int id1: @modifiable ref,
|
||||
int id2: @modifier ref
|
||||
);
|
||||
|
||||
imports(
|
||||
unique int id: @import,
|
||||
int holder: @classorinterfaceorpackage ref,
|
||||
string name: string ref,
|
||||
int kind: int ref
|
||||
);
|
||||
|
||||
#keyset[parent,idx]
|
||||
stmts(
|
||||
unique int id: @stmt,
|
||||
int kind: int ref,
|
||||
int parent: @stmtparent ref,
|
||||
int idx: int ref,
|
||||
int bodydecl: @callable ref
|
||||
);
|
||||
|
||||
@stmtparent = @callable | @stmt | @switchexpr;
|
||||
|
||||
case @stmt.kind of
|
||||
0 = @block
|
||||
| 1 = @ifstmt
|
||||
| 2 = @forstmt
|
||||
| 3 = @enhancedforstmt
|
||||
| 4 = @whilestmt
|
||||
| 5 = @dostmt
|
||||
| 6 = @trystmt
|
||||
| 7 = @switchstmt
|
||||
| 8 = @synchronizedstmt
|
||||
| 9 = @returnstmt
|
||||
| 10 = @throwstmt
|
||||
| 11 = @breakstmt
|
||||
| 12 = @continuestmt
|
||||
| 13 = @emptystmt
|
||||
| 14 = @exprstmt
|
||||
| 15 = @labeledstmt
|
||||
| 16 = @assertstmt
|
||||
| 17 = @localvariabledeclstmt
|
||||
| 18 = @localtypedeclstmt
|
||||
| 19 = @constructorinvocationstmt
|
||||
| 20 = @superconstructorinvocationstmt
|
||||
| 21 = @case
|
||||
| 22 = @catchclause
|
||||
| 23 = @yieldstmt
|
||||
;
|
||||
|
||||
#keyset[parent,idx]
|
||||
exprs(
|
||||
unique int id: @expr,
|
||||
int kind: int ref,
|
||||
int typeid: @type ref,
|
||||
int parent: @exprparent ref,
|
||||
int idx: int ref
|
||||
);
|
||||
|
||||
callableEnclosingExpr(
|
||||
unique int id: @expr ref,
|
||||
int callable_id: @callable ref
|
||||
);
|
||||
|
||||
statementEnclosingExpr(
|
||||
unique int id: @expr ref,
|
||||
int statement_id: @stmt ref
|
||||
);
|
||||
|
||||
isParenthesized(
|
||||
unique int id: @expr ref,
|
||||
int parentheses: int ref
|
||||
);
|
||||
|
||||
case @expr.kind of
|
||||
1 = @arrayaccess
|
||||
| 2 = @arraycreationexpr
|
||||
| 3 = @arrayinit
|
||||
| 4 = @assignexpr
|
||||
| 5 = @assignaddexpr
|
||||
| 6 = @assignsubexpr
|
||||
| 7 = @assignmulexpr
|
||||
| 8 = @assigndivexpr
|
||||
| 9 = @assignremexpr
|
||||
| 10 = @assignandexpr
|
||||
| 11 = @assignorexpr
|
||||
| 12 = @assignxorexpr
|
||||
| 13 = @assignlshiftexpr
|
||||
| 14 = @assignrshiftexpr
|
||||
| 15 = @assignurshiftexpr
|
||||
| 16 = @booleanliteral
|
||||
| 17 = @integerliteral
|
||||
| 18 = @longliteral
|
||||
| 19 = @floatingpointliteral
|
||||
| 20 = @doubleliteral
|
||||
| 21 = @characterliteral
|
||||
| 22 = @stringliteral
|
||||
| 23 = @nullliteral
|
||||
| 24 = @mulexpr
|
||||
| 25 = @divexpr
|
||||
| 26 = @remexpr
|
||||
| 27 = @addexpr
|
||||
| 28 = @subexpr
|
||||
| 29 = @lshiftexpr
|
||||
| 30 = @rshiftexpr
|
||||
| 31 = @urshiftexpr
|
||||
| 32 = @andbitexpr
|
||||
| 33 = @orbitexpr
|
||||
| 34 = @xorbitexpr
|
||||
| 35 = @andlogicalexpr
|
||||
| 36 = @orlogicalexpr
|
||||
| 37 = @ltexpr
|
||||
| 38 = @gtexpr
|
||||
| 39 = @leexpr
|
||||
| 40 = @geexpr
|
||||
| 41 = @eqexpr
|
||||
| 42 = @neexpr
|
||||
| 43 = @postincexpr
|
||||
| 44 = @postdecexpr
|
||||
| 45 = @preincexpr
|
||||
| 46 = @predecexpr
|
||||
| 47 = @minusexpr
|
||||
| 48 = @plusexpr
|
||||
| 49 = @bitnotexpr
|
||||
| 50 = @lognotexpr
|
||||
| 51 = @castexpr
|
||||
| 52 = @newexpr
|
||||
| 53 = @conditionalexpr
|
||||
| 54 = @parexpr // deprecated
|
||||
| 55 = @instanceofexpr
|
||||
| 56 = @localvariabledeclexpr
|
||||
| 57 = @typeliteral
|
||||
| 58 = @thisaccess
|
||||
| 59 = @superaccess
|
||||
| 60 = @varaccess
|
||||
| 61 = @methodaccess
|
||||
| 62 = @unannotatedtypeaccess
|
||||
| 63 = @arraytypeaccess
|
||||
| 64 = @packageaccess
|
||||
| 65 = @wildcardtypeaccess
|
||||
| 66 = @declannotation
|
||||
| 67 = @uniontypeaccess
|
||||
| 68 = @lambdaexpr
|
||||
| 69 = @memberref
|
||||
| 70 = @annotatedtypeaccess
|
||||
| 71 = @typeannotation
|
||||
| 72 = @intersectiontypeaccess
|
||||
| 73 = @switchexpr
|
||||
;
|
||||
|
||||
@classinstancexpr = @newexpr | @lambdaexpr | @memberref
|
||||
|
||||
@annotation = @declannotation | @typeannotation
|
||||
@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess
|
||||
|
||||
@assignment = @assignexpr
|
||||
| @assignop;
|
||||
|
||||
@unaryassignment = @postincexpr
|
||||
| @postdecexpr
|
||||
| @preincexpr
|
||||
| @predecexpr;
|
||||
|
||||
@assignop = @assignaddexpr
|
||||
| @assignsubexpr
|
||||
| @assignmulexpr
|
||||
| @assigndivexpr
|
||||
| @assignremexpr
|
||||
| @assignandexpr
|
||||
| @assignorexpr
|
||||
| @assignxorexpr
|
||||
| @assignlshiftexpr
|
||||
| @assignrshiftexpr
|
||||
| @assignurshiftexpr;
|
||||
|
||||
@literal = @booleanliteral
|
||||
| @integerliteral
|
||||
| @longliteral
|
||||
| @floatingpointliteral
|
||||
| @doubleliteral
|
||||
| @characterliteral
|
||||
| @stringliteral
|
||||
| @nullliteral;
|
||||
|
||||
@binaryexpr = @mulexpr
|
||||
| @divexpr
|
||||
| @remexpr
|
||||
| @addexpr
|
||||
| @subexpr
|
||||
| @lshiftexpr
|
||||
| @rshiftexpr
|
||||
| @urshiftexpr
|
||||
| @andbitexpr
|
||||
| @orbitexpr
|
||||
| @xorbitexpr
|
||||
| @andlogicalexpr
|
||||
| @orlogicalexpr
|
||||
| @ltexpr
|
||||
| @gtexpr
|
||||
| @leexpr
|
||||
| @geexpr
|
||||
| @eqexpr
|
||||
| @neexpr;
|
||||
|
||||
@unaryexpr = @postincexpr
|
||||
| @postdecexpr
|
||||
| @preincexpr
|
||||
| @predecexpr
|
||||
| @minusexpr
|
||||
| @plusexpr
|
||||
| @bitnotexpr
|
||||
| @lognotexpr;
|
||||
|
||||
@caller = @classinstancexpr
|
||||
| @methodaccess
|
||||
| @constructorinvocationstmt
|
||||
| @superconstructorinvocationstmt;
|
||||
|
||||
callableBinding(
|
||||
unique int callerid: @caller ref,
|
||||
int callee: @callable ref
|
||||
);
|
||||
|
||||
memberRefBinding(
|
||||
unique int id: @expr ref,
|
||||
int callable: @callable ref
|
||||
);
|
||||
|
||||
@exprparent = @stmt | @expr | @callable | @field | @fielddecl | @class | @interface | @param | @localvar | @typevariable;
|
||||
|
||||
variableBinding(
|
||||
unique int expr: @varaccess ref,
|
||||
int variable: @variable ref
|
||||
);
|
||||
|
||||
@variable = @localscopevariable | @field;
|
||||
|
||||
@localscopevariable = @localvar | @param;
|
||||
|
||||
localvars(
|
||||
unique int id: @localvar,
|
||||
string nodeName: string ref,
|
||||
int typeid: @type ref,
|
||||
int parentid: @localvariabledeclexpr ref
|
||||
);
|
||||
|
||||
@namedexprorstmt = @breakstmt
|
||||
| @continuestmt
|
||||
| @labeledstmt
|
||||
| @literal;
|
||||
|
||||
namestrings(
|
||||
string name: string ref,
|
||||
string value: string ref,
|
||||
unique int parent: @namedexprorstmt ref
|
||||
);
|
||||
|
||||
/*
|
||||
* Modules
|
||||
*/
|
||||
|
||||
#keyset[name]
|
||||
modules(
|
||||
unique int id: @module,
|
||||
string name: string ref
|
||||
);
|
||||
|
||||
isOpen(
|
||||
int id: @module ref
|
||||
);
|
||||
|
||||
#keyset[fileId]
|
||||
cumodule(
|
||||
int fileId: @file ref,
|
||||
int moduleId: @module ref
|
||||
);
|
||||
|
||||
@directive = @requires
|
||||
| @exports
|
||||
| @opens
|
||||
| @uses
|
||||
| @provides
|
||||
|
||||
#keyset[directive]
|
||||
directives(
|
||||
int id: @module ref,
|
||||
int directive: @directive ref
|
||||
);
|
||||
|
||||
requires(
|
||||
unique int id: @requires,
|
||||
int target: @module ref
|
||||
);
|
||||
|
||||
isTransitive(
|
||||
int id: @requires ref
|
||||
);
|
||||
|
||||
isStatic(
|
||||
int id: @requires ref
|
||||
);
|
||||
|
||||
exports(
|
||||
unique int id: @exports,
|
||||
int target: @package ref
|
||||
);
|
||||
|
||||
exportsTo(
|
||||
int id: @exports ref,
|
||||
int target: @module ref
|
||||
);
|
||||
|
||||
opens(
|
||||
unique int id: @opens,
|
||||
int target: @package ref
|
||||
);
|
||||
|
||||
opensTo(
|
||||
int id: @opens ref,
|
||||
int target: @module ref
|
||||
);
|
||||
|
||||
uses(
|
||||
unique int id: @uses,
|
||||
string serviceInterface: string ref
|
||||
);
|
||||
|
||||
provides(
|
||||
unique int id: @provides,
|
||||
string serviceInterface: string ref
|
||||
);
|
||||
|
||||
providesWith(
|
||||
int id: @provides ref,
|
||||
string serviceImpl: string ref
|
||||
);
|
||||
|
||||
/*
|
||||
* Javadoc
|
||||
*/
|
||||
|
||||
javadoc(
|
||||
unique int id: @javadoc
|
||||
);
|
||||
|
||||
isNormalComment(
|
||||
int commentid : @javadoc ref
|
||||
);
|
||||
|
||||
isEolComment(
|
||||
int commentid : @javadoc ref
|
||||
);
|
||||
|
||||
hasJavadoc(
|
||||
int documentableid: @member ref,
|
||||
int javadocid: @javadoc ref
|
||||
);
|
||||
|
||||
#keyset[parentid,idx]
|
||||
javadocTag(
|
||||
unique int id: @javadocTag,
|
||||
string name: string ref,
|
||||
int parentid: @javadocParent ref,
|
||||
int idx: int ref
|
||||
);
|
||||
|
||||
#keyset[parentid,idx]
|
||||
javadocText(
|
||||
unique int id: @javadocText,
|
||||
string text: string ref,
|
||||
int parentid: @javadocParent ref,
|
||||
int idx: int ref
|
||||
);
|
||||
|
||||
@javadocParent = @javadoc | @javadocTag;
|
||||
@javadocElement = @javadocTag | @javadocText;
|
||||
|
||||
@classorinterface = @interface | @class;
|
||||
@classorinterfaceorpackage = @classorinterface | @package;
|
||||
@classorinterfaceorcallable = @classorinterface | @callable;
|
||||
@boundedtype = @typevariable | @wildcard;
|
||||
@reftype = @classorinterface | @array | @boundedtype;
|
||||
@classorarray = @class | @array;
|
||||
@type = @primitive | @reftype;
|
||||
@callable = @method | @constructor;
|
||||
@element = @file | @package | @primitive | @class | @interface | @method | @constructor | @modifier | @param | @exception | @field |
|
||||
@annotation | @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl;
|
||||
|
||||
@modifiable = @member_modifiable| @param | @localvar ;
|
||||
|
||||
@member_modifiable = @class | @interface | @method | @constructor | @field ;
|
||||
|
||||
@member = @method | @constructor | @field | @reftype ;
|
||||
|
||||
@locatable = @file | @class | @interface | @fielddecl | @field | @constructor | @method | @param | @exception
|
||||
| @boundedtype | @typebound | @array | @primitive
|
||||
| @import | @stmt | @expr | @localvar | @javadoc | @javadocTag | @javadocText
|
||||
| @xmllocatable;
|
||||
|
||||
@top = @element | @locatable | @folder;
|
||||
|
||||
/*
|
||||
* XML Files
|
||||
*/
|
||||
|
||||
xmlEncoding(
|
||||
unique int id: @file ref,
|
||||
string encoding: string ref
|
||||
);
|
||||
|
||||
xmlDTDs(
|
||||
unique int id: @xmldtd,
|
||||
string root: string ref,
|
||||
string publicId: string ref,
|
||||
string systemId: string ref,
|
||||
int fileid: @file ref
|
||||
);
|
||||
|
||||
xmlElements(
|
||||
unique int id: @xmlelement,
|
||||
string name: string ref,
|
||||
int parentid: @xmlparent ref,
|
||||
int idx: int ref,
|
||||
int fileid: @file ref
|
||||
);
|
||||
|
||||
xmlAttrs(
|
||||
unique int id: @xmlattribute,
|
||||
int elementid: @xmlelement ref,
|
||||
string name: string ref,
|
||||
string value: string ref,
|
||||
int idx: int ref,
|
||||
int fileid: @file ref
|
||||
);
|
||||
|
||||
xmlNs(
|
||||
int id: @xmlnamespace,
|
||||
string prefixName: string ref,
|
||||
string URI: string ref,
|
||||
int fileid: @file ref
|
||||
);
|
||||
|
||||
xmlHasNs(
|
||||
int elementId: @xmlnamespaceable ref,
|
||||
int nsId: @xmlnamespace ref,
|
||||
int fileid: @file ref
|
||||
);
|
||||
|
||||
xmlComments(
|
||||
unique int id: @xmlcomment,
|
||||
string text: string ref,
|
||||
int parentid: @xmlparent ref,
|
||||
int fileid: @file ref
|
||||
);
|
||||
|
||||
xmlChars(
|
||||
unique int id: @xmlcharacters,
|
||||
string text: string ref,
|
||||
int parentid: @xmlparent ref,
|
||||
int idx: int ref,
|
||||
int isCDATA: int ref,
|
||||
int fileid: @file ref
|
||||
);
|
||||
|
||||
@xmlparent = @file | @xmlelement;
|
||||
@xmlnamespaceable = @xmlelement | @xmlattribute;
|
||||
|
||||
xmllocations(
|
||||
int xmlElement: @xmllocatable ref,
|
||||
int location: @location_default ref
|
||||
);
|
||||
|
||||
@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
|
||||
|
||||
/*
|
||||
* configuration files with key value pairs
|
||||
*/
|
||||
|
||||
configs(
|
||||
unique int id: @config
|
||||
);
|
||||
|
||||
configNames(
|
||||
unique int id: @configName,
|
||||
int config: @config ref,
|
||||
string name: string ref
|
||||
);
|
||||
|
||||
configValues(
|
||||
unique int id: @configValue,
|
||||
int config: @config ref,
|
||||
string value: string ref
|
||||
);
|
||||
|
||||
configLocations(
|
||||
int locatable: @configLocatable ref,
|
||||
int location: @location_default ref
|
||||
);
|
||||
|
||||
@configLocatable = @config | @configName | @configValue;
|
||||
@@ -0,0 +1,3 @@
|
||||
description: Use the more specific classorinterface rather than type
|
||||
compatibility: backwards
|
||||
|
||||
@@ -5,7 +5,7 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -17,6 +17,7 @@ import com.semmle.js.extractor.trapcache.CachingTrapWriter;
|
||||
import com.semmle.js.extractor.trapcache.ITrapCache;
|
||||
import com.semmle.util.data.StringUtil;
|
||||
import com.semmle.util.exception.Exceptions;
|
||||
import com.semmle.util.exception.ResourceError;
|
||||
import com.semmle.util.extraction.ExtractorOutputConfig;
|
||||
import com.semmle.util.files.FileUtil;
|
||||
import com.semmle.util.io.WholeIO;
|
||||
@@ -438,7 +439,16 @@ public class FileExtractor {
|
||||
}
|
||||
|
||||
// populate source archive
|
||||
String source = new WholeIO(config.getDefaultEncoding()).strictread(f);
|
||||
WholeIO wholeIO = new WholeIO(config.getDefaultEncoding(), true);
|
||||
String source = wholeIO.read(f);
|
||||
if (source == null) {
|
||||
if (wholeIO.getLastException() instanceof CharacterCodingException) {
|
||||
System.err.println("Skipped due to unsupported character encoding: " + f);
|
||||
return 0;
|
||||
} else {
|
||||
throw new ResourceError("Failed to read file " + f, wholeIO.getLastException());
|
||||
}
|
||||
}
|
||||
outputConfig.getSourceArchive().add(f, source);
|
||||
|
||||
// extract language-independent bits
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: codeql/javascript-experimental-atm-src
|
||||
name: codeql/javascript-experimental-atm-queries
|
||||
language: javascript
|
||||
version: 0.0.0
|
||||
suites: codeql-suites
|
||||
|
||||
@@ -183,6 +183,7 @@ class PyFunctionObject extends FunctionObject {
|
||||
}
|
||||
|
||||
/** Factored out to help join ordering */
|
||||
pragma[noinline]
|
||||
private predicate implicitlyReturns(Object none_, ClassObject noneType) {
|
||||
noneType = theNoneType() and
|
||||
not this.getFunction().isGenerator() and
|
||||
|
||||
10
ruby/.codeqlmanifest.json
Normal file
10
ruby/.codeqlmanifest.json
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
1
ruby/.gitattributes
vendored
Normal file
1
ruby/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Cargo.lock -diff -whitespace
|
||||
8
ruby/.gitignore
vendored
Normal file
8
ruby/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/target
|
||||
extractor-pack
|
||||
.vscode/launch.json
|
||||
.cache
|
||||
ql/test/**/*.testproj
|
||||
ql/test/**/*.actual
|
||||
ql/test/**/CONSISTENCY
|
||||
.codeql
|
||||
14
ruby/.vscode/tasks.json
vendored
Normal file
14
ruby/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "cargo",
|
||||
"subcommand": "build",
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"group": "build",
|
||||
"label": "Rust: cargo build"
|
||||
}
|
||||
]
|
||||
}
|
||||
64
ruby/CONTRIBUTING.md
Normal file
64
ruby/CONTRIBUTING.md
Normal file
@@ -0,0 +1,64 @@
|
||||
## Contributing
|
||||
|
||||
Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
|
||||
|
||||
Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE).
|
||||
|
||||
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
|
||||
|
||||
## Building and testing
|
||||
|
||||
See [Developer information](docs/HOWTO.md) for information on building the Ruby extractor. There is no need to rebuild the extractor if you are only developing queries.
|
||||
|
||||
1. Install the CodeQL CLI as described in [Getting started with the CodeQL CLI](https://codeql.github.com/docs/codeql-cli/getting-started-with-the-codeql-cli/).
|
||||
|
||||
2. Ensure that `<extraction-root>/codeql` is in your `PATH`.
|
||||
|
||||
3. Clone this repository into `<extraction-root>/codeql-ruby` and change to this directory.
|
||||
|
||||
4. To run all tests in a directory and its subdirectories, run `codeql test run <directory>`, for example `codeql test run ql/test/query-tests/security`.
|
||||
|
||||
6. To run an individual test, run `codeql test run <filename>`, where `<filename>` is a `.ql` or `.qlref` file, for example `codeql test run ql/test/query-tests/security/cwe-078/CommandInjection.qlref`.
|
||||
|
||||
## Adding a new query
|
||||
|
||||
If you have an idea for a query that you would like to share with other CodeQL users, please open a pull request to add it to this repository.
|
||||
Follow the steps below to help other users understand what your query does, and to ensure that your query is consistent with the other CodeQL queries.
|
||||
|
||||
1. **Consult the documentation for query writers**
|
||||
|
||||
There is lots of useful documentation to help you write CodeQL queries, ranging from information about query file structure to language-specific tutorials. For more information on the documentation available, see [Writing CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/) and the [CodeQL documentation](https://codeql.github.com/docs).
|
||||
|
||||
2. **Format your code correctly**
|
||||
|
||||
All of the standard CodeQL queries and libraries are uniformly formatted for clarity and consistency, so we strongly recommend that all contributions follow the same formatting guidelines. If you use the CodeQL extension for Visual Studio Code, you can auto-format your query using the [Format Document command](https://code.visualstudio.com/docs/editor/codebasics#_formatting). For more information, see the [QL style guide](https://github.com/github/codeql/blob/main/docs/ql-style-guide.md).
|
||||
|
||||
3. **Make sure your query has the correct metadata**
|
||||
|
||||
Query metadata is used to identify your query and make sure the query results are displayed properly.
|
||||
The most important metadata to include are the `@name`, `@description`, and the `@kind`.
|
||||
Other metadata properties (`@precision`, `@severity`, and `@tags`) are usually added after the query has been reviewed by the maintainers.
|
||||
For more information on writing query metadata, see the [Query metadata style guide](https://github.com/github/codeql/blob/main/docs/query-metadata-style-guide.md).
|
||||
|
||||
4. **Make sure the `select` statement is compatible with the query type**
|
||||
|
||||
The `select` statement of your query must be compatible with the query type (determined by the `@kind` metadata property) for alert or path results to be displayed correctly in LGTM and Visual Studio Code.
|
||||
For more information on `select` statement format, see [About CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/about-codeql-queries/#select-clause) on the [CodeQL documentation](https://codeql.github.com/docs) site.
|
||||
|
||||
5. **Write a query help file**
|
||||
|
||||
Query help files explain the purpose of your query to other users. Write your query help in a `.qhelp` file and save it in the same directory as your new query.
|
||||
For more information on writing query help, see the [Query help style guide](https://github.com/github/codeql/blob/main/docs/query-help-style-guide.md).
|
||||
|
||||
6. **Maintain backwards compatibility**
|
||||
|
||||
The standard CodeQL libraries must evolve in a backwards compatible manner. If any backwards incompatible changes need to be made, the existing API must first be marked as deprecated. This is done by adding a `deprecated` annotation along with a QLDoc reference to the replacement API. Only after at least one full release cycle has elapsed may the old API be removed.
|
||||
|
||||
In addition to contributions to our standard queries and libraries, we also welcome contributions of a more experimental nature, which do not need to fulfill all the requirements listed above. See the guidelines for [experimental queries and libraries](ql/docs/experimental.md) for details.
|
||||
|
||||
## Resources
|
||||
|
||||
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
||||
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
|
||||
- [GitHub Help](https://help.github.com)
|
||||
- [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
||||
BIN
ruby/Cargo.lock
generated
Normal file
BIN
ruby/Cargo.lock
generated
Normal file
Binary file not shown.
7
ruby/Cargo.toml
Normal file
7
ruby/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"autobuilder",
|
||||
"extractor",
|
||||
"generator",
|
||||
"node-types",
|
||||
]
|
||||
71
ruby/Makefile
Normal file
71
ruby/Makefile
Normal file
@@ -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
|
||||
50
ruby/README.md
Normal file
50
ruby/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Ruby analysis support for CodeQL
|
||||
|
||||
This open-source repository contains the extractor, CodeQL libraries, and queries that power Ruby
|
||||
support in [LGTM](https://lgtm.com) and the other CodeQL products that [GitHub](https://github.com)
|
||||
makes available to its customers worldwide.
|
||||
|
||||
It contains two major components:
|
||||
- an extractor, written in Rust, that parses Ruby source code and converts it into a database
|
||||
that can be queried using CodeQL.
|
||||
- static analysis libraries and queries written in [CodeQL](https://codeql.github.com/docs/) that can be
|
||||
used to analyze such a database to find coding mistakes or security vulnerabilities.
|
||||
|
||||
The goal of this project is to provide comprehensive static analysis support for Ruby in CodeQL.
|
||||
|
||||
For the queries and libraries that power CodeQL support for other languages, visit [the CodeQL
|
||||
repository](https://github.com/github/codeql).
|
||||
|
||||
## Installation
|
||||
|
||||
Simply clone this repository. There are no external dependencies.
|
||||
|
||||
If you want to use the CodeQL extension for Visual Studio Code, import this repository into your VS
|
||||
Code workspace.
|
||||
|
||||
## Usage
|
||||
|
||||
To analyze a Ruby codebase, either use the [CodeQL command-line
|
||||
interface](https://codeql.github.com/docs/codeql-cli/) to create a database yourself, or
|
||||
download a pre-built database from [LGTM.com](https://lgtm.com/). You can then run any of the
|
||||
queries contained in this repository either on the command line or using the VS Code extension.
|
||||
|
||||
Note that the [lgtm.com](https://github.com/github/codeql-ruby/tree/lgtm.com) branch of this
|
||||
repository corresponds to the version of the queries that is currently deployed on LGTM.com.
|
||||
The [main](https://github.com/github/codeql-ruby/tree/main) branch may contain changes that
|
||||
have not been deployed yet, so you may need to upgrade databases downloaded from [LGTM.com](https://lgtm.com) before
|
||||
running queries on them.
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are welcome! Please see our [contribution guidelines](CONTRIBUTING.md) and our
|
||||
[code of conduct](CODE_OF_CONDUCT.md) for details on how to participate in our community.
|
||||
|
||||
## Licensing
|
||||
|
||||
The code in this repository is licensed under the [MIT license](LICENSE).
|
||||
|
||||
## Resources
|
||||
|
||||
- [Writing CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/)
|
||||
- [CodeQL documentation](https://codeql.github.com/docs/)
|
||||
16
ruby/actions/create-extractor-pack/action.yml
Normal file
16
ruby/actions/create-extractor-pack/action.yml
Normal file
@@ -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
|
||||
9
ruby/autobuilder/Cargo.toml
Normal file
9
ruby/autobuilder/Cargo.toml
Normal file
@@ -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]
|
||||
39
ruby/autobuilder/src/main.rs
Normal file
39
ruby/autobuilder/src/main.rs
Normal file
@@ -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 let Some(stripped) = line.strip_prefix("include:") {
|
||||
cmd.arg("--include").arg(stripped);
|
||||
} else if let Some(stripped) = line.strip_prefix("exclude:") {
|
||||
cmd.arg("--exclude").arg(stripped);
|
||||
}
|
||||
}
|
||||
let exit = &cmd.spawn()?.wait()?;
|
||||
std::process::exit(exit.code().unwrap_or(1))
|
||||
}
|
||||
2
ruby/change-notes/2021-10-14-codeql-ruby-beta.md
Normal file
2
ruby/change-notes/2021-10-14-codeql-ruby-beta.md
Normal file
@@ -0,0 +1,2 @@
|
||||
codescanning
|
||||
* Open-sourcing the Ruby extractor and CodeQL queries as part of the [public beta](https://github.com/github/roadmap/issues/136) release.
|
||||
2
ruby/change-notes/2021-10-20-path-injection.md
Normal file
2
ruby/change-notes/2021-10-20-path-injection.md
Normal file
@@ -0,0 +1,2 @@
|
||||
lgtm,codescanning
|
||||
* A new query (`rb/path-injection`) has been added. The query finds file operations using paths that derive from user input without being sanitized.
|
||||
14
ruby/codeql-extractor.yml
Normal file
14
ruby/codeql-extractor.yml
Normal file
@@ -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
|
||||
47
ruby/doc/HOWTO.md
Normal file
47
ruby/doc/HOWTO.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Developer information
|
||||
|
||||
This document contains information about common development tasks.
|
||||
|
||||
## 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 <database-path> -l ruby -s <project-source-path> --search-path <extractor-pack-path>
|
||||
```
|
||||
|
||||
## Running qltests
|
||||
|
||||
Run
|
||||
|
||||
```bash
|
||||
codeql test run <test-path> --search-path <repository-root-path>
|
||||
```
|
||||
|
||||
## Writing database upgrade scripts
|
||||
|
||||
See [this guide](prepare-db-upgrade.md).
|
||||
91
ruby/doc/prepare-db-upgrade.md
Normal file
91
ruby/doc/prepare-db-upgrade.md
Normal file
@@ -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).
|
||||
20
ruby/extractor/Cargo.toml
Normal file
20
ruby/extractor/Cargo.toml
Normal file
@@ -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 = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "bb6a42e42b048627a74a127d3e0184c1eef01de9" }
|
||||
clap = "2.33"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
rayon = "1.5.0"
|
||||
num_cpus = "1.13.0"
|
||||
regex = "1.4.3"
|
||||
829
ruby/extractor/src/extractor.rs
Normal file
829
ruby/extractor/src/extractor.rs
Normal file
@@ -0,0 +1,829 @@
|
||||
use node_types::{EntryKind, Field, NodeTypeMap, Storage, TypeName};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap as Map;
|
||||
use std::collections::BTreeSet as Set;
|
||||
use std::fmt;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use tracing::{error, info, span, Level};
|
||||
use tree_sitter::{Language, Node, Parser, Range, Tree};
|
||||
|
||||
pub struct TrapWriter {
|
||||
/// The accumulated trap entries
|
||||
trap_output: Vec<TrapEntry>,
|
||||
/// A counter for generating fresh labels
|
||||
counter: u32,
|
||||
/// cache of global keys
|
||||
global_keys: std::collections::HashMap<String, Label>,
|
||||
}
|
||||
|
||||
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<Arg>) {
|
||||
self.trap_output
|
||||
.push(TrapEntry::GenericTuple(table_name.to_owned(), args))
|
||||
}
|
||||
|
||||
fn populate_file(&mut self, absolute_path: &Path) -> Label {
|
||||
let (file_label, fresh) = self.global_id(&full_id_for_file(absolute_path));
|
||||
if fresh {
|
||||
self.add_tuple(
|
||||
"files",
|
||||
vec![
|
||||
Arg::Label(file_label),
|
||||
Arg::String(normalize_path(absolute_path)),
|
||||
],
|
||||
);
|
||||
self.populate_parent_folders(file_label, absolute_path.parent());
|
||||
}
|
||||
file_label
|
||||
}
|
||||
|
||||
fn populate_empty_file(&mut self) -> Label {
|
||||
let (file_label, fresh) = self.global_id("empty;sourcefile");
|
||||
if fresh {
|
||||
self.add_tuple(
|
||||
"files",
|
||||
vec![Arg::Label(file_label), Arg::String("".to_string())],
|
||||
);
|
||||
}
|
||||
file_label
|
||||
}
|
||||
|
||||
pub fn populate_empty_location(&mut self) {
|
||||
let file_label = self.populate_empty_file();
|
||||
self.location(file_label, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
fn populate_parent_folders(&mut self, child_label: Label, path: Option<&Path>) {
|
||||
let mut path = path;
|
||||
let mut child_label = child_label;
|
||||
loop {
|
||||
match path {
|
||||
None => break,
|
||||
Some(folder) => {
|
||||
let (folder_label, fresh) = self.global_id(&full_id_for_folder(folder));
|
||||
self.add_tuple(
|
||||
"containerparent",
|
||||
vec![Arg::Label(folder_label), Arg::Label(child_label)],
|
||||
);
|
||||
if fresh {
|
||||
self.add_tuple(
|
||||
"folders",
|
||||
vec![
|
||||
Arg::Label(folder_label),
|
||||
Arg::String(normalize_path(folder)),
|
||||
],
|
||||
);
|
||||
path = folder.parent();
|
||||
child_label = folder_label;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn location(
|
||||
&mut self,
|
||||
file_label: Label,
|
||||
start_line: usize,
|
||||
start_column: usize,
|
||||
end_line: usize,
|
||||
end_column: usize,
|
||||
) -> Label {
|
||||
let (loc_label, fresh) = self.global_id(&format!(
|
||||
"loc,{{{}}},{},{},{},{}",
|
||||
file_label, start_line, start_column, end_line, end_column
|
||||
));
|
||||
if fresh {
|
||||
self.add_tuple(
|
||||
"locations_default",
|
||||
vec![
|
||||
Arg::Label(loc_label),
|
||||
Arg::Label(file_label),
|
||||
Arg::Int(start_line),
|
||||
Arg::Int(start_column),
|
||||
Arg::Int(end_line),
|
||||
Arg::Int(end_column),
|
||||
],
|
||||
);
|
||||
}
|
||||
loc_label
|
||||
}
|
||||
|
||||
fn comment(&mut self, text: String) {
|
||||
self.trap_output.push(TrapEntry::Comment(text));
|
||||
}
|
||||
|
||||
pub fn output(self, writer: &mut dyn Write) -> std::io::Result<()> {
|
||||
write!(writer, "{}", Program(self.trap_output))
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts the source file at `path`, which is assumed to be canonicalized.
|
||||
pub fn extract(
|
||||
language: Language,
|
||||
language_prefix: &str,
|
||||
schema: &NodeTypeMap,
|
||||
trap_writer: &mut TrapWriter,
|
||||
path: &Path,
|
||||
source: &[u8],
|
||||
ranges: &[Range],
|
||||
) -> std::io::Result<()> {
|
||||
let span = span!(
|
||||
Level::TRACE,
|
||||
"extract",
|
||||
file = %path.display()
|
||||
);
|
||||
|
||||
let _enter = span.enter();
|
||||
|
||||
info!("extracting: {}", path.display());
|
||||
|
||||
let mut parser = Parser::new();
|
||||
parser.set_language(language).unwrap();
|
||||
parser.set_included_ranges(ranges).unwrap();
|
||||
let tree = parser.parse(&source, None).expect("Failed to parse file");
|
||||
trap_writer.comment(format!("Auto-generated TRAP file for {}", path.display()));
|
||||
let file_label = &trap_writer.populate_file(path);
|
||||
let mut visitor = Visitor {
|
||||
source,
|
||||
trap_writer,
|
||||
// TODO: should we handle path strings that are not valid UTF8 better?
|
||||
path: format!("{}", path.display()),
|
||||
file_label: *file_label,
|
||||
toplevel_child_counter: 0,
|
||||
stack: Vec::new(),
|
||||
language_prefix,
|
||||
schema,
|
||||
};
|
||||
traverse(&tree, &mut visitor);
|
||||
|
||||
parser.reset();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Escapes a string for use in a TRAP key, by replacing special characters with
|
||||
/// HTML entities.
|
||||
fn escape_key<'a, S: Into<Cow<'a, str>>>(key: S) -> Cow<'a, str> {
|
||||
fn needs_escaping(c: char) -> bool {
|
||||
matches!(c, '&' | '{' | '}' | '"' | '@' | '#')
|
||||
}
|
||||
|
||||
let key = key.into();
|
||||
if key.contains(needs_escaping) {
|
||||
let mut escaped = String::with_capacity(2 * key.len());
|
||||
for c in key.chars() {
|
||||
match c {
|
||||
'&' => escaped.push_str("&"),
|
||||
'{' => escaped.push_str("{"),
|
||||
'}' => escaped.push_str("}"),
|
||||
'"' => escaped.push_str("""),
|
||||
'@' => escaped.push_str("@"),
|
||||
'#' => escaped.push_str("#"),
|
||||
_ => escaped.push(c),
|
||||
}
|
||||
}
|
||||
Cow::Owned(escaped)
|
||||
} else {
|
||||
key
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalizes the path according the common CodeQL specification. Assumes that
|
||||
/// `path` has already been canonicalized using `std::fs::canonicalize`.
|
||||
fn normalize_path(path: &Path) -> String {
|
||||
if cfg!(windows) {
|
||||
// The way Rust canonicalizes paths doesn't match the CodeQL spec, so we
|
||||
// have to do a bit of work removing certain prefixes and replacing
|
||||
// backslashes.
|
||||
let mut components: Vec<String> = Vec::new();
|
||||
for component in path.components() {
|
||||
match component {
|
||||
std::path::Component::Prefix(prefix) => match prefix.kind() {
|
||||
std::path::Prefix::Disk(letter) | std::path::Prefix::VerbatimDisk(letter) => {
|
||||
components.push(format!("{}:", letter as char));
|
||||
}
|
||||
std::path::Prefix::Verbatim(x) | std::path::Prefix::DeviceNS(x) => {
|
||||
components.push(x.to_string_lossy().to_string());
|
||||
}
|
||||
std::path::Prefix::UNC(server, share)
|
||||
| std::path::Prefix::VerbatimUNC(server, share) => {
|
||||
components.push(server.to_string_lossy().to_string());
|
||||
components.push(share.to_string_lossy().to_string());
|
||||
}
|
||||
},
|
||||
std::path::Component::Normal(n) => {
|
||||
components.push(n.to_string_lossy().to_string());
|
||||
}
|
||||
std::path::Component::RootDir => {}
|
||||
std::path::Component::CurDir => {}
|
||||
std::path::Component::ParentDir => {}
|
||||
}
|
||||
}
|
||||
components.join("/")
|
||||
} else {
|
||||
// For other operating systems, we can use the canonicalized path
|
||||
// without modifications.
|
||||
format!("{}", path.display())
|
||||
}
|
||||
}
|
||||
|
||||
fn full_id_for_file(path: &Path) -> String {
|
||||
format!("{};sourcefile", escape_key(&normalize_path(path)))
|
||||
}
|
||||
|
||||
fn full_id_for_folder(path: &Path) -> String {
|
||||
format!("{};folder", escape_key(&normalize_path(path)))
|
||||
}
|
||||
|
||||
struct ChildNode {
|
||||
field_name: Option<&'static str>,
|
||||
label: Label,
|
||||
type_name: TypeName,
|
||||
}
|
||||
|
||||
struct Visitor<'a> {
|
||||
/// The file path of the source code (as string)
|
||||
path: String,
|
||||
/// The label to use whenever we need to refer to the `@file` entity of this
|
||||
/// source file.
|
||||
file_label: Label,
|
||||
/// The source code as a UTF-8 byte array
|
||||
source: &'a [u8],
|
||||
/// A TrapWriter to accumulate trap entries
|
||||
trap_writer: &'a mut TrapWriter,
|
||||
/// A counter for top-level child nodes
|
||||
toplevel_child_counter: usize,
|
||||
/// Language prefix
|
||||
language_prefix: &'a str,
|
||||
/// A lookup table from type name to node types
|
||||
schema: &'a NodeTypeMap,
|
||||
/// A stack for gathering information from child nodes. Whenever a node is
|
||||
/// entered the parent's [Label], child counter, and an empty list is pushed.
|
||||
/// All children append their data to the the list. When the visitor leaves a
|
||||
/// node the list containing the child data is popped from the stack and
|
||||
/// matched against the dbscheme for the node. If the expectations are met
|
||||
/// the corresponding row definitions are added to the trap_output.
|
||||
stack: Vec<(Label, usize, Vec<ChildNode>)>,
|
||||
}
|
||||
|
||||
impl Visitor<'_> {
|
||||
fn record_parse_error(
|
||||
&mut self,
|
||||
error_message: String,
|
||||
full_error_message: String,
|
||||
loc: Label,
|
||||
) {
|
||||
error!("{}", full_error_message);
|
||||
let id = self.trap_writer.fresh_id();
|
||||
self.trap_writer.add_tuple(
|
||||
"diagnostics",
|
||||
vec![
|
||||
Arg::Label(id),
|
||||
Arg::Int(40), // severity 40 = error
|
||||
Arg::String("parse_error".to_string()),
|
||||
Arg::String(error_message),
|
||||
Arg::String(full_error_message),
|
||||
Arg::Label(loc),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
fn record_parse_error_for_node(
|
||||
&mut self,
|
||||
error_message: String,
|
||||
full_error_message: String,
|
||||
node: Node,
|
||||
) {
|
||||
let (start_line, start_column, end_line, end_column) = location_for(self.source, node);
|
||||
let loc = self.trap_writer.location(
|
||||
self.file_label,
|
||||
start_line,
|
||||
start_column,
|
||||
end_line,
|
||||
end_column,
|
||||
);
|
||||
self.record_parse_error(error_message, full_error_message, loc);
|
||||
}
|
||||
|
||||
fn enter_node(&mut self, node: Node) -> bool {
|
||||
if node.is_error() || node.is_missing() {
|
||||
let error_message = if node.is_missing() {
|
||||
format!("parse error: expecting '{}'", node.kind())
|
||||
} else {
|
||||
"parse error".to_string()
|
||||
};
|
||||
let full_error_message = format!(
|
||||
"{}:{}: {}",
|
||||
&self.path,
|
||||
node.start_position().row + 1,
|
||||
error_message
|
||||
);
|
||||
self.record_parse_error_for_node(error_message, full_error_message, node);
|
||||
return false;
|
||||
}
|
||||
|
||||
let id = self.trap_writer.fresh_id();
|
||||
|
||||
self.stack.push((id, 0, Vec::new()));
|
||||
true
|
||||
}
|
||||
|
||||
fn leave_node(&mut self, field_name: Option<&'static str>, node: Node) {
|
||||
if node.is_error() || node.is_missing() {
|
||||
return;
|
||||
}
|
||||
let (id, _, child_nodes) = self.stack.pop().expect("Vistor: empty stack");
|
||||
let (start_line, start_column, end_line, end_column) = location_for(self.source, node);
|
||||
let loc = self.trap_writer.location(
|
||||
self.file_label,
|
||||
start_line,
|
||||
start_column,
|
||||
end_line,
|
||||
end_column,
|
||||
);
|
||||
let table = self
|
||||
.schema
|
||||
.get(&TypeName {
|
||||
kind: node.kind().to_owned(),
|
||||
named: node.is_named(),
|
||||
})
|
||||
.unwrap();
|
||||
let mut valid = true;
|
||||
let (parent_id, parent_index) = match self.stack.last_mut() {
|
||||
Some(p) if !node.is_extra() => {
|
||||
p.1 += 1;
|
||||
(p.0, p.1 - 1)
|
||||
}
|
||||
_ => {
|
||||
self.toplevel_child_counter += 1;
|
||||
(self.file_label, self.toplevel_child_counter - 1)
|
||||
}
|
||||
};
|
||||
match &table.kind {
|
||||
EntryKind::Token { kind_id, .. } => {
|
||||
self.trap_writer.add_tuple(
|
||||
&format!("{}_ast_node_parent", self.language_prefix),
|
||||
vec![
|
||||
Arg::Label(id),
|
||||
Arg::Label(parent_id),
|
||||
Arg::Int(parent_index),
|
||||
],
|
||||
);
|
||||
self.trap_writer.add_tuple(
|
||||
&format!("{}_tokeninfo", self.language_prefix),
|
||||
vec![
|
||||
Arg::Label(id),
|
||||
Arg::Int(*kind_id),
|
||||
sliced_source_arg(self.source, node),
|
||||
Arg::Label(loc),
|
||||
],
|
||||
);
|
||||
}
|
||||
EntryKind::Table {
|
||||
fields,
|
||||
name: table_name,
|
||||
} => {
|
||||
if let Some(args) = self.complex_node(&node, fields, &child_nodes, id) {
|
||||
self.trap_writer.add_tuple(
|
||||
&format!("{}_ast_node_parent", self.language_prefix),
|
||||
vec![
|
||||
Arg::Label(id),
|
||||
Arg::Label(parent_id),
|
||||
Arg::Int(parent_index),
|
||||
],
|
||||
);
|
||||
let mut all_args = vec![Arg::Label(id)];
|
||||
all_args.extend(args);
|
||||
all_args.push(Arg::Label(loc));
|
||||
self.trap_writer.add_tuple(table_name, all_args);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let error_message = format!("unknown table type: '{}'", node.kind());
|
||||
let full_error_message = format!(
|
||||
"{}:{}: {}",
|
||||
&self.path,
|
||||
node.start_position().row + 1,
|
||||
error_message
|
||||
);
|
||||
self.record_parse_error(error_message, full_error_message, loc);
|
||||
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
if valid && !node.is_extra() {
|
||||
// Extra nodes are independent root nodes and do not belong to the parent node
|
||||
// Therefore we should not register them in the parent vector
|
||||
if let Some(parent) = self.stack.last_mut() {
|
||||
parent.2.push(ChildNode {
|
||||
field_name,
|
||||
label: id,
|
||||
type_name: TypeName {
|
||||
kind: node.kind().to_owned(),
|
||||
named: node.is_named(),
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn complex_node(
|
||||
&mut self,
|
||||
node: &Node,
|
||||
fields: &[Field],
|
||||
child_nodes: &[ChildNode],
|
||||
parent_id: Label,
|
||||
) -> Option<Vec<Arg>> {
|
||||
let mut map: Map<&Option<String>, (&Field, Vec<Arg>)> = Map::new();
|
||||
for field in fields {
|
||||
map.insert(&field.name, (field, Vec::new()));
|
||||
}
|
||||
for child_node in child_nodes {
|
||||
if let Some((field, values)) = map.get_mut(&child_node.field_name.map(|x| x.to_owned()))
|
||||
{
|
||||
//TODO: handle error and missing nodes
|
||||
if self.type_matches(&child_node.type_name, &field.type_info) {
|
||||
if let node_types::FieldTypeInfo::ReservedWordInt(int_mapping) =
|
||||
&field.type_info
|
||||
{
|
||||
// We can safely unwrap because type_matches checks the key is in the map.
|
||||
let (int_value, _) = int_mapping.get(&child_node.type_name.kind).unwrap();
|
||||
values.push(Arg::Int(*int_value));
|
||||
} else {
|
||||
values.push(Arg::Label(child_node.label));
|
||||
}
|
||||
} else if field.name.is_some() {
|
||||
let error_message = format!(
|
||||
"type mismatch for field {}::{} with type {:?} != {:?}",
|
||||
node.kind(),
|
||||
child_node.field_name.unwrap_or("child"),
|
||||
child_node.type_name,
|
||||
field.type_info
|
||||
);
|
||||
let full_error_message = format!(
|
||||
"{}:{}: {}",
|
||||
&self.path,
|
||||
node.start_position().row + 1,
|
||||
error_message
|
||||
);
|
||||
self.record_parse_error_for_node(error_message, full_error_message, *node);
|
||||
}
|
||||
} else if child_node.field_name.is_some() || child_node.type_name.named {
|
||||
let error_message = format!(
|
||||
"value for unknown field: {}::{} and type {:?}",
|
||||
node.kind(),
|
||||
&child_node.field_name.unwrap_or("child"),
|
||||
&child_node.type_name
|
||||
);
|
||||
let full_error_message = format!(
|
||||
"{}:{}: {}",
|
||||
&self.path,
|
||||
node.start_position().row + 1,
|
||||
error_message
|
||||
);
|
||||
self.record_parse_error_for_node(error_message, full_error_message, *node);
|
||||
}
|
||||
}
|
||||
let mut args = Vec::new();
|
||||
let mut is_valid = true;
|
||||
for field in fields {
|
||||
let child_values = &map.get(&field.name).unwrap().1;
|
||||
match &field.storage {
|
||||
Storage::Column { name: column_name } => {
|
||||
if child_values.len() == 1 {
|
||||
args.push(child_values.first().unwrap().clone());
|
||||
} else {
|
||||
is_valid = false;
|
||||
let error_message = format!(
|
||||
"{} for field: {}::{}",
|
||||
if child_values.is_empty() {
|
||||
"missing value"
|
||||
} else {
|
||||
"too many values"
|
||||
},
|
||||
node.kind(),
|
||||
column_name
|
||||
);
|
||||
let full_error_message = format!(
|
||||
"{}:{}: {}",
|
||||
&self.path,
|
||||
node.start_position().row + 1,
|
||||
error_message
|
||||
);
|
||||
self.record_parse_error_for_node(error_message, full_error_message, *node);
|
||||
}
|
||||
}
|
||||
Storage::Table {
|
||||
name: table_name,
|
||||
has_index,
|
||||
column_name: _,
|
||||
} => {
|
||||
for (index, child_value) in child_values.iter().enumerate() {
|
||||
if !*has_index && index > 0 {
|
||||
error!(
|
||||
"{}:{}: too many values for field: {}::{}",
|
||||
&self.path,
|
||||
node.start_position().row + 1,
|
||||
node.kind(),
|
||||
table_name,
|
||||
);
|
||||
break;
|
||||
}
|
||||
let mut args = vec![Arg::Label(parent_id)];
|
||||
if *has_index {
|
||||
args.push(Arg::Int(index))
|
||||
}
|
||||
args.push(child_value.clone());
|
||||
self.trap_writer.add_tuple(table_name, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_valid {
|
||||
Some(args)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn type_matches(&self, tp: &TypeName, type_info: &node_types::FieldTypeInfo) -> bool {
|
||||
match type_info {
|
||||
node_types::FieldTypeInfo::Single(single_type) => {
|
||||
if tp == single_type {
|
||||
return true;
|
||||
}
|
||||
if let EntryKind::Union { members } = &self.schema.get(single_type).unwrap().kind {
|
||||
if self.type_matches_set(tp, members) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
node_types::FieldTypeInfo::Multiple { types, .. } => {
|
||||
return self.type_matches_set(tp, types);
|
||||
}
|
||||
|
||||
node_types::FieldTypeInfo::ReservedWordInt(int_mapping) => {
|
||||
return !tp.named && int_mapping.contains_key(&tp.kind)
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn type_matches_set(&self, tp: &TypeName, types: &Set<TypeName>) -> bool {
|
||||
if types.contains(tp) {
|
||||
return true;
|
||||
}
|
||||
for other in types.iter() {
|
||||
if let EntryKind::Union { members } = &self.schema.get(other).unwrap().kind {
|
||||
if self.type_matches_set(tp, members) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// Emit a slice of a source file as an Arg.
|
||||
fn sliced_source_arg(source: &[u8], n: Node) -> Arg {
|
||||
let range = n.byte_range();
|
||||
Arg::String(String::from_utf8_lossy(&source[range.start..range.end]).into_owned())
|
||||
}
|
||||
|
||||
// Emit a pair of `TrapEntry`s for the provided node, appropriately calibrated.
|
||||
// The first is the location and label definition, and the second is the
|
||||
// 'Located' entry.
|
||||
fn location_for(source: &[u8], n: Node) -> (usize, usize, usize, usize) {
|
||||
// Tree-sitter row, column values are 0-based while CodeQL starts
|
||||
// counting at 1. In addition Tree-sitter's row and column for the
|
||||
// end position are exclusive while CodeQL's end positions are inclusive.
|
||||
// This means that all values should be incremented by 1 and in addition the
|
||||
// end position needs to be shift 1 to the left. In most cases this means
|
||||
// simply incrementing all values except the end column except in cases where
|
||||
// the end column is 0 (start of a line). In such cases the end position must be
|
||||
// set to the end of the previous line.
|
||||
let start_line = n.start_position().row + 1;
|
||||
let start_col = n.start_position().column + 1;
|
||||
let mut end_line = n.end_position().row + 1;
|
||||
let mut end_col = n.end_position().column;
|
||||
if start_line > end_line || start_line == end_line && start_col > end_col {
|
||||
// the range is empty, clip it to sensible values
|
||||
end_line = start_line;
|
||||
end_col = start_col - 1;
|
||||
} else if end_col == 0 {
|
||||
// end_col = 0 means that we are at the start of a line
|
||||
// unfortunately 0 is invalid as column number, therefore
|
||||
// we should update the end location to be the end of the
|
||||
// previous line
|
||||
let mut index = n.end_byte();
|
||||
if index > 0 && index <= source.len() {
|
||||
index -= 1;
|
||||
if source[index] != b'\n' {
|
||||
error!("expecting a line break symbol, but none found while correcting end column value");
|
||||
}
|
||||
end_line -= 1;
|
||||
end_col = 1;
|
||||
while index > 0 && source[index - 1] != b'\n' {
|
||||
index -= 1;
|
||||
end_col += 1;
|
||||
}
|
||||
} else {
|
||||
error!(
|
||||
"cannot correct end column value: end_byte index {} is not in range [1,{}]",
|
||||
index,
|
||||
source.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
(start_line, start_col, end_line, end_col)
|
||||
}
|
||||
|
||||
fn traverse(tree: &Tree, visitor: &mut Visitor) {
|
||||
let cursor = &mut tree.walk();
|
||||
visitor.enter_node(cursor.node());
|
||||
let mut recurse = true;
|
||||
loop {
|
||||
if recurse && cursor.goto_first_child() {
|
||||
recurse = visitor.enter_node(cursor.node());
|
||||
} else {
|
||||
visitor.leave_node(cursor.field_name(), cursor.node());
|
||||
|
||||
if cursor.goto_next_sibling() {
|
||||
recurse = visitor.enter_node(cursor.node());
|
||||
} else if cursor.goto_parent() {
|
||||
recurse = false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Program(Vec<TrapEntry>);
|
||||
|
||||
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<Arg>),
|
||||
Comment(String),
|
||||
}
|
||||
impl fmt::Display for TrapEntry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
TrapEntry::FreshId(label) => write!(f, "{}=*", label),
|
||||
TrapEntry::MapLabelToKey(label, key) => {
|
||||
write!(f, "{}=@\"{}\"", label, key.replace("\"", "\"\""))
|
||||
}
|
||||
TrapEntry::GenericTuple(name, args) => {
|
||||
write!(f, "{}(", name)?;
|
||||
for (index, arg) in args.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, ",")?;
|
||||
}
|
||||
write!(f, "{}", arg)?;
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
TrapEntry::Comment(line) => write!(f, "// {}", line),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
// Identifiers of the form #0, #1...
|
||||
struct Label(u32);
|
||||
|
||||
impl fmt::Display for Label {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "#{:x}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
// Numeric indices.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Index(usize);
|
||||
|
||||
impl fmt::Display for Index {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
// Some untyped argument to a TrapEntry.
|
||||
#[derive(Debug, Clone)]
|
||||
enum Arg {
|
||||
Label(Label),
|
||||
Int(usize),
|
||||
String(String),
|
||||
}
|
||||
|
||||
const MAX_STRLEN: usize = 1048576;
|
||||
|
||||
impl fmt::Display for Arg {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Arg::Label(x) => write!(f, "{}", x),
|
||||
Arg::Int(x) => write!(f, "{}", x),
|
||||
Arg::String(x) => write!(
|
||||
f,
|
||||
"\"{}\"",
|
||||
limit_string(x, MAX_STRLEN).replace("\"", "\"\"")
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Limit the length (in bytes) of a string. If the string's length in bytes is
|
||||
/// less than or equal to the limit then the entire string is returned. Otherwise
|
||||
/// the string is sliced at the provided limit. If there is a multi-byte character
|
||||
/// at the limit then the returned slice will be slightly shorter than the limit to
|
||||
/// avoid splitting that multi-byte character.
|
||||
fn limit_string(string: &str, max_size: usize) -> &str {
|
||||
if string.len() <= max_size {
|
||||
return string;
|
||||
}
|
||||
let p = string.as_bytes();
|
||||
let mut index = max_size;
|
||||
// We want to clip the string at [max_size]; however, the character at that position
|
||||
// may span several bytes. We need to find the first byte of the character. In UTF-8
|
||||
// encoded data any byte that matches the bit pattern 10XXXXXX is not a start byte.
|
||||
// Therefore we decrement the index as long as there are bytes matching this pattern.
|
||||
// This ensures we cut the string at the border between one character and another.
|
||||
while index > 0 && (p[index] & 0b11000000) == 0b10000000 {
|
||||
index -= 1;
|
||||
}
|
||||
&string[0..index]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn limit_string_test() {
|
||||
assert_eq!("hello", limit_string(&"hello world".to_owned(), 5));
|
||||
assert_eq!("hi ☹", limit_string(&"hi ☹☹".to_owned(), 6));
|
||||
assert_eq!("hi ", limit_string(&"hi ☹☹".to_owned(), 5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escape_key_test() {
|
||||
assert_eq!("foo!", escape_key("foo!"));
|
||||
assert_eq!("foo{}", escape_key("foo{}"));
|
||||
assert_eq!("{}", escape_key("{}"));
|
||||
assert_eq!("", escape_key(""));
|
||||
assert_eq!("/path/to/foo.rb", escape_key("/path/to/foo.rb"));
|
||||
assert_eq!(
|
||||
"/path/to/foo&{}"@#.rb",
|
||||
escape_key("/path/to/foo&{}\"@#.rb")
|
||||
);
|
||||
}
|
||||
302
ruby/extractor/src/main.rs
Normal file
302
ruby/extractor/src/main.rs
Normal file
@@ -0,0 +1,302 @@
|
||||
mod extractor;
|
||||
|
||||
extern crate num_cpus;
|
||||
|
||||
use flate2::write::GzEncoder;
|
||||
use rayon::prelude::*;
|
||||
use std::fs;
|
||||
use std::io::{BufRead, BufWriter};
|
||||
use std::path::{Path, PathBuf};
|
||||
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<TrapCompression> {
|
||||
match s.to_lowercase().as_ref() {
|
||||
"none" => Some(TrapCompression::None),
|
||||
"gzip" => Some(TrapCompression::Gzip),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn extension(&self) -> &str {
|
||||
match self {
|
||||
TrapCompression::None => "trap",
|
||||
TrapCompression::Gzip => "trap.gz",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of threads the extractor should use, by reading the
|
||||
* CODEQL_THREADS environment variable and using it as described in the
|
||||
* extractor spec:
|
||||
*
|
||||
* "If the number is positive, it indicates the number of threads that should
|
||||
* be used. If the number is negative or zero, it should be added to the number
|
||||
* of cores available on the machine to determine how many threads to use
|
||||
* (minimum of 1). If unspecified, should be considered as set to -1."
|
||||
*/
|
||||
fn num_codeql_threads() -> usize {
|
||||
let threads_str = std::env::var("CODEQL_THREADS").unwrap_or_else(|_| "-1".to_owned());
|
||||
match threads_str.parse::<i32>() {
|
||||
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::try_from_default_env()
|
||||
.unwrap_or(tracing_subscriber::EnvFilter::new("ruby_extractor=warn")),
|
||||
)
|
||||
.init();
|
||||
tracing::warn!("Support for Ruby is currently in Beta: https://git.io/codeql-language-support");
|
||||
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=<DIR> 'Sets a custom source archive folder'
|
||||
--output-dir=<DIR> 'Sets a custom trap folder'
|
||||
--file-list=<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<Vec<String>> = 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: &Path,
|
||||
path: PathBuf,
|
||||
trap_writer: extractor::TrapWriter,
|
||||
trap_compression: &TrapCompression,
|
||||
) -> std::io::Result<()> {
|
||||
let trap_file = path_for(trap_dir, &path, trap_compression.extension());
|
||||
std::fs::create_dir_all(&trap_file.parent().unwrap())?;
|
||||
let trap_file = std::fs::File::create(&trap_file)?;
|
||||
let mut trap_file = BufWriter::new(trap_file);
|
||||
match trap_compression {
|
||||
TrapCompression::None => trap_writer.output(&mut trap_file),
|
||||
TrapCompression::Gzip => {
|
||||
let mut compressed_writer = GzEncoder::new(trap_file, flate2::Compression::fast());
|
||||
trap_writer.output(&mut compressed_writer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scan_erb(
|
||||
erb: Language,
|
||||
source: &[u8],
|
||||
directive_id: u16,
|
||||
output_directive_id: u16,
|
||||
code_id: u16,
|
||||
) -> (Vec<Range>, Vec<usize>) {
|
||||
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.is_empty() {
|
||||
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
|
||||
}
|
||||
15
ruby/generator/Cargo.toml
Normal file
15
ruby/generator/Cargo.toml
Normal file
@@ -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.3", features = ["env-filter"] }
|
||||
tree-sitter-embedded-template = "0.19"
|
||||
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "bb6a42e42b048627a74a127d3e0184c1eef01de9" }
|
||||
130
ruby/generator/src/dbscheme.rs
Normal file
130
ruby/generator/src/dbscheme.rs
Normal file
@@ -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<Column<'a>>,
|
||||
pub keysets: Option<Vec<&'a str>>,
|
||||
}
|
||||
|
||||
/// A union in the database schema.
|
||||
pub struct Union<'a> {
|
||||
pub name: &'a str,
|
||||
pub members: Set<&'a str>,
|
||||
}
|
||||
|
||||
/// A table in the database schema.
|
||||
pub struct Case<'a> {
|
||||
pub name: &'a str,
|
||||
pub column: &'a str,
|
||||
pub branches: Vec<(usize, &'a str)>,
|
||||
}
|
||||
|
||||
/// A column in a table.
|
||||
pub struct Column<'a> {
|
||||
pub db_type: DbColumnType,
|
||||
pub name: &'a str,
|
||||
pub unique: bool,
|
||||
pub ql_type: ql::Type<'a>,
|
||||
pub ql_type_is_ref: bool,
|
||||
}
|
||||
|
||||
/// The database column type.
|
||||
pub enum DbColumnType {
|
||||
Int,
|
||||
String,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Case<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "case @{}.{} of", &self.name, &self.column)?;
|
||||
let mut sep = " ";
|
||||
for (c, tp) in &self.branches {
|
||||
writeln!(f, "{} {} = @{}", sep, c, tp)?;
|
||||
sep = "|";
|
||||
}
|
||||
writeln!(f, ";")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Table<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(keyset) = &self.keysets {
|
||||
write!(f, "#keyset[")?;
|
||||
for (key_index, key) in keyset.iter().enumerate() {
|
||||
if key_index > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "{}", key)?;
|
||||
}
|
||||
writeln!(f, "]")?;
|
||||
}
|
||||
|
||||
writeln!(f, "{}(", self.name)?;
|
||||
for (column_index, column) in self.columns.iter().enumerate() {
|
||||
write!(f, " ")?;
|
||||
if column.unique {
|
||||
write!(f, "unique ")?;
|
||||
}
|
||||
write!(
|
||||
f,
|
||||
"{} ",
|
||||
match column.db_type {
|
||||
DbColumnType::Int => "int",
|
||||
DbColumnType::String => "string",
|
||||
}
|
||||
)?;
|
||||
write!(f, "{}: {}", column.name, column.ql_type)?;
|
||||
if column.ql_type_is_ref {
|
||||
write!(f, " ref")?;
|
||||
}
|
||||
if column_index + 1 != self.columns.len() {
|
||||
write!(f, ",")?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
write!(f, ");")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Union<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "@{} = ", self.name)?;
|
||||
let mut first = true;
|
||||
for member in &self.members {
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
write!(f, " | ")?;
|
||||
}
|
||||
write!(f, "@{}", member)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the dbscheme by writing the given dbscheme `entries` to the `file`.
|
||||
pub fn write<'a>(file: &mut dyn std::io::Write, entries: &'a [Entry]) -> std::io::Result<()> {
|
||||
for entry in entries {
|
||||
match entry {
|
||||
Entry::Case(case) => write!(file, "{}\n\n", case)?,
|
||||
Entry::Table(table) => write!(file, "{}\n\n", table)?,
|
||||
Entry::Union(union) => write!(file, "{}\n\n", union)?,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
4
ruby/generator/src/language.rs
Normal file
4
ruby/generator/src/language.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub struct Language {
|
||||
pub name: String,
|
||||
pub node_types: &'static str,
|
||||
}
|
||||
671
ruby/generator/src/main.rs
Normal file
671
ruby/generator/src/main.rs
Normal file
@@ -0,0 +1,671 @@
|
||||
mod dbscheme;
|
||||
mod language;
|
||||
mod ql;
|
||||
mod ql_gen;
|
||||
|
||||
use language::Language;
|
||||
use std::collections::BTreeMap as Map;
|
||||
use std::collections::BTreeSet as Set;
|
||||
use std::fs::File;
|
||||
use std::io::LineWriter;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Given the name of the parent node, and its field information, returns a pair,
|
||||
/// the first of which is the field's type. The second is an optional dbscheme
|
||||
/// entry that should be added.
|
||||
fn make_field_type<'a>(
|
||||
parent_name: &'a str,
|
||||
field: &'a node_types::Field,
|
||||
nodes: &'a node_types::NodeTypeMap,
|
||||
) -> (ql::Type<'a>, Option<dbscheme::Entry<'a>>) {
|
||||
match &field.type_info {
|
||||
node_types::FieldTypeInfo::Multiple {
|
||||
types,
|
||||
dbscheme_union,
|
||||
ql_class: _,
|
||||
} => {
|
||||
// This field can have one of several types. Create an ad-hoc QL union
|
||||
// type to represent them.
|
||||
let members: Set<&str> = types
|
||||
.iter()
|
||||
.map(|t| nodes.get(t).unwrap().dbscheme_name.as_str())
|
||||
.collect();
|
||||
(
|
||||
ql::Type::At(dbscheme_union),
|
||||
Some(dbscheme::Entry::Union(dbscheme::Union {
|
||||
name: dbscheme_union,
|
||||
members,
|
||||
})),
|
||||
)
|
||||
}
|
||||
node_types::FieldTypeInfo::Single(t) => {
|
||||
let dbscheme_name = &nodes.get(t).unwrap().dbscheme_name;
|
||||
(ql::Type::At(dbscheme_name), None)
|
||||
}
|
||||
node_types::FieldTypeInfo::ReservedWordInt(int_mapping) => {
|
||||
// The field will be an `int` in the db, and we add a case split to
|
||||
// create other db types for each integer value.
|
||||
let mut branches: Vec<(usize, &'a str)> = Vec::new();
|
||||
for (value, name) in int_mapping.values() {
|
||||
branches.push((*value, name));
|
||||
}
|
||||
let case = dbscheme::Entry::Case(dbscheme::Case {
|
||||
name: parent_name,
|
||||
column: match &field.storage {
|
||||
node_types::Storage::Column { name } => name,
|
||||
node_types::Storage::Table { name, .. } => name,
|
||||
},
|
||||
branches,
|
||||
});
|
||||
(ql::Type::Int, Some(case))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_field_for_table_storage<'a>(
|
||||
field: &'a node_types::Field,
|
||||
table_name: &'a str,
|
||||
column_name: &'a str,
|
||||
has_index: bool,
|
||||
nodes: &'a node_types::NodeTypeMap,
|
||||
) -> (dbscheme::Table<'a>, Option<dbscheme::Entry<'a>>) {
|
||||
let parent_name = &nodes.get(&field.parent).unwrap().dbscheme_name;
|
||||
// This field can appear zero or multiple times, so put
|
||||
// it in an auxiliary table.
|
||||
let (field_ql_type, field_type_entry) = make_field_type(parent_name, field, nodes);
|
||||
let parent_column = dbscheme::Column {
|
||||
unique: !has_index,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: parent_name,
|
||||
ql_type: ql::Type::At(parent_name),
|
||||
ql_type_is_ref: true,
|
||||
};
|
||||
let index_column = dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "index",
|
||||
ql_type: ql::Type::Int,
|
||||
ql_type_is_ref: true,
|
||||
};
|
||||
let field_column = dbscheme::Column {
|
||||
unique: true,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: column_name,
|
||||
ql_type: field_ql_type,
|
||||
ql_type_is_ref: true,
|
||||
};
|
||||
let field_table = dbscheme::Table {
|
||||
name: table_name,
|
||||
columns: if has_index {
|
||||
vec![parent_column, index_column, field_column]
|
||||
} else {
|
||||
vec![parent_column, field_column]
|
||||
},
|
||||
// In addition to the field being unique, the combination of
|
||||
// parent+index is unique, so add a keyset for them.
|
||||
keysets: if has_index {
|
||||
Some(vec![parent_name, "index"])
|
||||
} else {
|
||||
None
|
||||
},
|
||||
};
|
||||
(field_table, field_type_entry)
|
||||
}
|
||||
|
||||
fn add_field_for_column_storage<'a>(
|
||||
parent_name: &'a str,
|
||||
field: &'a node_types::Field,
|
||||
column_name: &'a str,
|
||||
nodes: &'a node_types::NodeTypeMap,
|
||||
) -> (dbscheme::Column<'a>, Option<dbscheme::Entry<'a>>) {
|
||||
// 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 `<lang>_ast_node` union.
|
||||
/// 3. A map where the keys are the dbscheme names for token kinds, and the
|
||||
/// values are their integer representations.
|
||||
fn convert_nodes(
|
||||
nodes: &node_types::NodeTypeMap,
|
||||
) -> (Vec<dbscheme::Entry>, Set<&str>, Map<&str, usize>) {
|
||||
let mut entries: Vec<dbscheme::Entry> = Vec::new();
|
||||
let mut ast_node_members: Set<&str> = Set::new();
|
||||
let token_kinds: Map<&str, usize> = nodes
|
||||
.iter()
|
||||
.filter_map(|(_, node)| match &node.kind {
|
||||
node_types::EntryKind::Token { kind_id } => {
|
||||
Some((node.dbscheme_name.as_str(), *kind_id))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
for node in nodes.values() {
|
||||
match &node.kind {
|
||||
node_types::EntryKind::Union { members: n_members } => {
|
||||
// It's a tree-sitter supertype node, for which we create a union
|
||||
// type.
|
||||
let members: Set<&str> = n_members
|
||||
.iter()
|
||||
.map(|n| nodes.get(n).unwrap().dbscheme_name.as_str())
|
||||
.collect();
|
||||
entries.push(dbscheme::Entry::Union(dbscheme::Union {
|
||||
name: &node.dbscheme_name,
|
||||
members,
|
||||
}));
|
||||
}
|
||||
node_types::EntryKind::Table { name, fields } => {
|
||||
// It's a product type, defined by a table.
|
||||
let mut main_table = dbscheme::Table {
|
||||
name,
|
||||
columns: vec![dbscheme::Column {
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "id",
|
||||
unique: true,
|
||||
ql_type: ql::Type::At(&node.dbscheme_name),
|
||||
ql_type_is_ref: false,
|
||||
}],
|
||||
keysets: None,
|
||||
};
|
||||
ast_node_members.insert(&node.dbscheme_name);
|
||||
|
||||
// If the type also has fields or children, then we create either
|
||||
// auxiliary tables or columns in the defining table for them.
|
||||
for field in fields {
|
||||
match &field.storage {
|
||||
node_types::Storage::Column { name: column_name } => {
|
||||
let (field_column, field_type_entry) = add_field_for_column_storage(
|
||||
&node.dbscheme_name,
|
||||
field,
|
||||
column_name,
|
||||
nodes,
|
||||
);
|
||||
if let Some(field_type_entry) = field_type_entry {
|
||||
entries.push(field_type_entry);
|
||||
}
|
||||
main_table.columns.push(field_column);
|
||||
}
|
||||
node_types::Storage::Table {
|
||||
name,
|
||||
has_index,
|
||||
column_name,
|
||||
} => {
|
||||
let (field_table, field_type_entry) = add_field_for_table_storage(
|
||||
field,
|
||||
name,
|
||||
column_name,
|
||||
*has_index,
|
||||
nodes,
|
||||
);
|
||||
if let Some(field_type_entry) = field_type_entry {
|
||||
entries.push(field_type_entry);
|
||||
}
|
||||
entries.push(dbscheme::Entry::Table(field_table));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fields.is_empty() {
|
||||
// There were no fields and no children, so it's a leaf node in
|
||||
// the TS grammar. Add a column for the node text.
|
||||
main_table.columns.push(dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::String,
|
||||
name: "text",
|
||||
ql_type: ql::Type::String,
|
||||
ql_type_is_ref: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Finally, the type's defining table also includes the location.
|
||||
main_table.columns.push(dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "loc",
|
||||
ql_type: ql::Type::At("location"),
|
||||
ql_type_is_ref: true,
|
||||
});
|
||||
|
||||
entries.push(dbscheme::Entry::Table(main_table));
|
||||
}
|
||||
node_types::EntryKind::Token { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
(entries, ast_node_members, token_kinds)
|
||||
}
|
||||
|
||||
/// Creates a dbscheme table entry representing the parent relation for AST nodes.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `name` - the name of both the table to create and the node parent type.
|
||||
/// - `ast_node_name` - the name of the node child type.
|
||||
fn create_ast_node_parent_table<'a>(name: &'a str, ast_node_name: &'a str) -> dbscheme::Table<'a> {
|
||||
dbscheme::Table {
|
||||
name,
|
||||
columns: vec![
|
||||
dbscheme::Column {
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "child",
|
||||
unique: false,
|
||||
ql_type: ql::Type::At(ast_node_name),
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "parent",
|
||||
unique: false,
|
||||
ql_type: ql::Type::At(name),
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "parent_index",
|
||||
ql_type: ql::Type::Int,
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
],
|
||||
keysets: Some(vec!["parent", "parent_index"]),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tokeninfo<'a>(name: &'a str, type_name: &'a str) -> dbscheme::Table<'a> {
|
||||
dbscheme::Table {
|
||||
name,
|
||||
keysets: None,
|
||||
columns: vec![
|
||||
dbscheme::Column {
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "id",
|
||||
unique: true,
|
||||
ql_type: ql::Type::At(type_name),
|
||||
ql_type_is_ref: false,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "kind",
|
||||
ql_type: ql::Type::Int,
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::String,
|
||||
name: "value",
|
||||
ql_type: ql::Type::String,
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "loc",
|
||||
ql_type: ql::Type::At("location"),
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
fn create_token_case<'a>(name: &'a str, token_kinds: Map<&'a str, usize>) -> dbscheme::Case<'a> {
|
||||
let branches: Vec<(usize, &str)> = token_kinds
|
||||
.iter()
|
||||
.map(|(&name, kind_id)| (*kind_id, name))
|
||||
.collect();
|
||||
dbscheme::Case {
|
||||
name,
|
||||
column: "kind",
|
||||
branches,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_location_union<'a>() -> dbscheme::Entry<'a> {
|
||||
dbscheme::Entry::Union(dbscheme::Union {
|
||||
name: "location",
|
||||
members: vec!["location_default"].into_iter().collect(),
|
||||
})
|
||||
}
|
||||
|
||||
fn create_files_table<'a>() -> dbscheme::Entry<'a> {
|
||||
dbscheme::Entry::Table(dbscheme::Table {
|
||||
name: "files",
|
||||
keysets: None,
|
||||
columns: vec![
|
||||
dbscheme::Column {
|
||||
unique: true,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "id",
|
||||
ql_type: ql::Type::At("file"),
|
||||
ql_type_is_ref: false,
|
||||
},
|
||||
dbscheme::Column {
|
||||
db_type: dbscheme::DbColumnType::String,
|
||||
name: "name",
|
||||
unique: false,
|
||||
ql_type: ql::Type::String,
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
fn create_folders_table<'a>() -> dbscheme::Entry<'a> {
|
||||
dbscheme::Entry::Table(dbscheme::Table {
|
||||
name: "folders",
|
||||
keysets: None,
|
||||
columns: vec![
|
||||
dbscheme::Column {
|
||||
unique: true,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "id",
|
||||
ql_type: ql::Type::At("folder"),
|
||||
ql_type_is_ref: false,
|
||||
},
|
||||
dbscheme::Column {
|
||||
db_type: dbscheme::DbColumnType::String,
|
||||
name: "name",
|
||||
unique: false,
|
||||
ql_type: ql::Type::String,
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
fn create_locations_default_table<'a>() -> dbscheme::Entry<'a> {
|
||||
dbscheme::Entry::Table(dbscheme::Table {
|
||||
name: "locations_default",
|
||||
keysets: None,
|
||||
columns: vec![
|
||||
dbscheme::Column {
|
||||
unique: true,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "id",
|
||||
ql_type: ql::Type::At("location_default"),
|
||||
ql_type_is_ref: false,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "file",
|
||||
ql_type: ql::Type::At("file"),
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "start_line",
|
||||
ql_type: ql::Type::Int,
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "start_column",
|
||||
ql_type: ql::Type::Int,
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "end_line",
|
||||
ql_type: ql::Type::Int,
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "end_column",
|
||||
ql_type: ql::Type::Int,
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
fn create_container_union<'a>() -> dbscheme::Entry<'a> {
|
||||
dbscheme::Entry::Union(dbscheme::Union {
|
||||
name: "container",
|
||||
members: vec!["folder", "file"].into_iter().collect(),
|
||||
})
|
||||
}
|
||||
|
||||
fn create_containerparent_table<'a>() -> dbscheme::Entry<'a> {
|
||||
dbscheme::Entry::Table(dbscheme::Table {
|
||||
name: "containerparent",
|
||||
columns: vec![
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "parent",
|
||||
ql_type: ql::Type::At("container"),
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: true,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "child",
|
||||
ql_type: ql::Type::At("container"),
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
],
|
||||
keysets: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_source_location_prefix_table<'a>() -> dbscheme::Entry<'a> {
|
||||
dbscheme::Entry::Table(dbscheme::Table {
|
||||
name: "sourceLocationPrefix",
|
||||
keysets: None,
|
||||
columns: vec![dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::String,
|
||||
name: "prefix",
|
||||
ql_type: ql::Type::String,
|
||||
ql_type_is_ref: true,
|
||||
}],
|
||||
})
|
||||
}
|
||||
|
||||
fn create_diagnostics<'a>() -> (dbscheme::Case<'a>, dbscheme::Table<'a>) {
|
||||
let table = dbscheme::Table {
|
||||
name: "diagnostics",
|
||||
keysets: None,
|
||||
columns: vec![
|
||||
dbscheme::Column {
|
||||
unique: true,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "id",
|
||||
ql_type: ql::Type::At("diagnostic"),
|
||||
ql_type_is_ref: false,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "severity",
|
||||
ql_type: ql::Type::Int,
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::String,
|
||||
name: "error_tag",
|
||||
ql_type: ql::Type::String,
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::String,
|
||||
name: "error_message",
|
||||
ql_type: ql::Type::String,
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::String,
|
||||
name: "full_error_message",
|
||||
ql_type: ql::Type::String,
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
dbscheme::Column {
|
||||
unique: false,
|
||||
db_type: dbscheme::DbColumnType::Int,
|
||||
name: "location",
|
||||
ql_type: ql::Type::At("location_default"),
|
||||
ql_type_is_ref: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
let severities: Vec<(usize, &str)> = vec![
|
||||
(10, "diagnostic_debug"),
|
||||
(20, "diagnostic_info"),
|
||||
(30, "diagnostic_warning"),
|
||||
(40, "diagnostic_error"),
|
||||
];
|
||||
let case = dbscheme::Case {
|
||||
name: "diagnostic",
|
||||
column: "severity",
|
||||
branches: severities,
|
||||
};
|
||||
(case, table)
|
||||
}
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_target(false)
|
||||
.without_time()
|
||||
.with_level(true)
|
||||
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
let matches = clap::App::new("Ruby dbscheme generator")
|
||||
.version("1.0")
|
||||
.author("GitHub")
|
||||
.about("CodeQL Ruby dbscheme generator")
|
||||
.args_from_usage(
|
||||
"--dbscheme=<FILE> 'Path of the generated dbscheme file'
|
||||
--library=<FILE> '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(())
|
||||
}
|
||||
275
ruby/generator/src/ql.rs
Normal file
275
ruby/generator/src/ql.rs
Normal file
@@ -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<String>,
|
||||
pub name: &'a str,
|
||||
pub is_abstract: bool,
|
||||
pub supertypes: BTreeSet<Type<'a>>,
|
||||
pub characteristic_predicate: Option<Expression<'a>>,
|
||||
pub predicates: Vec<Predicate<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Class<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let Some(qldoc) = &self.qldoc {
|
||||
write!(f, "/** {} */", qldoc)?;
|
||||
}
|
||||
if self.is_abstract {
|
||||
write!(f, "abstract ")?;
|
||||
}
|
||||
write!(f, "class {} extends ", &self.name)?;
|
||||
for (index, supertype) in self.supertypes.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "{}", supertype)?;
|
||||
}
|
||||
writeln!(f, " {{ ")?;
|
||||
|
||||
if let Some(charpred) = &self.characteristic_predicate {
|
||||
writeln!(
|
||||
f,
|
||||
" {}",
|
||||
Predicate {
|
||||
qldoc: None,
|
||||
name: self.name,
|
||||
overridden: false,
|
||||
return_type: None,
|
||||
formal_parameters: vec![],
|
||||
body: charpred.clone(),
|
||||
}
|
||||
)?;
|
||||
}
|
||||
|
||||
for predicate in &self.predicates {
|
||||
writeln!(f, " {}", predicate)?;
|
||||
}
|
||||
|
||||
write!(f, "}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Module<'a> {
|
||||
pub qldoc: Option<String>,
|
||||
pub name: &'a str,
|
||||
pub body: Vec<TopLevel<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Module<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let Some(qldoc) = &self.qldoc {
|
||||
write!(f, "/** {} */", qldoc)?;
|
||||
}
|
||||
writeln!(f, "module {} {{ ", self.name)?;
|
||||
for decl in &self.body {
|
||||
writeln!(f, " {}", decl)?;
|
||||
}
|
||||
write!(f, "}}")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
// The QL type of a column.
|
||||
#[derive(Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
pub enum Type<'a> {
|
||||
/// Primitive `int` type.
|
||||
Int,
|
||||
|
||||
/// Primitive `string` type.
|
||||
String,
|
||||
|
||||
/// A database type that will need to be referred to with an `@` prefix.
|
||||
At(&'a str),
|
||||
|
||||
/// A user-defined type.
|
||||
Normal(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Type<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Type::Int => write!(f, "int"),
|
||||
Type::String => write!(f, "string"),
|
||||
Type::Normal(name) => write!(f, "{}", name),
|
||||
Type::At(name) => write!(f, "@{}", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||
pub enum Expression<'a> {
|
||||
Var(&'a str),
|
||||
String(&'a str),
|
||||
Integer(usize),
|
||||
Pred(&'a str, Vec<Expression<'a>>),
|
||||
And(Vec<Expression<'a>>),
|
||||
Or(Vec<Expression<'a>>),
|
||||
Equals(Box<Expression<'a>>, Box<Expression<'a>>),
|
||||
Dot(Box<Expression<'a>>, &'a str, Vec<Expression<'a>>),
|
||||
Aggregate {
|
||||
name: &'a str,
|
||||
vars: Vec<FormalParameter<'a>>,
|
||||
range: Option<Box<Expression<'a>>>,
|
||||
expr: Box<Expression<'a>>,
|
||||
second_expr: Option<Box<Expression<'a>>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Expression<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Expression::Var(x) => write!(f, "{}", x),
|
||||
Expression::String(s) => write!(f, "\"{}\"", s),
|
||||
Expression::Integer(n) => write!(f, "{}", n),
|
||||
Expression::Pred(n, args) => {
|
||||
write!(f, "{}(", n)?;
|
||||
for (index, arg) in args.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "{}", arg)?;
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
Expression::And(conjuncts) => {
|
||||
if conjuncts.is_empty() {
|
||||
write!(f, "any()")
|
||||
} else {
|
||||
for (index, conjunct) in conjuncts.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, " and ")?;
|
||||
}
|
||||
write!(f, "({})", conjunct)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Expression::Or(disjuncts) => {
|
||||
if disjuncts.is_empty() {
|
||||
write!(f, "none()")
|
||||
} else {
|
||||
for (index, disjunct) in disjuncts.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, " or ")?;
|
||||
}
|
||||
write!(f, "({})", disjunct)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Expression::Equals(a, b) => write!(f, "{} = {}", a, b),
|
||||
Expression::Dot(x, member_pred, args) => {
|
||||
write!(f, "{}.{}(", x, member_pred)?;
|
||||
for (index, arg) in args.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "{}", arg)?;
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
Expression::Aggregate {
|
||||
name,
|
||||
vars,
|
||||
range,
|
||||
expr,
|
||||
second_expr,
|
||||
} => {
|
||||
write!(f, "{}(", name)?;
|
||||
if !vars.is_empty() {
|
||||
for (index, var) in vars.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "{}", var)?;
|
||||
}
|
||||
write!(f, " | ")?;
|
||||
}
|
||||
if let Some(range) = range {
|
||||
write!(f, "{} | ", range)?;
|
||||
}
|
||||
write!(f, "{}", expr)?;
|
||||
if let Some(second_expr) = second_expr {
|
||||
write!(f, ", {}", second_expr)?;
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Predicate<'a> {
|
||||
pub qldoc: Option<String>,
|
||||
pub name: &'a str,
|
||||
pub overridden: bool,
|
||||
pub return_type: Option<Type<'a>>,
|
||||
pub formal_parameters: Vec<FormalParameter<'a>>,
|
||||
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(())
|
||||
}
|
||||
610
ruby/generator/src/ql_gen.rs
Normal file
610
ruby/generator/src/ql_gen.rs
Normal file
@@ -0,0 +1,610 @@
|
||||
use crate::ql;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
/// Creates the hard-coded `AstNode` class that acts as a supertype of all
|
||||
/// classes we generate.
|
||||
pub fn create_ast_node_class<'a>(ast_node: &'a str, ast_node_parent: &'a str) -> ql::Class<'a> {
|
||||
// Default implementation of `toString` calls `this.getAPrimaryQlClass()`
|
||||
let to_string = ql::Predicate {
|
||||
qldoc: Some(String::from(
|
||||
"Gets a string representation of this element.",
|
||||
)),
|
||||
name: "toString",
|
||||
overridden: false,
|
||||
return_type: Some(ql::Type::String),
|
||||
formal_parameters: vec![],
|
||||
body: ql::Expression::Equals(
|
||||
Box::new(ql::Expression::Var("result")),
|
||||
Box::new(ql::Expression::Dot(
|
||||
Box::new(ql::Expression::Var("this")),
|
||||
"getAPrimaryQlClass",
|
||||
vec![],
|
||||
)),
|
||||
),
|
||||
};
|
||||
let get_location = create_none_predicate(
|
||||
Some(String::from("Gets the location of this element.")),
|
||||
"getLocation",
|
||||
false,
|
||||
Some(ql::Type::Normal("Location")),
|
||||
);
|
||||
let get_a_field_or_child = create_none_predicate(
|
||||
Some(String::from("Gets a field or child node of this node.")),
|
||||
"getAFieldOrChild",
|
||||
false,
|
||||
Some(ql::Type::Normal("AstNode")),
|
||||
);
|
||||
let get_parent = ql::Predicate {
|
||||
qldoc: Some(String::from("Gets the parent of this element.")),
|
||||
name: "getParent",
|
||||
overridden: false,
|
||||
return_type: Some(ql::Type::Normal("AstNode")),
|
||||
formal_parameters: vec![],
|
||||
body: ql::Expression::Pred(
|
||||
ast_node_parent,
|
||||
vec![
|
||||
ql::Expression::Var("this"),
|
||||
ql::Expression::Var("result"),
|
||||
ql::Expression::Var("_"),
|
||||
],
|
||||
),
|
||||
};
|
||||
let get_parent_index = ql::Predicate {
|
||||
qldoc: Some(String::from(
|
||||
"Gets the index of this node among the children of its parent.",
|
||||
)),
|
||||
name: "getParentIndex",
|
||||
overridden: false,
|
||||
return_type: Some(ql::Type::Int),
|
||||
formal_parameters: vec![],
|
||||
body: ql::Expression::Pred(
|
||||
ast_node_parent,
|
||||
vec![
|
||||
ql::Expression::Var("this"),
|
||||
ql::Expression::Var("_"),
|
||||
ql::Expression::Var("result"),
|
||||
],
|
||||
),
|
||||
};
|
||||
let get_a_primary_ql_class = ql::Predicate {
|
||||
qldoc: Some(String::from(
|
||||
"Gets the name of the primary QL class for this element.",
|
||||
)),
|
||||
name: "getAPrimaryQlClass",
|
||||
overridden: false,
|
||||
return_type: Some(ql::Type::String),
|
||||
formal_parameters: vec![],
|
||||
body: ql::Expression::Equals(
|
||||
Box::new(ql::Expression::Var("result")),
|
||||
Box::new(ql::Expression::String("???")),
|
||||
),
|
||||
};
|
||||
let get_primary_ql_classes = ql::Predicate {
|
||||
qldoc: Some(
|
||||
"Gets a comma-separated list of the names of the primary CodeQL \
|
||||
classes to which this element belongs."
|
||||
.to_owned(),
|
||||
),
|
||||
name: "getPrimaryQlClasses",
|
||||
overridden: false,
|
||||
return_type: Some(ql::Type::String),
|
||||
formal_parameters: vec![],
|
||||
body: ql::Expression::Equals(
|
||||
Box::new(ql::Expression::Var("result")),
|
||||
Box::new(ql::Expression::Aggregate {
|
||||
name: "concat",
|
||||
vars: vec![],
|
||||
range: None,
|
||||
expr: Box::new(ql::Expression::Dot(
|
||||
Box::new(ql::Expression::Var("this")),
|
||||
"getAPrimaryQlClass",
|
||||
vec![],
|
||||
)),
|
||||
second_expr: Some(Box::new(ql::Expression::String(","))),
|
||||
}),
|
||||
),
|
||||
};
|
||||
ql::Class {
|
||||
qldoc: Some(String::from("The base class for all AST nodes")),
|
||||
name: "AstNode",
|
||||
is_abstract: false,
|
||||
supertypes: vec![ql::Type::At(ast_node)].into_iter().collect(),
|
||||
characteristic_predicate: None,
|
||||
predicates: vec![
|
||||
to_string,
|
||||
get_location,
|
||||
get_parent,
|
||||
get_parent_index,
|
||||
get_a_field_or_child,
|
||||
get_a_primary_ql_class,
|
||||
get_primary_ql_classes,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_token_class<'a>(token_type: &'a str, tokeninfo: &'a str) -> ql::Class<'a> {
|
||||
let tokeninfo_arity = 4;
|
||||
let get_value = ql::Predicate {
|
||||
qldoc: Some(String::from("Gets the value of this token.")),
|
||||
name: "getValue",
|
||||
overridden: false,
|
||||
return_type: Some(ql::Type::String),
|
||||
formal_parameters: vec![],
|
||||
body: create_get_field_expr_for_column_storage("result", tokeninfo, 1, tokeninfo_arity),
|
||||
};
|
||||
let get_location = ql::Predicate {
|
||||
qldoc: Some(String::from("Gets the location of this token.")),
|
||||
name: "getLocation",
|
||||
overridden: true,
|
||||
return_type: Some(ql::Type::Normal("Location")),
|
||||
formal_parameters: vec![],
|
||||
body: create_get_field_expr_for_column_storage("result", tokeninfo, 2, tokeninfo_arity),
|
||||
};
|
||||
let to_string = ql::Predicate {
|
||||
qldoc: Some(String::from(
|
||||
"Gets a string representation of this element.",
|
||||
)),
|
||||
name: "toString",
|
||||
overridden: true,
|
||||
return_type: Some(ql::Type::String),
|
||||
formal_parameters: vec![],
|
||||
body: ql::Expression::Equals(
|
||||
Box::new(ql::Expression::Var("result")),
|
||||
Box::new(ql::Expression::Dot(
|
||||
Box::new(ql::Expression::Var("this")),
|
||||
"getValue",
|
||||
vec![],
|
||||
)),
|
||||
),
|
||||
};
|
||||
ql::Class {
|
||||
qldoc: Some(String::from("A token.")),
|
||||
name: "Token",
|
||||
is_abstract: false,
|
||||
supertypes: vec![ql::Type::At(token_type), ql::Type::Normal("AstNode")]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
characteristic_predicate: None,
|
||||
predicates: vec![
|
||||
get_value,
|
||||
get_location,
|
||||
to_string,
|
||||
create_get_a_primary_ql_class("Token"),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
// Creates the `ReservedWord` class.
|
||||
pub fn create_reserved_word_class(db_name: &str) -> ql::Class {
|
||||
let class_name = "ReservedWord";
|
||||
let get_a_primary_ql_class = create_get_a_primary_ql_class(class_name);
|
||||
ql::Class {
|
||||
qldoc: Some(String::from("A reserved word.")),
|
||||
name: class_name,
|
||||
is_abstract: false,
|
||||
supertypes: vec![ql::Type::At(db_name), ql::Type::Normal("Token")]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
characteristic_predicate: None,
|
||||
predicates: vec![get_a_primary_ql_class],
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a predicate whose body is `none()`.
|
||||
fn create_none_predicate<'a>(
|
||||
qldoc: Option<String>,
|
||||
name: &'a str,
|
||||
overridden: bool,
|
||||
return_type: Option<ql::Type<'a>>,
|
||||
) -> ql::Predicate<'a> {
|
||||
ql::Predicate {
|
||||
qldoc,
|
||||
name,
|
||||
overridden,
|
||||
return_type,
|
||||
formal_parameters: Vec::new(),
|
||||
body: ql::Expression::Pred("none", vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an overridden `getAPrimaryQlClass` predicate that returns the given
|
||||
/// name.
|
||||
fn create_get_a_primary_ql_class(class_name: &str) -> ql::Predicate {
|
||||
ql::Predicate {
|
||||
qldoc: Some(String::from(
|
||||
"Gets the name of the primary QL class for this element.",
|
||||
)),
|
||||
name: "getAPrimaryQlClass",
|
||||
overridden: true,
|
||||
return_type: Some(ql::Type::String),
|
||||
formal_parameters: vec![],
|
||||
body: ql::Expression::Equals(
|
||||
Box::new(ql::Expression::Var("result")),
|
||||
Box::new(ql::Expression::String(class_name)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the `getLocation` predicate.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// `def_table` - the name of the table that defines the entity and its location.
|
||||
/// `arity` - the total number of columns in the table
|
||||
fn create_get_location_predicate(def_table: &str, arity: usize) -> ql::Predicate {
|
||||
ql::Predicate {
|
||||
qldoc: Some(String::from("Gets the location of this element.")),
|
||||
name: "getLocation",
|
||||
overridden: true,
|
||||
return_type: Some(ql::Type::Normal("Location")),
|
||||
formal_parameters: vec![],
|
||||
// body of the form: foo_bar_def(_, _, ..., result)
|
||||
body: ql::Expression::Pred(
|
||||
def_table,
|
||||
[
|
||||
vec![ql::Expression::Var("this")],
|
||||
vec![ql::Expression::Var("_"); arity - 2],
|
||||
vec![ql::Expression::Var("result")],
|
||||
]
|
||||
.concat(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the `getText` predicate for a leaf node.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// `def_table` - the name of the table that defines the entity and its text.
|
||||
fn create_get_text_predicate(def_table: &str) -> ql::Predicate {
|
||||
ql::Predicate {
|
||||
qldoc: Some(String::from("Gets the text content of this element.")),
|
||||
name: "getText",
|
||||
overridden: false,
|
||||
return_type: Some(ql::Type::String),
|
||||
formal_parameters: vec![],
|
||||
body: ql::Expression::Pred(
|
||||
def_table,
|
||||
vec![
|
||||
ql::Expression::Var("this"),
|
||||
ql::Expression::Var("result"),
|
||||
ql::Expression::Var("_"),
|
||||
],
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an expression to get a field that's defined as a column in the parent's table.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `result_var_name` - the name of the variable to which the resulting value should be bound
|
||||
/// * `table_name` - the name of parent's defining table
|
||||
/// * `column_index` - the index in that table that defines the field
|
||||
/// * `arity` - the total number of columns in the table
|
||||
fn create_get_field_expr_for_column_storage<'a>(
|
||||
result_var_name: &'a str,
|
||||
table_name: &'a str,
|
||||
column_index: usize,
|
||||
arity: usize,
|
||||
) -> ql::Expression<'a> {
|
||||
let num_underscores_before = column_index;
|
||||
let num_underscores_after = arity - 2 - num_underscores_before;
|
||||
ql::Expression::Pred(
|
||||
table_name,
|
||||
[
|
||||
vec![ql::Expression::Var("this")],
|
||||
vec![ql::Expression::Var("_"); num_underscores_before],
|
||||
vec![ql::Expression::Var(result_var_name)],
|
||||
vec![ql::Expression::Var("_"); num_underscores_after],
|
||||
]
|
||||
.concat(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns an expression to get the field with the given index from its
|
||||
/// auxiliary table. The index name can be "_" so the expression will hold for
|
||||
/// all indices.
|
||||
fn create_get_field_expr_for_table_storage<'a>(
|
||||
result_var_name: &'a str,
|
||||
table_name: &'a str,
|
||||
index_var_name: Option<&'a str>,
|
||||
) -> ql::Expression<'a> {
|
||||
ql::Expression::Pred(
|
||||
table_name,
|
||||
match index_var_name {
|
||||
Some(index_var_name) => vec![
|
||||
ql::Expression::Var("this"),
|
||||
ql::Expression::Var(index_var_name),
|
||||
ql::Expression::Var(result_var_name),
|
||||
],
|
||||
None => vec![ql::Expression::Var("this"), ql::Expression::Var("result")],
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a pair consisting of a predicate to get the given field, and an
|
||||
/// optional expression that will get the same field. When the field can occur
|
||||
/// multiple times, the predicate will take an index argument, while the
|
||||
/// expression will use the "don't care" expression to hold for all occurrences.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// `main_table_name` - the name of the defining table for the parent node
|
||||
/// `main_table_arity` - the number of columns in the main table
|
||||
/// `main_table_column_index` - a mutable reference to a column index indicating
|
||||
/// where the field is in the main table. If this is used (i.e. the field has
|
||||
/// column storage), then the index is incremented.
|
||||
/// `parent_name` - the name of the parent node
|
||||
/// `field` - the field whose getters we are creating
|
||||
/// `field_type` - the db name of the field's type (possibly being a union we created)
|
||||
fn create_field_getters<'a>(
|
||||
main_table_name: &'a str,
|
||||
main_table_arity: usize,
|
||||
main_table_column_index: &mut usize,
|
||||
field: &'a node_types::Field,
|
||||
nodes: &'a node_types::NodeTypeMap,
|
||||
) -> (ql::Predicate<'a>, Option<ql::Expression<'a>>) {
|
||||
let return_type = match &field.type_info {
|
||||
node_types::FieldTypeInfo::Single(t) => {
|
||||
Some(ql::Type::Normal(&nodes.get(t).unwrap().ql_class_name))
|
||||
}
|
||||
node_types::FieldTypeInfo::Multiple {
|
||||
types: _,
|
||||
dbscheme_union: _,
|
||||
ql_class,
|
||||
} => Some(ql::Type::Normal(ql_class)),
|
||||
node_types::FieldTypeInfo::ReservedWordInt(_) => Some(ql::Type::String),
|
||||
};
|
||||
let formal_parameters = match &field.storage {
|
||||
node_types::Storage::Column { .. } => vec![],
|
||||
node_types::Storage::Table { has_index, .. } => {
|
||||
if *has_index {
|
||||
vec![ql::FormalParameter {
|
||||
name: "i",
|
||||
param_type: ql::Type::Int,
|
||||
}]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// For the expression to get a value, what variable name should the result
|
||||
// be bound to?
|
||||
let get_value_result_var_name = match &field.type_info {
|
||||
node_types::FieldTypeInfo::ReservedWordInt(_) => "value",
|
||||
node_types::FieldTypeInfo::Single(_) => "result",
|
||||
node_types::FieldTypeInfo::Multiple { .. } => "result",
|
||||
};
|
||||
|
||||
// Two expressions for getting the value. One that's suitable use in the
|
||||
// getter predicate (where there may be a specific index), and another for
|
||||
// use in `getAFieldOrChild` (where we use a "don't care" expression to
|
||||
// match any index).
|
||||
let (get_value, get_value_any_index) = match &field.storage {
|
||||
node_types::Storage::Column { name: _ } => {
|
||||
let column_index = *main_table_column_index;
|
||||
*main_table_column_index += 1;
|
||||
(
|
||||
create_get_field_expr_for_column_storage(
|
||||
get_value_result_var_name,
|
||||
main_table_name,
|
||||
column_index,
|
||||
main_table_arity,
|
||||
),
|
||||
create_get_field_expr_for_column_storage(
|
||||
get_value_result_var_name,
|
||||
main_table_name,
|
||||
column_index,
|
||||
main_table_arity,
|
||||
),
|
||||
)
|
||||
}
|
||||
node_types::Storage::Table {
|
||||
name: field_table_name,
|
||||
has_index,
|
||||
column_name: _,
|
||||
} => (
|
||||
create_get_field_expr_for_table_storage(
|
||||
get_value_result_var_name,
|
||||
field_table_name,
|
||||
if *has_index { Some("i") } else { None },
|
||||
),
|
||||
create_get_field_expr_for_table_storage(
|
||||
get_value_result_var_name,
|
||||
field_table_name,
|
||||
if *has_index { Some("_") } else { None },
|
||||
),
|
||||
),
|
||||
};
|
||||
let (body, optional_expr) = match &field.type_info {
|
||||
node_types::FieldTypeInfo::ReservedWordInt(int_mapping) => {
|
||||
// Create an expression that binds the corresponding string to `result` for each `value`, e.g.:
|
||||
// result = "foo" and value = 0 or
|
||||
// result = "bar" and value = 1 or
|
||||
// result = "baz" and value = 2
|
||||
let disjuncts = int_mapping
|
||||
.iter()
|
||||
.map(|(token_str, (value, _))| {
|
||||
ql::Expression::And(vec![
|
||||
ql::Expression::Equals(
|
||||
Box::new(ql::Expression::Var("result")),
|
||||
Box::new(ql::Expression::String(token_str)),
|
||||
),
|
||||
ql::Expression::Equals(
|
||||
Box::new(ql::Expression::Var("value")),
|
||||
Box::new(ql::Expression::Integer(*value)),
|
||||
),
|
||||
])
|
||||
})
|
||||
.collect();
|
||||
(
|
||||
ql::Expression::Aggregate {
|
||||
name: "exists",
|
||||
vars: vec![ql::FormalParameter {
|
||||
name: "value",
|
||||
param_type: ql::Type::Int,
|
||||
}],
|
||||
range: Some(Box::new(get_value)),
|
||||
expr: Box::new(ql::Expression::Or(disjuncts)),
|
||||
second_expr: None,
|
||||
},
|
||||
// Since the getter returns a string and not an AstNode, it won't be part of getAFieldOrChild:
|
||||
None,
|
||||
)
|
||||
}
|
||||
node_types::FieldTypeInfo::Single(_) | node_types::FieldTypeInfo::Multiple { .. } => {
|
||||
(get_value, Some(get_value_any_index))
|
||||
}
|
||||
};
|
||||
let qldoc = match &field.name {
|
||||
Some(name) => format!("Gets the node corresponding to the field `{}`.", name),
|
||||
None => {
|
||||
if formal_parameters.is_empty() {
|
||||
"Gets the child of this node.".to_owned()
|
||||
} else {
|
||||
"Gets the `i`th child of this node.".to_owned()
|
||||
}
|
||||
}
|
||||
};
|
||||
(
|
||||
ql::Predicate {
|
||||
qldoc: Some(qldoc),
|
||||
name: &field.getter_name,
|
||||
overridden: false,
|
||||
return_type,
|
||||
formal_parameters,
|
||||
body,
|
||||
},
|
||||
optional_expr,
|
||||
)
|
||||
}
|
||||
|
||||
/// Converts the given node types into CodeQL classes wrapping the dbscheme.
|
||||
pub fn convert_nodes(nodes: &node_types::NodeTypeMap) -> Vec<ql::TopLevel> {
|
||||
let mut classes: Vec<ql::TopLevel> = 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<ql::Type> = BTreeSet::new();
|
||||
supertypes.insert(ql::Type::At(&node.dbscheme_name));
|
||||
supertypes.insert(ql::Type::Normal("Token"));
|
||||
classes.push(ql::TopLevel::Class(ql::Class {
|
||||
qldoc: Some(format!("A class representing `{}` tokens.", type_name.kind)),
|
||||
name: &node.ql_class_name,
|
||||
is_abstract: false,
|
||||
supertypes,
|
||||
characteristic_predicate: None,
|
||||
predicates: vec![get_a_primary_ql_class],
|
||||
}));
|
||||
}
|
||||
}
|
||||
node_types::EntryKind::Union { members: _ } => {
|
||||
// It's a tree-sitter supertype node, so we're wrapping a dbscheme
|
||||
// union type.
|
||||
classes.push(ql::TopLevel::Class(ql::Class {
|
||||
qldoc: None,
|
||||
name: &node.ql_class_name,
|
||||
is_abstract: false,
|
||||
supertypes: vec![
|
||||
ql::Type::At(&node.dbscheme_name),
|
||||
ql::Type::Normal("AstNode"),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
characteristic_predicate: None,
|
||||
predicates: vec![],
|
||||
}));
|
||||
}
|
||||
node_types::EntryKind::Table {
|
||||
name: main_table_name,
|
||||
fields,
|
||||
} => {
|
||||
// Count how many columns there will be in the main table.
|
||||
// There will be:
|
||||
// - one for the id
|
||||
// - one for the location
|
||||
// - one for each field that's stored as a column
|
||||
// - if there are no fields, one for the text column.
|
||||
let main_table_arity = 2 + if fields.is_empty() {
|
||||
1
|
||||
} else {
|
||||
fields
|
||||
.iter()
|
||||
.filter(|&f| matches!(f.storage, node_types::Storage::Column { .. }))
|
||||
.count()
|
||||
};
|
||||
|
||||
let main_class_name = &node.ql_class_name;
|
||||
let mut main_class = ql::Class {
|
||||
qldoc: Some(format!("A class representing `{}` nodes.", type_name.kind)),
|
||||
name: main_class_name,
|
||||
is_abstract: false,
|
||||
supertypes: vec![
|
||||
ql::Type::At(&node.dbscheme_name),
|
||||
ql::Type::Normal("AstNode"),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
characteristic_predicate: None,
|
||||
predicates: vec![
|
||||
create_get_a_primary_ql_class(main_class_name),
|
||||
create_get_location_predicate(main_table_name, main_table_arity),
|
||||
],
|
||||
};
|
||||
|
||||
if fields.is_empty() {
|
||||
main_class
|
||||
.predicates
|
||||
.push(create_get_text_predicate(main_table_name));
|
||||
} else {
|
||||
let mut main_table_column_index: usize = 0;
|
||||
let mut get_child_exprs: Vec<ql::Expression> = 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
|
||||
}
|
||||
11
ruby/node-types/Cargo.toml
Normal file
11
ruby/node-types/Cargo.toml
Normal file
@@ -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"
|
||||
440
ruby/node-types/src/lib.rs
Normal file
440
ruby/node-types/src/lib.rs
Normal file
@@ -0,0 +1,440 @@
|
||||
use serde::Deserialize;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
|
||||
use std::collections::BTreeSet as Set;
|
||||
use std::fs;
|
||||
|
||||
/// A lookup table from TypeName to Entry.
|
||||
pub type NodeTypeMap = BTreeMap<TypeName, Entry>;
|
||||
|
||||
#[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<TypeName> },
|
||||
Table { name: String, fields: Vec<Field> },
|
||||
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<TypeName>,
|
||||
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<String, (usize, String)>),
|
||||
}
|
||||
|
||||
#[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<String>,
|
||||
/// 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>) -> 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<NodeTypeMap> {
|
||||
let file = fs::File::open(node_types_path)?;
|
||||
let node_types: Vec<NodeInfo> = 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<NodeTypeMap> {
|
||||
let node_types: Vec<NodeInfo> = serde_json::from_str(node_types_json)?;
|
||||
Ok(convert_nodes(prefix, &node_types))
|
||||
}
|
||||
|
||||
fn convert_type(node_type: &NodeType) -> TypeName {
|
||||
TypeName {
|
||||
kind: node_type.kind.to_string(),
|
||||
named: node_type.named,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_types(node_types: &[NodeType]) -> Set<TypeName> {
|
||||
node_types.iter().map(convert_type).collect()
|
||||
}
|
||||
|
||||
pub fn convert_nodes(prefix: &str, nodes: &[NodeInfo]) -> NodeTypeMap {
|
||||
let mut entries = NodeTypeMap::new();
|
||||
let mut token_kinds = Set::new();
|
||||
|
||||
// First, find all the token kinds
|
||||
for node in nodes {
|
||||
if node.subtypes.is_none()
|
||||
&& node.fields.as_ref().map_or(0, |x| x.len()) == 0
|
||||
&& node.children.is_none()
|
||||
{
|
||||
let type_name = TypeName {
|
||||
kind: node.kind.clone(),
|
||||
named: node.named,
|
||||
};
|
||||
token_kinds.insert(type_name);
|
||||
}
|
||||
}
|
||||
|
||||
for node in nodes {
|
||||
let flattened_name = &node_type_name(&node.kind, node.named);
|
||||
let dbscheme_name = escape_name(flattened_name);
|
||||
let ql_class_name = dbscheme_name_to_class_name(&dbscheme_name);
|
||||
let dbscheme_name = format!("{}_{}", prefix, &dbscheme_name);
|
||||
if let Some(subtypes) = &node.subtypes {
|
||||
// It's a tree-sitter supertype node, for which we create a union
|
||||
// type.
|
||||
entries.insert(
|
||||
TypeName {
|
||||
kind: node.kind.clone(),
|
||||
named: node.named,
|
||||
},
|
||||
Entry {
|
||||
dbscheme_name,
|
||||
ql_class_name,
|
||||
kind: EntryKind::Union {
|
||||
members: convert_types(subtypes),
|
||||
},
|
||||
},
|
||||
);
|
||||
} else if node.fields.as_ref().map_or(0, |x| x.len()) == 0 && node.children.is_none() {
|
||||
// Token kind, handled above.
|
||||
} else {
|
||||
// It's a product type, defined by a table.
|
||||
let type_name = TypeName {
|
||||
kind: node.kind.clone(),
|
||||
named: node.named,
|
||||
};
|
||||
let table_name = escape_name(&(format!("{}_def", &flattened_name)));
|
||||
let table_name = format!("{}_{}", prefix, &table_name);
|
||||
|
||||
let mut fields = Vec::new();
|
||||
|
||||
// If the type also has fields or children, then we create either
|
||||
// auxiliary tables or columns in the defining table for them.
|
||||
if let Some(node_fields) = &node.fields {
|
||||
for (field_name, field_info) in node_fields {
|
||||
add_field(
|
||||
prefix,
|
||||
&type_name,
|
||||
Some(field_name.to_string()),
|
||||
field_info,
|
||||
&mut fields,
|
||||
&token_kinds,
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(children) = &node.children {
|
||||
// Treat children as if they were a field called 'child'.
|
||||
add_field(
|
||||
prefix,
|
||||
&type_name,
|
||||
None,
|
||||
children,
|
||||
&mut fields,
|
||||
&token_kinds,
|
||||
);
|
||||
}
|
||||
entries.insert(
|
||||
type_name,
|
||||
Entry {
|
||||
dbscheme_name,
|
||||
ql_class_name,
|
||||
kind: EntryKind::Table {
|
||||
name: table_name,
|
||||
fields,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
let mut counter = 0;
|
||||
for type_name in token_kinds {
|
||||
let entry = if type_name.named {
|
||||
counter += 1;
|
||||
let unprefixed_name = node_type_name(&type_name.kind, true);
|
||||
Entry {
|
||||
dbscheme_name: escape_name(&format!("{}_token_{}", &prefix, &unprefixed_name)),
|
||||
ql_class_name: dbscheme_name_to_class_name(&escape_name(&unprefixed_name)),
|
||||
kind: EntryKind::Token { kind_id: counter },
|
||||
}
|
||||
} else {
|
||||
Entry {
|
||||
dbscheme_name: format!("{}_reserved_word", &prefix),
|
||||
ql_class_name: "ReservedWord".to_owned(),
|
||||
kind: EntryKind::Token { kind_id: 0 },
|
||||
}
|
||||
};
|
||||
entries.insert(type_name, entry);
|
||||
}
|
||||
entries
|
||||
}
|
||||
|
||||
fn add_field(
|
||||
prefix: &str,
|
||||
parent_type_name: &TypeName,
|
||||
field_name: Option<String>,
|
||||
field_info: &FieldInfo,
|
||||
fields: &mut Vec<Field>,
|
||||
token_kinds: &Set<TypeName>,
|
||||
) {
|
||||
let parent_flattened_name = node_type_name(&parent_type_name.kind, parent_type_name.named);
|
||||
let column_name = escape_name(&name_for_field_or_child(&field_name));
|
||||
let storage = if !field_info.multiple && field_info.required {
|
||||
// This field must appear exactly once, so we add it as
|
||||
// a column to the main table for the node type.
|
||||
Storage::Column { name: column_name }
|
||||
} else {
|
||||
// Put the field in an auxiliary table.
|
||||
let has_index = field_info.multiple;
|
||||
let field_table_name = escape_name(&format!(
|
||||
"{}_{}_{}",
|
||||
&prefix,
|
||||
parent_flattened_name,
|
||||
&name_for_field_or_child(&field_name)
|
||||
));
|
||||
Storage::Table {
|
||||
has_index,
|
||||
name: field_table_name,
|
||||
column_name,
|
||||
}
|
||||
};
|
||||
let converted_types = convert_types(&field_info.types);
|
||||
let type_info = if field_info
|
||||
.types
|
||||
.iter()
|
||||
.all(|t| !t.named && token_kinds.contains(&convert_type(t)))
|
||||
{
|
||||
// All possible types for this field are reserved words. The db
|
||||
// representation will be an `int` with a `case @foo.field = ...` to
|
||||
// enumerate the possible values.
|
||||
let mut field_token_ints: BTreeMap<String, (usize, String)> = BTreeMap::new();
|
||||
for (counter, t) in converted_types.into_iter().enumerate() {
|
||||
let dbscheme_variant_name =
|
||||
escape_name(&format!("{}_{}_{}", &prefix, parent_flattened_name, t.kind));
|
||||
field_token_ints.insert(t.kind.to_owned(), (counter, dbscheme_variant_name));
|
||||
}
|
||||
FieldTypeInfo::ReservedWordInt(field_token_ints)
|
||||
} else if field_info.types.len() == 1 {
|
||||
FieldTypeInfo::Single(converted_types.into_iter().next().unwrap())
|
||||
} else {
|
||||
// The dbscheme type for this field will be a union. In QL, it'll just be AstNode.
|
||||
FieldTypeInfo::Multiple {
|
||||
types: converted_types,
|
||||
dbscheme_union: format!(
|
||||
"{}_{}_{}_type",
|
||||
&prefix,
|
||||
&parent_flattened_name,
|
||||
&name_for_field_or_child(&field_name)
|
||||
),
|
||||
ql_class: "AstNode".to_owned(),
|
||||
}
|
||||
};
|
||||
let getter_name = format!(
|
||||
"get{}",
|
||||
dbscheme_name_to_class_name(&escape_name(&name_for_field_or_child(&field_name)))
|
||||
);
|
||||
fields.push(Field {
|
||||
parent: TypeName {
|
||||
kind: parent_type_name.kind.to_string(),
|
||||
named: parent_type_name.named,
|
||||
},
|
||||
type_info,
|
||||
name: field_name,
|
||||
getter_name,
|
||||
storage,
|
||||
});
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
pub struct NodeInfo {
|
||||
#[serde(rename = "type")]
|
||||
pub kind: String,
|
||||
pub named: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub fields: Option<BTreeMap<String, FieldInfo>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub children: Option<FieldInfo>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub subtypes: Option<Vec<NodeType>>,
|
||||
}
|
||||
|
||||
#[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<NodeType>,
|
||||
}
|
||||
|
||||
/// Given a tree-sitter node type's (kind, named) pair, returns a single string
|
||||
/// representing the (unescaped) name we'll use to refer to corresponding QL
|
||||
/// type.
|
||||
fn node_type_name(kind: &str, named: bool) -> String {
|
||||
if named {
|
||||
kind.to_string()
|
||||
} else {
|
||||
format!("{}_unnamed", kind)
|
||||
}
|
||||
}
|
||||
|
||||
const RESERVED_KEYWORDS: [&str; 14] = [
|
||||
"boolean", "case", "date", "float", "int", "key", "of", "order", "ref", "string", "subtype",
|
||||
"type", "unique", "varchar",
|
||||
];
|
||||
|
||||
/// Returns a string that's a copy of `name` but suitably escaped to be a valid
|
||||
/// QL identifier.
|
||||
fn escape_name(name: &str) -> String {
|
||||
let mut result = String::new();
|
||||
|
||||
// If there's a leading underscore, replace it with 'underscore_'.
|
||||
if let Some(c) = name.chars().next() {
|
||||
if c == '_' {
|
||||
result.push_str("underscore");
|
||||
}
|
||||
}
|
||||
for c in name.chars() {
|
||||
match c {
|
||||
'{' => result.push_str("lbrace"),
|
||||
'}' => result.push_str("rbrace"),
|
||||
'<' => result.push_str("langle"),
|
||||
'>' => result.push_str("rangle"),
|
||||
'[' => result.push_str("lbracket"),
|
||||
']' => result.push_str("rbracket"),
|
||||
'(' => result.push_str("lparen"),
|
||||
')' => result.push_str("rparen"),
|
||||
'|' => result.push_str("pipe"),
|
||||
'=' => result.push_str("equal"),
|
||||
'~' => result.push_str("tilde"),
|
||||
'?' => result.push_str("question"),
|
||||
'`' => result.push_str("backtick"),
|
||||
'^' => result.push_str("caret"),
|
||||
'!' => result.push_str("bang"),
|
||||
'#' => result.push_str("hash"),
|
||||
'%' => result.push_str("percent"),
|
||||
'&' => result.push_str("ampersand"),
|
||||
'.' => result.push_str("dot"),
|
||||
',' => result.push_str("comma"),
|
||||
'/' => result.push_str("slash"),
|
||||
':' => result.push_str("colon"),
|
||||
';' => result.push_str("semicolon"),
|
||||
'"' => result.push_str("dquote"),
|
||||
'*' => result.push_str("star"),
|
||||
'+' => result.push_str("plus"),
|
||||
'-' => result.push_str("minus"),
|
||||
'@' => result.push_str("at"),
|
||||
_ if c.is_uppercase() => {
|
||||
result.push('_');
|
||||
result.push_str(&c.to_lowercase().to_string())
|
||||
}
|
||||
_ => result.push(c),
|
||||
}
|
||||
}
|
||||
|
||||
for &keyword in &RESERVED_KEYWORDS {
|
||||
if result == keyword {
|
||||
result.push_str("__");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn to_snake_case(word: &str) -> String {
|
||||
let mut prev_upper = true;
|
||||
let mut result = String::new();
|
||||
for c in word.chars() {
|
||||
if c.is_uppercase() {
|
||||
if !prev_upper {
|
||||
result.push('_')
|
||||
}
|
||||
prev_upper = true;
|
||||
result.push(c.to_ascii_lowercase());
|
||||
} else {
|
||||
prev_upper = false;
|
||||
result.push(c);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
/// Given a valid dbscheme name (i.e. in snake case), produces the equivalent QL
|
||||
/// name (i.e. in CamelCase). For example, "foo_bar_baz" becomes "FooBarBaz".
|
||||
fn dbscheme_name_to_class_name(dbscheme_name: &str) -> String {
|
||||
fn to_title_case(word: &str) -> String {
|
||||
let mut first = true;
|
||||
let mut result = String::new();
|
||||
for c in word.chars() {
|
||||
if first {
|
||||
first = false;
|
||||
result.push(c.to_ascii_uppercase());
|
||||
} else {
|
||||
result.push(c);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
dbscheme_name
|
||||
.split('_')
|
||||
.map(|word| to_title_case(word))
|
||||
.collect::<Vec<String>>()
|
||||
.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"));
|
||||
}
|
||||
25
ruby/ql/consistency-queries/AstConsistency.ql
Normal file
25
ruby/ql/consistency-queries/AstConsistency.ql
Normal file
@@ -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
|
||||
)
|
||||
}
|
||||
1
ruby/ql/consistency-queries/CfgConsistency.ql
Normal file
1
ruby/ql/consistency-queries/CfgConsistency.ql
Normal file
@@ -0,0 +1 @@
|
||||
import codeql.ruby.controlflow.internal.ControlFlowGraphImplShared::Consistency
|
||||
1
ruby/ql/consistency-queries/DataFlowConsistency.ql
Normal file
1
ruby/ql/consistency-queries/DataFlowConsistency.ql
Normal file
@@ -0,0 +1 @@
|
||||
import codeql.ruby.dataflow.internal.DataFlowImplConsistency::Consistency
|
||||
22
ruby/ql/consistency-queries/SsaConsistency.ql
Normal file
22
ruby/ql/consistency-queries/SsaConsistency.ql
Normal file
@@ -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()
|
||||
}
|
||||
6
ruby/ql/consistency-queries/VariablesConsistency.ql
Normal file
6
ruby/ql/consistency-queries/VariablesConsistency.ql
Normal file
@@ -0,0 +1,6 @@
|
||||
import codeql.ruby.ast.Variable
|
||||
|
||||
query predicate ambiguousVariable(VariableAccess access, Variable variable) {
|
||||
access.getVariable() = variable and
|
||||
count(access.getVariable()) > 1
|
||||
}
|
||||
5
ruby/ql/consistency-queries/qlpack.yml
Normal file
5
ruby/ql/consistency-queries/qlpack.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
name: codeql/ruby-consistency-queries
|
||||
version: 0.0.1
|
||||
dependencies:
|
||||
codeql/ruby-all: 0.0.1
|
||||
|
||||
37
ruby/ql/docs/experimental.md
Normal file
37
ruby/ql/docs/experimental.md
Normal file
@@ -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://code.visualstudio.com/docs/editor/codebasics#_formatting).
|
||||
|
||||
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).
|
||||
4
ruby/ql/examples/qlpack.lock.yml
Normal file
4
ruby/ql/examples/qlpack.lock.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
dependencies: {}
|
||||
compiled: false
|
||||
lockVersion: 1.0.0
|
||||
4
ruby/ql/examples/qlpack.yml
Normal file
4
ruby/ql/examples/qlpack.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
name: codeql/ruby-examples
|
||||
version: 0.0.2
|
||||
dependencies:
|
||||
codeql/ruby-all: ^0.0.2
|
||||
1
ruby/ql/examples/queries.xml
Normal file
1
ruby/ql/examples/queries.xml
Normal file
@@ -0,0 +1 @@
|
||||
<queries language="ruby"/>
|
||||
18
ruby/ql/examples/snippets/emptythen.ql
Normal file
18
ruby/ql/examples/snippets/emptythen.ql
Normal file
@@ -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
|
||||
19
ruby/ql/lib/codeql/IDEContextual.qll
Normal file
19
ruby/ql/lib/codeql/IDEContextual.qll
Normal file
@@ -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("//", "/")
|
||||
}
|
||||
66
ruby/ql/lib/codeql/Locations.qll
Normal file
66
ruby/ql/lib/codeql/Locations.qll
Normal file
@@ -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 = this.getEndLine() - this.getStartLine() + 1 }
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() {
|
||||
exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
|
||||
this.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) }
|
||||
}
|
||||
177
ruby/ql/lib/codeql/files/FileSystem.qll
Normal file
177
ruby/ql/lib/codeql/files/FileSystem.qll
Normal file
@@ -0,0 +1,177 @@
|
||||
/** 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 = this.getAChildContainer() }
|
||||
|
||||
/** Gets a sub-folder in this container. */
|
||||
Folder getAFolder() { result = this.getAChildContainer() }
|
||||
|
||||
/**
|
||||
* Gets the absolute, canonical path of this container, using forward slashes
|
||||
* as path separator.
|
||||
*
|
||||
* The path starts with a _root prefix_ followed by zero or more _path
|
||||
* segments_ separated by forward slashes.
|
||||
*
|
||||
* The root prefix is of one of the following forms:
|
||||
*
|
||||
* 1. A single forward slash `/` (Unix-style)
|
||||
* 2. An upper-case drive letter followed by a colon and a forward slash,
|
||||
* such as `C:/` (Windows-style)
|
||||
* 3. Two forward slashes, a computer name, and then another forward slash,
|
||||
* such as `//FileServer/` (UNC-style)
|
||||
*
|
||||
* Path segments are never empty (that is, absolute paths never contain two
|
||||
* contiguous slashes, except as part of a UNC-style root prefix). Also, path
|
||||
* segments never contain forward slashes, and no path segment is of the
|
||||
* form `.` (one dot) or `..` (two dots).
|
||||
*
|
||||
* Note that an absolute path never ends with a forward slash, except if it is
|
||||
* a bare root prefix, that is, the path has no path segments. A container
|
||||
* whose absolute path has no segments is always a `Folder`, not a `File`.
|
||||
*/
|
||||
abstract string getAbsolutePath();
|
||||
|
||||
/**
|
||||
* Gets the base name of this container including extension, that is, the last
|
||||
* segment of its absolute path, or the empty string if it has no segments.
|
||||
*
|
||||
* Here are some examples of absolute paths and the corresponding base names
|
||||
* (surrounded with quotes to avoid ambiguity):
|
||||
*
|
||||
* <table border="1">
|
||||
* <tr><th>Absolute path</th><th>Base name</th></tr>
|
||||
* <tr><td>"/tmp/tst.go"</td><td>"tst.go"</td></tr>
|
||||
* <tr><td>"C:/Program Files (x86)"</td><td>"Program Files (x86)"</td></tr>
|
||||
* <tr><td>"/"</td><td>""</td></tr>
|
||||
* <tr><td>"C:/"</td><td>""</td></tr>
|
||||
* <tr><td>"D:/"</td><td>""</td></tr>
|
||||
* <tr><td>"//FileServer/"</td><td>""</td></tr>
|
||||
* </table>
|
||||
*/
|
||||
string getBaseName() {
|
||||
result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension of this container, that is, the suffix of its base name
|
||||
* after the last dot character, if any.
|
||||
*
|
||||
* In particular,
|
||||
*
|
||||
* - if the name does not include a dot, there is no extension, so this
|
||||
* predicate has no result;
|
||||
* - if the name ends in a dot, the extension is the empty string;
|
||||
* - if the name contains multiple dots, the extension follows the last dot.
|
||||
*
|
||||
* Here are some examples of absolute paths and the corresponding extensions
|
||||
* (surrounded with quotes to avoid ambiguity):
|
||||
*
|
||||
* <table border="1">
|
||||
* <tr><th>Absolute path</th><th>Extension</th></tr>
|
||||
* <tr><td>"/tmp/tst.go"</td><td>"go"</td></tr>
|
||||
* <tr><td>"/tmp/.classpath"</td><td>"classpath"</td></tr>
|
||||
* <tr><td>"/bin/bash"</td><td>not defined</td></tr>
|
||||
* <tr><td>"/tmp/tst2."</td><td>""</td></tr>
|
||||
* <tr><td>"/tmp/x.tar.gz"</td><td>"gz"</td></tr>
|
||||
* </table>
|
||||
*/
|
||||
string getExtension() {
|
||||
result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3)
|
||||
}
|
||||
|
||||
/** Gets the file in this container that has the given `baseName`, if any. */
|
||||
File getFile(string baseName) {
|
||||
result = this.getAFile() and
|
||||
result.getBaseName() = baseName
|
||||
}
|
||||
|
||||
/** Gets the sub-folder in this container that has the given `baseName`, if any. */
|
||||
Folder getFolder(string baseName) {
|
||||
result = this.getAFolder() and
|
||||
result.getBaseName() = baseName
|
||||
}
|
||||
|
||||
/** Gets the parent container of this file or folder, if any. */
|
||||
Container getParentContainer() { containerparent(result, this) }
|
||||
|
||||
/**
|
||||
* Gets the relative path of this file or folder from the root folder of the
|
||||
* analyzed source location. The relative path of the root folder itself is
|
||||
* the empty string.
|
||||
*
|
||||
* This has no result if the container is outside the source root, that is,
|
||||
* if the root folder is not a reflexive, transitive parent of this container.
|
||||
*/
|
||||
string getRelativePath() {
|
||||
exists(string absPath, string pref |
|
||||
absPath = this.getAbsolutePath() and sourceLocationPrefix(pref)
|
||||
|
|
||||
absPath = pref and result = ""
|
||||
or
|
||||
absPath = pref.regexpReplaceAll("/$", "") + "/" + result and
|
||||
not result.matches("/%")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stem of this container, that is, the prefix of its base name up to
|
||||
* (but not including) the last dot character if there is one, or the entire
|
||||
* base name if there is not.
|
||||
*
|
||||
* Here are some examples of absolute paths and the corresponding stems
|
||||
* (surrounded with quotes to avoid ambiguity):
|
||||
*
|
||||
* <table border="1">
|
||||
* <tr><th>Absolute path</th><th>Stem</th></tr>
|
||||
* <tr><td>"/tmp/tst.go"</td><td>"tst"</td></tr>
|
||||
* <tr><td>"/tmp/.classpath"</td><td>""</td></tr>
|
||||
* <tr><td>"/bin/bash"</td><td>"bash"</td></tr>
|
||||
* <tr><td>"/tmp/tst2."</td><td>"tst2"</td></tr>
|
||||
* <tr><td>"/tmp/x.tar.gz"</td><td>"x.tar"</td></tr>
|
||||
* </table>
|
||||
*/
|
||||
string getStem() {
|
||||
result = this.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 = this.getAbsolutePath() }
|
||||
}
|
||||
|
||||
/** A folder. */
|
||||
class Folder extends Container, @folder {
|
||||
override string getAbsolutePath() { folders(this, result) }
|
||||
|
||||
/** Gets the URL of this folder. */
|
||||
override string getURL() { result = "folder://" + this.getAbsolutePath() }
|
||||
}
|
||||
|
||||
/** A file. */
|
||||
class File extends Container, @file {
|
||||
override string getAbsolutePath() { files(this, result) }
|
||||
|
||||
/** Gets the URL of this file. */
|
||||
override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
|
||||
|
||||
/** Holds if this file was extracted from ordinary source code. */
|
||||
predicate fromSource() { any() }
|
||||
}
|
||||
141
ruby/ql/lib/codeql/ruby/AST.qll
Normal file
141
ruby/ql/lib/codeql/ruby/AST.qll
Normal file
@@ -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)) }
|
||||
}
|
||||
410
ruby/ql/lib/codeql/ruby/ApiGraphs.qll
Normal file
410
ruby/ql/lib/codeql/ruby/ApiGraphs.qll
Normal file
@@ -0,0 +1,410 @@
|
||||
/**
|
||||
* 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 = this.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 = this.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 = this.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 = this.getASuccessor(Label::member(_)) or
|
||||
result = this.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 = this.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 = this.getASuccessor(Label::return(method)) }
|
||||
|
||||
/**
|
||||
* Gets a `new` call to the function represented by this API component.
|
||||
*/
|
||||
DataFlow::Node getAnInstantiation() { result = this.getInstance().getAnImmediateUse() }
|
||||
|
||||
/**
|
||||
* Gets a node representing a subclass of the class represented by this node.
|
||||
*/
|
||||
Node getASubclass() { result = this.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 = this.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 = this.getAPredecessor(_) }
|
||||
|
||||
/**
|
||||
* Gets a node such that there is an edge in the API graph between that other node and
|
||||
* this one.
|
||||
*/
|
||||
Node getASuccessor() { result = this.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(this.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 | type = "Use " |
|
||||
result = type + this.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) { isUse(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 the root
|
||||
* node labeled `lbl` in the API graph.
|
||||
*/
|
||||
cached
|
||||
predicate useRoot(string lbl, DataFlow::Node ref) {
|
||||
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())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ref` is a use of a node that should have an incoming edge from use node
|
||||
* `base` labeled `lbl` in the API graph.
|
||||
*/
|
||||
cached
|
||||
predicate useUse(DataFlow::LocalSourceNode base, string lbl, DataFlow::Node ref) {
|
||||
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 isUse(DataFlow::Node nd) {
|
||||
useRoot(_, nd)
|
||||
or
|
||||
useUse(_, _, nd)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate useExpr(ExprCfgNode node, DataFlow::LocalSourceNode src) {
|
||||
exists(DataFlow::LocalSourceNode pred |
|
||||
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
|
||||
isUse(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 | succ = MkUse(ref) |
|
||||
pred = MkRoot() and
|
||||
useRoot(lbl, ref)
|
||||
or
|
||||
exists(DataFlow::Node nd |
|
||||
pred = MkUse(nd) and
|
||||
useUse(nd, lbl, 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()" }
|
||||
}
|
||||
5
ruby/ql/lib/codeql/ruby/CFG.qll
Normal file
5
ruby/ql/lib/codeql/ruby/CFG.qll
Normal file
@@ -0,0 +1,5 @@
|
||||
/** Provides classes representing the control flow graph. */
|
||||
|
||||
import controlflow.ControlFlowGraph
|
||||
import controlflow.CfgNodes as CfgNodes
|
||||
import controlflow.BasicBlocks
|
||||
603
ruby/ql/lib/codeql/ruby/Concepts.qll
Normal file
603
ruby/ql/lib/codeql/ruby/Concepts.qll
Normal file
@@ -0,0 +1,603 @@
|
||||
/**
|
||||
* 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
|
||||
* `<p>{}</p>`.
|
||||
*/
|
||||
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() }
|
||||
|
||||
/**
|
||||
* Holds if this request is made using a mode that disables SSL/TLS
|
||||
* certificate validation, where `disablingNode` represents the point at
|
||||
* which the validation was disabled.
|
||||
*/
|
||||
predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
super.disablesCertificateValidation(disablingNode)
|
||||
}
|
||||
}
|
||||
|
||||
/** 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();
|
||||
|
||||
/**
|
||||
* Holds if this request is made using a mode that disables SSL/TLS
|
||||
* certificate validation, where `disablingNode` represents the point at
|
||||
* which the validation was disabled.
|
||||
*/
|
||||
abstract predicate disablesCertificateValidation(DataFlow::Node disablingNode);
|
||||
}
|
||||
}
|
||||
|
||||
/** 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that parses XML content.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `XmlParserCall::Range` instead.
|
||||
*/
|
||||
class XmlParserCall extends DataFlow::Node {
|
||||
XmlParserCall::Range range;
|
||||
|
||||
XmlParserCall() { this = range }
|
||||
|
||||
/** Gets the argument that specifies the XML content to be parsed. */
|
||||
DataFlow::Node getInput() { result = range.getInput() }
|
||||
|
||||
/** Holds if this XML parser call is configured to process external entities */
|
||||
predicate externalEntitiesEnabled() { range.externalEntitiesEnabled() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new XML parsing APIs. */
|
||||
module XmlParserCall {
|
||||
/**
|
||||
* A data-flow node that parses XML content.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `class XmlParserCall` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the XML content to be parsed. */
|
||||
abstract DataFlow::Node getInput();
|
||||
|
||||
/** Holds if this XML parser call is configured to process external entities */
|
||||
abstract predicate externalEntitiesEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that may represent a database object in an ORM system.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `OrmInstantiation::Range` instead.
|
||||
*/
|
||||
class OrmInstantiation extends DataFlow::Node instanceof OrmInstantiation::Range {
|
||||
/** Holds if a call to `methodName` on this instance may return a field of this ORM object. */
|
||||
bindingset[methodName]
|
||||
predicate methodCallMayAccessField(string methodName) {
|
||||
super.methodCallMayAccessField(methodName)
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new ORM object instantiation APIs. */
|
||||
module OrmInstantiation {
|
||||
/**
|
||||
* A data-flow node that may represent a database object in an ORM system.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `OrmInstantiation` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Holds if a call to `methodName` on this instance may return a field of this ORM object. */
|
||||
bindingset[methodName]
|
||||
abstract predicate methodCallMayAccessField(string methodName);
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides classes for modeling path-related APIs. */
|
||||
module Path {
|
||||
/**
|
||||
* A data-flow node that performs path sanitization. This is often needed in order
|
||||
* to safely access paths.
|
||||
*/
|
||||
class PathSanitization extends DataFlow::Node instanceof PathSanitization::Range { }
|
||||
|
||||
/** Provides a class for modeling new path sanitization APIs. */
|
||||
module PathSanitization {
|
||||
/**
|
||||
* A data-flow node that performs path sanitization. This is often needed in order
|
||||
* to safely access paths.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node { }
|
||||
}
|
||||
}
|
||||
7
ruby/ql/lib/codeql/ruby/DataFlow.qll
Normal file
7
ruby/ql/lib/codeql/ruby/DataFlow.qll
Normal file
@@ -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
|
||||
}
|
||||
7
ruby/ql/lib/codeql/ruby/DataFlow2.qll
Normal file
7
ruby/ql/lib/codeql/ruby/DataFlow2.qll
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Provides classes for performing local (intra-procedural) and
|
||||
* global (inter-procedural) data flow analyses.
|
||||
*/
|
||||
module DataFlow2 {
|
||||
import codeql.ruby.dataflow.internal.DataFlowImpl2
|
||||
}
|
||||
52
ruby/ql/lib/codeql/ruby/Diagnostics.qll
Normal file
52
ruby/ql/lib/codeql/ruby/Diagnostics.qll
Normal file
@@ -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" }
|
||||
}
|
||||
12
ruby/ql/lib/codeql/ruby/Frameworks.qll
Normal file
12
ruby/ql/lib/codeql/ruby/Frameworks.qll
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Helper file that imports all framework modeling.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.frameworks.ActionController
|
||||
private import codeql.ruby.frameworks.ActiveRecord
|
||||
private import codeql.ruby.frameworks.ActiveStorage
|
||||
private import codeql.ruby.frameworks.ActionView
|
||||
private import codeql.ruby.frameworks.StandardLibrary
|
||||
private import codeql.ruby.frameworks.Files
|
||||
private import codeql.ruby.frameworks.HttpClients
|
||||
private import codeql.ruby.frameworks.XmlParsing
|
||||
7
ruby/ql/lib/codeql/ruby/TaintTracking.qll
Executable file
7
ruby/ql/lib/codeql/ruby/TaintTracking.qll
Executable file
@@ -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
|
||||
}
|
||||
215
ruby/ql/lib/codeql/ruby/ast/Call.qll
Normal file
215
ruby/ql/lib/codeql/ruby/ast/Call.qll
Normal file
@@ -0,0 +1,215 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `...` expression that contains forwarded arguments.
|
||||
* ```rb
|
||||
* foo(...)
|
||||
* ```
|
||||
*/
|
||||
class ForwardedArguments extends Expr, TForwardArgument {
|
||||
private Ruby::ForwardArgument g;
|
||||
|
||||
ForwardedArguments() { this = TForwardArgument(g) }
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "ForwardedArguments" }
|
||||
|
||||
final override string toString() { result = "..." }
|
||||
}
|
||||
210
ruby/ql/lib/codeql/ruby/ast/Constant.qll
Normal file
210
ruby/ql/lib/codeql/ruby/ast/Constant.qll
Normal file
@@ -0,0 +1,210 @@
|
||||
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())
|
||||
}
|
||||
|
||||
override string getValueText() { result = this.getValue().getValueText() }
|
||||
|
||||
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" }
|
||||
|
||||
/**
|
||||
* Gets the fully qualified name for this constant, based on the context in
|
||||
* which it is defined.
|
||||
*
|
||||
* For example, given
|
||||
* ```rb
|
||||
* module Foo
|
||||
* module Bar
|
||||
* class Baz
|
||||
* end
|
||||
* end
|
||||
* CONST_A = "a"
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* the constant `Baz` has the fully qualified name `Foo::Bar::Baz`, and
|
||||
* `CONST_A` has the fully qualified name `Foo::CONST_A`.
|
||||
*/
|
||||
string getQualifiedName() {
|
||||
/* get the qualified name for the parent module, then append w */
|
||||
exists(ConstantWriteAccess parent | parent = this.getEnclosingModule() |
|
||||
result = parent.getQualifiedName() + "::" + this.getName()
|
||||
)
|
||||
or
|
||||
/* base case - there's no parent module */
|
||||
not exists(ConstantWriteAccess parent | parent = this.getEnclosingModule()) and
|
||||
result = this.getName()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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" }
|
||||
}
|
||||
611
ruby/ql/lib/codeql/ruby/ast/Control.qll
Normal file
611
ruby/ql/lib/codeql/ruby/ast/Control.qll
Normal file
@@ -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 = this.getThen()
|
||||
or
|
||||
cond = true and result = this.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 = this.getThen()
|
||||
or
|
||||
cond = false and result = this.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 = this.getABranch() }
|
||||
|
||||
/** Gets the `else` branch of this case expression, if any. */
|
||||
final StmtSequence getElseBranch() { result = this.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()
|
||||
}
|
||||
}
|
||||
313
ruby/ql/lib/codeql/ruby/ast/Erb.qll
Normal file
313
ruby/ql/lib/codeql/ruby/ast/Erb.qll
Normal file
@@ -0,0 +1,313 @@
|
||||
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 file containing an ERB directive. */
|
||||
private class ErbDirectiveFile extends File {
|
||||
pragma[nomagic]
|
||||
ErbDirectiveFile() { this = any(ErbDirective dir).getLocation().getFile() }
|
||||
|
||||
/** Gets a statement in this file. */
|
||||
pragma[nomagic]
|
||||
Stmt getAStmt(int startLine, int startColumn) {
|
||||
exists(Location loc |
|
||||
result.getLocation() = loc and
|
||||
loc.getFile() = this and
|
||||
loc.getStartLine() = startLine and
|
||||
loc.getStartColumn() = startColumn
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A directive in an ERB template.
|
||||
*/
|
||||
class ErbDirective extends TDirectiveNode, ErbAstNode {
|
||||
/** Holds if this directive spans line `line` in the file `file`. */
|
||||
pragma[nomagic]
|
||||
private predicate spans(ErbDirectiveFile file, int line) {
|
||||
exists(Location loc |
|
||||
loc = this.getLocation() and
|
||||
file = loc.getFile() and
|
||||
line in [loc.getStartLine() .. loc.getEndLine()]
|
||||
)
|
||||
}
|
||||
|
||||
private predicate containsStmtStart(Stmt s) {
|
||||
// `Toplevel` statements are not contained within individual directives,
|
||||
// though their start location may appear within a directive location
|
||||
not s instanceof Toplevel and
|
||||
exists(ErbDirectiveFile file, int startLine, int startColumn |
|
||||
this.spans(file, startLine) and
|
||||
s = file.getAStmt(startLine, startColumn) and
|
||||
locationIncludesPosition(this.getLocation(), startLine, startColumn)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 base template name associated with this ERB file.
|
||||
* For instance, a file named `foo.html.erb` has a template name of `foo`.
|
||||
* A partial template file named `_item.html.erb` has a template name of `item`.
|
||||
*/
|
||||
string getTemplateName() { none() }
|
||||
|
||||
/**
|
||||
* Gets the erb template contained within this file.
|
||||
*/
|
||||
ErbTemplate getTemplate() { result = template }
|
||||
}
|
||||
|
||||
private class PartialErbFile extends ErbFile {
|
||||
PartialErbFile() { this.isPartial() }
|
||||
|
||||
// Drop the leading underscore
|
||||
override string getTemplateName() { result = this.getStem().splitAt(".", 0).suffix(1) }
|
||||
}
|
||||
|
||||
private class FullErbFile extends ErbFile {
|
||||
FullErbFile() { not this.isPartial() }
|
||||
|
||||
override string getTemplateName() { result = this.getStem().splitAt(".", 0) }
|
||||
}
|
||||
456
ruby/ql/lib/codeql/ruby/ast/Expr.qll
Normal file
456
ruby/ql/lib/codeql/ruby/ast/Expr.qll
Normal file
@@ -0,0 +1,456 @@
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.CFG
|
||||
private import internal.AST
|
||||
private import internal.TreeSitter
|
||||
|
||||
/**
|
||||
* An expression.
|
||||
*
|
||||
* This is the root QL class for all expressions.
|
||||
*/
|
||||
class Expr extends Stmt, TExpr {
|
||||
/** Gets the textual (constant) value of this expression, if any. */
|
||||
string getValueText() {
|
||||
forex(CfgNodes::ExprCfgNode n | n = this.getAControlFlowNode() | result = n.getValueText())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) = this.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(_)
|
||||
}
|
||||
}
|
||||
892
ruby/ql/lib/codeql/ruby/ast/Literal.qll
Normal file
892
ruby/ql/lib/codeql/ruby/ast/Literal.qll
Normal file
@@ -0,0 +1,892 @@
|
||||
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.
|
||||
*/
|
||||
override 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() }
|
||||
|
||||
/** Gets the value of this Boolean literal. */
|
||||
boolean getValue() {
|
||||
this.isTrue() and result = true
|
||||
or
|
||||
this.isFalse() and result = false
|
||||
}
|
||||
}
|
||||
|
||||
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 = <<SQL
|
||||
* SELECT * FROM person
|
||||
* WHERE age > 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() { this.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
|
||||
* <<IDENTIFIER
|
||||
* ```
|
||||
*/
|
||||
final string getIndentationModifier() {
|
||||
exists(string s |
|
||||
s = g.getValue() and
|
||||
s.charAt(2) = result and
|
||||
result = ["-", "~"]
|
||||
)
|
||||
}
|
||||
|
||||
final override StringComponent getComponent(int n) {
|
||||
toGenerated(result) = getHereDocBody(g).getChild(n)
|
||||
}
|
||||
|
||||
final override string toString() { result = g.getValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An array literal.
|
||||
*
|
||||
* ```rb
|
||||
* [123, 'foo', bar()]
|
||||
* %w(foo bar)
|
||||
* %i(foo bar)
|
||||
* ```
|
||||
*/
|
||||
class ArrayLiteral extends Literal, TArrayLiteral {
|
||||
final override string getAPrimaryQlClass() { result = "ArrayLiteral" }
|
||||
|
||||
/** Gets the `n`th element in this array literal. */
|
||||
final Expr getElement(int n) { result = this.(ArrayLiteralImpl).getElementImpl(n) }
|
||||
|
||||
/** Gets an element in this array literal. */
|
||||
final Expr getAnElement() { result = this.getElement(_) }
|
||||
|
||||
/** Gets the number of elements in this array literal. */
|
||||
final int getNumberOfElements() { result = this.(ArrayLiteralImpl).getNumberOfElementsImpl() }
|
||||
|
||||
final override AstNode getAChild(string pred) {
|
||||
result = super.getAChild(pred)
|
||||
or
|
||||
pred = "getElement" and result = this.getElement(_)
|
||||
}
|
||||
}
|
||||
|
||||
abstract private class ArrayLiteralImpl extends ArrayLiteral {
|
||||
abstract Expr getElementImpl(int n);
|
||||
|
||||
abstract int getNumberOfElementsImpl();
|
||||
}
|
||||
|
||||
private class RegularArrayLiteral extends ArrayLiteralImpl, TRegularArrayLiteral {
|
||||
private Ruby::Array g;
|
||||
|
||||
RegularArrayLiteral() { this = TRegularArrayLiteral(g) }
|
||||
|
||||
final override Expr getElementImpl(int i) { toGenerated(result) = g.getChild(i) }
|
||||
|
||||
final override int getNumberOfElementsImpl() { result = count(g.getChild(_)) }
|
||||
|
||||
final override string toString() { result = "[...]" }
|
||||
}
|
||||
|
||||
private class StringArrayLiteral extends ArrayLiteralImpl, TStringArrayLiteral {
|
||||
private Ruby::StringArray g;
|
||||
|
||||
StringArrayLiteral() { this = TStringArrayLiteral(g) }
|
||||
|
||||
final override Expr getElementImpl(int i) { toGenerated(result) = g.getChild(i) }
|
||||
|
||||
final override int getNumberOfElementsImpl() { result = count(g.getChild(_)) }
|
||||
|
||||
final override string toString() { result = "%w(...)" }
|
||||
}
|
||||
|
||||
private class SymbolArrayLiteral extends ArrayLiteralImpl, TSymbolArrayLiteral {
|
||||
private Ruby::SymbolArray g;
|
||||
|
||||
SymbolArrayLiteral() { this = TSymbolArrayLiteral(g) }
|
||||
|
||||
final override Expr getElementImpl(int i) { toGenerated(result) = g.getChild(i) }
|
||||
|
||||
final override int getNumberOfElementsImpl() { result = count(g.getChild(_)) }
|
||||
|
||||
final override string toString() { result = "%i(...)" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A hash literal.
|
||||
*
|
||||
* ```rb
|
||||
* { foo: 123, bar: 456 }
|
||||
* ```
|
||||
*/
|
||||
class HashLiteral extends Literal, THashLiteral {
|
||||
private Ruby::Hash g;
|
||||
|
||||
HashLiteral() { this = THashLiteral(g) }
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "HashLiteral" }
|
||||
|
||||
/**
|
||||
* Gets the `n`th element in this hash literal.
|
||||
*
|
||||
* In the following example, the 0th element is a `Pair`, and the 1st element
|
||||
* is a `HashSplatExpr`.
|
||||
*
|
||||
* ```rb
|
||||
* { foo: 123, **bar }
|
||||
* ```
|
||||
*/
|
||||
final Expr getElement(int n) { toGenerated(result) = g.getChild(n) }
|
||||
|
||||
/** Gets an element in this hash literal. */
|
||||
final Expr getAnElement() { result = this.getElement(_) }
|
||||
|
||||
/** Gets a key-value `Pair` in this hash literal. */
|
||||
final Pair getAKeyValuePair() { result = this.getAnElement() }
|
||||
|
||||
/** Gets the number of elements in this hash literal. */
|
||||
final int getNumberOfElements() { result = count(this.getAnElement()) }
|
||||
|
||||
final override string toString() { result = "{...}" }
|
||||
|
||||
final override AstNode getAChild(string pred) {
|
||||
result = super.getAChild(pred)
|
||||
or
|
||||
pred = "getElement" and result = this.getElement(_)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A range literal.
|
||||
*
|
||||
* ```rb
|
||||
* (1..10)
|
||||
* (1024...2048)
|
||||
* ```
|
||||
*/
|
||||
class RangeLiteral extends Literal, TRangeLiteral {
|
||||
final override string getAPrimaryQlClass() { result = "RangeLiteral" }
|
||||
|
||||
/** Gets the begin expression of this range, if any. */
|
||||
Expr getBegin() { none() }
|
||||
|
||||
/** Gets the end expression of this range, if any. */
|
||||
Expr getEnd() { none() }
|
||||
|
||||
/**
|
||||
* Holds if the range is inclusive of the end value, i.e. uses the `..`
|
||||
* operator.
|
||||
*/
|
||||
predicate isInclusive() { none() }
|
||||
|
||||
/**
|
||||
* Holds if the range is exclusive of the end value, i.e. uses the `...`
|
||||
* operator.
|
||||
*/
|
||||
predicate isExclusive() { none() }
|
||||
|
||||
final override AstNode getAChild(string pred) {
|
||||
result = super.getAChild(pred)
|
||||
or
|
||||
pred = "getBegin" and result = this.getBegin()
|
||||
or
|
||||
pred = "getEnd" and result = this.getEnd()
|
||||
}
|
||||
|
||||
final override string toString() {
|
||||
exists(string op |
|
||||
this.isInclusive() and op = ".."
|
||||
or
|
||||
this.isExclusive() and op = "..."
|
||||
|
|
||||
result = "_ " + op + " _"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class RangeLiteralReal extends RangeLiteral, TRangeLiteralReal {
|
||||
private Ruby::Range g;
|
||||
|
||||
RangeLiteralReal() { this = TRangeLiteralReal(g) }
|
||||
|
||||
final override Expr getBegin() { toGenerated(result) = g.getBegin() }
|
||||
|
||||
final override Expr getEnd() { toGenerated(result) = g.getEnd() }
|
||||
|
||||
final override predicate isInclusive() { g instanceof @ruby_range_dotdot }
|
||||
|
||||
final override predicate isExclusive() { g instanceof @ruby_range_dotdotdot }
|
||||
}
|
||||
|
||||
private class RangeLiteralSynth extends RangeLiteral, TRangeLiteralSynth {
|
||||
private boolean inclusive;
|
||||
|
||||
RangeLiteralSynth() { this = TRangeLiteralSynth(_, _, inclusive) }
|
||||
|
||||
final override Expr getBegin() { result = TIntegerLiteralSynth(this, 0, _) }
|
||||
|
||||
final override Expr getEnd() { result = TIntegerLiteralSynth(this, 1, _) }
|
||||
|
||||
final override predicate isInclusive() { inclusive = true }
|
||||
|
||||
final override predicate isExclusive() { inclusive = false }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method name literal. For example:
|
||||
* ```rb
|
||||
* method_name # a normal name
|
||||
* + # an operator
|
||||
* :method_name # a symbol
|
||||
* :"eval_#{name}" # a complex symbol
|
||||
* ```
|
||||
*/
|
||||
class MethodName extends Literal {
|
||||
MethodName() { MethodName::range(toGenerated(this)) }
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "MethodName" }
|
||||
}
|
||||
|
||||
private class TokenMethodName extends MethodName, TTokenMethodName {
|
||||
private MethodName::Token g;
|
||||
|
||||
TokenMethodName() { this = TTokenMethodName(g) }
|
||||
|
||||
final override string getValueText() {
|
||||
result = g.(Ruby::Token).getValue()
|
||||
or
|
||||
result = g.(Ruby::Setter).getName().getValue() + "="
|
||||
}
|
||||
|
||||
final override string toString() { result = this.getValueText() }
|
||||
}
|
||||
228
ruby/ql/lib/codeql/ruby/ast/Method.qll
Normal file
228
ruby/ql/lib/codeql/ruby/ast/Method.qll
Normal file
@@ -0,0 +1,228 @@
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.controlflow.ControlFlowGraph
|
||||
private import internal.AST
|
||||
private import internal.TreeSitter
|
||||
|
||||
/** A callable. */
|
||||
class Callable extends StmtSequence, Expr, Scope, TCallable {
|
||||
/** Gets the number of parameters of this callable. */
|
||||
final int getNumberOfParameters() { result = count(this.getAParameter()) }
|
||||
|
||||
/** Gets a parameter of this callable. */
|
||||
final Parameter getAParameter() { result = this.getParameter(_) }
|
||||
|
||||
/** Gets the `n`th parameter of this callable. */
|
||||
Parameter getParameter(int n) { none() }
|
||||
|
||||
override AstNode getAChild(string pred) {
|
||||
result = super.getAChild(pred)
|
||||
or
|
||||
pred = "getParameter" and result = this.getParameter(_)
|
||||
}
|
||||
}
|
||||
|
||||
/** A method. */
|
||||
class MethodBase extends Callable, BodyStmt, Scope, TMethodBase {
|
||||
/** Gets the name of this method. */
|
||||
string getName() { none() }
|
||||
|
||||
/** Holds if the name of this method is `name`. */
|
||||
final predicate hasName(string name) { this.getName() = name }
|
||||
|
||||
override AstNode getAChild(string pred) {
|
||||
result = Callable.super.getAChild(pred)
|
||||
or
|
||||
result = BodyStmt.super.getAChild(pred)
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to `private`. */
|
||||
private class Private extends MethodCall {
|
||||
Private() { this.getMethodName() = "private" }
|
||||
}
|
||||
|
||||
/** A normal method. */
|
||||
class Method extends MethodBase, TMethod {
|
||||
private Ruby::Method g;
|
||||
|
||||
Method() { this = TMethod(g) }
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "Method" }
|
||||
|
||||
final override string getName() {
|
||||
result = g.getName().(Ruby::Token).getValue() or
|
||||
result = g.getName().(Ruby::Setter).getName().getValue() + "="
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this is a setter method, as in the following example:
|
||||
* ```rb
|
||||
* class Person
|
||||
* def name=(n)
|
||||
* @name = n
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
final predicate isSetter() { g.getName() instanceof Ruby::Setter }
|
||||
|
||||
/**
|
||||
* Holds if this method is private. All methods with the name prefix
|
||||
* `private` are private below:
|
||||
*
|
||||
* ```rb
|
||||
* class C
|
||||
* private def private1
|
||||
* end
|
||||
*
|
||||
* def public
|
||||
* end
|
||||
*
|
||||
* def private2
|
||||
* end
|
||||
* private :private2
|
||||
*
|
||||
* private
|
||||
*
|
||||
* def private3
|
||||
* end
|
||||
*
|
||||
* def private4
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
predicate isPrivate() {
|
||||
this = any(Private p).getArgument(0)
|
||||
or
|
||||
exists(ClassDeclaration c, Private p, SymbolLiteral s |
|
||||
p.getArgument(0) = s and
|
||||
p = c.getAStmt() and
|
||||
this.getName() = s.getValueText() and
|
||||
this = c.getAStmt()
|
||||
)
|
||||
or
|
||||
exists(ClassDeclaration c, int i, int j |
|
||||
c.getStmt(i).(Private).getNumberOfArguments() = 0 and
|
||||
this = c.getStmt(j) and
|
||||
j > i
|
||||
)
|
||||
or
|
||||
// Top-level methods are private members of the Object class
|
||||
this.getEnclosingModule() instanceof Toplevel
|
||||
}
|
||||
|
||||
final override Parameter getParameter(int n) {
|
||||
toGenerated(result) = g.getParameters().getChild(n)
|
||||
}
|
||||
|
||||
final override string toString() { result = this.getName() }
|
||||
}
|
||||
|
||||
/** A singleton method. */
|
||||
class SingletonMethod extends MethodBase, TSingletonMethod {
|
||||
private Ruby::SingletonMethod g;
|
||||
|
||||
SingletonMethod() { this = TSingletonMethod(g) }
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "SingletonMethod" }
|
||||
|
||||
/** Gets the object of this singleton method. */
|
||||
final Expr getObject() { toGenerated(result) = g.getObject() }
|
||||
|
||||
final override string getName() {
|
||||
result = g.getName().(Ruby::Token).getValue()
|
||||
or
|
||||
result = g.getName().(Ruby::Setter).getName().getValue() + "="
|
||||
}
|
||||
|
||||
final override Parameter getParameter(int n) {
|
||||
toGenerated(result) = g.getParameters().getChild(n)
|
||||
}
|
||||
|
||||
final override string toString() { result = this.getName() }
|
||||
|
||||
final override AstNode getAChild(string pred) {
|
||||
result = super.getAChild(pred)
|
||||
or
|
||||
pred = "getObject" and result = this.getObject()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A lambda (anonymous method). For example:
|
||||
* ```rb
|
||||
* -> (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" }
|
||||
}
|
||||
365
ruby/ql/lib/codeql/ruby/ast/Module.qll
Normal file
365
ruby/ql/lib/codeql/ruby/ast/Module.qll
Normal file
@@ -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 = this.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 = "SingletonClass" }
|
||||
|
||||
/**
|
||||
* 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())
|
||||
)
|
||||
}
|
||||
}
|
||||
620
ruby/ql/lib/codeql/ruby/ast/Operation.qll
Normal file
620
ruby/ql/lib/codeql/ruby/ast/Operation.qll
Normal file
@@ -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 = this.getLeftOperand()
|
||||
or
|
||||
pred = "getRightOperand" and result = this.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" }
|
||||
}
|
||||
248
ruby/ql/lib/codeql/ruby/ast/Parameter.qll
Normal file
248
ruby/ql/lib/codeql/ruby/ast/Parameter.qll
Normal file
@@ -0,0 +1,248 @@
|
||||
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() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A special `...` parameter that forwards positional/keyword/block arguments:
|
||||
* ```rb
|
||||
* def foo(...)
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
class ForwardParameter extends Parameter, TForwardParameter {
|
||||
final override string getAPrimaryQlClass() { result = "ForwardParameter" }
|
||||
|
||||
final override string toString() { result = "..." }
|
||||
}
|
||||
96
ruby/ql/lib/codeql/ruby/ast/Pattern.qll
Normal file
96
ruby/ql/lib/codeql/ruby/ast/Pattern.qll
Normal file
@@ -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 = this.getElement(_) }
|
||||
}
|
||||
24
ruby/ql/lib/codeql/ruby/ast/Scope.qll
Normal file
24
ruby/ql/lib/codeql/ruby/ast/Scope.qll
Normal file
@@ -0,0 +1,24 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
class SelfScope extends Scope, TSelfScopeType { }
|
||||
251
ruby/ql/lib/codeql/ruby/ast/Statement.qll
Normal file
251
ruby/ql/lib/codeql/ruby/ast/Statement.qll
Normal file
@@ -0,0 +1,251 @@
|
||||
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 a control-flow entry node for this statement, if any */
|
||||
AstNode getAControlFlowEntryNode() { result = getAControlFlowEntryNode(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 = this.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" }
|
||||
}
|
||||
204
ruby/ql/lib/codeql/ruby/ast/Variable.qll
Normal file
204
ruby/ql/lib/codeql/ruby/ast/Variable.qll
Normal file
@@ -0,0 +1,204 @@
|
||||
/** 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 }
|
||||
}
|
||||
|
||||
/** A `self` variable. */
|
||||
class SelfVariable extends LocalVariable instanceof SelfVariableImpl { }
|
||||
|
||||
/** 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 {
|
||||
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.
|
||||
*/
|
||||
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 { }
|
||||
|
||||
/** An access to the `self` variable */
|
||||
class SelfVariableAccess extends LocalVariableAccess instanceof SelfVariableAccessImpl {
|
||||
final override string getAPrimaryQlClass() { result = "SelfVariableAccess" }
|
||||
}
|
||||
|
||||
/** An access to the `self` variable where the value is read. */
|
||||
class SelfVariableReadAccess extends SelfVariableAccess, VariableReadAccess {
|
||||
// We override the definition in `LocalVariableAccess` because it gives the
|
||||
// wrong result for synthesised `self` variables.
|
||||
override predicate isCapturedAccess() {
|
||||
this.getVariable().getDeclaringScope() != this.getCfgScope()
|
||||
}
|
||||
}
|
||||
710
ruby/ql/lib/codeql/ruby/ast/internal/AST.qll
Normal file
710
ruby/ql/lib/codeql/ruby/ast/internal/AST.qll
Normal file
@@ -0,0 +1,710 @@
|
||||
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
|
||||
TForwardParameter(Ruby::ForwardParameter g) or
|
||||
TForwardArgument(Ruby::ForwardArgument g) or
|
||||
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, AST::SelfVariable v) {
|
||||
mkSynthChild(SelfKind(v), 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 = TForwardArgument(result) or
|
||||
n = TForwardParameter(result) or
|
||||
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 or
|
||||
TForwardArgument;
|
||||
|
||||
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 or TForwardParameter;
|
||||
|
||||
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 or TSelfVariableAccess;
|
||||
|
||||
class TLocalVariableAccess =
|
||||
TLocalVariableAccessReal or TLocalVariableAccessSynth or TSelfVariableAccess;
|
||||
|
||||
class TGlobalVariableAccess = TGlobalVariableAccessReal or TGlobalVariableAccessSynth;
|
||||
|
||||
class TInstanceVariableAccess = TInstanceVariableAccessReal or TInstanceVariableAccessSynth;
|
||||
|
||||
class TClassVariableAccess = TClassVariableAccessReal or TClassVariableAccessSynth;
|
||||
|
||||
class TSelfVariableAccess = TSelfReal or TSelfSynth;
|
||||
186
ruby/ql/lib/codeql/ruby/ast/internal/Call.qll
Normal file
186
ruby/ql/lib/codeql/ruby/ast/internal/Call.qll
Normal file
@@ -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(_)) }
|
||||
}
|
||||
43
ruby/ql/lib/codeql/ruby/ast/internal/Erb.qll
Normal file
43
ruby/ql/lib/codeql/ruby/ast/internal/Erb.qll
Normal file
@@ -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;
|
||||
409
ruby/ql/lib/codeql/ruby/ast/internal/Module.qll
Normal file
409
ruby/ql/lib/codeql/ruby/ast/internal/Module.qll
Normal file
@@ -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()
|
||||
}
|
||||
198
ruby/ql/lib/codeql/ruby/ast/internal/Operation.qll
Normal file
198
ruby/ql/lib/codeql/ruby/ast/internal/Operation.qll
Normal file
@@ -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() }
|
||||
}
|
||||
19
ruby/ql/lib/codeql/ruby/ast/internal/Parameter.qll
Normal file
19
ruby/ql/lib/codeql/ruby/ast/internal/Parameter.qll
Normal file
@@ -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 }
|
||||
}
|
||||
}
|
||||
32
ruby/ql/lib/codeql/ruby/ast/internal/Pattern.qll
Normal file
32
ruby/ql/lib/codeql/ruby/ast/internal/Pattern.qll
Normal file
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user