commit cdc320512a2f9743ff006d9c811066f17f00249c
parent 06ba914aefcbb8ccfae5282e2c469e7897b2eadf
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 9 Jun 2026 17:06:12 -0700
core: fix hashmap OOM NULL-deref, route API diagnostics through diag_emitv
- hashmap _set/_try_insert: guard against resize-from-empty OOM (return -1
instead of dereferencing a NULL slots array through a wrapped mask)
- cas/compress/package/api-core _diagf + compiler_panicv: emit through
diag_emitv so the sink's error counter is maintained (incl. FATAL before
longjmp); count-before-emit in diag_emitv
- arena_reset: release a retained oversize head block instead of inflating
the baseline permanently
- pool: drop dead core-Pool type_cache field (frontend state lives on the
frontend Pool); add pool_intern_cstr convenience
- __kit_assert_fail: mark _Noreturn to match its declaration
- core.h: guard KIT_API definition to stay idempotent with trace.h
Diffstat:
11 files changed, 51 insertions(+), 24 deletions(-)
diff --git a/include/kit/core.h b/include/kit/core.h
@@ -16,11 +16,15 @@
#include <stddef.h>
#include <stdint.h>
+/* KIT_API is defined identically here and in <kit/trace.h> so each header
+ * stands alone; the #ifndef guard keeps the two idempotent if both are seen. */
+#ifndef KIT_API
#if defined(__GNUC__) || defined(__clang__) || defined(__kit__)
#define KIT_API __attribute__((visibility("default")))
#else
#define KIT_API
#endif
+#endif
/* Developer tracing (KIT_TRACE / KIT_LOG*) is part of the core substrate. */
#include <kit/trace.h>
diff --git a/include/kit/support/hashmap.h b/include/kit/support/hashmap.h
@@ -105,6 +105,7 @@ static inline uint32_t kit_hash_u64(uint64_t x) {
if (m->cap == 0 || \
m->used * KIT_HASHMAP_LOAD_DEN >= m->cap * KIT_HASHMAP_LOAD_NUM) \
NAME##_resize(m, m->cap ? m->cap * 2u : KIT_HASHMAP_INIT_CAP); \
+ if (m->cap == 0) return -1; /* resize OOM'd from empty: avoid NULL deref */\
mask = m->cap - 1u; \
j = HASH_FN(k) & mask; \
while (m->slots[j].k) { \
@@ -126,6 +127,7 @@ static inline uint32_t kit_hash_u64(uint64_t x) {
if (m->cap == 0 || \
m->used * KIT_HASHMAP_LOAD_DEN >= m->cap * KIT_HASHMAP_LOAD_NUM) \
NAME##_resize(m, m->cap ? m->cap * 2u : KIT_HASHMAP_INIT_CAP); \
+ if (m->cap == 0) return -1; /* resize OOM'd from empty: avoid NULL deref */\
mask = m->cap - 1u; \
j = HASH_FN(k) & mask; \
while (m->slots[j].k) { \
diff --git a/src/api/cas.c b/src/api/cas.c
@@ -8,6 +8,7 @@
#include <stdio.h>
#include <string.h>
+#include "core/diag.h"
#include "dist/blob.h"
#include "dist/dist.h"
#include "dist/tree.h"
@@ -18,12 +19,12 @@
static void cas_diagf(const KitContext* ctx, const char* fmt, ...) {
va_list ap;
KitSrcLoc loc;
- if (!ctx || !ctx->diag || !ctx->diag->emit) return;
+ if (!ctx || !ctx->diag) return;
loc.file_id = 0;
loc.line = 0;
loc.col = 0;
va_start(ap, fmt);
- ctx->diag->emit(ctx->diag, KIT_DIAG_ERROR, loc, fmt, ap);
+ diag_emitv(ctx->diag, KIT_DIAG_ERROR, loc, fmt, ap);
va_end(ap);
}
diff --git a/src/api/compress.c b/src/api/compress.c
@@ -5,6 +5,7 @@
#include <kit/compress.h>
#include <stdarg.h>
+#include "core/diag.h"
#include "dist/deflate.h"
#include "dist/dist.h"
#include "dist/lz4frame.h"
@@ -19,12 +20,12 @@ static const uint8_t COMPRESS_LZ4F_MAGIC[4] = {0x04, 0x22, 0x4d, 0x18};
static void compress_diagf(const KitContext* ctx, const char* fmt, ...) {
va_list ap;
KitSrcLoc loc;
- if (!ctx || !ctx->diag || !ctx->diag->emit) return;
+ if (!ctx || !ctx->diag) return;
loc.file_id = 0;
loc.line = 0;
loc.col = 0;
va_start(ap, fmt);
- ctx->diag->emit(ctx->diag, KIT_DIAG_ERROR, loc, fmt, ap);
+ diag_emitv(ctx->diag, KIT_DIAG_ERROR, loc, fmt, ap);
va_end(ap);
}
diff --git a/src/api/core.c b/src/api/core.c
@@ -7,6 +7,7 @@
#include <string.h>
#include "arch/arch.h"
+#include "core/diag.h"
#include "core/heap.h"
#include "core/pool.h"
#include "core/slice.h"
@@ -14,12 +15,12 @@
static void target_diag(const KitContext* ctx, const char* fmt, ...) {
va_list ap;
KitSrcLoc loc;
- if (!ctx || !ctx->diag || !ctx->diag->emit) return;
+ if (!ctx || !ctx->diag) return;
loc.file_id = 0;
loc.line = 0;
loc.col = 0;
va_start(ap, fmt);
- ctx->diag->emit(ctx->diag, KIT_DIAG_ERROR, loc, fmt, ap);
+ diag_emitv(ctx->diag, KIT_DIAG_ERROR, loc, fmt, ap);
va_end(ap);
}
diff --git a/src/api/package.c b/src/api/package.c
@@ -14,6 +14,7 @@
#include <stdlib.h>
#include <string.h>
+#include "core/diag.h"
#include "dist/blake2b.h"
#include "dist/blob.h"
#include "dist/cas.h"
@@ -103,12 +104,12 @@ typedef struct PkgLoadedTree {
static void pkg_diagf(const KitContext* ctx, const char* fmt, ...) {
va_list ap;
KitSrcLoc loc;
- if (!ctx || !ctx->diag || !ctx->diag->emit) return;
+ if (!ctx || !ctx->diag) return;
loc.file_id = 0;
loc.line = 0;
loc.col = 0;
va_start(ap, fmt);
- ctx->diag->emit(ctx->diag, KIT_DIAG_ERROR, loc, fmt, ap);
+ diag_emitv(ctx->diag, KIT_DIAG_ERROR, loc, fmt, ap);
va_end(ap);
}
diff --git a/src/core/arena.c b/src/core/arena.c
@@ -1,7 +1,8 @@
/* Bump-pointer arena. One linked list of fixed-size blocks; new blocks
- * are allocated when a request doesn't fit. arena_reset releases all
- * but the head block (so the freed-and-reallocated common case stays
- * O(1)). Oversize allocations get their own dedicated block. */
+ * are allocated when a request doesn't fit. arena_reset releases all but a
+ * default-size head block (so the freed-and-reallocated common case stays
+ * O(1)); a retained oversize head is released too. Oversize allocations get
+ * their own dedicated block. */
#include "core/arena.h"
@@ -47,7 +48,11 @@ void arena_fini(Arena* a) {
}
void arena_reset(Arena* a) {
- /* Free every block past the head; reuse head if present. */
+ /* Free every block past the head, then reuse the head as the O(1) reset
+ * baseline -- unless it is an oversize dedicated block (cap > block_size).
+ * The head is the most-recently allocated block, so a one-off large
+ * allocation just before reset would otherwise become a permanently
+ * inflated baseline; release it and let the next alloc size a fresh one. */
if (a->head) {
ArenaBlock* b = a->head->next;
while (b) {
@@ -55,9 +60,15 @@ void arena_reset(Arena* a) {
a->heap->free(a->heap, b, sizeof(ArenaBlock) + b->cap);
b = next;
}
- a->head->next = NULL;
- a->cur = a->head->data;
- a->end = a->head->data + a->head->cap;
+ if (a->head->cap > a->block_size) {
+ a->heap->free(a->heap, a->head, sizeof(ArenaBlock) + a->head->cap);
+ a->head = NULL;
+ a->cur = a->end = NULL;
+ } else {
+ a->head->next = NULL;
+ a->cur = a->head->data;
+ a->end = a->head->data + a->head->cap;
+ }
} else {
a->cur = a->end = NULL;
}
diff --git a/src/core/core.c b/src/core/core.c
@@ -66,8 +66,8 @@ void kit_trace_emit(const char* module, int level, const char* file, int line,
#if defined(__GNUC__) || defined(__clang__) || defined(__kit__)
__attribute__((weak))
#endif
-void __kit_assert_fail(const char* expr, const char* file, int line,
- const char* func) {
+_Noreturn void __kit_assert_fail(const char* expr, const char* file, int line,
+ const char* func) {
(void)expr;
(void)file;
(void)line;
@@ -219,8 +219,10 @@ void compiler_panic(Compiler* c, SrcLoc loc, const char* fmt, ...) {
}
void compiler_panicv(Compiler* c, SrcLoc loc, const char* fmt, va_list ap) {
- if (c->ctx && c->ctx->diag && c->ctx->diag->emit) {
- c->ctx->diag->emit(c->ctx->diag, KIT_DIAG_FATAL, loc, fmt, ap);
+ /* Route through diag_emitv so the FATAL bumps the sink's error counter
+ * before we longjmp away (diag_emitv tolerates a NULL emit slot). */
+ if (c->ctx && c->ctx->diag) {
+ diag_emitv(c->ctx->diag, DIAG_FATAL, loc, fmt, ap);
}
if (c->panic_frame) longjmp(c->panic_frame->env, 1);
longjmp(c->panic, 1);
diff --git a/src/core/diag.c b/src/core/diag.c
@@ -13,11 +13,13 @@ void diag_emit(DiagSink* s, DiagKind k, SrcLoc loc, const char* fmt, ...) {
void diag_emitv(DiagSink* s, DiagKind k, SrcLoc loc, const char* fmt,
va_list ap) {
- if (s && s->emit) s->emit(s, k, loc, fmt, ap);
+ /* Count before emitting: a FATAL sink that longjmps out of emit must still
+ * leave the error reflected in the counter. */
if (s) {
if (k == DIAG_WARN)
s->warnings++;
else if (k == DIAG_ERROR || k == DIAG_FATAL)
s->errors++;
}
+ if (s && s->emit) s->emit(s, k, loc, fmt, ap);
}
diff --git a/src/core/pool.c b/src/core/pool.c
@@ -75,7 +75,6 @@ void pool_init(Pool* p, Heap* h) {
p->entries = NULL;
p->nentries = 0;
p->entries_cap = 0;
- p->type_cache = NULL;
table_rehash(p, POOL_INITIAL_TABLE_CAP);
/* Reserve entry 0 as the "none" sentinel. */
if (entries_grow(p) == 0) {
@@ -95,6 +94,10 @@ void pool_fini(Pool* p) {
p->entries = NULL;
}
+Sym pool_intern_cstr(Pool* p, const char* z) {
+ return pool_intern_slice(p, slice_from_cstr(z));
+}
+
Sym pool_intern_slice(Pool* p, Slice in) {
const char* s = in.s;
size_t len = in.len;
diff --git a/src/core/pool.h b/src/core/pool.h
@@ -25,9 +25,6 @@ struct Pool {
PoolEntry* entries;
u32 nentries;
u32 entries_cap;
-
- /* Frontends may hang language-specific interning state here. */
- void* type_cache;
};
void pool_init(Pool*, Heap*);
@@ -38,6 +35,8 @@ void pool_fini(Pool*);
* slice (SLICE_NULL for Sym 0); its pointer is NUL-terminated as a boundary
* convenience, with the NUL excluded from len. */
Sym pool_intern_slice(Pool*, Slice);
+/* Convenience: intern a NUL-terminated C string (interns Sym 0 for NULL). */
+Sym pool_intern_cstr(Pool*, const char*);
Slice pool_slice(Pool*, Sym);
#endif