diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 1b926ab..48edb9b 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -22,6 +22,18 @@ jobs: run: | find src examples -name '*.c' -o -name '*.h' | xargs clang-format --dry-run --Werror + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install ruff + run: pip install ruff + + - name: Lint Python and SCons files + run: | + ruff check $(find . -path ./build -prune -o \( -name "*.py" -o -name "SConstruct" -o -name "SConscript" \) -print) + scons-test: runs-on: ubuntu-latest steps: @@ -40,7 +52,7 @@ jobs: build-deb: runs-on: ubuntu-latest - needs: [clang-format, scons-test] + needs: [clang-format, ruff, scons-test] steps: - uses: actions/checkout@v4 diff --git a/VERSION b/VERSION index 45a1b3f..38f77a6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.2 +2.0.1 diff --git a/debian/changelog b/debian/changelog index 3529a5a..7a814d1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,16 @@ +libmicrohammer (2.0.1-1) unstable; urgency=medium + + * Widen HToken.len and bits_env.length from uint8_t to size_t, fixing silent + integer truncation for tokens and bit counts exceeding 255 (closes #3) + * Introduce HAMMER_ASSERT in internal.h: unconditional abort for programmer + errors that survives -DNDEBUG; replaces assert()-based guards in bitwriter.c, + glue.c, glue.h, and datastructures.c (closes #6) + * Update h_assert_type macro to abort on type mismatch via HAMMER_ASSERT + * Update token length test to verify 256-byte tokens parse correctly + * Add ruff linting of Python and SCons files to CI pipeline + + -- Mahmoud Elbasiouny Fri, 17 Apr 2026 12:00:00 -0400 + libmicrohammer (2.0.0-1) unstable; urgency=medium * Reintroduce Python, Java/JNI, and C++ language bindings diff --git a/src/bitwriter.c b/src/bitwriter.c index c2b71bc..84a3761 100644 --- a/src/bitwriter.c +++ b/src/bitwriter.c @@ -52,7 +52,7 @@ static void h_bit_writer_reserve(HBitWriter *w, size_t nbits) { } void h_bit_writer_put(HBitWriter *w, uint64_t data, size_t nbits) { - assert(nbits > 0); // Less than or equal to zero makes complete nonsense + HAMMER_ASSERT(nbits > 0); // expand size... h_bit_writer_reserve(w, nbits); @@ -90,10 +90,9 @@ void h_bit_writer_put(HBitWriter *w, uint64_t data, size_t nbits) { } const uint8_t *h_bit_writer_get_buffer(HBitWriter *w, size_t *len) { - assert(len != NULL); - assert(w != NULL); - // Not entirely sure how to handle a non-integral number of bytes... make it an error for now - assert(w->bit_offset == 0); // BUG: change this to some sane behaviour + HAMMER_ASSERT(w != NULL); + HAMMER_ASSERT(len != NULL); + HAMMER_ASSERT(w->bit_offset == 0); *len = w->index; return w->buf; diff --git a/src/datastructures.c b/src/datastructures.c index 46e10df..587b264 100644 --- a/src/datastructures.c +++ b/src/datastructures.c @@ -112,7 +112,7 @@ void h_slist_push(HSlist *slist, void *item) { } bool h_slist_find(HSlist *slist, const void *item) { - assert(item != NULL); + HAMMER_ASSERT(item != NULL); HSlistNode *head = slist->head; while (head != NULL) { if (head->elem == item) @@ -123,7 +123,7 @@ bool h_slist_find(HSlist *slist, const void *item) { } HSlist *h_slist_remove_all(HSlist *slist, const void *item) { - assert(item != NULL); + HAMMER_ASSERT(item != NULL); HSlistNode *node = slist->head; HSlistNode *prev = NULL; while (node != NULL) { diff --git a/src/glue.c b/src/glue.c index 87041e9..b74491d 100644 --- a/src/glue.c +++ b/src/glue.c @@ -78,7 +78,7 @@ HParsedToken *h_make_(HArena *arena, HTokenType type) { } HParsedToken *h_make(HArena *arena, HTokenType type, void *value) { - assert(type >= TT_USER); + HAMMER_ASSERT(type >= TT_USER); HParsedToken *ret = h_make_(arena, type); ret->user = value; return ret; @@ -129,25 +129,25 @@ HParsedToken *h_make_float(HArena *arena, float val) { // XXX -> internal HParsedToken *h_carray_index(const HCountedArray *a, size_t i) { - assert(i < a->used); + HAMMER_ASSERT(i < a->used); return a->elements[i]; } size_t h_seq_len(const HParsedToken *p) { - assert(p != NULL); - assert(p->token_type == TT_SEQUENCE); + HAMMER_ASSERT(p != NULL); + HAMMER_ASSERT(p->token_type == TT_SEQUENCE); return p->seq->used; } HParsedToken **h_seq_elements(const HParsedToken *p) { - assert(p != NULL); - assert(p->token_type == TT_SEQUENCE); + HAMMER_ASSERT(p != NULL); + HAMMER_ASSERT(p->token_type == TT_SEQUENCE); return p->seq->elements; } HParsedToken *h_seq_index(const HParsedToken *p, size_t i) { - assert(p != NULL); - assert(p->token_type == TT_SEQUENCE); + HAMMER_ASSERT(p != NULL); + HAMMER_ASSERT(p->token_type == TT_SEQUENCE); return h_carray_index(p->seq, i); } @@ -172,17 +172,17 @@ HParsedToken *h_seq_index_vpath(const HParsedToken *p, size_t i, va_list va) { } void h_seq_snoc(HParsedToken *xs, const HParsedToken *x) { - assert(xs != NULL); - assert(xs->token_type == TT_SEQUENCE); + HAMMER_ASSERT(xs != NULL); + HAMMER_ASSERT(xs->token_type == TT_SEQUENCE); h_carray_append(xs->seq, (HParsedToken *)x); } void h_seq_append(HParsedToken *xs, const HParsedToken *ys) { - assert(xs != NULL); - assert(xs->token_type == TT_SEQUENCE); - assert(ys != NULL); - assert(ys->token_type == TT_SEQUENCE); + HAMMER_ASSERT(xs != NULL); + HAMMER_ASSERT(xs->token_type == TT_SEQUENCE); + HAMMER_ASSERT(ys != NULL); + HAMMER_ASSERT(ys->token_type == TT_SEQUENCE); for (size_t i = 0; i < ys->seq->used; i++) h_carray_append(xs->seq, ys->seq->elements[i]); @@ -191,7 +191,7 @@ void h_seq_append(HParsedToken *xs, const HParsedToken *ys) { // Flatten nested sequences. Always returns a sequence. // If input element is not a sequence, returns it as a singleton sequence. const HParsedToken *h_seq_flatten(HArena *arena, const HParsedToken *p) { - assert(p != NULL); + HAMMER_ASSERT(p != NULL); HParsedToken *ret = h_make_seq(arena); switch (p->token_type) { diff --git a/src/glue.h b/src/glue.h index 4ae2cca..c2e2a3f 100644 --- a/src/glue.h +++ b/src/glue.h @@ -26,8 +26,7 @@ #define HAMMER_GLUE__H #include "hammer.h" - -#include +#include "internal.h" /** * Grammar specification @@ -218,8 +217,8 @@ HParsedToken *h_make_float(HArena *arena, float val); /** Extract (cast) type-specific value back from HParsedTokens... */ -/** Pass-through assertion that a given token has the expected type. */ -#define h_assert_type(T, P) (assert(P->token_type == (HTokenType)T), P) +/** Pass-through assertion that a given token has the expected type. Aborts on mismatch. */ +#define h_assert_type(T, P) (HAMMER_ASSERT((P)->token_type == (HTokenType)(T)), (P)) /** Convenience short-hand forms of h_assert_type. */ #define H_ASSERT(TYP, TOK) h_assert_type(TT_##TYP, TOK) diff --git a/src/internal.h b/src/internal.h index 3afc416..e1b7791 100644 --- a/src/internal.h +++ b/src/internal.h @@ -28,6 +28,7 @@ #include #include +#include #include /* "Internal" in this case means "we're not ready to commit @@ -47,6 +48,12 @@ } while (0) #endif +/* Unconditional assertion for programmer errors — fires in all builds, including -DNDEBUG. */ +#define HAMMER_ASSERT(cond) \ + ((void)((cond) || \ + (fprintf(stderr, "Hammer assertion failed: %s (%s:%d)\n", #cond, __FILE__, __LINE__), \ + abort(), 0))) + #define HAMMER_FN_IMPL_NOARGS(rtype_t, name) \ rtype_t name(void) { return name##__m(system_allocator); } \ rtype_t name##__m(HAllocator *mm__) diff --git a/src/parsers/bits.c b/src/parsers/bits.c index 8fc6755..42bcf80 100644 --- a/src/parsers/bits.c +++ b/src/parsers/bits.c @@ -4,7 +4,7 @@ #include struct bits_env { - uint8_t length; + size_t length; uint8_t signedp; }; @@ -12,10 +12,11 @@ static HParseResult *parse_bits(void *env, HParseState *state) { struct bits_env *env_ = env; HParsedToken *result = a_new(HParsedToken, 1); result->token_type = (env_->signedp ? TT_SINT : TT_UINT); + // h_read_bits takes int; cast is required by its signature if (env_->signedp) - result->sint = h_read_bits(&state->input_stream, env_->length, true); + result->sint = h_read_bits(&state->input_stream, (int)env_->length, true); else - result->uint = h_read_bits(&state->input_stream, env_->length, false); + result->uint = h_read_bits(&state->input_stream, (int)env_->length, false); result->index = 0; result->bit_length = 0; result->bit_offset = 0; diff --git a/src/parsers/token.c b/src/parsers/token.c index f67cf18..7baa824 100644 --- a/src/parsers/token.c +++ b/src/parsers/token.c @@ -5,12 +5,12 @@ typedef struct { uint8_t *str; - uint8_t len; + size_t len; } HToken; static HParseResult *parse_token(void *env, HParseState *state) { HToken *t = (HToken *)env; - for (int i = 0; i < t->len; ++i) { + for (size_t i = 0; i < t->len; ++i) { uint8_t chr = (uint8_t)h_read_bits(&state->input_stream, 8, false); if (t->str[i] != chr) { return NULL; @@ -76,11 +76,9 @@ HParser *h_token(const uint8_t *str, const size_t len) { return h_token__m(&system_allocator, str, len); } HParser *h_token__m(HAllocator *mm__, const uint8_t *str, const size_t len) { - // Length has to be <= 255 (uint8) as defined by HToken struct - assert(len <= UINT8_MAX); HToken *t = h_new(HToken, 1); uint8_t *str_cpy = h_new(uint8_t, len); memcpy(str_cpy, str, len); - t->str = str_cpy, t->len = (uint8_t)len; + t->str = str_cpy, t->len = len; return h_new_parser(mm__, &token_vt, t); } diff --git a/src/sloballoc.c b/src/sloballoc.c index 7643a3e..6b2f656 100644 --- a/src/sloballoc.c +++ b/src/sloballoc.c @@ -25,8 +25,10 @@ struct slob { SLOB *slobinit(void *mem, size_t size) { SLOB *slob = mem; - assert(size >= sizeof(SLOB) + sizeof(struct block)); - assert(size < UINTPTR_MAX - (uintptr_t)mem); + if (size < sizeof(SLOB) + sizeof(struct block)) + return NULL; + if (size >= UINTPTR_MAX - (uintptr_t)mem) + return NULL; slob = mem; slob->size = size - sizeof(SLOB); diff --git a/tests/parsers/test_token.c b/tests/parsers/test_token.c index 611191e..26f0b88 100644 --- a/tests/parsers/test_token.c +++ b/tests/parsers/test_token.c @@ -38,29 +38,32 @@ static void test_reshape_token(gconstpointer backend) { } } -// Test token.c: len not > UINT8_MAX assert +// Test token.c: tokens longer than 255 bytes are now supported +#define TOKEN_TEST_LEN 256 static void test_token_len_assert(gconstpointer backend) { - (void)backend; + HParserBackend be = (HParserBackend)GPOINTER_TO_INT(backend); - if (g_test_subprocess()) { - uint8_t expected[256]; - memset(expected, 0x41, sizeof(expected)); + uint8_t expected[TOKEN_TEST_LEN]; + memset(expected, 0x41, sizeof(expected)); - HParser *parser = h_token(expected, sizeof(expected)); + HParser *parser = h_token(expected, TOKEN_TEST_LEN); + g_check_cmp_ptr(parser, !=, NULL); - // Shouldn't get here - (void)parser; - return; + h_compile(parser, be, NULL); + HParseResult *res = h_parse(parser, expected, TOKEN_TEST_LEN); + g_check_cmp_ptr(res, !=, NULL); + if (res) { + g_check_cmp_ptr(res->ast, !=, NULL); + if (res->ast && res->ast->token_type == TT_BYTES) + g_check_cmp_int(res->ast->bytes.len, ==, TOKEN_TEST_LEN); + h_parse_result_free(res); } - - g_test_trap_subprocess(NULL, 0, 0); - g_test_trap_assert_failed(); } void register_token_tests(void) { g_test_add_data_func("/core/parser/packrat/reshape_token", GINT_TO_POINTER(PB_PACKRAT), test_reshape_token); - + g_test_add_data_func("/core/parser/packrat/token_len_assert", GINT_TO_POINTER(PB_PACKRAT), test_token_len_assert); } diff --git a/tests/t_bitreader.c b/tests/t_bitreader.c index c7cdf93..9911b23 100644 --- a/tests/t_bitreader.c +++ b/tests/t_bitreader.c @@ -234,4 +234,3 @@ void register_bitreader_tests(void) { g_test_add_func("/core/bitreader/byte_le_fast_path", test_read_bits_byte_le_fast_path); g_test_add_func("/core/bitreader/byte_le_slow_path", test_read_bits_byte_le_slow_path); } -