Files
Frédéric Desbiens cfed1d8095 Fixed the kernel object leaks on the static creation error paths (#584)
xQueueCreateStatic() and xTaskCreateStatic() take their storage from the
caller, so neither leaks memory, but both create ThreadX objects and both
return NULL when a later step fails. The caller is left without a handle and
cannot call the matching delete function, so any object already created stays
registered in the kernel, pointing into a caller buffer that the application
is now free to reuse or discard.

Three paths were affected. xQueueCreateStatic() abandoned the read semaphore
when the write semaphore could not be created. xTaskCreateStatic() abandoned
the notification semaphore when the thread could not be created, and abandoned
both the semaphore and the thread when the thread could not be resumed.

Delete what was already created before returning on each of them. The resume
path terminates the thread before deleting it, since a thread created with
TX_DONT_START is suspended rather than terminated, which is the same order the
idle task uses when it reaps a deleted task.

Extend the regression suite to cover all three paths, and add thread resume to
the set of entry points the harness can force to fail. Each static failure case
now uses its own control block, so a future regression on one path cannot carry
damage into the next case and report misleading counts there.

Verified against the layer as it stands on dev, where the three new checks fail
with the objects left behind, and against the fixed layer, where the suite
passes.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-08-09 08:44:30 -04:00

104 lines
4.4 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_THREAD_RESUME
} 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;
/* Counted, but not part of the object totals checked below, since a
resume creates nothing. */
int thread_resume;
} 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 */