Skip to content

Commit a7bcbd4

Browse files
More TextDecoder tests (#141)
`std::wstring_convert` is too strict and doesn't accept strings with invalid characters. Other impl (Chakra, V8) and browsers accept such strings.
1 parent 3794fde commit a7bcbd4

5 files changed

Lines changed: 102 additions & 5 deletions

File tree

Core/Node-API/Source/js_native_api_javascriptcore.cc

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
#include <stdexcept>
88
#include <string>
99
#include <vector>
10-
#include <locale>
11-
#include <codecvt>
10+
1211

1312
struct napi_callback_info__ {
1413
napi_value newTarget;
@@ -103,13 +102,68 @@ namespace {
103102
}
104103

105104
private:
105+
// Decode UTF-8 to UTF-16, replacing invalid sequences with U+FFFD.
106+
static std::u16string Utf8ToUtf16(const char* str, size_t len) {
107+
std::u16string result;
108+
result.reserve(len);
109+
const auto* s = reinterpret_cast<const unsigned char*>(str);
110+
const auto* end = s + len;
111+
while (s < end) {
112+
uint32_t cp;
113+
int trail;
114+
unsigned char lead = *s++;
115+
if (lead < 0x80) {
116+
cp = lead; trail = 0;
117+
} else if ((lead >> 5) == 0x6) {
118+
cp = lead & 0x1F; trail = 1;
119+
} else if ((lead >> 4) == 0xE) {
120+
cp = lead & 0x0F; trail = 2;
121+
} else if ((lead >> 3) == 0x1E) {
122+
cp = lead & 0x07; trail = 3;
123+
} else {
124+
result.push_back(0xFFFD);
125+
continue;
126+
}
127+
if (s + trail > end) {
128+
result.push_back(0xFFFD);
129+
break;
130+
}
131+
bool valid = true;
132+
for (int i = 0; i < trail; ++i) {
133+
if ((s[i] & 0xC0) != 0x80) { valid = false; break; }
134+
cp = (cp << 6) | (s[i] & 0x3F);
135+
}
136+
if (!valid) {
137+
result.push_back(0xFFFD);
138+
continue;
139+
}
140+
s += trail;
141+
// Reject overlong encodings, surrogates, and out-of-range values.
142+
if ((trail == 1 && cp < 0x80) ||
143+
(trail == 2 && cp < 0x800) ||
144+
(trail == 3 && cp < 0x10000) ||
145+
(cp >= 0xD800 && cp <= 0xDFFF) ||
146+
cp > 0x10FFFF) {
147+
result.push_back(0xFFFD);
148+
continue;
149+
}
150+
if (cp <= 0xFFFF) {
151+
result.push_back(static_cast<char16_t>(cp));
152+
} else {
153+
cp -= 0x10000;
154+
result.push_back(static_cast<char16_t>(0xD800 + (cp >> 10)));
155+
result.push_back(static_cast<char16_t>(0xDC00 + (cp & 0x3FF)));
156+
}
157+
}
158+
return result;
159+
}
160+
106161
static JSStringRef CreateUTF8(const char* string, size_t length) {
107162
if (length == NAPI_AUTO_LENGTH) {
108163
return JSStringCreateWithUTF8CString(string);
109164
}
110165

111-
std::u16string u16str{std::wstring_convert<
112-
std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(string, string + length)};
166+
std::u16string u16str{Utf8ToUtf16(string, length)};
113167
return JSStringCreateWithCharacters(reinterpret_cast<JSChar*>(u16str.data()), u16str.size());
114168
}
115169

Tests/UnitTests/Android/app/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ task copyScripts {
8888
include "*.js"
8989
into 'src/main/assets/Scripts'
9090
}
91+
copy
92+
{
93+
from '../../Assets'
94+
include "*.*"
95+
into 'src/main/assets/Assets'
96+
}
9197
}
9298
}
9399

1020 KB
Binary file not shown.

Tests/UnitTests/CMakeLists.txt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ set(SCRIPTS
55
set(TYPE_SCRIPTS
66
"Scripts/tests.ts")
77

8+
file(GLOB ASSETS "${CMAKE_CURRENT_SOURCE_DIR}/Assets/*")
9+
810
set(SOURCES
911
"Shared/Shared.cpp"
1012
"Shared/Shared.h")
@@ -25,6 +27,10 @@ if(APPLE)
2527
${SCRIPTS}
2628
${TYPE_SCRIPTS}
2729
PROPERTIES MACOSX_PACKAGE_LOCATION "Scripts")
30+
31+
set_source_files_properties(
32+
${ASSETS}
33+
PROPERTIES MACOSX_PACKAGE_LOCATION "Assets")
2834
else()
2935
set(SOURCES ${SOURCES}
3036
macOS/App.mm)
@@ -37,7 +43,7 @@ elseif(UNIX AND NOT ANDROID)
3743
Linux/App.cpp)
3844
endif()
3945

40-
add_executable(UnitTests ${SOURCES} ${SCRIPTS} ${TYPE_SCRIPTS})
46+
add_executable(UnitTests ${SOURCES} ${SCRIPTS} ${TYPE_SCRIPTS} ${ASSETS})
4147
target_compile_definitions(UnitTests PRIVATE JSRUNTIMEHOST_PLATFORM="${JSRUNTIMEHOST_PLATFORM}")
4248

4349
target_link_libraries(UnitTests
@@ -87,9 +93,17 @@ else()
8793
COMMAND ${CMAKE_COMMAND} -E create_symlink "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/Scripts/symlink_target.js" "${CMAKE_CFG_INTDIR}/Scripts/symlink_1.js")
8894
add_custom_command(TARGET UnitTests POST_BUILD
8995
COMMAND ${CMAKE_COMMAND} -E create_symlink "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/Scripts/symlink_1.js" "${CMAKE_CFG_INTDIR}/Scripts/symlink_2.js")
96+
97+
foreach(ASSET ${ASSETS})
98+
get_filename_component(ASSET_NAME "${ASSET}" NAME)
99+
add_custom_command(TARGET UnitTests POST_BUILD
100+
COMMAND "${CMAKE_COMMAND}" -E copy "${ASSET}" "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/Assets/${ASSET_NAME}"
101+
COMMENT "Copying ${ASSET_NAME}")
102+
endforeach()
90103
endif()
91104

92105
set_property(TARGET UnitTests PROPERTY FOLDER Tests)
93106

94107
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES} ${SCRIPTS})
95108
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/Scripts PREFIX scripts FILES ${TYPE_SCRIPTS})
109+
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/Assets PREFIX assets FILES ${ASSETS})

Tests/UnitTests/Scripts/tests.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,21 @@ describe("XMLHTTPRequest", function () {
196196
var response = new Uint8Array(xhr.response);
197197
expect(response).to.eql(expected);
198198
});
199+
200+
it("should load a PLY file and parse vertex count from header using TextDecoder", async function () {
201+
this.timeout(30000);
202+
const xhr = await createRequest("GET", "app:///Assets/Halo_Believe.ply", undefined, "arraybuffer");
203+
expect(xhr.status).to.equal(200);
204+
205+
const ubuf = new Uint8Array(xhr.response);
206+
const header = new TextDecoder().decode(ubuf.slice(0, 1024 * 10));
207+
const headerEnd = "end_header\n";
208+
const headerEndIndex = header.indexOf(headerEnd);
209+
expect(headerEndIndex).to.be.greaterThan(0);
210+
211+
const vertexCount = parseInt(/element vertex (\d+)\n/.exec(header)![1]);
212+
expect(vertexCount).to.equal(18713);
213+
});
199214
});
200215

201216
describe("setTimeout", function () {
@@ -1268,6 +1283,14 @@ describe("TextDecoder", function () {
12681283
const result = decoder.decode(sub);
12691284
expect(result).to.equal("Hi");
12701285
});
1286+
1287+
it("should decode a Uint8Array containing a null byte", function () {
1288+
const decoder = new TextDecoder();
1289+
const encoded = new Uint8Array([72, 0, 105]); // "H\0i"
1290+
const result = decoder.decode(encoded);
1291+
expect(result).to.equal("H\0i");
1292+
expect(result.length).to.equal(3);
1293+
});
12711294
});
12721295

12731296
function runTests() {

0 commit comments

Comments
 (0)