From 185a756fba1d792f66b30c010f18de6186989158 Mon Sep 17 00:00:00 2001 From: Le Wang Date: Mon, 7 Sep 2026 12:19:11 -0400 Subject: [PATCH] Fix module string copying and empty-needle scoring copy_emacs_string used a too-small buffer as a size query. Emacs 31 signals memory-buffer-too-small in that case (module_copy_string_contents), so the error escaped when the retry path did not clear it. Ask for the size with a NULL buffer instead, which is the documented query form on all versions. Also reserve the uint64_t-rounded size: strtolower rewrites the string in 8-byte chunks and touches up to 7 bytes past the null-terminator, which could cross the end of a bump block. calc_cost returned c[m - 1] with m unsigned, so an empty needle indexed c[UINT_MAX] and crashed. An empty needle matches everything at no cost. --- hotfuzz-module.c | 50 +++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/hotfuzz-module.c b/hotfuzz-module.c index 3636652..41b8dc0 100644 --- a/hotfuzz-module.c +++ b/hotfuzz-module.c @@ -82,6 +82,7 @@ static void match_row(struct Str a, struct Str b, int *bonuses, unsigned i, int static int calc_cost(struct Str needle, struct Str haystack, bool ignore_case) { unsigned n = haystack.len, m = needle.len; if (n > MAX_HAYSTACK_LEN || m > MAX_NEEDLE_LEN) return 100000; + if (!m) return 0; // Everything matches the empty needle, at no cost int c[MAX_NEEDLE_LEN], d[MAX_NEEDLE_LEN]; for (unsigned j = 0; j < m; ++j) c[j] = d[j] = 100000; @@ -131,32 +132,33 @@ static void bump_free(struct Bump *head) { /** Copies the Emacs string to make its contents accessible. */ static struct Str copy_emacs_string(emacs_env *env, struct Bump **bump, emacs_value value) { - char *buf = NULL; - ptrdiff_t origlen, len; - if (*bump) { - // Opportunistically try to copy into remaining space - buf = (*bump)->cursor; - len = origlen = (*bump)->limit - (*bump)->cursor; - } - // Determine the size of the string (including null-terminator) - if (env->copy_string_contents(env, value, buf, &len)) { - if (buf) goto success; - } else { - if (!buf || len == origlen) return (struct Str) { 0 }; - env->non_local_exit_clear(env); + // Ask for the size (including null-terminator) with a NULL buffer. Passing + // a buffer that turns out to be too small is not a usable size query: Emacs + // 31 signals `memory-buffer-too-small' in that case, and older versions + // leave the required size unreported when the shortfall is detected early. + ptrdiff_t len = 0; + if (!env->copy_string_contents(env, value, NULL, &len)) return (struct Str) { 0 }; + + // `strtolower' rewrites the string in uint64_t-sized chunks and so touches up + // to alignof(uint64_t)-1 bytes past the null-terminator. Reserve the rounded + // up size to keep those writes inside the block. + ptrdiff_t need = (len + alignof(uint64_t) - 1) & ~(ptrdiff_t) (alignof(uint64_t) - 1); + + char *buf; + if (*bump && (*bump)->limit - (*bump)->cursor >= need) + buf = (*bump)->cursor; // Fits in the remaining space + else { + size_t capacity = *bump ? 2 * ((*bump)->limit - (*bump)->b) : 2048; + if (capacity < (size_t) need) capacity = need; + struct Bump *new; + if (!(new = malloc(sizeof *new + capacity))) return (struct Str) { 0 }; + *new = (struct Bump) { .next = *bump, .cursor = new->b, .limit = new->b + capacity }; + *bump = new; + buf = new->cursor; } - size_t capacity = *bump ? 2 * ((*bump)->limit - (*bump)->b) : 2048; - if (capacity < (size_t) len) capacity = len + alignof(uint64_t) - 1; - struct Bump *new; - if (!(new = malloc(sizeof *new + capacity))) return (struct Str) { 0 }; - *new = (struct Bump) { .next = *bump, .cursor = new->b, .limit = new->b + capacity }; - *bump = new; - - env->copy_string_contents(env, value, buf = new->cursor, &len); -success: - (*bump)->cursor = (char *) (((uintptr_t) (*bump)->cursor + len - + alignof(uint64_t) - 1) & ~(alignof(uint64_t) - 1)); + if (!env->copy_string_contents(env, value, buf, &len)) return (struct Str) { 0 }; + (*bump)->cursor = buf + need; return (struct Str) { buf, len - 1 }; }