#define _XOPEN_SOURCE 700 /* 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 "xtc/xtc_editing.h" #include "xtc/xtc_audio.h" #include "xtc/xtc_sample.h" #include #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); } } static void modern_edit_failures(void) { xtc_project *p=NULL;xtc_clipboard *board=NULL;xtc_mix_snapshot *mix=NULL; uint64_t track,clip,note;int operation,failure; xtc_midi_transform edit={0};edit.grid=240;edit.swing=50;edit.strength=100; assert(xtc_project_create(&p)==XTC_OK); assert(xtc_project_add_track(p,"Track",&track)==XTC_OK); assert(xtc_project_add_midi_clip(p,track,960,1920,&clip)==XTC_OK); assert(xtc_project_add_note(p,clip,230,120,61,100,0,¬e)==XTC_OK); assert(xtc_clipboard_create(p,&clip,1,&board)==XTC_OK); assert(xtc_mix_snapshot_create(p,&mix)==XTC_OK); for(operation=0;operation<7;++operation) { int succeeded=0; for(failure=0;failure<32;++failure) { xtc_project *copy=NULL;xtc_clipboard *created=NULL;xtc_mix_snapshot *captured=NULL; uint64_t output=999;xtc_clip_info c;xtc_note_info n;size_t tracks,clips;xtc_result r; assert(xtc_project_clone(p,©)==XTC_OK); fail_after=failure; switch(operation) { case 0:r=xtc_project_transform_clips(copy,&clip,1,XTC_CLIPS_DUPLICATE,1920,&output);break; case 1:r=xtc_clipboard_create(copy,&clip,1,&created);break; case 2:r=xtc_clipboard_paste(board,copy,track,3840,&output);break; case 3:r=xtc_project_repeat_clips(copy,&clip,1,2);break; case 4:r=xtc_project_transform_midi(copy,&clip,1,&edit);break; case 5:r=xtc_mix_snapshot_create(copy,&captured);break; default:r=xtc_mix_snapshot_recall(mix,copy);break; } fail_after=-1; if(r==XTC_OK)succeeded=1; else { assert(r==XTC_ERR_OUT_OF_MEMORY && output==999 && !created && !captured); assert(xtc_project_get_counts(copy,&tracks,&clips)==XTC_OK && clips==1); assert(xtc_project_find_clip(copy,clip,&c)==XTC_OK && c.start_tick==960); assert(xtc_project_get_note(copy,0,&n)==XTC_OK && n.start_tick==230 && n.id==note); {uint64_t probe;assert(xtc_project_duplicate_clip(copy,clip,track,3840,&probe)==XTC_OK && probe==clip+1);} } xtc_clipboard_destroy(created);xtc_mix_snapshot_destroy(captured);xtc_project_destroy(copy); if(succeeded)break; } assert(succeeded); } xtc_clipboard_destroy(board);xtc_mix_snapshot_destroy(mix);xtc_project_destroy(p); } static void composition_failures(void) { xtc_project *p=NULL;uint64_t t,ids[2],note,audio;char sample[4096],root[]="/tmp/xtc-compose-fail-XXXXXX"; int operation,failure;xtc_midi_transform human={0};int intervals[2]={4,7}; assert(mkdtemp(root));assert(getcwd(sample,sizeof(sample)));assert(strlen(sample)+40d_name,".")||!strcmp(entry->d_name,".."));assert(closedir(d)==0);} xtc_sample_destroy(output);xtc_project_destroy(copy);if(succeeded)break; }assert(succeeded); } xtc_sample_destroy(source);xtc_project_destroy(p);assert(rmdir(root)==0); } static void track_insert_failures(void) { xtc_project *p=NULL;xtc_track_chain c={0};uint64_t t,clip;int mode,failure; assert(xtc_project_create(&p)==XTC_OK && xtc_project_add_track(p,"Track",&t)==XTC_OK); assert(xtc_plugin_probe_format(XTC_PLUGIN_BUILTIN,XTC_BUILTIN_PATH,2,48000,&c.slots[0].plugin)==XTC_OK); c.count=1;c.output_gain=1;c.slots[0].wet=.5f;c.slots[0].enabled=1; assert(xtc_project_set_chain(p,t,&c)==XTC_OK); for(mode=0;mode<2;++mode) { int succeeded=0; if(mode)assert(xtc_project_add_midi_clip(p,t,0,960,&clip)==XTC_OK); for(failure=0;failure<128;++failure) { xtc_audio_plan *a=NULL;xtc_midi_plan *m=NULL;float out[512];xtc_result r; fail_after=failure; r=mode ? xtc_midi_plan_create(p,48000,&m) : xtc_audio_plan_create(p,48000,&a); fail_after=-1; if(r==XTC_OK) { processing=1; if(mode)assert(xtc_midi_plan_render(m,0,NULL,0,out,512)==XTC_OK); else assert(xtc_audio_plan_render(a,0,out,512)==XTC_OK); processing=0;succeeded=1; }else assert(r==XTC_ERR_OUT_OF_MEMORY && !a && !m); xtc_audio_plan_destroy(a);xtc_midi_plan_destroy(m); if(succeeded)break; } assert(succeeded); } xtc_project_destroy(p); } int main(void) { track_insert_failures(); sample_failures(); composition_failures(); modern_edit_failures(); 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; }