mirror of
https://github.com/github/codeql.git
synced 2026-05-26 17:11:24 +02:00
Compare commits
2 Commits
henrymerce
...
rb/generat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab49776285 | ||
|
|
e73ff4a232 |
37
.github/workflows/csharp-qltest.yml
vendored
37
.github/workflows/csharp-qltest.yml
vendored
@@ -53,6 +53,7 @@ jobs:
|
||||
slice: ["1/2", "2/2"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/fetch-codeql
|
||||
- uses: ./csharp/actions/create-extractor-pack
|
||||
- name: Cache compilation cache
|
||||
id: query-cache
|
||||
@@ -61,14 +62,16 @@ jobs:
|
||||
key: csharp-qltest-${{ matrix.slice }}
|
||||
- name: Run QL tests
|
||||
run: |
|
||||
codeql test run --threads=0 --ram 50000 --slice ${{ matrix.slice }} --search-path extractor-pack --check-databases --check-undefined-labels --check-repeated-labels --check-redefined-labels --consistency-queries ql/consistency-queries ql/test --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}"
|
||||
CODEQL_PATH=$(gh codeql version --format=json | jq -r .unpackedLocation)
|
||||
# The legacy ASP extractor is not in this repo, so take the one from the nightly build
|
||||
mv "$CODEQL_PATH/csharp/tools/extractor-asp.jar" "${{ github.workspace }}/csharp/extractor-pack/tools"
|
||||
# Safe guard against using the bundled extractor
|
||||
rm -rf "$CODEQL_PATH/csharp"
|
||||
codeql test run --threads=0 --ram 50000 --slice ${{ matrix.slice }} --search-path "${{ github.workspace }}/csharp/extractor-pack" --check-databases --check-undefined-labels --check-repeated-labels --check-redefined-labels --consistency-queries ql/consistency-queries ql/test --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
unit-tests:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-2019]
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup dotnet
|
||||
@@ -77,25 +80,7 @@ jobs:
|
||||
dotnet-version: 7.0.102
|
||||
- name: Extractor unit tests
|
||||
run: |
|
||||
dotnet test -p:RuntimeFrameworkVersion=7.0.2 extractor/Semmle.Util.Tests
|
||||
dotnet test -p:RuntimeFrameworkVersion=7.0.2 extractor/Semmle.Extraction.Tests
|
||||
dotnet test -p:RuntimeFrameworkVersion=7.0.2 autobuilder/Semmle.Autobuild.CSharp.Tests
|
||||
dotnet test -p:RuntimeFrameworkVersion=7.0.2 "${{ github.workspace }}/csharp/extractor/Semmle.Util.Tests"
|
||||
dotnet test -p:RuntimeFrameworkVersion=7.0.2 "${{ github.workspace }}/csharp/extractor/Semmle.Extraction.Tests"
|
||||
dotnet test -p:RuntimeFrameworkVersion=7.0.2 "${{ github.workspace }}/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests"
|
||||
dotnet test -p:RuntimeFrameworkVersion=7.0.2 "${{ github.workspace }}/cpp/autobuilder/Semmle.Autobuild.Cpp.Tests"
|
||||
shell: bash
|
||||
stubgentest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./csharp/actions/create-extractor-pack
|
||||
- name: Run stub generator tests
|
||||
run: |
|
||||
# Generate (Asp)NetCore stubs
|
||||
STUBS_PATH=stubs_output
|
||||
python3 ql/src/Stubs/make_stubs_nuget.py webapp Swashbuckle.AspNetCore.Swagger latest "$STUBS_PATH"
|
||||
rm -rf ql/test/resources/stubs/_frameworks
|
||||
# Update existing stubs in the repo with the freshly generated ones
|
||||
mv "$STUBS_PATH/output/stubs/_frameworks" ql/test/resources/stubs/
|
||||
git status
|
||||
codeql test run --threads=0 --search-path extractor-pack --check-databases --check-undefined-labels --check-repeated-labels --check-redefined-labels --consistency-queries ql/consistency-queries -- ql/test/library-tests/dataflow/flowsources/aspremote
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
65
.github/workflows/js-ml-tests.yml
vendored
Normal file
65
.github/workflows/js-ml-tests.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: JS ML-powered queries tests
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "javascript/ql/experimental/adaptivethreatmodeling/**"
|
||||
- .github/workflows/js-ml-tests.yml
|
||||
- .github/actions/fetch-codeql/action.yml
|
||||
- codeql-workspace.yml
|
||||
branches:
|
||||
- main
|
||||
- "rc/*"
|
||||
pull_request:
|
||||
paths:
|
||||
- "javascript/ql/experimental/adaptivethreatmodeling/**"
|
||||
- .github/workflows/js-ml-tests.yml
|
||||
- .github/actions/fetch-codeql/action.yml
|
||||
- codeql-workspace.yml
|
||||
workflow_dispatch:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: javascript/ql/experimental/adaptivethreatmodeling
|
||||
|
||||
jobs:
|
||||
qltest:
|
||||
name: Test QL
|
||||
runs-on: ubuntu-latest-xl
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: ./.github/actions/fetch-codeql
|
||||
|
||||
- name: Install pack dependencies
|
||||
run: |
|
||||
for pack in modelbuilding src test; do
|
||||
codeql pack install --mode verify -- "${pack}"
|
||||
done
|
||||
|
||||
- name: Cache compilation cache
|
||||
id: query-cache
|
||||
uses: ./.github/actions/cache-query-compilation
|
||||
with:
|
||||
key: js-ml-test
|
||||
|
||||
- name: Check QL compilation
|
||||
run: |
|
||||
codeql query compile \
|
||||
--check-only \
|
||||
--ram 50000 \
|
||||
--additional-packs "${{ github.workspace }}" \
|
||||
--threads=0 \
|
||||
--compilation-cache "${{ steps.query-cache.outputs.cache-dir }}" \
|
||||
-- \
|
||||
lib modelbuilding src
|
||||
|
||||
- name: Run QL tests
|
||||
run: |
|
||||
codeql test run \
|
||||
--threads=0 \
|
||||
--ram 50000 \
|
||||
--additional-packs "${{ github.workspace }}" \
|
||||
--compilation-cache "${{ steps.query-cache.outputs.cache-dir }}" \
|
||||
-- \
|
||||
test
|
||||
@@ -1,22 +1,3 @@
|
||||
## 0.9.3
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.9.2
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* `getAllocatorCall` on `DeleteExpr` and `DeleteArrayExpr` has been deprecated. `getDeallocatorCall` should be used instead.
|
||||
|
||||
### New Features
|
||||
|
||||
* Added `DeleteOrDeleteArrayExpr` as a super type of `DeleteExpr` and `DeleteArrayExpr`
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* `delete` and `delete[]` are now modeled as calls to the relevant `operator delete` in the IR. In the case of a dynamic delete call a new instruction `VirtualDeleteFunctionAddress` is used to represent a function that dispatches to the correct delete implementation.
|
||||
* Only the 2 level indirection of `argv` (corresponding to `**argv`) is consided for `FlowSource`.
|
||||
|
||||
## 0.9.1
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Only the 2 level indirection of `argv` (corresponding to `**argv`) is consided for `FlowSource`.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* Added `DeleteOrDeleteArrayExpr` as a super type of `DeleteExpr` and `DeleteArrayExpr`
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
* `getAllocatorCall` on `DeleteExpr` and `DeleteArrayExpr` has been deprecated. `getDeallocatorCall` should be used instead.
|
||||
4
cpp/ql/lib/change-notes/2023-08-29-delete-ir.md
Normal file
4
cpp/ql/lib/change-notes/2023-08-29-delete-ir.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* `delete` and `delete[]` are now modeled as calls to the relevant `operator delete` in the IR. In the case of a dynamic delete call a new instruction `VirtualDeleteFunctionAddress` is used to represent a function that dispatches to the correct delete implementation.
|
||||
@@ -1,14 +0,0 @@
|
||||
## 0.9.2
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* `getAllocatorCall` on `DeleteExpr` and `DeleteArrayExpr` has been deprecated. `getDeallocatorCall` should be used instead.
|
||||
|
||||
### New Features
|
||||
|
||||
* Added `DeleteOrDeleteArrayExpr` as a super type of `DeleteExpr` and `DeleteArrayExpr`
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* `delete` and `delete[]` are now modeled as calls to the relevant `operator delete` in the IR. In the case of a dynamic delete call a new instruction `VirtualDeleteFunctionAddress` is used to represent a function that dispatches to the correct delete implementation.
|
||||
* Only the 2 level indirection of `argv` (corresponding to `**argv`) is consided for `FlowSource`.
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.9.3
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.9.3
|
||||
lastReleaseVersion: 0.9.1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/cpp-all
|
||||
version: 0.10.0-dev
|
||||
version: 0.9.2-dev
|
||||
groups: cpp
|
||||
dbscheme: semmlecode.cpp.dbscheme
|
||||
extractor: cpp
|
||||
|
||||
@@ -5,35 +5,155 @@
|
||||
import semmle.code.cpp.Element
|
||||
import semmle.code.cpp.Declaration
|
||||
import semmle.code.cpp.metrics.MetricFile
|
||||
private import codeql.util.FileSystem
|
||||
|
||||
private module Input implements InputSig {
|
||||
abstract class ContainerBase extends @container {
|
||||
abstract string getAbsolutePath();
|
||||
|
||||
ContainerBase getParentContainer() {
|
||||
containerparent(unresolveElement(result), underlyingElement(this))
|
||||
}
|
||||
|
||||
string toString() { result = this.getAbsolutePath() }
|
||||
}
|
||||
|
||||
class FolderBase extends ContainerBase, @folder {
|
||||
override string getAbsolutePath() { folders(underlyingElement(this), result) }
|
||||
}
|
||||
|
||||
class FileBase extends ContainerBase, @file {
|
||||
override string getAbsolutePath() { files(underlyingElement(this), result) }
|
||||
}
|
||||
|
||||
predicate hasSourceLocationPrefix = sourceLocationPrefix/1;
|
||||
}
|
||||
|
||||
private module Impl = Make<Input>;
|
||||
|
||||
/** A file or folder. */
|
||||
class Container extends Locatable, Impl::Container {
|
||||
override string toString() { result = Impl::Container.super.toString() }
|
||||
class Container extends Locatable, @container {
|
||||
/**
|
||||
* Gets the absolute, canonical path of this container, using forward slashes
|
||||
* as path separator.
|
||||
*
|
||||
* The path starts with a _root prefix_ followed by zero or more _path
|
||||
* segments_ separated by forward slashes.
|
||||
*
|
||||
* The root prefix is of one of the following forms:
|
||||
*
|
||||
* 1. A single forward slash `/` (Unix-style)
|
||||
* 2. An upper-case drive letter followed by a colon and a forward slash,
|
||||
* such as `C:/` (Windows-style)
|
||||
* 3. Two forward slashes, a computer name, and then another forward slash,
|
||||
* such as `//FileServer/` (UNC-style)
|
||||
*
|
||||
* Path segments are never empty (that is, absolute paths never contain two
|
||||
* contiguous slashes, except as part of a UNC-style root prefix). Also, path
|
||||
* segments never contain forward slashes, and no path segment is of the
|
||||
* form `.` (one dot) or `..` (two dots).
|
||||
*
|
||||
* Note that an absolute path never ends with a forward slash, except if it is
|
||||
* a bare root prefix, that is, the path has no path segments. A container
|
||||
* whose absolute path has no segments is always a `Folder`, not a `File`.
|
||||
*/
|
||||
string getAbsolutePath() { none() } // overridden by subclasses
|
||||
|
||||
/**
|
||||
* Gets the relative path of this file or folder from the root folder of the
|
||||
* analyzed source location. The relative path of the root folder itself is
|
||||
* the empty string.
|
||||
*
|
||||
* This has no result if the container is outside the source root, that is,
|
||||
* if the root folder is not a reflexive, transitive parent of this container.
|
||||
*/
|
||||
string getRelativePath() {
|
||||
exists(string absPath, string pref |
|
||||
absPath = this.getAbsolutePath() and sourceLocationPrefix(pref)
|
||||
|
|
||||
absPath = pref and result = ""
|
||||
or
|
||||
absPath = pref.regexpReplaceAll("/$", "") + "/" + result and
|
||||
not result.matches("/%")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base name of this container including extension, that is, the last
|
||||
* segment of its absolute path, or the empty string if it has no segments.
|
||||
*
|
||||
* Here are some examples of absolute paths and the corresponding base names
|
||||
* (surrounded with quotes to avoid ambiguity):
|
||||
*
|
||||
* <table border="1">
|
||||
* <tr><th>Absolute path</th><th>Base name</th></tr>
|
||||
* <tr><td>"/tmp/tst.js"</td><td>"tst.js"</td></tr>
|
||||
* <tr><td>"C:/Program Files (x86)"</td><td>"Program Files (x86)"</td></tr>
|
||||
* <tr><td>"/"</td><td>""</td></tr>
|
||||
* <tr><td>"C:/"</td><td>""</td></tr>
|
||||
* <tr><td>"D:/"</td><td>""</td></tr>
|
||||
* <tr><td>"//FileServer/"</td><td>""</td></tr>
|
||||
* </table>
|
||||
*/
|
||||
string getBaseName() {
|
||||
result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension of this container, that is, the suffix of its base name
|
||||
* after the last dot character, if any.
|
||||
*
|
||||
* In particular,
|
||||
*
|
||||
* - if the name does not include a dot, there is no extension, so this
|
||||
* predicate has no result;
|
||||
* - if the name ends in a dot, the extension is the empty string;
|
||||
* - if the name contains multiple dots, the extension follows the last dot.
|
||||
*
|
||||
* Here are some examples of absolute paths and the corresponding extensions
|
||||
* (surrounded with quotes to avoid ambiguity):
|
||||
*
|
||||
* <table border="1">
|
||||
* <tr><th>Absolute path</th><th>Extension</th></tr>
|
||||
* <tr><td>"/tmp/tst.js"</td><td>"js"</td></tr>
|
||||
* <tr><td>"/tmp/.classpath"</td><td>"classpath"</td></tr>
|
||||
* <tr><td>"/bin/bash"</td><td>not defined</td></tr>
|
||||
* <tr><td>"/tmp/tst2."</td><td>""</td></tr>
|
||||
* <tr><td>"/tmp/x.tar.gz"</td><td>"gz"</td></tr>
|
||||
* </table>
|
||||
*/
|
||||
string getExtension() {
|
||||
result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stem of this container, that is, the prefix of its base name up to
|
||||
* (but not including) the last dot character if there is one, or the entire
|
||||
* base name if there is not.
|
||||
*
|
||||
* Here are some examples of absolute paths and the corresponding stems
|
||||
* (surrounded with quotes to avoid ambiguity):
|
||||
*
|
||||
* <table border="1">
|
||||
* <tr><th>Absolute path</th><th>Stem</th></tr>
|
||||
* <tr><td>"/tmp/tst.js"</td><td>"tst"</td></tr>
|
||||
* <tr><td>"/tmp/.classpath"</td><td>""</td></tr>
|
||||
* <tr><td>"/bin/bash"</td><td>"bash"</td></tr>
|
||||
* <tr><td>"/tmp/tst2."</td><td>"tst2"</td></tr>
|
||||
* <tr><td>"/tmp/x.tar.gz"</td><td>"x.tar"</td></tr>
|
||||
* </table>
|
||||
*/
|
||||
string getStem() {
|
||||
result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1)
|
||||
}
|
||||
|
||||
/** Gets the parent container of this file or folder, if any. */
|
||||
Container getParentContainer() {
|
||||
containerparent(unresolveElement(result), underlyingElement(this))
|
||||
}
|
||||
|
||||
/** Gets a file or sub-folder in this container. */
|
||||
Container getAChildContainer() { this = result.getParentContainer() }
|
||||
|
||||
/** Gets a file in this container. */
|
||||
File getAFile() { result = this.getAChildContainer() }
|
||||
|
||||
/** Gets the file in this container that has the given `baseName`, if any. */
|
||||
File getFile(string baseName) {
|
||||
result = this.getAFile() and
|
||||
result.getBaseName() = baseName
|
||||
}
|
||||
|
||||
/** Gets a sub-folder in this container. */
|
||||
Folder getAFolder() { result = this.getAChildContainer() }
|
||||
|
||||
/** Gets the sub-folder in this container that has the given `baseName`, if any. */
|
||||
Folder getFolder(string baseName) {
|
||||
result = this.getAFolder() and
|
||||
result.getBaseName() = baseName
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a textual representation of the path of this container.
|
||||
*
|
||||
* This is the absolute path of the container.
|
||||
*/
|
||||
override string toString() { result = this.getAbsolutePath() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +166,9 @@ class Container extends Locatable, Impl::Container {
|
||||
*
|
||||
* To get the full path, use `getAbsolutePath`.
|
||||
*/
|
||||
class Folder extends Container, Impl::Folder {
|
||||
class Folder extends Container, @folder {
|
||||
override string getAbsolutePath() { folders(underlyingElement(this), result) }
|
||||
|
||||
override Location getLocation() {
|
||||
result.getContainer() = this and
|
||||
result.hasLocationInfo(_, 0, 0, 0, 0)
|
||||
@@ -67,7 +189,9 @@ class Folder extends Container, Impl::Folder {
|
||||
* The base name further decomposes into the _stem_ and _extension_ -- see
|
||||
* `getStem` and `getExtension`. To get the full path, use `getAbsolutePath`.
|
||||
*/
|
||||
class File extends Container, Impl::File {
|
||||
class File extends Container, @file {
|
||||
override string getAbsolutePath() { files(underlyingElement(this), result) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "File" }
|
||||
|
||||
override Location getLocation() {
|
||||
|
||||
@@ -297,10 +297,6 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -297,10 +297,6 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -297,10 +297,6 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -297,10 +297,6 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -297,10 +297,6 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -297,10 +297,6 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -297,10 +297,6 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -297,10 +297,6 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -297,10 +297,6 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -645,24 +645,12 @@ private predicate adjustForPointerArith(PostUpdateNode pun, UseOrPhi use) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` flows to `nodeTo` because there is `def-use` or
|
||||
* `use-use` flow from `defOrUse` to `use`.
|
||||
*
|
||||
* `uncertain` is `true` if the `defOrUse` is an uncertain definition.
|
||||
*/
|
||||
private predicate localSsaFlow(
|
||||
SsaDefOrUse defOrUse, Node nodeFrom, UseOrPhi use, Node nodeTo, boolean uncertain
|
||||
) {
|
||||
nodeToDefOrUse(nodeFrom, defOrUse, uncertain) and
|
||||
adjacentDefRead(defOrUse, use) and
|
||||
useToNode(use, nodeTo) and
|
||||
nodeFrom != nodeTo
|
||||
}
|
||||
|
||||
private predicate ssaFlowImpl(SsaDefOrUse defOrUse, Node nodeFrom, Node nodeTo, boolean uncertain) {
|
||||
exists(UseOrPhi use |
|
||||
localSsaFlow(defOrUse, nodeFrom, use, nodeTo, uncertain)
|
||||
nodeToDefOrUse(nodeFrom, defOrUse, uncertain) and
|
||||
adjacentDefRead(defOrUse, use) and
|
||||
useToNode(use, nodeTo) and
|
||||
nodeFrom != nodeTo
|
||||
or
|
||||
// Initial global variable value to a first use
|
||||
nodeFrom.(InitialGlobalValue).getGlobalDef() = defOrUse and
|
||||
@@ -740,62 +728,15 @@ private predicate isArgumentOfCallable(DataFlowCall call, Node n) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is use-use flow from `pun`'s pre-update node to `n`.
|
||||
*/
|
||||
private predicate postUpdateNodeToFirstUse(PostUpdateNode pun, Node n) {
|
||||
exists(UseOrPhi use |
|
||||
adjustForPointerArith(pun, use) and
|
||||
useToNode(use, n)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate stepUntilNotInCall(DataFlowCall call, Node n1, Node n2) {
|
||||
isArgumentOfCallable(call, n1) and
|
||||
exists(Node mid | localSsaFlow(_, n1, _, mid, _) |
|
||||
isArgumentOfCallable(call, mid) and
|
||||
stepUntilNotInCall(call, mid, n2)
|
||||
or
|
||||
not isArgumentOfCallable(call, mid) and
|
||||
mid = n2
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[n1, n2]
|
||||
pragma[inline_late]
|
||||
private predicate isArgumentOfSameCall(DataFlowCall call, Node n1, Node n2) {
|
||||
isArgumentOfCallable(call, n1) and isArgumentOfCallable(call, n2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is def-use or use-use flow from `pun` to `nodeTo`.
|
||||
*
|
||||
* Note: This is more complex than it sounds. Consider a call such as:
|
||||
* ```cpp
|
||||
* write_first_argument(x, x);
|
||||
* sink(x);
|
||||
* ```
|
||||
* Assume flow comes out of the first argument to `write_first_argument`. We
|
||||
* don't want flow to go to the `x` that's also an argument to
|
||||
* `write_first_argument` (because we just flowed out of that function, and we
|
||||
* don't want to flow back into it again).
|
||||
*
|
||||
* We do, however, want flow from the output argument to `x` on the next line, and
|
||||
* similarly we want flow from the second argument of `write_first_argument` to `x`
|
||||
* on the next line.
|
||||
*/
|
||||
/** Holds if there is def-use or use-use flow from `pun` to `nodeTo`. */
|
||||
predicate postUpdateFlow(PostUpdateNode pun, Node nodeTo) {
|
||||
exists(Node preUpdate, Node mid |
|
||||
exists(UseOrPhi use, Node preUpdate |
|
||||
adjustForPointerArith(pun, use) and
|
||||
useToNode(use, nodeTo) and
|
||||
preUpdate = pun.getPreUpdateNode() and
|
||||
postUpdateNodeToFirstUse(pun, mid)
|
||||
|
|
||||
exists(DataFlowCall call |
|
||||
isArgumentOfSameCall(call, preUpdate, mid) and
|
||||
stepUntilNotInCall(call, mid, nodeTo)
|
||||
not exists(DataFlowCall call |
|
||||
isArgumentOfCallable(call, preUpdate) and isArgumentOfCallable(call, nodeTo)
|
||||
)
|
||||
or
|
||||
not isArgumentOfSameCall(_, preUpdate, mid) and
|
||||
nodeTo = mid
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
## 0.7.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.7.4
|
||||
|
||||
### New Queries
|
||||
|
||||
* Added a new query, `cpp/invalid-pointer-deref`, to detect out-of-bounds pointer reads and writes.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The "Comparison where assignment was intended" query (`cpp/compare-where-assign-meant`) no longer reports comparisons that appear in macro expansions.
|
||||
* Some queries that had repeated results corresponding to different levels of indirection for `argv` now only have a single result.
|
||||
* The `cpp/non-constant-format` query no longer considers an assignment on the right-hand side of another assignment to be a source of non-constant format strings. As a result, the query may now produce fewer results.
|
||||
|
||||
## 0.7.3
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @name Potential double free
|
||||
* @description Freeing a resource more than once can lead to undefined behavior and cause memory corruption.
|
||||
* @kind path-problem
|
||||
* @precision high
|
||||
* @precision medium
|
||||
* @id cpp/double-free
|
||||
* @problem.severity warning
|
||||
* @security-severity 9.3
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @name Potential use after free
|
||||
* @description An allocated memory block is used after it has been freed. Behavior in such cases is undefined and can cause memory corruption.
|
||||
* @kind path-problem
|
||||
* @precision high
|
||||
* @precision medium
|
||||
* @id cpp/use-after-free
|
||||
* @problem.severity warning
|
||||
* @security-severity 9.3
|
||||
@@ -29,7 +29,8 @@ private predicate externalCallNeverDereferences(FormattingFunctionCall call, int
|
||||
)
|
||||
}
|
||||
|
||||
predicate isUse0(Expr e) {
|
||||
predicate isUse0(DataFlow::Node n, Expr e) {
|
||||
e = n.asExpr() and
|
||||
not isFree(_, e, _) and
|
||||
(
|
||||
e = any(PointerDereferenceExpr pde).getOperand()
|
||||
@@ -42,7 +43,7 @@ predicate isUse0(Expr e) {
|
||||
or
|
||||
// Assume any function without a body will dereference the pointer
|
||||
exists(int i, Call call, Function f |
|
||||
e = call.getArgument(i) and
|
||||
n.asExpr() = call.getArgument(i) and
|
||||
f = call.getTarget() and
|
||||
not f.hasEntryPoint() and
|
||||
// Exclude known functions we know won't dereference the pointer.
|
||||
@@ -56,7 +57,7 @@ module ParameterSinks {
|
||||
import semmle.code.cpp.ir.ValueNumbering
|
||||
|
||||
predicate flowsToUse(DataFlow::Node n) {
|
||||
isUse0(n.asExpr())
|
||||
isUse0(n, _)
|
||||
or
|
||||
exists(DataFlow::Node succ |
|
||||
flowsToUse(succ) and
|
||||
@@ -89,7 +90,7 @@ module ParameterSinks {
|
||||
) {
|
||||
pragma[only_bind_out](source.asParameter()) = pragma[only_bind_out](init.getParameter()) and
|
||||
paramToUse(source, sink) and
|
||||
isUse0(sink.asExpr())
|
||||
isUse0(sink, _)
|
||||
}
|
||||
|
||||
private InitializeParameterInstruction getAnAlwaysDereferencedParameter0() {
|
||||
@@ -138,7 +139,7 @@ module IsUse {
|
||||
private import semmle.code.cpp.ir.dataflow.internal.DataFlowImplCommon
|
||||
|
||||
predicate isUse(DataFlow::Node n, Expr e) {
|
||||
isUse0(e) and n.asExpr() = e
|
||||
isUse0(n, e)
|
||||
or
|
||||
exists(CallInstruction call, InitializeParameterInstruction init |
|
||||
n.asOperand().getDef().getUnconvertedResultExpression() = e and
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* Added a new query, `cpp/invalid-pointer-deref`, to detect out-of-bounds pointer reads and writes.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Some queries that had repeated results corresponding to different levels of indirection for `argv` now only have a single result.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `cpp/non-constant-format` query no longer considers an assignment on the right-hand side of another assignment to be a source of non-constant format strings. As a result, the query may now produce fewer results.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The "Comparison where assignment was intended" query (`cpp/compare-where-assign-meant`) no longer reports comparisons that appear in macro expansions.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: queryMetadata
|
||||
---
|
||||
* The `cpp/double-free` query has been further improved to reduce false positives and its precision has been increased from `medium` to `high`.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: queryMetadata
|
||||
---
|
||||
* The `cpp/use-after-free` query has been further improved to reduce false positives and its precision has been increased from `medium` to `high`.
|
||||
@@ -1,11 +0,0 @@
|
||||
## 0.7.4
|
||||
|
||||
### New Queries
|
||||
|
||||
* Added a new query, `cpp/invalid-pointer-deref`, to detect out-of-bounds pointer reads and writes.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The "Comparison where assignment was intended" query (`cpp/compare-where-assign-meant`) no longer reports comparisons that appear in macro expansions.
|
||||
* Some queries that had repeated results corresponding to different levels of indirection for `argv` now only have a single result.
|
||||
* The `cpp/non-constant-format` query no longer considers an assignment on the right-hand side of another assignment to be a source of non-constant format strings. As a result, the query may now produce fewer results.
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.7.5
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.7.5
|
||||
lastReleaseVersion: 0.7.3
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
name: codeql/cpp-queries
|
||||
version: 0.8.0-dev
|
||||
groups:
|
||||
version: 0.7.4-dev
|
||||
groups:
|
||||
- cpp
|
||||
- queries
|
||||
dependencies:
|
||||
codeql/cpp-all: ${workspace}
|
||||
codeql/suite-helpers: ${workspace}
|
||||
codeql/util: ${workspace}
|
||||
codeql/cpp-all: ${workspace}
|
||||
codeql/suite-helpers: ${workspace}
|
||||
codeql/util: ${workspace}
|
||||
suites: codeql-suites
|
||||
extractor: cpp
|
||||
defaultSuiteFile: codeql-suites/cpp-code-scanning.qls
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
WARNING: Module TaintedWithPath has been deprecated and may be removed in future (tainted.ql:10,8-47)
|
||||
WARNING: Predicate tainted has been deprecated and may be removed in future (tainted.ql:21,3-28)
|
||||
testFailures
|
||||
failures
|
||||
testFailures
|
||||
|
||||
@@ -788,12 +788,4 @@ void test_sometimes_calls_sink_switch() {
|
||||
sometimes_calls_sink_switch(source(), 1);
|
||||
sometimes_calls_sink_switch(0, 0);
|
||||
sometimes_calls_sink_switch(source(), 0);
|
||||
}
|
||||
|
||||
void intPointerSource(int *ref_source, const int* another_arg);
|
||||
|
||||
void test() {
|
||||
MyStruct a;
|
||||
intPointerSource(a.content, a.content);
|
||||
indirect_sink(a.content); // $ ast ir
|
||||
}
|
||||
@@ -46,6 +46,3 @@
|
||||
| test.cpp:595:8:595:9 | xs | test.cpp:597:9:597:10 | xs |
|
||||
| test.cpp:733:7:733:7 | x | test.cpp:734:41:734:41 | x |
|
||||
| test.cpp:733:7:733:7 | x | test.cpp:735:8:735:8 | x |
|
||||
| test.cpp:796:12:796:12 | a | test.cpp:797:20:797:20 | a |
|
||||
| test.cpp:796:12:796:12 | a | test.cpp:797:31:797:31 | a |
|
||||
| test.cpp:796:12:796:12 | a | test.cpp:798:17:798:17 | a |
|
||||
|
||||
@@ -96,7 +96,6 @@
|
||||
| test_free.cpp:255:10:255:10 | p |
|
||||
| test_free.cpp:260:9:260:9 | p |
|
||||
| test_free.cpp:263:12:263:12 | p |
|
||||
| test_free.cpp:269:7:269:11 | ... = ... |
|
||||
| virtual.cpp:18:10:18:10 | a |
|
||||
| virtual.cpp:19:10:19:10 | c |
|
||||
| virtual.cpp:38:10:38:10 | b |
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
| test_free.cpp:36:22:36:35 | ... = ... | This memory allocation may not be released at $@. | test_free.cpp:38:1:38:1 | return ... | this exit point |
|
||||
| test_free.cpp:267:12:267:17 | call to malloc | This memory allocation may not be released at $@. | test_free.cpp:270:1:270:1 | return ... | this exit point |
|
||||
|
||||
@@ -261,10 +261,4 @@ void test_ref_delete(int *&p) {
|
||||
p = new int;
|
||||
use(p); // GOOD
|
||||
delete p; // GOOD
|
||||
}
|
||||
|
||||
void test_free_assign() {
|
||||
void *a = malloc(10);
|
||||
void *b;
|
||||
free(b = a); // GOOD
|
||||
}
|
||||
@@ -7,7 +7,6 @@ edges
|
||||
| overflowdestination.cpp:50:52:50:54 | src indirection | overflowdestination.cpp:53:15:53:17 | src indirection |
|
||||
| overflowdestination.cpp:50:52:50:54 | src indirection | overflowdestination.cpp:54:9:54:12 | memcpy output argument |
|
||||
| overflowdestination.cpp:53:9:53:12 | memcpy output argument | overflowdestination.cpp:54:9:54:12 | memcpy output argument |
|
||||
| overflowdestination.cpp:54:9:54:12 | memcpy output argument | overflowdestination.cpp:54:9:54:12 | memcpy output argument |
|
||||
| overflowdestination.cpp:57:52:57:54 | src indirection | overflowdestination.cpp:64:16:64:19 | src2 indirection |
|
||||
| overflowdestination.cpp:73:8:73:10 | fgets output argument | overflowdestination.cpp:75:30:75:32 | src indirection |
|
||||
| overflowdestination.cpp:73:8:73:10 | fgets output argument | overflowdestination.cpp:76:30:76:32 | src indirection |
|
||||
|
||||
@@ -3,7 +3,6 @@ description: Builds the C# CodeQL pack
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: ./.github/actions/fetch-codeql
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
@@ -12,13 +11,3 @@ runs:
|
||||
shell: bash
|
||||
run: scripts/create-extractor-pack.sh
|
||||
working-directory: csharp
|
||||
- name: Patch bundle to include ASP extractor
|
||||
shell: bash
|
||||
run: |
|
||||
CODEQL_PATH=$(gh codeql version --format=json | jq -r .unpackedLocation)
|
||||
# The legacy ASP extractor is not in this repo, so take the one from the nightly build
|
||||
mv "$CODEQL_PATH/csharp/tools/extractor-asp.jar" "${{ github.workspace }}/csharp/extractor-pack/tools"
|
||||
# Safe guard against using the bundled extractor
|
||||
rm -rf "$CODEQL_PATH/csharp"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
@@ -24,5 +24,5 @@ Microsoft.Win32,,,8,,,,,,,,,,,,,,8,
|
||||
MySql.Data.MySqlClient,48,,,,,,,,,,,48,,,,,,
|
||||
Newtonsoft.Json,,,91,,,,,,,,,,,,,,73,18
|
||||
ServiceStack,194,,7,27,,,,,75,,,92,,,,,7,
|
||||
System,65,25,12149,,8,8,9,,,4,3,33,1,17,3,4,10163,1986
|
||||
System,65,25,12148,,8,8,9,,,4,3,33,1,17,3,4,10163,1985
|
||||
Windows.Security.Cryptography.Core,1,,,,,,,1,,,,,,,,,,
|
||||
|
||||
|
@@ -8,7 +8,7 @@ C# framework & library support
|
||||
|
||||
Framework / library,Package,Flow sources,Taint & value steps,Sinks (total),`CWE-079` :sub:`Cross-site scripting`
|
||||
`ServiceStack <https://servicestack.net/>`_,"``ServiceStack.*``, ``ServiceStack``",,7,194,
|
||||
System,"``System.*``, ``System``",25,12149,65,7
|
||||
System,"``System.*``, ``System``",25,12148,65,7
|
||||
Others,"``Dapper``, ``JsonToItemsTaskFactory``, ``Microsoft.ApplicationBlocks.Data``, ``Microsoft.CSharp``, ``Microsoft.EntityFrameworkCore``, ``Microsoft.Extensions.Caching.Distributed``, ``Microsoft.Extensions.Caching.Memory``, ``Microsoft.Extensions.Configuration``, ``Microsoft.Extensions.DependencyInjection``, ``Microsoft.Extensions.DependencyModel``, ``Microsoft.Extensions.FileProviders``, ``Microsoft.Extensions.FileSystemGlobbing``, ``Microsoft.Extensions.Hosting``, ``Microsoft.Extensions.Http``, ``Microsoft.Extensions.Logging``, ``Microsoft.Extensions.Options``, ``Microsoft.Extensions.Primitives``, ``Microsoft.Interop``, ``Microsoft.NET.Build.Tasks``, ``Microsoft.NETCore.Platforms.BuildTasks``, ``Microsoft.VisualBasic``, ``Microsoft.Win32``, ``MySql.Data.MySqlClient``, ``Newtonsoft.Json``, ``Windows.Security.Cryptography.Core``",,568,138,
|
||||
Totals,,25,12724,397,7
|
||||
Totals,,25,12723,397,7
|
||||
|
||||
|
||||
@@ -23,8 +23,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
|
||||
private readonly IDictionary<string, string> unresolvedReferences = new ConcurrentDictionary<string, string>();
|
||||
private int failedProjects;
|
||||
private int succeededProjects;
|
||||
private readonly List<string> nonGeneratedSources;
|
||||
private readonly List<string> generatedSources;
|
||||
private readonly List<string> allSources;
|
||||
private int conflictedReferences = 0;
|
||||
private readonly IDependencyOptions options;
|
||||
private readonly DirectoryInfo sourceDir;
|
||||
@@ -32,7 +31,6 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
|
||||
private readonly FileContent fileContent;
|
||||
private readonly TemporaryDirectory packageDirectory;
|
||||
private readonly TemporaryDirectory tempWorkingDirectory;
|
||||
private readonly bool cleanupTempWorkingDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// Performs C# dependency fetching.
|
||||
@@ -60,15 +58,14 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
|
||||
this.progressMonitor.FindingFiles(srcDir);
|
||||
|
||||
packageDirectory = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName));
|
||||
tempWorkingDirectory = new TemporaryDirectory(FileUtils.GetTemporaryWorkingDirectory(out cleanupTempWorkingDirectory));
|
||||
tempWorkingDirectory = new TemporaryDirectory(GetTemporaryWorkingDirectory());
|
||||
|
||||
var allFiles = GetAllFiles();
|
||||
var binaryFileExtensions = new HashSet<string>(new[] { ".dll", ".exe" }); // TODO: add more binary file extensions.
|
||||
var allNonBinaryFiles = allFiles.Where(f => !binaryFileExtensions.Contains(f.Extension.ToLowerInvariant())).ToList();
|
||||
var smallNonBinaryFiles = allNonBinaryFiles.SelectSmallFiles(progressMonitor).SelectFileNames();
|
||||
this.fileContent = new FileContent(progressMonitor, smallNonBinaryFiles);
|
||||
this.nonGeneratedSources = allNonBinaryFiles.SelectFileNamesByExtension(".cs").ToList();
|
||||
this.generatedSources = new();
|
||||
this.allSources = allNonBinaryFiles.SelectFileNamesByExtension(".cs").ToList();
|
||||
var allProjects = allNonBinaryFiles.SelectFileNamesByExtension(".csproj");
|
||||
var solutions = options.SolutionFile is not null
|
||||
? new[] { options.SolutionFile }
|
||||
@@ -167,11 +164,6 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
|
||||
Path.Combine(packageFolder, "microsoft.netcore.app.runtime"),
|
||||
Path.Combine(packageFolder, "microsoft.aspnetcore.app.runtime"),
|
||||
Path.Combine(packageFolder, "microsoft.windowsdesktop.app.runtime"),
|
||||
|
||||
// legacy runtime packages:
|
||||
Path.Combine(packageFolder, "runtime.linux-x64.microsoft.netcore.app"),
|
||||
Path.Combine(packageFolder, "runtime.osx-x64.microsoft.netcore.app"),
|
||||
Path.Combine(packageFolder, "runtime.win-x64.microsoft.netcore.app"),
|
||||
};
|
||||
|
||||
foreach (var filename in usedReferences.Keys)
|
||||
@@ -222,7 +214,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
|
||||
}
|
||||
}
|
||||
|
||||
this.generatedSources.Add(path);
|
||||
this.allSources.Add(path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +236,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
|
||||
var razor = new Razor(sdk, dotnet, progressMonitor);
|
||||
var targetDir = GetTemporaryWorkingDirectory("razor");
|
||||
var generatedFiles = razor.GenerateFiles(views, usedReferences.Keys, targetDir);
|
||||
this.generatedSources.AddRange(generatedFiles);
|
||||
this.allSources.AddRange(generatedFiles);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -286,6 +278,20 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
|
||||
return Path.Combine(Path.GetTempPath(), "GitHub", "packages", sb.ToString());
|
||||
}
|
||||
|
||||
private static string GetTemporaryWorkingDirectory()
|
||||
{
|
||||
var tempFolder = EnvironmentVariables.GetScratchDirectory();
|
||||
|
||||
if (string.IsNullOrEmpty(tempFolder))
|
||||
{
|
||||
var tempPath = Path.GetTempPath();
|
||||
var name = Guid.NewGuid().ToString("N").ToUpper();
|
||||
tempFolder = Path.Combine(tempPath, "GitHub", name);
|
||||
}
|
||||
|
||||
return tempFolder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a temporary directory with the given subfolder name.
|
||||
/// The created directory might be inside the repo folder, and it is deleted when the object is disposed.
|
||||
@@ -373,15 +379,10 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
|
||||
/// </summary>
|
||||
public IEnumerable<string> ProjectSourceFiles => sources.Where(s => s.Value).Select(s => s.Key);
|
||||
|
||||
/// <summary>
|
||||
/// All of the generated source files in the source directory.
|
||||
/// </summary>
|
||||
public IEnumerable<string> GeneratedSourceFiles => generatedSources;
|
||||
|
||||
/// <summary>
|
||||
/// All of the source files in the source directory.
|
||||
/// </summary>
|
||||
public IEnumerable<string> AllSourceFiles => generatedSources.Concat(nonGeneratedSources);
|
||||
public IEnumerable<string> AllSourceFiles => allSources;
|
||||
|
||||
/// <summary>
|
||||
/// List of assembly IDs which couldn't be resolved.
|
||||
@@ -561,8 +562,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
|
||||
public void Dispose()
|
||||
{
|
||||
packageDirectory?.Dispose();
|
||||
if (cleanupTempWorkingDirectory)
|
||||
tempWorkingDirectory?.Dispose();
|
||||
tempWorkingDirectory?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,46 +30,29 @@ namespace Semmle.Extraction.CSharp.Standalone
|
||||
IProgressMonitor progressMonitor,
|
||||
Stopwatch stopwatch)
|
||||
{
|
||||
var output = FileUtils.CreateTemporaryFile(".dll", out var shouldCleanUpContainingFolder);
|
||||
|
||||
try
|
||||
{
|
||||
CSharp.Extractor.Analyse(stopwatch, analyser, options,
|
||||
references => GetResolvedReferencesStandalone(referencePaths, references),
|
||||
(analyser, syntaxTrees) => CSharp.Extractor.ReadSyntaxTrees(sources, analyser, null, null, syntaxTrees),
|
||||
(syntaxTrees, references) => CSharpCompilation.Create(
|
||||
output.Name, syntaxTrees, references, new CSharpCompilationOptions(OutputKind.ConsoleApplication, allowUnsafe: true)
|
||||
),
|
||||
(compilation, options) => analyser.Initialize(output.FullName, compilation, options),
|
||||
_ => { },
|
||||
() =>
|
||||
{
|
||||
foreach (var type in analyser.MissingNamespaces)
|
||||
{
|
||||
progressMonitor.MissingNamespace(type);
|
||||
}
|
||||
|
||||
foreach (var type in analyser.MissingTypes)
|
||||
{
|
||||
progressMonitor.MissingType(type);
|
||||
}
|
||||
|
||||
progressMonitor.MissingSummary(analyser.MissingTypes.Count(), analyser.MissingNamespaces.Count());
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
CSharp.Extractor.Analyse(stopwatch, analyser, options,
|
||||
references => GetResolvedReferencesStandalone(referencePaths, references),
|
||||
(analyser, syntaxTrees) => CSharp.Extractor.ReadSyntaxTrees(sources, analyser, null, null, syntaxTrees),
|
||||
(syntaxTrees, references) => CSharpCompilation.Create(
|
||||
"csharp.dll", syntaxTrees, references, new CSharpCompilationOptions(OutputKind.ConsoleApplication, allowUnsafe: true)
|
||||
),
|
||||
(compilation, options) => analyser.Initialize(compilation, options),
|
||||
() => { },
|
||||
_ => { },
|
||||
() =>
|
||||
{
|
||||
FileUtils.TryDelete(output.FullName);
|
||||
if (shouldCleanUpContainingFolder)
|
||||
foreach (var type in analyser.MissingNamespaces)
|
||||
{
|
||||
output.Directory?.Delete(true);
|
||||
progressMonitor.MissingNamespace(type);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
|
||||
foreach (var type in analyser.MissingTypes)
|
||||
{
|
||||
progressMonitor.MissingType(type);
|
||||
}
|
||||
|
||||
progressMonitor.MissingSummary(analyser.MissingTypes.Count(), analyser.MissingNamespaces.Count());
|
||||
});
|
||||
}
|
||||
|
||||
private static void ExtractStandalone(
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Semmle.Util.Logging;
|
||||
|
||||
@@ -13,15 +11,13 @@ namespace Semmle.Extraction.CSharp
|
||||
{
|
||||
}
|
||||
|
||||
public void Initialize(string outputPath, CSharpCompilation compilationIn, CommonOptions options)
|
||||
public void Initialize(CSharpCompilation compilationIn, CommonOptions options)
|
||||
{
|
||||
compilation = compilationIn;
|
||||
extractor = new StandaloneExtractor(outputPath, Logger, PathTransformer, options);
|
||||
extractor = new StandaloneExtractor(Logger, PathTransformer, options);
|
||||
this.options = options;
|
||||
LogExtractorInfo(Extraction.Extractor.Version);
|
||||
SetReferencePaths();
|
||||
|
||||
Entities.Compilation.Settings = (Directory.GetCurrentDirectory(), Array.Empty<string>());
|
||||
}
|
||||
|
||||
#nullable disable warnings
|
||||
@@ -1,9 +0,0 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.StubGenerator;
|
||||
|
||||
internal interface IRelevantSymbol
|
||||
{
|
||||
bool IsRelevantNamedType(INamedTypeSymbol symbol);
|
||||
bool IsRelevantNamespace(INamespaceSymbol symbol);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Semmle.Extraction.CSharp.StubGenerator")]
|
||||
[assembly: AssemblyDescription("Stub generator for C#")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Semmle.Extraction.CSharp.StubGenerator")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2023")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("6d55ca39-615a-4c1b-a648-a4010995e1ea")]
|
||||
|
||||
// Expose internals for testing purposes.
|
||||
[assembly: InternalsVisibleTo("Semmle.Extraction.Tests")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -1,10 +1,13 @@
|
||||
using System.Linq;
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
using Semmle.Util;
|
||||
|
||||
namespace Semmle.Extraction.CSharp.StubGenerator;
|
||||
|
||||
internal class RelevantSymbol : IRelevantSymbol
|
||||
internal class RelevantSymbol
|
||||
{
|
||||
private readonly IAssemblySymbol assembly;
|
||||
private readonly MemoizedFunc<INamedTypeSymbol, bool> isRelevantNamedType;
|
||||
|
||||
@@ -20,14 +20,13 @@ public static class StubGenerator
|
||||
/// </summary>
|
||||
/// <param name="referencesPaths">The paths of the assemblies to generate stubs for.</param>
|
||||
/// <param name="outputPath">The path in which to store the stubs.</param>
|
||||
public static string[] GenerateStubs(ILogger logger, IEnumerable<string> referencesPaths, string outputPath)
|
||||
public static void GenerateStubs(ILogger logger, IEnumerable<string> referencesPaths, string outputPath)
|
||||
{
|
||||
var stopWatch = new System.Diagnostics.Stopwatch();
|
||||
stopWatch.Start();
|
||||
|
||||
var threads = EnvironmentVariables.GetDefaultNumberOfThreads();
|
||||
|
||||
using var stubPaths = new BlockingCollection<string>();
|
||||
using var references = new BlockingCollection<(MetadataReference Reference, string Path)>();
|
||||
|
||||
Parallel.ForEach(referencesPaths, new ParallelOptions { MaxDegreeOfParallelism = threads }, path =>
|
||||
@@ -46,16 +45,14 @@ public static class StubGenerator
|
||||
|
||||
Parallel.ForEach(references, new ParallelOptions { MaxDegreeOfParallelism = threads }, @ref =>
|
||||
{
|
||||
StubReference(logger, compilation, outputPath, @ref.Reference, @ref.Path, stubPaths);
|
||||
StubReference(logger, compilation, outputPath, @ref.Reference, @ref.Path);
|
||||
});
|
||||
|
||||
stopWatch.Stop();
|
||||
logger.Log(Severity.Info, $"Stub generation took {stopWatch.Elapsed}.");
|
||||
|
||||
return stubPaths.ToArray();
|
||||
}
|
||||
|
||||
private static void StubReference(ILogger logger, CSharpCompilation compilation, string outputPath, MetadataReference reference, string path, BlockingCollection<string> stubPaths)
|
||||
private static void StubReference(ILogger logger, CSharpCompilation compilation, string outputPath, MetadataReference reference, string path)
|
||||
{
|
||||
if (compilation.GetAssemblyOrModuleSymbol(reference) is not IAssemblySymbol assembly)
|
||||
return;
|
||||
@@ -65,10 +62,8 @@ public static class StubGenerator
|
||||
if (!assembly.Modules.Any(m => relevantSymbol.IsRelevantNamespace(m.GlobalNamespace)))
|
||||
return;
|
||||
|
||||
var stubPath = FileUtils.NestPaths(logger, outputPath, path.Replace(".dll", ".cs"));
|
||||
stubPaths.Add(stubPath);
|
||||
using var fileStream = new FileStream(stubPath, FileMode.Create, FileAccess.Write);
|
||||
using var writer = new StreamWriter(fileStream, new UTF8Encoding(false)) { NewLine = "\n" };
|
||||
using var fileStream = new FileStream(FileUtils.NestPaths(logger, outputPath, path.Replace(".dll", ".cs")), FileMode.Create, FileAccess.Write);
|
||||
using var writer = new StreamWriter(fileStream, new UTF8Encoding(false));
|
||||
|
||||
var visitor = new StubVisitor(writer, relevantSymbol);
|
||||
|
||||
@@ -83,3 +78,4 @@ public static class StubGenerator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ namespace Semmle.Extraction.CSharp.StubGenerator;
|
||||
internal sealed class StubVisitor : SymbolVisitor
|
||||
{
|
||||
private readonly TextWriter stubWriter;
|
||||
private readonly IRelevantSymbol relevantSymbol;
|
||||
private readonly RelevantSymbol relevantSymbol;
|
||||
|
||||
public StubVisitor(TextWriter stubWriter, IRelevantSymbol relevantSymbol)
|
||||
public StubVisitor(TextWriter stubWriter, RelevantSymbol relevantSymbol)
|
||||
{
|
||||
this.stubWriter = stubWriter;
|
||||
this.relevantSymbol = relevantSymbol;
|
||||
@@ -296,35 +296,6 @@ internal sealed class StubVisitor : SymbolVisitor
|
||||
private static string EscapeIdentifier(string identifier) =>
|
||||
keywords.Contains(identifier) ? "@" + identifier : identifier;
|
||||
|
||||
private static bool TryGetConstantValue(IFieldSymbol symbol, out string value)
|
||||
{
|
||||
value = "";
|
||||
if (!symbol.HasConstantValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var v = symbol.ConstantValue;
|
||||
switch (symbol.Type.SpecialType)
|
||||
{
|
||||
case SpecialType.System_Boolean:
|
||||
value = (bool)v ? "true" : "false";
|
||||
return true;
|
||||
case SpecialType.System_Byte:
|
||||
case SpecialType.System_SByte:
|
||||
case SpecialType.System_UInt16:
|
||||
case SpecialType.System_UInt32:
|
||||
case SpecialType.System_UInt64:
|
||||
case SpecialType.System_Int16:
|
||||
case SpecialType.System_Int32:
|
||||
case SpecialType.System_Int64:
|
||||
value = v.ToString()!;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitField(IFieldSymbol symbol)
|
||||
{
|
||||
if (IsNotPublic(symbol.DeclaredAccessibility))
|
||||
@@ -335,14 +306,7 @@ internal sealed class StubVisitor : SymbolVisitor
|
||||
StubModifiers(symbol);
|
||||
|
||||
if (symbol.IsConst)
|
||||
{
|
||||
stubWriter.Write("const ");
|
||||
}
|
||||
|
||||
if (!symbol.IsConst && symbol.IsReadOnly)
|
||||
{
|
||||
stubWriter.Write("readonly ");
|
||||
}
|
||||
|
||||
if (IsUnsafe(symbol.Type))
|
||||
{
|
||||
@@ -353,10 +317,7 @@ internal sealed class StubVisitor : SymbolVisitor
|
||||
stubWriter.Write(" ");
|
||||
stubWriter.Write(EscapeIdentifier(symbol.Name));
|
||||
if (symbol.IsConst)
|
||||
{
|
||||
var v = TryGetConstantValue(symbol, out var value) ? value : "default";
|
||||
stubWriter.Write($" = {v}");
|
||||
}
|
||||
stubWriter.Write(" = default");
|
||||
stubWriter.WriteLine(";");
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
if (init is null)
|
||||
{
|
||||
// This is the output assembly
|
||||
assemblyPath = cx.Extractor.OutputPath;
|
||||
assemblyPath = ((TracingExtractor)cx.Extractor).OutputPath;
|
||||
assembly = cx.Compilation.Assembly;
|
||||
}
|
||||
else
|
||||
@@ -63,6 +63,8 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
|
||||
public static Assembly CreateOutputAssembly(Context cx)
|
||||
{
|
||||
if (cx.Extractor.Mode.HasFlag(ExtractorMode.Standalone))
|
||||
throw new InternalError("Attempting to create the output assembly in standalone extraction mode");
|
||||
return AssemblyConstructorFactory.Instance.CreateEntity(cx, outputAssemblyCacheKey, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,10 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
|
||||
if (attributeSyntax is not null)
|
||||
{
|
||||
trapFile.attribute_location(this, Assembly.CreateOutputAssembly(Context));
|
||||
if (!Context.Extractor.Mode.HasFlag(ExtractorMode.Standalone))
|
||||
{
|
||||
trapFile.attribute_location(this, Assembly.CreateOutputAssembly(Context));
|
||||
}
|
||||
|
||||
TypeMention.Create(Context, attributeSyntax.Name, this, type);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
{
|
||||
// Some built in operators lack locations, so loc is null.
|
||||
yield return Context.CreateLocation(ReportingLocation);
|
||||
if (loc.Kind == LocationKind.SourceFile)
|
||||
if (!Context.Extractor.Mode.HasFlag(ExtractorMode.Standalone) && loc.Kind == LocationKind.SourceFile)
|
||||
yield return Assembly.CreateOutputAssembly(Context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,11 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
trapFile.preprocessor_directive_active(this, Symbol.IsActive);
|
||||
trapFile.preprocessor_directive_location(this, Context.CreateLocation(ReportingLocation));
|
||||
|
||||
var compilation = Compilation.Create(Context);
|
||||
trapFile.preprocessor_directive_compilation(this, compilation);
|
||||
if (!Context.Extractor.Mode.HasFlag(ExtractorMode.Standalone))
|
||||
{
|
||||
var compilation = Compilation.Create(Context);
|
||||
trapFile.preprocessor_directive_compilation(this, compilation);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void PopulatePreprocessor(TextWriter trapFile);
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
foreach (var l in GetLocations(Symbol))
|
||||
yield return Context.CreateLocation(l);
|
||||
|
||||
if (Symbol.DeclaringSyntaxReferences.Any())
|
||||
if (!Context.Extractor.Mode.HasFlag(ExtractorMode.Standalone) && Symbol.DeclaringSyntaxReferences.Any())
|
||||
yield return Assembly.CreateOutputAssembly(Context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ namespace Semmle.Extraction.CSharp
|
||||
protected Extraction.Extractor? extractor;
|
||||
protected CSharpCompilation? compilation;
|
||||
protected CommonOptions? options;
|
||||
private protected Entities.Compilation? compilationEntity;
|
||||
private IDisposable? compilationTrapFile;
|
||||
|
||||
private readonly object progressMutex = new object();
|
||||
|
||||
@@ -228,35 +226,8 @@ namespace Semmle.Extraction.CSharp
|
||||
}
|
||||
}
|
||||
|
||||
private void DoAnalyseCompilation()
|
||||
{
|
||||
try
|
||||
{
|
||||
var assemblyPath = extractor.OutputPath;
|
||||
var transformedAssemblyPath = PathTransformer.Transform(assemblyPath);
|
||||
var assembly = compilation.Assembly;
|
||||
var trapWriter = transformedAssemblyPath.CreateTrapWriter(Logger, options.TrapCompression, discardDuplicates: false);
|
||||
compilationTrapFile = trapWriter; // Dispose later
|
||||
var cx = new Context(extractor, compilation.Clone(), trapWriter, new AssemblyScope(assembly, assemblyPath), addAssemblyTrapPrefix);
|
||||
|
||||
compilationEntity = Entities.Compilation.Create(cx);
|
||||
}
|
||||
catch (Exception ex) // lgtm[cs/catch-of-all-exceptions]
|
||||
{
|
||||
Logger.Log(Severity.Error, " Unhandled exception analyzing {0}: {1}", "compilation", ex);
|
||||
}
|
||||
}
|
||||
|
||||
#nullable restore warnings
|
||||
|
||||
/// <summary>
|
||||
/// Extracts compilation-wide entities, such as compilations and compiler diagnostics.
|
||||
/// </summary>
|
||||
public void AnalyseCompilation()
|
||||
{
|
||||
extractionTasks.Add(() => DoAnalyseCompilation());
|
||||
}
|
||||
|
||||
private static bool FileIsUpToDate(string src, string dest)
|
||||
{
|
||||
return File.Exists(dest) &&
|
||||
@@ -304,8 +275,6 @@ namespace Semmle.Extraction.CSharp
|
||||
Logger.Log(Severity.Info, "EXTRACTION SUCCEEDED in {0}", stopWatch.Elapsed);
|
||||
|
||||
Logger.Dispose();
|
||||
|
||||
compilationTrapFile?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -302,6 +302,7 @@ namespace Semmle.Extraction.CSharp
|
||||
Func<Analyser, List<SyntaxTree>, IEnumerable<Action>> getSyntaxTreeTasks,
|
||||
Func<IEnumerable<SyntaxTree>, IEnumerable<MetadataReference>, CSharpCompilation> getCompilation,
|
||||
Action<CSharpCompilation, CommonOptions> initializeAnalyser,
|
||||
Action analyseCompilation,
|
||||
Action<Entities.PerformanceMetrics> logPerformance,
|
||||
Action postProcess)
|
||||
{
|
||||
@@ -331,7 +332,7 @@ namespace Semmle.Extraction.CSharp
|
||||
var compilation = getCompilation(syntaxTrees, references);
|
||||
|
||||
initializeAnalyser(compilation, options);
|
||||
analyser.AnalyseCompilation();
|
||||
analyseCompilation();
|
||||
analyser.AnalyseReferences();
|
||||
|
||||
foreach (var tree in compilation.SyntaxTrees)
|
||||
@@ -415,6 +416,7 @@ namespace Semmle.Extraction.CSharp
|
||||
);
|
||||
},
|
||||
(compilation, options) => analyser.EndInitialize(compilerArguments, options, compilation),
|
||||
() => analyser.AnalyseCompilation(),
|
||||
performance => analyser.LogPerformance(performance),
|
||||
() => { });
|
||||
}
|
||||
|
||||
@@ -9,8 +9,11 @@ using Semmle.Util.Logging;
|
||||
|
||||
namespace Semmle.Extraction.CSharp
|
||||
{
|
||||
public class TracingAnalyser : Analyser
|
||||
public class TracingAnalyser : Analyser, IDisposable
|
||||
{
|
||||
private Entities.Compilation? compilationEntity;
|
||||
private IDisposable? compilationTrapFile;
|
||||
|
||||
private bool init;
|
||||
|
||||
public TracingAnalyser(IProgressMonitor pm, ILogger logger, bool addAssemblyTrapPrefix, PathTransformer pathTransformer)
|
||||
@@ -52,6 +55,20 @@ namespace Semmle.Extraction.CSharp
|
||||
CompilationErrors += FilteredDiagnostics.Count();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
compilationTrapFile?.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts compilation-wide entities, such as compilations and compiler diagnostics.
|
||||
/// </summary>
|
||||
public void AnalyseCompilation()
|
||||
{
|
||||
extractionTasks.Add(() => DoAnalyseCompilation());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs information about the extractor, as well as the arguments to Roslyn.
|
||||
/// </summary>
|
||||
@@ -176,6 +193,25 @@ namespace Semmle.Extraction.CSharp
|
||||
}
|
||||
}
|
||||
|
||||
private void DoAnalyseCompilation()
|
||||
{
|
||||
try
|
||||
{
|
||||
var assemblyPath = ((TracingExtractor?)extractor).OutputPath;
|
||||
var transformedAssemblyPath = PathTransformer.Transform(assemblyPath);
|
||||
var assembly = compilation.Assembly;
|
||||
var trapWriter = transformedAssemblyPath.CreateTrapWriter(Logger, options.TrapCompression, discardDuplicates: false);
|
||||
compilationTrapFile = trapWriter; // Dispose later
|
||||
var cx = new Context(extractor, compilation.Clone(), trapWriter, new AssemblyScope(assembly, assemblyPath), addAssemblyTrapPrefix);
|
||||
|
||||
compilationEntity = Entities.Compilation.Create(cx);
|
||||
}
|
||||
catch (Exception ex) // lgtm[cs/catch-of-all-exceptions]
|
||||
{
|
||||
Logger.Log(Severity.Error, " Unhandled exception analyzing {0}: {1}", "compilation", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void LogPerformance(Entities.PerformanceMetrics p) => compilationEntity.PopulatePerformance(p);
|
||||
|
||||
#nullable restore warnings
|
||||
|
||||
@@ -82,6 +82,9 @@ namespace Semmle.Extraction.CSharp.Populators
|
||||
|
||||
public override void VisitAttributeList(AttributeListSyntax node)
|
||||
{
|
||||
if (Cx.Extractor.Mode.HasFlag(ExtractorMode.Standalone))
|
||||
return;
|
||||
|
||||
var outputAssembly = Assembly.CreateOutputAssembly(Cx);
|
||||
var kind = node.Target?.Identifier.Kind() switch
|
||||
{
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Semmle.Extraction.CSharp.StubGenerator\Semmle.Extraction.CSharp.StubGenerator.csproj" />
|
||||
<ProjectReference Include="..\Semmle.Extraction.CSharp.Standalone\Semmle.Extraction.CSharp.Standalone.csproj" />
|
||||
<ProjectReference Include="..\Semmle.Extraction.CSharp\Semmle.Extraction.CSharp.csproj" />
|
||||
<ProjectReference Include="..\Semmle.Extraction\Semmle.Extraction.csproj" />
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
using Xunit;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Semmle.Extraction.CSharp.StubGenerator;
|
||||
|
||||
namespace Semmle.Extraction.Tests;
|
||||
/// <summary>
|
||||
/// Tests for the stub generator.
|
||||
///
|
||||
/// These tests can be used to more easily step debug the stub generator SymbolVisitor.
|
||||
/// </summary>
|
||||
public class StubGeneratorTests
|
||||
{
|
||||
[Fact]
|
||||
public void StubGeneratorFieldTest()
|
||||
{
|
||||
// Setup
|
||||
const string source = @"
|
||||
public class MyTest {
|
||||
public static readonly int MyField1;
|
||||
public const string MyField2 = ""hello"";
|
||||
}
|
||||
";
|
||||
|
||||
// Execute
|
||||
var stub = GenerateStub(source);
|
||||
|
||||
// Verify
|
||||
const string expected = @"public class MyTest {
|
||||
public static readonly int MyField1;
|
||||
public const string MyField2 = default;
|
||||
}
|
||||
";
|
||||
Assert.Equal(expected, stub);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StubGeneratorMethodTest()
|
||||
{
|
||||
// Setup
|
||||
const string source = @"
|
||||
public class MyTest {
|
||||
public int M1(string arg1) { return 0;}
|
||||
}";
|
||||
|
||||
// Execute
|
||||
var stub = GenerateStub(source);
|
||||
|
||||
// Verify
|
||||
const string expected = @"public class MyTest {
|
||||
public int M1(string arg1) => throw null;
|
||||
}
|
||||
";
|
||||
Assert.Equal(expected, stub);
|
||||
}
|
||||
|
||||
private static string GenerateStub(string source)
|
||||
{
|
||||
var st = CSharpSyntaxTree.ParseText(source);
|
||||
var compilation = CSharpCompilation.Create(null, new[] { st });
|
||||
var sb = new StringBuilder();
|
||||
var visitor = new StubVisitor(new StringWriter(sb) { NewLine = "\n" }, new RelevantSymbolStub());
|
||||
compilation.GlobalNamespace.Accept(visitor);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private class RelevantSymbolStub : IRelevantSymbol
|
||||
{
|
||||
public bool IsRelevantNamedType(INamedTypeSymbol symbol) => true;
|
||||
public bool IsRelevantNamespace(INamespaceSymbol symbol) => true;
|
||||
}
|
||||
}
|
||||
@@ -9,16 +9,14 @@ namespace Semmle.Extraction
|
||||
public abstract class Extractor
|
||||
{
|
||||
public abstract ExtractorMode Mode { get; }
|
||||
public string OutputPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new extractor instance for one compilation unit.
|
||||
/// </summary>
|
||||
/// <param name="logger">The object used for logging.</param>
|
||||
/// <param name="pathTransformer">The object used for path transformations.</param>
|
||||
protected Extractor(string outputPath, ILogger logger, PathTransformer pathTransformer)
|
||||
protected Extractor(ILogger logger, PathTransformer pathTransformer)
|
||||
{
|
||||
OutputPath = outputPath;
|
||||
Logger = logger;
|
||||
PathTransformer = pathTransformer;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Semmle.Extraction
|
||||
/// </summary>
|
||||
/// <param name="logger">The object used for logging.</param>
|
||||
/// <param name="pathTransformer">The object used for path transformations.</param>
|
||||
public StandaloneExtractor(string outputPath, ILogger logger, PathTransformer pathTransformer, CommonOptions options) : base(outputPath, logger, pathTransformer)
|
||||
public StandaloneExtractor(ILogger logger, PathTransformer pathTransformer, CommonOptions options) : base(logger, pathTransformer)
|
||||
{
|
||||
Mode = ExtractorMode.Standalone;
|
||||
if (options.QlTest)
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Semmle.Extraction
|
||||
public class TracingExtractor : Extractor
|
||||
{
|
||||
public override ExtractorMode Mode { get; }
|
||||
public string OutputPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new extractor instance for one compilation unit.
|
||||
@@ -12,8 +13,9 @@ namespace Semmle.Extraction
|
||||
/// <param name="outputPath">The name of the output DLL/EXE, or null if not specified (standalone extraction).</param>
|
||||
/// <param name="logger">The object used for logging.</param>
|
||||
/// <param name="pathTransformer">The object used for path transformations.</param>
|
||||
public TracingExtractor(string outputPath, ILogger logger, PathTransformer pathTransformer, CommonOptions options) : base(outputPath, logger, pathTransformer)
|
||||
public TracingExtractor(string outputPath, ILogger logger, PathTransformer pathTransformer, CommonOptions options) : base(logger, pathTransformer)
|
||||
{
|
||||
OutputPath = outputPath;
|
||||
Mode = ExtractorMode.None;
|
||||
if (options.QlTest)
|
||||
{
|
||||
|
||||
@@ -143,37 +143,5 @@ namespace Semmle.Util
|
||||
}
|
||||
return nested;
|
||||
}
|
||||
|
||||
public static string GetTemporaryWorkingDirectory(out bool shouldCleanUp)
|
||||
{
|
||||
shouldCleanUp = false;
|
||||
var tempFolder = EnvironmentVariables.GetScratchDirectory();
|
||||
|
||||
if (string.IsNullOrEmpty(tempFolder))
|
||||
{
|
||||
var tempPath = Path.GetTempPath();
|
||||
var name = Guid.NewGuid().ToString("N").ToUpper();
|
||||
tempFolder = Path.Combine(tempPath, "GitHub", name);
|
||||
shouldCleanUp = true;
|
||||
}
|
||||
|
||||
return tempFolder;
|
||||
}
|
||||
|
||||
public static FileInfo CreateTemporaryFile(string extension, out bool shouldCleanUpContainingFolder)
|
||||
{
|
||||
var tempFolder = GetTemporaryWorkingDirectory(out shouldCleanUpContainingFolder);
|
||||
Directory.CreateDirectory(tempFolder);
|
||||
string outputPath;
|
||||
do
|
||||
{
|
||||
outputPath = Path.Combine(tempFolder, Path.GetRandomFileName() + extension);
|
||||
}
|
||||
while (File.Exists(outputPath));
|
||||
|
||||
File.Create(outputPath);
|
||||
|
||||
return new FileInfo(outputPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
## 1.6.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.6.4
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.6.3
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
## 1.6.4
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,3 +0,0 @@
|
||||
## 1.6.5
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 1.6.5
|
||||
lastReleaseVersion: 1.6.3
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
name: codeql/csharp-solorigate-all
|
||||
version: 1.7.0-dev
|
||||
version: 1.6.4-dev
|
||||
groups:
|
||||
- csharp
|
||||
- solorigate
|
||||
- csharp
|
||||
- solorigate
|
||||
library: true
|
||||
dependencies:
|
||||
codeql/csharp-all: ${workspace}
|
||||
codeql/csharp-all: ${workspace}
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
## 1.6.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.6.4
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.6.3
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
## 1.6.4
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,3 +0,0 @@
|
||||
## 1.6.5
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 1.6.5
|
||||
lastReleaseVersion: 1.6.3
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
name: codeql/csharp-solorigate-queries
|
||||
version: 1.7.0-dev
|
||||
version: 1.6.4-dev
|
||||
groups:
|
||||
- csharp
|
||||
- solorigate
|
||||
- csharp
|
||||
- solorigate
|
||||
defaultSuiteFile: codeql-suites/solorigate.qls
|
||||
dependencies:
|
||||
codeql/csharp-all: ${workspace}
|
||||
codeql/csharp-solorigate-all: ${workspace}
|
||||
codeql/csharp-all: ${workspace}
|
||||
codeql/csharp-solorigate-all: ${workspace}
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
## 0.7.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.7.4
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `--nostdlib` extractor option for the standalone extractor has been removed.
|
||||
|
||||
## 0.7.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
## 0.7.4
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `--nostdlib` extractor option for the standalone extractor has been removed.
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `--nostdlib` extractor option for the standalone extractor has been removed.
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.7.5
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.7.5
|
||||
lastReleaseVersion: 0.7.3
|
||||
|
||||
@@ -47,7 +47,6 @@ extensions:
|
||||
- ["System.Collections.Generic", "List<>", False, "FindAll", "(System.Predicate<T>)", "", "Argument[this].Element", "ReturnValue", "value", "manual"]
|
||||
- ["System.Collections.Generic", "List<>", False, "FindLast", "(System.Predicate<T>)", "", "Argument[this].Element", "Argument[0].Parameter[0]", "value", "manual"]
|
||||
- ["System.Collections.Generic", "List<>", False, "FindLast", "(System.Predicate<T>)", "", "Argument[this].Element", "ReturnValue", "value", "manual"]
|
||||
- ["System.Collections.Generic", "List<>", False, "ForEach", "(System.Action<T>)", "", "Argument[this].Element", "Argument[0].Parameter[0]", "value", "manual"]
|
||||
- ["System.Collections.Generic", "List<>", False, "GetEnumerator", "()", "", "Argument[this].Element", "ReturnValue.Property[System.Collections.Generic.List<>+Enumerator.Current]", "value", "manual"]
|
||||
- ["System.Collections.Generic", "List<>", False, "GetRange", "(System.Int32,System.Int32)", "", "Argument[this].Element", "ReturnValue.Element", "value", "manual"]
|
||||
- ["System.Collections.Generic", "List<>", False, "InsertRange", "(System.Int32,System.Collections.Generic.IEnumerable<T>)", "", "Argument[1].Element", "Argument[this].Element", "value", "manual"]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/csharp-all
|
||||
version: 0.8.0-dev
|
||||
version: 0.7.4-dev
|
||||
groups: csharp
|
||||
dbscheme: semmlecode.csharp.dbscheme
|
||||
extractor: csharp
|
||||
|
||||
@@ -29,7 +29,8 @@ private module Impl = Make<Input>;
|
||||
|
||||
class Container = Impl::Container;
|
||||
|
||||
class Folder = Impl::Folder;
|
||||
/** A folder. */
|
||||
class Folder extends Container, Impl::Folder { }
|
||||
|
||||
bindingset[flag]
|
||||
private predicate fileHasExtractionFlag(File f, int flag) {
|
||||
|
||||
@@ -418,20 +418,6 @@ Element interpretElement(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A callable where there exists a MaD sink model that applies to it.
|
||||
*/
|
||||
class SinkCallable extends Callable {
|
||||
SinkCallable() { sinkElement(this, _, _, _) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A callable where there exists a MaD source model that applies to it.
|
||||
*/
|
||||
class SourceCallable extends Callable {
|
||||
SourceCallable() { sourceElement(this, _, _, _) }
|
||||
}
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,6 @@ import csharp
|
||||
private import dotnet
|
||||
private import internal.FlowSummaryImpl as Impl
|
||||
private import internal.DataFlowDispatch as DataFlowDispatch
|
||||
private import Impl::Public::SummaryComponent as SummaryComponentInternal
|
||||
|
||||
class ParameterPosition = DataFlowDispatch::ParameterPosition;
|
||||
|
||||
@@ -19,6 +18,8 @@ class SummaryComponent = Impl::Public::SummaryComponent;
|
||||
|
||||
/** Provides predicates for constructing summary components. */
|
||||
module SummaryComponent {
|
||||
private import Impl::Public::SummaryComponent as SummaryComponentInternal
|
||||
|
||||
predicate content = SummaryComponentInternal::content/1;
|
||||
|
||||
/** Gets a summary component for parameter `i`. */
|
||||
@@ -154,45 +155,3 @@ private class RecordConstructorFlowRequiredSummaryComponentStack extends Require
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Provenance = Impl::Public::Provenance;
|
||||
|
||||
private import semmle.code.csharp.frameworks.system.linq.Expressions
|
||||
|
||||
private SummaryComponent delegateSelf() {
|
||||
exists(ArgumentPosition pos |
|
||||
result = SummaryComponentInternal::parameter(pos) and
|
||||
pos.isDelegateSelf()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate mayInvokeCallback(Callable c, int n) {
|
||||
c.getParameter(n).getType() instanceof SystemLinqExpressions::DelegateExtType and
|
||||
not c.fromSource()
|
||||
}
|
||||
|
||||
private class SummarizedCallableWithCallback extends SummarizedCallable {
|
||||
private int pos;
|
||||
|
||||
SummarizedCallableWithCallback() { mayInvokeCallback(this, pos) }
|
||||
|
||||
override predicate propagatesFlow(
|
||||
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
|
||||
) {
|
||||
input = SummaryComponentStack::argument(pos) and
|
||||
output = SummaryComponentStack::push(delegateSelf(), input) and
|
||||
preservesValue = true
|
||||
}
|
||||
|
||||
override predicate hasProvenance(Provenance provenance) { provenance = "hq-generated" }
|
||||
}
|
||||
|
||||
private class RequiredComponentStackForCallback extends RequiredSummaryComponentStack {
|
||||
override predicate required(SummaryComponent head, SummaryComponentStack tail) {
|
||||
exists(int pos |
|
||||
mayInvokeCallback(_, pos) and
|
||||
head = delegateSelf() and
|
||||
tail = SummaryComponentStack::argument(pos)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,15 +136,13 @@ private module Cached {
|
||||
newtype TParameterPosition =
|
||||
TPositionalParameterPosition(int i) { i = any(Parameter p).getPosition() } or
|
||||
TThisParameterPosition() or
|
||||
TImplicitCapturedParameterPosition(LocalScopeVariable v) { capturedWithFlowIn(v) } or
|
||||
TDelegateSelfParameterPosition()
|
||||
TImplicitCapturedParameterPosition(LocalScopeVariable v) { capturedWithFlowIn(v) }
|
||||
|
||||
cached
|
||||
newtype TArgumentPosition =
|
||||
TPositionalArgumentPosition(int i) { i = any(Parameter p).getPosition() } or
|
||||
TQualifierArgumentPosition() or
|
||||
TImplicitCapturedArgumentPosition(LocalScopeVariable v) { capturedWithFlowIn(v) } or
|
||||
TDelegateSelfArgumentPosition()
|
||||
TImplicitCapturedArgumentPosition(LocalScopeVariable v) { capturedWithFlowIn(v) }
|
||||
}
|
||||
|
||||
import Cached
|
||||
@@ -482,14 +480,6 @@ class ParameterPosition extends TParameterPosition {
|
||||
this = TImplicitCapturedParameterPosition(v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this position represents a reference to a delegate itself.
|
||||
*
|
||||
* Used for tracking flow through captured variables and for improving
|
||||
* delegate dispatch.
|
||||
*/
|
||||
predicate isDelegateSelf() { this = TDelegateSelfParameterPosition() }
|
||||
|
||||
/** Gets a textual representation of this position. */
|
||||
string toString() {
|
||||
result = "position " + this.getPosition()
|
||||
@@ -499,9 +489,6 @@ class ParameterPosition extends TParameterPosition {
|
||||
exists(LocalScopeVariable v |
|
||||
this.isImplicitCapturedParameterPosition(v) and result = "captured " + v
|
||||
)
|
||||
or
|
||||
this.isDelegateSelf() and
|
||||
result = "delegate self"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,14 +505,6 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
this = TImplicitCapturedArgumentPosition(v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this position represents a reference to a delegate itself.
|
||||
*
|
||||
* Used for tracking flow through captured variables and for improving
|
||||
* delegate dispatch.
|
||||
*/
|
||||
predicate isDelegateSelf() { this = TDelegateSelfArgumentPosition() }
|
||||
|
||||
/** Gets a textual representation of this position. */
|
||||
string toString() {
|
||||
result = "position " + this.getPosition()
|
||||
@@ -535,9 +514,6 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
exists(LocalScopeVariable v |
|
||||
this.isImplicitCapturedArgumentPosition(v) and result = "captured " + v
|
||||
)
|
||||
or
|
||||
this.isDelegateSelf() and
|
||||
result = "delegate self"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,6 +527,4 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
ppos.isImplicitCapturedParameterPosition(v) and
|
||||
apos.isImplicitCapturedArgumentPosition(v)
|
||||
)
|
||||
or
|
||||
ppos.isDelegateSelf() and apos.isDelegateSelf()
|
||||
}
|
||||
|
||||
@@ -297,10 +297,6 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -297,10 +297,6 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -297,10 +297,6 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -297,10 +297,6 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -297,10 +297,6 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -45,9 +45,9 @@ abstract class NodeImpl extends Node {
|
||||
abstract DotNet::Type getTypeImpl();
|
||||
|
||||
/** Gets the type of this node used for type pruning. */
|
||||
DataFlowType getDataFlowType() {
|
||||
Gvn::GvnType getDataFlowType() {
|
||||
forceCachingInSameStage() and
|
||||
exists(Type t0 | result.asGvnType() = Gvn::getGlobalValueNumber(t0) |
|
||||
exists(Type t0 | result = Gvn::getGlobalValueNumber(t0) |
|
||||
t0 = getCSharpType(this.getType())
|
||||
or
|
||||
not exists(getCSharpType(this.getType())) and
|
||||
@@ -557,8 +557,6 @@ module LocalFlow {
|
||||
exists(SsaImpl::getAReadAtNode(def, node2.(ExprNode).getControlFlowNode()))
|
||||
)
|
||||
or
|
||||
delegateCreationStep(node1, node2)
|
||||
or
|
||||
node1 =
|
||||
unique(FlowSummaryNode n1 |
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(n1.getSummaryNode(),
|
||||
@@ -795,16 +793,16 @@ private Type getCSharpType(DotNet::Type t) {
|
||||
result.matchesHandle(t)
|
||||
}
|
||||
|
||||
private class RelevantGvnType extends Gvn::GvnType {
|
||||
RelevantGvnType() { this = any(NodeImpl n).getDataFlowType().asGvnType() }
|
||||
private class RelevantDataFlowType extends DataFlowType {
|
||||
RelevantDataFlowType() { this = any(NodeImpl n).getDataFlowType() }
|
||||
}
|
||||
|
||||
/** A GVN type that is either a `DataFlowType` or unifiable with a `DataFlowType`. */
|
||||
private class DataFlowTypeOrUnifiable extends Gvn::GvnType {
|
||||
pragma[nomagic]
|
||||
DataFlowTypeOrUnifiable() {
|
||||
this instanceof RelevantGvnType or
|
||||
Gvn::unifiable(any(RelevantGvnType t), this)
|
||||
this instanceof RelevantDataFlowType or
|
||||
Gvn::unifiable(any(RelevantDataFlowType t), this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -815,7 +813,7 @@ private TypeParameter getATypeParameterSubType(DataFlowTypeOrUnifiable t) {
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private TypeParameter getATypeParameterSubTypeRestricted(RelevantGvnType t) {
|
||||
private TypeParameter getATypeParameterSubTypeRestricted(RelevantDataFlowType t) {
|
||||
result = getATypeParameterSubType(t)
|
||||
}
|
||||
|
||||
@@ -831,7 +829,7 @@ private Gvn::GvnType getANonTypeParameterSubType(DataFlowTypeOrUnifiable t) {
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private Gvn::GvnType getANonTypeParameterSubTypeRestricted(RelevantGvnType t) {
|
||||
private Gvn::GvnType getANonTypeParameterSubTypeRestricted(RelevantDataFlowType t) {
|
||||
result = getANonTypeParameterSubType(t)
|
||||
}
|
||||
|
||||
@@ -868,7 +866,6 @@ private module Cached {
|
||||
c = any(DataFlowCallable dfc).asCallable() and
|
||||
not c.(Modifiable).isStatic()
|
||||
} or
|
||||
TDelegateSelfReferenceNode(Callable c) { lambdaCreationExpr(_, c) } or
|
||||
TYieldReturnNode(ControlFlow::Nodes::ElementNode cfn) {
|
||||
any(Callable c).canYieldReturn(cfn.getAstNode())
|
||||
} or
|
||||
@@ -952,7 +949,7 @@ private module Cached {
|
||||
TSyntheticFieldApproxContent()
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate commonSubTypeGeneral(DataFlowTypeOrUnifiable t1, RelevantGvnType t2) {
|
||||
private predicate commonSubTypeGeneral(DataFlowTypeOrUnifiable t1, RelevantDataFlowType t2) {
|
||||
not t1 instanceof Gvn::TypeParameterGvnType and
|
||||
t1 = t2
|
||||
or
|
||||
@@ -966,20 +963,17 @@ private module Cached {
|
||||
* `t2` are allowed to be type parameters.
|
||||
*/
|
||||
cached
|
||||
predicate commonSubType(RelevantGvnType t1, RelevantGvnType t2) { commonSubTypeGeneral(t1, t2) }
|
||||
predicate commonSubType(RelevantDataFlowType t1, RelevantDataFlowType t2) {
|
||||
commonSubTypeGeneral(t1, t2)
|
||||
}
|
||||
|
||||
cached
|
||||
predicate commonSubTypeUnifiableLeft(RelevantGvnType t1, RelevantGvnType t2) {
|
||||
predicate commonSubTypeUnifiableLeft(RelevantDataFlowType t1, RelevantDataFlowType t2) {
|
||||
exists(Gvn::GvnType t |
|
||||
Gvn::unifiable(t1, t) and
|
||||
commonSubTypeGeneral(t, t2)
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
newtype TDataFlowType =
|
||||
TGvnDataFlowType(Gvn::GvnType t) or
|
||||
TDelegateDataFlowType(Callable lambda) { lambdaCreationExpr(_, lambda) }
|
||||
}
|
||||
|
||||
import Cached
|
||||
@@ -1125,37 +1119,6 @@ private module ParameterNodes {
|
||||
override string toStringImpl() { result = "this" }
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of a delegate itself at function entry, viewed as a node in a data
|
||||
* flow graph.
|
||||
*
|
||||
* This is used for improving lambda dispatch, and will eventually also be
|
||||
* used for tracking flow through captured variables.
|
||||
*/
|
||||
private class DelegateSelfReferenceNode extends ParameterNodeImpl, TDelegateSelfReferenceNode {
|
||||
private Callable callable;
|
||||
|
||||
DelegateSelfReferenceNode() { this = TDelegateSelfReferenceNode(callable) }
|
||||
|
||||
final Callable getCallable() { result = callable }
|
||||
|
||||
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
||||
callable = c.asCallable() and pos.isDelegateSelf()
|
||||
}
|
||||
|
||||
override ControlFlow::Node getControlFlowNodeImpl() { none() }
|
||||
|
||||
override DataFlowCallable getEnclosingCallableImpl() { result.asCallable() = callable }
|
||||
|
||||
override Location getLocationImpl() { result = callable.getLocation() }
|
||||
|
||||
override DotNet::Type getTypeImpl() { none() }
|
||||
|
||||
override DataFlowType getDataFlowType() { callable = result.asDelegate() }
|
||||
|
||||
override string toStringImpl() { result = "delegate self in " + callable }
|
||||
}
|
||||
|
||||
/** An implicit entry definition for a captured variable. */
|
||||
class SsaCapturedEntryDefinition extends Ssa::ImplicitEntryDefinition {
|
||||
private LocalScopeVariable v;
|
||||
@@ -1269,18 +1232,6 @@ private module ArgumentNodes {
|
||||
}
|
||||
}
|
||||
|
||||
/** A data-flow node that represents a delegate passed into itself. */
|
||||
class DelegateSelfArgumentNode extends ArgumentNodeImpl {
|
||||
private DataFlowCall call_;
|
||||
|
||||
DelegateSelfArgumentNode() { lambdaCallExpr(call_, this) }
|
||||
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
call = call_ and
|
||||
pos.isDelegateSelf()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of a captured variable as an implicit argument of a call, viewed
|
||||
* as a node in a data flow graph.
|
||||
@@ -2034,92 +1985,41 @@ predicate isUnreachableInCall(Node n, DataFlowCall call) {
|
||||
* For example, `Func<T, int>` and `Func<S, int>` are mapped to the same
|
||||
* `DataFlowType`, while `Func<T, int>` and `Func<string, int>` are not, because
|
||||
* `string` is not a type parameter.
|
||||
*
|
||||
* For delegates, we use the delegate itself instead of its type, in order to
|
||||
* improve dispatch.
|
||||
*/
|
||||
class DataFlowType extends TDataFlowType {
|
||||
Gvn::GvnType asGvnType() { this = TGvnDataFlowType(result) }
|
||||
|
||||
Callable asDelegate() { this = TDelegateDataFlowType(result) }
|
||||
|
||||
/**
|
||||
* Gets an expression that creates a delegate of this type.
|
||||
*
|
||||
* For methods used as method groups in calls there can be multiple
|
||||
* creations associated with the same type.
|
||||
*/
|
||||
Expr getADelegateCreation() {
|
||||
exists(Callable callable |
|
||||
lambdaCreationExpr(result, callable) and
|
||||
this = TDelegateDataFlowType(callable)
|
||||
)
|
||||
}
|
||||
|
||||
final string toString() {
|
||||
result = this.asGvnType().toString()
|
||||
or
|
||||
result = this.asDelegate().toString()
|
||||
}
|
||||
}
|
||||
class DataFlowType = Gvn::GvnType;
|
||||
|
||||
/** Gets the type of `n` used for type pruning. */
|
||||
DataFlowType getNodeType(Node n) {
|
||||
result = n.(NodeImpl).getDataFlowType() and
|
||||
not lambdaCreation(n, _, _) and
|
||||
not delegateCreationStep(_, n)
|
||||
or
|
||||
exists(Node arg |
|
||||
delegateCreationStep(arg, n) and
|
||||
result = getNodeType(arg)
|
||||
)
|
||||
or
|
||||
n.asExpr() = result.getADelegateCreation()
|
||||
}
|
||||
Gvn::GvnType getNodeType(Node n) { result = n.(NodeImpl).getDataFlowType() }
|
||||
|
||||
/** Gets a string representation of a `DataFlowType`. */
|
||||
string ppReprType(DataFlowType t) { result = t.toString() }
|
||||
|
||||
private class DataFlowNullType extends Gvn::GvnType {
|
||||
private class DataFlowNullType extends DataFlowType {
|
||||
DataFlowNullType() { this = Gvn::getGlobalValueNumber(any(NullType nt)) }
|
||||
|
||||
pragma[noinline]
|
||||
predicate isConvertibleTo(Gvn::GvnType t) {
|
||||
predicate isConvertibleTo(DataFlowType t) {
|
||||
defaultNullConversion(_, any(Type t0 | t = Gvn::getGlobalValueNumber(t0)))
|
||||
}
|
||||
}
|
||||
|
||||
private class GvnUnknownType extends Gvn::GvnType {
|
||||
GvnUnknownType() { this = Gvn::getGlobalValueNumber(any(UnknownType ut)) }
|
||||
private class DataFlowUnknownType extends DataFlowType {
|
||||
DataFlowUnknownType() { this = Gvn::getGlobalValueNumber(any(UnknownType ut)) }
|
||||
}
|
||||
|
||||
private predicate uselessTypebound(DataFlowType t) {
|
||||
t instanceof DataFlowUnknownType or
|
||||
t instanceof Gvn::TypeParameterGvnType
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate uselessTypebound(DataFlowType dt) {
|
||||
dt.asGvnType() =
|
||||
any(Gvn::GvnType t |
|
||||
t instanceof GvnUnknownType or
|
||||
t instanceof Gvn::TypeParameterGvnType
|
||||
)
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
private predicate compatibleTypesDelegateLeft(DataFlowType dt1, DataFlowType dt2) {
|
||||
exists(Gvn::GvnType t1, Gvn::GvnType t2 |
|
||||
t1 = exprNode(dt1.getADelegateCreation()).(NodeImpl).getDataFlowType().asGvnType() and
|
||||
t2 = dt2.asGvnType()
|
||||
|
|
||||
commonSubType(t1, t2)
|
||||
or
|
||||
commonSubTypeUnifiableLeft(t1, t2)
|
||||
or
|
||||
commonSubTypeUnifiableLeft(t2, t1)
|
||||
or
|
||||
t2.(DataFlowNullType).isConvertibleTo(t1)
|
||||
or
|
||||
t2 instanceof Gvn::TypeParameterGvnType
|
||||
or
|
||||
t2 instanceof GvnUnknownType
|
||||
)
|
||||
predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) {
|
||||
t1 != t2 and
|
||||
t1 = getANonTypeParameterSubTypeRestricted(t2)
|
||||
or
|
||||
t1 instanceof RelevantDataFlowType and
|
||||
not uselessTypebound(t1) and
|
||||
uselessTypebound(t2)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2127,47 +2027,24 @@ private predicate compatibleTypesDelegateLeft(DataFlowType dt1, DataFlowType dt2
|
||||
* a node of type `t1` to a node of type `t2`.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate compatibleTypes(DataFlowType dt1, DataFlowType dt2) {
|
||||
exists(Gvn::GvnType t1, Gvn::GvnType t2 |
|
||||
t1 = dt1.asGvnType() and
|
||||
t2 = dt2.asGvnType()
|
||||
|
|
||||
commonSubType(t1, t2)
|
||||
or
|
||||
commonSubTypeUnifiableLeft(t1, t2)
|
||||
or
|
||||
commonSubTypeUnifiableLeft(t2, t1)
|
||||
or
|
||||
t1.(DataFlowNullType).isConvertibleTo(t2)
|
||||
or
|
||||
t2.(DataFlowNullType).isConvertibleTo(t1)
|
||||
or
|
||||
t1 instanceof Gvn::TypeParameterGvnType
|
||||
or
|
||||
t2 instanceof Gvn::TypeParameterGvnType
|
||||
or
|
||||
t1 instanceof GvnUnknownType
|
||||
or
|
||||
t2 instanceof GvnUnknownType
|
||||
)
|
||||
predicate compatibleTypes(DataFlowType t1, DataFlowType t2) {
|
||||
commonSubType(t1, t2)
|
||||
or
|
||||
compatibleTypesDelegateLeft(dt1, dt2)
|
||||
commonSubTypeUnifiableLeft(t1, t2)
|
||||
or
|
||||
compatibleTypesDelegateLeft(dt2, dt1)
|
||||
commonSubTypeUnifiableLeft(t2, t1)
|
||||
or
|
||||
dt1.asDelegate() = dt2.asDelegate()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) {
|
||||
t1 != t2 and
|
||||
t1.asGvnType() = getANonTypeParameterSubTypeRestricted(t2.asGvnType())
|
||||
t1.(DataFlowNullType).isConvertibleTo(t2)
|
||||
or
|
||||
t1.asGvnType() instanceof RelevantGvnType and
|
||||
not uselessTypebound(t1) and
|
||||
uselessTypebound(t2)
|
||||
t2.(DataFlowNullType).isConvertibleTo(t1)
|
||||
or
|
||||
compatibleTypesDelegateLeft(t1, t2)
|
||||
t1 instanceof Gvn::TypeParameterGvnType
|
||||
or
|
||||
t2 instanceof Gvn::TypeParameterGvnType
|
||||
or
|
||||
t1 instanceof DataFlowUnknownType
|
||||
or
|
||||
t2 instanceof DataFlowUnknownType
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2339,20 +2216,17 @@ int accessPathLimit() { result = 5 }
|
||||
*/
|
||||
predicate forceHighPrecision(Content c) { c instanceof ElementContent }
|
||||
|
||||
private predicate lambdaCreationExpr(Expr creation, Callable c) {
|
||||
c =
|
||||
[
|
||||
creation.(AnonymousFunctionExpr),
|
||||
creation.(CallableAccess).getTarget().getUnboundDeclaration(),
|
||||
creation.(AddressOfExpr).getOperand().(CallableAccess).getTarget().getUnboundDeclaration()
|
||||
]
|
||||
}
|
||||
|
||||
class LambdaCallKind = Unit;
|
||||
|
||||
/** Holds if `creation` is an expression that creates a delegate for `c`. */
|
||||
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) {
|
||||
lambdaCreationExpr(creation.asExpr(), c.asCallable()) and
|
||||
exists(Expr e | e = creation.asExpr() |
|
||||
c.asCallable() =
|
||||
[
|
||||
e.(AnonymousFunctionExpr), e.(CallableAccess).getTarget().getUnboundDeclaration(),
|
||||
e.(AddressOfExpr).getOperand().(CallableAccess).getTarget().getUnboundDeclaration()
|
||||
]
|
||||
) and
|
||||
exists(kind)
|
||||
}
|
||||
|
||||
@@ -2374,29 +2248,19 @@ private class LambdaConfiguration extends ControlFlowReachabilityConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
private predicate lambdaCallExpr(DataFlowCall call, ExprNode receiver) {
|
||||
exists(LambdaConfiguration x, DelegateLikeCall dc |
|
||||
x.hasExprPath(dc.getExpr(), receiver.getControlFlowNode(), dc, call.getControlFlowNode())
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `call` is a lambda call where `receiver` is the lambda expression. */
|
||||
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
|
||||
(
|
||||
lambdaCallExpr(call, receiver)
|
||||
exists(LambdaConfiguration x, DelegateLikeCall dc |
|
||||
x.hasExprPath(dc.getExpr(), receiver.(ExprNode).getControlFlowNode(), dc,
|
||||
call.getControlFlowNode())
|
||||
)
|
||||
or
|
||||
receiver.(FlowSummaryNode).getSummaryNode() = call.(SummaryCall).getReceiver()
|
||||
) and
|
||||
exists(kind)
|
||||
}
|
||||
|
||||
private predicate delegateCreationStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(LambdaConfiguration x, DelegateCreation dc |
|
||||
x.hasExprPath(dc.getArgument(), nodeFrom.(ExprNode).getControlFlowNode(), dc,
|
||||
nodeTo.(ExprNode).getControlFlowNode())
|
||||
)
|
||||
}
|
||||
|
||||
/** Extra data-flow steps needed for lambda flow analysis. */
|
||||
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) {
|
||||
exists(SsaImpl::DefinitionExt def |
|
||||
@@ -2405,8 +2269,11 @@ predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preserves
|
||||
preservesValue = true
|
||||
)
|
||||
or
|
||||
delegateCreationStep(nodeFrom, nodeTo) and
|
||||
preservesValue = true
|
||||
exists(LambdaConfiguration x, DelegateCreation dc |
|
||||
x.hasExprPath(dc.getArgument(), nodeFrom.(ExprNode).getControlFlowNode(), dc,
|
||||
nodeTo.(ExprNode).getControlFlowNode()) and
|
||||
preservesValue = false
|
||||
)
|
||||
or
|
||||
exists(AddEventExpr aee |
|
||||
nodeFrom.asExpr() = aee.getRValue() and
|
||||
|
||||
@@ -35,14 +35,14 @@ private module SyntheticGlobals {
|
||||
DataFlowCallable inject(SummarizedCallable c) { result.asSummarizedCallable() = c }
|
||||
|
||||
/** Gets the parameter position of the instance parameter. */
|
||||
ArgumentPosition callbackSelfParameterPosition() { result.isDelegateSelf() }
|
||||
ArgumentPosition callbackSelfParameterPosition() { none() } // disables implicit summary flow to `this` for callbacks
|
||||
|
||||
/** Gets the synthesized data-flow call for `receiver`. */
|
||||
SummaryCall summaryDataFlowCall(SummaryNode receiver) { receiver = result.getReceiver() }
|
||||
|
||||
/** Gets the type of content `c`. */
|
||||
DataFlowType getContentType(Content c) {
|
||||
exists(Type t | result.asGvnType() = Gvn::getGlobalValueNumber(t) |
|
||||
exists(Type t | result = Gvn::getGlobalValueNumber(t) |
|
||||
t = c.(FieldContent).getField().getType()
|
||||
or
|
||||
t = c.(PropertyContent).getProperty().getType()
|
||||
@@ -56,7 +56,7 @@ DataFlowType getContentType(Content c) {
|
||||
|
||||
/** Gets the type of the parameter at the given position. */
|
||||
DataFlowType getParameterType(SummarizedCallable c, ParameterPosition pos) {
|
||||
exists(Type t | result.asGvnType() = Gvn::getGlobalValueNumber(t) |
|
||||
exists(Type t | result = Gvn::getGlobalValueNumber(t) |
|
||||
exists(int i |
|
||||
pos.getPosition() = i and
|
||||
t = c.getParameter(i).getType()
|
||||
@@ -69,7 +69,7 @@ DataFlowType getParameterType(SummarizedCallable c, ParameterPosition pos) {
|
||||
|
||||
/** Gets the return type of kind `rk` for callable `c`. */
|
||||
DataFlowType getReturnType(DotNet::Callable c, ReturnKind rk) {
|
||||
exists(Type t | result.asGvnType() = Gvn::getGlobalValueNumber(t) |
|
||||
exists(Type t | result = Gvn::getGlobalValueNumber(t) |
|
||||
rk instanceof NormalReturnKind and
|
||||
(
|
||||
t = c.(Constructor).getDeclaringType()
|
||||
@@ -88,13 +88,10 @@ DataFlowType getReturnType(DotNet::Callable c, ReturnKind rk) {
|
||||
*/
|
||||
DataFlowType getCallbackParameterType(DataFlowType t, ArgumentPosition pos) {
|
||||
exists(SystemLinqExpressions::DelegateExtType dt |
|
||||
t.asGvnType() = Gvn::getGlobalValueNumber(dt) and
|
||||
result.asGvnType() =
|
||||
t = Gvn::getGlobalValueNumber(dt) and
|
||||
result =
|
||||
Gvn::getGlobalValueNumber(dt.getDelegateType().getParameter(pos.getPosition()).getType())
|
||||
)
|
||||
or
|
||||
pos.isDelegateSelf() and
|
||||
result = t
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,15 +101,15 @@ DataFlowType getCallbackParameterType(DataFlowType t, ArgumentPosition pos) {
|
||||
DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) {
|
||||
rk instanceof NormalReturnKind and
|
||||
exists(SystemLinqExpressions::DelegateExtType dt |
|
||||
t.asGvnType() = Gvn::getGlobalValueNumber(dt) and
|
||||
result.asGvnType() = Gvn::getGlobalValueNumber(dt.getDelegateType().getReturnType())
|
||||
t = Gvn::getGlobalValueNumber(dt) and
|
||||
result = Gvn::getGlobalValueNumber(dt.getDelegateType().getReturnType())
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the type of synthetic global `sg`. */
|
||||
DataFlowType getSyntheticGlobalType(SummaryComponent::SyntheticGlobal sg) {
|
||||
exists(sg) and
|
||||
result.asGvnType() = Gvn::getGlobalValueNumber(any(ObjectType t))
|
||||
result = Gvn::getGlobalValueNumber(any(ObjectType t))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,9 +223,6 @@ string getParameterPosition(ParameterPosition pos) {
|
||||
or
|
||||
pos.isThisParameter() and
|
||||
result = "this"
|
||||
or
|
||||
pos.isDelegateSelf() and
|
||||
result = "delegate-self"
|
||||
}
|
||||
|
||||
/** Gets the textual representation of an argument position in the format used for flow summaries. */
|
||||
@@ -237,9 +231,6 @@ string getArgumentPosition(ArgumentPosition pos) {
|
||||
or
|
||||
pos.isQualifier() and
|
||||
result = "this"
|
||||
or
|
||||
pos.isDelegateSelf() and
|
||||
result = "delegate-self"
|
||||
}
|
||||
|
||||
/** Holds if input specification component `c` needs a reference. */
|
||||
@@ -321,9 +312,6 @@ ArgumentPosition parseParamBody(string s) {
|
||||
or
|
||||
s = "this" and
|
||||
result.isQualifier()
|
||||
or
|
||||
s = "delegate-self" and
|
||||
result.isDelegateSelf()
|
||||
}
|
||||
|
||||
/** Gets the parameter position obtained by parsing `X` in `Argument[X]`. */
|
||||
@@ -333,7 +321,4 @@ ParameterPosition parseArgBody(string s) {
|
||||
or
|
||||
s = "this" and
|
||||
result.isThisParameter()
|
||||
or
|
||||
s = "delegate-self" and
|
||||
result.isDelegateSelf()
|
||||
}
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
/** Common definitions for queries checking for access control measures on action methods. */
|
||||
|
||||
import csharp
|
||||
import semmle.code.csharp.frameworks.microsoft.AspNetCore
|
||||
import semmle.code.csharp.frameworks.system.web.UI
|
||||
|
||||
/** A method representing an action for a web endpoint. */
|
||||
abstract class ActionMethod extends Method {
|
||||
/**
|
||||
* Gets a string that can indicate what this method does to determine if it should have an auth check;
|
||||
* such as its method name, class name, or file path.
|
||||
*/
|
||||
string getADescription() {
|
||||
result = [this.getName(), this.getDeclaringType().getBaseClass*().getName(), this.getARoute()]
|
||||
}
|
||||
|
||||
/** Holds if this method may represent a stateful action such as editing or deleting */
|
||||
predicate isEdit() {
|
||||
exists(string str |
|
||||
str =
|
||||
this.getADescription()
|
||||
// separate camelCase words
|
||||
.regexpReplaceAll("([a-z])([A-Z])", "$1_$2") and
|
||||
str.regexpMatch("(?i).*(edit|delete|modify|change).*") and
|
||||
not str.regexpMatch("(?i).*(on_?change|changed).*")
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if this method may be intended to be restricted to admin users */
|
||||
predicate isAdmin() {
|
||||
this.getADescription()
|
||||
// separate camelCase words
|
||||
.regexpReplaceAll("([a-z])([A-Z])", "$1_$2")
|
||||
.regexpMatch("(?i).*(admin|superuser).*")
|
||||
}
|
||||
|
||||
/** Gets a callable for which if it contains an auth check, this method should be considered authenticated. */
|
||||
Callable getAnAuthorizingCallable() { result = this }
|
||||
|
||||
/**
|
||||
* Gets a possible url route that could refer to this action,
|
||||
* which would be covered by `<location>` configurations specifying a prefix of it.
|
||||
*/
|
||||
string getARoute() { result = this.getDeclaringType().getFile().getRelativePath() }
|
||||
}
|
||||
|
||||
/** An action method in the MVC framework. */
|
||||
private class MvcActionMethod extends ActionMethod {
|
||||
MvcActionMethod() { this = any(MicrosoftAspNetCoreMvcController c).getAnActionMethod() }
|
||||
}
|
||||
|
||||
/** An action method on a subclass of `System.Web.UI.Page`. */
|
||||
private class WebFormActionMethod extends ActionMethod {
|
||||
WebFormActionMethod() {
|
||||
this.getDeclaringType().getBaseClass+() instanceof SystemWebUIPageClass and
|
||||
this.getAParameter().getType().getName().matches("%EventArgs")
|
||||
}
|
||||
|
||||
override Callable getAnAuthorizingCallable() {
|
||||
result = super.getAnAuthorizingCallable()
|
||||
or
|
||||
pageLoad(result, this.getDeclaringType())
|
||||
}
|
||||
|
||||
override string getARoute() {
|
||||
exists(string physicalRoute | physicalRoute = super.getARoute() |
|
||||
result = physicalRoute
|
||||
or
|
||||
exists(string absolutePhysical |
|
||||
virtualRouteMapping(result, absolutePhysical) and
|
||||
physicalRouteMatches(absolutePhysical, physicalRoute)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate pageLoad(Callable c, Type decl) {
|
||||
c.getName() = "Page_Load" and
|
||||
decl = c.getDeclaringType()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `virtualRoute` is a URL path
|
||||
* that can map to the corresponding `physicalRoute` filepath
|
||||
* through a call to `MapPageRoute`
|
||||
*/
|
||||
private predicate virtualRouteMapping(string virtualRoute, string physicalRoute) {
|
||||
exists(MethodCall mapPageRouteCall, StringLiteral virtualLit, StringLiteral physicalLit |
|
||||
mapPageRouteCall
|
||||
.getTarget()
|
||||
.hasQualifiedName("System.Web.Routing", "RouteCollection", "MapPageRoute") and
|
||||
virtualLit = mapPageRouteCall.getArgument(1) and
|
||||
physicalLit = mapPageRouteCall.getArgument(2) and
|
||||
virtualLit.getValue() = virtualRoute and
|
||||
physicalLit.getValue() = physicalRoute
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the filepath `route` can refer to `actual` after expanding a '~". */
|
||||
bindingset[route, actual]
|
||||
private predicate physicalRouteMatches(string route, string actual) {
|
||||
route = actual
|
||||
or
|
||||
route.charAt(0) = "~" and
|
||||
exists(string dir | actual = dir + route.suffix(1) + ".cs")
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/** Definitions for the Insecure Direct Object Reference query */
|
||||
|
||||
import csharp
|
||||
import semmle.code.csharp.dataflow.flowsources.Remote
|
||||
import ActionMethods
|
||||
|
||||
/**
|
||||
* Holds if `m` is a method that may require checks
|
||||
* against the current user to modify a particular resource.
|
||||
*/
|
||||
// We exclude admin methods as it may be expected that an admin user should be able to modify any resource.
|
||||
// Other queries check that there are authorization checks in place for admin methods.
|
||||
private predicate needsChecks(ActionMethod m) { m.isEdit() and not m.isAdmin() }
|
||||
|
||||
/**
|
||||
* Holds if `m` has a parameter or access a remote flow source
|
||||
* that may indicate that it's used as the ID for some resource
|
||||
*/
|
||||
private predicate hasIdParameter(ActionMethod m) {
|
||||
exists(RemoteFlowSource src | src.getEnclosingCallable() = m |
|
||||
src.asParameter().getName().toLowerCase().matches(["%id", "%idx"])
|
||||
or
|
||||
// handle cases like `Request.QueryString["Id"]`
|
||||
exists(StringLiteral idStr, IndexerCall idx |
|
||||
idStr.getValue().toLowerCase().matches(["%id", "%idx"]) and
|
||||
TaintTracking::localTaint(src, DataFlow::exprNode(idx.getQualifier())) and
|
||||
DataFlow::localExprFlow(idStr, idx.getArgument(0))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate authorizingCallable(Callable c) {
|
||||
exists(string name | name = c.getName().toLowerCase() |
|
||||
name.matches(["%user%", "%session%"]) and
|
||||
not name.matches("%get%by%") // methods like `getUserById` or `getXByUsername` aren't likely to be referring to the current user
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `m` at some point in its call graph may make some kind of check against the current user. */
|
||||
private predicate checksUser(ActionMethod m) {
|
||||
exists(Callable c |
|
||||
authorizingCallable(c) and
|
||||
callsPlus(m, c)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate calls(Callable c1, Callable c2) { c1.calls(c2) }
|
||||
|
||||
private predicate callsPlus(Callable c1, Callable c2) = fastTC(calls/2)(c1, c2)
|
||||
|
||||
/** Holds if `m`, its containing class, or a parent class has an attribute that extends `AuthorizeAttribute` */
|
||||
private predicate hasAuthorizeAttribute(ActionMethod m) {
|
||||
exists(Attribute attr |
|
||||
attr.getType()
|
||||
.getABaseType*()
|
||||
.hasQualifiedName([
|
||||
"Microsoft.AspNetCore.Authorization", "System.Web.Mvc", "System.Web.Http"
|
||||
], "AuthorizeAttribute")
|
||||
|
|
||||
attr = m.getOverridee*().getAnAttribute() or
|
||||
attr = m.getDeclaringType().getABaseType*().getAnAttribute()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `m`, its containing class, or a parent class has an attribute that extends `AllowAnonymousAttribute` */
|
||||
private predicate hasAllowAnonymousAttribute(ActionMethod m) {
|
||||
exists(Attribute attr |
|
||||
attr.getType()
|
||||
.getABaseType*()
|
||||
.hasQualifiedName([
|
||||
"Microsoft.AspNetCore.Authorization", "System.Web.Mvc", "System.Web.Http"
|
||||
], "AllowAnonymousAttribute")
|
||||
|
|
||||
attr = m.getOverridee*().getAnAttribute() or
|
||||
attr = m.getDeclaringType().getABaseType*().getAnAttribute()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `m` is authorized via an `Authorize` attribute */
|
||||
private predicate isAuthorizedViaAttribute(ActionMethod m) {
|
||||
hasAuthorizeAttribute(m) and
|
||||
not hasAllowAnonymousAttribute(m)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `m` is a method that modifies a particular resource based on
|
||||
* an ID provided by user input, but does not check anything based on the current user
|
||||
* to determine if they should modify this resource.
|
||||
*/
|
||||
predicate hasInsecureDirectObjectReference(ActionMethod m) {
|
||||
needsChecks(m) and
|
||||
hasIdParameter(m) and
|
||||
not checksUser(m) and
|
||||
not isAuthorizedViaAttribute(m) and
|
||||
exists(m.getBody().getAChildStmt())
|
||||
}
|
||||
@@ -4,10 +4,96 @@ import csharp
|
||||
import semmle.code.csharp.frameworks.microsoft.AspNetCore
|
||||
import semmle.code.csharp.frameworks.system.web.UI
|
||||
import semmle.code.asp.WebConfig
|
||||
import ActionMethods
|
||||
|
||||
/** Holds if the method `m` may need an authorization check. */
|
||||
predicate needsAuth(ActionMethod m) { m.isEdit() or m.isAdmin() }
|
||||
/** A method representing an action for a web endpoint. */
|
||||
abstract class ActionMethod extends Method {
|
||||
/**
|
||||
* Gets a string that can indicate what this method does to determine if it should have an auth check;
|
||||
* such as its method name, class name, or file path.
|
||||
*/
|
||||
string getADescription() {
|
||||
result =
|
||||
[
|
||||
this.getName(), this.getDeclaringType().getBaseClass*().getName(),
|
||||
this.getDeclaringType().getFile().getRelativePath()
|
||||
]
|
||||
}
|
||||
|
||||
/** Holds if this method may need an authorization check. */
|
||||
predicate needsAuth() {
|
||||
this.getADescription()
|
||||
.regexpReplaceAll("([a-z])([A-Z])", "$1_$2")
|
||||
// separate camelCase words
|
||||
.toLowerCase()
|
||||
.regexpMatch(".*(edit|delete|modify|admin|superuser).*")
|
||||
}
|
||||
|
||||
/** Gets a callable for which if it contains an auth check, this method should be considered authenticated. */
|
||||
Callable getAnAuthorizingCallable() { result = this }
|
||||
|
||||
/**
|
||||
* Gets a possible url route that could refer to this action,
|
||||
* which would be covered by `<location>` configurations specifying a prefix of it.
|
||||
*/
|
||||
string getARoute() { result = this.getDeclaringType().getFile().getRelativePath() }
|
||||
}
|
||||
|
||||
/** An action method in the MVC framework. */
|
||||
private class MvcActionMethod extends ActionMethod {
|
||||
MvcActionMethod() { this = any(MicrosoftAspNetCoreMvcController c).getAnActionMethod() }
|
||||
}
|
||||
|
||||
/** An action method on a subclass of `System.Web.UI.Page`. */
|
||||
private class WebFormActionMethod extends ActionMethod {
|
||||
WebFormActionMethod() {
|
||||
this.getDeclaringType().getBaseClass+() instanceof SystemWebUIPageClass and
|
||||
this.getAParameter().getType().getName().matches("%EventArgs")
|
||||
}
|
||||
|
||||
override Callable getAnAuthorizingCallable() {
|
||||
result = super.getAnAuthorizingCallable()
|
||||
or
|
||||
result.getDeclaringType() = this.getDeclaringType() and
|
||||
result.getName() = "Page_Load"
|
||||
}
|
||||
|
||||
override string getARoute() {
|
||||
exists(string physicalRoute | physicalRoute = super.getARoute() |
|
||||
result = physicalRoute
|
||||
or
|
||||
exists(string absolutePhysical |
|
||||
virtualRouteMapping(result, absolutePhysical) and
|
||||
physicalRouteMatches(absolutePhysical, physicalRoute)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `virtualRoute` is a URL path
|
||||
* that can map to the corresponding `physicalRoute` filepath
|
||||
* through a call to `MapPageRoute`
|
||||
*/
|
||||
private predicate virtualRouteMapping(string virtualRoute, string physicalRoute) {
|
||||
exists(MethodCall mapPageRouteCall, StringLiteral virtualLit, StringLiteral physicalLit |
|
||||
mapPageRouteCall
|
||||
.getTarget()
|
||||
.hasQualifiedName("System.Web.Routing", "RouteCollection", "MapPageRoute") and
|
||||
virtualLit = mapPageRouteCall.getArgument(1) and
|
||||
physicalLit = mapPageRouteCall.getArgument(2) and
|
||||
virtualLit.getValue() = virtualRoute and
|
||||
physicalLit.getValue() = physicalRoute
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the filepath `route` can refer to `actual` after expanding a '~". */
|
||||
bindingset[route, actual]
|
||||
private predicate physicalRouteMatches(string route, string actual) {
|
||||
route = actual
|
||||
or
|
||||
route.charAt(0) = "~" and
|
||||
exists(string dir | actual = dir + route.suffix(1) + ".cs")
|
||||
}
|
||||
|
||||
/** An expression that indicates that some authorization/authentication check is being performed. */
|
||||
class AuthExpr extends Expr {
|
||||
@@ -28,7 +114,7 @@ class AuthExpr extends Expr {
|
||||
|
||||
/** Holds if `m` is a method that should have an auth check, and does indeed have one. */
|
||||
predicate hasAuthViaCode(ActionMethod m) {
|
||||
needsAuth(m) and
|
||||
m.needsAuth() and
|
||||
exists(Callable caller, AuthExpr auth |
|
||||
m.getAnAuthorizingCallable().calls*(caller) and
|
||||
auth.getEnclosingCallable() = caller
|
||||
@@ -89,7 +175,7 @@ predicate hasAuthViaAttribute(ActionMethod m) {
|
||||
|
||||
/** Holds if `m` is a method that should have an auth check, but is missing it. */
|
||||
predicate missingAuth(ActionMethod m) {
|
||||
needsAuth(m) and
|
||||
m.needsAuth() and
|
||||
not hasAuthViaCode(m) and
|
||||
not hasAuthViaXml(m) and
|
||||
not hasAuthViaAttribute(m) and
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
## 0.7.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.7.4
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.7.3
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Resources like comments or user profiles can be accessed and modified through an action method. To target a certain resource, the action method accepts an ID parameter pointing to that specific resource. If the methods do not check that the current user is authorized to access the specified resource, an attacker can access a resource by guessing or otherwise determining the linked ID parameter.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
Ensure that the current user is authorized to access the resource of the provided ID.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In the following example, in the "BAD" case, there is no authorization check, so any user can edit any comment for which they guess or determine the ID parameter.
|
||||
The "GOOD" case includes a check that the current user matches the author of the comment, preventing unauthorized access.</p>
|
||||
<sample src="WebFormsExample.cs" />
|
||||
<p>The following example shows a similar scenario for the ASP.NET Core framework. As above, the "BAD" case provides an example with no authorization check, and the first "GOOD" case provides an example with a check that the current user authored the specified comment. Additionally, in the second "GOOD" case, the `Authorize` attribute is used to restrict the method to administrators, who are expected to be able to access arbitrary resources.</p>
|
||||
<sample src="MVCExample.cs" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>OWASP: <a href="https://wiki.owasp.org/index.php/Top_10_2013-A4-Insecure_Direct_Object_References">Insecure Direct Object Refrences</a>.</li>
|
||||
<li>OWASP: <a href="https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/05-Authorization_Testing/04-Testing_for_Insecure_Direct_Object_References">Testing for Insecure Direct Object References</a>.</li>
|
||||
<li>Microsoft Learn: <a href="https://learn.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased?view=aspnetcore-7.0">Resource-based authorization in ASP.NET Core</a>.</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user