mirror of
https://github.com/github/codeql.git
synced 2026-05-27 17:41:24 +02:00
Compare commits
1 Commits
henrymerce
...
esbena-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22c77ebb87 |
@@ -1,27 +1,11 @@
|
||||
{
|
||||
"provide": [
|
||||
"*/ql/src/qlpack.yml",
|
||||
"*/ql/lib/qlpack.yml",
|
||||
"*/ql/test/qlpack.yml",
|
||||
"*/ql/examples/qlpack.yml",
|
||||
"cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/tainted/qlpack.yml",
|
||||
"javascript/ql/experimental/adaptivethreatmodeling/lib/qlpack.yml",
|
||||
"javascript/ql/experimental/adaptivethreatmodeling/src/qlpack.yml",
|
||||
"csharp/ql/campaigns/Solorigate/lib/qlpack.yml",
|
||||
"csharp/ql/campaigns/Solorigate/src/qlpack.yml",
|
||||
"csharp/ql/campaigns/Solorigate/test/qlpack.yml",
|
||||
"misc/legacy-support/*/qlpack.yml",
|
||||
"misc/suite-helpers/qlpack.yml",
|
||||
"ruby/extractor-pack/codeql-extractor.yml",
|
||||
"ruby/ql/consistency-queries/qlpack.yml",
|
||||
"ql/ql/consistency-queries/qlpack.yml",
|
||||
"ql/extractor-pack/codeql-extractor.yml"
|
||||
],
|
||||
"versionPolicies": {
|
||||
"default": {
|
||||
"requireChangeNotes": true,
|
||||
"committedPrereleaseSuffix": "dev",
|
||||
"committedVersion": "nextPatchRelease"
|
||||
}
|
||||
}
|
||||
}
|
||||
{ "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",
|
||||
"*/ql/examples/qlpack.yml",
|
||||
"*/upgrades/qlpack.yml",
|
||||
"javascript/ql/experimental/adaptivethreatmodeling/lib/qlpack.yml",
|
||||
"javascript/ql/experimental/adaptivethreatmodeling/src/qlpack.yml",
|
||||
"misc/legacy-support/*/qlpack.yml",
|
||||
"misc/suite-helpers/qlpack.yml" ] }
|
||||
|
||||
4
.github/actions/fetch-codeql/action.yml
vendored
4
.github/actions/fetch-codeql/action.yml
vendored
@@ -8,7 +8,7 @@ runs:
|
||||
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 -d "${RUNNER_TEMP}" codeql-linux64.zip
|
||||
echo "${RUNNER_TEMP}/codeql" >> "${GITHUB_PATH}"
|
||||
unzip -q codeql-linux64.zip
|
||||
echo "${{ github.workspace }}/codeql" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
3
.github/labeler.yml
vendored
3
.github/labeler.yml
vendored
@@ -26,6 +26,3 @@ documentation:
|
||||
- "**/*.qhelp"
|
||||
- "**/*.md"
|
||||
- docs/**/*
|
||||
|
||||
"QL-for-QL":
|
||||
- ql/**/*
|
||||
1
.github/workflows/check-change-note.yml
vendored
1
.github/workflows/check-change-note.yml
vendored
@@ -7,7 +7,6 @@ on:
|
||||
- "*/ql/src/**/*.ql"
|
||||
- "*/ql/src/**/*.qll"
|
||||
- "!**/experimental/**"
|
||||
- "!ql/**"
|
||||
|
||||
jobs:
|
||||
check-change-note:
|
||||
|
||||
31
.github/workflows/post-pr-comment.yml
vendored
31
.github/workflows/post-pr-comment.yml
vendored
@@ -1,31 +0,0 @@
|
||||
name: Post pull-request comment
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Query help preview"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
post_comment:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifact
|
||||
run: gh run download "${WORKFLOW_RUN_ID}" --repo "${GITHUB_REPOSITORY}" --name "comment"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
- run: |
|
||||
PR="$(grep -o '^[0-9]\+$' pr.txt)"
|
||||
PR_HEAD_SHA="$(gh api "/repos/${GITHUB_REPOSITORY}/pulls/${PR}" --jq .head.sha)"
|
||||
# Check that the pull-request head SHA matches the head SHA of the workflow run
|
||||
if [ "${WORKFLOW_RUN_HEAD_SHA}" != "${PR_HEAD_SHA}" ]; then
|
||||
echo "PR head SHA ${PR_HEAD_SHA} does not match workflow_run event SHA ${WORKFLOW_RUN_HEAD_SHA}. Stopping." 1>&2
|
||||
exit 1
|
||||
fi
|
||||
gh pr comment "${PR}" --repo "${GITHUB_REPOSITORY}" -F comment.txt
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
WORKFLOW_RUN_HEAD_SHA: ${{ github.event.workflow_run.head_commit.id }}
|
||||
52
.github/workflows/qhelp-pr-preview.yml
vendored
52
.github/workflows/qhelp-pr-preview.yml
vendored
@@ -1,13 +1,10 @@
|
||||
name: Query help preview
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- "rc/*"
|
||||
- 'rc/*'
|
||||
paths:
|
||||
- "ruby/**/*.qhelp"
|
||||
|
||||
@@ -15,49 +12,28 @@ jobs:
|
||||
qhelp:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "${{ github.event.number }}" > pr.txt
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: comment
|
||||
path: pr.txt
|
||||
retention-days: 1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
persist-credentials: false
|
||||
- uses: ./.github/actions/fetch-codeql
|
||||
- name: Determine changed files
|
||||
id: changes
|
||||
run: |
|
||||
(git diff -z --name-only --diff-filter=ACMRT HEAD~1 HEAD | grep -z '.qhelp$' | grep -z -v '.inc.qhelp';
|
||||
git diff -z --name-only --diff-filter=ACMRT HEAD~1 HEAD | grep -z '.inc.qhelp$' | xargs --null -rn1 basename | xargs --null -rn1 git grep -z -l) |
|
||||
grep -z '.qhelp$' | grep -z -v '^-' | sort -z -u > "${RUNNER_TEMP}/paths.txt"
|
||||
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: |
|
||||
EXIT_CODE=0
|
||||
echo "QHelp previews:" > comment.txt
|
||||
while read -r -d $'\0' path; do
|
||||
if [ ! -f "${path}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
( 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}" 2> errors.txt || EXIT_CODE="$?"
|
||||
if [ -s errors.txt ]; then
|
||||
echo "# errors/warnings:"
|
||||
echo '```'
|
||||
cat errors.txt
|
||||
cat errors.txt 1>&2
|
||||
echo '```'
|
||||
fi
|
||||
codeql generate query-help --format=markdown ${path}
|
||||
echo "</details>"
|
||||
done < "${RUNNER_TEMP}/paths.txt" >> comment.txt
|
||||
exit "${EXIT_CODE}"
|
||||
|
||||
- if: always()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: comment
|
||||
path: comment.txt
|
||||
retention-days: 1
|
||||
done) | gh pr comment "${{ github.event.pull_request.number }}" -F -
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
192
.github/workflows/ql-for-ql-build.yml
vendored
192
.github/workflows/ql-for-ql-build.yml
vendored
@@ -1,192 +0,0 @@
|
||||
name: Run QL for QL
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
queries:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Find codeql
|
||||
id: find-codeql
|
||||
uses: github/codeql-action/init@erik-krogh/ql
|
||||
with:
|
||||
languages: javascript # does not matter
|
||||
- name: Get CodeQL version
|
||||
id: get-codeql-version
|
||||
run: |
|
||||
echo "::set-output name=version::$("${CODEQL}" --version | head -n 1 | rev | cut -d " " -f 1 | rev)"
|
||||
shell: bash
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
- name: Cache queries
|
||||
id: cache-queries
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ runner.temp }}/query-pack.zip
|
||||
key: queries-${{ hashFiles('ql/**/*.ql*') }}-${{ hashFiles('ql/ql/src/ql.dbscheme*') }}-${{ steps.get-codeql-version.outputs.version }}
|
||||
- name: Build query pack
|
||||
if: steps.cache-queries.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd ql/ql/src
|
||||
"${CODEQL}" pack create
|
||||
cd .codeql/pack/codeql/ql-all/0.0.0
|
||||
zip "${PACKZIP}" -r .
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
PACKZIP: ${{ runner.temp }}/query-pack.zip
|
||||
- name: Upload query pack
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: query-pack-zip
|
||||
path: ${{ runner.temp }}/query-pack.zip
|
||||
|
||||
extractors:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache entire extractor
|
||||
id: cache-extractor
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
ql/target/release/ql-autobuilder
|
||||
ql/target/release/ql-autobuilder.exe
|
||||
ql/target/release/ql-extractor
|
||||
ql/target/release/ql-extractor.exe
|
||||
key: ${{ runner.os }}-extractor-${{ hashFiles('ql/**/Cargo.lock') }}-${{ hashFiles('ql/**/*.rs') }}
|
||||
- name: Cache cargo
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
ql/target
|
||||
key: ${{ runner.os }}-rust-cargo-${{ hashFiles('ql/**/Cargo.lock') }}
|
||||
- name: Check formatting
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
run: cd ql; cargo fmt --all -- --check
|
||||
- name: Build
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
run: cd ql; cargo build --verbose
|
||||
- name: Run tests
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
run: cd ql; cargo test --verbose
|
||||
- name: Release build
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
run: cd ql; cargo build --release
|
||||
- name: Generate dbscheme
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
run: ql/target/release/ql-generator --dbscheme ql/ql/src/ql.dbscheme --library ql/ql/src/codeql_ql/ast/internal/TreeSitter.qll
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: extractor-ubuntu-latest
|
||||
path: |
|
||||
ql/target/release/ql-autobuilder
|
||||
ql/target/release/ql-autobuilder.exe
|
||||
ql/target/release/ql-extractor
|
||||
ql/target/release/ql-extractor.exe
|
||||
retention-days: 1
|
||||
package:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
needs:
|
||||
- extractors
|
||||
- queries
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: query-pack-zip
|
||||
path: query-pack-zip
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: extractor-ubuntu-latest
|
||||
path: linux64
|
||||
- run: |
|
||||
unzip query-pack-zip/*.zip -d pack
|
||||
cp -r ql/codeql-extractor.yml ql/tools ql/ql/src/ql.dbscheme.stats pack/
|
||||
mkdir -p pack/tools/linux64
|
||||
if [[ -f linux64/ql-autobuilder ]]; then
|
||||
cp linux64/ql-autobuilder pack/tools/linux64/autobuilder
|
||||
chmod +x pack/tools/linux64/autobuilder
|
||||
fi
|
||||
if [[ -f linux64/ql-extractor ]]; then
|
||||
cp linux64/ql-extractor pack/tools/linux64/extractor
|
||||
chmod +x pack/tools/linux64/extractor
|
||||
fi
|
||||
cd pack
|
||||
zip -rq ../codeql-ql.zip .
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: codeql-ql-pack
|
||||
path: codeql-ql.zip
|
||||
retention-days: 1
|
||||
analyze:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
folder: [cpp, csharp, java, javascript, python, ql, ruby]
|
||||
|
||||
needs:
|
||||
- package
|
||||
|
||||
steps:
|
||||
- name: Download pack
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: codeql-ql-pack
|
||||
path: ${{ runner.temp }}/codeql-ql-pack-artifact
|
||||
|
||||
- name: Prepare pack
|
||||
run: |
|
||||
unzip "${PACK_ARTIFACT}/*.zip" -d "${PACK}"
|
||||
env:
|
||||
PACK_ARTIFACT: ${{ runner.temp }}/codeql-ql-pack-artifact
|
||||
PACK: ${{ runner.temp }}/pack
|
||||
- name: Hack codeql-action options
|
||||
run: |
|
||||
JSON=$(jq -nc --arg pack "${PACK}" '.resolve.queries=["--search-path", $pack] | .resolve.extractor=["--search-path", $pack] | .database.init=["--search-path", $pack]')
|
||||
echo "CODEQL_ACTION_EXTRA_OPTIONS=${JSON}" >> ${GITHUB_ENV}
|
||||
env:
|
||||
PACK: ${{ runner.temp }}/pack
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Create CodeQL config file
|
||||
run: |
|
||||
echo "paths:" > ${CONF}
|
||||
echo " - ${FOLDER}" >> ${CONF}
|
||||
echo "paths-ignore:" >> ${CONF}
|
||||
echo " - ql/ql/test" >> ${CONF}
|
||||
echo "Config file: "
|
||||
cat ${CONF}
|
||||
env:
|
||||
CONF: ./ql-for-ql-config.yml
|
||||
FOLDER: ${{ matrix.folder }}
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@erik-krogh/ql
|
||||
with:
|
||||
languages: ql
|
||||
db-location: ${{ runner.temp }}/db
|
||||
config-file: ./ql-for-ql-config.yml
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@erik-krogh/ql
|
||||
with:
|
||||
category: "ql-for-ql-${{ matrix.folder }}"
|
||||
|
||||
84
.github/workflows/ql-for-ql-dataset_measure.yml
vendored
84
.github/workflows/ql-for-ql-dataset_measure.yml
vendored
@@ -1,84 +0,0 @@
|
||||
name: Collect database stats for QL for QL
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- ql/ql/src/ql.dbscheme
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- ql/ql/src/ql.dbscheme
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
measure:
|
||||
env:
|
||||
CODEQL_THREADS: 4 # TODO: remove this once it's set by the CLI
|
||||
strategy:
|
||||
matrix:
|
||||
repo:
|
||||
- github/codeql
|
||||
- github/codeql-go
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Find codeql
|
||||
id: find-codeql
|
||||
uses: github/codeql-action/init@erik-krogh/ql
|
||||
with:
|
||||
languages: javascript # does not matter
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
ql/target
|
||||
key: ${{ runner.os }}-qltest-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Build Extractor
|
||||
run: cd ql; env "PATH=$PATH:`dirname ${CODEQL}`" ./create-extractor-pack.sh
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
- name: Checkout ${{ matrix.repo }}
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ${{ matrix.repo }}
|
||||
path: ${{ github.workspace }}/repo
|
||||
- name: Create database
|
||||
run: |
|
||||
"${CODEQL}" database create \
|
||||
--search-path "ql/extractor-pack" \
|
||||
--threads 4 \
|
||||
--language ql --source-root "${{ github.workspace }}/repo" \
|
||||
"${{ runner.temp }}/database"
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
- name: Measure database
|
||||
run: |
|
||||
mkdir -p "stats/${{ matrix.repo }}"
|
||||
"${CODEQL}" dataset measure --threads 4 --output "stats/${{ matrix.repo }}/stats.xml" "${{ runner.temp }}/database/db-ql"
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: measurements
|
||||
path: stats
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs: measure
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: measurements
|
||||
path: stats
|
||||
- run: |
|
||||
python -m pip install --user lxml
|
||||
find stats -name 'stats.xml' -print0 | sort -z | xargs -0 python ql/scripts/merge_stats.py --output ql/ql/src/ql.dbscheme.stats --normalise ql_tokeninfo
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ql.dbscheme.stats
|
||||
path: ql/ql/src/ql.dbscheme.stats
|
||||
52
.github/workflows/ql-for-ql-tests.yml
vendored
52
.github/workflows/ql-for-ql-tests.yml
vendored
@@ -1,52 +0,0 @@
|
||||
name: Run QL for QL Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "ql/**"
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "ql/**"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
qltest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Find codeql
|
||||
id: find-codeql
|
||||
uses: github/codeql-action/init@erik-krogh/ql
|
||||
with:
|
||||
languages: javascript # does not matter
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
ql/target
|
||||
key: ${{ runner.os }}-qltest-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Build extractor
|
||||
run: |
|
||||
cd ql;
|
||||
codeqlpath=$(dirname ${{ steps.find-codeql.outputs.codeql-path }});
|
||||
env "PATH=$PATH:$codeqlpath" ./create-extractor-pack.sh
|
||||
- name: Run QL tests
|
||||
run: |
|
||||
"${CODEQL}" test run --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --search-path "${{ github.workspace }}/ql/extractor-pack" --consistency-queries ql/ql/consistency-queries ql/ql/test
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
- name: Check QL formatting
|
||||
run: |
|
||||
find ql/ql "(" -name "*.ql" -or -name "*.qll" ")" -print0 | xargs -0 "${CODEQL}" query format --check-only
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
- name: Check QL compilation
|
||||
run: |
|
||||
"${CODEQL}" query compile --check-only --threads=4 --warnings=error --search-path "${{ github.workspace }}/ql/extractor-pack" "ql/ql/src" "ql/ql/examples"
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
20
.github/workflows/ruby-build.yml
vendored
20
.github/workflows/ruby-build.yml
vendored
@@ -3,18 +3,16 @@ name: "Ruby: Build"
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "ruby/**"
|
||||
- .github/workflows/ruby-build.yml
|
||||
- 'ruby/**'
|
||||
branches:
|
||||
- main
|
||||
- "rc/*"
|
||||
- 'rc/*'
|
||||
pull_request:
|
||||
paths:
|
||||
- "ruby/**"
|
||||
- .github/workflows/ruby-build.yml
|
||||
- 'ruby/**'
|
||||
branches:
|
||||
- main
|
||||
- "rc/*"
|
||||
- 'rc/*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
@@ -102,6 +100,16 @@ jobs:
|
||||
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
|
||||
|
||||
10
.github/workflows/ruby-dataset-measure.yml
vendored
10
.github/workflows/ruby-dataset-measure.yml
vendored
@@ -4,17 +4,15 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- "rc/*"
|
||||
- 'rc/*'
|
||||
paths:
|
||||
- ruby/ql/lib/ruby.dbscheme
|
||||
- .github/workflows/ruby-dataset-measure.yml
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- "rc/*"
|
||||
- 'rc/*'
|
||||
paths:
|
||||
- ruby/ql/lib/ruby.dbscheme
|
||||
- .github/workflows/ruby-dataset-measure.yml
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -24,7 +22,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
repo: [rails/rails, discourse/discourse, spree/spree, ruby/ruby]
|
||||
repo: [rails/rails, discourse/discourse, spree/spree]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -41,7 +39,7 @@ jobs:
|
||||
- name: Create database
|
||||
run: |
|
||||
codeql database create \
|
||||
--search-path "${{ github.workspace }}/ruby/extractor-pack" \
|
||||
--search-path "${{ github.workspace }}/ruby" \
|
||||
--threads 4 \
|
||||
--language ruby --source-root "${{ github.workspace }}/repo" \
|
||||
"${{ runner.temp }}/database"
|
||||
|
||||
16
.github/workflows/ruby-qltest.yml
vendored
16
.github/workflows/ruby-qltest.yml
vendored
@@ -3,18 +3,16 @@ name: "Ruby: Run QL Tests"
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "ruby/**"
|
||||
- .github/workflows/ruby-qltest.yml
|
||||
- 'ruby/**'
|
||||
branches:
|
||||
- main
|
||||
- "rc/*"
|
||||
- 'rc/*'
|
||||
pull_request:
|
||||
paths:
|
||||
- "ruby/**"
|
||||
- .github/workflows/ruby-qltest.yml
|
||||
- 'ruby/**'
|
||||
branches:
|
||||
- main
|
||||
- "rc/*"
|
||||
- 'rc/*'
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -32,19 +30,19 @@ jobs:
|
||||
- uses: ./ruby/actions/create-extractor-pack
|
||||
- name: Run QL tests
|
||||
run: |
|
||||
codeql test run --search-path "${{ github.workspace }}/ruby/extractor-pack" --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --consistency-queries ql/consistency-queries ql/test
|
||||
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 "ql/src" "ql/examples"
|
||||
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
|
||||
codeql dataset upgrade testdb --additional-packs ql/lib/upgrades
|
||||
diff -q testdb/ruby.dbscheme ql/lib/ruby.dbscheme
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -27,6 +27,3 @@ csharp/extractor/Semmle.Extraction.CSharp.Driver/Properties/launchSettings.json
|
||||
|
||||
# Avoid committing cached package components
|
||||
.codeql
|
||||
|
||||
# Compiled class file
|
||||
*.class
|
||||
@@ -25,6 +25,3 @@
|
||||
/docs/codeql-for-visual-studio-code/ @github/codeql-vscode-reviewers
|
||||
/docs/ql-language-reference/ @github/codeql-frontend-reviewers
|
||||
/docs/query-*-style-guide.md @github/codeql-analysis-reviewers
|
||||
|
||||
# QL for QL reviewers
|
||||
/ql/ @erik-krogh @tausbn
|
||||
@@ -1,11 +1,11 @@
|
||||
# CodeQL
|
||||
|
||||
This open source repository contains the standard CodeQL libraries and queries that power [GitHub Advanced Security](https://github.com/features/security/code) and the other application security products that [GitHub](https://github.com/features/security/) makes available to its customers worldwide. For the queries, libraries, and extractor that power Go analysis, visit the [CodeQL for Go repository](https://github.com/github/codeql-go).
|
||||
This open source repository contains the standard CodeQL libraries and queries that power [LGTM](https://lgtm.com) and the other CodeQL products that [GitHub](https://github.com) makes available to its customers worldwide. For the queries, libraries, and extractor that power Go analysis, visit the [CodeQL for Go repository](https://github.com/github/codeql-go).
|
||||
|
||||
## How do I learn CodeQL and run queries?
|
||||
|
||||
There is [extensive documentation](https://codeql.github.com/docs/) on getting started with writing CodeQL.
|
||||
You can use the [CodeQL for Visual Studio Code](https://codeql.github.com/docs/codeql-for-visual-studio-code/) extension or the [interactive query console](https://lgtm.com/help/lgtm/using-query-console) on LGTM.com (Semmle Legacy product) to try out your queries on any open source project that's currently being analyzed.
|
||||
You can use the [interactive query console](https://lgtm.com/help/lgtm/using-query-console) on LGTM.com or the [CodeQL for Visual Studio Code](https://codeql.github.com/docs/codeql-for-visual-studio-code/) extension to try out your queries on any open source project that's currently being analyzed.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -13,7 +13,7 @@ We welcome contributions to our standard library and standard checks. Do you hav
|
||||
|
||||
## License
|
||||
|
||||
The code in this repository is licensed under the [MIT License](LICENSE) by [GitHub](https://github.com). The use of CodeQL on open source code is licensed under specific [Terms & Conditions](https://securitylab.github.com/tools/codeql/license/) UNLESS you have a commercial license in place. If you'd like to use CodeQL with a commercial codebase, please [contact us](https://github.com/enterprise/contact) for further help.
|
||||
The code in this repository is licensed under the [MIT License](LICENSE) by [GitHub](https://github.com).
|
||||
|
||||
## Visual Studio Code integration
|
||||
|
||||
|
||||
@@ -449,27 +449,19 @@
|
||||
"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",
|
||||
"ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll"
|
||||
"ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll"
|
||||
],
|
||||
"CryptoAlgorithms Python/JS/Ruby": [
|
||||
"CryptoAlgorithms Python/JS": [
|
||||
"javascript/ql/lib/semmle/javascript/security/CryptoAlgorithms.qll",
|
||||
"python/ql/lib/semmle/python/concepts/CryptoAlgorithms.qll",
|
||||
"ruby/ql/lib/codeql/ruby/security/CryptoAlgorithms.qll"
|
||||
],
|
||||
"CryptoAlgorithmNames Python/JS/Ruby": [
|
||||
"javascript/ql/lib/semmle/javascript/security/internal/CryptoAlgorithmNames.qll",
|
||||
"python/ql/lib/semmle/python/concepts/internal/CryptoAlgorithmNames.qll",
|
||||
"ruby/ql/lib/codeql/ruby/security/internal/CryptoAlgorithmNames.qll"
|
||||
"python/ql/lib/semmle/python/concepts/CryptoAlgorithms.qll"
|
||||
],
|
||||
"SensitiveDataHeuristics Python/JS": [
|
||||
"javascript/ql/lib/semmle/javascript/security/internal/SensitiveDataHeuristics.qll",
|
||||
"python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll"
|
||||
],
|
||||
"ReDoS Util Python/JS/Ruby": [
|
||||
"ReDoS Util Python/JS": [
|
||||
"javascript/ql/lib/semmle/javascript/security/performance/ReDoSUtil.qll",
|
||||
"python/ql/lib/semmle/python/security/performance/ReDoSUtil.qll",
|
||||
"ruby/ql/lib/codeql/ruby/security/performance/ReDoSUtil.qll"
|
||||
"python/ql/lib/semmle/python/security/performance/ReDoSUtil.qll"
|
||||
],
|
||||
"ReDoS Exponential Python/JS": [
|
||||
"javascript/ql/lib/semmle/javascript/security/performance/ExponentialBackTracking.qll",
|
||||
@@ -478,12 +470,7 @@
|
||||
"ReDoS Polynomial Python/JS": [
|
||||
"javascript/ql/lib/semmle/javascript/security/performance/SuperlinearBackTracking.qll",
|
||||
"python/ql/lib/semmle/python/security/performance/SuperlinearBackTracking.qll",
|
||||
"ruby/ql/lib/codeql/ruby/security/performance/SuperlinearBackTracking.qll"
|
||||
],
|
||||
"BadTagFilterQuery Python/JS/Ruby": [
|
||||
"javascript/ql/lib/semmle/javascript/security/BadTagFilterQuery.qll",
|
||||
"python/ql/lib/semmle/python/security/BadTagFilterQuery.qll",
|
||||
"ruby/ql/lib/codeql/ruby/security/BadTagFilterQuery.qll"
|
||||
"ruby/ql/lib/codeql/ruby/regexp/SuperlinearBackTracking.qll"
|
||||
],
|
||||
"CFG": [
|
||||
"csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImplShared.qll",
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build" Version="16.11.0" />
|
||||
<PackageReference Include="Microsoft.Build" Version="16.9.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
## 0.0.4
|
||||
|
||||
### New Features
|
||||
|
||||
lgtm,codescanning
|
||||
* The QL library `semmle.code.cpp.commons.Exclusions` now contains a predicate
|
||||
`isFromSystemMacroDefinition` for identifying code that originates from a
|
||||
macro outside the project being analyzed.
|
||||
@@ -1,2 +0,0 @@
|
||||
lgtm,codescanning
|
||||
* The "Cleartext transmission of sensitive information" (`cpp/cleartext-transmission`) query has been improved, reducing the number of false positive results when encryption is present.
|
||||
@@ -1,9 +0,0 @@
|
||||
## 0.0.5
|
||||
|
||||
## 0.0.4
|
||||
|
||||
### New Features
|
||||
|
||||
* The QL library `semmle.code.cpp.commons.Exclusions` now contains a predicate
|
||||
`isFromSystemMacroDefinition` for identifying code that originates from a
|
||||
macro outside the project being analyzed.
|
||||
@@ -73,7 +73,7 @@ class Options extends string {
|
||||
* __assume(0);
|
||||
* ```
|
||||
* (note that in this case if the hint is wrong and the expression is reached at
|
||||
* runtime, the program's behavior is undefined)
|
||||
* runtime, the program's behaviour is undefined)
|
||||
*/
|
||||
predicate exprExits(Expr e) {
|
||||
e.(AssumeExpr).getChild(0).(CompileTimeConstantInt).getIntValue() = 0 or
|
||||
|
||||
@@ -50,7 +50,7 @@ class CustomOptions extends Options {
|
||||
* __assume(0);
|
||||
* ```
|
||||
* (note that in this case if the hint is wrong and the expression is reached at
|
||||
* runtime, the program's behavior is undefined)
|
||||
* runtime, the program's behaviour is undefined)
|
||||
*/
|
||||
override predicate exprExits(Expr e) { Options.super.exprExits(e) }
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
## 0.0.5
|
||||
@@ -1,2 +0,0 @@
|
||||
---
|
||||
lastReleaseVersion: 0.0.5
|
||||
@@ -37,7 +37,7 @@ abstract class SimpleRangeAnalysisDefinition extends RangeSsaDefinition {
|
||||
* dependencies. Without this information, range analysis might work for
|
||||
* simple cases but will go into infinite loops on complex code.
|
||||
*
|
||||
* For example, when modeling the definition by reference in a call to an
|
||||
* For example, when modelling the definition by reference in a call to an
|
||||
* overloaded `operator=`, written as `v = e`, the definition of `(this, v)`
|
||||
* depends on `e`.
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* `Instruction` level), and then using the array length analysis and the range
|
||||
* analysis together to prove that some of these pointer dereferences are safe.
|
||||
*
|
||||
* The analysis is soundy, i.e. it is sound if no undefined behavior is present
|
||||
* The analysis is soundy, i.e. it is sound if no undefined behaviour is present
|
||||
* in the program.
|
||||
* Furthermore, it crucially depends on the soundiness of the range analysis and
|
||||
* the array length analysis.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: codeql/cpp-all
|
||||
version: 0.0.6-dev
|
||||
groups: cpp
|
||||
version: 0.0.2
|
||||
dbscheme: semmlecode.cpp.dbscheme
|
||||
extractor: cpp
|
||||
library: true
|
||||
upgrades: upgrades
|
||||
dependencies:
|
||||
codeql/cpp-upgrades: 0.0.2
|
||||
|
||||
@@ -3,14 +3,11 @@ private import semmle.code.cpp.models.interfaces.ArrayFunction
|
||||
private import semmle.code.cpp.models.implementations.Strcat
|
||||
import semmle.code.cpp.dataflow.DataFlow
|
||||
|
||||
/**
|
||||
* Holds if the expression `e` assigns something including `va` to a
|
||||
* stack variable `v0`.
|
||||
*/
|
||||
private predicate mayAddNullTerminatorHelper(Expr e, VariableAccess va, StackVariable v0) {
|
||||
exists(Expr val |
|
||||
exprDefinition(v0, e, val) and // `e` is `v0 := val`
|
||||
val.getAChild*() = va
|
||||
private predicate mayAddNullTerminatorHelper(Expr e, VariableAccess va, Expr e0) {
|
||||
exists(StackVariable v0, Expr val |
|
||||
exprDefinition(v0, e, val) and
|
||||
val.getAChild*() = va and
|
||||
mayAddNullTerminator(e0, v0.getAnAccess())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -28,8 +25,8 @@ private predicate controlFlowNodeSuccessorTransitive(ControlFlowNode n1, Control
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the expression `e` may add a null terminator to the string
|
||||
* accessed by `va`.
|
||||
* Holds if the expression `e` may add a null terminator to the string in
|
||||
* variable `v`.
|
||||
*/
|
||||
predicate mayAddNullTerminator(Expr e, VariableAccess va) {
|
||||
// Assignment: dereferencing or array access
|
||||
@@ -46,9 +43,8 @@ predicate mayAddNullTerminator(Expr e, VariableAccess va) {
|
||||
)
|
||||
or
|
||||
// Assignment to another stack variable
|
||||
exists(StackVariable v0, Expr e0 |
|
||||
mayAddNullTerminatorHelper(e, va, v0) and
|
||||
mayAddNullTerminator(pragma[only_bind_into](e0), pragma[only_bind_into](v0.getAnAccess())) and
|
||||
exists(Expr e0 |
|
||||
mayAddNullTerminatorHelper(pragma[only_bind_into](e), va, pragma[only_bind_into](e0)) and
|
||||
controlFlowNodeSuccessorTransitive(e, e0)
|
||||
)
|
||||
or
|
||||
|
||||
@@ -6,85 +6,6 @@ import semmle.code.cpp.Type
|
||||
import semmle.code.cpp.commons.CommonType
|
||||
import semmle.code.cpp.commons.StringAnalysis
|
||||
import semmle.code.cpp.models.interfaces.FormattingFunction
|
||||
private import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
|
||||
private import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
|
||||
|
||||
private newtype TBufferWriteEstimationReason =
|
||||
TNoSpecifiedEstimateReason() or
|
||||
TTypeBoundsAnalysis() or
|
||||
TValueFlowAnalysis()
|
||||
|
||||
/**
|
||||
* A reason for a specific buffer write size estimate.
|
||||
*/
|
||||
abstract class BufferWriteEstimationReason extends TBufferWriteEstimationReason {
|
||||
/**
|
||||
* Returns the name of the concrete class.
|
||||
*/
|
||||
abstract string toString();
|
||||
|
||||
/**
|
||||
* Returns a human readable representation of this reason.
|
||||
*/
|
||||
abstract string getDescription();
|
||||
|
||||
/**
|
||||
* Combine estimate reasons. Used to give a reason for the size of a format string
|
||||
* conversion given reasons coming from its individual specifiers.
|
||||
*/
|
||||
abstract BufferWriteEstimationReason combineWith(BufferWriteEstimationReason other);
|
||||
}
|
||||
|
||||
/**
|
||||
* No particular reason given. This is currently used for backward compatibility so that
|
||||
* classes derived from BufferWrite and overriding `getMaxData/0` still work with the
|
||||
* queries as intended.
|
||||
*/
|
||||
class NoSpecifiedEstimateReason extends BufferWriteEstimationReason, TNoSpecifiedEstimateReason {
|
||||
override string toString() { result = "NoSpecifiedEstimateReason" }
|
||||
|
||||
override string getDescription() { result = "no reason specified" }
|
||||
|
||||
override BufferWriteEstimationReason combineWith(BufferWriteEstimationReason other) {
|
||||
// this reason should not be used in format specifiers, so it should not be combined
|
||||
// with other reasons
|
||||
none()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The estimation comes from rough bounds just based on the type (e.g.
|
||||
* `0 <= x < 2^32` for an unsigned 32 bit integer).
|
||||
*/
|
||||
class TypeBoundsAnalysis extends BufferWriteEstimationReason, TTypeBoundsAnalysis {
|
||||
override string toString() { result = "TypeBoundsAnalysis" }
|
||||
|
||||
override string getDescription() { result = "based on type bounds" }
|
||||
|
||||
override BufferWriteEstimationReason combineWith(BufferWriteEstimationReason other) {
|
||||
other != TNoSpecifiedEstimateReason() and result = TTypeBoundsAnalysis()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The estimation comes from non trivial bounds found via actual flow analysis.
|
||||
* For example
|
||||
* ```
|
||||
* unsigned u = x;
|
||||
* if (u < 1000) {
|
||||
* //... <- estimation done here based on u
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class ValueFlowAnalysis extends BufferWriteEstimationReason, TValueFlowAnalysis {
|
||||
override string toString() { result = "ValueFlowAnalysis" }
|
||||
|
||||
override string getDescription() { result = "based on flow analysis of value bounds" }
|
||||
|
||||
override BufferWriteEstimationReason combineWith(BufferWriteEstimationReason other) {
|
||||
other != TNoSpecifiedEstimateReason() and result = other
|
||||
}
|
||||
}
|
||||
|
||||
class PrintfFormatAttribute extends FormatAttribute {
|
||||
PrintfFormatAttribute() { this.getArchetype() = ["printf", "__printf__"] }
|
||||
@@ -347,18 +268,6 @@ class FormattingFunctionCall extends Expr {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of digits required to represent the integer represented by `f`.
|
||||
*
|
||||
* `f` is assumed to be nonnegative.
|
||||
*/
|
||||
bindingset[f]
|
||||
private int lengthInBase10(float f) {
|
||||
f = 0 and result = 1
|
||||
or
|
||||
result = f.log10().floor() + 1
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to represent format strings that occur as arguments to invocations of formatting functions.
|
||||
*/
|
||||
@@ -1067,14 +976,7 @@ class FormatLiteral extends Literal {
|
||||
* conversion specifier of this format string; has no result if this cannot
|
||||
* be determined.
|
||||
*/
|
||||
int getMaxConvertedLength(int n) { result = max(getMaxConvertedLength(n, _)) }
|
||||
|
||||
/**
|
||||
* Gets the maximum length of the string that can be produced by the nth
|
||||
* conversion specifier of this format string, specifying the estimation reason;
|
||||
* has no result if this cannot be determined.
|
||||
*/
|
||||
int getMaxConvertedLength(int n, BufferWriteEstimationReason reason) {
|
||||
int getMaxConvertedLength(int n) {
|
||||
exists(int len |
|
||||
(
|
||||
(
|
||||
@@ -1086,12 +988,10 @@ class FormatLiteral extends Literal {
|
||||
) and
|
||||
(
|
||||
this.getConversionChar(n) = "%" and
|
||||
len = 1 and
|
||||
reason = TValueFlowAnalysis()
|
||||
len = 1
|
||||
or
|
||||
this.getConversionChar(n).toLowerCase() = "c" and
|
||||
len = 1 and
|
||||
reason = TValueFlowAnalysis() // e.g. 'a'
|
||||
len = 1 // e.g. 'a'
|
||||
or
|
||||
this.getConversionChar(n).toLowerCase() = "f" and
|
||||
exists(int dot, int afterdot |
|
||||
@@ -1105,8 +1005,7 @@ class FormatLiteral extends Literal {
|
||||
afterdot = 6
|
||||
) and
|
||||
len = 1 + 309 + dot + afterdot
|
||||
) and
|
||||
reason = TTypeBoundsAnalysis() // e.g. -1e308="-100000"...
|
||||
) // e.g. -1e308="-100000"...
|
||||
or
|
||||
this.getConversionChar(n).toLowerCase() = "e" and
|
||||
exists(int dot, int afterdot |
|
||||
@@ -1120,8 +1019,7 @@ class FormatLiteral extends Literal {
|
||||
afterdot = 6
|
||||
) and
|
||||
len = 1 + 1 + dot + afterdot + 1 + 1 + 3
|
||||
) and
|
||||
reason = TTypeBoundsAnalysis() // -1e308="-1.000000e+308"
|
||||
) // -1e308="-1.000000e+308"
|
||||
or
|
||||
this.getConversionChar(n).toLowerCase() = "g" and
|
||||
exists(int dot, int afterdot |
|
||||
@@ -1144,79 +1042,42 @@ class FormatLiteral extends Literal {
|
||||
// (e.g. 123456, 0.000123456 are just OK)
|
||||
// so case %f can be at most P characters + 4 zeroes, sign, dot = P + 6
|
||||
len = (afterdot.maximum(1) + 6).maximum(1 + 1 + dot + afterdot + 1 + 1 + 3)
|
||||
) and
|
||||
reason = TTypeBoundsAnalysis() // (e.g. "-1.59203e-319")
|
||||
) // (e.g. "-1.59203e-319")
|
||||
or
|
||||
this.getConversionChar(n).toLowerCase() = ["d", "i"] and
|
||||
// e.g. -2^31 = "-2147483648"
|
||||
exists(float typeBasedBound, float valueBasedBound |
|
||||
// The first case handles length sub-specifiers
|
||||
// Subtract one in the exponent because one bit is for the sign.
|
||||
// Add 1 to account for the possible sign in the output.
|
||||
typeBasedBound =
|
||||
1 + lengthInBase10(2.pow(this.getIntegralDisplayType(n).getSize() * 8 - 1)) and
|
||||
// The second case uses range analysis to deduce a length that's shorter than the length
|
||||
// of the number -2^31.
|
||||
exists(Expr arg, float lower, float upper, float typeLower, float typeUpper |
|
||||
arg = this.getUse().getConversionArgument(n) and
|
||||
lower = lowerBound(arg.getFullyConverted()) and
|
||||
upper = upperBound(arg.getFullyConverted()) and
|
||||
typeLower = exprMinVal(arg.getFullyConverted()) and
|
||||
typeUpper = exprMaxVal(arg.getFullyConverted())
|
||||
|
|
||||
valueBasedBound =
|
||||
max(int cand |
|
||||
// Include the sign bit in the length if it can be negative
|
||||
(
|
||||
if lower < 0
|
||||
then cand = 1 + lengthInBase10(lower.abs())
|
||||
else cand = lengthInBase10(lower)
|
||||
)
|
||||
or
|
||||
(
|
||||
if upper < 0
|
||||
then cand = 1 + lengthInBase10(upper.abs())
|
||||
else cand = lengthInBase10(upper)
|
||||
)
|
||||
) and
|
||||
(
|
||||
if lower > typeLower or upper < typeUpper
|
||||
then reason = TValueFlowAnalysis()
|
||||
else reason = TTypeBoundsAnalysis()
|
||||
)
|
||||
) and
|
||||
len = valueBasedBound.minimum(typeBasedBound)
|
||||
exists(int sizeBits |
|
||||
sizeBits =
|
||||
min(int bits |
|
||||
bits = this.getIntegralDisplayType(n).getSize() * 8
|
||||
or
|
||||
exists(IntegralType t |
|
||||
t = this.getUse().getConversionArgument(n).getType().getUnderlyingType()
|
||||
|
|
||||
t.isSigned() and bits = t.getSize() * 8
|
||||
)
|
||||
) and
|
||||
len = 1 + ((sizeBits - 1) / 10.0.log2()).ceil()
|
||||
// this calculation is as %u (below) only we take out the sign bit (- 1) and allow a whole
|
||||
// character for it to be expressed as '-'.
|
||||
)
|
||||
or
|
||||
this.getConversionChar(n).toLowerCase() = "u" and
|
||||
// e.g. 2^32 - 1 = "4294967295"
|
||||
exists(float typeBasedBound, float valueBasedBound |
|
||||
// The first case handles length sub-specifiers
|
||||
typeBasedBound = lengthInBase10(2.pow(this.getIntegralDisplayType(n).getSize() * 8) - 1) and
|
||||
// The second case uses range analysis to deduce a length that's shorter than
|
||||
// the length of the number 2^31 - 1.
|
||||
exists(Expr arg, float lower, float upper, float typeLower, float typeUpper |
|
||||
arg = this.getUse().getConversionArgument(n) and
|
||||
lower = lowerBound(arg.getFullyConverted()) and
|
||||
upper = upperBound(arg.getFullyConverted()) and
|
||||
typeLower = exprMinVal(arg.getFullyConverted()) and
|
||||
typeUpper = exprMaxVal(arg.getFullyConverted())
|
||||
|
|
||||
valueBasedBound =
|
||||
lengthInBase10(max(float cand |
|
||||
// If lower can be negative we use `(unsigned)-1` as the candidate value.
|
||||
lower < 0 and
|
||||
cand = 2.pow(any(IntType t | t.isUnsigned()).getSize() * 8)
|
||||
or
|
||||
cand = upper
|
||||
)) and
|
||||
(
|
||||
if lower > typeLower or upper < typeUpper
|
||||
then reason = TValueFlowAnalysis()
|
||||
else reason = TTypeBoundsAnalysis()
|
||||
)
|
||||
) and
|
||||
len = valueBasedBound.minimum(typeBasedBound)
|
||||
exists(int sizeBits |
|
||||
sizeBits =
|
||||
min(int bits |
|
||||
bits = this.getIntegralDisplayType(n).getSize() * 8
|
||||
or
|
||||
exists(IntegralType t |
|
||||
t = this.getUse().getConversionArgument(n).getType().getUnderlyingType()
|
||||
|
|
||||
t.isUnsigned() and bits = t.getSize() * 8
|
||||
)
|
||||
) and
|
||||
len = (sizeBits / 10.0.log2()).ceil()
|
||||
// convert the size from bits to decimal characters, and round up as you can't have
|
||||
// fractional characters (10.0.log2() is the number of bits expressed per decimal character)
|
||||
)
|
||||
or
|
||||
this.getConversionChar(n).toLowerCase() = "x" and
|
||||
@@ -1236,8 +1097,7 @@ class FormatLiteral extends Literal {
|
||||
(
|
||||
if this.hasAlternateFlag(n) then len = 2 + baseLen else len = baseLen // "0x"
|
||||
)
|
||||
) and
|
||||
reason = TTypeBoundsAnalysis()
|
||||
)
|
||||
or
|
||||
this.getConversionChar(n).toLowerCase() = "p" and
|
||||
exists(PointerType ptrType, int baseLen |
|
||||
@@ -1246,8 +1106,7 @@ class FormatLiteral extends Literal {
|
||||
(
|
||||
if this.hasAlternateFlag(n) then len = 2 + baseLen else len = baseLen // "0x"
|
||||
)
|
||||
) and
|
||||
reason = TValueFlowAnalysis()
|
||||
)
|
||||
or
|
||||
this.getConversionChar(n).toLowerCase() = "o" and
|
||||
// e.g. 2^32 - 1 = "37777777777"
|
||||
@@ -1266,16 +1125,14 @@ class FormatLiteral extends Literal {
|
||||
(
|
||||
if this.hasAlternateFlag(n) then len = 1 + baseLen else len = baseLen // "0"
|
||||
)
|
||||
) and
|
||||
reason = TTypeBoundsAnalysis()
|
||||
)
|
||||
or
|
||||
this.getConversionChar(n).toLowerCase() = "s" and
|
||||
len =
|
||||
min(int v |
|
||||
v = this.getPrecision(n) or
|
||||
v = this.getUse().getFormatArgument(n).(AnalysedString).getMaxLength() - 1 // (don't count null terminator)
|
||||
) and
|
||||
reason = TValueFlowAnalysis()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -1287,19 +1144,10 @@ class FormatLiteral extends Literal {
|
||||
* determining whether a buffer overflow is caused by long float to string
|
||||
* conversions.
|
||||
*/
|
||||
int getMaxConvertedLengthLimited(int n) { result = max(getMaxConvertedLengthLimited(n, _)) }
|
||||
|
||||
/**
|
||||
* Gets the maximum length of the string that can be produced by the nth
|
||||
* conversion specifier of this format string, specifying the reason for the
|
||||
* estimation, except that float to string conversions are assumed to be 8
|
||||
* characters. This is helpful for determining whether a buffer overflow is
|
||||
* caused by long float to string conversions.
|
||||
*/
|
||||
int getMaxConvertedLengthLimited(int n, BufferWriteEstimationReason reason) {
|
||||
int getMaxConvertedLengthLimited(int n) {
|
||||
if this.getConversionChar(n).toLowerCase() = "f"
|
||||
then result = this.getMaxConvertedLength(n, reason).minimum(8)
|
||||
else result = this.getMaxConvertedLength(n, reason)
|
||||
then result = this.getMaxConvertedLength(n).minimum(8)
|
||||
else result = this.getMaxConvertedLength(n)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1339,35 +1187,29 @@ class FormatLiteral extends Literal {
|
||||
)
|
||||
}
|
||||
|
||||
private int getMaxConvertedLengthAfter(int n, BufferWriteEstimationReason reason) {
|
||||
private int getMaxConvertedLengthAfter(int n) {
|
||||
if n = this.getNumConvSpec()
|
||||
then result = this.getConstantSuffix().length() + 1 and reason = TValueFlowAnalysis()
|
||||
then result = this.getConstantSuffix().length() + 1
|
||||
else
|
||||
exists(BufferWriteEstimationReason headReason, BufferWriteEstimationReason tailReason |
|
||||
result =
|
||||
this.getConstantPart(n).length() + this.getMaxConvertedLength(n, headReason) +
|
||||
this.getMaxConvertedLengthAfter(n + 1, tailReason) and
|
||||
reason = headReason.combineWith(tailReason)
|
||||
)
|
||||
result =
|
||||
this.getConstantPart(n).length() + this.getMaxConvertedLength(n) +
|
||||
this.getMaxConvertedLengthAfter(n + 1)
|
||||
}
|
||||
|
||||
private int getMaxConvertedLengthAfterLimited(int n, BufferWriteEstimationReason reason) {
|
||||
private int getMaxConvertedLengthAfterLimited(int n) {
|
||||
if n = this.getNumConvSpec()
|
||||
then result = this.getConstantSuffix().length() + 1 and reason = TValueFlowAnalysis()
|
||||
then result = this.getConstantSuffix().length() + 1
|
||||
else
|
||||
exists(BufferWriteEstimationReason headReason, BufferWriteEstimationReason tailReason |
|
||||
result =
|
||||
this.getConstantPart(n).length() + this.getMaxConvertedLengthLimited(n, headReason) +
|
||||
this.getMaxConvertedLengthAfterLimited(n + 1, tailReason) and
|
||||
reason = headReason.combineWith(tailReason)
|
||||
)
|
||||
result =
|
||||
this.getConstantPart(n).length() + this.getMaxConvertedLengthLimited(n) +
|
||||
this.getMaxConvertedLengthAfterLimited(n + 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum length of the string that can be produced by this format
|
||||
* string. Has no result if this cannot be determined.
|
||||
*/
|
||||
int getMaxConvertedLength() { result = this.getMaxConvertedLengthAfter(0, _) }
|
||||
int getMaxConvertedLength() { result = this.getMaxConvertedLengthAfter(0) }
|
||||
|
||||
/**
|
||||
* Gets the maximum length of the string that can be produced by this format
|
||||
@@ -1375,24 +1217,5 @@ class FormatLiteral extends Literal {
|
||||
* characters. This is helpful for determining whether a buffer overflow
|
||||
* is caused by long float to string conversions.
|
||||
*/
|
||||
int getMaxConvertedLengthLimited() { result = this.getMaxConvertedLengthAfterLimited(0, _) }
|
||||
|
||||
/**
|
||||
* Gets the maximum length of the string that can be produced by this format
|
||||
* string, specifying the reason for the estimate. Has no result if no estimate
|
||||
* can be found.
|
||||
*/
|
||||
int getMaxConvertedLengthWithReason(BufferWriteEstimationReason reason) {
|
||||
result = this.getMaxConvertedLengthAfter(0, reason)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum length of the string that can be produced by this format
|
||||
* string, specifying the reason for the estimate, except that float to string
|
||||
* conversions are assumed to be 8 characters. This is helpful for determining
|
||||
* whether a buffer overflow is caused by long float to string conversions.
|
||||
*/
|
||||
int getMaxConvertedLengthLimitedWithReason(BufferWriteEstimationReason reason) {
|
||||
result = this.getMaxConvertedLengthAfterLimited(0, reason)
|
||||
}
|
||||
int getMaxConvertedLengthLimited() { result = this.getMaxConvertedLengthAfterLimited(0) }
|
||||
}
|
||||
|
||||
@@ -153,11 +153,9 @@ library class SSAHelper extends int {
|
||||
* Modern Compiler Implementation by Andrew Appel.
|
||||
*/
|
||||
private predicate frontier_phi_node(StackVariable v, BasicBlock b) {
|
||||
exists(BasicBlock x |
|
||||
dominanceFrontier(x, b) and ssa_defn_rec(pragma[only_bind_into](v), pragma[only_bind_into](x))
|
||||
) and
|
||||
exists(BasicBlock x | dominanceFrontier(x, b) and ssa_defn_rec(v, x)) and
|
||||
/* We can also eliminate those nodes where the variable is not live on any incoming edge */
|
||||
live_at_start_of_bb(pragma[only_bind_into](v), b)
|
||||
live_at_start_of_bb(v, b)
|
||||
}
|
||||
|
||||
private predicate ssa_defn_rec(StackVariable v, BasicBlock b) {
|
||||
|
||||
@@ -626,9 +626,9 @@ library class ExprEvaluator extends int {
|
||||
// All assignments must have the same int value
|
||||
result =
|
||||
unique(Expr value |
|
||||
value = v.getAnAssignedValue() and not this.ignoreVariableAssignment(e, v, value)
|
||||
value = v.getAnAssignedValue() and not ignoreVariableAssignment(e, v, value)
|
||||
|
|
||||
this.getValueInternalNonSubExpr(value)
|
||||
getValueInternalNonSubExpr(value)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
private import cpp
|
||||
private import semmle.code.cpp.dataflow.internal.DataFlowPrivate
|
||||
private import semmle.code.cpp.dataflow.internal.DataFlowUtil
|
||||
|
||||
/**
|
||||
* Gets a function that might be called by `call`.
|
||||
@@ -65,17 +63,3 @@ predicate mayBenefitFromCallContext(Call call, Function f) { none() }
|
||||
* restricted to those `call`s for which a context might make a difference.
|
||||
*/
|
||||
Function viableImplInCallContext(Call call, Call ctx) { none() }
|
||||
|
||||
/** A parameter position represented by an integer. */
|
||||
class ParameterPosition extends int {
|
||||
ParameterPosition() { any(ParameterNode p).isParameterOf(_, this) }
|
||||
}
|
||||
|
||||
/** An argument position represented by an integer. */
|
||||
class ArgumentPosition extends int {
|
||||
ArgumentPosition() { any(ArgumentNode a).argumentOf(_, this) }
|
||||
}
|
||||
|
||||
/** Holds if arguments at position `apos` match parameters at position `ppos`. */
|
||||
pragma[inline]
|
||||
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { ppos = apos }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2,42 +2,6 @@ private import DataFlowImplSpecific::Private
|
||||
private import DataFlowImplSpecific::Public
|
||||
import Cached
|
||||
|
||||
module DataFlowImplCommonPublic {
|
||||
private newtype TFlowFeature =
|
||||
TFeatureHasSourceCallContext() or
|
||||
TFeatureHasSinkCallContext() or
|
||||
TFeatureEqualSourceSinkCallContext()
|
||||
|
||||
/** A flow configuration feature for use in `Configuration::getAFeature()`. */
|
||||
class FlowFeature extends TFlowFeature {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow configuration feature that implies that sources have some existing
|
||||
* call context.
|
||||
*/
|
||||
class FeatureHasSourceCallContext extends FlowFeature, TFeatureHasSourceCallContext {
|
||||
override string toString() { result = "FeatureHasSourceCallContext" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow configuration feature that implies that sinks have some existing
|
||||
* call context.
|
||||
*/
|
||||
class FeatureHasSinkCallContext extends FlowFeature, TFeatureHasSinkCallContext {
|
||||
override string toString() { result = "FeatureHasSinkCallContext" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow configuration feature that implies that source-sink pairs have some
|
||||
* shared existing call context.
|
||||
*/
|
||||
class FeatureEqualSourceSinkCallContext extends FlowFeature, TFeatureEqualSourceSinkCallContext {
|
||||
override string toString() { result = "FeatureEqualSourceSinkCallContext" }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion.
|
||||
*
|
||||
@@ -62,18 +26,6 @@ predicate accessPathCostLimits(int apLimit, int tupleLimit) {
|
||||
tupleLimit = 1000
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `arg` is an argument of `call` with an argument position that matches
|
||||
* parameter position `ppos`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate argumentPositionMatch(DataFlowCall call, ArgNode arg, ParameterPosition ppos) {
|
||||
exists(ArgumentPosition apos |
|
||||
arg.argumentOf(call, apos) and
|
||||
parameterMatch(ppos, apos)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a simple data-flow analysis for resolving lambda calls. The analysis
|
||||
* currently excludes read-steps, store-steps, and flow-through.
|
||||
@@ -83,27 +35,25 @@ predicate argumentPositionMatch(DataFlowCall call, ArgNode arg, ParameterPositio
|
||||
* calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly.
|
||||
*/
|
||||
private module LambdaFlow {
|
||||
pragma[noinline]
|
||||
private predicate viableParamNonLambda(DataFlowCall call, ParameterPosition ppos, ParamNode p) {
|
||||
p.isParameterOf(viableCallable(call), ppos)
|
||||
private predicate viableParamNonLambda(DataFlowCall call, int i, ParamNode p) {
|
||||
p.isParameterOf(viableCallable(call), i)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate viableParamLambda(DataFlowCall call, ParameterPosition ppos, ParamNode p) {
|
||||
p.isParameterOf(viableCallableLambda(call, _), ppos)
|
||||
private predicate viableParamLambda(DataFlowCall call, int i, ParamNode p) {
|
||||
p.isParameterOf(viableCallableLambda(call, _), i)
|
||||
}
|
||||
|
||||
private predicate viableParamArgNonLambda(DataFlowCall call, ParamNode p, ArgNode arg) {
|
||||
exists(ParameterPosition ppos |
|
||||
viableParamNonLambda(call, ppos, p) and
|
||||
argumentPositionMatch(call, arg, ppos)
|
||||
exists(int i |
|
||||
viableParamNonLambda(call, i, p) and
|
||||
arg.argumentOf(call, i)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate viableParamArgLambda(DataFlowCall call, ParamNode p, ArgNode arg) {
|
||||
exists(ParameterPosition ppos |
|
||||
viableParamLambda(call, ppos, p) and
|
||||
argumentPositionMatch(call, arg, ppos)
|
||||
exists(int i |
|
||||
viableParamLambda(call, i, p) and
|
||||
arg.argumentOf(call, i)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -336,7 +286,7 @@ private module Cached {
|
||||
or
|
||||
exists(ArgNode arg |
|
||||
result.(PostUpdateNode).getPreUpdateNode() = arg and
|
||||
arg.argumentOf(call, k.(ParamUpdateReturnKind).getAMatchingArgumentPosition())
|
||||
arg.argumentOf(call, k.(ParamUpdateReturnKind).getPosition())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -344,7 +294,7 @@ private module Cached {
|
||||
predicate returnNodeExt(Node n, ReturnKindExt k) {
|
||||
k = TValueReturn(n.(ReturnNode).getKind())
|
||||
or
|
||||
exists(ParamNode p, ParameterPosition pos |
|
||||
exists(ParamNode p, int pos |
|
||||
parameterValueFlowsToPreUpdate(p, n) and
|
||||
p.isParameterOf(_, pos) and
|
||||
k = TParamUpdate(pos)
|
||||
@@ -366,13 +316,11 @@ private module Cached {
|
||||
}
|
||||
|
||||
cached
|
||||
predicate parameterNode(Node p, DataFlowCallable c, ParameterPosition pos) {
|
||||
isParameterNode(p, c, pos)
|
||||
}
|
||||
predicate parameterNode(Node p, DataFlowCallable c, int pos) { isParameterNode(p, c, pos) }
|
||||
|
||||
cached
|
||||
predicate argumentNode(Node n, DataFlowCall call, ArgumentPosition pos) {
|
||||
isArgumentNode(n, call, pos)
|
||||
predicate argumentNode(Node n, DataFlowCall call, int pos) {
|
||||
n.(ArgumentNode).argumentOf(call, pos)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -390,12 +338,12 @@ private module Cached {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `p` is the parameter of a viable dispatch target of `call`,
|
||||
* and `p` has position `ppos`.
|
||||
* Holds if `p` is the `i`th parameter of a viable dispatch target of `call`.
|
||||
* The instance parameter is considered to have index `-1`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate viableParam(DataFlowCall call, ParameterPosition ppos, ParamNode p) {
|
||||
p.isParameterOf(viableCallableExt(call), ppos)
|
||||
private predicate viableParam(DataFlowCall call, int i, ParamNode p) {
|
||||
p.isParameterOf(viableCallableExt(call), i)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -404,9 +352,9 @@ private module Cached {
|
||||
*/
|
||||
cached
|
||||
predicate viableParamArg(DataFlowCall call, ParamNode p, ArgNode arg) {
|
||||
exists(ParameterPosition ppos |
|
||||
viableParam(call, ppos, p) and
|
||||
argumentPositionMatch(call, arg, ppos) and
|
||||
exists(int i |
|
||||
viableParam(call, i, p) and
|
||||
arg.argumentOf(call, i) and
|
||||
compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(p))
|
||||
)
|
||||
}
|
||||
@@ -878,7 +826,7 @@ private module Cached {
|
||||
cached
|
||||
newtype TReturnKindExt =
|
||||
TValueReturn(ReturnKind kind) or
|
||||
TParamUpdate(ParameterPosition pos) { exists(ParamNode p | p.isParameterOf(_, pos)) }
|
||||
TParamUpdate(int pos) { exists(ParamNode p | p.isParameterOf(_, pos)) }
|
||||
|
||||
cached
|
||||
newtype TBooleanOption =
|
||||
@@ -1070,9 +1018,9 @@ class ParamNode extends Node {
|
||||
|
||||
/**
|
||||
* Holds if this node is the parameter of callable `c` at the specified
|
||||
* position.
|
||||
* (zero-based) position.
|
||||
*/
|
||||
predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { parameterNode(this, c, pos) }
|
||||
predicate isParameterOf(DataFlowCallable c, int i) { parameterNode(this, c, i) }
|
||||
}
|
||||
|
||||
/** A data-flow node that represents a call argument. */
|
||||
@@ -1080,9 +1028,7 @@ class ArgNode extends Node {
|
||||
ArgNode() { argumentNode(this, _, _) }
|
||||
|
||||
/** Holds if this argument occurs at the given position in the given call. */
|
||||
final predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
argumentNode(this, call, pos)
|
||||
}
|
||||
final predicate argumentOf(DataFlowCall call, int pos) { argumentNode(this, call, pos) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1128,14 +1074,11 @@ class ValueReturnKind extends ReturnKindExt, TValueReturn {
|
||||
}
|
||||
|
||||
class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate {
|
||||
private ParameterPosition pos;
|
||||
private int pos;
|
||||
|
||||
ParamUpdateReturnKind() { this = TParamUpdate(pos) }
|
||||
|
||||
ParameterPosition getPosition() { result = pos }
|
||||
|
||||
pragma[nomagic]
|
||||
ArgumentPosition getAMatchingArgumentPosition() { parameterMatch(pos, result) }
|
||||
int getPosition() { result = pos }
|
||||
|
||||
override string toString() { result = "param update " + pos }
|
||||
}
|
||||
|
||||
@@ -9,31 +9,6 @@ private import tainttracking1.TaintTrackingParameter::Private
|
||||
private import tainttracking1.TaintTrackingParameter::Public
|
||||
|
||||
module Consistency {
|
||||
private newtype TConsistencyConfiguration = MkConsistencyConfiguration()
|
||||
|
||||
/** A class for configuring the consistency queries. */
|
||||
class ConsistencyConfiguration extends TConsistencyConfiguration {
|
||||
string toString() { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `uniqueEnclosingCallable`. */
|
||||
predicate uniqueEnclosingCallableExclude(Node n) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `uniqueNodeLocation`. */
|
||||
predicate uniqueNodeLocationExclude(Node n) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `missingLocation`. */
|
||||
predicate missingLocationExclude(Node n) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `postWithInFlow`. */
|
||||
predicate postWithInFlowExclude(Node n) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `argHasPostUpdate`. */
|
||||
predicate argHasPostUpdateExclude(ArgumentNode n) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `reverseRead`. */
|
||||
predicate reverseReadExclude(Node n) { none() }
|
||||
}
|
||||
|
||||
private class RelevantNode extends Node {
|
||||
RelevantNode() {
|
||||
this instanceof ArgumentNode or
|
||||
@@ -58,7 +33,6 @@ module Consistency {
|
||||
n instanceof RelevantNode and
|
||||
c = count(nodeGetEnclosingCallable(n)) and
|
||||
c != 1 and
|
||||
not any(ConsistencyConfiguration conf).uniqueEnclosingCallableExclude(n) and
|
||||
msg = "Node should have one enclosing callable but has " + c + "."
|
||||
)
|
||||
}
|
||||
@@ -79,7 +53,6 @@ module Consistency {
|
||||
n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
) and
|
||||
c != 1 and
|
||||
not any(ConsistencyConfiguration conf).uniqueNodeLocationExclude(n) and
|
||||
msg = "Node should have one location but has " + c + "."
|
||||
)
|
||||
}
|
||||
@@ -90,8 +63,7 @@ module Consistency {
|
||||
strictcount(Node n |
|
||||
not exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
|
||||
n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
) and
|
||||
not any(ConsistencyConfiguration conf).missingLocationExclude(n)
|
||||
)
|
||||
) and
|
||||
msg = "Nodes without location: " + c
|
||||
)
|
||||
@@ -187,13 +159,12 @@ module Consistency {
|
||||
|
||||
query predicate reverseRead(Node n, string msg) {
|
||||
exists(Node n2 | readStep(n, _, n2) and hasPost(n2) and not hasPost(n)) and
|
||||
not any(ConsistencyConfiguration conf).reverseReadExclude(n) and
|
||||
msg = "Origin of readStep is missing a PostUpdateNode."
|
||||
}
|
||||
|
||||
query predicate argHasPostUpdate(ArgumentNode n, string msg) {
|
||||
not hasPost(n) and
|
||||
not any(ConsistencyConfiguration c).argHasPostUpdateExclude(n) and
|
||||
not isImmutableOrUnobservable(n) and
|
||||
msg = "ArgumentNode is missing PostUpdateNode."
|
||||
}
|
||||
|
||||
@@ -206,7 +177,6 @@ module Consistency {
|
||||
isPostUpdateNode(n) and
|
||||
not clearsContent(n, _) and
|
||||
simpleLocalFlowStep(_, n) and
|
||||
not any(ConsistencyConfiguration c).postWithInFlowExclude(n) and
|
||||
msg = "PostUpdateNode should not be the target of local flow."
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,20 +2,12 @@ private import cpp
|
||||
private import DataFlowUtil
|
||||
private import DataFlowDispatch
|
||||
private import FlowVar
|
||||
private import DataFlowImplConsistency
|
||||
|
||||
/** Gets the callable in which this node occurs. */
|
||||
DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.getEnclosingCallable() }
|
||||
|
||||
/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
|
||||
predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) {
|
||||
p.isParameterOf(c, pos)
|
||||
}
|
||||
|
||||
/** Holds if `arg` is an `ArgumentNode` of `c` with position `pos`. */
|
||||
predicate isArgumentNode(ArgumentNode arg, DataFlowCall c, ArgumentPosition pos) {
|
||||
arg.argumentOf(c, pos)
|
||||
}
|
||||
predicate isParameterNode(ParameterNode p, DataFlowCallable c, int pos) { p.isParameterOf(c, pos) }
|
||||
|
||||
/** Gets the instance argument of a non-static call. */
|
||||
private Node getInstanceArgument(Call call) {
|
||||
@@ -267,6 +259,27 @@ class Unit extends TUnit {
|
||||
string toString() { result = "unit" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` does not require a `PostUpdateNode` as it either cannot be
|
||||
* modified or its modification cannot be observed, for example if it is a
|
||||
* freshly created object that is not saved in a variable.
|
||||
*
|
||||
* This predicate is only used for consistency checks.
|
||||
*/
|
||||
predicate isImmutableOrUnobservable(Node n) {
|
||||
// Is the null pointer (or something that's not really a pointer)
|
||||
exists(n.asExpr().getValue())
|
||||
or
|
||||
// Isn't a pointer or is a pointer to const
|
||||
forall(DerivedType dt | dt = n.asExpr().getActualType() |
|
||||
dt.getBaseType().isConst()
|
||||
or
|
||||
dt.getBaseType() instanceof RoutineType
|
||||
)
|
||||
// The above list of cases isn't exhaustive, but it narrows down the
|
||||
// consistency alerts enough that most of them are interesting.
|
||||
}
|
||||
|
||||
/** Holds if `n` should be hidden from path explanations. */
|
||||
predicate nodeIsHidden(Node n) { none() }
|
||||
|
||||
@@ -289,19 +302,3 @@ predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preserves
|
||||
* by default as a heuristic.
|
||||
*/
|
||||
predicate allowParameterReturnInSelf(ParameterNode p) { none() }
|
||||
|
||||
private class MyConsistencyConfiguration extends Consistency::ConsistencyConfiguration {
|
||||
override predicate argHasPostUpdateExclude(ArgumentNode n) {
|
||||
// Is the null pointer (or something that's not really a pointer)
|
||||
exists(n.asExpr().getValue())
|
||||
or
|
||||
// Isn't a pointer or is a pointer to const
|
||||
forall(DerivedType dt | dt = n.asExpr().getActualType() |
|
||||
dt.getBaseType().isConst()
|
||||
or
|
||||
dt.getBaseType() instanceof RoutineType
|
||||
)
|
||||
// The above list of cases isn't exhaustive, but it narrows down the
|
||||
// consistency alerts enough that most of them are interesting.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ class LambdaCapture extends Locatable, @lambdacapture {
|
||||
* An identifier is captured by reference if:
|
||||
* - It is explicitly captured by reference.
|
||||
* - It is implicitly captured, and the lambda's default capture mode is by-reference.
|
||||
* - The identifier is "this". [Said behavior is dictated by the C++11 standard, but it
|
||||
* - The identifier is "this". [Said behaviour is dictated by the C++11 standard, but it
|
||||
* is actually "*this" being captured rather than "this".]
|
||||
*/
|
||||
predicate isCapturedByReference() { lambda_capture(this, _, _, _, true, _, _) }
|
||||
|
||||
@@ -474,25 +474,6 @@ module TaintedWithPath {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*/
|
||||
module Private {
|
||||
/** Gets a predecessor `PathNode` of `pathNode`, if any. */
|
||||
PathNode getAPredecessor(PathNode pathNode) { edges(result, pathNode) }
|
||||
|
||||
/** Gets the element that `pathNode` wraps, if any. */
|
||||
Element getElementFromPathNode(PathNode pathNode) {
|
||||
exists(DataFlow::Node node | node = pathNode.(WrapPathNode).inner().getNode() |
|
||||
result = node.asInstruction().getAST()
|
||||
or
|
||||
result = node.asOperand().getDef().getAST()
|
||||
)
|
||||
or
|
||||
result = pathNode.(EndpointPathNode).inner()
|
||||
}
|
||||
}
|
||||
|
||||
private class WrapPathNode extends PathNode, TWrapPathNode {
|
||||
DataFlow3::PathNode inner() { this = TWrapPathNode(result) }
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ private import cpp
|
||||
private import semmle.code.cpp.ir.IR
|
||||
private import semmle.code.cpp.ir.dataflow.DataFlow
|
||||
private import semmle.code.cpp.ir.dataflow.internal.DataFlowPrivate
|
||||
private import semmle.code.cpp.ir.dataflow.internal.DataFlowUtil
|
||||
private import DataFlowImplCommon as DataFlowImplCommon
|
||||
|
||||
/**
|
||||
@@ -63,11 +62,9 @@ private module VirtualDispatch {
|
||||
this.flowsFrom(other, allowOtherFromArg)
|
||||
|
|
||||
// Call argument
|
||||
exists(DataFlowCall call, Position i |
|
||||
other
|
||||
.(DataFlow::ParameterNode)
|
||||
.isParameterOf(pragma[only_bind_into](call).getStaticCallTarget(), i) and
|
||||
src.(ArgumentNode).argumentOf(call, pragma[only_bind_into](pragma[only_bind_out](i)))
|
||||
exists(DataFlowCall call, int i |
|
||||
other.(DataFlow::ParameterNode).isParameterOf(call.getStaticCallTarget(), i) and
|
||||
src.(ArgumentNode).argumentOf(call, i)
|
||||
) and
|
||||
allowOtherFromArg = true and
|
||||
allowFromArg = true
|
||||
@@ -131,7 +128,6 @@ private module VirtualDispatch {
|
||||
*
|
||||
* Used to fix a join ordering issue in flowsFrom.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate returnNodeWithKindAndEnclosingCallable(
|
||||
ReturnNode node, ReturnKind kind, DataFlowCallable callable
|
||||
) {
|
||||
@@ -267,7 +263,3 @@ Function viableImplInCallContext(CallInstruction call, CallInstruction ctx) {
|
||||
result = ctx.getArgument(i).getUnconvertedResultExpression().(FunctionAccess).getTarget()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if arguments at position `apos` match parameters at position `ppos`. */
|
||||
pragma[inline]
|
||||
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { ppos = apos }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2,42 +2,6 @@ private import DataFlowImplSpecific::Private
|
||||
private import DataFlowImplSpecific::Public
|
||||
import Cached
|
||||
|
||||
module DataFlowImplCommonPublic {
|
||||
private newtype TFlowFeature =
|
||||
TFeatureHasSourceCallContext() or
|
||||
TFeatureHasSinkCallContext() or
|
||||
TFeatureEqualSourceSinkCallContext()
|
||||
|
||||
/** A flow configuration feature for use in `Configuration::getAFeature()`. */
|
||||
class FlowFeature extends TFlowFeature {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow configuration feature that implies that sources have some existing
|
||||
* call context.
|
||||
*/
|
||||
class FeatureHasSourceCallContext extends FlowFeature, TFeatureHasSourceCallContext {
|
||||
override string toString() { result = "FeatureHasSourceCallContext" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow configuration feature that implies that sinks have some existing
|
||||
* call context.
|
||||
*/
|
||||
class FeatureHasSinkCallContext extends FlowFeature, TFeatureHasSinkCallContext {
|
||||
override string toString() { result = "FeatureHasSinkCallContext" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow configuration feature that implies that source-sink pairs have some
|
||||
* shared existing call context.
|
||||
*/
|
||||
class FeatureEqualSourceSinkCallContext extends FlowFeature, TFeatureEqualSourceSinkCallContext {
|
||||
override string toString() { result = "FeatureEqualSourceSinkCallContext" }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion.
|
||||
*
|
||||
@@ -62,18 +26,6 @@ predicate accessPathCostLimits(int apLimit, int tupleLimit) {
|
||||
tupleLimit = 1000
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `arg` is an argument of `call` with an argument position that matches
|
||||
* parameter position `ppos`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate argumentPositionMatch(DataFlowCall call, ArgNode arg, ParameterPosition ppos) {
|
||||
exists(ArgumentPosition apos |
|
||||
arg.argumentOf(call, apos) and
|
||||
parameterMatch(ppos, apos)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a simple data-flow analysis for resolving lambda calls. The analysis
|
||||
* currently excludes read-steps, store-steps, and flow-through.
|
||||
@@ -83,27 +35,25 @@ predicate argumentPositionMatch(DataFlowCall call, ArgNode arg, ParameterPositio
|
||||
* calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly.
|
||||
*/
|
||||
private module LambdaFlow {
|
||||
pragma[noinline]
|
||||
private predicate viableParamNonLambda(DataFlowCall call, ParameterPosition ppos, ParamNode p) {
|
||||
p.isParameterOf(viableCallable(call), ppos)
|
||||
private predicate viableParamNonLambda(DataFlowCall call, int i, ParamNode p) {
|
||||
p.isParameterOf(viableCallable(call), i)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate viableParamLambda(DataFlowCall call, ParameterPosition ppos, ParamNode p) {
|
||||
p.isParameterOf(viableCallableLambda(call, _), ppos)
|
||||
private predicate viableParamLambda(DataFlowCall call, int i, ParamNode p) {
|
||||
p.isParameterOf(viableCallableLambda(call, _), i)
|
||||
}
|
||||
|
||||
private predicate viableParamArgNonLambda(DataFlowCall call, ParamNode p, ArgNode arg) {
|
||||
exists(ParameterPosition ppos |
|
||||
viableParamNonLambda(call, ppos, p) and
|
||||
argumentPositionMatch(call, arg, ppos)
|
||||
exists(int i |
|
||||
viableParamNonLambda(call, i, p) and
|
||||
arg.argumentOf(call, i)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate viableParamArgLambda(DataFlowCall call, ParamNode p, ArgNode arg) {
|
||||
exists(ParameterPosition ppos |
|
||||
viableParamLambda(call, ppos, p) and
|
||||
argumentPositionMatch(call, arg, ppos)
|
||||
exists(int i |
|
||||
viableParamLambda(call, i, p) and
|
||||
arg.argumentOf(call, i)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -336,7 +286,7 @@ private module Cached {
|
||||
or
|
||||
exists(ArgNode arg |
|
||||
result.(PostUpdateNode).getPreUpdateNode() = arg and
|
||||
arg.argumentOf(call, k.(ParamUpdateReturnKind).getAMatchingArgumentPosition())
|
||||
arg.argumentOf(call, k.(ParamUpdateReturnKind).getPosition())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -344,7 +294,7 @@ private module Cached {
|
||||
predicate returnNodeExt(Node n, ReturnKindExt k) {
|
||||
k = TValueReturn(n.(ReturnNode).getKind())
|
||||
or
|
||||
exists(ParamNode p, ParameterPosition pos |
|
||||
exists(ParamNode p, int pos |
|
||||
parameterValueFlowsToPreUpdate(p, n) and
|
||||
p.isParameterOf(_, pos) and
|
||||
k = TParamUpdate(pos)
|
||||
@@ -366,13 +316,11 @@ private module Cached {
|
||||
}
|
||||
|
||||
cached
|
||||
predicate parameterNode(Node p, DataFlowCallable c, ParameterPosition pos) {
|
||||
isParameterNode(p, c, pos)
|
||||
}
|
||||
predicate parameterNode(Node p, DataFlowCallable c, int pos) { isParameterNode(p, c, pos) }
|
||||
|
||||
cached
|
||||
predicate argumentNode(Node n, DataFlowCall call, ArgumentPosition pos) {
|
||||
isArgumentNode(n, call, pos)
|
||||
predicate argumentNode(Node n, DataFlowCall call, int pos) {
|
||||
n.(ArgumentNode).argumentOf(call, pos)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -390,12 +338,12 @@ private module Cached {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `p` is the parameter of a viable dispatch target of `call`,
|
||||
* and `p` has position `ppos`.
|
||||
* Holds if `p` is the `i`th parameter of a viable dispatch target of `call`.
|
||||
* The instance parameter is considered to have index `-1`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate viableParam(DataFlowCall call, ParameterPosition ppos, ParamNode p) {
|
||||
p.isParameterOf(viableCallableExt(call), ppos)
|
||||
private predicate viableParam(DataFlowCall call, int i, ParamNode p) {
|
||||
p.isParameterOf(viableCallableExt(call), i)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -404,9 +352,9 @@ private module Cached {
|
||||
*/
|
||||
cached
|
||||
predicate viableParamArg(DataFlowCall call, ParamNode p, ArgNode arg) {
|
||||
exists(ParameterPosition ppos |
|
||||
viableParam(call, ppos, p) and
|
||||
argumentPositionMatch(call, arg, ppos) and
|
||||
exists(int i |
|
||||
viableParam(call, i, p) and
|
||||
arg.argumentOf(call, i) and
|
||||
compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(p))
|
||||
)
|
||||
}
|
||||
@@ -878,7 +826,7 @@ private module Cached {
|
||||
cached
|
||||
newtype TReturnKindExt =
|
||||
TValueReturn(ReturnKind kind) or
|
||||
TParamUpdate(ParameterPosition pos) { exists(ParamNode p | p.isParameterOf(_, pos)) }
|
||||
TParamUpdate(int pos) { exists(ParamNode p | p.isParameterOf(_, pos)) }
|
||||
|
||||
cached
|
||||
newtype TBooleanOption =
|
||||
@@ -1070,9 +1018,9 @@ class ParamNode extends Node {
|
||||
|
||||
/**
|
||||
* Holds if this node is the parameter of callable `c` at the specified
|
||||
* position.
|
||||
* (zero-based) position.
|
||||
*/
|
||||
predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) { parameterNode(this, c, pos) }
|
||||
predicate isParameterOf(DataFlowCallable c, int i) { parameterNode(this, c, i) }
|
||||
}
|
||||
|
||||
/** A data-flow node that represents a call argument. */
|
||||
@@ -1080,9 +1028,7 @@ class ArgNode extends Node {
|
||||
ArgNode() { argumentNode(this, _, _) }
|
||||
|
||||
/** Holds if this argument occurs at the given position in the given call. */
|
||||
final predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
argumentNode(this, call, pos)
|
||||
}
|
||||
final predicate argumentOf(DataFlowCall call, int pos) { argumentNode(this, call, pos) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1128,14 +1074,11 @@ class ValueReturnKind extends ReturnKindExt, TValueReturn {
|
||||
}
|
||||
|
||||
class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate {
|
||||
private ParameterPosition pos;
|
||||
private int pos;
|
||||
|
||||
ParamUpdateReturnKind() { this = TParamUpdate(pos) }
|
||||
|
||||
ParameterPosition getPosition() { result = pos }
|
||||
|
||||
pragma[nomagic]
|
||||
ArgumentPosition getAMatchingArgumentPosition() { parameterMatch(pos, result) }
|
||||
int getPosition() { result = pos }
|
||||
|
||||
override string toString() { result = "param update " + pos }
|
||||
}
|
||||
|
||||
@@ -9,31 +9,6 @@ private import tainttracking1.TaintTrackingParameter::Private
|
||||
private import tainttracking1.TaintTrackingParameter::Public
|
||||
|
||||
module Consistency {
|
||||
private newtype TConsistencyConfiguration = MkConsistencyConfiguration()
|
||||
|
||||
/** A class for configuring the consistency queries. */
|
||||
class ConsistencyConfiguration extends TConsistencyConfiguration {
|
||||
string toString() { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `uniqueEnclosingCallable`. */
|
||||
predicate uniqueEnclosingCallableExclude(Node n) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `uniqueNodeLocation`. */
|
||||
predicate uniqueNodeLocationExclude(Node n) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `missingLocation`. */
|
||||
predicate missingLocationExclude(Node n) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `postWithInFlow`. */
|
||||
predicate postWithInFlowExclude(Node n) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `argHasPostUpdate`. */
|
||||
predicate argHasPostUpdateExclude(ArgumentNode n) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `reverseRead`. */
|
||||
predicate reverseReadExclude(Node n) { none() }
|
||||
}
|
||||
|
||||
private class RelevantNode extends Node {
|
||||
RelevantNode() {
|
||||
this instanceof ArgumentNode or
|
||||
@@ -58,7 +33,6 @@ module Consistency {
|
||||
n instanceof RelevantNode and
|
||||
c = count(nodeGetEnclosingCallable(n)) and
|
||||
c != 1 and
|
||||
not any(ConsistencyConfiguration conf).uniqueEnclosingCallableExclude(n) and
|
||||
msg = "Node should have one enclosing callable but has " + c + "."
|
||||
)
|
||||
}
|
||||
@@ -79,7 +53,6 @@ module Consistency {
|
||||
n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
) and
|
||||
c != 1 and
|
||||
not any(ConsistencyConfiguration conf).uniqueNodeLocationExclude(n) and
|
||||
msg = "Node should have one location but has " + c + "."
|
||||
)
|
||||
}
|
||||
@@ -90,8 +63,7 @@ module Consistency {
|
||||
strictcount(Node n |
|
||||
not exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
|
||||
n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
) and
|
||||
not any(ConsistencyConfiguration conf).missingLocationExclude(n)
|
||||
)
|
||||
) and
|
||||
msg = "Nodes without location: " + c
|
||||
)
|
||||
@@ -187,13 +159,12 @@ module Consistency {
|
||||
|
||||
query predicate reverseRead(Node n, string msg) {
|
||||
exists(Node n2 | readStep(n, _, n2) and hasPost(n2) and not hasPost(n)) and
|
||||
not any(ConsistencyConfiguration conf).reverseReadExclude(n) and
|
||||
msg = "Origin of readStep is missing a PostUpdateNode."
|
||||
}
|
||||
|
||||
query predicate argHasPostUpdate(ArgumentNode n, string msg) {
|
||||
not hasPost(n) and
|
||||
not any(ConsistencyConfiguration c).argHasPostUpdateExclude(n) and
|
||||
not isImmutableOrUnobservable(n) and
|
||||
msg = "ArgumentNode is missing PostUpdateNode."
|
||||
}
|
||||
|
||||
@@ -206,7 +177,6 @@ module Consistency {
|
||||
isPostUpdateNode(n) and
|
||||
not clearsContent(n, _) and
|
||||
simpleLocalFlowStep(_, n) and
|
||||
not any(ConsistencyConfiguration c).postWithInFlowExclude(n) and
|
||||
msg = "PostUpdateNode should not be the target of local flow."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,20 +2,12 @@ private import cpp
|
||||
private import DataFlowUtil
|
||||
private import semmle.code.cpp.ir.IR
|
||||
private import DataFlowDispatch
|
||||
private import DataFlowImplConsistency
|
||||
|
||||
/** Gets the callable in which this node occurs. */
|
||||
DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.getEnclosingCallable() }
|
||||
|
||||
/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
|
||||
predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) {
|
||||
p.isParameterOf(c, pos)
|
||||
}
|
||||
|
||||
/** Holds if `arg` is an `ArgumentNode` of `c` with position `pos`. */
|
||||
predicate isArgumentNode(ArgumentNode arg, DataFlowCall c, ArgumentPosition pos) {
|
||||
arg.argumentOf(c, pos)
|
||||
}
|
||||
predicate isParameterNode(ParameterNode p, DataFlowCallable c, int pos) { p.isParameterOf(c, pos) }
|
||||
|
||||
/**
|
||||
* A data flow node that occurs as the argument of a call and is passed as-is
|
||||
@@ -27,7 +19,7 @@ abstract class ArgumentNode extends OperandNode {
|
||||
* Holds if this argument occurs at the given position in the given call.
|
||||
* The instance argument is considered to have index `-1`.
|
||||
*/
|
||||
abstract predicate argumentOf(DataFlowCall call, ArgumentPosition pos);
|
||||
abstract predicate argumentOf(DataFlowCall call, int pos);
|
||||
|
||||
/** Gets the call in which this node is an argument. */
|
||||
DataFlowCall getCall() { this.argumentOf(result, _) }
|
||||
@@ -42,9 +34,7 @@ private class PrimaryArgumentNode extends ArgumentNode {
|
||||
|
||||
PrimaryArgumentNode() { exists(CallInstruction call | op = call.getAnArgumentOperand()) }
|
||||
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
op = call.getArgumentOperand(pos.(DirectPosition).getIndex())
|
||||
}
|
||||
override predicate argumentOf(DataFlowCall call, int pos) { op = call.getArgumentOperand(pos) }
|
||||
|
||||
override string toString() {
|
||||
exists(Expr unconverted |
|
||||
@@ -73,9 +63,9 @@ private class SideEffectArgumentNode extends ArgumentNode {
|
||||
|
||||
SideEffectArgumentNode() { op = read.getSideEffectOperand() }
|
||||
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
override predicate argumentOf(DataFlowCall call, int pos) {
|
||||
read.getPrimaryInstruction() = call and
|
||||
pos.(IndirectionPosition).getIndex() = read.getIndex()
|
||||
pos = getArgumentPosOfSideEffect(read.getIndex())
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
@@ -92,54 +82,6 @@ private class SideEffectArgumentNode extends ArgumentNode {
|
||||
}
|
||||
}
|
||||
|
||||
/** A parameter position represented by an integer. */
|
||||
class ParameterPosition = Position;
|
||||
|
||||
/** An argument position represented by an integer. */
|
||||
class ArgumentPosition = Position;
|
||||
|
||||
class Position extends TPosition {
|
||||
abstract string toString();
|
||||
}
|
||||
|
||||
class DirectPosition extends TDirectPosition {
|
||||
int index;
|
||||
|
||||
DirectPosition() { this = TDirectPosition(index) }
|
||||
|
||||
string toString() {
|
||||
index = -1 and
|
||||
result = "this"
|
||||
or
|
||||
index != -1 and
|
||||
result = index.toString()
|
||||
}
|
||||
|
||||
int getIndex() { result = index }
|
||||
}
|
||||
|
||||
class IndirectionPosition extends TIndirectionPosition {
|
||||
int index;
|
||||
|
||||
IndirectionPosition() { this = TIndirectionPosition(index) }
|
||||
|
||||
string toString() {
|
||||
index = -1 and
|
||||
result = "this"
|
||||
or
|
||||
index != -1 and
|
||||
result = index.toString()
|
||||
}
|
||||
|
||||
int getIndex() { result = index }
|
||||
}
|
||||
|
||||
newtype TPosition =
|
||||
TDirectPosition(int index) { exists(any(CallInstruction c).getArgument(index)) } or
|
||||
TIndirectionPosition(int index) {
|
||||
exists(ReadSideEffectInstruction instr | instr.getIndex() = index)
|
||||
}
|
||||
|
||||
private newtype TReturnKind =
|
||||
TNormalReturnKind() or
|
||||
TIndirectReturnKind(ParameterIndex index)
|
||||
@@ -170,9 +112,11 @@ class ReturnNode extends InstructionNode {
|
||||
Instruction primary;
|
||||
|
||||
ReturnNode() {
|
||||
exists(ReturnValueInstruction ret | instr = ret and primary = ret)
|
||||
exists(ReturnValueInstruction ret | instr = ret.getReturnValue() and primary = ret)
|
||||
or
|
||||
exists(ReturnIndirectionInstruction rii | instr = rii and primary = rii)
|
||||
exists(ReturnIndirectionInstruction rii |
|
||||
instr = rii.getSideEffectOperand().getAnyDef() and primary = rii
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the kind of this returned value. */
|
||||
@@ -246,16 +190,108 @@ OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
|
||||
*/
|
||||
predicate jumpStep(Node n1, Node n2) { none() }
|
||||
|
||||
private predicate fieldStoreStepNoChi(Node node1, FieldContent f, PostUpdateNode node2) {
|
||||
exists(StoreInstruction store, Class c |
|
||||
store = node2.asInstruction() and
|
||||
store.getSourceValueOperand() = node1.asOperand() and
|
||||
getWrittenField(store, f.getAField(), c) and
|
||||
f.hasOffset(c, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
private FieldAddressInstruction getFieldInstruction(Instruction instr) {
|
||||
result = instr or
|
||||
result = instr.(CopyValueInstruction).getUnary()
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate getWrittenField(Instruction instr, Field f, Class c) {
|
||||
exists(FieldAddressInstruction fa |
|
||||
fa =
|
||||
getFieldInstruction([
|
||||
instr.(StoreInstruction).getDestinationAddress(),
|
||||
instr.(WriteSideEffectInstruction).getDestinationAddress()
|
||||
]) and
|
||||
f = fa.getField() and
|
||||
c = f.getDeclaringType()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate fieldStoreStepChi(Node node1, FieldContent f, PostUpdateNode node2) {
|
||||
exists(ChiPartialOperand operand, ChiInstruction chi |
|
||||
chi.getPartialOperand() = operand and
|
||||
node1.asOperand() = operand and
|
||||
node2.asInstruction() = chi and
|
||||
exists(Class c |
|
||||
c = chi.getResultType() and
|
||||
exists(int startBit, int endBit |
|
||||
chi.getUpdatedInterval(startBit, endBit) and
|
||||
f.hasOffset(c, startBit, endBit)
|
||||
)
|
||||
or
|
||||
getWrittenField(operand.getDef(), f.getAField(), c) and
|
||||
f.hasOffset(c, _, _)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate arrayStoreStepChi(Node node1, ArrayContent a, PostUpdateNode node2) {
|
||||
exists(a) and
|
||||
exists(ChiPartialOperand operand, ChiInstruction chi, StoreInstruction store |
|
||||
chi.getPartialOperand() = operand and
|
||||
store = operand.getDef() and
|
||||
node1.asOperand() = operand and
|
||||
// This `ChiInstruction` will always have a non-conflated result because both `ArrayStoreNode`
|
||||
// and `PointerStoreNode` require it in their characteristic predicates.
|
||||
node2.asInstruction() = chi and
|
||||
(
|
||||
// `x[i] = taint()`
|
||||
// This matches the characteristic predicate in `ArrayStoreNode`.
|
||||
store.getDestinationAddress() instanceof PointerAddInstruction
|
||||
or
|
||||
// `*p = taint()`
|
||||
// This matches the characteristic predicate in `PointerStoreNode`.
|
||||
store.getDestinationAddress().(CopyValueInstruction).getUnary() instanceof LoadInstruction
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` via an assignment to `f`.
|
||||
* Thus, `node2` references an object with a field `f` that contains the
|
||||
* value of `node1`.
|
||||
*/
|
||||
predicate storeStep(StoreNodeInstr node1, FieldContent f, StoreNodeInstr node2) {
|
||||
exists(FieldAddressInstruction fai |
|
||||
node1.getInstruction() = fai and
|
||||
node2.getInstruction() = fai.getObjectAddress() and
|
||||
f.getField() = fai.getField()
|
||||
predicate storeStep(Node node1, Content f, PostUpdateNode node2) {
|
||||
fieldStoreStepNoChi(node1, f, node2) or
|
||||
fieldStoreStepChi(node1, f, node2) or
|
||||
arrayStoreStepChi(node1, f, node2) or
|
||||
fieldStoreStepAfterArraySuppression(node1, f, node2)
|
||||
}
|
||||
|
||||
// This predicate pushes the correct `FieldContent` onto the access path when the
|
||||
// `suppressArrayRead` predicate has popped off an `ArrayContent`.
|
||||
private predicate fieldStoreStepAfterArraySuppression(
|
||||
Node node1, FieldContent f, PostUpdateNode node2
|
||||
) {
|
||||
exists(WriteSideEffectInstruction write, ChiInstruction chi, Class c |
|
||||
not chi.isResultConflated() and
|
||||
node1.asInstruction() = chi and
|
||||
node2.asInstruction() = chi and
|
||||
chi.getPartial() = write and
|
||||
getWrittenField(write, f.getAField(), c) and
|
||||
f.hasOffset(c, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[result, i]
|
||||
private int unbindInt(int i) { i <= result and i >= result }
|
||||
|
||||
pragma[noinline]
|
||||
private predicate getLoadedField(LoadInstruction load, Field f, Class c) {
|
||||
exists(FieldAddressInstruction fa |
|
||||
fa = load.getSourceAddress() and
|
||||
f = fa.getField() and
|
||||
c = f.getDeclaringType()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -264,14 +300,122 @@ predicate storeStep(StoreNodeInstr node1, FieldContent f, StoreNodeInstr node2)
|
||||
* Thus, `node1` references an object with a field `f` whose value ends up in
|
||||
* `node2`.
|
||||
*/
|
||||
predicate readStep(ReadNode node1, FieldContent f, ReadNode node2) {
|
||||
exists(FieldAddressInstruction fai |
|
||||
node1.getInstruction() = fai.getObjectAddress() and
|
||||
node2.getInstruction() = fai and
|
||||
f.getField() = fai.getField()
|
||||
private predicate fieldReadStep(Node node1, FieldContent f, Node node2) {
|
||||
exists(LoadOperand operand |
|
||||
node2.asOperand() = operand and
|
||||
node1.asInstruction() = operand.getAnyDef() and
|
||||
exists(Class c |
|
||||
c = operand.getAnyDef().getResultType() and
|
||||
exists(int startBit, int endBit |
|
||||
operand.getUsedInterval(unbindInt(startBit), unbindInt(endBit)) and
|
||||
f.hasOffset(c, startBit, endBit)
|
||||
)
|
||||
or
|
||||
getLoadedField(operand.getUse(), f.getAField(), c) and
|
||||
f.hasOffset(c, _, _)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* When a store step happens in a function that looks like an array write such as:
|
||||
* ```cpp
|
||||
* void f(int* pa) {
|
||||
* pa = source();
|
||||
* }
|
||||
* ```
|
||||
* it can be a write to an array, but it can also happen that `f` is called as `f(&a.x)`. If that is
|
||||
* the case, the `ArrayContent` that was written by the call to `f` should be popped off the access
|
||||
* path, and a `FieldContent` containing `x` should be pushed instead.
|
||||
* So this case pops `ArrayContent` off the access path, and the `fieldStoreStepAfterArraySuppression`
|
||||
* predicate in `storeStep` ensures that we push the right `FieldContent` onto the access path.
|
||||
*/
|
||||
predicate suppressArrayRead(Node node1, ArrayContent a, Node node2) {
|
||||
exists(a) and
|
||||
exists(WriteSideEffectInstruction write, ChiInstruction chi |
|
||||
node1.asInstruction() = write and
|
||||
node2.asInstruction() = chi and
|
||||
chi.getPartial() = write and
|
||||
getWrittenField(write, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
private class ArrayToPointerConvertInstruction extends ConvertInstruction {
|
||||
ArrayToPointerConvertInstruction() {
|
||||
this.getUnary().getResultType() instanceof ArrayType and
|
||||
this.getResultType() instanceof PointerType
|
||||
}
|
||||
}
|
||||
|
||||
private Instruction skipOneCopyValueInstructionRec(CopyValueInstruction copy) {
|
||||
copy.getUnary() = result and not result instanceof CopyValueInstruction
|
||||
or
|
||||
result = skipOneCopyValueInstructionRec(copy.getUnary())
|
||||
}
|
||||
|
||||
private Instruction skipCopyValueInstructions(Operand op) {
|
||||
not result instanceof CopyValueInstruction and result = op.getDef()
|
||||
or
|
||||
result = skipOneCopyValueInstructionRec(op.getDef())
|
||||
}
|
||||
|
||||
private predicate arrayReadStep(Node node1, ArrayContent a, Node node2) {
|
||||
exists(a) and
|
||||
// Explicit dereferences such as `*p` or `p[i]` where `p` is a pointer or array.
|
||||
exists(LoadOperand operand, Instruction address |
|
||||
operand.isDefinitionInexact() and
|
||||
node1.asInstruction() = operand.getAnyDef() and
|
||||
operand = node2.asOperand() and
|
||||
address = skipCopyValueInstructions(operand.getAddressOperand()) and
|
||||
(
|
||||
address instanceof LoadInstruction or
|
||||
address instanceof ArrayToPointerConvertInstruction or
|
||||
address instanceof PointerOffsetInstruction
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* In cases such as:
|
||||
* ```cpp
|
||||
* void f(int* pa) {
|
||||
* *pa = source();
|
||||
* }
|
||||
* ...
|
||||
* int x;
|
||||
* f(&x);
|
||||
* use(x);
|
||||
* ```
|
||||
* the load on `x` in `use(x)` will exactly overlap with its definition (in this case the definition
|
||||
* is a `WriteSideEffect`). This predicate pops the `ArrayContent` (pushed by the store in `f`)
|
||||
* from the access path.
|
||||
*/
|
||||
private predicate exactReadStep(Node node1, ArrayContent a, Node node2) {
|
||||
exists(a) and
|
||||
exists(WriteSideEffectInstruction write, ChiInstruction chi |
|
||||
not chi.isResultConflated() and
|
||||
chi.getPartial() = write and
|
||||
node1.asInstruction() = write and
|
||||
node2.asInstruction() = chi and
|
||||
// To distinquish this case from the `arrayReadStep` case we require that the entire variable was
|
||||
// overwritten by the `WriteSideEffectInstruction` (i.e., there is a load that reads the
|
||||
// entire variable).
|
||||
exists(LoadInstruction load | load.getSourceValue() = chi)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` via a read of `f`.
|
||||
* Thus, `node1` references an object with a field `f` whose value ends up in
|
||||
* `node2`.
|
||||
*/
|
||||
predicate readStep(Node node1, Content f, Node node2) {
|
||||
fieldReadStep(node1, f, node2) or
|
||||
arrayReadStep(node1, f, node2) or
|
||||
exactReadStep(node1, f, node2) or
|
||||
suppressArrayRead(node1, f, node2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if values stored inside content `c` are cleared at node `n`.
|
||||
*/
|
||||
@@ -303,7 +447,7 @@ private predicate suppressUnusedNode(Node n) { any() }
|
||||
// Java QL library compatibility wrappers
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/** A node that performs a type cast. */
|
||||
class CastNode extends Node {
|
||||
class CastNode extends InstructionNode {
|
||||
CastNode() { none() } // stub implementation
|
||||
}
|
||||
|
||||
@@ -343,19 +487,22 @@ class Unit extends TUnit {
|
||||
string toString() { result = "unit" }
|
||||
}
|
||||
|
||||
/** Holds if `n` should be hidden from path explanations. */
|
||||
predicate nodeIsHidden(Node n) {
|
||||
n instanceof OperandNode and not n instanceof ArgumentNode
|
||||
or
|
||||
StoreNodeFlow::flowThrough(n, _) and
|
||||
not StoreNodeFlow::flowOutOf(n, _) and
|
||||
not StoreNodeFlow::flowInto(_, n)
|
||||
or
|
||||
ReadNodeFlow::flowThrough(n, _) and
|
||||
not ReadNodeFlow::flowOutOf(n, _) and
|
||||
not ReadNodeFlow::flowInto(_, n)
|
||||
/**
|
||||
* Holds if `n` does not require a `PostUpdateNode` as it either cannot be
|
||||
* modified or its modification cannot be observed, for example if it is a
|
||||
* freshly created object that is not saved in a variable.
|
||||
*
|
||||
* This predicate is only used for consistency checks.
|
||||
*/
|
||||
predicate isImmutableOrUnobservable(Node n) {
|
||||
// The rules for whether an IR argument gets a post-update node are too
|
||||
// complex to model here.
|
||||
any()
|
||||
}
|
||||
|
||||
/** Holds if `n` should be hidden from path explanations. */
|
||||
predicate nodeIsHidden(Node n) { n instanceof OperandNode and not n instanceof ArgumentNode }
|
||||
|
||||
class LambdaCallKind = Unit;
|
||||
|
||||
/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */
|
||||
@@ -375,11 +522,3 @@ predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preserves
|
||||
* by default as a heuristic.
|
||||
*/
|
||||
predicate allowParameterReturnInSelf(ParameterNode p) { none() }
|
||||
|
||||
private class MyConsistencyConfiguration extends Consistency::ConsistencyConfiguration {
|
||||
override predicate argHasPostUpdateExclude(ArgumentNode n) {
|
||||
// The rules for whether an IR argument gets a post-update node are too
|
||||
// complex to model here.
|
||||
any()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,78 +10,19 @@ private import semmle.code.cpp.ir.ValueNumbering
|
||||
private import semmle.code.cpp.ir.IR
|
||||
private import semmle.code.cpp.controlflow.IRGuards
|
||||
private import semmle.code.cpp.models.interfaces.DataFlow
|
||||
private import DataFlowPrivate
|
||||
private import SsaInternals as Ssa
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
/**
|
||||
* The IR dataflow graph consists of the following nodes:
|
||||
* - `InstructionNode`, which represents an `Instruction` in the graph.
|
||||
* - `OperandNode`, which represents an `Operand` in the graph.
|
||||
* - `VariableNode`, which is used to model global variables.
|
||||
* - Two kinds of `StoreNode`s:
|
||||
* 1. `StoreNodeInstr`, which represents the value of an address computed by an `Instruction` that
|
||||
* has been updated by a write operation.
|
||||
* 2. `StoreNodeOperand`, which represents the value of an address in an `ArgumentOperand` after a
|
||||
* function call that may have changed the value.
|
||||
* - `ReadNode`, which represents the result of reading a field of an object.
|
||||
* - `SsaPhiNode`, which represents phi nodes as computed by the shared SSA library.
|
||||
*
|
||||
* The following section describes how flow is generally transferred between these nodes:
|
||||
* - Flow between `InstructionNode`s and `OperandNode`s follow the def-use information as computed by
|
||||
* the IR. Because the IR compute must-alias information for memory operands, we only follow def-use
|
||||
* flow for register operands.
|
||||
* - Flow can enter a `StoreNode` in two ways (both done in `StoreNode.flowInto`):
|
||||
* 1. Flow is transferred from a `StoreValueOperand` to a `StoreNodeInstr`. Flow will then proceed
|
||||
* along the chain of addresses computed by `StoreNodeInstr.getInner` to identify field writes
|
||||
* and call `storeStep` accordingly (i.e., for an expression like `a.b.c = x`, we visit `c`, then
|
||||
* `b`, then `a`).
|
||||
* 2. Flow is transfered from a `WriteSideEffectInstruction` to a `StoreNodeOperand` after flow
|
||||
* returns to a caller. Flow will then proceed to the defining instruction of the operand (because
|
||||
* the `StoreNodeInstr` computed by `StoreNodeOperand.getInner()` is the `StoreNode` containing
|
||||
* the defining instruction), and then along the chain computed by `StoreNodeInstr.getInner` like
|
||||
* above.
|
||||
* In both cases, flow leaves a `StoreNode` once the entire chain has been traversed, and the shared
|
||||
* SSA library is used to find the next use of the variable at the end of the chain.
|
||||
* - Flow can enter a `ReadNode` through an `OperandNode` that represents an address of some variable.
|
||||
* Flow will then proceed along the chain of addresses computed by `ReadNode.getOuter` (i.e., for an
|
||||
* expression like `use(a.b.c)` we visit `a`, then `b`, then `c`) and call `readStep` accordingly.
|
||||
* Once the entire chain has been traversed, flow is transferred to the load instruction that reads
|
||||
* the final address of the chain.
|
||||
* - Flow can enter a `SsaPhiNode` from an `InstructionNode`, a `StoreNode` or another `SsaPhiNode`
|
||||
* (in `toPhiNode`), depending on which node provided the previous definition of the underlying
|
||||
* variable. Flow leaves a `SsaPhiNode` (in `fromPhiNode`) by using the shared SSA library to
|
||||
* determine the next use of the variable.
|
||||
*/
|
||||
cached
|
||||
newtype TIRDataFlowNode =
|
||||
TInstructionNode(Instruction i) or
|
||||
TOperandNode(Operand op) or
|
||||
TVariableNode(Variable var) or
|
||||
TStoreNodeInstr(Instruction i) { Ssa::explicitWrite(_, _, i) } or
|
||||
TStoreNodeOperand(ArgumentOperand op) { Ssa::explicitWrite(_, _, op.getDef()) } or
|
||||
TReadNode(Instruction i) { needsPostReadNode(i) } or
|
||||
TSsaPhiNode(Ssa::PhiNode phi)
|
||||
TVariableNode(Variable var)
|
||||
|
||||
cached
|
||||
predicate localFlowStepCached(Node nodeFrom, Node nodeTo) {
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
private predicate needsPostReadNode(Instruction iFrom) {
|
||||
// If the instruction generates an address that flows to a load.
|
||||
Ssa::addressFlowTC(iFrom, Ssa::getSourceAddress(_)) and
|
||||
(
|
||||
// And it is either a field address
|
||||
iFrom instanceof FieldAddressInstruction
|
||||
or
|
||||
// Or it is instruction that either uses or is used for an address that needs a post read node.
|
||||
exists(Instruction mid | needsPostReadNode(mid) |
|
||||
Ssa::addressFlow(mid, iFrom) or Ssa::addressFlow(iFrom, mid)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private import Cached
|
||||
@@ -239,234 +180,6 @@ class OperandNode extends Node, TOperandNode {
|
||||
override string toString() { result = this.getOperand().toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: do not use.
|
||||
*
|
||||
* A `StoreNode` is a node that has been (or is about to be) the
|
||||
* source or target of a `storeStep`.
|
||||
*/
|
||||
abstract private class StoreNode extends Node {
|
||||
/** Holds if this node should receive flow from `addr`. */
|
||||
abstract predicate flowInto(Instruction addr);
|
||||
|
||||
override Declaration getEnclosingCallable() { result = this.getFunction() }
|
||||
|
||||
/** Holds if this `StoreNode` is the root of the address computation used by a store operation. */
|
||||
predicate isTerminal() {
|
||||
not exists(this.getInner()) and
|
||||
not storeStep(this, _, _)
|
||||
}
|
||||
|
||||
/** Gets the store operation that uses the address computed by this `StoreNode`. */
|
||||
abstract Instruction getStoreInstruction();
|
||||
|
||||
/** Holds if the store operation associated with this `StoreNode` overwrites the entire variable. */
|
||||
final predicate isCertain() { Ssa::explicitWrite(true, this.getStoreInstruction(), _) }
|
||||
|
||||
/**
|
||||
* Gets the `StoreNode` that computes the address used by this `StoreNode`.
|
||||
*/
|
||||
abstract StoreNode getInner();
|
||||
|
||||
/** The inverse of `StoreNode.getInner`. */
|
||||
final StoreNode getOuter() { result.getInner() = this }
|
||||
}
|
||||
|
||||
class StoreNodeInstr extends StoreNode, TStoreNodeInstr {
|
||||
Instruction instr;
|
||||
|
||||
StoreNodeInstr() { this = TStoreNodeInstr(instr) }
|
||||
|
||||
override predicate flowInto(Instruction addr) { this.getInstruction() = addr }
|
||||
|
||||
/** Gets the underlying instruction. */
|
||||
Instruction getInstruction() { result = instr }
|
||||
|
||||
override Function getFunction() { result = this.getInstruction().getEnclosingFunction() }
|
||||
|
||||
override IRType getType() { result = this.getInstruction().getResultIRType() }
|
||||
|
||||
override Location getLocation() { result = this.getInstruction().getLocation() }
|
||||
|
||||
override string toString() {
|
||||
result = instructionNode(this.getInstruction()).toString() + " [store]"
|
||||
}
|
||||
|
||||
override Instruction getStoreInstruction() {
|
||||
Ssa::explicitWrite(_, result, this.getInstruction())
|
||||
}
|
||||
|
||||
override StoreNodeInstr getInner() {
|
||||
Ssa::addressFlow(result.getInstruction(), this.getInstruction())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To avoid having `PostUpdateNode`s with multiple pre-update nodes (which can cause performance
|
||||
* problems) we attach the `PostUpdateNode` that represent output arguments to an operand instead of
|
||||
* an instruction.
|
||||
*
|
||||
* To see why we need this, consider the expression `b->set(new C())`. The IR of this expression looks
|
||||
* like (simplified):
|
||||
* ```
|
||||
* r1(glval<unknown>) = FunctionAddress[set] :
|
||||
* r2(glval<unknown>) = FunctionAddress[operator new] :
|
||||
* r3(unsigned long) = Constant[8] :
|
||||
* r4(void *) = Call[operator new] : func:r2, 0:r3
|
||||
* r5(C *) = Convert : r4
|
||||
* r6(glval<unknown>) = FunctionAddress[C] :
|
||||
* v1(void) = Call[C] : func:r6, this:r5
|
||||
* v2(void) = Call[set] : func:r1, this:r0, 0:r5
|
||||
* ```
|
||||
*
|
||||
* Notice that both the call to `C` and the call to `set` will have an argument that is the
|
||||
* result of calling `operator new` (i.e., `r4`). If we only have `PostUpdateNode`s that are
|
||||
* instructions, both `PostUpdateNode`s would have `r4` as their pre-update node.
|
||||
*
|
||||
* We avoid this issue by having a `PostUpdateNode` for each argument, and let the pre-update node of
|
||||
* each `PostUpdateNode` be the argument _operand_, instead of the defining instruction.
|
||||
*/
|
||||
class StoreNodeOperand extends StoreNode, TStoreNodeOperand {
|
||||
ArgumentOperand operand;
|
||||
|
||||
StoreNodeOperand() { this = TStoreNodeOperand(operand) }
|
||||
|
||||
override predicate flowInto(Instruction addr) { this.getOperand().getDef() = addr }
|
||||
|
||||
/** Gets the underlying operand. */
|
||||
Operand getOperand() { result = operand }
|
||||
|
||||
override Function getFunction() { result = operand.getDef().getEnclosingFunction() }
|
||||
|
||||
override IRType getType() { result = operand.getIRType() }
|
||||
|
||||
override Location getLocation() { result = operand.getLocation() }
|
||||
|
||||
override string toString() { result = operandNode(this.getOperand()).toString() + " [store]" }
|
||||
|
||||
override WriteSideEffectInstruction getStoreInstruction() {
|
||||
Ssa::explicitWrite(_, result, operand.getDef())
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of `StoreNodeOperand.getInner` is the `StoreNodeInstr` representation the instruction
|
||||
* that defines this operand. This means the graph of `getInner` looks like this:
|
||||
* ```
|
||||
* I---I---I
|
||||
* \ \ \
|
||||
* O O O
|
||||
* ```
|
||||
* where each `StoreNodeOperand` "hooks" into the chain computed by `StoreNodeInstr.getInner`.
|
||||
* This means that the chain of `getInner` calls on the argument `&o.f` on an expression
|
||||
* like `func(&o.f)` is:
|
||||
* ```
|
||||
* r4---r3---r2
|
||||
* \
|
||||
* 0:r4
|
||||
* ```
|
||||
* where the IR for `func(&o.f)` looks like (simplified):
|
||||
* ```
|
||||
* r1(glval<unknown>) = FunctionAddress[func] :
|
||||
* r2(glval<O>) = VariableAddress[o] :
|
||||
* r3(glval<int>) = FieldAddress[f] : r2
|
||||
* r4(int *) = CopyValue : r3
|
||||
* v1(void) = Call[func] : func:r1, 0:r4
|
||||
* ```
|
||||
*/
|
||||
override StoreNodeInstr getInner() { operand.getDef() = result.getInstruction() }
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: do not use.
|
||||
*
|
||||
* A `ReadNode` is a node that has been (or is about to be) the
|
||||
* source or target of a `readStep`.
|
||||
*/
|
||||
class ReadNode extends Node, TReadNode {
|
||||
Instruction i;
|
||||
|
||||
ReadNode() { this = TReadNode(i) }
|
||||
|
||||
/** Gets the underlying instruction. */
|
||||
Instruction getInstruction() { result = i }
|
||||
|
||||
override Declaration getEnclosingCallable() { result = this.getFunction() }
|
||||
|
||||
override Function getFunction() { result = this.getInstruction().getEnclosingFunction() }
|
||||
|
||||
override IRType getType() { result = this.getInstruction().getResultIRType() }
|
||||
|
||||
override Location getLocation() { result = this.getInstruction().getLocation() }
|
||||
|
||||
override string toString() {
|
||||
result = instructionNode(this.getInstruction()).toString() + " [read]"
|
||||
}
|
||||
|
||||
/** Gets a load instruction that uses the address computed by this read node. */
|
||||
final Instruction getALoadInstruction() {
|
||||
Ssa::addressFlowTC(this.getInstruction(), Ssa::getSourceAddress(result))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a read node with an underlying instruction that is used by this
|
||||
* underlying instruction to compute an address of a load instruction.
|
||||
*/
|
||||
final ReadNode getInner() { Ssa::addressFlow(result.getInstruction(), this.getInstruction()) }
|
||||
|
||||
/** The inverse of `ReadNode.getInner`. */
|
||||
final ReadNode getOuter() { result.getInner() = this }
|
||||
|
||||
/** Holds if this read node computes a value that will not be used for any future read nodes. */
|
||||
final predicate isTerminal() {
|
||||
not exists(this.getOuter()) and
|
||||
not readStep(this, _, _)
|
||||
}
|
||||
|
||||
/** Holds if this read node computes a value that has not yet been used for any read operations. */
|
||||
final predicate isInitial() {
|
||||
not exists(this.getInner()) and
|
||||
not readStep(_, _, this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: do not use.
|
||||
*
|
||||
* A phi node produced by the shared SSA library, viewed as a node in a data flow graph.
|
||||
*/
|
||||
class SsaPhiNode extends Node, TSsaPhiNode {
|
||||
Ssa::PhiNode phi;
|
||||
|
||||
SsaPhiNode() { this = TSsaPhiNode(phi) }
|
||||
|
||||
/* Get the phi node associated with this node. */
|
||||
Ssa::PhiNode getPhiNode() { result = phi }
|
||||
|
||||
override Declaration getEnclosingCallable() { result = this.getFunction() }
|
||||
|
||||
override Function getFunction() { result = phi.getBasicBlock().getEnclosingFunction() }
|
||||
|
||||
override IRType getType() { result instanceof IRVoidType }
|
||||
|
||||
override Location getLocation() { result = phi.getBasicBlock().getLocation() }
|
||||
|
||||
/** Holds if this phi node has input from the `rnk`'th write operation in block `block`. */
|
||||
final predicate hasInputAtRankInBlock(IRBlock block, int rnk) {
|
||||
this.hasInputAtRankInBlock(block, rnk, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this phi node has input from the definition `input` (which is the `rnk`'th write
|
||||
* operation in block `block`).
|
||||
*/
|
||||
cached
|
||||
final predicate hasInputAtRankInBlock(IRBlock block, int rnk, Ssa::Definition input) {
|
||||
Ssa::phiHasInputFromBlock(phi, input, _) and input.definesAt(_, block, rnk)
|
||||
}
|
||||
|
||||
override string toString() { result = "Phi" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression, viewed as a node in a data flow graph.
|
||||
*/
|
||||
@@ -490,6 +203,19 @@ class ExprNode extends InstructionNode {
|
||||
override string toString() { result = this.asConvertedExpr().toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: do not use. Translates a parameter/argument index into a negative
|
||||
* number that denotes the index of its side effect (pointer indirection).
|
||||
*/
|
||||
bindingset[index]
|
||||
int getArgumentPosOfSideEffect(int index) {
|
||||
// -1 -> -2
|
||||
// 0 -> -3
|
||||
// 1 -> -4
|
||||
// ...
|
||||
result = -3 - index
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of a parameter at function entry, viewed as a node in a data
|
||||
* flow graph. This includes both explicit parameters such as `x` in `f(x)`
|
||||
@@ -512,7 +238,7 @@ class ParameterNode extends InstructionNode {
|
||||
* implicit `this` parameter is considered to have position `-1`, and
|
||||
* pointer-indirection parameters are at further negative positions.
|
||||
*/
|
||||
predicate isParameterOf(Function f, ParameterPosition pos) { none() } // overridden by subclasses
|
||||
predicate isParameterOf(Function f, int pos) { none() } // overridden by subclasses
|
||||
}
|
||||
|
||||
/** An explicit positional parameter, not including `this` or `...`. */
|
||||
@@ -521,8 +247,8 @@ private class ExplicitParameterNode extends ParameterNode {
|
||||
|
||||
ExplicitParameterNode() { exists(instr.getParameter()) }
|
||||
|
||||
override predicate isParameterOf(Function f, ParameterPosition pos) {
|
||||
f.getParameter(pos.(DirectPosition).getIndex()) = instr.getParameter()
|
||||
override predicate isParameterOf(Function f, int pos) {
|
||||
f.getParameter(pos) = instr.getParameter()
|
||||
}
|
||||
|
||||
/** Gets the `Parameter` associated with this node. */
|
||||
@@ -537,8 +263,8 @@ class ThisParameterNode extends ParameterNode {
|
||||
|
||||
ThisParameterNode() { instr.getIRVariable() instanceof IRThisVariable }
|
||||
|
||||
override predicate isParameterOf(Function f, ParameterPosition pos) {
|
||||
pos.(DirectPosition).getIndex() = -1 and instr.getEnclosingFunction() = f
|
||||
override predicate isParameterOf(Function f, int pos) {
|
||||
pos = -1 and instr.getEnclosingFunction() = f
|
||||
}
|
||||
|
||||
override string toString() { result = "this" }
|
||||
@@ -548,12 +274,12 @@ class ThisParameterNode extends ParameterNode {
|
||||
class ParameterIndirectionNode extends ParameterNode {
|
||||
override InitializeIndirectionInstruction instr;
|
||||
|
||||
override predicate isParameterOf(Function f, ParameterPosition pos) {
|
||||
override predicate isParameterOf(Function f, int pos) {
|
||||
exists(int index |
|
||||
instr.getEnclosingFunction() = f and
|
||||
instr.hasIndex(index)
|
||||
|
|
||||
pos.(IndirectionPosition).getIndex() = index
|
||||
pos = getArgumentPosOfSideEffect(index)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -587,14 +313,15 @@ deprecated class UninitializedNode extends Node {
|
||||
* Nodes corresponding to AST elements, for example `ExprNode`, usually refer
|
||||
* to the value before the update with the exception of `ClassInstanceExpr`,
|
||||
* which represents the value after the constructor has run.
|
||||
*
|
||||
* This class exists to match the interface used by Java. There are currently no non-abstract
|
||||
* classes that extend it. When we implement field flow, we can revisit this.
|
||||
*/
|
||||
abstract class PostUpdateNode extends Node {
|
||||
abstract class PostUpdateNode extends InstructionNode {
|
||||
/**
|
||||
* Gets the node before the state update.
|
||||
*/
|
||||
abstract Node getPreUpdateNode();
|
||||
|
||||
override string toString() { result = this.getPreUpdateNode() + " [post update]" }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -605,7 +332,7 @@ abstract class PostUpdateNode extends Node {
|
||||
* value, but does not necessarily replace it entirely. For example:
|
||||
* ```
|
||||
* x.y = 1; // a partial definition of the object `x`.
|
||||
* x.y.z = 1; // a partial definition of the object `x.y` and `x`.
|
||||
* x.y.z = 1; // a partial definition of the object `x.y`.
|
||||
* x.setY(1); // a partial definition of the object `x`.
|
||||
* setY(&x); // a partial definition of the object `x`.
|
||||
* ```
|
||||
@@ -614,34 +341,135 @@ abstract private class PartialDefinitionNode extends PostUpdateNode {
|
||||
abstract Expr getDefinedExpr();
|
||||
}
|
||||
|
||||
private class FieldPartialDefinitionNode extends PartialDefinitionNode, StoreNodeInstr {
|
||||
FieldPartialDefinitionNode() {
|
||||
this.getInstruction() = any(FieldAddressInstruction fai).getObjectAddress()
|
||||
private class ExplicitFieldStoreQualifierNode extends PartialDefinitionNode {
|
||||
override ChiInstruction instr;
|
||||
StoreInstruction store;
|
||||
|
||||
ExplicitFieldStoreQualifierNode() {
|
||||
not instr.isResultConflated() and
|
||||
instr.getPartial() = store and
|
||||
(
|
||||
instr.getUpdatedInterval(_, _) or
|
||||
store.getDestinationAddress() instanceof FieldAddressInstruction
|
||||
)
|
||||
}
|
||||
|
||||
override Node getPreUpdateNode() { result.asInstruction() = this.getInstruction() }
|
||||
|
||||
override Expr getDefinedExpr() { result = this.getInstruction().getUnconvertedResultExpression() }
|
||||
|
||||
override string toString() { result = PartialDefinitionNode.super.toString() }
|
||||
}
|
||||
|
||||
private class NonPartialDefinitionPostUpdate extends PostUpdateNode, StoreNodeInstr {
|
||||
NonPartialDefinitionPostUpdate() { not this instanceof PartialDefinitionNode }
|
||||
|
||||
override Node getPreUpdateNode() { result.asInstruction() = this.getInstruction() }
|
||||
|
||||
override string toString() { result = PostUpdateNode.super.toString() }
|
||||
}
|
||||
|
||||
private class ArgumentPostUpdateNode extends PartialDefinitionNode, StoreNodeOperand {
|
||||
override ArgumentNode getPreUpdateNode() { result.asOperand() = operand }
|
||||
// By using an operand as the result of this predicate we avoid the dataflow inconsistency errors
|
||||
// caused by having multiple nodes sharing the same pre update node. This inconsistency error can cause
|
||||
// a tuple explosion in the big step dataflow relation since it can make many nodes be the entry node
|
||||
// into a big step.
|
||||
override Node getPreUpdateNode() { result.asOperand() = instr.getTotalOperand() }
|
||||
|
||||
override Expr getDefinedExpr() {
|
||||
result = this.getOperand().getDef().getUnconvertedResultExpression()
|
||||
result =
|
||||
store
|
||||
.getDestinationAddress()
|
||||
.(FieldAddressInstruction)
|
||||
.getObjectAddress()
|
||||
.getUnconvertedResultExpression()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Not every store instruction generates a chi instruction that we can attach a PostUpdateNode to.
|
||||
* For instance, an update to a field of a struct containing only one field. Even if the store does
|
||||
* have a chi instruction, a subsequent use of the result of the store may be linked directly to the
|
||||
* result of the store as an inexact definition if the store totally overlaps the use. For these
|
||||
* cases we attach the PostUpdateNode to the store instruction. There's no obvious pre update node
|
||||
* for this case (as the entire memory is updated), so `getPreUpdateNode` is implemented as
|
||||
* `none()`.
|
||||
*/
|
||||
private class ExplicitSingleFieldStoreQualifierNode extends PartialDefinitionNode {
|
||||
override StoreInstruction instr;
|
||||
|
||||
ExplicitSingleFieldStoreQualifierNode() {
|
||||
(
|
||||
instr.getAUse().isDefinitionInexact()
|
||||
or
|
||||
not exists(ChiInstruction chi | chi.getPartial() = instr)
|
||||
) and
|
||||
// Without this condition any store would create a `PostUpdateNode`.
|
||||
instr.getDestinationAddress() instanceof FieldAddressInstruction
|
||||
}
|
||||
|
||||
override string toString() { result = PartialDefinitionNode.super.toString() }
|
||||
override Node getPreUpdateNode() { none() }
|
||||
|
||||
override Expr getDefinedExpr() {
|
||||
result =
|
||||
instr
|
||||
.getDestinationAddress()
|
||||
.(FieldAddressInstruction)
|
||||
.getObjectAddress()
|
||||
.getUnconvertedResultExpression()
|
||||
}
|
||||
}
|
||||
|
||||
private FieldAddressInstruction getFieldInstruction(Instruction instr) {
|
||||
result = instr or
|
||||
result = instr.(CopyValueInstruction).getUnary()
|
||||
}
|
||||
|
||||
/**
|
||||
* The target of a `fieldStoreStepAfterArraySuppression` store step, which is used to convert
|
||||
* an `ArrayContent` to a `FieldContent` when the `WriteSideEffect` instruction stores
|
||||
* into a field. See the QLDoc for `suppressArrayRead` for an example of where such a conversion
|
||||
* is inserted.
|
||||
*/
|
||||
private class WriteSideEffectFieldStoreQualifierNode extends PartialDefinitionNode {
|
||||
override ChiInstruction instr;
|
||||
WriteSideEffectInstruction write;
|
||||
FieldAddressInstruction field;
|
||||
|
||||
WriteSideEffectFieldStoreQualifierNode() {
|
||||
not instr.isResultConflated() and
|
||||
instr.getPartial() = write and
|
||||
field = getFieldInstruction(write.getDestinationAddress())
|
||||
}
|
||||
|
||||
override Node getPreUpdateNode() { result.asOperand() = instr.getTotalOperand() }
|
||||
|
||||
override Expr getDefinedExpr() {
|
||||
result = field.getObjectAddress().getUnconvertedResultExpression()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `PostUpdateNode` that is the target of a `arrayStoreStepChi` store step. The overriden
|
||||
* `ChiInstruction` corresponds to the instruction represented by `node2` in `arrayStoreStepChi`.
|
||||
*/
|
||||
private class ArrayStoreNode extends PartialDefinitionNode {
|
||||
override ChiInstruction instr;
|
||||
PointerAddInstruction add;
|
||||
|
||||
ArrayStoreNode() {
|
||||
not instr.isResultConflated() and
|
||||
exists(StoreInstruction store |
|
||||
instr.getPartial() = store and
|
||||
add = store.getDestinationAddress()
|
||||
)
|
||||
}
|
||||
|
||||
override Node getPreUpdateNode() { result.asOperand() = instr.getTotalOperand() }
|
||||
|
||||
override Expr getDefinedExpr() { result = add.getLeft().getUnconvertedResultExpression() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `PostUpdateNode` that is the target of a `arrayStoreStepChi` store step. The overriden
|
||||
* `ChiInstruction` corresponds to the instruction represented by `node2` in `arrayStoreStepChi`.
|
||||
*/
|
||||
private class PointerStoreNode extends PostUpdateNode {
|
||||
override ChiInstruction instr;
|
||||
|
||||
PointerStoreNode() {
|
||||
not instr.isResultConflated() and
|
||||
exists(StoreInstruction store |
|
||||
instr.getPartial() = store and
|
||||
store.getDestinationAddress().(CopyValueInstruction).getUnary() instanceof LoadInstruction
|
||||
)
|
||||
}
|
||||
|
||||
override Node getPreUpdateNode() { result.asOperand() = instr.getTotalOperand() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -720,11 +548,6 @@ class VariableNode extends Node, TVariableNode {
|
||||
*/
|
||||
InstructionNode instructionNode(Instruction instr) { result.getInstruction() = instr }
|
||||
|
||||
/**
|
||||
* Gets the node corresponding to `operand`.
|
||||
*/
|
||||
OperandNode operandNode(Operand operand) { result.getOperand() = operand }
|
||||
|
||||
/**
|
||||
* DEPRECATED: use `definitionByReferenceNodeFromArgument` instead.
|
||||
*
|
||||
@@ -791,174 +614,59 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
or
|
||||
// Instruction -> Operand flow
|
||||
simpleOperandLocalFlowStep(nodeFrom.asInstruction(), nodeTo.asOperand())
|
||||
or
|
||||
// Flow into, through, and out of store nodes
|
||||
StoreNodeFlow::flowInto(nodeFrom.asInstruction(), nodeTo)
|
||||
or
|
||||
StoreNodeFlow::flowThrough(nodeFrom, nodeTo)
|
||||
or
|
||||
StoreNodeFlow::flowOutOf(nodeFrom, nodeTo)
|
||||
or
|
||||
// Flow into, through, and out of read nodes
|
||||
ReadNodeFlow::flowInto(nodeFrom, nodeTo)
|
||||
or
|
||||
ReadNodeFlow::flowThrough(nodeFrom, nodeTo)
|
||||
or
|
||||
ReadNodeFlow::flowOutOf(nodeFrom, nodeTo)
|
||||
or
|
||||
// Adjacent-def-use and adjacent-use-use flow
|
||||
adjacentDefUseFlow(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
private predicate adjacentDefUseFlow(Node nodeFrom, Node nodeTo) {
|
||||
// Flow that isn't already covered by field flow out of store/read nodes.
|
||||
not nodeFrom.asInstruction() = any(StoreNode pun).getStoreInstruction() and
|
||||
not nodeFrom.asInstruction() = any(ReadNode pun).getALoadInstruction() and
|
||||
(
|
||||
//Def-use flow
|
||||
Ssa::ssaFlow(nodeFrom, nodeTo)
|
||||
or
|
||||
// Use-use flow through stores.
|
||||
exists(Instruction loadAddress, Node store |
|
||||
loadAddress = Ssa::getSourceAddressFromNode(nodeFrom) and
|
||||
Ssa::explicitWrite(_, store.asInstruction(), loadAddress) and
|
||||
Ssa::ssaFlow(store, nodeTo)
|
||||
)
|
||||
pragma[noinline]
|
||||
private predicate getFieldSizeOfClass(Class c, Type type, int size) {
|
||||
exists(Field f |
|
||||
f.getDeclaringType() = c and
|
||||
f.getUnderlyingType() = type and
|
||||
type.getSize() = size
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*/
|
||||
module ReadNodeFlow {
|
||||
/** Holds if the read node `nodeTo` should receive flow from `nodeFrom`. */
|
||||
predicate flowInto(Node nodeFrom, ReadNode nodeTo) {
|
||||
nodeTo.isInitial() and
|
||||
(
|
||||
// If we entered through an address operand.
|
||||
nodeFrom.asOperand().getDef() = nodeTo.getInstruction()
|
||||
or
|
||||
// If we entered flow through a memory-producing instruction.
|
||||
// This can happen if we have flow to an `InitializeParameterIndirection` through
|
||||
// a `ReadSideEffectInstruction`.
|
||||
exists(Instruction load, Instruction def |
|
||||
def = nodeFrom.asInstruction() and
|
||||
def = Ssa::getSourceValueOperand(load).getAnyDef() and
|
||||
not def = any(StoreNode store).getStoreInstruction() and
|
||||
pragma[only_bind_into](nodeTo).getALoadInstruction() = load
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the read node `nodeTo` should receive flow from the read node `nodeFrom`.
|
||||
*
|
||||
* This happens when `readFrom` is _not_ the source of a `readStep`, and `nodeTo` is
|
||||
* the `ReadNode` that represents an address that directly depends on `nodeFrom`.
|
||||
*/
|
||||
predicate flowThrough(ReadNode nodeFrom, ReadNode nodeTo) {
|
||||
not readStep(nodeFrom, _, _) and
|
||||
nodeFrom.getOuter() = nodeTo
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow should leave the read node `nFrom` and enter the node `nodeTo`.
|
||||
* This happens either because there is use-use flow from one of the variables used in
|
||||
* the read operation, or because we have traversed all the field dereferences in the
|
||||
* read operation.
|
||||
*/
|
||||
predicate flowOutOf(ReadNode nFrom, Node nodeTo) {
|
||||
// Use-use flow to another use of the same variable instruction
|
||||
Ssa::ssaFlow(nFrom, nodeTo)
|
||||
or
|
||||
not exists(nFrom.getInner()) and
|
||||
exists(Node store |
|
||||
Ssa::explicitWrite(_, store.asInstruction(), nFrom.getInstruction()) and
|
||||
Ssa::ssaFlow(store, nodeTo)
|
||||
)
|
||||
or
|
||||
// Flow out of read nodes and into memory instructions if we cannot move any further through
|
||||
// read nodes.
|
||||
nFrom.isTerminal() and
|
||||
(
|
||||
exists(Instruction load |
|
||||
load = nodeTo.asInstruction() and
|
||||
Ssa::getSourceAddress(load) = nFrom.getInstruction()
|
||||
)
|
||||
or
|
||||
exists(CallInstruction call, int i |
|
||||
call.getArgument(i) = nodeTo.asInstruction() and
|
||||
call.getArgument(i) = nFrom.getInstruction()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*/
|
||||
module StoreNodeFlow {
|
||||
/** Holds if the store node `nodeTo` should receive flow from `nodeFrom`. */
|
||||
predicate flowInto(Instruction instrFrom, StoreNode nodeTo) {
|
||||
nodeTo.flowInto(Ssa::getDestinationAddress(instrFrom))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the store node `nodeTo` should receive flow from `nodeFom`.
|
||||
*
|
||||
* This happens when `nodeFrom` is _not_ the source of a `storeStep`, and `nodeFrom` is
|
||||
* the `Storenode` that represents an address that directly depends on `nodeTo`.
|
||||
*/
|
||||
predicate flowThrough(StoreNode nodeFrom, StoreNode nodeTo) {
|
||||
// Flow through a post update node that doesn't need a store step.
|
||||
not storeStep(nodeFrom, _, _) and
|
||||
nodeTo.getOuter() = nodeFrom
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow should leave the store node `nodeFrom` and enter the node `nodeTo`.
|
||||
* This happens because we have traversed an entire chain of field dereferences
|
||||
* after a store operation.
|
||||
*/
|
||||
predicate flowOutOf(StoreNodeInstr nFrom, Node nodeTo) {
|
||||
nFrom.isTerminal() and
|
||||
Ssa::ssaFlow(nFrom, nodeTo)
|
||||
}
|
||||
private predicate isSingleFieldClass(Type type, Operand op) {
|
||||
exists(int size, Class c |
|
||||
c = op.getType().getUnderlyingType() and
|
||||
c.getSize() = size and
|
||||
getFieldSizeOfClass(c, type, size)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate simpleOperandLocalFlowStep(Instruction iFrom, Operand opTo) {
|
||||
// Propagate flow from an instruction to its exact uses.
|
||||
// We do this for all instruction/operand pairs, except when the operand is the
|
||||
// side effect operand of a ReturnIndirectionInstruction, or the load operand of a LoadInstruction.
|
||||
// This is because we get these flows through the shared SSA library already, and including this
|
||||
// flow here will create multiple dataflow paths which creates a blowup in stage 3 of dataflow.
|
||||
(
|
||||
not any(ReturnIndirectionInstruction ret).getSideEffectOperand() = opTo and
|
||||
not any(LoadInstruction load).getSourceValueOperand() = opTo and
|
||||
not any(ReturnValueInstruction ret).getReturnValueOperand() = opTo
|
||||
) and
|
||||
opTo.getDef() = iFrom
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate getAddressType(LoadInstruction load, Type t) {
|
||||
exists(Instruction address |
|
||||
address = load.getSourceAddress() and
|
||||
t = address.getResultType()
|
||||
or
|
||||
opTo = any(ReadSideEffectInstruction read).getSideEffectOperand() and
|
||||
not iFrom.isResultConflated() and
|
||||
iFrom = opTo.getAnyDef()
|
||||
or
|
||||
// Loading a single `int` from an `int *` parameter is not an exact load since
|
||||
// the parameter may point to an entire array rather than a single `int`. The
|
||||
// following rule ensures that any flow going into the
|
||||
// `InitializeIndirectionInstruction`, even if it's for a different array
|
||||
// element, will propagate to a load of the first element.
|
||||
//
|
||||
// Since we're linking `InitializeIndirectionInstruction` and
|
||||
// `LoadInstruction` together directly, this rule will break if there's any
|
||||
// reassignment of the parameter indirection, including a conditional one that
|
||||
// leads to a phi node.
|
||||
exists(InitializeIndirectionInstruction init |
|
||||
iFrom = init and
|
||||
opTo.(LoadOperand).getAnyDef() = init and
|
||||
// Check that the types match. Otherwise we can get flow from an object to
|
||||
// its fields, which leads to field conflation when there's flow from other
|
||||
// fields to the object elsewhere.
|
||||
init.getParameter().getType().getUnspecifiedType().(DerivedType).getBaseType() =
|
||||
opTo.getType().getUnspecifiedType()
|
||||
)
|
||||
or
|
||||
// Flow from stores to structs with a single field to a load of that field.
|
||||
exists(LoadInstruction load |
|
||||
load.getSourceValueOperand() = opTo and
|
||||
opTo.getAnyDef() = iFrom and
|
||||
isSingleFieldClass(pragma[only_bind_out](pragma[only_bind_out](iFrom).getResultType()), opTo)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Like the AST dataflow library, we want to conflate the address and value of a reference. This class
|
||||
* represents the `LoadInstruction` that is generated from a reference dereference.
|
||||
*/
|
||||
private class ReferenceDereferenceInstruction extends LoadInstruction {
|
||||
ReferenceDereferenceInstruction() {
|
||||
exists(ReferenceType ref |
|
||||
getAddressType(this, ref) and
|
||||
this.getResultType() = ref.getBaseType()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private predicate simpleInstructionLocalFlowStep(Operand opFrom, Instruction iTo) {
|
||||
@@ -973,8 +681,40 @@ private predicate simpleInstructionLocalFlowStep(Operand opFrom, Instruction iTo
|
||||
or
|
||||
iTo.(InheritanceConversionInstruction).getUnaryOperand() = opFrom
|
||||
or
|
||||
// Conflate references and values like in AST dataflow.
|
||||
iTo.(ReferenceDereferenceInstruction).getSourceAddressOperand() = opFrom
|
||||
// A chi instruction represents a point where a new value (the _partial_
|
||||
// operand) may overwrite an old value (the _total_ operand), but the alias
|
||||
// analysis couldn't determine that it surely will overwrite every bit of it or
|
||||
// that it surely will overwrite no bit of it.
|
||||
//
|
||||
// By allowing flow through the total operand, we ensure that flow is not lost
|
||||
// due to shortcomings of the alias analysis. We may get false flow in cases
|
||||
// where the data is indeed overwritten.
|
||||
//
|
||||
// Flow through the partial operand belongs in the taint-tracking libraries
|
||||
// for now.
|
||||
iTo.getAnOperand().(ChiTotalOperand) = opFrom
|
||||
or
|
||||
// Add flow from write side-effects to non-conflated chi instructions through their
|
||||
// partial operands. From there, a `readStep` will find subsequent reads of that field.
|
||||
// Consider the following example:
|
||||
// ```
|
||||
// void setX(Point* p, int new_x) {
|
||||
// p->x = new_x;
|
||||
// }
|
||||
// ...
|
||||
// setX(&p, taint());
|
||||
// ```
|
||||
// Here, a `WriteSideEffectInstruction` will provide a new definition for `p->x` after the call to
|
||||
// `setX`, which will be melded into `p` through a chi instruction.
|
||||
exists(ChiInstruction chi | chi = iTo |
|
||||
opFrom.getAnyDef() instanceof WriteSideEffectInstruction and
|
||||
chi.getPartialOperand() = opFrom and
|
||||
not chi.isResultConflated() and
|
||||
// In a call such as `set_value(&x->val);` we don't want the memory representing `x` to receive
|
||||
// dataflow by a simple step. Instead, this is handled by field flow. If we add a simple step here
|
||||
// we can get field-to-object flow.
|
||||
not chi.isPartialUpdate()
|
||||
)
|
||||
or
|
||||
// Flow through modeled functions
|
||||
modelFlow(opFrom, iTo)
|
||||
@@ -1048,14 +788,25 @@ predicate localInstructionFlow(Instruction e1, Instruction e2) {
|
||||
*/
|
||||
predicate localExprFlow(Expr e1, Expr e2) { localFlow(exprNode(e1), exprNode(e2)) }
|
||||
|
||||
/**
|
||||
* Gets a field corresponding to the bit range `[startBit..endBit)` of class `c`, if any.
|
||||
*/
|
||||
private Field getAField(Class c, int startBit, int endBit) {
|
||||
result.getDeclaringType() = c and
|
||||
startBit = 8 * result.getByteOffset() and
|
||||
endBit = 8 * result.getType().getSize() + startBit
|
||||
or
|
||||
exists(Field f, Class cInner |
|
||||
f = c.getAField() and
|
||||
cInner = f.getUnderlyingType() and
|
||||
result = getAField(cInner, startBit - 8 * f.getByteOffset(), endBit - 8 * f.getByteOffset())
|
||||
)
|
||||
}
|
||||
|
||||
private newtype TContent =
|
||||
TFieldContent(Field f) {
|
||||
// As reads and writes to union fields can create flow even though the reads and writes
|
||||
// target different fields, we don't want a read (write) to create a read (write) step.
|
||||
not f.getDeclaringType() instanceof Union
|
||||
} or
|
||||
TCollectionContent() or // Not used in C/C++
|
||||
TArrayContent() // Not used in C/C++.
|
||||
TFieldContent(Class c, int startBit, int endBit) { exists(getAField(c, startBit, endBit)) } or
|
||||
TCollectionContent() or
|
||||
TArrayContent()
|
||||
|
||||
/**
|
||||
* A description of the way data may be stored inside an object. Examples
|
||||
@@ -1073,13 +824,18 @@ class Content extends TContent {
|
||||
|
||||
/** A reference through an instance field. */
|
||||
class FieldContent extends Content, TFieldContent {
|
||||
Field f;
|
||||
Class c;
|
||||
int startBit;
|
||||
int endBit;
|
||||
|
||||
FieldContent() { this = TFieldContent(f) }
|
||||
FieldContent() { this = TFieldContent(c, startBit, endBit) }
|
||||
|
||||
override string toString() { result = f.toString() }
|
||||
// Ensure that there's just 1 result for `toString`.
|
||||
override string toString() { result = min(Field f | f = this.getAField() | f.toString()) }
|
||||
|
||||
Field getField() { result = f }
|
||||
predicate hasOffset(Class cl, int start, int end) { cl = c and start = startBit and end = endBit }
|
||||
|
||||
Field getAField() { result = getAField(c, startBit, endBit) }
|
||||
}
|
||||
|
||||
/** A reference through an array. */
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user