From 8ca1ab92b2d8e1788cd5e69aa4b49f09bb6fb8c8 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Sun, 9 Aug 2026 09:55:09 -0700 Subject: [PATCH] fix(ffi): route __invokeBase through the host object's selector dispatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit invokeNativeApiJsiBaseMethod called the raw callObjCSelector, bypassing NativeApiObjectHostObject::callObjectSelector's initializer bookkeeping. Every prototype-resolved instance call goes through __invokeBase, so init-family selectors invoked that way (UIColor.alloc().initWith... in @nativescript/core's Color) consumed their receiver while the wrapper and the bridge's round-trip/expando maps kept dangling claims — NSZombies reports '-[UIPlaceholderColor retain]: message sent to deallocated instance' during the first text draw. Dispatch through the host object so initializers disown the consumed receiver and forget its cached values. Co-Authored-By: Claude Fable 5 --- .../ffi/shared/jsi/NativeApiJsiClassBuilder.h | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiClassBuilder.h b/NativeScript/ffi/shared/jsi/NativeApiJsiClassBuilder.h index bf4d3b2fc..7ef6bc3f8 100644 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiClassBuilder.h +++ b/NativeScript/ffi/shared/jsi/NativeApiJsiClassBuilder.h @@ -745,8 +745,12 @@ Value invokeNativeApiJsiBaseMethod( throw facebook::jsi::JSError(runtime, "__invokeBase receiver is not native."); } - id receiver = - receiverObject.getHostObject(runtime)->object(); + // Dispatch through the host object so initializer selectors get their + // receiver-consumption bookkeeping (disown + round-trip/expando forget); + // calling the raw selector path here leaves dangling ownership of the + // placeholder an init consumes (e.g. UIColor.alloc().initWith...). + auto hostObject = receiverObject.getHostObject(runtime); + id receiver = hostObject->object(); std::string memberName = args[2].asString(runtime).utf8(runtime); size_t actualArgc = count - 3; @@ -760,9 +764,8 @@ Value invokeNativeApiJsiBaseMethod( if (actualArgc == 0) { Class dispatchClass = dispatchSuperclassForJsiDerivedReceiver(receiver, baseClass); - return callObjCSelector(runtime, bridge, receiver, false, - propertyMember->selectorName, propertyMember, - nullptr, 0, dispatchClass); + return hostObject->callObjectSelector(runtime, propertyMember->selectorName, + propertyMember, nullptr, 0, dispatchClass); } if (actualArgc == 1 && !propertyMember->setterSelectorName.empty() && !propertyMember->readonly) { @@ -771,9 +774,8 @@ Value invokeNativeApiJsiBaseMethod( NativeApiMember setterMember = *propertyMember; setterMember.selectorName = propertyMember->setterSelectorName; setterMember.signatureOffset = propertyMember->setterSignatureOffset; - return callObjCSelector(runtime, bridge, receiver, false, - setterMember.selectorName, &setterMember, - args + 3, actualArgc, dispatchClass); + return hostObject->callObjectSelector(runtime, setterMember.selectorName, &setterMember, + args + 3, actualArgc, dispatchClass); } } } @@ -784,6 +786,6 @@ Value invokeNativeApiJsiBaseMethod( Class dispatchClass = dispatchSuperclassForJsiDerivedReceiver(receiver, baseClass); - return callObjCSelector(runtime, bridge, receiver, false, member->selectorName, - member, args + 3, actualArgc, dispatchClass); + return hostObject->callObjectSelector(runtime, member->selectorName, member, args + 3, + actualArgc, dispatchClass); }