diff options
Diffstat (limited to 'test/tx/module_manager/threadx_module_manager_delete_ownership_test.c')
| -rw-r--r-- | test/tx/module_manager/threadx_module_manager_delete_ownership_test.c | 1031 |
1 files changed, 1031 insertions, 0 deletions
diff --git a/test/tx/module_manager/threadx_module_manager_delete_ownership_test.c b/test/tx/module_manager/threadx_module_manager_delete_ownership_test.c new file mode 100644 index 00000000..95a85ddc --- /dev/null +++ b/test/tx/module_manager/threadx_module_manager_delete_ownership_test.c @@ -0,0 +1,1031 @@ +/*************************************************************************** + * 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 kernel object delete ownership */ +/** */ +/**************************************************************************/ +/**************************************************************************/ + +/* A module reaches a kernel object's delete service through the Module Manager's + dispatch layer, which ran one check on the pointer: that the object lies outside + the module's own code and data. That is the policy for using an object, and using + an object a module does not own is supported -- the manager hands application-owned + objects to modules by name through its object lookup service, and sharing is what + that service is for. Destroying one is not sharing it. + + Two things follow from the missing question, and this test covers both. + + The manager's private header sits immediately in front of each allocation it makes, + and records which module the allocation belongs to. The deallocation the delete path + performs afterwards reached that header by subtracting from whatever address the + caller supplied, and read it before deciding whether it was a header at all. For an + object the application allocated statically, or for any address outside the object + pool, the words in front of it are unrelated memory read in privileged mode -- and + acted on: an address whose preceding words happen to name the calling module was + unlinked from that module's allocation list and handed to the byte pool. + + The manager now finds the allocation by searching the module's own allocation list + instead. The caller's address is compared, never dereferenced, so an address that + names none of this module's allocations is refused without a privileged read of + anything in front of it -- and the search establishes what the header read could + not, that the address is the exact start of an allocation rather than somewhere + inside one. + + The same search is the ownership question the delete dispatchers now ask before the + delete rather than after it. It is a separate question from whether an object is + there and of the expected type, and it is composed with that one rather than + replacing it. + + This test drives the search and the deallocation on the host. The object pool behind + it is modelled -- a bump allocator that hands back the block it most recently + released, so that an address a module has given up can be seen to come back to + another module -- while the record the fix actually consults is built the way the + system builds it, by running the real _txm_module_manager_object_allocate. + + The object standing for one the application owns is deliberately preceded by a + header naming the requesting module. Out-of-bounds reads do not fault on a host, so + that is how this test makes one observable: on the unfixed manager those words are + read and believed, and the application's object memory is released; here they must + be neither read nor believed. */ + +#include <stdio.h> +#include <string.h> + +#include "threadx_module_manager_host_test_port.h" + +#include "tx_thread.h" +#include "tx_timer.h" +#include "tx_queue.h" +#include "tx_event_flags.h" +#include "tx_semaphore.h" +#include "tx_mutex.h" +#include "tx_block_pool.h" +#include "tx_byte_pool.h" +#include "txm_module.h" +#include "txm_module_manager_util.h" + + +/* 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; + + +/* Define the modelled object pool. */ + +#define TEST_POOL_BYTES ((ULONG) 8192) +#define TEST_MODULE_MEMORY_BYTES 512U + +static union +{ + ALIGN_TYPE test_pool_alignment; + UCHAR test_pool_bytes[TEST_POOL_BYTES]; +} test_pool_arena; + +static ULONG test_pool_offset; + + +/* Define the memory that stands for the calling module's own code and data. */ + +static union +{ + ALIGN_TYPE test_data_alignment; + UCHAR test_data_bytes[TEST_MODULE_MEMORY_BYTES]; +} test_module_data; + +static union +{ + ALIGN_TYPE test_code_alignment; + UCHAR test_code_bytes[TEST_MODULE_MEMORY_BYTES]; +} test_module_code; + + +/* Define an object that stands for one the application owns and shared with a + module by name. It lives outside the object pool, so the manager holds no + allocation record of it, and it carries in front of it exactly what the manager + would have put there had it allocated it -- a header naming the module that does + the asking below. Nothing outside this test can arrange those words; they are + here so that reading them can be seen to have happened. */ + +static struct +{ + TXM_MODULE_ALLOCATED_OBJECT test_host_header; + TX_QUEUE test_host_object; +} test_host_area; + + +/* Define the thread that stands for the module thread making the request. The + manager takes the calling module from it. */ + +static TX_THREAD test_current_thread; + + +/* Define the block the modelled pool most recently released, so that a later + allocation of the same size lands on the address a released one had, and so that + a case can see whether anything was released at all. */ + +static UCHAR *test_released_start; +static ULONG test_released_bytes; +static ULONG test_releases; + + +/* 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; + + +/* 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 ownership of the manager's own allocations. */ + +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; + + +static UINT test_failures; +static ULONG test_checks; + + +/* Record the outcome of one expectation. */ +static VOID test_expect(const char *description, ULONG actual, ULONG expected) +{ + test_checks++; + + if (actual != expected) + { + printf("FAIL: %s (expected %lu, got %lu)\n", description, (unsigned long) expected, (unsigned long) actual); + test_failures++; + } +} + + +/* Report every module object type the manager knows and the size of its control + block, so that the cases below can be written once and run against all of them. + The size is what a delete dispatcher passes, and the eight sizes are not all + distinct: on a 32-bit target a byte pool, a mutex and a timer are the same size, + which is why the check below is an ownership check and not a type check. */ + +typedef struct TEST_OBJECT_KIND_STRUCT +{ + const char *test_kind_name; + ULONG test_kind_size; +} TEST_OBJECT_KIND; + +static const TEST_OBJECT_KIND test_object_kinds[] = +{ + { "block pool", (ULONG) sizeof(TX_BLOCK_POOL) }, + { "byte pool", (ULONG) sizeof(TX_BYTE_POOL) }, + { "event flags", (ULONG) sizeof(TX_EVENT_FLAGS_GROUP) }, + { "mutex", (ULONG) sizeof(TX_MUTEX) }, + { "queue", (ULONG) sizeof(TX_QUEUE) }, + { "semaphore", (ULONG) sizeof(TX_SEMAPHORE) }, + { "thread", (ULONG) sizeof(TX_THREAD) }, + { "timer", (ULONG) sizeof(TX_TIMER) } +}; + +#define TEST_OBJECT_KIND_COUNT (sizeof(test_object_kinds) / sizeof(test_object_kinds[0])) + + +/* 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))); +} + + +/* Stand in for the byte pool the manager allocates object memory from. + + The two size bounds are the real ones. The placement is a bump allocator with one + refinement: a request that exactly fits the block most recently released is given + that block back. A first-fit byte pool reuses freed memory, and the cases about an + address that has changed hands need the address to actually come back. */ +UINT _txe_byte_allocate(TX_BYTE_POOL *pool_ptr, VOID **memory_ptr, ULONG memory_size, ULONG wait_option) +{ + +ULONG rounded_size; +UCHAR *block_start; + + + (VOID) wait_option; + + if ((pool_ptr != &_txm_module_manager_object_pool) || (memory_ptr == TX_NULL)) + { + 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 ((test_released_start != TX_NULL) && (test_released_bytes == rounded_size)) + { + block_start = test_released_start; + test_released_start = TX_NULL; + test_released_bytes = ((ULONG) 0); + } + else + { + if ((test_pool_offset + rounded_size) > TEST_POOL_BYTES) + { + return(TX_NO_MEMORY); + } + + block_start = &test_pool_arena.test_pool_bytes[test_pool_offset]; + test_pool_offset = test_pool_offset + rounded_size; + } + + pool_ptr -> tx_byte_pool_available = pool_ptr -> tx_byte_pool_available - rounded_size; + + *memory_ptr = (VOID *) block_start; + + return(TX_SUCCESS); +} + + +/* Stand in for the release side, remembering the block so it can come back, and + counting releases so that a case can see whether one happened. + + The bounds test is the one thing the model must keep: releasing an address the + pool never owned is the corruption the fix exists to prevent, so it is reported + as a failure rather than silently absorbed. */ +UINT _txe_byte_release(VOID *memory_ptr) +{ + +UCHAR *block_start; + + + block_start = (UCHAR *) memory_ptr; + + if ((block_start < test_pool_arena.test_pool_bytes) || + (block_start >= &test_pool_arena.test_pool_bytes[TEST_POOL_BYTES])) + { + printf("FAIL: an address outside the object pool was released to it\n"); + test_failures++; + + return(TX_PTR_ERROR); + } + + test_releases++; + + /* The model does not track block lengths, and the only released block a case + needs to come back is the one it just released, whose length it knows. The + length is recorded by the caller through test_release_size. */ + test_released_start = block_start; + + return(TX_SUCCESS); +} + + +/* Tell the model how long the block just released was. */ +static VOID test_release_size(ULONG object_size) +{ + test_released_bytes = test_round_up(object_size + ((ULONG) sizeof(TXM_MODULE_ALLOCATED_OBJECT))); +} + + +/* Stand in for the protection mutex. */ +UINT _txe_mutex_get(TX_MUTEX *mutex_ptr, ULONG wait_option) +{ + (VOID) mutex_ptr; + (VOID) wait_option; + + return(TX_SUCCESS); +} + + +UINT _txe_mutex_put(TX_MUTEX *mutex_ptr) +{ + (VOID) mutex_ptr; + + return(TX_SUCCESS); +} + + +/* Stand in for the module port's data range check. */ +UINT _txm_module_manager_inside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, + ULONG obj_size, UINT write_request) +{ + +ULONG data_start; +ULONG data_end; + + + (VOID) module_instance; + (VOID) write_request; + + data_start = (ULONG) (ALIGN_TYPE) test_module_data.test_data_bytes; + data_end = data_start + ((ULONG) TEST_MODULE_MEMORY_BYTES); + + /* The whole range has to lie inside the module's data, not just its first + address, and the subtraction is done on the end rather than the start so that + a size which would carry the range past the top of the address space cannot + wrap into an accepting answer. */ + if (((ULONG) obj_ptr >= data_start) && (obj_size <= (data_end - (ULONG) obj_ptr))) + { + return(TX_TRUE); + } + + return(TX_FALSE); +} + + +/* Outside is not the negation of inside: a range that only partly reaches into the + module's data is neither. It has to end before that data begins or begin after it + ends, and a size that would carry the end past the top of the address space is + refused rather than allowed to compare as though it had not. */ +UINT _txm_module_manager_outside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, + ULONG obj_size) +{ + +ULONG data_start; +ULONG data_end; + + + (VOID) module_instance; + + data_start = (ULONG) (ALIGN_TYPE) test_module_data.test_data_bytes; + data_end = data_start + ((ULONG) TEST_MODULE_MEMORY_BYTES); + + if (obj_size > (~((ULONG) 0) - (ULONG) obj_ptr)) + { + return(TX_FALSE); + } + + if ((((ULONG) obj_ptr + obj_size) <= data_start) || ((ULONG) obj_ptr >= data_end)) + { + return(TX_TRUE); + } + + return(TX_FALSE); +} + + +/* Stand in for the port primitive a manager source under test refers to but that + no case here exercises. */ +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; +} + + +/* Bring the pool, the modules and the application's object back to a known state. + + Everything is reset together deliberately. The allocation lists point into the + pool, so a case that left one behind would have the next case reading the + wreckage of an earlier one rather than what it was written to check. */ +static VOID test_reset(TXM_MODULE_INSTANCE *module_a, TXM_MODULE_INSTANCE *module_b) +{ + +UINT index; + + + for (index = 0U; index < (UINT) TEST_POOL_BYTES; index++) + { + test_pool_arena.test_pool_bytes[index] = (UCHAR) 0; + } + + for (index = 0U; index < TEST_MODULE_MEMORY_BYTES; index++) + { + test_module_data.test_data_bytes[index] = (UCHAR) 0; + test_module_code.test_code_bytes[index] = (UCHAR) 0; + } + + test_pool_offset = ((ULONG) 0); + test_released_start = TX_NULL; + test_released_bytes = ((ULONG) 0); + test_releases = ((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_pool_arena.test_pool_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_created = TX_TRUE; + + module_a -> txm_module_instance_object_list_count = ((ULONG) 0); + module_a -> txm_module_instance_object_list_head = TX_NULL; + module_b -> txm_module_instance_object_list_count = ((ULONG) 0); + module_b -> txm_module_instance_object_list_head = TX_NULL; + + /* The application's object, and the header in front of it naming module A. The + links are made to point at the header itself, which is what an allocation + list of one looks like, so that the unfixed manager would find nothing + unusual about it either. */ + (VOID) memset((VOID *) &test_host_area, 0, sizeof(test_host_area)); + test_host_area.test_host_header.txm_module_allocated_object_module_instance = module_a; + test_host_area.test_host_header.txm_module_allocated_object_next = &test_host_area.test_host_header; + test_host_area.test_host_header.txm_module_allocated_object_previous = &test_host_area.test_host_header; + test_host_area.test_host_header.txm_module_object_size = (ULONG) sizeof(TX_QUEUE); + test_host_area.test_host_object.tx_queue_id = TX_QUEUE_ID; + + /* The requesting thread belongs to module A throughout, except where a case + says otherwise. */ + test_current_thread.tx_thread_module_instance_ptr = module_a; + _tx_thread_current_ptr = &test_current_thread; +} + + +/* Zero the interrupt lock accounting before a call into the manager. */ +static VOID test_interrupts_clear(VOID) +{ + test_interrupt_disable_depth = 0U; + test_interrupt_disable_max_depth = 0U; + test_interrupt_restore_underflows = 0U; +} + + +/* Whatever the manager decided, it must have left the interrupt lock as it found + it. */ +static VOID test_interrupts_check(const char *what) +{ + if ((test_interrupt_disable_depth != 0U) || (test_interrupt_restore_underflows != 0U)) + { + printf("FAIL: %s left the interrupt lock unbalanced (depth %u, underflows %u)\n", + what, test_interrupt_disable_depth, test_interrupt_restore_underflows); + test_failures++; + } +} + + +/* Allocate object memory for a module through the real manager path. */ +static VOID *test_allocate(TXM_MODULE_INSTANCE *module_instance, ULONG object_size) +{ + +VOID *object_ptr; +UINT status; + + + object_ptr = TX_NULL; + status = _txm_module_manager_object_allocate(&object_ptr, object_size, module_instance); + + if (status != TX_SUCCESS) + { + printf("FAIL: the modelled pool refused an allocation of %lu bytes (status %u)\n", + (unsigned long) object_size, status); + test_failures++; + + return(TX_NULL); + } + + return(object_ptr); +} + + +/* Ask the question a delete dispatcher asks: may this module destroy the object at + this address? The macro is what the dispatchers use, so the macro is what is + called here. */ +static ULONG test_owner_check(TXM_MODULE_INSTANCE *module_instance, VOID *object_ptr, ULONG object_size) +{ + +ULONG status; + + + test_interrupts_clear(); + + status = (ULONG) TXM_MODULE_MANAGER_PARAM_CHECK_OBJECT_FOR_DELETION(module_instance, + (ALIGN_TYPE) object_ptr, object_size); + + test_interrupts_check("the ownership check"); + + return(status); +} + + +/* Ask the manager to give an address back, with the same accounting. */ +static ULONG test_deallocate(VOID *object_ptr) +{ + +ULONG status; + + + test_interrupts_clear(); + + status = (ULONG) _txm_module_manager_object_deallocate(object_ptr); + + test_interrupts_check("the deallocation"); + + return(status); +} + + +int main(void) +{ + +TXM_MODULE_INSTANCE module_a; +TXM_MODULE_INSTANCE module_b; +const TEST_OBJECT_KIND *kind; +VOID *object; +VOID *second_object; +VOID *third_object; +VOID *reused_object; +TXM_MODULE_ALLOCATED_OBJECT *header_ptr; +ALIGN_TYPE address; +ULONG index; +ULONG offset; +ULONG reported_size; +char description[160]; + + + /* Report expectations before anything can crash, so that a case that brings the + process down is still preceded by everything that passed. */ + setvbuf(stdout, TX_NULL, _IONBF, 0); + + test_failures = 0U; + test_checks = ((ULONG) 0); + + /* Set up two memory-protected modules. Module A does the asking throughout; + module B is there to own allocations A did not make. */ + module_a.txm_module_instance_property_flags = TXM_MODULE_MEMORY_PROTECTION; + module_a.txm_module_instance_code_start = (VOID *) test_module_code.test_code_bytes; + module_a.txm_module_instance_code_end = (VOID *) &test_module_code.test_code_bytes[TEST_MODULE_MEMORY_BYTES - 1U]; + module_a.txm_module_instance_data_start = (VOID *) test_module_data.test_data_bytes; + module_a.txm_module_instance_data_end = (VOID *) &test_module_data.test_data_bytes[TEST_MODULE_MEMORY_BYTES - 1U]; + + module_b = module_a; + + /**********************************************************************/ + /* Every object type, owned and not owned. */ + /**********************************************************************/ + + for (index = ((ULONG) 0); index < (ULONG) TEST_OBJECT_KIND_COUNT; index++) + { + kind = &test_object_kinds[index]; + + test_reset(&module_a, &module_b); + + object = test_allocate(&module_a, kind -> test_kind_size); + + (void) snprintf(description, sizeof(description), + "the module that allocated a %s may delete it", kind -> test_kind_name); + test_expect(description, test_owner_check(&module_a, object, kind -> test_kind_size), (ULONG) TX_TRUE); + + /* The search has to have run with interrupts disabled, which is what keeps + it from observing the allocation list being changed under it. */ + test_expect("...found with interrupts disabled, one deep", + (ULONG) test_interrupt_disable_max_depth, ((ULONG) 1)); + + (void) snprintf(description, sizeof(description), + "another module may not delete that %s", kind -> test_kind_name); + test_expect(description, test_owner_check(&module_b, object, kind -> test_kind_size), (ULONG) TX_FALSE); + + (void) snprintf(description, sizeof(description), + "...and the refusal also ran with interrupts disabled"); + test_expect(description, (ULONG) test_interrupt_disable_max_depth, ((ULONG) 1)); + + (void) snprintf(description, sizeof(description), + "a %s allocation is not accepted as a larger object", kind -> test_kind_name); + test_expect(description, + test_owner_check(&module_a, object, kind -> test_kind_size + ((ULONG) sizeof(ALIGN_TYPE))), + (ULONG) TX_FALSE); + + (void) snprintf(description, sizeof(description), + "a %s allocation is not accepted as a smaller object", kind -> test_kind_name); + test_expect(description, + test_owner_check(&module_a, object, kind -> test_kind_size - ((ULONG) sizeof(ALIGN_TYPE))), + (ULONG) TX_FALSE); + + /* The search returns the allocation's own header, which is the one the + deallocation goes on to unlink. */ + test_interrupts_clear(); + reported_size = ((ULONG) 0); + header_ptr = _txm_module_manager_allocated_object_find(&module_a, (ALIGN_TYPE) object, &reported_size); + test_interrupts_check("the search"); + + (void) snprintf(description, sizeof(description), + "the search finds the header in front of the %s", kind -> test_kind_name); + test_expect(description, + (ULONG) (header_ptr == (((TXM_MODULE_ALLOCATED_OBJECT *) object) - 1)), (ULONG) TX_TRUE); + + (void) snprintf(description, sizeof(description), + "...and reports the size the %s allocation was made for", kind -> test_kind_name); + test_expect(description, reported_size, kind -> test_kind_size); + + /* A caller that does not want the size gets the same answer. */ + test_interrupts_clear(); + header_ptr = _txm_module_manager_allocated_object_find(&module_a, (ALIGN_TYPE) object, TX_NULL); + test_interrupts_check("the search"); + + test_expect("a search that does not ask for the size finds the same allocation", + (ULONG) (header_ptr == (((TXM_MODULE_ALLOCATED_OBJECT *) object) - 1)), (ULONG) TX_TRUE); + + /* Another module asking for the same address is told there is nothing, and + is told it without a size being written anywhere. */ + test_interrupts_clear(); + reported_size = (ULONG) 0xA5A5A5A5UL; + header_ptr = _txm_module_manager_allocated_object_find(&module_b, (ALIGN_TYPE) object, &reported_size); + test_interrupts_check("the search"); + + test_expect("a search of another module's allocations finds nothing", + (ULONG) (header_ptr == TX_NULL), (ULONG) TX_TRUE); + test_expect("...and reports no size", + reported_size, (ULONG) 0xA5A5A5A5UL); + + /* The deallocation the delete dispatcher performs after a successful delete + still goes through for the module that owns the memory. */ + test_release_size(kind -> test_kind_size); + + (void) snprintf(description, sizeof(description), + "the owning module gets its %s memory back", kind -> test_kind_name); + test_expect(description, test_deallocate(object), (ULONG) TX_SUCCESS); + test_expect("...released exactly once", test_releases, ((ULONG) 1)); + test_expect("...leaving nothing on its allocation list", + module_a.txm_module_instance_object_list_count, ((ULONG) 0)); + } + + /**********************************************************************/ + /* The reported call: an object the application owns. */ + /**********************************************************************/ + + test_reset(&module_a, &module_b); + + object = test_allocate(&module_a, (ULONG) sizeof(TX_QUEUE)); + + /* What the fixture is: the words in front of the application's object name the + module that is about to ask, so a manager that reads them finds its own + header there. That is the read the fix removes. */ + test_expect("the words in front of the application's object name the asking module", + (ULONG) ((((TXM_MODULE_ALLOCATED_OBJECT *) &test_host_area.test_host_object) - 1) + -> txm_module_allocated_object_module_instance == &module_a), + (ULONG) TX_TRUE); + + test_expect("the application's object is outside the object pool", + (ULONG) (((UCHAR *) &test_host_area.test_host_object) < test_pool_arena.test_pool_bytes || + ((UCHAR *) &test_host_area.test_host_object) >= &test_pool_arena.test_pool_bytes[TEST_POOL_BYTES]), + (ULONG) TX_TRUE); + + test_expect("a module may not delete an object the application owns", + test_owner_check(&module_a, &test_host_area.test_host_object, (ULONG) sizeof(TX_QUEUE)), + (ULONG) TX_FALSE); + + test_expect("and the manager will not give its memory back either", + test_deallocate(&test_host_area.test_host_object), (ULONG) TX_PTR_ERROR); + + test_expect("...having released nothing", test_releases, ((ULONG) 0)); + + test_expect("...left the module's own allocation on its list", + module_a.txm_module_instance_object_list_count, ((ULONG) 1)); + + test_expect("...with the list head where it was", + (ULONG) (module_a.txm_module_instance_object_list_head == (((TXM_MODULE_ALLOCATED_OBJECT *) object) - 1)), + (ULONG) TX_TRUE); + + test_expect("...and the application's object untouched", + (ULONG) test_host_area.test_host_object.tx_queue_id, (ULONG) TX_QUEUE_ID); + + /* The module's own object is unaffected by the refusal, and still deletable. */ + test_release_size((ULONG) sizeof(TX_QUEUE)); + test_expect("the module can still delete its own object afterwards", + test_deallocate(object), (ULONG) TX_SUCCESS); + + /**********************************************************************/ + /* An object another module owns. */ + /**********************************************************************/ + + test_reset(&module_a, &module_b); + + object = test_allocate(&module_a, (ULONG) sizeof(TX_QUEUE)); + second_object = test_allocate(&module_b, (ULONG) sizeof(TX_QUEUE)); + + test_expect("module A may delete its own object", + test_owner_check(&module_a, object, (ULONG) sizeof(TX_QUEUE)), (ULONG) TX_TRUE); + test_expect("module A may not delete module B's object", + test_owner_check(&module_a, second_object, (ULONG) sizeof(TX_QUEUE)), (ULONG) TX_FALSE); + test_expect("module B may delete its own object", + test_owner_check(&module_b, second_object, (ULONG) sizeof(TX_QUEUE)), (ULONG) TX_TRUE); + test_expect("module B may not delete module A's object", + test_owner_check(&module_b, object, (ULONG) sizeof(TX_QUEUE)), (ULONG) TX_FALSE); + + /* And the deallocation refuses it too, so a module that reached it another way + is stopped there as well. */ + test_expect("module A cannot give back module B's memory", + test_deallocate(second_object), (ULONG) TX_PTR_ERROR); + test_expect("...having released nothing", test_releases, ((ULONG) 0)); + test_expect("...leaving module B's allocation on module B's list", + module_b.txm_module_instance_object_list_count, ((ULONG) 1)); + + /**********************************************************************/ + /* Addresses that are not the start of an allocation. */ + /**********************************************************************/ + + test_reset(&module_a, &module_b); + + object = test_allocate(&module_a, (ULONG) sizeof(TX_QUEUE)); + second_object = test_allocate(&module_a, (ULONG) sizeof(TX_QUEUE)); + + test_expect("the header in front of an allocation is not the allocation", + test_owner_check(&module_a, (VOID *) ((((TXM_MODULE_ALLOCATED_OBJECT *) object) - 1)), + (ULONG) sizeof(TX_QUEUE)), + (ULONG) TX_FALSE); + + /* Every aligned address inside the object, which is where a shifted pointer + lands. The bytes in front of one of these are the object's own, so a manager + that read a header there would be reading what the module put there. */ + for (offset = (ULONG) sizeof(ALIGN_TYPE); + offset < (ULONG) sizeof(TX_QUEUE); + offset = offset + (ULONG) sizeof(ALIGN_TYPE)) + { + address = ((ALIGN_TYPE) object) + (ALIGN_TYPE) offset; + + (void) snprintf(description, sizeof(description), + "an address %lu bytes into an allocation is not that allocation", (unsigned long) offset); + test_expect(description, test_owner_check(&module_a, (VOID *) address, (ULONG) sizeof(TX_QUEUE)), + (ULONG) TX_FALSE); + + (void) snprintf(description, sizeof(description), + "...and the manager will not give back %lu bytes in", (unsigned long) offset); + test_expect(description, test_deallocate((VOID *) address), (ULONG) TX_PTR_ERROR); + } + + test_expect("the address one past an allocation is not that allocation", + test_owner_check(&module_a, (VOID *) (((ALIGN_TYPE) object) + (ALIGN_TYPE) sizeof(TX_QUEUE)), + (ULONG) sizeof(TX_QUEUE)), + (ULONG) TX_FALSE); + + test_expect("nothing was released by any of that", test_releases, ((ULONG) 0)); + test_expect("...and both allocations are still on the list", + module_a.txm_module_instance_object_list_count, ((ULONG) 2)); + + /**********************************************************************/ + /* Addresses with nothing behind them at all. */ + /**********************************************************************/ + + test_reset(&module_a, &module_b); + + object = test_allocate(&module_a, (ULONG) sizeof(TX_QUEUE)); + + test_expect("a null pointer names no allocation", + test_owner_check(&module_a, TX_NULL, (ULONG) sizeof(TX_QUEUE)), (ULONG) TX_FALSE); + test_expect("...and is refused by the deallocation", + test_deallocate(TX_NULL), (ULONG) TX_PTR_ERROR); + + test_expect("an address with no room for a header in front of it names no allocation", + test_owner_check(&module_a, (VOID *) ((ALIGN_TYPE) sizeof(TXM_MODULE_ALLOCATED_OBJECT)), + (ULONG) sizeof(TX_QUEUE)), + (ULONG) TX_FALSE); + + test_expect("the start of the object pool names no allocation", + test_owner_check(&module_a, (VOID *) test_pool_arena.test_pool_bytes, (ULONG) sizeof(TX_QUEUE)), + (ULONG) TX_FALSE); + + test_expect("the last byte of the object pool names no allocation", + test_owner_check(&module_a, (VOID *) &test_pool_arena.test_pool_bytes[TEST_POOL_BYTES - 1U], + (ULONG) sizeof(TX_QUEUE)), + (ULONG) TX_FALSE); + + test_expect("the address one past the object pool names no allocation", + test_owner_check(&module_a, (VOID *) &test_pool_arena.test_pool_bytes[TEST_POOL_BYTES], + (ULONG) sizeof(TX_QUEUE)), + (ULONG) TX_FALSE); + + test_expect("an address inside the calling module's own data names no allocation", + test_owner_check(&module_a, (VOID *) test_module_data.test_data_bytes, (ULONG) sizeof(TX_QUEUE)), + (ULONG) TX_FALSE); + + test_expect("nothing was released by any of that", test_releases, ((ULONG) 0)); + + /**********************************************************************/ + /* A request that comes from no module. */ + /**********************************************************************/ + + test_reset(&module_a, &module_b); + + object = test_allocate(&module_a, (ULONG) sizeof(TX_QUEUE)); + + test_expect("no module owns anything", + test_owner_check(TX_NULL, object, (ULONG) sizeof(TX_QUEUE)), (ULONG) TX_FALSE); + + test_interrupts_clear(); + header_ptr = _txm_module_manager_allocated_object_find(TX_NULL, (ALIGN_TYPE) object, TX_NULL); + test_interrupts_check("the search"); + + test_expect("...and there is no list to search", + (ULONG) (header_ptr == TX_NULL), (ULONG) TX_TRUE); + test_expect("...reached without disabling interrupts at all", + (ULONG) test_interrupt_disable_max_depth, ((ULONG) 0)); + + /* The deallocation service is documented as callable by the application, and an + application thread carries no module instance. */ + test_current_thread.tx_thread_module_instance_ptr = TX_NULL; + + test_expect("a caller that is not a module cannot give a module's memory back", + test_deallocate(object), (ULONG) TX_PTR_ERROR); + test_expect("...having released nothing", test_releases, ((ULONG) 0)); + + test_current_thread.tx_thread_module_instance_ptr = &module_a; + + /**********************************************************************/ + /* Memory that has changed hands. */ + /**********************************************************************/ + + test_reset(&module_a, &module_b); + + object = test_allocate(&module_a, (ULONG) sizeof(TX_QUEUE)); + + test_release_size((ULONG) sizeof(TX_QUEUE)); + test_expect("module A gives its memory back", test_deallocate(object), (ULONG) TX_SUCCESS); + + test_expect("...and may not delete the object there any more", + test_owner_check(&module_a, object, (ULONG) sizeof(TX_QUEUE)), (ULONG) TX_FALSE); + + test_expect("...nor give the same address back a second time", + test_deallocate(object), (ULONG) TX_PTR_ERROR); + + /* The pool hands the same address to module B, and the stale pointer module A + still holds now names memory that belongs to someone else. */ + reused_object = test_allocate(&module_b, (ULONG) sizeof(TX_QUEUE)); + + test_expect("the pool handed the same address to module B", + (ULONG) (reused_object == object), (ULONG) TX_TRUE); + test_expect("module A's stale pointer does not let it delete module B's object", + test_owner_check(&module_a, reused_object, (ULONG) sizeof(TX_QUEUE)), (ULONG) TX_FALSE); + test_expect("module B may delete what it was given", + test_owner_check(&module_b, reused_object, (ULONG) sizeof(TX_QUEUE)), (ULONG) TX_TRUE); + test_expect("module A's stale pointer does not get the memory back either", + test_deallocate(reused_object), (ULONG) TX_PTR_ERROR); + test_expect("...leaving module B's allocation on module B's list", + module_b.txm_module_instance_object_list_count, ((ULONG) 1)); + + /**********************************************************************/ + /* What bounds the search. */ + /**********************************************************************/ + + test_reset(&module_a, &module_b); + + object = test_allocate(&module_a, (ULONG) sizeof(TX_QUEUE)); + second_object = test_allocate(&module_a, (ULONG) sizeof(TX_QUEUE)); + third_object = test_allocate(&module_a, (ULONG) sizeof(TX_QUEUE)); + + test_expect("three allocations are on the list", module_a.txm_module_instance_object_list_count, ((ULONG) 3)); + test_expect("each of the three is found", + test_owner_check(&module_a, object, (ULONG) sizeof(TX_QUEUE)) + + test_owner_check(&module_a, second_object, (ULONG) sizeof(TX_QUEUE)) + + test_owner_check(&module_a, third_object, (ULONG) sizeof(TX_QUEUE)), + ((ULONG) 3) * (ULONG) TX_TRUE); + + /* A count smaller than the list stops the search early. Only the allocations + the count covers are found, which is what keeps the cost of a check bounded + by what the manager itself recorded. */ + module_a.txm_module_instance_object_list_count = ((ULONG) 1); + + test_expect("a search bounded by a count of one reaches the head", + test_owner_check(&module_a, module_a.txm_module_instance_object_list_head + 1, + (ULONG) sizeof(TX_QUEUE)), + (ULONG) TX_TRUE); + test_expect("...and goes no further", + test_owner_check(&module_a, object, (ULONG) sizeof(TX_QUEUE)) + + test_owner_check(&module_a, second_object, (ULONG) sizeof(TX_QUEUE)) + + test_owner_check(&module_a, third_object, (ULONG) sizeof(TX_QUEUE)), + (ULONG) TX_TRUE); + + module_a.txm_module_instance_object_list_count = ((ULONG) 3); + + /* A count larger than the list, with the chain broken, stops the search at the + break rather than running on into whatever follows. */ + (((TXM_MODULE_ALLOCATED_OBJECT *) second_object) - 1) -> txm_module_allocated_object_next = TX_NULL; + module_a.txm_module_instance_object_list_count = ((ULONG) 64); + + test_expect("a search stops at a broken link rather than running on", + test_owner_check(&module_a, third_object, (ULONG) sizeof(TX_QUEUE)), (ULONG) TX_FALSE); + test_expect("...having still found what came before the break", + test_owner_check(&module_a, second_object, (ULONG) sizeof(TX_QUEUE)), (ULONG) TX_TRUE); + + /* An empty list, which is the state a module starts in and the state the + manager is left in by module stop. */ + module_a.txm_module_instance_object_list_count = ((ULONG) 0); + module_a.txm_module_instance_object_list_head = TX_NULL; + + test_expect("a module that has allocated nothing owns nothing", + test_owner_check(&module_a, object, (ULONG) sizeof(TX_QUEUE)), (ULONG) TX_FALSE); + test_expect("...and gets nothing back", + test_deallocate(object), (ULONG) TX_PTR_ERROR); + + /**********************************************************************/ + /* Giving memory back, in each of the shapes the list can be in. */ + /**********************************************************************/ + + test_reset(&module_a, &module_b); + + object = test_allocate(&module_a, (ULONG) sizeof(TX_QUEUE)); + second_object = test_allocate(&module_a, (ULONG) sizeof(TX_QUEUE)); + third_object = test_allocate(&module_a, (ULONG) sizeof(TX_QUEUE)); + + test_expect("the first allocation is the list head", + (ULONG) (module_a.txm_module_instance_object_list_head == (((TXM_MODULE_ALLOCATED_OBJECT *) object) - 1)), + (ULONG) TX_TRUE); + + /* Releasing one that is not the head links its neighbours together and leaves + the head alone. */ + test_release_size((ULONG) sizeof(TX_QUEUE)); + test_expect("releasing an allocation in the middle succeeds", + test_deallocate(second_object), (ULONG) TX_SUCCESS); + test_expect("...leaving two on the list", module_a.txm_module_instance_object_list_count, ((ULONG) 2)); + test_expect("...with the head where it was", + (ULONG) (module_a.txm_module_instance_object_list_head == (((TXM_MODULE_ALLOCATED_OBJECT *) object) - 1)), + (ULONG) TX_TRUE); + test_expect("...and the released one no longer owned", + test_owner_check(&module_a, second_object, (ULONG) sizeof(TX_QUEUE)), (ULONG) TX_FALSE); + + /* Releasing the head moves it on. */ + test_release_size((ULONG) sizeof(TX_QUEUE)); + test_expect("releasing the head allocation succeeds", + test_deallocate(object), (ULONG) TX_SUCCESS); + test_expect("...leaving one on the list", module_a.txm_module_instance_object_list_count, ((ULONG) 1)); + test_expect("...and the head moved on to it", + (ULONG) (module_a.txm_module_instance_object_list_head == (((TXM_MODULE_ALLOCATED_OBJECT *) third_object) - 1)), + (ULONG) TX_TRUE); + + /* Releasing the last empties the list. */ + test_release_size((ULONG) sizeof(TX_QUEUE)); + test_expect("releasing the last allocation succeeds", + test_deallocate(third_object), (ULONG) TX_SUCCESS); + test_expect("...emptying the list", module_a.txm_module_instance_object_list_count, ((ULONG) 0)); + test_expect("...and clearing its head", + (ULONG) (module_a.txm_module_instance_object_list_head == TX_NULL), (ULONG) TX_TRUE); + test_expect("three allocations were released and no more", test_releases, ((ULONG) 3)); + + /**********************************************************************/ + /* An object pool that was never created. */ + /**********************************************************************/ + + test_reset(&module_a, &module_b); + + object = test_allocate(&module_a, (ULONG) sizeof(TX_QUEUE)); + + _txm_module_manager_object_pool_created = TX_FALSE; + + test_expect("with no object pool the deallocation reports the feature is not available", + test_deallocate(object), (ULONG) TX_NOT_AVAILABLE); + test_expect("...having released nothing", test_releases, ((ULONG) 0)); + + /* The ownership question does not depend on the pool flag: it is answered from + the module's allocation list, which is where the manager recorded what it + handed out. */ + test_expect("...and the ownership question is still answered from the allocation list", + test_owner_check(&module_a, object, (ULONG) sizeof(TX_QUEUE)), (ULONG) TX_TRUE); + + _txm_module_manager_object_pool_created = TX_TRUE; + + /**********************************************************************/ + /* What the whole run has to have held. */ + /**********************************************************************/ + + test_expect("every case ran", (ULONG) (test_checks > ((ULONG) 100)), (ULONG) TX_TRUE); + + if (test_failures == 0U) + { + printf("SUCCESS! %lu expectations\n", (unsigned long) test_checks); + return(0); + } + + printf("ERROR: %u expectation(s) failed of %lu\n", test_failures, (unsigned long) test_checks); + return(1); +} |
