mirror of
https://github.com/eclipse-threadx/threadx.git
synced 2026-10-06 06:59:08 +08:00
* Fixed the resource leaks on the xQueueCreate error paths xQueueCreate() allocated the queue descriptor and its backing memory, then created two ThreadX semaphores, and returned NULL on either semaphore failure without releasing anything. Since no handle reached the caller, vQueueDelete() could not be used to recover, so both allocations were lost. A failure on the second semaphore additionally abandoned the read semaphore it had already created, leaving a live ThreadX control block inside freed memory. Release the backing memory and the descriptor on both paths, and delete the read semaphore before returning when the write semaphore cannot be created. This is the teardown order vQueueDelete() already uses, and it matches the cleanup xTaskCreate() performs on its own error paths. Verified with a fault injection harness that intercepts the ThreadX byte pool and semaphore entry points to force tx_semaphore_create() to fail on a chosen call. On a read semaphore failure the layer previously performed 2 allocations and 0 releases, and on a write semaphore failure 2 allocations, 0 releases and 0 semaphore deletions. It now performs 2 releases in both cases and deletes the read semaphore in the second, with the byte pool restored to its prior state. Fixes https://github.com/eclipse-threadx/threadx/issues/570 Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com> * Added a regression suite for the FreeRTOS compatibility layer The compatibility layer had no tests in this repository, which is awkward for its creation functions in particular. Each of them takes one or two byte pool allocations for its bookkeeping and then creates ThreadX kernel objects, and each returns NULL when a kernel object cannot be created. The caller is left without a handle, so it cannot call the matching delete function, and anything the layer failed to release is gone until the system restarts. A leaking version and a correct version are indistinguishable from the outside, which is how the leak in issue 570 went unnoticed. Add a suite that counts what the layer takes and gives back. A test asks the harness to fail a chosen kernel creation call, then checks the number of byte pool allocations, releases, object creations and object deletions performed. The ThreadX entry points are intercepted with the linker's --wrap so that tx_freertos.c is compiled exactly as it ships, with no test hooks in it. Note that tx_api.h maps the public API onto the error checking entry points, so the _txe_ symbols are the ones wrapped. Coverage is the creation and teardown paths of queues, tasks, semaphores, mutexes, event groups and timers, including a regression test for the two paths fixed for issue 570. The suite follows the layout of the existing ThreadX and SMP suites, is registered with ctest, and runs in CI through the shared regression template. It is built 32 bit because the Linux port defines ULONG as unsigned int on x86_64 while the layer passes pointers through ULONG arguments, so a 64 bit build truncates them. It is Linux only because --wrap has no MSVC equivalent, and the CMake configuration says so rather than failing at link time. Validated by building the suite against the layer as it stands before the issue 570 fix, where the two expected checks fail with the leaked counts, and against the fixed layer, where all three tests pass. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
99 lines
4.2 KiB
C
99 lines
4.2 KiB
C
/***************************************************************************
|
|
* 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 Component */
|
|
/** */
|
|
/** FreeRTOS compatibility layer test harness */
|
|
/** */
|
|
/**************************************************************************/
|
|
|
|
/* This harness lets a test force a ThreadX object creation call to fail and
|
|
then account for what the compatibility layer did with the resources it had
|
|
already taken. The creation entry points of the layer take a byte pool
|
|
allocation for the descriptor, sometimes a second one for backing storage,
|
|
and then create one or more kernel objects; an error path that forgets to
|
|
undo any of that is invisible to the caller, which only ever sees NULL.
|
|
|
|
Interception is done with the linker's --wrap option rather than with hooks
|
|
in the layer itself, so tx_freertos.c is compiled exactly as it ships. Note
|
|
that tx_api.h maps the public API onto the error checking entry points, so
|
|
the symbols that exist at link time, and therefore the ones wrapped, are the
|
|
_txe_ variants. The wrap options live in the test CMakeLists.
|
|
|
|
This mechanism is specific to GNU ld and lld, so the suite is Linux only. */
|
|
|
|
#ifndef TXFR_TEST_HARNESS_H
|
|
#define TXFR_TEST_HARNESS_H
|
|
|
|
#include "FreeRTOS.h"
|
|
|
|
/* Identifies the kernel creation call a test wants to fail. */
|
|
typedef enum TXFR_INJECT_TARGET_ENUM
|
|
{
|
|
TXFR_INJECT_NONE = 0,
|
|
TXFR_INJECT_SEMAPHORE_CREATE,
|
|
TXFR_INJECT_MUTEX_CREATE,
|
|
TXFR_INJECT_EVENT_FLAGS_CREATE,
|
|
TXFR_INJECT_TIMER_CREATE,
|
|
TXFR_INJECT_THREAD_CREATE
|
|
} TXFR_INJECT_TARGET;
|
|
|
|
/* Counts of the ThreadX primitives the layer reached for while accounting was
|
|
active. The byte pool counts stand in for txfr_malloc() and txfr_free(),
|
|
which cannot be wrapped: they are defined in tx_freertos.c and called from
|
|
within it, so the compiler resolves those calls internally. */
|
|
typedef struct TXFR_COUNTERS_STRUCT
|
|
{
|
|
int byte_allocate;
|
|
int byte_release;
|
|
int semaphore_create;
|
|
int semaphore_delete;
|
|
int mutex_create;
|
|
int mutex_delete;
|
|
int event_flags_create;
|
|
int event_flags_delete;
|
|
int timer_create;
|
|
int timer_delete;
|
|
int thread_create;
|
|
int thread_delete;
|
|
} TXFR_COUNTERS;
|
|
|
|
/* Start accounting. Pass TXFR_INJECT_NONE to only count, or a target together
|
|
with the 1 based index of the call to fail, counted from this call onward. */
|
|
void txfr_test_account_start(TXFR_INJECT_TARGET target, int fail_on_call);
|
|
|
|
/* Stop accounting and copy out what was counted. */
|
|
void txfr_test_account_stop(TXFR_COUNTERS *p_counters);
|
|
|
|
/* Record the outcome of one expectation. Prints a line per check and remembers
|
|
whether anything failed. */
|
|
void txfr_test_check(const char *label, int condition);
|
|
|
|
/* As above, with the observed and expected counts printed on a failure. */
|
|
void txfr_test_check_counts(const char *label, const TXFR_COUNTERS *p_counters,
|
|
int expect_allocate, int expect_release,
|
|
int expect_object_create, int expect_object_delete);
|
|
|
|
/* Number of failed checks so far. */
|
|
int txfr_test_failures(void);
|
|
|
|
/* Each test file defines this. It runs in thread context, since the byte pool
|
|
operations under test are not callable from initialization. */
|
|
void txfr_test_body(void);
|
|
|
|
#endif /* TXFR_TEST_HARNESS_H */
|