1589 lines
49 KiB
C++
1589 lines
49 KiB
C++
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#include "JSCRuntime.h"
|
|
|
|
#include <JavaScriptCore/JavaScript.h>
|
|
#include <jsi/jsilib.h>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <condition_variable>
|
|
#include <cstdlib>
|
|
#include <deque>
|
|
#include <mutex>
|
|
#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;
|
|
|
|
// If we use this interface to implement microtasks in the host we need to
|
|
// polyfill `Promise` to use these methods, because JSC doesn't currently
|
|
// support providing a custom queue for its built-in implementation.
|
|
// Not doing this would result in a non-compliant behavior, as microtasks
|
|
// wouldn't execute in the order in which they were queued.
|
|
void queueMicrotask(const jsi::Function& callback) override;
|
|
bool drainMicrotasks(int maxMicrotasksHint = -1) override;
|
|
|
|
jsi::Object global() override;
|
|
|
|
std::string description() override;
|
|
|
|
bool isInspectable() override;
|
|
|
|
void setDescription(const std::string& desc);
|
|
|
|
// Please don't use the following two functions, only exposed for
|
|
// integration efforts.
|
|
JSGlobalContextRef getContext() {
|
|
return ctx_;
|
|
}
|
|
|
|
// JSValueRef->JSValue (needs make.*Value so it must be member function)
|
|
jsi::Value createValue(JSValueRef value) const;
|
|
|
|
// Value->JSValueRef (similar to above)
|
|
JSValueRef valueRef(const jsi::Value& value);
|
|
|
|
protected:
|
|
friend class detail::ArgsConverter;
|
|
class JSCSymbolValue final : public PointerValue {
|
|
#ifndef NDEBUG
|
|
JSCSymbolValue(
|
|
JSGlobalContextRef ctx,
|
|
const std::atomic<bool>& ctxInvalid,
|
|
JSValueRef sym,
|
|
std::atomic<intptr_t>& counter);
|
|
#else
|
|
JSCSymbolValue(
|
|
JSGlobalContextRef ctx,
|
|
const std::atomic<bool>& ctxInvalid,
|
|
JSValueRef sym);
|
|
#endif
|
|
void invalidate() noexcept 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() noexcept 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() noexcept 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* cloneBigInt(const Runtime::PointerValue* pv) override;
|
|
PointerValue* cloneString(const Runtime::PointerValue* pv) override;
|
|
PointerValue* cloneObject(const Runtime::PointerValue* pv) override;
|
|
PointerValue* clonePropNameID(const Runtime::PointerValue* pv) override;
|
|
|
|
jsi::PropNameID createPropNameIDFromAscii(const char* str, size_t length)
|
|
override;
|
|
jsi::PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, size_t length)
|
|
override;
|
|
jsi::PropNameID createPropNameIDFromString(const jsi::String& str) override;
|
|
jsi::PropNameID createPropNameIDFromSymbol(const jsi::Symbol& sym) override;
|
|
std::string utf8(const jsi::PropNameID&) override;
|
|
bool compare(const jsi::PropNameID&, const jsi::PropNameID&) override;
|
|
|
|
std::string symbolToString(const jsi::Symbol&) override;
|
|
|
|
jsi::BigInt createBigIntFromInt64(int64_t) override;
|
|
jsi::BigInt createBigIntFromUint64(uint64_t) override;
|
|
bool bigintIsInt64(const jsi::BigInt&) override;
|
|
bool bigintIsUint64(const jsi::BigInt&) override;
|
|
uint64_t truncate(const jsi::BigInt&) override;
|
|
jsi::String bigintToString(const jsi::BigInt&, int) override;
|
|
|
|
jsi::String createStringFromAscii(const char* str, size_t length) override;
|
|
jsi::String createStringFromUtf8(const uint8_t* utf8, size_t length) override;
|
|
std::string utf8(const jsi::String&) override;
|
|
|
|
jsi::Object createObject() override;
|
|
jsi::Object createObject(std::shared_ptr<jsi::HostObject> ho) override;
|
|
virtual std::shared_ptr<jsi::HostObject> getHostObject(
|
|
const jsi::Object&) override;
|
|
jsi::HostFunctionType& getHostFunction(const jsi::Function&) override;
|
|
|
|
bool hasNativeState(const jsi::Object&) override;
|
|
std::shared_ptr<jsi::NativeState> getNativeState(const jsi::Object&) override;
|
|
void setNativeState(const jsi::Object&, std::shared_ptr<jsi::NativeState>)
|
|
override;
|
|
|
|
jsi::Value getProperty(const jsi::Object&, const jsi::String& name) override;
|
|
jsi::Value getProperty(const jsi::Object&, const jsi::PropNameID& name)
|
|
override;
|
|
bool hasProperty(const jsi::Object&, const jsi::String& name) override;
|
|
bool hasProperty(const jsi::Object&, const jsi::PropNameID& name) override;
|
|
void setPropertyValue(
|
|
const jsi::Object&,
|
|
const jsi::String& name,
|
|
const jsi::Value& value) override;
|
|
void setPropertyValue(
|
|
const jsi::Object&,
|
|
const jsi::PropNameID& name,
|
|
const jsi::Value& value) override;
|
|
bool isArray(const jsi::Object&) const override;
|
|
bool isArrayBuffer(const jsi::Object&) const override;
|
|
bool isFunction(const jsi::Object&) const override;
|
|
bool isHostObject(const jsi::Object&) const override;
|
|
bool isHostFunction(const jsi::Function&) const override;
|
|
jsi::Array getPropertyNames(const jsi::Object&) override;
|
|
|
|
// TODO: revisit this implementation
|
|
jsi::WeakObject createWeakObject(const jsi::Object&) override;
|
|
jsi::Value lockWeakObject(const jsi::WeakObject&) override;
|
|
|
|
jsi::Array createArray(size_t length) override;
|
|
jsi::ArrayBuffer createArrayBuffer(
|
|
std::shared_ptr<jsi::MutableBuffer> buffer) override;
|
|
size_t size(const jsi::Array&) override;
|
|
size_t size(const jsi::ArrayBuffer&) override;
|
|
uint8_t* data(const jsi::ArrayBuffer&) override;
|
|
jsi::Value getValueAtIndex(const jsi::Array&, size_t i) override;
|
|
void setValueAtIndexImpl(const jsi::Array&, size_t i, const jsi::Value& value)
|
|
override;
|
|
|
|
jsi::Function createFunctionFromHostFunction(
|
|
const jsi::PropNameID& name,
|
|
unsigned int paramCount,
|
|
jsi::HostFunctionType func) override;
|
|
jsi::Value call(
|
|
const jsi::Function&,
|
|
const jsi::Value& jsThis,
|
|
const jsi::Value* args,
|
|
size_t count) override;
|
|
jsi::Value callAsConstructor(
|
|
const jsi::Function&,
|
|
const jsi::Value* args,
|
|
size_t count) override;
|
|
|
|
bool strictEquals(const jsi::Symbol& a, const jsi::Symbol& b) const override;
|
|
bool strictEquals(const jsi::BigInt& a, const jsi::BigInt& b) const override;
|
|
bool strictEquals(const jsi::String& a, const jsi::String& b) const override;
|
|
bool strictEquals(const jsi::Object& a, const jsi::Object& b) const override;
|
|
bool instanceOf(const jsi::Object& o, const jsi::Function& f) override;
|
|
void setExternalMemoryPressure(const jsi::Object&, size_t) 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);
|
|
static JSObjectRef objectRef(const jsi::WeakObject& obj);
|
|
|
|
// 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;
|
|
|
|
JSValueRef getNativeStateSymbol();
|
|
|
|
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_;
|
|
JSValueRef nativeStateSymbol_ = nullptr;
|
|
std::deque<jsi::Function> microtaskQueue_;
|
|
#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)
|
|
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 160400
|
|
#define _JSC_HAS_INSPECTABLE
|
|
#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;
|
|
}
|
|
} // namespace
|
|
|
|
// std::string utility
|
|
namespace {
|
|
std::string to_string(void* value) {
|
|
std::ostringstream ss;
|
|
ss << value;
|
|
return ss.str();
|
|
}
|
|
} // namespace
|
|
|
|
JSCRuntime::JSCRuntime()
|
|
: JSCRuntime(JSGlobalContextCreateInGroup(nullptr, nullptr)) {
|
|
JSGlobalContextRelease(ctx_);
|
|
}
|
|
|
|
JSCRuntime::JSCRuntime(JSGlobalContextRef ctx)
|
|
: ctx_(JSGlobalContextRetain(ctx)),
|
|
ctxInvalid_(false)
|
|
#ifndef NDEBUG
|
|
,
|
|
objectCounter_(0),
|
|
stringCounter_(0)
|
|
#endif
|
|
{
|
|
#ifndef NDEBUG
|
|
#ifdef _JSC_HAS_INSPECTABLE
|
|
#if (__OSX_AVAILABLE_STARTING(MAC_NA, IPHONE_16_4))
|
|
if (__builtin_available(macOS 13.3, iOS 16.4, tvOS 16.4, *)) {
|
|
JSGlobalContextSetInspectable(ctx_, true);
|
|
}
|
|
#endif
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
JSCRuntime::~JSCRuntime() {
|
|
// We need to clear the microtask queue to remove all references to the
|
|
// callbacks, so objectCounter_ would be 0 below.
|
|
microtaskQueue_.clear();
|
|
|
|
// 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;
|
|
// No need to unprotect nativeStateSymbol_ since the heap is getting torn down
|
|
// anyway
|
|
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);
|
|
}
|
|
|
|
void JSCRuntime::queueMicrotask(const jsi::Function& callback) {
|
|
microtaskQueue_.emplace_back(callback.getFunction(*this));
|
|
}
|
|
|
|
bool JSCRuntime::drainMicrotasks(int /*maxMicrotasksHint*/) {
|
|
// Note that new jobs can be enqueued during the draining.
|
|
while (!microtaskQueue_.empty()) {
|
|
jsi::Function callback = std::move(microtaskQueue_.front());
|
|
|
|
// We need to pop before calling the callback because that might throw.
|
|
// When that happens, the host will call `drainMicrotasks` again to execute
|
|
// the remaining microtasks, and this one shouldn't run again.
|
|
microtaskQueue_.pop_front();
|
|
|
|
callback.call(*this);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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(JSValueIsSymbol(ctx_, sym_));
|
|
JSValueProtect(ctx_, sym_);
|
|
#ifndef NDEBUG
|
|
counter_ += 1;
|
|
#endif
|
|
}
|
|
|
|
void JSCRuntime::JSCSymbolValue::invalidate() noexcept {
|
|
#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() noexcept {
|
|
// 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() noexcept {
|
|
#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::cloneBigInt(
|
|
const Runtime::PointerValue*) {
|
|
throw std::logic_error("Not implemented");
|
|
}
|
|
|
|
jsi::Runtime::PointerValue* JSCRuntime::cloneString(
|
|
const jsi::Runtime::PointerValue* pv) {
|
|
if (!pv) {
|
|
return nullptr;
|
|
}
|
|
const JSCStringValue* string = static_cast<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));
|
|
}
|
|
|
|
jsi::PropNameID JSCRuntime::createPropNameIDFromSymbol(const jsi::Symbol& sym) {
|
|
// TODO(T204185517): Support for symbols through the native API in JSC is very
|
|
// limited. While we could construct a PropNameID here, we would not be able
|
|
// to get a symbol property through the C++ API.
|
|
throw std::logic_error("Not implemented");
|
|
}
|
|
|
|
std::string JSCRuntime::utf8(const jsi::PropNameID& sym) {
|
|
return JSStringToSTLString(stringRef(sym));
|
|
}
|
|
|
|
bool JSCRuntime::compare(const jsi::PropNameID& a, const jsi::PropNameID& b) {
|
|
return JSStringIsEqual(stringRef(a), stringRef(b));
|
|
}
|
|
|
|
std::string JSCRuntime::symbolToString(const jsi::Symbol& sym) {
|
|
return jsi::Value(*this, sym).toString(*this).utf8(*this);
|
|
}
|
|
|
|
jsi::BigInt JSCRuntime::createBigIntFromInt64(int64_t) {
|
|
throw std::logic_error("Not implemented");
|
|
}
|
|
|
|
jsi::BigInt JSCRuntime::createBigIntFromUint64(uint64_t) {
|
|
throw std::logic_error("Not implemented");
|
|
}
|
|
|
|
bool JSCRuntime::bigintIsInt64(const jsi::BigInt&) {
|
|
throw std::logic_error("Not implemented");
|
|
}
|
|
|
|
bool JSCRuntime::bigintIsUint64(const jsi::BigInt&) {
|
|
throw std::logic_error("Not implemented");
|
|
}
|
|
|
|
uint64_t JSCRuntime::truncate(const jsi::BigInt&) {
|
|
throw std::logic_error("Not implemented");
|
|
}
|
|
|
|
jsi::String JSCRuntime::bigintToString(const jsi::BigInt&, int) {
|
|
throw std::logic_error("Not implemented");
|
|
}
|
|
|
|
jsi::String JSCRuntime::createStringFromAscii(const char* str, size_t length) {
|
|
// Yes we end up double casting for semantic reasons (UTF8 contains ASCII,
|
|
// not the other way around)
|
|
return this->createStringFromUtf8(
|
|
reinterpret_cast<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 {
|
|
struct HostObjectProxyBase {
|
|
HostObjectProxyBase(
|
|
JSCRuntime& rt,
|
|
const std::shared_ptr<jsi::HostObject>& sho)
|
|
: runtime(rt), hostObject(sho) {}
|
|
|
|
JSCRuntime& runtime;
|
|
std::shared_ptr<jsi::HostObject> hostObject;
|
|
};
|
|
|
|
std::once_flag hostObjectClassOnceFlag;
|
|
JSClassRef hostObjectClass{};
|
|
} // namespace
|
|
|
|
jsi::Object JSCRuntime::createObject(std::shared_ptr<jsi::HostObject> ho) {
|
|
struct HostObjectProxy : public 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<HostObjectProxyBase*>(JSObjectGetPrivate(object));
|
|
assert(metadata);
|
|
return metadata->hostObject;
|
|
}
|
|
|
|
// NativeState details
|
|
namespace {
|
|
struct NativeStateContainer {
|
|
NativeStateContainer(std::shared_ptr<jsi::NativeState> state)
|
|
: nativeState(std::move(state)) {}
|
|
|
|
std::shared_ptr<jsi::NativeState> nativeState;
|
|
|
|
static void finalize(JSObjectRef obj) {
|
|
auto container =
|
|
static_cast<NativeStateContainer*>(JSObjectGetPrivate(obj));
|
|
delete container;
|
|
}
|
|
};
|
|
|
|
JSClassRef getNativeStateClass() {
|
|
static JSClassRef nativeStateClass = [] {
|
|
JSClassDefinition nativeStateClassDef = kJSClassDefinitionEmpty;
|
|
nativeStateClassDef.version = 0;
|
|
nativeStateClassDef.attributes = kJSClassAttributeNoAutomaticPrototype;
|
|
nativeStateClassDef.finalize = NativeStateContainer::finalize;
|
|
return JSClassCreate(&nativeStateClassDef);
|
|
}();
|
|
return nativeStateClass;
|
|
}
|
|
} // namespace
|
|
|
|
JSValueRef JSCRuntime::getNativeStateSymbol() {
|
|
if (!nativeStateSymbol_) {
|
|
JSStringRef symbolName =
|
|
JSStringCreateWithUTF8CString("__internal_nativeState");
|
|
JSValueRef symbol = JSValueMakeSymbol(ctx_, symbolName);
|
|
JSValueProtect(ctx_, symbol);
|
|
nativeStateSymbol_ = symbol;
|
|
JSStringRelease(symbolName);
|
|
}
|
|
return nativeStateSymbol_;
|
|
}
|
|
|
|
bool JSCRuntime::hasNativeState(const jsi::Object& obj) {
|
|
JSValueRef exc = nullptr;
|
|
JSValueRef state = JSObjectGetPropertyForKey(
|
|
ctx_, objectRef(obj), getNativeStateSymbol(), &exc);
|
|
checkException(exc);
|
|
|
|
return JSValueIsObjectOfClass(ctx_, state, getNativeStateClass());
|
|
}
|
|
|
|
std::shared_ptr<jsi::NativeState> JSCRuntime::getNativeState(
|
|
const jsi::Object& obj) {
|
|
JSValueRef exc = nullptr;
|
|
JSValueRef state = JSObjectGetPropertyForKey(
|
|
ctx_, objectRef(obj), getNativeStateSymbol(), &exc);
|
|
checkException(exc);
|
|
|
|
JSObjectRef stateObj = JSValueToObject(ctx_, state, &exc);
|
|
checkException(exc);
|
|
|
|
auto container =
|
|
static_cast<NativeStateContainer*>(JSObjectGetPrivate(stateObj));
|
|
assert(container);
|
|
return container->nativeState;
|
|
}
|
|
|
|
void JSCRuntime::setNativeState(
|
|
const jsi::Object& obj,
|
|
std::shared_ptr<jsi::NativeState> nativeState) {
|
|
JSValueRef nativeStateSymbol = getNativeStateSymbol();
|
|
|
|
JSValueRef exc = nullptr;
|
|
JSValueRef state =
|
|
JSObjectGetPropertyForKey(ctx_, objectRef(obj), nativeStateSymbol, &exc);
|
|
checkException(exc);
|
|
if (JSValueIsUndefined(ctx_, state)) {
|
|
JSObjectRef stateObj = JSObjectMake(
|
|
ctx_,
|
|
getNativeStateClass(),
|
|
new NativeStateContainer(std::move(nativeState)));
|
|
JSObjectSetPropertyForKey(
|
|
ctx_,
|
|
objectRef(obj),
|
|
nativeStateSymbol,
|
|
stateObj,
|
|
kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum |
|
|
kJSPropertyAttributeDontDelete,
|
|
&exc);
|
|
checkException(exc);
|
|
} else {
|
|
JSObjectRef stateObj = JSValueToObject(ctx_, state, &exc);
|
|
checkException(exc);
|
|
|
|
auto container =
|
|
static_cast<NativeStateContainer*>(JSObjectGetPrivate(stateObj));
|
|
assert(container);
|
|
container->nativeState = std::move(nativeState);
|
|
}
|
|
}
|
|
|
|
jsi::Value JSCRuntime::getProperty(
|
|
const jsi::Object& obj,
|
|
const jsi::String& name) {
|
|
JSObjectRef objRef = objectRef(obj);
|
|
JSValueRef exc = nullptr;
|
|
JSValueRef res = JSObjectGetProperty(ctx_, objRef, stringRef(name), &exc);
|
|
checkException(exc);
|
|
return createValue(res);
|
|
}
|
|
|
|
jsi::Value JSCRuntime::getProperty(
|
|
const jsi::Object& obj,
|
|
const jsi::PropNameID& name) {
|
|
JSObjectRef objRef = objectRef(obj);
|
|
JSValueRef exc = nullptr;
|
|
JSValueRef res = JSObjectGetProperty(ctx_, objRef, stringRef(name), &exc);
|
|
checkException(exc);
|
|
return createValue(res);
|
|
}
|
|
|
|
bool JSCRuntime::hasProperty(const jsi::Object& obj, const jsi::String& name) {
|
|
JSObjectRef objRef = objectRef(obj);
|
|
return JSObjectHasProperty(ctx_, objRef, stringRef(name));
|
|
}
|
|
|
|
bool JSCRuntime::hasProperty(
|
|
const jsi::Object& obj,
|
|
const jsi::PropNameID& name) {
|
|
JSObjectRef objRef = objectRef(obj);
|
|
return JSObjectHasProperty(ctx_, objRef, stringRef(name));
|
|
}
|
|
|
|
void JSCRuntime::setPropertyValue(
|
|
const jsi::Object& object,
|
|
const jsi::PropNameID& name,
|
|
const jsi::Value& value) {
|
|
JSValueRef exc = nullptr;
|
|
JSObjectSetProperty(
|
|
ctx_,
|
|
objectRef(object),
|
|
stringRef(name),
|
|
valueRef(value),
|
|
kJSPropertyAttributeNone,
|
|
&exc);
|
|
checkException(exc);
|
|
}
|
|
|
|
void JSCRuntime::setPropertyValue(
|
|
const jsi::Object& object,
|
|
const jsi::String& name,
|
|
const jsi::Value& value) {
|
|
JSValueRef exc = nullptr;
|
|
JSObjectSetProperty(
|
|
ctx_,
|
|
objectRef(object),
|
|
stringRef(name),
|
|
valueRef(value),
|
|
kJSPropertyAttributeNone,
|
|
&exc);
|
|
checkException(exc);
|
|
}
|
|
|
|
bool JSCRuntime::isArray(const jsi::Object& obj) const {
|
|
return JSValueIsArray(ctx_, objectRef(obj));
|
|
}
|
|
|
|
bool JSCRuntime::isArrayBuffer(const jsi::Object& obj) const {
|
|
auto typedArrayType = JSValueGetTypedArrayType(ctx_, objectRef(obj), nullptr);
|
|
return typedArrayType == kJSTypedArrayTypeArrayBuffer;
|
|
}
|
|
|
|
uint8_t* JSCRuntime::data(const jsi::ArrayBuffer& obj) {
|
|
return static_cast<uint8_t*>(
|
|
JSObjectGetArrayBufferBytesPtr(ctx_, objectRef(obj), nullptr));
|
|
}
|
|
|
|
size_t JSCRuntime::size(const jsi::ArrayBuffer& obj) {
|
|
return JSObjectGetArrayBufferByteLength(ctx_, objectRef(obj), nullptr);
|
|
}
|
|
|
|
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) {
|
|
// TODO: revisit this implementation
|
|
JSObjectRef objRef = objectRef(obj);
|
|
return make<jsi::WeakObject>(makeObjectValue(objRef));
|
|
}
|
|
|
|
jsi::Value JSCRuntime::lockWeakObject(const jsi::WeakObject& obj) {
|
|
// TODO: revisit this implementation
|
|
JSObjectRef objRef = objectRef(obj);
|
|
return jsi::Value(createObject(objRef));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
jsi::ArrayBuffer JSCRuntime::createArrayBuffer(
|
|
std::shared_ptr<jsi::MutableBuffer> buffer) {
|
|
throw std::logic_error("Not implemented");
|
|
}
|
|
|
|
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(
|
|
const jsi::Array& arr,
|
|
size_t i,
|
|
const jsi::Value& value) {
|
|
JSValueRef exc = nullptr;
|
|
JSObjectSetPropertyAtIndex(
|
|
ctx_, objectRef(arr), (int)i, valueRef(value), &exc);
|
|
checkException(exc);
|
|
}
|
|
|
|
namespace {
|
|
std::once_flag hostFunctionClassOnceFlag;
|
|
JSClassRef hostFunctionClass{};
|
|
|
|
class HostFunctionProxy {
|
|
public:
|
|
HostFunctionProxy(jsi::HostFunctionType hostFunction)
|
|
: hostFunction_(hostFunction) {}
|
|
|
|
jsi::HostFunctionType& getHostFunction() {
|
|
return hostFunction_;
|
|
}
|
|
|
|
protected:
|
|
jsi::HostFunctionType hostFunction_;
|
|
};
|
|
} // namespace
|
|
|
|
jsi::Function JSCRuntime::createFunctionFromHostFunction(
|
|
const jsi::PropNameID& name,
|
|
unsigned int paramCount,
|
|
jsi::HostFunctionType func) {
|
|
class HostFunctionMetadata : public HostFunctionProxy {
|
|
public:
|
|
static void initialize(JSContextRef ctx, JSObjectRef object) {
|
|
// We need to set up the prototype chain properly here. In theory we
|
|
// could set func.prototype.prototype = Function.prototype to get the
|
|
// same result. Not sure which approach is better.
|
|
HostFunctionMetadata* metadata =
|
|
static_cast<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 (const std::string& ex) {
|
|
std::string exceptionString("Exception in HostFunction: ");
|
|
exceptionString += ex;
|
|
*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::BigInt& a, const jsi::BigInt& b)
|
|
const {
|
|
throw std::logic_error("Not implemented");
|
|
}
|
|
|
|
bool JSCRuntime::strictEquals(const jsi::String& a, const jsi::String& b)
|
|
const {
|
|
return JSStringIsEqual(stringRef(a), stringRef(b));
|
|
}
|
|
|
|
bool JSCRuntime::strictEquals(const jsi::Object& a, const jsi::Object& b)
|
|
const {
|
|
return objectRef(a) == objectRef(b);
|
|
}
|
|
|
|
bool JSCRuntime::instanceOf(const jsi::Object& o, const jsi::Function& f) {
|
|
JSValueRef exc = nullptr;
|
|
bool res =
|
|
JSValueIsInstanceOfConstructor(ctx_, objectRef(o), objectRef(f), &exc);
|
|
checkException(exc);
|
|
return res;
|
|
}
|
|
|
|
void JSCRuntime::setExternalMemoryPressure(const jsi::Object&, size_t) {}
|
|
|
|
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));
|
|
}
|
|
case kJSTypeSymbol:
|
|
return jsi::Value(createSymbol(value));
|
|
default:
|
|
// 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_;
|
|
}
|
|
|
|
JSObjectRef JSCRuntime::objectRef(const jsi::WeakObject& obj) {
|
|
// TODO: revisit this implementation
|
|
return static_cast<const JSCObjectValue*>(getPointerValue(obj))->obj_;
|
|
}
|
|
|
|
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
|