Skip to content

System::CoefficientBound calls Eigen maxCoeff() on empty arrays: bertini.System().to_classic_input() segfaults #395

Description

@ofloveandhate

Three lines

import bertini
bertini.System().to_classic_input()      # Segmentation fault (core dumped)

Also crashes with variables present, as long as there are no functions:

S = bertini.System(); vg = bertini.VariableGroup(); vg.append(Variable('x'))
S.add_variable_group(vg)
S.to_classic_input()                     # Segmentation fault

The trigger is zero functions. A system with functions but no variable group is fine —
it raises a clean RuntimeError: SLP compile: a function references the variable 'x', which is not in the system's variable ordering, which is the behaviour one wants.

Cause

System::CoefficientBound<NumT>() (core/src/system/system.cpp:944) ends with

bound = max(f_vals.array().abs().maxCoeff(),
            dh_dx.array().abs().maxCoeff(), bound);

With no functions, f_vals is an empty vector and dh_dx an empty matrix, and Eigen's
maxCoeff() on an empty array is undefined behaviour — it asserts in a debug build and
reads out of bounds in a release one.

DegreeBound(), immediately below it in the same file, already guards precisely this case:

auto degs = Degrees(Variables());
if (degs.empty())
    return 0;   // a system with no functions has no degree bound

CoefficientBound needs the same guard.

The Python path in is to_classic_input()EmitConfig (io/classic_writer.hpp), which
emits coefficientbound: and so calls CoefficientBound<complex_dbl>(). But the bug is in
CoefficientBound itself, so any other caller reaching it with a function-less system
crashes too.

Expected

A function-less system is a legitimate object — bertini.System() constructs one, and
num_functions(), num_variables() and degrees() all answer for it correctly (0, 0,
[]). Its coefficient bound should be 0, matching DegreeBound()'s convention, and no
call reachable from Python should ever be able to segfault.

Found via

A downstream records layer that writes systems as classic-input text; a parse that silently
produced an empty system (filed separately) then handed that empty system back to
to_classic_input(), which crashed the interpreter rather than reporting anything.

Version: 3.5.0.dev0 (develop @ 8f0cd0f), Linux, Python 3.14.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions