Skip to content

Commit d1fed5d

Browse files
committed
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.
1 parent 1c14651 commit d1fed5d

File tree

1 file changed

+7
-1
lines changed

1 file changed

+7
-1
lines changed

src/implementation/c/node/table-lookup.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,13 @@ export class TableLookup extends Node<frontend.node.TableLookup> {
245245
// https://community.arm.com/arm-community-blogs/b/servers-and-cloud-computing-blog/posts/porting-x86-vector-bitmask-optimizations-to-arm-neon
246246
out.push(' narrow = vshrn_n_u16(mask, 4);');
247247
out.push(' match_mask = ~vget_lane_u64(vreinterpret_u64_u8(narrow), 0);');
248-
out.push(' match_len = __builtin_ctzll(match_mask) >> 2;');
248+
// When all 16 bytes match, match_mask is 0. Calling __builtin_ctzll(0) is
249+
// undefined behavior, so we handle this case explicitly.
250+
out.push(' if (match_mask == 0) {');
251+
out.push(' match_len = 16;');
252+
out.push(' } else {');
253+
out.push(' match_len = __builtin_ctzll(match_mask) >> 2;');
254+
out.push(' }');
249255
out.push(' if (match_len != 16) {');
250256
out.push(` ${ctx.posArg()} += match_len;`);
251257
{

0 commit comments

Comments
 (0)