Skip to content

Commit

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

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

Changelog: [Internal]

Differential Revision: D67070206
  • Loading branch information
tsaichien authored and facebook-github-bot committed Dec 11, 2024
1 parent 3ff9212 commit 3fac4b7
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 0 deletions.
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 @@ -637,6 +644,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 @@ -692,6 +704,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
50 changes: 50 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,37 @@ 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 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 = "'";
auto* curr = utf16;
auto* end = curr + length;
while (curr < end) {
s.append("\\u");

char buffer[4];
memset(buffer, '0', 4);

char* bufferEnd = buffer + 4;
char16_t unit = *curr;
while (unit) {
unsigned char x = static_cast<unsigned char>(unit) % 16;
*--bufferEnd = hexDigit(x);
unit /= 16;
}
s.append(buffer, 4);
curr++;
}
s.append("'");
return s;
}

} // namespace

Buffer::~Buffer() = default;
Expand Down Expand Up @@ -248,6 +279,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) {
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 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 @@ -509,6 +513,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 @@ -693,6 +712,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 @@ -1664,6 +1664,49 @@ TEST_P(JSITest, GetStringDataTest) {
EXPECT_EQ(buf, str.utf16(rd));
}

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

0 comments on commit 3fac4b7

Please sign in to comment.