diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index f1844da86cf..7f218e99da4 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -2,7 +2,13 @@ name: "Code scanning - action"
on:
push:
+ branches:
+ - main
+ - 'rc/*'
pull_request:
+ branches:
+ - main
+ - 'rc/*'
schedule:
- cron: '0 9 * * 1'
@@ -14,16 +20,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
- with:
- # We must fetch at least the immediate parents so that if this is
- # a pull request then we can checkout the head.
- fetch-depth: 2
- # If this run was triggered by a pull request event, then checkout
- # the head of the pull request instead of the merge commit.
- - run: git checkout HEAD^2
- if: ${{ github.event_name == 'pull_request' }}
-
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
diff --git a/.github/workflows/generate-query-help-docs.yml b/.github/workflows/generate-query-help-docs.yml
new file mode 100644
index 00000000000..e3af19c6dea
--- /dev/null
+++ b/.github/workflows/generate-query-help-docs.yml
@@ -0,0 +1,60 @@
+name: Generate CodeQL query help documentation using Sphinx
+
+on:
+ workflow_dispatch:
+ inputs:
+ description:
+ description: A description of the purpose of this job. For human consumption.
+ required: false
+ push:
+ branches:
+ - 'lgtm.com'
+ pull_request:
+ paths:
+ - '.github/workflows/generate-query-help-docs.yml'
+ - 'docs/codeql/query-help/**'
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Clone github/codeql
+ uses: actions/checkout@v2
+ with:
+ path: codeql
+ - name: Clone github/codeql-go
+ uses: actions/checkout@v2
+ with:
+ repository: 'github/codeql-go'
+ path: codeql-go
+ - name: Set up Python 3.8
+ uses: actions/setup-python@v2
+ with:
+ python-version: 3.8
+ - name: Download CodeQL CLI
+ uses: dsaltares/fetch-gh-release-asset@aa37ae5c44d3c9820bc12fe675e8670ecd93bd1c
+ with:
+ repo: "github/codeql-cli-binaries"
+ version: "latest"
+ file: "codeql-linux64.zip"
+ token: ${{ secrets.GITHUB_TOKEN }}
+ - name: Unzip CodeQL CLI
+ run: unzip -d codeql-cli codeql-linux64.zip
+ - name: Set up query help docs folder
+ run: |
+ cp -r codeql/docs/codeql/** .
+ - name: Query help to markdown
+ run: |
+ PATH="$PATH:codeql-cli/codeql" python codeql/docs/codeql/query-help-markdown.py
+ - name: Run Sphinx for query help
+ uses: ammaraskar/sphinx-action@8b4f60114d7fd1faeba1a712269168508d4750d2 # v0.4
+ with:
+ docs-folder: "query-help/"
+ pre-build-command: "python -m pip install --upgrade recommonmark && python -m pip install --upgrade sphinx-markdown-tables"
+ build-command: "sphinx-build -b dirhtml . _build"
+ - name: Upload HTML artifacts
+ uses: actions/upload-artifact@v2
+ with:
+ name: query-help-html
+ path: query-help/_build
+
diff --git a/.gitignore b/.gitignore
index b14dab0a6b1..0951496d45c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,9 @@
# Byte-compiled python files
*.pyc
+# python virtual environment folder
+.venv/
+
# It's useful (though not required) to be able to unpack codeql in the ql checkout itself
/codeql/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fa88395e5d0..39552ad0bd9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -38,6 +38,8 @@ If you have an idea for a query that you would like to share with other CodeQL u
- The queries and libraries must be autoformatted, for example using the "Format Document" command in [CodeQL for Visual Studio Code](https://help.semmle.com/codeql/codeql-for-vscode/procedures/about-codeql-for-vscode.html).
+ If you prefer, you can use this [pre-commit hook](misc/scripts/pre-commit) that automatically checks whether your files are correctly formatted. See the [pre-commit hook installation guide](docs/install-pre-commit-hook.md) for instructions on how to install the hook.
+
4. **Compilation**
- Compilation of the query and any associated libraries and tests must be resilient to future development of the [supported](docs/supported-queries.md) libraries. This means that the functionality cannot use internal libraries, cannot depend on the output of `getAQlClass`, and cannot make use of regexp matching on `toString`.
diff --git a/change-notes/1.26/analysis-python.md b/change-notes/1.26/analysis-python.md
index f60eb6b4354..0c6cbeaacfa 100644
--- a/change-notes/1.26/analysis-python.md
+++ b/change-notes/1.26/analysis-python.md
@@ -4,19 +4,34 @@ The following changes in version 1.26 affect Python analysis in all applications
## General improvements
-
-## New queries
-
-| **Query** | **Tags** | **Purpose** |
-|-----------------------------|-----------|--------------------------------------------------------------------|
-
-
## Changes to existing queries
| **Query** | **Expected impact** | **Change** |
|----------------------------|------------------------|------------------------------------------------------------------|
-
-
+|`py/unsafe-deserialization` | Different results. | The underlying data flow library has been changed. See below for more details. |
+|`py/path-injection` | Different results. | The underlying data flow library has been changed. See below for more details. |
+|`py/command-line-injection` | Different results. | The underlying data flow library has been changed. See below for more details. |
+|`py/reflective-xss` | Different results. | The underlying data flow library has been changed. See below for more details. |
+|`py/sql-injection` | Different results. | The underlying data flow library has been changed. See below for more details. |
+|`py/code-injection` | Different results. | The underlying data flow library has been changed. See below for more details. |
## Changes to libraries
-
+* Some of the security queries now use the shared data flow library for data flow and taint tracking. This has resulted in an overall more robust and accurate analysis. The libraries mentioned below have been modelled in this new framework. Other libraries (e.g. the web framework `CherryPy`) have not been modelled yet, and this may lead to a temporary loss of results for these frameworks.
+* Improved modelling of the following serialization libraries:
+ - `PyYAML`
+ - `dill`
+ - `pickle`
+ - `marshal`
+* Improved modelling of the following web frameworks:
+ - `Django` (Note that modelling of class-based response handlers is currently incomplete.)
+ - `Flask`
+* Support for Werkzeug `MultiDict`.
+* Support for the [Python Database API Specification v2.0 (PEP-249)](https://www.python.org/dev/peps/pep-0249/), including the following libraries:
+ - `MySQLdb`
+ - `mysql-connector-python`
+ - `django.db`
+* Improved modelling of the following command execution libraries:
+ - `Fabric`
+ - `Invoke`
+* Improved modelling of security-related standard library modules, such as `os`, `popen2`, `platform`, and `base64`.
+* The original versions of the updated queries have been preserved [here](https://github.com/github/codeql/tree/main/python/ql/src/experimental/Security-old-dataflow).
* Added taint tracking support for string formatting through f-strings.
diff --git a/cpp/change-notes/2020-02-04-unsigned-difference-expression-compared-zero.md b/cpp/change-notes/2020-02-04-unsigned-difference-expression-compared-zero.md
new file mode 100644
index 00000000000..d4a6ebafa0c
--- /dev/null
+++ b/cpp/change-notes/2020-02-04-unsigned-difference-expression-compared-zero.md
@@ -0,0 +1,2 @@
+lgtm
+* A new query (`cpp/unsigned-difference-expression-compared-zero`) is run but not yet displayed on LGTM. The query finds unsigned subtractions used in relational comparisons with the value 0. This query was originally submitted as an experimental query by @ihsinme in https://github.com/github/codeql/pull/4745.
diff --git a/cpp/config/suites/cpp/correctness b/cpp/config/suites/cpp/correctness
index bcdf94c94ce..55678a1dd37 100644
--- a/cpp/config/suites/cpp/correctness
+++ b/cpp/config/suites/cpp/correctness
@@ -10,6 +10,7 @@
+ semmlecode-cpp-queries/Likely Bugs/Underspecified Functions/MistypedFunctionArguments.ql: /Correctness/Dangerous Conversions
+ semmlecode-cpp-queries/Security/CWE/CWE-253/HResultBooleanConversion.ql: /Correctness/Dangerous Conversions
+ semmlecode-cpp-queries/Likely Bugs/OO/UnsafeUseOfThis.ql: /Correctness/Dangerous Conversions
++ semmlecode-cpp-queries/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql: /Correctness/Dangerous Conversions
# Consistent Use
+ semmlecode-cpp-queries/Critical/ReturnValueIgnored.ql: /Correctness/Consistent Use
+ semmlecode-cpp-queries/Likely Bugs/InconsistentCheckReturnNull.ql: /Correctness/Consistent Use
diff --git a/cpp/ql/src/Architecture/General Class-Level Information/ClassHierarchies.ql b/cpp/ql/src/Architecture/General Class-Level Information/ClassHierarchies.ql
index 9e399982fec..4d17618f1bc 100644
--- a/cpp/ql/src/Architecture/General Class-Level Information/ClassHierarchies.ql
+++ b/cpp/ql/src/Architecture/General Class-Level Information/ClassHierarchies.ql
@@ -4,9 +4,6 @@
* @kind graph
* @id cpp/architecture/class-hierarchies
* @graph.layout organic
- * @workingset jhotdraw
- * @result succeed 48
- * @result_ondemand succeed 48
* @tags maintainability
*/
diff --git a/cpp/ql/src/Architecture/General Class-Level Information/InheritanceDepthDistribution.ql b/cpp/ql/src/Architecture/General Class-Level Information/InheritanceDepthDistribution.ql
index 72605be0540..0fc6f0ba2a7 100644
--- a/cpp/ql/src/Architecture/General Class-Level Information/InheritanceDepthDistribution.ql
+++ b/cpp/ql/src/Architecture/General Class-Level Information/InheritanceDepthDistribution.ql
@@ -4,9 +4,6 @@
* @kind chart
* @id cpp/architecture/inheritance-depth-distribution
* @chart.type line
- * @workingset jhotdraw
- * @result succeed 48
- * @result_ondemand succeed 48
* @tags maintainability
*/
diff --git a/cpp/ql/src/Architecture/General Namespace-Level Information/GlobalNamespaceClasses.ql b/cpp/ql/src/Architecture/General Namespace-Level Information/GlobalNamespaceClasses.ql
index 77f1be32aa0..eec387e2957 100644
--- a/cpp/ql/src/Architecture/General Namespace-Level Information/GlobalNamespaceClasses.ql
+++ b/cpp/ql/src/Architecture/General Namespace-Level Information/GlobalNamespaceClasses.ql
@@ -1,7 +1,8 @@
/**
* @name Global namespace classes
* @description Finds classes that belong to no namespace.
- * @kind table
+ * @kind problem
+ * @problem.severity recommendation
* @id cpp/architecture/global-namespace-classes
* @tags maintainability
* modularity
diff --git a/cpp/ql/src/Architecture/Refactoring Opportunities/ClassesWithManyDependencies.ql b/cpp/ql/src/Architecture/Refactoring Opportunities/ClassesWithManyDependencies.ql
index 33db264a549..384af9ebef8 100644
--- a/cpp/ql/src/Architecture/Refactoring Opportunities/ClassesWithManyDependencies.ql
+++ b/cpp/ql/src/Architecture/Refactoring Opportunities/ClassesWithManyDependencies.ql
@@ -4,9 +4,6 @@
* @kind problem
* @id cpp/architecture/classes-with-many-dependencies
* @problem.severity recommendation
- * @workingset jhotdraw
- * @result succeed 20
- * @result_ondemand succeed 20
* @tags maintainability
* statistical
* non-attributable
diff --git a/cpp/ql/src/Best Practices/Magic Constants/MagicConstants.qll b/cpp/ql/src/Best Practices/Magic Constants/MagicConstants.qll
index d5bf4f71292..587b64b60b3 100644
--- a/cpp/ql/src/Best Practices/Magic Constants/MagicConstants.qll
+++ b/cpp/ql/src/Best Practices/Magic Constants/MagicConstants.qll
@@ -8,168 +8,41 @@ import semmle.code.cpp.AutogeneratedFile
predicate trivialPositiveIntValue(string s) {
// Small numbers
s = [0 .. 20].toString() or
- // Popular powers of two (decimal)
- s = "16" or
- s = "24" or
- s = "32" or
- s = "64" or
- s = "128" or
- s = "256" or
- s = "512" or
- s = "1024" or
- s = "2048" or
- s = "4096" or
- s = "16384" or
- s = "32768" or
- s = "65536" or
- s = "1048576" or
- s = "2147483648" or
- s = "4294967296" or
- // Popular powers of two, minus one (decimal)
- s = "15" or
- s = "31" or
- s = "63" or
- s = "127" or
- s = "255" or
- s = "511" or
- s = "1023" or
- s = "2047" or
- s = "4095" or
- s = "16383" or
- s = "32767" or
- s = "65535" or
- s = "1048577" or
- s = "2147483647" or
- s = "4294967295" or
- // Popular powers of two (32-bit hex)
- s = "0x00000001" or
- s = "0x00000002" or
- s = "0x00000004" or
- s = "0x00000008" or
- s = "0x00000010" or
- s = "0x00000020" or
- s = "0x00000040" or
- s = "0x00000080" or
- s = "0x00000100" or
- s = "0x00000200" or
- s = "0x00000400" or
- s = "0x00000800" or
- s = "0x00001000" or
- s = "0x00002000" or
- s = "0x00004000" or
- s = "0x00008000" or
- s = "0x00010000" or
- s = "0x00020000" or
- s = "0x00040000" or
- s = "0x00080000" or
- s = "0x00100000" or
- s = "0x00200000" or
- s = "0x00400000" or
- s = "0x00800000" or
- s = "0x01000000" or
- s = "0x02000000" or
- s = "0x04000000" or
- s = "0x08000000" or
- s = "0x10000000" or
- s = "0x20000000" or
- s = "0x40000000" or
- s = "0x80000000" or
- // Popular powers of two, minus one (32-bit hex)
- s = "0x00000001" or
- s = "0x00000003" or
- s = "0x00000007" or
- s = "0x0000000f" or
- s = "0x0000001f" or
- s = "0x0000003f" or
- s = "0x0000007f" or
- s = "0x000000ff" or
- s = "0x000001ff" or
- s = "0x000003ff" or
- s = "0x000007ff" or
- s = "0x00000fff" or
- s = "0x00001fff" or
- s = "0x00003fff" or
- s = "0x00007fff" or
- s = "0x0000ffff" or
- s = "0x0001ffff" or
- s = "0x0003ffff" or
- s = "0x0007ffff" or
- s = "0x000fffff" or
- s = "0x001fffff" or
- s = "0x003fffff" or
- s = "0x007fffff" or
- s = "0x00ffffff" or
- s = "0x01ffffff" or
- s = "0x03ffffff" or
- s = "0x07ffffff" or
- s = "0x0fffffff" or
- s = "0x1fffffff" or
- s = "0x3fffffff" or
- s = "0x7fffffff" or
- s = "0xffffffff" or
- // Popular powers of two (16-bit hex)
- s = "0x0001" or
- s = "0x0002" or
- s = "0x0004" or
- s = "0x0008" or
- s = "0x0010" or
- s = "0x0020" or
- s = "0x0040" or
- s = "0x0080" or
- s = "0x0100" or
- s = "0x0200" or
- s = "0x0400" or
- s = "0x0800" or
- s = "0x1000" or
- s = "0x2000" or
- s = "0x4000" or
- s = "0x8000" or
- // Popular powers of two, minus one (16-bit hex)
- s = "0x0001" or
- s = "0x0003" or
- s = "0x0007" or
- s = "0x000f" or
- s = "0x001f" or
- s = "0x003f" or
- s = "0x007f" or
- s = "0x00ff" or
- s = "0x01ff" or
- s = "0x03ff" or
- s = "0x07ff" or
- s = "0x0fff" or
- s = "0x1fff" or
- s = "0x3fff" or
- s = "0x7fff" or
- s = "0xffff" or
- // Popular powers of two (8-bit hex)
- s = "0x01" or
- s = "0x02" or
- s = "0x04" or
- s = "0x08" or
- s = "0x10" or
- s = "0x20" or
- s = "0x40" or
- s = "0x80" or
- // Popular powers of two, minus one (8-bit hex)
- s = "0x01" or
- s = "0x03" or
- s = "0x07" or
- s = "0x0f" or
- s = "0x1f" or
- s = "0x3f" or
- s = "0x7f" or
- s = "0xff" or
- s = "0x00" or
- // Powers of ten
- s = "10" or
- s = "100" or
- s = "1000" or
- s = "10000" or
- s = "100000" or
- s = "1000000" or
- s = "10000000" or
- s = "100000000" or
- s = "1000000000"
+ s =
+ [
+ // Popular powers of two (decimal)
+ "16", "24", "32", "64", "128", "256", "512", "1024", "2048", "4096", "16384", "32768",
+ "65536", "1048576", "2147483648", "4294967296",
+ // Popular powers of two, minus one (decimal)
+ "15", "31", "63", "127", "255", "511", "1023", "2047", "4095", "16383", "32767", "65535",
+ "1048577", "2147483647", "4294967295",
+ // Popular powers of two (32-bit hex)
+ "0x00000001", "0x00000002", "0x00000004", "0x00000008", "0x00000010", "0x00000020",
+ "0x00000040", "0x00000080", "0x00000100", "0x00000200", "0x00000400", "0x00000800",
+ "0x00001000", "0x00002000", "0x00004000", "0x00008000", "0x00010000", "0x00020000",
+ "0x00040000", "0x00080000", "0x00100000", "0x00200000", "0x00400000", "0x00800000",
+ "0x01000000", "0x02000000", "0x04000000", "0x08000000", "0x10000000", "0x20000000",
+ "0x40000000", "0x80000000",
+ // Popular powers of two, minus one (32-bit hex)
+ "0x00000001", "0x00000003", "0x00000007", "0x0000000f", "0x0000001f", "0x0000003f",
+ "0x0000007f", "0x000000ff", "0x000001ff", "0x000003ff", "0x000007ff", "0x00000fff",
+ "0x00001fff", "0x00003fff", "0x00007fff", "0x0000ffff", "0x0001ffff", "0x0003ffff",
+ "0x0007ffff", "0x000fffff", "0x001fffff", "0x003fffff", "0x007fffff", "0x00ffffff",
+ "0x01ffffff", "0x03ffffff", "0x07ffffff", "0x0fffffff", "0x1fffffff", "0x3fffffff",
+ "0x7fffffff", "0xffffffff",
+ // Popular powers of two (16-bit hex)
+ "0x0001", "0x0002", "0x0004", "0x0008", "0x0010", "0x0020", "0x0040", "0x0080", "0x0100",
+ "0x0200", "0x0400", "0x0800", "0x1000", "0x2000", "0x4000", "0x8000",
+ // Popular powers of two, minus one (16-bit hex)
+ "0x0001", "0x0003", "0x0007", "0x000f", "0x001f", "0x003f", "0x007f", "0x00ff", "0x01ff",
+ "0x03ff", "0x07ff", "0x0fff", "0x1fff", "0x3fff", "0x7fff", "0xffff",
+ // Popular powers of two (8-bit hex)
+ "0x01", "0x02", "0x04", "0x08", "0x10", "0x20", "0x40", "0x80",
+ // Popular powers of two, minus one (8-bit hex)
+ "0x01", "0x03", "0x07", "0x0f", "0x1f", "0x3f", "0x7f", "0xff", "0x00",
+ // Powers of ten
+ "10", "100", "1000", "10000", "100000", "1000000", "10000000", "100000000", "1000000000"
+ ]
}
predicate trivialIntValue(string s) {
@@ -235,10 +108,7 @@ predicate joiningStringTrivial(Literal lit) {
// understand (which is against the spirit of these queries).
stringLiteral(lit) and
exists(FunctionCall fc |
- (
- fc.getTarget().getName() = "operator+" or
- fc.getTarget().getName() = "operator<<"
- ) and
+ fc.getTarget().getName() = ["operator+", "operator<<"] and
fc.getAnArgument().getAChild*() = lit
) and
lit.getValue().length() < 16
diff --git a/cpp/ql/src/DefaultOptions.qll b/cpp/ql/src/DefaultOptions.qll
index 3e03ec9ee65..9ed99054098 100644
--- a/cpp/ql/src/DefaultOptions.qll
+++ b/cpp/ql/src/DefaultOptions.qll
@@ -59,14 +59,9 @@ class Options extends string {
predicate exits(Function f) {
f.getAnAttribute().hasName("noreturn")
or
- exists(string name | f.hasGlobalOrStdName(name) |
- name = "exit" or
- name = "_exit" or
- name = "abort" or
- name = "__assert_fail" or
- name = "longjmp" or
- name = "__builtin_unreachable"
- )
+ f.hasGlobalOrStdName([
+ "exit", "_exit", "abort", "__assert_fail", "longjmp", "__builtin_unreachable"
+ ])
or
CustomOptions::exits(f) // old Options.qll
}
diff --git a/cpp/ql/src/JPL_C/LOC-2/Rule 05/HeapMemory.ql b/cpp/ql/src/JPL_C/LOC-2/Rule 05/HeapMemory.ql
index e170c48f4f6..30324d8840d 100644
--- a/cpp/ql/src/JPL_C/LOC-2/Rule 05/HeapMemory.ql
+++ b/cpp/ql/src/JPL_C/LOC-2/Rule 05/HeapMemory.ql
@@ -21,15 +21,7 @@ class Initialization extends Function {
}
class Allocation extends FunctionCall {
- Allocation() {
- exists(string name | name = this.getTarget().getName() |
- name = "malloc" or
- name = "calloc" or
- name = "alloca" or
- name = "sbrk" or
- name = "valloc"
- )
- }
+ Allocation() { this.getTarget().getName() = ["malloc", "calloc", "alloca", "sbrk", "valloc"] }
}
from Function f, Allocation a
diff --git a/cpp/ql/src/JPL_C/LOC-2/Rule 07/ThreadSafety.ql b/cpp/ql/src/JPL_C/LOC-2/Rule 07/ThreadSafety.ql
index 5b9f9cf79d1..4eacedc3339 100644
--- a/cpp/ql/src/JPL_C/LOC-2/Rule 07/ThreadSafety.ql
+++ b/cpp/ql/src/JPL_C/LOC-2/Rule 07/ThreadSafety.ql
@@ -13,13 +13,8 @@ import cpp
class ForbiddenCall extends FunctionCall {
ForbiddenCall() {
- exists(string name | name = this.getTarget().getName() |
- name = "task_delay" or
- name = "taskDelay" or
- name = "sleep" or
- name = "nanosleep" or
- name = "clock_nanosleep"
- )
+ this.getTarget().getName() =
+ ["task_delay", "taskDelay", "sleep", "nanosleep", "clock_nanosleep"]
}
}
diff --git a/cpp/ql/src/JPL_C/LOC-2/Rule 09/Semaphores.qll b/cpp/ql/src/JPL_C/LOC-2/Rule 09/Semaphores.qll
index d662aa85033..0b60a3b9877 100644
--- a/cpp/ql/src/JPL_C/LOC-2/Rule 09/Semaphores.qll
+++ b/cpp/ql/src/JPL_C/LOC-2/Rule 09/Semaphores.qll
@@ -6,12 +6,7 @@ import cpp
class SemaphoreCreation extends FunctionCall {
SemaphoreCreation() {
- exists(string name | name = this.getTarget().getName() |
- name = "semBCreate" or
- name = "semMCreate" or
- name = "semCCreate" or
- name = "semRWCreate"
- )
+ this.getTarget().getName() = ["semBCreate", "semMCreate", "semCCreate", "semRWCreate"]
}
Variable getSemaphore() { result.getAnAccess() = this.getParent().(Assignment).getLValue() }
@@ -72,11 +67,7 @@ class SemaphoreGive extends UnlockOperation {
}
class LockingPrimitive extends FunctionCall, LockOperation {
- LockingPrimitive() {
- exists(string name | name = this.getTarget().getName() |
- name = "taskLock" or name = "intLock" or name = "taskRtpLock"
- )
- }
+ LockingPrimitive() { this.getTarget().getName() = ["taskLock", "intLock", "taskRtpLock"] }
override Function getLocked() { result = this.getTarget() }
@@ -89,11 +80,7 @@ class LockingPrimitive extends FunctionCall, LockOperation {
}
class UnlockingPrimitive extends FunctionCall, UnlockOperation {
- UnlockingPrimitive() {
- exists(string name | name = this.getTarget().getName() |
- name = "taskUnlock" or name = "intUnlock" or name = "taskRtpUnlock"
- )
- }
+ UnlockingPrimitive() { this.getTarget().getName() = ["taskUnlock", "intUnlock", "taskRtpUnlock"] }
Function getLocked() { result = getMatchingLock().getLocked() }
diff --git a/cpp/ql/src/JPL_C/LOC-3/Rule 17/BasicIntTypes.ql b/cpp/ql/src/JPL_C/LOC-3/Rule 17/BasicIntTypes.ql
index 965bb8440b0..82b7f146554 100644
--- a/cpp/ql/src/JPL_C/LOC-3/Rule 17/BasicIntTypes.ql
+++ b/cpp/ql/src/JPL_C/LOC-3/Rule 17/BasicIntTypes.ql
@@ -12,18 +12,7 @@
import cpp
predicate allowedTypedefs(TypedefType t) {
- exists(string name | name = t.getName() |
- name = "I64" or
- name = "U64" or
- name = "I32" or
- name = "U32" or
- name = "I16" or
- name = "U16" or
- name = "I8" or
- name = "U8" or
- name = "F64" or
- name = "F32"
- )
+ t.getName() = ["I64", "U64", "I32", "U32", "I16", "U16", "I8", "U8", "F64", "F32"]
}
/**
diff --git a/cpp/ql/src/JPL_C/Tasks.qll b/cpp/ql/src/JPL_C/Tasks.qll
index 51eb0477c03..19f4b5a9840 100644
--- a/cpp/ql/src/JPL_C/Tasks.qll
+++ b/cpp/ql/src/JPL_C/Tasks.qll
@@ -5,8 +5,8 @@ import cpp
*/
class Task extends Function {
Task() {
- exists(FunctionCall taskCreate, string name | name = "taskCreate" or name = "taskSpawn" |
- name = taskCreate.getTarget().getName() and
+ exists(FunctionCall taskCreate |
+ taskCreate.getTarget().getName() = ["taskCreate", "taskSpawn"] and
this = taskCreate.getArgument(4).(AddressOfExpr).getAddressable()
)
}
diff --git a/cpp/ql/src/Likely Bugs/Conversion/LossyFunctionResultCast.ql b/cpp/ql/src/Likely Bugs/Conversion/LossyFunctionResultCast.ql
index 27f2000692c..2a8aba6a961 100644
--- a/cpp/ql/src/Likely Bugs/Conversion/LossyFunctionResultCast.ql
+++ b/cpp/ql/src/Likely Bugs/Conversion/LossyFunctionResultCast.ql
@@ -13,38 +13,17 @@ import cpp
import semmle.code.cpp.dataflow.DataFlow
predicate whitelist(Function f) {
- exists(string fName |
- fName = f.getName() and
- (
- fName = "ceil" or
- fName = "ceilf" or
- fName = "ceill" or
- fName = "floor" or
- fName = "floorf" or
- fName = "floorl" or
- fName = "nearbyint" or
- fName = "nearbyintf" or
- fName = "nearbyintl" or
- fName = "rint" or
- fName = "rintf" or
- fName = "rintl" or
- fName = "round" or
- fName = "roundf" or
- fName = "roundl" or
- fName = "trunc" or
- fName = "truncf" or
- fName = "truncl" or
- fName.matches("__builtin_%")
- )
- )
+ f.getName() =
+ [
+ "ceil", "ceilf", "ceill", "floor", "floorf", "floorl", "nearbyint", "nearbyintf",
+ "nearbyintl", "rint", "rintf", "rintl", "round", "roundf", "roundl", "trunc", "truncf",
+ "truncl"
+ ] or
+ f.getName().matches("__builtin_%")
}
predicate whitelistPow(FunctionCall fc) {
- (
- fc.getTarget().getName() = "pow" or
- fc.getTarget().getName() = "powf" or
- fc.getTarget().getName() = "powl"
- ) and
+ fc.getTarget().getName() = ["pow", "powf", "powl"] and
exists(float value |
value = fc.getArgument(0).getValue().toFloat() and
(value.floor() - value).abs() < 0.001
diff --git a/cpp/ql/src/Security/CWE/CWE-020/ExternalAPIsSpecific.qll b/cpp/ql/src/Security/CWE/CWE-020/ExternalAPIsSpecific.qll
index 788baeddbff..9ca598f86d6 100644
--- a/cpp/ql/src/Security/CWE/CWE-020/ExternalAPIsSpecific.qll
+++ b/cpp/ql/src/Security/CWE/CWE-020/ExternalAPIsSpecific.qll
@@ -46,7 +46,7 @@ class UntrustedDataToExternalAPIConfig extends TaintTracking::Configuration {
UntrustedDataToExternalAPIConfig() { this = "UntrustedDataToExternalAPIConfig" }
override predicate isSource(DataFlow::Node source) {
- exists(RemoteFlowFunction remoteFlow |
+ exists(RemoteFlowSourceFunction remoteFlow |
remoteFlow = source.asExpr().(Call).getTarget() and
remoteFlow.hasRemoteFlowSource(_, _)
)
diff --git a/cpp/ql/src/Security/CWE/CWE-079/CgiXss.ql b/cpp/ql/src/Security/CWE/CWE-079/CgiXss.ql
index 0e0c4add7f6..24484a9dcaf 100644
--- a/cpp/ql/src/Security/CWE/CWE-079/CgiXss.ql
+++ b/cpp/ql/src/Security/CWE/CWE-079/CgiXss.ql
@@ -29,6 +29,8 @@ class QueryString extends EnvironmentRead {
}
class Configuration extends TaintTrackingConfiguration {
+ override predicate isSource(Expr source) { source instanceof QueryString }
+
override predicate isSink(Element tainted) {
exists(PrintStdoutCall call | call.getAnArgument() = tainted)
}
diff --git a/cpp/ql/src/Security/CWE/CWE-089/SqlTainted.ql b/cpp/ql/src/Security/CWE/CWE-089/SqlTainted.ql
index de786d22f30..5ed84f45250 100644
--- a/cpp/ql/src/Security/CWE/CWE-089/SqlTainted.ql
+++ b/cpp/ql/src/Security/CWE/CWE-089/SqlTainted.ql
@@ -27,6 +27,10 @@ class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) {
exists(SQLLikeFunction runSql | runSql.outermostWrapperFunctionCall(tainted, _))
}
+
+ override predicate isBarrier(Expr e) {
+ super.isBarrier(e) or e.getUnspecifiedType() instanceof IntegralType
+ }
}
from
diff --git a/cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.c b/cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.c
new file mode 100644
index 00000000000..d2f2b76fddc
--- /dev/null
+++ b/cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.c
@@ -0,0 +1,5 @@
+unsigned limit = get_limit();
+unsigned total = 0;
+while (limit - total > 0) { // wrong: if `total` is greater than `limit` this will underflow and continue executing the loop.
+ total += get_data();
+}
\ No newline at end of file
diff --git a/cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.qhelp b/cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.qhelp
new file mode 100644
index 00000000000..965429bda8a
--- /dev/null
+++ b/cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.qhelp
@@ -0,0 +1,31 @@
+
+
+
+
+This rule finds relational comparisons between the result of an unsigned subtraction and the value 0.
+Such comparisons are likely to be wrong as the value of an unsigned subtraction can never be negative. So the
+relational comparison ends up checking whether the result of the subtraction is equal to 0.
+This is probably not what the programmer intended.
+
+
+
+
+
If a relational comparison is intended, consider casting the result of the subtraction to a signed type.
+ If the intention was to test for equality, consider replacing the relational comparison with an equality test.
+
+
+
+
diff --git a/cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql b/cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql
new file mode 100644
index 00000000000..007a4fd746d
--- /dev/null
+++ b/cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql
@@ -0,0 +1,49 @@
+/**
+ * @name Unsigned difference expression compared to zero
+ * @description A subtraction with an unsigned result can never be negative. Using such an expression in a relational comparison with `0` is likely to be wrong.
+ * @kind problem
+ * @id cpp/unsigned-difference-expression-compared-zero
+ * @problem.severity warning
+ * @precision medium
+ * @tags security
+ * correctness
+ * external/cwe/cwe-191
+ */
+
+import cpp
+import semmle.code.cpp.commons.Exclusions
+import semmle.code.cpp.valuenumbering.GlobalValueNumbering
+import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
+import semmle.code.cpp.controlflow.Guards
+
+/** Holds if `sub` is guarded by a condition which ensures that `left >= right`. */
+pragma[noinline]
+predicate isGuarded(SubExpr sub, Expr left, Expr right) {
+ exists(GuardCondition guard |
+ guard.controls(sub.getBasicBlock(), true) and
+ guard.ensuresLt(left, right, 0, sub.getBasicBlock(), false)
+ )
+}
+
+/** Holds if `sub` will never be negative. */
+predicate nonNegative(SubExpr sub) {
+ not exprMightOverflowNegatively(sub.getFullyConverted())
+ or
+ // The subtraction is guarded by a check of the form `left >= right`.
+ exists(GVN left, GVN right |
+ // This is basically a poor man's version of a directional unbind operator.
+ strictcount([left, globalValueNumber(sub.getLeftOperand())]) = 1 and
+ strictcount([right, globalValueNumber(sub.getRightOperand())]) = 1 and
+ isGuarded(sub, left.getAnExpr(), right.getAnExpr())
+ )
+}
+
+from RelationalOperation ro, SubExpr sub
+where
+ not isFromMacroDefinition(ro) and
+ not isFromMacroDefinition(sub) and
+ ro.getLesserOperand().getValue().toInt() = 0 and
+ ro.getGreaterOperand() = sub and
+ sub.getFullyConverted().getUnspecifiedType().(IntegralType).isUnsigned() and
+ not nonNegative(sub)
+select ro, "Unsigned subtraction can never be negative."
diff --git a/cpp/ql/src/Security/CWE/CWE-313/CleartextSqliteDatabase.ql b/cpp/ql/src/Security/CWE/CWE-313/CleartextSqliteDatabase.ql
index 000833cbb58..fb5454d523e 100644
--- a/cpp/ql/src/Security/CWE/CWE-313/CleartextSqliteDatabase.ql
+++ b/cpp/ql/src/Security/CWE/CWE-313/CleartextSqliteDatabase.ql
@@ -34,6 +34,10 @@ predicate sqlite_encryption_used() {
}
class Configuration extends TaintTrackingConfiguration {
+ override predicate isSource(Expr source) {
+ super.isSource(source) and source instanceof SensitiveExpr
+ }
+
override predicate isSink(Element taintedArg) {
exists(SqliteFunctionCall sqliteCall |
taintedArg = sqliteCall.getASource() and
diff --git a/cpp/ql/src/Security/CWE/CWE-457/InitializationFunctions.qll b/cpp/ql/src/Security/CWE/CWE-457/InitializationFunctions.qll
index b77ff068150..4739c7ad5cf 100644
--- a/cpp/ql/src/Security/CWE/CWE-457/InitializationFunctions.qll
+++ b/cpp/ql/src/Security/CWE/CWE-457/InitializationFunctions.qll
@@ -326,52 +326,37 @@ class InitializationFunction extends Function {
// Return value is not a success code but the output functions never fail.
name.matches("_Interlocked%")
or
- // Functions that never fail, according to MSDN.
- name = "QueryPerformanceCounter"
- or
- name = "QueryPerformanceFrequency"
- or
- // Functions that never fail post-Vista, according to MSDN.
- name = "InitializeCriticalSectionAndSpinCount"
- or
- // `rand_s` writes 0 to a non-null argument if it fails, according to MSDN.
- name = "rand_s"
- or
- // IntersectRect initializes the argument regardless of whether the input intersects
- name = "IntersectRect"
- or
- name = "SetRect"
- or
- name = "UnionRect"
- or
- // These functions appears to have an incorrect CFG, which leads to false positives
- name = "PhysicalToLogicalDPIPoint"
- or
- name = "LogicalToPhysicalDPIPoint"
- or
- // Sets NtProductType to default on error
- name = "RtlGetNtProductType"
- or
- // Our CFG is not sophisticated enough to detect that the argument is always initialized
- name = "StringCchLengthA"
- or
- // All paths init the argument, and always returns SUCCESS.
- name = "RtlUnicodeToMultiByteSize"
- or
- // All paths init the argument, and always returns SUCCESS.
- name = "RtlMultiByteToUnicodeSize"
- or
- // All paths init the argument, and always returns SUCCESS.
- name = "RtlUnicodeToMultiByteN"
- or
- // Always initializes argument
- name = "RtlGetFirstRange"
- or
- // Destination range is zeroed out on failure, assuming first two parameters are valid
- name = "memcpy_s"
- or
- // This zeroes the memory unconditionally
- name = "SeCreateAccessState"
+ name =
+ [
+ // Functions that never fail, according to MSDN.
+ "QueryPerformanceCounter", "QueryPerformanceFrequency",
+ // Functions that never fail post-Vista, according to MSDN.
+ "InitializeCriticalSectionAndSpinCount",
+ // `rand_s` writes 0 to a non-null argument if it fails, according to MSDN.
+ "rand_s",
+ // IntersectRect initializes the argument regardless of whether the input intersects
+ "IntersectRect", "SetRect", "UnionRect",
+ // These functions appears to have an incorrect CFG, which leads to false positives
+ "PhysicalToLogicalDPIPoint", "LogicalToPhysicalDPIPoint",
+ // Sets NtProductType to default on error
+ "RtlGetNtProductType",
+ // Our CFG is not sophisticated enough to detect that the argument is always initialized
+ "StringCchLengthA",
+ // All paths init the argument, and always returns SUCCESS.
+ "RtlUnicodeToMultiByteSize",
+ // All paths init the argument, and always returns SUCCESS.
+ "RtlMultiByteToUnicodeSize",
+ // All paths init the argument, and always returns SUCCESS.
+ "RtlUnicodeToMultiByteN",
+ // Always initializes argument
+ "RtlGetFirstRange",
+ // Destination range is zeroed out on failure, assuming first two parameters are valid
+ "memcpy_s",
+ // This zeroes the memory unconditionally
+ "SeCreateAccessState",
+ // Argument initialization is optional, but always succeeds
+ "KeGetCurrentProcessorNumberEx"
+ ]
)
}
}
diff --git a/cpp/ql/src/Security/CWE/CWE-732/FilePermissions.qll b/cpp/ql/src/Security/CWE/CWE-732/FilePermissions.qll
index d62f3e6a6da..ad5ed29098c 100644
--- a/cpp/ql/src/Security/CWE/CWE-732/FilePermissions.qll
+++ b/cpp/ql/src/Security/CWE/CWE-732/FilePermissions.qll
@@ -140,12 +140,9 @@ class FopenCreationExpr extends FileCreationExpr {
class FopensCreationExpr extends FileCreationExpr {
FopensCreationExpr() {
- exists(string name | name = this.getTarget().getName() |
- name = "fopen_s" or
- name = "_wfopen_s"
- ) and
+ this.getTarget().getName() = ["fopen_s", "_wfopen_s"] and
exists(string mode |
- (mode = "w" or mode = "a") and
+ mode = ["w", "a"] and
this.getArgument(2).getValue().matches(mode + "%")
)
}
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-120/MemoryUnsafeFunctionScan.cpp b/cpp/ql/src/experimental/Security/CWE/CWE-120/MemoryUnsafeFunctionScan.cpp
index 001f4da6028..e7394ffefc7 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-120/MemoryUnsafeFunctionScan.cpp
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-120/MemoryUnsafeFunctionScan.cpp
@@ -13,9 +13,10 @@ int main(int argc, char **argv)
char buf1[10];
scanf("%s", buf1);
- // GOOD, length is specified. The length should be one less than the size of the buffer, since the last character is the NULL terminator.
- char buf2[10];
- sscanf(buf2, "%9s");
+ // GOOD, length is specified. The length should be one less than the size of the destination buffer, since the last character is the NULL terminator.
+ char buf2[20];
+ char buf3[10];
+ sscanf(buf2, "%9s", buf3);
// BAD, do not use scanf without specifying a length first
char file[10];
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-14/CompilerRemovalOfCodeToClearBuffers.c b/cpp/ql/src/experimental/Security/CWE/CWE-14/CompilerRemovalOfCodeToClearBuffers.c
new file mode 100644
index 00000000000..14dd07a573e
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-14/CompilerRemovalOfCodeToClearBuffers.c
@@ -0,0 +1,35 @@
+// BAD: the memset call will probably be removed.
+void getPassword(void) {
+ char pwd[64];
+ if (GetPassword(pwd, sizeof(pwd))) {
+ /* Checking of password, secure operations, etc. */
+ }
+ memset(pwd, 0, sizeof(pwd));
+}
+// GOOD: in this case the memset will not be removed.
+void getPassword(void) {
+ char pwd[64];
+
+ if (retrievePassword(pwd, sizeof(pwd))) {
+ /* Checking of password, secure operations, etc. */
+ }
+ memset_s(pwd, 0, sizeof(pwd));
+}
+// GOOD: in this case the memset will not be removed.
+void getPassword(void) {
+ char pwd[64];
+ if (retrievePassword(pwd, sizeof(pwd))) {
+ /* Checking of password, secure operations, etc. */
+ }
+ SecureZeroMemory(pwd, sizeof(pwd));
+}
+// GOOD: in this case the memset will not be removed.
+void getPassword(void) {
+ char pwd[64];
+ if (retrievePassword(pwd, sizeof(pwd))) {
+ /* Checking of password, secure operations, etc. */
+ }
+#pragma optimize("", off)
+ memset(pwd, 0, sizeof(pwd));
+#pragma optimize("", on)
+}
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-14/CompilerRemovalOfCodeToClearBuffers.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-14/CompilerRemovalOfCodeToClearBuffers.qhelp
new file mode 100644
index 00000000000..df0ed151d8f
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-14/CompilerRemovalOfCodeToClearBuffers.qhelp
@@ -0,0 +1,31 @@
+
+
+
+
Compiler optimization will exclude the cleaning of private information.
+Using the memset function to clear private data in a variable that has no subsequent use is potentially dangerous, since the compiler can remove the call.
+For some compilers, optimization is also possible when using calls to free memory after the memset function.
+
+
It is possible to miss detection of vulnerabilities if used to clear fields of structures or parts of a buffer.
+
+
+
+
+
We recommend to use the RtlSecureZeroMemory or memset_s functions, or compilation flags that exclude optimization of memset calls (e.g. -fno-builtin-memset).
+
+
+
+
The following example demonstrates an erroneous and corrected use of the memset function.
+
+
+
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-14/CompilerRemovalOfCodeToClearBuffers.ql b/cpp/ql/src/experimental/Security/CWE/CWE-14/CompilerRemovalOfCodeToClearBuffers.ql
new file mode 100644
index 00000000000..db09de9430d
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-14/CompilerRemovalOfCodeToClearBuffers.ql
@@ -0,0 +1,127 @@
+/**
+ * @name Compiler Removal Of Code To Clear Buffers
+ * @description Using memset the function to clear private data in a variable that has no subsequent use
+ * is potentially dangerous because the compiler can remove the call.
+ * @kind problem
+ * @id cpp/compiler-removal-of-code-to-clear-buffers
+ * @problem.severity warning
+ * @precision medium
+ * @tags security
+ * external/cwe/cwe-14
+ */
+
+import cpp
+import semmle.code.cpp.dataflow.DataFlow
+import semmle.code.cpp.dataflow.StackAddress
+
+/**
+ * A call to `memset` of the form `memset(ptr, value, num)`, for some local variable `ptr`.
+ */
+class CompilerRemovaMemset extends FunctionCall {
+ CompilerRemovaMemset() {
+ this.getTarget().hasGlobalOrStdName("memset") and
+ exists(DataFlow::Node source, DataFlow::Node sink, LocalVariable isv, Expr exp |
+ DataFlow::localFlow(source, sink) and
+ this.getArgument(0) = isv.getAnAccess() and
+ (
+ source.asExpr() = exp
+ or
+ // handle the case where exp is defined by an address being passed into some function.
+ source.asDefiningArgument() = exp
+ ) and
+ exp.getLocation().getEndLine() < this.getArgument(0).getLocation().getStartLine() and
+ sink.asExpr() = this.getArgument(0)
+ )
+ }
+
+ predicate isExistsAllocForThisVariable() {
+ exists(AllocationExpr alloc, Variable v |
+ alloc = v.getAnAssignedValue() and
+ this.getArgument(0) = v.getAnAccess() and
+ alloc.getASuccessor+() = this
+ )
+ or
+ not stackPointerFlowsToUse(this.getArgument(0), _, _, _)
+ }
+
+ predicate isExistsFreeForThisVariable() {
+ exists(DeallocationExpr free, Variable v |
+ this.getArgument(0) = v.getAnAccess() and
+ free.getFreedExpr() = v.getAnAccess() and
+ this.getASuccessor+() = free
+ )
+ }
+
+ predicate isExistsCallWithThisVariableExcludingDeallocationCalls() {
+ exists(FunctionCall fc, Variable v |
+ not fc instanceof DeallocationExpr and
+ this.getArgument(0) = v.getAnAccess() and
+ fc.getAnArgument() = v.getAnAccess() and
+ this.getASuccessor+() = fc
+ )
+ }
+
+ predicate isVariableUseAfterMemsetExcludingCalls() {
+ exists(DataFlow::Node source, DataFlow::Node sink, LocalVariable isv, Expr exp |
+ DataFlow::localFlow(source, sink) and
+ this.getArgument(0) = isv.getAnAccess() and
+ source.asExpr() = isv.getAnAccess() and
+ exp.getLocation().getStartLine() > this.getArgument(2).getLocation().getEndLine() and
+ not exp.getParent() instanceof FunctionCall and
+ sink.asExpr() = exp
+ )
+ }
+
+ predicate isVariableUseBoundWithArgumentFunction() {
+ exists(DataFlow::Node source, DataFlow::Node sink, LocalVariable isv, Parameter p, Expr exp |
+ DataFlow::localFlow(source, sink) and
+ this.getArgument(0) = isv.getAnAccess() and
+ this.getEnclosingFunction().getAParameter() = p and
+ exp.getAChild*() = p.getAnAccess() and
+ source.asExpr() = exp and
+ sink.asExpr() = isv.getAnAccess()
+ )
+ }
+
+ predicate isVariableUseBoundWithGlobalVariable() {
+ exists(
+ DataFlow::Node source, DataFlow::Node sink, LocalVariable isv, GlobalVariable gv, Expr exp
+ |
+ DataFlow::localFlow(source, sink) and
+ this.getArgument(0) = isv.getAnAccess() and
+ exp.getAChild*() = gv.getAnAccess() and
+ source.asExpr() = exp and
+ sink.asExpr() = isv.getAnAccess()
+ )
+ }
+
+ predicate isExistsCompilationFlagsBlockingRemoval() {
+ exists(Compilation c |
+ c.getAFileCompiled() = this.getFile() and
+ c.getAnArgument() = "-fno-builtin-memset"
+ )
+ }
+
+ predicate isUseVCCompilation() {
+ exists(Compilation c |
+ c.getAFileCompiled() = this.getFile() and
+ (
+ c.getArgument(2).matches("%gcc%") or
+ c.getArgument(2).matches("%g++%") or
+ c.getArgument(2).matches("%clang%") or
+ c.getArgument(2) = "--force-recompute"
+ )
+ )
+ }
+}
+
+from CompilerRemovaMemset fc
+where
+ not (fc.isExistsAllocForThisVariable() and not fc.isExistsFreeForThisVariable()) and
+ not (fc.isExistsFreeForThisVariable() and not fc.isUseVCCompilation()) and
+ not fc.isVariableUseAfterMemsetExcludingCalls() and
+ not fc.isExistsCallWithThisVariableExcludingDeallocationCalls() and
+ not fc.isVariableUseBoundWithArgumentFunction() and
+ not fc.isVariableUseBoundWithGlobalVariable() and
+ not fc.isExistsCompilationFlagsBlockingRemoval()
+select fc.getArgument(0), "This variable will not be cleared."
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-190/AllocMultiplicationOverflow.cpp b/cpp/ql/src/experimental/Security/CWE/CWE-190/AllocMultiplicationOverflow.cpp
new file mode 100644
index 00000000000..bde49c946ea
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-190/AllocMultiplicationOverflow.cpp
@@ -0,0 +1,19 @@
+
+image::image(int width, int height)
+{
+ int x, y;
+
+ // allocate width * height pixels
+ pixels = new uint32_t[width * height];
+
+ // fill width * height pixels
+ for (y = 0; y < height; y++)
+ {
+ for (x = 0; x < width; x++)
+ {
+ pixels[(y * width) + height] = 0;
+ }
+ }
+
+ // ...
+}
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-190/AllocMultiplicationOverflow.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-190/AllocMultiplicationOverflow.qhelp
new file mode 100644
index 00000000000..97bd393d5a0
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-190/AllocMultiplicationOverflow.qhelp
@@ -0,0 +1,25 @@
+
+
+
+
+
The result of a multiplication is used in the size of an allocation. If the multiplication can be made to overflow, a much smaller amount of memory may be allocated than the rest of the code expects. This may lead to overflowing writes when the buffer is accessed later.
+
+
+
+
To fix this issue, ensure that the arithmetic used in the size of an allocation cannot overflow before memory is allocated.
+
+
+
+
In the following example, an array of size width * height is allocated and stored as pixels. If width and height are set such that the multiplication overflows and wraps to a small value (say, 4) then the initialization code that follows the allocation will write beyond the end of the array.
+
+
+
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-190/AllocMultiplicationOverflow.ql b/cpp/ql/src/experimental/Security/CWE/CWE-190/AllocMultiplicationOverflow.ql
new file mode 100644
index 00000000000..3a253854679
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-190/AllocMultiplicationOverflow.ql
@@ -0,0 +1,40 @@
+/**
+ * @name Multiplication result may overflow and be used in allocation
+ * @description Using a multiplication result that may overflow in the size of an allocation may lead to buffer overflows when the allocated memory is used.
+ * @kind path-problem
+ * @problem.severity warning
+ * @precision low
+ * @tags security
+ * correctness
+ * external/cwe/cwe-190
+ * external/cwe/cwe-128
+ * @id cpp/multiplication-overflow-in-alloc
+ */
+
+import cpp
+import semmle.code.cpp.models.interfaces.Allocation
+import semmle.code.cpp.dataflow.DataFlow
+import DataFlow::PathGraph
+
+class MultToAllocConfig extends DataFlow::Configuration {
+ MultToAllocConfig() { this = "MultToAllocConfig" }
+
+ override predicate isSource(DataFlow::Node node) {
+ // a multiplication of two non-constant expressions
+ exists(MulExpr me |
+ me = node.asExpr() and
+ forall(Expr e | e = me.getAnOperand() | not exists(e.getValue()))
+ )
+ }
+
+ override predicate isSink(DataFlow::Node node) {
+ // something that affects an allocation size
+ node.asExpr() = any(AllocationExpr ae).getSizeExpr().getAChild*()
+ }
+}
+
+from MultToAllocConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
+where config.hasFlowPath(source, sink)
+select sink, source, sink,
+ "Potentially overflowing value from $@ is used in the size of this allocation.", source,
+ "multiplication"
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.c b/cpp/ql/src/experimental/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.c
deleted file mode 100644
index f6054226848..00000000000
--- a/cpp/ql/src/experimental/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.c
+++ /dev/null
@@ -1,11 +0,0 @@
-unsigned long sizeArray;
-
-// BAD: let's consider several values, taking ULONG_MAX =18446744073709551615
-// sizeArray = 60; (sizeArray - 10) = 50; true
-// sizeArray = 10; (sizeArray - 10) = 0; false
-// sizeArray = 1; (sizeArray - 10) = 18446744073709551607; true
-// sizeArray = 0; (sizeArray - 10) = 18446744073709551606; true
-if (sizeArray - 10 > 0)
-
-// GOOD: Prevent overflow by checking the input
-if (sizeArray > 10)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.qhelp
deleted file mode 100644
index 3bf28d13df4..00000000000
--- a/cpp/ql/src/experimental/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.qhelp
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
The code compares the unsigned difference with zero.
-It is highly probable that the condition is wrong if the difference expression has the unsigned type.
-The condition holds in all the cases when difference is not equal to zero.
-It means that we may use condition not equal. But the programmer probably wanted to compare the difference of elements.
-
-
False positives include code in which the first difference element is always greater than or equal to the second.
-For comparison, ">" such conditions are equivalent to "! =", And are recommended for replacement.
-For comparison "> =", the conditions are always true and are recommended to be excluded.
-
-
-
-
-
Use a simple comparison of two elements, instead of comparing their difference to zero.
-
-
-
-
The following example demonstrates an erroneous and corrected use of comparison.
-
-
-
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql b/cpp/ql/src/experimental/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql
deleted file mode 100644
index 300b2f944b5..00000000000
--- a/cpp/ql/src/experimental/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * @name Unsigned difference expression compared to zero
- * @description It is highly probable that the condition is wrong if the difference expression has the unsigned type.
- * The condition holds in all the cases when difference is not equal to zero. It means that we may use condition not equal.
- * But the programmer probably wanted to compare the difference of elements.
- * @kind problem
- * @id cpp/unsigned-difference-expression-compared-zero
- * @problem.severity warning
- * @precision medium
- * @tags security
- * external/cwe/cwe-191
- */
-
-import cpp
-import semmle.code.cpp.commons.Exclusions
-
-from RelationalOperation ro, SubExpr sub
-where
- not isFromMacroDefinition(ro) and
- ro.getLesserOperand().getValue().toInt() = 0 and
- ro.getGreaterOperand() = sub and
- sub.getFullyConverted().getUnspecifiedType().(IntegralType).isUnsigned()
-select ro, "Difference in condition is always greater than or equal to zero"
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-359/PrivateCleartextWrite.ql b/cpp/ql/src/experimental/Security/CWE/CWE-359/PrivateCleartextWrite.ql
index 60b13525aff..205f17c06c9 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-359/PrivateCleartextWrite.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-359/PrivateCleartextWrite.ql
@@ -16,6 +16,6 @@ import DataFlow::PathGraph
from WriteConfig b, DataFlow::PathNode source, DataFlow::PathNode sink
where b.hasFlowPath(source, sink)
-select sink.getNode(),
- "This write into the external location '" + sink + "' may contain unencrypted data from $@",
- source, "this source."
+select sink.getNode(), source, sink,
+ "This write into the external location '" + sink.getNode() +
+ "' may contain unencrypted data from $@", source, "this source."
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-401/MemoryLeakOnFailedCallToRealloc.c b/cpp/ql/src/experimental/Security/CWE/CWE-401/MemoryLeakOnFailedCallToRealloc.c
new file mode 100644
index 00000000000..dc5565f3471
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-401/MemoryLeakOnFailedCallToRealloc.c
@@ -0,0 +1,20 @@
+// BAD: on unsuccessful call to realloc, we will lose a pointer to a valid memory block
+if (currentSize < newSize)
+{
+ buffer = (unsigned char *)realloc(buffer, newSize);
+}
+
+
+
+// GOOD: this way we will exclude possible memory leak
+unsigned char * tmp;
+if (currentSize < newSize)
+{
+ tmp = (unsigned char *)realloc(buffer, newSize);
+}
+if (tmp == NULL)
+{
+ free(buffer);
+}
+else
+ buffer = tmp;
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-401/MemoryLeakOnFailedCallToRealloc.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-401/MemoryLeakOnFailedCallToRealloc.qhelp
new file mode 100644
index 00000000000..14dcff74a25
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-401/MemoryLeakOnFailedCallToRealloc.qhelp
@@ -0,0 +1,38 @@
+
+
+
+
Memory leak on failed call to realloc.
+The expression mem = realloc (mem, size) is potentially dangerous, if the call fails, we will lose the pointer to the memory block.
+An unsuccessful call is possible not only when trying to allocate a large amount of memory, but also when the process memory is strongly segmented.
+
+
False positives include code in which immediately after calling the realloc function, the pointer is manipulated without first checking for validity.
+In this case, an exception will occur in the program and it will terminate.
+But from the point of view of safe coding, these places require the attention of developers.
+At this stage, false positives are also possible in situations where the exception handling is quite complicated and occurs outside the base block in which memory is redistributed.
+
+
+
+
+
We recommend storing the result in a temporary variable and eliminating memory leak.
+
+
+
+
The following example demonstrates an erroneous and corrected use of the realloc function.
+
+
+
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-401/MemoryLeakOnFailedCallToRealloc.ql b/cpp/ql/src/experimental/Security/CWE/CWE-401/MemoryLeakOnFailedCallToRealloc.ql
new file mode 100644
index 00000000000..cf0afc64013
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-401/MemoryLeakOnFailedCallToRealloc.ql
@@ -0,0 +1,61 @@
+/**
+ * @name Memory leak on failed call to realloc
+ * @description The expression mem = realloc (mem, size) is potentially dangerous, if the call fails, we will lose the pointer to the memory block.
+ * We recommend storing the result in a temporary variable and eliminating memory leak.
+ * @kind problem
+ * @id cpp/memory-leak-on-failed-call-to-realloc
+ * @problem.severity warning
+ * @precision medium
+ * @tags correctness
+ * security
+ * external/cwe/cwe-401
+ */
+
+import cpp
+import semmle.code.cpp.controlflow.Guards
+import semmle.code.cpp.valuenumbering.HashCons
+
+/**
+ * A function call that potentially does not return (such as `exit`).
+ */
+class CallMayNotReturn extends FunctionCall {
+ CallMayNotReturn() {
+ // call that is known to not return
+ not exists(this.(ControlFlowNode).getASuccessor())
+ or
+ // call to another function that may not return
+ exists(CallMayNotReturn exit | getTarget() = exit.getEnclosingFunction())
+ }
+}
+
+/**
+ * A call to `realloc` of the form `v = realloc(v, size)`, for some variable `v`.
+ */
+class ReallocCallLeak extends FunctionCall {
+ Variable v;
+
+ ReallocCallLeak() {
+ exists(AssignExpr ex |
+ this.getTarget().hasGlobalOrStdName("realloc") and
+ this = ex.getRValue() and
+ hashCons(ex.getLValue()) = hashCons(this.getArgument(0)) and
+ v.getAnAccess() = this.getArgument(0)
+ )
+ }
+
+ /**
+ * Holds if failure of this allocation may be handled by termination, for
+ * example a call to `exit()`.
+ */
+ predicate mayHandleByTermination() {
+ exists(GuardCondition guard, CallMayNotReturn exit |
+ this.(ControlFlowNode).getASuccessor*() = guard and
+ guard.getAChild*() = v.getAnAccess() and
+ guard.controls(exit.getBasicBlock(), _)
+ )
+ }
+}
+
+from ReallocCallLeak rcl
+where not rcl.mayHandleByTermination()
+select rcl, "possible loss of original pointer on unsuccessful call realloc"
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp
new file mode 100644
index 00000000000..df69886e97b
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp
@@ -0,0 +1,35 @@
+// BAD: on memory allocation error, the program terminates.
+void badFunction(const int *source, std::size_t length) noexcept {
+ int * dest = new int[length];
+ std::memset(dest, 0, length);
+// ..
+}
+// GOOD: memory allocation error will be handled.
+void goodFunction(const int *source, std::size_t length) noexcept {
+ try {
+ int * dest = new int[length];
+ } catch(std::bad_alloc) {
+ // ...
+ }
+ std::memset(dest, 0, length);
+// ..
+}
+// BAD: memory allocation error will not be handled.
+void badFunction(const int *source, std::size_t length) noexcept {
+ try {
+ int * dest = new (std::nothrow) int[length];
+ } catch(std::bad_alloc) {
+ // ...
+ }
+ std::memset(dest, 0, length);
+// ..
+}
+// GOOD: memory allocation error will be handled.
+void goodFunction(const int *source, std::size_t length) noexcept {
+ int * dest = new (std::nothrow) int[length];
+ if (!dest) {
+ return;
+ }
+ std::memset(dest, 0, length);
+// ..
+}
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.qhelp
new file mode 100644
index 00000000000..9e6cb2d89ce
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.qhelp
@@ -0,0 +1,27 @@
+
+
+
+
When using the new operator to allocate memory, you need to pay attention to the different ways of detecting errors. ::operator new(std::size_t) throws an exception on error, whereas ::operator new(std::size_t, const std::nothrow_t &) returns zero on error. The programmer can get confused and check the error that occurs when allocating memory incorrectly. That can lead to an unhandled program termination or to a violation of the program logic.
+
+
+
+
+
Use the correct error detection method corresponding with the memory allocation.
+
+
+
+
The following example demonstrates various approaches to detecting memory allocation errors using the new operator.
+
+
+
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
new file mode 100644
index 00000000000..dd9c16fac11
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
@@ -0,0 +1,87 @@
+/**
+ * @name Detect And Handle Memory Allocation Errors
+ * @description --::operator new(std::size_t) throws an exception on error, and ::operator new(std::size_t, const std::nothrow_t &) returns zero on error.
+ * --the programmer can get confused when check the error that occurs when allocating memory incorrectly.
+ * @kind problem
+ * @id cpp/detect-and-handle-memory-allocation-errors
+ * @problem.severity warning
+ * @precision medium
+ * @tags correctness
+ * security
+ * external/cwe/cwe-570
+ */
+
+import cpp
+
+/**
+ * Lookup if condition compare with 0
+ */
+class IfCompareWithZero extends IfStmt {
+ IfCompareWithZero() {
+ this.getCondition().(EQExpr).getAChild().getValue() = "0"
+ or
+ this.getCondition().(NEExpr).getAChild().getValue() = "0" and
+ this.hasElse()
+ or
+ this.getCondition().(NEExpr).getAChild().getValue() = "0" and
+ this.getThen().getAChild*() instanceof ReturnStmt
+ }
+}
+
+/**
+ * lookup for calls to `operator new`, with incorrect error handling.
+ */
+class WrongCheckErrorOperatorNew extends FunctionCall {
+ Expr exp;
+
+ WrongCheckErrorOperatorNew() {
+ this = exp.(NewOrNewArrayExpr).getAChild().(FunctionCall) and
+ (
+ this.getTarget().hasGlobalOrStdName("operator new")
+ or
+ this.getTarget().hasGlobalOrStdName("operator new[]")
+ )
+ }
+
+ /**
+ * Holds if handler `try ... catch` exists.
+ */
+ predicate isExistsTryCatchBlock() {
+ exists(TryStmt ts | this.getEnclosingStmt() = ts.getStmt().getAChild*())
+ }
+
+ /**
+ * Holds if results call `operator new` check in `operator if`.
+ */
+ predicate isExistsIfCondition() {
+ exists(IfCompareWithZero ifc, AssignExpr aex, Initializer it |
+ // call `operator new` directly from the condition of `operator if`.
+ this = ifc.getCondition().getAChild*()
+ or
+ // check results call `operator new` with variable appropriation
+ postDominates(ifc, this) and
+ aex.getAChild() = exp and
+ ifc.getCondition().getAChild().(VariableAccess).getTarget() =
+ aex.getLValue().(VariableAccess).getTarget()
+ or
+ // check results call `operator new` with declaration variable
+ postDominates(ifc, this) and
+ exp = it.getExpr() and
+ it.getDeclaration() = ifc.getCondition().getAChild().(VariableAccess).getTarget()
+ )
+ }
+
+ /**
+ * Holds if `(std::nothrow)` exists in call `operator new`.
+ */
+ predicate isExistsNothrow() { this.getAChild().toString() = "nothrow" }
+}
+
+from WrongCheckErrorOperatorNew op
+where
+ // use call `operator new` with `(std::nothrow)` and checking error using `try ... catch` block and not `operator if`
+ op.isExistsNothrow() and not op.isExistsIfCondition() and op.isExistsTryCatchBlock()
+ or
+ // use call `operator new` without `(std::nothrow)` and checking error using `operator if` and not `try ... catch` block
+ not op.isExistsNothrow() and not op.isExistsTryCatchBlock() and op.isExistsIfCondition()
+select op, "memory allocation error check is incorrect or missing"
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.c b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.c
new file mode 100644
index 00000000000..ba78d4b97d1
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.c
@@ -0,0 +1,9 @@
+// BAD: if buffer does not have a terminal zero, then access outside the allocated memory is possible.
+
+buffer[strlen(buffer)] = 0;
+
+
+// GOOD: we will eliminate dangerous behavior if we use a different method of calculating the length.
+size_t len;
+...
+buffer[len] = 0
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qhelp
new file mode 100644
index 00000000000..51424ce7619
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qhelp
@@ -0,0 +1,31 @@
+
+
+
+
Potentially dangerous use of the strlen function to calculate the length of a string.
+The expression buffer[strlen(buffer)] = 0 is potentially dangerous, if the variable buffer does not have a terminal zero, then access beyond the bounds of the allocated memory is possible, which will lead to undefined behavior.
+If terminal zero is present, then the specified expression is meaningless.
+
+
False positives include heavily nested strlen. This situation is unlikely.
+
+
+
+
+
We recommend using another method for calculating the string length
+
+
+
+
The following example demonstrates an erroneous and corrected use of the strlen function.
+
+
+
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
new file mode 100644
index 00000000000..012109074e9
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
@@ -0,0 +1,34 @@
+/**
+ * @name Access Of Memory Location After End Of Buffer
+ * @description The expression `buffer [strlen (buffer)] = 0` is potentially dangerous, if the variable `buffer` does not have a terminal zero, then access beyond the bounds of the allocated memory is possible, which will lead to undefined behavior.
+ * If terminal zero is present, then the specified expression is meaningless.
+ * @kind problem
+ * @id cpp/access-memory-location-after-end-buffer
+ * @problem.severity warning
+ * @precision medium
+ * @tags correctness
+ * security
+ * external/cwe/cwe-788
+ */
+
+import cpp
+import semmle.code.cpp.valuenumbering.GlobalValueNumbering
+import semmle.code.cpp.dataflow.DataFlow
+
+from StrlenCall fc, AssignExpr expr, ArrayExpr exprarr
+where
+ exprarr = expr.getLValue() and
+ expr.getRValue().getValue().toInt() = 0 and
+ globalValueNumber(exprarr.getArrayOffset()) = globalValueNumber(fc) and
+ not exists(Expr exptmp |
+ (
+ DataFlow::localExprFlow(fc, exptmp) or
+ exptmp.getAChild*() = fc.getArgument(0).(VariableAccess).getTarget().getAnAccess()
+ ) and
+ dominates(exptmp, expr) and
+ postDominates(exptmp, fc) and
+ not exptmp.getEnclosingStmt() = fc.getEnclosingStmt() and
+ not exptmp.getEnclosingStmt() = expr.getEnclosingStmt()
+ ) and
+ globalValueNumber(fc.getArgument(0)) = globalValueNumber(exprarr.getArrayBase())
+select expr, "potential unsafe or redundant assignment."
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.c b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.c
new file mode 100644
index 00000000000..060a22b5c18
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.c
@@ -0,0 +1,4 @@
+
+strncat(dest, source, sizeof(dest) - strlen(dest)); // BAD: writes a zero byte past the `dest` buffer.
+
+strncat(dest, source, sizeof(dest) - strlen(dest) -1); // GOOD: Reserves space for the zero byte.
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.qhelp
new file mode 100644
index 00000000000..5c2154097ec
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.qhelp
@@ -0,0 +1,32 @@
+
+
+
+
The standard library function strncat(dest, source, count) appends the source string to the dest string. count specifies the maximum number of characters to append and must be less than the remaining space in the target buffer. Calls of the form strncat (dest, source, sizeof (dest) - strlen (dest)) set the third argument to one more than possible. So when the dest is full, the expression sizeof (dest) - strlen (dest) will be equal to one, and not zero as the programmer might think. Making a call of this type may result in a zero byte being written just outside the dest buffer.
+
+
+
+
+
+
We recommend subtracting one from the third argument. For example, replace strncat(dest, source, sizeof(dest)-strlen(dest)) with strncat(dest, source, sizeof(dest)-strlen(dest)-1).
+
+
+
+
The following example demonstrates an erroneous and corrected use of the strncat function.
+
+
+
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.ql b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.ql
new file mode 100644
index 00000000000..5311ffe2708
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.ql
@@ -0,0 +1,64 @@
+/**
+ * @name Access Of Memory Location After The End Of A Buffer Using Strncat
+ * @description Calls of the form `strncat(dest, source, sizeof (dest) - strlen (dest))` set the third argument to one more than possible. So when `dest` is full, the expression `sizeof(dest) - strlen (dest)` will be equal to one, and not zero as the programmer might think. Making a call of this type may result in a zero byte being written just outside the `dest` buffer.
+ * @kind problem
+ * @id cpp/access-memory-location-after-end-buffer
+ * @problem.severity warning
+ * @precision medium
+ * @tags correctness
+ * security
+ * external/cwe/cwe-788
+ */
+
+import cpp
+import semmle.code.cpp.valuenumbering.GlobalValueNumbering
+
+/**
+ * A call to `strncat` of the form `strncat(buff, str, someExpr - strlen(buf))`, for some expression `someExpr` equal to `sizeof(buff)`.
+ */
+class WrongCallStrncat extends FunctionCall {
+ Expr leftsomeExpr;
+
+ WrongCallStrncat() {
+ this.getTarget().hasGlobalOrStdName("strncat") and
+ // the expression of the first argument in `strncat` and `strnlen` is identical
+ globalValueNumber(this.getArgument(0)) =
+ globalValueNumber(this.getArgument(2).(SubExpr).getRightOperand().(StrlenCall).getStringExpr()) and
+ // using a string constant often speaks of manually calculating the length of the required buffer.
+ (
+ not this.getArgument(1) instanceof StringLiteral and
+ not this.getArgument(1) instanceof CharLiteral
+ ) and
+ // for use in predicates
+ leftsomeExpr = this.getArgument(2).(SubExpr).getLeftOperand()
+ }
+
+ /**
+ * Holds if the left side of the expression `someExpr` equal to `sizeof(buf)`.
+ */
+ predicate isExpressionEqualSizeof() {
+ // the left side of the expression `someExpr` is `sizeof(buf)`.
+ globalValueNumber(this.getArgument(0)) =
+ globalValueNumber(leftsomeExpr.(SizeofExprOperator).getExprOperand())
+ or
+ // value of the left side of the expression `someExpr` equal `sizeof(buf)` value, and `buf` is array.
+ leftsomeExpr.getValue().toInt() = this.getArgument(0).getType().getSize()
+ }
+
+ /**
+ * Holds if the left side of the expression `someExpr` equal to variable containing the length of the memory allocated for the buffer.
+ */
+ predicate isVariableEqualValueSizegBuffer() {
+ // the left side of expression `someExpr` is the variable that was used in the function of allocating memory for the buffer`.
+ exists(AllocationExpr alc |
+ leftsomeExpr.(VariableAccess).getTarget() =
+ alc.(FunctionCall).getArgument(0).(VariableAccess).getTarget()
+ )
+ }
+}
+
+from WrongCallStrncat sc
+where
+ sc.isExpressionEqualSizeof() or
+ sc.isVariableEqualValueSizegBuffer()
+select sc, "if the used buffer is full, writing out of the buffer is possible"
diff --git a/cpp/ql/src/semmle/code/cpp/Comments.qll b/cpp/ql/src/semmle/code/cpp/Comments.qll
index 65e2af5fd22..7574f0ff32c 100644
--- a/cpp/ql/src/semmle/code/cpp/Comments.qll
+++ b/cpp/ql/src/semmle/code/cpp/Comments.qll
@@ -50,5 +50,5 @@ class CStyleComment extends Comment {
* ```
*/
class CppStyleComment extends Comment {
- CppStyleComment() { this.getContents().prefix(2) = "//" }
+ CppStyleComment() { this.getContents().matches("//%") }
}
diff --git a/cpp/ql/src/semmle/code/cpp/Declaration.qll b/cpp/ql/src/semmle/code/cpp/Declaration.qll
index 35ae092780d..7ac79fd99c1 100644
--- a/cpp/ql/src/semmle/code/cpp/Declaration.qll
+++ b/cpp/ql/src/semmle/code/cpp/Declaration.qll
@@ -139,6 +139,19 @@ class Declaration extends Locatable, @declaration {
this.hasQualifiedName("std", "", name)
}
+ /**
+ * Holds if this declaration has the given name in the global namespace,
+ * the `std` namespace or the `bsl` namespace.
+ * We treat `std` and `bsl` as the same in some of our models.
+ */
+ predicate hasGlobalOrStdOrBslName(string name) {
+ this.hasGlobalName(name)
+ or
+ this.hasQualifiedName("std", "", name)
+ or
+ this.hasQualifiedName("bsl", "", name)
+ }
+
/** Gets a specifier of this declaration. */
Specifier getASpecifier() { none() } // overridden in subclasses
diff --git a/cpp/ql/src/semmle/code/cpp/Element.qll b/cpp/ql/src/semmle/code/cpp/Element.qll
index f4c1bd972c6..66f23ea110f 100644
--- a/cpp/ql/src/semmle/code/cpp/Element.qll
+++ b/cpp/ql/src/semmle/code/cpp/Element.qll
@@ -270,7 +270,12 @@ private predicate isFromUninstantiatedTemplateRec(Element e, Element template) {
}
/**
- * A C++11 `static_assert` or C11 `_Static_assert` construct.
+ * A C++11 `static_assert` or C11 `_Static_assert` construct. For example each
+ * line in the following example contains a static assert:
+ * ```
+ * static_assert(sizeof(MyStruct) <= 4096);
+ * static_assert(sizeof(MyStruct) <= 4096, "MyStruct is too big!");
+ * ```
*/
class StaticAssert extends Locatable, @static_assert {
override string toString() { result = "static_assert(..., \"" + getMessage() + "\")" }
diff --git a/cpp/ql/src/semmle/code/cpp/File.qll b/cpp/ql/src/semmle/code/cpp/File.qll
index 1887f43b70c..c7486f4d75d 100644
--- a/cpp/ql/src/semmle/code/cpp/File.qll
+++ b/cpp/ql/src/semmle/code/cpp/File.qll
@@ -363,20 +363,8 @@ class File extends Container, @file {
*/
class HeaderFile extends File {
HeaderFile() {
- exists(string ext | ext = this.getExtension().toLowerCase() |
- ext = "h" or
- ext = "r" or
- /* --- */ ext = "hpp" or
- ext = "hxx" or
- ext = "h++" or
- ext = "hh" or
- ext = "hp" or
- ext = "tcc" or
- ext = "tpp" or
- ext = "txx" or
- ext = "t++"
- /* --- --- */
- )
+ this.getExtension().toLowerCase() =
+ ["h", "r", "hpp", "hxx", "h++", "hh", "hp", "tcc", "tpp", "txx", "t++"]
or
not exists(this.getExtension()) and
exists(Include i | i.getIncludedFile() = this)
@@ -406,7 +394,7 @@ class HeaderFile extends File {
* `File.compiledAsC`.
*/
class CFile extends File {
- CFile() { exists(string ext | ext = this.getExtension().toLowerCase() | ext = "c" or ext = "i") }
+ CFile() { this.getExtension().toLowerCase() = ["c", "i"] }
override string getAPrimaryQlClass() { result = "CFile" }
}
@@ -419,21 +407,10 @@ class CFile extends File {
*/
class CppFile extends File {
CppFile() {
- exists(string ext | ext = this.getExtension().toLowerCase() |
- /* --- */ ext = "cpp" or
- ext = "cxx" or
- ext = "c++" or
- ext = "cc" or
- ext = "cp" or
- ext = "icc" or
- ext = "ipp" or
- ext = "ixx" or
- ext = "i++" or
- ext = "ii"
- /* --- */
- // Note: .C files are indistinguishable from .c files on some
- // file systems, so we just treat them as CFile's.
- )
+ this.getExtension().toLowerCase() =
+ ["cpp", "cxx", "c++", "cc", "cp", "icc", "ipp", "ixx", "i++", "ii"]
+ // Note: .C files are indistinguishable from .c files on some
+ // file systems, so we just treat them as CFile's.
}
override string getAPrimaryQlClass() { result = "CppFile" }
diff --git a/cpp/ql/src/semmle/code/cpp/Function.qll b/cpp/ql/src/semmle/code/cpp/Function.qll
index 2c45fa184cf..ac345e8a29a 100644
--- a/cpp/ql/src/semmle/code/cpp/Function.qll
+++ b/cpp/ql/src/semmle/code/cpp/Function.qll
@@ -334,6 +334,18 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
)
}
+ /**
+ * Gets the class of which this function, called `memberName`, is a member.
+ *
+ * Prefer to use `getDeclaringType()` or `getName()` directly if you do not
+ * need to reason about both.
+ */
+ pragma[nomagic]
+ Class getClassAndName(string memberName) {
+ this.hasName(memberName) and
+ this.getDeclaringType() = result
+ }
+
/**
* Implements `ControlFlowNode.getControlFlowScope`. The `Function` is
* used to represent the exit node of the control flow graph, so it is
@@ -391,20 +403,30 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
/** Holds if this function has a `noexcept` exception specification. */
predicate isNoExcept() { getADeclarationEntry().isNoExcept() }
- /** Gets a function that overloads this one. */
+ /**
+ * Gets a function that overloads this one.
+ *
+ * Note: if _overrides_ are wanted rather than _overloads_ then
+ * `MemberFunction::getAnOverridingFunction` should be used instead.
+ */
Function getAnOverload() {
- result.getName() = getName() and
- result.getNamespace() = getNamespace() and
- result != this and
- // If this function is declared in a class, only consider other
- // functions from the same class. Conversely, if this function is not
- // declared in a class, only consider other functions not declared in a
- // class.
(
- if exists(getDeclaringType())
- then result.getDeclaringType() = getDeclaringType()
- else not exists(result.getDeclaringType())
+ // If this function is declared in a class, only consider other
+ // functions from the same class.
+ exists(string name, Class declaringType |
+ candGetAnOverloadMember(name, declaringType, this) and
+ candGetAnOverloadMember(name, declaringType, result)
+ )
+ or
+ // Conversely, if this function is not
+ // declared in a class, only consider other functions not declared in a
+ // class.
+ exists(string name, Namespace namespace |
+ candGetAnOverloadNonMember(name, namespace, this) and
+ candGetAnOverloadNonMember(name, namespace, result)
+ )
) and
+ result != this and
// Instantiations and specializations don't participate in overload
// resolution.
not (
@@ -445,50 +467,15 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
// ... and likewise for destructors.
this.(Destructor).getADestruction().mayBeGloballyImpure()
else
- not exists(string name | this.hasGlobalOrStdName(name) |
- // Unless it's a function that we know is side-effect-free, it may
- // have side-effects.
- name = "strcmp" or
- name = "wcscmp" or
- name = "_mbscmp" or
- name = "strlen" or
- name = "wcslen" or
- name = "_mbslen" or
- name = "_mbslen_l" or
- name = "_mbstrlen" or
- name = "_mbstrlen_l" or
- name = "strnlen" or
- name = "strnlen_s" or
- name = "wcsnlen" or
- name = "wcsnlen_s" or
- name = "_mbsnlen" or
- name = "_mbsnlen_l" or
- name = "_mbstrnlen" or
- name = "_mbstrnlen_l" or
- name = "strncmp" or
- name = "wcsncmp" or
- name = "_mbsncmp" or
- name = "_mbsncmp_l" or
- name = "strchr" or
- name = "memchr" or
- name = "wmemchr" or
- name = "memcmp" or
- name = "wmemcmp" or
- name = "_memicmp" or
- name = "_memicmp_l" or
- name = "feof" or
- name = "isdigit" or
- name = "isxdigit" or
- name = "abs" or
- name = "fabs" or
- name = "labs" or
- name = "floor" or
- name = "ceil" or
- name = "atoi" or
- name = "atol" or
- name = "atoll" or
- name = "atof"
- )
+ // Unless it's a function that we know is side-effect-free, it may
+ // have side-effects.
+ not this.hasGlobalOrStdName([
+ "strcmp", "wcscmp", "_mbscmp", "strlen", "wcslen", "_mbslen", "_mbslen_l", "_mbstrlen",
+ "_mbstrlen_l", "strnlen", "strnlen_s", "wcsnlen", "wcsnlen_s", "_mbsnlen", "_mbsnlen_l",
+ "_mbstrnlen", "_mbstrnlen_l", "strncmp", "wcsncmp", "_mbsncmp", "_mbsncmp_l", "strchr",
+ "memchr", "wmemchr", "memcmp", "wmemcmp", "_memicmp", "_memicmp_l", "feof", "isdigit",
+ "isxdigit", "abs", "fabs", "labs", "floor", "ceil", "atoi", "atol", "atoll", "atof"
+ ])
}
/**
@@ -497,6 +484,19 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
override AccessHolder getEnclosingAccessHolder() { result = this.getDeclaringType() }
}
+pragma[noinline]
+private predicate candGetAnOverloadMember(string name, Class declaringType, Function f) {
+ f.getName() = name and
+ f.getDeclaringType() = declaringType
+}
+
+pragma[noinline]
+private predicate candGetAnOverloadNonMember(string name, Namespace namespace, Function f) {
+ f.getName() = name and
+ f.getNamespace() = namespace and
+ not exists(f.getDeclaringType())
+}
+
/**
* A particular declaration or definition of a C/C++ function. For example the
* declaration and definition of `MyFunction` in the following code are each a
@@ -680,7 +680,7 @@ class FunctionDeclarationEntry extends DeclarationEntry, @fun_decl {
/**
* A C/C++ non-member function (a function that is not a member of any
- * class). For example the in the following code, `MyFunction` is a
+ * class). For example, in the following code, `MyFunction` is a
* `TopLevelFunction` but `MyMemberFunction` is not:
* ```
* void MyFunction() {
diff --git a/cpp/ql/src/semmle/code/cpp/Namespace.qll b/cpp/ql/src/semmle/code/cpp/Namespace.qll
index 6172c3af50c..d46abc6b4db 100644
--- a/cpp/ql/src/semmle/code/cpp/Namespace.qll
+++ b/cpp/ql/src/semmle/code/cpp/Namespace.qll
@@ -7,8 +7,21 @@ import semmle.code.cpp.Type
import semmle.code.cpp.metrics.MetricNamespace
/**
- * A C++ namespace.
+ * A C++ namespace. For example the (single) namespace `A` in the following
+ * code:
+ * ```
+ * namespace A
+ * {
+ * // ...
+ * }
*
+ * // ...
+ *
+ * namespace A
+ * {
+ * // ...
+ * }
+ * ```
* Note that namespaces are somewhat nebulous entities, as they do not in
* general have a single well-defined location in the source code. The
* related notion of a `NamespaceDeclarationEntry` is rather more concrete,
@@ -96,10 +109,22 @@ class Namespace extends NameQualifyingElement, @namespace {
}
/**
- * A declaration of (part of) a C++ namespace.
+ * A declaration of (part of) a C++ namespace. This corresponds to a single
+ * `namespace N { ... }` occurrence in the source code. For example the two
+ * mentions of `A` in the following code:
+ * ```
+ * namespace A
+ * {
+ * // ...
+ * }
*
- * This corresponds to a single `namespace N { ... }` occurrence in the
- * source code.
+ * // ...
+ *
+ * namespace A
+ * {
+ * // ...
+ * }
+ * ```
*/
class NamespaceDeclarationEntry extends Locatable, @namespace_decl {
/**
@@ -143,8 +168,9 @@ class UsingEntry extends Locatable, @using {
/**
* A C++ `using` declaration. For example:
- *
- * `using std::string;`
+ * ```
+ * using std::string;
+ * ```
*/
class UsingDeclarationEntry extends UsingEntry {
UsingDeclarationEntry() {
@@ -162,8 +188,9 @@ class UsingDeclarationEntry extends UsingEntry {
/**
* A C++ `using` directive. For example:
- *
- * `using namespace std;`
+ * ```
+ * using namespace std;
+ * ```
*/
class UsingDirectiveEntry extends UsingEntry {
UsingDirectiveEntry() {
diff --git a/cpp/ql/src/semmle/code/cpp/Preprocessor.qll b/cpp/ql/src/semmle/code/cpp/Preprocessor.qll
index a9609922b74..2389db07f2a 100644
--- a/cpp/ql/src/semmle/code/cpp/Preprocessor.qll
+++ b/cpp/ql/src/semmle/code/cpp/Preprocessor.qll
@@ -2,9 +2,14 @@ import semmle.code.cpp.Location
import semmle.code.cpp.Element
/**
- * A C/C++ preprocessor directive.
- *
- * For example: `#ifdef`, `#line`, or `#pragma`.
+ * A C/C++ preprocessor directive. For example each of the following lines of
+ * code contains a `PreprocessorDirective`:
+ * ```
+ * #pragma once
+ * #ifdef MYDEFINE
+ * #include "myfile.h"
+ * #line 1 "source.c"
+ * ```
*/
class PreprocessorDirective extends Locatable, @preprocdirect {
override string toString() { result = "Preprocessor directive" }
@@ -98,9 +103,9 @@ class PreprocessorBranchDirective extends PreprocessorDirective, TPreprocessorBr
* A C/C++ preprocessor branching directive: `#if`, `#ifdef`, `#ifndef`, or
* `#elif`.
*
- * A branching directive can have its condition evaluated at compile-time,
- * and as a result, the preprocessor will either take the branch, or not
- * take the branch.
+ * A branching directive has a condition and that condition may be evaluated
+ * at compile-time. As a result, the preprocessor will either take the
+ * branch, or not take the branch.
*
* However, there are also situations in which a branch's condition isn't
* evaluated. The obvious case of this is when the directive is contained
@@ -136,8 +141,13 @@ class PreprocessorBranch extends PreprocessorBranchDirective, @ppd_branch {
}
/**
- * A C/C++ preprocessor `#if` directive.
- *
+ * A C/C++ preprocessor `#if` directive. For example there is a
+ * `PreprocessorIf` on the first line of the following code:
+ * ```
+ * #if defined(MYDEFINE)
+ * // ...
+ * #endif
+ * ```
* For the related notion of a directive which causes branching (which
* includes `#if`, plus also `#ifdef`, `#ifndef`, and `#elif`), see
* `PreprocessorBranch`.
@@ -147,8 +157,13 @@ class PreprocessorIf extends PreprocessorBranch, @ppd_if {
}
/**
- * A C/C++ preprocessor `#ifdef` directive.
- *
+ * A C/C++ preprocessor `#ifdef` directive. For example there is a
+ * `PreprocessorIfdef` on the first line of the following code:
+ * ```
+ * #ifdef MYDEFINE
+ * // ...
+ * #endif
+ * ```
* The syntax `#ifdef X` is shorthand for `#if defined(X)`.
*/
class PreprocessorIfdef extends PreprocessorBranch, @ppd_ifdef {
@@ -158,8 +173,13 @@ class PreprocessorIfdef extends PreprocessorBranch, @ppd_ifdef {
}
/**
- * A C/C++ preprocessor `#ifndef` directive.
- *
+ * A C/C++ preprocessor `#ifndef` directive. For example there is a
+ * `PreprocessorIfndef` on the first line of the following code:
+ * ```
+ * #ifndef MYDEFINE
+ * // ...
+ * #endif
+ * ```
* The syntax `#ifndef X` is shorthand for `#if !defined(X)`.
*/
class PreprocessorIfndef extends PreprocessorBranch, @ppd_ifndef {
@@ -167,42 +187,80 @@ class PreprocessorIfndef extends PreprocessorBranch, @ppd_ifndef {
}
/**
- * A C/C++ preprocessor `#else` directive.
+ * A C/C++ preprocessor `#else` directive. For example there is a
+ * `PreprocessorElse` on the fifth line of the following code:
+ * ```
+ * #ifdef MYDEFINE1
+ * // ...
+ * #elif MYDEFINE2
+ * // ...
+ * #else
+ * // ...
+ * #endif
+ * ```
*/
class PreprocessorElse extends PreprocessorBranchDirective, @ppd_else {
override string toString() { result = "#else" }
}
/**
- * A C/C++ preprocessor `#elif` directive.
+ * A C/C++ preprocessor `#elif` directive. For example there is a
+ * `PreprocessorElif` on the third line of the following code:
+ * ```
+ * #ifdef MYDEFINE1
+ * // ...
+ * #elif MYDEFINE2
+ * // ...
+ * #else
+ * // ...
+ * #endif
+ * ```
*/
class PreprocessorElif extends PreprocessorBranch, @ppd_elif {
override string toString() { result = "#elif " + this.getHead() }
}
/**
- * A C/C++ preprocessor `#endif` directive.
+ * A C/C++ preprocessor `#endif` directive. For example there is a
+ * `PreprocessorEndif` on the third line of the following code:
+ * ```
+ * #ifdef MYDEFINE
+ * // ...
+ * #endif
+ * ```
*/
class PreprocessorEndif extends PreprocessorBranchDirective, @ppd_endif {
override string toString() { result = "#endif" }
}
/**
- * A C/C++ preprocessor `#warning` directive.
+ * A C/C++ preprocessor `#warning` directive. For example:
+ * ```
+ * #warning "This configuration is not supported."
+ * ```
*/
class PreprocessorWarning extends PreprocessorDirective, @ppd_warning {
override string toString() { result = "#warning " + this.getHead() }
}
/**
- * A C/C++ preprocessor `#error` directive.
+ * A C/C++ preprocessor `#error` directive. For example:
+ * ```
+ * #error "This configuration is not implemented."
+ * ```
*/
class PreprocessorError extends PreprocessorDirective, @ppd_error {
override string toString() { result = "#error " + this.getHead() }
}
/**
- * A C/C++ preprocessor `#undef` directive.
+ * A C/C++ preprocessor `#undef` directive. For example there is a
+ * `PreprocessorUndef` on the second line of the following code:
+ * ```
+ * #ifdef MYMACRO
+ * #undef MYMACRO
+ * #endif
+ * ```
*/
class PreprocessorUndef extends PreprocessorDirective, @ppd_undef {
override string toString() { result = "#undef " + this.getHead() }
@@ -214,7 +272,10 @@ class PreprocessorUndef extends PreprocessorDirective, @ppd_undef {
}
/**
- * A C/C++ preprocessor `#pragma` directive.
+ * A C/C++ preprocessor `#pragma` directive. For example:
+ * ```
+ * #pragma once
+ * ```
*/
class PreprocessorPragma extends PreprocessorDirective, @ppd_pragma {
override string toString() {
@@ -223,7 +284,10 @@ class PreprocessorPragma extends PreprocessorDirective, @ppd_pragma {
}
/**
- * A C/C++ preprocessor `#line` directive.
+ * A C/C++ preprocessor `#line` directive. For example:
+ * ```
+ * #line 1 "source.c"
+ * ```
*/
class PreprocessorLine extends PreprocessorDirective, @ppd_line {
override string toString() { result = "#line " + this.getHead() }
diff --git a/cpp/ql/src/semmle/code/cpp/Print.qll b/cpp/ql/src/semmle/code/cpp/Print.qll
index 4c9cc4edf27..f8d30f55a88 100644
--- a/cpp/ql/src/semmle/code/cpp/Print.qll
+++ b/cpp/ql/src/semmle/code/cpp/Print.qll
@@ -385,7 +385,7 @@ private class DumpFunction extends DumpDeclaration, Function {
private string getACVQualifier() {
result = getASpecifier().getName() and
- (result = "const" or result = "volatile")
+ result = ["const", "volatile"]
}
private string getDeclaratorSuffix() {
diff --git a/cpp/ql/src/semmle/code/cpp/Specifier.qll b/cpp/ql/src/semmle/code/cpp/Specifier.qll
index 3d68fb374f1..1c1eb0c090a 100644
--- a/cpp/ql/src/semmle/code/cpp/Specifier.qll
+++ b/cpp/ql/src/semmle/code/cpp/Specifier.qll
@@ -171,8 +171,11 @@ class StdAttribute extends Attribute, @stdattribute {
}
/**
- * An attribute introduced by Microsoft's `__declspec(name)` syntax, for
- * example: `__declspec(dllimport)`.
+ * An attribute introduced by Microsoft's `__declspec(name)` syntax. For
+ * example the attribute on the following declaration:
+ * ```
+ * __declspec(dllimport) void myFunction();
+ * ```
*/
class Declspec extends Attribute, @declspec { }
@@ -186,8 +189,13 @@ class MicrosoftAttribute extends Attribute, @msattribute {
}
/**
- * A C++11 `alignas` construct.
- *
+ * A C++11 `alignas` construct. For example the attribute in the following
+ * code:
+ * ```
+ * struct alignas(16) MyStruct {
+ * int x;
+ * };
+ * ```
* Though it doesn't use the attribute syntax, `alignas(...)` is presented
* as an `Attribute` for consistency with the `[[align(...)]]` attribute.
*/
@@ -197,7 +205,11 @@ class AlignAs extends Attribute, @alignas {
/**
* A GNU `format` attribute of the form `__attribute__((format(archetype, format-index, first-arg)))`
- * that declares a function to accept a `printf` style format string.
+ * that declares a function to accept a `printf` style format string. For example the attribute
+ * on the following declaration:
+ * ```
+ * int myPrintf(const char *format, ...) __attribute__((format(printf, 1, 2)));
+ * ```
*/
class FormatAttribute extends GnuAttribute {
FormatAttribute() { getName() = "format" }
@@ -242,7 +254,11 @@ class FormatAttribute extends GnuAttribute {
}
/**
- * An argument to an `Attribute`.
+ * An argument to an `Attribute`. For example the argument "dllimport" on the
+ * attribute in the following code:
+ * ```
+ * __declspec(dllimport) void myFunction();
+ * ```
*/
class AttributeArgument extends Element, @attribute_arg {
/**
diff --git a/cpp/ql/src/semmle/code/cpp/Type.qll b/cpp/ql/src/semmle/code/cpp/Type.qll
index 81b28cca4c9..a0d8c42df32 100644
--- a/cpp/ql/src/semmle/code/cpp/Type.qll
+++ b/cpp/ql/src/semmle/code/cpp/Type.qll
@@ -274,7 +274,7 @@ class Type extends Locatable, @type {
/**
* Gets this type with any typedefs resolved. For example, given
- * `typedef C T`, this would resolve `const T&` to `const C&`.
+ * `typedef C T`, this would resolve `const T&` to `const C&`.
* Note that this will only work if the resolved type actually appears
* on its own elsewhere in the program.
*/
@@ -1544,9 +1544,9 @@ class FunctionPointerIshType extends DerivedType {
/**
* A C++ pointer to data member. See 15.5.
* ```
- * class C { int m; };
+ * class C { public: int m; };
* int C::* p = &C::m; // pointer to data member m of class C
- * class C *;
+ * class C c;
* int val = c.*p; // access data member
* ```
*/
diff --git a/cpp/ql/src/semmle/code/cpp/commons/DateTime.qll b/cpp/ql/src/semmle/code/cpp/commons/DateTime.qll
index abbb1193021..c67bf7cf96e 100644
--- a/cpp/ql/src/semmle/code/cpp/commons/DateTime.qll
+++ b/cpp/ql/src/semmle/code/cpp/commons/DateTime.qll
@@ -14,11 +14,7 @@ class PackedTimeType extends Type {
}
}
-private predicate timeType(string typeName) {
- typeName = "_SYSTEMTIME" or
- typeName = "SYSTEMTIME" or
- typeName = "tm"
-}
+private predicate timeType(string typeName) { typeName = ["_SYSTEMTIME", "SYSTEMTIME", "tm"] }
/**
* A type that is used to represent times and dates in an 'unpacked' form, that is,
diff --git a/cpp/ql/src/semmle/code/cpp/commons/Printf.qll b/cpp/ql/src/semmle/code/cpp/commons/Printf.qll
index 4785c2b4aa4..8d9ad5c77e4 100644
--- a/cpp/ql/src/semmle/code/cpp/commons/Printf.qll
+++ b/cpp/ql/src/semmle/code/cpp/commons/Printf.qll
@@ -50,7 +50,15 @@ predicate primitiveVariadicFormatter(
then formatParamIndex = f.getNumberOfParameters() - 3
else formatParamIndex = f.getNumberOfParameters() - 2
) and
- if type = "" then outputParamIndex = -1 else outputParamIndex = 0 // Conveniently, these buffer parameters are all at index 0.
+ (
+ if type = "" then outputParamIndex = -1 else outputParamIndex = 0 // Conveniently, these buffer parameters are all at index 0.
+ ) and
+ not (
+ // exclude functions with an implementation in the snapshot source
+ // directory, as they may not be standard implementations.
+ exists(f.getBlock()) and
+ exists(f.getFile().getRelativePath())
+ )
}
private predicate callsVariadicFormatter(
@@ -900,6 +908,7 @@ class FormatLiteral extends Literal {
*/
int getNumArgNeeded(int n) {
exists(this.getConvSpecOffset(n)) and
+ exists(this.getConversionChar(n)) and
result = count(int mode | hasFormatArgumentIndexFor(n, mode))
}
diff --git a/cpp/ql/src/semmle/code/cpp/commons/Scanf.qll b/cpp/ql/src/semmle/code/cpp/commons/Scanf.qll
index 53fee14cd2a..461030f389d 100644
--- a/cpp/ql/src/semmle/code/cpp/commons/Scanf.qll
+++ b/cpp/ql/src/semmle/code/cpp/commons/Scanf.qll
@@ -34,10 +34,10 @@ class Scanf extends ScanfFunction {
Scanf() {
this instanceof TopLevelFunction and
(
- hasName("scanf") or // scanf(format, args...)
- hasName("wscanf") or // wscanf(format, args...)
- hasName("_scanf_l") or // _scanf_l(format, locale, args...)
- hasName("_wscanf_l") // _wscanf_l(format, locale, args...)
+ hasGlobalOrStdOrBslName("scanf") or // scanf(format, args...)
+ hasGlobalOrStdOrBslName("wscanf") or // wscanf(format, args...)
+ hasGlobalName("_scanf_l") or // _scanf_l(format, locale, args...)
+ hasGlobalName("_wscanf_l") // _wscanf_l(format, locale, args...)
)
}
@@ -53,10 +53,10 @@ class Fscanf extends ScanfFunction {
Fscanf() {
this instanceof TopLevelFunction and
(
- hasName("fscanf") or // fscanf(src_stream, format, args...)
- hasName("fwscanf") or // fwscanf(src_stream, format, args...)
- hasName("_fscanf_l") or // _fscanf_l(src_stream, format, locale, args...)
- hasName("_fwscanf_l") // _fwscanf_l(src_stream, format, locale, args...)
+ hasGlobalOrStdOrBslName("fscanf") or // fscanf(src_stream, format, args...)
+ hasGlobalOrStdOrBslName("fwscanf") or // fwscanf(src_stream, format, args...)
+ hasGlobalName("_fscanf_l") or // _fscanf_l(src_stream, format, locale, args...)
+ hasGlobalName("_fwscanf_l") // _fwscanf_l(src_stream, format, locale, args...)
)
}
@@ -72,10 +72,10 @@ class Sscanf extends ScanfFunction {
Sscanf() {
this instanceof TopLevelFunction and
(
- hasName("sscanf") or // sscanf(src_stream, format, args...)
- hasName("swscanf") or // swscanf(src, format, args...)
- hasName("_sscanf_l") or // _sscanf_l(src, format, locale, args...)
- hasName("_swscanf_l") // _swscanf_l(src, format, locale, args...)
+ hasGlobalOrStdOrBslName("sscanf") or // sscanf(src_stream, format, args...)
+ hasGlobalOrStdOrBslName("swscanf") or // swscanf(src, format, args...)
+ hasGlobalName("_sscanf_l") or // _sscanf_l(src, format, locale, args...)
+ hasGlobalName("_swscanf_l") // _swscanf_l(src, format, locale, args...)
)
}
@@ -91,8 +91,10 @@ class Snscanf extends ScanfFunction {
Snscanf() {
this instanceof TopLevelFunction and
(
- hasName("_snscanf") or // _snscanf(src, max_amount, format, args...)
- hasName("_snwscanf") // _snwscanf(src, max_amount, format, args...)
+ hasGlobalName("_snscanf") or // _snscanf(src, max_amount, format, args...)
+ hasGlobalName("_snwscanf") or // _snwscanf(src, max_amount, format, args...)
+ hasGlobalName("_snscanf_l") or // _snscanf_l(src, max_amount, format, locale, args...)
+ hasGlobalName("_snwscanf_l") // _snwscanf_l(src, max_amount, format, locale, args...)
// note that the max_amount is not a limit on the output length, it's an input length
// limit used with non null-terminated strings.
)
@@ -101,6 +103,12 @@ class Snscanf extends ScanfFunction {
override int getInputParameterIndex() { result = 0 }
override int getFormatParameterIndex() { result = 2 }
+
+ /**
+ * Gets the position at which the maximum number of characters in the
+ * input string is specified.
+ */
+ int getInputLengthParameterIndex() { result = 1 }
}
/**
diff --git a/cpp/ql/src/semmle/code/cpp/commons/Synchronization.qll b/cpp/ql/src/semmle/code/cpp/commons/Synchronization.qll
index c7641385393..92955ae3580 100644
--- a/cpp/ql/src/semmle/code/cpp/commons/Synchronization.qll
+++ b/cpp/ql/src/semmle/code/cpp/commons/Synchronization.qll
@@ -87,7 +87,7 @@ abstract class MutexType extends Type {
private Function mustlockCandidate() {
exists(string name | name = result.getName() |
name = "lock" or
- name.suffix(name.length() - 10) = "mutex_lock"
+ name.matches("%mutex\\_lock")
)
}
@@ -97,7 +97,7 @@ private Function mustlockCandidate() {
private Function trylockCandidate() {
exists(string name | name = result.getName() |
name = "try_lock" or
- name.suffix(name.length() - 13) = "mutex_trylock"
+ name.matches("%mutex\\_trylock")
)
}
@@ -107,7 +107,7 @@ private Function trylockCandidate() {
private Function unlockCandidate() {
exists(string name | name = result.getName() |
name = "unlock" or
- name.suffix(name.length() - 12) = "mutex_unlock"
+ name.matches("%mutex\\_unlock")
)
}
diff --git a/cpp/ql/src/semmle/code/cpp/controlflow/Dataflow.qll b/cpp/ql/src/semmle/code/cpp/controlflow/Dataflow.qll
index 1a5d81ee9f3..32eb4ecc2e0 100644
--- a/cpp/ql/src/semmle/code/cpp/controlflow/Dataflow.qll
+++ b/cpp/ql/src/semmle/code/cpp/controlflow/Dataflow.qll
@@ -13,7 +13,7 @@ import Dereferenced
* predicates that implement this analysis.
*/
abstract class DataflowAnnotation extends string {
- DataflowAnnotation() { this = "pointer-null" or this = "pointer-valid" }
+ DataflowAnnotation() { this = ["pointer-null", "pointer-valid"] }
/** Holds if this annotation is the default annotation. */
abstract predicate isDefault();
@@ -98,7 +98,7 @@ abstract class DataflowAnnotation extends string {
* respectively.
*/
class NullnessAnnotation extends DataflowAnnotation {
- NullnessAnnotation() { this = "pointer-null" or this = "pointer-valid" }
+ NullnessAnnotation() { this = ["pointer-null", "pointer-valid"] }
override predicate isDefault() { this = "pointer-valid" }
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
index d9f5acdd279..59cc8d529a7 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
@@ -3598,6 +3598,7 @@ private module FlowExploration {
or
exists(PartialPathNodeRev mid |
revPartialPathStep(mid, node, sc1, sc2, ap, config) and
+ not clearsContent(node, ap.getHead()) and
not fullBarrier(node, config) and
distSink(node.getEnclosingCallable(), config) <= config.explorationLimit()
)
@@ -3611,6 +3612,7 @@ private module FlowExploration {
exists(PartialPathNodeFwd mid |
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
+ not clearsContent(node, ap.getHead().getContent()) and
if node instanceof CastingNode
then compatibleTypes(getNodeType(node), ap.getType())
else any()
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
index d9f5acdd279..59cc8d529a7 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
@@ -3598,6 +3598,7 @@ private module FlowExploration {
or
exists(PartialPathNodeRev mid |
revPartialPathStep(mid, node, sc1, sc2, ap, config) and
+ not clearsContent(node, ap.getHead()) and
not fullBarrier(node, config) and
distSink(node.getEnclosingCallable(), config) <= config.explorationLimit()
)
@@ -3611,6 +3612,7 @@ private module FlowExploration {
exists(PartialPathNodeFwd mid |
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
+ not clearsContent(node, ap.getHead().getContent()) and
if node instanceof CastingNode
then compatibleTypes(getNodeType(node), ap.getType())
else any()
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
index d9f5acdd279..59cc8d529a7 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
@@ -3598,6 +3598,7 @@ private module FlowExploration {
or
exists(PartialPathNodeRev mid |
revPartialPathStep(mid, node, sc1, sc2, ap, config) and
+ not clearsContent(node, ap.getHead()) and
not fullBarrier(node, config) and
distSink(node.getEnclosingCallable(), config) <= config.explorationLimit()
)
@@ -3611,6 +3612,7 @@ private module FlowExploration {
exists(PartialPathNodeFwd mid |
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
+ not clearsContent(node, ap.getHead().getContent()) and
if node instanceof CastingNode
then compatibleTypes(getNodeType(node), ap.getType())
else any()
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
index d9f5acdd279..59cc8d529a7 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
@@ -3598,6 +3598,7 @@ private module FlowExploration {
or
exists(PartialPathNodeRev mid |
revPartialPathStep(mid, node, sc1, sc2, ap, config) and
+ not clearsContent(node, ap.getHead()) and
not fullBarrier(node, config) and
distSink(node.getEnclosingCallable(), config) <= config.explorationLimit()
)
@@ -3611,6 +3612,7 @@ private module FlowExploration {
exists(PartialPathNodeFwd mid |
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
+ not clearsContent(node, ap.getHead().getContent()) and
if node instanceof CastingNode
then compatibleTypes(getNodeType(node), ap.getType())
else any()
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll
index d9f5acdd279..59cc8d529a7 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll
@@ -3598,6 +3598,7 @@ private module FlowExploration {
or
exists(PartialPathNodeRev mid |
revPartialPathStep(mid, node, sc1, sc2, ap, config) and
+ not clearsContent(node, ap.getHead()) and
not fullBarrier(node, config) and
distSink(node.getEnclosingCallable(), config) <= config.explorationLimit()
)
@@ -3611,6 +3612,7 @@ private module FlowExploration {
exists(PartialPathNodeFwd mid |
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
+ not clearsContent(node, ap.getHead().getContent()) and
if node instanceof CastingNode
then compatibleTypes(getNodeType(node), ap.getType())
else any()
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
index 33a74c96718..f3aa94a7992 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
@@ -620,7 +620,8 @@ module FlowVar_internal {
private predicate largeVariable(Variable v, int liveBlocks, int defs) {
liveBlocks = strictcount(SubBasicBlock sbb | variableLiveInSBB(sbb, v)) and
defs = strictcount(SubBasicBlock sbb | exists(TBlockVar(sbb, v))) and
- liveBlocks * defs > 1000000
+ // Convert to float to avoid int overflow (32-bit two's complement)
+ liveBlocks.(float) * defs.(float) > 100000.0
}
/**
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll
index 7ca5020422d..7ca03067d0a 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll
@@ -36,43 +36,33 @@ private predicate predictableInstruction(Instruction instr) {
* library's `returnArgument` predicate.
*/
predicate predictableOnlyFlow(string name) {
- name = "strcasestr" or
- name = "strchnul" or
- name = "strchr" or
- name = "strchrnul" or
- name = "strcmp" or
- name = "strcspn" or
- name = "strncmp" or
- name = "strndup" or
- name = "strnlen" or
- name = "strrchr" or
- name = "strspn" or
- name = "strstr" or
- name = "strtod" or
- name = "strtof" or
- name = "strtol" or
- name = "strtoll" or
- name = "strtoq" or
- name = "strtoul"
+ name =
+ [
+ "strcasestr", "strchnul", "strchr", "strchrnul", "strcmp", "strcspn", "strncmp", "strndup",
+ "strnlen", "strrchr", "strspn", "strstr", "strtod", "strtof", "strtol", "strtoll", "strtoq",
+ "strtoul"
+ ]
}
private DataFlow::Node getNodeForSource(Expr source) {
isUserInput(source, _) and
- (
- result = DataFlow::exprNode(source)
- or
- // Some of the sources in `isUserInput` are intended to match the value of
- // an expression, while others (those modeled below) are intended to match
- // the taint that propagates out of an argument, like the `char *` argument
- // to `gets`. It's impossible here to tell which is which, but the "access
- // to argv" source is definitely not intended to match an output argument,
- // and it causes false positives if we let it.
- //
- // This case goes together with the similar (but not identical) rule in
- // `nodeIsBarrierIn`.
- result = DataFlow::definitionByReferenceNodeFromArgument(source) and
- not argv(source.(VariableAccess).getTarget())
- )
+ result = getNodeForExpr(source)
+}
+
+private DataFlow::Node getNodeForExpr(Expr node) {
+ result = DataFlow::exprNode(node)
+ or
+ // Some of the sources in `isUserInput` are intended to match the value of
+ // an expression, while others (those modeled below) are intended to match
+ // the taint that propagates out of an argument, like the `char *` argument
+ // to `gets`. It's impossible here to tell which is which, but the "access
+ // to argv" source is definitely not intended to match an output argument,
+ // and it causes false positives if we let it.
+ //
+ // This case goes together with the similar (but not identical) rule in
+ // `nodeIsBarrierIn`.
+ result = DataFlow::definitionByReferenceNodeFromArgument(node) and
+ not argv(node.(VariableAccess).getTarget())
}
private class DefaultTaintTrackingCfg extends DataFlow::Configuration {
@@ -216,16 +206,27 @@ private predicate nodeIsBarrierIn(DataFlow::Node node) {
cached
private predicate commonTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
- instructionToInstructionTaintStep(fromNode.asInstruction(), toNode.asInstruction())
- or
operandToInstructionTaintStep(fromNode.asOperand(), toNode.asInstruction())
or
- operandToOperandTaintStep(fromNode.asOperand(), toNode.asOperand())
+ instructionToOperandTaintStep(fromNode.asInstruction(), toNode.asOperand())
}
-private predicate operandToOperandTaintStep(Operand fromOperand, Operand toOperand) {
+private predicate instructionToOperandTaintStep(Instruction fromInstr, Operand toOperand) {
+ // Propagate flow from the definition of an operand to the operand, even when the overlap is inexact.
+ // We only do this in certain cases:
+ // 1. The instruction's result must not be conflated, and
+ // 2. The instruction's result type is one the types where we expect element-to-object flow. Currently
+ // this is array types and union types. This matches the other two cases of element-to-object flow in
+ // `DefaultTaintTracking`.
+ toOperand.getAnyDef() = fromInstr and
+ not fromInstr.isResultConflated() and
+ (
+ fromInstr.getResultType() instanceof ArrayType or
+ fromInstr.getResultType() instanceof Union
+ )
+ or
exists(ReadSideEffectInstruction readInstr |
- fromOperand = readInstr.getArgumentOperand() and
+ fromInstr = readInstr.getArgumentDef() and
toOperand = readInstr.getSideEffectOperand()
)
}
@@ -268,18 +269,18 @@ private predicate operandToInstructionTaintStep(Operand fromOperand, Instruction
outInstr.getPrimaryInstruction() = call
)
)
-}
-
-private predicate instructionToInstructionTaintStep(Instruction i1, Instruction i2) {
+ or
// Flow through pointer dereference
- i2.(LoadInstruction).getSourceAddress() = i1
+ toInstr.(LoadInstruction).getSourceAddressOperand() = fromOperand
or
// Flow through partial reads of arrays and unions
- i2.(LoadInstruction).getSourceValueOperand().getAnyDef() = i1 and
- not i1.isResultConflated() and
- (
- i1.getResultType() instanceof ArrayType or
- i1.getResultType() instanceof Union
+ toInstr.(LoadInstruction).getSourceValueOperand() = fromOperand and
+ exists(Instruction fromInstr | fromInstr = fromOperand.getAnyDef() |
+ not fromInstr.isResultConflated() and
+ (
+ fromInstr.getResultType() instanceof ArrayType or
+ fromInstr.getResultType() instanceof Union
+ )
)
or
// Unary instructions tend to preserve enough information in practice that we
@@ -289,63 +290,54 @@ private predicate instructionToInstructionTaintStep(Instruction i1, Instruction
// `FieldAddressInstruction` could cause flow into one field to come out an
// unrelated field. This would happen across function boundaries, where the IR
// would not be able to match loads to stores.
- i2.(UnaryInstruction).getUnary() = i1 and
+ toInstr.(UnaryInstruction).getUnaryOperand() = fromOperand and
(
- not i2 instanceof FieldAddressInstruction
+ not toInstr instanceof FieldAddressInstruction
or
- i2.(FieldAddressInstruction).getField().getDeclaringType() instanceof Union
+ toInstr.(FieldAddressInstruction).getField().getDeclaringType() instanceof Union
)
or
- // Flow out of definition-by-reference
- i2.(ChiInstruction).getPartial() = i1.(WriteSideEffectInstruction) and
- not i2.isResultConflated()
- or
// Flow from an element to an array or union that contains it.
- i2.(ChiInstruction).getPartial() = i1 and
- not i2.isResultConflated() and
- exists(Type t | i2.getResultLanguageType().hasType(t, false) |
+ toInstr.(ChiInstruction).getPartialOperand() = fromOperand and
+ not toInstr.isResultConflated() and
+ exists(Type t | toInstr.getResultLanguageType().hasType(t, false) |
t instanceof Union
or
t instanceof ArrayType
)
or
exists(BinaryInstruction bin |
- bin = i2 and
- predictableInstruction(i2.getAnOperand().getDef()) and
- i1 = i2.getAnOperand().getDef()
+ bin = toInstr and
+ predictableInstruction(toInstr.getAnOperand().getDef()) and
+ fromOperand = toInstr.getAnOperand()
)
or
// This is part of the translation of `a[i]`, where we want taint to flow
// from `a`.
- i2.(PointerAddInstruction).getLeft() = i1
- or
- // Until we have from through indirections across calls, we'll take flow out
- // of the parameter and into its indirection.
- exists(IRFunction f, Parameter parameter |
- i1 = getInitializeParameter(f, parameter) and
- i2 = getInitializeIndirection(f, parameter)
- )
+ toInstr.(PointerAddInstruction).getLeftOperand() = fromOperand
or
// Until we have flow through indirections across calls, we'll take flow out
// of the indirection and into the argument.
// When we get proper flow through indirections across calls, this code can be
// moved to `adjusedSink` or possibly into the `DataFlow::ExprNode` class.
exists(ReadSideEffectInstruction read |
- read.getAnOperand().(SideEffectOperand).getAnyDef() = i1 and
- read.getArgumentDef() = i2
+ read.getSideEffectOperand() = fromOperand and
+ read.getArgumentDef() = toInstr
)
-}
-
-pragma[noinline]
-private InitializeIndirectionInstruction getInitializeIndirection(IRFunction f, Parameter p) {
- result.getParameter() = p and
- result.getEnclosingIRFunction() = f
-}
-
-pragma[noinline]
-private InitializeParameterInstruction getInitializeParameter(IRFunction f, Parameter p) {
- result.getParameter() = p and
- result.getEnclosingIRFunction() = f
+ or
+ // Until we have from through indirections across calls, we'll take flow out
+ // of the parameter and into its indirection.
+ // `InitializeIndirectionInstruction` only has a single operand: the address of the
+ // value whose indirection we are initializing. When initializing an indirection of a parameter `p`,
+ // the IR looks like this:
+ // ```
+ // m1 = InitializeParameter[p] : &r1
+ // r2 = Load[p] : r2, m1
+ // m3 = InitializeIndirection[p] : &r2
+ // ```
+ // So by having flow from `r2` to `m3` we're enabling flow from `m1` to `m3`. This relies on the
+ // `LoadOperand`'s overlap being exact.
+ toInstr.(InitializeIndirectionInstruction).getAnOperand() = fromOperand
}
/**
@@ -547,9 +539,15 @@ module TaintedWithPath {
* a characteristic predicate.
*/
class TaintTrackingConfiguration extends TSingleton {
+ /** Override this to specify which elements are sources in this configuration. */
+ predicate isSource(Expr source) { exists(getNodeForSource(source)) }
+
/** Override this to specify which elements are sinks in this configuration. */
abstract predicate isSink(Element e);
+ /** Override this to specify which expressions are barriers in this configuration. */
+ predicate isBarrier(Expr e) { nodeIsBarrier(getNodeForExpr(e)) }
+
/**
* Override this predicate to `any()` to allow taint to flow through global
* variables.
@@ -563,7 +561,11 @@ module TaintedWithPath {
private class AdjustedConfiguration extends DataFlow3::Configuration {
AdjustedConfiguration() { this = "AdjustedConfiguration" }
- override predicate isSource(DataFlow::Node source) { source = getNodeForSource(_) }
+ override predicate isSource(DataFlow::Node source) {
+ exists(TaintTrackingConfiguration cfg, Expr e |
+ cfg.isSource(e) and source = getNodeForExpr(e)
+ )
+ }
override predicate isSink(DataFlow::Node sink) {
exists(TaintTrackingConfiguration cfg | cfg.isSink(adjustedSink(sink)))
@@ -579,7 +581,9 @@ module TaintedWithPath {
)
}
- override predicate isBarrier(DataFlow::Node node) { nodeIsBarrier(node) }
+ override predicate isBarrier(DataFlow::Node node) {
+ exists(TaintTrackingConfiguration cfg, Expr e | cfg.isBarrier(e) and node = getNodeForExpr(e))
+ }
override predicate isBarrierIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
}
@@ -606,7 +610,8 @@ module TaintedWithPath {
exists(AdjustedConfiguration cfg, DataFlow3::Node sourceNode, DataFlow3::Node sinkNode |
cfg.hasFlow(sourceNode, sinkNode)
|
- sourceNode = getNodeForSource(e)
+ sourceNode = getNodeForExpr(e) and
+ exists(TaintTrackingConfiguration ttCfg | ttCfg.isSource(e))
or
e = adjustedSink(sinkNode) and
exists(TaintTrackingConfiguration ttCfg | ttCfg.isSink(e))
@@ -660,7 +665,7 @@ module TaintedWithPath {
/** A PathNode whose `Element` is a source. It may also be a sink. */
private class InitialPathNode extends EndpointPathNode {
- InitialPathNode() { exists(getNodeForSource(this.inner())) }
+ InitialPathNode() { exists(TaintTrackingConfiguration cfg | cfg.isSource(this.inner())) }
}
/** A PathNode whose `Element` is a sink. It may also be a source. */
@@ -682,14 +687,14 @@ module TaintedWithPath {
// Same for the first node
exists(WrapPathNode sourceNode |
DataFlow3::PathGraph::edges(sourceNode.inner(), b.(WrapPathNode).inner()) and
- sourceNode.inner().getNode() = getNodeForSource(a.(InitialPathNode).inner())
+ sourceNode.inner().getNode() = getNodeForExpr(a.(InitialPathNode).inner())
)
or
// Finally, handle the case where the path goes directly from a source to a
// sink, meaning that they both need to be translated.
exists(WrapPathNode sinkNode, WrapPathNode sourceNode |
DataFlow3::PathGraph::edges(sourceNode.inner(), sinkNode.inner()) and
- sourceNode.inner().getNode() = getNodeForSource(a.(InitialPathNode).inner()) and
+ sourceNode.inner().getNode() = getNodeForExpr(a.(InitialPathNode).inner()) and
b.(FinalPathNode).inner() = adjustedSink(sinkNode.inner().getNode())
)
}
@@ -712,7 +717,7 @@ module TaintedWithPath {
predicate taintedWithPath(Expr source, Element tainted, PathNode sourceNode, PathNode sinkNode) {
exists(AdjustedConfiguration cfg, DataFlow3::Node flowSource, DataFlow3::Node flowSink |
source = sourceNode.(InitialPathNode).inner() and
- flowSource = getNodeForSource(source) and
+ flowSource = getNodeForExpr(source) and
cfg.hasFlow(flowSource, flowSink) and
tainted = adjustedSink(flowSink) and
tainted = sinkNode.(FinalPathNode).inner()
@@ -734,8 +739,8 @@ module TaintedWithPath {
* through a global variable.
*/
predicate taintedWithoutGlobals(Element tainted) {
- exists(PathNode sourceNode, FinalPathNode sinkNode |
- sourceNode.(WrapPathNode).inner().getNode() = getNodeForSource(_) and
+ exists(AdjustedConfiguration cfg, PathNode sourceNode, FinalPathNode sinkNode |
+ cfg.isSource(sourceNode.(WrapPathNode).inner().getNode()) and
edgesWithoutGlobals+(sourceNode, sinkNode) and
tainted = sinkNode.inner()
)
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
index d9f5acdd279..59cc8d529a7 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
@@ -3598,6 +3598,7 @@ private module FlowExploration {
or
exists(PartialPathNodeRev mid |
revPartialPathStep(mid, node, sc1, sc2, ap, config) and
+ not clearsContent(node, ap.getHead()) and
not fullBarrier(node, config) and
distSink(node.getEnclosingCallable(), config) <= config.explorationLimit()
)
@@ -3611,6 +3612,7 @@ private module FlowExploration {
exists(PartialPathNodeFwd mid |
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
+ not clearsContent(node, ap.getHead().getContent()) and
if node instanceof CastingNode
then compatibleTypes(getNodeType(node), ap.getType())
else any()
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll
index d9f5acdd279..59cc8d529a7 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll
@@ -3598,6 +3598,7 @@ private module FlowExploration {
or
exists(PartialPathNodeRev mid |
revPartialPathStep(mid, node, sc1, sc2, ap, config) and
+ not clearsContent(node, ap.getHead()) and
not fullBarrier(node, config) and
distSink(node.getEnclosingCallable(), config) <= config.explorationLimit()
)
@@ -3611,6 +3612,7 @@ private module FlowExploration {
exists(PartialPathNodeFwd mid |
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
+ not clearsContent(node, ap.getHead().getContent()) and
if node instanceof CastingNode
then compatibleTypes(getNodeType(node), ap.getType())
else any()
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll
index d9f5acdd279..59cc8d529a7 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll
@@ -3598,6 +3598,7 @@ private module FlowExploration {
or
exists(PartialPathNodeRev mid |
revPartialPathStep(mid, node, sc1, sc2, ap, config) and
+ not clearsContent(node, ap.getHead()) and
not fullBarrier(node, config) and
distSink(node.getEnclosingCallable(), config) <= config.explorationLimit()
)
@@ -3611,6 +3612,7 @@ private module FlowExploration {
exists(PartialPathNodeFwd mid |
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
+ not clearsContent(node, ap.getHead().getContent()) and
if node instanceof CastingNode
then compatibleTypes(getNodeType(node), ap.getType())
else any()
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll
index d9f5acdd279..59cc8d529a7 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll
@@ -3598,6 +3598,7 @@ private module FlowExploration {
or
exists(PartialPathNodeRev mid |
revPartialPathStep(mid, node, sc1, sc2, ap, config) and
+ not clearsContent(node, ap.getHead()) and
not fullBarrier(node, config) and
distSink(node.getEnclosingCallable(), config) <= config.explorationLimit()
)
@@ -3611,6 +3612,7 @@ private module FlowExploration {
exists(PartialPathNodeFwd mid |
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
+ not clearsContent(node, ap.getHead().getContent()) and
if node instanceof CastingNode
then compatibleTypes(getNodeType(node), ap.getType())
else any()
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
index 15fc2fa1d9a..cbc44ac920d 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
@@ -716,21 +716,15 @@ private predicate modelFlow(Operand opFrom, Instruction iTo) {
iTo = call
or
exists(int index, WriteSideEffectInstruction outNode |
- modelOut.isParameterDeref(index) and
+ modelOut.isParameterDerefOrQualifierObject(index) and
iTo = outNode and
outNode = getSideEffectFor(call, index)
)
- or
- exists(WriteSideEffectInstruction outNode |
- modelOut.isQualifierObject() and
- iTo = outNode and
- outNode = getSideEffectFor(call, -1)
- )
) and
(
exists(int index |
- modelIn.isParameter(index) and
- opFrom = call.getPositionalArgumentOperand(index)
+ modelIn.isParameterOrQualifierAddress(index) and
+ opFrom = call.getArgumentOperand(index)
)
or
exists(int index, ReadSideEffectInstruction read |
@@ -739,9 +733,6 @@ private predicate modelFlow(Operand opFrom, Instruction iTo) {
opFrom = read.getSideEffectOperand()
)
or
- modelIn.isQualifierAddress() and
- opFrom = call.getThisArgumentOperand()
- or
exists(ReadSideEffectInstruction read |
modelIn.isQualifierObject() and
read = getSideEffectFor(call, -1) and
diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/IRType.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/IRType.qll
index 3bf3bf2e276..e0bccafae6b 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/implementation/IRType.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/IRType.qll
@@ -341,7 +341,7 @@ module IRTypeConsistency {
query predicate multipleIRTypes(Language::LanguageType type, string message) {
strictcount(type.getIRType()) > 1 and
message =
- "`LanguageType` " + type.getAQlClass() + " has multiple `IRType`s: " +
+ "`LanguageType` " + type + " has multiple `IRType`s: " +
concat(type.getIRType().toString(), ", ")
}
diff --git a/cpp/ql/src/semmle/code/cpp/models/Models.qll b/cpp/ql/src/semmle/code/cpp/models/Models.qll
index f2612d69b84..c36c1da9bcd 100644
--- a/cpp/ql/src/semmle/code/cpp/models/Models.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/Models.qll
@@ -15,6 +15,10 @@ private import implementations.Strcat
private import implementations.Strcpy
private import implementations.Strdup
private import implementations.Strftime
+private import implementations.Strtok
+private import implementations.Strset
+private import implementations.Strcrement
+private import implementations.Strnextc
private import implementations.StdContainer
private import implementations.StdPair
private import implementations.StdMap
@@ -23,3 +27,6 @@ private import implementations.StdString
private import implementations.Swap
private import implementations.GetDelim
private import implementations.SmartPointer
+private import implementations.Sscanf
+private import implementations.Send
+private import implementations.Recv
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Allocation.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Allocation.qll
index 709d3c3db00..91ec1525834 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/Allocation.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Allocation.qll
@@ -14,95 +14,58 @@ private class MallocAllocationFunction extends AllocationFunction {
int sizeArg;
MallocAllocationFunction() {
- exists(string name |
- hasGlobalOrStdName(name) and
- // malloc(size)
- (name = "malloc" and sizeArg = 0)
- or
- hasGlobalName(name) and
- (
- // ExAllocatePool(type, size)
- name = "ExAllocatePool" and sizeArg = 1
- or
- // ExAllocatePool(type, size, tag)
- name = "ExAllocatePoolWithTag" and sizeArg = 1
- or
- // ExAllocatePoolWithTagPriority(type, size, tag, priority)
- name = "ExAllocatePoolWithTagPriority" and sizeArg = 1
- or
- // ExAllocatePoolWithQuota(type, size)
- name = "ExAllocatePoolWithQuota" and sizeArg = 1
- or
- // ExAllocatePoolWithQuotaTag(type, size, tag)
- name = "ExAllocatePoolWithQuotaTag" and sizeArg = 1
- or
- // IoAllocateMdl(address, size, flag, flag, irp)
- name = "IoAllocateMdl" and sizeArg = 1
- or
- // IoAllocateErrorLogEntry(object, size)
- name = "IoAllocateErrorLogEntry" and sizeArg = 1
- or
- // MmAllocateContiguousMemory(size, maxaddress)
- name = "MmAllocateContiguousMemory" and sizeArg = 0
- or
- // MmAllocateContiguousNodeMemory(size, minaddress, maxaddress, bound, flag, prefer)
- name = "MmAllocateContiguousNodeMemory" and sizeArg = 0
- or
- // MmAllocateContiguousMemorySpecifyCache(size, minaddress, maxaddress, bound, type)
- name = "MmAllocateContiguousMemorySpecifyCache" and sizeArg = 0
- or
- // MmAllocateContiguousMemorySpecifyCacheNode(size, minaddress, maxaddress, bound, type, prefer)
- name = "MmAllocateContiguousMemorySpecifyCacheNode" and sizeArg = 0
- or
- // MmAllocateNonCachedMemory(size)
- name = "MmAllocateNonCachedMemory" and sizeArg = 0
- or
- // MmAllocateMappingAddress(size, tag)
- name = "MmAllocateMappingAddress" and sizeArg = 0
- or
- // MmAllocatePagesForMdl(minaddress, maxaddress, skip, size)
- name = "MmAllocatePagesForMdl" and sizeArg = 3
- or
- // MmAllocatePagesForMdlEx(minaddress, maxaddress, skip, size, type, flags)
- name = "MmAllocatePagesForMdlEx" and sizeArg = 3
- or
- // MmAllocateNodePagesForMdlEx(minaddress, maxaddress, skip, size, type, prefer, flags)
- name = "MmAllocateNodePagesForMdlEx" and sizeArg = 3
- or
- // LocalAlloc(flags, size)
- name = "LocalAlloc" and sizeArg = 1
- or
- // GlobalAlloc(flags, size)
- name = "GlobalAlloc" and sizeArg = 1
- or
- // HeapAlloc(heap, flags, size)
- name = "HeapAlloc" and sizeArg = 2
- or
- // VirtualAlloc(address, size, type, flag)
- name = "VirtualAlloc" and sizeArg = 1
- or
- // CoTaskMemAlloc(size)
- name = "CoTaskMemAlloc" and sizeArg = 0
- or
- // kmem_alloc(size, flags)
- name = "kmem_alloc" and sizeArg = 0
- or
- // kmem_zalloc(size, flags)
- name = "kmem_zalloc" and sizeArg = 0
- or
- // CRYPTO_malloc(size_t num, const char *file, int line)
- name = "CRYPTO_malloc" and sizeArg = 0
- or
- // CRYPTO_zalloc(size_t num, const char *file, int line)
- name = "CRYPTO_zalloc" and sizeArg = 0
- or
- // CRYPTO_secure_malloc(size_t num, const char *file, int line)
- name = "CRYPTO_secure_malloc" and sizeArg = 0
- or
- // CRYPTO_secure_zalloc(size_t num, const char *file, int line)
- name = "CRYPTO_secure_zalloc" and sizeArg = 0
- )
- )
+ // --- C library allocation
+ hasGlobalOrStdName("malloc") and // malloc(size)
+ sizeArg = 0
+ or
+ hasGlobalName([
+ // --- Windows Memory Management for Windows Drivers
+ "MmAllocateContiguousMemory", // MmAllocateContiguousMemory(size, maxaddress)
+ "MmAllocateContiguousNodeMemory", // MmAllocateContiguousNodeMemory(size, minaddress, maxaddress, bound, flag, prefer)
+ "MmAllocateContiguousMemorySpecifyCache", // MmAllocateContiguousMemorySpecifyCache(size, minaddress, maxaddress, bound, type)
+ "MmAllocateContiguousMemorySpecifyCacheNode", // MmAllocateContiguousMemorySpecifyCacheNode(size, minaddress, maxaddress, bound, type, prefer)
+ "MmAllocateNonCachedMemory", // MmAllocateNonCachedMemory(size)
+ "MmAllocateMappingAddress", // MmAllocateMappingAddress(size, tag)
+ // --- Windows COM allocation
+ "CoTaskMemAlloc", // CoTaskMemAlloc(size)
+ // --- Solaris/BSD kernel memory allocator
+ "kmem_alloc", // kmem_alloc(size, flags)
+ "kmem_zalloc", // kmem_zalloc(size, flags)
+ // --- OpenSSL memory allocation
+ "CRYPTO_malloc", // CRYPTO_malloc(size_t num, const char *file, int line)
+ "CRYPTO_zalloc", // CRYPTO_zalloc(size_t num, const char *file, int line)
+ "CRYPTO_secure_malloc", // CRYPTO_secure_malloc(size_t num, const char *file, int line)
+ "CRYPTO_secure_zalloc" // CRYPTO_secure_zalloc(size_t num, const char *file, int line)
+ ]) and
+ sizeArg = 0
+ or
+ hasGlobalName([
+ // --- Windows Memory Management for Windows Drivers
+ "ExAllocatePool", // ExAllocatePool(type, size)
+ "ExAllocatePoolWithTag", // ExAllocatePool(type, size, tag)
+ "ExAllocatePoolWithTagPriority", // ExAllocatePoolWithTagPriority(type, size, tag, priority)
+ "ExAllocatePoolWithQuota", // ExAllocatePoolWithQuota(type, size)
+ "ExAllocatePoolWithQuotaTag", // ExAllocatePoolWithQuotaTag(type, size, tag)
+ "IoAllocateMdl", // IoAllocateMdl(address, size, flag, flag, irp)
+ "IoAllocateErrorLogEntry", // IoAllocateErrorLogEntry(object, size)
+ // --- Windows Global / Local legacy allocation
+ "LocalAlloc", // LocalAlloc(flags, size)
+ "GlobalAlloc", // GlobalAlloc(flags, size)
+ // --- Windows System Services allocation
+ "VirtualAlloc" // VirtualAlloc(address, size, type, flag)
+ ]) and
+ sizeArg = 1
+ or
+ hasGlobalName(["HeapAlloc"]) and // HeapAlloc(heap, flags, size)
+ sizeArg = 2
+ or
+ hasGlobalName([
+ // --- Windows Memory Management for Windows Drivers
+ "MmAllocatePagesForMdl", // MmAllocatePagesForMdl(minaddress, maxaddress, skip, size)
+ "MmAllocatePagesForMdlEx", // MmAllocatePagesForMdlEx(minaddress, maxaddress, skip, size, type, flags)
+ "MmAllocateNodePagesForMdlEx" // MmAllocateNodePagesForMdlEx(minaddress, maxaddress, skip, size, type, prefer, flags)
+ ]) and
+ sizeArg = 3
}
override int getSizeArg() { result = sizeArg }
@@ -116,16 +79,14 @@ private class AllocaAllocationFunction extends AllocationFunction {
int sizeArg;
AllocaAllocationFunction() {
- exists(string name |
- hasGlobalName(name) and
- (
- // alloca(size)
- name = "alloca" and sizeArg = 0
- or
- // __builtin_alloca(size)
- name = "__builtin_alloca" and sizeArg = 0
- )
- )
+ hasGlobalName([
+ // --- stack allocation
+ "alloca", // // alloca(size)
+ "__builtin_alloca", // __builtin_alloca(size)
+ "_alloca", // _alloca(size)
+ "_malloca" // _malloca(size)
+ ]) and
+ sizeArg = 0
}
override int getSizeArg() { result = sizeArg }
@@ -142,11 +103,10 @@ private class CallocAllocationFunction extends AllocationFunction {
int multArg;
CallocAllocationFunction() {
- exists(string name |
- hasGlobalOrStdName(name) and
- // calloc(num, size)
- (name = "calloc" and sizeArg = 1 and multArg = 0)
- )
+ // --- C library allocation
+ hasGlobalOrStdName("calloc") and // calloc(num, size)
+ sizeArg = 1 and
+ multArg = 0
}
override int getSizeArg() { result = sizeArg }
@@ -163,29 +123,26 @@ private class ReallocAllocationFunction extends AllocationFunction {
int reallocArg;
ReallocAllocationFunction() {
- exists(string name |
- hasGlobalOrStdName(name) and
- // realloc(ptr, size)
- (name = "realloc" and sizeArg = 1 and reallocArg = 0)
- or
- hasGlobalName(name) and
- (
- // LocalReAlloc(ptr, size, flags)
- name = "LocalReAlloc" and sizeArg = 1 and reallocArg = 0
- or
- // GlobalReAlloc(ptr, size, flags)
- name = "GlobalReAlloc" and sizeArg = 1 and reallocArg = 0
- or
- // HeapReAlloc(heap, flags, ptr, size)
- name = "HeapReAlloc" and sizeArg = 3 and reallocArg = 2
- or
- // CoTaskMemRealloc(ptr, size)
- name = "CoTaskMemRealloc" and sizeArg = 1 and reallocArg = 0
- or
- // CRYPTO_realloc(void *addr, size_t num, const char *file, int line);
- name = "CRYPTO_realloc" and sizeArg = 1 and reallocArg = 0
- )
- )
+ // --- C library allocation
+ hasGlobalOrStdName("realloc") and // realloc(ptr, size)
+ sizeArg = 1 and
+ reallocArg = 0
+ or
+ hasGlobalName([
+ // --- Windows Global / Local legacy allocation
+ "LocalReAlloc", // LocalReAlloc(ptr, size, flags)
+ "GlobalReAlloc", // GlobalReAlloc(ptr, size, flags)
+ // --- Windows COM allocation
+ "CoTaskMemRealloc", // CoTaskMemRealloc(ptr, size)
+ // --- OpenSSL memory allocation
+ "CRYPTO_realloc" // CRYPTO_realloc(void *addr, size_t num, const char *file, int line)
+ ]) and
+ sizeArg = 1 and
+ reallocArg = 0
+ or
+ hasGlobalName("HeapReAlloc") and // HeapReAlloc(heap, flags, ptr, size)
+ sizeArg = 3 and
+ reallocArg = 2
}
override int getSizeArg() { result = sizeArg }
@@ -199,40 +156,20 @@ private class ReallocAllocationFunction extends AllocationFunction {
*/
private class SizelessAllocationFunction extends AllocationFunction {
SizelessAllocationFunction() {
- exists(string name |
- hasGlobalName(name) and
- (
- // ExAllocateFromLookasideListEx(list)
- name = "ExAllocateFromLookasideListEx"
- or
- // ExAllocateFromPagedLookasideList(list)
- name = "ExAllocateFromPagedLookasideList"
- or
- // ExAllocateFromNPagedLookasideList(list)
- name = "ExAllocateFromNPagedLookasideList"
- or
- // ExAllocateTimer(callback, context, attributes)
- name = "ExAllocateTimer"
- or
- // IoAllocateWorkItem(object)
- name = "IoAllocateWorkItem"
- or
- // MmMapLockedPagesWithReservedMapping(address, tag, list, type)
- name = "MmMapLockedPagesWithReservedMapping"
- or
- // MmMapLockedPages(list, mode)
- name = "MmMapLockedPages"
- or
- // MmMapLockedPagesSpecifyCache(list, mode, type, address, flag, flag)
- name = "MmMapLockedPagesSpecifyCache"
- or
- // pool_get(pool, flags)
- name = "pool_get"
- or
- // pool_cache_get(pool, flags)
- name = "pool_cache_get"
- )
- )
+ hasGlobalName([
+ // --- Windows Memory Management for Windows Drivers
+ "ExAllocateFromLookasideListEx", // ExAllocateFromLookasideListEx(list)
+ "ExAllocateFromPagedLookasideList", // ExAllocateFromPagedLookasideList(list)
+ "ExAllocateFromNPagedLookasideList", // ExAllocateFromNPagedLookasideList(list)
+ "ExAllocateTimer", // ExAllocateTimer(callback, context, attributes)
+ "IoAllocateWorkItem", // IoAllocateWorkItem(object)
+ "MmMapLockedPagesWithReservedMapping", // MmMapLockedPagesWithReservedMapping(address, tag, list, type)
+ "MmMapLockedPages", // MmMapLockedPages(list, mode)
+ "MmMapLockedPagesSpecifyCache", // MmMapLockedPagesSpecifyCache(list, mode, type, address, flag, flag)
+ // --- NetBSD pool manager
+ "pool_get", // pool_get(pool, flags)
+ "pool_cache_get" // pool_cache_get(pool, flags)
+ ])
}
}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Deallocation.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Deallocation.qll
index 3c65aa699be..b56c8c21949 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/Deallocation.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Deallocation.qll
@@ -13,77 +13,43 @@ private class StandardDeallocationFunction extends DeallocationFunction {
int freedArg;
StandardDeallocationFunction() {
- exists(string name |
- hasGlobalName(name) and
- (
- name = "free" and freedArg = 0
- or
- name = "realloc" and freedArg = 0
- or
- name = "CRYPTO_free" and freedArg = 0
- or
- name = "CRYPTO_secure_free" and freedArg = 0
- )
- or
- hasGlobalOrStdName(name) and
- (
- name = "ExFreePoolWithTag" and freedArg = 0
- or
- name = "ExFreeToLookasideListEx" and freedArg = 1
- or
- name = "ExFreeToPagedLookasideList" and freedArg = 1
- or
- name = "ExFreeToNPagedLookasideList" and freedArg = 1
- or
- name = "ExDeleteTimer" and freedArg = 0
- or
- name = "IoFreeMdl" and freedArg = 0
- or
- name = "IoFreeWorkItem" and freedArg = 0
- or
- name = "IoFreeErrorLogEntry" and freedArg = 0
- or
- name = "MmFreeContiguousMemory" and freedArg = 0
- or
- name = "MmFreeContiguousMemorySpecifyCache" and freedArg = 0
- or
- name = "MmFreeNonCachedMemory" and freedArg = 0
- or
- name = "MmFreeMappingAddress" and freedArg = 0
- or
- name = "MmFreePagesFromMdl" and freedArg = 0
- or
- name = "MmUnmapReservedMapping" and freedArg = 0
- or
- name = "MmUnmapLockedPages" and freedArg = 0
- or
- name = "LocalFree" and freedArg = 0
- or
- name = "GlobalFree" and freedArg = 0
- or
- name = "HeapFree" and freedArg = 2
- or
- name = "VirtualFree" and freedArg = 0
- or
- name = "CoTaskMemFree" and freedArg = 0
- or
- name = "SysFreeString" and freedArg = 0
- or
- name = "LocalReAlloc" and freedArg = 0
- or
- name = "GlobalReAlloc" and freedArg = 0
- or
- name = "HeapReAlloc" and freedArg = 2
- or
- name = "CoTaskMemRealloc" and freedArg = 0
- or
- name = "kmem_free" and freedArg = 0
- or
- name = "pool_put" and freedArg = 1
- or
- name = "pool_cache_put" and freedArg = 1
- )
- )
+ hasGlobalName([
+ // --- C library allocation
+ "free", "realloc",
+ // --- OpenSSL memory allocation
+ "CRYPTO_free", "CRYPTO_secure_free"
+ ]) and
+ freedArg = 0
+ or
+ hasGlobalOrStdName([
+ // --- Windows Memory Management for Windows Drivers
+ "ExFreePoolWithTag", "ExDeleteTimer", "IoFreeMdl", "IoFreeWorkItem", "IoFreeErrorLogEntry",
+ "MmFreeContiguousMemory", "MmFreeContiguousMemorySpecifyCache", "MmFreeNonCachedMemory",
+ "MmFreeMappingAddress", "MmFreePagesFromMdl", "MmUnmapReservedMapping",
+ "MmUnmapLockedPages",
+ // --- Windows Global / Local legacy allocation
+ "LocalFree", "GlobalFree", "LocalReAlloc", "GlobalReAlloc",
+ // --- Windows System Services allocation
+ "VirtualFree",
+ // --- Windows COM allocation
+ "CoTaskMemFree", "CoTaskMemRealloc",
+ // --- Windows Automation
+ "SysFreeString",
+ // --- Solaris/BSD kernel memory allocator
+ "kmem_free"
+ ]) and
+ freedArg = 0
+ or
+ hasGlobalOrStdName([
+ // --- Windows Memory Management for Windows Drivers
+ "ExFreeToLookasideListEx", "ExFreeToPagedLookasideList", "ExFreeToNPagedLookasideList",
+ // --- NetBSD pool manager
+ "pool_put", "pool_cache_put"
+ ]) and
+ freedArg = 1
+ or
+ hasGlobalOrStdName(["HeapFree", "HeapReAlloc"]) and
+ freedArg = 2
}
override int getFreedArg() { result = freedArg }
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Fread.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Fread.qll
index 11568c8a974..9524b18b0a2 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/Fread.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Fread.qll
@@ -1,7 +1,7 @@
import semmle.code.cpp.models.interfaces.Alias
import semmle.code.cpp.models.interfaces.FlowSource
-private class Fread extends AliasFunction, RemoteFlowFunction {
+private class Fread extends AliasFunction, RemoteFlowSourceFunction {
Fread() { this.hasGlobalName("fread") }
override predicate parameterNeverEscapes(int n) {
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/GetDelim.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/GetDelim.qll
index 3561caee7fb..e2015406346 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/GetDelim.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/GetDelim.qll
@@ -7,7 +7,7 @@ import semmle.code.cpp.models.interfaces.FlowSource
* The standard functions `getdelim`, `getwdelim` and the glibc variant `__getdelim`.
*/
private class GetDelimFunction extends TaintFunction, AliasFunction, SideEffectFunction,
- RemoteFlowFunction {
+ RemoteFlowSourceFunction {
GetDelimFunction() { hasGlobalName(["getdelim", "getwdelim", "__getdelim"]) }
override predicate hasTaintFlow(FunctionInput i, FunctionOutput o) {
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Getenv.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Getenv.qll
index 9761a4293a8..93b601de752 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/Getenv.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Getenv.qll
@@ -8,7 +8,7 @@ import semmle.code.cpp.models.interfaces.FlowSource
/**
* The POSIX function `getenv`.
*/
-class Getenv extends LocalFlowFunction {
+class Getenv extends LocalFlowSourceFunction {
Getenv() { this.hasGlobalName("getenv") }
override predicate hasLocalFlowSource(FunctionOutput output, string description) {
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Gets.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Gets.qll
index 91653cd6b37..f698a1209f4 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/Gets.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Gets.qll
@@ -14,7 +14,7 @@ import semmle.code.cpp.models.interfaces.FlowSource
* The standard functions `gets` and `fgets`.
*/
private class GetsFunction extends DataFlowFunction, TaintFunction, ArrayFunction, AliasFunction,
- SideEffectFunction, RemoteFlowFunction {
+ SideEffectFunction, RemoteFlowSourceFunction {
GetsFunction() {
// gets(str)
// fgets(str, num, stream)
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/IdentityFunction.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/IdentityFunction.qll
index 0178cd0f7ec..60afd2b25ef 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/IdentityFunction.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/IdentityFunction.qll
@@ -6,12 +6,9 @@ import semmle.code.cpp.models.interfaces.SideEffect
/**
* The standard function templates `std::move` and `std::forward`.
*/
-private class IdentityFunction extends DataFlowFunction, SideEffectFunction, AliasFunction {
- IdentityFunction() {
- this.getNamespace().getParentNamespace() instanceof GlobalNamespace and
- this.getNamespace().getName() = "std" and
- this.getName() = ["move", "forward"]
- }
+private class IdentityFunction extends DataFlowFunction, SideEffectFunction, AliasFunction,
+ FunctionTemplateInstantiation {
+ IdentityFunction() { this.hasQualifiedName("std", ["move", "forward"]) }
override predicate hasOnlySpecificReadSideEffects() { any() }
@@ -32,5 +29,7 @@ private class IdentityFunction extends DataFlowFunction, SideEffectFunction, Ali
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
// These functions simply return the argument value.
input.isParameter(0) and output.isReturnValue()
+ or
+ input.isParameterDeref(0) and output.isReturnValueDeref()
}
}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Iterator.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Iterator.qll
index 3a789a6aa25..a5b1c0e83ec 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/Iterator.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Iterator.qll
@@ -109,6 +109,8 @@ private class IteratorCrementOperator extends Operator, DataFlowFunction {
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
input = iteratorInput and
output.isReturnValue()
+ or
+ input.isParameterDeref(0) and output.isReturnValueDeref()
}
}
@@ -159,6 +161,8 @@ private class IteratorAssignArithmeticOperator extends Operator, DataFlowFunctio
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
input.isParameter(0) and
output.isReturnValue()
+ or
+ input.isParameterDeref(0) and output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -201,6 +205,9 @@ private class IteratorCrementMemberOperator extends MemberFunction, DataFlowFunc
or
input.isReturnValueDeref() and
output.isQualifierObject()
+ or
+ input.isQualifierObject() and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Memcpy.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Memcpy.qll
index 813c2834586..b7d8aed60fa 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/Memcpy.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Memcpy.qll
@@ -6,6 +6,7 @@
import semmle.code.cpp.Function
import semmle.code.cpp.models.interfaces.ArrayFunction
import semmle.code.cpp.models.interfaces.DataFlow
+import semmle.code.cpp.models.interfaces.Alias
import semmle.code.cpp.models.interfaces.SideEffect
import semmle.code.cpp.models.interfaces.Taint
@@ -13,32 +14,35 @@ import semmle.code.cpp.models.interfaces.Taint
* The standard functions `memcpy`, `memmove` and `bcopy`; and the gcc variant
* `__builtin___memcpy_chk`.
*/
-private class MemcpyFunction extends ArrayFunction, DataFlowFunction, SideEffectFunction {
+private class MemcpyFunction extends ArrayFunction, DataFlowFunction, SideEffectFunction,
+ AliasFunction {
MemcpyFunction() {
// memcpy(dest, src, num)
// memmove(dest, src, num)
// memmove(dest, src, num, remaining)
- this.hasName(["memcpy", "memmove", "__builtin___memcpy_chk"])
+ this.hasGlobalOrStdOrBslName(["memcpy", "memmove"])
or
// bcopy(src, dest, num)
- this.hasGlobalOrStdName("bcopy")
+ // mempcpy(dest, src, num)
+ // memccpy(dest, src, c, n)
+ this.hasGlobalName(["bcopy", mempcpy(), "memccpy", "__builtin___memcpy_chk"])
}
/**
* Gets the index of the parameter that is the source buffer for the copy.
*/
- int getParamSrc() { if this.hasGlobalOrStdName("bcopy") then result = 0 else result = 1 }
+ int getParamSrc() { if this.hasGlobalName("bcopy") then result = 0 else result = 1 }
/**
* Gets the index of the parameter that is the destination buffer for the
* copy.
*/
- int getParamDest() { if this.hasGlobalOrStdName("bcopy") then result = 1 else result = 0 }
+ int getParamDest() { if this.hasGlobalName("bcopy") then result = 1 else result = 0 }
/**
* Gets the index of the parameter that is the size of the copy (in bytes).
*/
- int getParamSize() { result = 2 }
+ int getParamSize() { if this.hasGlobalName("memccpy") then result = 3 else result = 2 }
override predicate hasArrayInput(int bufParam) { bufParam = getParamSrc() }
@@ -68,7 +72,10 @@ private class MemcpyFunction extends ArrayFunction, DataFlowFunction, SideEffect
override predicate hasOnlySpecificWriteSideEffects() { any() }
override predicate hasSpecificWriteSideEffect(ParameterIndex i, boolean buffer, boolean mustWrite) {
- i = getParamDest() and buffer = true and mustWrite = true
+ i = getParamDest() and
+ buffer = true and
+ // memccpy only writes until a given character `c` is found
+ (if this.hasGlobalName("memccpy") then mustWrite = false else mustWrite = true)
}
override predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) {
@@ -82,4 +89,21 @@ private class MemcpyFunction extends ArrayFunction, DataFlowFunction, SideEffect
i = getParamSrc()
)
}
+
+ override predicate parameterNeverEscapes(int index) {
+ index = getParamSrc()
+ or
+ this.hasGlobalName("bcopy") and index = getParamDest()
+ }
+
+ override predicate parameterEscapesOnlyViaReturn(int index) {
+ not this.hasGlobalName("bcopy") and index = getParamDest()
+ }
+
+ override predicate parameterIsAlwaysReturned(int index) {
+ not this.hasGlobalName(["bcopy", mempcpy(), "memccpy"]) and
+ index = getParamDest()
+ }
}
+
+private string mempcpy() { result = ["mempcpy", "wmempcpy"] }
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Memset.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Memset.qll
index 0436fbeead9..d646be0363d 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/Memset.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Memset.qll
@@ -15,13 +15,11 @@ import semmle.code.cpp.models.interfaces.SideEffect
private class MemsetFunction extends ArrayFunction, DataFlowFunction, AliasFunction,
SideEffectFunction {
MemsetFunction() {
- hasGlobalName("memset") or
- hasGlobalName("wmemset") or
- hasGlobalName("bzero") or
- hasGlobalName("__builtin_memset") or
- hasGlobalName("__builtin_memset_chk") or
- hasQualifiedName("std", "memset") or
- hasQualifiedName("std", "wmemset")
+ this.hasGlobalOrStdOrBslName("memset")
+ or
+ this.hasGlobalOrStdName("wmemset")
+ or
+ this.hasGlobalName([bzero(), "__builtin_memset", "__builtin_memset_chk"])
}
override predicate hasArrayOutput(int bufParam) { bufParam = 0 }
@@ -33,17 +31,17 @@ private class MemsetFunction extends ArrayFunction, DataFlowFunction, AliasFunct
override predicate hasArrayWithVariableSize(int bufParam, int countParam) {
bufParam = 0 and
- (if hasGlobalName("bzero") then countParam = 1 else countParam = 2)
+ (if hasGlobalName(bzero()) then countParam = 1 else countParam = 2)
}
- override predicate parameterNeverEscapes(int index) { hasGlobalName("bzero") and index = 0 }
+ override predicate parameterNeverEscapes(int index) { hasGlobalName(bzero()) and index = 0 }
override predicate parameterEscapesOnlyViaReturn(int index) {
- not hasGlobalName("bzero") and index = 0
+ not hasGlobalName(bzero()) and index = 0
}
override predicate parameterIsAlwaysReturned(int index) {
- not hasGlobalName("bzero") and index = 0
+ not hasGlobalName(bzero()) and index = 0
}
override predicate hasOnlySpecificReadSideEffects() { any() }
@@ -56,6 +54,8 @@ private class MemsetFunction extends ArrayFunction, DataFlowFunction, AliasFunct
override ParameterIndex getParameterSizeIndex(ParameterIndex i) {
i = 0 and
- if hasGlobalName("bzero") then result = 1 else result = 2
+ if hasGlobalName(bzero()) then result = 1 else result = 2
}
}
+
+private string bzero() { result = ["bzero", "explicit_bzero"] }
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Printf.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Printf.qll
index efa39849b14..ed201a14587 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/Printf.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Printf.qll
@@ -15,21 +15,15 @@ private class Printf extends FormattingFunction, AliasFunction {
Printf() {
this instanceof TopLevelFunction and
(
- hasGlobalOrStdName("printf") or
- hasGlobalName("printf_s") or
- hasGlobalOrStdName("wprintf") or
- hasGlobalName("wprintf_s") or
- hasGlobalName("g_printf")
+ hasGlobalOrStdOrBslName(["printf", "wprintf"]) or
+ hasGlobalName(["printf_s", "wprintf_s", "g_printf"])
) and
not exists(getDefinition().getFile().getRelativePath())
}
override int getFormatParameterIndex() { result = 0 }
- deprecated override predicate isWideCharDefault() {
- hasGlobalOrStdName("wprintf") or
- hasGlobalName("wprintf_s")
- }
+ deprecated override predicate isWideCharDefault() { hasName(["wprintf", "wprintf_s"]) }
override predicate isOutputGlobal() { any() }
@@ -47,8 +41,7 @@ private class Fprintf extends FormattingFunction {
Fprintf() {
this instanceof TopLevelFunction and
(
- hasGlobalOrStdName("fprintf") or
- hasGlobalOrStdName("fwprintf") or
+ hasGlobalOrStdOrBslName(["fprintf", "fwprintf"]) or
hasGlobalName("g_fprintf")
) and
not exists(getDefinition().getFile().getRelativePath())
@@ -56,7 +49,7 @@ private class Fprintf extends FormattingFunction {
override int getFormatParameterIndex() { result = 1 }
- deprecated override predicate isWideCharDefault() { hasGlobalOrStdName("fwprintf") }
+ deprecated override predicate isWideCharDefault() { hasName("fwprintf") }
override int getOutputParameterIndex(boolean isStream) { result = 0 and isStream = true }
}
@@ -68,26 +61,18 @@ private class Sprintf extends FormattingFunction {
Sprintf() {
this instanceof TopLevelFunction and
(
- // sprintf(dst, format, args...)
- hasGlobalOrStdName("sprintf")
+ hasGlobalOrStdOrBslName([
+ "sprintf", // sprintf(dst, format, args...)
+ "wsprintf" // wsprintf(dst, format, args...)
+ ])
or
- // _sprintf_l(dst, format, locale, args...)
- hasGlobalName("_sprintf_l")
- or
- // __swprintf_l(dst, format, locale, args...)
- hasGlobalName("__swprintf_l")
- or
- // wsprintf(dst, format, args...)
- hasGlobalOrStdName("wsprintf")
- or
- // g_strdup_printf(format, ...)
- hasGlobalName("g_strdup_printf")
- or
- // g_sprintf(dst, format, ...)
- hasGlobalName("g_sprintf")
- or
- // __builtin___sprintf_chk(dst, flag, os, format, ...)
- hasGlobalName("__builtin___sprintf_chk")
+ hasGlobalName([
+ "_sprintf_l", // _sprintf_l(dst, format, locale, args...)
+ "__swprintf_l", // __swprintf_l(dst, format, locale, args...)
+ "g_strdup_printf", // g_strdup_printf(format, ...)
+ "g_sprintf", // g_sprintf(dst, format, ...)
+ "__builtin___sprintf_chk" // __builtin___sprintf_chk(dst, flag, os, format, ...)
+ ])
) and
not exists(getDefinition().getFile().getRelativePath())
}
@@ -102,23 +87,20 @@ private class Sprintf extends FormattingFunction {
}
override int getFormatParameterIndex() {
- hasGlobalName("g_strdup_printf") and result = 0
+ hasName("g_strdup_printf") and result = 0
or
- hasGlobalName("__builtin___sprintf_chk") and result = 3
+ hasName("__builtin___sprintf_chk") and result = 3
or
- getName() != "g_strdup_printf" and
- getName() != "__builtin___sprintf_chk" and
+ not getName() = ["g_strdup_printf", "__builtin___sprintf_chk"] and
result = 1
}
override int getOutputParameterIndex(boolean isStream) {
- not hasGlobalName("g_strdup_printf") and result = 0 and isStream = false
+ not hasName("g_strdup_printf") and result = 0 and isStream = false
}
override int getFirstFormatArgumentIndex() {
- if hasGlobalName("__builtin___sprintf_chk")
- then result = 4
- else result = getNumberOfParameters()
+ if hasName("__builtin___sprintf_chk") then result = 4 else result = getNumberOfParameters()
}
}
@@ -129,26 +111,18 @@ private class SnprintfImpl extends Snprintf {
SnprintfImpl() {
this instanceof TopLevelFunction and
(
- hasGlobalOrStdName("snprintf") or // C99 defines snprintf
- hasGlobalOrStdName("swprintf") or // The s version of wide-char printf is also always the n version
+ hasGlobalOrStdOrBslName([
+ "snprintf", // C99 defines snprintf
+ "swprintf" // The s version of wide-char printf is also always the n version
+ ])
+ or
// Microsoft has _snprintf as well as several other variations
- hasGlobalName("sprintf_s") or
- hasGlobalName("snprintf_s") or
- hasGlobalName("swprintf_s") or
- hasGlobalName("_snprintf") or
- hasGlobalName("_snprintf_s") or
- hasGlobalName("_snprintf_l") or
- hasGlobalName("_snprintf_s_l") or
- hasGlobalName("_snwprintf") or
- hasGlobalName("_snwprintf_s") or
- hasGlobalName("_snwprintf_l") or
- hasGlobalName("_snwprintf_s_l") or
- hasGlobalName("_sprintf_s_l") or
- hasGlobalName("_swprintf_l") or
- hasGlobalName("_swprintf_s_l") or
- hasGlobalName("g_snprintf") or
- hasGlobalName("wnsprintf") or
- hasGlobalName("__builtin___snprintf_chk")
+ hasGlobalName([
+ "sprintf_s", "snprintf_s", "swprintf_s", "_snprintf", "_snprintf_s", "_snprintf_l",
+ "_snprintf_s_l", "_snwprintf", "_snwprintf_s", "_snwprintf_l", "_snwprintf_s_l",
+ "_sprintf_s_l", "_swprintf_l", "_swprintf_s_l", "g_snprintf", "wnsprintf",
+ "__builtin___snprintf_chk"
+ ])
) and
not exists(getDefinition().getFile().getRelativePath())
}
@@ -184,12 +158,7 @@ private class SnprintfImpl extends Snprintf {
}
override predicate returnsFullFormatLength() {
- (
- hasGlobalOrStdName("snprintf") or
- hasGlobalName("g_snprintf") or
- hasGlobalName("__builtin___snprintf_chk") or
- hasGlobalName("snprintf_s")
- ) and
+ hasName(["snprintf", "g_snprintf", "__builtin___snprintf_chk", "snprintf_s"]) and
not exists(getDefinition().getFile().getRelativePath())
}
@@ -202,16 +171,10 @@ private class SnprintfImpl extends Snprintf {
private class StringCchPrintf extends FormattingFunction {
StringCchPrintf() {
this instanceof TopLevelFunction and
- (
- hasGlobalName("StringCchPrintf") or
- hasGlobalName("StringCchPrintfEx") or
- hasGlobalName("StringCchPrintf_l") or
- hasGlobalName("StringCchPrintf_lEx") or
- hasGlobalName("StringCbPrintf") or
- hasGlobalName("StringCbPrintfEx") or
- hasGlobalName("StringCbPrintf_l") or
- hasGlobalName("StringCbPrintf_lEx")
- ) and
+ hasGlobalName([
+ "StringCchPrintf", "StringCchPrintfEx", "StringCchPrintf_l", "StringCchPrintf_lEx",
+ "StringCbPrintf", "StringCbPrintfEx", "StringCbPrintf_l", "StringCbPrintf_lEx"
+ ]) and
not exists(getDefinition().getFile().getRelativePath())
}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Pure.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Pure.qll
index 42021c48953..d81ac80deb8 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/Pure.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Pure.qll
@@ -7,10 +7,9 @@ import semmle.code.cpp.models.interfaces.SideEffect
private class PureStrFunction extends AliasFunction, ArrayFunction, TaintFunction,
SideEffectFunction {
PureStrFunction() {
- hasGlobalOrStdName([
- "atof", "atoi", "atol", "atoll", "strcasestr", "strchnul", "strchr", "strchrnul", "strstr",
- "strpbrk", "strcmp", "strcspn", "strncmp", "strrchr", "strspn", "strtod", "strtof",
- "strtol", "strtoll", "strtoq", "strtoul"
+ hasGlobalOrStdOrBslName([
+ atoi(), "strcasestr", "strchnul", "strchr", "strchrnul", "strstr", "strpbrk", "strrchr",
+ "strspn", strtol(), strrev(), strcmp(), strlwr(), strupr()
])
}
@@ -24,11 +23,16 @@ private class PureStrFunction extends AliasFunction, ArrayFunction, TaintFunctio
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
exists(ParameterIndex i |
- input.isParameter(i) and
- exists(getParameter(i))
- or
- input.isParameterDeref(i) and
- getParameter(i).getUnspecifiedType() instanceof PointerType
+ (
+ input.isParameter(i) and
+ exists(getParameter(i))
+ or
+ input.isParameterDeref(i) and
+ getParameter(i).getUnspecifiedType() instanceof PointerType
+ ) and
+ // Functions that end with _l also take a locale argument (always as the last argument),
+ // and we don't want taint from those arguments.
+ (not this.getName().matches("%\\_l") or exists(getParameter(i + 1)))
) and
(
output.isReturnValueDeref() and
@@ -60,10 +64,35 @@ private class PureStrFunction extends AliasFunction, ArrayFunction, TaintFunctio
}
}
+private string atoi() { result = ["atof", "atoi", "atol", "atoll"] }
+
+private string strtol() { result = ["strtod", "strtof", "strtol", "strtoll", "strtoq", "strtoul"] }
+
+private string strlwr() {
+ result = ["_strlwr", "_wcslwr", "_mbslwr", "_strlwr_l", "_wcslwr_l", "_mbslwr_l"]
+}
+
+private string strupr() {
+ result = ["_strupr", "_wcsupr", "_mbsupr", "_strupr_l", "_wcsupr_l", "_mbsupr_l"]
+}
+
+private string strrev() { result = ["_strrev", "_wcsrev", "_mbsrev", "_mbsrev_l"] }
+
+private string strcmp() {
+ // NOTE: `strcoll` doesn't satisfy _all_ the definitions of purity: its behavior depends on
+ // `LC_COLLATE` (which is set by `setlocale`). Not sure this behavior worth including in the model, so
+ // for now we interpret the function as being pure.
+ result =
+ [
+ "strcmp", "strcspn", "strncmp", "strcoll", "strverscmp", "_mbsnbcmp", "_mbsnbcmp_l",
+ "_stricmp"
+ ]
+}
+
/** String standard `strlen` function, and related functions for computing string lengths. */
private class StrLenFunction extends AliasFunction, ArrayFunction, SideEffectFunction {
StrLenFunction() {
- hasGlobalOrStdName(["strlen", "strnlen", "wcslen"])
+ hasGlobalOrStdOrBslName(["strlen", "strnlen", "wcslen"])
or
hasGlobalName(["_mbslen", "_mbslen_l", "_mbstrlen", "_mbstrlen_l"])
}
@@ -96,7 +125,7 @@ private class StrLenFunction extends AliasFunction, ArrayFunction, SideEffectFun
/** Pure functions. */
private class PureFunction extends TaintFunction, SideEffectFunction {
- PureFunction() { hasGlobalOrStdName(["abs", "labs"]) }
+ PureFunction() { hasGlobalOrStdOrBslName(["abs", "labs"]) }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
exists(ParameterIndex i |
@@ -114,7 +143,12 @@ private class PureFunction extends TaintFunction, SideEffectFunction {
/** Pure raw-memory functions. */
private class PureMemFunction extends AliasFunction, ArrayFunction, TaintFunction,
SideEffectFunction {
- PureMemFunction() { hasGlobalOrStdName(["memchr", "memrchr", "rawmemchr", "memcmp", "memmem"]) }
+ PureMemFunction() {
+ hasGlobalOrStdOrBslName([
+ "memchr", "__builtin_memchr", "memrchr", "rawmemchr", "memcmp", "__builtin_memcmp", "memmem"
+ ]) or
+ this.hasGlobalName("memfrob")
+ }
override predicate hasArrayInput(int bufParam) {
getParameter(bufParam).getUnspecifiedType() instanceof PointerType
@@ -122,11 +156,15 @@ private class PureMemFunction extends AliasFunction, ArrayFunction, TaintFunctio
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
exists(ParameterIndex i |
- input.isParameter(i) and
- exists(getParameter(i))
- or
- input.isParameterDeref(i) and
- getParameter(i).getUnspecifiedType() instanceof PointerType
+ (
+ input.isParameter(i) and
+ exists(getParameter(i))
+ or
+ input.isParameterDeref(i) and
+ getParameter(i).getUnspecifiedType() instanceof PointerType
+ ) and
+ // `memfrob` should not have taint from the size argument.
+ (not this.hasGlobalName("memfrob") or i = 0)
) and
(
output.isReturnValueDeref() and
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Recv.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Recv.qll
new file mode 100644
index 00000000000..691ba528f42
--- /dev/null
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Recv.qll
@@ -0,0 +1,88 @@
+/**
+ * Provides implementation classes modeling `recv` and various similar
+ * functions. See `semmle.code.cpp.models.Models` for usage information.
+ */
+
+import semmle.code.cpp.models.interfaces.Taint
+import semmle.code.cpp.models.interfaces.ArrayFunction
+import semmle.code.cpp.models.interfaces.Alias
+import semmle.code.cpp.models.interfaces.FlowSource
+import semmle.code.cpp.models.interfaces.SideEffect
+
+/** The function `recv` and its assorted variants */
+private class Recv extends AliasFunction, ArrayFunction, SideEffectFunction,
+ RemoteFlowSourceFunction {
+ Recv() {
+ this.hasGlobalName([
+ "recv", // recv(socket, dest, len, flags)
+ "recvfrom", // recvfrom(socket, dest, len, flags, from, fromlen)
+ "recvmsg", // recvmsg(socket, msg, flags)
+ "read", // read(socket, dest, len)
+ "pread", // pread(socket, dest, len, offset)
+ "readv", // readv(socket, dest, len)
+ "preadv", // readv(socket, dest, len, offset)
+ "preadv2" // readv2(socket, dest, len, offset, flags)
+ ])
+ }
+
+ override predicate parameterNeverEscapes(int index) {
+ this.getParameter(index).getUnspecifiedType() instanceof PointerType
+ }
+
+ override predicate parameterEscapesOnlyViaReturn(int index) { none() }
+
+ override predicate parameterIsAlwaysReturned(int index) { none() }
+
+ override predicate hasArrayWithVariableSize(int bufParam, int countParam) {
+ not this.hasGlobalName("recvmsg") and
+ bufParam = 1 and
+ countParam = 2
+ }
+
+ override predicate hasArrayInput(int bufParam) { this.hasGlobalName("recvfrom") and bufParam = 4 }
+
+ override predicate hasArrayOutput(int bufParam) {
+ bufParam = 1
+ or
+ this.hasGlobalName("recvfrom") and bufParam = 4
+ }
+
+ override predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) {
+ this.hasGlobalName("recvfrom") and
+ (
+ i = 4 and buffer = true
+ or
+ i = 5 and buffer = false
+ )
+ or
+ this.hasGlobalName("recvmsg") and
+ i = 1 and
+ buffer = true
+ }
+
+ override ParameterIndex getParameterSizeIndex(ParameterIndex i) { i = 1 and result = 2 }
+
+ override predicate hasSpecificWriteSideEffect(ParameterIndex i, boolean buffer, boolean mustWrite) {
+ i = 1 and buffer = true and mustWrite = false
+ or
+ this.hasGlobalName("recvfrom") and
+ (
+ i = 4 and buffer = true and mustWrite = false
+ or
+ i = 5 and buffer = false and mustWrite = false
+ )
+ }
+
+ override predicate hasOnlySpecificReadSideEffects() { any() }
+
+ override predicate hasOnlySpecificWriteSideEffects() { any() }
+
+ override predicate hasRemoteFlowSource(FunctionOutput output, string description) {
+ (
+ output.isParameterDeref(1)
+ or
+ this.hasGlobalName("recvfrom") and output.isParameterDeref([4, 5])
+ ) and
+ description = "Buffer read by " + this.getName()
+ }
+}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Send.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Send.qll
new file mode 100644
index 00000000000..6086bc7748f
--- /dev/null
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Send.qll
@@ -0,0 +1,63 @@
+/**
+ * Provides implementation classes modeling `send` and various similar
+ * functions. See `semmle.code.cpp.models.Models` for usage information.
+ */
+
+import semmle.code.cpp.models.interfaces.Taint
+import semmle.code.cpp.models.interfaces.ArrayFunction
+import semmle.code.cpp.models.interfaces.Alias
+import semmle.code.cpp.models.interfaces.FlowSource
+import semmle.code.cpp.models.interfaces.SideEffect
+
+/** The function `send` and its assorted variants */
+private class Send extends AliasFunction, ArrayFunction, SideEffectFunction, RemoteFlowSinkFunction {
+ Send() {
+ this.hasGlobalName([
+ "send", // send(socket, buf, len, flags)
+ "sendto", // sendto(socket, buf, len, flags, to, tolen)
+ "sendmsg", // sendmsg(socket, msg, flags)
+ "write", // write(socket, buf, len)
+ "writev", // writev(socket, buf, len)
+ "pwritev", // pwritev(socket, buf, len, offset)
+ "pwritev2" // pwritev2(socket, buf, len, offset, flags)
+ ])
+ }
+
+ override predicate parameterNeverEscapes(int index) {
+ this.getParameter(index).getUnspecifiedType() instanceof PointerType
+ }
+
+ override predicate parameterEscapesOnlyViaReturn(int index) { none() }
+
+ override predicate parameterIsAlwaysReturned(int index) { none() }
+
+ override predicate hasArrayWithVariableSize(int bufParam, int countParam) {
+ not this.hasGlobalName("sendmsg") and
+ bufParam = 1 and
+ countParam = 2
+ }
+
+ override predicate hasArrayInput(int bufParam) { bufParam = 1 }
+
+ override predicate hasOnlySpecificReadSideEffects() { any() }
+
+ override predicate hasOnlySpecificWriteSideEffects() { any() }
+
+ override predicate hasSpecificWriteSideEffect(ParameterIndex i, boolean buffer, boolean mustWrite) {
+ none()
+ }
+
+ override predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) {
+ i = 1 and buffer = true
+ or
+ this.hasGlobalName("sendto") and i = 4 and buffer = false
+ or
+ this.hasGlobalName("sendmsg") and i = 1 and buffer = true
+ }
+
+ override ParameterIndex getParameterSizeIndex(ParameterIndex i) { i = 1 and result = 2 }
+
+ override predicate hasRemoteFlowSink(FunctionInput input, string description) {
+ input.isParameterDeref(1) and description = "Buffer sent by " + this.getName()
+ }
+}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/SmartPointer.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/SmartPointer.qll
index b13b4a8801d..c6152624792 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/SmartPointer.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/SmartPointer.qll
@@ -4,14 +4,14 @@ import semmle.code.cpp.models.interfaces.Taint
* The `std::shared_ptr` and `std::unique_ptr` template classes.
*/
private class UniqueOrSharedPtr extends Class {
- UniqueOrSharedPtr() { this.hasQualifiedName("std", ["shared_ptr", "unique_ptr"]) }
+ UniqueOrSharedPtr() { this.hasQualifiedName(["std", "bsl"], ["shared_ptr", "unique_ptr"]) }
}
/**
* The `std::make_shared` and `std::make_unique` template functions.
*/
private class MakeUniqueOrShared extends TaintFunction {
- MakeUniqueOrShared() { this.hasQualifiedName("std", ["make_shared", "make_unique"]) }
+ MakeUniqueOrShared() { this.hasQualifiedName(["bsl", "std"], ["make_shared", "make_unique"]) }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// Exclude the specializations of `std::make_shared` and `std::make_unique` that allocate arrays
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Sscanf.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Sscanf.qll
new file mode 100644
index 00000000000..b6120abf05a
--- /dev/null
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Sscanf.qll
@@ -0,0 +1,72 @@
+/**
+ * Provides implementation classes modeling `sscanf`, `fscanf` and various similar
+ * functions. See `semmle.code.cpp.models.Models` for usage information.
+ */
+
+import semmle.code.cpp.Function
+import semmle.code.cpp.commons.Scanf
+import semmle.code.cpp.models.interfaces.ArrayFunction
+import semmle.code.cpp.models.interfaces.Taint
+import semmle.code.cpp.models.interfaces.Alias
+import semmle.code.cpp.models.interfaces.SideEffect
+
+/**
+ * The standard function `sscanf`, `fscanf` and its assorted variants
+ */
+private class SscanfModel extends ArrayFunction, TaintFunction, AliasFunction, SideEffectFunction {
+ SscanfModel() { this instanceof Sscanf or this instanceof Fscanf or this instanceof Snscanf }
+
+ override predicate hasArrayWithNullTerminator(int bufParam) {
+ bufParam = this.(ScanfFunction).getFormatParameterIndex()
+ or
+ not this instanceof Fscanf and
+ bufParam = this.(ScanfFunction).getInputParameterIndex()
+ }
+
+ override predicate hasArrayInput(int bufParam) { hasArrayWithNullTerminator(bufParam) }
+
+ private int getLengthParameterIndex() { result = this.(Snscanf).getInputLengthParameterIndex() }
+
+ private int getLocaleParameterIndex() {
+ this.getName().matches("%\\_l") and
+ (
+ if exists(getLengthParameterIndex())
+ then result = getLengthParameterIndex() + 2
+ else result = 2
+ )
+ }
+
+ private int getArgsStartPosition() { result = this.getNumberOfParameters() }
+
+ override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
+ input.isParameterDeref(this.(ScanfFunction).getInputParameterIndex()) and
+ output.isParameterDeref(any(int i | i >= getArgsStartPosition()))
+ }
+
+ override predicate parameterNeverEscapes(int index) {
+ index = [0 .. max(getACallToThisFunction().getNumberOfArguments())]
+ }
+
+ override predicate parameterEscapesOnlyViaReturn(int index) { none() }
+
+ override predicate parameterIsAlwaysReturned(int index) { none() }
+
+ override predicate hasOnlySpecificReadSideEffects() { any() }
+
+ override predicate hasOnlySpecificWriteSideEffects() { any() }
+
+ override predicate hasSpecificWriteSideEffect(ParameterIndex i, boolean buffer, boolean mustWrite) {
+ i >= getArgsStartPosition() and
+ buffer = true and
+ mustWrite = true
+ }
+
+ override predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) {
+ buffer = true and
+ i =
+ [
+ this.(ScanfFunction).getInputParameterIndex(),
+ this.(ScanfFunction).getFormatParameterIndex(), getLocaleParameterIndex()
+ ]
+ }
+}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/StdContainer.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/StdContainer.qll
index e7abae4161f..367db1613fc 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/StdContainer.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/StdContainer.qll
@@ -5,6 +5,41 @@
import semmle.code.cpp.models.interfaces.Taint
import semmle.code.cpp.models.interfaces.Iterator
+/**
+ * The `std::array` template class.
+ */
+private class Array extends Class {
+ Array() { this.hasQualifiedName(["std", "bsl"], "array") }
+}
+
+/**
+ * The `std::deque` template class.
+ */
+private class Deque extends Class {
+ Deque() { this.hasQualifiedName(["std", "bsl"], "deque") }
+}
+
+/**
+ * The `std::forward_list` template class.
+ */
+private class ForwardList extends Class {
+ ForwardList() { this.hasQualifiedName(["std", "bsl"], "forward_list") }
+}
+
+/**
+ * The `std::list` template class.
+ */
+private class List extends Class {
+ List() { this.hasQualifiedName(["std", "bsl"], "list") }
+}
+
+/**
+ * The `std::vector` template class.
+ */
+private class Vector extends Class {
+ Vector() { this.hasQualifiedName(["std", "bsl"], "vector") }
+}
+
/**
* Additional model for standard container constructors that reference the
* value type of the container (that is, the `T` in `std::vector`). For
@@ -15,7 +50,10 @@ import semmle.code.cpp.models.interfaces.Iterator
*/
private class StdSequenceContainerConstructor extends Constructor, TaintFunction {
StdSequenceContainerConstructor() {
- this.getDeclaringType().hasQualifiedName("std", ["vector", "deque", "list", "forward_list"])
+ this.getDeclaringType() instanceof Vector or
+ this.getDeclaringType() instanceof Deque or
+ this.getDeclaringType() instanceof List or
+ this.getDeclaringType() instanceof ForwardList
}
/**
@@ -50,7 +88,10 @@ private class StdSequenceContainerConstructor extends Constructor, TaintFunction
* The standard container function `data`.
*/
private class StdSequenceContainerData extends TaintFunction {
- StdSequenceContainerData() { this.hasQualifiedName("std", ["array", "vector"], "data") }
+ StdSequenceContainerData() {
+ this.getClassAndName("data") instanceof Array or
+ this.getClassAndName("data") instanceof Vector
+ }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from container itself (qualifier) to return value
@@ -69,10 +110,10 @@ private class StdSequenceContainerData extends TaintFunction {
*/
private class StdSequenceContainerPush extends TaintFunction {
StdSequenceContainerPush() {
- this.hasQualifiedName("std", "vector", "push_back") or
- this.hasQualifiedName("std", "deque", ["push_back", "push_front"]) or
- this.hasQualifiedName("std", "list", ["push_back", "push_front"]) or
- this.hasQualifiedName("std", "forward_list", "push_front")
+ this.getClassAndName("push_back") instanceof Vector or
+ this.getClassAndName(["push_back", "push_front"]) instanceof Deque or
+ this.getClassAndName("push_front") instanceof ForwardList or
+ this.getClassAndName(["push_back", "push_front"]) instanceof List
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -87,11 +128,11 @@ private class StdSequenceContainerPush extends TaintFunction {
*/
private class StdSequenceContainerFrontBack extends TaintFunction {
StdSequenceContainerFrontBack() {
- this.hasQualifiedName("std", "array", ["front", "back"]) or
- this.hasQualifiedName("std", "vector", ["front", "back"]) or
- this.hasQualifiedName("std", "deque", ["front", "back"]) or
- this.hasQualifiedName("std", "list", ["front", "back"]) or
- this.hasQualifiedName("std", "forward_list", "front")
+ this.getClassAndName(["front", "back"]) instanceof Array or
+ this.getClassAndName(["front", "back"]) instanceof Deque or
+ this.getClassAndName("front") instanceof ForwardList or
+ this.getClassAndName(["front", "back"]) instanceof List or
+ this.getClassAndName(["front", "back"]) instanceof Vector
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -106,8 +147,10 @@ private class StdSequenceContainerFrontBack extends TaintFunction {
*/
private class StdSequenceContainerInsert extends TaintFunction {
StdSequenceContainerInsert() {
- this.hasQualifiedName("std", ["vector", "deque", "list"], "insert") or
- this.hasQualifiedName("std", ["forward_list"], "insert_after")
+ this.getClassAndName("insert") instanceof Deque or
+ this.getClassAndName("insert") instanceof List or
+ this.getClassAndName("insert") instanceof Vector or
+ this.getClassAndName("insert_after") instanceof ForwardList
}
/**
@@ -143,7 +186,10 @@ private class StdSequenceContainerInsert extends TaintFunction {
*/
private class StdSequenceContainerAssign extends TaintFunction {
StdSequenceContainerAssign() {
- this.hasQualifiedName("std", ["vector", "deque", "list", "forward_list"], "assign")
+ this.getClassAndName("assign") instanceof Deque or
+ this.getClassAndName("assign") instanceof ForwardList or
+ this.getClassAndName("assign") instanceof List or
+ this.getClassAndName("assign") instanceof Vector
}
/**
@@ -170,30 +216,14 @@ private class StdSequenceContainerAssign extends TaintFunction {
}
}
-/**
- * The standard container `swap` functions.
- */
-private class StdSequenceContainerSwap extends TaintFunction {
- StdSequenceContainerSwap() {
- this.hasQualifiedName("std", ["array", "vector", "deque", "list", "forward_list"], "swap")
- }
-
- override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
- // container1.swap(container2)
- input.isQualifierObject() and
- output.isParameterDeref(0)
- or
- input.isParameterDeref(0) and
- output.isQualifierObject()
- }
-}
-
/**
* The standard container functions `at` and `operator[]`.
*/
private class StdSequenceContainerAt extends TaintFunction {
StdSequenceContainerAt() {
- this.hasQualifiedName("std", ["vector", "array", "deque"], ["at", "operator[]"])
+ this.getClassAndName(["at", "operator[]"]) instanceof Array or
+ this.getClassAndName(["at", "operator[]"]) instanceof Deque or
+ this.getClassAndName(["at", "operator[]"]) instanceof Vector
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -211,12 +241,12 @@ private class StdSequenceContainerAt extends TaintFunction {
* The standard vector `emplace` function.
*/
class StdVectorEmplace extends TaintFunction {
- StdVectorEmplace() { this.hasQualifiedName("std", "vector", "emplace") }
+ StdVectorEmplace() { this.getClassAndName("emplace") instanceof Vector }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from any parameter except the position iterator to qualifier and return value
// (here we assume taint flow from any constructor parameter to the constructed object)
- input.isParameter([1 .. getNumberOfParameters() - 1]) and
+ input.isParameterDeref([1 .. getNumberOfParameters() - 1]) and
(
output.isQualifierObject() or
output.isReturnValue()
@@ -228,12 +258,12 @@ class StdVectorEmplace extends TaintFunction {
* The standard vector `emplace_back` function.
*/
class StdVectorEmplaceBack extends TaintFunction {
- StdVectorEmplaceBack() { this.hasQualifiedName("std", "vector", "emplace_back") }
+ StdVectorEmplaceBack() { this.getClassAndName("emplace_back") instanceof Vector }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from any parameter to qualifier
// (here we assume taint flow from any constructor parameter to the constructed object)
- input.isParameter([0 .. getNumberOfParameters() - 1]) and
+ input.isParameterDeref([0 .. getNumberOfParameters() - 1]) and
output.isQualifierObject()
}
}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/StdMap.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/StdMap.qll
index dc7f7f8dd11..aecd98981e8 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/StdMap.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/StdMap.qll
@@ -5,14 +5,18 @@
import semmle.code.cpp.models.interfaces.Taint
import semmle.code.cpp.models.interfaces.Iterator
+/**
+ * The `std::map` and `std::unordered_map` template classes.
+ */
+private class MapOrUnorderedMap extends Class {
+ MapOrUnorderedMap() { this.hasQualifiedName(["std", "bsl"], ["map", "unordered_map"]) }
+}
+
/**
* Additional model for map constructors using iterator inputs.
*/
private class StdMapConstructor extends Constructor, TaintFunction {
- StdMapConstructor() {
- this.hasQualifiedName("std", "map", "map") or
- this.hasQualifiedName("std", "unordered_map", "unordered_map")
- }
+ StdMapConstructor() { this.getDeclaringType() instanceof MapOrUnorderedMap }
/**
* Gets the index of a parameter to this function that is an iterator.
@@ -37,7 +41,7 @@ private class StdMapConstructor extends Constructor, TaintFunction {
*/
private class StdMapInsert extends TaintFunction {
StdMapInsert() {
- this.hasQualifiedName("std", ["map", "unordered_map"], ["insert", "insert_or_assign"])
+ this.getClassAndName(["insert", "insert_or_assign"]) instanceof MapOrUnorderedMap
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -55,9 +59,7 @@ private class StdMapInsert extends TaintFunction {
* The standard map `emplace` and `emplace_hint` functions.
*/
private class StdMapEmplace extends TaintFunction {
- StdMapEmplace() {
- this.hasQualifiedName("std", ["map", "unordered_map"], ["emplace", "emplace_hint"])
- }
+ StdMapEmplace() { this.getClassAndName(["emplace", "emplace_hint"]) instanceof MapOrUnorderedMap }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from the last parameter (which may be the value part used to
@@ -79,7 +81,7 @@ private class StdMapEmplace extends TaintFunction {
* The standard map `try_emplace` function.
*/
private class StdMapTryEmplace extends TaintFunction {
- StdMapTryEmplace() { this.hasQualifiedName("std", ["map", "unordered_map"], "try_emplace") }
+ StdMapTryEmplace() { this.getClassAndName("try_emplace") instanceof MapOrUnorderedMap }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from any parameter apart from the key to qualifier and return value
@@ -102,27 +104,11 @@ private class StdMapTryEmplace extends TaintFunction {
}
}
-/**
- * The standard map `swap` function.
- */
-private class StdMapSwap extends TaintFunction {
- StdMapSwap() { this.hasQualifiedName("std", ["map", "unordered_map"], "swap") }
-
- override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
- // container1.swap(container2)
- input.isQualifierObject() and
- output.isParameterDeref(0)
- or
- input.isParameterDeref(0) and
- output.isQualifierObject()
- }
-}
-
/**
* The standard map `merge` function.
*/
private class StdMapMerge extends TaintFunction {
- StdMapMerge() { this.hasQualifiedName("std", ["map", "unordered_map"], "merge") }
+ StdMapMerge() { this.getClassAndName("merge") instanceof MapOrUnorderedMap }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// container1.merge(container2)
@@ -135,7 +121,7 @@ private class StdMapMerge extends TaintFunction {
* The standard map functions `at` and `operator[]`.
*/
private class StdMapAt extends TaintFunction {
- StdMapAt() { this.hasQualifiedName("std", ["map", "unordered_map"], ["at", "operator[]"]) }
+ StdMapAt() { this.getClassAndName(["at", "operator[]"]) instanceof MapOrUnorderedMap }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from qualifier to referenced return value
@@ -152,7 +138,7 @@ private class StdMapAt extends TaintFunction {
* The standard map `find` function.
*/
private class StdMapFind extends TaintFunction {
- StdMapFind() { this.hasQualifiedName("std", ["map", "unordered_map"], "find") }
+ StdMapFind() { this.getClassAndName("find") instanceof MapOrUnorderedMap }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
input.isQualifierObject() and
@@ -164,7 +150,7 @@ private class StdMapFind extends TaintFunction {
* The standard map `erase` function.
*/
private class StdMapErase extends TaintFunction {
- StdMapErase() { this.hasQualifiedName("std", ["map", "unordered_map"], "erase") }
+ StdMapErase() { this.getClassAndName("erase") instanceof MapOrUnorderedMap }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from qualifier to iterator return value
@@ -179,8 +165,7 @@ private class StdMapErase extends TaintFunction {
*/
private class StdMapEqualRange extends TaintFunction {
StdMapEqualRange() {
- this.hasQualifiedName("std", ["map", "unordered_map"],
- ["lower_bound", "upper_bound", "equal_range"])
+ this.getClassAndName(["lower_bound", "upper_bound", "equal_range"]) instanceof MapOrUnorderedMap
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/StdPair.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/StdPair.qll
index fbc8515b5ca..755f6a48520 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/StdPair.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/StdPair.qll
@@ -7,10 +7,16 @@ import semmle.code.cpp.models.interfaces.Taint
/**
* An instantiation of `std::pair`.
*/
-class StdPairClass extends ClassTemplateInstantiation {
- StdPairClass() { getTemplate().hasQualifiedName("std", "pair") }
+private class StdPair extends ClassTemplateInstantiation {
+ StdPair() { this.hasQualifiedName(["std", "bsl"], "pair") }
}
+/**
+ * DEPRECATED: This is now called `StdPair` and is a private part of the
+ * library implementation.
+ */
+deprecated class StdPairClass = StdPair;
+
/**
* Any of the single-parameter constructors of `std::pair` that takes a reference to an
* instantiation of `std::pair`. These constructors allow conversion between pair types when the
@@ -18,9 +24,9 @@ class StdPairClass extends ClassTemplateInstantiation {
*/
class StdPairCopyishConstructor extends Constructor, TaintFunction {
StdPairCopyishConstructor() {
- this.getDeclaringType() instanceof StdPairClass and
+ this.getDeclaringType() instanceof StdPair and
this.getNumberOfParameters() = 1 and
- this.getParameter(0).getUnspecifiedType().(ReferenceType).getBaseType() instanceof StdPairClass
+ this.getParameter(0).getUnspecifiedType().(ReferenceType).getBaseType() instanceof StdPair
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -38,7 +44,7 @@ class StdPairCopyishConstructor extends Constructor, TaintFunction {
* Additional model for `std::pair` constructors.
*/
private class StdPairConstructor extends Constructor, TaintFunction {
- StdPairConstructor() { this.hasQualifiedName("std", "pair", "pair") }
+ StdPairConstructor() { this.getDeclaringType() instanceof StdPair }
/**
* Gets the index of a parameter to this function that is a reference to
@@ -60,19 +66,3 @@ private class StdPairConstructor extends Constructor, TaintFunction {
)
}
}
-
-/**
- * The standard pair `swap` function.
- */
-private class StdPairSwap extends TaintFunction {
- StdPairSwap() { this.hasQualifiedName("std", "pair", "swap") }
-
- override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
- // container1.swap(container2)
- input.isQualifierObject() and
- output.isParameterDeref(0)
- or
- input.isParameterDeref(0) and
- output.isQualifierObject()
- }
-}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/StdSet.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/StdSet.qll
index 846fce0fe8c..d2e9892abcb 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/StdSet.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/StdSet.qll
@@ -5,14 +5,18 @@
import semmle.code.cpp.models.interfaces.Taint
import semmle.code.cpp.models.interfaces.Iterator
+/**
+ * An instantiation of `std::set` or `std::unordered_set`.
+ */
+private class StdSet extends ClassTemplateInstantiation {
+ StdSet() { this.hasQualifiedName(["std", "bsl"], ["set", "unordered_set"]) }
+}
+
/**
* Additional model for set constructors using iterator inputs.
*/
private class StdSetConstructor extends Constructor, TaintFunction {
- StdSetConstructor() {
- this.hasQualifiedName("std", "set", "set") or
- this.hasQualifiedName("std", "unordered_set", "unordered_set")
- }
+ StdSetConstructor() { this.getDeclaringType() instanceof StdSet }
/**
* Gets the index of a parameter to this function that is an iterator.
@@ -36,7 +40,7 @@ private class StdSetConstructor extends Constructor, TaintFunction {
* The standard set `insert` and `insert_or_assign` functions.
*/
private class StdSetInsert extends TaintFunction {
- StdSetInsert() { this.hasQualifiedName("std", ["set", "unordered_set"], "insert") }
+ StdSetInsert() { this.getClassAndName("insert") instanceof StdSet }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from last parameter to qualifier and return value
@@ -53,9 +57,7 @@ private class StdSetInsert extends TaintFunction {
* The standard set `emplace` and `emplace_hint` functions.
*/
private class StdSetEmplace extends TaintFunction {
- StdSetEmplace() {
- this.hasQualifiedName("std", ["set", "unordered_set"], ["emplace", "emplace_hint"])
- }
+ StdSetEmplace() { this.getClassAndName(["emplace", "emplace_hint"]) instanceof StdSet }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from any parameter to qualifier and return value
@@ -72,27 +74,11 @@ private class StdSetEmplace extends TaintFunction {
}
}
-/**
- * The standard set `swap` functions.
- */
-private class StdSetSwap extends TaintFunction {
- StdSetSwap() { this.hasQualifiedName("std", ["set", "unordered_set"], "swap") }
-
- override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
- // container1.swap(container2)
- input.isQualifierObject() and
- output.isParameterDeref(0)
- or
- input.isParameterDeref(0) and
- output.isQualifierObject()
- }
-}
-
/**
* The standard set `merge` function.
*/
private class StdSetMerge extends TaintFunction {
- StdSetMerge() { this.hasQualifiedName("std", ["set", "unordered_set"], "merge") }
+ StdSetMerge() { this.getClassAndName("merge") instanceof StdSet }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// container1.merge(container2)
@@ -105,7 +91,7 @@ private class StdSetMerge extends TaintFunction {
* The standard set `find` function.
*/
private class StdSetFind extends TaintFunction {
- StdSetFind() { this.hasQualifiedName("std", ["set", "unordered_set"], "find") }
+ StdSetFind() { this.getClassAndName("find") instanceof StdSet }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
input.isQualifierObject() and
@@ -117,7 +103,7 @@ private class StdSetFind extends TaintFunction {
* The standard set `erase` function.
*/
private class StdSetErase extends TaintFunction {
- StdSetErase() { this.hasQualifiedName("std", ["set", "unordered_set"], "erase") }
+ StdSetErase() { this.getClassAndName("erase") instanceof StdSet }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from qualifier to iterator return value
@@ -132,8 +118,7 @@ private class StdSetErase extends TaintFunction {
*/
private class StdSetEqualRange extends TaintFunction {
StdSetEqualRange() {
- this.hasQualifiedName("std", ["set", "unordered_set"],
- ["lower_bound", "upper_bound", "equal_range"])
+ this.getClassAndName(["lower_bound", "upper_bound", "equal_range"]) instanceof StdSet
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/StdString.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/StdString.qll
index 1e52a5ea6bd..73a0f6edf26 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/StdString.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/StdString.qll
@@ -9,10 +9,10 @@ import semmle.code.cpp.models.interfaces.Iterator
import semmle.code.cpp.models.interfaces.DataFlow
/**
- * The `std::basic_string` template class.
+ * The `std::basic_string` template class instantiations.
*/
-private class StdBasicString extends TemplateClass {
- StdBasicString() { this.hasQualifiedName("std", "basic_string") }
+private class StdBasicString extends ClassTemplateInstantiation {
+ StdBasicString() { this.hasQualifiedName(["std", "bsl"], "basic_string") }
}
/**
@@ -24,7 +24,7 @@ private class StdBasicString extends TemplateClass {
* ```
*/
private class StdStringConstructor extends Constructor, TaintFunction {
- StdStringConstructor() { this.getDeclaringType().hasQualifiedName("std", "basic_string") }
+ StdStringConstructor() { this.getDeclaringType() instanceof StdBasicString }
/**
* Gets the index of a parameter to this function that is a string (or
@@ -69,7 +69,7 @@ private class StdStringConstructor extends Constructor, TaintFunction {
* The `std::string` function `c_str`.
*/
private class StdStringCStr extends TaintFunction {
- StdStringCStr() { this.hasQualifiedName("std", "basic_string", "c_str") }
+ StdStringCStr() { this.getClassAndName("c_str") instanceof StdBasicString }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from string itself (qualifier) to return value
@@ -82,7 +82,7 @@ private class StdStringCStr extends TaintFunction {
* The `std::string` function `data`.
*/
private class StdStringData extends TaintFunction {
- StdStringData() { this.hasQualifiedName("std", "basic_string", "data") }
+ StdStringData() { this.getClassAndName("data") instanceof StdBasicString }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from string itself (qualifier) to return value
@@ -100,7 +100,7 @@ private class StdStringData extends TaintFunction {
* The `std::string` function `push_back`.
*/
private class StdStringPush extends TaintFunction {
- StdStringPush() { this.hasQualifiedName("std", "basic_string", "push_back") }
+ StdStringPush() { this.getClassAndName("push_back") instanceof StdBasicString }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from parameter to qualifier
@@ -113,7 +113,7 @@ private class StdStringPush extends TaintFunction {
* The `std::string` functions `front` and `back`.
*/
private class StdStringFrontBack extends TaintFunction {
- StdStringFrontBack() { this.hasQualifiedName("std", "basic_string", ["front", "back"]) }
+ StdStringFrontBack() { this.getClassAndName(["front", "back"]) instanceof StdBasicString }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from object to returned reference
@@ -123,12 +123,12 @@ private class StdStringFrontBack extends TaintFunction {
}
/**
- * The `std::string` function `operator+`.
+ * The (non-member) `std::string` function `operator+`.
*/
private class StdStringPlus extends TaintFunction {
StdStringPlus() {
- this.hasQualifiedName("std", "operator+") and
- this.getUnspecifiedType() = any(StdBasicString s).getAnInstantiation()
+ this.hasQualifiedName(["std", "bsl"], "operator+") and
+ this.getUnspecifiedType() instanceof StdBasicString
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -148,7 +148,7 @@ private class StdStringPlus extends TaintFunction {
*/
private class StdStringAppend extends TaintFunction {
StdStringAppend() {
- this.hasQualifiedName("std", "basic_string", ["operator+=", "append", "insert", "replace"])
+ this.getClassAndName(["operator+=", "append", "insert", "replace"]) instanceof StdBasicString
}
/**
@@ -190,7 +190,7 @@ private class StdStringAppend extends TaintFunction {
* The standard function `std::string.assign`.
*/
private class StdStringAssign extends TaintFunction {
- StdStringAssign() { this.hasQualifiedName("std", "basic_string", "assign") }
+ StdStringAssign() { this.getClassAndName("assign") instanceof StdBasicString }
/**
* Gets the index of a parameter to this function that is a string (or
@@ -230,7 +230,7 @@ private class StdStringAssign extends TaintFunction {
* The standard function `std::string.copy`.
*/
private class StdStringCopy extends TaintFunction {
- StdStringCopy() { this.hasQualifiedName("std", "basic_string", "copy") }
+ StdStringCopy() { this.getClassAndName("copy") instanceof StdBasicString }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// copy(dest, num, pos)
@@ -243,7 +243,7 @@ private class StdStringCopy extends TaintFunction {
* The standard function `std::string.substr`.
*/
private class StdStringSubstr extends TaintFunction {
- StdStringSubstr() { this.hasQualifiedName("std", "basic_string", "substr") }
+ StdStringSubstr() { this.getClassAndName("substr") instanceof StdBasicString }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// substr(pos, num)
@@ -252,30 +252,11 @@ private class StdStringSubstr extends TaintFunction {
}
}
-/**
- * The standard functions `std::string.swap` and `std::stringstream::swap`.
- */
-private class StdStringSwap extends TaintFunction {
- StdStringSwap() {
- this.hasQualifiedName("std", "basic_string", "swap") or
- this.hasQualifiedName("std", "basic_stringstream", "swap")
- }
-
- override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
- // str1.swap(str2)
- input.isQualifierObject() and
- output.isParameterDeref(0)
- or
- input.isParameterDeref(0) and
- output.isQualifierObject()
- }
-}
-
/**
* The `std::string` functions `at` and `operator[]`.
*/
private class StdStringAt extends TaintFunction {
- StdStringAt() { this.hasQualifiedName("std", "basic_string", ["at", "operator[]"]) }
+ StdStringAt() { this.getClassAndName(["at", "operator[]"]) instanceof StdBasicString }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from qualifier to referenced return value
@@ -289,22 +270,25 @@ private class StdStringAt extends TaintFunction {
}
/**
- * The `std::basic_istream` template class.
+ * The `std::basic_istream` template class instantiations.
*/
-private class StdBasicIStream extends TemplateClass {
- StdBasicIStream() { this.hasQualifiedName("std", "basic_istream") }
+private class StdBasicIStream extends ClassTemplateInstantiation {
+ StdBasicIStream() { this.hasQualifiedName(["std", "bsl"], "basic_istream") }
}
/**
* The `std::istream` function `operator>>` (defined as a member function).
*/
private class StdIStreamIn extends DataFlowFunction, TaintFunction {
- StdIStreamIn() { this.hasQualifiedName("std", "basic_istream", "operator>>") }
+ StdIStreamIn() { this.getClassAndName("operator>>") instanceof StdBasicIStream }
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
// returns reference to `*this`
input.isQualifierAddress() and
output.isReturnValue()
+ or
+ input.isQualifierObject() and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -323,15 +307,17 @@ private class StdIStreamIn extends DataFlowFunction, TaintFunction {
*/
private class StdIStreamInNonMember extends DataFlowFunction, TaintFunction {
StdIStreamInNonMember() {
- this.hasQualifiedName("std", "operator>>") and
- this.getUnspecifiedType().(ReferenceType).getBaseType() =
- any(StdBasicIStream s).getAnInstantiation()
+ this.hasQualifiedName(["std", "bsl"], "operator>>") and
+ this.getUnspecifiedType().(ReferenceType).getBaseType() instanceof StdBasicIStream
}
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
// flow from first parameter to return value
input.isParameter(0) and
output.isReturnValue()
+ or
+ input.isParameterDeref(0) and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -350,7 +336,7 @@ private class StdIStreamInNonMember extends DataFlowFunction, TaintFunction {
*/
private class StdIStreamGet extends TaintFunction {
StdIStreamGet() {
- this.hasQualifiedName("std", "basic_istream", ["get", "peek"]) and
+ this.getClassAndName(["get", "peek"]) instanceof StdBasicIStream and
this.getNumberOfParameters() = 0
}
@@ -366,7 +352,7 @@ private class StdIStreamGet extends TaintFunction {
*/
private class StdIStreamRead extends DataFlowFunction, TaintFunction {
StdIStreamRead() {
- this.hasQualifiedName("std", "basic_istream", ["get", "read"]) and
+ this.getClassAndName(["get", "read"]) instanceof StdBasicIStream and
this.getNumberOfParameters() > 0
}
@@ -374,6 +360,9 @@ private class StdIStreamRead extends DataFlowFunction, TaintFunction {
// returns reference to `*this`
input.isQualifierAddress() and
output.isReturnValue()
+ or
+ input.isQualifierObject() and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -391,7 +380,7 @@ private class StdIStreamRead extends DataFlowFunction, TaintFunction {
* The `std::istream` function `readsome`.
*/
private class StdIStreamReadSome extends TaintFunction {
- StdIStreamReadSome() { this.hasQualifiedName("std", "basic_istream", "readsome") }
+ StdIStreamReadSome() { this.getClassAndName("readsome") instanceof StdBasicIStream }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from qualifier to first parameter
@@ -404,12 +393,15 @@ private class StdIStreamReadSome extends TaintFunction {
* The `std::istream` function `putback`.
*/
private class StdIStreamPutBack extends DataFlowFunction, TaintFunction {
- StdIStreamPutBack() { this.hasQualifiedName("std", "basic_istream", "putback") }
+ StdIStreamPutBack() { this.getClassAndName("putback") instanceof StdBasicIStream }
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
// returns reference to `*this`
input.isQualifierAddress() and
output.isReturnValue()
+ or
+ input.isQualifierObject() and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -437,12 +429,15 @@ private class StdIStreamPutBack extends DataFlowFunction, TaintFunction {
* The `std::istream` function `getline`.
*/
private class StdIStreamGetLine extends DataFlowFunction, TaintFunction {
- StdIStreamGetLine() { this.hasQualifiedName("std", "basic_istream", "getline") }
+ StdIStreamGetLine() { this.getClassAndName("getline") instanceof StdBasicIStream }
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
// returns reference to `*this`
input.isQualifierAddress() and
output.isReturnValue()
+ or
+ input.isQualifierObject() and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -460,12 +455,15 @@ private class StdIStreamGetLine extends DataFlowFunction, TaintFunction {
* The (non-member) function `std::getline`.
*/
private class StdGetLine extends DataFlowFunction, TaintFunction {
- StdGetLine() { this.hasQualifiedName("std", "getline") }
+ StdGetLine() { this.hasQualifiedName(["std", "bsl"], "getline") }
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
// flow from first parameter to return value
input.isParameter(0) and
output.isReturnValue()
+ or
+ input.isParameterDeref(0) and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -480,10 +478,10 @@ private class StdGetLine extends DataFlowFunction, TaintFunction {
}
/**
- * The `std::basic_ostream` template class.
+ * The `std::basic_ostream` template class instantiations.
*/
-private class StdBasicOStream extends TemplateClass {
- StdBasicOStream() { this.hasQualifiedName("std", "basic_ostream") }
+private class StdBasicOStream extends ClassTemplateInstantiation {
+ StdBasicOStream() { this.hasQualifiedName(["std", "bsl"], "basic_ostream") }
}
/**
@@ -491,12 +489,17 @@ private class StdBasicOStream extends TemplateClass {
* `put` and `write`.
*/
private class StdOStreamOut extends DataFlowFunction, TaintFunction {
- StdOStreamOut() { this.hasQualifiedName("std", "basic_ostream", ["operator<<", "put", "write"]) }
+ StdOStreamOut() {
+ this.getClassAndName(["operator<<", "put", "write"]) instanceof StdBasicOStream
+ }
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
// returns reference to `*this`
input.isQualifierAddress() and
output.isReturnValue()
+ or
+ input.isQualifierObject() and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -525,15 +528,17 @@ private class StdOStreamOut extends DataFlowFunction, TaintFunction {
*/
private class StdOStreamOutNonMember extends DataFlowFunction, TaintFunction {
StdOStreamOutNonMember() {
- this.hasQualifiedName("std", "operator<<") and
- this.getUnspecifiedType().(ReferenceType).getBaseType() =
- any(StdBasicOStream s).getAnInstantiation()
+ this.hasQualifiedName(["std", "bsl"], "operator<<") and
+ this.getUnspecifiedType().(ReferenceType).getBaseType() instanceof StdBasicOStream
}
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
// flow from first parameter to return value
input.isParameter(0) and
output.isReturnValue()
+ or
+ input.isParameterDeref(0) and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -551,14 +556,19 @@ private class StdOStreamOutNonMember extends DataFlowFunction, TaintFunction {
}
}
+/**
+ * The `std::basic_stringstream` template class instantiations.
+ */
+private class StdBasicStringStream extends ClassTemplateInstantiation {
+ StdBasicStringStream() { this.hasQualifiedName(["std", "bsl"], "basic_stringstream") }
+}
+
/**
* Additional model for `std::stringstream` constructors that take a string
* input parameter.
*/
private class StdStringStreamConstructor extends Constructor, TaintFunction {
- StdStringStreamConstructor() {
- this.getDeclaringType().hasQualifiedName("std", "basic_stringstream")
- }
+ StdStringStreamConstructor() { this.getDeclaringType() instanceof StdBasicStringStream }
/**
* Gets the index of a parameter to this function that is a string.
@@ -582,7 +592,7 @@ private class StdStringStreamConstructor extends Constructor, TaintFunction {
* The `std::stringstream` function `str`.
*/
private class StdStringStreamStr extends TaintFunction {
- StdStringStreamStr() { this.hasQualifiedName("std", "basic_stringstream", "str") }
+ StdStringStreamStr() { this.getClassAndName("str") instanceof StdBasicStringStream }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from qualifier to return value (if any)
@@ -595,21 +605,33 @@ private class StdStringStreamStr extends TaintFunction {
}
}
+/**
+ * The `std::basic_ios` template class instantiations.
+ */
+private class StdBasicIOS extends ClassTemplateInstantiation {
+ StdBasicIOS() { this.hasQualifiedName(["std", "bsl"], "basic_ios") }
+}
+
/**
* A `std::` stream function that does not require a model, except that it
* returns a reference to `*this` and thus could be used in a chain.
*/
private class StdStreamFunction extends DataFlowFunction, TaintFunction {
StdStreamFunction() {
- this.hasQualifiedName("std", "basic_istream", ["ignore", "unget", "seekg"]) or
- this.hasQualifiedName("std", "basic_ostream", ["seekp", "flush"]) or
- this.hasQualifiedName("std", "basic_ios", "copyfmt")
+ this.getClassAndName(["ignore", "unget", "seekg"]) instanceof StdBasicIStream
+ or
+ this.getClassAndName(["seekp", "flush"]) instanceof StdBasicOStream
+ or
+ this.getClassAndName("copyfmt") instanceof StdBasicIOS
}
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
// returns reference to `*this`
input.isQualifierAddress() and
output.isReturnValue()
+ or
+ input.isQualifierObject() and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Strcat.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Strcat.qll
index 9acd5b32d4f..ee9af547582 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/Strcat.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Strcat.qll
@@ -13,15 +13,20 @@ import semmle.code.cpp.models.interfaces.SideEffect
*/
class StrcatFunction extends TaintFunction, DataFlowFunction, ArrayFunction, SideEffectFunction {
StrcatFunction() {
- exists(string name | name = getName() |
- name = "strcat" or // strcat(dst, src)
- name = "strncat" or // strncat(dst, src, max_amount)
- name = "wcscat" or // wcscat(dst, src)
- name = "_mbscat" or // _mbscat(dst, src)
- name = "wcsncat" or // wcsncat(dst, src, max_amount)
- name = "_mbsncat" or // _mbsncat(dst, src, max_amount)
- name = "_mbsncat_l" // _mbsncat_l(dst, src, max_amount, locale)
- )
+ this.hasGlobalOrStdOrBslName([
+ "strcat", // strcat(dst, src)
+ "strncat", // strncat(dst, src, max_amount)
+ "wcscat", // wcscat(dst, src)
+ "wcsncat" // wcsncat(dst, src, max_amount)
+ ])
+ or
+ this.hasGlobalName([
+ "_mbscat", // _mbscat(dst, src)
+ "_mbsncat", // _mbsncat(dst, src, max_amount)
+ "_mbsncat_l", // _mbsncat_l(dst, src, max_amount, locale)
+ "_mbsnbcat", // _mbsnbcat(dest, src, count)
+ "_mbsnbcat_l" // _mbsnbcat_l(dest, src, count, locale)
+ ])
}
/**
@@ -45,20 +50,13 @@ class StrcatFunction extends TaintFunction, DataFlowFunction, ArrayFunction, Sid
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
- exists(string name | name = getName() |
- (
- name = "strncat" or
- name = "wcsncat" or
- name = "_mbsncat" or
- name = "_mbsncat_l"
- ) and
- input.isParameter(2) and
- output.isParameterDeref(0)
- or
- name = "_mbsncat_l" and
- input.isParameter(3) and
- output.isParameterDeref(0)
- )
+ getName() = ["strncat", "wcsncat", "_mbsncat", "_mbsncat_l"] and
+ input.isParameter(2) and
+ output.isParameterDeref(0)
+ or
+ getName() = ["_mbsncat_l", "_mbsnbcat_l"] and
+ input.isParameter(3) and
+ output.isParameterDeref(0)
or
input.isParameterDeref(0) and
output.isParameterDeref(0)
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Strcpy.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Strcpy.qll
index 061209d65b7..432fbf999ef 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/Strcpy.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Strcpy.qll
@@ -13,25 +13,36 @@ import semmle.code.cpp.models.interfaces.SideEffect
*/
class StrcpyFunction extends ArrayFunction, DataFlowFunction, TaintFunction, SideEffectFunction {
StrcpyFunction() {
- getName() =
- [
+ this.hasGlobalOrStdOrBslName([
"strcpy", // strcpy(dst, src)
"wcscpy", // wcscpy(dst, src)
- "_mbscpy", // _mbscpy(dst, src)
"strncpy", // strncpy(dst, src, max_amount)
- "_strncpy_l", // _strncpy_l(dst, src, max_amount, locale)
"wcsncpy", // wcsncpy(dst, src, max_amount)
+ "strxfrm", // strxfrm(dest, src, max_amount)
+ "wcsxfrm" // wcsxfrm(dest, src, max_amount)
+ ])
+ or
+ this.hasGlobalName([
+ "_mbscpy", // _mbscpy(dst, src)
+ "_strncpy_l", // _strncpy_l(dst, src, max_amount, locale)
"_wcsncpy_l", // _wcsncpy_l(dst, src, max_amount, locale)
"_mbsncpy", // _mbsncpy(dst, src, max_amount)
- "_mbsncpy_l"
- ] // _mbsncpy_l(dst, src, max_amount, locale)
+ "_mbsncpy_l", // _mbsncpy_l(dst, src, max_amount, locale)
+ "_strxfrm_l", // _strxfrm_l(dest, src, max_amount, locale)
+ "wcsxfrm_l", // _strxfrm_l(dest, src, max_amount, locale)
+ "_mbsnbcpy", // _mbsnbcpy(dest, src, max_amount)
+ "stpcpy", // stpcpy(dest, src)
+ "stpncpy" // stpcpy(dest, src, max_amount)
+ ])
or
- getName() =
- [
- "strcpy_s", // strcpy_s(dst, max_amount, src)
- "wcscpy_s", // wcscpy_s(dst, max_amount, src)
- "_mbscpy_s"
- ] and // _mbscpy_s(dst, max_amount, src)
+ (
+ this.hasGlobalOrStdName([
+ "strcpy_s", // strcpy_s(dst, max_amount, src)
+ "wcscpy_s" // wcscpy_s(dst, max_amount, src)
+ ])
+ or
+ this.hasGlobalName("_mbscpy_s") // _mbscpy_s(dst, max_amount, src)
+ ) and
// exclude the 2-parameter template versions
// that find the size of a fixed size destination buffer.
getNumberOfParameters() = 3
@@ -40,9 +51,7 @@ class StrcpyFunction extends ArrayFunction, DataFlowFunction, TaintFunction, Sid
/**
* Holds if this is one of the `strcpy_s` variants.
*/
- private predicate isSVariant() {
- exists(string name | name = getName() | name.suffix(name.length() - 2) = "_s")
- }
+ private predicate isSVariant() { getName().matches("%\\_s") }
/**
* Gets the index of the parameter that is the maximum size of the copy (in characters).
@@ -50,10 +59,10 @@ class StrcpyFunction extends ArrayFunction, DataFlowFunction, TaintFunction, Sid
int getParamSize() {
if isSVariant()
then result = 1
- else
- if exists(getName().indexOf("ncpy"))
- then result = 2
- else none()
+ else (
+ getName().matches(["%ncpy%", "%nbcpy%", "%xfrm%"]) and
+ result = 2
+ )
}
/**
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Strcrement.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Strcrement.qll
new file mode 100644
index 00000000000..4c335c8581e
--- /dev/null
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Strcrement.qll
@@ -0,0 +1,49 @@
+/**
+ * Provides implementation classes modeling `_strinc`, `_strdec` and their variants.
+ * See `semmle.code.cpp.models.Models` for usage information.
+ */
+
+import semmle.code.cpp.models.interfaces.ArrayFunction
+import semmle.code.cpp.models.interfaces.Taint
+import semmle.code.cpp.models.interfaces.SideEffect
+
+/**
+ * The function `_strinc`, `_strdec` and their variants.
+ */
+private class Strcrement extends ArrayFunction, TaintFunction, SideEffectFunction {
+ Strcrement() {
+ this.hasGlobalName([
+ "_strinc", // _strinc(source, locale)
+ "_wcsinc", // _strinc(source, locale)
+ "_mbsinc", // _strinc(source)
+ "_mbsinc_l", // _strinc(source, locale)
+ "_strdec", // _strdec(start, source)
+ "_wcsdec", // _wcsdec(start, source)
+ "_mbsdec", // _mbsdec(start, source)
+ "_mbsdec_l" // _mbsdec_l(start, source, locale)
+ ])
+ }
+
+ override predicate hasArrayWithNullTerminator(int bufParam) {
+ // Match all parameters that are not locales.
+ this.getParameter(bufParam).getUnspecifiedType() instanceof PointerType
+ }
+
+ override predicate hasArrayInput(int bufParam) { hasArrayWithNullTerminator(bufParam) }
+
+ override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
+ exists(int index | hasArrayInput(index) |
+ input.isParameter(index) and output.isReturnValue()
+ or
+ input.isParameterDeref(index) and output.isReturnValueDeref()
+ )
+ }
+
+ override predicate hasOnlySpecificReadSideEffects() { any() }
+
+ override predicate hasOnlySpecificWriteSideEffects() { any() }
+
+ override predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) {
+ hasArrayInput(i) and buffer = true
+ }
+}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Strdup.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Strdup.qll
index 7f71dfc0647..e2443b8151c 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/Strdup.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Strdup.qll
@@ -13,25 +13,14 @@ import semmle.code.cpp.models.interfaces.Taint
*/
private class StrdupFunction extends AllocationFunction, ArrayFunction, DataFlowFunction {
StrdupFunction() {
- exists(string name |
- hasGlobalName(name) and
- (
- // strdup(str)
- name = "strdup"
- or
- // wcsdup(str)
- name = "wcsdup"
- or
- // _strdup(str)
- name = "_strdup"
- or
- // _wcsdup(str)
- name = "_wcsdup"
- or
- // _mbsdup(str)
- name = "_mbsdup"
- )
- )
+ hasGlobalName([
+ // --- C library allocation
+ "strdup", // strdup(str)
+ "wcsdup", // wcsdup(str)
+ "_strdup", // _strdup(str)
+ "_wcsdup", // _wcsdup(str)
+ "_mbsdup" // _mbsdup(str)
+ ])
}
override predicate hasArrayInput(int bufParam) { bufParam = 0 }
@@ -51,8 +40,8 @@ private class StrndupFunction extends AllocationFunction, ArrayFunction, DataFlo
StrndupFunction() {
exists(string name |
hasGlobalName(name) and
- // strndup(str, maxlen)
- name = "strndup"
+ // --- C library allocation
+ name = "strndup" // strndup(str, maxlen)
)
}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Strnextc.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Strnextc.qll
new file mode 100644
index 00000000000..fc8ac17b5f6
--- /dev/null
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Strnextc.qll
@@ -0,0 +1,38 @@
+/**
+ * Provides implementation classes modeling `strnextc` and various similar functions.
+ * See `semmle.code.cpp.models.Models` for usage information.
+ */
+
+import semmle.code.cpp.models.interfaces.ArrayFunction
+import semmle.code.cpp.models.interfaces.Taint
+import semmle.code.cpp.models.interfaces.Alias
+import semmle.code.cpp.models.interfaces.SideEffect
+
+/**
+ * The function `strnextc` and its variants.
+ */
+private class Strnextc extends TaintFunction, ArrayFunction, AliasFunction, SideEffectFunction {
+ Strnextc() { this.hasGlobalName(["_strnextc", "_wcsnextc", "_mbsnextc", "_mbsnextc_l"]) }
+
+ override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
+ input.isParameterDeref(0) and output.isReturnValue()
+ }
+
+ override predicate hasArrayWithNullTerminator(int bufParam) { bufParam = 0 }
+
+ override predicate hasArrayInput(int bufParam) { bufParam = 0 }
+
+ override predicate parameterNeverEscapes(int index) { index = 0 }
+
+ override predicate parameterEscapesOnlyViaReturn(int index) { none() }
+
+ override predicate parameterIsAlwaysReturned(int index) { none() }
+
+ override predicate hasOnlySpecificReadSideEffects() { any() }
+
+ override predicate hasOnlySpecificWriteSideEffects() { any() }
+
+ override predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) {
+ i = 0 and buffer = true
+ }
+}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Strset.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Strset.qll
new file mode 100644
index 00000000000..f4a80cbabac
--- /dev/null
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Strset.qll
@@ -0,0 +1,62 @@
+/**
+ * Provides implementation classes modeling `strset` and various similar
+ * functions. See `semmle.code.cpp.models.Models` for usage information.
+ */
+
+import semmle.code.cpp.Function
+import semmle.code.cpp.models.interfaces.ArrayFunction
+import semmle.code.cpp.models.interfaces.DataFlow
+import semmle.code.cpp.models.interfaces.Alias
+import semmle.code.cpp.models.interfaces.SideEffect
+
+/**
+ * The standard function `strset` and its assorted variants
+ */
+private class StrsetFunction extends ArrayFunction, DataFlowFunction, AliasFunction,
+ SideEffectFunction {
+ StrsetFunction() {
+ hasGlobalName([
+ "strset", "_strset", "_strset_l", "_wcsset", "_wcsset_l", "_mbsset", "_mbsset_l",
+ "_mbsnbset", "_mbsnbset_l", "_strnset", "_strnset_l", "_wcsnset", "_wcsnset_l", "_mbsnset",
+ "_mbsnset_l"
+ ])
+ }
+
+ override predicate hasArrayWithNullTerminator(int bufParam) { bufParam = 0 }
+
+ override predicate hasArrayInput(int bufParam) { bufParam = 0 }
+
+ override predicate hasArrayOutput(int bufParam) { bufParam = 0 }
+
+ override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
+ // flow from the character that overrides the string
+ input.isParameter(1) and
+ (
+ output.isReturnValueDeref()
+ or
+ output.isParameterDeref(0)
+ )
+ or
+ // flow from the input string to the output string
+ input.isParameter(0) and
+ output.isReturnValue()
+ }
+
+ override predicate parameterNeverEscapes(int index) { none() }
+
+ override predicate parameterEscapesOnlyViaReturn(int index) { index = 0 }
+
+ override predicate parameterIsAlwaysReturned(int index) { index = 0 }
+
+ override predicate hasOnlySpecificReadSideEffects() { any() }
+
+ override predicate hasOnlySpecificWriteSideEffects() { any() }
+
+ override predicate hasSpecificWriteSideEffect(ParameterIndex i, boolean buffer, boolean mustWrite) {
+ i = 0 and buffer = true and mustWrite = true
+ }
+
+ override predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) {
+ i = 0 and buffer = true
+ }
+}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Strtok.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Strtok.qll
new file mode 100644
index 00000000000..f2cb6498819
--- /dev/null
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Strtok.qll
@@ -0,0 +1,88 @@
+/**
+ * Provides implementation classes modeling `strtok` and various similar
+ * functions. See `semmle.code.cpp.models.Models` for usage information.
+ */
+
+import semmle.code.cpp.Function
+import semmle.code.cpp.models.interfaces.ArrayFunction
+import semmle.code.cpp.models.interfaces.ArrayFunction
+import semmle.code.cpp.models.interfaces.Alias
+import semmle.code.cpp.models.interfaces.SideEffect
+import semmle.code.cpp.models.interfaces.Taint
+
+/**
+ * The standard function `strtok` and its assorted variants
+ */
+private class Strtok extends ArrayFunction, AliasFunction, TaintFunction, SideEffectFunction {
+ Strtok() {
+ this.hasGlobalOrStdOrBslName("strtok") or
+ this.hasGlobalName(["strtok_r", "_strtok_l", "wcstok", "_wcstok_l", "_mbstok", "_mbstok_l"])
+ }
+
+ override predicate hasArrayWithNullTerminator(int bufParam) { bufParam = [0, 1] }
+
+ override predicate hasArrayInput(int bufParam) { bufParam = [0, 1] }
+
+ override predicate hasArrayOutput(int bufParam) { bufParam = 0 }
+
+ override predicate parameterNeverEscapes(int index) { index = 1 }
+
+ override predicate parameterEscapesOnlyViaReturn(int index) { index = 0 }
+
+ override predicate parameterIsAlwaysReturned(int index) { none() }
+
+ override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
+ input.isParameter(0) and output.isReturnValue()
+ }
+
+ override predicate hasOnlySpecificReadSideEffects() { none() }
+
+ override predicate hasOnlySpecificWriteSideEffects() { none() }
+
+ override predicate hasSpecificWriteSideEffect(ParameterIndex i, boolean buffer, boolean mustWrite) {
+ i = 0 and buffer = true and mustWrite = false
+ }
+
+ override predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) {
+ i = [0, 1] and buffer = true
+ }
+}
+
+/**
+ * The function `strtok` is a variant of `strtok` that takes a `char**` parameter instead of
+ * a `char*` as the first parameter.
+ */
+private class Strsep extends ArrayFunction, AliasFunction, TaintFunction, SideEffectFunction {
+ Strsep() { this.hasGlobalName("strsep") }
+
+ override predicate hasArrayWithNullTerminator(int bufParam) { bufParam = 1 }
+
+ override predicate hasArrayInput(int bufParam) { bufParam = 1 }
+
+ override predicate parameterNeverEscapes(int index) { index = [0, 1] }
+
+ override predicate parameterEscapesOnlyViaReturn(int index) { none() }
+
+ override predicate parameterIsAlwaysReturned(int index) { none() }
+
+ override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
+ // NOTE: What we really want here is: (input.isParameterDerefDeref(0) or input.isParameterDeref(1))
+ // as the first conjunct.
+ input.isParameterDeref([0, 1]) and
+ (output.isReturnValue() or output.isReturnValueDeref())
+ }
+
+ override predicate hasOnlySpecificReadSideEffects() { any() }
+
+ override predicate hasOnlySpecificWriteSideEffects() { any() }
+
+ override predicate hasSpecificWriteSideEffect(ParameterIndex i, boolean buffer, boolean mustWrite) {
+ i = 0 and buffer = false and mustWrite = false
+ }
+
+ override predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) {
+ i = 0 and buffer = false
+ or
+ i = 1 and buffer = true
+ }
+}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Swap.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Swap.qll
index 9fb1f470022..b79f7afe5d9 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/Swap.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Swap.qll
@@ -1,11 +1,15 @@
import semmle.code.cpp.models.interfaces.DataFlow
import semmle.code.cpp.models.interfaces.Taint
+import semmle.code.cpp.models.interfaces.Alias
/**
- * The standard function `swap`.
+ * The standard function `swap`. A use of `swap` looks like this:
+ * ```
+ * std::swap(obj1, obj2)
+ * ```
*/
private class Swap extends DataFlowFunction {
- Swap() { this.hasQualifiedName("std", "swap") }
+ Swap() { this.hasQualifiedName(["std", "bsl"], "swap") }
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
input.isParameterDeref(0) and
@@ -15,3 +19,32 @@ private class Swap extends DataFlowFunction {
output.isParameterDeref(0)
}
}
+
+/**
+ * A `swap` member function that is used as follows:
+ * ```
+ * obj1.swap(obj2)
+ * ```
+ */
+private class MemberSwap extends TaintFunction, MemberFunction, AliasFunction {
+ MemberSwap() {
+ this.hasName("swap") and
+ this.getNumberOfParameters() = 1 and
+ this.getParameter(0).getType().(ReferenceType).getBaseType().getUnspecifiedType() =
+ getDeclaringType()
+ }
+
+ override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
+ input.isQualifierObject() and
+ output.isParameterDeref(0)
+ or
+ input.isParameterDeref(0) and
+ output.isQualifierObject()
+ }
+
+ override predicate parameterNeverEscapes(int index) { none() }
+
+ override predicate parameterEscapesOnlyViaReturn(int index) { index = 0 }
+
+ override predicate parameterIsAlwaysReturned(int index) { index = 0 }
+}
diff --git a/cpp/ql/src/semmle/code/cpp/models/interfaces/Allocation.qll b/cpp/ql/src/semmle/code/cpp/models/interfaces/Allocation.qll
index 65c677761cc..d309791f79a 100644
--- a/cpp/ql/src/semmle/code/cpp/models/interfaces/Allocation.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/interfaces/Allocation.qll
@@ -93,16 +93,10 @@ abstract class AllocationExpr extends Expr {
*/
class OperatorNewAllocationFunction extends AllocationFunction {
OperatorNewAllocationFunction() {
- exists(string name |
- hasGlobalName(name) and
- (
- // operator new(bytes, ...)
- name = "operator new"
- or
- // operator new[](bytes, ...)
- name = "operator new[]"
- )
- )
+ hasGlobalName([
+ "operator new", // operator new(bytes, ...)
+ "operator new[]" // operator new[](bytes, ...)
+ ])
}
override int getSizeArg() { result = 0 }
diff --git a/cpp/ql/src/semmle/code/cpp/models/interfaces/DataFlow.qll b/cpp/ql/src/semmle/code/cpp/models/interfaces/DataFlow.qll
index c1b65d62706..b30861254dc 100644
--- a/cpp/ql/src/semmle/code/cpp/models/interfaces/DataFlow.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/interfaces/DataFlow.qll
@@ -24,5 +24,6 @@ abstract class DataFlowFunction extends Function {
* represented by `input` to the return value or buffer represented by
* `output`
*/
+ pragma[nomagic]
abstract predicate hasDataFlow(FunctionInput input, FunctionOutput output);
}
diff --git a/cpp/ql/src/semmle/code/cpp/models/interfaces/Deallocation.qll b/cpp/ql/src/semmle/code/cpp/models/interfaces/Deallocation.qll
index 501839de678..9c74102e99c 100644
--- a/cpp/ql/src/semmle/code/cpp/models/interfaces/Deallocation.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/interfaces/Deallocation.qll
@@ -38,16 +38,10 @@ abstract class DeallocationExpr extends Expr {
*/
class OperatorDeleteDeallocationFunction extends DeallocationFunction {
OperatorDeleteDeallocationFunction() {
- exists(string name |
- hasGlobalName(name) and
- (
- // operator delete(pointer, ...)
- name = "operator delete"
- or
- // operator delete[](pointer, ...)
- name = "operator delete[]"
- )
- )
+ hasGlobalName([
+ "operator delete", // operator delete(pointer, ...)
+ "operator delete[]" // operator delete[](pointer, ...)
+ ])
}
override int getFreedArg() { result = 0 }
diff --git a/cpp/ql/src/semmle/code/cpp/models/interfaces/FlowSource.qll b/cpp/ql/src/semmle/code/cpp/models/interfaces/FlowSource.qll
index c0c95b38756..8c80377c8ec 100644
--- a/cpp/ql/src/semmle/code/cpp/models/interfaces/FlowSource.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/interfaces/FlowSource.qll
@@ -1,9 +1,9 @@
/**
- * Provides a class for modeling functions that return data from potentially untrusted sources. To use
- * this QL library, create a QL class extending `DataFlowFunction` with a
+ * Provides classes for modeling functions that return data from (or send data to) potentially untrusted
+ * sources. To use this QL library, create a QL class extending `DataFlowFunction` with a
* characteristic predicate that selects the function or set of functions you
* are modeling. Within that class, override the predicates provided by
- * `RemoteFlowFunction` to match the flow within that function.
+ * `RemoteFlowSourceFunction` or `RemoteFlowSinkFunction` to match the flow within that function.
*/
import cpp
@@ -13,19 +13,42 @@ import semmle.code.cpp.models.Models
/**
* A library function that returns data that may be read from a network connection.
*/
-abstract class RemoteFlowFunction extends Function {
+abstract class RemoteFlowSourceFunction extends Function {
/**
* Holds if remote data described by `description` flows from `output` of a call to this function.
*/
abstract predicate hasRemoteFlowSource(FunctionOutput output, string description);
}
+/**
+ * DEPRECATED: Use `RemoteFlowSourceFunction` instead.
+ *
+ * A library function that returns data that may be read from a network connection.
+ */
+deprecated class RemoteFlowFunction = RemoteFlowSourceFunction;
+
/**
* A library function that returns data that is directly controlled by a user.
*/
-abstract class LocalFlowFunction extends Function {
+abstract class LocalFlowSourceFunction extends Function {
/**
* Holds if data described by `description` flows from `output` of a call to this function.
*/
abstract predicate hasLocalFlowSource(FunctionOutput output, string description);
}
+
+/**
+ * DEPRECATED: Use `LocalFlowSourceFunction` instead.
+ *
+ * A library function that returns data that is directly controlled by a user.
+ */
+deprecated class LocalFlowFunction = LocalFlowSourceFunction;
+
+/** A library function that sends data over a network connection. */
+abstract class RemoteFlowSinkFunction extends Function {
+ /**
+ * Holds if data described by `description` flows into `input` to a call to this function, and is then
+ * send over a network connection.
+ */
+ abstract predicate hasRemoteFlowSink(FunctionInput input, string description);
+}
diff --git a/cpp/ql/src/semmle/code/cpp/models/interfaces/FunctionInputsAndOutputs.qll b/cpp/ql/src/semmle/code/cpp/models/interfaces/FunctionInputsAndOutputs.qll
index 195cbcb63b5..4ab55ee5b3f 100644
--- a/cpp/ql/src/semmle/code/cpp/models/interfaces/FunctionInputsAndOutputs.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/interfaces/FunctionInputsAndOutputs.qll
@@ -108,6 +108,16 @@ class FunctionInput extends TFunctionInput {
*/
predicate isQualifierAddress() { none() }
+ /**
+ * Holds if `i >= 0` and `isParameter(i)` holds for this value, or
+ * if `i = -1` and `isQualifierAddress()` holds for this value.
+ */
+ final predicate isParameterOrQualifierAddress(ParameterIndex i) {
+ i >= 0 and this.isParameter(i)
+ or
+ i = -1 and this.isQualifierAddress()
+ }
+
/**
* Holds if this is the input value pointed to by the return value of a
* function, if the function returns a pointer, or the input value referred
@@ -134,7 +144,7 @@ class FunctionInput extends TFunctionInput {
predicate isReturnValueDeref() { none() }
/**
- * Holds if `i >= 0` and `isParameterDeref(i)` holds for this is value, or
+ * Holds if `i >= 0` and `isParameterDeref(i)` holds for this value, or
* if `i = -1` and `isQualifierObject()` holds for this value.
*/
final predicate isParameterDerefOrQualifierObject(ParameterIndex i) {
diff --git a/cpp/ql/src/semmle/code/cpp/models/interfaces/Taint.qll b/cpp/ql/src/semmle/code/cpp/models/interfaces/Taint.qll
index fe617533f59..05a5d9f1c28 100644
--- a/cpp/ql/src/semmle/code/cpp/models/interfaces/Taint.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/interfaces/Taint.qll
@@ -28,5 +28,6 @@ abstract class TaintFunction extends Function {
* Holds if data passed into the argument, qualifier, or buffer represented by
* `input` influences the return value or buffer represented by `output`
*/
+ pragma[nomagic]
abstract predicate hasTaintFlow(FunctionInput input, FunctionOutput output);
}
diff --git a/cpp/ql/src/semmle/code/cpp/security/BufferAccess.qll b/cpp/ql/src/semmle/code/cpp/security/BufferAccess.qll
index 977e5fcb078..faeb859506d 100644
--- a/cpp/ql/src/semmle/code/cpp/security/BufferAccess.qll
+++ b/cpp/ql/src/semmle/code/cpp/security/BufferAccess.qll
@@ -42,13 +42,11 @@ abstract class BufferAccess extends Expr {
*/
class MemcpyBA extends BufferAccess {
MemcpyBA() {
- this.(FunctionCall).getTarget().getName() = "memcpy" or
- this.(FunctionCall).getTarget().getName() = "wmemcpy" or
- this.(FunctionCall).getTarget().getName() = "memmove" or
- this.(FunctionCall).getTarget().getName() = "wmemmove" or
- this.(FunctionCall).getTarget().getName() = "mempcpy" or
- this.(FunctionCall).getTarget().getName() = "wmempcpy" or
- this.(FunctionCall).getTarget().getName() = "RtlCopyMemoryNonTemporal"
+ this.(FunctionCall).getTarget().getName() =
+ [
+ "memcpy", "wmemcpy", "memmove", "wmemmove", "mempcpy", "wmempcpy",
+ "RtlCopyMemoryNonTemporal"
+ ]
}
override string getName() { result = this.(FunctionCall).getTarget().getName() }
@@ -157,10 +155,7 @@ class MemccpyBA extends BufferAccess {
*/
class MemcmpBA extends BufferAccess {
MemcmpBA() {
- this.(FunctionCall).getTarget().getName() = "memcmp" or
- this.(FunctionCall).getTarget().getName() = "wmemcmp" or
- this.(FunctionCall).getTarget().getName() = "_memicmp" or
- this.(FunctionCall).getTarget().getName() = "_memicmp_l"
+ this.(FunctionCall).getTarget().getName() = ["memcmp", "wmemcmp", "_memicmp", "_memicmp_l"]
}
override string getName() { result = this.(FunctionCall).getTarget().getName() }
@@ -188,10 +183,7 @@ class MemcmpBA extends BufferAccess {
* _swab(src, dest, num)
*/
class SwabBA extends BufferAccess {
- SwabBA() {
- this.(FunctionCall).getTarget().getName() = "swab" or
- this.(FunctionCall).getTarget().getName() = "_swab"
- }
+ SwabBA() { this.(FunctionCall).getTarget().getName() = ["swab", "_swab"] }
override string getName() { result = this.(FunctionCall).getTarget().getName() }
@@ -218,10 +210,7 @@ class SwabBA extends BufferAccess {
* wmemset(dest, value, num)
*/
class MemsetBA extends BufferAccess {
- MemsetBA() {
- this.(FunctionCall).getTarget().getName() = "memset" or
- this.(FunctionCall).getTarget().getName() = "wmemset"
- }
+ MemsetBA() { this.(FunctionCall).getTarget().getName() = ["memset", "wmemset"] }
override string getName() { result = this.(FunctionCall).getTarget().getName() }
@@ -262,10 +251,7 @@ class ZeroMemoryBA extends BufferAccess {
* wmemchr(buffer, value, num)
*/
class MemchrBA extends BufferAccess {
- MemchrBA() {
- this.(FunctionCall).getTarget().getName() = "memchr" or
- this.(FunctionCall).getTarget().getName() = "wmemchr"
- }
+ MemchrBA() { this.(FunctionCall).getTarget().getName() = ["memchr", "wmemchr"] }
override string getName() { result = this.(FunctionCall).getTarget().getName() }
diff --git a/cpp/ql/src/semmle/code/cpp/security/CommandExecution.qll b/cpp/ql/src/semmle/code/cpp/security/CommandExecution.qll
index 38187f2e0e6..d9bb701be58 100644
--- a/cpp/ql/src/semmle/code/cpp/security/CommandExecution.qll
+++ b/cpp/ql/src/semmle/code/cpp/security/CommandExecution.qll
@@ -168,17 +168,14 @@ class ArrayExecFunctionCall extends FunctionCall {
* for testing purposes.
*/
predicate shellCommandPreface(string cmd, string flag) {
- (cmd = "sh" or cmd = "/bin/sh" or cmd = "bash" or cmd = "/bin/bash") and
+ cmd = ["sh", "/bin/sh", "bash", "/bin/bash"] and
flag = "-c"
or
- (
- cmd = "cmd" or
- cmd = "cmd.exe" or
- cmd = "CMD" or
- cmd = "CMD.EXE" or
- cmd = "%WINDIR%\\system32\\cmd.exe" // used in Juliet tests
- ) and
- (flag = "/c" or flag = "/C")
+ cmd =
+ [
+ "cmd", "cmd.exe", "CMD", "CMD.EXE", "%WINDIR%\\system32\\cmd.exe" // used in Juliet tests
+ ] and
+ flag = ["/c", "/C"]
}
/**
diff --git a/cpp/ql/src/semmle/code/cpp/security/Encryption.qll b/cpp/ql/src/semmle/code/cpp/security/Encryption.qll
index d8b2d44c923..606242e833c 100644
--- a/cpp/ql/src/semmle/code/cpp/security/Encryption.qll
+++ b/cpp/ql/src/semmle/code/cpp/security/Encryption.qll
@@ -8,21 +8,17 @@ import cpp
* Gets the name of an algorithm that is known to be insecure.
*/
string getAnInsecureAlgorithmName() {
- result = "DES" or
- result = "RC2" or
- result = "RC4" or
- result = "RC5" or
- result = "ARCFOUR" // a variant of RC4
+ result =
+ [
+ "DES", "RC2", "RC4", "RC5", "ARCFOUR" // ARCFOUR is a variant of RC4
+ ]
}
/**
* Gets the name of a hash algorithm that is insecure if it is being used for
* encryption (but it is hard to know when that is happening).
*/
-string getAnInsecureHashAlgorithmName() {
- result = "SHA1" or
- result = "MD5"
-}
+string getAnInsecureHashAlgorithmName() { result = ["SHA1", "MD5"] }
/**
* Gets the regular expression used for matching strings that look like they
@@ -45,13 +41,7 @@ string getInsecureAlgorithmRegex() {
* Gets the name of an algorithm that is known to be secure.
*/
string getASecureAlgorithmName() {
- result = "RSA" or
- result = "SHA256" or
- result = "CCM" or
- result = "GCM" or
- result = "AES" or
- result = "Blowfish" or
- result = "ECIES"
+ result = ["RSA", "SHA256", "CCM", "GCM", "AES", "Blowfish", "ECIES"]
}
/**
diff --git a/cpp/ql/src/semmle/code/cpp/security/FlowSources.qll b/cpp/ql/src/semmle/code/cpp/security/FlowSources.qll
index c8bad67352b..b080651951f 100644
--- a/cpp/ql/src/semmle/code/cpp/security/FlowSources.qll
+++ b/cpp/ql/src/semmle/code/cpp/security/FlowSources.qll
@@ -23,7 +23,7 @@ private class RemoteReturnSource extends RemoteFlowSource {
string sourceType;
RemoteReturnSource() {
- exists(RemoteFlowFunction func, CallInstruction instr, FunctionOutput output |
+ exists(RemoteFlowSourceFunction func, CallInstruction instr, FunctionOutput output |
asInstruction() = instr and
instr.getStaticCallTarget() = func and
func.hasRemoteFlowSource(output, sourceType) and
@@ -42,7 +42,7 @@ private class RemoteParameterSource extends RemoteFlowSource {
string sourceType;
RemoteParameterSource() {
- exists(RemoteFlowFunction func, WriteSideEffectInstruction instr, FunctionOutput output |
+ exists(RemoteFlowSourceFunction func, WriteSideEffectInstruction instr, FunctionOutput output |
asInstruction() = instr and
instr.getPrimaryInstruction().(CallInstruction).getStaticCallTarget() = func and
func.hasRemoteFlowSource(output, sourceType) and
@@ -57,7 +57,7 @@ private class LocalReturnSource extends LocalFlowSource {
string sourceType;
LocalReturnSource() {
- exists(LocalFlowFunction func, CallInstruction instr, FunctionOutput output |
+ exists(LocalFlowSourceFunction func, CallInstruction instr, FunctionOutput output |
asInstruction() = instr and
instr.getStaticCallTarget() = func and
func.hasLocalFlowSource(output, sourceType) and
@@ -76,7 +76,7 @@ private class LocalParameterSource extends LocalFlowSource {
string sourceType;
LocalParameterSource() {
- exists(LocalFlowFunction func, WriteSideEffectInstruction instr, FunctionOutput output |
+ exists(LocalFlowSourceFunction func, WriteSideEffectInstruction instr, FunctionOutput output |
asInstruction() = instr and
instr.getPrimaryInstruction().(CallInstruction).getStaticCallTarget() = func and
func.hasLocalFlowSource(output, sourceType) and
@@ -98,3 +98,31 @@ private class ArgvSource extends LocalFlowSource {
override string getSourceType() { result = "a command-line argument" }
}
+
+/** A remote data flow sink. */
+abstract class RemoteFlowSink extends DataFlow::Node {
+ /** Gets a string that describes the type of this flow sink. */
+ abstract string getSinkType();
+}
+
+private class RemoteParameterSink extends RemoteFlowSink {
+ string sourceType;
+
+ RemoteParameterSink() {
+ exists(RemoteFlowSinkFunction func, FunctionInput input, CallInstruction call, int index |
+ func.hasRemoteFlowSink(input, sourceType) and call.getStaticCallTarget() = func
+ |
+ exists(ReadSideEffectInstruction read |
+ call = read.getPrimaryInstruction() and
+ read.getIndex() = index and
+ this.asOperand() = read.getSideEffectOperand() and
+ input.isParameterDerefOrQualifierObject(index)
+ )
+ or
+ input.isParameterOrQualifierAddress(index) and
+ this.asOperand() = call.getArgumentOperand(index)
+ )
+ }
+
+ override string getSinkType() { result = sourceType }
+}
diff --git a/cpp/ql/src/semmle/code/cpp/security/Security.qll b/cpp/ql/src/semmle/code/cpp/security/Security.qll
index c12a70b52c6..d39c13a25a0 100644
--- a/cpp/ql/src/semmle/code/cpp/security/Security.qll
+++ b/cpp/ql/src/semmle/code/cpp/security/Security.qll
@@ -6,6 +6,7 @@
import semmle.code.cpp.exprs.Expr
import semmle.code.cpp.commons.Environment
import semmle.code.cpp.security.SecurityOptions
+import semmle.code.cpp.models.interfaces.FlowSource
/**
* Extend this class to customize the security queries for
@@ -20,36 +21,13 @@ class SecurityOptions extends string {
* name is a pure function of its arguments.
*/
predicate isPureFunction(string name) {
- name = "abs" or
- name = "atof" or
- name = "atoi" or
- name = "atol" or
- name = "atoll" or
- name = "labs" or
- name = "strcasestr" or
- name = "strcat" or
- name = "strchnul" or
- name = "strchr" or
- name = "strchrnul" or
- name = "strcmp" or
- name = "strcpy" or
- name = "strcspn" or
- name = "strdup" or
- name = "strlen" or
- name = "strncat" or
- name = "strncmp" or
- name = "strncpy" or
- name = "strndup" or
- name = "strnlen" or
- name = "strrchr" or
- name = "strspn" or
- name = "strstr" or
- name = "strtod" or
- name = "strtof" or
- name = "strtol" or
- name = "strtoll" or
- name = "strtoq" or
- name = "strtoul"
+ name =
+ [
+ "abs", "atof", "atoi", "atol", "atoll", "labs", "strcasestr", "strcat", "strchnul",
+ "strchr", "strchrnul", "strcmp", "strcpy", "strcspn", "strdup", "strlen", "strncat",
+ "strncmp", "strncpy", "strndup", "strnlen", "strrchr", "strspn", "strstr", "strtod",
+ "strtof", "strtol", "strtoll", "strtoq", "strtoul"
+ ]
}
/**
@@ -73,13 +51,7 @@ class SecurityOptions extends string {
functionCall.getTarget().hasGlobalOrStdName(fname) and
exists(functionCall.getArgument(arg)) and
(
- fname = "fread" and arg = 0
- or
- fname = "fgets" and arg = 0
- or
- fname = "fgetws" and arg = 0
- or
- fname = "gets" and arg = 0
+ fname = ["fread", "fgets", "fgetws", "gets"] and arg = 0
or
fname = "scanf" and arg >= 1
or
@@ -88,18 +60,14 @@ class SecurityOptions extends string {
or
functionCall.getTarget().hasGlobalName(fname) and
exists(functionCall.getArgument(arg)) and
- (
- fname = "read" and arg = 1
- or
- fname = "getaddrinfo" and arg = 3
- or
- fname = "recv" and arg = 1
- or
- fname = "recvfrom" and
- (arg = 1 or arg = 4 or arg = 5)
- or
- fname = "recvmsg" and arg = 1
- )
+ fname = "getaddrinfo" and
+ arg = 3
+ )
+ or
+ exists(RemoteFlowSourceFunction remote, FunctionOutput output |
+ functionCall.getTarget() = remote and
+ output.isParameterDerefOrQualifierObject(arg) and
+ remote.hasRemoteFlowSource(output, _)
)
}
@@ -110,11 +78,16 @@ class SecurityOptions extends string {
exists(string fname |
functionCall.getTarget().getName() = fname and
(
- fname = "fgets" or
- fname = "gets" or
+ fname = ["fgets", "gets"] or
userInputReturn(fname)
)
)
+ or
+ exists(RemoteFlowSourceFunction remote, FunctionOutput output |
+ functionCall.getTarget() = remote and
+ (output.isReturnValue() or output.isReturnValueDeref()) and
+ remote.hasRemoteFlowSource(output, _)
+ )
}
/**
@@ -132,30 +105,12 @@ class SecurityOptions extends string {
*/
predicate isProcessOperationArgument(string function, int arg) {
// POSIX
- function = "system" and arg = 0
- or
- function = "popen" and arg = 0
- or
- function = "execl" and arg = 0
- or
- function = "execlp" and arg = 0
- or
- function = "execle" and arg = 0
- or
- function = "execv" and arg = 0
- or
- function = "execvp" and arg = 0
- or
- function = "execvpe" and arg = 0
- or
- function = "dlopen" and arg = 0
+ function =
+ ["system", "popen", "execl", "execlp", "execle", "execv", "execvp", "execvpe", "dlopen"] and
+ arg = 0
or
// Windows
- function = "LoadLibrary" and arg = 0
- or
- function = "LoadLibraryA" and arg = 0
- or
- function = "LoadLibraryW" and arg = 0
+ function = ["LoadLibrary", "LoadLibraryA", "LoadLibraryW"] and arg = 0
}
/**
diff --git a/cpp/ql/src/semmlecode.cpp.dbscheme b/cpp/ql/src/semmlecode.cpp.dbscheme
index c82db4c596b..ddd31fd02e5 100644
--- a/cpp/ql/src/semmlecode.cpp.dbscheme
+++ b/cpp/ql/src/semmlecode.cpp.dbscheme
@@ -691,8 +691,28 @@ typedefbase(
int type_id: @type ref
);
+/**
+ * An instance of the C++11 `decltype` operator. For example:
+ * ```
+ * int a;
+ * decltype(1+a) b;
+ * ```
+ * Here `expr` is `1+a`.
+ *
+ * Sometimes an additional pair of parentheses around the expression
+ * would change the semantics of this decltype, e.g.
+ * ```
+ * struct A { double x; };
+ * const A* a = new A();
+ * decltype( a->x ); // type is double
+ * decltype((a->x)); // type is const double&
+ * ```
+ * (Please consult the C++11 standard for more details).
+ * `parentheses_would_change_meaning` is `true` iff that is the case.
+ */
+#keyset[id, expr]
decltypes(
- unique int id: @decltype,
+ int id: @decltype,
int expr: @expr ref,
int base_type: @type ref,
boolean parentheses_would_change_meaning: boolean ref
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-14/semmle/tests/CompilerRemovalOfCodeToClearBuffers.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-14/semmle/tests/CompilerRemovalOfCodeToClearBuffers.expected
new file mode 100644
index 00000000000..8d98d94ff46
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-14/semmle/tests/CompilerRemovalOfCodeToClearBuffers.expected
@@ -0,0 +1,3 @@
+| test.c:13:9:13:13 | buff1 | This variable will not be cleared. |
+| test.c:35:9:35:13 | buff1 | This variable will not be cleared. |
+| test.c:43:9:43:13 | buff1 | This variable will not be cleared. |
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-14/semmle/tests/CompilerRemovalOfCodeToClearBuffers.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-14/semmle/tests/CompilerRemovalOfCodeToClearBuffers.qlref
new file mode 100644
index 00000000000..61d2a29b126
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-14/semmle/tests/CompilerRemovalOfCodeToClearBuffers.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-14/CompilerRemovalOfCodeToClearBuffers.ql
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-14/semmle/tests/test.c b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-14/semmle/tests/test.c
new file mode 100644
index 00000000000..221072330c3
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-14/semmle/tests/test.c
@@ -0,0 +1,201 @@
+struct buffers
+{
+ unsigned char buff1[50];
+ unsigned char *buff2;
+} globalBuff1,*globalBuff2;
+
+unsigned char * globalBuff;
+void badFunc0_0(){
+ unsigned char buff1[12];
+ int i;
+ for(i=0;i<12;i++)
+ buff1[i]=13;
+ memset(buff1,12,12);
+}
+void nobadFunc0_0(){
+ unsigned char buff1[12];
+ memset(buff1,12,12);
+}
+void nobadFunc0_1(){
+ unsigned char buff1[12];
+ int i;
+ memset(buff1,12,12);
+ for(i=0;i<12;i++)
+ buff1[i]=13;
+ free(buff1);
+}
+void nobadFunc1_0(){
+ unsigned char * buff1;
+ buff1 = (unsigned char *) malloc(12);
+ memset(buff1,12,12);
+}
+void badFunc1_0(){
+ unsigned char * buff1;
+ buff1 = (unsigned char *) malloc(12);
+ memset(buff1,12,12);
+ free(buff1);
+}
+void badFunc1_1(){
+ unsigned char buff1[12];
+ int i;
+ for(i=0;i<12;i++)
+ buff1[i]=13;
+ memset(buff1,12,12);
+ free(buff1);
+}
+void nobadFunc2_0_0(){
+ unsigned char buff1[12];
+ int i;
+ for(i=0;i<12;i++)
+ buff1[i]=13;
+ memset(buff1,12,12);
+ printf(buff1);
+}
+
+void nobadFunc2_0_1(){
+ unsigned char buff1[12];
+ int i;
+ for(i=0;i<12;i++)
+ buff1[i]=13;
+ memset(buff1,12,12);
+ printf(buff1+3);
+}
+
+void nobadFunc2_0_2(){
+ unsigned char buff1[12];
+ int i;
+ for(i=0;i<12;i++)
+ buff1[i]=13;
+ memset(buff1,12,12);
+ printf(*buff1);
+}
+
+void nobadFunc2_0_3(){
+ unsigned char buff1[12];
+ int i;
+ for(i=0;i<12;i++)
+ buff1[i]=13;
+ memset(buff1,12,12);
+ printf(*(buff1+3));
+}
+unsigned char * nobadFunc2_0_4(){
+ unsigned char buff1[12];
+ int i;
+ for(i=0;i<12;i++)
+ buff1[i]=13;
+ memset(buff1,12,12);
+ return buff1;
+}
+
+unsigned char * nobadFunc2_0_5(){
+ unsigned char buff1[12];
+ int i;
+ for(i=0;i<12;i++)
+ buff1[i]=13;
+ memset(buff1,12,12);
+ return buff1+3;
+}
+unsigned char nobadFunc2_0_6(){
+ unsigned char buff1[12];
+ int i;
+ for(i=0;i<12;i++)
+ buff1[i]=13;
+ memset(buff1,12,12);
+ return *buff1;
+}
+
+unsigned char nobadFunc2_0_7(){
+ unsigned char buff1[12];
+ int i;
+ for(i=0;i<12;i++)
+ buff1[i]=13;
+ memset(buff1,12,12);
+ return *(buff1+3);
+}
+void nobadFunc2_1_0(){
+ unsigned char buff1[12];
+ int i;
+ for(i=0;i<12;i++)
+ buff1[i]=13;
+ memset(buff1,12,12);
+ if(*buff1==0)
+ printf("123123");
+}
+void nobadFunc2_1_1(){
+ unsigned char buff1[12];
+ int i;
+ for(i=0;i<12;i++)
+ buff1[i]=13;
+ memset(buff1,12,12);
+ if(*(buff1+3)==0)
+ printf("123123");
+}
+void nobadFunc2_1_2(){
+ unsigned char buff1[12];
+ int i;
+ for(i=0;i<12;i++)
+ buff1[i]=13;
+ memset(buff1,12,12);
+ buff1[2]=5;
+}
+void nobadFunc3_0(unsigned char * buffAll){
+ unsigned char * buff1 = buffAll;
+ memset(buff1,12,12);
+}
+void nobadFunc3_1(unsigned char * buffAll){
+ unsigned char * buff1 = buffAll+3;
+ memset(buff1,12,12);
+}
+void nobadFunc3_2(struct buffers buffAll){
+ unsigned char * buff1 = buffAll.buff1;
+ memset(buff1,12,12);
+}
+void nobadFunc3_3(struct buffers buffAll){
+ unsigned char * buff1 = buffAll.buff2;
+ memset(buff1,12,12);
+}
+void nobadFunc3_4(struct buffers buffAll){
+ unsigned char * buff1 = buffAll.buff2+3;
+ memset(buff1,12,12);
+}
+void nobadFunc3_5(struct buffers * buffAll){
+ unsigned char * buff1 = buffAll->buff1;
+ memset(buff1,12,12);
+}
+void nobadFunc3_6(struct buffers *buffAll){
+ unsigned char * buff1 = buffAll->buff2;
+ memset(buff1,12,12);
+}
+void nobadFunc4(){
+ unsigned char * buff1 = globalBuff;
+ memset(buff1,12,12);
+}
+void nobadFunc4_0(){
+ unsigned char * buff1 = globalBuff;
+ memset(buff1,12,12);
+}
+void nobadFunc4_1(){
+ unsigned char * buff1 = globalBuff+3;
+ memset(buff1,12,12);
+}
+void nobadFunc4_2(){
+ unsigned char * buff1 = globalBuff1.buff1;
+ memset(buff1,12,12);
+}
+void nobadFunc4_3(){
+ unsigned char * buff1 = globalBuff1.buff2;
+ memset(buff1,12,12);
+}
+void nobadFunc4_4(){
+ unsigned char * buff1 = globalBuff1.buff2+3;
+ memset(buff1,12,12);
+}
+void nobadFunc4_5(){
+ unsigned char * buff1 = globalBuff2->buff1;
+ memset(buff1,12,12);
+}
+void nobadFunc4_6(){
+ unsigned char * buff1 = globalBuff2->buff2;
+ memset(buff1,12,12);
+}
+
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-190/AllocMultiplicationOverflow/AllocMultiplicationOverflow.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-190/AllocMultiplicationOverflow/AllocMultiplicationOverflow.expected
new file mode 100644
index 00000000000..b8428a43d11
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-190/AllocMultiplicationOverflow/AllocMultiplicationOverflow.expected
@@ -0,0 +1,17 @@
+edges
+| test.cpp:22:17:22:21 | ... * ... | test.cpp:23:33:23:37 | size1 |
+nodes
+| test.cpp:13:33:13:37 | ... * ... | semmle.label | ... * ... |
+| test.cpp:15:31:15:35 | ... * ... | semmle.label | ... * ... |
+| test.cpp:19:34:19:38 | ... * ... | semmle.label | ... * ... |
+| test.cpp:22:17:22:21 | ... * ... | semmle.label | ... * ... |
+| test.cpp:23:33:23:37 | size1 | semmle.label | size1 |
+| test.cpp:30:27:30:31 | ... * ... | semmle.label | ... * ... |
+| test.cpp:31:27:31:31 | ... * ... | semmle.label | ... * ... |
+#select
+| test.cpp:13:33:13:37 | ... * ... | test.cpp:13:33:13:37 | ... * ... | test.cpp:13:33:13:37 | ... * ... | Potentially overflowing value from $@ is used in the size of this allocation. | test.cpp:13:33:13:37 | ... * ... | multiplication |
+| test.cpp:15:31:15:35 | ... * ... | test.cpp:15:31:15:35 | ... * ... | test.cpp:15:31:15:35 | ... * ... | Potentially overflowing value from $@ is used in the size of this allocation. | test.cpp:15:31:15:35 | ... * ... | multiplication |
+| test.cpp:19:34:19:38 | ... * ... | test.cpp:19:34:19:38 | ... * ... | test.cpp:19:34:19:38 | ... * ... | Potentially overflowing value from $@ is used in the size of this allocation. | test.cpp:19:34:19:38 | ... * ... | multiplication |
+| test.cpp:23:33:23:37 | size1 | test.cpp:22:17:22:21 | ... * ... | test.cpp:23:33:23:37 | size1 | Potentially overflowing value from $@ is used in the size of this allocation. | test.cpp:22:17:22:21 | ... * ... | multiplication |
+| test.cpp:30:27:30:31 | ... * ... | test.cpp:30:27:30:31 | ... * ... | test.cpp:30:27:30:31 | ... * ... | Potentially overflowing value from $@ is used in the size of this allocation. | test.cpp:30:27:30:31 | ... * ... | multiplication |
+| test.cpp:31:27:31:31 | ... * ... | test.cpp:31:27:31:31 | ... * ... | test.cpp:31:27:31:31 | ... * ... | Potentially overflowing value from $@ is used in the size of this allocation. | test.cpp:31:27:31:31 | ... * ... | multiplication |
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-190/AllocMultiplicationOverflow/AllocMultiplicationOverflow.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-190/AllocMultiplicationOverflow/AllocMultiplicationOverflow.qlref
new file mode 100644
index 00000000000..7bb108b6628
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-190/AllocMultiplicationOverflow/AllocMultiplicationOverflow.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-190/AllocMultiplicationOverflow.ql
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-190/AllocMultiplicationOverflow/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-190/AllocMultiplicationOverflow/test.cpp
new file mode 100644
index 00000000000..ab26fef3ed0
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-190/AllocMultiplicationOverflow/test.cpp
@@ -0,0 +1,32 @@
+
+typedef unsigned long size_t;
+void *malloc(size_t size);
+
+int getAnInt();
+
+void test()
+{
+ int x = getAnInt();
+ int y = getAnInt();
+
+ char *buffer1 = (char *)malloc(x + y); // GOOD
+ char *buffer2 = (char *)malloc(x * y); // BAD
+ int *buffer3 = (int *)malloc(x * sizeof(int)); // GOOD
+ int *buffer4 = (int *)malloc(x * y * sizeof(int)); // BAD
+
+ if ((x <= 1000) && (y <= 1000))
+ {
+ char *buffer5 = (char *)malloc(x * y); // GOOD [FALSE POSITIVE]
+ }
+
+ size_t size1 = x * y;
+ char *buffer5 = (char *)malloc(size1); // BAD
+
+ size_t size2 = x;
+ size2 *= y;
+ char *buffer6 = (char *)malloc(size2); // BAD [NOT DETECTED]
+
+ char *buffer7 = new char[x * 10]; // GOOD
+ char *buffer8 = new char[x * y]; // BAD
+ char *buffer9 = new char[x * x]; // BAD
+}
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-359/semmle/tests/PrivateCleartextWrite.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-359/semmle/tests/PrivateCleartextWrite.expected
index c263d7e6dfd..887547f2c2e 100644
--- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-359/semmle/tests/PrivateCleartextWrite.expected
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-359/semmle/tests/PrivateCleartextWrite.expected
@@ -13,9 +13,9 @@ nodes
| test.cpp:96:37:96:46 | theZipcode | semmle.label | theZipcode |
| test.cpp:99:42:99:51 | theZipcode | semmle.label | theZipcode |
#select
-| test.cpp:57:9:57:18 | theZipcode | This write into the external location 'theZipcode' may contain unencrypted data from $@ | test.cpp:57:9:57:18 | theZipcode | this source. |
-| test.cpp:74:24:74:30 | medical | This write into the external location 'medical' may contain unencrypted data from $@ | test.cpp:74:24:74:30 | medical | this source. |
-| test.cpp:78:24:78:27 | temp | This write into the external location 'temp' may contain unencrypted data from $@ | test.cpp:77:16:77:22 | medical | this source. |
-| test.cpp:82:24:82:28 | buff5 | This write into the external location 'buff5' may contain unencrypted data from $@ | test.cpp:81:22:81:28 | medical | this source. |
-| test.cpp:96:37:96:46 | theZipcode | This write into the external location 'theZipcode' may contain unencrypted data from $@ | test.cpp:96:37:96:46 | theZipcode | this source. |
-| test.cpp:99:42:99:51 | theZipcode | This write into the external location 'theZipcode' may contain unencrypted data from $@ | test.cpp:99:42:99:51 | theZipcode | this source. |
+| test.cpp:57:9:57:18 | theZipcode | test.cpp:57:9:57:18 | theZipcode | test.cpp:57:9:57:18 | theZipcode | This write into the external location 'theZipcode' may contain unencrypted data from $@ | test.cpp:57:9:57:18 | theZipcode | this source. |
+| test.cpp:74:24:74:30 | medical | test.cpp:74:24:74:30 | medical | test.cpp:74:24:74:30 | medical | This write into the external location 'medical' may contain unencrypted data from $@ | test.cpp:74:24:74:30 | medical | this source. |
+| test.cpp:78:24:78:27 | temp | test.cpp:77:16:77:22 | medical | test.cpp:78:24:78:27 | temp | This write into the external location 'temp' may contain unencrypted data from $@ | test.cpp:77:16:77:22 | medical | this source. |
+| test.cpp:82:24:82:28 | buff5 | test.cpp:81:22:81:28 | medical | test.cpp:82:24:82:28 | buff5 | This write into the external location 'buff5' may contain unencrypted data from $@ | test.cpp:81:22:81:28 | medical | this source. |
+| test.cpp:96:37:96:46 | theZipcode | test.cpp:96:37:96:46 | theZipcode | test.cpp:96:37:96:46 | theZipcode | This write into the external location 'theZipcode' may contain unencrypted data from $@ | test.cpp:96:37:96:46 | theZipcode | this source. |
+| test.cpp:99:42:99:51 | theZipcode | test.cpp:99:42:99:51 | theZipcode | test.cpp:99:42:99:51 | theZipcode | This write into the external location 'theZipcode' may contain unencrypted data from $@ | test.cpp:99:42:99:51 | theZipcode | this source. |
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-401/semmle/tests/MemoryLeakOnFailedCallToRealloc.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-401/semmle/tests/MemoryLeakOnFailedCallToRealloc.expected
new file mode 100644
index 00000000000..b40e19c7559
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-401/semmle/tests/MemoryLeakOnFailedCallToRealloc.expected
@@ -0,0 +1,8 @@
+| test.c:34:29:34:35 | call to realloc | possible loss of original pointer on unsuccessful call realloc |
+| test.c:63:29:63:35 | call to realloc | possible loss of original pointer on unsuccessful call realloc |
+| test.c:139:29:139:35 | call to realloc | possible loss of original pointer on unsuccessful call realloc |
+| test.c:186:29:186:35 | call to realloc | possible loss of original pointer on unsuccessful call realloc |
+| test.c:282:29:282:35 | call to realloc | possible loss of original pointer on unsuccessful call realloc |
+| test.c:299:26:299:32 | call to realloc | possible loss of original pointer on unsuccessful call realloc |
+| test.c:328:29:328:35 | call to realloc | possible loss of original pointer on unsuccessful call realloc |
+| test.c:342:29:342:35 | call to realloc | possible loss of original pointer on unsuccessful call realloc |
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-401/semmle/tests/MemoryLeakOnFailedCallToRealloc.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-401/semmle/tests/MemoryLeakOnFailedCallToRealloc.qlref
new file mode 100644
index 00000000000..e80e86cbdcc
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-401/semmle/tests/MemoryLeakOnFailedCallToRealloc.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-401/MemoryLeakOnFailedCallToRealloc.ql
\ No newline at end of file
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-401/semmle/tests/test.c b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-401/semmle/tests/test.c
new file mode 100644
index 00000000000..df33fc19ef6
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-401/semmle/tests/test.c
@@ -0,0 +1,346 @@
+#define size_t int
+#define NULL ((void*)0)
+
+#define assert(x) if (!(x)) __assert_fail(#x,__FILE__,__LINE__)
+void __assert_fail(const char *assertion, const char *file, int line);
+
+void aFakeFailed_1(int file, int line)
+{
+}
+void aFailed_1(int file, int line)
+{
+ exit(0);
+}
+void aFailed_2(int file, int line, int ex)
+{
+ if(ex == 1)
+ exit(0);
+ else
+ return;
+}
+#define F_NUM 1
+#define myASSERT_1(expr) \
+ if (!(expr)) \
+ aFailed_1(F_NUM, __LINE__)
+#define myASSERT_2(expr) \
+ if (!(expr)) \
+ aFailed_2(F_NUM, __LINE__, 1)
+
+unsigned char * badResize_0(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // BAD: on unsuccessful call to realloc, we will lose a pointer to a valid memory block
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ }
+ return buffer;
+}
+
+unsigned char * goodResize_0(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: this way we will exclude possible memory leak
+ unsigned char * tmp;
+ if (currentSize < newSize)
+ {
+ tmp = (unsigned char *)realloc(buffer, newSize);
+ }
+ if (tmp == NULL)
+ {
+ free(buffer);
+ return NULL;
+ }
+ else
+ buffer = tmp;
+ return buffer;
+}
+unsigned char * badResize_1_0(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ if(!buffer)
+ exit(0);
+ // BAD: on unsuccessful call to realloc, we will lose a pointer to a valid memory block
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ }
+ return buffer;
+}
+
+unsigned char * noBadResize_1_0(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ }
+ if(!buffer)
+ exit(0);
+ return buffer;
+}
+unsigned char * noBadResize_1_1(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ }
+ if(buffer)
+ return buffer;
+ else
+ exit(0);
+}
+unsigned char * noBadResize_1_2(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ if(buffer = (unsigned char *)realloc(buffer, newSize))
+ exit(0);
+ }
+ return buffer;
+}
+unsigned char * noBadResize_1_3(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ }
+ if(!buffer)
+ aFailed_1(1, 1);
+ return buffer;
+}
+unsigned char * noBadResize_1_4(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ }
+ if(buffer)
+ return buffer;
+ else
+ aFailed_1(1, 1);
+}
+unsigned char * noBadResize_1_5(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ if(buffer = (unsigned char *)realloc(buffer, newSize))
+ aFailed_1(1, 1);
+ }
+ return buffer;
+}
+unsigned char * badResize_1_1(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // BAD: on unsuccessful call to realloc, we will lose a pointer to a valid memory block
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ }
+ if(!buffer)
+ aFakeFailed_1(1, 1);
+ return buffer;
+}
+
+unsigned char * noBadResize_1_6(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ }
+ if(!buffer)
+ aFailed_2(1, 1, 1);
+ return buffer;
+}
+unsigned char * noBadResize_1_7(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ }
+ if(buffer)
+ return buffer;
+ else
+ aFailed_2(1, 1, 1);
+}
+unsigned char * noBadResize_1_8(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ if(buffer = (unsigned char *)realloc(buffer, newSize))
+ aFailed_2(1, 1, 1);
+ }
+ return buffer;
+}
+
+unsigned char * badResize_2_0(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // BAD: on unsuccessful call to realloc, we will lose a pointer to a valid memory block
+ assert(buffer!=0);
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ }
+ return buffer;
+}
+
+unsigned char * noBadResize_2_0(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ assert(buffer!=0);
+ }
+ return buffer;
+}
+
+unsigned char * noBadResize2e_2_1(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ }
+ assert(buffer!=0);
+ return buffer;
+}
+unsigned char * noBadResize_2_2(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ assert(buffer);
+ }
+ return buffer;
+}
+
+unsigned char * noBadResize_2_3(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ }
+ assert(buffer);
+ return buffer;
+}
+unsigned char * noBadResize_2_4(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ myASSERT_1(buffer);
+ }
+ return buffer;
+}
+
+unsigned char * noBadResize_2_5(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ }
+ myASSERT_1(buffer);
+ return buffer;
+}
+unsigned char * noBadResize_2_6(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ myASSERT_2(buffer);
+ }
+ return buffer;
+}
+
+unsigned char * noBadResize_2_7(unsigned char * buffer,size_t currentSize,size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ }
+ myASSERT_2(buffer);
+ return buffer;
+}
+
+unsigned char *goodResize_3_1(unsigned char *buffer, size_t currentSize, size_t newSize)
+{
+ // GOOD: this way we will exclude possible memory leak [FALSE POSITIVE]
+ unsigned char *tmp = buffer;
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ if (buffer == NULL)
+ {
+ free(tmp);
+ return NULL;
+ }
+ }
+
+ return buffer;
+}
+
+unsigned char *goodResize_3_2(unsigned char *buffer, size_t currentSize, size_t newSize)
+{
+ // GOOD: this way we will exclude possible memory leak [FALSE POSITIVE]
+ unsigned char *tmp = buffer;
+ if (currentSize < newSize)
+ {
+ tmp = (unsigned char *)realloc(tmp, newSize);
+ if (tmp != 0)
+ {
+ buffer = tmp;
+ }
+ }
+
+ return buffer;
+}
+
+void abort(void);
+
+unsigned char *noBadResize_4_1(unsigned char *buffer, size_t currentSize, size_t newSize)
+{
+ // GOOD: program to end
+ if (currentSize < newSize)
+ {
+ if (buffer = (unsigned char *)realloc(buffer, newSize))
+ abort();
+ }
+
+ return buffer;
+}
+
+unsigned char * badResize_5_2(unsigned char *buffer, size_t currentSize, size_t newSize, int cond)
+{
+ // BAD: on unsuccessful call to realloc, we will lose a pointer to a valid memory block
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ }
+ if (cond)
+ {
+ abort(); // irrelevant
+ }
+ return buffer;
+}
+
+unsigned char * badResize_5_1(unsigned char *buffer, size_t currentSize, size_t newSize, int cond)
+{
+ // BAD: on unsuccessful call to realloc, we will lose a pointer to a valid memory block
+ if (currentSize < newSize)
+ {
+ buffer = (unsigned char *)realloc(buffer, newSize);
+ assert(cond); // irrelevant
+ }
+ return buffer;
+}
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.expected
new file mode 100644
index 00000000000..80e82cff212
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.expected
@@ -0,0 +1,5 @@
+| test.cpp:30:15:30:26 | call to operator new[] | memory allocation error check is incorrect or missing |
+| test.cpp:38:9:38:20 | call to operator new[] | memory allocation error check is incorrect or missing |
+| test.cpp:50:13:50:38 | call to operator new[] | memory allocation error check is incorrect or missing |
+| test.cpp:51:22:51:47 | call to operator new[] | memory allocation error check is incorrect or missing |
+| test.cpp:53:18:53:43 | call to operator new[] | memory allocation error check is incorrect or missing |
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.qlref
new file mode 100644
index 00000000000..fc3252ef122
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/test.cpp
new file mode 100644
index 00000000000..e4aa8cf2976
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/test.cpp
@@ -0,0 +1,97 @@
+#define NULL ((void*)0)
+class exception {};
+
+namespace std{
+ struct nothrow_t {};
+ typedef unsigned long size_t;
+ class bad_alloc{
+ const char* what() const throw();
+ };
+ extern const std::nothrow_t nothrow;
+}
+
+using namespace std;
+
+void* operator new(std::size_t _Size);
+void* operator new[](std::size_t _Size);
+void* operator new( std::size_t count, const std::nothrow_t& tag );
+void* operator new[]( std::size_t count, const std::nothrow_t& tag );
+
+void badNew_0_0()
+{
+ while (true) {
+ new int[100]; // BAD [NOT DETECTED]
+ if(!(new int[100])) // BAD [NOT DETECTED]
+ return;
+ }
+}
+void badNew_0_1()
+{
+ int * i = new int[100]; // BAD
+ if(i == 0)
+ return;
+ if(!i)
+ return;
+ if(i == NULL)
+ return;
+ int * j;
+ j = new int[100]; // BAD
+ if(j == 0)
+ return;
+ if(!j)
+ return;
+ if(j == NULL)
+ return;
+}
+void badNew_1_0()
+{
+ try {
+ while (true) {
+ new(std::nothrow) int[100]; // BAD
+ int* p = new(std::nothrow) int[100]; // BAD
+ int* p1;
+ p1 = new(std::nothrow) int[100]; // BAD
+ }
+ } catch (const exception &){//const std::bad_alloc& e) {
+// std::cout << e.what() << '\n';
+ }
+}
+void badNew_1_1()
+{
+ while (true) {
+ int* p = new(std::nothrow) int[100]; // BAD [NOT DETECTED]
+ new(std::nothrow) int[100]; // BAD [NOT DETECTED]
+ }
+}
+
+void goodNew_0_0()
+{
+ try {
+ while (true) {
+ new int[100]; // GOOD
+ }
+ } catch (const exception &){//const std::bad_alloc& e) {
+// std::cout << e.what() << '\n';
+ }
+}
+
+void goodNew_1_0()
+{
+ while (true) {
+ int* p = new(std::nothrow) int[100]; // GOOD
+ if (p == nullptr) {
+// std::cout << "Allocation returned nullptr\n";
+ break;
+ }
+ int* p1;
+ p1 = new(std::nothrow) int[100]; // GOOD
+ if (p1 == nullptr) {
+// std::cout << "Allocation returned nullptr\n";
+ break;
+ }
+ if (new(std::nothrow) int[100] == nullptr) { // GOOD
+// std::cout << "Allocation returned nullptr\n";
+ break;
+ }
+ }
+}
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
new file mode 100644
index 00000000000..103afd8ffd9
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
@@ -0,0 +1,9 @@
+| test.c:42:3:42:24 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:43:3:43:40 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:44:3:44:40 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:45:3:45:44 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:46:3:46:44 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:47:3:47:48 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:48:3:48:48 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:49:3:49:50 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:50:3:50:50 | ... = ... | potential unsafe or redundant assignment. |
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref
new file mode 100644
index 00000000000..6ba005d087a
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.expected
new file mode 100644
index 00000000000..af52dac0144
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.expected
@@ -0,0 +1,3 @@
+| test.c:4:3:4:9 | call to strncat | if the used buffer is full, writing out of the buffer is possible |
+| test.c:11:3:11:9 | call to strncat | if the used buffer is full, writing out of the buffer is possible |
+| test.c:19:3:19:9 | call to strncat | if the used buffer is full, writing out of the buffer is possible |
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.qlref
new file mode 100644
index 00000000000..8fd8b1b3217
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.ql
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c
new file mode 100644
index 00000000000..d986bb3b13c
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c
@@ -0,0 +1,73 @@
+void workFunction_0(char *s) {
+ char buf[80];
+ strncat(buf, s, sizeof(buf)-strlen(buf)-1); // GOOD
+ strncat(buf, s, sizeof(buf)-strlen(buf)); // BAD
+ strncat(buf, "fix", sizeof(buf)-strlen(buf)); // BAD [NOT DETECTED]
+}
+void workFunction_1(char *s) {
+#define MAX_SIZE 80
+ char buf[MAX_SIZE];
+ strncat(buf, s, MAX_SIZE-strlen(buf)-1); // GOOD
+ strncat(buf, s, MAX_SIZE-strlen(buf)); // BAD
+ strncat(buf, "fix", MAX_SIZE-strlen(buf)); // BAD [NOT DETECTED]
+}
+void workFunction_2_0(char *s) {
+ char * buf;
+ int len=80;
+ buf = (char *) malloc(len);
+ strncat(buf, s, len-strlen(buf)-1); // GOOD
+ strncat(buf, s, len-strlen(buf)); // BAD
+ strncat(buf, "fix", len-strlen(buf)); // BAD [NOT DETECTED]
+}
+void workFunction_2_1(char *s) {
+ char * buf;
+ int len=80;
+ buf = (char *) malloc(len+1);
+ strncat(buf, s, len-strlen(buf)-1); // GOOD
+ strncat(buf, s, len-strlen(buf)); // GOOD
+}
+
+struct buffers
+{
+ unsigned char buff1[50];
+ unsigned char *buff2;
+} globalBuff1,*globalBuff2,globalBuff1_c,*globalBuff2_c;
+
+
+void badFunc0(){
+ unsigned char buff1[12];
+ struct buffers buffAll;
+ struct buffers * buffAll1;
+
+ buff1[strlen(buff1)]=0; // BAD
+ buffAll.buff1[strlen(buffAll.buff1)]=0; // BAD
+ buffAll.buff2[strlen(buffAll.buff2)]=0; // BAD
+ buffAll1->buff1[strlen(buffAll1->buff1)]=0; // BAD
+ buffAll1->buff2[strlen(buffAll1->buff2)]=0; // BAD
+ globalBuff1.buff1[strlen(globalBuff1.buff1)]=0; // BAD
+ globalBuff1.buff2[strlen(globalBuff1.buff2)]=0; // BAD
+ globalBuff2->buff1[strlen(globalBuff2->buff1)]=0; // BAD
+ globalBuff2->buff2[strlen(globalBuff2->buff2)]=0; // BAD
+}
+void noBadFunc0(){
+ unsigned char buff1[12],buff1_c[12];
+ struct buffers buffAll,buffAll_c;
+ struct buffers * buffAll1,*buffAll1_c;
+
+ buff1[strlen(buff1_c)]=0; // GOOD
+ buffAll.buff1[strlen(buffAll_c.buff1)]=0; // GOOD
+ buffAll.buff2[strlen(buffAll.buff1)]=0; // GOOD
+ buffAll1->buff1[strlen(buffAll1_c->buff1)]=0; // GOOD
+ buffAll1->buff2[strlen(buffAll1->buff1)]=0; // GOOD
+ globalBuff1.buff1[strlen(globalBuff1_c.buff1)]=0; // GOOD
+ globalBuff1.buff2[strlen(globalBuff1.buff1)]=0; // GOOD
+ globalBuff2->buff1[strlen(globalBuff2_c->buff1)]=0; // GOOD
+ globalBuff2->buff2[strlen(globalBuff2->buff1)]=0; // GOOD
+}
+void goodFunc0(){
+ unsigned char buffer[12];
+ int i;
+ for(i = 0; i < 6; i++)
+ buffer[i] = 'A';
+ buffer[i]=0;
+}
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/semmle/tests/MemoryUnsafeFunctionScan.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/semmle/tests/MemoryUnsafeFunctionScan.cpp
index f9ffc5e8687..09506cbc087 100644
--- a/cpp/ql/test/experimental/query-tests/Security/CWE/semmle/tests/MemoryUnsafeFunctionScan.cpp
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/semmle/tests/MemoryUnsafeFunctionScan.cpp
@@ -1,5 +1,10 @@
///// Library routines /////
+typedef unsigned long size_t;
+void *malloc(size_t size);
+
+size_t strlen(const char *s);
+
int scanf(const char *format, ...);
int sscanf(const char *str, const char *format, ...);
int fscanf(const char *str, const char *format, ...);
@@ -13,13 +18,23 @@ int main(int argc, char **argv)
char buf1[10];
scanf("%s", buf1);
- // GOOD, length is specified
- char buf2[10];
- sscanf(buf2, "%9s");
+ // GOOD, length is specified. The length should be one less than the size of the destination buffer, since the last character is the NULL terminator.
+ char buf2[20];
+ char buf3[10];
+ sscanf(buf2, "%9s", buf3);
// BAD, do not use scanf without specifying a length first
char file[10];
fscanf(file, "%s", buf2);
+ // GOOD, with 'sscanf' the input can be checked first and enough room allocated [FALSE POSITIVE]
+ if (argc >= 1)
+ {
+ char *src = argv[0];
+ char *dest = (char *)malloc(strlen(src) + 1);
+
+ sscanf(src, "%s", dest);
+ }
+
return 0;
}
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/semmle/tests/MemoryUnsafeFunctionScan.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/semmle/tests/MemoryUnsafeFunctionScan.expected
index 701fd4320b0..2739ed872e2 100644
--- a/cpp/ql/test/experimental/query-tests/Security/CWE/semmle/tests/MemoryUnsafeFunctionScan.expected
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/semmle/tests/MemoryUnsafeFunctionScan.expected
@@ -1,2 +1,3 @@
-| MemoryUnsafeFunctionScan.cpp:14:5:14:9 | call to scanf | Dangerous use of one of the scanf functions |
-| MemoryUnsafeFunctionScan.cpp:22:5:22:10 | call to fscanf | Dangerous use of one of the scanf functions |
+| MemoryUnsafeFunctionScan.cpp:19:5:19:9 | call to scanf | Dangerous use of one of the scanf functions |
+| MemoryUnsafeFunctionScan.cpp:28:5:28:10 | call to fscanf | Dangerous use of one of the scanf functions |
+| MemoryUnsafeFunctionScan.cpp:36:3:36:8 | call to sscanf | Dangerous use of one of the scanf functions |
diff --git a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/annotate_sinks_only/defaulttainttracking.cpp b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/annotate_sinks_only/defaulttainttracking.cpp
index b24efc1dc8e..020a2f90b9e 100644
--- a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/annotate_sinks_only/defaulttainttracking.cpp
+++ b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/annotate_sinks_only/defaulttainttracking.cpp
@@ -216,3 +216,46 @@ void test_pointers2()
sink(ptr4); // clean
sink(*ptr4); // $ MISSING: ast,ir
}
+
+// --- recv ---
+
+int recv(int s, char* buf, int len, int flags);
+
+void test_recv() {
+ char buffer[1024];
+ recv(0, buffer, sizeof(buffer), 0);
+ sink(buffer); // $ ast,ir
+ sink(*buffer); // $ ast,ir
+}
+
+// --- send and related functions ---
+
+int send(int, const void*, int, int);
+
+void test_send(char* buffer, int length) {
+ send(0, buffer, length, 0); // $ remote
+}
+
+struct iovec {
+ void *iov_base;
+ unsigned iov_len;
+};
+
+int readv(int, const struct iovec*, int);
+int writev(int, const struct iovec*, int);
+
+void sink(const iovec* iovs);
+void sink(iovec);
+
+int test_readv_and_writev(iovec* iovs) {
+ readv(0, iovs, 16);
+ sink(iovs); // $ast,ir
+ sink(iovs[0]); // $ast MISSING: ir
+ sink(*iovs); // $ast MISSING: ir
+
+ char* p = (char*)iovs[1].iov_base;
+ sink(p); // $ MISSING: ast,ir
+ sink(*p); // $ MISSING: ast,ir
+
+ writev(0, iovs, 16); // $ remote
+}
diff --git a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/annotate_sinks_only/remote-flow-sink.expected b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/annotate_sinks_only/remote-flow-sink.expected
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/annotate_sinks_only/remote-flow-sink.ql b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/annotate_sinks_only/remote-flow-sink.ql
new file mode 100644
index 00000000000..4fbbe8fa3bf
--- /dev/null
+++ b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/annotate_sinks_only/remote-flow-sink.ql
@@ -0,0 +1,20 @@
+/** This tests that we are able to detect remote flow sinks. */
+
+import cpp
+import TestUtilities.InlineExpectationsTest
+import semmle.code.cpp.security.FlowSources
+
+class RemoteFlowSinkTest extends InlineExpectationsTest {
+ RemoteFlowSinkTest() { this = "RemoteFlowSinkTest" }
+
+ override string getARelevantTag() { result = "remote" }
+
+ override predicate hasActualResult(Location location, string element, string tag, string value) {
+ tag = "remote" and
+ value = "" and
+ exists(RemoteFlowSink node |
+ location = node.getLocation() and
+ element = node.toString()
+ )
+ }
+}
diff --git a/cpp/ql/test/library-tests/dataflow/fields/conflated.cpp b/cpp/ql/test/library-tests/dataflow/fields/conflated.cpp
new file mode 100644
index 00000000000..aa600a99a5e
--- /dev/null
+++ b/cpp/ql/test/library-tests/dataflow/fields/conflated.cpp
@@ -0,0 +1,62 @@
+int user_input();
+void sink(int);
+
+struct A {
+ int* p;
+ int x;
+};
+
+void pointer_without_allocation(const A& ra) {
+ *ra.p = user_input();
+ sink(*ra.p); // $ MISSING: ast,ir
+}
+
+void argument_source(void*);
+void sink(void*);
+
+void pointer_without_allocation_2() {
+ char *raw;
+ argument_source(raw);
+ sink(raw); // $ ast MISSING: ir
+}
+
+A* makeA() {
+ return new A;
+}
+
+void no_InitializeDynamicAllocation_instruction() {
+ A* pa = makeA();
+ pa->x = user_input();
+ sink(pa->x); // $ ast MISSING: ir
+}
+
+void fresh_or_arg(A* arg, bool unknown) {
+ A* pa;
+ pa = unknown ? arg : new A;
+ pa->x = user_input();
+ sink(pa->x); // $ ast MISSING: ir
+}
+
+struct LinkedList {
+ LinkedList* next;
+ int y;
+
+ LinkedList() = default;
+ LinkedList(LinkedList* next) : next(next) {}
+};
+
+// Note: This example also suffers from #113: there is no ChiInstruction that merges the result of the
+// InitializeDynamicAllocation instruction into {AllAliasedMemory}. But even when that's fixed there's
+// still no dataflow because `ll->next->y = user_input()` writes to {AllAliasedMemory}.
+void too_many_indirections() {
+ LinkedList* ll = new LinkedList;
+ ll->next = new LinkedList;
+ ll->next->y = user_input();
+ sink(ll->next->y); // $ ast MISSING: ir
+}
+
+void too_many_indirections_2(LinkedList* next) {
+ LinkedList* ll = new LinkedList(next);
+ ll->next->y = user_input();
+ sink(ll->next->y); // $ ast MISSING: ir
+}
diff --git a/cpp/ql/test/library-tests/dataflow/fields/dataflow-consistency.expected b/cpp/ql/test/library-tests/dataflow/fields/dataflow-consistency.expected
index 49b12c14350..c6528723d22 100644
--- a/cpp/ql/test/library-tests/dataflow/fields/dataflow-consistency.expected
+++ b/cpp/ql/test/library-tests/dataflow/fields/dataflow-consistency.expected
@@ -121,6 +121,13 @@ postWithInFlow
| by_reference.cpp:127:30:127:38 | inner_ptr [inner post update] | PostUpdateNode should not be the target of local flow. |
| complex.cpp:11:22:11:23 | a_ [post update] | PostUpdateNode should not be the target of local flow. |
| complex.cpp:12:22:12:23 | b_ [post update] | PostUpdateNode should not be the target of local flow. |
+| conflated.cpp:10:3:10:7 | * ... [post update] | PostUpdateNode should not be the target of local flow. |
+| conflated.cpp:10:7:10:7 | p [inner post update] | PostUpdateNode should not be the target of local flow. |
+| conflated.cpp:29:7:29:7 | x [post update] | PostUpdateNode should not be the target of local flow. |
+| conflated.cpp:36:7:36:7 | x [post update] | PostUpdateNode should not be the target of local flow. |
+| conflated.cpp:53:7:53:10 | next [post update] | PostUpdateNode should not be the target of local flow. |
+| conflated.cpp:54:13:54:13 | y [post update] | PostUpdateNode should not be the target of local flow. |
+| conflated.cpp:60:13:60:13 | y [post update] | PostUpdateNode should not be the target of local flow. |
| constructors.cpp:20:24:20:25 | a_ [post update] | PostUpdateNode should not be the target of local flow. |
| constructors.cpp:21:24:21:25 | b_ [post update] | PostUpdateNode should not be the target of local flow. |
| qualifiers.cpp:9:36:9:36 | a [post update] | PostUpdateNode should not be the target of local flow. |
diff --git a/cpp/ql/test/library-tests/dataflow/fields/dataflow-ir-consistency.expected b/cpp/ql/test/library-tests/dataflow/fields/dataflow-ir-consistency.expected
index b09317c2c1b..63d3b2c0f48 100644
--- a/cpp/ql/test/library-tests/dataflow/fields/dataflow-ir-consistency.expected
+++ b/cpp/ql/test/library-tests/dataflow/fields/dataflow-ir-consistency.expected
@@ -1,6 +1,10 @@
uniqueEnclosingCallable
uniqueType
uniqueNodeLocation
+| E.cpp:15:31:15:33 | buf | Node should have one location but has 2. |
+| aliasing.cpp:2:11:2:13 | (unnamed parameter 0) | Node should have one location but has 2. |
+| conflated.cpp:2:11:2:13 | (unnamed parameter 0) | Node should have one location but has 2. |
+| conflated.cpp:14:22:14:25 | buf | Node should have one location but has 2. |
| file://:0:0:0:0 | (unnamed parameter 0) | Node should have one location but has 0. |
| file://:0:0:0:0 | (unnamed parameter 0) | Node should have one location but has 0. |
| file://:0:0:0:0 | (unnamed parameter 0) | Node should have one location but has 0. |
@@ -129,6 +133,8 @@ postWithInFlow
| complex.cpp:54:12:54:12 | Chi | PostUpdateNode should not be the target of local flow. |
| complex.cpp:55:12:55:12 | Chi | PostUpdateNode should not be the target of local flow. |
| complex.cpp:56:12:56:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| conflated.cpp:45:39:45:42 | Chi | PostUpdateNode should not be the target of local flow. |
+| conflated.cpp:53:3:53:27 | Chi | PostUpdateNode should not be the target of local flow. |
| constructors.cpp:20:24:20:29 | Chi | PostUpdateNode should not be the target of local flow. |
| constructors.cpp:21:24:21:29 | Chi | PostUpdateNode should not be the target of local flow. |
| constructors.cpp:23:28:23:28 | Chi | PostUpdateNode should not be the target of local flow. |
diff --git a/cpp/ql/test/library-tests/dataflow/fields/partial-definition-diff.expected b/cpp/ql/test/library-tests/dataflow/fields/partial-definition-diff.expected
index bba86417ca5..996836a65b4 100644
--- a/cpp/ql/test/library-tests/dataflow/fields/partial-definition-diff.expected
+++ b/cpp/ql/test/library-tests/dataflow/fields/partial-definition-diff.expected
@@ -309,6 +309,22 @@
| complex.cpp:62:7:62:8 | b2 | AST only |
| complex.cpp:65:7:65:8 | b3 | AST only |
| complex.cpp:68:7:68:8 | b4 | AST only |
+| conflated.cpp:10:3:10:7 | * ... | AST only |
+| conflated.cpp:10:4:10:5 | ra | AST only |
+| conflated.cpp:19:19:19:21 | raw | AST only |
+| conflated.cpp:20:8:20:10 | raw | AST only |
+| conflated.cpp:29:3:29:4 | pa | AST only |
+| conflated.cpp:29:7:29:7 | x | AST only |
+| conflated.cpp:36:3:36:4 | pa | AST only |
+| conflated.cpp:36:7:36:7 | x | AST only |
+| conflated.cpp:53:7:53:10 | next | AST only |
+| conflated.cpp:54:3:54:4 | ll | AST only |
+| conflated.cpp:54:7:54:10 | next | AST only |
+| conflated.cpp:54:13:54:13 | y | AST only |
+| conflated.cpp:59:35:59:38 | next | AST only |
+| conflated.cpp:60:3:60:4 | ll | AST only |
+| conflated.cpp:60:7:60:10 | next | AST only |
+| conflated.cpp:60:13:60:13 | y | AST only |
| constructors.cpp:20:24:20:25 | a_ | AST only |
| constructors.cpp:21:24:21:25 | b_ | AST only |
| constructors.cpp:28:10:28:10 | f | AST only |
diff --git a/cpp/ql/test/library-tests/dataflow/fields/partial-definition-ir.expected b/cpp/ql/test/library-tests/dataflow/fields/partial-definition-ir.expected
index 8d12b2a7e05..294c46a5694 100644
--- a/cpp/ql/test/library-tests/dataflow/fields/partial-definition-ir.expected
+++ b/cpp/ql/test/library-tests/dataflow/fields/partial-definition-ir.expected
@@ -57,6 +57,7 @@
| complex.cpp:54:6:54:10 | inner |
| complex.cpp:55:6:55:10 | inner |
| complex.cpp:56:6:56:10 | inner |
+| conflated.cpp:53:3:53:4 | ll |
| constructors.cpp:20:24:20:25 | this |
| constructors.cpp:21:24:21:25 | this |
| qualifiers.cpp:9:30:9:33 | this |
diff --git a/cpp/ql/test/library-tests/dataflow/fields/partial-definition.expected b/cpp/ql/test/library-tests/dataflow/fields/partial-definition.expected
index 6c58d51ff74..9b03e4f8039 100644
--- a/cpp/ql/test/library-tests/dataflow/fields/partial-definition.expected
+++ b/cpp/ql/test/library-tests/dataflow/fields/partial-definition.expected
@@ -366,6 +366,23 @@
| complex.cpp:62:7:62:8 | b2 |
| complex.cpp:65:7:65:8 | b3 |
| complex.cpp:68:7:68:8 | b4 |
+| conflated.cpp:10:3:10:7 | * ... |
+| conflated.cpp:10:4:10:5 | ra |
+| conflated.cpp:19:19:19:21 | raw |
+| conflated.cpp:20:8:20:10 | raw |
+| conflated.cpp:29:3:29:4 | pa |
+| conflated.cpp:29:7:29:7 | x |
+| conflated.cpp:36:3:36:4 | pa |
+| conflated.cpp:36:7:36:7 | x |
+| conflated.cpp:53:3:53:4 | ll |
+| conflated.cpp:53:7:53:10 | next |
+| conflated.cpp:54:3:54:4 | ll |
+| conflated.cpp:54:7:54:10 | next |
+| conflated.cpp:54:13:54:13 | y |
+| conflated.cpp:59:35:59:38 | next |
+| conflated.cpp:60:3:60:4 | ll |
+| conflated.cpp:60:7:60:10 | next |
+| conflated.cpp:60:13:60:13 | y |
| constructors.cpp:20:24:20:25 | a_ |
| constructors.cpp:20:24:20:25 | this |
| constructors.cpp:21:24:21:25 | b_ |
diff --git a/cpp/ql/test/library-tests/dataflow/fields/path-flow.expected b/cpp/ql/test/library-tests/dataflow/fields/path-flow.expected
index b932e0395f4..6604dde87d4 100644
--- a/cpp/ql/test/library-tests/dataflow/fields/path-flow.expected
+++ b/cpp/ql/test/library-tests/dataflow/fields/path-flow.expected
@@ -336,6 +336,27 @@ edges
| complex.cpp:62:7:62:8 | b2 [inner, f, b_] | complex.cpp:40:17:40:17 | b [inner, f, b_] |
| complex.cpp:65:7:65:8 | b3 [inner, f, a_] | complex.cpp:40:17:40:17 | b [inner, f, a_] |
| complex.cpp:65:7:65:8 | b3 [inner, f, b_] | complex.cpp:40:17:40:17 | b [inner, f, b_] |
+| conflated.cpp:19:19:19:21 | ref arg raw | conflated.cpp:20:8:20:10 | raw |
+| conflated.cpp:29:3:29:4 | pa [post update] [x] | conflated.cpp:30:8:30:9 | pa [x] |
+| conflated.cpp:29:3:29:22 | ... = ... | conflated.cpp:29:3:29:4 | pa [post update] [x] |
+| conflated.cpp:29:11:29:20 | call to user_input | conflated.cpp:29:3:29:22 | ... = ... |
+| conflated.cpp:30:8:30:9 | pa [x] | conflated.cpp:30:12:30:12 | x |
+| conflated.cpp:36:3:36:4 | pa [post update] [x] | conflated.cpp:37:8:37:9 | pa [x] |
+| conflated.cpp:36:3:36:22 | ... = ... | conflated.cpp:36:3:36:4 | pa [post update] [x] |
+| conflated.cpp:36:11:36:20 | call to user_input | conflated.cpp:36:3:36:22 | ... = ... |
+| conflated.cpp:37:8:37:9 | pa [x] | conflated.cpp:37:12:37:12 | x |
+| conflated.cpp:54:3:54:4 | ll [post update] [next, y] | conflated.cpp:55:8:55:9 | ll [next, y] |
+| conflated.cpp:54:3:54:28 | ... = ... | conflated.cpp:54:7:54:10 | next [post update] [y] |
+| conflated.cpp:54:7:54:10 | next [post update] [y] | conflated.cpp:54:3:54:4 | ll [post update] [next, y] |
+| conflated.cpp:54:17:54:26 | call to user_input | conflated.cpp:54:3:54:28 | ... = ... |
+| conflated.cpp:55:8:55:9 | ll [next, y] | conflated.cpp:55:12:55:15 | next [y] |
+| conflated.cpp:55:12:55:15 | next [y] | conflated.cpp:55:18:55:18 | y |
+| conflated.cpp:60:3:60:4 | ll [post update] [next, y] | conflated.cpp:61:8:61:9 | ll [next, y] |
+| conflated.cpp:60:3:60:28 | ... = ... | conflated.cpp:60:7:60:10 | next [post update] [y] |
+| conflated.cpp:60:7:60:10 | next [post update] [y] | conflated.cpp:60:3:60:4 | ll [post update] [next, y] |
+| conflated.cpp:60:17:60:26 | call to user_input | conflated.cpp:60:3:60:28 | ... = ... |
+| conflated.cpp:61:8:61:9 | ll [next, y] | conflated.cpp:61:12:61:15 | next [y] |
+| conflated.cpp:61:12:61:15 | next [y] | conflated.cpp:61:18:61:18 | y |
| constructors.cpp:26:15:26:15 | f [a_] | constructors.cpp:28:10:28:10 | f [a_] |
| constructors.cpp:26:15:26:15 | f [b_] | constructors.cpp:29:10:29:10 | f [b_] |
| constructors.cpp:28:10:28:10 | f [a_] | constructors.cpp:28:12:28:12 | call to a |
@@ -827,6 +848,32 @@ nodes
| complex.cpp:62:7:62:8 | b2 [inner, f, b_] | semmle.label | b2 [inner, f, b_] |
| complex.cpp:65:7:65:8 | b3 [inner, f, a_] | semmle.label | b3 [inner, f, a_] |
| complex.cpp:65:7:65:8 | b3 [inner, f, b_] | semmle.label | b3 [inner, f, b_] |
+| conflated.cpp:19:19:19:21 | ref arg raw | semmle.label | ref arg raw |
+| conflated.cpp:20:8:20:10 | raw | semmle.label | raw |
+| conflated.cpp:29:3:29:4 | pa [post update] [x] | semmle.label | pa [post update] [x] |
+| conflated.cpp:29:3:29:22 | ... = ... | semmle.label | ... = ... |
+| conflated.cpp:29:11:29:20 | call to user_input | semmle.label | call to user_input |
+| conflated.cpp:30:8:30:9 | pa [x] | semmle.label | pa [x] |
+| conflated.cpp:30:12:30:12 | x | semmle.label | x |
+| conflated.cpp:36:3:36:4 | pa [post update] [x] | semmle.label | pa [post update] [x] |
+| conflated.cpp:36:3:36:22 | ... = ... | semmle.label | ... = ... |
+| conflated.cpp:36:11:36:20 | call to user_input | semmle.label | call to user_input |
+| conflated.cpp:37:8:37:9 | pa [x] | semmle.label | pa [x] |
+| conflated.cpp:37:12:37:12 | x | semmle.label | x |
+| conflated.cpp:54:3:54:4 | ll [post update] [next, y] | semmle.label | ll [post update] [next, y] |
+| conflated.cpp:54:3:54:28 | ... = ... | semmle.label | ... = ... |
+| conflated.cpp:54:7:54:10 | next [post update] [y] | semmle.label | next [post update] [y] |
+| conflated.cpp:54:17:54:26 | call to user_input | semmle.label | call to user_input |
+| conflated.cpp:55:8:55:9 | ll [next, y] | semmle.label | ll [next, y] |
+| conflated.cpp:55:12:55:15 | next [y] | semmle.label | next [y] |
+| conflated.cpp:55:18:55:18 | y | semmle.label | y |
+| conflated.cpp:60:3:60:4 | ll [post update] [next, y] | semmle.label | ll [post update] [next, y] |
+| conflated.cpp:60:3:60:28 | ... = ... | semmle.label | ... = ... |
+| conflated.cpp:60:7:60:10 | next [post update] [y] | semmle.label | next [post update] [y] |
+| conflated.cpp:60:17:60:26 | call to user_input | semmle.label | call to user_input |
+| conflated.cpp:61:8:61:9 | ll [next, y] | semmle.label | ll [next, y] |
+| conflated.cpp:61:12:61:15 | next [y] | semmle.label | next [y] |
+| conflated.cpp:61:18:61:18 | y | semmle.label | y |
| constructors.cpp:26:15:26:15 | f [a_] | semmle.label | f [a_] |
| constructors.cpp:26:15:26:15 | f [b_] | semmle.label | f [b_] |
| constructors.cpp:28:10:28:10 | f [a_] | semmle.label | f [a_] |
@@ -1028,6 +1075,11 @@ nodes
| complex.cpp:42:18:42:18 | call to a | complex.cpp:55:19:55:28 | call to user_input | complex.cpp:42:18:42:18 | call to a | call to a flows from $@ | complex.cpp:55:19:55:28 | call to user_input | call to user_input |
| complex.cpp:43:18:43:18 | call to b | complex.cpp:54:19:54:28 | call to user_input | complex.cpp:43:18:43:18 | call to b | call to b flows from $@ | complex.cpp:54:19:54:28 | call to user_input | call to user_input |
| complex.cpp:43:18:43:18 | call to b | complex.cpp:56:19:56:28 | call to user_input | complex.cpp:43:18:43:18 | call to b | call to b flows from $@ | complex.cpp:56:19:56:28 | call to user_input | call to user_input |
+| conflated.cpp:20:8:20:10 | raw | conflated.cpp:19:19:19:21 | ref arg raw | conflated.cpp:20:8:20:10 | raw | raw flows from $@ | conflated.cpp:19:19:19:21 | ref arg raw | ref arg raw |
+| conflated.cpp:30:12:30:12 | x | conflated.cpp:29:11:29:20 | call to user_input | conflated.cpp:30:12:30:12 | x | x flows from $@ | conflated.cpp:29:11:29:20 | call to user_input | call to user_input |
+| conflated.cpp:37:12:37:12 | x | conflated.cpp:36:11:36:20 | call to user_input | conflated.cpp:37:12:37:12 | x | x flows from $@ | conflated.cpp:36:11:36:20 | call to user_input | call to user_input |
+| conflated.cpp:55:18:55:18 | y | conflated.cpp:54:17:54:26 | call to user_input | conflated.cpp:55:18:55:18 | y | y flows from $@ | conflated.cpp:54:17:54:26 | call to user_input | call to user_input |
+| conflated.cpp:61:18:61:18 | y | conflated.cpp:60:17:60:26 | call to user_input | conflated.cpp:61:18:61:18 | y | y flows from $@ | conflated.cpp:60:17:60:26 | call to user_input | call to user_input |
| constructors.cpp:28:12:28:12 | call to a | constructors.cpp:34:11:34:20 | call to user_input | constructors.cpp:28:12:28:12 | call to a | call to a flows from $@ | constructors.cpp:34:11:34:20 | call to user_input | call to user_input |
| constructors.cpp:28:12:28:12 | call to a | constructors.cpp:36:11:36:20 | call to user_input | constructors.cpp:28:12:28:12 | call to a | call to a flows from $@ | constructors.cpp:36:11:36:20 | call to user_input | call to user_input |
| constructors.cpp:29:12:29:12 | call to b | constructors.cpp:35:14:35:23 | call to user_input | constructors.cpp:29:12:29:12 | call to b | call to b flows from $@ | constructors.cpp:35:14:35:23 | call to user_input | call to user_input |
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/constructor_delegation.cpp b/cpp/ql/test/library-tests/dataflow/taint-tests/constructor_delegation.cpp
new file mode 100644
index 00000000000..d1acc67f81f
--- /dev/null
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/constructor_delegation.cpp
@@ -0,0 +1,47 @@
+
+int source();
+void sink(...);
+
+class MyValue
+{
+public:
+ MyValue(int _x) : x(_x) {}; // taint flows from parameter `_x` to member variable `x`
+ MyValue(int _x, bool ex) : MyValue(_x) {}; // taint flows from parameter `_x` to member variable `x`
+ MyValue(int _x, int _y) : MyValue(_x + _y) {}; // taint flows from parameters `_x` and `_y` to member variable `x`
+ MyValue(int _x, bool ex1, bool ex2) : MyValue(0) {}; // taint doesn't flow from parameter `_x`
+
+ int x;
+};
+
+class MyDerivedValue : public MyValue
+{
+public:
+ MyDerivedValue(bool ex, int _x) : MyValue(_x) {}; // taint flows from parameter `_x` to member variable `x`
+};
+
+void test_inits()
+{
+ MyValue v1(0);
+ MyValue v2(source());
+ MyValue v3(0, true);
+ MyValue v4(source(), true);
+ MyValue v5(0, 1);
+ MyValue v6(source(), 1);
+ MyValue v7(0, source());
+ MyValue v8(0, true, true);
+ MyValue v9(source(), true, true);
+ MyDerivedValue v10(true, 0);
+ MyDerivedValue v11(true, source());
+
+ sink(v1.x);
+ sink(v2.x); // $ ast,ir
+ sink(v3.x);
+ sink(v4.x); // $ ir MISSING: ast
+ sink(v5.x);
+ sink(v6.x); // $ ir MISSING: ast
+ sink(v7.x); // $ ir MISSING: ast
+ sink(v8.x);
+ sink(v9.x);
+ sink(v10.x);
+ sink(v11.x); // $ ir MISSING: ast
+}
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/format.cpp b/cpp/ql/test/library-tests/dataflow/taint-tests/format.cpp
index 77c42570721..c6b877e9ca8 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/format.cpp
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/format.cpp
@@ -123,7 +123,7 @@ void test1()
{
int i = 0;
sink(sscanf(string::source(), "%i", &i));
- sink(i); // $ MISSING: ast,ir
+ sink(i); // $ ast,ir
}
{
char buffer[256] = {0};
@@ -133,7 +133,7 @@ void test1()
{
char buffer[256] = {0};
sink(sscanf(string::source(), "%s", &buffer));
- sink(buffer); // $ MISSING: ast,ir
+ sink(buffer); // $ ast,ir
}
}
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
index 4407266a6e7..9f038101d67 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
@@ -135,6 +135,28 @@
| arrayassignment.cpp:145:12:145:12 | 5 | arrayassignment.cpp:145:7:145:13 | access to array | TAINT |
| arrayassignment.cpp:146:7:146:10 | arr3 | arrayassignment.cpp:146:7:146:13 | access to array | |
| arrayassignment.cpp:146:12:146:12 | 5 | arrayassignment.cpp:146:7:146:13 | access to array | TAINT |
+| constructor_delegation.cpp:8:2:8:8 | this | constructor_delegation.cpp:8:20:8:24 | constructor init of field x [pre-this] | |
+| constructor_delegation.cpp:8:14:8:15 | _x | constructor_delegation.cpp:8:22:8:23 | _x | |
+| constructor_delegation.cpp:8:22:8:23 | _x | constructor_delegation.cpp:8:20:8:24 | constructor init of field x | TAINT |
+| constructor_delegation.cpp:9:37:9:38 | _x | constructor_delegation.cpp:9:29:9:39 | call to MyValue | TAINT |
+| constructor_delegation.cpp:10:36:10:37 | _x | constructor_delegation.cpp:10:36:10:42 | ... + ... | TAINT |
+| constructor_delegation.cpp:10:36:10:42 | ... + ... | constructor_delegation.cpp:10:28:10:43 | call to MyValue | TAINT |
+| constructor_delegation.cpp:10:41:10:42 | _y | constructor_delegation.cpp:10:36:10:42 | ... + ... | TAINT |
+| constructor_delegation.cpp:11:48:11:48 | 0 | constructor_delegation.cpp:11:40:11:49 | call to MyValue | TAINT |
+| constructor_delegation.cpp:19:44:19:45 | _x | constructor_delegation.cpp:19:36:19:46 | call to MyValue | TAINT |
+| constructor_delegation.cpp:24:13:24:13 | 0 | constructor_delegation.cpp:24:13:24:14 | call to MyValue | TAINT |
+| constructor_delegation.cpp:24:13:24:14 | call to MyValue | constructor_delegation.cpp:36:7:36:8 | v1 | |
+| constructor_delegation.cpp:25:13:25:18 | call to source | constructor_delegation.cpp:25:13:25:21 | call to MyValue | TAINT |
+| constructor_delegation.cpp:25:13:25:21 | call to MyValue | constructor_delegation.cpp:37:7:37:8 | v2 | |
+| constructor_delegation.cpp:26:13:26:20 | call to MyValue | constructor_delegation.cpp:38:7:38:8 | v3 | |
+| constructor_delegation.cpp:27:13:27:27 | call to MyValue | constructor_delegation.cpp:39:7:39:8 | v4 | |
+| constructor_delegation.cpp:28:13:28:17 | call to MyValue | constructor_delegation.cpp:40:7:40:8 | v5 | |
+| constructor_delegation.cpp:29:13:29:24 | call to MyValue | constructor_delegation.cpp:41:7:41:8 | v6 | |
+| constructor_delegation.cpp:30:13:30:24 | call to MyValue | constructor_delegation.cpp:42:7:42:8 | v7 | |
+| constructor_delegation.cpp:31:13:31:26 | call to MyValue | constructor_delegation.cpp:43:7:43:8 | v8 | |
+| constructor_delegation.cpp:32:13:32:33 | call to MyValue | constructor_delegation.cpp:44:7:44:8 | v9 | |
+| constructor_delegation.cpp:33:21:33:28 | call to MyDerivedValue | constructor_delegation.cpp:45:7:45:9 | v10 | |
+| constructor_delegation.cpp:34:21:34:35 | call to MyDerivedValue | constructor_delegation.cpp:46:7:46:9 | v11 | |
| copyableclass.cpp:8:2:8:16 | this | copyableclass.cpp:8:28:8:32 | constructor init of field v [pre-this] | |
| copyableclass.cpp:8:22:8:23 | _v | copyableclass.cpp:8:30:8:31 | _v | |
| copyableclass.cpp:8:30:8:31 | _v | copyableclass.cpp:8:28:8:32 | constructor init of field v | TAINT |
@@ -356,23 +378,27 @@
| format.cpp:114:37:114:50 | call to source | format.cpp:114:18:114:23 | ref arg buffer | TAINT |
| format.cpp:119:10:119:11 | 0 | format.cpp:120:29:120:29 | i | |
| format.cpp:119:10:119:11 | 0 | format.cpp:121:8:121:8 | i | |
+| format.cpp:120:15:120:19 | 123 | format.cpp:120:28:120:29 | ref arg & ... | TAINT |
| format.cpp:120:28:120:29 | ref arg & ... | format.cpp:120:29:120:29 | i [inner post update] | |
| format.cpp:120:28:120:29 | ref arg & ... | format.cpp:121:8:121:8 | i | |
| format.cpp:120:29:120:29 | i | format.cpp:120:28:120:29 | & ... | |
| format.cpp:124:10:124:11 | 0 | format.cpp:125:40:125:40 | i | |
| format.cpp:124:10:124:11 | 0 | format.cpp:126:8:126:8 | i | |
+| format.cpp:125:15:125:28 | call to source | format.cpp:125:39:125:40 | ref arg & ... | TAINT |
| format.cpp:125:39:125:40 | ref arg & ... | format.cpp:125:40:125:40 | i [inner post update] | |
| format.cpp:125:39:125:40 | ref arg & ... | format.cpp:126:8:126:8 | i | |
| format.cpp:125:40:125:40 | i | format.cpp:125:39:125:40 | & ... | |
| format.cpp:129:21:129:24 | {...} | format.cpp:130:32:130:37 | buffer | |
| format.cpp:129:21:129:24 | {...} | format.cpp:131:8:131:13 | buffer | |
| format.cpp:129:23:129:23 | 0 | format.cpp:129:21:129:24 | {...} | TAINT |
+| format.cpp:130:15:130:22 | Hello. | format.cpp:130:31:130:37 | ref arg & ... | TAINT |
| format.cpp:130:31:130:37 | ref arg & ... | format.cpp:130:32:130:37 | buffer [inner post update] | |
| format.cpp:130:31:130:37 | ref arg & ... | format.cpp:131:8:131:13 | buffer | |
| format.cpp:130:32:130:37 | buffer | format.cpp:130:31:130:37 | & ... | |
| format.cpp:134:21:134:24 | {...} | format.cpp:135:40:135:45 | buffer | |
| format.cpp:134:21:134:24 | {...} | format.cpp:136:8:136:13 | buffer | |
| format.cpp:134:23:134:23 | 0 | format.cpp:134:21:134:24 | {...} | TAINT |
+| format.cpp:135:15:135:28 | call to source | format.cpp:135:39:135:45 | ref arg & ... | TAINT |
| format.cpp:135:39:135:45 | ref arg & ... | format.cpp:135:40:135:45 | buffer [inner post update] | |
| format.cpp:135:39:135:45 | ref arg & ... | format.cpp:136:8:136:13 | buffer | |
| format.cpp:135:40:135:45 | buffer | format.cpp:135:39:135:45 | & ... | |
@@ -5004,7 +5030,9 @@
| swap1.cpp:24:9:24:13 | this | swap1.cpp:24:31:24:34 | this | |
| swap1.cpp:24:23:24:26 | that | swap1.cpp:24:23:24:26 | that | |
| swap1.cpp:24:23:24:26 | that | swap1.cpp:24:36:24:39 | that | |
+| swap1.cpp:24:31:24:34 | this | swap1.cpp:24:36:24:39 | ref arg that | TAINT |
| swap1.cpp:24:36:24:39 | ref arg that | swap1.cpp:24:23:24:26 | that | |
+| swap1.cpp:24:36:24:39 | that | swap1.cpp:24:31:24:34 | ref arg this | TAINT |
| swap1.cpp:25:9:25:13 | this | swap1.cpp:25:36:25:52 | constructor init of field data1 [pre-this] | |
| swap1.cpp:25:28:25:31 | that | swap1.cpp:25:42:25:45 | that | |
| swap1.cpp:25:47:25:51 | data1 | swap1.cpp:25:36:25:52 | constructor init of field data1 | TAINT |
@@ -5014,28 +5042,36 @@
| swap1.cpp:29:23:29:27 | call to Class | swap1.cpp:30:18:30:20 | tmp | |
| swap1.cpp:29:24:29:27 | that | swap1.cpp:29:23:29:27 | call to Class | |
| swap1.cpp:30:13:30:16 | ref arg this | swap1.cpp:31:21:31:24 | this | |
+| swap1.cpp:30:13:30:16 | this | swap1.cpp:30:18:30:20 | ref arg tmp | TAINT |
| swap1.cpp:30:13:30:16 | this | swap1.cpp:31:21:31:24 | this | |
+| swap1.cpp:30:18:30:20 | tmp | swap1.cpp:30:13:30:16 | ref arg this | TAINT |
| swap1.cpp:31:21:31:24 | this | swap1.cpp:31:20:31:24 | * ... | TAINT |
| swap1.cpp:34:16:34:24 | this | swap1.cpp:36:13:36:16 | this | |
| swap1.cpp:34:34:34:37 | that | swap1.cpp:34:34:34:37 | that | |
| swap1.cpp:34:34:34:37 | that | swap1.cpp:36:18:36:21 | that | |
| swap1.cpp:36:13:36:16 | ref arg this | swap1.cpp:37:21:37:24 | this | |
+| swap1.cpp:36:13:36:16 | this | swap1.cpp:36:18:36:21 | ref arg that | TAINT |
| swap1.cpp:36:13:36:16 | this | swap1.cpp:37:21:37:24 | this | |
| swap1.cpp:36:18:36:21 | ref arg that | swap1.cpp:34:34:34:37 | that | |
+| swap1.cpp:36:18:36:21 | that | swap1.cpp:36:13:36:16 | ref arg this | TAINT |
| swap1.cpp:37:21:37:24 | this | swap1.cpp:37:20:37:24 | * ... | TAINT |
| swap1.cpp:40:16:40:26 | this | swap1.cpp:43:13:43:16 | this | |
| swap1.cpp:40:41:40:44 | that | swap1.cpp:42:24:42:27 | that | |
| swap1.cpp:42:23:42:27 | call to Class | swap1.cpp:43:18:43:20 | tmp | |
| swap1.cpp:42:24:42:27 | that | swap1.cpp:42:23:42:27 | call to Class | |
| swap1.cpp:43:13:43:16 | ref arg this | swap1.cpp:44:21:44:24 | this | |
+| swap1.cpp:43:13:43:16 | this | swap1.cpp:43:18:43:20 | ref arg tmp | TAINT |
| swap1.cpp:43:13:43:16 | this | swap1.cpp:44:21:44:24 | this | |
+| swap1.cpp:43:18:43:20 | tmp | swap1.cpp:43:13:43:16 | ref arg this | TAINT |
| swap1.cpp:44:21:44:24 | this | swap1.cpp:44:20:44:24 | * ... | TAINT |
| swap1.cpp:47:16:47:26 | this | swap1.cpp:49:13:49:16 | this | |
| swap1.cpp:47:36:47:39 | that | swap1.cpp:47:36:47:39 | that | |
| swap1.cpp:47:36:47:39 | that | swap1.cpp:49:18:49:21 | that | |
| swap1.cpp:49:13:49:16 | ref arg this | swap1.cpp:50:21:50:24 | this | |
+| swap1.cpp:49:13:49:16 | this | swap1.cpp:49:18:49:21 | ref arg that | TAINT |
| swap1.cpp:49:13:49:16 | this | swap1.cpp:50:21:50:24 | this | |
| swap1.cpp:49:18:49:21 | ref arg that | swap1.cpp:47:36:47:39 | that | |
+| swap1.cpp:49:18:49:21 | that | swap1.cpp:49:13:49:16 | ref arg this | TAINT |
| swap1.cpp:50:21:50:24 | this | swap1.cpp:50:20:50:24 | * ... | TAINT |
| swap1.cpp:53:14:53:17 | this | swap1.cpp:56:18:56:22 | this | |
| swap1.cpp:53:26:53:29 | that | swap1.cpp:53:26:53:29 | that | |
@@ -5049,7 +5085,9 @@
| swap1.cpp:61:32:61:32 | y | swap1.cpp:61:32:61:32 | y | |
| swap1.cpp:61:32:61:32 | y | swap1.cpp:63:16:63:16 | y | |
| swap1.cpp:63:9:63:9 | ref arg x | swap1.cpp:61:22:61:22 | x | |
+| swap1.cpp:63:9:63:9 | x | swap1.cpp:63:16:63:16 | ref arg y | TAINT |
| swap1.cpp:63:16:63:16 | ref arg y | swap1.cpp:61:32:61:32 | y | |
+| swap1.cpp:63:16:63:16 | y | swap1.cpp:63:9:63:9 | ref arg x | TAINT |
| swap1.cpp:69:23:69:23 | x | swap1.cpp:71:5:71:5 | x | |
| swap1.cpp:69:23:69:23 | x | swap1.cpp:73:10:73:10 | x | |
| swap1.cpp:69:23:69:23 | x | swap1.cpp:76:9:76:9 | x | |
@@ -5158,7 +5196,9 @@
| swap2.cpp:24:9:24:13 | this | swap2.cpp:24:31:24:34 | this | |
| swap2.cpp:24:23:24:26 | that | swap2.cpp:24:23:24:26 | that | |
| swap2.cpp:24:23:24:26 | that | swap2.cpp:24:36:24:39 | that | |
+| swap2.cpp:24:31:24:34 | this | swap2.cpp:24:36:24:39 | ref arg that | TAINT |
| swap2.cpp:24:36:24:39 | ref arg that | swap2.cpp:24:23:24:26 | that | |
+| swap2.cpp:24:36:24:39 | that | swap2.cpp:24:31:24:34 | ref arg this | TAINT |
| swap2.cpp:25:9:25:13 | this | swap2.cpp:25:36:25:52 | constructor init of field data1 [pre-this] | |
| swap2.cpp:25:28:25:31 | that | swap2.cpp:25:42:25:45 | that | |
| swap2.cpp:25:28:25:31 | that | swap2.cpp:25:61:25:64 | that | |
@@ -5173,28 +5213,36 @@
| swap2.cpp:29:23:29:27 | call to Class | swap2.cpp:30:18:30:20 | tmp | |
| swap2.cpp:29:24:29:27 | that | swap2.cpp:29:23:29:27 | call to Class | |
| swap2.cpp:30:13:30:16 | ref arg this | swap2.cpp:31:21:31:24 | this | |
+| swap2.cpp:30:13:30:16 | this | swap2.cpp:30:18:30:20 | ref arg tmp | TAINT |
| swap2.cpp:30:13:30:16 | this | swap2.cpp:31:21:31:24 | this | |
+| swap2.cpp:30:18:30:20 | tmp | swap2.cpp:30:13:30:16 | ref arg this | TAINT |
| swap2.cpp:31:21:31:24 | this | swap2.cpp:31:20:31:24 | * ... | TAINT |
| swap2.cpp:34:16:34:24 | this | swap2.cpp:36:13:36:16 | this | |
| swap2.cpp:34:34:34:37 | that | swap2.cpp:34:34:34:37 | that | |
| swap2.cpp:34:34:34:37 | that | swap2.cpp:36:18:36:21 | that | |
| swap2.cpp:36:13:36:16 | ref arg this | swap2.cpp:37:21:37:24 | this | |
+| swap2.cpp:36:13:36:16 | this | swap2.cpp:36:18:36:21 | ref arg that | TAINT |
| swap2.cpp:36:13:36:16 | this | swap2.cpp:37:21:37:24 | this | |
| swap2.cpp:36:18:36:21 | ref arg that | swap2.cpp:34:34:34:37 | that | |
+| swap2.cpp:36:18:36:21 | that | swap2.cpp:36:13:36:16 | ref arg this | TAINT |
| swap2.cpp:37:21:37:24 | this | swap2.cpp:37:20:37:24 | * ... | TAINT |
| swap2.cpp:40:16:40:26 | this | swap2.cpp:43:13:43:16 | this | |
| swap2.cpp:40:41:40:44 | that | swap2.cpp:42:24:42:27 | that | |
| swap2.cpp:42:23:42:27 | call to Class | swap2.cpp:43:18:43:20 | tmp | |
| swap2.cpp:42:24:42:27 | that | swap2.cpp:42:23:42:27 | call to Class | |
| swap2.cpp:43:13:43:16 | ref arg this | swap2.cpp:44:21:44:24 | this | |
+| swap2.cpp:43:13:43:16 | this | swap2.cpp:43:18:43:20 | ref arg tmp | TAINT |
| swap2.cpp:43:13:43:16 | this | swap2.cpp:44:21:44:24 | this | |
+| swap2.cpp:43:18:43:20 | tmp | swap2.cpp:43:13:43:16 | ref arg this | TAINT |
| swap2.cpp:44:21:44:24 | this | swap2.cpp:44:20:44:24 | * ... | TAINT |
| swap2.cpp:47:16:47:26 | this | swap2.cpp:49:13:49:16 | this | |
| swap2.cpp:47:36:47:39 | that | swap2.cpp:47:36:47:39 | that | |
| swap2.cpp:47:36:47:39 | that | swap2.cpp:49:18:49:21 | that | |
| swap2.cpp:49:13:49:16 | ref arg this | swap2.cpp:50:21:50:24 | this | |
+| swap2.cpp:49:13:49:16 | this | swap2.cpp:49:18:49:21 | ref arg that | TAINT |
| swap2.cpp:49:13:49:16 | this | swap2.cpp:50:21:50:24 | this | |
| swap2.cpp:49:18:49:21 | ref arg that | swap2.cpp:47:36:47:39 | that | |
+| swap2.cpp:49:18:49:21 | that | swap2.cpp:49:13:49:16 | ref arg this | TAINT |
| swap2.cpp:50:21:50:24 | this | swap2.cpp:50:20:50:24 | * ... | TAINT |
| swap2.cpp:53:14:53:17 | this | swap2.cpp:56:18:56:22 | this | |
| swap2.cpp:53:26:53:29 | that | swap2.cpp:53:26:53:29 | that | |
@@ -5216,7 +5264,9 @@
| swap2.cpp:61:32:61:32 | y | swap2.cpp:61:32:61:32 | y | |
| swap2.cpp:61:32:61:32 | y | swap2.cpp:63:16:63:16 | y | |
| swap2.cpp:63:9:63:9 | ref arg x | swap2.cpp:61:22:61:22 | x | |
+| swap2.cpp:63:9:63:9 | x | swap2.cpp:63:16:63:16 | ref arg y | TAINT |
| swap2.cpp:63:16:63:16 | ref arg y | swap2.cpp:61:32:61:32 | y | |
+| swap2.cpp:63:16:63:16 | y | swap2.cpp:63:9:63:9 | ref arg x | TAINT |
| swap2.cpp:69:23:69:23 | x | swap2.cpp:71:5:71:5 | x | |
| swap2.cpp:69:23:69:23 | x | swap2.cpp:73:10:73:10 | x | |
| swap2.cpp:69:23:69:23 | x | swap2.cpp:76:9:76:9 | x | |
@@ -5815,6 +5865,183 @@
| taint.cpp:483:18:483:19 | ref arg & ... | taint.cpp:483:19:483:19 | n [inner post update] | |
| taint.cpp:483:19:483:19 | n | taint.cpp:483:18:483:19 | & ... | |
| taint.cpp:483:28:483:34 | source1 | taint.cpp:483:11:483:15 | ref arg & ... | TAINT |
+| taint.cpp:492:24:492:29 | source | taint.cpp:494:27:494:32 | source | |
+| taint.cpp:493:22:493:29 | ,.-;:_ | taint.cpp:494:35:494:39 | delim | |
+| taint.cpp:493:22:493:29 | ,.-;:_ | taint.cpp:496:7:496:11 | delim | |
+| taint.cpp:494:20:494:25 | call to strtok | taint.cpp:495:7:495:15 | tokenized | |
+| taint.cpp:494:27:494:32 | source | taint.cpp:494:20:494:25 | call to strtok | TAINT |
+| taint.cpp:503:26:503:28 | ptr | taint.cpp:504:10:504:12 | ptr | |
+| taint.cpp:503:26:503:28 | ptr | taint.cpp:505:7:505:9 | ptr | |
+| taint.cpp:503:26:503:28 | ptr | taint.cpp:506:8:506:10 | ptr | |
+| taint.cpp:503:36:503:41 | source | taint.cpp:504:15:504:20 | source | |
+| taint.cpp:504:10:504:12 | ptr | taint.cpp:504:2:504:8 | call to _strset | |
+| taint.cpp:504:10:504:12 | ref arg ptr | taint.cpp:505:7:505:9 | ptr | |
+| taint.cpp:504:10:504:12 | ref arg ptr | taint.cpp:506:8:506:10 | ptr | |
+| taint.cpp:504:15:504:20 | source | taint.cpp:504:2:504:8 | call to _strset | TAINT |
+| taint.cpp:504:15:504:20 | source | taint.cpp:504:10:504:12 | ref arg ptr | |
+| taint.cpp:505:7:505:9 | ref arg ptr | taint.cpp:506:8:506:10 | ptr | |
+| taint.cpp:506:8:506:10 | ptr | taint.cpp:506:7:506:10 | * ... | TAINT |
+| taint.cpp:509:26:509:31 | source | taint.cpp:510:10:510:15 | source | |
+| taint.cpp:509:26:509:31 | source | taint.cpp:511:7:511:12 | source | |
+| taint.cpp:510:10:510:15 | ref arg source | taint.cpp:511:7:511:12 | source | |
+| taint.cpp:510:10:510:15 | source | taint.cpp:510:2:510:8 | call to _strset | |
+| taint.cpp:510:18:510:18 | 0 | taint.cpp:510:2:510:8 | call to _strset | TAINT |
+| taint.cpp:510:18:510:18 | 0 | taint.cpp:510:10:510:15 | ref arg source | |
+| taint.cpp:518:24:518:29 | source | taint.cpp:520:14:520:19 | source | |
+| taint.cpp:519:6:519:6 | x | taint.cpp:520:11:520:11 | x | |
+| taint.cpp:519:6:519:6 | x | taint.cpp:521:7:521:7 | x | |
+| taint.cpp:520:10:520:11 | & ... | taint.cpp:520:2:520:8 | call to mempcpy | |
+| taint.cpp:520:10:520:11 | ref arg & ... | taint.cpp:520:11:520:11 | x [inner post update] | |
+| taint.cpp:520:10:520:11 | ref arg & ... | taint.cpp:521:7:521:7 | x | |
+| taint.cpp:520:11:520:11 | x | taint.cpp:520:10:520:11 | & ... | |
+| taint.cpp:520:14:520:19 | source | taint.cpp:520:2:520:8 | call to mempcpy | TAINT |
+| taint.cpp:520:14:520:19 | source | taint.cpp:520:10:520:11 | ref arg & ... | TAINT |
+| taint.cpp:528:24:528:29 | source | taint.cpp:530:16:530:21 | source | |
+| taint.cpp:529:6:529:9 | dest | taint.cpp:530:10:530:13 | dest | |
+| taint.cpp:529:6:529:9 | dest | taint.cpp:530:35:530:38 | dest | |
+| taint.cpp:529:6:529:9 | dest | taint.cpp:531:7:531:10 | dest | |
+| taint.cpp:530:10:530:13 | dest | taint.cpp:530:2:530:8 | call to memccpy | |
+| taint.cpp:530:10:530:13 | ref arg dest | taint.cpp:531:7:531:10 | dest | |
+| taint.cpp:530:16:530:21 | source | taint.cpp:530:2:530:8 | call to memccpy | TAINT |
+| taint.cpp:530:16:530:21 | source | taint.cpp:530:10:530:13 | ref arg dest | TAINT |
+| taint.cpp:538:24:538:28 | dest1 | taint.cpp:539:9:539:13 | dest1 | |
+| taint.cpp:538:24:538:28 | dest1 | taint.cpp:540:7:540:11 | dest1 | |
+| taint.cpp:538:37:538:41 | dest2 | taint.cpp:542:9:542:13 | dest2 | |
+| taint.cpp:538:37:538:41 | dest2 | taint.cpp:543:7:543:11 | dest2 | |
+| taint.cpp:538:50:538:54 | clean | taint.cpp:542:16:542:20 | clean | |
+| taint.cpp:538:63:538:68 | source | taint.cpp:539:16:539:21 | source | |
+| taint.cpp:539:9:539:13 | dest1 | taint.cpp:539:2:539:7 | call to strcat | |
+| taint.cpp:539:9:539:13 | dest1 | taint.cpp:539:9:539:13 | ref arg dest1 | TAINT |
+| taint.cpp:539:9:539:13 | ref arg dest1 | taint.cpp:540:7:540:11 | dest1 | |
+| taint.cpp:539:16:539:21 | source | taint.cpp:539:9:539:13 | ref arg dest1 | TAINT |
+| taint.cpp:542:9:542:13 | dest2 | taint.cpp:542:2:542:7 | call to strcat | |
+| taint.cpp:542:9:542:13 | dest2 | taint.cpp:542:9:542:13 | ref arg dest2 | TAINT |
+| taint.cpp:542:9:542:13 | ref arg dest2 | taint.cpp:543:7:543:11 | dest2 | |
+| taint.cpp:542:16:542:20 | clean | taint.cpp:542:9:542:13 | ref arg dest2 | TAINT |
+| taint.cpp:550:37:550:41 | dest1 | taint.cpp:552:36:552:40 | dest1 | |
+| taint.cpp:550:37:550:41 | dest1 | taint.cpp:553:7:553:11 | dest1 | |
+| taint.cpp:550:37:550:41 | dest1 | taint.cpp:554:8:554:12 | dest1 | |
+| taint.cpp:550:65:550:67 | ptr | taint.cpp:552:43:552:45 | ptr | |
+| taint.cpp:550:65:550:67 | ptr | taint.cpp:558:43:558:45 | ptr | |
+| taint.cpp:550:85:550:89 | dest3 | taint.cpp:558:36:558:40 | dest3 | |
+| taint.cpp:550:85:550:89 | dest3 | taint.cpp:559:7:559:11 | dest3 | |
+| taint.cpp:550:85:550:89 | dest3 | taint.cpp:560:8:560:12 | dest3 | |
+| taint.cpp:551:32:551:36 | clean | taint.cpp:558:51:558:55 | clean | |
+| taint.cpp:551:49:551:54 | source | taint.cpp:552:51:552:56 | source | |
+| taint.cpp:551:61:551:61 | n | taint.cpp:552:48:552:48 | n | |
+| taint.cpp:551:61:551:61 | n | taint.cpp:558:48:558:48 | n | |
+| taint.cpp:552:25:552:34 | call to _mbsncat_l | taint.cpp:555:7:555:11 | dest2 | |
+| taint.cpp:552:25:552:34 | call to _mbsncat_l | taint.cpp:556:8:556:12 | dest2 | |
+| taint.cpp:552:36:552:40 | dest1 | taint.cpp:552:25:552:34 | call to _mbsncat_l | |
+| taint.cpp:552:36:552:40 | dest1 | taint.cpp:552:36:552:40 | ref arg dest1 | TAINT |
+| taint.cpp:552:36:552:40 | ref arg dest1 | taint.cpp:553:7:553:11 | dest1 | |
+| taint.cpp:552:36:552:40 | ref arg dest1 | taint.cpp:554:8:554:12 | dest1 | |
+| taint.cpp:552:43:552:45 | ptr | taint.cpp:552:36:552:40 | ref arg dest1 | TAINT |
+| taint.cpp:552:48:552:48 | n | taint.cpp:552:36:552:40 | ref arg dest1 | TAINT |
+| taint.cpp:552:51:552:56 | source | taint.cpp:552:36:552:40 | ref arg dest1 | TAINT |
+| taint.cpp:553:7:553:11 | ref arg dest1 | taint.cpp:554:8:554:12 | dest1 | |
+| taint.cpp:554:8:554:12 | dest1 | taint.cpp:554:7:554:12 | * ... | TAINT |
+| taint.cpp:555:7:555:11 | ref arg dest2 | taint.cpp:556:8:556:12 | dest2 | |
+| taint.cpp:556:8:556:12 | dest2 | taint.cpp:556:7:556:12 | * ... | TAINT |
+| taint.cpp:558:25:558:34 | call to _mbsncat_l | taint.cpp:561:7:561:11 | dest4 | |
+| taint.cpp:558:25:558:34 | call to _mbsncat_l | taint.cpp:562:8:562:12 | dest4 | |
+| taint.cpp:558:36:558:40 | dest3 | taint.cpp:558:25:558:34 | call to _mbsncat_l | |
+| taint.cpp:558:36:558:40 | dest3 | taint.cpp:558:36:558:40 | ref arg dest3 | TAINT |
+| taint.cpp:558:36:558:40 | ref arg dest3 | taint.cpp:559:7:559:11 | dest3 | |
+| taint.cpp:558:36:558:40 | ref arg dest3 | taint.cpp:560:8:560:12 | dest3 | |
+| taint.cpp:558:43:558:45 | ptr | taint.cpp:558:36:558:40 | ref arg dest3 | TAINT |
+| taint.cpp:558:48:558:48 | n | taint.cpp:558:36:558:40 | ref arg dest3 | TAINT |
+| taint.cpp:558:51:558:55 | clean | taint.cpp:558:36:558:40 | ref arg dest3 | TAINT |
+| taint.cpp:559:7:559:11 | ref arg dest3 | taint.cpp:560:8:560:12 | dest3 | |
+| taint.cpp:560:8:560:12 | dest3 | taint.cpp:560:7:560:12 | * ... | TAINT |
+| taint.cpp:561:7:561:11 | ref arg dest4 | taint.cpp:562:8:562:12 | dest4 | |
+| taint.cpp:562:8:562:12 | dest4 | taint.cpp:562:7:562:12 | * ... | TAINT |
+| taint.cpp:569:24:569:29 | source | taint.cpp:572:29:572:34 | source | |
+| taint.cpp:570:23:570:30 | ,.-;:_ | taint.cpp:572:37:572:41 | delim | |
+| taint.cpp:572:9:572:17 | tokenized | taint.cpp:572:9:572:42 | ... = ... | |
+| taint.cpp:572:21:572:26 | call to strsep | taint.cpp:572:9:572:42 | ... = ... | |
+| taint.cpp:572:21:572:26 | call to strsep | taint.cpp:573:10:573:18 | tokenized | |
+| taint.cpp:572:21:572:26 | call to strsep | taint.cpp:574:11:574:19 | tokenized | |
+| taint.cpp:572:28:572:34 | & ... | taint.cpp:572:21:572:26 | call to strsep | TAINT |
+| taint.cpp:572:28:572:34 | ref arg & ... | taint.cpp:572:29:572:34 | source | |
+| taint.cpp:572:28:572:34 | ref arg & ... | taint.cpp:572:29:572:34 | source [inner post update] | |
+| taint.cpp:572:29:572:34 | source | taint.cpp:572:21:572:26 | call to strsep | TAINT |
+| taint.cpp:572:29:572:34 | source | taint.cpp:572:28:572:34 | & ... | |
+| taint.cpp:572:37:572:41 | delim | taint.cpp:572:21:572:26 | call to strsep | TAINT |
+| taint.cpp:573:10:573:18 | ref arg tokenized | taint.cpp:574:11:574:19 | tokenized | |
+| taint.cpp:574:11:574:19 | tokenized | taint.cpp:574:10:574:19 | * ... | TAINT |
+| taint.cpp:584:25:584:30 | source | taint.cpp:585:18:585:23 | source | |
+| taint.cpp:584:39:584:43 | clean | taint.cpp:589:18:589:22 | clean | |
+| taint.cpp:584:82:584:87 | locale | taint.cpp:585:26:585:31 | locale | |
+| taint.cpp:584:82:584:87 | locale | taint.cpp:589:25:589:30 | locale | |
+| taint.cpp:585:10:585:16 | call to _strinc | taint.cpp:585:2:585:32 | ... = ... | |
+| taint.cpp:585:10:585:16 | call to _strinc | taint.cpp:586:7:586:11 | dest1 | |
+| taint.cpp:585:10:585:16 | call to _strinc | taint.cpp:587:8:587:12 | dest1 | |
+| taint.cpp:585:18:585:23 | source | taint.cpp:585:10:585:16 | call to _strinc | TAINT |
+| taint.cpp:585:26:585:31 | locale | taint.cpp:585:10:585:16 | call to _strinc | TAINT |
+| taint.cpp:585:26:585:31 | ref arg locale | taint.cpp:589:25:589:30 | locale | |
+| taint.cpp:586:7:586:11 | ref arg dest1 | taint.cpp:587:8:587:12 | dest1 | |
+| taint.cpp:587:8:587:12 | dest1 | taint.cpp:587:7:587:12 | * ... | TAINT |
+| taint.cpp:589:10:589:16 | call to _strinc | taint.cpp:589:2:589:31 | ... = ... | |
+| taint.cpp:589:10:589:16 | call to _strinc | taint.cpp:590:7:590:11 | dest2 | |
+| taint.cpp:589:10:589:16 | call to _strinc | taint.cpp:591:8:591:12 | dest2 | |
+| taint.cpp:589:18:589:22 | clean | taint.cpp:589:10:589:16 | call to _strinc | TAINT |
+| taint.cpp:589:25:589:30 | locale | taint.cpp:589:10:589:16 | call to _strinc | TAINT |
+| taint.cpp:590:7:590:11 | ref arg dest2 | taint.cpp:591:8:591:12 | dest2 | |
+| taint.cpp:591:8:591:12 | dest2 | taint.cpp:591:7:591:12 | * ... | TAINT |
+| taint.cpp:594:34:594:48 | source_unsigned | taint.cpp:595:26:595:40 | source_unsigned | |
+| taint.cpp:594:57:594:62 | source | taint.cpp:599:40:599:45 | source | |
+| taint.cpp:595:18:595:24 | call to _mbsinc | taint.cpp:595:2:595:41 | ... = ... | |
+| taint.cpp:595:18:595:24 | call to _mbsinc | taint.cpp:596:7:596:19 | dest_unsigned | |
+| taint.cpp:595:18:595:24 | call to _mbsinc | taint.cpp:597:8:597:20 | dest_unsigned | |
+| taint.cpp:595:26:595:40 | source_unsigned | taint.cpp:595:18:595:24 | call to _mbsinc | TAINT |
+| taint.cpp:596:7:596:19 | ref arg dest_unsigned | taint.cpp:597:8:597:20 | dest_unsigned | |
+| taint.cpp:597:8:597:20 | dest_unsigned | taint.cpp:597:7:597:20 | * ... | TAINT |
+| taint.cpp:599:16:599:22 | call to _mbsinc | taint.cpp:599:2:599:46 | ... = ... | |
+| taint.cpp:599:16:599:22 | call to _mbsinc | taint.cpp:600:7:600:10 | dest | |
+| taint.cpp:599:16:599:22 | call to _mbsinc | taint.cpp:601:8:601:11 | dest | |
+| taint.cpp:599:40:599:45 | source | taint.cpp:599:16:599:22 | call to _mbsinc | TAINT |
+| taint.cpp:600:7:600:10 | ref arg dest | taint.cpp:601:8:601:11 | dest | |
+| taint.cpp:601:8:601:11 | dest | taint.cpp:601:7:601:11 | * ... | TAINT |
+| taint.cpp:604:40:604:45 | source | taint.cpp:605:18:605:23 | source | |
+| taint.cpp:604:40:604:45 | source | taint.cpp:605:31:605:36 | source | |
+| taint.cpp:604:40:604:45 | source | taint.cpp:611:25:611:30 | source | |
+| taint.cpp:604:40:604:45 | source | taint.cpp:616:18:616:23 | source | |
+| taint.cpp:604:63:604:67 | clean | taint.cpp:611:18:611:22 | clean | |
+| taint.cpp:604:63:604:67 | clean | taint.cpp:616:26:616:30 | clean | |
+| taint.cpp:605:10:605:16 | call to _strdec | taint.cpp:605:2:605:37 | ... = ... | |
+| taint.cpp:605:10:605:16 | call to _strdec | taint.cpp:606:7:606:11 | dest1 | |
+| taint.cpp:605:10:605:16 | call to _strdec | taint.cpp:607:8:607:12 | dest1 | |
+| taint.cpp:605:18:605:23 | source | taint.cpp:605:18:605:28 | ... + ... | TAINT |
+| taint.cpp:605:18:605:28 | ... + ... | taint.cpp:605:10:605:16 | call to _strdec | TAINT |
+| taint.cpp:605:27:605:28 | 12 | taint.cpp:605:18:605:28 | ... + ... | TAINT |
+| taint.cpp:605:31:605:36 | source | taint.cpp:605:10:605:16 | call to _strdec | TAINT |
+| taint.cpp:606:7:606:11 | ref arg dest1 | taint.cpp:607:8:607:12 | dest1 | |
+| taint.cpp:607:8:607:12 | dest1 | taint.cpp:607:7:607:12 | * ... | TAINT |
+| taint.cpp:611:10:611:16 | call to _strdec | taint.cpp:611:2:611:31 | ... = ... | |
+| taint.cpp:611:10:611:16 | call to _strdec | taint.cpp:612:7:612:11 | dest2 | |
+| taint.cpp:611:10:611:16 | call to _strdec | taint.cpp:613:8:613:12 | dest2 | |
+| taint.cpp:611:18:611:22 | clean | taint.cpp:611:10:611:16 | call to _strdec | TAINT |
+| taint.cpp:611:25:611:30 | source | taint.cpp:611:10:611:16 | call to _strdec | TAINT |
+| taint.cpp:612:7:612:11 | ref arg dest2 | taint.cpp:613:8:613:12 | dest2 | |
+| taint.cpp:613:8:613:12 | dest2 | taint.cpp:613:7:613:12 | * ... | TAINT |
+| taint.cpp:616:10:616:16 | call to _strdec | taint.cpp:616:2:616:31 | ... = ... | |
+| taint.cpp:616:10:616:16 | call to _strdec | taint.cpp:617:7:617:11 | dest3 | |
+| taint.cpp:616:10:616:16 | call to _strdec | taint.cpp:618:8:618:12 | dest3 | |
+| taint.cpp:616:18:616:23 | source | taint.cpp:616:10:616:16 | call to _strdec | TAINT |
+| taint.cpp:616:26:616:30 | clean | taint.cpp:616:10:616:16 | call to _strdec | TAINT |
+| taint.cpp:617:7:617:11 | ref arg dest3 | taint.cpp:618:8:618:12 | dest3 | |
+| taint.cpp:618:8:618:12 | dest3 | taint.cpp:618:7:618:12 | * ... | TAINT |
+| taint.cpp:625:33:625:38 | source | taint.cpp:628:17:628:22 | source | |
+| taint.cpp:628:7:628:15 | call to _strnextc | taint.cpp:628:3:628:25 | ... = ... | |
+| taint.cpp:628:7:628:15 | call to _strnextc | taint.cpp:629:8:629:8 | c | |
+| taint.cpp:628:7:628:15 | call to _strnextc | taint.cpp:630:10:630:10 | c | |
+| taint.cpp:628:17:628:22 | source | taint.cpp:628:17:628:24 | ... ++ | |
+| taint.cpp:628:17:628:24 | ... ++ | taint.cpp:628:7:628:15 | call to _strnextc | TAINT |
+| taint.cpp:628:17:628:24 | ... ++ | taint.cpp:628:17:628:22 | source | TAINT |
+| taint.cpp:631:6:631:14 | call to _strnextc | taint.cpp:631:2:631:18 | ... = ... | |
+| taint.cpp:631:6:631:14 | call to _strnextc | taint.cpp:632:7:632:7 | c | |
+| taint.cpp:631:16:631:17 | | taint.cpp:631:6:631:14 | call to _strnextc | TAINT |
| vector.cpp:16:43:16:49 | source1 | vector.cpp:17:26:17:32 | source1 | |
| vector.cpp:16:43:16:49 | source1 | vector.cpp:31:38:31:44 | source1 | |
| vector.cpp:17:21:17:33 | call to vector | vector.cpp:19:14:19:14 | v | |
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/standalone_iterators.cpp b/cpp/ql/test/library-tests/dataflow/taint-tests/standalone_iterators.cpp
index 709ba8e7af4..3fb8c738305 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/standalone_iterators.cpp
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/standalone_iterators.cpp
@@ -38,13 +38,13 @@ public:
void test_typedefs(int_iterator_by_typedefs source1) {
sink(*source1); // $ ast,ir
- sink(*(source1++)); // $ ast MISSING: ir
+ sink(*(source1++)); // $ ast,ir
sink(*(++source1)); // $ ast MISSING: ir
}
void test_trait(int_iterator_by_trait source1) {
sink(*source1); // $ ast,ir
- sink(*(source1++)); // $ ast MISSING: ir
+ sink(*(source1++)); // $ ast,ir
sink(*(++source1)); // $ ast MISSING: ir
}
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/string.cpp b/cpp/ql/test/library-tests/dataflow/taint-tests/string.cpp
index 3741c54285b..9169113c1d9 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/string.cpp
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/string.cpp
@@ -415,10 +415,10 @@ void test_string_iterators() {
sink(*i9); // $ ast,ir
i10 = i2;
- sink(*(i10++)); // $ ast MISSING: ir
+ sink(*(i10++)); // $ ast,ir
sink(i10); // $ ast,ir
i11 = i2;
- sink(*(i11--)); // $ ast MISSING: ir
+ sink(*(i11--)); // $ ast,ir
sink(i11); // $ ast,ir
}
}
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/stringstream.cpp b/cpp/ql/test/library-tests/dataflow/taint-tests/stringstream.cpp
index 794df6704da..7b7712f0c01 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/stringstream.cpp
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/stringstream.cpp
@@ -75,7 +75,7 @@ void test_stringstream_int(int source)
sink(ss1 << 1234);
sink(ss2 << source); // $ ast MISSING: ir
sink(ss1 >> v1);
- sink(ss2 >> v2); // $ ast MISSING: ir
+ sink(ss2 >> v2); // $ ast,ir
sink(ss1);
sink(ss2); // $ ast,ir
@@ -143,27 +143,27 @@ void test_stringstream_in()
sink(ss2 << source()); // $ ast,ir
sink(ss1 >> s1);
- sink(ss2 >> s2); // $ ast MISSING: ir
- sink(ss2 >> s3 >> s4); // $ ast MISSING: ir
+ sink(ss2 >> s2); // $ ast,ir
+ sink(ss2 >> s3 >> s4); // $ ast,ir
sink(s1);
sink(s2); // $ ast,ir
sink(s3); // $ ast,ir
- sink(s4); // $ ast MISSING: ir
+ sink(s4); // $ ast,ir
sink(ss1 >> b1);
- sink(ss2 >> b2); // $ ast MISSING: ir
- sink(ss2 >> b3 >> b4); // $ ast MISSING: ir
+ sink(ss2 >> b2); // $ ast,ir
+ sink(ss2 >> b3 >> b4); // $ ast,ir
sink(b1);
sink(b2); // $ ast,ir
sink(b3); // $ ast,ir
- sink(b4); // $ ast MISSING: ir
+ sink(b4); // $ ast,ir
sink(ss1.read(b5, 100));
- sink(ss2.read(b6, 100)); // $ ast MISSING: ir
+ sink(ss2.read(b6, 100)); // $ ast,ir
sink(ss1.readsome(b7, 100));
sink(ss2.readsome(b8, 100)); // (returns a length, not significantly tainted)
sink(ss1.get(b9, 100));
- sink(ss2.get(b10, 100)); // $ ast MISSING: ir
+ sink(ss2.get(b10, 100)); // $ ast,ir
sink(b5);
sink(b6); // $ ast,ir
sink(b7);
@@ -176,7 +176,7 @@ void test_stringstream_in()
sink(c3 = ss1.peek());
sink(c4 = ss2.peek()); // $ ast,ir
sink(ss1.get(c5));
- sink(ss2.get(c6)); // $ ast MISSING: ir
+ sink(ss2.get(c6)); // $ ast,ir
sink(c1);
sink(c2); // $ ast,ir
sink(c3);
@@ -212,44 +212,44 @@ void test_getline()
std::string s1, s2, s3, s4, s5, s6, s7, s8;
sink(ss1.getline(b1, 1000));
- sink(ss2.getline(b2, 1000)); // $ ast MISSING: ir
- sink(ss2.getline(b3, 1000)); // $ ast MISSING: ir
+ sink(ss2.getline(b2, 1000)); // $ ast,ir
+ sink(ss2.getline(b3, 1000)); // $ ast,ir
sink(ss1.getline(b3, 1000));
sink(b1);
sink(b2); // $ ast,ir
sink(b3); // $ SPURIOUS: ast,ir
sink(ss1.getline(b4, 1000, ' '));
- sink(ss2.getline(b5, 1000, ' ')); // $ ast MISSING: ir
- sink(ss2.getline(b6, 1000, ' ')); // $ ast MISSING: ir
+ sink(ss2.getline(b5, 1000, ' ')); // $ ast,ir
+ sink(ss2.getline(b6, 1000, ' ')); // $ ast,ir
sink(ss1.getline(b6, 1000, ' '));
sink(b4);
sink(b5); // $ ast,ir
sink(b6); // $ SPURIOUS: ast,ir
- sink(ss2.getline(b7, 1000).getline(b8, 1000)); // $ ast MISSING: ir
+ sink(ss2.getline(b7, 1000).getline(b8, 1000)); // $ ast,ir
sink(b7); // $ ast,ir
sink(b8); // $ ast MISSING: ir
sink(getline(ss1, s1));
- sink(getline(ss2, s2)); // $ ast MISSING: ir
- sink(getline(ss2, s3)); // $ ast MISSING: ir
+ sink(getline(ss2, s2)); // $ ast,ir
+ sink(getline(ss2, s3)); // $ ast,ir
sink(getline(ss1, s3));
sink(s1);
sink(s2); // $ ast,ir
sink(s3); // $ SPURIOUS: ast,ir
sink(getline(ss1, s4, ' '));
- sink(getline(ss2, s5, ' ')); // $ ast MISSING: ir
- sink(getline(ss2, s6, ' ')); // $ ast MISSING: ir
+ sink(getline(ss2, s5, ' ')); // $ ast,ir
+ sink(getline(ss2, s6, ' ')); // $ ast,ir
sink(getline(ss1, s6, ' '));
sink(s4);
sink(s5); // $ ast,ir
sink(s6); // $ SPURIOUS: ast,ir
- sink(getline(getline(ss2, s7), s8)); // $ ast MISSING: ir
+ sink(getline(getline(ss2, s7), s8)); // $ ast,ir
sink(s7); // $ ast,ir
- sink(s8); // $ ast MISSING: ir
+ sink(s8); // $ ast,ir
}
void test_chaining()
@@ -259,7 +259,7 @@ void test_chaining()
char b1[1000] = {0};
char b2[1000] = {0};
- sink(ss1.get(b1, 100).unget().get(b2, 100)); // $ ast MISSING: ir
+ sink(ss1.get(b1, 100).unget().get(b2, 100)); // $ ast,ir
sink(b1); // $ ast,ir
sink(b2); // $ ast MISSING: ir
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/taint.cpp b/cpp/ql/test/library-tests/dataflow/taint-tests/taint.cpp
index 5ccedc7fc1c..44d84c0c0b0 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/taint.cpp
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/taint.cpp
@@ -484,3 +484,150 @@ void test_getdelim(FILE* source1) {
sink(line); // $ ir,ast
}
+
+// --- strtok ---
+
+char *strtok(char *str, const char *delim);
+
+void test_strtok(char *source) {
+ const char* delim = ",.-;:_";
+ char* tokenized = strtok(source, delim);
+ sink(tokenized); // $ ast,ir
+ sink(delim);
+}
+
+// --- strset ---
+
+char *_strset(char *str, int c);
+
+void test_strset_1(char* ptr, char source) {
+ _strset(ptr, source);
+ sink(ptr); // $ SPURIOUS: ast,ir
+ sink(*ptr); // $ ast,ir
+}
+
+void test_strset_2(char* source) {
+ _strset(source, 0);
+ sink(source); // $ ast,ir
+}
+
+// --- mempcpy ---
+
+void *mempcpy(void *dest, const void *src, size_t n);
+
+void test_mempcpy(int *source) {
+ int x;
+ mempcpy(&x, source, sizeof(int));
+ sink(x); // $ ast=518:24 MISSING: ir SPURIOUS: ast=519:6
+}
+
+// --- memccpy ---
+
+void *memccpy(void *dest, const void *src, int c, size_t n);
+
+void test_memccpy(int *source) {
+ int dest[16];
+ memccpy(dest, source, 42, sizeof(dest));
+ sink(dest); // $ ast=528:24 MISSING: ir SPURIOUS: ast=529:6
+}
+
+// --- strcat and related functions ---
+
+char* strcat (char*, const char*);
+
+void test_strcat(char* dest1, char* dest2, char* clean, char* source) {
+ strcat(dest1, source);
+ sink(dest1); // $ ast,ir
+
+ strcat(dest2, clean);
+ sink(dest2);
+}
+
+typedef void* _locale_t;
+
+unsigned char *_mbsncat_l(unsigned char *, const unsigned char *, int, _locale_t);
+
+void test__mbsncat_l(unsigned char* dest1, unsigned const char* ptr, unsigned char* dest3,
+ _locale_t clean, _locale_t source, int n) {
+ unsigned char* dest2 = _mbsncat_l(dest1, ptr, n, source);
+ sink(dest1); // $ SPURIOUS: ast,ir
+ sink(*dest1); // $ ast,ir
+ sink(dest2); // $ SPURIOUS: ir
+ sink(*dest2); // $ ir
+
+ unsigned char* dest4 = _mbsncat_l(dest3, ptr, n, clean);
+ sink(dest3);
+ sink(*dest3);
+ sink(dest4);
+ sink(*dest4);
+}
+
+// --- strsep ---
+
+char *strsep(char**, const char *);
+
+void test_strsep(char *source) {
+ const char* delim = ",.-;:_";
+ char* tokenized;
+ while(tokenized = strsep(&source, delim)) {
+ sink(tokenized); // $ ast,ir
+ sink(*tokenized); // $ ast,ir
+ }
+}
+
+// --- _strinc and related functions ---
+
+char* _strinc(const char*, _locale_t);
+unsigned char* _mbsinc(const unsigned char*);
+unsigned char *_strdec(const unsigned char*, const unsigned char*);
+
+void test__strinc(char* source, char* clean, char* dest1, char* dest2, _locale_t locale) {
+ dest1 = _strinc(source, locale);
+ sink(dest1); // $ ast,ir
+ sink(*dest1); // $ ast,ir
+
+ dest2 = _strinc(clean, locale);
+ sink(dest2);
+ sink(*dest2);
+}
+
+void test__mbsinc(unsigned char* source_unsigned, char* source, unsigned char* dest_unsigned, char* dest) {
+ dest_unsigned = _mbsinc(source_unsigned);
+ sink(dest_unsigned); // $ ast,ir
+ sink(*dest_unsigned); // $ ast,ir
+
+ dest = (char*)_mbsinc((unsigned char*)source);
+ sink(dest); // $ ast,ir
+ sink(*dest); // $ ast,ir
+}
+
+void test__strdec(const unsigned char* source, unsigned char* clean, unsigned char* dest1, unsigned char* dest2, unsigned char* dest3) {
+ dest1 = _strdec(source + 12, source);
+ sink(dest1); // $ ast,ir
+ sink(*dest1); // $ ast,ir
+
+ // If `clean` does not precede `source` this technically breaks the precondition of _strdec.
+ // We would still like to have taint, though.
+ dest2 = _strdec(clean, source);
+ sink(dest2); // $ ast,ir
+ sink(*dest2); // $ ast,ir
+
+ // Also breaks the precondition on _strdec.
+ dest3 = _strdec(source, clean);
+ sink(dest3); // $ ast,ir
+ sink(*dest3); // $ ast,ir
+}
+
+// --- strnextc ---
+
+unsigned int _strnextc(const char*);
+
+void test__strnextc(const char* source) {
+ unsigned c = 0;
+ do {
+ c = _strnextc(source++);
+ sink(c); // $ ast,ir
+ } while(c != '\0');
+ c = _strnextc("");
+ sink(c);
+}
\ No newline at end of file
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/vector.cpp b/cpp/ql/test/library-tests/dataflow/taint-tests/vector.cpp
index 7e7fc8176ce..ec354da548c 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/vector.cpp
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/vector.cpp
@@ -491,8 +491,8 @@ void test_vector_emplace() {
std::vector v1(10), v2(10);
v1.emplace_back(source());
- sink(v1); // $ ast MISSING: ir
+ sink(v1); // $ ast,ir
v2.emplace(v2.begin(), source());
- sink(v2); // $ ast MISSING: ir
+ sink(v2); // $ ast,ir
}
diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected
index f241c851109..c4019a38251 100644
--- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected
+++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected
@@ -889,10 +889,8 @@ ssa.cpp:
# 207| m207_4(unknown) = Chi : total:m207_2, partial:m207_3
# 207| r207_5(glval) = VariableAddress[x] :
# 207| m207_6(int) = InitializeParameter[x] : &:r207_5
-# 207| m207_7(unknown) = Chi : total:m207_4, partial:m207_6
# 208| r208_1(glval) = VariableAddress[y] :
# 208| m208_2(int) = Uninitialized[y] : &:r208_1
-# 208| m208_3(unknown) = Chi : total:m207_7, partial:m208_2
# 209| r209_1(glval) = FunctionAddress[memcpy] :
# 209| r209_2(glval) = VariableAddress[y] :
# 209| r209_3(int *) = CopyValue : r209_2
@@ -904,15 +902,15 @@ ssa.cpp:
# 209| r209_9(void *) = Call[memcpy] : func:r209_1, 0:r209_4, 1:r209_7, 2:r209_8
# 209| v209_10(void) = ^SizedBufferReadSideEffect[1] : &:r209_7, r209_8, ~m207_6
# 209| m209_11(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r209_4, r209_8
-# 209| m209_12(unknown) = Chi : total:m208_3, partial:m209_11
+# 209| m209_12(int) = Chi : total:m208_2, partial:m209_11
# 210| r210_1(glval) = VariableAddress[#return] :
# 210| r210_2(glval) = VariableAddress[y] :
-# 210| r210_3(int) = Load[y] : &:r210_2, ~m209_12
+# 210| r210_3(int) = Load[y] : &:r210_2, m209_12
# 210| m210_4(int) = Store[#return] : &:r210_1, r210_3
-# 207| r207_8(glval) = VariableAddress[#return] :
-# 207| v207_9(void) = ReturnValue : &:r207_8, m210_4
-# 207| v207_10(void) = AliasedUse : m207_3
-# 207| v207_11(void) = ExitFunction :
+# 207| r207_7(glval) = VariableAddress[#return] :
+# 207| v207_8(void) = ReturnValue : &:r207_7, m210_4
+# 207| v207_9(void) = AliasedUse : m207_3
+# 207| v207_10(void) = ExitFunction :
# 213| void InitArray()
# 213| Block 0
@@ -1104,51 +1102,49 @@ ssa.cpp:
# 247| m247_6(char *) = InitializeParameter[src] : &:r247_5
# 247| r247_7(char *) = Load[src] : &:r247_5, m247_6
# 247| m247_8(unknown) = InitializeIndirection[src] : &:r247_7
-# 247| m247_9(unknown) = Chi : total:m247_4, partial:m247_8
-# 247| r247_10(glval) = VariableAddress[size] :
-# 247| m247_11(int) = InitializeParameter[size] : &:r247_10
+# 247| r247_9(glval) = VariableAddress[size] :
+# 247| m247_10(int) = InitializeParameter[size] : &:r247_9
# 248| r248_1(glval) = VariableAddress[dst] :
# 248| r248_2(glval) = FunctionAddress[operator new[]] :
# 248| r248_3(glval) = VariableAddress[size] :
-# 248| r248_4(int) = Load[size] : &:r248_3, m247_11
+# 248| r248_4(int) = Load[size] : &:r248_3, m247_10
# 248| r248_5(unsigned long) = Convert : r248_4
# 248| r248_6(unsigned long) = Constant[1] :
# 248| r248_7(unsigned long) = Mul : r248_5, r248_6
# 248| r248_8(void *) = Call[operator new[]] : func:r248_2, 0:r248_7
-# 248| m248_9(unknown) = ^CallSideEffect : ~m247_9
-# 248| m248_10(unknown) = Chi : total:m247_9, partial:m248_9
+# 248| m248_9(unknown) = ^CallSideEffect : ~m247_4
+# 248| m248_10(unknown) = Chi : total:m247_4, partial:m248_9
# 248| m248_11(unknown) = ^InitializeDynamicAllocation : &:r248_8
-# 248| m248_12(unknown) = Chi : total:m248_10, partial:m248_11
-# 248| r248_13(char *) = Convert : r248_8
-# 248| m248_14(char *) = Store[dst] : &:r248_1, r248_13
+# 248| r248_12(char *) = Convert : r248_8
+# 248| m248_13(char *) = Store[dst] : &:r248_1, r248_12
# 249| r249_1(char) = Constant[97] :
# 249| r249_2(glval) = VariableAddress[src] :
# 249| r249_3(char *) = Load[src] : &:r249_2, m247_6
# 249| r249_4(glval) = CopyValue : r249_3
# 249| m249_5(char) = Store[?] : &:r249_4, r249_1
-# 249| m249_6(unknown) = Chi : total:m248_12, partial:m249_5
+# 249| m249_6(unknown) = Chi : total:m247_8, partial:m249_5
# 250| r250_1(glval) = FunctionAddress[memcpy] :
# 250| r250_2(glval) = VariableAddress[dst] :
-# 250| r250_3(char *) = Load[dst] : &:r250_2, m248_14
+# 250| r250_3(char *) = Load[dst] : &:r250_2, m248_13
# 250| r250_4(void *) = Convert : r250_3
# 250| r250_5(glval) = VariableAddress[src] :
# 250| r250_6(char *) = Load[src] : &:r250_5, m247_6
# 250| r250_7(void *) = Convert : r250_6
# 250| r250_8(glval) = VariableAddress[size] :
-# 250| r250_9(int) = Load[size] : &:r250_8, m247_11
+# 250| r250_9(int) = Load[size] : &:r250_8, m247_10
# 250| r250_10(void *) = Call[memcpy] : func:r250_1, 0:r250_4, 1:r250_7, 2:r250_9
# 250| v250_11(void) = ^SizedBufferReadSideEffect[1] : &:r250_7, r250_9, ~m249_6
# 250| m250_12(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r250_4, r250_9
-# 250| m250_13(unknown) = Chi : total:m249_6, partial:m250_12
+# 250| m250_13(unknown) = Chi : total:m248_11, partial:m250_12
# 251| r251_1(glval) = VariableAddress[#return] :
# 251| r251_2(glval) = VariableAddress[dst] :
-# 251| r251_3(char *) = Load[dst] : &:r251_2, m248_14
+# 251| r251_3(char *) = Load[dst] : &:r251_2, m248_13
# 251| m251_4(char *) = Store[#return] : &:r251_1, r251_3
-# 247| v247_12(void) = ReturnIndirection[src] : &:r247_7, ~m250_13
-# 247| r247_13(glval) = VariableAddress[#return] :
-# 247| v247_14(void) = ReturnValue : &:r247_13, m251_4
-# 247| v247_15(void) = AliasedUse : ~m250_13
-# 247| v247_16(void) = ExitFunction :
+# 247| v247_11(void) = ReturnIndirection[src] : &:r247_7, m249_6
+# 247| r247_12(glval) = VariableAddress[#return] :
+# 247| v247_13(void) = ReturnValue : &:r247_12, m251_4
+# 247| v247_14(void) = AliasedUse : ~m248_10
+# 247| v247_15(void) = ExitFunction :
# 254| char StringLiteralAliasing2(bool)
# 254| Block 0
@@ -1206,39 +1202,37 @@ ssa.cpp:
# 268| m268_6(void *) = InitializeParameter[s] : &:r268_5
# 268| r268_7(void *) = Load[s] : &:r268_5, m268_6
# 268| m268_8(unknown) = InitializeIndirection[s] : &:r268_7
-# 268| m268_9(unknown) = Chi : total:m268_4, partial:m268_8
-# 268| r268_10(glval) = VariableAddress[size] :
-# 268| m268_11(int) = InitializeParameter[size] : &:r268_10
+# 268| r268_9(glval) = VariableAddress[size] :
+# 268| m268_10(int) = InitializeParameter[size] : &:r268_9
# 269| r269_1(glval) = VariableAddress[buf] :
# 269| r269_2(glval) = FunctionAddress[malloc] :
# 269| r269_3(glval) = VariableAddress[size] :
-# 269| r269_4(int) = Load[size] : &:r269_3, m268_11
+# 269| r269_4(int) = Load[size] : &:r269_3, m268_10
# 269| r269_5(void *) = Call[malloc] : func:r269_2, 0:r269_4
-# 269| m269_6(unknown) = ^CallSideEffect : ~m268_9
-# 269| m269_7(unknown) = Chi : total:m268_9, partial:m269_6
+# 269| m269_6(unknown) = ^CallSideEffect : ~m268_4
+# 269| m269_7(unknown) = Chi : total:m268_4, partial:m269_6
# 269| m269_8(unknown) = ^InitializeDynamicAllocation : &:r269_5
-# 269| m269_9(unknown) = Chi : total:m269_7, partial:m269_8
-# 269| m269_10(void *) = Store[buf] : &:r269_1, r269_5
+# 269| m269_9(void *) = Store[buf] : &:r269_1, r269_5
# 270| r270_1(glval) = FunctionAddress[memcpy] :
# 270| r270_2(glval) = VariableAddress[buf] :
-# 270| r270_3(void *) = Load[buf] : &:r270_2, m269_10
+# 270| r270_3(void *) = Load[buf] : &:r270_2, m269_9
# 270| r270_4(glval) = VariableAddress[s] :
# 270| r270_5(void *) = Load[s] : &:r270_4, m268_6
# 270| r270_6(glval) = VariableAddress[size] :
-# 270| r270_7(int) = Load[size] : &:r270_6, m268_11
+# 270| r270_7(int) = Load[size] : &:r270_6, m268_10
# 270| r270_8(void *) = Call[memcpy] : func:r270_1, 0:r270_3, 1:r270_5, 2:r270_7
-# 270| v270_9(void) = ^SizedBufferReadSideEffect[1] : &:r270_5, r270_7, ~m269_7
+# 270| v270_9(void) = ^SizedBufferReadSideEffect[1] : &:r270_5, r270_7, ~m268_8
# 270| m270_10(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r270_3, r270_7
-# 270| m270_11(unknown) = Chi : total:m269_9, partial:m270_10
+# 270| m270_11(unknown) = Chi : total:m269_8, partial:m270_10
# 271| r271_1(glval) = VariableAddress[#return] :
# 271| r271_2(glval) = VariableAddress[buf] :
-# 271| r271_3(void *) = Load[buf] : &:r271_2, m269_10
+# 271| r271_3(void *) = Load[buf] : &:r271_2, m269_9
# 271| m271_4(void *) = Store[#return] : &:r271_1, r271_3
-# 268| v268_12(void) = ReturnIndirection[s] : &:r268_7, ~m270_11
-# 268| r268_13(glval) = VariableAddress[#return] :
-# 268| v268_14(void) = ReturnValue : &:r268_13, m271_4
-# 268| v268_15(void) = AliasedUse : ~m270_11
-# 268| v268_16(void) = ExitFunction :
+# 268| v268_11(void) = ReturnIndirection[s] : &:r268_7, m268_8
+# 268| r268_12(glval) = VariableAddress[#return] :
+# 268| v268_13(void) = ReturnValue : &:r268_12, m271_4
+# 268| v268_14(void) = AliasedUse : ~m269_7
+# 268| v268_15(void) = ExitFunction :
# 275| void EscapedButNotConflated(bool, Point, int)
# 275| Block 0
diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_ir_gvn.expected b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_ir_gvn.expected
new file mode 100644
index 00000000000..2e8aecd11a6
--- /dev/null
+++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_ir_gvn.expected
@@ -0,0 +1,46 @@
+| test.cpp:5:3:5:3 | GVN | 5:c3-c3 6:c3-c3 |
+| test.cpp:5:7:5:8 | GVN | 5:c7-c8 6:c7-c8 |
+| test.cpp:5:7:5:13 | GVN | 5:c7-c13 6:c7-c13 7:c7-c7 |
+| test.cpp:5:12:5:13 | GVN | 5:c12-c13 6:c12-c13 |
+| test.cpp:16:3:16:3 | GVN | 16:c3-c3 17:c3-c3 |
+| test.cpp:16:7:16:8 | GVN | 16:c7-c8 17:c7-c8 |
+| test.cpp:16:7:16:13 | GVN | 16:c7-c13 17:c7-c13 |
+| test.cpp:16:7:16:24 | GVN | 16:c7-c24 17:c7-c24 18:c7-c7 |
+| test.cpp:16:12:16:13 | GVN | 16:c12-c13 17:c12-c13 |
+| test.cpp:16:17:16:24 | GVN | 16:c17-c24 17:c17-c24 |
+| test.cpp:29:3:29:3 | GVN | 29:c3-c3 31:c3-c3 |
+| test.cpp:29:7:29:8 | GVN | 29:c7-c8 31:c7-c8 |
+| test.cpp:29:7:29:13 | GVN | 29:c7-c13 31:c7-c13 |
+| test.cpp:29:12:29:13 | GVN | 29:c12-c13 31:c12-c13 |
+| test.cpp:31:7:31:24 | GVN | 31:c7-c24 32:c7-c7 |
+| test.cpp:43:3:43:3 | GVN | 43:c3-c3 45:c3-c3 |
+| test.cpp:43:7:43:8 | GVN | 43:c7-c8 45:c7-c8 |
+| test.cpp:43:7:43:13 | GVN | 43:c7-c13 45:c7-c13 |
+| test.cpp:43:7:43:24 | GVN | 43:c7-c24 45:c7-c24 46:c7-c7 |
+| test.cpp:43:12:43:13 | GVN | 43:c12-c13 45:c12-c13 |
+| test.cpp:43:17:43:24 | GVN | 43:c17-c24 45:c17-c24 |
+| test.cpp:44:3:44:5 | GVN | 44:c3-c5 44:c4-c5 |
+| test.cpp:53:10:53:13 | GVN | 53:c10-c13 56:c21-c24 |
+| test.cpp:53:10:53:13 | GVN | 53:c10-c13 56:c21-c24 |
+| test.cpp:53:11:53:13 | GVN | 53:c11-c13 56:c22-c24 |
+| test.cpp:53:18:53:21 | GVN | 53:c18-c21 56:c39-c42 59:c17-c20 |
+| test.cpp:56:13:56:16 | GVN | 56:c13-c16 56:c31-c34 59:c9-c12 |
+| test.cpp:56:13:56:16 | GVN | 56:c13-c16 56:c31-c34 59:c9-c12 |
+| test.cpp:56:14:56:16 | GVN | 56:c14-c16 56:c32-c34 56:c47-c49 59:c10-c12 |
+| test.cpp:62:5:62:10 | GVN | 62:c5-c10 65:c10-c15 |
+| test.cpp:77:20:77:28 | GVN | 77:c20-c28 79:c7-c7 |
+| test.cpp:79:11:79:14 | GVN | 79:c11-c14 79:c24-c27 |
+| test.cpp:92:11:92:16 | GVN | 92:c11-c16 92:c15-c16 93:c10-c10 |
+| test.cpp:105:11:105:12 | GVN | 105:c11-c12 106:c33-c34 |
+| test.cpp:105:11:105:12 | GVN | 105:c11-c12 106:c33-c34 107:c11-c12 |
+| test.cpp:105:15:105:15 | GVN | 105:c15-c15 107:c15-c15 109:c10-c10 |
+| test.cpp:113:3:113:5 | GVN | 113:c3-c5 115:c3-c5 |
+| test.cpp:125:11:125:12 | GVN | 125:c11-c12 126:c11-c12 128:c3-c4 129:c11-c12 |
+| test.cpp:125:15:125:15 | GVN | 125:c15-c15 126:c15-c15 |
+| test.cpp:128:11:128:11 | GVN | 128:c11-c11 129:c15-c15 |
+| test.cpp:136:11:136:18 | GVN | 136:c11-c18 137:c11-c18 139:c3-c10 |
+| test.cpp:136:21:136:21 | GVN | 136:c21-c21 137:c21-c21 |
+| test.cpp:144:11:144:12 | GVN | 144:c11-c12 145:c11-c12 147:c3-c4 149:c11-c12 |
+| test.cpp:144:15:144:15 | GVN | 144:c15-c15 149:c15-c15 |
+| test.cpp:153:11:153:18 | GVN | 153:c11-c18 154:c11-c18 156:c3-c10 |
+| test.cpp:153:21:153:21 | GVN | 153:c21-c21 154:c21-c21 |
diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_ir_gvn.ql b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_ir_gvn.ql
new file mode 100644
index 00000000000..0e525a753b5
--- /dev/null
+++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_ir_gvn.ql
@@ -0,0 +1,11 @@
+import cpp
+import semmle.code.cpp.ir.internal.ASTValueNumbering
+
+from GVN g
+where strictcount(g.getAnExpr()) > 1
+select g,
+ strictconcat(Location loc |
+ loc = g.getAnExpr().getLocation()
+ |
+ loc.getStartLine() + ":c" + loc.getStartColumn() + "-c" + loc.getEndColumn(), " "
+ )
diff --git a/cpp/ql/test/qlpack.yml b/cpp/ql/test/qlpack.yml
index 008af57baef..89f684e9e3e 100644
--- a/cpp/ql/test/qlpack.yml
+++ b/cpp/ql/test/qlpack.yml
@@ -2,3 +2,4 @@ name: codeql-cpp-tests
version: 0.0.0
libraryPathDependencies: codeql-cpp
extractor: cpp
+tests: .
diff --git a/cpp/ql/test/query-tests/Critical/NewFree/NewFreeMismatch.expected b/cpp/ql/test/query-tests/Critical/NewFree/NewFreeMismatch.expected
index 45f88d426c9..3326d94a7d2 100644
--- a/cpp/ql/test/query-tests/Critical/NewFree/NewFreeMismatch.expected
+++ b/cpp/ql/test/query-tests/Critical/NewFree/NewFreeMismatch.expected
@@ -18,3 +18,6 @@
| test.cpp:235:2:235:5 | call to free | There is a new/free mismatch between this free and the corresponding $@. | test.cpp:227:7:227:13 | new | new |
| test.cpp:239:2:239:5 | call to free | There is a new/free mismatch between this free and the corresponding $@. | test.cpp:228:7:228:17 | new[] | new[] |
| test.cpp:272:3:272:6 | call to free | There is a new/free mismatch between this free and the corresponding $@. | test.cpp:265:7:265:13 | new | new |
+| test.cpp:441:2:441:10 | delete | There is a malloc/delete mismatch between this delete and the corresponding $@. | test.cpp:434:13:434:18 | call to strdup | malloc |
+| test.cpp:443:2:443:10 | delete | There is a malloc/delete mismatch between this delete and the corresponding $@. | test.cpp:436:13:436:19 | call to strndup | malloc |
+| test.cpp:445:2:445:10 | delete | There is a malloc/delete mismatch between this delete and the corresponding $@. | test.cpp:438:16:438:21 | call to wcsdup | malloc |
diff --git a/cpp/ql/test/query-tests/Critical/NewFree/test.cpp b/cpp/ql/test/query-tests/Critical/NewFree/test.cpp
index 9a9483b7f72..0807eadb333 100644
--- a/cpp/ql/test/query-tests/Critical/NewFree/test.cpp
+++ b/cpp/ql/test/query-tests/Critical/NewFree/test.cpp
@@ -424,3 +424,24 @@ void test13()
delete myPointer3.getPointer(); // GOOD
}
+
+char *strdup(const char *s1);
+char *strndup(const char *s1, size_t n);
+wchar_t* wcsdup(const wchar_t* s1);
+
+void test14()
+{
+ char *s1 = strdup("string");
+ char *s2 = strdup("string");
+ char *s3 = strndup("string", 3);
+ char *s4 = strndup("string", 3);
+ wchar_t *s5 = wcsdup(L"string");
+ wchar_t *s6 = wcsdup(L"string");
+
+ delete s1; // BAD: strdup -> delete
+ free(s2); // GOOD
+ delete s3; // BAD: strndup -> delete
+ free(s4); // GOOD
+ delete s5; // BAD: wcsdup -> delete
+ free(s6); // GOOD
+}
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongNumberOfFormatArguments/test.c b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongNumberOfFormatArguments/test.c
index 8fc3fbe0425..2feb8db9a5a 100644
--- a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongNumberOfFormatArguments/test.c
+++ b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongNumberOfFormatArguments/test.c
@@ -42,4 +42,8 @@ void test(int i, const char *str)
}
printf("%@ %i %i", 1, 2); // GOOD
+
+ printf("%Y", 1, 2); // GOOD (unknown format character, this might be correct)
+ printf("%1.1Y", 1, 2); // GOOD (unknown format character, this might be correct)
+ printf("%*.*Y", 1, 2); // GOOD (unknown format character, this might be correct)
}
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_two_byte_wprintf/WrongTypeFormatArguments.expected b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_two_byte_wprintf/WrongTypeFormatArguments.expected
index c5e9fa9aa7a..dbbf2c125bc 100644
--- a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_two_byte_wprintf/WrongTypeFormatArguments.expected
+++ b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_two_byte_wprintf/WrongTypeFormatArguments.expected
@@ -1,3 +1,3 @@
-| printf.cpp:33:31:33:37 | test | This argument should be of type 'char *' but is of type 'char16_t *' |
-| printf.cpp:45:29:45:35 | test | This argument should be of type 'char *' but is of type 'char16_t *' |
-| printf.cpp:52:29:52:35 | test | This argument should be of type 'char16_t *' but is of type 'wchar_t *' |
+| printf.cpp:31:31:31:37 | test | This argument should be of type 'char *' but is of type 'char16_t *' |
+| printf.cpp:43:29:43:35 | test | This argument should be of type 'char *' but is of type 'char16_t *' |
+| printf.cpp:50:29:50:35 | test | This argument should be of type 'char16_t *' but is of type 'wchar_t *' |
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_two_byte_wprintf/formattingFunction.expected b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_two_byte_wprintf/formattingFunction.expected
index b6f89c398e5..1fc280ab011 100644
--- a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_two_byte_wprintf/formattingFunction.expected
+++ b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_two_byte_wprintf/formattingFunction.expected
@@ -1,2 +1,2 @@
-| printf.cpp:15:5:15:12 | swprintf | char | char16_t | char16_t |
-| printf.cpp:26:5:26:11 | sprintf | char | char16_t | char16_t |
+| printf.cpp:13:5:13:12 | swprintf | char | char16_t | char16_t |
+| printf.cpp:24:5:24:11 | sprintf | char | char16_t | char16_t |
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_two_byte_wprintf/printf.cpp b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_two_byte_wprintf/printf.cpp
index 013df87ff5d..deb022d3b12 100644
--- a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_two_byte_wprintf/printf.cpp
+++ b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_two_byte_wprintf/printf.cpp
@@ -8,9 +8,7 @@ typedef void *va_list;
#define va_start(va, other)
#define va_end(args)
-int vswprintf(WCHAR *dest, WCHAR *format, va_list args) {
- return 0;
-}
+int vswprintf(WCHAR *dest, WCHAR *format, va_list args);
int swprintf(WCHAR *dest, WCHAR *format, ...) {
va_list args;
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/SqlTainted.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/SqlTainted.expected
new file mode 100644
index 00000000000..af50f184740
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/SqlTainted.expected
@@ -0,0 +1,13 @@
+edges
+| test.c:15:20:15:23 | argv | test.c:21:18:21:23 | (const char *)... |
+| test.c:15:20:15:23 | argv | test.c:21:18:21:23 | (const char *)... |
+| test.c:15:20:15:23 | argv | test.c:21:18:21:23 | query1 |
+| test.c:15:20:15:23 | argv | test.c:21:18:21:23 | query1 |
+nodes
+| test.c:15:20:15:23 | argv | semmle.label | argv |
+| test.c:15:20:15:23 | argv | semmle.label | argv |
+| test.c:21:18:21:23 | (const char *)... | semmle.label | (const char *)... |
+| test.c:21:18:21:23 | (const char *)... | semmle.label | (const char *)... |
+| test.c:21:18:21:23 | query1 | semmle.label | query1 |
+#select
+| test.c:21:18:21:23 | query1 | test.c:15:20:15:23 | argv | test.c:21:18:21:23 | query1 | This argument to a SQL query function is derived from $@ and then passed to mysql_query(sqlArg) | test.c:15:20:15:23 | argv | user input (argv) |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/SqlTainted.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/SqlTainted.qlref
new file mode 100644
index 00000000000..21a12e5eadd
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/SqlTainted.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-089/SqlTainted.ql
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/test.c
new file mode 100644
index 00000000000..45304f13872
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/test.c
@@ -0,0 +1,34 @@
+// Semmle test case for rule SprintfToSqlQuery.ql (Uncontrolled sprintf for SQL query)
+// Associated with CWE-089: SQL injection. http://cwe.mitre.org/data/definitions/89.html
+
+///// Library routines /////
+
+typedef unsigned long size_t;
+int snprintf(char *s, size_t n, const char *format, ...);
+void sanitizeString(char *stringOut, size_t len, const char *strIn);
+int mysql_query(int arg1, const char *sqlArg);
+int atoi(const char *nptr);
+
+///// Test code /////
+
+int main(int argc, char** argv) {
+ char *userName = argv[2];
+ int userNumber = atoi(argv[3]);
+
+ // a string from the user is injected directly into an SQL query.
+ char query1[1000] = {0};
+ snprintf(query1, 1000, "SELECT UID FROM USERS where name = \"%s\"", userName);
+ mysql_query(0, query1); // BAD
+
+ // the user string is encoded by a library routine.
+ char userNameSanitized[1000] = {0};
+ sanitizeString(userNameSanitized, 1000, userName);
+ char query2[1000] = {0};
+ snprintf(query2, 1000, "SELECT UID FROM USERS where name = \"%s\"", userNameSanitized);
+ mysql_query(0, query2); // GOOD
+
+ // an integer from the user is injected into an SQL query.
+ char query3[1000] = {0};
+ snprintf(query3, 1000, "SELECT UID FROM USERS where number = \"%i\"", userNumber);
+ mysql_query(0, query3); // GOOD
+}
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/globalVars/UncontrolledFormatStringThroughGlobalVar.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/globalVars/UncontrolledFormatStringThroughGlobalVar.expected
index 826a659755d..6e90a21c7e1 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/globalVars/UncontrolledFormatStringThroughGlobalVar.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/globalVars/UncontrolledFormatStringThroughGlobalVar.expected
@@ -4,12 +4,14 @@ edges
| globalVars.c:8:7:8:10 | copy | globalVars.c:27:9:27:12 | copy |
| globalVars.c:8:7:8:10 | copy | globalVars.c:30:15:30:18 | copy |
| globalVars.c:8:7:8:10 | copy | globalVars.c:30:15:30:18 | copy |
+| globalVars.c:8:7:8:10 | copy | globalVars.c:30:15:30:18 | copy |
| globalVars.c:8:7:8:10 | copy | globalVars.c:35:11:35:14 | copy |
| globalVars.c:9:7:9:11 | copy2 | globalVars.c:38:9:38:13 | copy2 |
| globalVars.c:9:7:9:11 | copy2 | globalVars.c:38:9:38:13 | copy2 |
| globalVars.c:9:7:9:11 | copy2 | globalVars.c:38:9:38:13 | copy2 |
| globalVars.c:9:7:9:11 | copy2 | globalVars.c:41:15:41:19 | copy2 |
| globalVars.c:9:7:9:11 | copy2 | globalVars.c:41:15:41:19 | copy2 |
+| globalVars.c:9:7:9:11 | copy2 | globalVars.c:41:15:41:19 | copy2 |
| globalVars.c:9:7:9:11 | copy2 | globalVars.c:50:9:50:13 | copy2 |
| globalVars.c:9:7:9:11 | copy2 | globalVars.c:50:9:50:13 | copy2 |
| globalVars.c:9:7:9:11 | copy2 | globalVars.c:50:9:50:13 | copy2 |
@@ -25,9 +27,15 @@ edges
| globalVars.c:24:11:24:14 | argv | globalVars.c:11:22:11:25 | argv |
| globalVars.c:27:9:27:12 | copy | globalVars.c:27:9:27:12 | (const char *)... |
| globalVars.c:27:9:27:12 | copy | globalVars.c:27:9:27:12 | copy |
+| globalVars.c:30:15:30:18 | copy | globalVars.c:30:15:30:18 | copy |
+| globalVars.c:30:15:30:18 | copy | globalVars.c:30:15:30:18 | copy |
+| globalVars.c:30:15:30:18 | copy | globalVars.c:30:15:30:18 | copy |
| globalVars.c:35:11:35:14 | copy | globalVars.c:15:21:15:23 | val |
| globalVars.c:38:9:38:13 | copy2 | globalVars.c:38:9:38:13 | (const char *)... |
| globalVars.c:38:9:38:13 | copy2 | globalVars.c:38:9:38:13 | copy2 |
+| globalVars.c:41:15:41:19 | copy2 | globalVars.c:41:15:41:19 | copy2 |
+| globalVars.c:41:15:41:19 | copy2 | globalVars.c:41:15:41:19 | copy2 |
+| globalVars.c:41:15:41:19 | copy2 | globalVars.c:41:15:41:19 | copy2 |
| globalVars.c:50:9:50:13 | copy2 | globalVars.c:50:9:50:13 | (const char *)... |
| globalVars.c:50:9:50:13 | copy2 | globalVars.c:50:9:50:13 | copy2 |
nodes
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.expected
new file mode 100644
index 00000000000..2d28795d126
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.expected
@@ -0,0 +1,10 @@
+| test.cpp:6:5:6:13 | ... > ... | Unsigned subtraction can never be negative. |
+| test.cpp:10:8:10:24 | ... > ... | Unsigned subtraction can never be negative. |
+| test.cpp:15:9:15:25 | ... > ... | Unsigned subtraction can never be negative. |
+| test.cpp:32:12:32:20 | ... > ... | Unsigned subtraction can never be negative. |
+| test.cpp:39:12:39:20 | ... > ... | Unsigned subtraction can never be negative. |
+| test.cpp:47:5:47:13 | ... > ... | Unsigned subtraction can never be negative. |
+| test.cpp:55:5:55:13 | ... > ... | Unsigned subtraction can never be negative. |
+| test.cpp:62:5:62:13 | ... > ... | Unsigned subtraction can never be negative. |
+| test.cpp:69:5:69:13 | ... > ... | Unsigned subtraction can never be negative. |
+| test.cpp:75:8:75:16 | ... > ... | Unsigned subtraction can never be negative. |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.qlref
new file mode 100644
index 00000000000..9681978c0ad
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/test.cpp
new file mode 100644
index 00000000000..11de69e8c62
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/test.cpp
@@ -0,0 +1,77 @@
+int getAnInt();
+
+bool cond();
+
+void test(unsigned x, unsigned y, bool unknown) {
+ if(x - y > 0) { } // BAD
+
+ unsigned total = getAnInt();
+ unsigned limit = getAnInt();
+ while(limit - total > 0) { // BAD
+ total += getAnInt();
+ }
+
+ if(total <= limit) {
+ while(limit - total > 0) { // GOOD [FALSE POSITIVE]
+ total += getAnInt();
+ if(total > limit) break;
+ }
+ }
+
+ if(x >= y) {
+ bool b = x - y > 0; // GOOD
+ }
+
+ if((int)(x - y) >= 0) { } // GOOD. Maybe an overflow happened, but the result is converted to the "likely intended" result before the comparison
+
+ if(unknown) {
+ y = x & 0xFF;
+ } else {
+ y = x;
+ }
+ bool b1 = x - y > 0; // GOOD [FALSE POSITIVE]
+
+ x = getAnInt();
+ y = getAnInt();
+ if(y > x) {
+ y = x - 1;
+ }
+ bool b2 = x - y > 0; // GOOD [FALSE POSITIVE]
+
+ int N = getAnInt();
+ y = x;
+ while(cond()) {
+ if(unknown) { y--; }
+ }
+
+ if(x - y > 0) { } // GOOD [FALSE POSITIVE]
+
+ x = y;
+ while(cond()) {
+ if(unknown) break;
+ y--;
+ }
+
+ if(x - y > 0) { } // GOOD [FALSE POSITIVE]
+
+ y = 0;
+ for(int i = 0; i < x; ++i) {
+ if(unknown) { ++y; }
+ }
+
+ if(x - y > 0) { } // GOOD [FALSE POSITIVE]
+
+ x = y;
+ while(cond()) {
+ if(unknown) { x++; }
+ }
+
+ if(x - y > 0) { } // GOOD [FALSE POSITIVE]
+
+ int n = getAnInt();
+ if (n > x - y) { n = x - y; }
+ if (n > 0) {
+ y += n; // NOTE: `n` is at most `x - y` at this point.
+ if (x - y > 0) {} // GOOD [FALSE POSITIVE]
+ }
+}
\ No newline at end of file
diff --git a/cpp/upgrades/c82db4c596b8979eba9a8958e24353a5756d7a02/old.dbscheme b/cpp/upgrades/c82db4c596b8979eba9a8958e24353a5756d7a02/old.dbscheme
new file mode 100644
index 00000000000..c82db4c596b
--- /dev/null
+++ b/cpp/upgrades/c82db4c596b8979eba9a8958e24353a5756d7a02/old.dbscheme
@@ -0,0 +1,2125 @@
+
+/**
+ * An invocation of the compiler. Note that more than one file may be
+ * compiled per invocation. For example, this command compiles three
+ * source files:
+ *
+ * gcc -c f1.c f2.c f3.c
+ *
+ * The `id` simply identifies the invocation, while `cwd` is the working
+ * directory from which the compiler was invoked.
+ */
+compilations(
+ /**
+ * An invocation of the compiler. Note that more than one file may
+ * be compiled per invocation. For example, this command compiles
+ * three source files:
+ *
+ * gcc -c f1.c f2.c f3.c
+ */
+ unique int id : @compilation,
+ string cwd : string ref
+);
+
+/**
+ * The arguments that were passed to the extractor for a compiler
+ * invocation. If `id` is for the compiler invocation
+ *
+ * gcc -c f1.c f2.c f3.c
+ *
+ * then typically there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | *path to extractor*
+ * 1 | `--mimic`
+ * 2 | `/usr/bin/gcc`
+ * 3 | `-c`
+ * 4 | f1.c
+ * 5 | f2.c
+ * 6 | f3.c
+ */
+#keyset[id, num]
+compilation_args(
+ int id : @compilation ref,
+ int num : int ref,
+ string arg : string ref
+);
+
+/**
+ * The source files that are compiled by a compiler invocation.
+ * If `id` is for the compiler invocation
+ *
+ * gcc -c f1.c f2.c f3.c
+ *
+ * then there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | f1.c
+ * 1 | f2.c
+ * 2 | f3.c
+ *
+ * Note that even if those files `#include` headers, those headers
+ * do not appear as rows.
+ */
+#keyset[id, num]
+compilation_compiling_files(
+ int id : @compilation ref,
+ int num : int ref,
+ int file : @file ref
+);
+
+/**
+ * The time taken by the extractor for a compiler invocation.
+ *
+ * For each file `num`, there will be rows for
+ *
+ * kind | seconds
+ * ---- | ---
+ * 1 | CPU seconds used by the extractor frontend
+ * 2 | Elapsed seconds during the extractor frontend
+ * 3 | CPU seconds used by the extractor backend
+ * 4 | Elapsed seconds during the extractor backend
+ */
+#keyset[id, num, kind]
+compilation_time(
+ int id : @compilation ref,
+ int num : int ref,
+ /* kind:
+ 1 = frontend_cpu_seconds
+ 2 = frontend_elapsed_seconds
+ 3 = extractor_cpu_seconds
+ 4 = extractor_elapsed_seconds
+ */
+ int kind : int ref,
+ float seconds : float ref
+);
+
+/**
+ * An error or warning generated by the extractor.
+ * The diagnostic message `diagnostic` was generated during compiler
+ * invocation `compilation`, and is the `file_number_diagnostic_number`th
+ * message generated while extracting the `file_number`th file of that
+ * invocation.
+ */
+#keyset[compilation, file_number, file_number_diagnostic_number]
+diagnostic_for(
+ int diagnostic : @diagnostic ref,
+ int compilation : @compilation ref,
+ int file_number : int ref,
+ int file_number_diagnostic_number : int ref
+);
+
+/**
+ * If extraction was successful, then `cpu_seconds` and
+ * `elapsed_seconds` are the CPU time and elapsed time (respectively)
+ * that extraction took for compiler invocation `id`.
+ */
+compilation_finished(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref
+);
+
+
+/**
+ * External data, loaded from CSV files during snapshot creation. See
+ * [Tutorial: Incorporating external data](https://help.semmle.com/wiki/display/SD/Tutorial%3A+Incorporating+external+data)
+ * for more information.
+ */
+externalData(
+ int id : @externalDataElement,
+ string path : string ref,
+ int column: int ref,
+ string value : string ref
+);
+
+/**
+ * The date of the snapshot.
+ */
+snapshotDate(unique date snapshotDate : date ref);
+
+/**
+ * The source location of the snapshot.
+ */
+sourceLocationPrefix(string prefix : string ref);
+
+/**
+ * Data used by the 'duplicate code' detection.
+ */
+duplicateCode(
+ unique int id : @duplication,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+/**
+ * Data used by the 'similar code' detection.
+ */
+similarCode(
+ unique int id : @similarity,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+/**
+ * Data used by the 'duplicate code' and 'similar code' detection.
+ */
+@duplication_or_similarity = @duplication | @similarity
+
+/**
+ * Data used by the 'duplicate code' and 'similar code' detection.
+ */
+#keyset[id, offset]
+tokens(
+ int id : @duplication_or_similarity ref,
+ int offset : int ref,
+ int beginLine : int ref,
+ int beginColumn : int ref,
+ int endLine : int ref,
+ int endColumn : int ref
+);
+
+/**
+ * Information about packages that provide code used during compilation.
+ * The `id` is just a unique identifier.
+ * The `namespace` is typically the name of the package manager that
+ * provided the package (e.g. "dpkg" or "yum").
+ * The `package_name` is the name of the package, and `version` is its
+ * version (as a string).
+ */
+external_packages(
+ unique int id: @external_package,
+ string namespace : string ref,
+ string package_name : string ref,
+ string version : string ref
+);
+
+/**
+ * Holds if File `fileid` was provided by package `package`.
+ */
+header_to_external_package(
+ int fileid : @file ref,
+ int package : @external_package ref
+);
+
+/*
+ * Version history
+ */
+
+svnentries(
+ unique int id : @svnentry,
+ string revision : string ref,
+ string author : string ref,
+ date revisionDate : date ref,
+ int changeSize : int ref
+)
+
+svnaffectedfiles(
+ int id : @svnentry ref,
+ int file : @file ref,
+ string action : string ref
+)
+
+svnentrymsg(
+ unique int id : @svnentry ref,
+ string message : string ref
+)
+
+svnchurn(
+ int commit : @svnentry ref,
+ int file : @file ref,
+ int addedLines : int ref,
+ int deletedLines : int ref
+)
+
+/*
+ * C++ dbscheme
+ */
+
+@location = @location_stmt | @location_expr | @location_default ;
+
+/**
+ * The location of an element that is not an expression or a statement.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `file`.
+ * For more information, see
+ * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
+ */
+locations_default(
+ /** The location of an element that is not an expression or a statement. */
+ unique int id: @location_default,
+ int container: @container ref,
+ int startLine: int ref,
+ int startColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+/**
+ * The location of a statement.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `file`.
+ * For more information, see
+ * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
+ */
+locations_stmt(
+ /** The location of a statement. */
+ unique int id: @location_stmt,
+ int container: @container ref,
+ int startLine: int ref,
+ int startColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+/**
+ * The location of an expression.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `file`.
+ * For more information, see
+ * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
+ */
+locations_expr(
+ /** The location of an expression. */
+ unique int id: @location_expr,
+ int container: @container ref,
+ int startLine: int ref,
+ int startColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+/** An element for which line-count information is available. */
+@sourceline = @file | @function | @variable | @enumconstant | @xmllocatable;
+
+numlines(
+ int element_id: @sourceline ref,
+ int num_lines: int ref,
+ int num_code: int ref,
+ int num_comment: int ref
+);
+
+diagnostics(
+ unique int id: @diagnostic,
+ int severity: int ref,
+ string error_tag: string ref,
+ string error_message: string ref,
+ string full_error_message: string ref,
+ int location: @location_default ref
+);
+
+/*
+ fromSource(0) = unknown,
+ fromSource(1) = from source,
+ fromSource(2) = from library
+*/
+files(
+ unique int id: @file,
+ string name: string ref,
+ string simple: string ref,
+ string ext: string ref,
+ int fromSource: int ref
+);
+
+folders(
+ unique int id: @folder,
+ string name: string ref,
+ string simple: string ref
+);
+
+@container = @folder | @file
+
+containerparent(
+ int parent: @container ref,
+ unique int child: @container ref
+);
+
+fileannotations(
+ int id: @file ref,
+ int kind: int ref,
+ string name: string ref,
+ string value: string ref
+);
+
+inmacroexpansion(
+ int id: @element ref,
+ int inv: @macroinvocation ref
+);
+
+affectedbymacroexpansion(
+ int id: @element ref,
+ int inv: @macroinvocation ref
+);
+
+/*
+ case @macroinvocations.kind of
+ 1 = macro expansion
+ | 2 = other macro reference
+ ;
+*/
+macroinvocations(
+ unique int id: @macroinvocation,
+ int macro_id: @ppd_define ref,
+ int location: @location_default ref,
+ int kind: int ref
+);
+
+macroparent(
+ unique int id: @macroinvocation ref,
+ int parent_id: @macroinvocation ref
+);
+
+// a macroinvocation may be part of another location
+// the way to find a constant expression that uses a macro
+// is thus to find a constant expression that has a location
+// to which a macro invocation is bound
+macrolocationbind(
+ int id: @macroinvocation ref,
+ int location: @location ref
+);
+
+#keyset[invocation, argument_index]
+macro_argument_unexpanded(
+ int invocation: @macroinvocation ref,
+ int argument_index: int ref,
+ string text: string ref
+);
+
+#keyset[invocation, argument_index]
+macro_argument_expanded(
+ int invocation: @macroinvocation ref,
+ int argument_index: int ref,
+ string text: string ref
+);
+
+/*
+ case @function.kind of
+ 1 = normal
+ | 2 = constructor
+ | 3 = destructor
+ | 4 = conversion
+ | 5 = operator
+ | 6 = builtin // GCC built-in functions, e.g. __builtin___memcpy_chk
+ ;
+*/
+functions(
+ unique int id: @function,
+ string name: string ref,
+ int kind: int ref
+);
+
+function_entry_point(int id: @function ref, unique int entry_point: @stmt ref);
+
+function_return_type(int id: @function ref, int return_type: @type ref);
+
+/** If `function` is a coroutine, then this gives the
+ std::experimental::resumable_traits instance associated with it,
+ and the variables representing the `handle` and `promise` for it. */
+coroutine(
+ unique int function: @function ref,
+ int traits: @type ref,
+ int handle: @variable ref,
+ int promise: @variable ref
+);
+
+/** The `new` function used for allocating the coroutine state, if any. */
+coroutine_new(
+ unique int function: @function ref,
+ int new: @function ref
+);
+
+/** The `delete` function used for deallocating the coroutine state, if any. */
+coroutine_delete(
+ unique int function: @function ref,
+ int delete: @function ref
+);
+
+purefunctions(unique int id: @function ref);
+
+function_deleted(unique int id: @function ref);
+
+function_defaulted(unique int id: @function ref);
+
+member_function_this_type(unique int id: @function ref, int this_type: @type ref);
+
+#keyset[id, type_id]
+fun_decls(
+ int id: @fun_decl,
+ int function: @function ref,
+ int type_id: @type ref,
+ string name: string ref,
+ int location: @location_default ref
+);
+fun_def(unique int id: @fun_decl ref);
+fun_specialized(unique int id: @fun_decl ref);
+fun_implicit(unique int id: @fun_decl ref);
+fun_decl_specifiers(
+ int id: @fun_decl ref,
+ string name: string ref
+)
+#keyset[fun_decl, index]
+fun_decl_throws(
+ int fun_decl: @fun_decl ref,
+ int index: int ref,
+ int type_id: @type ref
+);
+/* an empty throw specification is different from none */
+fun_decl_empty_throws(unique int fun_decl: @fun_decl ref);
+fun_decl_noexcept(
+ int fun_decl: @fun_decl ref,
+ int constant: @expr ref
+);
+fun_decl_empty_noexcept(int fun_decl: @fun_decl ref);
+fun_decl_typedef_type(
+ unique int fun_decl: @fun_decl ref,
+ int typedeftype_id: @usertype ref
+);
+
+param_decl_bind(
+ unique int id: @var_decl ref,
+ int index: int ref,
+ int fun_decl: @fun_decl ref
+);
+
+#keyset[id, type_id]
+var_decls(
+ int id: @var_decl,
+ int variable: @variable ref,
+ int type_id: @type ref,
+ string name: string ref,
+ int location: @location_default ref
+);
+var_def(unique int id: @var_decl ref);
+var_decl_specifiers(
+ int id: @var_decl ref,
+ string name: string ref
+)
+
+type_decls(
+ unique int id: @type_decl,
+ int type_id: @type ref,
+ int location: @location_default ref
+);
+type_def(unique int id: @type_decl ref);
+type_decl_top(
+ unique int type_decl: @type_decl ref
+);
+
+namespace_decls(
+ unique int id: @namespace_decl,
+ int namespace_id: @namespace ref,
+ int location: @location_default ref,
+ int bodylocation: @location_default ref
+);
+
+usings(
+ unique int id: @using,
+ int element_id: @element ref,
+ int location: @location_default ref
+);
+
+/** The element which contains the `using` declaration. */
+using_container(
+ int parent: @element ref,
+ int child: @using ref
+);
+
+static_asserts(
+ unique int id: @static_assert,
+ int condition : @expr ref,
+ string message : string ref,
+ int location: @location_default ref,
+ int enclosing : @element ref
+);
+
+// each function has an ordered list of parameters
+#keyset[id, type_id]
+#keyset[function, index, type_id]
+params(
+ int id: @parameter,
+ int function: @functionorblock ref,
+ int index: int ref,
+ int type_id: @type ref
+);
+
+overrides(int new: @function ref, int old: @function ref);
+
+#keyset[id, type_id]
+membervariables(
+ int id: @membervariable,
+ int type_id: @type ref,
+ string name: string ref
+);
+
+#keyset[id, type_id]
+globalvariables(
+ int id: @globalvariable,
+ int type_id: @type ref,
+ string name: string ref
+);
+
+#keyset[id, type_id]
+localvariables(
+ int id: @localvariable,
+ int type_id: @type ref,
+ string name: string ref
+);
+
+autoderivation(
+ unique int var: @variable ref,
+ int derivation_type: @type ref
+);
+
+enumconstants(
+ unique int id: @enumconstant,
+ int parent: @usertype ref,
+ int index: int ref,
+ int type_id: @type ref,
+ string name: string ref,
+ int location: @location_default ref
+);
+
+@variable = @localscopevariable | @globalvariable | @membervariable;
+
+@localscopevariable = @localvariable | @parameter;
+
+/*
+ Built-in types are the fundamental types, e.g., integral, floating, and void.
+
+ case @builtintype.kind of
+ 1 = error
+ | 2 = unknown
+ | 3 = void
+ | 4 = boolean
+ | 5 = char
+ | 6 = unsigned_char
+ | 7 = signed_char
+ | 8 = short
+ | 9 = unsigned_short
+ | 10 = signed_short
+ | 11 = int
+ | 12 = unsigned_int
+ | 13 = signed_int
+ | 14 = long
+ | 15 = unsigned_long
+ | 16 = signed_long
+ | 17 = long_long
+ | 18 = unsigned_long_long
+ | 19 = signed_long_long
+ | 20 = __int8 // Microsoft-specific
+ | 21 = __int16 // Microsoft-specific
+ | 22 = __int32 // Microsoft-specific
+ | 23 = __int64 // Microsoft-specific
+ | 24 = float
+ | 25 = double
+ | 26 = long_double
+ | 27 = _Complex_float // C99-specific
+ | 28 = _Complex_double // C99-specific
+ | 29 = _Complex_long double // C99-specific
+ | 30 = _Imaginary_float // C99-specific
+ | 31 = _Imaginary_double // C99-specific
+ | 32 = _Imaginary_long_double // C99-specific
+ | 33 = wchar_t // Microsoft-specific
+ | 34 = decltype_nullptr // C++11
+ | 35 = __int128
+ | 36 = unsigned___int128
+ | 37 = signed___int128
+ | 38 = __float128
+ | 39 = _Complex___float128
+ | 40 = _Decimal32
+ | 41 = _Decimal64
+ | 42 = _Decimal128
+ | 43 = char16_t
+ | 44 = char32_t
+ | 45 = _Float32
+ | 46 = _Float32x
+ | 47 = _Float64
+ | 48 = _Float64x
+ | 49 = _Float128
+ | 50 = _Float128x
+ | 51 = char8_t
+ ;
+*/
+builtintypes(
+ unique int id: @builtintype,
+ string name: string ref,
+ int kind: int ref,
+ int size: int ref,
+ int sign: int ref,
+ int alignment: int ref
+);
+
+/*
+ Derived types are types that are directly derived from existing types and
+ point to, refer to, transform type data to return a new type.
+
+ case @derivedtype.kind of
+ 1 = pointer
+ | 2 = reference
+ | 3 = type_with_specifiers
+ | 4 = array
+ | 5 = gnu_vector
+ | 6 = routineptr
+ | 7 = routinereference
+ | 8 = rvalue_reference // C++11
+// ... 9 type_conforming_to_protocols deprecated
+ | 10 = block
+ ;
+*/
+derivedtypes(
+ unique int id: @derivedtype,
+ string name: string ref,
+ int kind: int ref,
+ int type_id: @type ref
+);
+
+pointerishsize(unique int id: @derivedtype ref,
+ int size: int ref,
+ int alignment: int ref);
+
+arraysizes(
+ unique int id: @derivedtype ref,
+ int num_elements: int ref,
+ int bytesize: int ref,
+ int alignment: int ref
+);
+
+typedefbase(
+ unique int id: @usertype ref,
+ int type_id: @type ref
+);
+
+decltypes(
+ unique int id: @decltype,
+ int expr: @expr ref,
+ int base_type: @type ref,
+ boolean parentheses_would_change_meaning: boolean ref
+);
+
+/*
+ case @usertype.kind of
+ 1 = struct
+ | 2 = class
+ | 3 = union
+ | 4 = enum
+ | 5 = typedef // classic C: typedef typedef type name
+ | 6 = template
+ | 7 = template_parameter
+ | 8 = template_template_parameter
+ | 9 = proxy_class // a proxy class associated with a template parameter
+// ... 10 objc_class deprecated
+// ... 11 objc_protocol deprecated
+// ... 12 objc_category deprecated
+ | 13 = scoped_enum
+ | 14 = using_alias // a using name = type style typedef
+ ;
+*/
+usertypes(
+ unique int id: @usertype,
+ string name: string ref,
+ int kind: int ref
+);
+
+usertypesize(
+ unique int id: @usertype ref,
+ int size: int ref,
+ int alignment: int ref
+);
+
+usertype_final(unique int id: @usertype ref);
+
+usertype_uuid(
+ unique int id: @usertype ref,
+ unique string uuid: string ref
+);
+
+mangled_name(
+ unique int id: @declaration ref,
+ int mangled_name : @mangledname
+);
+
+is_pod_class(unique int id: @usertype ref);
+is_standard_layout_class(unique int id: @usertype ref);
+
+is_complete(unique int id: @usertype ref);
+
+is_class_template(unique int id: @usertype ref);
+class_instantiation(
+ int to: @usertype ref,
+ int from: @usertype ref
+);
+class_template_argument(
+ int type_id: @usertype ref,
+ int index: int ref,
+ int arg_type: @type ref
+);
+class_template_argument_value(
+ int type_id: @usertype ref,
+ int index: int ref,
+ int arg_value: @expr ref
+);
+
+is_proxy_class_for(
+ unique int id: @usertype ref,
+ unique int templ_param_id: @usertype ref
+);
+
+type_mentions(
+ unique int id: @type_mention,
+ int type_id: @type ref,
+ int location: @location ref,
+ // a_symbol_reference_kind from the EDG frontend. See symbol_ref.h there.
+ int kind: int ref
+);
+
+is_function_template(unique int id: @function ref);
+function_instantiation(
+ unique int to: @function ref,
+ int from: @function ref
+);
+function_template_argument(
+ int function_id: @function ref,
+ int index: int ref,
+ int arg_type: @type ref
+);
+function_template_argument_value(
+ int function_id: @function ref,
+ int index: int ref,
+ int arg_value: @expr ref
+);
+
+is_variable_template(unique int id: @variable ref);
+variable_instantiation(
+ unique int to: @variable ref,
+ int from: @variable ref
+);
+variable_template_argument(
+ int variable_id: @variable ref,
+ int index: int ref,
+ int arg_type: @type ref
+);
+variable_template_argument_value(
+ int variable_id: @variable ref,
+ int index: int ref,
+ int arg_value: @expr ref
+);
+
+/*
+ Fixed point types
+ precision(1) = short, precision(2) = default, precision(3) = long
+ is_unsigned(1) = unsigned is_unsigned(2) = signed
+ is_fract_type(1) = declared with _Fract
+ saturating(1) = declared with _Sat
+*/
+/* TODO
+fixedpointtypes(
+ unique int id: @fixedpointtype,
+ int precision: int ref,
+ int is_unsigned: int ref,
+ int is_fract_type: int ref,
+ int saturating: int ref);
+*/
+
+routinetypes(
+ unique int id: @routinetype,
+ int return_type: @type ref
+);
+
+routinetypeargs(
+ int routine: @routinetype ref,
+ int index: int ref,
+ int type_id: @type ref
+);
+
+ptrtomembers(
+ unique int id: @ptrtomember,
+ int type_id: @type ref,
+ int class_id: @type ref
+);
+
+/*
+ specifiers for types, functions, and variables
+
+ "public",
+ "protected",
+ "private",
+
+ "const",
+ "volatile",
+ "static",
+
+ "pure",
+ "virtual",
+ "sealed", // Microsoft
+ "__interface", // Microsoft
+ "inline",
+ "explicit",
+
+ "near", // near far extension
+ "far", // near far extension
+ "__ptr32", // Microsoft
+ "__ptr64", // Microsoft
+ "__sptr", // Microsoft
+ "__uptr", // Microsoft
+ "dllimport", // Microsoft
+ "dllexport", // Microsoft
+ "thread", // Microsoft
+ "naked", // Microsoft
+ "microsoft_inline", // Microsoft
+ "forceinline", // Microsoft
+ "selectany", // Microsoft
+ "nothrow", // Microsoft
+ "novtable", // Microsoft
+ "noreturn", // Microsoft
+ "noinline", // Microsoft
+ "noalias", // Microsoft
+ "restrict", // Microsoft
+*/
+
+specifiers(
+ unique int id: @specifier,
+ unique string str: string ref
+);
+
+typespecifiers(
+ int type_id: @type ref,
+ int spec_id: @specifier ref
+);
+
+funspecifiers(
+ int func_id: @function ref,
+ int spec_id: @specifier ref
+);
+
+varspecifiers(
+ int var_id: @accessible ref,
+ int spec_id: @specifier ref
+);
+
+attributes(
+ unique int id: @attribute,
+ int kind: int ref,
+ string name: string ref,
+ string name_space: string ref,
+ int location: @location_default ref
+);
+
+case @attribute.kind of
+ 0 = @gnuattribute
+| 1 = @stdattribute
+| 2 = @declspec
+| 3 = @msattribute
+| 4 = @alignas
+// ... 5 @objc_propertyattribute deprecated
+;
+
+attribute_args(
+ unique int id: @attribute_arg,
+ int kind: int ref,
+ int attribute: @attribute ref,
+ int index: int ref,
+ int location: @location_default ref
+);
+
+case @attribute_arg.kind of
+ 0 = @attribute_arg_empty
+| 1 = @attribute_arg_token
+| 2 = @attribute_arg_constant
+| 3 = @attribute_arg_type
+;
+
+attribute_arg_value(
+ unique int arg: @attribute_arg ref,
+ string value: string ref
+);
+attribute_arg_type(
+ unique int arg: @attribute_arg ref,
+ int type_id: @type ref
+);
+attribute_arg_name(
+ unique int arg: @attribute_arg ref,
+ string name: string ref
+);
+
+typeattributes(
+ int type_id: @type ref,
+ int spec_id: @attribute ref
+);
+
+funcattributes(
+ int func_id: @function ref,
+ int spec_id: @attribute ref
+);
+
+varattributes(
+ int var_id: @accessible ref,
+ int spec_id: @attribute ref
+);
+
+stmtattributes(
+ int stmt_id: @stmt ref,
+ int spec_id: @attribute ref
+);
+
+@type = @builtintype
+ | @derivedtype
+ | @usertype
+ /* TODO | @fixedpointtype */
+ | @routinetype
+ | @ptrtomember
+ | @decltype;
+
+unspecifiedtype(
+ unique int type_id: @type ref,
+ int unspecified_type_id: @type ref
+);
+
+member(
+ int parent: @type ref,
+ int index: int ref,
+ int child: @member ref
+);
+
+@enclosingfunction_child = @usertype | @variable | @namespace
+
+enclosingfunction(
+ unique int child: @enclosingfunction_child ref,
+ int parent: @function ref
+);
+
+derivations(
+ unique int derivation: @derivation,
+ int sub: @type ref,
+ int index: int ref,
+ int super: @type ref,
+ int location: @location_default ref
+);
+
+derspecifiers(
+ int der_id: @derivation ref,
+ int spec_id: @specifier ref
+);
+
+/**
+ * Contains the byte offset of the base class subobject within the derived
+ * class. Only holds for non-virtual base classes, but see table
+ * `virtual_base_offsets` for offsets of virtual base class subobjects.
+ */
+direct_base_offsets(
+ unique int der_id: @derivation ref,
+ int offset: int ref
+);
+
+/**
+ * Contains the byte offset of the virtual base class subobject for class
+ * `super` within a most-derived object of class `sub`. `super` can be either a
+ * direct or indirect base class.
+ */
+#keyset[sub, super]
+virtual_base_offsets(
+ int sub: @usertype ref,
+ int super: @usertype ref,
+ int offset: int ref
+);
+
+frienddecls(
+ unique int id: @frienddecl,
+ int type_id: @type ref,
+ int decl_id: @declaration ref,
+ int location: @location_default ref
+);
+
+@declaredtype = @usertype ;
+
+@declaration = @function
+ | @declaredtype
+ | @variable
+ | @enumconstant
+ | @frienddecl;
+
+@member = @membervariable
+ | @function
+ | @declaredtype
+ | @enumconstant;
+
+@locatable = @diagnostic
+ | @declaration
+ | @ppd_include
+ | @ppd_define
+ | @macroinvocation
+ /*| @funcall*/
+ | @xmllocatable
+ | @attribute
+ | @attribute_arg;
+
+@namedscope = @namespace | @usertype;
+
+@element = @locatable
+ | @file
+ | @folder
+ | @specifier
+ | @type
+ | @expr
+ | @namespace
+ | @initialiser
+ | @stmt
+ | @derivation
+ | @comment
+ | @preprocdirect
+ | @fun_decl
+ | @var_decl
+ | @type_decl
+ | @namespace_decl
+ | @using
+ | @namequalifier
+ | @specialnamequalifyingelement
+ | @static_assert
+ | @type_mention
+ | @lambdacapture;
+
+@exprparent = @element;
+
+comments(
+ unique int id: @comment,
+ string contents: string ref,
+ int location: @location_default ref
+);
+
+commentbinding(
+ int id: @comment ref,
+ int element: @element ref
+);
+
+exprconv(
+ int converted: @expr ref,
+ unique int conversion: @expr ref
+);
+
+compgenerated(unique int id: @element ref);
+
+/**
+ * `destructor_call` destructs the `i`'th entity that should be
+ * destructed following `element`. Note that entities should be
+ * destructed in reverse construction order, so for a given `element`
+ * these should be called from highest to lowest `i`.
+ */
+#keyset[element, destructor_call]
+#keyset[element, i]
+synthetic_destructor_call(
+ int element: @element ref,
+ int i: int ref,
+ int destructor_call: @routineexpr ref
+);
+
+namespaces(
+ unique int id: @namespace,
+ string name: string ref
+);
+
+namespace_inline(
+ unique int id: @namespace ref
+);
+
+namespacembrs(
+ int parentid: @namespace ref,
+ unique int memberid: @namespacembr ref
+);
+
+@namespacembr = @declaration | @namespace;
+
+exprparents(
+ int expr_id: @expr ref,
+ int child_index: int ref,
+ int parent_id: @exprparent ref
+);
+
+expr_isload(unique int expr_id: @expr ref);
+
+@cast = @c_style_cast
+ | @const_cast
+ | @dynamic_cast
+ | @reinterpret_cast
+ | @static_cast
+ ;
+
+/*
+case @conversion.kind of
+ 0 = @simple_conversion // a numeric conversion, qualification conversion, or a reinterpret_cast
+| 1 = @bool_conversion // conversion to 'bool'
+| 2 = @base_class_conversion // a derived-to-base conversion
+| 3 = @derived_class_conversion // a base-to-derived conversion
+| 4 = @pm_base_class_conversion // a derived-to-base conversion of a pointer to member
+| 5 = @pm_derived_class_conversion // a base-to-derived conversion of a pointer to member
+| 6 = @glvalue_adjust // an adjustment of the type of a glvalue
+| 7 = @prvalue_adjust // an adjustment of the type of a prvalue
+;
+*/
+/**
+ * Describes the semantics represented by a cast expression. This is largely
+ * independent of the source syntax of the cast, so it is separate from the
+ * regular expression kind.
+ */
+conversionkinds(
+ unique int expr_id: @cast ref,
+ int kind: int ref
+);
+
+@conversion = @cast
+ | @array_to_pointer
+ | @parexpr
+ | @reference_to
+ | @ref_indirect
+ | @temp_init
+ ;
+
+/*
+case @funbindexpr.kind of
+ 0 = @normal_call // a normal call
+| 1 = @virtual_call // a virtual call
+| 2 = @adl_call // a call whose target is only found by ADL
+;
+*/
+iscall(unique int caller: @funbindexpr ref, int kind: int ref);
+
+numtemplatearguments(
+ unique int expr_id: @expr ref,
+ int num: int ref
+);
+
+specialnamequalifyingelements(
+ unique int id: @specialnamequalifyingelement,
+ unique string name: string ref
+);
+
+@namequalifiableelement = @expr | @namequalifier;
+@namequalifyingelement = @namespace
+ | @specialnamequalifyingelement
+ | @usertype;
+
+namequalifiers(
+ unique int id: @namequalifier,
+ unique int qualifiableelement: @namequalifiableelement ref,
+ int qualifyingelement: @namequalifyingelement ref,
+ int location: @location_default ref
+);
+
+varbind(
+ int expr: @varbindexpr ref,
+ int var: @accessible ref
+);
+
+funbind(
+ int expr: @funbindexpr ref,
+ int fun: @function ref
+);
+
+@any_new_expr = @new_expr
+ | @new_array_expr;
+
+@new_or_delete_expr = @any_new_expr
+ | @delete_expr
+ | @delete_array_expr;
+
+@prefix_crement_expr = @preincrexpr | @predecrexpr;
+
+@postfix_crement_expr = @postincrexpr | @postdecrexpr;
+
+@increment_expr = @preincrexpr | @postincrexpr;
+
+@decrement_expr = @predecrexpr | @postdecrexpr;
+
+@crement_expr = @increment_expr | @decrement_expr;
+
+@un_arith_op_expr = @arithnegexpr
+ | @unaryplusexpr
+ | @conjugation
+ | @realpartexpr
+ | @imagpartexpr
+ | @crement_expr
+ ;
+
+@un_bitwise_op_expr = @complementexpr;
+
+@un_log_op_expr = @notexpr;
+
+@un_op_expr = @address_of
+ | @indirect
+ | @un_arith_op_expr
+ | @un_bitwise_op_expr
+ | @builtinaddressof
+ | @vec_fill
+ | @un_log_op_expr
+ | @co_await
+ | @co_yield
+ ;
+
+@bin_log_op_expr = @andlogicalexpr | @orlogicalexpr;
+
+@cmp_op_expr = @eq_op_expr | @rel_op_expr;
+
+@eq_op_expr = @eqexpr | @neexpr;
+
+@rel_op_expr = @gtexpr
+ | @ltexpr
+ | @geexpr
+ | @leexpr
+ | @spaceshipexpr
+ ;
+
+@bin_bitwise_op_expr = @lshiftexpr
+ | @rshiftexpr
+ | @andexpr
+ | @orexpr
+ | @xorexpr
+ ;
+
+@p_arith_op_expr = @paddexpr
+ | @psubexpr
+ | @pdiffexpr
+ ;
+
+@bin_arith_op_expr = @addexpr
+ | @subexpr
+ | @mulexpr
+ | @divexpr
+ | @remexpr
+ | @jmulexpr
+ | @jdivexpr
+ | @fjaddexpr
+ | @jfaddexpr
+ | @fjsubexpr
+ | @jfsubexpr
+ | @minexpr
+ | @maxexpr
+ | @p_arith_op_expr
+ ;
+
+@bin_op_expr = @bin_arith_op_expr
+ | @bin_bitwise_op_expr
+ | @cmp_op_expr
+ | @bin_log_op_expr
+ ;
+
+@op_expr = @un_op_expr
+ | @bin_op_expr
+ | @assign_expr
+ | @conditionalexpr
+ ;
+
+@assign_arith_expr = @assignaddexpr
+ | @assignsubexpr
+ | @assignmulexpr
+ | @assigndivexpr
+ | @assignremexpr
+ ;
+
+@assign_bitwise_expr = @assignandexpr
+ | @assignorexpr
+ | @assignxorexpr
+ | @assignlshiftexpr
+ | @assignrshiftexpr
+ | @assignpaddexpr
+ | @assignpsubexpr
+ ;
+
+@assign_op_expr = @assign_arith_expr | @assign_bitwise_expr
+
+@assign_expr = @assignexpr | @assign_op_expr
+
+/*
+ case @allocator.form of
+ 0 = plain
+ | 1 = alignment
+ ;
+*/
+
+/**
+ * The allocator function associated with a `new` or `new[]` expression.
+ * The `form` column specified whether the allocation call contains an alignment
+ * argument.
+ */
+expr_allocator(
+ unique int expr: @any_new_expr ref,
+ int func: @function ref,
+ int form: int ref
+);
+
+/*
+ case @deallocator.form of
+ 0 = plain
+ | 1 = size
+ | 2 = alignment
+ | 3 = size_and_alignment
+ ;
+*/
+
+/**
+ * The deallocator function associated with a `delete`, `delete[]`, `new`, or
+ * `new[]` expression. For a `new` or `new[]` expression, the deallocator is the
+ * one used to free memory if the initialization throws an exception.
+ * The `form` column specifies whether the deallocation call contains a size
+ * argument, and alignment argument, or both.
+ */
+expr_deallocator(
+ unique int expr: @new_or_delete_expr ref,
+ int func: @function ref,
+ int form: int ref
+);
+
+/**
+ * Holds if the `@conditionalexpr` is of the two operand form
+ * `guard ? : false`.
+ */
+expr_cond_two_operand(
+ unique int cond: @conditionalexpr ref
+);
+
+/**
+ * The guard of `@conditionalexpr` `guard ? true : false`
+ */
+expr_cond_guard(
+ unique int cond: @conditionalexpr ref,
+ int guard: @expr ref
+);
+
+/**
+ * The expression used when the guard of `@conditionalexpr`
+ * `guard ? true : false` holds. For the two operand form
+ * `guard ?: false` consider using `expr_cond_guard` instead.
+ */
+expr_cond_true(
+ unique int cond: @conditionalexpr ref,
+ int true: @expr ref
+);
+
+/**
+ * The expression used when the guard of `@conditionalexpr`
+ * `guard ? true : false` does not hold.
+ */
+expr_cond_false(
+ unique int cond: @conditionalexpr ref,
+ int false: @expr ref
+);
+
+/** A string representation of the value. */
+values(
+ unique int id: @value,
+ string str: string ref
+);
+
+/** The actual text in the source code for the value, if any. */
+valuetext(
+ unique int id: @value ref,
+ string text: string ref
+);
+
+valuebind(
+ int val: @value ref,
+ unique int expr: @expr ref
+);
+
+fieldoffsets(
+ unique int id: @variable ref,
+ int byteoffset: int ref,
+ int bitoffset: int ref
+);
+
+bitfield(
+ unique int id: @variable ref,
+ int bits: int ref,
+ int declared_bits: int ref
+);
+
+/* TODO
+memberprefix(
+ int member: @expr ref,
+ int prefix: @expr ref
+);
+*/
+
+/*
+ kind(1) = mbrcallexpr
+ kind(2) = mbrptrcallexpr
+ kind(3) = mbrptrmbrcallexpr
+ kind(4) = ptrmbrptrmbrcallexpr
+ kind(5) = mbrreadexpr // x.y
+ kind(6) = mbrptrreadexpr // p->y
+ kind(7) = mbrptrmbrreadexpr // x.*pm
+ kind(8) = mbrptrmbrptrreadexpr // x->*pm
+ kind(9) = staticmbrreadexpr // static x.y
+ kind(10) = staticmbrptrreadexpr // static p->y
+*/
+/* TODO
+memberaccess(
+ int member: @expr ref,
+ int kind: int ref
+);
+*/
+
+initialisers(
+ unique int init: @initialiser,
+ int var: @accessible ref,
+ unique int expr: @expr ref,
+ int location: @location_expr ref
+);
+
+/**
+ * An ancestor for the expression, for cases in which we cannot
+ * otherwise find the expression's parent.
+ */
+expr_ancestor(
+ int exp: @expr ref,
+ int ancestor: @element ref
+);
+
+exprs(
+ unique int id: @expr,
+ int kind: int ref,
+ int location: @location_expr ref
+);
+
+/*
+ case @value.category of
+ 1 = prval
+ | 2 = xval
+ | 3 = lval
+ ;
+*/
+expr_types(
+ int id: @expr ref,
+ int typeid: @type ref,
+ int value_category: int ref
+);
+
+case @expr.kind of
+ 1 = @errorexpr
+| 2 = @address_of // & AddressOfExpr
+| 3 = @reference_to // ReferenceToExpr (implicit?)
+| 4 = @indirect // * PointerDereferenceExpr
+| 5 = @ref_indirect // ReferenceDereferenceExpr (implicit?)
+// ...
+| 8 = @array_to_pointer // (???)
+| 9 = @vacuous_destructor_call // VacuousDestructorCall
+// ...
+| 11 = @assume // Microsoft
+| 12 = @parexpr
+| 13 = @arithnegexpr
+| 14 = @unaryplusexpr
+| 15 = @complementexpr
+| 16 = @notexpr
+| 17 = @conjugation // GNU ~ operator
+| 18 = @realpartexpr // GNU __real
+| 19 = @imagpartexpr // GNU __imag
+| 20 = @postincrexpr
+| 21 = @postdecrexpr
+| 22 = @preincrexpr
+| 23 = @predecrexpr
+| 24 = @conditionalexpr
+| 25 = @addexpr
+| 26 = @subexpr
+| 27 = @mulexpr
+| 28 = @divexpr
+| 29 = @remexpr
+| 30 = @jmulexpr // C99 mul imaginary
+| 31 = @jdivexpr // C99 div imaginary
+| 32 = @fjaddexpr // C99 add real + imaginary
+| 33 = @jfaddexpr // C99 add imaginary + real
+| 34 = @fjsubexpr // C99 sub real - imaginary
+| 35 = @jfsubexpr // C99 sub imaginary - real
+| 36 = @paddexpr // pointer add (pointer + int or int + pointer)
+| 37 = @psubexpr // pointer sub (pointer - integer)
+| 38 = @pdiffexpr // difference between two pointers
+| 39 = @lshiftexpr
+| 40 = @rshiftexpr
+| 41 = @andexpr
+| 42 = @orexpr
+| 43 = @xorexpr
+| 44 = @eqexpr
+| 45 = @neexpr
+| 46 = @gtexpr
+| 47 = @ltexpr
+| 48 = @geexpr
+| 49 = @leexpr
+| 50 = @minexpr // GNU minimum
+| 51 = @maxexpr // GNU maximum
+| 52 = @assignexpr
+| 53 = @assignaddexpr
+| 54 = @assignsubexpr
+| 55 = @assignmulexpr
+| 56 = @assigndivexpr
+| 57 = @assignremexpr
+| 58 = @assignlshiftexpr
+| 59 = @assignrshiftexpr
+| 60 = @assignandexpr
+| 61 = @assignorexpr
+| 62 = @assignxorexpr
+| 63 = @assignpaddexpr // assign pointer add
+| 64 = @assignpsubexpr // assign pointer sub
+| 65 = @andlogicalexpr
+| 66 = @orlogicalexpr
+| 67 = @commaexpr
+| 68 = @subscriptexpr // access to member of an array, e.g., a[5]
+// ... 69 @objc_subscriptexpr deprecated
+// ... 70 @cmdaccess deprecated
+// ...
+| 73 = @virtfunptrexpr
+| 74 = @callexpr
+// ... 75 @msgexpr_normal deprecated
+// ... 76 @msgexpr_super deprecated
+// ... 77 @atselectorexpr deprecated
+// ... 78 @atprotocolexpr deprecated
+| 79 = @vastartexpr
+| 80 = @vaargexpr
+| 81 = @vaendexpr
+| 82 = @vacopyexpr
+// ... 83 @atencodeexpr deprecated
+| 84 = @varaccess
+| 85 = @thisaccess
+// ... 86 @objc_box_expr deprecated
+| 87 = @new_expr
+| 88 = @delete_expr
+| 89 = @throw_expr
+| 90 = @condition_decl // a variable declared in a condition, e.g., if(int x = y > 2)
+| 91 = @braced_init_list
+| 92 = @type_id
+| 93 = @runtime_sizeof
+| 94 = @runtime_alignof
+| 95 = @sizeof_pack
+| 96 = @expr_stmt // GNU extension
+| 97 = @routineexpr
+| 98 = @type_operand // used to access a type in certain contexts (haven't found any examples yet....)
+| 99 = @offsetofexpr // offsetof ::= type and field
+| 100 = @hasassignexpr // __has_assign ::= type
+| 101 = @hascopyexpr // __has_copy ::= type
+| 102 = @hasnothrowassign // __has_nothrow_assign ::= type
+| 103 = @hasnothrowconstr // __has_nothrow_constructor ::= type
+| 104 = @hasnothrowcopy // __has_nothrow_copy ::= type
+| 105 = @hastrivialassign // __has_trivial_assign ::= type
+| 106 = @hastrivialconstr // __has_trivial_constructor ::= type
+| 107 = @hastrivialcopy // __has_trivial_copy ::= type
+| 108 = @hasuserdestr // __has_user_destructor ::= type
+| 109 = @hasvirtualdestr // __has_virtual_destructor ::= type
+| 110 = @isabstractexpr // __is_abstract ::= type
+| 111 = @isbaseofexpr // __is_base_of ::= type type
+| 112 = @isclassexpr // __is_class ::= type
+| 113 = @isconvtoexpr // __is_convertible_to ::= type type
+| 114 = @isemptyexpr // __is_empty ::= type
+| 115 = @isenumexpr // __is_enum ::= type
+| 116 = @ispodexpr // __is_pod ::= type
+| 117 = @ispolyexpr // __is_polymorphic ::= type
+| 118 = @isunionexpr // __is_union ::= type
+| 119 = @typescompexpr // GNU __builtin_types_compatible ::= type type
+| 120 = @intaddrexpr // EDG internal builtin, used to implement offsetof
+// ...
+| 122 = @hastrivialdestructor // __has_trivial_destructor ::= type
+| 123 = @literal
+| 124 = @uuidof
+| 127 = @aggregateliteral
+| 128 = @delete_array_expr
+| 129 = @new_array_expr
+// ... 130 @objc_array_literal deprecated
+// ... 131 @objc_dictionary_literal deprecated
+| 132 = @foldexpr
+// ...
+| 200 = @ctordirectinit
+| 201 = @ctorvirtualinit
+| 202 = @ctorfieldinit
+| 203 = @ctordelegatinginit
+| 204 = @dtordirectdestruct
+| 205 = @dtorvirtualdestruct
+| 206 = @dtorfielddestruct
+// ...
+| 210 = @static_cast
+| 211 = @reinterpret_cast
+| 212 = @const_cast
+| 213 = @dynamic_cast
+| 214 = @c_style_cast
+| 215 = @lambdaexpr
+| 216 = @param_ref
+| 217 = @noopexpr
+// ...
+| 294 = @istriviallyconstructibleexpr
+| 295 = @isdestructibleexpr
+| 296 = @isnothrowdestructibleexpr
+| 297 = @istriviallydestructibleexpr
+| 298 = @istriviallyassignableexpr
+| 299 = @isnothrowassignableexpr
+| 300 = @istrivialexpr
+| 301 = @isstandardlayoutexpr
+| 302 = @istriviallycopyableexpr
+| 303 = @isliteraltypeexpr
+| 304 = @hastrivialmoveconstructorexpr
+| 305 = @hastrivialmoveassignexpr
+| 306 = @hasnothrowmoveassignexpr
+| 307 = @isconstructibleexpr
+| 308 = @isnothrowconstructibleexpr
+| 309 = @hasfinalizerexpr
+| 310 = @isdelegateexpr
+| 311 = @isinterfaceclassexpr
+| 312 = @isrefarrayexpr
+| 313 = @isrefclassexpr
+| 314 = @issealedexpr
+| 315 = @issimplevalueclassexpr
+| 316 = @isvalueclassexpr
+| 317 = @isfinalexpr
+| 319 = @noexceptexpr
+| 320 = @builtinshufflevector
+| 321 = @builtinchooseexpr
+| 322 = @builtinaddressof
+| 323 = @vec_fill
+| 324 = @builtinconvertvector
+| 325 = @builtincomplex
+| 326 = @spaceshipexpr
+| 327 = @co_await
+| 328 = @co_yield
+| 329 = @temp_init
+;
+
+@var_args_expr = @vastartexpr
+ | @vaendexpr
+ | @vaargexpr
+ | @vacopyexpr
+ ;
+
+@builtin_op = @var_args_expr
+ | @noopexpr
+ | @offsetofexpr
+ | @intaddrexpr
+ | @hasassignexpr
+ | @hascopyexpr
+ | @hasnothrowassign
+ | @hasnothrowconstr
+ | @hasnothrowcopy
+ | @hastrivialassign
+ | @hastrivialconstr
+ | @hastrivialcopy
+ | @hastrivialdestructor
+ | @hasuserdestr
+ | @hasvirtualdestr
+ | @isabstractexpr
+ | @isbaseofexpr
+ | @isclassexpr
+ | @isconvtoexpr
+ | @isemptyexpr
+ | @isenumexpr
+ | @ispodexpr
+ | @ispolyexpr
+ | @isunionexpr
+ | @typescompexpr
+ | @builtinshufflevector
+ | @builtinconvertvector
+ | @builtinaddressof
+ | @istriviallyconstructibleexpr
+ | @isdestructibleexpr
+ | @isnothrowdestructibleexpr
+ | @istriviallydestructibleexpr
+ | @istriviallyassignableexpr
+ | @isnothrowassignableexpr
+ | @isstandardlayoutexpr
+ | @istriviallycopyableexpr
+ | @isliteraltypeexpr
+ | @hastrivialmoveconstructorexpr
+ | @hastrivialmoveassignexpr
+ | @hasnothrowmoveassignexpr
+ | @isconstructibleexpr
+ | @isnothrowconstructibleexpr
+ | @hasfinalizerexpr
+ | @isdelegateexpr
+ | @isinterfaceclassexpr
+ | @isrefarrayexpr
+ | @isrefclassexpr
+ | @issealedexpr
+ | @issimplevalueclassexpr
+ | @isvalueclassexpr
+ | @isfinalexpr
+ | @builtinchooseexpr
+ | @builtincomplex
+ ;
+
+new_allocated_type(
+ unique int expr: @new_expr ref,
+ int type_id: @type ref
+);
+
+new_array_allocated_type(
+ unique int expr: @new_array_expr ref,
+ int type_id: @type ref
+);
+
+/**
+ * The field being initialized by an initializer expression within an aggregate
+ * initializer for a class/struct/union.
+ */
+#keyset[aggregate, field]
+aggregate_field_init(
+ int aggregate: @aggregateliteral ref,
+ int initializer: @expr ref,
+ int field: @membervariable ref
+);
+
+/**
+ * The index of the element being initialized by an initializer expression
+ * within an aggregate initializer for an array.
+ */
+#keyset[aggregate, element_index]
+aggregate_array_init(
+ int aggregate: @aggregateliteral ref,
+ int initializer: @expr ref,
+ int element_index: int ref
+);
+
+@ctorinit = @ctordirectinit
+ | @ctorvirtualinit
+ | @ctorfieldinit
+ | @ctordelegatinginit;
+@dtordestruct = @dtordirectdestruct
+ | @dtorvirtualdestruct
+ | @dtorfielddestruct;
+
+
+condition_decl_bind(
+ unique int expr: @condition_decl ref,
+ unique int decl: @declaration ref
+);
+
+typeid_bind(
+ unique int expr: @type_id ref,
+ int type_id: @type ref
+);
+
+uuidof_bind(
+ unique int expr: @uuidof ref,
+ int type_id: @type ref
+);
+
+@runtime_sizeof_or_alignof = @runtime_sizeof | @runtime_alignof;
+
+sizeof_bind(
+ unique int expr: @runtime_sizeof_or_alignof ref,
+ int type_id: @type ref
+);
+
+code_block(
+ unique int block: @literal ref,
+ unique int routine: @function ref
+);
+
+lambdas(
+ unique int expr: @lambdaexpr ref,
+ string default_capture: string ref,
+ boolean has_explicit_return_type: boolean ref
+);
+
+lambda_capture(
+ unique int id: @lambdacapture,
+ int lambda: @lambdaexpr ref,
+ int index: int ref,
+ int field: @membervariable ref,
+ boolean captured_by_reference: boolean ref,
+ boolean is_implicit: boolean ref,
+ int location: @location_default ref
+);
+
+@funbindexpr = @routineexpr
+ | @new_expr
+ | @delete_expr
+ | @delete_array_expr
+ | @ctordirectinit
+ | @ctorvirtualinit
+ | @ctordelegatinginit
+ | @dtordirectdestruct
+ | @dtorvirtualdestruct;
+
+@varbindexpr = @varaccess | @ctorfieldinit | @dtorfielddestruct;
+@addressable = @function | @variable ;
+@accessible = @addressable | @enumconstant ;
+
+@access = @varaccess | @routineexpr ;
+
+fold(
+ int expr: @foldexpr ref,
+ string operator: string ref,
+ boolean is_left_fold: boolean ref
+);
+
+stmts(
+ unique int id: @stmt,
+ int kind: int ref,
+ int location: @location_stmt ref
+);
+
+case @stmt.kind of
+ 1 = @stmt_expr
+| 2 = @stmt_if
+| 3 = @stmt_while
+| 4 = @stmt_goto
+| 5 = @stmt_label
+| 6 = @stmt_return
+| 7 = @stmt_block
+| 8 = @stmt_end_test_while // do { ... } while ( ... )
+| 9 = @stmt_for
+| 10 = @stmt_switch_case
+| 11 = @stmt_switch
+| 13 = @stmt_asm // "asm" statement or the body of an asm function
+| 15 = @stmt_try_block
+| 16 = @stmt_microsoft_try // Microsoft
+| 17 = @stmt_decl
+| 18 = @stmt_set_vla_size // C99
+| 19 = @stmt_vla_decl // C99
+| 25 = @stmt_assigned_goto // GNU
+| 26 = @stmt_empty
+| 27 = @stmt_continue
+| 28 = @stmt_break
+| 29 = @stmt_range_based_for // C++11
+// ... 30 @stmt_at_autoreleasepool_block deprecated
+// ... 31 @stmt_objc_for_in deprecated
+// ... 32 @stmt_at_synchronized deprecated
+| 33 = @stmt_handler
+// ... 34 @stmt_finally_end deprecated
+| 35 = @stmt_constexpr_if
+| 37 = @stmt_co_return
+;
+
+type_vla(
+ int type_id: @type ref,
+ int decl: @stmt_vla_decl ref
+);
+
+variable_vla(
+ int var: @variable ref,
+ int decl: @stmt_vla_decl ref
+);
+
+if_then(
+ unique int if_stmt: @stmt_if ref,
+ int then_id: @stmt ref
+);
+
+if_else(
+ unique int if_stmt: @stmt_if ref,
+ int else_id: @stmt ref
+);
+
+constexpr_if_then(
+ unique int constexpr_if_stmt: @stmt_constexpr_if ref,
+ int then_id: @stmt ref
+);
+
+constexpr_if_else(
+ unique int constexpr_if_stmt: @stmt_constexpr_if ref,
+ int else_id: @stmt ref
+);
+
+while_body(
+ unique int while_stmt: @stmt_while ref,
+ int body_id: @stmt ref
+);
+
+do_body(
+ unique int do_stmt: @stmt_end_test_while ref,
+ int body_id: @stmt ref
+);
+
+#keyset[switch_stmt, index]
+switch_case(
+ int switch_stmt: @stmt_switch ref,
+ int index: int ref,
+ int case_id: @stmt_switch_case ref
+);
+
+switch_body(
+ unique int switch_stmt: @stmt_switch ref,
+ int body_id: @stmt ref
+);
+
+for_initialization(
+ unique int for_stmt: @stmt_for ref,
+ int init_id: @stmt ref
+);
+
+for_condition(
+ unique int for_stmt: @stmt_for ref,
+ int condition_id: @expr ref
+);
+
+for_update(
+ unique int for_stmt: @stmt_for ref,
+ int update_id: @expr ref
+);
+
+for_body(
+ unique int for_stmt: @stmt_for ref,
+ int body_id: @stmt ref
+);
+
+@stmtparent = @stmt | @expr_stmt ;
+stmtparents(
+ unique int id: @stmt ref,
+ int index: int ref,
+ int parent: @stmtparent ref
+);
+
+ishandler(unique int block: @stmt_block ref);
+
+@cfgnode = @stmt | @expr | @function | @initialiser ;
+
+stmt_decl_bind(
+ int stmt: @stmt_decl ref,
+ int num: int ref,
+ int decl: @declaration ref
+);
+
+stmt_decl_entry_bind(
+ int stmt: @stmt_decl ref,
+ int num: int ref,
+ int decl_entry: @element ref
+);
+
+@functionorblock = @function | @stmt_block;
+
+blockscope(
+ unique int block: @stmt_block ref,
+ int enclosing: @functionorblock ref
+);
+
+@jump = @stmt_goto | @stmt_break | @stmt_continue;
+
+@jumporlabel = @jump | @stmt_label | @literal;
+
+jumpinfo(
+ unique int id: @jumporlabel ref,
+ string str: string ref,
+ int target: @stmt ref
+);
+
+preprocdirects(
+ unique int id: @preprocdirect,
+ int kind: int ref,
+ int location: @location_default ref
+);
+case @preprocdirect.kind of
+ 0 = @ppd_if
+| 1 = @ppd_ifdef
+| 2 = @ppd_ifndef
+| 3 = @ppd_elif
+| 4 = @ppd_else
+| 5 = @ppd_endif
+| 6 = @ppd_plain_include
+| 7 = @ppd_define
+| 8 = @ppd_undef
+| 9 = @ppd_line
+| 10 = @ppd_error
+| 11 = @ppd_pragma
+| 12 = @ppd_objc_import
+| 13 = @ppd_include_next
+| 18 = @ppd_warning
+;
+
+@ppd_include = @ppd_plain_include | @ppd_objc_import | @ppd_include_next;
+
+@ppd_branch = @ppd_if | @ppd_ifdef | @ppd_ifndef | @ppd_elif;
+
+preprocpair(
+ int begin : @ppd_branch ref,
+ int elseelifend : @preprocdirect ref
+);
+
+preproctrue(int branch : @ppd_branch ref);
+preprocfalse(int branch : @ppd_branch ref);
+
+preproctext(
+ unique int id: @preprocdirect ref,
+ string head: string ref,
+ string body: string ref
+);
+
+includes(
+ unique int id: @ppd_include ref,
+ int included: @file ref
+);
+
+link_targets(
+ unique int id: @link_target,
+ int binary: @file ref
+);
+
+link_parent(
+ int element : @element ref,
+ int link_target : @link_target ref
+);
+
+/* XML Files */
+
+xmlEncoding(unique int id: @file ref, string encoding: string ref);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ string root: string ref,
+ string publicId: string ref,
+ string systemId: string ref,
+ int fileid: @file ref
+);
+
+xmlElements(
+ unique int id: @xmlelement,
+ string name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ string name: string ref,
+ string value: string ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlNs(
+ int id: @xmlnamespace,
+ string prefixName: string ref,
+ string URI: string ref,
+ int fileid: @file ref
+);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref
+);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref
+);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref
+);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref
+);
+
+@xmllocatable = @xmlcharacters
+ | @xmlelement
+ | @xmlcomment
+ | @xmlattribute
+ | @xmldtd
+ | @file
+ | @xmlnamespace;
diff --git a/cpp/upgrades/c82db4c596b8979eba9a8958e24353a5756d7a02/semmlecode.cpp.dbscheme b/cpp/upgrades/c82db4c596b8979eba9a8958e24353a5756d7a02/semmlecode.cpp.dbscheme
new file mode 100644
index 00000000000..ddd31fd02e5
--- /dev/null
+++ b/cpp/upgrades/c82db4c596b8979eba9a8958e24353a5756d7a02/semmlecode.cpp.dbscheme
@@ -0,0 +1,2145 @@
+
+/**
+ * An invocation of the compiler. Note that more than one file may be
+ * compiled per invocation. For example, this command compiles three
+ * source files:
+ *
+ * gcc -c f1.c f2.c f3.c
+ *
+ * The `id` simply identifies the invocation, while `cwd` is the working
+ * directory from which the compiler was invoked.
+ */
+compilations(
+ /**
+ * An invocation of the compiler. Note that more than one file may
+ * be compiled per invocation. For example, this command compiles
+ * three source files:
+ *
+ * gcc -c f1.c f2.c f3.c
+ */
+ unique int id : @compilation,
+ string cwd : string ref
+);
+
+/**
+ * The arguments that were passed to the extractor for a compiler
+ * invocation. If `id` is for the compiler invocation
+ *
+ * gcc -c f1.c f2.c f3.c
+ *
+ * then typically there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | *path to extractor*
+ * 1 | `--mimic`
+ * 2 | `/usr/bin/gcc`
+ * 3 | `-c`
+ * 4 | f1.c
+ * 5 | f2.c
+ * 6 | f3.c
+ */
+#keyset[id, num]
+compilation_args(
+ int id : @compilation ref,
+ int num : int ref,
+ string arg : string ref
+);
+
+/**
+ * The source files that are compiled by a compiler invocation.
+ * If `id` is for the compiler invocation
+ *
+ * gcc -c f1.c f2.c f3.c
+ *
+ * then there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | f1.c
+ * 1 | f2.c
+ * 2 | f3.c
+ *
+ * Note that even if those files `#include` headers, those headers
+ * do not appear as rows.
+ */
+#keyset[id, num]
+compilation_compiling_files(
+ int id : @compilation ref,
+ int num : int ref,
+ int file : @file ref
+);
+
+/**
+ * The time taken by the extractor for a compiler invocation.
+ *
+ * For each file `num`, there will be rows for
+ *
+ * kind | seconds
+ * ---- | ---
+ * 1 | CPU seconds used by the extractor frontend
+ * 2 | Elapsed seconds during the extractor frontend
+ * 3 | CPU seconds used by the extractor backend
+ * 4 | Elapsed seconds during the extractor backend
+ */
+#keyset[id, num, kind]
+compilation_time(
+ int id : @compilation ref,
+ int num : int ref,
+ /* kind:
+ 1 = frontend_cpu_seconds
+ 2 = frontend_elapsed_seconds
+ 3 = extractor_cpu_seconds
+ 4 = extractor_elapsed_seconds
+ */
+ int kind : int ref,
+ float seconds : float ref
+);
+
+/**
+ * An error or warning generated by the extractor.
+ * The diagnostic message `diagnostic` was generated during compiler
+ * invocation `compilation`, and is the `file_number_diagnostic_number`th
+ * message generated while extracting the `file_number`th file of that
+ * invocation.
+ */
+#keyset[compilation, file_number, file_number_diagnostic_number]
+diagnostic_for(
+ int diagnostic : @diagnostic ref,
+ int compilation : @compilation ref,
+ int file_number : int ref,
+ int file_number_diagnostic_number : int ref
+);
+
+/**
+ * If extraction was successful, then `cpu_seconds` and
+ * `elapsed_seconds` are the CPU time and elapsed time (respectively)
+ * that extraction took for compiler invocation `id`.
+ */
+compilation_finished(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref
+);
+
+
+/**
+ * External data, loaded from CSV files during snapshot creation. See
+ * [Tutorial: Incorporating external data](https://help.semmle.com/wiki/display/SD/Tutorial%3A+Incorporating+external+data)
+ * for more information.
+ */
+externalData(
+ int id : @externalDataElement,
+ string path : string ref,
+ int column: int ref,
+ string value : string ref
+);
+
+/**
+ * The date of the snapshot.
+ */
+snapshotDate(unique date snapshotDate : date ref);
+
+/**
+ * The source location of the snapshot.
+ */
+sourceLocationPrefix(string prefix : string ref);
+
+/**
+ * Data used by the 'duplicate code' detection.
+ */
+duplicateCode(
+ unique int id : @duplication,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+/**
+ * Data used by the 'similar code' detection.
+ */
+similarCode(
+ unique int id : @similarity,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+/**
+ * Data used by the 'duplicate code' and 'similar code' detection.
+ */
+@duplication_or_similarity = @duplication | @similarity
+
+/**
+ * Data used by the 'duplicate code' and 'similar code' detection.
+ */
+#keyset[id, offset]
+tokens(
+ int id : @duplication_or_similarity ref,
+ int offset : int ref,
+ int beginLine : int ref,
+ int beginColumn : int ref,
+ int endLine : int ref,
+ int endColumn : int ref
+);
+
+/**
+ * Information about packages that provide code used during compilation.
+ * The `id` is just a unique identifier.
+ * The `namespace` is typically the name of the package manager that
+ * provided the package (e.g. "dpkg" or "yum").
+ * The `package_name` is the name of the package, and `version` is its
+ * version (as a string).
+ */
+external_packages(
+ unique int id: @external_package,
+ string namespace : string ref,
+ string package_name : string ref,
+ string version : string ref
+);
+
+/**
+ * Holds if File `fileid` was provided by package `package`.
+ */
+header_to_external_package(
+ int fileid : @file ref,
+ int package : @external_package ref
+);
+
+/*
+ * Version history
+ */
+
+svnentries(
+ unique int id : @svnentry,
+ string revision : string ref,
+ string author : string ref,
+ date revisionDate : date ref,
+ int changeSize : int ref
+)
+
+svnaffectedfiles(
+ int id : @svnentry ref,
+ int file : @file ref,
+ string action : string ref
+)
+
+svnentrymsg(
+ unique int id : @svnentry ref,
+ string message : string ref
+)
+
+svnchurn(
+ int commit : @svnentry ref,
+ int file : @file ref,
+ int addedLines : int ref,
+ int deletedLines : int ref
+)
+
+/*
+ * C++ dbscheme
+ */
+
+@location = @location_stmt | @location_expr | @location_default ;
+
+/**
+ * The location of an element that is not an expression or a statement.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `file`.
+ * For more information, see
+ * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
+ */
+locations_default(
+ /** The location of an element that is not an expression or a statement. */
+ unique int id: @location_default,
+ int container: @container ref,
+ int startLine: int ref,
+ int startColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+/**
+ * The location of a statement.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `file`.
+ * For more information, see
+ * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
+ */
+locations_stmt(
+ /** The location of a statement. */
+ unique int id: @location_stmt,
+ int container: @container ref,
+ int startLine: int ref,
+ int startColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+/**
+ * The location of an expression.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `file`.
+ * For more information, see
+ * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
+ */
+locations_expr(
+ /** The location of an expression. */
+ unique int id: @location_expr,
+ int container: @container ref,
+ int startLine: int ref,
+ int startColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+/** An element for which line-count information is available. */
+@sourceline = @file | @function | @variable | @enumconstant | @xmllocatable;
+
+numlines(
+ int element_id: @sourceline ref,
+ int num_lines: int ref,
+ int num_code: int ref,
+ int num_comment: int ref
+);
+
+diagnostics(
+ unique int id: @diagnostic,
+ int severity: int ref,
+ string error_tag: string ref,
+ string error_message: string ref,
+ string full_error_message: string ref,
+ int location: @location_default ref
+);
+
+/*
+ fromSource(0) = unknown,
+ fromSource(1) = from source,
+ fromSource(2) = from library
+*/
+files(
+ unique int id: @file,
+ string name: string ref,
+ string simple: string ref,
+ string ext: string ref,
+ int fromSource: int ref
+);
+
+folders(
+ unique int id: @folder,
+ string name: string ref,
+ string simple: string ref
+);
+
+@container = @folder | @file
+
+containerparent(
+ int parent: @container ref,
+ unique int child: @container ref
+);
+
+fileannotations(
+ int id: @file ref,
+ int kind: int ref,
+ string name: string ref,
+ string value: string ref
+);
+
+inmacroexpansion(
+ int id: @element ref,
+ int inv: @macroinvocation ref
+);
+
+affectedbymacroexpansion(
+ int id: @element ref,
+ int inv: @macroinvocation ref
+);
+
+/*
+ case @macroinvocations.kind of
+ 1 = macro expansion
+ | 2 = other macro reference
+ ;
+*/
+macroinvocations(
+ unique int id: @macroinvocation,
+ int macro_id: @ppd_define ref,
+ int location: @location_default ref,
+ int kind: int ref
+);
+
+macroparent(
+ unique int id: @macroinvocation ref,
+ int parent_id: @macroinvocation ref
+);
+
+// a macroinvocation may be part of another location
+// the way to find a constant expression that uses a macro
+// is thus to find a constant expression that has a location
+// to which a macro invocation is bound
+macrolocationbind(
+ int id: @macroinvocation ref,
+ int location: @location ref
+);
+
+#keyset[invocation, argument_index]
+macro_argument_unexpanded(
+ int invocation: @macroinvocation ref,
+ int argument_index: int ref,
+ string text: string ref
+);
+
+#keyset[invocation, argument_index]
+macro_argument_expanded(
+ int invocation: @macroinvocation ref,
+ int argument_index: int ref,
+ string text: string ref
+);
+
+/*
+ case @function.kind of
+ 1 = normal
+ | 2 = constructor
+ | 3 = destructor
+ | 4 = conversion
+ | 5 = operator
+ | 6 = builtin // GCC built-in functions, e.g. __builtin___memcpy_chk
+ ;
+*/
+functions(
+ unique int id: @function,
+ string name: string ref,
+ int kind: int ref
+);
+
+function_entry_point(int id: @function ref, unique int entry_point: @stmt ref);
+
+function_return_type(int id: @function ref, int return_type: @type ref);
+
+/** If `function` is a coroutine, then this gives the
+ std::experimental::resumable_traits instance associated with it,
+ and the variables representing the `handle` and `promise` for it. */
+coroutine(
+ unique int function: @function ref,
+ int traits: @type ref,
+ int handle: @variable ref,
+ int promise: @variable ref
+);
+
+/** The `new` function used for allocating the coroutine state, if any. */
+coroutine_new(
+ unique int function: @function ref,
+ int new: @function ref
+);
+
+/** The `delete` function used for deallocating the coroutine state, if any. */
+coroutine_delete(
+ unique int function: @function ref,
+ int delete: @function ref
+);
+
+purefunctions(unique int id: @function ref);
+
+function_deleted(unique int id: @function ref);
+
+function_defaulted(unique int id: @function ref);
+
+member_function_this_type(unique int id: @function ref, int this_type: @type ref);
+
+#keyset[id, type_id]
+fun_decls(
+ int id: @fun_decl,
+ int function: @function ref,
+ int type_id: @type ref,
+ string name: string ref,
+ int location: @location_default ref
+);
+fun_def(unique int id: @fun_decl ref);
+fun_specialized(unique int id: @fun_decl ref);
+fun_implicit(unique int id: @fun_decl ref);
+fun_decl_specifiers(
+ int id: @fun_decl ref,
+ string name: string ref
+)
+#keyset[fun_decl, index]
+fun_decl_throws(
+ int fun_decl: @fun_decl ref,
+ int index: int ref,
+ int type_id: @type ref
+);
+/* an empty throw specification is different from none */
+fun_decl_empty_throws(unique int fun_decl: @fun_decl ref);
+fun_decl_noexcept(
+ int fun_decl: @fun_decl ref,
+ int constant: @expr ref
+);
+fun_decl_empty_noexcept(int fun_decl: @fun_decl ref);
+fun_decl_typedef_type(
+ unique int fun_decl: @fun_decl ref,
+ int typedeftype_id: @usertype ref
+);
+
+param_decl_bind(
+ unique int id: @var_decl ref,
+ int index: int ref,
+ int fun_decl: @fun_decl ref
+);
+
+#keyset[id, type_id]
+var_decls(
+ int id: @var_decl,
+ int variable: @variable ref,
+ int type_id: @type ref,
+ string name: string ref,
+ int location: @location_default ref
+);
+var_def(unique int id: @var_decl ref);
+var_decl_specifiers(
+ int id: @var_decl ref,
+ string name: string ref
+)
+
+type_decls(
+ unique int id: @type_decl,
+ int type_id: @type ref,
+ int location: @location_default ref
+);
+type_def(unique int id: @type_decl ref);
+type_decl_top(
+ unique int type_decl: @type_decl ref
+);
+
+namespace_decls(
+ unique int id: @namespace_decl,
+ int namespace_id: @namespace ref,
+ int location: @location_default ref,
+ int bodylocation: @location_default ref
+);
+
+usings(
+ unique int id: @using,
+ int element_id: @element ref,
+ int location: @location_default ref
+);
+
+/** The element which contains the `using` declaration. */
+using_container(
+ int parent: @element ref,
+ int child: @using ref
+);
+
+static_asserts(
+ unique int id: @static_assert,
+ int condition : @expr ref,
+ string message : string ref,
+ int location: @location_default ref,
+ int enclosing : @element ref
+);
+
+// each function has an ordered list of parameters
+#keyset[id, type_id]
+#keyset[function, index, type_id]
+params(
+ int id: @parameter,
+ int function: @functionorblock ref,
+ int index: int ref,
+ int type_id: @type ref
+);
+
+overrides(int new: @function ref, int old: @function ref);
+
+#keyset[id, type_id]
+membervariables(
+ int id: @membervariable,
+ int type_id: @type ref,
+ string name: string ref
+);
+
+#keyset[id, type_id]
+globalvariables(
+ int id: @globalvariable,
+ int type_id: @type ref,
+ string name: string ref
+);
+
+#keyset[id, type_id]
+localvariables(
+ int id: @localvariable,
+ int type_id: @type ref,
+ string name: string ref
+);
+
+autoderivation(
+ unique int var: @variable ref,
+ int derivation_type: @type ref
+);
+
+enumconstants(
+ unique int id: @enumconstant,
+ int parent: @usertype ref,
+ int index: int ref,
+ int type_id: @type ref,
+ string name: string ref,
+ int location: @location_default ref
+);
+
+@variable = @localscopevariable | @globalvariable | @membervariable;
+
+@localscopevariable = @localvariable | @parameter;
+
+/*
+ Built-in types are the fundamental types, e.g., integral, floating, and void.
+
+ case @builtintype.kind of
+ 1 = error
+ | 2 = unknown
+ | 3 = void
+ | 4 = boolean
+ | 5 = char
+ | 6 = unsigned_char
+ | 7 = signed_char
+ | 8 = short
+ | 9 = unsigned_short
+ | 10 = signed_short
+ | 11 = int
+ | 12 = unsigned_int
+ | 13 = signed_int
+ | 14 = long
+ | 15 = unsigned_long
+ | 16 = signed_long
+ | 17 = long_long
+ | 18 = unsigned_long_long
+ | 19 = signed_long_long
+ | 20 = __int8 // Microsoft-specific
+ | 21 = __int16 // Microsoft-specific
+ | 22 = __int32 // Microsoft-specific
+ | 23 = __int64 // Microsoft-specific
+ | 24 = float
+ | 25 = double
+ | 26 = long_double
+ | 27 = _Complex_float // C99-specific
+ | 28 = _Complex_double // C99-specific
+ | 29 = _Complex_long double // C99-specific
+ | 30 = _Imaginary_float // C99-specific
+ | 31 = _Imaginary_double // C99-specific
+ | 32 = _Imaginary_long_double // C99-specific
+ | 33 = wchar_t // Microsoft-specific
+ | 34 = decltype_nullptr // C++11
+ | 35 = __int128
+ | 36 = unsigned___int128
+ | 37 = signed___int128
+ | 38 = __float128
+ | 39 = _Complex___float128
+ | 40 = _Decimal32
+ | 41 = _Decimal64
+ | 42 = _Decimal128
+ | 43 = char16_t
+ | 44 = char32_t
+ | 45 = _Float32
+ | 46 = _Float32x
+ | 47 = _Float64
+ | 48 = _Float64x
+ | 49 = _Float128
+ | 50 = _Float128x
+ | 51 = char8_t
+ ;
+*/
+builtintypes(
+ unique int id: @builtintype,
+ string name: string ref,
+ int kind: int ref,
+ int size: int ref,
+ int sign: int ref,
+ int alignment: int ref
+);
+
+/*
+ Derived types are types that are directly derived from existing types and
+ point to, refer to, transform type data to return a new type.
+
+ case @derivedtype.kind of
+ 1 = pointer
+ | 2 = reference
+ | 3 = type_with_specifiers
+ | 4 = array
+ | 5 = gnu_vector
+ | 6 = routineptr
+ | 7 = routinereference
+ | 8 = rvalue_reference // C++11
+// ... 9 type_conforming_to_protocols deprecated
+ | 10 = block
+ ;
+*/
+derivedtypes(
+ unique int id: @derivedtype,
+ string name: string ref,
+ int kind: int ref,
+ int type_id: @type ref
+);
+
+pointerishsize(unique int id: @derivedtype ref,
+ int size: int ref,
+ int alignment: int ref);
+
+arraysizes(
+ unique int id: @derivedtype ref,
+ int num_elements: int ref,
+ int bytesize: int ref,
+ int alignment: int ref
+);
+
+typedefbase(
+ unique int id: @usertype ref,
+ int type_id: @type ref
+);
+
+/**
+ * An instance of the C++11 `decltype` operator. For example:
+ * ```
+ * int a;
+ * decltype(1+a) b;
+ * ```
+ * Here `expr` is `1+a`.
+ *
+ * Sometimes an additional pair of parentheses around the expression
+ * would change the semantics of this decltype, e.g.
+ * ```
+ * struct A { double x; };
+ * const A* a = new A();
+ * decltype( a->x ); // type is double
+ * decltype((a->x)); // type is const double&
+ * ```
+ * (Please consult the C++11 standard for more details).
+ * `parentheses_would_change_meaning` is `true` iff that is the case.
+ */
+#keyset[id, expr]
+decltypes(
+ int id: @decltype,
+ int expr: @expr ref,
+ int base_type: @type ref,
+ boolean parentheses_would_change_meaning: boolean ref
+);
+
+/*
+ case @usertype.kind of
+ 1 = struct
+ | 2 = class
+ | 3 = union
+ | 4 = enum
+ | 5 = typedef // classic C: typedef typedef type name
+ | 6 = template
+ | 7 = template_parameter
+ | 8 = template_template_parameter
+ | 9 = proxy_class // a proxy class associated with a template parameter
+// ... 10 objc_class deprecated
+// ... 11 objc_protocol deprecated
+// ... 12 objc_category deprecated
+ | 13 = scoped_enum
+ | 14 = using_alias // a using name = type style typedef
+ ;
+*/
+usertypes(
+ unique int id: @usertype,
+ string name: string ref,
+ int kind: int ref
+);
+
+usertypesize(
+ unique int id: @usertype ref,
+ int size: int ref,
+ int alignment: int ref
+);
+
+usertype_final(unique int id: @usertype ref);
+
+usertype_uuid(
+ unique int id: @usertype ref,
+ unique string uuid: string ref
+);
+
+mangled_name(
+ unique int id: @declaration ref,
+ int mangled_name : @mangledname
+);
+
+is_pod_class(unique int id: @usertype ref);
+is_standard_layout_class(unique int id: @usertype ref);
+
+is_complete(unique int id: @usertype ref);
+
+is_class_template(unique int id: @usertype ref);
+class_instantiation(
+ int to: @usertype ref,
+ int from: @usertype ref
+);
+class_template_argument(
+ int type_id: @usertype ref,
+ int index: int ref,
+ int arg_type: @type ref
+);
+class_template_argument_value(
+ int type_id: @usertype ref,
+ int index: int ref,
+ int arg_value: @expr ref
+);
+
+is_proxy_class_for(
+ unique int id: @usertype ref,
+ unique int templ_param_id: @usertype ref
+);
+
+type_mentions(
+ unique int id: @type_mention,
+ int type_id: @type ref,
+ int location: @location ref,
+ // a_symbol_reference_kind from the EDG frontend. See symbol_ref.h there.
+ int kind: int ref
+);
+
+is_function_template(unique int id: @function ref);
+function_instantiation(
+ unique int to: @function ref,
+ int from: @function ref
+);
+function_template_argument(
+ int function_id: @function ref,
+ int index: int ref,
+ int arg_type: @type ref
+);
+function_template_argument_value(
+ int function_id: @function ref,
+ int index: int ref,
+ int arg_value: @expr ref
+);
+
+is_variable_template(unique int id: @variable ref);
+variable_instantiation(
+ unique int to: @variable ref,
+ int from: @variable ref
+);
+variable_template_argument(
+ int variable_id: @variable ref,
+ int index: int ref,
+ int arg_type: @type ref
+);
+variable_template_argument_value(
+ int variable_id: @variable ref,
+ int index: int ref,
+ int arg_value: @expr ref
+);
+
+/*
+ Fixed point types
+ precision(1) = short, precision(2) = default, precision(3) = long
+ is_unsigned(1) = unsigned is_unsigned(2) = signed
+ is_fract_type(1) = declared with _Fract
+ saturating(1) = declared with _Sat
+*/
+/* TODO
+fixedpointtypes(
+ unique int id: @fixedpointtype,
+ int precision: int ref,
+ int is_unsigned: int ref,
+ int is_fract_type: int ref,
+ int saturating: int ref);
+*/
+
+routinetypes(
+ unique int id: @routinetype,
+ int return_type: @type ref
+);
+
+routinetypeargs(
+ int routine: @routinetype ref,
+ int index: int ref,
+ int type_id: @type ref
+);
+
+ptrtomembers(
+ unique int id: @ptrtomember,
+ int type_id: @type ref,
+ int class_id: @type ref
+);
+
+/*
+ specifiers for types, functions, and variables
+
+ "public",
+ "protected",
+ "private",
+
+ "const",
+ "volatile",
+ "static",
+
+ "pure",
+ "virtual",
+ "sealed", // Microsoft
+ "__interface", // Microsoft
+ "inline",
+ "explicit",
+
+ "near", // near far extension
+ "far", // near far extension
+ "__ptr32", // Microsoft
+ "__ptr64", // Microsoft
+ "__sptr", // Microsoft
+ "__uptr", // Microsoft
+ "dllimport", // Microsoft
+ "dllexport", // Microsoft
+ "thread", // Microsoft
+ "naked", // Microsoft
+ "microsoft_inline", // Microsoft
+ "forceinline", // Microsoft
+ "selectany", // Microsoft
+ "nothrow", // Microsoft
+ "novtable", // Microsoft
+ "noreturn", // Microsoft
+ "noinline", // Microsoft
+ "noalias", // Microsoft
+ "restrict", // Microsoft
+*/
+
+specifiers(
+ unique int id: @specifier,
+ unique string str: string ref
+);
+
+typespecifiers(
+ int type_id: @type ref,
+ int spec_id: @specifier ref
+);
+
+funspecifiers(
+ int func_id: @function ref,
+ int spec_id: @specifier ref
+);
+
+varspecifiers(
+ int var_id: @accessible ref,
+ int spec_id: @specifier ref
+);
+
+attributes(
+ unique int id: @attribute,
+ int kind: int ref,
+ string name: string ref,
+ string name_space: string ref,
+ int location: @location_default ref
+);
+
+case @attribute.kind of
+ 0 = @gnuattribute
+| 1 = @stdattribute
+| 2 = @declspec
+| 3 = @msattribute
+| 4 = @alignas
+// ... 5 @objc_propertyattribute deprecated
+;
+
+attribute_args(
+ unique int id: @attribute_arg,
+ int kind: int ref,
+ int attribute: @attribute ref,
+ int index: int ref,
+ int location: @location_default ref
+);
+
+case @attribute_arg.kind of
+ 0 = @attribute_arg_empty
+| 1 = @attribute_arg_token
+| 2 = @attribute_arg_constant
+| 3 = @attribute_arg_type
+;
+
+attribute_arg_value(
+ unique int arg: @attribute_arg ref,
+ string value: string ref
+);
+attribute_arg_type(
+ unique int arg: @attribute_arg ref,
+ int type_id: @type ref
+);
+attribute_arg_name(
+ unique int arg: @attribute_arg ref,
+ string name: string ref
+);
+
+typeattributes(
+ int type_id: @type ref,
+ int spec_id: @attribute ref
+);
+
+funcattributes(
+ int func_id: @function ref,
+ int spec_id: @attribute ref
+);
+
+varattributes(
+ int var_id: @accessible ref,
+ int spec_id: @attribute ref
+);
+
+stmtattributes(
+ int stmt_id: @stmt ref,
+ int spec_id: @attribute ref
+);
+
+@type = @builtintype
+ | @derivedtype
+ | @usertype
+ /* TODO | @fixedpointtype */
+ | @routinetype
+ | @ptrtomember
+ | @decltype;
+
+unspecifiedtype(
+ unique int type_id: @type ref,
+ int unspecified_type_id: @type ref
+);
+
+member(
+ int parent: @type ref,
+ int index: int ref,
+ int child: @member ref
+);
+
+@enclosingfunction_child = @usertype | @variable | @namespace
+
+enclosingfunction(
+ unique int child: @enclosingfunction_child ref,
+ int parent: @function ref
+);
+
+derivations(
+ unique int derivation: @derivation,
+ int sub: @type ref,
+ int index: int ref,
+ int super: @type ref,
+ int location: @location_default ref
+);
+
+derspecifiers(
+ int der_id: @derivation ref,
+ int spec_id: @specifier ref
+);
+
+/**
+ * Contains the byte offset of the base class subobject within the derived
+ * class. Only holds for non-virtual base classes, but see table
+ * `virtual_base_offsets` for offsets of virtual base class subobjects.
+ */
+direct_base_offsets(
+ unique int der_id: @derivation ref,
+ int offset: int ref
+);
+
+/**
+ * Contains the byte offset of the virtual base class subobject for class
+ * `super` within a most-derived object of class `sub`. `super` can be either a
+ * direct or indirect base class.
+ */
+#keyset[sub, super]
+virtual_base_offsets(
+ int sub: @usertype ref,
+ int super: @usertype ref,
+ int offset: int ref
+);
+
+frienddecls(
+ unique int id: @frienddecl,
+ int type_id: @type ref,
+ int decl_id: @declaration ref,
+ int location: @location_default ref
+);
+
+@declaredtype = @usertype ;
+
+@declaration = @function
+ | @declaredtype
+ | @variable
+ | @enumconstant
+ | @frienddecl;
+
+@member = @membervariable
+ | @function
+ | @declaredtype
+ | @enumconstant;
+
+@locatable = @diagnostic
+ | @declaration
+ | @ppd_include
+ | @ppd_define
+ | @macroinvocation
+ /*| @funcall*/
+ | @xmllocatable
+ | @attribute
+ | @attribute_arg;
+
+@namedscope = @namespace | @usertype;
+
+@element = @locatable
+ | @file
+ | @folder
+ | @specifier
+ | @type
+ | @expr
+ | @namespace
+ | @initialiser
+ | @stmt
+ | @derivation
+ | @comment
+ | @preprocdirect
+ | @fun_decl
+ | @var_decl
+ | @type_decl
+ | @namespace_decl
+ | @using
+ | @namequalifier
+ | @specialnamequalifyingelement
+ | @static_assert
+ | @type_mention
+ | @lambdacapture;
+
+@exprparent = @element;
+
+comments(
+ unique int id: @comment,
+ string contents: string ref,
+ int location: @location_default ref
+);
+
+commentbinding(
+ int id: @comment ref,
+ int element: @element ref
+);
+
+exprconv(
+ int converted: @expr ref,
+ unique int conversion: @expr ref
+);
+
+compgenerated(unique int id: @element ref);
+
+/**
+ * `destructor_call` destructs the `i`'th entity that should be
+ * destructed following `element`. Note that entities should be
+ * destructed in reverse construction order, so for a given `element`
+ * these should be called from highest to lowest `i`.
+ */
+#keyset[element, destructor_call]
+#keyset[element, i]
+synthetic_destructor_call(
+ int element: @element ref,
+ int i: int ref,
+ int destructor_call: @routineexpr ref
+);
+
+namespaces(
+ unique int id: @namespace,
+ string name: string ref
+);
+
+namespace_inline(
+ unique int id: @namespace ref
+);
+
+namespacembrs(
+ int parentid: @namespace ref,
+ unique int memberid: @namespacembr ref
+);
+
+@namespacembr = @declaration | @namespace;
+
+exprparents(
+ int expr_id: @expr ref,
+ int child_index: int ref,
+ int parent_id: @exprparent ref
+);
+
+expr_isload(unique int expr_id: @expr ref);
+
+@cast = @c_style_cast
+ | @const_cast
+ | @dynamic_cast
+ | @reinterpret_cast
+ | @static_cast
+ ;
+
+/*
+case @conversion.kind of
+ 0 = @simple_conversion // a numeric conversion, qualification conversion, or a reinterpret_cast
+| 1 = @bool_conversion // conversion to 'bool'
+| 2 = @base_class_conversion // a derived-to-base conversion
+| 3 = @derived_class_conversion // a base-to-derived conversion
+| 4 = @pm_base_class_conversion // a derived-to-base conversion of a pointer to member
+| 5 = @pm_derived_class_conversion // a base-to-derived conversion of a pointer to member
+| 6 = @glvalue_adjust // an adjustment of the type of a glvalue
+| 7 = @prvalue_adjust // an adjustment of the type of a prvalue
+;
+*/
+/**
+ * Describes the semantics represented by a cast expression. This is largely
+ * independent of the source syntax of the cast, so it is separate from the
+ * regular expression kind.
+ */
+conversionkinds(
+ unique int expr_id: @cast ref,
+ int kind: int ref
+);
+
+@conversion = @cast
+ | @array_to_pointer
+ | @parexpr
+ | @reference_to
+ | @ref_indirect
+ | @temp_init
+ ;
+
+/*
+case @funbindexpr.kind of
+ 0 = @normal_call // a normal call
+| 1 = @virtual_call // a virtual call
+| 2 = @adl_call // a call whose target is only found by ADL
+;
+*/
+iscall(unique int caller: @funbindexpr ref, int kind: int ref);
+
+numtemplatearguments(
+ unique int expr_id: @expr ref,
+ int num: int ref
+);
+
+specialnamequalifyingelements(
+ unique int id: @specialnamequalifyingelement,
+ unique string name: string ref
+);
+
+@namequalifiableelement = @expr | @namequalifier;
+@namequalifyingelement = @namespace
+ | @specialnamequalifyingelement
+ | @usertype;
+
+namequalifiers(
+ unique int id: @namequalifier,
+ unique int qualifiableelement: @namequalifiableelement ref,
+ int qualifyingelement: @namequalifyingelement ref,
+ int location: @location_default ref
+);
+
+varbind(
+ int expr: @varbindexpr ref,
+ int var: @accessible ref
+);
+
+funbind(
+ int expr: @funbindexpr ref,
+ int fun: @function ref
+);
+
+@any_new_expr = @new_expr
+ | @new_array_expr;
+
+@new_or_delete_expr = @any_new_expr
+ | @delete_expr
+ | @delete_array_expr;
+
+@prefix_crement_expr = @preincrexpr | @predecrexpr;
+
+@postfix_crement_expr = @postincrexpr | @postdecrexpr;
+
+@increment_expr = @preincrexpr | @postincrexpr;
+
+@decrement_expr = @predecrexpr | @postdecrexpr;
+
+@crement_expr = @increment_expr | @decrement_expr;
+
+@un_arith_op_expr = @arithnegexpr
+ | @unaryplusexpr
+ | @conjugation
+ | @realpartexpr
+ | @imagpartexpr
+ | @crement_expr
+ ;
+
+@un_bitwise_op_expr = @complementexpr;
+
+@un_log_op_expr = @notexpr;
+
+@un_op_expr = @address_of
+ | @indirect
+ | @un_arith_op_expr
+ | @un_bitwise_op_expr
+ | @builtinaddressof
+ | @vec_fill
+ | @un_log_op_expr
+ | @co_await
+ | @co_yield
+ ;
+
+@bin_log_op_expr = @andlogicalexpr | @orlogicalexpr;
+
+@cmp_op_expr = @eq_op_expr | @rel_op_expr;
+
+@eq_op_expr = @eqexpr | @neexpr;
+
+@rel_op_expr = @gtexpr
+ | @ltexpr
+ | @geexpr
+ | @leexpr
+ | @spaceshipexpr
+ ;
+
+@bin_bitwise_op_expr = @lshiftexpr
+ | @rshiftexpr
+ | @andexpr
+ | @orexpr
+ | @xorexpr
+ ;
+
+@p_arith_op_expr = @paddexpr
+ | @psubexpr
+ | @pdiffexpr
+ ;
+
+@bin_arith_op_expr = @addexpr
+ | @subexpr
+ | @mulexpr
+ | @divexpr
+ | @remexpr
+ | @jmulexpr
+ | @jdivexpr
+ | @fjaddexpr
+ | @jfaddexpr
+ | @fjsubexpr
+ | @jfsubexpr
+ | @minexpr
+ | @maxexpr
+ | @p_arith_op_expr
+ ;
+
+@bin_op_expr = @bin_arith_op_expr
+ | @bin_bitwise_op_expr
+ | @cmp_op_expr
+ | @bin_log_op_expr
+ ;
+
+@op_expr = @un_op_expr
+ | @bin_op_expr
+ | @assign_expr
+ | @conditionalexpr
+ ;
+
+@assign_arith_expr = @assignaddexpr
+ | @assignsubexpr
+ | @assignmulexpr
+ | @assigndivexpr
+ | @assignremexpr
+ ;
+
+@assign_bitwise_expr = @assignandexpr
+ | @assignorexpr
+ | @assignxorexpr
+ | @assignlshiftexpr
+ | @assignrshiftexpr
+ | @assignpaddexpr
+ | @assignpsubexpr
+ ;
+
+@assign_op_expr = @assign_arith_expr | @assign_bitwise_expr
+
+@assign_expr = @assignexpr | @assign_op_expr
+
+/*
+ case @allocator.form of
+ 0 = plain
+ | 1 = alignment
+ ;
+*/
+
+/**
+ * The allocator function associated with a `new` or `new[]` expression.
+ * The `form` column specified whether the allocation call contains an alignment
+ * argument.
+ */
+expr_allocator(
+ unique int expr: @any_new_expr ref,
+ int func: @function ref,
+ int form: int ref
+);
+
+/*
+ case @deallocator.form of
+ 0 = plain
+ | 1 = size
+ | 2 = alignment
+ | 3 = size_and_alignment
+ ;
+*/
+
+/**
+ * The deallocator function associated with a `delete`, `delete[]`, `new`, or
+ * `new[]` expression. For a `new` or `new[]` expression, the deallocator is the
+ * one used to free memory if the initialization throws an exception.
+ * The `form` column specifies whether the deallocation call contains a size
+ * argument, and alignment argument, or both.
+ */
+expr_deallocator(
+ unique int expr: @new_or_delete_expr ref,
+ int func: @function ref,
+ int form: int ref
+);
+
+/**
+ * Holds if the `@conditionalexpr` is of the two operand form
+ * `guard ? : false`.
+ */
+expr_cond_two_operand(
+ unique int cond: @conditionalexpr ref
+);
+
+/**
+ * The guard of `@conditionalexpr` `guard ? true : false`
+ */
+expr_cond_guard(
+ unique int cond: @conditionalexpr ref,
+ int guard: @expr ref
+);
+
+/**
+ * The expression used when the guard of `@conditionalexpr`
+ * `guard ? true : false` holds. For the two operand form
+ * `guard ?: false` consider using `expr_cond_guard` instead.
+ */
+expr_cond_true(
+ unique int cond: @conditionalexpr ref,
+ int true: @expr ref
+);
+
+/**
+ * The expression used when the guard of `@conditionalexpr`
+ * `guard ? true : false` does not hold.
+ */
+expr_cond_false(
+ unique int cond: @conditionalexpr ref,
+ int false: @expr ref
+);
+
+/** A string representation of the value. */
+values(
+ unique int id: @value,
+ string str: string ref
+);
+
+/** The actual text in the source code for the value, if any. */
+valuetext(
+ unique int id: @value ref,
+ string text: string ref
+);
+
+valuebind(
+ int val: @value ref,
+ unique int expr: @expr ref
+);
+
+fieldoffsets(
+ unique int id: @variable ref,
+ int byteoffset: int ref,
+ int bitoffset: int ref
+);
+
+bitfield(
+ unique int id: @variable ref,
+ int bits: int ref,
+ int declared_bits: int ref
+);
+
+/* TODO
+memberprefix(
+ int member: @expr ref,
+ int prefix: @expr ref
+);
+*/
+
+/*
+ kind(1) = mbrcallexpr
+ kind(2) = mbrptrcallexpr
+ kind(3) = mbrptrmbrcallexpr
+ kind(4) = ptrmbrptrmbrcallexpr
+ kind(5) = mbrreadexpr // x.y
+ kind(6) = mbrptrreadexpr // p->y
+ kind(7) = mbrptrmbrreadexpr // x.*pm
+ kind(8) = mbrptrmbrptrreadexpr // x->*pm
+ kind(9) = staticmbrreadexpr // static x.y
+ kind(10) = staticmbrptrreadexpr // static p->y
+*/
+/* TODO
+memberaccess(
+ int member: @expr ref,
+ int kind: int ref
+);
+*/
+
+initialisers(
+ unique int init: @initialiser,
+ int var: @accessible ref,
+ unique int expr: @expr ref,
+ int location: @location_expr ref
+);
+
+/**
+ * An ancestor for the expression, for cases in which we cannot
+ * otherwise find the expression's parent.
+ */
+expr_ancestor(
+ int exp: @expr ref,
+ int ancestor: @element ref
+);
+
+exprs(
+ unique int id: @expr,
+ int kind: int ref,
+ int location: @location_expr ref
+);
+
+/*
+ case @value.category of
+ 1 = prval
+ | 2 = xval
+ | 3 = lval
+ ;
+*/
+expr_types(
+ int id: @expr ref,
+ int typeid: @type ref,
+ int value_category: int ref
+);
+
+case @expr.kind of
+ 1 = @errorexpr
+| 2 = @address_of // & AddressOfExpr
+| 3 = @reference_to // ReferenceToExpr (implicit?)
+| 4 = @indirect // * PointerDereferenceExpr
+| 5 = @ref_indirect // ReferenceDereferenceExpr (implicit?)
+// ...
+| 8 = @array_to_pointer // (???)
+| 9 = @vacuous_destructor_call // VacuousDestructorCall
+// ...
+| 11 = @assume // Microsoft
+| 12 = @parexpr
+| 13 = @arithnegexpr
+| 14 = @unaryplusexpr
+| 15 = @complementexpr
+| 16 = @notexpr
+| 17 = @conjugation // GNU ~ operator
+| 18 = @realpartexpr // GNU __real
+| 19 = @imagpartexpr // GNU __imag
+| 20 = @postincrexpr
+| 21 = @postdecrexpr
+| 22 = @preincrexpr
+| 23 = @predecrexpr
+| 24 = @conditionalexpr
+| 25 = @addexpr
+| 26 = @subexpr
+| 27 = @mulexpr
+| 28 = @divexpr
+| 29 = @remexpr
+| 30 = @jmulexpr // C99 mul imaginary
+| 31 = @jdivexpr // C99 div imaginary
+| 32 = @fjaddexpr // C99 add real + imaginary
+| 33 = @jfaddexpr // C99 add imaginary + real
+| 34 = @fjsubexpr // C99 sub real - imaginary
+| 35 = @jfsubexpr // C99 sub imaginary - real
+| 36 = @paddexpr // pointer add (pointer + int or int + pointer)
+| 37 = @psubexpr // pointer sub (pointer - integer)
+| 38 = @pdiffexpr // difference between two pointers
+| 39 = @lshiftexpr
+| 40 = @rshiftexpr
+| 41 = @andexpr
+| 42 = @orexpr
+| 43 = @xorexpr
+| 44 = @eqexpr
+| 45 = @neexpr
+| 46 = @gtexpr
+| 47 = @ltexpr
+| 48 = @geexpr
+| 49 = @leexpr
+| 50 = @minexpr // GNU minimum
+| 51 = @maxexpr // GNU maximum
+| 52 = @assignexpr
+| 53 = @assignaddexpr
+| 54 = @assignsubexpr
+| 55 = @assignmulexpr
+| 56 = @assigndivexpr
+| 57 = @assignremexpr
+| 58 = @assignlshiftexpr
+| 59 = @assignrshiftexpr
+| 60 = @assignandexpr
+| 61 = @assignorexpr
+| 62 = @assignxorexpr
+| 63 = @assignpaddexpr // assign pointer add
+| 64 = @assignpsubexpr // assign pointer sub
+| 65 = @andlogicalexpr
+| 66 = @orlogicalexpr
+| 67 = @commaexpr
+| 68 = @subscriptexpr // access to member of an array, e.g., a[5]
+// ... 69 @objc_subscriptexpr deprecated
+// ... 70 @cmdaccess deprecated
+// ...
+| 73 = @virtfunptrexpr
+| 74 = @callexpr
+// ... 75 @msgexpr_normal deprecated
+// ... 76 @msgexpr_super deprecated
+// ... 77 @atselectorexpr deprecated
+// ... 78 @atprotocolexpr deprecated
+| 79 = @vastartexpr
+| 80 = @vaargexpr
+| 81 = @vaendexpr
+| 82 = @vacopyexpr
+// ... 83 @atencodeexpr deprecated
+| 84 = @varaccess
+| 85 = @thisaccess
+// ... 86 @objc_box_expr deprecated
+| 87 = @new_expr
+| 88 = @delete_expr
+| 89 = @throw_expr
+| 90 = @condition_decl // a variable declared in a condition, e.g., if(int x = y > 2)
+| 91 = @braced_init_list
+| 92 = @type_id
+| 93 = @runtime_sizeof
+| 94 = @runtime_alignof
+| 95 = @sizeof_pack
+| 96 = @expr_stmt // GNU extension
+| 97 = @routineexpr
+| 98 = @type_operand // used to access a type in certain contexts (haven't found any examples yet....)
+| 99 = @offsetofexpr // offsetof ::= type and field
+| 100 = @hasassignexpr // __has_assign ::= type
+| 101 = @hascopyexpr // __has_copy ::= type
+| 102 = @hasnothrowassign // __has_nothrow_assign ::= type
+| 103 = @hasnothrowconstr // __has_nothrow_constructor ::= type
+| 104 = @hasnothrowcopy // __has_nothrow_copy ::= type
+| 105 = @hastrivialassign // __has_trivial_assign ::= type
+| 106 = @hastrivialconstr // __has_trivial_constructor ::= type
+| 107 = @hastrivialcopy // __has_trivial_copy ::= type
+| 108 = @hasuserdestr // __has_user_destructor ::= type
+| 109 = @hasvirtualdestr // __has_virtual_destructor ::= type
+| 110 = @isabstractexpr // __is_abstract ::= type
+| 111 = @isbaseofexpr // __is_base_of ::= type type
+| 112 = @isclassexpr // __is_class ::= type
+| 113 = @isconvtoexpr // __is_convertible_to ::= type type
+| 114 = @isemptyexpr // __is_empty ::= type
+| 115 = @isenumexpr // __is_enum ::= type
+| 116 = @ispodexpr // __is_pod ::= type
+| 117 = @ispolyexpr // __is_polymorphic ::= type
+| 118 = @isunionexpr // __is_union ::= type
+| 119 = @typescompexpr // GNU __builtin_types_compatible ::= type type
+| 120 = @intaddrexpr // EDG internal builtin, used to implement offsetof
+// ...
+| 122 = @hastrivialdestructor // __has_trivial_destructor ::= type
+| 123 = @literal
+| 124 = @uuidof
+| 127 = @aggregateliteral
+| 128 = @delete_array_expr
+| 129 = @new_array_expr
+// ... 130 @objc_array_literal deprecated
+// ... 131 @objc_dictionary_literal deprecated
+| 132 = @foldexpr
+// ...
+| 200 = @ctordirectinit
+| 201 = @ctorvirtualinit
+| 202 = @ctorfieldinit
+| 203 = @ctordelegatinginit
+| 204 = @dtordirectdestruct
+| 205 = @dtorvirtualdestruct
+| 206 = @dtorfielddestruct
+// ...
+| 210 = @static_cast
+| 211 = @reinterpret_cast
+| 212 = @const_cast
+| 213 = @dynamic_cast
+| 214 = @c_style_cast
+| 215 = @lambdaexpr
+| 216 = @param_ref
+| 217 = @noopexpr
+// ...
+| 294 = @istriviallyconstructibleexpr
+| 295 = @isdestructibleexpr
+| 296 = @isnothrowdestructibleexpr
+| 297 = @istriviallydestructibleexpr
+| 298 = @istriviallyassignableexpr
+| 299 = @isnothrowassignableexpr
+| 300 = @istrivialexpr
+| 301 = @isstandardlayoutexpr
+| 302 = @istriviallycopyableexpr
+| 303 = @isliteraltypeexpr
+| 304 = @hastrivialmoveconstructorexpr
+| 305 = @hastrivialmoveassignexpr
+| 306 = @hasnothrowmoveassignexpr
+| 307 = @isconstructibleexpr
+| 308 = @isnothrowconstructibleexpr
+| 309 = @hasfinalizerexpr
+| 310 = @isdelegateexpr
+| 311 = @isinterfaceclassexpr
+| 312 = @isrefarrayexpr
+| 313 = @isrefclassexpr
+| 314 = @issealedexpr
+| 315 = @issimplevalueclassexpr
+| 316 = @isvalueclassexpr
+| 317 = @isfinalexpr
+| 319 = @noexceptexpr
+| 320 = @builtinshufflevector
+| 321 = @builtinchooseexpr
+| 322 = @builtinaddressof
+| 323 = @vec_fill
+| 324 = @builtinconvertvector
+| 325 = @builtincomplex
+| 326 = @spaceshipexpr
+| 327 = @co_await
+| 328 = @co_yield
+| 329 = @temp_init
+;
+
+@var_args_expr = @vastartexpr
+ | @vaendexpr
+ | @vaargexpr
+ | @vacopyexpr
+ ;
+
+@builtin_op = @var_args_expr
+ | @noopexpr
+ | @offsetofexpr
+ | @intaddrexpr
+ | @hasassignexpr
+ | @hascopyexpr
+ | @hasnothrowassign
+ | @hasnothrowconstr
+ | @hasnothrowcopy
+ | @hastrivialassign
+ | @hastrivialconstr
+ | @hastrivialcopy
+ | @hastrivialdestructor
+ | @hasuserdestr
+ | @hasvirtualdestr
+ | @isabstractexpr
+ | @isbaseofexpr
+ | @isclassexpr
+ | @isconvtoexpr
+ | @isemptyexpr
+ | @isenumexpr
+ | @ispodexpr
+ | @ispolyexpr
+ | @isunionexpr
+ | @typescompexpr
+ | @builtinshufflevector
+ | @builtinconvertvector
+ | @builtinaddressof
+ | @istriviallyconstructibleexpr
+ | @isdestructibleexpr
+ | @isnothrowdestructibleexpr
+ | @istriviallydestructibleexpr
+ | @istriviallyassignableexpr
+ | @isnothrowassignableexpr
+ | @isstandardlayoutexpr
+ | @istriviallycopyableexpr
+ | @isliteraltypeexpr
+ | @hastrivialmoveconstructorexpr
+ | @hastrivialmoveassignexpr
+ | @hasnothrowmoveassignexpr
+ | @isconstructibleexpr
+ | @isnothrowconstructibleexpr
+ | @hasfinalizerexpr
+ | @isdelegateexpr
+ | @isinterfaceclassexpr
+ | @isrefarrayexpr
+ | @isrefclassexpr
+ | @issealedexpr
+ | @issimplevalueclassexpr
+ | @isvalueclassexpr
+ | @isfinalexpr
+ | @builtinchooseexpr
+ | @builtincomplex
+ ;
+
+new_allocated_type(
+ unique int expr: @new_expr ref,
+ int type_id: @type ref
+);
+
+new_array_allocated_type(
+ unique int expr: @new_array_expr ref,
+ int type_id: @type ref
+);
+
+/**
+ * The field being initialized by an initializer expression within an aggregate
+ * initializer for a class/struct/union.
+ */
+#keyset[aggregate, field]
+aggregate_field_init(
+ int aggregate: @aggregateliteral ref,
+ int initializer: @expr ref,
+ int field: @membervariable ref
+);
+
+/**
+ * The index of the element being initialized by an initializer expression
+ * within an aggregate initializer for an array.
+ */
+#keyset[aggregate, element_index]
+aggregate_array_init(
+ int aggregate: @aggregateliteral ref,
+ int initializer: @expr ref,
+ int element_index: int ref
+);
+
+@ctorinit = @ctordirectinit
+ | @ctorvirtualinit
+ | @ctorfieldinit
+ | @ctordelegatinginit;
+@dtordestruct = @dtordirectdestruct
+ | @dtorvirtualdestruct
+ | @dtorfielddestruct;
+
+
+condition_decl_bind(
+ unique int expr: @condition_decl ref,
+ unique int decl: @declaration ref
+);
+
+typeid_bind(
+ unique int expr: @type_id ref,
+ int type_id: @type ref
+);
+
+uuidof_bind(
+ unique int expr: @uuidof ref,
+ int type_id: @type ref
+);
+
+@runtime_sizeof_or_alignof = @runtime_sizeof | @runtime_alignof;
+
+sizeof_bind(
+ unique int expr: @runtime_sizeof_or_alignof ref,
+ int type_id: @type ref
+);
+
+code_block(
+ unique int block: @literal ref,
+ unique int routine: @function ref
+);
+
+lambdas(
+ unique int expr: @lambdaexpr ref,
+ string default_capture: string ref,
+ boolean has_explicit_return_type: boolean ref
+);
+
+lambda_capture(
+ unique int id: @lambdacapture,
+ int lambda: @lambdaexpr ref,
+ int index: int ref,
+ int field: @membervariable ref,
+ boolean captured_by_reference: boolean ref,
+ boolean is_implicit: boolean ref,
+ int location: @location_default ref
+);
+
+@funbindexpr = @routineexpr
+ | @new_expr
+ | @delete_expr
+ | @delete_array_expr
+ | @ctordirectinit
+ | @ctorvirtualinit
+ | @ctordelegatinginit
+ | @dtordirectdestruct
+ | @dtorvirtualdestruct;
+
+@varbindexpr = @varaccess | @ctorfieldinit | @dtorfielddestruct;
+@addressable = @function | @variable ;
+@accessible = @addressable | @enumconstant ;
+
+@access = @varaccess | @routineexpr ;
+
+fold(
+ int expr: @foldexpr ref,
+ string operator: string ref,
+ boolean is_left_fold: boolean ref
+);
+
+stmts(
+ unique int id: @stmt,
+ int kind: int ref,
+ int location: @location_stmt ref
+);
+
+case @stmt.kind of
+ 1 = @stmt_expr
+| 2 = @stmt_if
+| 3 = @stmt_while
+| 4 = @stmt_goto
+| 5 = @stmt_label
+| 6 = @stmt_return
+| 7 = @stmt_block
+| 8 = @stmt_end_test_while // do { ... } while ( ... )
+| 9 = @stmt_for
+| 10 = @stmt_switch_case
+| 11 = @stmt_switch
+| 13 = @stmt_asm // "asm" statement or the body of an asm function
+| 15 = @stmt_try_block
+| 16 = @stmt_microsoft_try // Microsoft
+| 17 = @stmt_decl
+| 18 = @stmt_set_vla_size // C99
+| 19 = @stmt_vla_decl // C99
+| 25 = @stmt_assigned_goto // GNU
+| 26 = @stmt_empty
+| 27 = @stmt_continue
+| 28 = @stmt_break
+| 29 = @stmt_range_based_for // C++11
+// ... 30 @stmt_at_autoreleasepool_block deprecated
+// ... 31 @stmt_objc_for_in deprecated
+// ... 32 @stmt_at_synchronized deprecated
+| 33 = @stmt_handler
+// ... 34 @stmt_finally_end deprecated
+| 35 = @stmt_constexpr_if
+| 37 = @stmt_co_return
+;
+
+type_vla(
+ int type_id: @type ref,
+ int decl: @stmt_vla_decl ref
+);
+
+variable_vla(
+ int var: @variable ref,
+ int decl: @stmt_vla_decl ref
+);
+
+if_then(
+ unique int if_stmt: @stmt_if ref,
+ int then_id: @stmt ref
+);
+
+if_else(
+ unique int if_stmt: @stmt_if ref,
+ int else_id: @stmt ref
+);
+
+constexpr_if_then(
+ unique int constexpr_if_stmt: @stmt_constexpr_if ref,
+ int then_id: @stmt ref
+);
+
+constexpr_if_else(
+ unique int constexpr_if_stmt: @stmt_constexpr_if ref,
+ int else_id: @stmt ref
+);
+
+while_body(
+ unique int while_stmt: @stmt_while ref,
+ int body_id: @stmt ref
+);
+
+do_body(
+ unique int do_stmt: @stmt_end_test_while ref,
+ int body_id: @stmt ref
+);
+
+#keyset[switch_stmt, index]
+switch_case(
+ int switch_stmt: @stmt_switch ref,
+ int index: int ref,
+ int case_id: @stmt_switch_case ref
+);
+
+switch_body(
+ unique int switch_stmt: @stmt_switch ref,
+ int body_id: @stmt ref
+);
+
+for_initialization(
+ unique int for_stmt: @stmt_for ref,
+ int init_id: @stmt ref
+);
+
+for_condition(
+ unique int for_stmt: @stmt_for ref,
+ int condition_id: @expr ref
+);
+
+for_update(
+ unique int for_stmt: @stmt_for ref,
+ int update_id: @expr ref
+);
+
+for_body(
+ unique int for_stmt: @stmt_for ref,
+ int body_id: @stmt ref
+);
+
+@stmtparent = @stmt | @expr_stmt ;
+stmtparents(
+ unique int id: @stmt ref,
+ int index: int ref,
+ int parent: @stmtparent ref
+);
+
+ishandler(unique int block: @stmt_block ref);
+
+@cfgnode = @stmt | @expr | @function | @initialiser ;
+
+stmt_decl_bind(
+ int stmt: @stmt_decl ref,
+ int num: int ref,
+ int decl: @declaration ref
+);
+
+stmt_decl_entry_bind(
+ int stmt: @stmt_decl ref,
+ int num: int ref,
+ int decl_entry: @element ref
+);
+
+@functionorblock = @function | @stmt_block;
+
+blockscope(
+ unique int block: @stmt_block ref,
+ int enclosing: @functionorblock ref
+);
+
+@jump = @stmt_goto | @stmt_break | @stmt_continue;
+
+@jumporlabel = @jump | @stmt_label | @literal;
+
+jumpinfo(
+ unique int id: @jumporlabel ref,
+ string str: string ref,
+ int target: @stmt ref
+);
+
+preprocdirects(
+ unique int id: @preprocdirect,
+ int kind: int ref,
+ int location: @location_default ref
+);
+case @preprocdirect.kind of
+ 0 = @ppd_if
+| 1 = @ppd_ifdef
+| 2 = @ppd_ifndef
+| 3 = @ppd_elif
+| 4 = @ppd_else
+| 5 = @ppd_endif
+| 6 = @ppd_plain_include
+| 7 = @ppd_define
+| 8 = @ppd_undef
+| 9 = @ppd_line
+| 10 = @ppd_error
+| 11 = @ppd_pragma
+| 12 = @ppd_objc_import
+| 13 = @ppd_include_next
+| 18 = @ppd_warning
+;
+
+@ppd_include = @ppd_plain_include | @ppd_objc_import | @ppd_include_next;
+
+@ppd_branch = @ppd_if | @ppd_ifdef | @ppd_ifndef | @ppd_elif;
+
+preprocpair(
+ int begin : @ppd_branch ref,
+ int elseelifend : @preprocdirect ref
+);
+
+preproctrue(int branch : @ppd_branch ref);
+preprocfalse(int branch : @ppd_branch ref);
+
+preproctext(
+ unique int id: @preprocdirect ref,
+ string head: string ref,
+ string body: string ref
+);
+
+includes(
+ unique int id: @ppd_include ref,
+ int included: @file ref
+);
+
+link_targets(
+ unique int id: @link_target,
+ int binary: @file ref
+);
+
+link_parent(
+ int element : @element ref,
+ int link_target : @link_target ref
+);
+
+/* XML Files */
+
+xmlEncoding(unique int id: @file ref, string encoding: string ref);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ string root: string ref,
+ string publicId: string ref,
+ string systemId: string ref,
+ int fileid: @file ref
+);
+
+xmlElements(
+ unique int id: @xmlelement,
+ string name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ string name: string ref,
+ string value: string ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlNs(
+ int id: @xmlnamespace,
+ string prefixName: string ref,
+ string URI: string ref,
+ int fileid: @file ref
+);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref
+);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref
+);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref
+);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref
+);
+
+@xmllocatable = @xmlcharacters
+ | @xmlelement
+ | @xmlcomment
+ | @xmlattribute
+ | @xmldtd
+ | @file
+ | @xmlnamespace;
diff --git a/cpp/upgrades/c82db4c596b8979eba9a8958e24353a5756d7a02/upgrade.properties b/cpp/upgrades/c82db4c596b8979eba9a8958e24353a5756d7a02/upgrade.properties
new file mode 100644
index 00000000000..df9dcbdef5e
--- /dev/null
+++ b/cpp/upgrades/c82db4c596b8979eba9a8958e24353a5756d7a02/upgrade.properties
@@ -0,0 +1,3 @@
+description: Change decltype keysets
+compatibility: backwards
+
diff --git a/csharp/autobuilder/Semmle.Autobuild.Shared/Solution.cs b/csharp/autobuilder/Semmle.Autobuild.Shared/Solution.cs
index 2f16872dd74..c5d65794fb5 100644
--- a/csharp/autobuilder/Semmle.Autobuild.Shared/Solution.cs
+++ b/csharp/autobuilder/Semmle.Autobuild.Shared/Solution.cs
@@ -45,6 +45,7 @@ namespace Semmle.Autobuild.Shared
private readonly SolutionFile? solution;
private readonly IEnumerable includedProjects;
+
public override IEnumerable IncludedProjects => includedProjects;
public IEnumerable Configurations =>
@@ -84,8 +85,12 @@ namespace Semmle.Autobuild.Shared
.ToArray();
}
- private IEnumerable ToolsVersions => includedProjects.Where(p => p.ValidToolsVersion).Select(p => p.ToolsVersion);
+ private IEnumerable ToolsVersions => includedProjects
+ .Where(p => p.ValidToolsVersion)
+ .Select(p => p.ToolsVersion);
- public Version ToolsVersion => ToolsVersions.Any() ? ToolsVersions.Max() : new Version();
+ public Version ToolsVersion => ToolsVersions.Any()
+ ? ToolsVersions.Max()
+ : new Version();
}
}
diff --git a/csharp/change-notes/2020-12-08-cil-enum-underlying-type.md b/csharp/change-notes/2020-12-08-cil-enum-underlying-type.md
new file mode 100644
index 00000000000..bf0dce9232c
--- /dev/null
+++ b/csharp/change-notes/2020-12-08-cil-enum-underlying-type.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* CIL extraction has been improved to store numeric underlying type of `enum` declarations.
\ No newline at end of file
diff --git a/csharp/change-notes/2020-12-17-format-method-empty-overload.md b/csharp/change-notes/2020-12-17-format-method-empty-overload.md
new file mode 100644
index 00000000000..9341f2c39a7
--- /dev/null
+++ b/csharp/change-notes/2020-12-17-format-method-empty-overload.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* For string formatting methods, such as `System.Console.WriteLine(string format, params object[] arg)`, we now also recognize overloads without insertion parameters as string formatting methods. For example, `System.Console.WriteLine(string value)` is now also a member of the class `FormatMethod` in `frameworks/Format.qll`.
\ No newline at end of file
diff --git a/csharp/change-notes/2020-12-18-extract-custom-modifiers.md b/csharp/change-notes/2020-12-18-extract-custom-modifiers.md
new file mode 100644
index 00000000000..92f141e041e
--- /dev/null
+++ b/csharp/change-notes/2020-12-18-extract-custom-modifiers.md
@@ -0,0 +1,4 @@
+lgtm,codescanning
+* CIL extraction has been improved to store `modreq` and `modopt` custom modifiers.
+The extracted information is surfaced through the `CustomModifierReceiver` class. Additionally,
+the information is also used to evaluate the new `Setter::isInitOnly` predicate.
\ No newline at end of file
diff --git a/csharp/change-notes/2020-12-21-merge-format-queries.md b/csharp/change-notes/2020-12-21-merge-format-queries.md
new file mode 100644
index 00000000000..9845a991b90
--- /dev/null
+++ b/csharp/change-notes/2020-12-21-merge-format-queries.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* The queries `FormatInvalid.ql`, `FormatMissingArgument.ql`, and `FormatUnusedArgument.ql` have been merged into a single `FormatInvalid.ql` query.
\ No newline at end of file
diff --git a/csharp/change-notes/2021-01-14-Unary-pattern.md b/csharp/change-notes/2021-01-14-Unary-pattern.md
new file mode 100644
index 00000000000..0afd5f0c7c1
--- /dev/null
+++ b/csharp/change-notes/2021-01-14-Unary-pattern.md
@@ -0,0 +1,3 @@
+lgtm,codescanning
+* The `UnaryPatternExpr` and `NotPatternExpr` classes have been added to support
+C# 9 unary `not` pattern.
diff --git a/csharp/change-notes/2021-01-15-Relational-pattern.md b/csharp/change-notes/2021-01-15-Relational-pattern.md
new file mode 100644
index 00000000000..77179884d12
--- /dev/null
+++ b/csharp/change-notes/2021-01-15-Relational-pattern.md
@@ -0,0 +1,3 @@
+lgtm,codescanning
+* The `RelationalPatternExpr` and its 4 sub class have been added to support C# 9
+relational `<`, `>`, `<=`, and `>=` patterns.
diff --git a/csharp/change-notes/2021-01-19-Function-pointer.md b/csharp/change-notes/2021-01-19-Function-pointer.md
new file mode 100644
index 00000000000..b48855d2f89
--- /dev/null
+++ b/csharp/change-notes/2021-01-19-Function-pointer.md
@@ -0,0 +1,3 @@
+lgtm,codescanning
+* Function pointer types (`FunctionPointerType`) and call to function pointers
+(`FunctionPointerCall`) are extracted.
diff --git a/csharp/change-notes/2021-01-25-Function-pointer-cil.md b/csharp/change-notes/2021-01-25-Function-pointer-cil.md
new file mode 100644
index 00000000000..097df002d0f
--- /dev/null
+++ b/csharp/change-notes/2021-01-25-Function-pointer-cil.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* Function pointer types (`FunctionPointerType`) are extracted from IL. Also, `pinned` and `by-reference` (`cil_type_annotation`) type extraction is fixed.
diff --git a/csharp/change-notes/2021-01-27-Add-binary-pattern.md b/csharp/change-notes/2021-01-27-Add-binary-pattern.md
new file mode 100644
index 00000000000..27e8ce2a726
--- /dev/null
+++ b/csharp/change-notes/2021-01-27-Add-binary-pattern.md
@@ -0,0 +1,3 @@
+lgtm,codescanning
+* The `BinaryPatternExpr` class has been added to support C# 9 `and` and `or`
+patterns.
diff --git a/csharp/change-notes/2021-02-01-Preprocessor-directives.md b/csharp/change-notes/2021-02-01-Preprocessor-directives.md
new file mode 100644
index 00000000000..d81979c36cd
--- /dev/null
+++ b/csharp/change-notes/2021-02-01-Preprocessor-directives.md
@@ -0,0 +1,8 @@
+lgtm,codescanning
+* The `PreprocessorDirective` class and its base classes have been added to support
+preprocessor directives, such as `#if`, `#define`, `#undef`, `#line`, `#region`,
+`#warning`, `#error`, `#pragma warning`, `#pragma checksum` and `#nullable`. Furthermore,
+`#line` directives are now taken into account when querying the location of any
+code construct. Files referenced in preprocessor directives are also included in the
+extraction sources. This change is expected to lead to better error reporting locations
+in generated code, such as generated code from `.cshtml` files in ASP.NET Core.
diff --git a/csharp/change-notes/2021-02-02-foreach-underlying-methods.md b/csharp/change-notes/2021-02-02-foreach-underlying-methods.md
new file mode 100644
index 00000000000..a9e934a0e5d
--- /dev/null
+++ b/csharp/change-notes/2021-02-02-foreach-underlying-methods.md
@@ -0,0 +1,3 @@
+lgtm,codescanning
+* The underlying methods of `foreach` statements are now explicitly extracted and
+they are made available on the `ForeachStmt` class.
diff --git a/csharp/change-notes/2021-02-04-Records.md b/csharp/change-notes/2021-02-04-Records.md
new file mode 100644
index 00000000000..069737e5239
--- /dev/null
+++ b/csharp/change-notes/2021-02-04-Records.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* Record types (`Record`) are extracted.
diff --git a/csharp/extractor/Semmle.Extraction.CIL.Driver/ExtractorOptions.cs b/csharp/extractor/Semmle.Extraction.CIL.Driver/ExtractorOptions.cs
index 9964ff5bf0c..75405d562fe 100644
--- a/csharp/extractor/Semmle.Extraction.CIL.Driver/ExtractorOptions.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL.Driver/ExtractorOptions.cs
@@ -163,7 +163,7 @@ namespace Semmle.Extraction.CIL.Driver
}
private readonly HashSet filesAnalyzed = new HashSet();
- public HashSet MissingReferences {get;} = new HashSet();
+ public HashSet MissingReferences { get; } = new HashSet();
}
///
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Context.cs b/csharp/extractor/Semmle.Extraction.CIL/Context.cs
index 26a9298aa07..ae662f2e87c 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Context.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Context.cs
@@ -1,6 +1,5 @@
using System;
using System.IO;
-using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
@@ -83,7 +82,7 @@ namespace Semmle.Extraction.CIL
trapFile.Write(GetString(def.Name));
trapFile.Write('_');
trapFile.Write(def.Version.ToString());
- trapFile.Write("::");
+ trapFile.Write(Entities.Type.AssemblyTypeNameSeparator);
}
public Entities.TypeSignatureDecoder TypeSignatureDecoder { get; }
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/ArrayType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/ArrayType.cs
index fef6d313a1a..ae04add2b12 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/ArrayType.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/ArrayType.cs
@@ -27,27 +27,26 @@ namespace Semmle.Extraction.CIL.Entities
return obj is ArrayType array && elementType.Equals(array.elementType) && rank == array.rank;
}
- public override int GetHashCode()
- {
- return elementType.GetHashCode() * 5 + rank;
- }
+ public override int GetHashCode() => HashCode.Combine(elementType, rank);
public override void WriteId(TextWriter trapFile, bool inContext)
{
- elementType.GetId(trapFile, inContext);
+ elementType.WriteId(trapFile, inContext);
trapFile.Write('[');
for (var i = 1; i < rank; ++i)
+ {
trapFile.Write(',');
+ }
trapFile.Write(']');
}
public override string Name => elementType.Name + "[]";
- public override Namespace Namespace => Cx.SystemNamespace;
+ public override Namespace ContainingNamespace => Cx.SystemNamespace;
public override Type? ContainingType => null;
- public override int ThisTypeParameters => elementType.ThisTypeParameters;
+ public override int ThisTypeParameterCount => elementType.ThisTypeParameterCount;
public override CilTypeKind Kind => CilTypeKind.Array;
@@ -71,7 +70,5 @@ namespace Semmle.Extraction.CIL.Entities
public override IEnumerable GenericArguments => elementType.GenericArguments;
public override IEnumerable TypeParameters => elementType.TypeParameters;
-
- public override IEnumerable MethodParameters => throw new NotImplementedException();
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Assembly.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Assembly.cs
index 8a843f1672d..5c0a834909c 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Assembly.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Assembly.cs
@@ -136,7 +136,7 @@ namespace Semmle.Extraction.CIL.Entities
trapFile = trapWriter.TrapFile;
if (nocache || !System.IO.File.Exists(trapFile))
{
- var cx = extractor.CreateContext(null, trapWriter, null, false);
+ var cx = new Extraction.Context(extractor, trapWriter);
ExtractCIL(cx, assemblyPath, extractPdbs);
extracted = true;
}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Attribute.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Attribute.cs
index bfbe8ccc854..c862bc3fc4e 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Attribute.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Attribute.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Reflection.Metadata;
namespace Semmle.Extraction.CIL.Entities
@@ -42,28 +43,43 @@ namespace Semmle.Extraction.CIL.Entities
{
decoded = attrib.DecodeValue(new CustomAttributeDecoder(Cx));
}
- catch (NotImplementedException)
+ catch
{
- // Attribute decoding is only partial at this stage.
+ Cx.Cx.Extractor.Logger.Log(Util.Logging.Severity.Info,
+ $"Attribute decoding is partial. Decoding attribute {constructor.DeclaringType.GetQualifiedName()} failed on {@object}.");
yield break;
}
for (var index = 0; index < decoded.FixedArguments.Length; ++index)
{
- var value = decoded.FixedArguments[index].Value;
- var stringValue = value?.ToString();
- yield return Tuples.cil_attribute_positional_argument(this, index, stringValue ?? "null");
+ var stringValue = GetStringValue(decoded.FixedArguments[index].Type, decoded.FixedArguments[index].Value);
+ yield return Tuples.cil_attribute_positional_argument(this, index, stringValue);
}
foreach (var p in decoded.NamedArguments)
{
- var value = p.Value;
- var stringValue = value?.ToString();
- yield return Tuples.cil_attribute_named_argument(this, p.Name, stringValue ?? "null");
+ var stringValue = GetStringValue(p.Type, p.Value);
+ yield return Tuples.cil_attribute_named_argument(this, p.Name!, stringValue);
}
}
}
+ private static string GetStringValue(Type type, object? value)
+ {
+ if (value is System.Collections.Immutable.ImmutableArray> values)
+ {
+ return "[" + string.Join(",", values.Select(v => GetStringValue(v.Type, v.Value))) + "]";
+ }
+
+ if (type.GetQualifiedName() == "System.Type" &&
+ value is Type t)
+ {
+ return t.GetQualifiedName();
+ }
+
+ return value?.ToString() ?? "null";
+ }
+
public static IEnumerable Populate(Context cx, IEntity @object, CustomAttributeHandleCollection attributes)
{
foreach (var attrib in attributes)
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/ByRefType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/ByRefType.cs
new file mode 100644
index 00000000000..2a527419249
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/ByRefType.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Semmle.Extraction.CIL.Entities
+{
+ ///
+ /// Types that are passed by reference are not written directly to trap files. Instead, the annotation is stored on
+ /// the entity.
+ ///
+ internal sealed class ByRefType : Type
+ {
+ public ByRefType(Context cx, Type elementType) : base(cx)
+ {
+ ElementType = elementType;
+ }
+
+ public override CilTypeKind Kind => throw new NotImplementedException();
+
+ public override Namespace? ContainingNamespace => throw new NotImplementedException();
+
+ public override Type? ContainingType => throw new NotImplementedException();
+
+ public override int ThisTypeParameterCount => throw new NotImplementedException();
+
+ public override IEnumerable TypeParameters => throw new NotImplementedException();
+
+ public override Type Construct(IEnumerable typeArguments) => throw new NotImplementedException();
+
+ public override string Name => $"{ElementType.Name}&";
+
+ public Type ElementType { get; }
+
+ public override void WriteAssemblyPrefix(TextWriter trapFile) => throw new NotImplementedException();
+
+ public override void WriteId(TextWriter trapFile, bool inContext)
+ {
+ ElementType.WriteId(trapFile, inContext);
+ trapFile.Write('&');
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/CilTypeKind.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/CilTypeKind.cs
index f5ccf5057f7..e95fe0e28c0 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/CilTypeKind.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/CilTypeKind.cs
@@ -8,6 +8,7 @@ namespace Semmle.Extraction.CIL.Entities
ValueOrRefType,
TypeParameter,
Array,
- Pointer
+ Pointer,
+ FunctionPointer
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/ConstructedType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/ConstructedType.cs
index 165883a8663..be2ab5da4d8 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/ConstructedType.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/ConstructedType.cs
@@ -1,5 +1,4 @@
using System;
-using Microsoft.CodeAnalysis;
using System.Linq;
using System.Collections.Generic;
using System.IO;
@@ -17,36 +16,18 @@ namespace Semmle.Extraction.CIL.Entities
// Either null or notEmpty
private readonly Type[]? thisTypeArguments;
- public override IEnumerable ThisTypeArguments => thisTypeArguments.EnumerateNull();
-
- public override IEnumerable ThisGenericArguments => thisTypeArguments.EnumerateNull();
-
- public override IEnumerable Contents
- {
- get
- {
- foreach (var c in base.Contents)
- yield return c;
-
- var i = 0;
- foreach (var type in ThisGenericArguments)
- {
- yield return type;
- yield return Tuples.cil_type_argument(this, i++, type);
- }
- }
- }
-
- public override Type SourceDeclaration => unboundGenericType;
+ private readonly Type? containingType;
+ private readonly NamedTypeIdWriter idWriter;
public ConstructedType(Context cx, Type unboundType, IEnumerable typeArguments) : base(cx)
{
+ idWriter = new NamedTypeIdWriter(this);
var suppliedArgs = typeArguments.Count();
- if (suppliedArgs != unboundType.TotalTypeParametersCheck)
+ if (suppliedArgs != unboundType.TotalTypeParametersCount)
throw new InternalError("Unexpected number of type arguments in ConstructedType");
unboundGenericType = unboundType;
- var thisParams = unboundType.ThisTypeParameters;
+ var thisParams = unboundType.ThisTypeParameterCount;
if (typeArguments.Count() == thisParams)
{
@@ -88,14 +69,29 @@ namespace Semmle.Extraction.CIL.Entities
return h;
}
- private readonly Type? containingType;
+ public override IEnumerable Contents
+ {
+ get
+ {
+ foreach (var c in base.Contents)
+ yield return c;
+
+ var i = 0;
+ foreach (var type in ThisTypeArguments)
+ {
+ yield return type;
+ yield return Tuples.cil_type_argument(this, i++, type);
+ }
+ }
+ }
+
+ public override Type SourceDeclaration => unboundGenericType;
+
public override Type? ContainingType => containingType;
public override string Name => unboundGenericType.Name;
- public override Namespace Namespace => unboundGenericType.Namespace!;
-
- public override int ThisTypeParameters => thisTypeArguments == null ? 0 : thisTypeArguments.Length;
+ public override Namespace ContainingNamespace => unboundGenericType.ContainingNamespace!;
public override CilTypeKind Kind => unboundGenericType.Kind;
@@ -106,40 +102,17 @@ namespace Semmle.Extraction.CIL.Entities
public override void WriteId(TextWriter trapFile, bool inContext)
{
- if (ContainingType != null)
- {
- ContainingType.GetId(trapFile, inContext);
- trapFile.Write('.');
- }
- else
- {
- WriteAssemblyPrefix(trapFile);
-
- if (!Namespace.IsGlobalNamespace)
- {
- Namespace.WriteId(trapFile);
- trapFile.Write('.');
- }
- }
- trapFile.Write(unboundGenericType.Name);
-
- if (thisTypeArguments != null && thisTypeArguments.Any())
- {
- trapFile.Write('<');
- var index = 0;
- foreach (var t in thisTypeArguments)
- {
- trapFile.WriteSeparator(",", ref index);
- t.WriteId(trapFile);
- }
- trapFile.Write('>');
- }
+ idWriter.WriteId(trapFile, inContext);
}
public override void WriteAssemblyPrefix(TextWriter trapFile) => unboundGenericType.WriteAssemblyPrefix(trapFile);
+ public override int ThisTypeParameterCount => thisTypeArguments?.Length ?? 0;
+
public override IEnumerable TypeParameters => GenericArguments;
- public override IEnumerable MethodParameters => throw new NotImplementedException();
+ public override IEnumerable ThisTypeArguments => thisTypeArguments.EnumerateNull();
+
+ public override IEnumerable ThisGenericArguments => thisTypeArguments.EnumerateNull();
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/CustomAttributeDecoder.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/CustomAttributeDecoder.cs
index 25ea18e3229..94d6cc0ab6d 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/CustomAttributeDecoder.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/CustomAttributeDecoder.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Reflection.Metadata;
namespace Semmle.Extraction.CIL.Entities
@@ -14,7 +15,7 @@ namespace Semmle.Extraction.CIL.Entities
public Type GetPrimitiveType(PrimitiveTypeCode typeCode) => cx.Create(typeCode);
- public Type GetSystemType() => throw new NotImplementedException();
+ public Type GetSystemType() => new NoMetadataHandleType(cx, "System.Type");
public Type GetSZArrayType(Type elementType) =>
cx.Populate(new ArrayType(cx, elementType));
@@ -25,10 +26,37 @@ namespace Semmle.Extraction.CIL.Entities
public Type GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) =>
(Type)cx.Create(handle);
- public Type GetTypeFromSerializedName(string name) => throw new NotImplementedException();
+ public Type GetTypeFromSerializedName(string name) => new NoMetadataHandleType(cx, name);
- public PrimitiveTypeCode GetUnderlyingEnumType(Type type) => throw new NotImplementedException();
+ public PrimitiveTypeCode GetUnderlyingEnumType(Type type)
+ {
+ if (type is TypeDefinitionType tdt &&
+ tdt.GetUnderlyingEnumType() is PrimitiveTypeCode underlying)
+ {
+ return underlying;
+ }
- public bool IsSystemType(Type type) => type is PrimitiveType; // ??
+ var name = type.GetQualifiedName();
+
+ if (wellKnownEnums.TryGetValue(name, out var code))
+ {
+ cx.Cx.Extractor.Logger.Log(Util.Logging.Severity.Debug, $"Using hard coded underlying enum type for {name}");
+ return code;
+ }
+
+ cx.Cx.Extractor.Logger.Log(Util.Logging.Severity.Info, $"Couldn't get underlying enum type for {name}");
+
+ // We can't fall back to Int32, because the type returned here defines how many bytes are read from the
+ // stream and how those bytes are interpreted.
+ throw new NotImplementedException();
+ }
+
+ public bool IsSystemType(Type type) => type.GetQualifiedName() == "System.Type";
+
+ private static readonly Dictionary wellKnownEnums = new Dictionary
+ {
+ { "System.AttributeTargets", PrimitiveTypeCode.Int32 },
+ { "System.ComponentModel.EditorBrowsableState", PrimitiveTypeCode.Int32 }
+ };
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/DefinitionMethod.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/DefinitionMethod.cs
index 43bee72a9c0..175da806597 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/DefinitionMethod.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/DefinitionMethod.cs
@@ -76,9 +76,10 @@ namespace Semmle.Extraction.CIL.Entities
var typeSignature = md.DecodeSignature(Cx.TypeSignatureDecoder, this);
- Parameters = MakeParameters(typeSignature.ParameterTypes).ToArray();
+ var parameters = GetParameterExtractionProducts(typeSignature.ParameterTypes).ToArray();
+ Parameters = parameters.OfType().ToArray();
- foreach (var c in Parameters)
+ foreach (var c in parameters)
yield return c;
foreach (var c in PopulateFlags)
@@ -95,7 +96,12 @@ namespace Semmle.Extraction.CIL.Entities
}
yield return Tuples.metadata_handle(this, Cx.Assembly, MetadataTokens.GetToken(handle));
- yield return Tuples.cil_method(this, Name, declaringType, typeSignature.ReturnType);
+
+ foreach (var m in GetMethodExtractionProducts(Name, declaringType, typeSignature.ReturnType))
+ {
+ yield return m;
+ }
+
yield return Tuples.cil_method_source_declaration(this, this);
yield return Tuples.cil_method_location(this, Cx.Assembly);
@@ -115,8 +121,19 @@ namespace Semmle.Extraction.CIL.Entities
for (var l = 0; l < this.locals.Length; ++l)
{
- this.locals[l] = Cx.Populate(new LocalVariable(Cx, Implementation, l, localVariableTypes[l]));
- yield return this.locals[l];
+ var t = localVariableTypes[l];
+ if (t is ByRefType brt)
+ {
+ t = brt.ElementType;
+ this.locals[l] = Cx.Populate(new LocalVariable(Cx, Implementation, l, t));
+ yield return this.locals[l];
+ yield return Tuples.cil_type_annotation(this.locals[l], TypeAnnotation.Ref);
+ }
+ else
+ {
+ this.locals[l] = Cx.Populate(new LocalVariable(Cx, Implementation, l, t));
+ yield return this.locals[l];
+ }
}
}
@@ -178,7 +195,7 @@ namespace Semmle.Extraction.CIL.Entities
}
}
- private IEnumerable Decode(byte[] ilbytes, Dictionary jump_table)
+ private IEnumerable Decode(byte[]? ilbytes, Dictionary jump_table)
{
// Sequence points are stored in order of offset.
// We use an enumerator to locate the correct sequence point for each instruction.
@@ -203,9 +220,9 @@ namespace Semmle.Extraction.CIL.Entities
}
var child = 0;
- for (var offset = 0; offset < ilbytes.Length;)
+ for (var offset = 0; offset < (ilbytes?.Length ?? 0);)
{
- var instruction = new Instruction(Cx, this, ilbytes, offset, child++);
+ var instruction = new Instruction(Cx, this, ilbytes!, offset, child++);
yield return instruction;
if (nextSequencePoint != null && offset >= nextSequencePoint.Current.Offset)
@@ -245,12 +262,12 @@ namespace Semmle.Extraction.CIL.Entities
var ilbytes = body.GetILBytes();
var child = 0;
- for (var offset = 0; offset < ilbytes.Length;)
+ for (var offset = 0; offset < (ilbytes?.Length ?? 0);)
{
Instruction decoded;
try
{
- decoded = new Instruction(Cx, this, ilbytes, offset, child++);
+ decoded = new Instruction(Cx, this, ilbytes!, offset, child++);
offset += decoded.Width;
}
catch // lgtm[cs/catch-of-all-exceptions]
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/ErrorType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/ErrorType.cs
index 67a2a495322..e99fd8b4bc3 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/ErrorType.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/ErrorType.cs
@@ -16,18 +16,16 @@ namespace Semmle.Extraction.CIL.Entities
public override string Name => "!error";
- public override Namespace Namespace => Cx.GlobalNamespace;
+ public override Namespace ContainingNamespace => Cx.GlobalNamespace;
public override Type? ContainingType => null;
- public override int ThisTypeParameters => 0;
+ public override int ThisTypeParameterCount => 0;
public override void WriteAssemblyPrefix(TextWriter trapFile) => throw new NotImplementedException();
public override IEnumerable TypeParameters => throw new NotImplementedException();
- public override IEnumerable MethodParameters => throw new NotImplementedException();
-
public override Type Construct(IEnumerable typeArguments) => throw new NotImplementedException();
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Field.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Field.cs
index 98a28f36286..3f051796887 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Field.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Field.cs
@@ -8,7 +8,7 @@ namespace Semmle.Extraction.CIL.Entities
///
/// An entity representing a field.
///
- internal abstract class Field : GenericContext, IMember
+ internal abstract class Field : GenericContext, IMember, ICustomModifierReceiver
{
protected Field(Context cx) : base(cx)
{
@@ -45,7 +45,13 @@ namespace Semmle.Extraction.CIL.Entities
{
get
{
- yield return Tuples.cil_field(this, DeclaringType, Name, Type);
+ var t = Type;
+ if (t is ModifiedType mt)
+ {
+ t = mt.Unmodified;
+ yield return Tuples.cil_custom_modifiers(this, mt.Modifier, mt.IsRequired);
+ }
+ yield return Tuples.cil_field(this, DeclaringType, Name, t);
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/FunctionPointerType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/FunctionPointerType.cs
new file mode 100644
index 00000000000..e92dd957cb5
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/FunctionPointerType.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection.Metadata;
+
+namespace Semmle.Extraction.CIL.Entities
+{
+ internal sealed class FunctionPointerType : Type, IParameterizable, ICustomModifierReceiver
+ {
+ private readonly MethodSignature signature;
+
+ public FunctionPointerType(Context cx, MethodSignature signature) : base(cx)
+ {
+ this.signature = signature;
+ }
+
+ public override CilTypeKind Kind => CilTypeKind.FunctionPointer;
+
+ public override string Name
+ {
+ get
+ {
+ using var id = new StringWriter();
+ WriteName(
+ id.Write,
+ t => id.Write(t.Name),
+ signature
+ );
+ return id.ToString();
+ }
+ }
+
+ public override Namespace? ContainingNamespace => Cx.GlobalNamespace;
+
+ public override Type? ContainingType => null;
+
+ public override int ThisTypeParameterCount => throw new System.NotImplementedException();
+
+ public override IEnumerable TypeParameters => throw new System.NotImplementedException();
+
+ public override Type Construct(IEnumerable typeArguments) => throw new System.NotImplementedException();
+
+ public override void WriteAssemblyPrefix(TextWriter trapFile) { }
+
+ public override void WriteId(TextWriter trapFile, bool inContext)
+ {
+ WriteName(
+ trapFile.Write,
+ t => t.WriteId(trapFile, inContext),
+ signature
+ );
+ }
+
+ internal static void WriteName(Action write, Action writeType, MethodSignature signature)
+ {
+ write("delegate* ");
+ write(GetCallingConvention(signature.Header.CallingConvention));
+ write("<");
+ foreach (var pt in signature.ParameterTypes)
+ {
+ writeType(pt);
+ write(",");
+ }
+ writeType(signature.ReturnType);
+ write(">");
+ }
+
+ internal static string GetCallingConvention(SignatureCallingConvention callingConvention)
+ {
+ if (callingConvention == SignatureCallingConvention.Default)
+ {
+ return "managed";
+ }
+
+ if (callingConvention == SignatureCallingConvention.Unmanaged)
+ {
+ return "unmanaged";
+ }
+
+ return $"unmanaged[{callingConvention}]";
+ }
+
+ public override IEnumerable Contents
+ {
+ get
+ {
+ foreach (var c in base.Contents)
+ {
+ yield return c;
+ }
+
+ var retType = signature.ReturnType;
+ if (retType is ModifiedType mt)
+ {
+ retType = mt.Unmodified;
+ yield return Tuples.cil_custom_modifiers(this, mt.Modifier, mt.IsRequired);
+ }
+ if (retType is ByRefType byRefType)
+ {
+ retType = byRefType.ElementType;
+ yield return Tuples.cil_type_annotation(this, TypeAnnotation.Ref);
+ }
+ yield return Tuples.cil_function_pointer_return_type(this, retType);
+
+ yield return Tuples.cil_function_pointer_calling_conventions(this, signature.Header.CallingConvention);
+
+ foreach (var p in Method.GetParameterExtractionProducts(signature.ParameterTypes, this, this, Cx, 0))
+ {
+ yield return p;
+ }
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/GenericsHelper.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/GenericsHelper.cs
new file mode 100644
index 00000000000..311d4e48134
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/GenericsHelper.cs
@@ -0,0 +1,59 @@
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+
+namespace Semmle.Extraction.CIL.Entities
+{
+ internal static class GenericsHelper
+ {
+ public static TypeTypeParameter[] MakeTypeParameters(Type container, int count)
+ {
+ var newTypeParams = new TypeTypeParameter[count];
+ for (var i = 0; i < newTypeParams.Length; ++i)
+ {
+ newTypeParams[i] = new TypeTypeParameter(container, i);
+ }
+ return newTypeParams;
+ }
+
+ public static string GetNonGenericName(StringHandle name, MetadataReader reader)
+ {
+ var n = reader.GetString(name);
+ return GetNonGenericName(n);
+ }
+
+ public static string GetNonGenericName(string name)
+ {
+ var tick = name.LastIndexOf('`');
+ return tick == -1
+ ? name
+ : name.Substring(0, tick);
+ }
+
+ public static int GetGenericTypeParameterCount(StringHandle name, MetadataReader reader)
+ {
+ var n = reader.GetString(name);
+ return GetGenericTypeParameterCount(n);
+ }
+
+ public static int GetGenericTypeParameterCount(string name)
+ {
+ var tick = name.LastIndexOf('`');
+ return tick == -1
+ ? 0
+ : int.Parse(name.Substring(tick + 1));
+ }
+
+ public static IEnumerable GetAllTypeParameters(Type? container, IEnumerable thisTypeParameters)
+ {
+ if (container != null)
+ {
+ foreach (var t in container.TypeParameters)
+ yield return t;
+ }
+
+ foreach (var t in thisTypeParameters)
+ yield return t;
+
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/IParameterizable.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/IParameterizable.cs
new file mode 100644
index 00000000000..ad9347848e4
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/IParameterizable.cs
@@ -0,0 +1,7 @@
+namespace Semmle.Extraction.CIL.Entities
+{
+ internal interface IParameterizable : IEntity
+ {
+
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Instruction.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Instruction.cs
index 9512cfc5690..75d0aab454a 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Instruction.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Instruction.cs
@@ -481,7 +481,7 @@ namespace Semmle.Extraction.CIL.Entities
// TODO: Find a solution to this.
// For now, just log the error
- Cx.Cx.ExtractionError("A CIL instruction jumps outside the current method", "", Extraction.Entities.GeneratedLocation.Create(Cx.Cx), "", Util.Logging.Severity.Warning);
+ Cx.Cx.ExtractionError("A CIL instruction jumps outside the current method", null, Extraction.Entities.GeneratedLocation.Create(Cx.Cx), "", Util.Logging.Severity.Warning);
}
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/MemberReferenceMethod.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/MemberReferenceMethod.cs
index f515f7190e6..cacbc6ab874 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/MemberReferenceMethod.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/MemberReferenceMethod.cs
@@ -76,12 +76,16 @@ namespace Semmle.Extraction.CIL.Entities
var typeSignature = mr.DecodeMethodSignature(Cx.TypeSignatureDecoder, this);
- Parameters = MakeParameters(typeSignature.ParameterTypes).ToArray();
- foreach (var p in Parameters) yield return p;
+ var parameters = GetParameterExtractionProducts(typeSignature.ParameterTypes).ToArray();
+ Parameters = parameters.OfType().ToArray();
+ foreach (var p in parameters) yield return p;
foreach (var f in PopulateFlags) yield return f;
- yield return Tuples.cil_method(this, Name, DeclaringType, typeSignature.ReturnType);
+ foreach (var m in GetMethodExtractionProducts(Name, DeclaringType, typeSignature.ReturnType))
+ {
+ yield return m;
+ }
if (SourceDeclaration != null)
yield return Tuples.cil_method_source_declaration(this, SourceDeclaration);
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Method.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Method.cs
index ade2189e665..30e94c8e16a 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Method.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Method.cs
@@ -3,14 +3,13 @@ using System.Reflection.Metadata;
using System.Collections.Generic;
using System.Linq;
using System.IO;
-using Semmle.Util;
namespace Semmle.Extraction.CIL.Entities
{
///
/// A method entity.
///
- internal abstract class Method : TypeContainer, IMember
+ internal abstract class Method : TypeContainer, IMember, ICustomModifierReceiver, IParameterizable
{
protected MethodTypeParameter[]? genericParams;
protected GenericContext gc;
@@ -21,6 +20,8 @@ namespace Semmle.Extraction.CIL.Entities
this.gc = gc;
}
+ public ITypeSignature ReturnType => signature.ReturnType;
+
public override IEnumerable TypeParameters => gc.TypeParameters.Concat(DeclaringType.TypeParameters);
public override IEnumerable MethodParameters =>
@@ -76,7 +77,7 @@ namespace Semmle.Extraction.CIL.Entities
public abstract bool IsStatic { get; }
- protected IEnumerable MakeParameters(IEnumerable parameterTypes)
+ protected IEnumerable GetParameterExtractionProducts(IEnumerable parameterTypes)
{
var i = 0;
@@ -85,8 +86,51 @@ namespace Semmle.Extraction.CIL.Entities
yield return Cx.Populate(new Parameter(Cx, this, i++, DeclaringType));
}
+ foreach (var p in GetParameterExtractionProducts(parameterTypes, this, this, Cx, i))
+ {
+ yield return p;
+ }
+ }
+
+ internal static IEnumerable GetParameterExtractionProducts(IEnumerable parameterTypes, IParameterizable parameterizable, ICustomModifierReceiver receiver, Context cx, int firstChildIndex)
+ {
+ var i = firstChildIndex;
foreach (var p in parameterTypes)
- yield return Cx.Populate(new Parameter(Cx, this, i++, p));
+ {
+ var t = p;
+ if (t is ModifiedType mt)
+ {
+ t = mt.Unmodified;
+ yield return Tuples.cil_custom_modifiers(receiver, mt.Modifier, mt.IsRequired);
+ }
+ if (t is ByRefType brt)
+ {
+ t = brt.ElementType;
+ var parameter = cx.Populate(new Parameter(cx, parameterizable, i++, t));
+ yield return parameter;
+ yield return Tuples.cil_type_annotation(parameter, TypeAnnotation.Ref);
+ }
+ else
+ {
+ yield return cx.Populate(new Parameter(cx, parameterizable, i++, t));
+ }
+ }
+ }
+
+ protected IEnumerable GetMethodExtractionProducts(string name, Type declaringType, Type returnType)
+ {
+ var t = returnType;
+ if (t is ModifiedType mt)
+ {
+ t = mt.Unmodified;
+ yield return Tuples.cil_custom_modifiers(this, mt.Modifier, mt.IsRequired);
+ }
+ if (t is ByRefType brt)
+ {
+ t = brt.ElementType;
+ yield return Tuples.cil_type_annotation(this, TypeAnnotation.Ref);
+ }
+ yield return Tuples.cil_method(this, name, declaringType, t);
}
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/MethodSpecificationMethod.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/MethodSpecificationMethod.cs
index 60ec9f21bc4..26811ce6c80 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/MethodSpecificationMethod.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/MethodSpecificationMethod.cs
@@ -77,14 +77,19 @@ namespace Semmle.Extraction.CIL.Entities
throw new InternalError($"Unexpected constructed method handle kind {ms.Method.Kind}");
}
- Parameters = MakeParameters(constructedTypeSignature.ParameterTypes).ToArray();
- foreach (var p in Parameters)
+ var parameters = GetParameterExtractionProducts(constructedTypeSignature.ParameterTypes).ToArray();
+ Parameters = parameters.OfType().ToArray();
+ foreach (var p in parameters)
yield return p;
foreach (var f in PopulateFlags)
yield return f;
- yield return Tuples.cil_method(this, Name, DeclaringType, constructedTypeSignature.ReturnType);
+ foreach (var m in GetMethodExtractionProducts(Name, DeclaringType, constructedTypeSignature.ReturnType))
+ {
+ yield return m;
+ }
+
yield return Tuples.cil_method_source_declaration(this, SourceDeclaration);
if (typeParams.Length != unboundMethod.GenericParameterCount)
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/MethodTypeParameter.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/MethodTypeParameter.cs
index 38790ce3827..db5e56acb17 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/MethodTypeParameter.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/MethodTypeParameter.cs
@@ -41,8 +41,6 @@ namespace Semmle.Extraction.CIL.Entities
public override IEnumerable TypeParameters => throw new NotImplementedException();
- public override IEnumerable MethodParameters => throw new NotImplementedException();
-
public override IEnumerable Contents
{
get
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/ModifiedType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/ModifiedType.cs
new file mode 100644
index 00000000000..f160c6869de
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/ModifiedType.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Semmle.Extraction.CIL.Entities
+{
+ ///
+ /// Modified types are not written directly to trap files. Instead, the modifiers are stored
+ /// on the modifiable entity (field type, property/method/function pointer parameter or return types).
+ ///
+ internal sealed class ModifiedType : Type
+ {
+ public ModifiedType(Context cx, Type unmodified, Type modifier, bool isRequired) : base(cx)
+ {
+ Unmodified = unmodified;
+ Modifier = modifier;
+ IsRequired = isRequired;
+ }
+
+ public Type Unmodified { get; }
+ public Type Modifier { get; }
+ public bool IsRequired { get; }
+
+ public override CilTypeKind Kind => throw new NotImplementedException();
+
+ public override Namespace? ContainingNamespace => throw new NotImplementedException();
+
+ public override Type? ContainingType => throw new NotImplementedException();
+
+ public override IEnumerable TypeParameters => throw new NotImplementedException();
+
+ public override int ThisTypeParameterCount => throw new NotImplementedException();
+
+ public override Type Construct(IEnumerable typeArguments) => throw new NotImplementedException();
+
+ public override string Name => $"{Unmodified.Name} {(IsRequired ? "modreq" : "modopt")}({Modifier.Name})";
+
+ public override void WriteAssemblyPrefix(TextWriter trapFile) => throw new NotImplementedException();
+
+ public override void WriteId(TextWriter trapFile, bool inContext)
+ {
+ Unmodified.WriteId(trapFile, inContext);
+ trapFile.Write(IsRequired ? " modreq" : " modopt");
+ trapFile.Write("(");
+ Modifier.WriteId(trapFile, inContext);
+ trapFile.Write(")");
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/NamedTypeIdWriter.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/NamedTypeIdWriter.cs
new file mode 100644
index 00000000000..98f9e94ec82
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/NamedTypeIdWriter.cs
@@ -0,0 +1,62 @@
+using System.Linq;
+using System.IO;
+
+namespace Semmle.Extraction.CIL.Entities
+{
+ internal class NamedTypeIdWriter
+ {
+ private readonly Type type;
+
+ public NamedTypeIdWriter(Type type)
+ {
+ this.type = type;
+ }
+
+ public void WriteId(TextWriter trapFile, bool inContext)
+ {
+ if (type.IsPrimitiveType)
+ {
+ Type.WritePrimitiveTypeId(trapFile, type.Name);
+ return;
+ }
+
+ var ct = type.ContainingType;
+ if (ct != null)
+ {
+ ct.WriteId(trapFile, inContext);
+ trapFile.Write('.');
+ }
+ else
+ {
+ type.WriteAssemblyPrefix(trapFile);
+
+ var ns = type.ContainingNamespace!;
+ if (!ns.IsGlobalNamespace)
+ {
+ ns.WriteId(trapFile);
+ trapFile.Write('.');
+ }
+ }
+
+ trapFile.Write(type.Name);
+
+ var thisTypeArguments = type.ThisTypeArguments;
+ if (thisTypeArguments != null && thisTypeArguments.Any())
+ {
+ trapFile.Write('<');
+ var index = 0;
+ foreach (var t in thisTypeArguments)
+ {
+ trapFile.WriteSeparator(",", ref index);
+ t.WriteId(trapFile);
+ }
+ trapFile.Write('>');
+ }
+ else if (type.ThisTypeParameterCount > 0)
+ {
+ trapFile.Write('`');
+ trapFile.Write(type.ThisTypeParameterCount);
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/NoMetadataHandleType.FullyQualifiedNameParser.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/NoMetadataHandleType.FullyQualifiedNameParser.cs
new file mode 100644
index 00000000000..999259e9ad6
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/NoMetadataHandleType.FullyQualifiedNameParser.cs
@@ -0,0 +1,163 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Semmle.Util;
+
+namespace Semmle.Extraction.CIL.Entities
+{
+ internal sealed partial class NoMetadataHandleType
+ {
+ ///
+ /// Parser to split a fully qualified name into short name, namespace or declaring type name, assembly name, and
+ /// type argument names. Names are in the following format:
+ /// N1.N2.T1`2+T2`2[T3,[T4, A1, Version=...],T5,T6], A2, Version=...
+ ///
+ ///
+ /// typeof(System.Collections.Generic.List.Enumerator)
+ /// -> System.Collections.Generic.List`1+Enumerator[[System.Int32, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
+ /// typeof(System.Collections.Generic.List<>.Enumerator)
+ /// -> System.Collections.Generic.List`1+Enumerator, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
+ ///
+ ///
+ private class FullyQualifiedNameParser
+ {
+ public string ShortName { get; internal set; }
+ public string? AssemblyName { get; private set; }
+ public IEnumerable? TypeArguments { get; internal set; }
+ public string? UnboundGenericTypeName { get; internal set; }
+ public string ContainerName { get; internal set; }
+ public bool IsContainerNamespace { get; internal set; }
+
+ private string AssemblySuffix => string.IsNullOrWhiteSpace(AssemblyName) ? "" : $", {AssemblyName}";
+
+ public FullyQualifiedNameParser(string name)
+ {
+ ExtractAssemblyName(ref name, out var lastBracketIndex);
+ ExtractTypeArguments(ref name, lastBracketIndex, out var containerTypeArguments);
+ ContainerName = ExtractContainer(ref name, containerTypeArguments);
+
+ ShortName = name;
+ }
+
+ private void ExtractTypeArguments(ref string name, int lastBracketIndex, out string containerTypeArguments)
+ {
+ var firstBracketIndex = name.IndexOf('[');
+ if (firstBracketIndex < 0)
+ {
+ // not generic or non-constructed generic
+ TypeArguments = null;
+ containerTypeArguments = "";
+ UnboundGenericTypeName = null;
+ return;
+ }
+
+ // "T3,[T4, Assembly1, Version=...],T5,T6"
+ string typeArgs;
+ (name, _, typeArgs, _) = name.Split(firstBracketIndex, firstBracketIndex + 1, lastBracketIndex - firstBracketIndex - 1);
+
+ var thisTypeArgCount = GenericsHelper.GetGenericTypeParameterCount(name);
+ if (thisTypeArgCount == 0)
+ {
+ // not generic or non-constructed generic; container is constructed
+ TypeArguments = null;
+ containerTypeArguments = $"[{typeArgs}]";
+ UnboundGenericTypeName = null;
+ return;
+ }
+
+ // constructed generic
+ // "T3,[T4, Assembly1, Version=...]", ["T5", "T6"]
+ var (containerTypeArgs, thisTypeArgs) = ParseTypeArgumentStrings(typeArgs, thisTypeArgCount);
+
+ TypeArguments = thisTypeArgs;
+
+ containerTypeArguments = string.IsNullOrWhiteSpace(containerTypeArgs)
+ ? "" // containing type is not constructed generics
+ : $"[{containerTypeArgs}]"; // "T3,[T4, Assembly1, Version=...],,]"
+
+ UnboundGenericTypeName = $"{name}{AssemblySuffix}";
+ }
+
+ private string ExtractContainer(ref string name, string containerTypeArguments)
+ {
+ var lastPlusIndex = name.LastIndexOf('+');
+ IsContainerNamespace = lastPlusIndex < 0;
+ if (IsContainerNamespace)
+ {
+ return ExtractContainerNamespace(ref name);
+ }
+
+ return ExtractContainerType(ref name, containerTypeArguments, lastPlusIndex);
+ }
+
+ private static string ExtractContainerNamespace(ref string name)
+ {
+ var lastDotIndex = name.LastIndexOf('.');
+ if (lastDotIndex >= 0)
+ {
+ string containerName;
+ (containerName, _, name) = name.Split(lastDotIndex, lastDotIndex + 1);
+ return containerName;
+ }
+
+ return ""; // global namespace name
+ }
+
+ private string ExtractContainerType(ref string name, string containerTypeArguments, int lastPlusIndex)
+ {
+ string containerName;
+ (containerName, _, name) = name.Split(lastPlusIndex, lastPlusIndex + 1);
+ return $"{containerName}{containerTypeArguments}{AssemblySuffix}";
+ }
+
+ private void ExtractAssemblyName(ref string name, out int lastBracketIndex)
+ {
+ lastBracketIndex = name.LastIndexOf(']');
+ var assemblyCommaIndex = name.IndexOf(',', lastBracketIndex < 0 ? 0 : lastBracketIndex);
+ if (assemblyCommaIndex >= 0)
+ {
+ // "Assembly2, Version=..."
+ (name, _, AssemblyName) = name.Split(assemblyCommaIndex, assemblyCommaIndex + 2);
+ }
+ }
+
+ private static (string, IEnumerable) ParseTypeArgumentStrings(string typeArgs, int thisTypeArgCount)
+ {
+ var thisTypeArgs = new Stack(thisTypeArgCount);
+ while (typeArgs.Length > 0 && thisTypeArgCount > 0)
+ {
+ int startCurrentType;
+ if (typeArgs[^1] != ']')
+ {
+ startCurrentType = typeArgs.LastIndexOf(',') + 1;
+ thisTypeArgs.Push(typeArgs.Substring(startCurrentType));
+ }
+ else
+ {
+ var bracketCount = 1;
+ for (startCurrentType = typeArgs.Length - 2; startCurrentType >= 0 && bracketCount > 0; startCurrentType--)
+ {
+ if (typeArgs[startCurrentType] == ']')
+ {
+ bracketCount++;
+ }
+ else if (typeArgs[startCurrentType] == '[')
+ {
+ bracketCount--;
+ }
+ }
+ startCurrentType++;
+ thisTypeArgs.Push(typeArgs[(startCurrentType + 1)..^1]);
+ }
+
+ typeArgs = startCurrentType != 0
+ ? typeArgs.Substring(0, startCurrentType - 1)
+ : "";
+
+ thisTypeArgCount--;
+ }
+ return (typeArgs, thisTypeArgs.ToList());
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/NoMetadataHandleType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/NoMetadataHandleType.cs
new file mode 100644
index 00000000000..c1259534f79
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/NoMetadataHandleType.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+
+namespace Semmle.Extraction.CIL.Entities
+{
+ internal sealed partial class NoMetadataHandleType : Type
+ {
+ private readonly string originalName;
+ private readonly string name;
+ private readonly string? assemblyName;
+ private readonly string containerName;
+ private readonly bool isContainerNamespace;
+
+ private readonly Lazy? typeParams;
+
+ // Either null or notEmpty
+ private readonly Type[]? thisTypeArguments;
+ private readonly Type unboundGenericType;
+ private readonly Type? containingType;
+ private readonly Namespace? containingNamespace;
+
+ private readonly NamedTypeIdWriter idWriter;
+
+ public NoMetadataHandleType(Context cx, string originalName) : base(cx)
+ {
+ this.originalName = originalName;
+ this.idWriter = new NamedTypeIdWriter(this);
+
+ var nameParser = new FullyQualifiedNameParser(originalName);
+
+ name = nameParser.ShortName;
+ assemblyName = nameParser.AssemblyName;
+ isContainerNamespace = nameParser.IsContainerNamespace;
+ containerName = nameParser.ContainerName;
+
+ unboundGenericType = nameParser.UnboundGenericTypeName == null
+ ? this
+ : new NoMetadataHandleType(Cx, nameParser.UnboundGenericTypeName);
+
+ if (nameParser.TypeArguments != null)
+ {
+ thisTypeArguments = nameParser.TypeArguments.Select(t => new NoMetadataHandleType(Cx, t)).ToArray();
+ }
+ else
+ {
+ typeParams = new Lazy(GenericsHelper.MakeTypeParameters(this, ThisTypeParameterCount));
+ }
+
+ containingType = isContainerNamespace
+ ? null
+ : new NoMetadataHandleType(Cx, containerName);
+
+ containingNamespace = isContainerNamespace
+ ? containerName == Cx.GlobalNamespace.Name
+ ? Cx.GlobalNamespace
+ : containerName == Cx.SystemNamespace.Name
+ ? Cx.SystemNamespace
+ : new Namespace(Cx, containerName)
+ : null;
+
+ Populate();
+ }
+
+ private void Populate()
+ {
+ if (ContainingNamespace is object)
+ {
+ Cx.Populate(ContainingNamespace);
+ }
+
+ Cx.Populate(this);
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is NoMetadataHandleType t && originalName.Equals(t.originalName, StringComparison.Ordinal);
+ }
+
+ public override int GetHashCode()
+ {
+ return originalName.GetHashCode(StringComparison.Ordinal);
+ }
+
+ public override IEnumerable Contents
+ {
+ get
+ {
+ foreach (var tp in typeParams?.Value ?? Array.Empty())
+ yield return tp;
+
+ foreach (var c in base.Contents)
+ yield return c;
+
+ var i = 0;
+ foreach (var type in ThisTypeArguments)
+ {
+ yield return type;
+ yield return Tuples.cil_type_argument(this, i++, type);
+ }
+ }
+ }
+
+ public override CilTypeKind Kind => CilTypeKind.ValueOrRefType;
+
+ public override string Name => GenericsHelper.GetNonGenericName(name);
+
+ public override Namespace? ContainingNamespace => containingNamespace;
+
+ public override Type? ContainingType => containingType;
+
+ public override Type SourceDeclaration => unboundGenericType;
+
+ public override Type Construct(IEnumerable typeArguments)
+ {
+ if (TotalTypeParametersCount != typeArguments.Count())
+ throw new InternalError("Mismatched type arguments");
+
+ return Cx.Populate(new ConstructedType(Cx, this, typeArguments));
+ }
+
+ public override void WriteAssemblyPrefix(TextWriter trapFile)
+ {
+ if (!string.IsNullOrWhiteSpace(assemblyName))
+ {
+ var an = new AssemblyName(assemblyName);
+ trapFile.Write(an.Name);
+ trapFile.Write('_');
+ trapFile.Write((an.Version ?? new Version(0, 0, 0, 0)).ToString());
+ trapFile.Write(Type.AssemblyTypeNameSeparator);
+ }
+ else
+ {
+ Cx.WriteAssemblyPrefix(trapFile);
+ }
+ }
+
+ public override void WriteId(TextWriter trapFile, bool inContext)
+ {
+ idWriter.WriteId(trapFile, inContext);
+ }
+
+ public override int ThisTypeParameterCount => unboundGenericType == this
+ ? GenericsHelper.GetGenericTypeParameterCount(name)
+ : thisTypeArguments!.Length;
+
+ public override IEnumerable TypeParameters => unboundGenericType == this
+ ? GenericsHelper.GetAllTypeParameters(containingType, typeParams!.Value)
+ : GenericArguments;
+
+ public override IEnumerable ThisTypeArguments => unboundGenericType == this
+ ? base.ThisTypeArguments
+ : thisTypeArguments!;
+
+ public override IEnumerable ThisGenericArguments => unboundGenericType == this
+ ? typeParams!.Value
+ : thisTypeArguments!;
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Parameter.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Parameter.cs
index dea5f2db30b..90452fe9265 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Parameter.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Parameter.cs
@@ -8,11 +8,11 @@ namespace Semmle.Extraction.CIL.Entities
///
internal sealed class Parameter : LabelledEntity
{
- private readonly Method method;
+ private readonly IParameterizable method;
private readonly int index;
private readonly Type type;
- public Parameter(Context cx, Method m, int i, Type t) : base(cx)
+ public Parameter(Context cx, IParameterizable m, int i, Type t) : base(cx)
{
method = m;
index = i;
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/PointerType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/PointerType.cs
index 2f12de982ef..8b1209925b6 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/PointerType.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/PointerType.cs
@@ -18,11 +18,7 @@ namespace Semmle.Extraction.CIL.Entities
return obj is PointerType pt && pointee.Equals(pt.pointee);
}
-
- public override int GetHashCode()
- {
- return pointee.GetHashCode() * 29;
- }
+ public override int GetHashCode() => HashCode.Combine(pointee, nameof(PointerType));
public override void WriteId(TextWriter trapFile, bool inContext)
{
@@ -32,13 +28,13 @@ namespace Semmle.Extraction.CIL.Entities
public override string Name => pointee.Name + "*";
- public override Namespace? Namespace => pointee.Namespace;
+ public override Namespace? ContainingNamespace => pointee.ContainingNamespace;
public override Type? ContainingType => pointee.ContainingType;
public override TypeContainer Parent => pointee.Parent;
- public override int ThisTypeParameters => 0;
+ public override int ThisTypeParameterCount => 0;
public override CilTypeKind Kind => CilTypeKind.Pointer;
@@ -46,8 +42,6 @@ namespace Semmle.Extraction.CIL.Entities
public override IEnumerable TypeParameters => throw new NotImplementedException();
- public override IEnumerable MethodParameters => throw new NotImplementedException();
-
public override Type Construct(IEnumerable typeArguments) => throw new NotImplementedException();
public override IEnumerable Contents
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/PrimitiveType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/PrimitiveType.cs
index 5ec0911c5ef..661ae0f535b 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/PrimitiveType.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/PrimitiveType.cs
@@ -18,24 +18,20 @@ namespace Semmle.Extraction.CIL.Entities
return obj is PrimitiveType pt && typeCode == pt.typeCode;
}
- public override int GetHashCode()
- {
- return 1337 * (int)typeCode;
- }
+ public override int GetHashCode() => typeCode.GetHashCode();
public override void WriteId(TextWriter trapFile, bool inContext)
{
- trapFile.Write("builtin:");
- trapFile.Write(Name);
+ Type.WritePrimitiveTypeId(trapFile, Name);
}
public override string Name => typeCode.Id();
- public override Namespace Namespace => Cx.SystemNamespace;
+ public override Namespace ContainingNamespace => Cx.SystemNamespace;
public override Type? ContainingType => null;
- public override int ThisTypeParameters => 0;
+ public override int ThisTypeParameterCount => 0;
public override CilTypeKind Kind => CilTypeKind.ValueOrRefType;
@@ -43,8 +39,6 @@ namespace Semmle.Extraction.CIL.Entities
public override IEnumerable TypeParameters => throw new NotImplementedException();
- public override IEnumerable MethodParameters => throw new NotImplementedException();
-
public override Type Construct(IEnumerable typeArguments) => throw new NotImplementedException();
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Property.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Property.cs
index 6fc588b1d34..5fb22b40409 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Property.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Property.cs
@@ -8,7 +8,7 @@ namespace Semmle.Extraction.CIL.Entities
///
/// A property.
///
- internal sealed class Property : LabelledEntity
+ internal sealed class Property : LabelledEntity, ICustomModifierReceiver
{
private readonly Handle handle;
private readonly Type type;
@@ -54,7 +54,20 @@ namespace Semmle.Extraction.CIL.Entities
yield return Tuples.metadata_handle(this, Cx.Assembly, MetadataTokens.GetToken(handle));
var sig = pd.DecodeSignature(Cx.TypeSignatureDecoder, type);
- yield return Tuples.cil_property(this, type, Cx.ShortName(pd.Name), sig.ReturnType);
+ var name = Cx.ShortName(pd.Name);
+
+ var t = sig.ReturnType;
+ if (t is ModifiedType mt)
+ {
+ t = mt.Unmodified;
+ yield return Tuples.cil_custom_modifiers(this, mt.Modifier, mt.IsRequired);
+ }
+ if (t is ByRefType brt)
+ {
+ t = brt.ElementType;
+ yield return Tuples.cil_type_annotation(this, TypeAnnotation.Ref);
+ }
+ yield return Tuples.cil_property(this, type, name, t);
var accessors = pd.GetAccessors();
if (!accessors.Getter.IsNil)
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/SignatureDecoder.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/SignatureDecoder.cs
index fcb860b6d13..a955ef685c0 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/SignatureDecoder.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/SignatureDecoder.cs
@@ -22,7 +22,9 @@ namespace Semmle.Extraction.CIL.Entities
elementType.WriteId(trapFile, gc);
trapFile.Write('[');
for (var i = 1; i < shape.Rank; ++i)
+ {
trapFile.Write(',');
+ }
trapFile.Write(']');
}
}
@@ -38,17 +40,27 @@ namespace Semmle.Extraction.CIL.Entities
public void WriteId(TextWriter trapFile, GenericContext gc)
{
- trapFile.Write("ref ");
elementType.WriteId(trapFile, gc);
+ trapFile.Write('&');
}
}
private struct FnPtr : ITypeSignature
{
+ private readonly MethodSignature signature;
+
+ public FnPtr(MethodSignature signature)
+ {
+ this.signature = signature;
+ }
public void WriteId(TextWriter trapFile, GenericContext gc)
{
- trapFile.Write("");
+ FunctionPointerType.WriteName(
+ trapFile.Write,
+ t => t.WriteId(trapFile, gc),
+ signature
+ );
}
}
@@ -59,7 +71,7 @@ namespace Semmle.Extraction.CIL.Entities
new ByRef(elementType);
ITypeSignature ISignatureTypeProvider.GetFunctionPointerType(MethodSignature signature) =>
- new FnPtr();
+ new FnPtr(signature);
private class Instantiation : ITypeSignature
{
@@ -136,42 +148,33 @@ namespace Semmle.Extraction.CIL.Entities
private class Modified : ITypeSignature
{
private readonly ITypeSignature unmodifiedType;
+ private readonly ITypeSignature modifier;
+ private readonly bool isRequired;
- public Modified(ITypeSignature unmodifiedType)
+ public Modified(ITypeSignature unmodifiedType, ITypeSignature modifier, bool isRequired)
{
this.unmodifiedType = unmodifiedType;
+ this.modifier = modifier;
+ this.isRequired = isRequired;
}
public void WriteId(TextWriter trapFile, GenericContext gc)
{
unmodifiedType.WriteId(trapFile, gc);
+ trapFile.Write(isRequired ? " modreq(" : " modopt(");
+ modifier.WriteId(trapFile, gc);
+ trapFile.Write(")");
}
}
ITypeSignature ISignatureTypeProvider.GetModifiedType(ITypeSignature modifier, ITypeSignature unmodifiedType, bool isRequired)
{
- return new Modified(unmodifiedType);
- }
-
- private class Pinned : ITypeSignature
- {
- private readonly ITypeSignature elementType;
-
- public Pinned(ITypeSignature elementType)
- {
- this.elementType = elementType;
- }
-
- public void WriteId(TextWriter trapFile, GenericContext gc)
- {
- trapFile.Write("pinned ");
- elementType.WriteId(trapFile, gc);
- }
+ return new Modified(unmodifiedType, modifier, isRequired);
}
ITypeSignature ISignatureTypeProvider.GetPinnedType(ITypeSignature elementType)
{
- return new Pinned(elementType);
+ return elementType;
}
private class PointerType : ITypeSignature
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Type.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Type.cs
index 5434e55aa69..edeb1a54136 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Type.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Type.cs
@@ -1,8 +1,8 @@
using System.Reflection.Metadata;
using System.Collections.Generic;
-using Semmle.Util;
using System.IO;
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
namespace Semmle.Extraction.CIL.Entities
{
@@ -12,8 +12,10 @@ namespace Semmle.Extraction.CIL.Entities
public abstract class Type : TypeContainer, IMember
{
public override string IdSuffix => ";cil-type";
+ internal const string AssemblyTypeNameSeparator = "::";
+ internal const string PrimitiveTypePrefix = "builtin" + AssemblyTypeNameSeparator + "System.";
- public sealed override void WriteId(TextWriter trapFile) => WriteId(trapFile, false);
+ protected Type(Context cx) : base(cx) { }
///
/// Find the method in this type matching the name and signature.
@@ -29,29 +31,6 @@ namespace Semmle.Extraction.CIL.Entities
return null;
}
- public IEnumerable TypeArguments
- {
- get
- {
- if (ContainingType != null)
- {
- foreach (var t in ContainingType.TypeArguments)
- yield return t;
- }
- foreach (var t in ThisTypeArguments)
- yield return t;
-
- }
- }
-
- public virtual IEnumerable ThisTypeArguments
- {
- get
- {
- yield break;
- }
- }
-
///
/// Writes the assembly identifier of this type.
///
@@ -67,22 +46,24 @@ namespace Semmle.Extraction.CIL.Entities
///
public abstract void WriteId(TextWriter trapFile, bool inContext);
- public void GetId(TextWriter trapFile, bool inContext)
+ public sealed override void WriteId(TextWriter trapFile) => WriteId(trapFile, false);
+
+ ///
+ /// Returns the friendly qualified name of types, such as
+ /// ``"System.Collection.Generic.List`1"`` or
+ /// ``"System.Collection.Generic.List"``.
+ ///
+ /// Note that method/type generic type parameters never show up in the returned name.
+ ///
+ public string GetQualifiedName()
{
- if (inContext)
- WriteId(trapFile, true);
- else
- WriteId(trapFile);
+ using var writer = new StringWriter();
+ WriteId(writer, false);
+ var name = writer.ToString();
+ return name.Substring(name.IndexOf(AssemblyTypeNameSeparator) + 2);
}
- protected Type(Context cx) : base(cx) { }
-
- public abstract CilTypeKind Kind
- {
- get;
- }
-
- public virtual TypeContainer Parent => (TypeContainer?)ContainingType ?? Namespace!;
+ public abstract CilTypeKind Kind { get; }
public override IEnumerable Contents
{
@@ -92,35 +73,43 @@ namespace Semmle.Extraction.CIL.Entities
}
}
- ///
- /// Whether this type is visible outside the current assembly.
- ///
- public virtual bool IsVisible => true;
-
public abstract string Name { get; }
- public abstract Namespace? Namespace { get; }
+ public abstract Namespace? ContainingNamespace { get; }
public abstract Type? ContainingType { get; }
+ public virtual TypeContainer Parent => (TypeContainer?)ContainingType ?? ContainingNamespace!;
+
public abstract Type Construct(IEnumerable typeArguments);
///
- /// The number of type arguments, or 0 if this isn't generic.
- /// The containing type may also have type arguments.
+ /// Returns the type arguments of constructed types. For non-constructed types it returns an
+ /// empty collection.
///
- public abstract int ThisTypeParameters { get; }
+ public virtual IEnumerable ThisTypeArguments
+ {
+ get
+ {
+ yield break;
+ }
+ }
///
- /// The total number of type parameters (including parent types).
+ /// The number of type parameters for non-constructed generic types, the number of type arguments
+ /// for constructed types, or 0.
+ ///
+ public abstract int ThisTypeParameterCount { get; }
+
+ ///
+ /// The total number of type parameters/type arguments (including parent types).
/// This is used for internal consistency checking only.
///
- public int TotalTypeParametersCheck =>
- ContainingType == null ? ThisTypeParameters : ThisTypeParameters + ContainingType.TotalTypeParametersCheck;
+ public int TotalTypeParametersCount =>
+ ThisTypeParameterCount + (ContainingType?.TotalTypeParametersCount ?? 0);
///
- /// Returns all bound/unbound generic arguments
- /// of a constructed/unbound generic type.
+ /// Returns all bound/unbound generic arguments of a constructed/unbound generic type.
///
public virtual IEnumerable ThisGenericArguments
{
@@ -139,6 +128,7 @@ namespace Semmle.Extraction.CIL.Entities
foreach (var t in ContainingType.GenericArguments)
yield return t;
}
+
foreach (var t in ThisGenericArguments)
yield return t;
}
@@ -146,12 +136,34 @@ namespace Semmle.Extraction.CIL.Entities
public virtual Type SourceDeclaration => this;
- public void PrimitiveTypeId(TextWriter trapFile)
+ public static void WritePrimitiveTypeId(TextWriter trapFile, string name)
{
- trapFile.Write("builtin:");
- trapFile.Write(Name);
+ trapFile.Write(PrimitiveTypePrefix);
+ trapFile.Write(name);
}
+ private static readonly Dictionary primitiveTypeCodeMapping = new Dictionary
+ {
+ {"Boolean", PrimitiveTypeCode.Boolean},
+ {"Object", PrimitiveTypeCode.Object},
+ {"Byte", PrimitiveTypeCode.Byte},
+ {"SByte", PrimitiveTypeCode.SByte},
+ {"Int16", PrimitiveTypeCode.Int16},
+ {"UInt16", PrimitiveTypeCode.UInt16},
+ {"Int32", PrimitiveTypeCode.Int32},
+ {"UInt32", PrimitiveTypeCode.UInt32},
+ {"Int64", PrimitiveTypeCode.Int64},
+ {"UInt64", PrimitiveTypeCode.UInt64},
+ {"Single", PrimitiveTypeCode.Single},
+ {"Double", PrimitiveTypeCode.Double},
+ {"String", PrimitiveTypeCode.String},
+ {"Void", PrimitiveTypeCode.Void},
+ {"IntPtr", PrimitiveTypeCode.IntPtr},
+ {"UIntPtr", PrimitiveTypeCode.UIntPtr},
+ {"Char", PrimitiveTypeCode.Char},
+ {"TypedReference", PrimitiveTypeCode.TypedReference}
+ };
+
///
/// Gets the primitive type corresponding to this type, if possible.
///
@@ -171,72 +183,20 @@ namespace Semmle.Extraction.CIL.Entities
private bool TryGetPrimitiveTypeCode(out PrimitiveTypeCode code)
{
- if (ContainingType == null && Namespace?.Name == Cx.SystemNamespace.Name)
+ if (ContainingType == null &&
+ ContainingNamespace?.Name == Cx.SystemNamespace.Name &&
+ primitiveTypeCodeMapping.TryGetValue(Name, out code))
{
- switch (Name)
- {
- case "Boolean":
- code = PrimitiveTypeCode.Boolean;
- return true;
- case "Object":
- code = PrimitiveTypeCode.Object;
- return true;
- case "Byte":
- code = PrimitiveTypeCode.Byte;
- return true;
- case "SByte":
- code = PrimitiveTypeCode.SByte;
- return true;
- case "Int16":
- code = PrimitiveTypeCode.Int16;
- return true;
- case "UInt16":
- code = PrimitiveTypeCode.UInt16;
- return true;
- case "Int32":
- code = PrimitiveTypeCode.Int32;
- return true;
- case "UInt32":
- code = PrimitiveTypeCode.UInt32;
- return true;
- case "Int64":
- code = PrimitiveTypeCode.Int64;
- return true;
- case "UInt64":
- code = PrimitiveTypeCode.UInt64;
- return true;
- case "Single":
- code = PrimitiveTypeCode.Single;
- return true;
- case "Double":
- code = PrimitiveTypeCode.Double;
- return true;
- case "String":
- code = PrimitiveTypeCode.String;
- return true;
- case "Void":
- code = PrimitiveTypeCode.Void;
- return true;
- case "IntPtr":
- code = PrimitiveTypeCode.IntPtr;
- return true;
- case "UIntPtr":
- code = PrimitiveTypeCode.UIntPtr;
- return true;
- case "Char":
- code = PrimitiveTypeCode.Char;
- return true;
- case "TypedReference":
- code = PrimitiveTypeCode.TypedReference;
- return true;
- }
+ return true;
}
- code = default(PrimitiveTypeCode);
+ code = default;
return false;
}
- protected bool IsPrimitiveType => TryGetPrimitiveTypeCode(out _);
+ protected internal bool IsPrimitiveType => TryGetPrimitiveTypeCode(out _);
+
+ public sealed override IEnumerable MethodParameters => Enumerable.Empty();
public static Type DecodeType(GenericContext gc, TypeSpecificationHandle handle) =>
gc.Cx.MdReader.GetTypeSpecification(handle).DecodeSignature(gc.Cx.TypeSignatureDecoder, gc);
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeAnnotation.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeAnnotation.cs
new file mode 100644
index 00000000000..fe005326b95
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeAnnotation.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Semmle.Extraction.CIL.Entities
+{
+ [Flags]
+ public enum TypeAnnotation
+ {
+ None = 0,
+ Ref = 32
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeDefinitionType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeDefinitionType.cs
index 61a761b4cba..461141ae719 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeDefinitionType.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeDefinitionType.cs
@@ -1,11 +1,10 @@
using System;
-using Microsoft.CodeAnalysis;
-using System.Reflection.Metadata;
-using System.Collections.Immutable;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using System.IO;
+using System.Reflection.Metadata.Ecma335;
+using System.Reflection.Metadata;
namespace Semmle.Extraction.CIL.Entities
{
@@ -14,11 +13,15 @@ namespace Semmle.Extraction.CIL.Entities
///
public sealed class TypeDefinitionType : Type
{
- private readonly Handle handle;
+ private readonly TypeDefinitionHandle handle;
private readonly TypeDefinition td;
+ private readonly Lazy> typeParams;
+ private readonly Type? declType;
+ private readonly NamedTypeIdWriter idWriter;
public TypeDefinitionType(Context cx, TypeDefinitionHandle handle) : base(cx)
{
+ idWriter = new NamedTypeIdWriter(this);
td = cx.MdReader.GetTypeDefinition(handle);
this.handle = handle;
@@ -39,66 +42,22 @@ namespace Semmle.Extraction.CIL.Entities
public override void WriteId(TextWriter trapFile, bool inContext)
{
- if (IsPrimitiveType)
- {
- PrimitiveTypeId(trapFile);
- return;
- }
-
- var name = Cx.GetString(td.Name);
-
- if (ContainingType != null)
- {
- ContainingType.GetId(trapFile, inContext);
- trapFile.Write('.');
- }
- else
- {
- WriteAssemblyPrefix(trapFile);
-
- var ns = Namespace;
- if (!ns.IsGlobalNamespace)
- {
- ns.WriteId(trapFile);
- trapFile.Write('.');
- }
- }
-
- trapFile.Write(name);
+ idWriter.WriteId(trapFile, inContext);
}
- public override string Name
- {
- get
- {
- var name = Cx.GetString(td.Name);
- var tick = name.IndexOf('`');
- return tick == -1 ? name : name.Substring(0, tick);
- }
- }
+ public override string Name => GenericsHelper.GetNonGenericName(td.Name, Cx.MdReader);
- public override Namespace Namespace => Cx.Create(td.NamespaceDefinition);
-
- private readonly Type? declType;
+ public override Namespace ContainingNamespace => Cx.Create(td.NamespaceDefinition);
public override Type? ContainingType => declType;
- public override int ThisTypeParameters
- {
- get
- {
- var containingType = td.GetDeclaringType();
- var parentTypeParameters = containingType.IsNil ? 0 :
- Cx.MdReader.GetTypeDefinition(containingType).GetGenericParameters().Count;
-
- return td.GetGenericParameters().Count - parentTypeParameters;
- }
- }
-
public override CilTypeKind Kind => CilTypeKind.ValueOrRefType;
public override Type Construct(IEnumerable typeArguments)
{
+ if (TotalTypeParametersCount != typeArguments.Count())
+ throw new InternalError("Mismatched type arguments");
+
return Cx.Populate(new ConstructedType(Cx, this, typeArguments));
}
@@ -108,52 +67,50 @@ namespace Semmle.Extraction.CIL.Entities
if (ct is null)
Cx.WriteAssemblyPrefix(trapFile);
else if (IsPrimitiveType)
- trapFile.Write("builtin:");
+ trapFile.Write(Type.PrimitiveTypePrefix);
else
ct.WriteAssemblyPrefix(trapFile);
}
private IEnumerable MakeTypeParameters()
{
- if (ThisTypeParameters == 0)
+ if (ThisTypeParameterCount == 0)
return Enumerable.Empty();
- var newTypeParams = new TypeTypeParameter[ThisTypeParameters];
+ var newTypeParams = new TypeTypeParameter[ThisTypeParameterCount];
var genericParams = td.GetGenericParameters();
var toSkip = genericParams.Count - newTypeParams.Length;
// Two-phase population because type parameters can be mutually dependent
for (var i = 0; i < newTypeParams.Length; ++i)
- newTypeParams[i] = Cx.Populate(new TypeTypeParameter(this, this, i));
+ newTypeParams[i] = Cx.Populate(new TypeTypeParameter(this, i));
for (var i = 0; i < newTypeParams.Length; ++i)
newTypeParams[i].PopulateHandle(genericParams[i + toSkip]);
return newTypeParams;
}
- private readonly Lazy> typeParams;
-
- public override IEnumerable MethodParameters => Enumerable.Empty();
-
- public override IEnumerable TypeParameters
+ public override int ThisTypeParameterCount
{
get
{
- if (declType != null)
- {
- foreach (var t in declType.TypeParameters)
- yield return t;
- }
+ var containingType = td.GetDeclaringType();
+ var parentTypeParameters = containingType.IsNil
+ ? 0
+ : Cx.MdReader.GetTypeDefinition(containingType).GetGenericParameters().Count;
- foreach (var t in typeParams.Value)
- yield return t;
+ return td.GetGenericParameters().Count - parentTypeParameters;
}
}
+ public override IEnumerable TypeParameters => GenericsHelper.GetAllTypeParameters(declType, typeParams!.Value);
+
+ public override IEnumerable ThisGenericArguments => typeParams.Value;
+
public override IEnumerable Contents
{
get
{
- yield return Tuples.metadata_handle(this, Cx.Assembly, handle.GetHashCode());
+ yield return Tuples.metadata_handle(this, Cx.Assembly, MetadataTokens.GetToken(handle));
foreach (var c in base.Contents) yield return c;
@@ -212,6 +169,15 @@ namespace Semmle.Extraction.CIL.Entities
var @base = (Type)Cx.CreateGeneric(this, td.BaseType);
yield return @base;
yield return Tuples.cil_base_class(this, @base);
+
+ if (IsSystemEnum(td.BaseType) &&
+ GetUnderlyingEnumType() is var underlying &&
+ underlying.HasValue)
+ {
+ var underlyingType = Cx.Create(underlying.Value);
+ yield return underlyingType;
+ yield return Tuples.cil_enum_underlying_type(this, underlyingType);
+ }
}
foreach (var @interface in td.GetInterfaceImplementations().Select(i => Cx.MdReader.GetInterfaceImplementation(i)))
@@ -226,6 +192,59 @@ namespace Semmle.Extraction.CIL.Entities
}
}
+ private bool IsSystemEnum(EntityHandle baseType)
+ {
+ return baseType.Kind switch
+ {
+ HandleKind.TypeReference => IsSystemEnum((TypeReferenceHandle)baseType),
+ HandleKind.TypeDefinition => IsSystemEnum((TypeDefinitionHandle)baseType),
+ _ => false,
+ };
+ }
+
+ private bool IsSystemEnum(TypeReferenceHandle baseType)
+ {
+ var baseTypeReference = Cx.MdReader.GetTypeReference(baseType);
+
+ return IsSystemEnum(baseTypeReference.Name, baseTypeReference.Namespace);
+ }
+
+ private bool IsSystemEnum(TypeDefinitionHandle baseType)
+ {
+ var baseTypeDefinition = Cx.MdReader.GetTypeDefinition(baseType);
+
+ return IsSystemEnum(baseTypeDefinition.Name, baseTypeDefinition.Namespace);
+ }
+
+ private bool IsSystemEnum(StringHandle typeName, StringHandle namespaceName)
+ {
+ return Cx.MdReader.StringComparer.Equals(typeName, "Enum") &&
+ !namespaceName.IsNil &&
+ Cx.MdReader.StringComparer.Equals(namespaceName, "System");
+ }
+
+ internal PrimitiveTypeCode? GetUnderlyingEnumType()
+ {
+ foreach (var handle in td.GetFields())
+ {
+ var field = Cx.MdReader.GetFieldDefinition(handle);
+ if (field.Attributes.HasFlag(FieldAttributes.Static))
+ {
+ continue;
+ }
+
+ var blob = Cx.MdReader.GetBlobReader(field.Signature);
+ if (blob.ReadSignatureHeader().Kind != SignatureKind.Field)
+ {
+ break;
+ }
+
+ return (PrimitiveTypeCode)blob.ReadByte();
+ }
+
+ return null;
+ }
+
internal override Method LookupMethod(StringHandle name, BlobHandle signature)
{
foreach (var h in td.GetMethods())
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeParameter.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeParameter.cs
index 91a1c6f925b..7c8d2222fa5 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeParameter.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeParameter.cs
@@ -16,11 +16,11 @@ namespace Semmle.Extraction.CIL.Entities
this.gc = gc;
}
- public override Namespace? Namespace => null;
+ public override Namespace? ContainingNamespace => null;
public override Type? ContainingType => null;
- public override int ThisTypeParameters => 0;
+ public override int ThisTypeParameterCount => 0;
public override CilTypeKind Kind => CilTypeKind.TypeParameter;
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeReferenceType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeReferenceType.cs
index ba789e87204..49a15965dd0 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeReferenceType.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeReferenceType.cs
@@ -1,7 +1,5 @@
using System;
-using Microsoft.CodeAnalysis;
using System.Reflection.Metadata;
-using System.Collections.Immutable;
using System.Linq;
using System.Collections.Generic;
using System.IO;
@@ -16,12 +14,14 @@ namespace Semmle.Extraction.CIL.Entities
private readonly TypeReferenceHandle handle;
private readonly TypeReference tr;
private readonly Lazy typeParams;
+ private readonly NamedTypeIdWriter idWriter;
public TypeReferenceType(Context cx, TypeReferenceHandle handle) : base(cx)
{
- this.typeParams = new Lazy(MakeTypeParameters);
+ this.idWriter = new NamedTypeIdWriter(this);
this.handle = handle;
this.tr = cx.MdReader.GetTypeReference(handle);
+ this.typeParams = new Lazy(GenericsHelper.MakeTypeParameters(this, ThisTypeParameterCount));
}
public override bool Equals(object? obj)
@@ -34,16 +34,6 @@ namespace Semmle.Extraction.CIL.Entities
return handle.GetHashCode();
}
- private TypeTypeParameter[] MakeTypeParameters()
- {
- var newTypeParams = new TypeTypeParameter[ThisTypeParameters];
- for (var i = 0; i < newTypeParams.Length; ++i)
- {
- newTypeParams[i] = new TypeTypeParameter(this, this, i);
- }
- return newTypeParams;
- }
-
public override IEnumerable Contents
{
get
@@ -56,45 +46,17 @@ namespace Semmle.Extraction.CIL.Entities
}
}
- public override string Name
- {
- get
- {
- var name = Cx.GetString(tr.Name);
- var tick = name.IndexOf('`');
- return tick == -1 ? name : name.Substring(0, tick);
- }
- }
+ public override string Name => GenericsHelper.GetNonGenericName(tr.Name, Cx.MdReader);
- public override Namespace Namespace => Cx.CreateNamespace(tr.Namespace);
-
- public override int ThisTypeParameters
- {
- get
- {
- // Parse the name
- var name = Cx.GetString(tr.Name);
- var tick = name.IndexOf('`');
- return tick == -1 ? 0 : int.Parse(name.Substring(tick + 1));
- }
- }
-
- public override IEnumerable ThisGenericArguments
- {
- get
- {
- foreach (var t in typeParams.Value)
- yield return t;
- }
- }
+ public override Namespace ContainingNamespace => Cx.CreateNamespace(tr.Namespace);
public override Type? ContainingType
{
get
{
- if (tr.ResolutionScope.Kind == HandleKind.TypeReference)
- return (Type)Cx.Create((TypeReferenceHandle)tr.ResolutionScope);
- return null;
+ return tr.ResolutionScope.Kind == HandleKind.TypeReference
+ ? (Type)Cx.Create((TypeReferenceHandle)tr.ResolutionScope)
+ : null;
}
}
@@ -112,7 +74,7 @@ namespace Semmle.Extraction.CIL.Entities
trapFile.Write(Cx.GetString(assemblyDef.Name));
trapFile.Write('_');
trapFile.Write(assemblyDef.Version.ToString());
- trapFile.Write("::");
+ trapFile.Write(Entities.Type.AssemblyTypeNameSeparator);
break;
default:
Cx.WriteAssemblyPrefix(trapFile);
@@ -120,43 +82,20 @@ namespace Semmle.Extraction.CIL.Entities
}
}
- public override IEnumerable TypeParameters => typeParams.Value;
+ public override int ThisTypeParameterCount => GenericsHelper.GetGenericTypeParameterCount(tr.Name, Cx.MdReader);
- public override IEnumerable MethodParameters => throw new InternalError("This type does not have method parameters");
+ public override IEnumerable TypeParameters => GenericsHelper.GetAllTypeParameters(ContainingType, typeParams!.Value);
+
+ public override IEnumerable ThisGenericArguments => typeParams.Value;
public override void WriteId(TextWriter trapFile, bool inContext)
{
- if (IsPrimitiveType)
- {
- PrimitiveTypeId(trapFile);
- return;
- }
-
- var ct = ContainingType;
- if (ct != null)
- {
- ct.GetId(trapFile, inContext);
- }
- else
- {
- if (tr.ResolutionScope.Kind == HandleKind.AssemblyReference)
- {
- WriteAssemblyPrefix(trapFile);
- }
-
- if (!Namespace.IsGlobalNamespace)
- {
- Namespace.WriteId(trapFile);
- }
- }
-
- trapFile.Write('.');
- trapFile.Write(Cx.GetString(tr.Name));
+ idWriter.WriteId(trapFile, inContext);
}
public override Type Construct(IEnumerable typeArguments)
{
- if (TotalTypeParametersCheck != typeArguments.Count())
+ if (TotalTypeParametersCount != typeArguments.Count())
throw new InternalError("Mismatched type arguments");
return Cx.Populate(new ConstructedType(Cx, this, typeArguments));
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeSignatureDecoder.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeSignatureDecoder.cs
index 632933edcd6..dcb986f1526 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeSignatureDecoder.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeSignatureDecoder.cs
@@ -20,10 +20,10 @@ namespace Semmle.Extraction.CIL.Entities
cx.Populate(new ArrayType(cx, elementType, shape.Rank));
Type IConstructedTypeProvider.GetByReferenceType(Type elementType) =>
- elementType; // ??
+ new ByRefType(cx, elementType);
Type ISignatureTypeProvider.GetFunctionPointerType(MethodSignature signature) =>
- cx.ErrorType; // Don't know what to do !!
+ cx.Populate(new FunctionPointerType(cx, signature));
Type IConstructedTypeProvider.GetGenericInstantiation(Type genericType, ImmutableArray typeArguments) =>
genericType.Construct(typeArguments);
@@ -35,10 +35,9 @@ namespace Semmle.Extraction.CIL.Entities
genericContext.GetGenericTypeParameter(index);
Type ISignatureTypeProvider.GetModifiedType(Type modifier, Type unmodifiedType, bool isRequired) =>
- unmodifiedType; // !! Not implemented properly
+ new ModifiedType(cx, unmodifiedType, modifier, isRequired);
- Type ISignatureTypeProvider.GetPinnedType(Type elementType) =>
- cx.Populate(new PointerType(cx, elementType));
+ Type ISignatureTypeProvider.GetPinnedType(Type elementType) => elementType;
Type IConstructedTypeProvider.GetPointerType(Type elementType) =>
cx.Populate(new PointerType(cx, elementType));
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeTypeParameter.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeTypeParameter.cs
index 790b8da2469..15db1536a3f 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeTypeParameter.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeTypeParameter.cs
@@ -9,7 +9,7 @@ namespace Semmle.Extraction.CIL.Entities
private readonly Type type;
private readonly int index;
- public TypeTypeParameter(GenericContext cx, Type t, int i) : base(cx)
+ public TypeTypeParameter(Type t, int i) : base(t)
{
index = i;
type = t;
@@ -37,8 +37,6 @@ namespace Semmle.Extraction.CIL.Entities
public override IEnumerable TypeParameters => Enumerable.Empty();
- public override IEnumerable MethodParameters => Enumerable.Empty();
-
public override IEnumerable Contents
{
get
diff --git a/csharp/extractor/Semmle.Extraction.CIL/GenericContext.cs b/csharp/extractor/Semmle.Extraction.CIL/GenericContext.cs
index 6dafcf1b1ea..6db62bf5bbf 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/GenericContext.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/GenericContext.cs
@@ -17,7 +17,7 @@ namespace Semmle.Extraction.CIL
}
///
- /// The list of generic type parameters, including type parameters of
+ /// The list of generic type parameters/arguments, including type parameters/arguments of
/// containing types.
///
public abstract IEnumerable TypeParameters { get; }
diff --git a/csharp/extractor/Semmle.Extraction.CIL/ICustomModifierReceiver.cs b/csharp/extractor/Semmle.Extraction.CIL/ICustomModifierReceiver.cs
new file mode 100644
index 00000000000..24d2c057b2e
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CIL/ICustomModifierReceiver.cs
@@ -0,0 +1,6 @@
+namespace Semmle.Extraction.CIL
+{
+ internal interface ICustomModifierReceiver
+ {
+ }
+}
\ No newline at end of file
diff --git a/csharp/extractor/Semmle.Extraction.CIL/PDB/MdProvider.cs b/csharp/extractor/Semmle.Extraction.CIL/PDB/MdProvider.cs
index c6c943032a6..fc22f1f6e94 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/PDB/MdProvider.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/PDB/MdProvider.cs
@@ -2,6 +2,8 @@ using System;
using Microsoft.DiaSymReader;
using System.Reflection;
+#pragma warning disable IDE0060, CA1822
+
namespace Semmle.Extraction.PDB
{
///
@@ -31,3 +33,5 @@ namespace Semmle.Extraction.PDB
throw new NotImplementedException();
}
}
+
+#pragma warning restore
diff --git a/csharp/extractor/Semmle.Extraction.CIL/PDB/MetadataPdbReader.cs b/csharp/extractor/Semmle.Extraction.CIL/PDB/MetadataPdbReader.cs
index c6d02a119dc..81c91946657 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/PDB/MetadataPdbReader.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/PDB/MetadataPdbReader.cs
@@ -76,7 +76,7 @@ namespace Semmle.Extraction.PDB
out provider,
out _))
{
- return new MetadataPdbReader(provider);
+ return new MetadataPdbReader(provider!);
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CIL/PDB/Method.cs b/csharp/extractor/Semmle.Extraction.CIL/PDB/Method.cs
index e0d41bed77a..6a534a4dd20 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/PDB/Method.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/PDB/Method.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using System.Reflection.Metadata;
using System.Linq;
namespace Semmle.Extraction.PDB
diff --git a/csharp/extractor/Semmle.Extraction.CIL/PDB/NativePdbReader.cs b/csharp/extractor/Semmle.Extraction.CIL/PDB/NativePdbReader.cs
index 6d49dd8fd85..0a8479fb454 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/PDB/NativePdbReader.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/PDB/NativePdbReader.cs
@@ -6,7 +6,6 @@ using Microsoft.DiaSymReader;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.Metadata;
using System.IO;
-using System.Reflection;
namespace Semmle.Extraction.PDB
{
diff --git a/csharp/extractor/Semmle.Extraction.CIL/PDB/PdbReader.cs b/csharp/extractor/Semmle.Extraction.CIL/PDB/PdbReader.cs
index c84cd6a824f..1f0f1b455dc 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/PDB/PdbReader.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/PDB/PdbReader.cs
@@ -1,9 +1,8 @@
-using System.Linq;
-using System.Reflection.PortableExecutable;
+using System.Reflection.PortableExecutable;
namespace Semmle.Extraction.PDB
{
- internal class PdbReader
+ internal static class PdbReader
{
///
/// Returns the PDB information associated with an assembly.
diff --git a/csharp/extractor/Semmle.Extraction.CIL/Tuples.cs b/csharp/extractor/Semmle.Extraction.CIL/Tuples.cs
index 498fe629ed6..0ae92386c3e 100644
--- a/csharp/extractor/Semmle.Extraction.CIL/Tuples.cs
+++ b/csharp/extractor/Semmle.Extraction.CIL/Tuples.cs
@@ -32,6 +32,9 @@ namespace Semmle.Extraction.CIL
internal static Tuple cil_base_class(Type t, Type @base) =>
new Tuple("cil_base_class", t, @base);
+ internal static Tuple cil_enum_underlying_type(Type t, PrimitiveType underlying) =>
+ new Tuple("cil_enum_underlying_type", t, underlying);
+
internal static Tuple cil_base_interface(Type t, Type @base) =>
new Tuple("cil_base_interface", t, @base);
@@ -83,6 +86,12 @@ namespace Semmle.Extraction.CIL
internal static Tuple cil_method(Method method, string name, Type declType, Type returnType) =>
new Tuple("cil_method", method, name, declType, returnType);
+ internal static Tuple cil_function_pointer_return_type(FunctionPointerType fnptr, Type returnType) =>
+ new Tuple("cil_function_pointer_return_type", fnptr, returnType);
+
+ internal static Tuple cil_function_pointer_calling_conventions(FunctionPointerType fnptr, System.Reflection.Metadata.SignatureCallingConvention callingConvention) =>
+ new Tuple("cil_function_pointer_calling_conventions", fnptr, (int)callingConvention);
+
internal static Tuple cil_method_implementation(MethodImplementation impl, Method method, Assembly assembly) =>
new Tuple("cil_method_implementation", impl, method, assembly);
@@ -98,7 +107,7 @@ namespace Semmle.Extraction.CIL
internal static Tuple cil_newslot(Method method) =>
new Tuple("cil_newslot", method);
- internal static Tuple cil_parameter(Parameter p, Method m, int i, Type t) =>
+ internal static Tuple cil_parameter(Parameter p, IParameterizable m, int i, Type t) =>
new Tuple("cil_parameter", p, m, i, t);
internal static Tuple cil_parameter_in(Parameter p) =>
@@ -185,6 +194,12 @@ namespace Semmle.Extraction.CIL
internal static Tuple cil_virtual(Method method) =>
new Tuple("cil_virtual", method);
+ internal static Tuple cil_custom_modifiers(ICustomModifierReceiver receiver, Type modifier, bool isRequired) =>
+ new Tuple("cil_custom_modifiers", receiver, modifier, isRequired ? 1 : 0);
+
+ internal static Tuple cil_type_annotation(IEntity receiver, TypeAnnotation annotation) =>
+ new Tuple("cil_type_annotation", receiver, (int)annotation);
+
internal static Tuple containerparent(Folder parent, IFileOrFolder child) =>
new Tuple("containerparent", parent, child);
diff --git a/csharp/extractor/Semmle.Extraction.CSharp.Driver/Driver.cs b/csharp/extractor/Semmle.Extraction.CSharp.Driver/Driver.cs
index 5806bd375b1..1c4142097fe 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp.Driver/Driver.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp.Driver/Driver.cs
@@ -3,7 +3,7 @@ namespace Semmle.Extraction.CSharp
///
/// A command-line driver for the extractor.
///
- public class Driver
+ public static class Driver
{
public static int Main(string[] args)
{
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs b/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
index b5a4c79d8f2..b04b429993d 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
@@ -17,7 +17,7 @@ namespace Semmle.Extraction.CSharp
///
public sealed class Analyser : IDisposable
{
- private IExtractor extractor;
+ private Extraction.Extractor extractor;
private CSharpCompilation compilation;
private Layout layout;
private bool init;
@@ -215,14 +215,12 @@ namespace Semmle.Extraction.CSharp
///
/// Extracts compilation-wide entities, such as compilations and compiler diagnostics.
///
- public void AnalyseCompilation(string cwd, string[] args)
+ public void AnalyseCompilation()
{
- extractionTasks.Add(() => DoAnalyseCompilation(cwd, args));
+ extractionTasks.Add(() => DoAnalyseCompilation());
}
-
-
- private void DoAnalyseCompilation(string cwd, string[] args)
+ private void DoAnalyseCompilation()
{
try
{
@@ -232,9 +230,9 @@ namespace Semmle.Extraction.CSharp
var projectLayout = layout.LookupProjectOrDefault(transformedAssemblyPath);
var trapWriter = projectLayout.CreateTrapWriter(Logger, transformedAssemblyPath, options.TrapCompression, discardDuplicates: false);
compilationTrapFile = trapWriter; // Dispose later
- var cx = extractor.CreateContext(compilation.Clone(), trapWriter, new AssemblyScope(assembly, assemblyPath, true), AddAssemblyTrapPrefix);
+ var cx = new Context(extractor, compilation.Clone(), trapWriter, new AssemblyScope(assembly, assemblyPath), AddAssemblyTrapPrefix);
- compilationEntity = new Entities.Compilation(cx, cwd, args);
+ compilationEntity = Entities.Compilation.Create(cx);
}
catch (Exception ex) // lgtm[cs/catch-of-all-exceptions]
{
@@ -287,7 +285,7 @@ namespace Semmle.Extraction.CSharp
if (c.GetAssemblyOrModuleSymbol(r) is IAssemblySymbol assembly)
{
- var cx = extractor.CreateContext(c, trapWriter, new AssemblyScope(assembly, assemblyPath, false), AddAssemblyTrapPrefix);
+ var cx = new Context(extractor, c, trapWriter, new AssemblyScope(assembly, assemblyPath), AddAssemblyTrapPrefix);
foreach (var module in assembly.Modules)
{
@@ -373,10 +371,16 @@ namespace Semmle.Extraction.CSharp
if (!upToDate)
{
- var cx = extractor.CreateContext(compilation.Clone(), trapWriter, new SourceScope(tree), AddAssemblyTrapPrefix);
- Populators.CompilationUnit.Extract(cx, tree.GetRoot());
+ var cx = new Context(extractor, compilation.Clone(), trapWriter, new SourceScope(tree), AddAssemblyTrapPrefix);
+ // Ensure that the file itself is populated in case the source file is totally empty
+ var root = tree.GetRoot();
+ Extraction.Entities.File.Create(cx, root.SyntaxTree.FilePath);
+
+ var csNode = (CSharpSyntaxNode)root;
+ csNode.Accept(new CompilationUnitVisitor(cx));
+ csNode.Accept(new DirectiveVisitor(cx));
cx.PopulateAll();
- cx.ExtractComments(cx.CommentGenerator);
+ CommentPopulator.ExtractCommentBlocks(cx, cx.CommentGenerator);
cx.PopulateAll();
}
}
@@ -389,7 +393,7 @@ namespace Semmle.Extraction.CSharp
}
catch (Exception ex) // lgtm[cs/catch-of-all-exceptions]
{
- extractor.Message(new Message("Unhandled exception processing syntax tree", tree.FilePath, null, ex.StackTrace));
+ extractor.Message(new Message($"Unhandled exception processing syntax tree. {ex.Message}", tree.FilePath, null, ex.StackTrace));
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Accessor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Accessor.cs
index c53a2c402e0..08782ec36a7 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Accessor.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Accessor.cs
@@ -76,6 +76,11 @@ namespace Semmle.Extraction.CSharp.Entities
{
trapFile.compiler_generated(this);
}
+
+ if (symbol.IsInitOnly)
+ {
+ trapFile.init_only_accessors(this);
+ }
}
public static new Accessor Create(Context cx, IMethodSymbol symbol) =>
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Attribute.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Attribute.cs
index 9ef33f5ba47..e1663350055 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Attribute.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Attribute.cs
@@ -66,12 +66,25 @@ namespace Semmle.Extraction.CSharp.Entities
private void ExtractArguments(TextWriter trapFile)
{
+ var ctorArguments = attributeSyntax?.ArgumentList?.Arguments.Where(a => a.NameEquals == null).ToList();
+
var childIndex = 0;
- foreach (var constructorArgument in symbol.ConstructorArguments)
+ for (var i = 0; i < symbol.ConstructorArguments.Length; i++)
{
+ var constructorArgument = symbol.ConstructorArguments[i];
+ var paramName = symbol.AttributeConstructor?.Parameters[i].Name;
+ var argSyntax = ctorArguments?.SingleOrDefault(a => a.NameColon != null && a.NameColon.Name.Identifier.Text == paramName);
+
+ if (argSyntax == null && // couldn't find named argument
+ ctorArguments?.Count > childIndex && // there're more arguments
+ ctorArguments[childIndex].NameColon == null) // the argument is positional
+ {
+ argSyntax = ctorArguments[childIndex];
+ }
+
CreateExpressionFromArgument(
constructorArgument,
- attributeSyntax?.ArgumentList.Arguments[childIndex].Expression,
+ argSyntax?.Expression,
this,
childIndex++);
}
@@ -105,7 +118,7 @@ namespace Semmle.Extraction.CSharp.Entities
private Semmle.Extraction.Entities.Location location;
private Semmle.Extraction.Entities.Location Location =>
- location ?? (location = Semmle.Extraction.Entities.Location.Create(Context, attributeSyntax is null ? entity.ReportingLocation : attributeSyntax.Name.GetLocation()));
+ location ?? (location = Context.CreateLocation(attributeSyntax is null ? entity.ReportingLocation : attributeSyntax.Name.GetLocation()));
public override bool NeedsPopulation => true;
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/CallTypeExtensions.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/CallTypeExtensions.cs
new file mode 100644
index 00000000000..6b9564e49dc
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/CallTypeExtensions.cs
@@ -0,0 +1,27 @@
+using Semmle.Extraction.Kinds;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal static class CallTypeExtensions
+ {
+ ///
+ /// Adjust the expression kind to match this call type.
+ ///
+ public static ExprKind AdjustKind(this Expression.CallType ct, ExprKind k)
+ {
+ if (k == ExprKind.ADDRESS_OF)
+ {
+ return k;
+ }
+
+ switch (ct)
+ {
+ case Expression.CallType.Dynamic:
+ case Expression.CallType.UserOperator:
+ return ExprKind.OPERATOR_INVOCATION;
+ default:
+ return k;
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentBlock.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentBlock.cs
index ab6802cf457..9f105bb1e1a 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentBlock.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentBlock.cs
@@ -13,7 +13,7 @@ namespace Semmle.Extraction.CSharp.Entities
{
trapFile.commentblock(this);
var child = 0;
- trapFile.commentblock_location(this, Context.Create(symbol.Location));
+ trapFile.commentblock_location(this, Context.CreateLocation(symbol.Location));
foreach (var l in symbol.CommentLines)
{
trapFile.commentblock_child(this, (CommentLine)l, child++);
@@ -24,7 +24,7 @@ namespace Semmle.Extraction.CSharp.Entities
public override void WriteId(TextWriter trapFile)
{
- trapFile.WriteSubId(Context.Create(symbol.Location));
+ trapFile.WriteSubId(Context.CreateLocation(symbol.Location));
trapFile.Write(";commentblock");
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs
index ba1770f20e5..5ba3bbd87f4 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs
@@ -1,6 +1,4 @@
using Semmle.Extraction.CommentProcessing;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
using Semmle.Extraction.Entities;
using System.IO;
@@ -21,102 +19,11 @@ namespace Semmle.Extraction.CSharp.Entities
public string Text { get { return symbol.Item2; } }
public string RawText { get; private set; }
- public static void Extract(Context cx, SyntaxTrivia trivia)
- {
- switch (trivia.Kind())
- {
- case SyntaxKind.SingleLineDocumentationCommentTrivia:
- /*
- This is actually a multi-line comment consisting of /// lines.
- So split it up.
- */
-
- var text = trivia.ToFullString();
-
- var split = text.Split('\n');
- var currentLocation = trivia.GetLocation().SourceSpan.Start - 3;
-
- for (var line = 0; line < split.Length - 1; ++line)
- {
- var fullLine = split[line];
- var nextLineLocation = currentLocation + fullLine.Length + 1;
- fullLine = fullLine.TrimEnd('\r');
- var trimmedLine = fullLine;
-
- var leadingSpaces = trimmedLine.IndexOf('/');
- if (leadingSpaces != -1)
- {
- fullLine = fullLine.Substring(leadingSpaces);
- currentLocation += leadingSpaces;
- trimmedLine = trimmedLine.Substring(leadingSpaces + 3); // Remove leading spaces and the "///"
- trimmedLine = trimmedLine.Trim();
-
- var span = Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(currentLocation, currentLocation + fullLine.Length);
- var location = Microsoft.CodeAnalysis.Location.Create(trivia.SyntaxTree, span);
- var commentType = CommentLineType.XmlDoc;
- cx.CommentGenerator.AddComment(Create(cx, location, commentType, trimmedLine, fullLine));
- }
- else
- {
- cx.ModelError("Unexpected comment format");
- }
- currentLocation = nextLineLocation;
- }
- break;
-
- case SyntaxKind.SingleLineCommentTrivia:
- {
- var contents = trivia.ToString().Substring(2);
- var commentType = CommentLineType.Singleline;
- if (contents.Length > 0 && contents[0] == '/')
- {
- commentType = CommentLineType.XmlDoc;
- contents = contents.Substring(1); // An XML comment.
- }
- cx.CommentGenerator.AddComment(Create(cx, trivia.GetLocation(), commentType, contents.Trim(), trivia.ToFullString()));
- }
- break;
- case SyntaxKind.MultiLineDocumentationCommentTrivia:
- case SyntaxKind.MultiLineCommentTrivia:
- /* We receive a single SyntaxTrivia for a multiline block spanning several lines.
- So we split it into separate lines
- */
- text = trivia.ToFullString();
-
- split = text.Split('\n');
- currentLocation = trivia.GetLocation().SourceSpan.Start;
-
- for (var line = 0; line < split.Length; ++line)
- {
- var fullLine = split[line];
- var nextLineLocation = currentLocation + fullLine.Length + 1;
- fullLine = fullLine.TrimEnd('\r');
- var trimmedLine = fullLine;
- if (line == 0)
- trimmedLine = trimmedLine.Substring(2);
- if (line == split.Length - 1)
- trimmedLine = trimmedLine.Substring(0, trimmedLine.Length - 2);
- trimmedLine = trimmedLine.Trim();
-
- var span = Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(currentLocation, currentLocation + fullLine.Length);
- var location = Microsoft.CodeAnalysis.Location.Create(trivia.SyntaxTree, span);
- var commentType = line == 0 ? CommentLineType.Multiline : CommentLineType.MultilineContinuation;
- cx.CommentGenerator.AddComment(Create(cx, location, commentType, trimmedLine, fullLine));
- currentLocation = nextLineLocation;
- }
- break;
- // Strangely, these are reported as SingleLineCommentTrivia.
- case SyntaxKind.DocumentationCommentExteriorTrivia:
- cx.ModelError($"Unhandled comment type {trivia.Kind()} for {trivia}");
- break;
- }
- }
-
- private Extraction.Entities.Location location;
+ private Location location;
public override void Populate(TextWriter trapFile)
{
- location = Context.Create(Location);
+ location = Context.CreateLocation(Location);
trapFile.commentline(this, Type == CommentLineType.MultilineContinuation ? CommentLineType.Multiline : Type, Text, RawText);
trapFile.commentline_location(this, location);
}
@@ -127,11 +34,11 @@ namespace Semmle.Extraction.CSharp.Entities
public override void WriteId(TextWriter trapFile)
{
- trapFile.WriteSubId(Context.Create(Location));
+ trapFile.WriteSubId(Context.CreateLocation(Location));
trapFile.Write(";commentline");
}
- private static CommentLine Create(Context cx, Microsoft.CodeAnalysis.Location loc, CommentLineType type, string text, string raw)
+ internal static CommentLine Create(Context cx, Microsoft.CodeAnalysis.Location loc, CommentLineType type, string text, string raw)
{
var init = (loc, type, text, raw);
return CommentLineFactory.Instance.CreateEntity(cx, init, init);
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilation.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilation.cs
deleted file mode 100644
index 67a4325ce35..00000000000
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilation.cs
+++ /dev/null
@@ -1,124 +0,0 @@
-using Microsoft.CodeAnalysis;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using Semmle.Util;
-
-namespace Semmle.Extraction.CSharp.Entities
-{
- internal class Compilation : FreshEntity
- {
- private readonly string cwd;
- private readonly string[] args;
-
- public Compilation(Context cx, string cwd, string[] args) : base(cx)
- {
- this.cwd = cwd;
- this.args = args;
- TryPopulate();
- }
-
- protected override void Populate(TextWriter trapFile)
- {
- Extraction.Entities.Assembly.CreateOutputAssembly(cx);
-
- trapFile.compilations(this, FileUtils.ConvertToUnix(cwd));
-
- // Arguments
- var index = 0;
- foreach (var arg in args)
- {
- trapFile.compilation_args(this, index++, arg);
- }
-
- // Files
- index = 0;
- foreach (var file in cx.Compilation.SyntaxTrees.Select(tree => Extraction.Entities.File.Create(cx, tree.FilePath)))
- {
- trapFile.compilation_compiling_files(this, index++, file);
- }
-
- // References
- index = 0;
- foreach (var file in cx.Compilation.References.OfType().Select(r => Extraction.Entities.File.Create(cx, r.FilePath)))
- {
- trapFile.compilation_referencing_files(this, index++, file);
- }
-
- // Diagnostics
- index = 0;
- foreach (var diag in cx.Compilation.GetDiagnostics().Select(d => new Diagnostic(cx, d)))
- {
- trapFile.diagnostic_for(diag, this, 0, index++);
- }
- }
-
- public void PopulatePerformance(PerformanceMetrics p)
- {
- var trapFile = cx.TrapWriter.Writer;
- var index = 0;
- foreach (var metric in p.Metrics)
- {
- trapFile.compilation_time(this, -1, index++, metric);
- }
- trapFile.compilation_finished(this, (float)p.Total.Cpu.TotalSeconds, (float)p.Total.Elapsed.TotalSeconds);
- }
-
- public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.NoLabel;
- }
-
- internal class Diagnostic : FreshEntity
- {
- public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.NoLabel;
-
- private readonly Microsoft.CodeAnalysis.Diagnostic diagnostic;
-
- public Diagnostic(Context cx, Microsoft.CodeAnalysis.Diagnostic diag) : base(cx)
- {
- diagnostic = diag;
- TryPopulate();
- }
-
- protected override void Populate(TextWriter trapFile)
- {
- trapFile.diagnostics(this, (int)diagnostic.Severity, diagnostic.Id, diagnostic.Descriptor.Title.ToString(),
- diagnostic.GetMessage(), Extraction.Entities.Location.Create(cx, diagnostic.Location));
- }
- }
-
- public struct Timings
- {
- public TimeSpan Elapsed { get; set; }
- public TimeSpan Cpu { get; set; }
- public TimeSpan User { get; set; }
- }
-
- ///
- /// The various performance metrics to log.
- ///
- public struct PerformanceMetrics
- {
- public Timings Frontend { get; set; }
- public Timings Extractor { get; set; }
- public Timings Total { get; set; }
- public long PeakWorkingSet { get; set; }
-
- ///
- /// These are in database order (0 indexed)
- ///
- public IEnumerable Metrics
- {
- get
- {
- yield return (float)Frontend.Cpu.TotalSeconds;
- yield return (float)Frontend.Elapsed.TotalSeconds;
- yield return (float)Extractor.Cpu.TotalSeconds;
- yield return (float)Extractor.Elapsed.TotalSeconds;
- yield return (float)Frontend.User.TotalSeconds;
- yield return (float)Extractor.User.TotalSeconds;
- yield return PeakWorkingSet / 1024.0f / 1024.0f;
- }
- }
- }
-}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilations/Compilation.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilations/Compilation.cs
new file mode 100644
index 00000000000..800e35d5ff4
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilations/Compilation.cs
@@ -0,0 +1,105 @@
+using Microsoft.CodeAnalysis;
+using System;
+using System.IO;
+using System.Linq;
+using Semmle.Util;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ public class Compilation : CachedEntity
public static Expression CreateParenthesized(Context cx, DeclarationExpressionSyntax node, ParenthesizedVariableDesignationSyntax designation, IExpressionParentEntity parent, int child)
{
- var type = Entities.NullType.Create(cx); // Should ideally be a corresponding tuple type
- var tuple = new Expression(new ExpressionInfo(cx, type, cx.Create(node.GetLocation()), ExprKind.TUPLE, parent, child, false, null));
+ AnnotatedTypeSymbol? type = null; // Should ideally be a corresponding tuple type
+ var tuple = new Expression(new ExpressionInfo(cx, type, cx.CreateLocation(node.GetLocation()), ExprKind.TUPLE, parent, child, false, null));
cx.Try(null, null, () =>
{
@@ -64,8 +64,8 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
public static Expression CreateParenthesized(Context cx, VarPatternSyntax varPattern, ParenthesizedVariableDesignationSyntax designation, IExpressionParentEntity parent, int child)
{
- var type = NullType.Create(cx); // Should ideally be a corresponding tuple type
- var tuple = new Expression(new ExpressionInfo(cx, type, cx.Create(varPattern.GetLocation()), ExprKind.TUPLE, parent, child, false, null));
+ AnnotatedTypeSymbol? type = null; // Should ideally be a corresponding tuple type
+ var tuple = new Expression(new ExpressionInfo(cx, type, cx.CreateLocation(varPattern.GetLocation()), ExprKind.TUPLE, parent, child, false, null));
cx.Try(null, null, () =>
{
@@ -80,7 +80,7 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
case SingleVariableDesignationSyntax single:
if (cx.GetModel(variable).GetDeclaredSymbol(single) is ILocalSymbol local)
{
- var decl = Create(cx, variable, Entities.Type.Create(cx, local.GetAnnotatedType()), tuple, child0++);
+ var decl = Create(cx, variable, local.GetAnnotatedType(), tuple, child0++);
var l = LocalVariable.Create(cx, local);
l.PopulateManual(decl, true);
}
@@ -111,25 +111,24 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
case ParenthesizedVariableDesignationSyntax paren:
return CreateParenthesized(cx, node, paren, parent, child);
case DiscardDesignationSyntax discard:
- var ti = cx.GetType(discard);
- var type = Entities.Type.Create(cx, ti);
+ var type = cx.GetType(discard);
return Create(cx, node, type, parent, child);
default:
cx.ModelError(node, "Failed to determine designation type");
- return Create(cx, node, NullType.Create(cx), parent, child);
+ return Create(cx, node, null, parent, child);
}
}
public static Expression Create(Context cx, DeclarationExpressionSyntax node, IExpressionParentEntity parent, int child) =>
Create(cx, node, node.Designation, parent, child);
- public static VariableDeclaration Create(Context cx, CSharpSyntaxNode c, AnnotatedType type, IExpressionParentEntity parent, int child) =>
- new VariableDeclaration(new ExpressionInfo(cx, type, cx.Create(c.FixedLocation()), ExprKind.LOCAL_VAR_DECL, parent, child, false, null));
+ public static VariableDeclaration Create(Context cx, CSharpSyntaxNode c, AnnotatedTypeSymbol? type, IExpressionParentEntity parent, int child) =>
+ new VariableDeclaration(new ExpressionInfo(cx, type, cx.CreateLocation(c.FixedLocation()), ExprKind.LOCAL_VAR_DECL, parent, child, false, null));
public static VariableDeclaration Create(Context cx, CatchDeclarationSyntax d, bool isVar, IExpressionParentEntity parent, int child)
{
var symbol = cx.GetModel(d).GetDeclaredSymbol(d);
- var type = Entities.Type.Create(cx, symbol.GetAnnotatedType());
+ var type = symbol.GetAnnotatedType();
var ret = Create(cx, d, type, parent, child);
cx.Try(d, null, () =>
{
@@ -141,7 +140,7 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
return ret;
}
- public static VariableDeclaration CreateDeclarator(Context cx, VariableDeclaratorSyntax d, AnnotatedType type, bool isVar, IExpressionParentEntity parent, int child)
+ public static VariableDeclaration CreateDeclarator(Context cx, VariableDeclaratorSyntax d, AnnotatedTypeSymbol type, bool isVar, IExpressionParentEntity parent, int child)
{
var ret = Create(cx, d, type, parent, child);
cx.Try(d, null, () =>
@@ -170,7 +169,7 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
{
public static void Populate(Context cx, VariableDeclarationSyntax decl, IExpressionParentEntity parent, int child, int childIncrement = 1)
{
- var type = Type.Create(cx, cx.GetType(decl.Type));
+ var type = cx.GetType(decl.Type);
foreach (var v in decl.Variables)
{
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs
index 2990332648d..d17b2f003aa 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs
@@ -14,7 +14,7 @@ namespace Semmle.Extraction.CSharp.Entities
private Field(Context cx, IFieldSymbol init)
: base(cx, init)
{
- type = new Lazy(() => Entities.Type.Create(cx, symbol.GetAnnotatedType()));
+ type = new Lazy(() => Entities.Type.Create(cx, symbol.Type));
}
public static Field Create(Context cx, IFieldSymbol field) => FieldFactory.Instance.CreateEntityFromSymbol(cx, field);
@@ -32,7 +32,7 @@ namespace Semmle.Extraction.CSharp.Entities
PopulateNullability(trapFile, symbol.GetAnnotatedType());
var unboundFieldKey = Field.Create(Context, symbol.OriginalDefinition);
- trapFile.fields(this, (symbol.IsConst ? 2 : 1), symbol.Name, ContainingType, Type.Type.TypeRef, unboundFieldKey);
+ trapFile.fields(this, (symbol.IsConst ? 2 : 1), symbol.Name, ContainingType, Type.TypeRef, unboundFieldKey);
PopulateModifiers(trapFile);
@@ -65,13 +65,13 @@ namespace Semmle.Extraction.CSharp.Entities
{
Context.PopulateLater(() =>
{
- var loc = Context.Create(initializer.GetLocation());
+ var loc = Context.CreateLocation(initializer.GetLocation());
var fieldAccess = AddInitializerAssignment(trapFile, initializer.Initializer.Value, loc, null, ref child);
if (!symbol.IsStatic)
{
- This.CreateImplicit(Context, Entities.Type.Create(Context, symbol.ContainingType), Location, fieldAccess, -1);
+ This.CreateImplicit(Context, symbol.ContainingType, Location, fieldAccess, -1);
}
});
}
@@ -86,7 +86,7 @@ namespace Semmle.Extraction.CSharp.Entities
? Expression.ValueAsString(symbol.ConstantValue)
: null;
- var loc = Context.Create(initializer.GetLocation());
+ var loc = Context.CreateLocation(initializer.GetLocation());
AddInitializerAssignment(trapFile, initializer.EqualsValue.Value, loc, constValue, ref child);
}
@@ -107,19 +107,20 @@ namespace Semmle.Extraction.CSharp.Entities
private Expression AddInitializerAssignment(TextWriter trapFile, ExpressionSyntax initializer, Extraction.Entities.Location loc,
string constValue, ref int child)
{
- var simpleAssignExpr = new Expression(new ExpressionInfo(Context, Type, loc, ExprKind.SIMPLE_ASSIGN, this, child++, false, constValue));
+ var type = symbol.GetAnnotatedType();
+ var simpleAssignExpr = new Expression(new ExpressionInfo(Context, type, loc, ExprKind.SIMPLE_ASSIGN, this, child++, false, constValue));
Expression.CreateFromNode(new ExpressionNodeInfo(Context, initializer, simpleAssignExpr, 0));
- var access = new Expression(new ExpressionInfo(Context, Type, Location, ExprKind.FIELD_ACCESS, simpleAssignExpr, 1, false, constValue));
+ var access = new Expression(new ExpressionInfo(Context, type, Location, ExprKind.FIELD_ACCESS, simpleAssignExpr, 1, false, constValue));
trapFile.expr_access(access, this);
return access;
}
- private readonly Lazy type;
- public AnnotatedType Type => type.Value;
+ private readonly Lazy type;
+ public Type Type => type.Value;
public override void WriteId(TextWriter trapFile)
{
- trapFile.WriteSubId(Type.Type);
+ trapFile.WriteSubId(Type);
trapFile.Write(" ");
trapFile.WriteSubId(ContainingType);
trapFile.Write('.');
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/IExpressionInfo.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/IExpressionInfo.cs
new file mode 100644
index 00000000000..1a43a6fbd02
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/IExpressionInfo.cs
@@ -0,0 +1,53 @@
+using Microsoft.CodeAnalysis;
+using Semmle.Extraction.Kinds;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ ///
+ /// Holds all information required to create an Expression entity.
+ ///
+ internal interface IExpressionInfo
+ {
+ Context Context { get; }
+
+ ///
+ /// The type of the expression.
+ ///
+ AnnotatedTypeSymbol? Type { get; }
+
+ ///
+ /// The location of the expression.
+ ///
+ Extraction.Entities.Location Location { get; }
+
+ ///
+ /// The kind of the expression.
+ ///
+ ExprKind Kind { get; }
+
+ ///
+ /// The parent of the expression.
+ ///
+ IExpressionParentEntity Parent { get; }
+
+ ///
+ /// The child index of the expression.
+ ///
+ int Child { get; }
+
+ ///
+ /// Holds if this is an implicit expression.
+ ///
+ bool IsCompilerGenerated { get; }
+
+ ///
+ /// Gets a string representation of the value.
+ /// null is encoded as the string "null".
+ /// If the expression does not have a value, then this
+ /// is null.
+ ///
+ string ExprValue { get; }
+
+ NullableFlowState FlowState { get; }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/IExpressionParentEntity.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/IExpressionParentEntity.cs
new file mode 100644
index 00000000000..a8e4df3d6ca
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/IExpressionParentEntity.cs
@@ -0,0 +1,10 @@
+namespace Semmle.Extraction.CSharp.Entities
+{
+ public interface IExpressionParentEntity : IEntity
+ {
+ ///
+ /// Whether this entity is the parent of a top-level expression.
+ ///
+ bool IsTopLevelParent { get; }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/IStatementParentEntity.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/IStatementParentEntity.cs
new file mode 100644
index 00000000000..40bc0f218c0
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/IStatementParentEntity.cs
@@ -0,0 +1,10 @@
+namespace Semmle.Extraction.CSharp.Entities
+{
+ ///
+ /// Whether this entity is the parent of a top-level statement.
+ ///
+ public interface IStatementParentEntity : IEntity
+ {
+ bool IsTopLevelParent { get; }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs
index 5873d3e19f4..70b15a6fe16 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs
@@ -46,7 +46,7 @@ namespace Semmle.Extraction.CSharp.Entities
{
// The expression may need to reference parameters in the getter.
// So we need to arrange that the expression is populated after the getter.
- Context.PopulateLater(() => Expression.CreateFromNode(new ExpressionNodeInfo(Context, expressionBody, this, 0) { Type = Type.Create(Context, symbol.GetAnnotatedType()) }));
+ Context.PopulateLater(() => Expression.CreateFromNode(new ExpressionNodeInfo(Context, expressionBody, this, 0).SetType(symbol.GetAnnotatedType())));
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs
index 309feabfe3d..b376566e841 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs
@@ -1,8 +1,6 @@
using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.IO;
-using System.Linq;
namespace Semmle.Extraction.CSharp.Entities
{
@@ -39,7 +37,7 @@ namespace Semmle.Extraction.CSharp.Entities
var originalDefinition = IsSourceDeclaration ? this : Create(Context, symbol.OriginalDefinition);
var returnType = Type.Create(Context, symbol.ReturnType);
trapFile.local_functions(this, symbol.Name, returnType, originalDefinition);
- ExtractRefReturn(trapFile);
+ ExtractRefReturn(trapFile, symbol, this);
}
public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.NeedsLabel;
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalVariable.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalVariable.cs
index de44b16d69f..e988ec2c815 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalVariable.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalVariable.cs
@@ -24,9 +24,9 @@ namespace Semmle.Extraction.CSharp.Entities
{
var trapFile = Context.TrapWriter.Writer;
var (kind, type) = symbol is ILocalSymbol l
- ? (l.IsRef ? 3 : l.IsConst ? 2 : 1, Type.Create(Context, l.GetAnnotatedType()))
+ ? (l.IsRef ? 3 : l.IsConst ? 2 : 1, l.GetAnnotatedType())
: (1, parent.Type);
- trapFile.localvars(this, kind, symbol.Name, isVar ? 1 : 0, type.Type.TypeRef, parent);
+ trapFile.localvars(this, kind, symbol.Name, isVar ? 1 : 0, Type.Create(Context, type).TypeRef, parent);
if (symbol is ILocalSymbol local)
{
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs
index 94e8c838e7b..00bf8f3638e 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs
@@ -1,4 +1,5 @@
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.CSharp.Populators;
using System.Collections.Generic;
@@ -40,7 +41,9 @@ namespace Semmle.Extraction.CSharp.Entities
foreach (var p in parameters.Zip(originalParameters, (paramSymbol, originalParam) => new { paramSymbol, originalParam }))
{
- var original = SymbolEqualityComparer.Default.Equals(p.paramSymbol, p.originalParam) ? null : Parameter.Create(Context, p.originalParam, originalMethod);
+ var original = SymbolEqualityComparer.Default.Equals(p.paramSymbol, p.originalParam)
+ ? null
+ : Parameter.Create(Context, p.originalParam, originalMethod);
Parameter.Create(Context, p.paramSymbol, this, original);
}
@@ -81,11 +84,21 @@ namespace Semmle.Extraction.CSharp.Entities
else
Expression.Create(Context, expr, this, 0);
- Context.NumberOfLines(trapFile, BodyDeclaringSymbol, this);
+ NumberOfLines(trapFile, BodyDeclaringSymbol, this);
});
}
}
+ public static void NumberOfLines(TextWriter trapFile, ISymbol symbol, IEntity callable)
+ {
+ foreach (var decl in symbol.DeclaringSyntaxReferences)
+ {
+ var node = (CSharpSyntaxNode)decl.GetSyntax();
+ var lineCounts = node.Accept(new AstLineCounter());
+ trapFile.numlines(callable, lineCounts);
+ }
+ }
+
public void Overrides(TextWriter trapFile)
{
foreach (var explicitInterface in symbol.ExplicitInterfaceImplementations
@@ -110,7 +123,7 @@ namespace Semmle.Extraction.CSharp.Entities
///
/// Factored out to share logic between `Method` and `UserOperator`.
///
- protected static void BuildMethodId(Method m, TextWriter trapFile)
+ private static void BuildMethodId(Method m, TextWriter trapFile)
{
m.symbol.ReturnType.BuildOrWriteId(m.Context, trapFile, m.symbol);
trapFile.Write(" ");
@@ -324,12 +337,12 @@ namespace Semmle.Extraction.CSharp.Entities
}
}
- protected void ExtractRefReturn(TextWriter trapFile)
+ public static void ExtractRefReturn(TextWriter trapFile, IMethodSymbol method, IEntity element)
{
- if (symbol.ReturnsByRef)
- trapFile.type_annotation(this, Kinds.TypeAnnotation.Ref);
- if (symbol.ReturnsByRefReadonly)
- trapFile.type_annotation(this, Kinds.TypeAnnotation.ReadonlyRef);
+ if (method.ReturnsByRef)
+ trapFile.type_annotation(element, Kinds.TypeAnnotation.Ref);
+ if (method.ReturnsByRefReadonly)
+ trapFile.type_annotation(element, Kinds.TypeAnnotation.ReadonlyRef);
}
protected void PopulateMethod(TextWriter trapFile)
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/NamespaceDeclaration.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/NamespaceDeclaration.cs
index 17f9a198a39..3a19417008a 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/NamespaceDeclaration.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/NamespaceDeclaration.cs
@@ -22,7 +22,7 @@ namespace Semmle.Extraction.CSharp.Entities
public override void WriteId(TextWriter trapFile)
{
- trapFile.WriteSubId(Context.Create(ReportingLocation));
+ trapFile.WriteSubId(Context.CreateLocation(ReportingLocation));
trapFile.Write(";namespacedeclaration");
}
@@ -31,7 +31,7 @@ namespace Semmle.Extraction.CSharp.Entities
var @namespace = (INamespaceSymbol)Context.GetModel(node).GetSymbolInfo(node.Name).Symbol;
var ns = Namespace.Create(Context, @namespace);
trapFile.namespace_declarations(this, ns);
- trapFile.namespace_declaration_location(this, Context.Create(node.Name.GetLocation()));
+ trapFile.namespace_declaration_location(this, Context.CreateLocation(node.Name.GetLocation()));
var visitor = new Populators.TypeOrNamespaceVisitor(Context, trapFile, this);
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/OrdinaryMethod.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/OrdinaryMethod.cs
index dbeab838b25..d81fc25f72b 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/OrdinaryMethod.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/OrdinaryMethod.cs
@@ -49,7 +49,7 @@ namespace Semmle.Extraction.CSharp.Entities
PopulateGenerics(trapFile);
Overrides(trapFile);
- ExtractRefReturn(trapFile);
+ ExtractRefReturn(trapFile, symbol, this);
ExtractCompilerGenerated(trapFile);
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Parameter.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Parameter.cs
index c42a3edd236..bb87fccdd74 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Parameter.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Parameter.cs
@@ -110,7 +110,15 @@ namespace Semmle.Extraction.CSharp.Entities
trapFile.@params(this, Name, type.TypeRef, Ordinal, ParamKind, Parent, Original);
foreach (var l in symbol.Locations)
- trapFile.param_location(this, Context.Create(l));
+ trapFile.param_location(this, Context.CreateLocation(l));
+
+ if (!symbol.Locations.Any() &&
+ symbol.ContainingSymbol is IMethodSymbol ms &&
+ ms.Name == WellKnownMemberNames.TopLevelStatementsEntryPointMethodName &&
+ ms.ContainingType.Name == WellKnownMemberNames.TopLevelStatementsEntryPointTypeName)
+ {
+ trapFile.param_location(this, Context.CreateLocation());
+ }
if (!IsSourceDeclaration || !symbol.FromSource())
return;
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/DefineDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/DefineDirective.cs
new file mode 100644
index 00000000000..2ca967a4864
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/DefineDirective.cs
@@ -0,0 +1,18 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class DefineDirective : PreprocessorDirective
+ {
+ public DefineDirective(Context cx, DefineDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_defines(this, trivia.Name.ToString());
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElifDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElifDirective.cs
new file mode 100644
index 00000000000..ace89464b96
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElifDirective.cs
@@ -0,0 +1,28 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class ElifDirective : PreprocessorDirective, IIfSiblingDirective, IExpressionParentEntity
+ {
+ private readonly IfDirective start;
+ private readonly int index;
+
+ public ElifDirective(Context cx, ElifDirectiveTriviaSyntax trivia, IfDirective start, int index)
+ : base(cx, trivia, populateFromBase: false)
+ {
+ this.start = start;
+ this.index = index;
+ TryPopulate();
+ }
+
+ public bool IsTopLevelParent => true;
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_elifs(this, trivia.BranchTaken, trivia.ConditionValue, start, index);
+
+ Expression.Create(cx, trivia.Condition, this, 0);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElseDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElseDirective.cs
new file mode 100644
index 00000000000..7ab7d45b6e9
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElseDirective.cs
@@ -0,0 +1,24 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class ElseDirective : PreprocessorDirective, IIfSiblingDirective
+ {
+ private readonly IfDirective start;
+ private readonly int index;
+
+ public ElseDirective(Context cx, ElseDirectiveTriviaSyntax trivia, IfDirective start, int index)
+ : base(cx, trivia, populateFromBase: false)
+ {
+ this.start = start;
+ this.index = index;
+ TryPopulate();
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_elses(this, trivia.BranchTaken, start, index);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndIfDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndIfDirective.cs
new file mode 100644
index 00000000000..9c349844dc6
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndIfDirective.cs
@@ -0,0 +1,22 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class EndIfDirective : PreprocessorDirective
+ {
+ private readonly IfDirective start;
+
+ public EndIfDirective(Context cx, EndIfDirectiveTriviaSyntax trivia, IfDirective start)
+ : base(cx, trivia, populateFromBase: false)
+ {
+ this.start = start;
+ TryPopulate();
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_endifs(this, start);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndRegionDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndRegionDirective.cs
new file mode 100644
index 00000000000..d4d75470a97
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndRegionDirective.cs
@@ -0,0 +1,22 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class EndRegionDirective : PreprocessorDirective
+ {
+ private readonly RegionDirective region;
+
+ public EndRegionDirective(Context cx, EndRegionDirectiveTriviaSyntax trivia, RegionDirective region)
+ : base(cx, trivia, populateFromBase: false)
+ {
+ this.region = region;
+ TryPopulate();
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_endregions(this, region);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ErrorDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ErrorDirective.cs
new file mode 100644
index 00000000000..2917d077839
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ErrorDirective.cs
@@ -0,0 +1,18 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class ErrorDirective : PreprocessorDirective
+ {
+ public ErrorDirective(Context cx, ErrorDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_errors(this, trivia.EndOfDirectiveToken.LeadingTrivia.ToString());
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IIfSiblingDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IIfSiblingDirective.cs
new file mode 100644
index 00000000000..7a849962f09
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IIfSiblingDirective.cs
@@ -0,0 +1,4 @@
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal interface IIfSiblingDirective { }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IfDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IfDirective.cs
new file mode 100644
index 00000000000..e1b81e60d8a
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IfDirective.cs
@@ -0,0 +1,22 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class IfDirective : PreprocessorDirective, IExpressionParentEntity
+ {
+ public IfDirective(Context cx, IfDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ public bool IsTopLevelParent => true;
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_ifs(this, trivia.BranchTaken, trivia.ConditionValue);
+
+ Expression.Create(cx, trivia.Condition, this, 0);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/LineDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/LineDirective.cs
new file mode 100644
index 00000000000..54681d317e9
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/LineDirective.cs
@@ -0,0 +1,40 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class LineDirective : PreprocessorDirective
+ {
+ public LineDirective(Context cx, LineDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ var type = trivia.Line.Kind() switch
+ {
+ SyntaxKind.DefaultKeyword => 0,
+ SyntaxKind.HiddenKeyword => 1,
+ SyntaxKind.NumericLiteralToken => 2,
+ _ => throw new InternalError(trivia, "Unhandled line token kind")
+ };
+
+ trapFile.directive_lines(this, type);
+
+ if (trivia.Line.IsKind(SyntaxKind.NumericLiteralToken))
+ {
+ var value = (int)trivia.Line.Value;
+ trapFile.directive_line_value(this, value);
+
+ if (!string.IsNullOrWhiteSpace(trivia.File.ValueText))
+ {
+ var file = Extraction.Entities.File.Create(cx, trivia.File.ValueText);
+ trapFile.directive_line_file(this, file);
+ }
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/NullableDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/NullableDirective.cs
new file mode 100644
index 00000000000..e6bb4e79fed
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/NullableDirective.cs
@@ -0,0 +1,35 @@
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class NullableDirective : PreprocessorDirective
+ {
+ public NullableDirective(Context cx, NullableDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ var setting = trivia.SettingToken.Kind() switch
+ {
+ SyntaxKind.DisableKeyword => 0,
+ SyntaxKind.EnableKeyword => 1,
+ SyntaxKind.RestoreKeyword => 2,
+ _ => throw new InternalError(trivia, "Unhandled setting token kind")
+ };
+
+ var target = trivia.TargetToken.Kind() switch
+ {
+ SyntaxKind.None => 0,
+ SyntaxKind.AnnotationsKeyword => 1,
+ SyntaxKind.WarningsKeyword => 2,
+ _ => throw new InternalError(trivia, "Unhandled target token kind")
+ };
+
+ trapFile.directive_nullables(this, setting, target);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaChecksumDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaChecksumDirective.cs
new file mode 100644
index 00000000000..dff901fa826
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaChecksumDirective.cs
@@ -0,0 +1,19 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class PragmaChecksumDirective : PreprocessorDirective
+ {
+ public PragmaChecksumDirective(Context cx, PragmaChecksumDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ var file = Extraction.Entities.File.Create(cx, trivia.File.ValueText);
+ trapFile.pragma_checksums(this, file, trivia.Guid.ToString(), trivia.Bytes.ToString());
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaWarningDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaWarningDirective.cs
new file mode 100644
index 00000000000..4502fa4a87a
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaWarningDirective.cs
@@ -0,0 +1,26 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class PragmaWarningDirective : PreprocessorDirective
+ {
+ public PragmaWarningDirective(Context cx, PragmaWarningDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.pragma_warnings(this, trivia.DisableOrRestoreKeyword.IsKind(SyntaxKind.DisableKeyword) ? 0 : 1);
+
+ var childIndex = 0;
+ foreach (var code in trivia.ErrorCodes)
+ {
+ trapFile.pragma_warning_error_codes(this, code.ToString(), childIndex++);
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs
new file mode 100644
index 00000000000..4575dd8b8dc
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs
@@ -0,0 +1,41 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Semmle.Extraction.Entities;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal abstract class PreprocessorDirective : FreshEntity where TDirective : DirectiveTriviaSyntax
+ {
+ protected readonly TDirective trivia;
+
+ protected PreprocessorDirective(Context cx, TDirective trivia, bool populateFromBase = true)
+ : base(cx)
+ {
+ this.trivia = trivia;
+ if (populateFromBase)
+ {
+ TryPopulate();
+ }
+ }
+
+ protected sealed override void Populate(TextWriter trapFile)
+ {
+ PopulatePreprocessor(trapFile);
+
+ trapFile.preprocessor_directive_active(this, trivia.IsActive);
+ trapFile.preprocessor_directive_location(this, cx.CreateLocation(ReportingLocation));
+
+ if (!cx.Extractor.Standalone)
+ {
+ var compilation = Compilation.Create(cx);
+ trapFile.preprocessor_directive_compilation(this, compilation);
+ }
+ }
+
+ protected abstract void PopulatePreprocessor(TextWriter trapFile);
+
+ public sealed override Microsoft.CodeAnalysis.Location ReportingLocation => trivia.GetLocation();
+
+ public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel;
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/RegionDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/RegionDirective.cs
new file mode 100644
index 00000000000..b2f017688a3
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/RegionDirective.cs
@@ -0,0 +1,19 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class RegionDirective : PreprocessorDirective
+ {
+ public RegionDirective(Context cx, RegionDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_regions(this, trivia.EndOfDirectiveToken.LeadingTrivia.ToString());
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/UndefineDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/UndefineDirective.cs
new file mode 100644
index 00000000000..d4b976d50c0
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/UndefineDirective.cs
@@ -0,0 +1,18 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class UndefineDirective : PreprocessorDirective
+ {
+ public UndefineDirective(Context cx, UndefDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_undefines(this, trivia.Name.ToString());
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/WarningDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/WarningDirective.cs
new file mode 100644
index 00000000000..1511be8d28c
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/WarningDirective.cs
@@ -0,0 +1,18 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class WarningDirective : PreprocessorDirective
+ {
+ public WarningDirective(Context cx, WarningDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_warnings(this, trivia.EndOfDirectiveToken.LeadingTrivia.ToString());
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs
index 739708138ad..6795c4a12df 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs
@@ -84,15 +84,15 @@ namespace Semmle.Extraction.CSharp.Entities
{
Context.PopulateLater(() =>
{
- var loc = Context.Create(initializer.GetLocation());
- var annotatedType = new AnnotatedType(type, NullableAnnotation.None);
+ var loc = Context.CreateLocation(initializer.GetLocation());
+ var annotatedType = AnnotatedTypeSymbol.CreateNotAnnotated(symbol.Type);
var simpleAssignExpr = new Expression(new ExpressionInfo(Context, annotatedType, loc, ExprKind.SIMPLE_ASSIGN, this, child++, false, null));
Expression.CreateFromNode(new ExpressionNodeInfo(Context, initializer.Value, simpleAssignExpr, 0));
var access = new Expression(new ExpressionInfo(Context, annotatedType, Location, ExprKind.PROPERTY_ACCESS, simpleAssignExpr, 1, false, null));
trapFile.expr_access(access, this);
if (!symbol.IsStatic)
{
- This.CreateImplicit(Context, Type.Create(Context, symbol.ContainingType), Location, access, -1);
+ This.CreateImplicit(Context, symbol.ContainingType, Location, access, -1);
}
});
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statement.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statement.cs
index 44bd7f80583..94ab9a7ce61 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statement.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statement.cs
@@ -1,24 +1,41 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Semmle.Extraction.CSharp.Populators;
using Microsoft.CodeAnalysis.CSharp;
-using Semmle.Extraction.Entities;
using System.IO;
namespace Semmle.Extraction.CSharp.Entities
{
- ///
- /// Whether this entity is the parent of a top-level statement.
- ///
- public interface IStatementParentEntity : IEntity
- {
- bool IsTopLevelParent { get; }
- }
-
internal abstract class Statement : FreshEntity, IExpressionParentEntity, IStatementParentEntity
{
- protected Statement(Context cx) : base(cx) { }
+ private readonly int child;
+ private readonly Kinds.StmtKind kind;
+ private readonly IStatementParentEntity parent;
- public static Statement Create(Context cx, StatementSyntax node, Statement parent, int child) =>
+ protected Statement(Context cx, Kinds.StmtKind kind, IStatementParentEntity parent, int child)
+ : base(cx)
+ {
+ this.kind = kind;
+ this.parent = parent;
+ this.child = child;
+ }
+
+ protected override void Populate(TextWriter trapFile)
+ {
+ trapFile.statements(this, kind);
+ if (parent.IsTopLevelParent)
+ {
+ trapFile.stmt_parent_top_level(this, child, parent);
+ }
+ else
+ {
+ trapFile.stmt_parent(this, child, parent);
+ }
+
+ PopulateStatement(trapFile);
+ }
+
+ protected abstract void PopulateStatement(TextWriter trapFile);
+
+ public static Statement Create(Context cx, StatementSyntax node, IStatementParentEntity parent, int child) =>
Statements.Factory.Create(cx, node, parent, child);
///
@@ -27,54 +44,10 @@ namespace Semmle.Extraction.CSharp.Entities
///
public virtual int NumberOfStatements => 1;
- public override Microsoft.CodeAnalysis.Location ReportingLocation => GetStatementSyntax().GetLocation();
-
bool IExpressionParentEntity.IsTopLevelParent => false;
bool IStatementParentEntity.IsTopLevelParent => false;
- protected abstract CSharpSyntaxNode GetStatementSyntax();
-
public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.NeedsLabel;
}
-
- internal abstract class Statement : Statement where TSyntax : CSharpSyntaxNode
- {
- protected readonly TSyntax Stmt;
- private readonly int child;
- private readonly Kinds.StmtKind kind;
- private readonly IStatementParentEntity parent;
- private readonly Location location;
-
- protected override CSharpSyntaxNode GetStatementSyntax() => Stmt;
-
- protected Statement(Context cx, TSyntax stmt, Kinds.StmtKind kind, IStatementParentEntity parent, int child, Location location)
- : base(cx)
- {
- Stmt = stmt;
- this.parent = parent;
- this.child = child;
- this.location = location;
- this.kind = kind;
- cx.BindComments(this, location.symbol);
- }
-
- protected sealed override void Populate(TextWriter trapFile)
- {
- trapFile.statements(this, kind);
- if (parent.IsTopLevelParent)
- trapFile.stmt_parent_top_level(this, child, parent);
- else
- trapFile.stmt_parent(this, child, parent);
- trapFile.stmt_location(this, location);
- PopulateStatement(trapFile);
- }
-
- protected abstract void PopulateStatement(TextWriter trapFile);
-
- protected Statement(Context cx, TSyntax stmt, Kinds.StmtKind kind, IStatementParentEntity parent, int child)
- : this(cx, stmt, kind, parent, child, cx.Create(stmt.FixedLocation())) { }
-
- public override string ToString() => Label.ToString();
- }
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statement`1.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statement`1.cs
new file mode 100644
index 00000000000..8ea6650d4f7
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statement`1.cs
@@ -0,0 +1,35 @@
+using Semmle.Extraction.CSharp.Populators;
+using Microsoft.CodeAnalysis.CSharp;
+using Semmle.Extraction.Entities;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal abstract class Statement : Statement where TSyntax : CSharpSyntaxNode
+ {
+ protected readonly TSyntax Stmt;
+ private readonly Location location;
+
+ protected Statement(Context cx, TSyntax stmt, Kinds.StmtKind kind, IStatementParentEntity parent, int child, Location location)
+ : base(cx, kind, parent, child)
+ {
+ Stmt = stmt;
+ this.location = location;
+ cx.BindComments(this, location.symbol);
+ }
+
+ protected Statement(Context cx, TSyntax stmt, Kinds.StmtKind kind, IStatementParentEntity parent, int child)
+ : this(cx, stmt, kind, parent, child, cx.CreateLocation(stmt.FixedLocation())) { }
+
+ protected sealed override void Populate(TextWriter trapFile)
+ {
+ base.Populate(trapFile);
+
+ trapFile.stmt_location(this, location);
+ }
+
+ public override Microsoft.CodeAnalysis.Location ReportingLocation => Stmt.GetLocation();
+
+ public override string ToString() => Label.ToString();
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Case.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Case.cs
index e2a712c006b..8046bdb6e26 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Case.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Case.cs
@@ -10,7 +10,7 @@ namespace Semmle.Extraction.CSharp.Entities.Statements
internal abstract class Case : Statement where TSyntax : SwitchLabelSyntax
{
protected Case(Context cx, TSyntax node, Switch parent, int child)
- : base(cx, node, StmtKind.CASE, parent, child, cx.Create(node.GetLocation())) { }
+ : base(cx, node, StmtKind.CASE, parent, child, cx.CreateLocation(node.GetLocation())) { }
public static Statement Create(Context cx, SwitchLabelSyntax node, Switch parent, int child)
{
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Catch.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Catch.cs
index 13cc39f7fea..b9bddab82e2 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Catch.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Catch.cs
@@ -10,7 +10,7 @@ namespace Semmle.Extraction.CSharp.Entities.Statements
private static readonly string systemExceptionName = typeof(System.Exception).ToString();
private Catch(Context cx, CatchClauseSyntax node, Try parent, int child)
- : base(cx, node, StmtKind.CATCH, parent, child, cx.Create(node.GetLocation())) { }
+ : base(cx, node, StmtKind.CATCH, parent, child, cx.CreateLocation(node.GetLocation())) { }
protected override void PopulateStatement(TextWriter trapFile)
{
@@ -20,11 +20,11 @@ namespace Semmle.Extraction.CSharp.Entities.Statements
if (hasVariableDeclaration) // A catch clause of the form 'catch(Ex ex) { ... }'
{
var decl = Expressions.VariableDeclaration.Create(cx, Stmt.Declaration, false, this, 0);
- trapFile.catch_type(this, decl.Type.Type.TypeRef, true);
+ trapFile.catch_type(this, Type.Create(cx, decl.Type).TypeRef, true);
}
else if (isSpecificCatchClause) // A catch clause of the form 'catch(Ex) { ... }'
{
- trapFile.catch_type(this, Type.Create(cx, cx.GetType(Stmt.Declaration.Type)).Type.TypeRef, true);
+ trapFile.catch_type(this, Type.Create(cx, cx.GetType(Stmt.Declaration.Type)).TypeRef, true);
}
else // A catch clause of the form 'catch { ... }'
{
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Do.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Do.cs
index a0cd9da7372..7cdcfb1f57b 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Do.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Do.cs
@@ -8,7 +8,7 @@ namespace Semmle.Extraction.CSharp.Entities.Statements
internal class Do : Statement
{
private Do(Context cx, DoStatementSyntax node, IStatementParentEntity parent, int child)
- : base(cx, node, StmtKind.DO, parent, child, cx.Create(node.GetLocation())) { }
+ : base(cx, node, StmtKind.DO, parent, child, cx.CreateLocation(node.GetLocation())) { }
public static Do Create(Context cx, DoStatementSyntax node, IStatementParentEntity parent, int child)
{
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Factory.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Factory.cs
index 69ebdca1c16..2960982061e 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Factory.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Factory.cs
@@ -5,7 +5,7 @@ namespace Semmle.Extraction.CSharp.Entities.Statements
{
internal static class Factory
{
- internal static Statement Create(Context cx, StatementSyntax node, Statement parent, int child)
+ internal static Statement Create(Context cx, StatementSyntax node, IStatementParentEntity parent, int child)
{
switch (node.Kind())
{
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/ForEach.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/ForEach.cs
index 653ed384e7b..e7bb987574e 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/ForEach.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/ForEach.cs
@@ -8,6 +8,15 @@ namespace Semmle.Extraction.CSharp.Entities.Statements
{
internal class ForEach : Statement
{
+ internal enum ForeachSymbolType
+ {
+ GetEnumeratorMethod = 1,
+ CurrentProperty,
+ MoveNextMethod,
+ DisposeMethod,
+ ElementType
+ }
+
private ForEach(Context cx, ForEachStatementSyntax stmt, IStatementParentEntity parent, int child)
: base(cx, stmt, StmtKind.FOREACH, parent, child) { }
@@ -18,18 +27,59 @@ namespace Semmle.Extraction.CSharp.Entities.Statements
return ret;
}
- protected override void PopulateStatement(TextWriter _)
+ protected override void PopulateStatement(TextWriter trapFile)
{
Expression.Create(cx, Stmt.Expression, this, 1);
- var typeSymbol = cx.GetModel(Stmt).GetDeclaredSymbol(Stmt);
- var type = Type.Create(cx, typeSymbol.GetAnnotatedType());
+ var semanticModel = cx.GetModel(Stmt);
+ var typeSymbol = semanticModel.GetDeclaredSymbol(Stmt);
+ var type = typeSymbol.GetAnnotatedType();
- var location = cx.Create(Stmt.Identifier.GetLocation());
+ var location = cx.CreateLocation(Stmt.Identifier.GetLocation());
Expressions.VariableDeclaration.Create(cx, typeSymbol, type, Stmt.Type, location, Stmt.Type.IsVar, this, 0);
Statement.Create(cx, Stmt.Statement, this, 2);
+
+ var info = semanticModel.GetForEachStatementInfo(Stmt);
+
+ if (info.Equals(default))
+ {
+ cx.ExtractionError("Could not get foreach statement info", null, cx.CreateLocation(this.ReportingLocation), severity: Util.Logging.Severity.Info);
+ return;
+ }
+
+ trapFile.foreach_stmt_info(this, info.IsAsynchronous);
+
+ if (info.GetEnumeratorMethod != null)
+ {
+ var m = Method.Create(cx, info.GetEnumeratorMethod);
+ trapFile.foreach_stmt_desugar(this, m, ForeachSymbolType.GetEnumeratorMethod);
+ }
+
+ if (info.MoveNextMethod != null)
+ {
+ var m = Method.Create(cx, info.MoveNextMethod);
+ trapFile.foreach_stmt_desugar(this, m, ForeachSymbolType.MoveNextMethod);
+ }
+
+ if (info.DisposeMethod != null)
+ {
+ var m = Method.Create(cx, info.DisposeMethod);
+ trapFile.foreach_stmt_desugar(this, m, ForeachSymbolType.DisposeMethod);
+ }
+
+ if (info.CurrentProperty != null)
+ {
+ var p = Property.Create(cx, info.CurrentProperty);
+ trapFile.foreach_stmt_desugar(this, p, ForeachSymbolType.CurrentProperty);
+ }
+
+ if (info.ElementType != null)
+ {
+ var t = Type.Create(cx, info.ElementType);
+ trapFile.foreach_stmt_desugar(this, t, ForeachSymbolType.ElementType);
+ }
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/GlobalStatementsBlock.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/GlobalStatementsBlock.cs
new file mode 100644
index 00000000000..062a09e3fae
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/GlobalStatementsBlock.cs
@@ -0,0 +1,42 @@
+using Semmle.Extraction.Kinds;
+using System.Linq;
+using System.IO;
+using Semmle.Extraction.Entities;
+
+namespace Semmle.Extraction.CSharp.Entities.Statements
+{
+ internal class GlobalStatementsBlock : Statement
+ {
+ private readonly Method parent;
+
+ private GlobalStatementsBlock(Context cx, Method parent)
+ : base(cx, StmtKind.BLOCK, parent, 0)
+ {
+ this.parent = parent;
+ }
+
+ public override Microsoft.CodeAnalysis.Location ReportingLocation
+ {
+ get
+ {
+ return parent.symbol
+ ?.DeclaringSyntaxReferences
+ .FirstOrDefault()
+ ?.GetSyntax()
+ .GetLocation();
+ }
+ }
+
+ public static GlobalStatementsBlock Create(Context cx, Method parent)
+ {
+ var ret = new GlobalStatementsBlock(cx, parent);
+ ret.TryPopulate();
+ return ret;
+ }
+
+ protected override void PopulateStatement(TextWriter trapFile)
+ {
+ trapFile.stmt_location(this, cx.CreateLocation(ReportingLocation));
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Labeled.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Labeled.cs
index 1fce622b347..fe69af917ba 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Labeled.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/Labeled.cs
@@ -6,17 +6,18 @@ namespace Semmle.Extraction.CSharp.Entities.Statements
{
internal class Labeled : Statement
{
- private readonly Statement parent;
+ private readonly IStatementParentEntity parent;
private readonly int child;
+ private Statement labelledStmt;
- private Labeled(Context cx, LabeledStatementSyntax stmt, Statement parent, int child)
+ private Labeled(Context cx, LabeledStatementSyntax stmt, IStatementParentEntity parent, int child)
: base(cx, stmt, StmtKind.LABEL, parent, child)
{
this.parent = parent;
this.child = child;
}
- public static Labeled Create(Context cx, LabeledStatementSyntax node, Statement parent, int child)
+ public static Labeled Create(Context cx, LabeledStatementSyntax node, IStatementParentEntity parent, int child)
{
var ret = new Labeled(cx, node, parent, child);
ret.TryPopulate();
@@ -27,13 +28,11 @@ namespace Semmle.Extraction.CSharp.Entities.Statements
{
trapFile.exprorstmt_name(this, Stmt.Identifier.ToString());
- // For compatilibty with the Mono extractor, make insert the labelled statement into the same block
+ // For compatibility with the Mono extractor, make insert the labelled statement into the same block
// as this one. The parent MUST be a block statement.
labelledStmt = Statement.Create(cx, Stmt.Statement, parent, child + 1);
}
- private Statement labelledStmt;
-
public override int NumberOfStatements => 1 + labelledStmt.NumberOfStatements;
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/LocalFunction.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/LocalFunction.cs
index eb81f27cb3a..5e417fd8885 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/LocalFunction.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Statements/LocalFunction.cs
@@ -10,7 +10,7 @@ namespace Semmle.Extraction.CSharp.Entities.Statements
internal class LocalFunction : Statement
{
private LocalFunction(Context cx, LocalFunctionStatementSyntax node, IStatementParentEntity parent, int child)
- : base(cx, node, StmtKind.LOCAL_FUNCTION, parent, child, cx.Create(node.GetLocation())) { }
+ : base(cx, node, StmtKind.LOCAL_FUNCTION, parent, child, cx.CreateLocation(node.GetLocation())) { }
public static LocalFunction Create(Context cx, LocalFunctionStatementSyntax node, IStatementParentEntity parent, int child)
{
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Symbol.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Symbol.cs
index 5e0850ba8c8..f7a493af78e 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Symbol.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Symbol.cs
@@ -78,7 +78,7 @@ namespace Semmle.Extraction.CSharp.Entities
if (loc != null)
{
// Some built in operators lack locations, so loc is null.
- yield return Context.Create(ReportingLocation);
+ yield return Context.CreateLocation(ReportingLocation);
if (Context.Extractor.OutputPath != null && loc.Kind == LocationKind.SourceFile)
yield return Assembly.CreateOutputAssembly(Context);
}
@@ -124,7 +124,7 @@ namespace Semmle.Extraction.CSharp.Entities
public override bool NeedsPopulation => Context.Defines(symbol);
- public Extraction.Entities.Location Location => Context.Create(ReportingLocation);
+ public Extraction.Entities.Location Location => Context.CreateLocation(ReportingLocation);
protected void PopulateMetadataHandle(TextWriter trapFile)
{
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/TypeMention.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/TypeMention.cs
index 4f1e166b8dc..e05c2d0c102 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/TypeMention.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/TypeMention.cs
@@ -47,7 +47,7 @@ namespace Semmle.Extraction.CSharp.Entities
switch (type)
{
case ArrayType at:
- return GetArrayElementType(at.ElementType.Type);
+ return GetArrayElementType(at.ElementType);
case NamedType nt when nt.symbol.IsBoundSpan() ||
nt.symbol.IsBoundReadOnlySpan():
return nt.TypeArguments.Single();
@@ -118,7 +118,7 @@ namespace Semmle.Extraction.CSharp.Entities
private void Emit(TextWriter trapFile, Microsoft.CodeAnalysis.Location loc, IEntity parent, Type type)
{
trapFile.type_mention(this, type.TypeRef, parent);
- trapFile.type_mention_location(this, cx.Create(loc));
+ trapFile.type_mention_location(this, cx.CreateLocation(loc));
}
public static TypeMention Create(Context cx, TypeSyntax syntax, IEntity parent, Type type, Microsoft.CodeAnalysis.Location loc = null)
@@ -128,8 +128,8 @@ namespace Semmle.Extraction.CSharp.Entities
return ret;
}
- public static TypeMention Create(Context cx, TypeSyntax syntax, IEntity parent, AnnotatedType type, Microsoft.CodeAnalysis.Location loc = null) =>
- Create(cx, syntax, parent, type.Type, loc);
+ public static TypeMention Create(Context cx, TypeSyntax syntax, IEntity parent, AnnotatedTypeSymbol? type, Microsoft.CodeAnalysis.Location loc = null) =>
+ Create(cx, syntax, parent, Type.Create(cx, type?.Symbol), loc);
public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel;
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/ArrayType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/ArrayType.cs
index 2c617158219..4ec1db558e5 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/ArrayType.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/ArrayType.cs
@@ -1,3 +1,4 @@
+using System;
using System.IO;
using Microsoft.CodeAnalysis;
@@ -8,16 +9,16 @@ namespace Semmle.Extraction.CSharp.Entities
private ArrayType(Context cx, IArrayTypeSymbol init)
: base(cx, init)
{
- element = Create(cx, symbol.GetAnnotatedElementType());
+ elementLazy = new Lazy(() => Create(cx, symbol.ElementType));
}
- private readonly AnnotatedType element;
+ private readonly Lazy elementLazy;
public int Rank => symbol.Rank;
- public override AnnotatedType ElementType => element;
+ public Type ElementType => elementLazy.Value;
- public override int Dimension => 1 + element.Type.Dimension;
+ public override int Dimension => 1 + ElementType.Dimension;
// All array types are extracted because they won't
// be extracted in their defining assembly.
@@ -25,18 +26,19 @@ namespace Semmle.Extraction.CSharp.Entities
public override void Populate(TextWriter trapFile)
{
- trapFile.array_element_type(this, Dimension, Rank, element.Type.TypeRef);
+ trapFile.array_element_type(this, Dimension, Rank, ElementType.TypeRef);
PopulateType(trapFile);
}
public override void WriteId(TextWriter trapFile)
{
- trapFile.WriteSubId(element.Type);
+ trapFile.WriteSubId(ElementType);
symbol.BuildArraySuffix(trapFile);
trapFile.Write(";type");
}
- public static ArrayType Create(Context cx, IArrayTypeSymbol symbol) => ArrayTypeFactory.Instance.CreateEntityFromSymbol(cx, symbol);
+ public static ArrayType Create(Context cx, IArrayTypeSymbol symbol) =>
+ ArrayTypeFactory.Instance.CreateEntityFromSymbol(cx, symbol);
private class ArrayTypeFactory : ICachedEntityFactory
{
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/FunctionPointerType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/FunctionPointerType.cs
new file mode 100644
index 00000000000..1bf68a4c3c2
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/FunctionPointerType.cs
@@ -0,0 +1,42 @@
+using System.IO;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class FunctionPointerType : Type
+ {
+ private FunctionPointerType(Context cx, IFunctionPointerTypeSymbol init)
+ : base(cx, init)
+ {
+ }
+
+ public override void WriteId(TextWriter trapFile)
+ {
+ symbol.BuildTypeId(Context, trapFile, symbol);
+ trapFile.Write(";functionpointertype");
+ }
+
+ public override bool NeedsPopulation => true;
+
+ public override void Populate(TextWriter trapFile)
+ {
+ trapFile.function_pointer_calling_conventions(this, (int)symbol.Signature.CallingConvention);
+ foreach (var (conv, i) in symbol.Signature.UnmanagedCallingConventionTypes.Select((nt, i) => (Create(Context, nt), i)))
+ {
+ trapFile.has_unmanaged_calling_conventions(this, i, conv.TypeRef);
+ }
+
+ PopulateType(trapFile);
+ }
+
+ public static FunctionPointerType Create(Context cx, IFunctionPointerTypeSymbol symbol) => FunctionPointerTypeFactory.Instance.CreateEntityFromSymbol(cx, symbol);
+
+ private class FunctionPointerTypeFactory : ICachedEntityFactory
+ {
+ public static FunctionPointerTypeFactory Instance { get; } = new FunctionPointerTypeFactory();
+
+ public FunctionPointerType Create(Context cx, IFunctionPointerTypeSymbol init) => new FunctionPointerType(cx, init);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs
index 5812c4a9303..0d95d776592 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs
@@ -87,6 +87,11 @@ namespace Semmle.Extraction.CSharp.Entities
foreach (var l in Locations)
trapFile.type_location(this, l);
}
+
+ if (symbol.IsAnonymousType)
+ {
+ trapFile.anonymous_types(this);
+ }
}
private readonly Lazy typeArgumentsLazy;
@@ -101,7 +106,7 @@ namespace Semmle.Extraction.CSharp.Entities
get
{
foreach (var l in GetLocations(symbol))
- yield return Context.Create(l);
+ yield return Context.CreateLocation(l);
if (Context.Extractor.OutputPath != null && symbol.DeclaringSyntaxReferences.Any())
yield return Assembly.CreateOutputAssembly(Context);
@@ -144,36 +149,6 @@ namespace Semmle.Extraction.CSharp.Entities
base.WriteQuotedId(trapFile);
}
- ///
- /// Returns the element type in an Enumerable/IEnumerable
- ///
- /// Extraction context.
- /// The enumerable type.
- /// The element type, or null.
- private static AnnotatedTypeSymbol GetElementType(Context cx, INamedTypeSymbol type)
- {
- var et = GetEnumerableType(cx, type);
- if (et.Symbol != null)
- return et;
-
- return type.AllInterfaces
- .Where(i => i.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T)
- .Concat(type.AllInterfaces.Where(i => i.SpecialType == SpecialType.System_Collections_IEnumerable))
- .Select(i => GetEnumerableType(cx, i))
- .FirstOrDefault();
- }
-
- private static AnnotatedTypeSymbol GetEnumerableType(Context cx, INamedTypeSymbol type)
- {
- return type.SpecialType == SpecialType.System_Collections_IEnumerable
- ? cx.Compilation.ObjectType.WithAnnotation(NullableAnnotation.NotAnnotated)
- : type.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T
- ? type.GetAnnotatedTypeArguments().First()
- : default(AnnotatedTypeSymbol);
- }
-
- public override AnnotatedType ElementType => Type.Create(Context, GetElementType(Context, symbol));
-
private class NamedTypeFactory : ICachedEntityFactory
{
public static NamedTypeFactory Instance { get; } = new NamedTypeFactory();
@@ -230,5 +205,5 @@ namespace Semmle.Extraction.CSharp.Entities
{
trapFile.typerefs(this, symbol.Name);
}
- };
+ }
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NullType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NullType.cs
index 2822e346384..33f7c44d21f 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NullType.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NullType.cs
@@ -27,7 +27,7 @@ namespace Semmle.Extraction.CSharp.Entities
return obj != null && obj.GetType() == typeof(NullType);
}
- public static AnnotatedType Create(Context cx) => new AnnotatedType(NullTypeFactory.Instance.CreateEntity(cx, typeof(NullType), null), NullableAnnotation.None);
+ public static Type Create(Context cx) => NullTypeFactory.Instance.CreateEntity(cx, typeof(NullType), null);
private class NullTypeFactory : ICachedEntityFactory
{
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TupleType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TupleType.cs
index af9c4bf4fa6..6f6ff85cf77 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TupleType.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TupleType.cs
@@ -53,12 +53,12 @@ namespace Semmle.Extraction.CSharp.Entities
// about what locations are available for a tuple type.
// Sometimes it's the source code, and sometimes it's empty.
foreach (var l in symbol.Locations)
- trapFile.type_location(this, Context.Create(l));
+ trapFile.type_location(this, Context.CreateLocation(l));
}
private readonly Lazy tupleElementsLazy;
public Field[] TupleElements => tupleElementsLazy.Value;
- public override IEnumerable TypeMentions => TupleElements.Select(e => e.Type.Type);
+ public override IEnumerable TypeMentions => TupleElements.Select(e => e.Type);
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Type.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Type.cs
index 1b1d205b3d7..8c96a334565 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Type.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Type.cs
@@ -1,42 +1,18 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Util;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Semmle.Extraction.CSharp.Entities
{
- ///
- /// Represents an annotated type consisting of a type entity and nullability information.
- ///
- public struct AnnotatedType
- {
- public AnnotatedType(Type t, NullableAnnotation n)
- {
- Type = t;
- annotation = n;
- }
-
- ///
- /// The underlying type.
- ///
- public Type Type { get; private set; }
-
- private readonly NullableAnnotation annotation;
-
- ///
- /// Gets the annotated type symbol of this annotated type.
- ///
- public AnnotatedTypeSymbol Symbol => new AnnotatedTypeSymbol(Type.symbol, annotation);
- }
-
public abstract class Type : CachedSymbol
{
protected Type(Context cx, ITypeSymbol init)
: base(cx, init) { }
- public virtual AnnotatedType ElementType => default(AnnotatedType);
public override bool NeedsPopulation =>
base.NeedsPopulation || symbol.TypeKind == TypeKind.Dynamic || symbol.TypeKind == TypeKind.TypeParameter;
@@ -82,6 +58,7 @@ namespace Semmle.Extraction.CSharp.Entities
case TypeKind.Enum: return Kinds.TypeKind.ENUM;
case TypeKind.Delegate: return Kinds.TypeKind.DELEGATE;
case TypeKind.Pointer: return Kinds.TypeKind.POINTER;
+ case TypeKind.FunctionPointer: return Kinds.TypeKind.FUNCTION_POINTER;
case TypeKind.Error: return Kinds.TypeKind.UNKNOWN;
default:
cx.ModelError(t, $"Unhandled type kind '{t.TypeKind}'");
@@ -156,23 +133,14 @@ namespace Semmle.Extraction.CSharp.Entities
// This is a delegate.
// The method "Invoke" has the return type.
var invokeMethod = ((INamedTypeSymbol)symbol).DelegateInvokeMethod;
+ ExtractParametersForDelegateLikeType(trapFile, invokeMethod,
+ t => trapFile.delegate_return_type(this, t));
+ }
- // Copy the parameters from the "Invoke" method to the delegate type
- for (var i = 0; i < invokeMethod.Parameters.Length; ++i)
- {
- var param = invokeMethod.Parameters[i];
- var originalParam = invokeMethod.OriginalDefinition.Parameters[i];
- var originalParamEntity = SymbolEqualityComparer.Default.Equals(param, originalParam) ? null :
- DelegateTypeParameter.Create(Context, originalParam, Create(Context, ((INamedTypeSymbol)symbol).OriginalDefinition));
- DelegateTypeParameter.Create(Context, param, this, originalParamEntity);
- }
-
- var returnKey = Create(Context, invokeMethod.ReturnType);
- trapFile.delegate_return_type(this, returnKey.TypeRef);
- if (invokeMethod.ReturnsByRef)
- trapFile.type_annotation(this, Kinds.TypeAnnotation.Ref);
- if (invokeMethod.ReturnsByRefReadonly)
- trapFile.type_annotation(this, Kinds.TypeAnnotation.ReadonlyRef);
+ if (symbol is IFunctionPointerTypeSymbol functionPointer)
+ {
+ ExtractParametersForDelegateLikeType(trapFile, functionPointer.Signature,
+ t => trapFile.function_pointer_return_type(this, t));
}
Modifier.ExtractModifiers(Context, trapFile, this, symbol);
@@ -195,6 +163,23 @@ namespace Semmle.Extraction.CSharp.Entities
}
}
+ private void ExtractParametersForDelegateLikeType(TextWriter trapFile, IMethodSymbol invokeMethod, Action storeReturnType)
+ {
+ for (var i = 0; i < invokeMethod.Parameters.Length; ++i)
+ {
+ var param = invokeMethod.Parameters[i];
+ var originalParam = invokeMethod.OriginalDefinition.Parameters[i];
+ var originalParamEntity = SymbolEqualityComparer.Default.Equals(param, originalParam)
+ ? null
+ : DelegateTypeParameter.Create(Context, originalParam, Create(Context, ((INamedTypeSymbol)symbol).OriginalDefinition));
+ DelegateTypeParameter.Create(Context, param, this, originalParamEntity);
+ }
+
+ var returnKey = Create(Context, invokeMethod.ReturnType);
+ storeReturnType(returnKey.TypeRef);
+ Method.ExtractRefReturn(trapFile, invokeMethod, this);
+ }
+
///
/// Called to extract all members and nested types.
/// This is called on each member of a namespace,
@@ -277,12 +262,12 @@ namespace Semmle.Extraction.CSharp.Entities
{
type = type.DisambiguateType();
return type == null
- ? NullType.Create(cx).Type
+ ? NullType.Create(cx)
: (Type)cx.CreateEntity(type);
}
- public static AnnotatedType Create(Context cx, AnnotatedTypeSymbol type) =>
- new AnnotatedType(Create(cx, type.Symbol), type.Nullability);
+ public static Type Create(Context cx, AnnotatedTypeSymbol? type) =>
+ Create(cx, type?.Symbol);
public virtual int Dimension => 0;
@@ -290,8 +275,7 @@ namespace Semmle.Extraction.CSharp.Entities
symbol != null && symbol.TypeKind == TypeKind.Delegate;
///
- /// A copy of a delegate "Invoke" method parameter used for the delgate
- /// type.
+ /// A copy of a delegate "Invoke" method or function pointer parameter.
///
private class DelegateTypeParameter : Parameter
{
@@ -331,10 +315,10 @@ namespace Semmle.Extraction.CSharp.Entities
public override bool Equals(object obj)
{
var other = obj as Type;
- return other?.GetType() == GetType() && SymbolEqualityComparer.IncludeNullability.Equals(other.symbol, symbol);
+ return other?.GetType() == GetType() && SymbolEqualityComparer.Default.Equals(other.symbol, symbol);
}
- public override int GetHashCode() => SymbolEqualityComparer.IncludeNullability.GetHashCode(symbol);
+ public override int GetHashCode() => SymbolEqualityComparer.Default.GetHashCode(symbol);
}
internal abstract class Type : Type where T : ITypeSymbol
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs
index b4d35f7b506..e92a3891704 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs
@@ -54,7 +54,7 @@ namespace Semmle.Extraction.CSharp.Entities
foreach (var l in symbol.Locations)
{
- trapFile.type_location(this, Context.Create(l));
+ trapFile.type_location(this, Context.CreateLocation(l));
}
if (IsSourceDeclaration)
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/UsingDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/UsingDirective.cs
index caa12ab0082..d08a1320ba2 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/UsingDirective.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/UsingDirective.cs
@@ -30,7 +30,7 @@ namespace Semmle.Extraction.CSharp.Entities
{
var ns = Namespace.Create(cx, namespaceSymbol);
trapFile.using_namespace_directives(this, ns);
- trapFile.using_directive_location(this, cx.Create(ReportingLocation));
+ trapFile.using_directive_location(this, cx.CreateLocation(ReportingLocation));
}
else
{
@@ -44,7 +44,7 @@ namespace Semmle.Extraction.CSharp.Entities
// A "using static"
var m = Type.Create(cx, (ITypeSymbol)info.Symbol);
trapFile.using_static_directives(this, m.TypeRef);
- trapFile.using_directive_location(this, cx.Create(ReportingLocation));
+ trapFile.using_directive_location(this, cx.CreateLocation(ReportingLocation));
}
if (parent != null)
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Extractor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Extractor.cs
index cf991e0930a..2f979863dd0 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Extractor.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Extractor.cs
@@ -68,7 +68,10 @@ namespace Semmle.Extraction.CSharp
{
var stopwatch = new Stopwatch();
stopwatch.Start();
- var commandLineArguments = Options.CreateWithEnvironment(args);
+
+ Entities.Compilation.Settings = (Directory.GetCurrentDirectory(), args);
+
+ var commandLineArguments = Options.CreateWithEnvironment(Entities.Compilation.Settings.Args);
var fileLogger = new FileLogger(commandLineArguments.Verbosity, GetCSharpLogPath());
using var logger = commandLineArguments.Console
? new CombinedLogger(new ConsoleLogger(commandLineArguments.Verbosity), fileLogger)
@@ -95,10 +98,9 @@ namespace Semmle.Extraction.CSharp
return ExitCode.Ok;
}
- var cwd = Directory.GetCurrentDirectory();
var compilerArguments = CSharpCommandLineParser.Default.Parse(
compilerVersion.ArgsWithResponse,
- cwd,
+ Entities.Compilation.Settings.Cwd,
compilerVersion.FrameworkPath,
compilerVersion.AdditionalReferenceDirectories
);
@@ -106,7 +108,7 @@ namespace Semmle.Extraction.CSharp
if (compilerArguments == null)
{
var sb = new StringBuilder();
- sb.Append(" Failed to parse command line: ").AppendList(" ", args);
+ sb.Append(" Failed to parse command line: ").AppendList(" ", Entities.Compilation.Settings.Args);
logger.Log(Severity.Error, sb.ToString());
++analyser.CompilationErrors;
return ExitCode.Failed;
@@ -159,7 +161,7 @@ namespace Semmle.Extraction.CSharp
);
analyser.EndInitialize(compilerArguments, commandLineArguments, compilation);
- analyser.AnalyseCompilation(cwd, args);
+ analyser.AnalyseCompilation();
analyser.AnalyseReferences();
foreach (var tree in compilation.SyntaxTrees)
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs b/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs
index f46ed47ff5f..292e8c47d98 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs
@@ -115,6 +115,16 @@ namespace Semmle.Extraction.Kinds
SWITCH_CASE = 118,
ASSIGN_COALESCE = 119,
SUPPRESS_NULLABLE_WARNING = 120,
- NAMESPACE_ACCESS = 121
+ NAMESPACE_ACCESS = 121,
+ LT_PATTERN = 122,
+ GT_PATTERN = 123,
+ LE_PATTERN = 124,
+ GE_PATTERN = 125,
+ NOT_PATTERN = 126,
+ AND_PATTERN = 127,
+ OR_PATTERN = 128,
+ FUNCTION_POINTER_INVOCATION = 129,
+
+ DEFINE_SYMBOL = 999
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Kinds/TypeKind.cs b/csharp/extractor/Semmle.Extraction.CSharp/Kinds/TypeKind.cs
index fb205a6646e..01fad60e003 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Kinds/TypeKind.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Kinds/TypeKind.cs
@@ -34,6 +34,7 @@ namespace Semmle.Extraction.Kinds // lgtm[cs/similar-file]
DYNAMIC = 29,
ARGLIST = 30,
UNKNOWN = 31,
- TUPLE = 32
+ TUPLE = 32,
+ FUNCTION_POINTER = 33
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/Ast.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/Ast.cs
deleted file mode 100644
index 866d436bda1..00000000000
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/Ast.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Semmle.Extraction.CSharp.Entities;
-
-namespace Semmle.Extraction.CSharp.Populators
-{
- internal class Ast : CSharpSyntaxVisitor
- {
- private readonly Context cx;
- private readonly IExpressionParentEntity parent;
- private readonly int child;
-
- public Ast(Context cx, IExpressionParentEntity parent, int child)
- {
- this.cx = cx;
- this.parent = parent;
- this.child = child;
- }
-
- public override void DefaultVisit(SyntaxNode node)
- {
- cx.ModelError(node, $"Unhandled syntax node {node.Kind()}");
- }
-
- public override void VisitArgumentList(ArgumentListSyntax node)
- {
- var c = 0;
- foreach (var m in node.Arguments)
- {
- cx.Extract(m, parent, c++);
- }
- }
-
- public override void VisitArgument(ArgumentSyntax node)
- {
- Expression.Create(cx, node.Expression, parent, child);
- }
- }
-
- public static class AstExtensions
- {
- public static void Extract(this Context cx, CSharpSyntaxNode node, IExpressionParentEntity parent, int child)
- {
- using (cx.StackGuard)
- {
- try
- {
- node.Accept(new Ast(cx, parent, child));
- }
- catch (System.Exception ex) // lgtm[cs/catch-of-all-exceptions]
- {
- cx.ModelError(node, $"Exception processing syntax node of type {node.Kind()}: {ex.Message}");
- }
- }
- }
-
- public static void Extract(this Context cx, SyntaxNode node, IEntity parent, int child)
- {
- cx.Extract(((CSharpSyntaxNode)node), parent, child);
- }
- }
-}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/AstLineCounter.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/AstLineCounter.cs
new file mode 100644
index 00000000000..a185675ee27
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/AstLineCounter.cs
@@ -0,0 +1,48 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Semmle.Util;
+
+namespace Semmle.Extraction.CSharp.Populators
+{
+ internal class AstLineCounter : CSharpSyntaxVisitor
+ {
+ public override LineCounts DefaultVisit(SyntaxNode node)
+ {
+ var text = node.SyntaxTree.GetText().GetSubText(node.GetLocation().SourceSpan).ToString();
+ return LineCounter.ComputeLineCounts(text);
+ }
+
+ public override LineCounts VisitMethodDeclaration(MethodDeclarationSyntax method)
+ {
+ return Visit(method.Identifier, method.Body ?? (SyntaxNode)method.ExpressionBody);
+ }
+
+ public static LineCounts Visit(SyntaxToken identifier, SyntaxNode body)
+ {
+ var start = identifier.GetLocation().SourceSpan.Start;
+ var end = body.GetLocation().SourceSpan.End - 1;
+
+ var textSpan = new Microsoft.CodeAnalysis.Text.TextSpan(start, end - start);
+
+ var text = body.SyntaxTree.GetText().GetSubText(textSpan) + "\r\n";
+ return LineCounter.ComputeLineCounts(text);
+ }
+
+ public override LineCounts VisitConstructorDeclaration(ConstructorDeclarationSyntax method)
+ {
+ return Visit(method.Identifier, (SyntaxNode)method.Body ?? method.ExpressionBody);
+ }
+
+ public override LineCounts VisitDestructorDeclaration(DestructorDeclarationSyntax method)
+ {
+ return Visit(method.Identifier, (SyntaxNode)method.Body ?? method.ExpressionBody);
+ }
+
+ public override LineCounts VisitOperatorDeclaration(OperatorDeclarationSyntax node)
+ {
+ return Visit(node.OperatorToken, node.Body ?? (SyntaxNode)node.ExpressionBody);
+ }
+ }
+
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/CommentPopulator.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/CommentPopulator.cs
new file mode 100644
index 00000000000..325746274ff
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/CommentPopulator.cs
@@ -0,0 +1,123 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Semmle.Extraction.CommentProcessing;
+using Semmle.Extraction.CSharp.Entities;
+using System;
+
+namespace Semmle.Extraction.CSharp.Populators
+{
+ ///
+ /// Populators for comments.
+ ///
+ public static class CommentPopulator
+ {
+ public static void ExtractCommentBlocks(Context cx, ICommentGenerator gen)
+ {
+ cx.Try(null, null, () =>
+ {
+ gen.GenerateBindings((entity, duplicationGuardKey, block, binding) =>
+ {
+ var commentBlock = Entities.CommentBlock.Create(cx, block);
+ Action a = () =>
+ {
+ commentBlock.BindTo(entity, binding);
+ };
+ // When the duplication guard key exists, it means that the entity is guarded against
+ // trap duplication ().
+ // We must therefore also guard comment construction.
+ if (duplicationGuardKey != null)
+ cx.WithDuplicationGuard(duplicationGuardKey, a);
+ else
+ a();
+ });
+ });
+ }
+
+ public static void ExtractComment(Context cx, SyntaxTrivia trivia)
+ {
+ switch (trivia.Kind())
+ {
+ case SyntaxKind.SingleLineDocumentationCommentTrivia:
+ /*
+ This is actually a multi-line comment consisting of /// lines.
+ So split it up.
+ */
+
+ var text = trivia.ToFullString();
+
+ var split = text.Split('\n');
+ var currentLocation = trivia.GetLocation().SourceSpan.Start - 3;
+
+ for (var line = 0; line < split.Length - 1; ++line)
+ {
+ var fullLine = split[line];
+ var nextLineLocation = currentLocation + fullLine.Length + 1;
+ fullLine = fullLine.TrimEnd('\r');
+ var trimmedLine = fullLine;
+
+ var leadingSpaces = trimmedLine.IndexOf('/');
+ if (leadingSpaces != -1)
+ {
+ fullLine = fullLine.Substring(leadingSpaces);
+ currentLocation += leadingSpaces;
+ trimmedLine = trimmedLine.Substring(leadingSpaces + 3); // Remove leading spaces and the "///"
+ trimmedLine = trimmedLine.Trim();
+
+ var span = Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(currentLocation, currentLocation + fullLine.Length);
+ var location = Microsoft.CodeAnalysis.Location.Create(trivia.SyntaxTree, span);
+ var commentType = CommentLineType.XmlDoc;
+ cx.CommentGenerator.AddComment(CommentLine.Create(cx, location, commentType, trimmedLine, fullLine));
+ }
+ else
+ {
+ cx.ModelError("Unexpected comment format");
+ }
+ currentLocation = nextLineLocation;
+ }
+ break;
+
+ case SyntaxKind.SingleLineCommentTrivia:
+ {
+ var contents = trivia.ToString().Substring(2);
+ var commentType = CommentLineType.Singleline;
+ if (contents.Length > 0 && contents[0] == '/')
+ {
+ commentType = CommentLineType.XmlDoc;
+ contents = contents.Substring(1); // An XML comment.
+ }
+ cx.CommentGenerator.AddComment(CommentLine.Create(cx, trivia.GetLocation(), commentType, contents.Trim(), trivia.ToFullString()));
+ }
+ break;
+ case SyntaxKind.MultiLineDocumentationCommentTrivia:
+ case SyntaxKind.MultiLineCommentTrivia:
+ /* We receive a single SyntaxTrivia for a multiline block spanning several lines.
+ So we split it into separate lines
+ */
+ text = trivia.ToFullString();
+
+ split = text.Split('\n');
+ currentLocation = trivia.GetLocation().SourceSpan.Start;
+
+ for (var line = 0; line < split.Length; ++line)
+ {
+ var fullLine = split[line];
+ var nextLineLocation = currentLocation + fullLine.Length + 1;
+ fullLine = fullLine.TrimEnd('\r');
+ var trimmedLine = fullLine;
+ if (line == 0)
+ trimmedLine = trimmedLine.Substring(2);
+ if (line == split.Length - 1)
+ trimmedLine = trimmedLine.Substring(0, trimmedLine.Length - 2);
+ trimmedLine = trimmedLine.Trim();
+
+ var span = Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(currentLocation, currentLocation + fullLine.Length);
+ var location = Microsoft.CodeAnalysis.Location.Create(trivia.SyntaxTree, span);
+ var commentType = line == 0 ? CommentLineType.Multiline : CommentLineType.MultilineContinuation;
+ cx.CommentGenerator.AddComment(CommentLine.Create(cx, location, commentType, trimmedLine, fullLine));
+ currentLocation = nextLineLocation;
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/Comments.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/Comments.cs
deleted file mode 100644
index 8f6bb6f5206..00000000000
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/Comments.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using Semmle.Extraction.CommentProcessing;
-using System;
-
-namespace Semmle.Extraction.CSharp.Populators
-{
- ///
- /// Populators for comments.
- ///
- public static class Comments
- {
- public static void ExtractComments(this Context cx, ICommentGenerator gen)
- {
- cx.Try(null, null, () =>
- {
- gen.GenerateBindings((entity, duplicationGuardKey, block, binding) =>
- {
- var commentBlock = Entities.CommentBlock.Create(cx, block);
- Action a = () =>
- {
- commentBlock.BindTo(entity, binding);
- };
- // When the duplication guard key exists, it means that the entity is guarded against
- // trap duplication ().
- // We must therefore also guard comment construction.
- if (duplicationGuardKey != null)
- cx.WithDuplicationGuard(duplicationGuardKey, a);
- else
- a();
- });
- });
- }
- }
-}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnit.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnit.cs
deleted file mode 100644
index 69814bf197a..00000000000
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnit.cs
+++ /dev/null
@@ -1,150 +0,0 @@
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Semmle.Extraction.CSharp.Entities;
-using Semmle.Extraction.Entities;
-using Semmle.Util;
-using Semmle.Util.Logging;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-
-namespace Semmle.Extraction.CSharp.Populators
-{
- public class TypeContainerVisitor : CSharpSyntaxVisitor
- {
- protected Context cx { get; }
- protected IEntity parent { get; }
- protected TextWriter trapFile { get; }
- private readonly Lazy> attributeLookup;
-
- public TypeContainerVisitor(Context cx, TextWriter trapFile, IEntity parent)
- {
- this.cx = cx;
- this.parent = parent;
- this.trapFile = trapFile;
- attributeLookup = new Lazy>(() =>
- {
- var dict = new Dictionary();
- foreach (var attributeData in cx.Compilation.Assembly.GetAttributes().Concat(cx.Compilation.Assembly.Modules.SelectMany(m => m.GetAttributes())))
- {
- if (attributeData.ApplicationSyntaxReference?.GetSyntax() is SyntaxNode syntax)
- dict.Add(syntax, attributeData);
- }
- return dict.GetValueOrDefault;
- });
- }
-
- public override void DefaultVisit(SyntaxNode node)
- {
- throw new InternalError(node, "Unhandled top-level syntax node");
- }
-
- public override void VisitDelegateDeclaration(DelegateDeclarationSyntax node)
- {
- Entities.NamedType.Create(cx, cx.GetModel(node).GetDeclaredSymbol(node)).ExtractRecursive(trapFile, parent);
- }
-
- public override void VisitClassDeclaration(ClassDeclarationSyntax classDecl)
- {
- Entities.Type.Create(cx, cx.GetModel(classDecl).GetDeclaredSymbol(classDecl)).ExtractRecursive(trapFile, parent);
- }
-
- public override void VisitStructDeclaration(StructDeclarationSyntax node)
- {
- Entities.Type.Create(cx, cx.GetModel(node).GetDeclaredSymbol(node)).ExtractRecursive(trapFile, parent);
- }
-
- public override void VisitEnumDeclaration(EnumDeclarationSyntax node)
- {
- Entities.Type.Create(cx, cx.GetModel(node).GetDeclaredSymbol(node)).ExtractRecursive(trapFile, parent);
- }
-
- public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)
- {
- Entities.Type.Create(cx, cx.GetModel(node).GetDeclaredSymbol(node)).ExtractRecursive(trapFile, parent);
- }
-
- public override void VisitAttributeList(AttributeListSyntax node)
- {
- if (cx.Extractor.Standalone)
- return;
-
- var outputAssembly = Assembly.CreateOutputAssembly(cx);
- foreach (var attribute in node.Attributes)
- {
- if (attributeLookup.Value(attribute) is AttributeData attributeData)
- {
- var ae = Semmle.Extraction.CSharp.Entities.Attribute.Create(cx, attributeData, outputAssembly);
- cx.BindComments(ae, attribute.GetLocation());
- }
- }
- }
- }
-
- internal class TypeOrNamespaceVisitor : TypeContainerVisitor
- {
- public TypeOrNamespaceVisitor(Context cx, TextWriter trapFile, IEntity parent)
- : base(cx, trapFile, parent) { }
-
- public override void VisitUsingDirective(UsingDirectiveSyntax usingDirective)
- {
- // Only deal with "using namespace" not "using X = Y"
- if (usingDirective.Alias == null)
- new UsingDirective(cx, usingDirective, (NamespaceDeclaration)parent);
- }
-
- public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
- {
- NamespaceDeclaration.Create(cx, node, (NamespaceDeclaration)parent);
- }
- }
-
- internal class CompilationUnitVisitor : TypeOrNamespaceVisitor
- {
- public CompilationUnitVisitor(Context cx)
- : base(cx, cx.TrapWriter.Writer, null) { }
-
- public override void VisitExternAliasDirective(ExternAliasDirectiveSyntax node)
- {
- // This information is not yet extracted.
- cx.ExtractionError("Not implemented extern alias directive", node.ToFullString(), Extraction.Entities.Location.Create(cx, node.GetLocation()), "", Severity.Info);
- }
-
- public override void VisitCompilationUnit(CompilationUnitSyntax compilationUnit)
- {
- foreach (var m in compilationUnit.ChildNodes())
- {
- cx.Try(m, null, () => ((CSharpSyntaxNode)m).Accept(this));
- }
-
- // Gather comments:
- foreach (var trivia in compilationUnit.DescendantTrivia(compilationUnit.Span))
- {
- CommentLine.Extract(cx, trivia);
- }
-
- foreach (var trivia in compilationUnit.GetLeadingTrivia())
- {
- CommentLine.Extract(cx, trivia);
- }
-
- foreach (var trivia in compilationUnit.GetTrailingTrivia())
- {
- CommentLine.Extract(cx, trivia);
- }
- }
- }
-
- public class CompilationUnit
- {
- public static void Extract(Context cx, SyntaxNode unit)
- {
- // Ensure that the file itself is populated in case the source file is totally empty
- Semmle.Extraction.Entities.File.Create(cx, unit.SyntaxTree.FilePath);
-
- ((CSharpSyntaxNode)unit).Accept(new CompilationUnitVisitor(cx));
- }
- }
-}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnitVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnitVisitor.cs
new file mode 100644
index 00000000000..58e5404ccd7
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnitVisitor.cs
@@ -0,0 +1,73 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Semmle.Util.Logging;
+using Semmle.Extraction.CSharp.Entities;
+using Semmle.Extraction.CSharp.Entities.Statements;
+using System.Linq;
+
+namespace Semmle.Extraction.CSharp.Populators
+{
+ internal class CompilationUnitVisitor : TypeOrNamespaceVisitor
+ {
+ public CompilationUnitVisitor(Context cx)
+ : base(cx, cx.TrapWriter.Writer, null) { }
+
+ public override void VisitExternAliasDirective(ExternAliasDirectiveSyntax node)
+ {
+ // This information is not yet extracted.
+ Cx.ExtractionError("Not implemented extern alias directive", node.ToFullString(), Cx.CreateLocation(node.GetLocation()), "", Severity.Info);
+ }
+
+ public override void VisitCompilationUnit(CompilationUnitSyntax compilationUnit)
+ {
+ foreach (var m in compilationUnit.ChildNodes())
+ {
+ Cx.Try(m, null, () => ((CSharpSyntaxNode)m).Accept(this));
+ }
+
+ ExtractGlobalStatements(compilationUnit);
+
+ // Gather comments:
+ foreach (var trivia in compilationUnit.DescendantTrivia(compilationUnit.Span, descendIntoTrivia: true))
+ {
+ CommentPopulator.ExtractComment(Cx, trivia);
+ }
+
+ foreach (var trivia in compilationUnit.GetLeadingTrivia())
+ {
+ CommentPopulator.ExtractComment(Cx, trivia);
+ }
+
+ foreach (var trivia in compilationUnit.GetTrailingTrivia())
+ {
+ CommentPopulator.ExtractComment(Cx, trivia);
+ }
+ }
+
+ private void ExtractGlobalStatements(CompilationUnitSyntax compilationUnit)
+ {
+ var globalStatements = compilationUnit
+ .ChildNodes()
+ .OfType()
+ .ToList();
+
+ if (!globalStatements.Any())
+ {
+ return;
+ }
+
+ var entryPoint = Cx.Compilation.GetEntryPoint(System.Threading.CancellationToken.None);
+ var entryMethod = Method.Create(Cx, entryPoint);
+ var block = GlobalStatementsBlock.Create(Cx, entryMethod);
+
+ for (var i = 0; i < globalStatements.Count; i++)
+ {
+ if (globalStatements[i].Statement is object)
+ {
+ Statement.Create(Cx, globalStatements[i].Statement, block, i);
+ }
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
new file mode 100644
index 00000000000..0c400728554
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
@@ -0,0 +1,137 @@
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Semmle.Extraction.CSharp.Populators
+{
+ internal class DirectiveVisitor : CSharpSyntaxWalker
+ {
+ private readonly Context cx;
+
+ public DirectiveVisitor(Context cx) : base(SyntaxWalkerDepth.StructuredTrivia)
+ {
+ this.cx = cx;
+ }
+
+ public override void VisitPragmaWarningDirectiveTrivia(PragmaWarningDirectiveTriviaSyntax node)
+ {
+ new Entities.PragmaWarningDirective(cx, node);
+ }
+
+ public override void VisitPragmaChecksumDirectiveTrivia(PragmaChecksumDirectiveTriviaSyntax node)
+ {
+ new Entities.PragmaChecksumDirective(cx, node);
+ }
+
+ public override void VisitDefineDirectiveTrivia(DefineDirectiveTriviaSyntax node)
+ {
+ new Entities.DefineDirective(cx, node);
+ }
+
+ public override void VisitUndefDirectiveTrivia(UndefDirectiveTriviaSyntax node)
+ {
+ new Entities.UndefineDirective(cx, node);
+ }
+
+ public override void VisitWarningDirectiveTrivia(WarningDirectiveTriviaSyntax node)
+ {
+ new Entities.WarningDirective(cx, node);
+ }
+
+ public override void VisitErrorDirectiveTrivia(ErrorDirectiveTriviaSyntax node)
+ {
+ new Entities.ErrorDirective(cx, node);
+ }
+
+ public override void VisitNullableDirectiveTrivia(NullableDirectiveTriviaSyntax node)
+ {
+ new Entities.NullableDirective(cx, node);
+ }
+
+ public override void VisitLineDirectiveTrivia(LineDirectiveTriviaSyntax node)
+ {
+ new Entities.LineDirective(cx, node);
+ }
+
+ private readonly Stack regionStarts = new Stack();
+
+ public override void VisitRegionDirectiveTrivia(RegionDirectiveTriviaSyntax node)
+ {
+ var region = new Entities.RegionDirective(cx, node);
+ regionStarts.Push(region);
+ }
+
+ public override void VisitEndRegionDirectiveTrivia(EndRegionDirectiveTriviaSyntax node)
+ {
+ if (regionStarts.Count == 0)
+ {
+ cx.ExtractionError("Couldn't find start region", null,
+ cx.CreateLocation(node.GetLocation()), null, Util.Logging.Severity.Warning);
+ return;
+ }
+
+ var start = regionStarts.Pop();
+ new Entities.EndRegionDirective(cx, node, start);
+ }
+
+ private class IfDirectiveStackElement
+ {
+ public Entities.IfDirective Entity { get; }
+ public int SiblingCount { get; set; }
+
+
+ public IfDirectiveStackElement(Entities.IfDirective entity)
+ {
+ Entity = entity;
+ }
+ }
+
+ private readonly Stack ifStarts = new Stack();
+
+ public override void VisitIfDirectiveTrivia(IfDirectiveTriviaSyntax node)
+ {
+ var ifStart = new Entities.IfDirective(cx, node);
+ ifStarts.Push(new IfDirectiveStackElement(ifStart));
+ }
+
+ public override void VisitEndIfDirectiveTrivia(EndIfDirectiveTriviaSyntax node)
+ {
+ if (ifStarts.Count == 0)
+ {
+ cx.ExtractionError("Couldn't find start if", null,
+ cx.CreateLocation(node.GetLocation()), null, Util.Logging.Severity.Warning);
+ return;
+ }
+
+ var start = ifStarts.Pop();
+ new Entities.EndIfDirective(cx, node, start.Entity);
+ }
+
+ public override void VisitElifDirectiveTrivia(ElifDirectiveTriviaSyntax node)
+ {
+ if (ifStarts.Count == 0)
+ {
+ cx.ExtractionError("Couldn't find start if", null,
+ cx.CreateLocation(node.GetLocation()), null, Util.Logging.Severity.Warning);
+ return;
+ }
+
+ var start = ifStarts.Peek();
+ new Entities.ElifDirective(cx, node, start.Entity, start.SiblingCount++);
+ }
+
+ public override void VisitElseDirectiveTrivia(ElseDirectiveTriviaSyntax node)
+ {
+ if (ifStarts.Count == 0)
+ {
+ cx.ExtractionError("Couldn't find start if", null,
+ cx.CreateLocation(node.GetLocation()), null, Util.Logging.Severity.Warning);
+ return;
+ }
+
+ var start = ifStarts.Peek();
+ new Entities.ElseDirective(cx, node, start.Entity, start.SiblingCount++);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/Methods.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/Methods.cs
deleted file mode 100644
index 148160b8815..00000000000
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/Methods.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Semmle.Util;
-using System.IO;
-
-namespace Semmle.Extraction.CSharp.Populators
-{
- public static class MethodExtensions
- {
- private class AstLineCounter : CSharpSyntaxVisitor
- {
- public override LineCounts DefaultVisit(SyntaxNode node)
- {
- var text = node.SyntaxTree.GetText().GetSubText(node.GetLocation().SourceSpan).ToString();
- return Semmle.Util.LineCounter.ComputeLineCounts(text);
- }
-
- public override LineCounts VisitMethodDeclaration(MethodDeclarationSyntax method)
- {
- return Visit(method.Identifier, method.Body ?? (SyntaxNode)method.ExpressionBody);
- }
-
- public static LineCounts Visit(SyntaxToken identifier, SyntaxNode body)
- {
- var start = identifier.GetLocation().SourceSpan.Start;
- var end = body.GetLocation().SourceSpan.End - 1;
-
- var textSpan = new Microsoft.CodeAnalysis.Text.TextSpan(start, end - start);
-
- var text = body.SyntaxTree.GetText().GetSubText(textSpan) + "\r\n";
- return Semmle.Util.LineCounter.ComputeLineCounts(text);
- }
-
- public override LineCounts VisitConstructorDeclaration(ConstructorDeclarationSyntax method)
- {
- return Visit(method.Identifier, (SyntaxNode)method.Body ?? method.ExpressionBody);
- }
-
- public override LineCounts VisitDestructorDeclaration(DestructorDeclarationSyntax method)
- {
- return Visit(method.Identifier, (SyntaxNode)method.Body ?? method.ExpressionBody);
- }
-
- public override LineCounts VisitOperatorDeclaration(OperatorDeclarationSyntax node)
- {
- return Visit(node.OperatorToken, node.Body ?? (SyntaxNode)node.ExpressionBody);
- }
- }
-
- public static void NumberOfLines(this Context cx, TextWriter trapFile, ISymbol symbol, IEntity callable)
- {
- foreach (var decl in symbol.DeclaringSyntaxReferences)
- {
- cx.NumberOfLines(trapFile, (CSharpSyntaxNode)decl.GetSyntax(), callable);
- }
- }
-
- public static void NumberOfLines(this Context cx, TextWriter trapFile, CSharpSyntaxNode node, IEntity callable)
- {
- var lineCounts = node.Accept(new AstLineCounter());
- trapFile.numlines(callable, lineCounts);
- }
- }
-}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/Symbols.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/Symbols.cs
index ddf64257cbe..a4a06fd6ba2 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/Symbols.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/Symbols.cs
@@ -38,6 +38,8 @@ namespace Semmle.Extraction.CSharp.Populators
public override IEntity VisitPointerType(IPointerTypeSymbol symbol) => PointerType.Create(cx, symbol);
+ public override IEntity VisitFunctionPointerType(IFunctionPointerTypeSymbol symbol) => FunctionPointerType.Create(cx, symbol);
+
public override IEntity VisitDynamicType(IDynamicTypeSymbol symbol) => DynamicType.Create(cx, symbol);
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/TypeContainerVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/TypeContainerVisitor.cs
new file mode 100644
index 00000000000..ededf2978b9
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/TypeContainerVisitor.cs
@@ -0,0 +1,99 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Semmle.Extraction.Entities;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Semmle.Extraction.CSharp.Populators
+{
+ public class TypeContainerVisitor : CSharpSyntaxVisitor
+ {
+ protected Context Cx { get; }
+ protected IEntity Parent { get; }
+ protected TextWriter TrapFile { get; }
+ private readonly Lazy> attributeLookup;
+
+ public TypeContainerVisitor(Context cx, TextWriter trapFile, IEntity parent)
+ {
+ Cx = cx;
+ Parent = parent;
+ TrapFile = trapFile;
+
+ attributeLookup = new Lazy>(() =>
+ {
+ var dict = new Dictionary();
+ foreach (var attributeData in cx.Compilation.Assembly.GetAttributes().Concat(cx.Compilation.Assembly.Modules.SelectMany(m => m.GetAttributes())))
+ {
+ if (attributeData.ApplicationSyntaxReference?.GetSyntax() is SyntaxNode syntax)
+ dict.Add(syntax, attributeData);
+ }
+ return dict.GetValueOrDefault;
+ });
+ }
+
+ public override void DefaultVisit(SyntaxNode node)
+ {
+ throw new InternalError(node, "Unhandled top-level syntax node");
+ }
+
+ public override void VisitGlobalStatement(GlobalStatementSyntax node)
+ {
+ // Intentionally left empty.
+ // Global statements are handled in CompilationUnitVisitor
+ }
+
+ public override void VisitDelegateDeclaration(DelegateDeclarationSyntax node)
+ {
+ Entities.NamedType.Create(Cx, Cx.GetModel(node).GetDeclaredSymbol(node)).ExtractRecursive(TrapFile, Parent);
+ }
+
+ public override void VisitRecordDeclaration(RecordDeclarationSyntax node)
+ {
+ ExtractTypeDeclaration(node);
+ }
+
+ public override void VisitClassDeclaration(ClassDeclarationSyntax node)
+ {
+ ExtractTypeDeclaration(node);
+ }
+
+ public override void VisitStructDeclaration(StructDeclarationSyntax node)
+ {
+ ExtractTypeDeclaration(node);
+ }
+
+ public override void VisitEnumDeclaration(EnumDeclarationSyntax node)
+ {
+ ExtractTypeDeclaration(node);
+ }
+
+ public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)
+ {
+ ExtractTypeDeclaration(node);
+ }
+
+ private void ExtractTypeDeclaration(BaseTypeDeclarationSyntax node)
+ {
+ Entities.Type.Create(Cx, Cx.GetModel(node).GetDeclaredSymbol(node)).ExtractRecursive(TrapFile, Parent);
+ }
+
+ public override void VisitAttributeList(AttributeListSyntax node)
+ {
+ if (Cx.Extractor.Standalone)
+ return;
+
+ var outputAssembly = Assembly.CreateOutputAssembly(Cx);
+ foreach (var attribute in node.Attributes)
+ {
+ if (attributeLookup.Value(attribute) is AttributeData attributeData)
+ {
+ var ae = Entities.Attribute.Create(Cx, attributeData, outputAssembly);
+ Cx.BindComments(ae, attribute.GetLocation());
+ }
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/TypeOrNamespaceVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/TypeOrNamespaceVisitor.cs
new file mode 100644
index 00000000000..a3daae08a60
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/TypeOrNamespaceVisitor.cs
@@ -0,0 +1,24 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Semmle.Extraction.CSharp.Entities;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Populators
+{
+ internal class TypeOrNamespaceVisitor : TypeContainerVisitor
+ {
+ public TypeOrNamespaceVisitor(Context cx, TextWriter trapFile, IEntity parent)
+ : base(cx, trapFile, parent) { }
+
+ public override void VisitUsingDirective(UsingDirectiveSyntax usingDirective)
+ {
+ // Only deal with "using namespace" not "using X = Y"
+ if (usingDirective.Alias == null)
+ new UsingDirective(Cx, usingDirective, (NamespaceDeclaration)Parent);
+ }
+
+ public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
+ {
+ NamespaceDeclaration.Create(Cx, node, (NamespaceDeclaration)Parent);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs b/csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs
index 530a30d8a51..77e27a00be5 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs
@@ -23,6 +23,9 @@ namespace Semmle.Extraction.CSharp
Symbol = symbol;
Nullability = nullability;
}
+
+ public static AnnotatedTypeSymbol? CreateNotAnnotated(ITypeSymbol symbol) =>
+ symbol is null ? (AnnotatedTypeSymbol?)null : new AnnotatedTypeSymbol(symbol, NullableAnnotation.None);
}
internal static class SymbolExtensions
@@ -131,6 +134,13 @@ namespace Semmle.Extraction.CSharp
return tp.ContainingSymbol is ITypeSymbol cont
? IdDependsOnImpl(cont)
: SymbolEqualityComparer.Default.Equals(tp.ContainingSymbol, symbol);
+ case TypeKind.FunctionPointer:
+ var funptr = (IFunctionPointerTypeSymbol)type;
+ if (funptr.Signature.Parameters.Any(p => IdDependsOnImpl(p.Type)))
+ {
+ return true;
+ }
+ return IdDependsOnImpl(funptr.Signature.ReturnType);
default:
return false;
}
@@ -187,6 +197,10 @@ namespace Semmle.Extraction.CSharp
case TypeKind.Dynamic:
trapFile.Write("dynamic");
return;
+ case TypeKind.FunctionPointer:
+ var funptr = (IFunctionPointerTypeSymbol)type;
+ funptr.BuildFunctionPointerTypeId(cx, trapFile, symbolBeingDefined);
+ return;
default:
throw new InternalError(type, $"Unhandled type kind '{type.TypeKind}'");
}
@@ -262,6 +276,11 @@ namespace Semmle.Extraction.CSharp
trapFile.Write("::");
}
+ private static void BuildFunctionPointerTypeId(this IFunctionPointerTypeSymbol funptr, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined)
+ {
+ BuildFunctionPointerSignature(funptr, trapFile, (s, tw) => s.BuildOrWriteId(cx, tw, symbolBeingDefined));
+ }
+
private static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined, bool addBaseClass, bool constructUnderlyingTupleType)
{
if (!constructUnderlyingTupleType && named.IsTupleType)
@@ -391,6 +410,10 @@ namespace Semmle.Extraction.CSharp
ptr.PointedAtType.BuildDisplayName(cx, trapFile);
trapFile.Write('*');
return;
+ case TypeKind.FunctionPointer:
+ var funptr = (IFunctionPointerTypeSymbol)type;
+ funptr.BuildFunctionPointerTypeDisplayName(cx, trapFile);
+ return;
case TypeKind.TypeParameter:
trapFile.Write(type.Name);
return;
@@ -403,31 +426,93 @@ namespace Semmle.Extraction.CSharp
}
}
- public static void BuildNamedTypeDisplayName(this INamedTypeSymbol namedType, Context cx, TextWriter trapFile, bool constructUnderlyingTupleType)
+ public static void BuildFunctionPointerSignature(IFunctionPointerTypeSymbol funptr, TextWriter trapFile,
+ Action buildNested)
+ {
+ trapFile.Write("delegate* ");
+ trapFile.Write(funptr.Signature.CallingConvention.ToString().ToLowerInvariant());
+
+ if (funptr.Signature.UnmanagedCallingConventionTypes.Any())
+ {
+ trapFile.Write('[');
+ trapFile.BuildList(",", funptr.Signature.UnmanagedCallingConventionTypes, buildNested);
+ trapFile.Write("]");
+ }
+
+ trapFile.Write('<');
+ trapFile.BuildList(",", funptr.Signature.Parameters,
+ (p, trap) =>
+ {
+ buildNested(p.Type, trap);
+ switch (p.RefKind)
+ {
+ case RefKind.Out:
+ trap.Write(" out");
+ break;
+ case RefKind.In:
+ trap.Write(" in");
+ break;
+ case RefKind.Ref:
+ trap.Write(" ref");
+ break;
+ }
+ });
+
+ if (funptr.Signature.Parameters.Any())
+ {
+ trapFile.Write(",");
+ }
+
+ buildNested(funptr.Signature.ReturnType, trapFile);
+
+ if (funptr.Signature.ReturnsByRef)
+ trapFile.Write(" ref");
+ if (funptr.Signature.ReturnsByRefReadonly)
+ trapFile.Write(" ref readonly");
+
+ trapFile.Write('>');
+ }
+
+ private static void BuildFunctionPointerTypeDisplayName(this IFunctionPointerTypeSymbol funptr, Context cx, TextWriter trapFile)
+ {
+ BuildFunctionPointerSignature(funptr, trapFile, (s, tw) => s.BuildDisplayName(cx, tw));
+ }
+
+ private static void BuildNamedTypeDisplayName(this INamedTypeSymbol namedType, Context cx, TextWriter trapFile, bool constructUnderlyingTupleType)
{
if (!constructUnderlyingTupleType && namedType.IsTupleType)
{
trapFile.Write('(');
- trapFile.BuildList(",", namedType.TupleElements.Select(f => f.Type),
- (t, tb0) => t.BuildDisplayName(cx, tb0)
- );
-
+ trapFile.BuildList(
+ ",",
+ namedType.TupleElements.Select(f => f.Type),
+ (t, tb0) => t.BuildDisplayName(cx, tb0));
trapFile.Write(")");
return;
}
if (namedType.IsAnonymousType)
+ {
namedType.BuildAnonymousName(cx, trapFile);
+ }
else
+ {
trapFile.Write(namedType.Name);
+ }
+
if (namedType.IsGenericType && namedType.TypeKind != TypeKind.Error && namedType.TypeArguments.Any())
{
trapFile.Write('<');
- trapFile.BuildList(",", namedType.TypeArguments, (p, tb0) =>
- {
- if (IsReallyBound(namedType))
- p.BuildDisplayName(cx, tb0);
- });
+ trapFile.BuildList(
+ ",",
+ namedType.TypeArguments,
+ (p, tb0) =>
+ {
+ if (IsReallyBound(namedType))
+ {
+ p.BuildDisplayName(cx, tb0);
+ }
+ });
trapFile.Write('>');
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
index 4123cc1808d..df39d6a836c 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
@@ -1,5 +1,7 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.CommentProcessing;
using Semmle.Extraction.CSharp.Entities;
+using Semmle.Extraction.CSharp.Entities.Expressions;
using Semmle.Extraction.Entities;
using Semmle.Extraction.Kinds;
using Semmle.Util;
@@ -26,6 +28,11 @@ namespace Semmle.Extraction.CSharp
trapFile.WriteTuple("accessors", accessorKey, kind, name, propKey, unboundAccessor);
}
+ internal static void init_only_accessors(this TextWriter trapFile, Accessor accessorKey)
+ {
+ trapFile.WriteTuple("init_only_accessors", accessorKey);
+ }
+
internal static void array_element_type(this TextWriter trapFile, ArrayType array, int dimension, int rank, Type elementType)
{
trapFile.WriteTuple("array_element_type", array, dimension, rank, elementType);
@@ -46,6 +53,17 @@ namespace Semmle.Extraction.CSharp
trapFile.WriteTuple("catch_type", @catch, type, explicityCaught ? 1 : 2);
}
+ internal static void foreach_stmt_info(this TextWriter trapFile, Entities.Statements.ForEach @foreach, bool isAsync)
+ {
+ trapFile.WriteTuple("foreach_stmt_info", @foreach, isAsync ? 2 : 1);
+ }
+
+ internal static void foreach_stmt_desugar(this TextWriter trapFile, Entities.Statements.ForEach @foreach, IEntity entity,
+ Entities.Statements.ForEach.ForeachSymbolType type)
+ {
+ trapFile.WriteTuple("foreach_stmt_desugar", @foreach, entity, (int)type);
+ }
+
internal static void commentblock(this TextWriter trapFile, CommentBlock k)
{
trapFile.WriteTuple("commentblock", k);
@@ -106,6 +124,11 @@ namespace Semmle.Extraction.CSharp
trapFile.WriteTuple("compilations", compilation, cwd);
}
+ internal static void compilation_assembly(this TextWriter trapFile, Compilation compilation, Assembly assembly)
+ {
+ trapFile.WriteTuple("compilation_assembly", compilation, assembly);
+ }
+
internal static void compiler_generated(this TextWriter trapFile, IEntity entity)
{
trapFile.WriteTuple("compiler_generated", entity);
@@ -141,6 +164,11 @@ namespace Semmle.Extraction.CSharp
trapFile.WriteTuple("delegate_return_type", delegateKey, returnType);
}
+ internal static void function_pointer_return_type(this TextWriter trapFile, Type functionPointer, Type returnType)
+ {
+ trapFile.WriteTuple("function_pointer_return_type", functionPointer, returnType);
+ }
+
internal static void destructor_location(this TextWriter trapFile, Destructor destructor, Location location)
{
trapFile.WriteTuple("destructor_location", destructor, location);
@@ -266,6 +294,11 @@ namespace Semmle.Extraction.CSharp
trapFile.WriteTuple("extend", type, super);
}
+ internal static void anonymous_types(this TextWriter trapFile, Type type)
+ {
+ trapFile.WriteTuple("anonymous_types", type);
+ }
+
internal static void field_location(this TextWriter trapFile, Field field, Location location)
{
trapFile.WriteTuple("field_location", field, location);
@@ -296,6 +329,11 @@ namespace Semmle.Extraction.CSharp
trapFile.WriteTuple("implicitly_typed_array_creation", array);
}
+ internal static void implicitly_typed_object_creation(this TextWriter trapFile, Expression expression)
+ {
+ trapFile.WriteTuple("implicitly_typed_object_creation", expression);
+ }
+
internal static void indexer_location(this TextWriter trapFile, Indexer indexer, Location location)
{
trapFile.WriteTuple("indexer_location", indexer, location);
@@ -456,6 +494,16 @@ namespace Semmle.Extraction.CSharp
trapFile.WriteTuple("specific_type_parameter_nullability", constraints, baseType, nullability);
}
+ internal static void function_pointer_calling_conventions(this TextWriter trapFile, FunctionPointerType type, int kind)
+ {
+ trapFile.WriteTuple("function_pointer_calling_conventions", type, kind);
+ }
+
+ internal static void has_unmanaged_calling_conventions(this TextWriter trapFile, FunctionPointerType type, int index, Type convention)
+ {
+ trapFile.WriteTuple("has_unmanaged_calling_conventions", type, index, convention);
+ }
+
internal static void stackalloc_array_creation(this TextWriter trapFile, Expression array)
{
trapFile.WriteTuple("stackalloc_array_creation", array);
@@ -555,5 +603,123 @@ namespace Semmle.Extraction.CSharp
{
trapFile.WriteTuple("using_static_directives", @using, type);
}
+
+ internal static void preprocessor_directive_location(this TextWriter trapFile,
+ PreprocessorDirective directive, Location location)
+ where TDirective : DirectiveTriviaSyntax
+ {
+ trapFile.WriteTuple("preprocessor_directive_location", directive, location);
+ }
+
+ internal static void preprocessor_directive_compilation(this TextWriter trapFile,
+ PreprocessorDirective directive, Compilation compilation)
+ where TDirective : DirectiveTriviaSyntax
+ {
+ trapFile.WriteTuple("preprocessor_directive_compilation", directive, compilation);
+ }
+
+ internal static void preprocessor_directive_active(this TextWriter trapFile,
+ PreprocessorDirective directive, bool isActive)
+ where TDirective : DirectiveTriviaSyntax
+ {
+ trapFile.WriteTuple("preprocessor_directive_active", directive, isActive ? 1 : 0);
+ }
+
+ internal static void pragma_warnings(this TextWriter trapFile, PragmaWarningDirective pragma, int kind)
+ {
+ trapFile.WriteTuple("pragma_warnings", pragma, kind);
+ }
+
+ internal static void pragma_warning_error_codes(this TextWriter trapFile, PragmaWarningDirective pragma, string errorCode, int child)
+ {
+ trapFile.WriteTuple("pragma_warning_error_codes", pragma, errorCode, child);
+ }
+
+ internal static void pragma_checksums(this TextWriter trapFile, PragmaChecksumDirective pragma, Extraction.Entities.File file, string guid, string bytes)
+ {
+ trapFile.WriteTuple("pragma_checksums", pragma, file, guid, bytes);
+ }
+
+ internal static void directive_defines(this TextWriter trapFile, DefineDirective directive, string name)
+ {
+ trapFile.WriteTuple("directive_defines", directive, name);
+ }
+
+ internal static void directive_undefines(this TextWriter trapFile, UndefineDirective directive, string name)
+ {
+ trapFile.WriteTuple("directive_undefines", directive, name);
+ }
+
+ internal static void directive_warnings(this TextWriter trapFile, WarningDirective directive, string message)
+ {
+ trapFile.WriteTuple("directive_warnings", directive, message);
+ }
+
+ internal static void directive_errors(this TextWriter trapFile, ErrorDirective directive, string message)
+ {
+ trapFile.WriteTuple("directive_errors", directive, message);
+ }
+
+ internal static void directive_nullables(this TextWriter trapFile, NullableDirective directive, int setting, int target)
+ {
+ trapFile.WriteTuple("directive_nullables", directive, setting, target);
+ }
+
+ internal static void directive_lines(this TextWriter trapFile, LineDirective directive, int kind)
+ {
+ trapFile.WriteTuple("directive_lines", directive, kind);
+ }
+
+ internal static void directive_line_value(this TextWriter trapFile, LineDirective directive, int line)
+ {
+ trapFile.WriteTuple("directive_line_value", directive, line);
+ }
+
+ internal static void directive_line_file(this TextWriter trapFile, LineDirective directive, Extraction.Entities.File file)
+ {
+ trapFile.WriteTuple("directive_line_file", directive, file);
+ }
+
+ internal static void directive_regions(this TextWriter trapFile, RegionDirective directive, string name)
+ {
+ trapFile.WriteTuple("directive_regions", directive, name);
+ }
+
+ internal static void directive_endregions(this TextWriter trapFile, EndRegionDirective directive, RegionDirective start)
+ {
+ trapFile.WriteTuple("directive_endregions", directive, start);
+ }
+
+ internal static void regions(this TextWriter trapFile, RegionDirective start, EndRegionDirective end)
+ {
+ trapFile.WriteTuple("regions", start, end);
+ }
+
+ internal static void directive_ifs(this TextWriter trapFile, IfDirective directive, bool branchTaken, bool conditionValue)
+ {
+ trapFile.WriteTuple("directive_ifs", directive, branchTaken ? 1 : 0, conditionValue ? 1 : 0);
+ }
+
+ internal static void directive_elifs(this TextWriter trapFile, ElifDirective directive, bool branchTaken, bool conditionValue,
+ IfDirective start, int index)
+ {
+ trapFile.WriteTuple("directive_elifs", directive, branchTaken ? 1 : 0, conditionValue ? 1 : 0, start, index);
+ }
+
+ internal static void directive_elses(this TextWriter trapFile, ElseDirective directive, bool branchTaken,
+ IfDirective start, int index)
+ {
+ trapFile.WriteTuple("directive_elses", directive, branchTaken ? 1 : 0, start, index);
+ }
+
+ internal static void directive_endifs(this TextWriter trapFile, EndIfDirective directive, IfDirective start)
+ {
+ trapFile.WriteTuple("directive_endifs", directive, start);
+ }
+
+ internal static void directive_define_symbols(this TextWriter trapFile, DefineSymbol symb, string name)
+ {
+ trapFile.WriteTuple("directive_define_symbols", symb, name);
+ }
}
}
diff --git a/csharp/extractor/Semmle.Extraction/AssemblyScope.cs b/csharp/extractor/Semmle.Extraction/AssemblyScope.cs
new file mode 100644
index 00000000000..27c9377bb30
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction/AssemblyScope.cs
@@ -0,0 +1,25 @@
+using Microsoft.CodeAnalysis;
+
+namespace Semmle.Extraction
+{
+ ///
+ /// The scope of symbols in an assembly.
+ ///
+ public class AssemblyScope : IExtractionScope
+ {
+ private readonly IAssemblySymbol assembly;
+ private readonly string filepath;
+
+ public AssemblyScope(IAssemblySymbol symbol, string path)
+ {
+ assembly = symbol;
+ filepath = path;
+ }
+
+ public bool InFileScope(string path) => path == filepath;
+
+ public bool InScope(ISymbol symbol) =>
+ SymbolEqualityComparer.Default.Equals(symbol.ContainingAssembly, assembly) ||
+ SymbolEqualityComparer.Default.Equals(symbol, assembly);
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction/CommentProcessing.cs b/csharp/extractor/Semmle.Extraction/CommentProcessing.cs
index f407f5703fe..201ac705136 100644
--- a/csharp/extractor/Semmle.Extraction/CommentProcessing.cs
+++ b/csharp/extractor/Semmle.Extraction/CommentProcessing.cs
@@ -53,7 +53,7 @@ namespace Semmle.Extraction.CommentProcessing
if (l2 == null)
return 1;
- var diff = l1.SourceTree == l2.SourceTree ? 0 : l1.SourceTree.FilePath.CompareTo(l2.SourceTree.FilePath);
+ var diff = l1.SourceTree == l2.SourceTree ? 0 : l1.SourceTree!.FilePath.CompareTo(l2.SourceTree!.FilePath);
if (diff != 0)
return diff;
diff = l1.SourceSpan.Start - l2.SourceSpan.Start;
@@ -384,9 +384,12 @@ namespace Semmle.Extraction.CommentProcessing
/// The line to add.
public void AddCommentLine(ICommentLine line)
{
- Location = !lines.Any() ?
- line.Location :
- Location.Create(line.Location.SourceTree, new TextSpan(Location.SourceSpan.Start, line.Location.SourceSpan.End - Location.SourceSpan.Start));
+ Location = !lines.Any()
+ ? line.Location
+ : Location.Create(
+ line.Location.SourceTree!,
+ new TextSpan(Location.SourceSpan.Start, line.Location.SourceSpan.End - Location.SourceSpan.Start));
+
lines.Add(line);
}
}
diff --git a/csharp/extractor/Semmle.Extraction/Context.cs b/csharp/extractor/Semmle.Extraction/Context.cs
index d6b67bb9af1..7f5ef6b1110 100644
--- a/csharp/extractor/Semmle.Extraction/Context.cs
+++ b/csharp/extractor/Semmle.Extraction/Context.cs
@@ -18,7 +18,7 @@ namespace Semmle.Extraction
///
/// Access various extraction functions, e.g. logger, trap writer.
///
- public IExtractor Extractor { get; }
+ public Extractor Extractor { get; }
///
/// The program database provided by Roslyn.
@@ -51,7 +51,7 @@ namespace Semmle.Extraction
// A recursion guard against writing to the trap file whilst writing an id to the trap file.
private bool writingLabel = false;
- public void DefineLabel(IEntity entity, TextWriter trapFile, IExtractor extractor)
+ public void DefineLabel(IEntity entity, TextWriter trapFile, Extractor extractor)
{
if (writingLabel)
{
@@ -77,7 +77,7 @@ namespace Semmle.Extraction
{
if (idLabelCache.ContainsKey(id))
{
- this.Extractor.Message(new Message("Label collision for " + id, entity.Label.ToString(), Entities.Location.Create(this, entity.ReportingLocation), "", Severity.Warning));
+ this.Extractor.Message(new Message("Label collision for " + id, entity.Label.ToString(), CreateLocation(entity.ReportingLocation), "", Severity.Warning));
}
else
{
@@ -169,7 +169,7 @@ namespace Semmle.Extraction
#endif
private readonly IDictionary objectEntityCache = new Dictionary();
- private readonly IDictionary symbolEntityCache = new Dictionary(10000, SymbolEqualityComparer.IncludeNullability);
+ private readonly IDictionary symbolEntityCache = new Dictionary(10000, SymbolEqualityComparer.Default);
private readonly HashSet
- /// The context.
/// Optional syntax node for error reporting.
/// Optional symbol for error reporting.
/// The action to perform.
- public static void Try(this Context context, SyntaxNode? node, ISymbol? symbol, Action a)
+ public void Try(SyntaxNode? node, ISymbol? symbol, Action a)
{
try
{
@@ -522,26 +508,49 @@ namespace Semmle.Extraction
Message message;
if (node != null)
- message = Message.Create(context, ex.Message, node, ex.StackTrace);
+ {
+ message = Message.Create(this, ex.Message, node, ex.StackTrace);
+ }
else if (symbol != null)
- message = Message.Create(context, ex.Message, symbol, ex.StackTrace);
+ {
+ message = Message.Create(this, ex.Message, symbol, ex.StackTrace);
+ }
else if (ex is InternalError ie)
- message = new Message(ie.Text, ie.EntityText, Entities.Location.Create(context, ie.Location), ex.StackTrace);
+ {
+ message = new Message(ie.Text, ie.EntityText, CreateLocation(ie.Location), ex.StackTrace);
+ }
else
- message = new Message("Uncaught exception", ex.Message, GeneratedLocation.Create(context), ex.StackTrace);
+ {
+ message = new Message($"Uncaught exception. {ex.Message}", null, CreateLocation(), ex.StackTrace);
+ }
- context.ExtractionError(message);
+ ExtractionError(message);
}
}
///
/// Write the given tuple to the trap file.
///
- /// Extractor context.
/// Tuple to write.
- public static void Emit(this Context cx, Tuple tuple)
+ public void Emit(Tuple tuple)
{
- cx.TrapWriter.Emit(tuple);
+ TrapWriter.Emit(tuple);
+ }
+
+ public Entities.Location CreateLocation()
+ {
+ return SourceTree == null
+ ? GeneratedLocation.Create(this)
+ : CreateLocation(Microsoft.CodeAnalysis.Location.Create(SourceTree, Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(0, 0)));
+ }
+
+ public Entities.Location CreateLocation(Microsoft.CodeAnalysis.Location? location)
+ {
+ return (location == null || location.Kind == LocationKind.None)
+ ? GeneratedLocation.Create(this)
+ : location.IsInSource
+ ? NonGeneratedSourceLocation.Create(this, location)
+ : Assembly.Create(this, location);
}
}
}
diff --git a/csharp/extractor/Semmle.Extraction/Entities/Assembly.cs b/csharp/extractor/Semmle.Extraction/Entities/Assembly.cs
index 35055541371..e8b1e2cb46b 100644
--- a/csharp/extractor/Semmle.Extraction/Entities/Assembly.cs
+++ b/csharp/extractor/Semmle.Extraction/Entities/Assembly.cs
@@ -19,7 +19,7 @@ namespace Semmle.Extraction.Entities
}
else
{
- assembly = init.MetadataModule.ContainingAssembly;
+ assembly = init.MetadataModule!.ContainingAssembly;
var identity = assembly.Identity;
var idString = identity.Name + " " + identity.Version;
assemblyPath = cx.Extractor.GetAssemblyFile(idString);
@@ -35,8 +35,7 @@ namespace Semmle.Extraction.Entities
}
}
- public override bool NeedsPopulation =>
- !SymbolEqualityComparer.Default.Equals(assembly, Context.Compilation.Assembly) || !Context.IsGlobalContext;
+ public override bool NeedsPopulation => true;
public override int GetHashCode() =>
symbol == null ? 91187354 : symbol.GetHashCode();
@@ -49,7 +48,7 @@ namespace Semmle.Extraction.Entities
return false;
}
- public static new Location Create(Context cx, Microsoft.CodeAnalysis.Location loc) => AssemblyConstructorFactory.Instance.CreateEntity(cx, loc, loc);
+ public static Location Create(Context cx, Microsoft.CodeAnalysis.Location loc) => AssemblyConstructorFactory.Instance.CreateEntity(cx, loc, loc);
private class AssemblyConstructorFactory : ICachedEntityFactory
{
@@ -59,7 +58,8 @@ namespace Semmle.Extraction.Entities
}
private static readonly object outputAssemblyCacheKey = new object();
- public static Location CreateOutputAssembly(Context cx)
+
+ public static Assembly CreateOutputAssembly(Context cx)
{
if (cx.Extractor.OutputPath == null)
throw new InternalError("Attempting to create the output assembly in standalone extraction mode");
diff --git a/csharp/extractor/Semmle.Extraction/Entities/ExtractionError.cs b/csharp/extractor/Semmle.Extraction/Entities/ExtractionError.cs
index 74bc41fa957..96aabb57f8e 100644
--- a/csharp/extractor/Semmle.Extraction/Entities/ExtractionError.cs
+++ b/csharp/extractor/Semmle.Extraction/Entities/ExtractionError.cs
@@ -14,7 +14,8 @@ namespace Semmle.Extraction.Entities
protected override void Populate(TextWriter trapFile)
{
- trapFile.extractor_messages(this, msg.Severity, "C# extractor", msg.Text, msg.EntityText, msg.Location ?? GeneratedLocation.Create(cx), msg.StackTrace);
+ trapFile.extractor_messages(this, msg.Severity, "C# extractor", msg.Text, msg.EntityText ?? string.Empty,
+ msg.Location ?? cx.CreateLocation(), msg.StackTrace ?? string.Empty);
}
public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.NoLabel;
diff --git a/csharp/extractor/Semmle.Extraction/Entities/File.cs b/csharp/extractor/Semmle.Extraction/Entities/File.cs
index 80bc3ad0533..9ad618512a4 100644
--- a/csharp/extractor/Semmle.Extraction/Entities/File.cs
+++ b/csharp/extractor/Semmle.Extraction/Entities/File.cs
@@ -18,7 +18,7 @@ namespace Semmle.Extraction.Entities
private readonly Lazy transformedPathLazy;
private PathTransformer.ITransformedPath TransformedPath => transformedPathLazy.Value;
- public override bool NeedsPopulation => Context.DefinesFile(originalPath) || originalPath == Context.Extractor.OutputPath;
+ public override bool NeedsPopulation => true;
public override void Populate(TextWriter trapFile)
{
@@ -27,12 +27,11 @@ namespace Semmle.Extraction.Entities
if (TransformedPath.ParentDirectory is PathTransformer.ITransformedPath dir)
trapFile.containerparent(Folder.Create(Context, dir), this);
- var fromSource = TransformedPath.Extension.ToLowerInvariant().Equals("cs");
- if (fromSource)
+ var trees = Context.Compilation.SyntaxTrees.Where(t => t.FilePath == originalPath);
+
+ if (trees.Any())
{
- foreach (var text in Context.Compilation.SyntaxTrees
- .Where(t => t.FilePath == originalPath)
- .Select(tree => tree.GetText()))
+ foreach (var text in trees.Select(tree => tree.GetText()))
{
var rawText = text.ToString() ?? "";
var lineCounts = LineCounter.ComputeLineCounts(rawText);
@@ -43,10 +42,39 @@ namespace Semmle.Extraction.Entities
Context.TrapWriter.Archive(originalPath, TransformedPath, text.Encoding ?? System.Text.Encoding.Default);
}
}
+ else if (IsPossiblyTextFile())
+ {
+ try
+ {
+ System.Text.Encoding encoding;
+ var lineCount = 0;
+ using (var sr = new StreamReader(originalPath, detectEncodingFromByteOrderMarks: true))
+ {
+ while (sr.ReadLine() != null)
+ {
+ lineCount++;
+ }
+ encoding = sr.CurrentEncoding;
+ }
+
+ trapFile.numlines(this, new LineCounts() { Total = lineCount, Code = 0, Comment = 0 });
+ Context.TrapWriter.Archive(originalPath, TransformedPath, encoding ?? System.Text.Encoding.Default);
+ }
+ catch (Exception exc)
+ {
+ Context.ExtractionError($"Couldn't read file: {originalPath}. {exc.Message}", null, null, exc.StackTrace);
+ }
+ }
trapFile.file_extraction_mode(this, Context.Extractor.Standalone ? 1 : 0);
}
+ private bool IsPossiblyTextFile()
+ {
+ var extension = TransformedPath.Extension.ToLowerInvariant();
+ return !extension.Equals("dll") && !extension.Equals("exe");
+ }
+
public override void WriteId(System.IO.TextWriter trapFile)
{
trapFile.Write(TransformedPath.DatabaseId);
diff --git a/csharp/extractor/Semmle.Extraction/Entities/Location.cs b/csharp/extractor/Semmle.Extraction/Entities/Location.cs
index 354caaa65d9..cdd3c9a50dc 100644
--- a/csharp/extractor/Semmle.Extraction/Entities/Location.cs
+++ b/csharp/extractor/Semmle.Extraction/Entities/Location.cs
@@ -1,4 +1,6 @@
+using Microsoft.CodeAnalysis.Text;
+
namespace Semmle.Extraction.Entities
{
public abstract class Location : CachedEntity
@@ -6,27 +8,8 @@ namespace Semmle.Extraction.Entities
protected Location(Context cx, Microsoft.CodeAnalysis.Location? init)
: base(cx, init) { }
- public static Location Create(Context cx, Microsoft.CodeAnalysis.Location? loc) =>
- (loc == null || loc.Kind == Microsoft.CodeAnalysis.LocationKind.None)
- ? GeneratedLocation.Create(cx)
- : loc.IsInSource
- ? NonGeneratedSourceLocation.Create(cx, loc)
- : Assembly.Create(cx, loc);
-
public override Microsoft.CodeAnalysis.Location? ReportingLocation => symbol;
public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel;
}
-
- public static class LocationExtensions
- {
- ///
- /// Creates a Location entity.
- ///
- /// The extraction context.
- /// The CodeAnalysis location.
- /// The Location entity.
- public static Location Create(this Context cx, Microsoft.CodeAnalysis.Location? location) =>
- Location.Create(cx, location);
- }
}
diff --git a/csharp/extractor/Semmle.Extraction/Entities/SourceLocation.cs b/csharp/extractor/Semmle.Extraction/Entities/SourceLocation.cs
index 2697049dce2..ae69f95449a 100644
--- a/csharp/extractor/Semmle.Extraction/Entities/SourceLocation.cs
+++ b/csharp/extractor/Semmle.Extraction/Entities/SourceLocation.cs
@@ -15,21 +15,28 @@ namespace Semmle.Extraction.Entities
public class NonGeneratedSourceLocation : SourceLocation
{
- protected NonGeneratedSourceLocation(Context cx, Microsoft.CodeAnalysis.Location? init)
+ protected NonGeneratedSourceLocation(Context cx, Microsoft.CodeAnalysis.Location init)
: base(cx, init)
{
- if (init is null)
- throw new ArgumentException("Location may not be null", nameof(init));
Position = init.GetLineSpan();
FileEntity = File.Create(Context, Position.Path);
}
- public static new Location Create(Context cx, Microsoft.CodeAnalysis.Location loc) => SourceLocationFactory.Instance.CreateEntity(cx, loc, loc);
+ public static Location Create(Context cx, Microsoft.CodeAnalysis.Location loc) => SourceLocationFactory.Instance.CreateEntity(cx, loc, loc);
public override void Populate(TextWriter trapFile)
{
- trapFile.locations_default(this, FileEntity, Position.Span.Start.Line + 1, Position.Span.Start.Character + 1,
- Position.Span.End.Line + 1, Position.Span.End.Character);
+ trapFile.locations_default(this, FileEntity,
+ Position.Span.Start.Line + 1, Position.Span.Start.Character + 1,
+ Position.Span.End.Line + 1, Position.Span.End.Character);
+
+ var mapped = symbol!.GetMappedLineSpan();
+ if (mapped.HasMappedPath && mapped.IsValid)
+ {
+ var mappedLoc = Create(Context, Microsoft.CodeAnalysis.Location.Create(mapped.Path, default, mapped.Span));
+
+ trapFile.locations_mapped(this, mappedLoc);
+ }
}
public FileLinePositionSpan Position
diff --git a/csharp/extractor/Semmle.Extraction/Entity.cs b/csharp/extractor/Semmle.Extraction/Entity.cs
index 5479c93fee9..8839b6ede2a 100644
--- a/csharp/extractor/Semmle.Extraction/Entity.cs
+++ b/csharp/extractor/Semmle.Extraction/Entity.cs
@@ -137,7 +137,7 @@ namespace Semmle.Extraction
where TSymbol : ISymbol
where TEntity : ICachedEntity => cx.CreateEntityFromSymbol(factory, init);
- public static void DefineLabel(this IEntity entity, TextWriter trapFile, IExtractor extractor)
+ public static void DefineLabel(this IEntity entity, TextWriter trapFile, Extractor extractor)
{
trapFile.WriteLabel(entity);
trapFile.Write("=");
diff --git a/csharp/extractor/Semmle.Extraction/ExtractionScope.cs b/csharp/extractor/Semmle.Extraction/ExtractionScope.cs
deleted file mode 100644
index 7f70284c278..00000000000
--- a/csharp/extractor/Semmle.Extraction/ExtractionScope.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-using Microsoft.CodeAnalysis;
-using System.Linq;
-
-namespace Semmle.Extraction
-{
- ///
- /// Defines which entities belong in the trap file
- /// for the currently extracted entity. This is used to ensure that
- /// trap files do not contain redundant information. Generally a symbol
- /// should have an affinity with exactly one trap file, except for constructed
- /// symbols.
- ///
- public interface IExtractionScope
- {
- ///
- /// Whether the given symbol belongs in the trap file.
- ///
- /// The symbol to populate.
- bool InScope(ISymbol symbol);
-
- ///
- /// Whether the given file belongs in the trap file.
- ///
- /// The path to populate.
- bool InFileScope(string path);
-
- bool IsGlobalScope { get; }
-
- bool FromSource { get; }
- }
-
- ///
- /// The scope of symbols in an assembly.
- ///
- public class AssemblyScope : IExtractionScope
- {
- private readonly IAssemblySymbol assembly;
- private readonly string filepath;
-
- public AssemblyScope(IAssemblySymbol symbol, string path, bool isOutput)
- {
- assembly = symbol;
- filepath = path;
- IsGlobalScope = isOutput;
- }
-
- public bool IsGlobalScope { get; }
-
- public bool InFileScope(string path) => path == filepath;
-
- public bool InScope(ISymbol symbol) =>
- SymbolEqualityComparer.Default.Equals(symbol.ContainingAssembly, assembly) ||
- SymbolEqualityComparer.Default.Equals(symbol, assembly);
-
- public bool FromSource => false;
- }
-
- ///
- /// The scope of symbols in a source file.
- ///
- public class SourceScope : IExtractionScope
- {
- private readonly SyntaxTree sourceTree;
-
- public SourceScope(SyntaxTree tree)
- {
- sourceTree = tree;
- }
-
- public bool IsGlobalScope => false;
-
- public bool InFileScope(string path) => path == sourceTree.FilePath;
-
- public bool InScope(ISymbol symbol) => symbol.Locations.Any(loc => loc.SourceTree == sourceTree);
-
- public bool FromSource => true;
- }
-}
diff --git a/csharp/extractor/Semmle.Extraction/Extractor.cs b/csharp/extractor/Semmle.Extraction/Extractor.cs
index 1931aeeeb33..6e031fbca52 100644
--- a/csharp/extractor/Semmle.Extraction/Extractor.cs
+++ b/csharp/extractor/Semmle.Extraction/Extractor.cs
@@ -1,105 +1,13 @@
-using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Semmle.Util.Logging;
namespace Semmle.Extraction
{
- ///
- /// Provides common extraction functions for use during extraction.
- ///
- ///
- ///
- /// This is held in the passed to each entity.
- ///
- public interface IExtractor
- {
- ///
- /// Logs a message (to csharp.log).
- /// Increases the error count if msg.severity is Error.
- ///
- /// The message to log.
- void Message(Message msg);
-
- ///
- /// Cache assembly names.
- ///
- /// The assembly name.
- /// The file defining the assembly.
- void SetAssemblyFile(string assembly, string file);
-
- ///
- /// Maps assembly names to file names.
- ///
- /// The assembly name
- /// The file defining the assmebly.
- string GetAssemblyFile(string assembly);
-
- ///
- /// How many errors encountered during extraction?
- ///
- int Errors { get; }
-
- ///
- /// The extraction is standalone - meaning there will be a lot of errors.
- ///
- bool Standalone { get; }
-
- ///
- /// Record a new error type.
- ///
- /// The display name of the type, qualified where possible.
- /// If the missing type was referenced from a source file.
- void MissingType(string fqn, bool fromSource);
-
- ///
- /// Record an unresolved `using namespace` directive.
- ///
- /// The full name of the namespace.
- /// If the missing namespace was referenced from a source file.
- void MissingNamespace(string fqn, bool fromSource);
-
- ///
- /// The list of missing types.
- ///
- IEnumerable MissingTypes { get; }
-
- ///
- /// The list of missing namespaces.
- ///
- IEnumerable MissingNamespaces { get; }
-
- ///
- /// The full path of the generated DLL/EXE.
- /// null if not specified.
- ///
- string OutputPath { get; }
-
- ///
- /// The object used for logging.
- ///
- ILogger Logger { get; }
-
- ///
- /// The path transformer to apply.
- ///
- PathTransformer PathTransformer { get; }
-
- ///
- /// Creates a new context.
- ///
- /// The C# compilation.
- /// The trap writer.
- /// The extraction scope (what to include in this trap file).
- /// Whether to add assembly prefixes to TRAP labels.
- ///
- Context CreateContext(Compilation c, TrapWriter trapWriter, IExtractionScope scope, bool addAssemblyTrapPrefix);
- }
-
///
/// Implementation of the main extractor state.
///
- public class Extractor : IExtractor
+ public class Extractor
{
public bool Standalone
{
@@ -190,11 +98,6 @@ namespace Semmle.Extraction
}
}
- public Context CreateContext(Compilation c, TrapWriter trapWriter, IExtractionScope scope, bool addAssemblyTrapPrefix)
- {
- return new Context(this, c, trapWriter, scope, addAssemblyTrapPrefix);
- }
-
public IEnumerable MissingTypes => missingTypes;
public IEnumerable MissingNamespaces => missingNamespaces;
diff --git a/csharp/extractor/Semmle.Extraction/IExtractionScope.cs b/csharp/extractor/Semmle.Extraction/IExtractionScope.cs
new file mode 100644
index 00000000000..f12823b3f96
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction/IExtractionScope.cs
@@ -0,0 +1,26 @@
+using Microsoft.CodeAnalysis;
+
+namespace Semmle.Extraction
+{
+ ///
+ /// Defines which entities belong in the trap file
+ /// for the currently extracted entity. This is used to ensure that
+ /// trap files do not contain redundant information. Generally a symbol
+ /// should have an affinity with exactly one trap file, except for constructed
+ /// symbols.
+ ///
+ public interface IExtractionScope
+ {
+ ///
+ /// Whether the given symbol belongs in the trap file.
+ ///
+ /// The symbol to populate.
+ bool InScope(ISymbol symbol);
+
+ ///
+ /// Whether the given file belongs in the trap file.
+ ///
+ /// The path to populate.
+ bool InFileScope(string path);
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction/Message.cs b/csharp/extractor/Semmle.Extraction/Message.cs
index fbe91fb95e6..a100a69a285 100644
--- a/csharp/extractor/Semmle.Extraction/Message.cs
+++ b/csharp/extractor/Semmle.Extraction/Message.cs
@@ -13,27 +13,27 @@ namespace Semmle.Extraction
{
public Severity Severity { get; }
public string Text { get; }
- public string StackTrace { get; }
- public string EntityText { get; }
+ public string? StackTrace { get; }
+ public string? EntityText { get; }
public Entities.Location? Location { get; }
- public Message(string text, string entityText, Entities.Location? location, string? stackTrace = null, Severity severity = Severity.Error)
+ public Message(string text, string? entityText, Entities.Location? location, string? stackTrace = null, Severity severity = Severity.Error)
{
Severity = severity;
Text = text;
- StackTrace = stackTrace ?? "";
+ StackTrace = stackTrace;
EntityText = entityText;
Location = location;
}
public static Message Create(Context cx, string text, ISymbol symbol, string? stackTrace = null, Severity severity = Severity.Error)
{
- return new Message(text, symbol.ToString() ?? "", Entities.Location.Create(cx, symbol.Locations.FirstOrDefault()), stackTrace, severity);
+ return new Message(text, symbol.ToString(), cx.CreateLocation(symbol.Locations.FirstOrDefault()), stackTrace, severity);
}
public static Message Create(Context cx, string text, SyntaxNode node, string? stackTrace = null, Severity severity = Severity.Error)
{
- return new Message(text, node.ToString(), Entities.Location.Create(cx, node.GetLocation()), stackTrace, severity);
+ return new Message(text, node.ToString(), cx.CreateLocation(node.GetLocation()), stackTrace, severity);
}
public override string ToString() => Text;
diff --git a/csharp/extractor/Semmle.Extraction/SourceScope.cs b/csharp/extractor/Semmle.Extraction/SourceScope.cs
new file mode 100644
index 00000000000..fba816f6363
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction/SourceScope.cs
@@ -0,0 +1,23 @@
+using Microsoft.CodeAnalysis;
+using System.Linq;
+
+namespace Semmle.Extraction
+{
+
+ ///
+ /// The scope of symbols in a source file.
+ ///
+ public class SourceScope : IExtractionScope
+ {
+ public SyntaxTree SourceTree { get; }
+
+ public SourceScope(SyntaxTree tree)
+ {
+ SourceTree = tree;
+ }
+
+ public bool InFileScope(string path) => path == SourceTree.FilePath;
+
+ public bool InScope(ISymbol symbol) => symbol.Locations.Any(loc => loc.SourceTree == SourceTree);
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction/Symbol.cs b/csharp/extractor/Semmle.Extraction/Symbol.cs
index 4366cff7f06..2585ffd037f 100644
--- a/csharp/extractor/Semmle.Extraction/Symbol.cs
+++ b/csharp/extractor/Semmle.Extraction/Symbol.cs
@@ -79,7 +79,7 @@ namespace Semmle.Extraction
/// A class used to wrap an `ISymbol` object, which uses `SymbolEqualityComparer.Default`
/// for comparison.
///
- public sealed class SymbolEqualityWrapper
+ public struct SymbolEqualityWrapper
{
public ISymbol Symbol { get; }
@@ -88,6 +88,6 @@ namespace Semmle.Extraction
public override bool Equals(object? other) =>
other is SymbolEqualityWrapper sew && SymbolEqualityComparer.Default.Equals(Symbol, sew.Symbol);
- public override int GetHashCode() => 11 * Symbol.GetHashCode();
+ public override int GetHashCode() => 11 * SymbolEqualityComparer.Default.GetHashCode(Symbol);
}
}
diff --git a/csharp/extractor/Semmle.Extraction/Tuples.cs b/csharp/extractor/Semmle.Extraction/Tuples.cs
index 0a71ba6b3d9..8e9016cb6b6 100644
--- a/csharp/extractor/Semmle.Extraction/Tuples.cs
+++ b/csharp/extractor/Semmle.Extraction/Tuples.cs
@@ -43,6 +43,11 @@ namespace Semmle.Extraction
trapFile.WriteTuple("locations_default", label, file, startLine, startCol, endLine, endCol);
}
+ public static void locations_mapped(this System.IO.TextWriter trapFile, SourceLocation l1, Location l2)
+ {
+ trapFile.WriteTuple("locations_mapped", l1, l2);
+ }
+
public static void numlines(this System.IO.TextWriter trapFile, IEntity label, LineCounts lineCounts)
{
trapFile.WriteTuple("numlines", label, lineCounts.Total, lineCounts.Code, lineCounts.Comment);
diff --git a/csharp/extractor/Semmle.Util/StringExtensions.cs b/csharp/extractor/Semmle.Util/StringExtensions.cs
new file mode 100644
index 00000000000..d5d6c8cf27d
--- /dev/null
+++ b/csharp/extractor/Semmle.Util/StringExtensions.cs
@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Semmle.Util
+{
+ public static class StringExtensions
+ {
+ public static (string, string) Split(this string self, int index0)
+ {
+ var split = self.Split(new[] { index0 });
+ return (split[0], split[1]);
+ }
+
+ public static (string, string, string) Split(this string self, int index0, int index1)
+ {
+ var split = self.Split(new[] { index0, index1 });
+ return (split[0], split[1], split[2]);
+ }
+
+ public static (string, string, string, string) Split(this string self, int index0, int index1, int index2)
+ {
+ var split = self.Split(new[] { index0, index1, index2 });
+ return (split[0], split[1], split[2], split[4]);
+ }
+
+ private static List Split(this string self, params int[] indices)
+ {
+ var ret = new List();
+ var previousIndex = 0;
+ foreach (var index in indices.OrderBy(i => i))
+ {
+ ret.Add(self.Substring(previousIndex, index - previousIndex));
+ previousIndex = index;
+ }
+
+ ret.Add(self.Substring(previousIndex));
+
+ return ret;
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Util/Win32.cs b/csharp/extractor/Semmle.Util/Win32.cs
index fe198b9612a..046a0957e87 100644
--- a/csharp/extractor/Semmle.Util/Win32.cs
+++ b/csharp/extractor/Semmle.Util/Win32.cs
@@ -8,7 +8,7 @@ namespace Semmle.Util
///
/// Holder for various Win32 functions.
///
- public class Win32
+ public static class Win32
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int GetFinalPathNameByHandle( // lgtm[cs/unmanaged-code]
diff --git a/csharp/ql/src/API Abuse/FormatInvalid.qhelp b/csharp/ql/src/API Abuse/FormatInvalid.qhelp
index e4ef9b0ca2c..868c43ffc14 100644
--- a/csharp/ql/src/API Abuse/FormatInvalid.qhelp
+++ b/csharp/ql/src/API Abuse/FormatInvalid.qhelp
@@ -4,28 +4,46 @@
-The format string supplied to formatting methods (such as string.Format())
-must be formatted correctly, otherwise the exception System.FormatException
-will be thrown.
+When using string formatting methods (such as string.Format()), the following
+should be taken into account:
+
+
+ The formatting string must be formatted correctly, otherwise the exception
+ System.FormatException will be thrown.
+
+
+ All passed arguments should be used by the formatting string, otherwise such
+ arguments will be ignored.
+
+
+ Missing arguments will result in a System.FormatException exception
+ being thrown.
+
+
-
-Change the format string so that it is correctly formatted. Ensure that each
-format item adheres to the syntax:
-
-
-
-
{index[,alignment][:formatString]}
-
-
-
-When literals { or } are required, replace them with {{ and
-}}, respectively, or supply them as arguments.
-
+
+
+ Change the format string so that it is correctly formatted. Ensure that each
+ format item adheres to the syntax:
+
+
{index[,alignment][:formatString]}
+
+ When literals { or } are required, replace them with {{ and
+ }}, respectively, or supply them as arguments.
+
+
+ Change the format string to use the highlighted argument, or remove the unnecessary argument.
+
+
+ Supply the correct number of arguments to the format method, or change the format string
+ to use the correct arguments.
+
+
@@ -40,8 +58,39 @@ literals are not properly escaped.
In the revised example, the literals are properly escaped.
-
+
+
+
+Here are three examples where the format string does not use all the arguments.
+
+
+
+
On line 7, the second argument (ex.HResult) is not logged.
+
On line 8, the first argument (ex) is not logged but the second
+ argument (ex.HResult) is logged twice.
+
On line 9, a C-style format string is used, which is incorrect, and neither
+ argument will be logged.
+
+
+
+
+
+Here are two examples where the call to String.Format() is missing arguments.
+
+
+
+
On line 7, the second argument (last) is not supplied.
+
On line 8, the format items are numbered {1} and {2},
+ instead of {0} and {1} as they should be.
+
+
+
+In the revised example, both arguments are supplied.
+
diff --git a/csharp/ql/src/API Abuse/FormatInvalid.ql b/csharp/ql/src/API Abuse/FormatInvalid.ql
index 979af415936..ef16359de09 100644
--- a/csharp/ql/src/API Abuse/FormatInvalid.ql
+++ b/csharp/ql/src/API Abuse/FormatInvalid.ql
@@ -1,18 +1,86 @@
/**
- * @name Invalid format string
- * @description Using a format string with an incorrect format causes a 'System.FormatException'.
+ * @name Invalid string formatting
+ * @description Calling 'string.Format()' with either an invalid format string or incorrect
+ * number of arguments may result in dropped arguments or a 'System.FormatException'.
* @kind path-problem
* @problem.severity error
* @precision high
- * @id cs/invalid-format-string
+ * @id cs/invalid-string-formatting
* @tags reliability
* maintainability
*/
import csharp
import semmle.code.csharp.frameworks.Format
-import FormatFlow
+import DataFlow::PathGraph
-from FormatCall s, InvalidFormatString src, PathNode source, PathNode sink
-where hasFlowPath(src, source, s, sink)
-select src, source, sink, "Invalid format string used in $@ formatting call.", s, "this"
+private class FormatConfiguration extends DataFlow::Configuration {
+ FormatConfiguration() { this = "format" }
+
+ override predicate isSource(DataFlow::Node n) { n.asExpr() instanceof StringLiteral }
+
+ override predicate isSink(DataFlow::Node n) {
+ exists(FormatCall c | n.asExpr() = c.getFormatExpr())
+ }
+}
+
+private predicate invalidFormatString(
+ InvalidFormatString src, DataFlow::PathNode source, DataFlow::PathNode sink, string msg,
+ FormatCall call, string callString
+) {
+ source.getNode().asExpr() = src and
+ sink.getNode().asExpr() = call.getFormatExpr() and
+ any(FormatConfiguration conf).hasFlowPath(source, sink) and
+ call.hasInsertions() and
+ msg = "Invalid format string used in $@ formatting call." and
+ callString = "this"
+}
+
+private predicate unusedArgument(
+ FormatCall call, DataFlow::PathNode source, DataFlow::PathNode sink, string msg,
+ ValidFormatString src, string srcString, Expr unusedExpr, string unusedString
+) {
+ exists(int unused |
+ source.getNode().asExpr() = src and
+ sink.getNode().asExpr() = call.getFormatExpr() and
+ any(FormatConfiguration conf).hasFlowPath(source, sink) and
+ unused = call.getASuppliedArgument() and
+ not unused = src.getAnInsert() and
+ not src.getValue() = "" and
+ msg = "The $@ ignores $@." and
+ srcString = "format string" and
+ unusedExpr = call.getSuppliedExpr(unused) and
+ unusedString = "this supplied value"
+ )
+}
+
+private predicate missingArgument(
+ FormatCall call, DataFlow::PathNode source, DataFlow::PathNode sink, string msg,
+ ValidFormatString src, string srcString
+) {
+ exists(int used, int supplied |
+ source.getNode().asExpr() = src and
+ sink.getNode().asExpr() = call.getFormatExpr() and
+ any(FormatConfiguration conf).hasFlowPath(source, sink) and
+ used = src.getAnInsert() and
+ supplied = call.getSuppliedArguments() and
+ used >= supplied and
+ msg = "Argument '{" + used + "}' has not been supplied to $@ format string." and
+ srcString = "this"
+ )
+}
+
+from
+ Element alert, DataFlow::PathNode source, DataFlow::PathNode sink, string msg, Element extra1,
+ string extra1String, Element extra2, string extra2String
+where
+ invalidFormatString(alert, source, sink, msg, extra1, extra1String) and
+ extra2 = extra1 and
+ extra2String = extra1String
+ or
+ unusedArgument(alert, source, sink, msg, extra1, extra1String, extra2, extra2String)
+ or
+ missingArgument(alert, source, sink, msg, extra1, extra1String) and
+ extra2 = extra1 and
+ extra2String = extra1String
+select alert, source, sink, msg, extra1, extra1String, extra2, extra2String
diff --git a/csharp/ql/src/API Abuse/FormatInvalidBad.cs b/csharp/ql/src/API Abuse/FormatInvalidBad.cs
index 7f7faad5bdc..ec3df72655d 100644
--- a/csharp/ql/src/API Abuse/FormatInvalidBad.cs
+++ b/csharp/ql/src/API Abuse/FormatInvalidBad.cs
@@ -1,9 +1,9 @@
using System;
-class Bad
+class Bad1
{
string GenerateEmptyClass(string c)
{
- return string.Format("class {0} { }");
+ return string.Format("class {0} { }", "C");
}
}
diff --git a/csharp/ql/src/API Abuse/FormatInvalidGood.cs b/csharp/ql/src/API Abuse/FormatInvalidGood.cs
index 2060501541d..efc62a07391 100644
--- a/csharp/ql/src/API Abuse/FormatInvalidGood.cs
+++ b/csharp/ql/src/API Abuse/FormatInvalidGood.cs
@@ -1,9 +1,9 @@
using System;
-class Good
+class Good1
{
string GenerateEmptyClass(string c)
{
- return string.Format("class {0} {{ }}");
+ return string.Format("class {0} {{ }}", "C");
}
}
diff --git a/csharp/ql/src/API Abuse/FormatMissingArgument.qhelp b/csharp/ql/src/API Abuse/FormatMissingArgument.qhelp
deleted file mode 100644
index ddab9a65a04..00000000000
--- a/csharp/ql/src/API Abuse/FormatMissingArgument.qhelp
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-Formatting methods (such as String.Format()) that are missing arguments will
-throw the exception System.FormatException. This is caused by the format
-string not matching the actual arguments supplied or an incorrect format string.
-
-
-
-
-
-Supply the correct number of arguments to the format method, or change the format string
-to use the correct arguments.
-
-
-
-
-
-Here are two examples where the call to String.Format() is missing arguments.
-
-
-
-
On line 5, the second argument (last) is not supplied.
-
On line 6, the format items are numbered {1} and {2},
- instead of {0} and {1} as they should be.
-
-
-
-In the revised example, both arguments are supplied.
-
-
-
\ No newline at end of file
diff --git a/csharp/ql/src/API Abuse/FormatMissingArgument.ql b/csharp/ql/src/API Abuse/FormatMissingArgument.ql
deleted file mode 100644
index 58385d4a295..00000000000
--- a/csharp/ql/src/API Abuse/FormatMissingArgument.ql
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * @name Missing format argument
- * @description Supplying too few arguments to a format string causes a 'System.FormatException'.
- * @kind path-problem
- * @problem.severity error
- * @precision high
- * @id cs/format-argument-missing
- * @tags reliability
- * maintainability
- */
-
-import csharp
-import semmle.code.csharp.frameworks.Format
-import FormatFlow
-
-from
- FormatCall format, ValidFormatString src, int used, int supplied, PathNode source, PathNode sink
-where
- hasFlowPath(src, source, format, sink) and
- used = src.getAnInsert() and
- supplied = format.getSuppliedArguments() and
- used >= supplied
-select format, source, sink, "Argument '{" + used + "}' has not been supplied to $@ format string.",
- src, "this"
diff --git a/csharp/ql/src/API Abuse/FormatMissingArgumentBad.cs b/csharp/ql/src/API Abuse/FormatMissingArgumentBad.cs
index 0fc5b21f1ae..a66eea4cf32 100644
--- a/csharp/ql/src/API Abuse/FormatMissingArgumentBad.cs
+++ b/csharp/ql/src/API Abuse/FormatMissingArgumentBad.cs
@@ -1,6 +1,6 @@
using System;
-class Bad
+class Bad3
{
void Hello(string first, string last)
{
diff --git a/csharp/ql/src/API Abuse/FormatMissingArgumentGood.cs b/csharp/ql/src/API Abuse/FormatMissingArgumentGood.cs
index 36fbe2de8a7..817da4d8336 100644
--- a/csharp/ql/src/API Abuse/FormatMissingArgumentGood.cs
+++ b/csharp/ql/src/API Abuse/FormatMissingArgumentGood.cs
@@ -1,6 +1,6 @@
using System;
-class Good
+class Good3
{
void Hello(string first, string last)
{
diff --git a/csharp/ql/src/API Abuse/FormatUnusedArgument.qhelp b/csharp/ql/src/API Abuse/FormatUnusedArgument.qhelp
deleted file mode 100644
index fc280f1c3b3..00000000000
--- a/csharp/ql/src/API Abuse/FormatUnusedArgument.qhelp
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-Arguments which are passed to formatting methods (such as String.Format())
-but are not used, are either unnecessary or mean that the format string is incorrect. The result
-is that the argument will be ignored, which may not be the intended behavior.
-
-
-
-
-
-Change the format string to use the highlighted argument, or remove the unnecessary argument.
-
-
-
-
-
-Here are three examples where the format string does not use all the arguments.
-
-
-
-
On line 5, the second argument (ex.HResult) is not logged.
-
On line 6, the first argument (ex) is not logged but the second
- argument (ex.HResult) is logged twice.
-
On line 4, a C-style format string is used, which is incorrect, and neither
- argument will be logged.