Skip to content

Commit 19350b1

Browse files
lukeharveymeta-codesync[bot]
authored andcommitted
Fix NSRangeException crash in TextInput delegate adapters (#55950)
Summary: The existing range guard in the UITextView delegate (added in #24084) only checks range.location + range.length > text.length. It doesn't handle range.location > text.length, which causes an unsigned integer underflow in the clamped length, still crashing with NSRangeException. See #45050 for another report of this. The UITextField delegate has no range validation at all. This PR adds a range.location > textLength early return to both delegate adapters, ahead of the existing length clamping. ## Changelog: <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: [ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests --> [IOS] [FIXED] - Fix NSRangeException crash in RCTBackedTextInputDelegateAdapter when text range is out of bounds Pull Request resolved: #55950 Test Plan: This is a race condition between iOS computing the text range and React Native updating the backing text (e.g. controlled TextInput state updates, maxLength truncation, autocorrect). It's difficult to reproduce deterministically but shows up in production crash logs. The fix is straightforward defensive bounds checking before calling replaceCharactersInRange:. Reviewed By: shwanton Differential Revision: D95564295 Pulled By: cipolleschi fbshipit-source-id: 721e803f03e973be462a18fd3f5d7a5aeb073b3e
1 parent 9d231af commit 19350b1

1 file changed

Lines changed: 16 additions & 2 deletions

File tree

packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,15 @@ - (BOOL)textField:(__unused UITextField *)textField
9696
return YES;
9797
}
9898

99+
// Clamp the range to the current text length to avoid NSRangeException.
100+
NSUInteger textLength = _backedTextInputView.attributedText.length;
101+
if (range.location > textLength) {
102+
return NO;
103+
}
104+
if (range.location + range.length > textLength) {
105+
range = NSMakeRange(range.location, textLength - range.location);
106+
}
107+
99108
NSMutableAttributedString *attributedString = [_backedTextInputView.attributedText mutableCopy];
100109
[attributedString replaceCharactersInRange:range withString:newText];
101110
[_backedTextInputView setAttributedText:[attributedString copy]];
@@ -292,8 +301,13 @@ - (BOOL)textView:(__unused UITextView *)textView shouldChangeTextInRange:(NSRang
292301
return NO;
293302
}
294303

295-
if (range.location + range.length > _backedTextInputView.text.length) {
296-
range = NSMakeRange(range.location, _backedTextInputView.text.length - range.location);
304+
// Clamp the range to the current text length to avoid NSRangeException.
305+
NSUInteger textLength = _backedTextInputView.attributedText.length;
306+
if (range.location > textLength) {
307+
return NO;
308+
}
309+
if (range.location + range.length > textLength) {
310+
range = NSMakeRange(range.location, textLength - range.location);
297311
} else if ([newText isEqualToString:text]) {
298312
_textDidChangeIsComing = YES;
299313
return YES;

0 commit comments

Comments
 (0)