CNA-BUG-122: ContentManager::Load<SoundEffect> lets its loose tier's System::NotSupportedException escape instead of ContentLoadException
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 3b43bd86; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
The SoundEffect specialization of ContentManager::Load returns its loose reader's result without the generic template's wrapper, so a missing or undecodable sound file escapes as System::NotSupportedException rather than ContentLoadException.
- Identifier
CNA-BUG-122- Category
- Bug
- Subsystem
- Content & XNB/CNB/CNJ
- Status
- Narrowed (partially fixed; describes only what survives)
- Verified against
- CNA
3b43bd86(3b43bd8687ed46523d873f9bec4b32bd8481dcb5) - Severity
- Medium (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
- ContentManager::Load<Audio::SoundEffect> (documented to throw ContentLoadException when the asset cannot be loaded); XNA ContentManager.Load (ContentLoadException for open and read failures)
Expected behaviour
XNA 4.0 reports open and read failures of any asset as a ContentLoadException. CNA documents Load<T> as throwing ContentLoadException when the asset cannot be loaded, and its generic loose tier wraps every std::exception in one.
Actual behaviour at TARGET
The explicit SoundEffect specialization of Load (ContentSoundEffectLoaders.cpp) returns reader.Read(resolvedPath, *this) from its loose tier without the generic template's try/catch. A missing or undecodable .wav in a sound-enabled build surfaces as System::NotSupportedException ("Failed to load sound: …") from SoundEffect::SoundEffect(const std::string&), which derives from System::Exception, not from ContentLoadException. XNA-style handlers such as catch (const ContentLoadException&) around an optional or localized sound therefore miss it; since KF-13 (2d85a276f) ContentLoadException also derives from System::Exception, so a catch (System::Exception) covers both.
Fixed since first recorded: a reader that produces another type than requested is a ContentLoadException naming both types (CastReadResult in ContentReader.hpp, KF-9, 0af45cbd4), and a disposed manager now throws System::ObjectDisposedException from Load<T>, the SoundEffect specialization, ReadAsset<T>, Unload and LoadUntypedXnbReference (cd211420e, 39ef8ea2f).
Source locations
modules/content/src/Xna/ContentSoundEffectLoaders.cpp— ContentManager::Load specialization for Audio::SoundEffect - loose tier returns reader.Read without a wrappermodules/content/include/Microsoft/Xna/Framework/Content/ContentManager.hpp— ContentManager::Load - the generic loose tier wraps std::exception in ContentLoadException; disposed checkmodules/audio/src/Xna/SoundEffect.cpp— SoundEffect file-name constructor - throws System::NotSupportedExceptionmodules/content/include/Microsoft/Xna/Framework/Content/ContentReader.hpp— CastReadResult - the fixed wrong-type path
Evidence
Checked by reading at 3b43bd86; not executed. CNA's own XnbContainerFuzzTests.cpp counts std::out_of_range, std::length_error and std::invalid_argument escaping Load<T> on mutated .xnb input as "clean rejections"; those corrupt-input cases are not claimed here as a contract violation. XNA behaviour taken from the decompiled XNA 4.0 ContentManager.Load and ContentReader.InvokeReader.
Re-verified at c6dec91f: Load<T> (ContentManager.hpp) and the SoundEffect specialization check disposed_ and throw System::ObjectDisposedException("ContentManager") (cd211420e), which ContentOwnershipCompatibilityTest.DisposedManagerRefusesLoadReadAndUnloadBeforeOpening pins; the specialization, moved from ContentManager.cpp to ContentSoundEffectLoaders.cpp by the link-boundary split, still returns reader.Read(...) unwrapped. Not executed.
Focused reproduction
Illustrative; not compiled.
ContentManager content(&services, "Content");
try { auto s = content.Load<Audio::SoundEffect>("missing"); } // no missing.* anywhere
catch (const ContentLoadException&) { /* not reached: System::NotSupportedException */ }
content.Dispose();
try { auto t = content.Load<Graphics::Texture2D>("x"); }
catch (const System::ObjectDisposedException&) { /* not reached: std::runtime_error */ }
Current tests
CustomContentTypeReaderTests.cpp pins the fixed wrong-type case (LoadingAsAnotherTypeThrowsContentLoadExceptionNotBadAnyCast), and ContentOwnershipTests.cpp the disposed-manager refusal (DisposedManagerRefusesLoadReadAndUnloadBeforeOpening). No test calls Load<SoundEffect> on a missing or undecodable loose file.
Regression test
An EXPECT_THROW of Load<SoundEffect>("missing") (and of an undecodable .wav) with SDL's dummy audio driver, expecting ContentLoadException.
Blast radius
Callers that rely on ContentLoadException for fallback (optional content, localized-asset probing, mod loaders) when sound effects come from loose files. The .xnb and .cnb tiers, every other type's loose tier and the disposed-manager case already conform. A build without SOUND_ENABLED does not throw from SoundEffect's file constructor at all.
Workaround
Catch System::Exception (or std::exception) around Load<SoundEffect> and treat any failure as a load failure.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- XNB guide: diagnosing XNB problems
- Internals
- Content runtime: inconsistencies
- Maintainer workflow
- Modify ContentManager: error contract
- Known issues
- Bug index