lib/os/heap: fix realloc issues

If the new size amounts to the same number of chunks then:

- If right-chunk is used then we needlessly allocate new memory and
  copy data over.

- If right-chunk is free then we attempt to split it with a zero size
  which corrupts the prev/next list.

Make sure this case is properly handled and add a test for it.

While at it, let's simplify the code somewhat as well.

Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
This commit is contained in:
Nicolas Pitre 2020-12-17 22:21:10 -05:00 committed by Anas Nashif
commit aeda6ccdd8
2 changed files with 23 additions and 11 deletions

View file

@ -311,7 +311,10 @@ void *sys_heap_realloc(struct sys_heap *heap, void *ptr, size_t bytes)
chunkid_t rc = right_chunk(h, c);
size_t chunks_need = bytes_to_chunksz(h, bytes);
if (chunk_size(h, c) > chunks_need) {
if (chunk_size(h, c) == chunks_need) {
/* We're good already */
return ptr;
} else if (chunk_size(h, c) > chunks_need) {
/* Shrink in place, split off and free unused suffix */
split_chunks(h, c, c + chunks_need);
set_chunk_used(h, c, true);
@ -323,20 +326,15 @@ void *sys_heap_realloc(struct sys_heap *heap, void *ptr, size_t bytes)
chunkid_t split_size = chunks_need - chunk_size(h, c);
free_list_remove(h, rc);
if (split_size < chunk_size(h, rc)) {
split_chunks(h, rc, rc + split_size);
free_list_add(h, rc + split_size);
}
chunkid_t newsz = chunk_size(h, c) + split_size;
set_chunk_size(h, c, newsz);
merge_chunks(h, c, rc);
set_chunk_used(h, c, true);
set_left_chunk_size(h, c + newsz, newsz);
CHECK(chunk_used(h, c));
return chunk_mem(h, c);
return ptr;
} else {
/* Reallocate and copy */
void *ptr2 = sys_heap_alloc(heap, bytes);
@ -345,8 +343,7 @@ void *sys_heap_realloc(struct sys_heap *heap, void *ptr, size_t bytes)
return NULL;
}
memcpy(ptr2, ptr,
chunk_size(h, c) * CHUNK_UNIT - chunk_header_bytes(h));
memcpy(ptr2, ptr, bytes);
sys_heap_free(heap, ptr);
return ptr2;
}

View file

@ -275,6 +275,21 @@ static void test_realloc(void)
"Realloc should have shrunk in place %p -> %p",
p1, p2);
zassert_true(realloc_check_block(p2, p1, 64), "data changed");
/* Allocate two blocks, then expand the first within a chunk.
* validate that it doesn't move. We assume CHUNK_UNIT == 8.
*/
p1 = sys_heap_alloc(&heap, 61);
realloc_fill_block(p1, 61);
p2 = sys_heap_alloc(&heap, 80);
realloc_fill_block(p2, 80);
p3 = sys_heap_realloc(&heap, p1, 64);
zassert_true(sys_heap_validate(&heap), "invalid heap");
zassert_true(p1 == p3,
"Realloc should have expanded in place %p -> %p",
p1, p3);
zassert_true(realloc_check_block(p3, p1, 61), "data changed");
}
void test_main(void)