From 54b34e055297e98222336d4808f9d4f2779eea06 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Fri, 3 Apr 2026 17:41:21 -0300 Subject: [PATCH 1/3] =?UTF-8?q?Revert=20"fix:=20prevent=20crash=20during?= =?UTF-8?q?=20debug=20on=20fast=20view=20churn=20(like=20with=20HMR)=20(#?= =?UTF-8?q?=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 42a5328f9e95b2298efe067485ac6775718d0510. --- NativeScript/runtime/ArgConverter.mm | 42 ++-------------------------- 1 file changed, 3 insertions(+), 39 deletions(-) diff --git a/NativeScript/runtime/ArgConverter.mm b/NativeScript/runtime/ArgConverter.mm index c4820212..65026dac 100644 --- a/NativeScript/runtime/ArgConverter.mm +++ b/NativeScript/runtime/ArgConverter.mm @@ -1,6 +1,5 @@ #include #include -#include #include "ArgConverter.h" #include "NativeScriptException.h" #include "DictionaryAdapter.h" @@ -8,7 +7,6 @@ #include "Interop.h" #include "Helpers.h" #include "Runtime.h" -#include "RuntimeConfig.h" using namespace v8; using namespace std; @@ -29,27 +27,7 @@ bool callSuper = false; if (instanceMethod) { BaseDataWrapper* wrapper = tns::GetValue(isolate, receiver); - - if (wrapper == nullptr) { - // During fast view churn like HMR in development, JS objects can outlive their - // native wrappers briefly. In Debug, avoid a crash and just skip the native call. - // In Release, assert so crash reporting can capture unexpected cases. - if (RuntimeConfig.IsDebug) { - const char* selectorStr = meta ? meta->selectorAsString() : ""; - const char* jsNameStr = meta ? meta->jsName() : ""; - const char* classNameStr = klass ? class_getName(klass) : ""; - // Suppress duplicate logs: only log once per class+selector for this process. - static std::unordered_set s_logged; - std::string key = std::string(classNameStr) + ":" + selectorStr; - if (s_logged.insert(key).second) { - Log(@"Note: ignore method on non-native receiver (class: %s, selector: %s, jsName: %s, args: %d). Common during HMR.", - classNameStr, selectorStr, jsNameStr, (int)args.Length()); - } - return v8::Undefined(isolate); - } else { - tns::Assert(false, isolate); - } - } + tns::Assert(wrapper != nullptr, isolate); if (wrapper->Type() == WrapperType::ObjCAllocObject) { ObjCAllocDataWrapper* allocWrapper = static_cast(wrapper); @@ -65,21 +43,7 @@ // For extended classes we will call the base method callSuper = isMethodCallback && it != cache->ClassPrototypes.end(); } else { - if (RuntimeConfig.IsDebug) { - const char* selectorStr = meta ? meta->selectorAsString() : ""; - const char* jsNameStr = meta ? meta->jsName() : ""; - const char* classNameStr = klass ? class_getName(klass) : ""; - // Suppress duplicate logs: only log once per class+selector for this process. - static std::unordered_set s_logged; - std::string key = std::string(classNameStr) + ":" + selectorStr; - if (s_logged.insert(key).second) { - Log(@"Note: ignore receiver wrapper type %d (class: %s, selector: %s, jsName: %s). Common during HMR.", - (int)wrapper->Type(), classNameStr, selectorStr, jsNameStr); - } - return v8::Undefined(isolate); - } else { - tns::Assert(false, isolate); - } + tns::Assert(false, isolate); } } @@ -914,7 +878,7 @@ Local thiz = args.This(); Isolate* isolate = args.GetIsolate(); BaseDataWrapper* wrapper = tns::GetValue(isolate, thiz); - if (wrapper == nullptr || wrapper->Type() != WrapperType::ObjCObject) { + if (wrapper == nullptr && wrapper->Type() != WrapperType::ObjCObject) { return; } From 0fd0e8409024677d81700f9ec4f5a379950bfa76 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 22:26:51 +0000 Subject: [PATCH 2/3] fix: throw JS exception instead of silent warning for disposed native object calls in debug mode Agent-Logs-Url: https://github.com/NativeScript/ios/sessions/695d1d62-9bc9-46d4-871f-9bfccf2f5ee2 Co-authored-by: NathanWalker <457187+NathanWalker@users.noreply.github.com> --- NativeScript/runtime/ArgConverter.mm | 36 +++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/NativeScript/runtime/ArgConverter.mm b/NativeScript/runtime/ArgConverter.mm index 65026dac..4e6564be 100644 --- a/NativeScript/runtime/ArgConverter.mm +++ b/NativeScript/runtime/ArgConverter.mm @@ -7,6 +7,7 @@ #include "Interop.h" #include "Helpers.h" #include "Runtime.h" +#include "RuntimeConfig.h" using namespace v8; using namespace std; @@ -27,7 +28,24 @@ bool callSuper = false; if (instanceMethod) { BaseDataWrapper* wrapper = tns::GetValue(isolate, receiver); - tns::Assert(wrapper != nullptr, isolate); + + if (wrapper == nullptr) { + // During fast view churn like HMR in development, JS objects can outlive their + // native wrappers briefly. In Debug, throw a catchable JS error instead of crashing. + // In Release, assert so crash reporting can capture unexpected cases. + if (RuntimeConfig.IsDebug) { + const char* selectorStr = meta ? meta->selectorAsString() : ""; + const char* jsNameStr = meta ? meta->jsName() : ""; + const char* classNameStr = klass ? class_getName(klass) : ""; + std::string errMsg = std::string("Cannot call method '") + jsNameStr + + "' on a disposed native object (class: " + classNameStr + + ", selector: " + selectorStr + "). This can happen during HMR or fast view churn."; + isolate->ThrowException(Exception::Error(tns::ToV8String(isolate, errMsg))); + return v8::Undefined(isolate); + } else { + tns::Assert(false, isolate); + } + } if (wrapper->Type() == WrapperType::ObjCAllocObject) { ObjCAllocDataWrapper* allocWrapper = static_cast(wrapper); @@ -43,7 +61,19 @@ // For extended classes we will call the base method callSuper = isMethodCallback && it != cache->ClassPrototypes.end(); } else { - tns::Assert(false, isolate); + if (RuntimeConfig.IsDebug) { + const char* selectorStr = meta ? meta->selectorAsString() : ""; + const char* jsNameStr = meta ? meta->jsName() : ""; + const char* classNameStr = klass ? class_getName(klass) : ""; + std::string errMsg = std::string("Unexpected receiver wrapper type ") + + std::to_string((int)wrapper->Type()) + " for method '" + jsNameStr + + "' (class: " + classNameStr + ", selector: " + selectorStr + + "). This can happen during HMR or fast view churn."; + isolate->ThrowException(Exception::Error(tns::ToV8String(isolate, errMsg))); + return v8::Undefined(isolate); + } else { + tns::Assert(false, isolate); + } } } @@ -878,7 +908,7 @@ Local thiz = args.This(); Isolate* isolate = args.GetIsolate(); BaseDataWrapper* wrapper = tns::GetValue(isolate, thiz); - if (wrapper == nullptr && wrapper->Type() != WrapperType::ObjCObject) { + if (wrapper == nullptr || wrapper->Type() != WrapperType::ObjCObject) { return; } From 289ae2a6375f40013a9779f24102897e07dd82b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 23:37:07 +0000 Subject: [PATCH 3/3] revert: \"fix: prevent crash during debug on fast view churn (like with HMR)\" - restore original revert Agent-Logs-Url: https://github.com/NativeScript/ios/sessions/62444c68-20cd-4f29-98e1-008f34ee1416 Co-authored-by: NathanWalker <457187+NathanWalker@users.noreply.github.com> --- NativeScript/runtime/ArgConverter.mm | 36 +++------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/NativeScript/runtime/ArgConverter.mm b/NativeScript/runtime/ArgConverter.mm index 4e6564be..65026dac 100644 --- a/NativeScript/runtime/ArgConverter.mm +++ b/NativeScript/runtime/ArgConverter.mm @@ -7,7 +7,6 @@ #include "Interop.h" #include "Helpers.h" #include "Runtime.h" -#include "RuntimeConfig.h" using namespace v8; using namespace std; @@ -28,24 +27,7 @@ bool callSuper = false; if (instanceMethod) { BaseDataWrapper* wrapper = tns::GetValue(isolate, receiver); - - if (wrapper == nullptr) { - // During fast view churn like HMR in development, JS objects can outlive their - // native wrappers briefly. In Debug, throw a catchable JS error instead of crashing. - // In Release, assert so crash reporting can capture unexpected cases. - if (RuntimeConfig.IsDebug) { - const char* selectorStr = meta ? meta->selectorAsString() : ""; - const char* jsNameStr = meta ? meta->jsName() : ""; - const char* classNameStr = klass ? class_getName(klass) : ""; - std::string errMsg = std::string("Cannot call method '") + jsNameStr + - "' on a disposed native object (class: " + classNameStr + - ", selector: " + selectorStr + "). This can happen during HMR or fast view churn."; - isolate->ThrowException(Exception::Error(tns::ToV8String(isolate, errMsg))); - return v8::Undefined(isolate); - } else { - tns::Assert(false, isolate); - } - } + tns::Assert(wrapper != nullptr, isolate); if (wrapper->Type() == WrapperType::ObjCAllocObject) { ObjCAllocDataWrapper* allocWrapper = static_cast(wrapper); @@ -61,19 +43,7 @@ // For extended classes we will call the base method callSuper = isMethodCallback && it != cache->ClassPrototypes.end(); } else { - if (RuntimeConfig.IsDebug) { - const char* selectorStr = meta ? meta->selectorAsString() : ""; - const char* jsNameStr = meta ? meta->jsName() : ""; - const char* classNameStr = klass ? class_getName(klass) : ""; - std::string errMsg = std::string("Unexpected receiver wrapper type ") + - std::to_string((int)wrapper->Type()) + " for method '" + jsNameStr + - "' (class: " + classNameStr + ", selector: " + selectorStr + - "). This can happen during HMR or fast view churn."; - isolate->ThrowException(Exception::Error(tns::ToV8String(isolate, errMsg))); - return v8::Undefined(isolate); - } else { - tns::Assert(false, isolate); - } + tns::Assert(false, isolate); } } @@ -908,7 +878,7 @@ Local thiz = args.This(); Isolate* isolate = args.GetIsolate(); BaseDataWrapper* wrapper = tns::GetValue(isolate, thiz); - if (wrapper == nullptr || wrapper->Type() != WrapperType::ObjCObject) { + if (wrapper == nullptr && wrapper->Type() != WrapperType::ObjCObject) { return; }