From 6a6ceb2612211321711984ba6e57e9131a7823c3 Mon Sep 17 00:00:00 2001 From: harrierpigeon Date: Thu, 24 Sep 2026 23:39:13 -0500 Subject: [PATCH] G-code: key the island tour cache on the island layout, never index past the islands The per-filament island tour was cached by island centroids only. A later layer with the same centroids but fewer islands (thin walls, negative volumes) reused the stale visit list, whose catch-all index pointed past the layer's islands, and extrude_perimeters read freed memory (three fuzz crashes, planar and belt). The per-instance island layout is part of the cache key and the use site never indexes past the islands. --- src/libslic3r/GCode.cpp | 21 +++++++++++++++------ src/libslic3r/GCode.hpp | 18 +++++++++++++++--- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index fae8860b7b..75b14e06bc 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6491,6 +6491,7 @@ LayerResult GCode::process_layer( std::vector &objects_by_extruder = objects_by_extruder_it->second; std::vector &instances = filament_plan.first; std::vector nodes; + std::vector> layout; // Per instance, see IslandOrderCacheEntry std::vector node_instances; auto quantize_to_mm = [](const Point &pt) -> Point { const coord_t grid = coord_t(scale_(1.)); @@ -6515,6 +6516,7 @@ LayerResult GCode::process_layer( const size_t instance_idx = instances.size(); instances.emplace_back(object_by_extruder, layer_id, *print_object, instance_id, print_object->instances()[instance_id].model_instance->get_labeled_id()); + layout.emplace_back(islands.size(), ! islands.empty() && ! islands.back().by_region.empty()); const Point &shift = print_object->instances()[instance_id].shift; const size_t first_node = nodes.size(); if (islands_chainable) @@ -6534,8 +6536,9 @@ LayerResult GCode::process_layer( // Reuse the cached tour while this filament's island layout is unchanged. auto &cache_entry = m_ordering_cache[filament_id]; - if (!(cache_entry.first == nodes)) { - cache_entry.first = nodes; + if (! (cache_entry.nodes == nodes && cache_entry.layout == layout)) { + cache_entry.nodes = nodes; + cache_entry.layout = layout; Points node_points; node_points.reserve(nodes.size()); for (const IslandOrderNode &node : nodes) @@ -6568,12 +6571,12 @@ LayerResult GCode::process_layer( // A visit without explicit islands already prints everything. continue; std::vector &islands = instances[i].object_by_extruder.islands; - if (!islands.back().by_region.empty()) + if (! islands.empty() && ! islands.back().by_region.empty()) last_visit.islands.emplace_back(islands.size() - 1); } - cache_entry.second = std::move(visits); + cache_entry.visits = std::move(visits); } - filament_plan.second = cache_entry.second; + filament_plan.second = cache_entry.visits; } } @@ -6896,7 +6899,13 @@ LayerResult GCode::process_layer( // in this instance's frame after set_origin() above). Empty islands are skipped; // the trailing catch-all island has no centroid to chain by and always goes last. std::vector &islands = instance_to_print.object_by_extruder.islands; - std::vector island_order = visit.islands; + std::vector island_order; + island_order.reserve(visit.islands.size()); + for (size_t idx : visit.islands) // Never index past the islands (see IslandOrderCacheEntry) + if (idx < islands.size()) + island_order.emplace_back(idx); + else + BOOST_LOG_TRIVIAL(error) << "island tour refers to island " << idx << " of " << islands.size() << ", skipped"; if (island_order.empty()) { island_order.reserve(islands.size()); if (layer_to_print.object_layer != nullptr && islands.size() == layer_to_print.object_layer->lslices.size() + 1) { diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index e62d9cee41..5d99c219c6 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -632,9 +632,21 @@ protected: }; // Cache the per-filament island tour to avoid recomputing while the layer's island layout is - // unchanged. Key: filament_id. Value: {nodes the tour was computed from, resulting visits}. - std::map, std::vector>> - m_ordering_cache; + // unchanged. Key: filament_id. Value: the nodes the tour was computed from, the per-instance + // island layout (count and whether the trailing catch-all island has anything to print), and + // the resulting visits. + // The layout is part of the key. Nodes only cover the chainable islands, so two + // layers with the same centroids but a different number of islands (thin walls, negative + // volumes come and go) matched the cache and the visit's catch-all index -- islands.size() - 1 + // of the OLD layer -- ran past the new layer's islands (found by fuzzing: segfault in + // extrude_perimeters on multi-part objects). + struct IslandOrderCacheEntry + { + std::vector nodes; + std::vector> layout; + std::vector visits; + }; + std::map m_ordering_cache; ExtrusionQualityEstimator m_extrusion_quality_estimator;