Repository navigation
Fix Text truncation at NULL character on iOS and Android (#24129) - #57906
Closed
kulkarni-rohan wants to merge 1 commit into
Closed
kulkarni-rohan wants to merge 1 commit into
kulkarni-rohan wants to merge 1 commit into
Conversation
…t#24129) Fixes react#24129 where <Text>{'Hello \u0000 World'}</Text> renders as 'Hello' truncated. Root cause: JS String preserves \u0000, but iOS stringWithUTF8String: and stringWithCString: and Android NewStringUTF use C-string NUL-terminated APIs that stop at embedded \0. Fix: Replace with length-aware conversions: - iOS: initWithBytes:length:encoding: (same as correct FollyConvert.mm pattern and MapBufferBuilder memcpy) - Android: convert UTF-8 std::string with size to UTF-16 via rt.utf16() + NewString, and reverse via GetStringChars + GetStringLength + createFromUtf16 Covers all surfaces (Text, TextInput, any TurboModule string param) across Fabric and old arch. [GENERAL] [FIXED] - Fix Text truncation when string contains NULL character \u0000 (react#24129)
|
@fabriziocucci has imported this pull request. If you are a Meta employee, you can view this in D115707536. |
|
@fabriziocucci merged this pull request in 5906cfb. |
22 of 40 tasks
MelvinBot
added a commit
to Expensify/App
that referenced
this pull request
Sep 17, 2026
…reasons The NUL-truncation bug was already fixed upstream in react/react-native#57906 (merged as 5906cfb, ships in v0.88.0). Replace the hand-rolled single-file patch with a verbatim backport of that commit so it can be dropped on the 0.88 upgrade without re-deriving anything, and shorten the details.md Reason entries for patches 042 and 043 to the essentials. Co-authored-by: daledah <daledah@users.noreply.github.com>
tvinhas
pushed a commit
to tvinhas/react-native-macos
that referenced
this pull request
Oct 21, 2026
react#57906) Summary: Fixes react#24129 **Problem:** `<Text>{'Hello \u0000 World'}</Text>` renders as "Hello" (truncated at \u0000). Does NOT reproduce when Debug JS Remotely (JSON preserves \u0000), but reproduces in production Hermes+Fabric on iOS and Android. **Root cause:** JS String preserves \u0000 (length includes it), C++ std::string from JSI utf8() also preserves via size. Truncation happens at platform bridge where C-string NUL-terminated APIs are used: - iOS: RCTAttributedTextUtils.mm:420 stringWithUTF8String:fragment.string.c_str() stops at embedded \0 - iOS: RCTConversions.h RCTNSStringFromString via stringWithCString: and reverse std::string{UTF8String} - iOS: RCTTurboModule.mm convertJSIStringToNSString same - Android: JavaTurboModule.cpp NewStringUTF(c_str()) expects NUL-terminated **Fix (no new API):** Replace the C-string APIs with length-aware equivalents. On iOS, `[[NSString alloc] initWithBytes:length:encoding:]` for std::string to NSString, and `dataUsingEncoding:` for the reverse, matching the existing correct pattern in FollyConvert.mm:31 and MapBufferBuilder.cpp. On Android the conversion now goes through UTF-16 in both directions rather than UTF-8: - outbound: `rt.utf16(...)` then `NewString(jchar*, len)` - inbound: `GetStringLength()` + `GetStringChars()` then `jsi::String::createFromUtf16(...)`, released with `ReleaseStringChars` That is worth calling out because it fixes a second latent bug. `NewStringUTF` expects *modified* UTF-8 (CESU-8), so it already mishandled 4-byte sequences such as emoji and other supplementary-plane characters. Going UTF-16 to UTF-16 avoids both problems. All surfaces (Text, TextInput, accessibility, TurboModule params) are fixed at once because the shared converters are fixed. ## NOTE: nil becomes empty string at two call sites RCTTurboModule.mm and RCTAttributedTextUtils.mm gain a `?: @""` fallback. Previously `stringWithUTF8String:` returned `nil` for invalid UTF-8 and callers received nil; they now receive `@""`. This matches the fallback `RCTNSStringFromString` already had, and `@""` is safer than nil for the ObjC call sites involved, but it is a behaviour change rather than a pure refactor. ## Changelog: [GENERAL] [FIXED] - Fix Text truncation when string contains NULL character \u0000 (react#24129) Pull Request resolved: react#57906 Test Plan: **Reproduction (RNTester):** - Add screen Text > NullCharacter with <Text>{'Hello\u0000World'}</Text> and 'A\u0000B\u0000C' - Before: "Hello" truncated - After: "HelloWorld" full (invisible \0 zero-width but World visible, length preserved) Closes react#24129 ## Added on import: regression tests Four cases added to the existing `React/Tests/Text/RCTAttributedTextUtilsTest.mm`, which is owned by `TextTestsApple`: ``` buck2 test fbsource//xplat/js/react-native-github:TextTestsApple → Pass 31. Fail 0. ``` Restoring the pre-fix `RCTConversions.h` and `RCTAttributedTextUtils.mm` and re-running fails all four and nothing else: ``` ✗ RCTAttributedTextUtilsTest/testNSStringFromStringPreservesEmbeddedNull ✗ RCTAttributedTextUtilsTest/testStringFromNSStringPreservesEmbeddedNull ✗ RCTAttributedTextUtilsTest/testStringConversionRoundTripsEmbeddedNull ✗ RCTAttributedTextUtilsTest/testAttributedStringFromFragmentPreservesEmbeddedNull Tests finished: Pass 27. Fail 4. ``` They cover `RCTNSStringFromString`, `RCTStringFromNSString`, a round trip through both, and the real AttributedString to NSAttributedString path. The changed function in RCTAttributedTextUtils.mm (`RCTNSAttributedStringFragmentFromFragment`) is static, so that last one goes through the public `RCTNSAttributedStringFromAttributedString`. This replaces the original `node -p "'a\u0000b'.length"` check, which exercised JavaScript string length rather than any of the changed code. The Android hunks are not covered by these tests. They are iOS-only test targets and there is no equivalent JNI-level unit test in tree, so the Android side rests on code review. ## On the reverse-direction allocation `RCTStringFromNSString` now allocates an `NSData` where it previously used `UTF8String` (an interior pointer). That converter has 6 callers. The hot one is `RCTNSStringFromString` with 43 callers, and it does not add an allocation: `stringWithCString:` and `initWithBytes:` both allocate an NSString. Keeping the `NSData` form deliberately, because the cheaper alternative relies on `UTF8String`'s buffer containing embedded NULs, which is exactly the ambiguity this diff removes. `arc lint -e extra` on the test file reports only pre-existing warnings plus a NULLSAFECLANG infrastructure failure. Reviewed By: cipolleschi Differential Revision: D115707536 Pulled By: fabriziocucci fbshipit-source-id: 4cde8d4b584e0305fcfc3a4520f3690b411500cb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #24129
Summary:
Problem:
<Text>{'Hello \u0000 World'}</Text>renders as "Hello" (truncated at \u0000). Does NOT reproduce when Debug JS Remotely (JSON preserves \u0000), but reproduces in production Hermes+Fabric on iOS and Android.Root cause: JS String preserves \u0000 (length includes it), C++ std::string from JSI utf8() also preserves via size. Truncation happens at platform bridge where C-string NUL-terminated APIs are used:
Fix (4 files, ~10 lines, no new API): Replace C-string APIs with length-aware [[NSString alloc] initWithBytes:data length:size encoding:NSUTF8StringEncoding] (same as existing correct pattern in FollyConvert.mm:31 and MapBufferBuilder.cpp) and Android convertUTF8ToUTF16 + NewString(jchar*, len) + assign(jsChars, GetStringUTFLength()). All surfaces (Text, TextInput, accessibility, TurboModule params) fixed at once because shared converters are fixed.
Changelog:
[GENERAL] [FIXED] - Fix Text truncation when string contains NULL character \u0000 (#24129)
Test Plan:
Reproduction (RNTester):
Commands:
Closes #24129