diff --git a/common_modules/module_manager/inc/txm_module_manager_dispatch.h b/common_modules/module_manager/inc/txm_module_manager_dispatch.h index 9c0e5d21..6b75be7f 100644 --- a/common_modules/module_manager/inc/txm_module_manager_dispatch.h +++ b/common_modules/module_manager/inc/txm_module_manager_dispatch.h @@ -1,6 +1,7 @@ /*************************************************************************** * Copyright (c) 2024 Microsoft Corporation * Copyright (c) 2025 Eclipse ThreadX Contributors + * 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 @@ -12,6 +13,8 @@ // Some portions generated by Claude Code (Opus 5). +// Some portions generated by Claude Code (Opus 5). + /**************************************************************************/ /**************************************************************************/ @@ -106,6 +109,9 @@ ALIGN_TYPE return_value; { if (!TXM_MODULE_MANAGER_PARAM_CHECK_TYPED_OBJECT_FOR_USE(module_instance, param_0, TXM_BLOCK_POOL_OBJECT)) return(TXM_MODULE_INVALID_MEMORY); + + if (!TXM_MODULE_MANAGER_PARAM_CHECK_OBJECT_FOR_DELETION(module_instance, param_0, sizeof(TX_BLOCK_POOL))) + return(TXM_MODULE_INVALID_MEMORY); } return_value = (ALIGN_TYPE) _txe_block_pool_delete( @@ -410,6 +416,9 @@ ALIGN_TYPE return_value; { if (!TXM_MODULE_MANAGER_PARAM_CHECK_TYPED_OBJECT_FOR_USE(module_instance, param_0, TXM_BYTE_POOL_OBJECT)) return(TXM_MODULE_INVALID_MEMORY); + + if (!TXM_MODULE_MANAGER_PARAM_CHECK_OBJECT_FOR_DELETION(module_instance, param_0, sizeof(TX_BYTE_POOL))) + return(TXM_MODULE_INVALID_MEMORY); } return_value = (ALIGN_TYPE) _txe_byte_pool_delete( @@ -700,6 +709,9 @@ ALIGN_TYPE return_value; { if (!TXM_MODULE_MANAGER_PARAM_CHECK_TYPED_OBJECT_FOR_USE(module_instance, param_0, TXM_EVENT_FLAGS_OBJECT)) return(TXM_MODULE_INVALID_MEMORY); + + if (!TXM_MODULE_MANAGER_PARAM_CHECK_OBJECT_FOR_DELETION(module_instance, param_0, sizeof(TX_EVENT_FLAGS_GROUP))) + return(TXM_MODULE_INVALID_MEMORY); } return_value = (ALIGN_TYPE) _txe_event_flags_delete( @@ -1005,6 +1017,9 @@ ALIGN_TYPE return_value; { if (!TXM_MODULE_MANAGER_PARAM_CHECK_TYPED_OBJECT_FOR_USE(module_instance, param_0, TXM_MUTEX_OBJECT)) return(TXM_MODULE_INVALID_MEMORY); + + if (!TXM_MODULE_MANAGER_PARAM_CHECK_OBJECT_FOR_DELETION(module_instance, param_0, sizeof(TX_MUTEX))) + return(TXM_MODULE_INVALID_MEMORY); } return_value = (ALIGN_TYPE) _txe_mutex_delete( @@ -1302,6 +1317,9 @@ ALIGN_TYPE return_value; { if (!TXM_MODULE_MANAGER_PARAM_CHECK_TYPED_OBJECT_FOR_USE(module_instance, param_0, TXM_QUEUE_OBJECT)) return(TXM_MODULE_INVALID_MEMORY); + + if (!TXM_MODULE_MANAGER_PARAM_CHECK_OBJECT_FOR_DELETION(module_instance, param_0, sizeof(TX_QUEUE))) + return(TXM_MODULE_INVALID_MEMORY); } return_value = (ALIGN_TYPE) _txe_queue_delete( @@ -1734,6 +1752,9 @@ ALIGN_TYPE return_value; { if (!TXM_MODULE_MANAGER_PARAM_CHECK_TYPED_OBJECT_FOR_USE(module_instance, param_0, TXM_SEMAPHORE_OBJECT)) return(TXM_MODULE_INVALID_MEMORY); + + if (!TXM_MODULE_MANAGER_PARAM_CHECK_OBJECT_FOR_DELETION(module_instance, param_0, sizeof(TX_SEMAPHORE))) + return(TXM_MODULE_INVALID_MEMORY); } return_value = (ALIGN_TYPE) _txe_semaphore_delete( @@ -2075,6 +2096,9 @@ ALIGN_TYPE stack_status; { if (!TXM_MODULE_MANAGER_PARAM_CHECK_TYPED_OBJECT_FOR_USE(module_instance, param_0, TXM_THREAD_OBJECT)) return(TXM_MODULE_INVALID_MEMORY); + + if (!TXM_MODULE_MANAGER_PARAM_CHECK_OBJECT_FOR_DELETION(module_instance, param_0, sizeof(TX_THREAD))) + return(TXM_MODULE_INVALID_MEMORY); } return_value = (ALIGN_TYPE) _txe_thread_delete(thread_ptr); @@ -2896,6 +2920,9 @@ ALIGN_TYPE return_value; { if (!TXM_MODULE_MANAGER_PARAM_CHECK_TYPED_OBJECT_FOR_USE(module_instance, param_0, TXM_TIMER_OBJECT)) return(TXM_MODULE_INVALID_MEMORY); + + if (!TXM_MODULE_MANAGER_PARAM_CHECK_OBJECT_FOR_DELETION(module_instance, param_0, sizeof(TX_TIMER))) + return(TXM_MODULE_INVALID_MEMORY); } return_value = (ALIGN_TYPE) _txe_timer_delete( diff --git a/common_modules/module_manager/inc/txm_module_manager_util.h b/common_modules/module_manager/inc/txm_module_manager_util.h index 77631bea..2630fe29 100644 --- a/common_modules/module_manager/inc/txm_module_manager_util.h +++ b/common_modules/module_manager/inc/txm_module_manager_util.h @@ -120,6 +120,16 @@ #define TXM_MODULE_MANAGER_PARAM_CHECK_OBJECT_FOR_CREATION(module_instance, obj_ptr, obj_size) \ (_txm_module_manager_param_check_object_for_creation(module_instance, obj_ptr, obj_size)) +/* When deleting an object, being allowed to use it is not enough. Deletion is the + inverse of creation, so it asks what creation asks: that the object is one this + module allocated from the manager's object pool, at the exact address the manager + gave it, for an object of this size. A module may hold a pointer to an object it + does not own -- the manager's object lookup service hands application-owned + objects to modules by name, and sharing an object is what that service is for -- + and this is what separates using such an object from destroying it. */ +#define TXM_MODULE_MANAGER_PARAM_CHECK_OBJECT_FOR_DELETION(module_instance, obj_ptr, obj_size) \ + (_txm_module_manager_allocated_object_check(module_instance, obj_ptr, obj_size)) + /* Strings we dereference can be in RW/RO/Shared areas. */ #define TXM_MODULE_MANAGER_PARAM_CHECK_DEREFERENCE_STRING(module_instance, string_ptr) \ ((TXM_MODULE_MANAGER_ENSURE_INSIDE_MODULE(module_instance, string_ptr, 1)) || \ @@ -147,6 +157,8 @@ UINT _txm_module_manager_param_check_object_for_creation(TXM_MODULE_INSTANCE UINT _txm_module_manager_param_check_object_for_use(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE object_ptr, ULONG object_size); UINT _txm_module_manager_param_check_typed_object_for_use(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE object_ptr, UINT object_type); UINT _txm_module_manager_allocated_object_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE object_ptr, ULONG object_size); +TXM_MODULE_ALLOCATED_OBJECT + *_txm_module_manager_allocated_object_find(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE object_ptr, ULONG *object_size_ptr); UINT _txm_module_manager_object_type_size_get(UINT object_type, ULONG *object_size); UINT _txm_module_manager_created_object_type_check(ALIGN_TYPE object_ptr, UINT object_type); UINT _txm_module_manager_object_id_check(ALIGN_TYPE object_ptr, UINT object_type); diff --git a/common_modules/module_manager/src/txm_module_manager_object_deallocate.c b/common_modules/module_manager/src/txm_module_manager_object_deallocate.c index c34a0005..43d96f3c 100644 --- a/common_modules/module_manager/src/txm_module_manager_object_deallocate.c +++ b/common_modules/module_manager/src/txm_module_manager_object_deallocate.c @@ -27,6 +27,7 @@ #include "tx_api.h" #include "tx_thread.h" #include "txm_module.h" +#include "txm_module_manager_util.h" /**************************************************************************/ /* */ @@ -52,6 +53,13 @@ /* tx_*_delete() service is sufficient; pool deallocation is handled */ /* automatically by the dispatch layer. */ /* */ +/* Only memory the calling module allocated from the object pool is */ +/* released, and the allocation is identified by searching the */ +/* module's own allocation list rather than by reading a private */ +/* header from in front of the address the caller supplied. An */ +/* address that names none of this module's allocations returns */ +/* TX_PTR_ERROR and is not dereferenced. */ +/* */ /* INPUT */ /* */ /* object_ptr Object pointer to deallocate */ @@ -64,6 +72,8 @@ /* */ /* _txe_mutex_get Get module instance mutex */ /* _txe_mutex_put Release module instance mutex */ +/* _txm_module_manager_allocated_object_find */ +/* Find the module's allocation */ /* _txe_byte_release Release object back to pool */ /* */ /* */ @@ -90,14 +100,32 @@ UINT return_value; /* Pickup module instance pointer. */ module_instance = _tx_thread_current_ptr -> tx_thread_module_instance_ptr; - /* Setup the memory pointer. */ - module_allocated_object_ptr = (TXM_MODULE_ALLOCATED_OBJECT *) object_ptr; + /* Find the allocation this address names. - /* Position the object pointer backwards to position back to the module manager information. */ - previous_object = module_allocated_object_ptr--; + The private header in front of an allocation is the manager's own + record of who the memory belongs to, but it is only a header when the + address is one the manager gave out. Reaching it by subtraction from + whatever address the caller supplied assumes the answer: for an object + the application allocated statically, or for any address outside the + object pool, the words in front of it are unrelated memory, and this + read them in privileged mode before deciding they were not a header. + Every delete dispatcher reaches this function with the address the + module named, so an object the module does not own arrived here as a + matter of course rather than exceptionally. + + Searching the module's own allocation list answers the same question + without that assumption. The address is compared against the + allocations the manager made for this module and is never + dereferenced, so an address that names none of them -- an object the + application owns, an object another module owns, or an address nowhere + near the object pool -- is refused without a privileged read. The + search also establishes what the header read could not: that the + address is the exact start of an allocation rather than somewhere + inside one. */ + module_allocated_object_ptr = _txm_module_manager_allocated_object_find(module_instance, (ALIGN_TYPE) object_ptr, TX_NULL); /* Make sure the object is valid. */ - if ((module_allocated_object_ptr == TX_NULL) || (module_allocated_object_ptr -> txm_module_allocated_object_module_instance != module_instance) || (module_instance -> txm_module_instance_object_list_count == 0)) + if (module_allocated_object_ptr == TX_NULL) { /* Set return value to invalid pointer. */ return_value = TX_PTR_ERROR; diff --git a/common_modules/module_manager/src/txm_module_manager_util.c b/common_modules/module_manager/src/txm_module_manager_util.c index 1b60f900..ba7e5a29 100644 --- a/common_modules/module_manager/src/txm_module_manager_util.c +++ b/common_modules/module_manager/src/txm_module_manager_util.c @@ -629,6 +629,145 @@ UINT status; } +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_allocated_object_find PORTABLE C */ +/* 6.4.3 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function returns the manager's private header for the object */ +/* allocation an address names, or TX_NULL when the address names */ +/* none of the specified module's allocations. The size the */ +/* allocation was made for is reported to callers that ask for it. */ +/* */ +/* The allocation list is the manager's own record, built as it hands */ +/* object memory out, and it is searched rather than reached through */ +/* a header taken from in front of the caller's address. That order */ +/* is what makes the answer both correct and safe. */ +/* */ +/* Correct, because the address immediately after a header is the */ +/* only address in an allocation that the manager ever gave out, so */ +/* an address chosen inside an allocation is distinguishable from the */ +/* start of one. Reading a header cannot make that distinction: the */ +/* bytes in front of an interior address are part of the object, and */ +/* a module places values there through ordinary services. */ +/* */ +/* Safe, because the caller's address is only ever compared, never */ +/* dereferenced. An address that belongs to no allocation of this */ +/* module -- an object the application owns, an object another module */ +/* owns, or an address nowhere near the object pool -- is rejected */ +/* without a privileged read of the words in front of it. */ +/* */ +/* INPUT */ +/* */ +/* module_instance Requesting module instance pointer*/ +/* object_ptr Address of object memory area */ +/* object_size_ptr Address to return the size the */ +/* allocation was made for, or */ +/* TX_NULL to not return it */ +/* */ +/* OUTPUT */ +/* */ +/* allocation The allocation's private header, */ +/* or TX_NULL when the address */ +/* names none of this module's */ +/* */ +/* CALLS */ +/* */ +/* None */ +/* */ +/* CALLED BY */ +/* */ +/* _txm_module_manager_allocated_object_check */ +/* Module object ownership check */ +/* _txm_module_manager_object_deallocate Module object deallocation */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* xx-xx-2026 Eclipse ThreadX Initial Version 6.4.3 */ +/* contributors */ +/* */ +/**************************************************************************/ +TXM_MODULE_ALLOCATED_OBJECT *_txm_module_manager_allocated_object_find(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE object_ptr, ULONG *object_size_ptr) +{ + +TX_INTERRUPT_SAVE_AREA + +TXM_MODULE_ALLOCATED_OBJECT *allocated_object_ptr; +TXM_MODULE_ALLOCATED_OBJECT *found_object_ptr; +ULONG objects_examined; + + + /* Assume the address names none of this module's allocations. */ + found_object_ptr = TX_NULL; + + /* Determine if there is a module whose allocations can be searched. A request + that does not come from a module owns no allocation. */ + if (module_instance == TX_NULL) + { + + /* Nothing to search, and nothing to report. */ + return(TX_NULL); + } + + /* Disable interrupts. The allocation list is maintained by threads holding the + manager protection mutex, so a scan that runs to completion with interrupts + disabled cannot observe it being changed, and unlike taking the mutex it adds + no blocking point and no priority inversion to a kernel request. */ + TX_DISABLE + + allocated_object_ptr = module_instance -> txm_module_instance_object_list_head; + objects_examined = ((ULONG) 0); + + /* Loop through the objects allocated to this module. The loop is bounded by the + count the manager maintains alongside the list, so the cost of one search is + bounded by the number of objects this module has allocated, and a list whose + links have been damaged cannot make the scan run on. */ + while ((objects_examined < module_instance -> txm_module_instance_object_list_count) && + (allocated_object_ptr != TX_NULL)) + { + + /* The address the module was given is the one immediately after the private + header, so that is the only address in this allocation that names it. */ + if (((ALIGN_TYPE) (allocated_object_ptr + 1)) == object_ptr) + { + + /* Found it. An address matches at most one allocation, so there is + nothing further to look at. */ + found_object_ptr = allocated_object_ptr; + + /* Report the size this allocation was made for, if the caller asked for + it. It is read here, inside the scan, so that the answer cannot be + taken from a header that stopped being one after the search. */ + if (object_size_ptr != TX_NULL) + { + + *object_size_ptr = allocated_object_ptr -> txm_module_object_size; + } + + break; + } + + /* Move to the next allocated object. */ + objects_examined++; + allocated_object_ptr = allocated_object_ptr -> txm_module_allocated_object_next; + } + + /* Restore interrupts. */ + TX_RESTORE + + return(found_object_ptr); +} + + /**************************************************************************/ /* */ /* FUNCTION RELEASE */ @@ -646,13 +785,14 @@ UINT status; /* allocations, and whether that allocation is the size the caller */ /* expects. */ /* */ -/* The allocation list is the manager's own record, built as it hands */ -/* object memory out. Comparing against it is what makes an address */ -/* inside the object pool distinguishable from the exact start of an */ -/* allocation. Reading a header in front of a candidate address cannot */ -/* make that distinction: the bytes in front of an address chosen */ -/* inside an allocation are part of the object, and a module can place */ -/* values there through ordinary create and set services. */ +/* This is the question of ownership, and it is deliberately a */ +/* separate question from whether an object is there at all. A */ +/* module may hold, and legitimately use, a pointer to an object it */ +/* does not own: the manager hands application-owned objects to */ +/* modules by name through its object lookup service, and sharing an */ +/* object is what that service is for. Destroying one is not sharing */ +/* it, so the delete services ask this question in addition to the */ +/* ones that establish that the address denotes a usable object. */ /* */ /* INPUT */ /* */ @@ -667,12 +807,12 @@ UINT status; /* */ /* CALLS */ /* */ -/* None */ +/* _txm_module_manager_allocated_object_find */ +/* Find the module's allocation */ /* */ /* CALLED BY */ /* */ -/* _txm_module_manager_param_check_object_for_use */ -/* Module object authentication */ +/* txm_module_manager_* Module manager functions */ /* */ /* RELEASE HISTORY */ /* */ @@ -685,60 +825,31 @@ UINT status; UINT _txm_module_manager_allocated_object_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE object_ptr, ULONG object_size) { -TX_INTERRUPT_SAVE_AREA - -TXM_MODULE_ALLOCATED_OBJECT *allocated_object_ptr; -ULONG objects_examined; -UINT status; +ULONG allocated_size; +UINT status; /* Assume the address is not one of this module's allocations. */ status = TX_FALSE; - /* Disable interrupts. The allocation list is maintained by threads holding the - manager protection mutex, so a scan that runs to completion with interrupts - disabled cannot observe it being changed, and unlike taking the mutex it adds - no blocking point and no priority inversion to a kernel request. */ - TX_DISABLE + /* Initialize the size, so that nothing is read if no allocation is found. */ + allocated_size = ((ULONG) 0); - allocated_object_ptr = module_instance -> txm_module_instance_object_list_head; - objects_examined = ((ULONG) 0); - - /* Loop through the objects allocated to this module. The loop is bounded by the - count the manager maintains alongside the list, so the cost of one check is - bounded by the number of objects this module has allocated, and a list whose - links have been damaged cannot make the scan run on. */ - while ((objects_examined < module_instance -> txm_module_instance_object_list_count) && - (allocated_object_ptr != TX_NULL)) + /* Determine if the address is the exact start of one of this module's + allocations. */ + if (_txm_module_manager_allocated_object_find(module_instance, object_ptr, &allocated_size) != TX_NULL) { - /* The address the module was given is the one immediately after the private - header, so that is the only address in this allocation that names it. */ - if (((ALIGN_TYPE) (allocated_object_ptr + 1)) == object_ptr) + /* Is the allocation the size the caller expects? An allocation made for a + smaller object does not become a larger one because a service was asked + to treat it as one. */ + if (allocated_size == object_size) { - /* Is the allocation the size the caller expects? An allocation made for a - smaller object does not become a larger one because a service was asked - to treat it as one. */ - if (allocated_object_ptr -> txm_module_object_size == object_size) - { - - status = TX_TRUE; - } - - /* An address matches at most one allocation, so there is nothing further - to look at either way. */ - break; + status = TX_TRUE; } - - /* Move to the next allocated object. */ - objects_examined++; - allocated_object_ptr = allocated_object_ptr -> txm_module_allocated_object_next; } - /* Restore interrupts. */ - TX_RESTORE - return(status); } diff --git a/test/tx/cmake/module_manager/CMakeLists.txt b/test/tx/cmake/module_manager/CMakeLists.txt index 35a6f8bb..695a1fe5 100644 --- a/test/tx/cmake/module_manager/CMakeLists.txt +++ b/test/tx/cmake/module_manager/CMakeLists.txt @@ -103,3 +103,27 @@ target_compile_options( add_test(${CMAKE_BUILD_TYPE}::threadx_module_manager_object_authentication_test threadx_module_manager_object_authentication_test) + +# This test drives the manager services directly rather than through a dispatcher, +# so it needs none of the dispatch table and no guard list. +add_executable( + threadx_module_manager_delete_ownership_test + ${SOURCE_DIR}/threadx_module_manager_delete_ownership_test.c + ${module_manager_dir}/src/txm_module_manager_util.c + ${module_manager_dir}/src/txm_module_manager_object_allocate.c + ${module_manager_dir}/src/txm_module_manager_object_deallocate.c) + +target_include_directories( + threadx_module_manager_delete_ownership_test + PRIVATE ${SOURCE_DIR} + ${REPO_ROOT}/common/inc + ${REPO_ROOT}/common_modules/inc + ${module_manager_dir}/inc + ${cortex_a7_module_dir}/inc) + +target_compile_options( + threadx_module_manager_delete_ownership_test + PRIVATE -include ${SOURCE_DIR}/threadx_module_manager_host_test_port.h) + +add_test(${CMAKE_BUILD_TYPE}::threadx_module_manager_delete_ownership_test + threadx_module_manager_delete_ownership_test) 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..a8477253 --- /dev/null +++ b/test/tx/module_manager/threadx_module_manager_delete_ownership_test.c @@ -0,0 +1,992 @@ +/*************************************************************************** + * 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 +#include + +#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(ULONG obj_ptr) +{ + +ULONG data_start; +ULONG data_end; + + + data_start = (ULONG) (ALIGN_TYPE) test_module_data.test_data_bytes; + data_end = data_start + ((ULONG) TEST_MODULE_MEMORY_BYTES); + + if ((obj_ptr >= data_start) && (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); +}