Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JSI version 11 #7

Merged
merged 1 commit into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions jsi/jsi/JSIDynamic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,10 @@ void dynamicFromValueShallow(

} // namespace

folly::dynamic dynamicFromValue(Runtime& runtime, const Value& valueInput) {
folly::dynamic dynamicFromValue(
Runtime& runtime,
const Value& valueInput,
std::function<bool(const std::string&)> filterObjectKeys) {
std::vector<FromValue> stack;
folly::dynamic ret;

Expand Down Expand Up @@ -184,13 +187,17 @@ folly::dynamic dynamicFromValue(Runtime& runtime, const Value& valueInput) {
if (prop.isUndefined()) {
continue;
}
auto nameStr = name.utf8(runtime);
if (filterObjectKeys && filterObjectKeys(nameStr)) {
continue;
}
// The JSC conversion uses JSON.stringify, which substitutes
// null for a function, so we do the same here. Just dropping
// the pair might also work, but would require more testing.
if (prop.isObject() && prop.getObject(runtime).isFunction(runtime)) {
prop = Value::null();
}
props.emplace_back(name.utf8(runtime), std::move(prop));
props.emplace_back(std::move(nameStr), std::move(prop));
top.dyn->insert(props.back().first, nullptr);
}
for (const auto& prop : props) {
Expand Down
3 changes: 2 additions & 1 deletion jsi/jsi/JSIDynamic.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ facebook::jsi::Value valueFromDynamic(

folly::dynamic dynamicFromValue(
facebook::jsi::Runtime& runtime,
const facebook::jsi::Value& value);
const facebook::jsi::Value& value,
std::function<bool(const std::string&)> filterObjectKeys = nullptr);

} // namespace jsi
} // namespace facebook
5 changes: 3 additions & 2 deletions jsi/jsi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ It is being used by React Native project to work with JS engines.
JSI has versions associated with the following commit hashes in the
https://github.com/facebook/hermes repo.

| Version | Commit Hash | Commit Description
|--------:|:-----------------------------------------|------------------------------------------------------
| Version | Commit Hash | Commit Description
|--------:|:-------------------------------------------|------------------------------------------------------
| 11 | `a1c168705f609c8f1ae800c60d88eb199154264b` | Add JSI method for setting external memory size
| 10 | `b81666598672cb5f8b365fe6548d3273f216322e` | Clarify const-ness of JSI references
| 9 | `e6d887ae96bef5c71032f11ed1a9fb9fecec7b46` | Add external ArrayBuffers to JSI
| 8 | `4d64e61a1f9926eca0afd4eb38d17cea30bdc34c` | Add BigInt JSI API support
Expand Down
6 changes: 6 additions & 0 deletions jsi/jsi/decorator.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,12 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation {
}
#endif

#if JSI_VERSION >= 11
void setExternalMemoryPressure(const Object& obj, size_t amt) override {
plain_.setExternalMemoryPressure(obj, amt);
}
#endif

Value getProperty(const Object& o, const PropNameID& name) override {
return plain_.getProperty(o, name);
};
Expand Down
7 changes: 7 additions & 0 deletions jsi/jsi/jsi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,13 @@ inline void Object::setNativeState(
}
#endif

#if JSI_VERSION >= 11
inline void Object::setExternalMemoryPressure(Runtime& runtime, size_t amt)
const {
runtime.setExternalMemoryPressure(*this, amt);
}
#endif

inline Array Object::getPropertyNames(Runtime& runtime) const {
return runtime.getPropertyNames(*this);
}
Expand Down
6 changes: 6 additions & 0 deletions jsi/jsi/jsi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,12 @@ JSError::JSError(std::string what, Runtime& rt, Value&& value)
setValue(rt, std::move(value));
}

JSError::JSError(Value&& value, std::string message, std::string stack)
: JSIException(message + "\n\n" + stack),
value_(std::make_shared<Value>(std::move(value))),
message_(std::move(message)),
stack_(std::move(stack)) {}

void JSError::setValue(Runtime& rt, Value&& value) {
value_ = std::make_shared<Value>(std::move(value));

Expand Down
56 changes: 42 additions & 14 deletions jsi/jsi/jsi.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
// JSI version defines set of features available in the API.
// Each significant API change must be under a new version.
#ifndef JSI_VERSION
#define JSI_VERSION 10
#define JSI_VERSION 11
#endif

#if JSI_VERSION >= 3
Expand Down Expand Up @@ -431,6 +431,13 @@ class JSI_EXPORT Runtime {

virtual bool instanceOf(const Object& o, const Function& f) = 0;

#if JSI_VERSION >= 11
/// See Object::setExternalMemoryPressure.
virtual void setExternalMemoryPressure(
const jsi::Object& obj,
size_t amount) = 0;
#endif

// These exist so derived classes can access the private parts of
// Value, Symbol, String, and Object, which are all friends of Runtime.
template <typename T>
Expand Down Expand Up @@ -890,6 +897,18 @@ class JSI_EXPORT Object : public Pointer {
/// works. I only need it in one place.)
Array getPropertyNames(Runtime& runtime) const;

#if JSI_VERSION >= 11
/// Inform the runtime that there is additional memory associated with a given
/// JavaScript object that is not visible to the GC. This can be used if an
/// object is known to retain some native memory, and may be used to guide
/// decisions about when to run garbage collection.
/// This method may be invoked multiple times on an object, and subsequent
/// calls will overwrite any previously set value. Once the object is garbage
/// collected, the associated external memory will be considered freed and may
/// no longer factor into GC decisions.
void setExternalMemoryPressure(Runtime& runtime, size_t amt) const;
#endif

protected:
void setPropertyValue(
Runtime& runtime,
Expand Down Expand Up @@ -980,6 +999,7 @@ class JSI_EXPORT Array : public Object {
private:
friend class Object;
friend class Value;
friend class Runtime;

void setValueAtIndexImpl(Runtime& runtime, size_t i, const Value& value)
JSI_CONST_10 {
Expand All @@ -1000,7 +1020,8 @@ class JSI_EXPORT ArrayBuffer : public Object {
: ArrayBuffer(runtime.createArrayBuffer(std::move(buffer))) {}
#endif

/// \return the size of the ArrayBuffer, according to its byteLength property.
/// \return the size of the ArrayBuffer storage. This is not affected by
/// overriding the byteLength property.
/// (C++ naming convention)
size_t size(Runtime& runtime) const {
return runtime.size(*this);
Expand All @@ -1017,6 +1038,7 @@ class JSI_EXPORT ArrayBuffer : public Object {
private:
friend class Object;
friend class Value;
friend class Runtime;

ArrayBuffer(Runtime::PointerValue* value) : Object(value) {}
};
Expand Down Expand Up @@ -1125,6 +1147,7 @@ class JSI_EXPORT Function : public Object {
private:
friend class Object;
friend class Value;
friend class Runtime;

Function(Runtime::PointerValue* value) : Object(value) {}
};
Expand Down Expand Up @@ -1156,16 +1179,16 @@ class JSI_EXPORT Value {
}

/// Moves a Symbol, String, or Object rvalue into a new JS value.
template <typename T>
/* implicit */ Value(T&& other) : Value(kindOf(other)) {
static_assert(
std::is_base_of<Symbol, T>::value ||
template <
typename T,
typename = std::enable_if_t<
std::is_base_of<Symbol, T>::value ||
#if JSI_VERSION >= 6
std::is_base_of<BigInt, T>::value ||
std::is_base_of<BigInt, T>::value ||
#endif
std::is_base_of<String, T>::value ||
std::is_base_of<Object, T>::value,
"Value cannot be implicitly move-constructed from this type");
std::is_base_of<String, T>::value ||
std::is_base_of<Object, T>::value>>
/* implicit */ Value(T&& other) : Value(kindOf(other)) {
new (&data_.pointer) T(std::move(other));
}

Expand Down Expand Up @@ -1464,7 +1487,7 @@ class JSI_EXPORT Scope {
explicit Scope(Runtime& rt) : rt_(rt), prv_(rt.pushScope()) {}
~Scope() {
rt_.popScope(prv_);
};
}

Scope(const Scope&) = delete;
Scope(Scope&&) = delete;
Expand All @@ -1486,8 +1509,8 @@ class JSI_EXPORT Scope {
/// Base class for jsi exceptions
class JSI_EXPORT JSIException : public std::exception {
protected:
JSIException(){};
JSIException(std::string what) : what_(std::move(what)){};
JSIException() {}
JSIException(std::string what) : what_(std::move(what)) {}

public:
JSIException(const JSIException&) = default;
Expand Down Expand Up @@ -1528,7 +1551,7 @@ class JSI_EXPORT JSError : public JSIException {
/// Creates a JSError referring to new \c Error instance capturing current
/// JavaScript stack. The error message property is set to given \c message.
JSError(Runtime& rt, const char* message)
: JSError(rt, std::string(message)){};
: JSError(rt, std::string(message)) {}

/// Creates a JSError referring to a JavaScript Object having message and
/// stack properties set to provided values.
Expand All @@ -1539,6 +1562,11 @@ class JSI_EXPORT JSError : public JSIException {
/// but necessary to avoid ambiguity with the above.
JSError(std::string what, Runtime& rt, Value&& value);

/// Creates a JSError referring to the provided value, message and stack. This
/// constructor does not take a Runtime parameter, and therefore cannot result
/// in recursively invoking the JSError constructor.
JSError(Value&& value, std::string message, std::string stack);

JSError(const JSError&) = default;

virtual ~JSError();
Expand Down
130 changes: 125 additions & 5 deletions jsi/jsi/test/testlib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ TEST_P(JSITest, PropNameIDTest) {
rt, movedQuux, PropNameID::forAscii(rt, std::string("foo"))));
uint8_t utf8[] = {0xF0, 0x9F, 0x86, 0x97};
PropNameID utf8PropNameID = PropNameID::forUtf8(rt, utf8, sizeof(utf8));
// See about char8_t conversion: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1423r2.html
EXPECT_EQ(utf8PropNameID.utf8(rt), reinterpret_cast<const char *>(u8"\U0001F197"));
EXPECT_EQ(
utf8PropNameID.utf8(rt), reinterpret_cast<const char*>(u8"\U0001F197"));
EXPECT_TRUE(PropNameID::compare(
rt, utf8PropNameID, PropNameID::forUtf8(rt, utf8, sizeof(utf8))));
PropNameID nonUtf8PropNameID = PropNameID::forUtf8(rt, "meow");
Expand Down Expand Up @@ -479,6 +479,20 @@ TEST_P(JSITest, ArrayTest) {
Array alpha2 = Array(rt, 1);
alpha2 = std::move(alpha);
EXPECT_EQ(alpha2.size(rt), 4);

// Test getting/setting an element that is an accessor.
// TODO: Make it pass for Hermes and V8
// auto arrWithAccessor =
// eval(
// "Object.defineProperty([], '0', {set(){ throw 72 }, get(){ return
// 45 }});") .getObject(rt) .getArray(rt);
// try {
// arrWithAccessor.setValueAtIndex(rt, 0, 1);
// FAIL() << "Expected exception";
// } catch (const JSError& err) {
// EXPECT_EQ(err.value().getNumber(), 72);
// }
// EXPECT_EQ(arrWithAccessor.getValueAtIndex(rt, 0).getNumber(), 45);
}

TEST_P(JSITest, FunctionTest) {
Expand Down Expand Up @@ -741,7 +755,7 @@ TEST_P(JSITest, HostFunctionTest) {
.utf8(rt),
"A cat was called with std::function::target");
EXPECT_TRUE(callable.isHostFunction(rt));
EXPECT_NE(callable.getHostFunction(rt).target<Callable>(), nullptr);
EXPECT_TRUE(callable.getHostFunction(rt).target<Callable>() != nullptr);

std::string strval = "strval1";
auto getter = Object(rt);
Expand Down Expand Up @@ -1232,7 +1246,7 @@ TEST_P(JSITest, MultiDecoratorTest) {
0,
[](Runtime& rt, const Value& thisVal, const Value* args, size_t count) {
MultiRuntime* funcmrt = dynamic_cast<MultiRuntime*>(&rt);
EXPECT_NE(funcmrt, nullptr);
EXPECT_TRUE(funcmrt != nullptr);
EXPECT_EQ(funcmrt->count(), 3);
EXPECT_EQ(funcmrt->nest(), 1);
return Value::undefined();
Expand Down Expand Up @@ -1426,7 +1440,113 @@ TEST_P(JSITest, MultilevelDecoratedHostObject) {
EXPECT_EQ(1, RD2::numGets);
}

INSTANTIATE_TEST_SUITE_P(
TEST_P(JSITest, ArrayBufferSizeTest) {
auto ab =
eval("var x = new ArrayBuffer(10); x").getObject(rt).getArrayBuffer(rt);
EXPECT_EQ(ab.size(rt), 10);

try {
// Ensure we can safely write some data to the buffer.
memset(ab.data(rt), 0xab, 10);
} catch (const JSINativeException& ex) {
// data() is unimplemented by some runtimes, ignore such failures.
}

// Ensure that setting the byteLength property does not change the length.
eval("Object.defineProperty(x, 'byteLength', {value: 20})");
EXPECT_EQ(ab.size(rt), 10);
}

namespace {

struct IntState : public NativeState {
explicit IntState(int value) : value(value) {}
int value;
};

} // namespace

TEST_P(JSITest, NativeState) {
Object holder(rt);
EXPECT_FALSE(holder.hasNativeState(rt));

auto stateValue = std::make_shared<IntState>(42);
holder.setNativeState(rt, stateValue);
EXPECT_TRUE(holder.hasNativeState(rt));
EXPECT_EQ(
std::dynamic_pointer_cast<IntState>(holder.getNativeState(rt))->value,
42);

stateValue = std::make_shared<IntState>(21);
holder.setNativeState(rt, stateValue);
EXPECT_TRUE(holder.hasNativeState(rt));
EXPECT_EQ(
std::dynamic_pointer_cast<IntState>(holder.getNativeState(rt))->value,
21);

// There's currently way to "delete" the native state of a component fully
// Even when reset with nullptr, hasNativeState will still return true
// TODO: Make it pass for Hermes and V8
// holder.setNativeState(rt, nullptr);
// EXPECT_TRUE(holder.hasNativeState(rt));
// EXPECT_TRUE(holder.getNativeState(rt) == nullptr);
}

// TODO: Make it pass on Hermes
// TEST_P(JSITest, NativeStateSymbolOverrides) {
// Object holder(rt);

// auto stateValue = std::make_shared<IntState>(42);
// holder.setNativeState(rt, stateValue);

// // Attempting to change configurable attribute of unconfigurable property
// try {
// function(
// "function (obj) {"
// " var mySymbol = Symbol();"
// " obj[mySymbol] = 'foo';"
// " var allSymbols = Object.getOwnPropertySymbols(obj);"
// " for (var sym of allSymbols) {"
// " Object.defineProperty(obj, sym, {configurable: true, writable:
// true});" " obj[sym] = 'bar';" " }"
// "}")
// .call(rt, holder);
// } catch (const JSError& ex) {
// // On JSC this throws, but it doesn't on Hermes
// std::string exc = ex.what();
// EXPECT_NE(
// exc.find(
// "Attempting to change configurable attribute of unconfigurable
// property"),
// std::string::npos);
// }

// EXPECT_TRUE(holder.hasNativeState(rt));
// EXPECT_EQ(
// std::dynamic_pointer_cast<IntState>(holder.getNativeState(rt))->value,
// 42);
// }

TEST_P(JSITest, UTF8ExceptionTest) {
// Test that a native exception containing UTF-8 characters is correctly
// passed through.
Function throwUtf8 = Function::createFromHostFunction(
rt,
PropNameID::forAscii(rt, "throwUtf8"),
1,
[](Runtime& rt, const Value&, const Value* args, size_t) -> Value {
throw JSINativeException(args[0].asString(rt).utf8(rt));
});
std::string utf8 = "👍";
try {
throwUtf8.call(rt, utf8);
FAIL();
} catch (const JSError& e) {
EXPECT_NE(e.getMessage().find(utf8), std::string::npos);
}
}

INSTANTIATE_TEST_CASE_P(
Runtimes,
JSITest,
::testing::ValuesIn(runtimeGenerators()));
Loading