CNA-BUG-065: Curve::Evaluate divides by a zero key span in the cyclic loop modes and converts the resulting infinity to int
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 3b43bd86; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
When every key shares one position, GetNumberOfCycle divides by the zero key span without a guard and casts the infinity to int (undefined behaviour) in the Cycle, CycleOffset and Oscillate loop modes, where XNA's cycle count is 0.
- Identifier
CNA-BUG-065- Category
- Bug
- Subsystem
- Math & geometry
- Status
- Narrowed (partially fixed; describes only what survives)
- Verified against
- CNA
3b43bd86(3b43bd8687ed46523d873f9bec4b32bd8481dcb5) - 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
- Curve::Evaluate(float) for curves whose keys all share one position, with a Cycle, CycleOffset or Oscillate pre- or post-loop; C ABI cna_curve_evaluate
Expected behaviour
XNA 4.0 guards the division (read from the IL of the genuine XNA 4.0 Microsoft.Xna.Framework assembly; not executed): CurveKeyCollection.ComputeCacheValues leaves the inverse time range at 0 unless the range is above float.Epsilon (1.401298E-45), so the cycle count of an all-coincident curve is 0 and XNA returns finite values from its keys.
Actual behaviour at TARGET
CurveKeyCollection::AddinCurveKeyCollection.cppaccepts a key at an existing position (it is inserted after the equal ones), as XNA's collection does.- All keys at one position p with a Cycle,
CycleOffsetor Oscillate pre- or post-loop:GetNumberOfCycleinCurve.cppcomputes (position - p) / 0, an infinity, andstatic_cast<int>of it is undefined behaviour in C++ (typicallyINT_MINon x86-64). ACycleOffsetloop then adds cycle * (last.Value - first.Value), so a curve whose key values differ returns a huge number; XNA returns a finite value from the keys. - The path is ported unchanged from FNA's C# implementation (read at FNA commit 1358793, 2026-08-01).
Source locations
modules/math/src/Curve.cpp— Curve::GetNumberOfCycle (unguarded division and cast); GetCurvePosition (the fixed half)modules/math/src/CurveKeyCollection.cpp— CurveKeyCollection::Add accepts duplicate positionsmodules/math/tests/Microsoft/Xna/Framework/CurveTests.cpp— DuplicateAndTinySegmentsMatchXnaSelection; no all-coincident cyclic case
Evidence
Checked by reading at 3b43bd86; nothing was built or executed for this entry. The division and the cast are in the function body; XNA's guard was read from its IL. The C++ rule that converting an out-of-range floating value to int is undefined comes from the standard's floating-integral conversions.
Narrowed at c6dec91f: d753d0997 (CNA-DEMOS-ISSUE-138) fixed the segment half of this entry: Curve::GetCurvePosition now normalises in double and uses t = 0 for a span of 1e-10 or less, so coincident keys select the first key instead of returning NaN (CurveTest.DuplicateAndTinySegmentsMatchXnaSelection). The cycle count is unchanged.
Focused reproduction
Illustrative; not compiled or run for this entry.
Curve c;\nc.setPostLoopProperty(CurveLoopType::CycleOffset);\nc.getKeysProperty().Add(CurveKey(1.0f, 3.0f));\nc.getKeysProperty().Add(CurveKey(1.0f, 4.0f));\nfloat v = c.Evaluate(2.0f); // GetNumberOfCycle: (2 - 1) / 0 cast to int -- undefined behaviour
Current tests
CurveTests.cpp now evaluates duplicate and tiny segments, but no test evaluates an all-coincident curve with a cyclic loop type; the loop-type tests use keys at distinct positions.
Regression test
An all-coincident curve with each cyclic loop type evaluated outside the range, expecting XNA's finite value, run under UBSan, whose float-cast-overflow check flags the current code.
Blast radius
Degenerate curves only: all keys at one position with a cyclic loop mode, whether built in code, through the C ABI or loaded from XNB. Well-formed curves are unaffected.
Workaround
Give cyclic curves a positive time range.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- Math types: curve types
- Maintainer workflow
- Add a regression test: the math case
- Known issues
- Bug index