Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions NativeScript/runtime/ClassBuilder.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1002,16 +1002,35 @@ void ScopeClassNameToIsolate(std::string& name, int isolateId) {
void ClassBuilder::SuperAccessorGetterCallback(const FunctionCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Local<Object> thiz = info.This();
// Copying the implementation object or the class prototype (Object.assign,
// spread) reads `super` with a receiver that is no native instance: like the
// other native accessors, it reads undefined.
Local<Value> target = tns::UnwrapProxy(info.This());
if (target.IsEmpty() || !target->IsObject() || target.As<Object>()->InternalFieldCount() < 1) {
return;
}
// Field 0 holds the object that carries the wrapper, never another alias,
// so resolving an alias takes one step.
Local<Object> receiver = tns::SuperAliasReceiver(target.As<Object>());
if (receiver.IsEmpty()) {
receiver = target.As<Object>();
}

// receiver -> extended class prototype -> implementation object -> base
// class prototype.
Local<Value> superPrototype = receiver;
for (int i = 0; i < 3; i++) {
if (!superPrototype->IsObject()) {
return;
}
superPrototype = superPrototype.As<Object>()->GetPrototype();
}

std::shared_ptr<Persistent<Value>> poValue = ArgConverter::CreateEmptyObject(context);
Local<Object> superValue = poValue->Get(isolate).As<Object>();

superValue
->SetPrototype(context,
thiz->GetPrototype().As<Object>()->GetPrototype().As<Object>()->GetPrototype())
.ToChecked();
superValue->SetInternalField(0, thiz->GetInternalField(0));
superValue->SetPrototype(context, superPrototype).ToChecked();
superValue->SetInternalField(0, receiver);
superValue->SetInternalField(1, tns::ToV8String(isolate, "super"));

info.GetReturnValue().Set(superValue);
Expand Down
10 changes: 10 additions & 0 deletions NativeScript/runtime/Helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,19 @@ v8::Local<v8::Context> GetCreationContextOrCurrent(v8::Isolate* isolate,

// Set/Get/DeleteValue all resolve through proxies: wrapper state is keyed by
// the Proxy target, so a proxied wrapper yields its target's wrapper.
// GetValue alone also follows a `this.super` alias to its receiver
// (SuperAliasReceiver), so code that writes back to an object it read with
// GetValue must first resolve an alias, or it overwrites the alias's receiver
// field instead of the wrapper it read.
void SetValue(v8::Isolate* isolate, const v8::Local<v8::Object>& obj, BaseDataWrapper* value);
BaseDataWrapper* GetValue(v8::Isolate* isolate, const v8::Local<v8::Value>& val);

// The receiver a `this.super` alias (ClassBuilder::SuperAccessorGetterCallback)
// stands for, or empty when obj is not one. An alias keeps its receiver in
// internal field 0 and the string "super" in internal field 1; it owns no
// wrapper, so GetValue reads the receiver's at every use.
v8::Local<v8::Object> SuperAliasReceiver(const v8::Local<v8::Object>& obj);

// What happens when JS touches a wrapper whose native counterpart has already
// been released (its internal field was neutered by DisposeValue) — a state a
// resurrected object can expose by outliving a referenced object's native
Expand Down
21 changes: 20 additions & 1 deletion NativeScript/runtime/Helpers.mm
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,21 @@ throw NativeScriptException(
}
}

Local<Object> tns::SuperAliasReceiver(const Local<Object>& obj) {
if (obj->InternalFieldCount() < 2) {
return Local<Object>();
}
Local<Value> marker = obj->GetInternalField(1).As<v8::Value>();
if (marker.IsEmpty() || !marker->IsString()) {
return Local<Object>();
}
Local<Value> receiver = obj->GetInternalField(0).As<v8::Value>();
if (receiver.IsEmpty() || !receiver->IsObject() || receiver->IsExternal()) {
return Local<Object>();
}
return receiver.As<Object>();
}

tns::BaseDataWrapper* tns::GetValue(Isolate* isolate, const Local<Value>& val) {
if (val.IsEmpty() || val->IsNullOrUndefined() || !val->IsObject()) {
return nullptr;
Expand All @@ -320,9 +335,13 @@ throw NativeScriptException(
}
if (obj->InternalFieldCount() > 0) {
Local<Value> field = obj->GetInternalField(0).As<v8::Value>();
if (field.IsEmpty() || field->IsNullOrUndefined() || !field->IsExternal()) {
if (field.IsEmpty() || field->IsNullOrUndefined()) {
return nullptr;
}
if (!field->IsExternal()) {
Local<Object> receiver = SuperAliasReceiver(obj);
return receiver.IsEmpty() ? nullptr : GetValue(isolate, receiver);
}

return static_cast<BaseDataWrapper*>(
field.As<External>()->Value(v8::kExternalPointerTypeTagDefault));
Expand Down
14 changes: 11 additions & 3 deletions NativeScript/runtime/ObjectManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,11 @@ void DisposeHandle(v8::Isolate* isolate,
}

Local<Object> obj = value.As<Object>();
if (obj->InternalFieldCount() > 1 && !isFinalDisposal) {
if (obj->InternalFieldCount() > 1) {
Local<Value> superValue = obj->GetInternalField(1).As<v8::Value>();
if (!superValue.IsEmpty() && superValue->IsString()) {
// Do not dispose the ObjCWrapper contained in a "super" instance
// A "super" alias owns no wrapper; the one GetValue finds through it is
// its receiver's.
return true;
}
}
Expand Down Expand Up @@ -431,8 +432,15 @@ void DisposeHandle(v8::Isolate* isolate,
}

// The lookup, the Instances key and the final SetValue must all address the
// same object, so a Proxy is resolved once here.
// same object, so a Proxy is resolved once here, and a "super" alias to the
// receiver whose wrapper it reads.
Local<Value> value = tns::UnwrapProxy(info[0]);
if (!value.IsEmpty() && value->IsObject()) {
Local<Object> receiver = tns::SuperAliasReceiver(value.As<Object>());
if (!receiver.IsEmpty()) {
value = receiver;
}
}
BaseDataWrapper* wrapper = tns::GetValue(isolate, value);

if (wrapper == nullptr) {
Expand Down
Loading
Loading