diff --git a/source/blender/blenkernel/BKE_main.h b/source/blender/blenkernel/BKE_main.h index daf394b811a..f4ac0cfd0e7 100644 --- a/source/blender/blenkernel/BKE_main.h +++ b/source/blender/blenkernel/BKE_main.h @@ -138,6 +138,13 @@ typedef struct Main { */ bool is_locked_for_linking; + /** + * When set, indicates that an unrecoverable error/data corruption was detected. + * Should only be set by readfile code, and used by upper-level code (typically #setup_app_data) + * to cancel a file reading operation. + */ + bool is_read_invalid; + /** * True if this main is the 'GMAIN' of current Blender. * diff --git a/source/blender/blenkernel/intern/blendfile.cc b/source/blender/blenkernel/intern/blendfile.cc index 8f0e462cd27..a0ecb0c95b5 100644 --- a/source/blender/blenkernel/intern/blendfile.cc +++ b/source/blender/blenkernel/intern/blendfile.cc @@ -476,6 +476,13 @@ void BKE_blendfile_read_setup_ex(bContext *C, const bool startup_update_defaults, const char *startup_app_template) { + if (bfd->main->is_read_invalid) { + BKE_reports_prepend(reports->reports, + "File could not be read, critical data corruption detected"); + BLO_blendfiledata_free(bfd); + return; + } + if (startup_update_defaults) { if ((params->skip_flags & BLO_READ_SKIP_DATA) == 0) { BLO_update_defaults_startup_blend(bfd->main, startup_app_template); @@ -503,6 +510,10 @@ struct BlendFileData *BKE_blendfile_read(const char *filepath, } BlendFileData *bfd = BLO_read_from_file(filepath, eBLOReadSkip(params->skip_flags), reports); + if (bfd && bfd->main->is_read_invalid) { + BLO_blendfiledata_free(bfd); + bfd = nullptr; + } if (bfd) { handle_subversion_warning(bfd->main, reports); } @@ -519,6 +530,10 @@ struct BlendFileData *BKE_blendfile_read_from_memory(const void *filebuf, { BlendFileData *bfd = BLO_read_from_memory( filebuf, filelength, eBLOReadSkip(params->skip_flags), reports); + if (bfd && bfd->main->is_read_invalid) { + BLO_blendfiledata_free(bfd); + bfd = nullptr; + } if (bfd) { /* Pass. */ } @@ -535,6 +550,10 @@ struct BlendFileData *BKE_blendfile_read_from_memfile(Main *bmain, { BlendFileData *bfd = BLO_read_from_memfile( bmain, BKE_main_blendfile_path(bmain), memfile, params, reports); + if (bfd && bfd->main->is_read_invalid) { + BLO_blendfiledata_free(bfd); + bfd = nullptr; + } if (bfd) { /* Removing the unused workspaces, screens and wm is useless here, setup_app_data will switch * those lists with the ones from old bmain, which freeing is much more efficient than diff --git a/source/blender/blenloader/BLO_readfile.h b/source/blender/blenloader/BLO_readfile.h index 8b5f9d10044..e28688b6bab 100644 --- a/source/blender/blenloader/BLO_readfile.h +++ b/source/blender/blenloader/BLO_readfile.h @@ -288,6 +288,26 @@ struct LinkNode *BLO_blendhandle_get_linkable_groups(BlendHandle *bh); */ void BLO_blendhandle_close(BlendHandle *bh); +/** Mark the given Main (and the 'root' local one in case of lib-split Mains) as invalid, and + * generate an error report containing given `message`. */ +void BLO_read_invalidate_message(BlendHandle *bh, struct Main *bmain, const char *message); + +/** + * BLI_assert-like macro to check a condition, and if `false`, fail the whole .blend reading + * process by marking the Main data-base as invalid, and returning provided `_ret_value`. + * + * NOTE: About usages: + * - #BLI_assert should be used when the error is considered as a bug, but there is some code to + * recover from it and produce a valid Main data-base. + * - #BLO_read_assert_message should be used when the error is not considered as recoverable. + */ +#define BLO_read_assert_message(_check_expr, _ret_value, _bh, _bmain, _message) \ + if (_check_expr) { \ + BLO_read_invalidate_message((_bh), (_bmain), (_message)); \ + return _ret_value; \ + } \ + (void)0 + /** \} */ #define BLO_GROUP_MAX 32 diff --git a/source/blender/blenloader/intern/readblenentry.cc b/source/blender/blenloader/intern/readblenentry.cc index 6b59ef87cec..c7884b3ae75 100644 --- a/source/blender/blenloader/intern/readblenentry.cc +++ b/source/blender/blenloader/intern/readblenentry.cc @@ -385,6 +385,13 @@ void BLO_blendhandle_close(BlendHandle *bh) blo_filedata_free(fd); } +void BLO_read_invalidate_message(BlendHandle *bh, Main *bmain, const char *message) +{ + FileData *fd = reinterpret_cast(bh); + + blo_readfile_invalidate(fd, bmain, message); +} + /**********/ BlendFileData *BLO_read_from_file(const char *filepath, diff --git a/source/blender/blenloader/intern/readfile.cc b/source/blender/blenloader/intern/readfile.cc index 29730f7f10a..bbcc0d54c14 100644 --- a/source/blender/blenloader/intern/readfile.cc +++ b/source/blender/blenloader/intern/readfile.cc @@ -320,6 +320,10 @@ static void add_main_to_main(Main *mainvar, Main *from) ListBase *lbarray[INDEX_ID_MAX], *fromarray[INDEX_ID_MAX]; int a; + if (from->is_read_invalid) { + mainvar->is_read_invalid = true; + } + set_listbasepointers(mainvar, lbarray); a = set_listbasepointers(from, fromarray); while (a--) { @@ -340,6 +344,9 @@ void blo_join_main(ListBase *mainlist) } while ((tojoin = mainl->next)) { + if (tojoin->is_read_invalid) { + mainl->is_read_invalid = true; + } add_main_to_main(mainl, tojoin); BLI_remlink(mainlist, tojoin); BKE_main_free(tojoin); @@ -548,6 +555,21 @@ static Main *blo_find_main(FileData *fd, const char *filepath, const char *relab return m; } +void blo_readfile_invalidate(FileData *fd, Main *bmain, const char *message) +{ + /* Tag given bmain, and 'root 'local' main one (in case given one is a library one) as invalid. + */ + bmain->is_read_invalid = true; + for (; bmain->prev != nullptr; bmain = bmain->prev) + ; + bmain->is_read_invalid = true; + + BLO_reportf_wrap(fd->reports, + RPT_ERROR, + "A critical error happened (the blend file is likely corrupted): %s", + message); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -3564,15 +3586,33 @@ static void do_versions(FileData *fd, Library *lib, Main *main) main->build_hash); } - blo_do_versions_pre250(fd, lib, main); - blo_do_versions_250(fd, lib, main); - blo_do_versions_260(fd, lib, main); - blo_do_versions_270(fd, lib, main); - blo_do_versions_280(fd, lib, main); - blo_do_versions_290(fd, lib, main); - blo_do_versions_300(fd, lib, main); - blo_do_versions_400(fd, lib, main); - blo_do_versions_cycles(fd, lib, main); + if (!main->is_read_invalid) { + blo_do_versions_pre250(fd, lib, main); + } + if (!main->is_read_invalid) { + blo_do_versions_250(fd, lib, main); + } + if (!main->is_read_invalid) { + blo_do_versions_260(fd, lib, main); + } + if (!main->is_read_invalid) { + blo_do_versions_270(fd, lib, main); + } + if (!main->is_read_invalid) { + blo_do_versions_280(fd, lib, main); + } + if (!main->is_read_invalid) { + blo_do_versions_290(fd, lib, main); + } + if (!main->is_read_invalid) { + blo_do_versions_300(fd, lib, main); + } + if (!main->is_read_invalid) { + blo_do_versions_400(fd, lib, main); + } + if (!main->is_read_invalid) { + blo_do_versions_cycles(fd, lib, main); + } /* WATCH IT!!!: pointers from libdata have not been converted yet here! */ /* WATCH IT 2!: Userdef struct init see do_versions_userdef() above! */ @@ -3582,7 +3622,7 @@ static void do_versions(FileData *fd, Library *lib, Main *main) main->is_locked_for_linking = false; } -static void do_versions_after_linking(Main *main, ReportList *reports) +static void do_versions_after_linking(FileData *fd, Main *main) { CLOG_INFO(&LOG, 2, @@ -3595,13 +3635,27 @@ static void do_versions_after_linking(Main *main, ReportList *reports) /* Don't allow versioning to create new data-blocks. */ main->is_locked_for_linking = true; - do_versions_after_linking_250(main); - do_versions_after_linking_260(main); - do_versions_after_linking_270(main); - do_versions_after_linking_280(main, reports); - do_versions_after_linking_290(main, reports); - do_versions_after_linking_300(main, reports); - do_versions_after_linking_cycles(main); + if (!main->is_read_invalid) { + do_versions_after_linking_250(main); + } + if (!main->is_read_invalid) { + do_versions_after_linking_260(main); + } + if (!main->is_read_invalid) { + do_versions_after_linking_270(main); + } + if (!main->is_read_invalid) { + do_versions_after_linking_280(fd, main); + } + if (!main->is_read_invalid) { + do_versions_after_linking_290(fd, main); + } + if (!main->is_read_invalid) { + do_versions_after_linking_300(fd, main); + } + if (!main->is_read_invalid) { + do_versions_after_linking_cycles(main); + } main->is_locked_for_linking = false; } @@ -3810,6 +3864,9 @@ static BHead *read_userdef(BlendFileData *bfd, FileData *fd, BHead *bhead) static void blo_read_file_checks(Main *bmain) { #ifndef NDEBUG + BLI_assert(bmain->next == nullptr); + BLI_assert(!bmain->is_read_invalid); + LISTBASE_FOREACH (wmWindowManager *, wm, &bmain->wm) { LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { /* This pointer is deprecated and should always be nullptr. */ @@ -3914,6 +3971,10 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath) bhead = read_libblock(fd, bfd->main, bhead, LIB_TAG_LOCAL, false, nullptr); } } + + if (bfd->main->is_read_invalid) { + return bfd; + } } /* do before read_libraries, but skip undo case */ @@ -3927,6 +3988,10 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath) } } + if (bfd->main->is_read_invalid) { + return bfd; + } + if ((fd->skip_flags & BLO_READ_SKIP_DATA) == 0) { fd->reports->duration.libraries = PIL_check_seconds_timer(); read_libraries(fd, &mainlist); @@ -3953,7 +4018,7 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath) blo_split_main(&mainlist, bfd->main); LISTBASE_FOREACH (Main *, mainvar, &mainlist) { BLI_assert(mainvar->versionfile != 0); - do_versions_after_linking(mainvar, fd->reports->reports); + do_versions_after_linking(fd, mainvar); } blo_join_main(&mainlist); @@ -3963,6 +4028,10 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath) BKE_main_id_refcount_recompute(bfd->main, false); } + if (bfd->main->is_read_invalid) { + return bfd; + } + /* After all data has been read and versioned, uses LIB_TAG_NEW. Theoretically this should * not be calculated in the undo case, but it is currently needed even on undo to recalculate * a cache. */ @@ -4174,6 +4243,10 @@ static void expand_doit_library(void *fdhandle, Main *mainvar, void *old) { FileData *fd = static_cast(fdhandle); + if (mainvar->is_read_invalid) { + return; + } + BHead *bhead = find_bhead(fd, old); if (bhead == nullptr) { return; @@ -4432,7 +4505,16 @@ ID *BLO_library_link_named_part(Main *mainl, const LibraryLink_Params *params) { FileData *fd = (FileData *)(*bh); - return link_named_part(mainl, fd, idcode, name, params->flag); + + ID *ret_id = nullptr; + if (!mainl->is_read_invalid) { + ret_id = link_named_part(mainl, fd, idcode, name, params->flag); + } + + if (mainl->is_read_invalid) { + return nullptr; + } + return ret_id; } /* common routine to append/link something from a library */ @@ -4562,6 +4644,10 @@ static void library_link_end(Main *mainl, FileData **fd, const int flag) mainvar = static_cast
((*fd)->mainlist->first); mainl = nullptr; /* blo_join_main free's mainl, can't use anymore */ + if (mainvar->is_read_invalid) { + return; + } + lib_link_all(*fd, mainvar); after_liblink_merged_bmain_process(mainvar); @@ -4582,9 +4668,13 @@ static void library_link_end(Main *mainl, FileData **fd, const int flag) * or they will go again through do_versions - bad, very bad! */ split_main_newid(mainvar, main_newid); - do_versions_after_linking(main_newid, (*fd)->reports->reports); + do_versions_after_linking(*fd, main_newid); add_main_to_main(mainvar, main_newid); + + if (mainvar->is_read_invalid) { + return; + } } blo_join_main((*fd)->mainlist); @@ -4631,9 +4721,12 @@ static void library_link_end(Main *mainl, FileData **fd, const int flag) void BLO_library_link_end(Main *mainl, BlendHandle **bh, const LibraryLink_Params *params) { - FileData *fd = (FileData *)(*bh); - library_link_end(mainl, &fd, params->flag); - *bh = (BlendHandle *)fd; + FileData *fd = reinterpret_cast(*bh); + + if (!mainl->is_read_invalid) { + library_link_end(mainl, &fd, params->flag); + *bh = reinterpret_cast(fd); + } } void *BLO_library_read_struct(FileData *fd, BHead *bh, const char *blockname) diff --git a/source/blender/blenloader/intern/readfile.h b/source/blender/blenloader/intern/readfile.h index 027f1453581..c79dc0670d6 100644 --- a/source/blender/blenloader/intern/readfile.h +++ b/source/blender/blenloader/intern/readfile.h @@ -219,9 +219,9 @@ void blo_do_versions_cycles(struct FileData *fd, struct Library *lib, struct Mai void do_versions_after_linking_250(struct Main *bmain); void do_versions_after_linking_260(struct Main *bmain); void do_versions_after_linking_270(struct Main *bmain); -void do_versions_after_linking_280(struct Main *bmain, struct ReportList *reports); -void do_versions_after_linking_290(struct Main *bmain, struct ReportList *reports); -void do_versions_after_linking_300(struct Main *bmain, struct ReportList *reports); +void do_versions_after_linking_280(struct FileData *fd, struct Main *bmain); +void do_versions_after_linking_290(struct FileData *fd, struct Main *bmain); +void do_versions_after_linking_300(struct FileData *fd, struct Main *bmain); void do_versions_after_linking_cycles(struct Main *bmain); /** @@ -232,6 +232,10 @@ void do_versions_after_linking_cycles(struct Main *bmain); */ void *blo_read_get_new_globaldata_address(struct FileData *fd, const void *adr); +/* Mark the Main data as invalid (.blend file reading should be aborted ASAP, and the already read + * data should be discarded). Also add an error report to `fd` including given `message`. */ +void blo_readfile_invalidate(struct FileData *fd, struct Main *bmain, const char *message); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index ff860677663..df2d6cd7d2b 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -1171,7 +1171,7 @@ static void do_version_fcurve_hide_viewport_fix(struct ID *UNUSED(id), fcu->rna_path = BLI_strdupn("hide_viewport", 13); } -void do_versions_after_linking_280(Main *bmain, ReportList *UNUSED(reports)) +void do_versions_after_linking_280(FileData *UNUSED(fd), Main *bmain) { bool use_collection_compat_28 = true; diff --git a/source/blender/blenloader/intern/versioning_290.cc b/source/blender/blenloader/intern/versioning_290.cc index 580a1437c16..af78c38b577 100644 --- a/source/blender/blenloader/intern/versioning_290.cc +++ b/source/blender/blenloader/intern/versioning_290.cc @@ -410,7 +410,7 @@ static void version_node_socket_duplicate(bNodeTree *ntree, } } -void do_versions_after_linking_290(Main *bmain, ReportList * /*reports*/) +void do_versions_after_linking_290(FileData * /*fd*/, Main *bmain) { if (!MAIN_VERSION_ATLEAST(bmain, 290, 1)) { /* Patch old grease pencil modifiers material filter. */ diff --git a/source/blender/blenloader/intern/versioning_300.cc b/source/blender/blenloader/intern/versioning_300.cc index 59e4be70989..1e4a3b51bea 100644 --- a/source/blender/blenloader/intern/versioning_300.cc +++ b/source/blender/blenloader/intern/versioning_300.cc @@ -939,7 +939,7 @@ static void version_geometry_nodes_primitive_uv_maps(bNodeTree &ntree) } } -void do_versions_after_linking_300(Main *bmain, ReportList * /*reports*/) +void do_versions_after_linking_300(FileData * /*fd*/, Main *bmain) { if (MAIN_VERSION_ATLEAST(bmain, 300, 0) && !MAIN_VERSION_ATLEAST(bmain, 300, 1)) { /* Set zero user text objects to have a fake user. */