diff --git a/src/parsers/choice.c b/src/parsers/choice.c index 90c3662b515babe4a69b0e24dc146ebe1d0a647d..69e4aee778977243594f0ffc124cb3931f4a8d03 100644 --- a/src/parsers/choice.c +++ b/src/parsers/choice.c @@ -164,5 +164,6 @@ HParser* h_choice__ma(HAllocator* mm__, void *args[]) { ret->vtable = &choice_vt; ret->env = (void*)s; ret->backend = PB_MIN; + ret->desugared = NULL; return ret; } diff --git a/src/parsers/permutation.c b/src/parsers/permutation.c index b16758413eeafe2ce2ae91db2ebbe7593681d3cd..ec256c4af1f76292847102d0a07eca5cb19e5bae 100644 --- a/src/parsers/permutation.c +++ b/src/parsers/permutation.c @@ -176,5 +176,6 @@ HParser* h_permutation__ma(HAllocator* mm__, void *args[]) { ret->vtable = &permutation_vt; ret->env = (void*)s; ret->backend = PB_MIN; + ret->desugared = NULL; return ret; } diff --git a/src/parsers/sequence.c b/src/parsers/sequence.c index 55c0c8885573ef7779714efd49eaf64cc59ac878..5ca034ad50c1b752ff48e202008b21c0d1e75375 100644 --- a/src/parsers/sequence.c +++ b/src/parsers/sequence.c @@ -171,5 +171,6 @@ HParser* h_sequence__ma(HAllocator* mm__, void *args[]) { ret->vtable = &sequence_vt; ret->env = (void*)s; ret->backend = PB_MIN; + ret->desugared = NULL; return ret; } diff --git a/src/system_allocator.c b/src/system_allocator.c index 39a1a7e77040c865f2d4f99977eb264391286bb4..f6e9cdcbbe74fedea568a73ada868e93d83c0660 100644 --- a/src/system_allocator.c +++ b/src/system_allocator.c @@ -59,6 +59,8 @@ static void* system_realloc(HAllocator *allocator, void* uptr, size_t size) { if (!uptr) { return system_alloc(allocator, size); } + // XXX this is incorrect if size == 0 and BLOCK_HEADER_SIZE != 0; it fails + // to behave like free(3) void* block = realloc(block_for_user_ptr(uptr), block_size(size)); if (!block) { return NULL; @@ -66,6 +68,7 @@ static void* system_realloc(HAllocator *allocator, void* uptr, size_t size) { uptr = user_ptr(block); #ifdef DEBUG__MEMFILL + // XXX this is the wrong block; this is reading uninitialized memory size_t old_size = ((HDebugBlockHeader*)block)->size; if (size > old_size) memset((char*)uptr+old_size, DEBUG__MEMFILL, size - old_size); diff --git a/src/t_regression.c b/src/t_regression.c index 3dfe5dce12d0d9c9306ede01d5daf3a45ebab488..4e8ad0cdfc9df5c8ba2df32e4faaead50218ee7a 100644 --- a/src/t_regression.c +++ b/src/t_regression.c @@ -193,6 +193,83 @@ static void test_charset_bits(void) { g_check_cmp_uint32(test_charset_bits__buf[32], ==, 0xAB); } + +// Allocator for reproducing error 19. + +// The bug is a result of uninitialized data being used, initially +// assumed to be zero. Unfortunately, this assumption is often true, +// so reproducing the bug reliably and in a minimal fashion requires +// making it false. Fortunately, glibc malloc has an M_PERTURB option +// for making that assumption false. Unfortunately, we want the test +// to reproduce the bug on systems that don't use glibc. Fortunately, +// the standard Hammer system allocator has a DEBUG__MEMFILL option to +// fill uninitialized memory with a fill byte. Unfortunately, you +// have to recompile Hammer with that symbol #defined in order to +// enable it. Fortunately, hammer allows you to supply your own +// allocator. So this is a simple non-#define-dependent allocator +// that writes 0xbabababa†over all the memory it allocates. (But not +// the memory it reallocs, because, as it happens, the uninitialized +// memory in this case didn't come from a realloc.) +// +// Honestly I think we ought to remove the #ifdefs from +// system_allocator and always compile both the DEBUG__MEMFILL version +// and the non-DEBUG__MEMFILL version, merely changing which one is +// system_allocator, which is after all a struct of three pointers +// that can even be modified at run-time. +// +// †Can you hear it, Mr. Toot? + +static void* deadbeefing_malloc(HAllocator *allocator, size_t size) { + char *block = malloc(size); + if (block) memset(block, 0xba, size); + return block; +} + +// Don't deadbeef on realloc because it isn't necessary to reproduce this bug. +static void* deadbeefing_realloc(HAllocator *allocator, void *uptr, size_t size) { + return realloc(uptr, size); +} + +static void deadbeefing_free(HAllocator *allocator, void *uptr) { + free(uptr); +} + +static HAllocator deadbeefing_allocator = { + .alloc = deadbeefing_malloc, + .realloc = deadbeefing_realloc, + .free = deadbeefing_free, +}; + +static void test_bug_19() { + void *args[] = { + h_ch_range__m(&deadbeefing_allocator, '0', '9'), + h_ch_range__m(&deadbeefing_allocator, 'A', 'Z'), + h_ch_range__m(&deadbeefing_allocator, 'a', 'z'), + NULL, + }; + + HParser *parser = h_choice__ma(&deadbeefing_allocator, args); + + // In bug 19 ("GLR backend reaches unreachable code"), this call + // would fail because h_choice__ma allocated an HParser with h_new + // and didn't initialize its ->desugared field; consequently in + // the call chain h_compile ... h_lalr_compile ... h_desugar, + // h_desugar would find that ->desugared was already non-NULL (set + // to 0xbabababa in the above deadbeefing_malloc), and just return + // it, leading to a crash immediately afterwards in collect_nts. + // We don't actually care if the compile succeeds or fails, just + // that it doesn't crash. + h_compile(parser, PB_GLR, NULL); + + // The same bug happened in h_sequence__ma. + h_compile(h_sequence__ma(&deadbeefing_allocator, args), PB_GLR, NULL); + + // It also exists in h_permutation__ma, but it doesn't happen to + // manifest in the same way. I don't know how to write a test for + // the h_permutation__ma case. + g_assert_true(1); +} + void register_regression_tests(void) { g_test_add_func("/core/regression/bug118", test_bug118); g_test_add_func("/core/regression/seq_index_path", test_seq_index_path); @@ -202,4 +279,5 @@ void register_regression_tests(void) { g_test_add_func("/core/regression/lalr_charset_lhs", test_lalr_charset_lhs); g_test_add_func("/core/regression/cfg_many_seq", test_cfg_many_seq); g_test_add_func("/core/regression/charset_bits", test_charset_bits); + g_test_add_func("/core/regression/bug19", test_bug_19); }