eos: guard null thermo in apply_primitive_limiter_#186
Conversation
apply_conserved_limiter_ already checks options->thermo() before reading vapor/cloud species counts; apply_primitive_limiter_ did not, so any species-free EOS (e.g. ideal-gas) hit a null-thermo dereference every time the primitive limiter ran. In release builds this doesn't throw -- it silently corrupts the tracer slice of prim with garbage, and once a column's density/pressure are driven low enough (e.g. a strong transient updraft), the corrupted state reaches the implicit vertical solver as a singular Jacobian, which then spins retrying without bound. Reproduced on a cubed-sphere Jupiter case: ideal-gas with limiter disabled loses all density/pressure flooring and crashes via a singular LU decomposition; enabling the limiter instead hits this null-thermo path and corrupts state (some columns pinned exactly at the floor, others with velocities on the order of 1e30+). An identical run using ideal-moist with trace (~1e-10) vapor species -- same forcing, same cold start, same perturbation -- has no such bug (thermo is non-null) and survives the same event cleanly, isolating the failure to this one line. Confirmed fixed by directly constructing a low-density/high-momentum cell (the exact failure shape) and calling U->W: before the fix this corrupts memory; after, density and pressure clamp to their floors as intended. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Fixes a null-dereference/UB in the EOS primitive limiter path when running species-free EOS configurations (e.g., ideal-gas) by guarding access to options->thermo() before clamping tracer-related primitive entries. This aligns apply_primitive_limiter_ with the existing safety behavior already present in apply_conserved_limiter_, preventing silent state corruption that can propagate into the implicit vertical solver.
Changes:
- Add a
options->thermo()null guard around vapor/cloud tracer clamping inEquationOfStateImpl::apply_primitive_limiter_. - Document why the guard is required (species-free EOS types have unset
thermo()and previously triggered UB).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Additional end-to-end confirmation on v2.7.5 (avoids an unrelated Before the fix (same case, After the fix, same forcing/IC/perturbation, same extreme event ( |
Summary
apply_conserved_limiter_already guardsoptions->thermo()before reading vapor/cloud species counts;apply_primitive_limiter_did not, so any species-free EOS (e.g.ideal-gas) dereferenced a nullthermo()every time the primitive limiter ran.prim. Once a column's density/pressure get low enough (e.g. a strong transient updraft), the corrupted state reaches the implicit vertical solver as a singular Jacobian, which then spins retrying without bound.How this was found
Reproduced on a cubed-sphere Jupiter case:
ideal-gaswithlimiter: falseloses all density/pressure flooring entirely and crashes via a singular LU decomposition in the vertical implicit solver.ideal-gaswithlimiter: trueinstead hits this null-thermo path and corrupts state (some columns pinned exactly at the density/pressure floor, others with velocities ~1e30+).ideal-moistwith trace (~1e-10) vapor species — same forcing, same cold start, same perturbation — has no such bug (thermo()is non-null there) and survives the same event cleanly. This isolated the failure to the one line fixed here.Confirmed the fix directly by constructing a low-density/high-momentum cell (the exact failure shape: tiny density, huge momentum, energy far too small for the resulting kinetic energy) and calling
U->Won anideal-gasblock withlimiter: true: before the fix this corrupts memory; after, density and pressure clamp cleanly to their floors as intended.Test plan
pre-commit runpasses on the changed filesnap_releasebuilds clean(rho=1e-10, mom=1e6, E=1e5)throughideal-gasEOScompute("U->W", ...)withlimiter: true— before: undefined behavior / memory corruption; after:rhoandpclamp to their configured floors, no crashideal-moistwith trace species (same driver/config otherwise), isolating the fix to this code path🤖 Generated with Claude Code