eigen_memset_helper should check if custom scalar needs initialized
Summary
Eigen 5.0.1 setZero() uses a memset-based zeroing path for trivially copyable scalars and ignores NumTraits<Scalar>::RequireInitialization. This bypasses scalar constructors and breaks types that require non-zero initialization (e.g., Stan’s var, or the custom scalar below), causing null pointer state and downstream segfaults. This is a regression vs Eigen 3.4.0 where setZero() used setConstant(Scalar(0)).
I think the answer is to just have eigen_memset_helper check if the expressions scalar type requires initialization.
template <typename Xpr>
struct eigen_memset_helper {
static constexpr bool value =
std::is_trivially_copyable<typename Xpr::Scalar>::value && eigen_fill_helper<Xpr>::value && (NumTraits<Xpr>::RequireInitialization == 0);
};Changing that line makes the following test pass which currently fails on master.
EIGEN_DECLARE_TEST(zero_init) {
using PlainType = Matrix<AnnoyingScalar, Dynamic, Dynamic, ColMajor>;
PlainType m(4, 4);
m.setZero();
// This segfaults on master
VERIFY_IS_CWISE_EQUAL(m, PlainType::Zero(4, 4));
}Environment
Operating System : Linux Architecture : x64 Eigen Version : 5.0.1 Compiler Version : g++14.2.0 Compile Flags : -O2 -std=c++17
Minimal Example
https://godbolt.org/z/44qvTh1v5
#include <Eigen/Dense>
#include <iostream>
#include <type_traits>
struct NeedsInit {
int* p;
double v;
static int sentinel;
NeedsInit(double x = 0.0) : p(&sentinel), v(x) {}
double get() const { return *p + v; }
};
int NeedsInit::sentinel = 1;
namespace Eigen {
template <>
struct NumTraits<NeedsInit> : GenericNumTraits<NeedsInit> {
typedef NeedsInit Real;
typedef NeedsInit NonInteger;
typedef NeedsInit Nested;
enum {
IsComplex = 0,
IsInteger = 0,
IsSigned = 1,
RequireInitialization = 1,
ReadCost = 1,
AddCost = 1,
MulCost = 1
};
static inline Real dummy_precision() { return Real(0); }
static inline Real epsilon() { return Real(0); }
static inline Real highest() { return Real(0); }
static inline Real lowest() { return Real(0); }
};
}
int main() {
static_assert(std::is_trivially_copyable<NeedsInit>::value,
"NeedsInit should be trivially copyable");
Eigen::Matrix<NeedsInit, -1, -1> m(4, 4);
std::cout << "Init\n";
m.setZero(); // Eigen 5.0.1 uses memset, zeroing p
std::cout << "Zeroed\n";
if (m(0, 0).p == nullptr) {
std::cout << "FAIL: pointer nulled by setZero\n";
return 1;
}
std::cout << m(0, 0).get() << "\n";
return 0;
}Steps to reproduce the issue
See godbolt above
What is the current bug behavior?
When Eigen attempts to use the matrix that has not been set to zero then a segfault occurs.
What is the expected correct behavior?
No segfault
Anything else that might help
I tried finding a simpler solution to this, but without a patch for this I don't think Stan can update to Eigen 5.0.1 :(