Fix #124167: Crash opening file after asset browser is closed

Switching to an asset browser, then back to a different editor would
cause a crash when loading a new file.

Basically the file/asset browser tried to free dangling asset and asset
library pointers when trying to free itself. That's because the asset
system gets destructed with a `AS_asset_libraries_exit()` call before
the screen and with that the asset browser were freed.

Pull Request: https://projects.blender.org/blender/blender/pulls/125084

Pull Request: https://projects.blender.org/blender/blender/pulls/127696
This commit is contained in:
Julian Eisel
2024-07-23 18:29:50 +02:00
committed by Gitea
parent 638d950f64
commit 06b9a46edb
6 changed files with 106 additions and 73 deletions
@@ -127,13 +127,16 @@ class AssetLibrary {
* \param relative_asset_path: The path of the asset relative to the asset library root. With
* this the asset must be uniquely identifiable within the asset
* library.
* \return A weak pointer to the new asset representation. The caller needs to keep some
* reference stored to be able to call #remove_asset(). This would be dangling once the
* asset library is destructed, so a weak pointer should be used to reference it.
*/
AssetRepresentation &add_external_asset(StringRef relative_asset_path,
StringRef name,
int id_type,
std::unique_ptr<AssetMetaData> metadata);
std::weak_ptr<AssetRepresentation> add_external_asset(StringRef relative_asset_path,
StringRef name,
int id_type,
std::unique_ptr<AssetMetaData> metadata);
/** See #AssetLibrary::add_external_asset(). */
AssetRepresentation &add_local_id_asset(StringRef relative_asset_path, ID &id);
std::weak_ptr<AssetRepresentation> add_local_id_asset(StringRef relative_asset_path, ID &id);
/**
* Remove an asset from the library that was added using #add_external_asset() or
* #add_local_id_asset(). Can usually be expected to be constant time complexity (worst case may
@@ -208,17 +208,19 @@ AssetCatalogService &AssetLibrary::catalog_service() const
void AssetLibrary::refresh_catalogs() {}
AssetRepresentation &AssetLibrary::add_external_asset(StringRef relative_asset_path,
StringRef name,
const int id_type,
std::unique_ptr<AssetMetaData> metadata)
std::weak_ptr<AssetRepresentation> AssetLibrary::add_external_asset(
StringRef relative_asset_path,
StringRef name,
const int id_type,
std::unique_ptr<AssetMetaData> metadata)
{
AssetIdentifier identifier = this->asset_identifier_from_library(relative_asset_path);
return asset_storage_->add_external_asset(
std::move(identifier), name, id_type, std::move(metadata), *this);
}
AssetRepresentation &AssetLibrary::add_local_id_asset(StringRef relative_asset_path, ID &id)
std::weak_ptr<AssetRepresentation> AssetLibrary::add_local_id_asset(StringRef relative_asset_path,
ID &id)
{
AssetIdentifier identifier = this->asset_identifier_from_library(relative_asset_path);
return asset_storage_->add_local_id_asset(std::move(identifier), id, *this);
@@ -17,21 +17,21 @@
namespace blender::asset_system {
AssetRepresentation &AssetStorage::add_local_id_asset(AssetIdentifier &&identifier,
ID &id,
const AssetLibrary &owner_asset_library)
std::weak_ptr<AssetRepresentation> AssetStorage::add_local_id_asset(
AssetIdentifier &&identifier, ID &id, const AssetLibrary &owner_asset_library)
{
return *local_id_assets_.lookup_key_or_add(
std::make_unique<AssetRepresentation>(std::move(identifier), id, owner_asset_library));
return local_id_assets_.lookup_key_or_add(
std::make_shared<AssetRepresentation>(std::move(identifier), id, owner_asset_library));
}
AssetRepresentation &AssetStorage::add_external_asset(AssetIdentifier &&identifier,
StringRef name,
const int id_type,
std::unique_ptr<AssetMetaData> metadata,
const AssetLibrary &owner_asset_library)
std::weak_ptr<AssetRepresentation> AssetStorage::add_external_asset(
AssetIdentifier &&identifier,
StringRef name,
const int id_type,
std::unique_ptr<AssetMetaData> metadata,
const AssetLibrary &owner_asset_library)
{
return *external_assets_.lookup_key_or_add(std::make_unique<AssetRepresentation>(
return external_assets_.lookup_key_or_add(std::make_shared<AssetRepresentation>(
std::move(identifier), name, id_type, std::move(metadata), owner_asset_library));
}
@@ -25,10 +25,13 @@ class IDRemapper;
namespace blender::asset_system {
class AssetIdentifier;
class AssetLibrary;
class AssetRepresentation;
class AssetStorage {
using StorageT = Set<std::unique_ptr<AssetRepresentation>>;
/* Uses shared pointers so the UI can aquire weak pointers. It can then ensure pointers are not
* dangling before accessing. */
using StorageT = Set<std::shared_ptr<AssetRepresentation>>;
StorageT external_assets_;
/* Store local ID assets separately for efficient lookups.
@@ -38,15 +41,15 @@ class AssetStorage {
public:
/** See #AssetLibrary::add_external_asset(). */
AssetRepresentation &add_external_asset(AssetIdentifier &&identifier,
StringRef name,
int id_type,
std::unique_ptr<AssetMetaData> metadata,
const AssetLibrary &owner_asset_library);
std::weak_ptr<AssetRepresentation> add_external_asset(AssetIdentifier &&identifier,
StringRef name,
int id_type,
std::unique_ptr<AssetMetaData> metadata,
const AssetLibrary &owner_asset_library);
/** See #AssetLibrary::add_external_asset(). */
AssetRepresentation &add_local_id_asset(AssetIdentifier &&identifier,
ID &id,
const AssetLibrary &owner_asset_library);
std::weak_ptr<AssetRepresentation> add_local_id_asset(AssetIdentifier &&identifier,
ID &id,
const AssetLibrary &owner_asset_library);
/** See #AssetLibrary::remove_asset(). */
bool remove_asset(AssetRepresentation &asset);
@@ -37,8 +37,9 @@ class AssetRepresentationTest : public AssetLibraryTestBase {
AssetRepresentation &add_dummy_asset(AssetLibrary &library, StringRef relative_path)
{
std::unique_ptr<AssetMetaData> dummy_metadata = std::make_unique<AssetMetaData>();
return library.add_external_asset(
relative_path, "Some asset name", 0, std::move(dummy_metadata));
return *library
.add_external_asset(relative_path, "Some asset name", 0, std::move(dummy_metadata))
.lock();
}
};
+65 -41
View File
@@ -12,6 +12,7 @@
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <memory>
#include <optional>
#include <sys/stat.h>
@@ -86,20 +87,20 @@ using namespace blender;
/* ------------------FILELIST------------------------ */
struct FileListInternEntry {
FileListInternEntry *next, *prev;
FileListInternEntry *next = nullptr, *prev = nullptr;
FileUID uid;
FileUID uid = 0;
eFileSel_File_Types typeflag;
eFileSel_File_Types typeflag = eFileSel_File_Types(0);
/** ID type, in case typeflag has FILE_TYPE_BLENDERLIB set. */
int blentype;
int blentype = 0;
char *relpath;
char *relpath = nullptr;
/** Optional argument for shortcuts, aliases etc. */
char *redirection_path;
char *redirection_path = nullptr;
/** not strictly needed, but used during sorting, avoids to have to recompute it there... */
const char *name;
bool free_name;
const char *name = nullptr;
bool free_name = false;
/**
* This is data from the current main, represented by this file. It's crucial that this is
@@ -108,22 +109,45 @@ struct FileListInternEntry {
*/
struct {
/** When showing local IDs (FILE_MAIN, FILE_MAIN_ASSET), the ID this file entry represents. */
ID *id;
ID *id = nullptr;
/* For the few file types that have the preview already in memory. For others, there's delayed
* preview reading from disk. Non-owning pointer. */
PreviewImage *preview_image;
PreviewImage *preview_image = nullptr;
} local_data;
/* References an asset in the asset library storage. */
asset_system::AssetRepresentation *asset; /* Non-owning. */
/**
* References an asset in the asset library storage.
* The file list inserts this asset representation into the library, and removes it again when
* the file list is destructed. In that sense it manages the asset but doesn't own it.
*
* Weak pointer so access is protected in case the asset library gets destructed externally.
*/
std::weak_ptr<asset_system::AssetRepresentation> asset;
/* See #FILE_ENTRY_BLENDERLIB_NO_PREVIEW. */
bool blenderlib_has_no_preview;
bool blenderlib_has_no_preview = false;
/** Defined in BLI_fileops.h */
eFileAttributes attributes;
BLI_stat_t st;
eFileAttributes attributes = eFileAttributes(0);
BLI_stat_t st = {0};
/**
* Be careful not to use the returned asset pointer in a context where it might be dangling, e.g.
* because the file list or the asset library were destroyed.
*/
asset_system::AssetRepresentation *get_asset() const
{
if (std::shared_ptr<asset_system::AssetRepresentation> asset_ptr = asset.lock()) {
/* Returning a raw pointer from a shared pointer and destructing the shared pointer
* immediately afterwards isn't entirely clean. But it's just a way to get the raw pointer
* from the weak pointer. Nothing should free the asset in the asset library meanwhile, so
* this should be fine really. */
BLI_assert(asset_ptr.use_count() > 1);
return asset_ptr.get();
}
return nullptr;
}
};
struct FileListIntern {
@@ -572,15 +596,17 @@ static int compare_asset_catalog(void *user_data, const void *a1, const void *a2
const FileListInternEntry *entry1 = static_cast<const FileListInternEntry *>(a1);
const FileListInternEntry *entry2 = static_cast<const FileListInternEntry *>(a2);
const FileSortData *sort_data = static_cast<const FileSortData *>(user_data);
const asset_system::AssetRepresentation *asset1 = entry1->get_asset();
const asset_system::AssetRepresentation *asset2 = entry2->get_asset();
/* Order non-assets. */
if (entry1->asset && !entry2->asset) {
if (asset1 && !asset2) {
return 1;
}
else if (!entry1->asset && entry2->asset) {
else if (!asset1 && asset2) {
return -1;
}
else if (!entry1->asset && !entry2->asset) {
else if (!asset1 && !asset2) {
if (int order = compare_direntry_generic(entry1, entry2); order) {
return compare_apply_inverted(order, sort_data);
}
@@ -588,13 +614,13 @@ static int compare_asset_catalog(void *user_data, const void *a1, const void *a2
return compare_apply_inverted(compare_tiebreaker(entry1, entry2), sort_data);
}
const asset_system::AssetLibrary &asset_library1 = entry1->asset->owner_asset_library();
const asset_system::AssetLibrary &asset_library2 = entry2->asset->owner_asset_library();
const asset_system::AssetLibrary &asset_library1 = asset1->owner_asset_library();
const asset_system::AssetLibrary &asset_library2 = asset2->owner_asset_library();
const asset_system::AssetCatalog *catalog1 = asset_library1.catalog_service().find_catalog(
entry1->asset->get_metadata().catalog_id);
asset1->get_metadata().catalog_id);
const asset_system::AssetCatalog *catalog2 = asset_library2.catalog_service().find_catalog(
entry2->asset->get_metadata().catalog_id);
asset2->get_metadata().catalog_id);
/* Order by catalog. Always keep assets without catalog last. */
int order = 0;
@@ -806,10 +832,10 @@ static bool is_filtered_id_file_type(const FileListInternEntry *file,
*/
static AssetMetaData *filelist_file_internal_get_asset_data(const FileListInternEntry *file)
{
if (!file->asset) {
return nullptr;
if (asset_system::AssetRepresentation *asset = file->get_asset()) {
return &asset->get_metadata();
}
return &file->asset->get_metadata();
return nullptr;
}
static void prepare_filter_asset_library(const FileList *filelist, FileListFilter *filter)
@@ -1456,11 +1482,9 @@ static void filelist_direntryarr_free(FileDirEntryArr *array)
static void filelist_intern_entry_free(FileList *filelist, FileListInternEntry *entry)
{
/* Asset system storage might be cleared already on file exit. Asset library
* pointers are dangling then, so don't access (#120466). */
if (AS_asset_libraries_available() && entry->asset) {
if (auto asset_ptr = entry->asset.lock()) {
BLI_assert(filelist->asset_library);
filelist->asset_library->remove_asset(*entry->asset);
filelist->asset_library->remove_asset(*asset_ptr);
}
if (entry->relpath) {
@@ -1472,7 +1496,7 @@ static void filelist_intern_entry_free(FileList *filelist, FileListInternEntry *
if (entry->name && entry->free_name) {
MEM_freeN((char *)entry->name);
}
MEM_freeN(entry);
MEM_delete(entry);
}
static void filelist_intern_free(FileList *filelist)
@@ -1985,8 +2009,8 @@ int filelist_files_num_entries(FileList *filelist)
static const char *fileentry_uiname(const char *root, FileListInternEntry *entry, char *buff)
{
if (entry->asset) {
const StringRefNull asset_name = entry->asset->get_name();
if (asset_system::AssetRepresentation *asset = entry->get_asset()) {
const StringRefNull asset_name = asset->get_name();
return BLI_strdupn(asset_name.c_str(), asset_name.size());
}
@@ -2133,7 +2157,7 @@ static FileDirEntry *filelist_file_create_entry(FileList *filelist, const int in
ret->redirection_path = BLI_strdup(entry->redirection_path);
}
ret->id = entry->local_data.id;
ret->asset = entry->asset;
ret->asset = entry->get_asset();
/* For some file types the preview is already available. */
if (entry->local_data.preview_image &&
BKE_previewimg_is_finished(entry->local_data.preview_image, ICON_SIZE_PREVIEW))
@@ -2270,7 +2294,7 @@ asset_system::AssetRepresentation *filelist_entry_get_asset_representation(
const FileList *filelist, const int index)
{
const FileListInternEntry *intern_entry = filelist_entry_intern_get(filelist, index);
return intern_entry->asset;
return intern_entry->get_asset();
}
ID *filelist_file_get_id(const FileDirEntry *file)
@@ -3094,7 +3118,7 @@ static int filelist_readjob_list_dir(FileListReadJob *job_params,
continue;
}
entry = MEM_cnew<FileListInternEntry>(__func__);
entry = MEM_new<FileListInternEntry>(__func__);
entry->relpath = current_relpath_append(job_params, files[i].relname);
entry->st = files[i].s;
@@ -3186,7 +3210,7 @@ ENUM_OPERATORS(ListLibOptions, LIST_LIB_ADD_PARENT);
static FileListInternEntry *filelist_readjob_list_lib_group_create(
const FileListReadJob *job_params, const int idcode, const char *group_name)
{
FileListInternEntry *entry = MEM_cnew<FileListInternEntry>(__func__);
FileListInternEntry *entry = MEM_new<FileListInternEntry>(__func__);
entry->relpath = current_relpath_append(job_params, group_name);
entry->typeflag |= FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR;
entry->blentype = idcode;
@@ -3204,7 +3228,7 @@ static void filelist_readjob_list_lib_add_datablock(FileListReadJob *job_params,
const int idcode,
const char *group_name)
{
FileListInternEntry *entry = MEM_cnew<FileListInternEntry>(__func__);
FileListInternEntry *entry = MEM_new<FileListInternEntry>(__func__);
if (prefix_relpath_with_group_name) {
std::string datablock_path = StringRef(group_name) + SEP_STR + datablock_info->name;
entry->relpath = current_relpath_append(job_params, datablock_path.c_str());
@@ -3229,7 +3253,7 @@ static void filelist_readjob_list_lib_add_datablock(FileListReadJob *job_params,
datablock_info->asset_data = metadata.get();
datablock_info->free_asset_data = false;
entry->asset = &job_params->load_asset_library->add_external_asset(
entry->asset = job_params->load_asset_library->add_external_asset(
entry->relpath, datablock_info->name, idcode, std::move(metadata));
}
}
@@ -3273,7 +3297,7 @@ static void filelist_readjob_list_lib_add_from_indexer_entries(
static FileListInternEntry *filelist_readjob_list_lib_navigate_to_parent_entry_create(
const FileListReadJob *job_params)
{
FileListInternEntry *entry = MEM_cnew<FileListInternEntry>(__func__);
FileListInternEntry *entry = MEM_new<FileListInternEntry>(__func__);
entry->relpath = current_relpath_append(job_params, FILENAME_PARENT);
entry->typeflag |= (FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR);
return entry;
@@ -3893,7 +3917,7 @@ static void filelist_readjob_main_assets_add_items(FileListReadJob *job_params,
const char *id_code_name = BKE_idtype_idcode_to_name(GS(id_iter->name));
entry = MEM_cnew<FileListInternEntry>(__func__);
entry = MEM_new<FileListInternEntry>(__func__);
std::string datablock_path = StringRef(id_code_name) + SEP_STR + (id_iter->name + 2);
entry->relpath = current_relpath_append(job_params, datablock_path.c_str());
entry->name = id_iter->name + 2;
@@ -3905,7 +3929,7 @@ static void filelist_readjob_main_assets_add_items(FileListReadJob *job_params,
id_iter);
entry->local_data.id = id_iter;
if (job_params->load_asset_library) {
entry->asset = &job_params->load_asset_library->add_local_id_asset(entry->relpath, *id_iter);
entry->asset = job_params->load_asset_library->add_local_id_asset(entry->relpath, *id_iter);
}
entries_num++;
BLI_addtail(&tmp_entries, entry);