Add a much better workaround for bug #8382 and some expert info.
[metze/wireshark/wip.git] / epan / except.c
1 /*
2  * Portable Exception Handling for ANSI C.
3  * Copyright (C) 1999 Kaz Kylheku <kaz@ashi.footprints.net>
4  *
5  * $Id$
6  *
7  * Free Software License:
8  *
9  * All rights are reserved by the author, with the following exceptions:
10  * Permission is granted to freely reproduce and distribute this software,
11  * possibly in exchange for a fee, provided that this copyright notice appears
12  * intact. Permission is also granted to adapt this software to produce
13  * derivative works, as long as the modified versions carry this copyright
14  * notice and additional notices stating that the work has been modified.
15  * This source code may be translated into executable form and incorporated
16  * into proprietary software; there is no requirement for such software to
17  * contain a copyright notice related to this source.
18  */
19
20 /*
21  * Modified to support throwing an exception with a null message pointer,
22  * and to have the message not be const (as we generate messages with
23  * "g_strdup_sprintf()", which means they need to be freed; using
24  * a null message means that we don't have to use a special string
25  * for exceptions with no message, and don't have to worry about
26  * not freeing that).
27  */
28
29 #include "config.h"
30
31 #include <assert.h>
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <stdarg.h>
35 #include <limits.h>
36
37 #include <glib.h>
38
39 #include "except.h"
40
41 #ifdef _WIN32
42 #if _MSC_VER < 1500
43 /* IsDebuggerPresent() needs this #define! */
44 #define _WIN32_WINNT 0x0400
45 #endif
46 #include <windows.h>
47 #include "exceptions.h"
48 #endif
49
50 #define XCEPT_BUFFER_SIZE       1024
51
52 #ifdef KAZLIB_POSIX_THREADS
53
54 #include <pthread.h>
55
56 static pthread_mutex_t init_mtx = PTHREAD_MUTEX_INITIALIZER;
57 static int init_counter;
58 static pthread_key_t top_key;
59 static pthread_key_t uh_key;
60 static pthread_key_t alloc_key;
61 static pthread_key_t dealloc_key;
62 static void unhandled_catcher(except_t *);
63
64 #define get_top() ((struct except_stacknode *) pthread_getspecific(top_key))
65 #define set_top(T) (pthread_setspecific(top_key, (T)), (void)((T) == (struct except_stacknode *) 0))
66 #define set_catcher(C) (pthread_setspecific(uh_key, (void *) (C)), (void)((C) == (void (*)(except_t *)) 0))
67 #define set_alloc(A) (pthread_setspecific(alloc_key, (void *) (A)), (void)((A) == (void *(*)(size_t)) 0))
68 #define set_dealloc(D) (pthread_setspecific(dealloc_key, (void *) (D)), (void)((D) == (void (*)(void *)) 0))
69
70 static void (*get_catcher(void))(except_t *)
71 {
72     void (*catcher)(except_t *) = (void (*)(except_t *)) pthread_getspecific(uh_key);
73     return (catcher == 0) ? unhandled_catcher : catcher;
74 }
75
76 static void *(*get_alloc(void))(size_t)
77 {
78     void *(*alloc)(size_t) = (void *(*)(size_t)) pthread_getspecific(alloc_key);
79     return (alloc == 0) ? malloc : alloc;
80 }
81
82 static void (*get_dealloc(void))(void *)
83 {
84     void (*dealloc)(void *) = (void (*)(void *)) pthread_getspecific(dealloc_key);
85     return (dealloc == 0) ? free : dealloc;
86 }
87
88 int except_init(void)
89 {
90     int retval = 1;
91
92     pthread_mutex_lock(&init_mtx);
93
94     assert (init_counter < INT_MAX);
95
96     if (init_counter++ == 0) {
97         int top_ok = (pthread_key_create(&top_key, 0) == 0);
98         int uh_ok = (pthread_key_create(&uh_key, 0) == 0);
99         int alloc_ok = (pthread_key_create(&alloc_key, 0) == 0);
100         int dealloc_ok = (pthread_key_create(&dealloc_key, 0) == 0);
101
102         if (!top_ok || !uh_ok || !alloc_ok || !dealloc_ok) {
103             retval = 0;
104             init_counter = 0;
105             if (top_ok)
106                 pthread_key_delete(top_key);
107             if (uh_ok)
108                 pthread_key_delete(uh_key);
109             if (alloc_ok)
110                 pthread_key_delete(alloc_key);
111             if (dealloc_ok)
112                 pthread_key_delete(dealloc_key);
113         }
114     }
115
116     pthread_mutex_unlock(&init_mtx);
117
118     return retval;
119 }
120
121 void except_deinit(void)
122 {
123     pthread_mutex_lock(&init_mtx);
124
125     assert (init_counter > 0);
126
127     if (--init_counter == 0) {
128         pthread_key_delete(top_key);
129         pthread_key_delete(uh_key);
130         pthread_key_delete(alloc_key);
131         pthread_key_delete(dealloc_key);
132     }
133
134     pthread_mutex_unlock(&init_mtx);
135 }
136
137 #else   /* no thread support */
138
139 static int init_counter;
140 static void unhandled_catcher(except_t *);
141 static void (*uh_catcher_ptr)(except_t *) = unhandled_catcher;
142 /* We need this 'size_t' cast due to a glitch in GLib where g_malloc was prototyped
143  * as 'gpointer g_malloc (gulong n_bytes)'. This was later fixed to the correct prototype
144  * 'gpointer g_malloc (gsize n_bytes)'. In Wireshark we use the latter prototype
145  * throughout the code. We can get away with this even with older versions of GLib by
146  * adding a '(void *(*)(size_t))' cast whenever we refer to g_malloc. The only platform
147  * supported by Wireshark where this isn't safe (sizeof size_t != sizeof gulong) is Win64.
148  * However, we _always_ bundle the newest version of GLib on this platform so
149  * the size_t issue doesn't exists here. Pheew.. */
150 static void *(*allocator)(size_t) = (void *(*)(size_t)) g_malloc;
151 static void (*deallocator)(void *) = g_free;
152 static struct except_stacknode *stack_top;
153
154 #define get_top() (stack_top)
155 #define set_top(T) (stack_top = (T))
156 #define get_catcher() (uh_catcher_ptr)
157 #define set_catcher(C) (uh_catcher_ptr = (C))
158 #define get_alloc() (allocator)
159 #define set_alloc(A) (allocator = (A))
160 #define get_dealloc() (deallocator)
161 #define set_dealloc(D) (deallocator = (D))
162
163 int except_init(void)
164 {
165     assert (init_counter < INT_MAX);
166     init_counter++;
167     return 1;
168 }
169
170 void except_deinit(void)
171 {
172     assert (init_counter > 0);
173     init_counter--;
174 }
175
176 #endif
177
178
179 static int match(const volatile except_id_t *thrown, const except_id_t *caught)
180 {
181     int group_match = (caught->except_group == XCEPT_GROUP_ANY ||
182         caught->except_group == thrown->except_group);
183     int code_match = (caught->except_code == XCEPT_CODE_ANY ||
184         caught->except_code == thrown->except_code);
185
186     return group_match && code_match;
187 }
188
189 G_GNUC_NORETURN WS_MSVC_NORETURN static void do_throw(except_t *except)
190 {
191     struct except_stacknode *top;
192
193     assert (except->except_id.except_group != 0 &&
194         except->except_id.except_code != 0);
195
196     for (top = get_top(); top != 0; top = top->except_down) {
197         if (top->except_type == XCEPT_CLEANUP) {
198             top->except_info.except_cleanup->except_func(top->except_info.except_cleanup->except_context);
199         } else {
200             struct except_catch *catcher = top->except_info.except_catcher;
201             const except_id_t *pi = catcher->except_id;
202             size_t i;
203
204             assert (top->except_type == XCEPT_CATCHER);
205             except_free(catcher->except_obj.except_dyndata);
206
207             for (i = 0; i < catcher->except_size; pi++, i++) {
208                 if (match(&except->except_id, pi)) {
209                     catcher->except_obj = *except;
210                     set_top(top);
211                     longjmp(catcher->except_jmp, 1);
212                 }
213             }
214         }
215     }
216
217     set_top(top);
218     get_catcher()(except);      /* unhandled exception */
219     abort();
220 }
221
222 static void unhandled_catcher(except_t *except)
223 {
224     if (except->except_message == NULL) {
225         fprintf(stderr, "Unhandled exception (group=%ld, code=%ld)\n",
226                 except->except_id.except_group,
227                 except->except_id.except_code);
228     } else {
229         fprintf(stderr, "Unhandled exception (\"%s\", group=%ld, code=%ld)\n",
230                 except->except_message, except->except_id.except_group,
231                 except->except_id.except_code);
232     }
233     abort();
234 }
235
236 static void stack_push(struct except_stacknode *node)
237 {
238     node->except_down = get_top();
239     set_top(node);
240 }
241
242 void except_setup_clean(struct except_stacknode *esn,
243         struct except_cleanup *ecl, void (*cleanf)(void *), void *context)
244 {
245     esn->except_type = XCEPT_CLEANUP;
246     ecl->except_func = cleanf;
247     ecl->except_context = context;
248     esn->except_info.except_cleanup = ecl;
249     stack_push(esn);
250 }
251
252 void except_setup_try(struct except_stacknode *esn,
253         struct except_catch *ech, const except_id_t id[], size_t size)
254 {
255    ech->except_id = id;
256    ech->except_size = size;
257    ech->except_obj.except_dyndata = 0;
258    esn->except_type = XCEPT_CATCHER;
259    esn->except_info.except_catcher = ech;
260    stack_push(esn);
261 }
262
263 struct except_stacknode *except_pop(void)
264 {
265     struct except_stacknode *top = get_top();
266     set_top(top->except_down);
267     return top;
268 }
269
270 G_GNUC_NORETURN WS_MSVC_NORETURN void except_rethrow(except_t *except)
271 {
272     struct except_stacknode *top = get_top();
273     assert (top != 0);
274     assert (top->except_type == XCEPT_CATCHER);
275     assert (&top->except_info.except_catcher->except_obj == except);
276     set_top(top->except_down);
277     do_throw(except);
278 }
279
280 G_GNUC_NORETURN WS_MSVC_NORETURN void except_throw(long group, long code, const char *msg)
281 {
282     except_t except;
283
284     except.except_id.except_group = group;
285     except.except_id.except_code = code;
286     except.except_message = msg;
287     except.except_dyndata = 0;
288
289 #ifdef _WIN32
290     if (code == DissectorError && IsDebuggerPresent()) {
291         DebugBreak();
292     }
293 #endif
294
295     do_throw(&except);
296 }
297
298 G_GNUC_NORETURN WS_MSVC_NORETURN void except_throwd(long group, long code, const char *msg, void *data)
299 {
300     except_t except;
301
302     except.except_id.except_group = group;
303     except.except_id.except_code = code;
304     except.except_message = msg;
305     except.except_dyndata = data;
306
307     do_throw(&except);
308 }
309
310 /*
311  * XXX - should we use g_strdup_sprintf() here, so we're not limited by
312  * XCEPT_BUFFER_SIZE?  We could then just use this to generate formatted
313  * messages.
314  */
315 G_GNUC_NORETURN WS_MSVC_NORETURN void except_throwf(long group, long code, const char *fmt, ...)
316 {
317     char *buf = except_alloc(XCEPT_BUFFER_SIZE);
318     va_list vl;
319
320     va_start (vl, fmt);
321     g_vsnprintf(buf, XCEPT_BUFFER_SIZE, fmt, vl);
322     va_end (vl);
323     except_throwd(group, code, buf, buf);
324 }
325
326 void (*except_unhandled_catcher(void (*new_catcher)(except_t *)))(except_t *)
327 {
328     void (*old_catcher)(except_t *) = get_catcher();
329     set_catcher(new_catcher);
330     return old_catcher;
331 }
332
333 #undef except_code
334 #undef except_group
335 #undef except_message
336 #undef except_data
337
338 unsigned long except_code(except_t *ex)
339 {
340     return ex->except_id.except_code;
341 }
342
343 unsigned long except_group(except_t *ex)
344 {
345     return ex->except_id.except_group;
346 }
347
348 const char *except_message(except_t *ex)
349 {
350     return ex->except_message;
351 }
352
353 void *except_data(except_t *ex)
354 {
355     return ex->except_dyndata;
356 }
357
358 void *except_take_data(except_t *ex)
359 {
360     void *data = ex->except_dyndata;
361     ex->except_dyndata = 0;
362     return data;
363 }
364
365 void except_set_allocator(void *(*alloc)(size_t), void (*dealloc)(void *))
366 {
367     set_alloc(alloc);
368     set_dealloc(dealloc);
369 }
370
371 void *except_alloc(size_t size)
372 {
373     void *ptr = get_alloc()(size);
374
375     if (ptr == 0)
376         except_throw(XCEPT_BAD_ALLOC, 0, "out of memory");
377     return ptr;
378 }
379
380 void except_free(void *ptr)
381 {
382     get_dealloc()(ptr);
383 }
384
385 #ifdef KAZLIB_TEST_MAIN
386
387 #include <stdio.h>
388 #include <ctype.h>
389
390 static void cleanup(void *arg)
391 {
392     printf("cleanup(\"%s\") called\n", (char *) arg);
393 }
394
395 static void bottom_level(void)
396 {
397     char buf[256];
398     printf("throw exception? "); fflush(stdout);
399     fgets(buf, sizeof buf, stdin);
400
401     if (buf[0] >= 0 && toupper(buf[0]) == 'Y')
402         except_throw(1, 1, "nasty exception");
403 }
404
405 static void top_level(void)
406 {
407     except_cleanup_push(cleanup, "argument");
408     bottom_level();
409     except_cleanup_pop(0);
410 }
411
412 int main(int argc, char **argv)
413 {
414     static const except_id_t catch[] = { { 1, 1 }, { 1, 2 } };
415     except_t *ex;
416     char *msg;
417
418     /*
419      * Nested exception ``try blocks''
420      */
421
422     /* outer */
423     except_try_push(catch, 2, &ex);
424     if (!ex) {
425         /* inner */
426         except_try_push(catch, 2, &ex);
427         if (!ex) {
428             top_level();
429         } else {
430             /* inner catch */
431             msg = except_message(ex);
432             if (msg == NULL) {
433                 printf("caught exception (inner): s=%ld, c=%ld\n",
434                        except_group(ex), except_code(ex));
435             } else {
436                 printf("caught exception (inner): \"%s\", s=%ld, c=%ld\n",
437                        msg, except_group(ex), except_code(ex));
438             }
439             except_rethrow(ex);
440         }
441         except_try_pop();
442     } else {
443         /* outer catch */
444         msg = except_message(ex);
445         if (msg == NULL) {
446             printf("caught exception (outer): s=%ld, c=%ld\n",
447                    except_group(ex), except_code(ex));
448         } else {
449             printf("caught exception (outer): \"%s\", s=%ld, c=%ld\n",
450                    except_message(ex), except_group(ex), except_code(ex));
451         }
452     }
453     except_try_pop();
454     except_throw(99, 99, "exception in main");
455     return 0;
456 }
457
458
459 #endif