From 2aca02150481ab558f136e816412cd0cff740c5f Mon Sep 17 00:00:00 2001 From: Adam Comella Date: Mon, 5 Dec 2016 10:01:28 -0800 Subject: [PATCH] iOS: Fix dequeueTasks crash in image loader Summary: This fixes a crash occurring [on this line](https://github.com/facebook/react-native/blob/f9f32eb426b5385e199ef6e1d2b610dfe20e60e7/Libraries/Image/RCTImageLoader.m#L253). It was reported in a comment in #10433. The problem is that `task` is deallocated at this point and is unsafe to use. Removing it from `_pendingTasks` dropped its ref count to 0. [The ARC docs](http://clang.llvm.org/docs/AutomaticReferenceCounting.html#fast-enumeration-iteration-variables) state that, by default, loop variables in fast enumeration loops are not retained. That's why `task`'s ref count is 0. It's likely we ran into this bug because the code disobeyed the [reverseObjectEnumerator docs](https://developer.apple.com/reference/foundation/nsarray/1415734-reverseobjectenumerator) which state that "you must not modify the array during enumeration". The default retention policy for fast enumeration seems to assume you follow this. To fix this bug and avoid other potential pitfalls, the code now follows the docs and `_pendingTa Closes https://github.com/facebook/react-native/pull/11296 Differential Revision: D4277167 Pulled By: javache fbshipit-source-id: 1211b32935bab7f4080dc00b88d85348786e859a --- Libraries/Image/RCTImageLoader.m | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 4f70fd0b9b4232..b8edc9148a980c 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -236,10 +236,14 @@ - (void)dequeueTasks { dispatch_async(_URLRequestQueue, ^{ // Remove completed tasks + NSMutableArray *tasksToRemove = nil; for (RCTNetworkTask *task in self->_pendingTasks.reverseObjectEnumerator) { switch (task.status) { case RCTNetworkTaskFinished: - [self->_pendingTasks removeObject:task]; + if (!tasksToRemove) { + tasksToRemove = [NSMutableArray new]; + } + [tasksToRemove addObject:task]; self->_activeTasks--; break; case RCTNetworkTaskPending: @@ -248,13 +252,20 @@ - (void)dequeueTasks // Check task isn't "stuck" if (task.requestToken == nil) { RCTLogWarn(@"Task orphaned for request %@", task.request); - [self->_pendingTasks removeObject:task]; + if (!tasksToRemove) { + tasksToRemove = [NSMutableArray new]; + } + [tasksToRemove addObject:task]; self->_activeTasks--; [task cancel]; } break; } } + + if (tasksToRemove) { + [self->_pendingTasks removeObjectsInArray:tasksToRemove]; + } // Start queued decode NSInteger activeDecodes = self->_scheduledDecodes - self->_pendingDecodes.count;