summaryrefslogtreecommitdiff
path: root/test/tx/module_manager/threadx_module_manager_delete_ownership_test.c
diff options
context:
space:
mode:
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.c1031
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);
+}