/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "JSCRuntime.h" #include #include #include #include #include #include #include #include #include #include namespace facebook { namespace jsc { namespace detail { class ArgsConverter; } // namespace detail class JSCRuntime; struct Lock { void lock(const jsc::JSCRuntime &) const {} void unlock(const jsc::JSCRuntime &) const {} }; class JSCRuntime : public jsi::Runtime { public: // Creates new context in new context group JSCRuntime(); // Retains ctx JSCRuntime(JSGlobalContextRef ctx); ~JSCRuntime(); std::shared_ptr prepareJavaScript( const std::shared_ptr &buffer, std::string sourceURL) override; jsi::Value evaluatePreparedJavaScript( const std::shared_ptr &js) override; jsi::Value evaluateJavaScript( const std::shared_ptr &buffer, const std::string &sourceURL) override; bool drainMicrotasks(int maxMicrotasksHint = -1) override; jsi::Object global() override; std::string description() override; bool isInspectable() override; void setDescription(const std::string &desc); // Please don't use the following two functions, only exposed for // integration efforts. JSGlobalContextRef getContext() { return ctx_; } // JSValueRef->JSValue (needs make.*Value so it must be member function) jsi::Value createValue(JSValueRef value) const; // Value->JSValueRef (similar to above) JSValueRef valueRef(const jsi::Value &value); protected: friend class detail::ArgsConverter; class JSCSymbolValue final : public PointerValue { #ifndef NDEBUG JSCSymbolValue( JSGlobalContextRef ctx, const std::atomic &ctxInvalid, JSValueRef sym, std::atomic &counter); #else JSCSymbolValue( JSGlobalContextRef ctx, const std::atomic &ctxInvalid, JSValueRef sym); #endif void invalidate() override; JSGlobalContextRef ctx_; const std::atomic &ctxInvalid_; // There is no C type in the JSC API to represent Symbol, so this stored // a JSValueRef which contains the Symbol. JSValueRef sym_; #ifndef NDEBUG std::atomic &counter_; #endif protected: friend class JSCRuntime; }; class JSCStringValue final : public PointerValue { #ifndef NDEBUG JSCStringValue(JSStringRef str, std::atomic &counter); #else JSCStringValue(JSStringRef str); #endif void invalidate() override; JSStringRef str_; #ifndef NDEBUG std::atomic &counter_; #endif protected: friend class JSCRuntime; }; class JSCObjectValue final : public PointerValue { JSCObjectValue( JSGlobalContextRef ctx, const std::atomic &ctxInvalid, JSObjectRef obj #ifndef NDEBUG , std::atomic &counter #endif ); void invalidate() override; JSGlobalContextRef ctx_; const std::atomic &ctxInvalid_; JSObjectRef obj_; #ifndef NDEBUG std::atomic &counter_; #endif protected: friend class JSCRuntime; }; PointerValue *cloneSymbol(const Runtime::PointerValue *pv) override; PointerValue *cloneBigInt(const Runtime::PointerValue *pv) override; PointerValue *cloneString(const Runtime::PointerValue *pv) override; PointerValue *cloneObject(const Runtime::PointerValue *pv) override; PointerValue *clonePropNameID(const Runtime::PointerValue *pv) override; jsi::PropNameID createPropNameIDFromAscii(const char *str, size_t length) override; jsi::PropNameID createPropNameIDFromUtf8(const uint8_t *utf8, size_t length) override; jsi::PropNameID createPropNameIDFromString(const jsi::String &str) override; jsi::PropNameID createPropNameIDFromSymbol(const jsi::Symbol &sym) override; std::string utf8(const jsi::PropNameID &) override; bool compare(const jsi::PropNameID &, const jsi::PropNameID &) override; std::string symbolToString(const jsi::Symbol &) override; jsi::BigInt createBigIntFromInt64(int64_t) override; jsi::BigInt createBigIntFromUint64(uint64_t) override; bool bigintIsInt64(const jsi::BigInt &) override; bool bigintIsUint64(const jsi::BigInt &) override; uint64_t truncate(const jsi::BigInt &) override; jsi::String bigintToString(const jsi::BigInt &, int) override; jsi::String createStringFromAscii(const char *str, size_t length) override; jsi::String createStringFromUtf8(const uint8_t *utf8, size_t length) override; std::string utf8(const jsi::String &) override; jsi::Object createObject() override; jsi::Object createObject(std::shared_ptr ho) override; virtual std::shared_ptr getHostObject( const jsi::Object &) override; jsi::HostFunctionType &getHostFunction(const jsi::Function &) override; bool hasNativeState(const jsi::Object &) override; std::shared_ptr getNativeState( const jsi::Object &) override; void setNativeState(const jsi::Object &, std::shared_ptr) override; jsi::Value getProperty(const jsi::Object &, const jsi::String &name) override; jsi::Value getProperty(const jsi::Object &, const jsi::PropNameID &name) override; bool hasProperty(const jsi::Object &, const jsi::String &name) override; bool hasProperty(const jsi::Object &, const jsi::PropNameID &name) override; void setPropertyValue( const jsi::Object &, const jsi::String &name, const jsi::Value &value) override; void setPropertyValue( const jsi::Object &, const jsi::PropNameID &name, const jsi::Value &value) override; bool isArray(const jsi::Object &) const override; bool isArrayBuffer(const jsi::Object &) const override; bool isFunction(const jsi::Object &) const override; bool isHostObject(const jsi::Object &) const override; bool isHostFunction(const jsi::Function &) const override; jsi::Array getPropertyNames(const jsi::Object &) override; // TODO: revisit this implementation jsi::WeakObject createWeakObject(const jsi::Object &) override; jsi::Value lockWeakObject(const jsi::WeakObject &) override; jsi::Array createArray(size_t length) override; jsi::ArrayBuffer createArrayBuffer( std::shared_ptr buffer) override; size_t size(const jsi::Array &) override; size_t size(const jsi::ArrayBuffer &) override; uint8_t *data(const jsi::ArrayBuffer &) override; jsi::Value getValueAtIndex(const jsi::Array &, size_t i) override; void setValueAtIndexImpl( const jsi::Array &, size_t i, const jsi::Value &value) override; jsi::Function createFunctionFromHostFunction( const jsi::PropNameID &name, unsigned int paramCount, jsi::HostFunctionType func) override; jsi::Value call( const jsi::Function &, const jsi::Value &jsThis, const jsi::Value *args, size_t count) override; jsi::Value callAsConstructor( const jsi::Function &, const jsi::Value *args, size_t count) override; bool strictEquals(const jsi::Symbol &a, const jsi::Symbol &b) const override; bool strictEquals(const jsi::BigInt &a, const jsi::BigInt &b) const override; bool strictEquals(const jsi::String &a, const jsi::String &b) const override; bool strictEquals(const jsi::Object &a, const jsi::Object &b) const override; bool instanceOf(const jsi::Object &o, const jsi::Function &f) override; private: // Basically convenience casts static JSValueRef symbolRef(const jsi::Symbol &str); static JSStringRef stringRef(const jsi::String &str); static JSStringRef stringRef(const jsi::PropNameID &sym); static JSObjectRef objectRef(const jsi::Object &obj); #ifdef RN_FABRIC_ENABLED static JSObjectRef objectRef(const jsi::WeakObject &obj); #endif // Factory methods for creating String/Object jsi::Symbol createSymbol(JSValueRef symbolRef) const; jsi::String createString(JSStringRef stringRef) const; jsi::PropNameID createPropNameID(JSStringRef stringRef); jsi::Object createObject(JSObjectRef objectRef) const; // Used by factory methods and clone methods jsi::Runtime::PointerValue *makeSymbolValue(JSValueRef sym) const; jsi::Runtime::PointerValue *makeStringValue(JSStringRef str) const; jsi::Runtime::PointerValue *makeObjectValue(JSObjectRef obj) const; void checkException(JSValueRef exc); void checkException(JSValueRef res, JSValueRef exc); void checkException(JSValueRef exc, const char *msg); void checkException(JSValueRef res, JSValueRef exc, const char *msg); JSGlobalContextRef ctx_; std::atomic ctxInvalid_; std::string desc_; #ifndef NDEBUG mutable std::atomic objectCounter_; mutable std::atomic symbolCounter_; mutable std::atomic stringCounter_; #endif }; #ifndef __has_builtin #define __has_builtin(x) 0 #endif #if __has_builtin(__builtin_expect) || defined(__GNUC__) #define JSC_LIKELY(EXPR) __builtin_expect((bool)(EXPR), true) #define JSC_UNLIKELY(EXPR) __builtin_expect((bool)(EXPR), false) #else #define JSC_LIKELY(EXPR) (EXPR) #define JSC_UNLIKELY(EXPR) (EXPR) #endif #define JSC_ASSERT(x) \ do { \ if (JSC_UNLIKELY(!!(x))) { \ abort(); \ } \ } while (0) #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) // This takes care of watch and tvos (due to backwards compatibility in // Availability.h #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_9_0 #define _JSC_FAST_IS_ARRAY #endif #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0 #define _JSC_NO_ARRAY_BUFFERS #endif #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 160400 #define _JSC_HAS_INSPECTABLE #endif #endif #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) #if __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_11 // Only one of these should be set for a build. If somehow that's not // true, this will be a compile-time error and it can be resolved when // we understand why. #define _JSC_FAST_IS_ARRAY #endif #if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_12 #define _JSC_NO_ARRAY_BUFFERS #endif #endif // JSStringRef utilities namespace { std::string JSStringToSTLString(JSStringRef str) { // Small string optimization: Avoid one heap allocation for strings that fit // in stackBuffer.size() bytes of UTF-8 (including the null terminator). std::array stackBuffer; std::unique_ptr heapBuffer; char *buffer; // NOTE: By definition, maxBytes >= 1 since the null terminator is included. size_t maxBytes = JSStringGetMaximumUTF8CStringSize(str); if (maxBytes <= stackBuffer.size()) { buffer = stackBuffer.data(); } else { heapBuffer = std::make_unique(maxBytes); buffer = heapBuffer.get(); } size_t actualBytes = JSStringGetUTF8CString(str, buffer, maxBytes); if (!actualBytes) { // Happens if maxBytes == 0 (never the case here) or if str contains // invalid UTF-16 data, since JSStringGetUTF8CString attempts a strict // conversion. // When converting an invalid string, JSStringGetUTF8CString writes a null // terminator before returning. So we can reliably treat our buffer as a C // string and return the truncated data to our caller. This is slightly // slower than if we knew the length (like below) but better than crashing. // TODO(T62295565): Perform a non-strict, best effort conversion of the // full string instead, like we did before the JSI migration. return std::string(buffer); } return std::string(buffer, actualBytes - 1); } JSStringRef getLengthString() { static JSStringRef length = JSStringCreateWithUTF8CString("length"); return length; } JSStringRef getNameString() { static JSStringRef name = JSStringCreateWithUTF8CString("name"); return name; } JSStringRef getFunctionString() { static JSStringRef func = JSStringCreateWithUTF8CString("Function"); return func; } #if !defined(_JSC_FAST_IS_ARRAY) JSStringRef getArrayString() { static JSStringRef array = JSStringCreateWithUTF8CString("Array"); return array; } JSStringRef getIsArrayString() { static JSStringRef isArray = JSStringCreateWithUTF8CString("isArray"); return isArray; } #endif } // namespace // std::string utility namespace { std::string to_string(void *value) { std::ostringstream ss; ss << value; return ss.str(); } } // namespace JSCRuntime::JSCRuntime() : JSCRuntime(JSGlobalContextCreateInGroup(nullptr, nullptr)) { JSGlobalContextRelease(ctx_); } JSCRuntime::JSCRuntime(JSGlobalContextRef ctx) : ctx_(JSGlobalContextRetain(ctx)), ctxInvalid_(false) #ifndef NDEBUG , objectCounter_(0), stringCounter_(0) #endif { #ifndef NDEBUG #ifdef _JSC_HAS_INSPECTABLE if (__builtin_available(macOS 13.3, iOS 16.4, tvOS 16.4, *)) { JSGlobalContextSetInspectable(ctx_, true); } #endif #endif } JSCRuntime::~JSCRuntime() { // On shutting down and cleaning up: when JSC is actually torn down, // it calls JSC::Heap::lastChanceToFinalize internally which // finalizes anything left over. But at this point, // JSValueUnprotect() can no longer be called. We use an // atomic to avoid unsafe unprotects happening after shutdown // has started. ctxInvalid_ = true; JSGlobalContextRelease(ctx_); #ifndef NDEBUG assert( objectCounter_ == 0 && "JSCRuntime destroyed with a dangling API object"); assert( stringCounter_ == 0 && "JSCRuntime destroyed with a dangling API string"); #endif } std::shared_ptr JSCRuntime::prepareJavaScript( const std::shared_ptr &buffer, std::string sourceURL) { return std::make_shared( buffer, std::move(sourceURL)); } jsi::Value JSCRuntime::evaluatePreparedJavaScript( const std::shared_ptr &js) { assert( dynamic_cast(js.get()) && "preparedJavaScript must be a SourceJavaScriptPreparation"); auto sourceJs = std::static_pointer_cast(js); return evaluateJavaScript(sourceJs, sourceJs->sourceURL()); } jsi::Value JSCRuntime::evaluateJavaScript( const std::shared_ptr &buffer, const std::string &sourceURL) { std::string tmp( reinterpret_cast(buffer->data()), buffer->size()); JSStringRef sourceRef = JSStringCreateWithUTF8CString(tmp.c_str()); JSStringRef sourceURLRef = nullptr; if (!sourceURL.empty()) { sourceURLRef = JSStringCreateWithUTF8CString(sourceURL.c_str()); } JSValueRef exc = nullptr; JSValueRef res = JSEvaluateScript(ctx_, sourceRef, nullptr, sourceURLRef, 0, &exc); JSStringRelease(sourceRef); if (sourceURLRef) { JSStringRelease(sourceURLRef); } checkException(res, exc); return createValue(res); } bool JSCRuntime::drainMicrotasks(int maxMicrotasksHint) { return true; } jsi::Object JSCRuntime::global() { return createObject(JSContextGetGlobalObject(ctx_)); } std::string JSCRuntime::description() { if (desc_.empty()) { desc_ = std::string(""; } return desc_; } bool JSCRuntime::isInspectable() { return false; } namespace { bool smellsLikeES6Symbol(JSGlobalContextRef ctx, JSValueRef ref) { // Since iOS 13, JSValueGetType will return kJSTypeSymbol // Before: Empirically, an es6 Symbol is not an object, but its type is // object. This makes no sense, but we'll run with it. // https://github.com/WebKit/webkit/blob/master/Source/JavaScriptCore/API/JSValueRef.cpp#L79-L82 JSType type = JSValueGetType(ctx, ref); if (type == /* kJSTypeSymbol */ 6) { return true; } return (!JSValueIsObject(ctx, ref) && type == kJSTypeObject); } } // namespace JSCRuntime::JSCSymbolValue::JSCSymbolValue( JSGlobalContextRef ctx, const std::atomic &ctxInvalid, JSValueRef sym #ifndef NDEBUG , std::atomic &counter #endif ) : ctx_(ctx), ctxInvalid_(ctxInvalid), sym_(sym) #ifndef NDEBUG , counter_(counter) #endif { assert(smellsLikeES6Symbol(ctx_, sym_)); JSValueProtect(ctx_, sym_); #ifndef NDEBUG counter_ += 1; #endif } void JSCRuntime::JSCSymbolValue::invalidate() { #ifndef NDEBUG counter_ -= 1; #endif if (!ctxInvalid_) { JSValueUnprotect(ctx_, sym_); } delete this; } #ifndef NDEBUG JSCRuntime::JSCStringValue::JSCStringValue( JSStringRef str, std::atomic &counter) : str_(JSStringRetain(str)), counter_(counter) { // Since std::atomic returns a copy instead of a reference when calling // operator+= we must do this explicitly in the constructor counter_ += 1; } #else JSCRuntime::JSCStringValue::JSCStringValue(JSStringRef str) : str_(JSStringRetain(str)) {} #endif void JSCRuntime::JSCStringValue::invalidate() { // These JSC{String,Object}Value objects are implicitly owned by the // {String,Object} objects, thus when a String/Object is destructed // the JSC{String,Object}Value should be released. #ifndef NDEBUG counter_ -= 1; #endif JSStringRelease(str_); // Angery reaccs only delete this; } JSCRuntime::JSCObjectValue::JSCObjectValue( JSGlobalContextRef ctx, const std::atomic &ctxInvalid, JSObjectRef obj #ifndef NDEBUG , std::atomic &counter #endif ) : ctx_(ctx), ctxInvalid_(ctxInvalid), obj_(obj) #ifndef NDEBUG , counter_(counter) #endif { JSValueProtect(ctx_, obj_); #ifndef NDEBUG counter_ += 1; #endif } void JSCRuntime::JSCObjectValue::invalidate() { #ifndef NDEBUG counter_ -= 1; #endif // When shutting down the VM, if there is a HostObject which // contains or otherwise owns a jsi::Object, then the final GC will // finalize the HostObject, leading to a call to invalidate(). But // at that point, making calls to JSValueUnprotect will crash. // It is up to the application to make sure that any other calls to // invalidate() happen before VM destruction; see the comment on // jsi::Runtime. // // Another potential concern here is that in the non-shutdown case, // if a HostObject is GCd, JSValueUnprotect will be called from the // JSC finalizer. The documentation warns against this: "You must // not call any function that may cause a garbage collection or an // allocation of a garbage collected object from within a // JSObjectFinalizeCallback. This includes all functions that have a // JSContextRef parameter." However, an audit of the source code for // JSValueUnprotect in late 2018 shows that it cannot cause // allocation or a GC, and further, this code has not changed in // about two years. In the future, we may choose to reintroduce the // mechanism previously used here which uses a separate thread for // JSValueUnprotect, in order to conform to the documented API, but // use the "unsafe" synchronous version on iOS 11 and earlier. if (!ctxInvalid_) { JSValueUnprotect(ctx_, obj_); } delete this; } jsi::Runtime::PointerValue *JSCRuntime::cloneSymbol( const jsi::Runtime::PointerValue *pv) { if (!pv) { return nullptr; } const JSCSymbolValue *symbol = static_cast(pv); return makeSymbolValue(symbol->sym_); } jsi::Runtime::PointerValue *JSCRuntime::cloneBigInt( const Runtime::PointerValue *) { throw std::logic_error("Not implemented"); } jsi::Runtime::PointerValue *JSCRuntime::cloneString( const jsi::Runtime::PointerValue *pv) { if (!pv) { return nullptr; } const JSCStringValue *string = static_cast(pv); return makeStringValue(string->str_); } jsi::Runtime::PointerValue *JSCRuntime::cloneObject( const jsi::Runtime::PointerValue *pv) { if (!pv) { return nullptr; } const JSCObjectValue *object = static_cast(pv); assert( object->ctx_ == ctx_ && "Don't try to clone an object backed by a different Runtime"); return makeObjectValue(object->obj_); } jsi::Runtime::PointerValue *JSCRuntime::clonePropNameID( const jsi::Runtime::PointerValue *pv) { if (!pv) { return nullptr; } const JSCStringValue *string = static_cast(pv); return makeStringValue(string->str_); } jsi::PropNameID JSCRuntime::createPropNameIDFromAscii( const char *str, size_t length) { // For system JSC this must is identical to a string std::string tmp(str, length); JSStringRef strRef = JSStringCreateWithUTF8CString(tmp.c_str()); auto res = createPropNameID(strRef); JSStringRelease(strRef); return res; } jsi::PropNameID JSCRuntime::createPropNameIDFromUtf8( const uint8_t *utf8, size_t length) { std::string tmp(reinterpret_cast(utf8), length); JSStringRef strRef = JSStringCreateWithUTF8CString(tmp.c_str()); auto res = createPropNameID(strRef); JSStringRelease(strRef); return res; } jsi::PropNameID JSCRuntime::createPropNameIDFromString(const jsi::String &str) { return createPropNameID(stringRef(str)); } jsi::PropNameID JSCRuntime::createPropNameIDFromSymbol(const jsi::Symbol &sym) { // TODO: Support for symbols through the native API in JSC is very limited. // While we could construct a PropNameID here, we would not be able to get a // symbol property through the C++ API. throw std::logic_error("Not implemented"); } std::string JSCRuntime::utf8(const jsi::PropNameID &sym) { return JSStringToSTLString(stringRef(sym)); } bool JSCRuntime::compare(const jsi::PropNameID &a, const jsi::PropNameID &b) { return JSStringIsEqual(stringRef(a), stringRef(b)); } std::string JSCRuntime::symbolToString(const jsi::Symbol &sym) { return jsi::Value(*this, sym).toString(*this).utf8(*this); } jsi::BigInt JSCRuntime::createBigIntFromInt64(int64_t) { throw std::logic_error("Not implemented"); } jsi::BigInt JSCRuntime::createBigIntFromUint64(uint64_t) { throw std::logic_error("Not implemented"); } bool JSCRuntime::bigintIsInt64(const jsi::BigInt &) { throw std::logic_error("Not implemented"); } bool JSCRuntime::bigintIsUint64(const jsi::BigInt &) { throw std::logic_error("Not implemented"); } uint64_t JSCRuntime::truncate(const jsi::BigInt &) { throw std::logic_error("Not implemented"); } jsi::String JSCRuntime::bigintToString(const jsi::BigInt &, int) { throw std::logic_error("Not implemented"); } jsi::String JSCRuntime::createStringFromAscii(const char *str, size_t length) { // Yes we end up double casting for semantic reasons (UTF8 contains ASCII, // not the other way around) return this->createStringFromUtf8( reinterpret_cast(str), length); } jsi::String JSCRuntime::createStringFromUtf8( const uint8_t *str, size_t length) { std::string tmp(reinterpret_cast(str), length); JSStringRef stringRef = JSStringCreateWithUTF8CString(tmp.c_str()); auto result = createString(stringRef); JSStringRelease(stringRef); return result; } std::string JSCRuntime::utf8(const jsi::String &str) { return JSStringToSTLString(stringRef(str)); } jsi::Object JSCRuntime::createObject() { return createObject(static_cast(nullptr)); } // HostObject details namespace detail { struct HostObjectProxyBase { HostObjectProxyBase( JSCRuntime &rt, const std::shared_ptr &sho) : runtime(rt), hostObject(sho) {} JSCRuntime &runtime; std::shared_ptr hostObject; }; } // namespace detail namespace { std::once_flag hostObjectClassOnceFlag; JSClassRef hostObjectClass{}; } // namespace jsi::Object JSCRuntime::createObject(std::shared_ptr ho) { struct HostObjectProxy : public detail::HostObjectProxyBase { static JSValueRef getProperty( JSContextRef ctx, JSObjectRef object, JSStringRef propName, JSValueRef *exception) { auto proxy = static_cast(JSObjectGetPrivate(object)); auto &rt = proxy->runtime; jsi::PropNameID sym = rt.createPropNameID(propName); jsi::Value ret; try { ret = proxy->hostObject->get(rt, sym); } catch (const jsi::JSError &error) { *exception = rt.valueRef(error.value()); return JSValueMakeUndefined(ctx); } catch (const std::exception &ex) { auto excValue = rt.global() .getPropertyAsFunction(rt, "Error") .call( rt, std::string("Exception in HostObject::get(propName:") + JSStringToSTLString(propName) + std::string("): ") + ex.what()); *exception = rt.valueRef(excValue); return JSValueMakeUndefined(ctx); } catch (...) { auto excValue = rt.global() .getPropertyAsFunction(rt, "Error") .call( rt, std::string("Exception in HostObject::get(propName:") + JSStringToSTLString(propName) + std::string("): ")); *exception = rt.valueRef(excValue); return JSValueMakeUndefined(ctx); } return rt.valueRef(ret); } #define JSC_UNUSED(x) (void)(x); static bool setProperty( JSContextRef ctx, JSObjectRef object, JSStringRef propName, JSValueRef value, JSValueRef *exception) { JSC_UNUSED(ctx); auto proxy = static_cast(JSObjectGetPrivate(object)); auto &rt = proxy->runtime; jsi::PropNameID sym = rt.createPropNameID(propName); try { proxy->hostObject->set(rt, sym, rt.createValue(value)); } catch (const jsi::JSError &error) { *exception = rt.valueRef(error.value()); return false; } catch (const std::exception &ex) { auto excValue = rt.global() .getPropertyAsFunction(rt, "Error") .call( rt, std::string("Exception in HostObject::set(propName:") + JSStringToSTLString(propName) + std::string("): ") + ex.what()); *exception = rt.valueRef(excValue); return false; } catch (...) { auto excValue = rt.global() .getPropertyAsFunction(rt, "Error") .call( rt, std::string("Exception in HostObject::set(propName:") + JSStringToSTLString(propName) + std::string("): ")); *exception = rt.valueRef(excValue); return false; } return true; } // JSC does not provide means to communicate errors from this callback, // so the error handling strategy is very brutal - we'll just crash // due to noexcept. static void getPropertyNames( JSContextRef ctx, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames) noexcept { JSC_UNUSED(ctx); auto proxy = static_cast(JSObjectGetPrivate(object)); auto &rt = proxy->runtime; auto names = proxy->hostObject->getPropertyNames(rt); for (auto &name : names) { JSPropertyNameAccumulatorAddName(propertyNames, stringRef(name)); } } #undef JSC_UNUSED static void finalize(JSObjectRef obj) { auto hostObject = static_cast(JSObjectGetPrivate(obj)); JSObjectSetPrivate(obj, nullptr); delete hostObject; } using HostObjectProxyBase::HostObjectProxyBase; }; std::call_once(hostObjectClassOnceFlag, []() { JSClassDefinition hostObjectClassDef = kJSClassDefinitionEmpty; hostObjectClassDef.version = 0; hostObjectClassDef.attributes = kJSClassAttributeNoAutomaticPrototype; hostObjectClassDef.finalize = HostObjectProxy::finalize; hostObjectClassDef.getProperty = HostObjectProxy::getProperty; hostObjectClassDef.setProperty = HostObjectProxy::setProperty; hostObjectClassDef.getPropertyNames = HostObjectProxy::getPropertyNames; hostObjectClass = JSClassCreate(&hostObjectClassDef); }); JSObjectRef obj = JSObjectMake(ctx_, hostObjectClass, new HostObjectProxy(*this, ho)); return createObject(obj); } std::shared_ptr JSCRuntime::getHostObject( const jsi::Object &obj) { // We are guaranteed at this point to have isHostObject(obj) == true // so the private data should be HostObjectMetadata JSObjectRef object = objectRef(obj); auto metadata = static_cast(JSObjectGetPrivate(object)); assert(metadata); return metadata->hostObject; } bool JSCRuntime::hasNativeState(const jsi::Object &) { throw std::logic_error("Not implemented"); } std::shared_ptr JSCRuntime::getNativeState( const jsi::Object &) { throw std::logic_error("Not implemented"); } void JSCRuntime::setNativeState( const jsi::Object &, std::shared_ptr) { throw std::logic_error("Not implemented"); } jsi::Value JSCRuntime::getProperty( const jsi::Object &obj, const jsi::String &name) { JSObjectRef objRef = objectRef(obj); JSValueRef exc = nullptr; JSValueRef res = JSObjectGetProperty(ctx_, objRef, stringRef(name), &exc); checkException(exc); return createValue(res); } jsi::Value JSCRuntime::getProperty( const jsi::Object &obj, const jsi::PropNameID &name) { JSObjectRef objRef = objectRef(obj); JSValueRef exc = nullptr; JSValueRef res = JSObjectGetProperty(ctx_, objRef, stringRef(name), &exc); checkException(exc); return createValue(res); } bool JSCRuntime::hasProperty(const jsi::Object &obj, const jsi::String &name) { JSObjectRef objRef = objectRef(obj); return JSObjectHasProperty(ctx_, objRef, stringRef(name)); } bool JSCRuntime::hasProperty( const jsi::Object &obj, const jsi::PropNameID &name) { JSObjectRef objRef = objectRef(obj); return JSObjectHasProperty(ctx_, objRef, stringRef(name)); } void JSCRuntime::setPropertyValue( const jsi::Object &object, const jsi::PropNameID &name, const jsi::Value &value) { JSValueRef exc = nullptr; JSObjectSetProperty( ctx_, objectRef(object), stringRef(name), valueRef(value), kJSPropertyAttributeNone, &exc); checkException(exc); } void JSCRuntime::setPropertyValue( const jsi::Object &object, const jsi::String &name, const jsi::Value &value) { JSValueRef exc = nullptr; JSObjectSetProperty( ctx_, objectRef(object), stringRef(name), valueRef(value), kJSPropertyAttributeNone, &exc); checkException(exc); } bool JSCRuntime::isArray(const jsi::Object &obj) const { #if !defined(_JSC_FAST_IS_ARRAY) JSObjectRef global = JSContextGetGlobalObject(ctx_); JSStringRef arrayString = getArrayString(); JSValueRef exc = nullptr; JSValueRef arrayCtorValue = JSObjectGetProperty(ctx_, global, arrayString, &exc); JSC_ASSERT(exc); JSObjectRef arrayCtor = JSValueToObject(ctx_, arrayCtorValue, &exc); JSC_ASSERT(exc); JSStringRef isArrayString = getIsArrayString(); JSValueRef isArrayValue = JSObjectGetProperty(ctx_, arrayCtor, isArrayString, &exc); JSC_ASSERT(exc); JSObjectRef isArray = JSValueToObject(ctx_, isArrayValue, &exc); JSC_ASSERT(exc); JSValueRef arg = objectRef(obj); JSValueRef result = JSObjectCallAsFunction(ctx_, isArray, nullptr, 1, &arg, &exc); JSC_ASSERT(exc); return JSValueToBoolean(ctx_, result); #else return JSValueIsArray(ctx_, objectRef(obj)); #endif } bool JSCRuntime::isArrayBuffer(const jsi::Object &obj) const { #if defined(_JSC_NO_ARRAY_BUFFERS) throw std::runtime_error("Unsupported"); #else auto typedArrayType = JSValueGetTypedArrayType(ctx_, objectRef(obj), nullptr); return typedArrayType == kJSTypedArrayTypeArrayBuffer; #endif } uint8_t *JSCRuntime::data(const jsi::ArrayBuffer &obj) { #if defined(_JSC_NO_ARRAY_BUFFERS) throw std::runtime_error("Unsupported"); #else return static_cast( JSObjectGetArrayBufferBytesPtr(ctx_, objectRef(obj), nullptr)); #endif } size_t JSCRuntime::size(const jsi::ArrayBuffer &obj) { #if defined(_JSC_NO_ARRAY_BUFFERS) throw std::runtime_error("Unsupported"); #else return JSObjectGetArrayBufferByteLength(ctx_, objectRef(obj), nullptr); #endif } bool JSCRuntime::isFunction(const jsi::Object &obj) const { return JSObjectIsFunction(ctx_, objectRef(obj)); } bool JSCRuntime::isHostObject(const jsi::Object &obj) const { auto cls = hostObjectClass; return cls != nullptr && JSValueIsObjectOfClass(ctx_, objectRef(obj), cls); } // Very expensive jsi::Array JSCRuntime::getPropertyNames(const jsi::Object &obj) { JSPropertyNameArrayRef names = JSObjectCopyPropertyNames(ctx_, objectRef(obj)); size_t len = JSPropertyNameArrayGetCount(names); // Would be better if we could create an array with explicit elements auto result = createArray(len); for (size_t i = 0; i < len; i++) { JSStringRef str = JSPropertyNameArrayGetNameAtIndex(names, i); result.setValueAtIndex(*this, i, createString(str)); } JSPropertyNameArrayRelease(names); return result; } jsi::WeakObject JSCRuntime::createWeakObject(const jsi::Object &obj) { #ifdef RN_FABRIC_ENABLED // TODO: revisit this implementation JSObjectRef objRef = objectRef(obj); return make(makeObjectValue(objRef)); #else throw std::logic_error("Not implemented"); #endif } jsi::Value JSCRuntime::lockWeakObject(const jsi::WeakObject &obj) { #ifdef RN_FABRIC_ENABLED // TODO: revisit this implementation JSObjectRef objRef = objectRef(obj); return jsi::Value(createObject(objRef)); #else throw std::logic_error("Not implemented"); #endif } jsi::Array JSCRuntime::createArray(size_t length) { JSValueRef exc = nullptr; JSObjectRef obj = JSObjectMakeArray(ctx_, 0, nullptr, &exc); checkException(obj, exc); JSObjectSetProperty( ctx_, obj, getLengthString(), JSValueMakeNumber(ctx_, static_cast(length)), 0, &exc); checkException(exc); return createObject(obj).getArray(*this); } jsi::ArrayBuffer JSCRuntime::createArrayBuffer( std::shared_ptr buffer) { throw std::logic_error("Not implemented"); } size_t JSCRuntime::size(const jsi::Array &arr) { return static_cast( getProperty(arr, createPropNameID(getLengthString())).getNumber()); } jsi::Value JSCRuntime::getValueAtIndex(const jsi::Array &arr, size_t i) { JSValueRef exc = nullptr; auto res = JSObjectGetPropertyAtIndex(ctx_, objectRef(arr), (int)i, &exc); checkException(exc); return createValue(res); } void JSCRuntime::setValueAtIndexImpl( const jsi::Array &arr, size_t i, const jsi::Value &value) { JSValueRef exc = nullptr; JSObjectSetPropertyAtIndex( ctx_, objectRef(arr), (int)i, valueRef(value), &exc); checkException(exc); } namespace { std::once_flag hostFunctionClassOnceFlag; JSClassRef hostFunctionClass{}; class HostFunctionProxy { public: HostFunctionProxy(jsi::HostFunctionType hostFunction) : hostFunction_(hostFunction) {} jsi::HostFunctionType &getHostFunction() { return hostFunction_; } protected: jsi::HostFunctionType hostFunction_; }; } // namespace jsi::Function JSCRuntime::createFunctionFromHostFunction( const jsi::PropNameID &name, unsigned int paramCount, jsi::HostFunctionType func) { class HostFunctionMetadata : public HostFunctionProxy { public: static void initialize(JSContextRef ctx, JSObjectRef object) { // We need to set up the prototype chain properly here. In theory we // could set func.prototype.prototype = Function.prototype to get the // same result. Not sure which approach is better. HostFunctionMetadata *metadata = static_cast(JSObjectGetPrivate(object)); JSValueRef exc = nullptr; JSObjectSetProperty( ctx, object, getLengthString(), JSValueMakeNumber(ctx, metadata->argCount), kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete, &exc); if (exc) { // Silently fail to set length exc = nullptr; } JSStringRef name = nullptr; std::swap(metadata->name, name); JSObjectSetProperty( ctx, object, getNameString(), JSValueMakeString(ctx, name), kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete, &exc); JSStringRelease(name); if (exc) { // Silently fail to set name exc = nullptr; } JSObjectRef global = JSContextGetGlobalObject(ctx); JSValueRef value = JSObjectGetProperty(ctx, global, getFunctionString(), &exc); // If we don't have Function then something bad is going on. if (JSC_UNLIKELY(exc)) { abort(); } JSObjectRef funcCtor = JSValueToObject(ctx, value, &exc); if (!funcCtor) { // We can't do anything if Function is not an object return; } JSValueRef funcProto = JSObjectGetPrototype(ctx, funcCtor); JSObjectSetPrototype(ctx, object, funcProto); } static JSValueRef makeError(JSCRuntime &rt, const std::string &desc) { jsi::Value value = rt.global().getPropertyAsFunction(rt, "Error").call(rt, desc); return rt.valueRef(value); } static JSValueRef call( JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { HostFunctionMetadata *metadata = static_cast(JSObjectGetPrivate(function)); JSCRuntime &rt = *(metadata->runtime); const unsigned maxStackArgCount = 8; jsi::Value stackArgs[maxStackArgCount]; std::unique_ptr heapArgs; jsi::Value *args; if (argumentCount > maxStackArgCount) { heapArgs = std::make_unique(argumentCount); for (size_t i = 0; i < argumentCount; i++) { heapArgs[i] = rt.createValue(arguments[i]); } args = heapArgs.get(); } else { for (size_t i = 0; i < argumentCount; i++) { stackArgs[i] = rt.createValue(arguments[i]); } args = stackArgs; } JSValueRef res; jsi::Value thisVal(rt.createObject(thisObject)); try { res = rt.valueRef( metadata->hostFunction_(rt, thisVal, args, argumentCount)); } catch (const jsi::JSError &error) { *exception = rt.valueRef(error.value()); res = JSValueMakeUndefined(ctx); } catch (const std::exception &ex) { std::string exceptionString("Exception in HostFunction: "); exceptionString += ex.what(); *exception = makeError(rt, exceptionString); res = JSValueMakeUndefined(ctx); } catch (...) { std::string exceptionString("Exception in HostFunction: "); *exception = makeError(rt, exceptionString); res = JSValueMakeUndefined(ctx); } return res; } static void finalize(JSObjectRef object) { HostFunctionMetadata *metadata = static_cast(JSObjectGetPrivate(object)); JSObjectSetPrivate(object, nullptr); delete metadata; } HostFunctionMetadata( JSCRuntime *rt, jsi::HostFunctionType hf, unsigned ac, JSStringRef n) : HostFunctionProxy(hf), runtime(rt), argCount(ac), name(JSStringRetain(n)) {} JSCRuntime *runtime; unsigned argCount; JSStringRef name; }; std::call_once(hostFunctionClassOnceFlag, []() { JSClassDefinition functionClass = kJSClassDefinitionEmpty; functionClass.version = 0; functionClass.attributes = kJSClassAttributeNoAutomaticPrototype; functionClass.initialize = HostFunctionMetadata::initialize; functionClass.finalize = HostFunctionMetadata::finalize; functionClass.callAsFunction = HostFunctionMetadata::call; hostFunctionClass = JSClassCreate(&functionClass); }); JSObjectRef funcRef = JSObjectMake( ctx_, hostFunctionClass, new HostFunctionMetadata(this, func, paramCount, stringRef(name))); return createObject(funcRef).getFunction(*this); } namespace detail { class ArgsConverter { public: ArgsConverter(JSCRuntime &rt, const jsi::Value *args, size_t count) { JSValueRef *destination = inline_; if (count > maxStackArgs) { outOfLine_ = std::make_unique(count); destination = outOfLine_.get(); } for (size_t i = 0; i < count; ++i) { destination[i] = rt.valueRef(args[i]); } } operator JSValueRef *() { return outOfLine_ ? outOfLine_.get() : inline_; } private: constexpr static unsigned maxStackArgs = 8; JSValueRef inline_[maxStackArgs]; std::unique_ptr outOfLine_; }; } // namespace detail bool JSCRuntime::isHostFunction(const jsi::Function &obj) const { auto cls = hostFunctionClass; return cls != nullptr && JSValueIsObjectOfClass(ctx_, objectRef(obj), cls); } jsi::HostFunctionType &JSCRuntime::getHostFunction(const jsi::Function &obj) { // We know that isHostFunction(obj) is true here, so its safe to proceed auto proxy = static_cast(JSObjectGetPrivate(objectRef(obj))); return proxy->getHostFunction(); } jsi::Value JSCRuntime::call( const jsi::Function &f, const jsi::Value &jsThis, const jsi::Value *args, size_t count) { JSValueRef exc = nullptr; auto res = JSObjectCallAsFunction( ctx_, objectRef(f), jsThis.isUndefined() ? nullptr : objectRef(jsThis.getObject(*this)), count, detail::ArgsConverter(*this, args, count), &exc); checkException(exc); return createValue(res); } jsi::Value JSCRuntime::callAsConstructor( const jsi::Function &f, const jsi::Value *args, size_t count) { JSValueRef exc = nullptr; auto res = JSObjectCallAsConstructor( ctx_, objectRef(f), count, detail::ArgsConverter(*this, args, count), &exc); checkException(exc); return createValue(res); } bool JSCRuntime::strictEquals(const jsi::Symbol &a, const jsi::Symbol &b) const { JSValueRef exc = nullptr; bool ret = JSValueIsEqual(ctx_, symbolRef(a), symbolRef(b), &exc); const_cast(this)->checkException(exc); return ret; } bool JSCRuntime::strictEquals(const jsi::BigInt &a, const jsi::BigInt &b) const { throw std::logic_error("Not implemented"); } bool JSCRuntime::strictEquals(const jsi::String &a, const jsi::String &b) const { return JSStringIsEqual(stringRef(a), stringRef(b)); } bool JSCRuntime::strictEquals(const jsi::Object &a, const jsi::Object &b) const { return objectRef(a) == objectRef(b); } bool JSCRuntime::instanceOf(const jsi::Object &o, const jsi::Function &f) { JSValueRef exc = nullptr; bool res = JSValueIsInstanceOfConstructor(ctx_, objectRef(o), objectRef(f), &exc); checkException(exc); return res; } jsi::Runtime::PointerValue *JSCRuntime::makeSymbolValue( JSValueRef symbolRef) const { #ifndef NDEBUG return new JSCSymbolValue(ctx_, ctxInvalid_, symbolRef, symbolCounter_); #else return new JSCSymbolValue(ctx_, ctxInvalid_, symbolRef); #endif } namespace { JSStringRef getEmptyString() { static JSStringRef empty = JSStringCreateWithUTF8CString(""); return empty; } } // namespace jsi::Runtime::PointerValue *JSCRuntime::makeStringValue( JSStringRef stringRef) const { if (!stringRef) { stringRef = getEmptyString(); } #ifndef NDEBUG return new JSCStringValue(stringRef, stringCounter_); #else return new JSCStringValue(stringRef); #endif } jsi::Symbol JSCRuntime::createSymbol(JSValueRef sym) const { return make(makeSymbolValue(sym)); } jsi::String JSCRuntime::createString(JSStringRef str) const { return make(makeStringValue(str)); } jsi::PropNameID JSCRuntime::createPropNameID(JSStringRef str) { return make(makeStringValue(str)); } jsi::Runtime::PointerValue *JSCRuntime::makeObjectValue( JSObjectRef objectRef) const { if (!objectRef) { objectRef = JSObjectMake(ctx_, nullptr, nullptr); } #ifndef NDEBUG return new JSCObjectValue(ctx_, ctxInvalid_, objectRef, objectCounter_); #else return new JSCObjectValue(ctx_, ctxInvalid_, objectRef); #endif } jsi::Object JSCRuntime::createObject(JSObjectRef obj) const { return make(makeObjectValue(obj)); } jsi::Value JSCRuntime::createValue(JSValueRef value) const { JSType type = JSValueGetType(ctx_, value); switch (type) { case kJSTypeNumber: return jsi::Value(JSValueToNumber(ctx_, value, nullptr)); case kJSTypeBoolean: return jsi::Value(JSValueToBoolean(ctx_, value)); case kJSTypeNull: return jsi::Value(nullptr); case kJSTypeUndefined: return jsi::Value(); case kJSTypeString: { JSStringRef str = JSValueToStringCopy(ctx_, value, nullptr); auto result = jsi::Value(createString(str)); JSStringRelease(str); return result; } case kJSTypeObject: { JSObjectRef objRef = JSValueToObject(ctx_, value, nullptr); return jsi::Value(createObject(objRef)); } // TODO: Uncomment this when all supported JSC versions have this symbol // case kJSTypeSymbol: default: { if (smellsLikeES6Symbol(ctx_, value)) { return jsi::Value(createSymbol(value)); } else { // WHAT ARE YOU abort(); } } } } JSValueRef JSCRuntime::valueRef(const jsi::Value &value) { // I would rather switch on value.kind_ if (value.isUndefined()) { return JSValueMakeUndefined(ctx_); } else if (value.isNull()) { return JSValueMakeNull(ctx_); } else if (value.isBool()) { return JSValueMakeBoolean(ctx_, value.getBool()); } else if (value.isNumber()) { return JSValueMakeNumber(ctx_, value.getNumber()); } else if (value.isSymbol()) { return symbolRef(value.getSymbol(*this)); } else if (value.isString()) { return JSValueMakeString(ctx_, stringRef(value.getString(*this))); } else if (value.isObject()) { return objectRef(value.getObject(*this)); } else { // What are you? abort(); } } JSValueRef JSCRuntime::symbolRef(const jsi::Symbol &sym) { return static_cast(getPointerValue(sym))->sym_; } JSStringRef JSCRuntime::stringRef(const jsi::String &str) { return static_cast(getPointerValue(str))->str_; } JSStringRef JSCRuntime::stringRef(const jsi::PropNameID &sym) { return static_cast(getPointerValue(sym))->str_; } JSObjectRef JSCRuntime::objectRef(const jsi::Object &obj) { return static_cast(getPointerValue(obj))->obj_; } #ifdef RN_FABRIC_ENABLED JSObjectRef JSCRuntime::objectRef(const jsi::WeakObject &obj) { // TODO: revisit this implementation return static_cast(getPointerValue(obj))->obj_; } #endif void JSCRuntime::checkException(JSValueRef exc) { if (JSC_UNLIKELY(exc)) { throw jsi::JSError(*this, createValue(exc)); } } void JSCRuntime::checkException(JSValueRef res, JSValueRef exc) { if (JSC_UNLIKELY(!res)) { throw jsi::JSError(*this, createValue(exc)); } } void JSCRuntime::checkException(JSValueRef exc, const char *msg) { if (JSC_UNLIKELY(exc)) { throw jsi::JSError(std::string(msg), *this, createValue(exc)); } } void JSCRuntime::checkException( JSValueRef res, JSValueRef exc, const char *msg) { if (JSC_UNLIKELY(!res)) { throw jsi::JSError(std::string(msg), *this, createValue(exc)); } } std::unique_ptr makeJSCRuntime() { return std::make_unique(); } } // namespace jsc } // namespace facebook