From b58dcf59780cc4b41905d76178e376278e873428 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Fri, 10 Jul 2026 01:55:00 +0800 Subject: [PATCH] test(libnest2d): fix use-after-free crash in NfpPlacer lifetime tests NfpPlacer stores std::reference_wrapper to the items it packs and re-reads them from finalAlign() in its destructor (via clearItems()). Two placer tests declared the placer before the items in the same scope, so the items were destroyed first and the destructor dereferenced dangling references. On macOS this is a deterministic SIGSEGV: libmalloc poisons the freed block on free, so the item's point vector reads back as ~null (deref at 0x8). On Linux/glibc the freed bytes usually survive, which is why it slipped through upstream CI (introduced by #14267). Declare the items before the placer so they outlive it, matching the pattern the sibling 'packs many items' and 'obstacle' tests already use. Test-only; the library lifetime contract (items must outlive the placer) is unchanged and honored in production via _Nester in Arrange.cpp. --- tests/libnest2d/test_nfp_placer.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/libnest2d/test_nfp_placer.cpp b/tests/libnest2d/test_nfp_placer.cpp index 8219dda71d..4446333f5d 100644 --- a/tests/libnest2d/test_nfp_placer.cpp +++ b/tests/libnest2d/test_nfp_placer.cpp @@ -56,8 +56,10 @@ struct NfpPlacerFixture { } // namespace TEST_CASE_METHOD(NfpPlacerFixture, "NfpPlacer places a single item inside the bin", "[Nesting][Placer]") { - NfpPlacer placer = placer_with(); + // The placer only keeps references to the items it packs and re-reads them + // from finalAlign() in its destructor, so the item must outlive the placer. RectangleItem item{100000000, 100000000}; + NfpPlacer placer = placer_with(); REQUIRE(place(placer, item)); REQUIRE(placer.getItems().size() == 1u); @@ -103,12 +105,15 @@ TEST_CASE_METHOD(NfpPlacerFixture, "NfpPlacer packs many items without overlap", } TEST_CASE_METHOD(NfpPlacerFixture, "NfpPlacer evaluates the rotation candidates", "[Nesting][Placer]") { + // The placer re-reads its packed items from finalAlign() in its destructor, + // so the items must outlive the placer — declare them first. + std::vector rects = { + {180000000, 40000000}, {180000000, 40000000}, {180000000, 40000000}}; + Cfg cfg; cfg.rotations = {0.0, Pi / 2.0}; // exercise the rotation search loop NfpPlacer placer = placer_with(cfg); - std::vector rects = { - {180000000, 40000000}, {180000000, 40000000}, {180000000, 40000000}}; place_all(placer, rects); require_disjoint_in_bin(rects); }