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

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  source links pinned to 009d40f5

✓

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

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.

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

Known issues
Bug index