/* 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 "xtc/xtc_control.h" #include "xtc/xtc_plugin.h" #include #include #include #include #include #include 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; /* Catch direct host I/O and blocking synchronization in guarded callbacks. * Dynamic dependencies have their own symbols and are outside this audit. */ FILE *__real_fopen(const char *path, const char *mode); size_t __real_fread(void *buffer, size_t size, size_t count, FILE *file); size_t __real_fwrite(const void *buffer, size_t size, size_t count, FILE *file); ssize_t __real_read(int fd, void *buffer, size_t size); ssize_t __real_write(int fd, const void *buffer, size_t size); int __real_pthread_mutex_lock(pthread_mutex_t *mutex); int __real_pthread_cond_wait(pthread_cond_t *condition, pthread_mutex_t *mutex); FILE *__wrap_fopen(const char *path, const char *mode) { assert(!processing); return __real_fopen(path, mode); } size_t __wrap_fread(void *buffer, size_t size, size_t count, FILE *file) { assert(!processing); return __real_fread(buffer, size, count, file); } size_t __wrap_fwrite(const void *buffer, size_t size, size_t count, FILE *file) { assert(!processing); return __real_fwrite(buffer, size, count, file); } ssize_t __wrap_read(int fd, void *buffer, size_t size) { assert(!processing); return __real_read(fd, buffer, size); } ssize_t __wrap_write(int fd, const void *buffer, size_t size) { assert(!processing); return __real_write(fd, buffer, size); } int __wrap_pthread_mutex_lock(pthread_mutex_t *mutex) { assert(!processing); return __real_pthread_mutex_lock(mutex); } int __wrap_pthread_cond_wait(pthread_cond_t *condition, pthread_mutex_t *mutex) { assert(!processing); return __real_pthread_cond_wait(condition, mutex); } 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); } static void history_reservations(void) { xtc_project *project = NULL, *snapshot = NULL; xtc_history *history = NULL; uint64_t track, bus; unsigned int map[3] = {1,74,11}, actual[3]; size_t undo_count, redo_count, tracks, clips; int failure, succeeded = 0; assert(xtc_project_create(&project) == XTC_OK); assert(xtc_project_add_track(project,"History",&track) == XTC_OK); assert(xtc_history_create(project,&history) == XTC_OK); assert(xtc_project_set_mix(project,track,.5f,0,0,0) == XTC_OK); assert(xtc_history_record(history,project) == XTC_OK); assert(xtc_history_checkout(history,0,&snapshot) == XTC_OK && xtc_history_accept(history,0) == XTC_OK); xtc_project_destroy(project); project = snapshot; snapshot = NULL; for (failure = 0; failure < 12; ++failure) { xtc_result r; fail_after = failure; r = xtc_history_reserve(history); fail_after = -1; assert(xtc_history_counts(history,&undo_count,&redo_count) == XTC_OK && undo_count == 0 && redo_count == 1); if (r == XTC_OK) { succeeded = 1; break; } assert(r == XTC_ERR_OUT_OF_MEMORY); } assert(succeeded); fail_after = 0; // Fail every allocation: reserved record must still succeed. assert(xtc_history_reserve(history) == XTC_OK); assert(xtc_project_add_bus(project,"Bus",&bus) == XTC_OK); assert(xtc_project_route_track(project,track,bus) == XTC_OK); assert(xtc_project_set_control_map(project,0,map) == XTC_OK); assert(xtc_history_record(history,project) == XTC_OK); fail_after = -1; assert(xtc_history_counts(history,&undo_count,&redo_count) == XTC_OK && undo_count == 1 && redo_count == 0); assert(xtc_history_checkout(history,0,&snapshot) == XTC_OK); assert(xtc_project_get_counts(snapshot,&tracks,&clips) == XTC_OK && tracks == 1 && clips == 0); assert(xtc_project_get_control_map(snapshot,0,actual) == XTC_OK && actual[0] == 7); xtc_project_destroy(snapshot); snapshot = NULL; assert(xtc_history_accept(history,0) == XTC_OK && xtc_history_checkout(history,1,&snapshot) == XTC_OK); assert(xtc_project_get_counts(snapshot,&tracks,&clips) == XTC_OK && tracks == 2); assert(xtc_project_get_control_map(snapshot,0,actual) == XTC_OK && actual[0] == 1 && actual[1] == 74); xtc_project_destroy(snapshot); xtc_project_destroy(project); xtc_history_destroy(history); } static void builtin_failures(void) { unsigned int id; for(id=0;id<7;++id) { int failure, succeeded=0; xtc_plugin_spec spec; assert(xtc_plugin_probe(XTC_BUILTIN_PATH,id,48000,&spec)==XTC_OK); if(id==4) {assert(getcwd(spec.sample_path,sizeof(spec.sample_path)));strcat(spec.sample_path,"/examples/projects/original-pluck.wav");} for(failure=0;failure<16;++failure) { xtc_plugin *plugin=NULL; xtc_result result; float in[256]={0},out[256]; fail_after=failure;result=xtc_plugin_create(&spec,48000,256,&plugin);fail_after=-1; if(result!=XTC_OK) {assert(result==XTC_ERR_OUT_OF_MEMORY && !plugin);continue;} processing=1; if(spec.instrument) {xtc_midi_event event={0,XTC_MIDI_NOTE_ON,0,60,100}; assert(xtc_plugin_process_midi(plugin,&event,1,out,256)==XTC_OK); } else if(id>=5) {float right[256];assert(xtc_plugin_process_stereo_at(plugin,in,in,out,right,256,0)==XTC_OK);} else assert(xtc_plugin_process(plugin,in,out,256)==XTC_OK); xtc_plugin_reset_history(plugin); processing=0;xtc_plugin_destroy(plugin);succeeded=1;break; } assert(succeeded); } } int main(void) { builtin_failures(); graph_limits(); history_reservations(); 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; }