mirror of
https://github.com/github/codeql.git
synced 2026-05-18 05:07:06 +02:00
Compare commits
9 Commits
mbg/csharp
...
java/stres
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d227a018e6 | ||
|
|
606b836af4 | ||
|
|
709d0c0c8d | ||
|
|
83e2cbee82 | ||
|
|
777feccaea | ||
|
|
927d017f3d | ||
|
|
11636862f6 | ||
|
|
3b77a1d24f | ||
|
|
798f4efcfb |
120
.github/actions/cache-query-compilation/action.yml
vendored
120
.github/actions/cache-query-compilation/action.yml
vendored
@@ -9,7 +9,7 @@ inputs:
|
||||
outputs:
|
||||
cache-dir:
|
||||
description: "The directory where the cache was stored"
|
||||
value: ${{ steps.output-compilation-dir.outputs.compdir }}
|
||||
value: ${{ steps.fill-compilation-dir.outputs.compdir }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
@@ -23,127 +23,33 @@ runs:
|
||||
run: |
|
||||
MERGE_BASE=$(git cat-file commit $GITHUB_SHA | grep '^parent ' | head -1 | cut -f 2 -d " ")
|
||||
echo "merge_base=$MERGE_BASE" >> $GITHUB_ENV
|
||||
- name: Restore cache (PR)
|
||||
- name: Restore read-only cache (PR)
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
uses: actions/cache/restore@v3
|
||||
uses: erik-krogh/actions-cache@a88d0603fe5fb5606db9f002dfcadeb32b5f84c6
|
||||
with:
|
||||
path: |
|
||||
**/.cache
|
||||
~/.codeql/compile-cache
|
||||
path: '**/.cache'
|
||||
read-only: true
|
||||
key: codeql-compile-${{ inputs.key }}-pr-${{ github.sha }}
|
||||
restore-keys: |
|
||||
codeql-compile-${{ inputs.key }}-${{ github.base_ref }}-${{ env.merge_base }}
|
||||
codeql-compile-${{ inputs.key }}-${{ github.base_ref }}-
|
||||
codeql-compile-${{ inputs.key }}-main-
|
||||
- name: Fill cache (only branch push)
|
||||
- name: Fill cache (push)
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: actions/cache@v3
|
||||
uses: erik-krogh/actions-cache@a88d0603fe5fb5606db9f002dfcadeb32b5f84c6
|
||||
with:
|
||||
path: |
|
||||
**/.cache
|
||||
~/.codeql/compile-cache
|
||||
path: '**/.cache'
|
||||
key: codeql-compile-${{ inputs.key }}-${{ github.ref_name }}-${{ github.sha }} # just fill on main
|
||||
restore-keys: | # restore the latest cache if the exact cache is unavailable, to speed up compilation.
|
||||
codeql-compile-${{ inputs.key }}-${{ github.ref_name }}-
|
||||
codeql-compile-${{ inputs.key }}-main-
|
||||
- name: Output-compilationdir
|
||||
id: output-compilation-dir
|
||||
- name: Fill compilation cache directory
|
||||
id: fill-compilation-dir
|
||||
shell: bash
|
||||
run: |
|
||||
# Move all the existing cache into another folder, so we only preserve the cache for the current queries.
|
||||
node $GITHUB_WORKSPACE/.github/actions/cache-query-compilation/move-caches.js ${COMBINED_CACHE_DIR}
|
||||
|
||||
echo "compdir=${COMBINED_CACHE_DIR}" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
COMBINED_CACHE_DIR: ${{ runner.temp }}/compilation-dir
|
||||
- name: Fill compilation cache directory
|
||||
id: fill-compilation-dir
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
COMBINED_CACHE_DIR: ${{ runner.temp }}/compilation-dir
|
||||
with:
|
||||
script: |
|
||||
// # Move all the existing cache into another folder, so we only preserve the cache for the current queries.
|
||||
// mkdir -p ${COMBINED_CACHE_DIR}
|
||||
// rm -f **/.cache/{lock,size} # -f to avoid errors if the cache is empty.
|
||||
// # copy the contents of the .cache folders into the combined cache folder.
|
||||
// cp -r **/.cache/* ${COMBINED_CACHE_DIR}/ || : # ignore missing files
|
||||
// # clean up the .cache folders
|
||||
// rm -rf **/.cache/*
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const os = require("os");
|
||||
|
||||
// the first argv is the cache folder to create.
|
||||
const COMBINED_CACHE_DIR = process.env.COMBINED_CACHE_DIR;
|
||||
|
||||
function* walkCaches(dir) {
|
||||
const files = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const file of files) {
|
||||
if (file.isDirectory()) {
|
||||
const filePath = path.join(dir, file.name);
|
||||
yield* walkCaches(filePath);
|
||||
if (file.name === ".cache") {
|
||||
yield filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function copyDir(src, dest) {
|
||||
for await (const file of await fs.promises.readdir(src, { withFileTypes: true })) {
|
||||
const srcPath = path.join(src, file.name);
|
||||
const destPath = path.join(dest, file.name);
|
||||
if (file.isDirectory()) {
|
||||
if (!fs.existsSync(destPath)) {
|
||||
fs.mkdirSync(destPath);
|
||||
}
|
||||
await copyDir(srcPath, destPath);
|
||||
} else {
|
||||
await fs.promises.copyFile(srcPath, destPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const cacheDirs = [...walkCaches(".")];
|
||||
|
||||
for (const dir of cacheDirs) {
|
||||
console.log(`Found .cache dir at ${dir}`);
|
||||
}
|
||||
|
||||
const globalCacheDir = path.join(os.homedir(), ".codeql", "compile-cache");
|
||||
if (fs.existsSync(globalCacheDir)) {
|
||||
console.log("Found global home dir: " + globalCacheDir);
|
||||
cacheDirs.push(globalCacheDir);
|
||||
}
|
||||
|
||||
if (cacheDirs.length === 0) {
|
||||
console.log("No cache dirs found");
|
||||
return;
|
||||
}
|
||||
|
||||
// mkdir -p ${COMBINED_CACHE_DIR}
|
||||
fs.mkdirSync(COMBINED_CACHE_DIR, { recursive: true });
|
||||
|
||||
// rm -f **/.cache/{lock,size} # -f to avoid errors if the cache is empty.
|
||||
await Promise.all(
|
||||
cacheDirs.map((cacheDir) =>
|
||||
(async function () {
|
||||
await fs.promises.rm(path.join(cacheDir, "lock"), { force: true });
|
||||
await fs.promises.rm(path.join(cacheDir, "size"), { force: true });
|
||||
})()
|
||||
)
|
||||
);
|
||||
|
||||
// # copy the contents of the .cache folders into the combined cache folder.
|
||||
// cp -r **/.cache/* ${COMBINED_CACHE_DIR}/ || : # ignore missing files
|
||||
await Promise.all(
|
||||
cacheDirs.map((cacheDir) => copyDir(cacheDir, COMBINED_CACHE_DIR))
|
||||
);
|
||||
|
||||
// # clean up the .cache folders
|
||||
// rm -rf **/.cache/*
|
||||
await Promise.all(
|
||||
cacheDirs.map((cacheDir) => fs.promises.rm(cacheDir, { recursive: true }))
|
||||
);
|
||||
}
|
||||
main();
|
||||
|
||||
75
.github/actions/cache-query-compilation/move-caches.js
vendored
Normal file
75
.github/actions/cache-query-compilation/move-caches.js
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
// # Move all the existing cache into another folder, so we only preserve the cache for the current queries.
|
||||
// mkdir -p ${COMBINED_CACHE_DIR}
|
||||
// rm -f **/.cache/{lock,size} # -f to avoid errors if the cache is empty.
|
||||
// # copy the contents of the .cache folders into the combined cache folder.
|
||||
// cp -r **/.cache/* ${COMBINED_CACHE_DIR}/ || : # ignore missing files
|
||||
// # clean up the .cache folders
|
||||
// rm -rf **/.cache/*
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
// the first argv is the cache folder to create.
|
||||
const COMBINED_CACHE_DIR = process.argv[2];
|
||||
|
||||
function* walkCaches(dir) {
|
||||
const files = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const file of files) {
|
||||
if (file.isDirectory()) {
|
||||
const filePath = path.join(dir, file.name);
|
||||
yield* walkCaches(filePath);
|
||||
if (file.name === ".cache") {
|
||||
yield filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function copyDir(src, dest) {
|
||||
for await (const file of await fs.promises.readdir(src, { withFileTypes: true })) {
|
||||
const srcPath = path.join(src, file.name);
|
||||
const destPath = path.join(dest, file.name);
|
||||
if (file.isDirectory()) {
|
||||
if (!fs.existsSync(destPath)) {
|
||||
fs.mkdirSync(destPath);
|
||||
}
|
||||
await copyDir(srcPath, destPath);
|
||||
} else {
|
||||
await fs.promises.copyFile(srcPath, destPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const cacheDirs = [...walkCaches(".")];
|
||||
|
||||
for (const dir of cacheDirs) {
|
||||
console.log(`Found .cache dir at ${dir}`);
|
||||
}
|
||||
|
||||
// mkdir -p ${COMBINED_CACHE_DIR}
|
||||
fs.mkdirSync(COMBINED_CACHE_DIR, { recursive: true });
|
||||
|
||||
// rm -f **/.cache/{lock,size} # -f to avoid errors if the cache is empty.
|
||||
await Promise.all(
|
||||
cacheDirs.map((cacheDir) =>
|
||||
(async function () {
|
||||
await fs.promises.rm(path.join(cacheDir, "lock"), { force: true });
|
||||
await fs.promises.rm(path.join(cacheDir, "size"), { force: true });
|
||||
})()
|
||||
)
|
||||
);
|
||||
|
||||
// # copy the contents of the .cache folders into the combined cache folder.
|
||||
// cp -r **/.cache/* ${COMBINED_CACHE_DIR}/ || : # ignore missing files
|
||||
await Promise.all(
|
||||
cacheDirs.map((cacheDir) => copyDir(cacheDir, COMBINED_CACHE_DIR))
|
||||
);
|
||||
|
||||
// # clean up the .cache folders
|
||||
// rm -rf **/.cache/*
|
||||
await Promise.all(
|
||||
cacheDirs.map((cacheDir) => fs.promises.rm(cacheDir, { recursive: true }))
|
||||
);
|
||||
}
|
||||
main();
|
||||
2
.github/actions/fetch-codeql/action.yml
vendored
2
.github/actions/fetch-codeql/action.yml
vendored
@@ -19,6 +19,4 @@ runs:
|
||||
gh extension install github/gh-codeql
|
||||
gh codeql set-channel "$CHANNEL"
|
||||
gh codeql version
|
||||
printf "CODEQL_FETCHED_CODEQL_PATH=" >> "${GITHUB_ENV}"
|
||||
gh codeql version --format=json | jq -r .unpackedLocation >> "${GITHUB_ENV}"
|
||||
gh codeql version --format=json | jq -r .unpackedLocation >> "${GITHUB_PATH}"
|
||||
|
||||
26
.github/actions/find-latest-bundle/action.yml
vendored
Normal file
26
.github/actions/find-latest-bundle/action.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Find Latest CodeQL Bundle
|
||||
description: Finds the URL of the latest released version of the CodeQL bundle.
|
||||
outputs:
|
||||
url:
|
||||
description: The download URL of the latest CodeQL bundle release
|
||||
value: ${{ steps.find-latest.outputs.url }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Find Latest Release
|
||||
id: find-latest
|
||||
shell: pwsh
|
||||
run: |
|
||||
$Latest = gh release list --repo github/codeql-action --exclude-drafts --limit 1000 |
|
||||
ForEach-Object { $C = $_ -split "`t"; return @{ type = $C[1]; tag = $C[2]; } } |
|
||||
Where-Object { $_.type -eq 'Latest' }
|
||||
|
||||
$Tag = $Latest.tag
|
||||
if ($Tag -eq '') {
|
||||
throw 'Failed to find latest bundle release.'
|
||||
}
|
||||
|
||||
Write-Output "Latest bundle tag is '${Tag}'."
|
||||
"url=https://github.com/github/codeql-action/releases/download/${Tag}/codeql-bundle-linux64.tar.gz" >> $env:GITHUB_OUTPUT
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
32
.github/actions/os-version/action.yml
vendored
32
.github/actions/os-version/action.yml
vendored
@@ -1,32 +0,0 @@
|
||||
name: OS Version
|
||||
description: Get OS version.
|
||||
|
||||
outputs:
|
||||
version:
|
||||
description: "OS version"
|
||||
value: ${{ steps.version.outputs.version }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
. /etc/os-release
|
||||
echo "VERSION=${NAME} ${VERSION}" >> $GITHUB_ENV
|
||||
- if: runner.os == 'Windows'
|
||||
shell: powershell
|
||||
run: |
|
||||
$objects = systeminfo.exe /FO CSV | ConvertFrom-Csv
|
||||
"VERSION=$($objects.'OS Name') $($objects.'OS Version')" >> $env:GITHUB_ENV
|
||||
- if: runner.os == 'macOS'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "VERSION=$(sw_vers -productName) $(sw_vers -productVersion)" >> $GITHUB_ENV
|
||||
- name: Emit OS version
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
echo "$VERSION"
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
|
||||
13
.github/dependabot.yml
vendored
13
.github/dependabot.yml
vendored
@@ -1,12 +1,19 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "ruby"
|
||||
directory: "ruby/node-types"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "ql"
|
||||
directory: "ruby/generator"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "ruby/extractor"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "ruby/autobuilder"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
|
||||
11
.github/workflows/atm-check-query-suite.yml
vendored
11
.github/workflows/atm-check-query-suite.yml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
|
||||
jobs:
|
||||
atm-check-query-suite:
|
||||
runs-on: ubuntu-latest-xl
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -23,12 +23,6 @@ jobs:
|
||||
with:
|
||||
channel: release
|
||||
|
||||
- name: Cache compilation cache
|
||||
id: query-cache
|
||||
uses: ./.github/actions/cache-query-compilation
|
||||
with:
|
||||
key: atm-suite
|
||||
|
||||
- name: Install ATM model
|
||||
run: |
|
||||
set -exu
|
||||
@@ -56,13 +50,10 @@ jobs:
|
||||
echo "SARIF_PATH=${SARIF_PATH}" >> "${GITHUB_ENV}"
|
||||
|
||||
codeql database analyze \
|
||||
--threads=0 \
|
||||
--ram 50000 \
|
||||
--format sarif-latest \
|
||||
--output "${SARIF_PATH}" \
|
||||
--sarif-group-rules-by-pack \
|
||||
-vv \
|
||||
--compilation-cache "${{ steps.query-cache.outputs.cache-dir }}" \
|
||||
-- \
|
||||
"${DB_PATH}" \
|
||||
"${QUERY_PACK}/${QUERY_SUITE}"
|
||||
|
||||
6
.github/workflows/check-change-note.yml
vendored
6
.github/workflows/check-change-note.yml
vendored
@@ -26,9 +26,3 @@ jobs:
|
||||
run: |
|
||||
gh api 'repos/${{github.repository}}/pulls/${{github.event.number}}/files' --paginate --jq 'any(.[].filename ; test("/change-notes/.*[.]md$"))' |
|
||||
grep true -c
|
||||
- name: Fail if the change note filename doesn't match the expected format. The file name must be of the form 'YYYY-MM-DD.md' or 'YYYY-MM-DD-{title}.md', where '{title}' is arbitrary text.
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh api 'repos/${{github.repository}}/pulls/${{github.event.number}}/files' --paginate --jq '[.[].filename | select(test("/change-notes/.*[.]md$"))] | all(test("/change-notes/[0-9]{4}-[0-9]{2}-[0-9]{2}.*[.]md$"))' |
|
||||
grep true -c
|
||||
|
||||
3
.github/workflows/check-qldoc.yml
vendored
3
.github/workflows/check-qldoc.yml
vendored
@@ -26,8 +26,9 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
EXIT_CODE=0
|
||||
# TODO: remove the swift exception from the regex when we fix generated QLdoc
|
||||
# TODO: remove the shared exception from the regex when coverage of qlpacks without dbschemes is supported
|
||||
changed_lib_packs="$(git diff --name-only --diff-filter=ACMRT HEAD^ HEAD | { grep -Po '^(?!(shared))[a-z]*/ql/lib' || true; } | sort -u)"
|
||||
changed_lib_packs="$(git diff --name-only --diff-filter=ACMRT HEAD^ HEAD | { grep -Po '^(?!(swift|shared))[a-z]*/ql/lib' || true; } | sort -u)"
|
||||
for pack_dir in ${changed_lib_packs}; do
|
||||
lang="${pack_dir%/ql/lib}"
|
||||
codeql generate library-doc-coverage --output="${RUNNER_TEMP}/${lang}-current.txt" --dir="${pack_dir}"
|
||||
|
||||
2
.github/workflows/close-stale.yml
vendored
2
.github/workflows/close-stale.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v7
|
||||
- uses: actions/stale@v6
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'This issue is stale because it has been open 14 days with no activity. Comment or remove the `Stale` label in order to avoid having this issue closed in 7 days.'
|
||||
|
||||
4
.github/workflows/codeql-analysis.yml
vendored
4
.github/workflows/codeql-analysis.yml
vendored
@@ -28,9 +28,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: 7.0.102
|
||||
dotnet-version: 6.0.202
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
6
.github/workflows/compile-queries.yml
vendored
6
.github/workflows/compile-queries.yml
vendored
@@ -24,14 +24,14 @@ jobs:
|
||||
with:
|
||||
key: all-queries
|
||||
- name: check formatting
|
||||
run: find */ql -type f \( -name "*.qll" -o -name "*.ql" \) -print0 | xargs -0 -n 3000 -P 10 codeql query format -q --check-only
|
||||
run: find */ql -type f \( -name "*.qll" -o -name "*.ql" \) -print0 | xargs -0 codeql query format --check-only
|
||||
- name: compile queries - check-only
|
||||
# run with --check-only if running in a PR (github.sha != main)
|
||||
if : ${{ github.event_name == 'pull_request' }}
|
||||
shell: bash
|
||||
run: codeql query compile -q -j0 */ql/{src,examples} --keep-going --warnings=error --check-only --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}"
|
||||
run: codeql query compile -j0 */ql/{src,examples} --keep-going --warnings=error --check-only --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}"
|
||||
- name: compile queries - full
|
||||
# do full compile if running on main - this populates the cache
|
||||
if : ${{ github.event_name != 'pull_request' }}
|
||||
shell: bash
|
||||
run: codeql query compile -q -j0 */ql/{src,examples} --keep-going --warnings=error --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}"
|
||||
run: codeql query compile -j0 */ql/{src,examples} --keep-going --warnings=error --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}"
|
||||
|
||||
10
.github/workflows/csharp-qltest.yml
vendored
10
.github/workflows/csharp-qltest.yml
vendored
@@ -77,10 +77,10 @@ jobs:
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 7.0.102
|
||||
dotnet-version: 6.0.202
|
||||
- name: Extractor unit tests
|
||||
run: |
|
||||
dotnet test -p:RuntimeFrameworkVersion=7.0.2 "${{ github.workspace }}/csharp/extractor/Semmle.Util.Tests"
|
||||
dotnet test -p:RuntimeFrameworkVersion=7.0.2 "${{ github.workspace }}/csharp/extractor/Semmle.Extraction.Tests"
|
||||
dotnet test -p:RuntimeFrameworkVersion=7.0.2 "${{ github.workspace }}/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests"
|
||||
dotnet test -p:RuntimeFrameworkVersion=7.0.2 "${{ github.workspace }}/cpp/autobuilder/Semmle.Autobuild.Cpp.Tests"
|
||||
dotnet test -p:RuntimeFrameworkVersion=6.0.4 "${{ github.workspace }}/csharp/extractor/Semmle.Util.Tests"
|
||||
dotnet test -p:RuntimeFrameworkVersion=6.0.4 "${{ github.workspace }}/csharp/extractor/Semmle.Extraction.Tests"
|
||||
dotnet test -p:RuntimeFrameworkVersion=6.0.4 "${{ github.workspace }}/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests"
|
||||
dotnet test -p:RuntimeFrameworkVersion=6.0.4 "${{ github.workspace }}/cpp/autobuilder/Semmle.Autobuild.Cpp.Tests"
|
||||
|
||||
8
.github/workflows/go-tests-other-os.yml
vendored
8
.github/workflows/go-tests-other-os.yml
vendored
@@ -12,10 +12,10 @@ jobs:
|
||||
name: Test MacOS
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Set up Go 1.20
|
||||
- name: Set up Go 1.19
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.20.0
|
||||
go-version: 1.19
|
||||
id: go
|
||||
|
||||
- name: Check out code
|
||||
@@ -47,10 +47,10 @@ jobs:
|
||||
name: Test Windows
|
||||
runs-on: windows-latest-xl
|
||||
steps:
|
||||
- name: Set up Go 1.20
|
||||
- name: Set up Go 1.19
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.20.0
|
||||
go-version: 1.19
|
||||
id: go
|
||||
|
||||
- name: Check out code
|
||||
|
||||
4
.github/workflows/go-tests.yml
vendored
4
.github/workflows/go-tests.yml
vendored
@@ -20,10 +20,10 @@ jobs:
|
||||
name: Test Linux (Ubuntu)
|
||||
runs-on: ubuntu-latest-xl
|
||||
steps:
|
||||
- name: Set up Go 1.20
|
||||
- name: Set up Go 1.19
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.20.0
|
||||
go-version: 1.19
|
||||
id: go
|
||||
|
||||
- name: Check out code
|
||||
|
||||
10
.github/workflows/mad_modelDiff.yml
vendored
10
.github/workflows/mad_modelDiff.yml
vendored
@@ -11,7 +11,7 @@ on:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "java/ql/src/utils/modelgenerator/**/*.*"
|
||||
- "java/ql/src/utils/model-generator/**/*.*"
|
||||
- ".github/workflows/mad_modelDiff.yml"
|
||||
|
||||
permissions:
|
||||
@@ -40,12 +40,12 @@ jobs:
|
||||
- name: Download database
|
||||
env:
|
||||
SLUG: ${{ matrix.slug }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -x
|
||||
mkdir lib-dbs
|
||||
SHORTNAME=${SLUG//[^a-zA-Z0-9_]/}
|
||||
gh api -H "Accept: application/zip" "/repos/${SLUG}/code-scanning/codeql/databases/java" > "$SHORTNAME.zip"
|
||||
projectId=`curl -s https://lgtm.com/api/v1.0/projects/g/${SLUG} | jq .id`
|
||||
curl -L "https://lgtm.com/api/v1.0/snapshots/$projectId/java" -o "$SHORTNAME.zip"
|
||||
unzip -q -d "${SHORTNAME}-db" "${SHORTNAME}.zip"
|
||||
mkdir "lib-dbs/$SHORTNAME/"
|
||||
mv "${SHORTNAME}-db/"$(ls -1 "${SHORTNAME}"-db)/* "lib-dbs/${SHORTNAME}/"
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
DATABASE=$2
|
||||
cd codeql-$QL_VARIANT
|
||||
SHORTNAME=`basename $DATABASE`
|
||||
python java/ql/src/utils/modelgenerator/GenerateFlowModel.py --with-summaries --with-sinks $DATABASE ${SHORTNAME}.temp.model.yml
|
||||
python java/ql/src/utils/model-generator/GenerateFlowModel.py --with-summaries --with-sinks $DATABASE ${SHORTNAME}.temp.model.yml
|
||||
mv java/ql/lib/ext/generated/${SHORTNAME}.temp.model.yml $MODELS/${SHORTNAME}Generated_${QL_VARIANT}.model.yml
|
||||
cd ..
|
||||
}
|
||||
@@ -100,6 +100,4 @@ jobs:
|
||||
with:
|
||||
name: diffs
|
||||
path: tmp-models/*.html
|
||||
# An html file is only produced if the generated models differ.
|
||||
if-no-files-found: ignore
|
||||
retention-days: 20
|
||||
|
||||
2
.github/workflows/mad_regenerate-models.yml
vendored
2
.github/workflows/mad_regenerate-models.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
SLUG: ${{ matrix.slug }}
|
||||
run: |
|
||||
SHORTNAME=${SLUG//[^a-zA-Z0-9_]/}
|
||||
java/ql/src/utils/modelgenerator/RegenerateModels.py "${SLUG}" dbs/${SHORTNAME}
|
||||
java/ql/src/utils/model-generator/RegenerateModels.py "${SLUG}" dbs/${SHORTNAME}
|
||||
- name: Stage changes
|
||||
run: |
|
||||
find java -name "*.model.yml" -print0 | xargs -0 git add
|
||||
|
||||
159
.github/workflows/ql-for-ql-build.yml
vendored
159
.github/workflows/ql-for-ql-build.yml
vendored
@@ -5,6 +5,13 @@ on:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "ql/**"
|
||||
- "**.qll"
|
||||
- "**.ql"
|
||||
- "**.dbscheme"
|
||||
- "**/qlpack.yml"
|
||||
- ".github/workflows/ql-for-ql-build.yml"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -15,60 +22,142 @@ jobs:
|
||||
steps:
|
||||
### Build the queries ###
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Find latest bundle
|
||||
id: find-latest-bundle
|
||||
uses: ./.github/actions/find-latest-bundle
|
||||
- name: Find codeql
|
||||
id: find-codeql
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@77a8d2d10c0b403a8b4aadbd223dc489ecd22683
|
||||
with:
|
||||
languages: javascript # does not matter
|
||||
- uses: ./.github/actions/os-version
|
||||
id: os_version
|
||||
tools: ${{ steps.find-latest-bundle.outputs.url }}
|
||||
- name: Get CodeQL version
|
||||
id: get-codeql-version
|
||||
run: |
|
||||
echo "version=$("${CODEQL}" --version | head -n 1 | rev | cut -d " " -f 1 | rev)" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
- name: Cache entire pack
|
||||
id: cache-pack
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ runner.temp }}/pack
|
||||
key: ${{ runner.os }}-pack-${{ hashFiles('ql/**/Cargo.lock') }}-${{ hashFiles('ql/**/*.rs') }}-${{ hashFiles('ql/**/*.ql*') }}-${{ hashFiles('ql/**/qlpack.yml') }}-${{ hashFiles('ql/ql/src/ql.dbscheme*') }}-${{ steps.get-codeql-version.outputs.version }}--${{ hashFiles('.github/workflows/ql-for-ql-build.yml') }}
|
||||
- name: Cache queries
|
||||
if: steps.cache-pack.outputs.cache-hit != 'true'
|
||||
id: cache-queries
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ runner.temp }}/queries
|
||||
key: queries-${{ hashFiles('ql/**/*.ql*') }}-${{ hashFiles('ql/**/qlpack.yml') }}-${{ hashFiles('ql/ql/src/ql.dbscheme*') }}-${{ steps.get-codeql-version.outputs.version }}--${{ hashFiles('.github/workflows/ql-for-ql-build.yml') }}
|
||||
- name: Build query pack
|
||||
if: steps.cache-queries.outputs.cache-hit != 'true' && steps.cache-pack.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd ql/ql/src
|
||||
"${CODEQL}" pack create -j 16
|
||||
mv .codeql/pack/codeql/ql/0.0.0 ${{ runner.temp }}/queries
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
- name: Move cache queries to pack
|
||||
if: steps.cache-pack.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cp -r ${{ runner.temp }}/queries ${{ runner.temp }}/pack
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
|
||||
### Build the extractor ###
|
||||
- name: Cache entire extractor
|
||||
if: steps.cache-pack.outputs.cache-hit != 'true'
|
||||
id: cache-extractor
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
ql/extractor-pack/
|
||||
ql/target/release/buramu
|
||||
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-extractor-${{ hashFiles('ql/**/Cargo.lock') }}-${{ hashFiles('ql/**/*.rs') }}
|
||||
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'
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true' && steps.cache-pack.outputs.cache-hit != 'true'
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
ql/target
|
||||
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-rust-cargo-${{ hashFiles('ql/**/Cargo.lock') }}
|
||||
key: ${{ runner.os }}-rust-cargo-${{ hashFiles('ql/**/Cargo.lock') }}
|
||||
- name: Check formatting
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true' && steps.cache-pack.outputs.cache-hit != 'true'
|
||||
run: cd ql; cargo fmt --all -- --check
|
||||
- name: Build
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true' && steps.cache-pack.outputs.cache-hit != 'true'
|
||||
run: cd ql; cargo build --verbose
|
||||
- name: Run tests
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true' && steps.cache-pack.outputs.cache-hit != 'true'
|
||||
run: cd ql; cargo test --verbose
|
||||
- name: Release build
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
run: cd ql; ./scripts/create-extractor-pack.sh
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
- name: Cache compilation cache
|
||||
id: query-cache
|
||||
uses: ./.github/actions/cache-query-compilation
|
||||
with:
|
||||
key: run-ql-for-ql
|
||||
- name: Make database and analyze
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true' && steps.cache-pack.outputs.cache-hit != 'true'
|
||||
run: cd ql; cargo build --release
|
||||
- name: Generate dbscheme
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true' && steps.cache-pack.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
|
||||
|
||||
### Package the queries and extractor ###
|
||||
- name: Package pack
|
||||
if: steps.cache-pack.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
./ql/target/release/buramu | tee deprecated.blame # Add a blame file for the extractor to parse.
|
||||
${CODEQL} database create -l=ql --search-path ql/extractor-pack ${DB}
|
||||
${CODEQL} database analyze -j0 --format=sarif-latest --output=ql-for-ql.sarif ${DB} ql/ql/src/codeql-suites/ql-code-scanning.qls --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}"
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
DB: ${{ runner.temp }}/DB
|
||||
LGTM_INDEX_FILTERS: |
|
||||
exclude:ql/ql/test
|
||||
exclude:*/ql/lib/upgrades/
|
||||
exclude:java/ql/integration-tests
|
||||
- name: Upload sarif to code-scanning
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
cp -r ql/codeql-extractor.yml ql/tools ql/ql/src/ql.dbscheme.stats ${PACK}/
|
||||
mkdir -p ${PACK}/tools/linux64
|
||||
cp ql/target/release/ql-autobuilder ${PACK}/tools/linux64/autobuilder
|
||||
cp ql/target/release/ql-extractor ${PACK}/tools/linux64/extractor
|
||||
chmod +x ${PACK}/tools/linux64/autobuilder
|
||||
chmod +x ${PACK}/tools/linux64/extractor
|
||||
env:
|
||||
PACK: ${{ runner.temp }}/pack
|
||||
|
||||
### Run the analysis ###
|
||||
- name: Hack codeql-action options
|
||||
run: |
|
||||
JSON=$(jq -nc --arg pack "${PACK}" '.database."run-queries"=["--search-path", $pack] | .resolve.queries=["--search-path", $pack] | .resolve.extractor=["--search-path", $pack] | .resolve.languages=["--search-path", $pack] | .database.init=["--search-path", $pack]')
|
||||
echo "CODEQL_ACTION_EXTRA_OPTIONS=${JSON}" >> ${GITHUB_ENV}
|
||||
env:
|
||||
PACK: ${{ runner.temp }}/pack
|
||||
|
||||
- name: Create CodeQL config file
|
||||
run: |
|
||||
echo "paths-ignore:" >> ${CONF}
|
||||
echo " - ql/ql/test" >> ${CONF}
|
||||
echo " - \"*/ql/lib/upgrades/\"" >> ${CONF}
|
||||
echo "disable-default-queries: true" >> ${CONF}
|
||||
echo "queries:" >> ${CONF}
|
||||
echo " - uses: ./ql/ql/src/codeql-suites/ql-code-scanning.qls" >> ${CONF}
|
||||
echo "Config file: "
|
||||
cat ${CONF}
|
||||
env:
|
||||
CONF: ./ql-for-ql-config.yml
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@77a8d2d10c0b403a8b4aadbd223dc489ecd22683
|
||||
with:
|
||||
sarif_file: ql-for-ql.sarif
|
||||
category: ql-for-ql
|
||||
languages: ql
|
||||
db-location: ${{ runner.temp }}/db
|
||||
config-file: ./ql-for-ql-config.yml
|
||||
tools: ${{ steps.find-latest-bundle.outputs.url }}
|
||||
- name: Move pack cache
|
||||
run: |
|
||||
cp -r ${PACK}/.cache ql/ql/src/.cache
|
||||
env:
|
||||
PACK: ${{ runner.temp }}/pack
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@77a8d2d10c0b403a8b4aadbd223dc489ecd22683
|
||||
with:
|
||||
category: "ql-for-ql"
|
||||
- name: Copy sarif file to CWD
|
||||
run: cp ../results/ql.sarif ./ql-for-ql.sarif
|
||||
- name: Fixup the $scema in sarif # Until https://github.com/microsoft/sarif-vscode-extension/pull/436/ is part in a stable release
|
||||
run: |
|
||||
sed -i 's/\$schema.*/\$schema": "https:\/\/raw.githubusercontent.com\/oasis-tcs\/sarif-spec\/master\/Schemata\/sarif-schema-2.1.0",/' ql-for-ql.sarif
|
||||
- name: Sarif as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -83,4 +172,4 @@ jobs:
|
||||
with:
|
||||
name: ql-for-ql-langs
|
||||
path: split-sarif
|
||||
retention-days: 1
|
||||
retention-days: 1
|
||||
@@ -25,18 +25,16 @@ jobs:
|
||||
|
||||
- name: Find codeql
|
||||
id: find-codeql
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@77a8d2d10c0b403a8b4aadbd223dc489ecd22683
|
||||
with:
|
||||
languages: javascript # does not matter
|
||||
- uses: ./.github/actions/os-version
|
||||
id: os_version
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
ql/target
|
||||
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-qltest-cargo-${{ hashFiles('ql/**/Cargo.lock') }}
|
||||
key: ${{ runner.os }}-qltest-cargo-${{ hashFiles('ql/**/Cargo.lock') }}
|
||||
- name: Build Extractor
|
||||
run: cd ql; env "PATH=$PATH:`dirname ${CODEQL}`" ./scripts/create-extractor-pack.sh
|
||||
env:
|
||||
|
||||
70
.github/workflows/ql-for-ql-tests.yml
vendored
70
.github/workflows/ql-for-ql-tests.yml
vendored
@@ -6,13 +6,11 @@ on:
|
||||
paths:
|
||||
- "ql/**"
|
||||
- codeql-workspace.yml
|
||||
- .github/workflows/ql-for-ql-tests.yml
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "ql/**"
|
||||
- codeql-workspace.yml
|
||||
- .github/workflows/ql-for-ql-tests.yml
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -24,86 +22,28 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Find codeql
|
||||
id: find-codeql
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@77a8d2d10c0b403a8b4aadbd223dc489ecd22683
|
||||
with:
|
||||
languages: javascript # does not matter
|
||||
- uses: ./.github/actions/os-version
|
||||
id: os_version
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
ql/target
|
||||
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-qltest-cargo-${{ hashFiles('ql/rust-toolchain.toml', 'ql/**/Cargo.lock') }}
|
||||
- name: Check formatting
|
||||
run: cd ql; cargo fmt --all -- --check
|
||||
key: ${{ runner.os }}-qltest-cargo-${{ hashFiles('ql/**/Cargo.lock') }}
|
||||
- name: Build extractor
|
||||
run: |
|
||||
cd ql;
|
||||
codeqlpath=$(dirname ${{ steps.find-codeql.outputs.codeql-path }});
|
||||
env "PATH=$PATH:$codeqlpath" ./scripts/create-extractor-pack.sh
|
||||
- name: Cache compilation cache
|
||||
id: query-cache
|
||||
uses: ./.github/actions/cache-query-compilation
|
||||
with:
|
||||
key: ql-for-ql-tests
|
||||
- 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 --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}" ql/ql/test
|
||||
"${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 }}
|
||||
|
||||
other-os:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest]
|
||||
needs: [qltest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install GNU tar
|
||||
if: runner.os == 'macOS'
|
||||
- name: Check QL formatting
|
||||
run: |
|
||||
brew install gnu-tar
|
||||
echo "/usr/local/opt/gnu-tar/libexec/gnubin" >> $GITHUB_PATH
|
||||
- name: Find codeql
|
||||
id: find-codeql
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: javascript # does not matter
|
||||
- uses: ./.github/actions/os-version
|
||||
id: os_version
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
ql/target
|
||||
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-qltest-cargo-${{ hashFiles('ql/rust-toolchain.toml', 'ql/**/Cargo.lock') }}
|
||||
- name: Build extractor
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
cd ql;
|
||||
codeqlpath=$(dirname ${{ steps.find-codeql.outputs.codeql-path }});
|
||||
env "PATH=$PATH:$codeqlpath" ./scripts/create-extractor-pack.sh
|
||||
- name: Build extractor (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
cd ql;
|
||||
$Env:PATH += ";$(dirname ${{ steps.find-codeql.outputs.codeql-path }})"
|
||||
pwsh ./scripts/create-extractor-pack.ps1
|
||||
- name: Run a single QL tests - Unix
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
"${CODEQL}" test run --check-databases --search-path "${{ github.workspace }}/ql/extractor-pack" ql/ql/test/queries/style/DeadCode/DeadCode.qlref
|
||||
find ql/ql/src "(" -name "*.ql" -or -name "*.qll" ")" -print0 | xargs -0 "${CODEQL}" query format --check-only
|
||||
env:
|
||||
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
|
||||
- name: Run a single QL tests - Windows
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$Env:PATH += ";$(dirname ${{ steps.find-codeql.outputs.codeql-path }})"
|
||||
codeql test run --check-databases --search-path "${{ github.workspace }}/ql/extractor-pack" ql/ql/test/queries/style/DeadCode/DeadCode.qlref
|
||||
|
||||
31
.github/workflows/ruby-build.yml
vendored
31
.github/workflows/ruby-build.yml
vendored
@@ -48,8 +48,6 @@ jobs:
|
||||
run: |
|
||||
brew install gnu-tar
|
||||
echo "/usr/local/opt/gnu-tar/libexec/gnubin" >> $GITHUB_PATH
|
||||
- uses: ./.github/actions/os-version
|
||||
id: os_version
|
||||
- name: Cache entire extractor
|
||||
uses: actions/cache@v3
|
||||
id: cache-extractor
|
||||
@@ -60,7 +58,7 @@ jobs:
|
||||
ruby/target/release/ruby-extractor
|
||||
ruby/target/release/ruby-extractor.exe
|
||||
ruby/ql/lib/codeql/ruby/ast/internal/TreeSitter.qll
|
||||
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-ruby-extractor-${{ hashFiles('ruby/rust-toolchain.toml', 'ruby/**/Cargo.lock') }}--${{ hashFiles('ruby/**/*.rs') }}
|
||||
key: ${{ runner.os }}-ruby-extractor-${{ hashFiles('ruby/rust-toolchain.toml', 'ruby/**/Cargo.lock') }}--${{ hashFiles('ruby/**/*.rs') }}
|
||||
- uses: actions/cache@v3
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
with:
|
||||
@@ -68,7 +66,7 @@ jobs:
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
ruby/target
|
||||
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-ruby-rust-cargo-${{ hashFiles('ruby/rust-toolchain.toml', 'ruby/**/Cargo.lock') }}
|
||||
key: ${{ runner.os }}-ruby-rust-cargo-${{ hashFiles('ruby/rust-toolchain.toml', 'ruby/**/Cargo.lock') }}
|
||||
- name: Check formatting
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
run: cargo fmt --all -- --check
|
||||
@@ -117,10 +115,9 @@ jobs:
|
||||
- name: Build Query Pack
|
||||
run: |
|
||||
rm -rf target/packs
|
||||
codeql pack create ../shared/ssa --output target/packs
|
||||
codeql pack create ../misc/suite-helpers --output target/packs
|
||||
codeql pack create ../shared/regex --output target/packs
|
||||
codeql pack create ../shared/ssa --output target/packs
|
||||
codeql pack create ../shared/tutorial --output target/packs
|
||||
codeql pack create ql/lib --output target/packs
|
||||
codeql pack create -j0 ql/src --output target/packs --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}"
|
||||
PACK_FOLDER=$(readlink -f target/packs/codeql/ruby-queries/*)
|
||||
@@ -205,6 +202,11 @@ jobs:
|
||||
- name: Fetch CodeQL
|
||||
uses: ./.github/actions/fetch-codeql
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: Shopify/example-ruby-app
|
||||
ref: 67a0decc5eb550f3a9228eda53925c3afd40dfe9
|
||||
|
||||
- name: Download Ruby bundle
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
@@ -213,15 +215,26 @@ jobs:
|
||||
- name: Unzip Ruby bundle
|
||||
shell: bash
|
||||
run: unzip -q -d "${{ runner.temp }}/ruby-bundle" "${{ runner.temp }}/codeql-ruby-bundle.zip"
|
||||
|
||||
- name: Prepare test files
|
||||
shell: bash
|
||||
run: |
|
||||
echo "import codeql.ruby.AST select count(File f)" > "test.ql"
|
||||
echo "| 4 |" > "test.expected"
|
||||
echo 'name: sample-tests
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
codeql/ruby-all: "*"
|
||||
extractor: ruby
|
||||
tests: .
|
||||
' > qlpack.yml
|
||||
- name: Run QL test
|
||||
shell: bash
|
||||
run: |
|
||||
codeql test run --search-path "${{ runner.temp }}/ruby-bundle" --additional-packs "${{ runner.temp }}/ruby-bundle" ruby/ql/test/library-tests/ast/constants/
|
||||
codeql test run --search-path "${{ runner.temp }}/ruby-bundle" --additional-packs "${{ runner.temp }}/ruby-bundle" .
|
||||
- name: Create database
|
||||
shell: bash
|
||||
run: |
|
||||
codeql database create --search-path "${{ runner.temp }}/ruby-bundle" --language ruby --source-root ruby/ql/test/library-tests/ast/constants/ ../database
|
||||
codeql database create --search-path "${{ runner.temp }}/ruby-bundle" --language ruby --source-root . ../database
|
||||
- name: Analyze database
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
2
.github/workflows/swift.yml
vendored
2
.github/workflows/swift.yml
vendored
@@ -5,7 +5,6 @@ on:
|
||||
paths:
|
||||
- "swift/**"
|
||||
- "misc/bazel/**"
|
||||
- "misc/codegen/**"
|
||||
- "*.bazel*"
|
||||
- .github/workflows/swift.yml
|
||||
- .github/actions/**
|
||||
@@ -20,7 +19,6 @@ on:
|
||||
paths:
|
||||
- "swift/**"
|
||||
- "misc/bazel/**"
|
||||
- "misc/codegen/**"
|
||||
- "*.bazel*"
|
||||
- .github/workflows/swift.yml
|
||||
- .github/actions/**
|
||||
|
||||
@@ -53,5 +53,5 @@ repos:
|
||||
name: Run Swift code generation unit tests
|
||||
files: ^swift/codegen/.*\.py$
|
||||
language: system
|
||||
entry: bazel test //misc/codegen/test
|
||||
entry: bazel test //swift/codegen/test
|
||||
pass_filenames: false
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
/csharp/ @github/codeql-csharp
|
||||
/go/ @github/codeql-go
|
||||
/java/ @github/codeql-java
|
||||
/javascript/ @github/codeql-dynamic
|
||||
/python/ @github/codeql-dynamic
|
||||
/ruby/ @github/codeql-dynamic
|
||||
/javascript/ @github/codeql-javascript
|
||||
/python/ @github/codeql-python
|
||||
/ruby/ @github/codeql-ruby
|
||||
/swift/ @github/codeql-swift
|
||||
/misc/codegen/ @github/codeql-swift
|
||||
/java/kotlin-extractor/ @github/codeql-kotlin
|
||||
/java/kotlin-explorer/ @github/codeql-kotlin
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ If you have an idea for a query that you would like to share with other CodeQL u
|
||||
|
||||
Each language-specific directory contains further subdirectories that group queries based on their `@tags` or purpose.
|
||||
- Experimental queries and libraries are stored in the `experimental` subdirectory within each language-specific directory in the [CodeQL repository](https://github.com/github/codeql). For example, experimental Java queries and libraries are stored in `java/ql/src/experimental` and any corresponding tests in `java/ql/test/experimental`.
|
||||
- Experimental queries need to include `experimental` in their `@tags`
|
||||
- The structure of an `experimental` subdirectory mirrors the structure of its parent directory.
|
||||
- Select or create an appropriate directory in `experimental` based on the existing directory structure of `experimental` or its parent directory.
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@ There is [extensive documentation](https://codeql.github.com/docs/) on getting s
|
||||
|
||||
We welcome contributions to our standard library and standard checks. Do you have an idea for a new check, or how to improve an existing query? Then please go ahead and open a pull request! Before you do, though, please take the time to read our [contributing guidelines](CONTRIBUTING.md). You can also consult our [style guides](https://github.com/github/codeql/tree/main/docs) to learn how to format your code for consistency and clarity, how to write query metadata, and how to write query help documentation for your query.
|
||||
|
||||
For information on contributing to CodeQL documentation, see the "[contributing guide](docs/codeql/CONTRIBUTING.md)" for docs.
|
||||
|
||||
## License
|
||||
|
||||
The code in this repository is licensed under the [MIT License](LICENSE) by [GitHub](https://github.com).
|
||||
|
||||
@@ -1,28 +1,6 @@
|
||||
{
|
||||
"DataFlow Java/C++/C#/Go/Python/Ruby/Swift": [
|
||||
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlow.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlow.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlow.qll",
|
||||
"cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlow.qll",
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlow.qll",
|
||||
"go/ql/lib/semmle/go/dataflow/internal/DataFlow.qll",
|
||||
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlow.qll",
|
||||
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlow.qll",
|
||||
"swift/ql/lib/codeql/swift/dataflow/internal/DataFlow.qll"
|
||||
],
|
||||
"DataFlowImpl Java/C++/C#/Go/Python/Ruby/Swift": [
|
||||
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll",
|
||||
"cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll",
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll",
|
||||
"go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll",
|
||||
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll",
|
||||
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll",
|
||||
"swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl.qll"
|
||||
],
|
||||
"DataFlow Java/C++/C#/Go/Python/Ruby/Swift Legacy Configuration": [
|
||||
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl1.qll",
|
||||
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll",
|
||||
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll",
|
||||
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll",
|
||||
@@ -30,38 +8,37 @@
|
||||
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll",
|
||||
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll",
|
||||
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForOnActivityResult.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl1.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl1.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll",
|
||||
"cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl1.qll",
|
||||
"cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll",
|
||||
"cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll",
|
||||
"cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll",
|
||||
"cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll",
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl1.qll",
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll",
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll",
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll",
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll",
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll",
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplForContentDataFlow.qll",
|
||||
"go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl1.qll",
|
||||
"go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll",
|
||||
"go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl2.qll",
|
||||
"go/ql/lib/semmle/go/dataflow/internal/DataFlowImplForStringsNewReplacer.qll",
|
||||
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl1.qll",
|
||||
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll",
|
||||
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll",
|
||||
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll",
|
||||
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll",
|
||||
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplForRegExp.qll",
|
||||
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl1.qll",
|
||||
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll",
|
||||
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl2.qll",
|
||||
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplForRegExp.qll",
|
||||
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplForHttpClientLibraries.qll",
|
||||
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplForPathname.qll",
|
||||
"swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl1.qll"
|
||||
"swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl.qll"
|
||||
],
|
||||
"DataFlow Java/C++/C#/Go/Python/Ruby/Swift Common": [
|
||||
"java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll",
|
||||
@@ -74,18 +51,7 @@
|
||||
"ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplCommon.qll",
|
||||
"swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImplCommon.qll"
|
||||
],
|
||||
"TaintTracking Java/C++/C#/Go/Python/Ruby/Swift": [
|
||||
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTracking.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTracking.qll",
|
||||
"cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTracking.qll",
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/tainttracking1/TaintTracking.qll",
|
||||
"go/ql/lib/semmle/go/dataflow/internal/tainttracking1/TaintTracking.qll",
|
||||
"java/ql/lib/semmle/code/java/dataflow/internal/tainttracking1/TaintTracking.qll",
|
||||
"python/ql/lib/semmle/python/dataflow/new/internal/tainttracking1/TaintTracking.qll",
|
||||
"ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTracking.qll",
|
||||
"swift/ql/lib/codeql/swift/dataflow/internal/tainttracking1/TaintTracking.qll"
|
||||
],
|
||||
"TaintTracking Legacy Configuration Java/C++/C#/Go/Python/Ruby/Swift": [
|
||||
"TaintTracking::Configuration Java/C++/C#/Go/Python/Ruby/Swift": [
|
||||
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking1/TaintTrackingImpl.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/dataflow/internal/tainttracking2/TaintTrackingImpl.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/tainttracking1/TaintTrackingImpl.qll",
|
||||
@@ -436,6 +402,16 @@
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/ControlFlowReachability.qll",
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/rangeanalysis/ControlFlowReachability.qll"
|
||||
],
|
||||
"Inline Test Expectations": [
|
||||
"cpp/ql/test/TestUtilities/InlineExpectationsTest.qll",
|
||||
"csharp/ql/test/TestUtilities/InlineExpectationsTest.qll",
|
||||
"java/ql/test/TestUtilities/InlineExpectationsTest.qll",
|
||||
"python/ql/test/TestUtilities/InlineExpectationsTest.qll",
|
||||
"ruby/ql/test/TestUtilities/InlineExpectationsTest.qll",
|
||||
"ql/ql/test/TestUtilities/InlineExpectationsTest.qll",
|
||||
"go/ql/test/TestUtilities/InlineExpectationsTest.qll",
|
||||
"swift/ql/test/TestUtilities/InlineExpectationsTest.qll"
|
||||
],
|
||||
"C++ ExternalAPIs": [
|
||||
"cpp/ql/src/Security/CWE/CWE-020/ExternalAPIs.qll",
|
||||
"cpp/ql/src/Security/CWE/CWE-020/ir/ExternalAPIs.qll"
|
||||
@@ -529,6 +505,14 @@
|
||||
"python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll",
|
||||
"ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll"
|
||||
],
|
||||
"CodeQL Tutorial": [
|
||||
"cpp/ql/lib/tutorial.qll",
|
||||
"csharp/ql/lib/tutorial.qll",
|
||||
"java/ql/lib/tutorial.qll",
|
||||
"javascript/ql/lib/tutorial.qll",
|
||||
"python/ql/lib/tutorial.qll",
|
||||
"ruby/ql/lib/tutorial.qll"
|
||||
],
|
||||
"AccessPathSyntax": [
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/AccessPathSyntax.qll",
|
||||
"go/ql/lib/semmle/go/dataflow/internal/AccessPathSyntax.qll",
|
||||
@@ -547,6 +531,11 @@
|
||||
"ruby/ql/lib/codeql/ruby/internal/ConceptsShared.qll",
|
||||
"javascript/ql/lib/semmle/javascript/internal/ConceptsShared.qll"
|
||||
],
|
||||
"Hostname Regexp queries": [
|
||||
"javascript/ql/src/Security/CWE-020/HostnameRegexpShared.qll",
|
||||
"python/ql/src/Security/CWE-020/HostnameRegexpShared.qll",
|
||||
"ruby/ql/src/queries/security/cwe-020/HostnameRegexpShared.qll"
|
||||
],
|
||||
"ApiGraphModels": [
|
||||
"javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll",
|
||||
"ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Xunit;
|
||||
using Semmle.Autobuild.Shared;
|
||||
using Semmle.Util;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
@@ -76,15 +75,6 @@ namespace Semmle.Autobuild.Cpp.Tests
|
||||
throw new ArgumentException("Missing RunProcess " + pattern);
|
||||
}
|
||||
|
||||
int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory, IDictionary<string, string>? env, BuildOutputHandler onOutput, BuildOutputHandler onError)
|
||||
{
|
||||
var ret = (this as IBuildActions).RunProcess(cmd, args, workingDirectory, env, out var stdout);
|
||||
|
||||
stdout.ForEach(line => onOutput(line));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public IList<string> DirectoryDeleteIn = new List<string>();
|
||||
|
||||
void IBuildActions.DirectoryDelete(string dir, bool recursive)
|
||||
@@ -141,14 +131,6 @@ namespace Semmle.Autobuild.Cpp.Tests
|
||||
|
||||
bool IBuildActions.IsWindows() => IsWindows;
|
||||
|
||||
public bool IsMacOs { get; set; }
|
||||
|
||||
bool IBuildActions.IsMacOs() => IsMacOs;
|
||||
|
||||
public bool IsArm { get; set; }
|
||||
|
||||
bool IBuildActions.IsArm() => IsArm;
|
||||
|
||||
string IBuildActions.PathCombine(params string[] parts)
|
||||
{
|
||||
return string.Join(IsWindows ? '\\' : '/', parts.Where(p => !string.IsNullOrWhiteSpace(p)));
|
||||
@@ -194,15 +176,6 @@ namespace Semmle.Autobuild.Cpp.Tests
|
||||
if (!DownloadFiles.Contains((address, fileName)))
|
||||
throw new ArgumentException($"Missing DownloadFile, {address}, {fileName}");
|
||||
}
|
||||
|
||||
public IDiagnosticsWriter CreateDiagnosticsWriter(string filename) => new TestDiagnosticWriter();
|
||||
}
|
||||
|
||||
internal class TestDiagnosticWriter : IDiagnosticsWriter
|
||||
{
|
||||
public IList<DiagnosticMessage> Diagnostics { get; } = new List<DiagnosticMessage>();
|
||||
|
||||
public void AddEntry(DiagnosticMessage message) => this.Diagnostics.Add(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -262,7 +235,6 @@ namespace Semmle.Autobuild.Cpp.Tests
|
||||
Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_TRAP_DIR"] = "";
|
||||
Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_SOURCE_ARCHIVE_DIR"] = "";
|
||||
Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_ROOT"] = $@"C:\codeql\{codeqlUpperLanguage.ToLowerInvariant()}";
|
||||
Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_DIAGNOSTIC_DIR"] = "";
|
||||
Actions.GetEnvironmentVariable["CODEQL_JAVA_HOME"] = @"C:\codeql\tools\java";
|
||||
Actions.GetEnvironmentVariable["CODEQL_PLATFORM"] = "win64";
|
||||
Actions.GetEnvironmentVariable["SEMMLE_DIST"] = @"C:\odasa";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Semmle.Autobuild.Shared;
|
||||
using Semmle.Util;
|
||||
|
||||
namespace Semmle.Autobuild.Cpp
|
||||
{
|
||||
@@ -22,7 +21,7 @@ namespace Semmle.Autobuild.Cpp
|
||||
|
||||
public class CppAutobuilder : Autobuilder<CppAutobuildOptions>
|
||||
{
|
||||
public CppAutobuilder(IBuildActions actions, CppAutobuildOptions options) : base(actions, options, new DiagnosticClassifier()) { }
|
||||
public CppAutobuilder(IBuildActions actions, CppAutobuildOptions options) : base(actions, options) { }
|
||||
|
||||
public override BuildScript GetBuildScript()
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AssemblyName>Semmle.Autobuild.Cpp</AssemblyName>
|
||||
<RootNamespace>Semmle.Autobuild.Cpp</RootNamespace>
|
||||
<ApplicationIcon />
|
||||
|
||||
@@ -13,5 +13,5 @@ predicate isExprWithNewBuiltin(Expr expr) {
|
||||
from Expr expr, int kind, int kind_new, Location location
|
||||
where
|
||||
exprs(expr, kind, location) and
|
||||
if isExprWithNewBuiltin(expr) then kind_new = 1 else kind_new = kind
|
||||
if isExprWithNewBuiltin(expr) then kind_new = 0 else kind_new = kind
|
||||
select expr, kind_new, location
|
||||
|
||||
@@ -9,5 +9,5 @@ class Location extends @location_expr {
|
||||
from Expr expr, int kind, int kind_new, Location location
|
||||
where
|
||||
exprs(expr, kind, location) and
|
||||
if expr instanceof @blockassignexpr then kind_new = 1 else kind_new = kind
|
||||
if expr instanceof @blockassignexpr then kind_new = 0 else kind_new = kind
|
||||
select expr, kind_new, location
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
class BuiltinType extends @builtintype {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from BuiltinType type, string name, int kind, int kind_new, int size, int sign, int alignment
|
||||
where
|
||||
builtintypes(type, name, kind, size, sign, alignment) and
|
||||
if type instanceof @float16 or type instanceof @complex_float16
|
||||
then kind_new = 2
|
||||
else kind_new = kind
|
||||
select type, name, kind_new, size, sign, alignment
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
||||
description: Introduce (_Complex) _Float16 type
|
||||
compatibility: backwards
|
||||
builtintypes.rel: run builtintypes.qlo
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
||||
description: Uncomment case splits in dbscheme
|
||||
compatibility: full
|
||||
@@ -1,40 +1,3 @@
|
||||
## 0.5.3
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.5.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.5.1
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
The predicates in the `MustFlow::Configuration` class used by the `MustFlow` library (`semmle.code.cpp.ir.dataflow.MustFlow`) have changed to be defined directly in terms of the C++ IR instead of IR dataflow nodes.
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* Deprecated `semmle.code.cpp.ir.dataflow.DefaultTaintTracking`. Use `semmle.code.cpp.ir.dataflow.TaintTracking`.
|
||||
* Deprecated `semmle.code.cpp.security.TaintTrackingImpl`. Use `semmle.code.cpp.ir.dataflow.TaintTracking`.
|
||||
* Deprecated `semmle.code.cpp.valuenumbering.GlobalValueNumberingImpl`. Use `semmle.code.cpp.valuenumbering.GlobalValueNumbering`, which exposes the same API.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `ArgvSource` flow source now uses the second parameter of `main` as its source instead of the uses of this parameter.
|
||||
* The `ArgvSource` flow source has been generalized to handle cases where the argument vector of `main` is not named `argv`.
|
||||
* The `getaddrinfo` function is now recognized as a flow source.
|
||||
* The `secure_getenv` and `_wgetenv` functions are now recognized as local flow sources.
|
||||
* The `scanf` and `fscanf` functions and their variants are now recognized as flow sources.
|
||||
* Deleted the deprecated `getName` and `getShortName` predicates from the `Folder` class.
|
||||
|
||||
## 0.4.6
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.4.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
6
cpp/ql/lib/change-notes/2022-11-14-deprecate-ast-gvn.md
Normal file
6
cpp/ql/lib/change-notes/2022-11-14-deprecate-ast-gvn.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
|
||||
|
||||
* Deprecated `semmle.code.cpp.valuenumbering.GlobalValueNumberingImpl`. Use `semmle.code.cpp.valuenumbering.GlobalValueNumbering`, which exposes the same API.
|
||||
4
cpp/ql/lib/change-notes/2022-11-16-must-flow.md
Normal file
4
cpp/ql/lib/change-notes/2022-11-16-must-flow.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: breaking
|
||||
---
|
||||
The predicates in the `MustFlow::Configuration` class used by the `MustFlow` library (`semmle.code.cpp.ir.dataflow.MustFlow`) have changed to be defined directly in terms of the C++ IR instead of IR dataflow nodes.
|
||||
4
cpp/ql/lib/change-notes/2022-11-17-deleted-deps.md
Normal file
4
cpp/ql/lib/change-notes/2022-11-17-deleted-deps.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Deleted the deprecated `getName` and `getShortName` predicates from the `Folder` class.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
|
||||
* Deprecated `semmle.code.cpp.ir.dataflow.DefaultTaintTracking`. Use `semmle.code.cpp.ir.dataflow.TaintTracking`.
|
||||
* Deprecated `semmle.code.cpp.security.TaintTrackingImpl`. Use `semmle.code.cpp.ir.dataflow.TaintTracking`.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `getaddrinfo` function is now recognized as a flow source.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `secure_getenv` and `_wgetenv` functions are now recognized as local flow sources.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `scanf` and `fscanf` functions and their variants are now recognized as flow sources.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `ArgvSource` flow source has been generalized to handle cases where the argument vector of `main` is not named `argv`.
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
category: majorAnalysis
|
||||
---
|
||||
* The main data flow and taint tracking APIs have been changed. The old APIs
|
||||
remain in place for now and translate to the new through a
|
||||
backwards-compatible wrapper. If multiple configurations are in scope
|
||||
simultaneously, then this may affect results slightly. The new API is quite
|
||||
similar to the old, but makes use of a configuration module instead of a
|
||||
configuration class.
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.4.6
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,20 +0,0 @@
|
||||
## 0.5.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
The predicates in the `MustFlow::Configuration` class used by the `MustFlow` library (`semmle.code.cpp.ir.dataflow.MustFlow`) have changed to be defined directly in terms of the C++ IR instead of IR dataflow nodes.
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* Deprecated `semmle.code.cpp.ir.dataflow.DefaultTaintTracking`. Use `semmle.code.cpp.ir.dataflow.TaintTracking`.
|
||||
* Deprecated `semmle.code.cpp.security.TaintTrackingImpl`. Use `semmle.code.cpp.ir.dataflow.TaintTracking`.
|
||||
* Deprecated `semmle.code.cpp.valuenumbering.GlobalValueNumberingImpl`. Use `semmle.code.cpp.valuenumbering.GlobalValueNumbering`, which exposes the same API.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `ArgvSource` flow source now uses the second parameter of `main` as its source instead of the uses of this parameter.
|
||||
* The `ArgvSource` flow source has been generalized to handle cases where the argument vector of `main` is not named `argv`.
|
||||
* The `getaddrinfo` function is now recognized as a flow source.
|
||||
* The `secure_getenv` and `_wgetenv` functions are now recognized as local flow sources.
|
||||
* The `scanf` and `fscanf` functions and their variants are now recognized as flow sources.
|
||||
* Deleted the deprecated `getName` and `getShortName` predicates from the `Folder` class.
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.5.1
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.5.2
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.5.3
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.5.3
|
||||
lastReleaseVersion: 0.4.5
|
||||
|
||||
@@ -123,13 +123,6 @@ private predicate constructorCallTypeMention(ConstructorCall cc, TypeMention tm)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `loc` has the container `container` and is on the line starting at `startLine`. */
|
||||
pragma[nomagic]
|
||||
private predicate hasContainerAndStartLine(Location loc, Container container, int startLine) {
|
||||
loc.getStartLine() = startLine and
|
||||
loc.getContainer() = container
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an element, of kind `kind`, that element `e` uses, if any.
|
||||
* Attention: This predicate yields multiple definitions for a single location.
|
||||
@@ -166,9 +159,9 @@ Top definitionOf(Top e, string kind) {
|
||||
// Multiple type mentions can be generated when a typedef is used, and
|
||||
// in such cases we want to exclude all but the originating typedef.
|
||||
not exists(Type secondary |
|
||||
exists(File f, int startline, int startcol |
|
||||
exists(TypeMention tm, File f, int startline, int startcol |
|
||||
typeMentionStartLoc(e, result, f, startline, startcol) and
|
||||
typeMentionStartLoc(_, secondary, f, startline, startcol) and
|
||||
typeMentionStartLoc(tm, secondary, f, startline, startcol) and
|
||||
(
|
||||
result = secondary.(TypedefType).getBaseType() or
|
||||
result = secondary.(TypedefType).getBaseType().(SpecifiedType).getBaseType()
|
||||
@@ -191,9 +184,11 @@ Top definitionOf(Top e, string kind) {
|
||||
kind = "I" and
|
||||
result = e.(Include).getIncludedFile() and
|
||||
// exclude `#include` directives containing macros
|
||||
not exists(MacroInvocation mi, Container container, int startLine |
|
||||
hasContainerAndStartLine(e.(Include).getLocation(), container, startLine) and
|
||||
hasContainerAndStartLine(mi.getLocation(), container, startLine)
|
||||
not exists(MacroInvocation mi, Location l1, Location l2 |
|
||||
l1 = e.(Include).getLocation() and
|
||||
l2 = mi.getLocation() and
|
||||
l1.getContainer() = l2.getContainer() and
|
||||
l1.getStartLine() = l2.getStartLine()
|
||||
// (an #include directive must be always on it's own line)
|
||||
)
|
||||
) and
|
||||
|
||||
@@ -22,6 +22,5 @@
|
||||
import cpp
|
||||
|
||||
module DataFlow {
|
||||
import experimental.semmle.code.cpp.ir.dataflow.internal.DataFlow
|
||||
import experimental.semmle.code.cpp.ir.dataflow.internal.DataFlowImpl1
|
||||
import experimental.semmle.code.cpp.ir.dataflow.internal.DataFlowImpl
|
||||
}
|
||||
|
||||
@@ -19,6 +19,5 @@ import semmle.code.cpp.ir.dataflow.DataFlow
|
||||
import semmle.code.cpp.ir.dataflow.DataFlow2
|
||||
|
||||
module TaintTracking {
|
||||
import experimental.semmle.code.cpp.ir.dataflow.internal.tainttracking1.TaintTracking
|
||||
import experimental.semmle.code.cpp.ir.dataflow.internal.tainttracking1.TaintTrackingImpl
|
||||
}
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
/**
|
||||
* Provides an implementation of global (interprocedural) data flow. This file
|
||||
* re-exports the local (intraprocedural) data flow analysis from
|
||||
* `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed
|
||||
* through the `Make` and `MakeWithState` modules.
|
||||
*/
|
||||
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
private import DataFlowImpl
|
||||
|
||||
/** An input configuration for data flow. */
|
||||
signature module ConfigSig {
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source.
|
||||
*/
|
||||
predicate isSource(Node source);
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink.
|
||||
*/
|
||||
predicate isSink(Node sink);
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited. This completely removes
|
||||
* `node` from the data flow graph.
|
||||
*/
|
||||
default predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
default predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
default predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
*/
|
||||
default predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Holds if an arbitrary number of implicit read steps of content `c` may be
|
||||
* taken at `node`.
|
||||
*/
|
||||
default predicate allowImplicitRead(Node node, ContentSet c) { none() }
|
||||
|
||||
/**
|
||||
* Gets the virtual dispatch branching limit when calculating field flow.
|
||||
* This can be overridden to a smaller value to improve performance (a
|
||||
* value of 0 disables field flow), or a larger value to get more results.
|
||||
*/
|
||||
default int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*
|
||||
* These features are generally not relevant for typical end-to-end data flow
|
||||
* queries, but should only be used for constructing paths that need to
|
||||
* somehow be pluggable in another path context.
|
||||
*/
|
||||
default FlowFeature getAFeature() { none() }
|
||||
|
||||
/** Holds if sources should be grouped in the result of `hasFlowPath`. */
|
||||
default predicate sourceGrouping(Node source, string sourceGroup) { none() }
|
||||
|
||||
/** Holds if sinks should be grouped in the result of `hasFlowPath`. */
|
||||
default predicate sinkGrouping(Node sink, string sinkGroup) { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (as it is in a `path-problem` query).
|
||||
*/
|
||||
default predicate includeHiddenNodes() { none() }
|
||||
}
|
||||
|
||||
/** An input configuration for data flow using flow state. */
|
||||
signature module StateConfigSig {
|
||||
bindingset[this]
|
||||
class FlowState;
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source with the given initial
|
||||
* `state`.
|
||||
*/
|
||||
predicate isSource(Node source, FlowState state);
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink accepting `state`.
|
||||
*/
|
||||
predicate isSink(Node sink, FlowState state);
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited. This completely removes
|
||||
* `node` from the data flow graph.
|
||||
*/
|
||||
default predicate isBarrier(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited when the flow state is
|
||||
* `state`.
|
||||
*/
|
||||
predicate isBarrier(Node node, FlowState state);
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
default predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
default predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
*/
|
||||
default predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
* This step is only applicable in `state1` and updates the flow state to `state2`.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2);
|
||||
|
||||
/**
|
||||
* Holds if an arbitrary number of implicit read steps of content `c` may be
|
||||
* taken at `node`.
|
||||
*/
|
||||
default predicate allowImplicitRead(Node node, ContentSet c) { none() }
|
||||
|
||||
/**
|
||||
* Gets the virtual dispatch branching limit when calculating field flow.
|
||||
* This can be overridden to a smaller value to improve performance (a
|
||||
* value of 0 disables field flow), or a larger value to get more results.
|
||||
*/
|
||||
default int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*
|
||||
* These features are generally not relevant for typical end-to-end data flow
|
||||
* queries, but should only be used for constructing paths that need to
|
||||
* somehow be pluggable in another path context.
|
||||
*/
|
||||
default FlowFeature getAFeature() { none() }
|
||||
|
||||
/** Holds if sources should be grouped in the result of `hasFlowPath`. */
|
||||
default predicate sourceGrouping(Node source, string sourceGroup) { none() }
|
||||
|
||||
/** Holds if sinks should be grouped in the result of `hasFlowPath`. */
|
||||
default predicate sinkGrouping(Node sink, string sinkGroup) { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (as it is in a `path-problem` query).
|
||||
*/
|
||||
default predicate includeHiddenNodes() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
|
||||
* measured in approximate number of interprocedural steps.
|
||||
*/
|
||||
signature int explorationLimitSig();
|
||||
|
||||
/**
|
||||
* The output of a data flow computation.
|
||||
*/
|
||||
signature module DataFlowSig {
|
||||
/**
|
||||
* A `Node` augmented with a call context (except for sinks) and an access path.
|
||||
* Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated.
|
||||
*/
|
||||
class PathNode;
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `source` to `sink`.
|
||||
*
|
||||
* The corresponding paths are generated from the end-points and the graph
|
||||
* included in the module `PathGraph`.
|
||||
*/
|
||||
predicate hasFlowPath(PathNode source, PathNode sink);
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `source` to `sink`.
|
||||
*/
|
||||
predicate hasFlow(Node source, Node sink);
|
||||
|
||||
/**
|
||||
* Holds if data can flow from some source to `sink`.
|
||||
*/
|
||||
predicate hasFlowTo(Node sink);
|
||||
|
||||
/**
|
||||
* Holds if data can flow from some source to `sink`.
|
||||
*/
|
||||
predicate hasFlowToExpr(DataFlowExpr sink);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a standard data flow computation.
|
||||
*/
|
||||
module Make<ConfigSig Config> implements DataFlowSig {
|
||||
private module C implements FullStateConfigSig {
|
||||
import DefaultState<Config>
|
||||
import Config
|
||||
}
|
||||
|
||||
import Impl<C>
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a data flow computation using flow state.
|
||||
*/
|
||||
module MakeWithState<StateConfigSig Config> implements DataFlowSig {
|
||||
private module C implements FullStateConfigSig {
|
||||
import Config
|
||||
}
|
||||
|
||||
import Impl<C>
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,396 +0,0 @@
|
||||
/**
|
||||
* DEPRECATED: Use `Make` and `MakeWithState` instead.
|
||||
*
|
||||
* Provides a `Configuration` class backwards-compatible interface to the data
|
||||
* flow library.
|
||||
*/
|
||||
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
private import DataFlowImpl
|
||||
import DataFlowImplCommonPublic
|
||||
import FlowStateString
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
* sources, sinks, and any other configurable aspect of the analysis. Each
|
||||
* use of the global data flow library must define its own unique extension
|
||||
* of this abstract class. To create a configuration, extend this class with
|
||||
* a subclass whose characteristic predicate is a unique singleton string.
|
||||
* For example, write
|
||||
*
|
||||
* ```ql
|
||||
* class MyAnalysisConfiguration extends DataFlow::Configuration {
|
||||
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
|
||||
* // Override `isSource` and `isSink`.
|
||||
* // Optionally override `isBarrier`.
|
||||
* // Optionally override `isAdditionalFlowStep`.
|
||||
* }
|
||||
* ```
|
||||
* Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and
|
||||
* the edges are those data-flow steps that preserve the value of the node
|
||||
* along with any additional edges defined by `isAdditionalFlowStep`.
|
||||
* Specifying nodes in `isBarrier` will remove those nodes from the graph, and
|
||||
* specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going
|
||||
* and/or out-going edges from those nodes, respectively.
|
||||
*
|
||||
* Then, to query whether there is flow between some `source` and `sink`,
|
||||
* write
|
||||
*
|
||||
* ```ql
|
||||
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
|
||||
* ```
|
||||
*
|
||||
* Multiple configurations can coexist, but two classes extending
|
||||
* `DataFlow::Configuration` should never depend on each other. One of them
|
||||
* should instead depend on a `DataFlow2::Configuration`, a
|
||||
* `DataFlow3::Configuration`, or a `DataFlow4::Configuration`.
|
||||
*/
|
||||
abstract class Configuration extends string {
|
||||
bindingset[this]
|
||||
Configuration() { any() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source.
|
||||
*/
|
||||
predicate isSource(Node source) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source with the given initial
|
||||
* `state`.
|
||||
*/
|
||||
predicate isSource(Node source, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink.
|
||||
*/
|
||||
predicate isSink(Node sink) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink accepting `state`.
|
||||
*/
|
||||
predicate isSink(Node sink, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited. This completely removes
|
||||
* `node` from the data flow graph.
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited when the flow state is
|
||||
* `state`.
|
||||
*/
|
||||
predicate isBarrier(Node node, FlowState state) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited.
|
||||
*/
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard) { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited when
|
||||
* the flow state is `state`
|
||||
*/
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
* This step is only applicable in `state1` and updates the flow state to `state2`.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an arbitrary number of implicit read steps of content `c` may be
|
||||
* taken at `node`.
|
||||
*/
|
||||
predicate allowImplicitRead(Node node, ContentSet c) { none() }
|
||||
|
||||
/**
|
||||
* Gets the virtual dispatch branching limit when calculating field flow.
|
||||
* This can be overridden to a smaller value to improve performance (a
|
||||
* value of 0 disables field flow), or a larger value to get more results.
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*
|
||||
* These features are generally not relevant for typical end-to-end data flow
|
||||
* queries, but should only be used for constructing paths that need to
|
||||
* somehow be pluggable in another path context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/** Holds if sources should be grouped in the result of `hasFlowPath`. */
|
||||
predicate sourceGrouping(Node source, string sourceGroup) { none() }
|
||||
|
||||
/** Holds if sinks should be grouped in the result of `hasFlowPath`. */
|
||||
predicate sinkGrouping(Node sink, string sinkGroup) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*
|
||||
* The corresponding paths are generated from the end-points and the graph
|
||||
* included in the module `PathGraph`.
|
||||
*/
|
||||
predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `FlowExploration<explorationLimit>` instead.
|
||||
*
|
||||
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
|
||||
* measured in approximate number of interprocedural steps.
|
||||
*/
|
||||
deprecated int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* This class exists to prevent mutual recursion between the user-overridden
|
||||
* member predicates of `Configuration` and the rest of the data-flow library.
|
||||
* Good performance cannot be guaranteed in the presence of such recursion, so
|
||||
* it should be replaced by using more than one copy of the data flow library.
|
||||
*/
|
||||
abstract private class ConfigurationRecursionPrevention extends Configuration {
|
||||
bindingset[this]
|
||||
ConfigurationRecursionPrevention() { any() }
|
||||
|
||||
override predicate hasFlow(Node source, Node sink) {
|
||||
strictcount(Node n | this.isSource(n)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSource(n, _)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSink(n)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSink(n, _)) < 0
|
||||
or
|
||||
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
|
||||
or
|
||||
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0
|
||||
or
|
||||
super.hasFlow(source, sink)
|
||||
}
|
||||
}
|
||||
|
||||
/** A bridge class to access the deprecated `isBarrierGuard`. */
|
||||
private class BarrierGuardGuardedNodeBridge extends Unit {
|
||||
abstract predicate guardedNode(Node n, Configuration config);
|
||||
|
||||
abstract predicate guardedNode(Node n, FlowState state, Configuration config);
|
||||
}
|
||||
|
||||
private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge {
|
||||
deprecated override predicate guardedNode(Node n, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
|
||||
deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g, state) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private FlowState relevantState(Configuration config) {
|
||||
config.isSource(_, result) or
|
||||
config.isSink(_, result) or
|
||||
config.isBarrier(_, result) or
|
||||
config.isAdditionalFlowStep(_, result, _, _) or
|
||||
config.isAdditionalFlowStep(_, _, _, result)
|
||||
}
|
||||
|
||||
private newtype TConfigState =
|
||||
TMkConfigState(Configuration config, FlowState state) {
|
||||
state = relevantState(config) or state instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) }
|
||||
|
||||
private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) }
|
||||
|
||||
private predicate singleConfiguration() { 1 = strictcount(Configuration c) }
|
||||
|
||||
private module Config implements FullStateConfigSig {
|
||||
class FlowState = TConfigState;
|
||||
|
||||
predicate isSource(Node source, FlowState state) {
|
||||
getConfig(state).isSource(source, getState(state))
|
||||
or
|
||||
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
predicate isSink(Node sink, FlowState state) {
|
||||
getConfig(state).isSink(sink, getState(state))
|
||||
or
|
||||
getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
predicate isBarrier(Node node, FlowState state) {
|
||||
getConfig(state).isBarrier(node, getState(state)) or
|
||||
getConfig(state).isBarrier(node) or
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state))
|
||||
}
|
||||
|
||||
predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) }
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) {
|
||||
getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and
|
||||
getConfig(state2) = getConfig(state1)
|
||||
or
|
||||
not singleConfiguration() and
|
||||
getConfig(state1).isAdditionalFlowStep(node1, node2) and
|
||||
state2 = state1
|
||||
}
|
||||
|
||||
predicate allowImplicitRead(Node node, ContentSet c) {
|
||||
any(Configuration config).allowImplicitRead(node, c)
|
||||
}
|
||||
|
||||
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
|
||||
|
||||
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }
|
||||
|
||||
predicate sourceGrouping(Node source, string sourceGroup) {
|
||||
any(Configuration config).sourceGrouping(source, sourceGroup)
|
||||
}
|
||||
|
||||
predicate sinkGrouping(Node sink, string sinkGroup) {
|
||||
any(Configuration config).sinkGrouping(sink, sinkGroup)
|
||||
}
|
||||
|
||||
predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() }
|
||||
}
|
||||
|
||||
private import Impl<Config> as I
|
||||
import I
|
||||
|
||||
/**
|
||||
* A `Node` augmented with a call context (except for sinks), an access path, and a configuration.
|
||||
* Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated.
|
||||
*/
|
||||
class PathNode instanceof I::PathNode {
|
||||
/** Gets a textual representation of this element. */
|
||||
final string toString() { result = super.toString() }
|
||||
|
||||
/**
|
||||
* Gets a textual representation of this element, including a textual
|
||||
* representation of the call context.
|
||||
*/
|
||||
final string toStringWithContext() { result = super.toStringWithContext() }
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
final predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
/** Gets the underlying `Node`. */
|
||||
final Node getNode() { result = super.getNode() }
|
||||
|
||||
/** Gets the `FlowState` of this node. */
|
||||
final FlowState getState() { result = getState(super.getState()) }
|
||||
|
||||
/** Gets the associated configuration. */
|
||||
final Configuration getConfiguration() { result = getConfig(super.getState()) }
|
||||
|
||||
/** Gets a successor of this node, if any. */
|
||||
final PathNode getASuccessor() { result = super.getASuccessor() }
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
final predicate isSource() { super.isSource() }
|
||||
|
||||
/** Holds if this node is a grouping of source nodes. */
|
||||
final predicate isSourceGroup(string group) { super.isSourceGroup(group) }
|
||||
|
||||
/** Holds if this node is a grouping of sink nodes. */
|
||||
final predicate isSinkGroup(string group) { super.isSinkGroup(group) }
|
||||
}
|
||||
|
||||
private predicate hasFlow(Node source, Node sink, Configuration config) {
|
||||
exists(PathNode source0, PathNode sink0 |
|
||||
hasFlowPath(source0, sink0, config) and
|
||||
source0.getNode() = source and
|
||||
sink0.getNode() = sink
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) {
|
||||
hasFlowPath(source, sink) and source.getConfiguration() = config
|
||||
}
|
||||
|
||||
private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) }
|
||||
|
||||
predicate flowsTo = hasFlow/3;
|
||||
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
@@ -3,18 +3,15 @@ private import DataFlowImplSpecific::Public
|
||||
import Cached
|
||||
|
||||
module DataFlowImplCommonPublic {
|
||||
/** Provides `FlowState = string`. */
|
||||
module FlowStateString {
|
||||
/** A state value to track during data flow. */
|
||||
class FlowState = string;
|
||||
/** A state value to track during data flow. */
|
||||
class FlowState = string;
|
||||
|
||||
/**
|
||||
* The default state, which is used when the state is unspecified for a source
|
||||
* or a sink.
|
||||
*/
|
||||
class FlowStateEmpty extends FlowState {
|
||||
FlowStateEmpty() { this = "" }
|
||||
}
|
||||
/**
|
||||
* The default state, which is used when the state is unspecified for a source
|
||||
* or a sink.
|
||||
*/
|
||||
class FlowStateEmpty extends FlowState {
|
||||
FlowStateEmpty() { this = "" }
|
||||
}
|
||||
|
||||
private newtype TFlowFeature =
|
||||
@@ -710,8 +707,8 @@ private module Cached {
|
||||
* Gets a viable dispatch target of `call` in the context `ctx`. This is
|
||||
* restricted to those `call`s for which a context might make a difference.
|
||||
*/
|
||||
cached
|
||||
DataFlowCallable viableImplInCallContextExt(DataFlowCall call, DataFlowCall ctx) {
|
||||
pragma[nomagic]
|
||||
private DataFlowCallable viableImplInCallContextExt(DataFlowCall call, DataFlowCall ctx) {
|
||||
result = viableImplInCallContext(call, ctx) and
|
||||
result = viableCallable(call)
|
||||
or
|
||||
@@ -919,15 +916,15 @@ private module Cached {
|
||||
TDataFlowCallSome(DataFlowCall call)
|
||||
|
||||
cached
|
||||
newtype TParamNodeOption =
|
||||
TParamNodeNone() or
|
||||
TParamNodeSome(ParamNode p)
|
||||
newtype TParameterPositionOption =
|
||||
TParameterPositionNone() or
|
||||
TParameterPositionSome(ParameterPosition pos)
|
||||
|
||||
cached
|
||||
newtype TReturnCtx =
|
||||
TReturnCtxNone() or
|
||||
TReturnCtxNoFlowThrough() or
|
||||
TReturnCtxMaybeFlowThrough(ReturnPosition pos)
|
||||
TReturnCtxMaybeFlowThrough(ReturnKindExt kind)
|
||||
|
||||
cached
|
||||
newtype TTypedContentApprox =
|
||||
@@ -1346,15 +1343,15 @@ class DataFlowCallOption extends TDataFlowCallOption {
|
||||
}
|
||||
}
|
||||
|
||||
/** An optional `ParamNode`. */
|
||||
class ParamNodeOption extends TParamNodeOption {
|
||||
/** An optional `ParameterPosition`. */
|
||||
class ParameterPositionOption extends TParameterPositionOption {
|
||||
string toString() {
|
||||
this = TParamNodeNone() and
|
||||
this = TParameterPositionNone() and
|
||||
result = "(none)"
|
||||
or
|
||||
exists(ParamNode p |
|
||||
this = TParamNodeSome(p) and
|
||||
result = p.toString()
|
||||
exists(ParameterPosition pos |
|
||||
this = TParameterPositionSome(pos) and
|
||||
result = pos.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1366,7 +1363,7 @@ class ParamNodeOption extends TParamNodeOption {
|
||||
*
|
||||
* - `TReturnCtxNone()`: no return flow.
|
||||
* - `TReturnCtxNoFlowThrough()`: return flow, but flow through is not possible.
|
||||
* - `TReturnCtxMaybeFlowThrough(ReturnPosition pos)`: return flow, of kind `pos`, and
|
||||
* - `TReturnCtxMaybeFlowThrough(ReturnKindExt kind)`: return flow, of kind `kind`, and
|
||||
* flow through may be possible.
|
||||
*/
|
||||
class ReturnCtx extends TReturnCtx {
|
||||
@@ -1377,9 +1374,9 @@ class ReturnCtx extends TReturnCtx {
|
||||
this = TReturnCtxNoFlowThrough() and
|
||||
result = "(no flow through)"
|
||||
or
|
||||
exists(ReturnPosition pos |
|
||||
this = TReturnCtxMaybeFlowThrough(pos) and
|
||||
result = pos.toString()
|
||||
exists(ReturnKindExt kind |
|
||||
this = TReturnCtxMaybeFlowThrough(kind) and
|
||||
result = kind.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1394,9 +1391,6 @@ class TypedContentApprox extends MkTypedContentApprox {
|
||||
/** Gets a typed content approximated by this value. */
|
||||
TypedContent getATypedContent() { result = getATypedContent(this) }
|
||||
|
||||
/** Gets the content. */
|
||||
ContentApprox getContent() { result = c }
|
||||
|
||||
/** Gets the container type. */
|
||||
DataFlowType getContainerType() { result = t }
|
||||
|
||||
@@ -1414,8 +1408,6 @@ abstract class ApproxAccessPathFront extends TApproxAccessPathFront {
|
||||
|
||||
abstract boolean toBoolNonEmpty();
|
||||
|
||||
TypedContentApprox getHead() { this = TApproxFrontHead(result) }
|
||||
|
||||
pragma[nomagic]
|
||||
TypedContent getAHead() {
|
||||
exists(TypedContentApprox cont |
|
||||
|
||||
@@ -45,16 +45,6 @@ module Consistency {
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
/** Holds if `(c, pos, p)` should be excluded from the consistency test `uniqueParameterNodeAtPosition`. */
|
||||
predicate uniqueParameterNodeAtPositionExclude(DataFlowCallable c, ParameterPosition pos, Node p) {
|
||||
none()
|
||||
}
|
||||
|
||||
/** Holds if `(c, pos, p)` should be excluded from the consistency test `uniqueParameterNodePosition`. */
|
||||
predicate uniqueParameterNodePositionExclude(DataFlowCallable c, ParameterPosition pos, Node p) {
|
||||
none()
|
||||
}
|
||||
}
|
||||
|
||||
private class RelevantNode extends Node {
|
||||
@@ -111,7 +101,9 @@ module Consistency {
|
||||
exists(int c |
|
||||
c =
|
||||
strictcount(Node n |
|
||||
not n.hasLocationInfo(_, _, _, _, _) and
|
||||
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
|
||||
@@ -256,7 +248,6 @@ module Consistency {
|
||||
query predicate uniqueParameterNodeAtPosition(
|
||||
DataFlowCallable c, ParameterPosition pos, Node p, string msg
|
||||
) {
|
||||
not any(ConsistencyConfiguration conf).uniqueParameterNodeAtPositionExclude(c, pos, p) and
|
||||
isParameterNode(p, c, pos) and
|
||||
not exists(unique(Node p0 | isParameterNode(p0, c, pos))) and
|
||||
msg = "Parameters with overlapping positions."
|
||||
@@ -265,7 +256,6 @@ module Consistency {
|
||||
query predicate uniqueParameterNodePosition(
|
||||
DataFlowCallable c, ParameterPosition pos, Node p, string msg
|
||||
) {
|
||||
not any(ConsistencyConfiguration conf).uniqueParameterNodePositionExclude(c, pos, p) and
|
||||
isParameterNode(p, c, pos) and
|
||||
not exists(unique(ParameterPosition pos0 | isParameterNode(p, c, pos0))) and
|
||||
msg = "Parameter node with multiple positions."
|
||||
|
||||
@@ -565,12 +565,3 @@ private class MyConsistencyConfiguration extends Consistency::ConsistencyConfigu
|
||||
any()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an additional term that is added to the `join` and `branch` computations to reflect
|
||||
* an additional forward or backwards branching factor that is not taken into account
|
||||
* when calculating the (virtual) dispatch cost.
|
||||
*
|
||||
* Argument `arg` is part of a path from a source to a sink, and `p` is the target parameter.
|
||||
*/
|
||||
int getAdditionalFlowIntoCallNodeTerm(ArgumentNode arg, ParameterNode p) { none() }
|
||||
|
||||
@@ -97,23 +97,23 @@ private string getNodeProperty(DataFlow::Node node, string key) {
|
||||
|
|
||||
kind, ", "
|
||||
)
|
||||
// or
|
||||
// // Is there partial flow from a source to this node?
|
||||
// // This property will only be emitted if partial flow is enabled by overriding
|
||||
// // `DataFlow::Configuration::explorationLimit()`.
|
||||
// key = "pflow" and
|
||||
// result =
|
||||
// strictconcat(DataFlow::PartialPathNode sourceNode, DataFlow::PartialPathNode destNode, int dist,
|
||||
// int order1, int order2 |
|
||||
// any(DataFlow::Configuration cfg).hasPartialFlow(sourceNode, destNode, dist) and
|
||||
// destNode.getNode() = node and
|
||||
// // Only print flow from a source in the same function.
|
||||
// sourceNode.getNode().getEnclosingCallable() = node.getEnclosingCallable()
|
||||
// |
|
||||
// nodeId(sourceNode.getNode(), order1, order2) + "+" + dist.toString(), ", "
|
||||
// order by
|
||||
// order1, order2, dist desc
|
||||
// )
|
||||
or
|
||||
// Is there partial flow from a source to this node?
|
||||
// This property will only be emitted if partial flow is enabled by overriding
|
||||
// `DataFlow::Configuration::explorationLimit()`.
|
||||
key = "pflow" and
|
||||
result =
|
||||
strictconcat(DataFlow::PartialPathNode sourceNode, DataFlow::PartialPathNode destNode, int dist,
|
||||
int order1, int order2 |
|
||||
any(DataFlow::Configuration cfg).hasPartialFlow(sourceNode, destNode, dist) and
|
||||
destNode.getNode() = node and
|
||||
// Only print flow from a source in the same function.
|
||||
sourceNode.getNode().getEnclosingCallable() = node.getEnclosingCallable()
|
||||
|
|
||||
nodeId(sourceNode.getNode(), order1, order2) + "+" + dist.toString(), ", "
|
||||
order by
|
||||
order1, order2, dist desc
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* Provides classes for performing local (intra-procedural) and
|
||||
* global (inter-procedural) taint-tracking analyses.
|
||||
*/
|
||||
|
||||
import TaintTrackingParameter::Public
|
||||
private import TaintTrackingParameter::Private
|
||||
|
||||
private module AddTaintDefaults<DataFlowInternal::FullStateConfigSig Config> implements
|
||||
DataFlowInternal::FullStateConfigSig {
|
||||
import Config
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
Config::isBarrier(node) or defaultTaintSanitizer(node)
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
Config::isAdditionalFlowStep(node1, node2) or
|
||||
defaultAdditionalTaintStep(node1, node2)
|
||||
}
|
||||
|
||||
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
||||
Config::allowImplicitRead(node, c)
|
||||
or
|
||||
(
|
||||
Config::isSink(node, _) or
|
||||
Config::isAdditionalFlowStep(node, _) or
|
||||
Config::isAdditionalFlowStep(node, _, _, _)
|
||||
) and
|
||||
defaultImplicitTaintRead(node, c)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a standard taint tracking computation.
|
||||
*/
|
||||
module Make<DataFlow::ConfigSig Config> implements DataFlow::DataFlowSig {
|
||||
private module Config0 implements DataFlowInternal::FullStateConfigSig {
|
||||
import DataFlowInternal::DefaultState<Config>
|
||||
import Config
|
||||
}
|
||||
|
||||
private module C implements DataFlowInternal::FullStateConfigSig {
|
||||
import AddTaintDefaults<Config0>
|
||||
}
|
||||
|
||||
import DataFlowInternal::Impl<C>
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a taint tracking computation using flow state.
|
||||
*/
|
||||
module MakeWithState<DataFlow::StateConfigSig Config> implements DataFlow::DataFlowSig {
|
||||
private module Config0 implements DataFlowInternal::FullStateConfigSig {
|
||||
import Config
|
||||
}
|
||||
|
||||
private module C implements DataFlowInternal::FullStateConfigSig {
|
||||
import AddTaintDefaults<Config0>
|
||||
}
|
||||
|
||||
import DataFlowInternal::Impl<C>
|
||||
}
|
||||
@@ -2,5 +2,4 @@ import experimental.semmle.code.cpp.ir.dataflow.internal.TaintTrackingUtil as Pu
|
||||
|
||||
module Private {
|
||||
import experimental.semmle.code.cpp.ir.dataflow.DataFlow::DataFlow as DataFlow
|
||||
import experimental.semmle.code.cpp.ir.dataflow.internal.DataFlowImpl as DataFlowInternal
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ private predicate allocation(Instruction array, Length length, int delta) {
|
||||
length.(VNLength).getInstruction().getConvertedResultExpression() = lengthExpr
|
||||
)
|
||||
or
|
||||
not deconstructMallocSizeExpr(alloc.getSizeExpr(), _, _) and
|
||||
not exists(int d | deconstructMallocSizeExpr(alloc.getSizeExpr(), _, d)) and
|
||||
length.(VNLength).getInstruction().getConvertedResultExpression() = alloc.getSizeExpr() and
|
||||
delta = 0
|
||||
)
|
||||
|
||||
@@ -543,7 +543,9 @@ private predicate boundedPhiCand(
|
||||
PhiInstruction phi, boolean upper, Bound b, int delta, boolean fromBackEdge, int origdelta,
|
||||
Reason reason
|
||||
) {
|
||||
boundedPhiInp(phi, _, b, delta, upper, fromBackEdge, origdelta, reason)
|
||||
exists(PhiInputOperand op |
|
||||
boundedPhiInp(phi, op, b, delta, upper, fromBackEdge, origdelta, reason)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,263 +0,0 @@
|
||||
private import cpp
|
||||
private import experimental.semmle.code.cpp.models.interfaces.SimpleRangeAnalysisExpr
|
||||
private import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
|
||||
|
||||
float evaluateConstantExpr(Expr e) {
|
||||
result = e.getValue().toFloat()
|
||||
or
|
||||
// This handles when a constant value is put into a variable
|
||||
// and the variable is used later
|
||||
exists(SsaDefinition defn, StackVariable sv |
|
||||
defn.getAUse(sv) = e and
|
||||
result = defn.getDefiningValue(sv).getValue().toFloat()
|
||||
)
|
||||
}
|
||||
|
||||
// If the constant right operand is negative or is greater than or equal to the number of
|
||||
// bits in the left operands type, then the result is undefined (except on the IA-32
|
||||
// architecture where the shift value is masked with 0b00011111, but we can't
|
||||
// assume the architecture).
|
||||
bindingset[val]
|
||||
private predicate isValidShiftExprShift(float val, Expr l) {
|
||||
val >= 0 and
|
||||
// We use getFullyConverted because the spec says to use the *promoted* left operand
|
||||
val < (l.getFullyConverted().getUnderlyingType().getSize() * 8)
|
||||
}
|
||||
|
||||
bindingset[val, shift, max_val]
|
||||
private predicate canLShiftOverflow(int val, int shift, int max_val) {
|
||||
// val << shift = val * 2^shift > max_val => val > max_val/2^shift = max_val >> b
|
||||
val > max_val.bitShiftRight(shift)
|
||||
}
|
||||
|
||||
/**
|
||||
* A range analysis expression consisting of the `>>` or `>>=` operator when at least
|
||||
* one operand is a constant (and if the right operand is a constant, it must be "valid"
|
||||
* (see `isValidShiftExprShift`)). When handling any undefined behavior, it leaves the
|
||||
* values unconstrained. From the C++ standard: "The behavior is undefined if the right
|
||||
* operand is negative, or greater than or equal to the length in bits of the promoted
|
||||
* left operand. The value of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an
|
||||
* unsigned type or if E1 has a signed type and a non-negative value, the value of the
|
||||
* result is the integral part of the quotient of E1/2^E2. If E1 has a signed type and a
|
||||
* negative value, the resulting value is implementation-defined."
|
||||
*/
|
||||
class ConstantRShiftExprRange extends SimpleRangeAnalysisExpr {
|
||||
/**
|
||||
* Holds for `a >> b` or `a >>= b` in one of the following two cases:
|
||||
* 1. `a` is a constant and `b` is not
|
||||
* 2. `b` is constant
|
||||
*
|
||||
* We don't handle the case where `a` and `b` are both non-constant values.
|
||||
*/
|
||||
ConstantRShiftExprRange() {
|
||||
getUnspecifiedType() instanceof IntegralType and
|
||||
exists(Expr l, Expr r |
|
||||
l = this.(RShiftExpr).getLeftOperand() and
|
||||
r = this.(RShiftExpr).getRightOperand()
|
||||
or
|
||||
l = this.(AssignRShiftExpr).getLValue() and
|
||||
r = this.(AssignRShiftExpr).getRValue()
|
||||
|
|
||||
l.getUnspecifiedType() instanceof IntegralType and
|
||||
r.getUnspecifiedType() instanceof IntegralType and
|
||||
(
|
||||
// If the left operand is a constant, verify that the right operand is not a constant
|
||||
exists(evaluateConstantExpr(l)) and not exists(evaluateConstantExpr(r))
|
||||
or
|
||||
// If the right operand is a constant, check if it is a valid shift expression
|
||||
exists(float constROp |
|
||||
constROp = evaluateConstantExpr(r) and isValidShiftExprShift(constROp, l)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Expr getLeftOperand() {
|
||||
result = this.(RShiftExpr).getLeftOperand() or
|
||||
result = this.(AssignRShiftExpr).getLValue()
|
||||
}
|
||||
|
||||
Expr getRightOperand() {
|
||||
result = this.(RShiftExpr).getRightOperand() or
|
||||
result = this.(AssignRShiftExpr).getRValue()
|
||||
}
|
||||
|
||||
override float getLowerBounds() {
|
||||
exists(int lLower, int lUpper, int rLower, int rUpper |
|
||||
lLower = getFullyConvertedLowerBounds(getLeftOperand()) and
|
||||
lUpper = getFullyConvertedUpperBounds(getLeftOperand()) and
|
||||
rLower = getFullyConvertedLowerBounds(getRightOperand()) and
|
||||
rUpper = getFullyConvertedUpperBounds(getRightOperand()) and
|
||||
lLower <= lUpper and
|
||||
rLower <= rUpper
|
||||
|
|
||||
if
|
||||
lLower < 0
|
||||
or
|
||||
not (
|
||||
isValidShiftExprShift(rLower, getLeftOperand()) and
|
||||
isValidShiftExprShift(rUpper, getLeftOperand())
|
||||
)
|
||||
then
|
||||
// We don't want to deal with shifting negative numbers at the moment,
|
||||
// and a negative shift is implementation defined, so we set the result
|
||||
// to the minimum value
|
||||
result = exprMinVal(this)
|
||||
else
|
||||
// We can get the smallest value by shifting the smallest bound by the largest bound
|
||||
result = lLower.bitShiftRight(rUpper)
|
||||
)
|
||||
}
|
||||
|
||||
override float getUpperBounds() {
|
||||
exists(int lLower, int lUpper, int rLower, int rUpper |
|
||||
lLower = getFullyConvertedLowerBounds(getLeftOperand()) and
|
||||
lUpper = getFullyConvertedUpperBounds(getLeftOperand()) and
|
||||
rLower = getFullyConvertedLowerBounds(getRightOperand()) and
|
||||
rUpper = getFullyConvertedUpperBounds(getRightOperand()) and
|
||||
lLower <= lUpper and
|
||||
rLower <= rUpper
|
||||
|
|
||||
if
|
||||
lLower < 0
|
||||
or
|
||||
not (
|
||||
isValidShiftExprShift(rLower, getLeftOperand()) and
|
||||
isValidShiftExprShift(rUpper, getLeftOperand())
|
||||
)
|
||||
then
|
||||
// We don't want to deal with shifting negative numbers at the moment,
|
||||
// and a negative shift is implementation defined, so we set the result
|
||||
// to the maximum value
|
||||
result = exprMaxVal(this)
|
||||
else
|
||||
// We can get the largest value by shifting the largest bound by the smallest bound
|
||||
result = lUpper.bitShiftRight(rLower)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate dependsOnChild(Expr child) {
|
||||
child = getLeftOperand() or child = getRightOperand()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A range analysis expression consisting of the `<<` or `<<=` operator when at least
|
||||
* one operand is a constant (and if the right operand is a constant, it must be "valid"
|
||||
* (see `isValidShiftExprShift`)). When handling any undefined behavior, it leaves the
|
||||
* values unconstrained. From the C++ standard: "The behavior is undefined if the right
|
||||
* operand is negative, or greater than or equal to the length in bits of the promoted left operand.
|
||||
* The value of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are zero-filled. If E1
|
||||
* has an unsigned type, the value of the result is E1 x 2 E2, reduced modulo one more than the
|
||||
* maximum value representable in the result type. Otherwise, if E1 has a signed type and
|
||||
* non-negative value, and E1 x 2 E2 is representable in the corresponding unsigned type of the
|
||||
* result type, then that value, converted to the result type, is the resulting value; otherwise,
|
||||
* the behavior is undefined."
|
||||
*/
|
||||
class ConstantLShiftExprRange extends SimpleRangeAnalysisExpr {
|
||||
/**
|
||||
* Holds for `a << b` or `a <<= b` in one of the following two cases:
|
||||
* 1. `a` is a constant and `b` is not
|
||||
* 2. `b` is constant
|
||||
*
|
||||
* We don't handle the case where `a` and `b` are both non-constant values.
|
||||
*/
|
||||
ConstantLShiftExprRange() {
|
||||
getUnspecifiedType() instanceof IntegralType and
|
||||
exists(Expr l, Expr r |
|
||||
l = this.(LShiftExpr).getLeftOperand() and
|
||||
r = this.(LShiftExpr).getRightOperand()
|
||||
or
|
||||
l = this.(AssignLShiftExpr).getLValue() and
|
||||
r = this.(AssignLShiftExpr).getRValue()
|
||||
|
|
||||
l.getUnspecifiedType() instanceof IntegralType and
|
||||
r.getUnspecifiedType() instanceof IntegralType and
|
||||
(
|
||||
// If the left operand is a constant, verify that the right operand is not a constant
|
||||
exists(evaluateConstantExpr(l)) and not exists(evaluateConstantExpr(r))
|
||||
or
|
||||
// If the right operand is a constant, check if it is a valid shift expression
|
||||
exists(float constROp |
|
||||
constROp = evaluateConstantExpr(r) and isValidShiftExprShift(constROp, l)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Expr getLeftOperand() {
|
||||
result = this.(LShiftExpr).getLeftOperand() or
|
||||
result = this.(AssignLShiftExpr).getLValue()
|
||||
}
|
||||
|
||||
Expr getRightOperand() {
|
||||
result = this.(LShiftExpr).getRightOperand() or
|
||||
result = this.(AssignLShiftExpr).getRValue()
|
||||
}
|
||||
|
||||
override float getLowerBounds() {
|
||||
exists(int lLower, int lUpper, int rLower, int rUpper |
|
||||
lLower = getFullyConvertedLowerBounds(getLeftOperand()) and
|
||||
lUpper = getFullyConvertedUpperBounds(getLeftOperand()) and
|
||||
rLower = getFullyConvertedLowerBounds(getRightOperand()) and
|
||||
rUpper = getFullyConvertedUpperBounds(getRightOperand()) and
|
||||
lLower <= lUpper and
|
||||
rLower <= rUpper
|
||||
|
|
||||
if
|
||||
lLower < 0
|
||||
or
|
||||
not (
|
||||
isValidShiftExprShift(rLower, getLeftOperand()) and
|
||||
isValidShiftExprShift(rUpper, getLeftOperand())
|
||||
)
|
||||
then
|
||||
// We don't want to deal with shifting negative numbers at the moment,
|
||||
// and a negative shift is undefined, so we set to the minimum value
|
||||
result = exprMinVal(this)
|
||||
else
|
||||
// If we have `0b01010000 << [0, 2]`, the max value for 8 bits is 0b10100000
|
||||
// (a shift of 1) but doing a shift by the upper bound would give 0b01000000.
|
||||
// So if the left shift operation causes an overflow, we just assume the max value
|
||||
// If necessary, we may be able to improve this bound in the future
|
||||
if canLShiftOverflow(lUpper, rUpper, exprMaxVal(this))
|
||||
then result = exprMinVal(this)
|
||||
else result = lLower.bitShiftLeft(rLower)
|
||||
)
|
||||
}
|
||||
|
||||
override float getUpperBounds() {
|
||||
exists(int lLower, int lUpper, int rLower, int rUpper |
|
||||
lLower = getFullyConvertedLowerBounds(getLeftOperand()) and
|
||||
lUpper = getFullyConvertedUpperBounds(getLeftOperand()) and
|
||||
rLower = getFullyConvertedLowerBounds(getRightOperand()) and
|
||||
rUpper = getFullyConvertedUpperBounds(getRightOperand()) and
|
||||
lLower <= lUpper and
|
||||
rLower <= rUpper
|
||||
|
|
||||
if
|
||||
lLower < 0
|
||||
or
|
||||
not (
|
||||
isValidShiftExprShift(rLower, getLeftOperand()) and
|
||||
isValidShiftExprShift(rUpper, getLeftOperand())
|
||||
)
|
||||
then
|
||||
// We don't want to deal with shifting negative numbers at the moment,
|
||||
// and a negative shift is undefined, so we set it to the maximum value
|
||||
result = exprMaxVal(this)
|
||||
else
|
||||
// If we have `0b01010000 << [0, 2]`, the max value for 8 bits is 0b10100000
|
||||
// (a shift of 1) but doing a shift by the upper bound would give 0b01000000.
|
||||
// So if the left shift operation causes an overflow, we just assume the max value
|
||||
// If necessary, we may be able to improve this bound in the future
|
||||
if canLShiftOverflow(lUpper, rUpper, exprMaxVal(this))
|
||||
then result = exprMaxVal(this)
|
||||
else result = lUpper.bitShiftLeft(rUpper)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate dependsOnChild(Expr child) {
|
||||
child = getLeftOperand() or child = getRightOperand()
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
private import RangeAnalysisStage
|
||||
|
||||
module FloatDelta implements DeltaSig {
|
||||
class Delta = float;
|
||||
|
||||
bindingset[d]
|
||||
bindingset[result]
|
||||
float toFloat(Delta d) { result = d }
|
||||
|
||||
bindingset[d]
|
||||
bindingset[result]
|
||||
int toInt(Delta d) { result = d }
|
||||
|
||||
bindingset[n]
|
||||
bindingset[result]
|
||||
Delta fromInt(int n) { result = n }
|
||||
|
||||
bindingset[f]
|
||||
Delta fromFloat(float f) {
|
||||
result =
|
||||
min(float diff, float res |
|
||||
diff = (res - f) and res = f.ceil()
|
||||
or
|
||||
diff = (f - res) and res = f.floor()
|
||||
|
|
||||
res order by diff
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -14,328 +14,321 @@ private import ModulusAnalysisSpecific::Private
|
||||
private import experimental.semmle.code.cpp.semantic.Semantic
|
||||
private import ConstantAnalysis
|
||||
private import RangeUtils
|
||||
private import RangeAnalysisStage
|
||||
|
||||
module ModulusAnalysis<DeltaSig D, BoundSig<D> Bounds, UtilSig<D> U> {
|
||||
/**
|
||||
* Holds if `e + delta` equals `v` at `pos`.
|
||||
*/
|
||||
private predicate valueFlowStepSsa(SemSsaVariable v, SemSsaReadPosition pos, SemExpr e, int delta) {
|
||||
U::semSsaUpdateStep(v, e, D::fromInt(delta)) and pos.hasReadOfVar(v)
|
||||
or
|
||||
exists(SemGuard guard, boolean testIsTrue |
|
||||
pos.hasReadOfVar(v) and
|
||||
guard = U::semEqFlowCond(v, e, D::fromInt(delta), true, testIsTrue) and
|
||||
semGuardDirectlyControlsSsaRead(guard, pos, testIsTrue)
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Holds if `e + delta` equals `v` at `pos`.
|
||||
*/
|
||||
private predicate valueFlowStepSsa(SemSsaVariable v, SemSsaReadPosition pos, SemExpr e, int delta) {
|
||||
semSsaUpdateStep(v, e, delta) and pos.hasReadOfVar(v)
|
||||
or
|
||||
exists(SemGuard guard, boolean testIsTrue |
|
||||
pos.hasReadOfVar(v) and
|
||||
guard = semEqFlowCond(v, e, delta, true, testIsTrue) and
|
||||
semGuardDirectlyControlsSsaRead(guard, pos, testIsTrue)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `add` is the addition of `larg` and `rarg`, neither of which are
|
||||
* `ConstantIntegerExpr`s.
|
||||
*/
|
||||
private predicate nonConstAddition(SemExpr add, SemExpr larg, SemExpr rarg) {
|
||||
exists(SemAddExpr a | a = add |
|
||||
larg = a.getLeftOperand() and
|
||||
rarg = a.getRightOperand()
|
||||
) and
|
||||
not larg instanceof SemConstantIntegerExpr and
|
||||
not rarg instanceof SemConstantIntegerExpr
|
||||
}
|
||||
/**
|
||||
* Holds if `add` is the addition of `larg` and `rarg`, neither of which are
|
||||
* `ConstantIntegerExpr`s.
|
||||
*/
|
||||
private predicate nonConstAddition(SemExpr add, SemExpr larg, SemExpr rarg) {
|
||||
exists(SemAddExpr a | a = add |
|
||||
larg = a.getLeftOperand() and
|
||||
rarg = a.getRightOperand()
|
||||
) and
|
||||
not larg instanceof SemConstantIntegerExpr and
|
||||
not rarg instanceof SemConstantIntegerExpr
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `sub` is the subtraction of `larg` and `rarg`, where `rarg` is not
|
||||
* a `ConstantIntegerExpr`.
|
||||
*/
|
||||
private predicate nonConstSubtraction(SemExpr sub, SemExpr larg, SemExpr rarg) {
|
||||
exists(SemSubExpr s | s = sub |
|
||||
larg = s.getLeftOperand() and
|
||||
rarg = s.getRightOperand()
|
||||
) and
|
||||
not rarg instanceof SemConstantIntegerExpr
|
||||
}
|
||||
/**
|
||||
* Holds if `sub` is the subtraction of `larg` and `rarg`, where `rarg` is not
|
||||
* a `ConstantIntegerExpr`.
|
||||
*/
|
||||
private predicate nonConstSubtraction(SemExpr sub, SemExpr larg, SemExpr rarg) {
|
||||
exists(SemSubExpr s | s = sub |
|
||||
larg = s.getLeftOperand() and
|
||||
rarg = s.getRightOperand()
|
||||
) and
|
||||
not rarg instanceof SemConstantIntegerExpr
|
||||
}
|
||||
|
||||
/** Gets an expression that is the remainder modulo `mod` of `arg`. */
|
||||
private SemExpr modExpr(SemExpr arg, int mod) {
|
||||
exists(SemRemExpr rem |
|
||||
result = rem and
|
||||
arg = rem.getLeftOperand() and
|
||||
rem.getRightOperand().(SemConstantIntegerExpr).getIntValue() = mod and
|
||||
mod >= 2
|
||||
)
|
||||
or
|
||||
exists(SemConstantIntegerExpr c |
|
||||
mod = 2.pow([1 .. 30]) and
|
||||
c.getIntValue() = mod - 1 and
|
||||
result.(SemBitAndExpr).hasOperands(arg, c)
|
||||
)
|
||||
}
|
||||
/** Gets an expression that is the remainder modulo `mod` of `arg`. */
|
||||
private SemExpr modExpr(SemExpr arg, int mod) {
|
||||
exists(SemRemExpr rem |
|
||||
result = rem and
|
||||
arg = rem.getLeftOperand() and
|
||||
rem.getRightOperand().(SemConstantIntegerExpr).getIntValue() = mod and
|
||||
mod >= 2
|
||||
)
|
||||
or
|
||||
exists(SemConstantIntegerExpr c |
|
||||
mod = 2.pow([1 .. 30]) and
|
||||
c.getIntValue() = mod - 1 and
|
||||
result.(SemBitAndExpr).hasOperands(arg, c)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a guard that tests whether `v` is congruent with `val` modulo `mod` on
|
||||
* its `testIsTrue` branch.
|
||||
*/
|
||||
private SemGuard moduloCheck(SemSsaVariable v, int val, int mod, boolean testIsTrue) {
|
||||
exists(SemExpr rem, SemConstantIntegerExpr c, int r, boolean polarity |
|
||||
result.isEquality(rem, c, polarity) and
|
||||
c.getIntValue() = r and
|
||||
rem = modExpr(v.getAUse(), mod) and
|
||||
(
|
||||
testIsTrue = polarity and val = r
|
||||
or
|
||||
testIsTrue = polarity.booleanNot() and
|
||||
mod = 2 and
|
||||
val = 1 - r and
|
||||
(r = 0 or r = 1)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a guard ensures that `v` at `pos` is congruent with `val` modulo `mod`.
|
||||
*/
|
||||
private predicate moduloGuardedRead(SemSsaVariable v, SemSsaReadPosition pos, int val, int mod) {
|
||||
exists(SemGuard guard, boolean testIsTrue |
|
||||
pos.hasReadOfVar(v) and
|
||||
guard = moduloCheck(v, val, mod, testIsTrue) and
|
||||
semGuardControlsSsaRead(guard, pos, testIsTrue)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `factor` is a power of 2 that divides `mask`. */
|
||||
bindingset[mask]
|
||||
private predicate andmaskFactor(int mask, int factor) {
|
||||
mask % factor = 0 and
|
||||
factor = 2.pow([1 .. 30])
|
||||
}
|
||||
|
||||
/** Holds if `e` is evenly divisible by `factor`. */
|
||||
private predicate evenlyDivisibleExpr(SemExpr e, int factor) {
|
||||
exists(SemConstantIntegerExpr c, int k | k = c.getIntValue() |
|
||||
e.(SemMulExpr).getAnOperand() = c and factor = k.abs() and factor >= 2
|
||||
/**
|
||||
* Gets a guard that tests whether `v` is congruent with `val` modulo `mod` on
|
||||
* its `testIsTrue` branch.
|
||||
*/
|
||||
private SemGuard moduloCheck(SemSsaVariable v, int val, int mod, boolean testIsTrue) {
|
||||
exists(SemExpr rem, SemConstantIntegerExpr c, int r, boolean polarity |
|
||||
result.isEquality(rem, c, polarity) and
|
||||
c.getIntValue() = r and
|
||||
rem = modExpr(v.getAUse(), mod) and
|
||||
(
|
||||
testIsTrue = polarity and val = r
|
||||
or
|
||||
e.(SemShiftLeftExpr).getRightOperand() = c and factor = 2.pow(k) and k > 0
|
||||
or
|
||||
e.(SemBitAndExpr).getAnOperand() = c and factor = max(int f | andmaskFactor(k, f))
|
||||
testIsTrue = polarity.booleanNot() and
|
||||
mod = 2 and
|
||||
val = 1 - r and
|
||||
(r = 0 or r = 1)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `rix` is the number of input edges to `phi`.
|
||||
*/
|
||||
private predicate maxPhiInputRank(SemSsaPhiNode phi, int rix) {
|
||||
rix = max(int r | rankedPhiInput(phi, _, _, r))
|
||||
}
|
||||
/**
|
||||
* Holds if a guard ensures that `v` at `pos` is congruent with `val` modulo `mod`.
|
||||
*/
|
||||
private predicate moduloGuardedRead(SemSsaVariable v, SemSsaReadPosition pos, int val, int mod) {
|
||||
exists(SemGuard guard, boolean testIsTrue |
|
||||
pos.hasReadOfVar(v) and
|
||||
guard = moduloCheck(v, val, mod, testIsTrue) and
|
||||
semGuardControlsSsaRead(guard, pos, testIsTrue)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the remainder of `val` modulo `mod`.
|
||||
*
|
||||
* For `mod = 0` the result equals `val` and for `mod > 1` the result is within
|
||||
* the range `[0 .. mod-1]`.
|
||||
*/
|
||||
bindingset[val, mod]
|
||||
private int remainder(int val, int mod) {
|
||||
mod = 0 and result = val
|
||||
/** Holds if `factor` is a power of 2 that divides `mask`. */
|
||||
bindingset[mask]
|
||||
private predicate andmaskFactor(int mask, int factor) {
|
||||
mask % factor = 0 and
|
||||
factor = 2.pow([1 .. 30])
|
||||
}
|
||||
|
||||
/** Holds if `e` is evenly divisible by `factor`. */
|
||||
private predicate evenlyDivisibleExpr(SemExpr e, int factor) {
|
||||
exists(SemConstantIntegerExpr c, int k | k = c.getIntValue() |
|
||||
e.(SemMulExpr).getAnOperand() = c and factor = k.abs() and factor >= 2
|
||||
or
|
||||
mod > 1 and result = ((val % mod) + mod) % mod
|
||||
}
|
||||
e.(SemShiftLeftExpr).getRightOperand() = c and factor = 2.pow(k) and k > 0
|
||||
or
|
||||
e.(SemBitAndExpr).getAnOperand() = c and factor = max(int f | andmaskFactor(k, f))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `inp` is an input to `phi` and equals `phi` modulo `mod` along `edge`.
|
||||
*/
|
||||
private predicate phiSelfModulus(
|
||||
SemSsaPhiNode phi, SemSsaVariable inp, SemSsaReadPositionPhiInputEdge edge, int mod
|
||||
) {
|
||||
exists(Bounds::SemSsaBound phibound, int v, int m |
|
||||
edge.phiInput(phi, inp) and
|
||||
phibound.getAVariable() = phi and
|
||||
ssaModulus(inp, edge, phibound, v, m) and
|
||||
mod = m.gcd(v) and
|
||||
mod != 1
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Holds if `rix` is the number of input edges to `phi`.
|
||||
*/
|
||||
private predicate maxPhiInputRank(SemSsaPhiNode phi, int rix) {
|
||||
rix = max(int r | rankedPhiInput(phi, _, _, r))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `b + val` modulo `mod` is a candidate congruence class for `phi`.
|
||||
*/
|
||||
private predicate phiModulusInit(SemSsaPhiNode phi, Bounds::SemBound b, int val, int mod) {
|
||||
exists(SemSsaVariable inp, SemSsaReadPositionPhiInputEdge edge |
|
||||
edge.phiInput(phi, inp) and
|
||||
ssaModulus(inp, edge, b, val, mod)
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Gets the remainder of `val` modulo `mod`.
|
||||
*
|
||||
* For `mod = 0` the result equals `val` and for `mod > 1` the result is within
|
||||
* the range `[0 .. mod-1]`.
|
||||
*/
|
||||
bindingset[val, mod]
|
||||
private int remainder(int val, int mod) {
|
||||
mod = 0 and result = val
|
||||
or
|
||||
mod > 1 and result = ((val % mod) + mod) % mod
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if all inputs to `phi` numbered `1` to `rix` are equal to `b + val` modulo `mod`.
|
||||
/**
|
||||
* Holds if `inp` is an input to `phi` and equals `phi` modulo `mod` along `edge`.
|
||||
*/
|
||||
private predicate phiSelfModulus(
|
||||
SemSsaPhiNode phi, SemSsaVariable inp, SemSsaReadPositionPhiInputEdge edge, int mod
|
||||
) {
|
||||
exists(SemSsaBound phibound, int v, int m |
|
||||
edge.phiInput(phi, inp) and
|
||||
phibound.getAVariable() = phi and
|
||||
ssaModulus(inp, edge, phibound, v, m) and
|
||||
mod = m.gcd(v) and
|
||||
mod != 1
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `b + val` modulo `mod` is a candidate congruence class for `phi`.
|
||||
*/
|
||||
private predicate phiModulusInit(SemSsaPhiNode phi, SemBound b, int val, int mod) {
|
||||
exists(SemSsaVariable inp, SemSsaReadPositionPhiInputEdge edge |
|
||||
edge.phiInput(phi, inp) and
|
||||
ssaModulus(inp, edge, b, val, mod)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if all inputs to `phi` numbered `1` to `rix` are equal to `b + val` modulo `mod`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate phiModulusRankStep(SemSsaPhiNode phi, SemBound b, int val, int mod, int rix) {
|
||||
/*
|
||||
* base case. If any phi input is equal to `b + val` modulo `mod`, that's a potential congruence
|
||||
* class for the phi node.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate phiModulusRankStep(
|
||||
SemSsaPhiNode phi, Bounds::SemBound b, int val, int mod, int rix
|
||||
) {
|
||||
|
||||
rix = 0 and
|
||||
phiModulusInit(phi, b, val, mod)
|
||||
or
|
||||
exists(SemSsaVariable inp, SemSsaReadPositionPhiInputEdge edge, int v1, int m1 |
|
||||
mod != 1 and
|
||||
val = remainder(v1, mod)
|
||||
|
|
||||
/*
|
||||
* base case. If any phi input is equal to `b + val` modulo `mod`, that's a potential congruence
|
||||
* class for the phi node.
|
||||
* Recursive case. If `inp` = `b + v2` mod `m2`, we combine that with the preceding potential
|
||||
* congruence class `b + v1` mod `m1`. The result will be the congruence class of `v1` modulo
|
||||
* the greatest common denominator of `m1`, `m2`, and `v1 - v2`.
|
||||
*/
|
||||
|
||||
rix = 0 and
|
||||
phiModulusInit(phi, b, val, mod)
|
||||
or
|
||||
exists(SemSsaVariable inp, SemSsaReadPositionPhiInputEdge edge, int v1, int m1 |
|
||||
mod != 1 and
|
||||
val = remainder(v1, mod)
|
||||
|
|
||||
/*
|
||||
* Recursive case. If `inp` = `b + v2` mod `m2`, we combine that with the preceding potential
|
||||
* congruence class `b + v1` mod `m1`. The result will be the congruence class of `v1` modulo
|
||||
* the greatest common denominator of `m1`, `m2`, and `v1 - v2`.
|
||||
*/
|
||||
|
||||
exists(int v2, int m2 |
|
||||
rankedPhiInput(pragma[only_bind_out](phi), inp, edge, rix) and
|
||||
phiModulusRankStep(phi, b, v1, m1, rix - 1) and
|
||||
ssaModulus(inp, edge, b, v2, m2) and
|
||||
mod = m1.gcd(m2).gcd(v1 - v2)
|
||||
)
|
||||
or
|
||||
/*
|
||||
* Recursive case. If `inp` = `phi` mod `m2`, we combine that with the preceding potential
|
||||
* congruence class `b + v1` mod `m1`. The result will be a congruence class modulo the greatest
|
||||
* common denominator of `m1` and `m2`.
|
||||
*/
|
||||
|
||||
exists(int m2 |
|
||||
rankedPhiInput(phi, inp, edge, rix) and
|
||||
phiModulusRankStep(phi, b, v1, m1, rix - 1) and
|
||||
phiSelfModulus(phi, inp, edge, m2) and
|
||||
mod = m1.gcd(m2)
|
||||
)
|
||||
exists(int v2, int m2 |
|
||||
rankedPhiInput(pragma[only_bind_out](phi), inp, edge, rix) and
|
||||
phiModulusRankStep(phi, b, v1, m1, rix - 1) and
|
||||
ssaModulus(inp, edge, b, v2, m2) and
|
||||
mod = m1.gcd(m2).gcd(v1 - v2)
|
||||
)
|
||||
}
|
||||
or
|
||||
/*
|
||||
* Recursive case. If `inp` = `phi` mod `m2`, we combine that with the preceding potential
|
||||
* congruence class `b + v1` mod `m1`. The result will be a congruence class modulo the greatest
|
||||
* common denominator of `m1` and `m2`.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Holds if `phi` is equal to `b + val` modulo `mod`.
|
||||
*/
|
||||
private predicate phiModulus(SemSsaPhiNode phi, Bounds::SemBound b, int val, int mod) {
|
||||
exists(int r |
|
||||
maxPhiInputRank(phi, r) and
|
||||
phiModulusRankStep(phi, b, val, mod, r)
|
||||
exists(int m2 |
|
||||
rankedPhiInput(phi, inp, edge, rix) and
|
||||
phiModulusRankStep(phi, b, v1, m1, rix - 1) and
|
||||
phiSelfModulus(phi, inp, edge, m2) and
|
||||
mod = m1.gcd(m2)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `v` at `pos` is equal to `b + val` modulo `mod`.
|
||||
*/
|
||||
private predicate ssaModulus(
|
||||
SemSsaVariable v, SemSsaReadPosition pos, Bounds::SemBound b, int val, int mod
|
||||
) {
|
||||
phiModulus(v, b, val, mod) and pos.hasReadOfVar(v)
|
||||
/**
|
||||
* Holds if `phi` is equal to `b + val` modulo `mod`.
|
||||
*/
|
||||
private predicate phiModulus(SemSsaPhiNode phi, SemBound b, int val, int mod) {
|
||||
exists(int r |
|
||||
maxPhiInputRank(phi, r) and
|
||||
phiModulusRankStep(phi, b, val, mod, r)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `v` at `pos` is equal to `b + val` modulo `mod`.
|
||||
*/
|
||||
private predicate ssaModulus(SemSsaVariable v, SemSsaReadPosition pos, SemBound b, int val, int mod) {
|
||||
phiModulus(v, b, val, mod) and pos.hasReadOfVar(v)
|
||||
or
|
||||
b.(SemSsaBound).getAVariable() = v and pos.hasReadOfVar(v) and val = 0 and mod = 0
|
||||
or
|
||||
exists(SemExpr e, int val0, int delta |
|
||||
semExprModulus(e, b, val0, mod) and
|
||||
valueFlowStepSsa(v, pos, e, delta) and
|
||||
val = remainder(val0 + delta, mod)
|
||||
)
|
||||
or
|
||||
moduloGuardedRead(v, pos, val, mod) and b instanceof SemZeroBound
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` is equal to `b + val` modulo `mod`.
|
||||
*
|
||||
* There are two cases for the modulus:
|
||||
* - `mod = 0`: The equality `e = b + val` is an ordinary equality.
|
||||
* - `mod > 1`: `val` lies within the range `[0 .. mod-1]`.
|
||||
*/
|
||||
cached
|
||||
predicate semExprModulus(SemExpr e, SemBound b, int val, int mod) {
|
||||
not ignoreExprModulus(e) and
|
||||
(
|
||||
e = b.getExpr(val) and mod = 0
|
||||
or
|
||||
b.(Bounds::SemSsaBound).getAVariable() = v and pos.hasReadOfVar(v) and val = 0 and mod = 0
|
||||
evenlyDivisibleExpr(e, mod) and
|
||||
val = 0 and
|
||||
b instanceof SemZeroBound
|
||||
or
|
||||
exists(SemExpr e, int val0, int delta |
|
||||
semExprModulus(e, b, val0, mod) and
|
||||
valueFlowStepSsa(v, pos, e, delta) and
|
||||
exists(SemSsaVariable v, SemSsaReadPositionBlock bb |
|
||||
ssaModulus(v, bb, b, val, mod) and
|
||||
e = v.getAUse() and
|
||||
bb.getAnExpr() = e
|
||||
)
|
||||
or
|
||||
exists(SemExpr mid, int val0, int delta |
|
||||
semExprModulus(mid, b, val0, mod) and
|
||||
semValueFlowStep(e, mid, delta) and
|
||||
val = remainder(val0 + delta, mod)
|
||||
)
|
||||
or
|
||||
moduloGuardedRead(v, pos, val, mod) and b instanceof Bounds::SemZeroBound
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` is equal to `b + val` modulo `mod`.
|
||||
*
|
||||
* There are two cases for the modulus:
|
||||
* - `mod = 0`: The equality `e = b + val` is an ordinary equality.
|
||||
* - `mod > 1`: `val` lies within the range `[0 .. mod-1]`.
|
||||
*/
|
||||
cached
|
||||
predicate semExprModulus(SemExpr e, Bounds::SemBound b, int val, int mod) {
|
||||
not ignoreExprModulus(e) and
|
||||
(
|
||||
e = b.getExpr(D::fromInt(val)) and mod = 0
|
||||
or
|
||||
evenlyDivisibleExpr(e, mod) and
|
||||
val = 0 and
|
||||
b instanceof Bounds::SemZeroBound
|
||||
or
|
||||
exists(SemSsaVariable v, SemSsaReadPositionBlock bb |
|
||||
ssaModulus(v, bb, b, val, mod) and
|
||||
e = v.getAUse() and
|
||||
bb.getAnExpr() = e
|
||||
)
|
||||
or
|
||||
exists(SemExpr mid, int val0, int delta |
|
||||
semExprModulus(mid, b, val0, mod) and
|
||||
U::semValueFlowStep(e, mid, D::fromInt(delta)) and
|
||||
val = remainder(val0 + delta, mod)
|
||||
)
|
||||
or
|
||||
exists(SemConditionalExpr cond, int v1, int v2, int m1, int m2 |
|
||||
cond = e and
|
||||
condExprBranchModulus(cond, true, b, v1, m1) and
|
||||
condExprBranchModulus(cond, false, b, v2, m2) and
|
||||
mod = m1.gcd(m2).gcd(v1 - v2) and
|
||||
mod != 1 and
|
||||
val = remainder(v1, mod)
|
||||
)
|
||||
or
|
||||
exists(Bounds::SemBound b1, Bounds::SemBound b2, int v1, int v2, int m1, int m2 |
|
||||
addModulus(e, true, b1, v1, m1) and
|
||||
addModulus(e, false, b2, v2, m2) and
|
||||
mod = m1.gcd(m2) and
|
||||
mod != 1 and
|
||||
val = remainder(v1 + v2, mod)
|
||||
|
|
||||
b = b1 and b2 instanceof Bounds::SemZeroBound
|
||||
or
|
||||
b = b2 and b1 instanceof Bounds::SemZeroBound
|
||||
)
|
||||
or
|
||||
exists(int v1, int v2, int m1, int m2 |
|
||||
subModulus(e, true, b, v1, m1) and
|
||||
subModulus(e, false, any(Bounds::SemZeroBound zb), v2, m2) and
|
||||
mod = m1.gcd(m2) and
|
||||
mod != 1 and
|
||||
val = remainder(v1 - v2, mod)
|
||||
)
|
||||
exists(SemConditionalExpr cond, int v1, int v2, int m1, int m2 |
|
||||
cond = e and
|
||||
condExprBranchModulus(cond, true, b, v1, m1) and
|
||||
condExprBranchModulus(cond, false, b, v2, m2) and
|
||||
mod = m1.gcd(m2).gcd(v1 - v2) and
|
||||
mod != 1 and
|
||||
val = remainder(v1, mod)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate condExprBranchModulus(
|
||||
SemConditionalExpr cond, boolean branch, Bounds::SemBound b, int val, int mod
|
||||
) {
|
||||
semExprModulus(cond.getBranchExpr(branch), b, val, mod)
|
||||
}
|
||||
|
||||
private predicate addModulus(SemExpr add, boolean isLeft, Bounds::SemBound b, int val, int mod) {
|
||||
exists(SemExpr larg, SemExpr rarg | nonConstAddition(add, larg, rarg) |
|
||||
semExprModulus(larg, b, val, mod) and isLeft = true
|
||||
or
|
||||
exists(SemBound b1, SemBound b2, int v1, int v2, int m1, int m2 |
|
||||
addModulus(e, true, b1, v1, m1) and
|
||||
addModulus(e, false, b2, v2, m2) and
|
||||
mod = m1.gcd(m2) and
|
||||
mod != 1 and
|
||||
val = remainder(v1 + v2, mod)
|
||||
|
|
||||
b = b1 and b2 instanceof SemZeroBound
|
||||
or
|
||||
semExprModulus(rarg, b, val, mod) and isLeft = false
|
||||
b = b2 and b1 instanceof SemZeroBound
|
||||
)
|
||||
}
|
||||
|
||||
private predicate subModulus(SemExpr sub, boolean isLeft, Bounds::SemBound b, int val, int mod) {
|
||||
exists(SemExpr larg, SemExpr rarg | nonConstSubtraction(sub, larg, rarg) |
|
||||
semExprModulus(larg, b, val, mod) and isLeft = true
|
||||
or
|
||||
semExprModulus(rarg, b, val, mod) and isLeft = false
|
||||
or
|
||||
exists(int v1, int v2, int m1, int m2 |
|
||||
subModulus(e, true, b, v1, m1) and
|
||||
subModulus(e, false, any(SemZeroBound zb), v2, m2) and
|
||||
mod = m1.gcd(m2) and
|
||||
mod != 1 and
|
||||
val = remainder(v1 - v2, mod)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate condExprBranchModulus(
|
||||
SemConditionalExpr cond, boolean branch, SemBound b, int val, int mod
|
||||
) {
|
||||
semExprModulus(cond.getBranchExpr(branch), b, val, mod)
|
||||
}
|
||||
|
||||
private predicate addModulus(SemExpr add, boolean isLeft, SemBound b, int val, int mod) {
|
||||
exists(SemExpr larg, SemExpr rarg | nonConstAddition(add, larg, rarg) |
|
||||
semExprModulus(larg, b, val, mod) and isLeft = true
|
||||
or
|
||||
semExprModulus(rarg, b, val, mod) and isLeft = false
|
||||
)
|
||||
}
|
||||
|
||||
private predicate subModulus(SemExpr sub, boolean isLeft, SemBound b, int val, int mod) {
|
||||
exists(SemExpr larg, SemExpr rarg | nonConstSubtraction(sub, larg, rarg) |
|
||||
semExprModulus(larg, b, val, mod) and isLeft = true
|
||||
or
|
||||
semExprModulus(rarg, b, val, mod) and isLeft = false
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `inp` is an input to `phi` along `edge` and this input has index `r`
|
||||
* in an arbitrary 1-based numbering of the input edges to `phi`.
|
||||
*/
|
||||
private predicate rankedPhiInput(
|
||||
SemSsaPhiNode phi, SemSsaVariable inp, SemSsaReadPositionPhiInputEdge edge, int r
|
||||
) {
|
||||
edge.phiInput(phi, inp) and
|
||||
edge =
|
||||
rank[r](SemSsaReadPositionPhiInputEdge e |
|
||||
e.phiInput(phi, _)
|
||||
|
|
||||
e order by e.getOrigBlock().getUniqueId()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `inp` is an input to `phi` along `edge` and this input has index `r`
|
||||
* in an arbitrary 1-based numbering of the input edges to `phi`.
|
||||
*/
|
||||
private predicate rankedPhiInput(
|
||||
SemSsaPhiNode phi, SemSsaVariable inp, SemSsaReadPositionPhiInputEdge edge, int r
|
||||
) {
|
||||
edge.phiInput(phi, inp) and
|
||||
edge =
|
||||
rank[r](SemSsaReadPositionPhiInputEdge e |
|
||||
e.phiInput(phi, _)
|
||||
|
|
||||
e order by e.getOrigBlock().getUniqueId()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,832 @@
|
||||
private import RangeAnalysisStage
|
||||
private import RangeAnalysisSpecific
|
||||
private import experimental.semmle.code.cpp.semantic.analysis.FloatDelta
|
||||
/**
|
||||
* Provides classes and predicates for range analysis.
|
||||
*
|
||||
* An inferred bound can either be a specific integer, the abstract value of an
|
||||
* SSA variable, or the abstract value of an interesting expression. The latter
|
||||
* category includes array lengths that are not SSA variables.
|
||||
*
|
||||
* If an inferred bound relies directly on a condition, then this condition is
|
||||
* reported as the reason for the bound.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This library tackles range analysis as a flow problem. Consider e.g.:
|
||||
* ```
|
||||
* len = arr.length;
|
||||
* if (x < len) { ... y = x-1; ... y ... }
|
||||
* ```
|
||||
* In this case we would like to infer `y <= arr.length - 2`, and this is
|
||||
* accomplished by tracking the bound through a sequence of steps:
|
||||
* ```
|
||||
* arr.length --> len = .. --> x < len --> x-1 --> y = .. --> y
|
||||
* ```
|
||||
*
|
||||
* In its simplest form the step relation `E1 --> E2` relates two expressions
|
||||
* such that `E1 <= B` implies `E2 <= B` for any `B` (with a second separate
|
||||
* step relation handling lower bounds). Examples of such steps include
|
||||
* assignments `E2 = E1` and conditions `x <= E1` where `E2` is a use of `x`
|
||||
* guarded by the condition.
|
||||
*
|
||||
* In order to handle subtractions and additions with constants, and strict
|
||||
* comparisons, the step relation is augmented with an integer delta. With this
|
||||
* generalization `E1 --(delta)--> E2` relates two expressions and an integer
|
||||
* such that `E1 <= B` implies `E2 <= B + delta` for any `B`. This corresponds
|
||||
* to the predicate `boundFlowStep`.
|
||||
*
|
||||
* The complete range analysis is then implemented as the transitive closure of
|
||||
* the step relation summing the deltas along the way. If `E1` transitively
|
||||
* steps to `E2`, `delta` is the sum of deltas along the path, and `B` is an
|
||||
* interesting bound equal to the value of `E1` then `E2 <= B + delta`. This
|
||||
* corresponds to the predicate `bounded`.
|
||||
*
|
||||
* Phi nodes need a little bit of extra handling. Consider `x0 = phi(x1, x2)`.
|
||||
* There are essentially two cases:
|
||||
* - If `x1 <= B + d1` and `x2 <= B + d2` then `x0 <= B + max(d1,d2)`.
|
||||
* - If `x1 <= B + d1` and `x2 <= x0 + d2` with `d2 <= 0` then `x0 <= B + d1`.
|
||||
* The first case is for whenever a bound can be proven without taking looping
|
||||
* into account. The second case is relevant when `x2` comes from a back-edge
|
||||
* where we can prove that the variable has been non-increasing through the
|
||||
* loop-iteration as this means that any upper bound that holds prior to the
|
||||
* loop also holds for the variable during the loop.
|
||||
* This generalizes to a phi node with `n` inputs, so if
|
||||
* `x0 = phi(x1, ..., xn)` and `xi <= B + delta` for one of the inputs, then we
|
||||
* also have `x0 <= B + delta` if we can prove either:
|
||||
* - `xj <= B + d` with `d <= delta` or
|
||||
* - `xj <= x0 + d` with `d <= 0`
|
||||
* for each input `xj`.
|
||||
*
|
||||
* As all inferred bounds can be related directly to a path in the source code
|
||||
* the only source of non-termination is if successive redundant (and thereby
|
||||
* increasingly worse) bounds are calculated along a loop in the source code.
|
||||
* We prevent this by weakening the bound to a small finite set of bounds when
|
||||
* a path follows a second back-edge (we postpone weakening till the second
|
||||
* back-edge as a precise bound might require traversing a loop once).
|
||||
*/
|
||||
|
||||
private import RangeAnalysisSpecific as Specific
|
||||
private import RangeUtils
|
||||
private import experimental.semmle.code.cpp.semantic.SemanticBound as SemanticBound
|
||||
private import SignAnalysisCommon
|
||||
private import ModulusAnalysis
|
||||
private import experimental.semmle.code.cpp.semantic.Semantic
|
||||
private import ConstantAnalysis
|
||||
|
||||
module Bounds implements BoundSig<FloatDelta> {
|
||||
class SemBound instanceof SemanticBound::SemBound {
|
||||
string toString() { result = super.toString() }
|
||||
|
||||
SemExpr getExpr(float delta) { result = super.getExpr(delta) }
|
||||
cached
|
||||
private module RangeAnalysisCache {
|
||||
cached
|
||||
module RangeAnalysisPublic {
|
||||
/**
|
||||
* Holds if `b + delta` is a valid bound for `e`.
|
||||
* - `upper = true` : `e <= b + delta`
|
||||
* - `upper = false` : `e >= b + delta`
|
||||
*
|
||||
* The reason for the bound is given by `reason` and may be either a condition
|
||||
* or `NoReason` if the bound was proven directly without the use of a bounding
|
||||
* condition.
|
||||
*/
|
||||
cached
|
||||
predicate semBounded(SemExpr e, SemBound b, int delta, boolean upper, SemReason reason) {
|
||||
bounded(e, b, delta, upper, _, _, reason) and
|
||||
bestBound(e, b, delta, upper)
|
||||
}
|
||||
}
|
||||
|
||||
class SemZeroBound extends SemBound instanceof SemanticBound::SemZeroBound { }
|
||||
|
||||
class SemSsaBound extends SemBound instanceof SemanticBound::SemSsaBound {
|
||||
SemSsaVariable getAVariable() { result = this.(SemanticBound::SemSsaBound).getAVariable() }
|
||||
/**
|
||||
* Holds if `guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _)`.
|
||||
*/
|
||||
cached
|
||||
predicate possibleReason(SemGuard guard) {
|
||||
guard = boundFlowCond(_, _, _, _, _) or guard = semEqFlowCond(_, _, _, _, _)
|
||||
}
|
||||
}
|
||||
|
||||
private module CppRangeAnalysis =
|
||||
RangeStage<FloatDelta, Bounds, CppLangImpl, RangeUtil<FloatDelta, CppLangImpl>>;
|
||||
private import RangeAnalysisCache
|
||||
import RangeAnalysisPublic
|
||||
|
||||
import CppRangeAnalysis
|
||||
/**
|
||||
* Holds if `b + delta` is a valid bound for `e` and this is the best such delta.
|
||||
* - `upper = true` : `e <= b + delta`
|
||||
* - `upper = false` : `e >= b + delta`
|
||||
*/
|
||||
private predicate bestBound(SemExpr e, SemBound b, int delta, boolean upper) {
|
||||
delta = min(int d | bounded(e, b, d, upper, _, _, _)) and upper = true
|
||||
or
|
||||
delta = max(int d | bounded(e, b, d, upper, _, _, _)) and upper = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `comp` corresponds to:
|
||||
* - `upper = true` : `v <= e + delta` or `v < e + delta`
|
||||
* - `upper = false` : `v >= e + delta` or `v > e + delta`
|
||||
*/
|
||||
private predicate boundCondition(
|
||||
SemRelationalExpr comp, SemSsaVariable v, SemExpr e, int delta, boolean upper
|
||||
) {
|
||||
comp.getLesserOperand() = semSsaRead(v, delta) and e = comp.getGreaterOperand() and upper = true
|
||||
or
|
||||
comp.getGreaterOperand() = semSsaRead(v, delta) and e = comp.getLesserOperand() and upper = false
|
||||
or
|
||||
exists(SemSubExpr sub, SemConstantIntegerExpr c, int d |
|
||||
// (v - d) - e < c
|
||||
comp.getLesserOperand() = sub and
|
||||
comp.getGreaterOperand() = c and
|
||||
sub.getLeftOperand() = semSsaRead(v, d) and
|
||||
sub.getRightOperand() = e and
|
||||
upper = true and
|
||||
delta = d + c.getIntValue()
|
||||
or
|
||||
// (v - d) - e > c
|
||||
comp.getGreaterOperand() = sub and
|
||||
comp.getLesserOperand() = c and
|
||||
sub.getLeftOperand() = semSsaRead(v, d) and
|
||||
sub.getRightOperand() = e and
|
||||
upper = false and
|
||||
delta = d + c.getIntValue()
|
||||
or
|
||||
// e - (v - d) < c
|
||||
comp.getLesserOperand() = sub and
|
||||
comp.getGreaterOperand() = c and
|
||||
sub.getLeftOperand() = e and
|
||||
sub.getRightOperand() = semSsaRead(v, d) and
|
||||
upper = false and
|
||||
delta = d - c.getIntValue()
|
||||
or
|
||||
// e - (v - d) > c
|
||||
comp.getGreaterOperand() = sub and
|
||||
comp.getLesserOperand() = c and
|
||||
sub.getLeftOperand() = e and
|
||||
sub.getRightOperand() = semSsaRead(v, d) and
|
||||
upper = true and
|
||||
delta = d - c.getIntValue()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `comp` is a comparison between `x` and `y` for which `y - x` has a
|
||||
* fixed value modulo some `mod > 1`, such that the comparison can be
|
||||
* strengthened by `strengthen` when evaluating to `testIsTrue`.
|
||||
*/
|
||||
private predicate modulusComparison(SemRelationalExpr comp, boolean testIsTrue, int strengthen) {
|
||||
exists(
|
||||
SemBound b, int v1, int v2, int mod1, int mod2, int mod, boolean resultIsStrict, int d, int k
|
||||
|
|
||||
// If `x <= y` and `x =(mod) b + v1` and `y =(mod) b + v2` then
|
||||
// `0 <= y - x =(mod) v2 - v1`. By choosing `k =(mod) v2 - v1` with
|
||||
// `0 <= k < mod` we get `k <= y - x`. If the resulting comparison is
|
||||
// strict then the strengthening amount is instead `k - 1` modulo `mod`:
|
||||
// `x < y` means `0 <= y - x - 1 =(mod) k - 1` so `k - 1 <= y - x - 1` and
|
||||
// thus `k - 1 < y - x` with `0 <= k - 1 < mod`.
|
||||
semExprModulus(comp.getLesserOperand(), b, v1, mod1) and
|
||||
semExprModulus(comp.getGreaterOperand(), b, v2, mod2) and
|
||||
mod = mod1.gcd(mod2) and
|
||||
mod != 1 and
|
||||
(testIsTrue = true or testIsTrue = false) and
|
||||
(
|
||||
if comp.isStrict()
|
||||
then resultIsStrict = testIsTrue
|
||||
else resultIsStrict = testIsTrue.booleanNot()
|
||||
) and
|
||||
(
|
||||
resultIsStrict = true and d = 1
|
||||
or
|
||||
resultIsStrict = false and d = 0
|
||||
) and
|
||||
(
|
||||
testIsTrue = true and k = v2 - v1
|
||||
or
|
||||
testIsTrue = false and k = v1 - v2
|
||||
) and
|
||||
strengthen = (((k - d) % mod) + mod) % mod
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a condition that tests whether `v` is bounded by `e + delta`.
|
||||
*
|
||||
* If the condition evaluates to `testIsTrue`:
|
||||
* - `upper = true` : `v <= e + delta`
|
||||
* - `upper = false` : `v >= e + delta`
|
||||
*/
|
||||
private SemGuard boundFlowCond(
|
||||
SemSsaVariable v, SemExpr e, int delta, boolean upper, boolean testIsTrue
|
||||
) {
|
||||
exists(
|
||||
SemRelationalExpr comp, int d1, int d2, int d3, int strengthen, boolean compIsUpper,
|
||||
boolean resultIsStrict
|
||||
|
|
||||
comp = result.asExpr() and
|
||||
boundCondition(comp, v, e, d1, compIsUpper) and
|
||||
(testIsTrue = true or testIsTrue = false) and
|
||||
upper = compIsUpper.booleanXor(testIsTrue.booleanNot()) and
|
||||
(
|
||||
if comp.isStrict()
|
||||
then resultIsStrict = testIsTrue
|
||||
else resultIsStrict = testIsTrue.booleanNot()
|
||||
) and
|
||||
(
|
||||
if
|
||||
getTrackedTypeForSsaVariable(v) instanceof SemIntegerType or
|
||||
getTrackedTypeForSsaVariable(v) instanceof SemAddressType
|
||||
then
|
||||
upper = true and strengthen = -1
|
||||
or
|
||||
upper = false and strengthen = 1
|
||||
else strengthen = 0
|
||||
) and
|
||||
(
|
||||
exists(int k | modulusComparison(comp, testIsTrue, k) and d2 = strengthen * k)
|
||||
or
|
||||
not modulusComparison(comp, testIsTrue, _) and d2 = 0
|
||||
) and
|
||||
// A strict inequality `x < y` can be strengthened to `x <= y - 1`.
|
||||
(
|
||||
resultIsStrict = true and d3 = strengthen
|
||||
or
|
||||
resultIsStrict = false and d3 = 0
|
||||
) and
|
||||
delta = d1 + d2 + d3
|
||||
)
|
||||
or
|
||||
exists(boolean testIsTrue0 |
|
||||
semImplies_v2(result, testIsTrue, boundFlowCond(v, e, delta, upper, testIsTrue0), testIsTrue0)
|
||||
)
|
||||
or
|
||||
result = semEqFlowCond(v, e, delta, true, testIsTrue) and
|
||||
(upper = true or upper = false)
|
||||
or
|
||||
// guard that tests whether `v2` is bounded by `e + delta + d1 - d2` and
|
||||
// exists a guard `guardEq` such that `v = v2 - d1 + d2`.
|
||||
exists(SemSsaVariable v2, SemGuard guardEq, boolean eqIsTrue, int d1, int d2 |
|
||||
guardEq = semEqFlowCond(v, semSsaRead(v2, d1), d2, true, eqIsTrue) and
|
||||
result = boundFlowCond(v2, e, delta + d1 - d2, upper, testIsTrue) and
|
||||
// guardEq needs to control guard
|
||||
guardEq.directlyControls(result.getBasicBlock(), eqIsTrue)
|
||||
)
|
||||
}
|
||||
|
||||
private newtype TSemReason =
|
||||
TSemNoReason() or
|
||||
TSemCondReason(SemGuard guard) { possibleReason(guard) }
|
||||
|
||||
/**
|
||||
* A reason for an inferred bound. This can either be `CondReason` if the bound
|
||||
* is due to a specific condition, or `NoReason` if the bound is inferred
|
||||
* without going through a bounding condition.
|
||||
*/
|
||||
abstract class SemReason extends TSemReason {
|
||||
/** Gets a textual representation of this reason. */
|
||||
abstract string toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* A reason for an inferred bound that indicates that the bound is inferred
|
||||
* without going through a bounding condition.
|
||||
*/
|
||||
class SemNoReason extends SemReason, TSemNoReason {
|
||||
override string toString() { result = "NoReason" }
|
||||
}
|
||||
|
||||
/** A reason for an inferred bound pointing to a condition. */
|
||||
class SemCondReason extends SemReason, TSemCondReason {
|
||||
/** Gets the condition that is the reason for the bound. */
|
||||
SemGuard getCond() { this = TSemCondReason(result) }
|
||||
|
||||
override string toString() { result = getCond().toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e + delta` is a valid bound for `v` at `pos`.
|
||||
* - `upper = true` : `v <= e + delta`
|
||||
* - `upper = false` : `v >= e + delta`
|
||||
*/
|
||||
private predicate boundFlowStepSsa(
|
||||
SemSsaVariable v, SemSsaReadPosition pos, SemExpr e, int delta, boolean upper, SemReason reason
|
||||
) {
|
||||
semSsaUpdateStep(v, e, delta) and
|
||||
pos.hasReadOfVar(v) and
|
||||
(upper = true or upper = false) and
|
||||
reason = TSemNoReason()
|
||||
or
|
||||
exists(SemGuard guard, boolean testIsTrue |
|
||||
pos.hasReadOfVar(v) and
|
||||
guard = boundFlowCond(v, e, delta, upper, testIsTrue) and
|
||||
semGuardDirectlyControlsSsaRead(guard, pos, testIsTrue) and
|
||||
reason = TSemCondReason(guard)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `v != e + delta` at `pos` and `v` is of integral type. */
|
||||
private predicate unequalFlowStepIntegralSsa(
|
||||
SemSsaVariable v, SemSsaReadPosition pos, SemExpr e, int delta, SemReason reason
|
||||
) {
|
||||
getTrackedTypeForSsaVariable(v) instanceof SemIntegerType and
|
||||
exists(SemGuard guard, boolean testIsTrue |
|
||||
pos.hasReadOfVar(v) and
|
||||
guard = semEqFlowCond(v, e, delta, false, testIsTrue) and
|
||||
semGuardDirectlyControlsSsaRead(guard, pos, testIsTrue) and
|
||||
reason = TSemCondReason(guard)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression that does conversion, boxing, or unboxing
|
||||
*/
|
||||
private class ConvertOrBoxExpr extends SemUnaryExpr {
|
||||
ConvertOrBoxExpr() {
|
||||
this instanceof SemConvertExpr
|
||||
or
|
||||
this instanceof SemBoxExpr
|
||||
or
|
||||
this instanceof SemUnboxExpr
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cast that can be ignored for the purpose of range analysis.
|
||||
*/
|
||||
private class SafeCastExpr extends ConvertOrBoxExpr {
|
||||
SafeCastExpr() {
|
||||
conversionCannotOverflow(getTrackedType(pragma[only_bind_into](getOperand())),
|
||||
getTrackedType(this))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `typ` is a small integral type with the given lower and upper bounds.
|
||||
*/
|
||||
private predicate typeBound(SemIntegerType typ, int lowerbound, int upperbound) {
|
||||
exists(int bitSize | bitSize = typ.getByteSize() * 8 |
|
||||
bitSize < 32 and
|
||||
(
|
||||
if typ.isSigned()
|
||||
then (
|
||||
upperbound = 1.bitShiftLeft(bitSize - 1) - 1 and
|
||||
lowerbound = -upperbound - 1
|
||||
) else (
|
||||
lowerbound = 0 and
|
||||
upperbound = 1.bitShiftLeft(bitSize) - 1
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A cast to a small integral type that may overflow or underflow.
|
||||
*/
|
||||
private class NarrowingCastExpr extends ConvertOrBoxExpr {
|
||||
NarrowingCastExpr() {
|
||||
not this instanceof SafeCastExpr and
|
||||
typeBound(getTrackedType(this), _, _)
|
||||
}
|
||||
|
||||
/** Gets the lower bound of the resulting type. */
|
||||
int getLowerBound() { typeBound(getTrackedType(this), result, _) }
|
||||
|
||||
/** Gets the upper bound of the resulting type. */
|
||||
int getUpperBound() { typeBound(getTrackedType(this), _, result) }
|
||||
}
|
||||
|
||||
/** Holds if `e >= 1` as determined by sign analysis. */
|
||||
private predicate strictlyPositiveIntegralExpr(SemExpr e) {
|
||||
semStrictlyPositive(e) and getTrackedType(e) instanceof SemIntegerType
|
||||
}
|
||||
|
||||
/** Holds if `e <= -1` as determined by sign analysis. */
|
||||
private predicate strictlyNegativeIntegralExpr(SemExpr e) {
|
||||
semStrictlyNegative(e) and getTrackedType(e) instanceof SemIntegerType
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e1 + delta` is a valid bound for `e2`.
|
||||
* - `upper = true` : `e2 <= e1 + delta`
|
||||
* - `upper = false` : `e2 >= e1 + delta`
|
||||
*/
|
||||
private predicate boundFlowStep(SemExpr e2, SemExpr e1, int delta, boolean upper) {
|
||||
semValueFlowStep(e2, e1, delta) and
|
||||
(upper = true or upper = false)
|
||||
or
|
||||
e2.(SafeCastExpr).getOperand() = e1 and
|
||||
delta = 0 and
|
||||
(upper = true or upper = false)
|
||||
or
|
||||
exists(SemExpr x | e2.(SemAddExpr).hasOperands(e1, x) |
|
||||
// `x instanceof ConstantIntegerExpr` is covered by valueFlowStep
|
||||
not x instanceof SemConstantIntegerExpr and
|
||||
not e1 instanceof SemConstantIntegerExpr and
|
||||
if strictlyPositiveIntegralExpr(x)
|
||||
then upper = false and delta = 1
|
||||
else
|
||||
if semPositive(x)
|
||||
then upper = false and delta = 0
|
||||
else
|
||||
if strictlyNegativeIntegralExpr(x)
|
||||
then upper = true and delta = -1
|
||||
else
|
||||
if semNegative(x)
|
||||
then upper = true and delta = 0
|
||||
else none()
|
||||
)
|
||||
or
|
||||
exists(SemExpr x, SemSubExpr sub |
|
||||
e2 = sub and
|
||||
sub.getLeftOperand() = e1 and
|
||||
sub.getRightOperand() = x
|
||||
|
|
||||
// `x instanceof ConstantIntegerExpr` is covered by valueFlowStep
|
||||
not x instanceof SemConstantIntegerExpr and
|
||||
if strictlyPositiveIntegralExpr(x)
|
||||
then upper = true and delta = -1
|
||||
else
|
||||
if semPositive(x)
|
||||
then upper = true and delta = 0
|
||||
else
|
||||
if strictlyNegativeIntegralExpr(x)
|
||||
then upper = false and delta = 1
|
||||
else
|
||||
if semNegative(x)
|
||||
then upper = false and delta = 0
|
||||
else none()
|
||||
)
|
||||
or
|
||||
e2.(SemRemExpr).getRightOperand() = e1 and
|
||||
semPositive(e1) and
|
||||
delta = -1 and
|
||||
upper = true
|
||||
or
|
||||
e2.(SemRemExpr).getLeftOperand() = e1 and semPositive(e1) and delta = 0 and upper = true
|
||||
or
|
||||
e2.(SemBitAndExpr).getAnOperand() = e1 and
|
||||
semPositive(e1) and
|
||||
delta = 0 and
|
||||
upper = true
|
||||
or
|
||||
e2.(SemBitOrExpr).getAnOperand() = e1 and
|
||||
semPositive(e2) and
|
||||
delta = 0 and
|
||||
upper = false
|
||||
or
|
||||
Specific::hasBound(e2, e1, delta, upper)
|
||||
}
|
||||
|
||||
/** Holds if `e2 = e1 * factor` and `factor > 0`. */
|
||||
private predicate boundFlowStepMul(SemExpr e2, SemExpr e1, int factor) {
|
||||
exists(SemConstantIntegerExpr c, int k | k = c.getIntValue() and k > 0 |
|
||||
e2.(SemMulExpr).hasOperands(e1, c) and factor = k
|
||||
or
|
||||
exists(SemShiftLeftExpr e |
|
||||
e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e2 = e1 / factor` and `factor > 0`.
|
||||
*
|
||||
* This conflates division, right shift, and unsigned right shift and is
|
||||
* therefore only valid for non-negative numbers.
|
||||
*/
|
||||
private predicate boundFlowStepDiv(SemExpr e2, SemExpr e1, int factor) {
|
||||
exists(SemConstantIntegerExpr c, int k | k = c.getIntValue() and k > 0 |
|
||||
exists(SemDivExpr e |
|
||||
e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = k
|
||||
)
|
||||
or
|
||||
exists(SemShiftRightExpr e |
|
||||
e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)
|
||||
)
|
||||
or
|
||||
exists(SemShiftRightUnsignedExpr e |
|
||||
e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `b + delta` is a valid bound for `v` at `pos`.
|
||||
* - `upper = true` : `v <= b + delta`
|
||||
* - `upper = false` : `v >= b + delta`
|
||||
*/
|
||||
private predicate boundedSsa(
|
||||
SemSsaVariable v, SemSsaReadPosition pos, SemBound b, int delta, boolean upper,
|
||||
boolean fromBackEdge, int origdelta, SemReason reason
|
||||
) {
|
||||
exists(SemExpr mid, int d1, int d2, SemReason r1, SemReason r2 |
|
||||
boundFlowStepSsa(v, pos, mid, d1, upper, r1) and
|
||||
bounded(mid, b, d2, upper, fromBackEdge, origdelta, r2) and
|
||||
// upper = true: v <= mid + d1 <= b + d1 + d2 = b + delta
|
||||
// upper = false: v >= mid + d1 >= b + d1 + d2 = b + delta
|
||||
delta = d1 + d2 and
|
||||
(if r1 instanceof SemNoReason then reason = r2 else reason = r1)
|
||||
)
|
||||
or
|
||||
exists(int d, SemReason r1, SemReason r2 |
|
||||
boundedSsa(v, pos, b, d, upper, fromBackEdge, origdelta, r2) or
|
||||
boundedPhi(v, b, d, upper, fromBackEdge, origdelta, r2)
|
||||
|
|
||||
unequalIntegralSsa(v, pos, b, d, r1) and
|
||||
(
|
||||
upper = true and delta = d - 1
|
||||
or
|
||||
upper = false and delta = d + 1
|
||||
) and
|
||||
(
|
||||
reason = r1
|
||||
or
|
||||
reason = r2 and not r2 instanceof SemNoReason
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `v != b + delta` at `pos` and `v` is of integral type.
|
||||
*/
|
||||
private predicate unequalIntegralSsa(
|
||||
SemSsaVariable v, SemSsaReadPosition pos, SemBound b, int delta, SemReason reason
|
||||
) {
|
||||
exists(SemExpr e, int d1, int d2 |
|
||||
unequalFlowStepIntegralSsa(v, pos, e, d1, reason) and
|
||||
boundedUpper(e, b, d1) and
|
||||
boundedLower(e, b, d2) and
|
||||
delta = d2 + d1
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `b + delta` is an upper bound for `e`.
|
||||
*
|
||||
* This predicate only exists to prevent a bad standard order in `unequalIntegralSsa`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate boundedUpper(SemExpr e, SemBound b, int delta) {
|
||||
bounded(e, b, delta, true, _, _, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `b + delta` is a lower bound for `e`.
|
||||
*
|
||||
* This predicate only exists to prevent a bad standard order in `unequalIntegralSsa`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate boundedLower(SemExpr e, SemBound b, int delta) {
|
||||
bounded(e, b, delta, false, _, _, _)
|
||||
}
|
||||
|
||||
/** Weakens a delta to lie in the range `[-1..1]`. */
|
||||
bindingset[delta, upper]
|
||||
private int weakenDelta(boolean upper, int delta) {
|
||||
delta in [-1 .. 1] and result = delta
|
||||
or
|
||||
upper = true and result = -1 and delta < -1
|
||||
or
|
||||
upper = false and result = 1 and delta > 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `b + delta` is a valid bound for `inp` when used as an input to
|
||||
* `phi` along `edge`.
|
||||
* - `upper = true` : `inp <= b + delta`
|
||||
* - `upper = false` : `inp >= b + delta`
|
||||
*/
|
||||
private predicate boundedPhiInp(
|
||||
SemSsaPhiNode phi, SemSsaVariable inp, SemSsaReadPositionPhiInputEdge edge, SemBound b, int delta,
|
||||
boolean upper, boolean fromBackEdge, int origdelta, SemReason reason
|
||||
) {
|
||||
edge.phiInput(phi, inp) and
|
||||
exists(int d, boolean fromBackEdge0 |
|
||||
boundedSsa(inp, edge, b, d, upper, fromBackEdge0, origdelta, reason)
|
||||
or
|
||||
boundedPhi(inp, b, d, upper, fromBackEdge0, origdelta, reason)
|
||||
or
|
||||
b.(SemSsaBound).getAVariable() = inp and
|
||||
d = 0 and
|
||||
(upper = true or upper = false) and
|
||||
fromBackEdge0 = false and
|
||||
origdelta = 0 and
|
||||
reason = TSemNoReason()
|
||||
|
|
||||
if semBackEdge(phi, inp, edge)
|
||||
then
|
||||
fromBackEdge = true and
|
||||
(
|
||||
fromBackEdge0 = true and delta = weakenDelta(upper, d - origdelta) + origdelta
|
||||
or
|
||||
fromBackEdge0 = false and delta = d
|
||||
)
|
||||
else (
|
||||
delta = d and fromBackEdge = fromBackEdge0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `b + delta` is a valid bound for `inp` when used as an input to
|
||||
* `phi` along `edge`.
|
||||
* - `upper = true` : `inp <= b + delta`
|
||||
* - `upper = false` : `inp >= b + delta`
|
||||
*
|
||||
* Equivalent to `boundedPhiInp(phi, inp, edge, b, delta, upper, _, _, _)`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate boundedPhiInp1(
|
||||
SemSsaPhiNode phi, SemBound b, boolean upper, SemSsaVariable inp,
|
||||
SemSsaReadPositionPhiInputEdge edge, int delta
|
||||
) {
|
||||
boundedPhiInp(phi, inp, edge, b, delta, upper, _, _, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `phi` is a valid bound for `inp` when used as an input to `phi`
|
||||
* along `edge`.
|
||||
* - `upper = true` : `inp <= phi`
|
||||
* - `upper = false` : `inp >= phi`
|
||||
*/
|
||||
private predicate selfBoundedPhiInp(
|
||||
SemSsaPhiNode phi, SemSsaVariable inp, SemSsaReadPositionPhiInputEdge edge, boolean upper
|
||||
) {
|
||||
exists(int d, SemSsaBound phibound |
|
||||
phibound.getAVariable() = phi and
|
||||
boundedPhiInp(phi, inp, edge, phibound, d, upper, _, _, _) and
|
||||
(
|
||||
upper = true and d <= 0
|
||||
or
|
||||
upper = false and d >= 0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `b + delta` is a valid bound for some input, `inp`, to `phi`, and
|
||||
* thus a candidate bound for `phi`.
|
||||
* - `upper = true` : `inp <= b + delta`
|
||||
* - `upper = false` : `inp >= b + delta`
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate boundedPhiCand(
|
||||
SemSsaPhiNode phi, boolean upper, SemBound b, int delta, boolean fromBackEdge, int origdelta,
|
||||
SemReason reason
|
||||
) {
|
||||
exists(SemSsaVariable inp, SemSsaReadPositionPhiInputEdge edge |
|
||||
boundedPhiInp(phi, inp, edge, b, delta, upper, fromBackEdge, origdelta, reason)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the candidate bound `b + delta` for `phi` is valid for the phi input
|
||||
* `inp` along `edge`.
|
||||
*/
|
||||
private predicate boundedPhiCandValidForEdge(
|
||||
SemSsaPhiNode phi, SemBound b, int delta, boolean upper, boolean fromBackEdge, int origdelta,
|
||||
SemReason reason, SemSsaVariable inp, SemSsaReadPositionPhiInputEdge edge
|
||||
) {
|
||||
boundedPhiCand(phi, upper, b, delta, fromBackEdge, origdelta, reason) and
|
||||
(
|
||||
exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = true and d <= delta)
|
||||
or
|
||||
exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = false and d >= delta)
|
||||
or
|
||||
selfBoundedPhiInp(phi, inp, edge, upper)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `b + delta` is a valid bound for `phi`.
|
||||
* - `upper = true` : `phi <= b + delta`
|
||||
* - `upper = false` : `phi >= b + delta`
|
||||
*/
|
||||
private predicate boundedPhi(
|
||||
SemSsaPhiNode phi, SemBound b, int delta, boolean upper, boolean fromBackEdge, int origdelta,
|
||||
SemReason reason
|
||||
) {
|
||||
forex(SemSsaVariable inp, SemSsaReadPositionPhiInputEdge edge | edge.phiInput(phi, inp) |
|
||||
boundedPhiCandValidForEdge(phi, b, delta, upper, fromBackEdge, origdelta, reason, inp, edge)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` has an upper (for `upper = true`) or lower
|
||||
* (for `upper = false`) bound of `b`.
|
||||
*/
|
||||
private predicate baseBound(SemExpr e, int b, boolean upper) {
|
||||
Specific::hasConstantBound(e, b, upper)
|
||||
or
|
||||
upper = false and
|
||||
b = 0 and
|
||||
semPositive(e.(SemBitAndExpr).getAnOperand()) and
|
||||
// REVIEW: We let the language opt out here to preserve original results.
|
||||
not Specific::ignoreZeroLowerBound(e)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the value being cast has an upper (for `upper = true`) or lower
|
||||
* (for `upper = false`) bound within the bounds of the resulting type.
|
||||
* For `upper = true` this means that the cast will not overflow and for
|
||||
* `upper = false` this means that the cast will not underflow.
|
||||
*/
|
||||
private predicate safeNarrowingCast(NarrowingCastExpr cast, boolean upper) {
|
||||
exists(int bound | bounded(cast.getOperand(), any(SemZeroBound zb), bound, upper, _, _, _) |
|
||||
upper = true and bound <= cast.getUpperBound()
|
||||
or
|
||||
upper = false and bound >= cast.getLowerBound()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate boundedCastExpr(
|
||||
NarrowingCastExpr cast, SemBound b, int delta, boolean upper, boolean fromBackEdge, int origdelta,
|
||||
SemReason reason
|
||||
) {
|
||||
bounded(cast.getOperand(), b, delta, upper, fromBackEdge, origdelta, reason)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `b + delta` is a valid bound for `e`.
|
||||
* - `upper = true` : `e <= b + delta`
|
||||
* - `upper = false` : `e >= b + delta`
|
||||
*/
|
||||
private predicate bounded(
|
||||
SemExpr e, SemBound b, int delta, boolean upper, boolean fromBackEdge, int origdelta,
|
||||
SemReason reason
|
||||
) {
|
||||
not Specific::ignoreExprBound(e) and
|
||||
(
|
||||
e = b.getExpr(delta) and
|
||||
(upper = true or upper = false) and
|
||||
fromBackEdge = false and
|
||||
origdelta = delta and
|
||||
reason = TSemNoReason()
|
||||
or
|
||||
baseBound(e, delta, upper) and
|
||||
b instanceof SemZeroBound and
|
||||
fromBackEdge = false and
|
||||
origdelta = delta and
|
||||
reason = TSemNoReason()
|
||||
or
|
||||
exists(SemSsaVariable v, SemSsaReadPositionBlock bb |
|
||||
boundedSsa(v, bb, b, delta, upper, fromBackEdge, origdelta, reason) and
|
||||
e = v.getAUse() and
|
||||
bb.getBlock() = e.getBasicBlock()
|
||||
)
|
||||
or
|
||||
exists(SemExpr mid, int d1, int d2 |
|
||||
boundFlowStep(e, mid, d1, upper) and
|
||||
// Constants have easy, base-case bounds, so let's not infer any recursive bounds.
|
||||
not e instanceof SemConstantIntegerExpr and
|
||||
bounded(mid, b, d2, upper, fromBackEdge, origdelta, reason) and
|
||||
// upper = true: e <= mid + d1 <= b + d1 + d2 = b + delta
|
||||
// upper = false: e >= mid + d1 >= b + d1 + d2 = b + delta
|
||||
delta = d1 + d2
|
||||
)
|
||||
or
|
||||
exists(SemSsaPhiNode phi |
|
||||
boundedPhi(phi, b, delta, upper, fromBackEdge, origdelta, reason) and
|
||||
e = phi.getAUse()
|
||||
)
|
||||
or
|
||||
exists(SemExpr mid, int factor, int d |
|
||||
boundFlowStepMul(e, mid, factor) and
|
||||
not e instanceof SemConstantIntegerExpr and
|
||||
bounded(mid, b, d, upper, fromBackEdge, origdelta, reason) and
|
||||
b instanceof SemZeroBound and
|
||||
delta = d * factor
|
||||
)
|
||||
or
|
||||
exists(SemExpr mid, int factor, int d |
|
||||
boundFlowStepDiv(e, mid, factor) and
|
||||
not e instanceof SemConstantIntegerExpr and
|
||||
bounded(mid, b, d, upper, fromBackEdge, origdelta, reason) and
|
||||
b instanceof SemZeroBound and
|
||||
d >= 0 and
|
||||
delta = d / factor
|
||||
)
|
||||
or
|
||||
exists(NarrowingCastExpr cast |
|
||||
cast = e and
|
||||
safeNarrowingCast(cast, upper.booleanNot()) and
|
||||
boundedCastExpr(cast, b, delta, upper, fromBackEdge, origdelta, reason)
|
||||
)
|
||||
or
|
||||
exists(
|
||||
SemConditionalExpr cond, int d1, int d2, boolean fbe1, boolean fbe2, int od1, int od2,
|
||||
SemReason r1, SemReason r2
|
||||
|
|
||||
cond = e and
|
||||
boundedConditionalExpr(cond, b, upper, true, d1, fbe1, od1, r1) and
|
||||
boundedConditionalExpr(cond, b, upper, false, d2, fbe2, od2, r2) and
|
||||
(
|
||||
delta = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1
|
||||
or
|
||||
delta = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2
|
||||
)
|
||||
|
|
||||
upper = true and delta = d1.maximum(d2)
|
||||
or
|
||||
upper = false and delta = d1.minimum(d2)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate boundedConditionalExpr(
|
||||
SemConditionalExpr cond, SemBound b, boolean upper, boolean branch, int delta,
|
||||
boolean fromBackEdge, int origdelta, SemReason reason
|
||||
) {
|
||||
bounded(cond.getBranchExpr(branch), b, delta, upper, fromBackEdge, origdelta, reason)
|
||||
}
|
||||
|
||||
@@ -3,90 +3,86 @@
|
||||
*/
|
||||
|
||||
private import experimental.semmle.code.cpp.semantic.Semantic
|
||||
private import RangeAnalysisStage
|
||||
private import experimental.semmle.code.cpp.semantic.analysis.FloatDelta
|
||||
|
||||
module CppLangImpl implements LangSig<FloatDelta> {
|
||||
/**
|
||||
* Holds if the specified expression should be excluded from the result of `ssaRead()`.
|
||||
*
|
||||
* This predicate is to keep the results identical to the original Java implementation. It should be
|
||||
* removed once we have the new implementation matching the old results exactly.
|
||||
*/
|
||||
predicate ignoreSsaReadCopy(SemExpr e) { none() }
|
||||
/**
|
||||
* Holds if the specified expression should be excluded from the result of `ssaRead()`.
|
||||
*
|
||||
* This predicate is to keep the results identical to the original Java implementation. It should be
|
||||
* removed once we have the new implementation matching the old results exactly.
|
||||
*/
|
||||
predicate ignoreSsaReadCopy(SemExpr e) { none() }
|
||||
|
||||
/**
|
||||
* Ignore the bound on this expression.
|
||||
*
|
||||
* This predicate is to keep the results identical to the original Java implementation. It should be
|
||||
* removed once we have the new implementation matching the old results exactly.
|
||||
*/
|
||||
predicate ignoreExprBound(SemExpr e) { none() }
|
||||
/**
|
||||
* Ignore the bound on this expression.
|
||||
*
|
||||
* This predicate is to keep the results identical to the original Java implementation. It should be
|
||||
* removed once we have the new implementation matching the old results exactly.
|
||||
*/
|
||||
predicate ignoreExprBound(SemExpr e) { none() }
|
||||
|
||||
/**
|
||||
* Ignore any inferred zero lower bound on this expression.
|
||||
*
|
||||
* This predicate is to keep the results identical to the original Java implementation. It should be
|
||||
* removed once we have the new implementation matching the old results exactly.
|
||||
*/
|
||||
predicate ignoreZeroLowerBound(SemExpr e) { none() }
|
||||
/**
|
||||
* Ignore any inferred zero lower bound on this expression.
|
||||
*
|
||||
* This predicate is to keep the results identical to the original Java implementation. It should be
|
||||
* removed once we have the new implementation matching the old results exactly.
|
||||
*/
|
||||
predicate ignoreZeroLowerBound(SemExpr e) { none() }
|
||||
|
||||
/**
|
||||
* Holds if the specified expression should be excluded from the result of `ssaRead()`.
|
||||
*
|
||||
* This predicate is to keep the results identical to the original Java implementation. It should be
|
||||
* removed once we have the new implementation matching the old results exactly.
|
||||
*/
|
||||
predicate ignoreSsaReadArithmeticExpr(SemExpr e) { none() }
|
||||
/**
|
||||
* Holds if the specified expression should be excluded from the result of `ssaRead()`.
|
||||
*
|
||||
* This predicate is to keep the results identical to the original Java implementation. It should be
|
||||
* removed once we have the new implementation matching the old results exactly.
|
||||
*/
|
||||
predicate ignoreSsaReadArithmeticExpr(SemExpr e) { none() }
|
||||
|
||||
/**
|
||||
* Holds if the specified variable should be excluded from the result of `ssaRead()`.
|
||||
*
|
||||
* This predicate is to keep the results identical to the original Java implementation. It should be
|
||||
* removed once we have the new implementation matching the old results exactly.
|
||||
*/
|
||||
predicate ignoreSsaReadAssignment(SemSsaVariable v) { none() }
|
||||
/**
|
||||
* Holds if the specified variable should be excluded from the result of `ssaRead()`.
|
||||
*
|
||||
* This predicate is to keep the results identical to the original Java implementation. It should be
|
||||
* removed once we have the new implementation matching the old results exactly.
|
||||
*/
|
||||
predicate ignoreSsaReadAssignment(SemSsaVariable v) { none() }
|
||||
|
||||
/**
|
||||
* Adds additional results to `ssaRead()` that are specific to Java.
|
||||
*
|
||||
* This predicate handles propagation of offsets for post-increment and post-decrement expressions
|
||||
* in exactly the same way as the old Java implementation. Once the new implementation matches the
|
||||
* old one, we should remove this predicate and propagate deltas for all similar patterns, whether
|
||||
* or not they come from a post-increment/decrement expression.
|
||||
*/
|
||||
SemExpr specificSsaRead(SemSsaVariable v, float delta) { none() }
|
||||
/**
|
||||
* Adds additional results to `ssaRead()` that are specific to Java.
|
||||
*
|
||||
* This predicate handles propagation of offsets for post-increment and post-decrement expressions
|
||||
* in exactly the same way as the old Java implementation. Once the new implementation matches the
|
||||
* old one, we should remove this predicate and propagate deltas for all similar patterns, whether
|
||||
* or not they come from a post-increment/decrement expression.
|
||||
*/
|
||||
SemExpr specificSsaRead(SemSsaVariable v, int delta) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `e >= bound` (if `upper = false`) or `e <= bound` (if `upper = true`).
|
||||
*/
|
||||
predicate hasConstantBound(SemExpr e, float bound, boolean upper) { none() }
|
||||
/**
|
||||
* Holds if `e >= bound` (if `upper = false`) or `e <= bound` (if `upper = true`).
|
||||
*/
|
||||
predicate hasConstantBound(SemExpr e, int bound, boolean upper) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `e >= bound + delta` (if `upper = false`) or `e <= bound + delta` (if `upper = true`).
|
||||
*/
|
||||
predicate hasBound(SemExpr e, SemExpr bound, float delta, boolean upper) { none() }
|
||||
/**
|
||||
* Holds if `e >= bound + delta` (if `upper = false`) or `e <= bound + delta` (if `upper = true`).
|
||||
*/
|
||||
predicate hasBound(SemExpr e, SemExpr bound, int delta, boolean upper) { none() }
|
||||
|
||||
/**
|
||||
* Holds if the value of `dest` is known to be `src + delta`.
|
||||
*/
|
||||
predicate additionalValueFlowStep(SemExpr dest, SemExpr src, float delta) { none() }
|
||||
/**
|
||||
* Holds if the value of `dest` is known to be `src + delta`.
|
||||
*/
|
||||
predicate additionalValueFlowStep(SemExpr dest, SemExpr src, int delta) { none() }
|
||||
|
||||
/**
|
||||
* Gets the type that range analysis should use to track the result of the specified expression,
|
||||
* if a type other than the original type of the expression is to be used.
|
||||
*
|
||||
* This predicate is commonly used in languages that support immutable "boxed" types that are
|
||||
* actually references but whose values can be tracked as the type contained in the box.
|
||||
*/
|
||||
SemType getAlternateType(SemExpr e) { none() }
|
||||
/**
|
||||
* Gets the type that range analysis should use to track the result of the specified expression,
|
||||
* if a type other than the original type of the expression is to be used.
|
||||
*
|
||||
* This predicate is commonly used in languages that support immutable "boxed" types that are
|
||||
* actually references but whose values can be tracked as the type contained in the box.
|
||||
*/
|
||||
SemType getAlternateType(SemExpr e) { none() }
|
||||
|
||||
/**
|
||||
* Gets the type that range analysis should use to track the result of the specified source
|
||||
* variable, if a type other than the original type of the expression is to be used.
|
||||
*
|
||||
* This predicate is commonly used in languages that support immutable "boxed" types that are
|
||||
* actually references but whose values can be tracked as the type contained in the box.
|
||||
*/
|
||||
SemType getAlternateTypeForSsaVariable(SemSsaVariable var) { none() }
|
||||
}
|
||||
/**
|
||||
* Gets the type that range analysis should use to track the result of the specified source
|
||||
* variable, if a type other than the original type of the expression is to be used.
|
||||
*
|
||||
* This predicate is commonly used in languages that support immutable "boxed" types that are
|
||||
* actually references but whose values can be tracked as the type contained in the box.
|
||||
*/
|
||||
SemType getAlternateTypeForSsaVariable(SemSsaVariable var) { none() }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,138 +3,133 @@
|
||||
*/
|
||||
|
||||
private import experimental.semmle.code.cpp.semantic.Semantic
|
||||
private import RangeAnalysisSpecific
|
||||
private import RangeAnalysisStage as Range
|
||||
private import RangeAnalysisSpecific as Specific
|
||||
private import ConstantAnalysis
|
||||
|
||||
module RangeUtil<Range::DeltaSig D, Range::LangSig<D> Lang> implements Range::UtilSig<D> {
|
||||
/**
|
||||
* Gets an expression that equals `v - d`.
|
||||
*/
|
||||
SemExpr semSsaRead(SemSsaVariable v, D::Delta delta) {
|
||||
// There are various language-specific extension points that can be removed once we no longer
|
||||
// expect to match the original Java implementation's results exactly.
|
||||
result = v.getAUse() and delta = D::fromInt(0)
|
||||
or
|
||||
exists(D::Delta d1, SemConstantIntegerExpr c |
|
||||
result.(SemAddExpr).hasOperands(semSsaRead(v, d1), c) and
|
||||
delta = D::fromFloat(D::toFloat(d1) - c.getIntValue()) and
|
||||
not Lang::ignoreSsaReadArithmeticExpr(result)
|
||||
)
|
||||
or
|
||||
exists(SemSubExpr sub, D::Delta d1, SemConstantIntegerExpr c |
|
||||
result = sub and
|
||||
sub.getLeftOperand() = semSsaRead(v, d1) and
|
||||
sub.getRightOperand() = c and
|
||||
delta = D::fromFloat(D::toFloat(d1) + c.getIntValue()) and
|
||||
not Lang::ignoreSsaReadArithmeticExpr(result)
|
||||
)
|
||||
or
|
||||
result = v.(SemSsaExplicitUpdate).getSourceExpr() and
|
||||
delta = D::fromFloat(0) and
|
||||
not Lang::ignoreSsaReadAssignment(v)
|
||||
or
|
||||
result = Lang::specificSsaRead(v, delta)
|
||||
or
|
||||
result.(SemCopyValueExpr).getOperand() = semSsaRead(v, delta) and
|
||||
not Lang::ignoreSsaReadCopy(result)
|
||||
or
|
||||
result.(SemStoreExpr).getOperand() = semSsaRead(v, delta)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a condition that tests whether `v` equals `e + delta`.
|
||||
*
|
||||
* If the condition evaluates to `testIsTrue`:
|
||||
* - `isEq = true` : `v == e + delta`
|
||||
* - `isEq = false` : `v != e + delta`
|
||||
*/
|
||||
SemGuard semEqFlowCond(
|
||||
SemSsaVariable v, SemExpr e, D::Delta delta, boolean isEq, boolean testIsTrue
|
||||
) {
|
||||
exists(boolean eqpolarity |
|
||||
result.isEquality(semSsaRead(v, delta), e, eqpolarity) and
|
||||
(testIsTrue = true or testIsTrue = false) and
|
||||
eqpolarity.booleanXor(testIsTrue).booleanNot() = isEq
|
||||
)
|
||||
or
|
||||
exists(boolean testIsTrue0 |
|
||||
semImplies_v2(result, testIsTrue, semEqFlowCond(v, e, delta, isEq, testIsTrue0), testIsTrue0)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `v` is an `SsaExplicitUpdate` that equals `e + delta`.
|
||||
*/
|
||||
predicate semSsaUpdateStep(SemSsaExplicitUpdate v, SemExpr e, D::Delta delta) {
|
||||
exists(SemExpr defExpr | defExpr = v.getSourceExpr() |
|
||||
defExpr.(SemCopyValueExpr).getOperand() = e and delta = D::fromFloat(0)
|
||||
or
|
||||
defExpr.(SemStoreExpr).getOperand() = e and delta = D::fromFloat(0)
|
||||
or
|
||||
defExpr.(SemAddOneExpr).getOperand() = e and delta = D::fromFloat(1)
|
||||
or
|
||||
defExpr.(SemSubOneExpr).getOperand() = e and delta = D::fromFloat(-1)
|
||||
or
|
||||
e = defExpr and
|
||||
not (
|
||||
defExpr instanceof SemCopyValueExpr or
|
||||
defExpr instanceof SemStoreExpr or
|
||||
defExpr instanceof SemAddOneExpr or
|
||||
defExpr instanceof SemSubOneExpr
|
||||
) and
|
||||
delta = D::fromFloat(0)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e1 + delta` equals `e2`.
|
||||
*/
|
||||
predicate semValueFlowStep(SemExpr e2, SemExpr e1, D::Delta delta) {
|
||||
e2.(SemCopyValueExpr).getOperand() = e1 and delta = D::fromFloat(0)
|
||||
or
|
||||
e2.(SemStoreExpr).getOperand() = e1 and delta = D::fromFloat(0)
|
||||
or
|
||||
e2.(SemAddOneExpr).getOperand() = e1 and delta = D::fromFloat(1)
|
||||
or
|
||||
e2.(SemSubOneExpr).getOperand() = e1 and delta = D::fromFloat(-1)
|
||||
or
|
||||
Lang::additionalValueFlowStep(e2, e1, delta)
|
||||
or
|
||||
exists(SemExpr x | e2.(SemAddExpr).hasOperands(e1, x) |
|
||||
D::fromInt(x.(SemConstantIntegerExpr).getIntValue()) = delta
|
||||
)
|
||||
or
|
||||
exists(SemExpr x, SemSubExpr sub |
|
||||
e2 = sub and
|
||||
sub.getLeftOperand() = e1 and
|
||||
sub.getRightOperand() = x
|
||||
|
|
||||
D::fromInt(-x.(SemConstantIntegerExpr).getIntValue()) = delta
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type used to track the specified expression's range information.
|
||||
*
|
||||
* Usually, this just `e.getSemType()`, but the language can override this to track immutable boxed
|
||||
* primitive types as the underlying primitive type.
|
||||
*/
|
||||
SemType getTrackedType(SemExpr e) {
|
||||
result = Lang::getAlternateType(e)
|
||||
or
|
||||
not exists(Lang::getAlternateType(e)) and result = e.getSemType()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type used to track the specified source variable's range information.
|
||||
*
|
||||
* Usually, this just `e.getType()`, but the language can override this to track immutable boxed
|
||||
* primitive types as the underlying primitive type.
|
||||
*/
|
||||
SemType getTrackedTypeForSsaVariable(SemSsaVariable var) {
|
||||
result = Lang::getAlternateTypeForSsaVariable(var)
|
||||
or
|
||||
not exists(Lang::getAlternateTypeForSsaVariable(var)) and result = var.getType()
|
||||
}
|
||||
/**
|
||||
* Gets an expression that equals `v - d`.
|
||||
*/
|
||||
SemExpr semSsaRead(SemSsaVariable v, int delta) {
|
||||
// There are various language-specific extension points that can be removed once we no longer
|
||||
// expect to match the original Java implementation's results exactly.
|
||||
result = v.getAUse() and delta = 0
|
||||
or
|
||||
exists(int d1, SemConstantIntegerExpr c |
|
||||
result.(SemAddExpr).hasOperands(semSsaRead(v, d1), c) and
|
||||
delta = d1 - c.getIntValue() and
|
||||
not Specific::ignoreSsaReadArithmeticExpr(result)
|
||||
)
|
||||
or
|
||||
exists(SemSubExpr sub, int d1, SemConstantIntegerExpr c |
|
||||
result = sub and
|
||||
sub.getLeftOperand() = semSsaRead(v, d1) and
|
||||
sub.getRightOperand() = c and
|
||||
delta = d1 + c.getIntValue() and
|
||||
not Specific::ignoreSsaReadArithmeticExpr(result)
|
||||
)
|
||||
or
|
||||
result = v.(SemSsaExplicitUpdate).getSourceExpr() and
|
||||
delta = 0 and
|
||||
not Specific::ignoreSsaReadAssignment(v)
|
||||
or
|
||||
result = Specific::specificSsaRead(v, delta)
|
||||
or
|
||||
result.(SemCopyValueExpr).getOperand() = semSsaRead(v, delta) and
|
||||
not Specific::ignoreSsaReadCopy(result)
|
||||
or
|
||||
result.(SemStoreExpr).getOperand() = semSsaRead(v, delta)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a condition that tests whether `v` equals `e + delta`.
|
||||
*
|
||||
* If the condition evaluates to `testIsTrue`:
|
||||
* - `isEq = true` : `v == e + delta`
|
||||
* - `isEq = false` : `v != e + delta`
|
||||
*/
|
||||
SemGuard semEqFlowCond(SemSsaVariable v, SemExpr e, int delta, boolean isEq, boolean testIsTrue) {
|
||||
exists(boolean eqpolarity |
|
||||
result.isEquality(semSsaRead(v, delta), e, eqpolarity) and
|
||||
(testIsTrue = true or testIsTrue = false) and
|
||||
eqpolarity.booleanXor(testIsTrue).booleanNot() = isEq
|
||||
)
|
||||
or
|
||||
exists(boolean testIsTrue0 |
|
||||
semImplies_v2(result, testIsTrue, semEqFlowCond(v, e, delta, isEq, testIsTrue0), testIsTrue0)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `v` is an `SsaExplicitUpdate` that equals `e + delta`.
|
||||
*/
|
||||
predicate semSsaUpdateStep(SemSsaExplicitUpdate v, SemExpr e, int delta) {
|
||||
exists(SemExpr defExpr | defExpr = v.getSourceExpr() |
|
||||
defExpr.(SemCopyValueExpr).getOperand() = e and delta = 0
|
||||
or
|
||||
defExpr.(SemStoreExpr).getOperand() = e and delta = 0
|
||||
or
|
||||
defExpr.(SemAddOneExpr).getOperand() = e and delta = 1
|
||||
or
|
||||
defExpr.(SemSubOneExpr).getOperand() = e and delta = -1
|
||||
or
|
||||
e = defExpr and
|
||||
not (
|
||||
defExpr instanceof SemCopyValueExpr or
|
||||
defExpr instanceof SemStoreExpr or
|
||||
defExpr instanceof SemAddOneExpr or
|
||||
defExpr instanceof SemSubOneExpr
|
||||
) and
|
||||
delta = 0
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e1 + delta` equals `e2`.
|
||||
*/
|
||||
predicate semValueFlowStep(SemExpr e2, SemExpr e1, int delta) {
|
||||
e2.(SemCopyValueExpr).getOperand() = e1 and delta = 0
|
||||
or
|
||||
e2.(SemStoreExpr).getOperand() = e1 and delta = 0
|
||||
or
|
||||
e2.(SemAddOneExpr).getOperand() = e1 and delta = 1
|
||||
or
|
||||
e2.(SemSubOneExpr).getOperand() = e1 and delta = -1
|
||||
or
|
||||
Specific::additionalValueFlowStep(e2, e1, delta)
|
||||
or
|
||||
exists(SemExpr x | e2.(SemAddExpr).hasOperands(e1, x) |
|
||||
x.(SemConstantIntegerExpr).getIntValue() = delta
|
||||
)
|
||||
or
|
||||
exists(SemExpr x, SemSubExpr sub |
|
||||
e2 = sub and
|
||||
sub.getLeftOperand() = e1 and
|
||||
sub.getRightOperand() = x
|
||||
|
|
||||
x.(SemConstantIntegerExpr).getIntValue() = -delta
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type used to track the specified expression's range information.
|
||||
*
|
||||
* Usually, this just `e.getSemType()`, but the language can override this to track immutable boxed
|
||||
* primitive types as the underlying primitive type.
|
||||
*/
|
||||
SemType getTrackedType(SemExpr e) {
|
||||
result = Specific::getAlternateType(e)
|
||||
or
|
||||
not exists(Specific::getAlternateType(e)) and result = e.getSemType()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type used to track the specified source variable's range information.
|
||||
*
|
||||
* Usually, this just `e.getType()`, but the language can override this to track immutable boxed
|
||||
* primitive types as the underlying primitive type.
|
||||
*/
|
||||
SemType getTrackedTypeForSsaVariable(SemSsaVariable var) {
|
||||
result = Specific::getAlternateTypeForSsaVariable(var)
|
||||
or
|
||||
not exists(Specific::getAlternateTypeForSsaVariable(var)) and result = var.getType()
|
||||
}
|
||||
|
||||
@@ -6,494 +6,488 @@
|
||||
* three-valued domain `{negative, zero, positive}`.
|
||||
*/
|
||||
|
||||
private import RangeAnalysisStage
|
||||
private import SignAnalysisSpecific as Specific
|
||||
private import experimental.semmle.code.cpp.semantic.Semantic
|
||||
private import ConstantAnalysis
|
||||
private import RangeUtils
|
||||
private import Sign
|
||||
|
||||
module SignAnalysis<DeltaSig D, UtilSig<D> Utils> {
|
||||
/**
|
||||
* An SSA definition for which the analysis can compute the sign.
|
||||
*
|
||||
* The actual computation of the sign is done in an override of the `getSign()` predicate. The
|
||||
* charpred of any subclass must _not_ invoke `getSign()`, directly or indirectly. This ensures
|
||||
* that the charpred does not introduce negative recursion. The `getSign()` predicate may be
|
||||
* recursive.
|
||||
*/
|
||||
abstract private class SignDef instanceof SemSsaVariable {
|
||||
final string toString() { result = super.toString() }
|
||||
/**
|
||||
* An SSA definition for which the analysis can compute the sign.
|
||||
*
|
||||
* The actual computation of the sign is done in an override of the `getSign()` predicate. The
|
||||
* charpred of any subclass must _not_ invoke `getSign()`, directly or indirectly. This ensures
|
||||
* that the charpred does not introduce negative recursion. The `getSign()` predicate may be
|
||||
* recursive.
|
||||
*/
|
||||
abstract private class SignDef instanceof SemSsaVariable {
|
||||
final string toString() { result = super.toString() }
|
||||
|
||||
/** Gets the possible signs of this SSA definition. */
|
||||
abstract Sign getSign();
|
||||
}
|
||||
/** Gets the possible signs of this SSA definition. */
|
||||
abstract Sign getSign();
|
||||
}
|
||||
|
||||
/** An SSA definition whose sign is computed based on standard flow. */
|
||||
abstract private class FlowSignDef extends SignDef {
|
||||
abstract override Sign getSign();
|
||||
}
|
||||
/** An SSA definition whose sign is computed based on standard flow. */
|
||||
abstract private class FlowSignDef extends SignDef {
|
||||
abstract override Sign getSign();
|
||||
}
|
||||
|
||||
/** An SSA definition whose sign is determined by the sign of that definitions source expression. */
|
||||
private class ExplicitSignDef extends FlowSignDef instanceof SemSsaExplicitUpdate {
|
||||
final override Sign getSign() { result = semExprSign(super.getSourceExpr()) }
|
||||
}
|
||||
/** An SSA definition whose sign is determined by the sign of that definitions source expression. */
|
||||
private class ExplicitSignDef extends FlowSignDef instanceof SemSsaExplicitUpdate {
|
||||
final override Sign getSign() { result = semExprSign(super.getSourceExpr()) }
|
||||
}
|
||||
|
||||
/** An SSA Phi definition, whose sign is the union of the signs of its inputs. */
|
||||
private class PhiSignDef extends FlowSignDef instanceof SemSsaPhiNode {
|
||||
final override Sign getSign() {
|
||||
exists(SemSsaVariable inp, SemSsaReadPositionPhiInputEdge edge |
|
||||
edge.phiInput(this, inp) and
|
||||
result = semSsaSign(inp, edge)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An SSA definition whose sign is computed by a language-specific implementation. */
|
||||
abstract class CustomSignDef extends SignDef {
|
||||
abstract override Sign getSign();
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression for which the analysis can compute the sign.
|
||||
*
|
||||
* The actual computation of the sign is done in an override of the `getSign()` predicate. The
|
||||
* charpred of any subclass must _not_ invoke `getSign()`, directly or indirectly. This ensures
|
||||
* that the charpred does not introduce negative recursion. The `getSign()` predicate may be
|
||||
* recursive.
|
||||
*
|
||||
* Concrete implementations extend one of the following subclasses:
|
||||
* - `ConstantSignExpr`, for expressions with a compile-time constant value.
|
||||
* - `FlowSignExpr`, for expressions whose sign can be computed from the signs of their operands.
|
||||
* - `CustomsignExpr`, for expressions whose sign can be computed by a language-specific
|
||||
* implementation.
|
||||
*
|
||||
* If the same expression matches more than one of the above subclasses, the sign is computed as
|
||||
* follows:
|
||||
* - The sign of a `ConstantSignExpr` is computed solely from `ConstantSignExpr.getSign()`,
|
||||
* regardless of any other subclasses.
|
||||
* - If a non-`ConstantSignExpr` expression matches exactly one of `FlowSignExpr` or
|
||||
* `CustomSignExpr`, the sign is computed by that class' `getSign()` predicate.
|
||||
* - If a non-`ConstantSignExpr` expression matches both `FlowSignExpr` and `CustomSignExpr`, the
|
||||
* sign is the _intersection_ of the signs of those two classes' `getSign()` predicates. Thus,
|
||||
* both classes have the opportunity to _restrict_ the set of possible signs, not to generate new
|
||||
* possible signs.
|
||||
* - If an expression does not match any of the three subclasses, then it can have any sign.
|
||||
*
|
||||
* Note that the `getSign()` predicate is introduced only in subclasses of `SignExpr`.
|
||||
*/
|
||||
abstract class SignExpr instanceof SemExpr {
|
||||
SignExpr() { not Specific::ignoreExprSign(this) }
|
||||
|
||||
final string toString() { result = super.toString() }
|
||||
|
||||
abstract Sign getSign();
|
||||
}
|
||||
|
||||
/** An expression whose sign is determined by its constant numeric value. */
|
||||
private class ConstantSignExpr extends SignExpr {
|
||||
ConstantSignExpr() {
|
||||
this instanceof SemConstantIntegerExpr or
|
||||
exists(this.(SemNumericLiteralExpr).getApproximateFloatValue())
|
||||
}
|
||||
|
||||
final override Sign getSign() {
|
||||
exists(int i | this.(SemConstantIntegerExpr).getIntValue() = i |
|
||||
i < 0 and result = TNeg()
|
||||
or
|
||||
i = 0 and result = TZero()
|
||||
or
|
||||
i > 0 and result = TPos()
|
||||
)
|
||||
or
|
||||
not exists(this.(SemConstantIntegerExpr).getIntValue()) and
|
||||
exists(float f | f = this.(SemNumericLiteralExpr).getApproximateFloatValue() |
|
||||
f < 0 and result = TNeg()
|
||||
or
|
||||
f = 0 and result = TZero()
|
||||
or
|
||||
f > 0 and result = TPos()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
abstract private class NonConstantSignExpr extends SignExpr {
|
||||
NonConstantSignExpr() { not this instanceof ConstantSignExpr }
|
||||
|
||||
final override Sign getSign() {
|
||||
// The result is the _intersection_ of the signs computed from flow and by the language.
|
||||
(result = this.(FlowSignExpr).getSignRestriction() or not this instanceof FlowSignExpr) and
|
||||
(result = this.(CustomSignExpr).getSignRestriction() or not this instanceof CustomSignExpr)
|
||||
}
|
||||
}
|
||||
|
||||
/** An expression whose sign is computed from the signs of its operands. */
|
||||
abstract private class FlowSignExpr extends NonConstantSignExpr {
|
||||
abstract Sign getSignRestriction();
|
||||
}
|
||||
|
||||
/** An expression whose sign is computed by a language-specific implementation. */
|
||||
abstract class CustomSignExpr extends NonConstantSignExpr {
|
||||
abstract Sign getSignRestriction();
|
||||
}
|
||||
|
||||
/** An expression whose sign is unknown. */
|
||||
private class UnknownSignExpr extends SignExpr {
|
||||
UnknownSignExpr() {
|
||||
not this instanceof FlowSignExpr and
|
||||
not this instanceof CustomSignExpr and
|
||||
not this instanceof ConstantSignExpr and
|
||||
(
|
||||
// Only track numeric types.
|
||||
Utils::getTrackedType(this) instanceof SemNumericType
|
||||
or
|
||||
// Unless the language says to track this expression anyway.
|
||||
Specific::trackUnknownNonNumericExpr(this)
|
||||
)
|
||||
}
|
||||
|
||||
final override Sign getSign() { semAnySign(result) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Load` expression whose sign is computed from the sign of its SSA definition, restricted by
|
||||
* inference from any intervening guards.
|
||||
*/
|
||||
class UseSignExpr extends FlowSignExpr {
|
||||
SemSsaVariable v;
|
||||
|
||||
UseSignExpr() { v.getAUse() = this }
|
||||
|
||||
override Sign getSignRestriction() {
|
||||
// Propagate via SSA
|
||||
// Propagate the sign from the def of `v`, incorporating any inference from guards.
|
||||
result = semSsaSign(v, any(SemSsaReadPositionBlock bb | bb.getAnExpr() = this))
|
||||
or
|
||||
// No block for this read. Just use the sign of the def.
|
||||
// REVIEW: How can this happen?
|
||||
not exists(SemSsaReadPositionBlock bb | bb.getAnExpr() = this) and
|
||||
result = semSsaDefSign(v)
|
||||
}
|
||||
}
|
||||
|
||||
/** A binary expression whose sign is computed from the signs of its operands. */
|
||||
private class BinarySignExpr extends FlowSignExpr {
|
||||
SemBinaryExpr binary;
|
||||
|
||||
BinarySignExpr() { binary = this }
|
||||
|
||||
override Sign getSignRestriction() {
|
||||
exists(SemExpr left, SemExpr right |
|
||||
binaryExprOperands(binary, left, right) and
|
||||
result =
|
||||
semExprSign(pragma[only_bind_out](left))
|
||||
.applyBinaryOp(semExprSign(pragma[only_bind_out](right)), binary.getOpcode())
|
||||
)
|
||||
or
|
||||
exists(SemDivExpr div | div = binary |
|
||||
result = semExprSign(div.getLeftOperand()) and
|
||||
result != TZero() and
|
||||
div.getRightOperand().(SemFloatingPointLiteralExpr).getFloatValue() = 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate binaryExprOperands(SemBinaryExpr binary, SemExpr left, SemExpr right) {
|
||||
binary.getLeftOperand() = left and binary.getRightOperand() = right
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Convert`, `Box`, or `Unbox` expression.
|
||||
*/
|
||||
private class SemCastExpr instanceof SemUnaryExpr {
|
||||
string toString() { result = super.toString() }
|
||||
|
||||
SemCastExpr() {
|
||||
this instanceof SemConvertExpr
|
||||
or
|
||||
this instanceof SemBoxExpr
|
||||
or
|
||||
this instanceof SemUnboxExpr
|
||||
}
|
||||
}
|
||||
|
||||
/** A unary expression whose sign is computed from the sign of its operand. */
|
||||
private class UnarySignExpr extends FlowSignExpr {
|
||||
SemUnaryExpr unary;
|
||||
|
||||
UnarySignExpr() { unary = this and not this instanceof SemCastExpr }
|
||||
|
||||
override Sign getSignRestriction() {
|
||||
result =
|
||||
semExprSign(pragma[only_bind_out](unary.getOperand())).applyUnaryOp(unary.getOpcode())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Convert`, `Box`, or `Unbox` expression, whose sign is computed based on
|
||||
* the sign of its operand and the source and destination types.
|
||||
*/
|
||||
abstract private class CastSignExpr extends FlowSignExpr {
|
||||
SemUnaryExpr cast;
|
||||
|
||||
CastSignExpr() { cast = this and cast instanceof SemCastExpr }
|
||||
|
||||
override Sign getSignRestriction() { result = semExprSign(cast.getOperand()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Convert` expression.
|
||||
*/
|
||||
private class ConvertSignExpr extends CastSignExpr {
|
||||
override SemConvertExpr cast;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Box` expression.
|
||||
*/
|
||||
private class BoxSignExpr extends CastSignExpr {
|
||||
override SemBoxExpr cast;
|
||||
}
|
||||
|
||||
/**
|
||||
* An `Unbox` expression.
|
||||
*/
|
||||
private class UnboxSignExpr extends CastSignExpr {
|
||||
override SemUnboxExpr cast;
|
||||
|
||||
UnboxSignExpr() {
|
||||
exists(SemType fromType | fromType = Utils::getTrackedType(cast.getOperand()) |
|
||||
// Only numeric source types are handled here.
|
||||
fromType instanceof SemNumericType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private predicate unknownSign(SemExpr e) { e instanceof UnknownSignExpr }
|
||||
|
||||
/**
|
||||
* Holds if `lowerbound` is a lower bound for `v` at `pos`. This is restricted
|
||||
* to only include bounds for which we might determine a sign.
|
||||
*/
|
||||
private predicate lowerBound(
|
||||
SemExpr lowerbound, SemSsaVariable v, SemSsaReadPosition pos, boolean isStrict
|
||||
) {
|
||||
exists(boolean testIsTrue, SemRelationalExpr comp |
|
||||
pos.hasReadOfVar(v) and
|
||||
semGuardControlsSsaRead(semGetComparisonGuard(comp), pos, testIsTrue) and
|
||||
not unknownSign(lowerbound)
|
||||
|
|
||||
testIsTrue = true and
|
||||
comp.getLesserOperand() = lowerbound and
|
||||
comp.getGreaterOperand() = Utils::semSsaRead(v, D::fromInt(0)) and
|
||||
(if comp.isStrict() then isStrict = true else isStrict = false)
|
||||
or
|
||||
testIsTrue = false and
|
||||
comp.getGreaterOperand() = lowerbound and
|
||||
comp.getLesserOperand() = Utils::semSsaRead(v, D::fromInt(0)) and
|
||||
(if comp.isStrict() then isStrict = false else isStrict = true)
|
||||
/** An SSA Phi definition, whose sign is the union of the signs of its inputs. */
|
||||
private class PhiSignDef extends FlowSignDef instanceof SemSsaPhiNode {
|
||||
final override Sign getSign() {
|
||||
exists(SemSsaVariable inp, SemSsaReadPositionPhiInputEdge edge |
|
||||
edge.phiInput(this, inp) and
|
||||
result = semSsaSign(inp, edge)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `upperbound` is an upper bound for `v` at `pos`. This is restricted
|
||||
* to only include bounds for which we might determine a sign.
|
||||
*/
|
||||
private predicate upperBound(
|
||||
SemExpr upperbound, SemSsaVariable v, SemSsaReadPosition pos, boolean isStrict
|
||||
) {
|
||||
exists(boolean testIsTrue, SemRelationalExpr comp |
|
||||
pos.hasReadOfVar(v) and
|
||||
semGuardControlsSsaRead(semGetComparisonGuard(comp), pos, testIsTrue) and
|
||||
not unknownSign(upperbound)
|
||||
|
|
||||
testIsTrue = true and
|
||||
comp.getGreaterOperand() = upperbound and
|
||||
comp.getLesserOperand() = Utils::semSsaRead(v, D::fromInt(0)) and
|
||||
(if comp.isStrict() then isStrict = true else isStrict = false)
|
||||
or
|
||||
testIsTrue = false and
|
||||
comp.getLesserOperand() = upperbound and
|
||||
comp.getGreaterOperand() = Utils::semSsaRead(v, D::fromInt(0)) and
|
||||
(if comp.isStrict() then isStrict = false else isStrict = true)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `eqbound` is an equality/inequality for `v` at `pos`. This is
|
||||
* restricted to only include bounds for which we might determine a sign. The
|
||||
* boolean `isEq` gives the polarity:
|
||||
* - `isEq = true` : `v = eqbound`
|
||||
* - `isEq = false` : `v != eqbound`
|
||||
*/
|
||||
private predicate eqBound(SemExpr eqbound, SemSsaVariable v, SemSsaReadPosition pos, boolean isEq) {
|
||||
exists(SemGuard guard, boolean testIsTrue, boolean polarity |
|
||||
pos.hasReadOfVar(v) and
|
||||
semGuardControlsSsaRead(guard, pos, testIsTrue) and
|
||||
guard.isEquality(eqbound, Utils::semSsaRead(v, D::fromInt(0)), polarity) and
|
||||
isEq = polarity.booleanXor(testIsTrue).booleanNot() and
|
||||
not unknownSign(eqbound)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `bound` is a bound for `v` at `pos` that needs to be positive in
|
||||
* order for `v` to be positive.
|
||||
*/
|
||||
private predicate posBound(SemExpr bound, SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
upperBound(bound, v, pos, _) or
|
||||
eqBound(bound, v, pos, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `bound` is a bound for `v` at `pos` that needs to be negative in
|
||||
* order for `v` to be negative.
|
||||
*/
|
||||
private predicate negBound(SemExpr bound, SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
lowerBound(bound, v, pos, _) or
|
||||
eqBound(bound, v, pos, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `bound` is a bound for `v` at `pos` that can restrict whether `v`
|
||||
* can be zero.
|
||||
*/
|
||||
private predicate zeroBound(SemExpr bound, SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
lowerBound(bound, v, pos, _) or
|
||||
upperBound(bound, v, pos, _) or
|
||||
eqBound(bound, v, pos, _)
|
||||
}
|
||||
|
||||
/** Holds if `bound` allows `v` to be positive at `pos`. */
|
||||
private predicate posBoundOk(SemExpr bound, SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
posBound(bound, v, pos) and TPos() = semExprSign(bound)
|
||||
}
|
||||
|
||||
/** Holds if `bound` allows `v` to be negative at `pos`. */
|
||||
private predicate negBoundOk(SemExpr bound, SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
negBound(bound, v, pos) and TNeg() = semExprSign(bound)
|
||||
}
|
||||
|
||||
/** Holds if `bound` allows `v` to be zero at `pos`. */
|
||||
private predicate zeroBoundOk(SemExpr bound, SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
lowerBound(bound, v, pos, _) and TNeg() = semExprSign(bound)
|
||||
or
|
||||
lowerBound(bound, v, pos, false) and TZero() = semExprSign(bound)
|
||||
or
|
||||
upperBound(bound, v, pos, _) and TPos() = semExprSign(bound)
|
||||
or
|
||||
upperBound(bound, v, pos, false) and TZero() = semExprSign(bound)
|
||||
or
|
||||
eqBound(bound, v, pos, true) and TZero() = semExprSign(bound)
|
||||
or
|
||||
eqBound(bound, v, pos, false) and TZero() != semExprSign(bound)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a bound that might restrict whether `v` has the sign `s`
|
||||
* at `pos`.
|
||||
*/
|
||||
private predicate hasGuard(SemSsaVariable v, SemSsaReadPosition pos, Sign s) {
|
||||
s = TPos() and posBound(_, v, pos)
|
||||
or
|
||||
s = TNeg() and negBound(_, v, pos)
|
||||
or
|
||||
s = TZero() and zeroBound(_, v, pos)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a possible sign of `v` at `pos` based on its definition, where the sign
|
||||
* might be ruled out by a guard.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private Sign guardedSsaSign(SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
result = semSsaDefSign(v) and
|
||||
pos.hasReadOfVar(v) and
|
||||
hasGuard(v, pos, result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a possible sign of `v` at `pos` based on its definition, where no guard
|
||||
* can rule it out.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private Sign unguardedSsaSign(SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
result = semSsaDefSign(v) and
|
||||
pos.hasReadOfVar(v) and
|
||||
not hasGuard(v, pos, result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a possible sign of `v` at read position `pos`, where a guard could have
|
||||
* ruled out the sign but does not.
|
||||
* This does not check that the definition of `v` also allows the sign.
|
||||
*/
|
||||
private Sign guardedSsaSignOk(SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
result = TPos() and
|
||||
forex(SemExpr bound | posBound(bound, v, pos) | posBoundOk(bound, v, pos))
|
||||
or
|
||||
result = TNeg() and
|
||||
forex(SemExpr bound | negBound(bound, v, pos) | negBoundOk(bound, v, pos))
|
||||
or
|
||||
result = TZero() and
|
||||
forex(SemExpr bound | zeroBound(bound, v, pos) | zeroBoundOk(bound, v, pos))
|
||||
}
|
||||
|
||||
/** Gets a possible sign for `v` at `pos`. */
|
||||
private Sign semSsaSign(SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
result = unguardedSsaSign(v, pos)
|
||||
or
|
||||
result = guardedSsaSign(v, pos) and
|
||||
result = guardedSsaSignOk(v, pos)
|
||||
}
|
||||
|
||||
/** Gets a possible sign for `v`. */
|
||||
pragma[nomagic]
|
||||
Sign semSsaDefSign(SemSsaVariable v) { result = v.(SignDef).getSign() }
|
||||
|
||||
/** Gets a possible sign for `e`. */
|
||||
cached
|
||||
Sign semExprSign(SemExpr e) {
|
||||
exists(Sign s | s = e.(SignExpr).getSign() |
|
||||
if
|
||||
Utils::getTrackedType(e) instanceof SemUnsignedIntegerType and
|
||||
s = TNeg() and
|
||||
not Specific::ignoreTypeRestrictions(e)
|
||||
then result = TPos()
|
||||
else result = s
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dummy predicate that holds for any sign. This is added to improve readability
|
||||
* of cases where the sign is unrestricted.
|
||||
*/
|
||||
predicate semAnySign(Sign s) { any() }
|
||||
|
||||
/** Holds if `e` can be positive and cannot be negative. */
|
||||
predicate semPositive(SemExpr e) {
|
||||
semExprSign(e) = TPos() and
|
||||
not semExprSign(e) = TNeg()
|
||||
}
|
||||
|
||||
/** Holds if `e` can be negative and cannot be positive. */
|
||||
predicate semNegative(SemExpr e) {
|
||||
semExprSign(e) = TNeg() and
|
||||
not semExprSign(e) = TPos()
|
||||
}
|
||||
|
||||
/** Holds if `e` is strictly positive. */
|
||||
predicate semStrictlyPositive(SemExpr e) {
|
||||
semExprSign(e) = TPos() and
|
||||
not semExprSign(e) = TNeg() and
|
||||
not semExprSign(e) = TZero()
|
||||
}
|
||||
|
||||
/** Holds if `e` is strictly negative. */
|
||||
predicate semStrictlyNegative(SemExpr e) {
|
||||
semExprSign(e) = TNeg() and
|
||||
not semExprSign(e) = TPos() and
|
||||
not semExprSign(e) = TZero()
|
||||
}
|
||||
}
|
||||
|
||||
/** An SSA definition whose sign is computed by a language-specific implementation. */
|
||||
abstract class CustomSignDef extends SignDef {
|
||||
abstract override Sign getSign();
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression for which the analysis can compute the sign.
|
||||
*
|
||||
* The actual computation of the sign is done in an override of the `getSign()` predicate. The
|
||||
* charpred of any subclass must _not_ invoke `getSign()`, directly or indirectly. This ensures
|
||||
* that the charpred does not introduce negative recursion. The `getSign()` predicate may be
|
||||
* recursive.
|
||||
*
|
||||
* Concrete implementations extend one of the following subclasses:
|
||||
* - `ConstantSignExpr`, for expressions with a compile-time constant value.
|
||||
* - `FlowSignExpr`, for expressions whose sign can be computed from the signs of their operands.
|
||||
* - `CustomsignExpr`, for expressions whose sign can be computed by a language-specific
|
||||
* implementation.
|
||||
*
|
||||
* If the same expression matches more than one of the above subclasses, the sign is computed as
|
||||
* follows:
|
||||
* - The sign of a `ConstantSignExpr` is computed solely from `ConstantSignExpr.getSign()`,
|
||||
* regardless of any other subclasses.
|
||||
* - If a non-`ConstantSignExpr` expression matches exactly one of `FlowSignExpr` or
|
||||
* `CustomSignExpr`, the sign is computed by that class' `getSign()` predicate.
|
||||
* - If a non-`ConstantSignExpr` expression matches both `FlowSignExpr` and `CustomSignExpr`, the
|
||||
* sign is the _intersection_ of the signs of those two classes' `getSign()` predicates. Thus,
|
||||
* both classes have the opportunity to _restrict_ the set of possible signs, not to generate new
|
||||
* possible signs.
|
||||
* - If an expression does not match any of the three subclasses, then it can have any sign.
|
||||
*
|
||||
* Note that the `getSign()` predicate is introduced only in subclasses of `SignExpr`.
|
||||
*/
|
||||
abstract class SignExpr instanceof SemExpr {
|
||||
SignExpr() { not Specific::ignoreExprSign(this) }
|
||||
|
||||
final string toString() { result = super.toString() }
|
||||
|
||||
abstract Sign getSign();
|
||||
}
|
||||
|
||||
/** An expression whose sign is determined by its constant numeric value. */
|
||||
private class ConstantSignExpr extends SignExpr {
|
||||
ConstantSignExpr() {
|
||||
this instanceof SemConstantIntegerExpr or
|
||||
exists(this.(SemNumericLiteralExpr).getApproximateFloatValue())
|
||||
}
|
||||
|
||||
final override Sign getSign() {
|
||||
exists(int i | this.(SemConstantIntegerExpr).getIntValue() = i |
|
||||
i < 0 and result = TNeg()
|
||||
or
|
||||
i = 0 and result = TZero()
|
||||
or
|
||||
i > 0 and result = TPos()
|
||||
)
|
||||
or
|
||||
not exists(this.(SemConstantIntegerExpr).getIntValue()) and
|
||||
exists(float f | f = this.(SemNumericLiteralExpr).getApproximateFloatValue() |
|
||||
f < 0 and result = TNeg()
|
||||
or
|
||||
f = 0 and result = TZero()
|
||||
or
|
||||
f > 0 and result = TPos()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
abstract private class NonConstantSignExpr extends SignExpr {
|
||||
NonConstantSignExpr() { not this instanceof ConstantSignExpr }
|
||||
|
||||
final override Sign getSign() {
|
||||
// The result is the _intersection_ of the signs computed from flow and by the language.
|
||||
(result = this.(FlowSignExpr).getSignRestriction() or not this instanceof FlowSignExpr) and
|
||||
(result = this.(CustomSignExpr).getSignRestriction() or not this instanceof CustomSignExpr)
|
||||
}
|
||||
}
|
||||
|
||||
/** An expression whose sign is computed from the signs of its operands. */
|
||||
abstract private class FlowSignExpr extends NonConstantSignExpr {
|
||||
abstract Sign getSignRestriction();
|
||||
}
|
||||
|
||||
/** An expression whose sign is computed by a language-specific implementation. */
|
||||
abstract class CustomSignExpr extends NonConstantSignExpr {
|
||||
abstract Sign getSignRestriction();
|
||||
}
|
||||
|
||||
/** An expression whose sign is unknown. */
|
||||
private class UnknownSignExpr extends SignExpr {
|
||||
UnknownSignExpr() {
|
||||
not this instanceof FlowSignExpr and
|
||||
not this instanceof CustomSignExpr and
|
||||
not this instanceof ConstantSignExpr and
|
||||
(
|
||||
// Only track numeric types.
|
||||
getTrackedType(this) instanceof SemNumericType
|
||||
or
|
||||
// Unless the language says to track this expression anyway.
|
||||
Specific::trackUnknownNonNumericExpr(this)
|
||||
)
|
||||
}
|
||||
|
||||
final override Sign getSign() { semAnySign(result) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Load` expression whose sign is computed from the sign of its SSA definition, restricted by
|
||||
* inference from any intervening guards.
|
||||
*/
|
||||
class UseSignExpr extends FlowSignExpr {
|
||||
SemSsaVariable v;
|
||||
|
||||
UseSignExpr() { v.getAUse() = this }
|
||||
|
||||
override Sign getSignRestriction() {
|
||||
// Propagate via SSA
|
||||
// Propagate the sign from the def of `v`, incorporating any inference from guards.
|
||||
result = semSsaSign(v, any(SemSsaReadPositionBlock bb | bb.getAnExpr() = this))
|
||||
or
|
||||
// No block for this read. Just use the sign of the def.
|
||||
// REVIEW: How can this happen?
|
||||
not exists(SemSsaReadPositionBlock bb | bb.getAnExpr() = this) and
|
||||
result = semSsaDefSign(v)
|
||||
}
|
||||
}
|
||||
|
||||
/** A binary expression whose sign is computed from the signs of its operands. */
|
||||
private class BinarySignExpr extends FlowSignExpr {
|
||||
SemBinaryExpr binary;
|
||||
|
||||
BinarySignExpr() { binary = this }
|
||||
|
||||
override Sign getSignRestriction() {
|
||||
exists(SemExpr left, SemExpr right |
|
||||
binaryExprOperands(binary, left, right) and
|
||||
result =
|
||||
semExprSign(pragma[only_bind_out](left))
|
||||
.applyBinaryOp(semExprSign(pragma[only_bind_out](right)), binary.getOpcode())
|
||||
)
|
||||
or
|
||||
exists(SemDivExpr div | div = binary |
|
||||
result = semExprSign(div.getLeftOperand()) and
|
||||
result != TZero() and
|
||||
div.getRightOperand().(SemFloatingPointLiteralExpr).getFloatValue() = 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate binaryExprOperands(SemBinaryExpr binary, SemExpr left, SemExpr right) {
|
||||
binary.getLeftOperand() = left and binary.getRightOperand() = right
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Convert`, `Box`, or `Unbox` expression.
|
||||
*/
|
||||
private class SemCastExpr extends SemUnaryExpr {
|
||||
SemCastExpr() {
|
||||
this instanceof SemConvertExpr
|
||||
or
|
||||
this instanceof SemBoxExpr
|
||||
or
|
||||
this instanceof SemUnboxExpr
|
||||
}
|
||||
}
|
||||
|
||||
/** A unary expression whose sign is computed from the sign of its operand. */
|
||||
private class UnarySignExpr extends FlowSignExpr {
|
||||
SemUnaryExpr unary;
|
||||
|
||||
UnarySignExpr() { unary = this and not this instanceof SemCastExpr }
|
||||
|
||||
override Sign getSignRestriction() {
|
||||
result = semExprSign(pragma[only_bind_out](unary.getOperand())).applyUnaryOp(unary.getOpcode())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Convert`, `Box`, or `Unbox` expression, whose sign is computed based on
|
||||
* the sign of its operand and the source and destination types.
|
||||
*/
|
||||
abstract private class CastSignExpr extends FlowSignExpr {
|
||||
SemUnaryExpr cast;
|
||||
|
||||
CastSignExpr() { cast = this and cast instanceof SemCastExpr }
|
||||
|
||||
override Sign getSignRestriction() { result = semExprSign(cast.getOperand()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Convert` expression.
|
||||
*/
|
||||
private class ConvertSignExpr extends CastSignExpr {
|
||||
override SemConvertExpr cast;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Box` expression.
|
||||
*/
|
||||
private class BoxSignExpr extends CastSignExpr {
|
||||
override SemBoxExpr cast;
|
||||
}
|
||||
|
||||
/**
|
||||
* An `Unbox` expression.
|
||||
*/
|
||||
private class UnboxSignExpr extends CastSignExpr {
|
||||
override SemUnboxExpr cast;
|
||||
|
||||
UnboxSignExpr() {
|
||||
exists(SemType fromType | fromType = getTrackedType(cast.getOperand()) |
|
||||
// Only numeric source types are handled here.
|
||||
fromType instanceof SemNumericType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private predicate unknownSign(SemExpr e) { e instanceof UnknownSignExpr }
|
||||
|
||||
/**
|
||||
* Holds if `lowerbound` is a lower bound for `v` at `pos`. This is restricted
|
||||
* to only include bounds for which we might determine a sign.
|
||||
*/
|
||||
private predicate lowerBound(
|
||||
SemExpr lowerbound, SemSsaVariable v, SemSsaReadPosition pos, boolean isStrict
|
||||
) {
|
||||
exists(boolean testIsTrue, SemRelationalExpr comp |
|
||||
pos.hasReadOfVar(v) and
|
||||
semGuardControlsSsaRead(semGetComparisonGuard(comp), pos, testIsTrue) and
|
||||
not unknownSign(lowerbound)
|
||||
|
|
||||
testIsTrue = true and
|
||||
comp.getLesserOperand() = lowerbound and
|
||||
comp.getGreaterOperand() = semSsaRead(v, 0) and
|
||||
(if comp.isStrict() then isStrict = true else isStrict = false)
|
||||
or
|
||||
testIsTrue = false and
|
||||
comp.getGreaterOperand() = lowerbound and
|
||||
comp.getLesserOperand() = semSsaRead(v, 0) and
|
||||
(if comp.isStrict() then isStrict = false else isStrict = true)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `upperbound` is an upper bound for `v` at `pos`. This is restricted
|
||||
* to only include bounds for which we might determine a sign.
|
||||
*/
|
||||
private predicate upperBound(
|
||||
SemExpr upperbound, SemSsaVariable v, SemSsaReadPosition pos, boolean isStrict
|
||||
) {
|
||||
exists(boolean testIsTrue, SemRelationalExpr comp |
|
||||
pos.hasReadOfVar(v) and
|
||||
semGuardControlsSsaRead(semGetComparisonGuard(comp), pos, testIsTrue) and
|
||||
not unknownSign(upperbound)
|
||||
|
|
||||
testIsTrue = true and
|
||||
comp.getGreaterOperand() = upperbound and
|
||||
comp.getLesserOperand() = semSsaRead(v, 0) and
|
||||
(if comp.isStrict() then isStrict = true else isStrict = false)
|
||||
or
|
||||
testIsTrue = false and
|
||||
comp.getLesserOperand() = upperbound and
|
||||
comp.getGreaterOperand() = semSsaRead(v, 0) and
|
||||
(if comp.isStrict() then isStrict = false else isStrict = true)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `eqbound` is an equality/inequality for `v` at `pos`. This is
|
||||
* restricted to only include bounds for which we might determine a sign. The
|
||||
* boolean `isEq` gives the polarity:
|
||||
* - `isEq = true` : `v = eqbound`
|
||||
* - `isEq = false` : `v != eqbound`
|
||||
*/
|
||||
private predicate eqBound(SemExpr eqbound, SemSsaVariable v, SemSsaReadPosition pos, boolean isEq) {
|
||||
exists(SemGuard guard, boolean testIsTrue, boolean polarity |
|
||||
pos.hasReadOfVar(v) and
|
||||
semGuardControlsSsaRead(guard, pos, testIsTrue) and
|
||||
guard.isEquality(eqbound, semSsaRead(v, 0), polarity) and
|
||||
isEq = polarity.booleanXor(testIsTrue).booleanNot() and
|
||||
not unknownSign(eqbound)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `bound` is a bound for `v` at `pos` that needs to be positive in
|
||||
* order for `v` to be positive.
|
||||
*/
|
||||
private predicate posBound(SemExpr bound, SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
upperBound(bound, v, pos, _) or
|
||||
eqBound(bound, v, pos, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `bound` is a bound for `v` at `pos` that needs to be negative in
|
||||
* order for `v` to be negative.
|
||||
*/
|
||||
private predicate negBound(SemExpr bound, SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
lowerBound(bound, v, pos, _) or
|
||||
eqBound(bound, v, pos, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `bound` is a bound for `v` at `pos` that can restrict whether `v`
|
||||
* can be zero.
|
||||
*/
|
||||
private predicate zeroBound(SemExpr bound, SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
lowerBound(bound, v, pos, _) or
|
||||
upperBound(bound, v, pos, _) or
|
||||
eqBound(bound, v, pos, _)
|
||||
}
|
||||
|
||||
/** Holds if `bound` allows `v` to be positive at `pos`. */
|
||||
private predicate posBoundOk(SemExpr bound, SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
posBound(bound, v, pos) and TPos() = semExprSign(bound)
|
||||
}
|
||||
|
||||
/** Holds if `bound` allows `v` to be negative at `pos`. */
|
||||
private predicate negBoundOk(SemExpr bound, SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
negBound(bound, v, pos) and TNeg() = semExprSign(bound)
|
||||
}
|
||||
|
||||
/** Holds if `bound` allows `v` to be zero at `pos`. */
|
||||
private predicate zeroBoundOk(SemExpr bound, SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
lowerBound(bound, v, pos, _) and TNeg() = semExprSign(bound)
|
||||
or
|
||||
lowerBound(bound, v, pos, false) and TZero() = semExprSign(bound)
|
||||
or
|
||||
upperBound(bound, v, pos, _) and TPos() = semExprSign(bound)
|
||||
or
|
||||
upperBound(bound, v, pos, false) and TZero() = semExprSign(bound)
|
||||
or
|
||||
eqBound(bound, v, pos, true) and TZero() = semExprSign(bound)
|
||||
or
|
||||
eqBound(bound, v, pos, false) and TZero() != semExprSign(bound)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a bound that might restrict whether `v` has the sign `s`
|
||||
* at `pos`.
|
||||
*/
|
||||
private predicate hasGuard(SemSsaVariable v, SemSsaReadPosition pos, Sign s) {
|
||||
s = TPos() and posBound(_, v, pos)
|
||||
or
|
||||
s = TNeg() and negBound(_, v, pos)
|
||||
or
|
||||
s = TZero() and zeroBound(_, v, pos)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a possible sign of `v` at `pos` based on its definition, where the sign
|
||||
* might be ruled out by a guard.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private Sign guardedSsaSign(SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
result = semSsaDefSign(v) and
|
||||
pos.hasReadOfVar(v) and
|
||||
hasGuard(v, pos, result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a possible sign of `v` at `pos` based on its definition, where no guard
|
||||
* can rule it out.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private Sign unguardedSsaSign(SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
result = semSsaDefSign(v) and
|
||||
pos.hasReadOfVar(v) and
|
||||
not hasGuard(v, pos, result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a possible sign of `v` at read position `pos`, where a guard could have
|
||||
* ruled out the sign but does not.
|
||||
* This does not check that the definition of `v` also allows the sign.
|
||||
*/
|
||||
private Sign guardedSsaSignOk(SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
result = TPos() and
|
||||
forex(SemExpr bound | posBound(bound, v, pos) | posBoundOk(bound, v, pos))
|
||||
or
|
||||
result = TNeg() and
|
||||
forex(SemExpr bound | negBound(bound, v, pos) | negBoundOk(bound, v, pos))
|
||||
or
|
||||
result = TZero() and
|
||||
forex(SemExpr bound | zeroBound(bound, v, pos) | zeroBoundOk(bound, v, pos))
|
||||
}
|
||||
|
||||
/** Gets a possible sign for `v` at `pos`. */
|
||||
private Sign semSsaSign(SemSsaVariable v, SemSsaReadPosition pos) {
|
||||
result = unguardedSsaSign(v, pos)
|
||||
or
|
||||
result = guardedSsaSign(v, pos) and
|
||||
result = guardedSsaSignOk(v, pos)
|
||||
}
|
||||
|
||||
/** Gets a possible sign for `v`. */
|
||||
pragma[nomagic]
|
||||
Sign semSsaDefSign(SemSsaVariable v) { result = v.(SignDef).getSign() }
|
||||
|
||||
/** Gets a possible sign for `e`. */
|
||||
cached
|
||||
Sign semExprSign(SemExpr e) {
|
||||
exists(Sign s | s = e.(SignExpr).getSign() |
|
||||
if
|
||||
getTrackedType(e) instanceof SemUnsignedIntegerType and
|
||||
s = TNeg() and
|
||||
not Specific::ignoreTypeRestrictions(e)
|
||||
then result = TPos()
|
||||
else result = s
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dummy predicate that holds for any sign. This is added to improve readability
|
||||
* of cases where the sign is unrestricted.
|
||||
*/
|
||||
predicate semAnySign(Sign s) { any() }
|
||||
|
||||
/** Holds if `e` can be positive and cannot be negative. */
|
||||
predicate semPositive(SemExpr e) {
|
||||
semExprSign(e) = TPos() and
|
||||
not semExprSign(e) = TNeg()
|
||||
}
|
||||
|
||||
/** Holds if `e` can be negative and cannot be positive. */
|
||||
predicate semNegative(SemExpr e) {
|
||||
semExprSign(e) = TNeg() and
|
||||
not semExprSign(e) = TPos()
|
||||
}
|
||||
|
||||
/** Holds if `e` is strictly positive. */
|
||||
predicate semStrictlyPositive(SemExpr e) {
|
||||
semExprSign(e) = TPos() and
|
||||
not semExprSign(e) = TNeg() and
|
||||
not semExprSign(e) = TZero()
|
||||
}
|
||||
|
||||
/** Holds if `e` is strictly negative. */
|
||||
predicate semStrictlyNegative(SemExpr e) {
|
||||
semExprSign(e) = TNeg() and
|
||||
not semExprSign(e) = TPos() and
|
||||
not semExprSign(e) = TZero()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/cpp-all
|
||||
version: 0.5.4-dev
|
||||
version: 0.4.6-dev
|
||||
groups: cpp
|
||||
dbscheme: semmlecode.cpp.dbscheme
|
||||
extractor: cpp
|
||||
@@ -7,4 +7,3 @@ library: true
|
||||
upgrades: upgrades
|
||||
dependencies:
|
||||
codeql/ssa: ${workspace}
|
||||
codeql/tutorial: ${workspace}
|
||||
|
||||
@@ -68,9 +68,7 @@ class Declaration extends Locatable, @declaration {
|
||||
* Holds if this declaration has the fully-qualified name `qualifiedName`.
|
||||
* See `getQualifiedName`.
|
||||
*/
|
||||
deprecated predicate hasQualifiedName(string qualifiedName) {
|
||||
this.getQualifiedName() = qualifiedName
|
||||
}
|
||||
predicate hasQualifiedName(string qualifiedName) { this.getQualifiedName() = qualifiedName }
|
||||
|
||||
/**
|
||||
* Holds if this declaration has a fully-qualified name with a name-space
|
||||
@@ -186,7 +184,7 @@ class Declaration extends Locatable, @declaration {
|
||||
predicate hasDefinition() { exists(this.getDefinition()) }
|
||||
|
||||
/** DEPRECATED: Use `hasDefinition` instead. */
|
||||
deprecated predicate isDefined() { this.hasDefinition() }
|
||||
predicate isDefined() { this.hasDefinition() }
|
||||
|
||||
/** Gets the preferred location of this declaration, if any. */
|
||||
override Location getLocation() { none() }
|
||||
|
||||
@@ -41,7 +41,7 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
|
||||
* `min<int>(int, int) -> int`, and the full signature of the uninstantiated
|
||||
* template on the first line would be `min<T>(T, T) -> T`.
|
||||
*/
|
||||
deprecated string getFullSignature() {
|
||||
string getFullSignature() {
|
||||
exists(string name, string templateArgs, string args |
|
||||
result = name + templateArgs + args + " -> " + this.getType().toString() and
|
||||
name = this.getQualifiedName() and
|
||||
@@ -318,7 +318,7 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
|
||||
MetricFunction getMetrics() { result = this }
|
||||
|
||||
/** Holds if this function calls the function `f`. */
|
||||
predicate calls(Function f) { this.calls(f, _) }
|
||||
predicate calls(Function f) { exists(Locatable l | this.calls(f, l)) }
|
||||
|
||||
/**
|
||||
* Holds if this function calls the function `f` in the `FunctionCall`
|
||||
@@ -335,7 +335,7 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
|
||||
}
|
||||
|
||||
/** Holds if this function accesses a function or variable or enumerator `a`. */
|
||||
predicate accesses(Declaration a) { this.accesses(a, _) }
|
||||
predicate accesses(Declaration a) { exists(Locatable l | this.accesses(a, l)) }
|
||||
|
||||
/**
|
||||
* Holds if this function accesses a function or variable or enumerator `a`
|
||||
|
||||
@@ -10,14 +10,12 @@ import semmle.code.cpp.File
|
||||
*/
|
||||
class Location extends @location {
|
||||
/** Gets the container corresponding to this location. */
|
||||
pragma[nomagic]
|
||||
Container getContainer() { this.fullLocationInfo(result, _, _, _, _) }
|
||||
|
||||
/** Gets the file corresponding to this location, if any. */
|
||||
File getFile() { result = this.getContainer() }
|
||||
|
||||
/** Gets the 1-based line number (inclusive) where this location starts. */
|
||||
pragma[nomagic]
|
||||
int getStartLine() { this.fullLocationInfo(_, result, _, _, _) }
|
||||
|
||||
/** Gets the 1-based column number (inclusive) where this location starts. */
|
||||
|
||||
@@ -816,12 +816,6 @@ private predicate floatingPointTypeMapping(
|
||||
or
|
||||
// _Float128x
|
||||
kind = 50 and base = 2 and domain = TRealDomain() and realKind = 50 and extended = true
|
||||
or
|
||||
// _Float16
|
||||
kind = 52 and base = 2 and domain = TRealDomain() and realKind = 52 and extended = false
|
||||
or
|
||||
// _Complex _Float16
|
||||
kind = 53 and base = 2 and domain = TComplexDomain() and realKind = 52 and extended = false
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,9 +12,7 @@ predicate freeFunction(Function f, int argNum) { argNum = f.(DeallocationFunctio
|
||||
*
|
||||
* DEPRECATED: Use `DeallocationExpr` instead (this also includes `delete` expressions).
|
||||
*/
|
||||
deprecated predicate freeCall(FunctionCall fc, Expr arg) {
|
||||
arg = fc.(DeallocationExpr).getFreedExpr()
|
||||
}
|
||||
predicate freeCall(FunctionCall fc, Expr arg) { arg = fc.(DeallocationExpr).getFreedExpr() }
|
||||
|
||||
/**
|
||||
* Is e some kind of allocation or deallocation (`new`, `alloc`, `realloc`, `delete`, `free` etc)?
|
||||
|
||||
@@ -33,7 +33,7 @@ DependencyOptions getDependencyOptions() { any() }
|
||||
class DependsSource extends Element {
|
||||
DependsSource() {
|
||||
// not inside a template instantiation
|
||||
not this.isFromTemplateInstantiation(_) or
|
||||
not exists(Element other | this.isFromTemplateInstantiation(other)) or
|
||||
// allow DeclarationEntrys of template specializations
|
||||
this.(DeclarationEntry).getDeclaration().(Function).isConstructedFrom(_) or
|
||||
this.(DeclarationEntry).getDeclaration().(Class).isConstructedFrom(_)
|
||||
|
||||
@@ -69,9 +69,12 @@ predicate functionContainsDisabledCode(Function f) {
|
||||
*/
|
||||
predicate functionContainsPreprocCode(Function f) {
|
||||
// `f` contains a preprocessor branch
|
||||
exists(string file, int pbdStartLine, int fBlockStartLine, int fBlockEndLine |
|
||||
exists(
|
||||
PreprocessorBranchDirective pbd, string file, int pbdStartLine, int fBlockStartLine,
|
||||
int fBlockEndLine
|
||||
|
|
||||
functionLocation(f, file, fBlockStartLine, fBlockEndLine) and
|
||||
pbdLocation(_, file, pbdStartLine) and
|
||||
pbdLocation(pbd, file, pbdStartLine) and
|
||||
pbdStartLine <= fBlockEndLine and
|
||||
pbdStartLine >= fBlockStartLine
|
||||
)
|
||||
|
||||
@@ -244,7 +244,9 @@ class ScanfFormatLiteral extends Expr {
|
||||
/**
|
||||
* Gets the maximum width option of the nth input (empty string if none is given).
|
||||
*/
|
||||
string getMaxWidthOpt(int n) { this.parseConvSpec(n, _, result, _, _) }
|
||||
string getMaxWidthOpt(int n) {
|
||||
exists(string spec, string len, string conv | this.parseConvSpec(n, spec, result, len, conv))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum width of the nth input.
|
||||
@@ -254,12 +256,18 @@ class ScanfFormatLiteral extends Expr {
|
||||
/**
|
||||
* Gets the length flag of the nth conversion specifier.
|
||||
*/
|
||||
string getLength(int n) { this.parseConvSpec(n, _, _, result, _) }
|
||||
string getLength(int n) {
|
||||
exists(string spec, string width, string conv |
|
||||
this.parseConvSpec(n, spec, width, result, conv)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the conversion character of the nth conversion specifier.
|
||||
*/
|
||||
string getConversionChar(int n) { this.parseConvSpec(n, _, _, _, result) }
|
||||
string getConversionChar(int n) {
|
||||
exists(string spec, string width, string len | this.parseConvSpec(n, spec, width, len, result))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum length of the string that can be produced by the nth
|
||||
|
||||
@@ -54,7 +54,7 @@ class SubBasicBlock extends ControlFlowNodeBase {
|
||||
* only condition under which a `SubBasicBlock` may have multiple
|
||||
* predecessors.
|
||||
*/
|
||||
predicate firstInBB() { this.getRankInBasicBlock(_) = 1 }
|
||||
predicate firstInBB() { exists(BasicBlock bb | this.getRankInBasicBlock(bb) = 1) }
|
||||
|
||||
/**
|
||||
* Holds if this `SubBasicBlock` comes last in its basic block. This is the
|
||||
|
||||
@@ -441,8 +441,8 @@ library class ExprEvaluator extends int {
|
||||
req = mid.(AssignExpr).getRValue()
|
||||
)
|
||||
or
|
||||
exists(Variable v, boolean sub1 |
|
||||
this.interestingVariableAccess(e, _, v, sub1) and
|
||||
exists(VariableAccess va, Variable v, boolean sub1 |
|
||||
this.interestingVariableAccess(e, va, v, sub1) and
|
||||
req = v.getAnAssignedValue() and
|
||||
(sub1 = true implies not this.ignoreVariableAssignment(e, v, req)) and
|
||||
sub = false
|
||||
@@ -876,7 +876,7 @@ private predicate nonAnalyzableVariableDefinition(Variable v, StmtParent def) {
|
||||
* empirically to have effect only on a few rare and pathological examples.
|
||||
*/
|
||||
private predicate tractableVariable(Variable v) {
|
||||
not nonAnalyzableVariableDefinition(v, _) or
|
||||
not exists(StmtParent def | nonAnalyzableVariableDefinition(v, def)) or
|
||||
strictcount(StmtParent def | nonAnalyzableVariableDefinition(v, def)) < 1000
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,5 @@
|
||||
import cpp
|
||||
|
||||
module DataFlow {
|
||||
import semmle.code.cpp.dataflow.internal.DataFlow
|
||||
import semmle.code.cpp.dataflow.internal.DataFlowImpl1
|
||||
import semmle.code.cpp.dataflow.internal.DataFlowImpl
|
||||
}
|
||||
|
||||
@@ -19,6 +19,5 @@ import semmle.code.cpp.dataflow.DataFlow
|
||||
import semmle.code.cpp.dataflow.DataFlow2
|
||||
|
||||
module TaintTracking {
|
||||
import semmle.code.cpp.dataflow.internal.tainttracking1.TaintTracking
|
||||
import semmle.code.cpp.dataflow.internal.tainttracking1.TaintTrackingImpl
|
||||
}
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
/**
|
||||
* Provides an implementation of global (interprocedural) data flow. This file
|
||||
* re-exports the local (intraprocedural) data flow analysis from
|
||||
* `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed
|
||||
* through the `Make` and `MakeWithState` modules.
|
||||
*/
|
||||
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
private import DataFlowImpl
|
||||
|
||||
/** An input configuration for data flow. */
|
||||
signature module ConfigSig {
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source.
|
||||
*/
|
||||
predicate isSource(Node source);
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink.
|
||||
*/
|
||||
predicate isSink(Node sink);
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited. This completely removes
|
||||
* `node` from the data flow graph.
|
||||
*/
|
||||
default predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
default predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
default predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
*/
|
||||
default predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Holds if an arbitrary number of implicit read steps of content `c` may be
|
||||
* taken at `node`.
|
||||
*/
|
||||
default predicate allowImplicitRead(Node node, ContentSet c) { none() }
|
||||
|
||||
/**
|
||||
* Gets the virtual dispatch branching limit when calculating field flow.
|
||||
* This can be overridden to a smaller value to improve performance (a
|
||||
* value of 0 disables field flow), or a larger value to get more results.
|
||||
*/
|
||||
default int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*
|
||||
* These features are generally not relevant for typical end-to-end data flow
|
||||
* queries, but should only be used for constructing paths that need to
|
||||
* somehow be pluggable in another path context.
|
||||
*/
|
||||
default FlowFeature getAFeature() { none() }
|
||||
|
||||
/** Holds if sources should be grouped in the result of `hasFlowPath`. */
|
||||
default predicate sourceGrouping(Node source, string sourceGroup) { none() }
|
||||
|
||||
/** Holds if sinks should be grouped in the result of `hasFlowPath`. */
|
||||
default predicate sinkGrouping(Node sink, string sinkGroup) { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (as it is in a `path-problem` query).
|
||||
*/
|
||||
default predicate includeHiddenNodes() { none() }
|
||||
}
|
||||
|
||||
/** An input configuration for data flow using flow state. */
|
||||
signature module StateConfigSig {
|
||||
bindingset[this]
|
||||
class FlowState;
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source with the given initial
|
||||
* `state`.
|
||||
*/
|
||||
predicate isSource(Node source, FlowState state);
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink accepting `state`.
|
||||
*/
|
||||
predicate isSink(Node sink, FlowState state);
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited. This completely removes
|
||||
* `node` from the data flow graph.
|
||||
*/
|
||||
default predicate isBarrier(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited when the flow state is
|
||||
* `state`.
|
||||
*/
|
||||
predicate isBarrier(Node node, FlowState state);
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
default predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
default predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
*/
|
||||
default predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
* This step is only applicable in `state1` and updates the flow state to `state2`.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2);
|
||||
|
||||
/**
|
||||
* Holds if an arbitrary number of implicit read steps of content `c` may be
|
||||
* taken at `node`.
|
||||
*/
|
||||
default predicate allowImplicitRead(Node node, ContentSet c) { none() }
|
||||
|
||||
/**
|
||||
* Gets the virtual dispatch branching limit when calculating field flow.
|
||||
* This can be overridden to a smaller value to improve performance (a
|
||||
* value of 0 disables field flow), or a larger value to get more results.
|
||||
*/
|
||||
default int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*
|
||||
* These features are generally not relevant for typical end-to-end data flow
|
||||
* queries, but should only be used for constructing paths that need to
|
||||
* somehow be pluggable in another path context.
|
||||
*/
|
||||
default FlowFeature getAFeature() { none() }
|
||||
|
||||
/** Holds if sources should be grouped in the result of `hasFlowPath`. */
|
||||
default predicate sourceGrouping(Node source, string sourceGroup) { none() }
|
||||
|
||||
/** Holds if sinks should be grouped in the result of `hasFlowPath`. */
|
||||
default predicate sinkGrouping(Node sink, string sinkGroup) { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (as it is in a `path-problem` query).
|
||||
*/
|
||||
default predicate includeHiddenNodes() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
|
||||
* measured in approximate number of interprocedural steps.
|
||||
*/
|
||||
signature int explorationLimitSig();
|
||||
|
||||
/**
|
||||
* The output of a data flow computation.
|
||||
*/
|
||||
signature module DataFlowSig {
|
||||
/**
|
||||
* A `Node` augmented with a call context (except for sinks) and an access path.
|
||||
* Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated.
|
||||
*/
|
||||
class PathNode;
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `source` to `sink`.
|
||||
*
|
||||
* The corresponding paths are generated from the end-points and the graph
|
||||
* included in the module `PathGraph`.
|
||||
*/
|
||||
predicate hasFlowPath(PathNode source, PathNode sink);
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `source` to `sink`.
|
||||
*/
|
||||
predicate hasFlow(Node source, Node sink);
|
||||
|
||||
/**
|
||||
* Holds if data can flow from some source to `sink`.
|
||||
*/
|
||||
predicate hasFlowTo(Node sink);
|
||||
|
||||
/**
|
||||
* Holds if data can flow from some source to `sink`.
|
||||
*/
|
||||
predicate hasFlowToExpr(DataFlowExpr sink);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a standard data flow computation.
|
||||
*/
|
||||
module Make<ConfigSig Config> implements DataFlowSig {
|
||||
private module C implements FullStateConfigSig {
|
||||
import DefaultState<Config>
|
||||
import Config
|
||||
}
|
||||
|
||||
import Impl<C>
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a data flow computation using flow state.
|
||||
*/
|
||||
module MakeWithState<StateConfigSig Config> implements DataFlowSig {
|
||||
private module C implements FullStateConfigSig {
|
||||
import Config
|
||||
}
|
||||
|
||||
import Impl<C>
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,396 +0,0 @@
|
||||
/**
|
||||
* DEPRECATED: Use `Make` and `MakeWithState` instead.
|
||||
*
|
||||
* Provides a `Configuration` class backwards-compatible interface to the data
|
||||
* flow library.
|
||||
*/
|
||||
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
private import DataFlowImpl
|
||||
import DataFlowImplCommonPublic
|
||||
import FlowStateString
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
* sources, sinks, and any other configurable aspect of the analysis. Each
|
||||
* use of the global data flow library must define its own unique extension
|
||||
* of this abstract class. To create a configuration, extend this class with
|
||||
* a subclass whose characteristic predicate is a unique singleton string.
|
||||
* For example, write
|
||||
*
|
||||
* ```ql
|
||||
* class MyAnalysisConfiguration extends DataFlow::Configuration {
|
||||
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
|
||||
* // Override `isSource` and `isSink`.
|
||||
* // Optionally override `isBarrier`.
|
||||
* // Optionally override `isAdditionalFlowStep`.
|
||||
* }
|
||||
* ```
|
||||
* Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and
|
||||
* the edges are those data-flow steps that preserve the value of the node
|
||||
* along with any additional edges defined by `isAdditionalFlowStep`.
|
||||
* Specifying nodes in `isBarrier` will remove those nodes from the graph, and
|
||||
* specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going
|
||||
* and/or out-going edges from those nodes, respectively.
|
||||
*
|
||||
* Then, to query whether there is flow between some `source` and `sink`,
|
||||
* write
|
||||
*
|
||||
* ```ql
|
||||
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
|
||||
* ```
|
||||
*
|
||||
* Multiple configurations can coexist, but two classes extending
|
||||
* `DataFlow::Configuration` should never depend on each other. One of them
|
||||
* should instead depend on a `DataFlow2::Configuration`, a
|
||||
* `DataFlow3::Configuration`, or a `DataFlow4::Configuration`.
|
||||
*/
|
||||
abstract class Configuration extends string {
|
||||
bindingset[this]
|
||||
Configuration() { any() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source.
|
||||
*/
|
||||
predicate isSource(Node source) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source with the given initial
|
||||
* `state`.
|
||||
*/
|
||||
predicate isSource(Node source, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink.
|
||||
*/
|
||||
predicate isSink(Node sink) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink accepting `state`.
|
||||
*/
|
||||
predicate isSink(Node sink, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited. This completely removes
|
||||
* `node` from the data flow graph.
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited when the flow state is
|
||||
* `state`.
|
||||
*/
|
||||
predicate isBarrier(Node node, FlowState state) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited.
|
||||
*/
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard) { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited when
|
||||
* the flow state is `state`
|
||||
*/
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
* This step is only applicable in `state1` and updates the flow state to `state2`.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an arbitrary number of implicit read steps of content `c` may be
|
||||
* taken at `node`.
|
||||
*/
|
||||
predicate allowImplicitRead(Node node, ContentSet c) { none() }
|
||||
|
||||
/**
|
||||
* Gets the virtual dispatch branching limit when calculating field flow.
|
||||
* This can be overridden to a smaller value to improve performance (a
|
||||
* value of 0 disables field flow), or a larger value to get more results.
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*
|
||||
* These features are generally not relevant for typical end-to-end data flow
|
||||
* queries, but should only be used for constructing paths that need to
|
||||
* somehow be pluggable in another path context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/** Holds if sources should be grouped in the result of `hasFlowPath`. */
|
||||
predicate sourceGrouping(Node source, string sourceGroup) { none() }
|
||||
|
||||
/** Holds if sinks should be grouped in the result of `hasFlowPath`. */
|
||||
predicate sinkGrouping(Node sink, string sinkGroup) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*
|
||||
* The corresponding paths are generated from the end-points and the graph
|
||||
* included in the module `PathGraph`.
|
||||
*/
|
||||
predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `FlowExploration<explorationLimit>` instead.
|
||||
*
|
||||
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
|
||||
* measured in approximate number of interprocedural steps.
|
||||
*/
|
||||
deprecated int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* This class exists to prevent mutual recursion between the user-overridden
|
||||
* member predicates of `Configuration` and the rest of the data-flow library.
|
||||
* Good performance cannot be guaranteed in the presence of such recursion, so
|
||||
* it should be replaced by using more than one copy of the data flow library.
|
||||
*/
|
||||
abstract private class ConfigurationRecursionPrevention extends Configuration {
|
||||
bindingset[this]
|
||||
ConfigurationRecursionPrevention() { any() }
|
||||
|
||||
override predicate hasFlow(Node source, Node sink) {
|
||||
strictcount(Node n | this.isSource(n)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSource(n, _)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSink(n)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSink(n, _)) < 0
|
||||
or
|
||||
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
|
||||
or
|
||||
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0
|
||||
or
|
||||
super.hasFlow(source, sink)
|
||||
}
|
||||
}
|
||||
|
||||
/** A bridge class to access the deprecated `isBarrierGuard`. */
|
||||
private class BarrierGuardGuardedNodeBridge extends Unit {
|
||||
abstract predicate guardedNode(Node n, Configuration config);
|
||||
|
||||
abstract predicate guardedNode(Node n, FlowState state, Configuration config);
|
||||
}
|
||||
|
||||
private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge {
|
||||
deprecated override predicate guardedNode(Node n, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
|
||||
deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g, state) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private FlowState relevantState(Configuration config) {
|
||||
config.isSource(_, result) or
|
||||
config.isSink(_, result) or
|
||||
config.isBarrier(_, result) or
|
||||
config.isAdditionalFlowStep(_, result, _, _) or
|
||||
config.isAdditionalFlowStep(_, _, _, result)
|
||||
}
|
||||
|
||||
private newtype TConfigState =
|
||||
TMkConfigState(Configuration config, FlowState state) {
|
||||
state = relevantState(config) or state instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) }
|
||||
|
||||
private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) }
|
||||
|
||||
private predicate singleConfiguration() { 1 = strictcount(Configuration c) }
|
||||
|
||||
private module Config implements FullStateConfigSig {
|
||||
class FlowState = TConfigState;
|
||||
|
||||
predicate isSource(Node source, FlowState state) {
|
||||
getConfig(state).isSource(source, getState(state))
|
||||
or
|
||||
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
predicate isSink(Node sink, FlowState state) {
|
||||
getConfig(state).isSink(sink, getState(state))
|
||||
or
|
||||
getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
predicate isBarrier(Node node, FlowState state) {
|
||||
getConfig(state).isBarrier(node, getState(state)) or
|
||||
getConfig(state).isBarrier(node) or
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state))
|
||||
}
|
||||
|
||||
predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) }
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) {
|
||||
getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and
|
||||
getConfig(state2) = getConfig(state1)
|
||||
or
|
||||
not singleConfiguration() and
|
||||
getConfig(state1).isAdditionalFlowStep(node1, node2) and
|
||||
state2 = state1
|
||||
}
|
||||
|
||||
predicate allowImplicitRead(Node node, ContentSet c) {
|
||||
any(Configuration config).allowImplicitRead(node, c)
|
||||
}
|
||||
|
||||
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
|
||||
|
||||
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }
|
||||
|
||||
predicate sourceGrouping(Node source, string sourceGroup) {
|
||||
any(Configuration config).sourceGrouping(source, sourceGroup)
|
||||
}
|
||||
|
||||
predicate sinkGrouping(Node sink, string sinkGroup) {
|
||||
any(Configuration config).sinkGrouping(sink, sinkGroup)
|
||||
}
|
||||
|
||||
predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() }
|
||||
}
|
||||
|
||||
private import Impl<Config> as I
|
||||
import I
|
||||
|
||||
/**
|
||||
* A `Node` augmented with a call context (except for sinks), an access path, and a configuration.
|
||||
* Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated.
|
||||
*/
|
||||
class PathNode instanceof I::PathNode {
|
||||
/** Gets a textual representation of this element. */
|
||||
final string toString() { result = super.toString() }
|
||||
|
||||
/**
|
||||
* Gets a textual representation of this element, including a textual
|
||||
* representation of the call context.
|
||||
*/
|
||||
final string toStringWithContext() { result = super.toStringWithContext() }
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
final predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
/** Gets the underlying `Node`. */
|
||||
final Node getNode() { result = super.getNode() }
|
||||
|
||||
/** Gets the `FlowState` of this node. */
|
||||
final FlowState getState() { result = getState(super.getState()) }
|
||||
|
||||
/** Gets the associated configuration. */
|
||||
final Configuration getConfiguration() { result = getConfig(super.getState()) }
|
||||
|
||||
/** Gets a successor of this node, if any. */
|
||||
final PathNode getASuccessor() { result = super.getASuccessor() }
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
final predicate isSource() { super.isSource() }
|
||||
|
||||
/** Holds if this node is a grouping of source nodes. */
|
||||
final predicate isSourceGroup(string group) { super.isSourceGroup(group) }
|
||||
|
||||
/** Holds if this node is a grouping of sink nodes. */
|
||||
final predicate isSinkGroup(string group) { super.isSinkGroup(group) }
|
||||
}
|
||||
|
||||
private predicate hasFlow(Node source, Node sink, Configuration config) {
|
||||
exists(PathNode source0, PathNode sink0 |
|
||||
hasFlowPath(source0, sink0, config) and
|
||||
source0.getNode() = source and
|
||||
sink0.getNode() = sink
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) {
|
||||
hasFlowPath(source, sink) and source.getConfiguration() = config
|
||||
}
|
||||
|
||||
private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) }
|
||||
|
||||
predicate flowsTo = hasFlow/3;
|
||||
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
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user