]> git.draconx.ca Git - cdecl99.git/blob - src/error.c
Release 1.3.
[cdecl99.git] / src / error.c
1 /*
2  *  Error handling for libcdecl.
3  *  Copyright © 2011-2012, 2021, 2023-2024 Nick Bowler
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include <config.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <assert.h>
23 #include <stdbool.h>
24
25 #include "cdecl.h"
26 #include "cdecl-internal.h"
27 #include "errmsg.h"
28
29 /* This pre-initialized error is reserved for dire out-of-memory conditions. */
30 static struct cdecl_error err_no_mem;
31
32 static void set_err(unsigned code, struct cdecl_error *err)
33 {
34         static const char _Alignas(1) errmsgs[] = STRTAB_INITIALIZER;
35
36         switch (code) {
37         case CDECL__ENOMEM:
38                 err->code = CDECL_ENOMEM;
39                 break;
40         default:
41                 err->code = CDECL_ENOPARSE;
42                 break;
43         }
44
45         err->str = _(&errmsgs[code]);
46 }
47
48 static void init_once_cb(void)
49 {
50 #if ENABLE_NLS
51         bindtextdomain(PACKAGE, LOCALEDIR);
52         bindtextdomain("bison-runtime", BISON_LOCALEDIR);
53 #endif
54         set_err(CDECL__ENOMEM, &err_no_mem);
55 }
56
57 #if USE_POSIX_THREADS
58 #  include "thread-posix.h"
59 #elif USE_ISOC_THREADS || USE_ISOC_AND_POSIX_THREADS
60 #  include "thread-stdc.h"
61 #elif USE_WINDOWS_THREADS
62 #  include "thread-w32.h"
63 #else
64 static void *tls_key;
65 enum { tls_key_valid = 1 };
66
67 #define tls_get() tls_key
68 #define tls_set(a) ((tls_key = (a)), 1)
69
70 static int init_once(void)
71 {
72         if (!err_no_mem.code)
73                 init_once_cb();
74         return 1;
75 }
76
77 #endif
78
79 struct err_state {
80         struct cdecl_error err;
81         size_t nstr;
82         char str[FLEXIBLE_ARRAY_MEMBER];
83 };
84
85 static void *alloc_err_state(void *old, size_t buf_size)
86 {
87         struct err_state *state;
88         void *p;
89
90         state = p = realloc(old, offsetof(struct err_state, str) + buf_size);
91         if (state) {
92                 state->nstr = buf_size;
93                 if (!tls_set(state)) {
94                         /*
95                          * We have to presume that pthread_setspecific etc.
96                          * cannot fail after the key has been successfully
97                          * assigned once, because there seems to be no
98                          * reasonable recovery from such a scenario.
99                          */
100                         free(p);
101                         p = NULL;
102                 }
103         } else if (old) {
104                 /* Failed allocation, but existing state is still good */
105                 p = old;
106         }
107
108         return p;
109 }
110
111 static struct err_state *get_err_state(void)
112 {
113         void *state;
114
115         if (!init_once())
116                 return NULL;
117
118         if (!(state = tls_get()))
119                 return alloc_err_state(state, 100);
120         return state;
121 }
122
123
124 #if ENABLE_NLS
125 /*
126  * Initialize gettext indirectly via get_err_state.
127  */
128 void cdecl__init_i18n(void)
129 {
130         get_err_state();
131 }
132 #endif
133
134 /*
135  * Set the library error to one of the preset messages defined in errmsg.h
136  * (CDECL__Exxx).
137  */
138 void cdecl__errmsg(unsigned msg)
139 {
140         struct err_state *state;
141
142         state = get_err_state();
143         if (!state)
144                 return;
145
146         set_err(msg, &state->err);
147 }
148
149 /*
150  * In the NLS-disabled case, all format strings are of the form
151  *
152  *   "blah blah %s"
153  *
154  * so we exploit this to implement a simple snprintf workalike using the
155  * libcdecl output helpers directly.
156  *
157  * In the NLS-enabled case, we have to use snprintf as format strings may
158  * be translated.  GNU libintl ensures a suitable version is available.
159  */
160 static size_t
161 fmt_err(struct err_state *state, const char *fmt, const char *arg)
162 {
163 #if ENABLE_NLS
164         snprintf(state->str, state->nstr, fmt, arg);
165         return strlen(fmt) + strlen(arg);
166 #else
167         struct output_state dst = { state->str, state->nstr };
168         size_t rc;
169
170         rc = cdecl__strlcpy(dst.dst, fmt, dst.dstlen);
171         cdecl__advance(&dst, rc-2);
172         cdecl__emit(&dst, arg);
173
174         return dst.accum;
175 #endif
176 }
177
178 /*
179  * Sets the library error to code; fmt is a printf-style string that may use
180  * up to one %s directive, to refer to arg.
181  */
182 void cdecl__err(const char *fmt, const char *arg)
183 {
184         struct err_state *state;
185         unsigned try = 0;
186         size_t rc;
187
188         state = get_err_state();
189         if (!state)
190                 return;
191
192 retry:
193         rc = fmt_err(state, fmt, arg);
194         if (rc >= state->nstr) {
195                 assert(try++ == 0 && rc < (size_t)-1 / 4);
196
197                 state = alloc_err_state(state, (size_t)(rc+1u) * 3 / 2);
198                 if (!state)
199                         return;
200
201                 goto retry;
202         }
203
204         state->err.code = CDECL_ENOPARSE;
205         state->err.str = state->str;
206 }
207
208 const struct cdecl_error *cdecl_get_error(void)
209 {
210         struct err_state *state = get_err_state();
211
212         return state ? &state->err : &err_no_mem;
213 }