Skip to content

Commit 3813b1b

Browse files
committed
Add defensive NULL checks for node inputs
This guards against NULL pointer dereferences in the processing loop: - picosynth_process() returns 0 for NULL synth pointer - Oscillator skips freq increment when freq pointer is NULL - HP filter outputs 0 when input pointer is NULL - Filter accumulator update uses 0 for NULL input
1 parent 5e0cb49 commit 3813b1b

File tree

2 files changed

+44
-5
lines changed

2 files changed

+44
-5
lines changed

src/picosynth.c

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,9 @@ static q15_t soft_clip(int32_t x)
469469

470470
q15_t picosynth_process(picosynth_t *s)
471471
{
472+
if (!s)
473+
return 0;
474+
472475
int32_t out = 0;
473476

474477
for (int vi = 0; vi < s->num_voices; vi++) {
@@ -518,9 +521,14 @@ q15_t picosynth_process(picosynth_t *s)
518521
break;
519522
case PICOSYNTH_NODE_HP:
520523
/* High-pass is the input signal minus the low-pass signal */
521-
tmp[i] =
522-
(int32_t) (((int64_t) n->flt.accum * n->flt.coeff) >> 15);
523-
tmp[i] = *n->flt.in - tmp[i];
524+
if (n->flt.in) {
525+
tmp[i] =
526+
(int32_t) (((int64_t) n->flt.accum * n->flt.coeff) >>
527+
15);
528+
tmp[i] = *n->flt.in - tmp[i];
529+
} else {
530+
tmp[i] = 0;
531+
}
524532
break;
525533
case PICOSYNTH_NODE_MIX: {
526534
int32_t sum = 0;
@@ -550,7 +558,8 @@ q15_t picosynth_process(picosynth_t *s)
550558

551559
switch (n->type) {
552560
case PICOSYNTH_NODE_OSC:
553-
n->state += *n->osc.freq;
561+
if (n->osc.freq)
562+
n->state += *n->osc.freq;
554563
if (n->osc.detune)
555564
n->state += *n->osc.detune;
556565
n->state =
@@ -631,7 +640,8 @@ q15_t picosynth_process(picosynth_t *s)
631640
* where output is the filtered signal from the previous sample.
632641
* This implements a simple recursive filter.
633642
*/
634-
int32_t delta = *n->flt.in - n->out;
643+
int32_t input_val = n->flt.in ? *n->flt.in : 0;
644+
int32_t delta = input_val - n->out;
635645
int64_t acc = (int64_t) n->flt.accum + delta;
636646
if (acc > 0x7FFFFFFF)
637647
acc = 0x7FFFFFFF;

tests/test-synth.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,34 @@ static void test_voice_set_out(void)
310310
picosynth_destroy(s);
311311
}
312312

313+
/* Test graphs with missing/null inputs */
314+
static void test_null_graph_inputs(void)
315+
{
316+
picosynth_t *s = picosynth_create(1, 2);
317+
TEST_ASSERT(s != NULL, "synth creation");
318+
319+
picosynth_voice_t *v = picosynth_get_voice(s, 0);
320+
picosynth_node_t *osc = picosynth_voice_get_node(v, 0);
321+
picosynth_node_t *hp = picosynth_voice_get_node(v, 1);
322+
323+
/* Intentionally leave freq/in pointers NULL to ensure null-safety */
324+
picosynth_init_osc(osc, NULL, NULL, picosynth_wave_sine);
325+
picosynth_init_hp(hp, NULL, NULL, Q15_MAX);
326+
picosynth_voice_set_out(v, 1);
327+
328+
picosynth_note_on(s, 0, 60);
329+
330+
int non_zero = 0;
331+
for (int i = 0; i < 64; i++) {
332+
q15_t sample = picosynth_process(s);
333+
if (sample != 0)
334+
non_zero++;
335+
}
336+
TEST_ASSERT(non_zero == 0, "null graph inputs produce silence");
337+
338+
picosynth_destroy(s);
339+
}
340+
313341
/* Test NULL pointer handling */
314342
static void test_null_safety(void)
315343
{
@@ -342,5 +370,6 @@ void test_synth_all(void)
342370
TEST_RUN(test_mixer);
343371
TEST_RUN(test_voice_freq_ptr);
344372
TEST_RUN(test_voice_set_out);
373+
TEST_RUN(test_null_graph_inputs);
345374
TEST_RUN(test_null_safety);
346375
}

0 commit comments

Comments
 (0)