| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting. Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughReplaces UTF-8 intermediate conversions in the V8-to-Objective-C string bridge with direct UTF-16 access. ToNSString and ToUtf16String now use v8::String::Value to read the UTF-16 buffer, propagating the change to ArgConverter and DictionaryAdapter call sites. Three new tests verify lone surrogates and embedded NULs are preserved. ChangesUTF-16 Bridging Conversion
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem🚥 Pre-merge checks | ✅ 5 ✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. ❤️ ShareComment @coderabbitai help to get the list of available commands and usage tips. |
Sorry, something went wrong.
There was a problem hiding this comment.
TestRunner/app/tests/ApiTests.js (1)🤖 Prompt for all review comments with AI agents15-22: ⚡ Quick win
Test description in PR objectives claims "round trip" but implementation is one-way.
The PR objectives state this test verifies "a JS lone high surrogate survives the JS → NSString → JS round trip," but the test only converts JS → NSString and checks the NSString properties. To truly verify a round trip, the test should convert the NSString back to a JS string and assert it still equals "\uD834".
The current assertions (ns.length === 1 and UTF-8 byte length === 0) do prove the NSString preserves the lone surrogate internally, but a complete round-trip verification would strengthen confidence in both directions of the bridge.
🔄 Suggested enhancement to complete the round trip🤖 Prompt for AI Agentsit("preserves a lone surrogate when bridging a JS string to NSString", function () { // A lone high surrogate is valid in a JS string but has no UTF-8 encoding. // Faithful UTF-16 bridging keeps it, so the NSString reports 0 UTF-8 bytes. // A UTF-8 round-trip would have replaced it with U+FFFD, which is 3 UTF-8 bytes. - var ns = NSString.stringWithString("\uD834"); + var original = "\uD834"; + var ns = NSString.stringWithString(original); expect(ns.length).toBe(1); expect(ns.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)).toBe(0); + + // Complete the round trip: NSString → JS + var roundTripped = ns.toString(); + expect(roundTripped).toBe(original); + expect(roundTripped.charCodeAt(0)).toBe(0xD834); });Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@TestRunner/app/tests/ApiTests.js` around lines 15 - 22, The test function "preserves a lone surrogate when bridging a JS string to NSString" verifies the first direction of the round trip (JS string to NSString) but does not complete the round trip by converting the NSString back to a JS string. After the existing assertions on ns.length and ns.lengthOfBytesUsingEncoding, add an assertion that converts the NSString (stored in variable ns) back to a JS string and verifies it equals the original lone surrogate character "\uD834" to fully validate the round-trip conversion.
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. Nitpick comments: In `@TestRunner/app/tests/ApiTests.js`: - Around line 15-22: The test function "preserves a lone surrogate when bridging a JS string to NSString" verifies the first direction of the round trip (JS string to NSString) but does not complete the round trip by converting the NSString back to a JS string. After the existing assertions on ns.length and ns.lengthOfBytesUsingEncoding, add an assertion that converts the NSString (stored in variable ns) back to a JS string and verifies it equals the original lone surrogate character "\uD834" to fully validate the round-trip conversion.
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: a85c5090-e1e6-4cf0-b02b-428ae8103d44
📥 CommitsReviewing files that changed from the base of the PR and between 16439b5 and a37e7f2.
📒 Files selected for processing (5)
Sorry, something went wrong.
|
@coderabbitai thanks, but I'm going to skip this suggestion, it would actually break the test. Completing the round trip with ns.toString() would fail: the reverse NSString to JS direction still goes through UTF-8 (ToV8String(NSString*) uses [value UTF8String]), so it turns the lone surrogate back into U+FFFD. expect(ns.toString()).toBe("\uD834") and charCodeAt(0) === 0xD834 would both fail. I confirmed that by running the TestRunner suite locally. That reverse path is intentionally not touched for surrogates in this PR. The reverse-direction edits here only fix NUL truncation (passing an explicit byte length instead of folding through a C string), not surrogate fidelity, so there is no faithful NSString to JS path to round-trip a lone surrogate through. That is exactly why the test observes the NSString with a numeric native query (lengthOfBytesUsingEncoding: is 0 for a faithfully kept lone surrogate, 3 once it has become U+FFFD) instead of converting back to a JS string. The bidirectional NUL case is already covered by the existing Marshals NSString with null character test in NSStringTests.js. |
Sorry, something went wrong.
|
Tip For best results, initiate chat on the files or code changes. @adrian-niculescu, thanks for the thorough explanation — that makes perfect sense. The ToV8String(NSString*) path still uses [value UTF8String] which mangles lone surrogates back to U+FFFD, so a round-trip assertion via ns.toString() would indeed be a false expectation at this stage. Using lengthOfBytesUsingEncoding(NSUTF8StringEncoding) === 0 as a numeric native proxy is a clean way to confirm the surrogate was kept intact without attempting the broken reverse path. I'll withdraw my suggestion. (╯°□°)╯ 🐇 ← gently backing away from that suggestion ✏️ Learnings added Learnt from: adrian-niculescu URL: https://github.com/NativeScript/ios/pull/392 Timestamp: 2026-06-13T22:37:15.839Z Learning: In NativeScript iOS (NativeScript/ios), the `ToV8String(NSString*)` function in `Helpers.mm` still goes through `[value UTF8String]`, which means lone (unpaired) UTF-16 surrogates get corrupted to U+FFFD on the NSString→JS direction. PR `#392` intentionally fixes only the JS→NSString direction (surrogate fidelity) and the NUL-truncation issue on both directions, but does NOT fix NSString→JS surrogate fidelity. Therefore, a test for a lone surrogate round-trip through NSString cannot assert `ns.toString() === "\uD834"` — it uses `ns.lengthOfBytesUsingEncoding(NSUTF8StringEncoding) === 0` as a native numeric proxy instead (faithfully stored lone surrogate yields 0 UTF-8 bytes; a corrupted U+FFFD would yield 3).
|
Sorry, something went wrong.
There was a problem hiding this comment.
TestRunner/app/tests/ApiTests.js (1)🤖 Prompt for all review comments with AI agents15-31: ⚡ Quick win
Consider adding a lone low surrogate test case for completeness.
The current test verifies a lone high surrogate (U+D834, range 0xD800-0xDBFF). Adding a similar test for a lone low surrogate (e.g., "\uDC00", range 0xDC00-0xDFFF) would strengthen coverage of the UTF-16 fidelity fix, since both surrogate ranges should be preserved but have different bit patterns.
📝 Optional additional test case🤖 Prompt for AI Agentsit("preserves a lone low surrogate when bridging a JS string to NSString", function () { var ns = NSString.stringWithString("\uDC00"); expect(ns.length).toBe(1); var buffer = interop.alloc(interop.sizeof(interop.types.uint16)); ns.getCharactersRange(buffer, NSMakeRange(0, 1)); var codeUnit = new interop.Reference(interop.types.uint16, buffer).value; interop.free(buffer); expect(codeUnit).toBe(0xDC00); });Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@TestRunner/app/tests/ApiTests.js` around lines 15 - 31, The test suite currently only covers high surrogates (U+D834) but lacks coverage for low surrogates in the UTF-16 bridging behavior. Add a new test case after the existing "preserves a lone surrogate when bridging a JS string to NSString" test that follows the same pattern but uses a low surrogate code unit (U+DC00 instead of U+D834) to verify that lone low surrogates are also preserved correctly when bridging JS strings to NSString. The new test should verify that the bridged NSString preserves the low surrogate value as 0xDC00.
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. Nitpick comments: In `@TestRunner/app/tests/ApiTests.js`: - Around line 15-31: The test suite currently only covers high surrogates (U+D834) but lacks coverage for low surrogates in the UTF-16 bridging behavior. Add a new test case after the existing "preserves a lone surrogate when bridging a JS string to NSString" test that follows the same pattern but uses a low surrogate code unit (U+DC00 instead of U+D834) to verify that lone low surrogates are also preserved correctly when bridging JS strings to NSString. The new test should verify that the bridged NSString preserves the low surrogate value as 0xDC00.
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: e750c080-3819-4ab5-b5d6-c546b25801a3
📥 CommitsReviewing files that changed from the base of the PR and between a37e7f2 and 705533c.
📒 Files selected for processing (5)
Sorry, something went wrong.
@coderabbitai, re the low surrogate nitpick: added a lone low surrogate case (U+DC00) next to the high surrogate one in 8cc59bf, and renamed the original so the pair reads clearly. Both read the bridged code unit back as a number via getCharacters:range: instead of a UTF-8 conversion, since converting a lone surrogate to UTF-8 hangs the test runner on CI's iOS 17.5 simulator. |
Sorry, something went wrong.
|
Tip For best results, initiate chat on the files or code changes. @adrian-niculescu, great — both high (\uD834) and low (\uDC00) surrogate cases are now covered, and the renaming makes the pair read cleanly together. The getCharacters:range: / numeric approach is exactly right given the CI constraint you surfaced. Good to know about the iOS 17.5 simulator hang on UTF-8 lone surrogate conversion — I'll record that for future reference. ✏️ Learnings added Learnt from: adrian-niculescu URL: https://github.com/NativeScript/ios/pull/392 Timestamp: 2026-06-13T23:57:12.035Z Learning: In NativeScript iOS (NativeScript/ios), attempting to convert a lone (unpaired) UTF-16 surrogate to UTF-8 (e.g., via `[NSString UTF8String]` or `lengthOfBytesUsingEncoding:NSUTF8StringEncoding` in a way that triggers actual string conversion) can **hang the test runner** on the iOS 17.5 simulator in CI. Tests that verify lone surrogate preservation (high surrogates U+D800–U+DBFF or low surrogates U+DC00–U+DFFF) must read the code unit back as a numeric value via `getCharacters:range:` into a `uint16` buffer rather than attempting any UTF-8 string conversion. This was discovered while implementing tests for PR `#392`.
If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
Sorry, something went wrong.
|
I'm curious, should we not just use utf16 everywhere we can then? Also seems like we're doing extra steps there: first convert to std::utf16string by passing a buffer + length and then pass the copied buffer + length to NSString. I feel we could have a tns::ToNSString which just reads the utf16 buffer directly. I believe most of the utf8 usages we have in this repo come from the misconception that v8 wasn't using utf16 internally or that the utf8 string conversion would be faster somehow (I know I myself already fixed a bunch of utf8 issues with null character in the middle of the string) |
Sorry, something went wrong.
V8 strings are UTF-16, but several bridge points round-tripped them through UTF-8 before building an NSString (and one reverse direction did too). That path corrupts lone surrogates, which get replaced with U+FFFD, and where it used C-string APIs (stringWithUTF8String:, [NSString UTF8String] folded into a std::string) it truncated at an embedded NUL. Rework tns::ToUtf16String to read the V8 string's native two-byte buffer directly, which also drops the deprecated std::codecvt_utf8_utf16. Switch the DictionaryAdapter, Interop and ArgConverter string sites to ToUtf16String + stringWithCharacters:length:, and pass NSString straight to ToV8String instead of going through a UTF8String C string. Add TestRunner cases asserting lone surrogates survive the JS to NSString bridge: each reads the bridged string's first UTF-16 code unit straight out of its buffer and checks it is unchanged (high U+D834 and low U+DC00), whereas the old UTF-8 round trip would have turned either into U+FFFD.
Made tns::ToNSString read the V8 string's UTF-16 buffer directly and pointed the six bridge sites at it, instead of building a std::u16string and copying it into NSString at each one. That drops the extra copy and the repeated stringWithCharacters boilerplate, keeps the conversion in one place, and brings Interop back to matching vanilla. Kept the ToUtf16String rewrite as the general accessor. Added an embedded-NUL test next to the lone-surrogate ones, since the bridge now has to preserve NUL bytes too.
Indeed, V8 strings are UTF-16 the whole way down, so these UTF-8 round-trips are just leftover from the old assumption. Same root cause as the embedded-NUL cases you mentioned. And you're right that we don't need a new helper. tns::ToNSString(isolate, value) already exists, I'd just stopped calling it because its body still went through String::Utf8Value. I made it read the two-byte buffer and hand it straight to stringWithCharacters:length:, then pointed the call sites back at it. That drops the intermediate std::u16string copy and keeps the conversion in one place. Pushed. On utf16 everywhere: agreed. The spot this doesn't close yet is the reverse direction. ToV8String(NSString*) still goes through [value UTF8String], so it's NUL-safe but not surrogate-safe, and there's already a commented-out NewFromTwoByte path sitting in there for it. Happy to finish that so the bridge is symmetric, reading with getCharacters:range: rather than the commented getBytes:...NSUTF16StringEncoding (which can write a BOM). A wider UTF-8 sweep across the repo is probably its own PR. |
Sorry, something went wrong.
|
Thanks for taking a look at this! |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
V8 strings are UTF-16, but a few of the V8 <-> NSString bridge points round-trip through UTF-8 first, and that loses data in two ways:
The fix is to keep everything in UTF-16:
I left tns::ToVector alone on purpose. Its input is already a UTF-8 std::string, so it is a different conversion and not part of this bug.
Test: added a TestRunner case that bridges a JS string with a lone high surrogate to an NSString and checks the code unit survives. Without the fix it comes back as U+FFFD.
Formatting note: I matched each file's existing indentation (DictionaryAdapter.mm is 4-space, the others are 2-space) and did not run clang-format, to keep the diff to just the real changes.
We have shipped this in our downstream fork for a while.
Summary by CodeRabbit
Bug Fixes
Tests