Compare commits

...

82 commits

Author SHA1 Message Date
pre-commit-ci[bot]
96ab752057 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/golangci/golangci-lint: v1.64.6 → v1.64.7](https://github.com/golangci/golangci-lint/compare/v1.64.6...v1.64.7)
2025-03-17 23:05:55 +01:00
pre-commit-ci[bot]
2cfdadfd85 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/golangci/golangci-lint: v1.64.5 → v1.64.6](https://github.com/golangci/golangci-lint/compare/v1.64.5...v1.64.6)
2025-03-03 23:53:01 +01:00
dependabot[bot]
5a001f2fbe build(deps): bump golang.org/x/tools from 0.29.0 to 0.30.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.29.0 to 0.30.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.29.0...v0.30.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-02 08:26:11 +01:00
pre-commit-ci[bot]
6f8b43553d [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/golangci/golangci-lint: v1.63.4 → v1.64.5](https://github.com/golangci/golangci-lint/compare/v1.63.4...v1.64.5)
2025-02-18 00:25:11 +01:00
dependabot[bot]
3947c6f8d5 build(deps): bump golang.org/x/tools from 0.28.0 to 0.29.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.28.0 to 0.29.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.28.0...v0.29.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-17 01:09:26 +01:00
Fernandez Ludovic
b568e8acf1 chore: groups github action updates 2025-01-17 00:57:15 +01:00
Fernandez Ludovic
ed135bb8e7 chore: use stable and olstable instead of explicit versions 2025-01-17 00:57:15 +01:00
Fernandez Ludovic
12e5409dea tests: add tests on suggested fixes 2025-01-17 00:54:35 +01:00
Fernandez Ludovic
2046ce80db fix: cgo 2025-01-17 00:46:58 +01:00
Fernandez Ludovic
7b0afb1f92 tests: rewrite tests 2025-01-17 00:46:58 +01:00
f887074f5d refactor: nested context recursion 2025-01-17 00:02:58 +01:00
ef9d47d1f0 feat: better discriminate assignations to struct pointers 2025-01-17 00:02:58 +01:00
939d65bc16 fix(goreleaser): draft release 2025-01-14 17:33:18 +01:00
54e593c1c6 feat: ignore context.TODO and context.Background
Related to 
2025-01-14 17:05:35 +01:00
529e088561 fix: goreleaser v2 2025-01-13 22:58:40 +01:00
pre-commit-ci[bot]
52f7fb588c [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/golangci/golangci-lint: v1.62.2 → v1.63.4](https://github.com/golangci/golangci-lint/compare/v1.62.2...v1.63.4)
2025-01-07 07:49:41 +01:00
dependabot[bot]
6130ad946e build(deps): bump golang.org/x/tools from 0.27.0 to 0.28.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.27.0 to 0.28.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.27.0...v0.28.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-01 22:41:48 +01:00
dependabot[bot]
48ee2433d6 build(deps): bump golang.org/x/tools from 0.26.0 to 0.27.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.26.0 to 0.27.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.26.0...v0.27.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-01 23:53:16 +01:00
pre-commit-ci[bot]
4c828f7302 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/golangci/golangci-lint: v1.62.0 → v1.62.2](https://github.com/golangci/golangci-lint/compare/v1.62.0...v1.62.2)
2024-11-26 16:37:34 +01:00
Oleksandr Redko
632a706303 refactor: avoid one string to []byte conversion 2024-11-20 15:58:58 +01:00
Oleksandr Redko
98578576b8 chore: format with goimports 2024-11-20 15:56:39 +01:00
pre-commit-ci[bot]
4ad817c8f3 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/golangci/golangci-lint: v1.61.0 → v1.62.0](https://github.com/golangci/golangci-lint/compare/v1.61.0...v1.62.0)
2024-11-12 07:25:20 +01:00
dependabot[bot]
fae7a27f40 build(deps): bump golang.org/x/tools from 0.25.0 to 0.26.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.25.0 to 0.26.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.25.0...v0.26.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-02 09:32:26 +01:00
Gabriel Augendre
fbf73fbd4d
update badge link to wiki 2024-10-30 16:37:48 +01:00
Gabriel Augendre
ed98e56f00
prevent concurrent coverage reports 2024-10-30 16:35:52 +01:00
Gabriel Augendre
7f2b12beab
add coverage badge 2024-10-30 16:29:06 +01:00
Gabriel Augendre
9b1b0c8986
remove unused contrib folder 2024-10-30 16:28:56 +01:00
Gabriel Augendre
1c05d23bb3
fix condition on coverage report job 2024-10-30 16:23:45 +01:00
Gabriel Augendre
e1e94fa7d4
use separate job with specific permissions for coverage report 2024-10-30 16:23:02 +01:00
pre-commit-ci[bot]
db40be2dee [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2024-10-30 16:17:27 +01:00
Gabriel Augendre
0c76b071d6 add coverage report 2024-10-30 16:17:27 +01:00
Gabriel Augendre
cddb074802
add badges in readme 2024-10-30 15:35:31 +01:00
pre-commit-ci[bot]
548d1beeac [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0)
2024-10-07 23:43:57 -04:00
dependabot[bot]
7e1e279ad5 build(deps): bump golang.org/x/tools from 0.24.0 to 0.25.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.24.0 to 0.25.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-02 13:53:46 +02:00
pre-commit-ci[bot]
88173f8b3f [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/golangci/golangci-lint: v1.60.3 → v1.61.0](https://github.com/golangci/golangci-lint/compare/v1.60.3...v1.61.0)
2024-09-10 07:13:57 +02:00
dependabot[bot]
4dc5fc8817 build(deps): bump golang.org/x/tools from 0.23.0 to 0.24.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.23.0 to 0.24.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.23.0...v0.24.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-02 00:16:13 +02:00
Fernandez Ludovic
25eb4fa74b chore: ignore binary 2024-09-01 13:40:56 +02:00
Fernandez Ludovic
4526c31d1d fix: use mon go1.22.0 2024-09-01 13:40:56 +02:00
pre-commit-ci[bot]
8782199988 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/golangci/golangci-lint: v1.60.1 → v1.60.3](https://github.com/golangci/golangci-lint/compare/v1.60.1...v1.60.3)
2024-08-27 08:09:22 +02:00
Gabriel Augendre
4410b65005
doc: update linter description 2024-08-26 10:30:12 +02:00
90afb8d3fa update readme 2024-08-26 07:47:30 +02:00
021f1b4ed6 cd: use go1.23 for golangci-lint 2024-08-26 07:46:50 +02:00
Venkatesh Kotwade
be0aa70f23
Detect nested contexts in function literals ()
* feat: Add detection for nested contexts in function literals
* feat: Improve detection of nested contexts in function literals
* refactor: Update getReportMessage function to handle unsupported nested context types
* use node instead of block
* refactor: use multi case
* added one more case
* feat: also added support for multiple contexts
2024-08-26 07:46:02 +02:00
0d2c4019d4 update go.mod to 1.22 2024-08-25 18:15:22 +02:00
77afd24616 cd: always run all test jobs 2024-08-25 16:00:22 +02:00
2e1ec44b79 drop support for go 1.21, and test against released go 1.23 2024-08-25 15:59:59 +02:00
0be9888cea
Merge pull request from Crocmagnon/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2024-08-20 08:05:44 +02:00
pre-commit-ci[bot]
ab55c271f9
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/golangci/golangci-lint: v1.59.1 → v1.60.1](https://github.com/golangci/golangci-lint/compare/v1.59.1...v1.60.1)
2024-08-19 22:52:08 +00:00
46a445090d
Merge pull request from Crocmagnon/dependabot/go_modules/golang.org/x/tools-0.23.0
build(deps): bump golang.org/x/tools from 0.22.0 to 0.23.0
2024-08-02 01:31:47 +02:00
dependabot[bot]
3df83da91f
build(deps): bump golang.org/x/tools from 0.22.0 to 0.23.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.22.0 to 0.23.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-01 21:38:51 +00:00
a80e8ddef6 cd: test with 1.23rc2 2024-07-23 19:13:11 +02:00
30606c7931
Merge pull request from Crocmagnon/feat/context-condition
handle contexts inside conditions
2024-07-23 19:07:45 +02:00
Gabriel Augendre
091030580e feat: detect in nested blocks 2024-07-23 19:07:34 +02:00
f35e8a2263
Merge pull request from Crocmagnon/go1.23
cd: test with go 1.23rc1
2024-07-02 00:32:37 +02:00
40f87bfc57 cd: test with go 1.23rc1 2024-07-02 00:30:31 +02:00
bf0cfec130
Merge pull request from Crocmagnon/dependabot/go_modules/golang.org/x/tools-0.22.0
build(deps): bump golang.org/x/tools from 0.21.0 to 0.22.0
2024-07-02 00:23:55 +02:00
c6325891c1
Merge pull request from Crocmagnon/dependabot/github_actions/goreleaser/goreleaser-action-6
build(deps): bump goreleaser/goreleaser-action from 5 to 6
2024-07-02 00:23:39 +02:00
dependabot[bot]
9a9e950a91
build(deps): bump goreleaser/goreleaser-action from 5 to 6
Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 5 to 6.
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 21:47:37 +00:00
dependabot[bot]
602eb047a9
build(deps): bump golang.org/x/tools from 0.21.0 to 0.22.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.21.0 to 0.22.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.21.0...v0.22.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 21:23:29 +00:00
a2ca606f30
Merge pull request from Crocmagnon/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2024-06-11 07:51:32 +02:00
pre-commit-ci[bot]
802ac1311c
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/golangci/golangci-lint: v1.59.0 → v1.59.1](https://github.com/golangci/golangci-lint/compare/v1.59.0...v1.59.1)
2024-06-10 22:55:26 +00:00
e8053df661
Merge pull request from MichaelUrman/contained
Test and fix cases with nested contexts
2024-05-29 17:29:50 +02:00
Michael Urman
69e9ae12fc Explain getRootIdent/ObjectOf/Pos check 2024-05-29 10:26:10 -05:00
Michael Urman
0e13d068ad Fix array test
The prior construct probably shouldn't issue an error.
These, however, both should (and do).
2024-05-29 10:07:03 -05:00
Michael Urman
3e9e29f41c Test and fix cases with nested contexts
As long as the context is rooted in a non-pointer value that has a new
copy in the loop, it is as safe to copy that value as it is to copy the
context. So only report such cases when they are indirected and thus
shared.
2024-05-29 09:47:58 -05:00
6be4ab74b8
Merge pull request from Crocmagnon/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2024-05-28 07:23:56 +02:00
pre-commit-ci[bot]
db036b18d4
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/golangci/golangci-lint: v1.58.2 → v1.59.0](https://github.com/golangci/golangci-lint/compare/v1.58.2...v1.59.0)
2024-05-27 22:24:08 +00:00
9beb365772
Merge pull request from Crocmagnon/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2024-05-21 08:56:02 +02:00
pre-commit-ci[bot]
954c9b64c6
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/golangci/golangci-lint: v1.58.1 → v1.58.2](https://github.com/golangci/golangci-lint/compare/v1.58.1...v1.58.2)
2024-05-20 21:54:23 +00:00
acb08d4731
Merge pull request from Crocmagnon/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2024-05-14 07:20:53 +02:00
pre-commit-ci[bot]
66526fe956
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/golangci/golangci-lint: v1.58.0 → v1.58.1](https://github.com/golangci/golangci-lint/compare/v1.58.0...v1.58.1)
2024-05-13 22:24:40 +00:00
d5171e1d54
Merge pull request from Crocmagnon/dependabot/go_modules/golang.org/x/tools-0.21.0
build(deps): bump golang.org/x/tools from 0.19.0 to 0.21.0
2024-05-07 10:53:48 +02:00
dependabot[bot]
de95617005
build(deps): bump golang.org/x/tools from 0.19.0 to 0.21.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.19.0 to 0.21.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.19.0...v0.21.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-07 08:50:53 +00:00
e7f3a75701
add dependabot config 2024-05-07 10:50:26 +02:00
ec963af14f
Merge pull request from Crocmagnon/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2024-05-07 10:50:13 +02:00
da706a0193 update golangci-lint action 2024-05-07 10:45:21 +02:00
pre-commit-ci[bot]
0b0c70a0a6
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/golangci/golangci-lint: v1.57.2 → v1.58.0](https://github.com/golangci/golangci-lint/compare/v1.57.2...v1.58.0)
2024-05-06 22:31:22 +00:00
6a0a70a6a3
Merge pull request from Crocmagnon/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2024-04-09 07:21:40 +02:00
pre-commit-ci[bot]
b6bd36f685
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0)
2024-04-08 23:04:56 +00:00
5cfbe16927
Update README.md 2024-04-06 09:09:43 +02:00
4c2ad68b8d
Merge pull request from Crocmagnon/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2024-04-02 08:08:43 +02:00
pre-commit-ci[bot]
a0fa7dbcdc
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v2.3.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v2.3.0...v4.5.0)
- [github.com/golangci/golangci-lint: v1.57.1 → v1.57.2](https://github.com/golangci/golangci-lint/compare/v1.57.1...v1.57.2)
2024-04-01 22:39:17 +00:00
20 changed files with 962 additions and 123 deletions

16
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,16 @@
# Doc: https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
groups:
github-actions:
patterns:
- "*" # Group all updates into a single larger pull request.
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "monthly"

View file

@ -14,8 +14,9 @@ permissions:
jobs:
build:
strategy:
fail-fast: false
matrix:
go: ['1.21', '1.22']
go: [stable, oldstable]
os: [macos-latest, windows-latest, ubuntu-latest]
name: build
runs-on: ${{ matrix.os }}
@ -28,3 +29,21 @@ jobs:
run: go build -v ./...
- name: Test
run: make test
coverage:
name: coverage
permissions:
contents: write
concurrency:
group: coverage
runs-on: ubuntu-latest
needs: [build]
steps:
- name: Update coverage report
uses: ncruces/go-coverage-report@v0
with:
report: true
chart: true
amend: true
if: |
github.event_name == 'push'
continue-on-error: true

View file

@ -16,7 +16,7 @@ jobs:
golangci:
strategy:
matrix:
go: ['1.21', '1.22']
go: [stable, oldstable]
name: lint
runs-on: ubuntu-latest
steps:
@ -25,6 +25,6 @@ jobs:
with:
go-version: ${{ matrix.go }}
- name: golangci-lint
uses: golangci/golangci-lint-action@v4
uses: golangci/golangci-lint-action@v6
with:
version: latest

View file

@ -17,8 +17,10 @@ jobs:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: stable
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: latest

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
.idea
/fatcontext
dist/

View file

@ -1,3 +1,9 @@
issues:
exclude-dirs:
- contrib
linters:
enable:
- goimports
linters-settings:
goimports:
local-prefixes: "github.com/Crocmagnon/fatcontext"

View file

@ -1,12 +1,6 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
# The lines below are called `modelines`. See `:help modeline`
# Feel free to remove those if you don't want/need to use them.
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
version: 1
version: 2
force_token: github
@ -45,3 +39,6 @@ changelog:
exclude:
- "^docs:"
- "^test:"
release:
draft: true

View file

@ -3,13 +3,13 @@ ci:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
rev: v5.0.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/golangci/golangci-lint
rev: v1.57.1
rev: v1.64.7
hooks:
- id: golangci-lint-full
- repo: local

View file

@ -1,8 +1,21 @@
# fatcontext
`fatcontext` is a Go linter which detects potential fat contexts in loops.
[![Go Reference](https://pkg.go.dev/badge/github.com/Crocmagnon/fatcontext.svg)](https://pkg.go.dev/github.com/Crocmagnon/fatcontext)
[![Go Report Card](https://goreportcard.com/badge/github.com/Crocmagnon/fatcontext)](https://goreportcard.com/report/github.com/Crocmagnon/fatcontext)
[![Go Coverage](https://github.com/Crocmagnon/fatcontext/wiki/coverage.svg)](https://github.com/Crocmagnon/fatcontext/wiki/Coverage)
`fatcontext` is a Go linter which detects potential fat contexts in loops or function literals.
They can lead to performance issues, as documented here: https://gabnotes.org/fat-contexts/
## Installation / usage
`fatcontext` is available in `golangci-lint` since v1.58.0.
```bash
go install github.com/Crocmagnon/fatcontext/cmd/fatcontext@latest
fatcontext ./...
```
## Example
```go

View file

@ -1,10 +1,11 @@
package main
import (
"github.com/Crocmagnon/fatcontext/pkg/analyzer"
"golang.org/x/tools/go/analysis/singlechecker"
"github.com/Crocmagnon/fatcontext/pkg/analyzer"
)
func main() {
singlechecker.Main(analyzer.Analyzer)
singlechecker.Main(analyzer.NewAnalyzer())
}

View file

@ -1,21 +0,0 @@
package contrib
import "context"
func ok() {
ctx := context.Background()
for i := 0; i < 10; i++ {
ctx := context.WithValue(ctx, "key", i)
_ = ctx
}
}
func notOk() {
ctx := context.Background()
for i := 0; i < 10; i++ {
ctx = context.WithValue(ctx, "key", i) // "nested context in loop"
_ = ctx
}
}

9
go.mod
View file

@ -1,7 +1,10 @@
module github.com/Crocmagnon/fatcontext
go 1.21
go 1.22.0
require golang.org/x/tools v0.19.0
require golang.org/x/tools v0.30.0
require golang.org/x/mod v0.16.0 // indirect
require (
golang.org/x/mod v0.23.0 // indirect
golang.org/x/sync v0.11.0 // indirect
)

14
go.sum
View file

@ -1,6 +1,8 @@
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=

View file

@ -3,30 +3,57 @@ package analyzer
import (
"bytes"
"errors"
"flag"
"fmt"
"go/ast"
"go/printer"
"go/token"
"slices"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
var Analyzer = &analysis.Analyzer{
Name: "fatcontext",
Doc: "detects nested contexts in loops",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
const FlagCheckStructPointers = "check-struct-pointers"
func NewAnalyzer() *analysis.Analyzer {
r := &runner{}
flags := flag.NewFlagSet("fatcontext", flag.ExitOnError)
flags.BoolVar(&r.DetectInStructPointers, FlagCheckStructPointers, false,
"set to true to detect potential fat contexts in struct pointers")
return &analysis.Analyzer{
Name: "fatcontext",
Doc: "detects nested contexts in loops and function literals",
Run: r.run,
Flags: *flags,
Requires: []*analysis.Analyzer{inspect.Analyzer},
}
}
var errUnknown = errors.New("unknown node type")
func run(pass *analysis.Pass) (interface{}, error) {
const (
categoryInLoop = "nested context in loop"
categoryInFuncLit = "nested context in function literal"
categoryInStructPointer = "potential nested context in struct pointer"
categoryUnsupported = "unsupported nested context type"
)
type runner struct {
DetectInStructPointers bool
}
func (r *runner) run(pass *analysis.Pass) (interface{}, error) {
inspctr := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.ForStmt)(nil),
(*ast.RangeStmt)(nil),
(*ast.FuncLit)(nil),
(*ast.FuncDecl)(nil),
}
inspctr.Preorder(nodeFilter, func(node ast.Node) {
@ -35,79 +62,241 @@ func run(pass *analysis.Pass) (interface{}, error) {
return
}
for _, stmt := range body.List {
assignStmt, ok := stmt.(*ast.AssignStmt)
if !ok {
continue
}
t := pass.TypesInfo.TypeOf(assignStmt.Lhs[0])
if t == nil {
continue
}
if t.String() != "context.Context" {
continue
}
if assignStmt.Tok == token.DEFINE {
break
}
suggestedStmt := ast.AssignStmt{
Lhs: assignStmt.Lhs,
TokPos: assignStmt.TokPos,
Tok: token.DEFINE,
Rhs: assignStmt.Rhs,
}
suggested, err := render(pass.Fset, &suggestedStmt)
var fixes []analysis.SuggestedFix
if err == nil {
fixes = append(fixes, analysis.SuggestedFix{
Message: "replace `=` with `:=`",
TextEdits: []analysis.TextEdit{
{
Pos: assignStmt.Pos(),
End: assignStmt.End(),
NewText: []byte(suggested),
},
},
})
}
pass.Report(analysis.Diagnostic{
Pos: assignStmt.Pos(),
Message: "nested context in loop",
SuggestedFixes: fixes,
})
break
if body == nil {
return
}
assignStmt := findNestedContext(pass, node, body.List)
if assignStmt == nil {
return
}
category := getCategory(pass, node, assignStmt)
if r.shouldIgnoreReport(category) {
return
}
fixes := r.getSuggestedFixes(pass, assignStmt, category)
pass.Report(analysis.Diagnostic{
Pos: assignStmt.Pos(),
Message: category,
SuggestedFixes: fixes,
})
})
return nil, nil
}
func getBody(node ast.Node) (*ast.BlockStmt, error) {
forStmt, ok := node.(*ast.ForStmt)
if ok {
return forStmt.Body, nil
func (r *runner) shouldIgnoreReport(category string) bool {
return category == categoryInStructPointer && !r.DetectInStructPointers
}
func (r *runner) getSuggestedFixes(pass *analysis.Pass, assignStmt *ast.AssignStmt, category string) []analysis.SuggestedFix {
switch category {
case categoryInStructPointer, categoryUnsupported:
return nil
}
rangeStmt, ok := node.(*ast.RangeStmt)
if ok {
return rangeStmt.Body, nil
suggestedStmt := ast.AssignStmt{
Lhs: assignStmt.Lhs,
TokPos: assignStmt.TokPos,
Tok: token.DEFINE,
Rhs: assignStmt.Rhs,
}
suggested, err := render(pass.Fset, &suggestedStmt)
var fixes []analysis.SuggestedFix
if err == nil {
fixes = append(fixes, analysis.SuggestedFix{
Message: "replace `=` with `:=`",
TextEdits: []analysis.TextEdit{
{
Pos: assignStmt.Pos(),
End: assignStmt.End(),
NewText: suggested,
},
},
})
}
return fixes
}
func getCategory(pass *analysis.Pass, node ast.Node, assignStmt *ast.AssignStmt) string {
switch node.(type) {
case *ast.ForStmt, *ast.RangeStmt:
return categoryInLoop
}
if isPointer(pass, assignStmt.Lhs[0]) {
return categoryInStructPointer
}
switch node.(type) {
case *ast.FuncLit, *ast.FuncDecl:
return categoryInFuncLit
default:
return categoryUnsupported
}
}
func getBody(node ast.Node) (*ast.BlockStmt, error) {
switch typedNode := node.(type) {
case *ast.ForStmt:
return typedNode.Body, nil
case *ast.RangeStmt:
return typedNode.Body, nil
case *ast.FuncLit:
return typedNode.Body, nil
case *ast.FuncDecl:
return typedNode.Body, nil
}
return nil, errUnknown
}
func findNestedContext(pass *analysis.Pass, node ast.Node, stmts []ast.Stmt) *ast.AssignStmt {
for _, stmt := range stmts {
// Recurse if necessary
switch typedStmt := stmt.(type) {
case *ast.BlockStmt:
if found := findNestedContext(pass, node, typedStmt.List); found != nil {
return found
}
case *ast.IfStmt:
if found := findNestedContext(pass, node, typedStmt.Body.List); found != nil {
return found
}
case *ast.SwitchStmt:
if found := findNestedContext(pass, node, typedStmt.Body.List); found != nil {
return found
}
case *ast.CaseClause:
if found := findNestedContext(pass, node, typedStmt.Body); found != nil {
return found
}
case *ast.SelectStmt:
if found := findNestedContext(pass, node, typedStmt.Body.List); found != nil {
return found
}
case *ast.CommClause:
if found := findNestedContext(pass, node, typedStmt.Body); found != nil {
return found
}
}
// Actually check for nested context
assignStmt, ok := stmt.(*ast.AssignStmt)
if !ok {
continue
}
t := pass.TypesInfo.TypeOf(assignStmt.Lhs[0])
if t == nil {
continue
}
if t.String() != "context.Context" {
continue
}
if assignStmt.Tok == token.DEFINE {
continue
}
// Ignore [context.Background] & [context.TODO].
if isContextFunction(assignStmt.Rhs[0], "Background", "TODO") {
continue
}
if isPointer(pass, assignStmt.Lhs[0]) {
return assignStmt
}
// allow assignment to non-pointer children of values defined within the loop
if isWithinLoop(assignStmt.Lhs[0], node, pass) {
continue
}
return assignStmt
}
return nil
}
// render returns the pretty-print of the given node
func render(fset *token.FileSet, x interface{}) (string, error) {
func render(fset *token.FileSet, x interface{}) ([]byte, error) {
var buf bytes.Buffer
if err := printer.Fprint(&buf, fset, x); err != nil {
return "", fmt.Errorf("printing node: %w", err)
return nil, fmt.Errorf("printing node: %w", err)
}
return buf.String(), nil
return buf.Bytes(), nil
}
func isContextFunction(exp ast.Expr, fnName ...string) bool {
call, ok := exp.(*ast.CallExpr)
if !ok {
return false
}
selector, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return false
}
ident, ok := selector.X.(*ast.Ident)
if !ok {
return false
}
return ident.Name == "context" && slices.Contains(fnName, selector.Sel.Name)
}
func isWithinLoop(exp ast.Expr, node ast.Node, pass *analysis.Pass) bool {
lhs := getRootIdent(pass, exp)
if lhs == nil {
return false
}
obj := pass.TypesInfo.ObjectOf(lhs)
if obj == nil {
return false
}
scope := obj.Parent()
if scope == nil {
return false
}
return scope.Pos() >= node.Pos() && scope.End() <= node.End()
}
func getRootIdent(pass *analysis.Pass, node ast.Node) *ast.Ident {
for {
switch n := node.(type) {
case *ast.Ident:
return n
case *ast.IndexExpr:
node = n.X
case *ast.SelectorExpr:
if sel, ok := pass.TypesInfo.Selections[n]; ok && sel.Indirect() {
return nil // indirected (pointer) roots don't imply a (safe) copy
}
node = n.X
default:
return nil
}
}
}
func isPointer(pass *analysis.Pass, exp ast.Node) bool {
switch n := exp.(type) {
case *ast.SelectorExpr:
sel, ok := pass.TypesInfo.Selections[n]
return ok && sel.Indirect()
}
return false
}

View file

@ -1,19 +1,63 @@
package analyzer_test
import (
"github.com/Crocmagnon/fatcontext/pkg/analyzer"
"golang.org/x/tools/go/analysis/analysistest"
"os"
"path/filepath"
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"github.com/Crocmagnon/fatcontext/pkg/analyzer"
)
func TestAll(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get wd: %s", err)
func TestAnalyzer(t *testing.T) {
testCases := []struct {
desc string
dir string
options map[string]string
}{
{
desc: "no func decl",
dir: "common",
},
{
desc: "no func decl",
dir: "no_structpointer",
},
{
desc: "func decl",
dir: "common",
options: map[string]string{
analyzer.FlagCheckStructPointers: "true",
},
},
{
desc: "func decl",
dir: "structpointer",
options: map[string]string{
analyzer.FlagCheckStructPointers: "true",
},
},
}
testdata := filepath.Join(filepath.Dir(filepath.Dir(wd)), "testdata")
analysistest.Run(t, testdata, analyzer.Analyzer, "./...")
for _, test := range testCases {
t.Run(test.desc+"_"+test.dir, func(t *testing.T) {
t.Parallel()
a := analyzer.NewAnalyzer()
for k, v := range test.options {
err := a.Flags.Set(k, v)
if err != nil {
t.Fatal(err)
}
}
analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), a, test.dir)
})
}
}
func TestAnalyzer_cgo(t *testing.T) {
a := analyzer.NewAnalyzer()
analysistest.Run(t, analysistest.TestData(), a, "cgo")
}

View file

@ -1,8 +1,27 @@
package src
package cgo
import "context"
/*
#include <stdio.h>
#include <stdlib.h>
func example() {
void myprint(char* s) {
printf("%d\n", s);
}
*/
import "C"
import (
"context"
"unsafe"
)
func _() {
cs := C.CString("Hello from stdio\n")
C.myprint(cs)
C.free(unsafe.Pointer(cs))
}
func _() {
ctx := context.Background()
for i := 0; i < 10; i++ {

View file

@ -0,0 +1,251 @@
package common
import (
"context"
"testing"
)
func example() {
ctx := context.Background()
for i := 0; i < 10; i++ {
ctx := context.WithValue(ctx, "key", i)
ctx = context.WithValue(ctx, "other", "val")
}
for i := 0; i < 10; i++ {
ctx = context.WithValue(ctx, "key", i) // want "nested context in loop"
ctx = context.WithValue(ctx, "other", "val")
}
for item := range []string{"one", "two", "three"} {
ctx = wrapContext(ctx) // want "nested context in loop"
ctx := context.WithValue(ctx, "key", item)
ctx = wrapContext(ctx)
}
for {
ctx = wrapContext(ctx) // want "nested context in loop"
break
}
// not fooled by shadowing in nested blocks
for {
err := doSomething()
if err != nil {
ctx := wrapContext(ctx)
ctx = wrapContext(ctx)
}
switch err {
case nil:
ctx := wrapContext(ctx)
ctx = wrapContext(ctx)
default:
ctx := wrapContext(ctx)
ctx = wrapContext(ctx)
}
{
ctx := wrapContext(ctx)
ctx = wrapContext(ctx)
}
select {
case <-ctx.Done():
ctx := wrapContext(ctx)
ctx = wrapContext(ctx)
default:
}
ctx = wrapContext(ctx) // want "nested context in loop"
break
}
// detects contexts wrapped in function literals (this is risky as function literals can be called multiple times)
_ = func() {
ctx = wrapContext(ctx) // want "nested context in function literal"
}
// this is fine because the context is created in the loop
for {
if ctx := context.Background(); doSomething() != nil {
ctx = wrapContext(ctx)
}
}
for {
ctx2 := context.Background()
ctx = wrapContext(ctx) // want "nested context in loop"
if doSomething() != nil {
ctx2 = wrapContext(ctx2)
}
}
}
func wrapContext(ctx context.Context) context.Context {
return context.WithoutCancel(ctx)
}
func doSomething() error {
return nil
}
// storing contexts in a struct isn't recommended, but local copies of a non-pointer struct should act like local copies of a context.
func inStructs(ctx context.Context) {
for i := 0; i < 10; i++ {
c := struct{ Ctx context.Context }{ctx}
c.Ctx = context.WithValue(c.Ctx, "key", i)
c.Ctx = context.WithValue(c.Ctx, "other", "val")
}
for i := 0; i < 10; i++ {
c := []struct{ Ctx context.Context }{{ctx}}
c[0].Ctx = context.WithValue(c[0].Ctx, "key", i)
c[0].Ctx = context.WithValue(c[0].Ctx, "other", "val")
}
c := struct{ Ctx context.Context }{ctx}
for i := 0; i < 10; i++ {
c := c
c.Ctx = context.WithValue(c.Ctx, "key", i)
c.Ctx = context.WithValue(c.Ctx, "other", "val")
}
pc := &struct{ Ctx context.Context }{ctx}
for i := 0; i < 10; i++ {
c := pc
c.Ctx = context.WithValue(c.Ctx, "key", i) // want "nested context in loop"
c.Ctx = context.WithValue(c.Ctx, "other", "val")
}
r := []struct{ Ctx context.Context }{{ctx}}
for i := 0; i < 10; i++ {
r[0].Ctx = context.WithValue(r[0].Ctx, "key", i) // want "nested context in loop"
r[0].Ctx = context.WithValue(r[0].Ctx, "other", "val")
}
rp := []*struct{ Ctx context.Context }{{ctx}}
for i := 0; i < 10; i++ {
rp[0].Ctx = context.WithValue(rp[0].Ctx, "key", i) // want "nested context in loop"
rp[0].Ctx = context.WithValue(rp[0].Ctx, "other", "val")
}
}
func inVariousNestedBlocks(ctx context.Context) {
for {
err := doSomething()
if err != nil {
ctx = wrapContext(ctx) // want "nested context in loop"
}
break
}
for {
err := doSomething()
if err != nil {
if true {
ctx = wrapContext(ctx) // want "nested context in loop"
}
}
break
}
for {
err := doSomething()
switch err {
case nil:
ctx = wrapContext(ctx) // want "nested context in loop"
}
break
}
for {
err := doSomething()
switch err {
default:
ctx = wrapContext(ctx) // want "nested context in loop"
}
break
}
for {
ctx := wrapContext(ctx)
err := doSomething()
if err != nil {
ctx = wrapContext(ctx)
}
break
}
for {
{
ctx = wrapContext(ctx) // want "nested context in loop"
}
break
}
for {
select {
case <-ctx.Done():
ctx = wrapContext(ctx) // want "nested context in loop"
default:
}
break
}
}
// this middleware could run on every request, bloating the request parameter level context and causing a memory leak
func badMiddleware(ctx context.Context) func() error {
return func() error {
ctx = wrapContext(ctx) // want "nested context in function literal"
return doSomethingWithCtx(ctx)
}
}
// this middleware is fine, as it doesn't modify the context of parent function
func okMiddleware(ctx context.Context) func() error {
return func() error {
ctx := wrapContext(ctx)
return doSomethingWithCtx(ctx)
}
}
// this middleware is fine, as it only modifies the context passed to it
func okMiddleware2(ctx context.Context) func(ctx context.Context) error {
return func(ctx context.Context) error {
ctx = wrapContext(ctx)
return doSomethingWithCtx(ctx)
}
}
func doSomethingWithCtx(ctx context.Context) error {
return nil
}
func testCasesInit(t *testing.T) {
cases := []struct {
ctx context.Context
}{
{},
{
ctx: context.WithValue(context.Background(), "key", "value"),
},
}
for _, tc := range cases {
t.Run("some test", func(t *testing.T) {
if tc.ctx == nil {
tc.ctx = context.Background()
}
})
}
}

View file

@ -0,0 +1,251 @@
package common
import (
"context"
"testing"
)
func example() {
ctx := context.Background()
for i := 0; i < 10; i++ {
ctx := context.WithValue(ctx, "key", i)
ctx = context.WithValue(ctx, "other", "val")
}
for i := 0; i < 10; i++ {
ctx := context.WithValue(ctx, "key", i) // want "nested context in loop"
ctx = context.WithValue(ctx, "other", "val")
}
for item := range []string{"one", "two", "three"} {
ctx := wrapContext(ctx) // want "nested context in loop"
ctx := context.WithValue(ctx, "key", item)
ctx = wrapContext(ctx)
}
for {
ctx := wrapContext(ctx) // want "nested context in loop"
break
}
// not fooled by shadowing in nested blocks
for {
err := doSomething()
if err != nil {
ctx := wrapContext(ctx)
ctx = wrapContext(ctx)
}
switch err {
case nil:
ctx := wrapContext(ctx)
ctx = wrapContext(ctx)
default:
ctx := wrapContext(ctx)
ctx = wrapContext(ctx)
}
{
ctx := wrapContext(ctx)
ctx = wrapContext(ctx)
}
select {
case <-ctx.Done():
ctx := wrapContext(ctx)
ctx = wrapContext(ctx)
default:
}
ctx := wrapContext(ctx) // want "nested context in loop"
break
}
// detects contexts wrapped in function literals (this is risky as function literals can be called multiple times)
_ = func() {
ctx := wrapContext(ctx) // want "nested context in function literal"
}
// this is fine because the context is created in the loop
for {
if ctx := context.Background(); doSomething() != nil {
ctx = wrapContext(ctx)
}
}
for {
ctx2 := context.Background()
ctx := wrapContext(ctx) // want "nested context in loop"
if doSomething() != nil {
ctx2 = wrapContext(ctx2)
}
}
}
func wrapContext(ctx context.Context) context.Context {
return context.WithoutCancel(ctx)
}
func doSomething() error {
return nil
}
// storing contexts in a struct isn't recommended, but local copies of a non-pointer struct should act like local copies of a context.
func inStructs(ctx context.Context) {
for i := 0; i < 10; i++ {
c := struct{ Ctx context.Context }{ctx}
c.Ctx = context.WithValue(c.Ctx, "key", i)
c.Ctx = context.WithValue(c.Ctx, "other", "val")
}
for i := 0; i < 10; i++ {
c := []struct{ Ctx context.Context }{{ctx}}
c[0].Ctx = context.WithValue(c[0].Ctx, "key", i)
c[0].Ctx = context.WithValue(c[0].Ctx, "other", "val")
}
c := struct{ Ctx context.Context }{ctx}
for i := 0; i < 10; i++ {
c := c
c.Ctx = context.WithValue(c.Ctx, "key", i)
c.Ctx = context.WithValue(c.Ctx, "other", "val")
}
pc := &struct{ Ctx context.Context }{ctx}
for i := 0; i < 10; i++ {
c := pc
c.Ctx := context.WithValue(c.Ctx, "key", i) // want "nested context in loop"
c.Ctx = context.WithValue(c.Ctx, "other", "val")
}
r := []struct{ Ctx context.Context }{{ctx}}
for i := 0; i < 10; i++ {
r[0].Ctx := context.WithValue(r[0].Ctx, "key", i) // want "nested context in loop"
r[0].Ctx = context.WithValue(r[0].Ctx, "other", "val")
}
rp := []*struct{ Ctx context.Context }{{ctx}}
for i := 0; i < 10; i++ {
rp[0].Ctx := context.WithValue(rp[0].Ctx, "key", i) // want "nested context in loop"
rp[0].Ctx = context.WithValue(rp[0].Ctx, "other", "val")
}
}
func inVariousNestedBlocks(ctx context.Context) {
for {
err := doSomething()
if err != nil {
ctx := wrapContext(ctx) // want "nested context in loop"
}
break
}
for {
err := doSomething()
if err != nil {
if true {
ctx := wrapContext(ctx) // want "nested context in loop"
}
}
break
}
for {
err := doSomething()
switch err {
case nil:
ctx := wrapContext(ctx) // want "nested context in loop"
}
break
}
for {
err := doSomething()
switch err {
default:
ctx := wrapContext(ctx) // want "nested context in loop"
}
break
}
for {
ctx := wrapContext(ctx)
err := doSomething()
if err != nil {
ctx = wrapContext(ctx)
}
break
}
for {
{
ctx := wrapContext(ctx) // want "nested context in loop"
}
break
}
for {
select {
case <-ctx.Done():
ctx := wrapContext(ctx) // want "nested context in loop"
default:
}
break
}
}
// this middleware could run on every request, bloating the request parameter level context and causing a memory leak
func badMiddleware(ctx context.Context) func() error {
return func() error {
ctx := wrapContext(ctx) // want "nested context in function literal"
return doSomethingWithCtx(ctx)
}
}
// this middleware is fine, as it doesn't modify the context of parent function
func okMiddleware(ctx context.Context) func() error {
return func() error {
ctx := wrapContext(ctx)
return doSomethingWithCtx(ctx)
}
}
// this middleware is fine, as it only modifies the context passed to it
func okMiddleware2(ctx context.Context) func(ctx context.Context) error {
return func(ctx context.Context) error {
ctx = wrapContext(ctx)
return doSomethingWithCtx(ctx)
}
}
func doSomethingWithCtx(ctx context.Context) error {
return nil
}
func testCasesInit(t *testing.T) {
cases := []struct {
ctx context.Context
}{
{},
{
ctx: context.WithValue(context.Background(), "key", "value"),
},
}
for _, tc := range cases {
t.Run("some test", func(t *testing.T) {
if tc.ctx == nil {
tc.ctx = context.Background()
}
})
}
}

View file

@ -0,0 +1,23 @@
package common
import (
"context"
)
type Container struct {
Ctx context.Context
}
func something() func(*Container) {
return func(r *Container) {
ctx := r.Ctx
ctx = context.WithValue(ctx, "key", "val")
r.Ctx = ctx
}
}
func blah(r *Container) {
ctx := r.Ctx
ctx = context.WithValue(ctx, "key", "val")
r.Ctx = ctx
}

View file

@ -0,0 +1,23 @@
package common
import (
"context"
)
type Container struct {
Ctx context.Context
}
func something() func(*Container) {
return func(r *Container) {
ctx := r.Ctx
ctx = context.WithValue(ctx, "key", "val")
r.Ctx = ctx // want "potential nested context in struct pointer"
}
}
func blah(r *Container) {
ctx := r.Ctx
ctx = context.WithValue(ctx, "key", "val")
r.Ctx = ctx // want "potential nested context in struct pointer"
}