Fix VFS use-after-move crash by using stable handles for mjVFS

Root cause: resources and default mount stored pointers tied to the public mjVFS object address. If mjVFS was moved after initialization, resource close/read paths could dereference stale pointers and crash.\n\nThis change stores a stable internal mjVFS handle inside VFS and points resources/default mount to that stable handle. It also tracks the current public owner pointer and rebinds it in Upcast so lifecycle operations (including self-destruct cleanup) target the current mjVFS address after moves.\n\nAdds regression test UserVfsTest.MoveVfsAfterOpenResource, which opens a resource, moves mjVFS, then reads/closes/deletes successfully.
This commit is contained in:
Nandini Dhanrale
2026-02-16 12:40:13 +05:30
parent 881544c0c5
commit c45f13d3a9
3 changed files with 49 additions and 7 deletions
+19 -6
View File
@@ -105,7 +105,9 @@ std::string StripPathAndLower(std::string path) {
namespace mujoco::user {
VFS::VFS(mjVFS* vfs) : self_(vfs) {
VFS::VFS(mjVFS* vfs) : owner_(vfs) {
stable_vfs_.impl_ = this;
mjp_defaultResourceProvider(&default_provider_);
default_provider_.open = [](mjResource* res) {
return OpenFile(res->name, res);
@@ -121,7 +123,7 @@ VFS::VFS(mjVFS* vfs) : self_(vfs) {
};
default_provider_.prefix = nullptr;
default_mount_.vfs = self_;
default_mount_.vfs = &stable_vfs_;
default_mount_.provider = &default_provider_;
default_mount_.data = nullptr;
default_mount_.name = nullptr;
@@ -245,7 +247,7 @@ int VFS::Read(mjResource* resource, const void** buffer) {
VFS::ResourcePtr VFS::CreateResource(std::string_view name,
const mjpResourceProvider* provider) {
mjResource* res = new mjResource();
res->vfs = self_;
res->vfs = &stable_vfs_;
res->provider = provider;
res->data = nullptr;
res->name = new char[name.size() + 1];
@@ -312,20 +314,31 @@ mjResource* VFS::FindMount(const std::string& fullpath) {
void VFS::MaybeSelfDestruct() {
if (destructor_) {
destructor_(self_);
destructor_(owner_);
}
}
void VFS::Bind(mjVFS* vfs) {
owner_ = vfs;
}
void VFS::SetToSelfDestruct(std::function<void(mjVFS*)> destructor) {
destructor_ = std::move(destructor);
}
VFS* VFS::Upcast(mjVFS* vfs) {
return vfs ? static_cast<VFS*>(vfs->impl_) : nullptr;
if (!vfs) {
return nullptr;
}
VFS* impl = static_cast<VFS*>(vfs->impl_);
if (impl) {
impl->Bind(vfs);
}
return impl;
}
const VFS* VFS::Upcast(const mjVFS* vfs) {
return vfs ? static_cast<const VFS*>(vfs->impl_) : nullptr;
return Upcast(const_cast<mjVFS*>(vfs));
}
} // namespace mujoco::user
+6 -1
View File
@@ -117,7 +117,12 @@ class VFS {
// that `this` will be invalidated after this call.
void MaybeSelfDestruct();
mjVFS* self_;
// Rebinds the current public mjVFS pointer to this implementation.
// This supports scenarios where the public mjVFS struct is moved.
void Bind(mjVFS* vfs);
mjVFS stable_vfs_;
mjVFS* owner_;
std::mutex mutex_; // Protects open_resources_ and mounts_.
std::unordered_map<mjResource*, ResourcePtr> open_resources_;
std::unordered_map<std::string, ResourcePtr> mounts_;