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