Skip to content

Commit 5614a8b

Browse files
add template specializations to avoid DATAPTR use (#1310)
* add template specializations to avoid DATAPTR use * also implement bounds checking in vector access * tweak error message * add tests; implement bounds check for proxy * finish NEWS * don't check index when constructing iterators * avoid invoking wrong 'get' * don't pass 'ncpus' * finish NEWS * test tweaks * Small robustification for package version check during dev cycle * use warning instead of stop * update NEWS * NEWS once more * fixup for check_indices in subsetter --------- Co-authored-by: Dirk Eddelbuettel <edd@debian.org>
1 parent 5207973 commit 5614a8b

File tree

10 files changed

+89
-23
lines changed

10 files changed

+89
-23
lines changed

ChangeLog

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
2024-06-11 Kevin Ushey <kevinushey@gmail.com>
2+
3+
* inst/include/Rcpp/internal/r_vector.h: Use template specializations to
4+
avoid DATAPTR usage
5+
* inst/include/Rcpp/vector/traits.h: Implement bounds checks in
6+
r_vector_cache access
7+
* inst/tinytest/cpp/Vector.cpp: Add unit tests
8+
* inst/tinytest/test_vector.R: Add unit tests
9+
* tests/tinytest.R: Test in serial by default
10+
111
2024-06-02 Dirk Eddelbuettel <edd@debian.org>
212

313
* inst/include/Rcpp/internal/export.h: More R_xlen_t switching

inst/NEWS.Rd

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@
99
\itemize{
1010
\item Set R_NO_REMAP if not already defined (Dirk in \ghpr{1296})
1111
\item Add variadic templates to be used instead of generated code
12-
(Andrew Johnson in \ghpr{1303}
12+
(Andrew Johnson in \ghpr{1303})
1313
\item Count variables were switches to \code{size_t} to avoid warnings
1414
about conversion-narrowing (Dirk in \ghpr{1307})
15+
\item Rcpp now avoids the usage of the (non-API) DATAPTR function when
16+
accessing the contents of Rcpp Vector objects where possible. (Kevin in
17+
\ghpr{1310})
18+
\item Rcpp now emits an R warning on out-of-bounds Vector accesses. This
19+
may become an error in a future Rcpp release. (Kevin in \ghpr{1310})
1520
}
1621
\item Changes in Rcpp Deployment:
1722
\itemize{

inst/include/Rcpp/internal/r_vector.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@ typename Rcpp::traits::storage_type<RTYPE>::type* r_vector_start(SEXP x) {
3232
return reinterpret_cast<pointer>(dataptr(x));
3333
}
3434

35+
// add specializations to avoid use of dataptr
36+
#define RCPP_VECTOR_START_IMPL(__RTYPE__, __ACCESSOR__) \
37+
template <> \
38+
inline typename Rcpp::traits::storage_type<__RTYPE__>::type* r_vector_start<__RTYPE__>(SEXP x) { \
39+
return __ACCESSOR__(x); \
40+
}
41+
42+
RCPP_VECTOR_START_IMPL(LGLSXP, LOGICAL);
43+
RCPP_VECTOR_START_IMPL(INTSXP, INTEGER);
44+
RCPP_VECTOR_START_IMPL(RAWSXP, RAW);
45+
RCPP_VECTOR_START_IMPL(CPLXSXP, COMPLEX);
46+
RCPP_VECTOR_START_IMPL(REALSXP, REAL);
47+
48+
#undef RCPP_VECTOR_START_IMPL
49+
3550
/**
3651
* The value 0 statically casted to the appropriate type for
3752
* the given SEXP type

inst/include/Rcpp/vector/Subsetter.h

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,9 @@ class SubsetProxy {
133133

134134
private:
135135

136-
#ifndef RCPP_NO_BOUNDS_CHECK
137136
template <typename IDX>
138137
void check_indices(IDX* x, R_xlen_t n, R_xlen_t size) {
138+
#ifndef RCPP_NO_BOUNDS_CHECK
139139
for (IDX i=0; i < n; ++i) {
140140
if (x[i] < 0 or x[i] >= size) {
141141
if(std::numeric_limits<IDX>::is_integer && size > std::numeric_limits<IDX>::max()) {
@@ -144,11 +144,8 @@ class SubsetProxy {
144144
stop("index error");
145145
}
146146
}
147+
#endif
147148
}
148-
#else
149-
template <typename IDX>
150-
void check_indices(IDX* x, IDX n, IDX size) {}
151-
#endif
152149

153150
void get_indices( traits::identity< traits::int2type<INTSXP> > t ) {
154151
indices.reserve(rhs_n);

inst/include/Rcpp/vector/traits.h

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,36 @@ namespace traits{
3535
typedef typename r_vector_const_proxy<RTYPE>::type const_proxy ;
3636
typedef typename storage_type<RTYPE>::type storage_type ;
3737

38-
r_vector_cache() : start(0){} ;
38+
r_vector_cache() : start(0), size(0) {} ;
39+
3940
inline void update( const VECTOR& v ) {
40-
start = ::Rcpp::internal::r_vector_start<RTYPE>(v) ;
41+
start = ::Rcpp::internal::r_vector_start<RTYPE>(v) ;
42+
size = v.size();
4143
}
44+
4245
inline iterator get() const { return start; }
4346
inline const_iterator get_const() const { return start; }
4447

45-
inline proxy ref() { return *start ;}
46-
inline proxy ref(R_xlen_t i) { return start[i] ; }
48+
inline proxy ref() { check_index(0); return start[0] ;}
49+
inline proxy ref(R_xlen_t i) { check_index(i); return start[i] ; }
50+
51+
inline proxy ref() const { check_index(0); return start[0] ;}
52+
inline proxy ref(R_xlen_t i) const { check_index(i); return start[i] ; }
4753

48-
inline proxy ref() const { return *start ;}
49-
inline proxy ref(R_xlen_t i) const { return start[i] ; }
54+
private:
5055

51-
private:
52-
iterator start ;
56+
void check_index(R_xlen_t i) const {
57+
#ifndef RCPP_NO_BOUNDS_CHECK
58+
if (i >= size) {
59+
warning("subscript out of bounds (index %s >= vector size %s)", i, size);
60+
}
61+
#endif
62+
}
63+
64+
iterator start ;
65+
R_xlen_t size ;
5366
} ;
67+
5468
template <int RTYPE, template <class> class StoragePolicy = PreserveStorage>
5569
class proxy_cache{
5670
public:
@@ -66,17 +80,24 @@ namespace traits{
6680
p = const_cast<VECTOR*>(&v) ;
6781
}
6882
inline iterator get() const { return iterator( proxy(*p, 0 ) ) ;}
69-
// inline const_iterator get_const() const { return const_iterator( *p ) ;}
7083
inline const_iterator get_const() const { return const_iterator( const_proxy(*p, 0) ) ; }
7184

72-
inline proxy ref() { return proxy(*p,0) ; }
73-
inline proxy ref(R_xlen_t i) { return proxy(*p,i);}
85+
inline proxy ref() { check_index(0); return proxy(*p,0) ; }
86+
inline proxy ref(R_xlen_t i) { check_index(i); return proxy(*p,i);}
7487

75-
inline const_proxy ref() const { return const_proxy(*p,0) ; }
76-
inline const_proxy ref(R_xlen_t i) const { return const_proxy(*p,i);}
88+
inline const_proxy ref() const { check_index(0); return const_proxy(*p,0) ; }
89+
inline const_proxy ref(R_xlen_t i) const { check_index(i); return const_proxy(*p,i);}
7790

7891
private:
7992
VECTOR* p ;
93+
94+
void check_index(R_xlen_t i) const {
95+
#ifndef RCPP_NO_BOUNDS_CHECK
96+
if (i >= p->size()) {
97+
warning("subscript out of bounds (index %s >= vector size %s)", i, p->size());
98+
}
99+
#endif
100+
}
80101
} ;
81102

82103
// regular types for INTSXP, REALSXP, ...

inst/tinytest/cpp/Vector.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,3 +886,13 @@ bool CharacterVector_test_equality_crosspolicy(CharacterVector x, Vector<STRSXP,
886886

887887
return std::equal(x.begin(), x.end(), y.begin());
888888
}
889+
890+
// [[Rcpp::export]]
891+
double NumericVector_test_out_of_bounds_read(NumericVector v, R_xlen_t i) {
892+
return v[i];
893+
}
894+
895+
// [[Rcpp::export]]
896+
SEXP CharacterVector_test_out_of_bounds_read(CharacterVector v, R_xlen_t i) {
897+
return v[i];
898+
}

inst/tinytest/test_packageversion.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
## Copyright (C) 2019 - 2022 Dirk Eddelbuettel
2+
## Copyright (C) 2019 - 2024 Dirk Eddelbuettel
33
##
44
## This file is part of Rcpp.
55
##
@@ -30,7 +30,7 @@ v <- as.integer(unlist(strsplit(pvstr, "\\.")))
3030
relstr <- as.character(as.package_version(paste(v[1:3], collapse=".")))
3131

3232
## call C++ function returning list of six values, three each for 'release' and 'dev' version
33-
res <- checkVersion(v)
33+
res <- checkVersion(v[1:min(4, length(v))])
3434

3535

3636
## basic check: is the #defined version equal to the computed version (issue #1014)

inst/tinytest/test_rcpp_package_skeleton.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ for (file in grep("RcppExports.R", R_files, invert=TRUE, value=TRUE)) {
6969
code <- readLines(file)
7070
fn <- eval(parse(text=paste(code, collapse="\n")))
7171
fn_name <- gsub(".*/(.*)\\.R$", "\\1", file)
72-
checkIdentical(fn, get(fn_name),
72+
checkIdentical(fn, base::get(fn_name),
7373
sprintf("we parsed the function '%s' correctly", fn_name)
7474
)
7575
}

inst/tinytest/test_vector.R

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,3 +694,11 @@ expect_equal(data, data2)
694694
# test.CharacterVector_test_equality <- function(){
695695
expect_true( !CharacterVector_test_equality("foo", "bar") )
696696
expect_true( !CharacterVector_test_equality_crosspolicy("foo", "bar") )
697+
698+
# https://github.com/RcppCore/Rcpp/issues/1308
699+
# tests disabled since these could trigger UBSAN warnings / crashes
700+
#expect_warning(NumericVector_test_out_of_bounds_read(numeric(0), 0))
701+
#expect_warning(NumericVector_test_out_of_bounds_read(numeric(1), 1))
702+
#expect_warning(CharacterVector_test_out_of_bounds_read(character(0), 0))
703+
#expect_warning(CharacterVector_test_out_of_bounds_read(character(1), 1))
704+

tests/tinytest.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,5 @@ if (requireNamespace("tinytest", quietly=TRUE)) {
4343
## there are several more granular ways to test files in a tinytest directory,
4444
## see its package vignette; tests can also run once the package is installed
4545
## using the same command `test_package(pkgName)`, or by director or file
46-
tinytest::test_package("Rcpp", ncpu=getOption("Ncpus", 1))
46+
tinytest::test_package("Rcpp")
4747
}

0 commit comments

Comments
 (0)