Skip to content

Commit a447f30

Browse files
committed
keep vector and matrix layouts unchanged
Caching the length in proxy_cache and the column count in Matrix added data members, changing the layout of List, CharacterVector and Matrix. Packages pass these by reference across shared libraries compiled against different Rcpp versions: rust hands a List to revdbayes through an XPtr function pointer, and lite, threshr and evmissing reach the same path. In the 2026-10-06 reverse-dependency run revdbayes read a size that rust never wrote and failed name lookup with 'Index out of bounds'. ncol() is now derived from the cached length and nrow(), and proxy_cache reads the length on demand; only the cold warning helper and the cached atomic-vector length remain.
1 parent 7946442 commit a447f30

5 files changed

Lines changed: 40 additions & 29 deletions

File tree

‎ChangeLog‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
2026-09-30 Kevin Ushey <kevinushey@gmail.com>
22

33
* inst/include/Rcpp/vector/traits.h: Move the out-of-bounds warning into
4-
a cold, out-of-line helper; cache the vector length in proxy_cache
5-
* inst/include/Rcpp/vector/Vector.h: Return the cached length from
6-
size() and length() rather than calling Rf_xlength() each time
7-
* inst/include/Rcpp/vector/Matrix.h: Cache the number of columns
8-
* inst/include/Rcpp/vector/SubMatrix.h: Idem
4+
a cold, out-of-line helper; add get_size() to the vector caches
5+
* inst/include/Rcpp/vector/Vector.h: Return the length cached by
6+
r_vector_cache from size() and length() rather than calling
7+
Rf_xlength() each time
8+
* inst/include/Rcpp/vector/Matrix.h: Derive ncol() from the cached
9+
length and nrow() instead of reading the dim attribute; document that
10+
no data members may be added, as the layout is shared across packages
911

1012
2026-09-22 Iñaki Ucar <iucar@fedoraproject.org>
1113

‎inst/NEWS.Rd‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@
2727
warnings (Iñaki in \ghpr{1508} closing \ghit{1497})
2828
\item The \code{col} constructor now uses \code{nrow} in an
2929
initialization (Iñaki in \ghpr{1509})
30-
\item Bounds-checked element access is faster, and vector lengths and
31-
matrix column counts are now cached (Kevin in \ghpr{1511} closing
30+
\item Bounds-checked element access is faster, \code{size()} of
31+
atomic vectors uses the cached length, and \code{ncol()} no longer
32+
reads the \code{dim} attribute; object layouts are unchanged, as
33+
packages such as \pkg{rust} and \pkg{revdbayes} exchange Rcpp
34+
objects across shared libraries (Kevin in \ghpr{1511} closing
3235
\ghit{1510})
3336
}
3437
\item Changes in Rcpp Documentation:

‎inst/include/Rcpp/vector/Matrix.h‎

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@ namespace Rcpp{
2727

2828
template <int RTYPE, template <class> class StoragePolicy = PreserveStorage >
2929
class Matrix : public Vector<RTYPE, StoragePolicy>, public MatrixBase<RTYPE, true, Matrix<RTYPE,StoragePolicy> > {
30+
// The only data member. Packages pass Rcpp objects by reference across
31+
// shared-library boundaries (e.g. rust calls into revdbayes through XPtr
32+
// function pointers), and the two sides may have been compiled against
33+
// different Rcpp versions. Adding a member changes the layout they share.
3034
int nrows ;
31-
int ncols ;
3235

3336
public:
3437
using Vector<RTYPE, StoragePolicy>::size; // disambiguate diamond pattern for g++-6 and later
@@ -50,33 +53,33 @@ class Matrix : public Vector<RTYPE, StoragePolicy>, public MatrixBase<RTYPE, tru
5053
typedef typename VECTOR::Proxy Proxy ;
5154
typedef typename VECTOR::const_Proxy const_Proxy ;
5255

53-
Matrix() : VECTOR(Dimension(0, 0)), nrows(0), ncols(0) {}
56+
Matrix() : VECTOR(Dimension(0, 0)), nrows(0) {}
5457

55-
Matrix(SEXP x) : VECTOR(x), nrows( VECTOR::dims()[0] ), ncols( VECTOR::dims()[1] ) {}
58+
Matrix(SEXP x) : VECTOR(x), nrows( VECTOR::dims()[0] ) {}
5659

57-
Matrix( const Dimension& dims) : VECTOR( Rf_allocMatrix( RTYPE, dims[0], dims[1] ) ), nrows(dims[0]), ncols(dims[1]) {
60+
Matrix( const Dimension& dims) : VECTOR( Rf_allocMatrix( RTYPE, dims[0], dims[1] ) ), nrows(dims[0]) {
5861
if( dims.size() != 2 ) throw not_a_matrix();
5962
VECTOR::init() ;
6063
}
61-
Matrix( const int& nrows_, const int& ncols_) : VECTOR( Dimension( nrows_, ncols_ ) ),
62-
nrows(nrows_), ncols(ncols_)
64+
Matrix( const int& nrows_, const int& ncols) : VECTOR( Dimension( nrows_, ncols ) ),
65+
nrows(nrows_)
6366
{}
6467

6568
template <typename Iterator>
66-
Matrix( const int& nrows_, const int& ncols_, Iterator start ) :
67-
VECTOR( start, start + (static_cast<R_xlen_t>(nrows_)*ncols_) ),
68-
nrows(nrows_), ncols(ncols_)
69+
Matrix( const int& nrows_, const int& ncols, Iterator start ) :
70+
VECTOR( start, start + (static_cast<R_xlen_t>(nrows_)*ncols) ),
71+
nrows(nrows_)
6972
{
7073
VECTOR::attr( "dim" ) = Dimension( nrows, ncols ) ;
7174
}
7275

73-
Matrix( const int& n) : VECTOR( Dimension( n, n ) ), nrows(n), ncols(n) {}
76+
Matrix( const int& n) : VECTOR( Dimension( n, n ) ), nrows(n) {}
7477

7578

76-
Matrix( const Matrix& other) : VECTOR( other.get__() ), nrows(other.nrows), ncols(other.ncols) {}
79+
Matrix( const Matrix& other) : VECTOR( other.get__() ), nrows(other.nrows) {}
7780

7881
template <bool NA, typename MAT>
79-
Matrix( const MatrixBase<RTYPE,NA,MAT>& other ) : VECTOR( Rf_allocMatrix( RTYPE, static_cast<int>(other.nrow()), static_cast<int>(other.ncol()) ) ), nrows(static_cast<int>(other.nrow())), ncols(static_cast<int>(other.ncol())) {
82+
Matrix( const MatrixBase<RTYPE,NA,MAT>& other ) : VECTOR( Rf_allocMatrix( RTYPE, static_cast<int>(other.nrow()), static_cast<int>(other.ncol()) ) ), nrows(static_cast<int>(other.nrow())) {
8083
import_matrix_expression<NA,MAT>( other, nrows, ncol() ) ;
8184
}
8285

@@ -87,21 +90,23 @@ class Matrix : public Vector<RTYPE, StoragePolicy>, public MatrixBase<RTYPE, tru
8790
if( ! ::Rf_isMatrix(x) ) throw not_a_matrix();
8891
VECTOR::set__( x ) ;
8992
nrows = other.nrows ;
90-
ncols = other.ncols ;
9193
return *this ;
9294
}
9395
Matrix& operator=( const SubMatrix<RTYPE>& ) ;
9496

95-
explicit Matrix( const no_init_matrix& obj) : VECTOR(Rf_allocMatrix(RTYPE, obj.nrow(), obj.ncol())), nrows(obj.nrow()), ncols(obj.ncol()) {}
97+
explicit Matrix( const no_init_matrix& obj) : VECTOR(Rf_allocMatrix(RTYPE, obj.nrow(), obj.ncol())), nrows(obj.nrow()) {}
9698

99+
// A matrix has exactly nrow * ncol elements, so the column count follows
100+
// from the (cached) length without reading the dim attribute. Only an
101+
// empty matrix, which may still have columns, needs the attribute.
97102
inline int ncol() const {
98-
return ncols ;
103+
return nrows == 0 ? VECTOR::dims()[1] : static_cast<int>( VECTOR::size() / nrows ) ;
99104
}
100105
inline int nrow() const {
101106
return nrows ;
102107
}
103108
inline int cols() const {
104-
return ncols ;
109+
return ncol() ;
105110
}
106111
inline int rows() const {
107112
return nrows ;

‎inst/include/Rcpp/vector/SubMatrix.h‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class SubMatrix : public Rcpp::MatrixBase< RTYPE, true, SubMatrix<RTYPE> > {
5656
} ;
5757

5858
template <int RTYPE, template <class> class StoragePolicy >
59-
Matrix<RTYPE,StoragePolicy>::Matrix( const SubMatrix<RTYPE>& sub ) : VECTOR( Rf_allocMatrix( RTYPE, sub.nrow(), sub.ncol() )), nrows(sub.nrow()), ncols(sub.ncol()) {
59+
Matrix<RTYPE,StoragePolicy>::Matrix( const SubMatrix<RTYPE>& sub ) : VECTOR( Rf_allocMatrix( RTYPE, sub.nrow(), sub.ncol() )), nrows(sub.nrow()) {
6060
int nc = sub.ncol() ;
6161
iterator start = VECTOR::begin() ;
6262
iterator rhs_it ;
@@ -73,7 +73,6 @@ Matrix<RTYPE,StoragePolicy>& Matrix<RTYPE,StoragePolicy>::operator=( const SubMa
7373
int nc = sub.ncol(), nr = sub.nrow() ;
7474
if( nc != nrow() || nr != ncol() ){
7575
nrows = nr ;
76-
ncols = nc ;
7776
VECTOR::set__( Rf_allocMatrix( RTYPE, nr, nc ) ) ;
7877
}
7978
iterator start = VECTOR::begin() ;

‎inst/include/Rcpp/vector/traits.h‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,17 @@ namespace traits{
8484
typedef typename r_vector_proxy<RTYPE, StoragePolicy>::type proxy ;
8585
typedef typename r_vector_const_proxy<RTYPE, StoragePolicy>::type const_proxy ;
8686

87-
proxy_cache(): p(0), size(0){}
87+
proxy_cache(): p(0){}
8888
~proxy_cache(){}
8989
void update( const VECTOR& v ){
9090
p = const_cast<VECTOR*>(&v) ;
91-
size = ::Rf_xlength(v.get__());
9291
}
9392
inline iterator get() const { return iterator( proxy(*p, 0 ) ) ;}
9493
inline const_iterator get_const() const { return const_iterator( const_proxy(*p, 0) ) ; }
95-
inline R_xlen_t get_size() const { return size; }
94+
// Not cached: a size member here would change the layout of List and
95+
// CharacterVector, which packages pass by reference across
96+
// shared-library boundaries (see the note in Matrix.h).
97+
inline R_xlen_t get_size() const { return ::Rf_xlength(p->get__()); }
9698

9799
inline proxy ref() { check_index(0); return proxy(*p,0) ; }
98100
inline proxy ref(R_xlen_t i) { check_index(i); return proxy(*p,i);}
@@ -102,10 +104,10 @@ namespace traits{
102104

103105
private:
104106
VECTOR* p ;
105-
R_xlen_t size ;
106107

107108
void check_index(R_xlen_t i) const {
108109
#ifndef RCPP_NO_BOUNDS_CHECK
110+
R_xlen_t size = get_size() ;
109111
if (i >= size) {
110112
warn_index_out_of_bounds(i, size); // #nocov
111113
}

0 commit comments

Comments
 (0)