1 /***********************************************************************************************************************************
2 Error Handler
3 ***********************************************************************************************************************************/
4 #include "build.auto.h"
5
6 #include <assert.h>
7 #include <stdarg.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11
12 #include "common/error.h"
13 #include "common/stackTrace.h"
14
15 /***********************************************************************************************************************************
16 Represents an error type
17 ***********************************************************************************************************************************/
18 struct ErrorType
19 {
20 const int code;
21 const char *name;
22 const struct ErrorType *parentType;
23 };
24
25 // Macro for defining new error types
26 #define ERROR_DEFINE(code, name, parentType) \
27 const ErrorType name = {code, #name, &parentType}
28
29 // Define test error
30 #ifdef DEBUG
31 ERROR_DEFINE(1, TestError, RuntimeError);
32 #endif
33
34 // Include error type definitions
35 #include "common/error.auto.c"
36
37 /***********************************************************************************************************************************
38 Maximum allowed number of nested try blocks
39 ***********************************************************************************************************************************/
40 #define ERROR_TRY_MAX 64
41
42 /***********************************************************************************************************************************
43 States for each try
44 ***********************************************************************************************************************************/
45 typedef enum {errorStateBegin, errorStateTry, errorStateCatch, errorStateFinal, errorStateEnd} ErrorState;
46
47 /***********************************************************************************************************************************
48 Track error handling
49 ***********************************************************************************************************************************/
50 typedef struct Error
51 {
52 const ErrorType *errorType; // Error type
53 const char *fileName; // Source file where the error occurred
54 const char *functionName; // Function where the error occurred
55 int fileLine; // Source file line where the error occurred
56 const char *message; // Description of the error
57 const char *stackTrace; // Stack trace
58 } Error;
59
60 static struct
61 {
62 // Array of jump buffers
63 jmp_buf jumpList[ERROR_TRY_MAX];
64
65 // Handler list
66 const ErrorHandlerFunction *handlerList;
67 unsigned int handlerTotal;
68
69 // State of each try
70 int tryTotal;
71
72 struct
73 {
74 ErrorState state;
75 bool uncaught;
76 } tryList[ERROR_TRY_MAX + 1];
77
78 // Last error
79 Error error;
80 } errorContext;
81
82 /***********************************************************************************************************************************
83 Message buffer and buffer size
84
85 The message buffer is statically allocated so there is some space to store error messages. Not being able to allocate such a small
86 amount of memory seems pretty unlikely so just keep the code simple and let the loader deal with massively constrained memory
87 situations.
88
89 The temp buffer is required because the error message being passed might be the error already stored in the message buffer.
90 ***********************************************************************************************************************************/
91 #define ERROR_MESSAGE_BUFFER_SIZE 8192
92
93 static char messageBuffer[ERROR_MESSAGE_BUFFER_SIZE];
94 static char messageBufferTemp[ERROR_MESSAGE_BUFFER_SIZE];
95 static char stackTraceBuffer[ERROR_MESSAGE_BUFFER_SIZE];
96
97 /**********************************************************************************************************************************/
errorHandlerSet(ErrorHandlerFunction * list,unsigned int total)98 void errorHandlerSet(ErrorHandlerFunction *list, unsigned int total)
99 {
100 assert(total == 0 || list != NULL);
101
102 errorContext.handlerList = list;
103 errorContext.handlerTotal = total;
104 }
105
106 /**********************************************************************************************************************************/
107 int
errorTypeCode(const ErrorType * errorType)108 errorTypeCode(const ErrorType *errorType)
109 {
110 return errorType->code;
111 }
112
113 /**********************************************************************************************************************************/
114 const ErrorType *
errorTypeFromCode(int code)115 errorTypeFromCode(int code)
116 {
117 // Search for error type by code
118 int errorTypeIdx = 0;
119 const ErrorType *result = errorTypeList[errorTypeIdx];
120
121 while (result != NULL)
122 {
123 if (result->code == code)
124 break;
125
126 result = errorTypeList[++errorTypeIdx];
127 }
128
129 // Error if type was not found
130 if (result == NULL)
131 result = &UnknownError;
132
133 return result;
134 }
135
136 /**********************************************************************************************************************************/
137 const char *
errorTypeName(const ErrorType * errorType)138 errorTypeName(const ErrorType *errorType)
139 {
140 return errorType->name;
141 }
142
143 /**********************************************************************************************************************************/
144 const ErrorType *
errorTypeParent(const ErrorType * errorType)145 errorTypeParent(const ErrorType *errorType)
146 {
147 return errorType->parentType;
148 }
149
150 /**********************************************************************************************************************************/
errorTryDepth(void)151 unsigned int errorTryDepth(void)
152 {
153 return (unsigned int)errorContext.tryTotal;
154 }
155
156 /**********************************************************************************************************************************/
157 bool
errorTypeExtends(const ErrorType * child,const ErrorType * parent)158 errorTypeExtends(const ErrorType *child, const ErrorType *parent)
159 {
160 const ErrorType *find = child;
161
162 do
163 {
164 find = errorTypeParent(find);
165
166 // Parent was found
167 if (find == parent)
168 return true;
169 }
170 while (find != errorTypeParent(find));
171
172 // Parent was not found
173 return false;
174 }
175
176 /**********************************************************************************************************************************/
177 const ErrorType *
errorType(void)178 errorType(void)
179 {
180 assert(errorContext.error.errorType != NULL);
181
182 return errorContext.error.errorType;
183 }
184
185 /**********************************************************************************************************************************/
186 int
errorCode(void)187 errorCode(void)
188 {
189 return errorTypeCode(errorType());
190 }
191
192 /**********************************************************************************************************************************/
193 const char *
errorFileName(void)194 errorFileName(void)
195 {
196 assert(errorContext.error.fileName != NULL);
197
198 return errorContext.error.fileName;
199 }
200
201 /**********************************************************************************************************************************/
202 const char *
errorFunctionName(void)203 errorFunctionName(void)
204 {
205 assert(errorContext.error.functionName != NULL);
206
207 return errorContext.error.functionName;
208 }
209
210 /**********************************************************************************************************************************/
211 int
errorFileLine(void)212 errorFileLine(void)
213 {
214 assert(errorContext.error.fileLine != 0);
215
216 return errorContext.error.fileLine;
217 }
218
219 /**********************************************************************************************************************************/
220 const char *
errorMessage(void)221 errorMessage(void)
222 {
223 assert(errorContext.error.message != NULL);
224
225 return errorContext.error.message;
226 }
227
228 /**********************************************************************************************************************************/
229 const char *
errorName(void)230 errorName(void)
231 {
232 return errorTypeName(errorType());
233 }
234
235 /**********************************************************************************************************************************/
236 const char *
errorStackTrace(void)237 errorStackTrace(void)
238 {
239 assert(errorContext.error.stackTrace != NULL);
240
241 return errorContext.error.stackTrace;
242 }
243
244 /**********************************************************************************************************************************/
245 bool
errorInstanceOf(const ErrorType * errorTypeTest)246 errorInstanceOf(const ErrorType *errorTypeTest)
247 {
248 return errorType() == errorTypeTest || errorTypeExtends(errorType(), errorTypeTest);
249 }
250
251 /***********************************************************************************************************************************
252 Return current error context state
253 ***********************************************************************************************************************************/
254 static ErrorState
errorInternalState(void)255 errorInternalState(void)
256 {
257 return errorContext.tryList[errorContext.tryTotal].state;
258 }
259
260 /**********************************************************************************************************************************/
261 bool
errorInternalStateTry(void)262 errorInternalStateTry(void)
263 {
264 return errorInternalState() == errorStateTry;
265 }
266
267 /**********************************************************************************************************************************/
268 bool
errorInternalStateCatch(const ErrorType * errorTypeCatch)269 errorInternalStateCatch(const ErrorType *errorTypeCatch)
270 {
271 if (errorInternalState() == errorStateCatch && errorInstanceOf(errorTypeCatch))
272 return errorInternalProcess(true);
273
274 return false;
275 }
276
277 /**********************************************************************************************************************************/
278 bool
errorInternalStateFinal(void)279 errorInternalStateFinal(void)
280 {
281 return errorInternalState() == errorStateFinal;
282 }
283
284 /**********************************************************************************************************************************/
285 jmp_buf *
errorInternalJump(void)286 errorInternalJump(void)
287 {
288 return &errorContext.jumpList[errorContext.tryTotal - 1];
289 }
290
291 /**********************************************************************************************************************************/
292 bool
errorInternalTry(const char * fileName,const char * functionName,int fileLine)293 errorInternalTry(const char *fileName, const char *functionName, int fileLine)
294 {
295 // If try total has been exceeded then throw an error
296 if (errorContext.tryTotal >= ERROR_TRY_MAX)
297 errorInternalThrowFmt(&AssertError, fileName, functionName, fileLine, "too many nested try blocks");
298
299 // Increment try total
300 errorContext.tryTotal++;
301
302 // Setup try
303 errorContext.tryList[errorContext.tryTotal].state = errorStateBegin;
304 errorContext.tryList[errorContext.tryTotal].uncaught = false;
305
306 // Try setup was successful
307 return true;
308 }
309
310 /**********************************************************************************************************************************/
311 void
errorInternalPropagate(void)312 errorInternalPropagate(void)
313 {
314 assert(errorContext.error.errorType != NULL);
315
316 // Mark the error as uncaught
317 errorContext.tryList[errorContext.tryTotal].uncaught = true;
318
319 // If there is a parent try then jump to it
320 if (errorContext.tryTotal > 0)
321 longjmp(errorContext.jumpList[errorContext.tryTotal - 1], 1);
322
323 // If there was no try to catch this error then output to stderr
324 fprintf(stderr, "\nUncaught %s: %s\n thrown at %s:%d\n\n", errorName(), errorMessage(), errorFileName(), errorFileLine());
325 fflush(stderr);
326
327 // Exit with failure
328 exit(UnhandledError.code);
329 }
330
331 /**********************************************************************************************************************************/
332 bool
errorInternalProcess(bool catch)333 errorInternalProcess(bool catch)
334 {
335 // If a catch statement then return
336 if (catch)
337 {
338 errorContext.tryList[errorContext.tryTotal].uncaught = false;
339 return true;
340 }
341 // Else if just entering error state clean up the stack
342 else if (errorContext.tryList[errorContext.tryTotal].state == errorStateTry)
343 {
344 for (unsigned int handlerIdx = 0; handlerIdx < errorContext.handlerTotal; handlerIdx++)
345 errorContext.handlerList[handlerIdx](errorTryDepth());
346 }
347
348 // Any catch blocks have been processed and none of them called RETHROW() so clear the error
349 if (errorContext.tryList[errorContext.tryTotal].state == errorStateCatch &&
350 !errorContext.tryList[errorContext.tryTotal].uncaught)
351 {
352 errorContext.error = (Error){0};
353 }
354
355 // Increment the state
356 errorContext.tryList[errorContext.tryTotal].state++;
357
358 // If the error has been caught then increment the state
359 if (errorContext.tryList[errorContext.tryTotal].state == errorStateCatch &&
360 !errorContext.tryList[errorContext.tryTotal].uncaught)
361 {
362 errorContext.tryList[errorContext.tryTotal].state++;
363 }
364
365 // Return if not done
366 if (errorContext.tryList[errorContext.tryTotal].state < errorStateEnd)
367 return true;
368
369 // Remove the try
370 errorContext.tryTotal--;
371
372 // If not caught in the last try then propagate
373 if (errorContext.tryList[errorContext.tryTotal + 1].uncaught)
374 errorInternalPropagate();
375
376 // Nothing left to process
377 return false;
378 }
379
380 /**********************************************************************************************************************************/
381 void
errorInternalThrow(const ErrorType * errorType,const char * fileName,const char * functionName,int fileLine,const char * message)382 errorInternalThrow(const ErrorType *errorType, const char *fileName, const char *functionName, int fileLine, const char *message)
383 {
384 // Setup error data
385 errorContext.error.errorType = errorType;
386 errorContext.error.fileName = fileName;
387 errorContext.error.functionName = functionName;
388 errorContext.error.fileLine = fileLine;
389
390 // Assign message to the error
391 strncpy(messageBuffer, message, sizeof(messageBuffer));
392 messageBuffer[sizeof(messageBuffer) - 1] = 0;
393
394 errorContext.error.message = (const char *)messageBuffer;
395
396 // Generate the stack trace for the error
397 if (stackTraceToZ(
398 stackTraceBuffer, sizeof(stackTraceBuffer), fileName, functionName, (unsigned int)fileLine) >= sizeof(stackTraceBuffer))
399 {
400 // Indicate that the stack trace was truncated
401 }
402
403 errorContext.error.stackTrace = (const char *)stackTraceBuffer;
404
405 // Propagate the error
406 errorInternalPropagate();
407 }
408
409 void
errorInternalThrowFmt(const ErrorType * errorType,const char * fileName,const char * functionName,int fileLine,const char * format,...)410 errorInternalThrowFmt(
411 const ErrorType *errorType, const char *fileName, const char *functionName, int fileLine, const char *format, ...)
412 {
413 // Format message
414 va_list argument;
415 va_start(argument, format);
416 vsnprintf(messageBufferTemp, ERROR_MESSAGE_BUFFER_SIZE - 1, format, argument);
417 va_end(argument);
418
419 errorInternalThrow(errorType, fileName, functionName, fileLine, messageBufferTemp);
420 }
421
422 /**********************************************************************************************************************************/
423 void
errorInternalThrowSys(int errNo,const ErrorType * errorType,const char * fileName,const char * functionName,int fileLine,const char * message)424 errorInternalThrowSys(
425 int errNo, const ErrorType *errorType, const char *fileName, const char *functionName, int fileLine, const char *message)
426 {
427 // Format message with system message appended
428 if (errNo == 0)
429 {
430 strncpy(messageBufferTemp, message, ERROR_MESSAGE_BUFFER_SIZE - 1);
431 messageBufferTemp[sizeof(messageBuffer) - 1] = 0;
432 }
433 else
434 snprintf(messageBufferTemp, ERROR_MESSAGE_BUFFER_SIZE - 1, "%s: [%d] %s", message, errNo, strerror(errNo));
435
436 errorInternalThrow(errorType, fileName, functionName, fileLine, messageBufferTemp);
437 }
438
439 #ifdef DEBUG_COVERAGE
440 void
errorInternalThrowOnSys(bool error,int errNo,const ErrorType * errorType,const char * fileName,const char * functionName,int fileLine,const char * message)441 errorInternalThrowOnSys(
442 bool error, int errNo, const ErrorType *errorType, const char *fileName, const char *functionName, int fileLine,
443 const char *message)
444 {
445 if (error)
446 errorInternalThrowSys(errNo, errorType, fileName, functionName, fileLine, message);
447 }
448 #endif
449
450 void
errorInternalThrowSysFmt(int errNo,const ErrorType * errorType,const char * fileName,const char * functionName,int fileLine,const char * format,...)451 errorInternalThrowSysFmt(
452 int errNo, const ErrorType *errorType, const char *fileName, const char *functionName, int fileLine, const char *format, ...)
453 {
454 // Format message
455 va_list argument;
456 va_start(argument, format);
457 size_t messageSize = (size_t)vsnprintf(messageBufferTemp, ERROR_MESSAGE_BUFFER_SIZE - 1, format, argument);
458 va_end(argument);
459
460 // Append the system message
461 if (errNo != 0)
462 snprintf(messageBufferTemp + messageSize, ERROR_MESSAGE_BUFFER_SIZE - 1 - messageSize, ": [%d] %s", errNo, strerror(errNo));
463
464 errorInternalThrow(errorType, fileName, functionName, fileLine, messageBufferTemp);
465 }
466
467 #ifdef DEBUG_COVERAGE
468 void
errorInternalThrowOnSysFmt(bool error,int errNo,const ErrorType * errorType,const char * fileName,const char * functionName,int fileLine,const char * format,...)469 errorInternalThrowOnSysFmt(
470 bool error, int errNo, const ErrorType *errorType, const char *fileName, const char *functionName, int fileLine,
471 const char *format, ...)
472 {
473 if (error)
474 {
475 // Format message
476 va_list argument;
477 va_start(argument, format);
478 size_t messageSize = (size_t)vsnprintf(messageBufferTemp, ERROR_MESSAGE_BUFFER_SIZE - 1, format, argument);
479 va_end(argument);
480
481 // Append the system message
482 if (errNo != 0)
483 {
484 snprintf(
485 messageBufferTemp + messageSize, ERROR_MESSAGE_BUFFER_SIZE - 1 - messageSize, ": [%d] %s", errNo, strerror(errNo));
486 }
487
488 errorInternalThrow(errorType, fileName, functionName, fileLine, messageBufferTemp);
489 }
490 }
491 #endif
492