1
1

Add a mechanism to abort a blend file reading on critical error.

This commit introduces a new Main boolean flag that marks is as invalid.

Higher-level file reading code does checks on this flag to abort reading
process if needed.

This is an implementation of the #105083 design task.

Given the extense of the change, I do not think this should be
considered for 3.5 and previous LTS releases.
This commit is contained in:
2023-02-22 16:37:15 +01:00
parent 3e5ce23c99
commit b3f42d8e98
9 changed files with 179 additions and 29 deletions

View File

@@ -138,6 +138,13 @@ typedef struct Main {
*/ */
bool is_locked_for_linking; 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. * True if this main is the 'GMAIN' of current Blender.
* *

View File

@@ -476,6 +476,13 @@ void BKE_blendfile_read_setup_ex(bContext *C,
const bool startup_update_defaults, const bool startup_update_defaults,
const char *startup_app_template) 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 (startup_update_defaults) {
if ((params->skip_flags & BLO_READ_SKIP_DATA) == 0) { if ((params->skip_flags & BLO_READ_SKIP_DATA) == 0) {
BLO_update_defaults_startup_blend(bfd->main, startup_app_template); 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); 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) { if (bfd) {
handle_subversion_warning(bfd->main, reports); 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( BlendFileData *bfd = BLO_read_from_memory(
filebuf, filelength, eBLOReadSkip(params->skip_flags), reports); filebuf, filelength, eBLOReadSkip(params->skip_flags), reports);
if (bfd && bfd->main->is_read_invalid) {
BLO_blendfiledata_free(bfd);
bfd = nullptr;
}
if (bfd) { if (bfd) {
/* Pass. */ /* Pass. */
} }
@@ -535,6 +550,10 @@ struct BlendFileData *BKE_blendfile_read_from_memfile(Main *bmain,
{ {
BlendFileData *bfd = BLO_read_from_memfile( BlendFileData *bfd = BLO_read_from_memfile(
bmain, BKE_main_blendfile_path(bmain), memfile, params, reports); bmain, BKE_main_blendfile_path(bmain), memfile, params, reports);
if (bfd && bfd->main->is_read_invalid) {
BLO_blendfiledata_free(bfd);
bfd = nullptr;
}
if (bfd) { if (bfd) {
/* Removing the unused workspaces, screens and wm is useless here, setup_app_data will switch /* 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 * those lists with the ones from old bmain, which freeing is much more efficient than

View File

@@ -288,6 +288,26 @@ struct LinkNode *BLO_blendhandle_get_linkable_groups(BlendHandle *bh);
*/ */
void BLO_blendhandle_close(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 #define BLO_GROUP_MAX 32

View File

@@ -385,6 +385,13 @@ void BLO_blendhandle_close(BlendHandle *bh)
blo_filedata_free(fd); blo_filedata_free(fd);
} }
void BLO_read_invalidate_message(BlendHandle *bh, Main *bmain, const char *message)
{
FileData *fd = reinterpret_cast<FileData *>(bh);
blo_readfile_invalidate(fd, bmain, message);
}
/**********/ /**********/
BlendFileData *BLO_read_from_file(const char *filepath, BlendFileData *BLO_read_from_file(const char *filepath,

View File

@@ -320,6 +320,10 @@ static void add_main_to_main(Main *mainvar, Main *from)
ListBase *lbarray[INDEX_ID_MAX], *fromarray[INDEX_ID_MAX]; ListBase *lbarray[INDEX_ID_MAX], *fromarray[INDEX_ID_MAX];
int a; int a;
if (from->is_read_invalid) {
mainvar->is_read_invalid = true;
}
set_listbasepointers(mainvar, lbarray); set_listbasepointers(mainvar, lbarray);
a = set_listbasepointers(from, fromarray); a = set_listbasepointers(from, fromarray);
while (a--) { while (a--) {
@@ -340,6 +344,9 @@ void blo_join_main(ListBase *mainlist)
} }
while ((tojoin = mainl->next)) { while ((tojoin = mainl->next)) {
if (tojoin->is_read_invalid) {
mainl->is_read_invalid = true;
}
add_main_to_main(mainl, tojoin); add_main_to_main(mainl, tojoin);
BLI_remlink(mainlist, tojoin); BLI_remlink(mainlist, tojoin);
BKE_main_free(tojoin); BKE_main_free(tojoin);
@@ -548,6 +555,21 @@ static Main *blo_find_main(FileData *fd, const char *filepath, const char *relab
return m; 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); main->build_hash);
} }
blo_do_versions_pre250(fd, lib, main); if (!main->is_read_invalid) {
blo_do_versions_250(fd, lib, main); blo_do_versions_pre250(fd, lib, main);
blo_do_versions_260(fd, lib, main); }
blo_do_versions_270(fd, lib, main); if (!main->is_read_invalid) {
blo_do_versions_280(fd, lib, main); blo_do_versions_250(fd, lib, main);
blo_do_versions_290(fd, lib, main); }
blo_do_versions_300(fd, lib, main); if (!main->is_read_invalid) {
blo_do_versions_400(fd, lib, main); blo_do_versions_260(fd, lib, main);
blo_do_versions_cycles(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!!!: pointers from libdata have not been converted yet here! */
/* WATCH IT 2!: Userdef struct init see do_versions_userdef() above! */ /* 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; 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, CLOG_INFO(&LOG,
2, 2,
@@ -3595,13 +3635,27 @@ static void do_versions_after_linking(Main *main, ReportList *reports)
/* Don't allow versioning to create new data-blocks. */ /* Don't allow versioning to create new data-blocks. */
main->is_locked_for_linking = true; main->is_locked_for_linking = true;
do_versions_after_linking_250(main); if (!main->is_read_invalid) {
do_versions_after_linking_260(main); do_versions_after_linking_250(main);
do_versions_after_linking_270(main); }
do_versions_after_linking_280(main, reports); if (!main->is_read_invalid) {
do_versions_after_linking_290(main, reports); do_versions_after_linking_260(main);
do_versions_after_linking_300(main, reports); }
do_versions_after_linking_cycles(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; 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) static void blo_read_file_checks(Main *bmain)
{ {
#ifndef NDEBUG #ifndef NDEBUG
BLI_assert(bmain->next == nullptr);
BLI_assert(!bmain->is_read_invalid);
LISTBASE_FOREACH (wmWindowManager *, wm, &bmain->wm) { LISTBASE_FOREACH (wmWindowManager *, wm, &bmain->wm) {
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
/* This pointer is deprecated and should always be nullptr. */ /* 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); 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 */ /* 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) { if ((fd->skip_flags & BLO_READ_SKIP_DATA) == 0) {
fd->reports->duration.libraries = PIL_check_seconds_timer(); fd->reports->duration.libraries = PIL_check_seconds_timer();
read_libraries(fd, &mainlist); read_libraries(fd, &mainlist);
@@ -3953,7 +4018,7 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath)
blo_split_main(&mainlist, bfd->main); blo_split_main(&mainlist, bfd->main);
LISTBASE_FOREACH (Main *, mainvar, &mainlist) { LISTBASE_FOREACH (Main *, mainvar, &mainlist) {
BLI_assert(mainvar->versionfile != 0); BLI_assert(mainvar->versionfile != 0);
do_versions_after_linking(mainvar, fd->reports->reports); do_versions_after_linking(fd, mainvar);
} }
blo_join_main(&mainlist); 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); 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 /* 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 * not be calculated in the undo case, but it is currently needed even on undo to recalculate
* a cache. */ * a cache. */
@@ -4174,6 +4243,10 @@ static void expand_doit_library(void *fdhandle, Main *mainvar, void *old)
{ {
FileData *fd = static_cast<FileData *>(fdhandle); FileData *fd = static_cast<FileData *>(fdhandle);
if (mainvar->is_read_invalid) {
return;
}
BHead *bhead = find_bhead(fd, old); BHead *bhead = find_bhead(fd, old);
if (bhead == nullptr) { if (bhead == nullptr) {
return; return;
@@ -4432,7 +4505,16 @@ ID *BLO_library_link_named_part(Main *mainl,
const LibraryLink_Params *params) const LibraryLink_Params *params)
{ {
FileData *fd = (FileData *)(*bh); 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 */ /* 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<Main *>((*fd)->mainlist->first); mainvar = static_cast<Main *>((*fd)->mainlist->first);
mainl = nullptr; /* blo_join_main free's mainl, can't use anymore */ mainl = nullptr; /* blo_join_main free's mainl, can't use anymore */
if (mainvar->is_read_invalid) {
return;
}
lib_link_all(*fd, mainvar); lib_link_all(*fd, mainvar);
after_liblink_merged_bmain_process(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! */ * or they will go again through do_versions - bad, very bad! */
split_main_newid(mainvar, main_newid); 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); add_main_to_main(mainvar, main_newid);
if (mainvar->is_read_invalid) {
return;
}
} }
blo_join_main((*fd)->mainlist); 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) void BLO_library_link_end(Main *mainl, BlendHandle **bh, const LibraryLink_Params *params)
{ {
FileData *fd = (FileData *)(*bh); FileData *fd = reinterpret_cast<FileData *>(*bh);
library_link_end(mainl, &fd, params->flag);
*bh = (BlendHandle *)fd; if (!mainl->is_read_invalid) {
library_link_end(mainl, &fd, params->flag);
*bh = reinterpret_cast<BlendHandle *>(fd);
}
} }
void *BLO_library_read_struct(FileData *fd, BHead *bh, const char *blockname) void *BLO_library_read_struct(FileData *fd, BHead *bh, const char *blockname)

View File

@@ -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_250(struct Main *bmain);
void do_versions_after_linking_260(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_270(struct Main *bmain);
void do_versions_after_linking_280(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 Main *bmain, struct ReportList *reports); void do_versions_after_linking_290(struct FileData *fd, struct Main *bmain);
void do_versions_after_linking_300(struct Main *bmain, struct ReportList *reports); void do_versions_after_linking_300(struct FileData *fd, struct Main *bmain);
void do_versions_after_linking_cycles(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); 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 #ifdef __cplusplus
} }
#endif #endif

View File

@@ -1171,7 +1171,7 @@ static void do_version_fcurve_hide_viewport_fix(struct ID *UNUSED(id),
fcu->rna_path = BLI_strdupn("hide_viewport", 13); 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; bool use_collection_compat_28 = true;

View File

@@ -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)) { if (!MAIN_VERSION_ATLEAST(bmain, 290, 1)) {
/* Patch old grease pencil modifiers material filter. */ /* Patch old grease pencil modifiers material filter. */

View File

@@ -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)) { if (MAIN_VERSION_ATLEAST(bmain, 300, 0) && !MAIN_VERSION_ATLEAST(bmain, 300, 1)) {
/* Set zero user text objects to have a fake user. */ /* Set zero user text objects to have a fake user. */