1 #include "ixxl.h"
2 
3 #ifdef WIN32
4 HANDLE xxl_heap = NULL;
5 
6 #ifdef XXL_WITHOUT_THREADS
7 static __inline xxl_tsd_t *get_xxl_tsd(void);
8 #endif
9 #endif
10 
11 static void          die(char *);
12 #if !defined(XXL_WITHOUT_THREADS) && defined(HAVE_PTHREAD_H)
13 static void          xxl_destructor(void *);
14 static void          xxl_init(void);
15 #endif
16 #if !defined(XXL_WITHOUT_THREADS)
17 static xxl_tsd_t     *get_xxl_tsd(void);
18 #endif
19 static xxl_asset_t   *alloc_asset(xxl_tsd_t *);
20 static void          free_asset(xxl_tsd_t *, xxl_asset_t *);
21 static xxl_context_t *alloc_context(xxl_tsd_t *);
22 static void          free_context(xxl_tsd_t *, xxl_context_t *);
23 static void          pop_assets(xxl_tsd_t *, xxl_context_t *);
24 static void          pop_asset_blocks(xxl_tsd_t *, xxl_context_t *, xxl_exception_t *);
25 static xxl_context_t *get_try_context(xxl_tsd_t *);
26 
27 static void
die(char * why)28 die(char *why)
29 {
30 #ifndef WIN32
31     fprintf(stderr, "%s", why);
32     abort();
33 #else
34     DebugBreak();
35     ExitProcess(0xFFFFFFFF);
36 #endif
37 }
38 
39 #ifndef XXL_WITHOUT_THREADS
40 #ifdef WIN32
41 static DWORD    xxl_tls_index = 0xFFFFFFFF;
42 
43 static xxl_tsd_t *
get_xxl_tsd(void)44 get_xxl_tsd(void)
45 {
46     DWORD       dwTlsIndex;
47     xxl_tsd_t   *tsd;
48 
49     if (!xxl_heap)
50         xxl_heap = GetProcessHeap();
51     if (xxl_tls_index == 0xFFFFFFFF)
52     {
53         dwTlsIndex = TlsAlloc();
54         xxl_tls_index ^= dwTlsIndex;
55         xxl_tls_index ^= 0xFFFFFFFF;
56         if (xxl_tls_index != dwTlsIndex)
57             die("XXL: Race condition in exception initialization!\n");
58     }
59 
60     if (!(tsd = (xxl_tsd_t *)TlsGetValue(xxl_tls_index)))
61     {
62         if (!(tsd = (xxl_tsd_t *)XXL_IMALLOC(sizeof(xxl_tsd_t))))
63             die("XXL: Insufficient memory to allocate thread-specific data!\n");
64         tsd->contexts = NULL;
65         TlsSetValue(xxl_tls_index, tsd);
66     }
67     return tsd;
68 }
69 #else
70 #ifdef HAVE_PTHREAD_H
71 #include <pthread.h>
72 #endif
73 static pthread_key_t    xxl_key;
74 static pthread_once_t   xxl_once = PTHREAD_ONCE_INIT;
75 
76 static void
xxl_destructor(void * arg)77 xxl_destructor(void *arg)
78 {
79     xxl_tsd_t       *tsd;
80     xxl_asset_t     *asset, *next_asset;
81     xxl_context_t   *context, *next_context;
82 
83     /* The thread has been prematurely cancelled or exited.  Cleanup saved
84      * assets, but don't run handlers.
85      */
86     tsd = (xxl_tsd_t *)arg;
87     while (tsd->contexts)
88     {
89         /* XXX: need thread cancelled error code */
90         tsd->contexts->exception.code = XXL_ERROR_THREAD_CANCELLED;
91         tsd->contexts->exception.data = NULL;
92         tsd->contexts->exception.file = NULL;
93         tsd->contexts->exception.line = 0;
94         xxl_pop_context();
95     }
96 
97     /* Release free list contents back to the system */
98     for (asset = tsd->free_assets;  asset;  asset = next_asset)
99     {
100         next_asset = asset->next;
101         XXL_IFREE(asset);
102     }
103     for (context = tsd->free_contexts;  context;  context = next_context)
104     {
105         next_context = context->next;
106         XXL_IFREE(context);
107     }
108     XXL_IFREE(arg);
109 }
110 
111 static void
xxl_init(void)112 xxl_init(void)
113 {
114     pthread_key_create(&xxl_key, xxl_destructor);
115 }
116 
117 static xxl_tsd_t *
get_xxl_tsd(void)118 get_xxl_tsd(void)
119 {
120     xxl_tsd_t   *tsd;
121 
122     pthread_once(&xxl_once, xxl_init);
123     if (!(tsd = (xxl_tsd_t *)pthread_getspecific(xxl_key)))
124     {
125         if (!(tsd = (xxl_tsd_t *)XXL_IMALLOC(sizeof(xxl_tsd_t))))
126             die("XXL: Insufficient memory to allocate thread-specific data!\n");
127         tsd->contexts      = NULL;
128         tsd->free_contexts = NULL;
129         tsd->free_assets   = NULL;
130         pthread_setspecific(xxl_key, tsd);
131     }
132     return tsd;
133 }
134 #endif  /* WIN32 */
135 #else   /* XXL_WITHOUT_THREADS */
136 #if !defined(WIN32) || defined(XXL_WITHOUT_THREADS)
137 static xxl_tsd_t    xxl_tsd = { 0, NULL, NULL };
138 #else
139 static xxl_tsd_t    xxl_tsd = { 0 };
140 #endif
141 #ifndef WIN32
142 #define get_xxl_tsd()   (&xxl_tsd)
143 #else
144 static __inline xxl_tsd_t *
get_xxl_tsd(void)145 get_xxl_tsd(void)
146 {
147     if (!xxl_heap)
148         xxl_heap = GetProcessHeap();
149     return &xxl_tsd;
150 }
151 #endif
152 #endif
153 
154 static xxl_asset_t *
alloc_asset(xxl_tsd_t * tsd)155 alloc_asset(xxl_tsd_t *tsd)
156 {
157     xxl_asset_t *asset;
158 
159 #if !defined(WIN32) || defined(XXL_WITHOUT_THREADS)
160     if (tsd->free_assets)
161     {
162         asset = tsd->free_assets;
163         tsd->free_assets = tsd->free_assets->next;
164     }
165     else
166 #endif
167     {
168         if (!(asset = (xxl_asset_t *)XXL_IMALLOC(sizeof(xxl_asset_t))))
169             die("XXL: Insufficient memory to allocate a new asset record!\n");
170     }
171     return asset;
172 }
173 
174 static void
free_asset(xxl_tsd_t * tsd,xxl_asset_t * asset)175 free_asset(xxl_tsd_t *tsd, xxl_asset_t *asset)
176 {
177 #if !defined(WIN32) || defined(XXL_WITHOUT_THREADS)
178     asset->next = tsd->free_assets;
179     tsd->free_assets = asset;
180 #else
181     /* Don't use the internal free list on Win32 because we have no way of
182      * cleaning it up later when the thread dies.
183      */
184     XXL_IFREE(asset);
185 #endif
186 }
187 
188 static xxl_context_t *
alloc_context(xxl_tsd_t * tsd)189 alloc_context(xxl_tsd_t *tsd)
190 {
191     xxl_context_t   *context;
192 
193 #if !defined(WIN32) || defined(XXL_WITHOUT_THREADS)
194     if (tsd->free_contexts)
195     {
196         context = tsd->free_contexts;
197         tsd->free_contexts = tsd->free_contexts->next;
198     }
199     else
200 #endif
201     {
202         if (!(context = (xxl_context_t *)XXL_IMALLOC(sizeof(xxl_context_t))))
203             die("XXL: Insufficient memory to allocate a new context!\n");
204     }
205     return context;
206 }
207 
208 static
free_context(xxl_tsd_t * tsd,xxl_context_t * context)209 void free_context(xxl_tsd_t *tsd, xxl_context_t *context)
210 {
211 #if !defined(WIN32) || defined(XXL_WITHOUT_THREADS)
212     context->next = tsd->free_contexts;
213     tsd->free_contexts = context;
214 #else
215     /* Don't use the internal free list on Win32 because we have no way of
216      * cleaning it up later when the thread dies.
217      */
218     XXL_IFREE(context);
219 #endif
220 }
221 
222 static void
pop_assets(xxl_tsd_t * tsd,xxl_context_t * context)223 pop_assets(xxl_tsd_t *tsd, xxl_context_t *context)
224 {
225     xxl_asset_t *asset;
226 
227     while (context->assets)
228     {
229         asset = context->assets;
230         context->assets = asset->next;
231 
232         switch (asset->type)
233         {
234             case XXL_ASSET_PROMOTE:
235                 if ((context->state & XXL_STATE_THROWN) && asset->freefn)
236                     asset->freefn(asset->ptr, asset->arg);
237                 break;
238             case XXL_ASSET_DEMOTE:
239                 if (!(context->state & XXL_STATE_THROWN) && asset->freefn)
240                 asset->freefn(asset->ptr, asset->arg);
241                 break;
242             case XXL_ASSET_TEMPORARY:
243                 if (asset->freefn)
244                     asset->freefn(asset->ptr, asset->arg);
245                 break;
246             case XXL_ASSET_PERMANENT:       /* keep the compiler quiet */
247             case XXL_ASSET_AUTO:            /* Shhh!                   */
248                 break;
249             default:
250                 die("XXL: Unknown asset type to pop!\n");
251         }
252 
253         free_asset(tsd, asset);
254     }
255 }
256 
257 static void
pop_asset_blocks(xxl_tsd_t * tsd,xxl_context_t * context,xxl_exception_t * exception)258 pop_asset_blocks(xxl_tsd_t *tsd, xxl_context_t *context, xxl_exception_t *exception)
259 {
260     static xxl_exception_t  null_exception = { 0, NULL, NULL, 0 };
261 
262     if (!exception)
263         exception = (context ? &context->exception : &null_exception);
264     while (tsd->contexts != context)
265     {
266         tsd->contexts->state |= (context->state & XXL_STATE_MASK);
267         tsd->contexts->exception = *exception;
268         xxl_pop_context();
269     }
270 }
271 
272 static xxl_context_t *
get_try_context(xxl_tsd_t * tsd)273 get_try_context(xxl_tsd_t *tsd)
274 {
275     xxl_context_t   *context;
276 
277     for (context = tsd->contexts;  context;  context = context->next)
278         if (context->context)
279             return context;
280     return NULL;
281 }
282 
283 xxl_context_t *
xxl_push_context(jmp_buf * context)284 xxl_push_context(jmp_buf *context)
285 {
286 #if !defined(WIN32) && !defined(XXL_WITHOUT_THREADS)
287     int             cancel_type;
288 #endif
289     xxl_tsd_t       *tsd;
290     xxl_context_t   *new_context;
291 
292 #if !defined(WIN32) && !defined(XXL_WITHOUT_THREADS)
293     pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &cancel_type);
294 #endif
295 
296     tsd = get_xxl_tsd();
297     new_context = alloc_context(tsd);
298 
299     new_context->context        = context;
300     new_context->state          = 0;
301     new_context->exception.code = new_context->pending.code = 0;
302     new_context->exception.data = new_context->pending.data = NULL;
303     new_context->exception.file = new_context->pending.file = NULL;
304     new_context->exception.line = new_context->pending.line = 0;
305 #if !defined(WIN32) && !defined(XXL_WITHOUT_THREADS)
306     new_context->cancel_type    = cancel_type;
307 #endif
308     new_context->assets         = NULL;
309     new_context->next           = tsd->contexts;
310     tsd->contexts               = new_context;
311 
312     return new_context;
313 }
314 
315 void
xxl_pop_context(void)316 xxl_pop_context(void)
317 {
318 #if !defined(WIN32) && !defined(XXL_WITHOUT_THREADS)
319     int             cancel_type;
320 #endif
321     xxl_tsd_t       *tsd;
322     xxl_context_t   *context;
323 
324     tsd = get_xxl_tsd();
325     context = tsd->contexts;
326     if (context->state & XXL_STATE_PENDING)
327         context->exception = context->pending;
328     pop_assets(tsd, context);
329 
330 #if !defined(WIN32) && !defined(XXL_WITHOUT_THREADS)
331     cancel_type = context->cancel_type;
332 #endif
333     tsd->contexts = context->next;
334     free_context(tsd, context);
335 #if !defined(WIN32) && !defined(XXL_WITHOUT_THREADS)
336     pthread_setcanceltype(cancel_type, NULL);
337 #endif
338 }
339 
340 void
xxl_pop_contexts(void)341 xxl_pop_contexts(void)
342 {
343     xxl_tsd_t   *tsd;
344 
345     tsd = get_xxl_tsd();
346     pop_asset_blocks(tsd, get_try_context(tsd), NULL);
347     xxl_pop_context();
348 }
349 
350 void
xxl_leave_handler(int how)351 xxl_leave_handler(int how)
352 {
353     xxl_tsd_t               *tsd;
354     xxl_context_t           *context;
355     xxl_exception_t         *exception;
356     static xxl_exception_t  null_exception = { 0, NULL, NULL, 0 };
357     static xxl_exception_t  retry_exception =
358                             { XXL_ERROR_RETRY_EXCEPTION, NULL, NULL, 0 };
359 
360     tsd = get_xxl_tsd();
361     if (!(context = get_try_context(tsd)))
362         die("XXL: Exception thrown with no handler to catch it!\n");
363 
364     if (how == XXL_SETJMP_PROMOTE && !(context->state & XXL_STATE_THROWN))
365         die("XXL: Cannot promote a non-existant exception!\n");
366 
367     exception = (how == XXL_SETJMP_RETRY ? &retry_exception : &context->exception);
368     pop_asset_blocks(tsd, context, exception);
369 
370     switch (how)
371     {
372         case XXL_SETJMP_RETRY:
373             tsd->contexts->exception = *exception;
374             pop_assets(tsd, tsd->contexts);
375             tsd->contexts->exception = null_exception;
376             break;
377 
378         case XXL_SETJMP_ERROR:
379             xxl_pop_context();
380             if (!tsd->contexts)
381                 die("XXL: Exception thrown with no handler to catch it!\n");
382             tsd->contexts->exception = *exception;
383 			how=XXL_SETJMP_TRY;
384 			tsd->contexts->state |= XXL_STATE_THROWN;
385             xxl_leave_handler(how);
386             return;
387     }
388     longjmp(*(tsd->contexts->context), how);
389 }
390 
391 void
xxl_throw_error(int code,void * data,const char * file,unsigned int line)392 xxl_throw_error(int code, void *data, const char *file, unsigned int line)
393 {
394     xxl_tsd_t       *tsd;
395     xxl_context_t   *context;
396 
397 #ifdef _DEBUG
398     fprintf(stderr, "XXL DEBUG: Exception 0x%08X (%d) thrown from %s line %u.\n",
399             code, code, file, line);
400 #endif
401 
402     tsd = get_xxl_tsd();
403     if (!(context = get_try_context(tsd)))
404         die("XXL: Exception thrown with no handler to catch it!\n");
405 
406     /* If we're inside of a catch or except block, set a pending exception and
407      * jump with XXL_SETJMP_PENDING so that the finally block will run if one
408      * is present.  We can tell if we're in a catch or except block by the
409      * current state being set to XXL_SETJMP_ERROR.
410      */
411     if ((context->state & XXL_SETJMP_MASK) == XXL_SETJMP_ERROR)
412     {
413         context->state |= (XXL_STATE_PENDING | XXL_STATE_THROWN);
414         context->pending.code = code;
415         context->pending.data = data;
416         context->pending.file = file;
417         context->pending.line = line;
418         pop_asset_blocks(tsd, context, NULL);
419         longjmp(*(context->context), XXL_SETJMP_PENDING);
420     }
421 
422     /* If the current state is XXL_SETJMP_PENDING, an exception was thrown
423      * from a catch or except block, and we must also be in a finally block
424      * now.  In this case, set the pending exception, pop the context stack,
425      * and deliver the exception.  We'll lose the exception that was thrown
426      * from the catch or except block.  This is the same behavior as Java in
427      * this pathological situation.
428      */
429     if ((context->state & XXL_SETJMP_MASK) == XXL_SETJMP_PENDING)
430     {
431         if (!(context->state & XXL_STATE_FINALLY))
432             die("XXL: Inconsistent state in xxl_throw_error()!\n");
433         context->state |= (XXL_STATE_PENDING | XXL_STATE_THROWN);
434         context->pending.code = code;
435         context->pending.data = data;
436         context->pending.file = file;
437         context->pending.line = line;
438         xxl_pop_contexts();
439         xxl_throw_error(code, data, file, line);
440     }
441 
442     /* The current state should be either XXL_SETJMP_TRY or XXL_SETJMP_RETRY.
443      * If it's neither, we've got an inconsistent state and we have to abort.
444      */
445     if ((context->state & XXL_SETJMP_MASK) == XXL_SETJMP_TRY ||
446         (context->state & XXL_SETJMP_MASK) == XXL_SETJMP_RETRY)
447     {
448         context->state |= XXL_STATE_THROWN;
449         context->exception.code = code;
450         context->exception.data = data;
451         context->exception.file = file;
452         context->exception.line = line;
453         pop_asset_blocks(tsd, context, NULL);
454         longjmp(*(context->context), XXL_SETJMP_ERROR);
455     }
456 
457     die("XXL: Inconsistent state in xxl_throw_error()!\n");
458 }
459 
460 int
xxl_current_error_code(void)461 xxl_current_error_code(void)
462 {
463     xxl_context_t   *context;
464 
465     context = get_try_context(get_xxl_tsd());
466     return (context ? context->exception.code : 0);
467 }
468 
469 void *
xxl_current_error_data(void)470 xxl_current_error_data(void)
471 {
472     xxl_context_t   *context;
473 
474     context = get_try_context(get_xxl_tsd());
475     return (context ? context->exception.data : NULL);
476 }
477 
478 const char *
xxl_current_error_file(void)479 xxl_current_error_file(void)
480 {
481     xxl_context_t   *context;
482 
483     context = get_try_context(get_xxl_tsd());
484     return (context ? context->exception.file : NULL);
485 }
486 
487 unsigned int
xxl_current_error_line(void)488 xxl_current_error_line(void)
489 {
490     xxl_context_t   *context;
491 
492     context = get_try_context(get_xxl_tsd());
493     return (context ? context->exception.line : 0);
494 }
495 
496 void
xxl_push_asset(void * ptr,xxl_assetfreefn_t freefn,void * arg,xxl_assettype_t type)497 xxl_push_asset(void *ptr, xxl_assetfreefn_t freefn, void *arg, xxl_assettype_t type)
498 {
499     xxl_tsd_t   *tsd;
500     xxl_asset_t *new_asset;
501 
502     if (type == XXL_ASSET_PERMANENT)
503         return;
504 
505     tsd = get_xxl_tsd();
506     if (!tsd->contexts)
507         die("XXL_: Attempt to push an asset outside of an asset block!\n");
508     new_asset = alloc_asset(tsd);
509 
510     new_asset->ptr        = ptr;
511     new_asset->freefn     = freefn;
512     new_asset->arg        = arg;
513     new_asset->type       = type;
514     new_asset->next       = tsd->contexts->assets;
515     tsd->contexts->assets = new_asset;
516 }
517 
518 void
xxl_update_asset(void * old_ptr,void * new_ptr)519 xxl_update_asset(void *old_ptr, void *new_ptr)
520 {
521     xxl_tsd_t       *tsd;
522     xxl_asset_t     *asset;
523     xxl_context_t   *context;
524 
525     tsd = get_xxl_tsd();
526     for (context = tsd->contexts;  context;  context = context->next)
527         for (asset = context->assets;  asset;  asset = asset->next)
528             if (asset->ptr == old_ptr)
529                 asset->ptr = new_ptr;
530 }
531 
532 void
xxl_release_asset(void * ptr,int mode)533 xxl_release_asset(void *ptr, int mode)
534 {
535     int             found = 0;
536     xxl_tsd_t       *tsd;
537     xxl_asset_t     *asset, *prev;
538     xxl_context_t   *context;
539 
540     tsd = get_xxl_tsd();
541     if (mode == XXL_ASSET_CURRENT)
542     {
543         prev = NULL;
544         for (asset = tsd->contexts->assets;  asset;  asset = asset->next)
545         {
546             if (asset->ptr == ptr)
547             {
548                 if (prev)
549                     prev->next = asset->next;
550                 else
551                     tsd->contexts->assets = asset->next;
552                 free_asset(tsd, asset);
553                 break;
554             }
555             prev = asset;
556         }
557         return;
558     }
559     for (context = tsd->contexts;  !found && context;  context = context->next)
560     {
561         prev = NULL;
562         for (asset = context->assets;  asset;  asset = asset->next)
563         {
564             if (asset->ptr == ptr)
565             {
566                 if (prev)
567                     prev->next = asset->next;
568                 else
569                     context->assets = asset->next;
570                 free_asset(tsd, asset);
571                 found = (mode == XXL_ASSET_FIRST);
572                 break;
573             }
574             prev = asset;
575         }
576     }
577 }
578