Problem
httpnoctx.hasContextInEnclosingFunc (pkg/linters/httpnoctx/httpnoctx.go:146-158) walks every lexically enclosing *ast.FuncDecl/*ast.FuncLit looking for a context.Context parameter, and stops at the first one it finds — without ever checking whether it has crossed a plain (non-go/defer) closure boundary on the way:
func hasContextInEnclosingFunc(pass *analysis.Pass, cursor inspector.Cursor) bool {
for enclosing := range cursor.Enclosing((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) {
fnType := astutil.EnclosingFuncType(enclosing.Node())
if fnType == nil || fnType.Params == nil {
continue
}
if _, ok := astutil.ContextParamName(pass, fnType); ok {
return true
}
}
return false
}
This is the exact walk shape (cursor.Enclosing((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil))) that every other scope-sensitive linter in this repo has already had audited and fixed for FuncLit-boundary crossing:
httpnoctx is the one CI-enforced (cgo.yml LINTER_FLAGS) member of this family that never received the corresponding fix — IsGoOrDeferClosure is not referenced anywhere in httpnoctx.go.
Impact
Because the loop never breaks at a plain closure boundary, httpnoctx will attribute an unrelated outer function's context.Context parameter to code inside a synchronous callback closure, e.g.:
func Serve(ctx context.Context) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
req, _ := http.NewRequest(http.MethodGet, upstreamURL, nil) // flagged
})
}
hasContextInEnclosingFunc finds Serve's ctx parameter (the http.HandlerFunc literal itself has no context param, and the walk doesn't stop there) and fires "http.NewRequest does not propagate context; use http.NewRequestWithContext when context.Context is in scope." The suggested remediation is actively wrong: the correct per-request context here is r.Context(), not the handler-construction-time ctx captured from Serve, which may already be long gone by the time the handler executes. This is the same false-positive/misleading-fix class documented for execcommandwithoutcontext in #43683, just never patched here.
There is no current production trigger (no http.NewRequest/http.Get/http.Post call sites under pkg/ outside httpnoctx's own testdata), so this is latent rather than actively firing — matching the precedent of similarly-filed latent bugs (e.g. #44187 writebytestring, filed and fixed before it had a production hit).
Evidence
pkg/linters/httpnoctx/httpnoctx.go:146-158 — the unguarded walk.
pkg/linters/httpnoctx/testdata/src/httpnoctx/httpnoctx.go — the entire test fixture has zero FuncLit/closure test cases (no goroutine, no defer, no plain closure), so the gap has no test coverage in either direction.
pkg/linters/execcommandwithoutcontext/execcommandwithoutcontext.go:68-79 and pkg/linters/timesleepnocontext/timesleepnocontext.go:59-70 — sibling linters with the identical walk shape, already carrying the astutil.IsGoOrDeferClosure guard.
Recommendation
Add the same boundary check used by execcommandwithoutcontext/timesleepnocontext: when the current enclosing node is a *ast.FuncLit with no context param, and it is not the direct callee of a go/defer statement (!astutil.IsGoOrDeferClosure(enclosing)), stop the walk instead of continuing to the next-outer scope.
Before:
for enclosing := range cursor.Enclosing((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) {
fnType := astutil.EnclosingFuncType(enclosing.Node())
if fnType == nil || fnType.Params == nil {
continue
}
if _, ok := astutil.ContextParamName(pass, fnType); ok {
return true
}
}
return false
After (mirroring execcommandwithoutcontext.go:68-79):
for enclosing := range cursor.Enclosing((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) {
fnType := astutil.EnclosingFuncType(enclosing.Node())
if fnType == nil {
continue
}
if _, ok := astutil.ContextParamName(pass, fnType); ok {
return true
}
if _, isFuncLit := enclosing.Node().(*ast.FuncLit); isFuncLit && !astutil.IsGoOrDeferClosure(enclosing) {
return false
}
}
return false
Validation checklist
Effort
Small — the fix is a ~4-line change mirroring an already-landed pattern (execcommandwithoutcontext.go:76-78), plus two new testdata fixtures.
Generated by 🤖 Sergo - Serena Go Expert · agent · 264.1 AIC · ⌖ 30.1 AIC · ⊞ 5.9K · ◷
Problem
httpnoctx.hasContextInEnclosingFunc(pkg/linters/httpnoctx/httpnoctx.go:146-158) walks every lexically enclosing*ast.FuncDecl/*ast.FuncLitlooking for acontext.Contextparameter, and stops at the first one it finds — without ever checking whether it has crossed a plain (non-go/defer) closure boundary on the way:This is the exact walk shape (
cursor.Enclosing((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil))) that every other scope-sensitive linter in this repo has already had audited and fixed for FuncLit-boundary crossing:execcommandwithoutcontext— fixed in fix(execcommandwithoutcontext): stop enclosing-scope walk at non-go/defer FuncLit boundaries #43692/execcommandwithoutcontext: enclosing-scope walk crosses non-go/defer FuncLit boundaries — false positives + misleading fix (same [Content truncated due to length] #43683 by breaking when a non-go/deferFuncLithas no context param (seeexeccommandwithoutcontext.go:76-78, guarded byastutil.IsGoOrDeferClosure)timesleepnocontext— same fix,timesleepnocontext.go:66-69ctxbackground— fixed in ctxbackground: enclosing-scope walk only inspects *ast.FuncDecl, skipping FuncLit boundaries — false negative for closures with [Content truncated due to length] #41164/ctxbackground: fix false negative and unsafe autofix for closures #41187 (stops the walk on the first non-context scope instead of tunneling through it)panicinlibrarycode,wgdonenotdeferred,httprespbodyclose,seenmapbool— all received equivalent FuncLit-scope-boundary fixes (panicinlibrarycode: enforce FuncLit boundaries for init/doc panic exemptions #41631/panicinlibrarycode: init() and documented-contract exemptions cross FuncLit boundaries — false negatives for panics in nested cl [Content truncated due to length] #41606, Fixwgdonenotdeferredloop-scope handling across goroutine closures #41026/wgdonenotdeferred precision: non-deferred wg.Done() inside a goroutine launched in a loop escapes detection (FuncLit scope bound [Content truncated due to length] #40947, httprespbodyclose: FuncDecl-only scope + FuncLit skip misses responses closed inside closures (goroutines/handlers) — false nega [Content truncated due to length] #43465, seenmapbool: duplicate diagnostics for set-maps declared inside function literals (double AST traversal) #40733/fix(seenmapbool): eliminate duplicate diagnostics for set-maps in closures #40741)httpnoctxis the one CI-enforced (cgo.ymlLINTER_FLAGS) member of this family that never received the corresponding fix —IsGoOrDeferClosureis not referenced anywhere inhttpnoctx.go.Impact
Because the loop never breaks at a plain closure boundary,
httpnoctxwill attribute an unrelated outer function'scontext.Contextparameter to code inside a synchronous callback closure, e.g.:hasContextInEnclosingFuncfindsServe'sctxparameter (thehttp.HandlerFuncliteral itself has no context param, and the walk doesn't stop there) and fires "http.NewRequestdoes not propagate context; usehttp.NewRequestWithContextwhencontext.Contextis in scope." The suggested remediation is actively wrong: the correct per-request context here isr.Context(), not the handler-construction-timectxcaptured fromServe, which may already be long gone by the time the handler executes. This is the same false-positive/misleading-fix class documented forexeccommandwithoutcontextin #43683, just never patched here.There is no current production trigger (no
http.NewRequest/http.Get/http.Postcall sites underpkg/outsidehttpnoctx's own testdata), so this is latent rather than actively firing — matching the precedent of similarly-filed latent bugs (e.g. #44187 writebytestring, filed and fixed before it had a production hit).Evidence
pkg/linters/httpnoctx/httpnoctx.go:146-158— the unguarded walk.pkg/linters/httpnoctx/testdata/src/httpnoctx/httpnoctx.go— the entire test fixture has zeroFuncLit/closure test cases (no goroutine, nodefer, no plain closure), so the gap has no test coverage in either direction.pkg/linters/execcommandwithoutcontext/execcommandwithoutcontext.go:68-79andpkg/linters/timesleepnocontext/timesleepnocontext.go:59-70— sibling linters with the identical walk shape, already carrying theastutil.IsGoOrDeferClosureguard.Recommendation
Add the same boundary check used by
execcommandwithoutcontext/timesleepnocontext: when the current enclosing node is a*ast.FuncLitwith no context param, and it is not the direct callee of ago/deferstatement (!astutil.IsGoOrDeferClosure(enclosing)), stop the walk instead of continuing to the next-outer scope.Before:
After (mirroring
execcommandwithoutcontext.go:68-79):Validation checklist
go/defer)FuncLitwith no context param, nested inside a context-aware outer function, callinghttp.NewRequest— must NOT be flagged.go/defer— current (pre-fix) behavior of flagging should be preserved/covered explicitly.golint-custom -httpnoctx(or the project's lint task) over./pkg/...and./cmd/...to confirm zero behavior change on real call sites (none exist today, so this should be a no-op on production code).Effort
Small — the fix is a ~4-line change mirroring an already-landed pattern (
execcommandwithoutcontext.go:76-78), plus two new testdata fixtures.