From 6fda82476dbfa1e6f8f82f62189d4a1323a60852 Mon Sep 17 00:00:00 2001 From: Kris Austin Date: Thu, 9 Jul 2026 02:47:57 -0500 Subject: [PATCH 1/3] fix: out-of-bounds read computing tool-ordering max layer height (#14665) * fix: out-of-bounds read computing tool-ordering max layer height calc_max_layer_height() loops over the extruder count (nozzle_diameter) but indexes max_layer_height with the same counter, reading past the end when that array is shorter. Silent on release builds, aborts under a bounds-checked STL (_GLIBCXX_ASSERTIONS). Read via get_at(), which falls back to the first entry when the index is out of range, as Slicing.cpp already does for this option. Add a fff_print regression test slicing a two-extruder printer with a single-entry max_layer_height. * docs: clarify how max_layer_height ends up short in the regression test Normalization sizes it to the filament count under single_extruder_multi_material, not "a mismatch a profile can ship" as the earlier comment guessed. --- src/libslic3r/GCode/ToolOrdering.cpp | 3 ++- tests/fff_print/test_multifilament.cpp | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index ee03d6e959..65c887f343 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -138,7 +138,8 @@ static double calc_max_layer_height(const PrintConfig &config, double max_object { double max_layer_height = std::numeric_limits::max(); for (size_t i = 0; i < config.nozzle_diameter.values.size(); ++ i) { - double mlh = config.max_layer_height.values[i]; + // max_layer_height may be shorter than the extruder count; get_at() clamps. + double mlh = config.max_layer_height.get_at(i); if (mlh == 0.) mlh = 0.75 * config.nozzle_diameter.values[i]; max_layer_height = std::min(max_layer_height, mlh); diff --git a/tests/fff_print/test_multifilament.cpp b/tests/fff_print/test_multifilament.cpp index 12c1cae766..f33a8e1e61 100644 --- a/tests/fff_print/test_multifilament.cpp +++ b/tests/fff_print/test_multifilament.cpp @@ -85,3 +85,22 @@ TEST_CASE("Per-object wall filament override is honored", "[MultiFilament]") CHECK(tools_for_role(gcode, "perimeter") == std::set{ 0, 1 }); CHECK(tools_for_role(gcode, "infill") == std::set{ 0 }); // infill not overridden: stays on F1 } + +// max_layer_height can be shorter than the extruder count (normalization sizes it to the +// filament count under single_extruder_multi_material). calc_max_layer_height() in ToolOrdering +// indexed it per-nozzle and read past the end. Shortened directly here to isolate that read; +// the other per-extruder keys stay extruder-length so slicing reaches the code under test. +TEST_CASE("Multi-extruder slice stays in bounds with a short max_layer_height", "[MultiFilament]") +{ + DynamicPrintConfig config = multifilament_config(2); + config.set_deserialize_strict({ + { "nozzle_diameter", "0.4,0.4" }, + { "printer_extruder_id", "1,2" }, + { "printer_extruder_variant", "Direct Drive Standard,Direct Drive Standard" }, + { "extruder_printable_height", "0,0" }, + { "max_layer_height", "0.3" }, // deliberately one entry short + }); + Print print; + init_and_process_print({ cube(20) }, print, config); + REQUIRE_FALSE(print.objects().front()->layers().empty()); +} From 05ed2e4dfabaf2494b86a3a68814c2df88e07e09 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Thu, 9 Jul 2026 21:57:59 +0800 Subject: [PATCH 2/3] Fixed a rare crash on app startup caused by ENOTSUP (errno 45) on Mac (#14686) fix: prevent startup crash when preset-sync directory scan hits a transient FS error On startup the user-preset sync thread scans the preset folder for orphaned .info files (scan_orphaned_info_files). It iterated the directory with a throwing boost::filesystem::directory_iterator while running on a background thread that has no exception guard. On macOS, readdir() can intermittently fail with ENOTSUP (errno 45); boost then throws filesystem_error, which -- uncaught on the sync thread -- calls std::terminate and aborts the whole application on startup. - Iterate with the error_code-based directory_iterator so a transient read failure is logged and skipped instead of thrown. The orphan scan is best-effort and re-runs on the next sync, so skipping a cycle is harmless. This mirrors the existing pattern in has_json_presets() and the plugin scan. - Wrap the entire sync-thread body in try/catch as defense-in-depth, so no future uncaught exception on that otherwise-unguarded thread can abort the app. --- src/slic3r/GUI/GUI_App.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 121acdb947..feaddf07ab 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -6965,6 +6965,10 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg) // finishFn tears down the progress dialog (and clears the re-entrancy guard), so it // must run on every exit path — otherwise an early bail-out would leak the modal // dialog and leave the guard stuck, blocking all later manual syncs. + // Guard the whole thread body: an uncaught exception here (e.g. a transient + // boost::filesystem error while scanning the preset folder) would otherwise + // propagate out of the thread and terminate the entire application. + try { if (!m_agent) { finishFn(false); return; } // One-time scan for orphaned .info files left over from offline deletions; queues HTTP DELETEs. @@ -7206,6 +7210,11 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg) boost::this_thread::sleep_for(boost::chrono::milliseconds(500)); } } + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "user preset sync thread terminated by exception: " << e.what(); + } catch (...) { + BOOST_LOG_TRIVIAL(error) << "user preset sync thread terminated by unknown exception"; + } }); } @@ -8526,8 +8535,13 @@ void GUI_App::scan_orphaned_info_files() if (!fs::exists(type_dir)) continue; - // Iterate through all .info files - for (auto& entry : boost::filesystem::directory_iterator(type_dir)) { + // Iterate through all .info files. Use the error_code-based iterator so a transient + // directory-read failure (e.g. macOS readdir returning ENOTSUP) is logged and skipped + // instead of throwing an uncaught exception that would terminate the app from the + // background sync thread this runs on. + boost::system::error_code ec; + for (boost::filesystem::directory_iterator it(type_dir, ec), end; !ec && it != end; it.increment(ec)) { + const auto& entry = *it; if (entry.path().extension() != ".info") continue; @@ -8546,6 +8560,8 @@ void GUI_App::scan_orphaned_info_files() } } } + if (ec) + BOOST_LOG_TRIVIAL(warning) << "scan_orphaned_info_files: failed to scan " << type_dir.string() << ": " << ec.message(); } } From 79d51b31b5bba39af29b680c098e99a6a13a2bf7 Mon Sep 17 00:00:00 2001 From: Robert J Audas Date: Thu, 9 Jul 2026 08:48:17 -0600 Subject: [PATCH 3/3] Disable prime tower width for rib walls (#14625) * Disable prime tower width for rib walls Only enable the prime tower Width field when the selected wall type uses the standard tower shape. Rib towers use the rib-specific settings instead, so leaving Width editable suggests it changes rib geometry when it does not. Fixes #14537 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Simplify prime tower width toggle Use the existing rib-wall helper when disabling the prime tower width control for rib wall towers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/slic3r/GUI/ConfigManipulation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 00e2cebc44..edae8c6c79 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -946,7 +946,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in toggle_line("wipe_tower_extra_rib_length", have_rib_wall); toggle_line("wipe_tower_rib_width", have_rib_wall); toggle_line("wipe_tower_fillet_wall", have_rib_wall); - toggle_field("prime_tower_width", have_prime_tower && (supports_wipe_tower_2 || have_rib_wall)); + toggle_field("prime_tower_width", have_prime_tower && !have_rib_wall); toggle_line("single_extruder_multi_material_priming", !bSEMM && have_prime_tower && supports_wipe_tower_2);