GT2/Ejectable/node_modules/react-native/ReactCommon/jsi/JSCRuntime.cpp

1474 lines
44 KiB
C++

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