/home/travis/build/MoarVM/MoarVM/src/core/exceptions.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "moar.h" |
2 | | |
3 | | #ifdef _MSC_VER |
4 | | #define snprintf _snprintf |
5 | | #define vsnprintf _vsnprintf |
6 | | #endif |
7 | | |
8 | | static int crash_on_error = 0; |
9 | | |
10 | | /* Maps ID of exception category to its name. */ |
11 | 0 | static const char * cat_name(MVMThreadContext *tc, MVMint32 cat) { |
12 | 0 | switch (cat) { |
13 | 0 | case MVM_EX_CAT_CATCH: |
14 | 0 | return "catch"; |
15 | 0 | case MVM_EX_CAT_CONTROL: |
16 | 0 | return "control"; |
17 | 0 | case MVM_EX_CAT_NEXT: |
18 | 0 | return "next"; |
19 | 0 | case MVM_EX_CAT_REDO: |
20 | 0 | return "redo"; |
21 | 0 | case MVM_EX_CAT_LAST: |
22 | 0 | return "last"; |
23 | 0 | case MVM_EX_CAT_RETURN: |
24 | 0 | return "return"; |
25 | 0 | case MVM_EX_CAT_TAKE: |
26 | 0 | return "take"; |
27 | 0 | case MVM_EX_CAT_WARN: |
28 | 0 | return "warn"; |
29 | 0 | case MVM_EX_CAT_SUCCEED: |
30 | 0 | return "succeed"; |
31 | 0 | case MVM_EX_CAT_PROCEED: |
32 | 0 | return "proceed"; |
33 | 0 | case MVM_EX_CAT_NEXT | MVM_EX_CAT_LABELED: |
34 | 0 | return "next_label"; |
35 | 0 | case MVM_EX_CAT_REDO | MVM_EX_CAT_LABELED: |
36 | 0 | return "redo_label"; |
37 | 0 | case MVM_EX_CAT_LAST | MVM_EX_CAT_LABELED: |
38 | 0 | return "last_label"; |
39 | 0 | default: |
40 | 0 | return "unknown"; |
41 | 0 | } |
42 | 0 | } |
43 | | |
44 | | /* Checks if an exception handler is already on the active handler stack, |
45 | | * so we don't re-trigger the same exception handler. Note: We have static |
46 | | * handlers that get reused, so also check for the same handler being in |
47 | | * the same frame, otherwise we consider the handler as being another one. */ |
48 | 415k | static MVMuint8 in_handler_stack(MVMThreadContext *tc, MVMFrameHandler *fh, MVMFrame *f) { |
49 | 415k | if (tc->active_handlers) { |
50 | 9 | MVMActiveHandler *ah = tc->active_handlers; |
51 | 19 | while (ah) { |
52 | 16 | if (ah->handler == fh && ah->frame == f) |
53 | 6 | return 1; |
54 | 10 | ah = ah->next_handler; |
55 | 10 | } |
56 | 9 | } |
57 | 415k | return 0; |
58 | 415k | } |
59 | | |
60 | | /* Checks if a frame is still active. Naively, we could scan the call stack |
61 | | * for it, but since we always clean up ->work when a frame is removed from |
62 | | * the call stack we can do it in O(1) that way. */ |
63 | 273k | static MVMuint8 in_caller_chain(MVMThreadContext *tc, MVMFrame *f_maybe) { |
64 | 273k | return f_maybe->work ? 1 : 0; |
65 | 273k | } |
66 | | |
67 | | |
68 | | /* Information about a located handler. */ |
69 | | typedef struct { |
70 | | MVMFrame *frame; |
71 | | MVMFrameHandler *handler; |
72 | | MVMJitHandler *jit_handler; |
73 | | MVMint32 handler_out_of_dynamic_scope; |
74 | | } LocatedHandler; |
75 | | |
76 | 1.33M | static MVMint32 handler_can_handle(MVMFrame *f, MVMFrameHandler *fh, MVMint32 cat, MVMObject *payload) { |
77 | 1.33M | MVMuint32 category_mask = fh->category_mask; |
78 | 1.33M | MVMuint64 block_has_label = category_mask & MVM_EX_CAT_LABELED; |
79 | 1.33M | MVMuint64 block_label = block_has_label ? (MVMuint64)(f->work[fh->label_reg].o) : 0; |
80 | 1.33M | MVMuint64 thrown_label = payload ? (MVMuint64)payload : 0; |
81 | 1.33M | MVMuint64 identical_label_found = thrown_label == block_label; |
82 | 595k | return ((cat & category_mask) == cat && (!(cat & MVM_EX_CAT_LABELED) || identical_label_found)) |
83 | 739k | || ((category_mask & MVM_EX_CAT_CONTROL) && cat != MVM_EX_CAT_CATCH); |
84 | 1.33M | } |
85 | | |
86 | | /* Looks through the handlers of a particular scope, and sees if one will |
87 | | * match what we're looking for. Returns 1 to it if so; if not, |
88 | | * returns 0. */ |
89 | | static MVMint32 search_frame_handlers(MVMThreadContext *tc, MVMFrame *f, |
90 | | MVMuint8 mode, MVMuint32 cat, |
91 | 419k | MVMObject *payload, LocatedHandler *lh) { |
92 | 419k | MVMuint32 i; |
93 | 419k | if (f->spesh_cand && f->spesh_cand->jitcode && f->jit_entry_label) { |
94 | 1.56k | MVMJitHandler *jhs = f->spesh_cand->jitcode->handlers; |
95 | 1.56k | MVMFrameHandler *fhs = f->effective_handlers; |
96 | 1.56k | MVMint32 num_handlers = f->spesh_cand->jitcode->num_handlers; |
97 | 1.56k | void **labels = f->spesh_cand->jitcode->labels; |
98 | 1.56k | void *cur_label = f->jit_entry_label; |
99 | 1.58k | for (i = 0; i < num_handlers; i++) { |
100 | 21 | if (mode == MVM_EX_THROW_LEX && fhs[i].inlined_and_not_lexical) |
101 | 0 | continue; |
102 | 21 | if (!handler_can_handle(f, &fhs[i], cat, payload)) |
103 | 21 | continue; |
104 | 0 | if (cur_label >= labels[jhs[i].start_label] && |
105 | 0 | cur_label <= labels[jhs[i].end_label] && |
106 | 0 | !in_handler_stack(tc, &fhs[i], f)) { |
107 | 0 | lh->handler = &fhs[i]; |
108 | 0 | lh->jit_handler = &jhs[i]; |
109 | 0 | return 1; |
110 | 0 | } |
111 | 0 | } |
112 | 417k | } else { |
113 | 417k | MVMint32 num_handlers = f->spesh_cand |
114 | 141k | ? f->spesh_cand->num_handlers |
115 | 275k | : f->static_info->body.num_handlers; |
116 | 417k | MVMint32 pc; |
117 | 417k | if (f == tc->cur_frame) |
118 | 414k | pc = (MVMuint32)(*tc->interp_cur_op - *tc->interp_bytecode_start); |
119 | 417k | else |
120 | 3.09k | pc = (MVMuint32)(f->return_address - f->effective_bytecode); |
121 | 1.33M | for (i = 0; i < num_handlers; i++) { |
122 | 1.33M | MVMFrameHandler *fh = &f->effective_handlers[i]; |
123 | 1.33M | if (mode == MVM_EX_THROW_LEX && fh->inlined_and_not_lexical) |
124 | 0 | continue; |
125 | 1.33M | if (!handler_can_handle(f, fh, cat, payload)) |
126 | 739k | continue; |
127 | 595k | if (pc >= fh->start_offset && pc <= fh->end_offset && !in_handler_stack(tc, fh, f)) { |
128 | 415k | lh->handler = fh; |
129 | 415k | return 1; |
130 | 415k | } |
131 | 595k | } |
132 | 417k | } |
133 | 3.40k | return 0; |
134 | 419k | } |
135 | | |
136 | | /* Searches for a handler of the specified category, relative to the given |
137 | | * starting frame, searching according to the chosen mode. */ |
138 | | static LocatedHandler search_for_handler_from(MVMThreadContext *tc, MVMFrame *f, |
139 | 415k | MVMuint8 mode, MVMuint32 cat, MVMObject *payload) { |
140 | 415k | LocatedHandler lh; |
141 | 415k | lh.frame = NULL; |
142 | 415k | lh.handler = NULL; |
143 | 415k | lh.jit_handler = NULL; |
144 | 415k | lh.handler_out_of_dynamic_scope = 0; |
145 | 415k | switch (mode) { |
146 | 0 | case MVM_EX_THROW_LEX_CALLER: |
147 | 0 | f = f->caller; |
148 | 0 | while (f && f->static_info->body.is_thunk) |
149 | 0 | f = f->caller; |
150 | 0 | /* And now we've gone down a caller, it's just lexical... */ |
151 | 273k | case MVM_EX_THROW_LEX: |
152 | 273k | while (f != NULL) { |
153 | 273k | if (search_frame_handlers(tc, f, MVM_EX_THROW_LEX, cat, payload, &lh)) { |
154 | 273k | if (in_caller_chain(tc, f)) |
155 | 273k | lh.frame = f; |
156 | 273k | else |
157 | 0 | lh.handler_out_of_dynamic_scope = 1; |
158 | 273k | return lh; |
159 | 273k | } |
160 | 0 | f = f->outer; |
161 | 0 | } |
162 | 0 | return lh; |
163 | 142k | case MVM_EX_THROW_DYN: |
164 | 145k | while (f != NULL) { |
165 | 145k | if (search_frame_handlers(tc, f, mode, cat, payload, &lh)) { |
166 | 142k | lh.frame = f; |
167 | 142k | return lh; |
168 | 142k | } |
169 | 3.40k | f = f->caller; |
170 | 3.40k | } |
171 | 0 | return lh; |
172 | 0 | case MVM_EX_THROW_LEXOTIC: |
173 | 0 | while (f != NULL) { |
174 | 0 | lh = search_for_handler_from(tc, f, MVM_EX_THROW_LEX, cat, payload); |
175 | 0 | if (lh.frame != NULL) |
176 | 0 | return lh; |
177 | 0 | f = f->caller; |
178 | 0 | } |
179 | 0 | return lh; |
180 | 0 | default: |
181 | 0 | MVM_panic(1, "Unhandled exception throw mode %d", (int)mode); |
182 | 415k | } |
183 | 415k | } |
184 | | |
185 | | /* Runs an exception handler (which really means updating interpreter state |
186 | | * so that when we return to the runloop, we're in the handler). If there is |
187 | | * an exception object already, it will be used; NULL can be passed if there |
188 | | * is not one, meaning it will be created if needed (based on the category |
189 | | * parameter; if ex_obj is passed, the category is not used). */ |
190 | | static void unwind_after_handler(MVMThreadContext *tc, void *sr_data); |
191 | | static void cleanup_active_handler(MVMThreadContext *tc, void *sr_data); |
192 | | static void run_handler(MVMThreadContext *tc, LocatedHandler lh, MVMObject *ex_obj, |
193 | 415k | MVMuint32 category, MVMObject *payload) { |
194 | 415k | switch (lh.handler->action) { |
195 | 273k | case MVM_EX_ACTION_GOTO_WITH_PAYLOAD: |
196 | 273k | if (payload) |
197 | 273k | tc->last_payload = payload; |
198 | 0 | else if (ex_obj && ((MVMException *)ex_obj)->body.payload) |
199 | 0 | tc->last_payload = ((MVMException *)ex_obj)->body.payload; |
200 | 0 | else |
201 | 0 | tc->last_payload = tc->instance->VMNull; |
202 | 273k | /* Deliberate fallthrough to unwind below. */ |
203 | 273k | |
204 | 415k | case MVM_EX_ACTION_GOTO: |
205 | 415k | if (lh.jit_handler) { |
206 | 0 | void **labels = lh.frame->spesh_cand->jitcode->labels; |
207 | 0 | MVMuint8 *pc = lh.frame->spesh_cand->jitcode->bytecode; |
208 | 0 | lh.frame->jit_entry_label = labels[lh.jit_handler->goto_label]; |
209 | 0 | MVM_frame_unwind_to(tc, lh.frame, pc, 0, NULL); |
210 | 415k | } else { |
211 | 415k | MVM_frame_unwind_to(tc, lh.frame, NULL, lh.handler->goto_offset, NULL); |
212 | 415k | } |
213 | 415k | break; |
214 | 273k | |
215 | 288 | case MVM_EX_ACTION_INVOKE: { |
216 | 288 | /* Create active handler record. */ |
217 | 288 | MVMActiveHandler *ah = MVM_malloc(sizeof(MVMActiveHandler)); |
218 | 288 | MVMFrame *cur_frame = tc->cur_frame; |
219 | 288 | MVMObject *handler_code; |
220 | 288 | |
221 | 288 | /* Ensure we have an exception object. */ |
222 | 288 | if (ex_obj == NULL) { |
223 | 1 | MVMROOT(tc, cur_frame, { |
224 | 1 | MVMROOT(tc, lh.frame, { |
225 | 1 | MVMROOT(tc, payload, { |
226 | 1 | ex_obj = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTException); |
227 | 1 | }); |
228 | 1 | }); |
229 | 1 | }); |
230 | 1 | ((MVMException *)ex_obj)->body.category = category; |
231 | 1 | MVM_ASSIGN_REF(tc, &(ex_obj->header), ((MVMException *)ex_obj)->body.payload, payload); |
232 | 1 | } |
233 | 288 | |
234 | 288 | /* Find frame to invoke. */ |
235 | 288 | handler_code = MVM_frame_find_invokee(tc, lh.frame->work[lh.handler->block_reg].o, NULL); |
236 | 288 | |
237 | 288 | /* Install active handler record. */ |
238 | 288 | ah->frame = lh.frame; |
239 | 288 | ah->handler = lh.handler; |
240 | 288 | ah->jit_handler = lh.jit_handler; |
241 | 288 | ah->ex_obj = ex_obj; |
242 | 288 | ah->next_handler = tc->active_handlers; |
243 | 288 | tc->active_handlers = ah; |
244 | 288 | |
245 | 288 | /* Set up special return to unwinding after running the |
246 | 288 | * handler. */ |
247 | 288 | cur_frame->return_value = (MVMRegister *)&tc->last_handler_result; |
248 | 288 | cur_frame->return_type = MVM_RETURN_OBJ; |
249 | 288 | cur_frame->special_return = unwind_after_handler; |
250 | 288 | cur_frame->special_unwind = cleanup_active_handler; |
251 | 288 | cur_frame->special_return_data = ah; |
252 | 288 | |
253 | 288 | /* Invoke the handler frame and return to runloop. */ |
254 | 288 | STABLE(handler_code)->invoke(tc, handler_code, MVM_callsite_get_common(tc, MVM_CALLSITE_ID_NULL_ARGS), |
255 | 288 | cur_frame->args); |
256 | 288 | break; |
257 | 273k | } |
258 | 0 | default: |
259 | 0 | MVM_panic(1, "Unimplemented handler action"); |
260 | 415k | } |
261 | 415k | } |
262 | | |
263 | | /* Unwinds after a handler. */ |
264 | 276 | static void unwind_after_handler(MVMThreadContext *tc, void *sr_data) { |
265 | 276 | MVMFrame *frame; |
266 | 276 | MVMException *exception; |
267 | 276 | MVMuint32 goto_offset; |
268 | 276 | MVMuint8 *abs_address; |
269 | 276 | |
270 | 276 | |
271 | 276 | /* Get active handler; sanity check (though it's possible other cases |
272 | 276 | * should be supported). */ |
273 | 276 | MVMActiveHandler *ah = (MVMActiveHandler *)sr_data; |
274 | 276 | if (tc->active_handlers != ah) |
275 | 0 | MVM_panic(1, "Trying to unwind from wrong handler"); |
276 | 276 | |
277 | 276 | /* Grab info we'll need to unwind. */ |
278 | 276 | frame = ah->frame; |
279 | 276 | exception = (MVMException *)ah->ex_obj; |
280 | 276 | if (ah->jit_handler) { |
281 | 0 | void **labels = frame->spesh_cand->jitcode->labels; |
282 | 0 | frame->jit_entry_label = labels[ah->jit_handler->goto_label]; |
283 | 0 | abs_address = frame->spesh_cand->jitcode->bytecode; |
284 | 0 | goto_offset = 0; |
285 | 0 | } |
286 | 276 | else { |
287 | 276 | goto_offset = ah->handler->goto_offset; |
288 | 276 | abs_address = NULL; |
289 | 276 | } |
290 | 276 | /* Clean up. */ |
291 | 276 | tc->active_handlers = ah->next_handler; |
292 | 276 | MVM_free(ah); |
293 | 276 | |
294 | 276 | /* Do the unwinding as needed. */ |
295 | 276 | if (exception && exception->body.return_after_unwind) { |
296 | 0 | MVM_frame_unwind_to(tc, frame->caller, NULL, 0, tc->last_handler_result); |
297 | 0 | } |
298 | 276 | else { |
299 | 276 | MVM_frame_unwind_to(tc, frame, abs_address, goto_offset, NULL); |
300 | 276 | } |
301 | 276 | } |
302 | | |
303 | | /* Cleans up an active handler record if we unwind over it. */ |
304 | 3 | static void cleanup_active_handler(MVMThreadContext *tc, void *sr_data) { |
305 | 3 | /* Get active handler; sanity check (though it's possible other cases |
306 | 3 | * should be supported). */ |
307 | 3 | MVMActiveHandler *ah = (MVMActiveHandler *)sr_data; |
308 | 3 | if (tc->active_handlers != ah) |
309 | 0 | MVM_panic(1, "Trying to unwind over wrong handler"); |
310 | 3 | |
311 | 3 | /* Clean up. */ |
312 | 3 | tc->active_handlers = ah->next_handler; |
313 | 3 | MVM_free(ah); |
314 | 3 | } |
315 | | |
316 | 0 | char * MVM_exception_backtrace_line(MVMThreadContext *tc, MVMFrame *cur_frame, MVMuint16 not_top) { |
317 | 0 | MVMString *filename = cur_frame->static_info->body.cu->body.filename; |
318 | 0 | MVMString *name = cur_frame->static_info->body.name; |
319 | 0 | /* XXX TODO: make the caller pass in a char ** and a length pointer so |
320 | 0 | * we can update it if necessary, and the caller can cache it. */ |
321 | 0 | char *o = MVM_malloc(1024); |
322 | 0 | MVMuint8 *cur_op = not_top ? cur_frame->return_address : cur_frame->throw_address; |
323 | 0 | MVMuint32 offset = cur_op - cur_frame->effective_bytecode; |
324 | 0 | MVMBytecodeAnnotation *annot = MVM_bytecode_resolve_annotation(tc, &cur_frame->static_info->body, |
325 | 0 | offset > 0 ? offset - 1 : 0); |
326 | 0 |
|
327 | 0 | MVMuint32 line_number = annot ? annot->line_number : 1; |
328 | 0 | MVMuint16 string_heap_index = annot ? annot->filename_string_heap_index : 0; |
329 | 0 | char *tmp1 = annot && string_heap_index < cur_frame->static_info->body.cu->body.num_strings |
330 | 0 | ? MVM_string_utf8_encode_C_string(tc, MVM_cu_string(tc, |
331 | 0 | cur_frame->static_info->body.cu, string_heap_index)) |
332 | 0 | : NULL; |
333 | 0 |
|
334 | 0 | char *filename_c = filename |
335 | 0 | ? MVM_string_utf8_encode_C_string(tc, filename) |
336 | 0 | : "<ephemeral file>"; |
337 | 0 | char *name_c = name |
338 | 0 | ? MVM_string_utf8_encode_C_string(tc, name) |
339 | 0 | : "<anonymous frame>"; |
340 | 0 |
|
341 | 0 | snprintf(o, 1024, " %s %s:%u (%s:%s)", |
342 | 0 | not_top ? "from" : " at", |
343 | 0 | tmp1 ? tmp1 : "<unknown>", |
344 | 0 | line_number, |
345 | 0 | filename_c, |
346 | 0 | name_c |
347 | 0 | ); |
348 | 0 | if (filename) |
349 | 0 | MVM_free(filename_c); |
350 | 0 | if (name) |
351 | 0 | MVM_free(name_c); |
352 | 0 |
|
353 | 0 | if (tmp1) |
354 | 0 | MVM_free(tmp1); |
355 | 0 | if (annot) |
356 | 0 | MVM_free(annot); |
357 | 0 |
|
358 | 0 | return o; |
359 | 0 | } |
360 | | |
361 | | /* Returns a list of hashes containing file, line, sub and annotations. */ |
362 | 0 | MVMObject * MVM_exception_backtrace(MVMThreadContext *tc, MVMObject *ex_obj) { |
363 | 0 | MVMFrame *cur_frame; |
364 | 0 | MVMObject *arr = NULL, *annotations = NULL, *row = NULL, *value = NULL; |
365 | 0 | MVMuint32 count = 0; |
366 | 0 | MVMString *k_file = NULL, *k_line = NULL, *k_sub = NULL, *k_anno = NULL; |
367 | 0 |
|
368 | 0 | if (IS_CONCRETE(ex_obj) && REPR(ex_obj)->ID == MVM_REPR_ID_MVMException) |
369 | 0 | cur_frame = ((MVMException *)ex_obj)->body.origin; |
370 | 0 | else |
371 | 0 | MVM_exception_throw_adhoc(tc, "Op 'backtrace' needs an exception object"); |
372 | 0 |
|
373 | 0 | MVM_gc_root_temp_push(tc, (MVMCollectable **)&arr); |
374 | 0 | MVM_gc_root_temp_push(tc, (MVMCollectable **)&annotations); |
375 | 0 | MVM_gc_root_temp_push(tc, (MVMCollectable **)&row); |
376 | 0 | MVM_gc_root_temp_push(tc, (MVMCollectable **)&value); |
377 | 0 | MVM_gc_root_temp_push(tc, (MVMCollectable **)&k_file); |
378 | 0 | MVM_gc_root_temp_push(tc, (MVMCollectable **)&k_line); |
379 | 0 | MVM_gc_root_temp_push(tc, (MVMCollectable **)&k_sub); |
380 | 0 | MVM_gc_root_temp_push(tc, (MVMCollectable **)&k_anno); |
381 | 0 | MVM_gc_root_temp_push(tc, (MVMCollectable **)&cur_frame); |
382 | 0 |
|
383 | 0 | k_file = MVM_string_ascii_decode_nt(tc, tc->instance->VMString, "file"); |
384 | 0 | k_line = MVM_string_ascii_decode_nt(tc, tc->instance->VMString, "line"); |
385 | 0 | k_sub = MVM_string_ascii_decode_nt(tc, tc->instance->VMString, "sub"); |
386 | 0 | k_anno = MVM_string_ascii_decode_nt(tc, tc->instance->VMString, "annotations"); |
387 | 0 |
|
388 | 0 | arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); |
389 | 0 |
|
390 | 0 | while (cur_frame != NULL) { |
391 | 0 | MVMuint8 *cur_op = count ? cur_frame->return_address : cur_frame->throw_address; |
392 | 0 | MVMuint32 offset = cur_op - cur_frame->effective_bytecode; |
393 | 0 | MVMBytecodeAnnotation *annot = MVM_bytecode_resolve_annotation(tc, &cur_frame->static_info->body, |
394 | 0 | offset > 0 ? offset - 1 : 0); |
395 | 0 | MVMint32 fshi = annot ? (MVMint32)annot->filename_string_heap_index : -1; |
396 | 0 | char *line_number = MVM_malloc(16); |
397 | 0 | MVMString *filename_str; |
398 | 0 | snprintf(line_number, 16, "%d", annot ? annot->line_number : 1); |
399 | 0 |
|
400 | 0 | /* annotations hash will contain "file" and "line" */ |
401 | 0 | annotations = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTHash); |
402 | 0 |
|
403 | 0 | /* file */ |
404 | 0 | filename_str = fshi >= 0 && fshi < cur_frame->static_info->body.cu->body.num_strings |
405 | 0 | ? MVM_cu_string(tc, cur_frame->static_info->body.cu, fshi) |
406 | 0 | : cur_frame->static_info->body.cu->body.filename; |
407 | 0 | value = MVM_repr_box_str(tc, MVM_hll_current(tc)->str_box_type, |
408 | 0 | filename_str ? filename_str : tc->instance->str_consts.empty); |
409 | 0 | MVM_repr_bind_key_o(tc, annotations, k_file, value); |
410 | 0 |
|
411 | 0 | /* line */ |
412 | 0 | value = (MVMObject *)MVM_string_ascii_decode_nt(tc, tc->instance->VMString, line_number); |
413 | 0 | value = MVM_repr_box_str(tc, MVM_hll_current(tc)->str_box_type, (MVMString *)value); |
414 | 0 | MVM_repr_bind_key_o(tc, annotations, k_line, value); |
415 | 0 | MVM_free(line_number); |
416 | 0 |
|
417 | 0 | /* row will contain "sub" and "annotations" */ |
418 | 0 | row = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTHash); |
419 | 0 | MVM_repr_bind_key_o(tc, row, k_sub, cur_frame->code_ref); |
420 | 0 | MVM_repr_bind_key_o(tc, row, k_anno, annotations); |
421 | 0 |
|
422 | 0 | MVM_repr_push_o(tc, arr, row); |
423 | 0 | MVM_free(annot); |
424 | 0 |
|
425 | 0 | cur_frame = cur_frame->caller; |
426 | 0 | while (cur_frame && cur_frame->static_info->body.is_thunk) |
427 | 0 | cur_frame = cur_frame->caller; |
428 | 0 | count++; |
429 | 0 | } |
430 | 0 |
|
431 | 0 | MVM_gc_root_temp_pop_n(tc, 9); |
432 | 0 |
|
433 | 0 | return arr; |
434 | 0 | } |
435 | | |
436 | | /* Returns the lines (backtrace) of an exception-object as an array. */ |
437 | 0 | MVMObject * MVM_exception_backtrace_strings(MVMThreadContext *tc, MVMObject *ex_obj) { |
438 | 0 | MVMException *ex; |
439 | 0 | MVMFrame *cur_frame; |
440 | 0 | MVMObject *arr; |
441 | 0 |
|
442 | 0 | if (IS_CONCRETE(ex_obj) && REPR(ex_obj)->ID == MVM_REPR_ID_MVMException) |
443 | 0 | ex = (MVMException *)ex_obj; |
444 | 0 | else |
445 | 0 | MVM_exception_throw_adhoc(tc, "Op 'backtracestrings' needs an exception object"); |
446 | 0 |
|
447 | 0 | arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); |
448 | 0 | cur_frame = ex->body.origin; |
449 | 0 |
|
450 | 0 | MVMROOT(tc, arr, { |
451 | 0 | MVMROOT(tc, cur_frame, { |
452 | 0 | MVMuint32 count = 0; |
453 | 0 | while (cur_frame != NULL) { |
454 | 0 | char *line = MVM_exception_backtrace_line(tc, cur_frame, count++); |
455 | 0 | MVMString *line_str = MVM_string_utf8_decode(tc, tc->instance->VMString, line, strlen(line)); |
456 | 0 | MVMObject *line_obj = MVM_repr_box_str(tc, tc->instance->boot_types.BOOTStr, line_str); |
457 | 0 | MVM_repr_push_o(tc, arr, line_obj); |
458 | 0 | cur_frame = cur_frame->caller; |
459 | 0 | MVM_free(line); |
460 | 0 | } |
461 | 0 | }); |
462 | 0 | }); |
463 | 0 |
|
464 | 0 | return arr; |
465 | 0 | } |
466 | | |
467 | | /* Dumps a backtrace relative to the current frame to stderr. */ |
468 | 0 | void MVM_dump_backtrace(MVMThreadContext *tc) { |
469 | 0 | MVMFrame *cur_frame = tc->cur_frame; |
470 | 0 | MVMuint32 count = 0; |
471 | 0 | MVMROOT(tc, cur_frame, { |
472 | 0 | while (cur_frame != NULL) { |
473 | 0 | char *line = MVM_exception_backtrace_line(tc, cur_frame, count++); |
474 | 0 | fprintf(stderr, "%s\n", line); |
475 | 0 | MVM_free(line); |
476 | 0 | cur_frame = cur_frame->caller; |
477 | 0 | } |
478 | 0 | }); |
479 | 0 | } |
480 | | |
481 | | /* Panic over an unhandled exception throw by category. */ |
482 | 0 | static void panic_unhandled_cat(MVMThreadContext *tc, MVMuint32 cat) { |
483 | 0 | /* If it's a control exception, try promoting it to a catch one. */ |
484 | 0 | if (cat != MVM_EX_CAT_CATCH) { |
485 | 0 | MVM_exception_throw_adhoc(tc, "No exception handler located for %s", |
486 | 0 | cat_name(tc, cat)); |
487 | 0 | } |
488 | 0 | else { |
489 | 0 | fprintf(stderr, "No exception handler located for %s\n", cat_name(tc, cat)); |
490 | 0 | MVM_dump_backtrace(tc); |
491 | 0 | if (crash_on_error) |
492 | 0 | abort(); |
493 | 0 | else |
494 | 0 | exit(1); |
495 | 0 | } |
496 | 0 | } |
497 | | |
498 | | /* Panic over an unhandled exception object. */ |
499 | 0 | static void panic_unhandled_ex(MVMThreadContext *tc, MVMException *ex) { |
500 | 0 | char *backtrace; |
501 | 0 |
|
502 | 0 | /* If it's a control exception, try promoting it to a catch one; use |
503 | 0 | * the category name. */ |
504 | 0 | if (ex->body.category != MVM_EX_CAT_CATCH) |
505 | 0 | panic_unhandled_cat(tc, ex->body.category); |
506 | 0 |
|
507 | 0 | /* If there's no message, fall back to category also. */ |
508 | 0 | if (!ex->body.message) |
509 | 0 | panic_unhandled_cat(tc, ex->body.category); |
510 | 0 |
|
511 | 0 | /* Otherwise, dump message and a backtrace. */ |
512 | 0 | backtrace = MVM_string_utf8_encode_C_string(tc, ex->body.message); |
513 | 0 | fprintf(stderr, "Unhandled exception: %s\n", backtrace); |
514 | 0 | MVM_free(backtrace); |
515 | 0 | MVM_dump_backtrace(tc); |
516 | 0 | if (crash_on_error) |
517 | 0 | abort(); |
518 | 0 | else |
519 | 0 | exit(1); |
520 | 0 | } |
521 | | |
522 | | /* Checks if we're throwing lexically, and - if yes - if the current HLL has |
523 | | * a handler for unlocated lexical handlers. */ |
524 | 0 | static MVMint32 use_lexical_handler_hll_error(MVMThreadContext *tc, MVMuint8 mode) { |
525 | 0 | return (mode == MVM_EX_THROW_LEX || mode == MVM_EX_THROW_LEX_CALLER) && |
526 | 0 | !MVM_is_null(tc, MVM_hll_current(tc)->lexical_handler_not_found_error); |
527 | 0 | } |
528 | | |
529 | | /* Invokes the HLL's handler for unresolved lexical throws. */ |
530 | 0 | static void invoke_lexical_handler_hll_error(MVMThreadContext *tc, MVMint64 cat, LocatedHandler lh) { |
531 | 0 | MVMObject *handler = MVM_hll_current(tc)->lexical_handler_not_found_error; |
532 | 0 | MVMCallsite *callsite = MVM_callsite_get_common(tc, MVM_CALLSITE_ID_INT_INT); |
533 | 0 | handler = MVM_frame_find_invokee(tc, handler, NULL); |
534 | 0 | MVM_args_setup_thunk(tc, NULL, MVM_RETURN_VOID, callsite); |
535 | 0 | tc->cur_frame->args[0].i64 = cat; |
536 | 0 | tc->cur_frame->args[1].i64 = lh.handler_out_of_dynamic_scope; |
537 | 0 | STABLE(handler)->invoke(tc, handler, callsite, tc->cur_frame->args); |
538 | 0 | } |
539 | | |
540 | | /* Throws an exception by category, searching for a handler according to |
541 | | * the specified mode. If the handler resumes, the resumption result will |
542 | | * be put into resume_result. Leaves the interpreter in a state where it |
543 | | * will next run the instruction of the handler. If there is no handler, |
544 | | * it will panic and exit with a backtrace. */ |
545 | 141k | void MVM_exception_throwcat(MVMThreadContext *tc, MVMuint8 mode, MVMuint32 cat, MVMRegister *resume_result) { |
546 | 141k | LocatedHandler lh = search_for_handler_from(tc, tc->cur_frame, mode, cat, NULL); |
547 | 141k | if (lh.frame == NULL) { |
548 | 0 | if (use_lexical_handler_hll_error(tc, mode)) { |
549 | 0 | invoke_lexical_handler_hll_error(tc, cat, lh); |
550 | 0 | return; |
551 | 0 | } |
552 | 0 | panic_unhandled_cat(tc, cat); |
553 | 0 | } |
554 | 141k | run_handler(tc, lh, NULL, cat, NULL); |
555 | 141k | } |
556 | | |
557 | 154 | void MVM_exception_die(MVMThreadContext *tc, MVMString *str, MVMRegister *rr) { |
558 | 154 | MVMException *ex; |
559 | 154 | MVMROOT(tc, str, { |
560 | 154 | ex = (MVMException *)MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTException); |
561 | 154 | }); |
562 | 154 | ex->body.category = MVM_EX_CAT_CATCH; |
563 | 154 | MVM_ASSIGN_REF(tc, &(ex->common.header), ex->body.message, str); |
564 | 154 | MVM_exception_throwobj(tc, MVM_EX_THROW_DYN, (MVMObject *)ex, rr); |
565 | 154 | } |
566 | | |
567 | | /* Throws the specified exception object, taking the category from it. If |
568 | | * the handler resumes, the resumption result will be put into resume_result. |
569 | | * Leaves the interpreter in a state where it will next run the instruction of |
570 | | * the handler. If there is no handler, it will panic and exit with a backtrace. */ |
571 | 195 | void MVM_exception_throwobj(MVMThreadContext *tc, MVMuint8 mode, MVMObject *ex_obj, MVMRegister *resume_result) { |
572 | 195 | LocatedHandler lh; |
573 | 195 | MVMException *ex; |
574 | 195 | |
575 | 195 | /* The current frame will be assigned as the thrower of the exception, so |
576 | 195 | * force it onto the heap before we begin (promoting it later would mean |
577 | 195 | * outer handler search result would be outdated). */ |
578 | 195 | MVMROOT(tc, ex_obj, { |
579 | 195 | MVM_frame_force_to_heap(tc, tc->cur_frame); |
580 | 195 | }); |
581 | 195 | |
582 | 195 | if (IS_CONCRETE(ex_obj) && REPR(ex_obj)->ID == MVM_REPR_ID_MVMException) |
583 | 195 | ex = (MVMException *)ex_obj; |
584 | 195 | else |
585 | 0 | MVM_exception_throw_adhoc(tc, "Can only throw an exception object"); |
586 | 195 | |
587 | 195 | if (!ex->body.category) |
588 | 9 | ex->body.category = MVM_EX_CAT_CATCH; |
589 | 195 | if (resume_result) { |
590 | 190 | ex->body.resume_addr = *tc->interp_cur_op; |
591 | 190 | /* Ensure that the jit resume label is stored. The throwish |
592 | 190 | * control guard should ensure that the jit entry label point to |
593 | 190 | * a position just after throwing. */ |
594 | 190 | ex->body.jit_resume_label = tc->cur_frame->jit_entry_label; |
595 | 190 | } |
596 | 195 | lh = search_for_handler_from(tc, tc->cur_frame, mode, ex->body.category, ex->body.payload); |
597 | 195 | if (lh.frame == NULL) { |
598 | 0 | if (use_lexical_handler_hll_error(tc, mode)) { |
599 | 0 | invoke_lexical_handler_hll_error(tc, ex->body.category, lh); |
600 | 0 | return; |
601 | 0 | } |
602 | 0 | panic_unhandled_ex(tc, ex); |
603 | 0 | } |
604 | 195 | |
605 | 195 | if (!ex->body.origin) { |
606 | 190 | MVM_ASSIGN_REF(tc, &(ex->common.header), ex->body.origin, tc->cur_frame); |
607 | 190 | tc->cur_frame->throw_address = *(tc->interp_cur_op); |
608 | 190 | } |
609 | 195 | |
610 | 195 | run_handler(tc, lh, ex_obj, 0, NULL); |
611 | 195 | } |
612 | | |
613 | | /* Throws an exception of the specified category and with the specified payload. |
614 | | * If a goto or payload handler exists, then no exception object will be created. */ |
615 | 273k | void MVM_exception_throwpayload(MVMThreadContext *tc, MVMuint8 mode, MVMuint32 cat, MVMObject *payload, MVMRegister *resume_result) { |
616 | 273k | LocatedHandler lh = search_for_handler_from(tc, tc->cur_frame, mode, cat, NULL); |
617 | 273k | if (lh.frame == NULL) { |
618 | 0 | if (use_lexical_handler_hll_error(tc, mode)) { |
619 | 0 | invoke_lexical_handler_hll_error(tc, cat, lh); |
620 | 0 | return; |
621 | 0 | } |
622 | 0 | panic_unhandled_cat(tc, cat); |
623 | 0 | } |
624 | 273k | run_handler(tc, lh, NULL, cat, payload); |
625 | 273k | } |
626 | | |
627 | 9 | void MVM_exception_resume(MVMThreadContext *tc, MVMObject *ex_obj) { |
628 | 9 | MVMException *ex; |
629 | 9 | MVMFrame *target; |
630 | 9 | MVMActiveHandler *ah; |
631 | 9 | |
632 | 9 | if (IS_CONCRETE(ex_obj) && REPR(ex_obj)->ID == MVM_REPR_ID_MVMException) |
633 | 9 | ex = (MVMException *)ex_obj; |
634 | 9 | else |
635 | 0 | MVM_exception_throw_adhoc(tc, "Can only resume an exception object"); |
636 | 9 | |
637 | 9 | /* Check that everything is in place to do the resumption. */ |
638 | 9 | if (!ex->body.resume_addr) |
639 | 0 | MVM_exception_throw_adhoc(tc, "This exception is not resumable"); |
640 | 9 | target = ex->body.origin; |
641 | 9 | if (!target) |
642 | 0 | MVM_exception_throw_adhoc(tc, "This exception is not resumable"); |
643 | 9 | if (target->special_return != unwind_after_handler) |
644 | 0 | MVM_exception_throw_adhoc(tc, "This exception is not resumable"); |
645 | 9 | if (!in_caller_chain(tc, target)) |
646 | 0 | MVM_exception_throw_adhoc(tc, "Too late to resume this exception"); |
647 | 9 | |
648 | 9 | /* Check that this is the exception we're currently handling. */ |
649 | 9 | if (!tc->active_handlers) |
650 | 0 | MVM_exception_throw_adhoc(tc, "Can only resume an exception in its handler"); |
651 | 9 | if (tc->active_handlers->ex_obj != ex_obj) |
652 | 0 | MVM_exception_throw_adhoc(tc, "Can only resume the current exception"); |
653 | 9 | |
654 | 9 | /* Clear special return handler; we'll do its work here. */ |
655 | 9 | target->special_return = NULL; |
656 | 9 | target->special_unwind = NULL; |
657 | 9 | |
658 | 9 | /* Clear the current active handler. */ |
659 | 9 | ah = tc->active_handlers; |
660 | 9 | tc->active_handlers = ah->next_handler; |
661 | 9 | MVM_free(ah); |
662 | 9 | |
663 | 9 | /* Unwind to the thrower of the exception; set PC and jit entry label. */ |
664 | 9 | target->jit_entry_label = ex->body.jit_resume_label; |
665 | 9 | MVM_frame_unwind_to(tc, target, ex->body.resume_addr, 0, NULL); |
666 | 9 | } |
667 | | |
668 | 0 | static MVMObject* get_lexotic_for_handler_idx(MVMThreadContext *tc, MVMint32 handler_idx) { |
669 | 0 | MVMLexotic *lexotic; |
670 | 0 | MVMStaticFrame *sf = tc->cur_frame->static_info; |
671 | 0 | /* See if we've got this lexotic cached; return it if so. */ |
672 | 0 | if (sf->body.pool_index < tc->lexotic_cache_size) { |
673 | 0 | lexotic = tc->lexotic_cache[sf->body.pool_index]; |
674 | 0 | if (lexotic && lexotic->body.handler_idx == handler_idx) |
675 | 0 | return (MVMObject *)lexotic; |
676 | 0 | } |
677 | 0 |
|
678 | 0 | /* Allocate lexotic object, set it up, and cache it. */ |
679 | 0 | MVMROOT(tc, sf, { |
680 | 0 | lexotic = (MVMLexotic *)MVM_repr_alloc_init(tc, tc->instance->Lexotic); |
681 | 0 | }); |
682 | 0 | lexotic->body.handler_idx = handler_idx; |
683 | 0 | MVM_ASSIGN_REF(tc, &(lexotic->common.header), lexotic->body.sf, sf); |
684 | 0 | if (sf->body.pool_index >= tc->lexotic_cache_size) { |
685 | 0 | MVMuint32 orig_size = tc->lexotic_cache_size; |
686 | 0 | tc->lexotic_cache_size = sf->body.pool_index + 1; |
687 | 0 | tc->lexotic_cache = orig_size |
688 | 0 | ? MVM_realloc(tc->lexotic_cache, tc->lexotic_cache_size * sizeof(MVMLexotic *)) |
689 | 0 | : MVM_malloc(tc->lexotic_cache_size * sizeof(MVMLexotic *)); |
690 | 0 | memset(tc->lexotic_cache + orig_size, 0, |
691 | 0 | (tc->lexotic_cache_size - orig_size) * sizeof(MVMLexotic *)); |
692 | 0 | } |
693 | 0 | if (!tc->lexotic_cache[sf->body.pool_index]) |
694 | 0 | tc->lexotic_cache[sf->body.pool_index] = lexotic; |
695 | 0 |
|
696 | 0 | return (MVMObject *)lexotic; |
697 | 0 | } |
698 | | |
699 | | /* Creates a new lexotic. */ |
700 | 0 | MVMObject * MVM_exception_newlexotic(MVMThreadContext *tc, MVMuint32 offset) { |
701 | 0 | /* Locate handler associated with the specified label. */ |
702 | 0 | MVMFrame *f = tc->cur_frame; |
703 | 0 | MVMStaticFrame *sf = f->static_info; |
704 | 0 | MVMint32 handler_idx = -1; |
705 | 0 | MVMint32 num_handlers = f->spesh_cand |
706 | 0 | ? f->spesh_cand->num_handlers |
707 | 0 | : sf->body.num_handlers; |
708 | 0 | MVMuint32 i; |
709 | 0 | for (i = 0; i < num_handlers; i++) { |
710 | 0 | if (f->effective_handlers[i].action == MVM_EX_ACTION_GOTO && |
711 | 0 | f->effective_handlers[i].goto_offset == offset) { |
712 | 0 | handler_idx = i; |
713 | 0 | break; |
714 | 0 | } |
715 | 0 | } |
716 | 0 | if (handler_idx < 0) |
717 | 0 | MVM_exception_throw_adhoc(tc, "Label with no handler passed to newlexotic"); |
718 | 0 | return get_lexotic_for_handler_idx(tc, handler_idx); |
719 | 0 | } |
720 | | |
721 | | /* Creates a new lexotic from the JIT. The JIT doesn't have access to |
722 | | * the offset, so we can't find it from within there. */ |
723 | 0 | MVMObject * MVM_exception_newlexotic_from_jit(MVMThreadContext *tc, MVMint32 label) { |
724 | 0 | /* Locate handler associated with the specified label. */ |
725 | 0 | MVMFrame *f = tc->cur_frame; |
726 | 0 | MVMint32 handler_idx = -1; |
727 | 0 | MVMint32 num_handlers = f->spesh_cand->jitcode->num_handlers; |
728 | 0 | MVMJitHandler *handlers = f->spesh_cand->jitcode->handlers; |
729 | 0 | MVMuint32 i; |
730 | 0 | for (i = 0; i < num_handlers; i++) { |
731 | 0 | if (f->effective_handlers[i].action == MVM_EX_ACTION_GOTO && |
732 | 0 | handlers[i].goto_label == label) { |
733 | 0 | handler_idx = i; |
734 | 0 | break; |
735 | 0 | } |
736 | 0 | } |
737 | 0 | if (handler_idx < 0) |
738 | 0 | MVM_exception_throw_adhoc(tc, "Label with no handler passed to newlexotic"); |
739 | 0 | return get_lexotic_for_handler_idx(tc, handler_idx); |
740 | 0 | } |
741 | | |
742 | | |
743 | | /* Unwinds to a lexotic captured handler. */ |
744 | 0 | void MVM_exception_gotolexotic(MVMThreadContext *tc, MVMint32 handler_idx, MVMStaticFrame *sf) { |
745 | 0 | MVMFrame *f, *search; |
746 | 0 | f = NULL; |
747 | 0 | search = tc->cur_frame; |
748 | 0 | while (search) { |
749 | 0 | f = search; |
750 | 0 | while (f) { |
751 | 0 | if (f->static_info == sf) |
752 | 0 | break; |
753 | 0 | f = f->outer; |
754 | 0 | } |
755 | 0 | if (f) |
756 | 0 | break; |
757 | 0 | search = search->caller; |
758 | 0 | } |
759 | 0 | if (f && in_caller_chain(tc, f)) { |
760 | 0 | LocatedHandler lh; |
761 | 0 | lh.frame = f; |
762 | 0 | lh.handler = &(f->effective_handlers[handler_idx]); |
763 | 0 | if (f->spesh_cand && f->spesh_cand->jitcode) |
764 | 0 | lh.jit_handler = &(f->spesh_cand->jitcode->handlers[handler_idx]); |
765 | 0 | else |
766 | 0 | lh.jit_handler = NULL; |
767 | 0 | run_handler(tc, lh, NULL, MVM_EX_CAT_RETURN, NULL); |
768 | 0 | } |
769 | 0 | else { |
770 | 0 | MVM_exception_throw_adhoc(tc, "Too late to invoke lexotic return"); |
771 | 0 | } |
772 | 0 | } |
773 | | |
774 | | /* Panics and shuts down the VM. Don't do this unless it's something quite |
775 | | * unrecoverable. |
776 | | * TODO: Some hook for embedders. |
777 | | */ |
778 | | MVM_NO_RETURN |
779 | 0 | void MVM_panic(MVMint32 exitCode, const char *messageFormat, ...) { |
780 | 0 | va_list args; |
781 | 0 | fprintf(stderr, "MoarVM panic: "); |
782 | 0 | va_start(args, messageFormat); |
783 | 0 | vfprintf(stderr, messageFormat, args); |
784 | 0 | va_end(args); |
785 | 0 | fwrite("\n", 1, 1, stderr); |
786 | 0 | if (crash_on_error) |
787 | 0 | abort(); |
788 | 0 | else |
789 | 0 | exit(exitCode); |
790 | 0 | } |
791 | | |
792 | | MVM_NO_RETURN |
793 | 0 | void MVM_panic_allocation_failed(size_t len) { |
794 | 0 | MVM_panic(1, "Memory allocation failed; could not allocate %"MVM_PRSz" bytes", len); |
795 | 0 | } |
796 | | |
797 | | /* A kinder MVM_panic() that doesn't assume our memory is corrupted (but does kill the |
798 | | * process to indicate that we've made an error */ |
799 | | MVM_NO_RETURN |
800 | 0 | void MVM_oops(MVMThreadContext *tc, const char *messageFormat, ...) { |
801 | 0 | va_list args; |
802 | 0 | va_start(args, messageFormat); |
803 | 0 | vfprintf(stderr, messageFormat, args); |
804 | 0 | va_end(args); |
805 | 0 | fprintf(stderr, "\n"); |
806 | 0 | MVM_dump_backtrace(tc); |
807 | 0 | fprintf(stderr, "\n"); |
808 | 0 | exit(1); |
809 | 0 | } |
810 | | |
811 | | /* Throws an ad-hoc (untyped) exception. */ |
812 | | MVM_NO_RETURN |
813 | 94 | void MVM_exception_throw_adhoc(MVMThreadContext *tc, const char *messageFormat, ...) { |
814 | 94 | va_list args; |
815 | 94 | va_start(args, messageFormat); |
816 | 94 | MVM_exception_throw_adhoc_free_va(tc, NULL, messageFormat, args); |
817 | 94 | va_end(args); |
818 | 94 | } |
819 | | |
820 | | /* Throws an ad-hoc (untyped) exception. */ |
821 | | MVM_NO_RETURN |
822 | 0 | void MVM_exception_throw_adhoc_va(MVMThreadContext *tc, const char *messageFormat, va_list args) { |
823 | 0 | MVM_exception_throw_adhoc_free_va(tc, NULL, messageFormat, args); |
824 | 0 | } |
825 | | |
826 | | /* Throws an ad-hoc (untyped) exception, taking a NULL-terminated array of |
827 | | * char pointers to deallocate after message construction. */ |
828 | | MVM_NO_RETURN |
829 | 9 | void MVM_exception_throw_adhoc_free(MVMThreadContext *tc, char **waste, const char *messageFormat, ...) { |
830 | 9 | va_list args; |
831 | 9 | va_start(args, messageFormat); |
832 | 9 | MVM_exception_throw_adhoc_free_va(tc, waste, messageFormat, args); |
833 | 9 | va_end(args); |
834 | 9 | } |
835 | | |
836 | | /* Throws an ad-hoc (untyped) exception, taking a NULL-terminated array of |
837 | | * char pointers to deallocate after message construction. */ |
838 | | MVM_NO_RETURN |
839 | 103 | void MVM_exception_throw_adhoc_free_va(MVMThreadContext *tc, char **waste, const char *messageFormat, va_list args) { |
840 | 103 | LocatedHandler lh; |
841 | 103 | MVMException *ex; |
842 | 103 | |
843 | 103 | /* The current frame will be assigned as the thrower of the exception, so |
844 | 103 | * force it onto the heap before we begin. */ |
845 | 103 | if (tc->cur_frame) |
846 | 103 | MVM_frame_force_to_heap(tc, tc->cur_frame); |
847 | 103 | |
848 | 103 | /* Create and set up an exception object. */ |
849 | 103 | ex = (MVMException *)MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTException); |
850 | 103 | MVMROOT(tc, ex, { |
851 | 103 | char *c_message = MVM_malloc(1024); |
852 | 103 | int bytes = vsnprintf(c_message, 1024, messageFormat, args); |
853 | 103 | MVMString *message = MVM_string_utf8_decode(tc, tc->instance->VMString, c_message, bytes); |
854 | 103 | MVM_free(c_message); |
855 | 103 | |
856 | 103 | /* Clean up after ourselves to avoid leaking C strings. */ |
857 | 103 | if (waste) { |
858 | 103 | while(*waste) |
859 | 103 | MVM_free(*waste++); |
860 | 103 | } |
861 | 103 | |
862 | 103 | MVM_ASSIGN_REF(tc, &(ex->common.header), ex->body.message, message); |
863 | 103 | if (tc->cur_frame) { |
864 | 103 | ex->body.origin = tc->cur_frame; |
865 | 103 | tc->cur_frame->throw_address = *(tc->interp_cur_op); |
866 | 103 | } |
867 | 103 | else { |
868 | 103 | ex->body.origin = NULL; |
869 | 103 | } |
870 | 103 | ex->body.category = MVM_EX_CAT_CATCH; |
871 | 103 | }); |
872 | 103 | |
873 | 103 | /* Try to locate a handler, so long as we're in the interpreter. */ |
874 | 103 | if (tc->interp_cur_op) |
875 | 103 | lh = search_for_handler_from(tc, tc->cur_frame, MVM_EX_THROW_DYN, ex->body.category, NULL); |
876 | 103 | else |
877 | 0 | lh.frame = NULL; |
878 | 103 | |
879 | 103 | /* Do we have a handler to unwind to? */ |
880 | 103 | if (lh.frame == NULL) { |
881 | 0 | /* No handler. Should we crash on these? */ |
882 | 0 | if (crash_on_error) { |
883 | 0 | /* Yes, abort. */ |
884 | 0 | vfprintf(stderr, messageFormat, args); |
885 | 0 | fwrite("\n", 1, 1, stderr); |
886 | 0 | MVM_dump_backtrace(tc); |
887 | 0 | abort(); |
888 | 0 | } |
889 | 0 | else { |
890 | 0 | /* No, just the usual panic. */ |
891 | 0 | panic_unhandled_ex(tc, ex); |
892 | 0 | } |
893 | 0 | } |
894 | 103 | |
895 | 103 | /* Run the handler, which doesn't actually run it but rather sets up the |
896 | 103 | * interpreter so that when we return to it, we'll be at the handler. */ |
897 | 103 | run_handler(tc, lh, (MVMObject *)ex, MVM_EX_CAT_CATCH, NULL); |
898 | 103 | |
899 | 103 | /* Clear any C stack temporaries that code may have pushed before throwing |
900 | 103 | * the exception, and release any needed mutex. */ |
901 | 103 | MVM_gc_root_temp_pop_all(tc); |
902 | 103 | MVM_tc_release_ex_release_mutex(tc); |
903 | 103 | |
904 | 103 | /* Jump back into the interpreter. */ |
905 | 103 | longjmp(tc->interp_jump, 1); |
906 | 103 | } |
907 | | |
908 | 0 | void MVM_crash_on_error(void) { |
909 | 0 | crash_on_error = 1; |
910 | 0 | } |