dist_man_MANS = doc/cdecl99.1 doc/libcdecl.3
include_HEADERS = src/cdecl.h
-noinst_HEADERS = conf_pre.h conf_post.h common/src/help.h common/src/xtra.h \
- src/scan.h src/parse.h t/declgen.h t/test.h
+noinst_HEADERS = conf_pre.h conf_post.h common/src/help.h common/src/tap.h \
+ common/src/xtra.h src/scan.h src/parse.h t/declgen.h t/test.h
noinst_DATA = $(MOFILES)
src/output.lo: src/specstr.h
t/declgen.$(OBJEXT): t/typegen.h
+check_PROGRAMS += t/cdeclerr
+t_cdeclerr_SOURCES = common/src/tap.c t/cdeclerr.c
+EXTRA_t_cdeclerr_DEPENDENCIES = src/error.lo $(shared_gl_objects)
+t_cdeclerr_LDADD = $(EXTRA_t_cdeclerr_DEPENDENCIES) $(LIBTHREAD)
+$(t_cdeclerr_OBJECTS): $(gnulib_headers)
+
# Supporting rules for gettext.
include $(top_srcdir)/common/snippet/gettext.mk
-Subproject commit 207d0dbfbbfa7ad70b59ada3a741895842958885
+Subproject commit f1527e8c5958c25abb54818a2e9618a85a75bec0
/*
* Error handling for libcdecl.
- * Copyright © 2011-2012, 2021 Nick Bowler
+ * Copyright © 2011-2012, 2021, 2023 Nick Bowler
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
char str[FLEXIBLE_ARRAY_MEMBER];
};
-/* This error is reserved for extremely dire out-of-memory conditions. */
-static struct err_state err_no_mem = {
- .err = {
- .code = CDECL_ENOMEM,
- .str = NULL,
- },
-};
+/* This pre-initialized error is reserved for dire out-of-memory conditions. */
+static struct cdecl_error err_no_mem;
static void free_err(void *err)
{
static void initialize(void)
{
cdecl__init_i18n();
- err_no_mem.err.str = _("failed to allocate memory");
-
+ err_no_mem.str = _("failed to allocate memory");
gl_tls_key_init(tls_key, free_err);
}
+static void *alloc_err_state(void *old, size_t buf_size)
+{
+ struct err_state *state;
+ void *p;
+
+ state = p = realloc(old, offsetof(struct err_state, str) + buf_size);
+ if (state) {
+ state->nstr = buf_size;
+ } else if (old) {
+ /* Failed allocation, but existing state is still good */
+ p = old;
+ } else {
+ /* Failed allocation, no existing state */
+ p = &err_no_mem;
+ }
+
+ gl_tls_set(tls_key, p);
+ return state;
+}
+
+static struct err_state *get_err_state(void)
+{
+ void *state;
+
+ gl_once(tls_initialized, initialize);
+
+ state = gl_tls_get(tls_key);
+ if (state == &err_no_mem)
+ state = NULL;
+ if (!state)
+ return alloc_err_state(state, 100);
+ return state;
+}
+
/*
* cdecl__err(CDECL_ENOMEM);
* cdecl__err(code, fmt, ...);
int rc, try = 0;
va_list ap;
- gl_once(tls_initialized, initialize);
-
- state = gl_tls_get(tls_key);
- if (!state || state == &err_no_mem) {
- void *tmp = malloc(sizeof *state + 100);
-
- if (!tmp) {
- state = &err_no_mem;
- return;
- }
-
- gl_tls_set(tls_key, (state = tmp));
- state->nstr = 100;
- }
+ state = get_err_state();
+ if (!state)
+ return;
if (code == CDECL_ENOMEM) {
- if (state != &err_no_mem)
- state->err = err_no_mem.err;
+ state->err.code = code;
+ state->err.str = err_no_mem.str;
return;
}
retry:
va_end(ap);
if (rc > 0 && rc >= state->nstr) {
- void *tmp;
- size_t n;
-
- assert(try == 0 && rc < SIZE_MAX / 4);
-
- n = ((size_t)rc + 1) * 2;
- tmp = realloc(state, sizeof *state + n);
- if (tmp) {
- state = tmp;
- state->nstr = n;
- try++;
-
- goto retry;
- }
+ assert(try++ == 0 && rc < SIZE_MAX / 4);
+ state = alloc_err_state(state, (size_t)(rc+1u) * 3 / 2);
+ if (!state)
+ return;
- state->err = err_no_mem.err;
- return;
+ goto retry;
}
state->err.str = state->str;
const struct cdecl_error *cdecl_get_error(void)
{
- struct err_state *state;
-
- gl_once(tls_initialized, initialize);
+ struct err_state *state = get_err_state();
- state = gl_tls_get(tls_key);
return state ? &state->err : NULL;
}
-/typegen.h
-/randomdecl
+/cdeclerr
/crossparse
/normalize
+/randomdecl
/rng-test
+/typegen.h
--- /dev/null
+/*
+ * Helper application to test internal library error reporting.
+ * Copyright © 2023 Nick Bowler
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "cdecl-internal.h"
+#include "cdecl.h"
+#include "tap.h"
+
+#if ENABLE_NLS
+void cdecl__init_i18n(void) { }
+#endif
+
+static char *fmt_char(int c, char *buf)
+{
+ int escape = 0;
+
+ switch (c) {
+ case '\a': escape = 'a'; break;
+ case '\b': escape = 'b'; break;
+ case '\t': escape = 't'; break;
+ case '\n': escape = 'n'; break;
+ case '\v': escape = 'v'; break;
+ case '\f': escape = 'f'; break;
+ case '\r': escape = 'r'; break;
+ case '\\': escape = '\\'; break;
+ case '\'': escape = '\''; break;
+ }
+
+ if (escape)
+ sprintf(buf, "'\\%c'", escape);
+ else if (isprint((unsigned char)c))
+ sprintf(buf, "'%c'", c);
+ else
+ sprintf(buf, "'\\%.3o'", c & 0777u);
+
+ return buf;
+}
+
+static void check_fixed_string(size_t len)
+{
+ const struct cdecl_error *err;
+ char *work1, *work2;
+ size_t errlen;
+
+ assert(len > 0);
+ work1 = calloc(2, len);
+ if (!work1)
+ abort();
+
+ work2 = work1 + len;
+ memset(work1, 'X', len - 1);
+ memset(work2, 'X', len - 1);
+
+ tap_diag("cdecl__err w/ %lu-byte string", (unsigned long)len);
+ cdecl__err(1234, work1);
+ memset(work1, 0, len);
+ err = cdecl_get_error();
+
+ if (!tap_result(err->code == 1234, "returned error code")) {
+ tap_diag("Failed, unexpected result");
+ tap_diag(" Received: %u", err->code);
+ tap_diag(" Expected: 1234");
+ }
+
+ errlen = strlen(err->str);
+ if (!tap_result(errlen == len-1, "returned string length")) {
+ tap_diag("Failed, unexpected result");
+ tap_diag(" Received: %lu", (unsigned long)errlen);
+ tap_diag(" Expected: %lu", (unsigned long)len);
+ }
+
+ if (!tap_result(!memcmp(err->str, work2, len), "returned string")) {
+ char buf[8];
+ size_t n;
+
+ n = strspn(err->str, "X");
+ if (n >= len)
+ n = len;
+
+ tap_diag("Failed, first incorrect character at offset %lu",
+ (unsigned long)n);
+ tap_diag(" Received: %s", fmt_char(err->str[n], buf));
+ tap_diag(" Expected: %s", fmt_char(work2[n], buf));
+ }
+
+ free(work1);
+}
+
+static void check_enomem()
+{
+ const char expmsg[] = "failed to allocate memory";
+ const struct cdecl_error *err;
+
+ tap_diag("cdecl__err(CDECL_ENOMEM)");
+ cdecl__err(CDECL_ENOMEM);
+ err = cdecl_get_error();
+
+ if (!tap_result(err->code == CDECL_ENOMEM, "returned error code")) {
+ tap_diag("Failed, unexpected result");
+ tap_diag(" Received: %u", err->code);
+ tap_diag(" Expected: %d", CDECL_ENOMEM);
+ }
+
+ if (!tap_result(!strcmp(err->str, "failed to allocate memory"),
+ "returned string"))
+ {
+ unsigned i;
+
+ for (i = 0; i < sizeof expmsg; i++) {
+ if (err->str[i] != expmsg[i])
+ break;
+ }
+
+ tap_diag("Failed, first incorrect character at offset %u", i);
+ tap_diag(" Received: %.*s", (int)sizeof expmsg, err->str);
+ tap_diag(" Expected: %s", expmsg);
+ }
+}
+
+int main(void)
+{
+ tap_plan(3*3 + 2);
+
+ check_fixed_string(50);
+ check_fixed_string(500);
+ check_fixed_string(5000);
+
+ check_enomem();
+
+ tap_done();
+}
-# Copyright © 2021 Nick Bowler
+# Copyright © 2021, 2023 Nick Bowler
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
AT_BANNER([Internal library behaviour])
AT_SETUP([cdecl__normalize_specs])
+AT_KEYWORDS([libcdecl internal])
TEST_NEED_PROGRAM([normalize])
]])
AT_CLEANUP
+
+TEST_TAP_SIMPLE([cdecl__err sanity], [cdeclerr],
+ [TEST_NEED_PROGRAM([cdeclerr])], [libcdecl internal])
AT_INIT
AT_COLOR_TESTS
+m4_include([common/snippet/test-tap.at])
+
AT_TESTED([cdecl99])
AT_ARG_OPTION_ARG([random-seed],