Skip to content
Draft
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
4 changes: 4 additions & 0 deletions NativeScript/runtime/ArgConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ class ArgConverter {
// recycled the address and give it a foreign prototype.
static std::shared_ptr<v8::Persistent<v8::Value>> FindCachedInstance(
v8::Isolate* isolate, const std::shared_ptr<Caches>& cache, id target);
// The JS `this` for a native call into a JS-implemented member: the object's
// wrapper, `fallback` when it has none (an empty fallback builds one).
static v8::Local<v8::Object> WrapCallbackSelf(v8::Local<v8::Context> context, id self,
v8::Local<v8::Object> fallback);
static const Meta* GetMeta(const std::string& name);
static const ProtocolMeta* FindProtocolMeta(Protocol* protocol);
static void MethodCallback(ffi_cif* cif, void* retValue, void** argValues,
Expand Down
56 changes: 54 additions & 2 deletions NativeScript/runtime/ArgConverter.mm
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "NSExceptionSupport.h"
#include "NativeScriptException.h"
#include "ObjectManager.h"
#include "WrapperLifetime.h"
#include "Runtime.h"
#include "RuntimeConfig.h"

Expand All @@ -31,8 +32,10 @@
Class allocKlass = nil;
bool instanceMethod = !receiver.IsEmpty();
bool callSuper = false;
WrapperLifetime::DrainAtCallEntry(isolate);
if (instanceMethod) {
BaseDataWrapper* wrapper = tns::GetValue(isolate, receiver);
WrapperLifetime::Escape(wrapper);

if (wrapper == nullptr) {
tns::ReleasedReason releasedReason = tns::GetReleasedReason(receiver);
Expand Down Expand Up @@ -368,8 +371,14 @@ throw NativeScriptException(
if (data->initialParamIndex_ > 1) {
id self_ = *static_cast<const id*>(argValues[0]);
auto it = cache->Instances.find(self_);
if (it != cache->Instances.end() && it->second->IsEmpty()) {
// A dead wrapper whose release is queued: rebuild below.
cache->Instances.erase(it);
it = cache->Instances.end();
}
if (it != cache->Instances.end()) {
thiz = it->second->Get(data->isolateWrapper_.Isolate()).As<Object>();
WrapperLifetime::Escape(tns::GetValue(isolate, thiz));
} else {
ObjCDataWrapper* wrapper = new ObjCDataWrapper(self_);
thiz = ArgConverter::CreateJsWrapper(context, wrapper, Local<Object>(), true).As<Object>();
Expand Down Expand Up @@ -662,6 +671,7 @@ throw NativeScriptException(
if (!resultIsOwned) {
[result retain];
}
WrapperLifetime::Adopt(isolate, wrapper, poThiz, result);
ExternalMemory::ChargeEstimatedSize(isolate, thiz);
}
}
Expand Down Expand Up @@ -922,14 +932,24 @@ throw NativeScriptException(
auto cache = Caches::Get(isolate);
if (receiver.IsEmpty()) {
auto it = cache->Instances.find(target);
if (it != cache->Instances.end() && it->second->IsEmpty()) {
// The wrapper died and its release is still queued (WrapperLifetime);
// the queue entry keeps its own retain, so a new wrapper starts over.
cache->Instances.erase(it);
it = cache->Instances.end();
}
if (it != cache->Instances.end()) {
receiver = it->second->Get(isolate).As<Object>();
WrapperLifetime::Escape(tns::GetValue(isolate, receiver));
} else {
std::shared_ptr<Persistent<Value>> poValue = CreateEmptyObject(context, skipGCRegistration);
receiver = poValue->Get(isolate).As<Object>();
tns::SetValue(isolate, receiver, wrapper);
cache->Instances.emplace(target, poValue);
[target retain];
if (wrapper->Type() == WrapperType::ObjCObject) {
WrapperLifetime::Adopt(isolate, static_cast<ObjCDataWrapper*>(wrapper), poValue, target);
}
}
} else {
tns::SetValue(isolate, receiver, wrapper);
Expand Down Expand Up @@ -986,8 +1006,14 @@ throw NativeScriptException(
if (it == cache->Instances.end()) {
return nullptr;
}
if (it->second->IsEmpty()) {
// A dead wrapper whose release is queued (WrapperLifetime).
cache->Instances.erase(it);
return nullptr;
}

BaseDataWrapper* wrapper = tns::GetValue(isolate, it->second->Get(isolate));
WrapperLifetime::Escape(wrapper);
if (wrapper != nullptr && wrapper->Type() == WrapperType::ObjCObject) {
Class expected = static_cast<ObjCDataWrapper*>(wrapper)->Klass();
// A KVO-style isa swizzle replaces the class in place but keeps -class
Expand All @@ -1004,6 +1030,30 @@ throw NativeScriptException(
return it->second;
}

Local<Object> ArgConverter::WrapCallbackSelf(Local<Context> context, id self,
Local<Object> fallback) {
Isolate* isolate = v8::Isolate::GetCurrent();
auto cache = Caches::Get(isolate);
auto it = cache->Instances.find(self);
if (it != cache->Instances.end() && it->second->IsEmpty()) {
// A dead wrapper whose release is queued (WrapperLifetime).
cache->Instances.erase(it);
it = cache->Instances.end();
}
if (it != cache->Instances.end()) {
Local<Object> existing = it->second->Get(isolate).As<Object>();
WrapperLifetime::Escape(tns::GetValue(isolate, existing));
return existing;
}
if (!fallback.IsEmpty()) {
return fallback;
}
ObjCDataWrapper* wrapper = new ObjCDataWrapper(self);
Local<Object> result = CreateJsWrapper(context, wrapper, Local<Object>()).As<Object>();
tns::DeleteWrapperIfUnused(isolate, result, wrapper);
return result;
}

const Meta* ArgConverter::FindMeta(Class klass, const TypeEncoding* typeEncoding) {
if (typeEncoding != nullptr &&
typeEncoding->type == BinaryTypeEncodingType::InterfaceDeclarationReference) {
Expand Down Expand Up @@ -1165,8 +1215,10 @@ throw NativeScriptException(

std::shared_ptr<Caches> cache = Caches::Get(isolate);
auto it = cache->Instances.find(obj);
if (it != cache->Instances.end()) {
args.GetReturnValue().Set(it->second->Get(isolate));
if (it != cache->Instances.end() && !it->second->IsEmpty()) {
Local<Value> existing = it->second->Get(isolate);
WrapperLifetime::Escape(tns::GetValue(isolate, existing));
args.GetReturnValue().Set(existing);
return v8::Intercepted::kYes;
}

Expand Down
16 changes: 16 additions & 0 deletions NativeScript/runtime/Caches.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,22 @@ class Caches {
return cache->GetOrCreateState<T>();
}

// Teardown only: the existing state, even after InvalidateIsolate, for a
// subsystem that must finish work its state still holds. Never creates.
template <typename T>
static T* ExistingStateFor(v8::Isolate* isolate) {
auto raw = isolate->GetData(0);
if (raw == nullptr) {
return nullptr;
}
Caches* cache = reinterpret_cast<std::shared_ptr<Caches>*>(raw)->get();
size_t index = StateSlotIndexFor<T>();
if (index >= cache->stateSlots_.size()) {
return nullptr;
}
return static_cast<T*>(cache->stateSlots_[index].get());
}

inline int getIsolateId() { return isolateId_; }

// The id assigned at Init; unlike getIsolateId() it survives
Expand Down
4 changes: 3 additions & 1 deletion NativeScript/runtime/ClassBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ struct PropertyCallbackContext {

class ClassBuilder {
public:
// The implementation of `method` on the first class above any JS-extended
// (TNSDerivedClass) ones in klass's hierarchy.
static IMP FindNotOverridenMethod(Class klass, SEL method);
static v8::Local<v8::FunctionTemplate> GetExtendFunction(
v8::Isolate* isolate, const InterfaceMeta* interfaceMeta);
static Class GetExtendedClass(const std::string& baseClassName,
Expand Down Expand Up @@ -72,7 +75,6 @@ class ClassBuilder {
static std::string GetTypeEncoding(const TypeEncoding* typeEncoding);
static BinaryTypeEncodingType GetTypeEncodingType(v8::Isolate* isolate,
v8::Local<v8::Value> value);
static IMP FindNotOverridenMethod(Class klass, SEL method);

struct CacheItem {
public:
Expand Down
Loading
Loading