From 38178a8a9b45a4e45971e99692d52cb022f016d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20=C3=9Acar?= Date: Tue, 24 Feb 2026 19:22:21 +0100 Subject: [PATCH 01/13] implementation of safe add/sub/mul for integers --- inst/include/Rcpp/sugar/sugar.h | 4 +- inst/include/Rcpp/sugar/tools/safe_math.h | 132 ++++++++++++++++++ inst/tinytest/cpp/sugar_safe_math.cpp | 36 +++++ .../tinytest/cpp/sugar_safe_math_fallback.cpp | 37 +++++ inst/tinytest/test_sugar.R | 22 ++- 5 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 inst/include/Rcpp/sugar/tools/safe_math.h create mode 100644 inst/tinytest/cpp/sugar_safe_math.cpp create mode 100644 inst/tinytest/cpp/sugar_safe_math_fallback.cpp diff --git a/inst/include/Rcpp/sugar/sugar.h b/inst/include/Rcpp/sugar/sugar.h index 0e2c2e37b..1bf160ab9 100644 --- a/inst/include/Rcpp/sugar/sugar.h +++ b/inst/include/Rcpp/sugar/sugar.h @@ -2,7 +2,8 @@ // // sugar.h: Rcpp R/C++ interface class library -- main file for Rcpp::sugar // -// Copyright (C) 2010 - 2012 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -23,6 +24,7 @@ #define RCPP_SUGAR_H #include +#include #include #include diff --git a/inst/include/Rcpp/sugar/tools/safe_math.h b/inst/include/Rcpp/sugar/tools/safe_math.h new file mode 100644 index 000000000..40edec81e --- /dev/null +++ b/inst/include/Rcpp/sugar/tools/safe_math.h @@ -0,0 +1,132 @@ +// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; tab-width: 8 -*- +// +// safe_math.h: Rcpp R/C++ interface class library -- +// +// Copyright (C) 2026 Iñaki Ucar +// +// This file is part of Rcpp. +// +// Rcpp is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// Rcpp is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Rcpp. If not, see . + +#ifndef Rcpp__sugar__tools_safe_math_h +#define Rcpp__sugar__tools_safe_math_h + +#ifndef safe_math__has_builtin +# ifdef __has_builtin +# define safe_math__has_builtin(x) __has_builtin(x) +# else +# define safe_math__has_builtin(x) 0 +# endif +#endif + +#define RCPP_SAFE_ADD(a, b) Rcpp::sugar::safe_add(a, b, __func__) +#define RCPP_SAFE_SUB(a, b) Rcpp::sugar::safe_sub(a, b, __func__) +#define RCPP_SAFE_MUL(a, b) Rcpp::sugar::safe_mul(a, b, __func__) + +namespace Rcpp { +namespace sugar { + + inline void stop_overflow(const char* caller) { + if (caller) + Rcpp::stop("[%s] Integer overflow!", caller); + Rcpp::stop("Integer overflow!"); + } + + // Addition + template + inline typename std::enable_if::value, T>::type + safe_add(T a, T b, const char* caller = nullptr) { + #if safe_math__has_builtin(__builtin_add_overflow) + T result; + if (__builtin_add_overflow(a, b, &result)) + stop_overflow(caller); + return result; + #else // fallback + if (std::is_signed::value) { + if ((b > 0 && a > std::numeric_limits::max() - b) || + (b < 0 && a < std::numeric_limits::min() - b)) + stop_overflow(caller); + } else { + if (a > std::numeric_limits::max() - b) + stop_overflow(caller); + } + return a + b; + #endif + } + + template + inline typename std::enable_if::value, T>::type + safe_add(T a, T b, const char* caller = nullptr) { return a + b; } + + // Subtraction + template + inline typename std::enable_if::value, T>::type + safe_sub(T a, T b, const char* caller = nullptr) { + #if safe_math__has_builtin(__builtin_sub_overflow) + T result; + if (__builtin_sub_overflow(a, b, &result)) + stop_overflow(caller); + return result; + #else // fallback + if (std::is_signed::value) { + if ((b < 0 && a > std::numeric_limits::max() + b) || + (b > 0 && a < std::numeric_limits::min() + b)) + stop_overflow(caller); + } else { + if (a < b) + stop_overflow(caller); + } + return a - b; + #endif + } + + template + inline typename std::enable_if::value, T>::type + safe_sub(T a, T b, const char* caller = nullptr) { return a - b; } + + // Multiplication + template + inline typename std::enable_if::value, T>::type + safe_mul(T a, T b, const char* caller = nullptr) { + #if safe_math__has_builtin(__builtin_mul_overflow) + T result; + if (__builtin_mul_overflow(a, b, &result)) + stop_overflow(caller); + return result; + #else // fallback + if (a == 0 || b == 0) return 0; + if (std::is_signed::value) { + if ((a > 0 && b > 0 && a > std::numeric_limits::max() / b) || + (a > 0 && b < 0 && b < std::numeric_limits::min() / a) || + (a < 0 && b > 0 && a < std::numeric_limits::min() / b) || + (a < 0 && b < 0 && a < std::numeric_limits::max() / b)) + stop_overflow(caller); + } else { + if (b > 0 && a > std::numeric_limits::max() / b) + stop_overflow(caller); + } + return a * b; + #endif + } + + template + inline typename std::enable_if::value, T>::type + safe_mul(T a, T b, const char* caller = nullptr) { return a * b; } + +} // namespace sugar +} // namespace Rcpp + +#undef safe_math__has_builtin + +#endif diff --git a/inst/tinytest/cpp/sugar_safe_math.cpp b/inst/tinytest/cpp/sugar_safe_math.cpp new file mode 100644 index 000000000..9677184b9 --- /dev/null +++ b/inst/tinytest/cpp/sugar_safe_math.cpp @@ -0,0 +1,36 @@ + +// sugar_safe_math.cpp: Rcpp R/C++ interface class library -- safe math unit tests +// +// Copyright (C) 2026 Iñaki Ucar +// +// This file is part of Rcpp. +// +// Rcpp is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// Rcpp is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Rcpp. If not, see . + +#include + +// [[Rcpp::export]] +int safe_add(int a, int b){ + return RCPP_SAFE_ADD(a, b); +} + +// [[Rcpp::export]] +int safe_sub(int a, int b){ + return RCPP_SAFE_SUB(a, b); +} + +// [[Rcpp::export]] +int safe_mul(int a, int b){ + return RCPP_SAFE_MUL(a, b); +} diff --git a/inst/tinytest/cpp/sugar_safe_math_fallback.cpp b/inst/tinytest/cpp/sugar_safe_math_fallback.cpp new file mode 100644 index 000000000..7b5e7c19d --- /dev/null +++ b/inst/tinytest/cpp/sugar_safe_math_fallback.cpp @@ -0,0 +1,37 @@ + +// sugar_safe_math.cpp: Rcpp R/C++ interface class library -- safe math unit tests +// +// Copyright (C) 2026 Iñaki Ucar +// +// This file is part of Rcpp. +// +// Rcpp is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// Rcpp is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Rcpp. If not, see . + +#define define safe_math__has_builtin(x) 0 +#include + +// [[Rcpp::export]] +int safe_add_fallback(int a, int b){ + return RCPP_SAFE_ADD(a, b); +} + +// [[Rcpp::export]] +int safe_sub_fallback(int a, int b){ + return RCPP_SAFE_SUB(a, b); +} + +// [[Rcpp::export]] +int safe_mul_fallback(int a, int b){ + return RCPP_SAFE_MUL(a, b); +} diff --git a/inst/tinytest/test_sugar.R b/inst/tinytest/test_sugar.R index 4465cedf7..eec85425a 100644 --- a/inst/tinytest/test_sugar.R +++ b/inst/tinytest/test_sugar.R @@ -1,6 +1,6 @@ -## Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois -## Copyright (C) 2025 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar +## Copyright (C) 2010 - 2024 Dirk Eddelbuettel and Romain Francois +## Copyright (C) 2025 - 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar ## ## This file is part of Rcpp. ## @@ -20,6 +20,8 @@ if (Sys.getenv("RunAllRcppTests") != "yes") exit_file("Set 'RunAllRcppTests' to 'yes' to run.") Rcpp::sourceCpp("cpp/sugar.cpp") +Rcpp::sourceCpp("cpp/sugar_safe_math.cpp") +Rcpp::sourceCpp("cpp/sugar_safe_math_fallback.cpp") ## There are some (documented, see https://blog.r-project.org/2020/11/02/will-r-work-on-apple-silicon/index.html) ## issues with NA propagation on arm64 / macOS. We not (yet ?) do anything special so we just skip some tests @@ -29,6 +31,22 @@ isArm <- Sys.info()[["machine"]] == "arm64" || Sys.info()[["machine"]] == "aarch ## Needed for a change in R 3.6.0 reducing a bias in very large samples suppressWarnings(RNGversion("3.5.0")) +# test.sugar.safe_math +expect_equal(safe_add(3, 2), 5) +expect_error(safe_add(.Machine$integer.max, 2)) +expect_equal(safe_sub(3, 2), 1) +expect_error(safe_sub(.Machine$integer.min, 2)) +expect_equal(safe_mul(3, 2), 6) +expect_error(safe_mul(.Machine$integer.max, 2)) + +expect_equal(safe_add_fallback(3, 2), 5) +expect_error(safe_add_fallback(.Machine$integer.max, 2)) +expect_equal(safe_sub_fallback(3, 2), 1) +expect_error(safe_sub_fallback(.Machine$integer.min, 2)) +expect_equal(safe_mul_fallback(3, 2), 6) +expect_error(safe_mul_fallback(.Machine$integer.max, 2)) + + # test.sugar.abs <- function( ){ x <- rnorm(10) y <- -10:10 From 7bcb01b94f225023a470c86349a7f5d98d343789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20=C3=9Acar?= Date: Wed, 25 Feb 2026 11:44:36 +0100 Subject: [PATCH 02/13] fix duplicated define --- inst/tinytest/cpp/sugar_safe_math_fallback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/tinytest/cpp/sugar_safe_math_fallback.cpp b/inst/tinytest/cpp/sugar_safe_math_fallback.cpp index 7b5e7c19d..15fdcc2e9 100644 --- a/inst/tinytest/cpp/sugar_safe_math_fallback.cpp +++ b/inst/tinytest/cpp/sugar_safe_math_fallback.cpp @@ -18,7 +18,7 @@ // You should have received a copy of the GNU General Public License // along with Rcpp. If not, see . -#define define safe_math__has_builtin(x) 0 +#define safe_math__has_builtin(x) 0 #include // [[Rcpp::export]] From f7420e4cb31ab4734464bacac4fbe0779f91b3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20=C3=9Acar?= Date: Wed, 25 Feb 2026 12:16:43 +0100 Subject: [PATCH 03/13] fix couple of tests, explicitly check for overflow errors --- inst/tinytest/test_sugar.R | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/inst/tinytest/test_sugar.R b/inst/tinytest/test_sugar.R index eec85425a..57f443f85 100644 --- a/inst/tinytest/test_sugar.R +++ b/inst/tinytest/test_sugar.R @@ -33,18 +33,18 @@ suppressWarnings(RNGversion("3.5.0")) # test.sugar.safe_math expect_equal(safe_add(3, 2), 5) -expect_error(safe_add(.Machine$integer.max, 2)) +expect_error(safe_add(.Machine$integer.max, 2), "overflow") expect_equal(safe_sub(3, 2), 1) -expect_error(safe_sub(.Machine$integer.min, 2)) +expect_error(safe_sub(-.Machine$integer.max, 2), "overflow") expect_equal(safe_mul(3, 2), 6) -expect_error(safe_mul(.Machine$integer.max, 2)) +expect_error(safe_mul(.Machine$integer.max, 2), "overflow") expect_equal(safe_add_fallback(3, 2), 5) -expect_error(safe_add_fallback(.Machine$integer.max, 2)) +expect_error(safe_add_fallback(.Machine$integer.max, 2), "overflow") expect_equal(safe_sub_fallback(3, 2), 1) -expect_error(safe_sub_fallback(.Machine$integer.min, 2)) +expect_error(safe_sub_fallback(-.Machine$integer.max, 2), "overflow") expect_equal(safe_mul_fallback(3, 2), 6) -expect_error(safe_mul_fallback(.Machine$integer.max, 2)) +expect_error(safe_mul_fallback(.Machine$integer.max, 2), "overflow") # test.sugar.abs <- function( ){ From 3adff65b1778cb0ce3cbf460e5f28a24dc4ff05d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20=C3=9Acar?= Date: Wed, 25 Feb 2026 21:02:51 +0100 Subject: [PATCH 04/13] fix functions/diff.h --- inst/include/Rcpp/sugar/functions/diff.h | 11 ++++++----- inst/tinytest/test_sugar.R | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/inst/include/Rcpp/sugar/functions/diff.h b/inst/include/Rcpp/sugar/functions/diff.h index 5d9910c72..aa1c9d7d6 100644 --- a/inst/include/Rcpp/sugar/functions/diff.h +++ b/inst/include/Rcpp/sugar/functions/diff.h @@ -2,7 +2,8 @@ // // diff.h: Rcpp R/C++ interface class library -- diff // -// Copyright (C) 2010 - 2013 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -51,7 +52,7 @@ class Diff : public Rcpp::VectorBase< RTYPE, LHS_NA , Diff > set_previous(i+1, y ) ; return traits::get_na() ; // NA } - STORAGE res = y - previous ; + STORAGE res = RCPP_SAFE_SUB(y, previous); set_previous( i+1, y) ; return res ; } @@ -81,7 +82,7 @@ class Diff : public Rcpp::VectorBase< REALSXP, LHS_NA, D inline double operator[]( R_xlen_t i ) const { double y = lhs[i+1] ; if( previous_index != i ) previous = lhs[i] ; - double res = y - previous ; + double res = RCPP_SAFE_SUB(y, previous); previous = y ; previous_index = i+1 ; return res ; @@ -105,10 +106,10 @@ class Diff : public Rcpp::VectorBase< RTYPE, false , Diff Date: Wed, 25 Feb 2026 21:03:30 +0100 Subject: [PATCH 05/13] fix functions/cumprod.h --- inst/include/Rcpp/sugar/functions/cumprod.h | 13 +++++++------ inst/tinytest/test_sugar.R | 1 + 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/inst/include/Rcpp/sugar/functions/cumprod.h b/inst/include/Rcpp/sugar/functions/cumprod.h index 59cf8d4bd..0ad8936fd 100644 --- a/inst/include/Rcpp/sugar/functions/cumprod.h +++ b/inst/include/Rcpp/sugar/functions/cumprod.h @@ -2,7 +2,8 @@ // // cumsum.h: Rcpp R/C++ interface class library -- cumsum // -// Copyright (C) 2010 - 2011 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -33,23 +34,23 @@ class Cumprod : public Lazy< Rcpp::Vector, Cumprod > { typedef Rcpp::Vector VECTOR; Cumprod(const VEC_TYPE& object_) : object(object_) {} - + VECTOR get() const { R_xlen_t n = object.size(); VECTOR result(n, Rcpp::traits::get_na()); STORAGE current = object[0]; - + if (Rcpp::traits::is_na(current)) return result; result[0] = current; for (R_xlen_t i = 1; i < n; i++) { current = object[i]; if (Rcpp::traits::is_na(current)) return result; - result[i] = result[i-1] * current; + result[i] = RCPP_SAFE_MUL(result[i-1], current); } return result ; } private: - const VEC_TYPE& object; + const VEC_TYPE& object; }; } // sugar @@ -72,5 +73,5 @@ inline sugar::Cumprod cumprod(const VectorBase& } // Rcpp -#endif // Rcpp__sugar__cumprod_h +#endif // Rcpp__sugar__cumprod_h diff --git a/inst/tinytest/test_sugar.R b/inst/tinytest/test_sugar.R index 2c7d6b79c..5a6085f75 100644 --- a/inst/tinytest/test_sugar.R +++ b/inst/tinytest/test_sugar.R @@ -845,6 +845,7 @@ expect_equal(fx(x), cumprod(x)) # test.sugar.cumprod_iv <- function() { +expect_error(runit_cumprod_iv(c(2, .Machine$integer.max)), "overflow") fx <- runit_cumprod_iv x <- as.integer(rpois(10, 5)) expect_equal(fx(x), cumprod(x)) From 3835da442e94576e0928d52dd5919150c5a4977c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20=C3=9Acar?= Date: Wed, 25 Feb 2026 21:09:47 +0100 Subject: [PATCH 06/13] fix functions/cumsum.h --- inst/include/Rcpp/sugar/functions/cumsum.h | 5 +++-- inst/tinytest/cpp/sugar.cpp | 12 +++++++++--- inst/tinytest/test_sugar.R | 19 ++++++++++++++----- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/inst/include/Rcpp/sugar/functions/cumsum.h b/inst/include/Rcpp/sugar/functions/cumsum.h index e42e5bc35..cb8e53886 100644 --- a/inst/include/Rcpp/sugar/functions/cumsum.h +++ b/inst/include/Rcpp/sugar/functions/cumsum.h @@ -2,7 +2,8 @@ // // cumsum.h: Rcpp R/C++ interface class library -- cumsum // -// Copyright (C) 2010 - 2011 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -45,7 +46,7 @@ class Cumsum : public Lazy< Rcpp::Vector , Cumsum > { current = object[i] ; if( Rcpp::traits::is_na(current) ) return result ; - result[i] = result[i-1] + current ; + result[i] = RCPP_SAFE_ADD(result[i-1], current); } return result ; } diff --git a/inst/tinytest/cpp/sugar.cpp b/inst/tinytest/cpp/sugar.cpp index d436559b9..15398201a 100644 --- a/inst/tinytest/cpp/sugar.cpp +++ b/inst/tinytest/cpp/sugar.cpp @@ -1,8 +1,8 @@ // sugar.cpp: Rcpp R/C++ interface class library -- sugar unit tests // -// Copyright (C) 2012 - 2025 Dirk Eddelbuettel and Romain Francois -// Copyright (C) 2025 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar +// Copyright (C) 2012 - 2024 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2025 - 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -609,11 +609,17 @@ double runit_sum( NumericVector xx){ } // [[Rcpp::export]] -NumericVector runit_cumsum( NumericVector xx ){ +NumericVector runit_cumsum_nv( NumericVector xx ){ NumericVector res = cumsum( xx ) ; return res ; } +// [[Rcpp::export]] +IntegerVector runit_cumsum_iv( IntegerVector xx ){ + IntegerVector res = cumsum( xx ) ; + return res ; +} + // [[Rcpp::export]] List runit_asvector( NumericMatrix z, NumericVector x, NumericVector y){ return List::create( diff --git a/inst/tinytest/test_sugar.R b/inst/tinytest/test_sugar.R index 5a6085f75..f7d8a21b8 100644 --- a/inst/tinytest/test_sugar.R +++ b/inst/tinytest/test_sugar.R @@ -653,12 +653,21 @@ x[4] <- NA expect_equal( fx(x), sum(x) ) -# test.sugar.cumsum <- function(){ -fx <- runit_cumsum -x <- rnorm( 10 ) -expect_equal( fx(x), cumsum(x) ) +# test.sugar.cumsum_nv <- function(){ +fx <- runit_cumsum_nv +x <- rnorm(10) +expect_equal(fx(x), cumsum(x)) +x[4] <- NA +expect_equal(fx(x), cumsum(x)) + + +# test.sugar.cumsum_iv <- function() { +expect_error(runit_cumsum_iv(c(2, .Machine$integer.max)), "overflow") +fx <- runit_cumsum_iv +x <- as.integer(rpois(10, 5)) +expect_equal(fx(x), cumsum(x)) x[4] <- NA -expect_equal( fx(x), cumsum(x) ) +expect_equal(fx(x), cumsum(x)) # test.sugar.asvector <- function(){ From 98734f9f66562d798e60e7132f5c10b2ee0fee15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20=C3=9Acar?= Date: Wed, 25 Feb 2026 21:25:13 +0100 Subject: [PATCH 07/13] move functions further down into the detail namespace --- inst/include/Rcpp/sugar/tools/safe_math.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/inst/include/Rcpp/sugar/tools/safe_math.h b/inst/include/Rcpp/sugar/tools/safe_math.h index 40edec81e..46774850d 100644 --- a/inst/include/Rcpp/sugar/tools/safe_math.h +++ b/inst/include/Rcpp/sugar/tools/safe_math.h @@ -30,12 +30,13 @@ # endif #endif -#define RCPP_SAFE_ADD(a, b) Rcpp::sugar::safe_add(a, b, __func__) -#define RCPP_SAFE_SUB(a, b) Rcpp::sugar::safe_sub(a, b, __func__) -#define RCPP_SAFE_MUL(a, b) Rcpp::sugar::safe_mul(a, b, __func__) +#define RCPP_SAFE_ADD(a, b) Rcpp::sugar::detail::safe_add(a, b, __func__) +#define RCPP_SAFE_SUB(a, b) Rcpp::sugar::detail::safe_sub(a, b, __func__) +#define RCPP_SAFE_MUL(a, b) Rcpp::sugar::detail::safe_mul(a, b, __func__) namespace Rcpp { namespace sugar { +namespace detail { inline void stop_overflow(const char* caller) { if (caller) @@ -124,6 +125,7 @@ namespace sugar { inline typename std::enable_if::value, T>::type safe_mul(T a, T b, const char* caller = nullptr) { return a * b; } +} // namespace detail } // namespace sugar } // namespace Rcpp From 0e796006d2ebf4f673959e4fe37d70841ac2af40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20=C3=9Acar?= Date: Wed, 25 Feb 2026 22:35:03 +0100 Subject: [PATCH 08/13] fix functions/rowSums.h --- inst/include/Rcpp/sugar/functions/rowSums.h | 184 +++++++------------- inst/tinytest/test_sugar.R | 5 + 2 files changed, 67 insertions(+), 122 deletions(-) diff --git a/inst/include/Rcpp/sugar/functions/rowSums.h b/inst/include/Rcpp/sugar/functions/rowSums.h index 99adbd8d2..ec9b26acb 100644 --- a/inst/include/Rcpp/sugar/functions/rowSums.h +++ b/inst/include/Rcpp/sugar/functions/rowSums.h @@ -1,8 +1,9 @@ // -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; indent-tabs-mode: nil; -*- // -// rowSums.h: Rcpp R/C++ interface class library -- rowSums, colSums, rowMeans, colMeans +// rowSums.h: Rcpp R/C++ interface class library -- rowSums, colSums, rowMeans, colMeans // -// Copyright (C) 2016 Nathan Russell +// Copyright (C) 2016 - 2025 Nathan Russell +// Copyright (C) 2026 Nathan Russell and Iñaki Ucar // // This file is part of Rcpp. // @@ -27,33 +28,12 @@ namespace sugar { namespace detail { -inline bool check_na(double x) { - return ISNAN(x); -} - -inline bool check_na(int x) { - return x == NA_INTEGER; -} - -inline bool check_na(Rboolean x) { - return x == NA_LOGICAL; -} - -inline bool check_na(SEXP x) { - return x == NA_STRING; -} - -inline bool check_na(Rcomplex x) { - return ISNAN(x.r) || ISNAN(x.i); -} - - inline void incr(double* lhs, double rhs) { *lhs += rhs; } inline void incr(int* lhs, int rhs) { - *lhs += rhs; + *lhs = RCPP_SAFE_ADD(*lhs, rhs); } inline void incr(Rcomplex* lhs, const Rcomplex& rhs) { @@ -159,12 +139,12 @@ class RowSumsImpl : // INTSXP output // // int + NA_LOGICAL (NA_INTEGER) != NA_INTEGER, as is the -// case with NA_REAL, so we specialize for these two SEXPTYPES -// and do explicit accounting of NAs. -// +// case with NA_REAL, so we specialize for these two SEXPTYPES +// and do explicit accounting of NAs. +// // The two specializations, while necessary, are redundant, hence -// the macro. The same applies to the 'na.rm = TRUE' variant, and -// likewise for colSums, rowMeans, and colMeans. +// the macro. The same applies to the 'na.rm = TRUE' variant, and +// likewise for colSums, rowMeans, and colMeans. // #define ROW_SUMS_IMPL_KEEPNA(__RTYPE__) \ \ @@ -178,10 +158,6 @@ private: typedef typename return_traits::type return_vector; \ typedef typename traits::storage_type::type stored_type; \ \ - struct bit { \ - unsigned char x : 1; \ - }; \ - \ public: \ RowSumsImpl(const MatrixBase<__RTYPE__, NA, T>& ref_) \ : ref(ref_) \ @@ -191,29 +167,22 @@ public: R_xlen_t i, j, nr = ref.nrow(), nc = ref.ncol(); \ return_vector res(nr); \ \ - std::vector na_flags(nr); \ - \ for (j = 0; j < nc; j++) { \ for (i = 0; i < nr; i++) { \ - if (detail::check_na(ref(i, j))) { \ - na_flags[i].x |= 0x1; \ - } \ - detail::incr(&res[i], ref(i, j)); \ - } \ - } \ - \ - for (i = 0; i < nr; i++) { \ - if (na_flags[i].x) { \ - res[i] = NA_INTEGER; \ + if (traits::is_na<__RTYPE__>(res[i])) \ + continue; \ + if (traits::is_na<__RTYPE__>(ref(i, j))) \ + res[i] = traits::get_na<__RTYPE__>(); \ + else detail::incr(&res[i], ref(i, j)); \ } \ } \ \ return res; \ } \ -}; +}; -ROW_SUMS_IMPL_KEEPNA(LGLSXP) -ROW_SUMS_IMPL_KEEPNA(INTSXP) +ROW_SUMS_IMPL_KEEPNA(LGLSXP) +ROW_SUMS_IMPL_KEEPNA(INTSXP) #undef ROW_SUMS_IMPL_KEEPNA @@ -246,7 +215,7 @@ class RowSumsImpl : for (j = 0; j < nc; j++) { for (i = 0; i < nr; i++) { current = ref(i, j); - if (!detail::check_na(current)) { + if (!traits::is_na(current)) { detail::incr(&res[i], current); } } @@ -287,7 +256,7 @@ public: for (j = 0; j < nc; j++) { \ for (i = 0; i < nr; i++) { \ current = ref(i, j); \ - if (!detail::check_na(current)) { \ + if (!traits::is_na<__RTYPE__>(current)) { \ detail::incr(&res[i], current); \ } \ } \ @@ -295,10 +264,10 @@ public: \ return res; \ } \ -}; +}; -ROW_SUMS_IMPL_RMNA(LGLSXP) -ROW_SUMS_IMPL_RMNA(INTSXP) +ROW_SUMS_IMPL_RMNA(LGLSXP) +ROW_SUMS_IMPL_RMNA(INTSXP) #undef ROW_SUMS_IMPL_RMNA @@ -362,10 +331,6 @@ private: typedef typename return_traits::type return_vector; \ typedef typename traits::storage_type::type stored_type; \ \ - struct bit { \ - unsigned char x : 1; \ - }; \ - \ public: \ ColSumsImpl(const MatrixBase<__RTYPE__, NA, T>& ref_) \ : ref(ref_) \ @@ -375,20 +340,13 @@ public: R_xlen_t i, j, nr = ref.nrow(), nc = ref.ncol(); \ return_vector res(nc); \ \ - std::vector na_flags(nc); \ - \ for (j = 0; j < nc; j++) { \ for (i = 0; i < nr; i++) { \ - if (detail::check_na(ref(i, j))) { \ - na_flags[j].x |= 0x1; \ - } \ - detail::incr(&res[j], ref(i, j)); \ - } \ - } \ - \ - for (j = 0; j < nc; j++) { \ - if (na_flags[j].x) { \ - res[j] = NA_INTEGER; \ + if (traits::is_na<__RTYPE__>(res[j])) \ + continue; \ + if (traits::is_na<__RTYPE__>(ref(i, j))) \ + res[j] = traits::get_na<__RTYPE__>(); \ + else detail::incr(&res[j], ref(i, j)); \ } \ } \ \ @@ -396,11 +354,11 @@ public: } \ }; -COL_SUMS_IMPL_KEEPNA(LGLSXP) -COL_SUMS_IMPL_KEEPNA(INTSXP) +COL_SUMS_IMPL_KEEPNA(LGLSXP) +COL_SUMS_IMPL_KEEPNA(INTSXP) #undef COL_SUMS_IMPL_KEEPNA - + // ColSums // na.rm = TRUE // default input @@ -430,7 +388,7 @@ class ColSumsImpl : for (j = 0; j < nc; j++) { for (i = 0; i < nr; i++) { current = ref(i, j); - if (!detail::check_na(current)) { + if (!traits::is_na(current)) { detail::incr(&res[j], current); } } @@ -471,7 +429,7 @@ public: for (j = 0; j < nc; j++) { \ for (i = 0; i < nr; i++) { \ current = ref(i, j); \ - if (!detail::check_na(current)) { \ + if (!traits::is_na<__RTYPE__>(current)) { \ detail::incr(&res[j], current); \ } \ } \ @@ -481,8 +439,8 @@ public: } \ }; -COL_SUMS_IMPL_RMNA(LGLSXP) -COL_SUMS_IMPL_RMNA(INTSXP) +COL_SUMS_IMPL_RMNA(LGLSXP) +COL_SUMS_IMPL_RMNA(INTSXP) #undef COL_SUMS_IMPL_RMNA @@ -553,10 +511,6 @@ private: typedef typename return_traits::type return_vector; \ typedef typename traits::storage_type::type stored_type; \ \ - struct bit { \ - unsigned char x : 1; \ - }; \ - \ public: \ RowMeansImpl(const MatrixBase<__RTYPE__, NA, T>& ref_) \ : ref(ref_) \ @@ -566,34 +520,29 @@ public: R_xlen_t i, j, nr = ref.nrow(), nc = ref.ncol(); \ return_vector res(nr); \ \ - std::vector na_flags(nc); \ - \ for (j = 0; j < nc; j++) { \ for (i = 0; i < nr; i++) { \ - if (detail::check_na(ref(i, j))) { \ - na_flags[i].x |= 0x1; \ - } \ - detail::incr(&res[i], ref(i, j)); \ + if (traits::is_na(res[i])) \ + continue; \ + if (traits::is_na<__RTYPE__>(ref(i, j))) \ + res[i] = traits::get_na(); \ + else detail::incr(&res[i], ref(i, j)); \ } \ } \ \ - for (i = 0; i < nr; i++) { \ - if (!na_flags[i].x) { \ + for (i = 0; i < nr; i++) \ + if (!traits::is_na(res[i])) \ detail::div(&res[i], nc); \ - } else { \ - res[i] = NA_REAL; \ - } \ - } \ \ return res; \ } \ }; -ROW_MEANS_IMPL_KEEPNA(LGLSXP) -ROW_MEANS_IMPL_KEEPNA(INTSXP) +ROW_MEANS_IMPL_KEEPNA(LGLSXP) +ROW_MEANS_IMPL_KEEPNA(INTSXP) #undef ROW_MEANS_IMPL_KEEPNA - + // RowMeans // na.rm = TRUE // default input @@ -625,7 +574,7 @@ class RowMeansImpl : for (j = 0; j < nc; j++) { for (i = 0; i < nr; i++) { current = ref(i, j); - if (!detail::check_na(current)) { + if (!traits::is_na(current)) { detail::incr(&res[i], ref(i, j)); ++n_ok[i]; } @@ -674,7 +623,7 @@ public: \ for (j = 0; j < nc; j++) { \ for (i = 0; i < nr; i++) { \ - if (!detail::check_na(ref(i, j))) { \ + if (!traits::is_na<__RTYPE__>(ref(i, j))) { \ detail::incr(&res[i], ref(i, j)); \ ++n_ok[i]; \ } \ @@ -693,8 +642,8 @@ public: } \ }; -ROW_MEANS_IMPL_RMNA(LGLSXP) -ROW_MEANS_IMPL_RMNA(INTSXP) +ROW_MEANS_IMPL_RMNA(LGLSXP) +ROW_MEANS_IMPL_RMNA(INTSXP) #undef ROW_MEANS_IMPL_RMNA @@ -762,10 +711,6 @@ private: typedef typename return_traits::type return_vector; \ typedef typename traits::storage_type::type stored_type; \ \ - struct bit { \ - unsigned char x : 1; \ - }; \ - \ public: \ ColMeansImpl(const MatrixBase<__RTYPE__, NA, T>& ref_) \ : ref(ref_) \ @@ -775,31 +720,26 @@ public: R_xlen_t i, j, nr = ref.nrow(), nc = ref.ncol(); \ return_vector res(nc); \ \ - std::vector na_flags(nc); \ - \ for (j = 0; j < nc; j++) { \ for (i = 0; i < nr; i++) { \ - if (detail::check_na(ref(i, j))) { \ - na_flags[j].x |= 0x1; \ - } \ - detail::incr(&res[j], ref(i, j)); \ + if (traits::is_na(res[j])) \ + continue; \ + if (traits::is_na<__RTYPE__>(ref(i, j))) \ + res[j] = traits::get_na(); \ + else detail::incr(&res[j], ref(i, j)); \ } \ } \ \ - for (j = 0; j < nc; j++) { \ - if (!na_flags[j].x) { \ + for (j = 0; j < nc; j++) \ + if (!traits::is_na(res[j])) \ detail::div(&res[j], nr); \ - } else { \ - res[j] = NA_REAL; \ - } \ - } \ \ return res; \ } \ }; -COL_MEANS_IMPL_KEEPNA(LGLSXP) -COL_MEANS_IMPL_KEEPNA(INTSXP) +COL_MEANS_IMPL_KEEPNA(LGLSXP) +COL_MEANS_IMPL_KEEPNA(INTSXP) #undef COL_MEANS_IMPL_KEEPNA @@ -834,7 +774,7 @@ class ColMeansImpl : for (j = 0; j < nc; j++) { for (i = 0; i < nr; i++) { current = ref(i, j); - if (!detail::check_na(current)) { + if (!traits::is_na(current)) { detail::incr(&res[j], ref(i, j)); ++n_ok[j]; } @@ -883,7 +823,7 @@ public: \ for (j = 0; j < nc; j++) { \ for (i = 0; i < nr; i++) { \ - if (!detail::check_na(ref(i, j))) { \ + if (!traits::is_na<__RTYPE__>(ref(i, j))) { \ detail::incr(&res[j], ref(i, j)); \ ++n_ok[j]; \ } \ @@ -902,11 +842,11 @@ public: } \ }; -COL_MEANS_IMPL_RMNA(LGLSXP) -COL_MEANS_IMPL_RMNA(INTSXP) +COL_MEANS_IMPL_RMNA(LGLSXP) +COL_MEANS_IMPL_RMNA(INTSXP) #undef COL_MEANS_IMPL_RMNA - + // ColMeans // Input with template parameter NA = false // ColMeansImpl<..., NA_RM = false> diff --git a/inst/tinytest/test_sugar.R b/inst/tinytest/test_sugar.R index f7d8a21b8..d81668c24 100644 --- a/inst/tinytest/test_sugar.R +++ b/inst/tinytest/test_sugar.R @@ -1189,6 +1189,11 @@ expect_equal(dbl_col_means(x, TRUE), colMeans(x, TRUE), info = "numeric / colMea ## {row,col}{Sums,Means} integer tests # test.sugar.rowMeans_integer <- function() { +x <- matrix(rep(.Machine$integer.max, 4), 2) + +expect_error(int_row_sums(x), "overflow") +expect_error(int_col_sums(x), "overflow") + x <- matrix(as.integer(rnorm(9) * 1e4), 3) expect_equal(int_row_sums(x), rowSums(x), info = "integer / rowSums / keep NA / clean input") From 638f329239a00978ecccbb215230f34c6da4c929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20=C3=9Acar?= Date: Thu, 26 Feb 2026 09:39:36 +0100 Subject: [PATCH 09/13] fix functions/sum.h --- inst/include/Rcpp/sugar/functions/sum.h | 7 ++++--- inst/tinytest/cpp/sugar.cpp | 7 ++++++- inst/tinytest/test_sugar.R | 13 +++++++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/inst/include/Rcpp/sugar/functions/sum.h b/inst/include/Rcpp/sugar/functions/sum.h index 601a65a73..fac5d10d4 100644 --- a/inst/include/Rcpp/sugar/functions/sum.h +++ b/inst/include/Rcpp/sugar/functions/sum.h @@ -2,7 +2,8 @@ // // sum.h: Rcpp R/C++ interface class library -- sum // -// Copyright (C) 2010 - 2011 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -42,7 +43,7 @@ class Sum : public Lazy< typename Rcpp::traits::storage_type::type , Sum< current = object[i] ; if( Rcpp::traits::is_na(current) ) return Rcpp::traits::get_na() ; - result += current ; + result = RCPP_SAFE_ADD(result, current); } return result ; } @@ -84,7 +85,7 @@ class Sum : public Lazy< typename Rcpp::traits::storage_type Date: Thu, 26 Feb 2026 12:21:31 +0100 Subject: [PATCH 10/13] fix operators/minus.h --- inst/include/Rcpp/sugar/operators/minus.h | 19 ++++++++++--------- inst/tinytest/cpp/sugar.cpp | 15 +++++++++++++++ inst/tinytest/test_sugar.R | 3 +++ 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/inst/include/Rcpp/sugar/operators/minus.h b/inst/include/Rcpp/sugar/operators/minus.h index f94061726..dcaefb8ea 100644 --- a/inst/include/Rcpp/sugar/operators/minus.h +++ b/inst/include/Rcpp/sugar/operators/minus.h @@ -2,7 +2,8 @@ // // minus.h: Rcpp R/C++ interface class library -- operator- // -// Copyright (C) 2010 - 2011 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -42,7 +43,7 @@ namespace sugar{ STORAGE x = lhs[i] ; if( Rcpp::traits::is_na( x ) ) return x ; STORAGE y = rhs[i] ; - return Rcpp::traits::is_na( y ) ? y : ( x - y ) ; + return Rcpp::traits::is_na( y ) ? y : RCPP_SAFE_SUB(x, y); } inline R_xlen_t size() const { return lhs.size() ; } @@ -91,7 +92,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { STORAGE y = rhs[i] ; if( Rcpp::traits::is_na( y ) ) return y ; - return lhs[i] - y ; + return RCPP_SAFE_SUB(lhs[i], y); } inline R_xlen_t size() const { return lhs.size() ; } @@ -140,7 +141,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { STORAGE x = lhs[i] ; if( Rcpp::traits::is_na( x ) ) return x ; - return x - rhs[i] ; + return RCPP_SAFE_SUB(x, rhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } @@ -188,7 +189,7 @@ namespace sugar{ lhs(lhs_.get_ref()), rhs(rhs_.get_ref()) {} inline STORAGE operator[]( R_xlen_t i ) const { - return lhs[i] - rhs[i] ; + return RCPP_SAFE_SUB(lhs[i], rhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } @@ -238,7 +239,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { if( rhs_na ) return rhs ; STORAGE x = lhs[i] ; - return Rcpp::traits::is_na(x) ? x : (x - rhs) ; + return Rcpp::traits::is_na(x) ? x : RCPP_SAFE_SUB(x, rhs); } inline R_xlen_t size() const { return lhs.size() ; } @@ -284,7 +285,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { if( rhs_na ) return rhs ; STORAGE x = lhs[i] ; - return Rcpp::traits::is_na(x) ? x : (x - rhs) ; + return Rcpp::traits::is_na(x) ? x : RCPP_SAFE_SUB(x, rhs); } inline R_xlen_t size() const { return lhs.size() ; } @@ -333,7 +334,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { if( lhs_na ) return lhs ; - return lhs - rhs[i] ; + return RCPP_SAFE_SUB(lhs, rhs[i]); } inline R_xlen_t size() const { return rhs.size() ; } @@ -377,7 +378,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { if( lhs_na ) return lhs ; - return lhs - rhs[i] ; + return RCPP_SAFE_SUB(lhs, rhs[i]); } inline R_xlen_t size() const { return rhs.size() ; } diff --git a/inst/tinytest/cpp/sugar.cpp b/inst/tinytest/cpp/sugar.cpp index 4bc8e11ed..bef86aaec 100644 --- a/inst/tinytest/cpp/sugar.cpp +++ b/inst/tinytest/cpp/sugar.cpp @@ -285,6 +285,21 @@ List runit_minus( IntegerVector xx ){ ) ; } +// [[Rcpp::export]] +IntegerVector runit_minus_ivv( IntegerVector x, IntegerVector y ){ + return x - y; +} + +// [[Rcpp::export]] +IntegerVector runit_minus_ivp( IntegerVector x, int y ){ + return x - y; +} + +// [[Rcpp::export]] +IntegerVector runit_minus_ipv( int x, IntegerVector y ){ + return x - y; +} + // [[Rcpp::export]] LogicalVector runit_any_equal_not( NumericVector xx, NumericVector yy){ return any( !( xx == yy) ) ; diff --git a/inst/tinytest/test_sugar.R b/inst/tinytest/test_sugar.R index cb6236e13..5089ea4b1 100644 --- a/inst/tinytest/test_sugar.R +++ b/inst/tinytest/test_sugar.R @@ -367,6 +367,9 @@ expect_equal( fx(1:10, 1:10*2) , mapply(seq, 1:10, 1:10*2) ) # test.sugar.minus <- function( ){ +expect_error(runit_minus_ivv(-.Machine$integer.max, 2), "overflow") +expect_error(runit_minus_ivp(-.Machine$integer.max, 2), "overflow") +expect_error(runit_minus_ipv(-.Machine$integer.max, 2), "overflow") fx <- runit_minus expect_equal(fx(1:10) , list( (1:10)-10L, 10L-(1:10), rep(0L,10), (1:10)-10L, 10L-(1:10) )) From 6e012f55a468b3c3b288df57d346b694697f9759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20=C3=9Acar?= Date: Thu, 26 Feb 2026 19:11:51 +0100 Subject: [PATCH 11/13] fix operators/plus.h --- inst/include/Rcpp/sugar/operators/minus.h | 4 ++-- inst/include/Rcpp/sugar/operators/plus.h | 20 +++++++++---------- inst/tinytest/cpp/sugar.cpp | 24 +++++++++++++++++++++++ inst/tinytest/test_sugar.R | 8 ++++++++ 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/inst/include/Rcpp/sugar/operators/minus.h b/inst/include/Rcpp/sugar/operators/minus.h index dcaefb8ea..a7a7ab8f1 100644 --- a/inst/include/Rcpp/sugar/operators/minus.h +++ b/inst/include/Rcpp/sugar/operators/minus.h @@ -371,14 +371,14 @@ namespace sugar{ public: typedef typename Rcpp::VectorBase VEC_TYPE ; typedef typename traits::storage_type::type STORAGE ; - typedef typename Rcpp::traits::Extractor::type VEC_EXT ; + typedef typename Rcpp::traits::Extractor::type VEC_EXT ; Minus_Primitive_Vector( STORAGE lhs_, const VEC_TYPE& rhs_ ) : lhs(lhs_), rhs(rhs_.get_ref()), lhs_na( Rcpp::traits::is_na(lhs_) ) {} inline STORAGE operator[]( R_xlen_t i ) const { if( lhs_na ) return lhs ; - return RCPP_SAFE_SUB(lhs, rhs[i]); + return lhs - rhs[i]; } inline R_xlen_t size() const { return rhs.size() ; } diff --git a/inst/include/Rcpp/sugar/operators/plus.h b/inst/include/Rcpp/sugar/operators/plus.h index 70f9f0e44..31a32ab92 100644 --- a/inst/include/Rcpp/sugar/operators/plus.h +++ b/inst/include/Rcpp/sugar/operators/plus.h @@ -2,7 +2,8 @@ // // plus.h: Rcpp R/C++ interface class library -- operator+ // -// Copyright (C) 2010 - 2011 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -42,7 +43,7 @@ namespace sugar{ STORAGE lhs_ = lhs[i] ; if( traits::is_na(lhs_) ) return lhs_ ; STORAGE rhs_ = rhs[i] ; - return traits::is_na(rhs_) ? rhs_ : (lhs_ + rhs_) ; + return traits::is_na(rhs_) ? rhs_ : RCPP_SAFE_ADD(lhs_, rhs_); } inline R_xlen_t size() const { return lhs.size() ; } @@ -99,7 +100,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { STORAGE rhs_ = rhs[i] ; if( traits::is_na(rhs_) ) return rhs_ ; - return lhs[i] + rhs_ ; + return RCPP_SAFE_ADD(lhs[i], rhs_); } inline R_xlen_t size() const { return lhs.size() ; } @@ -152,7 +153,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { STORAGE lhs_ = lhs[i] ; if( traits::is_na(lhs_) ) return lhs_ ; - return lhs_ + rhs[i] ; + return RCPP_SAFE_ADD(lhs_, rhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } @@ -204,7 +205,7 @@ namespace sugar{ lhs(lhs_.get_ref()), rhs(rhs_.get_ref()){} inline STORAGE operator[]( R_xlen_t i ) const { - return lhs[i] + rhs[i] ; + return RCPP_SAFE_ADD(lhs[i], rhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } @@ -250,7 +251,6 @@ namespace sugar{ public: typedef typename Rcpp::VectorBase VEC_TYPE ; typedef typename traits::storage_type::type STORAGE ; - typedef typename Rcpp::traits::Extractor< RTYPE, NA, T>::type EXT ; Plus_Vector_Primitive( const VEC_TYPE& lhs_, STORAGE rhs_ ) : @@ -260,7 +260,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { if( rhs_na ) return rhs ; STORAGE x = lhs[i] ; - return Rcpp::traits::is_na(x) ? x : (x + rhs) ; + return Rcpp::traits::is_na(x) ? x : RCPP_SAFE_ADD(x, rhs); } inline R_xlen_t size() const { return lhs.size() ; } @@ -308,7 +308,7 @@ namespace sugar{ lhs(lhs_.get_ref()), rhs(rhs_), rhs_na( Rcpp::traits::is_na(rhs_) ) {} inline STORAGE operator[]( R_xlen_t i ) const { - return rhs_na ? rhs : (rhs + lhs[i] ) ; + return rhs_na ? rhs : (rhs + lhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } @@ -360,7 +360,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { STORAGE x = lhs[i] ; - return Rcpp::traits::is_na(x) ? x : (x + rhs) ; + return Rcpp::traits::is_na(x) ? x : RCPP_SAFE_ADD(x, rhs); } inline R_xlen_t size() const { return lhs.size() ; } @@ -407,7 +407,7 @@ namespace sugar{ lhs(lhs_.get_ref()), rhs(rhs_) {} inline STORAGE operator[]( R_xlen_t i ) const { - return rhs + lhs[i] ; + return RCPP_SAFE_ADD(rhs, lhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } diff --git a/inst/tinytest/cpp/sugar.cpp b/inst/tinytest/cpp/sugar.cpp index bef86aaec..2b91b54ce 100644 --- a/inst/tinytest/cpp/sugar.cpp +++ b/inst/tinytest/cpp/sugar.cpp @@ -315,6 +315,21 @@ List runit_plus( IntegerVector xx ){ ) ; } +// [[Rcpp::export]] +IntegerVector runit_plus_ivv( IntegerVector x, IntegerVector y ){ + return x + y; +} + +// [[Rcpp::export]] +IntegerVector runit_plus_ivp( IntegerVector x, int y ){ + return x + y; +} + +// [[Rcpp::export]] +IntegerVector runit_plus_ipv( int x, IntegerVector y ){ + return x + y; +} + // [[Rcpp::export]] List runit_plus_seqlen(){ return List::create( @@ -324,6 +339,15 @@ List runit_plus_seqlen(){ ) ; } +// [[Rcpp::export]] +List runit_minus_seqlen(){ + return List::create( + seq_len(10) - 10, + 10 - seq_len(10), + seq_len(10) - seq_len(10) + ) ; +} + // [[Rcpp::export]] LogicalVector runit_plus_all( IntegerVector xx ){ return all( (xx+xx) < 10 ) ; diff --git a/inst/tinytest/test_sugar.R b/inst/tinytest/test_sugar.R index 5089ea4b1..105d40942 100644 --- a/inst/tinytest/test_sugar.R +++ b/inst/tinytest/test_sugar.R @@ -375,6 +375,11 @@ expect_equal(fx(1:10) , list( (1:10)-10L, 10L-(1:10), rep(0L,10), (1:10)-10L, 10L-(1:10) )) +# test.sugar.minus.seqlen <- function( ){ +fx <- runit_minus_seqlen +expect_equal( fx() , list( -9:0, 9:0, rep(0, 10)) ) + + # test.sugar.any.equal.not <- function( ){ fx <- runit_any_equal_not expect_true( ! fx( 1, 1 ) ) @@ -385,6 +390,9 @@ expect_true( is.na( fx( NA, 1 ) ) ) # test.sugar.plus <- function( ){ +expect_error(runit_plus_ivv(.Machine$integer.max, 2), "overflow") +expect_error(runit_plus_ivp(.Machine$integer.max, 2), "overflow") +expect_error(runit_plus_ipv(.Machine$integer.max, 2), "overflow") fx <- runit_plus expect_equal( fx(1:10) , list( 11:20,11:20,1:10+1:10, 3*(1:10)) ) From a8ce75798f5b2754007eeefb1ff4e4b36a6d196e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20=C3=9Acar?= Date: Thu, 26 Feb 2026 19:19:12 +0100 Subject: [PATCH 12/13] fix operators/times.h --- inst/include/Rcpp/sugar/operators/times.h | 19 +++++++++--------- inst/tinytest/cpp/sugar.cpp | 24 +++++++++++++++++++++++ inst/tinytest/test_sugar.R | 8 ++++++++ 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/inst/include/Rcpp/sugar/operators/times.h b/inst/include/Rcpp/sugar/operators/times.h index d1f335992..d172d7741 100644 --- a/inst/include/Rcpp/sugar/operators/times.h +++ b/inst/include/Rcpp/sugar/operators/times.h @@ -2,7 +2,8 @@ // // times.h: Rcpp R/C++ interface class library -- operator* // -// Copyright (C) 2010 - 2011 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2010 - 2025 Dirk Eddelbuettel and Romain Francois +// Copyright (C) 2026 Dirk Eddelbuettel, Romain Francois and Iñaki Ucar // // This file is part of Rcpp. // @@ -43,7 +44,7 @@ namespace sugar{ STORAGE lhs_ = lhs[i] ; if( traits::is_na(lhs_) ) return lhs_ ; STORAGE rhs_ = rhs[i] ; - return traits::is_na(rhs_) ? rhs_ : (lhs_ * rhs_) ; + return traits::is_na(rhs_) ? rhs_ : RCPP_SAFE_MUL(lhs_, rhs_) ; } inline R_xlen_t size() const { return lhs.size() ; } @@ -96,7 +97,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { STORAGE rhs_ = rhs[i] ; if( traits::is_na(rhs_) ) return rhs_ ; - return lhs[i] * rhs_ ; + return RCPP_SAFE_MUL(lhs[i], rhs_); } inline R_xlen_t size() const { return lhs.size() ; } @@ -148,7 +149,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { STORAGE lhs_ = lhs[i] ; if( traits::is_na(lhs_) ) return lhs_ ; - return lhs_ * rhs[i] ; + return RCPP_SAFE_MUL(lhs_, rhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } @@ -197,7 +198,7 @@ namespace sugar{ lhs(lhs_.get_ref()), rhs(rhs_.get_ref()){} inline STORAGE operator[]( R_xlen_t i ) const { - return lhs[i] * rhs[i] ; + return RCPP_SAFE_MUL(lhs[i], rhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } @@ -247,7 +248,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { if( rhs_na ) return rhs ; STORAGE x = lhs[i] ; - return Rcpp::traits::is_na(x) ? x : (x * rhs) ; + return Rcpp::traits::is_na(x) ? x : RCPP_SAFE_MUL(x, rhs) ; } inline R_xlen_t size() const { return lhs.size() ; } @@ -293,7 +294,7 @@ namespace sugar{ lhs(lhs_.get_ref()), rhs(rhs_), rhs_na( Rcpp::traits::is_na(rhs_) ) {} inline STORAGE operator[]( R_xlen_t i ) const { - return rhs_na ? rhs : (rhs * lhs[i] ) ; + return rhs_na ? rhs : (rhs * lhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } @@ -345,7 +346,7 @@ namespace sugar{ inline STORAGE operator[]( R_xlen_t i ) const { STORAGE x = lhs[i] ; - return Rcpp::traits::is_na(x) ? x : (x * rhs) ; + return Rcpp::traits::is_na(x) ? x : RCPP_SAFE_MUL(x, rhs); } inline R_xlen_t size() const { return lhs.size() ; } @@ -391,7 +392,7 @@ namespace sugar{ lhs(lhs_.get_ref()), rhs(rhs_) {} inline STORAGE operator[]( R_xlen_t i ) const { - return rhs * lhs[i] ; + return RCPP_SAFE_MUL(rhs, lhs[i]); } inline R_xlen_t size() const { return lhs.size() ; } diff --git a/inst/tinytest/cpp/sugar.cpp b/inst/tinytest/cpp/sugar.cpp index 2b91b54ce..19e771ef7 100644 --- a/inst/tinytest/cpp/sugar.cpp +++ b/inst/tinytest/cpp/sugar.cpp @@ -348,6 +348,15 @@ List runit_minus_seqlen(){ ) ; } +// [[Rcpp::export]] +List runit_times_seqlen(){ + return List::create( + seq_len(10) * 10, + 10 * seq_len(10), + seq_len(10) * seq_len(10) + ) ; +} + // [[Rcpp::export]] LogicalVector runit_plus_all( IntegerVector xx ){ return all( (xx+xx) < 10 ) ; @@ -467,6 +476,21 @@ List runit_times( IntegerVector xx ){ ) ; } +// [[Rcpp::export]] +IntegerVector runit_times_ivv( IntegerVector x, IntegerVector y ){ + return x * y; +} + +// [[Rcpp::export]] +IntegerVector runit_times_ivp( IntegerVector x, int y ){ + return x * y; +} + +// [[Rcpp::export]] +IntegerVector runit_times_ipv( int x, IntegerVector y ){ + return x * y; +} + // [[Rcpp::export]] List runit_divides( NumericVector xx ){ return List::create( diff --git a/inst/tinytest/test_sugar.R b/inst/tinytest/test_sugar.R index 105d40942..c623aefa1 100644 --- a/inst/tinytest/test_sugar.R +++ b/inst/tinytest/test_sugar.R @@ -485,12 +485,20 @@ expect_equal(fx( seq(-10, 10, length.out = 51), -25:25 ), # test.sugar.times <- function( ){ +expect_error(runit_times_ivv(.Machine$integer.max, 2), "overflow") +expect_error(runit_times_ivp(.Machine$integer.max, 2), "overflow") +expect_error(runit_times_ipv(.Machine$integer.max, 2), "overflow") fx <- runit_times expect_equal(fx(1:10) , list(10L*(1:10), 10L*(1:10), (1:10)*(1:10), (1:10)*(1:10)*(1:10), c(NA,(2:10)*(2:10)), c(NA,10L*(2:10)), c(NA,10L*(2:10)), rep( NA_integer_, 10L ))) +# test.sugar.times.seqlen <- function( ){ +fx <- runit_times_seqlen +expect_equal( fx() , list( seq(10, 100, 10), seq(10, 100, 10), 1:10*1:10) ) + + # test.sugar.divides <- function( ){ fx <- runit_divides expect_equal(fx(1:10) , From b06d8dc6647c76e1f5d802750a1adec0164a9fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20=C3=9Acar?= Date: Thu, 26 Feb 2026 19:25:12 +0100 Subject: [PATCH 13/13] update ChangeLog --- ChangeLog | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/ChangeLog b/ChangeLog index 183695dd7..43385e9d6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,23 @@ +2026-02-26 Iñaki Ucar + + * inst/include/Rcpp/sugar/tools/safe_math.h: New header implementing safe + versions of add/sub/mul operations for integral types + * inst/include/Rcpp/sugar/sugar.h: Includes the previous header + + * inst/include/Rcpp/sugar/functions/cumprod.h: Use the previous operations + * inst/include/Rcpp/sugar/functions/cumsum.h: Idem + * inst/include/Rcpp/sugar/functions/diff.h: Idem + * inst/include/Rcpp/sugar/functions/rowSums.h: Idem + * inst/include/Rcpp/sugar/functions/sum.h: Idem + * inst/include/Rcpp/sugar/operators/minus.h: Idem + * inst/include/Rcpp/sugar/operators/plus.h: Idem + * inst/include/Rcpp/sugar/operators/times.h: Idem + + * inst/tinytest/test_sugar.R: New tests covering the new operations + * inst/tinytest/cpp/sugar.cpp: Idem + * inst/tinytest/cpp/sugar_safe_math.cpp: Idem + * inst/tinytest/cpp/sugar_safe_math_fallback.cpp: Idem + 2026-02-17 Dirk Eddelbuettel * DESCRIPTION (Version, Date): Roll micro version and date