CNA-BUG-111: ShaderEffect::SetTexture(int, Texture2D&), SetTexture(int, TextureCube&) and SetTexture(int, Texture3D&) evaluate *renderer_ of a disposed texture (an empty shared_ptr) and forward a null pointer 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.
All three SetTexture overloads pass &texture.GetRenderer(), and GetRenderer() returns *renderer_. Dispose resets renderer_, so a disposed texture yields a reference formed from an empty shared_ptr (formally undefined behaviour) and a null pointer reaches IEffectRenderer::BindTexture, BindTextureCube or BindTexture3D.
- Identifier
CNA-BUG-111- Category
- Bug
- Subsystem
- Graphics & renderers
- 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
- CNA ShaderEffect::SetTexture(int, TextureCube&), SetTexture(int, Texture3D&)
Expected behaviour
CNA's convention for consuming a disposed resource is System::ObjectDisposedException (SpriteBatch::Draw, GraphicsDevice texture and buffer binding, and ShaderEffect::SetTextureArrayEXT, whose header documents the throw). SetTexture should refuse a disposed texture the same way instead of forming a reference from an empty shared_ptr.
Actual behaviour at TARGET
ShaderEffect.cpp binds &texture.GetRenderer() for Texture2D, TextureCube and Texture3D, and each of their Dispose overrides resets renderer_. Every renderer that overrides these binds tolerates the resulting null pointer: EasyGL and OpenGL4 return early, WebGPU and SDL_GPU resolve null to an empty binding, Vulkan, Direct3D 11 and Direct3D 12 dynamic_cast it to null and fall back to a default or empty view. The observable result is a silently unbound sampler slot (an abort only where the standard library is built with assertions), not a crash. The storage-less case is narrow: a TextureCube on DirectX9 without cubemap caps, or an FNA3D creation failure, both of which leave BindTextureCube at the base no-op; Texture3D's constructor already throws NotSupportedException when the capability is missing.
Source locations
modules/graphics/src/Xna/ShaderEffect.cpp— ShaderEffect::SetTexture overloadsmodules/graphics/include/Microsoft/Xna/Framework/Graphics/TextureCube.hpp— TextureCube::GetRenderermodules/graphics/src/Xna/TextureCube.cpp— TextureCube constructor and Dispose (renderer_ null/reset)
Evidence
Checked by reading the named sources at 009d40f5; nothing was built or executed for this entry.
Independent re-verification: Checked by reading the named sources at 009d40f5; nothing was built or executed for this entry. Effect tests that bind live cube and volume textures (for example vulkan_effect_bound_texture_test.cpp and easygl_texture3d_addressw_test.cpp) exercise the overloads; none binds a disposed texture.
Focused reproduction
// Illustrative; not compiled or run for this entry.
TextureCube cube(device, 64, false, SurfaceFormat::Color);
cube.Dispose();
shaderEffect.SetTexture(0, cube); // *renderer_ on a null shared_ptr
Current tests
No test binds a disposed cube or volume texture to a ShaderEffect.
Regression test
A test that disposes a TextureCube and a Texture3D and expects ObjectDisposedException from SetTexture.
Blast radius
Engine code that binds cube or volume textures to ShaderEffects across disposal or on renderers without cube storage.
Workaround
Check IsDisposed before binding.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- Deep dives
- Shader routes: the ShaderEffect contract
- Known issues
- Bug index