Skip to content
Open
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
12 changes: 12 additions & 0 deletions API/hermes/TracingRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,18 @@ jsi::Value TracingRuntime::createValueFromJsonUtf8(
return jsi::Runtime::createValueFromJsonUtf8(json, length);
}

jsi::Value TracingRuntime::createValueFromJsonASCII(
const char *json,
size_t length) {
return jsi::Runtime::createValueFromJsonASCII(json, length);
}

jsi::Value TracingRuntime::createValueFromJsonUtf16(
const char16_t *json,
size_t length) {
return jsi::Runtime::createValueFromJsonUtf16(json, length);
}

jsi::PropNameID TracingRuntime::createPropNameIDFromAscii(
const char *str,
size_t length) {
Expand Down
5 changes: 5 additions & 0 deletions API/hermes/TracingRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ class TracingRuntime : public jsi::RuntimeDecorator<jsi::Runtime>
/// needing a dedicated record type.
jsi::Value createValueFromJsonUtf8(const uint8_t *json, size_t length)
override;
/// Use the same recorded JSON.parse fallback as the UTF-8 entry point.
jsi::Value createValueFromJsonASCII(const char *json, size_t length) override;
/// Use the same recorded JSON.parse fallback, preserving UTF-16 code units.
jsi::Value createValueFromJsonUtf16(const char16_t *json, size_t length)
override;
std::string utf8(const jsi::PropNameID &) override;

jsi::PropNameID createPropNameIDFromAscii(const char *str, size_t length)
Expand Down
23 changes: 23 additions & 0 deletions API/hermes/hermes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,9 @@ class HermesRuntimeImpl final : public HermesRuntime,

jsi::Value createValueFromJsonUtf8(const uint8_t *json, size_t length)
override;
jsi::Value createValueFromJsonASCII(const char *json, size_t length) override;
jsi::Value createValueFromJsonUtf16(const char16_t *json, size_t length)
override;

jsi::Object createObject() override;
jsi::Object createObject(std::shared_ptr<jsi::HostObject> ho) override;
Expand Down Expand Up @@ -2653,6 +2656,26 @@ jsi::Value HermesRuntimeImpl::createValueFromJsonUtf8(
return valueFromHermesValue(*res);
}

jsi::Value HermesRuntimeImpl::createValueFromJsonASCII(
const char *json,
size_t length) {
ExecutionScopeRAII scopeRAII(mutatorScope);
vm::GCScope gcScope(runtime_);
auto res = runtimeJSONParseRef(runtime_, vm::ASCIIRef(json, length));
checkStatus(res.getStatus());
return valueFromHermesValue(*res);
}

jsi::Value HermesRuntimeImpl::createValueFromJsonUtf16(
const char16_t *json,
size_t length) {
ExecutionScopeRAII scopeRAII(mutatorScope);
vm::GCScope gcScope(runtime_);
auto res = runtimeJSONParseRef(runtime_, vm::UTF16Ref(json, length));
checkStatus(res.getStatus());
return valueFromHermesValue(*res);
}

jsi::Object HermesRuntimeImpl::createObject() {
ExecutionScopeRAII scopeRAII(mutatorScope);
vm::GCScope gcScope(runtime_);
Expand Down
16 changes: 16 additions & 0 deletions API/jsi/jsi/decorator.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation {
Value createValueFromJsonUtf8(const uint8_t* json, size_t length) override {
return plain_.createValueFromJsonUtf8(json, length);
}
Value createValueFromJsonASCII(const char* json, size_t length) override {
return plain_.createValueFromJsonASCII(json, length);
}
Value createValueFromJsonUtf16(const char16_t* json, size_t length) override {
return plain_.createValueFromJsonUtf16(json, length);
}

Value createError(const String& msg) override {
return plain().createError(msg);
Expand Down Expand Up @@ -908,6 +914,16 @@ class WithRuntimeDecorator : public RuntimeDecorator<Plain, Base> {
return RD::createValueFromJsonUtf8(json, length);
}

Value createValueFromJsonASCII(const char* json, size_t length) override {
Around around{with_};
return RD::createValueFromJsonASCII(json, length);
}

Value createValueFromJsonUtf16(const char16_t* json, size_t length) override {
Around around{with_};
return RD::createValueFromJsonUtf16(json, length);
}

Object createObjectWithPrototype(const Value& prototype) override {
Around around{with_};
return RD::createObjectWithPrototype(prototype);
Expand Down
14 changes: 14 additions & 0 deletions API/jsi/jsi/jsi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,20 @@ Value Runtime::createValueFromJsonUtf8(const uint8_t* json, size_t length) {
return parseJson.call(*this, String::createFromUtf8(*this, json, length));
}

Value Runtime::createValueFromJsonASCII(const char* json, size_t length) {
Function parseJson = global()
.getPropertyAsObject(*this, "JSON")
.getPropertyAsFunction(*this, "parse");
return parseJson.call(*this, String::createFromAscii(*this, json, length));
}

Value Runtime::createValueFromJsonUtf16(const char16_t* json, size_t length) {
Function parseJson = global()
.getPropertyAsObject(*this, "JSON")
.getPropertyAsFunction(*this, "parse");
return parseJson.call(*this, String::createFromUtf16(*this, json, length));
}

String Runtime::createStringFromUtf16(const char16_t* utf16, size_t length) {
if (isAllASCII(utf16, length)) {
std::string buffer(utf16, utf16 + length);
Expand Down
37 changes: 37 additions & 0 deletions API/jsi/jsi/jsi.h
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,27 @@ class JSI_EXPORT IRuntime : public ICast {
// implementation creates a \c String and invokes JSON.parse.
virtual Value createValueFromJsonUtf8(const uint8_t* json, size_t length) = 0;

/// Parse exactly \p length bytes of ASCII JSON from \p json, returning any
/// JSON value, including primitives. All bytes must be in [0, 127]; behavior
/// is undefined otherwise. The non-null buffer need not be NUL-terminated
/// and must remain readable and unchanged until this synchronous call
/// returns. No reference to it is retained. Invalid JSON throws JSError.
/// Like other IRuntime methods, this requires synchronized runtime access.
/// The default implementation creates a String and invokes JSON.parse.
virtual Value createValueFromJsonASCII(const char* json, size_t length) = 0;

/// Parse exactly \p length UTF-16 code units of JSON from \p json, returning
/// any JSON value, including primitives. The non-null buffer need not be
/// NUL-terminated and must remain readable and unchanged until this
/// synchronous call returns. No reference to it is retained. Lone surrogate
/// code units inside JSON strings are preserved. NUL is part of the input,
/// and a leading BOM or invalid JSON throws JSError. Like other IRuntime
/// methods, this requires synchronized runtime access. Parsing can allocate
/// and execute JavaScript in the default implementation, which creates a
/// String and invokes JSON.parse.
virtual Value createValueFromJsonUtf16(
const char16_t* json, size_t length) = 0;

virtual Object createObject() = 0;
virtual Object createObject(std::shared_ptr<HostObject> ho) = 0;
virtual std::shared_ptr<HostObject> getHostObject(const jsi::Object&) = 0;
Expand Down Expand Up @@ -743,6 +764,8 @@ class JSI_EXPORT Runtime : public IRuntime {
String createStringFromUtf16(const char16_t* utf16, size_t length) override;

Value createValueFromJsonUtf8(const uint8_t* json, size_t length) override;
Value createValueFromJsonASCII(const char* json, size_t length) override;
Value createValueFromJsonUtf16(const char16_t* json, size_t length) override;

Object createObjectWithPrototype(const Value& prototype) override;
void setPrototypeOf(const Object& object, const Value& prototype) override;
Expand Down Expand Up @@ -1858,6 +1881,20 @@ class JSI_EXPORT Value {
return runtime.createValueFromJsonUtf8(json, length);
}

/// Parse a bounded ASCII buffer. See IRuntime::createValueFromJsonASCII for
/// the input, lifetime and error contracts.
static Value createFromJsonASCII(
IRuntime& runtime, const char* json, size_t length) {
return runtime.createValueFromJsonASCII(json, length);
}

/// Parse a bounded UTF-16 buffer. See IRuntime::createValueFromJsonUtf16 for
/// the input, lifetime and error contracts. Length is in UTF-16 code units.
static Value createFromJsonUtf16(
IRuntime& runtime, const char16_t* json, size_t length) {
return runtime.createValueFromJsonUtf16(json, length);
}

/// \return according to the Strict Equality Comparison algorithm, see:
/// https://262.ecma-international.org/11.0/#sec-strict-equality-comparison
static bool strictEquals(IRuntime& runtime, const Value& a, const Value& b);
Expand Down
190 changes: 190 additions & 0 deletions API/jsi/jsi/test/testlib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2192,6 +2192,196 @@ TEST_P(JSITest, CreateValueFromJsonUtf8Test) {
EXPECT_EQ(obj.getProperty(outer, "a").getNumber(), 1);
}

namespace {

/// Exercise the JSON grammar shared by the ASCII and UTF-16 entry points.
template <typename Parse>
void checkJSONBufferParsing(IRuntime& rt, Parse parse) {
auto fromText = [&](const std::string& text) {
return parse(text.data(), text.size());
};
EXPECT_TRUE(fromText("null").isNull());
EXPECT_TRUE(fromText("true").getBool());
EXPECT_FALSE(fromText("false").getBool());
EXPECT_EQ(fromText(" \t\r\n42 \n").getNumber(), 42);
EXPECT_EQ(fromText("-12.5e2").getNumber(), -1250);
double zero = fromText("-0").getNumber();
EXPECT_EQ(zero, 0);
EXPECT_TRUE(std::signbit(zero));
EXPECT_EQ(fromText("1e400").getNumber(), INFINITY);
EXPECT_EQ(fromText("\"text\"").getString(rt).utf8(rt), "text");
EXPECT_EQ(fromText("\"\"").getString(rt).utf8(rt), "");
EXPECT_EQ(
fromText(R"("\u0000\uD800\uDC00\uDFFF")").getString(rt).utf16(rt),
(std::u16string{0, 0xd800, 0xdc00, 0xdfff}));
auto array = fromText("[null,true,3,\"x\",[],{}]").getObject(rt).getArray(rt);
EXPECT_EQ(array.size(rt), 6);
EXPECT_TRUE(array.getValueAtIndex(rt, 0).isNull());
EXPECT_TRUE(array.getValueAtIndex(rt, 1).getBool());
EXPECT_EQ(array.getValueAtIndex(rt, 2).getNumber(), 3);
EXPECT_EQ(array.getValueAtIndex(rt, 3).getString(rt).utf8(rt), "x");
EXPECT_TRUE(array.getValueAtIndex(rt, 4).getObject(rt).isArray(rt));
EXPECT_TRUE(array.getValueAtIndex(rt, 5).isObject());
auto object =
fromText(R"({"a":1,"a":2,"__proto__":3,"nested":{"b":4}})").getObject(rt);
EXPECT_EQ(object.getProperty(rt, "a").getNumber(), 2);
EXPECT_EQ(object.getProperty(rt, "__proto__").getNumber(), 3);
EXPECT_EQ(
object.getPropertyAsObject(rt, "nested").getProperty(rt, "b").getNumber(),
4);

const std::string invalid[] = {
"",
" ",
"undefined",
"NaN",
"01",
"-",
"1e+",
"1e",
"[1,]",
"{a:1}",
"{\"a\":}",
"\"unterminated",
R"("\x00")",
R"("\uD80")",
"null true",
std::string("null\0", 5),
std::string("\"x\0y\"", 5)};
for (const auto& text : invalid) {
SCOPED_TRACE(text);
EXPECT_THROW(fromText(text), JSError);
}

// The fifth byte is deliberately not part of the input.
const char bounded[] = {'t', 'r', 'u', 'e', '!'};
EXPECT_TRUE(parse(bounded, 4).getBool());
EXPECT_THROW(parse(bounded, 5), JSError);
}

} // namespace

TEST_P(JSITest, CreateValueFromJsonBuffers) {
class DefaultRD : public RuntimeDecorator<Runtime, Runtime> {
public:
explicit DefaultRD(Runtime& rt) : RuntimeDecorator(rt) {}

Value createValueFromJsonASCII(const char* json, size_t length) override {
return Runtime::createValueFromJsonASCII(json, length);
}
Value createValueFromJsonUtf16(
const char16_t* json, size_t length) override {
return Runtime::createValueFromJsonUtf16(json, length);
}
String createStringFromUtf16(const char16_t* text, size_t length) override {
return Runtime::createStringFromUtf16(text, length);
}
};
DefaultRD fallback(rt);
for (IRuntime* runtime :
{static_cast<IRuntime*>(&rt), static_cast<IRuntime*>(&fallback)}) {
SCOPED_TRACE(runtime == &rt ? "native" : "default");
checkJSONBufferParsing(*runtime, [&](const char* json, size_t length) {
return Value::createFromJsonASCII(*runtime, json, length);
});
checkJSONBufferParsing(*runtime, [&](const char* json, size_t length) {
std::u16string text(json, json + length);
return Value::createFromJsonUtf16(*runtime, text.data(), text.size());
});

const std::u16string unicode = u"\"Grüße 世界 \U0001F600\"";
EXPECT_EQ(
Value::createFromJsonUtf16(*runtime, unicode.data(), unicode.size())
.getString(*runtime)
.utf16(*runtime),
u"Grüße 世界 \U0001F600");
for (char16_t surrogate : {0xd800, 0xdc00}) {
// Check both the unescaped fast path and the path after an escape.
for (const std::u16string prefix : {u"\"", u"\"\\n"}) {
std::u16string json = prefix + surrogate + u'"';
auto value =
Value::createFromJsonUtf16(*runtime, json.data(), json.size());
std::u16string expected = prefix.size() == 1 ? u"" : u"\n";
expected += surrogate;
EXPECT_EQ(value.getString(*runtime).utf16(*runtime), expected);
}
std::u16string json = u"{\"";
json += surrogate;
json += u"\":\"";
json += surrogate;
json += u"\"}";
auto object =
Value::createFromJsonUtf16(*runtime, json.data(), json.size())
.getObject(*runtime);
auto key = String::createFromUtf16(*runtime, &surrogate, 1);
EXPECT_EQ(
object.getProperty(*runtime, key).getString(*runtime).utf16(*runtime),
std::u16string(1, surrogate));
json = u"null";
json += surrogate;
EXPECT_THROW(
Value::createFromJsonUtf16(*runtime, json.data(), json.size()),
JSError);
}
EXPECT_THROW(
Value::createFromJsonUtf16(*runtime, u"\uFEFFnull", 5), JSError);
}
}

TEST_P(JSITest, DecoratorsForwardJSONBuffers) {
class CountingRD : public RuntimeDecorator<Runtime, Runtime> {
public:
explicit CountingRD(Runtime& rt) : RuntimeDecorator(rt) {}
Value createValueFromJsonASCII(const char* json, size_t length) override {
EXPECT_EQ(json, ascii);
EXPECT_EQ(length, 4);
++asciiCalls;
return 123;
}
Value createValueFromJsonUtf16(
const char16_t* json, size_t length) override {
EXPECT_EQ(json, utf16);
EXPECT_EQ(length, 4);
++utf16Calls;
if (throws) {
throw JSError(*this, "parse failed");
}
return 456;
}
const char* ascii = "1234";
const char16_t* utf16 = u"5678";
int asciiCalls = 0;
int utf16Calls = 0;
bool throws = false;
};
class OuterRD : public RuntimeDecorator<Runtime, Runtime> {
public:
explicit OuterRD(Runtime& rt) : RuntimeDecorator(rt) {}
};
struct Counter {
void before() { ++befores; }
void after() { ++afters; }
int befores = 0;
int afters = 0;
};
CountingRD inner(rt);
OuterRD outer(inner);
Counter counter;
WithRuntimeDecorator<Counter> with(inner, counter);
EXPECT_EQ(outer.createValueFromJsonASCII(inner.ascii, 4).getNumber(), 123);
EXPECT_EQ(outer.createValueFromJsonUtf16(inner.utf16, 4).getNumber(), 456);
EXPECT_EQ(Value::createFromJsonASCII(with, inner.ascii, 4).getNumber(), 123);
EXPECT_EQ(Value::createFromJsonUtf16(with, inner.utf16, 4).getNumber(), 456);
EXPECT_EQ(inner.asciiCalls, 2);
EXPECT_EQ(inner.utf16Calls, 2);
EXPECT_EQ(counter.befores, 2);
EXPECT_EQ(counter.afters, 2);
inner.throws = true;
EXPECT_THROW(Value::createFromJsonUtf16(with, inner.utf16, 4), JSError);
EXPECT_EQ(counter.befores, 3);
EXPECT_EQ(counter.afters, 3);
}

TEST_P(JSITest, GetStringDataTest) {
// This Runtime Decorator is used to test the default getStringData
// implementation for VMs that do not provide their own implementation
Expand Down
Loading
Loading