Skip to content

Commit 0fd0e84

Browse files
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>
1 parent 54b34e0 commit 0fd0e84

File tree

1 file changed

+33
-3
lines changed

1 file changed

+33
-3
lines changed

NativeScript/runtime/ArgConverter.mm

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "Interop.h"
88
#include "Helpers.h"
99
#include "Runtime.h"
10+
#include "RuntimeConfig.h"
1011

1112
using namespace v8;
1213
using namespace std;
@@ -27,7 +28,24 @@
2728
bool callSuper = false;
2829
if (instanceMethod) {
2930
BaseDataWrapper* wrapper = tns::GetValue(isolate, receiver);
30-
tns::Assert(wrapper != nullptr, isolate);
31+
32+
if (wrapper == nullptr) {
33+
// During fast view churn like HMR in development, JS objects can outlive their
34+
// native wrappers briefly. In Debug, throw a catchable JS error instead of crashing.
35+
// In Release, assert so crash reporting can capture unexpected cases.
36+
if (RuntimeConfig.IsDebug) {
37+
const char* selectorStr = meta ? meta->selectorAsString() : "<unknown>";
38+
const char* jsNameStr = meta ? meta->jsName() : "<unknown>";
39+
const char* classNameStr = klass ? class_getName(klass) : "<unknown>";
40+
std::string errMsg = std::string("Cannot call method '") + jsNameStr +
41+
"' on a disposed native object (class: " + classNameStr +
42+
", selector: " + selectorStr + "). This can happen during HMR or fast view churn.";
43+
isolate->ThrowException(Exception::Error(tns::ToV8String(isolate, errMsg)));
44+
return v8::Undefined(isolate);
45+
} else {
46+
tns::Assert(false, isolate);
47+
}
48+
}
3149

3250
if (wrapper->Type() == WrapperType::ObjCAllocObject) {
3351
ObjCAllocDataWrapper* allocWrapper = static_cast<ObjCAllocDataWrapper*>(wrapper);
@@ -43,7 +61,19 @@
4361
// For extended classes we will call the base method
4462
callSuper = isMethodCallback && it != cache->ClassPrototypes.end();
4563
} else {
46-
tns::Assert(false, isolate);
64+
if (RuntimeConfig.IsDebug) {
65+
const char* selectorStr = meta ? meta->selectorAsString() : "<unknown>";
66+
const char* jsNameStr = meta ? meta->jsName() : "<unknown>";
67+
const char* classNameStr = klass ? class_getName(klass) : "<unknown>";
68+
std::string errMsg = std::string("Unexpected receiver wrapper type ") +
69+
std::to_string((int)wrapper->Type()) + " for method '" + jsNameStr +
70+
"' (class: " + classNameStr + ", selector: " + selectorStr +
71+
"). This can happen during HMR or fast view churn.";
72+
isolate->ThrowException(Exception::Error(tns::ToV8String(isolate, errMsg)));
73+
return v8::Undefined(isolate);
74+
} else {
75+
tns::Assert(false, isolate);
76+
}
4777
}
4878
}
4979

@@ -878,7 +908,7 @@
878908
Local<Object> thiz = args.This();
879909
Isolate* isolate = args.GetIsolate();
880910
BaseDataWrapper* wrapper = tns::GetValue(isolate, thiz);
881-
if (wrapper == nullptr && wrapper->Type() != WrapperType::ObjCObject) {
911+
if (wrapper == nullptr || wrapper->Type() != WrapperType::ObjCObject) {
882912
return;
883913
}
884914

0 commit comments

Comments
 (0)