/*************************************************************************** * Copyright (c) 2026 Eclipse ThreadX contributors * * This program and the accompanying materials are made available under the * terms of the MIT License which is available at * https://opensource.org/licenses/MIT. * * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). * The AI-generated portions may be considered public domain (CC0-1.0) * and not subject to the project's licence. The human contributor has * reviewed and verified that the code is correct. * * SPDX-License-Identifier: MIT and CC0-1.0 **************************************************************************/ /**************************************************************************/ /**************************************************************************/ /** */ /** ThreadX Test */ /** */ /** Module Manager object allocation size arithmetic */ /** */ /**************************************************************************/ /**************************************************************************/ /* A module asks the Module Manager for object memory and chooses the size. The manager adds its own private header to that size, and the header it writes is larger than the smallest allocation the byte pool will hand out, so the addition has to be checked before the pool is asked for anything. This test drives _txm_module_manager_object_allocate on the host with the Cortex-A7 module port's control blocks. The byte pool behind it is modelled rather than run: the model applies exactly the two bounds _txe_byte_allocate applies, refusing a zero size and a size larger than the pool, and it hands out a block of precisely the requested length with a guard band immediately after it. An allocation that the manager then overruns is therefore caught as a real out-of-bounds write rather than inferred from arithmetic, which is what makes the refusals below testable: revert the check and the manager writes its sixteen-byte header into an eight-byte block and stamps on the guard. */ #include #define TX_SOURCE_CODE #include "tx_api.h" #include "tx_byte_pool.h" #include "tx_thread.h" #include "txm_module.h" #include "txm_module_manager_util.h" /* Define the modelled pool. The arena is physically larger than the pool the manager is told about, so the guard band that follows every block has somewhere to live without the pool's own accounting having to know about it. */ #define TEST_ARENA_BYTES 8192U #define TEST_POOL_BYTES 4096UL #define TEST_GUARD_BYTES 32U #define TEST_GUARD_PATTERN 0xE7U #define TEST_MAX_BLOCKS 32U #define TEST_HEADER_BYTES ((ULONG) sizeof(TXM_MODULE_ALLOCATED_OBJECT)) #define TEST_ULONG_MAX ((ULONG) ~((ULONG) 0)) /* The object size that makes the unchecked addition wrap to a chosen value. */ #define TEST_SIZE_WRAPPING_TO(want) ((ULONG) (((ULONG) (want)) - TEST_HEADER_BYTES)) /* Define one block the modelled pool has handed out. */ typedef struct TEST_BLOCK_STRUCT { UCHAR *test_block_start; ULONG test_block_bytes; UINT test_block_live; } TEST_BLOCK; /* Define the state the modelled pool and the stubbed services answer from. */ static union { ALIGN_TYPE test_arena_alignment; UCHAR test_arena_bytes[TEST_ARENA_BYTES]; } test_arena; static ULONG test_arena_offset; static TEST_BLOCK test_blocks[TEST_MAX_BLOCKS]; static UINT test_block_count; static ULONG test_allocate_calls; static ULONG test_allocate_size; static ULONG test_allocate_wait; static ULONG test_release_calls; static ULONG test_mutex_get_calls; static ULONG test_mutex_put_calls; /* Counted across the whole run rather than per case, because what they answer -- was the pool ever asked for less than the header the manager then writes, and was it ever asked on behalf of something other than itself -- is about the run as a whole. */ static UINT test_undersized_requests; static UINT test_misdirected_requests; static UINT test_failures; /* Define the manager state the code under test reaches for. */ TX_BYTE_POOL _txm_module_manager_object_pool; UINT _txm_module_manager_object_pool_created; TX_MUTEX _txm_module_manager_mutex; TX_THREAD *_tx_thread_current_ptr; /* Define the stand-in interrupt lock the port shim counts through. */ unsigned int test_interrupt_disable_depth; unsigned int test_interrupt_disable_max_depth; unsigned int test_interrupt_restore_underflows; /* The kernel's created lists. A created object lives on the list for its type, and the manager reads those lists, so a test that stands in for the kernel has to provide them. All stay empty here, because this test creates no kernel object; it exercises the arithmetic the allocation does before any object exists. */ TX_BLOCK_POOL *_tx_block_pool_created_ptr; ULONG _tx_block_pool_created_count; TX_BYTE_POOL *_tx_byte_pool_created_ptr; ULONG _tx_byte_pool_created_count; TX_EVENT_FLAGS_GROUP *_tx_event_flags_created_ptr; ULONG _tx_event_flags_created_count; TX_MUTEX *_tx_mutex_created_ptr; ULONG _tx_mutex_created_count; TX_QUEUE *_tx_queue_created_ptr; ULONG _tx_queue_created_count; TX_SEMAPHORE *_tx_semaphore_created_ptr; ULONG _tx_semaphore_created_count; TX_THREAD *_tx_thread_created_ptr; ULONG _tx_thread_created_count; TX_TIMER *_tx_timer_created_ptr; ULONG _tx_timer_created_count; /* This test models no module memory of its own, so no address is ever inside the calling module's data. */ UINT _txm_module_manager_inside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, ULONG obj_size, UINT write_request) { (VOID) module_instance; (VOID) obj_ptr; (VOID) obj_size; (VOID) write_request; return(TX_FALSE); } /* No module data is modelled here, so every range lies outside it. */ UINT _txm_module_manager_outside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, ULONG obj_size) { (VOID) module_instance; (VOID) obj_ptr; (VOID) obj_size; return(TX_TRUE); } /* Not reached from the allocation path under test. */ VOID _txm_module_manager_alignment_adjust(TXM_MODULE_PREAMBLE *module_preamble, ULONG *code_size, ULONG *code_alignment, ULONG *data_size, ULONG *data_alignment) { (VOID) module_preamble; (VOID) code_size; (VOID) code_alignment; (VOID) data_size; (VOID) data_alignment; } /* Record the outcome of one expectation. */ static VOID test_expect(const char *description, ULONG actual, ULONG expected) { if (actual != expected) { printf("FAIL: %s (expected %lu, got %lu)\n", description, (unsigned long) expected, (unsigned long) actual); test_failures++; } } /* Round a request up the way _tx_byte_allocate does. */ static ULONG test_round_up(ULONG memory_size) { return((((memory_size + ((ULONG) sizeof(ALIGN_TYPE))) - ((ULONG) 1)) / ((ULONG) sizeof(ALIGN_TYPE))) * ((ULONG) sizeof(ALIGN_TYPE))); } /* Lay a guard band down after a block. */ static VOID test_guard_write(UCHAR *guard_ptr) { UINT index; for (index = 0U; index < TEST_GUARD_BYTES; index++) { guard_ptr[index] = (UCHAR) TEST_GUARD_PATTERN; } } /* Report how many guard bytes anything has written through, across every block the modelled pool has ever handed out, live or released. */ static ULONG test_guard_damage(VOID) { UINT block; UINT index; UCHAR *guard_ptr; ULONG damaged; damaged = ((ULONG) 0); for (block = 0U; block < test_block_count; block++) { guard_ptr = test_blocks[block].test_block_start + test_blocks[block].test_block_bytes; for (index = 0U; index < TEST_GUARD_BYTES; index++) { if (guard_ptr[index] != (UCHAR) TEST_GUARD_PATTERN) { damaged++; } } } return(damaged); } /* Stand in for the byte pool the manager allocates objects from. The two size bounds and the alignment round-up are the real ones. What is not real is the placement: every block ends where a guard band begins, so nothing the manager writes past the end of its allocation can land in memory that a later assertion would have accepted. */ UINT _txe_byte_allocate(TX_BYTE_POOL *pool_ptr, VOID **memory_ptr, ULONG memory_size, ULONG wait_option) { ULONG rounded_size; UCHAR *block_start; test_allocate_calls++; test_allocate_size = memory_size; test_allocate_wait = wait_option; /* The manager must never ask for less than the header it is about to write. This is the invariant the checked addition exists to hold, so the model records a breach here as well as letting the guard band catch the write. */ if (memory_size < TEST_HEADER_BYTES) { test_undersized_requests++; } /* _txe_byte_allocate answers a null pool, a pool that is not one, and a null destination with TX_POOL_ERROR and TX_PTR_ERROR. The manager always supplies its own pool and a real destination, so rather than model arms this test could never reach, note a breach and let the assertion at the end of the run report it. */ if ((pool_ptr != &_txm_module_manager_object_pool) || (pool_ptr -> tx_byte_pool_id != TX_BYTE_POOL_ID) || (memory_ptr == TX_NULL)) { test_misdirected_requests++; return(TX_POOL_ERROR); } if (memory_size == ((ULONG) 0)) { return(TX_SIZE_ERROR); } if (memory_size > pool_ptr -> tx_byte_pool_size) { return(TX_SIZE_ERROR); } rounded_size = test_round_up(memory_size); if ((rounded_size > pool_ptr -> tx_byte_pool_available) || (test_block_count == TEST_MAX_BLOCKS) || ((test_arena_offset + rounded_size + ((ULONG) TEST_GUARD_BYTES)) > ((ULONG) TEST_ARENA_BYTES))) { return(TX_NO_MEMORY); } block_start = &test_arena.test_arena_bytes[test_arena_offset]; test_blocks[test_block_count].test_block_start = block_start; test_blocks[test_block_count].test_block_bytes = rounded_size; test_blocks[test_block_count].test_block_live = TX_TRUE; test_block_count++; test_guard_write(block_start + rounded_size); test_arena_offset = test_arena_offset + rounded_size + ((ULONG) TEST_GUARD_BYTES); pool_ptr -> tx_byte_pool_available = pool_ptr -> tx_byte_pool_available - rounded_size; pool_ptr -> tx_byte_pool_fragments++; pool_ptr -> tx_byte_pool_search = block_start; *memory_ptr = (VOID *) block_start; return(TX_SUCCESS); } /* Stand in for the release side, so a normal allocation can be given back. */ UINT _txe_byte_release(VOID *memory_ptr) { UINT block; test_release_calls++; for (block = 0U; block < test_block_count; block++) { if ((test_blocks[block].test_block_start == (UCHAR *) memory_ptr) && (test_blocks[block].test_block_live == TX_TRUE)) { test_blocks[block].test_block_live = TX_FALSE; _txm_module_manager_object_pool.tx_byte_pool_available = _txm_module_manager_object_pool.tx_byte_pool_available + test_blocks[block].test_block_bytes; return(TX_SUCCESS); } } /* A release of something this pool never handed out, or handed out twice. */ return(TX_PTR_ERROR); } /* Stand in for the protection mutex, to show when it is and is not taken. */ UINT _txe_mutex_get(TX_MUTEX *mutex_ptr, ULONG wait_option) { (VOID) mutex_ptr; (VOID) wait_option; test_mutex_get_calls++; return(TX_SUCCESS); } UINT _txe_mutex_put(TX_MUTEX *mutex_ptr) { (VOID) mutex_ptr; test_mutex_put_calls++; return(TX_SUCCESS); } /* Bring the modelled pool up, empty. */ static VOID test_pool_reset(VOID) { UINT index; for (index = 0U; index < TEST_ARENA_BYTES; index++) { test_arena.test_arena_bytes[index] = (UCHAR) 0; } test_arena_offset = ((ULONG) 0); test_block_count = 0U; test_allocate_calls = ((ULONG) 0); test_allocate_size = ((ULONG) 0); test_allocate_wait = ((ULONG) 0); test_release_calls = ((ULONG) 0); test_mutex_get_calls = ((ULONG) 0); test_mutex_put_calls = ((ULONG) 0); _txm_module_manager_object_pool.tx_byte_pool_id = TX_BYTE_POOL_ID; _txm_module_manager_object_pool.tx_byte_pool_start = test_arena.test_arena_bytes; _txm_module_manager_object_pool.tx_byte_pool_size = TEST_POOL_BYTES; _txm_module_manager_object_pool.tx_byte_pool_available = TEST_POOL_BYTES; _txm_module_manager_object_pool.tx_byte_pool_fragments = 0U; _txm_module_manager_object_pool.tx_byte_pool_search = test_arena.test_arena_bytes; _txm_module_manager_object_pool_created = TX_TRUE; } /* Bring the pool and the module that allocates from it back to a known state. Both are reset together deliberately. The module's allocation list points into the pool, so a case that left objects behind would have the next case linking new objects onto addresses this function has just zeroed, and every later assertion would then be reading the wreckage of an earlier one rather than what it was written to check. */ static VOID test_reset(TXM_MODULE_INSTANCE *module_instance) { test_pool_reset(); module_instance -> txm_module_instance_object_list_count = ((ULONG) 0); module_instance -> txm_module_instance_object_list_head = TX_NULL; } /* Describe everything a refused request must have left alone. */ typedef struct TEST_SNAPSHOT_STRUCT { ULONG test_snapshot_available; UINT test_snapshot_fragments; UCHAR *test_snapshot_search; ULONG test_snapshot_list_count; TXM_MODULE_ALLOCATED_OBJECT *test_snapshot_list_head; ULONG test_snapshot_allocate_calls; ULONG test_snapshot_mutex_get_calls; } TEST_SNAPSHOT; static VOID test_snapshot_take(TEST_SNAPSHOT *snapshot, TXM_MODULE_INSTANCE *module_instance) { snapshot -> test_snapshot_available = _txm_module_manager_object_pool.tx_byte_pool_available; snapshot -> test_snapshot_fragments = _txm_module_manager_object_pool.tx_byte_pool_fragments; snapshot -> test_snapshot_search = _txm_module_manager_object_pool.tx_byte_pool_search; snapshot -> test_snapshot_list_count = module_instance -> txm_module_instance_object_list_count; snapshot -> test_snapshot_list_head = module_instance -> txm_module_instance_object_list_head; snapshot -> test_snapshot_allocate_calls = test_allocate_calls; snapshot -> test_snapshot_mutex_get_calls = test_mutex_get_calls; } /* Assert that a refused request changed nothing an accepted one would have. */ static VOID test_refusal_check(const char *description, TEST_SNAPSHOT *snapshot, TXM_MODULE_INSTANCE *module_instance, VOID *object, UINT status) { test_expect(description, (ULONG) status, (ULONG) TXM_MODULE_MATH_OVERFLOW); test_expect("...returning a null object pointer", (ULONG) (object == TX_NULL), (ULONG) TX_TRUE); test_expect("...without asking the pool for anything", test_allocate_calls, snapshot -> test_snapshot_allocate_calls); test_expect("...without taking the protection mutex", test_mutex_get_calls, snapshot -> test_snapshot_mutex_get_calls); test_expect("...leaving the bytes available unchanged", _txm_module_manager_object_pool.tx_byte_pool_available, snapshot -> test_snapshot_available); test_expect("...leaving the fragment count unchanged", (ULONG) _txm_module_manager_object_pool.tx_byte_pool_fragments, (ULONG) snapshot -> test_snapshot_fragments); test_expect("...leaving the search pointer unchanged", (ULONG) (_txm_module_manager_object_pool.tx_byte_pool_search == snapshot -> test_snapshot_search), (ULONG) TX_TRUE); test_expect("...leaving the module's object count unchanged", module_instance -> txm_module_instance_object_list_count, snapshot -> test_snapshot_list_count); test_expect("...leaving the module's object list head unchanged", (ULONG) (module_instance -> txm_module_instance_object_list_head == snapshot -> test_snapshot_list_head), (ULONG) TX_TRUE); test_expect("...and touching no guard band", test_guard_damage(), ((ULONG) 0)); } /* Model the arithmetic this test guards against, so that each refused size can show what the previous implementation would have asked the pool for. */ static ULONG test_unchecked_allocation_size(ULONG object_size) { return((ULONG) (object_size + sizeof(TXM_MODULE_ALLOCATED_OBJECT))); } /* Confirm that one accepted allocation is shaped the way the rest of the manager requires: the pool was asked for the object plus the header, the module was handed the address just past the header, and the header records the size that _txm_module_manager_object_size_check will later be asked to match. */ static VOID test_accepted_check(const char *description, TXM_MODULE_INSTANCE *module_instance, VOID *object, UINT status, ULONG object_size) { TXM_MODULE_ALLOCATED_OBJECT *header; test_expect(description, (ULONG) status, (ULONG) TX_SUCCESS); test_expect("...returning a non-null object pointer", (ULONG) (object != TX_NULL), (ULONG) TX_TRUE); if (object == TX_NULL) { return; } test_expect("...having asked the pool for the object and the header", test_allocate_size, object_size + TEST_HEADER_BYTES); test_expect("...without waiting", test_allocate_wait, (ULONG) TX_NO_WAIT); header = ((TXM_MODULE_ALLOCATED_OBJECT *) object) - 1; test_expect("...placing the object immediately after the header", (ULONG) (((UCHAR *) header) == test_blocks[test_block_count - 1U].test_block_start), (ULONG) TX_TRUE); /* The size recorded here is what _txm_module_manager_object_size_check later compares an object-creation request against, so a size the header did not record faithfully would fail every creation dispatcher. */ test_expect("...recording the requested size in the header", header -> txm_module_object_size, object_size); test_expect("...recording the owning module in the header", (ULONG) (header -> txm_module_allocated_object_module_instance == module_instance), (ULONG) TX_TRUE); test_expect("...and touching no guard band", test_guard_damage(), ((ULONG) 0)); } /* Walk the module's allocation list and report whether it is a consistent ring of the expected length, owned throughout by the expected module. */ static UINT test_list_consistent(TXM_MODULE_INSTANCE *module_instance, ULONG expected_count) { TXM_MODULE_ALLOCATED_OBJECT *object; ULONG walked; if (module_instance -> txm_module_instance_object_list_count != expected_count) { return(TX_FALSE); } if (expected_count == ((ULONG) 0)) { return((UINT) (module_instance -> txm_module_instance_object_list_head == TX_NULL)); } object = module_instance -> txm_module_instance_object_list_head; if (object == TX_NULL) { return(TX_FALSE); } for (walked = ((ULONG) 0); walked < expected_count; walked++) { if (object -> txm_module_allocated_object_module_instance != module_instance) { return(TX_FALSE); } if (object -> txm_module_allocated_object_next -> txm_module_allocated_object_previous != object) { return(TX_FALSE); } object = object -> txm_module_allocated_object_next; } /* The ring must close on the head after exactly the expected number of steps. */ return((UINT) (object == module_instance -> txm_module_instance_object_list_head)); } /* The control block sizes a module can ask the manager to allocate for it. */ typedef struct TEST_OBJECT_SIZE_STRUCT { const char *test_object_name; ULONG test_object_size; } TEST_OBJECT_SIZE; int main(void) { TXM_MODULE_INSTANCE module; TXM_MODULE_INSTANCE *module_instance; TX_THREAD module_thread; TEST_SNAPSHOT snapshot; VOID *object; VOID *objects[8]; UINT status; UINT index; ULONG object_size; ULONG wrapped_size; TEST_OBJECT_SIZE object_sizes[] = { {"a thread", (ULONG) sizeof(TX_THREAD)}, {"a queue", (ULONG) sizeof(TX_QUEUE)}, {"a semaphore", (ULONG) sizeof(TX_SEMAPHORE)}, {"a mutex", (ULONG) sizeof(TX_MUTEX)}, {"an event flags group",(ULONG) sizeof(TX_EVENT_FLAGS_GROUP)}, {"a block pool", (ULONG) sizeof(TX_BLOCK_POOL)}, {"a byte pool", (ULONG) sizeof(TX_BYTE_POOL)}, {"a timer", (ULONG) sizeof(TX_TIMER)} }; #define TEST_OBJECT_SIZE_ENTRIES (sizeof(object_sizes) / sizeof(object_sizes[0])) /* Unbuffered, so that the expectations reported below survive a crash. An undersized allocation is a memory-safety failure, and a build in which one gets through has to say which expectation noticed before it dies. */ setvbuf(stdout, TX_NULL, _IONBF, 0U); printf("Module Manager object allocation size arithmetic test.................. "); module_instance = &module; module.txm_module_instance_id = TXM_MODULE_ID; module.txm_module_instance_object_list_count = ((ULONG) 0); module.txm_module_instance_object_list_head = TX_NULL; module_thread.tx_thread_module_instance_ptr = (VOID *) module_instance; _tx_thread_current_ptr = &module_thread; test_reset(module_instance); /**********************************************************************/ /* The header the manager writes is larger than the smallest block */ /* the pool will hand out, which is what makes a wrapped size unsafe. */ /**********************************************************************/ test_expect("the object header spans more than one pool alignment unit", (ULONG) (TEST_HEADER_BYTES > (ULONG) sizeof(ALIGN_TYPE)), (ULONG) TX_TRUE); /**********************************************************************/ /* Sizes for which the header-inclusive addition wraps. */ /**********************************************************************/ /* The smallest size that overflows at all, and so the tight side of the new comparison. It wraps to exactly zero, a request the pool would have refused on its own, so this boundary fails safe even unfixed; it is here for the boundary rather than for the consequence. */ test_reset(module_instance); object_size = (TEST_ULONG_MAX - TEST_HEADER_BYTES) + ((ULONG) 1); test_expect("the smallest overflowing size is one past the largest that fits", object_size, TEST_SIZE_WRAPPING_TO(0UL)); test_expect("...and wraps to zero", test_unchecked_allocation_size(object_size), ((ULONG) 0)); test_snapshot_take(&snapshot, module_instance); object = (VOID *) &module; status = _txm_module_manager_object_allocate(&object, object_size, module_instance); test_refusal_check("the smallest overflowing size is refused", &snapshot, module_instance, object, status); /* Wrapping to eight. This is the exploitable one: the pool accepts an eight byte request and the manager then writes sixteen bytes into it. */ test_reset(module_instance); object_size = TEST_SIZE_WRAPPING_TO(8UL); wrapped_size = test_unchecked_allocation_size(object_size); test_expect("the size that wraps to eight is the one expected", wrapped_size, ((ULONG) 8)); test_expect("...a request the pool would have accepted", (ULONG) ((wrapped_size != ((ULONG) 0)) && (wrapped_size <= TEST_POOL_BYTES)), (ULONG) TX_TRUE); test_expect("...for fewer bytes than the header alone occupies", (ULONG) (wrapped_size < TEST_HEADER_BYTES), (ULONG) TX_TRUE); test_snapshot_take(&snapshot, module_instance); object = (VOID *) &module; status = _txm_module_manager_object_allocate(&object, object_size, module_instance); test_refusal_check("a size whose header-inclusive total wraps to a block smaller than the header is refused", &snapshot, module_instance, object, status); /* Wrapping to one, the smallest request the pool would have served at all. */ test_reset(module_instance); object_size = TEST_SIZE_WRAPPING_TO(1UL); test_expect("the size that wraps to one is the one expected", test_unchecked_allocation_size(object_size), ((ULONG) 1)); test_snapshot_take(&snapshot, module_instance); object = (VOID *) &module; status = _txm_module_manager_object_allocate(&object, object_size, module_instance); test_refusal_check("a size whose header-inclusive total wraps to a single byte is refused", &snapshot, module_instance, object, status); /* The largest size expressible at all, which wraps to one byte less than the header. */ test_reset(module_instance); test_snapshot_take(&snapshot, module_instance); object = (VOID *) &module; status = _txm_module_manager_object_allocate(&object, TEST_ULONG_MAX, module_instance); test_refusal_check("the largest expressible size is refused", &snapshot, module_instance, object, status); /**********************************************************************/ /* The largest size that does not overflow. */ /**********************************************************************/ /* This one must pass the check and reach the pool carrying its true total, which the pool then refuses on its own size bound. It covers the accepting side of the new comparison at the tightest point there is. */ test_reset(module_instance); object_size = TEST_ULONG_MAX - TEST_HEADER_BYTES; test_snapshot_take(&snapshot, module_instance); object = (VOID *) &module; status = _txm_module_manager_object_allocate(&object, object_size, module_instance); test_expect("the largest non-overflowing size is refused by the pool, not by the check", (ULONG) status, (ULONG) TX_SIZE_ERROR); test_expect("...having reached the pool", test_allocate_calls, snapshot.test_snapshot_allocate_calls + ((ULONG) 1)); test_expect("...asking for the full untruncated total", test_allocate_size, TEST_ULONG_MAX); test_expect("...returning a null object pointer", (ULONG) (object == TX_NULL), (ULONG) TX_TRUE); test_expect("...leaving the module's object count unchanged", module_instance -> txm_module_instance_object_list_count, snapshot.test_snapshot_list_count); test_expect("...leaving the bytes available unchanged", _txm_module_manager_object_pool.tx_byte_pool_available, snapshot.test_snapshot_available); test_expect("...and releasing the protection mutex it took", test_mutex_get_calls, test_mutex_put_calls); /**********************************************************************/ /* Ordinary allocations, one for every control block a module can own. */ /**********************************************************************/ test_reset(module_instance); for (index = 0U; index < (UINT) TEST_OBJECT_SIZE_ENTRIES; index++) { object = TX_NULL; status = _txm_module_manager_object_allocate(&object, object_sizes[index].test_object_size, module_instance); test_accepted_check(object_sizes[index].test_object_name, module_instance, object, status, object_sizes[index].test_object_size); objects[index] = object; } test_expect("the module's allocation list holds every object in a consistent ring", (ULONG) test_list_consistent(module_instance, (ULONG) TEST_OBJECT_SIZE_ENTRIES), (ULONG) TX_TRUE); /* Every one of them can be given back, and the list empties out. */ for (index = 0U; index < (UINT) TEST_OBJECT_SIZE_ENTRIES; index++) { status = _txm_module_manager_object_deallocate(objects[index]); test_expect("an allocated object can be released", (ULONG) status, (ULONG) TX_SUCCESS); } test_expect("the allocation list is empty again", (ULONG) test_list_consistent(module_instance, ((ULONG) 0)), (ULONG) TX_TRUE); test_expect("every byte charged to the pool came back", _txm_module_manager_object_pool.tx_byte_pool_available, TEST_POOL_BYTES); test_expect("no guard band was touched by any of it", test_guard_damage(), ((ULONG) 0)); test_expect("the protection mutex was balanced throughout", test_mutex_get_calls, test_mutex_put_calls); /**********************************************************************/ /* A zero-length object, the other end of the accepted range. */ /**********************************************************************/ test_reset(module_instance); object = TX_NULL; status = _txm_module_manager_object_allocate(&object, ((ULONG) 0), module_instance); test_accepted_check("a zero-length object", module_instance, object, status, ((ULONG) 0)); test_expect("...for which the pool was asked for exactly the header", test_allocate_size, TEST_HEADER_BYTES); (VOID) _txm_module_manager_object_deallocate(object); /**********************************************************************/ /* Sizes the pool cannot serve, which must still not corrupt anything. */ /**********************************************************************/ test_reset(module_instance); object = TX_NULL; status = _txm_module_manager_object_allocate(&object, TEST_POOL_BYTES, module_instance); test_expect("a size the pool is too small for is refused by the pool", (ULONG) status, (ULONG) TX_SIZE_ERROR); test_expect("...returning a null object pointer", (ULONG) (object == TX_NULL), (ULONG) TX_TRUE); test_expect("...leaving the allocation list empty", (ULONG) test_list_consistent(module_instance, ((ULONG) 0)), (ULONG) TX_TRUE); object = TX_NULL; status = _txm_module_manager_object_allocate(&object, ((ULONG) 4000), module_instance); test_expect("a size the pool can serve once succeeds", (ULONG) status, (ULONG) TX_SUCCESS); object = TX_NULL; status = _txm_module_manager_object_allocate(&object, ((ULONG) 4000), module_instance); test_expect("the same size again finds no memory", (ULONG) status, (ULONG) TX_NO_MEMORY); test_expect("...returning a null object pointer", (ULONG) (object == TX_NULL), (ULONG) TX_TRUE); test_expect("...leaving the one live object in the list", (ULONG) test_list_consistent(module_instance, ((ULONG) 1)), (ULONG) TX_TRUE); test_expect("...and touching no guard band", test_guard_damage(), ((ULONG) 0)); /**********************************************************************/ /* Invalid and disabled configurations. */ /**********************************************************************/ test_reset(module_instance); /* A null destination is rejected before the size is looked at, so it keeps its own error even when the size would also have been refused. */ status = _txm_module_manager_object_allocate(TX_NULL, TEST_ULONG_MAX, module_instance); test_expect("a null destination pointer is rejected on its own terms", (ULONG) status, (ULONG) TXM_MODULE_INVALID_MEMORY); test_expect("...without asking the pool for anything", test_allocate_calls, ((ULONG) 0)); test_expect("...without taking the protection mutex", test_mutex_get_calls, ((ULONG) 0)); /* With no object pool, an overflowing size is still refused as one: the check runs before the manager looks at its own configuration. */ _txm_module_manager_object_pool_created = TX_FALSE; test_snapshot_take(&snapshot, module_instance); object = (VOID *) &module; status = _txm_module_manager_object_allocate(&object, TEST_ULONG_MAX, module_instance); test_refusal_check("an overflowing size is refused with no object pool created", &snapshot, module_instance, object, status); object = TX_NULL; status = _txm_module_manager_object_allocate(&object, (ULONG) sizeof(TX_MUTEX), module_instance); test_expect("an ordinary size reports the pool is not available", (ULONG) status, (ULONG) TX_NOT_AVAILABLE); test_expect("...returning a null object pointer", (ULONG) (object == TX_NULL), (ULONG) TX_TRUE); test_expect("...and releasing the protection mutex it took", test_mutex_get_calls, test_mutex_put_calls); _txm_module_manager_object_pool_created = TX_TRUE; /**********************************************************************/ /* The invariant the whole check exists to hold, over the whole run. */ /**********************************************************************/ test_expect("the pool was never asked for less than the header it must hold", (ULONG) test_undersized_requests, ((ULONG) 0)); test_expect("every request named the manager's own pool and a real destination", (ULONG) test_misdirected_requests, ((ULONG) 0)); test_expect("and nothing wrote through a guard band", test_guard_damage(), ((ULONG) 0)); if (test_failures == 0U) { printf("SUCCESS!\n"); return(0); } printf("ERROR: %u expectation(s) failed\n", test_failures); return(1); }