1
0
Fork 0
forked from hanez/xtc

Phase 10: Audit engine reliability and strengthen lifecycle regression tests

This commit is contained in:
Johannes Findeisen 2026-10-27 03:17:29 +01:00
commit 8dd96d3cc5
13 changed files with 394 additions and 5 deletions

View file

@ -182,3 +182,13 @@ No-JACK core/GUI/install checks and Clang ASan/UBSan core/GUI checks also passed
(the GUI retains only its existing fontconfig leak suppression). TSan recorder
and realtime-handoff checks passed. Two independent optimized source exports
and their repacked source archives were byte-identical on this toolchain.
## Phase 10 reliability audit
Run `make test-reliability` for the core suites, allocation-failure sweep,
callback allocation/free guard and native reclamation checks. Run
`make test-sanitize` for Clang ASan/UBSan/leak checks and `make analyze` for
Clang static analysis of all core sources. These need installed development
tools; the allocation guard uses a GNU-compatible linker. See
[RELIABILITY.md](../../docs/RELIABILITY.md) for ownership, audit findings,
full verification commands and remaining limits.

View file

@ -0,0 +1,130 @@
/* GNU ld allocation interposition: fail preparation at every allocation and
* reject host allocation/reclamation inside processing. No production hooks. */
#include "xtc/xtc_realtime.h"
#include "xtc/xtc_native.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
void *__real_malloc(size_t size);
void *__real_calloc(size_t count, size_t size);
void *__real_realloc(void *pointer, size_t size);
void __real_free(void *pointer);
static int processing, fail_after = -1;
static unsigned int prepared, destroyed;
static int fail_allocation(void)
{
assert(!processing);
if (fail_after < 0) return 0;
if (fail_after == 0) return 1;
--fail_after;
return 0;
}
void *__wrap_malloc(size_t size) { return fail_allocation() ? NULL : __real_malloc(size); }
void *__wrap_calloc(size_t count, size_t size) { return fail_allocation() ? NULL : __real_calloc(count, size); }
void *__wrap_realloc(void *pointer, size_t size) { return fail_allocation() ? NULL : __real_realloc(pointer, size); }
void __wrap_free(void *pointer) { assert(!processing); __real_free(pointer); }
static xtc_result prepare(double rate, size_t frames, float parameter, void **state)
{
(void)rate; (void)frames;
assert(!processing);
*state = malloc(sizeof(float));
if (!*state) return XTC_ERR_OUT_OF_MEMORY;
*(float *)*state = parameter;
++prepared;
return XTC_OK;
}
static void reset(void *state) { (void)state; }
static xtc_result process(void *state, const xtc_native_block *block)
{
size_t i;
assert(processing);
for (i = 0; i < block->frames; ++i)
block->output[i] = block->input[i] * *(float *)state;
return XTC_OK;
}
static void destroy(void *state)
{
assert(!processing);
if (state) ++destroyed;
free(state);
}
static void render(xtc_realtime *engine, float expected)
{
float input[17], left[17], right[17];
size_t i;
for (i = 0; i < 17; ++i) input[i] = 1;
processing = 1;
assert(xtc_realtime_process_stereo(engine, input, NULL, NULL, NULL, NULL, left, right, 17) == XTC_OK);
processing = 0;
for (i = 0; i < 17; ++i) assert(left[i] == expected && right[i] == expected);
}
/* Maximum-sized reverse-insertion chain: long cycle rollback must preserve
* routing, and compiled snapshots survive destruction of the editable model. */
static void graph_limits(void)
{
xtc_graph *graph = NULL;
xtc_plan *plan = NULL;
size_t nodes[256], i;
float input[3] = {1, -0.5f, 0.25f}, output[3];
assert(xtc_graph_create(256, &graph) == XTC_OK);
for (i = 0; i < 256; ++i)
assert(xtc_graph_add_gain(graph, 1, &nodes[i]) == XTC_OK);
for (i = 255; i > 0; --i)
assert(xtc_graph_connect(graph, nodes[i], nodes[i - 1]) == XTC_OK);
assert(xtc_graph_connect(graph, nodes[0], nodes[255]) == XTC_ERR_GRAPH_CYCLE);
assert(xtc_graph_validate(graph) == XTC_OK);
assert(xtc_graph_compile(graph, 48000, 3, &plan) == XTC_OK);
xtc_graph_destroy(graph);
processing = 1;
assert(xtc_plan_process(plan, input, output, 3) == XTC_OK);
processing = 0;
for (i = 0; i < 3; ++i) assert(output[i] == input[i]);
xtc_plan_destroy(plan);
}
int main(void)
{
graph_limits();
xtc_native_descriptor descriptor = {XTC_NATIVE_ABI_VERSION, sizeof(descriptor),
"reliability.gain", XTC_NATIVE_EFFECT, prepare, reset, process, destroy};
xtc_graph *graph = NULL;
size_t node;
unsigned int iteration;
assert(xtc_graph_create(1, &graph) == XTC_OK);
assert(xtc_graph_add_native(graph, &descriptor, 2, &node) == XTC_OK);
for (iteration = 0; iteration < 100; ++iteration) {
xtc_realtime *engine = NULL;
int failure, succeeded = 0;
assert(xtc_realtime_create(48000, 17, &engine) == XTC_OK);
assert(xtc_realtime_submit(engine, graph) == XTC_OK);
render(engine, 2);
for (failure = 0; failure < 64; ++failure) {
xtc_result result;
fail_after = failure;
result = xtc_realtime_submit(engine, graph);
fail_after = -1;
if (result == XTC_OK) { succeeded = 1; break; }
assert(result == XTC_ERR_OUT_OF_MEMORY);
/* Failed replacement preserves the active DSP and revisions. */
{
xtc_realtime_status status;
assert(xtc_realtime_get_status(engine, &status) == XTC_OK);
assert(status.active_revision == 1 && status.submitted_revision == 1);
}
render(engine, 2);
}
assert(succeeded);
render(engine, 2); /* Leaves previous native instances retired. */
assert(destroyed + 4 == prepared);
xtc_realtime_collect(engine);
assert(destroyed + 2 == prepared);
assert(xtc_realtime_submit(engine, graph) == XTC_OK);
/* Destruction must also clean an unadopted pending plan. */
xtc_realtime_destroy(engine);
assert(prepared == destroyed);
}
xtc_graph_destroy(graph);
puts("Allocation failures, callback allocation guard, and native reclamation passed.");
return 0;
}

View file

@ -79,6 +79,33 @@ static int routing(void)
return 0;
}
/* External mono input is read by both channels, including when left aliases it. */
static int stereo_alias(void)
{
xtc_graph *graph = NULL;
xtc_plan *plan = NULL;
size_t gain, audio, output, i;
float left[4] = {1, 2, 3, 4}, right[4];
CHECK(xtc_graph_create(3, &graph) == XTC_OK);
CHECK(xtc_graph_add_gain(graph, 2, &gain) == XTC_OK);
CHECK(xtc_graph_add_audio(graph, &audio) == XTC_OK);
CHECK(xtc_graph_add_output(graph, &output) == XTC_OK);
CHECK(xtc_graph_connect(graph, gain, output) == XTC_OK);
CHECK(xtc_graph_connect(graph, audio, output) == XTC_OK);
CHECK(xtc_graph_compile(graph, 48000, 4, &plan) == XTC_OK);
CHECK(xtc_plan_process_stereo(plan, left, NULL, NULL, NULL, NULL, left, right, 4) == XTC_OK);
for (i = 0; i < 4; ++i)
CHECK(left[i] == 2 * (float)(i + 1) && right[i] == left[i]);
for (i = 0; i < 4; ++i) right[i] = 10 + (float)i;
/* Cross-channel source alias must also retain the original source samples. */
CHECK(xtc_plan_process_stereo(plan, NULL, right, left, NULL, NULL, left, right, 4) == XTC_OK);
for (i = 0; i < 4; ++i)
CHECK(left[i] == 10 + (float)i && right[i] == 2 * (float)(i + 1));
xtc_plan_destroy(plan);
xtc_graph_destroy(graph);
return 0;
}
static int validation(void)
{
xtc_graph *graph = NULL;
@ -133,6 +160,7 @@ int main(void)
CHECK(oscillator() == 0);
CHECK(routing() == 0);
CHECK(validation() == 0);
CHECK(stereo_alias() == 0);
puts("All offline graph tests passed.");
return 0;
}

View file

@ -1,11 +1,46 @@
#define _POSIX_C_SOURCE 200809L
#include "xtc/xtc_realtime.h"
#include "xtc/xtc_native.h"
#include <assert.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#define CHECK(c) do { if (!(c)) { fprintf(stderr,"FAIL %s:%d: %s\n",__FILE__,__LINE__,#c); return 1; } } while (0)
/* Count native lifetimes on the serialized producer while a real consumer
* traverses plans. Destruction on that consumer is always a contract violation. */
static pthread_t control_thread;
static unsigned int prepared, destroyed;
static xtc_result prepare(double rate, size_t frames, float gain, void **state)
{
(void)rate; (void)frames;
assert(pthread_equal(pthread_self(), control_thread));
*state = malloc(sizeof(float));
if (!*state) return XTC_ERR_OUT_OF_MEMORY;
*(float *)*state = gain;
++prepared;
return XTC_OK;
}
static void reset(void *state) { (void)state; }
static xtc_result process(void *state, const xtc_native_block *block)
{
size_t i;
for (i = 0; i < block->frames; ++i)
block->output[i] = block->input[i] * *(float *)state;
return XTC_OK;
}
static void destroy(void *state)
{
assert(pthread_equal(pthread_self(), control_thread));
if (state) ++destroyed;
free(state);
}
static const xtc_native_descriptor descriptor = {
XTC_NATIVE_ABI_VERSION, sizeof(xtc_native_descriptor), "concurrent.gain",
XTC_NATIVE_EFFECT, prepare, reset, process, destroy
};
typedef struct { xtc_realtime *engine; unsigned int stop, failed; } worker_state;
static void *worker(void *context)
{
@ -40,9 +75,9 @@ static int run(void)
CHECK(xtc_realtime_create(48000.0, 32U, &engine) == XTC_OK);
CHECK(xtc_realtime_process(engine, input, output, 4U) == XTC_OK && output[0] == 0.0f);
CHECK(xtc_graph_create(1U, &one) == XTC_OK);
CHECK(xtc_graph_add_gain(one, 1.0f, &node) == XTC_OK);
CHECK(xtc_graph_add_native(one, &descriptor, 1.0f, &node) == XTC_OK);
CHECK(xtc_graph_create(1U, &two) == XTC_OK);
CHECK(xtc_graph_add_gain(two, 2.0f, &node) == XTC_OK);
CHECK(xtc_graph_add_native(two, &descriptor, 2.0f, &node) == XTC_OK);
CHECK(xtc_realtime_submit(engine, one) == XTC_OK);
CHECK(xtc_realtime_submit(engine, two) == XTC_ERR_CAPACITY);
CHECK(xtc_realtime_get_status(engine, &status) == XTC_OK && status.submitted_revision == 1U && status.active_revision == 0U);
@ -68,6 +103,7 @@ static int run(void)
CHECK(state.failed == 0U);
CHECK(xtc_realtime_get_status(engine, &status) == XTC_OK && status.processed_blocks > 3U);
xtc_realtime_destroy(engine); /* Also cleans pending and retired plans. */
CHECK(prepared == destroyed);
xtc_graph_destroy(one); xtc_graph_destroy(two);
for (i = 0U; i < 100U; ++i) {
CHECK(xtc_realtime_create(48000.0, 4U, &engine) == XTC_OK);
@ -80,6 +116,7 @@ static int run(void)
}
int main(void)
{
control_thread = pthread_self();
CHECK(run() == 0);
puts("Realtime handoff and concurrent replacement tests passed.");
return 0;