Skip to content

Fix crash in normalize_fdm when nozzle_diameter is absent - #761

Open
Megarushing wants to merge 1 commit into
Snapmaker:mainfrom
Megarushing:fix/normalize-fdm-null-nozzle-diameter
Open

Fix crash in normalize_fdm when nozzle_diameter is absent#761
Megarushing wants to merge 1 commit into
Snapmaker:mainfrom
Megarushing:fix/normalize-fdm-null-nozzle-diameter

Conversation

@Megarushing

Copy link
Copy Markdown

Description

Fixes #689.

DynamicPrintConfig::normalize_fdm() crashes with a null dereference whenever it
normalizes a configuration that contains print settings but no printer settings.

if (this->has("wipe_tower_filament")) {
    // If invalid, replace with 0.
    int extruder      = this->opt<ConfigOptionInt>("wipe_tower_filament")->value;
    int num_extruders = this->opt<ConfigOptionFloats>("nozzle_diameter")->size();
    ...
}

wipe_tower_filament is a print (process) option, while nozzle_diameter is a
printer option. A process preset therefore satisfies the has() guard but does not
contain nozzle_diameter. ConfigBase::opt<T>() is
dynamic_cast<const T*>(this->option(opt_key)), so it returns nullptr in that case
and ->size() dereferences it.

The most direct way to reach this is the CLI, where a process preset is loaded on its
own:

snapmaker-orca --load-settings "machine.json;process.json" \
               --load-filaments "filament.json" \
               --slice 0 --outputdir out model.stl

Any U1 process preset reproduces it. Loading stops immediately after
load setting file .../process.json, with rule 1, with no further output.
On Linux this is a SIGSEGV (exit -11); #689 reports the same failure on Windows as
0xC0000005. A debugger places the crash in
DynamicPrintConfig::normalize_fdm(int), called from the load_config_file lambda in
CLI::run — that is, during config normalization right after
DynamicPrintConfig::load_from_json(), before compatibility checking, filament
loading, arranging or slicing.

Single-variable confirmation: removing only the wipe_tower_filament key from the
process JSON, changing nothing else, turns the identical command into a complete
slice that writes plate_1.gcode and exits 0. Restoring the key restores the crash.

Upstream OrcaSlicer no longer contains this block in normalize_fdm() at all, so the
crash is specific to this fork.

The change

Guard the nozzle_diameter lookup instead of dereferencing it unconditionally, and
keep the existing clamping behaviour when the option is present:

if (const ConfigOptionFloats *nozzle_diameter = this->opt<ConfigOptionFloats>("nozzle_diameter")) {
    int extruder      = this->opt<ConfigOptionInt>("wipe_tower_filament")->value;
    int num_extruders = (int) nozzle_diameter->size();
    if (extruder < 0 || extruder > num_extruders)
        this->option("wipe_tower_filament")->setInt(0);
}

This mirrors how upstream guards the same option elsewhere
(auto* nd = cfg.opt<ConfigOptionFloats>("nozzle_diameter"); if (nd && ...)).

Behaviour is unchanged for every configuration that includes printer settings, which
covers the GUI and any project-file path: the option is present, the branch is taken,
and the clamp runs exactly as before. Only the previously crashing case changes, and
there the clamp is meaningless because the extruder count is unknown.

Deleting the block entirely, as upstream did, is also defensible. I kept the minimal
guard so the existing intent is preserved.

Screenshots/Recordings/Graphs

Not applicable — no UI change.

Tests

  • Reproduced the crash on Linux x86_64 against the released 2.3.5 AppImage
    (internal engine 01.10.01.50) with manifest-correct flattened U1 machine, process
    and filament JSON: deterministic SIGSEGV, return -11, five runs out of five.
  • Confirmed the faulting frame with GDB against the unstripped binary:
    DynamicPrintConfig::normalize_fdm(int)CLI::run's load_config_file lambda.
  • Single-variable proof as described above: with wipe_tower_filament removed the same
    command slices 98 layers, writes plate_1.gcode and a successful result.json, and
    exits 0.
  • Verified opt<T>() returns nullptr rather than throwing for an absent key, so the
    guard is the correct shape, and checked the guarded control flow in isolation.

I was not able to build the full application in this environment, so the patched source
has not been compiled. The change is small and local, but please run it through CI
before merging.

normalize_fdm() dereferences the result of opt<ConfigOptionFloats>("nozzle_diameter")
unconditionally while normalizing wipe_tower_filament. nozzle_diameter is a printer
option, so it is missing whenever the config being normalized holds print settings
only, and opt<T>() then returns nullptr. Calling ->size() on it crashes.

Guard the lookup and keep the existing clamping when the option is present.
@adenta

adenta commented Aug 31, 2026

Copy link
Copy Markdown

Independent integration check on current upstream f34a62d: I built an arm64 Release CLI with the exact #761 commit (079f24c), plus the complementary existing fixes from #560 and #562 and the vendor resolver in #803. Loading the Snapmaker U1 machine, 0.20 Standard process, and PLA filament no longer crashed; the 20 mm cube CLI slice completed with exit 0 in 2.07 s. A representative BBL regression slice also completed with exit 0 in 2.07 s.

This is evidence for the combined headless path and compatibility with #803, not a claim that the complete result is attributable to #761 alone.

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.

CLI --slice crashes (0xC0000005 access violation) loading any U1 "process" settings file via --load-settings

2 participants