CNA-BUG-155: StorageContainer members keep working after Dispose instead of throwing ObjectDisposedException
Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
No StorageContainer member checks the disposed flag, so file and directory operations, name listings and the DisplayName and StorageDevice getters succeed after Dispose, where XNA 4.0 throws ObjectDisposedException.
- Identifier
CNA-BUG-155- Category
- Bug
- Subsystem
- Storage
- Status
- Open
- Verified against
- CNA
009d40f5(009d40f5dd085c4e674d3479675fac84b12b3e0a) - Severity
- Low (a triage suggestion, not a project priority)
- Evidence basis
- Source-verified: read at TARGET, not executed
- Tests touching this area
- Yes: see Current tests
- Affected contract
- Microsoft::Xna::Framework::Storage::StorageContainer after Dispose(): every path method, GetFileNames/GetDirectoryNames, getDisplayNameProperty() and getStorageDeviceProperty()
Expected behaviour
XNA 4.0 guards every member except IsDisposed and Dispose with VerifyNotDisposed, which throws ObjectDisposedException (decompiled StorageContainer: ValidateArguments, the two getters and both name listings). A disposed container is unusable.
Actual behaviour at TARGET
StorageContainer::Dispose in StorageContainer.cpp sets isDisposed_ and raises Disposing once; nothing else reads the flag except getIsDisposedProperty and the destructor. CreateFile, OpenFile, DeleteFile, CreateDirectory, DeleteDirectory, FileExists, DirectoryExists, both name listings and both getters behave exactly as before disposal. FNA's container has no disposal check either, so CNA follows FNA rather than XNA here. Streams opened before Dispose stay valid, which matches XNA (they are independent streams) and is not part of this defect; the FileShare point the candidate also raised is published as CNA-GAP-048. One CNA test pins part of the current behaviour: the C ABI smoke test (CABI-34, StorageSmoke.c) requires a container to answer file_exists from inside its own Disposing callback, which runs after the flag is set, so any fix has to keep that callback window usable or change that test deliberately; XNA itself would throw there.
Source locations
modules/storage/src/StorageContainer.cpp— StorageContainer::Dispose sets the flag; no member checks itmodules/storage/include/Microsoft/Xna/Framework/Storage/StorageContainer.hpp— getIsDisposedProperty and Dispose documentationmodules/c-api/src/CnaCApiStorage.cpp— cna_storage_container_dispose disposes without releasing the handle; the other container routes call the same unchecked members
Evidence
Checked by reading StorageContainer.cpp and its header at 009d40f5; not executed. XNA's behaviour is from the decompiled StorageContainer (VerifyNotDisposed); FNA's from a local reference checkout of FNA's src/Storage/StorageContainer.cs (revision b355124). The C ABI inherits the behaviour: cna_storage_container_dispose disposes "without releasing its handle", and the other container routes forward to the same members without consulting getIsDisposedProperty.
Independently observed as a separate finding (merged): Dispose only sets isDisposed_; no member consults it, so file and directory operations still run on a disposed container.
Independent re-verification: Checked by reading StorageContainer.cpp and its header at 009d40f5; not executed. XNA's behaviour is from the decompiled StorageContainer (VerifyNotDisposed); FNA's from a local reference checkout of FNA's src/Storage/StorageContainer.cs. The C ABI inherits the behaviour: cna_storage_container_dispose disposes "without releasing its handle", GetContainer does not consult the flag, and the other container routes forward to the same members. Tests: no test calls a member after Dispose() has returned, and StorageDeviceTests.cpp never disposes a container explicitly. CApi_StorageSmoke (C ABI builds only) does exercise Dispose, IsDisposed and the exactly-once Disposing event, and its CABI-34 ordering case asserts that FileExists succeeds from inside the Disposing callback. Independently observed as a separate finding (merged): Dispose only sets isDisposed_; no member consults it, so file and directory operations still run on a disposed container.
Focused reproduction
// Illustrative; not compiled or run for this entry.
container->Dispose();
auto after = container->CreateFile("after.sav"); // succeeds; XNA throws ObjectDisposedException
auto name = container->getDisplayNameProperty(); // succeeds; XNA throws
Current tests
None. StorageDeviceTests.cpp never disposes a container explicitly; the storage internals page lists disposal among what the suite does not establish.
Regression test
After Dispose(), each member except getIsDisposedProperty throws System::ObjectDisposedException (the type the sensor classes already use), and Disposing was raised exactly once.
Blast radius
Code that keeps using a container after disposing it silently keeps reading and writing where XNA fails fast, which hides lifetime bugs in ported save code; C ABI hosts that call cna_storage_container_dispose and keep the handle behave the same. Correct code is unaffected.
Workaround
Treat IsDisposed as terminal in game code, and open a new container instead of reusing a disposed one.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- Known issues
- Bug index