Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rcutils_string_array_resize function #247

Merged
merged 19 commits into from
Jun 8, 2020
Merged
Show file tree
Hide file tree
Changes from 15 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
77 changes: 77 additions & 0 deletions test/test_string_array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,80 @@ 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();
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
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
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);

// Allocation failure
sa0.allocator = failing_allocator;
ret = rcutils_string_array_resize(&sa0, 8);
EXPECT_EQ(RCUTILS_RET_BAD_ALLOC, ret);
EXPECT_EQ(0u, sa0.size);
rcutils_reset_error();

// Invalid allocator
sa0.allocator = invalid_allocator;
ret = rcutils_string_array_resize(&sa0, 8);
EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret);
EXPECT_EQ(0u, sa0.size);
rcutils_reset_error();

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