From 9c649b14cf99c926f9f78e862277cca38e01b6f4 Mon Sep 17 00:00:00 2001 From: Steve Bronder Date: Fri, 17 Oct 2025 13:14:43 -0400 Subject: [PATCH 1/2] update constraints to have checks for negative or zero sizes when not allowed --- src/stan/io/deserializer.hpp | 29 +++++++++++++++++++------- src/test/unit/io/deserializer_test.cpp | 18 ++++++++++++++++ 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/stan/io/deserializer.hpp b/src/stan/io/deserializer.hpp index 7cc805ebc5..79ac1201db 100644 --- a/src/stan/io/deserializer.hpp +++ b/src/stan/io/deserializer.hpp @@ -180,6 +180,7 @@ class deserializer { return map_vector_t(nullptr, m); } else { check_r_capacity(m); + stan::math::check_nonnegative("read", "size", m); return map_vector_t(&scalar_ptr_increment(m), m); } } @@ -195,6 +196,7 @@ class deserializer { if (unlikely(m == 0)) { return Ret(map_vector_t(nullptr, m)); } else { + stan::math::check_nonnegative("read", "size", m); check_r_capacity(2 * m); Ret ret(m); for (Eigen::Index i = 0; i < m; ++i) { @@ -217,6 +219,7 @@ class deserializer { if (unlikely(m == 0)) { return map_row_vector_t(nullptr, m); } else { + stan::math::check_nonnegative("read", "size", m); check_r_capacity(m); return map_row_vector_t(&scalar_ptr_increment(m), m); } @@ -233,6 +236,7 @@ class deserializer { if (unlikely(m == 0)) { return Ret(map_row_vector_t(nullptr, m)); } else { + stan::math::check_nonnegative("read", "size", m); check_r_capacity(2 * m); Ret ret(m); for (Eigen::Index i = 0; i < m; ++i) { @@ -256,6 +260,8 @@ class deserializer { if (rows == 0 || cols == 0) { return map_matrix_t(nullptr, rows, cols); } else { + stan::math::check_nonnegative("read", "rows", rows); + stan::math::check_nonnegative("read", "cols", cols); check_r_capacity(rows * cols); return map_matrix_t(&scalar_ptr_increment(rows * cols), rows, cols); } @@ -273,6 +279,8 @@ class deserializer { if (rows == 0 || cols == 0) { return Ret(map_matrix_t(nullptr, rows, cols)); } else { + stan::math::check_nonnegative("read", "rows", rows); + stan::math::check_nonnegative("read", "cols", cols); check_r_capacity(2 * rows * cols); Ret ret(rows, cols); for (Eigen::Index i = 0; i < rows * cols; ++i) { @@ -798,9 +806,11 @@ class deserializer { require_matrix_t* = nullptr> inline auto read_constrain_cholesky_factor_cov(LP& lp, Eigen::Index M, Eigen::Index N) { + stan::math::check_greater_or_equal("read_constrain_cholesky_factor_cov", + "M", M, N); + const Eigen::Index size = (M == 0 || N == 0) ? 0 : (N * (N + 1)) / 2 + (M - N) * N; return stan::math::cholesky_factor_constrain( - this->read>((N * (N + 1)) / 2 - + (M - N) * N), + this->read>(size), M, N, lp); } @@ -857,9 +867,10 @@ class deserializer { template * = nullptr> inline auto read_constrain_cholesky_factor_corr(LP& lp, Eigen::Index K) { + // If K is 0, we want to read 0 elements to form a 0x0 matrix + const Eigen::Index size = (K == 0) ? 0 : (K * (K - 1)) / 2; return stan::math::cholesky_corr_constrain( - this->read>((K * (K - 1)) / 2), K, - lp); + this->read>(size), K, lp); } /** @@ -914,9 +925,9 @@ class deserializer { template * = nullptr> inline auto read_constrain_cov_matrix(LP& lp, Eigen::Index k) { + const Eigen::Index size = (k == 0) ? 0 : (k * (k - 1)) / 2; return stan::math::cov_matrix_constrain( - this->read>(k + (k * (k - 1)) / 2), - k, lp); + this->read>(k + size), k, lp); } /** @@ -969,9 +980,9 @@ class deserializer { require_not_std_vector_t* = nullptr, require_matrix_t* = nullptr> inline auto read_constrain_corr_matrix(LP& lp, Eigen::Index k) { + const Eigen::Index size = (k == 0) ? 0 : (k * (k - 1)) / 2; return stan::math::corr_matrix_constrain( - this->read>((k * (k - 1)) / 2), k, - lp); + this->read>(size), k, lp); } /** @@ -1024,6 +1035,7 @@ class deserializer { require_matrix_t* = nullptr> inline auto read_constrain_stochastic_column(LP& lp, Eigen::Index rows, Eigen::Index cols) { + stan::math::check_positive("read_stochastic_column", "rows", rows); return stan::math::stochastic_column_constrain( this->read>(rows - 1, cols), lp); } @@ -1080,6 +1092,7 @@ class deserializer { require_matrix_t* = nullptr> inline auto read_constrain_stochastic_row(LP& lp, Eigen::Index rows, Eigen::Index cols) { + stan::math::check_positive("read_stochastic_row", "cols", cols); return stan::math::stochastic_row_constrain( this->read>(rows, cols - 1), lp); } diff --git a/src/test/unit/io/deserializer_test.cpp b/src/test/unit/io/deserializer_test.cpp index 74ad72e96f..fc055e2099 100644 --- a/src/test/unit/io/deserializer_test.cpp +++ b/src/test/unit/io/deserializer_test.cpp @@ -73,6 +73,7 @@ TEST(deserializer_vector, read) { double z = deserializer.read(); EXPECT_FLOAT_EQ(11.0, z); + EXPECT_THROW(deserializer.read(-1), std::domain_error); } TEST(deserializer_vector, complex_read) { @@ -100,6 +101,7 @@ TEST(deserializer_vector, complex_read) { double z = deserializer.read(); EXPECT_FLOAT_EQ(15.0, z); + EXPECT_THROW(deserializer.read(-1), std::domain_error); } // row vector @@ -126,6 +128,7 @@ TEST(deserializer_row_vector, read) { double z = deserializer.read(); EXPECT_FLOAT_EQ(11.0, z); + EXPECT_THROW(deserializer.read(-1), std::domain_error); } TEST(deserializer_row_vector, complex_read) { @@ -154,6 +157,7 @@ TEST(deserializer_row_vector, complex_read) { double z = deserializer.read(); EXPECT_FLOAT_EQ(15.0, z); + EXPECT_THROW(deserializer.read(-1), std::domain_error); } // matrix @@ -181,6 +185,8 @@ TEST(deserializer_matrix, read) { double a = deserializer.read(); EXPECT_FLOAT_EQ(13.0, a); + EXPECT_THROW(deserializer.read(-1, 1), std::domain_error); + EXPECT_THROW(deserializer.read(1, -1), std::domain_error); } TEST(deserializer_matrix, complex_read) { @@ -205,6 +211,8 @@ TEST(deserializer_matrix, complex_read) { EXPECT_FLOAT_EQ(sentinal, y(i).imag()); sentinal++; } + EXPECT_THROW(deserializer.read(-1, 1), std::domain_error); + EXPECT_THROW(deserializer.read(1, -1), std::domain_error); } // array @@ -643,6 +651,7 @@ TEST(deserializer_vector, simplex_constrain) { double lp = 0; Eigen::VectorXd reference = stan::math::simplex_constrain(stan::math::to_vector(theta)); + EXPECT_THROW((deserializer.read_constrain_simplex(lp, 0)), std::domain_error); Eigen::VectorXd phi( deserializer.read_constrain_simplex( lp, theta.size() + 1)); @@ -683,6 +692,7 @@ TEST(deserializer_vector, sum_to_zero_constrain) { theta.push_back(0.0); stan::io::deserializer deserializer(theta, theta_i); double lp = 0; + EXPECT_THROW((deserializer.read_constrain_sum_to_zero(lp, 0)), std::domain_error); Eigen::VectorXd reference = stan::math::sum_to_zero_constrain(stan::math::to_vector(theta)); Eigen::VectorXd phi( @@ -723,6 +733,8 @@ TEST(deserializer_matrix, sum_to_zero_constrain) { theta.push_back(0.0); stan::io::deserializer deserializer(theta, theta_i); double lp = 0.0; + EXPECT_THROW((deserializer.read_constrain_sum_to_zero(lp, 0, 1)), std::domain_error); + EXPECT_THROW((deserializer.read_constrain_sum_to_zero(lp, 1, 0)), std::domain_error); Eigen::MatrixXd reference = stan::math::sum_to_zero_constrain(stan::math::to_matrix(theta, 2, 2)); Eigen::MatrixXd phi( @@ -874,6 +886,8 @@ TEST(deserializer_matrix, cholesky_factor_cov_constrain) { theta.push_back(-static_cast(i)); stan::io::deserializer deserializer(theta, theta_i); double lp = 0; + EXPECT_THROW((deserializer.read_constrain_cholesky_factor_cov(lp, 0, 1)), std::domain_error); + EXPECT_NO_THROW((deserializer.read_constrain_cholesky_factor_cov(lp, 0, 0))); Eigen::MatrixXd reference = stan::math::cholesky_factor_constrain( stan::math::to_vector(theta).segment(0, 6), 3, 3); Eigen::MatrixXd L( @@ -956,6 +970,7 @@ TEST(deserializer_matrix, cholesky_factor_corr_constrain) { theta.push_back(-static_cast(i)); stan::io::deserializer deserializer(theta, theta_i); double lp = 0; + EXPECT_NO_THROW((deserializer.read_constrain_cholesky_factor_corr(lp, 0))); Eigen::MatrixXd reference = stan::math::cholesky_corr_constrain( stan::math::to_vector(theta).segment(0, 3), 3); Eigen::MatrixXd L( @@ -1002,6 +1017,7 @@ TEST(deserializer_matrix, cov_matrix_constrain) { theta.push_back(-static_cast(i)); stan::io::deserializer deserializer(theta, theta_i); double lp = 0; + EXPECT_NO_THROW((deserializer.read_constrain_cov_matrix(lp, 0))); Eigen::MatrixXd reference = stan::math::cov_matrix_constrain( stan::math::to_vector(theta).segment(0, 6), 3); Eigen::MatrixXd L( @@ -1042,6 +1058,7 @@ TEST(deserializer_matrix, corr_matrix_constrain) { theta.push_back(-static_cast(i)); stan::io::deserializer deserializer(theta, theta_i); double lp = 0; + EXPECT_NO_THROW((deserializer.read_constrain_corr_matrix(lp, 0))); Eigen::MatrixXd reference = stan::math::corr_matrix_constrain( stan::math::to_vector(theta).segment(0, 3), 3); Eigen::MatrixXd L( @@ -1061,6 +1078,7 @@ TEST(deserializer_matrix, corr_matrix_jacobian) { stan::io::deserializer deserializer(theta, theta_i); double lp_ref = 0.0; double lp = 0.0; + EXPECT_NO_THROW((deserializer.read_constrain_corr_matrix(lp, 0))); Eigen::MatrixXd reference = stan::math::corr_matrix_constrain( stan::math::to_vector(theta).segment(0, 3), 3, lp_ref); Eigen::MatrixXd L( From 92002b5acd7e4dab64ffb6223ab2586dc4bc5ecc Mon Sep 17 00:00:00 2001 From: Steve Bronder Date: Fri, 17 Oct 2025 13:15:55 -0400 Subject: [PATCH 2/2] clang format --- src/stan/io/deserializer.hpp | 8 +++--- src/test/unit/io/deserializer_test.cpp | 38 +++++++++++++++++++------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/stan/io/deserializer.hpp b/src/stan/io/deserializer.hpp index 79ac1201db..384924cec6 100644 --- a/src/stan/io/deserializer.hpp +++ b/src/stan/io/deserializer.hpp @@ -807,11 +807,11 @@ class deserializer { inline auto read_constrain_cholesky_factor_cov(LP& lp, Eigen::Index M, Eigen::Index N) { stan::math::check_greater_or_equal("read_constrain_cholesky_factor_cov", - "M", M, N); - const Eigen::Index size = (M == 0 || N == 0) ? 0 : (N * (N + 1)) / 2 + (M - N) * N; + "M", M, N); + const Eigen::Index size + = (M == 0 || N == 0) ? 0 : (N * (N + 1)) / 2 + (M - N) * N; return stan::math::cholesky_factor_constrain( - this->read>(size), - M, N, lp); + this->read>(size), M, N, lp); } /** diff --git a/src/test/unit/io/deserializer_test.cpp b/src/test/unit/io/deserializer_test.cpp index fc055e2099..481d6fb035 100644 --- a/src/test/unit/io/deserializer_test.cpp +++ b/src/test/unit/io/deserializer_test.cpp @@ -651,7 +651,9 @@ TEST(deserializer_vector, simplex_constrain) { double lp = 0; Eigen::VectorXd reference = stan::math::simplex_constrain(stan::math::to_vector(theta)); - EXPECT_THROW((deserializer.read_constrain_simplex(lp, 0)), std::domain_error); + EXPECT_THROW( + (deserializer.read_constrain_simplex(lp, 0)), + std::domain_error); Eigen::VectorXd phi( deserializer.read_constrain_simplex( lp, theta.size() + 1)); @@ -692,7 +694,9 @@ TEST(deserializer_vector, sum_to_zero_constrain) { theta.push_back(0.0); stan::io::deserializer deserializer(theta, theta_i); double lp = 0; - EXPECT_THROW((deserializer.read_constrain_sum_to_zero(lp, 0)), std::domain_error); + EXPECT_THROW( + (deserializer.read_constrain_sum_to_zero(lp, 0)), + std::domain_error); Eigen::VectorXd reference = stan::math::sum_to_zero_constrain(stan::math::to_vector(theta)); Eigen::VectorXd phi( @@ -733,8 +737,12 @@ TEST(deserializer_matrix, sum_to_zero_constrain) { theta.push_back(0.0); stan::io::deserializer deserializer(theta, theta_i); double lp = 0.0; - EXPECT_THROW((deserializer.read_constrain_sum_to_zero(lp, 0, 1)), std::domain_error); - EXPECT_THROW((deserializer.read_constrain_sum_to_zero(lp, 1, 0)), std::domain_error); + EXPECT_THROW((deserializer.read_constrain_sum_to_zero( + lp, 0, 1)), + std::domain_error); + EXPECT_THROW((deserializer.read_constrain_sum_to_zero( + lp, 1, 0)), + std::domain_error); Eigen::MatrixXd reference = stan::math::sum_to_zero_constrain(stan::math::to_matrix(theta, 2, 2)); Eigen::MatrixXd phi( @@ -886,8 +894,13 @@ TEST(deserializer_matrix, cholesky_factor_cov_constrain) { theta.push_back(-static_cast(i)); stan::io::deserializer deserializer(theta, theta_i); double lp = 0; - EXPECT_THROW((deserializer.read_constrain_cholesky_factor_cov(lp, 0, 1)), std::domain_error); - EXPECT_NO_THROW((deserializer.read_constrain_cholesky_factor_cov(lp, 0, 0))); + EXPECT_THROW( + (deserializer.read_constrain_cholesky_factor_cov( + lp, 0, 1)), + std::domain_error); + EXPECT_NO_THROW( + (deserializer.read_constrain_cholesky_factor_cov( + lp, 0, 0))); Eigen::MatrixXd reference = stan::math::cholesky_factor_constrain( stan::math::to_vector(theta).segment(0, 6), 3, 3); Eigen::MatrixXd L( @@ -970,7 +983,9 @@ TEST(deserializer_matrix, cholesky_factor_corr_constrain) { theta.push_back(-static_cast(i)); stan::io::deserializer deserializer(theta, theta_i); double lp = 0; - EXPECT_NO_THROW((deserializer.read_constrain_cholesky_factor_corr(lp, 0))); + EXPECT_NO_THROW( + (deserializer.read_constrain_cholesky_factor_corr( + lp, 0))); Eigen::MatrixXd reference = stan::math::cholesky_corr_constrain( stan::math::to_vector(theta).segment(0, 3), 3); Eigen::MatrixXd L( @@ -1017,7 +1032,8 @@ TEST(deserializer_matrix, cov_matrix_constrain) { theta.push_back(-static_cast(i)); stan::io::deserializer deserializer(theta, theta_i); double lp = 0; - EXPECT_NO_THROW((deserializer.read_constrain_cov_matrix(lp, 0))); + EXPECT_NO_THROW( + (deserializer.read_constrain_cov_matrix(lp, 0))); Eigen::MatrixXd reference = stan::math::cov_matrix_constrain( stan::math::to_vector(theta).segment(0, 6), 3); Eigen::MatrixXd L( @@ -1058,7 +1074,8 @@ TEST(deserializer_matrix, corr_matrix_constrain) { theta.push_back(-static_cast(i)); stan::io::deserializer deserializer(theta, theta_i); double lp = 0; - EXPECT_NO_THROW((deserializer.read_constrain_corr_matrix(lp, 0))); + EXPECT_NO_THROW( + (deserializer.read_constrain_corr_matrix(lp, 0))); Eigen::MatrixXd reference = stan::math::corr_matrix_constrain( stan::math::to_vector(theta).segment(0, 3), 3); Eigen::MatrixXd L( @@ -1078,7 +1095,8 @@ TEST(deserializer_matrix, corr_matrix_jacobian) { stan::io::deserializer deserializer(theta, theta_i); double lp_ref = 0.0; double lp = 0.0; - EXPECT_NO_THROW((deserializer.read_constrain_corr_matrix(lp, 0))); + EXPECT_NO_THROW( + (deserializer.read_constrain_corr_matrix(lp, 0))); Eigen::MatrixXd reference = stan::math::corr_matrix_constrain( stan::math::to_vector(theta).segment(0, 3), 3, lp_ref); Eigen::MatrixXd L(