Compare commits

..

52 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
030af8eac3 Remove dead "send" additional CFG node and unused SendInstruction 2026-06-25 12:45:49 +00:00
Owen Mansel-Chan
6fa89ef39f Fix break in select statements 2026-06-25 10:04:17 +01:00
Owen Mansel-Chan
62f90735fd f initial 2026-06-24 19:43:13 +01:00
Owen Mansel-Chan
67488a81c8 Go: adopt ForeachStmt with destructuring 2026-06-24 19:37:55 +01:00
Owen Mansel-Chan
8e99548cac Shared: allow destructuring in ForeachStmt 2026-06-24 19:37:50 +01:00
Owen Mansel-Chan
9f488f6daf Small refactors 2026-06-24 19:19:36 +01:00
copilot-swe-agent[bot]
d5f866535c Migrate Go type switches to shared CFG switch model (Option B) 2026-06-24 19:19:34 +01:00
copilot-swe-agent[bot]
cc80ecaaf6 Update SSA expected files for new shared-CFG node locations 2026-06-24 19:19:32 +01:00
copilot-swe-agent[bot]
73bf0613d9 Fix switch-case sanitizer edge for shared CFG and accept CFG expected 2026-06-24 19:19:30 +01:00
copilot-swe-agent[bot]
aa7bdb575f Migrate Go expression switch CFG to shared library; handle fallthrough via fallsThrough 2026-06-24 19:19:28 +01:00
Owen Mansel-Chan
2e1289dbdd Test changes to be checked 2026-06-24 19:19:25 +01:00
Owen Mansel-Chan
7bfab958dd Update query for unreachable statements 2026-06-24 19:19:23 +01:00
Owen Mansel-Chan
80bd6117ec Fix calls for defer statements 2026-06-24 19:19:21 +01:00
Owen Mansel-Chan
8fd9c31e81 Go CFG: run deferred calls at function exit in LIFO order
Model `defer`ed calls so the call runs at function exit rather than inline
at the `defer` statement, reproducing the previous control-flow semantics:

- Add a per-defer "defer-invoke" node for the deferred call.
- deferExitStep wires normal-exit predecessors (return nodes and body
  fall-through) through the active deferred-call invocations in
  last-in-first-out order, then on to the normal exit target (the
  result-read epilogue for named results, or the normal exit node).
- The chain is reachability-gated using the defer-free successor relation
  (succIgnoringDeferExit / isInOrderNode), so only deferred calls that were
  actually registered on a path are run on that path.
- overridesCallableBodyExit / overridesCallableEndAbruptCompletion suppress
  the default body-exit and return routing for functions containing
  `defer`, so the epilogue is interposed instead.
2026-06-24 19:19:19 +01:00
Owen Mansel-Chan
5b79e0d20d Shared CFG: add hooks for reachability-gated exit epilogue
Add opt-in InputSig2 predicates to support a function-exit epilogue whose
placement depends on reachability (such as Go's deferred calls, run at
exit in last-in-first-out order):

- deferExitStep: language-provided exit-epilogue edges, wired into
  explicitStep but excluded from the defer-free reachability.
- overridesCallableBodyExit: suppresses the default fall-through edge from
  a body's "after" node to the normal exit, so the epilogue can be
  interposed on fall-through paths.

To let a language compute the reachability gate for those edges without
observing them (and without a non-monotonic cycle through reachable):

- explicitStep is split into explicitStepCommon (defer-free) plus
  deferExitStep, and defaultCfg now negates explicitStepCommon so default
  evaluation does not depend on deferExitStep.
- succIgnoringDeferExit exposes the defer-free successor relation, typed
  over PreControlFlowNode so it does not depend on reachable.
- getASuccessorIgnoringDeferredExit exposes the same relation as a
  ControlFlowNode member for general use.
- isInOrderNode exposes a structural, reachability-free node-identity test
  for use inside the negations a language needs when computing its gate.
- EntryNodeImpl is no longer private, so a language can identify the entry
  node over PreControlFlowNode.

All InputSig2 additions default to none(), leaving other languages
unchanged.
2026-06-24 19:19:17 +01:00
Owen Mansel-Chan
71cc56464b Go CFG: anchor result-read epilogue on Normal Exit via new hooks 2026-06-24 19:19:15 +01:00
Owen Mansel-Chan
ac1b126efd Shared CFG: add callableExitStep hook for routing epilogue tails to exit nodes 2026-06-24 19:19:13 +01:00
Owen Mansel-Chan
0e50b3c00a Allow overriding endAbruptCompletion for callables 2026-06-24 19:19:11 +01:00
Owen Mansel-Chan
b0e886f553 Include implicit type switch var in CFG 2026-06-24 19:19:09 +01:00
Owen Mansel-Chan
9090ed3802 accept test changes 2026-06-24 19:19:07 +01:00
Owen Mansel-Chan
ebe24b26f7 Fix CFG for expressionless switch statements 2026-06-24 19:19:05 +01:00
Owen Mansel-Chan
e5cd376670 Accept changes 2026-06-24 19:19:03 +01:00
Owen Mansel-Chan
4a3fbf3beb Fix ConditionGuardNode 2026-06-24 19:19:01 +01:00
Owen Mansel-Chan
f5b0127bba Accept test output change names 2026-06-24 19:18:59 +01:00
Owen Mansel-Chan
f436d89b3f Restore ConditionGuardNode 2026-06-24 19:18:57 +01:00
Owen Mansel-Chan
c5895a85ce Fix treatment of ParenExpr 2026-06-24 19:18:55 +01:00
Owen Mansel-Chan
85d198ebdf Add Go CFG consistency query 2026-06-24 19:18:53 +01:00
Owen Mansel-Chan
4409d8271c Go: update expected node names 2026-06-24 19:18:51 +01:00
Owen Mansel-Chan
3a955b172f Use shared CFG getIfInit 2026-06-24 19:18:49 +01:00
Owen Mansel-Chan
c4011f16aa Fix lit-init nodes
There was a loop in the definitions which meant nothing ever got made
2026-06-24 19:18:47 +01:00
Owen Mansel-Chan
63464ecb84 Fix range loop CFG 2026-06-24 19:18:45 +01:00
Owen Mansel-Chan
4d627be3a0 update function-entry additional nodes 2026-06-24 19:18:43 +01:00
Owen Mansel-Chan
5962a22bdb Restore ExprNode for FuncLit 2026-06-24 19:18:41 +01:00
Owen Mansel-Chan
7430209c3a Accept change in test output
To determine that test9 can't return normally, you have to use the fact
that test5 can't return normally. This would make CFG construction
recursive, which would be bad for performance. Therefore we accept the
limitation that we cannot detect that test9 can't return normally, and
we change the test output.
2026-06-24 19:18:39 +01:00
Owen Mansel-Chan
dc7867b459 Fix empty switch statements 2026-06-24 19:18:37 +01:00
Owen Mansel-Chan
8c7859929c Control flow shouldn't enter another callable 2026-06-24 19:18:34 +01:00
Owen Mansel-Chan
c1dfeaca9f Fix CFG for return instructions 2026-06-24 19:18:32 +01:00
Owen Mansel-Chan
1bcdbd2d34 Produce CFG nodes for more reference expressions, like selector bases 2026-06-24 19:18:30 +01:00
Owen Mansel-Chan
cffc3c7bec Fix global value numbering calculation 2026-06-24 19:18:28 +01:00
Owen Mansel-Chan
8fb4875021 Include receivers in parameter init 2026-06-24 19:18:26 +01:00
Owen Mansel-Chan
008cbbadcf Fix CFG for range loop 2026-06-24 19:18:24 +01:00
Owen Mansel-Chan
d6d254fdda Fix CFG for select statements 2026-06-24 19:18:22 +01:00
Owen Mansel-Chan
a5e761254e Use shared CFG implementation of for loops 2026-06-24 19:18:20 +01:00
Owen Mansel-Chan
e328a5d69e Do not include comments in the CFG 2026-06-24 19:18:18 +01:00
Owen Mansel-Chan
01568dd2db Fix edges to function exit with result variables 2026-06-24 19:18:16 +01:00
Owen Mansel-Chan
c136d3ec30 Tweak getEnclosingCallable 2026-06-24 19:18:14 +01:00
Owen Mansel-Chan
6a1649d384 Model non-returning functions in CFG 2026-06-24 19:18:12 +01:00
Owen Mansel-Chan
fbd82ffd68 Create cfg node for child of ParenExpr 2026-06-24 19:18:10 +01:00
Owen Mansel-Chan
3937a96ca4 Add go/print-cfg 2026-06-24 19:18:07 +01:00
Owen Mansel-Chan
a26cbad255 Initial shared CFG library instantiation for Go 2026-06-24 19:18:05 +01:00
Owen Mansel-Chan
412f4c2403 Incidental fix to CaseClause.getAnExpr() 2026-06-24 19:18:03 +01:00
copilot-swe-agent[bot]
1e05710256 Initial plan 2026-06-24 19:18:01 +01:00
468 changed files with 9033 additions and 13156 deletions

View File

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

View File

@@ -1,10 +1,3 @@
## 0.4.38
### Bug Fixes
* GitHub Actions queries now better account for permission checks on jobs that call reusable workflows.
* The query `actions/pr-on-self-hosted-runner` was updated to the latest standard runner labels reducing false positive results.
## 0.4.37
### Minor Analysis Improvements

View File

@@ -0,0 +1,4 @@
---
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,6 +0,0 @@
## 0.4.38
### Bug Fixes
* GitHub Actions queries now better account for permission checks on jobs that call reusable workflows.
* The query `actions/pr-on-self-hosted-runner` was updated to the latest standard runner labels reducing false positive results.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.4.38
lastReleaseVersion: 0.4.37

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 */
abstract class ControlCheck extends AstNode {
ControlCheck() {
@@ -62,170 +53,43 @@ abstract class ControlCheck extends AstNode {
predicate protects(AstNode node, Event event, string category) {
// 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
this.protectsCategoryAndEvent(category, event.getName()) and
// The check can be triggered by the event
this.getATriggerEvent() = event and
// 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)
)
)
this.getATriggerEvent() = event
}
/**
* 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) {
predicate dominates(AstNode node) {
this instanceof If and
(
caller.getIf() = this or
caller.getANeededJob().(LocalJob).getIf() = this or
caller.getANeededJob().(LocalJob).getAStep().getIf() = this
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
this instanceof Environment and
(
caller.getEnvironment() = this or
caller.getANeededJob().getEnvironment() = this
node.getEnclosingJob().getEnvironment() = this
or
node.getEnclosingJob().getANeededJob().getEnvironment() = this
)
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);
}
/**
* 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 {
// 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

View File

@@ -1,5 +1,5 @@
name: codeql/actions-all
version: 0.4.39-dev
version: 0.4.38-dev
library: true
warnOnImplicitThis: true
dependencies:

View File

@@ -1,9 +1,3 @@
## 0.6.30
### Query Metadata Changes
* The name, description, and alert message of `actions/untrusted-checkout/medium` have been corrected to describe a non-privileged context.
## 0.6.29
### Query Metadata Changes

View File

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

View File

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

View File

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

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.6.30
lastReleaseVersion: 0.6.29

View File

@@ -1,5 +1,5 @@
name: codeql/actions-queries
version: 0.6.31-dev
version: 0.6.30-dev
library: false
warnOnImplicitThis: true
groups: [actions, queries]

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: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/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: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 |
@@ -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: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_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_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 |
@@ -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: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/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/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 |
@@ -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_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_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,20 +1,3 @@
## 11.0.0
### Breaking Changes
* Removed the deprecated `overrideReturnsNull` predicate from `Options.qll`. Use `CustomOptions.overrideReturnsNull` instead.
* Removed the deprecated `returnsNull` predicate from `Options.qll`. Use `CustomOptions.returnsNull` instead.
* Removed the deprecated `exits` predicate from `Options.qll`. Use `CustomOptions.exits` instead.
* Removed the deprecated `exprExits` predicate from `Options.qll`. Use `CustomOptions.exprExits` instead.
* Removed the deprecated `alwaysCheckReturnValue` predicate from `Options.qll`. Use `CustomOptions.alwaysCheckReturnValue` instead.
* Removed the deprecated `okToIgnoreReturnValue` predicate from `Options.qll`. Use `CustomOptions.okToIgnoreReturnValue` instead.
* Removed the deprecated `semmle.code.cpp.Member`. Import `semmle.code.cpp.Element` and/or `semmle.code.cpp.Type` directly.
* Removed the deprecated `UnknownDefaultLocation` class. Use `UnknownLocation` instead.
* Removed the deprecated `UnknownExprLocation` class. Use `UnknownLocation` instead.
* Removed the deprecated `UnknownStmtLocation` class. Use `UnknownLocation` instead.
* Removed the deprecated `TemplateParameter` class. Use `TypeTemplateParameter` instead.
* Support for class resolution across link targets has been removed for databases which were created with CodeQL versions before 1.23.0.
## 10.2.0
### Deprecated APIs

View File

@@ -1,7 +1,6 @@
## 11.0.0
### Breaking Changes
---
category: breaking
---
* Removed the deprecated `overrideReturnsNull` predicate from `Options.qll`. Use `CustomOptions.overrideReturnsNull` instead.
* Removed the deprecated `returnsNull` predicate from `Options.qll`. Use `CustomOptions.returnsNull` instead.
* Removed the deprecated `exits` predicate from `Options.qll`. Use `CustomOptions.exits` instead.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 11.0.0
lastReleaseVersion: 10.2.0

View File

@@ -1,5 +1,5 @@
name: codeql/cpp-all
version: 11.0.1-dev
version: 10.2.1-dev
groups: cpp
dbscheme: semmlecode.cpp.dbscheme
extractor: cpp

View File

@@ -1,7 +1,3 @@
## 1.6.5
No user-facing changes.
## 1.6.4
No user-facing changes.

View File

@@ -1,3 +0,0 @@
## 1.6.5
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 1.6.5
lastReleaseVersion: 1.6.4

View File

@@ -1,5 +1,5 @@
name: codeql/cpp-queries
version: 1.6.6-dev
version: 1.6.5-dev
groups:
- cpp
- queries

View File

@@ -1,7 +1,3 @@
## 1.7.69
No user-facing changes.
## 1.7.68
No user-facing changes.

View File

@@ -1,3 +0,0 @@
## 1.7.69
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 1.7.69
lastReleaseVersion: 1.7.68

View File

@@ -1,5 +1,5 @@
name: codeql/csharp-solorigate-all
version: 1.7.70-dev
version: 1.7.69-dev
groups:
- csharp
- solorigate

View File

@@ -1,7 +1,3 @@
## 1.7.69
No user-facing changes.
## 1.7.68
No user-facing changes.

View File

@@ -1,3 +0,0 @@
## 1.7.69
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 1.7.69
lastReleaseVersion: 1.7.68

View File

@@ -1,5 +1,5 @@
name: codeql/csharp-solorigate-queries
version: 1.7.70-dev
version: 1.7.69-dev
groups:
- csharp
- solorigate

View File

@@ -1,19 +1,3 @@
## 7.0.0
### Breaking Changes
* Renamed types related to *operation* expressions. The QL classes `BinaryArithmeticOperation`, `BinaryBitwiseOperation`, and `BinaryLogicalOperation` now include compound assignments; for example, `BinaryArithmeticOperation` now includes `a += b`.
### Major Analysis Improvements
* 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.
### Minor Analysis Improvements
* Improved property and indexer call target resolution for partially overridden properties and indexers.
* 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.
* Improved call target resolution for ref-return properties and indexers.
## 6.0.2
### Minor Analysis Improvements

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Improved call target resolution for ref-return properties and indexers.

View File

@@ -0,0 +1,4 @@
---
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

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

View File

@@ -0,0 +1,4 @@
---
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

@@ -0,0 +1,4 @@
---
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

@@ -1,15 +0,0 @@
## 7.0.0
### Breaking Changes
* Renamed types related to *operation* expressions. The QL classes `BinaryArithmeticOperation`, `BinaryBitwiseOperation`, and `BinaryLogicalOperation` now include compound assignments; for example, `BinaryArithmeticOperation` now includes `a += b`.
### Major Analysis Improvements
* 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.
### Minor Analysis Improvements
* Improved property and indexer call target resolution for partially overridden properties and indexers.
* 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.
* Improved call target resolution for ref-return properties and indexers.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 7.0.0
lastReleaseVersion: 6.0.2

View File

@@ -1,5 +1,5 @@
name: codeql/csharp-all
version: 7.0.1-dev
version: 6.0.3-dev
groups: csharp
dbscheme: semmlecode.csharp.dbscheme
extractor: csharp

View File

@@ -1,7 +1,3 @@
## 1.7.5
No user-facing changes.
## 1.7.4
No user-facing changes.

View File

@@ -14,6 +14,54 @@
import csharp
/**
* Gets a callable that either directly captures local variable `v`, or which
* is enclosed by the callable that declares `v` and encloses a callable that
* captures `v`.
*/
Callable getACapturingCallableAncestor(LocalVariable v) {
result = v.getACapturingCallable()
or
exists(Callable mid | mid = getACapturingCallableAncestor(v) |
result = mid.getEnclosingCallable() and
not v.getEnclosingCallable() = result
)
}
Expr getADelegateExpr(Callable c) {
c = result.(CallableAccess).getTarget()
or
result = c.(AnonymousFunctionExpr)
}
/**
* Holds if `c` is a call where any delegate argument is evaluated immediately.
*/
predicate nonEscapingCall(Call c) {
exists(string name | c.getTarget().hasName(name) |
name =
[
"ForEach", "Count", "Any", "All", "Average", "Aggregate", "First", "Last", "FirstOrDefault",
"LastOrDefault", "LongCount", "Max", "Single", "SingleOrDefault", "Sum"
]
)
}
/**
* Holds if `v` is a captured local variable, and one of the callables capturing
* `v` may escape the local scope.
*/
predicate mayEscape(LocalVariable v) {
exists(Callable c, Expr e, Expr succ | c = getACapturingCallableAncestor(v) |
e = getADelegateExpr(c) and
DataFlow::localExprFlow(e, succ) and
not succ = any(DelegateCall dc).getExpr() and
not succ = any(Cast cast).getExpr() and
not succ = any(Call call | nonEscapingCall(call)).getAnArgument() and
not succ = any(AssignableDefinition ad | ad.getTarget() instanceof LocalVariable).getSource()
)
}
class RelevantDefinition extends AssignableDefinition {
RelevantDefinition() {
this.(AssignableDefinitions::AssignmentDefinition).getAssignment() =
@@ -46,6 +94,8 @@ class RelevantDefinition extends AssignableDefinition {
// SSA definitions are only created for live variables
this = any(SsaExplicitWrite ssaDef).getDefinition()
or
mayEscape(v)
or
v.isCaptured()
)
}

View File

@@ -1,3 +0,0 @@
## 1.7.5
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 1.7.5
lastReleaseVersion: 1.7.4

View File

@@ -1,5 +1,5 @@
name: codeql/csharp-queries
version: 1.7.6-dev
version: 1.7.5-dev
groups:
- csharp
- queries

View File

@@ -3,13 +3,13 @@ class C
void Problems()
{
// correct expectation comment, but only for `problem-query`
var x = "Alert"; // $ Alert[problem-query]
var x = "Alert"; // $ Alert
// irrelevant expectation comment, will be ignored
x = "Not an alert"; // $ IrrelevantTag
// incorrect expectation comment
x = "Also not an alert"; // $ MISSING: Alert[problem-query]
x = "Also not an alert"; // $ Alert
// missing expectation comment, but only for `problem-query`
x = "Alert";

View File

@@ -13,6 +13,8 @@
| InlineTests.cs:88:13:88:23 | "Alert:0:1" | InlineTests.cs:88:13:88:23 | "Alert:0:1" | InlineTests.cs:87:16:87:21 | "Sink" | This is a problem |
edges
testFailures
| InlineTests.cs:6:26:6:35 | // ... | Missing result: Alert |
| InlineTests.cs:12:34:12:43 | // ... | Missing result: Alert |
| InlineTests.cs:37:28:37:38 | // ... | Missing result: Source |
| InlineTests.cs:38:24:38:32 | // ... | Missing result: Sink |
| InlineTests.cs:39:33:39:42 | // ... | Missing result: Alert |

View File

@@ -3,6 +3,8 @@
| InlineTests.cs:100:13:100:25 | "Alert:3:2:1" | InlineTests.cs:97:18:97:25 | "Source" | InlineTests.cs:98:16:98:21 | "Sink" | This is a problem with $@ | InlineTests.cs:99:19:99:27 | "Related" | a related location |
edges
testFailures
| InlineTests.cs:6:26:6:35 | // ... | Missing result: Alert |
| InlineTests.cs:12:34:12:43 | // ... | Missing result: Alert |
| InlineTests.cs:32:32:32:42 | // ... | Missing result: Source |
| InlineTests.cs:33:28:33:36 | // ... | Missing result: Sink |
| InlineTests.cs:34:30:34:39 | // ... | Missing result: Alert |

View File

@@ -3,6 +3,7 @@
| InlineTests.cs:15:13:15:19 | "Alert" | This is a problem |
| InlineTests.cs:18:13:18:19 | "Alert" | This is a problem |
testFailures
| InlineTests.cs:12:34:12:43 | // ... | Missing result: Alert |
| InlineTests.cs:15:13:15:19 | This is a problem | Unexpected result: Alert |
| InlineTests.cs:34:30:34:39 | // ... | Missing result: Alert |
| InlineTests.cs:39:33:39:42 | // ... | Missing result: Alert |

View File

@@ -2,6 +2,8 @@
| InlineTests.cs:22:13:22:21 | "Alert:1" | This is a problem with $@ | InlineTests.cs:21:23:21:31 | "Related" | a related location |
| InlineTests.cs:26:13:26:21 | "Alert:1" | This is a problem with $@ | InlineTests.cs:25:19:25:27 | "Related" | a related location |
testFailures
| InlineTests.cs:6:26:6:35 | // ... | Missing result: Alert |
| InlineTests.cs:12:34:12:43 | // ... | Missing result: Alert |
| InlineTests.cs:25:19:25:27 | "Related" | Unexpected result: RelatedLocation |
| InlineTests.cs:34:30:34:39 | // ... | Missing result: Alert |
| InlineTests.cs:39:33:39:42 | // ... | Missing result: Alert |

View File

@@ -21,7 +21,7 @@
Java,"Java 7 to 26 [6]_","javac (OpenJDK and Oracle JDK),
Eclipse compiler for Java (ECJ) [7]_",``.java``
Kotlin,"Kotlin 1.8.0 to 2.4.0\ *x*","kotlinc",``.kt``
Kotlin,"Kotlin 1.8.0 to 2.3.2\ *x*","kotlinc",``.kt``
JavaScript,ECMAScript 2022 or lower,Not applicable,"``.js``, ``.jsx``, ``.mjs``, ``.es``, ``.es6``, ``.htm``, ``.html``, ``.xhtm``, ``.xhtml``, ``.vue``, ``.hbs``, ``.ejs``, ``.njk``, ``.json``, ``.yaml``, ``.yml``, ``.raml``, ``.xml`` [8]_"
Python [9]_,"2.7, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13",Not applicable,``.py``
Ruby [10]_,"up to 3.3",Not applicable,"``.rb``, ``.erb``, ``.gemspec``, ``Gemfile``"

View File

@@ -10,7 +10,7 @@ toolchain go1.26.4
// bazel mod tidy
require (
golang.org/x/mod v0.37.0
golang.org/x/tools v0.47.0
golang.org/x/tools v0.46.0
)
require github.com/stretchr/testify v1.11.1

View File

@@ -10,8 +10,8 @@ golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk=
golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -1,7 +1,3 @@
## 1.0.52
No user-facing changes.
## 1.0.51
No user-facing changes.

View File

@@ -0,0 +1,3 @@
import go
private import semmle.go.controlflow.ControlFlowGraphShared
import GoCfg::ControlFlow::Consistency

View File

@@ -1,3 +0,0 @@
## 1.0.52
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 1.0.52
lastReleaseVersion: 1.0.51

View File

@@ -1,5 +1,5 @@
name: codeql-go-consistency-queries
version: 1.0.53-dev
version: 1.0.52-dev
groups:
- go
- queries

View File

@@ -1,20 +1,3 @@
## 7.2.0
### Deprecated APIs
* `FuncTypeExpr.getResultDecl()` has been deprecated. Use `FuncTypeExpr.getResultDecl(int i)` instead.
### Minor Analysis Improvements
* Added models for the `log/slog` package (Go 1.21+). Its logging functions and
`*slog.Logger` methods (`Debug`/`Info`/`Warn`/`Error`, their `Context`
variants, and `Log`/`LogAttrs`) are now recognized as logging sinks, so the
`go/log-injection` and `go/clear-text-logging` queries cover code that logs
through `slog`.
* `DataFlow::ResultNode`s are no longer created for returned expressions in functions with named result parameters. In this case there are already result nodes corresponding to `IR::ReadResultInstruction`s at the end of the function body.
* `FuncTypeExpr.getNumResult()` now gets the number of result parameters. It previously got the number of result declarations, which is different when one result declaration declares more than one variable, as in `x, y int`. All uses of it expected the number of result parameters. Its QLDoc has been updated.
* More logging functions are now recognized as not returning or panicking.
## 7.1.2
No user-facing changes.

View File

@@ -0,0 +1,4 @@
---
category: fix
---
* The Go control flow graph implementation has been migrated to use the shared CFG library. This is an internal change with no user-visible API changes.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* More logging functions are now recognized as not returning or panicking.

View File

@@ -0,0 +1,4 @@
---
category: deprecated
---
* `FuncTypeExpr.getResultDecl()` has been deprecated. Use `FuncTypeExpr.getResultDecl(int i)` instead.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* `DataFlow::ResultNode`s are no longer created for returned expressions in functions with named result parameters. In this case there are already result nodes corresponding to `IR::ReadResultInstruction`s at the end of the function body.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* `FuncTypeExpr.getNumResult()` now gets the number of result parameters. It previously got the number of result declarations, which is different when one result declaration declares more than one variable, as in `x, y int`. All uses of it expected the number of result parameters. Its QLDoc has been updated.

View File

@@ -0,0 +1,8 @@
---
category: minorAnalysis
---
* Added models for the `log/slog` package (Go 1.21+). Its logging functions and
`*slog.Logger` methods (`Debug`/`Info`/`Warn`/`Error`, their `Context`
variants, and `Log`/`LogAttrs`) are now recognized as logging sinks, so the
`go/log-injection` and `go/clear-text-logging` queries cover code that logs
through `slog`.

View File

@@ -1,16 +0,0 @@
## 7.2.0
### Deprecated APIs
* `FuncTypeExpr.getResultDecl()` has been deprecated. Use `FuncTypeExpr.getResultDecl(int i)` instead.
### Minor Analysis Improvements
* Added models for the `log/slog` package (Go 1.21+). Its logging functions and
`*slog.Logger` methods (`Debug`/`Info`/`Warn`/`Error`, their `Context`
variants, and `Log`/`LogAttrs`) are now recognized as logging sinks, so the
`go/log-injection` and `go/clear-text-logging` queries cover code that logs
through `slog`.
* `DataFlow::ResultNode`s are no longer created for returned expressions in functions with named result parameters. In this case there are already result nodes corresponding to `IR::ReadResultInstruction`s at the end of the function body.
* `FuncTypeExpr.getNumResult()` now gets the number of result parameters. It previously got the number of result declarations, which is different when one result declaration declares more than one variable, as in `x, y int`. All uses of it expected the number of result parameters. Its QLDoc has been updated.
* More logging functions are now recognized as not returning or panicking.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 7.2.0
lastReleaseVersion: 7.1.2

53
go/ql/lib/printCfg.ql Normal file
View File

@@ -0,0 +1,53 @@
/**
* @name Print CFG
* @description Produces a representation of a file's Control Flow Graph.
* This query is used by the VS Code extension.
* @id go/print-cfg
* @kind graph
* @tags ide-contextual-queries/print-cfg
*/
import go
import semmle.go.controlflow.ControlFlowGraph
private import semmle.go.controlflow.ControlFlowGraphShared
external string selectedSourceFile();
private predicate selectedSourceFileAlias = selectedSourceFile/0;
external int selectedSourceLine();
private predicate selectedSourceLineAlias = selectedSourceLine/0;
external int selectedSourceColumn();
private predicate selectedSourceColumnAlias = selectedSourceColumn/0;
module ViewCfgQueryInput implements GoCfg::ControlFlow::ViewCfgQueryInputSig<File> {
predicate selectedSourceFile = selectedSourceFileAlias/0;
predicate selectedSourceLine = selectedSourceLineAlias/0;
predicate selectedSourceColumn = selectedSourceColumnAlias/0;
predicate cfgScopeSpan(
CfgScope scope, File file, int startLine, int startColumn, int endLine, int endColumn
) {
file = scope.getFile() and
scope.getLocation().getStartLine() = startLine and
scope.getLocation().getStartColumn() = startColumn and
exists(Location loc |
loc.getEndLine() = endLine and
loc.getEndColumn() = endColumn and
loc = scope.(FuncDef).getBody().getLocation()
)
or
file = scope.(File) and
startLine = 1 and
startColumn = 1 and
endLine = file.getNumberOfLines() and
endColumn = 999999
}
}
import GoCfg::ControlFlow::ViewCfgQuery<File, ViewCfgQueryInput>

View File

@@ -1,5 +1,5 @@
name: codeql/go-all
version: 7.2.1-dev
version: 7.1.3-dev
groups: go
dbscheme: go.dbscheme
extractor: go

View File

@@ -431,7 +431,7 @@ private class HeuristicLoggerFunction extends Method {
)
}
override predicate mayReturnNormally() { logFunctionPrefix != "Fatal" }
override predicate mustNotReturnNormally() { logFunctionPrefix = "Fatal" }
override predicate mustPanic() { logFunctionPrefix = "Panic" }
}

View File

@@ -1,7 +1,7 @@
/**
* Provides queries to pretty-print a Go AST as a graph.
*/
overlay[local]
overlay[local?]
module;
import go

View File

@@ -437,11 +437,12 @@ class Function extends ValueEntity, @functionobject {
* This predicate is an over-approximation: it may hold for functions that can never
* return normally, but it never fails to hold for functions that can.
*
* Note this is declared here and not in `DeclaredFunction` so that library models can override this
* by extending `Function` rather than having to remember to extend `DeclaredFunction`.
* Library models should not override this predicate; override `mustNotReturnNormally`
* instead, so that the control-flow graph construction can take the model into account.
*/
predicate mayReturnNormally() {
not this.mustPanic() and
not this.mustNotReturnNormally() and
(ControlFlow::mayReturnNormally(this.getFuncDecl()) or not exists(this.getBody()))
}
@@ -461,6 +462,16 @@ class Function extends ValueEntity, @functionobject {
*/
predicate mustPanic() { none() }
/**
* Holds if calling this function never returns normally (for example because it
* always panics, exits the process, or loops forever).
*
* Unlike `mayReturnNormally`, this predicate must be defined without reference to
* the control-flow graph, so that it can be used during CFG construction to
* suppress normal-flow successors of calls to this function.
*/
predicate mustNotReturnNormally() { none() }
/** Gets the number of parameters of this function. */
int getNumParameter() { result = this.getType().(SignatureType).getNumParameter() }

View File

@@ -761,7 +761,7 @@ class CaseClause extends @caseclause, Stmt, ScopeNode {
*
* Note that the default clause does not have any expressions.
*/
Expr getAnExpr() { result = this.getAChildExpr() }
Expr getAnExpr() { result = this.getExpr(_) }
/**
* Gets the number of expressions of this `case` clause.

View File

@@ -5,66 +5,27 @@ overlay[local]
module;
import go
private import ControlFlowGraphImpl
private import codeql.controlflow.BasicBlock as BB
private import codeql.controlflow.SuccessorType
private import ControlFlowGraphShared
private module Input implements BB::InputSig<Location> {
/** A delineated part of the AST with its own CFG. */
class CfgScope = ControlFlow::Root;
/** A basic block in the control-flow graph. */
class BasicBlock = GoCfg::Cfg::BasicBlock;
/** The class of control flow nodes. */
class Node = ControlFlowNode;
/** Gets the CFG scope in which this node occurs. */
CfgScope nodeGetCfgScope(Node node) { node.getRoot() = result }
/** Gets an immediate successor of this node. */
Node nodeGetASuccessor(Node node, SuccessorType t) {
result = node.getASuccessor() and
(
not result instanceof ControlFlow::ConditionGuardNode and t instanceof DirectSuccessor
or
t.(BooleanSuccessor).getValue() = result.(ControlFlow::ConditionGuardNode).getOutcome()
)
}
/**
* Holds if `node` represents an entry node to be used when calculating
* dominance.
*/
predicate nodeIsDominanceEntry(Node node) { node instanceof EntryNode }
/**
* Holds if `node` represents an exit node to be used when calculating
* post dominance.
*/
predicate nodeIsPostDominanceExit(Node node) { node instanceof ExitNode }
}
module Cfg = BB::Make<Location, Input>;
class BasicBlock = Cfg::BasicBlock;
class EntryBasicBlock = Cfg::EntryBasicBlock;
cached
private predicate reachableBB(BasicBlock bb) {
bb instanceof EntryBasicBlock
or
exists(BasicBlock predBB | predBB.getASuccessor(_) = bb | reachableBB(predBB))
}
/** An entry basic block. */
class EntryBasicBlock = GoCfg::Cfg::EntryBasicBlock;
/**
* A basic block that is reachable from an entry basic block.
*
* Since the shared CFG library only creates nodes for reachable code,
* all basic blocks are reachable by construction.
*/
class ReachableBasicBlock extends BasicBlock {
ReachableBasicBlock() { reachableBB(this) }
ReachableBasicBlock() { any() }
}
/**
* A reachable basic block with more than one predecessor.
*/
class ReachableJoinBlock extends ReachableBasicBlock {
ReachableJoinBlock() { this.getFirstNode().isJoin() }
ReachableJoinBlock() { this.getFirstNode().(ControlFlow::Node).isJoin() }
}

View File

@@ -5,13 +5,17 @@ overlay[local]
module;
import go
private import ControlFlowGraphImpl
private import ControlFlowGraphShared
/** Provides helper predicates for mapping btween CFG nodes and the AST. */
/** Provides helper predicates for mapping between CFG nodes and the AST. */
module ControlFlow {
/** A file or function with which a CFG is associated. */
class Root extends AstNode {
Root() { exists(this.(File).getADecl()) or exists(this.(FuncDef).getBody()) }
Root() {
exists(this.(FuncDef).getBody())
or
exists(this.(File).getADecl())
}
/** Holds if `nd` belongs to this file or function. */
predicate isRootOf(AstNode nd) {
@@ -29,22 +33,16 @@ module ControlFlow {
}
/**
* A node in the intra-procedural control-flow graph of a Go function or file.
* A node in the intra-procedural control-flow graph of a Go function.
*
* Nodes correspond to expressions and statements that compute a value or perform
* an operation (as opposed to providing syntactic structure or type information).
*
* There are also synthetic entry and exit nodes for each Go function and file
* There are also synthetic entry and exit nodes for each Go function
* that mark the beginning and the end, respectively, of the execution of the
* function and the loading of the file.
* function.
*/
class Node extends TControlFlowNode {
/** Gets a node that directly follows this one in the control-flow graph. */
Node getASuccessor() { result = CFG::succ(this) }
/** Gets a node that directly precedes this one in the control-flow graph. */
Node getAPredecessor() { this = result.getASuccessor() }
class Node extends GoCfg::ControlFlowNode {
/** Holds if this is a node with more than one successor. */
predicate isBranch() { strictcount(this.getASuccessor()) > 1 }
@@ -52,22 +50,23 @@ module ControlFlow {
predicate isJoin() { strictcount(this.getAPredecessor()) > 1 }
/** Holds if this is the first control-flow node in `subtree`. */
predicate isFirstNodeOf(AstNode subtree) { CFG::firstNode(subtree, this) }
predicate isFirstNodeOf(AstNode subtree) {
this.isBefore(subtree)
or
this.injects(subtree)
}
/** Holds if this node is the (unique) entry node of a function or file. */
predicate isEntryNode() { this instanceof MkEntryNode }
/** Holds if this node is the (unique) entry node of a function. */
predicate isEntryNode() { this instanceof GoCfg::ControlFlow::EntryNode }
/** Holds if this node is the (unique) exit node of a function or file. */
predicate isExitNode() { this instanceof MkExitNode }
/** Gets the basic block to which this node belongs. */
BasicBlock getBasicBlock() { result.getANode() = this }
/** Holds if this node is the (unique) exit node of a function. */
predicate isExitNode() { this instanceof GoCfg::ControlFlow::ExitNode }
/** Holds if this node dominates `dominee` in the control-flow graph. */
overlay[caller?]
pragma[inline]
predicate dominatesNode(ControlFlow::Node dominee) {
exists(ReachableBasicBlock thisbb, ReachableBasicBlock dbb, int i, int j |
exists(GoCfg::Cfg::BasicBlock thisbb, GoCfg::Cfg::BasicBlock dbb, int i, int j |
this = thisbb.getNode(i) and dominee = dbb.getNode(j)
|
thisbb.strictlyDominates(dbb)
@@ -76,20 +75,12 @@ module ControlFlow {
)
}
/** Gets the innermost function or file to which this node belongs. */
Root getRoot() { none() }
/** Gets the innermost function to which this node belongs. */
Root getRoot() { result = this.getEnclosingCallable() }
/** Gets the file to which this node belongs. */
File getFile() { result = this.getLocation().getFile() }
/**
* Gets a textual representation of this control flow node.
*/
string toString() { result = "control-flow node" }
/** Gets the source location for this element. */
Location getLocation() { none() }
/**
* DEPRECATED: Use `getLocation()` instead.
*
@@ -113,6 +104,22 @@ module ControlFlow {
}
}
/** A synthetic entry node for a function. */
class EntryNode extends Node instanceof GoCfg::ControlFlow::EntryNode { }
/** A synthetic exit node for a function. */
class ExitNode extends Node instanceof GoCfg::ControlFlow::ExitNode { }
private predicate isBranchConditionRoot(Expr expr) {
expr = any(LogicalBinaryExpr lbe).getLeftOperand()
or
expr = any(ForStmt fs).getCond()
or
expr = any(IfStmt is).getCond()
or
expr = any(ExpressionSwitchStmt ess | not exists(ess.getExpr())).getACase().getAnExpr()
}
/**
* A control-flow node that initializes or updates the value of a constant, a variable,
* a field, or an (array, slice, or map) element.
@@ -172,7 +179,7 @@ module ControlFlow {
exists(IR::FieldTarget trg | trg = super.getLhs() |
(
trg.getBase() = base or
trg.getBase() = MkImplicitDeref(base.(IR::EvalInstruction).getExpr())
trg.getBase() = IR::implicitDerefInstruction(base.(IR::EvalInstruction).getExpr())
) and
trg.getField() = f and
super.getRhs() = rhs
@@ -220,7 +227,7 @@ module ControlFlow {
exists(IR::ElementTarget trg | trg = super.getLhs() |
(
trg.getBase() = base or
trg.getBase() = MkImplicitDeref(base.(IR::EvalInstruction).getExpr())
trg.getBase() = IR::implicitDerefInstruction(base.(IR::EvalInstruction).getExpr())
) and
trg.getIndex() = index and
super.getRhs() = rhs
@@ -250,11 +257,19 @@ module ControlFlow {
* A control-flow node recording the fact that a certain expression has a known
* Boolean value at this point in the program.
*/
class ConditionGuardNode extends IR::Instruction, MkConditionGuardNode {
class ConditionGuardNode extends IR::Instruction {
Expr cond;
boolean outcome;
ConditionGuardNode() { this = MkConditionGuardNode(cond, outcome) }
ConditionGuardNode() {
isBranchConditionRoot(cond) and
this.isAfterTrue(cond) and
outcome = true
or
isBranchConditionRoot(cond) and
this.isAfterFalse(cond) and
outcome = false
}
private predicate ensuresAux(Expr expr, boolean b) {
expr = cond and b = outcome
@@ -320,21 +335,17 @@ module ControlFlow {
boolean getOutcome() { result = outcome }
override Root getRoot() { result.isRootOf(cond) }
override string toString() { result = cond + " is " + outcome }
override Location getLocation() { result = cond.getLocation() }
}
/**
* Gets the entry node of function or file `root`.
* Gets the entry node of function `root`.
*/
Node entryNode(Root root) { result = MkEntryNode(root) }
EntryNode entryNode(Root root) { result.getEnclosingCallable() = root }
/**
* Gets the exit node of function or file `root`.
* Gets the exit node of function `root`.
*/
Node exitNode(Root root) { result = MkExitNode(root) }
ExitNode exitNode(Root root) { result.getEnclosingCallable() = root }
/**
* Holds if the function `f` may return without panicking, exiting the process, or looping forever.
@@ -342,20 +353,40 @@ module ControlFlow {
* This is defined conservatively, and so may also hold of a function that in fact
* cannot return normally, but never fails to hold of a function that can return normally.
*/
predicate mayReturnNormally(FuncDecl f) { CFG::mayReturnNormally(f.getBody()) }
predicate mayReturnNormally(FuncDecl f) {
exists(GoCfg::ControlFlow::NormalExitNode exit |
exit.getEnclosingCallable() = f and
exists(exit.getAPredecessor())
)
}
/**
* Holds if `pred` is the node for the case `testExpr` in an expression
* switch statement which is switching on `switchExpr`, and `succ` is the
* node to be executed next if the case test succeeds.
* Holds if `pred` is the node reached when a case of the expression switch
* statement switching on `switchExpr` matches, `testExpr` is one of that
* case's test expressions, and `succ` is the node to be executed next when
* the case matches.
*
* In the control-flow graph the individual case test expressions of a case
* clause all funnel into a single "matched" node for the clause, from which
* control transfers to the case body. Hence `pred` is that shared matched
* node, and the same `(pred, succ)` pair is reported once per test
* expression `testExpr` of the clause.
*/
predicate isSwitchCaseTestPassingEdge(
ControlFlow::Node pred, ControlFlow::Node succ, Expr switchExpr, Expr testExpr
) {
CFG::isSwitchCaseTestPassingEdge(pred, succ, switchExpr, testExpr)
exists(ExpressionSwitchStmt ess, CaseClause cc, int i |
ess.getExpr() = switchExpr and
cc = ess.getACase() and
testExpr = cc.getExpr(i) and
pred.isAfter(cc) and
succ.isFirstNodeOf(cc.getStmt(0))
)
}
}
class ControlFlowNode = ControlFlow::Node;
class CfgScope = GoCfg::CfgScope;
class Write = ControlFlow::WriteNode;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -200,7 +200,7 @@ private ControlFlow::Node mostRecentSideEffect(ControlFlow::Node entry, ControlF
cached
private ControlFlow::Node mostRecentSideEffectUnique(ControlFlow::Node node) {
result = unique( | | mostRecentSideEffect(_, node))
result = unique( | | mostRecentSideEffect(getControlFlowEntry(node), node))
}
/** Used to represent the "global value number" of an expression. */

View File

@@ -9,6 +9,7 @@ module;
import go
private import codeql.ssa.Ssa as SsaImplCommon
private import semmle.go.controlflow.BasicBlocks as BasicBlocks
private import semmle.go.controlflow.ControlFlowGraphShared
private class BasicBlock = BasicBlocks::BasicBlock;
@@ -38,7 +39,7 @@ private module Internal {
/** Holds if the `i`th node of `bb` in function `f` is an entry node. */
private predicate entryNode(FuncDef f, BasicBlock bb, int i) {
f = bb.getScope() and
bb.getNode(i).isEntryNode()
bb.getNode(i).(ControlFlow::Node).isEntryNode()
}
/**
@@ -110,7 +111,7 @@ private module Internal {
v.isCaptured() and
exists(FuncDef f |
f = bb.getScope() and
bb.getLastNode().isExitNode() and
bb.getLastNode().(ControlFlow::Node).isExitNode() and
i = bb.length() - 1 and
certain = false
|
@@ -126,7 +127,7 @@ private module Internal {
}
import Internal
import SsaImplCommon::Make<Location, BasicBlocks::Cfg, SsaInput> as Impl
import SsaImplCommon::Make<Location, GoCfg::Cfg, SsaInput> as Impl
final class Definition = Impl::Definition;

View File

@@ -341,10 +341,22 @@ private ControlFlow::Node getANonTestPassingPredecessor(
) {
isPossibleInputNode(inputNode, succ.getRoot()) and
result = succ.getAPredecessor() and
not exists(Expr testExpr, DataFlow::Node switchExprNode |
not exists(DataFlow::Node switchExprNode |
flowsToSwitchExpression(inputNode, switchExprNode) and
ControlFlow::isSwitchCaseTestPassingEdge(result, succ, switchExprNode.asExpr(), testExpr) and
testExpr.isConst()
// The case body is reachable only by matching a constant: at least one of
// the case's test expressions is constant, and none of them is
// non-constant. (All test expressions of a case share the same matched
// edge `result -> succ`, so a case mixing constant and non-constant tests
// must not be treated as a constant-only match.)
exists(Expr testExpr |
ControlFlow::isSwitchCaseTestPassingEdge(result, succ, switchExprNode.asExpr(), testExpr) and
testExpr.isConst()
) and
not exists(Expr nonConstTestExpr |
ControlFlow::isSwitchCaseTestPassingEdge(result, succ, switchExprNode.asExpr(),
nonConstTestExpr) and
not nonConstTestExpr.isConst()
)
)
}

View File

@@ -59,7 +59,7 @@ module Glog {
/** Holds if this function takes a format string. */
predicate formatter() { format = "f" }
override predicate mayReturnNormally() { level != "Fatal" and level != "Exit" }
override predicate mustNotReturnNormally() { level = "Fatal" or level = "Exit" }
}
private class StringFormatter extends StringOps::Formatting::Range instanceof GlogFunction {

View File

@@ -29,8 +29,8 @@ module Logrus {
)
}
override predicate mayReturnNormally() {
not exists(string level, string suffix | level = ["Fatal", "Panic"] |
override predicate mustNotReturnNormally() {
exists(string level, string suffix | level = ["Fatal", "Panic"] |
this.getName() = level + suffix
)
}

View File

@@ -154,7 +154,7 @@ module Revel {
private IR::EvalInstruction skipImplicitFieldReads(IR::Instruction insn) {
result = insn or
result = skipImplicitFieldReads(insn.(IR::ImplicitFieldReadInstruction).getBase())
result = skipImplicitFieldReads(insn.(IR::ImplicitFieldReadInstruction).getBaseInstruction())
}
/** A call to `Controller.Render`. */

View File

@@ -54,7 +54,7 @@ module Zap {
this.hasQualifiedName(packagePath(), "SugaredLogger", "Fatal" + getSuffix())
}
override predicate mayReturnNormally() { none() }
override predicate mustNotReturnNormally() { any() }
}
/** A Zap logging function which always panics. */

View File

@@ -44,7 +44,7 @@ module Log {
)
}
override predicate mayReturnNormally() { none() }
override predicate mustNotReturnNormally() { any() }
}
/** A log function which must panic. */

View File

@@ -12,7 +12,7 @@ module Os {
private class Exit extends Function {
Exit() { this.hasQualifiedName("os", "Exit") }
override predicate mayReturnNormally() { none() }
override predicate mustNotReturnNormally() { any() }
}
// These models are not implemented using Models-as-Data because they represent reverse flow.

View File

@@ -1,9 +1,3 @@
## 1.6.5
### Minor Analysis Improvements
* The query `go/unhandled-writable-file-close` ("Writable file handle closed without error handling") now produces fewer false positives. A deferred call to `Close` that is preceded on every execution path by a handled call to `Sync` on the same file handle is no longer flagged.
## 1.6.4
No user-facing changes.

View File

@@ -14,11 +14,36 @@
import go
ControlFlow::Node nonGuardPredecessor(ControlFlow::Node nd) {
exists(ControlFlow::Node pred | pred = nd.getAPredecessor() |
if pred instanceof ControlFlow::ConditionGuardNode
then result = nonGuardPredecessor(pred)
else result = pred
/**
* Holds if `s` is reachable, that is, the control-flow graph contains a node for it.
*
* The shared control-flow library does not create control-flow nodes for dead code, so an
* unreachable statement has no first control-flow node.
*/
predicate isReachable(Stmt s) { exists(s.getFirstControlFlowNode()) }
/** Gets the statement immediately preceding `s` in a statement list, if any. */
Stmt getPreviousStmt(Stmt s) {
exists(BlockStmt b, int i | s = b.getStmt(i) and result = b.getStmt(i - 1))
or
exists(CaseClause c, int i | s = c.getStmt(i) and result = c.getStmt(i - 1))
or
exists(CommClause c, int i | s = c.getStmt(i) and result = c.getStmt(i - 1))
}
/**
* Holds if `s` is unreachable but the code that would precede it in the control-flow graph is
* reachable, so that `s` is the first unreachable statement in a run of dead code.
*/
predicate firstUnreachableStmt(Stmt s) {
not isReachable(s) and
not s instanceof EmptyStmt and
(
// a statement whose preceding statement in the same list is reachable
isReachable(getPreviousStmt(s))
or
// the post statement of a `for` loop whose body is entered
exists(ForStmt f | s = f.getPost() and isReachable(f.getBody().getAStmt()))
)
}
@@ -63,18 +88,13 @@ predicate allowlist(Stmt s) {
forall(Expr retval | retval = ret.getAnExpr() | isAllowedReturnValue(retval))
)
or
// statements in an `if false { ... }` and similar
exists(IfStmt is, ControlFlow::ConditionGuardNode iffalse, Expr cond, boolean b |
iffalse.getCondition() = is.getCond() and
iffalse = s.getFirstControlFlowNode().getAPredecessor() and
cond.getBoolValue() = b and
iffalse.ensures(DataFlow::exprNode(cond), b.booleanNot())
)
// statements deliberately made unreachable by a constant condition, such as the code
// following `if true { return }`
exists(getPreviousStmt(s).(IfStmt).getCond().getBoolValue())
}
from Stmt s, ControlFlow::Node fst
from Stmt s
where
fst = s.getFirstControlFlowNode() and
not exists(nonGuardPredecessor(fst)) and
firstUnreachableStmt(s) and
not allowlist(s)
select s, "This statement is unreachable."

View File

@@ -1,5 +1,4 @@
## 1.6.5
### Minor Analysis Improvements
---
category: minorAnalysis
---
* The query `go/unhandled-writable-file-close` ("Writable file handle closed without error handling") now produces fewer false positives. A deferred call to `Close` that is preceded on every execution path by a handled call to `Sync` on the same file handle is no longer flagged.

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