#include #include #include #include #include #include "testutil.h" void reset_string_heap(void); akerr_ErrorContext *test_fresh_heap_gives_strings(void) { akgl_String *ptr = NULL; PREPARE_ERROR(errctx); for ( int i = 0; i < AKGL_MAX_HEAP_STRING - 1; i++ ) { ATTEMPT { CATCH(errctx, akgl_heap_next_string(&ptr)); } CLEANUP { reset_string_heap(); } PROCESS(errctx) { } FINISH(errctx, true); } return 0; } akerr_ErrorContext *test_string_heap_error_when_no_strings_left(void) { akgl_String *ptr; PREPARE_ERROR(errctx); for ( int i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) { akgl_heap_strings[i].refcount = 1; } for ( int i = 0; i < AKGL_MAX_HEAP_STRING - 1; i++ ) { ATTEMPT { CATCH(errctx, akgl_heap_next_string(&ptr)); } CLEANUP { } PROCESS(errctx) { } HANDLE(errctx, AKERR_NULLPOINTER) { return 0; } FINISH(errctx, true); } FAIL_RETURN(errctx, AKERR_OUTOFBOUNDS, "Expected AKERR_NULLPOINTER when accessing beyond string heap bounds"); SUCCEED_RETURN(errctx); } akerr_ErrorContext *test_string_heap_honors_refcount(void) { akgl_String *firstptr = &akgl_heap_strings[0]; akgl_String *secondptr = &akgl_heap_strings[1]; akgl_String *testptr = NULL; PREPARE_ERROR(errctx); ATTEMPT { CATCH(errctx, akgl_heap_next_string(&testptr)); if ( testptr != firstptr ) { FAIL_RETURN( errctx, AKERR_VALUE, "Expected testptr to equal (akgl_heap_strings[0] = %p) but got %p", firstptr, testptr ); } CATCH(errctx, akgl_string_initialize(testptr, NULL)); if ( testptr->refcount == 0 ) { FAIL_RETURN(errctx, AKERR_VALUE, "Expected string reference count to be nonzero but got 0"); } if ( testptr != firstptr ) { FAIL_RETURN( errctx, AKERR_VALUE, "Expected testptr to equal (akgl_heap_strings[1] = %p) but got %p", secondptr, testptr ); } CATCH(errctx, akgl_heap_next_string(&testptr)); } CLEANUP { } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } akerr_ErrorContext *test_strcpy_to_all_strings_no_segfault(void) { char copybuf[AKGL_MAX_STRING_LENGTH]; akgl_String *ptr; memset((void *)©buf, 'a', AKGL_MAX_STRING_LENGTH); PREPARE_ERROR(errctx); ATTEMPT { for ( int i = 0; i < AKGL_MAX_HEAP_STRING - 1; i++ ) { CATCH(errctx, akgl_heap_next_string(&ptr)); strncpy(ptr->data, (char *)©buf, AKGL_MAX_STRING_LENGTH); } } CLEANUP { } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } akerr_ErrorContext *test_akgl_string_initialize(void) { akgl_String *ptr; PREPARE_ERROR(errctx); ATTEMPT { CATCH(errctx, akgl_heap_next_string(&ptr)); CATCH(errctx, akgl_string_initialize(ptr, NULL)); FAIL_NONZERO_BREAK(errctx, ptr->data[0], AKERR_VALUE, "Expected empty zero length string data"); CATCH(errctx, akgl_heap_release_string(ptr)); CATCH(errctx, akgl_heap_next_string(&ptr)); CATCH(errctx, akgl_string_initialize(ptr, "Test value")); FAIL_NONZERO_BREAK(errctx, strcmp((char *)&ptr->data, "Test value"), AKERR_VALUE, "Expected 'Test value', got %s", (char *)&ptr->data); CATCH(errctx, akgl_heap_release_string(NULL)); FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Failure to properly handle NULL pointer"); } CLEANUP { } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } void reset_string_heap(void) { for ( int i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) { memset(&akgl_heap_strings[i], 0x00, sizeof(akgl_String)); } } /** * @brief akgl_string_initialize must not write past the buffer it is zeroing. * * The `NULL` init path zeroed `sizeof(akgl_String)` bytes starting at `data`. * `data` begins after the `int refcount` in front of it, so that ran four bytes * past the end of the object -- straight onto the *next* pool slot's refcount, * which is the field the allocator uses to decide whether a slot is free. * * Claiming two adjacent slots and initializing the first is enough to catch it: * the second's refcount goes to zero and the pool believes it is free while the * caller is still holding it. */ akerr_ErrorContext *test_akgl_string_initialize_stays_in_bounds(void) { PREPARE_ERROR(errctx); akgl_String *first = NULL; akgl_String *second = NULL; ATTEMPT { CATCH(errctx, akgl_heap_next_string(&first)); CATCH(errctx, akgl_heap_next_string(&second)); TEST_ASSERT(errctx, second == (first + 1), "the pool did not hand out adjacent slots; this test needs them"); // A sentinel the overrun would land on. akgl_heap_next_string has // already set it to 1; make it something an accidental write cannot // coincide with. second->refcount = 0x5A; CATCH(errctx, akgl_string_initialize(first, NULL)); TEST_ASSERT(errctx, second->refcount == 0x5A, "initializing a string wrote past its buffer: the next slot's " "refcount is %d, expected %d", second->refcount, 0x5A); TEST_ASSERT(errctx, first->refcount == 1, "initializing a string left its own refcount at %d, expected 1", first->refcount); TEST_ASSERT(errctx, first->data[0] == '\0', "initializing a string with NULL did not zero its buffer"); TEST_ASSERT(errctx, first->data[AKGL_MAX_STRING_LENGTH - 1] == '\0', "initializing a string with NULL did not zero its last byte"); } CLEANUP { if ( second != NULL ) { second->refcount = 1; IGNORE(akgl_heap_release_string(second)); } if ( first != NULL ) { IGNORE(akgl_heap_release_string(first)); } } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } /** * @brief akgl_string_copy must refuse a count that would leave both buffers. * * Both slots are exactly AKGL_MAX_STRING_LENGTH bytes, so a larger count read * past the end of one and wrote past the end of the other. The header used to * document that as behaviour. */ akerr_ErrorContext *test_akgl_string_copy_bounds_its_count(void) { PREPARE_ERROR(errctx); akgl_String *src = NULL; akgl_String *dest = NULL; ATTEMPT { CATCH(errctx, akgl_heap_next_string(&src)); CATCH(errctx, akgl_heap_next_string(&dest)); CATCH(errctx, akgl_string_initialize(src, "bounded")); CATCH(errctx, akgl_string_initialize(dest, NULL)); TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS, akgl_string_copy(src, dest, AKGL_MAX_STRING_LENGTH + 1), "copying one byte more than a pool string holds"); TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS, akgl_string_copy(src, dest, -1), "copying a negative number of bytes"); // The boundary itself is legal: it is exactly the buffer. TEST_EXPECT_OK(errctx, akgl_string_copy(src, dest, AKGL_MAX_STRING_LENGTH), "copying exactly a pool string's length"); TEST_ASSERT(errctx, strcmp((char *)&dest->data, "bounded") == 0, "a full-length copy did not transfer the contents"); // 0 still means "the whole buffer" rather than "nothing". CATCH(errctx, akgl_string_initialize(dest, NULL)); TEST_EXPECT_OK(errctx, akgl_string_copy(src, dest, 0), "copying with a count of zero"); TEST_ASSERT(errctx, strcmp((char *)&dest->data, "bounded") == 0, "a zero-count copy did not transfer the whole buffer"); } CLEANUP { if ( dest != NULL ) { IGNORE(akgl_heap_release_string(dest)); } if ( src != NULL ) { IGNORE(akgl_heap_release_string(src)); } } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } int main(void) { PREPARE_ERROR(errctx); ATTEMPT { CATCH(errctx, akgl_error_init()); printf("test_fresh_heap_gives_string ....\n"); test_fresh_heap_gives_strings(); reset_string_heap(); printf("test_string_heap_error_when_no_strings_left ...\n"); test_string_heap_error_when_no_strings_left(); reset_string_heap(); printf("test_string_heap_honors_refcount ...\n"); test_string_heap_honors_refcount(); reset_string_heap(); printf("test_strcpy_to_all_strings_no_segfault ...\n"); test_strcpy_to_all_strings_no_segfault(); reset_string_heap(); printf("test_akgl_string_initialize....\n"); test_akgl_string_initialize(); reset_string_heap(); printf("test_akgl_string_initialize_stays_in_bounds ...\n"); CATCH(errctx, test_akgl_string_initialize_stays_in_bounds()); reset_string_heap(); printf("test_akgl_string_copy_bounds_its_count ...\n"); CATCH(errctx, test_akgl_string_copy_bounds_its_count()); } CLEANUP { } PROCESS(errctx) { } FINISH_NORETURN(errctx); return 0; }