From e337bcafb0b017311c37f2dbc24e5a757af0a205 Mon Sep 17 00:00:00 2001 From: Marc Horowitz Date: Thu, 18 Oct 2018 00:47:02 -0700 Subject: [PATCH] Add JSI API and JSIDynamic glue to OSS React Native Summary: This will help abstract the JS engine from React Native Reviewed By: hramos Differential Revision: D9328237 fbshipit-source-id: 7b34f55f28e43d83ba24d22e83e836c92ca737a9 --- ReactCommon/jsi/BUCK | 61 ++ ReactCommon/jsi/JSIDynamic.cpp | 93 +++ ReactCommon/jsi/JSIDynamic.h | 18 + ReactCommon/jsi/instrumentation.h | 73 ++ ReactCommon/jsi/jsi-inl.h | 306 ++++++++ ReactCommon/jsi/jsi.cpp | 350 +++++++++ ReactCommon/jsi/jsi.h | 1146 +++++++++++++++++++++++++++++ tools/build_defs/oss/rn_defs.bzl | 6 +- 8 files changed, 2052 insertions(+), 1 deletion(-) create mode 100644 ReactCommon/jsi/BUCK create mode 100644 ReactCommon/jsi/JSIDynamic.cpp create mode 100644 ReactCommon/jsi/JSIDynamic.h create mode 100644 ReactCommon/jsi/instrumentation.h create mode 100644 ReactCommon/jsi/jsi-inl.h create mode 100644 ReactCommon/jsi/jsi.cpp create mode 100644 ReactCommon/jsi/jsi.h diff --git a/ReactCommon/jsi/BUCK b/ReactCommon/jsi/BUCK new file mode 100644 index 000000000..552cc8a9e --- /dev/null +++ b/ReactCommon/jsi/BUCK @@ -0,0 +1,61 @@ +# BUILD FILE SYNTAX: SKYLARK + +load("//tools/build_defs/oss:rn_defs.bzl", "react_native_xplat_dep", "rn_xplat_cxx_library") + +rn_xplat_cxx_library( + name = "jsi", + srcs = [ + "jsi.cpp", + ], + header_namespace = "jsi", + exported_headers = [ + "instrumentation.h", + "jsi.h", + "jsi-inl.h", + ], + compiler_flags = [ + "-O3", + "-fexceptions", + "-frtti", + "-std=c++14", + "-Wall", + "-Werror", + "-Wextra", + "-Wcast-qual", + "-Wdelete-non-virtual-dtor", + "-Wwrite-strings", + ], + cxx_compiler_flags = [ + "-Wglobal-constructors", + "-Wmissing-prototypes", + ], + fbobjc_compiler_flags = [ + "-Wglobal-constructors", + "-Wmissing-prototypes", + ], + visibility = ["PUBLIC"], +) + +rn_xplat_cxx_library( + name = "JSIDynamic", + srcs = [ + "JSIDynamic.cpp", + ], + header_namespace = "jsi", + exported_headers = [ + "JSIDynamic.h", + ], + compiler_flags = [ + "-fexceptions", + "-frtti", + ], + fbobjc_force_static = True, + visibility = [ + "PUBLIC", + ], + xcode_public_headers_symlinks = True, + deps = [ + "xplat//folly:molly", + react_native_xplat_dep("jsi:jsi"), + ], +) diff --git a/ReactCommon/jsi/JSIDynamic.cpp b/ReactCommon/jsi/JSIDynamic.cpp new file mode 100644 index 000000000..938f362bb --- /dev/null +++ b/ReactCommon/jsi/JSIDynamic.cpp @@ -0,0 +1,93 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "JSIDynamic.h" + +#include +#include + +using namespace facebook::jsi; + +namespace facebook { +namespace jsi { + +Value valueFromDynamic(Runtime& runtime, const folly::dynamic& dyn) { + switch (dyn.type()) { + case folly::dynamic::NULLT: + return Value::null(); + case folly::dynamic::ARRAY: { + Array ret = Array(runtime, dyn.size()); + for (size_t i = 0; i < dyn.size(); ++i) { + ret.setValueAtIndex(runtime, i, valueFromDynamic(runtime, dyn[i])); + } + return std::move(ret); + } + case folly::dynamic::BOOL: + return dyn.getBool(); + case folly::dynamic::DOUBLE: + return dyn.getDouble(); + case folly::dynamic::INT64: + // Can't use asDouble() here. If the int64 value is too bit to be + // represented precisely as a double, folly will throw an + // exception. + return (double)dyn.getInt(); + case folly::dynamic::OBJECT: { + Object ret(runtime); + for (const auto& element : dyn.items()) { + Value value = valueFromDynamic(runtime, element.second); + if (element.first.isNumber() || element.first.isString()) { + ret.setProperty(runtime, element.first.asString().c_str(), value); + } + } + return std::move(ret); + } + case folly::dynamic::STRING: + return String::createFromUtf8(runtime, dyn.getString()); + } + CHECK(false); +} + +folly::dynamic dynamicFromValue(Runtime& runtime, const Value& value) { + if (value.isUndefined() || value.isNull()) { + return nullptr; + } else if (value.isBool()) { + return value.getBool(); + } else if (value.isNumber()) { + return value.getNumber(); + } else if (value.isString()) { + return value.getString(runtime).utf8(runtime); + } else { + Object obj = value.getObject(runtime); + if (obj.isArray(runtime)) { + Array array = obj.getArray(runtime); + folly::dynamic ret = folly::dynamic::array(); + for (size_t i = 0; i < array.size(runtime); ++i) { + ret.push_back(dynamicFromValue(runtime, array.getValueAtIndex(runtime, i))); + } + return ret; + } else if (obj.isFunction(runtime)) { + throw JSError(runtime, "JS Functions are not convertible to dynamic"); + } else { + folly::dynamic ret = folly::dynamic::object(); + Array names = obj.getPropertyNames(runtime); + for (size_t i = 0; i < names.size(runtime); ++i) { + String name = names.getValueAtIndex(runtime, i).getString(runtime); + Value prop = obj.getProperty(runtime, name); + if (prop.isUndefined()) { + continue; + } + // The JSC conversion uses JSON.stringify, which substitutes + // null for a function, so we do the same here. Just dropping + // the pair might also work, but would require more testing. + if (prop.isObject() && prop.getObject(runtime).isFunction(runtime)) { + prop = Value::null(); + } + ret.insert( + name.utf8(runtime), dynamicFromValue(runtime, std::move(prop))); + } + return ret; + } + } +} + +} +} diff --git a/ReactCommon/jsi/JSIDynamic.h b/ReactCommon/jsi/JSIDynamic.h new file mode 100644 index 000000000..0daf273dc --- /dev/null +++ b/ReactCommon/jsi/JSIDynamic.h @@ -0,0 +1,18 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include +#include + +namespace facebook { +namespace jsi { + +facebook::jsi::Value valueFromDynamic( + facebook::jsi::Runtime& runtime, const folly::dynamic& dyn); + +folly::dynamic dynamicFromValue(facebook::jsi::Runtime& runtime, + const facebook::jsi::Value& value); + +} +} diff --git a/ReactCommon/jsi/instrumentation.h b/ReactCommon/jsi/instrumentation.h new file mode 100644 index 000000000..e35ef1dbf --- /dev/null +++ b/ReactCommon/jsi/instrumentation.h @@ -0,0 +1,73 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include + +#include + +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. +class Instrumentation { + public: + 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 jsi Value containing whichever statistics the runtime supports + /// for its heap. + virtual Value getHeapInfo(bool includeExpensive) = 0; + + /// perform a full garbage collection + virtual void collectGarbage() = 0; + + /// Captures the heap to a file + /// + /// \param path to save the heap capture + /// + /// \param compact Whether the JSON should be compact or pretty + /// + /// \return true iff the heap capture succeeded + virtual bool createSnapshotToFile(const std::string& path, bool compact) = 0; + + /// Write a trace of bridge traffic to the given file name. + virtual void writeBridgeTrafficTraceToFile( + const std::string& fileName) const = 0; + + /// Write basic block profile trace to the given file name. + virtual void writeBasicBlockProfileTraceToFile( + const std::string& fileName) const = 0; + + /// Enable sampling profiler. + virtual void enableSamplingProfiler() const = 0; + + /// Dump sampled stack trace to the given file name. + virtual void dumpSampledTraceToFile(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 diff --git a/ReactCommon/jsi/jsi-inl.h b/ReactCommon/jsi/jsi-inl.h new file mode 100644 index 000000000..155a840ce --- /dev/null +++ b/ReactCommon/jsi/jsi-inl.h @@ -0,0 +1,306 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#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&, 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::createFromAscii(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); +} + +void throwJSError(Runtime&, const char* msg); + +} // namespace detail + +template +inline T Runtime::make(Runtime::PointerValue* pv) { + return T(pv); +} + +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_; +} + +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) { + setProperty( + runtime, String::createFromAscii(runtime, name), std::forward(value)); +} + +template +void Object::setProperty(Runtime& runtime, const String& name, T&& value) { + setPropertyValue( + runtime, name, detail::toValue(runtime, std::forward(value))); +} + +template +void Object::setProperty(Runtime& runtime, const PropNameID& name, T&& value) { + 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::throwJSError(runtime, "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); +} + +inline Array Object::getPropertyNames(Runtime& runtime) const { + return runtime.getPropertyNames(*this); +} + +inline Value WeakObject::lock(Runtime& runtime) { + return runtime.lockWeakObject(*this); +} + +template +void Array::setValueAtIndex(Runtime& runtime, size_t i, T&& value) { + 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 as HermesValues, instead of having to + // wrap the args in Values and hvFromValue on each to unwrap them. + // 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 as HermesValues, instead of having to + // wrap the args in Values and hvFromValue on each to unwrap them. + // 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))...}); +} + +} // namespace jsi +} // namespace facebook diff --git a/ReactCommon/jsi/jsi.cpp b/ReactCommon/jsi/jsi.cpp new file mode 100644 index 000000000..5709d5ca9 --- /dev/null +++ b/ReactCommon/jsi/jsi.cpp @@ -0,0 +1,350 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include +#include +#include +#include + +#include +#include + +namespace facebook { +namespace jsi { + +namespace detail { + +void throwJSError(Runtime& rt, const char* msg) { + throw JSError(rt, msg); +} + +} // namespace detail + +Buffer::~Buffer() {} + +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() {} + +Runtime::~Runtime() {} + +Instrumentation& Runtime::instrumentation() { + class NoInstrumentation : public Instrumentation { + std::string getRecordedGCStats() override { + return ""; + } + + Value getHeapInfo(bool) override { + return Value::undefined(); + } + + void collectGarbage() override {} + + bool createSnapshotToFile(const std::string&, bool) override { + return false; + } + + void writeBridgeTrafficTraceToFile(const std::string&) const override { + std::abort(); + } + + void writeBasicBlockProfileTraceToFile(const std::string&) const override { + std::abort(); + } + + void enableSamplingProfiler() const override { + std::abort(); + } + + void dumpSampledTraceToFile(const std::string&) const override { + std::abort(); + } + + void dumpProfilerSymbolsToFile(const std::string&) const override { + std::abort(); + } + }; + + static NoInstrumentation sharedInstance; + return sharedInstance; +} + +Pointer& Pointer::operator=(Pointer&& other) { + 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 not 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 not a Function"); + }; + + Runtime::PointerValue* value = obj.ptr_; + obj.ptr_ = nullptr; + return Function(value); +} + +Array Object::asArray(Runtime& runtime) const& { + if (!isArray(runtime)) { + throw JSError(runtime, "Object is not an array"); + } + return getArray(runtime); +} + +Array Object::asArray(Runtime& runtime) && { + if (!isArray(runtime)) { + throw JSError(runtime, "Object is not an array"); + } + return std::move(*this).getArray(runtime); +} + +Function Object::asFunction(Runtime& runtime) const& { + if (!isFunction(runtime)) { + throw JSError(runtime, "Object is not a function"); + } + return getFunction(runtime); +} + +Function Object::asFunction(Runtime& runtime) && { + if (!isFunction(runtime)) { + throw JSError(runtime, "Object is not a function"); + } + return std::move(*this).getFunction(runtime); +} + +Value::Value(Value&& other) : 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_ == 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(); + } +} + +Value Value::createFromJsonUtf8( + Runtime& runtime, + const uint8_t* json, + size_t length) { + Function parseJson = runtime.global() + .getPropertyAsObject(runtime, "JSON") + .getPropertyAsFunction(runtime, "parse"); + return parseJson.call(runtime, String::createFromUtf8(runtime, json, length)); +} + +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 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; +} + +double Value::asNumber() const { + if (!isNumber()) { + throw JSINativeException("Value is not an Object"); + } + + return getNumber(); +} + +Object Value::asObject(Runtime& runtime) const& { + if (!isObject()) { + throw JSError(runtime, "Value is not an Object"); + } + + return getObject(runtime); +} + +Object Value::asObject(Runtime& rt) && { + if (!isObject()) { + throw JSError(rt, "Value is not an Object"); + } + auto ptr = data_.pointer.ptr_; + data_.pointer.ptr_ = nullptr; + return static_cast(ptr); +} + +String Value::asString(Runtime& rt) const& { + if (!isString()) { + throw JSError(rt, "Value is not a String"); + } + + return getString(rt); +} + +String Value::asString(Runtime& rt) && { + if (!isString()) { + throw JSError(rt, "Value is not 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); +} + +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, rt.global().getPropertyAsFunction(rt, "Error").call(rt, message_)); + } catch (...) { + setValue(rt, Value()); + } +} + +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 (...) { + setValue(rt, Value()); + } +} + +JSError::JSError(std::string what, Runtime& rt, Value&& value) + : JSIException(std::move(what)) { + setValue(rt, std::move(value)); +} + +void JSError::setValue(Runtime& rt, Value&& value) { + value_ = std::make_shared(std::move(value)); + + try { + if ((message_.empty() || stack_.empty()) && value_->isObject()) { + auto obj = value_->getObject(rt); + + if (message_.empty()) { + jsi::Value message = obj.getProperty(rt, "message"); + if (!message.isUndefined()) { + message_ = message.toString(rt).utf8(rt); + } + } + + if (stack_.empty()) { + jsi::Value stack = obj.getProperty(rt, "stack"); + if (!stack.isUndefined()) { + stack_ = stack.toString(rt).utf8(rt); + } + } + } + + if (message_.empty()) { + message_ = value_->toString(rt).utf8(rt); + } + + if (stack_.empty()) { + stack_ = "no stack"; + } + + if (what_.empty()) { + what_ = message_ + "\n\n" + stack_; + } + } catch (...) { + message_ = "[Exception caught creating message string]"; + stack_ = "[Exception caught creating stack string]"; + what_ = "[Exception caught getting value fields]"; + } +} + +} // namespace jsi +} // namespace facebook diff --git a/ReactCommon/jsi/jsi.h b/ReactCommon/jsi/jsi.h new file mode 100644 index 000000000..b1b26ff6a --- /dev/null +++ b/ReactCommon/jsi/jsi.h @@ -0,0 +1,1146 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#define JSI_EXPORT __attribute__((visibility("default"))) + +class FBJSRuntime; +namespace facebook { +namespace jsi { + +namespace detail { + +template +class ThreadSafeRuntimeImpl; +} + +class Buffer { + public: + virtual ~Buffer(); + virtual size_t size() const = 0; + virtual const uint8_t* data() const = 0; +}; + +class 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_; +}; + +class Runtime; +class Pointer; +class PropNameID; +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< + Value(Runtime& rt, const Value& thisVal, const Value* args, size_t count)>; + +/// 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 thow a + // JS \c Error object. The default implementation returns empty vector. + virtual std::vector getPropertyNames(Runtime& rt); +}; + +/// Represents a JS runtime. Movable, but not copyable. Note that +/// this object is not 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. +class 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 bytcode + /// whose format is specific to the implementation. If the input + /// format is unknown, or evaluation causes an error, a JSIException + /// will be thrown. + virtual void evaluateJavaScript( + std::unique_ptr buffer, + const std::string& sourceURL) = 0; + /// \return the global object + virtual Object global() = 0; + + /// \return a short printable description of the instance. 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 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: + ~PointerValue() = default; + }; + + 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 std::string utf8(const PropNameID&) = 0; + virtual bool compare(const PropNameID&, const PropNameID&) = 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; + + 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; + + 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(Object&, const PropNameID& name, const Value& value) = 0; + virtual void + setPropertyValue(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 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(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 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; + + // These exist so derived classes can access the private parts of + // Value, String, and Object, which are all friends of Runtime. + template + static T make(PointerValue* pv); + static const PointerValue* getPointerValue(const Pointer& pointer); + static const PointerValue* getPointerValue(const Value& value); + + // TODO T25594389: think harder about this friend declaration (and + // it's forward decl above) + template + friend class detail::ThreadSafeRuntimeImpl; + friend class ::FBJSRuntime; +}; + +// Base class for pointer-storing types. +class Pointer { + protected: + explicit Pointer(Pointer&& other) : ptr_(other.ptr_) { + other.ptr_ = nullptr; + } + + ~Pointer() { + if (ptr_) { + ptr_->invalidate(); + } + } + + Pointer& operator=(Pointer&& other); + + 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 PropNameID : public Pointer { + public: + using Pointer::Pointer; + + PropNameID(Runtime& runtime, const PropNameID& other) + : PropNameID(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. + 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. + 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); + } + + // 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); + } + + 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 String. Movable, not copyable. +class 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. + 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. + 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); + } + + friend class Runtime; + friend class Value; +}; + +class Array; +class Function; + +/// Represents a JS Object. Movable, not copyable. +class 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); + } + + /// \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) { + return rt.instanceOf(*this, ctor); + } + + /// \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); + + /// 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); + + /// 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); + + /// \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 crete this object. If \c isHostObject + /// is false, this will throw. + template + std::shared_ptr asHostObject(Runtime& runtime) 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; + + protected: + void + setPropertyValue(Runtime& runtime, const String& name, const Value& value) { + return runtime.setPropertyValue(*this, name, value); + } + + void setPropertyValue( + Runtime& runtime, + const PropNameID& name, + const Value& value) { + 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 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); + + friend class Runtime; +}; + +/// Represents a JS Object which can be efficiently used as an array +/// with integral indices. +class 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); + + /// 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 intitializer list. + static Array createWithElements( + Runtime& runtime, + std::initializer_list elements); + + private: + friend class Object; + friend class Value; + + void setValueAtIndexImpl(Runtime& runtime, size_t i, const Value& value) { + return runtime.setValueAtIndexImpl(*this, i, value); + } + + Array(Runtime::PointerValue* value) : Object(value) {} +}; + +/// Represents a JSArrayBuffer +class ArrayBuffer : public Object { + public: + ArrayBuffer(ArrayBuffer&&) = default; + ArrayBuffer& operator=(ArrayBuffer&&) = default; + + /// \return the size of the ArrayBuffer, according to its 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) { + return runtime.data(*this); + } + + private: + friend class Object; + friend class Value; + + ArrayBuffer(Runtime::PointerValue* value) : Object(value) {} +}; + +/// Represents a JS Object which is guaranteed to be Callable. +class 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. + 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 be undefined. + 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 be + /// undefined. + 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 be undefined. + template + Value call(Runtime& runtime, Args&&... args) const; + + /// Calls the function with \c count \c args and \c jsThis value passed + /// as 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. The \c this value of the JS function will be + /// undefined. + Value callWithThis( + Runtime& runtime, + const Object& jsThis, + 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 be undefined. + 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; + + Function(Runtime::PointerValue* value) : Object(value) {} +}; + +/// Represents any JS Value (undefined, null, boolean, number, string, +/// or object). Movable, or explicitly copyable (has no copy ctor). +class Value { + public: + /// Default ctor creates an \c undefined JS value. + Value() : 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 String or Object rvalue into a new JS value. + template + /* implicit */ Value(T&& other) : Value(kindOf(other)) { + static_assert( + std::is_base_of::value || std::is_base_of::value, + "Value cannot be implictly move-constructed from this type"); + 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&& value); + + /// 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 according to the SameValue algorithm see more here: + // https://www.ecma-international.org/ecma-262/5.1/#sec-11.9.4 + static bool strictEquals(Runtime& runtime, const Value& a, const Value& b); + + Value& operator=(Value&& other) { + 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 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 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 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); + } + + 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, + StringKind, + ObjectKind, + PointerKind = StringKind, + }; + + 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; // String, Object, Array, Function + }; + + Value(ValueKind kind) : kind_(kind) {} + + 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. In the Hermes impl, if + // the object contains a PinnedHermesValue, we need to be able to + // get a pointer to it; this can be casted from 'this'. In the JSC + // impl, we need to be able to convert the boxed value into a JSC + // ref. This can be done by casting this, deferencing it to get a + // number, doing some bit masks, and then casting again into the + // desired JSC ref type. +}; + +/// 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 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 JSIException : public std::exception { + protected: + JSIException(){}; + JSIException(std::string what) : what_(std::move(what)){}; + + public: + virtual const char* what() const noexcept override { + return what_.c_str(); + } + + protected: + std::string what_; +}; + +/// This exception will be thrown by API functions on errors not related to +/// JavaScript execution. +class JSINativeException : public JSIException { + public: + JSINativeException(std::string what) : JSIException(std::move(what)) {} +}; + +/// 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 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); + + 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 diff --git a/tools/build_defs/oss/rn_defs.bzl b/tools/build_defs/oss/rn_defs.bzl index 2f73d5f44..379039303 100644 --- a/tools/build_defs/oss/rn_defs.bzl +++ b/tools/build_defs/oss/rn_defs.bzl @@ -83,11 +83,15 @@ def react_native_tests_target(path): def react_native_integration_tests_target(path): return "//ReactAndroid/src/androidTest/" + path -# Helper for referring to non-RN code from RN OSS code. +# Helpers for referring to non-RN code from RN OSS code. # Example: react_native_dep('java/com/facebook/systrace:systrace') def react_native_dep(path): return "//ReactAndroid/src/main/" + path +# Example: react_native_xplat_dep('java/com/facebook/systrace:systrace') +def react_native_xplat_dep(path): + return "//ReactCommon/" + path + # React property preprocessor def rn_android_library(name, deps = [], plugins = [], *args, **kwargs): if react_native_target(