Skip to content

Commit ccdc57d

Browse files
cjhopmanFacebook Github Bot 4
authored andcommitted
Fix handling of bad utf16 passed out of JS
Summary: JSC's utf16 -> utf8 conversion crashes on encountering bad utf16. Instead, use our own conversion (conveniently copied from fbjni). Original fix thanks to rigdern (facebook#9302) Reviewed By: mhorowitz Differential Revision: D3746947 fbshipit-source-id: 29887ca720f6a2b074f01f853bad28a083b273bc
1 parent 9a5d3ba commit ccdc57d

File tree

5 files changed

+123
-5
lines changed

5 files changed

+123
-5
lines changed

ReactCommon/cxxreact/Android.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ LOCAL_SRC_FILES := \
2121
NativeToJsBridge.cpp \
2222
Platform.cpp \
2323
Value.cpp \
24+
Unicode.cpp \
2425

2526
LOCAL_C_INCLUDES := $(LOCAL_PATH)/..
2627
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)

ReactCommon/cxxreact/BUCK

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ react_library(
126126
'NativeToJsBridge.cpp',
127127
'Platform.cpp',
128128
'Value.cpp',
129+
'Unicode.cpp',
129130
],
130131
headers = [
131132
'JSCLegacyProfiler.h',
@@ -155,6 +156,7 @@ react_library(
155156
'Platform.h',
156157
'SystraceSection.h',
157158
'Value.h',
159+
'Unicode.h',
158160
],
159161
preprocessor_flags = [
160162
'-DLOG_TAG="ReactNative"',

ReactCommon/cxxreact/Unicode.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2004-present Facebook. All Rights Reserved.
2+
3+
#include "Unicode.h"
4+
5+
namespace facebook {
6+
namespace react {
7+
namespace unicode {
8+
namespace {
9+
10+
// TODO(12827176): Don't duplicate this code here and fbjni.
11+
12+
const uint16_t kUtf8OneByteBoundary = 0x80;
13+
const uint16_t kUtf8TwoBytesBoundary = 0x800;
14+
const uint16_t kUtf16HighSubLowBoundary = 0xD800;
15+
const uint16_t kUtf16HighSubHighBoundary = 0xDC00;
16+
const uint16_t kUtf16LowSubHighBoundary = 0xE000;
17+
18+
// Calculate how many bytes are needed to convert an UTF16 string into UTF8
19+
// UTF16 string
20+
size_t utf16toUTF8Length(const uint16_t* utf16String, size_t utf16StringLen) {
21+
if (!utf16String || utf16StringLen == 0) {
22+
return 0;
23+
}
24+
25+
uint32_t utf8StringLen = 0;
26+
auto utf16StringEnd = utf16String + utf16StringLen;
27+
auto idx16 = utf16String;
28+
while (idx16 < utf16StringEnd) {
29+
auto ch = *idx16++;
30+
if (ch < kUtf8OneByteBoundary) {
31+
utf8StringLen++;
32+
} else if (ch < kUtf8TwoBytesBoundary) {
33+
utf8StringLen += 2;
34+
} else if (
35+
(ch >= kUtf16HighSubLowBoundary) && (ch < kUtf16HighSubHighBoundary) &&
36+
(idx16 < utf16StringEnd) &&
37+
(*idx16 >= kUtf16HighSubHighBoundary) && (*idx16 < kUtf16LowSubHighBoundary)) {
38+
utf8StringLen += 4;
39+
idx16++;
40+
} else {
41+
utf8StringLen += 3;
42+
}
43+
}
44+
45+
return utf8StringLen;
46+
}
47+
48+
} // namespace
49+
50+
std::string utf16toUTF8(const uint16_t* utf16String, size_t utf16StringLen) noexcept {
51+
if (!utf16String || utf16StringLen <= 0) {
52+
return "";
53+
}
54+
55+
std::string utf8String(utf16toUTF8Length(utf16String, utf16StringLen), '\0');
56+
auto idx8 = utf8String.begin();
57+
auto idx16 = utf16String;
58+
auto utf16StringEnd = utf16String + utf16StringLen;
59+
while (idx16 < utf16StringEnd) {
60+
auto ch = *idx16++;
61+
if (ch < kUtf8OneByteBoundary) {
62+
*idx8++ = (ch & 0x7F);
63+
} else if (ch < kUtf8TwoBytesBoundary) {
64+
*idx8++ = 0b11000000 | (ch >> 6);
65+
*idx8++ = 0b10000000 | (ch & 0x3F);
66+
} else if (
67+
(ch >= kUtf16HighSubLowBoundary) && (ch < kUtf16HighSubHighBoundary) &&
68+
(idx16 < utf16StringEnd) &&
69+
(*idx16 >= kUtf16HighSubHighBoundary) && (*idx16 < kUtf16LowSubHighBoundary)) {
70+
auto ch2 = *idx16++;
71+
uint8_t trunc_byte = (((ch >> 6) & 0x0F) + 1);
72+
*idx8++ = 0b11110000 | (trunc_byte >> 2);
73+
*idx8++ = 0b10000000 | ((trunc_byte & 0x03) << 4) | ((ch >> 2) & 0x0F);
74+
*idx8++ = 0b10000000 | ((ch & 0x03) << 4) | ((ch2 >> 6) & 0x0F);
75+
*idx8++ = 0b10000000 | (ch2 & 0x3F);
76+
} else {
77+
*idx8++ = 0b11100000 | (ch >> 12);
78+
*idx8++ = 0b10000000 | ((ch >> 6) & 0x3F);
79+
*idx8++ = 0b10000000 | (ch & 0x3F);
80+
}
81+
}
82+
83+
return utf8String;
84+
}
85+
86+
} // namespace unicode
87+
} // namespace react
88+
} // namespace facebook

ReactCommon/cxxreact/Unicode.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2004-present Facebook. All Rights Reserved.
2+
3+
#pragma once
4+
5+
#include <string>
6+
#include <cstdint>
7+
8+
namespace facebook {
9+
namespace react {
10+
namespace unicode {
11+
std::string utf16toUTF8(const uint16_t* utf16, size_t length) noexcept;
12+
}
13+
}
14+
}

ReactCommon/cxxreact/Value.h

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <folly/dynamic.h>
1616

1717
#include "noncopyable.h"
18+
#include "Unicode.h"
1819

1920
#if WITH_FBJSCEXTENSIONS
2021
#include <jsc_stringref.h>
@@ -85,12 +86,24 @@ class String : public noncopyable {
8586
return JSStringGetMaximumUTF8CStringSize(m_string);
8687
}
8788

89+
/*
90+
* JavaScriptCore is built with strict utf16 -> utf8 conversion.
91+
* This means if JSC's built-in conversion function encounters a JavaScript
92+
* string which contains half of a 32-bit UTF-16 symbol, it produces an error
93+
* rather than returning a string.
94+
*
95+
* Instead of relying on this, we use our own utf16 -> utf8 conversion function
96+
* which is more lenient and always returns a string. When an invalid UTF-16
97+
* string is provided, it'll likely manifest as a rendering glitch in the app for
98+
* the invalid symbol.
99+
*
100+
* For details on JavaScript's unicode support see:
101+
* https://mathiasbynens.be/notes/javascript-unicode
102+
*/
88103
std::string str() const {
89-
size_t reserved = utf8Size();
90-
char* bytes = new char[reserved];
91-
size_t length = JSStringGetUTF8CString(m_string, bytes, reserved) - 1;
92-
std::unique_ptr<char[]> retainedBytes(bytes);
93-
return std::string(bytes, length);
104+
const JSChar* utf16 = JSStringGetCharactersPtr(m_string);
105+
int stringLength = JSStringGetLength(m_string);
106+
return unicode::utf16toUTF8(utf16, stringLength);
94107
}
95108

96109
// Assumes that utf8 is null terminated

0 commit comments

Comments
 (0)