/*************************************************************************** * 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 **************************************************************************/ /* This test drives the MISRA compliance shim. A MISRA build replaces the kernel's pointer conversions, its user timer derivation and its thread stack check with calls to functions that do the same work behind a prototype. The test drives each of them through the macro the kernel itself uses, so the shim and the in-line form the other builds compile are asserted to agree. The stack check is the one with logic in it. It scores seven conditions: whether there is a thread to check at all, whether its control block is still a thread, whether its stack pointer has gone below the lowest recorded, whether either of the two fill patterns that fence the stack has been overwritten, whether the lowest recorded stack pointer has dropped below the stack, and whether the word under that pointer is still fill. The test gives a scratch thread a fault in turn and checks it with the fault in place, so every condition is taken both ways. Every fault is written into the scratch thread's own control block or into the words the test laid out around its stack; the kernel is not modified to produce any of them. The scratch thread is created and never started, and the stack it is given is a buffer of the test's own with a fence on each side, so no faulted state is ever visible to the scheduler. That matters more here than it does on a single processor: TX_DISABLE on SMP is _tx_thread_smp_protect, and the check releases it around the stack error handler and the stack analysis, so those two run with the other cores free. Nothing but this test refers to the scratch thread, so a fault planted before a check is still there when the check reads it, whatever else runs in between. One of the conversions belongs to the trace component rather than the kernel proper, and no kernel source uses the macro that reaches it, so it is driven here alongside the others. */ #include #include "tx_api.h" #define TX_SOURCE_CODE #include "tx_trace.h" /* The scratch thread's stack, fenced on both sides. The check reads the word immediately above the stack area and, when the lowest recorded stack pointer is dropped below the stack, the word immediately below it, so both have to belong to the test rather than to whatever happens to be laid out next. */ #define SHIM_FENCE_WORDS 4 #define SHIM_STACK_WORDS (TEST_STACK_SIZE_PRINTF / sizeof(ULONG)) static ULONG shim_stack_area[SHIM_FENCE_WORDS + SHIM_STACK_WORDS + SHIM_FENCE_WORDS]; #define SHIM_STACK_START (&shim_stack_area[SHIM_FENCE_WORDS]) static unsigned long thread_0_counter = 0; static TX_THREAD thread_0; /* The thread the stack check is driven against. It is created without being started, so nothing ever runs on the stack the test lays out for it. */ static TX_THREAD thread_1; static TX_TIMER timer_0; #ifdef TX_ENABLE_EVENT_TRACE /* A trace buffer entry for the one conversion whose macro no kernel source uses. Nothing reads it; the conversion only takes its address. */ static TX_TRACE_BUFFER_ENTRY trace_entry; #endif /* Define thread prototypes. */ static void thread_0_entry(ULONG thread_input); static void thread_1_entry(ULONG thread_input); /* Prototype for test control return. */ void test_control_return(UINT status); /* Define the timer entry, which never runs: the timer exists so that the user timer derivation has a real control block to recover. */ static void timer_0_entry(ULONG timer_input) { } #ifdef TX_MISRA_ENABLE /* Fill the scratch stack and both of its fences with the pattern the check reads as untouched, so that every condition below is decided by the fault the test plants and by nothing else. */ static void shim_stack_reset(TX_THREAD *thread_ptr) { UINT index; for (index = 0; index < (SHIM_FENCE_WORDS + SHIM_STACK_WORDS + SHIM_FENCE_WORDS); index++) { shim_stack_area[index] = TX_STACK_FILL; } /* Start from the thread's own stack pointer, which is where an untouched thread's lowest recorded pointer sits. */ thread_ptr -> tx_thread_stack_highest_ptr = thread_ptr -> tx_thread_stack_ptr; } #endif /* Define what the initial system looks like. */ #ifdef CTEST void test_application_define(void *first_unused_memory) #else void threadx_misra_shim_application_define(void *first_unused_memory) #endif { UINT status; CHAR *pointer; /* Put first available memory address into a character pointer. */ pointer = (CHAR *) first_unused_memory; /* Put system definition stuff in here, e.g. thread creates and other assorted create information. */ status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 1, pointer, TEST_STACK_SIZE_PRINTF, 16, 16, TX_NO_TIME_SLICE, TX_AUTO_START); pointer = pointer + TEST_STACK_SIZE_PRINTF; /* Check for status. */ if (status != TX_SUCCESS) { printf("Running MISRA Shim Test............................................ ERROR #1\n"); test_control_return(1); } /* Create the thread the stack check is driven against, on the test's own fenced stack area and without starting it. */ status = tx_thread_create(&thread_1, "thread 1", thread_1_entry, 0, (VOID *) SHIM_STACK_START, SHIM_STACK_WORDS * sizeof(ULONG), 20, 20, TX_NO_TIME_SLICE, TX_DONT_START); /* Check for status. */ if (status != TX_SUCCESS) { printf("Running MISRA Shim Test............................................ ERROR #2\n"); test_control_return(1); } /* Create the timer the user timer derivation recovers. */ status = tx_timer_create(&timer_0, "timer 0", timer_0_entry, 0, 100, 100, TX_NO_ACTIVATE); /* Check for status. */ if (status != TX_SUCCESS) { printf("Running MISRA Shim Test............................................ ERROR #3\n"); test_control_return(1); } } /* Define the test thread. */ static void thread_0_entry(ULONG thread_input) { TX_TIMER *recovered_timer; TX_THREAD *recovered_thread; UCHAR *recovered_name; VOID *converted; CHAR name[] = "thread 0"; #ifdef TX_MISRA_ENABLE TX_THREAD *thread_ptr; ULONG *stack_start; VOID *scratch_highest; ULONG saved_id; #endif /* Increment thread 0 counter. */ thread_0_counter++; /* Inform user of success getting to this test. */ printf("Running MISRA Shim Test............................................ "); /* A ULONG taken back to a pointer is the pointer it came from. */ converted = TX_ULONG_TO_POINTER_CONVERT(TX_POINTER_TO_ULONG_CONVERT(&thread_0)); if (converted != (VOID *) &thread_0) { printf("ERROR #4\n"); test_control_return(1); } /* A void pointer taken to a thread pointer is the thread it came from. */ recovered_thread = TX_VOID_TO_THREAD_POINTER_CONVERT((VOID *) &thread_0); if (recovered_thread != &thread_0) { printf("ERROR #5\n"); test_control_return(1); } /* A character pointer taken to an unsigned one still addresses the same string. */ recovered_name = TX_CHAR_TO_UCHAR_POINTER_CONVERT(name); if ((recovered_name == TX_NULL) || (*recovered_name != (UCHAR) 't')) { printf("ERROR #6\n"); test_control_return(1); } /* The internal timer block a timer expiration is handed is derived back to the application timer that contains it. */ TX_USER_TIMER_POINTER_GET(&(timer_0.tx_timer_internal), recovered_timer) if (recovered_timer != &timer_0) { printf("ERROR #7\n"); test_control_return(1); } #ifdef TX_ENABLE_EVENT_TRACE /* A trace buffer entry taken to an unsigned character pointer still addresses the entry. */ if (TX_ENTRY_TO_UCHAR_POINTER_CONVERT(&trace_entry) != (UCHAR *) &trace_entry) { printf("ERROR #8\n"); test_control_return(1); } #endif #ifdef TX_MISRA_ENABLE thread_ptr = &thread_1; stack_start = (ULONG *) thread_ptr -> tx_thread_stack_start; /* No thread to check at all. The kernel reaches the check through TX_THREAD_STACK_CHECK, which hands it the thread's own lowest recorded stack pointer; with no thread there is no such field, so this one case is driven through the shim directly with a pointer of the test's own. */ scratch_highest = (VOID *) stack_start; _tx_misra_thread_stack_check(TX_NULL, &scratch_highest); /* A control block that is no longer a thread. The check reads the identifier and does nothing else. */ shim_stack_reset(thread_ptr); saved_id = thread_ptr -> tx_thread_id; thread_ptr -> tx_thread_id = ((ULONG) 0); TX_THREAD_STACK_CHECK(thread_ptr) thread_ptr -> tx_thread_id = saved_id; /* An untouched thread: the stack is where it has always been and both fences are intact. */ shim_stack_reset(thread_ptr); TX_THREAD_STACK_CHECK(thread_ptr) /* The lowest recorded stack pointer is put above the thread's own stack pointer, so the check finds the stack deeper than it has recorded and lowers its mark. */ shim_stack_reset(thread_ptr); thread_ptr -> tx_thread_stack_highest_ptr = thread_ptr -> tx_thread_stack_end; TX_THREAD_STACK_CHECK(thread_ptr) if (thread_ptr -> tx_thread_stack_highest_ptr != thread_ptr -> tx_thread_stack_ptr) { printf("ERROR #9\n"); test_control_return(1); } /* The fence below the stack is overwritten, which is an underflow as far as the check is concerned. */ shim_stack_reset(thread_ptr); *stack_start = ~TX_STACK_FILL; TX_THREAD_STACK_CHECK(thread_ptr) /* The fence above the stack is overwritten, which is an overflow. The fence below is left intact, so the check reaches the second condition of the three before it reports. */ shim_stack_reset(thread_ptr); *((ULONG *) (((UCHAR *) thread_ptr -> tx_thread_stack_end) + 1)) = ~TX_STACK_FILL; TX_THREAD_STACK_CHECK(thread_ptr) /* The lowest recorded stack pointer is dropped below the stack itself, with both fences intact so that the check reaches the third condition. The word under that pointer is left as fill, so no stack analysis follows. */ shim_stack_reset(thread_ptr); thread_ptr -> tx_thread_stack_highest_ptr = (VOID *) (stack_start - 1); TX_THREAD_STACK_CHECK(thread_ptr) /* The word under the lowest recorded stack pointer is made used, so the check runs the stack analysis with both fences intact and the pointer inside the stack. */ shim_stack_reset(thread_ptr); *(stack_start + 1) = ~TX_STACK_FILL; thread_ptr -> tx_thread_stack_highest_ptr = (VOID *) (stack_start + 2); TX_THREAD_STACK_CHECK(thread_ptr) /* The analysis walks up from the bottom of the stack to the first used word and records it. */ if (thread_ptr -> tx_thread_stack_highest_ptr != (VOID *) (stack_start + 1)) { printf("ERROR #10\n"); test_control_return(1); } /* Leave the scratch thread with nothing planted in it. */ shim_stack_reset(thread_ptr); #endif printf("SUCCESS!\n"); test_control_return(0); } /* The scratch thread never runs. */ static void thread_1_entry(ULONG thread_input) { while (1) { tx_thread_suspend(&thread_1); } }