Skip to content

runtime: AddCleanup is not robust to arg values pointing to struct embedded data #72001

@matttproud

Description

@matttproud

Go version

go version go1.25-20250216-RC00 cl/727547642 +d524e1eccd X:fieldtrack,boringcrypto linux/amd64

Output of go env in your module/workspace:

AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/usr/local/google/home/mtp/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/usr/local/google/home/mtp/.config/go/env'
GOEXE=''
GOEXPERIMENT='fieldtrack,boringcrypto'
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3976460735=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='/usr/local/google/home/mtp/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/usr/local/google/home/mtp/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/lib/google-golang'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/usr/local/google/home/mtp/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/lib/google-golang/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.25-20250216-RC00 cl/727547642 +d524e1eccd X:fieldtrack,boringcrypto'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

I noticed that runtime.AddCleanup does not robustly handle a case like this:

type NeedsCleanup struct { ... }
func (c *NeedsCleanup) Close() { ... }

type Embeds struct { NeedsCleanup }

func New() *Embeds {
  var e Embeds
  runtime.AddCleanup(&e, (*NeedsCleanup).Close, &e.NeedsCleanup)
  ...
  return &e
}

What did you see happen?

The runtime panics:

panic: runtime.AddCleanup: ptr is equal to arg, cleanup will never run

What did you expect to see?

I would expect that this snippet of code to be more robust to the case of struct embedding (if that is at all possible), or the documentation for runtime.AddFunc also mention that naive struct embedding of the data that needs to be cleaned up into an outer struct won't work due to how alignment of memory works.

We found a workaround by amending the definition of the outer type as follows:

type NeedsCleanup struct { ... }
func (c *NeedsCleanup) Close() { ... }

type Embeds struct {
  _ int  // Ensures that NeedsCleanup's address does not align with Embeds for finalization.
  NeedsCleanup
}

func New() *Embeds {
  var e Embeds
  runtime.AddCleanup(&e, (*NeedsCleanup).Close, &e.NeedsCleanup)
  ...
  return &e
}

Metadata

Metadata

Labels

BugReportIssues describing a possible bug in the Go implementation.DocumentationIssues describing a change to documentation.NeedsFixThe path to resolution is known, but the work has not been done.compiler/runtimeIssues related to the Go compiler and/or runtime.

Type

No type

Projects

Status

Todo

Relationships

None yet

Development

No branches or pull requests

Issue actions