Skip to content

nilctxpassed: spread call f(nil...) to a variadic context.Context param is a false positive — nil variadic slice misreported as [Content truncated due to length] #45903

Description

@github-actions

Summary

nilctxpassed (the newest un-audited linter; registry is now 45 analyzers) reports a false positive for a variadic spread call such as takesVariadicCtx(nil...). In that call nil is the whole []context.Context slice (a nil slice = zero contexts, which is perfectly valid), not a context.Context element. The linter never inspects call.Ellipsis, so it treats the spread arg as an individual variadic element and emits nil passed as context.Context; use context.Background() or context.TODO() instead — a wrong, non-actionable diagnostic.

nilctxpassed is the only custom linter that does variadic element-wise indexing via sig.Variadic(), and the only one that fails to check call.Ellipsis. The analogous variadic linter appendbytestring gets this right (pkg/linters/appendbytestring/appendbytestring.go:57 gates on call.Ellipsis.IsValid()).

Location

pkg/linters/nilctxpassed/nilctxpassed.go:57-70 (the per-arg variadic branch) together with the nil check at :76.

Root-cause walkthrough

For takesVariadicCtx(nil...) where func takesVariadicCtx(args ...context.Context):

  1. call.Args has length 1: the single nil expression, and call.Ellipsis is valid (the ... spread).
  2. Loop for i, arg := range call.Args at i=0: sig.Variadic() && params.Len() > 0 && i >= params.Len()-1 is true && true && 0 >= 0 → enters the variadic element branch.
  3. paramType is set to the slice element type context.Context (lines 61-65).
  4. isContextContext(paramType) → true; isBuiltinNil(pass, arg) → true (the predeclared nil, Uses[nil] is *types.Nil).
  5. pass.Report(...) fires.

But the code never distinguishes a spread call from a plain variadic call. In a spread call the final argument corresponds to the slice type []context.Context, not its element — and passing nil as that slice is a valid empty variadic, not a nil context.

Evidence it is unique:

$ grep -rln 'Variadic()' pkg/linters --include=*.go | grep -v _test
pkg/linters/nilctxpassed/nilctxpassed.go        # only element-indexing variadic linter
$ grep -rln '\.Ellipsis' pkg/linters --include=*.go | grep -v _test
pkg/linters/appendbytestring/appendbytestring.go # the only linter that guards on Ellipsis

Testdata (testdata/src/nilctxpassed/nilctxpassed.go:33-35) only exercises the non-spread variadic case takesVariadicCtx(nil) (a legitimately-flagged nil context). There is no spread-call fixture, so the FP is unguarded and untested.

Impact

  • False positive on valid code f(nil...) that spreads a nil []context.Context — the message is factually wrong (no context value is passed).
  • Latent enforcement blocker: nilctxpassed is otherwise clean and well-behaved (type-resolved context.Context identity via obj.Pkg().Path() == "context", wires internal/nolint, skips test + generated files via filecheck.ShouldSkipFilename). It is a natural CI-enforcement candidate, but this FP would fail CI on any legitimate nil... spread site.
  • Severity is low (spread-of-nil to a variadic context param is uncommon), but it is a clear correctness defect in a brand-new linter with zero test coverage for the case.

Recommendation

In the variadic branch, treat a spread call's final argument as the whole slice rather than an element. A nil slice is a valid empty variadic, so skip the element-wise nil-context check:

Intent (before → after):

  • Before: for takesVariadicCtx(nil...) the linter enters the element branch and reports a nil context.Context.
  • After: when call.Ellipsis.IsValid() and the arg is the variadic slice position, the linter recognizes nil as a nil []context.Context slice and does not report.

Concretely, guard the variadic branch on !call.Ellipsis.IsValid() (mirroring appendbytestring), or continue for the final arg when the call is a spread. The non-spread variadic path (takesVariadicCtx(nil)) must stay flagged.

Validation checklist

  • Add testdata fixture takesVariadicCtx(nil...) — asserted not flagged.
  • Add testdata fixture takesVariadicCtx(existingSlice...) — asserted not flagged (regression guard for the general spread case).
  • Keep existing takesVariadicCtx(nil) (non-spread) — still flagged.
  • go test ./pkg/linters/nilctxpassed/... passes.

Effort

XS — one call.Ellipsis.IsValid() guard in run plus 1-2 testdata fixtures.

References: §29472028410

Generated by 🤖 Sergo - Serena Go Expert · 322.5 AIC · ⌖ 15 AIC · ⊞ 5.8K ·

  • expires on Jul 22, 2026, 8:58 PM UTC-08:00

Activity

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

Metadata

Metadata

Labels

cookieIssue Monster Loves Cookies!sergo

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions