Compare commits

..

No commits in common. "a80e8ddef6b985bfbd9103c1812edd5f56fb57ac" and "f35e8a22630d17abd5e7d71588203218eb8bfcf2" have entirely different histories.

3 changed files with 54 additions and 217 deletions

View file

@ -15,7 +15,7 @@ jobs:
build: build:
strategy: strategy:
matrix: matrix:
go: ['1.21', '1.22', '1.23.0-rc.2'] go: ['1.21', '1.22', '1.23.0-rc.1']
os: [macos-latest, windows-latest, ubuntu-latest] os: [macos-latest, windows-latest, ubuntu-latest]
name: build name: build
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}

View file

@ -36,9 +36,32 @@ func run(pass *analysis.Pass) (interface{}, error) {
return return
} }
assignStmt := findNestedContext(pass, body, body.List) for _, stmt := range body.List {
if assignStmt == nil { assignStmt, ok := stmt.(*ast.AssignStmt)
return 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
}
// allow assignment to non-pointer children of values defined within the loop
if lhs := getRootIdent(pass, assignStmt.Lhs[0]); lhs != nil {
if obj := pass.TypesInfo.ObjectOf(lhs); obj != nil {
if obj.Pos() >= body.Pos() && obj.Pos() < body.End() {
continue // definition is within the loop
}
}
} }
suggestedStmt := ast.AssignStmt{ suggestedStmt := ast.AssignStmt{
@ -69,6 +92,8 @@ func run(pass *analysis.Pass) (interface{}, error) {
SuggestedFixes: fixes, SuggestedFixes: fixes,
}) })
break
}
}) })
return nil, nil return nil, nil
@ -88,85 +113,6 @@ func getBody(node ast.Node) (*ast.BlockStmt, error) {
return nil, errUnknown return nil, errUnknown
} }
func findNestedContext(pass *analysis.Pass, block *ast.BlockStmt, stmts []ast.Stmt) *ast.AssignStmt {
for _, stmt := range stmts {
// Recurse if necessary
if inner, ok := stmt.(*ast.BlockStmt); ok {
found := findNestedContext(pass, inner, inner.List)
if found != nil {
return found
}
}
if inner, ok := stmt.(*ast.IfStmt); ok {
found := findNestedContext(pass, inner.Body, inner.Body.List)
if found != nil {
return found
}
}
if inner, ok := stmt.(*ast.SwitchStmt); ok {
found := findNestedContext(pass, inner.Body, inner.Body.List)
if found != nil {
return found
}
}
if inner, ok := stmt.(*ast.CaseClause); ok {
found := findNestedContext(pass, block, inner.Body)
if found != nil {
return found
}
}
if inner, ok := stmt.(*ast.SelectStmt); ok {
found := findNestedContext(pass, inner.Body, inner.Body.List)
if found != nil {
return found
}
}
if inner, ok := stmt.(*ast.CommClause); ok {
found := findNestedContext(pass, block, inner.Body)
if 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 {
break
}
// allow assignment to non-pointer children of values defined within the loop
if lhs := getRootIdent(pass, assignStmt.Lhs[0]); lhs != nil {
if obj := pass.TypesInfo.ObjectOf(lhs); obj != nil {
if obj.Pos() >= block.Pos() && obj.Pos() < block.End() {
continue // definition is within the loop
}
}
}
return assignStmt
}
return nil
}
func getRootIdent(pass *analysis.Pass, node ast.Node) *ast.Ident { func getRootIdent(pass *analysis.Pass, node ast.Node) *ast.Ident {
for { for {
switch n := node.(type) { switch n := node.(type) {

View file

@ -25,50 +25,12 @@ func example() {
ctx = wrapContext(ctx) // want "nested context in loop" ctx = wrapContext(ctx) // want "nested context in loop"
break 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
}
} }
func wrapContext(ctx context.Context) context.Context { func wrapContext(ctx context.Context) context.Context {
return context.WithoutCancel(ctx) 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. // 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) { func inStructs(ctx context.Context) {
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
@ -109,74 +71,3 @@ func inStructs(ctx context.Context) {
rp[0].Ctx = context.WithValue(rp[0].Ctx, "other", "val") 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
}
}