From 43013510422a7e192dc2ea6942309e67a6c75b47 Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Mon, 15 Jun 2026 00:53:50 -0400 Subject: [PATCH] Fix stack overflow in no-musttail fallback for direct continuations The no-musttail trampoline (063872e) only rerouted keepGoing dispatch. Continuations that tail-call each other directly still recursed on the C stack when MUSTTAIL is a no-op, so deep input overflowed it -- nested arrays via n_value <-> n_array2, and runs of string escapes via n_string2 -> n_string2 (the latter isn't even bounded by stackSize). Add a TAILCALL(fn) macro: a direct musttail tail call when available, otherwise a bounce to the parse() trampoline (which re-dispatches continuations[top()] == fn, valid because fn's symbol is already on top of the stack at every site). Route the 10 direct continuation tail calls through it. The musttail build is unchanged; the fallback no longer recurses without bound. --- src/parser3.h | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/parser3.h b/src/parser3.h index a20674c..e26e0c1 100644 --- a/src/parser3.h +++ b/src/parser3.h @@ -161,6 +161,18 @@ struct Parser3 { #endif }; +// Transfer control to continuation `fn`, whose Symbol must already be on top of +// the stack. With musttail this is a direct tail call (fast path). Without it, +// a direct call would recurse on the C stack (e.g. n_value -> n_array2 -> +// n_value for nested arrays, or n_string2 -> n_string2 for runs of escapes) and +// overflow on deep input; instead we bounce to the parse() trampoline, which +// re-dispatches continuations[top()] == fn without growing the stack. +#ifdef HAS_MUSTTAIL +#define TAILCALL(fn) MUSTTAIL return fn(self, buf, bufEnd) +#else +#define TAILCALL(fn) return Parser3::keepGoing(self, buf, bufEnd) +#endif + inline PRESERVE_NONE ContinuationStatus skipWhitespace(char *&buf, char *bufEnd) { constexpr int kStride = 4; @@ -306,7 +318,7 @@ inline PRESERVE_NONE ContinuationStatus n_value(Parser3 *self, char *buf, if (buf == bufEnd) { return WeaselJson_AGAIN; } - MUSTTAIL return n_object2(self, buf, bufEnd); + TAILCALL(n_object2); case '[': self->callbacks->on_begin_array(self->userdata); ++buf; @@ -315,7 +327,7 @@ inline PRESERVE_NONE ContinuationStatus n_value(Parser3 *self, char *buf, if (buf == bufEnd) { return WeaselJson_AGAIN; } - MUSTTAIL return n_array2(self, buf, bufEnd); + TAILCALL(n_array2); case '"': ++buf; self->dataBegin = self->writeBuf = buf; @@ -325,7 +337,7 @@ inline PRESERVE_NONE ContinuationStatus n_value(Parser3 *self, char *buf, if (buf == bufEnd) { return WeaselJson_AGAIN; } - MUSTTAIL return n_string2(self, buf, bufEnd); + TAILCALL(n_string2); case '0': case '1': case '2': @@ -344,7 +356,7 @@ inline PRESERVE_NONE ContinuationStatus n_value(Parser3 *self, char *buf, if (buf == bufEnd) { return WeaselJson_AGAIN; } - MUSTTAIL return n_number(self, buf, bufEnd); + TAILCALL(n_number); case 't': ++buf; self->pop(); @@ -432,7 +444,7 @@ inline PRESERVE_NONE ContinuationStatus n_object2(Parser3 *self, char *buf, if (buf == bufEnd) { return WeaselJson_AGAIN; } - MUSTTAIL return n_string2(self, buf, bufEnd); + TAILCALL(n_string2); default: [[unlikely]] return WeaselJson_REJECT; } @@ -478,7 +490,7 @@ inline PRESERVE_NONE ContinuationStatus n_object3(Parser3 *self, char *buf, if (buf == bufEnd) { return WeaselJson_AGAIN; } - MUSTTAIL return n_string2(self, buf, bufEnd); + TAILCALL(n_string2); default: [[unlikely]] return WeaselJson_REJECT; } @@ -506,7 +518,7 @@ inline PRESERVE_NONE ContinuationStatus n_array2(Parser3 *self, char *buf, if (auto s = self->push({N_VALUE, N_ARRAY3})) { return s; } - MUSTTAIL return n_value(self, buf, bufEnd); + TAILCALL(n_value); } } @@ -536,7 +548,7 @@ inline PRESERVE_NONE ContinuationStatus n_array3(Parser3 *self, char *buf, if (buf == bufEnd) { return WeaselJson_AGAIN; } - MUSTTAIL return n_value(self, buf, bufEnd); + TAILCALL(n_value); default: [[unlikely]] return WeaselJson_REJECT; } @@ -561,7 +573,7 @@ inline PRESERVE_NONE ContinuationStatus n_string(Parser3 *self, char *buf, if (buf == bufEnd) { return WeaselJson_AGAIN; } - MUSTTAIL return n_string2(self, buf, bufEnd); + TAILCALL(n_string2); } inline int32_t read4_hex(const char *buf) { @@ -664,7 +676,7 @@ inline PRESERVE_NONE ContinuationStatus n_string2(Parser3 *self, char *buf, self->flushString(false, buf); return WeaselJson_AGAIN; } - MUSTTAIL return n_string2(self, buf, bufEnd); + TAILCALL(n_string2); } default: [[unlikely]] return WeaselJson_REJECT; @@ -1072,4 +1084,6 @@ inline PRESERVE_NONE ContinuationStatus Parser3::keepGoing(Parser3 *self, #endif } +#undef TAILCALL + } // namespace parser3