| 1 | /* |
| 2 | * This Source Code Form is subject to the terms of the Mozilla Public |
| 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
| 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| 5 | * |
| 6 | * Copyright 1997 - July 2008 CWI, August 2008 - 2019 MonetDB B.V. |
| 7 | */ |
| 8 | |
| 9 | /* |
| 10 | * (c) F. Groffen, M. Kersten |
| 11 | * For documentation see website |
| 12 | */ |
| 13 | #include "monetdb_config.h" |
| 14 | #include "mal_exception.h" |
| 15 | #include "mal_private.h" |
| 16 | |
| 17 | static char *exceptionNames[] = { |
| 18 | /* 0 */ "MALException" , |
| 19 | /* 1 */ "IllegalArgumentException" , |
| 20 | /* 2 */ "OutOfBoundsException" , |
| 21 | /* 3 */ "IOException" , |
| 22 | /* 4 */ "InvalidCredentialsException" , |
| 23 | /* 5 */ "OptimizerException" , |
| 24 | /* 6 */ "StackOverflowException" , |
| 25 | /* 7 */ "SyntaxException" , |
| 26 | /* 8 */ "TypeException" , |
| 27 | /* 9 */ "LoaderException" , |
| 28 | /*10 */ "ParseException" , |
| 29 | /*11 */ "ArithmeticException" , |
| 30 | /*12 */ "PermissionDeniedException" , |
| 31 | /*13 */ "SQLException" , |
| 32 | /*15 */ "Deprecated operation" , |
| 33 | /*EOE*/ NULL |
| 34 | }; |
| 35 | |
| 36 | int |
| 37 | isExceptionVariable(str nme){ |
| 38 | int i; |
| 39 | if( nme) |
| 40 | for(i=0; exceptionNames[i]; i++) |
| 41 | if( strcmp(exceptionNames[i],nme)==0) |
| 42 | return 1; |
| 43 | return 0; |
| 44 | } |
| 45 | |
| 46 | static char *M5OutOfMemory = MAL_MALLOC_FAIL; |
| 47 | |
| 48 | /** |
| 49 | * Internal helper function for createException and |
| 50 | * showException such that they share the same code, because reuse |
| 51 | * is good. |
| 52 | */ |
| 53 | static str createExceptionInternal(enum malexception type, const char *fcn, const char *format, va_list ap) |
| 54 | __attribute__((__format__(__printf__, 3, 0))) |
| 55 | __attribute__((__returns_nonnull__)); |
| 56 | static str |
| 57 | createExceptionInternal(enum malexception type, const char *fcn, const char *format, va_list ap) |
| 58 | { |
| 59 | char *message, local[GDKMAXERRLEN]; |
| 60 | int len; |
| 61 | // if there is an error we allow memory allocation once again |
| 62 | #ifndef NDEBUG |
| 63 | GDKsetmallocsuccesscount(-1); |
| 64 | #endif |
| 65 | message = GDKmalloc(GDKMAXERRLEN); |
| 66 | if (message == NULL){ |
| 67 | /* Leave a message behind in the logging system */ |
| 68 | len = snprintf(local, GDKMAXERRLEN, "%s:%s:" , exceptionNames[type], fcn); |
| 69 | len = vsnprintf(local + len, GDKMAXERRLEN, format, ap); |
| 70 | fprintf(stderr, "%s" , local); |
| 71 | return M5OutOfMemory; /* last resort */ |
| 72 | } |
| 73 | len = snprintf(message, GDKMAXERRLEN, "%s:%s:" , exceptionNames[type], fcn); |
| 74 | if (len >= GDKMAXERRLEN) /* shouldn't happen */ |
| 75 | return message; |
| 76 | len += vsnprintf(message + len, GDKMAXERRLEN - len, format, ap); |
| 77 | /* realloc to reduce amount of allocated memory (GDKMAXERRLEN is |
| 78 | * way more than what is normally needed) */ |
| 79 | if (len < GDKMAXERRLEN) { |
| 80 | /* in the extremely unlikely case that GDKrealloc fails, the |
| 81 | * original pointer is still valid, so use that and don't |
| 82 | * overwrite */ |
| 83 | char *newmsg = GDKrealloc(message, len + 1); |
| 84 | if (newmsg != NULL) |
| 85 | message = newmsg; |
| 86 | } |
| 87 | char *q = message; |
| 88 | for (char *p = strchr(q, '\n'); p; q = p + 1, p = strchr(q, '\n')) |
| 89 | fprintf(stderr, "#%s:!ERROR:%.*s\n" , MT_thread_getname(), (int) (p - q), q); |
| 90 | if (*q) |
| 91 | fprintf(stderr, "#%s:!ERROR:%s\n" , MT_thread_getname(), q); |
| 92 | return message; |
| 93 | } |
| 94 | |
| 95 | /** |
| 96 | * Returns an exception string for the given type of exception, function |
| 97 | * and additional formatting parameters. This function will crash the |
| 98 | * system or return bogus when the malexception enum is not aligned with |
| 99 | * the exceptionNames array. |
| 100 | */ |
| 101 | str |
| 102 | createException(enum malexception type, const char *fcn, const char *format, ...) |
| 103 | { |
| 104 | va_list ap; |
| 105 | str ret; |
| 106 | |
| 107 | if (GDKerrbuf && |
| 108 | (ret = strstr(format, MAL_MALLOC_FAIL)) != NULL && |
| 109 | ret[strlen(MAL_MALLOC_FAIL)] != ':' && |
| 110 | (strncmp(GDKerrbuf, "GDKmalloc" , 9) == 0 || |
| 111 | strncmp(GDKerrbuf, "GDKrealloc" , 10) == 0 || |
| 112 | strncmp(GDKerrbuf, "GDKzalloc" , 9) == 0 || |
| 113 | strncmp(GDKerrbuf, "GDKstrdup" , 9) == 0 || |
| 114 | strncmp(GDKerrbuf, "allocating too much virtual address space" , 41) == 0)) { |
| 115 | /* override errors when the underlying error is memory |
| 116 | * exhaustion, but include whatever it is that the GDK level |
| 117 | * reported */ |
| 118 | ret = createException(type, fcn, SQLSTATE(HY001) MAL_MALLOC_FAIL ": %s" , GDKerrbuf); |
| 119 | GDKclrerr(); |
| 120 | return ret; |
| 121 | } |
| 122 | if (strcmp(format, GDK_EXCEPTION) == 0 && GDKerrbuf[0]) { |
| 123 | /* for GDK errors, report the underlying error */ |
| 124 | char *p = GDKerrbuf; |
| 125 | if (strncmp(p, GDKERROR, strlen(GDKERROR)) == 0) |
| 126 | p += strlen(GDKERROR); |
| 127 | if (strlen(p) > 6 && p[5] == '!') |
| 128 | ret = createException(type, fcn, "%s" , p); |
| 129 | else |
| 130 | ret = createException(type, fcn, "GDK reported error: %s" , p); |
| 131 | GDKclrerr(); |
| 132 | return ret; |
| 133 | } |
| 134 | va_start(ap, format); |
| 135 | ret = createExceptionInternal(type, fcn, format, ap); |
| 136 | va_end(ap); |
| 137 | GDKclrerr(); |
| 138 | |
| 139 | return ret; |
| 140 | } |
| 141 | |
| 142 | void |
| 143 | freeException(str msg) |
| 144 | { |
| 145 | if (msg != MAL_SUCCEED && msg != M5OutOfMemory) |
| 146 | GDKfree(msg); |
| 147 | } |
| 148 | |
| 149 | |
| 150 | /** |
| 151 | * Internal helper function for createMalException and |
| 152 | * showScriptException such that they share the same code, because reuse |
| 153 | * is good. |
| 154 | */ |
| 155 | static str |
| 156 | createMalExceptionInternal(MalBlkPtr mb, int pc, enum malexception type, char *prev, const char *format, va_list ap) |
| 157 | __attribute__((__format__(__printf__, 5, 0))) |
| 158 | __attribute__((__returns_nonnull__)); |
| 159 | static str |
| 160 | createMalExceptionInternal(MalBlkPtr mb, int pc, enum malexception type, char *prev, const char *format, va_list ap) |
| 161 | { |
| 162 | char buf[GDKMAXERRLEN]; |
| 163 | size_t i; |
| 164 | str s, fcn; |
| 165 | |
| 166 | s = mb ? getModName(mb) : "unknown" ; |
| 167 | fcn = mb ? getFcnName(mb) : "unknown" ; |
| 168 | i = 0; |
| 169 | |
| 170 | if (prev){ |
| 171 | if( *prev){ |
| 172 | i += snprintf(buf + i, GDKMAXERRLEN - 1 - i, "%s" , prev); |
| 173 | if( buf[i-1] != '\n') |
| 174 | buf[i++]= '\n'; |
| 175 | } |
| 176 | i += snprintf(buf + i, GDKMAXERRLEN - 1 - i, "!%s:%s.%s[%d]:" , |
| 177 | exceptionNames[type], s, fcn, pc); |
| 178 | freeException(prev); |
| 179 | } else if( type == SYNTAX) |
| 180 | i += snprintf(buf + i, GDKMAXERRLEN - 1 - i, "%s:" , |
| 181 | exceptionNames[type]); |
| 182 | else |
| 183 | i += snprintf(buf + i, GDKMAXERRLEN - 1 - i, "%s:%s.%s[%d]:" , |
| 184 | exceptionNames[type], s, fcn, pc); |
| 185 | i += vsnprintf(buf + i, GDKMAXERRLEN - 1 - i, format, ap); |
| 186 | if( buf[i-1] != '\n') |
| 187 | buf[i++]= '\n'; |
| 188 | buf[i] = '\0'; |
| 189 | |
| 190 | s = GDKstrdup(buf); |
| 191 | if (s == NULL) /* make sure we always return something */ |
| 192 | s = M5OutOfMemory; |
| 193 | return s; |
| 194 | } |
| 195 | |
| 196 | /** |
| 197 | * Returns an exception string for the MAL instructions. These |
| 198 | * exceptions are newline terminated, and determine module and function |
| 199 | * from the given MalBlkPtr. An old exception can be given, such that |
| 200 | * this exception is chained to the previous one. Conceptually this |
| 201 | * creates a "stack" of exceptions. |
| 202 | * This function will crash the system or return bogus when the |
| 203 | * malexception enum is not aligned with the exceptionNames array. |
| 204 | */ |
| 205 | str |
| 206 | createMalException(MalBlkPtr mb, int pc, enum malexception type, const char *format, ...) |
| 207 | { |
| 208 | va_list ap; |
| 209 | str ret; |
| 210 | |
| 211 | va_start(ap, format); |
| 212 | ret = createMalExceptionInternal(mb, pc, type, mb->errors, format, ap); |
| 213 | va_end(ap); |
| 214 | |
| 215 | return(ret); |
| 216 | } |
| 217 | |
| 218 | /** |
| 219 | * Returns the malexception number for the given exception string. If no |
| 220 | * exception could be found in the string, MAL is returned indicating a |
| 221 | * generic MALException. |
| 222 | */ |
| 223 | enum malexception |
| 224 | getExceptionType(const char *exception) |
| 225 | { |
| 226 | enum malexception ret = MAL; |
| 227 | const char *s; |
| 228 | size_t len; |
| 229 | enum malexception i; |
| 230 | |
| 231 | if ((s = strchr(exception, ':')) != NULL) |
| 232 | len = s - exception; |
| 233 | else |
| 234 | len = strlen(exception); |
| 235 | |
| 236 | for (i = MAL; exceptionNames[i] != NULL; i++) { |
| 237 | if (strncmp(exceptionNames[i], exception, len) == 0 && |
| 238 | exceptionNames[i][len] == '\0') { |
| 239 | ret = i; |
| 240 | break; |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | return(ret); |
| 245 | } |
| 246 | |
| 247 | /** |
| 248 | * Returns the location the exception was raised, if known. It |
| 249 | * depends on how the exception was created, what the location looks |
| 250 | * like. The returned string is mallocced with GDKmalloc, and hence |
| 251 | * needs to be GDKfreed. |
| 252 | */ |
| 253 | str |
| 254 | getExceptionPlace(const char *exception) |
| 255 | { |
| 256 | str ret; |
| 257 | const char *s, *t; |
| 258 | enum malexception i; |
| 259 | size_t l; |
| 260 | |
| 261 | for (i = MAL; exceptionNames[i] != NULL; i++) { |
| 262 | l = strlen(exceptionNames[i]); |
| 263 | if (strncmp(exceptionNames[i], exception, l) == 0 && |
| 264 | exception[l] == ':') { |
| 265 | s = exception + l + 1; |
| 266 | if ((t = strchr(s, ':')) != NULL) { |
| 267 | if ((ret = GDKmalloc(t - s + 1)) == NULL) |
| 268 | return NULL; |
| 269 | strcpy_len(ret, s, t - s + 1); |
| 270 | return ret; |
| 271 | } |
| 272 | break; |
| 273 | } |
| 274 | } |
| 275 | return GDKstrdup("(unknown)" ); |
| 276 | } |
| 277 | |
| 278 | /** |
| 279 | * Returns the informational message of the exception given. |
| 280 | */ |
| 281 | str |
| 282 | getExceptionMessageAndState(const char *exception) |
| 283 | { |
| 284 | const char *s, *t; |
| 285 | enum malexception i; |
| 286 | size_t l; |
| 287 | |
| 288 | for (i = MAL; exceptionNames[i] != NULL; i++) { |
| 289 | l = strlen(exceptionNames[i]); |
| 290 | if (strncmp(exceptionNames[i], exception, l) == 0 && |
| 291 | exception[l] == ':') { |
| 292 | s = exception + l + 1; |
| 293 | if ((t = strpbrk(s, ":\n" )) != NULL && *t == ':') |
| 294 | return (str) (t + 1); |
| 295 | return (str) s; |
| 296 | } |
| 297 | } |
| 298 | if (strncmp(exception, "!ERROR: " , 8) == 0) |
| 299 | return (str) (exception + 8); |
| 300 | return (str) exception; |
| 301 | } |
| 302 | |
| 303 | str |
| 304 | getExceptionMessage(const char *exception) |
| 305 | { |
| 306 | char *msg = getExceptionMessageAndState(exception); |
| 307 | |
| 308 | if (strlen(msg) > 6 && msg[5] == '!' && |
| 309 | (isdigit((unsigned char) msg[0]) || |
| 310 | (msg[0] >= 'A' && msg[0] <= 'Z')) && |
| 311 | (isdigit((unsigned char) msg[1]) || |
| 312 | (msg[1] >= 'A' && msg[1] <= 'Z')) && |
| 313 | (isdigit((unsigned char) msg[2]) || |
| 314 | (msg[2] >= 'A' && msg[2] <= 'Z')) && |
| 315 | (isdigit((unsigned char) msg[3]) || |
| 316 | (msg[3] >= 'A' && msg[3] <= 'Z')) && |
| 317 | (isdigit((unsigned char) msg[4]) || |
| 318 | (msg[4] >= 'A' && msg[4] <= 'Z'))) |
| 319 | msg += 6; |
| 320 | return msg; |
| 321 | } |
| 322 | |