Skip to content

Commit 2a5ae7d

Browse files
authored
[ASDisplayNode] Fix a crash in insertSubnode (#2122)
* [ASDisplayNode] Fix a crash in insertSubnode If a node is already a subnode to a supernode, Inserting it again can lead to a crash. Here is a simple repro of the crash: ``` ASDisplayNode *subnode = [[ASDisplayNode alloc] init]; ASDisplayNode *supernode = [[ASDisplayNode alloc] init]; [supernode addSubnode:subnode]; // Crash on next line [supernode insertSubnode:subnode atIndex:1]; ``` The issue is that all the checks around subnode array boundaries are done BEFORE `subnode` is removed from its `supernode`. If it happens that the `supernode` is self, then removing the `subnode` causes all our index checks to no longer be valid. Here is the relevant code: ``` __instanceLock__.lock(); NSUInteger subnodesCount = _subnodes.count; __instanceLock__.unlock(); ////// Here we check our indexes if (subnodeIndex > subnodesCount || subnodeIndex < 0) { ASDisplayNodeFailAssert(@"Cannot insert a subnode at index %ld. Count is %ld", (long)subnodeIndex, (long)subnodesCount); return; } … ///////// Here our indexes could invalidate if self subnode’s supernode [subnode removeFromSupernode]; [oldSubnode removeFromSupernode]; __instanceLock__.lock(); if (_subnodes == nil) { _subnodes = [[NSMutableArray alloc] init]; } ////// Here would can crash if our index is too big [_subnodes insertObject:subnode atIndex:subnodeIndex]; _cachedSubnodes = nil; __instanceLock__.unlock(); ``` * add a separate check for this special case
1 parent 83c6ff8 commit 2a5ae7d

File tree

2 files changed

+19
-1
lines changed

2 files changed

+19
-1
lines changed

Source/ASDisplayNode.mm

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2114,11 +2114,18 @@ - (void)_insertSubnode:(ASDisplayNode *)subnode atSubnodeIndex:(NSInteger)subnod
21142114
__instanceLock__.lock();
21152115
NSUInteger subnodesCount = _subnodes.count;
21162116
__instanceLock__.unlock();
2117+
21172118
if (subnodeIndex > subnodesCount || subnodeIndex < 0) {
21182119
ASDisplayNodeFailAssert(@"Cannot insert a subnode at index %ld. Count is %ld", (long)subnodeIndex, (long)subnodesCount);
21192120
return;
21202121
}
2121-
2122+
2123+
// Check if subnode is already a in _subnodes. If so make sure the subnodeIndex will not be out of bounds once we call [subnode removeFromSupernode]
2124+
if (subnode.supernode == self && subnodeIndex >= subnodesCount) {
2125+
ASDisplayNodeFailAssert(@"node %@ is already a subnode of %@. index %ld will be out of bounds once we call [subnode removeFromSupernode]. This can be caused by using automaticallyManagesSubnodes while also calling addSubnode explicitly.", subnode, self, subnodeIndex);
2126+
return;
2127+
}
2128+
21222129
// Disable appearance methods during move between supernodes, but make sure we restore their state after we do our thing
21232130
ASDisplayNode *oldParent = subnode.supernode;
21242131
BOOL disableNotifications = shouldDisableNotificationsForMovingBetweenParents(oldParent, self);

Tests/ASDisplayNodeTests.mm

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2783,4 +2783,15 @@ - (void)testPlaceholder
27832783
XCTAssertTrue(hasPlaceholderLayer);
27842784
}
27852785

2786+
- (void)testInsertExistingSubnode
2787+
{
2788+
DeclareNodeNamed(parent);
2789+
DeclareNodeNamed(child);
2790+
2791+
[parent addSubnode:child];
2792+
// Previously this would cause a crash. We now protect inserting an already existing subnode out of bounds
2793+
XCTAssertThrows([parent insertSubnode:child atIndex:1]);
2794+
XCTAssertTrue(parent.subnodes.count == 1);
2795+
}
2796+
27862797
@end

0 commit comments

Comments
 (0)