Fix FileSystemModel crash on stale filesystem-model indexes

- dispatch filesystem events on the main thread in FileSystemListener, preserving model, tree, document, and callback ordering
- add address + generation node identity to close the allocator-reuse hole
- return null/empty results for stale indexes instead of root data
- assert the main-thread contract and token-guard queued listener actions
- add attached-view and stale-selection crash regression tests

Fixes crash reported in discussion SpartanJ/ecode#952
This commit is contained in:
Martín Lucas Golini
2026-08-20 00:24:16 -03:00
parent 8448d579ab
commit 9c794fc12f
9 changed files with 392 additions and 75 deletions
+28 -2
View File
@@ -5,6 +5,7 @@
#include <atomic>
#include <memory>
#include <eepp/core/containers.hpp>
#include <eepp/system/fileinfo.hpp>
#include <eepp/system/threadpool.hpp>
#include <eepp/system/translator.hpp>
@@ -82,7 +83,7 @@ class EE_API FileSystemModel : public Model {
Node( const std::string& rootPath, FileSystemModel& model,
const std::shared_ptr<ThreadPool>& threadPool = {} );
Node( FileInfo&& info, Node* parent );
Node( FileInfo&& info, Node* parent, const FileSystemModel& model );
const std::string& getName() const { return mName; }
@@ -137,6 +138,8 @@ class EE_API FileSystemModel : public Model {
String mDisplayName;
std::string mMimeType;
Node* mParent{ nullptr };
const FileSystemModel* mModel{ nullptr };
Uint64 mId{ 0 };
FileInfo mInfo;
std::vector<Node*> mChildren;
bool mHasTraversed{ false };
@@ -181,6 +184,14 @@ class EE_API FileSystemModel : public Model {
void update();
const Node& node( const ModelIndex& index ) const;
/**
* @brief Returns the node for a valid index, or nullptr for a stale index
* (node deleted by a background refresh). An invalid index resolves to the
* root.
*/
const Node* nodePtr( const ModelIndex& index ) const;
virtual size_t treeColumn() const { return Column::Name; }
virtual size_t rowCount( const ModelIndex& = ModelIndex() ) const;
virtual size_t columnCount( const ModelIndex& = ModelIndex() ) const;
@@ -203,8 +214,18 @@ class EE_API FileSystemModel : public Model {
void setPreviouslySelectedIndex( const ModelIndex& previouslySelectedIndex );
/**
* @brief Processes a filesystem event (add/delete/move/modified).
*
* Must be called from the main thread when views are attached: the method
* mutates view selections and metadata. ecode's FileSystemListener
* dispatches watcher events to the main thread before calling this; direct
* callers are responsible for the same requirement.
*/
bool handleFileEvent( const FileEvent& event );
virtual bool isValid( const ModelIndex& index ) const override;
virtual bool classModelRoleEnabled() { return true; }
~FileSystemModel();
@@ -222,7 +243,12 @@ class EE_API FileSystemModel : public Model {
ModelIndex mPreviouslySelectedIndex{};
Node& nodeRef( const ModelIndex& index ) const;
Node* nodeRef( const ModelIndex& index ) const;
bool isNodeAlive( const Node* node, Uint64 id ) const;
mutable UnorderedSet<const Node*> mAliveNodes;
mutable Uint64 mNextNodeId{ 1 };
FileSystemModel( const std::string& rootPath, const Mode& mode,
const DisplayConfig& displayConfig, Translator* translator,
+1 -1
View File
@@ -100,7 +100,7 @@ class EE_API Model {
virtual bool isEditable( const ModelIndex& ) const { return false; }
bool isValid( const ModelIndex& index ) const {
virtual bool isValid( const ModelIndex& index ) const {
auto parentIndex = this->parentIndex( index );
return index.row() >= 0 && index.row() < (Int64)rowCount( parentIndex ) &&
index.column() >= 0 && index.column() < (Int64)columnCount( parentIndex );