Fix (unreported) crash when undoing after ID deletion.
authorBastien Montagne <montagne29@wanadoo.fr>
Fri, 8 Feb 2019 17:45:57 +0000 (18:45 +0100)
committerBastien Montagne <montagne29@wanadoo.fr>
Fri, 8 Feb 2019 17:54:52 +0000 (18:54 +0100)
Yes, we do can undo an ID deletion now.

However, this requires extra care in UI 'remapping' to new IDs step
(when undoing, we do not fully reload the UI from saved .blend).
Otherwise, new UI (i.e. one from saved .blend file) might reference
IDs that where freed in old bmain (the one before the undo), we cannot
use those to get ID name then, that would be a nasty use-after-free!

To prevent this, we generate a GSet of all valid ID pointers at that
time (i.e. those found in both old and new Main's), and ensure any ID
we try to remap by its name is in that GSet. Otherwise, there is no
possible remapping, just return NULL.

source/blender/blenkernel/BKE_library_idmap.h
source/blender/blenkernel/intern/blendfile.c
source/blender/blenkernel/intern/library_idmap.c
source/blender/blenloader/BLO_readfile.h
source/blender/blenloader/intern/readfile.c

index ec814ac..0e16aa1 100644 (file)
@@ -26,8 +26,10 @@ struct IDNameLib_Map;
 struct Main;
 
 struct IDNameLib_Map *BKE_main_idmap_create(
-        struct Main *bmain)
-        ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
+        struct Main *bmain,
+        const bool create_valid_ids_set,
+        struct Main *old_bmain)
+        ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1);
 void BKE_main_idmap_destroy(
         struct IDNameLib_Map *id_typemap)
         ATTR_NONNULL();
index 34cc0a6..51c5b52 100644 (file)
@@ -203,7 +203,7 @@ static void setup_app_data(
                }
 
                /* BKE_blender_globals_clear will free G_MAIN, here we can still restore pointers */
-               blo_lib_link_restore(bfd->main, CTX_wm_manager(C), curscene, cur_view_layer);
+               blo_lib_link_restore(bmain, bfd->main, CTX_wm_manager(C), curscene, cur_view_layer);
                if (win) {
                        curscene = win->scene;
                }
index 2745a1f..3ab5cba 100644 (file)
@@ -65,6 +65,7 @@ struct IDNameLib_TypeMap {
 struct IDNameLib_Map {
        struct IDNameLib_TypeMap type_maps[MAX_LIBARRAY];
        struct Main *bmain;
+       struct GSet *valid_id_pointers;
 };
 
 static struct IDNameLib_TypeMap *main_idmap_from_idcode(struct IDNameLib_Map *id_map, short id_type)
@@ -77,7 +78,20 @@ static struct IDNameLib_TypeMap *main_idmap_from_idcode(struct IDNameLib_Map *id
        return NULL;
 }
 
-struct IDNameLib_Map *BKE_main_idmap_create(struct Main *bmain)
+/**
+ * Generate mapping from ID type/name to ID pointer for given \a bmain.
+ *
+ * \note When used during undo/redo, there is no guaranty that ID pointers from UI area
+ *       are not pointing to freed memory (when some IDs have been deleted). To avoid crashes
+ *       in those cases, one can provide the 'old' (aka current) Main databse as reference.
+ *       #BKE_main_idmap_lookup_id will then check that given ID does exist in \a old_bmain
+ *       before trying to use it.
+ *
+ * \param create_valid_ids_set If \a true, generate a reference to prevent freed memory accesses.
+ * \param old_bmain If not NULL, its IDs will be added the the valid references set.
+ */
+struct IDNameLib_Map *BKE_main_idmap_create(
+        struct Main *bmain, const bool create_valid_ids_set, struct Main *old_bmain)
 {
        struct IDNameLib_Map *id_map = MEM_mallocN(sizeof(*id_map), __func__);
 
@@ -92,6 +106,16 @@ struct IDNameLib_Map *BKE_main_idmap_create(struct Main *bmain)
 
        id_map->bmain = bmain;
 
+       if (create_valid_ids_set) {
+               id_map->valid_id_pointers = BKE_main_gset_create(bmain, NULL);
+               if (old_bmain != NULL) {
+                       id_map->valid_id_pointers = BKE_main_gset_create(old_bmain, id_map->valid_id_pointers);
+               }
+       }
+       else {
+               id_map->valid_id_pointers = NULL;
+       }
+
        return id_map;
 }
 
@@ -151,7 +175,15 @@ ID *BKE_main_idmap_lookup(struct IDNameLib_Map *id_map, short id_type, const cha
 
 ID *BKE_main_idmap_lookup_id(struct IDNameLib_Map *id_map, const ID *id)
 {
-       return BKE_main_idmap_lookup(id_map, GS(id->name), id->name + 2, id->lib);
+       /* When used during undo/redo, this function cannot assume that given id points to valid memory
+        * (i.e. has not been freed), so it has to check that it does exist in 'old' (aka current) Main database.
+        * Otherwise, we cannot provide new ID pointer that way (would crash accessing freed memory
+        * when trying to get ID name).
+        */
+       if (id_map->valid_id_pointers == NULL || BLI_gset_haskey(id_map->valid_id_pointers, id)) {
+               return BKE_main_idmap_lookup(id_map, GS(id->name), id->name + 2, id->lib);
+       }
+       return NULL;
 }
 
 void BKE_main_idmap_destroy(struct IDNameLib_Map *id_map)
@@ -165,6 +197,10 @@ void BKE_main_idmap_destroy(struct IDNameLib_Map *id_map)
                }
        }
 
+       if (id_map->valid_id_pointers != NULL) {
+               BLI_gset_free(id_map->valid_id_pointers, NULL);
+       }
+
        MEM_freeN(id_map);
 }
 
index 90f46cc..e8bfeec 100644 (file)
@@ -144,7 +144,7 @@ void *BLO_library_read_struct(struct FileData *fd, struct BHead *bh, const char
 
 /* internal function but we need to expose it */
 void blo_lib_link_restore(
-        struct Main *newmain, struct wmWindowManager *curwm,
+        struct Main *oldmain, struct Main *newmain, struct wmWindowManager *curwm,
         struct Scene *curscene, struct ViewLayer *cur_render_layer);
 
 typedef void (*BLOExpandDoitCallback) (void *fdhandle, struct Main *mainvar, void *idv);
index fa3d345..f511bf9 100644 (file)
@@ -7635,9 +7635,9 @@ static void lib_link_workspace_layout_restore(struct IDNameLib_Map *id_map, Main
  * Used to link a file (without UI) to the current UI.
  * Note that it assumes the old pointers in UI are still valid, so old Main is not freed.
  */
-void blo_lib_link_restore(Main *newmain, wmWindowManager *curwm, Scene *curscene, ViewLayer *cur_view_layer)
+void blo_lib_link_restore(Main *oldmain, Main *newmain, wmWindowManager *curwm, Scene *curscene, ViewLayer *cur_view_layer)
 {
-       struct IDNameLib_Map *id_map = BKE_main_idmap_create(newmain);
+       struct IDNameLib_Map *id_map = BKE_main_idmap_create(newmain, true, oldmain);
 
        for (WorkSpace *workspace = newmain->workspaces.first; workspace; workspace = workspace->id.next) {
                ListBase *layouts = BKE_workspace_layouts_get(workspace);