From 00e063e68c9f7ee7cc382986450c00a9a134a131 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Mon, 12 Oct 2026 17:48:55 -0700 Subject: [PATCH] feat(jsi): add facebook::jsi and V8Runtime with NitroModules support --- test-app/runtime/CMakeLists.txt | 28 + test-app/runtime/build.gradle | 8 + test-app/runtime/exported-symbols.map | 10 + test-app/runtime/src/main/cpp/jsi/LICENSE.md | 21 + test-app/runtime/src/main/cpp/jsi/decorator.h | 889 ++++++++ .../src/main/cpp/jsi/instrumentation.h | 129 ++ test-app/runtime/src/main/cpp/jsi/jsi-inl.h | 336 +++ test-app/runtime/src/main/cpp/jsi/jsi.cpp | 658 ++++++ test-app/runtime/src/main/cpp/jsi/jsi.h | 1525 ++++++++++++++ .../runtime/src/main/cpp/jsi/jsilib-posix.cpp | 103 + test-app/runtime/src/main/cpp/jsi/jsilib.h | 48 + .../runtime/src/main/cpp/jsi/threadsafe.h | 70 + .../src/main/cpp/v8runtime/HostProxy.cpp | 261 +++ .../src/main/cpp/v8runtime/HostProxy.h | 66 + .../src/main/cpp/v8runtime/JSIRuntime.cpp | 39 + .../src/main/cpp/v8runtime/JSIRuntime.h | 28 + .../cpp/v8runtime/JSIV8ValueConverter.cpp | 176 ++ .../main/cpp/v8runtime/JSIV8ValueConverter.h | 60 + .../runtime/src/main/cpp/v8runtime/LICENSE.md | 21 + .../src/main/cpp/v8runtime/V8PointerValue.cpp | 71 + .../src/main/cpp/v8runtime/V8PointerValue.h | 46 + .../src/main/cpp/v8runtime/V8Runtime.cpp | 1814 +++++++++++++++++ .../src/main/cpp/v8runtime/V8Runtime.h | 237 +++ .../src/main/cpp/v8runtime/V8RuntimeConfig.h | 26 + .../main/cpp/v8runtime/V8RuntimeFactory.cpp | 25 + .../src/main/cpp/v8runtime/V8RuntimeFactory.h | 22 + 26 files changed, 6717 insertions(+) create mode 100644 test-app/runtime/src/main/cpp/jsi/LICENSE.md create mode 100644 test-app/runtime/src/main/cpp/jsi/decorator.h create mode 100644 test-app/runtime/src/main/cpp/jsi/instrumentation.h create mode 100644 test-app/runtime/src/main/cpp/jsi/jsi-inl.h create mode 100644 test-app/runtime/src/main/cpp/jsi/jsi.cpp create mode 100644 test-app/runtime/src/main/cpp/jsi/jsi.h create mode 100644 test-app/runtime/src/main/cpp/jsi/jsilib-posix.cpp create mode 100644 test-app/runtime/src/main/cpp/jsi/jsilib.h create mode 100644 test-app/runtime/src/main/cpp/jsi/threadsafe.h create mode 100644 test-app/runtime/src/main/cpp/v8runtime/HostProxy.cpp create mode 100644 test-app/runtime/src/main/cpp/v8runtime/HostProxy.h create mode 100644 test-app/runtime/src/main/cpp/v8runtime/JSIRuntime.cpp create mode 100644 test-app/runtime/src/main/cpp/v8runtime/JSIRuntime.h create mode 100644 test-app/runtime/src/main/cpp/v8runtime/JSIV8ValueConverter.cpp create mode 100644 test-app/runtime/src/main/cpp/v8runtime/JSIV8ValueConverter.h create mode 100644 test-app/runtime/src/main/cpp/v8runtime/LICENSE.md create mode 100644 test-app/runtime/src/main/cpp/v8runtime/V8PointerValue.cpp create mode 100644 test-app/runtime/src/main/cpp/v8runtime/V8PointerValue.h create mode 100644 test-app/runtime/src/main/cpp/v8runtime/V8Runtime.cpp create mode 100644 test-app/runtime/src/main/cpp/v8runtime/V8Runtime.h create mode 100644 test-app/runtime/src/main/cpp/v8runtime/V8RuntimeConfig.h create mode 100644 test-app/runtime/src/main/cpp/v8runtime/V8RuntimeFactory.cpp create mode 100644 test-app/runtime/src/main/cpp/v8runtime/V8RuntimeFactory.h diff --git a/test-app/runtime/CMakeLists.txt b/test-app/runtime/CMakeLists.txt index f638b92a1..691a8e238 100644 --- a/test-app/runtime/CMakeLists.txt +++ b/test-app/runtime/CMakeLists.txt @@ -60,6 +60,8 @@ include_directories( # the shim's Node-internal stand-ins (env-inl.h, util-inl.h) bare. src/main/cpp/napi/vendor src/main/cpp/napi/shim + src/main/cpp/jsi + src/main/cpp/v8runtime ) # The runtime's builtin JavaScript (src/main/cpp/js) embedded into a generated @@ -249,6 +251,16 @@ add_library( src/main/cpp/napi/NapiThreadSafeFunction.cpp src/main/cpp/napi/NodeApiEmbed.cpp + # JSI (facebook::jsi + rnv8::V8Runtime) + src/main/cpp/jsi/jsi.cpp + src/main/cpp/jsi/jsilib-posix.cpp + src/main/cpp/v8runtime/HostProxy.cpp + src/main/cpp/v8runtime/JSIRuntime.cpp + src/main/cpp/v8runtime/JSIV8ValueConverter.cpp + src/main/cpp/v8runtime/V8PointerValue.cpp + src/main/cpp/v8runtime/V8Runtime.cpp + src/main/cpp/v8runtime/V8RuntimeFactory.cpp + ${RUNTIME_BUILTINS_GENERATED_DIR}/RuntimeBuiltins.cpp # V8 inspector source files will be included only in Release mode @@ -258,6 +270,22 @@ add_library( ${NAPI_TEST_SOURCES} ) +# JSI consumers (e.g. NitroModules) rely on RTTI (std::dynamic_pointer_cast on +# HostObject, NativeState, MutableBuffer, Runtime). Neither jsi nor v8runtime +# subclasses any V8 class, so enabling RTTI on these translation units exports +# their typeinfo without conflicting with V8's -fno-rtti build. +set_source_files_properties( + src/main/cpp/jsi/jsi.cpp + src/main/cpp/jsi/jsilib-posix.cpp + src/main/cpp/v8runtime/HostProxy.cpp + src/main/cpp/v8runtime/JSIRuntime.cpp + src/main/cpp/v8runtime/JSIV8ValueConverter.cpp + src/main/cpp/v8runtime/V8PointerValue.cpp + src/main/cpp/v8runtime/V8Runtime.cpp + src/main/cpp/v8runtime/V8RuntimeFactory.cpp + PROPERTIES COMPILE_FLAGS "-frtti" +) + set(NATIVES_BLOB_INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR}/src/main/libs/${ANDROID_ABI}/include) if (OPTIMIZED_BUILD OR OPTIMIZED_WITH_INSPECTOR_BUILD) diff --git a/test-app/runtime/build.gradle b/test-app/runtime/build.gradle index 16a6951e0..488b74e25 100644 --- a/test-app/runtime/build.gradle +++ b/test-app/runtime/build.gradle @@ -195,6 +195,14 @@ tasks.register("stageNapiPrefab", Sync) { from("src/main/cpp/napi/NapiRuntime.h") { into "prefab/modules/NativeScript/include" } + from("src/main/cpp/jsi") { + include "*.h" + into "prefab/modules/NativeScript/include/jsi" + } + from("src/main/cpp/v8runtime") { + include "JSIRuntime.h", "V8Runtime.h", "V8RuntimeConfig.h", "V8RuntimeFactory.h" + into "prefab/modules/NativeScript/include" + } includeEmptyDirs = false } afterEvaluate { diff --git a/test-app/runtime/exported-symbols.map b/test-app/runtime/exported-symbols.map index 9598f58f8..77d2ce08f 100644 --- a/test-app/runtime/exported-symbols.map +++ b/test-app/runtime/exported-symbols.map @@ -15,11 +15,21 @@ napi_*; node_api_*; NativeScriptNapiEnv; + NativeScriptJSIRuntime; + _ZTIN8facebook3jsi*; + _ZTSN8facebook3jsi*; + _ZTVN8facebook3jsi*; + _ZTIN4rnv8*; + _ZTSN4rnv8*; + _ZTVN4rnv8*; extern "C++" { v8::[A-Z]*; v8::api_internal::*; v8::platform::*; cppgc::[A-Z]*; + facebook::jsi::*; + rnv8::*; + tns::GetJSIRuntime*; }; local: *; diff --git a/test-app/runtime/src/main/cpp/jsi/LICENSE.md b/test-app/runtime/src/main/cpp/jsi/LICENSE.md new file mode 100644 index 000000000..b93be9051 --- /dev/null +++ b/test-app/runtime/src/main/cpp/jsi/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/test-app/runtime/src/main/cpp/jsi/decorator.h b/test-app/runtime/src/main/cpp/jsi/decorator.h new file mode 100644 index 000000000..deaa86b73 --- /dev/null +++ b/test-app/runtime/src/main/cpp/jsi/decorator.h @@ -0,0 +1,889 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include "jsi/instrumentation.h" +#include "jsi/jsi.h" + +// This file contains objects to help API users create their own +// runtime adapters, i.e. if you want to compose runtimes to add your +// own behavior. + +namespace facebook { +namespace jsi { + +// Use this to wrap host functions. It will pass the member runtime as +// the first arg to the callback. The first argument to the ctor +// should be the decorated runtime, not the plain one. +class DecoratedHostFunction { + public: + DecoratedHostFunction(Runtime& drt, HostFunctionType plainHF) + : drt_(drt), plainHF_(std::move(plainHF)) {} + + Runtime& decoratedRuntime() { return drt_; } + + Value operator()(Runtime&, const Value& thisVal, const Value* args, + size_t count) { + return plainHF_(decoratedRuntime(), thisVal, args, count); + } + + private: + template + friend class RuntimeDecorator; + + Runtime& drt_; + HostFunctionType plainHF_; +}; + +// From the perspective of the caller, a plain HostObject is passed to +// the decorated Runtime, and the HostObject methods expect to get +// passed that Runtime. But the plain Runtime will pass itself to its +// callback, so we need a helper here which curries the decorated +// Runtime, and calls the plain HostObject with it. +// +// If the concrete RuntimeDecorator derives DecoratedHostObject, it +// should call the base class get() and set() to invoke the plain +// HostObject functionality. The Runtime& it passes does not matter, +// as it is not used. +class DecoratedHostObject : public HostObject { + public: + DecoratedHostObject(Runtime& drt, std::shared_ptr plainHO) + : drt_(drt), plainHO_(plainHO) {} + + // The derived class methods can call this to get a reference to the + // decorated runtime, since the rt passed to the callback will be + // the plain runtime. + Runtime& decoratedRuntime() { return drt_; } + + Value get(Runtime&, const PropNameID& name) override { + return plainHO_->get(decoratedRuntime(), name); + } + + void set(Runtime&, const PropNameID& name, const Value& value) override { + plainHO_->set(decoratedRuntime(), name, value); + } + + std::vector getPropertyNames(Runtime&) override { + return plainHO_->getPropertyNames(decoratedRuntime()); + } + + private: + template + friend class RuntimeDecorator; + + Runtime& drt_; + std::shared_ptr plainHO_; +}; + +/// C++ variant on a standard Decorator pattern, using template +/// parameters. The \c Plain template parameter type is the +/// undecorated Runtime type. You can usually use \c Runtime here, +/// but if you know the concrete type ahead of time and it's final, +/// the compiler can devirtualize calls to the decorated +/// implementation. The \c Base template parameter type will be used +/// as the base class of the decorated type. Here, too, you can +/// usually use \c Runtime, but if you want the decorated type to +/// implement a derived class of Runtime, you can specify that here. +/// For an example, see threadsafe.h. +template +class RuntimeDecorator : public Base, private jsi::Instrumentation { + public: + Plain& plain() { + static_assert( + std::is_base_of::value, + "RuntimeDecorator's Plain type must derive from jsi::Runtime"); + static_assert(std::is_base_of::value, + "RuntimeDecorator's Base type must derive from jsi::Runtime"); + return plain_; + } + const Plain& plain() const { return plain_; } + + Value evaluateJavaScript(const std::shared_ptr& buffer, + const std::string& sourceURL) override { + return plain().evaluateJavaScript(buffer, sourceURL); + } + std::shared_ptr prepareJavaScript( + const std::shared_ptr& buffer, + std::string sourceURL) override { + return plain().prepareJavaScript(buffer, std::move(sourceURL)); + } + Value evaluatePreparedJavaScript( + const std::shared_ptr& js) override { + return plain().evaluatePreparedJavaScript(js); + } + void queueMicrotask(const jsi::Function& callback) override { + return plain().queueMicrotask(callback); + } + bool drainMicrotasks(int maxMicrotasksHint) override { + return plain().drainMicrotasks(maxMicrotasksHint); + } + Object global() override { return plain().global(); } + std::string description() override { return plain().description(); }; + bool isInspectable() override { return plain().isInspectable(); }; + Instrumentation& instrumentation() override { return *this; } + + protected: + // plain is generally going to be a reference to an object managed + // by a derived class. We cache it here so this class can be + // concrete, and avoid making virtual calls to find the plain + // Runtime. Note that the ctor and dtor do not access through the + // reference, so passing a reference to an object before its + // lifetime has started is ok. + RuntimeDecorator(Plain& plain) : plain_(plain) {} + + Runtime::PointerValue* cloneSymbol(const Runtime::PointerValue* pv) override { + return plain_.cloneSymbol(pv); + }; + Runtime::PointerValue* cloneBigInt(const Runtime::PointerValue* pv) override { + return plain_.cloneBigInt(pv); + }; + Runtime::PointerValue* cloneString(const Runtime::PointerValue* pv) override { + return plain_.cloneString(pv); + }; + Runtime::PointerValue* cloneObject(const Runtime::PointerValue* pv) override { + return plain_.cloneObject(pv); + }; + Runtime::PointerValue* clonePropNameID( + const Runtime::PointerValue* pv) override { + return plain_.clonePropNameID(pv); + }; + + PropNameID createPropNameIDFromAscii(const char* str, + size_t length) override { + return plain_.createPropNameIDFromAscii(str, length); + }; + PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, + size_t length) override { + return plain_.createPropNameIDFromUtf8(utf8, length); + }; + PropNameID createPropNameIDFromString(const String& str) override { + return plain_.createPropNameIDFromString(str); + }; + PropNameID createPropNameIDFromSymbol(const Symbol& sym) override { + return plain_.createPropNameIDFromSymbol(sym); + }; + std::string utf8(const PropNameID& id) override { return plain_.utf8(id); }; + bool compare(const PropNameID& a, const PropNameID& b) override { + return plain_.compare(a, b); + }; + + std::string symbolToString(const Symbol& sym) override { + return plain_.symbolToString(sym); + } + + BigInt createBigIntFromInt64(int64_t value) override { + return plain_.createBigIntFromInt64(value); + } + BigInt createBigIntFromUint64(uint64_t value) override { + return plain_.createBigIntFromUint64(value); + } + bool bigintIsInt64(const BigInt& b) override { + return plain_.bigintIsInt64(b); + } + bool bigintIsUint64(const BigInt& b) override { + return plain_.bigintIsUint64(b); + } + uint64_t truncate(const BigInt& b) override { return plain_.truncate(b); } + String bigintToString(const BigInt& bigint, int radix) override { + return plain_.bigintToString(bigint, radix); + } + + String createStringFromAscii(const char* str, size_t length) override { + return plain_.createStringFromAscii(str, length); + }; + String createStringFromUtf8(const uint8_t* utf8, size_t length) override { + return plain_.createStringFromUtf8(utf8, length); + }; + std::string utf8(const String& s) override { return plain_.utf8(s); } + + std::u16string utf16(const String& str) override { return plain_.utf16(str); } + std::u16string utf16(const PropNameID& sym) override { + return plain_.utf16(sym); + } + + void getStringData(const jsi::String& str, void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, + size_t num)) override { + plain_.getStringData(str, ctx, cb); + } + + void getPropNameIdData(const jsi::PropNameID& sym, void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, + size_t num)) override { + plain_.getPropNameIdData(sym, ctx, cb); + } + + Object createObjectWithPrototype(const Value& prototype) override { + return plain_.createObjectWithPrototype(prototype); + } + + Object createObject() override { return plain_.createObject(); }; + + Object createObject(std::shared_ptr ho) override { + return plain_.createObject( + std::make_shared(*this, std::move(ho))); + }; + std::shared_ptr getHostObject(const jsi::Object& o) override { + std::shared_ptr dho = plain_.getHostObject(o); + return static_cast(*dho).plainHO_; + }; + HostFunctionType& getHostFunction(const jsi::Function& f) override { + HostFunctionType& dhf = plain_.getHostFunction(f); + // This will fail if a cpp file including this header is not compiled + // with RTTI. + return dhf.target()->plainHF_; + }; + + bool hasNativeState(const Object& o) override { + return plain_.hasNativeState(o); + } + std::shared_ptr getNativeState(const Object& o) override { + return plain_.getNativeState(o); + } + void setNativeState(const Object& o, + std::shared_ptr state) override { + plain_.setNativeState(o, state); + } + + void setExternalMemoryPressure(const Object& obj, size_t amt) override { + plain_.setExternalMemoryPressure(obj, amt); + } + + void setPrototypeOf(const Object& object, const Value& prototype) override { + plain_.setPrototypeOf(object, prototype); + } + + Value getPrototypeOf(const Object& object) override { + return plain_.getPrototypeOf(object); + } + + Value getProperty(const Object& o, const PropNameID& name) override { + return plain_.getProperty(o, name); + }; + Value getProperty(const Object& o, const String& name) override { + return plain_.getProperty(o, name); + }; + bool hasProperty(const Object& o, const PropNameID& name) override { + return plain_.hasProperty(o, name); + }; + bool hasProperty(const Object& o, const String& name) override { + return plain_.hasProperty(o, name); + }; + void setPropertyValue(const Object& o, const PropNameID& name, + const Value& value) override { + plain_.setPropertyValue(o, name, value); + }; + void setPropertyValue(const Object& o, const String& name, + const Value& value) override { + plain_.setPropertyValue(o, name, value); + }; + + bool isArray(const Object& o) const override { return plain_.isArray(o); }; + bool isArrayBuffer(const Object& o) const override { + return plain_.isArrayBuffer(o); + }; + bool isFunction(const Object& o) const override { + return plain_.isFunction(o); + }; + bool isHostObject(const jsi::Object& o) const override { + return plain_.isHostObject(o); + }; + bool isHostFunction(const jsi::Function& f) const override { + return plain_.isHostFunction(f); + }; + Array getPropertyNames(const Object& o) override { + return plain_.getPropertyNames(o); + }; + + WeakObject createWeakObject(const Object& o) override { + return plain_.createWeakObject(o); + }; + Value lockWeakObject(const WeakObject& wo) override { + return plain_.lockWeakObject(wo); + }; + + Array createArray(size_t length) override { + return plain_.createArray(length); + }; + ArrayBuffer createArrayBuffer( + std::shared_ptr buffer) override { + return plain_.createArrayBuffer(std::move(buffer)); + }; + size_t size(const Array& a) override { return plain_.size(a); }; + size_t size(const ArrayBuffer& ab) override { return plain_.size(ab); }; + uint8_t* data(const ArrayBuffer& ab) override { return plain_.data(ab); }; + Value getValueAtIndex(const Array& a, size_t i) override { + return plain_.getValueAtIndex(a, i); + }; + void setValueAtIndexImpl(const Array& a, size_t i, + const Value& value) override { + plain_.setValueAtIndexImpl(a, i, value); + }; + + Function createFunctionFromHostFunction(const PropNameID& name, + unsigned int paramCount, + HostFunctionType func) override { + return plain_.createFunctionFromHostFunction( + name, paramCount, DecoratedHostFunction(*this, std::move(func))); + }; + Value call(const Function& f, const Value& jsThis, const Value* args, + size_t count) override { + return plain_.call(f, jsThis, args, count); + }; + Value callAsConstructor(const Function& f, const Value* args, + size_t count) override { + return plain_.callAsConstructor(f, args, count); + }; + + // Private data for managing scopes. + Runtime::ScopeState* pushScope() override { return plain_.pushScope(); } + void popScope(Runtime::ScopeState* ss) override { plain_.popScope(ss); } + + bool strictEquals(const Symbol& a, const Symbol& b) const override { + return plain_.strictEquals(a, b); + }; + bool strictEquals(const BigInt& a, const BigInt& b) const override { + return plain_.strictEquals(a, b); + }; + bool strictEquals(const String& a, const String& b) const override { + return plain_.strictEquals(a, b); + }; + bool strictEquals(const Object& a, const Object& b) const override { + return plain_.strictEquals(a, b); + }; + + bool instanceOf(const Object& o, const Function& f) override { + return plain_.instanceOf(o, f); + }; + + // jsi::Instrumentation methods + + std::string getRecordedGCStats() override { + return plain().instrumentation().getRecordedGCStats(); + } + + std::unordered_map getHeapInfo( + bool includeExpensive) override { + return plain().instrumentation().getHeapInfo(includeExpensive); + } + + void collectGarbage(std::string cause) override { + plain().instrumentation().collectGarbage(std::move(cause)); + } + + void startTrackingHeapObjectStackTraces( + std::function)> + callback) override { + plain().instrumentation().startTrackingHeapObjectStackTraces( + std::move(callback)); + } + + void stopTrackingHeapObjectStackTraces() override { + plain().instrumentation().stopTrackingHeapObjectStackTraces(); + } + + void startHeapSampling(size_t samplingInterval) override { + plain().instrumentation().startHeapSampling(samplingInterval); + } + + void stopHeapSampling(std::ostream& os) override { + plain().instrumentation().stopHeapSampling(os); + } + + void createSnapshotToFile(const std::string& path, + const HeapSnapshotOptions& options) override { + plain().instrumentation().createSnapshotToFile(path, options); + } + + void createSnapshotToStream(std::ostream& os, + const HeapSnapshotOptions& options) override { + plain().instrumentation().createSnapshotToStream(os, options); + } + + std::string flushAndDisableBridgeTrafficTrace() override { + return const_cast(plain()) + .instrumentation() + .flushAndDisableBridgeTrafficTrace(); + } + + void writeBasicBlockProfileTraceToFile( + const std::string& fileName) const override { + const_cast(plain()) + .instrumentation() + .writeBasicBlockProfileTraceToFile(fileName); + } + + /// Dump external profiler symbols to the given file name. + void dumpProfilerSymbolsToFile(const std::string& fileName) const override { + const_cast(plain()).instrumentation().dumpProfilerSymbolsToFile( + fileName); + } + + private: + Plain& plain_; +}; + +namespace detail { + +// This metaprogramming allows the With type's methods to be +// optional. + +template +struct BeforeCaller { + static void before(T&) {} +}; + +template +struct AfterCaller { + static void after(T&) {} +}; + +// decltype((void)&...) is either SFINAE, or void. +// So, if SFINAE does not happen for T, then this specialization exists +// for BeforeCaller, and always applies. If not, only the +// default above exists, and that is used instead. +template +struct BeforeCaller { + static void before(T& t) { t.before(); } +}; + +template +struct AfterCaller { + static void after(T& t) { t.after(); } +}; + +// It's possible to use multiple decorators by nesting +// WithRuntimeDecorator<...>, but this specialization allows use of +// std::tuple of decorator classes instead. See testlib.cpp for an +// example. +template +struct BeforeCaller> { + static void before(std::tuple& tuple) { all_before<0, T...>(tuple); } + + private: + template + static void all_before(std::tuple& tuple) { + detail::BeforeCaller::before(std::get(tuple)); + all_before(tuple); + } + + template + static void all_before(std::tuple&) {} +}; + +template +struct AfterCaller> { + static void after(std::tuple& tuple) { all_after<0, T...>(tuple); } + + private: + template + static void all_after(std::tuple& tuple) { + all_after(tuple); + detail::AfterCaller::after(std::get(tuple)); + } + + template + static void all_after(std::tuple&) {} +}; + +} // namespace detail + +// A decorator which implements an around idiom. A With instance is +// RAII constructed before each call to the undecorated class; the +// ctor is passed a single argument of type WithArg&. Plain and Base +// are used as in the base class. +template +class WithRuntimeDecorator : public RuntimeDecorator { + public: + using RD = RuntimeDecorator; + + // The reference arguments to the ctor are stored, but not used by + // the ctor, and there is no ctor, so they can be passed members of + // the derived class. + WithRuntimeDecorator(Plain& plain, With& with) : RD(plain), with_(with) {} + + Value evaluateJavaScript(const std::shared_ptr& buffer, + const std::string& sourceURL) override { + Around around{with_}; + return RD::evaluateJavaScript(buffer, sourceURL); + } + std::shared_ptr prepareJavaScript( + const std::shared_ptr& buffer, + std::string sourceURL) override { + Around around{with_}; + return RD::prepareJavaScript(buffer, std::move(sourceURL)); + } + Value evaluatePreparedJavaScript( + const std::shared_ptr& js) override { + Around around{with_}; + return RD::evaluatePreparedJavaScript(js); + } + void queueMicrotask(const Function& callback) override { + Around around{with_}; + RD::queueMicrotask(callback); + } + bool drainMicrotasks(int maxMicrotasksHint) override { + Around around{with_}; + return RD::drainMicrotasks(maxMicrotasksHint); + } + Object global() override { + Around around{with_}; + return RD::global(); + } + std::string description() override { + Around around{with_}; + return RD::description(); + }; + bool isInspectable() override { + Around around{with_}; + return RD::isInspectable(); + }; + + // The jsi:: prefix is necessary because MSVC compiler complains C2247: + // Instrumentation is not accessible because RuntimeDecorator uses private + // to inherit from Instrumentation. + // TODO(T40821815) Consider removing this workaround when updating MSVC + jsi::Instrumentation& instrumentation() override { + Around around{with_}; + return RD::instrumentation(); + } + + protected: + Runtime::PointerValue* cloneSymbol(const Runtime::PointerValue* pv) override { + Around around{with_}; + return RD::cloneSymbol(pv); + }; + Runtime::PointerValue* cloneBigInt(const Runtime::PointerValue* pv) override { + Around around{with_}; + return RD::cloneBigInt(pv); + }; + Runtime::PointerValue* cloneString(const Runtime::PointerValue* pv) override { + Around around{with_}; + return RD::cloneString(pv); + }; + Runtime::PointerValue* cloneObject(const Runtime::PointerValue* pv) override { + Around around{with_}; + return RD::cloneObject(pv); + }; + Runtime::PointerValue* clonePropNameID( + const Runtime::PointerValue* pv) override { + Around around{with_}; + return RD::clonePropNameID(pv); + }; + + PropNameID createPropNameIDFromAscii(const char* str, + size_t length) override { + Around around{with_}; + return RD::createPropNameIDFromAscii(str, length); + }; + PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, + size_t length) override { + Around around{with_}; + return RD::createPropNameIDFromUtf8(utf8, length); + }; + PropNameID createPropNameIDFromString(const String& str) override { + Around around{with_}; + return RD::createPropNameIDFromString(str); + }; + PropNameID createPropNameIDFromSymbol(const Symbol& sym) override { + Around around{with_}; + return RD::createPropNameIDFromSymbol(sym); + }; + std::string utf8(const PropNameID& id) override { + Around around{with_}; + return RD::utf8(id); + }; + bool compare(const PropNameID& a, const PropNameID& b) override { + Around around{with_}; + return RD::compare(a, b); + }; + + std::string symbolToString(const Symbol& sym) override { + Around around{with_}; + return RD::symbolToString(sym); + }; + + BigInt createBigIntFromInt64(int64_t i) override { + Around around{with_}; + return RD::createBigIntFromInt64(i); + }; + BigInt createBigIntFromUint64(uint64_t i) override { + Around around{with_}; + return RD::createBigIntFromUint64(i); + }; + bool bigintIsInt64(const BigInt& bi) override { + Around around{with_}; + return RD::bigintIsInt64(bi); + }; + bool bigintIsUint64(const BigInt& bi) override { + Around around{with_}; + return RD::bigintIsUint64(bi); + }; + uint64_t truncate(const BigInt& bi) override { + Around around{with_}; + return RD::truncate(bi); + }; + String bigintToString(const BigInt& bi, int i) override { + Around around{with_}; + return RD::bigintToString(bi, i); + }; + + String createStringFromAscii(const char* str, size_t length) override { + Around around{with_}; + return RD::createStringFromAscii(str, length); + }; + String createStringFromUtf8(const uint8_t* utf8, size_t length) override { + Around around{with_}; + return RD::createStringFromUtf8(utf8, length); + }; + std::string utf8(const String& s) override { + Around around{with_}; + return RD::utf8(s); + } + + std::u16string utf16(const String& str) override { + Around around{with_}; + return RD::utf16(str); + } + std::u16string utf16(const PropNameID& sym) override { + Around around{with_}; + return RD::utf16(sym); + } + + void getStringData(const jsi::String& str, void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, + size_t num)) override { + Around around{with_}; + RD::getStringData(str, ctx, cb); + } + + void getPropNameIdData(const jsi::PropNameID& sym, void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, + size_t num)) override { + Around around{with_}; + RD::getPropNameIdData(sym, ctx, cb); + } + + Value createValueFromJsonUtf8(const uint8_t* json, size_t length) override { + Around around{with_}; + return RD::createValueFromJsonUtf8(json, length); + }; + + Object createObjectWithPrototype(const Value& prototype) override { + Around around{with_}; + return RD::createObjectWithPrototype(prototype); + } + + Object createObject() override { + Around around{with_}; + return RD::createObject(); + }; + Object createObject(std::shared_ptr ho) override { + Around around{with_}; + return RD::createObject(std::move(ho)); + }; + std::shared_ptr getHostObject(const jsi::Object& o) override { + Around around{with_}; + return RD::getHostObject(o); + }; + HostFunctionType& getHostFunction(const jsi::Function& f) override { + Around around{with_}; + return RD::getHostFunction(f); + }; + + bool hasNativeState(const Object& o) override { + Around around{with_}; + return RD::hasNativeState(o); + }; + std::shared_ptr getNativeState(const Object& o) override { + Around around{with_}; + return RD::getNativeState(o); + }; + void setNativeState(const Object& o, + std::shared_ptr state) override { + Around around{with_}; + RD::setNativeState(o, state); + }; + + void setPrototypeOf(const Object& object, const Value& prototype) override { + Around around{with_}; + RD::setPrototypeOf(object, prototype); + } + + Value getPrototypeOf(const Object& object) override { + Around around{with_}; + return RD::getPrototypeOf(object); + } + + Value getProperty(const Object& o, const PropNameID& name) override { + Around around{with_}; + return RD::getProperty(o, name); + }; + Value getProperty(const Object& o, const String& name) override { + Around around{with_}; + return RD::getProperty(o, name); + }; + bool hasProperty(const Object& o, const PropNameID& name) override { + Around around{with_}; + return RD::hasProperty(o, name); + }; + bool hasProperty(const Object& o, const String& name) override { + Around around{with_}; + return RD::hasProperty(o, name); + }; + void setPropertyValue(const Object& o, const PropNameID& name, + const Value& value) override { + Around around{with_}; + RD::setPropertyValue(o, name, value); + }; + void setPropertyValue(const Object& o, const String& name, + const Value& value) override { + Around around{with_}; + RD::setPropertyValue(o, name, value); + }; + + bool isArray(const Object& o) const override { + Around around{with_}; + return RD::isArray(o); + }; + bool isArrayBuffer(const Object& o) const override { + Around around{with_}; + return RD::isArrayBuffer(o); + }; + bool isFunction(const Object& o) const override { + Around around{with_}; + return RD::isFunction(o); + }; + bool isHostObject(const jsi::Object& o) const override { + Around around{with_}; + return RD::isHostObject(o); + }; + bool isHostFunction(const jsi::Function& f) const override { + Around around{with_}; + return RD::isHostFunction(f); + }; + Array getPropertyNames(const Object& o) override { + Around around{with_}; + return RD::getPropertyNames(o); + }; + + WeakObject createWeakObject(const Object& o) override { + Around around{with_}; + return RD::createWeakObject(o); + }; + Value lockWeakObject(const WeakObject& wo) override { + Around around{with_}; + return RD::lockWeakObject(wo); + }; + + Array createArray(size_t length) override { + Around around{with_}; + return RD::createArray(length); + }; + ArrayBuffer createArrayBuffer( + std::shared_ptr buffer) override { + return RD::createArrayBuffer(std::move(buffer)); + }; + size_t size(const Array& a) override { + Around around{with_}; + return RD::size(a); + }; + size_t size(const ArrayBuffer& ab) override { + Around around{with_}; + return RD::size(ab); + }; + uint8_t* data(const ArrayBuffer& ab) override { + Around around{with_}; + return RD::data(ab); + }; + Value getValueAtIndex(const Array& a, size_t i) override { + Around around{with_}; + return RD::getValueAtIndex(a, i); + }; + void setValueAtIndexImpl(const Array& a, size_t i, + const Value& value) override { + Around around{with_}; + RD::setValueAtIndexImpl(a, i, value); + }; + + Function createFunctionFromHostFunction(const PropNameID& name, + unsigned int paramCount, + HostFunctionType func) override { + Around around{with_}; + return RD::createFunctionFromHostFunction(name, paramCount, + std::move(func)); + }; + Value call(const Function& f, const Value& jsThis, const Value* args, + size_t count) override { + Around around{with_}; + return RD::call(f, jsThis, args, count); + }; + Value callAsConstructor(const Function& f, const Value* args, + size_t count) override { + Around around{with_}; + return RD::callAsConstructor(f, args, count); + }; + + // Private data for managing scopes. + Runtime::ScopeState* pushScope() override { + Around around{with_}; + return RD::pushScope(); + } + void popScope(Runtime::ScopeState* ss) override { + Around around{with_}; + RD::popScope(ss); + } + + bool strictEquals(const Symbol& a, const Symbol& b) const override { + Around around{with_}; + return RD::strictEquals(a, b); + }; + bool strictEquals(const BigInt& a, const BigInt& b) const override { + Around around{with_}; + return RD::strictEquals(a, b); + }; + + bool strictEquals(const String& a, const String& b) const override { + Around around{with_}; + return RD::strictEquals(a, b); + }; + bool strictEquals(const Object& a, const Object& b) const override { + Around around{with_}; + return RD::strictEquals(a, b); + }; + + bool instanceOf(const Object& o, const Function& f) override { + Around around{with_}; + return RD::instanceOf(o, f); + }; + + void setExternalMemoryPressure(const jsi::Object& obj, + size_t amount) override { + Around around{with_}; + RD::setExternalMemoryPressure(obj, amount); + }; + + private: + // Wrap an RAII type around With& to guarantee after always happens. + struct Around { + Around(With& with) : with_(with) { + detail::BeforeCaller::before(with_); + } + ~Around() { detail::AfterCaller::after(with_); } + + With& with_; + }; + + With& with_; +}; + +} // namespace jsi +} // namespace facebook \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/jsi/instrumentation.h b/test-app/runtime/src/main/cpp/jsi/instrumentation.h new file mode 100644 index 000000000..db22f4a41 --- /dev/null +++ b/test-app/runtime/src/main/cpp/jsi/instrumentation.h @@ -0,0 +1,129 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "jsi/jsi.h" + +namespace facebook { +namespace jsi { + +/// Methods for starting and collecting instrumentation, an \c Instrumentation +/// instance is associated with a particular \c Runtime instance, which it +/// controls the instrumentation of. +/// None of these functions should return newly created jsi values, nor should +/// it modify the values of any jsi values in the heap (although GCs are fine). +class JSI_EXPORT Instrumentation { + public: + /// Additional options controlling what to include when capturing a heap + /// snapshot. + struct HeapSnapshotOptions { + bool captureNumericValue{false}; + }; + + virtual ~Instrumentation() = default; + + /// Returns GC statistics as a JSON-encoded string, with an object containing + /// "type" and "version" fields outermost. "type" is a string, unique to a + /// particular implementation of \c jsi::Instrumentation, and "version" is a + /// number to indicate any revision to that implementation and its output + /// format. + /// + /// \pre This call can only be made on the instrumentation instance of a + /// runtime initialised to collect GC statistics. + /// + /// \post All cumulative measurements mentioned in the output are accumulated + /// across the entire lifetime of the Runtime. + /// + /// \return the GC statistics collected so far, as a JSON-encoded string. + virtual std::string getRecordedGCStats() = 0; + + /// Request statistics about the current state of the runtime's heap. This + /// function can be called at any time, and should produce information that is + /// correct at the instant it is called (i.e, not stale). + /// + /// \return a map from a string key to a number associated with that + /// statistic. + virtual std::unordered_map getHeapInfo( + bool includeExpensive) = 0; + + /// Perform a full garbage collection. + /// \param cause The cause of this collection, as it should be reported in + /// logs. + virtual void collectGarbage(std::string cause) = 0; + + /// A HeapStatsUpdate is a tuple of the fragment index, the number of objects + /// in that fragment, and the number of bytes used by those objects. + /// A "fragment" is a view of all objects allocated within a time slice. + using HeapStatsUpdate = std::tuple; + + /// Start capturing JS stack-traces for all JS heap allocated objects. These + /// can be accessed via \c ::createSnapshotToFile(). + /// \param fragmentCallback If present, invoke this callback every so often + /// with the most recently seen object ID, and a list of fragments that have + /// been updated. This callback will be invoked on the same thread that the + /// runtime is using. + virtual void startTrackingHeapObjectStackTraces( + std::function stats)> + fragmentCallback) = 0; + + /// Stop capture JS stack-traces for JS heap allocated objects. + virtual void stopTrackingHeapObjectStackTraces() = 0; + + /// Start a heap sampling profiler that will sample heap allocations, and the + /// stack trace they were allocated at. Reports a summary of which functions + /// allocated the most. + /// \param samplingInterval The number of bytes allocated to wait between + /// samples. This will be used as the expected value of a poisson + /// distribution. + virtual void startHeapSampling(size_t samplingInterval) = 0; + + /// Turns off the heap sampling profiler previously enabled via + /// \c startHeapSampling. Writes the output of the sampling heap profiler to + /// \p os. The output is a JSON formatted string. + virtual void stopHeapSampling(std::ostream& os) = 0; + + /// Captures the heap to a file + /// + /// \param path to save the heap capture. + /// \param options additional options for what to capture. + virtual void createSnapshotToFile(const std::string& path, + const HeapSnapshotOptions& options = { + false}) = 0; + + /// Captures the heap to an output stream + /// + /// \param os output stream to write to. + /// \param options additional options for what to capture. + virtual void createSnapshotToStream(std::ostream& os, + const HeapSnapshotOptions& options = { + false}) = 0; + + /// If the runtime has been created to trace to a temp file, flush + /// any unwritten parts of the trace of bridge traffic to the file, + /// and return the name of the file. Otherwise, return the empty string. + /// Tracing is disabled after this call. + virtual std::string flushAndDisableBridgeTrafficTrace() = 0; + + /// Write basic block profile trace to the given file name. + virtual void writeBasicBlockProfileTraceToFile( + const std::string& fileName) const = 0; + + /// Dump external profiler symbols to the given file name. + virtual void dumpProfilerSymbolsToFile(const std::string& fileName) const = 0; +}; + +} // namespace jsi +} // namespace facebook \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/jsi/jsi-inl.h b/test-app/runtime/src/main/cpp/jsi/jsi-inl.h new file mode 100644 index 000000000..8f30c0566 --- /dev/null +++ b/test-app/runtime/src/main/cpp/jsi/jsi-inl.h @@ -0,0 +1,336 @@ +/* + * 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. + */ + +#pragma once + +namespace facebook { +namespace jsi { +namespace detail { + +inline Value toValue(Runtime&, std::nullptr_t) { return Value::null(); } +inline Value toValue(Runtime&, bool b) { return Value(b); } +inline Value toValue(Runtime&, double d) { return Value(d); } +inline Value toValue(Runtime&, float f) { + return Value(static_cast(f)); +} +inline Value toValue(Runtime&, int i) { return Value(i); } +inline Value toValue(Runtime& runtime, const char* str) { + return String::createFromAscii(runtime, str); +} +inline Value toValue(Runtime& runtime, const std::string& str) { + return String::createFromUtf8(runtime, str); +} +template +inline Value toValue(Runtime& runtime, const T& other) { + static_assert(std::is_base_of::value, + "This type cannot be converted to Value"); + return Value(runtime, other); +} +inline Value toValue(Runtime& runtime, const Value& value) { + return Value(runtime, value); +} +inline Value&& toValue(Runtime&, Value&& value) { return std::move(value); } + +inline PropNameID toPropNameID(Runtime& runtime, const char* name) { + return PropNameID::forAscii(runtime, name); +} +inline PropNameID toPropNameID(Runtime& runtime, const std::string& name) { + return PropNameID::forUtf8(runtime, name); +} +inline PropNameID&& toPropNameID(Runtime&, PropNameID&& name) { + return std::move(name); +} + +/// Helper to throw while still compiling with exceptions turned off. +template +[[noreturn]] inline void throwOrDie(Args&&... args) { + std::rethrow_exception( + std::make_exception_ptr(E{std::forward(args)...})); +} + +} // namespace detail + +template +inline T Runtime::make(Runtime::PointerValue* pv) { + return T(pv); +} + +inline Runtime::PointerValue* Runtime::getPointerValue(jsi::Pointer& pointer) { + return pointer.ptr_; +} + +inline const Runtime::PointerValue* Runtime::getPointerValue( + const jsi::Pointer& pointer) { + return pointer.ptr_; +} + +inline const Runtime::PointerValue* Runtime::getPointerValue( + const jsi::Value& value) { + return value.data_.pointer.ptr_; +} + +Value Object::getPrototype(Runtime& runtime) const { + return runtime.getPrototypeOf(*this); +} + +inline Value Object::getProperty(Runtime& runtime, const char* name) const { + return getProperty(runtime, String::createFromAscii(runtime, name)); +} + +inline Value Object::getProperty(Runtime& runtime, const String& name) const { + return runtime.getProperty(*this, name); +} + +inline Value Object::getProperty(Runtime& runtime, + const PropNameID& name) const { + return runtime.getProperty(*this, name); +} + +inline bool Object::hasProperty(Runtime& runtime, const char* name) const { + return hasProperty(runtime, String::createFromAscii(runtime, name)); +} + +inline bool Object::hasProperty(Runtime& runtime, const String& name) const { + return runtime.hasProperty(*this, name); +} + +inline bool Object::hasProperty(Runtime& runtime, + const PropNameID& name) const { + return runtime.hasProperty(*this, name); +} + +template +void Object::setProperty(Runtime& runtime, const char* name, T&& value) const { + setProperty(runtime, String::createFromAscii(runtime, name), + std::forward(value)); +} + +template +void Object::setProperty(Runtime& runtime, const String& name, + T&& value) const { + setPropertyValue(runtime, name, + detail::toValue(runtime, std::forward(value))); +} + +template +void Object::setProperty(Runtime& runtime, const PropNameID& name, + T&& value) const { + setPropertyValue(runtime, name, + detail::toValue(runtime, std::forward(value))); +} + +inline Array Object::getArray(Runtime& runtime) const& { + assert(runtime.isArray(*this)); + (void)runtime; // when assert is disabled we need to mark this as used + return Array(runtime.cloneObject(ptr_)); +} + +inline Array Object::getArray(Runtime& runtime) && { + assert(runtime.isArray(*this)); + (void)runtime; // when assert is disabled we need to mark this as used + Runtime::PointerValue* value = ptr_; + ptr_ = nullptr; + return Array(value); +} + +inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) const& { + assert(runtime.isArrayBuffer(*this)); + (void)runtime; // when assert is disabled we need to mark this as used + return ArrayBuffer(runtime.cloneObject(ptr_)); +} + +inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) && { + assert(runtime.isArrayBuffer(*this)); + (void)runtime; // when assert is disabled we need to mark this as used + Runtime::PointerValue* value = ptr_; + ptr_ = nullptr; + return ArrayBuffer(value); +} + +inline Function Object::getFunction(Runtime& runtime) const& { + assert(runtime.isFunction(*this)); + return Function(runtime.cloneObject(ptr_)); +} + +inline Function Object::getFunction(Runtime& runtime) && { + assert(runtime.isFunction(*this)); + (void)runtime; // when assert is disabled we need to mark this as used + Runtime::PointerValue* value = ptr_; + ptr_ = nullptr; + return Function(value); +} + +template +inline bool Object::isHostObject(Runtime& runtime) const { + return runtime.isHostObject(*this) && + std::dynamic_pointer_cast(runtime.getHostObject(*this)); +} + +template <> +inline bool Object::isHostObject(Runtime& runtime) const { + return runtime.isHostObject(*this); +} + +template +inline std::shared_ptr Object::getHostObject(Runtime& runtime) const { + assert(isHostObject(runtime)); + return std::static_pointer_cast(runtime.getHostObject(*this)); +} + +template +inline std::shared_ptr Object::asHostObject(Runtime& runtime) const { + if (!isHostObject(runtime)) { + detail::throwOrDie( + "Object is not a HostObject of desired type"); + } + return std::static_pointer_cast(runtime.getHostObject(*this)); +} + +template <> +inline std::shared_ptr Object::getHostObject( + Runtime& runtime) const { + assert(runtime.isHostObject(*this)); + return runtime.getHostObject(*this); +} + +template +inline bool Object::hasNativeState(Runtime& runtime) const { + return runtime.hasNativeState(*this) && + std::dynamic_pointer_cast(runtime.getNativeState(*this)); +} + +template <> +inline bool Object::hasNativeState(Runtime& runtime) const { + return runtime.hasNativeState(*this); +} + +template +inline std::shared_ptr Object::getNativeState(Runtime& runtime) const { + assert(hasNativeState(runtime)); + return std::static_pointer_cast(runtime.getNativeState(*this)); +} + +inline void Object::setNativeState(Runtime& runtime, + std::shared_ptr state) const { + runtime.setNativeState(*this, state); +} + +inline void Object::setExternalMemoryPressure(Runtime& runtime, + size_t amt) const { + runtime.setExternalMemoryPressure(*this, amt); +} + +inline Array Object::getPropertyNames(Runtime& runtime) const { + return runtime.getPropertyNames(*this); +} + +inline Value WeakObject::lock(Runtime& runtime) const { + return runtime.lockWeakObject(*this); +} + +template +void Array::setValueAtIndex(Runtime& runtime, size_t i, T&& value) const { + setValueAtIndexImpl(runtime, i, + detail::toValue(runtime, std::forward(value))); +} + +inline Value Array::getValueAtIndex(Runtime& runtime, size_t i) const { + return runtime.getValueAtIndex(*this, i); +} + +inline Function Function::createFromHostFunction(Runtime& runtime, + const jsi::PropNameID& name, + unsigned int paramCount, + jsi::HostFunctionType func) { + return runtime.createFunctionFromHostFunction(name, paramCount, + std::move(func)); +} + +inline Value Function::call(Runtime& runtime, const Value* args, + size_t count) const { + return runtime.call(*this, Value::undefined(), args, count); +} + +inline Value Function::call(Runtime& runtime, + std::initializer_list args) const { + return call(runtime, args.begin(), args.size()); +} + +template +inline Value Function::call(Runtime& runtime, Args&&... args) const { + // A more awesome version of this would be able to create raw values + // which can be used directly without wrapping and unwrapping, but + // this will do for now. + return call(runtime, {detail::toValue(runtime, std::forward(args))...}); +} + +inline Value Function::callWithThis(Runtime& runtime, const Object& jsThis, + const Value* args, size_t count) const { + return runtime.call(*this, Value(runtime, jsThis), args, count); +} + +inline Value Function::callWithThis(Runtime& runtime, const Object& jsThis, + std::initializer_list args) const { + return callWithThis(runtime, jsThis, args.begin(), args.size()); +} + +template +inline Value Function::callWithThis(Runtime& runtime, const Object& jsThis, + Args&&... args) const { + // A more awesome version of this would be able to create raw values + // which can be used directly without wrapping and unwrapping, but + // this will do for now. + return callWithThis(runtime, jsThis, + {detail::toValue(runtime, std::forward(args))...}); +} + +template +inline Array Array::createWithElements(Runtime& runtime, Args&&... args) { + return createWithElements( + runtime, {detail::toValue(runtime, std::forward(args))...}); +} + +template +inline std::vector PropNameID::names(Runtime& runtime, + Args&&... args) { + return names({detail::toPropNameID(runtime, std::forward(args))...}); +} + +template +inline std::vector PropNameID::names( + PropNameID(&&propertyNames)[N]) { + std::vector result; + result.reserve(N); + for (auto& name : propertyNames) { + result.push_back(std::move(name)); + } + return result; +} + +inline Value Function::callAsConstructor(Runtime& runtime, const Value* args, + size_t count) const { + return runtime.callAsConstructor(*this, args, count); +} + +inline Value Function::callAsConstructor( + Runtime& runtime, std::initializer_list args) const { + return callAsConstructor(runtime, args.begin(), args.size()); +} + +template +inline Value Function::callAsConstructor(Runtime& runtime, + Args&&... args) const { + return callAsConstructor( + runtime, {detail::toValue(runtime, std::forward(args))...}); +} + +String BigInt::toString(Runtime& runtime, int radix) const { + return runtime.bigintToString(*this, radix); +} + +} // namespace jsi +} // namespace facebook \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/jsi/jsi.cpp b/test-app/runtime/src/main/cpp/jsi/jsi.cpp new file mode 100644 index 000000000..5e3aac990 --- /dev/null +++ b/test-app/runtime/src/main/cpp/jsi/jsi.cpp @@ -0,0 +1,658 @@ +/* + * 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 "jsi/jsi.h" + +#include +#include +#include +#include + +#include "jsi/instrumentation.h" + +namespace facebook { +namespace jsi { + +namespace { + +// This is used for generating short exception strings. +std::string kindToString(const Value& v, Runtime* rt = nullptr) { + if (v.isUndefined()) { + return "undefined"; + } else if (v.isNull()) { + return "null"; + } else if (v.isBool()) { + return v.getBool() ? "true" : "false"; + } else if (v.isNumber()) { + return "a number"; + } else if (v.isString()) { + return "a string"; + } else if (v.isSymbol()) { + return "a symbol"; + } else if (v.isBigInt()) { + return "a bigint"; + } else { + assert(v.isObject() && "Expecting object."); + return rt != nullptr && v.getObject(*rt).isFunction(*rt) ? "a function" + : "an object"; + } +} + +// getPropertyAsFunction() will try to create a JSError. If the +// failure is in building a JSError, this will lead to infinite +// recursion. This function is used in place of getPropertyAsFunction +// when building JSError, to avoid that infinite recursion. +Value callGlobalFunction(Runtime& runtime, const char* name, const Value& arg) { + Value v = runtime.global().getProperty(runtime, name); + if (!v.isObject()) { + throw JSINativeException( + std::string("callGlobalFunction: JS global property '") + name + + "' is " + kindToString(v, &runtime) + ", expected a Function"); + } + Object o = v.getObject(runtime); + if (!o.isFunction(runtime)) { + throw JSINativeException( + std::string("callGlobalFunction: JS global property '") + name + + "' is a non-callable Object, expected a Function"); + } + Function f = std::move(o).getFunction(runtime); + return f.call(runtime, arg); +} + +// Given a sequence of UTF8 encoded bytes, advance the input to past where a +// 32-bit unicode codepoint as been decoded and return the codepoint. If the +// UTF8 encoding is invalid, then return the value with the unicode replacement +// character (U+FFFD). This decoder also relies on zero termination at end of +// the input for bound checks. +// \param input char pointer pointing to the current character +// \return Unicode codepoint +uint32_t decodeUTF8(const char*& input) { + uint32_t ch = (unsigned char)input[0]; + if (ch <= 0x7f) { + input += 1; + return ch; + } + uint32_t ret; + constexpr uint32_t replacementCharacter = 0xFFFD; + if ((ch & 0xE0) == 0xC0) { + uint32_t ch1 = (unsigned char)input[1]; + if ((ch1 & 0xC0) != 0x80) { + input += 1; + return replacementCharacter; + } + ret = ((ch & 0x1F) << 6) | (ch1 & 0x3F); + input += 2; + if (ret <= 0x7F) { + return replacementCharacter; + } + } else if ((ch & 0xF0) == 0xE0) { + uint32_t ch1 = (unsigned char)input[1]; + if ((ch1 & 0x40) != 0 || (ch1 & 0x80) == 0) { + input += 1; + return replacementCharacter; + } + uint32_t ch2 = (unsigned char)input[2]; + if ((ch2 & 0x40) != 0 || (ch2 & 0x80) == 0) { + input += 2; + return replacementCharacter; + } + ret = ((ch & 0x0F) << 12) | ((ch1 & 0x3F) << 6) | (ch2 & 0x3F); + input += 3; + if (ret <= 0x7FF) { + return replacementCharacter; + } + } else if ((ch & 0xF8) == 0xF0) { + uint32_t ch1 = (unsigned char)input[1]; + if ((ch1 & 0x40) != 0 || (ch1 & 0x80) == 0) { + input += 1; + return replacementCharacter; + } + uint32_t ch2 = (unsigned char)input[2]; + if ((ch2 & 0x40) != 0 || (ch2 & 0x80) == 0) { + input += 2; + return replacementCharacter; + } + uint32_t ch3 = (unsigned char)input[3]; + if ((ch3 & 0x40) != 0 || (ch3 & 0x80) == 0) { + input += 3; + return replacementCharacter; + } + ret = ((ch & 0x07) << 18) | ((ch1 & 0x3F) << 12) | ((ch2 & 0x3F) << 6) | + (ch3 & 0x3F); + input += 4; + if (ret <= 0xFFFF) { + return replacementCharacter; + } + if (ret > 0x10FFFF) { + return replacementCharacter; + } + } else { + input += 1; + return replacementCharacter; + } + return ret; +} + +// Given a valid 32-bit unicode codepoint, encode it as UTF-16 into the output. +void encodeUTF16(std::u16string& out, uint32_t cp) { + if (cp < 0x10000) { + out.push_back((uint16_t)cp); + return; + } + cp -= 0x10000; + uint16_t highSurrogate = 0xD800 + ((cp >> 10) & 0x3FF); + out.push_back(highSurrogate); + uint16_t lowSurrogate = 0xDC00 + (cp & 0x3FF); + out.push_back(lowSurrogate); +} + +// Convert the UTF8 encoded string into a UTF16 encoded string. If the +// input is not valid UTF8, the replacement character (U+FFFD) is used to +// represent the invalid sequence. +std::u16string convertUTF8ToUTF16(const std::string& utf8) { + std::u16string ret; + const char* curr = utf8.data(); + const char* end = curr + utf8.length(); + while (curr < end) { + auto cp = decodeUTF8(curr); + encodeUTF16(ret, cp); + } + return ret; +} + +} // namespace + +Buffer::~Buffer() = default; + +MutableBuffer::~MutableBuffer() = default; + +PreparedJavaScript::~PreparedJavaScript() = default; + +Value HostObject::get(Runtime&, const PropNameID&) { return Value(); } + +void HostObject::set(Runtime& rt, const PropNameID& name, const Value&) { + std::string msg("TypeError: Cannot assign to property '"); + msg += name.utf8(rt); + msg += "' on HostObject with default setter"; + throw JSError(rt, msg); +} + +HostObject::~HostObject() {} + +NativeState::~NativeState() {} + +Runtime::~Runtime() {} + +Instrumentation& Runtime::instrumentation() { + class NoInstrumentation : public Instrumentation { + std::string getRecordedGCStats() override { return ""; } + + std::unordered_map getHeapInfo(bool) override { + return std::unordered_map{}; + } + + void collectGarbage(std::string) override {} + + void startTrackingHeapObjectStackTraces( + std::function)>) override {} + void stopTrackingHeapObjectStackTraces() override {} + + void startHeapSampling(size_t) override {} + void stopHeapSampling(std::ostream&) override {} + + void createSnapshotToFile(const std::string& /*path*/, + const HeapSnapshotOptions& /*options*/) override { + throw JSINativeException( + "Default instrumentation cannot create a heap snapshot"); + } + + void createSnapshotToStream( + std::ostream& /*os*/, const HeapSnapshotOptions& /*options*/) override { + throw JSINativeException( + "Default instrumentation cannot create a heap snapshot"); + } + + std::string flushAndDisableBridgeTrafficTrace() override { std::abort(); } + + void writeBasicBlockProfileTraceToFile(const std::string&) const override { + std::abort(); + } + + void dumpProfilerSymbolsToFile(const std::string&) const override { + std::abort(); + } + }; + + static NoInstrumentation sharedInstance; + return sharedInstance; +} + +Value Runtime::createValueFromJsonUtf8(const uint8_t* json, size_t length) { + Function parseJson = global() + .getPropertyAsObject(*this, "JSON") + .getPropertyAsFunction(*this, "parse"); + return parseJson.call(*this, String::createFromUtf8(*this, json, length)); +} + +std::u16string Runtime::utf16(const PropNameID& sym) { + auto utf8Str = utf8(sym); + return convertUTF8ToUTF16(utf8Str); +} + +std::u16string Runtime::utf16(const String& str) { + auto utf8Str = utf8(str); + return convertUTF8ToUTF16(utf8Str); +} + +void Runtime::getStringData(const jsi::String& str, void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, + size_t num)) { + auto utf16Str = utf16(str); + cb(ctx, false, utf16Str.data(), utf16Str.size()); +} + +void Runtime::getPropNameIdData(const jsi::PropNameID& sym, void* ctx, + void (*cb)(void* ctx, bool ascii, + const void* data, size_t num)) { + auto utf16Str = utf16(sym); + cb(ctx, false, utf16Str.data(), utf16Str.size()); +} + +void Runtime::setPrototypeOf(const Object& object, const Value& prototype) { + auto setPrototypeOfFn = global() + .getPropertyAsObject(*this, "Object") + .getPropertyAsFunction(*this, "setPrototypeOf"); + setPrototypeOfFn.call(*this, object, prototype).asObject(*this); +} + +Value Runtime::getPrototypeOf(const Object& object) { + auto setPrototypeOfFn = global() + .getPropertyAsObject(*this, "Object") + .getPropertyAsFunction(*this, "getPrototypeOf"); + return setPrototypeOfFn.call(*this, object); +} + +Object Runtime::createObjectWithPrototype(const Value& prototype) { + auto createFn = global() + .getPropertyAsObject(*this, "Object") + .getPropertyAsFunction(*this, "create"); + return createFn.call(*this, prototype).asObject(*this); +} + +Pointer& Pointer::operator=(Pointer&& other) noexcept { + if (ptr_) { + ptr_->invalidate(); + } + ptr_ = other.ptr_; + other.ptr_ = nullptr; + return *this; +} + +Object Object::getPropertyAsObject(Runtime& runtime, const char* name) const { + Value v = getProperty(runtime, name); + + if (!v.isObject()) { + throw JSError(runtime, std::string("getPropertyAsObject: property '") + + name + "' is " + kindToString(v, &runtime) + + ", expected an Object"); + } + + return v.getObject(runtime); +} + +Function Object::getPropertyAsFunction(Runtime& runtime, + const char* name) const { + Object obj = getPropertyAsObject(runtime, name); + if (!obj.isFunction(runtime)) { + throw JSError(runtime, std::string("getPropertyAsFunction: property '") + + name + "' is " + + kindToString(std::move(obj), &runtime) + + ", expected a Function"); + }; + + return std::move(obj).getFunction(runtime); +} + +Array Object::asArray(Runtime& runtime) const& { + if (!isArray(runtime)) { + throw JSError(runtime, "Object is " + + kindToString(Value(runtime, *this), &runtime) + + ", expected an array"); + } + return getArray(runtime); +} + +Array Object::asArray(Runtime& runtime) && { + if (!isArray(runtime)) { + throw JSError(runtime, "Object is " + + kindToString(Value(runtime, *this), &runtime) + + ", expected an array"); + } + return std::move(*this).getArray(runtime); +} + +Function Object::asFunction(Runtime& runtime) const& { + if (!isFunction(runtime)) { + throw JSError(runtime, "Object is " + + kindToString(Value(runtime, *this), &runtime) + + ", expected a function"); + } + return getFunction(runtime); +} + +Function Object::asFunction(Runtime& runtime) && { + if (!isFunction(runtime)) { + throw JSError(runtime, "Object is " + + kindToString(Value(runtime, *this), &runtime) + + ", expected a function"); + } + return std::move(*this).getFunction(runtime); +} + +Value::Value(Value&& other) noexcept : Value(other.kind_) { + if (kind_ == BooleanKind) { + data_.boolean = other.data_.boolean; + } else if (kind_ == NumberKind) { + data_.number = other.data_.number; + } else if (kind_ >= PointerKind) { + new (&data_.pointer) Pointer(std::move(other.data_.pointer)); + } + // when the other's dtor runs, nothing will happen. + other.kind_ = UndefinedKind; +} + +Value::Value(Runtime& runtime, const Value& other) : Value(other.kind_) { + // data_ is uninitialized, so use placement new to create non-POD + // types in it. Any other kind of initialization will call a dtor + // first, which is incorrect. + if (kind_ == BooleanKind) { + data_.boolean = other.data_.boolean; + } else if (kind_ == NumberKind) { + data_.number = other.data_.number; + } else if (kind_ == SymbolKind) { + new (&data_.pointer) Pointer(runtime.cloneSymbol(other.data_.pointer.ptr_)); + } else if (kind_ == BigIntKind) { + new (&data_.pointer) Pointer(runtime.cloneBigInt(other.data_.pointer.ptr_)); + } else if (kind_ == StringKind) { + new (&data_.pointer) Pointer(runtime.cloneString(other.data_.pointer.ptr_)); + } else if (kind_ >= ObjectKind) { + new (&data_.pointer) Pointer(runtime.cloneObject(other.data_.pointer.ptr_)); + } +} + +Value::~Value() { + if (kind_ >= PointerKind) { + data_.pointer.~Pointer(); + } +} + +bool Value::strictEquals(Runtime& runtime, const Value& a, const Value& b) { + if (a.kind_ != b.kind_) { + return false; + } + switch (a.kind_) { + case UndefinedKind: + case NullKind: + return true; + case BooleanKind: + return a.data_.boolean == b.data_.boolean; + case NumberKind: + return a.data_.number == b.data_.number; + case SymbolKind: + return runtime.strictEquals(static_cast(a.data_.pointer), + static_cast(b.data_.pointer)); + case BigIntKind: + return runtime.strictEquals(static_cast(a.data_.pointer), + static_cast(b.data_.pointer)); + case StringKind: + return runtime.strictEquals(static_cast(a.data_.pointer), + static_cast(b.data_.pointer)); + case ObjectKind: + return runtime.strictEquals(static_cast(a.data_.pointer), + static_cast(b.data_.pointer)); + } + return false; +} + +bool Value::asBool() const { + if (!isBool()) { + throw JSINativeException("Value is " + kindToString(*this) + + ", expected a boolean"); + } + + return getBool(); +} + +double Value::asNumber() const { + if (!isNumber()) { + throw JSINativeException("Value is " + kindToString(*this) + + ", expected a number"); + } + + return getNumber(); +} + +Object Value::asObject(Runtime& rt) const& { + if (!isObject()) { + throw JSError( + rt, "Value is " + kindToString(*this, &rt) + ", expected an Object"); + } + + return getObject(rt); +} + +Object Value::asObject(Runtime& rt) && { + if (!isObject()) { + throw JSError( + rt, "Value is " + kindToString(*this, &rt) + ", expected an Object"); + } + auto ptr = data_.pointer.ptr_; + data_.pointer.ptr_ = nullptr; + return static_cast(ptr); +} + +Symbol Value::asSymbol(Runtime& rt) const& { + if (!isSymbol()) { + throw JSError( + rt, "Value is " + kindToString(*this, &rt) + ", expected a Symbol"); + } + + return getSymbol(rt); +} + +Symbol Value::asSymbol(Runtime& rt) && { + if (!isSymbol()) { + throw JSError( + rt, "Value is " + kindToString(*this, &rt) + ", expected a Symbol"); + } + + return std::move(*this).getSymbol(rt); +} + +BigInt Value::asBigInt(Runtime& rt) const& { + if (!isBigInt()) { + throw JSError( + rt, "Value is " + kindToString(*this, &rt) + ", expected a BigInt"); + } + + return getBigInt(rt); +} + +BigInt Value::asBigInt(Runtime& rt) && { + if (!isBigInt()) { + throw JSError( + rt, "Value is " + kindToString(*this, &rt) + ", expected a BigInt"); + } + + return std::move(*this).getBigInt(rt); +} + +String Value::asString(Runtime& rt) const& { + if (!isString()) { + throw JSError( + rt, "Value is " + kindToString(*this, &rt) + ", expected a String"); + } + + return getString(rt); +} + +String Value::asString(Runtime& rt) && { + if (!isString()) { + throw JSError( + rt, "Value is " + kindToString(*this, &rt) + ", expected a String"); + } + + return std::move(*this).getString(rt); +} + +String Value::toString(Runtime& runtime) const { + Function toString = runtime.global().getPropertyAsFunction(runtime, "String"); + return toString.call(runtime, *this).getString(runtime); +} + +uint64_t BigInt::asUint64(Runtime& runtime) const { + if (!isUint64(runtime)) { + throw JSError(runtime, "Lossy truncation in BigInt64::asUint64"); + } + return getUint64(runtime); +} + +int64_t BigInt::asInt64(Runtime& runtime) const { + if (!isInt64(runtime)) { + throw JSError(runtime, "Lossy truncation in BigInt64::asInt64"); + } + return getInt64(runtime); +} + +Array Array::createWithElements(Runtime& rt, + std::initializer_list elements) { + Array result(rt, elements.size()); + size_t index = 0; + for (const auto& element : elements) { + result.setValueAtIndex(rt, index++, element); + } + return result; +} + +std::vector HostObject::getPropertyNames(Runtime&) { return {}; } + +Runtime::ScopeState* Runtime::pushScope() { return nullptr; } + +void Runtime::popScope(ScopeState*) {} + +JSError::JSError(Runtime& rt, Value&& value) { setValue(rt, std::move(value)); } + +JSError::JSError(Runtime& rt, std::string msg) : message_(std::move(msg)) { + try { + setValue(rt, callGlobalFunction(rt, "Error", + String::createFromUtf8(rt, message_))); + } catch (const JSIException& ex) { + message_ = std::string(ex.what()) + " (while raising " + message_ + ")"; + setValue(rt, String::createFromUtf8(rt, message_)); + } +} + +JSError::JSError(Runtime& rt, std::string msg, std::string stack) + : message_(std::move(msg)), stack_(std::move(stack)) { + try { + Object e(rt); + e.setProperty(rt, "message", String::createFromUtf8(rt, message_)); + e.setProperty(rt, "stack", String::createFromUtf8(rt, stack_)); + setValue(rt, std::move(e)); + } catch (const JSIException& ex) { + setValue(rt, String::createFromUtf8(rt, ex.what())); + } +} + +JSError::JSError(std::string what, Runtime& rt, Value&& value) + : JSIException(std::move(what)) { + setValue(rt, std::move(value)); +} + +JSError::JSError(Value&& value, std::string message, std::string stack) + : JSIException(message + "\n\n" + stack), + value_(std::make_shared(std::move(value))), + message_(std::move(message)), + stack_(std::move(stack)) {} + +void JSError::setValue(Runtime& rt, Value&& value) { + value_ = std::make_shared(std::move(value)); + + if ((message_.empty() || stack_.empty()) && value_->isObject()) { + auto obj = value_->getObject(rt); + + if (message_.empty()) { + try { + Value message = obj.getProperty(rt, "message"); + if (!message.isUndefined() && !message.isString()) { + message = callGlobalFunction(rt, "String", message); + } + if (message.isString()) { + message_ = message.getString(rt).utf8(rt); + } else if (!message.isUndefined()) { + message_ = "String(e.message) is a " + kindToString(message, &rt); + } + } catch (const JSIException& ex) { + message_ = std::string("[Exception while creating message string: ") + + ex.what() + "]"; + } + } + + if (stack_.empty()) { + try { + Value stack = obj.getProperty(rt, "stack"); + if (!stack.isUndefined() && !stack.isString()) { + stack = callGlobalFunction(rt, "String", stack); + } + if (stack.isString()) { + stack_ = stack.getString(rt).utf8(rt); + } else if (!stack.isUndefined()) { + stack_ = "String(e.stack) is a " + kindToString(stack, &rt); + } + } catch (const JSIException& ex) { + message_ = std::string("[Exception while creating stack string: ") + + ex.what() + "]"; + } + } + } + + if (message_.empty()) { + try { + if (value_->isString()) { + message_ = value_->getString(rt).utf8(rt); + } else { + Value message = callGlobalFunction(rt, "String", *value_); + if (message.isString()) { + message_ = message.getString(rt).utf8(rt); + } else { + message_ = "String(e) is a " + kindToString(message, &rt); + } + } + } catch (const JSIException& ex) { + message_ = std::string("[Exception while creating message string: ") + + ex.what() + "]"; + } + } + + if (stack_.empty()) { + stack_ = "no stack"; + } + + if (what_.empty()) { + what_ = message_ + "\n\n" + stack_; + } +} + +JSIException::~JSIException() {} + +JSINativeException::~JSINativeException() {} + +JSError::~JSError() {} + +} // namespace jsi +} // namespace facebook \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/jsi/jsi.h b/test-app/runtime/src/main/cpp/jsi/jsi.h new file mode 100644 index 000000000..7d760ef5e --- /dev/null +++ b/test-app/runtime/src/main/cpp/jsi/jsi.h @@ -0,0 +1,1525 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#ifndef JSI_EXPORT +#ifdef _MSC_VER +#ifdef CREATE_SHARED_LIBRARY +#define JSI_EXPORT __declspec(dllexport) +#else +#define JSI_EXPORT +#endif // CREATE_SHARED_LIBRARY +#else // _MSC_VER +#define JSI_EXPORT __attribute__((visibility("default"))) +#endif // _MSC_VER +#endif // !defined(JSI_EXPORT) + +class FBJSRuntime; +namespace facebook { +namespace jsi { + +/// Base class for buffers of data or bytecode that need to be passed to the +/// runtime. The buffer is expected to be fully immutable, so the result of +/// size(), data(), and the contents of the pointer returned by data() must not +/// change after construction. +class JSI_EXPORT Buffer { + public: + virtual ~Buffer(); + virtual size_t size() const = 0; + virtual const uint8_t* data() const = 0; +}; + +class JSI_EXPORT StringBuffer : public Buffer { + public: + StringBuffer(std::string s) : s_(std::move(s)) {} + size_t size() const override { return s_.size(); } + const uint8_t* data() const override { + return reinterpret_cast(s_.data()); + } + + private: + std::string s_; +}; + +/// Base class for buffers of data that need to be passed to the runtime. The +/// result of size() and data() must not change after construction. However, the +/// region pointed to by data() may be modified by the user or the runtime. The +/// user must ensure that access to the contents of the buffer is properly +/// synchronised. +class JSI_EXPORT MutableBuffer { + public: + virtual ~MutableBuffer(); + virtual size_t size() const = 0; + virtual uint8_t* data() = 0; +}; + +/// PreparedJavaScript is a base class representing JavaScript which is in a +/// form optimized for execution, in a runtime-specific way. Construct one via +/// jsi::Runtime::prepareJavaScript(). +/// ** This is an experimental API that is subject to change. ** +class JSI_EXPORT PreparedJavaScript { + protected: + PreparedJavaScript() = default; + + public: + virtual ~PreparedJavaScript() = 0; +}; + +class Runtime; +class Pointer; +class PropNameID; +class Symbol; +class BigInt; +class String; +class Object; +class WeakObject; +class Array; +class ArrayBuffer; +class Function; +class Value; +class Instrumentation; +class Scope; +class JSIException; +class JSError; + +/// A function which has this type can be registered as a function +/// callable from JavaScript using Function::createFromHostFunction(). +/// When the function is called, args will point to the arguments, and +/// count will indicate how many arguments are passed. The function +/// can return a Value to the caller, or throw an exception. If a C++ +/// exception is thrown, a JS Error will be created and thrown into +/// JS; if the C++ exception extends std::exception, the Error's +/// message will be whatever what() returns. Note that it is undefined whether +/// HostFunctions may or may not be called in strict mode; that is `thisVal` +/// can be any value - it will not necessarily be coerced to an object or +/// or set to the global object. +using HostFunctionType = std::function; + +/// An object which implements this interface can be registered as an +/// Object with the JS runtime. +class JSI_EXPORT HostObject { + public: + // The C++ object's dtor will be called when the GC finalizes this + // object. (This may be as late as when the Runtime is shut down.) + // You have no control over which thread it is called on. This will + // be called from inside the GC, so it is unsafe to do any VM + // operations which require a Runtime&. Derived classes' dtors + // should also avoid doing anything expensive. Calling the dtor on + // a jsi object is explicitly ok. If you want to do JS operations, + // or any nontrivial work, you should add it to a work queue, and + // manage it externally. + virtual ~HostObject(); + + // When JS wants a property with a given name from the HostObject, + // it will call this method. If it throws an exception, the call + // will throw a JS \c Error object. By default this returns undefined. + // \return the value for the property. + virtual Value get(Runtime&, const PropNameID& name); + + // When JS wants to set a property with a given name on the HostObject, + // it will call this method. If it throws an exception, the call will + // throw a JS \c Error object. By default this throws a type error exception + // mimicking the behavior of a frozen object in strict mode. + virtual void set(Runtime&, const PropNameID& name, const Value& value); + + // When JS wants a list of property names for the HostObject, it will + // call this method. If it throws an exception, the call will throw a + // JS \c Error object. The default implementation returns empty vector. + virtual std::vector getPropertyNames(Runtime& rt); +}; + +/// Native state (and destructor) that can be attached to any JS object +/// using setNativeState. +class JSI_EXPORT NativeState { + public: + virtual ~NativeState(); +}; + +/// Represents a JS runtime. Movable, but not copyable. Note that +/// this object may not be thread-aware, but cannot be used safely from +/// multiple threads at once. The application is responsible for +/// ensuring that it is used safely. This could mean using the +/// Runtime from a single thread, using a mutex, doing all work on a +/// serial queue, etc. This restriction applies to the methods of +/// this class, and any method in the API which take a Runtime& as an +/// argument. Destructors (all but ~Scope), operators, or other methods +/// which do not take Runtime& as an argument are safe to call from any +/// thread, but it is still forbidden to make write operations on a single +/// instance of any class from more than one thread. In addition, to +/// make shutdown safe, destruction of objects associated with the Runtime +/// must be destroyed before the Runtime is destroyed, or from the +/// destructor of a managed HostObject or HostFunction. Informally, this +/// means that the main source of unsafe behavior is to hold a jsi object +/// in a non-Runtime-managed object, and not clean it up before the Runtime +/// is shut down. If your lifecycle is such that avoiding this is hard, +/// you will probably need to do use your own locks. +class JSI_EXPORT Runtime { + public: + virtual ~Runtime(); + + /// Evaluates the given JavaScript \c buffer. \c sourceURL is used + /// to annotate the stack trace if there is an exception. The + /// contents may be utf8-encoded JS source code, or binary bytecode + /// whose format is specific to the implementation. If the input + /// format is unknown, or evaluation causes an error, a JSIException + /// will be thrown. + /// Note this function should ONLY be used when there isn't another means + /// through the JSI API. For example, it will be much slower to use this to + /// call a global function than using the JSI APIs to read the function + /// property from the global object and then calling it explicitly. + virtual Value evaluateJavaScript(const std::shared_ptr& buffer, + const std::string& sourceURL) = 0; + + /// Prepares to evaluate the given JavaScript \c buffer by processing it into + /// a form optimized for execution. This may include pre-parsing, compiling, + /// etc. If the input is invalid (for example, cannot be parsed), a + /// JSIException will be thrown. The resulting object is tied to the + /// particular concrete type of Runtime from which it was created. It may be + /// used (via evaluatePreparedJavaScript) in any Runtime of the same concrete + /// type. + /// The PreparedJavaScript object may be passed to multiple VM instances, so + /// they can all share and benefit from the prepared script. + /// As with evaluateJavaScript(), using JavaScript code should be avoided + /// when the JSI API is sufficient. + virtual std::shared_ptr prepareJavaScript( + const std::shared_ptr& buffer, std::string sourceURL) = 0; + + /// Evaluates a PreparedJavaScript. If evaluation causes an error, a + /// JSIException will be thrown. + /// As with evaluateJavaScript(), using JavaScript code should be avoided + /// when the JSI API is sufficient. + virtual Value evaluatePreparedJavaScript( + const std::shared_ptr& js) = 0; + + /// Queues a microtask in the JavaScript VM internal Microtask (a.k.a. Job in + /// ECMA262) queue, to be executed when the host drains microtasks in + /// its event loop implementation. + /// + /// \param callback a function to be executed as a microtask. + virtual void queueMicrotask(const jsi::Function& callback) = 0; + + /// Drain the JavaScript VM internal Microtask (a.k.a. Job in ECMA262) queue. + /// + /// \param maxMicrotasksHint a hint to tell an implementation that it should + /// make a best effort not execute more than the given number. It's default + /// to -1 for infinity (unbounded execution). + /// \return true if the queue is drained or false if there is more work to do. + /// + /// When there were exceptions thrown from the execution of microtasks, + /// implementations shall discard the exceptional jobs. An implementation may + /// \throw a \c JSError object to signal the hosts to handle. In that case, an + /// implementation may or may not suspend the draining. + /// + /// Hosts may call this function again to resume the draining if it was + /// suspended due to either exceptions or the \p maxMicrotasksHint bound. + /// E.g. a host may repetitively invoke this function until the queue is + /// drained to implement the "microtask checkpoint" defined in WHATWG HTML + /// event loop: https://html.spec.whatwg.org/C#perform-a-microtask-checkpoint. + /// + /// Note that error propagation is only a concern if a host needs to implement + /// `queueMicrotask`, a recent API that allows enqueueing arbitrary functions + /// (hence may throw) as microtasks. Exceptions from ECMA-262 Promise Jobs are + /// handled internally to VMs and are never propagated to hosts. + /// + /// This API offers some queue management to hosts at its best effort due to + /// different behaviors and limitations imposed by different VMs and APIs. By + /// the time this is written, An implementation may swallow exceptions (JSC), + /// may not pause (V8), and may not support bounded executions. + virtual bool drainMicrotasks(int maxMicrotasksHint = -1) = 0; + + /// \return the global object + virtual Object global() = 0; + + /// \return a short printable description of the instance. It should + /// at least include some human-readable indication of the runtime + /// implementation. This should only be used by logging, debugging, + /// and other developer-facing callers. + virtual std::string description() = 0; + + /// \return whether or not the underlying runtime supports debugging via the + /// Chrome remote debugging protocol. + /// + /// NOTE: the API for determining whether a runtime is debuggable and + /// registering a runtime with the debugger is still in flux, so please don't + /// use this API unless you know what you're doing. + virtual bool isInspectable() = 0; + + /// \return an interface to extract metrics from this \c Runtime. The default + /// implementation of this function returns an \c Instrumentation instance + /// which returns no metrics. + virtual Instrumentation& instrumentation(); + + protected: + friend class Pointer; + friend class PropNameID; + friend class Symbol; + friend class BigInt; + friend class String; + friend class Object; + friend class WeakObject; + friend class Array; + friend class ArrayBuffer; + friend class Function; + friend class Value; + friend class Scope; + friend class JSError; + + // Potential optimization: avoid the cloneFoo() virtual dispatch, + // and instead just fix the number of fields, and copy them, since + // in practice they are trivially copyable. Sufficient use of + // rvalue arguments/methods would also reduce the number of clones. + + struct PointerValue { + virtual void invalidate() = 0; + + protected: + virtual ~PointerValue() = default; + }; + + virtual PointerValue* cloneSymbol(const Runtime::PointerValue* pv) = 0; + virtual PointerValue* cloneBigInt(const Runtime::PointerValue* pv) = 0; + virtual PointerValue* cloneString(const Runtime::PointerValue* pv) = 0; + virtual PointerValue* cloneObject(const Runtime::PointerValue* pv) = 0; + virtual PointerValue* clonePropNameID(const Runtime::PointerValue* pv) = 0; + + virtual PropNameID createPropNameIDFromAscii(const char* str, + size_t length) = 0; + virtual PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, + size_t length) = 0; + virtual PropNameID createPropNameIDFromString(const String& str) = 0; + virtual PropNameID createPropNameIDFromSymbol(const Symbol& sym) = 0; + virtual std::string utf8(const PropNameID&) = 0; + virtual bool compare(const PropNameID&, const PropNameID&) = 0; + + virtual std::string symbolToString(const Symbol&) = 0; + + virtual BigInt createBigIntFromInt64(int64_t) = 0; + virtual BigInt createBigIntFromUint64(uint64_t) = 0; + virtual bool bigintIsInt64(const BigInt&) = 0; + virtual bool bigintIsUint64(const BigInt&) = 0; + virtual uint64_t truncate(const BigInt&) = 0; + virtual String bigintToString(const BigInt&, int) = 0; + + virtual String createStringFromAscii(const char* str, size_t length) = 0; + virtual String createStringFromUtf8(const uint8_t* utf8, size_t length) = 0; + virtual std::string utf8(const String&) = 0; + + // \return a \c Value created from a utf8-encoded JSON string. The default + // implementation creates a \c String and invokes JSON.parse. + virtual Value createValueFromJsonUtf8(const uint8_t* json, size_t length); + + virtual Object createObject() = 0; + virtual Object createObject(std::shared_ptr ho) = 0; + virtual std::shared_ptr getHostObject(const jsi::Object&) = 0; + virtual HostFunctionType& getHostFunction(const jsi::Function&) = 0; + + // Creates a new Object with the custom prototype + virtual Object createObjectWithPrototype(const Value& prototype); + + virtual bool hasNativeState(const jsi::Object&) = 0; + virtual std::shared_ptr getNativeState(const jsi::Object&) = 0; + virtual void setNativeState(const jsi::Object&, + std::shared_ptr state) = 0; + + virtual void setPrototypeOf(const Object& object, const Value& prototype); + virtual Value getPrototypeOf(const Object& object); + + virtual Value getProperty(const Object&, const PropNameID& name) = 0; + virtual Value getProperty(const Object&, const String& name) = 0; + virtual bool hasProperty(const Object&, const PropNameID& name) = 0; + virtual bool hasProperty(const Object&, const String& name) = 0; + virtual void setPropertyValue(const Object&, const PropNameID& name, + const Value& value) = 0; + virtual void setPropertyValue(const Object&, const String& name, + const Value& value) = 0; + + virtual bool isArray(const Object&) const = 0; + virtual bool isArrayBuffer(const Object&) const = 0; + virtual bool isFunction(const Object&) const = 0; + virtual bool isHostObject(const jsi::Object&) const = 0; + virtual bool isHostFunction(const jsi::Function&) const = 0; + virtual Array getPropertyNames(const Object&) = 0; + + virtual WeakObject createWeakObject(const Object&) = 0; + virtual Value lockWeakObject(const WeakObject&) = 0; + + virtual Array createArray(size_t length) = 0; + virtual ArrayBuffer createArrayBuffer( + std::shared_ptr buffer) = 0; + virtual size_t size(const Array&) = 0; + virtual size_t size(const ArrayBuffer&) = 0; + virtual uint8_t* data(const ArrayBuffer&) = 0; + virtual Value getValueAtIndex(const Array&, size_t i) = 0; + virtual void setValueAtIndexImpl(const Array&, size_t i, + const Value& value) = 0; + + virtual Function createFunctionFromHostFunction(const PropNameID& name, + unsigned int paramCount, + HostFunctionType func) = 0; + virtual Value call(const Function&, const Value& jsThis, const Value* args, + size_t count) = 0; + virtual Value callAsConstructor(const Function&, const Value* args, + size_t count) = 0; + + // Private data for managing scopes. + struct ScopeState; + virtual ScopeState* pushScope(); + virtual void popScope(ScopeState*); + + virtual bool strictEquals(const Symbol& a, const Symbol& b) const = 0; + virtual bool strictEquals(const BigInt& a, const BigInt& b) const = 0; + virtual bool strictEquals(const String& a, const String& b) const = 0; + virtual bool strictEquals(const Object& a, const Object& b) const = 0; + + virtual bool instanceOf(const Object& o, const Function& f) = 0; + + /// See Object::setExternalMemoryPressure. + virtual void setExternalMemoryPressure(const jsi::Object& obj, + size_t amount) = 0; + + virtual std::u16string utf16(const String& str); + virtual std::u16string utf16(const PropNameID& sym); + + /// Invokes the provided callback \p cb with the String content in \p str. + /// The callback must take in three arguments: bool ascii, const void* data, + /// and size_t num, respectively. \p ascii indicates whether the \p data + /// passed to the callback should be interpreted as a pointer to a sequence of + /// \p num ASCII characters or UTF16 characters. Depending on the internal + /// representation of the string, the function may invoke the callback + /// multiple times, with a different format on each invocation. The callback + /// must not access runtime functionality, as any operation on the runtime may + /// invalidate the data pointers. + virtual void getStringData(const jsi::String& str, void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, + size_t num)); + + /// Invokes the provided callback \p cb with the PropNameID content in \p sym. + /// The callback must take in three arguments: bool ascii, const void* data, + /// and size_t num, respectively. \p ascii indicates whether the \p data + /// passed to the callback should be interpreted as a pointer to a sequence of + /// \p num ASCII characters or UTF16 characters. Depending on the internal + /// representation of the string, the function may invoke the callback + /// multiple times, with a different format on each invocation. The callback + /// must not access runtime functionality, as any operation on the runtime may + /// invalidate the data pointers. + virtual void getPropNameIdData(const jsi::PropNameID& sym, void* ctx, + void (*cb)(void* ctx, bool ascii, + const void* data, size_t num)); + + // These exist so derived classes can access the private parts of + // Value, Symbol, String, and Object, which are all friends of Runtime. + template + static T make(PointerValue* pv); + static PointerValue* getPointerValue(Pointer& pointer); + static const PointerValue* getPointerValue(const Pointer& pointer); + static const PointerValue* getPointerValue(const Value& value); + + friend class ::FBJSRuntime; + template + friend class RuntimeDecorator; +}; + +// Base class for pointer-storing types. +class JSI_EXPORT Pointer { + protected: + explicit Pointer(Pointer&& other) noexcept : ptr_(other.ptr_) { + other.ptr_ = nullptr; + } + + ~Pointer() { + if (ptr_) { + ptr_->invalidate(); + } + } + + Pointer& operator=(Pointer&& other) noexcept; + + friend class Runtime; + friend class Value; + + explicit Pointer(Runtime::PointerValue* ptr) : ptr_(ptr) {} + + typename Runtime::PointerValue* ptr_; +}; + +/// Represents something that can be a JS property key. Movable, not copyable. +class JSI_EXPORT PropNameID : public Pointer { + public: + using Pointer::Pointer; + + PropNameID(Runtime& runtime, const PropNameID& other) + : Pointer(runtime.clonePropNameID(other.ptr_)) {} + + PropNameID(PropNameID&& other) = default; + PropNameID& operator=(PropNameID&& other) = default; + + /// Create a JS property name id from ascii values. The data is + /// copied. + static PropNameID forAscii(Runtime& runtime, const char* str, size_t length) { + return runtime.createPropNameIDFromAscii(str, length); + } + + /// Create a property name id from a nul-terminated C ascii name. The data is + /// copied. + static PropNameID forAscii(Runtime& runtime, const char* str) { + return forAscii(runtime, str, strlen(str)); + } + + /// Create a PropNameID from a C++ string. The string is copied. + static PropNameID forAscii(Runtime& runtime, const std::string& str) { + return forAscii(runtime, str.c_str(), str.size()); + } + + /// Create a PropNameID from utf8 values. The data is copied. + /// Results are undefined if \p utf8 contains invalid code points. + static PropNameID forUtf8(Runtime& runtime, const uint8_t* utf8, + size_t length) { + return runtime.createPropNameIDFromUtf8(utf8, length); + } + + /// Create a PropNameID from utf8-encoded octets stored in a + /// std::string. The string data is transformed and copied. + /// Results are undefined if \p utf8 contains invalid code points. + static PropNameID forUtf8(Runtime& runtime, const std::string& utf8) { + return runtime.createPropNameIDFromUtf8( + reinterpret_cast(utf8.data()), utf8.size()); + } + + /// Create a PropNameID from a JS string. + static PropNameID forString(Runtime& runtime, const jsi::String& str) { + return runtime.createPropNameIDFromString(str); + } + + /// Create a PropNameID from a JS symbol. + static PropNameID forSymbol(Runtime& runtime, const jsi::Symbol& sym) { + return runtime.createPropNameIDFromSymbol(sym); + } + + // Creates a vector of PropNameIDs constructed from given arguments. + template + static std::vector names(Runtime& runtime, Args&&... args); + + // Creates a vector of given PropNameIDs. + template + static std::vector names(PropNameID(&&propertyNames)[N]); + + /// Copies the data in a PropNameID as utf8 into a C++ string. + std::string utf8(Runtime& runtime) const { return runtime.utf8(*this); } + + /// Copies the data in a PropNameID as utf16 into a C++ string. + std::u16string utf16(Runtime& runtime) const { return runtime.utf16(*this); } + + /// Invokes the user provided callback to process the content in PropNameId. + /// The callback must take in three arguments: bool ascii, const void* data, + /// and size_t num, respectively. \p ascii indicates whether the \p data + /// passed to the callback should be interpreted as a pointer to a sequence of + /// \p num ASCII characters or UTF16 characters. The function may invoke the + /// callback multiple times, with a different format on each invocation. The + /// callback must not access runtime functionality, as any operation on the + /// runtime may invalidate the data pointers. + template + void getPropNameIdData(Runtime& runtime, CB& cb) const { + runtime.getPropNameIdData( + *this, &cb, [](void* ctx, bool ascii, const void* data, size_t num) { + (*((CB*)ctx))(ascii, data, num); + }); + } + + static bool compare(Runtime& runtime, const jsi::PropNameID& a, + const jsi::PropNameID& b) { + return runtime.compare(a, b); + } + + friend class Runtime; + friend class Value; +}; + +/// Represents a JS Symbol (es6). Movable, not copyable. +/// TODO T40778724: this is a limited implementation sufficient for +/// the debugger not to crash when a Symbol is a property in an Object +/// or element in an array. Complete support for creating will come +/// later. +class JSI_EXPORT Symbol : public Pointer { + public: + using Pointer::Pointer; + + Symbol(Symbol&& other) = default; + Symbol& operator=(Symbol&& other) = default; + + /// \return whether a and b refer to the same symbol. + static bool strictEquals(Runtime& runtime, const Symbol& a, const Symbol& b) { + return runtime.strictEquals(a, b); + } + + /// Converts a Symbol into a C++ string as JS .toString would. The output + /// will look like \c Symbol(description) . + std::string toString(Runtime& runtime) const { + return runtime.symbolToString(*this); + } + + friend class Runtime; + friend class Value; +}; + +/// Represents a JS BigInt. Movable, not copyable. +class JSI_EXPORT BigInt : public Pointer { + public: + using Pointer::Pointer; + + BigInt(BigInt&& other) = default; + BigInt& operator=(BigInt&& other) = default; + + /// Create a BigInt representing the signed 64-bit \p value. + static BigInt fromInt64(Runtime& runtime, int64_t value) { + return runtime.createBigIntFromInt64(value); + } + + /// Create a BigInt representing the unsigned 64-bit \p value. + static BigInt fromUint64(Runtime& runtime, uint64_t value) { + return runtime.createBigIntFromUint64(value); + } + + /// \return whether a === b. + static bool strictEquals(Runtime& runtime, const BigInt& a, const BigInt& b) { + return runtime.strictEquals(a, b); + } + + /// \returns This bigint truncated to a signed 64-bit integer. + int64_t getInt64(Runtime& runtime) const { return runtime.truncate(*this); } + + /// \returns Whether this bigint can be losslessly converted to int64_t. + bool isInt64(Runtime& runtime) const { return runtime.bigintIsInt64(*this); } + + /// \returns This bigint truncated to a signed 64-bit integer. Throws a + /// JSIException if the truncation is lossy. + int64_t asInt64(Runtime& runtime) const; + + /// \returns This bigint truncated to an unsigned 64-bit integer. + uint64_t getUint64(Runtime& runtime) const { return runtime.truncate(*this); } + + /// \returns Whether this bigint can be losslessly converted to uint64_t. + bool isUint64(Runtime& runtime) const { + return runtime.bigintIsUint64(*this); + } + + /// \returns This bigint truncated to an unsigned 64-bit integer. Throws a + /// JSIException if the truncation is lossy. + uint64_t asUint64(Runtime& runtime) const; + + /// \returns this BigInt converted to a String in base \p radix. Throws a + /// JSIException if radix is not in the [2, 36] range. + inline String toString(Runtime& runtime, int radix = 10) const; + + friend class Runtime; + friend class Value; +}; + +/// Represents a JS String. Movable, not copyable. +class JSI_EXPORT String : public Pointer { + public: + using Pointer::Pointer; + + String(String&& other) = default; + String& operator=(String&& other) = default; + + /// Create a JS string from ascii values. The string data is + /// copied. + static String createFromAscii(Runtime& runtime, const char* str, + size_t length) { + return runtime.createStringFromAscii(str, length); + } + + /// Create a JS string from a nul-terminated C ascii string. The + /// string data is copied. + static String createFromAscii(Runtime& runtime, const char* str) { + return createFromAscii(runtime, str, strlen(str)); + } + + /// Create a JS string from a C++ string. The string data is + /// copied. + static String createFromAscii(Runtime& runtime, const std::string& str) { + return createFromAscii(runtime, str.c_str(), str.size()); + } + + /// Create a JS string from utf8-encoded octets. The string data is + /// transformed and copied. Results are undefined if \p utf8 contains invalid + /// code points. + static String createFromUtf8(Runtime& runtime, const uint8_t* utf8, + size_t length) { + return runtime.createStringFromUtf8(utf8, length); + } + + /// Create a JS string from utf8-encoded octets stored in a + /// std::string. The string data is transformed and copied. Results are + /// undefined if \p utf8 contains invalid code points. + static String createFromUtf8(Runtime& runtime, const std::string& utf8) { + return runtime.createStringFromUtf8( + reinterpret_cast(utf8.data()), utf8.length()); + } + + /// \return whether a and b contain the same characters. + static bool strictEquals(Runtime& runtime, const String& a, const String& b) { + return runtime.strictEquals(a, b); + } + + /// Copies the data in a JS string as utf8 into a C++ string. + std::string utf8(Runtime& runtime) const { return runtime.utf8(*this); } + + /// Copies the data in a JS string as utf16 into a C++ string. + std::u16string utf16(Runtime& runtime) const { return runtime.utf16(*this); } + + /// Invokes the user provided callback to process content in String. The + /// callback must take in three arguments: bool ascii, const void* data, and + /// size_t num, respectively. \p ascii indicates whether the \p data passed to + /// the callback should be interpreted as a pointer to a sequence of \p num + /// ASCII characters or UTF16 characters. The function may invoke the callback + /// multiple times, with a different format on each invocation. The callback + /// must not access runtime functionality, as any operation on the runtime may + /// invalidate the data pointers. + template + void getStringData(Runtime& runtime, CB& cb) const { + runtime.getStringData( + *this, &cb, [](void* ctx, bool ascii, const void* data, size_t num) { + (*((CB*)ctx))(ascii, data, num); + }); + } + + friend class Runtime; + friend class Value; +}; + +class Array; +class Function; + +/// Represents a JS Object. Movable, not copyable. +class JSI_EXPORT Object : public Pointer { + public: + using Pointer::Pointer; + + Object(Object&& other) = default; + Object& operator=(Object&& other) = default; + + /// Creates a new Object instance, like '{}' in JS. + Object(Runtime& runtime) : Object(runtime.createObject()) {} + + static Object createFromHostObject(Runtime& runtime, + std::shared_ptr ho) { + return runtime.createObject(ho); + } + + /// Creates a new Object with the custom prototype + static Object create(Runtime& runtime, const Value& prototype) { + return runtime.createObjectWithPrototype(prototype); + } + + /// \return whether this and \c obj are the same JSObject or not. + static bool strictEquals(Runtime& runtime, const Object& a, const Object& b) { + return runtime.strictEquals(a, b); + } + + /// \return the result of `this instanceOf ctor` in JS. + bool instanceOf(Runtime& rt, const Function& ctor) const { + return rt.instanceOf(*this, ctor); + } + + /// Sets \p prototype as the prototype of the object. The prototype must be + /// either an Object or null. If the prototype was not set successfully, this + /// method will throw. + void setPrototype(Runtime& runtime, const Value& prototype) const { + return runtime.setPrototypeOf(*this, prototype); + } + + /// \return the prototype of the object + inline Value getPrototype(Runtime& runtime) const; + + /// \return the property of the object with the given ascii name. + /// If the name isn't a property on the object, returns the + /// undefined value. + Value getProperty(Runtime& runtime, const char* name) const; + + /// \return the property of the object with the String name. + /// If the name isn't a property on the object, returns the + /// undefined value. + Value getProperty(Runtime& runtime, const String& name) const; + + /// \return the property of the object with the given JS PropNameID + /// name. If the name isn't a property on the object, returns the + /// undefined value. + Value getProperty(Runtime& runtime, const PropNameID& name) const; + + /// \return true if and only if the object has a property with the + /// given ascii name. + bool hasProperty(Runtime& runtime, const char* name) const; + + /// \return true if and only if the object has a property with the + /// given String name. + bool hasProperty(Runtime& runtime, const String& name) const; + + /// \return true if and only if the object has a property with the + /// given PropNameID name. + bool hasProperty(Runtime& runtime, const PropNameID& name) const; + + /// Sets the property value from a Value or anything which can be + /// used to make one: nullptr_t, bool, double, int, const char*, + /// String, or Object. + template + void setProperty(Runtime& runtime, const char* name, T&& value) const; + + /// Sets the property value from a Value or anything which can be + /// used to make one: nullptr_t, bool, double, int, const char*, + /// String, or Object. + template + void setProperty(Runtime& runtime, const String& name, T&& value) const; + + /// Sets the property value from a Value or anything which can be + /// used to make one: nullptr_t, bool, double, int, const char*, + /// String, or Object. + template + void setProperty(Runtime& runtime, const PropNameID& name, T&& value) const; + + /// \return true iff JS \c Array.isArray() would return \c true. If + /// so, then \c getArray() will succeed. + bool isArray(Runtime& runtime) const { return runtime.isArray(*this); } + + /// \return true iff the Object is an ArrayBuffer. If so, then \c + /// getArrayBuffer() will succeed. + bool isArrayBuffer(Runtime& runtime) const { + return runtime.isArrayBuffer(*this); + } + + /// \return true iff the Object is callable. If so, then \c + /// getFunction will succeed. + bool isFunction(Runtime& runtime) const { return runtime.isFunction(*this); } + + /// \return true iff the Object was initialized with \c createFromHostObject + /// and the HostObject passed is of type \c T. If returns \c true then + /// \c getHostObject will succeed. + template + bool isHostObject(Runtime& runtime) const; + + /// \return an Array instance which refers to the same underlying + /// object. If \c isArray() would return false, this will assert. + Array getArray(Runtime& runtime) const&; + + /// \return an Array instance which refers to the same underlying + /// object. If \c isArray() would return false, this will assert. + Array getArray(Runtime& runtime) &&; + + /// \return an Array instance which refers to the same underlying + /// object. If \c isArray() would return false, this will throw + /// JSIException. + Array asArray(Runtime& runtime) const&; + + /// \return an Array instance which refers to the same underlying + /// object. If \c isArray() would return false, this will throw + /// JSIException. + Array asArray(Runtime& runtime) &&; + + /// \return an ArrayBuffer instance which refers to the same underlying + /// object. If \c isArrayBuffer() would return false, this will assert. + ArrayBuffer getArrayBuffer(Runtime& runtime) const&; + + /// \return an ArrayBuffer instance which refers to the same underlying + /// object. If \c isArrayBuffer() would return false, this will assert. + ArrayBuffer getArrayBuffer(Runtime& runtime) &&; + + /// \return a Function instance which refers to the same underlying + /// object. If \c isFunction() would return false, this will assert. + Function getFunction(Runtime& runtime) const&; + + /// \return a Function instance which refers to the same underlying + /// object. If \c isFunction() would return false, this will assert. + Function getFunction(Runtime& runtime) &&; + + /// \return a Function instance which refers to the same underlying + /// object. If \c isFunction() would return false, this will throw + /// JSIException. + Function asFunction(Runtime& runtime) const&; + + /// \return a Function instance which refers to the same underlying + /// object. If \c isFunction() would return false, this will throw + /// JSIException. + Function asFunction(Runtime& runtime) &&; + + /// \return a shared_ptr which refers to the same underlying + /// \c HostObject that was used to create this object. If \c isHostObject + /// is false, this will assert. Note that this does a type check and will + /// assert if the underlying HostObject isn't of type \c T + template + std::shared_ptr getHostObject(Runtime& runtime) const; + + /// \return a shared_ptr which refers to the same underlying + /// \c HostObject that was used to create this object. If \c isHostObject + /// is false, this will throw. + template + std::shared_ptr asHostObject(Runtime& runtime) const; + + /// \return whether this object has native state of type T previously set by + /// \c setNativeState. + template + bool hasNativeState(Runtime& runtime) const; + + /// \return a shared_ptr to the state previously set by \c setNativeState. + /// If \c hasNativeState is false, this will assert. Note that this does a + /// type check and will assert if the native state isn't of type \c T + template + std::shared_ptr getNativeState(Runtime& runtime) const; + + /// Set the internal native state property of this object, overwriting any old + /// value. Creates a new shared_ptr to the object managed by \p state, which + /// will live until the value at this property becomes unreachable. + /// + /// Throws a type error if this object is a proxy or host object. + void setNativeState(Runtime& runtime, + std::shared_ptr state) const; + + /// \return same as \c getProperty(name).asObject(), except with + /// a better exception message. + Object getPropertyAsObject(Runtime& runtime, const char* name) const; + + /// \return similar to \c + /// getProperty(name).getObject().getFunction(), except it will + /// throw JSIException instead of asserting if the property is + /// not an object, or the object is not callable. + Function getPropertyAsFunction(Runtime& runtime, const char* name) const; + + /// \return an Array consisting of all enumerable property names in + /// the object and its prototype chain. All values in the return + /// will be isString(). (This is probably not optimal, but it + /// works. I only need it in one place.) + Array getPropertyNames(Runtime& runtime) const; + + /// Inform the runtime that there is additional memory associated with a given + /// JavaScript object that is not visible to the GC. This can be used if an + /// object is known to retain some native memory, and may be used to guide + /// decisions about when to run garbage collection. + /// This method may be invoked multiple times on an object, and subsequent + /// calls will overwrite any previously set value. Once the object is garbage + /// collected, the associated external memory will be considered freed and may + /// no longer factor into GC decisions. + void setExternalMemoryPressure(Runtime& runtime, size_t amt) const; + + protected: + void setPropertyValue(Runtime& runtime, const String& name, + const Value& value) const { + return runtime.setPropertyValue(*this, name, value); + } + + void setPropertyValue(Runtime& runtime, const PropNameID& name, + const Value& value) const { + return runtime.setPropertyValue(*this, name, value); + } + + friend class Runtime; + friend class Value; +}; + +/// Represents a weak reference to a JS Object. If the only reference +/// to an Object are these, the object is eligible for GC. Method +/// names are inspired by C++ weak_ptr. Movable, not copyable. +class JSI_EXPORT WeakObject : public Pointer { + public: + using Pointer::Pointer; + + WeakObject(WeakObject&& other) = default; + WeakObject& operator=(WeakObject&& other) = default; + + /// Create a WeakObject from an Object. + WeakObject(Runtime& runtime, const Object& o) + : WeakObject(runtime.createWeakObject(o)) {} + + /// \return a Value representing the underlying Object if it is still valid; + /// otherwise returns \c undefined. Note that this method has nothing to do + /// with threads or concurrency. The name is based on std::weak_ptr::lock() + /// which serves a similar purpose. + Value lock(Runtime& runtime) const; + + friend class Runtime; +}; + +/// Represents a JS Object which can be efficiently used as an array +/// with integral indices. +class JSI_EXPORT Array : public Object { + public: + Array(Array&&) = default; + /// Creates a new Array instance, with \c length undefined elements. + Array(Runtime& runtime, size_t length) : Array(runtime.createArray(length)) {} + + Array& operator=(Array&&) = default; + + /// \return the size of the Array, according to its length property. + /// (C++ naming convention) + size_t size(Runtime& runtime) const { return runtime.size(*this); } + + /// \return the size of the Array, according to its length property. + /// (JS naming convention) + size_t length(Runtime& runtime) const { return size(runtime); } + + /// \return the property of the array at index \c i. If there is no + /// such property, returns the undefined value. If \c i is out of + /// range [ 0..\c length ] throws a JSIException. + Value getValueAtIndex(Runtime& runtime, size_t i) const; + + /// Sets the property of the array at index \c i. The argument + /// value behaves as with Object::setProperty(). If \c i is out of + /// range [ 0..\c length ] throws a JSIException. + template + void setValueAtIndex(Runtime& runtime, size_t i, T&& value) const; + + /// There is no current API for changing the size of an array once + /// created. We'll probably need that eventually. + + /// Creates a new Array instance from provided values + template + static Array createWithElements(Runtime&, Args&&... args); + + /// Creates a new Array instance from initializer list. + static Array createWithElements(Runtime& runtime, + std::initializer_list elements); + + private: + friend class Object; + friend class Value; + friend class Runtime; + + void setValueAtIndexImpl(Runtime& runtime, size_t i, + const Value& value) const { + return runtime.setValueAtIndexImpl(*this, i, value); + } + + Array(Runtime::PointerValue* value) : Object(value) {} +}; + +/// Represents a JSArrayBuffer +class JSI_EXPORT ArrayBuffer : public Object { + public: + ArrayBuffer(ArrayBuffer&&) = default; + ArrayBuffer& operator=(ArrayBuffer&&) = default; + + ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) + : ArrayBuffer(runtime.createArrayBuffer(std::move(buffer))) {} + + /// \return the size of the ArrayBuffer storage. This is not affected by + /// overriding the byteLength property. + /// (C++ naming convention) + size_t size(Runtime& runtime) const { return runtime.size(*this); } + + size_t length(Runtime& runtime) const { return runtime.size(*this); } + + uint8_t* data(Runtime& runtime) const { return runtime.data(*this); } + + private: + friend class Object; + friend class Value; + friend class Runtime; + + ArrayBuffer(Runtime::PointerValue* value) : Object(value) {} +}; + +/// Represents a JS Object which is guaranteed to be Callable. +class JSI_EXPORT Function : public Object { + public: + Function(Function&&) = default; + Function& operator=(Function&&) = default; + + /// Create a function which, when invoked, calls C++ code. If the + /// function throws an exception, a JS Error will be created and + /// thrown. + /// \param name the name property for the function. + /// \param paramCount the length property for the function, which + /// may not be the number of arguments the function is passed. + /// \note The std::function's dtor will be called when the GC finalizes this + /// function. As with HostObject, this may be as late as when the Runtime is + /// shut down, and may occur on an arbitrary thread. If the function contains + /// any captured values, you are responsible for ensuring that their + /// destructors are safe to call on any thread. + static Function createFromHostFunction(Runtime& runtime, + const jsi::PropNameID& name, + unsigned int paramCount, + jsi::HostFunctionType func); + + /// Calls the function with \c count \c args. The \c this value of the JS + /// function will not be set by the C++ caller, similar to calling + /// Function.prototype.apply(undefined, args) in JS. + /// \b Note: as with Function.prototype.apply, \c this may not always be + /// \c undefined in the function itself. If the function is non-strict, + /// \c this will be set to the global object. + Value call(Runtime& runtime, const Value* args, size_t count) const; + + /// Calls the function with a \c std::initializer_list of Value + /// arguments. The \c this value of the JS function will not be set by the + /// C++ caller, similar to calling Function.prototype.apply(undefined, args) + /// in JS. + /// \b Note: as with Function.prototype.apply, \c this may not always be + /// \c undefined in the function itself. If the function is non-strict, + /// \c this will be set to the global object. + Value call(Runtime& runtime, std::initializer_list args) const; + + /// Calls the function with any number of arguments similarly to + /// Object::setProperty(). The \c this value of the JS function will not be + /// set by the C++ caller, similar to calling + /// Function.prototype.call(undefined, ...args) in JS. + /// \b Note: as with Function.prototype.call, \c this may not always be + /// \c undefined in the function itself. If the function is non-strict, + /// \c this will be set to the global object. + template + Value call(Runtime& runtime, Args&&... args) const; + + /// Calls the function with \c count \c args and \c jsThis value passed + /// as the \c this value. + Value callWithThis(Runtime& Runtime, const Object& jsThis, const Value* args, + size_t count) const; + + /// Calls the function with a \c std::initializer_list of Value + /// arguments and \c jsThis passed as the \c this value. + Value callWithThis(Runtime& runtime, const Object& jsThis, + std::initializer_list args) const; + + /// Calls the function with any number of arguments similarly to + /// Object::setProperty(), and with \c jsThis passed as the \c this value. + template + Value callWithThis(Runtime& runtime, const Object& jsThis, + Args&&... args) const; + + /// Calls the function as a constructor with \c count \c args. Equivalent + /// to calling `new Func` where `Func` is the js function reqresented by + /// this. + Value callAsConstructor(Runtime& runtime, const Value* args, + size_t count) const; + + /// Same as above `callAsConstructor`, except use an initializer_list to + /// supply the arguments. + Value callAsConstructor(Runtime& runtime, + std::initializer_list args) const; + + /// Same as above `callAsConstructor`, but automatically converts/wraps + /// any argument with a jsi Value. + template + Value callAsConstructor(Runtime& runtime, Args&&... args) const; + + /// Returns whether this was created with Function::createFromHostFunction. + /// If true then you can use getHostFunction to get the underlying + /// HostFunctionType. + bool isHostFunction(Runtime& runtime) const { + return runtime.isHostFunction(*this); + } + + /// Returns the underlying HostFunctionType iff isHostFunction returns true + /// and asserts otherwise. You can use this to use std::function<>::target + /// to get the object that was passed to create the HostFunctionType. + /// + /// Note: The reference returned is borrowed from the JS object underlying + /// \c this, and thus only lasts as long as the object underlying + /// \c this does. + HostFunctionType& getHostFunction(Runtime& runtime) const { + assert(isHostFunction(runtime)); + return runtime.getHostFunction(*this); + } + + private: + friend class Object; + friend class Value; + friend class Runtime; + + Function(Runtime::PointerValue* value) : Object(value) {} +}; + +/// Represents any JS Value (undefined, null, boolean, number, symbol, +/// string, or object). Movable, or explicitly copyable (has no copy +/// ctor). +class JSI_EXPORT Value { + public: + /// Default ctor creates an \c undefined JS value. + Value() noexcept : Value(UndefinedKind) {} + + /// Creates a \c null JS value. + /* implicit */ Value(std::nullptr_t) : kind_(NullKind) {} + + /// Creates a boolean JS value. + /* implicit */ Value(bool b) : Value(BooleanKind) { data_.boolean = b; } + + /// Creates a number JS value. + /* implicit */ Value(double d) : Value(NumberKind) { data_.number = d; } + + /// Creates a number JS value. + /* implicit */ Value(int i) : Value(NumberKind) { data_.number = i; } + + /// Moves a Symbol, String, or Object rvalue into a new JS value. + template ::value || + std::is_base_of::value || + std::is_base_of::value || + std::is_base_of::value>> + /* implicit */ Value(T&& other) : Value(kindOf(other)) { + new (&data_.pointer) T(std::move(other)); + } + + /// Value("foo") will treat foo as a bool. This makes doing that a + /// compile error. + template + Value(const char*) { + static_assert(!std::is_same::value, + "Value cannot be constructed directly from const char*"); + } + + Value(Value&& other) noexcept; + + /// Copies a Symbol lvalue into a new JS value. + Value(Runtime& runtime, const Symbol& sym) : Value(SymbolKind) { + new (&data_.pointer) Symbol(runtime.cloneSymbol(sym.ptr_)); + } + + /// Copies a BigInt lvalue into a new JS value. + Value(Runtime& runtime, const BigInt& bigint) : Value(BigIntKind) { + new (&data_.pointer) BigInt(runtime.cloneBigInt(bigint.ptr_)); + } + + /// Copies a String lvalue into a new JS value. + Value(Runtime& runtime, const String& str) : Value(StringKind) { + new (&data_.pointer) String(runtime.cloneString(str.ptr_)); + } + + /// Copies a Object lvalue into a new JS value. + Value(Runtime& runtime, const Object& obj) : Value(ObjectKind) { + new (&data_.pointer) Object(runtime.cloneObject(obj.ptr_)); + } + + /// Creates a JS value from another Value lvalue. + Value(Runtime& runtime, const Value& value); + + /// Value(rt, "foo") will treat foo as a bool. This makes doing + /// that a compile error. + template + Value(Runtime&, const char*) { + static_assert(!std::is_same::value, + "Value cannot be constructed directly from const char*"); + } + + ~Value(); + // \return the undefined \c Value. + static Value undefined() { return Value(); } + + // \return the null \c Value. + static Value null() { return Value(nullptr); } + + // \return a \c Value created from a utf8-encoded JSON string. + static Value createFromJsonUtf8(Runtime& runtime, const uint8_t* json, + size_t length) { + return runtime.createValueFromJsonUtf8(json, length); + } + + /// \return according to the Strict Equality Comparison algorithm, see: + /// https://262.ecma-international.org/11.0/#sec-strict-equality-comparison + static bool strictEquals(Runtime& runtime, const Value& a, const Value& b); + + Value& operator=(Value&& other) noexcept { + this->~Value(); + new (this) Value(std::move(other)); + return *this; + } + + bool isUndefined() const { return kind_ == UndefinedKind; } + + bool isNull() const { return kind_ == NullKind; } + + bool isBool() const { return kind_ == BooleanKind; } + + bool isNumber() const { return kind_ == NumberKind; } + + bool isString() const { return kind_ == StringKind; } + + bool isBigInt() const { return kind_ == BigIntKind; } + + bool isSymbol() const { return kind_ == SymbolKind; } + + bool isObject() const { return kind_ == ObjectKind; } + + /// \return the boolean value, or asserts if not a boolean. + bool getBool() const { + assert(isBool()); + return data_.boolean; + } + + /// \return the boolean value, or throws JSIException if not a + /// boolean. + bool asBool() const; + + /// \return the number value, or asserts if not a number. + double getNumber() const { + assert(isNumber()); + return data_.number; + } + + /// \return the number value, or throws JSIException if not a + /// number. + double asNumber() const; + + /// \return the Symbol value, or asserts if not a symbol. + Symbol getSymbol(Runtime& runtime) const& { + assert(isSymbol()); + return Symbol(runtime.cloneSymbol(data_.pointer.ptr_)); + } + + /// \return the Symbol value, or asserts if not a symbol. + /// Can be used on rvalue references to avoid cloning more symbols. + Symbol getSymbol(Runtime&) && { + assert(isSymbol()); + auto ptr = data_.pointer.ptr_; + data_.pointer.ptr_ = nullptr; + return static_cast(ptr); + } + + /// \return the Symbol value, or throws JSIException if not a + /// symbol + Symbol asSymbol(Runtime& runtime) const&; + Symbol asSymbol(Runtime& runtime) &&; + + /// \return the BigInt value, or asserts if not a bigint. + BigInt getBigInt(Runtime& runtime) const& { + assert(isBigInt()); + return BigInt(runtime.cloneBigInt(data_.pointer.ptr_)); + } + + /// \return the BigInt value, or asserts if not a bigint. + /// Can be used on rvalue references to avoid cloning more bigints. + BigInt getBigInt(Runtime&) && { + assert(isBigInt()); + auto ptr = data_.pointer.ptr_; + data_.pointer.ptr_ = nullptr; + return static_cast(ptr); + } + + /// \return the BigInt value, or throws JSIException if not a + /// bigint + BigInt asBigInt(Runtime& runtime) const&; + BigInt asBigInt(Runtime& runtime) &&; + + /// \return the String value, or asserts if not a string. + String getString(Runtime& runtime) const& { + assert(isString()); + return String(runtime.cloneString(data_.pointer.ptr_)); + } + + /// \return the String value, or asserts if not a string. + /// Can be used on rvalue references to avoid cloning more strings. + String getString(Runtime&) && { + assert(isString()); + auto ptr = data_.pointer.ptr_; + data_.pointer.ptr_ = nullptr; + return static_cast(ptr); + } + + /// \return the String value, or throws JSIException if not a + /// string. + String asString(Runtime& runtime) const&; + String asString(Runtime& runtime) &&; + + /// \return the Object value, or asserts if not an object. + Object getObject(Runtime& runtime) const& { + assert(isObject()); + return Object(runtime.cloneObject(data_.pointer.ptr_)); + } + + /// \return the Object value, or asserts if not an object. + /// Can be used on rvalue references to avoid cloning more objects. + Object getObject(Runtime&) && { + assert(isObject()); + auto ptr = data_.pointer.ptr_; + data_.pointer.ptr_ = nullptr; + return static_cast(ptr); + } + + /// \return the Object value, or throws JSIException if not an + /// object. + Object asObject(Runtime& runtime) const&; + Object asObject(Runtime& runtime) &&; + + // \return a String like JS .toString() would do. + String toString(Runtime& runtime) const; + + private: + friend class Runtime; + + enum ValueKind { + UndefinedKind, + NullKind, + BooleanKind, + NumberKind, + SymbolKind, + BigIntKind, + StringKind, + ObjectKind, + PointerKind = SymbolKind, + }; + + union Data { + // Value's ctor and dtor will manage the lifecycle of the contained Data. + Data() { + static_assert(sizeof(Data) == sizeof(uint64_t), + "Value data should fit in a 64-bit register"); + } + ~Data() {} + + // scalars + bool boolean; + double number; + // pointers + Pointer pointer; // Symbol, String, Object, Array, Function + }; + + Value(ValueKind kind) : kind_(kind) {} + + constexpr static ValueKind kindOf(const Symbol&) { return SymbolKind; } + constexpr static ValueKind kindOf(const BigInt&) { return BigIntKind; } + constexpr static ValueKind kindOf(const String&) { return StringKind; } + constexpr static ValueKind kindOf(const Object&) { return ObjectKind; } + + ValueKind kind_; + Data data_; + + // In the future: Value becomes NaN-boxed. See T40538354. +}; + +/// Not movable and not copyable RAII marker advising the underlying +/// JavaScript VM to track resources allocated since creation until +/// destruction so that they can be recycled eagerly when the Scope +/// goes out of scope instead of floating in the air until the next +/// garbage collection or any other delayed release occurs. +/// +/// This API should be treated only as advice, implementations can +/// choose to ignore the fact that Scopes are created or destroyed. +/// +/// This class is an exception to the rule allowing destructors to be +/// called without proper synchronization (see Runtime documentation). +/// The whole point of this class is to enable all sorts of clean ups +/// when the destructor is called and this proper synchronization is +/// required at that time. +/// +/// Instances of this class are intended to be created as automatic stack +/// variables in which case destructor calls don't require any additional +/// locking, provided that the lock (if any) is managed with RAII helpers. +class JSI_EXPORT Scope { + public: + explicit Scope(Runtime& rt) : rt_(rt), prv_(rt.pushScope()) {} + ~Scope() { rt_.popScope(prv_); } + + Scope(const Scope&) = delete; + Scope(Scope&&) = delete; + + Scope& operator=(const Scope&) = delete; + Scope& operator=(Scope&&) = delete; + + template + static auto callInNewScope(Runtime& rt, F f) -> decltype(f()) { + Scope s(rt); + return f(); + } + + private: + Runtime& rt_; + Runtime::ScopeState* prv_; +}; + +/// Base class for jsi exceptions +class JSI_EXPORT JSIException : public std::exception { + protected: + JSIException() {} + JSIException(std::string what) : what_(std::move(what)) {} + + public: + JSIException(const JSIException&) = default; + + virtual const char* what() const noexcept override { return what_.c_str(); } + + virtual ~JSIException() override; + + protected: + std::string what_; +}; + +/// This exception will be thrown by API functions on errors not related to +/// JavaScript execution. +class JSI_EXPORT JSINativeException : public JSIException { + public: + JSINativeException(std::string what) : JSIException(std::move(what)) {} + + JSINativeException(const JSINativeException&) = default; + + virtual ~JSINativeException(); +}; + +/// This exception will be thrown by API functions whenever a JS +/// operation causes an exception as described by the spec, or as +/// otherwise described. +class JSI_EXPORT JSError : public JSIException { + public: + /// Creates a JSError referring to provided \c value + JSError(Runtime& r, Value&& value); + + /// Creates a JSError referring to new \c Error instance capturing current + /// JavaScript stack. The error message property is set to given \c message. + JSError(Runtime& rt, std::string message); + + /// Creates a JSError referring to new \c Error instance capturing current + /// JavaScript stack. The error message property is set to given \c message. + JSError(Runtime& rt, const char* message) + : JSError(rt, std::string(message)) {} + + /// Creates a JSError referring to a JavaScript Object having message and + /// stack properties set to provided values. + JSError(Runtime& rt, std::string message, std::string stack); + + /// Creates a JSError referring to provided value and what string + /// set to provided message. This argument order is a bit weird, + /// but necessary to avoid ambiguity with the above. + JSError(std::string what, Runtime& rt, Value&& value); + + /// Creates a JSError referring to the provided value, message and stack. This + /// constructor does not take a Runtime parameter, and therefore cannot result + /// in recursively invoking the JSError constructor. + JSError(Value&& value, std::string message, std::string stack); + + JSError(const JSError&) = default; + + virtual ~JSError(); + + const std::string& getStack() const { return stack_; } + + const std::string& getMessage() const { return message_; } + + const jsi::Value& value() const { + assert(value_); + return *value_; + } + + private: + // This initializes the value_ member and does some other + // validation, so it must be called by every branch through the + // constructors. + void setValue(Runtime& rt, Value&& value); + + // This needs to be on the heap, because throw requires the object + // be copyable, and Value is not. + std::shared_ptr value_; + std::string message_; + std::string stack_; +}; + +} // namespace jsi +} // namespace facebook + +#include "jsi/jsi-inl.h" \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/jsi/jsilib-posix.cpp b/test-app/runtime/src/main/cpp/jsi/jsilib-posix.cpp new file mode 100644 index 000000000..42fa1a09f --- /dev/null +++ b/test-app/runtime/src/main/cpp/jsi/jsilib-posix.cpp @@ -0,0 +1,103 @@ +/* + * 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. + */ + +#ifndef _WINDOWS + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "jsi/jsilib.h" + +namespace facebook { +namespace jsi { + +namespace { + +constexpr size_t kErrorBufferSize = 512; + +__attribute__((format(printf, 1, 2))) void throwFormattedError(const char* fmt, + ...) { + char logBuffer[kErrorBufferSize]; + + va_list va_args; + va_start(va_args, fmt); + int result = vsnprintf(logBuffer, sizeof(logBuffer), fmt, va_args); + va_end(va_args); + + if (result < 0) { + throw JSINativeException(std::string("Failed to format error message: ") + + fmt); + } + + throw JSINativeException(logBuffer); +} + +class ScopedFile { + public: + ScopedFile(const std::string& path) + : path_(path), fd_(::open(path.c_str(), O_RDONLY)) { + if (fd_ == -1) { + throwFormattedError("Could not open %s: %s", path.c_str(), + strerror(errno)); + } + } + + ~ScopedFile() { ::close(fd_); } + + size_t size() { + struct stat fileInfo; + if (::fstat(fd_, &fileInfo) == -1) { + throwFormattedError("Could not stat %s: %s", path_.c_str(), + strerror(errno)); + } + return fileInfo.st_size; + } + + uint8_t* mmap(size_t size) { + void* result = ::mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd_, 0); + if (result == MAP_FAILED) { + throwFormattedError("Could not mmap %s: %s", path_.c_str(), + strerror(errno)); + } + return reinterpret_cast(result); + } + + const std::string& path_; + const int fd_; +}; + +} // namespace + +FileBuffer::FileBuffer(const std::string& path) { + ScopedFile file(path); + size_ = file.size(); + data_ = file.mmap(size_); +} + +FileBuffer::~FileBuffer() { + if (::munmap(data_, size_)) { + // terminate the program with pending exception + try { + throwFormattedError("Could not unmap memory (%p, %zu bytes): %s", data_, + size_, strerror(errno)); + } catch (...) { + std::terminate(); + } + } +} + +} // namespace jsi +} // namespace facebook + +#endif // !defined(_WINDOWS) \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/jsi/jsilib.h b/test-app/runtime/src/main/cpp/jsi/jsilib.h new file mode 100644 index 000000000..0dad2ee87 --- /dev/null +++ b/test-app/runtime/src/main/cpp/jsi/jsilib.h @@ -0,0 +1,48 @@ +/* + * 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. + */ + +#pragma once + +#include "jsi/jsi.h" + +namespace facebook { +namespace jsi { + +class FileBuffer : public Buffer { + public: + FileBuffer(const std::string& path); + ~FileBuffer() override; + + size_t size() const override { return size_; } + + const uint8_t* data() const override { return data_; } + + private: + size_t size_; + uint8_t* data_; +}; + +// A trivial implementation of PreparedJavaScript that simply stores the source +// buffer and URL. +class SourceJavaScriptPreparation final : public jsi::PreparedJavaScript, + public jsi::Buffer { + std::shared_ptr buf_; + std::string sourceURL_; + + public: + SourceJavaScriptPreparation(std::shared_ptr buf, + std::string sourceURL) + : buf_(std::move(buf)), sourceURL_(std::move(sourceURL)) {} + + const std::string& sourceURL() const { return sourceURL_; } + + size_t size() const override { return buf_->size(); } + const uint8_t* data() const override { return buf_->data(); } +}; + +} // namespace jsi +} // namespace facebook \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/jsi/threadsafe.h b/test-app/runtime/src/main/cpp/jsi/threadsafe.h new file mode 100644 index 000000000..768edb4ae --- /dev/null +++ b/test-app/runtime/src/main/cpp/jsi/threadsafe.h @@ -0,0 +1,70 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include "jsi/decorator.h" +#include "jsi/jsi.h" + +namespace facebook { +namespace jsi { + +class ThreadSafeRuntime : public Runtime { + public: + virtual void lock() const = 0; + virtual void unlock() const = 0; + virtual Runtime& getUnsafeRuntime() = 0; +}; + +namespace detail { + +template +struct WithLock { + L lock; + WithLock(R& r) : lock(r) {} + void before() { lock.lock(); } + void after() { lock.unlock(); } +}; + +// The actual implementation of a given ThreadSafeRuntime. It's parameterized +// by: +// +// - R: The actual Runtime type that this wraps +// - L: A lock type that has three members: +// - L(R& r) // ctor +// - void lock() +// - void unlock() +template +class ThreadSafeRuntimeImpl final + : public WithRuntimeDecorator, R, ThreadSafeRuntime> { + public: + template + ThreadSafeRuntimeImpl(Args&&... args) + : WithRuntimeDecorator, R, ThreadSafeRuntime>(unsafe_, + lock_), + unsafe_(std::forward(args)...), + lock_(unsafe_) {} + + R& getUnsafeRuntime() override { + return WithRuntimeDecorator, R, ThreadSafeRuntime>::plain(); + } + + void lock() const override { lock_.before(); } + + void unlock() const override { lock_.after(); } + + private: + R unsafe_; + mutable WithLock lock_; +}; + +} // namespace detail + +} // namespace jsi +} // namespace facebook \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/v8runtime/HostProxy.cpp b/test-app/runtime/src/main/cpp/v8runtime/HostProxy.cpp new file mode 100644 index 000000000..7331fe4e7 --- /dev/null +++ b/test-app/runtime/src/main/cpp/v8runtime/HostProxy.cpp @@ -0,0 +1,261 @@ +/* + * Copyright (c) Kudo Chien. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "HostProxy.h" + +#include "JSIV8ValueConverter.h" + +namespace jsi = facebook::jsi; + +namespace rnv8 { + +HostObjectProxy::HostObjectProxy(V8Runtime& runtime, v8::Isolate* isolate, + std::shared_ptr hostObject) + : runtime_(runtime), isolate_(isolate), hostObject_(hostObject) {} + +void HostObjectProxy::BindFinalizer(const v8::Local& object) { + v8::HandleScope scopedHandle(isolate_); + weakHandle_.Reset(isolate_, object); + weakHandle_.SetWeak(this, Finalizer, v8::WeakCallbackType::kParameter); +} + +std::shared_ptr HostObjectProxy::GetHostObject() { + return hostObject_; +} + +// static +v8::Intercepted HostObjectProxy::Getter( + v8::Local property, + const v8::PropertyCallbackInfo& info) { + v8::HandleScope scopedHandle(info.GetIsolate()); + v8::Local data = + v8::Local::Cast(info.Holder()->GetInternalField(1)); + HostObjectProxy* hostObjectProxy = reinterpret_cast( + data->Value(v8::kExternalPointerTypeTagDefault)); + + assert(hostObjectProxy); + + auto& runtime = hostObjectProxy->runtime_; + jsi::PropNameID sym = JSIV8ValueConverter::ToJSIPropNameID(runtime, property); + jsi::Value ret; + try { + ret = hostObjectProxy->hostObject_->get(runtime, sym); + } catch (const jsi::JSError& error) { + info.GetIsolate()->ThrowException( + JSIV8ValueConverter::ToV8Value(runtime, error.value())); + return v8::Intercepted::kYes; + } catch (const std::exception& ex) { + auto excValue = + runtime.global() + .getPropertyAsFunction(runtime, "Error") + .call(runtime, + std::string("Exception in HostObject::get(property:") + + JSIV8ValueConverter::ToSTLString(info.GetIsolate(), + property) + + std::string("): ") + ex.what()); + info.GetIsolate()->ThrowException( + JSIV8ValueConverter::ToV8Value(runtime, excValue)); + return v8::Intercepted::kYes; + } catch (...) { + auto excValue = + runtime.global() + .getPropertyAsFunction(runtime, "Error") + .call(runtime, + std::string("Exception in HostObject::get(property:") + + JSIV8ValueConverter::ToSTLString(info.GetIsolate(), + property) + + std::string("): ")); + info.GetIsolate()->ThrowException( + JSIV8ValueConverter::ToV8Value(runtime, excValue)); + return v8::Intercepted::kYes; + } + info.GetReturnValue().Set(JSIV8ValueConverter::ToV8Value(runtime, ret)); + return v8::Intercepted::kYes; +} + +// static +v8::Intercepted HostObjectProxy::Setter( + v8::Local property, v8::Local value, + const v8::PropertyCallbackInfo& info) { + v8::HandleScope scopedHandle(info.GetIsolate()); + v8::Local data = + v8::Local::Cast(info.Holder()->GetInternalField(1)); + HostObjectProxy* hostObjectProxy = reinterpret_cast( + data->Value(v8::kExternalPointerTypeTagDefault)); + + assert(hostObjectProxy); + auto& runtime = hostObjectProxy->runtime_; + jsi::PropNameID sym = JSIV8ValueConverter::ToJSIPropNameID(runtime, property); + try { + hostObjectProxy->hostObject_->set( + runtime, sym, + JSIV8ValueConverter::ToJSIValue(info.GetIsolate(), value)); + } catch (const jsi::JSError& error) { + info.GetIsolate()->ThrowException( + JSIV8ValueConverter::ToV8Value(runtime, error.value())); + return v8::Intercepted::kYes; + } catch (const std::exception& ex) { + auto excValue = + runtime.global() + .getPropertyAsFunction(runtime, "Error") + .call(runtime, + std::string("Exception in HostObject::set(property:") + + JSIV8ValueConverter::ToSTLString(info.GetIsolate(), + property) + + std::string("): ") + ex.what()); + info.GetIsolate()->ThrowException( + JSIV8ValueConverter::ToV8Value(runtime, excValue)); + return v8::Intercepted::kYes; + } catch (...) { + auto excValue = + runtime.global() + .getPropertyAsFunction(runtime, "Error") + .call(runtime, + std::string("Exception in HostObject::set(property:") + + JSIV8ValueConverter::ToSTLString(info.GetIsolate(), + property) + + std::string("): ")); + info.GetIsolate()->ThrowException( + JSIV8ValueConverter::ToV8Value(runtime, excValue)); + return v8::Intercepted::kYes; + } + // Not intercepted: the native write is done, but V8 must still perform + // the ordinary store, which is what the old void-returning callback did + // by falling through without setting a return value. + return v8::Intercepted::kNo; +} + +// static +void HostObjectProxy::Enumerator( + const v8::PropertyCallbackInfo& info) { + v8::HandleScope scopedHandle(info.GetIsolate()); + v8::Local data = + v8::Local::Cast(info.Holder()->GetInternalField(1)); + HostObjectProxy* hostObjectProxy = reinterpret_cast( + data->Value(v8::kExternalPointerTypeTagDefault)); + + assert(hostObjectProxy); + + auto& runtime = hostObjectProxy->runtime_; + + auto names = hostObjectProxy->hostObject_->getPropertyNames(runtime); + + v8::Local result = + v8::Array::New(info.GetIsolate(), static_cast(names.size())); + v8::Local context = info.GetIsolate()->GetCurrentContext(); + + for (uint32_t i = 0; i < result->Length(); ++i) { + v8::Local value = + JSIV8ValueConverter::ToV8String(runtime, names[i]); + if (!result->Set(context, i, value).FromJust()) { + std::abort(); + }; + } + + info.GetReturnValue().Set(result); +} + +// static +void HostObjectProxy::Finalizer( + const v8::WeakCallbackInfo& data) { + auto* pThis = data.GetParameter(); + if (pThis->hostObject_.use_count() == 1) { + pThis->hostObject_.reset(); + } + pThis->weakHandle_.Reset(); + delete pThis; +} + +HostFunctionProxy::HostFunctionProxy(V8Runtime& runtime, v8::Isolate* isolate, + jsi::HostFunctionType&& hostFunction) + : runtime_(runtime), + isolate_(isolate), + hostFunction_(std::move(hostFunction)) {} + +void HostFunctionProxy::BindFinalizer(const v8::Local& object) { + v8::HandleScope scopedHandle(isolate_); + weakHandle_.Reset(isolate_, object); + weakHandle_.SetWeak(this, Finalizer, v8::WeakCallbackType::kParameter); +} + +jsi::HostFunctionType& HostFunctionProxy::GetHostFunction() { + return hostFunction_; +} + +// static +void HostFunctionProxy::Finalizer( + const v8::WeakCallbackInfo& data) { + auto* pThis = data.GetParameter(); + pThis->weakHandle_.Reset(); + delete pThis; +} + +// static +void HostFunctionProxy::FunctionCallback( + const v8::FunctionCallbackInfo& info) { + v8::HandleScope scopedHandle(info.GetIsolate()); + v8::Local data = v8::Local::Cast(info.Data()); + auto* hostFunctionProxy = reinterpret_cast( + data->Value(v8::kExternalPointerTypeTagDefault)); + + auto& runtime = hostFunctionProxy->runtime_; + + int argumentCount = info.Length(); + 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] = + JSIV8ValueConverter::ToJSIValue(info.GetIsolate(), info[(int)i]); + } + args = heapArgs.get(); + } else { + for (size_t i = 0; i < argumentCount; i++) { + stackArgs[i] = + JSIV8ValueConverter::ToJSIValue(info.GetIsolate(), info[(int)i]); + } + args = stackArgs; + } + + v8::Local result; + jsi::Value thisVal( + JSIV8ValueConverter::ToJSIValue(info.GetIsolate(), info.This())); + try { + result = JSIV8ValueConverter::ToV8Value( + runtime, hostFunctionProxy->hostFunction_(runtime, thisVal, args, + argumentCount)); + } catch (const jsi::JSError& error) { + info.GetIsolate()->ThrowException( + JSIV8ValueConverter::ToV8Value(runtime, error.value())); + return; + } catch (const std::exception& ex) { + std::string exceptionString("Exception in HostFunction: "); + exceptionString += ex.what(); + auto excValue = runtime.global() + .getPropertyAsFunction(runtime, "Error") + .call(runtime, exceptionString); + + info.GetIsolate()->ThrowException( + JSIV8ValueConverter::ToV8Value(runtime, excValue)); + return; + } catch (...) { + std::string exceptionString("Exception in HostFunction: "); + auto excValue = runtime.global() + .getPropertyAsFunction(runtime, "Error") + .call(runtime, exceptionString); + + info.GetIsolate()->ThrowException( + JSIV8ValueConverter::ToV8Value(runtime, excValue)); + return; + } + info.GetReturnValue().Set(result); +} + +} // namespace rnv8 \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/v8runtime/HostProxy.h b/test-app/runtime/src/main/cpp/v8runtime/HostProxy.h new file mode 100644 index 000000000..ec9b4b406 --- /dev/null +++ b/test-app/runtime/src/main/cpp/v8runtime/HostProxy.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) Kudo Chien. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include "V8Runtime.h" +#include "jsi/jsi.h" +#include "v8.h" + +namespace rnv8 { + +class HostObjectProxy { + public: + HostObjectProxy(V8Runtime& runtime, v8::Isolate* isolate, + std::shared_ptr hostObject); + + void BindFinalizer(const v8::Local& object); + + std::shared_ptr GetHostObject(); + + public: + static v8::Intercepted Getter( + v8::Local property, + const v8::PropertyCallbackInfo& info); + + static v8::Intercepted Setter( + v8::Local property, v8::Local value, + const v8::PropertyCallbackInfo& info); + + static void Enumerator(const v8::PropertyCallbackInfo& info); + + static void Finalizer(const v8::WeakCallbackInfo& data); + + private: + V8Runtime& runtime_; + v8::Isolate* isolate_; + std::shared_ptr hostObject_; + v8::Global weakHandle_; +}; + +class HostFunctionProxy { + public: + HostFunctionProxy(V8Runtime& runtime, v8::Isolate* isolate, + facebook::jsi::HostFunctionType&& hostFunction); + + void BindFinalizer(const v8::Local& object); + + facebook::jsi::HostFunctionType& GetHostFunction(); + + public: + static void Finalizer(const v8::WeakCallbackInfo& data); + + static void FunctionCallback(const v8::FunctionCallbackInfo& info); + + private: + V8Runtime& runtime_; + v8::Isolate* isolate_; + facebook::jsi::HostFunctionType hostFunction_; + v8::Global weakHandle_; +}; + +} // namespace rnv8 \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/v8runtime/JSIRuntime.cpp b/test-app/runtime/src/main/cpp/v8runtime/JSIRuntime.cpp new file mode 100644 index 000000000..44ee23b53 --- /dev/null +++ b/test-app/runtime/src/main/cpp/v8runtime/JSIRuntime.cpp @@ -0,0 +1,39 @@ +#include "JSIRuntime.h" + +#include "Runtime.h" +#include "RuntimeState.h" +#include "V8Runtime.h" + +namespace { + +struct JSIRuntimeState { + std::shared_ptr runtime = + std::make_shared(); +}; + +} // namespace + +namespace tns { + +std::shared_ptr GetJSIRuntime() { + Runtime* currentRuntime = Runtime::GetCurrentRuntime(); + if (currentRuntime == nullptr) { + return nullptr; + } + v8::Isolate* isolate = currentRuntime->GetIsolate(); + if (isolate == nullptr) { + return nullptr; + } + auto* state = RuntimeState::For(isolate); + if (state == nullptr) { + return nullptr; + } + return state->runtime; +} + +} // namespace tns + +extern "C" facebook::jsi::Runtime* NativeScriptJSIRuntime(void) { + auto rt = tns::GetJSIRuntime(); + return rt ? rt.get() : nullptr; +} diff --git a/test-app/runtime/src/main/cpp/v8runtime/JSIRuntime.h b/test-app/runtime/src/main/cpp/v8runtime/JSIRuntime.h new file mode 100644 index 000000000..5f0ac8fe4 --- /dev/null +++ b/test-app/runtime/src/main/cpp/v8runtime/JSIRuntime.h @@ -0,0 +1,28 @@ +#ifndef JSIRUNTIME_H_ +#define JSIRUNTIME_H_ + +#include + +#include "jsi/jsi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// The JSI Runtime of the NativeScript runtime on the calling thread, or nullptr +// when this thread has no active runtime (or its runtime has torn down). Each +// runtime — the main one and every Worker — owns a separate JSI Runtime bound +// to its V8 isolate. +__attribute__((visibility("default"))) facebook::jsi::Runtime* +NativeScriptJSIRuntime(void); + +#ifdef __cplusplus +} + +namespace tns { +__attribute__((visibility("default"))) std::shared_ptr +GetJSIRuntime(); +} // namespace tns +#endif + +#endif /* JSIRUNTIME_H_ */ diff --git a/test-app/runtime/src/main/cpp/v8runtime/JSIV8ValueConverter.cpp b/test-app/runtime/src/main/cpp/v8runtime/JSIV8ValueConverter.cpp new file mode 100644 index 000000000..e7bc9655c --- /dev/null +++ b/test-app/runtime/src/main/cpp/v8runtime/JSIV8ValueConverter.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (c) Kudo Chien. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "JSIV8ValueConverter.h" + +#include "V8PointerValue.h" + +namespace jsi = facebook::jsi; + +namespace rnv8 { + +// static +jsi::Value JSIV8ValueConverter::ToJSIValue(v8::Isolate* isolate, + const v8::Local& value) { + v8::HandleScope scopedHandle(isolate); + if (value->IsUndefined()) { + return jsi::Value::undefined(); + } + if (value->IsNull()) { + return jsi::Value::null(); + } + if (value->IsBoolean()) { + return jsi::Value(value->BooleanValue(isolate)); + } + if (value->IsNumber()) { + return jsi::Value( + value->NumberValue(isolate->GetCurrentContext()).ToChecked()); + } + if (value->IsString()) { + return V8Runtime::make(new V8PointerValue(isolate, value)); + } + if (value->IsSymbol()) { + return V8Runtime::make(new V8PointerValue(isolate, value)); + } + if (value->IsObject()) { + return V8Runtime::make(new V8PointerValue(isolate, value)); + } + + return jsi::Value::undefined(); +} + +// static +v8::Local JSIV8ValueConverter::ToV8Value(const V8Runtime& runtime, + const jsi::Value& value) { + v8::EscapableHandleScope scopedHandle(runtime.isolate_); + + if (value.isUndefined()) { + return scopedHandle.Escape(v8::Undefined(runtime.isolate_)); + } else if (value.isNull()) { + return scopedHandle.Escape(v8::Null(runtime.isolate_)); + } else if (value.isBool()) { + return scopedHandle.Escape( + v8::Boolean::New(runtime.isolate_, std::move(value.getBool()))); + } else if (value.isNumber()) { + return scopedHandle.Escape( + v8::Number::New(runtime.isolate_, std::move(value.getNumber()))); + } else if (value.isString()) { + return scopedHandle.Escape(ToV8String( + runtime, std::move(value.getString(const_cast(runtime))))); + } else if (value.isObject()) { + return scopedHandle.Escape(ToV8Object( + runtime, std::move(value.getObject(const_cast(runtime))))); + } else { + // What are you? + std::abort(); + } +} + +// static +v8::Local JSIV8ValueConverter::ToV8String( + const V8Runtime& runtime, const jsi::String& string) { + v8::EscapableHandleScope scopedHandle(runtime.isolate_); + const V8PointerValue* v8PointerValue = + static_cast(runtime.getPointerValue(string)); + assert(v8PointerValue->Get(runtime.isolate_)->IsString()); + return scopedHandle.Escape( + v8::Local::Cast(v8PointerValue->Get(runtime.isolate_))); +} + +// static +v8::Local JSIV8ValueConverter::ToV8String( + const V8Runtime& runtime, const jsi::PropNameID& propName) { + v8::EscapableHandleScope scopedHandle(runtime.isolate_); + const V8PointerValue* v8PointerValue = + static_cast(runtime.getPointerValue(propName)); + assert(v8PointerValue->Get(runtime.isolate_)->IsString()); + return scopedHandle.Escape( + v8::Local::Cast(v8PointerValue->Get(runtime.isolate_))); +} + +// static +v8::MaybeLocal JSIV8ValueConverter::ToV8String( + const V8Runtime& runtime, + const std::shared_ptr& buffer) { + v8::EscapableHandleScope scopedHandle(runtime.isolate_); + v8::MaybeLocal ret = v8::String::NewFromUtf8( + runtime.isolate_, reinterpret_cast(buffer->data()), + v8::NewStringType::kNormal, (int)buffer->size()); + return scopedHandle.EscapeMaybe(ret); +} + +// static +v8::Local JSIV8ValueConverter::ToV8Symbol( + const V8Runtime& runtime, const jsi::Symbol& symbol) { + v8::EscapableHandleScope scopedHandle(runtime.isolate_); + const V8PointerValue* v8PointerValue = + static_cast(runtime.getPointerValue(symbol)); + assert(v8PointerValue->Get(runtime.isolate_)->IsSymbol()); + return scopedHandle.Escape( + v8::Local::Cast(v8PointerValue->Get(runtime.isolate_))); +} + +// static +v8::Local JSIV8ValueConverter::ToV8Object( + const V8Runtime& runtime, const jsi::Object& object) { + v8::EscapableHandleScope scopedHandle(runtime.isolate_); + const V8PointerValue* v8PointerValue = + static_cast(runtime.getPointerValue(object)); + assert(v8PointerValue->Get(runtime.isolate_)->IsObject()); + return scopedHandle.Escape( + v8::Local::Cast(v8PointerValue->Get(runtime.isolate_))); +} + +// static +v8::Local JSIV8ValueConverter::ToV8Array(const V8Runtime& runtime, + const jsi::Array& array) { + v8::EscapableHandleScope scopedHandle(runtime.isolate_); + const V8PointerValue* v8PointerValue = + static_cast(runtime.getPointerValue(array)); + assert(v8PointerValue->Get(runtime.isolate_)->IsArray()); + return scopedHandle.Escape( + v8::Local::Cast(v8PointerValue->Get(runtime.isolate_))); +} + +// static +v8::Local JSIV8ValueConverter::ToV8Function( + const V8Runtime& runtime, const jsi::Function& function) { + v8::EscapableHandleScope scopedHandle(runtime.isolate_); + const V8PointerValue* v8PointerValue = + static_cast(runtime.getPointerValue(function)); + assert(v8PointerValue->Get(runtime.isolate_)->IsFunction()); + return scopedHandle.Escape( + v8::Local::Cast(v8PointerValue->Get(runtime.isolate_))); +} + +// static +jsi::PropNameID JSIV8ValueConverter::ToJSIPropNameID( + const V8Runtime& runtime, const v8::Local& property) { + v8::HandleScope scopedHandle(runtime.isolate_); + return runtime.make( + new V8PointerValue(runtime.isolate_, property)); +} + +// static +std::string JSIV8ValueConverter::ToSTLString( + const v8::String::Utf8Value& string) { + if (*string) { + return std::string(*string, string.length()); + } + return {}; +} + +// static +std::string JSIV8ValueConverter::ToSTLString( + v8::Isolate* isolate, const v8::Local& string) { + v8::HandleScope scopedHandle(isolate); + assert(string->IsString()); + v8::String::Utf8Value utf8(isolate, string); + return ToSTLString(utf8); +} + +} // namespace rnv8 \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/v8runtime/JSIV8ValueConverter.h b/test-app/runtime/src/main/cpp/v8runtime/JSIV8ValueConverter.h new file mode 100644 index 000000000..0816a5fdf --- /dev/null +++ b/test-app/runtime/src/main/cpp/v8runtime/JSIV8ValueConverter.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) Kudo Chien. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include "V8Runtime.h" +#include "jsi/jsi.h" +#include "v8.h" + +namespace rnv8 { + +class JSIV8ValueConverter { + private: + JSIV8ValueConverter() = delete; + ~JSIV8ValueConverter() = delete; + JSIV8ValueConverter(JSIV8ValueConverter&&) = delete; + + public: + static facebook::jsi::Value ToJSIValue(v8::Isolate* isolate, + const v8::Local& value); + + static v8::Local ToV8Value(const V8Runtime& runtime, + const facebook::jsi::Value& value); + + static v8::Local ToV8String(const V8Runtime& runtime, + const facebook::jsi::String& string); + + static v8::Local ToV8String( + const V8Runtime& runtime, const facebook::jsi::PropNameID& propName); + + static v8::MaybeLocal ToV8String( + const V8Runtime& runtime, + const std::shared_ptr& buffer); + + static v8::Local ToV8Symbol(const V8Runtime& runtime, + const facebook::jsi::Symbol& symbol); + + static v8::Local ToV8Object(const V8Runtime& runtime, + const facebook::jsi::Object& object); + + static v8::Local ToV8Array(const V8Runtime& runtime, + const facebook::jsi::Array& array); + + static v8::Local ToV8Function( + const V8Runtime& runtime, const facebook::jsi::Function& function); + + static facebook::jsi::PropNameID ToJSIPropNameID( + const V8Runtime& runtime, const v8::Local& property); + + static std::string ToSTLString(const v8::String::Utf8Value& string); + + static std::string ToSTLString(v8::Isolate* isolate, + const v8::Local& string); +}; + +} // namespace rnv8 \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/v8runtime/LICENSE.md b/test-app/runtime/src/main/cpp/v8runtime/LICENSE.md new file mode 100644 index 000000000..87a7c60dd --- /dev/null +++ b/test-app/runtime/src/main/cpp/v8runtime/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Kudo Chien + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/test-app/runtime/src/main/cpp/v8runtime/V8PointerValue.cpp b/test-app/runtime/src/main/cpp/v8runtime/V8PointerValue.cpp new file mode 100644 index 000000000..d8aaa5847 --- /dev/null +++ b/test-app/runtime/src/main/cpp/v8runtime/V8PointerValue.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) Kudo Chien. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "V8PointerValue.h" + +namespace rnv8 { + +V8PointerValue::V8PointerValue(v8::Isolate* isolate, + const v8::Local& value) + : isolate_(isolate), value_(isolate, value) {} + +V8PointerValue::V8PointerValue(v8::Isolate* isolate, + v8::Global&& value) + : isolate_(isolate), value_(std::move(value)) {} + +V8PointerValue::~V8PointerValue() {} + +v8::Local V8PointerValue::Get(v8::Isolate* isolate) const { + v8::EscapableHandleScope scopedHandle(isolate); + return scopedHandle.Escape(value_.Get(isolate)); +} + +void V8PointerValue::Reset(v8::Isolate* isolate, v8::Local value) { + v8::HandleScope scopedHandle(isolate); + value_.Reset(isolate, value); +} + +// static +V8PointerValue* V8PointerValue::createFromOneByte(v8::Isolate* isolate, + const char* str, + size_t length) { + v8::HandleScope scopedHandle(isolate); + v8::Local v8String; + if (!v8::String::NewFromOneByte( + isolate, reinterpret_cast(str), + v8::NewStringType::kNormal, static_cast(length)) + .ToLocal(&v8String)) { + return nullptr; + } + return new V8PointerValue(isolate, v8String); +} + +// static +V8PointerValue* V8PointerValue::createFromUtf8(v8::Isolate* isolate, + const uint8_t* str, + size_t length) { + v8::HandleScope scopedHandle(isolate); + v8::Local v8String; + if (!v8::String::NewFromUtf8(isolate, reinterpret_cast(str), + v8::NewStringType::kNormal, + static_cast(length)) + .ToLocal(&v8String)) { + return nullptr; + } + return new V8PointerValue(isolate, v8String); +} + +void V8PointerValue::invalidate() { + { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + value_.Reset(); + } + delete this; +} + +} // namespace rnv8 \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/v8runtime/V8PointerValue.h b/test-app/runtime/src/main/cpp/v8runtime/V8PointerValue.h new file mode 100644 index 000000000..0ed63ff08 --- /dev/null +++ b/test-app/runtime/src/main/cpp/v8runtime/V8PointerValue.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) Kudo Chien. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include "V8Runtime.h" +#include "jsi/jsi.h" +#include "v8.h" + +namespace rnv8 { + +class V8PointerValue final : public V8Runtime::PointerValue { + public: + V8PointerValue(v8::Isolate* isolate, const v8::Local& value); + + // Passing Global value directly + V8PointerValue(v8::Isolate* isolate, v8::Global&& value); + + ~V8PointerValue() override; + + v8::Local Get(v8::Isolate* isolate) const; + + void Reset(v8::Isolate* isolate, v8::Local value); + + public: + static V8PointerValue* createFromOneByte(v8::Isolate* isolate, + const char* str, size_t length); + + static V8PointerValue* createFromUtf8(v8::Isolate* isolate, + const uint8_t* str, size_t length); + + private: + void invalidate() override; + + private: + friend class JSIV8ValueConverter; + friend class V8Runtime; + v8::Isolate* isolate_; + v8::Global value_; +}; + +} // namespace rnv8 \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/v8runtime/V8Runtime.cpp b/test-app/runtime/src/main/cpp/v8runtime/V8Runtime.cpp new file mode 100644 index 000000000..b7dafeb69 --- /dev/null +++ b/test-app/runtime/src/main/cpp/v8runtime/V8Runtime.cpp @@ -0,0 +1,1814 @@ +/* + * Copyright (c) Kudo Chien. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "V8Runtime.h" + +#include "Runtime.h" +#include "v8-external-memory-accounter.h" +#include "v8.h" + +// #include +#include +#include +#include + +#include "HostProxy.h" +#include "JSIV8ValueConverter.h" +// #include "V8Inspector.h" +#include "V8PointerValue.h" +#include "jsi/jsilib.h" + +namespace jsi = facebook::jsi; + +namespace rnv8 { + +namespace { + +const char kHostFunctionProxyProp[] = "__hostFunctionProxy"; + +struct NativeStateHolder { + std::shared_ptr state; + v8::Global weakObject; + + NativeStateHolder(v8::Isolate* isolate, v8::Local object, + std::shared_ptr&& nativeState) + : state(std::move(nativeState)), weakObject(isolate, object) { + weakObject.SetWeak(this, &NativeStateHolder::Finalizer, + v8::WeakCallbackType::kParameter); + } + + static void Finalizer(const v8::WeakCallbackInfo& data) { + auto* holder = data.GetParameter(); + holder->weakObject.Reset(); + delete holder; + } +}; + +struct ExternalMemoryTracker { + v8::Isolate* isolate; + v8::Global weakObject; + v8::ExternalMemoryAccounter accounter; + size_t amount = 0; + + ExternalMemoryTracker(v8::Isolate* iso, v8::Local obj, + size_t initialAmount) + : isolate(iso), weakObject(iso, obj), amount(initialAmount) { + if (amount > 0) { + accounter.Increase(isolate, amount); + } + weakObject.SetWeak(this, &ExternalMemoryTracker::Finalizer, + v8::WeakCallbackType::kParameter); + } + + void Update(size_t newAmount) { + if (newAmount == amount) { + return; + } + int64_t delta = + static_cast(newAmount) - static_cast(amount); + accounter.Update(isolate, delta); + amount = newAmount; + } + + void RebindObject(v8::Local newObj) { + weakObject.Reset(isolate, newObj); + weakObject.SetWeak(this, &ExternalMemoryTracker::Finalizer, + v8::WeakCallbackType::kParameter); + } + + static void Finalizer( + const v8::WeakCallbackInfo& data) { + auto* tracker = data.GetParameter(); + if (tracker->amount > 0) { + tracker->accounter.Decrease(tracker->isolate, tracker->amount); + tracker->amount = 0; + } + tracker->weakObject.Reset(); + delete tracker; + } +}; + +} // namespace + +// static +std::unique_ptr V8Runtime::s_platform = nullptr; +std::mutex s_platform_mutex; // protects s_platform + +V8Runtime::V8Runtime() { + isolate_ = tns::Runtime::GetCurrentRuntime()->GetIsolate(); + + // V8Runtime::V8Runtime( + // std::unique_ptr config, + // std::shared_ptr jsQueue) + // : config_(std::move(config)) { + // { + // const std::lock_guard lock(s_platform_mutex); + // if (!s_platform) { + // s_platform = v8::platform::NewDefaultPlatform(); + // v8::V8::InitializeICU(); + // v8::V8::InitializePlatform(s_platform.get()); + // #if TARGET_OS_IOS + // v8::V8::SetFlagsFromString("--nolazy --nofreeze_flags_after_init"); + // #else + // v8::V8::SetFlagsFromString("--nolazy"); + // #endif + // v8::V8::Initialize(); + // } + // } + + // if (config_->snapshotBlob) { + // snapshotBlob_ = std::make_unique(); + // snapshotBlob_->data = config_->snapshotBlob->c_str(); + // snapshotBlob_->raw_size = + // static_cast(config_->snapshotBlob->size()); + // createParams.snapshot_blob = snapshotBlob_.get(); + // } + + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + context_.Reset(isolate_, isolate_->GetCurrentContext()); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + arrayBufferAllocator_.reset( + v8::ArrayBuffer::Allocator::NewDefaultAllocator()); + // jsQueue_ = jsQueue; + // if (config_->enableInspector) { + // inspectorClient_ = std::make_shared( + // jsQueue_, + // context_.Get(isolate_), + // config_->appName, + // config_->deviceName); + // inspectorClient_->ConnectToReactFrontend(); + // } +} + +// V8Runtime::V8Runtime(const V8Runtime* v8Runtime, +// std::unique_ptr config) +// : config_(std::move(config)) { +// arrayBufferAllocator_.reset( +// v8::ArrayBuffer::Allocator::NewDefaultAllocator()); +// v8::Isolate::CreateParams createParams; +// createParams.array_buffer_allocator = arrayBufferAllocator_.get(); +// if (v8Runtime->config_->snapshotBlob) { +// snapshotBlob_ = std::make_unique(); +// snapshotBlob_->data = v8Runtime->config_->snapshotBlob->c_str(); +// snapshotBlob_->raw_size = +// static_cast(v8Runtime->config_->snapshotBlob->size()); +// createParams.snapshot_blob = snapshotBlob_.get(); +// } +// config_->codecacheMode = V8RuntimeConfig::CodecacheMode::kNone; + +// isolate_ = v8::Isolate::New(createParams); +// #if defined(__ANDROID__) +// if (!v8Runtime->config_->timezoneId.empty()) { +// isolate_->DateTimeConfigurationChangeNotification( +// v8::Isolate::TimeZoneDetection::kCustom, +// v8Runtime->config_->timezoneId.c_str()); +// } +// #endif +// v8::Locker locker(isolate_); +// v8::Isolate::Scope scopedIsolate(isolate_); +// v8::HandleScope scopedHandle(isolate_); +// context_.Reset(isolate_, CreateGlobalContext(isolate_)); +// v8::Context::Scope scopedContext(context_.Get(isolate_)); +// // jsQueue_ = v8Runtime->jsQueue_; + +// // if (config_->enableInspector) { +// // inspectorClient_ = std::make_shared( +// // jsQueue_, +// // context_.Get(isolate_), +// // config_->appName, +// // config_->deviceName); +// // inspectorClient_->ConnectToReactFrontend(); +// // } + +// #if 0 // Experimental shared global context +// isSharedRuntime_ = true; +// isolate_ = v8Runtime->isolate_; +// // jsQueue_ = v8Runtime->jsQueue_; + +// v8::Locker locker(isolate_); +// v8::Isolate::Scope scopedIsolate(isolate_); +// v8::HandleScope scopedHandle(isolate_); +// context_.Reset(isolate_, CreateGlobalContext(isolate_)); + +// auto localContext = context_.Get(isolate_); +// localContext->SetSecurityToken( +// v8Runtime->context_.Get(isolate_)->GetSecurityToken()); + +// bool inheritProtoResult = +// localContext->Global() +// ->GetPrototype() +// .As() +// ->SetPrototype( +// localContext, +// v8Runtime->context_.Get(isolate_)->Global()->GetPrototype()) +// .FromJust(); +// if (!inheritProtoResult) { +// LOG(ERROR) << "Unable to inherit prototype from parent shared runtime."; +// } + +// // if (config_->enableInspector) { +// // inspectorClient_ = std::make_shared( +// // jsQueue_, +// // context_.Get(isolate_), +// // config_->appName, +// // config_->deviceName); +// // inspectorClient_->ConnectToReactFrontend(); +// // } +// #endif +// } + +V8Runtime::~V8Runtime() { + { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + if (inspectorClient_) { + inspectorClient_.reset(); + } + + context_.Reset(); + } + if (!isSharedRuntime_) { + isolate_->Dispose(); + } + // v8::V8::Dispose(); + // v8::V8::DisposePlatform(); +} + +void V8Runtime::OnMainLoopIdle() { + // v8::Locker locker(isolate_); + // v8::Isolate::Scope scopedIsolate(isolate_); + // v8::HandleScope scopedHandle(isolate_); + // v8::Context::Scope scopedContext(context_.Get(isolate_)); + + // while (v8::platform::PumpMessageLoop( + // s_platform.get(), isolate_, + // v8::platform::MessageLoopBehavior::kDoNotWait)) { + // continue; + // } +} + +// v8::Local V8Runtime::CreateGlobalContext(v8::Isolate* isolate) { +// v8::HandleScope scopedHandle(isolate); +// v8::Local global = v8::ObjectTemplate::New(isolate_); +// global->Set( +// v8::String::NewFromUtf8(isolate, "_v8runtime", +// v8::NewStringType::kNormal) +// .ToLocalChecked(), +// v8::FunctionTemplate::New(isolate, V8Runtime::GetRuntimeInfo)); +// return v8::Context::New(isolate_, nullptr, global); +// } + +// jsi::Value V8Runtime::ExecuteScript(v8::Isolate* isolate, +// const v8::Local& script, +// const std::string& sourceURL) { +// v8::HandleScope scopedHandle(isolate); +// v8::TryCatch tryCatch(isolate); + +// v8::MaybeLocal sourceURLValue = v8::String::NewFromUtf8( +// isolate, sourceURL.c_str(), v8::NewStringType::kNormal, +// static_cast(sourceURL.length())); +// v8::ScriptOrigin origin(sourceURLValue.ToLocalChecked()); + +// v8::Local context(isolate->GetCurrentContext()); + +// auto codecache = LoadCodeCacheIfNeeded(sourceURL); +// v8::ScriptCompiler::CachedData* cachedData = codecache.release(); + +// std::unique_ptr source = +// UseFakeSourceIfNeeded(origin, cachedData); +// if (!source) { +// source = std::make_unique(script, origin, +// cachedData); +// } + +// v8::Local compiledScript; +// if (!v8::ScriptCompiler::Compile(context, source.release(), +// cachedData +// ? +// v8::ScriptCompiler::kConsumeCodeCache +// : +// v8::ScriptCompiler::kNoCompileOptions) +// .ToLocal(&compiledScript)) { +// ReportException(isolate, &tryCatch); +// return {}; +// } + +// if (cachedData && cachedData->rejected) { +// LOG(INFO) << "[rnv8] cache miss: " << sourceURL; +// } +// SaveCodeCacheIfNeeded(compiledScript, sourceURL, cachedData); + +// v8::Local result; +// if (!compiledScript->Run(context).ToLocal(&result)) { +// assert(tryCatch.HasCaught()); +// ReportException(isolate, &tryCatch); +// return {}; +// } + +// return JSIV8ValueConverter::ToJSIValue(isolate, result); +// } + +void V8Runtime::ReportException(v8::Isolate* isolate, + v8::TryCatch* tryCatch) const { + v8::HandleScope scopedHandle(isolate); + std::string exception = + JSIV8ValueConverter::ToSTLString(isolate, tryCatch->Exception()); + v8::Local message = tryCatch->Message(); + if (message.IsEmpty()) { + // V8 didn't provide any extra information about this error; just + // print the exception. + throw jsi::JSError(const_cast(*this), exception); + return; + } else { + std::ostringstream ss; + v8::Local context(isolate->GetCurrentContext()); + + // Print (filename):(line number): (message). + ss << JSIV8ValueConverter::ToSTLString( + isolate, message->GetScriptOrigin().ResourceName()) + << ":" << message->GetLineNumber(context).FromJust() << ": " << exception + << std::endl; + + // Print line of source code. + ss << JSIV8ValueConverter::ToSTLString( + isolate, message->GetSourceLine(context).ToLocalChecked()) + << std::endl; + + // Print wavy underline (GetUnderline is deprecated). + int start = message->GetStartColumn(context).FromJust(); + for (int i = 0; i < start; i++) { + ss << " "; + } + int end = message->GetEndColumn(context).FromJust(); + for (int i = start; i < end; i++) { + ss << "^"; + } + ss << std::endl; + + v8::Local stackTraceString; + if (tryCatch->StackTrace(context).ToLocal(&stackTraceString) && + stackTraceString->IsString() && + v8::Local::Cast(stackTraceString)->Length() > 0) { + v8::String::Utf8Value stackTrace(isolate, stackTraceString); + std::string stlStack = JSIV8ValueConverter::ToSTLString(stackTrace); + // Reverted: do not remap stack here + ss << stlStack << std::endl; + } + + throw jsi::JSError(const_cast(*this), ss.str()); + return; + } +} + +// std::unique_ptr +// V8Runtime::LoadCodeCacheIfNeeded(const std::string& sourceURL) { +// // caching is for main runtime only +// if (isSharedRuntime_) { +// return nullptr; +// } + +// if (config_->codecacheMode == V8RuntimeConfig::CodecacheMode::kNone) { +// return nullptr; +// } + +// std::filesystem::path codecachePath(config_->codecacheDir); +// codecachePath /= std::filesystem::path(sourceURL).filename(); +// auto* file = std::fopen(codecachePath.string().c_str(), "rb"); +// if (!file) { +// LOG(INFO) << "Cannot load codecache file: " << codecachePath.string(); +// return nullptr; +// } +// std::fseek(file, 0, SEEK_END); +// size_t size = std::ftell(file); +// uint8_t* buffer = new uint8_t[size]; +// std::rewind(file); + +// std::fread(buffer, size, 1, file); +// std::fclose(file); + +// return std::make_unique( +// buffer, static_cast(size), +// v8::ScriptCompiler::CachedData::BufferPolicy::BufferOwned); +// } + +// bool V8Runtime::SaveCodeCacheIfNeeded( +// const v8::Local& script, const std::string& sourceURL, +// v8::ScriptCompiler::CachedData* cachedData) { +// // caching is for main runtime only +// if (isSharedRuntime_) { +// return false; +// } + +// if (cachedData && !cachedData->rejected) { +// return false; +// } + +// if (config_->codecacheMode == V8RuntimeConfig::CodecacheMode::kNone) { +// return false; +// } + +// v8::HandleScope scopedHandle(isolate_); + +// v8::Local unboundScript = script->GetUnboundScript(); +// std::unique_ptr newCachedData; +// newCachedData.reset(v8::ScriptCompiler::CreateCodeCache(unboundScript)); +// if (!newCachedData) { +// return false; +// } + +// std::filesystem::path codecachePath(config_->codecacheDir); +// codecachePath /= std::filesystem::path(sourceURL).filename(); +// if (auto* file = std::fopen(codecachePath.string().c_str(), "wb")) { +// std::fwrite(newCachedData->data, 1, newCachedData->length, file); +// std::fclose(file); +// return true; +// } else { +// LOG(ERROR) << "Cannot save codecache file: " << codecachePath.string(); +// return false; +// } +// } + +// std::unique_ptr V8Runtime::UseFakeSourceIfNeeded( +// const v8::ScriptOrigin& origin, +// v8::ScriptCompiler::CachedData* cachedData) { +// // caching is for main runtime only +// if (isSharedRuntime_) { +// return nullptr; +// } + +// if (!cachedData) { +// return nullptr; +// } + +// if (config_->codecacheMode == V8RuntimeConfig::CodecacheMode::kStubBundle) +// { +// uint32_t payloadSize = +// (cachedData->data[8] << 0) | (cachedData->data[9] << 8) | +// (cachedData->data[10] << 16) | (cachedData->data[11] << 24); +// std::string stubScriptString(payloadSize, ' '); +// v8::Local stubScript = +// v8::String::NewFromUtf8(isolate_, stubScriptString.c_str()) +// .ToLocalChecked(); +// return std::make_unique(stubScript, origin, +// cachedData); +// } + +// return nullptr; +// } + +V8Runtime::InternalFieldType V8Runtime::GetInternalFieldType( + v8::Local object) const { + if (object->InternalFieldCount() != 2) { + return V8Runtime::InternalFieldType::kInvalid; + } + v8::Local typeValue = object->GetInternalField(0).As(); + if (!typeValue->IsUint32()) { + return V8Runtime::InternalFieldType::kInvalid; + } + return static_cast( + v8::Local::Cast(typeValue)->Value()); +} + +// static +v8::Platform* V8Runtime::GetPlatform() { return s_platform.get(); } + +// +// jsi::Runtime implementations +// +jsi::Value V8Runtime::evaluateJavaScript( + const std::shared_ptr& buffer, + const std::string& sourceURL) { + return {}; +} + +std::shared_ptr V8Runtime::prepareJavaScript( + const std::shared_ptr& buffer, std::string sourceURL) { + return nullptr; +} + +jsi::Value V8Runtime::evaluatePreparedJavaScript( + const std::shared_ptr& js) { + // The second arg is a `std::string` sourceURL; newer Clang (Xcode 26.x) + // enforces `-Wnonnull` on its non-null `const char*` constructor, so we + // can no longer pass `nullptr`. Empty string is the documented "unknown + // source URL" value and matches the existing `prepareJavaScript` no-op + // (which already returns nullptr above). + return evaluateJavaScript(nullptr, ""); +} + +void V8Runtime::queueMicrotask(const jsi::Function& callback) { + // TODO: add this when we revisit new architecture support +} + +bool V8Runtime::drainMicrotasks(int maxMicrotasksHint) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + while (v8::platform::PumpMessageLoop( + s_platform.get(), isolate_, + v8::platform::MessageLoopBehavior::kDoNotWait)) { + continue; + } + isolate_->PerformMicrotaskCheckpoint(); + return true; +} + +jsi::Object V8Runtime::global() { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + return make( + new V8PointerValue(isolate_, context_.Get(isolate_)->Global())); +} + +std::string V8Runtime::description() { + std::ostringstream ss; + ss << ""; + return ss.str(); +} + +bool V8Runtime::isInspectable() { return false; } + +// These clone methods are shallow clone +jsi::Runtime::PointerValue* V8Runtime::cloneSymbol( + const Runtime::PointerValue* pv) { + if (!pv) { + return nullptr; + } + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + const V8PointerValue* v8PointerValue = static_cast(pv); + assert(v8PointerValue->Get(isolate_)->IsSymbol()); + return new V8PointerValue(isolate_, v8PointerValue->Get(isolate_)); +} + +jsi::Runtime::PointerValue* V8Runtime::cloneBigInt( + const Runtime::PointerValue* pv) { + if (!pv) { + return nullptr; + } + + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + const V8PointerValue* v8PointerValue = static_cast(pv); + assert(v8PointerValue->Get(isolate_)->IsBigInt()); + return new V8PointerValue(isolate_, v8PointerValue->Get(isolate_)); +} + +jsi::Runtime::PointerValue* V8Runtime::cloneString( + const Runtime::PointerValue* pv) { + if (!pv) { + return nullptr; + } + + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + const V8PointerValue* v8PointerValue = static_cast(pv); + assert(v8PointerValue->Get(isolate_)->IsString()); + return new V8PointerValue(isolate_, v8PointerValue->Get(isolate_)); +} + +jsi::Runtime::PointerValue* V8Runtime::cloneObject( + const Runtime::PointerValue* pv) { + if (!pv) { + return nullptr; + } + + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + const V8PointerValue* v8PointerValue = static_cast(pv); + assert(v8PointerValue->Get(isolate_)->IsObject()); + return new V8PointerValue(isolate_, v8PointerValue->Get(isolate_)); +} + +jsi::Runtime::PointerValue* V8Runtime::clonePropNameID( + const Runtime::PointerValue* pv) { + return cloneString(pv); +} + +jsi::PropNameID V8Runtime::createPropNameIDFromAscii(const char* str, + size_t length) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + V8PointerValue* value = + V8PointerValue::createFromOneByte(isolate_, str, length); + if (!value) { + throw jsi::JSError(*this, "createFromOneByte() - string creation failed."); + } + + return make(value); +} + +jsi::PropNameID V8Runtime::createPropNameIDFromUtf8(const uint8_t* utf8, + size_t length) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + V8PointerValue* value = + V8PointerValue::createFromUtf8(isolate_, utf8, length); + if (!value) { + throw jsi::JSError(*this, "createFromUtf8() - string creation failed."); + } + + return make(value); +} + +jsi::PropNameID V8Runtime::createPropNameIDFromString(const jsi::String& str) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + const V8PointerValue* v8PointerValue = + static_cast(getPointerValue(str)); + assert(v8PointerValue->Get(isolate_)->IsString()); + + v8::String::Utf8Value utf8(isolate_, v8PointerValue->Get(isolate_)); + return createPropNameIDFromUtf8(reinterpret_cast(*utf8), + utf8.length()); +} + +jsi::PropNameID V8Runtime::createPropNameIDFromSymbol( + const facebook::jsi::Symbol& sym) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + assert(static_cast(getPointerValue(sym)) + ->Get(isolate_) + ->IsSymbol()); + + return make(const_cast(getPointerValue(sym))); +} + +std::string V8Runtime::utf8(const jsi::PropNameID& sym) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + const V8PointerValue* v8PointerValue = + static_cast(getPointerValue(sym)); + v8::String::Utf8Value utf8(isolate_, v8PointerValue->Get(isolate_)); + return JSIV8ValueConverter::ToSTLString(utf8); +} + +bool V8Runtime::compare(const jsi::PropNameID& a, const jsi::PropNameID& b) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + const V8PointerValue* v8PointerValueA = + static_cast(getPointerValue(a)); + const V8PointerValue* v8PointerValueB = + static_cast(getPointerValue(b)); + + v8::Local v8StringA = + v8::Local::Cast(v8PointerValueA->Get(isolate_)); + v8::Local v8StringB = + v8::Local::Cast(v8PointerValueB->Get(isolate_)); + return v8StringA->StringEquals(v8StringB); +} + +std::string V8Runtime::symbolToString(const jsi::Symbol& symbol) { + return jsi::Value(*this, symbol).toString(*this).utf8(*this); +} + +jsi::BigInt V8Runtime::createBigIntFromInt64(int64_t value) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8BigInt = v8::BigInt::New(isolate_, value); + return make(new V8PointerValue(isolate_, v8BigInt)); +} + +jsi::BigInt V8Runtime::createBigIntFromUint64(uint64_t value) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8BigInt = v8::BigInt::NewFromUnsigned(isolate_, value); + return make(new V8PointerValue(isolate_, v8BigInt)); +} + +bool V8Runtime::bigintIsInt64(const jsi::BigInt& value) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + const V8PointerValue* v8PointerValue = + static_cast(getPointerValue(value)); + assert(v8PointerValue->Get(isolate_)->IsBigInt()); + v8::Local v8BigInt = + v8::Local::Cast(v8PointerValue->Get(isolate_)); + bool lossless = false; + v8BigInt->Int64Value(&lossless); + return lossless == true; +} + +bool V8Runtime::bigintIsUint64(const jsi::BigInt& value) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + const V8PointerValue* v8PointerValue = + static_cast(getPointerValue(value)); + assert(v8PointerValue->Get(isolate_)->IsBigInt()); + v8::Local v8BigInt = + v8::Local::Cast(v8PointerValue->Get(isolate_)); + bool lossless = false; + v8BigInt->Uint64Value(&lossless); + return lossless == true; +} + +uint64_t V8Runtime::truncate(const jsi::BigInt& value) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + const V8PointerValue* v8PointerValue = + static_cast(getPointerValue(value)); + assert(v8PointerValue->Get(isolate_)->IsBigInt()); + v8::Local v8BigInt = + v8::Local::Cast(v8PointerValue->Get(isolate_)); + return v8BigInt->Uint64Value(); +} + +jsi::String V8Runtime::bigintToString(const jsi::BigInt& value, int radix) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + const V8PointerValue* v8PointerValue = + static_cast(getPointerValue(value)); + assert(v8PointerValue->Get(isolate_)->IsBigInt()); + v8::Local v8Value = + v8::Local::Cast(v8PointerValue->Get(isolate_)); + + // V8 does not expose `toString(radix)` in its API, so we have to use + // `BigInt.prototype.toString.call(value, radix)` instead. + v8::Local global = context_.Get(isolate_)->Global(); + v8::Local bigintClass = v8::Local::Cast( + global + ->Get(isolate_->GetCurrentContext(), + v8::String::NewFromUtf8Literal(isolate_, "BigInt")) + .ToLocalChecked()); + v8::Local bigintProto = v8::Local::Cast( + bigintClass + ->Get(isolate_->GetCurrentContext(), + v8::String::NewFromUtf8Literal(isolate_, "prototype")) + .ToLocalChecked()); + v8::Local bigintToStringFunction = + v8::Local::Cast( + bigintProto + ->Get(isolate_->GetCurrentContext(), + v8::String::NewFromUtf8Literal(isolate_, "toString")) + .ToLocalChecked()); + v8::Local args[] = {v8::Integer::New(isolate_, radix)}; + v8::Local result = + bigintToStringFunction + ->Call(isolate_->GetCurrentContext(), v8Value, 1, args) + .ToLocalChecked(); + assert(result->IsString()); + return V8Runtime::make(new V8PointerValue(isolate_, result)); +} + +jsi::String V8Runtime::createStringFromAscii(const char* str, size_t length) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + V8PointerValue* value = + V8PointerValue::createFromOneByte(isolate_, str, length); + if (!value) { + throw jsi::JSError(*this, "createFromOneByte() - string creation failed."); + } + + return make(value); +} + +jsi::String V8Runtime::createStringFromUtf8(const uint8_t* str, size_t length) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + V8PointerValue* value = V8PointerValue::createFromUtf8(isolate_, str, length); + if (!value) { + throw jsi::JSError(*this, "createFromUtf8() - string creation failed."); + } + + return make(value); +} + +std::string V8Runtime::utf8(const jsi::String& str) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + const V8PointerValue* v8PointerValue = + static_cast(getPointerValue(str)); + assert(v8PointerValue->Get(isolate_)->IsString()); + + v8::String::Utf8Value utf8(isolate_, v8PointerValue->Get(isolate_)); + return JSIV8ValueConverter::ToSTLString(utf8); +} + +jsi::Object V8Runtime::createObject() { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local object = v8::Object::New(isolate_); + return make(new V8PointerValue(isolate_, object)); +} + +jsi::Object V8Runtime::createObject( + std::shared_ptr hostObject) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + HostObjectProxy* hostObjectProxy = + new HostObjectProxy(*this, isolate_, hostObject); + v8::Local v8Object; + + v8::Local hostObjectTemplate = + v8::ObjectTemplate::New(isolate_); + hostObjectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration( + HostObjectProxy::Getter, HostObjectProxy::Setter, nullptr, nullptr, + HostObjectProxy::Enumerator)); + hostObjectTemplate->SetInternalFieldCount(2); + + if (!hostObjectTemplate->NewInstance(isolate_->GetCurrentContext()) + .ToLocal(&v8Object)) { + delete hostObjectProxy; + throw jsi::JSError(*this, "Unable to create HostObject"); + } + + v8::Local wrappedHostObjectProxy = v8::External::New( + isolate_, hostObjectProxy, v8::kExternalPointerTypeTagDefault); + v8Object->SetInternalField(0, v8::Integer::NewFromUnsigned( + isolate_, InternalFieldType::kHostObject)); + v8Object->SetInternalField(1, wrappedHostObjectProxy); + hostObjectProxy->BindFinalizer(v8Object); + + return make(new V8PointerValue(isolate_, v8Object)); +} + +std::shared_ptr V8Runtime::getHostObject( + const jsi::Object& object) { + assert(isHostObject(object)); + + // We are guarenteed at this point to have isHostObject(obj) == true + // so the internal data should be HostObjectMetadata + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + v8::Local internalField = + v8::Local::Cast(v8Object->GetInternalField(1)); + HostObjectProxy* hostObjectProxy = reinterpret_cast( + internalField->Value(v8::kExternalPointerTypeTagDefault)); + assert(hostObjectProxy); + return hostObjectProxy->GetHostObject(); +} + +jsi::HostFunctionType& V8Runtime::getHostFunction( + const jsi::Function& function) { + assert(isHostFunction(function)); + + // We know that isHostFunction(function) is true here, so its safe to proceed + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + const V8PointerValue* v8PointerValue = + static_cast(getPointerValue(function)); + assert(v8PointerValue->Get(isolate_)->IsFunction()); + v8::Local v8Function = + v8::Local::Cast(v8PointerValue->Get(isolate_)); + + v8::Local prop = + v8::String::NewFromUtf8(isolate_, kHostFunctionProxyProp, + v8::NewStringType::kNormal) + .ToLocalChecked(); + v8::Local wrappedHostFunctionProxy = + v8::Local::Cast( + v8Function->Get(isolate_->GetCurrentContext(), prop) + .ToLocalChecked()); + HostFunctionProxy* hostFunctionProxy = reinterpret_cast( + wrappedHostFunctionProxy->Value(v8::kExternalPointerTypeTagDefault)); + assert(hostFunctionProxy); + return hostFunctionProxy->GetHostFunction(); +} + +jsi::Object V8Runtime::createObjectWithPrototype(const jsi::Value& prototype) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Local context = context_.Get(isolate_); + v8::Context::Scope scopedContext(context); + + v8::Local objectTemplate = + v8::ObjectTemplate::New(isolate_); + objectTemplate->SetInternalFieldCount(2); + v8::Local v8Object; + if (!objectTemplate->NewInstance(context).ToLocal(&v8Object)) { + throw jsi::JSError(*this, + "Unable to create new Object with custom prototype"); + } + v8Object->SetInternalField( + 0, v8::Integer::NewFromUnsigned(isolate_, InternalFieldType::kInvalid)); + v8Object->SetAlignedPointerInInternalField( + 1, nullptr, v8::kEmbedderDataTypeTagDefault); + + v8::Local v8Prototype = + JSIV8ValueConverter::ToV8Value(*this, prototype); + if (!v8Object->SetPrototype(context, v8Prototype).FromMaybe(false)) { + throw jsi::JSError(*this, + "Unable to set prototype on newly created Object"); + } + + return make(new V8PointerValue(isolate_, v8Object)); +} + +bool V8Runtime::hasNativeState(const jsi::Object& object) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + if (GetInternalFieldType(v8Object) != InternalFieldType::kNativeState) { + return false; + } + auto* holder = reinterpret_cast( + v8Object->GetAlignedPointerFromInternalField( + 1, v8::kEmbedderDataTypeTagDefault)); + return holder != nullptr && holder->state != nullptr; +} + +std::shared_ptr V8Runtime::getNativeState( + const jsi::Object& object) { + if (isHostObject(object)) { + throw jsi::JSINativeException("native state unsupported on HostObject"); + } + assert(hasNativeState(object)); + + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + auto* holder = reinterpret_cast( + v8Object->GetAlignedPointerFromInternalField( + 1, v8::kEmbedderDataTypeTagDefault)); + return holder ? holder->state : nullptr; +} + +void V8Runtime::setNativeState(const jsi::Object& object, + std::shared_ptr state) { + if (isHostObject(object)) { + throw jsi::JSINativeException("native state unsupported on HostObject"); + } + + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Local context = context_.Get(isolate_); + v8::Context::Scope scopedContext(context); + + v8::Local v8ObjectOriginal = + JSIV8ValueConverter::ToV8Object(*this, object); + + if (GetInternalFieldType(v8ObjectOriginal) == + InternalFieldType::kNativeState) { + auto* existingHolder = reinterpret_cast( + v8ObjectOriginal->GetAlignedPointerFromInternalField( + 1, v8::kEmbedderDataTypeTagDefault)); + if (existingHolder) { + existingHolder->state = std::move(state); + return; + } + } + + // Objects created via createObjectWithPrototype pre-allocate 2 internal + // fields with field 0 initialized to kInvalid so setNativeState can populate + // them in place without replacing the V8 object identity. + if (v8ObjectOriginal->InternalFieldCount() == 2 && + v8ObjectOriginal->GetInternalField(0).As()->IsUint32() && + GetInternalFieldType(v8ObjectOriginal) == InternalFieldType::kInvalid) { + v8ObjectOriginal->SetInternalField( + 0, v8::Integer::NewFromUnsigned(isolate_, + InternalFieldType::kNativeState)); + auto* holder = + new NativeStateHolder(isolate_, v8ObjectOriginal, std::move(state)); + v8ObjectOriginal->SetAlignedPointerInInternalField( + 1, reinterpret_cast(holder), v8::kEmbedderDataTypeTagDefault); + return; + } + + v8::Local objectTemplate = + v8::ObjectTemplate::New(isolate_); + objectTemplate->SetInternalFieldCount(2); + v8::Local v8Object; + if (!objectTemplate->NewInstance(context).ToLocal(&v8Object)) { + throw jsi::JSError(*this, "Unable to create new Object for setNativeState"); + } + V8PointerValue* v8PointerValue = static_cast( + const_cast(getPointerValue(object))); + v8PointerValue->Reset(isolate_, v8Object); + + v8Object->SetInternalField(0, v8::Integer::NewFromUnsigned( + isolate_, InternalFieldType::kNativeState)); + auto* holder = new NativeStateHolder(isolate_, v8Object, std::move(state)); + v8Object->SetAlignedPointerInInternalField( + 1, reinterpret_cast(holder), v8::kEmbedderDataTypeTagDefault); + + v8::Local prototype = v8ObjectOriginal->GetPrototype(); + if (!prototype.IsEmpty()) { + v8Object->SetPrototype(context, prototype).Check(); + } + + v8::Local global = context->Global(); + v8::Local objectClass = v8::Local::Cast( + global + ->Get(context, v8::String::NewFromUtf8Literal(isolate_, "Object")) + .ToLocalChecked()); + v8::Local objectAssignFunction = v8::Local::Cast( + objectClass + ->Get(context, v8::String::NewFromUtf8Literal(isolate_, "assign")) + .ToLocalChecked()); + v8::Local args[] = {v8Object, v8ObjectOriginal}; + objectAssignFunction->Call(context, v8::Undefined(isolate_), 2, args) + .ToLocalChecked(); + + v8::Local externalMemoryKey = v8::Private::ForApi( + isolate_, + v8::String::NewFromUtf8Literal(isolate_, "rnv8::ExternalMemoryPressure")); + if (v8ObjectOriginal->HasPrivate(context, externalMemoryKey) + .FromMaybe(false)) { + v8::Local trackerVal; + if (v8ObjectOriginal->GetPrivate(context, externalMemoryKey) + .ToLocal(&trackerVal) && + trackerVal->IsExternal()) { + auto* tracker = static_cast( + v8::Local::Cast(trackerVal) + ->Value(v8::kExternalPointerTypeTagDefault)); + if (tracker) { + tracker->RebindObject(v8Object); + v8Object->SetPrivate(context, externalMemoryKey, trackerVal).Check(); + v8ObjectOriginal->DeletePrivate(context, externalMemoryKey).Check(); + } + } + } +} + +void V8Runtime::setPrototypeOf(const jsi::Object& object, + const jsi::Value& prototype) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Local context = context_.Get(isolate_); + v8::Context::Scope scopedContext(context); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + v8::Local v8Prototype = + JSIV8ValueConverter::ToV8Value(*this, prototype); + if (!v8Object->SetPrototype(context, v8Prototype).FromMaybe(false)) { + throw jsi::JSError(*this, "Unable to set prototype of Object"); + } +} + +jsi::Value V8Runtime::getPrototypeOf(const jsi::Object& object) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + return JSIV8ValueConverter::ToJSIValue(isolate_, v8Object->GetPrototype()); +} + +jsi::Value V8Runtime::getProperty(const jsi::Object& object, + const jsi::PropNameID& name) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::TryCatch tryCatch(isolate_); + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + + v8::MaybeLocal result = + v8Object->Get(isolate_->GetCurrentContext(), + JSIV8ValueConverter::ToV8String(*this, name)); + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + + if (result.IsEmpty()) { + return jsi::Value::undefined(); + } + return JSIV8ValueConverter::ToJSIValue(isolate_, result.ToLocalChecked()); +} + +jsi::Value V8Runtime::getProperty(const jsi::Object& object, + const jsi::String& name) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::TryCatch tryCatch(isolate_); + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + + v8::MaybeLocal result = + v8Object->Get(isolate_->GetCurrentContext(), + JSIV8ValueConverter::ToV8String(*this, name)); + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + if (result.IsEmpty()) { + return jsi::Value::undefined(); + } + return JSIV8ValueConverter::ToJSIValue(isolate_, result.ToLocalChecked()); +} + +bool V8Runtime::hasProperty(const jsi::Object& object, + const jsi::PropNameID& name) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::TryCatch tryCatch(isolate_); + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + + v8::Maybe result = + v8Object->Has(isolate_->GetCurrentContext(), + JSIV8ValueConverter::ToV8String(*this, name)); + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + if (result.IsNothing()) { + return false; + // throw jsi::JSError(*this, "V8Runtime::hasProperty failed."); + } + return result.FromJust(); +} + +bool V8Runtime::hasProperty(const jsi::Object& object, + const jsi::String& name) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::TryCatch tryCatch(isolate_); + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + + v8::Maybe result = + v8Object->Has(isolate_->GetCurrentContext(), + JSIV8ValueConverter::ToV8String(*this, name)); + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + if (result.IsNothing()) { + return false; + // throw jsi::JSError(*this, "V8Runtime::hasProperty failed."); + } + return result.FromJust(); +} + +void V8Runtime::setPropertyValue(const jsi::Object& object, + const jsi::PropNameID& name, + const jsi::Value& value) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + + if (v8Object + ->Set(isolate_->GetCurrentContext(), + JSIV8ValueConverter::ToV8String(*this, name), + JSIV8ValueConverter::ToV8Value(*this, value)) + .IsNothing()) { + throw jsi::JSError(*this, "V8Runtime::setPropertyValue failed."); + } +} + +void V8Runtime::setPropertyValue(const jsi::Object& object, + const jsi::String& name, + const jsi::Value& value) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + + if (v8Object + ->Set(isolate_->GetCurrentContext(), + JSIV8ValueConverter::ToV8String(*this, name), + JSIV8ValueConverter::ToV8Value(*this, value)) + .IsNothing()) { + throw jsi::JSError(*this, "V8Runtime::setPropertyValue failed."); + } +} + +bool V8Runtime::isArray(const jsi::Object& object) const { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + return v8Object->IsArray(); +} + +bool V8Runtime::isArrayBuffer(const jsi::Object& object) const { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + return v8Object->IsArrayBuffer(); +} + +bool V8Runtime::isFunction(const jsi::Object& object) const { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + return v8Object->IsFunction(); +} + +bool V8Runtime::isHostObject(const jsi::Object& object) const { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + return GetInternalFieldType(v8Object) == InternalFieldType::kHostObject; +} + +bool V8Runtime::isHostFunction(const jsi::Function& function) const { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Function = + JSIV8ValueConverter::ToV8Function(*this, function); + + v8::Local prop = + v8::String::NewFromUtf8(isolate_, kHostFunctionProxyProp, + v8::NewStringType::kNormal) + .ToLocalChecked(); + return v8Function->Has(isolate_->GetCurrentContext(), prop).ToChecked(); +} + +jsi::Array V8Runtime::getPropertyNames(const jsi::Object& object) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, object); + v8::Local propertyNames; + if (!v8Object + ->GetPropertyNames(isolate_->GetCurrentContext(), + v8::KeyCollectionMode::kIncludePrototypes, + static_cast( + v8::ONLY_ENUMERABLE | v8::SKIP_SYMBOLS), + v8::IndexFilter::kIncludeIndices, + v8::KeyConversionMode::kConvertToString) + .ToLocal(&propertyNames)) { + std::abort(); + } + return make(new V8PointerValue(isolate_, propertyNames)) + .getArray(*this); +} + +jsi::WeakObject V8Runtime::createWeakObject(const jsi::Object& weakObject) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + const V8PointerValue* v8PointerValue = + static_cast(getPointerValue(weakObject)); + assert(v8PointerValue->Get(isolate_)->IsObject()); + + v8::Global weakRef = + v8::Global(isolate_, v8PointerValue->Get(isolate_)); + weakRef.SetWeak(); + return make( + new V8PointerValue(isolate_, std::move(weakRef))); +} + +jsi::Value V8Runtime::lockWeakObject(const jsi::WeakObject& weakObject) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + const V8PointerValue* v8PointerValue = + static_cast(getPointerValue(weakObject)); + assert(v8PointerValue->Get(isolate_)->IsObject()); + return JSIV8ValueConverter::ToJSIValue(isolate_, + v8PointerValue->Get(isolate_)); +} + +jsi::Array V8Runtime::createArray(size_t length) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Array = + v8::Array::New(isolate_, static_cast(length)); + return make(new V8PointerValue(isolate_, v8Array)) + .getArray(*this); +} + +jsi::ArrayBuffer V8Runtime::createArrayBuffer( + std::shared_ptr buffer) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + if (!buffer) { + throw jsi::JSINativeException( + "V8Runtime::createArrayBuffer requires a non-null MutableBuffer"); + } + + uint8_t* data = buffer->data(); + size_t byteLength = buffer->size(); + auto* retainedBuffer = + new std::shared_ptr(std::move(buffer)); + + std::unique_ptr backingStore = + v8::ArrayBuffer::NewBackingStore( + data, byteLength, + [](void* /*data*/, size_t /*length*/, void* deleterData) { + delete static_cast*>( + deleterData); + }, + retainedBuffer); + + v8::Local v8ArrayBuffer = + v8::ArrayBuffer::New(isolate_, std::move(backingStore)); + return make(new V8PointerValue(isolate_, v8ArrayBuffer)) + .getArrayBuffer(*this); +} + +size_t V8Runtime::size(const jsi::Array& array) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Array = JSIV8ValueConverter::ToV8Array(*this, array); + return v8Array->Length(); +} + +size_t V8Runtime::size(const jsi::ArrayBuffer& arrayBuffer) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, arrayBuffer); + assert(v8Object->IsArrayBuffer()); + v8::ArrayBuffer* v8ArrayBuffer = v8::ArrayBuffer::Cast(*v8Object); + return v8ArrayBuffer->ByteLength(); +} + +uint8_t* V8Runtime::data(const jsi::ArrayBuffer& arrayBuffer) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Object = + JSIV8ValueConverter::ToV8Object(*this, arrayBuffer); + assert(v8Object->IsArrayBuffer()); + v8::ArrayBuffer* v8ArrayBuffer = v8::ArrayBuffer::Cast(*v8Object); + return reinterpret_cast(v8ArrayBuffer->GetBackingStore()->Data()); +} + +jsi::Value V8Runtime::getValueAtIndex(const jsi::Array& array, size_t i) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Array = JSIV8ValueConverter::ToV8Array(*this, array); + v8::MaybeLocal result = + v8Array->Get(isolate_->GetCurrentContext(), static_cast(i)); + if (result.IsEmpty()) { + throw jsi::JSError(*this, "V8Runtime::getValueAtIndex failed."); + } + return JSIV8ValueConverter::ToJSIValue(isolate_, result.ToLocalChecked()); +} + +void V8Runtime::setValueAtIndexImpl(const jsi::Array& array, size_t i, + const jsi::Value& value) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::Local v8Array = JSIV8ValueConverter::ToV8Array(*this, array); + v8::Maybe result = + v8Array->Set(isolate_->GetCurrentContext(), static_cast(i), + JSIV8ValueConverter::ToV8Value(*this, value)); + if (result.IsNothing()) { + throw jsi::JSError(*this, "V8Runtime::setValueAtIndexImpl failed."); + } +} + +jsi::Function V8Runtime::createFunctionFromHostFunction( + const jsi::PropNameID& name, unsigned int paramCount, + jsi::HostFunctionType func) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + HostFunctionProxy* hostFunctionProxy = + new HostFunctionProxy(*this, isolate_, std::move(func)); + v8::Local wrappedHostFunctionProxy = v8::External::New( + isolate_, hostFunctionProxy, v8::kExternalPointerTypeTagDefault); + v8::Local v8HostFunction = + v8::Function::New(isolate_->GetCurrentContext(), + HostFunctionProxy::FunctionCallback, + wrappedHostFunctionProxy) + .ToLocalChecked(); + hostFunctionProxy->BindFinalizer(v8HostFunction); + + v8::Local v8FunctionContainer = + v8::Function::New(isolate_->GetCurrentContext(), + V8Runtime::OnHostFuncionContainerCallback, + v8HostFunction) + .ToLocalChecked(); + + v8::Local prop = + v8::String::NewFromUtf8(isolate_, kHostFunctionProxyProp, + v8::NewStringType::kNormal) + .ToLocalChecked(); + v8FunctionContainer + ->Set(isolate_->GetCurrentContext(), prop, wrappedHostFunctionProxy) + .Check(); + // wrappedHostFunctionProxy is mainly owned by v8HostFunction and not bind + // finalizer to v8FunctionContainer + + v8::Local v8Name = JSIV8ValueConverter::ToV8String(*this, name); + v8FunctionContainer->SetName(v8Name); + + return make(new V8PointerValue(isolate_, v8FunctionContainer)) + .getFunction(*this); +} + +jsi::Value V8Runtime::call(const jsi::Function& function, + const jsi::Value& jsThis, const jsi::Value* args, + size_t count) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::TryCatch tryCatch(isolate_); + v8::Local v8Function = + JSIV8ValueConverter::ToV8Function(*this, function); + + v8::Local v8Receiver; + if (jsThis.isUndefined()) { + v8Receiver = context_.Get(isolate_)->Global(); + } else { + v8Receiver = JSIV8ValueConverter::ToV8Value(*this, jsThis); + } + + std::vector> argv; + for (size_t i = 0; i < count; ++i) { + v8::Local v8ArgValue = + JSIV8ValueConverter::ToV8Value(*this, args[i]); + argv.push_back(v8ArgValue); + } + + v8::MaybeLocal result = + v8Function->Call(isolate_->GetCurrentContext(), v8Receiver, + static_cast(count), argv.data()); + + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + + if (result.IsEmpty()) { + return JSIV8ValueConverter::ToJSIValue(isolate_, v8::Undefined(isolate_)); + } else { + return JSIV8ValueConverter::ToJSIValue(isolate_, result.ToLocalChecked()); + } +} + +jsi::Value V8Runtime::callAsConstructor(const jsi::Function& function, + const jsi::Value* args, size_t count) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::TryCatch tryCatch(isolate_); + v8::Local v8Function = + JSIV8ValueConverter::ToV8Function(*this, function); + std::vector> argv; + for (size_t i = 0; i < count; i++) { + v8::Local v8ArgValue = + JSIV8ValueConverter::ToV8Value(*this, args[i]); + argv.push_back(v8ArgValue); + } + + v8::Local v8Object; + if (!v8Function + ->NewInstance(isolate_->GetCurrentContext(), static_cast(count), + argv.data()) + .ToLocal(&v8Object)) { + throw jsi::JSError(*this, "CallAsConstructor failed"); + } + + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + + return JSIV8ValueConverter::ToJSIValue(isolate_, v8Object); +} + +bool V8Runtime::strictEquals(const jsi::Symbol& a, const jsi::Symbol& b) const { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::TryCatch tryCatch(isolate_); + v8::Local v8SymbolA = JSIV8ValueConverter::ToV8Symbol(*this, a); + v8::Local v8SymbolB = JSIV8ValueConverter::ToV8Symbol(*this, b); + bool result = v8SymbolA->StrictEquals(v8SymbolB); + + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + return result; +} + +bool V8Runtime::strictEquals(const jsi::BigInt& a, const jsi::BigInt& b) const { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::TryCatch tryCatch(isolate_); + v8::Local v8ValueA = + (static_cast(getPointerValue(a)))->Get(isolate_); + v8::Local v8ValueB = + (static_cast(getPointerValue(b)))->Get(isolate_); + bool result = v8ValueA->StrictEquals(v8ValueB); + + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + return result; +} + +bool V8Runtime::strictEquals(const jsi::String& a, const jsi::String& b) const { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::TryCatch tryCatch(isolate_); + v8::Local v8StringA = JSIV8ValueConverter::ToV8String(*this, a); + v8::Local v8StringB = JSIV8ValueConverter::ToV8String(*this, b); + bool result = v8StringA->StrictEquals(v8StringB); + + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + return result; +} + +bool V8Runtime::strictEquals(const jsi::Object& a, const jsi::Object& b) const { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::TryCatch tryCatch(isolate_); + v8::Local v8ObjectA = JSIV8ValueConverter::ToV8Object(*this, a); + v8::Local v8ObjectB = JSIV8ValueConverter::ToV8Object(*this, b); + bool result = v8ObjectA->StrictEquals(v8ObjectB); + + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + return result; +} + +bool V8Runtime::instanceOf(const jsi::Object& o, const jsi::Function& f) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Context::Scope scopedContext(context_.Get(isolate_)); + + v8::TryCatch tryCatch(isolate_); + v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, o); + v8::Local v8Function = JSIV8ValueConverter::ToV8Object(*this, f); + bool result = v8Object->InstanceOf(isolate_->GetCurrentContext(), v8Function) + .ToChecked(); + + if (tryCatch.HasCaught()) { + ReportException(isolate_, &tryCatch); + } + return result; +} + +void V8Runtime::setExternalMemoryPressure(const jsi::Object& obj, + size_t amount) { + v8::Locker locker(isolate_); + v8::Isolate::Scope scopedIsolate(isolate_); + v8::HandleScope scopedHandle(isolate_); + v8::Local context = context_.Get(isolate_); + v8::Context::Scope scopedContext(context); + + v8::Local v8Object = JSIV8ValueConverter::ToV8Object(*this, obj); + v8::Local privateKey = v8::Private::ForApi( + isolate_, + v8::String::NewFromUtf8Literal(isolate_, "rnv8::ExternalMemoryPressure")); + + if (v8Object->HasPrivate(context, privateKey).FromMaybe(false)) { + v8::Local existingVal; + if (v8Object->GetPrivate(context, privateKey).ToLocal(&existingVal) && + existingVal->IsExternal()) { + auto* tracker = static_cast( + v8::Local::Cast(existingVal) + ->Value(v8::kExternalPointerTypeTagDefault)); + if (tracker) { + tracker->Update(amount); + return; + } + } + } + + if (amount == 0) { + return; + } + + auto* tracker = new ExternalMemoryTracker(isolate_, v8Object, amount); + v8::Local wrappedTracker = v8::External::New( + isolate_, tracker, v8::kExternalPointerTypeTagDefault); + v8Object->SetPrivate(context, privateKey, wrappedTracker).Check(); +} + +// +// JS function/object handler callbacks +// + +// static +void V8Runtime::GetRuntimeInfo( + const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + v8::HandleScope scopedHandle(isolate); + v8::Local runtimeInfo = v8::Object::New(isolate); + v8::Local context(isolate->GetCurrentContext()); + + v8::Local versionKey = + v8::String::NewFromUtf8(isolate, "version", v8::NewStringType::kNormal) + .ToLocalChecked(); + v8::Local versionValue = + v8::String::NewFromUtf8(args.GetIsolate(), v8::V8::GetVersion(), + v8::NewStringType::kNormal) + .ToLocalChecked(); + runtimeInfo->Set(context, versionKey, versionValue).Check(); + + v8::Local memoryKey = + v8::String::NewFromUtf8(isolate, "memory", v8::NewStringType::kNormal) + .ToLocalChecked(); + v8::Local memoryInfo = v8::Object::New(isolate); + v8::HeapStatistics heapStats; + isolate->GetHeapStatistics(&heapStats); + memoryInfo + ->Set(context, + v8::String::NewFromUtf8(isolate, "jsHeapSizeLimit", + v8::NewStringType::kNormal) + .ToLocalChecked(), + v8::Number::New(isolate, heapStats.heap_size_limit())) + .Check(); + memoryInfo + ->Set(context, + v8::String::NewFromUtf8(isolate, "totalJSHeapSize", + v8::NewStringType::kNormal) + .ToLocalChecked(), + v8::Number::New(isolate, heapStats.total_heap_size())) + .Check(); + memoryInfo + ->Set(context, + v8::String::NewFromUtf8(isolate, "usedJSHeapSize", + v8::NewStringType::kNormal) + .ToLocalChecked(), + v8::Number::New(isolate, heapStats.used_heap_size())) + .Check(); + runtimeInfo->Set(context, memoryKey, memoryInfo).Check(); + + args.GetReturnValue().Set(runtimeInfo); +} + +// static +void V8Runtime::OnHostFuncionContainerCallback( + const v8::FunctionCallbackInfo& args) { + v8::HandleScope scopedHandle(args.GetIsolate()); + + v8::Local v8HostFunction = + v8::Local::Cast(args.Data()); + std::vector> argv; + for (size_t i = 0; i < args.Length(); ++i) { + argv.push_back(args[(int)i]); + } + v8::MaybeLocal result = + v8HostFunction->Call(args.GetIsolate()->GetCurrentContext(), args.This(), + args.Length(), argv.data()); + + if (!result.IsEmpty()) { + args.GetReturnValue().Set(result.ToLocalChecked()); + } +} + +} // namespace rnv8 \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/v8runtime/V8Runtime.h b/test-app/runtime/src/main/cpp/v8runtime/V8Runtime.h new file mode 100644 index 000000000..e2159c0af --- /dev/null +++ b/test-app/runtime/src/main/cpp/v8runtime/V8Runtime.h @@ -0,0 +1,237 @@ +/* + * Copyright (c) Kudo Chien. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +// #include +#include "V8RuntimeConfig.h" +#include "jsi/jsi.h" +#include "libplatform/libplatform.h" +#include "v8.h" + +namespace rnv8 { + +class V8Runtime; +class V8PointerValue; +class InspectorClient; + +class JSI_EXPORT V8Runtime : public facebook::jsi::Runtime { + public: + V8Runtime(); + // V8Runtime( + // std::unique_ptr config, + // std::shared_ptr jsQueue); + // V8Runtime( + // const V8Runtime *v8Runtime, + // std::unique_ptr config); + ~V8Runtime(); + + // Calling this function when the platform main runloop is idle + void OnMainLoopIdle(); + + private: + v8::Local CreateGlobalContext(v8::Isolate* isolate); + facebook::jsi::Value ExecuteScript(v8::Isolate* isolate, + const v8::Local& script, + const std::string& sourceURL); + void ReportException(v8::Isolate* isolate, v8::TryCatch* tryCatch) const; + + std::unique_ptr LoadCodeCacheIfNeeded( + const std::string& sourceURL); + bool SaveCodeCacheIfNeeded(const v8::Local& script, + const std::string& sourceURL, + v8::ScriptCompiler::CachedData* cachedData); + std::unique_ptr UseFakeSourceIfNeeded( + const v8::ScriptOrigin& origin, + v8::ScriptCompiler::CachedData* cachedData); + + enum InternalFieldType { + kInvalid = 0, + kHostObject = 1, + kNativeState = 2, + kMaxValue = kNativeState, + }; + InternalFieldType GetInternalFieldType(v8::Local object) const; + + static v8::Platform* GetPlatform(); + + // + // facebook::jsi::Runtime implementations + // + public: + facebook::jsi::Value evaluateJavaScript( + const std::shared_ptr& buffer, + const std::string& sourceURL) override; + + std::shared_ptr prepareJavaScript( + const std::shared_ptr& buffer, + std::string sourceURL) override; + facebook::jsi::Value evaluatePreparedJavaScript( + const std::shared_ptr& js) + override; + + void queueMicrotask(const facebook::jsi::Function& callback) override; + bool drainMicrotasks(int maxMicrotasksHint = -1) override; + + facebook::jsi::Object global() override; + std::string description() override; + bool isInspectable() override; + + protected: + 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; + + facebook::jsi::PropNameID createPropNameIDFromAscii(const char* str, + size_t length) override; + facebook::jsi::PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, + size_t length) override; + facebook::jsi::PropNameID createPropNameIDFromString( + const facebook::jsi::String& str) override; + facebook::jsi::PropNameID createPropNameIDFromSymbol( + const facebook::jsi::Symbol& sym) override; + std::string utf8(const facebook::jsi::PropNameID&) override; + bool compare(const facebook::jsi::PropNameID&, + const facebook::jsi::PropNameID&) override; + + std::string symbolToString(const facebook::jsi::Symbol&) override; + + facebook::jsi::BigInt createBigIntFromInt64(int64_t) override; + facebook::jsi::BigInt createBigIntFromUint64(uint64_t) override; + bool bigintIsInt64(const facebook::jsi::BigInt&) override; + bool bigintIsUint64(const facebook::jsi::BigInt&) override; + uint64_t truncate(const facebook::jsi::BigInt&) override; + facebook::jsi::String bigintToString(const facebook::jsi::BigInt&, + int) override; + + facebook::jsi::String createStringFromAscii(const char* str, + size_t length) override; + facebook::jsi::String createStringFromUtf8(const uint8_t* utf8, + size_t length) override; + std::string utf8(const facebook::jsi::String&) override; + + facebook::jsi::Object createObject() override; + facebook::jsi::Object createObject( + std::shared_ptr hostObject) override; + std::shared_ptr getHostObject( + const facebook::jsi::Object&) override; + facebook::jsi::HostFunctionType& getHostFunction( + const facebook::jsi::Function&) override; + + facebook::jsi::Object createObjectWithPrototype( + const facebook::jsi::Value& prototype) override; + + bool hasNativeState(const facebook::jsi::Object&) override; + std::shared_ptr getNativeState( + const facebook::jsi::Object&) override; + void setNativeState( + const facebook::jsi::Object&, + std::shared_ptr state) override; + + void setPrototypeOf(const facebook::jsi::Object& object, + const facebook::jsi::Value& prototype) override; + facebook::jsi::Value getPrototypeOf( + const facebook::jsi::Object& object) override; + + facebook::jsi::Value getProperty( + const facebook::jsi::Object&, + const facebook::jsi::PropNameID& name) override; + facebook::jsi::Value getProperty(const facebook::jsi::Object&, + const facebook::jsi::String& name) override; + bool hasProperty(const facebook::jsi::Object&, + const facebook::jsi::PropNameID& name) override; + bool hasProperty(const facebook::jsi::Object&, + const facebook::jsi::String& name) override; + void setPropertyValue(const facebook::jsi::Object&, + const facebook::jsi::PropNameID& name, + const facebook::jsi::Value& value) override; + void setPropertyValue(const facebook::jsi::Object&, + const facebook::jsi::String& name, + const facebook::jsi::Value& value) override; + + bool isArray(const facebook::jsi::Object&) const override; + bool isArrayBuffer(const facebook::jsi::Object&) const override; + bool isFunction(const facebook::jsi::Object&) const override; + bool isHostObject(const facebook::jsi::Object&) const override; + bool isHostFunction(const facebook::jsi::Function&) const override; + facebook::jsi::Array getPropertyNames(const facebook::jsi::Object&) override; + + facebook::jsi::WeakObject createWeakObject( + const facebook::jsi::Object&) override; + facebook::jsi::Value lockWeakObject( + const facebook::jsi::WeakObject&) override; + + facebook::jsi::Array createArray(size_t length) override; + facebook::jsi::ArrayBuffer createArrayBuffer( + std::shared_ptr buffer) override; + size_t size(const facebook::jsi::Array&) override; + size_t size(const facebook::jsi::ArrayBuffer&) override; + uint8_t* data(const facebook::jsi::ArrayBuffer&) override; + facebook::jsi::Value getValueAtIndex(const facebook::jsi::Array&, + size_t i) override; + void setValueAtIndexImpl(const facebook::jsi::Array&, size_t i, + const facebook::jsi::Value& value) override; + + facebook::jsi::Function createFunctionFromHostFunction( + const facebook::jsi::PropNameID& name, unsigned int paramCount, + facebook::jsi::HostFunctionType func) override; + facebook::jsi::Value call(const facebook::jsi::Function&, + const facebook::jsi::Value& jsThis, + const facebook::jsi::Value* args, + size_t count) override; + facebook::jsi::Value callAsConstructor(const facebook::jsi::Function&, + const facebook::jsi::Value* args, + size_t count) override; + + bool strictEquals(const facebook::jsi::Symbol& a, + const facebook::jsi::Symbol& b) const override; + bool strictEquals(const facebook::jsi::BigInt& a, + const facebook::jsi::BigInt& b) const override; + + bool strictEquals(const facebook::jsi::String& a, + const facebook::jsi::String& b) const override; + bool strictEquals(const facebook::jsi::Object& a, + const facebook::jsi::Object& b) const override; + + bool instanceOf(const facebook::jsi::Object& o, + const facebook::jsi::Function& f) override; + + void setExternalMemoryPressure(const facebook::jsi::Object& obj, + size_t amount) override; + + private: + friend class V8PointerValue; + friend class JSIV8ValueConverter; + + // + // JS function/object handler callbacks + // + private: + // For `global._v8runtime()` + static void GetRuntimeInfo(const v8::FunctionCallbackInfo& args); + + // For `HostFunctionContainer()`, will call underlying HostFunction + static void OnHostFuncionContainerCallback( + const v8::FunctionCallbackInfo& args); + + private: + static std::unique_ptr s_platform; + + private: + std::unique_ptr config_; + std::unique_ptr arrayBufferAllocator_; + std::unique_ptr snapshotBlob_; + v8::Isolate* isolate_; + v8::Global context_; + std::shared_ptr inspectorClient_; + bool isSharedRuntime_ = false; + // std::shared_ptr jsQueue_; +}; + +} // namespace rnv8 \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/v8runtime/V8RuntimeConfig.h b/test-app/runtime/src/main/cpp/v8runtime/V8RuntimeConfig.h new file mode 100644 index 000000000..76ae3c740 --- /dev/null +++ b/test-app/runtime/src/main/cpp/v8runtime/V8RuntimeConfig.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) Kudo Chien. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace rnv8 { + +struct V8RuntimeConfig { + V8RuntimeConfig() {} + ~V8RuntimeConfig() = default; + + V8RuntimeConfig(const V8RuntimeConfig& rhs) = delete; + V8RuntimeConfig& operator=(const V8RuntimeConfig& rhs) = delete; + + V8RuntimeConfig(V8RuntimeConfig&& rhs) = default; + V8RuntimeConfig& operator=(V8RuntimeConfig&& rhs) = default; +}; + +} // namespace rnv8 \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/v8runtime/V8RuntimeFactory.cpp b/test-app/runtime/src/main/cpp/v8runtime/V8RuntimeFactory.cpp new file mode 100644 index 000000000..c067260f7 --- /dev/null +++ b/test-app/runtime/src/main/cpp/v8runtime/V8RuntimeFactory.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) Kudo Chien. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "V8RuntimeFactory.h" + +#include "V8Runtime.h" + +namespace rnv8 { + +std::unique_ptr createV8Runtime() { + return std::make_unique(); +} + +// std::unique_ptr createSharedV8Runtime( +// const facebook::jsi::Runtime *sharedRuntime, +// std::unique_ptr config) { +// auto *sharedV8Runtime = dynamic_cast(sharedRuntime); +// return std::make_unique(sharedV8Runtime, std::move(config)); +// } + +} // namespace rnv8 \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/v8runtime/V8RuntimeFactory.h b/test-app/runtime/src/main/cpp/v8runtime/V8RuntimeFactory.h new file mode 100644 index 000000000..b973bdf31 --- /dev/null +++ b/test-app/runtime/src/main/cpp/v8runtime/V8RuntimeFactory.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) Kudo Chien. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include "jsi/jsi.h" + +namespace rnv8 { + +JSI_EXPORT std::unique_ptr createV8Runtime(); + +// std::unique_ptr createSharedV8Runtime( +// const facebook::jsi::Runtime *sharedRuntime, +// std::unique_ptr config); + +} // namespace rnv8 \ No newline at end of file