Skip to content

Commit 4dc7bb8

Browse files
author
Pavel Marek
committed
testrffi: Add some tests for CHARSXPs
(cherry picked from commit 8974b93)
1 parent b69fc3b commit 4dc7bb8

File tree

6 files changed

+287
-112
lines changed

6 files changed

+287
-112
lines changed
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*
2+
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 3 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 3 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 3 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
#include "charsxps.h"
25+
#include <string.h>
26+
#include <math.h>
27+
28+
static void assert_same_str(const char *actual, const char *expected);
29+
static void charsxp_same_ptrs_test();
30+
static void reorder_via_stringelt_test();
31+
static void reorder_via_dataptr_test();
32+
static void set_via_dataptr_test();
33+
static void get_via_dataptr_test();
34+
35+
/**
36+
* Replaces an n-th string in place from given character vector with `replacement`.
37+
* Is a wrapper for SET_STRING_ELT
38+
* @param n Index of the string to replace.
39+
* @param replacement Replacement for the string.
40+
* @returns New string vector with the replacement.
41+
*/
42+
SEXP charsxp_replace_nth_str(SEXP str, SEXP n, SEXP replacement) {
43+
if (TYPEOF(str) != STRSXP || LENGTH(str) == 0) {
44+
error("`str` expected STRSXP type with length greater than zero");
45+
}
46+
if (TYPEOF(n) != INTSXP || LENGTH(n) != 1) {
47+
error("`n` expected integer of length 1");
48+
}
49+
if (TYPEOF(replacement) != STRSXP || LENGTH(replacement) != 1) {
50+
error("`replacement` expected STRSXP of length 1");
51+
}
52+
const char *replacement_char = CHAR(STRING_ELT(replacement, 0));
53+
int idx = INTEGER_ELT(n, 0);
54+
if (LENGTH(str) < idx) {
55+
error("Trying to replace a string outside of bounds");
56+
}
57+
for (int i = 0; i < LENGTH(str); i++) {
58+
if (i == idx) {
59+
SET_STRING_ELT(str, i, mkChar(replacement_char));
60+
}
61+
}
62+
return str;
63+
}
64+
65+
/**
66+
* A wrapper for STRING_ELT.
67+
*/
68+
SEXP charsxp_nth_str(SEXP str, SEXP n) {
69+
int idx = INTEGER_ELT(n, 0);
70+
return ScalarString(STRING_ELT(str, idx));
71+
}
72+
73+
/**
74+
* Creates a native empty character vector. For the purpose of demonstration that we
75+
* can create a native character vector, and then modify it in R code.
76+
*/
77+
SEXP charsxp_create_empty_str(SEXP n) {
78+
int n_int = INTEGER_ELT(n, 0);
79+
SEXP str = PROTECT(allocVector(STRSXP, n_int));
80+
for (int i = 0; i < n_int; i++) {
81+
SET_STRING_ELT(str, i, mkChar(""));
82+
}
83+
UNPROTECT(1);
84+
return str;
85+
}
86+
87+
/**
88+
* Reverts a character vector in place via STRING_ELT API.
89+
*/
90+
SEXP charsxp_revert_via_elt(SEXP str) {
91+
int len = LENGTH(str);
92+
int half = (int) ceil(len / 2);
93+
for (int first_idx = 0; first_idx < half; first_idx++) {
94+
int second_idx = len - first_idx - 1;
95+
SEXP first_elem = STRING_ELT(str, first_idx);
96+
SET_STRING_ELT(str, first_idx, STRING_ELT(str, second_idx));
97+
SET_STRING_ELT(str, second_idx, first_elem);
98+
}
99+
return str;
100+
}
101+
102+
/**
103+
* Reverts a character vector in place via DATAPTR.
104+
*/
105+
SEXP charsxp_revert_via_dataptr(SEXP str) {
106+
int len = LENGTH(str);
107+
int half = (int) ceil(len / 2);
108+
SEXP *dataptr = (SEXP *) DATAPTR(str);
109+
for (int first_idx = 0; first_idx < half; first_idx++) {
110+
int second_idx = len - first_idx - 1;
111+
SEXP first_elem = dataptr[first_idx];
112+
dataptr[first_idx] = dataptr[second_idx];
113+
dataptr[second_idx] = first_elem;
114+
}
115+
return str;
116+
}
117+
118+
/**
119+
* Runs all other native tests
120+
*/
121+
SEXP charsxp_tests() {
122+
charsxp_same_ptrs_test();
123+
reorder_via_stringelt_test();
124+
reorder_via_dataptr_test();
125+
set_via_dataptr_test();
126+
get_via_dataptr_test();
127+
return R_NilValue;
128+
}
129+
130+
static void assert_same_str(const char *actual, const char *expected) {
131+
if (strcmp(actual, expected) != 0) {
132+
error("Strings are different: actual:'%s', expected:'%s'", actual, expected);
133+
}
134+
}
135+
136+
/**
137+
* CHARSXP SEXP types are compared with equality operator.
138+
*/
139+
static void charsxp_same_ptrs_test() {
140+
SEXP str = PROTECT(allocVector(STRSXP, 1));
141+
SEXP elem = mkChar("Hello");
142+
SET_STRING_ELT(str, 0, elem);
143+
SEXP elem_from_elt = STRING_ELT(str, 0);
144+
if (elem != elem_from_elt) {
145+
error("elem != elem_from_elt");
146+
}
147+
UNPROTECT(1);
148+
}
149+
150+
/**
151+
* Reorder the elements of the character vector via STRING_ELT API.
152+
*/
153+
static void reorder_via_stringelt_test() {
154+
// Reorder the elements of the character vector via STRING_ELT API.
155+
SEXP str = PROTECT(allocVector(STRSXP, 3));
156+
// We do not protect CHARSXP elements on purpose.
157+
SEXP first_elem = mkChar("One");
158+
SET_STRING_ELT(str, 0, first_elem);
159+
SEXP second_elem = mkChar("Two");
160+
SET_STRING_ELT(str, 1, second_elem);
161+
SEXP third_elem = mkChar("Three");
162+
SET_STRING_ELT(str, 2, third_elem);
163+
164+
// Check that the character vector is correctly initialized.
165+
assert_same_str(CHAR(STRING_ELT(str, 0)), CHAR(first_elem));
166+
assert_same_str(CHAR(STRING_ELT(str, 1)), CHAR(second_elem));
167+
assert_same_str(CHAR(STRING_ELT(str, 2)), CHAR(third_elem));
168+
169+
// Reorder
170+
SET_STRING_ELT(str, 0, third_elem);
171+
SET_STRING_ELT(str, 2, first_elem);
172+
173+
// Check that the character vector was correctly reordered.
174+
assert_same_str(CHAR(STRING_ELT(str, 0)), CHAR(third_elem));
175+
assert_same_str(CHAR(STRING_ELT(str, 1)), CHAR(second_elem));
176+
assert_same_str(CHAR(STRING_ELT(str, 2)), CHAR(first_elem));
177+
178+
UNPROTECT(1);
179+
}
180+
181+
/**
182+
* Reorder the elements of the character vector via DATAPTR.
183+
* Currently, we know only data.table package that does this.
184+
*/
185+
static void reorder_via_dataptr_test() {
186+
SEXP str = PROTECT(allocVector(STRSXP, 3));
187+
SEXP first_elem = mkChar("One");
188+
SET_STRING_ELT(str, 0, first_elem);
189+
SEXP second_elem = mkChar("Two");
190+
SET_STRING_ELT(str, 1, second_elem);
191+
SEXP third_elem = mkChar("Three");
192+
SET_STRING_ELT(str, 2, third_elem);
193+
194+
// Check that the character vector is correctly initialized.
195+
assert_same_str(CHAR(STRING_ELT(str, 0)), "One");
196+
assert_same_str(CHAR(STRING_ELT(str, 1)), "Two");
197+
assert_same_str(CHAR(STRING_ELT(str, 2)), "Three");
198+
199+
// Reorder via DATAPTR.
200+
SEXP *dataptr = (SEXP *) DATAPTR(str);
201+
dataptr[0] = third_elem;
202+
dataptr[2] = first_elem;
203+
204+
// Check (via STRING_ELT) that the character vector was correctly reordered.
205+
assert_same_str(CHAR(STRING_ELT(str, 0)), "Three");
206+
assert_same_str(CHAR(STRING_ELT(str, 1)), "Two");
207+
assert_same_str(CHAR(STRING_ELT(str, 2)), "One");
208+
209+
UNPROTECT(1);
210+
}
211+
212+
/**
213+
* Run with gctorture
214+
*/
215+
static void set_via_dataptr_test() {
216+
SEXP str = PROTECT(allocVector(STRSXP, 2));
217+
SEXP *dataptr = (SEXP *) DATAPTR(str);
218+
// Not protected on purpose.
219+
dataptr[0] = mkChar("One");
220+
// `dataptr[0]` must not be collected here, as it is referenced by `str`.
221+
dataptr[1] = mkChar("Two");
222+
assert_same_str(CHAR(STRING_ELT(str, 0)), "One");
223+
assert_same_str(CHAR(STRING_ELT(str, 1)), "Two");
224+
UNPROTECT(1);
225+
}
226+
227+
static void get_via_dataptr_test() {
228+
SEXP str = PROTECT(allocVector(STRSXP, 1));
229+
// Get the dataptr before we set the values.
230+
SEXP *dataptr = (SEXP *) DATAPTR(str);
231+
SET_STRING_ELT(str, 0, mkChar("foo"));
232+
assert_same_str(CHAR(dataptr[0]), "foo");
233+
UNPROTECT(1);
234+
}
235+

com.oracle.truffle.r.test.native/packages/testrffi/testrffi/src/strings.h renamed to com.oracle.truffle.r.test.native/packages/testrffi/testrffi/src/charsxps.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
*/
2323
#include <Rinternals.h>
2424

25-
SEXP replace_nth_str(SEXP str, SEXP n, SEXP replacement);
26-
SEXP nth_str(SEXP str, SEXP n);
27-
SEXP create_empty_str(SEXP n);
28-
SEXP str_tests();
25+
SEXP charsxp_replace_nth_str(SEXP str, SEXP n, SEXP replacement);
26+
SEXP charsxp_nth_str(SEXP str, SEXP n);
27+
SEXP charsxp_create_empty_str(SEXP n);
28+
SEXP charsxp_revert_via_elt(SEXP str);
29+
SEXP charsxp_revert_via_dataptr(SEXP str);
30+
SEXP charsxp_tests();

com.oracle.truffle.r.test.native/packages/testrffi/testrffi/src/init.c

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
#include <R_ext/Rdynload.h>
2727
#include "testrffi.h"
2828
#include "serialization.h"
29-
#include "strings.h"
29+
#include "charsxps.h"
3030
#include "rapi_helpers.h"
3131
#include "rffiwrappers.h"
3232

@@ -128,10 +128,12 @@ static const R_CallMethodDef CallEntries[] = {
128128
CALLDEF(testPRIMFUN, 2),
129129
CALLDEF(serialize, 1),
130130
CALLDEF(testInstallTrChar, 2),
131-
CALLDEF(replace_nth_str, 3),
132-
CALLDEF(nth_str, 2),
133-
CALLDEF(create_empty_str, 1),
134-
CALLDEF(str_tests, 0),
131+
CALLDEF(charsxp_replace_nth_str, 3),
132+
CALLDEF(charsxp_nth_str, 2),
133+
CALLDEF(charsxp_create_empty_str, 1),
134+
CALLDEF(charsxp_revert_via_elt, 1),
135+
CALLDEF(charsxp_revert_via_dataptr, 1),
136+
CALLDEF(charsxp_tests, 0),
135137
#include "init_api.h"
136138
{NULL, NULL, 0}
137139
};

com.oracle.truffle.r.test.native/packages/testrffi/testrffi/src/strings.c

Lines changed: 0 additions & 94 deletions
This file was deleted.

com.oracle.truffle.r.test.native/packages/testrffi/testrffi/src/testrffi.c

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,18 +1186,19 @@ SEXP test_RfMatch(SEXP x, SEXP y) {
11861186
}
11871187

11881188
/**
1189-
* Values returned by Rf_mkChar should not be garbage-collected even if they are not
1190-
* protected. This behavior is assumed in, e.g., vctrs package version 0.3.6.
1191-
* Make sure this test runs with `gctorture()`.
1192-
*/
1189+
* Values returned by Rf_mkChar must not be garbage-collected when they are referenced
1190+
* from some STRSXP vector.
1191+
* This behavior is assumed in, e.g., vctrs package version 0.3.6.
1192+
* Make sure this test runs with `gctorture()`.
1193+
*/
11931194
SEXP test_mkCharDoesNotCollect() {
11941195
SEXP string_one = PROTECT(allocVector(STRSXP, 1));
11951196
SEXP char_sxp = mkChar("XX_YY");
11961197
// Should be OK, char_sxp cannot be collected yet
11971198
SET_STRING_ELT(string_one, 0, char_sxp);
11981199
// char_sxp should be transitivelly referenced from GC root
11991200

1200-
// char_sxp can be potentially collected here.
1201+
// char_sxp must not be collected here.
12011202
SEXP string_two = PROTECT(allocVector(STRSXP, 1));
12021203
// If char_sxp is collected, the following statement throws an error.
12031204
SET_STRING_ELT(string_two, 0, char_sxp);

0 commit comments

Comments
 (0)