From f88665853b3c1e260f3251c0bd6540f8c4ea9feb Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Tue, 30 Sep 2025 10:42:17 -0700 Subject: [PATCH 1/3] fix: prevent crash during debug on fast view churn (like with HMR) --- NativeScript/runtime/ArgConverter.mm | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/NativeScript/runtime/ArgConverter.mm b/NativeScript/runtime/ArgConverter.mm index 65026dac..2927e064 100644 --- a/NativeScript/runtime/ArgConverter.mm +++ b/NativeScript/runtime/ArgConverter.mm @@ -27,7 +27,22 @@ 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, avoid a crash and just skip the native call. + // In Release, assert so crash reporting can capture unexpected cases. + #ifdef DEBUG + const char* selectorStr = meta ? meta->selectorAsString() : ""; + const char* jsNameStr = meta ? meta->jsName() : ""; + const char* classNameStr = klass ? class_getName(klass) : ""; + Log(@"ArgConverter::Invoke: skipping instance method on non-native receiver (class: %s, selector: %s, jsName: %s, args: %d). Normal during HMR.", + classNameStr, selectorStr, jsNameStr, (int)args.Length()); + return v8::Undefined(isolate); + #else + tns::Assert(false, isolate); + #endif + } if (wrapper->Type() == WrapperType::ObjCAllocObject) { ObjCAllocDataWrapper* allocWrapper = static_cast(wrapper); @@ -878,7 +893,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 1178678cebd1ffd8069720f64cd41b9d16208864 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Tue, 30 Sep 2025 11:51:05 -0700 Subject: [PATCH 2/3] chore: cleanup --- NativeScript/runtime/ArgConverter.mm | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/NativeScript/runtime/ArgConverter.mm b/NativeScript/runtime/ArgConverter.mm index 2927e064..99e76550 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; @@ -32,16 +33,16 @@ // 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. - #ifdef DEBUG + if (RuntimeConfig.IsDebug) { const char* selectorStr = meta ? meta->selectorAsString() : ""; const char* jsNameStr = meta ? meta->jsName() : ""; const char* classNameStr = klass ? class_getName(klass) : ""; - Log(@"ArgConverter::Invoke: skipping instance method on non-native receiver (class: %s, selector: %s, jsName: %s, args: %d). Normal during HMR.", + Log(@"ArgConverter::Invoke: 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 + } else { tns::Assert(false, isolate); - #endif + } } if (wrapper->Type() == WrapperType::ObjCAllocObject) { @@ -58,7 +59,16 @@ // 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) : ""; + Log(@"ArgConverter::Invoke: unexpected receiver wrapper type %d (class: %s, selector: %s, jsName: %s). Skipping in DEBUG.", + (int)wrapper->Type(), classNameStr, selectorStr, jsNameStr); + return v8::Undefined(isolate); + } else { + tns::Assert(false, isolate); + } } } From 757db7dc2c1140a4945097736ed050c4379a6210 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Tue, 30 Sep 2025 11:55:37 -0700 Subject: [PATCH 3/3] chore: cleanup --- NativeScript/runtime/ArgConverter.mm | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/NativeScript/runtime/ArgConverter.mm b/NativeScript/runtime/ArgConverter.mm index 99e76550..c4820212 100644 --- a/NativeScript/runtime/ArgConverter.mm +++ b/NativeScript/runtime/ArgConverter.mm @@ -1,5 +1,6 @@ #include #include +#include #include "ArgConverter.h" #include "NativeScriptException.h" #include "DictionaryAdapter.h" @@ -37,8 +38,13 @@ const char* selectorStr = meta ? meta->selectorAsString() : ""; const char* jsNameStr = meta ? meta->jsName() : ""; const char* classNameStr = klass ? class_getName(klass) : ""; - Log(@"ArgConverter::Invoke: ignore method on non-native receiver (class: %s, selector: %s, jsName: %s, args: %d). Common during HMR.", - classNameStr, selectorStr, jsNameStr, (int)args.Length()); + // 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); @@ -63,8 +69,13 @@ const char* selectorStr = meta ? meta->selectorAsString() : ""; const char* jsNameStr = meta ? meta->jsName() : ""; const char* classNameStr = klass ? class_getName(klass) : ""; - Log(@"ArgConverter::Invoke: unexpected receiver wrapper type %d (class: %s, selector: %s, jsName: %s). Skipping in DEBUG.", - (int)wrapper->Type(), classNameStr, selectorStr, jsNameStr); + // 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);