Repository navigation
Conversation
ffi_prep_cif stores rtype and the address of argtypes' backing array inside the CIF for as long as the CIF is used. A closure built from that CIF can be invoked long after PrepCIF returns, but nothing kept either value alive past the call itself. Go 1.25's escape analysis started stack-allocating the argtypes array instead of heap-allocating it, so that memory got reused before AppKit ever invoked the closure. macOS 26's libffi added a stricter arg-type check that turns this memory corruption into a reproducible SIGABRT on launch. Fixes progrium#286. Wrap rtype and argtypes in a cgo.Handle so they stay reachable for as long as the CIF does, and free that handle once the CIF is collected. This also forces the values onto the heap, closing off the escape analysis path that triggered the bug. Verified with -gcflags='github.com/progrium/darwinkit/objc=-m': before the fix, the argtypes array at the five affected call sites (call.go, block.go, protocol.go, type_convertion.go) does not escape; after, all five escape to heap.
Owner
|
LGTM, can anybody verify. Will merge with a thumbs up Also @pmoust thank you for this |
Joaquimmnetto
added a commit
to Joaquimmnetto/macos-ghpr-bar
that referenced
this pull request
Aug 20, 2026
darwinkit's objc/ffi.PrepCIF hands a Go-allocated arg-types array to ffi_prep_cif, which keeps a raw pointer to it inside the ffi_cif for as long as any closure built from it can still be invoked. Nothing kept that array reachable past PrepCIF returning, so on Go 1.25+ (whose smarter escape analysis stack-allocates it instead of heap-allocating it) the memory gets reused before AppKit ever calls back into Go, corrupting the CIF and aborting inside ffi_closure_SYSV_inner on the very first delegate callback (applicationDidFinishLaunching:) - reliably 100% of the time on this machine (macOS 15.7.4, Go 1.26.4). Pins the fork/branch backing progrium/darwinkit#289 via a go.mod replace directive until it's merged upstream. Also marks the status bar icon as a template image so it renders correctly in both light and dark menu bars.
|
I tested this in my project (macOS 15.7.4, Go 1.26.4) and works fine. Before I was having |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #286.
ffi_prep_cifstoresrtypeand the address ofargtypes' backingarray inside the CIF for as long as the CIF is used. A closure built
from that CIF (via
CreateClosure) can be invoked long afterPrepCIFreturns, once an AppKit delegate callback fires, but nothingkept either value alive past the call itself.
runtime.KeepAliveonlyextends liveness through the call it's in, not beyond it.
Go 1.25's smarter escape analysis started stack-allocating the
argtypesarray at the call sites incall.go,block.go,protocol.go, andtype_convertion.go, instead of heap-allocating itas earlier Go versions happened to. Once stack-allocated, that memory
gets reused before the closure fires, so
cif->arg_typesends uppointing at garbage. macOS 26's
libffi.dylibadded a stricterarg_types[i]->type > 15check that turns this into a reproducibleSIGABRTon launch, in the very first delegate callback(
applicationDidFinishLaunching:). Full disassembly-level detail isin the issue thread.
This wraps
rtypeandargtypesin acgo.Handlestored alongsidethe CIF, so both stay reachable for as long as the CIF itself does,
and deletes that handle once the CIF is garbage collected via
runtime.SetFinalizer. Passing them through acgo.Handlealso forcesthem onto the heap, which is what closes off the escape-analysis path
that caused the bug.
I considered copying
argtypesintomalloc'd C memory instead (anoption floated in the issue), but that would store Go pointers
(including ones from
MakeStructType) in memory the Go GC doesn'tscan, which is itself a cgo pointer-passing rule violation. The
cgo.Handleapproach avoids that.Verification:
go build -gcflags='github.com/progrium/darwinkit/objc=-m' ./objc/...shows the flip this fix is meant to produce: before,
make([]*ffi...)at all five affected call sites reports "does not escape"; after, all
five report "escapes to heap".
go test ./objc/...passes,go vet ./objc/...is clean,gofmt -lreports no changes needed on the touched files.
status-bar menu app using a closure-based
NSApplicationDelegate) onGo 1.26.1, macOS 26.5 arm64: it reliably
SIGABRTs on launch withoutthis fix and without the
-gcflags "all=-N"workaround, and launchesand stays running with this fix applied and that workaround removed.