CNA-BUG-122: ContentManager::Load<SoundEffect> lets its loose tier's System::NotSupportedException escape instead of ContentLoadException

CNA snapshot 3b43bd86  ·  Known Issues › Current bugs  ·  source links pinned to 3b43bd86

✓

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

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.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

Known issues
Bug index