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 createFromUtf16 JSI method #48211

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
16 changes: 16 additions & 0 deletions packages/react-native/ReactCommon/jsi/jsi/decorator.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation {
PropNameID createPropNameIDFromString(const String& str) override {
return plain_.createPropNameIDFromString(str);
};
PropNameID createPropNameIDFromUtf16(const char16_t* utf16, size_t length)
override {
return plain_.createPropNameIDFromUtf16(utf16, length);
}
PropNameID createPropNameIDFromSymbol(const Symbol& sym) override {
return plain_.createPropNameIDFromSymbol(sym);
};
Expand Down Expand Up @@ -221,6 +225,9 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation {
String createStringFromUtf8(const uint8_t* utf8, size_t length) override {
return plain_.createStringFromUtf8(utf8, length);
};
String createStringFromUtf16(const char16_t* utf16, size_t length) override {
return plain_.createStringFromUtf16(utf16, length);
}
std::string utf8(const String& s) override {
return plain_.utf8(s);
}
Expand Down Expand Up @@ -649,6 +656,11 @@ class WithRuntimeDecorator : public RuntimeDecorator<Plain, Base> {
Around around{with_};
return RD::createPropNameIDFromUtf8(utf8, length);
};
PropNameID createPropNameIDFromUtf16(const char16_t* utf16, size_t length)
override {
Around around{with_};
return RD::createPropNameIDFromUtf16(utf16, length);
}
PropNameID createPropNameIDFromString(const String& str) override {
Around around{with_};
return RD::createPropNameIDFromString(str);
Expand Down Expand Up @@ -704,6 +716,10 @@ class WithRuntimeDecorator : public RuntimeDecorator<Plain, Base> {
Around around{with_};
return RD::createStringFromUtf8(utf8, length);
};
String createStringFromUtf16(const char16_t* utf16, size_t length) override {
Around around{with_};
return RD::createStringFromUtf16(utf16, length);
}
std::string utf8(const String& s) override {
Around around{with_};
return RD::utf8(s);
Expand Down
71 changes: 71 additions & 0 deletions packages/react-native/ReactCommon/jsi/jsi/jsi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,49 @@ std::u16string convertUTF8ToUTF16(const std::string& utf8) {
return ret;
}

// Given a unsigned number, which is less than 16, return the hex character.
char hexDigit(unsigned int x) {
return x < 10 ? '0' + x : 'A' + (x - 10);
}

// Given a sequence of UTF 16 code units, return true if all code units are
// ASCII characters
bool isAllASCII(const char16_t* utf16, size_t length) {
auto* curr = utf16;
auto* end = curr + length;
for (; curr < end; ++curr) {
if (*curr > 0x7F)
return false;
}
return true;
}

// Given a sequences of UTF 16 code units, return a string that explicitly
// expresses the code units
std::string getUtf16CodeUnitString(const char16_t* utf16, size_t length) {
std::string s;
// Every character will need the character escape "\u" + 4 hex digits. Plus 2
// character for the opening and closing single quote.
s.reserve(6 * length + 2);
s.append("'");

auto* curr = utf16;
auto* end = curr + length;
for (; curr < end; ++curr) {
s.append("\\u");
char16_t ch = *curr;
char buffer[4];

buffer[0] = hexDigit((ch & 0xf000) >> 12);
buffer[1] = hexDigit((ch & 0x0f00) >> 8);
buffer[2] = hexDigit((ch & 0x00f0) >> 4);
buffer[3] = hexDigit(ch & 0x000f);
s.append(buffer, 4);
}
s.append("'");
return s;
}

} // namespace

Buffer::~Buffer() = default;
Expand Down Expand Up @@ -248,6 +291,34 @@ Value Runtime::createValueFromJsonUtf8(const uint8_t* json, size_t length) {
return parseJson.call(*this, String::createFromUtf8(*this, json, length));
}

String Runtime::createStringFromUtf16(const char16_t* utf16, size_t length) {
if (isAllASCII(utf16, length)) {
std::string buffer(utf16, utf16 + length);
return createStringFromAscii(buffer.data(), length);
}
auto s = getUtf16CodeUnitString(utf16, length);
return global()
.getPropertyAsFunction(*this, "eval")
.call(*this, s)
.getString(*this);
}

PropNameID Runtime::createPropNameIDFromUtf16(
const char16_t* utf16,
size_t length) {
if (isAllASCII(utf16, length)) {
std::string buffer(utf16, utf16 + length);
auto jsString = createStringFromAscii(buffer.data(), length);
return createPropNameIDFromString(jsString);
}
auto s = getUtf16CodeUnitString(utf16, length);
auto jsString = global()
.getPropertyAsFunction(*this, "eval")
.call(*this, s)
.getString(*this);
return createPropNameIDFromString(jsString);
}

std::u16string Runtime::utf16(const PropNameID& sym) {
auto utf8Str = utf8(sym);
return convertUTF8ToUTF16(utf8Str);
Expand Down
34 changes: 34 additions & 0 deletions packages/react-native/ReactCommon/jsi/jsi/jsi.h
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,9 @@ class JSI_EXPORT Runtime {
virtual PropNameID createPropNameIDFromUtf8(
const uint8_t* utf8,
size_t length) = 0;
virtual PropNameID createPropNameIDFromUtf16(
const char16_t* utf16,
size_t length);
virtual PropNameID createPropNameIDFromString(const String& str) = 0;
virtual PropNameID createPropNameIDFromSymbol(const Symbol& sym) = 0;
virtual std::string utf8(const PropNameID&) = 0;
Expand All @@ -322,6 +325,7 @@ class JSI_EXPORT Runtime {

virtual String createStringFromAscii(const char* str, size_t length) = 0;
virtual String createStringFromUtf8(const uint8_t* utf8, size_t length) = 0;
virtual String createStringFromUtf16(const char16_t* utf16, size_t length);
virtual std::string utf8(const String&) = 0;

// \return a \c Value created from a utf8-encoded JSON string. The default
Expand Down Expand Up @@ -515,6 +519,21 @@ class JSI_EXPORT PropNameID : public Pointer {
reinterpret_cast<const uint8_t*>(utf8.data()), utf8.size());
}

/// Given a series of UTF-16 encoded code units, create a PropNameId. The
/// input may contain unpaired surrogates, which will be interpreted as a code
/// point of the same value.
static PropNameID
forUtf16(Runtime& runtime, const char16_t* utf16, size_t length) {
return runtime.createPropNameIDFromUtf16(utf16, length);
}

/// Given a series of UTF-16 encoded code units stored inside std::u16string,
/// create a PropNameId. The input may contain unpaired surrogates, which
/// will be interpreted as a code point of the same value.
static PropNameID forUtf16(Runtime& runtime, const std::u16string& str) {
return runtime.createPropNameIDFromUtf16(str.data(), str.size());
}

/// Create a PropNameID from a JS string.
static PropNameID forString(Runtime& runtime, const jsi::String& str) {
return runtime.createPropNameIDFromString(str);
Expand Down Expand Up @@ -699,6 +718,21 @@ class JSI_EXPORT String : public Pointer {
reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
}

/// Given a series of UTF-16 encoded code units, create a JS String. The input
/// may contain unpaired surrogates, which will be interpreted as a code point
/// of the same value.
static String
createFromUtf16(Runtime& runtime, const char16_t* utf16, size_t length) {
return runtime.createStringFromUtf16(utf16, length);
}

/// Given a series of UTF-16 encoded code units stored inside std::u16string,
/// create a JS String. The input may contain unpaired surrogates, which will
/// be interpreted as a code point of the same value.
static String createFromUtf16(Runtime& runtime, const std::u16string& utf16) {
return runtime.createStringFromUtf16(utf16.data(), utf16.length());
}

/// \return whether a and b contain the same characters.
static bool strictEquals(Runtime& runtime, const String& a, const String& b) {
return runtime.strictEquals(a, b);
Expand Down
43 changes: 43 additions & 0 deletions packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1723,6 +1723,49 @@ TEST_P(JSITest, ObjectCreateWithPrototype) {
EXPECT_TRUE(child.getPrototype(rd).isNull());
}

TEST_P(JSITest, CreateFromUtf16Test) {
// This Runtime Decorator is used to test the default createStringFromUtf16
// and createPropNameIDFromUtf16 implementation for VMs that do not provide
// their own implementation
class RD : public RuntimeDecorator<Runtime, Runtime> {
public:
RD(Runtime& rt) : RuntimeDecorator(rt) {}

String createStringFromUtf16(const char16_t* utf16, size_t length)
override {
return Runtime::createStringFromUtf16(utf16, length);
}

PropNameID createPropNameIDFromUtf16(const char16_t* utf16, size_t length)
override {
return Runtime::createPropNameIDFromUtf16(utf16, length);
}
};

RD rd = RD(rt);
std::u16string utf16 = u"foobar";

auto jsString = String::createFromUtf16(rd, utf16);
EXPECT_EQ(jsString.utf16(rd), utf16);
auto prop = PropNameID::forUtf16(rd, utf16);
EXPECT_EQ(prop.utf16(rd), utf16);

utf16 = u"hello!👋";
jsString = String::createFromUtf16(rd, utf16.data(), utf16.length());
EXPECT_EQ(jsString.utf16(rd), utf16);
prop = PropNameID::forUtf16(rd, utf16);
EXPECT_EQ(prop.utf16(rd), utf16);

utf16 = u"\xd83d";
jsString = String::createFromUtf16(rd, utf16.data(), utf16.length());
/// We need to use charCodeAt instead of UTF16 because the default
/// implementation of UTF16 converts to UTF8, then to UTF16, so we will lose
/// the lone surrogate value.
rd.global().setProperty(rd, "loneSurrogate", jsString);
auto cp = eval("loneSurrogate.charCodeAt(0)").getNumber();
EXPECT_EQ(cp, 55357); // 0xD83D in decimal
}

INSTANTIATE_TEST_CASE_P(
Runtimes,
JSITest,
Expand Down
Loading