mirror of
https://github.com/github/codeql.git
synced 2026-05-27 01:21:23 +02:00
Compare commits
9 Commits
copilot/au
...
asgerf/js-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47d299e93b | ||
|
|
fbaf648e4f | ||
|
|
637ce99e44 | ||
|
|
bd9d6b1962 | ||
|
|
bd5e4761bd | ||
|
|
93deb33a2a | ||
|
|
f3b27a56b1 | ||
|
|
37852aa1d3 | ||
|
|
78b1651596 |
206
.github/workflows/go-version-update.yml
vendored
206
.github/workflows/go-version-update.yml
vendored
@@ -1,206 +0,0 @@
|
||||
name: Update Go version
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 3 * * 1" # Run weekly on Mondays at 3 AM UTC (1 = Monday)
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-go-version:
|
||||
name: Check and update Go version
|
||||
if: github.repository == 'github/codeql'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Fetch latest Go version
|
||||
id: fetch-version
|
||||
run: |
|
||||
LATEST_GO_VERSION=$(curl -s https://go.dev/dl/?mode=json | jq -r '.[0].version')
|
||||
|
||||
if [ -z "$LATEST_GO_VERSION" ] || [ "$LATEST_GO_VERSION" = "null" ]; then
|
||||
echo "Error: Failed to fetch latest Go version from go.dev"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Latest Go version from go.dev: $LATEST_GO_VERSION"
|
||||
echo "version=$LATEST_GO_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
# Extract version numbers (e.g., go1.26.0 -> 1.26.0)
|
||||
LATEST_VERSION_NUM=$(echo $LATEST_GO_VERSION | sed 's/^go//')
|
||||
echo "version_num=$LATEST_VERSION_NUM" >> $GITHUB_OUTPUT
|
||||
|
||||
# Extract major.minor version (e.g., 1.26.0 -> 1.26)
|
||||
LATEST_MAJOR_MINOR=$(echo $LATEST_VERSION_NUM | sed -E 's/^([0-9]+\.[0-9]+).*/\1/')
|
||||
echo "major_minor=$LATEST_MAJOR_MINOR" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Check current Go version
|
||||
id: current-version
|
||||
run: |
|
||||
CURRENT_VERSION=$(sed -n 's/.*go_sdk\.download(version = \"\([^\"]*\)\".*/\1/p' MODULE.bazel)
|
||||
|
||||
if [ -z "$CURRENT_VERSION" ]; then
|
||||
echo "Error: Could not extract Go version from MODULE.bazel"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Current Go version in MODULE.bazel: $CURRENT_VERSION"
|
||||
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
# Extract major.minor version
|
||||
CURRENT_MAJOR_MINOR=$(echo $CURRENT_VERSION | sed -E 's/^([0-9]+\.[0-9]+).*/\1/')
|
||||
echo "major_minor=$CURRENT_MAJOR_MINOR" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Compare versions
|
||||
id: compare
|
||||
run: |
|
||||
LATEST="${{ steps.fetch-version.outputs.version_num }}"
|
||||
CURRENT="${{ steps.current-version.outputs.version }}"
|
||||
|
||||
echo "Latest: $LATEST"
|
||||
echo "Current: $CURRENT"
|
||||
|
||||
if [ "$LATEST" = "$CURRENT" ]; then
|
||||
echo "Go version is up to date"
|
||||
echo "needs_update=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Go version needs update from $CURRENT to $LATEST"
|
||||
echo "needs_update=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Update Go version in files
|
||||
if: steps.compare.outputs.needs_update == 'true'
|
||||
run: |
|
||||
LATEST_VERSION_NUM="${{ steps.fetch-version.outputs.version_num }}"
|
||||
LATEST_MAJOR_MINOR="${{ steps.fetch-version.outputs.major_minor }}"
|
||||
CURRENT_VERSION="${{ steps.current-version.outputs.version }}"
|
||||
CURRENT_MAJOR_MINOR="${{ steps.current-version.outputs.major_minor }}"
|
||||
|
||||
echo "Updating from $CURRENT_VERSION to $LATEST_VERSION_NUM"
|
||||
|
||||
# Escape dots in current version strings for use in sed patterns
|
||||
CURRENT_VERSION_ESCAPED=$(echo "$CURRENT_VERSION" | sed 's/\./\\./g')
|
||||
CURRENT_MAJOR_MINOR_ESCAPED=$(echo "$CURRENT_MAJOR_MINOR" | sed 's/\./\\./g')
|
||||
|
||||
# Update MODULE.bazel
|
||||
if ! sed -i "s/go_sdk\.download(version = \"$CURRENT_VERSION_ESCAPED\")/go_sdk.download(version = \"$LATEST_VERSION_NUM\")/" MODULE.bazel; then
|
||||
echo "Warning: Failed to update MODULE.bazel"
|
||||
fi
|
||||
|
||||
# Update go/extractor/go.mod
|
||||
if ! sed -i "s/^go $CURRENT_MAJOR_MINOR_ESCAPED\$/go $LATEST_MAJOR_MINOR/" go/extractor/go.mod; then
|
||||
echo "Warning: Failed to update go directive in go.mod"
|
||||
fi
|
||||
if ! sed -i "s/^toolchain go$CURRENT_VERSION_ESCAPED\$/toolchain go$LATEST_VERSION_NUM/" go/extractor/go.mod; then
|
||||
echo "Warning: Failed to update toolchain in go.mod"
|
||||
fi
|
||||
|
||||
# Update go/extractor/autobuilder/build-environment.go
|
||||
if ! sed -i "s/var maxGoVersion = util\.NewSemVer(\"$CURRENT_MAJOR_MINOR_ESCAPED\")/var maxGoVersion = util.NewSemVer(\"$LATEST_MAJOR_MINOR\")/" go/extractor/autobuilder/build-environment.go; then
|
||||
echo "Warning: Failed to update build-environment.go"
|
||||
fi
|
||||
|
||||
# Update go/actions/test/action.yml
|
||||
if ! sed -i "s/default: \"~$CURRENT_VERSION_ESCAPED\"/default: \"~$LATEST_VERSION_NUM\"/" go/actions/test/action.yml; then
|
||||
echo "Warning: Failed to update action.yml"
|
||||
fi
|
||||
|
||||
# Show what changed
|
||||
git diff
|
||||
|
||||
- name: Check for changes
|
||||
id: check-changes
|
||||
if: steps.compare.outputs.needs_update == 'true'
|
||||
run: |
|
||||
if git diff --quiet; then
|
||||
echo "No changes detected"
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Changes detected"
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Check for existing PR
|
||||
if: steps.check-changes.outputs.has_changes == 'true'
|
||||
id: check-pr
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
BRANCH_NAME="workflow/go-version-update"
|
||||
PR_NUMBER=$(gh pr list --head "$BRANCH_NAME" --state open --json number --jq '.[0].number')
|
||||
|
||||
if [ -n "$PR_NUMBER" ]; then
|
||||
echo "Existing PR found: #$PR_NUMBER"
|
||||
echo "pr_exists=true" >> $GITHUB_OUTPUT
|
||||
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "No existing PR found"
|
||||
echo "pr_exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Commit and push changes
|
||||
if: steps.check-changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
BRANCH_NAME="workflow/go-version-update"
|
||||
LATEST_VERSION_NUM="${{ steps.fetch-version.outputs.version_num }}"
|
||||
LATEST_MAJOR_MINOR="${{ steps.fetch-version.outputs.major_minor }}"
|
||||
|
||||
# Create or switch to branch
|
||||
git checkout -B "$BRANCH_NAME"
|
||||
|
||||
# Stage and commit changes
|
||||
git add MODULE.bazel go/extractor/go.mod go/extractor/autobuilder/build-environment.go go/actions/test/action.yml
|
||||
git commit -m "Go: Update to $LATEST_MAJOR_MINOR"
|
||||
|
||||
# Push changes
|
||||
git push -f origin "$BRANCH_NAME"
|
||||
|
||||
- name: Create or update PR
|
||||
if: steps.check-changes.outputs.has_changes == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
BRANCH_NAME="workflow/go-version-update"
|
||||
LATEST_MAJOR_MINOR="${{ steps.fetch-version.outputs.major_minor }}"
|
||||
CURRENT_MAJOR_MINOR="${{ steps.current-version.outputs.major_minor }}"
|
||||
|
||||
PR_TITLE="Go: Update to $LATEST_MAJOR_MINOR"
|
||||
|
||||
PR_BODY=$(cat <<EOF
|
||||
This PR updates Go from $CURRENT_MAJOR_MINOR to $LATEST_MAJOR_MINOR.
|
||||
|
||||
Updated files:
|
||||
- \`MODULE.bazel\` - go_sdk.download version
|
||||
- \`go/extractor/go.mod\` - go directive and toolchain
|
||||
- \`go/extractor/autobuilder/build-environment.go\` - maxGoVersion
|
||||
- \`go/actions/test/action.yml\` - default go-test-version
|
||||
|
||||
This PR was automatically created by the [Go version update workflow](https://github.com/${{ github.repository }}/blob/main/.github/workflows/go-version-update.yml).
|
||||
EOF
|
||||
)
|
||||
|
||||
if [ "${{ steps.check-pr.outputs.pr_exists }}" = "true" ]; then
|
||||
echo "Updating existing PR #${{ steps.check-pr.outputs.pr_number }}"
|
||||
gh pr edit "${{ steps.check-pr.outputs.pr_number }}" --title "$PR_TITLE" --body "$PR_BODY"
|
||||
else
|
||||
echo "Creating new PR"
|
||||
gh pr create \
|
||||
--title "$PR_TITLE" \
|
||||
--body "$PR_BODY" \
|
||||
--base main \
|
||||
--head "$BRANCH_NAME" \
|
||||
--label "Go"
|
||||
fi
|
||||
@@ -1,13 +1,3 @@
|
||||
## 0.4.34
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Removed false positive injection sink models for the `context` input of `docker/build-push-action` and the `allowed-endpoints` input of `step-security/harden-runner`.
|
||||
|
||||
## 0.4.33
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.4.32
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.4.33
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,5 +0,0 @@
|
||||
## 0.4.34
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Removed false positive injection sink models for the `context` input of `docker/build-push-action` and the `allowed-endpoints` input of `step-security/harden-runner`.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.4.34
|
||||
lastReleaseVersion: 0.4.32
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/actions-all
|
||||
extensible: actionsSinkModel
|
||||
data:
|
||||
- ["docker/build-push-action", "*", "input.context", "code-injection", "manual"]
|
||||
@@ -0,0 +1,6 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/actions-all
|
||||
extensible: actionsSinkModel
|
||||
data:
|
||||
- ["step-security/harden-runner", "*", "input.allowed-endpoints", "command-injection", "manual"]
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/actions-all
|
||||
version: 0.4.35-dev
|
||||
version: 0.4.33-dev
|
||||
library: true
|
||||
warnOnImplicitThis: true
|
||||
dependencies:
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
## 0.6.26
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* Fixed alert messages in `actions/artifact-poisoning/critical` and `actions/artifact-poisoning/medium` as they previously included a redundant placeholder in the alert message that would on occasion contain a long block of yml that makes the alert difficult to understand. Also improved the wording to make it clearer that it is not the artifact that is being poisoned, but instead a potentially untrusted artifact that is consumed. Finally, changed the alert location to be the source, to align more with other queries reporting an artifact (e.g. zipslip) which is more useful.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The query `actions/missing-workflow-permissions` no longer produces false positive results on reusable workflows where all callers set permissions.
|
||||
|
||||
## 0.6.25
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.6.24
|
||||
|
||||
No user-facing changes.
|
||||
@@ -173,7 +159,7 @@ No user-facing changes.
|
||||
* `actions/if-expression-always-true/critical`
|
||||
* `actions/if-expression-always-true/high`
|
||||
* `actions/unnecessary-use-of-advanced-config`
|
||||
|
||||
|
||||
* The following query has been moved from the `code-scanning` suite to the `security-extended`
|
||||
suite. Any existing alerts for this query will be closed automatically unless the analysis is
|
||||
configured to use the `security-extended` suite.
|
||||
|
||||
@@ -26,23 +26,10 @@ string permissionsForJob(Job job) {
|
||||
"{" + concat(string permission | permission = jobNeedsPermission(job) | permission, ", ") + "}"
|
||||
}
|
||||
|
||||
predicate jobHasPermissions(Job job) {
|
||||
exists(job.getPermissions())
|
||||
or
|
||||
exists(job.getEnclosingWorkflow().getPermissions())
|
||||
or
|
||||
// The workflow is reusable and cannot be triggered in any other way; check callers
|
||||
exists(ReusableWorkflow r | r = job.getEnclosingWorkflow() |
|
||||
not exists(Event e | e = r.getOn().getAnEvent() | e.getName() != "workflow_call") and
|
||||
forall(Job caller | caller = job.getEnclosingWorkflow().(ReusableWorkflow).getACaller() |
|
||||
jobHasPermissions(caller)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
from Job job, string permissions
|
||||
where
|
||||
not jobHasPermissions(job) and
|
||||
not exists(job.getPermissions()) and
|
||||
not exists(job.getEnclosingWorkflow().getPermissions()) and
|
||||
// exists a trigger event that is not a workflow_call
|
||||
exists(Event e |
|
||||
e = job.getATriggerEvent() and
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: majorAnalysis
|
||||
---
|
||||
* Fixed alert messages in `actions/artifact-poisoning/critical` and `actions/artifact-poisoning/medium` as they previously included a redundant placeholder in the alert message that would on occasion contain a long block of yml that makes the alert difficult to understand. Also clarify the wording to make it clear that it is not the artifact that is being poisoned, but instead a potentially untrusted artifact that is consumed. Also change the alert location to be the source, to align more with other queries reporting an artifact (e.g. zipslip) which is more useful.
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.6.25
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,9 +0,0 @@
|
||||
## 0.6.26
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* Fixed alert messages in `actions/artifact-poisoning/critical` and `actions/artifact-poisoning/medium` as they previously included a redundant placeholder in the alert message that would on occasion contain a long block of yml that makes the alert difficult to understand. Also improved the wording to make it clearer that it is not the artifact that is being poisoned, but instead a potentially untrusted artifact that is consumed. Finally, changed the alert location to be the source, to align more with other queries reporting an artifact (e.g. zipslip) which is more useful.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The query `actions/missing-workflow-permissions` no longer produces false positive results on reusable workflows where all callers set permissions.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.6.26
|
||||
lastReleaseVersion: 0.6.24
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/actions-queries
|
||||
version: 0.6.27-dev
|
||||
version: 0.6.25-dev
|
||||
library: false
|
||||
warnOnImplicitThis: true
|
||||
groups: [actions, queries]
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/deploy-pages
|
||||
@@ -1,11 +0,0 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
pages: write
|
||||
|
||||
jobs:
|
||||
call-workflow:
|
||||
uses: ./.github/workflows/perms11.yml
|
||||
@@ -7,12 +7,10 @@ ql/cpp/ql/src/Diagnostics/ExtractedFiles.ql
|
||||
ql/cpp/ql/src/Diagnostics/ExtractionWarnings.ql
|
||||
ql/cpp/ql/src/Diagnostics/FailedExtractorInvocations.ql
|
||||
ql/cpp/ql/src/Likely Bugs/Arithmetic/BadAdditionOverflowCheck.ql
|
||||
ql/cpp/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql
|
||||
ql/cpp/ql/src/Likely Bugs/Arithmetic/SignedOverflowCheck.ql
|
||||
ql/cpp/ql/src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql
|
||||
ql/cpp/ql/src/Likely Bugs/Format/SnprintfOverflow.ql
|
||||
ql/cpp/ql/src/Likely Bugs/Format/WrongNumberOfFormatArguments.ql
|
||||
ql/cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql
|
||||
ql/cpp/ql/src/Likely Bugs/Memory Management/AllocaInLoop.ql
|
||||
ql/cpp/ql/src/Likely Bugs/Memory Management/PointerOverflow.ql
|
||||
ql/cpp/ql/src/Likely Bugs/Memory Management/ReturnStackAllocatedMemory.ql
|
||||
@@ -30,7 +28,6 @@ ql/cpp/ql/src/Security/CWE/CWE-120/VeryLikelyOverrunWrite.ql
|
||||
ql/cpp/ql/src/Security/CWE/CWE-131/NoSpaceForZeroTerminator.ql
|
||||
ql/cpp/ql/src/Security/CWE/CWE-134/UncontrolledFormatString.ql
|
||||
ql/cpp/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.ql
|
||||
ql/cpp/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.ql
|
||||
ql/cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql
|
||||
ql/cpp/ql/src/Security/CWE/CWE-253/HResultBooleanConversion.ql
|
||||
ql/cpp/ql/src/Security/CWE/CWE-311/CleartextFileWrite.ql
|
||||
@@ -43,7 +40,6 @@ ql/cpp/ql/src/Security/CWE/CWE-367/TOCTOUFilesystemRace.ql
|
||||
ql/cpp/ql/src/Security/CWE/CWE-416/IteratorToExpiredContainer.ql
|
||||
ql/cpp/ql/src/Security/CWE/CWE-416/UseOfStringAfterLifetimeEnds.ql
|
||||
ql/cpp/ql/src/Security/CWE/CWE-416/UseOfUniquePointerAfterLifetimeEnds.ql
|
||||
ql/cpp/ql/src/Security/CWE/CWE-468/SuspiciousAddWithSizeof.ql
|
||||
ql/cpp/ql/src/Security/CWE/CWE-497/ExposedSystemData.ql
|
||||
ql/cpp/ql/src/Security/CWE/CWE-611/XXE.ql
|
||||
ql/cpp/ql/src/Security/CWE/CWE-676/DangerousFunctionOverflow.ql
|
||||
|
||||
@@ -1,34 +1,3 @@
|
||||
## 10.0.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* The deprecated `NonThrowingFunction` class has been removed, use `NonCppThrowingFunction` instead.
|
||||
* The deprecated `ThrowingFunction` class has been removed, use `AlwaysSehThrowingFunction` instead.
|
||||
|
||||
### New Features
|
||||
|
||||
* Added a subclass `AutoconfConfigureTestFile` of `ConfigurationTestFile` that represents files created by GNU autoconf configure scripts to test the build configuration.
|
||||
|
||||
## 9.0.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* The `SourceModelCsv`, `SinkModelCsv`, and `SummaryModelCsv` classes and the associated CSV parsing infrastructure have been removed from `ExternalFlow.qll`. New models should be added as `.model.yml` files in the `ext/` directory.
|
||||
|
||||
### New Features
|
||||
|
||||
* Added a subclass `MesonPrivateTestFile` of `ConfigurationTestFile` that represents files created by Meson to test the build configuration.
|
||||
* Added a class `ConstructorDirectFieldInit` to represent field initializations that occur in member initializer lists.
|
||||
* Added a class `ConstructorDefaultFieldInit` to represent default field initializations.
|
||||
* Added a class `DataFlow::IndirectParameterNode` to represent the indirection of a parameter as a dataflow node.
|
||||
* Added a predicate `Node::asIndirectInstruction` which returns the `Instruction` that defines the indirect dataflow node, if any.
|
||||
* Added a class `IndirectUninitializedNode` to represent the indirection of an uninitialized local variable as a dataflow node.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Added `HttpReceiveHttpRequest`, `HttpReceiveRequestEntityBody`, and `HttpReceiveClientCertificate` from Win32's `http.h` as remote flow sources.
|
||||
* Added dataflow through members initialized via non-static data member initialization (NSDMI).
|
||||
|
||||
## 8.0.3
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* Added a class `IndirectUninitializedNode` to represent the indirection of an uninitialized local variable as a dataflow node.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* Data flow barriers and barrier guards can now be added using data extensions. For more information see [Customizing library models for C and C++](https://codeql.github.com/docs/codeql-language-guides/customizing-library-models-for-cpp/).
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* Added a class `DataFlow::IndirectParameterNode` to represent the indirection of a parameter as a dataflow node.
|
||||
* Added a predicate `Node::asIndirectInstruction` which returns the `Instruction` that defines the indirect dataflow node, if any.
|
||||
5
cpp/ql/lib/change-notes/2026-03-24-field-init.md
Normal file
5
cpp/ql/lib/change-notes/2026-03-24-field-init.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* Added a class `ConstructorDirectFieldInit` to represent field initializations that occur in member initializer lists.
|
||||
* Added a class `ConstructorDefaultFieldInit` to represent default field initializations.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: breaking
|
||||
---
|
||||
* The `SourceModelCsv`, `SinkModelCsv`, and `SummaryModelCsv` classes and the associated CSV parsing infrastructure have been removed from `ExternalFlow.qll`. New models should be added as `.model.yml` files in the `ext/` directory.
|
||||
4
cpp/ql/lib/change-notes/2026-03-30-nsdmi-dataflow.md
Normal file
4
cpp/ql/lib/change-notes/2026-03-30-nsdmi-dataflow.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added dataflow through members initialized via non-static data member initialization (NSDMI).
|
||||
4
cpp/ql/lib/change-notes/2026-03-31-http-flow-sources.md
Normal file
4
cpp/ql/lib/change-notes/2026-03-31-http-flow-sources.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added `HttpReceiveHttpRequest`, `HttpReceiveRequestEntityBody`, and `HttpReceiveClientCertificate` from Win32's `http.h` as remote flow sources.
|
||||
4
cpp/ql/lib/change-notes/2026-03-31-meson.md
Normal file
4
cpp/ql/lib/change-notes/2026-03-31-meson.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* Added a subclass `MesonPrivateTestFile` of `ConfigurationTestFile` that represents files created by Meson to test the build configuration.
|
||||
4
cpp/ql/lib/change-notes/2026-04-07-autoconf.md
Normal file
4
cpp/ql/lib/change-notes/2026-04-07-autoconf.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* Added a subclass `AutoconfConfigureTestFile` of `ConfigurationTestFile` that represents files created by GNU autoconf configure scripts to test the build configuration.
|
||||
@@ -1,10 +0,0 @@
|
||||
## 10.0.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* The deprecated `NonThrowingFunction` class has been removed, use `NonCppThrowingFunction` instead.
|
||||
* The deprecated `ThrowingFunction` class has been removed, use `AlwaysSehThrowingFunction` instead.
|
||||
|
||||
### New Features
|
||||
|
||||
* Added a subclass `AutoconfConfigureTestFile` of `ConfigurationTestFile` that represents files created by GNU autoconf configure scripts to test the build configuration.
|
||||
@@ -1,19 +0,0 @@
|
||||
## 9.0.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* The `SourceModelCsv`, `SinkModelCsv`, and `SummaryModelCsv` classes and the associated CSV parsing infrastructure have been removed from `ExternalFlow.qll`. New models should be added as `.model.yml` files in the `ext/` directory.
|
||||
|
||||
### New Features
|
||||
|
||||
* Added a subclass `MesonPrivateTestFile` of `ConfigurationTestFile` that represents files created by Meson to test the build configuration.
|
||||
* Added a class `ConstructorDirectFieldInit` to represent field initializations that occur in member initializer lists.
|
||||
* Added a class `ConstructorDefaultFieldInit` to represent default field initializations.
|
||||
* Added a class `DataFlow::IndirectParameterNode` to represent the indirection of a parameter as a dataflow node.
|
||||
* Added a predicate `Node::asIndirectInstruction` which returns the `Instruction` that defines the indirect dataflow node, if any.
|
||||
* Added a class `IndirectUninitializedNode` to represent the indirection of an uninitialized local variable as a dataflow node.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Added `HttpReceiveHttpRequest`, `HttpReceiveRequestEntityBody`, and `HttpReceiveClientCertificate` from Win32's `http.h` as remote flow sources.
|
||||
* Added dataflow through members initialized via non-static data member initialization (NSDMI).
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 10.0.0
|
||||
lastReleaseVersion: 8.0.3
|
||||
|
||||
@@ -12,7 +12,4 @@ extensions:
|
||||
- ["", "", False, "_malloca", "0", "", "", False]
|
||||
- ["", "", False, "calloc", "1", "0", "", True]
|
||||
- ["std", "", False, "calloc", "1", "0", "", True]
|
||||
- ["bsl", "", False, "calloc", "1", "0", "", True]
|
||||
- ["", "", False, "aligned_alloc", "1", "", "", True]
|
||||
- ["std", "", False, "aligned_alloc", "1", "", "", True]
|
||||
- ["bsl", "", False, "aligned_alloc", "1", "", "", True]
|
||||
- ["bsl", "", False, "calloc", "1", "0", "", True]
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/cpp-all
|
||||
version: 10.0.1-dev
|
||||
version: 8.0.4-dev
|
||||
groups: cpp
|
||||
dbscheme: semmlecode.cpp.dbscheme
|
||||
extractor: cpp
|
||||
|
||||
@@ -459,13 +459,6 @@ class FormatLiteral extends Literal instanceof StringLiteral {
|
||||
*/
|
||||
int getConvSpecOffset(int n) { result = this.getFormat().indexOf("%", n, 0) }
|
||||
|
||||
/**
|
||||
* Gets the nth conversion specifier string.
|
||||
*/
|
||||
private string getConvSpecString(int n) {
|
||||
n >= 0 and result = "%" + this.getFormat().splitAt("%", n + 1)
|
||||
}
|
||||
|
||||
/*
|
||||
* Each of these predicates gets a regular expressions to match each individual
|
||||
* parts of a conversion specifier.
|
||||
@@ -531,20 +524,22 @@ class FormatLiteral extends Literal instanceof StringLiteral {
|
||||
int n, string spec, string params, string flags, string width, string prec, string len,
|
||||
string conv
|
||||
) {
|
||||
exists(string convSpec, string regexp |
|
||||
convSpec = this.getConvSpecString(n) and
|
||||
exists(int offset, string fmt, string rst, string regexp |
|
||||
offset = this.getConvSpecOffset(n) and
|
||||
fmt = this.getFormat() and
|
||||
rst = fmt.substring(offset, fmt.length()) and
|
||||
regexp = this.getConvSpecRegexp() and
|
||||
(
|
||||
spec = convSpec.regexpCapture(regexp, 1) and
|
||||
params = convSpec.regexpCapture(regexp, 2) and
|
||||
flags = convSpec.regexpCapture(regexp, 3) and
|
||||
width = convSpec.regexpCapture(regexp, 4) and
|
||||
prec = convSpec.regexpCapture(regexp, 5) and
|
||||
len = convSpec.regexpCapture(regexp, 6) and
|
||||
conv = convSpec.regexpCapture(regexp, 7)
|
||||
spec = rst.regexpCapture(regexp, 1) and
|
||||
params = rst.regexpCapture(regexp, 2) and
|
||||
flags = rst.regexpCapture(regexp, 3) and
|
||||
width = rst.regexpCapture(regexp, 4) and
|
||||
prec = rst.regexpCapture(regexp, 5) and
|
||||
len = rst.regexpCapture(regexp, 6) and
|
||||
conv = rst.regexpCapture(regexp, 7)
|
||||
or
|
||||
spec = convSpec.regexpCapture(regexp, 1) and
|
||||
not exists(convSpec.regexpCapture(regexp, 2)) and
|
||||
spec = rst.regexpCapture(regexp, 1) and
|
||||
not exists(rst.regexpCapture(regexp, 2)) and
|
||||
params = "" and
|
||||
flags = "" and
|
||||
width = "" and
|
||||
@@ -559,10 +554,12 @@ class FormatLiteral extends Literal instanceof StringLiteral {
|
||||
* Gets the nth conversion specifier (including the initial `%`).
|
||||
*/
|
||||
string getConvSpec(int n) {
|
||||
exists(string convSpec, string regexp |
|
||||
convSpec = this.getConvSpecString(n) and
|
||||
exists(int offset, string fmt, string rst, string regexp |
|
||||
offset = this.getConvSpecOffset(n) and
|
||||
fmt = this.getFormat() and
|
||||
rst = fmt.substring(offset, fmt.length()) and
|
||||
regexp = this.getConvSpecRegexp() and
|
||||
result = convSpec.regexpCapture(regexp, 1)
|
||||
result = rst.regexpCapture(regexp, 1)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -194,13 +194,6 @@ class ScanfFormatLiteral extends Expr {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nth conversion specifier string.
|
||||
*/
|
||||
private string getConvSpecString(int n) {
|
||||
n >= 0 and result = "%" + this.getFormat().splitAt("%", n + 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the regular expression to match each individual part of a conversion specifier.
|
||||
*/
|
||||
@@ -234,14 +227,16 @@ class ScanfFormatLiteral extends Expr {
|
||||
* specifier.
|
||||
*/
|
||||
predicate parseConvSpec(int n, string spec, string width, string len, string conv) {
|
||||
exists(string convSpec, string regexp |
|
||||
convSpec = this.getConvSpecString(n) and
|
||||
exists(int offset, string fmt, string rst, string regexp |
|
||||
offset = this.getConvSpecOffset(n) and
|
||||
fmt = this.getFormat() and
|
||||
rst = fmt.substring(offset, fmt.length()) and
|
||||
regexp = this.getConvSpecRegexp() and
|
||||
(
|
||||
spec = convSpec.regexpCapture(regexp, 1) and
|
||||
width = convSpec.regexpCapture(regexp, 2) and
|
||||
len = convSpec.regexpCapture(regexp, 3) and
|
||||
conv = convSpec.regexpCapture(regexp, 4)
|
||||
spec = rst.regexpCapture(regexp, 1) and
|
||||
width = rst.regexpCapture(regexp, 2) and
|
||||
len = rst.regexpCapture(regexp, 3) and
|
||||
conv = rst.regexpCapture(regexp, 4)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,15 +6,11 @@
|
||||
*
|
||||
* The extensible relations have the following columns:
|
||||
* - Sources:
|
||||
* `namespace; type; subtypes; name; signature; ext; output; kind; provenance`
|
||||
* `namespace; type; subtypes; name; signature; ext; output; kind`
|
||||
* - Sinks:
|
||||
* `namespace; type; subtypes; name; signature; ext; input; kind; provenance`
|
||||
* `namespace; type; subtypes; name; signature; ext; input; kind`
|
||||
* - Summaries:
|
||||
* `namespace; type; subtypes; name; signature; ext; input; output; kind; provenance`
|
||||
* - Barriers:
|
||||
* `namespace; type; subtypes; name; signature; ext; output; kind; provenance`
|
||||
* - BarrierGuards:
|
||||
* `namespace; type; subtypes; name; signature; ext; input; acceptingValue; kind; provenance`
|
||||
* `namespace; type; subtypes; name; signature; ext; input; output; kind`
|
||||
*
|
||||
* The interpretation of a row is similar to API-graphs with a left-to-right
|
||||
* reading.
|
||||
@@ -91,23 +87,11 @@
|
||||
* value, and
|
||||
* - flow from the _second_ indirection of the 0th argument to the first
|
||||
* indirection of the return value, etc.
|
||||
* 8. The `acceptingValue` column of barrier guard models specifies the condition
|
||||
* under which the guard blocks flow. It can be one of "true" or "false". In
|
||||
* the future "no-exception", "not-zero", "null", "not-null" may be supported.
|
||||
* 9. The `kind` column is a tag that can be referenced from QL to determine to
|
||||
* 8. The `kind` column is a tag that can be referenced from QL to determine to
|
||||
* which classes the interpreted elements should be added. For example, for
|
||||
* sources "remote" indicates a default remote flow source, and for summaries
|
||||
* "taint" indicates a default additional taint step and "value" indicates a
|
||||
* globally applicable value-preserving step.
|
||||
* 10. The `provenance` column is a tag to indicate the origin and verification of a model.
|
||||
* The format is {origin}-{verification} or just "manual" where the origin describes
|
||||
* the origin of the model and verification describes how the model has been verified.
|
||||
* Some examples are:
|
||||
* - "df-generated": The model has been generated by the model generator tool.
|
||||
* - "df-manual": The model has been generated by the model generator and verified by a human.
|
||||
* - "manual": The model has been written by hand.
|
||||
* This information is used in a heuristic for dataflow analysis to determine, if a
|
||||
* model or source code should be used for determining flow.
|
||||
*/
|
||||
|
||||
import cpp
|
||||
@@ -947,13 +931,13 @@ private module Cached {
|
||||
|
||||
private predicate barrierGuardChecks(IRGuardCondition g, Expr e, boolean gv, TKindModelPair kmp) {
|
||||
exists(
|
||||
SourceSinkInterpretationInput::InterpretNode n, Public::AcceptingValue acceptingValue,
|
||||
SourceSinkInterpretationInput::InterpretNode n, Public::AcceptingValue acceptingvalue,
|
||||
string kind, string model
|
||||
|
|
||||
isBarrierGuardNode(n, acceptingValue, kind, model) and
|
||||
isBarrierGuardNode(n, acceptingvalue, kind, model) and
|
||||
n.asNode().asExpr() = e and
|
||||
kmp = TMkPair(kind, model) and
|
||||
gv = convertAcceptingValue(acceptingValue).asBooleanValue() and
|
||||
gv = convertAcceptingValue(acceptingvalue).asBooleanValue() and
|
||||
n.asNode().(Private::ArgumentNode).getCall().asCallInstruction() = g
|
||||
)
|
||||
}
|
||||
@@ -970,14 +954,14 @@ private module Cached {
|
||||
) {
|
||||
exists(
|
||||
SourceSinkInterpretationInput::InterpretNode interpretNode,
|
||||
Public::AcceptingValue acceptingValue, string kind, string model, int indirectionIndex,
|
||||
Public::AcceptingValue acceptingvalue, string kind, string model, int indirectionIndex,
|
||||
Private::ArgumentNode arg
|
||||
|
|
||||
isBarrierGuardNode(interpretNode, acceptingValue, kind, model) and
|
||||
isBarrierGuardNode(interpretNode, acceptingvalue, kind, model) and
|
||||
arg = interpretNode.asNode() and
|
||||
arg.asIndirectExpr(indirectionIndex) = e and
|
||||
kmp = MkKindModelPairIntPair(TMkPair(kind, model), indirectionIndex) and
|
||||
gv = convertAcceptingValue(acceptingValue).asBooleanValue() and
|
||||
gv = convertAcceptingValue(acceptingvalue).asBooleanValue() and
|
||||
arg.getCall().asCallInstruction() = g
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ extensible predicate barrierModel(
|
||||
*/
|
||||
extensible predicate barrierGuardModel(
|
||||
string namespace, string type, boolean subtypes, string name, string signature, string ext,
|
||||
string input, string acceptingValue, string kind, string provenance, QlBuiltins::ExtensionId madId
|
||||
string input, string acceptingvalue, string kind, string provenance, QlBuiltins::ExtensionId madId
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -162,13 +162,13 @@ module SourceSinkInterpretationInput implements
|
||||
}
|
||||
|
||||
predicate barrierGuardElement(
|
||||
Element e, string input, Public::AcceptingValue acceptingValue, string kind,
|
||||
Element e, string input, Public::AcceptingValue acceptingvalue, string kind,
|
||||
Public::Provenance provenance, string model
|
||||
) {
|
||||
exists(
|
||||
string package, string type, boolean subtypes, string name, string signature, string ext
|
||||
|
|
||||
barrierGuardModel(package, type, subtypes, name, signature, ext, input, acceptingValue, kind,
|
||||
barrierGuardModel(package, type, subtypes, name, signature, ext, input, acceptingvalue, kind,
|
||||
provenance, model) and
|
||||
e = interpretElement(package, type, subtypes, name, signature, ext)
|
||||
)
|
||||
|
||||
@@ -11,3 +11,10 @@ import semmle.code.cpp.models.Models
|
||||
* The function may still raise a structured exception handling (SEH) exception.
|
||||
*/
|
||||
abstract class NonCppThrowingFunction extends Function { }
|
||||
|
||||
/**
|
||||
* A function that is guaranteed to never throw.
|
||||
*
|
||||
* DEPRECATED: use `NonCppThrowingFunction` instead.
|
||||
*/
|
||||
deprecated class NonThrowingFunction = NonCppThrowingFunction;
|
||||
|
||||
@@ -10,6 +10,19 @@ import semmle.code.cpp.Function
|
||||
import semmle.code.cpp.models.Models
|
||||
import semmle.code.cpp.models.interfaces.FunctionInputsAndOutputs
|
||||
|
||||
/**
|
||||
* A function that is known to raise an exception.
|
||||
*
|
||||
* DEPRECATED: use `AlwaysSehThrowingFunction` instead.
|
||||
*/
|
||||
abstract deprecated class ThrowingFunction extends Function {
|
||||
/**
|
||||
* Holds if this function may throw an exception during evaluation.
|
||||
* If `unconditional` is `true` the function always throws an exception.
|
||||
*/
|
||||
abstract predicate mayThrowException(boolean unconditional);
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that unconditionally raises a structured exception handling (SEH) exception.
|
||||
*/
|
||||
|
||||
@@ -1,28 +1,3 @@
|
||||
## 1.6.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Added `AllocationFunction` models for `aligned_alloc`, `std::aligned_alloc`, and `bsl::aligned_alloc`.
|
||||
* The "Comparison of narrow type with wide type in loop condition" (`cpp/comparison-with-wider-type`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
|
||||
* The "Multiplication result converted to larger type" (`cpp/integer-multiplication-cast-to-long`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
|
||||
* The "Suspicious add with sizeof" (`cpp/suspicious-add-sizeof`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
|
||||
* The "Wrong type of arguments to formatting function" (`cpp/wrong-type-format-argument`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
|
||||
* The "Implicit function declaration" (`cpp/implicit-function-declaration`) query has been upgraded to `high` precision. However, for `build mode: none` databases, it no longer produces any results. The results in this mode were found to be very noisy and fundamentally imprecise.
|
||||
|
||||
## 1.6.0
|
||||
|
||||
### Query Metadata Changes
|
||||
|
||||
* The `@security-severity` metadata of `cpp/cgi-xss` has been increased from 6.1 (medium) to 7.8 (high).
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The "Extraction warnings" (`cpp/diagnostics/extraction-warnings`) diagnostics query no longer yields `ExtractionRecoverableWarning`s for `build-mode: none` databases. The results were found to significantly increase the sizes of the produced SARIF files, making them unprocessable in some cases.
|
||||
* Fixed an issue with the "Suspicious add with sizeof" (`cpp/suspicious-add-sizeof`) query causing false positive results in `build-mode: none` databases.
|
||||
* Fixed an issue with the "Uncontrolled format string" (`cpp/tainted-format-string`) query involving certain kinds of formatting function implementations.
|
||||
* Fixed an issue with the "Wrong type of arguments to formatting function" (`cpp/wrong-type-format-argument`) query causing false positive results in `build-mode: none` databases.
|
||||
* Fixed an issue with the "Multiplication result converted to larger type" (`cpp/integer-multiplication-cast-to-long`) query causing false positive results in `build-mode: none` databases.
|
||||
|
||||
## 1.5.15
|
||||
|
||||
No user-facing changes.
|
||||
@@ -366,7 +341,7 @@ No user-facing changes.
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The "non-constant format string" query (`cpp/non-constant-format`) has been updated to produce fewer false positives.
|
||||
* Added dataflow models for the `gettext` function variants.
|
||||
* Added dataflow models for the `gettext` function variants.
|
||||
|
||||
## 0.9.4
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 8.1
|
||||
* @precision high
|
||||
* @precision medium
|
||||
* @id cpp/integer-multiplication-cast-to-long
|
||||
* @tags reliability
|
||||
* security
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @security-severity 7.5
|
||||
* @precision high
|
||||
* @precision medium
|
||||
* @id cpp/wrong-type-format-argument
|
||||
* @tags reliability
|
||||
* correctness
|
||||
|
||||
@@ -14,9 +14,6 @@ function may behave unpredictably.</p>
|
||||
<p>This may indicate a misspelled function name, or that the required header containing
|
||||
the function declaration has not been included.</p>
|
||||
|
||||
<p>Note: This query is not compatible with <code>build mode: none</code> databases, and produces
|
||||
no results on those databases.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>Provide an explicit declaration of the function before invoking it.</p>
|
||||
@@ -29,4 +26,4 @@ no results on those databases.</p>
|
||||
<references>
|
||||
<li>SEI CERT C Coding Standard: <a href="https://wiki.sei.cmu.edu/confluence/display/c/DCL31-C.+Declare+identifiers+before+using+them">DCL31-C. Declare identifiers before using them</a></li>
|
||||
</references>
|
||||
</qhelp>
|
||||
</qhelp>
|
||||
@@ -5,7 +5,7 @@
|
||||
* may lead to unpredictable behavior.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @precision high
|
||||
* @precision medium
|
||||
* @id cpp/implicit-function-declaration
|
||||
* @tags correctness
|
||||
* maintainability
|
||||
@@ -17,11 +17,6 @@ import TooFewArguments
|
||||
import TooManyArguments
|
||||
import semmle.code.cpp.commons.Exclusions
|
||||
|
||||
/*
|
||||
* This query is not compatible with build mode: none databases, and produces
|
||||
* no results on those databases.
|
||||
*/
|
||||
|
||||
predicate locInfo(Locatable e, File file, int line, int col) {
|
||||
e.getFile() = file and
|
||||
e.getLocation().getStartLine() = line and
|
||||
@@ -44,7 +39,6 @@ predicate isCompiledAsC(File f) {
|
||||
from FunctionDeclarationEntry fdeIm, FunctionCall fc
|
||||
where
|
||||
isCompiledAsC(fdeIm.getFile()) and
|
||||
not any(Compilation c).buildModeNone() and
|
||||
not isFromMacroDefinition(fc) and
|
||||
fdeIm.isImplicit() and
|
||||
sameLocation(fdeIm, fc) and
|
||||
|
||||
@@ -79,7 +79,9 @@ private predicate hasZeroParamDecl(Function f) {
|
||||
|
||||
// True if this file (or header) was compiled as a C file
|
||||
private predicate isCompiledAsC(File f) {
|
||||
exists(File src | src.compiledAsC() | src.getAnIncludedFile*() = f)
|
||||
f.compiledAsC()
|
||||
or
|
||||
exists(File src | isCompiledAsC(src) | src.getAnIncludedFile() = f)
|
||||
}
|
||||
|
||||
predicate mistypedFunctionArguments(FunctionCall fc, Function f, Parameter p) {
|
||||
|
||||
@@ -28,7 +28,9 @@ private predicate hasZeroParamDecl(Function f) {
|
||||
|
||||
/* Holds if this file (or header) was compiled as a C file. */
|
||||
private predicate isCompiledAsC(File f) {
|
||||
exists(File src | src.compiledAsC() | src.getAnIncludedFile*() = f)
|
||||
f.compiledAsC()
|
||||
or
|
||||
exists(File src | isCompiledAsC(src) | src.getAnIncludedFile() = f)
|
||||
}
|
||||
|
||||
/** Holds if `fc` is a call to `f` with too few arguments. */
|
||||
|
||||
@@ -19,7 +19,9 @@ private predicate hasZeroParamDecl(Function f) {
|
||||
|
||||
// True if this file (or header) was compiled as a C file
|
||||
private predicate isCompiledAsC(File f) {
|
||||
exists(File src | src.compiledAsC() | src.getAnIncludedFile*() = f)
|
||||
f.compiledAsC()
|
||||
or
|
||||
exists(File src | isCompiledAsC(src) | src.getAnIncludedFile() = f)
|
||||
}
|
||||
|
||||
predicate tooManyArguments(FunctionCall fc, Function f) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 7.8
|
||||
* @precision high
|
||||
* @precision medium
|
||||
* @tags reliability
|
||||
* security
|
||||
* external/cwe/cwe-190
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 8.8
|
||||
* @precision high
|
||||
* @precision medium
|
||||
* @id cpp/suspicious-add-sizeof
|
||||
* @tags security
|
||||
* external/cwe/cwe-468
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Fixed an issue with the "Multiplication result converted to larger type" (`cpp/integer-multiplication-cast-to-long`) query causing false positive results in `build-mode: none` databases.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: queryMetadata
|
||||
---
|
||||
* The `@security-severity` metadata of `cpp/cgi-xss` has been increased from 6.1 (medium) to 7.8 (high).
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Fixed an issue with the "Wrong type of arguments to formatting function" (`cpp/wrong-type-format-argument`) query causing false positive results in `build-mode: none` databases.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Fixed an issue with the "Suspicious add with sizeof" (`cpp/suspicious-add-sizeof`) query causing false positive results in `build-mode: none` databases.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Fixed an issue with the "Uncontrolled format string" (`cpp/tainted-format-string`) query involving certain kinds of formatting function implementations.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The "Extraction warnings" (`cpp/diagnostics/extraction-warnings`) diagnostics query no longer yields `ExtractionRecoverableWarning`s for `build-mode: none` databases. The results were found to significantly increase the sizes of the produced SARIF files, making them unprocessable in some cases.
|
||||
@@ -1,13 +0,0 @@
|
||||
## 1.6.0
|
||||
|
||||
### Query Metadata Changes
|
||||
|
||||
* The `@security-severity` metadata of `cpp/cgi-xss` has been increased from 6.1 (medium) to 7.8 (high).
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The "Extraction warnings" (`cpp/diagnostics/extraction-warnings`) diagnostics query no longer yields `ExtractionRecoverableWarning`s for `build-mode: none` databases. The results were found to significantly increase the sizes of the produced SARIF files, making them unprocessable in some cases.
|
||||
* Fixed an issue with the "Suspicious add with sizeof" (`cpp/suspicious-add-sizeof`) query causing false positive results in `build-mode: none` databases.
|
||||
* Fixed an issue with the "Uncontrolled format string" (`cpp/tainted-format-string`) query involving certain kinds of formatting function implementations.
|
||||
* Fixed an issue with the "Wrong type of arguments to formatting function" (`cpp/wrong-type-format-argument`) query causing false positive results in `build-mode: none` databases.
|
||||
* Fixed an issue with the "Multiplication result converted to larger type" (`cpp/integer-multiplication-cast-to-long`) query causing false positive results in `build-mode: none` databases.
|
||||
@@ -1,10 +0,0 @@
|
||||
## 1.6.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Added `AllocationFunction` models for `aligned_alloc`, `std::aligned_alloc`, and `bsl::aligned_alloc`.
|
||||
* The "Comparison of narrow type with wide type in loop condition" (`cpp/comparison-with-wider-type`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
|
||||
* The "Multiplication result converted to larger type" (`cpp/integer-multiplication-cast-to-long`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
|
||||
* The "Suspicious add with sizeof" (`cpp/suspicious-add-sizeof`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
|
||||
* The "Wrong type of arguments to formatting function" (`cpp/wrong-type-format-argument`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
|
||||
* The "Implicit function declaration" (`cpp/implicit-function-declaration`) query has been upgraded to `high` precision. However, for `build mode: none` databases, it no longer produces any results. The results in this mode were found to be very noisy and fundamentally imprecise.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 1.6.1
|
||||
lastReleaseVersion: 1.5.15
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/cpp-queries
|
||||
version: 1.6.2-dev
|
||||
version: 1.5.16-dev
|
||||
groups:
|
||||
- cpp
|
||||
- queries
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
## 1.7.65
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.7.64
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.7.63
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
## 1.7.64
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,3 +0,0 @@
|
||||
## 1.7.65
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 1.7.65
|
||||
lastReleaseVersion: 1.7.63
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/csharp-solorigate-all
|
||||
version: 1.7.66-dev
|
||||
version: 1.7.64-dev
|
||||
groups:
|
||||
- csharp
|
||||
- solorigate
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
## 1.7.65
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.7.64
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.7.63
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -13,13 +13,11 @@ import csharp
|
||||
import Solorigate
|
||||
import experimental.code.csharp.Cryptography.NonCryptographicHashes
|
||||
|
||||
ControlFlowNode loopExitNode(LoopStmt loop) { result.isAfter(loop) }
|
||||
|
||||
from Variable v, Literal l, LoopStmt loop, Expr additional_xor
|
||||
where
|
||||
maybeUsedInFnvFunction(v, _, _, loop) and
|
||||
exists(BitwiseXorOperation xor2 | xor2.getAnOperand() = l and additional_xor = xor2 |
|
||||
loopExitNode(loop).getASuccessor*() = xor2.getControlFlowNode() and
|
||||
loop.getAControlFlowExitNode().getASuccessor*() = xor2.getAControlFlowNode() and
|
||||
xor2.getAnOperand() = v.getAnAccess()
|
||||
)
|
||||
select l, "This literal is used in an $@ after an FNV-like hash calculation with variable $@.",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
## 1.7.64
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,3 +0,0 @@
|
||||
## 1.7.65
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 1.7.65
|
||||
lastReleaseVersion: 1.7.63
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/csharp-solorigate-queries
|
||||
version: 1.7.66-dev
|
||||
version: 1.7.64-dev
|
||||
groups:
|
||||
- csharp
|
||||
- solorigate
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
import csharp
|
||||
import ControlFlow::Consistency
|
||||
import semmle.code.csharp.controlflow.internal.Completion
|
||||
import ControlFlow
|
||||
import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl::Consistency
|
||||
import semmle.code.csharp.controlflow.internal.Splitting
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import csharp
|
||||
private import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl as ControlFlowGraphImpl
|
||||
private import semmle.code.csharp.dataflow.internal.DataFlowImplSpecific
|
||||
private import semmle.code.csharp.dataflow.internal.TaintTrackingImplSpecific
|
||||
private import codeql.dataflow.internal.DataFlowImplConsistency
|
||||
@@ -6,6 +7,20 @@ private import codeql.dataflow.internal.DataFlowImplConsistency
|
||||
private module Input implements InputSig<Location, CsharpDataFlow> {
|
||||
private import CsharpDataFlow
|
||||
|
||||
private predicate isStaticAssignable(Assignable a) { a.(Modifiable).isStatic() }
|
||||
|
||||
predicate uniqueEnclosingCallableExclude(Node node) {
|
||||
// TODO: Remove once static initializers are folded into the
|
||||
// static constructors
|
||||
isStaticAssignable(ControlFlowGraphImpl::getNodeCfgScope(node.getControlFlowNode()))
|
||||
}
|
||||
|
||||
predicate uniqueCallEnclosingCallableExclude(DataFlowCall call) {
|
||||
// TODO: Remove once static initializers are folded into the
|
||||
// static constructors
|
||||
isStaticAssignable(ControlFlowGraphImpl::getNodeCfgScope(call.getControlFlowNode()))
|
||||
}
|
||||
|
||||
predicate uniqueNodeLocationExclude(Node n) {
|
||||
// Methods with multiple implementations
|
||||
n instanceof ParameterNode
|
||||
@@ -55,14 +70,17 @@ private module Input implements InputSig<Location, CsharpDataFlow> {
|
||||
init.getInitializer().getNumberOfChildren() > 1
|
||||
)
|
||||
or
|
||||
call.(NonDelegateDataFlowCall).getDispatchCall().isReflection()
|
||||
or
|
||||
// Exclude calls that are both getter and setter calls, as they share the same argument nodes.
|
||||
exists(AccessorCall ac |
|
||||
call.(NonDelegateDataFlowCall).getDispatchCall().getCall() = ac and
|
||||
ac instanceof AssignableRead and
|
||||
ac instanceof AssignableWrite
|
||||
exists(ControlFlow::Nodes::ElementNode cfn, ControlFlow::Nodes::Split split |
|
||||
exists(arg.asExprAtNode(cfn))
|
||||
|
|
||||
split = cfn.getASplit() and
|
||||
not split = call.getControlFlowNode().getASplit()
|
||||
or
|
||||
split = call.getControlFlowNode().getASplit() and
|
||||
not split = cfn.getASplit()
|
||||
)
|
||||
or
|
||||
call.(NonDelegateDataFlowCall).getDispatchCall().isReflection()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import csharp
|
||||
import semmle.code.csharp.dataflow.internal.DataFlowPrivate::VariableCapture::Flow::ConsistencyChecks
|
||||
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate::VariableCapture::Flow::ConsistencyChecks as ConsistencyChecks
|
||||
private import semmle.code.csharp.controlflow.BasicBlocks
|
||||
private import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl
|
||||
|
||||
query predicate uniqueEnclosingCallable(BasicBlock bb, string msg) {
|
||||
ConsistencyChecks::uniqueEnclosingCallable(bb, msg) and
|
||||
getNodeCfgScope(bb.getFirstNode()) instanceof Callable
|
||||
}
|
||||
|
||||
query predicate consistencyOverview(string msg, int n) { none() }
|
||||
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
import csharp
|
||||
|
||||
from IntegerLiteral literal
|
||||
where literal.getIntValue() = 0
|
||||
where literal.getValue().toInt() = 0
|
||||
select literal
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
## 5.5.0
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* The predicates `get[L|R]Value` in the class `Assignment` have been deprecated. Use `get[Left|Right]Operand` instead.
|
||||
|
||||
## 5.4.12
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The extractor no longer synthesizes expanded forms of compound assignments. This may have a small impact on the results of queries that explicitly or implicitly rely on the expanded form of compound assignments.
|
||||
* The `cs/log-forging` query no longer treats arguments to extension methods with
|
||||
source code on `ILogger` types as sinks. Instead, taint is tracked interprocedurally
|
||||
through extension method bodies, reducing false positives when extension methods
|
||||
sanitize input internally.
|
||||
|
||||
## 5.4.11
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -121,17 +121,15 @@ predicate missedOfTypeOpportunity(ForeachStmtEnumerable fes, LocalVariableDeclSt
|
||||
/**
|
||||
* Holds if `foreach` statement `fes` can be converted to a `.Select()` call.
|
||||
* That is, the loop variable is accessed only in the first statement of the
|
||||
* block, the access is not a cast, the first statement is a
|
||||
* local variable declaration statement `s`, and the initializer does not
|
||||
* contain an `await` expression (since `Select` does not support async lambdas).
|
||||
* block, the access is not a cast, and the first statement is a
|
||||
* local variable declaration statement `s`.
|
||||
*/
|
||||
predicate missedSelectOpportunity(ForeachStmtGenericEnumerable fes, LocalVariableDeclStmt s) {
|
||||
s = firstStmt(fes) and
|
||||
forex(VariableAccess va | va = fes.getVariable().getAnAccess() |
|
||||
va = s.getAVariableDeclExpr().getAChildExpr*()
|
||||
) and
|
||||
not s.getAVariableDeclExpr().getInitializer() instanceof Cast and
|
||||
not s.getAVariableDeclExpr().getInitializer().getAChildExpr*() instanceof AwaitExpr
|
||||
not s.getAVariableDeclExpr().getInitializer() instanceof Cast
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
## 5.4.12
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The extractor no longer synthesizes expanded forms of compound assignments. This may have a small impact on the results of queries that explicitly or implicitly rely on the expanded form of compound assignments.
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `cs/log-forging` query no longer treats arguments to extension methods with
|
||||
source code on `ILogger` types as sinks. Instead, taint is tracked interprocedurally
|
||||
through extension method bodies, reducing false positives when extension methods
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* Data flow barriers and barrier guards can now be added using data extensions. For more information see [Customizing library models for C#](https://codeql.github.com/docs/codeql-language-guides/customizing-library-models-for-csharp/).
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The extractor no longer synthesizes expanded forms of compound assignments. This may have a small impact on the results of queries that explicitly or implicitly rely on the expanded form of compound assignments.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Expanded ASP and ASP.NET remote source modeling to cover additional sources, including fields of tainted parameters as well as properties and fields that become tainted transitively.
|
||||
@@ -1,5 +1,4 @@
|
||||
## 5.5.0
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
* The predicates `get[L|R]Value` in the class `Assignment` have been deprecated. Use `get[Left|Right]Operand` instead.
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
category: breaking
|
||||
---
|
||||
* The C# control flow graph (CFG) implementation has been completely
|
||||
rewritten. The CFG now includes additional nodes to more accurately represent
|
||||
certain constructs. This also means that any existing code that implicitly
|
||||
relies on very specific details about the CFG may need to be updated.
|
||||
The CFG no longer uses splitting, which means that AST nodes now have a unique
|
||||
CFG node representation.
|
||||
Additionally, the following breaking changes have been made:
|
||||
- `ControlFlow::Node` has been renamed to `ControlFlowNode`.
|
||||
- `ControlFlow::Nodes` has been renamed to `ControlFlowNodes`.
|
||||
- `BasicBlock.getCallable` has been renamed to `BasicBlock.getEnclosingCallable`.
|
||||
- `BasicBlocks.qll` has been deleted.
|
||||
- `ControlFlowNode.getAstNode` has changed its meaning. The AST-to-CFG
|
||||
mapping remains one-to-many, but now for a different reason. It used to be
|
||||
because of splitting, but now it's because of additional "helper" CFG
|
||||
nodes. To get the (now canonical) CFG node for a given AST node, use
|
||||
`ControlFlowNode.asExpr()` or `ControlFlowNode.asStmt()` or
|
||||
`ControlFlowElement.getControlFlowNode()` instead.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 5.5.0
|
||||
lastReleaseVersion: 5.4.11
|
||||
|
||||
@@ -30,7 +30,7 @@ predicate maybeUsedInFnvFunction(Variable v, Operation xor, Operation mul, LoopS
|
||||
e2.getAChild*() = v.getAnAccess() and
|
||||
e1 = xor.getAnOperand() and
|
||||
e2 = mul.getAnOperand() and
|
||||
xor.getControlFlowNode().getASuccessor*() = mul.getControlFlowNode() and
|
||||
xor.getAControlFlowNode().getASuccessor*() = mul.getAControlFlowNode() and
|
||||
(xor instanceof AssignXorExpr or xor instanceof BitwiseXorExpr) and
|
||||
(mul instanceof AssignMulExpr or mul instanceof MulExpr)
|
||||
) and
|
||||
@@ -55,11 +55,11 @@ private predicate maybeUsedInElfHashFunction(Variable v, Operation xor, Operatio
|
||||
v = addAssign.getTargetVariable() and
|
||||
addAssign.getAChild*() = add and
|
||||
(xor instanceof BitwiseXorExpr or xor instanceof AssignXorExpr) and
|
||||
addAssign.getControlFlowNode().getASuccessor*() = xor.getControlFlowNode() and
|
||||
addAssign.getAControlFlowNode().getASuccessor*() = xor.getAControlFlowNode() and
|
||||
xorAssign.getAChild*() = xor and
|
||||
v = xorAssign.getTargetVariable() and
|
||||
(notOp instanceof UnaryBitwiseOperation or notOp instanceof AssignBitwiseOperation) and
|
||||
xor.getControlFlowNode().getASuccessor*() = notOp.getControlFlowNode() and
|
||||
xor.getAControlFlowNode().getASuccessor*() = notOp.getAControlFlowNode() and
|
||||
notAssign.getAChild*() = notOp and
|
||||
v = notAssign.getTargetVariable() and
|
||||
loop.getAChild*() = add.getEnclosingStmt() and
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @tags ide-contextual-queries/print-cfg
|
||||
*/
|
||||
|
||||
import csharp
|
||||
private import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl
|
||||
|
||||
external string selectedSourceFile();
|
||||
|
||||
@@ -21,7 +21,7 @@ external int selectedSourceColumn();
|
||||
|
||||
private predicate selectedSourceColumnAlias = selectedSourceColumn/0;
|
||||
|
||||
module ViewCfgQueryInput implements ControlFlow::ViewCfgQueryInputSig<File> {
|
||||
module ViewCfgQueryInput implements ViewCfgQueryInputSig<File> {
|
||||
predicate selectedSourceFile = selectedSourceFileAlias/0;
|
||||
|
||||
predicate selectedSourceLine = selectedSourceLineAlias/0;
|
||||
@@ -29,7 +29,7 @@ module ViewCfgQueryInput implements ControlFlow::ViewCfgQueryInputSig<File> {
|
||||
predicate selectedSourceColumn = selectedSourceColumnAlias/0;
|
||||
|
||||
predicate cfgScopeSpan(
|
||||
Callable scope, File file, int startLine, int startColumn, int endLine, int endColumn
|
||||
CfgScope scope, File file, int startLine, int startColumn, int endLine, int endColumn
|
||||
) {
|
||||
file = scope.getFile() and
|
||||
scope.getLocation().getStartLine() = startLine and
|
||||
@@ -40,20 +40,11 @@ module ViewCfgQueryInput implements ControlFlow::ViewCfgQueryInputSig<File> {
|
||||
|
|
||||
loc = scope.(Callable).getBody().getLocation()
|
||||
or
|
||||
loc = any(AssignExpr init | scope.(ObjectInitMethod).initializes(init)).getLocation()
|
||||
loc = scope.(Field).getInitializer().getLocation()
|
||||
or
|
||||
exists(AssignableMember a, Constructor ctor |
|
||||
scope = ctor and
|
||||
ctor.isStatic() and
|
||||
a.isStatic() and
|
||||
a.getDeclaringType() = ctor.getDeclaringType()
|
||||
|
|
||||
loc = a.(Field).getInitializer().getLocation()
|
||||
or
|
||||
loc = a.(Property).getInitializer().getLocation()
|
||||
)
|
||||
loc = scope.(Property).getInitializer().getLocation()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import ControlFlow::ViewCfgQuery<File, ViewCfgQueryInput>
|
||||
import ViewCfgQuery<File, ViewCfgQueryInput>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/csharp-all
|
||||
version: 5.5.1-dev
|
||||
version: 5.4.12-dev
|
||||
groups: csharp
|
||||
dbscheme: semmlecode.csharp.dbscheme
|
||||
extractor: csharp
|
||||
|
||||
@@ -85,8 +85,8 @@ class AssignableRead extends AssignableAccess {
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private ControlFlowNode getAnAdjacentReadSameVar() {
|
||||
SsaImpl::adjacentReadPairSameVar(_, this.getControlFlowNode(), result)
|
||||
private ControlFlow::Node getAnAdjacentReadSameVar() {
|
||||
SsaImpl::adjacentReadPairSameVar(_, this.getAControlFlowNode(), result)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +114,11 @@ class AssignableRead extends AssignableAccess {
|
||||
* - The read of `this.Field` on line 11 is next to the read on line 10.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
AssignableRead getANextRead() { result.getControlFlowNode() = this.getAnAdjacentReadSameVar() }
|
||||
AssignableRead getANextRead() {
|
||||
forex(ControlFlow::Node cfn | cfn = result.getAControlFlowNode() |
|
||||
cfn = this.getAnAdjacentReadSameVar()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -406,7 +410,7 @@ private import AssignableInternal
|
||||
*/
|
||||
class AssignableDefinition extends TAssignableDefinition {
|
||||
/**
|
||||
* DEPRECATED: Use `this.getExpr().getControlFlowNode()` instead.
|
||||
* DEPRECATED: Use `this.getExpr().getAControlFlowNode()` instead.
|
||||
*
|
||||
* Gets a control flow node that updates the targeted assignable when
|
||||
* reached.
|
||||
@@ -415,7 +419,9 @@ class AssignableDefinition extends TAssignableDefinition {
|
||||
* the definitions of `x` and `y` in `M(out x, out y)` and `(x, y) = (0, 1)`
|
||||
* relate to the same call to `M` and assignment node, respectively.
|
||||
*/
|
||||
deprecated ControlFlowNode getAControlFlowNode() { result = this.getExpr().getControlFlowNode() }
|
||||
deprecated ControlFlow::Node getAControlFlowNode() {
|
||||
result = this.getExpr().getAControlFlowNode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the underlying expression that updates the targeted assignable when
|
||||
@@ -488,7 +494,7 @@ class AssignableDefinition extends TAssignableDefinition {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
AssignableRead getAFirstRead() {
|
||||
exists(ControlFlowNode cfn | cfn = result.getControlFlowNode() |
|
||||
forex(ControlFlow::Node cfn | cfn = result.getAControlFlowNode() |
|
||||
exists(Ssa::ExplicitDefinition def | result = def.getAFirstReadAtNode(cfn) |
|
||||
this = def.getADefinition()
|
||||
)
|
||||
@@ -566,9 +572,11 @@ module AssignableDefinitions {
|
||||
}
|
||||
|
||||
/** Holds if a node in basic block `bb` assigns to `ref` parameter `p` via definition `def`. */
|
||||
private predicate basicBlockRefParamDef(BasicBlock bb, Parameter p, AssignableDefinition def) {
|
||||
private predicate basicBlockRefParamDef(
|
||||
ControlFlow::BasicBlock bb, Parameter p, AssignableDefinition def
|
||||
) {
|
||||
def = any(RefArg arg).getAnAnalyzableRefDef(p) and
|
||||
bb.getANode() = def.getExpr().getControlFlowNode()
|
||||
bb.getANode() = def.getExpr().getAControlFlowNode()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -577,7 +585,7 @@ module AssignableDefinitions {
|
||||
* any assignments to `p`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate parameterReachesWithoutDef(Parameter p, BasicBlock bb) {
|
||||
private predicate parameterReachesWithoutDef(Parameter p, ControlFlow::BasicBlock bb) {
|
||||
forall(AssignableDefinition def | basicBlockRefParamDef(bb, p, def) |
|
||||
isUncertainRefCall(def.getTargetAccess())
|
||||
) and
|
||||
@@ -585,7 +593,9 @@ module AssignableDefinitions {
|
||||
any(RefArg arg).isAnalyzable(p) and
|
||||
p.getCallable().getEntryPoint() = bb.getFirstNode()
|
||||
or
|
||||
exists(BasicBlock mid | parameterReachesWithoutDef(p, mid) | bb = mid.getASuccessor())
|
||||
exists(ControlFlow::BasicBlock mid | parameterReachesWithoutDef(p, mid) |
|
||||
bb = mid.getASuccessor()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -597,7 +607,7 @@ module AssignableDefinitions {
|
||||
cached
|
||||
predicate isUncertainRefCall(RefArg arg) {
|
||||
arg.isPotentialAssignment() and
|
||||
exists(BasicBlock bb, Parameter p | arg.isAnalyzable(p) |
|
||||
exists(ControlFlow::BasicBlock bb, Parameter p | arg.isAnalyzable(p) |
|
||||
parameterReachesWithoutDef(p, bb) and
|
||||
bb.getLastNode() = p.getCallable().getExitPoint()
|
||||
)
|
||||
@@ -678,7 +688,7 @@ module AssignableDefinitions {
|
||||
/** Gets the underlying parameter. */
|
||||
Parameter getParameter() { result = p }
|
||||
|
||||
deprecated override ControlFlowNode getAControlFlowNode() {
|
||||
deprecated override ControlFlow::Node getAControlFlowNode() {
|
||||
result = p.getCallable().getEntryPoint()
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,23 @@ private import csharp
|
||||
* in the same stage across different files.
|
||||
*/
|
||||
module Stages {
|
||||
cached
|
||||
module ControlFlowStage {
|
||||
private import semmle.code.csharp.controlflow.internal.Splitting
|
||||
|
||||
cached
|
||||
predicate forceCachingInSameStage() { any() }
|
||||
|
||||
cached
|
||||
private predicate forceCachingInSameStageRev() {
|
||||
exists(Split s)
|
||||
or
|
||||
exists(ControlFlow::Node n)
|
||||
or
|
||||
forceCachingInSameStageRev()
|
||||
}
|
||||
}
|
||||
|
||||
cached
|
||||
module GuardsStage {
|
||||
private import semmle.code.csharp.controlflow.Guards
|
||||
|
||||
@@ -22,7 +22,7 @@ private import TypeRef
|
||||
* an anonymous function (`AnonymousFunctionExpr`), or a local function
|
||||
* (`LocalFunction`).
|
||||
*/
|
||||
class Callable extends Parameterizable, ControlFlowElementOrCallable, @callable {
|
||||
class Callable extends Parameterizable, ExprOrStmtParent, @callable {
|
||||
/** Gets the return type of this callable. */
|
||||
Type getReturnType() { none() }
|
||||
|
||||
@@ -157,10 +157,10 @@ class Callable extends Parameterizable, ControlFlowElementOrCallable, @callable
|
||||
final predicate hasExpressionBody() { exists(this.getExpressionBody()) }
|
||||
|
||||
/** Gets the entry point in the control graph for this callable. */
|
||||
ControlFlow::EntryNode getEntryPoint() { result.getEnclosingCallable() = this }
|
||||
ControlFlow::Nodes::EntryNode getEntryPoint() { result.getCallable() = this }
|
||||
|
||||
/** Gets the exit point in the control graph for this callable. */
|
||||
ControlFlow::ExitNode getExitPoint() { result.getEnclosingCallable() = this }
|
||||
ControlFlow::Nodes::ExitNode getExitPoint() { result.getCallable() = this }
|
||||
|
||||
/**
|
||||
* Gets the enclosing callable of this callable, if any.
|
||||
|
||||
@@ -232,9 +232,14 @@ private module Identity {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate convTypeArguments(Type fromTypeArgument, Type toTypeArgument, int i) {
|
||||
fromTypeArgument = getTypeArgumentRanked(_, _, pragma[only_bind_into](i)) and
|
||||
toTypeArgument = getTypeArgumentRanked(_, _, pragma[only_bind_into](i)) and
|
||||
convIdentity(fromTypeArgument, toTypeArgument)
|
||||
exists(int j |
|
||||
fromTypeArgument = getTypeArgumentRanked(_, _, i) and
|
||||
toTypeArgument = getTypeArgumentRanked(_, _, j) and
|
||||
i <= j and
|
||||
j <= i
|
||||
|
|
||||
convIdentity(fromTypeArgument, toTypeArgument)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
@@ -713,7 +718,7 @@ private class SignedIntegralConstantExpr extends Expr {
|
||||
}
|
||||
|
||||
private predicate convConstantIntExpr(SignedIntegralConstantExpr e, SimpleType toType) {
|
||||
exists(int n | n = e.getIntValue() |
|
||||
exists(int n | n = e.getValue().toInt() |
|
||||
toType = any(SByteType t | n in [t.minValue() .. t.maxValue()])
|
||||
or
|
||||
toType = any(ByteType t | n in [t.minValue() .. t.maxValue()])
|
||||
@@ -730,7 +735,7 @@ private predicate convConstantIntExpr(SignedIntegralConstantExpr e, SimpleType t
|
||||
|
||||
private predicate convConstantLongExpr(SignedIntegralConstantExpr e) {
|
||||
e.getType() instanceof LongType and
|
||||
e.getIntValue() >= 0
|
||||
e.getValue().toInt() >= 0
|
||||
}
|
||||
|
||||
/** 6.1.10: Implicit reference conversions involving type parameters. */
|
||||
@@ -924,16 +929,19 @@ private module Variance {
|
||||
private predicate convTypeArguments(
|
||||
TypeArgument fromTypeArgument, TypeArgument toTypeArgument, int i, TVariance v
|
||||
) {
|
||||
fromTypeArgument = getTypeArgumentRanked(_, _, pragma[only_bind_into](i), _) and
|
||||
toTypeArgument = getTypeArgumentRanked(_, _, pragma[only_bind_into](i), _) and
|
||||
(
|
||||
exists(int j |
|
||||
fromTypeArgument = getTypeArgumentRanked(_, _, i, _) and
|
||||
toTypeArgument = getTypeArgumentRanked(_, _, j, _) and
|
||||
i <= j and
|
||||
j <= i
|
||||
|
|
||||
convIdentity(fromTypeArgument, toTypeArgument) and
|
||||
v = TNone()
|
||||
or
|
||||
convRefTypeTypeArgumentOut(fromTypeArgument, toTypeArgument, i) and
|
||||
convRefTypeTypeArgumentOut(fromTypeArgument, toTypeArgument, j) and
|
||||
v = TOut()
|
||||
or
|
||||
convRefTypeTypeArgumentIn(toTypeArgument, fromTypeArgument, i) and
|
||||
convRefTypeTypeArgumentIn(toTypeArgument, fromTypeArgument, j) and
|
||||
v = TIn()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -129,6 +129,13 @@ private module Cached {
|
||||
result = parent.getAChildStmt()
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
private ControlFlowElement enclosingStart(ControlFlowElement cfe) {
|
||||
result = cfe
|
||||
or
|
||||
getAChild(result).(AnonymousFunctionExpr) = cfe
|
||||
}
|
||||
|
||||
private predicate parent(ControlFlowElement child, ExprOrStmtParent parent) {
|
||||
child = getAChild(parent) and
|
||||
not child = getBody(_)
|
||||
@@ -138,7 +145,7 @@ private module Cached {
|
||||
cached
|
||||
predicate enclosingBody(ControlFlowElement cfe, ControlFlowElement body) {
|
||||
body = getBody(_) and
|
||||
parent*(cfe, body)
|
||||
parent*(enclosingStart(cfe), body)
|
||||
}
|
||||
|
||||
/** Holds if the enclosing callable of `cfe` is `c`. */
|
||||
@@ -146,7 +153,7 @@ private module Cached {
|
||||
predicate enclosingCallable(ControlFlowElement cfe, Callable c) {
|
||||
enclosingBody(cfe, getBody(c))
|
||||
or
|
||||
parent*(cfe, c.(Constructor).getInitializer())
|
||||
parent*(enclosingStart(cfe), c.(Constructor).getInitializer())
|
||||
or
|
||||
parent*(cfe, c.(Constructor).getObjectInitializerCall())
|
||||
or
|
||||
|
||||
@@ -54,44 +54,21 @@ private string genericCollectionTypeName() {
|
||||
]
|
||||
}
|
||||
|
||||
/** A collection type */
|
||||
abstract private class CollectionTypeImpl extends RefType {
|
||||
/**
|
||||
* Gets the element type of this collection, for example `int` in `List<int>`.
|
||||
*/
|
||||
abstract Type getElementType();
|
||||
}
|
||||
|
||||
private class GenericCollectionType extends CollectionTypeImpl {
|
||||
private ConstructedType base;
|
||||
|
||||
GenericCollectionType() {
|
||||
base = this.getABaseType*() and
|
||||
base.getUnboundGeneric()
|
||||
.hasFullyQualifiedName(genericCollectionNamespaceName(), genericCollectionTypeName())
|
||||
}
|
||||
|
||||
override Type getElementType() {
|
||||
result = base.getTypeArgument(0) and base.getNumberOfTypeArguments() = 1
|
||||
}
|
||||
}
|
||||
|
||||
private class NonGenericCollectionType extends CollectionTypeImpl {
|
||||
NonGenericCollectionType() {
|
||||
/** A collection type. */
|
||||
class CollectionType extends RefType {
|
||||
CollectionType() {
|
||||
exists(RefType base | base = this.getABaseType*() |
|
||||
base.hasFullyQualifiedName(collectionNamespaceName(), collectionTypeName())
|
||||
or
|
||||
base.(ConstructedType)
|
||||
.getUnboundGeneric()
|
||||
.hasFullyQualifiedName(genericCollectionNamespaceName(), genericCollectionTypeName())
|
||||
)
|
||||
or
|
||||
this instanceof ArrayType
|
||||
}
|
||||
|
||||
override Type getElementType() { none() }
|
||||
}
|
||||
|
||||
private class ArrayCollectionType extends CollectionTypeImpl instanceof ArrayType {
|
||||
override Type getElementType() { result = ArrayType.super.getElementType() }
|
||||
}
|
||||
|
||||
final class CollectionType = CollectionTypeImpl;
|
||||
|
||||
/**
|
||||
* A collection type that can be used as a `params` parameter type.
|
||||
*/
|
||||
|
||||
@@ -161,7 +161,7 @@ private newtype TComparisonTest =
|
||||
compare.getComparisonKind().isCompare() and
|
||||
outerKind = outer.getComparisonKind() and
|
||||
outer.getAnArgument() = compare.getExpr() and
|
||||
i = outer.getAnArgument().getIntValue()
|
||||
i = outer.getAnArgument().getValue().toInt()
|
||||
|
|
||||
outerKind.isEquality() and
|
||||
(
|
||||
|
||||
@@ -32,13 +32,13 @@ private module ConstantComparisonOperation {
|
||||
|
||||
private int maxValue(Expr expr) {
|
||||
if convertedType(expr) instanceof IntegralType and exists(expr.getValue())
|
||||
then result = expr.getIntValue()
|
||||
then result = expr.getValue().toInt()
|
||||
else result = convertedType(expr).maxValue()
|
||||
}
|
||||
|
||||
private int minValue(Expr expr) {
|
||||
if convertedType(expr) instanceof IntegralType and exists(expr.getValue())
|
||||
then result = expr.getIntValue()
|
||||
then result = expr.getValue().toInt()
|
||||
else result = convertedType(expr).minValue()
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class ImplicitToStringExpr extends Expr {
|
||||
m = p.getCallable()
|
||||
|
|
||||
m = any(SystemTextStringBuilderClass c).getAMethod() and
|
||||
m.getName() = "Append" and
|
||||
m.getName().regexpMatch("Append(Line)?") and
|
||||
not p.getType() instanceof ArrayType
|
||||
or
|
||||
p instanceof StringFormatItemParameter and
|
||||
|
||||
356
csharp/ql/lib/semmle/code/csharp/controlflow/BasicBlocks.qll
Normal file
356
csharp/ql/lib/semmle/code/csharp/controlflow/BasicBlocks.qll
Normal file
@@ -0,0 +1,356 @@
|
||||
/**
|
||||
* Provides classes representing basic blocks.
|
||||
*/
|
||||
|
||||
import csharp
|
||||
private import ControlFlow
|
||||
private import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl as CfgImpl
|
||||
private import CfgImpl::BasicBlocks as BasicBlocksImpl
|
||||
private import codeql.controlflow.BasicBlock as BB
|
||||
|
||||
/**
|
||||
* A basic block, that is, a maximal straight-line sequence of control flow nodes
|
||||
* without branches or joins.
|
||||
*/
|
||||
final class BasicBlock extends BasicBlocksImpl::BasicBlock {
|
||||
/** Gets an immediate successor of this basic block of a given type, if any. */
|
||||
BasicBlock getASuccessor(ControlFlow::SuccessorType t) { result = super.getASuccessor(t) }
|
||||
|
||||
/** DEPRECATED: Use `getASuccessor` instead. */
|
||||
deprecated BasicBlock getASuccessorByType(ControlFlow::SuccessorType t) {
|
||||
result = this.getASuccessor(t)
|
||||
}
|
||||
|
||||
/** Gets an immediate predecessor of this basic block of a given type, if any. */
|
||||
BasicBlock getAPredecessorByType(ControlFlow::SuccessorType t) {
|
||||
result = this.getAPredecessor(t)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an immediate `true` successor, if any.
|
||||
*
|
||||
* An immediate `true` successor is a successor that is reached when
|
||||
* the condition that ends this basic block evaluates to `true`.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```csharp
|
||||
* if (x < 0)
|
||||
* x = -x;
|
||||
* ```
|
||||
*
|
||||
* The basic block on line 2 is an immediate `true` successor of the
|
||||
* basic block on line 1.
|
||||
*/
|
||||
BasicBlock getATrueSuccessor() { result.getFirstNode() = this.getLastNode().getATrueSuccessor() }
|
||||
|
||||
/**
|
||||
* Gets an immediate `false` successor, if any.
|
||||
*
|
||||
* An immediate `false` successor is a successor that is reached when
|
||||
* the condition that ends this basic block evaluates to `false`.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```csharp
|
||||
* if (!(x >= 0))
|
||||
* x = -x;
|
||||
* ```
|
||||
*
|
||||
* The basic block on line 2 is an immediate `false` successor of the
|
||||
* basic block on line 1.
|
||||
*/
|
||||
BasicBlock getAFalseSuccessor() {
|
||||
result.getFirstNode() = this.getLastNode().getAFalseSuccessor()
|
||||
}
|
||||
|
||||
BasicBlock getASuccessor() { result = super.getASuccessor() }
|
||||
|
||||
/** Gets the control flow node at a specific (zero-indexed) position in this basic block. */
|
||||
ControlFlow::Node getNode(int pos) { result = super.getNode(pos) }
|
||||
|
||||
/** Gets a control flow node in this basic block. */
|
||||
ControlFlow::Node getANode() { result = super.getANode() }
|
||||
|
||||
/** Gets the first control flow node in this basic block. */
|
||||
ControlFlow::Node getFirstNode() { result = super.getFirstNode() }
|
||||
|
||||
/** Gets the last control flow node in this basic block. */
|
||||
ControlFlow::Node getLastNode() { result = super.getLastNode() }
|
||||
|
||||
/** Gets the callable that this basic block belongs to. */
|
||||
final Callable getCallable() { result = this.getFirstNode().getEnclosingCallable() }
|
||||
|
||||
/**
|
||||
* Holds if this basic block immediately dominates basic block `bb`.
|
||||
*
|
||||
* That is, this basic block is the unique basic block satisfying:
|
||||
* 1. This basic block strictly dominates `bb`
|
||||
* 2. There exists no other basic block that is strictly dominated by this
|
||||
* basic block and which strictly dominates `bb`.
|
||||
*
|
||||
* All basic blocks, except entry basic blocks, have a unique immediate
|
||||
* dominator.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```csharp
|
||||
* int M(string s) {
|
||||
* if (s == null)
|
||||
* throw new ArgumentNullException(nameof(s));
|
||||
* return s.Length;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The basic block starting on line 2 strictly dominates the
|
||||
* basic block on line 4 (all paths from the entry point of `M`
|
||||
* to `return s.Length;` must go through the null check).
|
||||
*/
|
||||
predicate immediatelyDominates(BasicBlock bb) { super.immediatelyDominates(bb) }
|
||||
|
||||
/**
|
||||
* Holds if this basic block strictly dominates basic block `bb`.
|
||||
*
|
||||
* That is, all paths reaching basic block `bb` from some entry point
|
||||
* basic block must go through this basic block (which must be different
|
||||
* from `bb`).
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```csharp
|
||||
* int M(string s) {
|
||||
* if (s == null)
|
||||
* throw new ArgumentNullException(nameof(s));
|
||||
* return s.Length;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The basic block starting on line 2 strictly dominates the
|
||||
* basic block on line 4 (all paths from the entry point of `M`
|
||||
* to `return s.Length;` must go through the null check).
|
||||
*/
|
||||
predicate strictlyDominates(BasicBlock bb) { super.strictlyDominates(bb) }
|
||||
|
||||
/**
|
||||
* Holds if this basic block dominates basic block `bb`.
|
||||
*
|
||||
* That is, all paths reaching basic block `bb` from some entry point
|
||||
* basic block must go through this basic block.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```csharp
|
||||
* int M(string s) {
|
||||
* if (s == null)
|
||||
* throw new ArgumentNullException(nameof(s));
|
||||
* return s.Length;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The basic block starting on line 2 dominates the basic
|
||||
* block on line 4 (all paths from the entry point of `M` to
|
||||
* `return s.Length;` must go through the null check).
|
||||
*
|
||||
* This predicate is *reflexive*, so for example `if (s == null)` dominates
|
||||
* itself.
|
||||
*/
|
||||
predicate dominates(BasicBlock bb) {
|
||||
bb = this or
|
||||
this.strictlyDominates(bb)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `df` is in the dominance frontier of this basic block.
|
||||
* That is, this basic block dominates a predecessor of `df`, but
|
||||
* does not dominate `df` itself.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```csharp
|
||||
* if (x < 0) {
|
||||
* x = -x;
|
||||
* if (x > 10)
|
||||
* x--;
|
||||
* }
|
||||
* Console.Write(x);
|
||||
* ```
|
||||
*
|
||||
* The basic block on line 6 is in the dominance frontier
|
||||
* of the basic block starting on line 2 because that block
|
||||
* dominates the basic block on line 4, which is a predecessor of
|
||||
* `Console.Write(x);`. Also, the basic block starting on line 2
|
||||
* does not dominate the basic block on line 6.
|
||||
*/
|
||||
predicate inDominanceFrontier(BasicBlock df) { super.inDominanceFrontier(df) }
|
||||
|
||||
/**
|
||||
* Gets the basic block that immediately dominates this basic block, if any.
|
||||
*
|
||||
* That is, the result is the unique basic block satisfying:
|
||||
* 1. The result strictly dominates this basic block.
|
||||
* 2. There exists no other basic block that is strictly dominated by the
|
||||
* result and which strictly dominates this basic block.
|
||||
*
|
||||
* All basic blocks, except entry basic blocks, have a unique immediate
|
||||
* dominator.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```csharp
|
||||
* int M(string s) {
|
||||
* if (s == null)
|
||||
* throw new ArgumentNullException(nameof(s));
|
||||
* return s.Length;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The basic block starting on line 2 is an immediate dominator of
|
||||
* the basic block online 4 (all paths from the entry point of `M`
|
||||
* to `return s.Length;` must go through the null check.
|
||||
*/
|
||||
BasicBlock getImmediateDominator() { result = super.getImmediateDominator() }
|
||||
|
||||
/**
|
||||
* Holds if the edge with successor type `s` out of this basic block is a
|
||||
* dominating edge for `dominated`.
|
||||
*
|
||||
* That is, all paths reaching `dominated` from the entry point basic
|
||||
* block must go through the `s` edge out of this basic block.
|
||||
*
|
||||
* Edge dominance is similar to node dominance except it concerns edges
|
||||
* instead of nodes: A basic block is dominated by a _basic block_ `bb` if it
|
||||
* can only be reached through `bb` and dominated by an _edge_ `e` if it can
|
||||
* only be reached through `e`.
|
||||
*
|
||||
* Note that where all basic blocks (except the entry basic block) are
|
||||
* strictly dominated by at least one basic block, a basic block may not be
|
||||
* dominated by any edge. If an edge dominates a basic block `bb`, then
|
||||
* both endpoints of the edge dominates `bb`. The converse is not the case,
|
||||
* as there may be multiple paths between the endpoints with none of them
|
||||
* dominating.
|
||||
*/
|
||||
predicate edgeDominates(BasicBlock dominated, ControlFlow::SuccessorType s) {
|
||||
super.edgeDominates(dominated, s)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this basic block strictly post-dominates basic block `bb`.
|
||||
*
|
||||
* That is, all paths reaching a normal exit point basic block from basic
|
||||
* block `bb` must go through this basic block (which must be different
|
||||
* from `bb`).
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```csharp
|
||||
* int M(string s) {
|
||||
* try {
|
||||
* return s.Length;
|
||||
* }
|
||||
* finally {
|
||||
* Console.WriteLine("M");
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The basic block on line 6 strictly post-dominates the basic block on
|
||||
* line 3 (all paths to the exit point of `M` from `return s.Length;`
|
||||
* must go through the `WriteLine` call).
|
||||
*/
|
||||
predicate strictlyPostDominates(BasicBlock bb) { super.strictlyPostDominates(bb) }
|
||||
|
||||
/**
|
||||
* Holds if this basic block post-dominates basic block `bb`.
|
||||
*
|
||||
* That is, all paths reaching a normal exit point basic block from basic
|
||||
* block `bb` must go through this basic block.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```csharp
|
||||
* int M(string s) {
|
||||
* try {
|
||||
* return s.Length;
|
||||
* }
|
||||
* finally {
|
||||
* Console.WriteLine("M");
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The basic block on line 6 post-dominates the basic block on line 3
|
||||
* (all paths to the exit point of `M` from `return s.Length;` must go
|
||||
* through the `WriteLine` call).
|
||||
*
|
||||
* This predicate is *reflexive*, so for example `Console.WriteLine("M");`
|
||||
* post-dominates itself.
|
||||
*/
|
||||
predicate postDominates(BasicBlock bb) { super.postDominates(bb) }
|
||||
|
||||
/**
|
||||
* Holds if this basic block is in a loop in the control flow graph. This
|
||||
* includes loops created by `goto` statements. This predicate may not hold
|
||||
* even if this basic block is syntactically inside a `while` loop if the
|
||||
* necessary back edges are unreachable.
|
||||
*/
|
||||
predicate inLoop() { this.getASuccessor+() = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* An entry basic block, that is, a basic block whose first node is
|
||||
* an entry node.
|
||||
*/
|
||||
final class EntryBasicBlock extends BasicBlock, BasicBlocksImpl::EntryBasicBlock { }
|
||||
|
||||
/**
|
||||
* An annotated exit basic block, that is, a basic block that contains an
|
||||
* annotated exit node.
|
||||
*/
|
||||
final class AnnotatedExitBasicBlock extends BasicBlock, BasicBlocksImpl::AnnotatedExitBasicBlock { }
|
||||
|
||||
/**
|
||||
* An exit basic block, that is, a basic block whose last node is
|
||||
* an exit node.
|
||||
*/
|
||||
final class ExitBasicBlock extends BasicBlock, BasicBlocksImpl::ExitBasicBlock { }
|
||||
|
||||
/** A basic block with more than one predecessor. */
|
||||
final class JoinBlock extends BasicBlock, BasicBlocksImpl::JoinBasicBlock {
|
||||
JoinBlockPredecessor getJoinBlockPredecessor(int i) { result = super.getJoinBlockPredecessor(i) }
|
||||
}
|
||||
|
||||
/** A basic block that is an immediate predecessor of a join block. */
|
||||
final class JoinBlockPredecessor extends BasicBlock, BasicBlocksImpl::JoinPredecessorBasicBlock { }
|
||||
|
||||
/**
|
||||
* A basic block that terminates in a condition, splitting the subsequent
|
||||
* control flow.
|
||||
*/
|
||||
final class ConditionBlock extends BasicBlock, BasicBlocksImpl::ConditionBasicBlock {
|
||||
/** DEPRECATED: Use `edgeDominates` instead. */
|
||||
deprecated predicate immediatelyControls(BasicBlock succ, ConditionalSuccessor s) {
|
||||
this.getASuccessor(s) = succ and
|
||||
BasicBlocksImpl::dominatingEdge(this, succ)
|
||||
}
|
||||
|
||||
/** DEPRECATED: Use `edgeDominates` instead. */
|
||||
deprecated predicate controls(BasicBlock controlled, ConditionalSuccessor s) {
|
||||
super.edgeDominates(controlled, s)
|
||||
}
|
||||
}
|
||||
|
||||
private class BasicBlockAlias = BasicBlock;
|
||||
|
||||
private class EntryBasicBlockAlias = EntryBasicBlock;
|
||||
|
||||
module Cfg implements BB::CfgSig<Location> {
|
||||
class ControlFlowNode = ControlFlow::Node;
|
||||
|
||||
class BasicBlock = BasicBlockAlias;
|
||||
|
||||
class EntryBasicBlock = EntryBasicBlockAlias;
|
||||
|
||||
predicate dominatingEdge(BasicBlock bb1, BasicBlock bb2) {
|
||||
BasicBlocksImpl::dominatingEdge(bb1, bb2)
|
||||
}
|
||||
}
|
||||
@@ -4,21 +4,20 @@ import csharp
|
||||
private import semmle.code.csharp.ExprOrStmtParent
|
||||
private import semmle.code.csharp.commons.Compilation
|
||||
private import ControlFlow
|
||||
private import ControlFlow::BasicBlocks
|
||||
private import semmle.code.csharp.Caching
|
||||
|
||||
private class TControlFlowElementOrCallable = @callable or @control_flow_element;
|
||||
|
||||
/** A `ControlFlowElement` or a `Callable`. */
|
||||
class ControlFlowElementOrCallable extends ExprOrStmtParent, TControlFlowElementOrCallable { }
|
||||
private import internal.ControlFlowGraphImpl as Impl
|
||||
|
||||
/**
|
||||
* A program element that can possess control flow. That is, either a statement or
|
||||
* an expression.
|
||||
*
|
||||
* A control flow element can be mapped to a control flow node (`ControlFlowNode`)
|
||||
* via `getControlFlowNode()`.
|
||||
* A control flow element can be mapped to a control flow node (`ControlFlow::Node`)
|
||||
* via `getAControlFlowNode()`. There is a one-to-many relationship between
|
||||
* control flow elements and control flow nodes. This allows control flow
|
||||
* splitting, for example modeling the control flow through `finally` blocks.
|
||||
*/
|
||||
class ControlFlowElement extends ControlFlowElementOrCallable, @control_flow_element {
|
||||
class ControlFlowElement extends ExprOrStmtParent, @control_flow_element {
|
||||
/** Gets the enclosing callable of this element, if any. */
|
||||
Callable getEnclosingCallable() { none() }
|
||||
|
||||
@@ -31,26 +30,41 @@ class ControlFlowElement extends ControlFlowElementOrCallable, @control_flow_ele
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `getControlFlowNode()` instead.
|
||||
*
|
||||
* Gets a control flow node for this element. That is, a node in the
|
||||
* control flow graph that corresponds to this element.
|
||||
*
|
||||
* Typically, there is exactly one `ControlFlow::Node` associated with a
|
||||
* `ControlFlowElement`, but a `ControlFlowElement` may be split into
|
||||
* several `ControlFlow::Node`s, for example to represent the continuation
|
||||
* flow in a `try/catch/finally` construction.
|
||||
*/
|
||||
deprecated ControlFlowNodes::ElementNode getAControlFlowNode() {
|
||||
result = this.getControlFlowNode()
|
||||
}
|
||||
Nodes::ElementNode getAControlFlowNode() { result.getAstNode() = this }
|
||||
|
||||
/** Gets the control flow node for this element, if any. */
|
||||
ControlFlowNode getControlFlowNode() { result.injects(this) }
|
||||
/** Gets the control flow node for this element. */
|
||||
ControlFlow::Node getControlFlowNode() { result.getAstNode() = this }
|
||||
|
||||
/** Gets the basic block in which this element occurs. */
|
||||
BasicBlock getBasicBlock() { result = this.getControlFlowNode().getBasicBlock() }
|
||||
BasicBlock getBasicBlock() { result = this.getAControlFlowNode().getBasicBlock() }
|
||||
|
||||
/**
|
||||
* Gets a first control flow node executed within this element.
|
||||
*/
|
||||
Nodes::ElementNode getAControlFlowEntryNode() {
|
||||
result = Impl::getAControlFlowEntryNode(this).(ControlFlowElement).getAControlFlowNode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a potential last control flow node executed within this element.
|
||||
*/
|
||||
Nodes::ElementNode getAControlFlowExitNode() {
|
||||
result = Impl::getAControlFlowExitNode(this).(ControlFlowElement).getAControlFlowNode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this element is live, that is this element can be reached
|
||||
* from the entry point of its enclosing callable.
|
||||
*/
|
||||
predicate isLive() { exists(this.getControlFlowNode()) }
|
||||
predicate isLive() { exists(this.getAControlFlowNode()) }
|
||||
|
||||
/** Holds if the current element is reachable from `src`. */
|
||||
// potentially very large predicate, so must be inlined
|
||||
@@ -63,13 +77,31 @@ class ControlFlowElement extends ControlFlowElementOrCallable, @control_flow_ele
|
||||
ControlFlowElement getAReachableElement() {
|
||||
// Reachable in same basic block
|
||||
exists(BasicBlock bb, int i, int j |
|
||||
bb.getNode(i) = this.getControlFlowNode() and
|
||||
bb.getNode(j) = result.getControlFlowNode() and
|
||||
bb.getNode(i) = this.getAControlFlowNode() and
|
||||
bb.getNode(j) = result.getAControlFlowNode() and
|
||||
i < j
|
||||
)
|
||||
or
|
||||
// Reachable in different basic blocks
|
||||
this.getControlFlowNode().getBasicBlock().getASuccessor+().getANode() =
|
||||
result.getControlFlowNode()
|
||||
this.getAControlFlowNode().getBasicBlock().getASuccessor+().getANode() =
|
||||
result.getAControlFlowNode()
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Guard` class instead.
|
||||
*
|
||||
* Holds if basic block `controlled` is controlled by this control flow element
|
||||
* with conditional value `s`. That is, `controlled` can only be reached from
|
||||
* the callable entry point by going via the `s` edge out of *some* basic block
|
||||
* ending with this element.
|
||||
*
|
||||
* `cb` records all of the possible condition blocks for this control flow element
|
||||
* that a path from the callable entry point to `controlled` may go through.
|
||||
*/
|
||||
deprecated predicate controlsBlock(
|
||||
BasicBlock controlled, ConditionalSuccessor s, ConditionBlock cb
|
||||
) {
|
||||
cb.getLastNode() = this.getAControlFlowNode() and
|
||||
cb.edgeDominates(controlled, s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,577 +1,315 @@
|
||||
import csharp
|
||||
|
||||
/**
|
||||
* Provides classes representing the control flow graph within callables.
|
||||
*/
|
||||
|
||||
import csharp
|
||||
private import codeql.controlflow.ControlFlowGraph
|
||||
private import codeql.controlflow.SuccessorType
|
||||
private import semmle.code.csharp.commons.Compilation
|
||||
private import semmle.code.csharp.controlflow.internal.NonReturning as NonReturning
|
||||
|
||||
private module Cfg0 = Make0<Location, Ast>;
|
||||
|
||||
private module Cfg1 = Make1<Input>;
|
||||
|
||||
private module Cfg2 = Make2<Input>;
|
||||
|
||||
private import Cfg0
|
||||
private import Cfg1
|
||||
private import Cfg2
|
||||
import Public
|
||||
|
||||
/** Provides an implementation of the AST signature for C#. */
|
||||
private module Ast implements AstSig<Location> {
|
||||
private import csharp as CS
|
||||
|
||||
class AstNode = ControlFlowElementOrCallable;
|
||||
|
||||
additional predicate skipControlFlow(AstNode e) {
|
||||
e instanceof TypeAccess and
|
||||
not e instanceof TypeAccessPatternExpr
|
||||
or
|
||||
not e.getFile().fromSource()
|
||||
}
|
||||
|
||||
private AstNode getExprChild0(Expr e, int i) {
|
||||
not e instanceof NameOfExpr and
|
||||
not e instanceof AnonymousFunctionExpr and
|
||||
not skipControlFlow(result) and
|
||||
result = e.getChild(i)
|
||||
}
|
||||
|
||||
private AstNode getStmtChild0(Stmt s, int i) {
|
||||
not s instanceof FixedStmt and
|
||||
not s instanceof UsingBlockStmt and
|
||||
result = s.getChild(i)
|
||||
or
|
||||
s =
|
||||
any(FixedStmt fs |
|
||||
result = fs.getVariableDeclExpr(i)
|
||||
or
|
||||
result = fs.getBody() and
|
||||
i = max(int j | exists(fs.getVariableDeclExpr(j))) + 1
|
||||
)
|
||||
or
|
||||
s =
|
||||
any(UsingBlockStmt us |
|
||||
result = us.getExpr() and
|
||||
i = 0
|
||||
or
|
||||
result = us.getVariableDeclExpr(i)
|
||||
or
|
||||
result = us.getBody() and
|
||||
i = max([1, count(us.getVariableDeclExpr(_))])
|
||||
)
|
||||
}
|
||||
|
||||
AstNode getChild(AstNode n, int index) {
|
||||
result = getStmtChild0(n, index)
|
||||
or
|
||||
result = getExprChild0(n, index)
|
||||
}
|
||||
|
||||
private AstNode getParent(AstNode n) { n = getChild(result, _) }
|
||||
|
||||
Callable getEnclosingCallable(AstNode node) {
|
||||
result = node.(ControlFlowElement).getEnclosingCallable() or
|
||||
result.(ObjectInitMethod).initializes(getParent*(node)) or
|
||||
Initializers::staticMemberInitializer(result, getParent*(node))
|
||||
}
|
||||
|
||||
class Callable = CS::Callable;
|
||||
|
||||
AstNode callableGetBody(Callable c) {
|
||||
not skipControlFlow(result) and
|
||||
result = c.getBody()
|
||||
}
|
||||
|
||||
class Stmt = CS::Stmt;
|
||||
|
||||
class Expr = CS::Expr;
|
||||
|
||||
class BlockStmt = CS::BlockStmt;
|
||||
|
||||
class ExprStmt = CS::ExprStmt;
|
||||
|
||||
class IfStmt = CS::IfStmt;
|
||||
|
||||
class LoopStmt = CS::LoopStmt;
|
||||
|
||||
class WhileStmt = CS::WhileStmt;
|
||||
|
||||
class DoStmt = CS::DoStmt;
|
||||
|
||||
final private class FinalForStmt = CS::ForStmt;
|
||||
|
||||
class ForStmt extends FinalForStmt {
|
||||
Expr getInit(int index) { result = this.getInitializer(index) }
|
||||
}
|
||||
|
||||
final private class FinalForeachStmt = CS::ForeachStmt;
|
||||
|
||||
class ForeachStmt extends FinalForeachStmt {
|
||||
Expr getVariable() {
|
||||
result = this.getVariableDeclExpr() or result = this.getVariableDeclTuple()
|
||||
}
|
||||
|
||||
Expr getCollection() { result = this.getIterableExpr() }
|
||||
}
|
||||
|
||||
class BreakStmt = CS::BreakStmt;
|
||||
|
||||
class ContinueStmt = CS::ContinueStmt;
|
||||
|
||||
class GotoStmt = CS::GotoStmt;
|
||||
|
||||
class ReturnStmt = CS::ReturnStmt;
|
||||
|
||||
class Throw = CS::ThrowElement;
|
||||
|
||||
final private class FinalTryStmt = CS::TryStmt;
|
||||
|
||||
class TryStmt extends FinalTryStmt {
|
||||
Stmt getBody() { result = this.getBlock() }
|
||||
|
||||
CatchClause getCatch(int index) { result = this.getCatchClause(index) }
|
||||
|
||||
Stmt getFinally() { result = super.getFinally() }
|
||||
}
|
||||
|
||||
final private class FinalCatchClause = CS::CatchClause;
|
||||
|
||||
class CatchClause extends FinalCatchClause {
|
||||
AstNode getVariable() { result = this.(CS::SpecificCatchClause).getVariableDeclExpr() }
|
||||
|
||||
Expr getCondition() { result = this.getFilterClause() }
|
||||
|
||||
Stmt getBody() { result = this.getBlock() }
|
||||
}
|
||||
|
||||
final private class FinalSwitch = CS::Switch;
|
||||
|
||||
class Switch extends FinalSwitch {
|
||||
Case getCase(int index) { result = super.getCase(index) }
|
||||
|
||||
Stmt getStmt(int index) { result = this.(CS::SwitchStmt).getStmt(index) }
|
||||
}
|
||||
|
||||
final private class FinalCase = CS::Case;
|
||||
|
||||
class Case extends FinalCase {
|
||||
AstNode getAPattern() { result = this.getPattern() }
|
||||
|
||||
Expr getGuard() { result = this.getCondition() }
|
||||
|
||||
AstNode getBody() { result = super.getBody() }
|
||||
}
|
||||
|
||||
class DefaultCase extends Case instanceof CS::DefaultCase { }
|
||||
|
||||
class ConditionalExpr = CS::ConditionalExpr;
|
||||
|
||||
class BinaryExpr = CS::BinaryOperation;
|
||||
|
||||
class LogicalAndExpr = CS::LogicalAndExpr;
|
||||
|
||||
class LogicalOrExpr = CS::LogicalOrExpr;
|
||||
|
||||
class NullCoalescingExpr = CS::NullCoalescingExpr;
|
||||
|
||||
class UnaryExpr = CS::UnaryOperation;
|
||||
|
||||
class LogicalNotExpr = CS::LogicalNotExpr;
|
||||
|
||||
class Assignment = CS::Assignment;
|
||||
|
||||
class AssignExpr = CS::AssignExpr;
|
||||
|
||||
class CompoundAssignment = CS::AssignOperation;
|
||||
|
||||
class AssignLogicalAndExpr extends CompoundAssignment {
|
||||
AssignLogicalAndExpr() { none() }
|
||||
}
|
||||
|
||||
class AssignLogicalOrExpr extends CompoundAssignment {
|
||||
AssignLogicalOrExpr() { none() }
|
||||
}
|
||||
|
||||
class AssignNullCoalescingExpr = CS::AssignCoalesceExpr;
|
||||
|
||||
final private class FinalBoolLiteral = CS::BoolLiteral;
|
||||
|
||||
class BooleanLiteral extends FinalBoolLiteral {
|
||||
boolean getValue() { result = this.getBoolValue() }
|
||||
}
|
||||
|
||||
final private class FinalIsExpr = CS::IsExpr;
|
||||
|
||||
class PatternMatchExpr extends FinalIsExpr {
|
||||
AstNode getPattern() { result = super.getPattern() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A compilation.
|
||||
*
|
||||
* Unlike the standard `Compilation` class, this class also supports buildless
|
||||
* extraction.
|
||||
*/
|
||||
private newtype TCompilationExt =
|
||||
TCompilation(Compilation c) { not extractionIsStandalone() } or
|
||||
TBuildless() { extractionIsStandalone() }
|
||||
|
||||
private class CompilationExt extends TCompilationExt {
|
||||
string toString() {
|
||||
exists(Compilation c |
|
||||
this = TCompilation(c) and
|
||||
result = c.toString()
|
||||
)
|
||||
or
|
||||
this = TBuildless() and result = "buildless compilation"
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the compilation that source file `f` belongs to. */
|
||||
private CompilationExt getCompilation(File f) {
|
||||
exists(Compilation c |
|
||||
f = c.getAFileCompiled() and
|
||||
result = TCompilation(c)
|
||||
)
|
||||
or
|
||||
result = TBuildless()
|
||||
}
|
||||
|
||||
private module Initializers {
|
||||
private import semmle.code.csharp.ExprOrStmtParent as ExprOrStmtParent
|
||||
module ControlFlow {
|
||||
private import semmle.code.csharp.controlflow.BasicBlocks as BBs
|
||||
import semmle.code.csharp.controlflow.internal.SuccessorType
|
||||
private import internal.ControlFlowGraphImpl as Impl
|
||||
private import internal.Splitting as Splitting
|
||||
|
||||
/**
|
||||
* The `expr_parent_top_level_adjusted()` relation restricted to exclude relations
|
||||
* between properties and their getters' expression bodies in properties such as
|
||||
* `int P => 0`.
|
||||
* A control flow node.
|
||||
*
|
||||
* This is in order to only associate the expression body with one CFG scope, namely
|
||||
* the getter (and not the declaration itself).
|
||||
* Either a callable entry node (`EntryNode`), a callable exit node (`ExitNode`),
|
||||
* or a control flow node for a control flow element, that is, an expression or a
|
||||
* statement (`ElementNode`).
|
||||
*
|
||||
* A control flow node is a node in the control flow graph (CFG). There is a
|
||||
* many-to-one relationship between `ElementNode`s and `ControlFlowElement`s.
|
||||
* This allows control flow splitting, for example modeling the control flow
|
||||
* through `finally` blocks.
|
||||
*
|
||||
* Only nodes that can be reached from the callable entry point are included in
|
||||
* the CFG.
|
||||
*/
|
||||
private predicate expr_parent_top_level_adjusted2(
|
||||
Expr child, int i, @top_level_exprorstmt_parent parent
|
||||
) {
|
||||
ExprOrStmtParent::expr_parent_top_level_adjusted(child, i, parent) and
|
||||
not exists(Getter g |
|
||||
g.getDeclaration() = parent and
|
||||
i = 0
|
||||
)
|
||||
}
|
||||
class Node extends Impl::Node {
|
||||
/** Gets the control flow element that this node corresponds to, if any. */
|
||||
final ControlFlowElement getAstNode() { result = super.getAstNode() }
|
||||
|
||||
/**
|
||||
* Holds if `init` is a static member initializer and `staticCtor` is the
|
||||
* static constructor in the same declaring type. Hence, `staticCtor` can be
|
||||
* considered to execute `init` prior to the execution of its body.
|
||||
*/
|
||||
predicate staticMemberInitializer(Constructor staticCtor, Expr init) {
|
||||
exists(Assignable a |
|
||||
a.(Modifiable).isStatic() and
|
||||
expr_parent_top_level_adjusted2(init, _, a) and
|
||||
a.getDeclaringType() = staticCtor.getDeclaringType() and
|
||||
staticCtor.isStatic()
|
||||
)
|
||||
}
|
||||
/** Gets the basic block that this control flow node belongs to. */
|
||||
BasicBlock getBasicBlock() { result.getANode() = this }
|
||||
|
||||
/**
|
||||
* Gets the `i`th static member initializer expression for static constructor `staticCtor`.
|
||||
*/
|
||||
Expr initializedStaticMemberOrder(Constructor staticCtor, int i) {
|
||||
result =
|
||||
rank[i + 1](Expr init, Location l, string filepath, int startline, int startcolumn |
|
||||
staticMemberInitializer(staticCtor, init) and
|
||||
l = init.getLocation() and
|
||||
l.hasLocationInfo(filepath, startline, startcolumn, _, _)
|
||||
|
|
||||
init order by startline, startcolumn, filepath
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `i`th member initializer expression for object initializer method `obinit`.
|
||||
*/
|
||||
AssignExpr initializedInstanceMemberOrder(ObjectInitMethod obinit, int i) {
|
||||
result =
|
||||
rank[i + 1](AssignExpr ae0, Location l, string filepath, int startline, int startcolumn |
|
||||
obinit.initializes(ae0) and
|
||||
l = ae0.getLocation() and
|
||||
l.hasLocationInfo(filepath, startline, startcolumn, _, _)
|
||||
|
|
||||
ae0 order by startline, startcolumn, filepath
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private module Exceptions {
|
||||
private import semmle.code.csharp.commons.Assertions
|
||||
|
||||
private class Overflowable extends UnaryOperation {
|
||||
Overflowable() {
|
||||
not this instanceof UnaryBitwiseOperation and
|
||||
this.getType() instanceof IntegralType
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `cfe` is a control flow element that may throw an exception. */
|
||||
predicate mayThrowException(ControlFlowElement cfe) {
|
||||
cfe.(TriedControlFlowElement).mayThrowException()
|
||||
or
|
||||
cfe instanceof Assertion
|
||||
}
|
||||
|
||||
/** A control flow element that is inside a `try` block. */
|
||||
private class TriedControlFlowElement extends ControlFlowElement {
|
||||
TriedControlFlowElement() {
|
||||
this = any(TryStmt try).getATriedElement() and
|
||||
not this instanceof NonReturning::NonReturningCall
|
||||
/**
|
||||
* Holds if this node dominates `that` node.
|
||||
*
|
||||
* That is, all paths reaching `that` node from some callable entry
|
||||
* node (`EntryNode`) must go through this node.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```csharp
|
||||
* int M(string s)
|
||||
* {
|
||||
* if (s == null)
|
||||
* throw new ArgumentNullException(nameof(s));
|
||||
* return s.Length;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The node on line 3 dominates the node on line 5 (all paths from the
|
||||
* entry point of `M` to `return s.Length;` must go through the null check).
|
||||
*
|
||||
* This predicate is *reflexive*, so for example `if (s == null)` dominates
|
||||
* itself.
|
||||
*/
|
||||
// potentially very large predicate, so must be inlined
|
||||
pragma[inline]
|
||||
predicate dominates(Node that) {
|
||||
this.strictlyDominates(that)
|
||||
or
|
||||
this = that
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this element may potentially throw an exception.
|
||||
* Holds if this node strictly dominates `that` node.
|
||||
*
|
||||
* That is, all paths reaching `that` node from some callable entry
|
||||
* node (`EntryNode`) must go through this node (which must
|
||||
* be different from `that` node).
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```csharp
|
||||
* int M(string s)
|
||||
* {
|
||||
* if (s == null)
|
||||
* throw new ArgumentNullException(nameof(s));
|
||||
* return s.Length;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The node on line 3 strictly dominates the node on line 5
|
||||
* (all paths from the entry point of `M` to `return s.Length;` must go
|
||||
* through the null check).
|
||||
*/
|
||||
predicate mayThrowException() {
|
||||
this instanceof Overflowable
|
||||
// potentially very large predicate, so must be inlined
|
||||
pragma[inline]
|
||||
predicate strictlyDominates(Node that) {
|
||||
this.getBasicBlock().strictlyDominates(that.getBasicBlock())
|
||||
or
|
||||
this.(CastExpr).getType() instanceof IntegralType
|
||||
or
|
||||
invalidCastCandidate(this)
|
||||
or
|
||||
this instanceof Call
|
||||
or
|
||||
this =
|
||||
any(MemberAccess ma |
|
||||
not ma.isConditional() and
|
||||
ma.getQualifier() = any(Expr e | not e instanceof TypeAccess)
|
||||
)
|
||||
or
|
||||
this instanceof DelegateCreation
|
||||
or
|
||||
this instanceof ArrayCreation
|
||||
or
|
||||
this =
|
||||
any(AddOperation ae |
|
||||
ae.getType() instanceof StringType
|
||||
or
|
||||
ae.getType() instanceof IntegralType
|
||||
)
|
||||
or
|
||||
this = any(SubOperation se | se.getType() instanceof IntegralType)
|
||||
or
|
||||
this = any(MulOperation me | me.getType() instanceof IntegralType)
|
||||
or
|
||||
this = any(DivOperation de | not de.getDenominator().getValue().toFloat() != 0)
|
||||
or
|
||||
this instanceof RemOperation
|
||||
or
|
||||
this instanceof DynamicExpr
|
||||
exists(BasicBlock bb, int i, int j |
|
||||
bb.getNode(i) = this and
|
||||
bb.getNode(j) = that and
|
||||
i < j
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private ValueOrRefType getACastExprBaseType(CastExpr ce) {
|
||||
result = ce.getType().(ValueOrRefType).getABaseType()
|
||||
or
|
||||
result = getACastExprBaseType(ce).getABaseType()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate invalidCastCandidate(CastExpr ce) {
|
||||
ce.getExpr().getType() = getACastExprBaseType(ce)
|
||||
}
|
||||
}
|
||||
|
||||
private module Input implements InputSig1, InputSig2 {
|
||||
predicate cfgCachedStageRef() { CfgCachedStage::ref() }
|
||||
|
||||
predicate catchAll(Ast::CatchClause catch) { catch instanceof GeneralCatchClause }
|
||||
|
||||
predicate matchAll(Ast::Case c) { c instanceof DefaultCase or c.(SwitchCaseExpr).matchesAll() }
|
||||
|
||||
private newtype TLabel =
|
||||
TLblGoto(string label) { any(GotoLabelStmt goto).getLabel() = label } or
|
||||
TLblSwitchCase(string value) { any(GotoCaseStmt goto).getLabel() = value } or
|
||||
TLblSwitchDefault()
|
||||
|
||||
class Label extends TLabel {
|
||||
string toString() {
|
||||
this = TLblGoto(result)
|
||||
/**
|
||||
* Holds if this node post-dominates `that` node.
|
||||
*
|
||||
* That is, all paths reaching a normal callable exit node (an `AnnotatedExitNode`
|
||||
* with a normal exit type) from `that` node must go through this node.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```csharp
|
||||
* int M(string s)
|
||||
* {
|
||||
* try
|
||||
* {
|
||||
* return s.Length;
|
||||
* }
|
||||
* finally
|
||||
* {
|
||||
* Console.WriteLine("M");
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The node on line 9 post-dominates the node on line 5 (all paths to the
|
||||
* exit point of `M` from `return s.Length;` must go through the `WriteLine`
|
||||
* call).
|
||||
*
|
||||
* This predicate is *reflexive*, so for example `Console.WriteLine("M");`
|
||||
* post-dominates itself.
|
||||
*/
|
||||
// potentially very large predicate, so must be inlined
|
||||
pragma[inline]
|
||||
predicate postDominates(Node that) {
|
||||
this.strictlyPostDominates(that)
|
||||
or
|
||||
this = TLblSwitchCase(result)
|
||||
or
|
||||
this = TLblSwitchDefault() and result = "default"
|
||||
this = that
|
||||
}
|
||||
}
|
||||
|
||||
predicate hasLabel(Ast::AstNode n, Label l) {
|
||||
l = TLblGoto(n.(GotoLabelStmt).getLabel())
|
||||
or
|
||||
l = TLblSwitchCase(n.(GotoCaseStmt).getLabel())
|
||||
or
|
||||
l = TLblSwitchDefault() and n instanceof GotoDefaultStmt
|
||||
or
|
||||
l = TLblGoto(n.(LabelStmt).getLabel())
|
||||
}
|
||||
|
||||
class CallableBodyPartContext = CompilationExt;
|
||||
|
||||
pragma[nomagic]
|
||||
Ast::AstNode callableGetBodyPart(Callable c, CallableBodyPartContext ctx, int index) {
|
||||
not Ast::skipControlFlow(result) and
|
||||
ctx = getCompilation(result.getFile()) and
|
||||
(
|
||||
result = Initializers::initializedInstanceMemberOrder(c, index)
|
||||
/**
|
||||
* Holds if this node strictly post-dominates `that` node.
|
||||
*
|
||||
* That is, all paths reaching a normal callable exit node (an `AnnotatedExitNode`
|
||||
* with a normal exit type) from `that` node must go through this node
|
||||
* (which must be different from `that` node).
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```csharp
|
||||
* int M(string s)
|
||||
* {
|
||||
* try
|
||||
* {
|
||||
* return s.Length;
|
||||
* }
|
||||
* finally
|
||||
* {
|
||||
* Console.WriteLine("M");
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The node on line 9 strictly post-dominates the node on line 5 (all
|
||||
* paths to the exit point of `M` from `return s.Length;` must go through
|
||||
* the `WriteLine` call).
|
||||
*/
|
||||
// potentially very large predicate, so must be inlined
|
||||
pragma[inline]
|
||||
predicate strictlyPostDominates(Node that) {
|
||||
this.getBasicBlock().strictlyPostDominates(that.getBasicBlock())
|
||||
or
|
||||
result = Initializers::initializedStaticMemberOrder(c, index)
|
||||
or
|
||||
exists(Constructor ctor, int i, int staticMembers |
|
||||
c = ctor and
|
||||
staticMembers = count(Expr init | Initializers::staticMemberInitializer(ctor, init)) and
|
||||
index = staticMembers + i + 1
|
||||
|
|
||||
i = 0 and result = ctor.getObjectInitializerCall()
|
||||
or
|
||||
i = 1 and result = ctor.getInitializer()
|
||||
or
|
||||
i = 2 and result = ctor.getBody()
|
||||
exists(BasicBlock bb, int i, int j |
|
||||
bb.getNode(i) = this and
|
||||
bb.getNode(j) = that and
|
||||
i > j
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a successor node of a given type, if any. */
|
||||
Node getASuccessorByType(SuccessorType t) { result = this.getASuccessor(t) }
|
||||
|
||||
/** Gets an immediate successor, if any. */
|
||||
Node getASuccessor() { result = this.getASuccessorByType(_) }
|
||||
|
||||
/** Gets an immediate predecessor node of a given flow type, if any. */
|
||||
Node getAPredecessorByType(SuccessorType t) { result.getASuccessorByType(t) = this }
|
||||
|
||||
/** Gets an immediate predecessor, if any. */
|
||||
Node getAPredecessor() { result = this.getAPredecessorByType(_) }
|
||||
|
||||
/**
|
||||
* Gets an immediate `true` successor, if any.
|
||||
*
|
||||
* An immediate `true` successor is a successor that is reached when
|
||||
* this condition evaluates to `true`.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```csharp
|
||||
* if (x < 0)
|
||||
* x = -x;
|
||||
* ```
|
||||
*
|
||||
* The node on line 2 is an immediate `true` successor of the node
|
||||
* on line 1.
|
||||
*/
|
||||
Node getATrueSuccessor() {
|
||||
result = this.getASuccessorByType(any(BooleanSuccessor t | t.getValue() = true))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an immediate `false` successor, if any.
|
||||
*
|
||||
* An immediate `false` successor is a successor that is reached when
|
||||
* this condition evaluates to `false`.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```csharp
|
||||
* if (!(x >= 0))
|
||||
* x = -x;
|
||||
* ```
|
||||
*
|
||||
* The node on line 2 is an immediate `false` successor of the node
|
||||
* on line 1.
|
||||
*/
|
||||
Node getAFalseSuccessor() {
|
||||
result = this.getASuccessorByType(any(BooleanSuccessor t | t.getValue() = false))
|
||||
}
|
||||
|
||||
/** Gets the enclosing callable of this control flow node. */
|
||||
final Callable getEnclosingCallable() { result = Impl::getNodeCfgScope(this) }
|
||||
}
|
||||
|
||||
private Expr getQualifier(QualifiableExpr qe) {
|
||||
result = qe.getQualifier() or
|
||||
result = qe.(ExtensionMethodCall).getArgument(0)
|
||||
/** Provides different types of control flow nodes. */
|
||||
module Nodes {
|
||||
/** A node for a callable entry point. */
|
||||
class EntryNode extends Node instanceof Impl::EntryNode {
|
||||
/** Gets the callable that this entry applies to. */
|
||||
Callable getCallable() { result = this.getScope() }
|
||||
|
||||
override BasicBlocks::EntryBlock getBasicBlock() { result = Node.super.getBasicBlock() }
|
||||
}
|
||||
|
||||
/** A node for a callable exit point, annotated with the type of exit. */
|
||||
class AnnotatedExitNode extends Node instanceof Impl::AnnotatedExitNode {
|
||||
/** Holds if this node represent a normal exit. */
|
||||
final predicate isNormal() { super.isNormal() }
|
||||
|
||||
/** Gets the callable that this exit applies to. */
|
||||
Callable getCallable() { result = this.getScope() }
|
||||
|
||||
override BasicBlocks::AnnotatedExitBlock getBasicBlock() {
|
||||
result = Node.super.getBasicBlock()
|
||||
}
|
||||
}
|
||||
|
||||
/** A control flow node indicating normal termination of a callable. */
|
||||
class NormalExitNode extends AnnotatedExitNode instanceof Impl::NormalExitNode { }
|
||||
|
||||
/** A node for a callable exit point. */
|
||||
class ExitNode extends Node instanceof Impl::ExitNode {
|
||||
/** Gets the callable that this exit applies to. */
|
||||
Callable getCallable() { result = this.getScope() }
|
||||
|
||||
override BasicBlocks::ExitBlock getBasicBlock() { result = Node.super.getBasicBlock() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A node for a control flow element, that is, an expression or a statement.
|
||||
*
|
||||
* Each control flow element maps to zero or more `ElementNode`s: zero when
|
||||
* the element is in unreachable (dead) code, and multiple when there are
|
||||
* different splits for the element.
|
||||
*/
|
||||
class ElementNode extends Node instanceof Impl::AstCfgNode {
|
||||
/** Gets a comma-separated list of strings for each split in this node, if any. */
|
||||
final string getSplitsString() { result = super.getSplitsString() }
|
||||
|
||||
/** Gets a split for this control flow node, if any. */
|
||||
final Split getASplit() { result = super.getASplit() }
|
||||
}
|
||||
|
||||
/** A control-flow node for an expression. */
|
||||
class ExprNode extends ElementNode {
|
||||
Expr e;
|
||||
|
||||
ExprNode() { e = unique(Expr e_ | e_ = this.getAstNode() | e_) }
|
||||
|
||||
/** Gets the expression that this control-flow node belongs to. */
|
||||
Expr getExpr() { result = e }
|
||||
|
||||
/** Gets the value of this expression node, if any. */
|
||||
string getValue() { result = e.getValue() }
|
||||
|
||||
/** Gets the type of this expression node. */
|
||||
Type getType() { result = e.getType() }
|
||||
}
|
||||
|
||||
class Split = Splitting::Split;
|
||||
}
|
||||
|
||||
predicate inConditionalContext(Ast::AstNode n, ConditionKind kind) {
|
||||
kind.isNullness() and
|
||||
exists(QualifiableExpr qe | qe.isConditional() | n = getQualifier(qe))
|
||||
}
|
||||
class BasicBlock = BBs::BasicBlock;
|
||||
|
||||
predicate postOrInOrder(Ast::AstNode n) { n instanceof YieldStmt or n instanceof Call }
|
||||
/** Provides different types of basic blocks. */
|
||||
module BasicBlocks {
|
||||
class EntryBlock = BBs::EntryBasicBlock;
|
||||
|
||||
predicate beginAbruptCompletion(
|
||||
Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c, boolean always
|
||||
) {
|
||||
// `yield break` behaves like a return statement
|
||||
ast instanceof YieldBreakStmt and
|
||||
n.isIn(ast) and
|
||||
c.asSimpleAbruptCompletion() instanceof ReturnSuccessor and
|
||||
always = true
|
||||
or
|
||||
Exceptions::mayThrowException(ast) and
|
||||
n.isIn(ast) and
|
||||
c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and
|
||||
always = false
|
||||
or
|
||||
ast instanceof NonReturning::NonReturningCall and
|
||||
n.isIn(ast) and
|
||||
c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and
|
||||
always = true
|
||||
}
|
||||
class AnnotatedExitBlock = BBs::AnnotatedExitBasicBlock;
|
||||
|
||||
predicate endAbruptCompletion(Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c) {
|
||||
exists(SwitchStmt switch, Label l, Ast::Case case |
|
||||
ast.(Stmt).getParent() = switch and
|
||||
c.getSuccessorType() instanceof GotoSuccessor and
|
||||
c.hasLabel(l) and
|
||||
n.isAfterValue(case, any(MatchingSuccessor t | t.getValue() = true))
|
||||
|
|
||||
exists(string value, ConstCase cc |
|
||||
l = TLblSwitchCase(value) and
|
||||
switch.getAConstCase() = cc and
|
||||
cc.getLabel() = value and
|
||||
cc = case
|
||||
)
|
||||
or
|
||||
l = TLblSwitchDefault() and switch.getDefaultCase() = case
|
||||
)
|
||||
}
|
||||
class ExitBlock = BBs::ExitBasicBlock;
|
||||
|
||||
predicate step(PreControlFlowNode n1, PreControlFlowNode n2) {
|
||||
exists(QualifiableExpr qe | qe.isConditional() |
|
||||
n1.isBefore(qe) and n2.isBefore(getQualifier(qe))
|
||||
or
|
||||
exists(NullnessSuccessor t | n1.isAfterValue(getQualifier(qe), t) |
|
||||
if t.isNull()
|
||||
then (
|
||||
// if `q` is null in `q?.f = x` then the assignment is skipped. This
|
||||
// holds for both regular, compound, and null-coalescing assignments.
|
||||
// On the other hand, the CFG definition for the assignment can treat
|
||||
// the LHS the same regardless of whether it's a conditionally
|
||||
// qualified access or not, as it just connects to the "before" and
|
||||
// "after" nodes of the LHS, and the "after" node is skipped in this
|
||||
// case.
|
||||
exists(AssignableDefinition def |
|
||||
def.getTargetAccess() = qe and
|
||||
n2.isAfterValue(def.getExpr(), t)
|
||||
)
|
||||
or
|
||||
not qe instanceof AssignableWrite and
|
||||
n2.isAfterValue(qe, t)
|
||||
) else (
|
||||
n2.isBefore(Ast::getChild(qe, 0))
|
||||
or
|
||||
n2.isIn(qe) and not exists(Ast::getChild(qe, 0))
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(int i | i >= 0 and n1.isAfter(Ast::getChild(qe, i)) |
|
||||
n2.isBefore(Ast::getChild(qe, i + 1))
|
||||
or
|
||||
not exists(Ast::getChild(qe, i + 1)) and n2.isIn(qe)
|
||||
)
|
||||
or
|
||||
n1.isIn(qe) and n2.isAfter(qe) and not beginAbruptCompletion(qe, n1, _, true)
|
||||
)
|
||||
or
|
||||
exists(ObjectCreation oc |
|
||||
n1.isBefore(oc) and n2.isBefore(oc.getArgument(0))
|
||||
or
|
||||
n1.isBefore(oc) and n2.isIn(oc) and not exists(oc.getAnArgument())
|
||||
or
|
||||
exists(int i | n1.isAfter(oc.getArgument(i)) |
|
||||
n2.isBefore(oc.getArgument(i + 1))
|
||||
or
|
||||
not exists(oc.getArgument(i + 1)) and n2.isIn(oc)
|
||||
)
|
||||
or
|
||||
n1.isIn(oc) and n2.isBefore(oc.getInitializer())
|
||||
or
|
||||
n1.isIn(oc) and n2.isAfter(oc) and not exists(oc.getInitializer())
|
||||
or
|
||||
n1.isAfter(oc.getInitializer()) and n2.isAfter(oc)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides different types of control flow nodes. */
|
||||
module ControlFlowNodes {
|
||||
/**
|
||||
* A node for a control flow element, that is, an expression or a statement.
|
||||
*
|
||||
* Each control flow element maps to zero or one `ElementNode`s: zero when
|
||||
* the element is in unreachable (dead) code, and otherwise one.
|
||||
*/
|
||||
class ElementNode extends ControlFlowNode {
|
||||
ElementNode() { exists(this.asExpr()) or exists(this.asStmt()) }
|
||||
}
|
||||
|
||||
/** A control-flow node for an expression. */
|
||||
class ExprNode extends ElementNode {
|
||||
Expr e;
|
||||
|
||||
ExprNode() { e = this.asExpr() }
|
||||
|
||||
/** Gets the expression that this control-flow node belongs to. */
|
||||
Expr getExpr() { result = e }
|
||||
|
||||
/** Gets the value of this expression node, if any. */
|
||||
string getValue() { result = e.getValue() }
|
||||
|
||||
/** Gets the type of this expression node. */
|
||||
Type getType() { result = e.getType() }
|
||||
class JoinBlock = BBs::JoinBlock;
|
||||
|
||||
class JoinBlockPredecessor = BBs::JoinBlockPredecessor;
|
||||
|
||||
class ConditionBlock = BBs::ConditionBlock;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
|
||||
import csharp
|
||||
private import codeql.controlflow.ControlFlowReachability
|
||||
private import semmle.code.csharp.controlflow.BasicBlocks
|
||||
private import semmle.code.csharp.controlflow.Guards as Guards
|
||||
private import semmle.code.csharp.ExprOrStmtParent
|
||||
|
||||
private module ControlFlowInput implements InputSig<Location, ControlFlowNode, BasicBlock> {
|
||||
private module ControlFlowInput implements
|
||||
InputSig<Location, ControlFlow::Node, ControlFlow::BasicBlock>
|
||||
{
|
||||
private import csharp as CS
|
||||
|
||||
AstNode getEnclosingAstNode(ControlFlowNode node) {
|
||||
AstNode getEnclosingAstNode(ControlFlow::Node node) {
|
||||
node.getAstNode() = result
|
||||
or
|
||||
not exists(node.getAstNode()) and result = node.getEnclosingCallable()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user