mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-15 23:12:08 +00:00
GCode::extrude_support declared its per-path speed helper as a function-local static lambda that captures `this` by reference. The closure is built once, on the first extrude_support call, and reused for the rest of the process, so a second G-code export in the same process runs the helper against a `this` from the first export's stack frame, which has already returned. The stale `this` flows through NOZZLE_CONFIG(...) -> cur_extruder_index() -> GCodeWriter::filament(), reading a garbage current-extruder id and indexing with it. It is silent whenever the reused stack still holds a usable pointer, and an order-dependent SIGSEGV otherwise; AddressSanitizer reports it as a stack-use-after-return in GCodeWriter::filament(). It is the only static capturing lambda in libslic3r. Drop static so the closure is rebuilt each call against the live frame. Add an fff_print regression test that slices a support object twice in one process; it fails without the fix (stack-use-after-return under ASan) and passes with it.
107 lines
4.7 KiB
C++
107 lines
4.7 KiB
C++
#include <catch2/catch_all.hpp>
|
|
|
|
#include "libslic3r/GCodeReader.hpp"
|
|
#include "libslic3r/Layer.hpp"
|
|
|
|
#include "test_helpers.hpp" // get access to init_print, etc
|
|
|
|
using namespace Slic3r::Test;
|
|
using namespace Slic3r;
|
|
|
|
TEST_CASE("Three raft layers are created", "[SupportMaterial]")
|
|
{
|
|
Slic3r::Print print;
|
|
Slic3r::Test::init_and_process_print({ cube(20) }, print, {
|
|
{ "enable_support", 1 },
|
|
{ "raft_layers", 3 }
|
|
});
|
|
REQUIRE(print.objects().front()->support_layers().size() == 3);
|
|
}
|
|
|
|
TEST_CASE("Enforced support layers are generated", "[SupportMaterial]")
|
|
{
|
|
// enforce_support_layers forces support on the first N layers even with support off.
|
|
Slic3r::Print baseline;
|
|
Slic3r::Test::init_and_process_print({ TestMesh::overhang }, baseline, {
|
|
{ "enable_support", 0 },
|
|
{ "enforce_support_layers", 0 }
|
|
});
|
|
REQUIRE(baseline.objects().front()->support_layers().empty());
|
|
|
|
Slic3r::Print enforced;
|
|
Slic3r::Test::init_and_process_print({ TestMesh::overhang }, enforced, {
|
|
{ "enable_support", 0 },
|
|
{ "enforce_support_layers", 100 }
|
|
});
|
|
REQUIRE(enforced.objects().front()->support_layers().size() > 0);
|
|
}
|
|
|
|
SCENARIO("Support layer Z honors contact distance", "[SupportMaterial]")
|
|
{
|
|
// Box h = 20mm, hole bottom at 5mm, hole height 10mm (top edge at 15mm).
|
|
TriangleMesh mesh = Slic3r::Test::mesh(Slic3r::Test::TestMesh::cube_with_hole);
|
|
mesh.rotate_x(float(M_PI / 2));
|
|
|
|
auto check = [](Slic3r::Print &print, bool &first_support_layer_height_ok, bool &layer_height_minimum_ok, bool &layer_height_maximum_ok)
|
|
{
|
|
ConstSupportLayerPtrsAdaptor support_layers = print.objects().front()->support_layers();
|
|
|
|
first_support_layer_height_ok = support_layers.front()->print_z == print.config().initial_layer_print_height.value;
|
|
|
|
layer_height_minimum_ok = true;
|
|
layer_height_maximum_ok = true;
|
|
double min_layer_height = print.config().min_layer_height.values.front();
|
|
double max_layer_height = print.config().nozzle_diameter.values.front();
|
|
if (print.config().max_layer_height.values.front() > EPSILON)
|
|
max_layer_height = std::min(max_layer_height, print.config().max_layer_height.values.front());
|
|
for (size_t i = 1; i < support_layers.size(); ++ i) {
|
|
if (support_layers[i]->print_z - support_layers[i - 1]->print_z < min_layer_height - EPSILON)
|
|
layer_height_minimum_ok = false;
|
|
if (support_layers[i]->print_z - support_layers[i - 1]->print_z > max_layer_height + EPSILON)
|
|
layer_height_maximum_ok = false;
|
|
}
|
|
};
|
|
|
|
GIVEN("A print object having one modelObject") {
|
|
WHEN("Layer height = 0.2 and first layer height = 0.4") {
|
|
Slic3r::Print print;
|
|
Slic3r::Test::init_and_process_print({ mesh }, print, {
|
|
{ "enable_support", 1 },
|
|
{ "layer_height", 0.2 },
|
|
{ "initial_layer_print_height", 0.4 },
|
|
{ "dont_support_bridges", false },
|
|
});
|
|
bool first_layer_ok, layer_min_ok, layer_max_ok;
|
|
check(print, first_layer_ok, layer_min_ok, layer_max_ok);
|
|
THEN("First layer height is honored") { REQUIRE(first_layer_ok == true); }
|
|
THEN("No null or negative support layers") { REQUIRE(layer_min_ok == true); }
|
|
THEN("No layers thicker than nozzle diameter") { REQUIRE(layer_max_ok == true); }
|
|
}
|
|
WHEN("Layer height = 0.2 and first layer height = 0.3") {
|
|
Slic3r::Print print;
|
|
Slic3r::Test::init_and_process_print({ mesh }, print, {
|
|
{ "enable_support", 1 },
|
|
{ "layer_height", 0.2 },
|
|
{ "initial_layer_print_height", 0.3 },
|
|
{ "dont_support_bridges", false },
|
|
});
|
|
bool first_layer_ok, layer_min_ok, layer_max_ok;
|
|
check(print, first_layer_ok, layer_min_ok, layer_max_ok);
|
|
THEN("First layer height is honored") { REQUIRE(first_layer_ok == true); }
|
|
THEN("No null or negative support layers") { REQUIRE(layer_min_ok == true); }
|
|
THEN("No layers thicker than nozzle diameter") { REQUIRE(layer_max_ok == true); }
|
|
}
|
|
}
|
|
}
|
|
|
|
// extrude_support once held a `static` lambda capturing `this`, so a second export in the
|
|
// same process dereferenced a returned stack frame (ASan: stack-use-after-return).
|
|
TEST_CASE("Support G-code emission survives a second slice in the same process", "[SupportMaterial][Regression]")
|
|
{
|
|
const std::string first = slice({ TestMesh::overhang }, { { "enable_support", 1 } });
|
|
REQUIRE(! layers_with_role(first, "support").empty());
|
|
|
|
const std::string second = slice({ TestMesh::overhang }, { { "enable_support", 1 } });
|
|
REQUIRE(! layers_with_role(second, "support").empty());
|
|
}
|