From: Nick Bowler Date: Thu, 29 Sep 2011 01:57:23 +0000 (-0400) Subject: Start implementing proper error handling. X-Git-Tag: v1~91 X-Git-Url: https://git.draconx.ca/gitweb/cdecl99.git/commitdiff_plain/3df85155e2dbf9307dd64bd222bf74389c45a75c Start implementing proper error handling. Since the library shouldn't be printing its own error messages, we need a mechanism to propagate error details to the caller. Implement a system using thread-local storage which tracks the most recent errors. --- diff --git a/Makefile.am b/Makefile.am index de6c066..f387ee3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -29,7 +29,8 @@ EXTRA_DIST = m4/gnulib-cache.m4 src/types.lst src/typenames.sed \ dist_man_MANS = doc/man/cdecl99.1 doc/man/libcdecl.3 include_HEADERS = src/cdecl.h -noinst_HEADERS = src/typemap.h src/output.h src/scan.h src/parse.h src/i18n.h +noinst_HEADERS = src/typemap.h src/output.h src/scan.h src/parse.h src/i18n.h \ + src/error.h noinst_DATA = $(MOFILES) @@ -37,7 +38,7 @@ lib_LTLIBRARIES = libcdecl.la libcdecl_la_LDFLAGS = -no-undefined \ -export-symbols-regex '^cdecl_[[:lower:]]' libcdecl_la_SOURCES = src/scan.c src/parse.c src/parse-decl.c src/typemap.c \ - src/output.c src/explain.c src/declare.c src/i18n.c + src/output.c src/explain.c src/declare.c src/i18n.c src/error.c libcdecl_la_LIBADD = libgnu.la $(LTLIBINTL) $(LTLIBTHREAD) $(libcdecl_la_OBJECTS): $(gnulib_headers) diff --git a/doc/man/libcdecl.3 b/doc/man/libcdecl.3 index d92dcbf..489d7a9 100644 --- a/doc/man/libcdecl.3 +++ b/doc/man/libcdecl.3 @@ -14,6 +14,8 @@ .Fd size_t cdecl_explain(char *buf, size_t n, struct cdecl *decl); .Fd size_t cdecl_declare(char *buf, size_t n, struct cdecl *decl); .Pp +.Fd const struct cdecl_error *cdecl_get_error(void); +.Pp .Fd int cdecl_spec_kind(struct cdecl_declspec *spec); .Sh DESCRIPTION .Nm @@ -244,6 +246,44 @@ each function parameter has exactly one full declarator (abstract or otherwise). If .Va variadic is true, then the function is variadic. +.Sh ERROR HANDLING +Some functions in +.Nm +can fail. Such functions will be documented as indicating an error condition +in a particular way. It is sometimes necessary to know more about a particular +error in order to print an informative error message or perform some other +action. To facilitate this, +.Nm +provides a structure which describes a particular error. +.Bd -literal -offset indent +struct cdecl_error { + unsigned code; + const char *str; +}; +.Ed +.Pp +The +.Va code +member identifies the sort of error which has occurred, while the +.Va str +member points to a string containing a human-readable description of the error. +This error information can be retrieved by calling the function +.Pp +.Fd const struct cdecl_error *cdecl_get_error(void); +.Pp +which returns a pointer to the error structure most recently generated in the +current thread. It is therefore thread-safe in that errors occurring in +another thread will not interfere with the current one. The returned pointer +shall remain valid until the next call to any function from +.Nm +by the same thread, except that multiple consecutive calls to +.Va cdecl_get_error +shall all return the same value. The same applies to the +.Va str +pointer inside the error structure itself. +.Pp +If this function is called before an error has been indicated by an earlier +call in the same thread, the behaviour is undefined. .Sh PARSING DECLARATIONS To parse a declaration, the function .Pp diff --git a/m4/.gitignore b/m4/.gitignore index 14287ab..f93decd 100644 --- a/m4/.gitignore +++ b/m4/.gitignore @@ -49,6 +49,7 @@ stdio_h.m4 stdlib_h.m4 thread.m4 threadlib.m4 +tls.m4 uintmax_t.m4 unistd_h.m4 visibility.m4 diff --git a/m4/gnulib-cache.m4 b/m4/gnulib-cache.m4 index 09fc0cd..3ecf1ef 100644 --- a/m4/gnulib-cache.m4 +++ b/m4/gnulib-cache.m4 @@ -15,7 +15,7 @@ # Specification in the form of a command-line invocation: -# gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=. --makefile-name=gnulib.mk.in --conditional-dependencies --libtool --macro-prefix=gl --no-vc-files getopt-gnu gettext lock readline +# gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=. --makefile-name=gnulib.mk.in --conditional-dependencies --libtool --macro-prefix=gl --no-vc-files getopt-gnu gettext lock readline tls # Specification in the form of a few gnulib-tool.m4 macro invocations: gl_LOCAL_DIR([]) @@ -24,6 +24,7 @@ gl_MODULES([ gettext lock readline + tls ]) gl_AVOID([]) gl_SOURCE_BASE([lib]) diff --git a/src/cdecl.h b/src/cdecl.h index c4dad87..a8e9946 100644 --- a/src/cdecl.h +++ b/src/cdecl.h @@ -107,4 +107,17 @@ static inline int cdecl_spec_kind(struct cdecl_declspec *spec) return spec->type & ~0xffu; } +/* Error handling. */ +enum { + CDECL_ENOMEM, + CDECL_ENOPARSE, +}; + +struct cdecl_error { + unsigned code; + const char *str; +}; + +const struct cdecl_error *cdecl_get_error(void); + #endif diff --git a/src/cdecl99.c b/src/cdecl99.c index 04de1a6..c5f913b 100644 --- a/src/cdecl99.c +++ b/src/cdecl99.c @@ -97,13 +97,17 @@ retry: static int cmd_explain(const char *cmd, const char *arg) { + const struct cdecl_error *err; struct cdecl *decl; const char *str; int ret = -1; decl = cdecl_parse_decl(arg); - if (!decl) + if (!decl) { + err = cdecl_get_error(); + fprintf(stderr, "%s\n", err->str); goto out; + } for (struct cdecl *i = decl; i; i = i->next) { str = do_format(cdecl_explain, i); @@ -121,13 +125,17 @@ out: static int cmd_simplify(const char *cmd, const char *arg) { + const struct cdecl_error *err; struct cdecl *decl; const char *str; int ret = -1; decl = cdecl_parse_decl(arg); - if (!decl) + if (!decl) { + err = cdecl_get_error(); + fprintf(stderr, "%s\n", err->str); goto out; + } for (struct cdecl *i = decl; i; i = i->next) { struct cdecl_declspec *s = i->specifiers; @@ -156,14 +164,18 @@ out: static int cmd_declare(const char *cmd, const char *arg) { + const struct cdecl_error *err; struct cdecl *decl; const char *str; int ret = -1; /* The name of the command is significant here. */ decl = cdecl_parse_english(cmd); - if (!decl) + if (!decl) { + err = cdecl_get_error(); + fprintf(stderr, "%s\n", err->str); goto out; + } /* * English parses have at most one full declarator, so no loop is diff --git a/src/error.c b/src/error.c new file mode 100644 index 0000000..e18ec80 --- /dev/null +++ b/src/error.c @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include +#include +#include "cdecl.h" +#include "error.h" +#include "i18n.h" + +gl_once_define(static, tls_initialized); +static gl_tls_key_t tls_key; + +struct err_state { + struct cdecl_error err; + size_t nstr; + char str[]; +}; + +/* This error is reserved for extremely dire out-of-memory conditions. */ +static struct err_state err_no_mem = { + .err = { + .code = CDECL_ENOMEM, + .str = "failed to allocate memory", + }, +}; + +static void free_err(void *err) +{ + if (err == &err_no_mem) + return; + + free(err); +} + +static void initialize(void) +{ + cdecl__init_i18n(); + err_no_mem.err.str = gettext(err_no_mem.err.str); + + gl_tls_key_init(tls_key, free_err); + + /* + * This default error message is a stop-gap measure until all library + * error conditions use the new interface. + */ + cdecl__set_error(&(const struct cdecl_error){ .code = CDECL_ENOPARSE }); +} + +const struct cdecl_error *cdecl_get_error(void) +{ + struct err_state *state; + + gl_once(tls_initialized, initialize); + + state = gl_tls_get(tls_key); + assert(state); + + return &state->err; +} + +void cdecl__set_error(const struct cdecl_error *err) +{ + struct err_state *state; + size_t need_len = 0; + + gl_once(tls_initialized, initialize); + + if (err->str) { + need_len = strlen(err->str) + 1; + } + + state = gl_tls_get(tls_key); + if (state == &err_no_mem) + state = NULL; + if (!state || state->nstr < need_len) { + struct err_state *tmp; + + tmp = realloc(state, sizeof *state + need_len); + if (!tmp) { + /* Re-use the existing state buffer, if any. */ + if (state) + state->err = err_no_mem.err; + else + state = &err_no_mem; + + gl_tls_set(tls_key, state); + return; + } + + state = tmp; + state->nstr = need_len; + gl_tls_set(tls_key, state); + } + + state->err = *err; + if (err->str) { + memcpy(state->str, err->str, need_len); + state->err.str = state->str; + } else { + state->err.str = "unknown error"; + } +} diff --git a/src/error.h b/src/error.h new file mode 100644 index 0000000..2404ab6 --- /dev/null +++ b/src/error.h @@ -0,0 +1,7 @@ +#ifndef CDECL_ERROR_H_ +#define CDECL_ERROR_H_ + +struct cdecl_error; +void cdecl__set_error(const struct cdecl_error *err); + +#endif diff --git a/src/parse.y b/src/parse.y index f90013a..5bcd9a9 100644 --- a/src/parse.y +++ b/src/parse.y @@ -32,6 +32,7 @@ #include #include "scan.h" +#include "error.h" #include "cdecl.h" #define FAIL(msg) do { \ @@ -584,5 +585,8 @@ yyerror(YYLTYPE *loc, yyscan_t scanner, struct cdecl **out, if (strstr(err, "T_LEX_ERROR")) return; - fprintf(stderr, "%s\n", err); + cdecl__set_error(&(const struct cdecl_error){ + .code = CDECL_ENOPARSE, + .str = err, + }); }