kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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:
Minclude/kit/core.h | 4++++
Minclude/kit/support/hashmap.h | 2++
Msrc/api/cas.c | 5+++--
Msrc/api/compress.c | 5+++--
Msrc/api/core.c | 5+++--
Msrc/api/package.c | 5+++--
Msrc/core/arena.c | 25++++++++++++++++++-------
Msrc/core/core.c | 10++++++----
Msrc/core/diag.c | 4+++-
Msrc/core/pool.c | 5++++-
Msrc/core/pool.h | 5++---
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