SparseCore: Fix random insertion when StorageIndex is wider than Index

libeigen/eigen!3379

Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
diff --git a/Eigen/src/SparseCore/SparseMatrix.h b/Eigen/src/SparseCore/SparseMatrix.h
index 1a9ac8d..4a90737 100644
--- a/Eigen/src/SparseCore/SparseMatrix.h
+++ b/Eigen/src/SparseCore/SparseMatrix.h
@@ -1725,7 +1725,11 @@
     std::iota(m_outerIndex, m_outerIndex + m_outerSize + 1, StorageIndex(0));
   } else {
     // check for integer overflow: if maxReserveSize == 0, insertion is not possible
-    Index maxReserveSize = static_cast<Index>(NumTraits<StorageIndex>::highest()) - m_data.allocatedSize();
+    // StorageIndex can be wider than Index (long long with a 32-bit Index), so clamp its maximum to Index's.
+    using WideIndex = typename std::common_type<Index, StorageIndex>::type;
+    const Index maxSize =
+        static_cast<Index>(numext::mini<WideIndex>(NumTraits<StorageIndex>::highest(), NumTraits<Index>::highest()));
+    Index maxReserveSize = maxSize - m_data.allocatedSize();
     eigen_assert(maxReserveSize > 0);
     if (m_outerSize <= maxReserveSize) {
       // allocate space for one additional element per vector
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index f17cdef..34a088a 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -502,6 +502,7 @@
 ei_add_test(sparse_scatter)
 ei_add_test(sparse_block)
 ei_add_test(sparse_vector)
+ei_add_test(sparse_int_index)
 ei_add_test(sparse_product)
 ei_add_test(sparse_threaded_product "-pthread" "${CMAKE_THREAD_LIBS_INIT}")
 ei_add_test(sparse_ref)
diff --git a/test/sparse_int_index.cpp b/test/sparse_int_index.cpp
new file mode 100644
index 0000000..4f670b4
--- /dev/null
+++ b/test/sparse_int_index.cpp
@@ -0,0 +1,58 @@
+// SPDX-FileCopyrightText: The Eigen Authors
+// SPDX-License-Identifier: MPL-2.0
+
+// Index is int here, as on 32-bit targets, so that a StorageIndex can be wider than Index.
+#ifdef EIGEN_DEFAULT_DENSE_INDEX_TYPE
+#undef EIGEN_DEFAULT_DENSE_INDEX_TYPE
+#endif
+#define EIGEN_DEFAULT_DENSE_INDEX_TYPE int
+#include "main.h"
+
+#include <Eigen/SparseCore>
+
+// Random insertion past the reserved room must grow the storage. Its growth limit is NumTraits<StorageIndex>::highest()
+// clamped to Index; a StorageIndex wider than Index used to wrap the limit to -1, so the insertion wrote past the end
+// of the storage instead.
+template <typename StorageIndex, int Options>
+void sparse_random_insertion_growth(bool reserveFirst) {
+  using SpMat = SparseMatrix<double, Options, StorageIndex>;
+  using DenseMat = Matrix<double, Dynamic, Dynamic>;
+  const Index rows = internal::random<Index>(2, 40);
+  const Index cols = internal::random<Index>(2, 40);
+
+  // Every coefficient, in random order, so that most insertions find no free slot in their inner vector.
+  std::vector<Index> order(rows * cols);
+  for (Index k = 0; k < rows * cols; ++k) order[k] = k;
+  for (Index k = rows * cols - 1; k > 0; --k) std::swap(order[k], order[internal::random<Index>(0, k)]);
+
+  SpMat A(rows, cols);
+  if (reserveFirst) A.reserve(VectorXi::Constant(A.outerSize(), 1));
+  DenseMat ref = DenseMat::Zero(rows, cols);
+  for (Index k : order) {
+    const Index i = k % rows, j = k / rows;
+    A.insert(i, j) = ref(i, j) = double(k + 1);
+  }
+  VERIFY_IS_EQUAL(A.nonZeros(), rows * cols);
+  VERIFY_IS_EQUAL(A.innerIndicesAreSorted(), A.outerSize());
+  VERIFY_IS_EQUAL(DenseMat(A), ref);
+
+  A.makeCompressed();
+  VERIFY(A.isCompressed());
+  VERIFY_IS_EQUAL(DenseMat(A), ref);
+}
+
+template <typename StorageIndex>
+void sparse_random_insertion_growth() {
+  for (bool reserveFirst : {false, true}) {
+    sparse_random_insertion_growth<StorageIndex, ColMajor>(reserveFirst);
+    sparse_random_insertion_growth<StorageIndex, RowMajor>(reserveFirst);
+  }
+}
+
+EIGEN_DECLARE_TEST(sparse_int_index) {
+  STATIC_CHECK(sizeof(Index) == sizeof(int));
+  for (int i = 0; i < g_repeat; ++i) {
+    CALL_SUBTEST_1(sparse_random_insertion_growth<long long>());
+    CALL_SUBTEST_2(sparse_random_insertion_growth<int>());
+  }
+}