]> git.draconx.ca Git - cdecl99.git/commitdiff
Start implementing proper error handling.
authorNick Bowler <nbowler@draconx.ca>
Thu, 29 Sep 2011 01:57:23 +0000 (21:57 -0400)
committerNick Bowler <nbowler@draconx.ca>
Sun, 23 Oct 2011 00:20:58 +0000 (20:20 -0400)
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.

Makefile.am
doc/man/libcdecl.3
m4/.gitignore
m4/gnulib-cache.m4
src/cdecl.h
src/cdecl99.c
src/error.c [new file with mode: 0644]
src/error.h [new file with mode: 0644]
src/parse.y

index de6c066c1064f4f82418a29721f9517854f1b4b8..f387ee348d3cbbe204294f2d1688cfa130dbdde2 100644 (file)
@@ -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)
 
index d92dcbfe478daf49790d5c5d6282e79aeccb05b1..489d7a9ef7eb8fce9a409c70d1aab18735e2efdd 100644 (file)
@@ -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
index 14287ab6a99a215c80d82da74ad56d0d4c781eec..f93decd99af3df9b59a3db1f0ce750f729292596 100644 (file)
@@ -49,6 +49,7 @@ stdio_h.m4
 stdlib_h.m4
 thread.m4
 threadlib.m4
+tls.m4
 uintmax_t.m4
 unistd_h.m4
 visibility.m4
index 09fc0cdee865e5cbfabd209b4662dd5042b1df4d..3ecf1efdbca6d441df14a0c27fef8984d2a1ab9d 100644 (file)
@@ -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])
index c4dad87a38106c6c107fa5ffc7515b5411cd878b..a8e994687ccc6dd02c84dcc783dac465364f1fbd 100644 (file)
@@ -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
index 04de1a6640e92225676f3e56872a63e26945a70d..c5f913bd12d1fd0a442bdc0f2aa82def63d3a5cf 100644 (file)
@@ -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 (file)
index 0000000..e18ec80
--- /dev/null
@@ -0,0 +1,103 @@
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <glthread/lock.h>
+#include <glthread/tls.h>
+#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 (file)
index 0000000..2404ab6
--- /dev/null
@@ -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
index f90013a44c458989e890dde1c04a323b5176676f..5bcd9a947c6e587e44fc4d403ab319798b3be41d 100644 (file)
@@ -32,6 +32,7 @@
 #include <stdbool.h>
 
 #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,
+       });
 }