/* * Copyright (c) 2002-2023 Julien Nadeau Carriere * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Routines related to error handling. */ #include #include #include #include #ifdef __CC65__ # include #endif #if defined(_WIN32) && !defined(_XBOX) # undef SLIST_ENTRY # include #endif char *_Nullable agErrorMsg = NULL; /* Last error message (UTF-8) */ AG_ErrorCode agErrorCode = AG_EUNDEFINED; /* Last error code */ #ifdef AG_THREADS AG_ThreadKey agErrorMsgKey; /* Last error message (UTF-8; thread-local) */ AG_ThreadKey agErrorCodeKey; /* Last error code (thread-local) */ #endif int agDebugLvl = 1; /* Default debug level */ /* Redirect Verbose() output to "foo-out.txt" file */ /* #define VERBOSE_TO_FILE */ /* Redirect Debug() output to "foo-debug.txt" file */ /* #define DEBUG_TO_FILE */ /* Use AttachConsole() on Windows */ /* #define USE_WIN32_CONSOLE */ static void (*_Nullable agErrorCallback)(const char *_Nonnull) = NULL; static int (*_Nullable agVerboseCallback)(const char *_Nonnull) = NULL; static int (*_Nullable agDebugCallback)(const char *_Nonnull) = NULL; #ifdef __CC65__ int ag65consoleX = 0, ag65consoleY = 0; #endif /* Import inlinables */ #undef AG_INLINE_HEADER #include #ifdef AG_THREADS static void DestroyErrorMsg(void *_Nonnull msg) { free(msg); } static void DestroyErrorCode(void *_Nonnull code) { free(code); } #endif int AG_InitErrorSubsystem(void) { #ifdef AG_THREADS AG_ErrorCode *errCode; if (AG_ThreadKeyTryCreate(&agErrorMsgKey, DestroyErrorMsg) == -1 || AG_ThreadKeyTryCreate(&agErrorCodeKey, DestroyErrorCode) == -1) return (-1); AG_ThreadKeySet(agErrorMsgKey, Strdup("No error")); errCode = Malloc(sizeof(AG_ErrorCode)); *errCode = AG_EUNDEFINED; AG_ThreadKeySet(agErrorCodeKey, errCode); #else agErrorMsg = Strdup("No error"); agErrorCode = AG_EUNDEFINED; #endif #if defined(_WIN32) && defined(USE_WIN32_CONSOLE) AttachConsole(ATTACH_PARENT_PROCESS); #endif return (0); } void AG_DestroyErrorSubsystem(void) { #if defined(_WIN32) && defined(USE_WIN32_CONSOLE) FreeConsole(); #endif Free(agErrorMsg); agErrorMsg = NULL; agErrorCode = AG_EUNDEFINED; #ifdef AG_THREADS AG_ThreadKeyDelete(agErrorMsgKey); AG_ThreadKeyDelete(agErrorCodeKey); #endif } /* Set the error message string (C string). */ void AG_SetErrorS(const char *msg) { char *newMsg; if ((newMsg = TryStrdup(msg)) == NULL) return; #ifdef AG_THREADS if ((agErrorMsg = (char *)AG_ThreadKeyGet(agErrorMsgKey)) != NULL) { free(agErrorMsg); } AG_ThreadKeySet(agErrorMsgKey, newMsg); #else free(agErrorMsg); #endif agErrorMsg = newMsg; } /* Set the error message string. */ void AG_SetError(const char *fmt, ...) { va_list args; #ifdef AG_THREADS char *newMsg; va_start(args, fmt); Vasprintf(&newMsg, fmt, args); va_end(args); if ((agErrorMsg = (char *)AG_ThreadKeyGet(agErrorMsgKey)) != NULL) { free(agErrorMsg); } AG_ThreadKeySet(agErrorMsgKey, newMsg); agErrorMsg = newMsg; #else free(agErrorMsg); va_start(args, fmt); Vasprintf(&agErrorMsg, fmt, args); va_end(args); #endif } /* Retrieve the error message string. */ const char * AG_GetError(void) { #ifdef AG_THREADS return ((const char *)AG_ThreadKeyGet(agErrorMsgKey)); #else return ((const char *)agErrorMsg); #endif } const char * AG_Strerror(int error) { #if defined(_WIN32) && !defined(_XBOX) static char *str = NULL; char *p; if(str != NULL) { AG_Free(str); str = NULL; } FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&str, 0, NULL ); p = strchr(str, '\r'); if(p != NULL) { *p = '\0'; } return (const char *)str; #else return (const char *)strerror(error); #endif } /* Set the symbolic error code. */ void AG_SetErrorCode(AG_ErrorCode code) { #ifdef AG_THREADS AG_ErrorCode *codeLast, *codeNew; codeNew = Malloc(sizeof(AG_ErrorCode)); codeLast = (AG_ErrorCode *)AG_ThreadKeyGet(agErrorCodeKey); free(codeLast); *codeNew = code; AG_ThreadKeySet(agErrorCodeKey, (void *)codeNew); #else agErrorCode = code; #endif } /* Retrieve the symbolic error code. */ AG_ErrorCode AG_GetErrorCode(void) { #ifdef AG_THREADS { AG_ErrorCode *code = (AG_ErrorCode *)AG_ThreadKeyGet(agErrorCodeKey); return *code; } #else return (agErrorCode); #endif } /* * Print a debug message on the error console if agDebugLvl is >= 1. * If pObj is not NULL, prepend the name/address of the object. */ void AG_Debug(void *pObj, const char *fmt, ...) { #if defined(AG_DEBUG) || defined(__CC65__) AG_Object *obj = pObj; va_list args; # ifdef __CC65__ Uint8 colorSave, wScr,hScr; if (agDebugLvl < 1) return; va_start(args, fmt); colorSave = textcolor(COLOR_CYAN); gotoxy(ag65consoleX, ag65consoleY); if (obj != NULL) { fputs(obj->name, stdout); fputc(':', stdout); } vcprintf(fmt, args); textcolor(colorSave); va_end(args); screensize(&wScr, &hScr); if (++ag65consoleY > hScr) { clrscr(); ag65consoleY = 0; } # else /* !__CC65__ */ if (agDebugLvl < 1) { return; } if (agDebugCallback != NULL) { char *buf; if (obj != NULL) { size_t bufLen; if (obj->name[0] != '\0') { bufLen = 5+strlen(obj->name)+6+1; buf = Malloc(bufLen); Strlcpy(buf, AGSI_ITALIC, bufLen); Strlcat(buf, obj->name, bufLen); Strlcat(buf, AGSI_RST ": ", bufLen); } else { bufLen = 6+2+(AG_MODEL >> 2)+7+1; buf = Malloc(bufLen); Snprintf(buf, bufLen, "<%p>: ", obj); } agDebugCallback(buf); free(buf); } va_start(args, fmt); Vasprintf(&buf, fmt, args); va_end(args); if (agDebugCallback(buf) == 1) { free(buf); return; } free(buf); } va_start(args, fmt); # if defined(DEBUG_TO_FILE) /* Redirect output to foo-debug.txt */ { char path[AG_FILENAME_MAX]; FILE *f; if (agProgName != NULL) { Strlcpy(path, agProgName, sizeof(path)); Strlcat(path, "-debug.txt", sizeof(path)); } else { Strlcpy(path, "debug.txt", sizeof(path)); } if ((f = fopen(path, "a")) != NULL) { if (obj != NULL) { if (obj->name[0] != '\0') { fprintf(f, "%s: ", obj->name); } else { fprintf(f, "<%p>: ", obj); } } vfprintf(f, fmt, args); fclose(f); } } # elif defined(_WIN32) && defined(USE_WIN32_CONSOLE) { HANDLE cons; char *buf; cons = GetStdHandle(STD_ERROR_HANDLE); if (cons != NULL && cons != INVALID_HANDLE_VALUE) { if (obj != NULL && obj->name[0] != '\0') { WriteConsole(cons, obj->name, strlen(obj->name), NULL, NULL); WriteConsole(cons, ": ", 2, NULL, NULL); } Vasprintf(&buf, fmt, args); WriteConsole(cons, buf, strlen(buf), NULL, NULL); free(buf); } } # else /* !_WIN32 */ if (obj != NULL) { if (obj->name[0] != '\0') { printf(AGSI_ITALIC "%s" AGSI_RST ": ", obj->name); } else { printf("<" AGSI_ITALIC "%p" AGSI_RST ">: ", obj); } } vprintf(fmt, args); # endif va_end(args); # endif /* !__CC65__ */ #endif /* AG_DEBUG */ } #ifndef __CC65__ /* * Print a debug message on the error console if agDebugLvl is >= 2. * If pObj is not NULL, prepend the name/address of the object. */ void AG_Debug2(void *pObj, const char *fmt, ...) { va_list args; char *msg; if (agDebugLvl < 2) return; va_start(args, fmt); Vasprintf(&msg, fmt, args); va_end(args); AG_Debug(pObj, "%s", msg); free(msg); } #endif /* !__CC65__ */ /* Issue a verbose message. */ void AG_Verbose(const char *fmt, ...) { #if defined(AG_VERBOSITY) || defined(__CC65__) # ifdef __CC65__ va_list args; Uint8 colorSave, wScr,hScr; va_start(args, fmt); colorSave = textcolor(COLOR_WHITE); gotoxy(ag65consoleX, ag65consoleY); vcprintf(fmt, args); textcolor(colorSave); va_end(args); screensize(&wScr, &hScr); if (++ag65consoleY > hScr) { clrscr(); ag65consoleY = 0; } # else /* !__CC65__ */ va_list args; if (!agVerbose) return; if (agVerboseCallback != NULL) { char *buf; va_start(args, fmt); Vasprintf(&buf, fmt, args); va_end(args); if (agVerboseCallback(buf) == 1) { free(buf); return; } free(buf); } va_start(args, fmt); # if defined(VERBOSE_TO_FILE) /* Redirect output to foo-out.txt */ { char path[AG_FILENAME_MAX]; FILE *f; if (agProgName != NULL) { Strlcpy(path, agProgName, sizeof(path)); Strlcat(path, "-out.txt", sizeof(path)); } else { Strlcpy(path, "output.txt", sizeof(path)); } if ((f = fopen(path, "a")) != NULL) { vfprintf(f, fmt, args); fclose(f); } } # elif defined(_WIN32) && defined(USE_WIN32_CONSOLE) { HANDLE cons; char *buf; cons = GetStdHandle(STD_ERROR_HANDLE); if (cons != NULL && cons != INVALID_HANDLE_VALUE) { Vasprintf(&buf, fmt, args); WriteConsole(cons, buf, strlen(buf), NULL, NULL); free(buf); } } # else vprintf(fmt, args); # endif va_end(args); # endif /* !__CC65__ */ #endif /* AG_VERBOSITY */ } /* Raise a fatal error condition (format string). */ void AG_FatalErrorF(const char *fmt, ...) { va_list args; char *s; if (fmt == NULL) { AG_FatalError(NULL); } va_start(args, fmt); Vasprintf(&s, fmt, args); va_end(args); AG_FatalError(s); } /* Raise a fatal error condition. */ void AG_FatalError(const char *msg) { #ifdef __CC65__ textcolor(COLOR_LIGHTRED); #endif if (agErrorCallback != NULL) { agErrorCallback(msg ? msg : AG_GetError()); abort(); /* not reached */ } else { fputs("AG_FatalError: ", stderr); if (msg != NULL) { fputs(msg, stderr); } else { fputs(AG_GetError(), stderr); } fputc('\n', stderr); abort(); } } void AG_SetFatalCallback(void (*fn)(const char *)) { agErrorCallback = fn; } void AG_SetVerboseCallback(int (*fn)(const char *)) { agVerboseCallback = fn; } void AG_SetDebugCallback(int (*fn)(const char *)) { agDebugCallback = fn; } #ifdef AG_TYPE_SAFETY /* * Raise fatal error condition due to a runtime type checking error * (if compiled with either --enable-debug or --enable-type-safety). */ # ifdef AG_VERBOSITY void *AG_GenericMismatch(const char *s) { AG_FatalErrorF("Illegal access: %s", s); } # else void *AG_GenericMismatch(const char *s) { AG_FatalErrorV("E29", s); } # endif void *AG_PtrMismatch(void) { AG_FatalErrorV("E290", "Illegal AG_PTR() / AG_cPTR() access"); } char *AG_StringMismatch(void) { AG_FatalErrorV("E291", "Illegal AG_STRING() access"); } int AG_IntMismatch(void) { AG_FatalErrorV("E292", "Illegal AG_INT() / AG_UINT() access"); } long AG_LongMismatch(void) { AG_FatalErrorV("E293", "Illegal AG_LONG() / AG_ULONG() access"); } float AG_FloatMismatch(void) { AG_FatalErrorV("E294", "Illegal AG_FLOAT() access"); } double AG_DoubleMismatch(void) { AG_FatalErrorV("E295", "Illegal AG_DOUBLE() access"); } void *AG_ObjectMismatch(void) { AG_FatalErrorV("E296", "Illegal AG_OBJECT() / AG_cOBJECT() access"); } #endif /* AG_TYPE_SAFETY */