From d1fed5ded88f95e7fcc8ca7ce5ceb60e23231516 Mon Sep 17 00:00:00 2001 From: Lukas Lalinsky Date: Fri, 2 Jan 2026 11:21:19 +0100 Subject: [PATCH] fix: handle match_mask == 0 in NEON ctzll to avoid undefined behavior When all 16 bytes match the allowed range, match_mask becomes 0 after the bitwise NOT. Calling __builtin_ctzll(0) is undefined behavior. The code expects match_len == 16 when all bytes match (so the branch is skipped and p += 16 continues the loop), but this relied on ctzll(0) returning 64, which is not guaranteed. Example panic on macOS ARM64: thread 44856 panic: passing zero to ctz(), which is not a valid argument src/llhttp/llhttp.c:2654:21: in llhttp__internal__run match_len = __builtin_ctzll(match_mask) >> 2; ^ Fix by explicitly checking for match_mask == 0 and setting match_len = 16. --- src/implementation/c/node/table-lookup.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/implementation/c/node/table-lookup.ts b/src/implementation/c/node/table-lookup.ts index b41b143..bf028d5 100644 --- a/src/implementation/c/node/table-lookup.ts +++ b/src/implementation/c/node/table-lookup.ts @@ -245,7 +245,13 @@ export class TableLookup extends Node { // https://community.arm.com/arm-community-blogs/b/servers-and-cloud-computing-blog/posts/porting-x86-vector-bitmask-optimizations-to-arm-neon out.push(' narrow = vshrn_n_u16(mask, 4);'); out.push(' match_mask = ~vget_lane_u64(vreinterpret_u64_u8(narrow), 0);'); - out.push(' match_len = __builtin_ctzll(match_mask) >> 2;'); + // When all 16 bytes match, match_mask is 0. Calling __builtin_ctzll(0) is + // undefined behavior, so we handle this case explicitly. + out.push(' if (match_mask == 0) {'); + out.push(' match_len = 16;'); + out.push(' } else {'); + out.push(' match_len = __builtin_ctzll(match_mask) >> 2;'); + out.push(' }'); out.push(' if (match_len != 16) {'); out.push(` ${ctx.posArg()} += match_len;`); {