diff --git a/.gitignore b/.gitignore index 08fb1b04..c8be8075 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,9 @@ Thumbs.db # VSCode .vscode +# Generated by tools/js2c.mjs (Xcode "Generate RuntimeBuiltins" build phase) +NativeScript/runtime/generated/ + # Other node_modules/ package-lock.json diff --git a/NativeScript/runtime/ArgConverter.h b/NativeScript/runtime/ArgConverter.h index 64ab8d5f..8353fbb2 100644 --- a/NativeScript/runtime/ArgConverter.h +++ b/NativeScript/runtime/ArgConverter.h @@ -2,61 +2,128 @@ #define ArgConverter_h #include -#include "libffi.h" -#include "Common.h" + #include "Caches.h" +#include "Common.h" #include "DataWrapper.h" #include "IsolateWrapper.h" +#include "libffi.h" namespace tns { class ArgConverter; struct MethodCallbackWrapper { -public: - MethodCallbackWrapper(v8::Isolate* isolate, std::shared_ptr> callback, const uint8_t initialParamIndex, const uint8_t paramsCount, const TypeEncoding* typeEncoding) - : isolateWrapper_(isolate), - callback_(callback), - initialParamIndex_(initialParamIndex), - paramsCount_(paramsCount), - typeEncoding_(typeEncoding) { - } - IsolateWrapper isolateWrapper_; - std::shared_ptr> callback_; - const uint8_t initialParamIndex_; - const uint8_t paramsCount_; - const TypeEncoding* typeEncoding_; + public: + MethodCallbackWrapper(v8::Isolate* isolate, + std::shared_ptr> callback, + const uint8_t initialParamIndex, + const uint8_t paramsCount, + const TypeEncoding* typeEncoding) + : isolateWrapper_(isolate), + callback_(callback), + initialParamIndex_(initialParamIndex), + paramsCount_(paramsCount), + typeEncoding_(typeEncoding) {} + IsolateWrapper isolateWrapper_; + std::shared_ptr> callback_; + const uint8_t initialParamIndex_; + const uint8_t paramsCount_; + const TypeEncoding* typeEncoding_; }; class ArgConverter { -public: - static void Init(v8::Local context, v8::GenericNamedPropertyGetterCallback structPropertyGetter, v8::GenericNamedPropertySetterCallback structPropertySetter); - static v8::Local Invoke(v8::Local context, Class klass, v8::Local receiver, V8Args& args, const MethodMeta* meta, bool isMethodCallback); - static v8::Local ConvertArgument(v8::Local context, BaseDataWrapper* wrapper, bool skipGCRegistration = false, const std::vector& additionalProtocols = std::vector()); - static v8::Local CreateJsWrapper(v8::Local context, BaseDataWrapper* wrapper, v8::Local receiver, bool skipGCRegistration = false, const std::vector& additionalProtocols = std::vector()); - static std::shared_ptr> CreateEmptyObject(v8::Local context, bool skipGCRegistration = false); - static std::shared_ptr> CreateEmptyStruct(v8::Local context); - static const Meta* FindMeta(Class klass, const TypeEncoding* typeEncoding = nullptr); - static const Meta* GetMeta(std::string name); - static const ProtocolMeta* FindProtocolMeta(Protocol* protocol); - static void MethodCallback(ffi_cif* cif, void* retValue, void** argValues, void* userData); - static void SetValue(v8::Local context, void* retValue, v8::Local value, const TypeEncoding* typeEncoding); - static void ConstructObject(v8::Local context, const v8::FunctionCallbackInfo& info, Class klass, const InterfaceMeta* interfaceMeta = nullptr); -private: - static v8::Local CreateEmptyInstanceFunction(v8::Local context, v8::GenericNamedPropertyGetterCallback propertyGetter = nullptr, v8::GenericNamedPropertySetterCallback propertySetter = nullptr); - static std::shared_ptr> CreateEmptyInstance(v8::Local context, v8::Persistent* ctorFunc, bool skipGCRegistration = false); - static void FindMethodOverloads(Class klass, std::string methodName, MemberType type, std::vector& overloads); - static const MethodMeta* FindInitializer(v8::Local context, Class klass, const InterfaceMeta* interfaceMeta, const v8::FunctionCallbackInfo& info, std::vector>& args); - static bool CanInvoke(v8::Local context, const TypeEncoding* typeEncoding, v8::Local arg); - static bool CanInvoke(v8::Local context, const MethodMeta* candidate, const v8::FunctionCallbackInfo& info); - static std::vector> GetInitializerArgs(v8::Local obj, std::string& constructorTokens); - static void IndexedPropertyGetterCallback(uint32_t index, const v8::PropertyCallbackInfo& args); - static void IndexedPropertySetterCallback(uint32_t index, v8::Local value, const v8::PropertyCallbackInfo& args); - static bool IsErrorOutParameter(const TypeEncoding* typeEncoding); - static std::vector GetInitializers(Caches* cache, Class klass, const InterfaceMeta* interfaceMeta); - static void MethodCallbackInternal(ffi_cif* cif, void* retValue, void** argValues, void* userData); + public: + static void Init(v8::Local context, + v8::GenericNamedPropertyGetterCallback structPropertyGetter, + v8::GenericNamedPropertySetterCallback structPropertySetter); + static v8::Local Invoke(v8::Local context, + Class klass, + v8::Local receiver, + V8Args& args, const MethodMeta* meta, + bool isMethodCallback); + static v8::Local ConvertArgument( + v8::Local context, BaseDataWrapper* wrapper, + bool skipGCRegistration = false, + const std::vector& additionalProtocols = + std::vector()); + static v8::Local CreateJsWrapper( + v8::Local context, BaseDataWrapper* wrapper, + v8::Local receiver, bool skipGCRegistration = false, + const std::vector& additionalProtocols = + std::vector()); + static std::shared_ptr> CreateEmptyObject( + v8::Local context, bool skipGCRegistration = false); + static std::shared_ptr> CreateEmptyStruct( + v8::Local context); + static const Meta* FindMeta(Class klass, + const TypeEncoding* typeEncoding = nullptr); + static const Meta* GetMeta(std::string name); + static const ProtocolMeta* FindProtocolMeta(Protocol* protocol); + static void MethodCallback(ffi_cif* cif, void* retValue, void** argValues, + void* userData); + static void SetValue(v8::Local context, void* retValue, + v8::Local value, + const TypeEncoding* typeEncoding); + // Returns (lazily creating) the per-isolate interop.escapeException brand + // private stored in Caches. Empty handle if the isolate cache is invalid. + static v8::Local GetEscapeExceptionBrand(v8::Isolate* isolate); + // If `value` carries the escapeException brand, returns the NSException to + // throw (typed `id` so this header is usable from the pure-C++ TUs that + // include it) — the original wrapped NSException when present, otherwise one + // synthesized from the branded name/message/stack payload. Returns nil when + // `value` is not branded. Never reports; the caller decides what to do. + static id ExtractEscapedException(v8::Local context, + v8::Local value); + // Shared JS<->native boundary helper. Reads tc.Exception(): if it carries + // the escapeException brand, returns the NSException (as `id`) the caller + // must @throw AFTER closing every live V8 scope (Locker/HandleScope/ + // Context::Scope). Otherwise reports the exception exactly once through the + // uncaught path (error-event dispatch + shims + log) and returns nil, so the + // caller returns its defined default. Resets `tc` in both cases. + static id HandleBoundaryException(v8::Local context, + v8::TryCatch& tc); + static void ConstructObject(v8::Local context, + const v8::FunctionCallbackInfo& info, + Class klass, + const InterfaceMeta* interfaceMeta = nullptr); + + private: + static v8::Local CreateEmptyInstanceFunction( + v8::Local context, + v8::GenericNamedPropertyGetterCallback propertyGetter = nullptr, + v8::GenericNamedPropertySetterCallback propertySetter = nullptr); + static std::shared_ptr> CreateEmptyInstance( + v8::Local context, v8::Persistent* ctorFunc, + bool skipGCRegistration = false); + static void FindMethodOverloads(Class klass, std::string methodName, + MemberType type, + std::vector& overloads); + static const MethodMeta* FindInitializer( + v8::Local context, Class klass, + const InterfaceMeta* interfaceMeta, + const v8::FunctionCallbackInfo& info, + std::vector>& args); + static bool CanInvoke(v8::Local context, + const TypeEncoding* typeEncoding, + v8::Local arg); + static bool CanInvoke(v8::Local context, + const MethodMeta* candidate, + const v8::FunctionCallbackInfo& info); + static std::vector> GetInitializerArgs( + v8::Local obj, std::string& constructorTokens); + static void IndexedPropertyGetterCallback( + uint32_t index, const v8::PropertyCallbackInfo& args); + static void IndexedPropertySetterCallback( + uint32_t index, v8::Local value, + const v8::PropertyCallbackInfo& args); + static bool IsErrorOutParameter(const TypeEncoding* typeEncoding); + static std::vector GetInitializers( + Caches* cache, Class klass, const InterfaceMeta* interfaceMeta); + static void MethodCallbackInternal(ffi_cif* cif, void* retValue, + void** argValues, void* userData); }; -} +} // namespace tns #endif /* ArgConverter_h */ diff --git a/NativeScript/runtime/ArgConverter.mm b/NativeScript/runtime/ArgConverter.mm index 2197c820..8c21f036 100644 --- a/NativeScript/runtime/ArgConverter.mm +++ b/NativeScript/runtime/ArgConverter.mm @@ -4,6 +4,7 @@ #include "DictionaryAdapter.h" #include "Helpers.h" #include "Interop.h" +#include "NSExceptionSupport.h" #include "NativeScriptException.h" #include "ObjectManager.h" #include "Runtime.h" @@ -128,6 +129,149 @@ return result; } +Local ArgConverter::GetEscapeExceptionBrand(Isolate* isolate) { + auto cache = Caches::Get(isolate); + // Caches::Get never returns null — after isolate removal it returns a dummy + // cache — so validity is the real liveness check before touching V8 state. + if (cache == nullptr || !cache->IsValid()) { + return Local(); + } + if (cache->EscapeExceptionBrand == nullptr) { + Local brand = + Private::New(isolate, tns::ToV8String(isolate, "interop.escapeException")); + cache->EscapeExceptionBrand = std::make_unique>(isolate, brand); + } + return cache->EscapeExceptionBrand->Get(isolate); +} + +// Builds the combined JS stack string attached to an escaped exception via the +// associated object: the origin (propagation) stack of the wrapping error and +// the escape-site stack, each under a labeled section. Empty or duplicate +// sections are omitted; returns nil when nothing is available. +static NSString* BuildCombinedJSStack(const std::string& origin, const std::string& escape) { + NSMutableArray* parts = [NSMutableArray array]; + if (!origin.empty()) { + [parts addObject:[NSString stringWithFormat:@"JS stack:\n%@", tns::ToNSString(origin)]]; + } + if (!escape.empty() && escape != origin) { + [parts addObject:[NSString stringWithFormat:@"Escaped at:\n%@", tns::ToNSString(escape)]]; + } + if (parts.count == 0) { + return nil; + } + return [parts componentsJoinedByString:@"\n\n"]; +} + +id ArgConverter::ExtractEscapedException(Local context, Local value) { + Isolate* isolate = context->GetIsolate(); + if (value.IsEmpty() || !value->IsObject()) { + return nil; + } + Local brand = GetEscapeExceptionBrand(isolate); + if (brand.IsEmpty()) { + return nil; + } + Local obj = value.As(); + Maybe hasBrand = obj->HasPrivate(context, brand); + if (hasBrand.IsNothing() || !hasBrand.FromJust()) { + return nil; + } + Local payloadVal; + if (!obj->GetPrivate(context, brand).ToLocal(&payloadVal) || !payloadVal->IsObject()) { + return nil; + } + Local payload = payloadVal.As(); + + auto readString = [&](const char* key, const std::string& fallback) -> std::string { + Local v; + if (payload->Get(context, tns::ToV8String(isolate, key)).ToLocal(&v) && !v.IsEmpty() && + v->IsString()) { + return tns::ToString(isolate, v); + } + return fallback; + }; + + // The escape-site stack always travels with the payload; the origin stack is + // the wrapping error's own `.stack`, carried when present. + std::string escapeStack = readString("escapeStack", ""); + + // Original NSException carried through unchanged when present. + Local nativeExc; + if (payload->Get(context, tns::ToV8String(isolate, "nativeException")).ToLocal(&nativeExc) && + nativeExc->IsObject()) { + BaseDataWrapper* wrapper = tns::GetValue(isolate, nativeExc); + if (wrapper != nullptr && wrapper->Type() == WrapperType::ObjCObject) { + id data = static_cast(wrapper)->Data(); + if ([data isKindOfClass:[NSException class]]) { + // Attach the JS stack WITHOUT mutating the original exception's identity + // or userInfo — the round-trip contract requires the very same object to + // reach the native @catch. The category accessor surfaces it uniformly. + std::string originStack = readString("stack", ""); + NSString* combined = BuildCombinedJSStack(originStack, escapeStack); + if (combined != nil) { + tns::SetJSStackOnException((NSException*)data, combined); + } + return (NSException*)data; + } + } + } + + // Otherwise synthesize an NSException from the branded name/message/stack. + std::string name = readString("name", "NativeScriptException"); + std::string message = readString("message", ""); + std::string stack = readString("stack", ""); + + NSString* reasonText = tns::ToNSString(message); + if (!stack.empty()) { + // Put the JS stack in the reason too so it shows up in native crash logs. + reasonText = [reasonText stringByAppendingFormat:@"\n%@", tns::ToNSString(stack)]; + } + NSMutableDictionary* userInfo = [NSMutableDictionary dictionary]; + if (!stack.empty()) { + userInfo[TNSJavaScriptStackTraceKey] = tns::ToNSString(stack); + } + // Carry the escape-site stack separately only when it adds information beyond + // the origin stack. + if (!escapeStack.empty() && escapeStack != stack) { + userInfo[TNSJavaScriptEscapeStackTraceKey] = tns::ToNSString(escapeStack); + } + NSException* synthesized = [NSException exceptionWithName:tns::ToNSString(name) + reason:reasonText + userInfo:userInfo.count ? userInfo : nil]; + // Attach the combined stack via associated object so the category accessor is + // uniform across synthesized and rethrown-original exceptions. + NSString* combined = BuildCombinedJSStack(stack, escapeStack); + if (combined != nil) { + tns::SetJSStackOnException(synthesized, combined); + } + return synthesized; +} + +id ArgConverter::HandleBoundaryException(Local context, TryCatch& tc) { + if (!tc.HasCaught()) { + return nil; + } + Isolate* isolate = context->GetIsolate(); + Local exception = tc.Exception(); + + id escaped = ExtractEscapedException(context, exception); + if (escaped != nil) { + // Branded escape: convert to an ObjC throw. Clear the V8 exception; the + // caller @throws this AFTER closing all V8 scopes. + tc.Reset(); + return escaped; + } + + // Unbranded: report exactly once through the uncaught path (error-event + // dispatch + shims + log; honors crashOnUncaughtJsExceptions). This is the + // path for the Assert-hardened sites that today never reach the message + // listener at all. + Local message = tc.Message(); + NativeScriptException::ReportToJsHandlersAndLog(isolate, exception, message); + tc.Reset(); + return nil; +} + void ArgConverter::MethodCallback(ffi_cif* cif, void* retValue, void** argValues, void* userData) { MethodCallbackWrapper* data = static_cast(userData); @@ -138,90 +282,123 @@ return; } - v8::Locker locker(isolate); - Isolate::Scope isolate_scope(isolate); - HandleScope handle_scope(isolate); - std::shared_ptr cache = Caches::Get(isolate); + // Declared before all V8 scopes: an ObjC exception must never unwind through a + // live V8 scope (Locker/HandleScope/Context::Scope). A branded escape caught + // below is captured here and @thrown only after the inner block closes every + // V8 scope. + NSException* __strong pendingThrow = nil; - Local context = cache->GetContext(); - Context::Scope context_scope(context); - std::shared_ptr> poCallback = data->callback_; + { + v8::Locker locker(isolate); + Isolate::Scope isolate_scope(isolate); + HandleScope handle_scope(isolate); + std::shared_ptr cache = Caches::Get(isolate); - bool hasErrorOutParameter = false; + Local context = cache->GetContext(); + Context::Scope context_scope(context); + std::shared_ptr> poCallback = data->callback_; + + bool hasErrorOutParameter = false; + + std::vector> v8Args; + v8Args.reserve(data->paramsCount_); + const TypeEncoding* typeEncoding = data->typeEncoding_; + for (int i = 0; i < data->paramsCount_; i++) { + typeEncoding = typeEncoding->next(); + if (i == data->paramsCount_ - 1 && ArgConverter::IsErrorOutParameter(typeEncoding)) { + hasErrorOutParameter = true; + // No need to provide the NSError** parameter to the javascript callback + continue; + } - std::vector> v8Args; - v8Args.reserve(data->paramsCount_); - const TypeEncoding* typeEncoding = data->typeEncoding_; - for (int i = 0; i < data->paramsCount_; i++) { - typeEncoding = typeEncoding->next(); - if (i == data->paramsCount_ - 1 && ArgConverter::IsErrorOutParameter(typeEncoding)) { - hasErrorOutParameter = true; - // No need to provide the NSError** parameter to the javascript callback - continue; - } + int argIndex = i + data->initialParamIndex_; - int argIndex = i + data->initialParamIndex_; + uint8_t* argBuffer = (uint8_t*)argValues[argIndex]; + BaseCall call(argBuffer); + Local jsWrapper = Interop::GetResult(context, typeEncoding, &call, true); - uint8_t* argBuffer = (uint8_t*)argValues[argIndex]; - BaseCall call(argBuffer); - Local jsWrapper = Interop::GetResult(context, typeEncoding, &call, true); + if (!jsWrapper.IsEmpty()) { + v8Args.push_back(jsWrapper); + } else { + v8Args.push_back(v8::Undefined(isolate)); + } + } - if (!jsWrapper.IsEmpty()) { - v8Args.push_back(jsWrapper); - } else { - v8Args.push_back(v8::Undefined(isolate)); + Local thiz = context->Global(); + if (data->initialParamIndex_ > 1) { + id self_ = *static_cast(argValues[0]); + auto it = cache->Instances.find(self_); + if (it != cache->Instances.end()) { + thiz = it->second->Get(data->isolateWrapper_.Isolate()).As(); + } else { + ObjCDataWrapper* wrapper = new ObjCDataWrapper(self_); + thiz = ArgConverter::CreateJsWrapper(context, wrapper, Local(), true).As(); + tns::DeleteWrapperIfUnused(isolate, thiz, wrapper); + } } - } - Local thiz = context->Global(); - if (data->initialParamIndex_ > 1) { - id self_ = *static_cast(argValues[0]); - auto it = cache->Instances.find(self_); - if (it != cache->Instances.end()) { - thiz = it->second->Get(data->isolateWrapper_.Isolate()).As(); + Local result; + Local callback = poCallback->Get(isolate).As(); + + bool success = false; + if (hasErrorOutParameter) { + // We don't want the global error handler (NativeScriptException::OnUncaughtError) to be + // called for javascript exceptions occuring inside methods that have NSError* parameters. + // Those js errors will be marshalled to NSError* and sent directly to the calling native + // code. The v8::TryCatch statement prevents the global handler to be called. + TryCatch tc(isolate); + success = callback->Call(context, thiz, (int)v8Args.size(), v8Args.data()).ToLocal(&result); + if (!success && tc.HasCaught()) { + // A branded escapeException follows the @throw path even here, taking + // precedence over NSError-out marshaling. + NSException* escaped = ExtractEscapedException(context, tc.Exception()); + if (escaped != nil) { + pendingThrow = escaped; + } else { + Local exception = tc.Exception(); + std::string message = tns::ToString(isolate, exception); + + int errorParamIndex = data->initialParamIndex_ + data->paramsCount_ - 1; + void* errorParam = argValues[errorParamIndex]; + NSError* __strong** outPtr = static_cast(errorParam); + if (outPtr && *outPtr) { + NSError* error = + [NSError errorWithDomain:@"TNSErrorDomain" + code:164 + userInfo:@{@"TNSJavaScriptError" : tns::ToNSString(message)}]; + **static_cast(outPtr) = error; + } + } + } } else { - ObjCDataWrapper* wrapper = new ObjCDataWrapper(self_); - thiz = ArgConverter::CreateJsWrapper(context, wrapper, Local(), true).As(); - tns::DeleteWrapperIfUnused(isolate, thiz, wrapper); + TryCatch tc(isolate); + success = callback->Call(context, thiz, (int)v8Args.size(), v8Args.data()).ToLocal(&result); + if (!success && tc.HasCaught()) { + NSException* escaped = ExtractEscapedException(context, tc.Exception()); + if (escaped != nil) { + pendingThrow = escaped; + } else { + // Unbranded: re-throw so the pending exception surfaces to V8's message + // listener exactly once (OnUncaughtError), preserving existing behavior. + tc.ReThrow(); + } + } } - } - Local result; - Local callback = poCallback->Get(isolate).As(); - - bool success = false; - if (hasErrorOutParameter) { - // We don't want the global error handler (NativeScriptException::OnUncaughtError) to be called - // for javascript exceptions occuring inside methods that have NSError* parameters. Those js - // errors will be marshalled to NSError* and sent directly to the calling native code. The - // v8::TryCatch statement prevents the global handler to be called. - TryCatch tc(isolate); - success = callback->Call(context, thiz, (int)v8Args.size(), v8Args.data()).ToLocal(&result); - if (!success && tc.HasCaught()) { - Local exception = tc.Exception(); - std::string message = tns::ToString(isolate, exception); - - int errorParamIndex = data->initialParamIndex_ + data->paramsCount_ - 1; - void* errorParam = argValues[errorParamIndex]; - NSError* __strong** outPtr = static_cast(errorParam); - if (outPtr && *outPtr) { - NSError* error = - [NSError errorWithDomain:@"TNSErrorDomain" - code:164 - userInfo:@{@"TNSJavaScriptError" : tns::ToNSString(message)}]; - **static_cast(outPtr) = error; + if (pendingThrow == nil) { + if (!success) { + memset(retValue, 0, cif->rtype->size); + } else { + ArgConverter::SetValue(context, retValue, result, data->typeEncoding_); } + } else { + memset(retValue, 0, cif->rtype->size); } - } else { - success = callback->Call(context, thiz, (int)v8Args.size(), v8Args.data()).ToLocal(&result); - } + } // all V8 scopes destruct here - if (!success) { - memset(retValue, 0, cif->rtype->size); - return; + if (pendingThrow != nil) { + @throw pendingThrow; } - - ArgConverter::SetValue(context, retValue, result, data->typeEncoding_); } void ArgConverter::SetValue(Local context, void* retValue, Local value, diff --git a/NativeScript/runtime/ArrayAdapter.mm b/NativeScript/runtime/ArrayAdapter.mm index 2dfba4ec..ed7d10f3 100644 --- a/NativeScript/runtime/ArrayAdapter.mm +++ b/NativeScript/runtime/ArrayAdapter.mm @@ -1,4 +1,5 @@ #include "ArrayAdapter.h" +#include "ArgConverter.h" #include "DataWrapper.h" #include "Helpers.h" #include "Interop.h" @@ -8,99 +9,123 @@ using namespace v8; @implementation ArrayAdapter { - IsolateWrapper* wrapper_; - std::shared_ptr> object_; - // we're responsible for this wrapper - ObjCDataWrapper* dataWrapper_; + IsolateWrapper* wrapper_; + std::shared_ptr> object_; + // we're responsible for this wrapper + ObjCDataWrapper* dataWrapper_; } - (instancetype)initWithJSObject:(Local)jsObject isolate:(Isolate*)isolate { - if (self) { - self->wrapper_ = new IsolateWrapper(isolate); - self->object_ = std::make_shared>(isolate, jsObject); - self->wrapper_->GetCache()->Instances.emplace(self, self->object_); - tns::SetValue(isolate, jsObject, (self->dataWrapper_ = new ObjCDataWrapper(self))); - } - - return self; + if (self) { + self->wrapper_ = new IsolateWrapper(isolate); + self->object_ = std::make_shared>(isolate, jsObject); + self->wrapper_->GetCache()->Instances.emplace(self, self->object_); + tns::SetValue(isolate, jsObject, (self->dataWrapper_ = new ObjCDataWrapper(self))); + } + + return self; } - (NSUInteger)count { - auto isolate = wrapper_->Isolate(); - if(!wrapper_->IsValid()) { - return 0; - } + auto isolate = wrapper_->Isolate(); + if (!wrapper_->IsValid()) { + return 0; + } + NSUInteger result = 0; + // Scopes-before-@throw: a branded escape from the JS boundary is @thrown only + // after the inner block's V8 scopes destruct. + NSException* __strong pendingThrow = nil; + { v8::Locker locker(isolate); Isolate::Scope isolate_scope(isolate); HandleScope handle_scope(isolate); - + Local object = self->object_->Get(isolate).As(); if (object->IsArray()) { - uint32_t length = object.As()->Length(); - return length; + result = object.As()->Length(); + } else { + Local context = wrapper_->GetCache()->GetContext(); + Local propertyNames; + TryCatch tc(isolate); + if (object->GetPropertyNames(context).ToLocal(&propertyNames)) { + result = propertyNames->Length(); + } else { + NSException* ex = ArgConverter::HandleBoundaryException(context, tc); + if (ex != nil) { + pendingThrow = ex; + } + } } - - Local context = wrapper_->GetCache()->GetContext(); - Local propertyNames; - bool success = object->GetPropertyNames(context).ToLocal(&propertyNames); - tns::Assert(success, isolate); - uint32_t length = propertyNames->Length(); - return length; + } + if (pendingThrow != nil) { + @throw pendingThrow; + } + return result; } - (id)objectAtIndex:(NSUInteger)index { - auto isolate = wrapper_->Isolate(); - if (!wrapper_->IsValid()) { - return nil; - } + auto isolate = wrapper_->Isolate(); + if (!wrapper_->IsValid()) { + return nil; + } + + if (!(index < [self count])) { + // Out of bounds: return the adapter default rather than aborting. + return nil; + } + + id result = nil; + NSException* __strong pendingThrow = nil; + { v8::Locker locker(isolate); Isolate::Scope isolate_scope(isolate); HandleScope handle_scope(isolate); - - if (!(index < [self count])) { - tns::Assert(false, isolate); - } - + Local object = self->object_->Get(isolate).As(); Local context = wrapper_->GetCache()->GetContext(); Local item; - bool success = object->Get(context, (uint)index).ToLocal(&item); - tns::Assert(success, isolate); - - if (item->IsNullOrUndefined()) { - return nil; + TryCatch tc(isolate); + if (!object->Get(context, (uint)index).ToLocal(&item)) { + NSException* ex = ArgConverter::HandleBoundaryException(context, tc); + if (ex != nil) { + pendingThrow = ex; + } + } else if (!item->IsNullOrUndefined()) { + result = Interop::ToObject(context, item); } - - id value = Interop::ToObject(context, item); - return value; + } + if (pendingThrow != nil) { + @throw pendingThrow; + } + return result; } - (void)dealloc { - if (wrapper_->IsValid()) { - auto isolate = wrapper_->Isolate(); - v8::Locker locker(isolate); - Isolate::Scope isolate_scope(isolate); - HandleScope handle_scope(isolate); - wrapper_->GetCache()->Instances.erase(self); - Local value = self->object_->Get(isolate); - BaseDataWrapper* wrapper = tns::GetValue(isolate, value); - if (wrapper != nullptr) { - tns::DeleteValue(isolate, value); - // ensure we don't delete the same wrapper twice - // this is just needed as a failsafe in case some other wrapper is assigned to this object - if (wrapper == dataWrapper_) { - dataWrapper_ = nullptr; - } - delete wrapper; - } - self->object_->Reset(); - } - delete wrapper_; - if (dataWrapper_ != nullptr) { - delete dataWrapper_; + if (wrapper_->IsValid()) { + auto isolate = wrapper_->Isolate(); + v8::Locker locker(isolate); + Isolate::Scope isolate_scope(isolate); + HandleScope handle_scope(isolate); + wrapper_->GetCache()->Instances.erase(self); + Local value = self->object_->Get(isolate); + BaseDataWrapper* wrapper = tns::GetValue(isolate, value); + if (wrapper != nullptr) { + tns::DeleteValue(isolate, value); + // ensure we don't delete the same wrapper twice + // this is just needed as a failsafe in case some other wrapper is assigned to this object + if (wrapper == dataWrapper_) { + dataWrapper_ = nullptr; + } + delete wrapper; } - self->object_ = nullptr; - [super dealloc]; + self->object_->Reset(); + } + delete wrapper_; + if (dataWrapper_ != nullptr) { + delete dataWrapper_; + } + self->object_ = nullptr; + [super dealloc]; } @end diff --git a/NativeScript/runtime/BuiltinLoader.cpp b/NativeScript/runtime/BuiltinLoader.cpp new file mode 100644 index 00000000..4df99109 --- /dev/null +++ b/NativeScript/runtime/BuiltinLoader.cpp @@ -0,0 +1,83 @@ +#include "BuiltinLoader.h" + +#include +#include + +#include "Helpers.h" + +using namespace v8; + +namespace tns { + +namespace { + +// Process-wide bytecode cache shared across isolates (main + workers). +std::mutex builtinCacheMutex; +std::vector builtinCache[static_cast(BuiltinId::kCount)]; + +} // namespace + +MaybeLocal BuiltinLoader::RunBuiltin(Local context, + BuiltinId id) { + Isolate* isolate = context->GetIsolate(); + const BuiltinSource& builtin = GetBuiltinSource(id); + const unsigned index = static_cast(id); + + // Copy the blob out so the shared slot can be refreshed concurrently while + // this compile still reads from the copy. + std::vector blob; + { + std::lock_guard lock(builtinCacheMutex); + blob = builtinCache[index]; + } + + ScriptOrigin origin(isolate, tns::ToV8String(isolate, builtin.name), + 0, // line offset + 0, // column offset + false, // shared_cross_origin + -1, // script_id + Local(), + false, // is_opaque + false, // is_wasm + false // is_module + ); + Local sourceText = tns::ToV8String( + isolate, builtin.source, static_cast(builtin.length)); + + Local