Skip to content

Commit d4cbe58

Browse files
committed
read subset elements before writing when both index the same vector
1 parent 32b70a4 commit d4cbe58

4 files changed

Lines changed: 55 additions & 6 deletions

File tree

‎inst/NEWS.Rd‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@
3030
\item Sugar expressions hold nested expressions by value so they can be
3131
stored, evaluate expressions that may read the target before assigning
3232
them in place, and signal an error for operands of different lengths
33-
(Kevin in \ghpr{1513} closing \ghit{1512})
33+
(Kevin in \ghpr{1513} closing \ghit{1512}). Same-length assignment
34+
of an expression that is not elementwise, such as \code{x = rev(x)},
35+
now goes through a temporary vector.
36+
\item Assignment between overlapping subsets of the same vector, as in
37+
\code{x[i] = x[j]}, reads the source elements before writing (Kevin in
38+
\ghpr{1513})
3439
\item The sugar operators \code{!}, \code{&&} and \code{||} on single
3540
logical results such as \code{all()} now compile, and follow R's
3641
handling of \code{NA} (Kevin in \ghpr{1513})

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,16 +110,28 @@ class SubsetProxy {
110110

111111
SubsetProxy& operator=(const SubsetProxy& other) {
112112
if (other.indices_n == 1) {
113+
// a single source element is only ever overwritten with itself
113114
for (R_xlen_t i=0; i < indices_n; ++i) {
114115
lhs[ indices[i] ] = other.lhs[other.indices[0]];
115116
}
116117
}
117-
else if (indices_n == other.indices_n) {
118-
for (R_xlen_t i=0; i < indices_n; ++i)
119-
lhs[ indices[i] ] = other.lhs[other.indices[i]];
118+
else if (indices_n != other.indices_n) {
119+
stop("index error");
120+
}
121+
else if (lhs.get__() == other.lhs.get__()) {
122+
// both subsets index the same vector, so a source element may
123+
// be overwritten before it is read; copy the source elements
124+
// first
125+
Vector<RTYPE, StoragePolicy> tmp = no_init(other.indices_n);
126+
for (R_xlen_t i=0; i < other.indices_n; ++i) {
127+
tmp[i] = other.lhs[other.indices[i]];
120128
}
129+
return *this = tmp;
130+
}
121131
else {
122-
stop("index error");
132+
for (R_xlen_t i=0; i < indices_n; ++i) {
133+
lhs[ indices[i] ] = other.lhs[other.indices[i]];
134+
}
123135
}
124136
return *this;
125137
}
@@ -238,7 +250,7 @@ class SubsetProxy {
238250
Vector<RTYPE, StoragePolicy> operator __OPERATOR__ ( \
239251
const SubsetProxy<RTYPE_OTHER, StoragePolicyOther, RHS_RTYPE_OTHER, \
240252
RHS_NA_OTHER, RHS_T_OTHER>& other) { \
241-
Vector<RTYPE, StoragePolicy> result(indices_n); \
253+
Vector<RTYPE, StoragePolicy> result = no_init(indices_n); \
242254
if (other.indices_n == 1) { \
243255
for (R_xlen_t i = 0; i < indices_n; ++i) \
244256
result[i] = lhs[indices[i]] __OPERATOR__ other.lhs[other.indices[0]]; \

‎inst/tinytest/cpp/Subset.cpp‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,31 @@ NumericVector subset_assign_vector_size_1(NumericVector x, int i) {
102102
return x;
103103
}
104104

105+
// [[Rcpp::export]]
106+
NumericVector subset_assign_alias(NumericVector x) {
107+
// overlapping subsets of the same vector
108+
x[IntegerVector::create(1, 2)] = x[IntegerVector::create(0, 1)];
109+
return x;
110+
}
111+
112+
// [[Rcpp::export]]
113+
CharacterVector subset_assign_alias_string(CharacterVector x) {
114+
x[IntegerVector::create(1, 2)] = x[IntegerVector::create(0, 1)];
115+
return x;
116+
}
117+
118+
// [[Rcpp::export]]
119+
NumericVector subset_assign_alias_disjoint(NumericVector x) {
120+
x[IntegerVector::create(0, 1)] = x[IntegerVector::create(2, 3)];
121+
return x;
122+
}
123+
124+
// [[Rcpp::export]]
125+
NumericVector subset_assign_alias_single(NumericVector x) {
126+
x[IntegerVector::create(0, 1, 2)] = x[IntegerVector::create(2)];
127+
return x;
128+
}
129+
105130
// [[Rcpp::export]]
106131
NumericVector subset_sugar_add(NumericVector x, IntegerVector y)
107132
{

‎inst/tinytest/test_subset.R‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ expect_error(subset_assign_subset5(1:6), info = "index error")
6565

6666
expect_identical(subset_assign_vector_size_1(1:6,7), c(7,7,7,4,5,6))
6767

68+
## assigning between subsets of the same vector reads every source element
69+
## before any is overwritten
70+
expect_identical(subset_assign_alias(c(1, 2, 3)), c(1, 1, 2), info = "x[1:2] = x[0:1]")
71+
expect_identical(subset_assign_alias_string(c("a", "b", "c")), c("a", "a", "b"), info = "x[1:2] = x[0:1], strings")
72+
expect_identical(subset_assign_alias_disjoint(c(1, 2, 3, 4)), c(3, 4, 3, 4), info = "x[0:1] = x[2:3]")
73+
expect_identical(subset_assign_alias_single(c(1, 2, 3)), c(3, 3, 3), info = "x[0:2] = x[2]")
74+
6875
x <- rnorm(10)
6976
y <- sample(10, 5)
7077
expect_identical(subset_sugar_add(x, y - 1L), x[y] + x[y])

0 commit comments

Comments
 (0)