Compare commits

..

1 Commits

Author SHA1 Message Date
Owen Mansel-Chan
7126b95b16 Add test with MISSING alerts 2026-06-11 07:22:17 +02:00
1959 changed files with 18548 additions and 79689 deletions

View File

@@ -2,7 +2,7 @@
* @github/code-scanning-alert-coverage * @github/code-scanning-alert-coverage
# CodeQL language libraries # CodeQL language libraries
/actions/ @github/code-scanning-alert-coverage /actions/ @github/codeql-dynamic
/cpp/ @github/codeql-c-analysis /cpp/ @github/codeql-c-analysis
/csharp/ @github/codeql-csharp /csharp/ @github/codeql-csharp
/csharp/autobuilder/Semmle.Autobuild.Cpp @github/codeql-c-extractor @github/code-scanning-language-coverage /csharp/autobuilder/Semmle.Autobuild.Cpp @github/codeql-c-extractor @github/code-scanning-language-coverage
@@ -59,5 +59,9 @@ MODULE.bazel @github/codeql-ci-reviewers
/.github/workflows/rust.yml @github/codeql-rust /.github/workflows/rust.yml @github/codeql-rust
/.github/workflows/swift.yml @github/codeql-swift /.github/workflows/swift.yml @github/codeql-swift
# Misc
/misc/scripts/accept-expected-changes-from-ci.py @RasmusWL
/misc/scripts/generate-code-scanning-query-list.py @RasmusWL
# .devcontainer # .devcontainer
/.devcontainer/ @github/codeql-ci-reviewers /.devcontainer/ @github/codeql-ci-reviewers

View File

@@ -248,7 +248,6 @@ use_repo(
"kotlin-compiler-2.2.20-Beta2", "kotlin-compiler-2.2.20-Beta2",
"kotlin-compiler-2.3.0", "kotlin-compiler-2.3.0",
"kotlin-compiler-2.3.20", "kotlin-compiler-2.3.20",
"kotlin-compiler-2.4.0",
"kotlin-compiler-embeddable-1.8.0", "kotlin-compiler-embeddable-1.8.0",
"kotlin-compiler-embeddable-1.9.0-Beta", "kotlin-compiler-embeddable-1.9.0-Beta",
"kotlin-compiler-embeddable-1.9.20-Beta", "kotlin-compiler-embeddable-1.9.20-Beta",
@@ -260,7 +259,6 @@ use_repo(
"kotlin-compiler-embeddable-2.2.20-Beta2", "kotlin-compiler-embeddable-2.2.20-Beta2",
"kotlin-compiler-embeddable-2.3.0", "kotlin-compiler-embeddable-2.3.0",
"kotlin-compiler-embeddable-2.3.20", "kotlin-compiler-embeddable-2.3.20",
"kotlin-compiler-embeddable-2.4.0",
"kotlin-stdlib-1.8.0", "kotlin-stdlib-1.8.0",
"kotlin-stdlib-1.9.0-Beta", "kotlin-stdlib-1.9.0-Beta",
"kotlin-stdlib-1.9.20-Beta", "kotlin-stdlib-1.9.20-Beta",
@@ -272,7 +270,6 @@ use_repo(
"kotlin-stdlib-2.2.20-Beta2", "kotlin-stdlib-2.2.20-Beta2",
"kotlin-stdlib-2.3.0", "kotlin-stdlib-2.3.0",
"kotlin-stdlib-2.3.20", "kotlin-stdlib-2.3.20",
"kotlin-stdlib-2.4.0",
) )
go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk")

View File

@@ -2,7 +2,7 @@
### Minor Analysis Improvements ### Minor Analysis Improvements
* The GitHub Actions analysis now recognizes more Bash regex checks that restrict a value to alphanumeric characters, including regexes like `^[0-9a-zA-Z]{40}([0-9a-zA-Z]{24})?$` which check for a SHA-1 or SHA-256 hash. This may reduce false positive results where command output is validated with grouped or optional alphanumeric patterns before being used. * The GitHub Actions analysis now recognizes more Bash regex checks that restrict a value to alphanumeric characters, include regexes like `^[0-9a-zA-Z]{40}([0-9a-zA-Z]{24})?$` which check for a sha1 or sha256 hash. This may reduce false positive results where command output is validated with grouped or optional alphanumeric patterns before being used.
## 0.4.36 ## 0.4.36

View File

@@ -1,4 +0,0 @@
---
category: fix
---
* The query `actions/pr-on-self-hosted-runner` was updated to the latest standard runner labels reducing false positive results.

View File

@@ -1,4 +0,0 @@
---
category: fix
---
* GitHub Actions queries now better account for permission checks on jobs that call reusable workflows.

View File

@@ -2,4 +2,4 @@
### Minor Analysis Improvements ### Minor Analysis Improvements
* The GitHub Actions analysis now recognizes more Bash regex checks that restrict a value to alphanumeric characters, including regexes like `^[0-9a-zA-Z]{40}([0-9a-zA-Z]{24})?$` which check for a SHA-1 or SHA-256 hash. This may reduce false positive results where command output is validated with grouped or optional alphanumeric patterns before being used. * The GitHub Actions analysis now recognizes more Bash regex checks that restrict a value to alphanumeric characters, include regexes like `^[0-9a-zA-Z]{40}([0-9a-zA-Z]{24})?$` which check for a sha1 or sha256 hash. This may reduce false positive results where command output is validated with grouped or optional alphanumeric patterns before being used.

View File

@@ -1920,5 +1920,3 @@ private YamlMappingLikeNode resolveMatrixAccessPath(
else result = resolveMatrixAccessPath(newRoot, rest) else result = resolveMatrixAccessPath(newRoot, rest)
) )
} }
class Comment = YamlComment;

View File

@@ -52,12 +52,6 @@ private module YamlSig implements LibYaml::InputSig {
class ParseErrorBase extends LocatableBase, @yaml_error { class ParseErrorBase extends LocatableBase, @yaml_error {
string getMessage() { yaml_errors(this, result) } string getMessage() { yaml_errors(this, result) }
} }
class CommentBase extends LocatableBase, @yaml_comment {
string getText() { yaml_comments(this, result, _) }
override string toString() { yaml_comments(this, _, result) }
}
} }
import LibYaml::Make<YamlSig> import LibYaml::Make<YamlSig>

View File

@@ -42,15 +42,6 @@ string actor_not_attacker_event() {
] ]
} }
/**
* Gets the outer caller of `ej`, i.e. the `ExternalJob` that calls the
* reusable workflow containing `ej`. Used with transitive closure to
* walk up nested reusable workflow chains.
*/
private ExternalJob getAnOuterCaller(ExternalJob ej) {
result = ej.getEnclosingWorkflow().(ReusableWorkflow).getACaller()
}
/** An If node that contains an actor, user or label check */ /** An If node that contains an actor, user or label check */
abstract class ControlCheck extends AstNode { abstract class ControlCheck extends AstNode {
ControlCheck() { ControlCheck() {
@@ -62,170 +53,43 @@ abstract class ControlCheck extends AstNode {
predicate protects(AstNode node, Event event, string category) { predicate protects(AstNode node, Event event, string category) {
// The check dominates the step it should protect // The check dominates the step it should protect
this.dominates(node, event) and this.dominates(node) and
// The check is effective against the event and category // The check is effective against the event and category
this.protectsCategoryAndEvent(category, event.getName()) and this.protectsCategoryAndEvent(category, event.getName()) and
// The check can be triggered by the event // The check can be triggered by the event
this.getATriggerEvent() = event and this.getATriggerEvent() = event
// For reusable workflows, there must be no unprotected caller chain for this event.
(
not node.getEnclosingWorkflow() instanceof ReusableWorkflow
or
this.dominatesSameWorkflow(node, event)
or
not exists(ExternalJob directCaller |
directCaller = node.getEnclosingWorkflow().(ReusableWorkflow).getACaller() and
unprotectedCallerChain(directCaller, event, category)
)
)
} }
/** predicate dominates(AstNode node) {
* Holds if this control check must execute and pass before `node` can run.
*/
predicate dominates(AstNode node, Event event) {
this.dominatesSameWorkflow(node, event)
or
// When the node is inside a reusable workflow,
// this check dominates via at least one caller chain.
this.dominatesViaCaller(node, event, _)
}
/**
* Holds if this control check dominates `node` within the same workflow.
*/
predicate dominatesSameWorkflow(AstNode node, Event event) {
this.getATriggerEvent() = event and
(
// Step-level: the check is an `if:` on the step containing `node`,
// or on the enclosing job, or on a needed job/step.
this instanceof If and
(
node.getEnclosingStep().getIf() = this or
node.getEnclosingJob().getIf() = this or
node.getEnclosingJob().getANeededJob().(LocalJob).getAStep().getIf() = this or
node.getEnclosingJob().getANeededJob().(LocalJob).getIf() = this
)
or
// Job-level: the check is an environment on the enclosing job or a needed job.
this instanceof Environment and
(
node.getEnclosingJob().getEnvironment() = this
or
node.getEnclosingJob().getANeededJob().getEnvironment() = this
)
or
// Step-level: the check is a Run/UsesStep that precedes `node`'s step
// in the same job, or is a step in a needed job.
(
this instanceof Run or
this instanceof UsesStep
) and
(
this.(Step).getAFollowingStep() = node.getEnclosingStep()
or
node.getEnclosingJob().getANeededJob().(LocalJob).getAStep() = this
)
)
}
/**
* Holds if this control check dominates `node` in a reusable workflow
* via the caller chain starting at `directCaller`.
*/
predicate dominatesViaCaller(AstNode node, Event event, ExternalJob directCaller) {
directCaller = node.getEnclosingWorkflow().(ReusableWorkflow).getACaller() and
directCaller.getATriggerEvent() = event and
exists(ExternalJob caller |
caller = getAnOuterCaller*(directCaller) and
this.dominatesCaller(caller)
)
}
/**
* Holds if this control check directly dominates `caller`.
*/
predicate dominatesCaller(ExternalJob caller) {
this instanceof If and this instanceof If and
( (
caller.getIf() = this or node.getEnclosingStep().getIf() = this or
caller.getANeededJob().(LocalJob).getIf() = this or node.getEnclosingJob().getIf() = this or
caller.getANeededJob().(LocalJob).getAStep().getIf() = this node.getEnclosingJob().getANeededJob().(LocalJob).getAStep().getIf() = this or
node.getEnclosingJob().getANeededJob().(LocalJob).getIf() = this
) )
or or
this instanceof Environment and this instanceof Environment and
( (
caller.getEnvironment() = this or node.getEnclosingJob().getEnvironment() = this
caller.getANeededJob().getEnvironment() = this or
node.getEnclosingJob().getANeededJob().getEnvironment() = this
) )
or or
(this instanceof Run or this instanceof UsesStep) and (
caller.getANeededJob().(LocalJob).getAStep() = this this instanceof Run or
this instanceof UsesStep
) and
(
this.(Step).getAFollowingStep() = node.getEnclosingStep()
or
node.getEnclosingJob().getANeededJob().(LocalJob).getAStep() = this.(Step)
)
} }
abstract predicate protectsCategoryAndEvent(string category, string event); abstract predicate protectsCategoryAndEvent(string category, string event);
} }
/**
* Holds if this control check directly protects `caller`.
*/
bindingset[caller, event, category]
private predicate protectedCaller(ExternalJob caller, Event event, string category) {
exists(ControlCheck check |
check.protectsCategoryAndEvent(category, event.getName()) and
check.getATriggerEvent() = event and
check.dominatesCaller(caller)
)
}
cached
private newtype TCallerState =
MkCallerState(ExternalJob caller, Event event, string category) {
caller.getATriggerEvent() = event and
category = any_category()
}
private class CallerState extends TCallerState, MkCallerState {
ExternalJob caller;
Event event;
string category;
CallerState() { this = MkCallerState(caller, event, category) }
ExternalJob getCaller() { result = caller }
Event getEvent() { result = event }
string getCategory() { result = category }
/**
* Gets an outer caller state if this caller is not protected.
*/
CallerState getUnprotectedOuterState() {
not protectedCaller(this.getCaller(), this.getEvent(), this.getCategory()) and
result = MkCallerState(getAnOuterCaller(this.getCaller()), this.getEvent(), this.getCategory())
}
predicate isUnprotectedOutermost() {
not protectedCaller(this.getCaller(), this.getEvent(), this.getCategory()) and
not exists(getAnOuterCaller(this.getCaller()))
}
string toString() { result = caller + " / " + event + " / " + category }
}
/**
* Holds if there is a caller path from `caller` to an outer workflow that has no protection.
*/
bindingset[caller, event, category]
private predicate unprotectedCallerChain(ExternalJob caller, Event event, string category) {
exists(CallerState start, CallerState outermost |
start = MkCallerState(caller, event, category) and
outermost = start.getUnprotectedOuterState*() and
outermost.isUnprotectedOutermost()
)
}
abstract class AssociationCheck extends ControlCheck { abstract class AssociationCheck extends ControlCheck {
// Checks if the actor is a MEMBER/OWNER the repo // Checks if the actor is a MEMBER/OWNER the repo
// - they are effective against pull requests and workflow_run (since these are triggered by pull_requests) since they can control who is making the PR // - they are effective against pull requests and workflow_run (since these are triggered by pull_requests) since they can control who is making the PR

View File

@@ -2,12 +2,10 @@ import actions
bindingset[runner] bindingset[runner]
predicate isGithubHostedRunner(string runner) { predicate isGithubHostedRunner(string runner) {
// The list of github hosted repos: // list of github hosted repos: https://github.com/actions/runner-images/blob/main/README.md#available-images
// https://github.com/actions/runner-images/blob/main/README.md#available-images runner
// https://docs.github.com/en/enterprise-cloud@latest/actions/how-tos/write-workflows/choose-where-workflows-run/choose-the-runner-for-a-job#standard-github-hosted-runners-for-public-repositories .toLowerCase()
runner.toLowerCase().regexpMatch("^ubuntu-([0-9.]+|latest|slim)(-arm)?$") or .regexpMatch("^(ubuntu-([0-9.]+|latest)|macos-([0-9]+|latest)(-x?large)?|windows-([0-9.]+|latest))$")
runner.toLowerCase().regexpMatch("^macos-([0-9]+|latest)(-x?large|-intel)?$") or
runner.toLowerCase().regexpMatch("^windows-([0-9.]+|latest)(-vs[0-9.]+)?(-arm)?$")
} }
bindingset[runner] bindingset[runner]

View File

@@ -15,7 +15,7 @@
### Bug Fixes ### Bug Fixes
* Adjusted (minor) help file descriptions for queries: `actions/untrusted-checkout/critical`, `actions/untrusted-checkout/high`, `actions/untrusted-checkout/medium`. Clarified wording on a minor point, added one more listed resource and added one more recommendation for things to check. * Adjusted (minor) help file descriptions for queries: `actions/untrusted-checkout/critical`, `actions/untrusted-checkout/high`, `actions/untrusted-checkout/medium`. Clarified wording on in minor point, added one more listed resource and added one more recommendation for things to check.
## 0.6.28 ## 0.6.28

View File

@@ -18,7 +18,7 @@ from LocalJob job, LabelCheck check, MutableRefCheckoutStep checkout, Event even
where where
job.isPrivileged() and job.isPrivileged() and
job.getAStep() = checkout and job.getAStep() = checkout and
check.dominates(checkout, event) and check.dominates(checkout) and
( (
job.getATriggerEvent() = event and job.getATriggerEvent() = event and
event.getName() = "pull_request_target" and event.getName() = "pull_request_target" and

View File

@@ -34,8 +34,8 @@ where
check instanceof AssociationCheck or check instanceof AssociationCheck or
check instanceof PermissionCheck check instanceof PermissionCheck
) and ) and
check.dominates(checkout, event) and check.dominates(checkout) and
date_check.dominates(checkout, event) date_check.dominates(checkout)
) )
or or
// not issue_comment triggered workflows // not issue_comment triggered workflows

View File

@@ -1,8 +1,8 @@
/** /**
* @name Checkout of untrusted code in a non-privileged context * @name Checkout of untrusted code in a trusted context
* @description Checking out and running the build script from a fork executes untrusted code. Even in a * @description Privileged workflows have read/write access to the base repository and access to secrets.
* non-privileged workflow, this can be abused, for example to compromise self-hosted runners * By explicitly checking out and running the build script from a fork the untrusted code is running in an environment
* or to poison caches and artifacts that are later consumed by privileged workflows. * that is able to push to the base repository and to access secrets.
* @kind problem * @kind problem
* @problem.severity warning * @problem.severity warning
* @precision medium * @precision medium
@@ -20,4 +20,4 @@ from PRHeadCheckoutStep checkout
where where
// the checkout occurs in a non-privileged context // the checkout occurs in a non-privileged context
inNonPrivilegedContext(checkout) inNonPrivilegedContext(checkout)
select checkout, "Potential unsafe checkout of untrusted pull request on non-privileged workflow." select checkout, "Potential unsafe checkout of untrusted pull request on privileged workflow."

View File

@@ -1,4 +0,0 @@
---
category: queryMetadata
---
* The name, description, and alert message of `actions/untrusted-checkout/medium` have been corrected to describe a non-privileged context.

View File

@@ -15,4 +15,4 @@
### Bug Fixes ### Bug Fixes
* Adjusted (minor) help file descriptions for queries: `actions/untrusted-checkout/critical`, `actions/untrusted-checkout/high`, `actions/untrusted-checkout/medium`. Clarified wording on a minor point, added one more listed resource and added one more recommendation for things to check. * Adjusted (minor) help file descriptions for queries: `actions/untrusted-checkout/critical`, `actions/untrusted-checkout/high`, `actions/untrusted-checkout/medium`. Clarified wording on in minor point, added one more listed resource and added one more recommendation for things to check.

View File

@@ -1,43 +0,0 @@
name: test
on:
pull_request:
jobs:
test:
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- ubuntu-24.04
- ubuntu-24.04-arm
- ubuntu-22.04
- ubuntu-22.04-arm
- ubuntu-26.04
- ubuntu-26.04-arm
- ubuntu-slim
- macos-26
- macos-26-xlarge
- macos-26-intel
- macos-26-large
- macos-latest-large
- macos-15-large
- macos-15
- macos-15-intel
- macos-latest
- macos-15
- macos-15-xlarge
- macos-14-large
- macos-14
- macos-14-xlarge
- windows-2025-vs2026
- windows-latest
- windows-2025
- windows-2022
- windows-11
- windows-11-arm
- windows-11-vs2026-arm
runs-on: ${{ matrix.os }}
steps:
- run: cmd

View File

@@ -1,17 +0,0 @@
on:
workflow_call:
inputs:
COMMIT_SHA:
type: string
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.COMMIT_SHA }}
- run: |
npm install
npm run lint

View File

@@ -1,13 +0,0 @@
on:
workflow_call:
inputs:
COMMIT_SHA:
type: string
jobs:
build:
uses: TestOrg/TestRepo/.github/workflows/build.yml@main
with:
COMMIT_SHA: ${{ inputs.COMMIT_SHA }}

View File

@@ -1,33 +0,0 @@
on:
workflow_call:
inputs:
COMMIT_SHA:
type: string
jobs:
is-collaborator:
runs-on: ubuntu-latest
steps:
- name: Get User Permission
id: checkAccess
uses: actions-cool/check-user-permission@cd622002ff25c2311d2e7fb82107c0d24be83f9b
with:
require: write
username: ${{ github.actor }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check User Permission
if: steps.checkAccess.outputs.require-result == 'false'
run: |
echo "${{ github.actor }} does not have permissions on this repo."
echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}"
exit 1
build_safe:
needs: is-collaborator
uses: TestOrg/TestRepo/.github/workflows/build_nested.yml@main
with:
COMMIT_SHA: ${{ inputs.COMMIT_SHA }}
build_unsafe:
uses: TestOrg/TestRepo/.github/workflows/build_nested.yml@main
with:
COMMIT_SHA: ${{ inputs.COMMIT_SHA }}

View File

@@ -1,31 +0,0 @@
on:
pull_request_target:
jobs:
is-collaborator:
runs-on: ubuntu-latest
steps:
- name: Get User Permission
id: checkAccess
uses: actions-cool/check-user-permission@cd622002ff25c2311d2e7fb82107c0d24be83f9b
with:
require: write
username: ${{ github.actor }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check User Permission
if: steps.checkAccess.outputs.require-result == 'false'
run: |
echo "${{ github.actor }} does not have permissions on this repo."
echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}"
exit 1
build:
runs-on: ubuntu-latest
#needs: is-collaborator Mistake, doesn't wait for the collaborator - no security check
steps:
- name: Checkout repo
uses: actions/checkout@4
with:
ref: ${{ github.event.pull_request.head.sha }} # should alert
fetch-depth: 2
- run: yarn test

View File

@@ -1,26 +0,0 @@
on:
pull_request_target:
jobs:
is-collaborator:
runs-on: ubuntu-latest
steps:
- name: Get User Permission
id: checkAccess
uses: actions-cool/check-user-permission@cd622002ff25c2311d2e7fb82107c0d24be83f9b
with:
require: write
username: ${{ github.actor }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check User Permission
if: steps.checkAccess.outputs.require-result == 'false'
run: |
echo "${{ github.actor }} does not have permissions on this repo."
echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}"
exit 1
build:
needs: is-collaborator
uses: TestOrg/TestRepo/.github/workflows/build.yml@main
with:
COMMIT_SHA: ${{ github.event.pull_request.head.sha }} # shouldn't alert since permission check

View File

@@ -1,31 +0,0 @@
on:
pull_request_target:
jobs:
is-collaborator:
runs-on: ubuntu-latest
steps:
- name: Get User Permission
id: checkAccess
uses: actions-cool/check-user-permission@cd622002ff25c2311d2e7fb82107c0d24be83f9b
with:
require: write
username: ${{ github.actor }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check User Permission
if: steps.checkAccess.outputs.require-result == 'false'
run: |
echo "${{ github.actor }} does not have permissions on this repo."
echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}"
exit 1
build_unsafe:
# needs: is-collaborator
uses: TestOrg/TestRepo/.github/workflows/build.yml@main
with:
COMMIT_SHA: ${{ github.event.pull_request.head.sha }} # should alert since no permission check
build_safe:
needs: is-collaborator
uses: TestOrg/TestRepo/.github/workflows/build.yml@main
with:
COMMIT_SHA: ${{ github.event.pull_request.head.sha }} # shouldn't alert since permission check

View File

@@ -1,8 +0,0 @@
on:
pull_request_target:
jobs:
build:
uses: TestOrg/TestRepo/.github/workflows/build_nested_branching.yml@main
with:
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}

View File

@@ -1,26 +0,0 @@
on:
pull_request_target:
jobs:
is-collaborator:
runs-on: ubuntu-latest
steps:
- name: Get User Permission
id: checkAccess
uses: actions-cool/check-user-permission@cd622002ff25c2311d2e7fb82107c0d24be83f9b
with:
require: write
username: ${{ github.actor }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check User Permission
if: steps.checkAccess.outputs.require-result == 'false'
run: |
echo "${{ github.actor }} does not have permissions on this repo."
echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}"
exit 1
build:
needs: is-collaborator
uses: TestOrg/TestRepo/.github/workflows/build_nested.yml@main
with:
COMMIT_SHA: ${{ github.event.pull_request.head.sha }} # shouldn't alert since permission check

View File

@@ -1,26 +0,0 @@
on:
pull_request_target:
jobs:
is-collaborator:
runs-on: ubuntu-latest
steps:
- name: Get User Permission
id: checkAccess
uses: actions-cool/check-user-permission@cd622002ff25c2311d2e7fb82107c0d24be83f9b
with:
require: write
username: ${{ github.actor }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check User Permission
if: steps.checkAccess.outputs.require-result == 'false'
run: |
echo "${{ github.actor }} does not have permissions on this repo."
echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}"
exit 1
build:
# needs: is-collaborator
uses: TestOrg/TestRepo/.github/workflows/build_nested.yml@main
with:
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}

View File

@@ -1,41 +0,0 @@
on:
pull_request_target:
jobs:
is-collaborator:
runs-on: ubuntu-latest
steps:
- name: Get User Permission
id: checkAccess
uses: actions-cool/check-user-permission@cd622002ff25c2311d2e7fb82107c0d24be83f9b
with:
require: write
username: ${{ github.actor }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check User Permission
if: steps.checkAccess.outputs.require-result == 'false'
run: |
echo "${{ github.actor }} does not have permissions on this repo."
echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}"
exit 1
build:
runs-on: ubuntu-latest
needs: is-collaborator
steps:
- name: Checkout repo
uses: actions/checkout@4
with:
ref: ${{ github.event.pull_request.head.sha }} # shouldn't alert since permission check
fetch-depth: 2
- run: yarn test
build_unsafe:
runs-on: ubuntu-latest
# needs: is-collaborator
steps:
- name: Checkout repo
uses: actions/checkout@4
with:
ref: ${{ github.event.pull_request.head.sha }} # should alert since no permission check
fetch-depth: 2
- run: yarn test

View File

@@ -1,48 +0,0 @@
on:
pull_request_target:
jobs:
is-collaborator-a:
runs-on: ubuntu-latest
steps:
- name: Get User Permission
id: checkAccess
uses: actions-cool/check-user-permission@cd622002ff25c2311d2e7fb82107c0d24be83f9b
with:
require: write
username: ${{ github.actor }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check User Permission
if: steps.checkAccess.outputs.require-result == 'false'
run: |
echo "${{ github.actor }} does not have permissions on this repo."
echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}"
exit 1
caller-a:
needs: is-collaborator-a
uses: TestOrg/TestRepo/.github/workflows/build.yml@main
with:
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
is-collaborator-b:
runs-on: ubuntu-latest
steps:
- name: Get User Permission
id: checkAccess
uses: actions-cool/check-user-permission@cd622002ff25c2311d2e7fb82107c0d24be83f9b
with:
require: write
username: ${{ github.actor }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check User Permission
if: steps.checkAccess.outputs.require-result == 'false'
run: |
echo "${{ github.actor }} does not have permissions on this repo."
echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}"
exit 1
caller-b:
needs: is-collaborator-b
uses: TestOrg/TestRepo/.github/workflows/build.yml@main
with:
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}

View File

@@ -93,8 +93,6 @@ edges
| .github/workflows/dependabot3.yml:15:9:20:6 | Uses Step | .github/workflows/dependabot3.yml:20:9:25:6 | Uses Step | | .github/workflows/dependabot3.yml:15:9:20:6 | Uses Step | .github/workflows/dependabot3.yml:20:9:25:6 | Uses Step |
| .github/workflows/dependabot3.yml:20:9:25:6 | Uses Step | .github/workflows/dependabot3.yml:25:9:48:6 | Run Step: set-milestone | | .github/workflows/dependabot3.yml:20:9:25:6 | Uses Step | .github/workflows/dependabot3.yml:25:9:48:6 | Run Step: set-milestone |
| .github/workflows/dependabot3.yml:25:9:48:6 | Run Step: set-milestone | .github/workflows/dependabot3.yml:48:9:52:57 | Run Step | | .github/workflows/dependabot3.yml:25:9:48:6 | Run Step: set-milestone | .github/workflows/dependabot3.yml:48:9:52:57 | Run Step |
| .github/workflows/external/TestOrg/TestRepo/.github/workflows/build.yml:11:9:14:6 | Uses Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/build.yml:14:9:17:7 | Run Step |
| .github/workflows/external/TestOrg/TestRepo/.github/workflows/build_nested_branching.yml:11:9:19:6 | Uses Step: checkAccess | .github/workflows/external/TestOrg/TestRepo/.github/workflows/build_nested_branching.yml:19:9:25:2 | Run Step |
| .github/workflows/external/TestOrg/TestRepo/.github/workflows/formal.yml:14:9:19:6 | Uses Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/formal.yml:19:9:25:6 | Run Step | | .github/workflows/external/TestOrg/TestRepo/.github/workflows/formal.yml:14:9:19:6 | Uses Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/formal.yml:19:9:25:6 | Run Step |
| .github/workflows/external/TestOrg/TestRepo/.github/workflows/formal.yml:19:9:25:6 | Run Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/formal.yml:25:9:70:20 | Run Step | | .github/workflows/external/TestOrg/TestRepo/.github/workflows/formal.yml:19:9:25:6 | Run Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/formal.yml:25:9:70:20 | Run Step |
| .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable.yml:23:9:26:6 | Uses Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable.yml:26:9:29:7 | Run Step | | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable.yml:23:9:26:6 | Uses Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable.yml:26:9:29:7 | Run Step |
@@ -336,17 +334,6 @@ edges
| .github/workflows/untrusted_checkout_6.yml:11:9:14:6 | Uses Step | .github/workflows/untrusted_checkout_6.yml:14:9:17:6 | Uses Step | | .github/workflows/untrusted_checkout_6.yml:11:9:14:6 | Uses Step | .github/workflows/untrusted_checkout_6.yml:14:9:17:6 | Uses Step |
| .github/workflows/untrusted_checkout_6.yml:14:9:17:6 | Uses Step | .github/workflows/untrusted_checkout_6.yml:17:9:21:6 | Uses Step | | .github/workflows/untrusted_checkout_6.yml:14:9:17:6 | Uses Step | .github/workflows/untrusted_checkout_6.yml:17:9:21:6 | Uses Step |
| .github/workflows/untrusted_checkout_6.yml:17:9:21:6 | Uses Step | .github/workflows/untrusted_checkout_6.yml:21:9:23:23 | Run Step | | .github/workflows/untrusted_checkout_6.yml:17:9:21:6 | Uses Step | .github/workflows/untrusted_checkout_6.yml:21:9:23:23 | Run Step |
| .github/workflows/untrusted_checkout_no_needs.yml:8:9:16:6 | Uses Step: checkAccess | .github/workflows/untrusted_checkout_no_needs.yml:16:9:22:2 | Run Step |
| .github/workflows/untrusted_checkout_no_needs.yml:26:9:31:6 | Uses Step | .github/workflows/untrusted_checkout_no_needs.yml:31:9:31:23 | Run Step |
| .github/workflows/untrusted_checkout_permission_check_reusable2.yml:8:9:16:6 | Uses Step: checkAccess | .github/workflows/untrusted_checkout_permission_check_reusable2.yml:16:9:22:2 | Run Step |
| .github/workflows/untrusted_checkout_permission_check_reusable.yml:8:9:16:6 | Uses Step: checkAccess | .github/workflows/untrusted_checkout_permission_check_reusable.yml:16:9:22:2 | Run Step |
| .github/workflows/untrusted_checkout_permission_check_reusable_level2.yml:8:9:16:6 | Uses Step: checkAccess | .github/workflows/untrusted_checkout_permission_check_reusable_level2.yml:16:9:22:2 | Run Step |
| .github/workflows/untrusted_checkout_permission_check_reusable_no_needs.yml:8:9:16:6 | Uses Step: checkAccess | .github/workflows/untrusted_checkout_permission_check_reusable_no_needs.yml:16:9:22:2 | Run Step |
| .github/workflows/untrusted_checkout_permissions_check.yml:8:9:16:6 | Uses Step: checkAccess | .github/workflows/untrusted_checkout_permissions_check.yml:16:9:22:2 | Run Step |
| .github/workflows/untrusted_checkout_permissions_check.yml:26:9:31:6 | Uses Step | .github/workflows/untrusted_checkout_permissions_check.yml:31:9:32:2 | Run Step |
| .github/workflows/untrusted_checkout_permissions_check.yml:36:9:41:6 | Uses Step | .github/workflows/untrusted_checkout_permissions_check.yml:41:9:41:22 | Run Step |
| .github/workflows/untrusted_checkout_two_callers_both_protected.yml:8:9:16:6 | Uses Step: checkAccess | .github/workflows/untrusted_checkout_two_callers_both_protected.yml:16:9:22:2 | Run Step |
| .github/workflows/untrusted_checkout_two_callers_both_protected.yml:30:9:38:6 | Uses Step: checkAccess | .github/workflows/untrusted_checkout_two_callers_both_protected.yml:38:9:44:2 | Run Step |
| .github/workflows/workflow_run_untrusted_checkout.yml:13:9:16:6 | Uses Step | .github/workflows/workflow_run_untrusted_checkout.yml:16:9:18:31 | Uses Step | | .github/workflows/workflow_run_untrusted_checkout.yml:13:9:16:6 | Uses Step | .github/workflows/workflow_run_untrusted_checkout.yml:16:9:18:31 | Uses Step |
| .github/workflows/workflow_run_untrusted_checkout_2.yml:13:9:16:6 | Uses Step | .github/workflows/workflow_run_untrusted_checkout_2.yml:16:9:18:31 | Uses Step | | .github/workflows/workflow_run_untrusted_checkout_2.yml:13:9:16:6 | Uses Step | .github/workflows/workflow_run_untrusted_checkout_2.yml:16:9:18:31 | Uses Step |
| .github/workflows/workflow_run_untrusted_checkout_3.yml:13:9:16:6 | Uses Step | .github/workflows/workflow_run_untrusted_checkout_3.yml:16:9:18:31 | Uses Step | | .github/workflows/workflow_run_untrusted_checkout_3.yml:13:9:16:6 | Uses Step | .github/workflows/workflow_run_untrusted_checkout_3.yml:16:9:18:31 | Uses Step |
@@ -357,9 +344,6 @@ edges
| .github/workflows/auto_ci.yml:67:9:74:6 | Uses Step | .github/workflows/auto_ci.yml:67:9:74:6 | Uses Step | .github/workflows/auto_ci.yml:79:9:84:6 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/auto_ci.yml:6:3:6:21 | pull_request_target | pull_request_target | | .github/workflows/auto_ci.yml:67:9:74:6 | Uses Step | .github/workflows/auto_ci.yml:67:9:74:6 | Uses Step | .github/workflows/auto_ci.yml:79:9:84:6 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/auto_ci.yml:6:3:6:21 | pull_request_target | pull_request_target |
| .github/workflows/auto_ci.yml:67:9:74:6 | Uses Step | .github/workflows/auto_ci.yml:67:9:74:6 | Uses Step | .github/workflows/auto_ci.yml:84:9:93:6 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/auto_ci.yml:6:3:6:21 | pull_request_target | pull_request_target | | .github/workflows/auto_ci.yml:67:9:74:6 | Uses Step | .github/workflows/auto_ci.yml:67:9:74:6 | Uses Step | .github/workflows/auto_ci.yml:84:9:93:6 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/auto_ci.yml:6:3:6:21 | pull_request_target | pull_request_target |
| .github/workflows/dependabot3.yml:15:9:20:6 | Uses Step | .github/workflows/dependabot3.yml:15:9:20:6 | Uses Step | .github/workflows/dependabot3.yml:25:9:48:6 | Run Step: set-milestone | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/dependabot3.yml:3:5:3:23 | pull_request_target | pull_request_target | | .github/workflows/dependabot3.yml:15:9:20:6 | Uses Step | .github/workflows/dependabot3.yml:15:9:20:6 | Uses Step | .github/workflows/dependabot3.yml:25:9:48:6 | Run Step: set-milestone | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/dependabot3.yml:3:5:3:23 | pull_request_target | pull_request_target |
| .github/workflows/external/TestOrg/TestRepo/.github/workflows/build.yml:11:9:14:6 | Uses Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/build.yml:11:9:14:6 | Uses Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/build.yml:14:9:17:7 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_permission_check_reusable2.yml:2:3:2:21 | pull_request_target | pull_request_target |
| .github/workflows/external/TestOrg/TestRepo/.github/workflows/build.yml:11:9:14:6 | Uses Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/build.yml:11:9:14:6 | Uses Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/build.yml:14:9:17:7 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_permission_check_reusable_branching_nested.yml:2:3:2:21 | pull_request_target | pull_request_target |
| .github/workflows/external/TestOrg/TestRepo/.github/workflows/build.yml:11:9:14:6 | Uses Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/build.yml:11:9:14:6 | Uses Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/build.yml:14:9:17:7 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_permission_check_reusable_no_needs.yml:2:3:2:21 | pull_request_target | pull_request_target |
| .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable.yml:23:9:26:6 | Uses Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable.yml:23:9:26:6 | Uses Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable.yml:26:9:29:7 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/reusable_caller1.yaml:4:3:4:21 | pull_request_target | pull_request_target | | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable.yml:23:9:26:6 | Uses Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable.yml:23:9:26:6 | Uses Step | .github/workflows/external/TestOrg/TestRepo/.github/workflows/reusable.yml:26:9:29:7 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/reusable_caller1.yaml:4:3:4:21 | pull_request_target | pull_request_target |
| .github/workflows/gitcheckout.yml:10:11:18:8 | Run Step | .github/workflows/gitcheckout.yml:10:11:18:8 | Run Step | .github/workflows/gitcheckout.yml:21:11:23:22 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/gitcheckout.yml:2:3:2:21 | pull_request_target | pull_request_target | | .github/workflows/gitcheckout.yml:10:11:18:8 | Run Step | .github/workflows/gitcheckout.yml:10:11:18:8 | Run Step | .github/workflows/gitcheckout.yml:21:11:23:22 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/gitcheckout.yml:2:3:2:21 | pull_request_target | pull_request_target |
| .github/workflows/label_trusted_checkout2.yml:12:7:16:4 | Uses Step | .github/workflows/label_trusted_checkout2.yml:12:7:16:4 | Uses Step | .github/workflows/label_trusted_checkout2.yml:17:7:21:4 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/label_trusted_checkout2.yml:2:3:2:21 | pull_request_target | pull_request_target | | .github/workflows/label_trusted_checkout2.yml:12:7:16:4 | Uses Step | .github/workflows/label_trusted_checkout2.yml:12:7:16:4 | Uses Step | .github/workflows/label_trusted_checkout2.yml:17:7:21:4 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/label_trusted_checkout2.yml:2:3:2:21 | pull_request_target | pull_request_target |
@@ -393,5 +377,3 @@ edges
| .github/workflows/untrusted_checkout4.yml:29:7:35:4 | Uses Step | .github/workflows/untrusted_checkout4.yml:29:7:35:4 | Uses Step | .github/workflows/untrusted_checkout4.yml:47:7:51:46 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout4.yml:2:3:2:15 | issue_comment | issue_comment | | .github/workflows/untrusted_checkout4.yml:29:7:35:4 | Uses Step | .github/workflows/untrusted_checkout4.yml:29:7:35:4 | Uses Step | .github/workflows/untrusted_checkout4.yml:47:7:51:46 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout4.yml:2:3:2:15 | issue_comment | issue_comment |
| .github/workflows/untrusted_checkout.yml:8:9:11:6 | Uses Step | .github/workflows/untrusted_checkout.yml:8:9:11:6 | Uses Step | .github/workflows/untrusted_checkout.yml:15:9:18:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout.yml:2:3:2:21 | pull_request_target | pull_request_target | | .github/workflows/untrusted_checkout.yml:8:9:11:6 | Uses Step | .github/workflows/untrusted_checkout.yml:8:9:11:6 | Uses Step | .github/workflows/untrusted_checkout.yml:15:9:18:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout.yml:2:3:2:21 | pull_request_target | pull_request_target |
| .github/workflows/untrusted_checkout.yml:23:9:26:6 | Uses Step | .github/workflows/untrusted_checkout.yml:23:9:26:6 | Uses Step | .github/workflows/untrusted_checkout.yml:30:9:32:23 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout.yml:2:3:2:21 | pull_request_target | pull_request_target | | .github/workflows/untrusted_checkout.yml:23:9:26:6 | Uses Step | .github/workflows/untrusted_checkout.yml:23:9:26:6 | Uses Step | .github/workflows/untrusted_checkout.yml:30:9:32:23 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout.yml:2:3:2:21 | pull_request_target | pull_request_target |
| .github/workflows/untrusted_checkout_no_needs.yml:26:9:31:6 | Uses Step | .github/workflows/untrusted_checkout_no_needs.yml:26:9:31:6 | Uses Step | .github/workflows/untrusted_checkout_no_needs.yml:31:9:31:23 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_no_needs.yml:2:3:2:21 | pull_request_target | pull_request_target |
| .github/workflows/untrusted_checkout_permissions_check.yml:36:9:41:6 | Uses Step | .github/workflows/untrusted_checkout_permissions_check.yml:36:9:41:6 | Uses Step | .github/workflows/untrusted_checkout_permissions_check.yml:41:9:41:22 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_permissions_check.yml:2:3:2:21 | pull_request_target | pull_request_target |

View File

@@ -1,10 +1,10 @@
| .github/workflows/artifactpoisoning81.yml:11:9:14:6 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. | | .github/workflows/artifactpoisoning81.yml:11:9:14:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
| .github/workflows/dependabot2.yml:33:9:38:6 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. | | .github/workflows/dependabot2.yml:33:9:38:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
| .github/workflows/mend.yml:22:9:29:6 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. | | .github/workflows/mend.yml:22:9:29:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
| .github/workflows/poc3.yml:18:7:25:4 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. | | .github/workflows/poc3.yml:18:7:25:4 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
| .github/workflows/poc.yml:30:9:36:6 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. | | .github/workflows/poc.yml:30:9:36:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
| .github/workflows/priv_pull_request_checkout.yml:14:9:20:6 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. | | .github/workflows/priv_pull_request_checkout.yml:14:9:20:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
| .github/workflows/test3.yml:28:9:33:6 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. | | .github/workflows/test3.yml:28:9:33:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
| .github/workflows/test4.yml:18:7:25:4 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. | | .github/workflows/test4.yml:18:7:25:4 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
| .github/workflows/test8.yml:20:9:26:6 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. | | .github/workflows/test8.yml:20:9:26:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
| .github/workflows/test9.yml:11:9:16:6 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. | | .github/workflows/test9.yml:11:9:16:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |

View File

@@ -1,2 +0,0 @@
description: Fix NameQualifier inconsistency
compatibility: full

View File

@@ -1071,7 +1071,7 @@ class NullPointerType extends BuiltInType {
* const float fa[40]; * const float fa[40];
* ``` * ```
*/ */
class DerivedType extends Type, NameQualifyingElement, @derivedtype { class DerivedType extends Type, @derivedtype {
override string toString() { result = this.getName() } override string toString() { result = this.getName() }
override string getName() { derivedtypes(underlyingElement(this), result, _, _) } override string getName() { derivedtypes(underlyingElement(this), result, _, _) }

View File

@@ -1430,8 +1430,7 @@ specialnamequalifyingelements(
@namequalifyingelement = @namespace @namequalifyingelement = @namespace
| @specialnamequalifyingelement | @specialnamequalifyingelement
| @usertype | @usertype
| @decltype | @decltype;
| @derivedtype;
namequalifiers( namequalifiers(
unique int id: @namequalifier, unique int id: @namequalifier,

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
description: Fix NameQualifier inconsistency
compatibility: full

View File

@@ -1,7 +1,3 @@
| inconsistency2.cpp:3:3:3:5 | T:: | inconsistency2.cpp:3:3:3:6 | x | inconsistency2.cpp:2:20:2:20 | T |
| inconsistency2.cpp:3:3:3:11 | const s:: | inconsistency2.cpp:3:3:3:6 | x | file://:0:0:0:0 | const s |
| inconsistency.cpp:7:20:7:22 | S:: | inconsistency.cpp:7:20:7:23 | (int)... | inconsistency.cpp:4:8:4:8 | S |
| inconsistency.cpp:7:20:7:22 | S:: | inconsistency.cpp:7:20:7:23 | A | inconsistency.cpp:4:8:4:8 | S |
| name_qualifiers.cpp:29:7:29:8 | :: | name_qualifiers.cpp:29:7:29:9 | x | file://:0:0:0:0 | (global namespace) | | name_qualifiers.cpp:29:7:29:8 | :: | name_qualifiers.cpp:29:7:29:9 | x | file://:0:0:0:0 | (global namespace) |
| name_qualifiers.cpp:31:7:31:10 | N1:: | name_qualifiers.cpp:31:7:31:12 | nx | name_qualifiers.cpp:4:11:4:12 | N1 | | name_qualifiers.cpp:31:7:31:10 | N1:: | name_qualifiers.cpp:31:7:31:12 | nx | name_qualifiers.cpp:4:11:4:12 | N1 |
| name_qualifiers.cpp:34:7:34:8 | :: | name_qualifiers.cpp:34:9:34:12 | N1:: | file://:0:0:0:0 | (global namespace) | | name_qualifiers.cpp:34:7:34:8 | :: | name_qualifiers.cpp:34:9:34:12 | N1:: | file://:0:0:0:0 | (global namespace) |

View File

@@ -1,5 +1,7 @@
import cpp import cpp
from NameQualifier nq, Location l from NameQualifier nq, Location l
where l = nq.getQualifiedElement().getLocation() where
l = nq.getQualifiedElement().getLocation() and
l.getFile().getShortName() = "name_qualifiers"
select nq, nq.getQualifiedElement(), nq.getQualifyingElement() select nq, nq.getQualifiedElement(), nq.getQualifyingElement()

View File

@@ -1,8 +1,8 @@
// This file is present to test whether name-qualifying an enum constant leads to a database inconsistency. // This file is present to test whether name-qualifying an enum constant leads to a database inconsistency.
// As such, there is no QL part of the test.
struct S { enum E { A }; }; struct S { enum E { A }; };
static void f() { static int f() {
switch(0) { case S::A: break; } switch(0) { case S::A: break; }
} }

View File

@@ -1,12 +0,0 @@
namespace {
template <typename T> T f() {
T::x;
return {};
}
struct s {
static int x;
};
struct t {
s x = f<const s>();
};
}

View File

@@ -135,7 +135,7 @@ namespace Semmle.Autobuild.CSharp.Tests
if (!EnumerateFiles.TryGetValue(dir, out var str)) if (!EnumerateFiles.TryGetValue(dir, out var str))
throw new ArgumentException("Missing EnumerateFiles " + dir); throw new ArgumentException("Missing EnumerateFiles " + dir);
return str.Split("\n").Select(p => PathJoin(dir, p)); return str.Split("\n").Select(p => PathCombine(dir, p));
} }
public IDictionary<string, string> EnumerateDirectories { get; } = new Dictionary<string, string>(); public IDictionary<string, string> EnumerateDirectories { get; } = new Dictionary<string, string>();
@@ -147,7 +147,7 @@ namespace Semmle.Autobuild.CSharp.Tests
return string.IsNullOrEmpty(str) return string.IsNullOrEmpty(str)
? Enumerable.Empty<string>() ? Enumerable.Empty<string>()
: str.Split("\n").Select(p => PathJoin(dir, p)); : str.Split("\n").Select(p => PathCombine(dir, p));
} }
public bool IsWindows { get; set; } public bool IsWindows { get; set; }
@@ -170,7 +170,7 @@ namespace Semmle.Autobuild.CSharp.Tests
bool IBuildActions.IsMonoInstalled() => IsMonoInstalled; bool IBuildActions.IsMonoInstalled() => IsMonoInstalled;
public string PathJoin(params string[] parts) public string PathCombine(params string[] parts)
{ {
return string.Join(IsWindows ? '\\' : '/', parts.Where(p => !string.IsNullOrWhiteSpace(p))); return string.Join(IsWindows ? '\\' : '/', parts.Where(p => !string.IsNullOrWhiteSpace(p)));
} }

View File

@@ -109,7 +109,7 @@ namespace Semmle.Autobuild.CSharp
=> WithDotNet(builder, ensureDotNetAvailable: false, (_, env) => f(env)); => WithDotNet(builder, ensureDotNetAvailable: false, (_, env) => f(env));
private static string DotNetCommand(IBuildActions actions, string? dotNetPath) => private static string DotNetCommand(IBuildActions actions, string? dotNetPath) =>
dotNetPath is not null ? actions.PathJoin(dotNetPath, "dotnet") : "dotnet"; dotNetPath is not null ? actions.PathCombine(dotNetPath, "dotnet") : "dotnet";
private static CommandBuilder GetCleanCommand(IBuildActions actions, string? dotNetPath, IDictionary<string, string>? environment) private static CommandBuilder GetCleanCommand(IBuildActions actions, string? dotNetPath, IDictionary<string, string>? environment)
{ {

View File

@@ -158,7 +158,7 @@ namespace Semmle.Autobuild.Cpp.Tests
bool IBuildActions.IsMonoInstalled() => IsMonoInstalled; bool IBuildActions.IsMonoInstalled() => IsMonoInstalled;
string IBuildActions.PathJoin(params string[] parts) string IBuildActions.PathCombine(params string[] parts)
{ {
return string.Join(IsWindows ? '\\' : '/', parts.Where(p => !string.IsNullOrWhiteSpace(p))); return string.Join(IsWindows ? '\\' : '/', parts.Where(p => !string.IsNullOrWhiteSpace(p)));
} }

View File

@@ -108,7 +108,7 @@ namespace Semmle.Autobuild.Shared
/// </summary> /// </summary>
/// <param name="path">The relative path.</param> /// <param name="path">The relative path.</param>
/// <returns>True iff the path was found.</returns> /// <returns>True iff the path was found.</returns>
public bool HasRelativePath(string path) => HasPath(Actions.PathJoin(RootDirectory, path)); public bool HasRelativePath(string path) => HasPath(Actions.PathCombine(RootDirectory, path));
/// <summary> /// <summary>
/// List of project/solution files to build. /// List of project/solution files to build.

View File

@@ -32,7 +32,7 @@ namespace Semmle.Autobuild.Shared
yield break; yield break;
// Attempt to use vswhere to find installations of Visual Studio // Attempt to use vswhere to find installations of Visual Studio
var vswhere = actions.PathJoin(programFilesx86, "Microsoft Visual Studio", "Installer", "vswhere.exe"); var vswhere = actions.PathCombine(programFilesx86, "Microsoft Visual Studio", "Installer", "vswhere.exe");
if (actions.FileExists(vswhere)) if (actions.FileExists(vswhere))
{ {
@@ -51,14 +51,14 @@ namespace Semmle.Autobuild.Shared
if (majorVersion < 15) if (majorVersion < 15)
{ {
// Visual Studio 2015 and below // Visual Studio 2015 and below
yield return new VcVarsBatFile(actions.PathJoin(vsInstallation.InstallationPath, @"VC\vcvarsall.bat"), majorVersion); yield return new VcVarsBatFile(actions.PathCombine(vsInstallation.InstallationPath, @"VC\vcvarsall.bat"), majorVersion);
} }
else else
{ {
// Visual Studio 2017 and above // Visual Studio 2017 and above
yield return new VcVarsBatFile(actions.PathJoin(vsInstallation.InstallationPath, @"VC\Auxiliary\Build\vcvars32.bat"), majorVersion); yield return new VcVarsBatFile(actions.PathCombine(vsInstallation.InstallationPath, @"VC\Auxiliary\Build\vcvars32.bat"), majorVersion);
yield return new VcVarsBatFile(actions.PathJoin(vsInstallation.InstallationPath, @"VC\Auxiliary\Build\vcvars64.bat"), majorVersion); yield return new VcVarsBatFile(actions.PathCombine(vsInstallation.InstallationPath, @"VC\Auxiliary\Build\vcvars64.bat"), majorVersion);
yield return new VcVarsBatFile(actions.PathJoin(vsInstallation.InstallationPath, @"Common7\Tools\VsDevCmd.bat"), majorVersion); yield return new VcVarsBatFile(actions.PathCombine(vsInstallation.InstallationPath, @"Common7\Tools\VsDevCmd.bat"), majorVersion);
} }
} }
// else: Skip installation without a version // else: Skip installation without a version
@@ -68,10 +68,10 @@ namespace Semmle.Autobuild.Shared
} }
// vswhere not installed or didn't run correctly - return legacy Visual Studio versions // vswhere not installed or didn't run correctly - return legacy Visual Studio versions
yield return new VcVarsBatFile(actions.PathJoin(programFilesx86, @"Microsoft Visual Studio 14.0\VC\vcvarsall.bat"), 14); yield return new VcVarsBatFile(actions.PathCombine(programFilesx86, @"Microsoft Visual Studio 14.0\VC\vcvarsall.bat"), 14);
yield return new VcVarsBatFile(actions.PathJoin(programFilesx86, @"Microsoft Visual Studio 12.0\VC\vcvarsall.bat"), 12); yield return new VcVarsBatFile(actions.PathCombine(programFilesx86, @"Microsoft Visual Studio 12.0\VC\vcvarsall.bat"), 12);
yield return new VcVarsBatFile(actions.PathJoin(programFilesx86, @"Microsoft Visual Studio 11.0\VC\vcvarsall.bat"), 11); yield return new VcVarsBatFile(actions.PathCombine(programFilesx86, @"Microsoft Visual Studio 11.0\VC\vcvarsall.bat"), 11);
yield return new VcVarsBatFile(actions.PathJoin(programFilesx86, @"Microsoft Visual Studio 10.0\VC\vcvarsall.bat"), 10); yield return new VcVarsBatFile(actions.PathCombine(programFilesx86, @"Microsoft Visual Studio 10.0\VC\vcvarsall.bat"), 10);
} }
/// <summary> /// <summary>

View File

@@ -60,7 +60,7 @@ namespace Semmle.Autobuild.Shared
// Use `nuget.exe` from source code repo, if present, otherwise first attempt with global // Use `nuget.exe` from source code repo, if present, otherwise first attempt with global
// `nuget` command, and if that fails, attempt to download `nuget.exe` from nuget.org // `nuget` command, and if that fails, attempt to download `nuget.exe` from nuget.org
var nuget = builder.GetFilename("nuget.exe").Select(t => t.Item1).FirstOrDefault() ?? "nuget"; var nuget = builder.GetFilename("nuget.exe").Select(t => t.Item1).FirstOrDefault() ?? "nuget";
var nugetDownloadPath = builder.Actions.PathJoin(FileUtils.GetTemporaryWorkingDirectory(builder.Actions.GetEnvironmentVariable, builder.Options.Language.UpperCaseName, out _), ".nuget", "nuget.exe"); var nugetDownloadPath = builder.Actions.PathCombine(FileUtils.GetTemporaryWorkingDirectory(builder.Actions.GetEnvironmentVariable, builder.Options.Language.UpperCaseName, out _), ".nuget", "nuget.exe");
var nugetDownloaded = false; var nugetDownloaded = false;
var ret = BuildScript.Success; var ret = BuildScript.Success;

View File

@@ -107,9 +107,8 @@ namespace Semmle.Autobuild.Shared
continue; continue;
} }
var includePath = builder.Actions.PathJoin(include.Value.Split('\\', StringSplitOptions.RemoveEmptyEntries)); var includePath = builder.Actions.PathCombine(include.Value.Split('\\', StringSplitOptions.RemoveEmptyEntries));
var path = Path.IsPathRooted(includePath) ? includePath : builder.Actions.PathJoin(DirectoryName, includePath); ret.Add(new Project<TAutobuildOptions>(builder, builder.Actions.PathCombine(DirectoryName, includePath)));
ret.Add(new Project<TAutobuildOptions>(builder, path));
} }
return ret; return ret;
}); });

View File

@@ -79,7 +79,7 @@ namespace Semmle.Autobuild.Shared
includedProjects = solution.ProjectsInOrder includedProjects = solution.ProjectsInOrder
.Where(p => p.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat) .Where(p => p.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat)
.Select(p => builder.Actions.PathJoin(DirectoryName, builder.Actions.PathJoin(p.RelativePath.Split('\\', StringSplitOptions.RemoveEmptyEntries)))) .Select(p => builder.Actions.PathCombine(DirectoryName, builder.Actions.PathCombine(p.RelativePath.Split('\\', StringSplitOptions.RemoveEmptyEntries))))
.Select(p => new Project<TAutobuildOptions>(builder, p)) .Select(p => new Project<TAutobuildOptions>(builder, p))
.ToArray(); .ToArray();
} }

View File

@@ -1,2 +0,0 @@
description: Restructure and rename types related to operations.
compatibility: full

View File

@@ -50,7 +50,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
return; return;
} }
var path = Path.Join(p, ParseFilePath(d)); var path = Path.Combine(p, ParseFilePath(d));
Paths.Add(path); Paths.Add(path);
Packages.Add(GetPackageName(p)); Packages.Add(GetPackageName(p));
} }

View File

@@ -75,7 +75,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
} }
} }
this.diagnosticsWriter = new DiagnosticsStream(Path.Join( this.diagnosticsWriter = new DiagnosticsStream(Path.Combine(
diagDirEnv ?? "", diagDirEnv ?? "",
$"dependency-manager-{DateTime.UtcNow:yyyyMMddHHmm}-{Environment.ProcessId}.jsonc")); $"dependency-manager-{DateTime.UtcNow:yyyyMMddHHmm}-{Environment.ProcessId}.jsonc"));
this.sourceDir = new DirectoryInfo(srcDir); this.sourceDir = new DirectoryInfo(srcDir);
@@ -327,7 +327,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
private void RemoveNugetPackageReference(string packagePrefix, ISet<AssemblyLookupLocation> dllLocations) private void RemoveNugetPackageReference(string packagePrefix, ISet<AssemblyLookupLocation> dllLocations)
{ {
var packageFolder = nugetPackageRestorer.PackageDirectory.DirInfo.FullName.ToLowerInvariant(); var packageFolder = nugetPackageRestorer.PackageDirectory.DirInfo.FullName.ToLowerInvariant();
var packagePathPrefix = Path.Join(packageFolder, packagePrefix.ToLowerInvariant()); var packagePathPrefix = Path.Combine(packageFolder, packagePrefix.ToLowerInvariant());
var toRemove = dllLocations.Where(s => s.Path.StartsWith(packagePathPrefix, StringComparison.InvariantCultureIgnoreCase)); var toRemove = dllLocations.Where(s => s.Path.StartsWith(packagePathPrefix, StringComparison.InvariantCultureIgnoreCase));
foreach (var path in toRemove) foreach (var path in toRemove)
{ {

View File

@@ -31,7 +31,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
} }
} }
private DotNet(ILogger logger, string? dotNetPath, TemporaryDirectory tempWorkingDirectory, DependabotProxy? dependabotProxy) : this(new DotNetCliInvoker(logger, Path.Join(dotNetPath ?? string.Empty, "dotnet"), dependabotProxy), logger, dotNetPath is null, tempWorkingDirectory) { } private DotNet(ILogger logger, string? dotNetPath, TemporaryDirectory tempWorkingDirectory, DependabotProxy? dependabotProxy) : this(new DotNetCliInvoker(logger, Path.Combine(dotNetPath ?? string.Empty, "dotnet"), dependabotProxy), logger, dotNetPath is null, tempWorkingDirectory) { }
internal static IDotNet Make(IDotNetCliInvoker dotnetCliInvoker, ILogger logger, bool runDotnetInfo) => new DotNet(dotnetCliInvoker, logger, runDotnetInfo); internal static IDotNet Make(IDotNetCliInvoker dotnetCliInvoker, ILogger logger, bool runDotnetInfo) => new DotNet(dotnetCliInvoker, logger, runDotnetInfo);
@@ -73,7 +73,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
var path = ".empty"; var path = ".empty";
if (tempWorkingDirectory != null) if (tempWorkingDirectory != null)
{ {
path = Path.Join(tempWorkingDirectory.ToString(), "emptyFakeDotnetRoot"); path = Path.Combine(tempWorkingDirectory.ToString(), "emptyFakeDotnetRoot");
Directory.CreateDirectory(path); Directory.CreateDirectory(path);
} }
@@ -303,7 +303,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
} }
else else
{ {
var dotnetInstallPath = actions.PathJoin(tempWorkingDirectory, ".dotnet", "dotnet-install.sh"); var dotnetInstallPath = actions.PathCombine(tempWorkingDirectory, ".dotnet", "dotnet-install.sh");
var downloadDotNetInstallSh = BuildScript.DownloadFile( var downloadDotNetInstallSh = BuildScript.DownloadFile(
"https://dot.net/v1/dotnet-install.sh", "https://dot.net/v1/dotnet-install.sh",
dotnetInstallPath, dotnetInstallPath,
@@ -339,7 +339,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}; };
} }
var dotnetInfo = InfoScript(actions, actions.PathJoin(path, "dotnet"), MinimalEnvironment.ToDictionary(), logger); var dotnetInfo = InfoScript(actions, actions.PathCombine(path, "dotnet"), MinimalEnvironment.ToDictionary(), logger);
Func<string, BuildScript> getInstallAndVerify = version => Func<string, BuildScript> getInstallAndVerify = version =>
// run `dotnet --info` after install, to check that it executes successfully // run `dotnet --info` after install, to check that it executes successfully
@@ -384,7 +384,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
/// </summary> /// </summary>
public static BuildScript WithDotNet(IBuildActions actions, ILogger logger, IEnumerable<string> files, string tempWorkingDirectory, bool shouldCleanUp, bool ensureDotNetAvailable, string? version, Func<string?, BuildScript> f) public static BuildScript WithDotNet(IBuildActions actions, ILogger logger, IEnumerable<string> files, string tempWorkingDirectory, bool shouldCleanUp, bool ensureDotNetAvailable, string? version, Func<string?, BuildScript> f)
{ {
var installDir = actions.PathJoin(tempWorkingDirectory, ".dotnet"); var installDir = actions.PathCombine(tempWorkingDirectory, ".dotnet");
var installScript = DownloadDotNet(actions, logger, files, tempWorkingDirectory, shouldCleanUp, installDir, version, ensureDotNetAvailable); var installScript = DownloadDotNet(actions, logger, files, tempWorkingDirectory, shouldCleanUp, installDir, version, ensureDotNetAvailable);
return BuildScript.Bind(installScript, installed => return BuildScript.Bind(installScript, installed =>
{ {

View File

@@ -12,7 +12,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
private string FullVersion => private string FullVersion =>
version.ToString(); version.ToString();
public string FullPath => Path.Join(dir, FullVersion); public string FullPath => Path.Combine(dir, FullVersion);
/** /**
* The full path to the reference assemblies for this runtime. * The full path to the reference assemblies for this runtime.
@@ -33,7 +33,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
{ {
directories[^2] = "packs"; directories[^2] = "packs";
directories[^1] = $"{directories[^1]}.Ref"; directories[^1] = $"{directories[^1]}.Ref";
return Path.Join(string.Join(Path.DirectorySeparatorChar, directories), FullVersion, "ref"); return Path.Combine(string.Join(Path.DirectorySeparatorChar, directories), FullVersion, "ref");
} }
return null; return null;
} }

View File

@@ -1,413 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Semmle.Util;
using Semmle.Util.Logging;
namespace Semmle.Extraction.CSharp.DependencyFetching
{
internal sealed partial class FeedManager : IDisposable
{
internal const string PublicNugetOrgFeed = "https://api.nuget.org/v3/index.json";
private readonly ILogger logger;
private readonly IDotNet dotnet;
private readonly FileProvider fileProvider;
private readonly DependabotProxy? dependabotProxy;
private readonly DependencyDirectory emptyPackageDirectory;
public ImmutableHashSet<string> PrivateRegistryFeeds { get; }
public bool HasPrivateRegistryFeeds { get; }
public bool CheckNugetFeedResponsiveness { get; } = EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.CheckNugetFeedResponsiveness);
public FeedManager(ILogger logger, IDotNet dotnet, DependabotProxy? dependabotProxy, FileProvider fileProvider)
{
this.logger = logger;
this.dotnet = dotnet;
this.dependabotProxy = dependabotProxy;
this.fileProvider = fileProvider;
PrivateRegistryFeeds = dependabotProxy?.RegistryURLs.ToImmutableHashSet() ?? [];
HasPrivateRegistryFeeds = PrivateRegistryFeeds.Count > 0;
emptyPackageDirectory = new DependencyDirectory("empty", "empty package", logger);
}
private string? GetDirectoryName(string path)
{
try
{
return new FileInfo(path).Directory?.FullName;
}
catch (Exception exc)
{
logger.LogWarning($"Failed to get directory of '{path}': {exc}");
}
return null;
}
private IEnumerable<string> GetFeeds(Func<IList<string>> getNugetFeeds)
{
var results = getNugetFeeds();
var regex = EnabledNugetFeed();
foreach (var result in results)
{
var match = regex.Match(result);
if (!match.Success)
{
logger.LogError($"Failed to parse feed from '{result}'");
continue;
}
var url = match.Groups[1].Value;
if (!url.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase) &&
!url.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
{
logger.LogInfo($"Skipping feed '{url}' as it is not a valid URL.");
continue;
}
if (!string.IsNullOrWhiteSpace(url))
{
yield return url;
}
}
}
private IEnumerable<string> GetFeedsFromFolder(string folderPath) =>
GetFeeds(() => dotnet.GetNugetFeedsFromFolder(folderPath));
private IEnumerable<string> GetFeedsFromNugetConfig(string nugetConfigPath) =>
GetFeeds(() => dotnet.GetNugetFeeds(nugetConfigPath));
private string FeedsToRestoreArgument(IEnumerable<string> feeds)
{
// If there are no feeds, we want to override any default feeds that `dotnet restore` would use by passing a dummy source argument.
if (!feeds.Any())
{
return $" -s \"{emptyPackageDirectory.DirInfo.FullName}\"";
}
// Add package sources. If any are present, they override all sources specified in
// the configuration file(s).
var feedArgs = new StringBuilder();
foreach (var feed in feeds)
{
feedArgs.Append($" -s \"{feed}\"");
}
return feedArgs.ToString();
}
/// <summary>
/// Constructs the list of NuGet sources to use for this restore.
/// (1) Use the feeds we get from `dotnet nuget list source`
/// (2) Use private registries, if they are configured
/// </summary>
/// <param name="path">Path to project/solution</param>
/// <param name="reachableFeeds">The set of reachable NuGet feeds.</param>
/// <returns>A string representing the NuGet sources argument for the restore command.</returns>
public string? MakeRestoreSourcesArgument(string path, HashSet<string> reachableFeeds)
{
// Do not construct a set of explicit NuGet sources to use for restore.
if (!CheckNugetFeedResponsiveness && !HasPrivateRegistryFeeds)
{
return null;
}
// Find the path specific feeds.
var folder = GetDirectoryName(path);
var feedsToConsider = folder is not null ? GetFeedsFromFolder(folder).ToHashSet() : new HashSet<string>();
if (HasPrivateRegistryFeeds)
{
feedsToConsider.UnionWith(PrivateRegistryFeeds);
}
var feedsToUse = CheckNugetFeedResponsiveness
? feedsToConsider.Where(reachableFeeds.Contains)
: feedsToConsider;
return FeedsToRestoreArgument(feedsToUse);
}
private (int initialTimeout, int tryCount) GetFeedRequestSettings(bool isFallback)
{
int timeoutMilliSeconds = isFallback && int.TryParse(Environment.GetEnvironmentVariable(EnvironmentVariableNames.NugetFeedResponsivenessInitialTimeoutForFallback), out timeoutMilliSeconds)
? timeoutMilliSeconds
: int.TryParse(Environment.GetEnvironmentVariable(EnvironmentVariableNames.NugetFeedResponsivenessInitialTimeout), out timeoutMilliSeconds)
? timeoutMilliSeconds
: 1000;
logger.LogDebug($"Initial timeout for NuGet feed reachability check is {timeoutMilliSeconds}ms.");
int tryCount = isFallback && int.TryParse(Environment.GetEnvironmentVariable(EnvironmentVariableNames.NugetFeedResponsivenessRequestCountForFallback), out tryCount)
? tryCount
: int.TryParse(Environment.GetEnvironmentVariable(EnvironmentVariableNames.NugetFeedResponsivenessRequestCount), out tryCount)
? tryCount
: 4;
logger.LogDebug($"Number of tries for NuGet feed reachability check is {tryCount}.");
return (timeoutMilliSeconds, tryCount);
}
private static async Task<HttpResponseMessage> ExecuteGetRequest(string address, HttpClient httpClient, CancellationToken cancellationToken)
{
return await httpClient.GetAsync(address, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
}
private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, out bool isTimeout)
{
logger.LogInfo($"Checking if NuGet feed '{feed}' is reachable...");
// Configure the HttpClient to be aware of the Dependabot Proxy, if used.
HttpClientHandler httpClientHandler = new();
if (dependabotProxy != null)
{
httpClientHandler.Proxy = new WebProxy(dependabotProxy.Address);
if (dependabotProxy.Certificate != null)
{
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, _) =>
{
if (chain is null || cert is null)
{
var msg = cert is null && chain is null
? "certificate and chain"
: chain is null
? "chain"
: "certificate";
logger.LogWarning($"Dependabot proxy certificate validation failed due to missing {msg}");
return false;
}
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(dependabotProxy.Certificate);
return chain.Build(cert);
};
}
}
using HttpClient client = new(httpClientHandler);
isTimeout = false;
for (var i = 0; i < tryCount; i++)
{
using var cts = new CancellationTokenSource();
cts.CancelAfter(timeoutMilliSeconds);
try
{
logger.LogInfo($"Attempt {i + 1}/{tryCount} to reach NuGet feed '{feed}'.");
using var response = ExecuteGetRequest(feed, client, cts.Token).GetAwaiter().GetResult();
response.EnsureSuccessStatusCode();
logger.LogInfo($"Querying NuGet feed '{feed}' succeeded.");
return true;
}
catch (Exception exc)
{
if (exc is TaskCanceledException tce &&
tce.CancellationToken == cts.Token &&
cts.Token.IsCancellationRequested)
{
logger.LogInfo($"Didn't receive answer from NuGet feed '{feed}' in {timeoutMilliSeconds}ms.");
timeoutMilliSeconds *= 2;
continue;
}
logger.LogInfo($"Querying NuGet feed '{feed}' failed. The reason for the failure: {exc.Message}");
return false;
}
}
logger.LogWarning($"Didn't receive answer from NuGet feed '{feed}'. Tried it {tryCount} times.");
isTimeout = true;
return false;
}
/// <summary>
/// Retrieves a list of excluded NuGet feeds from the corresponding environment variable.
/// </summary>
private HashSet<string> GetExcludedFeeds()
{
var excludedFeeds = EnvironmentVariables.GetURLs(EnvironmentVariableNames.ExcludedNugetFeedsFromResponsivenessCheck)
.ToHashSet();
if (excludedFeeds.Count > 0)
{
logger.LogInfo($"Excluded NuGet feeds from responsiveness check: {string.Join(", ", excludedFeeds.OrderBy(f => f))}");
}
return excludedFeeds;
}
/// <summary>
/// Checks that we can connect to the specified NuGet feeds.
/// </summary>
/// <param name="feeds">The set of package feeds to check.</param>
/// <param name="reachableFeeds">The list of feeds that were reachable.</param>
/// <returns>
/// True if there is a timeout when trying to reach the feeds (excluding any feeds that are configured
/// to be excluded from the check) or false otherwise.
/// </returns>
public bool CheckSpecifiedFeeds(HashSet<string> feeds, out HashSet<string> reachableFeeds)
{
// Exclude any feeds from the feed check that are configured by the corresponding environment variable.
// These feeds are always assumed to be reachable.
var excludedFeeds = GetExcludedFeeds();
HashSet<string> feedsToCheck = feeds.Where(feed =>
{
if (excludedFeeds.Contains(feed))
{
logger.LogInfo($"Not checking reachability of NuGet feed '{feed}' as it is in the list of excluded feeds.");
return false;
}
return true;
}).ToHashSet();
reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false, out var isTimeout).ToHashSet();
// Always consider feeds excluded for the reachability check as reachable.
reachableFeeds.UnionWith(feeds.Where(feed => excludedFeeds.Contains(feed)));
return isTimeout;
}
public bool IsDefaultFeedReachable()
{
if (CheckNugetFeedResponsiveness)
{
var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback: false);
return IsFeedReachable(PublicNugetOrgFeed, initialTimeout, tryCount, out var _);
}
return true;
}
/// <summary>
/// Tests which of the feeds given by <paramref name="feedsToCheck"/> are reachable.
/// </summary>
/// <param name="feedsToCheck">The feeds to check.</param>
/// <param name="isFallback">Whether the feeds are fallback feeds or not.</param>
/// <param name="isTimeout">Whether a timeout occurred while checking the feeds.</param>
/// <returns>The list of feeds that could be reached.</returns>
private List<string> GetReachableNuGetFeeds(HashSet<string> feedsToCheck, bool isFallback, out bool isTimeout)
{
var fallbackStr = isFallback ? "fallback " : "";
logger.LogInfo($"Checking {fallbackStr}NuGet feed reachability on feeds: {string.Join(", ", feedsToCheck.OrderBy(f => f))}");
var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback);
var timeout = false;
var reachableFeeds = feedsToCheck
.Where(feed =>
{
var reachable = IsFeedReachable(feed, initialTimeout, tryCount, out var feedTimeout);
timeout |= feedTimeout;
return reachable;
})
.ToList();
if (reachableFeeds.Count == 0)
{
logger.LogWarning($"No {fallbackStr}NuGet feeds are reachable.");
}
else
{
logger.LogInfo($"Reachable {fallbackStr}NuGet feeds: {string.Join(", ", reachableFeeds.OrderBy(f => f))}");
}
isTimeout = timeout;
return reachableFeeds;
}
public List<string> GetReachableFallbackNugetFeeds(HashSet<string>? feedsFromNugetConfigs)
{
var fallbackFeeds = EnvironmentVariables.GetURLs(EnvironmentVariableNames.FallbackNugetFeeds).ToHashSet();
if (fallbackFeeds.Count == 0)
{
fallbackFeeds.Add(PublicNugetOrgFeed);
logger.LogInfo($"No fallback NuGet feeds specified. Adding default feed: {PublicNugetOrgFeed}");
var shouldAddNugetConfigFeeds = EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.AddNugetConfigFeedsToFallback);
logger.LogInfo($"Adding feeds from nuget.config to fallback restore: {shouldAddNugetConfigFeeds}");
if (shouldAddNugetConfigFeeds && feedsFromNugetConfigs?.Count > 0)
{
// There are some feeds in `feedsFromNugetConfigs` that have already been checked for reachability, we could skip those.
// But we might use different responsiveness testing settings when we try them in the fallback logic, so checking them again is safer.
fallbackFeeds.UnionWith(feedsFromNugetConfigs);
logger.LogInfo($"Using NuGet feeds from nuget.config files as fallback feeds: {string.Join(", ", feedsFromNugetConfigs.OrderBy(f => f))}");
}
}
return GetReachableNuGetFeeds(fallbackFeeds, isFallback: true, out var _);
}
public (HashSet<string> explicitFeeds, HashSet<string> allFeeds) GetAllFeeds()
{
var nugetConfigs = fileProvider.NugetConfigs;
// Find feeds that are explicitly configured in the NuGet configuration files that we found.
var explicitFeeds = nugetConfigs
.SelectMany(GetFeedsFromNugetConfig)
.ToHashSet();
if (explicitFeeds.Count > 0)
{
logger.LogInfo($"Found {explicitFeeds.Count} NuGet feeds in nuget.config files: {string.Join(", ", explicitFeeds.OrderBy(f => f))}");
}
else
{
logger.LogDebug("No NuGet feeds found in nuget.config files.");
}
// If private package registries are configured for C#, then consider those
// in addition to the ones that are configured in `nuget.config` files.
if (HasPrivateRegistryFeeds)
{
logger.LogInfo($"Found {PrivateRegistryFeeds.Count} private registry feeds configured for C#: {string.Join(", ", PrivateRegistryFeeds.OrderBy(f => f))}");
explicitFeeds.UnionWith(PrivateRegistryFeeds);
}
HashSet<string> allFeeds = [];
// Add all explicitFeeds to the set of all feeds.
allFeeds.UnionWith(explicitFeeds);
// Obtain the list of feeds from the root source directory.
// If a NuGet file is present it will be respected, otherwise we will just get the machine/environment specific feeds.
var nugetFeedsFromRoot = GetFeedsFromFolder(fileProvider.SourceDir.FullName);
allFeeds.UnionWith(nugetFeedsFromRoot);
if (nugetConfigs.Count > 0)
{
var nugetConfigFeeds = nugetConfigs
.Select(GetDirectoryName)
.Where(folder => folder != null)
.SelectMany(folder => GetFeedsFromFolder(folder!))
.ToHashSet();
allFeeds.UnionWith(nugetConfigFeeds);
}
logger.LogInfo($"Found {allFeeds.Count} NuGet feeds (with inherited ones) in nuget.config files: {string.Join(", ", allFeeds.OrderBy(f => f))}");
return (explicitFeeds, allFeeds);
}
[GeneratedRegex(@"^E\s(.*)$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)]
private static partial Regex EnabledNugetFeed();
public void Dispose()
{
emptyPackageDirectory.Dispose();
}
}
}

View File

@@ -0,0 +1,304 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Semmle.Util;
namespace Semmle.Extraction.CSharp.DependencyFetching
{
/// <summary>
/// Manage the downloading of NuGet packages with nuget.exe.
/// Locates packages in a source tree and downloads all of the
/// referenced assemblies to a temp folder.
/// </summary>
internal class NugetExeWrapper : IDisposable
{
private readonly string? nugetExe;
private readonly Semmle.Util.Logging.ILogger logger;
public int PackageCount => fileProvider.PackagesConfigs.Count;
private readonly string? backupNugetConfig;
private readonly string? nugetConfigPath;
private readonly FileProvider fileProvider;
/// <summary>
/// The packages directory.
/// This will be in the user-specified or computed Temp location
/// so as to not trample the source tree.
/// </summary>
private readonly DependencyDirectory packageDirectory;
/// <summary>
/// Create the package manager for a specified source tree.
/// </summary>
public NugetExeWrapper(FileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, Func<bool> useDefaultFeed)
{
this.fileProvider = fileProvider;
this.packageDirectory = packageDirectory;
this.logger = logger;
if (fileProvider.PackagesConfigs.Count > 0)
{
logger.LogInfo($"Found packages.config files, trying to use nuget.exe for package restore");
nugetExe = ResolveNugetExe();
if (HasNoPackageSource() && useDefaultFeed())
{
// We only modify or add a top level nuget.config file
nugetConfigPath = Path.Combine(fileProvider.SourceDir.FullName, "nuget.config");
try
{
if (File.Exists(nugetConfigPath))
{
var tempFolderPath = FileUtils.GetTemporaryWorkingDirectory(out _);
do
{
backupNugetConfig = Path.Combine(tempFolderPath, Path.GetRandomFileName());
}
while (File.Exists(backupNugetConfig));
File.Copy(nugetConfigPath, backupNugetConfig, true);
}
else
{
File.WriteAllText(nugetConfigPath,
"""
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
</packageSources>
</configuration>
""");
}
AddDefaultPackageSource(nugetConfigPath);
}
catch (Exception e)
{
logger.LogError($"Failed to add default package source to {nugetConfigPath}: {e}");
}
}
}
}
/// <summary>
/// Tries to find the location of `nuget.exe`. It looks for
/// - the environment variable specifying a location,
/// - files in the repository,
/// - tries to resolve nuget from the PATH, or
/// - downloads it if it is not found.
/// </summary>
private string ResolveNugetExe()
{
var envVarPath = Environment.GetEnvironmentVariable(EnvironmentVariableNames.NugetExePath);
if (!string.IsNullOrEmpty(envVarPath))
{
logger.LogInfo($"Using nuget.exe from environment variable: '{envVarPath}'");
return envVarPath;
}
try
{
return DownloadNugetExe(fileProvider.SourceDir.FullName);
}
catch (Exception exc)
{
logger.LogInfo($"Download of nuget.exe failed: {exc.Message}");
}
var nugetExesInRepo = fileProvider.NugetExes;
if (nugetExesInRepo.Count > 1)
{
logger.LogInfo($"Found multiple nuget.exe files in the repository: {string.Join(", ", nugetExesInRepo.OrderBy(s => s))}");
}
if (nugetExesInRepo.Count > 0)
{
var path = nugetExesInRepo.First();
logger.LogInfo($"Using nuget.exe from path '{path}'");
return path;
}
var executableName = Win32.IsWindows() ? "nuget.exe" : "nuget";
var nugetPath = FileUtils.FindProgramOnPath(executableName);
if (nugetPath is not null)
{
nugetPath = Path.Combine(nugetPath, executableName);
logger.LogInfo($"Using nuget.exe from PATH: {nugetPath}");
return nugetPath;
}
throw new Exception("Could not find or download nuget.exe.");
}
private string DownloadNugetExe(string sourceDir)
{
var directory = Path.Combine(sourceDir, ".nuget");
var nuget = Path.Combine(directory, "nuget.exe");
// Nuget.exe already exists in the .nuget directory.
if (File.Exists(nuget))
{
logger.LogInfo($"Found nuget.exe at {nuget}");
return nuget;
}
Directory.CreateDirectory(directory);
logger.LogInfo("Attempting to download nuget.exe");
FileUtils.DownloadFile(FileUtils.NugetExeUrl, nuget, logger);
logger.LogInfo($"Downloaded nuget.exe to {nuget}");
return nuget;
}
private bool RunWithMono => !Win32.IsWindows() && !string.IsNullOrEmpty(Path.GetExtension(nugetExe));
/// <summary>
/// Restore all packages in the specified packages.config file.
/// </summary>
/// <param name="packagesConfig">The packages.config file.</param>
private bool TryRestoreNugetPackage(string packagesConfig)
{
logger.LogInfo($"Restoring file \"{packagesConfig}\"...");
/* Use nuget.exe to install a package.
* Note that there is a clutch of NuGet assemblies which could be used to
* invoke this directly, which would arguably be nicer. However they are
* really unwieldy and this solution works for now.
*/
string exe, args;
if (RunWithMono)
{
exe = "mono";
args = $"\"{nugetExe}\" install -OutputDirectory \"{packageDirectory}\" \"{packagesConfig}\"";
}
else
{
exe = nugetExe!;
args = $"install -OutputDirectory \"{packageDirectory}\" \"{packagesConfig}\"";
}
var pi = new ProcessStartInfo(exe, args)
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
var threadId = Environment.CurrentManagedThreadId;
void onOut(string s) => logger.LogDebug(s, threadId);
void onError(string s) => logger.LogError(s, threadId);
var exitCode = pi.ReadOutput(out _, onOut, onError);
if (exitCode != 0)
{
logger.LogError($"Command {pi.FileName} {pi.Arguments} failed with exit code {exitCode}");
return false;
}
else
{
logger.LogInfo($"Restored file \"{packagesConfig}\"");
return true;
}
}
/// <summary>
/// Download the packages to the temp folder.
/// </summary>
public int InstallPackages()
{
return fileProvider.PackagesConfigs.Count(TryRestoreNugetPackage);
}
private bool HasNoPackageSource()
{
if (Win32.IsWindows())
{
return false;
}
try
{
logger.LogInfo("Checking if default package source is available...");
RunMonoNugetCommand("sources list -ForceEnglishOutput", out var stdout);
if (stdout.All(line => line != "No sources found."))
{
return false;
}
return true;
}
catch (Exception e)
{
logger.LogWarning($"Failed to check if default package source is added: {e}");
return false;
}
}
private void RunMonoNugetCommand(string command, out IList<string> stdout)
{
string exe, args;
if (RunWithMono)
{
exe = "mono";
args = $"\"{nugetExe}\" {command}";
}
else
{
exe = nugetExe!;
args = command;
}
var pi = new ProcessStartInfo(exe, args)
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
var threadId = Environment.CurrentManagedThreadId;
void onOut(string s) => logger.LogDebug(s, threadId);
void onError(string s) => logger.LogError(s, threadId);
pi.ReadOutput(out stdout, onOut, onError);
}
private void AddDefaultPackageSource(string nugetConfig)
{
logger.LogInfo("Adding default package source...");
RunMonoNugetCommand($"sources add -Name DefaultNugetOrg -Source {NugetPackageRestorer.PublicNugetOrgFeed} -ConfigFile \"{nugetConfig}\"", out _);
}
public void Dispose()
{
if (nugetConfigPath is null)
{
return;
}
try
{
if (backupNugetConfig is null)
{
logger.LogInfo("Removing nuget.config file");
File.Delete(nugetConfigPath);
return;
}
logger.LogInfo("Reverting nuget.config file content");
// The content of the original nuget.config file is reverted without changing the file's attributes or casing:
using (var backup = File.OpenRead(backupNugetConfig))
using (var current = File.OpenWrite(nugetConfigPath))
{
current.SetLength(0); // Truncate file
backup.CopyTo(current); // Restore original content
}
logger.LogInfo("Deleting backup nuget.config file");
File.Delete(backupNugetConfig);
}
catch (Exception exc)
{
logger.LogError($"Failed to restore original nuget.config file: {exc}");
}
}
}
}

View File

@@ -4,6 +4,9 @@ using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
@@ -16,19 +19,24 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
{ {
internal sealed partial class NugetPackageRestorer : IDisposable internal sealed partial class NugetPackageRestorer : IDisposable
{ {
internal const string PublicNugetOrgFeed = "https://api.nuget.org/v3/index.json";
private readonly FileProvider fileProvider; private readonly FileProvider fileProvider;
private readonly FileContent fileContent; private readonly FileContent fileContent;
private readonly IDotNet dotnet; private readonly IDotNet dotnet;
private readonly DependabotProxy? dependabotProxy;
private readonly IDiagnosticsWriter diagnosticsWriter; private readonly IDiagnosticsWriter diagnosticsWriter;
private readonly DependencyDirectory legacyPackageDirectory; private readonly DependencyDirectory legacyPackageDirectory;
private readonly DependencyDirectory missingPackageDirectory; private readonly DependencyDirectory missingPackageDirectory;
private readonly DependencyDirectory emptyPackageDirectory;
private readonly ILogger logger; private readonly ILogger logger;
private readonly ICompilationInfoContainer compilationInfoContainer; private readonly ICompilationInfoContainer compilationInfoContainer;
private readonly FeedManager feedManager; private readonly bool checkNugetFeedResponsiveness = EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.CheckNugetFeedResponsiveness);
private readonly ImmutableHashSet<string> privateRegistryFeeds;
private readonly bool hasPrivateRegistryFeeds;
public DependencyDirectory PackageDirectory { get; } public DependencyDirectory PackageDirectory { get; }
public NugetPackageRestorer( public NugetPackageRestorer(
FileProvider fileProvider, FileProvider fileProvider,
FileContent fileContent, FileContent fileContent,
@@ -41,6 +49,9 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
this.fileProvider = fileProvider; this.fileProvider = fileProvider;
this.fileContent = fileContent; this.fileContent = fileContent;
this.dotnet = dotnet; this.dotnet = dotnet;
this.dependabotProxy = dependabotProxy;
this.privateRegistryFeeds = dependabotProxy?.RegistryURLs.ToImmutableHashSet() ?? [];
this.hasPrivateRegistryFeeds = privateRegistryFeeds.Count > 0;
this.diagnosticsWriter = diagnosticsWriter; this.diagnosticsWriter = diagnosticsWriter;
this.logger = logger; this.logger = logger;
this.compilationInfoContainer = compilationInfoContainer; this.compilationInfoContainer = compilationInfoContainer;
@@ -48,7 +59,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
PackageDirectory = new DependencyDirectory("packages", "package", logger); PackageDirectory = new DependencyDirectory("packages", "package", logger);
legacyPackageDirectory = new DependencyDirectory("legacypackages", "legacy package", logger); legacyPackageDirectory = new DependencyDirectory("legacypackages", "legacy package", logger);
missingPackageDirectory = new DependencyDirectory("missingpackages", "missing package", logger); missingPackageDirectory = new DependencyDirectory("missingpackages", "missing package", logger);
feedManager = new FeedManager(logger, dotnet, dependabotProxy, fileProvider); emptyPackageDirectory = new DependencyDirectory("empty", "empty package", logger);
} }
public string? TryRestore(string package) public string? TryRestore(string package)
@@ -107,22 +118,20 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
public HashSet<AssemblyLookupLocation> Restore() public HashSet<AssemblyLookupLocation> Restore()
{ {
var assemblyLookupLocations = new HashSet<AssemblyLookupLocation>(); var assemblyLookupLocations = new HashSet<AssemblyLookupLocation>();
logger.LogInfo($"Checking NuGet feed responsiveness: {feedManager.CheckNugetFeedResponsiveness}"); logger.LogInfo($"Checking NuGet feed responsiveness: {checkNugetFeedResponsiveness}");
compilationInfoContainer.CompilationInfos.Add(("NuGet feed responsiveness checked", feedManager.CheckNugetFeedResponsiveness ? "1" : "0")); compilationInfoContainer.CompilationInfos.Add(("NuGet feed responsiveness checked", checkNugetFeedResponsiveness ? "1" : "0"));
HashSet<string> explicitFeeds = []; HashSet<string> explicitFeeds = [];
HashSet<string> reachableFeeds = []; HashSet<string> reachableFeeds = [];
try try
{ {
EmitNugetConfigDiagnostics();
// Find feeds that are configured in NuGet.config files and divide them into ones that // Find feeds that are configured in NuGet.config files and divide them into ones that
// are explicitly configured for the project or by a private registry, and "all feeds" // are explicitly configured for the project or by a private registry, and "all feeds"
// (including inherited ones) from other locations on the host outside of the working directory. // (including inherited ones) from other locations on the host outside of the working directory.
(explicitFeeds, var allFeeds) = feedManager.GetAllFeeds(); (explicitFeeds, var allFeeds) = GetAllFeeds();
if (feedManager.CheckNugetFeedResponsiveness) if (checkNugetFeedResponsiveness)
{ {
var inheritedFeeds = allFeeds.Except(explicitFeeds).ToHashSet(); var inheritedFeeds = allFeeds.Except(explicitFeeds).ToHashSet();
@@ -131,7 +140,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
compilationInfoContainer.CompilationInfos.Add(("Inherited NuGet feed count", inheritedFeeds.Count.ToString())); compilationInfoContainer.CompilationInfos.Add(("Inherited NuGet feed count", inheritedFeeds.Count.ToString()));
} }
var timeout = feedManager.CheckSpecifiedFeeds(explicitFeeds, out var reachableExplicitFeeds); var timeout = CheckSpecifiedFeeds(explicitFeeds, out var reachableExplicitFeeds);
reachableFeeds.UnionWith(reachableExplicitFeeds); reachableFeeds.UnionWith(reachableExplicitFeeds);
var allExplicitReachable = explicitFeeds.Count == reachableExplicitFeeds.Count; var allExplicitReachable = explicitFeeds.Count == reachableExplicitFeeds.Count;
@@ -148,17 +157,17 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
} }
// Inherited feeds should only be used, if they are indeed reachable (as they may be environment specific). // Inherited feeds should only be used, if they are indeed reachable (as they may be environment specific).
feedManager.CheckSpecifiedFeeds(inheritedFeeds, out var reachableInheritedFeeds); CheckSpecifiedFeeds(inheritedFeeds, out var reachableInheritedFeeds);
reachableFeeds.UnionWith(reachableInheritedFeeds); reachableFeeds.UnionWith(reachableInheritedFeeds);
} }
using (var packagesConfigRestore = PackagesConfigRestoreFactory.Create(fileProvider, legacyPackageDirectory, logger, feedManager.IsDefaultFeedReachable)) using (var nuget = new NugetExeWrapper(fileProvider, legacyPackageDirectory, logger, IsDefaultFeedReachable))
{ {
var count = packagesConfigRestore.InstallPackages(); var count = nuget.InstallPackages();
if (packagesConfigRestore.PackageCount > 0) if (nuget.PackageCount > 0)
{ {
compilationInfoContainer.CompilationInfos.Add(("packages.config files", packagesConfigRestore.PackageCount.ToString())); compilationInfoContainer.CompilationInfos.Add(("packages.config files", nuget.PackageCount.ToString()));
compilationInfoContainer.CompilationInfos.Add(("Successfully restored packages.config files", count.ToString())); compilationInfoContainer.CompilationInfos.Add(("Successfully restored packages.config files", count.ToString()));
} }
} }
@@ -200,13 +209,13 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
var paths = dependencies var paths = dependencies
.Paths .Paths
.Select(d => Path.Join(PackageDirectory.DirInfo.FullName, d)) .Select(d => Path.Combine(PackageDirectory.DirInfo.FullName, d))
.ToList(); .ToList();
assemblyLookupLocations.UnionWith(paths.Select(p => new AssemblyLookupLocation(p))); assemblyLookupLocations.UnionWith(paths.Select(p => new AssemblyLookupLocation(p)));
var usedPackageNames = GetAllUsedPackageDirNames(dependencies); var usedPackageNames = GetAllUsedPackageDirNames(dependencies);
var missingPackageLocation = feedManager.CheckNugetFeedResponsiveness var missingPackageLocation = checkNugetFeedResponsiveness
? DownloadMissingPackagesFromSpecificFeeds(usedPackageNames, explicitFeeds) ? DownloadMissingPackagesFromSpecificFeeds(usedPackageNames, explicitFeeds)
: DownloadMissingPackages(usedPackageNames); : DownloadMissingPackages(usedPackageNames);
@@ -217,6 +226,79 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
return assemblyLookupLocations; return assemblyLookupLocations;
} }
/// <summary>
/// Tests which of the feeds given by <paramref name="feedsToCheck"/> are reachable.
/// </summary>
/// <param name="feedsToCheck">The feeds to check.</param>
/// <param name="isFallback">Whether the feeds are fallback feeds or not.</param>
/// <param name="isTimeout">Whether a timeout occurred while checking the feeds.</param>
/// <returns>The list of feeds that could be reached.</returns>
private List<string> GetReachableNuGetFeeds(HashSet<string> feedsToCheck, bool isFallback, out bool isTimeout)
{
var fallbackStr = isFallback ? "fallback " : "";
logger.LogInfo($"Checking {fallbackStr}NuGet feed reachability on feeds: {string.Join(", ", feedsToCheck.OrderBy(f => f))}");
var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback);
var timeout = false;
var reachableFeeds = feedsToCheck
.Where(feed =>
{
var reachable = IsFeedReachable(feed, initialTimeout, tryCount, out var feedTimeout);
timeout |= feedTimeout;
return reachable;
})
.ToList();
if (reachableFeeds.Count == 0)
{
logger.LogWarning($"No {fallbackStr}NuGet feeds are reachable.");
}
else
{
logger.LogInfo($"Reachable {fallbackStr}NuGet feeds: {string.Join(", ", reachableFeeds.OrderBy(f => f))}");
}
isTimeout = timeout;
return reachableFeeds;
}
private bool IsDefaultFeedReachable()
{
if (checkNugetFeedResponsiveness)
{
var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback: false);
return IsFeedReachable(PublicNugetOrgFeed, initialTimeout, tryCount, out var _);
}
return true;
}
private List<string> GetReachableFallbackNugetFeeds(HashSet<string>? feedsFromNugetConfigs)
{
var fallbackFeeds = EnvironmentVariables.GetURLs(EnvironmentVariableNames.FallbackNugetFeeds).ToHashSet();
if (fallbackFeeds.Count == 0)
{
fallbackFeeds.Add(PublicNugetOrgFeed);
logger.LogInfo($"No fallback NuGet feeds specified. Adding default feed: {PublicNugetOrgFeed}");
var shouldAddNugetConfigFeeds = EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.AddNugetConfigFeedsToFallback);
logger.LogInfo($"Adding feeds from nuget.config to fallback restore: {shouldAddNugetConfigFeeds}");
if (shouldAddNugetConfigFeeds && feedsFromNugetConfigs?.Count > 0)
{
// There are some feeds in `feedsFromNugetConfigs` that have already been checked for reachability, we could skip those.
// But we might use different responsiveness testing settings when we try them in the fallback logic, so checking them again is safer.
fallbackFeeds.UnionWith(feedsFromNugetConfigs);
logger.LogInfo($"Using NuGet feeds from nuget.config files as fallback feeds: {string.Join(", ", feedsFromNugetConfigs.OrderBy(f => f))}");
}
}
var reachableFallbackFeeds = GetReachableNuGetFeeds(fallbackFeeds, isFallback: true, out var _);
compilationInfoContainer.CompilationInfos.Add(("Reachable fallback NuGet feed count", reachableFallbackFeeds.Count.ToString()));
return reachableFallbackFeeds;
}
/// <summary> /// <summary>
/// Executes `dotnet restore` on all solution files in solutions. /// Executes `dotnet restore` on all solution files in solutions.
@@ -239,7 +321,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
var projects = fileProvider.Solutions.SelectMany(solution => var projects = fileProvider.Solutions.SelectMany(solution =>
{ {
logger.LogInfo($"Restoring solution {solution}..."); logger.LogInfo($"Restoring solution {solution}...");
var nugetSources = feedManager.MakeRestoreSourcesArgument(solution, reachableFeeds); var nugetSources = MakeRestoreSourcesArgument(solution, reachableFeeds);
var res = dotnet.Restore(new(solution, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, NugetSources: nugetSources, TargetWindows: isWindows)); var res = dotnet.Restore(new(solution, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, NugetSources: nugetSources, TargetWindows: isWindows));
if (res.Success) if (res.Success)
{ {
@@ -264,6 +346,57 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
return projects; return projects;
} }
private string FeedsToRestoreArgument(IEnumerable<string> feeds)
{
// If there are no feeds, we want to override any default feeds that `dotnet restore` would use by passing a dummy source argument.
if (!feeds.Any())
{
return $" -s \"{emptyPackageDirectory.DirInfo.FullName}\"";
}
// Add package sources. If any are present, they override all sources specified in
// the configuration file(s).
var feedArgs = new StringBuilder();
foreach (var feed in feeds)
{
feedArgs.Append($" -s \"{feed}\"");
}
return feedArgs.ToString();
}
/// <summary>
/// Constructs the list of NuGet sources to use for this restore.
/// (1) Use the feeds we get from `dotnet nuget list source`
/// (2) Use private registries, if they are configured
/// </summary>
/// <param name="path">Path to project/solution</param>
/// <param name="reachableFeeds">The set of reachable NuGet feeds.</param>
/// <returns>A string representing the NuGet sources argument for the restore command.</returns>
private string? MakeRestoreSourcesArgument(string path, HashSet<string> reachableFeeds)
{
// Do not construct an set of explicit NuGet sources to use for restore.
if (!checkNugetFeedResponsiveness && !hasPrivateRegistryFeeds)
{
return null;
}
// Find the path specific feeds.
var folder = GetDirectoryName(path);
var feedsToConsider = folder is not null ? GetFeeds(() => dotnet.GetNugetFeedsFromFolder(folder)).ToHashSet() : [];
if (hasPrivateRegistryFeeds)
{
feedsToConsider.UnionWith(privateRegistryFeeds);
}
var feedsToUse = checkNugetFeedResponsiveness
? feedsToConsider.Where(reachableFeeds.Contains)
: feedsToConsider;
return FeedsToRestoreArgument(feedsToUse);
}
/// <summary> /// <summary>
/// Executes `dotnet restore` on all projects in projects. /// Executes `dotnet restore` on all projects in projects.
/// This is done in parallel for performance reasons. /// This is done in parallel for performance reasons.
@@ -288,7 +421,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
foreach (var project in projectGroup) foreach (var project in projectGroup)
{ {
logger.LogInfo($"Restoring project {project}..."); logger.LogInfo($"Restoring project {project}...");
var nugetSources = feedManager.MakeRestoreSourcesArgument(project, reachableFeeds); var nugetSources = MakeRestoreSourcesArgument(project, reachableFeeds);
var res = dotnet.Restore(new(project, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, NugetSources: nugetSources, TargetWindows: isWindows)); var res = dotnet.Restore(new(project, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, NugetSources: nugetSources, TargetWindows: isWindows));
assets.AddDependenciesRange(res.AssetsFilePaths); assets.AddDependenciesRange(res.AssetsFilePaths);
lock (sync) lock (sync)
@@ -317,9 +450,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
private AssemblyLookupLocation? DownloadMissingPackagesFromSpecificFeeds(IEnumerable<string> usedPackageNames, HashSet<string>? feedsFromNugetConfigs) private AssemblyLookupLocation? DownloadMissingPackagesFromSpecificFeeds(IEnumerable<string> usedPackageNames, HashSet<string>? feedsFromNugetConfigs)
{ {
var reachableFallbackFeeds = feedManager.GetReachableFallbackNugetFeeds(feedsFromNugetConfigs); var reachableFallbackFeeds = GetReachableFallbackNugetFeeds(feedsFromNugetConfigs);
compilationInfoContainer.CompilationInfos.Add(("Reachable fallback NuGet feed count", reachableFallbackFeeds.Count.ToString()));
if (reachableFallbackFeeds.Count > 0) if (reachableFallbackFeeds.Count > 0)
{ {
return DownloadMissingPackages(usedPackageNames, fallbackNugetFeeds: reachableFallbackFeeds); return DownloadMissingPackages(usedPackageNames, fallbackNugetFeeds: reachableFallbackFeeds);
@@ -396,7 +527,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
var sb = new StringBuilder(); var sb = new StringBuilder();
fallbackNugetFeeds.ForEach((feed, index) => sb.AppendLine($"<add key=\"feed{index}\" value=\"{feed}\" />")); fallbackNugetFeeds.ForEach((feed, index) => sb.AppendLine($"<add key=\"feed{index}\" value=\"{feed}\" />"));
var nugetConfigPath = Path.Join(folderPath, "nuget.config"); var nugetConfigPath = Path.Combine(folderPath, "nuget.config");
logger.LogInfo($"Creating fallback nuget.config file {nugetConfigPath}."); logger.LogInfo($"Creating fallback nuget.config file {nugetConfigPath}.");
File.WriteAllText(nugetConfigPath, File.WriteAllText(nugetConfigPath,
$""" $"""
@@ -605,6 +736,147 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
} }
} }
private static async Task<HttpResponseMessage> ExecuteGetRequest(string address, HttpClient httpClient, CancellationToken cancellationToken)
{
return await httpClient.GetAsync(address, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
}
private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, out bool isTimeout)
{
logger.LogInfo($"Checking if NuGet feed '{feed}' is reachable...");
// Configure the HttpClient to be aware of the Dependabot Proxy, if used.
HttpClientHandler httpClientHandler = new();
if (dependabotProxy != null)
{
httpClientHandler.Proxy = new WebProxy(dependabotProxy.Address);
if (dependabotProxy.Certificate != null)
{
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, _) =>
{
if (chain is null || cert is null)
{
var msg = cert is null && chain is null
? "certificate and chain"
: chain is null
? "chain"
: "certificate";
logger.LogWarning($"Dependabot proxy certificate validation failed due to missing {msg}");
return false;
}
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(dependabotProxy.Certificate);
return chain.Build(cert);
};
}
}
using HttpClient client = new(httpClientHandler);
isTimeout = false;
for (var i = 0; i < tryCount; i++)
{
using var cts = new CancellationTokenSource();
cts.CancelAfter(timeoutMilliSeconds);
try
{
logger.LogInfo($"Attempt {i + 1}/{tryCount} to reach NuGet feed '{feed}'.");
using var response = ExecuteGetRequest(feed, client, cts.Token).GetAwaiter().GetResult();
response.EnsureSuccessStatusCode();
logger.LogInfo($"Querying NuGet feed '{feed}' succeeded.");
return true;
}
catch (Exception exc)
{
if (exc is TaskCanceledException tce &&
tce.CancellationToken == cts.Token &&
cts.Token.IsCancellationRequested)
{
logger.LogInfo($"Didn't receive answer from NuGet feed '{feed}' in {timeoutMilliSeconds}ms.");
timeoutMilliSeconds *= 2;
continue;
}
logger.LogInfo($"Querying NuGet feed '{feed}' failed. The reason for the failure: {exc.Message}");
return false;
}
}
logger.LogWarning($"Didn't receive answer from NuGet feed '{feed}'. Tried it {tryCount} times.");
isTimeout = true;
return false;
}
private (int initialTimeout, int tryCount) GetFeedRequestSettings(bool isFallback)
{
int timeoutMilliSeconds = isFallback && int.TryParse(Environment.GetEnvironmentVariable(EnvironmentVariableNames.NugetFeedResponsivenessInitialTimeoutForFallback), out timeoutMilliSeconds)
? timeoutMilliSeconds
: int.TryParse(Environment.GetEnvironmentVariable(EnvironmentVariableNames.NugetFeedResponsivenessInitialTimeout), out timeoutMilliSeconds)
? timeoutMilliSeconds
: 1000;
logger.LogDebug($"Initial timeout for NuGet feed reachability check is {timeoutMilliSeconds}ms.");
int tryCount = isFallback && int.TryParse(Environment.GetEnvironmentVariable(EnvironmentVariableNames.NugetFeedResponsivenessRequestCountForFallback), out tryCount)
? tryCount
: int.TryParse(Environment.GetEnvironmentVariable(EnvironmentVariableNames.NugetFeedResponsivenessRequestCount), out tryCount)
? tryCount
: 4;
logger.LogDebug($"Number of tries for NuGet feed reachability check is {tryCount}.");
return (timeoutMilliSeconds, tryCount);
}
/// <summary>
/// Retrieves a list of excluded NuGet feeds from the corresponding environment variable.
/// </summary>
private HashSet<string> GetExcludedFeeds()
{
var excludedFeeds = EnvironmentVariables.GetURLs(EnvironmentVariableNames.ExcludedNugetFeedsFromResponsivenessCheck)
.ToHashSet();
if (excludedFeeds.Count > 0)
{
logger.LogInfo($"Excluded NuGet feeds from responsiveness check: {string.Join(", ", excludedFeeds.OrderBy(f => f))}");
}
return excludedFeeds;
}
/// <summary>
/// Checks that we can connect to the specified NuGet feeds.
/// </summary>
/// <param name="feeds">The set of package feeds to check.</param>
/// <param name="reachableFeeds">The list of feeds that were reachable.</param>
/// <returns>
/// True if there is a timeout when trying to reach the feeds (excluding any feeds that are configured
/// to be excluded from the check) or false otherwise.
/// </returns>
private bool CheckSpecifiedFeeds(HashSet<string> feeds, out HashSet<string> reachableFeeds)
{
// Exclude any feeds from the feed check that are configured by the corresponding environment variable.
// These feeds are always assumed to be reachable.
var excludedFeeds = GetExcludedFeeds();
HashSet<string> feedsToCheck = feeds.Where(feed =>
{
if (excludedFeeds.Contains(feed))
{
logger.LogInfo($"Not checking reachability of NuGet feed '{feed}' as it is in the list of excluded feeds.");
return false;
}
return true;
}).ToHashSet();
reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false, out var isTimeout).ToHashSet();
// Always consider feeds excluded for the reachability check as reachable.
reachableFeeds.UnionWith(feeds.Where(feed => excludedFeeds.Contains(feed)));
return isTimeout;
}
/// <summary> /// <summary>
/// If <paramref name="allFeedsReachable"/> is `false`, logs this and emits a diagnostic. /// If <paramref name="allFeedsReachable"/> is `false`, logs this and emits a diagnostic.
/// Adds a `CompilationInfos` entry either way. /// Adds a `CompilationInfos` entry either way.
@@ -627,15 +899,56 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
compilationInfoContainer.CompilationInfos.Add(("All NuGet feeds reachable", allFeedsReachable ? "1" : "0")); compilationInfoContainer.CompilationInfos.Add(("All NuGet feeds reachable", allFeedsReachable ? "1" : "0"));
} }
private void EmitNugetConfigDiagnostics() private IEnumerable<string> GetFeeds(Func<IList<string>> getNugetFeeds)
{ {
var results = getNugetFeeds();
var regex = EnabledNugetFeed();
foreach (var result in results)
{
var match = regex.Match(result);
if (!match.Success)
{
logger.LogError($"Failed to parse feed from '{result}'");
continue;
}
var url = match.Groups[1].Value;
if (!url.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase) &&
!url.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
{
logger.LogInfo($"Skipping feed '{url}' as it is not a valid URL.");
continue;
}
if (!string.IsNullOrWhiteSpace(url))
{
yield return url;
}
}
}
private string? GetDirectoryName(string path)
{
try
{
return new FileInfo(path).Directory?.FullName;
}
catch (Exception exc)
{
logger.LogWarning($"Failed to get directory of '{path}': {exc}");
}
return null;
}
private (HashSet<string> explicitFeeds, HashSet<string> allFeeds) GetAllFeeds()
{
var nugetConfigs = fileProvider.NugetConfigs;
// On systems with case-sensitive file systems (for simplicity, we assume that is Linux), the // On systems with case-sensitive file systems (for simplicity, we assume that is Linux), the
// filenames of NuGet configuration files must be named correctly. For compatibility with projects // filenames of NuGet configuration files must be named correctly. For compatibility with projects
// that are typically built on Windows or macOS where this doesn't matter, we accept all variants // that are typically built on Windows or macOS where this doesn't matter, we accept all variants
// of `nuget.config` ourselves. However, `dotnet` does not. If we detect that incorrectly-named // of `nuget.config` ourselves. However, `dotnet` does not. If we detect that incorrectly-named
// files are present, we emit a diagnostic to warn the user. // files are present, we emit a diagnostic to warn the user.
var nugetConfigs = fileProvider.NugetConfigs;
if (SystemBuildActions.Instance.IsLinux()) if (SystemBuildActions.Instance.IsLinux())
{ {
string[] acceptedNugetConfigNames = ["nuget.config", "NuGet.config", "NuGet.Config"]; string[] acceptedNugetConfigNames = ["nuget.config", "NuGet.config", "NuGet.Config"];
@@ -665,6 +978,53 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
)); ));
} }
} }
// Find feeds that are explicitly configured in the NuGet configuration files that we found.
var explicitFeeds = nugetConfigs
.SelectMany(config => GetFeeds(() => dotnet.GetNugetFeeds(config)))
.ToHashSet();
if (explicitFeeds.Count > 0)
{
logger.LogInfo($"Found {explicitFeeds.Count} NuGet feeds in nuget.config files: {string.Join(", ", explicitFeeds.OrderBy(f => f))}");
}
else
{
logger.LogDebug("No NuGet feeds found in nuget.config files.");
}
// If private package registries are configured for C#, then consider those
// in addition to the ones that are configured in `nuget.config` files.
if (hasPrivateRegistryFeeds)
{
logger.LogInfo($"Found {privateRegistryFeeds.Count} private registry feeds configured for C#: {string.Join(", ", privateRegistryFeeds.OrderBy(f => f))}");
explicitFeeds.UnionWith(privateRegistryFeeds);
}
HashSet<string> allFeeds = [];
// Add all explicitFeeds to the set of all feeds.
allFeeds.UnionWith(explicitFeeds);
// Obtain the list of feeds from the root source directory.
// If a NuGet file is present it will be respected, otherwise we will just get the machine/environment specific feeds.
var nugetFeedsFromRoot = GetFeeds(() => dotnet.GetNugetFeedsFromFolder(fileProvider.SourceDir.FullName));
allFeeds.UnionWith(nugetFeedsFromRoot);
if (nugetConfigs.Count > 0)
{
var nugetConfigFeeds = nugetConfigs
.Select(GetDirectoryName)
.Where(folder => folder != null)
.SelectMany(folder => GetFeeds(() => dotnet.GetNugetFeedsFromFolder(folder!)))
.ToHashSet();
allFeeds.UnionWith(nugetConfigFeeds);
}
logger.LogInfo($"Found {allFeeds.Count} NuGet feeds (with inherited ones) in nuget.config files: {string.Join(", ", allFeeds.OrderBy(f => f))}");
return (explicitFeeds, allFeeds);
} }
[GeneratedRegex(@"<TargetFramework>.*</TargetFramework>", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)] [GeneratedRegex(@"<TargetFramework>.*</TargetFramework>", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)]
@@ -676,12 +1036,15 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
[GeneratedRegex(@"^(.+)\.(\d+\.\d+\.\d+(-(.+))?)$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)] [GeneratedRegex(@"^(.+)\.(\d+\.\d+\.\d+(-(.+))?)$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)]
private static partial Regex LegacyNugetPackage(); private static partial Regex LegacyNugetPackage();
[GeneratedRegex(@"^E\s(.*)$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)]
private static partial Regex EnabledNugetFeed();
public void Dispose() public void Dispose()
{ {
PackageDirectory?.Dispose(); PackageDirectory?.Dispose();
legacyPackageDirectory?.Dispose(); legacyPackageDirectory?.Dispose();
missingPackageDirectory?.Dispose(); missingPackageDirectory?.Dispose();
feedManager.Dispose(); emptyPackageDirectory?.Dispose();
} }
/// <summary> /// <summary>
@@ -689,7 +1052,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
/// </summary> /// </summary>
private static string ComputeTempDirectoryPath(string subfolderName) private static string ComputeTempDirectoryPath(string subfolderName)
{ {
return Path.Join(FileUtils.GetTemporaryWorkingDirectory(out _), subfolderName); return Path.Combine(FileUtils.GetTemporaryWorkingDirectory(out _), subfolderName);
} }
/// <summary> /// <summary>
@@ -697,7 +1060,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
/// </summary> /// </summary>
private static string ComputeTempDirectoryPath(string srcDir, string subfolderName) private static string ComputeTempDirectoryPath(string srcDir, string subfolderName)
{ {
return Path.Join(FileUtils.GetTemporaryWorkingDirectory(out _), FileUtils.ComputeHash(srcDir), subfolderName); return Path.Combine(FileUtils.GetTemporaryWorkingDirectory(out _), FileUtils.ComputeHash(srcDir), subfolderName);
} }
} }
} }

View File

@@ -1,368 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Semmle.Util;
namespace Semmle.Extraction.CSharp.DependencyFetching
{
internal interface IPackagesConfigRestore : IDisposable
{
/// <summary>
/// The number of packages.config files found in the source tree.
/// </summary>
int PackageCount { get; }
/// <summary>
/// Download the packages to the temp folder.
/// </summary>
int InstallPackages();
}
/// <summary>
/// Factory for creating a package manager to restore NuGet packages referenced in packages.config files.
/// If the environment doesn't support using nuget.exe to restore packages from packages.config files, a no-op implementation is returned.
/// It is worth noting that for macOS and Linux, nuget.exe is used with mono. However, mono is being deprecated and the last GitHub images
/// to contain mono are:
/// - Ubuntu 22.04
/// - macOS 14
///
/// If the packages from the packages.config files are not restored with the packages.config restore functionality below, there is a subsequent
/// step that still may succeed in restoring the packages without the help of nuget.exe (by attempting to restore using dotnet).
/// </summary>
internal class PackagesConfigRestoreFactory
{
public static IPackagesConfigRestore Create(FileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, Func<bool> useDefaultFeed)
{
if (SystemBuildActions.Instance.IsWindows() || SystemBuildActions.Instance.IsMonoInstalled())
{
return new NugetExeWrapper(fileProvider, packageDirectory, logger, useDefaultFeed);
}
return new NoOpPackagesConfig(fileProvider.PackagesConfigs, logger);
}
/// <summary>
/// Manage the downloading of NuGet packages with nuget.exe.
/// Locates packages in a source tree and downloads all of the
/// referenced assemblies to a temp folder.
/// </summary>
private class NugetExeWrapper : IPackagesConfigRestore
{
private readonly string? nugetExe;
private readonly Semmle.Util.Logging.ILogger logger;
public int PackageCount => fileProvider.PackagesConfigs.Count;
private readonly string? backupNugetConfig;
private readonly string? nugetConfigPath;
private readonly FileProvider fileProvider;
/// <summary>
/// The packages directory.
/// This will be in the user-specified or computed Temp location
/// so as to not trample the source tree.
/// </summary>
private readonly DependencyDirectory packageDirectory;
private bool IsWindows => SystemBuildActions.Instance.IsWindows();
/// <summary>
/// Create the package manager for a specified source tree.
/// </summary>
public NugetExeWrapper(FileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, Func<bool> useDefaultFeed)
{
this.fileProvider = fileProvider;
this.packageDirectory = packageDirectory;
this.logger = logger;
if (fileProvider.PackagesConfigs.Count > 0)
{
logger.LogInfo($"Found packages.config files, trying to use nuget.exe for package restore");
nugetExe = ResolveNugetExe();
if (!HasPackageSource() && useDefaultFeed())
{
// We only modify or add a top level nuget.config file
nugetConfigPath = Path.Join(fileProvider.SourceDir.FullName, "nuget.config");
try
{
if (File.Exists(nugetConfigPath))
{
var tempFolderPath = FileUtils.GetTemporaryWorkingDirectory(out _);
do
{
backupNugetConfig = Path.Join(tempFolderPath, Path.GetRandomFileName());
}
while (File.Exists(backupNugetConfig));
File.Copy(nugetConfigPath, backupNugetConfig, true);
}
else
{
File.WriteAllText(nugetConfigPath,
"""
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
</packageSources>
</configuration>
""");
}
AddDefaultPackageSource(nugetConfigPath);
}
catch (Exception e)
{
logger.LogError($"Failed to add default package source to {nugetConfigPath}: {e}");
}
}
}
}
/// <summary>
/// Tries to find the location of `nuget.exe`. It looks for
/// - the environment variable specifying a location,
/// - files in the repository,
/// - tries to resolve nuget from the PATH, or
/// - downloads it if it is not found.
/// </summary>
private string ResolveNugetExe()
{
var envVarPath = Environment.GetEnvironmentVariable(EnvironmentVariableNames.NugetExePath);
if (!string.IsNullOrEmpty(envVarPath))
{
logger.LogInfo($"Using nuget.exe from environment variable: '{envVarPath}'");
return envVarPath;
}
try
{
return DownloadNugetExe(fileProvider.SourceDir.FullName);
}
catch (Exception exc)
{
logger.LogInfo($"Download of nuget.exe failed: {exc.Message}");
}
var nugetExesInRepo = fileProvider.NugetExes;
if (nugetExesInRepo.Count > 1)
{
logger.LogInfo($"Found multiple nuget.exe files in the repository: {string.Join(", ", nugetExesInRepo.OrderBy(s => s))}");
}
if (nugetExesInRepo.Count > 0)
{
var path = nugetExesInRepo.First();
logger.LogInfo($"Using nuget.exe from path '{path}'");
return path;
}
var executableName = IsWindows ? "nuget.exe" : "nuget";
var nugetPath = FileUtils.FindProgramOnPath(executableName);
if (nugetPath is not null)
{
nugetPath = Path.Join(nugetPath, executableName);
logger.LogInfo($"Using nuget.exe from PATH: {nugetPath}");
return nugetPath;
}
throw new Exception("Could not find or download nuget.exe.");
}
private string DownloadNugetExe(string sourceDir)
{
var directory = Path.Join(sourceDir, ".nuget");
var nuget = Path.Join(directory, "nuget.exe");
// Nuget.exe already exists in the .nuget directory.
if (File.Exists(nuget))
{
logger.LogInfo($"Found nuget.exe at {nuget}");
return nuget;
}
Directory.CreateDirectory(directory);
logger.LogInfo("Attempting to download nuget.exe");
FileUtils.DownloadFile(FileUtils.NugetExeUrl, nuget, logger);
logger.LogInfo($"Downloaded nuget.exe to {nuget}");
return nuget;
}
private bool RunWithMono => !IsWindows && !string.IsNullOrEmpty(Path.GetExtension(nugetExe));
/// <summary>
/// Restore all packages in the specified packages.config file.
/// </summary>
/// <param name="packagesConfig">The packages.config file.</param>
private bool TryRestoreNugetPackage(string packagesConfig)
{
logger.LogInfo($"Restoring file \"{packagesConfig}\"...");
/* Use nuget.exe to install a package.
* Note that there is a clutch of NuGet assemblies which could be used to
* invoke this directly, which would arguably be nicer. However they are
* really unwieldy and this solution works for now.
*/
string exe, args;
if (RunWithMono)
{
exe = "mono";
args = $"\"{nugetExe}\" install -OutputDirectory \"{packageDirectory}\" \"{packagesConfig}\"";
}
else
{
exe = nugetExe!;
args = $"install -OutputDirectory \"{packageDirectory}\" \"{packagesConfig}\"";
}
var pi = new ProcessStartInfo(exe, args)
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
var threadId = Environment.CurrentManagedThreadId;
void onOut(string s) => logger.LogDebug(s, threadId);
void onError(string s) => logger.LogError(s, threadId);
var exitCode = pi.ReadOutput(out _, onOut, onError);
if (exitCode != 0)
{
logger.LogError($"Command {pi.FileName} {pi.Arguments} failed with exit code {exitCode}");
return false;
}
else
{
logger.LogInfo($"Restored file \"{packagesConfig}\"");
return true;
}
}
/// <summary>
/// Download the packages to the temp folder.
/// </summary>
public int InstallPackages()
{
return fileProvider.PackagesConfigs.Count(TryRestoreNugetPackage);
}
private bool HasPackageSource()
{
if (IsWindows)
{
return true;
}
try
{
logger.LogInfo("Checking if default package source is available...");
RunMonoNugetCommand("sources list -ForceEnglishOutput", out var stdout);
if (stdout.All(line => line != "No sources found."))
{
return true;
}
return false;
}
catch (Exception e)
{
logger.LogWarning($"Failed to check if default package source is added: {e}");
return true;
}
}
private void RunMonoNugetCommand(string command, out IList<string> stdout)
{
string exe, args;
if (RunWithMono)
{
exe = "mono";
args = $"\"{nugetExe}\" {command}";
}
else
{
exe = nugetExe!;
args = command;
}
var pi = new ProcessStartInfo(exe, args)
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
var threadId = Environment.CurrentManagedThreadId;
void onOut(string s) => logger.LogDebug(s, threadId);
void onError(string s) => logger.LogError(s, threadId);
pi.ReadOutput(out stdout, onOut, onError);
}
private void AddDefaultPackageSource(string nugetConfig)
{
logger.LogInfo("Adding default package source...");
RunMonoNugetCommand($"sources add -Name DefaultNugetOrg -Source {FeedManager.PublicNugetOrgFeed} -ConfigFile \"{nugetConfig}\"", out _);
}
public void Dispose()
{
if (nugetConfigPath is null)
{
return;
}
try
{
if (backupNugetConfig is null)
{
logger.LogInfo("Removing nuget.config file");
File.Delete(nugetConfigPath);
return;
}
logger.LogInfo("Reverting nuget.config file content");
// The content of the original nuget.config file is reverted without changing the file's attributes or casing:
using (var backup = File.OpenRead(backupNugetConfig))
using (var current = File.OpenWrite(nugetConfigPath))
{
current.SetLength(0); // Truncate file
backup.CopyTo(current); // Restore original content
}
logger.LogInfo("Deleting backup nuget.config file");
File.Delete(backupNugetConfig);
}
catch (Exception exc)
{
logger.LogError($"Failed to restore original nuget.config file: {exc}");
}
}
}
private class NoOpPackagesConfig : IPackagesConfigRestore
{
private readonly Semmle.Util.Logging.ILogger logger;
private readonly ICollection<string> packagesConfigs;
public NoOpPackagesConfig(ICollection<string> packagesConfigs, Semmle.Util.Logging.ILogger logger)
{
this.packagesConfigs = packagesConfigs;
this.logger = logger;
}
public int PackageCount => packagesConfigs.Count;
public int InstallPackages()
{
if (PackageCount > 0)
{
logger.LogInfo("Found packages.config files, but nuget.exe cannot be used to restore packages on this platform. Skipping restore of packages.config files.");
}
return 0;
}
public void Dispose() { }
}
}
}

View File

@@ -79,7 +79,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
var monoPath = FileUtils.FindProgramOnPath(Win32.IsWindows() ? "mono.exe" : "mono"); var monoPath = FileUtils.FindProgramOnPath(Win32.IsWindows() ? "mono.exe" : "mono");
string[] monoDirs = monoPath is not null string[] monoDirs = monoPath is not null
? [Path.GetFullPath(Path.Join(monoPath, "..", "lib", "mono")), monoPath] ? [Path.GetFullPath(Path.Combine(monoPath, "..", "lib", "mono")), monoPath]
: ["/usr/lib/mono", "/usr/local/mono", "/usr/local/bin/mono", @"C:\Program Files\Mono\lib\mono"]; : ["/usr/lib/mono", "/usr/local/mono", "/usr/local/bin/mono", @"C:\Program Files\Mono\lib\mono"];
var monoDir = monoDirs.FirstOrDefault(Directory.Exists); var monoDir = monoDirs.FirstOrDefault(Directory.Exists);

View File

@@ -63,7 +63,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
return null; return null;
} }
var path = Path.Join(version.FullPath, "Roslyn", "bincore", "csc.dll"); var path = Path.Combine(version.FullPath, "Roslyn", "bincore", "csc.dll");
logger.LogDebug($"Source generator CSC: '{path}'"); logger.LogDebug($"Source generator CSC: '{path}'");
if (!File.Exists(path)) if (!File.Exists(path))
{ {

View File

@@ -41,10 +41,10 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
.Replace('\\', '/'); // Ensure we're generating the same hash regardless of the OS .Replace('\\', '/'); // Ensure we're generating the same hash regardless of the OS
var name = FileUtils.ComputeHash($"{relativePathToCsProj}\n{this.GetType().Name}"); var name = FileUtils.ComputeHash($"{relativePathToCsProj}\n{this.GetType().Name}");
using var tempDir = new TemporaryDirectory(Path.Join(FileUtils.GetTemporaryWorkingDirectory(out _), "source-generator"), "source generator temporary", logger); using var tempDir = new TemporaryDirectory(Path.Join(FileUtils.GetTemporaryWorkingDirectory(out _), "source-generator"), "source generator temporary", logger);
var analyzerConfigPath = Path.Join(tempDir.DirInfo.FullName, $"{name}.txt"); var analyzerConfigPath = Path.Combine(tempDir.DirInfo.FullName, $"{name}.txt");
var dllPath = Path.Join(tempDir.DirInfo.FullName, $"{name}.dll"); var dllPath = Path.Combine(tempDir.DirInfo.FullName, $"{name}.dll");
var cscArgsPath = Path.Join(tempDir.DirInfo.FullName, $"{name}.rsp"); var cscArgsPath = Path.Combine(tempDir.DirInfo.FullName, $"{name}.rsp");
var outputFolder = Path.Join(targetDir, name); var outputFolder = Path.Combine(targetDir, name);
Directory.CreateDirectory(outputFolder); Directory.CreateDirectory(outputFolder);
logger.LogInfo("Producing analyzer config content."); logger.LogInfo("Producing analyzer config content.");
GenerateAnalyzerConfig(additionalFiles, csprojFile, analyzerConfigPath); GenerateAnalyzerConfig(additionalFiles, csprojFile, analyzerConfigPath);

View File

@@ -21,7 +21,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
throw new Exception("No SDK path available."); throw new Exception("No SDK path available.");
} }
SourceGeneratorFolder = Path.Join(sdkPath, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators"); SourceGeneratorFolder = Path.Combine(sdkPath, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators");
this.logger.LogInfo($"Razor source generator folder: {SourceGeneratorFolder}"); this.logger.LogInfo($"Razor source generator folder: {SourceGeneratorFolder}");
if (!Directory.Exists(SourceGeneratorFolder)) if (!Directory.Exists(SourceGeneratorFolder))
{ {

View File

@@ -50,7 +50,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
if (usings.Count > 0) if (usings.Count > 0)
{ {
var tempDir = GetTemporaryWorkingDirectory("implicitUsings"); var tempDir = GetTemporaryWorkingDirectory("implicitUsings");
var path = Path.Join(tempDir, "GlobalUsings.g.cs"); var path = Path.Combine(tempDir, "GlobalUsings.g.cs");
using (var writer = new StreamWriter(path)) using (var writer = new StreamWriter(path))
{ {
writer.WriteLine("// <auto-generated/>"); writer.WriteLine("// <auto-generated/>");

View File

@@ -32,7 +32,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
var nugetFolder = nugetPackageRestorer.TryRestore("Microsoft.CodeAnalysis.ResxSourceGenerator"); var nugetFolder = nugetPackageRestorer.TryRestore("Microsoft.CodeAnalysis.ResxSourceGenerator");
if (nugetFolder is not null) if (nugetFolder is not null)
{ {
sourceGeneratorFolder = System.IO.Path.Join(nugetFolder, "analyzers", "dotnet", "cs"); sourceGeneratorFolder = System.IO.Path.Combine(nugetFolder, "analyzers", "dotnet", "cs");
} }
} }
catch (Exception e) catch (Exception e)

View File

@@ -35,7 +35,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
/// </summary> /// </summary>
protected string GetTemporaryWorkingDirectory(string subfolder) protected string GetTemporaryWorkingDirectory(string subfolder)
{ {
var temp = Path.Join(tempWorkingDirectory.ToString(), subfolder); var temp = Path.Combine(tempWorkingDirectory.ToString(), subfolder);
Directory.CreateDirectory(temp); Directory.CreateDirectory(temp);
return temp; return temp;

View File

@@ -1,6 +1,5 @@
using System.IO; using System.IO;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.Kinds; using Semmle.Extraction.Kinds;
@@ -9,7 +8,7 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
internal abstract class ElementAccess : Expression<ExpressionSyntax> internal abstract class ElementAccess : Expression<ExpressionSyntax>
{ {
protected ElementAccess(ExpressionNodeInfo info, ExpressionSyntax qualifier, BracketedArgumentListSyntax argumentList) protected ElementAccess(ExpressionNodeInfo info, ExpressionSyntax qualifier, BracketedArgumentListSyntax argumentList)
: base(info.SetKind(GetKind(info.Context, info.Node, qualifier))) : base(info.SetKind(GetKind(info.Context, qualifier)))
{ {
this.qualifier = qualifier; this.qualifier = qualifier;
this.argumentList = argumentList; this.argumentList = argumentList;
@@ -18,125 +17,6 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
private readonly ExpressionSyntax qualifier; private readonly ExpressionSyntax qualifier;
private readonly BracketedArgumentListSyntax argumentList; private readonly BracketedArgumentListSyntax argumentList;
private ISymbol? GetTargetSymbol()
{
return Context.GetSymbolInfo(base.Syntax).Symbol;
}
private static void SetExprArgument(TextWriter trapFile, Expression left, Expression right)
{
trapFile.expr_argument(left, 0);
trapFile.expr_argument(right, 0);
}
private Expression MakeZeroFromEndExpression(IExpressionParentEntity parent, int child)
{
var info = new ExpressionInfo(
Context,
AnnotatedTypeSymbol.CreateNotAnnotated(Context.Compilation.GetSpecialType(SpecialType.System_Int32)),
Location,
ExprKind.INDEX,
parent,
child,
isCompilerGenerated: true,
null);
var index = new Expression(info);
MakeZeroLiteral(index, 0);
return index;
}
private Expression MakeZeroLiteral(IExpressionParentEntity parent, int child)
{
return Literal.CreateGenerated(Context, parent, child, Context.Compilation.GetSpecialType(SpecialType.System_Int32), 0, Location);
}
/// <summary>
/// It is assumed that either the input is
/// 1. A normal expression that can be used as endpoint (e.g a constant like "3").
/// 2. An index expression indicating that we should read from the end (e.g "^1").
/// </summary>
/// <param name="syntax">The syntax node representing the range endpoint.</param>
/// <param name="parent">The parent expression entity.</param>
/// <param name="child">The child index within the parent.</param>
/// <returns>An expression representing the endpoint of a range to be used in conjunction with a slice operation.</returns>
private Expression MakeFromRangeEndpoint(ExpressionSyntax syntax, IExpressionParentEntity parent, int child)
{
var info = new ExpressionNodeInfo(Context, syntax, parent, child);
return syntax.Kind() == SyntaxKind.IndexExpression
? PrefixUnary.Create(info.SetKind(ExprKind.INDEX))
: Factory.Create(info);
}
/// <summary>
/// Determines whether the given method is a slice method, which is defined as a method with
/// the name "Slice" or "Substring" and two parameters.
/// </summary>
/// <param name="method">The method symbol to check.</param>
/// <returns>True if the method is a slice method; false otherwise.</returns>
private bool IsSlice(IMethodSymbol method, out RangeExpressionSyntax? range)
{
range = null;
if (argumentList.Arguments.Count == 1)
{
range = argumentList.Arguments[0].Expression as RangeExpressionSyntax;
}
return (method.Name == "Slice" || method.Name == "Substring")
&& method.Parameters.Length == 2;
}
/// <summary>
/// Populates a slice method call based on the given range.
/// Roslyn translates indexer accesses with range expressions in the following way.
/// 1. s[a..b] -> s.Slice(a, b - a)
/// 2. s[..b] -> s.Slice(0, b)
/// 3. s[a..] -> s.Slice(a, s.Length - a)
/// 4. s[..] -> s.Slice(0, s.Length)
/// However, it is possible that both the qualifier or the index endpoints may contain method calls.
/// If we want to translate this accurately, we would need to introduce synthetic statements for qualifier and
/// the endpoints, which should then be used in the slice method call.
/// To avoid this, we translate as follows.
/// 1. s[a..b] -> s.Slice(a, b)
/// 2. s[..b] -> s.Slice(0, b)
/// 3. s[a..] -> s.Slice(a, ^0)
/// 4. s[..] -> s.Slice(0, ^0)
///
/// Even though index expressions can't technically be used in this way, they signal that we
/// could perceive ^b as "length - b".
///
/// Call arguments are only populated when a range expression is directly available in
/// the list of arguments.
/// This means that cases like below are not handled.
/// System.Range x = 1..3;
/// s[x]
/// </summary>
/// <param name="trapFile">The trap file to write to.</param>
/// <param name="slice">The slice method symbol.</param>
/// <param name="range">The range expression syntax.</param>
private void PopulateSlice(TextWriter trapFile, IMethodSymbol slice, RangeExpressionSyntax? range)
{
if (range is not null)
{
// Populate the call arguments
var left = range.LeftOperand is ExpressionSyntax lsyntax
? MakeFromRangeEndpoint(lsyntax, this, 0)
: MakeZeroLiteral(this, 0);
var right = range.RightOperand is ExpressionSyntax rsyntax
? MakeFromRangeEndpoint(rsyntax, this, 1)
: MakeZeroFromEndExpression(this, 1);
SetExprArgument(trapFile, left, right);
}
trapFile.expr_call(this, Method.Create(Context, slice));
}
protected override void PopulateExpression(TextWriter trapFile) protected override void PopulateExpression(TextWriter trapFile)
{ {
if (Kind == ExprKind.POINTER_INDIRECTION) if (Kind == ExprKind.POINTER_INDIRECTION)
@@ -150,19 +30,11 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
else else
{ {
Create(Context, qualifier, this, -1); Create(Context, qualifier, this, -1);
var target = GetTargetSymbol();
if (target is IMethodSymbol method && IsSlice(method, out var range))
{
// When an indexer on a span or string is used in conjunction with a range expression, the compiler translates
// this into a call to the "Slice" or "Substring" method.
// In this case, we want to populate a slice/substring method call instead of an indexer access.
PopulateSlice(trapFile, method, range);
return;
}
PopulateArguments(trapFile, argumentList, 0); PopulateArguments(trapFile, argumentList, 0);
if (target is IPropertySymbol { IsIndexer: true } indexer)
var symbolInfo = Context.GetSymbolInfo(base.Syntax);
if (symbolInfo.Symbol is IPropertySymbol indexer)
{ {
trapFile.expr_access(this, Indexer.Create(Context, indexer)); trapFile.expr_access(this, Indexer.Create(Context, indexer));
} }
@@ -174,11 +46,8 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
private static bool IsArray(ITypeSymbol symbol) => private static bool IsArray(ITypeSymbol symbol) =>
symbol.TypeKind == Microsoft.CodeAnalysis.TypeKind.Array || symbol.IsInlineArray(); symbol.TypeKind == Microsoft.CodeAnalysis.TypeKind.Array || symbol.IsInlineArray();
private static ExprKind GetKind(Context cx, ExpressionSyntax syntax, ExpressionSyntax qualifier) private static ExprKind GetKind(Context cx, ExpressionSyntax qualifier)
{ {
if (cx.GetSymbolInfo(syntax).Symbol is IMethodSymbol)
return ExprKind.METHOD_INVOCATION;
var qualifierType = cx.GetType(qualifier); var qualifierType = cx.GetType(qualifier);
// This is a compilation error, so make a guess and continue. // This is a compilation error, so make a guess and continue.

View File

@@ -23,9 +23,7 @@ namespace Semmle.Extraction.CSharp.Entities.Statements
} }
else if (isSpecificCatchClause) // A catch clause of the form 'catch(Ex) { ... }' else if (isSpecificCatchClause) // A catch clause of the form 'catch(Ex) { ... }'
{ {
var type = Type.Create(Context, Context.GetType(Stmt.Declaration!.Type)); trapFile.catch_type(this, Type.Create(Context, Context.GetType(Stmt.Declaration!.Type)).TypeRef, true);
trapFile.catch_type(this, type.TypeRef, true);
Expression.Create(Context, Stmt.Declaration!.Type, this, 0);
} }
else // A catch clause of the form 'catch { ... }' else // A catch clause of the form 'catch { ... }'
{ {

View File

@@ -67,7 +67,7 @@ namespace Semmle.Extraction.CSharp
return; return;
} }
var mscorlibExists = File.Exists(Path.Join(compilerDir, "mscorlib.dll")); var mscorlibExists = File.Exists(Path.Combine(compilerDir, "mscorlib.dll"));
if (specifiedFramework is null && mscorlibExists) if (specifiedFramework is null && mscorlibExists)
{ {
@@ -107,7 +107,7 @@ namespace Semmle.Extraction.CSharp
/// <summary> /// <summary>
/// The file csc.rsp. /// The file csc.rsp.
/// </summary> /// </summary>
private string CscRsp => Path.Join(FrameworkPath, csc_rsp); private string CscRsp => Path.Combine(FrameworkPath, csc_rsp);
/// <summary> /// <summary>
/// Should we skip extraction? /// Should we skip extraction?

View File

@@ -680,7 +680,7 @@ namespace Semmle.Extraction.CSharp
{ {
try try
{ {
var fullPath = Path.GetFullPath(Path.Join(Path.GetDirectoryName(mappedFromPath)!, mappedToPath)); var fullPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(mappedFromPath)!, mappedToPath));
ExtractionContext.Logger.LogDebug($"Found relative path in line mapping: '{mappedToPath}', interpreting it as '{fullPath}'"); ExtractionContext.Logger.LogDebug($"Found relative path in line mapping: '{mappedToPath}', interpreting it as '{fullPath}'");
mappedToPath = fullPath; mappedToPath = fullPath;

View File

@@ -159,11 +159,7 @@ namespace Semmle.Extraction.CSharp
return null; return null;
} }
var normalized = Path.DirectorySeparatorChar == '/' ? file.Replace("\\", "/") : file; return Path.GetFullPath(Path.Combine(projDir?.FullName ?? string.Empty, Path.DirectorySeparatorChar == '/' ? file.Replace("\\", "/") : file));
var path = projDir is not null && !Path.IsPathRooted(normalized)
? Path.Join(projDir.FullName, normalized)
: normalized;
return Path.GetFullPath(path);
} }
private readonly string[] references; private readonly string[] references;

View File

@@ -210,7 +210,7 @@ namespace Semmle.Extraction.CSharp
TracingAnalyser.GetOutputName(compilation, args), TracingAnalyser.GetOutputName(compilation, args),
compilation, compilation,
generatedSyntaxTrees, generatedSyntaxTrees,
Path.Join(compilationIdentifierPath, diagnosticName), Path.Combine(compilationIdentifierPath, diagnosticName),
options), options),
() => { }); () => { });
@@ -377,7 +377,7 @@ namespace Semmle.Extraction.CSharp
else else
{ {
var composed = referencePaths.Value var composed = referencePaths.Value
.Select(path => Path.Join(path, clref.Reference)) .Select(path => Path.Combine(path, clref.Reference))
.Where(path => File.Exists(path)) .Where(path => File.Exists(path))
.Select(path => analyser.PathCache.GetCanonicalPath(path)) .Select(path => analyser.PathCache.GetCanonicalPath(path))
.FirstOrDefault(); .FirstOrDefault();
@@ -559,13 +559,13 @@ namespace Semmle.Extraction.CSharp
/// Gets the path to the `csharp.log` file written to by the C# extractor. /// Gets the path to the `csharp.log` file written to by the C# extractor.
/// </summary> /// </summary>
public static string GetCSharpLogPath() => public static string GetCSharpLogPath() =>
Path.Join(GetCSharpLogDirectory(), "csharp.log"); Path.Combine(GetCSharpLogDirectory(), "csharp.log");
/// <summary> /// <summary>
/// Gets the path to a `csharp.{hash}.txt` file written to by the C# extractor. /// Gets the path to a `csharp.{hash}.txt` file written to by the C# extractor.
/// </summary> /// </summary>
public static string GetCSharpArgsLogPath(string hash) => public static string GetCSharpArgsLogPath(string hash) =>
Path.Join(GetCSharpLogDirectory(), $"csharp.{hash}.txt"); Path.Combine(GetCSharpLogDirectory(), $"csharp.{hash}.txt");
/// <summary> /// <summary>
/// Gets a list of all `csharp.{hash}.txt` files currently written to the log directory. /// Gets a list of all `csharp.{hash}.txt` files currently written to the log directory.

View File

@@ -131,7 +131,7 @@ namespace Semmle.Extraction.CSharp
return Path.ChangeExtension(entryPointFilename, ".exe"); return Path.ChangeExtension(entryPointFilename, ".exe");
} }
return Path.Join(commandLineArguments.OutputDirectory, commandLineArguments.OutputFileName); return Path.Combine(commandLineArguments.OutputDirectory, commandLineArguments.OutputFileName);
} }
private int LogDiagnostics() private int LogDiagnostics()

View File

@@ -61,7 +61,7 @@ namespace Semmle.Extraction.CSharp
* Although GetRandomFileName() is cryptographically secure, * Although GetRandomFileName() is cryptographically secure,
* there's a tiny chance the file could already exists. * there's a tiny chance the file could already exists.
*/ */
tmpFile = Path.Join(tempPath, Path.GetRandomFileName()); tmpFile = Path.Combine(tempPath, Path.GetRandomFileName());
} }
while (File.Exists(tmpFile)); while (File.Exists(tmpFile));

View File

@@ -82,13 +82,13 @@ namespace SemmleTests.Semmle.Util
[Fact] [Fact]
public void CanonicalPathMissingFile() public void CanonicalPathMissingFile()
{ {
Assert.Equal(Path.Join(Directory.GetCurrentDirectory(), "NOSUCHFILE"), cache.GetCanonicalPath("NOSUCHFILE")); Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), "NOSUCHFILE"), cache.GetCanonicalPath("NOSUCHFILE"));
} }
[Fact] [Fact]
public void CanonicalPathMissingAbsolutePath() public void CanonicalPathMissingAbsolutePath()
{ {
Assert.Equal(Path.Join(root, "no", "such", "file"), cache.GetCanonicalPath(Path.Join(root, "no", "such", "file"))); Assert.Equal(Path.Combine(root, "no", "such", "file"), cache.GetCanonicalPath(Path.Combine(root, "no", "such", "file")));
if (Win32.IsWindows()) if (Win32.IsWindows())
Assert.Equal(@"C:\Windows\no\such\file", cache.GetCanonicalPath(@"C:\windOws\no\such\file")); Assert.Equal(@"C:\Windows\no\such\file", cache.GetCanonicalPath(@"C:\windOws\no\such\file"));
@@ -97,7 +97,7 @@ namespace SemmleTests.Semmle.Util
[Fact] [Fact]
public void CanonicalPathMissingRelativePath() public void CanonicalPathMissingRelativePath()
{ {
Assert.Equal(Path.Join(Directory.GetCurrentDirectory(), "NO", "SUCH"), cache.GetCanonicalPath(Path.Join("NO", "SUCH"))); Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), "NO", "SUCH"), cache.GetCanonicalPath(Path.Combine("NO", "SUCH")));
} }
[Fact] [Fact]
@@ -125,7 +125,7 @@ namespace SemmleTests.Semmle.Util
public void CanonicalPathDots() public void CanonicalPathDots()
{ {
var abcPath = Path.GetFullPath("abc"); var abcPath = Path.GetFullPath("abc");
Assert.Equal(abcPath, cache.GetCanonicalPath(Path.Join("foo", ".", "..", "abc"))); Assert.Equal(abcPath, cache.GetCanonicalPath(Path.Combine("foo", ".", "..", "abc")));
} }
[Fact] [Fact]

View File

@@ -14,20 +14,20 @@ namespace SemmleTests.Semmle.Util
public sealed class LongPaths public sealed class LongPaths
{ {
private static readonly string tmpDir = Environment.GetEnvironmentVariable("TEST_TMPDIR") ?? Path.GetTempPath(); private static readonly string tmpDir = Environment.GetEnvironmentVariable("TEST_TMPDIR") ?? Path.GetTempPath();
private static readonly string longPathDir = Path.Join(tmpDir, "aaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbb", private static readonly string longPathDir = Path.Combine(tmpDir, "aaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"ccccccccccccccccccccccccccccccc", "ddddddddddddddddddddddddddddddddddddd", "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "fffffffffffffffffffffffffffffffff", "ccccccccccccccccccccccccccccccc", "ddddddddddddddddddddddddddddddddddddd", "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "fffffffffffffffffffffffffffffffff",
"ggggggggggggggggggggggggggggggggggg", "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhh"); "ggggggggggggggggggggggggggggggggggg", "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhh");
private static string MakeLongPath() private static string MakeLongPath()
{ {
var uniquePostfix = Guid.NewGuid().ToString("N"); var uniquePostfix = Guid.NewGuid().ToString("N");
return Path.Join(longPathDir, $"iiiiiiiiiiiiiiii{uniquePostfix}.txt"); return Path.Combine(longPathDir, $"iiiiiiiiiiiiiiii{uniquePostfix}.txt");
} }
private static string MakeShortPath() private static string MakeShortPath()
{ {
var uniquePostfix = Guid.NewGuid().ToString("N"); var uniquePostfix = Guid.NewGuid().ToString("N");
return Path.Join(tmpDir, $"test{uniquePostfix}.txt"); return Path.Combine(tmpDir, $"test{uniquePostfix}.txt");
} }
public LongPaths() public LongPaths()
@@ -62,7 +62,7 @@ namespace SemmleTests.Semmle.Util
[Fact] [Fact]
public void ParentDirectory() public void ParentDirectory()
{ {
Assert.Equal("abc", Path.GetDirectoryName(Path.Join("abc", "def"))); Assert.Equal("abc", Path.GetDirectoryName(Path.Combine("abc", "def")));
Assert.Equal(Win32.IsWindows() ? "\\" : "/", Path.GetDirectoryName($@"{Path.DirectorySeparatorChar}def")); Assert.Equal(Win32.IsWindows() ? "\\" : "/", Path.GetDirectoryName($@"{Path.DirectorySeparatorChar}def"));
Assert.Equal("", Path.GetDirectoryName(@"def")); Assert.Equal("", Path.GetDirectoryName(@"def"));

View File

@@ -137,11 +137,11 @@ namespace Semmle.Util
bool IsMonoInstalled(); bool IsMonoInstalled();
/// <summary> /// <summary>
/// Joins path segments, Path.Join(). /// Combine path segments, Path.Combine().
/// </summary> /// </summary>
/// <param name="parts">The parts of the path.</param> /// <param name="parts">The parts of the path.</param>
/// <returns>The combined path.</returns> /// <returns>The combined path.</returns>
string PathJoin(params string[] parts); string PathCombine(params string[] parts);
/// <summary> /// <summary>
/// Gets the full path for <paramref name="path"/>, Path.GetFullPath(). /// Gets the full path for <paramref name="path"/>, Path.GetFullPath().
@@ -293,7 +293,7 @@ namespace Semmle.Util
} }
} }
string IBuildActions.PathJoin(params string[] parts) => Path.Join(parts); string IBuildActions.PathCombine(params string[] parts) => Path.Combine(parts);
void IBuildActions.WriteAllText(string filename, string contents) => File.WriteAllText(filename, contents); void IBuildActions.WriteAllText(string filename, string contents) => File.WriteAllText(filename, contents);

View File

@@ -43,7 +43,7 @@ namespace Semmle.Util
var parent = Directory.GetParent(path); var parent = Directory.GetParent(path);
return parent is not null ? return parent is not null ?
Path.Join(cache.GetCanonicalPath(parent.FullName), Path.GetFileName(path)) : Path.Combine(cache.GetCanonicalPath(parent.FullName), Path.GetFileName(path)) :
path.ToUpperInvariant(); path.ToUpperInvariant();
} }
} }
@@ -138,12 +138,12 @@ namespace Semmle.Util
var entries = Directory.GetFileSystemEntries(parentPath, name); var entries = Directory.GetFileSystemEntries(parentPath, name);
return entries.Length == 1 return entries.Length == 1
? entries[0] ? entries[0]
: Path.Join(parentPath, name); : Path.Combine(parentPath, name);
} }
catch // lgtm[cs/catch-of-all-exceptions] catch // lgtm[cs/catch-of-all-exceptions]
{ {
// IO error or security error querying directory. // IO error or security error querying directory.
return Path.Join(parentPath, name); return Path.Combine(parentPath, name);
} }
} }
} }

View File

@@ -82,7 +82,7 @@ namespace Semmle.Util
{ {
exes = new[] { prog }; exes = new[] { prog };
} }
var candidates = paths?.Where(path => exes.Any(exe0 => File.Exists(Path.Join(path, exe0)))); var candidates = paths?.Where(path => exes.Any(exe0 => File.Exists(Path.Combine(path, exe0))));
return candidates?.FirstOrDefault(); return candidates?.FirstOrDefault();
} }
@@ -179,7 +179,7 @@ namespace Semmle.Util
{ {
innerpath = ConvertPathToSafeRelativePath(innerpath); innerpath = ConvertPathToSafeRelativePath(innerpath);
nested = Path.Join(outerpath, innerpath); nested = Path.Combine(outerpath, innerpath);
} }
try try
{ {
@@ -203,7 +203,7 @@ namespace Semmle.Util
{ {
var tempPath = Path.GetTempPath(); var tempPath = Path.GetTempPath();
var name = Guid.NewGuid().ToString("N").ToUpper(); var name = Guid.NewGuid().ToString("N").ToUpper();
var tempFolder = Path.Join(tempPath, "GitHub", name); var tempFolder = Path.Combine(tempPath, "GitHub", name);
Directory.CreateDirectory(tempFolder); Directory.CreateDirectory(tempFolder);
return tempFolder; return tempFolder;
}); });
@@ -231,7 +231,7 @@ namespace Semmle.Util
string outputPath; string outputPath;
do do
{ {
outputPath = Path.Join(tempFolder, Path.GetRandomFileName() + extension); outputPath = Path.Combine(tempFolder, Path.GetRandomFileName() + extension);
} }
while (File.Exists(outputPath)); while (File.Exists(outputPath));

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Improved extraction of range-access expressions on spans and strings (for example, `a[0..3]`). These expressions are now extracted as `Slice` (span) or `Substring` (string) calls.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Improved property and indexer call target resolution for partially overridden properties and indexers.

View File

@@ -1,4 +0,0 @@
---
category: majorAnalysis
---
* Added Razor Page handler method parameters (e.g., `OnGet`, `OnPost`, `OnPostAsync`) as remote flow sources, enabling security queries such as `cs/sql-injection` to detect vulnerabilities in `PageModel` subclasses.

View File

@@ -1,4 +0,0 @@
---
category: breaking
---
* Renamed types related to *operation* expressions. The QL classes `BinaryArithmeticOperation`, `BinaryBitwiseOperation`, and `BinaryLogicalOperation` now include compound assignments; for example, `BinaryArithmeticOperation` now includes `a += b`.

View File

@@ -50,15 +50,15 @@ private predicate maybeUsedInElfHashFunction(Variable v, Operation xor, Operatio
| |
add instanceof AddOperation and add instanceof AddOperation and
e1.getAChild*() = add.getAnOperand() and e1.getAChild*() = add.getAnOperand() and
e1 instanceof BinaryBitwiseExpr and e1 instanceof BinaryBitwiseOperation and
e2 = e1.(BinaryBitwiseExpr).getLeftOperand() and e2 = e1.(BinaryBitwiseOperation).getLeftOperand() and
v = addAssign.getTargetVariable() and v = addAssign.getTargetVariable() and
addAssign.getAChild*() = add and addAssign.getAChild*() = add and
(xor instanceof BitwiseXorExpr or xor instanceof AssignXorExpr) and (xor instanceof BitwiseXorExpr or xor instanceof AssignXorExpr) and
addAssign.getControlFlowNode().getASuccessor*() = xor.getControlFlowNode() and addAssign.getControlFlowNode().getASuccessor*() = xor.getControlFlowNode() and
xorAssign.getAChild*() = xor and xorAssign.getAChild*() = xor and
v = xorAssign.getTargetVariable() and v = xorAssign.getTargetVariable() and
(notOp instanceof UnaryBitwiseOperation or notOp instanceof AssignBitwiseExpr) and (notOp instanceof UnaryBitwiseOperation or notOp instanceof AssignBitwiseOperation) and
xor.getControlFlowNode().getASuccessor*() = notOp.getControlFlowNode() and xor.getControlFlowNode().getASuccessor*() = notOp.getControlFlowNode() and
notAssign.getAChild*() = notOp and notAssign.getAChild*() = notOp and
v = notAssign.getTargetVariable() and v = notAssign.getTargetVariable() and

View File

@@ -290,7 +290,7 @@ module AssignableInternal {
newtype TAssignableDefinition = newtype TAssignableDefinition =
TAssignmentDefinition(Assignment a) { TAssignmentDefinition(Assignment a) {
not a.getLeftOperand() instanceof TupleExpr and not a.getLeftOperand() instanceof TupleExpr and
not a instanceof AssignCallExpr and not a instanceof AssignCallOperation and
not a instanceof AssignCoalesceExpr not a instanceof AssignCoalesceExpr
} or } or
TTupleAssignmentDefinition(AssignExpr ae, Expr leaf) { tupleAssignmentDefinition(ae, leaf) } or TTupleAssignmentDefinition(AssignExpr ae, Expr leaf) { tupleAssignmentDefinition(ae, leaf) } or
@@ -324,7 +324,7 @@ module AssignableInternal {
TAddressOfDefinition(AddressOfExpr aoe) or TAddressOfDefinition(AddressOfExpr aoe) or
TPatternDefinition(TopLevelPatternDecl tlpd) or TPatternDefinition(TopLevelPatternDecl tlpd) or
TAssignOperationDefinition(AssignOperation ao) { TAssignOperationDefinition(AssignOperation ao) {
ao instanceof AssignCallExpr and not ao instanceof CompoundAssignmentOperatorCall ao instanceof AssignCallOperation and not ao instanceof CompoundAssignmentOperatorCall
or or
ao instanceof AssignCoalesceExpr ao instanceof AssignCoalesceExpr
} }

View File

@@ -57,28 +57,6 @@ class DeclarationWithGetSetAccessors extends DeclarationWithAccessors, TopLevelE
/** Gets the `set` accessor of this declaration, if any. */ /** Gets the `set` accessor of this declaration, if any. */
Setter getSetter() { result = this.getAnAccessor() } Setter getSetter() { result = this.getAnAccessor() }
/** Gets the target accessor of this declaration when used in a read context, if any. */
Accessor getReadTarget() {
result = this.getGetter()
or
not exists(this.getGetter()) and
result = this.getOverridee().getReadTarget()
}
/** Gets the target accessor of this declaration when used in a write context, if any. */
Accessor getWriteTarget() {
result = this.getSetter()
or
not exists(this.getSetter()) and
result = this.getOverridee().getWriteTarget()
or
result =
any(Getter g |
g = this.getReadTarget() and
g.getAnnotatedReturnType().isRef()
)
}
override DeclarationWithGetSetAccessors getOverridee() { override DeclarationWithGetSetAccessors getOverridee() {
result = DeclarationWithAccessors.super.getOverridee() result = DeclarationWithAccessors.super.getOverridee()
} }

View File

@@ -995,23 +995,6 @@ class SpecificCatchClause extends CatchClause {
/** Gets the local variable declaration of this catch clause, if any. */ /** Gets the local variable declaration of this catch clause, if any. */
LocalVariableDeclExpr getVariableDeclExpr() { result.getParent() = this } LocalVariableDeclExpr getVariableDeclExpr() { result.getParent() = this }
/**
* Gets the type access of this catch clause, if it has no variable declaration.
*
* For example, the type access in
*
* ```csharp
* try { ... }
* catch (IOException) { ... }
* ```
*
* is `IOException`.
*/
TypeAccess getTypeAccess() {
not exists(this.getVariableDeclExpr()) and
result = this.getChild(0)
}
override string toString() { result = "catch (...) {...}" } override string toString() { result = "catch (...) {...}" }
override string getAPrimaryQlClass() { result = "SpecificCatchClause" } override string getAPrimaryQlClass() { result = "SpecificCatchClause" }

View File

@@ -912,17 +912,18 @@ module Internal {
) )
or or
// In C#, `null + 1` has type `int?` with value `null` // In C#, `null + 1` has type `int?` with value `null`
result = exists(BinaryOperation bo, Expr o |
any(BinaryArithmeticOperation bao | bo instanceof BinaryArithmeticOperation or
exists(Expr o | bo instanceof AssignArithmeticOperation
bao.getAnOperand() = e and |
bao.getAnOperand() = o and result = bo and
// The other operand must be provably non-null in order bo.getAnOperand() = e and
// for `only if` to hold bo.getAnOperand() = o and
nonNullValueImplied(o) and // The other operand must be provably non-null in order
e != o // for `only if` to hold
) nonNullValueImplied(o) and
) e != o
)
} }
/** /**
@@ -933,10 +934,10 @@ module Internal {
any(QualifiableExpr qe | any(QualifiableExpr qe |
qe.isConditional() and qe.isConditional() and
result = qe.getQualifier() result = qe.getQualifier()
) ) or
or
// In C#, `null + 1` has type `int?` with value `null` // In C#, `null + 1` has type `int?` with value `null`
e = any(BinaryArithmeticOperation bao | result = bao.getAnOperand()) e = any(BinaryArithmeticOperation bao | result = bao.getAnOperand()) or
e = any(AssignArithmeticOperation aao | result = aao.getAnOperand())
} }
deprecated predicate isGuard(Expr e, GuardValue val) { deprecated predicate isGuard(Expr e, GuardValue val) {

View File

@@ -75,8 +75,7 @@ module Ast implements AstSig<Location> {
additional predicate skipControlFlow(AstNode e) { additional predicate skipControlFlow(AstNode e) {
e instanceof TypeAccess and e instanceof TypeAccess and
not e instanceof TypeAccessPatternExpr and not e instanceof TypeAccessPatternExpr
not any(CS::SpecificCatchClause sc).getTypeAccess() = e
or or
not e.getFile().fromSource() not e.getFile().fromSource()
} }
@@ -91,7 +90,6 @@ module Ast implements AstSig<Location> {
private AstNode getStmtChild0(Stmt s, int i) { private AstNode getStmtChild0(Stmt s, int i) {
not s instanceof FixedStmt and not s instanceof FixedStmt and
not s instanceof UsingBlockStmt and not s instanceof UsingBlockStmt and
not skipControlFlow(result) and
result = s.getChild(i) result = s.getChild(i)
or or
s = s =
@@ -147,8 +145,6 @@ module Ast implements AstSig<Location> {
final private class ParameterFinal = CS::Parameter; final private class ParameterFinal = CS::Parameter;
class Parameter extends ParameterFinal { class Parameter extends ParameterFinal {
AstNode getPattern() { result = this }
Expr getDefaultValue() { Expr getDefaultValue() {
// Avoid combinatorial explosions for callables with multiple bodies // Avoid combinatorial explosions for callables with multiple bodies
result = unique( | | super.getDefaultValue()) result = unique( | | super.getDefaultValue())
@@ -176,10 +172,6 @@ module Ast implements AstSig<Location> {
class DoStmt = CS::DoStmt; class DoStmt = CS::DoStmt;
class UntilStmt extends LoopStmt {
UntilStmt() { none() }
}
final private class FinalForStmt = CS::ForStmt; final private class FinalForStmt = CS::ForStmt;
class ForStmt extends FinalForStmt { class ForStmt extends FinalForStmt {
@@ -211,7 +203,7 @@ module Ast implements AstSig<Location> {
final private class FinalTryStmt = CS::TryStmt; final private class FinalTryStmt = CS::TryStmt;
class TryStmt extends FinalTryStmt { class TryStmt extends FinalTryStmt {
AstNode getBody(int index) { index = 0 and result = this.getBlock() } Stmt getBody() { result = this.getBlock() }
CatchClause getCatch(int index) { result = this.getCatchClause(index) } CatchClause getCatch(int index) { result = this.getCatchClause(index) }
@@ -221,12 +213,7 @@ module Ast implements AstSig<Location> {
final private class FinalCatchClause = CS::CatchClause; final private class FinalCatchClause = CS::CatchClause;
class CatchClause extends FinalCatchClause { class CatchClause extends FinalCatchClause {
AstNode getPattern() { AstNode getVariable() { result = this.(CS::SpecificCatchClause).getVariableDeclExpr() }
result = this.(CS::SpecificCatchClause).getVariableDeclExpr() or
result = this.(CS::SpecificCatchClause).getTypeAccess()
}
AstNode getVariable() { none() }
Expr getCondition() { result = this.getFilterClause() } Expr getCondition() { result = this.getFilterClause() }

View File

@@ -124,7 +124,9 @@ private module Internal {
TDispatchDynamicOperatorCall(DynamicOperatorCall doc) or TDispatchDynamicOperatorCall(DynamicOperatorCall doc) or
TDispatchDynamicMemberAccess(DynamicMemberAccess dma) or TDispatchDynamicMemberAccess(DynamicMemberAccess dma) or
TDispatchDynamicElementAccess(DynamicElementAccess dea) or TDispatchDynamicElementAccess(DynamicElementAccess dea) or
TDispatchDynamicEventAccess(AssignArithmeticExpr aao, DynamicMemberAccess dma, string name) { TDispatchDynamicEventAccess(
AssignArithmeticOperation aao, DynamicMemberAccess dma, string name
) {
isPotentialEventCall(aao, dma, name) isPotentialEventCall(aao, dma, name)
} or } or
TDispatchDynamicObjectCreation(DynamicObjectCreation doc) or TDispatchDynamicObjectCreation(DynamicObjectCreation doc) or
@@ -228,7 +230,7 @@ private module Internal {
* accessor. * accessor.
*/ */
private predicate isPotentialEventCall( private predicate isPotentialEventCall(
AssignArithmeticExpr aao, DynamicMemberAccess dma, string name AssignArithmeticOperation aao, DynamicMemberAccess dma, string name
) { ) {
aao instanceof DynamicOperatorCall and aao instanceof DynamicOperatorCall and
dma = aao.getLeftOperand() and dma = aao.getLeftOperand() and
@@ -1395,7 +1397,9 @@ private module Internal {
private class DispatchDynamicEventAccess extends DispatchReflectionOrDynamicCall, private class DispatchDynamicEventAccess extends DispatchReflectionOrDynamicCall,
TDispatchDynamicEventAccess TDispatchDynamicEventAccess
{ {
override AssignArithmeticExpr getCall() { this = TDispatchDynamicEventAccess(result, _, _) } override AssignArithmeticOperation getCall() {
this = TDispatchDynamicEventAccess(result, _, _)
}
override string getName() { this = TDispatchDynamicEventAccess(_, _, result) } override string getName() { this = TDispatchDynamicEventAccess(_, _, result) }

View File

@@ -11,27 +11,19 @@ import Expr
* (`UnaryArithmeticOperation`) or a binary arithmetic operation * (`UnaryArithmeticOperation`) or a binary arithmetic operation
* (`BinaryArithmeticOperation`). * (`BinaryArithmeticOperation`).
*/ */
class ArithmeticOperation extends Operation, @arith_operation { class ArithmeticOperation extends Operation, @arith_op_expr {
override string getOperator() { none() } override string getOperator() { none() }
} }
/** /**
* A binary arithmetic operation. Either a binary arithmetic expression (`BinaryArithmeticExpr`) or * A unary arithmetic operation. Either a unary minus operation
* an arithmetic assignment expression (`AssignArithmeticExpr`). * (`UnaryMinusExpr`), a unary plus operation (`UnaryPlusExpr`),
*/
class BinaryArithmeticOperation extends ArithmeticOperation, BinaryOperation, @bin_arith_operation {
override string getOperator() { none() }
}
/**
* A unary arithmetic operation. Either a unary minus expression
* (`UnaryMinusExpr`), a unary plus expression (`UnaryPlusExpr`),
* or a mutator operation (`MutatorOperation`). * or a mutator operation (`MutatorOperation`).
*/ */
class UnaryArithmeticOperation extends ArithmeticOperation, UnaryOperation, @un_arith_operation { } class UnaryArithmeticOperation extends ArithmeticOperation, UnaryOperation, @un_arith_op_expr { }
/** /**
* A unary minus expression, for example `-x`. * A unary minus operation, for example `-x`.
*/ */
class UnaryMinusExpr extends UnaryArithmeticOperation, @minus_expr { class UnaryMinusExpr extends UnaryArithmeticOperation, @minus_expr {
override string getOperator() { result = "-" } override string getOperator() { result = "-" }
@@ -40,7 +32,7 @@ class UnaryMinusExpr extends UnaryArithmeticOperation, @minus_expr {
} }
/** /**
* A unary plus expression, for example `+x`. * A unary plus operation, for example `+x`.
*/ */
class UnaryPlusExpr extends UnaryArithmeticOperation, @plus_expr { class UnaryPlusExpr extends UnaryArithmeticOperation, @plus_expr {
override string getOperator() { result = "+" } override string getOperator() { result = "+" }
@@ -52,40 +44,40 @@ class UnaryPlusExpr extends UnaryArithmeticOperation, @plus_expr {
* A mutator operation. Either an increment operation (`IncrementOperation`) * A mutator operation. Either an increment operation (`IncrementOperation`)
* or a decrement operation (`DecrementOperation`). * or a decrement operation (`DecrementOperation`).
*/ */
class MutatorOperation extends UnaryArithmeticOperation, @mut_operation { } class MutatorOperation extends UnaryArithmeticOperation, @mut_op_expr { }
/** /**
* An increment operation. Either a postfix increment expression * An increment operation. Either a postfix increment operation
* (`PostIncrExpr`) or a prefix increment expression (`PreIncrExpr`). * (`PostIncrExpr`) or a prefix increment operation (`PreIncrExpr`).
*/ */
class IncrementOperation extends MutatorOperation, @incr_operation { class IncrementOperation extends MutatorOperation, @incr_op_expr {
override string getOperator() { result = "++" } override string getOperator() { result = "++" }
} }
/** /**
* A decrement operation. Either a postfix decrement expression * A decrement operation. Either a postfix decrement operation
* (`PostDecrExpr`) or a prefix decrement expression (`PreDecrExpr`). * (`PostDecrExpr`) or a prefix decrement operation (`PreDecrExpr`).
*/ */
class DecrementOperation extends MutatorOperation, @decr_operation { class DecrementOperation extends MutatorOperation, @decr_op_expr {
override string getOperator() { result = "--" } override string getOperator() { result = "--" }
} }
/** /**
* A prefix increment expression, for example `++x`. * A prefix increment operation, for example `++x`.
*/ */
class PreIncrExpr extends IncrementOperation, @pre_incr_expr { class PreIncrExpr extends IncrementOperation, @pre_incr_expr {
override string getAPrimaryQlClass() { result = "PreIncrExpr" } override string getAPrimaryQlClass() { result = "PreIncrExpr" }
} }
/** /**
* A prefix decrement expression, for example `--x`. * A prefix decrement operation, for example `--x`.
*/ */
class PreDecrExpr extends DecrementOperation, @pre_decr_expr { class PreDecrExpr extends DecrementOperation, @pre_decr_expr {
override string getAPrimaryQlClass() { result = "PreDecrExpr" } override string getAPrimaryQlClass() { result = "PreDecrExpr" }
} }
/** /**
* A postfix increment expression, for example `x++`. * A postfix increment operation, for example `x++`.
*/ */
class PostIncrExpr extends IncrementOperation, @post_incr_expr { class PostIncrExpr extends IncrementOperation, @post_incr_expr {
override string toString() { result = "..." + this.getOperator() } override string toString() { result = "..." + this.getOperator() }
@@ -94,7 +86,7 @@ class PostIncrExpr extends IncrementOperation, @post_incr_expr {
} }
/** /**
* A postfix decrement expression, for example `x--`. * A postfix decrement operation, for example `x--`.
*/ */
class PostDecrExpr extends DecrementOperation, @post_decr_expr { class PostDecrExpr extends DecrementOperation, @post_decr_expr {
override string toString() { result = "..." + this.getOperator() } override string toString() { result = "..." + this.getOperator() }
@@ -103,84 +95,55 @@ class PostDecrExpr extends DecrementOperation, @post_decr_expr {
} }
/** /**
* An addition operation, either `x + y` or `x += y`. * A binary arithmetic operation. Either an addition operation
* (`AddExpr`), a subtraction operation (`SubExpr`), a multiplication
* operation (`MulExpr`), a division operation (`DivExpr`), or a
* remainder operation (`RemExpr`).
*/ */
class AddOperation extends BinaryArithmeticOperation, @add_operation { } class BinaryArithmeticOperation extends ArithmeticOperation, BinaryOperation, @bin_arith_op_expr {
override string getOperator() { none() }
/**
* A subtraction operation, either `x - y` or `x -= y`.
*/
class SubOperation extends BinaryArithmeticOperation, @sub_operation { }
/**
* A multiplication operation, either `x * y` or `x *= y`.
*/
class MulOperation extends BinaryArithmeticOperation, @mul_operation { }
/**
* A division operation, either `x / y` or `x /= y`.
*/
class DivOperation extends BinaryArithmeticOperation, @div_operation {
/** Gets the numerator of this division operation. */
Expr getNumerator() { result = this.getLeftOperand() }
/** Gets the denominator of this division operation. */
Expr getDenominator() { result = this.getRightOperand() }
} }
/** /**
* A remainder operation, either `x % y` or `x %= y`. * An addition operation, for example `x + y`.
*/ */
class RemOperation extends BinaryArithmeticOperation, @rem_operation { } class AddExpr extends BinaryArithmeticOperation, AddOperation, @add_expr {
/**
* A binary arithmetic expression. Either an addition expression
* (`AddExpr`), a subtraction expression (`SubExpr`), a multiplication
* expression (`MulExpr`), a division expression (`DivExpr`), or a
* remainder expression (`RemExpr`).
*/
class BinaryArithmeticExpr extends BinaryArithmeticOperation, @bin_arith_expr { }
/**
* An addition expression, for example `x + y`.
*/
class AddExpr extends BinaryArithmeticExpr, AddOperation, @add_expr {
override string getOperator() { result = "+" } override string getOperator() { result = "+" }
override string getAPrimaryQlClass() { result = "AddExpr" } override string getAPrimaryQlClass() { result = "AddExpr" }
} }
/** /**
* A subtraction expression, for example `x - y`. * A subtraction operation, for example `x - y`.
*/ */
class SubExpr extends BinaryArithmeticExpr, SubOperation, @sub_expr { class SubExpr extends BinaryArithmeticOperation, SubOperation, @sub_expr {
override string getOperator() { result = "-" } override string getOperator() { result = "-" }
override string getAPrimaryQlClass() { result = "SubExpr" } override string getAPrimaryQlClass() { result = "SubExpr" }
} }
/** /**
* A multiplication expression, for example `x * y`. * A multiplication operation, for example `x * y`.
*/ */
class MulExpr extends BinaryArithmeticExpr, MulOperation, @mul_expr { class MulExpr extends BinaryArithmeticOperation, MulOperation, @mul_expr {
override string getOperator() { result = "*" } override string getOperator() { result = "*" }
override string getAPrimaryQlClass() { result = "MulExpr" } override string getAPrimaryQlClass() { result = "MulExpr" }
} }
/** /**
* A division expression, for example `x / y`. * A division operation, for example `x / y`.
*/ */
class DivExpr extends BinaryArithmeticExpr, DivOperation, @div_expr { class DivExpr extends BinaryArithmeticOperation, DivOperation, @div_expr {
override string getOperator() { result = "/" } override string getOperator() { result = "/" }
override string getAPrimaryQlClass() { result = "DivExpr" } override string getAPrimaryQlClass() { result = "DivExpr" }
} }
/** /**
* A remainder expression, for example `x % y`. * A remainder operation, for example `x % y`.
*/ */
class RemExpr extends BinaryArithmeticExpr, RemOperation, @rem_expr { class RemExpr extends BinaryArithmeticOperation, RemOperation, @rem_expr {
override string getOperator() { result = "%" } override string getOperator() { result = "%" }
override string getAPrimaryQlClass() { result = "RemExpr" } override string getAPrimaryQlClass() { result = "RemExpr" }

View File

@@ -72,9 +72,9 @@ class AssignExpr extends Assignment, @simple_assign_expr {
} }
/** /**
* An assignment operation. Either an arithmetic assignment expression * An assignment operation. Either an arithmetic assignment operation
* (`AssignArithmeticExpr`), a bitwise assignment expression * (`AssignArithmeticOperation`), a bitwise assignment operation
* (`AssignBitwiseExpr`), an event assignment (`AddOrRemoveEventExpr`), or * (`AssignBitwiseOperation`), an event assignment (`AddOrRemoveEventExpr`), or
* a null-coalescing assignment (`AssignCoalesceExpr`). * a null-coalescing assignment (`AssignCoalesceExpr`).
*/ */
class AssignOperation extends Assignment, @assign_op_expr { class AssignOperation extends Assignment, @assign_op_expr {
@@ -94,147 +94,134 @@ class AssignOperation extends Assignment, @assign_op_expr {
} }
/** /**
* A compound assignment expression that invokes an operator. * A compound assignment operation that invokes an operator.
* *
* (1) `x += y` invokes the compound assignment operator `+=` (if it exists). * (1) `x += y` invokes the compound assignment operator `+=` (if it exists).
* (2) `x += y` invokes the operator `+` and assigns `x + y` to `x`. * (2) `x += y` invokes the operator `+` and assigns `x + y` to `x`.
* *
* Either an arithmetic assignment expression (`AssignArithmeticExpr`) or a bitwise * Either an arithmetic assignment operation (`AssignArithmeticOperation`) or a bitwise
* assignment expression (`AssignBitwiseExpr`). * assignment operation (`AssignBitwiseOperation`).
*/ */
class AssignCallExpr extends AssignOperation, OperatorCall, QualifiableExpr, @assign_op_call_expr { class AssignCallOperation extends AssignOperation, OperatorCall, QualifiableExpr,
@assign_op_call_expr
{
override string toString() { result = AssignOperation.super.toString() } override string toString() { result = AssignOperation.super.toString() }
} }
/** /**
* DEPRECATED: Use `AssignCallExpr` instead. * An arithmetic assignment operation. Either an addition assignment operation
*/ * (`AssignAddExpr`), a subtraction assignment operation (`AssignSubExpr`), a
deprecated class AssignCallOperation = AssignCallExpr; * multiplication assignment operation (`AssignMulExpr`), a division assignment
* operation (`AssignDivExpr`), or a remainder assignment operation
/**
* An arithmetic assignment expression. Either an addition assignment expression
* (`AssignAddExpr`), a subtraction assignment expression (`AssignSubExpr`), a
* multiplication assignment expression (`AssignMulExpr`), a division assignment
* expression (`AssignDivExpr`), or a remainder assignment expression
* (`AssignRemExpr`). * (`AssignRemExpr`).
*/ */
class AssignArithmeticExpr extends AssignCallExpr, @assign_arith_expr { } class AssignArithmeticOperation extends AssignCallOperation, @assign_arith_expr { }
/** /**
* DEPRECATED: Use `AssignArithmeticExpr` instead. * An addition assignment operation, for example `x += y`.
*/ */
deprecated class AssignArithmeticOperation = AssignArithmeticExpr; class AssignAddExpr extends AssignArithmeticOperation, AddOperation, @assign_add_expr {
/**
* An addition assignment expression, for example `x += y`.
*/
class AssignAddExpr extends AssignArithmeticExpr, AddOperation, @assign_add_expr {
override string getOperator() { result = "+=" } override string getOperator() { result = "+=" }
override string getAPrimaryQlClass() { result = "AssignAddExpr" } override string getAPrimaryQlClass() { result = "AssignAddExpr" }
} }
/** /**
* A subtraction assignment expression, for example `x -= y`. * A subtraction assignment operation, for example `x -= y`.
*/ */
class AssignSubExpr extends AssignArithmeticExpr, SubOperation, @assign_sub_expr { class AssignSubExpr extends AssignArithmeticOperation, SubOperation, @assign_sub_expr {
override string getOperator() { result = "-=" } override string getOperator() { result = "-=" }
override string getAPrimaryQlClass() { result = "AssignSubExpr" } override string getAPrimaryQlClass() { result = "AssignSubExpr" }
} }
/** /**
* A multiplication assignment expression, for example `x *= y`. * An multiplication assignment operation, for example `x *= y`.
*/ */
class AssignMulExpr extends AssignArithmeticExpr, MulOperation, @assign_mul_expr { class AssignMulExpr extends AssignArithmeticOperation, MulOperation, @assign_mul_expr {
override string getOperator() { result = "*=" } override string getOperator() { result = "*=" }
override string getAPrimaryQlClass() { result = "AssignMulExpr" } override string getAPrimaryQlClass() { result = "AssignMulExpr" }
} }
/** /**
* A division assignment expression, for example `x /= y`. * An division assignment operation, for example `x /= y`.
*/ */
class AssignDivExpr extends AssignArithmeticExpr, DivOperation, @assign_div_expr { class AssignDivExpr extends AssignArithmeticOperation, DivOperation, @assign_div_expr {
override string getOperator() { result = "/=" } override string getOperator() { result = "/=" }
override string getAPrimaryQlClass() { result = "AssignDivExpr" } override string getAPrimaryQlClass() { result = "AssignDivExpr" }
} }
/** /**
* A remainder assignment expression, for example `x %= y`. * A remainder assignment operation, for example `x %= y`.
*/ */
class AssignRemExpr extends AssignArithmeticExpr, RemOperation, @assign_rem_expr { class AssignRemExpr extends AssignArithmeticOperation, RemOperation, @assign_rem_expr {
override string getOperator() { result = "%=" } override string getOperator() { result = "%=" }
override string getAPrimaryQlClass() { result = "AssignRemExpr" } override string getAPrimaryQlClass() { result = "AssignRemExpr" }
} }
/** /**
* A bitwise assignment expression. Either a bitwise-and assignment * A bitwise assignment operation. Either a bitwise-and assignment
* expression (`AssignAndExpr`), a bitwise-or assignment * operation (`AssignAndExpr`), a bitwise-or assignment
* expression (`AssignOrExpr`), a bitwise exclusive-or assignment * operation (`AssignOrExpr`), a bitwise exclusive-or assignment
* expression (`AssignXorExpr`), a left-shift assignment * operation (`AssignXorExpr`), a left-shift assignment
* expression (`AssignLeftShiftExpr`), or a right-shift assignment * operation (`AssignLeftShiftExpr`), or a right-shift assignment
* expression (`AssignRightShiftExpr`), or an unsigned right-shift assignment * operation (`AssignRightShiftExpr`), or an unsigned right-shift assignment
* expression (`AssignUnsignedRightShiftExpr`). * operation (`AssignUnsignedRightShiftExpr`).
*/ */
class AssignBitwiseExpr extends AssignCallExpr, @assign_bitwise_expr { } class AssignBitwiseOperation extends AssignCallOperation, @assign_bitwise_expr { }
/** /**
* DEPRECATED: Use `AssignBitwiseExpr` instead. * A bitwise-and assignment operation, for example `x &= y`.
*/ */
deprecated class AssignBitwiseOperation = AssignBitwiseExpr; class AssignAndExpr extends AssignBitwiseOperation, BitwiseAndOperation, @assign_and_expr {
/**
* A bitwise-and assignment expression, for example `x &= y`.
*/
class AssignAndExpr extends AssignBitwiseExpr, BitwiseAndOperation, @assign_and_expr {
override string getOperator() { result = "&=" } override string getOperator() { result = "&=" }
override string getAPrimaryQlClass() { result = "AssignAndExpr" } override string getAPrimaryQlClass() { result = "AssignAndExpr" }
} }
/** /**
* A bitwise-or assignment expression, for example `x |= y`. * A bitwise-or assignment operation, for example `x |= y`.
*/ */
class AssignOrExpr extends AssignBitwiseExpr, BitwiseOrOperation, @assign_or_expr { class AssignOrExpr extends AssignBitwiseOperation, BitwiseOrOperation, @assign_or_expr {
override string getOperator() { result = "|=" } override string getOperator() { result = "|=" }
override string getAPrimaryQlClass() { result = "AssignOrExpr" } override string getAPrimaryQlClass() { result = "AssignOrExpr" }
} }
/** /**
* A bitwise exclusive-or assignment expression, for example `x ^= y`. * A bitwise exclusive-or assignment operation, for example `x ^= y`.
*/ */
class AssignXorExpr extends AssignBitwiseExpr, BitwiseXorOperation, @assign_xor_expr { class AssignXorExpr extends AssignBitwiseOperation, BitwiseXorOperation, @assign_xor_expr {
override string getOperator() { result = "^=" } override string getOperator() { result = "^=" }
override string getAPrimaryQlClass() { result = "AssignXorExpr" } override string getAPrimaryQlClass() { result = "AssignXorExpr" }
} }
/** /**
* A left-shift assignment expression, for example `x <<= y`. * A left-shift assignment operation, for example `x <<= y`.
*/ */
class AssignLeftShiftExpr extends AssignBitwiseExpr, LeftShiftOperation, @assign_lshift_expr { class AssignLeftShiftExpr extends AssignBitwiseOperation, LeftShiftOperation, @assign_lshift_expr {
override string getOperator() { result = "<<=" } override string getOperator() { result = "<<=" }
override string getAPrimaryQlClass() { result = "AssignLeftShiftExpr" } override string getAPrimaryQlClass() { result = "AssignLeftShiftExpr" }
} }
/** /**
* A right-shift assignment expression, for example `x >>= y`. * A right-shift assignment operation, for example `x >>= y`.
*/ */
class AssignRightShiftExpr extends AssignBitwiseExpr, RightShiftOperation, @assign_rshift_expr { class AssignRightShiftExpr extends AssignBitwiseOperation, RightShiftOperation, @assign_rshift_expr {
override string getOperator() { result = ">>=" } override string getOperator() { result = ">>=" }
override string getAPrimaryQlClass() { result = "AssignRightShiftExpr" } override string getAPrimaryQlClass() { result = "AssignRightShiftExpr" }
} }
/** /**
* An unsigned right-shift assignment expression, for example `x >>>= y`. * An unsigned right-shift assignment operation, for example `x >>>= y`.
*/ */
class AssignUnsignedRightShiftExpr extends AssignBitwiseExpr, UnsignedRightShiftOperation, class AssignUnsignedRightShiftExpr extends AssignBitwiseOperation, UnsignedRightShiftOperation,
@assign_urshift_expr @assign_urshift_expr
{ {
override string getOperator() { result = ">>>=" } override string getOperator() { result = ">>>=" }
@@ -310,10 +297,10 @@ class RemoveEventExpr extends AddOrRemoveEventExpr, @remove_event_expr {
} }
/** /**
* A null-coalescing assignment expression, for example `x ??= y`. * A null-coalescing assignment operation, for example `x ??= y`.
*/ */
class AssignCoalesceExpr extends AssignOperation, NullCoalescingOperation, @assign_coalesce_expr { class AssignCoalesceExpr extends AssignOperation, NullCoalescingOperation, @assign_coalesce_expr {
override string getOperator() { result = "??=" } override string toString() { result = "... ??= ..." }
override string getAPrimaryQlClass() { result = "AssignCoalesceExpr" } override string getAPrimaryQlClass() { result = "AssignCoalesceExpr" }
} }

View File

@@ -10,16 +10,16 @@ import Expr
* A bitwise operation. Either a unary bitwise operation (`UnaryBitwiseOperation`) * A bitwise operation. Either a unary bitwise operation (`UnaryBitwiseOperation`)
* or a binary bitwise operation (`BinaryBitwiseOperation`). * or a binary bitwise operation (`BinaryBitwiseOperation`).
*/ */
class BitwiseOperation extends Operation, @bit_operation { } class BitwiseOperation extends Operation, @bit_expr { }
/** /**
* A unary bitwise operation, that is, a bitwise complement operation * A unary bitwise operation, that is, a bitwise complement operation
* (`ComplementExpr`). * (`ComplementExpr`).
*/ */
class UnaryBitwiseOperation extends BitwiseOperation, UnaryOperation, @un_bit_operation { } class UnaryBitwiseOperation extends BitwiseOperation, UnaryOperation, @un_bit_op_expr { }
/** /**
* A bitwise complement expression, for example `~x`. * A bitwise complement operation, for example `~x`.
*/ */
class ComplementExpr extends UnaryBitwiseOperation, @bit_not_expr { class ComplementExpr extends UnaryBitwiseOperation, @bit_not_expr {
override string getOperator() { result = "~" } override string getOperator() { result = "~" }
@@ -28,101 +28,67 @@ class ComplementExpr extends UnaryBitwiseOperation, @bit_not_expr {
} }
/** /**
* A binary bitwise operation. Either a binary bitwise expression (`BinaryBitwiseExpr`) or * A binary bitwise operation. Either a bitwise-and operation
* a bitwise assignment expression (`AssignBitwiseExpr`). * (`BitwiseAndExpr`), a bitwise-or operation (`BitwiseOrExpr`),
* a bitwise exclusive-or operation (`BitwiseXorExpr`), a left-shift
* operation (`LeftShiftExpr`), a right-shift operation (`RightShiftExpr`),
* or an unsigned right-shift operation (`UnsignedRightShiftExpr`).
*/ */
class BinaryBitwiseOperation extends BitwiseOperation, BinaryOperation, @bin_bit_operation { class BinaryBitwiseOperation extends BitwiseOperation, BinaryOperation, @bin_bit_op_expr {
override string getOperator() { none() } override string getOperator() { none() }
} }
/** /**
* A bitwise-and operation, either `x & y` or `x &= y`. * A left-shift operation, for example `x << y`.
*/ */
class BitwiseAndOperation extends BinaryBitwiseOperation, @and_operation { } class LeftShiftExpr extends BinaryBitwiseOperation, LeftShiftOperation, @lshift_expr {
/**
* A bitwise-or operation, either `x | y` or `x |= y`.
*/
class BitwiseOrOperation extends BinaryBitwiseOperation, @or_operation { }
/**
* A bitwise exclusive-or operation, either `x ^ y` or `x ^= y`.
*/
class BitwiseXorOperation extends BinaryBitwiseOperation, @xor_operation { }
/**
* A left-shift operation, either `x << y` or `x <<= y`.
*/
class LeftShiftOperation extends BinaryBitwiseOperation, @lshift_operation { }
/**
* A right-shift operation, either `x >> y` or `x >>= y`.
*/
class RightShiftOperation extends BinaryBitwiseOperation, @rshift_operation { }
/**
* An unsigned right-shift operation, either `x >>> y` or `x >>>= y`.
*/
class UnsignedRightShiftOperation extends BinaryBitwiseOperation, @urshift_operation { }
/**
* A binary bitwise expression. Either a bitwise-and expression
* (`BitwiseAndExpr`), a bitwise-or expression (`BitwiseOrExpr`),
* a bitwise exclusive-or expression (`BitwiseXorExpr`), a left-shift
* expression (`LeftShiftExpr`), a right-shift expression (`RightShiftExpr`),
* or an unsigned right-shift expression (`UnsignedRightShiftExpr`).
*/
class BinaryBitwiseExpr extends BinaryBitwiseOperation, @bin_bit_expr { }
/**
* A left-shift expression, for example `x << y`.
*/
class LeftShiftExpr extends BinaryBitwiseExpr, LeftShiftOperation, @lshift_expr {
override string getOperator() { result = "<<" } override string getOperator() { result = "<<" }
override string getAPrimaryQlClass() { result = "LeftShiftExpr" } override string getAPrimaryQlClass() { result = "LeftShiftExpr" }
} }
/** /**
* A right-shift expression, for example `x >> y`. * A right-shift operation, for example `x >> y`.
*/ */
class RightShiftExpr extends BinaryBitwiseExpr, RightShiftOperation, @rshift_expr { class RightShiftExpr extends BinaryBitwiseOperation, RightShiftOperation, @rshift_expr {
override string getOperator() { result = ">>" } override string getOperator() { result = ">>" }
override string getAPrimaryQlClass() { result = "RightShiftExpr" } override string getAPrimaryQlClass() { result = "RightShiftExpr" }
} }
/** /**
* An unsigned right-shift expression, for example `x >>> y`. * An unsigned right-shift operation, for example `x >>> y`.
*/ */
class UnsignedRightShiftExpr extends BinaryBitwiseExpr, UnsignedRightShiftOperation, @urshift_expr { class UnsignedRightShiftExpr extends BinaryBitwiseOperation, UnsignedRightShiftOperation,
@urshift_expr
{
override string getOperator() { result = ">>>" } override string getOperator() { result = ">>>" }
override string getAPrimaryQlClass() { result = "UnsignedRightShiftExpr" } override string getAPrimaryQlClass() { result = "UnsignedRightShiftExpr" }
} }
/** /**
* A bitwise-and expression, for example `x & y`. * A bitwise-and operation, for example `x & y`.
*/ */
class BitwiseAndExpr extends BinaryBitwiseExpr, BitwiseAndOperation, @bit_and_expr { class BitwiseAndExpr extends BinaryBitwiseOperation, BitwiseAndOperation, @bit_and_expr {
override string getOperator() { result = "&" } override string getOperator() { result = "&" }
override string getAPrimaryQlClass() { result = "BitwiseAndExpr" } override string getAPrimaryQlClass() { result = "BitwiseAndExpr" }
} }
/** /**
* A bitwise-or expression, for example `x | y`. * A bitwise-or operation, for example `x | y`.
*/ */
class BitwiseOrExpr extends BinaryBitwiseExpr, BitwiseOrOperation, @bit_or_expr { class BitwiseOrExpr extends BinaryBitwiseOperation, BitwiseOrOperation, @bit_or_expr {
override string getOperator() { result = "|" } override string getOperator() { result = "|" }
override string getAPrimaryQlClass() { result = "BitwiseOrExpr" } override string getAPrimaryQlClass() { result = "BitwiseOrExpr" }
} }
/** /**
* A bitwise exclusive-or expression, for example `x ^ y`. * A bitwise exclusive-or operation, for example `x ^ y`.
*/ */
class BitwiseXorExpr extends BinaryBitwiseExpr, BitwiseXorOperation, @bit_xor_expr { class BitwiseXorExpr extends BinaryBitwiseOperation, BitwiseXorOperation, @bit_xor_expr {
override string getOperator() { result = "^" } override string getOperator() { result = "^" }
override string getAPrimaryQlClass() { result = "BitwiseXorExpr" } override string getAPrimaryQlClass() { result = "BitwiseXorExpr" }

View File

@@ -609,7 +609,7 @@ class InstanceMutatorOperatorCall extends MutatorOperatorCall {
* } * }
* ``` * ```
*/ */
class CompoundAssignmentOperatorCall extends AssignCallExpr { class CompoundAssignmentOperatorCall extends AssignCallOperation {
CompoundAssignmentOperatorCall() { this.getTarget() instanceof CompoundAssignmentOperator } CompoundAssignmentOperatorCall() { this.getTarget() instanceof CompoundAssignmentOperator }
override Expr getArgument(int i) { result = this.getChildExpr(i + 1) and i >= 0 } override Expr getArgument(int i) { result = this.getChildExpr(i + 1) and i >= 0 }
@@ -762,12 +762,20 @@ class AccessorCall extends Call, QualifiableExpr, @call_access_expr {
*/ */
class PropertyCall extends AccessorCall, PropertyAccessExpr { class PropertyCall extends AccessorCall, PropertyAccessExpr {
override Accessor getReadTarget() { override Accessor getReadTarget() {
this instanceof AssignableRead and result = this.getProperty().getReadTarget() this instanceof AssignableRead and result = this.getProperty().getGetter()
} }
override Accessor getWriteTarget() { override Accessor getWriteTarget() {
this instanceof AssignableWrite and this instanceof AssignableWrite and
result = this.getProperty().getWriteTarget() exists(Property p | p = this.getProperty() |
result = p.getSetter()
or
result =
any(Getter g |
g = p.getGetter() and
g.getAnnotatedReturnType().isRef()
)
)
} }
override Expr getArgument(int i) { override Expr getArgument(int i) {
@@ -798,12 +806,20 @@ class PropertyCall extends AccessorCall, PropertyAccessExpr {
*/ */
class IndexerCall extends AccessorCall, IndexerAccessExpr { class IndexerCall extends AccessorCall, IndexerAccessExpr {
override Accessor getReadTarget() { override Accessor getReadTarget() {
this instanceof AssignableRead and result = this.getIndexer().getReadTarget() this instanceof AssignableRead and result = this.getIndexer().getGetter()
} }
override Accessor getWriteTarget() { override Accessor getWriteTarget() {
this instanceof AssignableWrite and this instanceof AssignableWrite and
result = this.getIndexer().getWriteTarget() exists(Indexer i | i = this.getIndexer() |
result = i.getSetter()
or
result =
any(Getter g |
g = i.getGetter() and
g.getAnnotatedReturnType().isRef()
)
)
} }
override Expr getArgument(int i) { override Expr getArgument(int i) {

View File

@@ -14,6 +14,7 @@ import Creation
import Dynamic import Dynamic
import Literal import Literal
import LogicalOperation import LogicalOperation
import Operation
import semmle.code.csharp.controlflow.ControlFlowElement import semmle.code.csharp.controlflow.ControlFlowElement
import semmle.code.csharp.Location import semmle.code.csharp.Location
import semmle.code.csharp.Stmt import semmle.code.csharp.Stmt
@@ -211,7 +212,7 @@ class LocalConstantDeclExpr extends LocalVariableDeclExpr {
* (`UnaryOperation`), a binary operation (`BinaryOperation`), or a * (`UnaryOperation`), a binary operation (`BinaryOperation`), or a
* ternary operation (`TernaryOperation`). * ternary operation (`TernaryOperation`).
*/ */
class Operation extends Expr, @operation_expr { class Operation extends Expr, @op_expr {
/** Gets the name of the operator in this operation. */ /** Gets the name of the operator in this operation. */
string getOperator() { none() } string getOperator() { none() }
@@ -226,7 +227,7 @@ class Operation extends Expr, @operation_expr {
* indirection operation (`PointerIndirectionExpr`), an address-of operation * indirection operation (`PointerIndirectionExpr`), an address-of operation
* (`AddressOfExpr`), or a unary logical operation (`UnaryLogicalOperation`). * (`AddressOfExpr`), or a unary logical operation (`UnaryLogicalOperation`).
*/ */
class UnaryOperation extends Operation, @un_operation { class UnaryOperation extends Operation, @un_op {
/** Gets the operand of this unary operation. */ /** Gets the operand of this unary operation. */
Expr getOperand() { result = this.getChild(0) } Expr getOperand() { result = this.getChild(0) }
@@ -240,7 +241,7 @@ class UnaryOperation extends Operation, @un_operation {
* a binary logical operation (`BinaryLogicalOperation`), or an * a binary logical operation (`BinaryLogicalOperation`), or an
* assignment (`Assignment`). * assignment (`Assignment`).
*/ */
class BinaryOperation extends Operation, @bin_operation { class BinaryOperation extends Operation, @bin_op {
/** Gets the left operand of this binary operation. */ /** Gets the left operand of this binary operation. */
Expr getLeftOperand() { result = this.getChild(0) } Expr getLeftOperand() { result = this.getChild(0) }
@@ -263,7 +264,7 @@ class BinaryOperation extends Operation, @bin_operation {
* A ternary operation, that is, a ternary conditional operation * A ternary operation, that is, a ternary conditional operation
* (`ConditionalExpr`). * (`ConditionalExpr`).
*/ */
class TernaryOperation extends Operation, @ternary_operation { } class TernaryOperation extends Operation, @ternary_op { }
/** /**
* A parenthesized expression, for example `(2 + 3)` in * A parenthesized expression, for example `(2 + 3)` in

View File

@@ -11,14 +11,14 @@ import Expr
* a binary logical operation (`BinaryLogicalOperation`), or a ternary logical * a binary logical operation (`BinaryLogicalOperation`), or a ternary logical
* operation (`TernaryLogicalOperation`). * operation (`TernaryLogicalOperation`).
*/ */
class LogicalOperation extends Operation, @log_operation { class LogicalOperation extends Operation, @log_expr {
override string getOperator() { none() } override string getOperator() { none() }
} }
/** /**
* A unary logical operation, that is, a logical 'not' (`LogicalNotExpr`). * A unary logical operation, that is, a logical 'not' (`LogicalNotExpr`).
*/ */
class UnaryLogicalOperation extends LogicalOperation, UnaryOperation, @un_log_operation { } class UnaryLogicalOperation extends LogicalOperation, UnaryOperation, @un_log_op_expr { }
/** /**
* A logical 'not', for example `!String.IsNullOrEmpty(s)`. * A logical 'not', for example `!String.IsNullOrEmpty(s)`.
@@ -31,10 +31,10 @@ class LogicalNotExpr extends UnaryLogicalOperation, @log_not_expr {
/** /**
* A binary logical operation. Either a logical 'and' (`LogicalAndExpr`), * A binary logical operation. Either a logical 'and' (`LogicalAndExpr`),
* a logical 'or' (`LogicalOrExpr`), or a null-coalescing operation * a logical 'or' (`LogicalAndExpr`), or a null-coalescing operation
* (`NullCoalescingOperation`). * (`NullCoalescingExpr`).
*/ */
class BinaryLogicalOperation extends LogicalOperation, BinaryOperation, @bin_log_operation { class BinaryLogicalOperation extends LogicalOperation, BinaryOperation, @bin_log_op_expr {
override string getOperator() { none() } override string getOperator() { none() }
} }
@@ -57,12 +57,7 @@ class LogicalOrExpr extends BinaryLogicalOperation, @log_or_expr {
} }
/** /**
* A null-coalescing operation, either `x ?? y` or `x ??= y`. * A null-coalescing operation, for example `s ?? ""` on line 2 in
*/
class NullCoalescingOperation extends BinaryLogicalOperation, @null_coalescing_operation { }
/**
* A null-coalescing expression, for example `s ?? ""` on line 2 in
* *
* ```csharp * ```csharp
* string NonNullOrEmpty(string s) { * string NonNullOrEmpty(string s) {
@@ -70,7 +65,9 @@ class NullCoalescingOperation extends BinaryLogicalOperation, @null_coalescing_o
* } * }
* ``` * ```
*/ */
class NullCoalescingExpr extends NullCoalescingOperation, @null_coalescing_expr { class NullCoalescingExpr extends BinaryLogicalOperation, NullCoalescingOperation,
@null_coalescing_expr
{
override string getOperator() { result = "??" } override string getOperator() { result = "??" }
override string getAPrimaryQlClass() { result = "NullCoalescingExpr" } override string getAPrimaryQlClass() { result = "NullCoalescingExpr" }
@@ -80,7 +77,7 @@ class NullCoalescingExpr extends NullCoalescingOperation, @null_coalescing_expr
* A ternary logical operation, that is, a ternary conditional expression * A ternary logical operation, that is, a ternary conditional expression
* (`ConditionalExpr`). * (`ConditionalExpr`).
*/ */
class TernaryLogicalOperation extends LogicalOperation, TernaryOperation, @ternary_log_operation { } class TernaryLogicalOperation extends LogicalOperation, TernaryOperation, @ternary_log_op_expr { }
/** /**
* A conditional expression, for example `s != null ? s.Length : -1` * A conditional expression, for example `s != null ? s.Length : -1`

View File

@@ -1,6 +1,71 @@
/** /**
* Provides classes for operations that also have compound assignment forms. * Provides classes for operations that also have compound assignment forms.
*/ */
deprecated module;
import Expr import Expr
/**
* An addition operation, either `x + y` or `x += y`.
*/
class AddOperation extends BinaryOperation, @add_operation { }
/**
* A subtraction operation, either `x - y` or `x -= y`.
*/
class SubOperation extends BinaryOperation, @sub_operation { }
/**
* A multiplication operation, either `x * y` or `x *= y`.
*/
class MulOperation extends BinaryOperation, @mul_operation { }
/**
* A division operation, either `x / y` or `x /= y`.
*/
class DivOperation extends BinaryOperation, @div_operation {
/** Gets the numerator of this division operation. */
Expr getNumerator() { result = this.getLeftOperand() }
/** Gets the denominator of this division operation. */
Expr getDenominator() { result = this.getRightOperand() }
}
/**
* A remainder operation, either `x % y` or `x %= y`.
*/
class RemOperation extends BinaryOperation, @rem_operation { }
/**
* A bitwise-and operation, either `x & y` or `x &= y`.
*/
class BitwiseAndOperation extends BinaryOperation, @and_operation { }
/**
* A bitwise-or operation, either `x | y` or `x |= y`.
*/
class BitwiseOrOperation extends BinaryOperation, @or_operation { }
/**
* A bitwise exclusive-or operation, either `x ^ y` or `x ^= y`.
*/
class BitwiseXorOperation extends BinaryOperation, @xor_operation { }
/**
* A left-shift operation, either `x << y` or `x <<= y`.
*/
class LeftShiftOperation extends BinaryOperation, @lshift_operation { }
/**
* A right-shift operation, either `x >> y` or `x >>= y`.
*/
class RightShiftOperation extends BinaryOperation, @rshift_operation { }
/**
* An unsigned right-shift operation, either `x >>> y` or `x >>>= y`.
*/
class UnsignedRightShiftOperation extends BinaryOperation, @urshift_operation { }
/**
* A null-coalescing operation, either `x ?? y` or `x ??= y`.
*/
class NullCoalescingOperation extends BinaryOperation, @null_coalescing_operation { }

Some files were not shown because too many files have changed in this diff Show More