realm-swift icon indicating copy to clipboard operation
realm-swift copied to clipboard

Deleting a bunch of objects at once may cause a crash in migration

Open anlaital opened this issue 7 years ago • 6 comments

Goals

Delete objects in a migration.

Expected Results

Objects get deleted.

Actual Results

Realm crashes with the following stack trace:

image

This crash is caused by the following method (array index out-of-bounds):

- (void)deleteObjectsMarkedForDeletion {
    for (NSString *className in deletedObjectIndices.allKeys) {
        RLMResults *objects = [_realm allObjects:className]; // objects.count == 811 at start, decreasing by one with each deletion as this is a self-updating type
        for (NSNumber *index in deletedObjectIndices[className]) {
            // here the sort order is random (or implicitly user-defined); this means that lower indices may get deleted before higher ones, causing the crash
            RLMObject *object = objects[index.longValue]; 
            [_realm deleteObject:object];
        }
    }
}

Here's a very simple fixed version that sorts the indices before looping (this fixes the crash, haven't verified that it works semantically correctly):

- (void)deleteObjectsMarkedForDeletion {
    for (NSString *className in deletedObjectIndices.allKeys) {
        RLMResults *objects = [_realm allObjects:className];
        // Delete objects with indices sorted in descending order to prevent array index out-of-bounds crashes.
        NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:nil ascending:NO];
        NSArray *sortedIndices = [deletedObjectIndices[className] sortedArrayUsingDescriptors:@[sort]];
        for (NSNumber *index in sortedIndices) {
            RLMObject *object = objects[index.longValue];
            [_realm deleteObject:object];
        }
    }
}

Version of Realm and Tooling

Realm framework version: 3.0.2

anlaital avatar Jan 02 '18 18:01 anlaital

Can you please share some code that reproduces the issue so we'll be able to verify that whatever fix we make addresses the problem you're encountering?

bdash avatar Jan 02 '18 19:01 bdash

This was simple to reproduce. First create some data (doesn't matter what the data is):

class O: Object {
    
    @objc dynamic var i = 0
    
}

var conf = Realm.Configuration.defaultConfiguration

let realm = try! Realm(configuration: conf)

try! realm.write {
    for _ in 0..<1000 {
        realm.add(O())
    }
}

Then increment version number and implement the following migration block:

conf.schemaVersion = 1
conf.migrationBlock = {
    migration, oldSchemaVersion in
    
    var toDelete = [MigrationObject]()
    
    migration.enumerateObjects(ofType: O.className()) {
        oldObject, newObject in
        toDelete.append(newObject!)
    }
    
    // deleting objects in reversed order causes the crash
    for object in toDelete.reversed() {
        migration.delete(object)
    }
}

anlaital avatar Jan 02 '18 20:01 anlaital

Thank you!

bdash avatar Jan 02 '18 20:01 bdash

@anlaital did you find any workaround for this? :)

fmorau avatar May 22 '20 13:05 fmorau

Maintaining the enumeration order when deleting the objects doesn't crash.

anlaital avatar May 22 '20 14:05 anlaital

@anlaital 👍🏻👍🏻 Thank you, will try :)

fmorau avatar May 22 '20 14:05 fmorau