Skip to content

Commit 38f3168

Browse files
committed
fix(jni): handle static hermes constructor receivers
1 parent 698b4ed commit 38f3168

5 files changed

Lines changed: 165 additions & 65 deletions

File tree

NativeScript/ffi/jni/napi/conversion/ArgConverter.cpp

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,25 @@
1313
using namespace std;
1414
using namespace tns;
1515

16+
namespace {
17+
napi_value EnsurePlainConstructorThis(napi_env env, napi_value jsThis, napi_value prototype) {
18+
if (!napi_util::is_null_or_undefined(env, jsThis)) {
19+
return jsThis;
20+
}
21+
22+
napi_value receiver = nullptr;
23+
if (napi_create_object(env, &receiver) != napi_ok || receiver == nullptr) {
24+
return nullptr;
25+
}
26+
27+
if (!napi_util::is_null_or_undefined(env, prototype)) {
28+
napi_util::setPrototypeOf(env, receiver, prototype);
29+
}
30+
31+
return receiver;
32+
}
33+
}
34+
1635
void ArgConverter::Init(napi_env env) {
1736
auto cache = GetTypeLongCache(env);
1837

@@ -98,15 +117,24 @@ napi_value ArgConverter::NativeScriptLongToStringFunctionCallback(napi_env env,
98117
napi_value ArgConverter::NativeScriptLongFunctionCallback(napi_env env, napi_callback_info info) {
99118
try {
100119
NAPI_CALLBACK_BEGIN(1);
120+
napi_value newTarget;
121+
napi_get_new_target(env, info, &newTarget);
122+
napi_value receiverPrototype = !napi_util::is_null_or_undefined(env, newTarget)
123+
? napi_util::get_prototype(env, newTarget)
124+
: nullptr;
125+
napi_value receiver = EnsurePlainConstructorThis(env, jsThis, receiverPrototype);
126+
if (receiver == nullptr) {
127+
return nullptr;
128+
}
101129
auto cache = GetTypeLongCache(env);
102130
napi_value javaLong;
103131
napi_get_boolean(env, true, &javaLong);
104-
napi_set_named_property(env, jsThis, "javaLong", javaLong);
132+
napi_set_named_property(env, receiver, "javaLong", javaLong);
105133

106-
NumericCasts::MarkAsLong(env, jsThis, argv[0]);
134+
NumericCasts::MarkAsLong(env, receiver, argv[0]);
107135

108-
napi_set_named_property(env, jsThis, "prototype", napi_util::get_ref_value(env, cache->NanNumberObject));
109-
return jsThis;
136+
napi_set_named_property(env, receiver, "prototype", napi_util::get_ref_value(env, cache->NanNumberObject));
137+
return receiver;
110138

111139
} catch (NativeScriptException &e) {
112140
e.ReThrowToNapi(env);
@@ -262,4 +290,4 @@ void ArgConverter::onDisposeEnv(napi_env env) {
262290
}
263291
}
264292

265-
robin_hood::unordered_map<napi_env, ArgConverter::TypeLongOperationsCache *> ArgConverter::s_type_long_operations_cache;
293+
robin_hood::unordered_map<napi_env, ArgConverter::TypeLongOperationsCache *> ArgConverter::s_type_long_operations_cache;

NativeScript/ffi/jni/napi/metadata/MetadataNode.cpp

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,23 @@
2222

2323
using namespace std;
2424

25+
namespace {
26+
napi_value EnsureConstructorThis(napi_env env, napi_value jsThis, napi_value prototype) {
27+
if (!napi_util::is_null_or_undefined(env, jsThis)) {
28+
return jsThis;
29+
}
30+
31+
auto runtime = Runtime::GetRuntime(env);
32+
auto receiver = runtime->GetObjectManager()->GetEmptyObject();
33+
if (!napi_util::is_null_or_undefined(env, receiver) &&
34+
!napi_util::is_null_or_undefined(env, prototype)) {
35+
napi_util::setPrototypeOf(env, receiver, prototype);
36+
}
37+
38+
return receiver;
39+
}
40+
}
41+
2542
void MetadataNode::Init(napi_env env) {
2643
auto cache = GetMetadataNodeCache(env);
2744
}
@@ -41,7 +58,12 @@ napi_value MetadataNode::CreateArrayObjectConstructor(napi_env env) {
4158
napi_define_class(env, name, strlen(name),
4259
[](napi_env env, napi_callback_info info) -> napi_value {
4360
NAPI_CALLBACK_BEGIN(0)
44-
return jsThis;
61+
napi_value newTarget;
62+
napi_get_new_target(env, info, &newTarget);
63+
napi_value receiverPrototype = !napi_util::is_null_or_undefined(env, newTarget)
64+
? napi_util::get_prototype(env, newTarget)
65+
: nullptr;
66+
return EnsureConstructorThis(env, jsThis, receiverPrototype);
4567
}, nullptr, 0, nullptr, &arrayConstructor);
4668
napi_value proto = napi_util::get_prototype(env, arrayConstructor);
4769
ObjectManager::MarkObject(env, proto);
@@ -67,11 +89,14 @@ napi_value MetadataNode::CreateExtendedJSWrapper(napi_env env, ObjectManager *ob
6789

6890
if (cacheData.node != nullptr) {
6991
extInstance = objectManager->GetEmptyObject();
92+
if (napi_util::is_null_or_undefined(env, extInstance)) {
93+
return nullptr;
94+
}
7095
ObjectManager::MarkSuperCall(env, extInstance);
7196
napi_value extendedCtorFunc = napi_util::get_ref_value(env,
7297
cacheData.extendedCtorFunction);
73-
napi_util::setPrototypeOf(env, extInstance,
74-
napi_util::get_prototype(env, extendedCtorFunc));
98+
napi_value extendedPrototype = napi_util::get_prototype(env, extendedCtorFunc);
99+
napi_util::setPrototypeOf(env, extInstance, extendedPrototype);
75100

76101
napi_set_named_property(env, extInstance, CONSTRUCTOR, extendedCtorFunc);
77102

@@ -323,19 +348,21 @@ napi_value MetadataNode::ExtendedClassConstructorCallback(napi_env env, napi_cal
323348
napi_value newTarget;
324349
napi_get_new_target(env, info, &newTarget);
325350
if (napi_util::is_null_or_undefined(env, newTarget)) return nullptr;
351+
napi_value receiver = EnsureConstructorThis(env, jsThis, napi_util::get_prototype(env, newTarget));
352+
if (napi_util::is_null_or_undefined(env, receiver)) return nullptr;
326353

327354
auto extData = reinterpret_cast<ExtendedClassCallbackData *>(data);
328-
SetInstanceMetadata(env, jsThis, extData->node);
355+
SetInstanceMetadata(env, receiver, extData->node);
329356

330357
napi_value implementationObject = napi_util::get_ref_value(env,
331358
extData->implementationObject);
332-
ObjectManager::MarkSuperCall(env, jsThis);
359+
ObjectManager::MarkSuperCall(env, receiver);
333360

334361
string fullClassName = extData->fullClassName;
335362

336363
ArgsWrapper argWrapper(argv.data(), argc, ArgType::Class);
337364
napi_value jsThisProxy;
338-
bool success = CallbackHandlers::RegisterInstance(env, jsThis, fullClassName, argWrapper,
365+
bool success = CallbackHandlers::RegisterInstance(env, receiver, fullClassName, argWrapper,
339366
implementationObject, false,
340367
&jsThisProxy, extData->node->m_name);
341368

@@ -403,23 +430,30 @@ napi_value MetadataNode::InterfaceConstructorCallback(napi_env env, napi_callbac
403430
auto node = reinterpret_cast<MetadataNode *>(data);
404431

405432
auto className = node->m_implType;
433+
napi_value newTarget;
434+
napi_get_new_target(env, info, &newTarget);
435+
napi_value receiverPrototype = !napi_util::is_null_or_undefined(env, newTarget)
436+
? napi_util::get_prototype(env, newTarget)
437+
: nullptr;
438+
napi_value receiver = EnsureConstructorThis(env, jsThis, receiverPrototype);
439+
if (napi_util::is_null_or_undefined(env, receiver)) return nullptr;
406440

407-
SetInstanceMetadata(env, jsThis, node);
441+
SetInstanceMetadata(env, receiver, node);
408442

409-
ObjectManager::MarkSuperCall(env, jsThis);
443+
ObjectManager::MarkSuperCall(env, receiver);
410444

411445

412446
napi_util::setPrototypeOf(env, implementationObject,
413-
napi_util::getPrototypeOf(env, jsThis));
447+
napi_util::getPrototypeOf(env, receiver));
414448

415-
napi_util::setPrototypeOf(env, jsThis, implementationObject);
449+
napi_util::setPrototypeOf(env, receiver, implementationObject);
416450

417-
napi_set_named_property(env, jsThis, CLASS_IMPLEMENTATION_OBJECT, implementationObject);
451+
napi_set_named_property(env, receiver, CLASS_IMPLEMENTATION_OBJECT, implementationObject);
418452

419453
ArgsWrapper argsWrapper(argv.data(), argc, ArgType::Interface);
420454

421455
napi_value jsThisProxy;
422-
auto success = CallbackHandlers::RegisterInstance(env, jsThis, className, argsWrapper,
456+
auto success = CallbackHandlers::RegisterInstance(env, receiver, className, argsWrapper,
423457
implementationObject, true, &jsThisProxy);
424458
return jsThisProxy;
425459

@@ -444,8 +478,15 @@ napi_value MetadataNode::ClassConstructorCallback(napi_env env, napi_callback_in
444478
try {
445479

446480
auto node = reinterpret_cast<MetadataNode *>(data);
481+
napi_value newTarget;
482+
napi_get_new_target(env, info, &newTarget);
483+
napi_value receiverPrototype = !napi_util::is_null_or_undefined(env, newTarget)
484+
? napi_util::get_prototype(env, newTarget)
485+
: nullptr;
486+
napi_value receiver = EnsureConstructorThis(env, jsThis, receiverPrototype);
487+
if (napi_util::is_null_or_undefined(env, receiver)) return nullptr;
447488

448-
SetInstanceMetadata(env, jsThis, node);
489+
SetInstanceMetadata(env, receiver, node);
449490

450491
string extendName;
451492
auto className = node->m_name;
@@ -454,7 +495,7 @@ napi_value MetadataNode::ClassConstructorCallback(napi_env env, napi_callback_in
454495

455496
ArgsWrapper argsWrapper(argv.data(), argc, ArgType::Class);
456497
napi_value jsThisProxy;
457-
bool success = CallbackHandlers::RegisterInstance(env, jsThis, fullClassName, argsWrapper,
498+
bool success = CallbackHandlers::RegisterInstance(env, receiver, fullClassName, argsWrapper,
458499
nullptr, false, &jsThisProxy, className);
459500

460501
return jsThisProxy;
@@ -901,6 +942,7 @@ napi_value MetadataNode::PackageGetterCallback(napi_env env, napi_callback_info
901942
NativeScriptException nsEx(std::string("Error: c++ exception!"));
902943
nsEx.ReThrowToNapi(env);
903944
}
945+
return nullptr;
904946
}
905947

906948
void MetadataNode::RegisterSymbolHasInstanceCallback(napi_env env, const MetadataTreeNode *treeNode,
@@ -2160,4 +2202,4 @@ robin_hood::unordered_map<MetadataTreeNode *, MetadataNode *> MetadataNode::s_tr
21602202
tns::ConcurrentMap<napi_env, MetadataNode::MetadataNodeCache *> MetadataNode::s_metadata_node_cache;
21612203
robin_hood::unordered_map<napi_env, napi_ref> MetadataNode::s_arrayObjects;
21622204

2163-
bool MetadataNode::s_profilerEnabled = false;
2205+
bool MetadataNode::s_profilerEnabled = false;

NativeScript/ffi/jni/napi/objectmanager/ObjectManager.cpp

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -290,8 +290,12 @@ ObjectManager::GetJSInstanceInfoFromRuntimeObject(napi_value object) {
290290
}
291291

292292
bool ObjectManager::IsRuntimeJsObject(napi_value object) {
293-
bool result;
294-
napi_has_named_property(m_env, object, PRIVATE_IS_NAPI, &result);
293+
if (object == nullptr) return false;
294+
295+
bool result = false;
296+
if (napi_has_named_property(m_env, object, PRIVATE_IS_NAPI, &result) != napi_ok) {
297+
return false;
298+
}
295299
return result;
296300
}
297301

@@ -513,8 +517,21 @@ napi_value ObjectManager::GetEmptyObject() {
513517
napi_get_and_clear_last_exception(m_env, &ex);
514518

515519
napi_value jsWrapper = nullptr;
520+
auto status = napi_new_instance(m_env, emptyObjCtorFunc, 0, nullptr, &jsWrapper);
521+
if (status == napi_ok && !napi_util::is_null_or_undefined(m_env, jsWrapper)) {
522+
return jsWrapper;
523+
}
524+
525+
napi_get_and_clear_last_exception(m_env, &ex);
526+
527+
status = napi_create_object(m_env, &jsWrapper);
528+
if (status != napi_ok || jsWrapper == nullptr) return nullptr;
516529

517-
napi_new_instance(m_env, emptyObjCtorFunc, 0, nullptr, &jsWrapper);
530+
MarkObject(m_env, jsWrapper);
531+
auto prototype = napi_util::get_prototype(m_env, emptyObjCtorFunc);
532+
if (!napi_util::is_null_or_undefined(m_env, prototype)) {
533+
napi_util::setPrototypeOf(m_env, jsWrapper, prototype);
534+
}
518535

519536
if (napi_util::is_null_or_undefined(m_env, jsWrapper)) {
520537
return nullptr;
@@ -610,4 +627,4 @@ void ObjectManager::OnGarbageCollected(JNIEnv *jEnv, jintArray object_ids) {
610627
}
611628

612629
}
613-
}
630+
}

NativeScript/napi/common/native_api_util.h

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -359,23 +359,29 @@ inline napi_status define_property_get_set(
359359
setter, data, attributes);
360360
}
361361

362-
inline void setPrototypeOf(napi_env env, napi_value object,
363-
napi_value prototype) {
362+
inline napi_status setPrototypeOf(napi_env env, napi_value object,
363+
napi_value prototype) {
364+
if (object == nullptr || prototype == nullptr) return napi_invalid_arg;
365+
364366
napi_value global, global_object, set_proto;
365367

366368
// Get the global object
367-
napi_get_global(env, &global);
369+
auto status = napi_get_global(env, &global);
370+
if (status != napi_ok) return status;
368371

369372
// Get the Object global object
370-
napi_get_named_property(env, global, OBJECT, &global_object);
373+
status = napi_get_named_property(env, global, OBJECT, &global_object);
374+
if (status != napi_ok) return status;
371375

372376
// Get the setPrototypeOf function from the Object global object
373-
napi_get_named_property(env, global_object, SET_PROTOTYPE_OF, &set_proto);
377+
status = napi_get_named_property(env, global_object, SET_PROTOTYPE_OF, &set_proto);
378+
if (status != napi_ok) return status;
374379

375380
// Prepare the arguments for the setPrototypeOf call
376381
napi_value argv[]{object, prototype};
377382
// Call setPrototypeOf(object, prototype)
378-
napi_call_function(env, global, set_proto, 2, argv, nullptr);
383+
napi_value result;
384+
return napi_call_function(env, global, set_proto, 2, argv, &result);
379385
}
380386

381387
inline bool is_object_explicit(napi_env env, napi_value value) {
@@ -467,13 +473,14 @@ inline bool is_date(napi_env env, napi_value value) {
467473
inline bool is_undefined(napi_env env, napi_value value) {
468474
if (value == nullptr) return true;
469475
napi_valuetype type;
470-
napi_typeof(env, value, &type);
476+
if (napi_typeof(env, value, &type) != napi_ok) return false;
471477
return type == napi_undefined;
472478
}
473479

474480
inline bool is_null(napi_env env, napi_value value) {
481+
if (value == nullptr) return true;
475482
napi_valuetype type;
476-
napi_typeof(env, value, &type);
483+
if (napi_typeof(env, value, &type) != napi_ok) return false;
477484
return type == napi_null;
478485
}
479486

platforms/android/test-app/app/src/main/assets/internal/ts_helpers.js

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -356,39 +356,45 @@
356356
return BLOB_STORE.get(url);
357357
};
358358
URL.InternalAccessor = InternalAccessor;
359-
Object.defineProperty(URL.prototype, "searchParams", {
360-
get() {
361-
if (this._searchParams == null) {
362-
this._searchParams = new URLSearchParams(this.search);
363-
Object.defineProperty(this._searchParams, "_url", {
364-
enumerable: false,
365-
writable: false,
366-
value: this,
367-
});
368-
this._searchParams._append = this._searchParams.append;
369-
this._searchParams.append = function (name, value) {
370-
this._append(name, value);
371-
this._url.search = this.toString();
372-
};
373-
this._searchParams._delete = this._searchParams.delete;
374-
this._searchParams.delete = function (name) {
375-
this._delete(name);
376-
this._url.search = this.toString();
377-
};
378-
this._searchParams._set = this._searchParams.set;
379-
this._searchParams.set = function (name, value) {
380-
this._set(name, value);
381-
this._url.search = this.toString();
382-
};
383-
this._searchParams._sort = this._searchParams.sort;
384-
this._searchParams.sort = function () {
385-
this._sort();
386-
this._url.search = this.toString();
387-
};
388-
}
389-
return this._searchParams;
390-
},
391-
});
359+
const searchParamsDescriptor = Object.getOwnPropertyDescriptor(
360+
URL.prototype,
361+
"searchParams"
362+
);
363+
if (!searchParamsDescriptor || searchParamsDescriptor.configurable) {
364+
Object.defineProperty(URL.prototype, "searchParams", {
365+
get() {
366+
if (this._searchParams == null) {
367+
this._searchParams = new URLSearchParams(this.search);
368+
Object.defineProperty(this._searchParams, "_url", {
369+
enumerable: false,
370+
writable: false,
371+
value: this,
372+
});
373+
this._searchParams._append = this._searchParams.append;
374+
this._searchParams.append = function (name, value) {
375+
this._append(name, value);
376+
this._url.search = this.toString();
377+
};
378+
this._searchParams._delete = this._searchParams.delete;
379+
this._searchParams.delete = function (name) {
380+
this._delete(name);
381+
this._url.search = this.toString();
382+
};
383+
this._searchParams._set = this._searchParams.set;
384+
this._searchParams.set = function (name, value) {
385+
this._set(name, value);
386+
this._url.search = this.toString();
387+
};
388+
this._searchParams._sort = this._searchParams.sort;
389+
this._searchParams.sort = function () {
390+
this._sort();
391+
this._url.search = this.toString();
392+
};
393+
}
394+
return this._searchParams;
395+
},
396+
});
397+
}
392398
}
393399

394400
const pendingUnhandledRejections = [];

0 commit comments

Comments
 (0)