Skip to content

Commit 5d51fba

Browse files
Merge pull request #259 from ParsePlatform/richardross.isdirty.recursion
Fixed infinite recursion with `isDirty:` with recursive relation.
2 parents 2c1fdae + 7f5a132 commit 5d51fba

File tree

2 files changed

+53
-14
lines changed

2 files changed

+53
-14
lines changed

Parse/PFObject.m

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -657,19 +657,12 @@ - (BOOL)isDirty:(BOOL)considerChildren {
657657
if (self._state.deleted || dirty || [self _hasChanges]) {
658658
return YES;
659659
}
660+
660661
if (considerChildren) {
661-
// We only need to consider the currently estimated children here,
662-
// because they're the only ones that might need to be saved in a
663-
// subsequent call to save, which is the meaning of "dirtiness".
664-
__block BOOL retValue = NO;
665-
[_estimatedData enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
666-
if ([obj isKindOfClass:[PFObject class]] && [obj isDirty]) {
667-
retValue = YES;
668-
*stop = YES;
669-
}
670-
}];
671-
return retValue;
662+
NSMutableSet *seen = [NSMutableSet set];
663+
return [self _areChildrenDirty:seen];
672664
}
665+
673666
return NO;
674667
}
675668
}
@@ -680,6 +673,32 @@ - (void)_setDirty:(BOOL)aDirty {
680673
}
681674
}
682675

676+
- (BOOL)_areChildrenDirty:(NSMutableSet *)seenObjects {
677+
if ([seenObjects containsObject:self]) {
678+
return NO;
679+
}
680+
[seenObjects addObject:self];
681+
682+
@synchronized(lock) {
683+
[self checkpointAllMutableContainers];
684+
if (self._state.deleted || dirty || [self _hasChanges]) {
685+
return YES;
686+
}
687+
688+
// We only need to consider the currently estimated children here,
689+
// because they're the only ones that might need to be saved in a
690+
// subsequent call to save, which is the meaning of "dirtiness".
691+
__block BOOL retValue = NO;
692+
[_estimatedData enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
693+
if ([obj isKindOfClass:[PFObject class]] && [obj _areChildrenDirty:seenObjects]) {
694+
retValue = YES;
695+
*stop = YES;
696+
}
697+
}];
698+
return retValue;
699+
}
700+
}
701+
683702
///--------------------------------------
684703
#pragma mark - Mutable container management
685704
///--------------------------------------

Tests/Unit/ObjectUnitTests.m

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,9 @@ - (void)testRevert {
230230
NSDate *date = [NSDate date];
231231
NSNumber *number = @0.75;
232232
PFObject *object = [PFObject _objectFromDictionary:@{ @"yarr" : date,
233-
@"score": number }
234-
defaultClassName:@"Test" completeData:YES];
233+
@"score" : number }
234+
defaultClassName:@"Test"
235+
completeData:YES];
235236
object[@"yarr"] = @"yolo";
236237
[object revert];
237238
XCTAssertEqualObjects(object[@"yarr"], date);
@@ -243,12 +244,31 @@ - (void)testRevertObjectForKey {
243244
NSNumber *number = @0.75;
244245
PFObject *object = [PFObject _objectFromDictionary:@{ @"yarr" : date,
245246
@"score" : @1.0 }
246-
defaultClassName:@"Test" completeData:YES];
247+
defaultClassName:@"Test"
248+
completeData:YES];
247249
object[@"yarr"] = @"yolo";
248250
object[@"score"] = number;
249251
[object revertObjectForKey:@"yarr"];
250252
XCTAssertEqualObjects(object[@"yarr"], date);
251253
XCTAssertEqualObjects(object[@"score"], number);
252254
}
253255

256+
#pragma mark Dirty
257+
258+
- (void)testRecursiveDirty {
259+
// A -> B -> A is a supported use-case, but it would crash on older SDK versions.
260+
PFObject *objectA = [PFObject objectWithClassName:@"A"];
261+
PFObject *objectB = [PFObject objectWithClassName:@"B"];
262+
263+
[objectA _mergeAfterSaveWithResult:@{ @"objectId" : @"foo",
264+
@"B" : objectB }
265+
decoder:[PFDecoder objectDecoder]];
266+
267+
[objectB _mergeAfterSaveWithResult:@{ @"objectId" : @"bar",
268+
@"A" : objectA }
269+
decoder:[PFDecoder objectDecoder]];
270+
271+
XCTAssertFalse([objectA isDirty]);
272+
}
273+
254274
@end

0 commit comments

Comments
 (0)