Upgrade Go tooling and strengthen static checks

Update the Go and lint toolchain, expand static analysis, and keep
exceptions scoped to intentional parser and stack fixtures.

Lint the panic and panicweb entry points directly. Preserve their
intentional stack signatures and non-ASCII identifiers with narrow
annotations, and update the expected source lines for live snapshots.

Validation: make fix, make verify, and make test.
diff --git a/.golangci.yml b/.golangci.yml
index d0f7f56..664df9a 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -10,22 +10,89 @@
 linters:
   default: none
   enable:
+    - asasalint
+    - asciicheck
+    - bidichk
+    - bodyclose
+    - canonicalheader
+    - contextcheck
+    - copyloopvar
+    - dupl
+    - dupword
+    - durationcheck
+    - embeddedstructfieldcheck
+    - errcheck
+    - errchkjson
+    - errname
+    - errorlint
+    - exhaustive
+    - exptostd
+    - fatcontext
+    - forcetypeassert
+    - funcorder
+    - gocheckcompilerdirectives
+    - gochecksumtype
+    - gocritic
+    - godoclint
+    - godot
+    - gomoddirectives
+    - goprintffuncname
     - gosec
+    - gosmopolitan
     - govet
+    - grouper
+    - iface
+    - importas
+    - inamedparam
     - ineffassign
-    - misspell
+    - intrange
+    - iotamixing
+    - loggercheck
+    - makezero
+    - mirror
+    - modernize
+    - musttag
+    - nakedret
+    - nilerr
+    - nilnesserr
+    - nilnil
+    - noctx
+    - nolintlint
+    - nosprintfhostport
+    - paralleltest
+    - perfsprint
+    - prealloc
+    - predeclared
+    - reassign
+    - recvcheck
+    - revive
+    - sloglint
+    - sqlclosecheck
     - staticcheck
+    - tparallel
+    - unconvert
+    - unparam
     - unused
+    - usestdlibvars
+    - usetesting
+    - wastedassign
+    - whitespace
   exclusions:
     rules:
+      # This fixture deliberately exercises Unicode names.
+      - path: ^cmd/panic/internal/utf8/utf8\.go$
+        linters: [asciicheck]
+      # These standalone examples intentionally omit error handling and context plumbing,
+      # and include fixed source line numbers in their expected stack output.
+      - path: ^stack/example_test\.go$
+        linters: [errcheck, errorlint, gocritic, noctx, revive, whitespace]
+      # Repeated identifiers are valid source in the parser regression fixture.
+      - path: ^stack/source_test\.go$
+        text: Duplicate words \(z,\)
+        linters: [dupword]
       # The standalone gosec CI command scanned production sources only.
       - path: _test\.go$
         linters: [gosec]
-      # The restart resolves os.Executable and forwards argv without invoking a shell.
-      - path: ^cmd/panic/main\.go$
-        source: 'exec\.Command\(exe, os\.Args\[1:\]\.\.\.\)'
-        text: G(204|702)
-        linters: [gosec]
   settings:
     staticcheck:
       checks: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF*"]
diff --git a/Makefile b/Makefile
index 0cb0215..7a2d749 100644
--- a/Makefile
+++ b/Makefile
@@ -31,7 +31,6 @@
 	@go tool addlicense -ignore '.venv/**' -ignore '.ruff_cache/**' -ignore 'third_party/**' -ignore 'vendor/**' -check .
 	@uv run --locked python scripts/lint_binaries.py
 	@uv run --locked python scripts/update_agents_file_index.py --check
-	@git ls-files -z -- . ':(exclude)go.mod' ':(exclude)go.sum' | xargs -0 go run github.com/client9/misspell/cmd/misspell@v0.3.4 -error
 	@go mod tidy -diff
 
 test:
diff --git a/cmd/panic/main.go b/cmd/panic/main.go
index 8eff9b7..2bfdf19 100644
--- a/cmd/panic/main.go
+++ b/cmd/panic/main.go
@@ -28,6 +28,8 @@
 // below. That's it!
 
 import (
+	"context"
+	"errors"
 	"fmt"
 	"io"
 	"os"
@@ -40,7 +42,7 @@
 
 	"github.com/maruel/panicparse/v2/cmd/panic/internal"
 	correct "github.com/maruel/panicparse/v2/cmd/panic/internal/incorrect"
-	ùtf8 "github.com/maruel/panicparse/v2/cmd/panic/internal/utf8"
+	ùtf8 "github.com/maruel/panicparse/v2/cmd/panic/internal/utf8" //nolint:asciicheck // The fixture tests non-ASCII identifiers.
 )
 
 func main() {
@@ -75,7 +77,7 @@
 				f.f()
 				os.Exit(3)
 			}
-			fmt.Fprintf(stdErr, "unknown panic style %q\n", n)
+			_, _ = fmt.Fprintf(stdErr, "unknown panic style %q\n", n)
 			os.Exit(1)
 		}
 	}
@@ -104,7 +106,7 @@
 	panic(a)
 }
 
-func panicArgsElided(a, b, c, d, e, f, g, h, i, j, k int) {
+func panicArgsElided(a, _, _, _, _, _, _, _, _, _, _ int) {
 	panic(a)
 }
 
@@ -120,24 +122,24 @@
 	help := "'panic %s' can only be used when built with the race detector.\n" +
 		"To build, use:\n" +
 		"  go install -race github.com/maruel/panicparse/v2/cmd/panic\n"
-	fmt.Fprintf(stdErr, help, name)
+	_, _ = fmt.Fprintf(stdErr, help, name)
 }
 
 func rerunWithFastCrash() {
 	if os.Getenv("GORACE") != "log_path=stderr halt_on_error=1" {
 		exe, err := os.Executable()
 		if err != nil {
-			fmt.Fprintln(stdErr, err)
+			_, _ = fmt.Fprintln(stdErr, err)
 			os.Exit(1)
 		}
-		c := exec.Command(exe, os.Args[1:]...)
+		c := exec.CommandContext(context.Background(), exe, os.Args[1:]...) // #nosec G204 G702 -- re-executes this binary without a shell.
 		c.Env = append(os.Environ(), "GORACE=log_path=stderr halt_on_error=1")
 		c.Stderr = os.Stderr
 		if err := c.Run(); err != nil {
-			if e, ok := err.(*exec.ExitError); ok {
+			if e, ok := errors.AsType[*exec.ExitError](err); ok {
 				os.Exit(e.ExitCode())
 			}
-			fmt.Fprintln(stdErr, err)
+			_, _ = fmt.Fprintln(stdErr, err)
 			os.Exit(1)
 		}
 		os.Exit(0)
@@ -177,7 +179,7 @@
 }
 
 //go:noinline
-func panicChanStruct(x chan struct{}) {
+func panicChanStruct(_ chan struct{}) {
 	panic("test")
 }
 
@@ -285,7 +287,7 @@
 		"start 100 goroutines before panicking",
 		func() {
 			var wg sync.WaitGroup
-			for i := 0; i < 100; i++ {
+			for range 100 {
 				wg.Add(1)
 				go func() {
 					wg.Done()
@@ -301,9 +303,9 @@
 		"start 100 goroutines with different pointers before panicking",
 		func() {
 			var wg sync.WaitGroup
-			for i := 0; i < 100; i++ {
+			for range 100 {
 				wg.Add(1)
-				go func(b *int) {
+				go func(_ *int) {
 					wg.Done()
 					time.Sleep(time.Minute)
 				}(new(int))
@@ -466,7 +468,7 @@
 	}
 	sort.Strings(names)
 	for _, n := range names {
-		fmt.Fprintf(stdErr, "- %-*s  %s\n", m, n, types[n].desc)
+		_, _ = fmt.Fprintf(stdErr, "- %-*s  %s\n", m, n, types[n].desc)
 	}
 }
 
diff --git a/cmd/panic/main_test.go b/cmd/panic/main_test.go
index 475f773..b01a132 100644
--- a/cmd/panic/main_test.go
+++ b/cmd/panic/main_test.go
@@ -19,9 +19,9 @@
 	t.Parallel()
 	if !testing.Verbose() {
 		stdErr = io.Discard
-		defer func() {
+		t.Cleanup(func() {
 			stdErr = os.Stderr
-		}()
+		})
 	}
 	for name, l := range types {
 		if name == "simple" {
@@ -44,7 +44,6 @@
 			continue
 		}
 
-		l := l
 		t.Run(name, func(t *testing.T) {
 			t.Parallel()
 			defer func() {
@@ -59,6 +58,7 @@
 
 // TestRerunWithFastCrash verifies restart uses the executable rather than argv[0].
 func TestRerunWithFastCrash(t *testing.T) {
+	t.Parallel()
 	if os.Getenv("PANICPARSE_REEXEC_TEST") == "1" {
 		if os.Getenv("GORACE") == "log_path=stderr halt_on_error=1" {
 			fmt.Fprintln(os.Stderr, "trusted restart")
@@ -72,7 +72,7 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	cmd := exec.Command(exe, "-test.run=^TestRerunWithFastCrash$")
+	cmd := exec.CommandContext(t.Context(), exe, "-test.run=^TestRerunWithFastCrash$")
 	cmd.Env = append(os.Environ(), "PANICPARSE_REEXEC_TEST=1", "GORACE=")
 	out, err := cmd.CombinedOutput()
 	if err != nil {
diff --git a/cmd/panicweb/internal/internal.go b/cmd/panicweb/internal/internal.go
index 53d51a9..54df288 100644
--- a/cmd/panicweb/internal/internal.go
+++ b/cmd/panicweb/internal/internal.go
@@ -19,7 +19,8 @@
 // goroutine.
 func GetAsync(url string) {
 	/* #nosec G107 */
-	resp, err := http.Get(url)
+	// The response body is transferred to and closed by the goroutine below.
+	resp, err := http.Get(url) //nolint:bodyclose,noctx // Ownership moves to the goroutine; this fixture has no context.
 	if err != nil {
 		log.Fatalf("get %s: %v", url, err)
 	}
@@ -34,22 +35,22 @@
 }
 
 // URL1Handler is a http.HandlerFunc that hangs.
-func URL1Handler(w http.ResponseWriter, req *http.Request) {
+func URL1Handler(w http.ResponseWriter, _ *http.Request) {
 	// Respond the HTTP header to unblock the http.Get() function.
 	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
 	w.Header().Set("Content-Length", "100000")
-	w.WriteHeader(200)
+	w.WriteHeader(http.StatusOK)
 	b := [4096]byte{}
 	_, _ = w.Write(b[:])
 	<-Unblock
 }
 
 // URL2Handler is a http.HandlerFunc that hangs.
-func URL2Handler(w http.ResponseWriter, req *http.Request) {
+func URL2Handler(w http.ResponseWriter, _ *http.Request) {
 	// Respond the HTTP header to unblock the http.Get() function.
 	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
 	w.Header().Set("Content-Length", "100000")
-	w.WriteHeader(200)
+	w.WriteHeader(http.StatusOK)
 	b := [4096]byte{}
 	_, _ = w.Write(b[:])
 	<-Unblock
diff --git a/cmd/panicweb/main.go b/cmd/panicweb/main.go
index cda3bf6..2be0e69 100644
--- a/cmd/panicweb/main.go
+++ b/cmd/panicweb/main.go
@@ -14,6 +14,7 @@
 package main
 
 import (
+	"context"
 	"flag"
 	"fmt"
 	"log"
@@ -25,6 +26,7 @@
 	_ "net/http/pprof"
 	"os"
 	"runtime"
+	"strconv"
 	"strings"
 	"sync"
 	"time"
@@ -57,7 +59,7 @@
 	if !*allowremote {
 		addr = "localhost" + addr
 	}
-	ln, err := net.Listen("tcp", addr)
+	ln, err := (&net.ListenConfig{}).Listen(context.Background(), "tcp", addr)
 	if err != nil {
 		log.Fatalf("Failed to listen on localhost: %v", err)
 	}
@@ -104,7 +106,7 @@
 	} else {
 		http.HandleFunc("/panicparse", webstack.SnapshotHandler)
 	}
-	http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
+	http.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
 		w.Header().Set("Content-Type", "text/html; charset=utf-8")
 		_, _ = w.Write(rootPage)
 	})
@@ -112,7 +114,7 @@
 		Handler:           http.DefaultServeMux,
 		ReadHeaderTimeout: 2 * time.Second,
 	}
-	go srv.Serve(ln)
+	go srv.Serve(ln) //nolint:errcheck // Preserve the server goroutine's stack signature.
 
 	// Start many clients.
 	a := ln.Addr()
@@ -120,14 +122,14 @@
 	if *allowremote {
 		if h, err := os.Hostname(); err == nil {
 			if t, ok := a.(*net.TCPAddr); ok {
-				url = fmt.Sprintf("http://%s:%d/", h, t.Port)
+				url = "http://" + net.JoinHostPort(h, strconv.Itoa(t.Port)) + "/"
 			}
 		}
 	}
-	for i := 0; i < 10; i++ {
+	for range 10 {
 		internal.GetAsync(url + "url1")
 	}
-	for i := 0; i < 3; i++ {
+	for range 3 {
 		internal.GetAsync(url + "url2")
 	}
 
@@ -143,7 +145,7 @@
 	// that is both versioned and can be hacked to call back user code.
 	w := writeHang{hung: make(chan struct{}), unblock: make(chan struct{})}
 	v := colorable.NewNonColorable(&w)
-	go v.Write([]byte("foo bar"))
+	go v.Write([]byte("foo bar")) //nolint:errcheck // Preserve the writer goroutine's stack signature.
 	<-w.hung
 
 	if *sleep {
@@ -159,7 +161,7 @@
 	unblock chan struct{}
 }
 
-func (w *writeHang) Write(b []byte) (int, error) {
+func (w *writeHang) Write(_ []byte) (int, error) {
 	runtime.LockOSThread()
 	w.hung <- struct{}{}
 	<-w.unblock
diff --git a/go.mod b/go.mod
index e2e6784..f9aa7c3 100644
--- a/go.mod
+++ b/go.mod
@@ -1,10 +1,10 @@
 module github.com/maruel/panicparse/v2
 
-go 1.27.0
+go 1.27.3
 
 require (
 	github.com/google/go-cmp v0.7.0
-	github.com/mattn/go-colorable v0.1.15
+	github.com/mattn/go-colorable v0.1.16
 	github.com/mattn/go-isatty v0.0.22
 	github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
 	golang.org/x/sys v0.49.0
@@ -47,7 +47,7 @@
 	github.com/bmatcuk/doublestar/v4 v4.0.2 // indirect
 	github.com/bombsimon/wsl/v4 v4.7.0 // indirect
 	github.com/bombsimon/wsl/v5 v5.9.0 // indirect
-	github.com/breml/bidichk v0.3.3 // indirect
+	github.com/breml/bidichk v0.3.4 // indirect
 	github.com/breml/errchkjson v0.4.1 // indirect
 	github.com/butuzov/ireturn v0.4.1 // indirect
 	github.com/butuzov/mirror v1.3.3 // indirect
@@ -66,7 +66,7 @@
 	github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
 	github.com/curioswitch/go-reassign v0.3.0 // indirect
 	github.com/daixiang0/gci v0.13.7 // indirect
-	github.com/dave/dst v0.27.3 // indirect
+	github.com/dave/dst v0.28.0 // indirect
 	github.com/denis-tingaikin/go-header v0.5.0 // indirect
 	github.com/dlclark/regexp2/v2 v2.2.1 // indirect
 	github.com/ettle/strcase v0.2.0 // indirect
@@ -91,16 +91,16 @@
 	github.com/gofrs/flock v0.13.1 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/golangci/asciicheck v0.5.0 // indirect
-	github.com/golangci/canonicalheader v0.0.0-20260827115959-a25c71c521f6 // indirect
+	github.com/golangci/canonicalheader v0.0.0-20260928145534-e18e5c5a078f // indirect
 	github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 // indirect
 	github.com/golangci/go-printf-func-name v0.1.1 // indirect
 	github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792 // indirect
-	github.com/golangci/golangci-lint/v2 v2.14.0 // indirect
-	github.com/golangci/golines v0.15.0 // indirect
+	github.com/golangci/golangci-lint/v2 v2.15.0 // indirect
+	github.com/golangci/golines v0.16.0 // indirect
 	github.com/golangci/misspell v0.8.0 // indirect
 	github.com/golangci/plugin-module-register v0.1.2 // indirect
 	github.com/golangci/revgrep v0.8.0 // indirect
-	github.com/golangci/rowserrcheck v0.0.0-20260419091836-c5f79b8a11ba // indirect
+	github.com/golangci/rowserrcheck v0.0.0-20261005233414-e0740d52dee8 // indirect
 	github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect
 	github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect
 	github.com/google/addlicense v1.2.0 // indirect
@@ -119,10 +119,10 @@
 	github.com/jjti/go-spancheck v0.6.5 // indirect
 	github.com/julz/importas v0.2.0 // indirect
 	github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect
-	github.com/kisielk/errcheck v1.20.0 // indirect
+	github.com/kisielk/errcheck v1.30.0 // indirect
 	github.com/kkHAIKE/contextcheck v1.1.6 // indirect
 	github.com/kulti/thelper v0.7.1 // indirect
-	github.com/kunwardeep/paralleltest v1.0.15 // indirect
+	github.com/kunwardeep/paralleltest v1.0.16 // indirect
 	github.com/ldez/exptostd v0.4.5 // indirect
 	github.com/ldez/gomoddirectives v0.10.0 // indirect
 	github.com/ldez/grignotin v0.10.1 // indirect
@@ -140,14 +140,14 @@
 	github.com/matoous/godox v1.1.0 // indirect
 	github.com/mattn/go-runewidth v0.0.24 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
-	github.com/mgechev/revive v1.17.0 // indirect
+	github.com/mgechev/revive v1.17.1 // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/moricho/tparallel v0.3.2 // indirect
 	github.com/muesli/cancelreader v0.2.2 // indirect
 	github.com/nakabonne/nestif v0.3.1 // indirect
-	github.com/nishanths/exhaustive v0.13.0 // indirect
-	github.com/nishanths/predeclared v0.2.2 // indirect
+	github.com/nishanths/exhaustive v0.15.0 // indirect
+	github.com/nishanths/predeclared v0.3.0 // indirect
 	github.com/nunnatsa/ginkgolinter v0.24.0 // indirect
 	github.com/pelletier/go-toml v1.9.5 // indirect
 	github.com/pelletier/go-toml/v2 v2.4.3 // indirect
@@ -188,13 +188,13 @@
 	github.com/subosito/gotenv v1.4.1 // indirect
 	github.com/tetafro/godot v1.5.6 // indirect
 	github.com/timakin/bodyclose v0.0.0-20260723120731-857993a2939c // indirect
-	github.com/timonwong/loggercheck v0.12.0 // indirect
-	github.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect
+	github.com/timonwong/loggercheck v0.13.0 // indirect
+	github.com/tomarrell/wrapcheck/v2 v2.13.0 // indirect
 	github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
 	github.com/ultraware/funlen v0.2.0 // indirect
 	github.com/ultraware/whitespace v0.2.0 // indirect
-	github.com/uudashr/gocognit v1.2.1 // indirect
-	github.com/uudashr/iface v1.5.1 // indirect
+	github.com/uudashr/gocognit v1.3.0 // indirect
+	github.com/uudashr/iface v1.5.3 // indirect
 	github.com/xen0n/gosmopolitan v1.3.0 // indirect
 	github.com/xo/terminfo v1.0.0 // indirect
 	github.com/yagipy/maintidx v1.0.0 // indirect
@@ -217,7 +217,7 @@
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	honnef.co/go/tools v0.8.1 // indirect
+	honnef.co/go/tools v0.8.2 // indirect
 	mvdan.cc/gofumpt v0.12.0 // indirect
 	mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8 // indirect
 )
diff --git a/go.sum b/go.sum
index 8c9b9ee..a7e368a 100644
--- a/go.sum
+++ b/go.sum
@@ -116,8 +116,8 @@
 github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg=
 github.com/bombsimon/wsl/v5 v5.9.0 h1:WCrgZ7RQnZO5oEwbVTlYgBdU3wL294kR1BSWV8vTfsU=
 github.com/bombsimon/wsl/v5 v5.9.0/go.mod h1:kjo4HiAV5FDkHC8/uzJq9mBffEEd6WT/nvN7DoMovDM=
-github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE=
-github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE=
+github.com/breml/bidichk v0.3.4 h1:70G6w80G6GDZTUURHTC5gBKVjMLMw2736CzXquSf5Ek=
+github.com/breml/bidichk v0.3.4/go.mod h1:GOHprnh4i0V7HrvWjtbvbpicTUcqoYt9zYESKcJkk4M=
 github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg=
 github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s=
 github.com/butuzov/ireturn v0.4.1 h1:vWb3NO4t77iku/sjCQ/2pHTQeOmxEhjIriJqRLg1Y+I=
@@ -163,8 +163,8 @@
 github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88=
 github.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ=
 github.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ=
-github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY=
-github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc=
+github.com/dave/dst v0.28.0 h1:EIEuRC5XmVKuZF2OeyMahXX9ARVt6Xxr8vI0WtIY7K8=
+github.com/dave/dst v0.28.0/go.mod h1:C4mfimBeSHfsgskhR/mUYSE05s2ih+PW6g9FP3c49tE=
 github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
 github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -275,26 +275,26 @@
 github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0=
 github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ=
-github.com/golangci/canonicalheader v0.0.0-20260827115959-a25c71c521f6 h1:fVLolA3dG6s0brGetsiDAtcnpMSwVa2LqXJEw/9RJG4=
-github.com/golangci/canonicalheader v0.0.0-20260827115959-a25c71c521f6/go.mod h1:1xo+NFW5S+bEf2DKXhaxuzvcDN5AR6o/KMwvqC5Mkpo=
+github.com/golangci/canonicalheader v0.0.0-20260928145534-e18e5c5a078f h1:pOnwRNeWLPkADfkgqKcCP7RPQAS9OIzffaBaktRsKnk=
+github.com/golangci/canonicalheader v0.0.0-20260928145534-e18e5c5a078f/go.mod h1:aXo+AVSX0pC3TRo2wAfR92YwBKfG6C2eal+omjKXwYw=
 github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 h1:CbTB8KpqnViI6lIXxp03Oclc4VFHi3K4BWC1TacsZ+A=
 github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E=
 github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U=
 github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss=
 github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792 h1:WL8YKrt3UbOBqSRU7GpP5BTtQTMWtVtj+mfPijgZeIg=
 github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792/go.mod h1:te5hX0dW4C5r6YbXs+6ysNr8Q5UTmdIqGbb+mlFiYmA=
-github.com/golangci/golangci-lint/v2 v2.14.0 h1:ot8QffRa4LzAAEgtvNYrVs9esxQ7xoTwoAv6uhp4ngA=
-github.com/golangci/golangci-lint/v2 v2.14.0/go.mod h1:amHimCzNDP1GfVno3hIiNssjvqXmuW+CPZAHOdR4cIc=
-github.com/golangci/golines v0.15.0 h1:Qnph25g8Y1c5fdo1X7GaRDGgnMHgnxh4Gk4VfPTtRx0=
-github.com/golangci/golines v0.15.0/go.mod h1:AZjXd23tbHMpowhtnGlj9KCNsysj72aeZVVHnVcZx10=
+github.com/golangci/golangci-lint/v2 v2.15.0 h1:w94zf8HAYI7hm3164kwKsvZRuqXvY+YxeFoTbu1OLg8=
+github.com/golangci/golangci-lint/v2 v2.15.0/go.mod h1:/FRI304IxqDVhX2o3ZZ0qp8Vvz8egJdzCY5A4GjvmTU=
+github.com/golangci/golines v0.16.0 h1:xKdwzRvwzLd7V131B/dv90vUiEoTA0vQybdeqperPOg=
+github.com/golangci/golines v0.16.0/go.mod h1:mCMgRQHiCvuALJOeaNtylu09zbzvS9Ka01dfvLLJios=
 github.com/golangci/misspell v0.8.0 h1:qvxQhiE2/5z+BVRo1kwYA8yGz+lOlu5Jfvtx2b04Jbg=
 github.com/golangci/misspell v0.8.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg=
 github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg=
 github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw=
 github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s=
 github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k=
-github.com/golangci/rowserrcheck v0.0.0-20260419091836-c5f79b8a11ba h1:lqtcnSMDuuJdu/LrKWi5RJzpSNLOJXYe/nzQutTI5kg=
-github.com/golangci/rowserrcheck v0.0.0-20260419091836-c5f79b8a11ba/go.mod h1:sCBNcpRmhJCtbFGz49+IM3ETTFf7QdJ30AeYCd43NKk=
+github.com/golangci/rowserrcheck v0.0.0-20261005233414-e0740d52dee8 h1:oN042y1yEcpldrqY81puyNA4ZjQZJzt0U/t7HyYrMic=
+github.com/golangci/rowserrcheck v0.0.0-20261005233414-e0740d52dee8/go.mod h1:T1Bg58Sk3N352aBbPLJPpHykFWxEEwV3o4/Enn+PPU8=
 github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM=
 github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s=
 github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM=
@@ -380,8 +380,8 @@
 github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY=
 github.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0=
 github.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY=
-github.com/kisielk/errcheck v1.20.0 h1:9rwHBNKzd4wkDWcROy3DvFGNqEPlkxBg305rvk7HabI=
-github.com/kisielk/errcheck v1.20.0/go.mod h1:O+f80MKNwX8Oor2jwgpeQ9An7uJm+hRSgT+h22knRJU=
+github.com/kisielk/errcheck v1.30.0 h1:ZIxNDAfAd4larXHTceoZP/dYPAB90dHg1OYhA2sVh5k=
+github.com/kisielk/errcheck v1.30.0/go.mod h1:uiCAInmFuyOje4VDdbQSP9mgqf5gORYSXrDf19XilsI=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE=
 github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=
@@ -397,8 +397,8 @@
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98=
 github.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs=
-github.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJMLyncOb6w=
-github.com/kunwardeep/paralleltest v1.0.15/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk=
+github.com/kunwardeep/paralleltest v1.0.16 h1:I0gQkLNgfigjpxWAGhx7PYj3VpswzfGrSV/715t6g+o=
+github.com/kunwardeep/paralleltest v1.0.16/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk=
 github.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ=
 github.com/ldez/exptostd v0.4.5/go.mod h1:QRjHRMXJrCTIm9WxVNH6VW7oN7KrGSht69bIRwvdFsM=
 github.com/ldez/gomoddirectives v0.10.0 h1:Zb23rfMMkG21B6pZ9OfESzemByP5Qr7YvWcXQZE/S3Q=
@@ -431,16 +431,16 @@
 github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs=
 github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
 github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
-github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
-github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
+github.com/mattn/go-colorable v0.1.16 h1:+2YjeAe7xP1OQbZW1jERPWWCDLlNqFNCvjiqGiAt84A=
+github.com/mattn/go-colorable v0.1.16/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
 github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
 github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
 github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
 github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/mgechev/revive v1.17.0 h1:hwTTVC3svSv9X2FA2hiYT17Uws+lfxDWhPxzBZZCREg=
-github.com/mgechev/revive v1.17.0/go.mod h1:uiVNUF8ruVh4WHm/aPs7kKEeuo+m6oO3tuOfniyzP/g=
+github.com/mgechev/revive v1.17.1 h1:z8hV0L3uJ6yrwOMnJbkc5qBJpxXa2AxYKFKERGJyA4s=
+github.com/mgechev/revive v1.17.1/go.mod h1:FRd64pJwWSEE6j8ESoaX0PixPzjh2rpNSfckbEu3rIE=
 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@@ -460,10 +460,10 @@
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=
 github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=
-github.com/nishanths/exhaustive v0.13.0 h1:Rof0axOSNQaaPaPmLK5RxcRrJV2Vcv+vfUmuRy+2EAM=
-github.com/nishanths/exhaustive v0.13.0/go.mod h1:3/adQjJgvD/UFHHSYBjpK9Wbz8DOvf0FBtzx+zI0Rlo=
-github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=
-github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c=
+github.com/nishanths/exhaustive v0.15.0 h1:uFjiO/WvrwQG+ojSZ+vtl/jAFbqA2oAtfpUWK/iKQ8M=
+github.com/nishanths/exhaustive v0.15.0/go.mod h1:2by9rYmg9FnsnLQC4Ixig4fQaWxRsY7RluxJuAu9sa0=
+github.com/nishanths/predeclared v0.3.0 h1:W3i3JK/DZS5h7QwsEIyU8ler4Z2vwEwQSxNrZzhnlfk=
+github.com/nishanths/predeclared v0.3.0/go.mod h1:gN4uryPExDME/zmZdqkKIPSAqcfprj316ZDI6UTqz70=
 github.com/nunnatsa/ginkgolinter v0.24.0 h1:Mp0EagluLFP98JatP6nqp/gGEoljNG97uf9AcxcBVy8=
 github.com/nunnatsa/ginkgolinter v0.24.0/go.mod h1:2ZMRuzX6+3XXyY6UZOwb6n+MCocVGbkIsDBC4vuWz5c=
 github.com/onsi/ginkgo/v2 v2.32.1 h1:6tlvcDm/3sE8lGJbZ4+d4mO3RLy24/tQWOFzVSQNIfw=
@@ -591,20 +591,20 @@
 github.com/tetafro/godot v1.5.6/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU=
 github.com/timakin/bodyclose v0.0.0-20260723120731-857993a2939c h1:d/FUP8GD4QiY/rdFzZmBu3KqL9eBNVzMu7W/5pGkaMM=
 github.com/timakin/bodyclose v0.0.0-20260723120731-857993a2939c/go.mod h1:sDHLK7rb/59v/ZxZ7KtymgcoxuUMxjXq8gtu9VMOK8M=
-github.com/timonwong/loggercheck v0.12.0 h1:7Cb9XGLJZSWYNjgL459DIhpSiEHupD+MLQ6vaN2Pl3g=
-github.com/timonwong/loggercheck v0.12.0/go.mod h1:Wa6Y+SJHQR01mViZlLLY5GAZVOWOLNMbkXddMjFWQCs=
-github.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVFL27Of9is=
-github.com/tomarrell/wrapcheck/v2 v2.12.0/go.mod h1:AQhQuZd0p7b6rfW+vUwHm5OMCGgp63moQ9Qr/0BpIWo=
+github.com/timonwong/loggercheck v0.13.0 h1:6amIOmLLPFYt24rXUbNWzcJeNGuJc+3c31tZtO85VHs=
+github.com/timonwong/loggercheck v0.13.0/go.mod h1:thn3allpox0fI5JeUWTCwiwp45peQxCZZHkHuA1Oc6I=
+github.com/tomarrell/wrapcheck/v2 v2.13.0 h1:jYbLNs8PDkzGCY6d5LUzJdY7Bq//CTlb2HxZGV5z0JY=
+github.com/tomarrell/wrapcheck/v2 v2.13.0/go.mod h1:AQhQuZd0p7b6rfW+vUwHm5OMCGgp63moQ9Qr/0BpIWo=
 github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw=
 github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
 github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI=
 github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA=
 github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g=
 github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8=
-github.com/uudashr/gocognit v1.2.1 h1:CSJynt5txTnORn/DkhiB4mZjwPuifyASC8/6Q0I/QS4=
-github.com/uudashr/gocognit v1.2.1/go.mod h1:acaubQc6xYlXFEMb9nWX2dYBzJ/bIjEkc1zzvyIZg5Q=
-github.com/uudashr/iface v1.5.1 h1:BS3yrgaT55s3dAtJoxuTnsZNbsejq/mVuzp49YDyfBA=
-github.com/uudashr/iface v1.5.1/go.mod h1:5UWoT6SvTdTww/KToRj6clO+n4Kg3/NrAQvgOB+5Ggw=
+github.com/uudashr/gocognit v1.3.0 h1:Xdw5JwAoYmX3WAB+wsXY3nxEwdw7+MWAnM0wCAAv7A4=
+github.com/uudashr/gocognit v1.3.0/go.mod h1:acaubQc6xYlXFEMb9nWX2dYBzJ/bIjEkc1zzvyIZg5Q=
+github.com/uudashr/iface v1.5.3 h1:N61bzr11L7BHP6Rct+C6zQePHjSTMeyVBq+s03uB/Ds=
+github.com/uudashr/iface v1.5.3/go.mod h1:5UWoT6SvTdTww/KToRj6clO+n4Kg3/NrAQvgOB+5Ggw=
 github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM=
 github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4=
 github.com/xo/terminfo v1.0.0 h1:2ZpYzqWzyyytjk3TP6aJVDhkMAkc99/1xKQdA3TDTBY=
@@ -972,8 +972,8 @@
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-honnef.co/go/tools v0.8.1 h1:+JKf3xJ1ni4CwrhVg4/pqsfPGP6vNAXcKbMXJodYx3w=
-honnef.co/go/tools v0.8.1/go.mod h1:XA+OnlRA9EDh/ukGvXMNSZNKGwFQJ+5dER0ioUkOxks=
+honnef.co/go/tools v0.8.2 h1:+jPbCzuYxYrcoOvCecYUFDj+XJQRmzIhJmlbpps21Zk=
+honnef.co/go/tools v0.8.2/go.mod h1:xG1Q4n+mg3bKZrO6RWFA9jFlkqplflmDRHGeuexiLDs=
 mvdan.cc/gofumpt v0.12.0 h1:1Lbudkz2kpM9Cjz2pL4M19u7q+GaEhCTNf7N9mfpcho=
 mvdan.cc/gofumpt v0.12.0/go.mod h1:SmBHHrljiZu/uoypeKup3rFzP6eoC9UwCp2iH5E3jZA=
 mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8 h1:Re1NRyLpiAt9kB+ImaaoapwWiQXrKwER4tY8fLOkDew=
diff --git a/internal/internaltest/internaltest.go b/internal/internaltest/internaltest.go
index 4378263..f42e0a6 100644
--- a/internal/internaltest/internaltest.go
+++ b/internal/internaltest/internaltest.go
@@ -4,6 +4,7 @@
 
 // Compile and run panic fixtures and retrieve recorded goroutine snapshots.
 
+// Package internaltest builds the panic fixtures used by integration tests.
 package internaltest
 
 import (
@@ -60,9 +61,7 @@
 		}()
 
 		prace := build("panic", true)
-		if prace == "" {
-			// Race detector is not supported on this platform.
-		} else {
+		if prace != "" {
 			defer func() {
 				if err := os.Remove(prace); err != nil {
 					panic(err)
@@ -89,7 +88,7 @@
 				p = prace
 			}
 			if panicOutputs[cmd] = execRun(p, cmd); len(panicOutputs[cmd]) == 0 {
-				panic(fmt.Sprintf("no output for %s", cmd))
+				panic("no output for " + cmd)
 			}
 		}
 	})
@@ -170,7 +169,8 @@
 		args = append(args, "-race")
 	}
 	/* #nosec G204 */
-	c := exec.Command("go", append(args, in)...)
+	// The helper API has no caller context and waits for compilation.
+	c := exec.Command("go", append(args, in)...) //nolint:noctx // Synchronous fixture helper.
 	c.Dir = cwd
 	if out, err := c.CombinedOutput(); err != nil {
 		if race {
@@ -192,7 +192,8 @@
 // design.
 func execRun(cmd ...string) []byte {
 	/* #nosec G204 */
-	c := exec.Command(cmd[0], cmd[1:]...)
+	// The helper API has no caller context and waits for the fixture.
+	c := exec.Command(cmd[0], cmd[1:]...) //nolint:noctx // Synchronous fixture helper.
 	c.Env = append(os.Environ(), "GOTRACEBACK=all")
 	out, _ := c.CombinedOutput()
 	return out
diff --git a/internal/main.go b/internal/main.go
index cb90667..e20dc24 100644
--- a/internal/main.go
+++ b/internal/main.go
@@ -106,7 +106,7 @@
 }
 
 type toHTMLer interface {
-	ToHTML(io.Writer, template.HTML) error
+	ToHTML(w io.Writer, header template.HTML) error
 }
 
 func toHTML(h toHTMLer, p string, needsEnv bool) error {
@@ -126,7 +126,7 @@
 	return err
 }
 
-func processInner(out io.Writer, p *Palette, s stack.Similarity, pf pathFormat, html string, filter, match *regexp.Regexp, c *stack.Snapshot, first bool) error {
+func processInner(out io.Writer, p *Palette, s stack.Similarity, pf pathFormat, html string, filter, match *regexp.Regexp, c *stack.Snapshot, _ bool) error {
 	log.Printf("GOROOT=%s", c.RemoteGOROOT)
 	log.Printf("GOPATH=%s", c.RemoteGOPATHs)
 	needsEnv := len(c.Goroutines) == 1 && showBanner()
@@ -175,7 +175,7 @@
 				err = err1
 			}
 		}
-		if err == io.EOF {
+		if errors.Is(err, io.EOF) {
 			return nil
 		}
 		// Parts of the input will be lost.
@@ -216,27 +216,27 @@
 		} else {
 			out = colorable.NewColorableStderr()
 		}
-		fmt.Fprintf(out, "Usage of %s:\n", os.Args[0])
+		_, _ = fmt.Fprintf(out, "Usage of %s:\n", os.Args[0])
 		flag.CommandLine.SetOutput(out)
 		flag.CommandLine.PrintDefaults()
-		fmt.Fprintf(out, "\nLegend:\n")
-		fmt.Fprintf(out, "  Type             Exported    Private\n")
-		fmt.Fprintf(out, "  main             %smain.Foo()%s  %smain.foo()%s\n",
+		_, _ = fmt.Fprintf(out, "\nLegend:\n")
+		_, _ = fmt.Fprintf(out, "  Type             Exported    Private\n")
+		_, _ = fmt.Fprintf(out, "  main             %smain.Foo()%s  %smain.foo()%s\n",
 			p.funcColor(stack.LocationUnknown, true, false), p.EOLReset,
 			p.funcColor(stack.LocationUnknown, true, true), p.EOLReset)
-		fmt.Fprintf(out, "  <unknown>        %spkg.Foo()%s   %spkg.foo()%s\n",
+		_, _ = fmt.Fprintf(out, "  <unknown>        %spkg.Foo()%s   %spkg.foo()%s\n",
 			p.funcColor(stack.LocationUnknown, false, false), p.EOLReset,
 			p.funcColor(stack.LocationUnknown, false, true), p.EOLReset)
-		fmt.Fprintf(out, "  go.mod           %spkg.Foo()%s   %spkg.foo()%s\n",
+		_, _ = fmt.Fprintf(out, "  go.mod           %spkg.Foo()%s   %spkg.foo()%s\n",
 			p.funcColor(stack.GoMod, false, false), p.EOLReset,
 			p.funcColor(stack.GoMod, false, true), p.EOLReset)
-		fmt.Fprintf(out, "  $GOPATH/src      %spkg.Foo()%s   %spkg.foo()%s\n",
+		_, _ = fmt.Fprintf(out, "  $GOPATH/src      %spkg.Foo()%s   %spkg.foo()%s\n",
 			p.funcColor(stack.GOPATH, false, false), p.EOLReset,
 			p.funcColor(stack.GOPATH, false, true), p.EOLReset)
-		fmt.Fprintf(out, "  $GOPATH/pkg/mod  %spkg.Foo()%s   %spkg.foo()%s\n",
+		_, _ = fmt.Fprintf(out, "  $GOPATH/pkg/mod  %spkg.Foo()%s   %spkg.foo()%s\n",
 			p.funcColor(stack.GoPkg, false, false), p.EOLReset,
 			p.funcColor(stack.GoPkg, false, true), p.EOLReset)
-		fmt.Fprintf(out, "  $GOROOT/src      %spkg.Foo()%s   %spkg.Foo()%s\n",
+		_, _ = fmt.Fprintf(out, "  $GOROOT/src      %spkg.Foo()%s   %spkg.Foo()%s\n",
 			p.funcColor(stack.Stdlib, false, false), p.EOLReset,
 			p.funcColor(stack.Stdlib, false, true), p.EOLReset)
 	}
@@ -297,7 +297,11 @@
 			return fmt.Errorf("did you mean to specify a valid stack dump file name? %w", err)
 		}
 		/* #nosec G307 */
-		defer in.Close()
+		defer func() {
+			if err := in.Close(); err != nil {
+				log.Printf("close input: %v", err)
+			}
+		}()
 
 	default:
 		return errors.New("pipe from stdin or specify a single file")
diff --git a/internal/main_test.go b/internal/main_test.go
index 3c82202..766e32c 100644
--- a/internal/main_test.go
+++ b/internal/main_test.go
@@ -43,7 +43,7 @@
 			palette: testPalette,
 			simil:   stack.AnyPointer,
 			path:    basePath,
-			want:    "GOTRACEBACK=all\npanic: simple\n\nC1: runningA\n    Emain Fmain.go:73 GmainR()A\n\n",
+			want:    "GOTRACEBACK=all\npanic: simple\n\nC1: runningA\n    Emain Fmain.go:75 GmainR()A\n\n",
 		},
 		{
 			name:    "FullPath",
@@ -51,14 +51,14 @@
 			simil:   stack.AnyValue,
 			path:    fullPath,
 			// "/" is used even on Windows.
-			want: fmt.Sprintf("GOTRACEBACK=all\npanic: simple\n\nC1: runningA\n    Emain F%s:73 GmainR()A\n\n", strings.Replace(filepath.Join(filepath.Dir(d), "cmd", "panic", "main.go"), "\\", "/", -1)),
+			want: fmt.Sprintf("GOTRACEBACK=all\npanic: simple\n\nC1: runningA\n    Emain F%s:75 GmainR()A\n\n", strings.ReplaceAll(filepath.Join(filepath.Dir(d), "cmd", "panic", "main.go"), "\\", "/")),
 		},
 		{
 			name:    "NoColor",
 			palette: &Palette{},
 			simil:   stack.AnyValue,
 			path:    basePath,
-			want:    "GOTRACEBACK=all\npanic: simple\n\n1: running\n    main main.go:73 main()\n\n",
+			want:    "GOTRACEBACK=all\npanic: simple\n\n1: running\n    main main.go:75 main()\n\n",
 		},
 		{
 			name:    "Match",
@@ -74,11 +74,10 @@
 			simil:   stack.AnyValue,
 			path:    basePath,
 			filter:  regexp.MustCompile(`notpresent`),
-			want:    "GOTRACEBACK=all\npanic: simple\n\nC1: runningA\n    Emain Fmain.go:73 GmainR()A\n\n",
+			want:    "GOTRACEBACK=all\npanic: simple\n\nC1: runningA\n    Emain Fmain.go:75 GmainR()A\n\n",
 		},
 	}
 	for i, line := range data {
-		line := line
 		t.Run(fmt.Sprintf("%d-%s", i, line.name), func(t *testing.T) {
 			t.Parallel()
 			out := bytes.Buffer{}
@@ -109,14 +108,14 @@
 		"GOTRACEBACK=all\n" +
 		"panic: simple\n\n" +
 		"1: running\n" +
-		"    main main.go:73 main()\n\n" +
+		"    main main.go:75 main()\n\n" +
 		"Ye\n" +
 		"GOTRACEBACK=all\n" +
 		"panic: 42\n\n" +
 		"1: running\n" +
-		"    main main.go:92  panicint(0x2a)\n" +
-		"    main main.go:319 init.func9()\n" +
-		"    main main.go:75  main()\n\n" +
+		"    main main.go:94  panicint(0x2a)\n" +
+		"    main main.go:321 init.func9()\n" +
+		"    main main.go:77  main()\n\n" +
 		"Yo\n")
 	compareString(t, want, out.String())
 }
@@ -144,6 +143,8 @@
 		log.SetOutput(io.Discard)
 	}
 	// Set the environment variable so the stack doesn't include the info header.
-	os.Setenv("GOTRACEBACK", "all")
+	if err := os.Setenv("GOTRACEBACK", "all"); err != nil {
+		log.Fatal(err)
+	}
 	os.Exit(m.Run())
 }
diff --git a/internal/ui.go b/internal/ui.go
index 104b890..928b267 100644
--- a/internal/ui.go
+++ b/internal/ui.go
@@ -63,6 +63,8 @@
 			return fmt.Sprintf("%s:%d", c.LocalSrcPath, c.Line)
 		}
 		return fmt.Sprintf("%s:%d", c.RemoteSrcPath, c.Line)
+	case basePath:
+		return fmt.Sprintf("%s:%d", c.SrcName, c.Line)
 	default:
 		return fmt.Sprintf("%s:%d", c.SrcName, c.Line)
 	}
@@ -111,6 +113,65 @@
 	return srcLen, pkgLen
 }
 
+// StackLines prints one complete stack trace, without the header.
+func (p *Palette) StackLines(signature *stack.Signature, srcLen, pkgLen int, pf pathFormat) string {
+	// Populate indexed entries before optionally appending the elision marker.
+	out := make([]string, len(signature.Stack.Calls))
+	for i := range signature.Stack.Calls {
+		out[i] = p.callLine(&signature.Stack.Calls[i], srcLen, pkgLen, pf)
+	}
+	if signature.Stack.Elided {
+		//nolint:makezero // Existing entries were populated by index.
+		out = append(out, "    (...)")
+	}
+	return strings.Join(out, "\n") + "\n"
+}
+
+// GoroutineHeader prints the header of a goroutine.
+func (p *Palette) GoroutineHeader(g *stack.Goroutine, pf pathFormat, multipleGoroutines bool) string {
+	extra := ""
+	if s := g.SleepString(); s != "" {
+		extra += " [" + s + "]"
+	}
+	if g.Locked {
+		extra += " [locked]"
+	}
+	if c := pf.createdByString(&g.Signature); c != "" {
+		extra += p.CreatedBy + " [Created by " + c + "]"
+	}
+	if g.RaceAddr != 0 {
+		r := "read"
+		if g.RaceWrite {
+			r = "write"
+		}
+		extra += fmt.Sprintf("%s%s Race %s @ 0x%08x", p.EOLReset, p.Race, r, g.RaceAddr)
+	}
+	return fmt.Sprintf(
+		"%s%d: %s%s%s\n",
+		p.routineColor(g.First, multipleGoroutines), g.ID,
+		g.State, extra,
+		p.EOLReset)
+}
+
+// BucketHeader prints the header of a goroutine signature.
+func (p *Palette) BucketHeader(b *stack.Bucket, pf pathFormat, multipleBuckets bool) string {
+	extra := ""
+	if s := b.SleepString(); s != "" {
+		extra += " [" + s + "]"
+	}
+	if b.Locked {
+		extra += " [locked]"
+	}
+	if c := pf.createdByString(&b.Signature); c != "" {
+		extra += p.CreatedBy + " [Created by " + c + "]"
+	}
+	return fmt.Sprintf(
+		"%s%d: %s%s%s\n",
+		p.routineColor(b.First, multipleBuckets), len(b.IDs),
+		b.State, extra,
+		p.EOLReset)
+}
+
 // functionColor returns the color to be used for the function name based on
 // the type of package the function is in.
 func (p *Palette) functionColor(c *stack.Call) string {
@@ -160,51 +221,6 @@
 	return p.Routine
 }
 
-// BucketHeader prints the header of a goroutine signature.
-func (p *Palette) BucketHeader(b *stack.Bucket, pf pathFormat, multipleBuckets bool) string {
-	extra := ""
-	if s := b.SleepString(); s != "" {
-		extra += " [" + s + "]"
-	}
-	if b.Locked {
-		extra += " [locked]"
-	}
-	if c := pf.createdByString(&b.Signature); c != "" {
-		extra += p.CreatedBy + " [Created by " + c + "]"
-	}
-	return fmt.Sprintf(
-		"%s%d: %s%s%s\n",
-		p.routineColor(b.First, multipleBuckets), len(b.IDs),
-		b.State, extra,
-		p.EOLReset)
-}
-
-// GoroutineHeader prints the header of a goroutine.
-func (p *Palette) GoroutineHeader(g *stack.Goroutine, pf pathFormat, multipleGoroutines bool) string {
-	extra := ""
-	if s := g.SleepString(); s != "" {
-		extra += " [" + s + "]"
-	}
-	if g.Locked {
-		extra += " [locked]"
-	}
-	if c := pf.createdByString(&g.Signature); c != "" {
-		extra += p.CreatedBy + " [Created by " + c + "]"
-	}
-	if g.RaceAddr != 0 {
-		r := "read"
-		if g.RaceWrite {
-			r = "write"
-		}
-		extra += fmt.Sprintf("%s%s Race %s @ 0x%08x", p.EOLReset, p.Race, r, g.RaceAddr)
-	}
-	return fmt.Sprintf(
-		"%s%d: %s%s%s\n",
-		p.routineColor(g.First, multipleGoroutines), g.ID,
-		g.State, extra,
-		p.EOLReset)
-}
-
 // callLine prints one stack line.
 func (p *Palette) callLine(line *stack.Call, srcLen, pkgLen int, pf pathFormat) string {
 	return fmt.Sprintf(
@@ -215,15 +231,3 @@
 		p.Arguments, &line.Args,
 		p.EOLReset)
 }
-
-// StackLines prints one complete stack trace, without the header.
-func (p *Palette) StackLines(signature *stack.Signature, srcLen, pkgLen int, pf pathFormat) string {
-	out := make([]string, len(signature.Stack.Calls))
-	for i := range signature.Stack.Calls {
-		out[i] = p.callLine(&signature.Stack.Calls[i], srcLen, pkgLen, pf)
-	}
-	if signature.Stack.Elided {
-		out = append(out, "    (...)")
-	}
-	return strings.Join(out, "\n") + "\n"
-}
diff --git a/internal/ui_test.go b/internal/ui_test.go
index 018bf87..a63bd97 100644
--- a/internal/ui_test.go
+++ b/internal/ui_test.go
@@ -41,11 +41,9 @@
 	a := stack.Aggregated{
 		Buckets: []*stack.Bucket{
 			{
-				Signature: stack.Signature{
-					Stack: stack.Stack{
-						Calls: []stack.Call{
-							newCallLocal("main.func·001", stack.Args{}, "/home/user/go/src/foo/baz.go", 123),
-						},
+				Stack: stack.Stack{
+					Calls: []stack.Call{
+						newCallLocal("main.func·001", stack.Args{}, "/home/user/go/src/foo/baz.go", 123),
 					},
 				},
 				IDs:   []int{},
@@ -70,18 +68,16 @@
 func TestBucketHeader(t *testing.T) {
 	t.Parallel()
 	b := stack.Bucket{
-		Signature: stack.Signature{
-			State: "chan receive",
-			CreatedBy: stack.Stack{
-				Calls: []stack.Call{
-					newCallLocal("main.mainImpl", stack.Args{}, "/home/user/go/src/github.com/foo/bar/baz.go", 74),
-				},
+		State: "chan receive",
+		CreatedBy: stack.Stack{
+			Calls: []stack.Call{
+				newCallLocal("main.mainImpl", stack.Args{}, "/home/user/go/src/github.com/foo/bar/baz.go", 74),
 			},
-			SleepMax: 6,
-			SleepMin: 2,
 		},
-		IDs:   []int{1, 2},
-		First: true,
+		SleepMax: 6,
+		SleepMin: 2,
+		IDs:      []int{1, 2},
+		First:    true,
 	}
 	// When printing, it prints the remote path, not the transposed local path.
 	compareString(t, "B2: chan receive [2~6 minutes]D [Created by main.mainImpl @ /home/user/go/src/github.com/foo/bar/baz.go:74]A\n", testPalette.BucketHeader(&b, fullPath, true))
@@ -92,14 +88,12 @@
 	compareString(t, "C2: chan receive [2~6 minutes]D [Created by main.mainImpl @ baz.go:74]A\n", testPalette.BucketHeader(&b, basePath, false))
 
 	b = stack.Bucket{
-		Signature: stack.Signature{
-			State:    "b0rked",
-			SleepMax: 6,
-			SleepMin: 6,
-			Locked:   true,
-		},
-		IDs:   []int{},
-		First: true,
+		State:    "b0rked",
+		SleepMax: 6,
+		SleepMin: 6,
+		Locked:   true,
+		IDs:      []int{},
+		First:    true,
 	}
 	compareString(t, "C0: b0rked [6 minutes] [locked]A\n", testPalette.BucketHeader(&b, basePath, false))
 }
@@ -191,15 +185,16 @@
 	const gopath = "/home/user/go/src/"
 	const gopathmod = "/home/user/go/pkg/mod/"
 	// Do the equivalent of Call.updateLocations().
-	if strings.HasPrefix(s, goroot) {
+	switch {
+	case strings.HasPrefix(s, goroot):
 		c.LocalSrcPath = s
 		c.RelSrcPath = s[len(goroot):]
 		c.Location = stack.Stdlib
-	} else if strings.HasPrefix(s, gopath) {
+	case strings.HasPrefix(s, gopath):
 		c.LocalSrcPath = s
 		c.RelSrcPath = s[len(gopath):]
 		c.Location = stack.GOPATH
-	} else if strings.HasPrefix(s, gopathmod) {
+	case strings.HasPrefix(s, gopathmod):
 		c.LocalSrcPath = s
 		c.RelSrcPath = s[len(gopathmod):]
 		c.Location = stack.GoPkg
diff --git a/stack/bucket.go b/stack/bucket.go
index a3e5a81..309bc3b 100644
--- a/stack/bucket.go
+++ b/stack/bucket.go
@@ -104,6 +104,7 @@
 type Bucket struct {
 	// Signature is the generalized signature for this bucket.
 	Signature
+
 	// IDs is the ID of each Goroutine with this Signature.
 	IDs []int
 	// First is true if this Bucket contains the first goroutine, e.g. the one
diff --git a/stack/bucket_test.go b/stack/bucket_test.go
index 82c7bab..1bc4761 100644
--- a/stack/bucket_test.go
+++ b/stack/bucket_test.go
@@ -8,6 +8,7 @@
 
 import (
 	"bytes"
+	"errors"
 	"io"
 	"strings"
 	"testing"
@@ -32,7 +33,7 @@
 		"",
 	}
 	s, suffix, err := ScanSnapshot(bytes.NewBufferString(strings.Join(data, "\n")), io.Discard, defaultOpts())
-	if err != io.EOF {
+	if !errors.Is(err, io.EOF) {
 		t.Fatal(err)
 	}
 	if s == nil {
@@ -40,42 +41,38 @@
 	}
 	want := []*Bucket{
 		{
-			Signature: Signature{
-				State: "chan receive",
-				Stack: Stack{
-					Calls: []Call{
-						newCall(
-							"main.func·001",
-							Args{Values: []Arg{
-								{IsAggregate: true, Fields: Args{
-									Values: []Arg{{Value: 0x11000000, IsPtr: true}, {Value: 2}},
-								}},
-								{Value: 3},
+			State: "chan receive",
+			Stack: Stack{
+				Calls: []Call{
+					newCall(
+						"main.func·001",
+						Args{Values: []Arg{
+							{IsAggregate: true, Fields: Args{
+								Values: []Arg{{Value: 0x11000000, IsPtr: true}, {Value: 2}},
 							}},
-							"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-							72),
-					},
+							{Value: 3},
+						}},
+						"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+						72),
 				},
 			},
 			IDs:   []int{6},
 			First: true,
 		},
 		{
-			Signature: Signature{
-				State: "chan receive",
-				Stack: Stack{
-					Calls: []Call{
-						newCall(
-							"main.func·001",
-							Args{Values: []Arg{
-								{IsAggregate: true, Fields: Args{
-									Values: []Arg{{Value: 0x21000000, Name: "#1", IsPtr: true}, {Value: 2}},
-								}},
-								{Value: 3},
+			State: "chan receive",
+			Stack: Stack{
+				Calls: []Call{
+					newCall(
+						"main.func·001",
+						Args{Values: []Arg{
+							{IsAggregate: true, Fields: Args{
+								Values: []Arg{{Value: 0x21000000, Name: "#1", IsPtr: true}, {Value: 2}},
 							}},
-							"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-							72),
-					},
+							{Value: 3},
+						}},
+						"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+						72),
 				},
 			},
 			IDs: []int{7},
@@ -109,7 +106,7 @@
 		"",
 	}
 	s, suffix, err := ScanSnapshot(bytes.NewBufferString(strings.Join(data, "\n")), io.Discard, defaultOpts())
-	if err != io.EOF {
+	if !errors.Is(err, io.EOF) {
 		t.Fatal(err)
 	}
 	if s == nil {
@@ -117,30 +114,28 @@
 	}
 	want := []*Bucket{
 		{
-			Signature: Signature{
-				State: "chan receive",
-				CreatedBy: Stack{
-					Calls: []Call{
-						newCall(
-							"main.mainImpl",
-							Args{},
-							"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-							74),
-					},
+			State: "chan receive",
+			CreatedBy: Stack{
+				Calls: []Call{
+					newCall(
+						"main.mainImpl",
+						Args{},
+						"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+						74),
 				},
-				Stack: Stack{
-					Calls: []Call{
-						newCall(
-							"main.func·001",
-							Args{Values: []Arg{
-								{IsAggregate: true, Fields: Args{
-									Values: []Arg{{Value: 0x11000000, Name: "#1", IsPtr: true}, {Value: 2}},
-								}},
-								{Value: 3},
+			},
+			Stack: Stack{
+				Calls: []Call{
+					newCall(
+						"main.func·001",
+						Args{Values: []Arg{
+							{IsAggregate: true, Fields: Args{
+								Values: []Arg{{Value: 0x11000000, Name: "#1", IsPtr: true}, {Value: 2}},
 							}},
-							"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-							72),
-					},
+							{Value: 3},
+						}},
+						"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+						72),
 				},
 			},
 			IDs:   []int{6, 7},
@@ -171,7 +166,7 @@
 		"",
 	}
 	s, suffix, err := ScanSnapshot(bytes.NewBufferString(strings.Join(data, "\n")), io.Discard, defaultOpts())
-	if err != io.EOF {
+	if !errors.Is(err, io.EOF) {
 		t.Fatal(err)
 	}
 	if s == nil {
@@ -179,23 +174,21 @@
 	}
 	want := []*Bucket{
 		{
-			Signature: Signature{
-				State:    "chan receive",
-				SleepMin: 10,
-				SleepMax: 100,
-				Stack: Stack{
-					Calls: []Call{
-						newCall(
-							"main.func·001",
-							Args{Values: []Arg{
-								{IsAggregate: true, Fields: Args{
-									Values: []Arg{{Value: 0x11000000, Name: "*", IsPtr: true}, {Value: 2}},
-								}},
-								{Value: 3},
+			State:    "chan receive",
+			SleepMin: 10,
+			SleepMax: 100,
+			Stack: Stack{
+				Calls: []Call{
+					newCall(
+						"main.func·001",
+						Args{Values: []Arg{
+							{IsAggregate: true, Fields: Args{
+								Values: []Arg{{Value: 0x11000000, Name: "*", IsPtr: true}, {Value: 2}},
 							}},
-							"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-							72),
-					},
+							{Value: 3},
+						}},
+						"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+						72),
 				},
 			},
 			IDs:   []int{6, 7, 8},
@@ -235,7 +228,7 @@
 		"  foo/foo.go:631 +0x4b",
 	}
 	s, suffix, err := ScanSnapshot(bytes.NewBufferString(strings.Join(data, "\n")), io.Discard, defaultOpts())
-	if err != io.EOF {
+	if !errors.Is(err, io.EOF) {
 		t.Fatal(err)
 	}
 	if s == nil {
@@ -243,41 +236,39 @@
 	}
 	want := []*Bucket{
 		{
-			Signature: Signature{
-				State: "select",
-				CreatedBy: Stack{Calls: []Call{
+			State: "select",
+			CreatedBy: Stack{Calls: []Call{
+				{
+					Func:          Func{Complete: "main", Name: "main"},
+					RemoteSrcPath: "foo/foo.go",
+					Line:          631,
+					SrcName:       "foo.go",
+				},
+			}},
+			SleepMin: 55,
+			SleepMax: 55,
+			Stack: Stack{
+				Calls: []Call{
 					{
-						Func:          Func{Complete: "main", Name: "main"},
+						Func: Func{
+							Complete:   "foo.Bar",
+							ImportPath: "foo",
+							DirName:    "foo",
+							Name:       "Bar",
+							IsExported: true,
+						},
 						RemoteSrcPath: "foo/foo.go",
-						Line:          631,
+						Line:          467,
 						SrcName:       "foo.go",
+						ImportPath:    "foo",
 					},
-				}},
-				SleepMin: 55,
-				SleepMax: 55,
-				Stack: Stack{
-					Calls: []Call{
-						{
-							Func: Func{
-								Complete:   "foo.Bar",
-								ImportPath: "foo",
-								DirName:    "foo",
-								Name:       "Bar",
-								IsExported: true,
-							},
-							RemoteSrcPath: "foo/foo.go",
-							Line:          467,
-							SrcName:       "foo.go",
-							ImportPath:    "foo",
-						},
-						{
-							Func:          Func{Complete: "foo.baz", ImportPath: "foo", DirName: "foo", Name: "baz"},
-							Args:          Args{Values: []Arg{{Value: 3}}},
-							RemoteSrcPath: "foo/foo.go",
-							Line:          643,
-							SrcName:       "foo.go",
-							ImportPath:    "foo",
-						},
+					{
+						Func:          Func{Complete: "foo.baz", ImportPath: "foo", DirName: "foo", Name: "baz"},
+						Args:          Args{Values: []Arg{{Value: 3}}},
+						RemoteSrcPath: "foo/foo.go",
+						Line:          643,
+						SrcName:       "foo.go",
+						ImportPath:    "foo",
 					},
 				},
 			},
@@ -285,76 +276,72 @@
 			First: true,
 		},
 		{
-			Signature: Signature{
-				State: "select",
-				CreatedBy: Stack{Calls: []Call{
+			State: "select",
+			CreatedBy: Stack{Calls: []Call{
+				{
+					Func:          Func{Complete: "main", Name: "main"},
+					RemoteSrcPath: "foo/foo.go",
+					Line:          631,
+					SrcName:       "foo.go",
+				},
+			}},
+			SleepMin: 55,
+			SleepMax: 55,
+			Stack: Stack{
+				Calls: []Call{
 					{
-						Func:          Func{Complete: "main", Name: "main"},
+						Func: Func{
+							Complete:   "foo.Bar",
+							ImportPath: "foo",
+							DirName:    "foo",
+							Name:       "Bar",
+							IsExported: true,
+						},
 						RemoteSrcPath: "foo/foo.go",
-						Line:          631,
+						Line:          467,
 						SrcName:       "foo.go",
+						ImportPath:    "foo",
 					},
-				}},
-				SleepMin: 55,
-				SleepMax: 55,
-				Stack: Stack{
-					Calls: []Call{
-						{
-							Func: Func{
-								Complete:   "foo.Bar",
-								ImportPath: "foo",
-								DirName:    "foo",
-								Name:       "Bar",
-								IsExported: true,
-							},
-							RemoteSrcPath: "foo/foo.go",
-							Line:          467,
-							SrcName:       "foo.go",
-							ImportPath:    "foo",
-						},
-						{
-							Func:          Func{Complete: "foo.baz", ImportPath: "foo", DirName: "foo", Name: "baz"},
-							Args:          Args{Values: []Arg{{Value: 1}}},
-							RemoteSrcPath: "foo/foo.go",
-							Line:          643,
-							SrcName:       "foo.go",
-							ImportPath:    "foo",
-						},
+					{
+						Func:          Func{Complete: "foo.baz", ImportPath: "foo", DirName: "foo", Name: "baz"},
+						Args:          Args{Values: []Arg{{Value: 1}}},
+						RemoteSrcPath: "foo/foo.go",
+						Line:          643,
+						SrcName:       "foo.go",
+						ImportPath:    "foo",
 					},
 				},
 			},
 			IDs: []int{55},
 		},
 		{
-			Signature: Signature{
-				State: "select",
-				CreatedBy: Stack{
-					Calls: []Call{
-						{
-							Func:          Func{Complete: "bozo", Name: "bozo"},
-							RemoteSrcPath: "foo/foo.go",
-							Line:          420,
-							SrcName:       "foo.go",
-						},
+			State: "select",
+			CreatedBy: Stack{
+				Calls: []Call{
+					{
+						Func:          Func{Complete: "bozo", Name: "bozo"},
+						RemoteSrcPath: "foo/foo.go",
+						Line:          420,
+						SrcName:       "foo.go",
 					},
 				},
-				SleepMin: 55,
-				SleepMax: 55,
-				Stack: Stack{
-					Calls: []Call{
-						{
-							Func: Func{
-								Complete:   "foo.Bar",
-								ImportPath: "foo",
-								DirName:    "foo",
-								Name:       "Bar",
-								IsExported: true,
-							},
-							RemoteSrcPath: "foo/foo.go",
-							Line:          467,
-							SrcName:       "foo.go",
-							ImportPath:    "foo",
+			},
+			SleepMin: 55,
+			SleepMax: 55,
+			Stack: Stack{
+				Calls: []Call{
+					{
+						Func: Func{
+							Complete:   "foo.Bar",
+							ImportPath: "foo",
+							DirName:    "foo",
+							Name:       "Bar",
+							IsExported: true,
 						},
+						RemoteSrcPath: "foo/foo.go",
+						Line:          467,
+						SrcName:       "foo.go",
+						ImportPath:    "foo",
 					},
 				},
 			},
@@ -368,7 +355,7 @@
 func BenchmarkAggregate(b *testing.B) {
 	b.ReportAllocs()
 	s, suffix, err := ScanSnapshot(bytes.NewReader(internaltest.StaticPanicwebOutput()), io.Discard, defaultOpts())
-	if err != io.EOF {
+	if !errors.Is(err, io.EOF) {
 		b.Fatal(err)
 	}
 	if s == nil {
@@ -378,7 +365,7 @@
 		b.Fatalf("unexpected suffix: %q", string(suffix))
 	}
 	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
+	for range b.N {
 		buckets := s.Aggregate(AnyPointer).Buckets
 		if len(buckets) < 5 {
 			b.Fatal("expected more buckets")
diff --git a/stack/context.go b/stack/context.go
index 88a9df4..15adba7 100644
--- a/stack/context.go
+++ b/stack/context.go
@@ -69,7 +69,7 @@
 func DefaultOpts() *Opts {
 	p := build.Default.GOROOT
 	if runtime.GOOS == "windows" {
-		p = strings.Replace(p, pathSeparator, "/", -1)
+		p = strings.ReplaceAll(p, pathSeparator, "/")
 	}
 	return &Opts{
 		LocalGOROOT:    p,
@@ -181,7 +181,7 @@
 		var d []byte
 		if d, err = r.readLine(); len(d) != 0 {
 			l, err1 := s.scan(d)
-			if err1 != nil && (err == nil || err == io.EOF) {
+			if err1 != nil && (err == nil || errors.Is(err, io.EOF)) {
 				err = err1
 			}
 			if !l {
@@ -190,7 +190,7 @@
 					suffix = append(suffix, r.buffered()...)
 					break
 				}
-				if _, err1 = prefix.Write(d); err1 != nil && (err == nil || err == io.EOF) {
+				if _, err1 = prefix.Write(d); err1 != nil && (err == nil || errors.Is(err, io.EOF)) {
 					err = err1
 					break
 				}
@@ -257,9 +257,9 @@
 
 var (
 	lockedToThread = []byte("locked to thread")
-	// gotRaceHeader1, done
+	// gotRaceHeader1, done.
 	raceHeaderFooter = []byte("==================")
-	// gotRaceHeader2
+	// gotRaceHeader2.
 	raceHeader             = []byte("WARNING: DATA RACE")
 	crlf                   = []byte("\r\n")
 	lf                     = []byte("\n")
@@ -273,11 +273,11 @@
 
 // These are effectively constants.
 var (
-	// gotRoutineHeader
+	// gotRoutineHeader.
 	reRoutineHeader = regexp.MustCompile("^([ \t]*)goroutine (\\d+)(?: gp=[^ ]+ m=[^ ]+(?: mp=[^ ]+)?)? \\[([^\\]]+)\\]\\:$")
 	reMinutes       = regexp.MustCompile(`^(\d+) minutes$`)
 
-	// gotUnavail
+	// gotUnavail.
 	reUnavail = regexp.MustCompile("^(?:\t| +)goroutine running on other thread; stack unavailable")
 
 	// gotFileFunc, gotRaceOperationFile, gotRaceGoroutineFile
@@ -304,7 +304,7 @@
 	// parenthood.
 	reCreated = regexp.MustCompile("^created by (.+)$")
 
-	// gotFunc, gotRaceOperationFunc, gotRaceGoroutineFunc
+	// gotFunc, gotRaceOperationFunc, gotRaceGoroutineFunc.
 	reFunc = regexp.MustCompile(`^(.+)\((.*)\)$`)
 
 	// Race:
@@ -313,19 +313,19 @@
 	//   #else  // #if !SANITIZER_GO
 	// is used.
 	// TODO(maruel): "    [failed to restore the stack]\n\n"
-	// TODO(maruel): "Global var %s of size %zu at %p declared at %s:%zu\n"
+	// TODO(maruel): "Global var %s of size %zu at %p declared at %s:%zu\n".
 
-	// gotRaceOperationHeader
+	// gotRaceOperationHeader.
 	reRaceOperationHeader = regexp.MustCompile(`^(Read|Write) at (0x[0-9a-f]+) by goroutine (\d+):$`)
 
-	// gotRaceOperationHeader
+	// gotRaceOperationHeader.
 	reRacePreviousOperationHeader = regexp.MustCompile(`^Previous (read|write) at (0x[0-9a-f]+) by goroutine (\d+):$`)
 
-	// gotRaceGoroutineHeader
+	// gotRaceGoroutineHeader.
 	reRaceGoroutine = regexp.MustCompile(`^Goroutine (\d+) \((running|finished)\) created at:$`)
 
 	// TODO(maruel): Use it.
-	//reRacePreviousOperationMainHeader = regexp.MustCompile("^Previous (read|write) at (0x[0-9a-f]+) by main goroutine:$")
+	// reRacePreviousOperationMainHeader = regexp.MustCompile("^Previous (read|write) at (0x[0-9a-f]+) by main goroutine:$").
 )
 
 // state is the state of the scan to detect and process a stack trace.
@@ -340,7 +340,7 @@
 	// Done processing a stack trace.
 	done
 
-	// Panic stack trace:
+	// Panic stack trace:.
 
 	// Signature: ""
 	// An empty line between goroutines.
@@ -384,7 +384,7 @@
 	// to: betweenRoutine, gotCreated
 	gotUnavail
 
-	// Race detector:
+	// Race detector:.
 
 	// Constant: raceHeaderFooter
 	// Signature: "=================="
@@ -449,6 +449,7 @@
 // and stores the traces found.
 type scanningState struct {
 	*Snapshot
+
 	state          state
 	prefix         []byte
 	goroutineIndex int
@@ -473,7 +474,7 @@
 // TODO(maruel): Handle corrupted stack cases:
 // - missed stack barrier
 // - found next stack barrier at 0x123; expected
-// - runtime: unexpected return pc for FUNC_NAME called from 0x123
+// - runtime: unexpected return pc for FUNC_NAME called from 0x123.
 func (s *scanningState) scan(line []byte) (bool, error) {
 	/* This is very useful to debug issues in the state machine.
 	defer func() {
@@ -485,17 +486,14 @@
 		cur = s.Goroutines[len(s.Goroutines)-1]
 	}
 	trimmed := line
-	if bytes.HasSuffix(line, crlf) {
+	switch {
+	case bytes.HasSuffix(line, crlf):
 		trimmed = line[:len(line)-2]
-	} else if bytes.HasSuffix(line, lf) {
+	case bytes.HasSuffix(line, lf):
 		trimmed = line[:len(line)-1]
-	} else {
-		// It's the end of the stream and it's not terminating with EOL character.
-		if s.state == looking || s.state == done {
-			return false, nil
-		}
-		// Let it flow. It's possible the last line was trimmed and we still want
-		// to parse it.
+	case s.state == looking || s.state == done:
+		// A final unterminated line is ignored when no snapshot is active.
+		return false, nil
 	}
 
 	if len(trimmed) != 0 && len(s.prefix) != 0 {
@@ -539,14 +537,12 @@
 					}
 				}
 				g := &Goroutine{
-					Signature: Signature{
-						State:    string(items[0]),
-						SleepMin: sleep,
-						SleepMax: sleep,
-						Locked:   locked,
-					},
-					ID:    id,
-					First: len(s.Goroutines) == 0,
+					State:    string(items[0]),
+					SleepMin: sleep,
+					SleepMax: sleep,
+					Locked:   locked,
+					ID:       id,
+					First:    len(s.Goroutines) == 0,
 				}
 				// Increase performance by always allocating 4 goroutines minimally.
 				if s.Goroutines == nil {
@@ -832,7 +828,7 @@
 
 		args, err := parseArgs(match[2])
 		if err != nil {
-			return true, fmt.Errorf("%s on line: %q", err, bytes.TrimSpace(line))
+			return true, fmt.Errorf("%w on line: %q", err, bytes.TrimSpace(line))
 		}
 		c.Args = args
 		return true, nil
@@ -847,15 +843,15 @@
 	var args Args
 	frames := [maxDepth]*Args{&args}
 	stack := frames[:1]
-	for _, s := range bytes.Split(line, commaSpace) {
+	for s := range bytes.SplitSeq(line, commaSpace) {
 		opened, a, closed := trimCurlyBrackets(s)
-		for i := 0; i < opened; i++ {
+		for range opened {
 			cur := stack[len(stack)-1]
 			cur.Values = append(cur.Values, Arg{})
 			next := &cur.Values[len(cur.Values)-1]
 			next.IsAggregate = true
 			if len(stack) == maxDepth {
-				return Args{}, fmt.Errorf("nested aggregate-typed arguments exceeded depth limit")
+				return Args{}, errors.New("nested aggregate-typed arguments exceeded depth limit")
 			}
 			stack = stack[:len(stack)+1]
 			stack[len(stack)-1] = &next.Fields
@@ -884,7 +880,7 @@
 				cur.Values = append(cur.Values, arg)
 			}
 		}
-		for i := 0; i < closed; i++ {
+		for range closed {
 			if len(stack) == 1 {
 				return Args{}, errors.New("unmatched closing curly bracket")
 			}
@@ -1021,7 +1017,7 @@
 		(*g)[prefix] = struct{}{}
 		p := pathJoin(prefix, "go.mod")
 		if runtime.GOOS == "windows" {
-			p = strings.Replace(p, "/", pathSeparator, -1)
+			p = strings.ReplaceAll(p, "/", pathSeparator)
 		}
 		/* #nosec G304 */
 		b, err := os.ReadFile(p)
@@ -1066,7 +1062,7 @@
 	for _, f := range getFiles(s.Goroutines) {
 		// TODO(maruel): Could a stack dump have mixed cases? I think it's
 		// possible, need to confirm and handle.
-		//log.Printf("  Analyzing %s", f)
+		// log.Printf("  Analyzing %s", f)
 
 		// First checks skip file I/O.
 		if s.RemoteGOROOT != "" && strings.HasPrefix(f, s.RemoteGOROOT+"/src/") {
@@ -1087,14 +1083,14 @@
 		found := false
 		for _, l := range s.LocalGOPATHs {
 			if r := isRootedIn(l+src, parts); r != "" {
-				//log.Printf("Found RemoteGOPATHs[%s] = %s", r[:len(r)-len(src)], l)
+				// log.Printf("Found RemoteGOPATHs[%s] = %s", r[:len(r)-len(src)], l)
 				s.RemoteGOPATHs[r[:len(r)-len(src)]] = l
 				found = true
 				break
 			}
 			const pkgmod = "/pkg/mod"
 			if r := isRootedIn(l+pkgmod, parts); r != "" {
-				//log.Printf("Found RemoteGOPATHs[%s] = %s", r[:len(r)-len(pkgmod)], l)
+				// log.Printf("Found RemoteGOPATHs[%s] = %s", r[:len(r)-len(pkgmod)], l)
 				s.RemoteGOPATHs[r[:len(r)-len(pkgmod)]] = l
 				found = true
 				break
@@ -1118,7 +1114,7 @@
 			continue
 		}
 		// If the source is not found, just too bad.
-		//log.Printf("Failed to find locally: %s", f)
+		// log.Printf("Failed to find locally: %s", f)
 		missing++
 	}
 	return missing
@@ -1132,7 +1128,7 @@
 			// Disallow non-absolute paths?
 			if v != "" {
 				if runtime.GOOS == "windows" {
-					v = strings.Replace(v, pathSeparator, "/", -1)
+					v = strings.ReplaceAll(v, pathSeparator, "/")
 				}
 				// Trim trailing "/".
 				if l := len(v); v[l-1] == '/' {
@@ -1143,7 +1139,7 @@
 		}
 	}
 	if len(out) == 0 {
-		homeDir := ""
+		var homeDir string
 		u, err := user.Current()
 		if err != nil {
 			homeDir = os.Getenv("HOME")
@@ -1155,7 +1151,7 @@
 		}
 		p := homeDir + "/go"
 		if runtime.GOOS == "windows" {
-			p = strings.Replace(p, pathSeparator, "/", -1)
+			p = strings.ReplaceAll(p, pathSeparator, "/")
 		}
 		out = []string{p}
 	}
diff --git a/stack/context_test.go b/stack/context_test.go
index f0ab908..b688a02 100644
--- a/stack/context_test.go
+++ b/stack/context_test.go
@@ -91,29 +91,27 @@
 			err:    io.EOF,
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"github.com/cockroachdb/cockroach/storage/engine._Cfunc_DBIterSeek",
-									Args{}, "??", 0),
-								newCall(
-									"gopkg.in/yaml%2ev2.handleErr",
-									Args{Values: []Arg{{Value: 0x433b20, IsPtr: true}}},
-									"/gopath/src/gopkg.in/yaml.v2/yaml.go",
-									153),
-								newCall(
-									"reflect.Value.assignTo",
-									Args{Values: []Arg{{Value: 0x570860, IsPtr: true}, {Value: 0x803f3e0, IsPtr: true}, {Value: 0x15}}},
-									"/goroot/src/reflect/value.go",
-									2125),
-								newCall(
-									"main.main",
-									Args{},
-									"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-									428),
-							},
+					State: "running",
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"github.com/cockroachdb/cockroach/storage/engine._Cfunc_DBIterSeek",
+								Args{}, "??", 0),
+							newCall(
+								"gopkg.in/yaml%2ev2.handleErr",
+								Args{Values: []Arg{{Value: 0x433b20, IsPtr: true}}},
+								"/gopath/src/gopkg.in/yaml.v2/yaml.go",
+								153),
+							newCall(
+								"reflect.Value.assignTo",
+								Args{Values: []Arg{{Value: 0x570860, IsPtr: true}, {Value: 0x803f3e0, IsPtr: true}, {Value: 0x15}}},
+								"/goroot/src/reflect/value.go",
+								2125),
+							newCall(
+								"main.main",
+								Args{},
+								"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+								428),
 						},
 					},
 					ID:    1,
@@ -144,56 +142,50 @@
 			err:    io.EOF,
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State:    "chan send",
-						SleepMin: 100,
-						SleepMax: 100,
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"gopkg.in/yaml%2ev2.handleErr",
-									Args{Values: []Arg{{Value: 0x433b20, IsPtr: true}}},
-									"/gopath/src/gopkg.in/yaml.v2/yaml.go",
-									153),
-							},
+					State:    "chan send",
+					SleepMin: 100,
+					SleepMax: 100,
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"gopkg.in/yaml%2ev2.handleErr",
+								Args{Values: []Arg{{Value: 0x433b20, IsPtr: true}}},
+								"/gopath/src/gopkg.in/yaml.v2/yaml.go",
+								153),
 						},
 					},
 					ID:    1,
 					First: true,
 				},
 				{
-					Signature: Signature{
-						State:  "chan send",
-						Locked: true,
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"gopkg.in/yaml%2ev2.handleErr",
-									Args{Values: []Arg{{Value: 0x8033b21, Name: "#1", IsPtr: true}}},
-									"/gopath/src/gopkg.in/yaml.v2/yaml.go",
-									153),
-							},
+					State:  "chan send",
+					Locked: true,
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"gopkg.in/yaml%2ev2.handleErr",
+								Args{Values: []Arg{{Value: 0x8033b21, Name: "#1", IsPtr: true}}},
+								"/gopath/src/gopkg.in/yaml.v2/yaml.go",
+								153),
 						},
 					},
 					ID: 2,
 				},
 				{
-					Signature: Signature{
-						State:    "chan send",
-						SleepMin: 101,
-						SleepMax: 101,
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"gopkg.in/yaml%2ev2.handleErr",
-									Args{Values: []Arg{{Value: 0x8033b22, Name: "#2", IsPtr: true}}},
-									"/gopath/src/gopkg.in/yaml.v2/yaml.go",
-									153),
-							},
+					State:    "chan send",
+					SleepMin: 101,
+					SleepMax: 101,
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"gopkg.in/yaml%2ev2.handleErr",
+								Args{Values: []Arg{{Value: 0x8033b22, Name: "#2", IsPtr: true}}},
+								"/gopath/src/gopkg.in/yaml.v2/yaml.go",
+								153),
 						},
-						Locked: true,
 					},
-					ID: 3,
+					Locked: true,
+					ID:     3,
 				},
 			},
 		},
@@ -212,16 +204,14 @@
 			err:    io.EOF,
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "garbage collection",
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"runtime.switchtoM",
-									Args{},
-									"/goroot/src/runtime/asm_amd64.s",
-									198),
-							},
+					State: "garbage collection",
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"runtime.switchtoM",
+								Args{},
+								"/goroot/src/runtime/asm_amd64.s",
+								198),
 						},
 					},
 					ID:    16,
@@ -244,16 +234,14 @@
 			err:    io.EOF,
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "garbage collection",
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"runtime.switchtoM",
-									Args{},
-									"/goroot/src/runtime/asm_amd64.s",
-									198),
-							},
+					State: "garbage collection",
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"runtime.switchtoM",
+								Args{},
+								"/goroot/src/runtime/asm_amd64.s",
+								198),
 						},
 					},
 					ID:    16,
@@ -277,12 +265,10 @@
 			err:    errors.New("failed to parse int on line: \"/gopath/src/github.com/maruel/panicparse/stack/stack.go:12345678901234567890\""),
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						Stack: Stack{
-							Calls: []Call{
-								newCall("github.com/maruel/panicparse/stack/stack.recurseType", Args{}, "", 0),
-							},
+					State: "running",
+					Stack: Stack{
+						Calls: []Call{
+							newCall("github.com/maruel/panicparse/stack/stack.recurseType", Args{}, "", 0),
 						},
 					},
 					ID:    1,
@@ -308,17 +294,15 @@
 			err:    errors.New("failed to parse int on line: \"/goroot/src/testing/testing.go:123456789012345678901 +0xa8b\""),
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State:     "running",
-						CreatedBy: Stack{Calls: []Call{newCall("testing.RunTests", Args{}, "", 0)}},
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"github.com/maruel/panicparse/stack/stack.recurseType",
-									Args{},
-									"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-									1),
-							},
+					State:     "running",
+					CreatedBy: Stack{Calls: []Call{newCall("testing.RunTests", Args{}, "", 0)}},
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"github.com/maruel/panicparse/stack/stack.recurseType",
+								Args{},
+								"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+								1),
 						},
 					},
 					ID:    1,
@@ -343,12 +327,10 @@
 			err: errors.New("failed to parse int on line: \"github.com/maruel/panicparse/stack/stack.recurseType(123456789012345678901)\""),
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						Stack: Stack{
-							Calls: []Call{
-								newCall("github.com/maruel/panicparse/stack/stack.recurseType", Args{}, "", 0),
-							},
+					State: "running",
+					Stack: Stack{
+						Calls: []Call{
+							newCall("github.com/maruel/panicparse/stack/stack.recurseType", Args{}, "", 0),
 						},
 					},
 					ID:    1,
@@ -371,28 +353,26 @@
 			err:    io.EOF,
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"github.com/maruel/panicparse/stack/stack.recurseType",
-									Args{
+					State: "running",
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"github.com/maruel/panicparse/stack/stack.recurseType",
+								Args{
+									Values: []Arg{{IsAggregate: true, Fields: Args{
 										Values: []Arg{{IsAggregate: true, Fields: Args{
 											Values: []Arg{{IsAggregate: true, Fields: Args{
 												Values: []Arg{{IsAggregate: true, Fields: Args{
 													Values: []Arg{{IsAggregate: true, Fields: Args{
-														Values: []Arg{{IsAggregate: true, Fields: Args{
-															Elided: true,
-														}}},
+														Elided: true,
 													}}},
 												}}},
 											}}},
 										}}},
-									},
-									"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-									9),
-							},
+									}}},
+								},
+								"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+								9),
 						},
 					},
 					ID:    1,
@@ -417,12 +397,10 @@
 			err: errors.New("nested aggregate-typed arguments exceeded depth limit on line: \"github.com/maruel/panicparse/stack/stack.recurseType({{{{{{...}}}}}})\""),
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						Stack: Stack{
-							Calls: []Call{
-								newCall("github.com/maruel/panicparse/stack/stack.recurseType", Args{}, "", 0),
-							},
+					State: "running",
+					Stack: Stack{
+						Calls: []Call{
+							newCall("github.com/maruel/panicparse/stack/stack.recurseType", Args{}, "", 0),
 						},
 					},
 					ID:    1,
@@ -447,12 +425,10 @@
 			err: errors.New("unmatched opening curly bracket on line: \"github.com/maruel/panicparse/stack/stack.recurseType({{{{{...}}}})\""),
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						Stack: Stack{
-							Calls: []Call{
-								newCall("github.com/maruel/panicparse/stack/stack.recurseType", Args{}, "", 0),
-							},
+					State: "running",
+					Stack: Stack{
+						Calls: []Call{
+							newCall("github.com/maruel/panicparse/stack/stack.recurseType", Args{}, "", 0),
 						},
 					},
 					ID:    1,
@@ -477,12 +453,10 @@
 			err: errors.New("unmatched closing curly bracket on line: \"github.com/maruel/panicparse/stack/stack.recurseType({{{{...}}}}})\""),
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						Stack: Stack{
-							Calls: []Call{
-								newCall("github.com/maruel/panicparse/stack/stack.recurseType", Args{}, "", 0),
-							},
+					State: "running",
+					Stack: Stack{
+						Calls: []Call{
+							newCall("github.com/maruel/panicparse/stack/stack.recurseType", Args{}, "", 0),
 						},
 					},
 					ID:    1,
@@ -503,12 +477,10 @@
 			err:    errors.New(`inconsistent indentation: " \t/gopath/src/github.com/maruel/panicparse/stack/stack.go:1", expected "  "`),
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						Stack: Stack{
-							Calls: []Call{
-								newCall("github.com/maruel/panicparse/stack/stack.recurseType", Args{}, "", 0),
-							},
+					State: "running",
+					Stack: Stack{
+						Calls: []Call{
+							newCall("github.com/maruel/panicparse/stack/stack.recurseType", Args{}, "", 0),
 						},
 					},
 					ID:    1,
@@ -535,9 +507,9 @@
 			err: errors.New("expected a function after a goroutine header, got: \"/gopath/src/gopkg.in/yaml.v2/yaml.go:153 +0xc6\""),
 			want: []*Goroutine{
 				{
-					Signature: Signature{State: "garbage collection"},
-					ID:        16,
-					First:     true,
+					State: "garbage collection",
+					ID:    16,
+					First: true,
 				},
 			},
 		},
@@ -559,35 +531,33 @@
 			err:    io.EOF,
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "garbage collection",
-						CreatedBy: Stack{
-							Calls: []Call{
-								newCall(
-									"testing.RunTests",
-									Args{},
-									"/goroot/src/testing/testing.go",
-									555),
-							},
+					State: "garbage collection",
+					CreatedBy: Stack{
+						Calls: []Call{
+							newCall(
+								"testing.RunTests",
+								Args{},
+								"/goroot/src/testing/testing.go",
+								555),
 						},
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"github.com/maruel/panicparse/stack/stack.recurseType",
-									Args{
-										Values: []Arg{
-											{Value: 0x9a3ec70, IsPtr: true},
-											{Value: 0x8062580, IsPtr: true},
-											{Value: 0x9a3e818, IsPtr: true},
-											{Value: 0x50a820, IsPtr: true},
-											{Value: 0x803a8a0, IsPtr: true},
-										},
+					},
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"github.com/maruel/panicparse/stack/stack.recurseType",
+								Args{
+									Values: []Arg{
+										{Value: 0x9a3ec70, IsPtr: true},
+										{Value: 0x8062580, IsPtr: true},
+										{Value: 0x9a3e818, IsPtr: true},
+										{Value: 0x50a820, IsPtr: true},
+										{Value: 0x803a8a0, IsPtr: true},
 									},
-									"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-									53),
-							},
-							Elided: true,
+								},
+								"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+								53),
 						},
+						Elided: true,
 					},
 					ID:    16,
 					First: true,
@@ -617,46 +587,44 @@
 			err:    io.EOF,
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "syscall",
-						CreatedBy: Stack{
-							Calls: []Call{
-								newCall(
-									"os/signal.init·1",
-									Args{},
-									"/goroot/src/os/signal/signal_unix.go",
-									27),
-							},
+					State: "syscall",
+					CreatedBy: Stack{
+						Calls: []Call{
+							newCall(
+								"os/signal.init·1",
+								Args{},
+								"/goroot/src/os/signal/signal_unix.go",
+								27),
 						},
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"runtime.notetsleepg",
-									Args{
-										Values: []Arg{
-											{Value: 0x918100, IsPtr: true},
-											{Value: 0xffffffffffffffff},
-											{Value: 0x1},
-										},
+					},
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"runtime.notetsleepg",
+								Args{
+									Values: []Arg{
+										{Value: 0x918100, IsPtr: true},
+										{Value: 0xffffffffffffffff},
+										{Value: 0x1},
 									},
-									"/goroot/src/runtime/lock_futex.go",
-									201),
-								newCall(
-									"runtime.signal_recv",
-									Args{Values: []Arg{{}}},
-									"/goroot/src/runtime/sigqueue.go",
-									109),
-								newCall(
-									"os/signal.loop",
-									Args{},
-									"/goroot/src/os/signal/signal_unix.go",
-									21),
-								newCall(
-									"runtime.goexit",
-									Args{},
-									"/goroot/src/runtime/asm_amd64.s",
-									2232),
-							},
+								},
+								"/goroot/src/runtime/lock_futex.go",
+								201),
+							newCall(
+								"runtime.signal_recv",
+								Args{Values: []Arg{{}}},
+								"/goroot/src/runtime/sigqueue.go",
+								109),
+							newCall(
+								"os/signal.loop",
+								Args{},
+								"/goroot/src/os/signal/signal_unix.go",
+								21),
+							newCall(
+								"runtime.goexit",
+								Args{},
+								"/goroot/src/runtime/asm_amd64.s",
+								2232),
 						},
 					},
 					ID:    5,
@@ -680,20 +648,18 @@
 			err:    io.EOF,
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						CreatedBy: Stack{
-							Calls: []Call{
-								newCall(
-									"github.com/maruel/panicparse/stack.New",
-									Args{},
-									"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-									131),
-							},
+					State: "running",
+					CreatedBy: Stack{
+						Calls: []Call{
+							newCall(
+								"github.com/maruel/panicparse/stack.New",
+								Args{},
+								"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+								131),
 						},
-						Stack: Stack{
-							Calls: []Call{newCall("", Args{}, "<unavailable>", 0)},
-						},
+					},
+					Stack: Stack{
+						Calls: []Call{newCall("", Args{}, "<unavailable>", 0)},
 					},
 					ID:    24,
 					First: true,
@@ -715,11 +681,9 @@
 			err:    io.EOF,
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						Stack: Stack{
-							Calls: []Call{newCall("", Args{}, "<unavailable>", 0)},
-						},
+					State: "running",
+					Stack: Stack{
+						Calls: []Call{newCall("", Args{}, "<unavailable>", 0)},
 					},
 					ID:    24,
 					First: true,
@@ -741,11 +705,9 @@
 			err:    errors.New("expected empty line after unavailable stack, got: \"junk\""),
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						Stack: Stack{
-							Calls: []Call{newCall("", Args{}, "<unavailable>", 0)},
-						},
+					State: "running",
+					Stack: Stack{
+						Calls: []Call{newCall("", Args{}, "<unavailable>", 0)},
 					},
 					ID:    24,
 					First: true,
@@ -769,25 +731,23 @@
 			err:    io.EOF,
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "runnable",
-						CreatedBy: Stack{
-							Calls: []Call{
-								newCall(
-									"github.com/maruel/panicparse/stack.New",
-									Args{},
-									"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-									113),
-							},
+					State: "runnable",
+					CreatedBy: Stack{
+						Calls: []Call{
+							newCall(
+								"github.com/maruel/panicparse/stack.New",
+								Args{},
+								"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+								113),
 						},
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"github.com/maruel/panicparse/stack.func·002",
-									Args{},
-									"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-									110),
-							},
+					},
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"github.com/maruel/panicparse/stack.func·002",
+								Args{},
+								"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+								110),
 						},
 					},
 					ID:    37,
@@ -810,9 +770,9 @@
 			err:    errors.New("expected a function after a goroutine header, got: \"junk\""),
 			want: []*Goroutine{
 				{
-					Signature: Signature{State: "running"},
-					ID:        1,
-					First:     true,
+					State: "running",
+					ID:    1,
+					First: true,
 				},
 			},
 		},
@@ -832,12 +792,10 @@
 			err:    errors.New("expected a file after a function, got: \"junk\""),
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						Stack: Stack{
-							Calls: []Call{
-								newCall("github.com/maruel/panicparse/stack.func·002", Args{}, "", 0),
-							},
+					State: "running",
+					Stack: Stack{
+						Calls: []Call{
+							newCall("github.com/maruel/panicparse/stack.func·002", Args{}, "", 0),
 						},
 					},
 					ID:    1,
@@ -864,25 +822,23 @@
 			err:    io.EOF,
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						CreatedBy: Stack{
-							Calls: []Call{
-								newCall(
-									"github.com/maruel/panicparse/stack.New",
-									Args{},
-									"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-									131),
-							},
+					State: "running",
+					CreatedBy: Stack{
+						Calls: []Call{
+							newCall(
+								"github.com/maruel/panicparse/stack.New",
+								Args{},
+								"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+								131),
 						},
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"github.com/maruel/panicparse/stack.func·002",
-									Args{},
-									"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-									110),
-							},
+					},
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"github.com/maruel/panicparse/stack.func·002",
+								Args{},
+								"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+								110),
 						},
 					},
 					ID:    1,
@@ -908,21 +864,19 @@
 			err:    errors.New("expected a file after a created line, got: \"junk\""),
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						CreatedBy: Stack{
-							Calls: []Call{
-								newCall("github.com/maruel/panicparse/stack.New", Args{}, "", 0),
-							},
+					State: "running",
+					CreatedBy: Stack{
+						Calls: []Call{
+							newCall("github.com/maruel/panicparse/stack.New", Args{}, "", 0),
 						},
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"github.com/maruel/panicparse/stack.func·002",
-									Args{},
-									"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-									110),
-							},
+					},
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"github.com/maruel/panicparse/stack.func·002",
+								Args{},
+								"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+								110),
 						},
 					},
 					ID:    1,
@@ -956,51 +910,49 @@
 			err:    io.EOF,
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "idle",
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"runtime.epollwait",
-									Args{
-										Values: []Arg{
-											{Value: 0x4},
-											{Value: 0x71c7118, IsPtr: true},
-											{Value: 0xffffffff00000080},
-											{},
-											{Value: 0xffffffff0028c1be},
-											{},
-											{},
-											{},
-											{},
-											{},
-										},
-										Elided: true,
+					State: "idle",
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"runtime.epollwait",
+								Args{
+									Values: []Arg{
+										{Value: 0x4},
+										{Value: 0x71c7118, IsPtr: true},
+										{Value: 0xffffffff00000080},
+										{},
+										{Value: 0xffffffff0028c1be},
+										{},
+										{},
+										{},
+										{},
+										{},
 									},
-									"/goroot/src/runtime/sys_linux_amd64.s",
-									400),
-								newCall(
-									"runtime.netpoll",
-									Args{Values: []Arg{{Value: 0x901b01, IsPtr: true}, {}}},
-									"/goroot/src/runtime/netpoll_epoll.go",
-									68),
-								newCall(
-									"findrunnable",
-									Args{Values: []Arg{{Value: 0x8012000, IsPtr: true}}},
-									"/goroot/src/runtime/proc.c",
-									1472),
-								newCall("schedule", Args{}, "/goroot/src/runtime/proc.c", 1575),
-								newCall(
-									"runtime.park_m",
-									Args{Values: []Arg{{Value: 0x80017a0, IsPtr: true}}},
-									"/goroot/src/runtime/proc.c",
-									1654),
-								newCall(
-									"runtime.mcall",
-									Args{Values: []Arg{{Value: 0x432684, IsPtr: true}}},
-									"/goroot/src/runtime/asm_amd64.s",
-									186),
-							},
+									Elided: true,
+								},
+								"/goroot/src/runtime/sys_linux_amd64.s",
+								400),
+							newCall(
+								"runtime.netpoll",
+								Args{Values: []Arg{{Value: 0x901b01, IsPtr: true}, {}}},
+								"/goroot/src/runtime/netpoll_epoll.go",
+								68),
+							newCall(
+								"findrunnable",
+								Args{Values: []Arg{{Value: 0x8012000, IsPtr: true}}},
+								"/goroot/src/runtime/proc.c",
+								1472),
+							newCall("schedule", Args{}, "/goroot/src/runtime/proc.c", 1575),
+							newCall(
+								"runtime.park_m",
+								Args{Values: []Arg{{Value: 0x80017a0, IsPtr: true}}},
+								"/goroot/src/runtime/proc.c",
+								1654),
+							newCall(
+								"runtime.mcall",
+								Args{Values: []Arg{{Value: 0x432684, IsPtr: true}}},
+								"/goroot/src/runtime/asm_amd64.s",
+								186),
 						},
 					},
 					ID:    0,
@@ -1026,31 +978,29 @@
 			err: io.EOF,
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"github.com/cockroachdb/cockroach/storage/engine._Cfunc_DBIterSeek",
-									Args{},
-									"??",
-									0),
-								newCall(
-									"gopkg.in/yaml%2ev2.handleErr",
-									Args{Values: []Arg{{Value: 0x433b20, IsPtr: true}}},
-									"/gopath/src/gopkg.in/yaml.v2/yaml.go",
-									153),
-								newCall(
-									"reflect.Value.assignTo",
-									Args{Values: []Arg{{Value: 0x570860, IsPtr: true}, {Value: 0x803f3e0, IsPtr: true}, {Value: 0x15}}},
-									"/goroot/src/reflect/value.go",
-									2125),
-								newCall(
-									"main.main",
-									Args{},
-									"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-									428),
-							},
+					State: "running",
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"github.com/cockroachdb/cockroach/storage/engine._Cfunc_DBIterSeek",
+								Args{},
+								"??",
+								0),
+							newCall(
+								"gopkg.in/yaml%2ev2.handleErr",
+								Args{Values: []Arg{{Value: 0x433b20, IsPtr: true}}},
+								"/gopath/src/gopkg.in/yaml.v2/yaml.go",
+								153),
+							newCall(
+								"reflect.Value.assignTo",
+								Args{Values: []Arg{{Value: 0x570860, IsPtr: true}, {Value: 0x803f3e0, IsPtr: true}, {Value: 0x15}}},
+								"/goroot/src/reflect/value.go",
+								2125),
+							newCall(
+								"main.main",
+								Args{},
+								"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+								428),
 						},
 					},
 					ID:    1,
@@ -1076,31 +1026,29 @@
 			err: io.EOF,
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"github.com/cockroachdb/cockroach/storage/engine._Cfunc_DBIterSeek",
-									Args{},
-									"??",
-									0),
-								newCall(
-									"gopkg.in/yaml%2ev2.handleErr",
-									Args{Values: []Arg{{Value: 0x433b20, IsPtr: true, IsInaccurate: true}}},
-									"/gopath/src/gopkg.in/yaml.v2/yaml.go",
-									153),
-								newCall(
-									"reflect.Value.assignTo",
-									Args{Values: []Arg{{Value: 0x570860, IsPtr: true}, {Value: 0x803f3e0, IsPtr: true}, {Value: 0x15}}},
-									"/goroot/src/reflect/value.go",
-									2125),
-								newCall(
-									"main.main",
-									Args{},
-									"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-									428),
-							},
+					State: "running",
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"github.com/cockroachdb/cockroach/storage/engine._Cfunc_DBIterSeek",
+								Args{},
+								"??",
+								0),
+							newCall(
+								"gopkg.in/yaml%2ev2.handleErr",
+								Args{Values: []Arg{{Value: 0x433b20, IsPtr: true, IsInaccurate: true}}},
+								"/gopath/src/gopkg.in/yaml.v2/yaml.go",
+								153),
+							newCall(
+								"reflect.Value.assignTo",
+								Args{Values: []Arg{{Value: 0x570860, IsPtr: true}, {Value: 0x803f3e0, IsPtr: true}, {Value: 0x15}}},
+								"/goroot/src/reflect/value.go",
+								2125),
+							newCall(
+								"main.main",
+								Args{},
+								"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+								428),
 						},
 					},
 					ID:    1,
@@ -1145,35 +1093,33 @@
 			err: io.EOF,
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						CreatedBy: Stack{
-							Calls: []Call{
-								newCall(
-									"testing.(*T).Run",
-									Args{},
-									"/home/maruel/golang/go/src/testing/testing.go",
-									916),
-							},
+					State: "running",
+					CreatedBy: Stack{
+						Calls: []Call{
+							newCall(
+								"testing.(*T).Run",
+								Args{},
+								"/home/maruel/golang/go/src/testing/testing.go",
+								916),
 						},
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"foo/bar.TestArchiveFail.func1.2",
-									Args{},
-									"/home/maruel/go/foo/bar_test.go",
-									209),
-								newCall(
-									"foo/bar.TestArchiveFail",
-									Args{Values: []Arg{{Value: 0x3382000, Name: "#1", IsPtr: true}}},
-									"/home/maruel/go/src/foo/bar_test.go",
-									155),
-								newCall(
-									"testing.tRunner",
-									Args{Values: []Arg{{Value: 0x3382000, Name: "#1", IsPtr: true}, {Value: 0x1615bf8, IsPtr: true}}},
-									"/home/maruel/golang/go/src/testing/testing.go",
-									865),
-							},
+					},
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"foo/bar.TestArchiveFail.func1.2",
+								Args{},
+								"/home/maruel/go/foo/bar_test.go",
+								209),
+							newCall(
+								"foo/bar.TestArchiveFail",
+								Args{Values: []Arg{{Value: 0x3382000, Name: "#1", IsPtr: true}}},
+								"/home/maruel/go/src/foo/bar_test.go",
+								155),
+							newCall(
+								"testing.tRunner",
+								Args{Values: []Arg{{Value: 0x3382000, Name: "#1", IsPtr: true}, {Value: 0x1615bf8, IsPtr: true}}},
+								"/home/maruel/golang/go/src/testing/testing.go",
+								865),
 						},
 					},
 					ID:    8,
@@ -1188,38 +1134,36 @@
 			prefix: "\nGOTRACEBACK=all\n",
 			want: []*Goroutine{
 				{
-					Signature: Signature{
-						State: "running",
-						CreatedBy: Stack{
-							Calls: []Call{
-								newCall(
-									"main.panicRace",
-									Args{},
-									"/go/src/github.com/maruel/panicparse/cmd/panic/main.go",
-									153,
-								),
-								newCall(
-									"main.main",
-									Args{},
-									"/go/src/github.com/maruel/panicparse/cmd/panic/main.go",
-									54,
-								),
-							},
+					State: "running",
+					CreatedBy: Stack{
+						Calls: []Call{
+							newCall(
+								"main.panicRace",
+								Args{},
+								"/go/src/github.com/maruel/panicparse/cmd/panic/main.go",
+								153,
+							),
+							newCall(
+								"main.main",
+								Args{},
+								"/go/src/github.com/maruel/panicparse/cmd/panic/main.go",
+								54,
+							),
 						},
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"main.panicDoRaceRead",
-									Args{},
-									"/go/src/github.com/maruel/panicparse/cmd/panic/main.go",
-									137,
-								),
-								newCall(
-									"main.panicRace.func2",
-									Args{},
-									"/go/src/github.com/maruel/panicparse/cmd/panic/main.go",
-									154),
-							},
+					},
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"main.panicDoRaceRead",
+								Args{},
+								"/go/src/github.com/maruel/panicparse/cmd/panic/main.go",
+								137,
+							),
+							newCall(
+								"main.panicRace.func2",
+								Args{},
+								"/go/src/github.com/maruel/panicparse/cmd/panic/main.go",
+								154),
 						},
 					},
 					ID:       8,
@@ -1227,37 +1171,35 @@
 					RaceAddr: 0xc000014100,
 				},
 				{
-					Signature: Signature{
-						State: "running",
-						CreatedBy: Stack{
-							Calls: []Call{
-								newCall(
-									"main.panicRace",
-									Args{},
-									"/go/src/github.com/maruel/panicparse/cmd/panic/main.go",
-									150,
-								),
-								newCall(
-									"main.main",
-									Args{},
-									"/go/src/github.com/maruel/panicparse/cmd/panic/main.go",
-									54,
-								),
-							},
+					State: "running",
+					CreatedBy: Stack{
+						Calls: []Call{
+							newCall(
+								"main.panicRace",
+								Args{},
+								"/go/src/github.com/maruel/panicparse/cmd/panic/main.go",
+								150,
+							),
+							newCall(
+								"main.main",
+								Args{},
+								"/go/src/github.com/maruel/panicparse/cmd/panic/main.go",
+								54,
+							),
 						},
-						Stack: Stack{
-							Calls: []Call{
-								newCall(
-									"main.panicDoRaceWrite",
-									Args{},
-									"/go/src/github.com/maruel/panicparse/cmd/panic/main.go",
-									132),
-								newCall(
-									"main.panicRace.func1",
-									Args{},
-									"/go/src/github.com/maruel/panicparse/cmd/panic/main.go",
-									151),
-							},
+					},
+					Stack: Stack{
+						Calls: []Call{
+							newCall(
+								"main.panicDoRaceWrite",
+								Args{},
+								"/go/src/github.com/maruel/panicparse/cmd/panic/main.go",
+								132),
+							newCall(
+								"main.panicRace.func1",
+								Args{},
+								"/go/src/github.com/maruel/panicparse/cmd/panic/main.go",
+								151),
 						},
 					},
 					ID:        7,
@@ -1311,7 +1253,6 @@
 		},
 	}
 	for i, line := range data {
-		line := line
 		t.Run(fmt.Sprintf("%d-%s", i, line.name), func(t *testing.T) {
 			t.Parallel()
 			prefix := bytes.Buffer{}
@@ -1356,17 +1297,15 @@
 	}
 	want := []*Goroutine{
 		{
-			Signature: Signature{
-				State: "running",
-				Stack: Stack{
-					Calls: []Call{
-						newCallLocal(
-							"main.main",
-							Args{},
-							pathJoin(ppDir, "main.go"),
-							73,
-						),
-					},
+			State: "running",
+			Stack: Stack{
+				Calls: []Call{
+					newCallLocal(
+						"main.main",
+						Args{},
+						pathJoin(ppDir, "main.go"),
+						75,
+					),
 				},
 			},
 			ID:    1,
@@ -1386,29 +1325,27 @@
 	// This is a change detector on internal/main.go.
 	want = []*Goroutine{
 		{
-			Signature: Signature{
-				State: "running",
-				Stack: Stack{
-					Calls: []Call{
-						newCallLocal(
-							"main.panicint",
-							Args{Values: []Arg{{Value: 42}}},
-							pathJoin(ppDir, "main.go"),
-							92,
-						),
-						newCallLocal(
-							"main.init.func9",
-							Args{},
-							pathJoin(ppDir, "main.go"),
-							319,
-						),
-						newCallLocal(
-							"main.main",
-							Args{},
-							pathJoin(ppDir, "main.go"),
-							75,
-						),
-					},
+			State: "running",
+			Stack: Stack{
+				Calls: []Call{
+					newCallLocal(
+						"main.panicint",
+						Args{Values: []Arg{{Value: 42}}},
+						pathJoin(ppDir, "main.go"),
+						94,
+					),
+					newCallLocal(
+						"main.init.func9",
+						Args{},
+						pathJoin(ppDir, "main.go"),
+						321,
+					),
+					newCallLocal(
+						"main.main",
+						Args{},
+						pathJoin(ppDir, "main.go"),
+						77,
+					),
 				},
 			},
 			ID:    1,
@@ -1429,24 +1366,14 @@
 
 func TestGetGOPATHs(t *testing.T) {
 	// This test cannot run in parallel.
-	old := os.Getenv("GOPATH")
-	defer os.Setenv("GOPATH", old)
-	os.Setenv("GOPATH", "")
+	t.Setenv("GOPATH", "")
 	if p := getGOPATHs(); len(p) != 1 {
 		// It's the home directory + /go.
 		t.Fatalf("expected only one path: %v", p)
 	}
 
-	root, err := os.MkdirTemp("", "stack")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err = os.RemoveAll(root); err != nil {
-			t.Error(err)
-		}
-	}()
-	os.Setenv("GOPATH", filepath.Join(root, "a")+string(filepath.ListSeparator)+filepath.Join(root, "b")+string(filepath.Separator))
+	root := t.TempDir()
+	t.Setenv("GOPATH", filepath.Join(root, "a")+string(filepath.ListSeparator)+filepath.Join(root, "b")+string(filepath.Separator))
 	if p := getGOPATHs(); len(p) != 2 {
 		t.Fatalf("expected two paths: %v", p)
 	}
@@ -1456,20 +1383,11 @@
 // of go modules using the "replace" statement.
 func TestGomoduleComplex(t *testing.T) {
 	// This test cannot run in parallel.
-	old := os.Getenv("GOPATH")
-	defer os.Setenv("GOPATH", old)
-	root, err := os.MkdirTemp("", "stack")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err = os.RemoveAll(root); err != nil {
-			t.Error(err)
-		}
-	}()
+	root := t.TempDir()
+	var err error
 
-	os.Setenv("GOPATH", filepath.Join(root, "go"))
-	os.Setenv("GO111MODULE", "on")
+	t.Setenv("GOPATH", filepath.Join(root, "go"))
+	t.Setenv("GO111MODULE", "on")
 	tree := map[string]string{
 		"pkg1/go.mod": "module example.com/pkg1\n" +
 			"require (\n" +
@@ -1516,7 +1434,7 @@
 		t.Fatal(err)
 	}
 
-	out, err := exec.Command(exe).CombinedOutput()
+	out, err := exec.CommandContext(t.Context(), exe).CombinedOutput()
 	if err == nil {
 		t.Error("expected failure")
 	}
@@ -1536,12 +1454,12 @@
 	compareString(t, "", string(suffix))
 	wantGOROOT := ""
 	compareString(t, wantGOROOT, s.RemoteGOROOT)
-	compareString(t, build.Default.GOROOT, strings.Replace(s.LocalGOROOT, "/", pathSeparator, -1))
+	compareString(t, build.Default.GOROOT, strings.ReplaceAll(s.LocalGOROOT, "/", pathSeparator))
 
 	rootRemote := root
 	if runtime.GOOS == "windows" {
 		// On Windows, we must make the path to be POSIX style.
-		rootRemote = strings.Replace(root, pathSeparator, "/", -1)
+		rootRemote = strings.ReplaceAll(root, pathSeparator, "/")
 	}
 	rootLocal := rootRemote
 	wantGOPATHs := map[string]string{
@@ -1563,62 +1481,60 @@
 
 	want := []*Goroutine{
 		{
-			Signature: Signature{
-				State: "running",
-				Stack: Stack{
-					Calls: []Call{
-						{
-							Func:          newFunc("example.com/pkg3.Die"),
-							Args:          Args{Elided: true},
-							RemoteSrcPath: pathJoin(rootRemote, "go", "src", "example.com", "pkg3", "src3.go"),
-							Line:          2,
-							SrcName:       "src3.go",
-							DirSrc:        "pkg3/src3.go",
-							LocalSrcPath:  pathJoin(rootLocal, "go", "src", "example.com", "pkg3", "src3.go"),
-							RelSrcPath:    "example.com/pkg3/src3.go",
-							ImportPath:    "example.com/pkg3",
-							Location:      GOPATH,
-						},
-						{
-							Func:          newFunc("example.com/pkg2.CallDie"),
-							Args:          Args{Elided: true},
-							RemoteSrcPath: pathJoin(rootRemote, "pkg2", "src2.go"),
-							Line:          3,
-							SrcName:       "src2.go",
-							DirSrc:        "pkg2/src2.go",
-							// Since this was found locally as a go module using the remote
-							// path, this is correct, even if confusing.
-							LocalSrcPath: pathJoin(rootRemote, "pkg2", "src2.go"),
-							RelSrcPath:   "src2.go",
-							ImportPath:   "example.com/pkg2",
-							Location:     GoMod,
-						},
-						{
-							Func:          newFunc("example.com/pkg1/internal.CallCallDie"),
-							RemoteSrcPath: pathJoin(rootRemote, "pkg1", "internal", "int.go"),
-							Line:          2,
-							SrcName:       "int.go",
-							DirSrc:        "internal/int.go",
-							// Since this was found locally as a go module using the remote
-							// path, this is correct, even if confusing.
-							LocalSrcPath: pathJoin(rootRemote, "pkg1", "internal", "int.go"),
-							RelSrcPath:   "internal/int.go",
-							ImportPath:   "example.com/pkg1/internal",
-							Location:     GoMod,
-						},
-						{
-							Func:          newFunc("main.main"),
-							RemoteSrcPath: pathJoin(rootRemote, "pkg1", "cmd", "main.go"),
-							Line:          4,
-							SrcName:       "main.go",
-							DirSrc:        "cmd/main.go",
-							// Since this was found locally as a go module using the remote
-							// path, this is correct, even if confusing.
-							LocalSrcPath: pathJoin(rootRemote, "pkg1", "cmd", "main.go"),
-							RelSrcPath:   "cmd/main.go",
-							ImportPath:   "example.com/pkg1/cmd",
-							Location:     GoMod,
-						},
+			State: "running",
+			Stack: Stack{
+				Calls: []Call{
+					{
+						Func:          newFunc("example.com/pkg3.Die"),
+						Args:          Args{Elided: true},
+						RemoteSrcPath: pathJoin(rootRemote, "go", "src", "example.com", "pkg3", "src3.go"),
+						Line:          2,
+						SrcName:       "src3.go",
+						DirSrc:        "pkg3/src3.go",
+						LocalSrcPath:  pathJoin(rootLocal, "go", "src", "example.com", "pkg3", "src3.go"),
+						RelSrcPath:    "example.com/pkg3/src3.go",
+						ImportPath:    "example.com/pkg3",
+						Location:      GOPATH,
+					},
+					{
+						Func:          newFunc("example.com/pkg2.CallDie"),
+						Args:          Args{Elided: true},
+						RemoteSrcPath: pathJoin(rootRemote, "pkg2", "src2.go"),
+						Line:          3,
+						SrcName:       "src2.go",
+						DirSrc:        "pkg2/src2.go",
+						// Since this was found locally as a go module using the remote
+						// path, this is correct, even if confusing.
+						LocalSrcPath: pathJoin(rootRemote, "pkg2", "src2.go"),
+						RelSrcPath:   "src2.go",
+						ImportPath:   "example.com/pkg2",
+						Location:     GoMod,
+					},
+					{
+						Func:          newFunc("example.com/pkg1/internal.CallCallDie"),
+						RemoteSrcPath: pathJoin(rootRemote, "pkg1", "internal", "int.go"),
+						Line:          2,
+						SrcName:       "int.go",
+						DirSrc:        "internal/int.go",
+						// Since this was found locally as a go module using the remote
+						// path, this is correct, even if confusing.
+						LocalSrcPath: pathJoin(rootRemote, "pkg1", "internal", "int.go"),
+						RelSrcPath:   "internal/int.go",
+						ImportPath:   "example.com/pkg1/internal",
+						Location:     GoMod,
+					},
+					{
+						Func:          newFunc("main.main"),
+						RemoteSrcPath: pathJoin(rootRemote, "pkg1", "cmd", "main.go"),
+						Line:          4,
+						SrcName:       "main.go",
+						DirSrc:        "cmd/main.go",
+						// Since this was found locally as a go module using the remote
+						// path, this is correct, even if confusing.
+						LocalSrcPath: pathJoin(rootRemote, "pkg1", "cmd", "main.go"),
+						RelSrcPath:   "cmd/main.go",
+						ImportPath:   "example.com/pkg1/cmd",
+						Location:     GoMod,
 					},
 				},
 			},
@@ -1631,22 +1547,15 @@
 
 func TestGoRun(t *testing.T) {
 	t.Parallel()
-	root, err := os.MkdirTemp("", "stack")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err = os.RemoveAll(root); err != nil {
-			t.Error(err)
-		}
-	}()
+	root := t.TempDir()
+	var err error
 
 	p := filepath.Join(root, "main.go")
 	content := "package main\nfunc main() { panic(42) }\n"
 	if err = os.WriteFile(p, []byte(content), 0600); err != nil {
 		t.Fatal(err)
 	}
-	c := exec.Command("go", "run", p)
+	c := exec.CommandContext(t.Context(), "go", "run", p)
 	out, err := c.CombinedOutput()
 	if err == nil {
 		t.Fatal("expected failure")
@@ -1661,31 +1570,29 @@
 	}
 	if runtime.GOOS == "windows" {
 		// On Windows, we must make the path to be POSIX style.
-		p = strings.Replace(p, pathSeparator, "/", -1)
+		p = strings.ReplaceAll(p, pathSeparator, "/")
 	}
 
 	want := []*Goroutine{
 		{
-			Signature: Signature{
-				State: "running",
-				Stack: Stack{
-					Calls: []Call{
-						{
-							Func: Func{
-								Complete:   "main.main",
-								ImportPath: "main",
-								DirName:    "main",
-								Name:       "main",
-								IsExported: true,
-								IsPkgMain:  true,
-							},
-							RemoteSrcPath: p,
-							Line:          2,
-							SrcName:       "main.go",
-							DirSrc:        path.Base(path.Dir(p)) + "/main.go",
-							ImportPath:    "main",
-							Location:      LocationUnknown,
+			State: "running",
+			Stack: Stack{
+				Calls: []Call{
+					{
+						Func: Func{
+							Complete:   "main.main",
+							ImportPath: "main",
+							DirName:    "main",
+							Name:       "main",
+							IsExported: true,
+							IsPkgMain:  true,
 						},
+						RemoteSrcPath: p,
+						Line:          2,
+						SrcName:       "main.go",
+						DirSrc:        path.Base(path.Dir(p)) + "/main.go",
+						ImportPath:    "main",
+						Location:      LocationUnknown,
 					},
 				},
 			},
@@ -1742,13 +1649,11 @@
 	}
 
 	for cmd, data := range cmds {
-		cmd := cmd
-		data := data
 		t.Run(cmd, func(t *testing.T) {
 			t.Parallel()
 			prefix := bytes.Buffer{}
 			s, suffix, err := ScanSnapshot(bytes.NewReader(data), &prefix, defaultOpts())
-			if err != nil && err != io.EOF {
+			if err != nil && !errors.Is(err, io.EOF) {
 				t.Fatal(err)
 			}
 			if s == nil {
@@ -1782,21 +1687,19 @@
 	}
 	want := []*Goroutine{
 		{
-			Signature: Signature{
-				State: "running",
-				Stack: Stack{
-					Calls: []Call{
-						newCallLocal(
-							"main.panicArgsElided",
-							Args{
-								Values: []Arg{{Value: 1}, {Value: 2}, {Value: 3}, {Value: 4}, {Value: 5}, {Value: 6}, {Value: 7}, {Value: 8}, {Value: 9}, {Value: 10}},
-								Elided: true,
-							},
-							pathJoin(ppDir, "main.go"),
-							58),
-						newCallLocal("main.init.func1", Args{}, pathJoin(ppDir, "main.go"), 134),
-						newCallLocal("main.main", Args{}, pathJoin(ppDir, "main.go"), 340),
-					},
+			State: "running",
+			Stack: Stack{
+				Calls: []Call{
+					newCallLocal(
+						"main.panicArgsElided",
+						Args{
+							Values: []Arg{{Value: 1}, {Value: 2}, {Value: 3}, {Value: 4}, {Value: 5}, {Value: 6}, {Value: 7}, {Value: 8}, {Value: 9}, {Value: 10}},
+							Elided: true,
+						},
+						pathJoin(ppDir, "main.go"),
+						58),
+					newCallLocal("main.init.func1", Args{}, pathJoin(ppDir, "main.go"), 134),
+					newCallLocal("main.main", Args{}, pathJoin(ppDir, "main.go"), 340),
 				},
 			},
 			ID:    1,
@@ -1819,23 +1722,21 @@
 	}
 	want := []*Goroutine{
 		{
-			Signature: Signature{
-				State: "running",
-				Stack: Stack{
-					Calls: []Call{
-						newCallLocal(
-							// This is important to note here that the Go runtime prints out
-							// the package path, and not the package name.
-							//
-							// Here the package name is "correct". There is no way to deduce
-							// this from the stack trace.
-							"github.com/maruel/panicparse"+ver+"/cmd/panic/internal/incorrect.Panic",
-							Args{},
-							pathJoin(ppDir, "internal", "incorrect", "correct.go"),
-							7),
-						newCallLocal("main.init.func20", Args{}, pathJoin(ppDir, "main.go"), 314),
-						newCallLocal("main.main", Args{}, pathJoin(ppDir, "main.go"), 340),
-					},
+			State: "running",
+			Stack: Stack{
+				Calls: []Call{
+					newCallLocal(
+						// This is important to note here that the Go runtime prints out
+						// the package path, and not the package name.
+						//
+						// Here the package name is "correct". There is no way to deduce
+						// this from the stack trace.
+						"github.com/maruel/panicparse"+ver+"/cmd/panic/internal/incorrect.Panic",
+						Args{},
+						pathJoin(ppDir, "internal", "incorrect", "correct.go"),
+						7),
+					newCallLocal("main.init.func20", Args{}, pathJoin(ppDir, "main.go"), 314),
+					newCallLocal("main.main", Args{}, pathJoin(ppDir, "main.go"), 340),
 				},
 			},
 			ID:    1,
@@ -1854,73 +1755,69 @@
 	}
 	want := []*Goroutine{
 		{
-			Signature: Signature{
-				State: "running",
-				CreatedBy: Stack{
-					Calls: []Call{
-						newCallLocal(
-							"main.panicRace",
-							Args{},
-							pathJoin(ppDir, "main.go"),
-							151,
-						),
-						newCallLocal(
-							"main.main",
-							Args{},
-							pathJoin(ppDir, "main.go"),
-							75,
-						),
-					},
+			State: "running",
+			CreatedBy: Stack{
+				Calls: []Call{
+					newCallLocal(
+						"main.panicRace",
+						Args{},
+						pathJoin(ppDir, "main.go"),
+						151,
+					),
+					newCallLocal(
+						"main.main",
+						Args{},
+						pathJoin(ppDir, "main.go"),
+						77,
+					),
 				},
-				Stack: Stack{
-					Calls: []Call{
-						newCallLocal(
-							"main.panicDoRaceRead",
-							Args{},
-							pathJoin(ppDir, "main.go"),
-							150),
-						newCallLocal(
-							"main.panicRace.func2",
-							Args{},
-							pathJoin(ppDir, "main.go"),
-							135),
-					},
+			},
+			Stack: Stack{
+				Calls: []Call{
+					newCallLocal(
+						"main.panicDoRaceRead",
+						Args{},
+						pathJoin(ppDir, "main.go"),
+						150),
+					newCallLocal(
+						"main.panicRace.func2",
+						Args{},
+						pathJoin(ppDir, "main.go"),
+						135),
 				},
 			},
 			RaceAddr: pointer,
 		},
 		{
-			Signature: Signature{
-				State: "running",
-				CreatedBy: Stack{
-					Calls: []Call{
-						newCallLocal(
-							"main.panicRace",
-							Args{},
-							pathJoin(ppDir, "main.go"),
-							151,
-						),
-						newCallLocal(
-							"main.main",
-							Args{},
-							pathJoin(ppDir, "main.go"),
-							75,
-						),
-					},
+			State: "running",
+			CreatedBy: Stack{
+				Calls: []Call{
+					newCallLocal(
+						"main.panicRace",
+						Args{},
+						pathJoin(ppDir, "main.go"),
+						151,
+					),
+					newCallLocal(
+						"main.main",
+						Args{},
+						pathJoin(ppDir, "main.go"),
+						77,
+					),
 				},
-				Stack: Stack{
-					Calls: []Call{
-						newCallLocal(
-							"main.panicDoRaceWrite",
-							Args{},
-							pathJoin(ppDir, "main.go"),
-							145),
-						newCallLocal(
-							"main.panicRace.func1",
-							Args{},
-							pathJoin(ppDir, "main.go"),
-							132),
-					},
+			},
+			Stack: Stack{
+				Calls: []Call{
+					newCallLocal(
+						"main.panicDoRaceWrite",
+						Args{},
+						pathJoin(ppDir, "main.go"),
+						145),
+					newCallLocal(
+						"main.panicRace.func1",
+						Args{},
+						pathJoin(ppDir, "main.go"),
+						132),
 				},
 			},
 			RaceWrite: true,
@@ -1956,20 +1853,18 @@
 	}
 	want := []*Goroutine{
 		{
-			Signature: Signature{
-				State: "running",
-				Stack: Stack{
-					Calls: []Call{
-						newCallLocal(
-							"main.panicstr",
-							Args{Values: []Arg{{IsAggregate: true, Fields: Args{
-								Values: []Arg{{Value: 0x123456, IsPtr: true}, {Value: 4}},
-							}}}},
-							pathJoin(ppDir, "main.go"),
-							50),
-						newCallLocal("main.init.func19", Args{}, pathJoin(ppDir, "main.go"), 307),
-						newCallLocal("main.main", Args{}, pathJoin(ppDir, "main.go"), 340),
-					},
+			State: "running",
+			Stack: Stack{
+				Calls: []Call{
+					newCallLocal(
+						"main.panicstr",
+						Args{Values: []Arg{{IsAggregate: true, Fields: Args{
+							Values: []Arg{{Value: 0x123456, IsPtr: true}, {Value: 4}},
+						}}}},
+						pathJoin(ppDir, "main.go"),
+						50),
+					newCallLocal("main.init.func19", Args{}, pathJoin(ppDir, "main.go"), 307),
+					newCallLocal("main.main", Args{}, pathJoin(ppDir, "main.go"), 340),
 				},
 			},
 			ID:    1,
@@ -1992,23 +1887,21 @@
 	}
 	want := []*Goroutine{
 		{
-			Signature: Signature{
-				State: "running",
-				Stack: Stack{
-					Calls: []Call{
-						newCallLocal(
-							// This is important to note here the inconsistency in the Go
-							// runtime stack generator. The path is escaped, but symbols are
-							// not.
-							"github.com/maruel/panicparse"+ver+"/cmd/panic/internal/utf8.(*Strùct).Pànic",
-							Args{Values: []Arg{{Value: 1, IsInaccurate: true}}},
-							// See TestCallUTF8 in stack_test.go for exercising the methods on
-							// Call in this situation.
-							pathJoin(ppDir, "internal", "utf8", "utf8.go"),
-							10),
-						newCallLocal("main.init.func21", Args{}, pathJoin(ppDir, "main.go"), 322),
-						newCallLocal("main.main", Args{}, pathJoin(ppDir, "main.go"), 340),
-					},
+			State: "running",
+			Stack: Stack{
+				Calls: []Call{
+					newCallLocal(
+						// This is important to note here the inconsistency in the Go
+						// runtime stack generator. The path is escaped, but symbols are
+						// not.
+						"github.com/maruel/panicparse"+ver+"/cmd/panic/internal/utf8.(*Strùct).Pànic",
+						Args{Values: []Arg{{Value: 1, IsInaccurate: true}}},
+						// See TestCallUTF8 in stack_test.go for exercising the methods on
+						// Call in this situation.
+						pathJoin(ppDir, "internal", "utf8", "utf8.go"),
+						10),
+					newCallLocal("main.init.func21", Args{}, pathJoin(ppDir, "main.go"), 322),
+					newCallLocal("main.main", Args{}, pathJoin(ppDir, "main.go"), 340),
 				},
 			},
 			ID:    1,
@@ -2030,7 +1923,7 @@
 	}
 	prefix := bytes.Buffer{}
 	s, suffix, err := ScanSnapshot(bytes.NewReader(internaltest.PanicwebOutput()), &prefix, defaultOpts())
-	if err != io.EOF {
+	if !errors.Is(err, io.EOF) {
 		t.Fatal(err)
 	}
 	if s == nil {
@@ -2044,7 +1937,7 @@
 	if !s.guessPaths() {
 		t.Error("expected success")
 	}
-	if s.RemoteGOROOT != strings.Replace(build.Default.GOROOT, "\\", "/", -1) {
+	if s.RemoteGOROOT != strings.ReplaceAll(build.Default.GOROOT, "\\", "/") {
 		t.Fatalf("RemoteGOROOT mismatch; want:%q got:%q", build.Default.GOROOT, s.RemoteGOROOT)
 	}
 	if got := len(s.Goroutines); got < 30 {
@@ -2054,8 +1947,9 @@
 	// OSes and Go versions, but to find some of the expected signatures.
 	pwebDir := pathJoin(getPanicParseDir(t), "cmd", "panicweb")
 	// Reduce the goroutines and categorize the signatures.
-	var types []panicwebSignatureType
-	for _, b := range s.Aggregate(AnyPointer).Buckets {
+	buckets := s.Aggregate(AnyPointer).Buckets
+	types := make([]panicwebSignatureType, 0, len(buckets))
+	for _, b := range buckets {
 		types = append(types, identifyPanicwebSignature(t, b, pwebDir))
 	}
 	// Count the expected types.
@@ -2092,7 +1986,7 @@
 		t.Fatal(err)
 	}
 	// Our internal functions work with '/' as path separator.
-	parts := splitPath(strings.Replace(pwd, "\\", "/", -1))
+	parts := splitPath(strings.ReplaceAll(pwd, "\\", "/"))
 	gmc := gomodCache{}
 	root, importPath := gmc.isGoModule(parts)
 	if want := strings.Join(parts[:len(parts)-1], "/"); want != root {
@@ -2143,8 +2037,8 @@
 		{[]byte("{{}}a{{}}"), []byte("}}a{{"), 2, 2},
 	}
 	for i, line := range data {
-		line := line
 		t.Run(fmt.Sprintf("%d-%s", i, line.input), func(t *testing.T) {
+			t.Parallel()
 			gotOpened, got, gotClosed := trimCurlyBrackets(line.input)
 			if !bytes.Equal(line.want, got) {
 				t.Errorf("want %s, got %s", line.want, got)
@@ -2168,9 +2062,9 @@
 	data := internaltest.StaticPanicwebOutput()
 	opts := defaultOpts()
 	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
+	for range b.N {
 		s, _, err := ScanSnapshot(bytes.NewReader(data), io.Discard, opts)
-		if err != io.EOF {
+		if !errors.Is(err, io.EOF) {
 			b.Fatal(err)
 		}
 		if s == nil {
@@ -2184,9 +2078,9 @@
 	data := internaltest.StaticPanicwebOutput()
 	opts := defaultOpts()
 	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
+	for range b.N {
 		s, _, err := ScanSnapshot(bytes.NewReader(data), io.Discard, opts)
-		if err != io.EOF {
+		if !errors.Is(err, io.EOF) {
 			b.Fatal(err)
 		}
 		if s == nil {
@@ -2210,7 +2104,7 @@
 	opts := defaultOpts()
 	b.ResetTimer()
 	s, suffix, err := ScanSnapshot(r, &prefix, opts)
-	if err != io.EOF {
+	if !errors.Is(err, io.EOF) {
 		b.Fatal(err)
 	}
 	if s != nil {
@@ -2331,7 +2225,7 @@
 				t.Fatal("expected Locked")
 			}
 			// This is a change detector on internal/main.go.
-			want := Stack{Calls: []Call{newCallLocal("main.main in goroutine 1", Args{}, pathJoin(pwebDir, "main.go"), 146)}}
+			want := Stack{Calls: []Call{newCallLocal("main.main in goroutine 1", Args{}, pathJoin(pwebDir, "main.go"), 148)}}
 			compareStacks(t, &b.Signature.CreatedBy, &want)
 			for i := range b.Signature.Stack.Calls {
 				if strings.HasPrefix(b.Signature.Stack.Calls[i].ImportPath, "github.com/mattn/go-colorable") {
@@ -2398,7 +2292,7 @@
 			}
 			if expectVersion {
 				// Assert that it's using v0.1.0 format.
-				ver := strings.SplitN(b.Stack.Calls[callIdx].RelSrcPath[len(prefix):], "/", 2)[0]
+				ver, _, _ := strings.Cut(b.Stack.Calls[callIdx].RelSrcPath[len(prefix):], "/")
 				re := regexp.MustCompile(`^v\d+\.\d+\.\d+$`)
 				if !re.MatchString(ver) {
 					t.Fatalf("unexpected version string %q", ver)
@@ -2465,12 +2359,12 @@
 	}
 	// "/" is used even on Windows in the stack trace, return in this format to
 	// simply our life.
-	return strings.Replace(filepath.Dir(thisDir), "\\", "/", -1)
+	return strings.ReplaceAll(filepath.Dir(thisDir), "\\", "/")
 }
 
 func createTree(t *testing.T, root string, tree map[string]string) {
 	for path, content := range tree {
-		p := filepath.Join(root, strings.Replace(path, "/", pathSeparator, -1))
+		p := filepath.Join(root, strings.ReplaceAll(path, "/", pathSeparator))
 		b := filepath.Dir(p)
 		if err := os.MkdirAll(b, 0700); err != nil {
 			t.Fatal(err)
@@ -2483,6 +2377,7 @@
 
 // TestScanSnapshotToolchainRoots keeps standard-library files separate from cached modules.
 func TestScanSnapshotToolchainRoots(t *testing.T) {
+	t.Parallel()
 	packages := []struct{ pkg, file, fn string }{
 		{"runtime", "proc.go", "gopark"},
 		{"net/http", "server.go", "(*Server).Serve"},
@@ -2502,6 +2397,7 @@
 							continue
 						}
 						t.Run(fmt.Sprintf("%s/%s/%s/stdlib=%t/module=%t", line.pkg, remoteRoot, prefix, stdlib, withModule), func(t *testing.T) {
+							t.Parallel()
 							root := filepath.ToSlash(t.TempDir())
 							gopath := pathJoin(root, "gopath")
 							goroot := pathJoin(root, "go")
@@ -2529,7 +2425,7 @@
 							opts.LocalGOPATHs = []string{gopath}
 							opts.AnalyzeSources = false
 							s, _, err := ScanSnapshot(strings.NewReader(input), io.Discard, opts)
-							if err != io.EOF {
+							if !errors.Is(err, io.EOF) {
 								t.Fatal(err)
 							}
 							wantRoot := ""
diff --git a/stack/html.go b/stack/html.go
index 02b76f2..b7b936d 100644
--- a/stack/html.go
+++ b/stack/html.go
@@ -24,7 +24,7 @@
 //
 // Use footer to add custom HTML at the bottom of the page.
 func (a *Aggregated) ToHTML(w io.Writer, footer template.HTML) error {
-	data := map[string]interface{}{
+	data := map[string]any{
 		"Aggregated": a,
 		"Footer":     footer,
 		"Snapshot":   a.Snapshot,
@@ -36,7 +36,7 @@
 //
 // Use footer to add custom HTML at the bottom of the page.
 func (s *Snapshot) ToHTML(w io.Writer, footer template.HTML) error {
-	data := map[string]interface{}{
+	data := map[string]any{
 		"Footer":   footer,
 		"Snapshot": s,
 	}
@@ -45,7 +45,7 @@
 
 // Private stuff.
 
-func toHTML(w io.Writer, data map[string]interface{}) error {
+func toHTML(w io.Writer, data map[string]any) error {
 	m := template.FuncMap{
 		"funcClass": funcClass,
 		"minus":     minus,
@@ -93,7 +93,7 @@
 	if ip == "" {
 		return ""
 	}
-	url := template.URL("")
+	var url template.URL
 	if c.Location == Stdlib {
 		// This always links to the latest release, past releases are not online.
 		// That's somewhat unfortunate.
@@ -193,11 +193,11 @@
 
 	if c.LocalSrcPath != "" {
 		/* #nosec G203 */
-		return template.URL("file:///" + escape(c.LocalSrcPath)), template.URL(tag)
+		return "file:///" + escape(c.LocalSrcPath), template.URL(tag)
 	}
 	if c.RemoteSrcPath != "" {
 		/* #nosec G203 */
-		return template.URL("file:///" + escape(c.RemoteSrcPath)), template.URL(tag)
+		return "file:///" + escape(c.RemoteSrcPath), template.URL(tag)
 	}
 	return "", ""
 }
@@ -210,26 +210,26 @@
 	return parts[0], parts[1]
 }
 
-// "v0.0.0-20200223170610-d5e6a3e2c0ae"
+// "v0.0.0-20200223170610-d5e6a3e2c0ae".
 var reVersion = regexp.MustCompile(`v\d+\.\d+\.\d+\-\d+\-([a-f0-9]+)`)
 
 func splitTag(s string) (string, string, template.URL) {
 	// Default to branch master for non-versioned dependencies. It's not
 	// optimal but it's better than nothing?
 	// TODO(maruel): Replace with HEAD.
-	i := strings.IndexByte(s, '@')
-	if i == -1 {
+	before, after, ok := strings.Cut(s, "@")
+	if !ok {
 		// No tag was found.
 		return s, "master", "master"
 	}
 	// We got a versioned go module.
-	tag := s[i+1:]
+	tag := after
 	srcTag := tag
 	if m := reVersion.FindStringSubmatch(tag); len(m) != 0 {
 		srcTag = m[1]
 	}
 	/* #nosec G203 */
-	return s[:i], url.QueryEscape(srcTag), template.URL(url.QueryEscape(tag))
+	return before, url.QueryEscape(srcTag), template.URL(url.QueryEscape(tag))
 }
 
 // symbol is the hashtag to use to refer to the symbol when looking at
diff --git a/stack/html_test.go b/stack/html_test.go
index 85cda9a..4112532 100644
--- a/stack/html_test.go
+++ b/stack/html_test.go
@@ -8,6 +8,7 @@
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"go/build"
 	"html/template"
@@ -16,6 +17,7 @@
 	"os"
 	"regexp"
 	"runtime"
+	"strconv"
 	"strings"
 	"testing"
 
@@ -185,7 +187,6 @@
 		},
 	}
 	for i, line := range data {
-		line := line
 		t.Run(fmt.Sprintf("%d-%s", i, line.name), func(t *testing.T) {
 			t.Parallel()
 			url, branch := getSrcBranchURL(&line.c)
@@ -232,8 +233,8 @@
 		},
 	}
 	for i, line := range data {
-		line := line
-		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+		t.Run(strconv.Itoa(i), func(t *testing.T) {
+			t.Parallel()
 			if s := symbol(&line.in); s != line.want {
 				t.Fatalf("%q != %q", s, line.want)
 			}
@@ -268,7 +269,7 @@
 func BenchmarkAggregated_ToHTML(b *testing.B) {
 	b.ReportAllocs()
 	s, _, err := ScanSnapshot(bytes.NewReader(internaltest.StaticPanicwebOutput()), io.Discard, DefaultOpts())
-	if err != io.EOF {
+	if !errors.Is(err, io.EOF) {
 		b.Fatal(err)
 	}
 	if s == nil {
@@ -276,7 +277,7 @@
 	}
 	a := s.Aggregate(AnyPointer)
 	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
+	for range b.N {
 		if err := a.ToHTML(io.Discard, ""); err != nil {
 			b.Fatal(err)
 		}
@@ -309,54 +310,50 @@
 		},
 		Buckets: []*Bucket{
 			{
-				Signature: Signature{
-					State: "chan receive",
-					Stack: Stack{
-						Calls: []Call{
-							newCall(
-								"main.func·001",
-								Args{Values: []Arg{{Value: 0x11000000}, {Value: 2}}},
-								"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
-								72),
-							{
-								Func:          newFunc("sliceInternal"),
-								Args:          Args{Values: []Arg{{Value: 0x11000000}, {Value: 2}}},
-								RemoteSrcPath: "/golang/src/sort/slices.go",
-								Line:          72,
-								Location:      Stdlib,
-							},
-							{
-								Func:          newFunc("Slice"),
-								Args:          Args{Values: []Arg{{Value: 0x11000000}, {Value: 2}}},
-								RemoteSrcPath: "/golang/src/sort/slices.go",
-								Line:          72,
-								Location:      Stdlib,
-							},
-							newCall(
-								"DoStuff",
-								Args{Values: []Arg{{Value: 0x11000000}, {Value: 2}}},
-								"/gopath/src/foo/bar.go",
-								72),
-							newCall(
-								"doStuffInternal",
-								Args{
-									Values: []Arg{{Value: 0x11000000}, {Value: 2}},
-									Elided: true,
-								},
-								"/gopath/src/foo/bar.go",
-								72),
+				State: "chan receive",
+				Stack: Stack{
+					Calls: []Call{
+						newCall(
+							"main.func·001",
+							Args{Values: []Arg{{Value: 0x11000000}, {Value: 2}}},
+							"/gopath/src/github.com/maruel/panicparse/stack/stack.go",
+							72),
+						{
+							Func:          newFunc("sliceInternal"),
+							Args:          Args{Values: []Arg{{Value: 0x11000000}, {Value: 2}}},
+							RemoteSrcPath: "/golang/src/sort/slices.go",
+							Line:          72,
+							Location:      Stdlib,
 						},
+						{
+							Func:          newFunc("Slice"),
+							Args:          Args{Values: []Arg{{Value: 0x11000000}, {Value: 2}}},
+							RemoteSrcPath: "/golang/src/sort/slices.go",
+							Line:          72,
+							Location:      Stdlib,
+						},
+						newCall(
+							"DoStuff",
+							Args{Values: []Arg{{Value: 0x11000000}, {Value: 2}}},
+							"/gopath/src/foo/bar.go",
+							72),
+						newCall(
+							"doStuffInternal",
+							Args{
+								Values: []Arg{{Value: 0x11000000}, {Value: 2}},
+								Elided: true,
+							},
+							"/gopath/src/foo/bar.go",
+							72),
 					},
 				},
 				IDs:   []int{1, 2},
 				First: true,
 			},
 			{
-				IDs: []int{3},
-				Signature: Signature{
-					State: "running",
-					Stack: Stack{Elided: true},
-				},
+				IDs:   []int{3},
+				State: "running",
+				Stack: Stack{Elided: true},
 			},
 		},
 	}
diff --git a/stack/reader.go b/stack/reader.go
index b7fe693..5409e8f 100644
--- a/stack/reader.go
+++ b/stack/reader.go
@@ -91,7 +91,7 @@
 	var d []byte
 	for {
 		f, err := r.readSlice()
-		if err != errBufferFull {
+		if !errors.Is(err, errBufferFull) {
 			if d == nil {
 				return f, err
 			}
diff --git a/stack/source.go b/stack/source.go
index 83071c6..19f029d 100644
--- a/stack/source.go
+++ b/stack/source.go
@@ -39,7 +39,7 @@
 			continue
 		}
 		if err1 := c.loadFile(g.Stack.Calls[i].LocalSrcPath); err1 != nil {
-			//log.Printf("%s", err)
+			// log.Printf("%s", err)
 			err = err1
 		}
 		if p := c.parsed[call.LocalSrcPath]; p != nil {
@@ -113,7 +113,7 @@
 
 // getFuncAST gets the callee site function AST representation for the code
 // inside the function f at line l.
-func (p *parsedFile) getFuncAST(f string, l int) (d *ast.FuncDecl, err error) {
+func (p *parsedFile) getFuncAST(_ string, l int) (d *ast.FuncDecl, err error) {
 	if len(p.lineToByteOffset) <= l {
 		// The line number in the stack trace line does not exist in the file. That
 		// can only mean that the sources on disk do not match the sources used to
@@ -146,14 +146,14 @@
 			// be edge cases, like:
 			//   a = A{Foo(), Bar()}
 			d = lastFunc
-			//p.processNode(call, n)
+			// p.processNode(call, n)
 			return false
 		} else if f, ok := n.(*ast.FuncDecl); ok {
 			lastFunc = f
 		}
 		return true
 	})
-	return
+	return d, err
 }
 
 func name(n ast.Node) string {
@@ -201,7 +201,7 @@
 	case *ast.MapType:
 		return fmt.Sprintf("map[%s]%s", name(arg.Key), name(arg.Value)), false
 	case *ast.ChanType:
-		return fmt.Sprintf("chan %s", name(arg.Value)), false
+		return "chan " + name(arg.Value), false
 	default:
 		// TODO(maruel): Implement anything missing.
 		return "<unknown>", false
@@ -231,7 +231,7 @@
 		if mult == 0 {
 			mult = 1
 		}
-		for i := 0; i < mult; i++ {
+		for range mult {
 			types = append(types, t)
 		}
 	}
@@ -340,9 +340,10 @@
 			})
 			str = fmt.Sprintf("%s(%s, len=%s)", t, name, lenStr)
 		default:
-			if strings.HasPrefix(t, "*") {
+			switch {
+			case strings.HasPrefix(t, "*"):
 				str = fmt.Sprintf("%s(%s)", t, popName())
-			} else if strings.HasPrefix(t, "[]") {
+			case strings.HasPrefix(t, "[]"):
 				name := popName()
 				lenStr := popFmt(func(v uint64) string {
 					return strconv.FormatUint(v, 10)
@@ -351,13 +352,13 @@
 					return strconv.FormatUint(v, 10)
 				})
 				str = fmt.Sprintf("%s(%s len=%s cap=%s)", t, name, lenStr, capStr)
-			} else {
+			default:
 				if i < len(call.Args.Values) && call.Args.Values[i].IsAggregate {
 					// If top-level argument is an aggregate-type, include each
 					// of its sub-arguments.
 					v := &call.Args.Values[i].Fields
 					var fields []string
-					v.walk(func(arg *Arg) {
+					v.walk(func(_ *Arg) {
 						fields = append(fields, popName())
 					})
 					if v.Elided {
diff --git a/stack/source_test.go b/stack/source_test.go
index d107cf2..e927a86 100644
--- a/stack/source_test.go
+++ b/stack/source_test.go
@@ -29,7 +29,7 @@
 // goarchList is a list of GOARCH values.
 type goarchList []string
 
-var allPlatforms goarchList = nil
+var allPlatforms goarchList
 
 func TestAugment(t *testing.T) {
 	t.Parallel()
@@ -1350,8 +1350,6 @@
 	}
 
 	for i, line := range data {
-		i := i
-		line := line
 		t.Run(fmt.Sprintf("%d-%s", i, line.name), func(t *testing.T) {
 			t.Parallel()
 			for _, arch := range line.archBlock {
@@ -1372,22 +1370,14 @@
 			input := strings.Join(lines, "\n")
 
 			// Create one temporary directory by subtest.
-			root, err := os.MkdirTemp("", "stack")
-			if err != nil {
-				t.Fatalf("failed to create temporary directory: %v", err)
-			}
-			defer func() {
-				if err2 := os.RemoveAll(root); err2 != nil {
-					t.Fatalf("failed to remove temporary directory %q: %v", root, err2)
-				}
-			}()
+			root := t.TempDir()
 			main := filepath.Join(root, "main.go")
 			if err := os.WriteFile(main, []byte(input), 0500); err != nil {
 				t.Fatalf("failed to write %q: %v", main, err)
 			}
 
 			if runtime.GOOS == "windows" {
-				root = strings.Replace(root, pathSeparator, "/", -1)
+				root = strings.ReplaceAll(root, pathSeparator, "/")
 			}
 			const prefix = "/root"
 			for j, c := range line.want.Calls {
@@ -1443,7 +1433,7 @@
 		t.Errorf("augment() returned %v", err)
 	}
 	got := s.Goroutines[0].Signature.Stack
-	zapPointers(t, &want, &got)
+	zapPointers(&want, &got)
 
 	// With a non-pointer method, elided argument can be shown. It's only for
 	// test case "non-pointer method".
@@ -1467,15 +1457,7 @@
 
 func TestAugmentErr(t *testing.T) {
 	t.Parallel()
-	root, err := os.MkdirTemp("", "stack")
-	if err != nil {
-		t.Fatalf("failed to create temporary directory: %v", err)
-	}
-	defer func() {
-		if err2 := os.RemoveAll(root); err2 != nil {
-			t.Fatalf("failed to remove temporary directory %q: %v", root, err2)
-		}
-	}()
+	root := t.TempDir()
 
 	tree := map[string]string{
 		"bad.go":       "bad content",
@@ -1558,17 +1540,17 @@
 	}
 
 	for i, line := range data {
-		line := line
 		t.Run(fmt.Sprintf("%d-%s", i, line.name), func(t *testing.T) {
+			t.Parallel()
 			l := line.line
 			if l == 0 {
 				l = 1
 			}
 			s := Snapshot{
 				Goroutines: []*Goroutine{
-					{Signature: Signature{Stack: Stack{Calls: []Call{
+					{Stack: Stack{Calls: []Call{
 						{LocalSrcPath: filepath.Join(root, line.src), Args: line.args, Line: l},
-					}}}},
+					}}},
 				}}
 			if err := s.augment(); (line.errRe == "") != (err == nil) {
 				t.Fatalf("want: %q; got:  %q", line.errRe, err)
@@ -1584,6 +1566,7 @@
 }
 
 func TestLineToByteOffsets(t *testing.T) {
+	t.Parallel()
 	src := "\n\n\n"
 	want := []int{0, 0, 1, 2, 3}
 	if diff := cmp.Diff(want, lineToByteOffsets([]byte(src))); diff != "" {
@@ -1623,7 +1606,7 @@
 		// Disable both optimization (-N) and inlining (-l).
 		args = append(args, "-gcflags", "-N -l")
 	}
-	cmd := exec.Command("go", append(args, main)...)
+	cmd := exec.CommandContext(t.Context(), "go", append(args, main)...)
 	// Use the Go 1.4 compatible format.
 	cmd.Env = overrideEnv(os.Environ(), "GOTRACEBACK", "1")
 	out, err := cmd.CombinedOutput()
@@ -1635,7 +1618,7 @@
 }
 
 // zapPointers zaps out pointers in got.
-func zapPointers(t *testing.T, want, got *Stack) {
+func zapPointers(want, got *Stack) {
 	for i := range got.Calls {
 		if i >= len(want.Calls) {
 			// When using GOTRACEBACK=2, it'll include runtime.main() and
@@ -1645,22 +1628,22 @@
 			break
 		}
 		gotArgs := got.Calls[i].Args
-		ptrsToReplace := zapPointersInArgs(t, want.Calls[i].Args, gotArgs)
+		ptrsToReplace := zapPointersInArgs(want.Calls[i].Args, gotArgs)
 		for _, ptr := range ptrsToReplace {
 			for k := range gotArgs.Processed {
-				gotArgs.Processed[k] = strings.Replace(gotArgs.Processed[k], ptr, pointerStr, -1)
+				gotArgs.Processed[k] = strings.ReplaceAll(gotArgs.Processed[k], ptr, pointerStr)
 			}
 		}
 	}
 }
 
-func zapPointersInArgs(t *testing.T, want, got Args) (ptrs []string) {
+func zapPointersInArgs(want, got Args) (ptrs []string) {
 	for j := range got.Values {
 		if j >= len(want.Values) {
 			break
 		}
 		if want.Values[j].IsAggregate && got.Values[j].IsAggregate {
-			ptrs = append(ptrs, zapPointersInArgs(t, want.Values[j].Fields, got.Values[j].Fields)...)
+			ptrs = append(ptrs, zapPointersInArgs(want.Values[j].Fields, got.Values[j].Fields)...)
 		} else if want.Values[j].IsPtr && got.Values[j].IsPtr {
 			// Record the existing pointer value and then replace.
 			ptrs = append(ptrs, fmt.Sprintf("0x%x", got.Values[j].Value))
@@ -1672,6 +1655,7 @@
 
 // TestAugmentCall covers signed stack words and unused high bits of narrow values.
 func TestAugmentCall(t *testing.T) {
+	t.Parallel()
 	wantInt := "1311768467015204864"
 	if strconv.IntSize == 32 {
 		wantInt = "-2147483648"
@@ -1697,12 +1681,17 @@
 	}
 	for i, line := range data {
 		t.Run(fmt.Sprintf("%s/%d", line.typ, i), func(t *testing.T) {
+			t.Parallel()
 			f, err := parser.ParseFile(token.NewFileSet(), "args.go", "package main; func f(v "+line.typ+") {}", 0)
 			if err != nil {
 				t.Fatal(err)
 			}
 			call := Call{Args: Args{Values: []Arg{{Value: line.word}}}}
-			augmentCall(&call, f.Decls[0].(*ast.FuncDecl))
+			decl, ok := f.Decls[0].(*ast.FuncDecl)
+			if !ok {
+				t.Fatal("expected function declaration")
+			}
+			augmentCall(&call, decl)
 			if diff := cmp.Diff([]string{line.want}, call.Args.Processed); diff != "" {
 				t.Fatalf("decoded argument (-want +got):\n%s", diff)
 			}
diff --git a/stack/stack.go b/stack/stack.go
index 4d936bd..5254327 100644
--- a/stack/stack.go
+++ b/stack/stack.go
@@ -56,7 +56,7 @@
 	//  - foo  (C code)
 	//
 	// The function is optimized to reduce its memory usage.
-	endPkg := 0
+	var endPkg int
 	if lastSlash := strings.LastIndexByte(raw, '/'); lastSlash != -1 {
 		// Cut the path elements.
 		r := strings.IndexByte(raw[lastSlash+1:], '.')
@@ -83,8 +83,8 @@
 		cut := f.Name[:idx]
 		// TODO(go1.20): switch to strings.CutSuffix
 		const inGoroutineSuffix = " in goroutine"
-		if strings.HasSuffix(cut, inGoroutineSuffix) {
-			f.Name = strings.TrimSuffix(cut, inGoroutineSuffix)
+		if before, ok := strings.CutSuffix(cut, inGoroutineSuffix); ok {
+			f.Name = before
 		}
 	}
 	f.DirName = f.ImportPath
@@ -175,6 +175,20 @@
 	pointerCeiling = uint64((^uint(0)) >> 1)
 )
 
+// SleepString returns a string "N-M minutes" if the goroutine(s) slept for a
+// long time.
+//
+// Returns an empty string otherwise.
+func (s *Signature) SleepString() string {
+	if s.SleepMax == 0 {
+		return ""
+	}
+	if s.SleepMin != s.SleepMax {
+		return fmt.Sprintf("%d~%d minutes", s.SleepMin, s.SleepMax)
+	}
+	return fmt.Sprintf("%d minutes", s.SleepMax)
+}
+
 // equal returns true only if both arguments are exactly equal.
 func (a *Arg) equal(r *Arg) bool {
 	return a.similar(r, ExactFlags)
@@ -281,14 +295,15 @@
 	}
 	for i, l := range a.Values {
 		rv := &r.Values[i]
-		if l.IsAggregate {
+		switch {
+		case l.IsAggregate:
 			out.Values[i].IsAggregate = true
 			out.Values[i].Fields = l.Fields.merge(&rv.Fields)
-		} else if !l.equal(rv) {
+		case !l.equal(rv):
 			out.Values[i].Name = "*"
 			out.Values[i].Value = l.Value
 			out.Values[i].IsPtr = l.IsPtr
-		} else {
+		default:
 			out.Values[i] = l
 		}
 	}
@@ -711,19 +726,13 @@
 
 // merge merges two similar Signature, zapping out differences.
 func (s *Signature) merge(r *Signature) *Signature {
-	min := s.SleepMin
-	if r.SleepMin < min {
-		min = r.SleepMin
-	}
-	max := s.SleepMax
-	if r.SleepMax > max {
-		max = r.SleepMax
-	}
+	minSleep := min(s.SleepMin, r.SleepMin)
+	maxSleep := max(s.SleepMax, r.SleepMax)
 	return &Signature{
 		State:     s.State,     // Drop right side.
 		CreatedBy: s.CreatedBy, // Drop right side.
-		SleepMin:  min,
-		SleepMax:  max,
+		SleepMin:  minSleep,
+		SleepMax:  maxSleep,
 		Stack:     *s.Stack.merge(&r.Stack),
 		Locked:    s.Locked || r.Locked, // TODO(maruel): This is weirdo.
 	}
@@ -755,20 +764,6 @@
 	return false
 }
 
-// SleepString returns a string "N-M minutes" if the goroutine(s) slept for a
-// long time.
-//
-// Returns an empty string otherwise.
-func (s *Signature) SleepString() string {
-	if s.SleepMax == 0 {
-		return ""
-	}
-	if s.SleepMin != s.SleepMax {
-		return fmt.Sprintf("%d~%d minutes", s.SleepMin, s.SleepMax)
-	}
-	return fmt.Sprintf("%d minutes", s.SleepMax)
-}
-
 // updateLocations calls updateLocations on both CreatedBy and Stack and
 // returns true if they were both resolved.
 func (s *Signature) updateLocations(goroot, localgoroot string, localgomods, gopaths map[string]string) bool {
@@ -782,6 +777,7 @@
 	// Signature is the stack trace, internal bits, state, which call site
 	// created it, etc.
 	Signature
+
 	// ID is the goroutine id.
 	ID int
 	// First is the goroutine first printed, normally the one that crashed.
diff --git a/stack/stack_test.go b/stack/stack_test.go
index 16d030c..f326475 100644
--- a/stack/stack_test.go
+++ b/stack/stack_test.go
@@ -208,7 +208,6 @@
 		},
 	}
 	for i, line := range data {
-		line := line
 		t.Run(fmt.Sprintf("%d-%s", i, line.name), func(t *testing.T) {
 			t.Parallel()
 			c := newCall(line.f, Args{}, line.s, 153)
@@ -338,7 +337,7 @@
 )
 
 func init() {
-	goroot = strings.Replace(build.Default.GOROOT, "\\", "/", -1)
+	goroot = strings.ReplaceAll(build.Default.GOROOT, "\\", "/")
 	gopaths = map[string]string{}
 	for i, p := range getGOPATHs() {
 		gopaths[p] = p
@@ -353,7 +352,7 @@
 		panic(err)
 	}
 	// Our internal functions work with '/' as path separator.
-	pwd = strings.Replace(pwd, "\\", "/", -1)
+	pwd = strings.ReplaceAll(pwd, "\\", "/")
 	gomods = map[string]string{}
 	gmc := gomodCache{}
 	if prefix, path := gmc.isGoModule(splitPath(pwd)); prefix != "" {
diff --git a/stack/webstack/example_test.go b/stack/webstack/example_test.go
index a600bed..242cd48 100644
--- a/stack/webstack/example_test.go
+++ b/stack/webstack/example_test.go
@@ -64,7 +64,10 @@
 		}
 
 		// Must be called before touching req.Form.
-		req.ParseForm()
+		if err := req.ParseForm(); err != nil {
+			http.Error(w, err.Error(), http.StatusBadRequest)
+			return
+		}
 		// Disables source scanning by default since it's heavy.
 		if req.FormValue("augment") == "" {
 			req.Form.Set("augment", "0")
diff --git a/stack/webstack/webstack.go b/stack/webstack/webstack.go
index 588060e..d6fc9cb 100644
--- a/stack/webstack/webstack.go
+++ b/stack/webstack/webstack.go
@@ -10,6 +10,7 @@
 
 import (
 	"bytes"
+	"errors"
 	"io"
 	"net/http"
 	"runtime"
@@ -44,7 +45,7 @@
 // similarity: (default: "anypointer") Can be one of stack.Similarity value in
 // lowercase: "exactflags", "exactlines", "anypointer" or "anyvalue".
 func SnapshotHandler(w http.ResponseWriter, req *http.Request) {
-	if req.Method != "GET" {
+	if req.Method != http.MethodGet {
 		http.Error(w, "invalid method", http.StatusMethodNotAllowed)
 		return
 	}
@@ -112,15 +113,12 @@
 		if len(buf) >= maxmem {
 			break
 		}
-		l := len(buf) * 2
-		if l > maxmem {
-			l = maxmem
-		}
+		l := min(len(buf)*2, maxmem)
 		buf = make([]byte, l)
 	}
 	s, _, err := stack.ScanSnapshot(bytes.NewReader(buf), io.Discard, opts)
 	// That's expected.
-	if err == io.EOF {
+	if errors.Is(err, io.EOF) {
 		err = nil
 	}
 	return s, err
diff --git a/stack/webstack/webstack_test.go b/stack/webstack/webstack_test.go
index 76db65f..901b2ba 100644
--- a/stack/webstack/webstack_test.go
+++ b/stack/webstack/webstack_test.go
@@ -8,11 +8,13 @@
 
 import (
 	"context"
+	"net/http"
 	"net/http/httptest"
 	"sync"
 	"testing"
 )
 
+//nolint:paralleltest // Concurrent handlers change the goroutines being captured.
 func TestSnapshotHandler(t *testing.T) {
 	data := []string{
 		"/debug",
@@ -24,10 +26,10 @@
 		"/debug?similarity=anypointer",
 		"/debug?similarity=anyvalue",
 	}
+	//nolint:paralleltest // Snapshot contents depend on active goroutines.
 	for _, url := range data {
-		url := url
 		t.Run(url, func(t *testing.T) {
-			req := httptest.NewRequest("GET", url, nil)
+			req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, url, nil)
 			w := httptest.NewRecorder()
 			SnapshotHandler(w, req)
 			if w.Code != 200 {
@@ -45,10 +47,9 @@
 		"/debug?similarity=alike",
 	}
 	for _, url := range data {
-		url := url
 		t.Run(url, func(t *testing.T) {
 			t.Parallel()
-			req := httptest.NewRequest("GET", url, nil)
+			req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, url, nil)
 			w := httptest.NewRecorder()
 			SnapshotHandler(w, req)
 			if w.Code != 400 {
@@ -60,7 +61,7 @@
 
 func TestSnapshotHandler_Method_POST(t *testing.T) {
 	t.Parallel()
-	req := httptest.NewRequest("POST", "/debug", nil)
+	req := httptest.NewRequestWithContext(t.Context(), http.MethodPost, "/debug", nil)
 	w := httptest.NewRecorder()
 	SnapshotHandler(w, req)
 	if w.Code != 405 {
@@ -68,6 +69,7 @@
 	}
 }
 
+//nolint:paralleltest // This test intentionally creates thousands of goroutines.
 func TestSnapshotHandler_LargeMemory(t *testing.T) {
 	// Try to create a stack frame over 1MiB in size when serialized to string.
 	// This is tricky since this is dependent on many factors out of our control.
@@ -81,20 +83,18 @@
 	// use more than 1MiB of call stack. We must not put it too high or it'll
 	// crash on Travis.
 	const parallel = 2500
-	for i := 0; i < parallel; i++ {
-		wg.Add(1)
-		go func() {
-			defer wg.Done()
+	for range parallel {
+		wg.Go(func() {
 			alive <- struct{}{}
 			dummy(ctx, &a, &a, &a, &a, &a, &a, &a, &a, &a)
-		}()
+		})
 	}
-	for i := 0; i < parallel; i++ {
+	for range parallel {
 		<-alive
 	}
 
 	// Normal.
-	req := httptest.NewRequest("GET", "/debug", nil)
+	req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/debug", nil)
 	w := httptest.NewRecorder()
 	SnapshotHandler(w, req)
 	if w.Code != 200 {
@@ -102,7 +102,7 @@
 	}
 
 	// Cut off. That's 1<<20 + 1
-	req = httptest.NewRequest("GET", "/debug?maxmem=1048577", nil)
+	req = httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/debug?maxmem=1048577", nil)
 	w = httptest.NewRecorder()
 	SnapshotHandler(w, req)
 	// It can result in a 500 because the cut off is arbitrary, making parsing to
@@ -119,9 +119,9 @@
 	// TODO(maruel): We should hook runtime.Stack() to make it a deterministic
 	// output with internaltest.StaticPanicwebOutput().
 	b.ReportAllocs()
-	req := httptest.NewRequest("GET", "/", nil)
+	req := httptest.NewRequestWithContext(b.Context(), http.MethodGet, "/", nil)
 	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
+	for range b.N {
 		w := httptest.NewRecorder()
 		SnapshotHandler(w, req)
 		if w.Code != 200 {
@@ -130,6 +130,7 @@
 	}
 }
 
+//nolint:revive,unparam // Arguments intentionally enlarge the captured stack frame.
 func dummy(ctx context.Context, a1, a2, a3, a4, a5, a6, a7, a8, a9 *int) {
 	<-ctx.Done()
 }