Skip to content

Commit

Permalink
Add createFromUtf16 JSI method (#48211)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #48211

Adding the default implementation for `createFromUtf16` method for JSI
String and PropNameId.

Changelog: [Internal]

Reviewed By: tmikov

Differential Revision: D67070206

fbshipit-source-id: 47297e6ae3028ee0e101628aab5bc076fcbbdebc
  • Loading branch information
tsaichien authored and facebook-github-bot committed Jan 23, 2025
1 parent f89f191 commit d9d8240
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 1 deletion.
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
58 changes: 58 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,45 @@ std::u16string convertUTF8ToUTF16(const std::string& utf8) {
return ret;
}

// Given a unsigned number, which is less than 16, return the hex character.
inline char hexDigit(unsigned 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) {
for (const char16_t* e = utf16 + length; utf16 != e; ++utf16) {
if (*utf16 > 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) {
// Every character will need 4 hex digits + the character escape "\u".
// Plus 2 character for the opening and closing single quote.
std::string s = std::string(6 * length + 2, 0);
s.front() = '\'';

for (size_t i = 0; i != length; ++i) {
char16_t ch = utf16[i];
size_t start = (6 * i) + 1;

s[start] = '\\';
s[start + 1] = 'u';

s[start + 2] = hexDigit((ch >> 12) & 0x000f);
s[start + 3] = hexDigit((ch >> 8) & 0x000f);
s[start + 4] = hexDigit((ch >> 4) & 0x000f);
s[start + 5] = hexDigit(ch & 0x000f);
}
s.back() = '\'';
return s;
}

} // namespace

Buffer::~Buffer() = default;
Expand Down Expand Up @@ -248,6 +287,25 @@ 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) {
auto jsString = createStringFromUtf16(utf16, length);
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
46 changes: 45 additions & 1 deletion packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1576,7 +1576,7 @@ TEST_P(JSITest, UTF8ExceptionTest) {
}
}

TEST_P(JSITest, UTF16Test) {
TEST_P(JSITest, UTF16ConversionTest) {
// This Runtime Decorator is used to test the conversion from UTF-8 to UTF-16
// in the default utf16 method for runtimes that do not provide their own
// utf16 implementation.
Expand Down Expand Up @@ -1640,6 +1640,50 @@ TEST_P(JSITest, UTF16Test) {
EXPECT_EQ(str.utf16(rd), u"\uFFFD\u007A");
}

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);

// 👋 in UTF-16 encoding is 0xd83d 0xdc4b
utf16 = u"hello!\xd83d\xdc4b";
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
}

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

0 comments on commit d9d8240

Please sign in to comment.