Skip to content

Commit d620bdd

Browse files
cottsayahcordeKarsten1987
authored
Add rcutils_string_array_resize function (#247)
This change adds a new function to modify the size of an array which is already allocated, preserving existing entries where possible. Signed-off-by: Scott K Logan <logans@cottsay.net> Co-authored-by: Alejandro Hernández Cordero <alejandro@openrobotics.org> Co-authored-by: Karsten Knese <Karsten1987@users.noreply.github.com>
1 parent 1f9784e commit d620bdd

File tree

3 files changed

+190
-1
lines changed

3 files changed

+190
-1
lines changed

include/rcutils/types/string_array.h

+29
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,35 @@ rcutils_string_array_cmp(
133133
const rcutils_string_array_t * rhs,
134134
int * res);
135135

136+
/// Resize a string array, reclaiming removed resources.
137+
/**
138+
* This function changes the size of an existing string array.
139+
* If the new size is larger, new entries are added to the end of the array and
140+
* are zero- initialized.
141+
* If the new size is smaller, entries are removed from the end of the array
142+
* and their resources reclaimed.
143+
*
144+
* \par Note:
145+
* Resizing to 0 is not a substitute for calling ::rcutils_string_array_fini.
146+
*
147+
* \par Note:
148+
* If this function fails, \p string_array remains unchanged and should still
149+
* be reclaimed with ::rcutils_string_array_fini.
150+
*
151+
* \param[inout] string_array object to be resized.
152+
* \param[in] new_size the size the array should be changed to.
153+
* \return `RCUTILS_RET_OK` if successful, or
154+
* \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or
155+
* \return `RCUTILS_RET_BAD_ALLOC` if memory allocation fails, or
156+
* \return `RCUTILS_RET_ERROR` if an unknown error occurs.
157+
*/
158+
RCUTILS_PUBLIC
159+
RCUTILS_WARN_UNUSED
160+
rcutils_ret_t
161+
rcutils_string_array_resize(
162+
rcutils_string_array_t * string_array,
163+
size_t new_size);
164+
136165
#ifdef __cplusplus
137166
}
138167
#endif

src/string_array.c

+58-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ rcutils_string_array_init(
5353
string_array->size = size;
5454
string_array->data = allocator->zero_allocate(size, sizeof(char *), allocator->state);
5555
if (NULL == string_array->data && 0 != size) {
56-
RCUTILS_SET_ERROR_MSG("failed to allocator string array");
56+
RCUTILS_SET_ERROR_MSG("failed to allocate string array");
5757
return RCUTILS_RET_BAD_ALLOC;
5858
}
5959
string_array->allocator = *allocator;
@@ -84,6 +84,7 @@ rcutils_string_array_fini(rcutils_string_array_t * string_array)
8484
}
8585
allocator->deallocate(string_array->data, allocator->state);
8686
string_array->data = NULL;
87+
string_array->size = 0;
8788

8889
return RCUTILS_RET_OK;
8990
}
@@ -136,6 +137,62 @@ rcutils_string_array_cmp(
136137
return RCUTILS_RET_OK;
137138
}
138139

140+
rcutils_ret_t
141+
rcutils_string_array_resize(
142+
rcutils_string_array_t * string_array,
143+
size_t new_size)
144+
{
145+
RCUTILS_CHECK_FOR_NULL_WITH_MSG(
146+
string_array, "string_array is null", return RCUTILS_RET_INVALID_ARGUMENT);
147+
148+
if (string_array->size == new_size) {
149+
return RCUTILS_RET_OK;
150+
}
151+
152+
rcutils_allocator_t * allocator = &string_array->allocator;
153+
RCUTILS_CHECK_ALLOCATOR_WITH_MSG(
154+
allocator, "allocator is invalid", return RCUTILS_RET_INVALID_ARGUMENT);
155+
156+
// Stash entries being removed
157+
rcutils_string_array_t to_reclaim = rcutils_get_zero_initialized_string_array();
158+
if (new_size < string_array->size) {
159+
size_t num_removed = string_array->size - new_size;
160+
rcutils_ret_t ret = rcutils_string_array_init(&to_reclaim, num_removed, allocator);
161+
if (RCUTILS_RET_OK != ret) {
162+
// rcutils_string_array_init should have already set an error message
163+
return ret;
164+
}
165+
memcpy(
166+
to_reclaim.data, &string_array->data[new_size],
167+
to_reclaim.size * sizeof(char *));
168+
}
169+
170+
char ** new_data = allocator->reallocate(
171+
string_array->data, new_size * sizeof(char *), allocator->state);
172+
if (NULL == new_data && 0 != new_size) {
173+
RCUTILS_SET_ERROR_MSG("failed to allocate string array");
174+
for (size_t i = 0; i < to_reclaim.size; ++i) {
175+
to_reclaim.data[i] = NULL;
176+
}
177+
rcutils_ret_t ret = rcutils_string_array_fini(&to_reclaim);
178+
if (RCUTILS_RET_OK != ret) {
179+
RCUTILS_SET_ERROR_MSG("memory was leaked during error handling");
180+
}
181+
return RCUTILS_RET_BAD_ALLOC;
182+
}
183+
string_array->data = new_data;
184+
185+
// Zero-initialize new entries
186+
for (size_t i = string_array->size; i < new_size; ++i) {
187+
string_array->data[i] = NULL;
188+
}
189+
190+
string_array->size = new_size;
191+
192+
// Lastly, reclaim removed entries
193+
return rcutils_string_array_fini(&to_reclaim);
194+
}
195+
139196
#ifdef __cplusplus
140197
}
141198
#endif

test/test_string_array.cpp

+103
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "gtest/gtest.h"
1616

1717
#include "./allocator_testing_utils.h"
18+
#include "./time_bomb_allocator_testing_utils.h"
1819
#include "rcutils/error_handling.h"
1920
#include "rcutils/types/string_array.h"
2021

@@ -153,3 +154,105 @@ TEST(test_string_array, string_array_cmp) {
153154
ret = rcutils_string_array_fini(&incomplete_string_array);
154155
ASSERT_EQ(RCUTILS_RET_OK, ret);
155156
}
157+
158+
TEST(test_string_array, string_array_resize) {
159+
auto allocator = rcutils_get_default_allocator();
160+
auto failing_allocator = get_failing_allocator();
161+
auto invalid_allocator = rcutils_get_zero_initialized_allocator();
162+
auto time_bomb_allocator = get_time_bomb_allocator();
163+
rcutils_ret_t ret;
164+
165+
ret = rcutils_string_array_resize(nullptr, 8);
166+
ASSERT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret);
167+
rcutils_reset_error();
168+
169+
// Start with 8 elements
170+
rcutils_string_array_t sa0;
171+
ret = rcutils_string_array_init(&sa0, 8, &allocator);
172+
ASSERT_EQ(RCUTILS_RET_OK, ret);
173+
174+
for (size_t i = 0; i < sa0.size; i++) {
175+
const char val[] = {static_cast<char>('a' + i), '\0'};
176+
sa0.data[i] = strdup(val);
177+
}
178+
179+
// Resize to same size (hot path)
180+
ret = rcutils_string_array_resize(&sa0, sa0.size);
181+
ASSERT_EQ(RCUTILS_RET_OK, ret);
182+
183+
// Grow to 16 (with allocation failure)
184+
sa0.allocator = failing_allocator;
185+
ret = rcutils_string_array_resize(&sa0, 16);
186+
EXPECT_EQ(RCUTILS_RET_BAD_ALLOC, ret);
187+
EXPECT_EQ(8u, sa0.size);
188+
rcutils_reset_error();
189+
190+
// Grow to 16 (with invalid allocator)
191+
sa0.allocator = invalid_allocator;
192+
ret = rcutils_string_array_resize(&sa0, 16);
193+
EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret);
194+
EXPECT_EQ(8u, sa0.size);
195+
rcutils_reset_error();
196+
197+
// Grow to 16
198+
sa0.allocator = allocator;
199+
ret = rcutils_string_array_resize(&sa0, 16);
200+
ASSERT_EQ(RCUTILS_RET_OK, ret);
201+
ASSERT_EQ(16u, sa0.size);
202+
203+
// Check that existing data is intact
204+
for (size_t i = 0; i < 8; i++) {
205+
const char val[] = {static_cast<char>('a' + i), '\0'};
206+
EXPECT_STREQ(val, sa0.data[i]);
207+
}
208+
209+
// Check that new elements are empty
210+
for (size_t i = 8; i < sa0.size; i++) {
211+
const char val[] = {static_cast<char>('a' + i), '\0'};
212+
EXPECT_STREQ(nullptr, sa0.data[i]);
213+
sa0.data[i] = strdup(val);
214+
}
215+
216+
// Shrink to 4 (with allocation failure)
217+
sa0.allocator = failing_allocator;
218+
ret = rcutils_string_array_resize(&sa0, 4);
219+
EXPECT_EQ(RCUTILS_RET_BAD_ALLOC, ret);
220+
EXPECT_EQ(16u, sa0.size);
221+
rcutils_reset_error();
222+
223+
// Shrink to 4 (with delayed allocation failure)
224+
set_time_bomb_allocator_realloc_count(time_bomb_allocator, 0);
225+
sa0.allocator = time_bomb_allocator;
226+
ret = rcutils_string_array_resize(&sa0, 4);
227+
EXPECT_EQ(RCUTILS_RET_BAD_ALLOC, ret);
228+
EXPECT_EQ(16u, sa0.size);
229+
rcutils_reset_error();
230+
231+
// Shrink to 4 (with invalid allocator)
232+
sa0.allocator = invalid_allocator;
233+
ret = rcutils_string_array_resize(&sa0, 4);
234+
EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret);
235+
EXPECT_EQ(16u, sa0.size);
236+
rcutils_reset_error();
237+
238+
// Shrink to 4
239+
sa0.allocator = allocator;
240+
ret = rcutils_string_array_resize(&sa0, 4);
241+
ASSERT_EQ(RCUTILS_RET_OK, ret);
242+
ASSERT_EQ(4u, sa0.size);
243+
244+
// Check that existing data is intact
245+
for (size_t i = 0; i < sa0.size; i++) {
246+
const char val[] = {static_cast<char>('a' + i), '\0'};
247+
EXPECT_STREQ(val, sa0.data[i]);
248+
}
249+
250+
// Shrink to 0
251+
ret = rcutils_string_array_resize(&sa0, 0);
252+
EXPECT_EQ(RCUTILS_RET_OK, ret);
253+
EXPECT_EQ(0u, sa0.size);
254+
255+
sa0.allocator = allocator;
256+
ret = rcutils_string_array_fini(&sa0);
257+
ASSERT_EQ(RCUTILS_RET_OK, ret);
258+
}

0 commit comments

Comments
 (0)