Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions include/rcutils/types/string_array.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,35 @@ rcutils_string_array_cmp(
const rcutils_string_array_t * rhs,
int * res);

/// Resize a string array, reclaiming removed resources.
/**
* This function changes the size of an existing string array.
* If the new size is larger, new entries are added to the end of the array and
* are zero- initialized.
* If the new size is smaller, entries are removed from the end of the array
* and their resources reclaimed.
*
* \par Note:
* Resizing to 0 is not a substitute for calling ::rcutils_string_array_fini.
*
* \par Note:
* If this function fails, \p string_array remains unchanged and should still
* be reclaimed with ::rcutils_string_array_fini.
*
* \param[inout] string_array object to be resized.
* \param[in] new_size the size the array should be changed to.
* \return `RCUTILS_RET_OK` if successful, or
* \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or
* \return `RCUTILS_RET_BAD_ALLOC` if memory allocation fails, or
* \return `RCUTILS_RET_ERROR` if an unknown error occurs.
*/
RCUTILS_PUBLIC
RCUTILS_WARN_UNUSED
rcutils_ret_t
rcutils_string_array_resize(
rcutils_string_array_t * string_array,
size_t new_size);

#ifdef __cplusplus
}
#endif
Expand Down
59 changes: 58 additions & 1 deletion src/string_array.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ rcutils_string_array_init(
string_array->size = size;
string_array->data = allocator->zero_allocate(size, sizeof(char *), allocator->state);
if (NULL == string_array->data && 0 != size) {
RCUTILS_SET_ERROR_MSG("failed to allocator string array");
RCUTILS_SET_ERROR_MSG("failed to allocate string array");
return RCUTILS_RET_BAD_ALLOC;
}
string_array->allocator = *allocator;
Expand Down Expand Up @@ -84,6 +84,7 @@ rcutils_string_array_fini(rcutils_string_array_t * string_array)
}
allocator->deallocate(string_array->data, allocator->state);
string_array->data = NULL;
string_array->size = 0;

return RCUTILS_RET_OK;
}
Expand Down Expand Up @@ -136,6 +137,62 @@ rcutils_string_array_cmp(
return RCUTILS_RET_OK;
}

rcutils_ret_t
rcutils_string_array_resize(
rcutils_string_array_t * string_array,
size_t new_size)
{
RCUTILS_CHECK_FOR_NULL_WITH_MSG(
string_array, "string_array is null", return RCUTILS_RET_INVALID_ARGUMENT);

if (string_array->size == new_size) {
return RCUTILS_RET_OK;
}

rcutils_allocator_t * allocator = &string_array->allocator;
RCUTILS_CHECK_ALLOCATOR_WITH_MSG(
allocator, "allocator is invalid", return RCUTILS_RET_INVALID_ARGUMENT);

// Stash entries being removed
rcutils_string_array_t to_reclaim = rcutils_get_zero_initialized_string_array();
if (new_size < string_array->size) {
size_t num_removed = string_array->size - new_size;
rcutils_ret_t ret = rcutils_string_array_init(&to_reclaim, num_removed, allocator);
if (RCUTILS_RET_OK != ret) {
// rcutils_string_array_init should have already set an error message
return ret;
}
memcpy(
to_reclaim.data, &string_array->data[new_size],
to_reclaim.size * sizeof(char *));
}

char ** new_data = allocator->reallocate(
string_array->data, new_size * sizeof(char *), allocator->state);
if (NULL == new_data && 0 != new_size) {
RCUTILS_SET_ERROR_MSG("failed to allocate string array");
for (size_t i = 0; i < to_reclaim.size; ++i) {
to_reclaim.data[i] = NULL;
}
rcutils_ret_t ret = rcutils_string_array_fini(&to_reclaim);
if (RCUTILS_RET_OK != ret) {
RCUTILS_SET_ERROR_MSG("memory was leaked during error handling");
}
return RCUTILS_RET_BAD_ALLOC;
}
string_array->data = new_data;

// Zero-initialize new entries
for (size_t i = string_array->size; i < new_size; ++i) {
string_array->data[i] = NULL;
}

string_array->size = new_size;

// Lastly, reclaim removed entries
return rcutils_string_array_fini(&to_reclaim);
}

#ifdef __cplusplus
}
#endif
103 changes: 103 additions & 0 deletions test/test_string_array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "gtest/gtest.h"

#include "./allocator_testing_utils.h"
#include "./time_bomb_allocator_testing_utils.h"
#include "rcutils/error_handling.h"
#include "rcutils/types/string_array.h"

Expand Down Expand Up @@ -153,3 +154,105 @@ TEST(test_string_array, string_array_cmp) {
ret = rcutils_string_array_fini(&incomplete_string_array);
ASSERT_EQ(RCUTILS_RET_OK, ret);
}

TEST(test_string_array, string_array_resize) {
auto allocator = rcutils_get_default_allocator();
auto failing_allocator = get_failing_allocator();
auto invalid_allocator = rcutils_get_zero_initialized_allocator();
auto time_bomb_allocator = get_time_bomb_allocator();
rcutils_ret_t ret;

ret = rcutils_string_array_resize(nullptr, 8);
ASSERT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret);
rcutils_reset_error();

// Start with 8 elements
rcutils_string_array_t sa0;
ret = rcutils_string_array_init(&sa0, 8, &allocator);
ASSERT_EQ(RCUTILS_RET_OK, ret);

for (size_t i = 0; i < sa0.size; i++) {
const char val[] = {static_cast<char>('a' + i), '\0'};
sa0.data[i] = strdup(val);
}

// Resize to same size (hot path)
ret = rcutils_string_array_resize(&sa0, sa0.size);
ASSERT_EQ(RCUTILS_RET_OK, ret);

// Grow to 16 (with allocation failure)
sa0.allocator = failing_allocator;
ret = rcutils_string_array_resize(&sa0, 16);
EXPECT_EQ(RCUTILS_RET_BAD_ALLOC, ret);
EXPECT_EQ(8u, sa0.size);
rcutils_reset_error();

// Grow to 16 (with invalid allocator)
sa0.allocator = invalid_allocator;
ret = rcutils_string_array_resize(&sa0, 16);
EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret);
EXPECT_EQ(8u, sa0.size);
rcutils_reset_error();

// Grow to 16
sa0.allocator = allocator;
ret = rcutils_string_array_resize(&sa0, 16);
ASSERT_EQ(RCUTILS_RET_OK, ret);
ASSERT_EQ(16u, sa0.size);

// Check that existing data is intact
for (size_t i = 0; i < 8; i++) {
const char val[] = {static_cast<char>('a' + i), '\0'};
EXPECT_STREQ(val, sa0.data[i]);
}

// Check that new elements are empty
for (size_t i = 8; i < sa0.size; i++) {
const char val[] = {static_cast<char>('a' + i), '\0'};
EXPECT_STREQ(nullptr, sa0.data[i]);
sa0.data[i] = strdup(val);
}

// Shrink to 4 (with allocation failure)
sa0.allocator = failing_allocator;
ret = rcutils_string_array_resize(&sa0, 4);
EXPECT_EQ(RCUTILS_RET_BAD_ALLOC, ret);
EXPECT_EQ(16u, sa0.size);
rcutils_reset_error();

// Shrink to 4 (with delayed allocation failure)
set_time_bomb_allocator_realloc_count(time_bomb_allocator, 0);
sa0.allocator = time_bomb_allocator;
ret = rcutils_string_array_resize(&sa0, 4);
EXPECT_EQ(RCUTILS_RET_BAD_ALLOC, ret);
EXPECT_EQ(16u, sa0.size);
rcutils_reset_error();

// Shrink to 4 (with invalid allocator)
sa0.allocator = invalid_allocator;
ret = rcutils_string_array_resize(&sa0, 4);
EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret);
EXPECT_EQ(16u, sa0.size);
rcutils_reset_error();

// Shrink to 4
sa0.allocator = allocator;
ret = rcutils_string_array_resize(&sa0, 4);
ASSERT_EQ(RCUTILS_RET_OK, ret);
ASSERT_EQ(4u, sa0.size);

// Check that existing data is intact
for (size_t i = 0; i < sa0.size; i++) {
const char val[] = {static_cast<char>('a' + i), '\0'};
EXPECT_STREQ(val, sa0.data[i]);
}

// Shrink to 0
ret = rcutils_string_array_resize(&sa0, 0);
EXPECT_EQ(RCUTILS_RET_OK, ret);
EXPECT_EQ(0u, sa0.size);

sa0.allocator = allocator;
ret = rcutils_string_array_fini(&sa0);
ASSERT_EQ(RCUTILS_RET_OK, ret);
}