CNA-BUG-128: Model::Draw indexes its bone scratch buffer with bone 0 even for a model with no bones

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.

Model's three-argument constructor accepts an empty bone list, but Draw then reads sharedDrawBoneMatrices_[0] for every mesh without growing the thread-local vector, an unchecked out-of-range read or a stale matrix from an earlier model.

Identifier
CNA-BUG-128
Category
Bug
Subsystem
Models & glTF
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
Model::Draw(const Matrix&, const Matrix&, const Matrix&) and Model::Model(GraphicsDevice*, std::vector<ModelBone*>, std::vector<ModelMesh*>)

Expected behaviour

A model the constructor accepts should draw without undefined behaviour. The constructor treats an empty bone list as legal (“leniency”, leaving Root null), so Draw must handle it, for example by using the identity as the mesh's parent transform.

Actual behaviour at TARGET

Model::Draw in Model.cpp resizes the thread_local sharedDrawBoneMatrices_ only up to the bone count (0 here), then, for every effect implementing IEffectMatrices, uses the parent bone's index or 0 when the mesh has no parent bone, and reads sharedDrawBoneMatrices_[boneIdx] through the unchecked operator[]. On a thread that has not drawn a larger model the vector is empty (an out-of-range read); on one that has, World silently becomes a stale matrix from that earlier model. A mesh whose parent bone belongs to a different, larger model reaches the same read.

Source locations

Evidence

Checked by reading at 009d40f5; not executed. Every model CNA's content paths build has at least a root bone, so only hand-built models reach it.

Focused reproduction

// Illustrative; not compiled or run.
ModelMesh mesh(&device, {&part});          // part has a BasicEffect
Model model(&device, {}, {&mesh});           // accepted: no bones
model.Draw(world, view, projection);         // reads sharedDrawBoneMatrices_[0]

Current tests

ModelTests.cpp draws models that all have bones; none draws a zero-bone model with an effect.

Regression test

Draw a zero-bone model with one mesh and a BasicEffect on a fresh thread and assert World equals the passed world matrix (run under AddressSanitizer).

Blast radius

Hand-built Model objects without bones, and meshes attached to the wrong model. Loaded content is unaffected.

Workaround

Give every hand-built model at least one root bone and set each mesh's parent bone.

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

Known issues
Bug index