Skip to content

ctxbackground: enclosing-scope walk only inspects *ast.FuncDecl, skipping FuncLit boundaries — false negative for closures with [Content truncated due to length] #41164

Description

@github-actions

Summary

ctxbackground flags context.Background() when the enclosing function already receives a context.Context parameter. To find that parameter it walks ancestors with cur.Enclosing((*ast.FuncDecl)(nil)), which matches only top-level function declarations and silently steps over any intervening *ast.FuncLit (closure). As a result the linter attributes a Background() call to the wrong function scope. This is the same scope-boundary class as #40947 (wgdonenotdeferred), but in a distinct linter and with the opposite root cause: instead of walking too far, it considers too few node kinds.

Location

pkg/linters/ctxbackground/ctxbackground.go:43-71:

for encl := range cur.Enclosing((*ast.FuncDecl)(nil)) {   // <-- FuncLit not in filter
    fn, ok := encl.Node().(*ast.FuncDecl)
    ...
    ctxParamName, ok := contextParamName(pass, fn)
    if !ok {
        break
    }
    pass.Report(...)
    break
}

contextParamName only accepts *ast.FuncDecl (pkg/linters/ctxbackground/ctxbackground.go:98), so a closure's own parameter list is never examined.

Impact

False negative — the primary, clean case. A Background() call inside a closure whose own signature receives a context is missed, because the walk jumps past the closure to the outer FuncDecl:

func register(mux *http.ServeMux) {            // no ctx param
    mux.HandleFunc("/", func(ctx context.Context) {
        _ = context.Background()               // NOT flagged: should be (this func receives ctx)
    })
}

The nearest enclosing function (the FuncLit) does receive ctx — exactly the linter's target — yet Enclosing((*ast.FuncDecl)(nil)) resolves to register, which has no ctx, so the loop breaks without reporting.

Secondary — suspect autofix in detached closures. The inverse shape flags the call and rewrites it to the outer captured ctx:

func serve(ctx context.Context) {
    go func() {
        _ = context.Background()               // flagged -> suggests replacing with `ctx`
    }()
}

Substituting the request-scoped ctx into a goroutine that deliberately detached from it can be semantically wrong (the goroutine may outlive the request and must not be cancelled with it). The linter applies the fix without considering the FuncLit boundary or whether the closure even intends to capture that ctx.

Why the test gap hid it

testdata/src/ctxbackground/ctxbackground.go covers free functions, methods, blank _ params, init, and shadowing — but contains no FuncLit/closure cases at all. The boundary behavior is entirely untested.

Recommendation

Resolve the nearest enclosing function scope, FuncDecl or FuncLit, and inspect that node's own parameter list. Generalize contextParamName to take an *ast.FuncType (both FuncDecl.Type and FuncLit.Type are *ast.FuncType):

for encl := range cur.Enclosing((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) {
    var ftype *ast.FuncType
    switch fn := encl.Node().(type) {
    case *ast.FuncDecl:
        ftype = fn.Type
    case *ast.FuncLit:
        ftype = fn.Type
    }
    ctxParamName, ok := contextParamName(pass, ftype)
    if !ok {
        break   // nearest function scope has no ctx -> stop
    }
    pass.Report(...)
    break
}

Stopping at the nearest function scope (rather than continuing outward) keeps the diagnostic anchored to a parameter the call site can actually reference, and naturally excludes detached closures that take no ctx of their own.

Validation checklist

  • Add flagged testdata: Background() inside a func(ctx context.Context){...} closure nested in a no-ctx outer function.
  • Add not flagged testdata: Background() inside a no-param closure nested in a function that does receive ctx (confirm the chosen policy and document it).
  • Keep existing free-function/method/init/shadow cases green.

Effort: small–medium (single-file; widen the Enclosing filter + change one helper signature + testdata).

References: §28076275497

Generated by 🤖 Sergo - Serena Go Expert · 302.6 AIC · ⌖ 11.3 AIC · ⊞ 5.9K ·

  • expires on Jun 30, 2026, 9:12 PM UTC-08:00

Metadata

Metadata

Labels

cookieIssue Monster Loves Cookies!sergo

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions