fix(ios): prevent crash in saveFailedUpdate when server returns null fields#43
Open
MatthewPattell wants to merge 1 commit intorevopush:masterfrom
Open
Conversation
…fields Filter NSNull from the failed package dictionary before storing it in NSUserDefaults, and wrap the write in @try/@catch as a defensive guard. The server may return null for optional metadata fields (e.g. assetDownloadUrl, bundleDiffBlobUrl). NSJSONSerialization parses these to NSNull, which is not a property-list type. Passing such a dictionary to -[NSUserDefaults setObject:forKey:] raises NSInvalidArgumentException ("Attempt to insert non-property list object"). Because saveFailedUpdate: is invoked from -[CodePush init] via initializeUpdateAfterRestart -> rollbackPackage, an uncaught throw here aborts the process during native module instantiation - i.e. the app crashes on every launch until the corrupt state is cleared by uninstall + reinstall. The dictionary stored in failedUpdates is only consumed by +isFailedHash:, which reads the packageHash field (always an NSString), so dropping the URL/diff fields has no functional impact.
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.
Problem
-[CodePush saveFailedUpdate:]crashes the app on iOS withNSInvalidArgumentExceptionwhen the failed package metadata containsNSNullvalues for optional fields:Root cause
The package metadata is built from the server response via
NSJSONSerialization. JSONnullis parsed asNSNull. WhensaveFailedUpdate:writes the dictionary toNSUserDefaults,_CFPrefsValidateValueForKeyrejectsNSNull(onlyNSString/NSNumber/NSDate/NSArray/NSDictionary/NSDataare valid property-list types) and throwsNSInvalidArgumentException. The exception is uncaught and aborts the process.In the field this triggers for fields like
assetDownloadUrlandbundleDiffBlobUrl, which the server emits as `null` when no asset/diff URL is registered for the failed package.When it fires
`saveFailedUpdate:` is invoked from `-[CodePush init]` via `initializeUpdateAfterRestart` -> `rollbackPackage` (when a previous update was marked `isLoading=YES` and never confirmed via `notifyApplicationReady`). This runs during `RCTTurboModuleManager` instantiation, before any JS executes - so the app crashes on launch, every launch, until the corrupt state is cleared by an uninstall + reinstall.
Related history
This is a recurrence of microsoft/react-native-code-push#493 (fixed by PR #497) and #606. Those fixes only nil-checked the dictionary itself; they did not strip `NSNull` values from inside the dictionary, so the underlying bug was never fully addressed.
Fix
Two layers in `saveFailedUpdate:`:
The dictionary stored in `failedUpdates` is only consumed by `+isFailedHash:`, which reads `packageHash` (always an `NSString`, never null). Dropping the URL/diff fields has no functional impact.
Verification
Reproduced on iOS 26 / iPhone 13 mini against a deployment whose manifest contained `null` for `assetDownloadUrl` and `bundleDiffBlobUrl`. Before the fix: 100% crash on the second launch after a fresh install (first launch downloads the OTA, app is killed before `notifyApplicationReady`, second launch enters the rollback path and crashes in `saveFailedUpdate:`). After the fix: rollback completes cleanly and the app falls back to the binary bundle.