-
Notifications
You must be signed in to change notification settings - Fork 0
/
sljex.c
504 lines (470 loc) · 18 KB
/
sljex.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
///@file
///@internal
/*
Copyright (C) 2023 MCRusher
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; version 2.1.
This library 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "sljex.h"
#include "vector.h"
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
//leverage C11 native support for thread local variables,
// should be faster than get/setspecific
#if __STDC_VERSION__ >= 201112L
#define pthread_key_t _Thread_local vector *
#define pthread_key_create(...) 0
#define pthread_getspecific(tlv) tlv
#define pthread_setspecific(tlv, val) (tlv = val, 0)
#define pthread_key_delete(...) (void)0
#endif
///takes a fmt string and variadics, prints to stderr and calls exit(EXIT_FAILURE)
#define panic(...) do{fprintf(stderr, __VA_ARGS__);exit(EXIT_FAILURE);}while(0)
//gcc gives an "error returning array from function"
// when returning jmp_buf, so void * is used instead
//this is fine since the jmp_buf is part of a heap
// allocated struct, and does not go out of scope.
typedef void * jmp_buf_ptr;
bool sljex_init(void);
void sljex_deinit(void);
jmp_buf_ptr sljex_trybuf_(void);
bool sljex_catch_(int excode);
bool sljex_catchany_(void);
jmp_buf_ptr sljex_throwbuf_(int excode, char const * exstr);
jmp_buf_ptr sljex_rethrowbuf_(void);
void sljex_finally_(void);
int sljex_excode(void);
char const * sljex_exstr(void);
static bool sljex_exstate_vinit(void * * statespace);
static void sljex_exstate_vdeinit(void * * statespace);
static bool vector_vinit(void * * vecspace);
static void vector_vdeinit(void * * vecspace);
///holds all the internal information of an exception
typedef struct sljex_exstate {
///holds the info that setjmp/longjmp uses
jmp_buf jb;
///stores the exception code
int excode;
///stores the exception message
char const * exstr;
///indicates whether the exception has already been caught
bool caught;
} sljex_exstate;
///holds a reference to a vector<exstate> for each thread,
/// allocated and given by global_local_vec_holder
static pthread_key_t tlvec;
///stores the vector<exstate> threadlocal values to destroy all at once
static vector global_local_vec_holder;
///global used to sync pushes to global_local_vec_holder
static pthread_mutex_t mtx;
/**
initialize the library without automatic atexit cleanup
@pre
this is called exactly once before using any library functions
@post
the library will be ready for use
@returns
false if initialization fails,
library is left in an uninitialized state
@note
does not store sljex_deinit on atexit stack,
sljex_deinit must be called manually before program exit,
and memory will leak to OS if program ends (or panics) before it is called.
@note
the library may be loaded by the user at runtime since
deinitialization is handled manually.
*/
bool sljex_initNoCleanup(void) {
if(pthread_mutex_init(&mtx, NULL)){
return false;
}else if(pthread_key_create(&tlvec, NULL)){
pthread_mutex_destroy(&mtx);
return false;
}else if(!vector_init(&global_local_vec_holder, vector_vinit, vector_vdeinit)){
pthread_mutex_destroy(&mtx);
pthread_key_delete(tlvec);
return false;
}
return true;
}
/**
initialize the library
@pre
this is called exactly once before
using any library functions
@post
the library will be ready for use
@returns
false if initialization fails,
library is left in an uninitialized state
@note
stores sljex_deinit on atexit stack, to automatically perform cleanup,
do not load this library at runtime with custom/dynamic or similar,
or at least do not close the library.
*/
bool sljex_init(void) {
if(sljex_initNoCleanup()){
atexit(sljex_deinit);
return true;
}
return false;
}
/**
deinitializes the library
@pre
the library has been initialized exactly once
@post
the library is fully deinitialized
@note
called automatically using atexit if initialized with sljex_init
and not sljex_initNoCleanup
*/
void sljex_deinit(void) {
vector_deinit(&global_local_vec_holder);
pthread_key_delete(tlvec);
pthread_mutex_destroy(&mtx);
}
/**
internal function used in the try macro,
not meant to be called directly
@pre
library has been initialized exactly once
@post
panics if mutex cannot be locked or thread local storage cannot be set,
otherwise a new exstate is pushed to the global stack,
and its jmp_buf member is returned as a reference.
@note
the library should be properly deinitialized even upon failure
*/
jmp_buf_ptr sljex_trybuf_(void) {
//get the current thread's exception stack
vector * local_vec = pthread_getspecific(tlvec);
//initialize the thread's exstate vector if it doesn't exist
if(local_vec == NULL){
if(pthread_mutex_lock(&mtx)){
panic("sljex: failed to lock mutex.\n");
}
//panic if adding a new default-initialized
// vector to the global stack fails
if(!vector_pushInit(&global_local_vec_holder)){
//cannot delete a mutex while locked
pthread_mutex_unlock(&mtx);//should be impossible to fail if lock succeeded
//calls sljex_deinit through exit, which deletes the mutex
panic("sljex: failed to allocate exception vector.\n");
}
//get a reference to the new vector instance
local_vec = vector_getLast(&global_local_vec_holder);
//panic if setting threadlocal storage to
// the new vector instance reference fails,
if(pthread_setspecific(tlvec, local_vec)){
//cannot delete a mutex while locked
pthread_mutex_unlock(&mtx);//should be impossible to fail if lock succeeded
//calls sljex_deinit through exit, which deletes the mutex
panic("sljex: failed to initalize threadlocal exception vector.\n");
}
pthread_mutex_unlock(&mtx);//should be impossible to fail if lock succeeded
}
//create a new default initialzed exstate instance,
// and panic if initialization fails
if(!vector_pushInit(local_vec)){
panic("sljex: failed to initalize threadlocal exception state.\n");
}
//obtain reference to newly created exstate instance
sljex_exstate * local_state = vector_getLast(local_vec);
//return a reference the the exstate instance's jump_buf member
return local_state->jb;
}
/**
internal function used in catch macro,
not meant to be called directly
@pre
the library has been initialized exactly once
@post
panics if called without an associated try statement
@returns
returns false if excode does not match the exception,
@note
will only ever be called when an exception has been thrown
by virture of the structure of the library macros
*/
bool sljex_catch_(int excode) {
//obtain a reference to the current thread's exception stack
vector * local_vec = pthread_getspecific(tlvec);
//stores reference to exception state being caught
sljex_exstate * local_state;
//Obtain a reference to the current exception state.
//If there are no exceptions on the stack
// or the current exception was caught already,
// then catch was called without a
// try statement and function will panic
if(vector_size(local_vec) == 0 || (local_state = vector_getLast(local_vec))->caught){
panic("sljex: catch without try.\n");
}
//return true if the thrown exception's excode
// matches the excode argument
if(local_state->excode == excode){
//sets the current exception's state to caught
// to avoid accidental recatching
local_state->caught = true;
return true;
}
//return false otherwise
return false;
}
/**
internal function used in the catchany macro,
not meant to be called directly
@pre
the library has been initialized exactly once,
@post
always returns true
@returns
calls panic if called without an associated try statement
@note
will only ever be called when an exception has been thrown
by virture of the structure of the library macros
*/
bool sljex_catchany_(void) {
//obtain a reference to the current thread's exception stack
vector * local_vec = pthread_getspecific(tlvec);
//stores reference to exception state being caught
sljex_exstate * local_state;
//Obtain a reference to the current exception state.
//If there are no exceptions on the stack
// or the current exception was caught already,
// then catch was called without a
// try statement and function will panic
if(vector_size(local_vec) == 0 || (local_state = vector_getLast(local_vec))->caught){
panic("sljex: catchany without try.\n");
}
//sets the current exception's state to caught
// to avoid accidental recatching
local_state->caught = true;
return true;
}
/**
internal function used in the throw and throwWithMsg macros,
not meant to be called directly
@pre
library has been initialized exactly once
@note
calls panic if called outside a try block,
intentional behavior that mimics C++'s exception handling, not a failure
@note
the library should be properly deinitialized when panic is called
*/
jmp_buf_ptr sljex_throwbuf_(int excode, char const * exstr) {
//obtain a reference to the current thread's exception stack
vector * local_vec = pthread_getspecific(tlvec);
//discards a previously caught exception
if(vector_size(local_vec) > 0 && ((sljex_exstate *)vector_getLast(local_vec))->caught){
vector_popDeinit(local_vec);
}
//if there is no valid exstate instance to assign to,
// then throw was called outside a catch block and is an
// unhandled exception, and the function panics
if(vector_size(local_vec) == 0){
panic("sljex_terminate: unhandled \"%s\"(%d) thrown.\n", exstr, excode);
}
//obtain a reference to the current exception state
sljex_exstate * local_state = vector_getLast(local_vec);
//assign exception info to exstate
local_state->excode = excode;
local_state->exstr = exstr;
//return a reference to the exstate's jmp_buf member
return local_state->jb;
}
/**
internal function used by the rethrow macro,
not meant to be called directly
@pre
library has been initialized exactly once
@post
panics if called outside a try block
@note
intentionally calls panic if called outside catch/catchany,
to mimics C++'s exception handling, not a failure.
@note
the library should be properly deinitialized when panic is called
*/
jmp_buf_ptr sljex_rethrowbuf_(void) {
//obtain a reference to the current thread's exception stack
vector * local_vec = pthread_getspecific(tlvec);
//stores reference to current caught, and then new uncaught exception.
sljex_exstate * local_state;
//stores current caught exception into local_state
//if there is no current caught exception to rethrow,
// rethrow was called outside catch/catchany,
// and the function panics to report a programmer error.
if(vector_size(local_vec) == 0 || !(local_state = (sljex_exstate *)vector_getLast(local_vec))->caught){
panic("sljex: rethrow outside catch/catchany.\n");
}
int const excode = local_state->excode;
char const * const exstr = local_state->exstr;
//delete current, caught exception (invalidates local_state)
vector_popDeinit(local_vec);
//if there is no valid exstate instance to assign to,
// then rethrow was called outside a catch block and is an
// unhandled exception, and the function panics
if(vector_size(local_vec) == 0){
panic("sljex_terminate: unhandled \"%s\"(%d) thrown.\n", exstr, excode);
}
//obtain a reference to the new current exception state
local_state = vector_getLast(local_vec);
//assign exception info to exstate
local_state->excode = excode;
local_state->exstr = exstr;
//return a reference to the exstate's jmp_buf member
return local_state->jb;
}
/**
cleans up exstate in the case that no exceptions were thrown,
and terminates program if there is an uncaught exception remaining
@pre
library has been initialized exactly once,
and finally block follows a try block
@post
cleans up exception state created by try
@note
intentionally calls panic if called with an active exception state (unvaught exception)
to mimics C++'s exception handling, not a failure.
*/
void sljex_finally_(void) {
//obtain a reference to the current thread's exception stack
vector * local_vec = pthread_getspecific(tlvec);
//the try & finally macros ensure there is no
// easy way to call try and finally unpaired,
// so the runtime check has been removed.
//stores reference to exception state being caught
sljex_exstate * local_state = vector_getLast(local_vec);
//if the current exstate excode is not 0 and is uncaught,
// it is an unhandled exception, and the function panics
if(local_state->excode != 0 && !local_state->caught){
panic(
"sljex_terminate: unhandled \"%s\"(%d) thrown.\n",
local_state->exstr, local_state->excode
);
}
//cleans up exstate created by try
vector_popDeinit(local_vec);
}
/**
gets the integer code of the current exception
@pre
library has been initialized exactly once,
and the function is called inside a catch/catchany block
@post
fails and calls panic if called outside catch/catchany
@returns
the integer code representing the exception type
*/
int sljex_excode(void) {
vector * local_vec = pthread_getspecific(tlvec);
//stores reference to exception state being caught
sljex_exstate * local_state;
//if there is no valid exstate instance to access,
// then sljex_excode was called outside a catch block
// and the function panics to report a programmer error
if(vector_size(local_vec) == 0 || !(local_state = vector_getLast(local_vec))->caught){
panic("sljex: sljex_excode outside catch/catchany.\n");
}
//return the excode of the current exception
return local_state->excode;
}
/**
gets the string message of the current exception
@pre
library has been initialized exactly once,
and the function is called inside a catch/catchany block
@post
fails and calls panic if called outside catch/catchany
@returns
the string value accompanying the exception type
@note
the message will be the stringized exception code unless
throwWithMsg is used
*/
char const * sljex_exstr(void) {
vector * local_vec = pthread_getspecific(tlvec);
//stores reference to exception state being caught
sljex_exstate * local_state;
//if there is no valid exstate instance to access,
// then sljex_excode was called outside a catch block
// and the function panics to report a programmer error
if(vector_size(local_vec) == 0 || !(local_state = vector_getLast(local_vec))->caught){
panic("sljex: sljex_exstr outside catch/catchany.\n");
}
//return the exstr of the current exception
return local_state->exstr;
}
/**
internal function passed to vector_init that allocates a new exstate in-place,
should not be called manually
@pre
statespace represents an unallocated exstate slot in a vector
@post
statespace is assigned a new valid, allocated exspace instance
@returns
false if fails to initialize, statespace is unchanged
*/
bool sljex_exstate_vinit(void * * statespace) {
//allocate memory for the new exstate
sljex_exstate * sp = malloc(sizeof(sljex_exstate));
if(sp == NULL){
return false;
}
//initialize members to show that exstate
// does not currently hold an exception.
sp->excode = 0;//excode 0 means not-an-exception
sp->caught = false;
//assign the new exstate to the reference
// and return indicating success
*statespace = sp;
return true;
}
/**
internal function passed to vector_init that deinitializes an exstate in-place,
should not be called manually
@pre
statespace is a valid, allocated exstate instance
@post
statespace's instance will be deallocated
*/
static void sljex_exstate_vdeinit(void * * statespace) {
free(*statespace);
}
/**
internal function passed to vector_init that allocates a new vector in-place,
should not be called manually
@pre
vecspace represents an unallocated vector slot in another vector
@post
vecspace is assigned a new valid, allocated vector instance
@returns
false if fails to initialize, statespace is unchanged
*/
static bool vector_vinit(void * * vecspace) {
//allocate memory for vector
vector * vp = malloc(sizeof(vector));
//if allocation or initialization fails,
// return indicating failure
if(vp == NULL || !vector_init(vp, sljex_exstate_vinit, sljex_exstate_vdeinit)){
return false;
}
*vecspace = vp;
return true;
}
/**
internal function passed to vector_init that deinitializes an vector instance in-place,
should not be called manually
@pre
vecspace is a valid, allocated vector instance
@post
vecspace's instance will be deinitialized and deallocated
*/
static void vector_vdeinit(void * * vecspace) {
vector_deinit(*vecspace);
free(*vecspace);
}