Menu

#707 GTK3V2MAIN elev.c: mode0/mode1 elevation-mode precedence cascade -- bugprone-branch-clone, intentionally not merged

Next Release
needs-review
None
V5.3.1GA
Windows
5
2026-09-03
2026-08-21
No

Found triaging clang-tidy's bugprone-branch-clone Phase D (see .claude/phase-d-branch-clone-remaining-plan.md). app/bin/elev.c:1197 and :1199 -- inside the endpoint-elevation-mode reconciliation if chain (mode0/mode1 priority cascade around line 1180-1207).

Two identical-body pairs exist: the mode0==ELEV_GRADE / mode0==ELEV_COMP branches both no-op (;), and the mode1==ELEV_GRADE / mode1==ELEV_COMP branches both do mode0=mode1.

Deliberately NOT merged: the chain's ordering encodes which endpoint's mode 'wins' when both ends have a special mode simultaneously (checked in mode0-then-mode1 pairs, per mode type, in a specific sequence). A same-value merge across non-adjacent conditions risks silently changing that precedence -- not confirmed safe without a deeper read of every mode-combination's intended precedence, not attempted in this pass. This ticket exists purely to track the decision (per project policy: leave-alone findings get a ticket) so it doesn't keep resurfacing as 'new'. No fix planned. A regression fixture exercising a joined track pair where one endpoint is ELEV_GRADE and the other ELEV_COMP (and the reverse ordering) would give this cascade real coverage and clarify the intended precedence -- currently believed untested.

Discussion

  • Martin Fischer

    Martin Fischer - 2026-08-26
    • Description has changed:

    Diff:

    
    
    • status: --> open
    • assigned_to: Martin Fischer
    • XTrackCAD Version: --> V5.3.1GA
    • OS Level: -->
    • Operating System: --> Windows
    • Priority: --> 5
    • Milestone: --> Next Release
     
  • Martin Fischer

    Martin Fischer - 2026-08-26

    Investigated. Precedence in SetTrkElevModes() (elev.c:1171-1210) is actually already well-defined and consistent — STATION > GRADE > COMP, independent of which endpoint
    (trk0/trk1) holds which mode, because it's a single fixed-order if/else-if chain. So the "which endpoint wins" framing isn't quite the issue; elev correctly stays 0.0 in
    the GRADE/COMP branches since those heights are computed dynamically elsewhere (fork-propagation engine, elev.c:632+), not stored at join time.

    The actual gap: when both ends are GRADE (or both COMP), trk1's specific grade/computed state is silently discarded in favor of trk0's, with no check for whether they
    actually agree. This is inconsistent with the DEF/DEF case immediately above (line 1183-1185), which explicitly compares heights and raises MSG_JOIN_DIFFER_ELEV if they
    differ by more than 0.1.

    Fix: keep the STATION > GRADE > COMP precedence as-is, but add the same diff-check/warning for the mode0==mode1==GRADE and mode0==mode1==COMP cases that DEF/DEF already
    has, in SetTrkElevModes() itself (the conflict can only be detected once a join is attempted, not earlier in the UI).

     
  • Martin Fischer

    Martin Fischer - 2026-09-03
    • status: open --> needs-review
     
  • Martin Fischer

    Martin Fischer - 2026-09-03

    Fixed on branch bug-707-elev-join-mode-merge-gtk3 (changesets 6b70d18317e4 and c7aea294eb58).

    Findings

    The elevation-mode precedence in SetTrkElevModes() (elev.c) was reviewed. When two endpoints are joined, the resulting endpoint mode is chosen by this order: Defined
    elevation beats Station name beats Grade beats Computed; within the same tier the first endpoint wins. That ordering was already correct and is unchanged.

    Two real problems were found in the code that implements it:

    1. Latent bug in the join ladder. The branch that handled "only the second endpoint has a Defined elevation" assigned a local variable that was never used and then
      applied the FIRST endpoint's (non-Defined) mode to both ends. Consequence: joining, for example, a Grade endpoint to a Defined-elevation endpoint discarded the
      defined elevation entirely and left both ends as Grade. Joining in the other order worked. This is now fixed - the defined elevation always wins and propagates to
      both endpoints regardless of join order.
    2. No feedback on a station-name conflict. When both endpoints carried a Defined elevation the code already warned the user if the two heights differed
      (MSG_JOIN_DIFFER_ELEV). There was no equivalent when both endpoints carried an elevation Station and the names differed - one name was silently dropped. A new
      message, MSG_JOIN_DIFFER_STATION, is now shown in that case, keeping the first endpoint's station name (consistent with the existing precedence rule).

    Changes

    • The 11-branch mode-resolution ladder in SetTrkElevModes() was extracted into a new standalone, side-effect-free function, MergeJoinElev(), in app/bin/elevjoin.c /
      elevjoin.h. It takes the two endpoints' elevation state and returns the merged mode, height, and station plus flags for "heights differ" and "stations differ". The
      precedence order is now stated once, in a small rank helper, instead of being implicit in the ordering of the if/else arms.
    • SetTrkElevModes() now gathers the two endpoints, calls MergeJoinElev(), and emits MSG_JOIN_DIFFER_ELEV and/or the new MSG_JOIN_DIFFER_STATION as flagged.
    • New unit test app/bin/unittest/elevjointest.c (ctest target ElevJoinTest, 13 cases): covers the full precedence matrix in both join orders, the Defined/Defined
      averaging and height-mismatch flag, the Station/Station name-mismatch flag, tie-breaking, and the regression case for the latent bug above. The test links the
      extracted logic directly with no stubs.

    Verification: full build clean, AStyle clean, all unit tests pass. The GUI demo regression suite was run; its pre-existing unrelated failures are unchanged.

     

Anonymous
Anonymous

Add attachments
Cancel