From d3742e4393fe39249aaec7ae2914beaf3009924f Mon Sep 17 00:00:00 2001 From: Andy Date: Mon, 5 Jan 2026 15:25:42 +0300 Subject: [PATCH] fix(metal): NSString double-free on autorelease pool drain Problem: - NSString() used stringWithUTF8String: which returns autoreleased object - Callers then called Release() on these objects - When autorelease pool drained, it released already-freed objects - Caused intermittent panics in CreateRenderPipeline on macOS CI Root cause: - stringWithUTF8String: returns +0 autoreleased object (owned by pool) - Manual Release() decrements retain count below 0 - pool.Drain() tries to release invalid object = crash Fix: - Use alloc/initWithUTF8String: instead of stringWithUTF8String: - Returns +1 retained object owned by caller - Caller's Release() correctly decrements to 0 and frees - pool.Drain() has nothing extra to release Reference: Objective-C Memory Management Rules - alloc/init returns +1 retained - convenience methods (stringWith...) return +0 autoreleased --- hal/metal/objc.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/hal/metal/objc.go b/hal/metal/objc.go index b3b7294..3af7964 100644 --- a/hal/metal/objc.go +++ b/hal/metal/objc.go @@ -404,14 +404,21 @@ func (p *AutoreleasePool) Drain() { } // NSString creates an NSString from a Go string. +// Returns a +1 retained object that the caller must Release(). +// Uses alloc/initWithUTF8String: instead of stringWithUTF8String: +// to return a retained object (not autoreleased) for explicit ownership. func NSString(s string) ID { + nsStringClass := ID(GetClass("NSString")) if len(s) == 0 { - return MsgSend(ID(GetClass("NSString")), Sel("string")) + // Use alloc/init for empty string to get +1 retained object + obj := MsgSend(nsStringClass, Sel("alloc")) + return MsgSend(obj, Sel("init")) } cstr := append([]byte(s), 0) + obj := MsgSend(nsStringClass, Sel("alloc")) return MsgSend( - ID(GetClass("NSString")), - Sel("stringWithUTF8String:"), + obj, + Sel("initWithUTF8String:"), uintptr(unsafe.Pointer(&cstr[0])), ) }