CNA-BUG-077: An invalid CNA_GRAPHICS_RENDERER value (or Module.cnaPreferredRenderer) terminates the process during static initialisation

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.

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

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.

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

Known issues
Bug index