diff --git a/.gitignore b/.gitignore index 721dcf9377a334d6d4f75617ea31df6f7c2ee639..570fbf81029442ab30ef3f4c6e691fa04146efd7 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ Session.vim *.gcov cscope.out build/ +libhammer.pc .sconsign.dblite *.os *.pyc diff --git a/.travis.yml b/.travis.yml index 8973c57ee8376d774f79bd15d9ee4cf71ce485f7..b533da3191e75912e6d34f8c0bdd4a1212e684f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -92,6 +92,8 @@ matrix: env: BINDINGS=cpp CC=clang before_install: - sudo apt-get update -qq + - sudo apt-get install lcov + - gem install coveralls-lcov - if [ "$BINDINGS" != "none" ]; then sudo apt-get install -qq swig; fi - if [ "$BINDINGS" == "perl" ]; then sudo add-apt-repository ppa:dns/irc -y; sudo apt-get update -qq; sudo apt-get install -qq swig=2.0.8-1irc1~12.04; fi - if [ "$BINDINGS" == "python" ]; then sudo apt-get install -qq python-dev; fi @@ -99,12 +101,14 @@ before_install: install: true before_script: - if [ "$BINDINGS" == "php" ]; then phpenv config-add src/bindings/php/hammer.ini; fi -script: - - scons bindings=$BINDINGS test +script: + - if [ "$BINDINGS" == "none" ]; then scons test --variant=debug --coverage; else scons bindings=$BINDINGS test; fi +after_success: + - if [ "$BINDINGS" == "none" ]; then if [ "$CC" == "clang" ]; then llvm-cov gcov -o coverage.info build/debug/src/test_suite.gcda; else lcov --capture --directory build/debug/src --output-file coverage.info; fi; fi + - coveralls-lcov coverage.info notifications: irc: channels: - "irc.upstandinghackers.com#hammer" use_notice: true skip_join: true - diff --git a/SConstruct b/SConstruct index a8f7ce8b9d39964458dea9fd1ee1fbe3d0a4b474..bb2bb858e51ecf84db7588ba1c212bdf164e2e5d 100644 --- a/SConstruct +++ b/SConstruct @@ -90,15 +90,18 @@ if GetOption("variant") == 'debug': else: env = opt -if GetOption("coverage"): - env.Append(CFLAGS=["-fprofile-arcs", "-ftest-coverage"], - CXXFLAGS=["-fprofile-arcs", "-ftest-coverage"], - LDFLAGS=["-fprofile-arcs", "-ftest-coverage"], - LIBS=['gcov']) - env["CC"] = os.getenv("CC") or env["CC"] env["CXX"] = os.getenv("CXX") or env["CXX"] +if GetOption("coverage"): + env.Append(CFLAGS=["--coverage"], + CXXFLAGS=["--coverage"], + LDFLAGS=["--coverage"]) + if env["CC"] == "gcc": + env.Append(LIBS=['gcov']) + else: + env.ParseConfig('llvm-config --ldflags') + if os.getenv("CC") == "clang" or env['PLATFORM'] == 'darwin': env.Replace(CC="clang", CXX="clang++") diff --git a/src/SConscript b/src/SConscript index 05ffa983674488db41cc0c6f32f642c2f43e30f8..7a1b9d495b1e7d3fe018ad4342da18eae4a4f9e6 100644 --- a/src/SConscript +++ b/src/SConscript @@ -7,7 +7,8 @@ dist_headers = [ "allocator.h", "compiler_specifics.h", "glue.h", - "internal.h" + "internal.h", + "platform.h" ] parsers_headers = [ @@ -87,8 +88,9 @@ env.Install("$pkgconfigpath", "../../../libhammer.pc") testenv = env.Clone() testenv.ParseConfig('pkg-config --cflags --libs glib-2.0') -testenv.Append(LIBS=['hammer'], LIBPATH=['.']) -ctestexec = testenv.Program('test_suite', ctests + ['test_suite.c']) +testenv.Append(LIBS=['hammer']) +testenv.Prepend(LIBPATH=['.']) +ctestexec = testenv.Program('test_suite', ctests + ['test_suite.c'], LINKFLAGS="--coverage" if testenv.GetOption("coverage") else None) ctest = Alias('testc', [ctestexec], "".join(["env LD_LIBRARY_PATH=", os.path.dirname(ctestexec[0].path), " ", ctestexec[0].path])) AlwaysBuild(ctest) testruns.append(ctest) diff --git a/src/allocator.c b/src/allocator.c index 258edfa5643a6123a4ca3ce640b06e89bd39a5a2..cc259e605c56573b506f39194793e804ab4bf8b6 100644 --- a/src/allocator.c +++ b/src/allocator.c @@ -18,6 +18,7 @@ #include <string.h> #include <stdint.h> #include <sys/types.h> +#include <setjmp.h> #include "hammer.h" #include "internal.h" @@ -42,17 +43,24 @@ struct HArena_ { size_t block_size; size_t used; size_t wasted; + + jmp_buf *except; }; +void* h_alloc(HAllocator* mm__, size_t size) { + void *p = mm__->alloc(mm__, size); + if(!p) + h_platform_errx(1, "memory allocation failed (%uB requested)\n", (unsigned int)size); + return p; +} + HArena *h_new_arena(HAllocator* mm__, size_t block_size) { if (block_size == 0) block_size = 4096; struct HArena_ *ret = h_new(struct HArena_, 1); - struct arena_link *link = (struct arena_link*)mm__->alloc(mm__, sizeof(struct arena_link) + block_size); - if (!link) { - // TODO: error-reporting -- let user know that arena link couldn't be allocated - return NULL; - } + struct arena_link *link = (struct arena_link*)h_alloc(mm__, sizeof(struct arena_link) + block_size); + assert(ret != NULL); + assert(link != NULL); memset(link, 0, sizeof(struct arena_link) + block_size); link->free = block_size; link->used = 0; @@ -62,9 +70,26 @@ HArena *h_new_arena(HAllocator* mm__, size_t block_size) { ret->used = 0; ret->mm__ = mm__; ret->wasted = sizeof(struct arena_link) + sizeof(struct HArena_) + block_size; + ret->except = NULL; return ret; } +void h_arena_set_except(HArena *arena, jmp_buf *except) +{ + arena->except = except; +} + +static void *alloc_block(HArena *arena, size_t size) +{ + void *block = arena->mm__->alloc(arena->mm__, size); + if (!block) { + if (arena->except) + longjmp(*arena->except, 1); + h_platform_errx(1, "memory allocation failed (%uB requested)\n", (unsigned int)size); + } + return block; +} + void* h_arena_malloc(HArena *arena, size_t size) { if (size <= arena->head->free) { // fast path.. @@ -79,22 +104,16 @@ void* h_arena_malloc(HArena *arena, size_t size) { // This involves some annoying casting... arena->used += size; arena->wasted += sizeof(struct arena_link*); - void* link = arena->mm__->alloc(arena->mm__, size + sizeof(struct arena_link*)); - if (!link) { - // TODO: error-reporting -- let user know that arena link couldn't be allocated - return NULL; - } + void* link = alloc_block(arena, size + sizeof(struct arena_link*)); + assert(link != NULL); memset(link, 0, size + sizeof(struct arena_link*)); *(struct arena_link**)link = arena->head->next; arena->head->next = (struct arena_link*)link; return (void*)(((uint8_t*)link) + sizeof(struct arena_link*)); } else { // we just need to allocate an ordinary new block. - struct arena_link *link = (struct arena_link*)arena->mm__->alloc(arena->mm__, sizeof(struct arena_link) + arena->block_size); - if (!link) { - // TODO: error-reporting -- let user know that arena link couldn't be allocated - return NULL; - } + struct arena_link *link = alloc_block(arena, sizeof(struct arena_link) + arena->block_size); + assert(link != NULL); memset(link, 0, sizeof(struct arena_link) + arena->block_size); link->free = arena->block_size - size; link->used = size; diff --git a/src/allocator.h b/src/allocator.h index 4a486936a058c0a619a83e7afdf0c5dfffc50d48..dc88af68f22895f584065a491463b3f8576c09e9 100644 --- a/src/allocator.h +++ b/src/allocator.h @@ -18,22 +18,12 @@ #ifndef HAMMER_ALLOCATOR__H__ #define HAMMER_ALLOCATOR__H__ #include <sys/types.h> +#include <setjmp.h> #ifdef __cplusplus extern "C" { #endif -// TODO(thequux): Turn this into an "HAllocatorVtable", and add a wrapper that also takes an environment pointer. -typedef struct HAllocator_ { - void* (*alloc)(struct HAllocator_* allocator, size_t size); - void* (*realloc)(struct HAllocator_* allocator, void* ptr, size_t size); - void (*free)(struct HAllocator_* allocator, void* ptr); -} HAllocator; - -typedef struct HArena_ HArena ; // hidden implementation - -HArena *h_new_arena(HAllocator* allocator, size_t block_size); // pass 0 for default... - #if defined __llvm__ # if __has_attribute(malloc) # define ATTR_MALLOC(n) __attribute__((malloc)) @@ -48,9 +38,23 @@ HArena *h_new_arena(HAllocator* allocator, size_t block_size); // pass 0 for def # define ATTR_MALLOC(n) #endif +// TODO(thequux): Turn this into an "HAllocatorVtable", and add a wrapper that also takes an environment pointer. +typedef struct HAllocator_ { + void* (*alloc)(struct HAllocator_* allocator, size_t size); + void* (*realloc)(struct HAllocator_* allocator, void* ptr, size_t size); + void (*free)(struct HAllocator_* allocator, void* ptr); +} HAllocator; + +void* h_alloc(HAllocator* allocator, size_t size) ATTR_MALLOC(2); + +typedef struct HArena_ HArena ; // hidden implementation + +HArena *h_new_arena(HAllocator* allocator, size_t block_size); // pass 0 for default... + void* h_arena_malloc(HArena *arena, size_t count) ATTR_MALLOC(2); void h_arena_free(HArena *arena, void* ptr); // For future expansion, with alternate memory managers. void h_delete_arena(HArena *arena); +void h_arena_set_except(HArena *arena, jmp_buf *except); typedef struct { size_t used; diff --git a/src/backends/glr.c b/src/backends/glr.c index e753ea55d938cc07582ceed83db92354cbacf68f..535dc2860c59018324893da1450cfc4ff4fadf8b 100644 --- a/src/backends/glr.c +++ b/src/backends/glr.c @@ -198,6 +198,16 @@ HParseResult *h_glr_parse(HAllocator* mm__, const HParser* parser, HInputStream* HArena *arena = h_new_arena(mm__, 0); // will hold the results HArena *tarena = h_new_arena(mm__, 0); // tmp, deleted after parse + // out-of-memory handling + jmp_buf except; + h_arena_set_except(arena, &except); + h_arena_set_except(tarena, &except); + if(setjmp(except)) { + h_delete_arena(arena); + h_delete_arena(tarena); + return NULL; + } + // allocate engine lists (will hold one engine per state) // these are swapped each iteration HSlist *engines = h_slist_new(tarena); diff --git a/src/backends/llk.c b/src/backends/llk.c index 0ab4610a29a1fcdefd1ca163ea2be8785b3ed0e6..4e8209b30f4aa7bd97f5df1c49202643d4efedd4 100644 --- a/src/backends/llk.c +++ b/src/backends/llk.c @@ -383,12 +383,20 @@ static HCountedArray *llk_parse_chunk_(HLLkState *s, const HParser* parser, HArena *arena = s->arena; HArena *tarena = s->tarena; HSlist *stack = s->stack; - HCountedArray *seq = s->seq; size_t kmax = table->kmax; - if(!seq) + if(!s->seq) return NULL; // parse already failed + // out-of-memory handling + jmp_buf except; + h_arena_set_except(arena, &except); + h_arena_set_except(tarena, &except); + if(setjmp(except)) + goto no_parse; + + HCountedArray *seq = s->seq; + if(s->win.length > 0) { append_win(kmax, s, chunk); stream = &s->win; @@ -527,12 +535,15 @@ static HCountedArray *llk_parse_chunk_(HLLkState *s, const HParser* parser, // since we started with a single nonterminal on the stack, seq should // contain exactly the parse result. assert(seq->used == 1); + + end: + h_arena_set_except(arena, NULL); + h_arena_set_except(tarena, NULL); return seq; no_parse: - h_delete_arena(arena); - s->arena = NULL; - return NULL; + seq = NULL; + goto end; need_input: if(stream->last_chunk) @@ -540,7 +551,7 @@ static HCountedArray *llk_parse_chunk_(HLLkState *s, const HParser* parser, if(tok) h_arena_free(arena, tok); // no result, yet h_slist_push(stack, x); // try this symbol again next time - return seq; + goto end; } static HParseResult *llk_parse_finish_(HAllocator *mm__, HLLkState *s) @@ -550,6 +561,8 @@ static HParseResult *llk_parse_finish_(HAllocator *mm__, HLLkState *s) if(s->seq) { assert(s->seq->used == 1); res = make_result(s->arena, s->seq->elements[0]); + } else { + h_delete_arena(s->arena); } h_delete_arena(s->tarena); @@ -582,6 +595,9 @@ bool h_llk_parse_chunk(HSuspendedParser *s, HInputStream *input) state->seq = llk_parse_chunk_(state, s->parser, input); + h_arena_set_except(state->arena, NULL); + h_arena_set_except(state->tarena, NULL); + return (state->seq == NULL || h_slist_empty(state->stack)); } diff --git a/src/backends/lr.c b/src/backends/lr.c index fb256c0bfafa0b6c53b32307bea64f61d4885919..f2ac4956d80358e51d35c0e70484013bbfde212a 100644 --- a/src/backends/lr.c +++ b/src/backends/lr.c @@ -388,6 +388,16 @@ HParseResult *h_lr_parse(HAllocator* mm__, const HParser* parser, HInputStream* HArena *tarena = h_new_arena(mm__, 0); // tmp, deleted after parse HLREngine *engine = h_lrengine_new(arena, tarena, table, stream); + // out-of-memory handling + jmp_buf except; + h_arena_set_except(arena, &except); + h_arena_set_except(tarena, &except); + if(setjmp(except)) { + h_delete_arena(arena); + h_delete_arena(tarena); + return NULL; + } + // iterate engine to completion while(h_lrengine_step(engine, h_lrengine_action(engine))); @@ -416,6 +426,16 @@ bool h_lr_parse_chunk(HSuspendedParser* s, HInputStream *stream) engine->input = *stream; bool run = true; + + // out-of-memory handling + jmp_buf except; + h_arena_set_except(engine->arena, &except); + h_arena_set_except(engine->tarena, &except); + if(setjmp(except)) { + run = false; // done immediately + assert(engine->state != HLR_SUCCESS); // h_parse_finish will return NULL + } + while(run) { // check input against table to determine which action to take const HLRAction *action = h_lrengine_action(engine); @@ -431,6 +451,9 @@ bool h_lr_parse_chunk(HSuspendedParser* s, HInputStream *stream) break; } + h_arena_set_except(engine->arena, NULL); + h_arena_set_except(engine->tarena, NULL); + *stream = engine->input; return !run; // done if engine no longer running } diff --git a/src/backends/packrat.c b/src/backends/packrat.c index e6f86f2957e5988dd94557534ab66b6050fda915..b7e47aef07422a1520849d94e5420b56e6112d79 100644 --- a/src/backends/packrat.c +++ b/src/backends/packrat.c @@ -3,7 +3,7 @@ #include "../internal.h" #include "../parsers/parser_internal.h" -// short-hand for creating cache values (regular case) +// short-hand for creating lowlevel parse cache values (parse result case) static HParserCacheValue * cached_result(HParseState *state, HParseResult *result) { HParserCacheValue *ret = a_new(HParserCacheValue, 1); @@ -13,7 +13,7 @@ HParserCacheValue * cached_result(HParseState *state, HParseResult *result) { return ret; } -// short-hand for caching parse results (left recursion case) +// short-hand for creating lowlevel parse cache values (left recursion case) static HParserCacheValue *cached_lr(HParseState *state, HLeftRec *lr) { HParserCacheValue *ret = a_new(HParserCacheValue, 1); @@ -181,23 +181,31 @@ HParseResult* lr_answer(HParserCacheKey *k, HParseState *state, HLeftRec *growab HParseResult* h_do_parse(const HParser* parser, HParseState *state) { HParserCacheKey *key = a_new(HParserCacheKey, 1); key->input_pos = state->input_stream; key->parser = parser; - HParserCacheValue *m = recall(key, state); + HParserCacheValue *m = NULL; + if (parser->vtable->higher) { + m = recall(key, state); + } // check to see if there is already a result for this object... if (!m) { // It doesn't exist, so create a dummy result to cache HLeftRec *base = a_new(HLeftRec, 1); - base->seed = NULL; base->rule = parser; base->head = NULL; - h_slist_push(state->lr_stack, base); - // cache it - h_hashtable_put(state->cache, key, cached_lr(state, base)); - // parse the input + // But only cache it now if there's some chance it could grow; primitive parsers can't + if (parser->vtable->higher) { + base->seed = NULL; base->rule = parser; base->head = NULL; + h_slist_push(state->lr_stack, base); + // cache it + h_hashtable_put(state->cache, key, cached_lr(state, base)); + // parse the input + } HParseResult *tmp_res = perform_lowlevel_parse(state, parser); - // the base variable has passed equality tests with the cache - h_slist_pop(state->lr_stack); - // update the cached value to our new position - HParserCacheValue *cached = h_hashtable_get(state->cache, key); - assert(cached != NULL); - cached->input_stream = state->input_stream; + if (parser->vtable->higher) { + // the base variable has passed equality tests with the cache + h_slist_pop(state->lr_stack); + // update the cached value to our new position + HParserCacheValue *cached = h_hashtable_get(state->cache, key); + assert(cached != NULL); + cached->input_stream = state->input_stream; + } // setupLR, used below, mutates the LR to have a head if appropriate, so we check to see if we have one if (NULL == base->head) { h_hashtable_put(state->cache, key, cached_result(state, tmp_res)); @@ -246,6 +254,15 @@ static bool pos_equal(const void* key1, const void* key2) { HParseResult *h_packrat_parse(HAllocator* mm__, const HParser* parser, HInputStream *input_stream) { HArena * arena = h_new_arena(mm__, 0); + + // out-of-memory handling + jmp_buf except; + h_arena_set_except(arena, &except); + if(setjmp(except)) { + h_delete_arena(arena); + return NULL; + } + HParseState *parse_state = a_new_(arena, HParseState, 1); parse_state->cache = h_hashtable_new(arena, cache_key_equal, // key_equal_func cache_key_hash); // hash_func diff --git a/src/backends/regex.c b/src/backends/regex.c index f6494fa98afea084ab347511ec0f25dc0e11379c..f7dd98add85844366cca53aa96be31c596b4221c 100644 --- a/src/backends/regex.c +++ b/src/backends/regex.c @@ -52,11 +52,21 @@ HRVMTrace *invert_trace(HRVMTrace *trace) { void* h_rvm_run__m(HAllocator *mm__, HRVMProg *prog, const uint8_t* input, size_t len) { HArena *arena = h_new_arena(mm__, 0); - HSArray *heads_n = h_sarray_new(mm__, prog->length), // Both of these contain HRVMTrace*'s - *heads_p = h_sarray_new(mm__, prog->length); + HSArray *heads_a = h_sarray_new(mm__, prog->length), // Both of these contain HRVMTrace*'s + *heads_b = h_sarray_new(mm__, prog->length); HRVMTrace *ret_trace = NULL; + HParseResult *ret = NULL; + // out of memory handling + if(!arena || !heads_a || !heads_b) + goto end; + jmp_buf except; + h_arena_set_except(arena, &except); + if(setjmp(except)) + goto end; + + HSArray *heads_n = heads_a, *heads_p = heads_b; uint8_t *insn_seen = a_new(uint8_t, prog->length); // 0 -> not seen, 1->processed, 2->queued HRVMThread *ip_queue = a_new(HRVMThread, prog->length); size_t ipq_top; @@ -164,18 +174,19 @@ void* h_rvm_run__m(HAllocator *mm__, HRVMProg *prog, const uint8_t* input, size_ } // No accept was reached. match_fail: - if (ret_trace == NULL) { - // No match found; definite failure. - h_delete_arena(arena); - return NULL; + + h_arena_set_except(arena, NULL); // there should be no more allocs from this + if (ret_trace) { + // Invert the direction of the trace linked list. + ret_trace = invert_trace(ret_trace); + ret = run_trace(mm__, prog, ret_trace, input, len); + // NB: ret is in its own arena } - // Invert the direction of the trace linked list. - - ret_trace = invert_trace(ret_trace); - HParseResult *ret = run_trace(mm__, prog, ret_trace, input, len); - // ret is in its own arena - h_delete_arena(arena); + end: + if (arena) h_delete_arena(arena); + if (heads_a) h_sarray_free(heads_a); + if (heads_b) h_sarray_free(heads_b); return ret; } #undef PUSH_SVM @@ -203,6 +214,14 @@ HParseResult *run_trace(HAllocator *mm__, HRVMProg *orig_prog, HRVMTrace *trace, ctx.stack_capacity = 16; ctx.stack = h_new(HParsedToken*, ctx.stack_capacity); + // out of memory handling + if(!arena || !ctx.stack) + goto fail; + jmp_buf except; + h_arena_set_except(arena, &except); + if(setjmp(except)) + goto fail; + HParsedToken *tmp_res; HRVMTrace *cur; for (cur = trace; cur; cur = cur->next) { @@ -242,7 +261,7 @@ HParseResult *run_trace(HAllocator *mm__, HRVMProg *orig_prog, HRVMTrace *trace, break; case SVM_ACCEPT: assert(ctx.stack_count <= 1); - HParseResult *res = a_new(HParseResult, 1); + HParseResult *res = a_new(HParseResult, 1); if (ctx.stack_count == 1) { res->ast = ctx.stack[0]; } else { @@ -250,11 +269,14 @@ HParseResult *run_trace(HAllocator *mm__, HRVMProg *orig_prog, HRVMTrace *trace, } res->bit_length = cur->input_pos * 8; res->arena = arena; + h_arena_set_except(arena, NULL); + h_free(ctx.stack); return res; } } fail: - h_delete_arena(arena); + if (arena) h_delete_arena(arena); + if (ctx.stack) h_free(ctx.stack); return NULL; } diff --git a/src/bitwriter.c b/src/bitwriter.c index 74e273448aded2ae40ec99666f230447054c0dd0..bcc21dd308a743a7dd98a6bc52364f75f2d75328 100644 --- a/src/bitwriter.c +++ b/src/bitwriter.c @@ -12,10 +12,8 @@ HBitWriter *h_bit_writer_new(HAllocator* mm__) { HBitWriter *writer = h_new(HBitWriter, 1); memset(writer, 0, sizeof(*writer)); - writer->buf = mm__->alloc(mm__, writer->capacity = 8); - if (!writer) { - return NULL; - } + writer->buf = h_alloc(mm__, writer->capacity = 8); + assert(writer != NULL); memset(writer->buf, 0, writer->capacity); writer->mm__ = mm__; writer->flags = BYTE_BIG_ENDIAN | BIT_BIG_ENDIAN; diff --git a/src/datastructures.c b/src/datastructures.c index 0feeb2176b0422471f65a053a7ef9a716af1821c..af8477be36123a53506871827d4dae13d8c9002b 100644 --- a/src/datastructures.c +++ b/src/datastructures.c @@ -25,11 +25,12 @@ HCountedArray *h_carray_new(HArena * arena) { void h_carray_append(HCountedArray *array, void* item) { if (array->used >= array->capacity) { - HParsedToken **elements = h_arena_malloc(array->arena, (array->capacity *= 2) * sizeof(HCountedArray*)); + HParsedToken **elements = h_arena_malloc(array->arena, (array->capacity *= 2) * sizeof(void*)); for (size_t i = 0; i < array->used; i++) elements[i] = array->elements[i]; for (size_t i = array->used; i < array->capacity; i++) elements[i] = 0; + h_arena_free(array->arena, array->elements); array->elements = elements; } array->elements[array->used++] = item; diff --git a/src/internal.h b/src/internal.h index b35b7d5a3a22cb19bcfbcb341078070bda2bc93b..10db4b20f429283bbf0aa99928487b2754379d91 100644 --- a/src/internal.h +++ b/src/internal.h @@ -49,7 +49,7 @@ rtype_t name##__m(HAllocator* mm__) // Functions with arguments are difficult to forward cleanly. Alas, we will need to forward them manually. -#define h_new(type, count) ((type*)(mm__->alloc(mm__, sizeof(type)*(count)))) +#define h_new(type, count) ((type*)(h_alloc(mm__, sizeof(type)*(count)))) #define h_free(addr) (mm__->free(mm__, (addr))) #ifndef __cplusplus @@ -278,9 +278,9 @@ typedef struct HRecursionHead_ { /* A left recursion. * * Members: - * seed - - * rule - - * head - + * seed - the HResult yielded by rule + * rule - the HParser that produces seed + * head - the */ typedef struct HLeftRec_ { HParseResult *seed; @@ -419,6 +419,7 @@ struct HParserVtable_ { bool (*isValidCF)(void *env); bool (*compile_to_rvm)(HRVMProg *prog, void* env); // FIXME: forgot what the bool return value was supposed to mean. void (*desugar)(HAllocator *mm__, HCFStack *stk__, void *env); + bool higher; // false if primitive }; bool h_false(void*); diff --git a/src/parsers/action.c b/src/parsers/action.c index 04eb7a4c85c71f8ea3bc60b3371f052cc43d7603..a32433348c14c4d88b304e0b298e35f06f2dbd2e 100644 --- a/src/parsers/action.c +++ b/src/parsers/action.c @@ -81,6 +81,7 @@ static const HParserVtable action_vt = { .isValidCF = action_isValidCF, .desugar = desugar_action, .compile_to_rvm = action_ctrvm, + .higher = true, }; HParser* h_action(const HParser* p, const HAction a, void* user_data) { diff --git a/src/parsers/and.c b/src/parsers/and.c index c5c9836db57cc8864f785870a613e2ceb406b28c..e07bc9fcd36c91005d01eeef8d554a8202dce9ae 100644 --- a/src/parsers/and.c +++ b/src/parsers/and.c @@ -17,6 +17,7 @@ static const HParserVtable and_vt = { revision. --mlp, 18/12/12 */ .isValidCF = h_false, /* despite TODO above, this remains false. */ .compile_to_rvm = h_not_regular, + .higher = true, }; diff --git a/src/parsers/attr_bool.c b/src/parsers/attr_bool.c index e8359ab03e06e681061dd75788d6ca6bb6e9b89b..f766774026074d49c9609891638eb33575211918 100644 --- a/src/parsers/attr_bool.c +++ b/src/parsers/attr_bool.c @@ -79,6 +79,7 @@ static const HParserVtable attr_bool_vt = { .isValidCF = ab_isValidCF, .desugar = desugar_ab, .compile_to_rvm = ab_ctrvm, + .higher = true, }; diff --git a/src/parsers/bind.c b/src/parsers/bind.c index e301e746ddbfcc1fa4b14e6873def81191207a6b..87f42330ee8a5a35ef5a026f1060e5937bdc2d16 100644 --- a/src/parsers/bind.c +++ b/src/parsers/bind.c @@ -4,7 +4,6 @@ typedef struct { const HParser *p; HContinuation k; void *env; - HAllocator *mm__; } BindEnv; // an HAllocator backed by an HArena @@ -39,13 +38,11 @@ static HParseResult *parse_bind(void *be_, HParseState *state) { if(!res) return NULL; - // create a temporary arena allocator for the continuation - HArena *arena = h_new_arena(be->mm__, 0); - ArenaAllocator aa = {{aa_alloc, aa_realloc, aa_free}, arena}; + // create a wrapper arena allocator for the continuation + ArenaAllocator aa = {{aa_alloc, aa_realloc, aa_free}, state->arena}; HParser *kx = be->k((HAllocator *)&aa, res->ast, be->env); if(!kx) { - h_delete_arena(arena); return NULL; } @@ -53,7 +50,6 @@ static HParseResult *parse_bind(void *be_, HParseState *state) { if(res2) res2->bit_length = 0; // recalculate - h_delete_arena(arena); return res2; } @@ -62,6 +58,7 @@ static const HParserVtable bind_vt = { .isValidRegular = h_false, .isValidCF = h_false, .compile_to_rvm = h_not_regular, + .higher = true, }; HParser *h_bind(const HParser *p, HContinuation k, void *env) @@ -77,7 +74,6 @@ HParser *h_bind__m(HAllocator *mm__, be->p = p; be->k = k; be->env = env; - be->mm__ = mm__; return h_new_parser(mm__, &bind_vt, be); } diff --git a/src/parsers/bits.c b/src/parsers/bits.c index 716524ce61a0a35cb0f9581646f72a0efa491c7f..be8f13f10a65f67e50d134c5f3557a1a7a209d62 100644 --- a/src/parsers/bits.c +++ b/src/parsers/bits.c @@ -102,6 +102,7 @@ static const HParserVtable bits_vt = { .isValidCF = h_true, .desugar = desugar_bits, .compile_to_rvm = bits_ctrvm, + .higher = false, }; HParser* h_bits(size_t len, bool sign) { diff --git a/src/parsers/butnot.c b/src/parsers/butnot.c index f114a1fa5dbff8cdbee6bdf22670c271c2044e2e..24ece4bec6f7f80b0905401be6e72b10f73769f8 100644 --- a/src/parsers/butnot.c +++ b/src/parsers/butnot.c @@ -40,6 +40,7 @@ static const HParserVtable butnot_vt = { .isValidRegular = h_false, .isValidCF = h_false, // XXX should this be true if both p1 and p2 are CF? .compile_to_rvm = h_not_regular, + .higher = true, }; HParser* h_butnot(const HParser* p1, const HParser* p2) { diff --git a/src/parsers/ch.c b/src/parsers/ch.c index b4386cff2be1e95158776323d50ff76b00f2afd5..3da1091a4b71505aebdc6ed5b396084d12b1fde4 100644 --- a/src/parsers/ch.c +++ b/src/parsers/ch.c @@ -47,6 +47,7 @@ static const HParserVtable ch_vt = { .isValidCF = h_true, .desugar = desugar_ch, .compile_to_rvm = ch_ctrvm, + .higher = false, }; HParser* h_ch(const uint8_t c) { diff --git a/src/parsers/charset.c b/src/parsers/charset.c index e1a910f8df149a16cb74fd7c661c5490e6d80198..a4b8c89c7daca326cf77ee9bf5c8ae4660884c56 100644 --- a/src/parsers/charset.c +++ b/src/parsers/charset.c @@ -76,6 +76,7 @@ static const HParserVtable charset_vt = { .isValidCF = h_true, .desugar = desugar_charset, .compile_to_rvm = cs_ctrvm, + .higher = false, }; HParser* h_ch_range(const uint8_t lower, const uint8_t upper) { diff --git a/src/parsers/choice.c b/src/parsers/choice.c index bfc3f904f19a6d88ddc0bd77702b2c45f89c2b0f..dd3908ce93168f468ba6e1ff531a59476e404411 100644 --- a/src/parsers/choice.c +++ b/src/parsers/choice.c @@ -75,6 +75,7 @@ static const HParserVtable choice_vt = { .isValidCF = choice_isValidCF, .desugar = desugar_choice, .compile_to_rvm = choice_ctrvm, + .higher = true, }; HParser* h_choice(HParser* p, ...) { diff --git a/src/parsers/difference.c b/src/parsers/difference.c index 76a2cc447002da5a0e04119c016f7bf83fec443e..a24f5acf378c6b801677364f7a5902ae49ec60f1 100644 --- a/src/parsers/difference.c +++ b/src/parsers/difference.c @@ -39,6 +39,7 @@ static HParserVtable difference_vt = { .isValidRegular = h_false, .isValidCF = h_false, // XXX should this be true if both p1 and p2 are CF? .compile_to_rvm = h_not_regular, + .higher = true, }; HParser* h_difference(const HParser* p1, const HParser* p2) { diff --git a/src/parsers/end.c b/src/parsers/end.c index 30b3ba121a859b87399a59dc04dc86f3a6104a88..85499d9348cd1df6503428a55d7a2ab878d1ef63 100644 --- a/src/parsers/end.c +++ b/src/parsers/end.c @@ -25,6 +25,7 @@ static const HParserVtable end_vt = { .isValidCF = h_true, .desugar = desugar_end, .compile_to_rvm = end_ctrvm, + .higher = false, }; HParser* h_end_p() { diff --git a/src/parsers/endianness.c b/src/parsers/endianness.c index e3f53ab8225a75bde08ff7e3dd456822e1234b86..cb3abc3d3d2bf84dfe465aaec7833badbec2b5f6 100644 --- a/src/parsers/endianness.c +++ b/src/parsers/endianness.c @@ -46,6 +46,7 @@ static const HParserVtable endianness_vt = { .isValidCF = h_false, .desugar = NULL, .compile_to_rvm = h_not_regular, + .higher = true, }; HParser* h_with_endianness(char endianness, const HParser *p) diff --git a/src/parsers/epsilon.c b/src/parsers/epsilon.c index e8ef525ff79d523ab45c6357cfb852a6c3b4dd96..bb6e8beb31cca3ff09a565171b4e554e07f2ffad 100644 --- a/src/parsers/epsilon.c +++ b/src/parsers/epsilon.c @@ -18,6 +18,7 @@ static const HParserVtable epsilon_vt = { .isValidCF = h_true, .desugar = desugar_epsilon, .compile_to_rvm = epsilon_ctrvm, + .higher = false, }; HParser* h_epsilon_p() { diff --git a/src/parsers/ignore.c b/src/parsers/ignore.c index af606b0ea7567cf4e5068260386d907f10e0c8a7..c56802ac0885fc11429925f353a516d622b88a9d 100644 --- a/src/parsers/ignore.c +++ b/src/parsers/ignore.c @@ -49,6 +49,7 @@ static const HParserVtable ignore_vt = { .isValidCF = ignore_isValidCF, .desugar = desugar_ignore, .compile_to_rvm = ignore_ctrvm, + .higher = true, }; HParser* h_ignore(const HParser* p) { diff --git a/src/parsers/ignoreseq.c b/src/parsers/ignoreseq.c index 1e078d84850278582dbdb0280135b9dd6bdffdcd..af74bccd08d901cc5862b105e7251d491e64f2a0 100644 --- a/src/parsers/ignoreseq.c +++ b/src/parsers/ignoreseq.c @@ -105,6 +105,7 @@ static const HParserVtable ignoreseq_vt = { .isValidCF = is_isValidCF, .desugar = desugar_ignoreseq, .compile_to_rvm = is_ctrvm, + .higher = true, }; diff --git a/src/parsers/indirect.c b/src/parsers/indirect.c index 026286d3eb3d56be961050fc1467ccae1fdc8516..b36cb947921497f89cd35687f9259052e23a343e 100644 --- a/src/parsers/indirect.c +++ b/src/parsers/indirect.c @@ -31,6 +31,7 @@ static const HParserVtable indirect_vt = { .isValidCF = indirect_isValidCF, .desugar = desugar_indirect, .compile_to_rvm = h_not_regular, + .higher = true, }; void h_bind_indirect__m(HAllocator *mm__, HParser* indirect, const HParser* inner) { diff --git a/src/parsers/int_range.c b/src/parsers/int_range.c index 2937993034c9b18a98f8aeeda7a8eaa6014bdd99..49b064218b507ca13d90dcc2ec654c8ae3a9ee19 100644 --- a/src/parsers/int_range.c +++ b/src/parsers/int_range.c @@ -117,6 +117,7 @@ static const HParserVtable int_range_vt = { .isValidCF = h_true, .desugar = desugar_int_range, .compile_to_rvm = ir_ctrvm, + .higher = false, }; HParser* h_int_range(const HParser *p, const int64_t lower, const int64_t upper) { diff --git a/src/parsers/many.c b/src/parsers/many.c index cae2b0eade03450cae13f48e8f53c37db4237721..071e3fcd2d30ed35f4622962751ebc63bea3d37c 100644 --- a/src/parsers/many.c +++ b/src/parsers/many.c @@ -10,7 +10,10 @@ typedef struct { static HParseResult *parse_many(void* env, HParseState *state) { HRepeat *env_ = (HRepeat*) env; - HCountedArray *seq = h_carray_new_sized(state->arena, (env_->count > 0 ? env_->count : 4)); + size_t size = env_->count; + if(size <= 0) size = 4; + if(size > 1024) size = 1024; // let's try parsing some elements first... + HCountedArray *seq = h_carray_new_sized(state->arena, size); size_t count = 0; HInputStream bak; while (env_->min_p || env_->count > count) { @@ -199,6 +202,7 @@ static const HParserVtable many_vt = { .isValidCF = many_isValidCF, .desugar = desugar_many, .compile_to_rvm = many_ctrvm, + .higher = true, }; HParser* h_many(const HParser* p) { diff --git a/src/parsers/not.c b/src/parsers/not.c index 6c34bad48dc09ca2a290ce351b89e921422da265..8c2003dec77b946c50db3d0f62b7117a8ff12f69 100644 --- a/src/parsers/not.c +++ b/src/parsers/not.c @@ -15,6 +15,7 @@ static const HParserVtable not_vt = { .isValidRegular = h_false, /* see and.c for why */ .isValidCF = h_false, .compile_to_rvm = h_not_regular, // Is actually regular, but the generation step is currently unable to handle it. TODO: fix this. + .higher = true, }; HParser* h_not(const HParser* p) { diff --git a/src/parsers/nothing.c b/src/parsers/nothing.c index 120c1e01d0824ab5a70e39f96c2c19657ea0bf18..0a60108bcc2c0fe69a656fb1cfb4f067ff290922 100644 --- a/src/parsers/nothing.c +++ b/src/parsers/nothing.c @@ -22,6 +22,7 @@ static const HParserVtable nothing_vt = { .isValidCF = h_true, .desugar = desugar_nothing, .compile_to_rvm = nothing_ctrvm, + .higher = false, }; HParser* h_nothing_p() { diff --git a/src/parsers/optional.c b/src/parsers/optional.c index ccee53fa864469600db64bb76562ce469559d09e..726606643056b103f9481cb882dadc19417dd607 100644 --- a/src/parsers/optional.c +++ b/src/parsers/optional.c @@ -84,6 +84,7 @@ static const HParserVtable optional_vt = { .isValidCF = opt_isValidCF, .desugar = desugar_optional, .compile_to_rvm = opt_ctrvm, + .higher = true, }; HParser* h_optional(const HParser* p) { diff --git a/src/parsers/permutation.c b/src/parsers/permutation.c index 564565af555a0059a8a85773a86f2ae9a320df0f..b16758413eeafe2ce2ae91db2ebbe7593681d3cd 100644 --- a/src/parsers/permutation.c +++ b/src/parsers/permutation.c @@ -104,6 +104,7 @@ static const HParserVtable permutation_vt = { .isValidCF = h_false, .desugar = NULL, .compile_to_rvm = h_not_regular, + .higher = true, }; HParser* h_permutation(HParser* p, ...) { diff --git a/src/parsers/sequence.c b/src/parsers/sequence.c index 93c0cfb983200a33b7909fd1b2c73114711beac5..55c0c8885573ef7779714efd49eaf64cc59ac878 100644 --- a/src/parsers/sequence.c +++ b/src/parsers/sequence.c @@ -93,6 +93,7 @@ static const HParserVtable sequence_vt = { .isValidCF = sequence_isValidCF, .desugar = desugar_sequence, .compile_to_rvm = sequence_ctrvm, + .higher = true, }; HParser* h_sequence(HParser* p, ...) { @@ -116,26 +117,33 @@ HParser* h_sequence__v(HParser* p, va_list ap) { } HParser* h_sequence__mv(HAllocator* mm__, HParser *p, va_list ap_) { - va_list ap; - size_t len = 0; - const HParser *arg; - va_copy(ap, ap_); - do { - len++; - arg = va_arg(ap, HParser *); - } while (arg); - va_end(ap); HSequence *s = h_new(HSequence, 1); - s->p_array = h_new(HParser *, len); - - va_copy(ap, ap_); - s->p_array[0] = p; - for (size_t i = 1; i < len; i++) { - s->p_array[i] = va_arg(ap, HParser *); - } while (arg); - va_end(ap); + s->len = 0; + + if(p) { + // non-empty sequence + const HParser *arg; + size_t len = 0; + va_list ap; + + va_copy(ap, ap_); + do { + len++; + arg = va_arg(ap, HParser *); + } while (arg); + va_end(ap); + s->p_array = h_new(HParser *, len); + + va_copy(ap, ap_); + s->p_array[0] = p; + for (size_t i = 1; i < len; i++) { + s->p_array[i] = va_arg(ap, HParser *); + } while (arg); + va_end(ap); + + s->len = len; + } - s->len = len; return h_new_parser(mm__, &sequence_vt, s); } diff --git a/src/parsers/token.c b/src/parsers/token.c index d36ec54be4c07a35b729da71455c5bc3b3555cbc..19029726ad11a52fa0eadf62b67a7b15cd2e4744 100644 --- a/src/parsers/token.c +++ b/src/parsers/token.c @@ -73,6 +73,7 @@ const HParserVtable token_vt = { .isValidCF = h_true, .desugar = desugar_token, .compile_to_rvm = token_ctrvm, + .higher = false, }; HParser* h_token(const uint8_t *str, const size_t len) { diff --git a/src/parsers/unimplemented.c b/src/parsers/unimplemented.c index e3f3039407eacaa1d24689767a4a1038fce66a93..e085858bcf45f4219f111be3ba1328868a4aad4d 100644 --- a/src/parsers/unimplemented.c +++ b/src/parsers/unimplemented.c @@ -18,6 +18,7 @@ static const HParserVtable unimplemented_vt = { .isValidCF = h_false, .desugar = NULL, .compile_to_rvm = h_not_regular, + .higher = true, }; static HParser unimplemented = { diff --git a/src/parsers/value.c b/src/parsers/value.c index 531db7cb5274c30d3d482ee5bc84add58c1e9af7..7fa863a15b4abacdec1f5463f72c335121584c72 100644 --- a/src/parsers/value.c +++ b/src/parsers/value.c @@ -26,6 +26,7 @@ static const HParserVtable put_vt = { .isValidRegular = h_false, .isValidCF = h_false, .compile_to_rvm = h_not_regular, + .higher = true, }; HParser* h_put_value(const HParser* p, const char* name) { @@ -55,6 +56,7 @@ static const HParserVtable get_vt = { .isValidRegular = h_false, .isValidCF = h_false, .compile_to_rvm = h_not_regular, + .higher = true, }; HParser* h_get_value(const char* name) { diff --git a/src/parsers/whitespace.c b/src/parsers/whitespace.c index 04284e86e61d242c58a1c42689607ecfd3794dfe..970a32c8b57209a66f3588bddb4ea30de9f87454 100644 --- a/src/parsers/whitespace.c +++ b/src/parsers/whitespace.c @@ -75,6 +75,7 @@ static const HParserVtable whitespace_vt = { .isValidCF = ws_isValidCF, .desugar = desugar_whitespace, .compile_to_rvm = ws_ctrvm, + .higher = false, }; HParser* h_whitespace(const HParser* p) { diff --git a/src/parsers/xor.c b/src/parsers/xor.c index e031d5d542f80d345324c746e63d255e3b308655..3a3f21d27a928bf6d2d180eeb39763c918275fd0 100644 --- a/src/parsers/xor.c +++ b/src/parsers/xor.c @@ -36,6 +36,7 @@ static const HParserVtable xor_vt = { .isValidRegular = h_false, .isValidCF = h_false, // XXX should this be true if both p1 and p2 are CF? .compile_to_rvm = h_not_regular, + .higher = true, }; HParser* h_xor(const HParser* p1, const HParser* p2) { diff --git a/src/pprint.c b/src/pprint.c index 11ec3d67411df66043ddd7880edeb22cb5d7db51..9c7c6522c0a201cf544ffaea3227510fec4bf827 100644 --- a/src/pprint.c +++ b/src/pprint.c @@ -186,13 +186,11 @@ static void unamb_sub(const HParsedToken* tok, struct result_buf *buf) { char* h_write_result_unamb(const HParsedToken* tok) { struct result_buf buf = { - .output = (&system_allocator)->alloc(&system_allocator, 16), + .output = h_alloc(&system_allocator, 16), .len = 0, .capacity = 16 }; - if (!buf.output) { - return NULL; - } + assert(buf.output != NULL); unamb_sub(tok, &buf); append_buf_c(&buf, 0); return buf.output; diff --git a/src/registry.c b/src/registry.c index 2ebac1a94ad37a501c7c10a50c3eb6a45fc74dd0..d905320bbdf6131e5ab449c525086e4b1205a28b 100644 --- a/src/registry.c +++ b/src/registry.c @@ -46,10 +46,8 @@ static int compare_entries(const void* v1, const void* v2) { } HTokenType h_allocate_token_type(const char* name) { - Entry* new_entry = (&system_allocator)->alloc(&system_allocator, sizeof(*new_entry)); - if (!new_entry) { - return TT_INVALID; - } + Entry* new_entry = h_alloc(&system_allocator, sizeof(*new_entry)); + assert(new_entry != NULL); new_entry->name = name; new_entry->value = 0; Entry* probe = *(Entry**)tsearch(new_entry, &tt_registry, compare_entries); diff --git a/src/t_misc.c b/src/t_misc.c index 92c2b326471d48a82fcae8f110d1febad58c6fe3..04ce96c1f6153de2b196be2dbacda55f8e6d9e23 100644 --- a/src/t_misc.c +++ b/src/t_misc.c @@ -1,5 +1,6 @@ #include <glib.h> #include <string.h> +#include <sys/resource.h> #include "test_suite.h" #include "hammer.h" @@ -29,7 +30,54 @@ static void test_tt_registry(void) { g_check_cmp_int32(h_get_token_type_number("com.upstandinghackers.test.unkown_token_type"), ==, 0); } +// test out-of-memory handling with a selectively failing allocator +static void *fail_alloc(HAllocator *mm__, size_t size) { + if(size - 0xdead <= 0x30) // allow for overhead of arena link structure + return NULL; + return system_allocator.alloc(&system_allocator, size); +} +static void *fail_realloc(HAllocator *mm__, void *ptr, size_t size) { + return system_allocator.realloc(&system_allocator, ptr, size); +} +static void fail_free(HAllocator *mm__, void *ptr) { + return system_allocator.free(&system_allocator, ptr); +} +static HAllocator fail_allocator = {fail_alloc, fail_realloc, fail_free}; +static HParsedToken *act_oom(const HParseResult *r, void *user) { + void *buf = h_arena_malloc(r->arena, 0xdead); + assert(buf != NULL); + return NULL; // succeed with null token +} +static void test_oom(void) { + HParser *p = h_action(h_ch('x'), act_oom, NULL); + // this should always fail, but never crash + + // sanity-check: parses should succeed with the normal allocator... + g_check_parse_ok(p, PB_PACKRAT, "x",1); + g_check_parse_ok(p, PB_REGULAR, "x",1); + g_check_parse_ok(p, PB_LLk, "x",1); + g_check_parse_ok(p, PB_LALR, "x",1); + g_check_parse_ok(p, PB_GLR, "x",1); + //XXX g_check_parse_chunks_ok(p, PB_REGULAR, "",0, "x",1); + g_check_parse_chunks_ok(p, PB_LLk, "",0, "x",1); + g_check_parse_chunks_ok(p, PB_LALR, "",0, "x",1); + //XXX g_check_parse_chunks_ok(p, PB_GLR, "",0, "x",1); + + // ...and fail gracefully with the broken one + HAllocator *mm__ = &fail_allocator; + g_check_parse_failed__m(mm__, p, PB_PACKRAT, "x",1); + g_check_parse_failed__m(mm__, p, PB_REGULAR, "x",1); + g_check_parse_failed__m(mm__, p, PB_LLk, "x",1); + g_check_parse_failed__m(mm__, p, PB_LALR, "x",1); + g_check_parse_failed__m(mm__, p, PB_GLR, "x",1); + //XXX g_check_parse_chunks_failed__m(mm__, p, PB_REGULAR, "",0, "x",1); + g_check_parse_chunks_failed__m(mm__, p, PB_LLk, "",0, "x",1); + g_check_parse_chunks_failed__m(mm__, p, PB_LALR, "",0, "x",1); + //XXX g_check_parse_chunks_failed__m(mm__, p, PB_GLR, "",0, "x",1); +} + void register_misc_tests(void) { g_test_add_func("/core/misc/tt_user", test_tt_user); g_test_add_func("/core/misc/tt_registry", test_tt_registry); + g_test_add_func("/core/misc/oom", test_oom); } diff --git a/src/t_parser.c b/src/t_parser.c index c42eca91321c241a1987b99116c8c90deefbdf64..331d2629018b40717bf49309ba0b561ce7a618a3 100644 --- a/src/t_parser.c +++ b/src/t_parser.c @@ -241,6 +241,7 @@ static void test_nothing_p(gconstpointer backend) { static void test_sequence(gconstpointer backend) { const HParser *sequence_1 = h_sequence(h_ch('a'), h_ch('b'), NULL); const HParser *sequence_2 = h_sequence(h_ch('a'), h_whitespace(h_ch('b')), NULL); + const HParser *sequence_3 = h_sequence(NULL, NULL); // second NULL is to silence GCC g_check_parse_match(sequence_1, (HParserBackend)GPOINTER_TO_INT(backend), "ab", 2, "(u0x61 u0x62)"); g_check_parse_failed(sequence_1, (HParserBackend)GPOINTER_TO_INT(backend), "a", 1); @@ -248,6 +249,7 @@ static void test_sequence(gconstpointer backend) { g_check_parse_match(sequence_2, (HParserBackend)GPOINTER_TO_INT(backend), "ab", 2, "(u0x61 u0x62)"); g_check_parse_match(sequence_2, (HParserBackend)GPOINTER_TO_INT(backend), "a b", 3, "(u0x61 u0x62)"); g_check_parse_match(sequence_2, (HParserBackend)GPOINTER_TO_INT(backend), "a b", 4, "(u0x61 u0x62)"); + g_check_parse_match(sequence_3, (HParserBackend)GPOINTER_TO_INT(backend), "", 0, "()"); } static void test_choice(gconstpointer backend) { diff --git a/src/test_suite.h b/src/test_suite.h index 49f13cf81c50864eb8ae03ed705f582a7dd1ca0f..83359f9ec0a623f0c9f7ae5fa69175b94d6a98a8 100644 --- a/src/test_suite.h +++ b/src/test_suite.h @@ -78,29 +78,24 @@ } while(0) -// TODO: replace uses of this with g_check_parse_failed -#define g_check_failed(res) do { \ - const HParseResult *result = (res); \ - if (NULL != result) { \ - g_test_message("Check failed: shouldn't have succeeded, but did"); \ - g_test_fail(); \ - } \ - } while(0) - -#define g_check_parse_failed(parser, backend, input, inp_len) do { \ - int skip = h_compile((HParser *)(parser), (HParserBackend)backend, NULL); \ +#define g_check_parse_failed__m(mm__, parser, backend, input, inp_len) do { \ + int skip = h_compile__m(mm__, (HParser *)(parser), (HParserBackend)backend, NULL); \ if(skip != 0) { \ g_test_message("Compile failed"); \ g_test_fail(); \ break; \ } \ - const HParseResult *result = h_parse(parser, (const uint8_t*)input, inp_len); \ + HParseResult *result = h_parse__m(mm__, parser, (const uint8_t*)input, inp_len); \ if (NULL != result) { \ + h_parse_result_free(result); \ g_test_message("Check failed: shouldn't have succeeded, but did"); \ g_test_fail(); \ } \ } while(0) +#define g_check_parse_failed(p, be, input, len) \ + g_check_parse_failed__m(&system_allocator, p, be, input, len) + #define g_check_parse_ok(parser, backend, input, inp_len) do { \ int skip = h_compile((HParser *)(parser), (HParserBackend) backend, NULL); \ if(skip) { \ @@ -119,7 +114,7 @@ "Inefficiency: %5f%%", \ stats.used, stats.wasted, \ stats.wasted * 100. / (stats.used+stats.wasted)); \ - h_delete_arena(res->arena); \ + h_parse_result_free(res); \ } \ } while(0) @@ -144,22 +139,22 @@ "Inefficiency: %5f%%", \ stats.used, stats.wasted, \ stats.wasted * 100. / (stats.used+stats.wasted)); \ - h_delete_arena(res->arena); \ + h_parse_result_free(res); \ } \ } while(0) -#define g_check_parse_chunks_failed(parser, backend, chunk1, c1_len, chunk2, c2_len) do { \ - int skip = h_compile((HParser *)(parser), (HParserBackend)backend, NULL); \ +#define g_check_parse_chunks_failed__m(mm__, parser, backend, chunk1, c1_len, chunk2, c2_len) do { \ + int skip = h_compile__m(mm__, (HParser *)(parser), (HParserBackend)backend, NULL); \ if(skip) { \ g_test_message("Compile failed"); \ g_test_fail(); \ break; \ } \ - g_check_parse_chunks_failed_(parser, chunk1, c1_len, chunk2, c2_len); \ + g_check_parse_chunks_failed___m(mm__, parser, chunk1, c1_len, chunk2, c2_len); \ } while(0) -#define g_check_parse_chunks_failed_(parser, chunk1, c1_len, chunk2, c2_len) do { \ - HSuspendedParser *s = h_parse_start(parser); \ +#define g_check_parse_chunks_failed___m(mm__, parser, chunk1, c1_len, chunk2, c2_len) do { \ + HSuspendedParser *s = h_parse_start__m(mm__, parser); \ if(!s) { \ g_test_message("Chunk-wise parsing not available"); \ g_test_fail(); \ @@ -167,13 +162,54 @@ } \ h_parse_chunk(s, (const uint8_t*)chunk1, c1_len); \ h_parse_chunk(s, (const uint8_t*)chunk2, c2_len); \ - const HParseResult *res = h_parse_finish(s); \ + HParseResult *res = h_parse_finish(s); \ if (NULL != res) { \ + h_parse_result_free(res); \ g_test_message("Check failed: shouldn't have succeeded, but did"); \ g_test_fail(); \ } \ } while(0) +#define g_check_parse_chunks_failed(p, be, c1, c1_len, c2, c2_len) \ + g_check_parse_chunks_failed__m(&system_allocator, p, be, c1, c1_len, c2, c2_len) + +#define g_check_parse_chunks_failed_(p, c1, c1_len, c2, c2_len) \ + g_check_parse_chunks_failed___m(&system_allocator, p, c1, c1_len, c2, c2_len) + +#define g_check_parse_chunks_ok(parser, backend, chunk1, c1_len, chunk2, c2_len) do { \ + int skip = h_compile((HParser *)(parser), (HParserBackend)backend, NULL); \ + if(skip) { \ + g_test_message("Compile failed"); \ + g_test_fail(); \ + break; \ + } \ + g_check_parse_chunks_ok_(parser, chunk1, c1_len, chunk2, c2_len); \ + } while(0) + +#define g_check_parse_chunks_ok_(parser, chunk1, c1_len, chunk2, c2_len) do { \ + HSuspendedParser *s = h_parse_start(parser); \ + if(!s) { \ + g_test_message("Chunk-wise parsing not available"); \ + g_test_fail(); \ + break; \ + } \ + h_parse_chunk(s, (const uint8_t*)chunk1, c1_len); \ + h_parse_chunk(s, (const uint8_t*)chunk2, c2_len); \ + HParseResult *res = h_parse_finish(s); \ + if (!res) { \ + g_test_message("Parse failed on line %d", __LINE__); \ + g_test_fail(); \ + } else { \ + HArenaStats stats; \ + h_allocator_stats(res->arena, &stats); \ + g_test_message("Parse used %zd bytes, wasted %zd bytes. " \ + "Inefficiency: %5f%%", \ + stats.used, stats.wasted, \ + stats.wasted * 100. / (stats.used+stats.wasted)); \ + h_parse_result_free(res); \ + } \ + } while(0) + #define g_check_parse_chunks_match(parser, backend, chunk1, c1_len, chunk2, c2_len, result) do { \ int skip = h_compile((HParser *)(parser), (HParserBackend) backend, NULL); \ if(skip) { \ @@ -207,7 +243,7 @@ "Inefficiency: %5f%%", \ stats.used, stats.wasted, \ stats.wasted * 100. / (stats.used+stats.wasted)); \ - h_delete_arena(res->arena); \ + h_parse_result_free(res); \ } \ } while(0)