Skip to content

httprespbodyclose: analyze function literals as first-class scopes - #43526

Merged
pelikhan merged 4 commits into
mainfrom
copilot/fix-httprespbodyclose-analyzer
Jul 5, 2026
Merged

httprespbodyclose: analyze function literals as first-class scopes#43526
pelikhan merged 4 commits into
mainfrom
copilot/fix-httprespbodyclose-analyzer

Conversation

CopilotAI commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

httprespbodyclose missed a real leak pattern when both resp assignment and manual resp.Body.Close() occurred inside a closure. The analyzer only entered FuncDecl and skipped FuncLit bodies entirely, creating false negatives in goroutines/handlers/IIFEs.

  • Scope traversal fix

    • Extend analyzer node filter to include both *ast.FuncDecl and *ast.FuncLit.
    • Refactor per-function analysis into a shared function that extracts and inspects either body type.
    • Keep nested FuncLit short-circuiting during inner walks so each scope is analyzed exactly once (prevents duplicate diagnostics).
  • Behavioral coverage in testdata

    • Add closure-local true-positive cases:
      • IIFE closure with manual non-deferred close
      • go func() closure with manual non-deferred close
      • http.HandleFunc-style closure with manual non-deferred close
    • Add closure-local non-violation case:
      • assignment inside closure with immediate defer resp.Body.Close()
    • Preserve existing outer-assignment/inner-close case as unflagged.
mux.HandleFunc("/x", func(w http.ResponseWriter, r*http.Request) {
resp, err:=client.Do(req) // now tracked in FuncLit scopeiferr!=nil {
return
}
io.Copy(io.Discard, resp.Body)
resp.Body.Close() // now reported: should be deferred
})

Generated by 👨‍🍳 PR Sous Chef · 8.12 AIC · ⌖ 9.29 AIC · ⊞ 3.7K ·
Comment /souschef to run again

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
CopilotAI changed the title [WIP] Fix httprespbodyclose analyzer for closure response handlinghttprespbodyclose: analyze function literals as first-class scopesJul 5, 2026
CopilotAI requested a review from pelikhanJuly 5, 2026 10:42
@pelikhan
pelikhan marked this pull request as ready for review July 5, 2026 12:28
CopilotAI review requested due to automatic review settings July 5, 2026 12:28
@github-actions

github-actionsBot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

No test files were added or modified in this PR. The only changes are to the production linter code (httprespbodyclose.go) and linter testdata input (testdata/src/httprespbodyclose/httprespbodyclose.go). Test Quality Sentinel skipped.

@github-actions

github-actionsBot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actionsBot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

⚠️PR Code Quality Reviewer failed during code quality review.

@github-actions

github-actionsBot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #43526 does not have the 'implementation' label and has ≤100 new lines of code in business logic directories (67 additions detected).

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the httprespbodyclose Go analyzer so it treats function literals (func(...) { ... }) as first-class, independently analyzed scopes—fixing false negatives where both the HTTP response assignment and a non-deferred resp.Body.Close() occur inside a closure (e.g., goroutines, handlers, IIFEs).

Changes:

  • Extend the inspector traversal to analyze both *ast.FuncDecl and *ast.FuncLit nodes.
  • Refactor the per-scope analysis into a shared inspectFunc helper that operates on either node type’s Body.
  • Add testdata cases covering closure-local true-positives (manual close) and a closure-local non-violation (deferred close).
Show a summary per file
FileDescription
pkg/linters/httprespbodyclose/httprespbodyclose.goAdds FuncLit scope analysis via a unified function-body inspection path while preserving nested FuncLit short-circuiting.
pkg/linters/httprespbodyclose/testdata/src/httprespbodyclose/httprespbodyclose.goAdds closure-based positive/negative test cases to prevent regressions on the previously missed leak pattern.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Low

@github-actionsgithub-actionsBot mentioned this pull request Jul 5, 2026

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: httprespbodyclose — analyze function literals as first-class scopes

The fix is correct and the approach is sound.

Scope traversal design ✅

insp.Preorder visits both *ast.FuncDecl and *ast.FuncLit nodes. Within each scope, inspectNode returns false on any nested FuncLit, ensuring each closure scope is analyzed exactly once — no duplicate diagnostics, no bleed across scopes.

Captured-variable safety ✅

Variables from outer scopes do not appear in an inner scope's respVars map, so cross-scope closes (as in the preserved GoodCloseInsideClosure case) remain unflagged.

Test coverage ✅

IIFE, goroutine, and handler closures are all exercised; the want-annotation pattern is correctly placed on the assignment line.

Minor observation (non-blocking)

In inspectFunc, the FuncLit nil-body guard (if fn.Body == nil { return }) is logically unreachable — a FuncLit always has a body in valid Go AST. Removing it would marginally reduce noise, but leaving it is harmless defensive code and not worth a change request.

No blocking issues. Approving.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 40.6 AIC · ⌖ 5.82 AIC · ⊞ 4.9K

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /diagnosing-bugs, /tdd, and /codebase-design — this is a well-targeted bug fix with good test coverage. Leaving a few suggestions as comments; no blocking issues.

📋 Key Themes & Highlights

Key Themes

  • FuncLit nil guard is dead code (line 55): *ast.FuncLit.Body is never nil in a valid AST; the guard is misleading and both switch arms are identical. Minor but worth cleaning up.
  • Short-circuit deserves a comment (line 87): The inspectNode bail-out on *ast.FuncLit is the correctness linchpin that prevents double-counting, but it reads like a bug without a comment. Future maintainers need to understand why it's there.
  • Missing boundary test (line 48 of testdata): The new FuncLit scope doesn't cover the case where an IIFE assigns a *http.Response to an outer variable and returns without closing — a genuine non-violation that's worth pinning.
  • Naming (line 46): inspectFunc is generic; inspectFuncScope or a doc comment would telegraph the dual-form contract.

Positive Highlights

  • ✅ Root cause is correctly diagnosed — Preorder + FuncLit inclusion is the minimal fix
  • ✅ Inner walk correctly short-circuits at nested FuncLit to prevent double-reporting
  • ✅ Test cases cover all three real-world closure shapes (IIFE, goroutine, HTTP handler) plus the defer-good-path
  • GoodCloseInsideClosure (outer-assigned, inner-closed) is preserved as an explicit non-violation
  • ✅ No changes to unrelated linter logic

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 76.3 AIC · ⌖ 5.55 AIC · ⊞ 6.6K
Comment /matt to run again

Comments that could not be inline-anchored

pkg/linters/httprespbodyclose/httprespbodyclose.go:55

[/diagnosing-bugs]*ast.FuncLit.Body is never nil in a valid Go AST — the grammar requires a body for function literals. This nil guard is dead code and adds misleading defensive noise that could cause future readers to wonder what scenario it protects against.

<details>
<summary>💡 Suggested simplification</summary>

Collapse the two identical case arms into one check after the switch:

varbody*ast.BlockStmtswitchfn:=n.(type) {
case*ast.FuncDecl:
body=fn.Bodycase*a</details><details><summary>pkg/linters/httprespbodyclose/httprespbodyclose.go:87</summary>**[/diagnosing-bugs]**`inspectNode`short-circuitsonany`*ast.FuncLit`topreventdouble-counting, butnowthat`Preorder`visitsevery`FuncLit`independently, theshort-circuitonlyneedstoskipnestedliteralsduringthe*inner*`ast.Inspect`walkthispartiscorrect. However, theshort-circuitalsomeansa**deferredclosedefinedinanouterfunctionthatclosesoveravariableassignedinaninnerclosurewouldnotbetracked**. Acommenthereexplainingthisisintentionalwouldp</details><details><summary>pkg/linters/httprespbodyclose/testdata/src/httprespbodyclose/httprespbodyclose.go:48</summary>**[/tdd]**ThetestsuitecoversdetectioninIIFE, goroutine, andhandlerclosures, whichis great. Amissingboundarycase: whenanIIFEassignsa`*http.Response`intoanoutervariableandreturnswithoutclosingitthisshouldnotbeflagged (callerownsthebody), butit&#39;suntestedandclosetothenewcodepath.
&lt;details&gt;
&lt;summary&gt;💡 Suggestedtestcase&lt;/summary&gt;
```go// GoodIIFEReturnsResp returns the response from the IIFE — caller is responsible for closing.func GoodIIFEReturnsR…</details><details><summary>pkg/linters/httprespbodyclose/httprespbodyclose.go:46</summary>**[/codebase-design]** The function name `inspectFunc` is now shared between `FuncDecl` and `FuncLit`, but the test-file guard (`filecheck.IsTestFile`) uses `n.Pos()` to get the filename. For `FuncLit` nodes nested inside a non-test file, this will correctly skip test files — but the intent is less obvious than for `FuncDecl`. A renaming to something like `inspectFuncScope` and a short doc comment would clarify that this handles both declaration and literal forms.
&lt;details&gt;
&lt;summary&gt;💡 Suggest…
</details>

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔎 Code quality review by PR Code Quality Reviewer · 129.7 AIC · ⌖ 5.19 AIC · ⊞ 5.4K
Comment /review to run again

return
}
body = fn.Body
default:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead default: branch is unreachable and silently masks future type-contract violations.

💡 Details

insp.Preorder is invoked with an explicit node filter of [*ast.FuncDecl, *ast.FuncLit]. The framework guarantees the callback is only invoked for those two types, making the default: return permanently dead.

This creates two compounding risks:

  1. Readers are misled into thinking inspectFunc may receive other node types, obscuring the real caller contract.
  2. If inspectFunc is ever called directly (test helper, new filter entry), a wrong type silently produces no diagnostic — no panic, no error, just a quiet miss.

Suggestion — remove default entirely so the exhaustive type switch documents the contract:

switchfn:=n.(type) {
case*ast.FuncDecl:
iffn.Body==nil {
return
}
body=fn.Bodycase*ast.FuncLit:
iffn.Body==nil {
return
}
body=fn.Body
}

If forward-compatibility matters, use a compile-time exhaustiveness check or a panic.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Triage — Run §28741077056

FieldValue
Categorybug
Risk🟢 Low
Priority Score45/100 — impact:22 urgency:10 quality:13
Action🚀 fast_track

Rationale: Fixes false-negative leak detection in httprespbodyclose analyzer — closures/goroutines/IIFEs were silently skipped. 1× APPROVED. Go-only, 2 files (+67/−6), well-scoped fix.

Generated by 🔧 PR Triage Agent · 91.2 AIC · ⌖ 11.9 AIC · ⊞ 5.5K ·

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address unresolved review comments, and rerun checks once the branch is up to date.

Generated by 👨‍🍳 PR Sous Chef · 8.12 AIC · ⌖ 9.29 AIC · ⊞ 3.7K ·
Comment /souschef to run again

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

CopilotAI commented Jul 5, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot please run the pr-finisher skill, address unresolved review comments, and rerun checks once the branch is up to date.

...

Addressed the remaining actionable review feedback in f71eac4 by removing the unreachable default branch in inspectFunc. I reran local validation (make fmt, make lint, make test-unit, make test, make recompile, and make agent-report-progress) and final automated validation passed. The branch is up to date with main; the latest GitHub checks on f71eac4 are in action_required, so a maintainer will need to re-trigger/unstick CI from GitHub before merge.

CopilotAI requested a review from gh-aw-botJuly 5, 2026 13:12
@pelikhan
pelikhan merged commit 6c67696 into mainJul 5, 2026
25 checks passed
@pelikhan
pelikhan deleted the copilot/fix-httprespbodyclose-analyzer branch July 5, 2026 13:15
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

Hey @copilot-swe-agent 👋 — great fix for the httprespbodyclose false-negative bug! Extending the analyzer to treat *ast.FuncLit as a first-class scope (alongside *ast.FuncDecl) is exactly the right approach, and the refactor into a shared per-function analysis helper keeps things clean.

The test coverage is thorough — IIFEs, goroutine closures, and HTTP handler closures all covered, plus the non-violation defer case. This looks ready for review. 🟢

Generated by ✅ Contribution Check · 237.6 AIC · ⌖ 35.8 AIC · ⊞ 6.2K ·

@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.82.3

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

4 participants

@gh-aw-bot@pelikhan