Skip to content

No warning when nil cleanup func is deferred before checking error #1687

@starius

Description

@starius
  • staticcheck -version: staticcheck 2025.1.1 (0.6.1)
  • staticcheck -debug.version:
staticcheck 2025.1.1 (0.6.1)

Compiled with Go version: go1.25.2
Main module:
	honnef.co/go/[email protected] (sum: h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=)
Dependencies:
	github.com/BurntSushi/[email protected] (sum: h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=)
	golang.org/x/exp/[email protected] (sum: h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ=)
	golang.org/x/[email protected] (sum: h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=)
	golang.org/x/[email protected] (sum: h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=)
	golang.org/x/[email protected] (sum: h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=)
  • go version: go version go1.25.2 linux/amd64
  • go env:
AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/user/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/user/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3826221701=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/home/user/nautilus/go.mod'
GOMODCACHE='/home/user/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/user'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/nix/store/k1kn1c59ss7nq6agdppzq3krwmi3xqy4-go-1.25.2/share/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/user/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/nix/store/k1kn1c59ss7nq6agdppzq3krwmi3xqy4-go-1.25.2/share/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.25.2'
GOWORK=''
PKG_CONFIG='pkg-config'
  • Command ran: staticcheck ./... (run inside a module containing the reproducer below)

Code that triggers the bug

Create a module containing this single file (or run go run on it directly):

package main

import "errors"

var errStop = errors.New("stop")

func acquire(flag bool) (func(), error) {
        if flag {
                return nil, errStop
        }

        return func() {}, nil
}

func use(flag bool) error {
        release, err := acquire(flag)
        defer release()
        if err != nil {
                return err
        }

        return nil
}

func main() {
        _ = use(true)
}

Observed behavior

Running this program consistently crashes with panic: runtime error: invalid memory address or nil pointer dereference. When flag is true, acquire reports an error and returns a nil cleanup function. use unconditionally defers release() before inspecting err, so the defer executes with release == nil and the process dies. This pattern occurred in a long-lived production service: a shutdown path hit the nil defer only after years in prod, immediately taking down the whole server.

Expected behavior

Staticcheck (SA5001 or a new check) should warn whenever a func() return value is deferred before verifying that the accompanying error is nil. The tool already flags IO closers that are deferred prior to error handling, but it stays silent for cleanup functions stored in variables. Given that nil func() values panic when called, emitting a diagnostic here would catch a class of severe crashes that are otherwise very hard to spot in code review.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions