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

Copilot AI 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 scope
    if err != 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>
Copilot AI changed the title [WIP] Fix httprespbodyclose analyzer for closure response handling httprespbodyclose: analyze function literals as first-class scopes Jul 5, 2026
Copilot AI requested a review from pelikhan July 5, 2026 10:42
@pelikhan
pelikhan marked this pull request as ready for review July 5, 2026 12:28
Copilot AI review requested due to automatic review settings July 5, 2026 12:28
@github-actions

github-actions Bot 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-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot 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).

Copilot AI 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
File Description
pkg/linters/httprespbodyclose/httprespbodyclose.go Adds FuncLit scope analysis via a unified function-body inspection path while preserving nested FuncLit short-circuiting.
pkg/linters/httprespbodyclose/testdata/src/httprespbodyclose/httprespbodyclose.go Adds 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-actions github-actions Bot mentioned this pull request Jul 5, 2026

@github-actions github-actions Bot 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-actions github-actions Bot 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:

var body *ast.BlockStmt
switch fn := n.(type) {
case *ast.FuncDecl:
	body = fn.Body
case *a</details>

<details><summary>pkg/linters/httprespbodyclose/httprespbodyclose.go:87</summary>

**[/diagnosing-bugs]** `inspectNode` short-circuits on any `*ast.FuncLit` to prevent double-counting, but now that `Preorder` visits every `FuncLit` independently, the short-circuit only needs to skip nested literals during the *inner* `ast.Inspect` walkthis part is correct. However, the short-circuit also means a **deferred close defined in an outer function that closes over a variable assigned in an inner closure would not be tracked**. A comment here explaining this is intentional would p</details>

<details><summary>pkg/linters/httprespbodyclose/testdata/src/httprespbodyclose/httprespbodyclose.go:48</summary>

**[/tdd]** The test suite covers detection in IIFE, goroutine, and handler closures, which is great. A missing boundary case: when an IIFE assigns a `*http.Response` into an outer variable and returns without closing itthis should not be flagged (caller owns the body), but it&#39;s untested and close to the new code path.

&lt;details&gt;
&lt;summary&gt;💡 Suggested test case&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-actions github-actions Bot 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:

switch fn := n.(type) {
case *ast.FuncDecl:
    if fn.Body == nil {
        return
    }
    body = fn.Body
case *ast.FuncLit:
    if fn.Body == nil {
        return
    }
    body = fn.Body
}

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

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

🤖 PR Triage — Run §28741077056

Field Value
Category bug
Risk 🟢 Low
Priority Score 45/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>

Copilot AI commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

@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.

Copilot AI requested a review from gh-aw-bot July 5, 2026 13:12
@pelikhan
pelikhan merged commit 6c67696 into main Jul 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

github-actions Bot commented Jul 5, 2026

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

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.82.3

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

Projects

None yet

4 participants