From 8946c27306abed7065afad3f015df5ee81e72ad2 Mon Sep 17 00:00:00 2001 From: Kimplul Date: Fri, 1 May 2026 20:35:00 +0300 Subject: add support for coverage --- .gitignore | 6 ++ .gitmodules | 3 + deps/covsrv | 1 + include/fwd/ast.h | 8 +++ include/fwd/coverage.h | 21 ++++++ include/fwd/tracker.h | 7 ++ scripts/coverage | 34 ++++++++++ scripts/makefile | 8 +-- src/analyze.c | 43 +++++++++--- src/ast.c | 124 +++++++++++++--------------------- src/compiler.c | 57 +++++++++------- src/debug.c | 14 +++- src/lexer.l | 17 ++++- src/main.c | 5 ++ src/parser.y | 174 +++++++++++++++++++++++++++++++++++------------- src/path.c | 32 +++++++-- src/rewrite.c | 14 +++- src/scope.c | 8 +-- src/tracker.c | 22 ++++++ tests/Makefile | 2 + tests/scripts/gen-xfail | 8 +-- tests/scripts/gen-xpass | 5 +- tests/scripts/makefile | 22 ++++-- tests/scripts/run-xfail | 58 ++++++++++++++++ tests/scripts/run-xpass | 53 +++++++++++++++ 25 files changed, 549 insertions(+), 197 deletions(-) create mode 160000 deps/covsrv create mode 100644 include/fwd/coverage.h create mode 100644 include/fwd/tracker.h create mode 100755 scripts/coverage create mode 100644 src/tracker.c create mode 100755 tests/scripts/run-xfail create mode 100755 tests/scripts/run-xpass diff --git a/.gitignore b/.gitignore index bd4f9aa..a6392b2 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,10 @@ gen/*.c gen/*.inc !include/fwd reports +coverage +!scripts/coverage tests.mk +vgcore.* +*.gcda +*.gcno +*.d diff --git a/.gitmodules b/.gitmodules index bf47852..ffde00b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "deps/conts"] path = deps/conts url = https://metanimi.dy.fi/git/conts +[submodule "deps/covsrv"] + path = deps/covsrv + url = https://metanimi.dy.fi/cgit/covsrv diff --git a/deps/covsrv b/deps/covsrv new file mode 160000 index 0000000..5ff642c --- /dev/null +++ b/deps/covsrv @@ -0,0 +1 @@ +Subproject commit 5ff642c98760e0aec4de6b334a187cb9b4484aa7 diff --git a/include/fwd/ast.h b/include/fwd/ast.h index 8af444c..9b474c9 100644 --- a/include/fwd/ast.h +++ b/include/fwd/ast.h @@ -9,6 +9,8 @@ #include #include +#include + /** * @file ast.h * @@ -665,6 +667,12 @@ struct ast *clone_ast_list(struct ast *l); struct type *clone_type(struct type *type); struct type *clone_type_list(struct type *types); +#define clone_astc(...) cover_ptr(clone_ast, __VA_ARGS__) +#define clone_ast_listc(...) cover_ptr(clone_ast_list, __VA_ARGS__) + +#define clone_typec(...) cover_ptr(clone_type, __VA_ARGS__) +#define clone_type_listc(...) cover_ptr(clone_type_list, __VA_ARGS__) + void ast_dump_list(int depth, struct ast *root); void ast_dump(int depth, struct ast *node); void ast_append(struct ast **list, struct ast *elem); diff --git a/include/fwd/coverage.h b/include/fwd/coverage.h new file mode 100644 index 0000000..3ec0b64 --- /dev/null +++ b/include/fwd/coverage.h @@ -0,0 +1,21 @@ +#ifndef FWD_COVERAGE_H +#define FWD_COVERAGE_H + +#include + +#ifdef COVERAGE +#include +# define cover_ptr(name, ...) ({covsrv_die() ? NULL : name (__VA_ARGS__);}) +#else +# define cover_ptr(name, ...) name (__VA_ARGS__) +#endif + +#define mallocc(...) cover_ptr(malloc, __VA_ARGS__) +#define callocc(...) cover_ptr(calloc, __VA_ARGS__) +#define reallocc(...) cover_ptr(realloc, __VA_ARGS__) + +#define strdupc(...) cover_ptr(strdup, __VA_ARGS__) +#define strndupc(...) cover_ptr(strndup, __VA_ARGS__) + + +#endif /* FWD_COVERAGE_H */ diff --git a/include/fwd/tracker.h b/include/fwd/tracker.h new file mode 100644 index 0000000..94c657e --- /dev/null +++ b/include/fwd/tracker.h @@ -0,0 +1,7 @@ +#ifndef FWD_TRACKER_H +#define FWD_TRACKER_H + +void *track_ptr(void *p); +void free_tracked_ptrs(); + +#endif /* FWD_TRACKER_H */ diff --git a/scripts/coverage b/scripts/coverage new file mode 100755 index 0000000..0648355 --- /dev/null +++ b/scripts/coverage @@ -0,0 +1,34 @@ +#!/bin/sh +set -eu + +# build covsrv binary +make -C deps/covsrv + +# not super fantastic but most likely good enough +export COVSRV_SOCKET=$(mktemp -u) + +# server program should always be killed at the end of a test run +cleanup() { + ./deps/covsrv/build/covsrv quit +} + +# kill server program even if user interrupted us or something else exceptional +# happened +trap interrupt INT HUP TERM QUIT EXIT +interrupt () { + cleanup + exit 1 +} + +# start coverage server, should create a unix socket at COVSRV_SOCKET that test +# programs can connect to +./deps/covsrv/build/covsrv & + +# run tests, pass any flags like -j to make +make COVERAGEFLAGS="--coverage -DCOVERAGE=1" check "$@" + +mkdir -p coverage +lcov --capture --directory . --out coverage/covsrv.info +genhtml --ignore-errors range coverage/covsrv.info --out coverage + +exit 0 diff --git a/scripts/makefile b/scripts/makefile index ae42506..08a7a88 100644 --- a/scripts/makefile +++ b/scripts/makefile @@ -48,9 +48,9 @@ OBFLAGS := -g -rdynamic WARNFLAGS := -Wall -Wextra COMPILE_FLAGS := $(CFLAGS) $(WARNFLAGS) $(OPTFLAGS) $(OBFLAGS) $(ASSERTFLAGS) \ - $(DEBUGFLAGS) + $(DEBUGFLAGS) $(COVERAGEFLAGS) -INCLUDE_FLAGS := -I include -I deps/conts/include +INCLUDE_FLAGS := -I include -I deps/conts/include -I deps/covsrv/include COMPILE = $(COMPILER) \ $(COMPILE_FLAGS) $(DEPFLAGS) $(INCLUDE_FLAGS) @@ -62,8 +62,8 @@ COMPILE_FWD = $(COMPILE) $(FWD_FLAGS) -include deps.mk -fwd: $(FWD_OBJS) build/gen/parser.o - $(COMPILE_FWD) $(FWD_OBJS) build/gen/parser.o -o $@ +fwd: $(FWD_OBJS) deps/covsrv/src/client.c build/gen/parser.o + $(COMPILE_FWD) $(FWD_OBJS) deps/covsrv/src/client.c build/gen/parser.o -o $@ # might lint some common things twice diff --git a/src/analyze.c b/src/analyze.c index fd1aabb..df00b2f 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -2,6 +2,7 @@ /* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ #include +#include #include #include #include @@ -65,9 +66,12 @@ static int analyze_proc(struct state *state, struct scope *scope, group++; /* start of new group */ /* otherwise same or ending group, don't increment */ + struct type *ct = clone_type(param->t); + if (!ct) + return -1; param->t->group = group; - type_append(&proc_type->t0, clone_type(param->t)); + type_append(&proc_type->t0, ct); } node->t = proc_type; @@ -432,7 +436,11 @@ static int analyze_closure(struct state *state, struct scope *scope, } foreach_node(binding, closure_bindings(node)) { - type_append(&callable->t0, clone_type(binding->t)); + struct type *ct = clone_type(binding->t); + if (!ct) + return -1; + + type_append(&callable->t0, ct); } node->t = callable; @@ -608,7 +616,8 @@ static int analyze_construct(struct state *state, struct scope *scope, * a memory allocation? */ } - node->t = tgen_id(strdup(construct_id(node)), node->loc); + + node->t = tgen_id(construct_id(node), node->loc); return 0; } @@ -797,7 +806,11 @@ static int analyze_nil_check(struct state *state, struct scope *scope, struct ast *var = nil_check_ref(node); struct type *base = tptr_base(expr->t); - struct type *ref = tgen_ref(clone_type(base), node->loc); + struct type *ct = clone_type(base); + if (!ct) + return -1; + + struct type *ref = tgen_ref(ct, node->loc); if (!var_type(var)) var_type(var) = ref; @@ -1190,8 +1203,13 @@ int fwd_register(struct fwd_state *state, const char *name, fwd_extern_t func, /* eight bytes is likely more than enough, since first three are * var, then we have four bytes for indexes and one trailing * NULL */ - char *name = calloc(8, sizeof(char)); - assert(name); + char *name = callocc(8, sizeof(char)); + if (!name) { + internal_error("failed allocating ext var name"); + return -1; + } + + track_ptr(name); sprintf(name, "%s%zu", "var", idx); struct ast *new = gen_var(name, fwd_type_kind(type), @@ -1208,8 +1226,7 @@ int fwd_register(struct fwd_state *state, const char *name, fwd_extern_t func, /* append 'return' as a continuation */ if (rtype != FWD_VOID) { - char *name = strdup("ret"); - struct ast *new = gen_var(name, + struct ast *new = gen_var("ret", tgen_closure( fwd_type_kind(rtype), NULL_LOC() @@ -1224,7 +1241,15 @@ int fwd_register(struct fwd_state *state, const char *name, fwd_extern_t func, vars = reverse_ast_list(vars); - struct ast *def = gen_proc(strdup(name), vars, + char *cname = strdupc(name); + if (!cname) { + internal_error("failed allocating ext name copy"); + return -1; + } + + track_ptr(cname); + + struct ast *def = gen_proc(cname, vars, fwd_type_kind(rtype), NULL, NULL_LOC() ); diff --git a/src/ast.c b/src/ast.c index 4573d3e..bb8fb2b 100644 --- a/src/ast.c +++ b/src/ast.c @@ -16,6 +16,7 @@ #include #include +#include #define VEC_NAME ast_vec #define VEC_TYPE struct ast * @@ -25,77 +26,29 @@ #define VEC_TYPE struct type * #include -static struct ast_vec nodes; -static struct type_vec types; - -static void destroy_ast_node(struct ast *n) -{ - if (!n) - return; - - if (n->s) - free(n->s); - - free(n); -} - -static void destroy_type(struct type *n) -{ - if (!n) - return; - - if (n->id) - free(n->id); - - free(n); -} - -static void destroy_ast_nodes() -{ - foreach(ast_vec, n, &nodes) { - destroy_ast_node(*n); - } - - ast_vec_destroy(&nodes); -} - -static void destroy_types() -{ - foreach(type_vec, t, &types) { - destroy_type(*t); - } - - type_vec_destroy(&types); -} - -void destroy_allocs() -{ - destroy_ast_nodes(); - destroy_types(); -} - static struct ast *create_empty_ast() { - if (ast_vec_uninit(&nodes)) - nodes = ast_vec_create(1); - - struct ast *n = calloc(1, sizeof(struct ast)); - if (!n) + struct ast *n = callocc(1, sizeof(struct ast)); + if (!n) { + internal_error("failed allocating empty ast node"); return NULL; + } /* just to be safe */ n->k = AST_EMPTY; - ast_vec_append(&nodes, n); + track_ptr(n); return n; } static struct type *create_empty_type() { - if (type_vec_uninit(&types)) - types = type_vec_create(1); + struct type *n = callocc(1, sizeof(struct type)); + if (!n) { + internal_error("failed allocating empty type node"); + return NULL; + } - struct type *n = calloc(1, sizeof(struct type)); - type_vec_append(&types, n); + track_ptr(n); return n; } @@ -326,23 +279,34 @@ struct ast *clone_ast(struct ast *n) new->v = n->v; new->d = n->d; - if (n->s) - new->s = strdup(n->s); + /* names are handled by pointer tracker, so we can just copy a reference + * to the string directly */ + new->s = n->s; - if (n->a0) - new->a0 = clone_ast_list(n->a0); + if (n->a0 && !(new->a0 = clone_ast_listc(n->a0))) { + internal_error("failed cloning ast a0"); + return NULL; + } - if (n->a1) - new->a1 = clone_ast_list(n->a1); + if (n->a1 && !(new->a1 = clone_ast_listc(n->a1))) { + internal_error("failed cloning ast a1"); + return NULL; + } - if (n->a2) - new->a2 = clone_ast_list(n->a2); + if (n->a2 && !(new->a2 = clone_ast_listc(n->a2))) { + internal_error("failed cloning ast a2"); + return NULL; + } - if (n->a3) - new->a3 = clone_ast_list(n->a3); + if (n->a3 && !(new->a3 = clone_ast_listc(n->a3))) { + internal_error("failed cloning ast a3"); + return NULL; + } - if (n->t2) - new->t2 = clone_type_list(n->t2); + if (n->t2 && !(new->t2 = clone_type_listc(n->t2))) { + internal_error("failed cloning ast t2"); + return NULL; + } /** @todo rebuild opt group? */ @@ -353,7 +317,9 @@ struct ast *clone_ast_list(struct ast *root) { struct ast *n = root, *new_root = NULL, *prev = NULL; while (n) { - struct ast *new = clone_ast(n); + struct ast *new = clone_astc(n); + if (!new) + return NULL; if (prev) prev->n = new; else new_root = new; @@ -375,11 +341,13 @@ struct type *clone_type(struct type *type) return NULL; new->k = type->k; - if (type->id) - new->id = strdup(type->id); + /* names are tracked by the tracker, so no need to duplicate */ + new->id = type->id; - if (type->t0) - new->t0 = clone_type_list(type->t0); + if (type->t0 && !(new->t0 = clone_type_listc(type->t0))) { + internal_error("failed cloning type t0"); + return NULL; + } new->or = type->or; new->group = type->group; @@ -391,7 +359,9 @@ struct type *clone_type_list(struct type *root) { struct type *n = root, *new_root = NULL, *prev = NULL; while (n) { - struct type *new = clone_type(n); + struct type *new = clone_typec(n); + if (!new) + return NULL; if (prev) prev->n = new; else new_root = new; diff --git a/src/compiler.c b/src/compiler.c index f367f9a..f72ab1c 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -16,7 +16,9 @@ #include #include +#include #include +#include #include #include #include @@ -46,13 +48,16 @@ static char *read_file(const char *file, FILE *f) fseek(f, 0, SEEK_SET); - char *buf = malloc((size_t)(s + 1)); - if (!buf) + char *buf = mallocc((size_t)(s + 1)); + if (!buf) { + internal_error("failed allocating fread buffer"); return NULL; + } fread(buf, (size_t)(s + 1), 1, f); /* remember terminating null */ buf[s] = 0; + track_ptr(buf); return buf; } @@ -81,10 +86,8 @@ static int process(struct scope *scope, const char *file) return -1; struct parser *p = create_parser(); - if (!p) { - free((void *)buf); + if (!p) return -1; - } parse(p, file, buf); @@ -92,15 +95,21 @@ static int process(struct scope *scope, const char *file) bool failed = p->failed; destroy_parser(p); - if (failed) { - free((void *)buf); + if (failed) return -1; - } ast_dump_list(0, tree); + char *fname = strdupc(file); + if (!fname) { + internal_error("failed allocating fname"); + return -1; + } + + track_ptr(fname); + scope->fctx.fbuf = buf; - scope->fctx.fname = strdup(file); + scope->fctx.fname = fname; if (analyze_root(scope, tree)) return -1; @@ -124,7 +133,6 @@ static void destroy_scopes() { foreach(scopes, n, &scopes) { destroy_scope(n->data); - free((void *)n->key); } scopes_destroy(&scopes); @@ -136,57 +144,56 @@ struct scope *compile_file(const char *file) const char *base = NULL, *dir = NULL, *cwd = NULL, *real = NULL; if (!(base = fwd_basename(file))) { error("couldn't get basename of %s", file); - goto out; + return NULL; } if (!(dir = fwd_dirname(file))) { error("couldn't get dirname of %s", file); - goto out; + return NULL; } if (!(cwd = fwd_cwdname())) { error("couldn't get current working dir"); - goto out; + return NULL; } if (*dir != 0 && chdir(dir)) { error("couldn't change to directory %s: %s", dir, strerror(errno) ); - goto out; + return NULL; } if (!(real = realpath(base, NULL))) { error("no such file: %s", file); - goto out; + return NULL; } + /* const cast, ech */ + track_ptr((void *)real); + struct scope **exists = scopes_find(&scopes, real); if (exists) { scope = *exists; - goto out; + return NULL; } scope = create_scope(); - scopes_insert(&scopes, strdup(real), scope); + scopes_insert(&scopes, real, scope); if (process(scope, base)) { scope = NULL; - goto out; + return NULL; } if (chdir(cwd)) { error("couldn't change back to directory %s: %s", cwd, strerror(errno) ); - goto out; + + return NULL; } -out: - free((void *)base); - free((void *)dir); - free((void *)cwd); - free((void *)real); return scope; } @@ -207,6 +214,6 @@ int compile(const char *input) { out: destroy_scopes(); - destroy_allocs(); + free_tracked_ptrs(); return ret; } diff --git a/src/debug.c b/src/debug.c index 2426277..d229ae6 100644 --- a/src/debug.c +++ b/src/debug.c @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -272,8 +273,15 @@ static void _type_str(FILE *f, struct type *type) char *type_str(struct type *t) { - if (!t) - return strdup("NULL"); + if (!t) { + char *s = strdupc("NULL"); + if (!s) { + internal_error("failed allocating null string"); + return NULL; + } + + return s; + } char *buf = NULL; size_t size = 0; FILE *memstream = open_memstream(&buf, &size); @@ -283,6 +291,8 @@ char *type_str(struct type *t) _type_str(memstream, t); fclose(memstream); + + track_ptr(buf); return buf; } diff --git a/src/lexer.l b/src/lexer.l index 4f6598a..1f4c520 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -4,6 +4,7 @@ %option reentrant noyywrap nounput noinput nodefault %{ #define FROM_LEXER +#include #include #include @@ -140,7 +141,13 @@ STRING \"(\\.|[^"\\])*\" "sizeof" {return SIZEOF;} {STRING} { - yylval->str = strdup(yytext); + yylval->str = strdupc(yytext); + if (!yylval->str) { + internal_error("failed allocating lexed string"); + return ERROR; + } + + track_ptr(yylval->str); return STRING; } @@ -150,7 +157,13 @@ STRING \"(\\.|[^"\\])*\" } {ID} { - yylval->str = strdup(yytext); + yylval->str = strdupc(yytext); + if (!yylval->str) { + internal_error("failed allocating lexed ID"); + return ERROR; + } + + track_ptr(yylval->str); return ID; } diff --git a/src/main.c b/src/main.c index 204a920..c5937dc 100644 --- a/src/main.c +++ b/src/main.c @@ -45,6 +45,11 @@ static void usage() */ int main(int argc, char *argv[]) { +#ifdef COVERAGE + assert(!covsrv_init()); + atexit(covsrv_destroy); +#endif + int opt; while ((opt = getopt(argc, argv, "hI:")) != -1) { switch (opt) { diff --git a/src/parser.y b/src/parser.y index 776e1c9..bd5b033 100644 --- a/src/parser.y +++ b/src/parser.y @@ -191,12 +191,17 @@ static char match_escape(char c); */ static char *strip(const char *s); +#define Q(x) if (!(x)) { YYABORT; } + %} %start input; %% param - : type ID { $$ = gen_var($2, $1, src_loc(@$)); } + : type ID { + $$ = gen_var($2, $1, src_loc(@$)); + Q($$); + } rev_params : rev_params "," param { $$ = $3; $$->n = $1; } @@ -214,7 +219,10 @@ opt_params var : param - | ID { $$ = gen_var($1, NULL, src_loc(@$)); } + | ID { + $$ = gen_var($1, NULL, src_loc(@$)); + Q($$); + } rev_vars : rev_vars "," var { $$ = $3; $$->n = $1; } @@ -231,52 +239,60 @@ opt_vars proc : ID "(" opt_params ")" body { $$ = gen_proc($[ID], $[opt_params], NULL, $[body], src_loc(@$)); + Q($$); } proc_decl : ID "(" opt_params ")" ";" { $$ = gen_proc($[ID], $[opt_params], NULL, NULL, src_loc(@$)); + Q($$); } binop - : expr "+" expr { $$ = gen_binop(AST_ADD, $1, $3, src_loc(@$)); } - | expr "-" expr { $$ = gen_binop(AST_SUB, $1, $3, src_loc(@$)); } - | expr "*" expr { $$ = gen_binop(AST_MUL, $1, $3, src_loc(@$)); } - | expr "/" expr { $$ = gen_binop(AST_DIV, $1, $3, src_loc(@$)); } - | expr "%" expr { $$ = gen_binop(AST_REM, $1, $3, src_loc(@$)); } - | expr "<" expr { $$ = gen_comparison(AST_LT, $1, $3, src_loc(@$)); } - | expr ">" expr { $$ = gen_comparison(AST_GT, $1, $3, src_loc(@$)); } - | expr "<<" expr { $$ = gen_binop(AST_LSHIFT, $1, $3, src_loc(@$)); } - | expr ">>" expr { $$ = gen_binop(AST_RSHIFT, $1, $3, src_loc(@$)); } - | expr "<=" expr { $$ = gen_comparison(AST_LE, $1, $3, src_loc(@$)); } - | expr ">=" expr { $$ = gen_comparison(AST_GE, $1, $3, src_loc(@$)); } - | expr "!=" expr { $$ = gen_comparison(AST_NE, $1, $3, src_loc(@$)); } - | expr "==" expr { $$ = gen_comparison(AST_EQ, $1, $3, src_loc(@$)); } - | id "~" id { $$ = gen_paste($1, $3, src_loc(@$)); } + : expr "+" expr { $$ = gen_binop(AST_ADD, $1, $3, src_loc(@$)); Q($$); } + | expr "-" expr { $$ = gen_binop(AST_SUB, $1, $3, src_loc(@$)); Q($$); } + | expr "*" expr { $$ = gen_binop(AST_MUL, $1, $3, src_loc(@$)); Q($$); } + | expr "/" expr { $$ = gen_binop(AST_DIV, $1, $3, src_loc(@$)); Q($$); } + | expr "%" expr { $$ = gen_binop(AST_REM, $1, $3, src_loc(@$)); Q($$); } + | expr "<" expr { $$ = gen_comparison(AST_LT, $1, $3, src_loc(@$)); Q($$); } + | expr ">" expr { $$ = gen_comparison(AST_GT, $1, $3, src_loc(@$)); Q($$); } + | expr "<<" expr { $$ = gen_binop(AST_LSHIFT, $1, $3, src_loc(@$)); Q($$); } + | expr ">>" expr { $$ = gen_binop(AST_RSHIFT, $1, $3, src_loc(@$)); Q($$); } + | expr "<=" expr { $$ = gen_comparison(AST_LE, $1, $3, src_loc(@$)); Q($$); } + | expr ">=" expr { $$ = gen_comparison(AST_GE, $1, $3, src_loc(@$)); Q($$); } + | expr "!=" expr { $$ = gen_comparison(AST_NE, $1, $3, src_loc(@$)); Q($$); } + | expr "==" expr { $$ = gen_comparison(AST_EQ, $1, $3, src_loc(@$)); Q($$); } + | id "~" id { $$ = gen_paste($1, $3, src_loc(@$)); Q($$); } unop - : "-" expr { $$ = gen_unop(AST_NEG, $2, src_loc(@$)); } - | "!" expr { $$ = gen_unop(AST_LNOT, $2, src_loc(@$)); } - | "~" expr { $$ = gen_unop(AST_BNEG, $2, src_loc(@$)); } + : "-" expr { $$ = gen_unop(AST_NEG, $2, src_loc(@$)); Q($$); } + | "!" expr { $$ = gen_unop(AST_LNOT, $2, src_loc(@$)); Q($$); } + | "~" expr { $$ = gen_unop(AST_BNEG, $2, src_loc(@$)); Q($$); } type - : ID { $$ = tgen_id($1, src_loc(@$)); } - | "(" opt_types ")" { $$ = tgen_closure($2, src_loc(@$)); } + : ID { + $$ = tgen_id($1, src_loc(@$)); + Q($$); + } + | "(" opt_types ")" { $$ = tgen_closure($2, src_loc(@$)); Q($$); } | "&&" type { if ($2->k == TYPE_CLOSURE) { $$ = $2; $$->k = TYPE_PURE_CLOSURE; } else { $$ = tgen_ref($2, src_loc(@$)); + Q($$); } /* heh */ $$ = tgen_ref($$, src_loc(@$)); + Q($$); } | "&" type { if ($2->k == TYPE_CLOSURE) { $$ = $2; $$->k = TYPE_PURE_CLOSURE; } else { $$ = tgen_ref($2, src_loc(@$)); + Q($$); } } | "*" type { @@ -284,6 +300,7 @@ type $$ = $2; $$->k = TYPE_FUNC_PTR; } else { $$ = tgen_ptr($2, src_loc(@$)); + Q($$); } } @@ -302,6 +319,7 @@ opt_types construction : expr "=>" ID { $$ = gen_construction($3, $1, src_loc(@$)); + Q($$); } constructions @@ -315,6 +333,7 @@ opt_constructions deconstruction : ID "=>" var { $$ = gen_deconstruction($1, $3, src_loc(@$)); + Q($$); } deconstructions @@ -328,23 +347,33 @@ opt_deconstructions construct : "[" opt_constructions "]" ID { $$ = gen_construct($4, $2, src_loc(@$)); + Q($$); } id - : ID { $$ = gen_id($1, src_loc(@$)); } + : ID { + $$ = gen_id($1, src_loc(@$)); + Q($$); + } expr - : "sizeof" "(" type ")" { $$ = gen_sizeof($3, src_loc(@$)); } - | STRING { $$ = gen_const_str(strip($1), src_loc(@$)); } - | FLOAT { $$ = gen_const_float($1, src_loc(@$)); } - | BOOL { $$ = gen_const_bool($1, src_loc(@$)); } - | CHAR { $$ = gen_const_char($1, src_loc(@$)); } - | INT { $$ = gen_const_int($1, src_loc(@$)); } - | "*" { $$ = gen_nil(src_loc(@$)); } + : "sizeof" "(" type ")" { $$ = gen_sizeof($3, src_loc(@$)); Q($$); } + | STRING { + char *s = strip($1); + Q(s); + + $$ = gen_const_str(s, src_loc(@$)); + Q($$); + } + | FLOAT { $$ = gen_const_float($1, src_loc(@$)); Q($$); } + | BOOL { $$ = gen_const_bool($1, src_loc(@$)); Q($$); } + | CHAR { $$ = gen_const_char($1, src_loc(@$)); Q($$); } + | INT { $$ = gen_const_int($1, src_loc(@$)); Q($$); } + | "*" { $$ = gen_nil(src_loc(@$)); Q($$); } | "(" expr ")" { $$ = $2; } - | expr "&" { $$ = gen_ref($1, src_loc(@$)); } - | expr "*" { $$ = gen_deref($1, src_loc(@$)); } - | expr "as" type { $$ = gen_as($1, $3, src_loc(@$)); } + | expr "&" { $$ = gen_ref($1, src_loc(@$)); Q($$); } + | expr "*" { $$ = gen_deref($1, src_loc(@$)); Q($$); } + | expr "as" type { $$ = gen_as($1, $3, src_loc(@$)); Q($$); } | construct | id | binop @@ -364,9 +393,11 @@ opt_exprs closure : "=>" opt_vars body { $$ = gen_closure($[opt_vars], $[body], src_loc(@$)); + Q($$); } | "&" "=>" opt_vars body { $$ = gen_closure($[opt_vars], $[body], src_loc(@$)); + Q($$); ast_set_flags($$, AST_FLAG_NOMOVES); } @@ -375,9 +406,13 @@ rev_closures | closure trailing_closure - : "=>" opt_vars ";" { $$ = gen_closure($[opt_vars], NULL, src_loc(@$));} + : "=>" opt_vars ";" { + $$ = gen_closure($[opt_vars], NULL, src_loc(@$)); + Q($$); + } | "&" "=>" opt_vars ";" { $$ = gen_closure($[opt_vars], NULL, src_loc(@$)); + Q($$); ast_set_flags($$, AST_FLAG_NOMOVES); } @@ -398,46 +433,66 @@ closures call : expr "(" opt_exprs ")" { $$ = gen_call($1, $[opt_exprs], src_loc(@$)); + Q($$); } | expr "(" opt_exprs ")" "=>" opt_vars ";" { /* the rest of the function body is our closure, gets fixed up * later */ struct ast *closure = gen_closure($[opt_vars], NULL, src_loc(@$)); + Q(closure); + ast_append(&$[opt_exprs], closure); $$ = gen_call($[expr], $[opt_exprs], src_loc(@$)); + Q($$); } | expr "(" opt_exprs ")" closures { ast_append(&$[opt_exprs], $[closures]); $$ = gen_call($[expr], $[opt_exprs], src_loc(@$)); + Q($$); } put - : put "*" { $$ = gen_put($1, src_loc(@$)); } - | id "*" { $$ = gen_put($1, src_loc(@$)); } + : put "*" { $$ = gen_put($1, src_loc(@$)); Q($$); } + | id "*" { $$ = gen_put($1, src_loc(@$)); Q($$); } let - : expr "=>" var ";" { $$ = gen_let($3, $1, src_loc(@$)); } - | expr "=>" put ";" { $$ = gen_write($3, $1, src_loc(@$)); } + : expr "=>" var ";" { + $$ = gen_let($3, $1, src_loc(@$)); + Q($$); + } + | expr "=>" put ";" { + $$ = gen_write($3, $1, src_loc(@$)); + Q($$); + } explode : expr "=>" "[" opt_deconstructions "]" { $$ = gen_explode($4, $1, src_loc(@$)); + Q($$); } if - : "if" expr body "else" body { $$ = gen_if($2, $3, $5, src_loc(@$)); } - | "if" expr body "else" if { $$ = gen_if($2, $3, $5, src_loc(@$)); } + : "if" expr body "else" body { + $$ = gen_if($2, $3, $5, src_loc(@$)); + Q($$); + } + | "if" expr body "else" if { + $$ = gen_if($2, $3, $5, src_loc(@$)); + Q($$); + } nil : "nil" expr body "=>" var ";" { /** @todo should nil check define new scope for created * variable? */ $$ = gen_nil_check($2, $3, $5, src_loc(@$)); + Q($$); } forget : "nil" ID ";" { $$ = gen_forget($2, src_loc(@$)); + Q($$); } statement @@ -448,7 +503,7 @@ statement | let | nil | if - | ";" { $$ = gen_empty(src_loc(@$)); } + | ";" { $$ = gen_empty(src_loc(@$)); Q($$); } rev_statements : rev_statements statement { $$ = $2; $2->n = $1; } @@ -467,6 +522,7 @@ opt_statements body : "{" opt_statements "}" { $$ = gen_block($2, src_loc(@$)); + Q($$); } behaviour @@ -483,7 +539,10 @@ opt_behaviours | { $$ = NULL; } impl - : ID "!" { $$ = gen_implements($1, src_loc(@$)); } + : ID "!" { + $$ = gen_implements($1, src_loc(@$)); + Q($$); + } member : param @@ -499,7 +558,10 @@ opt_members type_param : ID ID { struct type *t = tgen_id($1, src_loc(@1)); + Q(t); + $$ = gen_var($2, t, src_loc(@$)); + Q($$); } type_params @@ -532,38 +594,50 @@ opt_elements template : ID "[" opt_type_params "]" "(" opt_vars ")" "{" opt_elements "}" { $$ = gen_template($1, $3, $6, $9, src_loc(@$)); + Q($$); } supertemplate : ID "[" opt_type_params "]" "(" opt_vars ")" "=" id "[" opt_types "]" "(" opt_exprs ")" "{" opt_elements "}" { struct ast *pseudoinst = gen_instance(NULL, $9, $11, $14, src_loc(@$)); + Q(pseudoinst); + $$ = gen_supertemplate($1, $3, $6, pseudoinst, $17, src_loc(@$)); + Q($$); } struct : ID "{" opt_members "}" { $$ = gen_struct($1, $3, src_loc(@$)); + Q($$); } struct_cont : "continue" ID "{" opt_behaviours "}" { $$ = gen_instance_cont($2, $4, src_loc(@$)); + Q($$); } trait : ID "=" "{" opt_behaviours "}" { $$ = gen_trait($1, $4, src_loc(@$)); + Q($$); } instance : ID "=" id "[" opt_types "]" "(" opt_exprs ")" { $$ = gen_instance($1, $3, $5, $8, src_loc(@$)); + Q($$); } import : "import" STRING { - $$ = gen_import(strip($2), src_loc(@$)); + char *s = strip($2); + Q(s); + + $$ = gen_import(s, src_loc(@$)); + Q($$); } top @@ -585,6 +659,8 @@ top | "pub" supertemplate { $$ = $2; ast_set_flags($$, AST_FLAG_PUBLIC); } | error { $$ = gen_empty(src_loc(@$)); + Q($$); + parser->failed = true; /* ignore any content inside a top level thing and just move onto * the next one */ @@ -679,11 +755,11 @@ static char match_escape(char c) static char *strip(const char *str) { const size_t len = strlen(str) + 1; - char *buf = malloc(len); + char *buf = mallocc(len); if (!buf) { /* should probably try to handle the error in some way... */ internal_error("failed allocating buffer for string clone"); - free((void *)str); + /* str should be in tracker already, so no need to free it */ return NULL; } @@ -693,14 +769,20 @@ static char *strip(const char *str) buf[j++] = str[i]; buf[j] = 0; - free((void *)str); + track_ptr(buf); return buf; } struct parser *create_parser() { - return calloc(1, sizeof(struct parser)); + struct parser *p = callocc(1, sizeof(struct parser)); + if (!p) { + internal_error("failed allocating parser"); + return NULL; + } + + return p; } void destroy_parser(struct parser *p) diff --git a/src/path.c b/src/path.c index 87cd26b..5502a7b 100644 --- a/src/path.c +++ b/src/path.c @@ -15,6 +15,8 @@ #include #include +#include +#include char *fwd_basename(const char *file) { @@ -25,10 +27,19 @@ char *fwd_basename(const char *file) break; } + char *s = NULL; if (n == 0) - return strdup(file); + s = strdupc(file); + else + s = strndupc(file + n + 1, l - n); + + if (!s) { + internal_error("failed allocating basename"); + return NULL; + } - return strndup(file + n + 1, l - n); + track_ptr(s); + return s; } char *fwd_dirname(const char *file) @@ -40,7 +51,14 @@ char *fwd_dirname(const char *file) break; } - return strndup(file, n); + char *s = strndupc(file, n); + if (!s) { + internal_error("failed allocating dirname"); + return NULL; + } + + track_ptr(s); + return s; } char *fwd_cwdname() @@ -52,13 +70,15 @@ char *fwd_cwdname() else size = (size_t)path_max; - char *buf = malloc(size); - if (!buf) + char *buf = mallocc(size); + if (!buf) { + internal_error("failed allocating cwd buf"); return NULL; + } + track_ptr(buf); if (!getcwd(buf, size)) { error("%s\n", strerror(errno)); - free(buf); return NULL; } diff --git a/src/rewrite.c b/src/rewrite.c index c5a497a..6409686 100644 --- a/src/rewrite.c +++ b/src/rewrite.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -11,7 +12,12 @@ struct type_helper { static int rewrite_type_ids(struct type *type, char *orig, char *new) { if (type->k == TYPE_ID && strcmp(type->id, orig) == 0) { - char *r = strdup(new); + char *r = strdupc(new); + if (!r) { + internal_error("failed allocating hole"); + return -1; + } + free(type->id); type->id = r; } @@ -49,7 +55,11 @@ static char *rewrite_hole(char *old, char *new) size_t nn = strlen(new); /* +1 for null terminator */ - char *r = malloc(on + nn + 1); + char *r = mallocc(on + nn + 1); + if (!r) { + internal_error("failed allocating hole rewrite buf"); + return NULL; + } memcpy(r, new, nn); diff --git a/src/scope.c b/src/scope.c index 251edef..f350090 100644 --- a/src/scope.c +++ b/src/scope.c @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -22,7 +23,7 @@ struct scope *create_scope() /* if I ever try making the parser multithreaded, this should be atomic. */ static size_t counter = 0; - struct scope *scope = calloc(1, sizeof(struct scope)); + struct scope *scope = callocc(1, sizeof(struct scope)); if (!scope) { internal_error("ran out of memory allocating scope"); return NULL; @@ -42,11 +43,6 @@ void destroy_scope(struct scope *scope) if (!scope) return; - if (!scope->parent) { - free((void *)scope->fctx.fbuf); - free((void *)scope->fctx.fname); - } - foreach(mod_vec, m, &scope->mods) { dlclose(*m); } diff --git a/src/tracker.c b/src/tracker.c new file mode 100644 index 0000000..2bf56d4 --- /dev/null +++ b/src/tracker.c @@ -0,0 +1,22 @@ +#include + +#define VEC_NAME ptr_tracker +#define VEC_TYPE void * +#include + +static struct ptr_tracker ptrs; + +void *track_ptr(void *p) +{ + ptr_tracker_append(&ptrs, p); + return p; +} + +void free_tracked_ptrs() +{ + foreach(ptr_tracker, ptr, &ptrs) { + free(*ptr); + } + + ptr_tracker_destroy(&ptrs); +} diff --git a/tests/Makefile b/tests/Makefile index ce009fc..81cc205 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,3 +1,5 @@ +export VALGRIND ?= 1 + check: $(MAKE) -f scripts/makefile check diff --git a/tests/scripts/gen-xfail b/tests/scripts/gen-xfail index c4cf382..a04f8d9 100755 --- a/tests/scripts/gen-xfail +++ b/tests/scripts/gen-xfail @@ -9,13 +9,7 @@ do EMSG=${s#${NAME},} echo ".PHONY: $NAME" >> tests.mk echo "$NAME:" >> tests.mk - echo " ../fwd $NAME/$NAME.fwd \\" >> tests.mk - echo " > reports/$NAME/gen.c 2> reports/$NAME/log \\" >> tests.mk - echo " && echo 'Wrong retval' > reports/$NAME/OK \\" >> tests.mk - echo " || :" >> tests.mk - echo " grep '$EMSG' reports/$NAME/log > /dev/null \\" >> tests.mk - echo " && echo OK > reports/$NAME/OK \\" >> tests.mk - echo " || echo EMSG > reports/$NAME/OK" >> tests.mk + echo " ./scripts/run-xfail $NAME '$EMSG'" >> tests.mk done echo -n "TESTS +=" >> tests.mk diff --git a/tests/scripts/gen-xpass b/tests/scripts/gen-xpass index 762650e..8ec383f 100755 --- a/tests/scripts/gen-xpass +++ b/tests/scripts/gen-xpass @@ -6,10 +6,7 @@ for s in "${@}" do echo ".PHONY: $s" >> tests.mk echo "$s:" >> tests.mk - echo " ../fwd $s/$s.fwd > reports/$s/gen.c 2> reports/$s/log" >> tests.mk - echo " \$(COMPILE_TEST) reports/$s/gen.c -o reports/$s/$s \\" >> tests.mk - echo " \$(TEST_LIBS)" >> tests.mk - echo " ./reports/$s/$s > reports/$s/OK 2>&1" >> tests.mk + echo " ./scripts/run-xpass $s" >> tests.mk done echo "TESTS += " "${@}" >> tests.mk diff --git a/tests/scripts/makefile b/tests/scripts/makefile index 21b1418..e6ea394 100644 --- a/tests/scripts/makefile +++ b/tests/scripts/makefile @@ -4,16 +4,24 @@ all: check COMPILE_TEST := $(CC) -L../mod -I../include -I../lib -Wl,-rpath=../mod -O2 TEST_LIBS := -lfwdio -lfwdmem -lfwdutil +export COMPILE_TEST +export TEST_LIBS + TESTS := include tests.mk .PHONY: check check: $(TESTS) - @cd reports; for d in * ; do \ - if [ ! -f "$$d/OK" ]; then \ - echo "BROKEN: $$d" ; \ - elif [ "$$(tail -n1 $$d/OK)" != "OK" ]; then \ - echo "FAIL: $$d" ; \ - fi \ - done + @cd reports; OK=1; for d in * ; do \ + if [ ! -f "$$d/OK" ]; then \ + echo "BROKEN: $$d" ; \ + elif [ "$$(tail -n1 $$d/OK)" != "OK" ]; then \ + OK=0 ; \ + echo "FAIL: $$d" ; \ + fi \ + done; \ + if [ "$$OK" != 1 ]; then \ + echo "There were failing tests"; \ + exit 1; \ + fi @echo "Done." diff --git a/tests/scripts/run-xfail b/tests/scripts/run-xfail new file mode 100755 index 0000000..35bcc75 --- /dev/null +++ b/tests/scripts/run-xfail @@ -0,0 +1,58 @@ +#!/bin/sh +NAME="$1" +EMSG="$2" + +try_vg() { + if [ "x$VALGRIND" != "x0" ] && which valgrind > /dev/null >&1; then + eval "valgrind -q --leak-check=full --error-exitcode=169 $1" + else + eval "$1" + fi + + RETVAL=$? + return "$RETVAL" +} + +I=0 +while :; do + if try_vg "../fwd $NAME/$NAME.fwd" \ + 1> "reports/$NAME/gen.c" \ + 2> "reports/$NAME/run-$I" + then + # success was not expected + echo "SUCC" > reports/$NAME/OK + exit 0 + fi + + if [ "$RETVAL" = 169 ]; then + echo "VALGRIND" > reports/$NAME/OK + + # don't return on an error so that makefile continues running + # other tests that are maybe failing, and reports the status + # after all tests have been run + exit 0 + fi + + # correctly handled coverage exception, continue to next run + # here we check for the special 'internal error' which should be + # reported if the error was handled gracefully, otherwise since the + # error message is not the one we expected and not a covsrv error, we + # don't know what it is and could end up in an infinite loop if we don't + # stop here. + if grep 'COVSRV: EXIT' "reports/$NAME/gen.c" > /dev/null \ + && grep 'internal error:' "reports/$NAME/run-$I" > /dev/null; then + I=$((I+1)) + continue + fi + + if grep "$EMSG" "reports/$NAME/run-$I" > /dev/null; then + # we exited with the expected error message and without leaking + # memory, presume covsrv is happy as well + echo "OK" > reports/$NAME/OK + exit 0 + fi + + # incorrectly handled coverage exception, stop run + echo "MEMERR" > reports/$NAME/OK + exit 0 +done diff --git a/tests/scripts/run-xpass b/tests/scripts/run-xpass new file mode 100755 index 0000000..93540c9 --- /dev/null +++ b/tests/scripts/run-xpass @@ -0,0 +1,53 @@ +#!/bin/sh +NAME="$1" + +if [ -z "${COMPILE_TEST+x}" -o -z "${TEST_LIBS+x}" ]; then + echo "Test runner script must have COMPILE_TEST and TEST_LIBS defined." + exit 1 +fi + +try_vg() { + if [ "x$VALGRIND" != "x0" ] && which valgrind > /dev/null >&1; then + eval "valgrind -q --leak-check=full --error-exitcode=169 $1" + else + eval "$1" + fi + + RETVAL=$? + return "$RETVAL" +} + +I=0 +while :; do + if try_vg "../fwd $NAME/$NAME.fwd" \ + 1> "reports/$NAME/gen.c" \ + 2> "reports/$NAME/run-$I" + then + # fwd succeeded, compile and run program to check result + # remove possible COVSRV lines in output + sed -i "s/COVSRV:.*$//" "reports/$NAME/gen.c" + eval "$COMPILE_TEST reports/$NAME/gen.c -o reports/$NAME/$NAME $TEST_LIBS" + ./reports/$NAME/$NAME > reports/$NAME/OK 2>&1 + exit 0 + fi + + if [ "$RETVAL" = 169 ]; then + echo "VALGRIND" > reports/$NAME/OK + + # don't return on an error so that makefile continues running + # other tests that are maybe failing, and reports the status + # after all tests have been run + exit 0 + fi + + # correctly handled coverage exception, continue to next run + if grep 'COVSRV: EXIT' "reports/$NAME/gen.c" > /dev/null \ + && grep 'internal error:' "reports/$NAME/run-$I" > /dev/null; then + I=$((I+1)) + continue + fi + + # incorrectly handled coverage exception, stop run + echo "MEMERR" > reports/$NAME/OK + exit 0 +done -- cgit v1.2.3