Skip to content

fix(xodr): never dereference a stale road link when writing (#311) - #330

Merged
JArmandoAnaya merged 1 commit into
mainfrom
fix/311-dangling-arm-segfault
Jul 20, 2026
Merged

fix(xodr): never dereference a stale road link when writing (#311)#330
JArmandoAnaya merged 1 commit into
mainfrom
fix/311-dangling-arm-segfault

Conversation

@JArmandoAnaya

Copy link
Copy Markdown
Contributor

Fixes the pre-existing write_xodr segfault filed as #311 — a P4 blocker: it gates p4-s4 (#319), whose junction membership editing multiplies the code paths that can leave a stale reference behind.

Root cause

core/src/xodr/writer.cpp dereferenced network.road(*road_target)->odr_id in write_link_element with no null check.

RoadNetwork::erase_road deliberately drops only the junction connections that name the erased road (core/src/road/network.cpp:110-115) — the surviving connecting roads keep a predecessor/successor holding the now-stale RoadId. That is exactly the state #311 reproduces by erasing a junction arm. Confirmed under lldb: EXC_BAD_ACCESS at +0x18, inside std::string.

erase_junction leaves the symmetric hole on the JunctionId arm of the same variant.

Two things worth recording, both of which the issue anticipated:

  • It is not the junction surface/boundary path. Those were already guarded — arm_face, link_arm and arm_face_vertices all null-check. The crash was plain road-link serialization, merely reached via the dangling arm.
  • The editor cannot produce this state. edit::delete_road's closure strips connections and arms and deletes orphaned connecting roads. So this is the defence-in-depth guard the issue asked for — and it also covers a hand-edited or foreign .xodr, which reaches the writer with no closure at all.

Fix

  • link_target_odr_id resolves a RoadLink to its @elementId or nullptr, covering both arms of the target variant and never dereferencing.
  • write_road resolves both ends before opening <link>, so a stale link is dropped and a road whose links are all stale emits no empty <link/>.
  • validate_network warns per omitted link, citing asam.net:xodr:1.4.0:ids.only_ref_defined_ids, so the drop is never silent — mirroring the existing rm:arms rule that stale references are not serialized.

Tests

Five new tests in core/tests/test_junction_export.cpp:

Test Covers
WriteXodrSurvivesAnErasedArmRoad the #311 repro — crashed before, passes now
StaleRoadLinksAreOmittedNotWritten erased road is unreferenced; no empty <link>
ValidateNetworkWarnsAboutTheOmittedLink the warning and its rule id
WriteWithADanglingArmIsDeterministic determinism holds in the degraded state
StaleJunctionLinksAreOmittedToo the erase_junction variant

Verification

  • roadmaker_core_tests670/670 pass.
  • CI-preset build (-Werror) clean; no new warnings.
  • Whole-tree clang-format --dry-run --Werror (CI's exact command) clean.

No public kernel API change, so no bindings or example update is required.

Closes #311

`write_link_element` dereferenced `network.road(*road_target)->odr_id`
with no null check, so serializing a road whose predecessor/successor
target had been erased segfaulted.

`RoadNetwork::erase_road` deliberately drops only the junction
connections that name the erased road (road/network.hpp) — surviving
connecting roads keep a `predecessor`/`successor` holding the now-stale
`RoadId`, which is exactly the state issue #311 reproduces by erasing a
junction arm. `erase_junction` leaves the symmetric hole on the
`JunctionId` arm of the variant. The editor's `edit::delete_road`
closure repairs both in practice, so this is the defence-in-depth guard
the export path was missing; a hand-edited or foreign .xodr reaches it
without any closure at all.

The junction surface and boundary paths were already guarded
(`arm_face`, `link_arm`, `arm_face_vertices` all null-check), so the
crash was plain road-link serialization reached via the dangling arm.

- `link_target_odr_id` resolves a link to its `@elementId` or nullptr,
  covering both arms of the target variant, never dereferencing.
- `write_road` resolves both ends before opening `<link>`, so a stale
  link is dropped and an all-stale road emits no empty `<link/>`.
- `validate_network` warns per omitted link citing
  `asam.net:xodr:1.4.0:ids.only_ref_defined_ids`, so the drop is never
  silent — mirroring the rm:arms rule that stale references are not
  written.

Tests cover the crash repro, the omitted-link output shape, the
validator warning, write determinism with a dangling arm, and the
`erase_junction` variant.

Closes #311
@JArmandoAnaya
JArmandoAnaya merged commit ce06d84 into main Jul 20, 2026
14 checks passed
@JArmandoAnaya
JArmandoAnaya deleted the fix/311-dangling-arm-segfault branch July 20, 2026 23:44
@JArmandoAnaya JArmandoAnaya mentioned this pull request Jul 20, 2026
17 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

write_xodr segfaults on a junction with a dangling arm road

1 participant