CNA-BUG-077: An invalid CNA_GRAPHICS_RENDERER value (or Module.cnaPreferredRenderer) terminates the process during static initialisation
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.
The renderer registry publishes the compiled-in set from a namespace-scope initialiser that consults the environment, so an unknown or not-linked renderer name throws out of that initialiser and ends the process through std::terminate before main.
- Identifier
CNA-BUG-077- Category
- Bug
- Subsystem
- Core & runtime
- 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
- The CNA_GRAPHICS_RENDERER environment variable and the Emscripten Module.cnaPreferredRenderer page property as inputs to CNA::GraphicsRendererSelection
Expected behaviour
The comment on ConsultEnvironmentOnce in GraphicsRendererSelection.cpp says a value naming a renderer that is not compiled in "throws, consistent with SetPreferred()", i.e. a catchable System::InvalidOperationException that game code can report. The public header documents no exception from GetSelected(), and on the web GraphicsRendererSelectionEmscripten.cpp states the aim of keeping "the page in control instead of aborting the module".
Actual behaviour at TARGET
The generated registry (CnaRendererRegistry.generated.cpp.in) initialises the namespace-scope cnaRendererRegistryPublished by calling GraphicsRendererSelectionAccessEXT::PublishAvailable, which marks the set published and calls RebuildAttemptOrder; that calls GraphicsRendererSelection::GetSelected, which, with no explicit preference yet, calls ConsultEnvironmentOnce. A value that names no identity throws InvalidOperationException; one that names a renderer not linked into the build is rejected by RejectIfNotCompiledIn (fallback is still off before main). The exception leaves the initialisation of a variable with static storage duration, which the C++ language turns into std::terminate. Game code cannot catch it, the build default never applies, and in a browser build the page property takes the same path and aborts the module.
Because environmentConsulted is set before the value is parsed, the variable is also read only once per process (and again by the test-only reset).
Source locations
cmake/templates/CnaRendererRegistry.generated.cpp.in— the namespace-scope cnaRendererRegistryPublished initialiser calling PublishAvailablemodules/core/src/GraphicsRendererSelection.cpp— PublishAvailable, RebuildAttemptOrder, GetSelected, ConsultEnvironmentOnce, RejectIfNotCompiledInmodules/core/include/CNA/GraphicsRendererSelection.hpp— GetSelected() documents the precedence but no exceptionmodules/core/src/GraphicsRendererSelectionEmscripten.cpp— cna_read_module_preferred_renderer feeds the page property into the same pathmodules/graphics/tests/CNA/GraphicsRendererSelectionTests.cpp— no test sets an invalid CNA_GRAPHICS_RENDERER
Evidence
Checked by reading at 009d40f5; nothing was built or executed for this entry. The call chain was followed by reading; the outcome rests on the language rule that an exception escaping the initialisation of a non-local static variable calls std::terminate, which holds whether or not the implementation defers that initialisation. The user guide already states this consequence as derived from the source.
Focused reproduction
Illustrative; not run for this entry.
CNA_GRAPHICS_RENDERER=NOT_A_RENDERER ./my_game
# expected by the source comment: a catchable System::InvalidOperationException
# derived from the code: terminate called before main()
Current tests
GraphicsRendererSelectionTests.cpp and GraphicsRendererFallbackTests.cpp cover precedence, rejection messages and the latch through the API; none sets the environment variable. The multi-renderer CI workflow sets a valid value in its reachability step.
Regression test
A subprocess (or GoogleTest death) test that starts a test binary with CNA_GRAPHICS_RENDERER=NOT_A_RENDERER and asserts the chosen contract: either a documented clean exit with a diagnostic, or a catchable exception raised at the first query after main (which requires moving the environment read out of the static initialiser).
Blast radius
Native and web builds, only when the variable or page property is set to an unknown identity or one not linked into the bundle. Valid values and unset variables are unaffected.
Workaround
Set the variable or page property only to an identity linked into the build (GraphicsRendererSelection::GetAvailable() lists them), or leave it unset. There is no in-process workaround, because the value is read before main.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- Runtime renderer selection: API
- Known issues
- Bug index