aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--.gitmodules3
m---------deps/covsrv0
-rw-r--r--include/fwd/ast.h8
-rw-r--r--include/fwd/coverage.h21
-rw-r--r--include/fwd/tracker.h7
-rwxr-xr-xscripts/coverage34
-rw-r--r--scripts/makefile8
-rw-r--r--src/analyze.c43
-rw-r--r--src/ast.c124
-rw-r--r--src/compiler.c57
-rw-r--r--src/debug.c14
-rw-r--r--src/lexer.l17
-rw-r--r--src/main.c5
-rw-r--r--src/parser.y174
-rw-r--r--src/path.c32
-rw-r--r--src/rewrite.c14
-rw-r--r--src/scope.c8
-rw-r--r--src/tracker.c22
-rw-r--r--tests/Makefile2
-rwxr-xr-xtests/scripts/gen-xfail8
-rwxr-xr-xtests/scripts/gen-xpass5
-rw-r--r--tests/scripts/makefile22
-rwxr-xr-xtests/scripts/run-xfail58
-rwxr-xr-xtests/scripts/run-xpass53
25 files changed, 548 insertions, 197 deletions
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
+Subproject 5ff642c98760e0aec4de6b334a187cb9b4484aa
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 <assert.h>
#include <stdbool.h>
+#include <fwd/coverage.h>
+
/**
* @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 <stdlib.h>
+
+#ifdef COVERAGE
+#include <covsrv/covsrv.h>
+# 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 <fwd/compiler.h>
+#include <fwd/tracker.h>
#include <fwd/analyze.h>
#include <fwd/rewrite.h>
#include <fwd/mod.h>
@@ -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 <fwd/ast.h>
#include <fwd/scope.h>
+#include <fwd/tracker.h>
#define VEC_NAME ast_vec
#define VEC_TYPE struct ast *
@@ -25,77 +26,29 @@
#define VEC_TYPE struct type *
#include <conts/vec.h>
-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 <errno.h>
#include <fwd/compiler.h>
+#include <fwd/coverage.h>
#include <fwd/analyze.h>
+#include <fwd/tracker.h>
#include <fwd/parser.h>
#include <fwd/debug.h>
#include <fwd/lower.h>
@@ -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 <string.h>
#include <stdarg.h>
+#include <fwd/tracker.h>
#include <fwd/debug.h>
#include <fwd/scope.h>
@@ -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 <fwd/tracker.h>
#include <fwd/parser.h>
#include <fwd/debug.h>
@@ -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 <fwd/path.h>
#include <fwd/debug.h>
+#include <fwd/tracker.h>
+#include <fwd/coverage.h>
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 <fwd/rewrite.h>
+#include <fwd/debug.h>
#include <stddef.h>
#include <stdlib.h>
@@ -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 <stdio.h>
#include <dlfcn.h>
+#include <fwd/coverage.h>
#include <fwd/debug.h>
#include <fwd/scope.h>
@@ -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 <fwd/tracker.h>
+
+#define VEC_NAME ptr_tracker
+#define VEC_TYPE void *
+#include <conts/vec.h>
+
+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