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 | |