Add a mechanism to abort a blend file reading on critical error - Blender 3.3 LTS #105486

Merged
Philipp Oeser merged 5 commits from mont29/blender:tmp-3.3-abort-fileread into blender-v3.3-release 2023-03-16 13:55:09 +01:00
11 changed files with 186 additions and 27 deletions

View File

@ -136,6 +136,13 @@ typedef struct Main {
*/
char 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;
BlendThumbnail *blen_thumb;
struct Library *curlib;

View File

@ -468,6 +468,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);
@ -495,6 +502,10 @@ struct BlendFileData *BKE_blendfile_read(const char *filepath,
}
BlendFileData *bfd = BLO_read_from_file(filepath, params->skip_flags, reports);
if (bfd && bfd->main->is_read_invalid) {
BLO_blendfiledata_free(bfd);
bfd = NULL;
}
if (bfd) {
handle_subversion_warning(bfd->main, reports);
}
@ -510,6 +521,10 @@ struct BlendFileData *BKE_blendfile_read_from_memory(const void *filebuf,
ReportList *reports)
{
BlendFileData *bfd = BLO_read_from_memory(filebuf, filelength, params->skip_flags, reports);
if (bfd && bfd->main->is_read_invalid) {
BLO_blendfiledata_free(bfd);
bfd = NULL;
}
if (bfd) {
/* Pass. */
}
@ -526,6 +541,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 = NULL;
}
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

View File

@ -39,6 +39,13 @@ Main *BKE_main_new(void)
void BKE_main_free(Main *mainvar)
{
/* In case this is called on a 'split-by-libraries' list of mains.
*
* Should not happen in typical usages, but can occur e.g. if a file reading is aborted. */
if (mainvar->next) {
BKE_main_free(mainvar->next);
}
/* also call when reading a file, erase all, etc */
ListBase *lbarray[INDEX_ID_MAX];
int a;

View File

@ -136,6 +136,8 @@ void BLI_freelistN(struct ListBase *listbase) ATTR_NONNULL(1);
void BLI_addtail(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1);
/**
* Removes \a vlink from \a listbase. Assumes it is linked into there!
*
* \warning Does _not_ clear the `prev`/`next` pointers of the removed `vlink`.
*/
void BLI_remlink(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1);
/**

View File

@ -271,6 +271,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

View File

@ -349,6 +349,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 = (FileData *)bh;
blo_readfile_invalidate(fd, bmain, message);
}
/**********/
BlendFileData *BLO_read_from_file(const char *filepath,

View File

@ -440,6 +440,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--) {
@ -462,6 +466,7 @@ void blo_join_main(ListBase *mainlist)
while ((tojoin = mainl->next)) {
add_main_to_main(mainl, tojoin);
BLI_remlink(mainlist, tojoin);
tojoin->next = tojoin->prev = NULL;
BKE_main_free(tojoin);
}
}
@ -665,6 +670,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 != NULL; 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);
}
/** \} */
/* -------------------------------------------------------------------- */
@ -3591,14 +3611,30 @@ 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_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_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! */
@ -3608,7 +3644,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,
@ -3621,13 +3657,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;
}
@ -3925,6 +3975,10 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath)
bhead = read_libblock(fd, bfd->main, bhead, LIB_TAG_LOCAL, false, NULL);
}
}
if (bfd->main->is_read_invalid) {
return bfd;
}
}
/* do before read_libraries, but skip undo case */
@ -3938,6 +3992,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);
@ -3964,7 +4022,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);
@ -3974,6 +4032,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. */
@ -4432,7 +4494,16 @@ ID *BLO_library_link_named_part(Main *mainl,
const struct LibraryLink_Params *params)
{
FileData *fd = (FileData *)(*bh);
return link_named_part(mainl, fd, idcode, name, params->flag);
ID *ret_id = NULL;
if (!mainl->is_read_invalid) {
ret_id = link_named_part(mainl, fd, idcode, name, params->flag);
}
if (mainl->is_read_invalid) {
return NULL;
}
return ret_id;
}
/* common routine to append/link something from a library */
@ -4562,6 +4633,10 @@ static void library_link_end(Main *mainl, FileData **fd, const int flag)
mainvar = (*fd)->mainlist->first;
mainl = NULL; /* 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,15 +4657,24 @@ 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) {
break;
}
}
blo_join_main((*fd)->mainlist);
mainvar = (*fd)->mainlist->first;
MEM_freeN((*fd)->mainlist);
if (mainvar->is_read_invalid) {
BKE_main_free(main_newid);
return;
}
/* This does not take into account old, deprecated data, so we also have to do it after
* `do_versions_after_linking()`. */
BKE_main_id_refcount_recompute(mainvar, false);
@ -4624,8 +4708,10 @@ static void library_link_end(Main *mainl, FileData **fd, const int flag)
void BLO_library_link_end(Main *mainl, BlendHandle **bh, const struct LibraryLink_Params *params)
{
FileData *fd = (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 = (BlendHandle *)fd;
}
}
void *BLO_library_read_struct(FileData *fd, BHead *bh, const char *blockname)

View File

@ -212,9 +212,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);
/**
@ -224,3 +224,8 @@ void do_versions_after_linking_cycles(struct Main *bmain);
* but better use that nasty hack in do_version than readfile itself.
*/
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);

View File

@ -1168,7 +1168,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 *fd, Main *bmain)
{
bool use_collection_compat_28 = true;
@ -1238,6 +1238,12 @@ void do_versions_after_linking_280(Main *bmain, ReportList *UNUSED(reports))
if (!MAIN_VERSION_ATLEAST(bmain, 280, 0)) {
for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) {
BLO_read_assert_message(screen->scene == NULL,
,
(BlendHandle *)fd,
bmain,
"No Screen data-block should ever have a NULL `scene` pointer");
/* same render-layer as do_version_workspaces_after_lib_link will activate,
* so same layer as BKE_view_layer_default_view would return */
ViewLayer *layer = screen->scene->view_layers.first;

View File

@ -412,7 +412,7 @@ static void version_node_socket_duplicate(bNodeTree *ntree,
}
}
void do_versions_after_linking_290(Main *bmain, ReportList *UNUSED(reports))
void do_versions_after_linking_290(FileData *UNUSED(fd), Main *bmain)
{
if (!MAIN_VERSION_ATLEAST(bmain, 290, 1)) {
/* Patch old grease pencil modifiers material filter. */

View File

@ -638,7 +638,7 @@ static bool seq_speed_factor_set(Sequence *seq, void *user_data)
return true;
}
void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports))
void do_versions_after_linking_300(FileData *UNUSED(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. */