#include "moar.h" #ifdef _MSC_VER #define snprintf _snprintf #define vsnprintf _vsnprintf #endif static int crash_on_error = 0; /* Function for getting effective (specialized or not) frame handlers. */ MVM_STATIC_INLINE MVMFrameHandler * MVM_frame_effective_handlers(MVMFrame *f) { MVMSpeshCandidate *spesh_cand = f->spesh_cand; return spesh_cand ? spesh_cand->body.handlers : f->static_info->body.handlers; } /* Maps ID of exception category to its name. */ static const char * cat_name(MVMThreadContext *tc, MVMint32 cat) { switch (cat) { case MVM_EX_CAT_CATCH: return "catch"; case MVM_EX_CAT_CONTROL: return "control"; case MVM_EX_CAT_NEXT: return "next"; case MVM_EX_CAT_REDO: return "redo"; case MVM_EX_CAT_LAST: return "last"; case MVM_EX_CAT_RETURN: return "return"; case MVM_EX_CAT_TAKE: return "take"; case MVM_EX_CAT_WARN: return "warn"; case MVM_EX_CAT_SUCCEED: return "succeed"; case MVM_EX_CAT_PROCEED: return "proceed"; case MVM_EX_CAT_NEXT | MVM_EX_CAT_LABELED: return "next_label"; case MVM_EX_CAT_REDO | MVM_EX_CAT_LABELED: return "redo_label"; case MVM_EX_CAT_LAST | MVM_EX_CAT_LABELED: return "last_label"; default: return "unknown"; } } /* Checks if an exception handler is already on the active handler stack, * so we don't re-trigger the same exception handler. Note: We have static * handlers that get reused, so also check for the same handler being in * the same frame, otherwise we consider the handler as being another one. */ static MVMuint8 in_handler_stack(MVMThreadContext *tc, MVMFrameHandler *fh, MVMFrame *f) { if (tc->active_handlers) { MVMActiveHandler *ah = tc->active_handlers; while (ah) { if (ah->handler == fh && ah->frame == f) return 1; ah = ah->next_handler; } } return 0; } /* Checks if a frame is still active. Naively, we could scan the call stack * for it, but since we always clean up ->work when a frame is removed from * the call stack we can do it in O(1) that way. */ static MVMuint8 in_caller_chain(MVMThreadContext *tc, MVMFrame *f_maybe) { return f_maybe->work ? 1 : 0; } /* Information about a located handler. */ typedef struct { MVMFrame *frame; MVMFrameHandler *handler; MVMJitHandler *jit_handler; MVMint32 handler_out_of_dynamic_scope; } LocatedHandler; static MVMint32 handler_can_handle(MVMFrame *f, MVMFrameHandler *fh, MVMuint32 cat, MVMObject *payload) { MVMuint32 category_mask = fh->category_mask; MVMuint64 block_has_label = category_mask & MVM_EX_CAT_LABELED; MVMuint64 block_label = block_has_label ? (uintptr_t)(f->work[fh->label_reg].o) : 0; MVMuint64 thrown_label = payload ? (uintptr_t)payload : 0; MVMuint64 identical_label_found = thrown_label == block_label; return ((cat & category_mask) == cat && (!(cat & MVM_EX_CAT_LABELED) || identical_label_found)) || ((category_mask & MVM_EX_CAT_CONTROL) && cat != MVM_EX_CAT_CATCH); } /* Looks through the handlers of a particular frame, including inlines in * dynamic scope, and sees if one will match what we're looking for. Returns * 1 to it if so, and 0 if not; in the case 1 is returned the *lh will be * populated with details of the located handler. Since upon inlining, the * dynamic scope becomes lexical so far as the optimized bytecode is * concerned, then this just needs a scan of the table without any further * checks being needed. */ static MVMint32 search_frame_handlers_dyn(MVMThreadContext *tc, MVMFrame *f, MVMuint32 cat, MVMObject *payload, LocatedHandler *lh) { MVMuint32 i; if (f->spesh_cand && f->spesh_cand->body.jitcode && f->jit_entry_label) { MVMJitCode *jitcode = f->spesh_cand->body.jitcode; void *current_position = MVM_jit_code_get_current_position(tc, jitcode, f); MVMJitHandler *jhs = f->spesh_cand->body.jitcode->handlers; MVMFrameHandler *fhs = MVM_frame_effective_handlers(f); for (i = MVM_jit_code_get_active_handlers(tc, jitcode, current_position, 0); i < jitcode->num_handlers; i = MVM_jit_code_get_active_handlers(tc, jitcode, current_position, i+1)) { if (handler_can_handle(f, &fhs[i], cat, payload) && !in_handler_stack(tc, &fhs[i], f)) { lh->handler = &fhs[i]; lh->jit_handler = &jhs[i]; return 1; } } } else { MVMuint32 num_handlers = f->spesh_cand ? f->spesh_cand->body.num_handlers : f->static_info->body.num_handlers; MVMuint32 pc; if (f == tc->cur_frame) pc = (MVMuint32)(*tc->interp_cur_op - *tc->interp_bytecode_start); else pc = (MVMuint32)(f->return_address - MVM_frame_effective_bytecode(f)); for (i = 0; i < num_handlers; i++) { MVMFrameHandler *fh = &(MVM_frame_effective_handlers(f)[i]); if (!handler_can_handle(f, fh, cat, payload)) continue; if (pc >= fh->start_offset && pc <= fh->end_offset && !in_handler_stack(tc, fh, f)) { lh->handler = fh; return 1; } } } return 0; } /* Looks for lexically applicable handlers in the current frame, accounting * for any inlines. The skip_first_inlinee flag indicates that we should skip * looking until we have encountered an inline boundary indicator saying that * we have crossed from an inlinee to its inliner's handlers; this is used to * handle the THROW_LEX_CALLER mode. If we never encounter an inline boundary * when skip_first_inlinee is true then we'll always return 0. * * If skip_first_inlinee is false or we already saw an inline boundary, then * we start looking for a matching handler. If one is found before seeing * another inline boundary, then it is applicable; the data pointed to by lh * will be updated with the frame handler details and 1 will be returned. * * Upon reaching an (or, in the case of skip_first_inlinee, a second) inline * boundary indicator, there are two cases that apply. We take the inline * that we are leaving, and look up the code ref using the code_ref_reg in * the inline. We know that we can never inline a frame that was closed over * (due to capturelex or takeclosure being marked :noinline). Thus, either: * 1. The outer of the inlinee is actually the current frame f. In this case, * we skip all inlined handlers and just consider those of f itself. * 2. The next frame we should search in is the ->outer of the inlinee, and * thus all the rest of the handlers in this frame should be ignored. In * this case, the MVMFrame **next_outer will be populated with a pointer * to that frame. * * The skip_all_inlinees flag is set once we are below the frame on the stack * to where the search started. Again, this is because a frame that did a * lexical capture may not be inlined, so we only need to consider the topmost * frame's handlers, not anything it might have inlined into it. */ static MVMint32 search_frame_handlers_lex(MVMThreadContext *tc, MVMFrame *f, MVMuint32 cat, MVMObject *payload, LocatedHandler *lh, MVMuint32 *skip_first_inlinee, MVMuint32 skip_all_inlinees, MVMFrame **next_outer) { MVMuint32 i; MVMuint32 skipping = *skip_first_inlinee; MVMFrameHandler *fhs = MVM_frame_effective_handlers(f); if (f->spesh_cand && f->spesh_cand->body.jitcode && f->jit_entry_label) { MVMJitCode *jitcode = f->spesh_cand->body.jitcode; void *current_position = MVM_jit_code_get_current_position(tc, jitcode, f); MVMJitHandler *jhs = jitcode->handlers; for (i = MVM_jit_code_get_active_handlers(tc, jitcode, current_position, 0); i < jitcode->num_handlers; i = MVM_jit_code_get_active_handlers(tc, jitcode, current_position, i+1)) { MVMFrameHandler *fh = &(fhs[i]); if (skip_all_inlinees && fh->inlinee >= 0) continue; if (fh->category_mask == MVM_EX_INLINE_BOUNDARY) { if (skipping) { skipping = 0; *skip_first_inlinee = 0; } else { MVMuint16 cr_reg = f->spesh_cand->body.inlines[fh->inlinee].code_ref_reg; MVMFrame *inline_outer = ((MVMCode *)f->work[cr_reg].o)->body.outer; if (inline_outer == f) { skip_all_inlinees = 1; } else { *next_outer = inline_outer; return 0; } } } if (skipping || !handler_can_handle(f, fh, cat, payload)) continue; if (!in_handler_stack(tc, fh, f)) { if (skipping && f->static_info->body.is_thunk) return 0; lh->handler = fh; lh->jit_handler = &jhs[i]; return 1; } } } else { MVMuint32 num_handlers = f->spesh_cand ? f->spesh_cand->body.num_handlers : f->static_info->body.num_handlers; MVMuint32 pc; if (f == tc->cur_frame) pc = (MVMuint32)(*tc->interp_cur_op - *tc->interp_bytecode_start); else pc = (MVMuint32)(f->return_address - MVM_frame_effective_bytecode(f)); for (i = 0; i < num_handlers; i++) { MVMFrameHandler *fh = &(fhs[i]); if (skip_all_inlinees && fh->inlinee >= 0) continue; if (fh->category_mask == MVM_EX_INLINE_BOUNDARY) { if (pc >= fh->start_offset && pc <= fh->end_offset) { if (skipping) { skipping = 0; *skip_first_inlinee = 0; } else { MVMuint16 cr_reg = f->spesh_cand->body.inlines[fh->inlinee].code_ref_reg; MVMFrame *inline_outer = ((MVMCode *)f->work[cr_reg].o)->body.outer; if (inline_outer == f) { skip_all_inlinees = 1; } else { *next_outer = inline_outer; return 0; } } } continue; } if (skipping || !handler_can_handle(f, fh, cat, payload)) continue; if (pc >= fh->start_offset && pc <= fh->end_offset && !in_handler_stack(tc, fh, f)) { if (skipping && f->static_info->body.is_thunk) return 0; lh->handler = fh; return 1; } } } return 0; } /* Searches for a handler of the specified category, relative to the given * starting frame, searching according to the chosen mode. */ static LocatedHandler search_for_handler_from(MVMThreadContext *tc, MVMFrame *f, MVMuint8 mode, MVMuint32 cat, MVMObject *payload) { MVMuint32 skip_first_inlinee = 0; LocatedHandler lh; lh.frame = NULL; lh.handler = NULL; lh.jit_handler = NULL; lh.handler_out_of_dynamic_scope = 0; switch (mode) { case MVM_EX_THROW_LEX_CALLER: skip_first_inlinee = 1; /* fallthrough */ case MVM_EX_THROW_LEX: { MVMint32 skip_all_inlinees = 0; while (f != NULL) { MVMFrame *outer_from_inlinee = NULL; if (search_frame_handlers_lex(tc, f, cat, payload, &lh, &skip_first_inlinee, skip_all_inlinees, &outer_from_inlinee)) { if (in_caller_chain(tc, f)) lh.frame = f; else lh.handler_out_of_dynamic_scope = 1; return lh; } if (skip_first_inlinee) { /* If this is still set, it means that the topmost frame * had no inlines, so we didn't already reach a chain of * outers to traverse. In this case, skip over any thunks * and continue the search. */ skip_first_inlinee = 0; f = f->caller; while (f && f->static_info->body.is_thunk) f = f->caller; } else { f = outer_from_inlinee ? outer_from_inlinee : f->outer; skip_all_inlinees = 1; } } return lh; } case MVM_EX_THROW_DYN: while (f != NULL) { if (search_frame_handlers_dyn(tc, f, cat, payload, &lh)) { lh.frame = f; return lh; } f = MVM_LIKELY(f != tc->thread_entry_frame) ? f->caller : NULL; } return lh; case MVM_EX_THROW_LEXOTIC: while (f != NULL) { lh = search_for_handler_from(tc, f, MVM_EX_THROW_LEX, cat, payload); if (lh.frame != NULL) return lh; f = MVM_LIKELY(f != tc->thread_entry_frame) ? f->caller : NULL; } return lh; default: MVM_panic(1, "Unhandled exception throw mode %d", (int)mode); } } /* Runs an exception handler (which really means updating interpreter state * so that when we return to the runloop, we're in the handler). If there is * an exception object already, it will be used; NULL can be passed if there * is not one, meaning it will be created if needed (based on the category * parameter; if ex_obj is passed, the category is not used). */ static void unwind_after_handler(MVMThreadContext *tc, void *sr_data); static void cleanup_active_handler(MVMThreadContext *tc, void *sr_data); static void run_handler(MVMThreadContext *tc, LocatedHandler lh, MVMObject *ex_obj, MVMuint32 category, MVMObject *payload) { switch (lh.handler->action) { case MVM_EX_ACTION_GOTO_WITH_PAYLOAD: if (payload) tc->last_payload = payload; else if (ex_obj && ((MVMException *)ex_obj)->body.payload) tc->last_payload = ((MVMException *)ex_obj)->body.payload; else tc->last_payload = tc->instance->VMNull; /* Deliberate fallthrough to unwind below. */ MVM_FALLTHROUGH case MVM_EX_ACTION_GOTO: if (lh.jit_handler) { void **labels = lh.frame->spesh_cand->body.jitcode->labels; MVMuint8 *pc = lh.frame->spesh_cand->body.jitcode->bytecode; MVM_frame_unwind_to(tc, lh.frame, pc, 0, NULL, labels[lh.jit_handler->goto_label]); } else { MVM_frame_unwind_to(tc, lh.frame, NULL, lh.handler->goto_offset, NULL, NULL); } break; case MVM_EX_ACTION_INVOKE: { /* Create active handler record. */ MVMActiveHandler *ah = MVM_malloc(sizeof(MVMActiveHandler)); /* Ensure we have an exception object. */ MVMFrame *cur_frame = tc->cur_frame; if (ex_obj == NULL) { MVMROOT3(tc, cur_frame, lh.frame, payload, { ex_obj = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTException); }); ((MVMException *)ex_obj)->body.category = category; MVM_ASSIGN_REF(tc, &(ex_obj->header), ((MVMException *)ex_obj)->body.payload, payload); } /* Preserve the frame caller chain, so we can do backtraces. */ MVMFrame *pres_frame = ((MVMException *)ex_obj)->body.origin; while (pres_frame) { MVMFrameExtra *extra = MVM_frame_extra(tc, pres_frame); extra->caller_info_needed = 1; pres_frame = pres_frame->caller; } /* Find frame to invoke. */ MVMObject *handler_code = lh.frame->work[lh.handler->block_reg].o; if (!MVM_code_iscode(tc, handler_code)) MVM_oops(tc, "Exception handler must be a VM code handle"); /* Install active handler record. */ ah->frame = lh.frame; ah->handler = lh.handler; ah->jit_handler = lh.jit_handler; ah->ex_obj = ex_obj; ah->next_handler = tc->active_handlers; tc->active_handlers = ah; /* Set up special return to unwinding after running the * handler. */ cur_frame->return_value = (MVMRegister *)&tc->last_handler_result; cur_frame->return_type = MVM_RETURN_OBJ; cur_frame->return_address = *(tc->interp_cur_op); MVMActiveHandler **sr = MVM_callstack_allocate_special_return(tc, unwind_after_handler, cleanup_active_handler, NULL, sizeof(MVMActiveHandler *)); *sr = ah; /* Invoke the handler frame and return to runloop. */ MVM_frame_dispatch_zero_args(tc, (MVMCode *)handler_code); break; } default: MVM_panic(1, "Unimplemented handler action"); } } /* Unwinds after a handler. */ static void unwind_after_handler(MVMThreadContext *tc, void *sr_data) { MVMFrame *frame; MVMException *exception; MVMuint32 goto_offset; MVMuint8 *abs_address; void *jit_return_label; /* Get active handler; sanity check (though it's possible other cases * should be supported). */ MVMActiveHandler *ah = *((MVMActiveHandler **)sr_data); if (tc->active_handlers != ah) MVM_panic(1, "Trying to unwind from wrong handler"); /* Grab info we'll need to unwind. */ frame = ah->frame; exception = (MVMException *)ah->ex_obj; if (ah->jit_handler) { void **labels = frame->spesh_cand->body.jitcode->labels; jit_return_label = labels[ah->jit_handler->goto_label]; abs_address = frame->spesh_cand->body.jitcode->bytecode; goto_offset = 0; } else { goto_offset = ah->handler->goto_offset; abs_address = NULL; jit_return_label = NULL; } /* Clean up. */ tc->active_handlers = ah->next_handler; MVM_free(ah); /* Do the unwinding as needed. */ if (exception && exception->body.return_after_unwind) { /* we can't very well return to our the unwod JIT address */ MVM_frame_unwind_to(tc, frame->caller, NULL, 0, tc->last_handler_result, NULL); } else { MVM_frame_unwind_to(tc, frame, abs_address, goto_offset, NULL, jit_return_label); } } /* Cleans up an active handler record if we unwind over it. */ static void cleanup_active_handler(MVMThreadContext *tc, void *sr_data) { /* Get active handler; sanity check (though it's possible other cases * should be supported). */ MVMActiveHandler *ah = *((MVMActiveHandler **)sr_data); if (tc->active_handlers != ah) MVM_panic(1, "Trying to unwind over wrong handler"); /* Clean up. */ tc->active_handlers = ah->next_handler; MVM_free(ah); } char * MVM_exception_backtrace_line(MVMThreadContext *tc, MVMFrame *cur_frame, MVMuint16 not_top, MVMuint8 *throw_address) { MVMString *filename = cur_frame->static_info->body.cu->body.filename; MVMString *name = cur_frame->static_info->body.name; /* XXX TODO: make the caller pass in a char ** and a length pointer so * we can update it if necessary, and the caller can cache it. */ char *o = MVM_malloc(1024); MVMuint8 *cur_op = not_top ? cur_frame->return_address : throw_address; MVMuint32 offset = cur_op - MVM_frame_effective_bytecode(cur_frame); MVMBytecodeAnnotation *annot = MVM_bytecode_resolve_annotation(tc, &cur_frame->static_info->body, offset > 0 ? offset - 1 : 0); MVMuint32 line_number = annot ? annot->line_number : 1; MVMuint16 string_heap_index = annot ? annot->filename_string_heap_index : 0; char *tmp1 = annot && string_heap_index < cur_frame->static_info->body.cu->body.num_strings ? MVM_string_utf8_encode_C_string(tc, MVM_cu_string(tc, cur_frame->static_info->body.cu, string_heap_index)) : NULL; char *filename_c = filename ? MVM_string_utf8_encode_C_string(tc, filename) : ""; char *name_c = name ? MVM_string_utf8_encode_C_string(tc, name) : ""; snprintf(o, 1024, " %s %s:%u (%s:%s)", not_top ? "from" : " at", tmp1 ? tmp1 : "", line_number, filename_c, name_c ); if (filename) MVM_free(filename_c); if (name) MVM_free(name_c); if (tmp1) MVM_free(tmp1); if (annot) MVM_free(annot); return o; } /* Returns a list of hashes containing file, line, sub and annotations. */ MVMObject * MVM_exception_backtrace(MVMThreadContext *tc, MVMObject *ex_obj) { MVMFrame *cur_frame; MVMSpeshFrameWalker fw; MVMObject *arr = NULL, *annotations = NULL, *row = NULL, *value = NULL; MVMuint32 count = 0; MVMString *k_file = NULL, *k_line = NULL, *k_sub = NULL, *k_anno = NULL; MVMuint8 *throw_address; if (IS_CONCRETE(ex_obj) && REPR(ex_obj)->ID == MVM_REPR_ID_MVMException) { cur_frame = ((MVMException *)ex_obj)->body.origin; throw_address = ((MVMException *)ex_obj)->body.throw_address; } else if (ex_obj == tc->instance->VMNull) { cur_frame = tc->cur_frame; throw_address = *tc->interp_cur_op; } else { MVM_exception_throw_adhoc(tc, "Op 'backtrace' needs an exception object"); } MVM_gc_root_temp_push(tc, (MVMCollectable **)&arr); MVM_gc_root_temp_push(tc, (MVMCollectable **)&annotations); MVM_gc_root_temp_push(tc, (MVMCollectable **)&row); MVM_gc_root_temp_push(tc, (MVMCollectable **)&value); MVM_gc_root_temp_push(tc, (MVMCollectable **)&k_file); MVM_gc_root_temp_push(tc, (MVMCollectable **)&k_line); MVM_gc_root_temp_push(tc, (MVMCollectable **)&k_sub); MVM_gc_root_temp_push(tc, (MVMCollectable **)&k_anno); MVM_gc_root_temp_push(tc, (MVMCollectable **)&cur_frame); k_file = MVM_string_ascii_decode_nt(tc, tc->instance->VMString, "file"); k_line = MVM_string_ascii_decode_nt(tc, tc->instance->VMString, "line"); k_sub = MVM_string_ascii_decode_nt(tc, tc->instance->VMString, "sub"); k_anno = MVM_string_ascii_decode_nt(tc, tc->instance->VMString, "annotations"); arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); if (cur_frame) { MVM_spesh_frame_walker_init(tc, &fw, cur_frame, 0); MVM_spesh_frame_walker_next(tc, &fw); // Go to first inline do { cur_frame = MVM_spesh_frame_walker_current_frame(tc, &fw); MVMuint8 *cur_op = count ? cur_frame->return_address : throw_address; MVMuint32 offset = cur_op - MVM_frame_effective_bytecode(cur_frame); MVMBytecodeAnnotation *annot = MVM_bytecode_resolve_annotation(tc, &cur_frame->static_info->body, offset > 0 ? offset - 1 : 0); MVMuint32 fshi = annot ? (MVMint32)annot->filename_string_heap_index : -1; char line_number[11] = {0}; // 11 == 10 (max # of decimal digits in an MVMuint32) + 1 (NULL terminator) MVMString *filename_str; snprintf(line_number, 11, "%d", annot ? annot->line_number : 1); /* annotations hash will contain "file" and "line" */ annotations = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTHash); /* file */ filename_str = annot && fshi < cur_frame->static_info->body.cu->body.num_strings ? MVM_cu_string(tc, cur_frame->static_info->body.cu, fshi) : cur_frame->static_info->body.cu->body.filename; value = MVM_repr_box_str(tc, MVM_hll_current(tc)->str_box_type, filename_str ? filename_str : tc->instance->str_consts.empty); MVM_repr_bind_key_o(tc, annotations, k_file, value); /* line */ value = (MVMObject *)MVM_string_ascii_decode_nt(tc, tc->instance->VMString, line_number); value = MVM_repr_box_str(tc, MVM_hll_current(tc)->str_box_type, (MVMString *)value); MVM_repr_bind_key_o(tc, annotations, k_line, value); /* row will contain "sub" and "annotations" */ row = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTHash); MVM_repr_bind_key_o(tc, row, k_sub, cur_frame->code_ref ? cur_frame->code_ref : tc->instance->VMNull); MVM_repr_bind_key_o(tc, row, k_anno, annotations); MVM_repr_push_o(tc, arr, row); MVM_free(annot); count++; } while (MVM_spesh_frame_walker_move_caller_skip_thunks(tc, &fw)); MVM_spesh_frame_walker_cleanup(tc, &fw); } MVM_gc_root_temp_pop_n(tc, 9); return arr; } /* Returns the lines (backtrace) of an exception-object as an array. */ MVMObject * MVM_exception_backtrace_strings(MVMThreadContext *tc, MVMObject *ex_obj) { MVMException *ex; MVMFrame *cur_frame; MVMObject *arr; if (IS_CONCRETE(ex_obj) && REPR(ex_obj)->ID == MVM_REPR_ID_MVMException) ex = (MVMException *)ex_obj; else MVM_exception_throw_adhoc(tc, "Op 'backtracestrings' needs an exception object"); MVMROOT(tc, ex, { arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); cur_frame = ex->body.origin; MVMROOT2(tc, arr, cur_frame, { MVMuint32 count = 0; while (cur_frame != NULL) { char *line = MVM_exception_backtrace_line(tc, cur_frame, count++, ex->body.throw_address); MVMString *line_str = MVM_string_utf8_decode(tc, tc->instance->VMString, line, strlen(line)); MVMObject *line_obj = MVM_repr_box_str(tc, tc->instance->boot_types.BOOTStr, line_str); MVM_repr_push_o(tc, arr, line_obj); cur_frame = cur_frame->caller; MVM_free(line); } }); }); return arr; } /* Dumps a backtrace relative to the current frame to stderr. */ void MVM_dump_backtrace(MVMThreadContext *tc) { MVMFrame *cur_frame = tc->cur_frame; MVMuint32 count = 0; MVMROOT(tc, cur_frame, { while (cur_frame != NULL) { char *line = MVM_exception_backtrace_line(tc, cur_frame, count++, *(tc->interp_cur_op)); fprintf(stderr, "%s\n", line); MVM_free(line); cur_frame = cur_frame->caller; } }); } /* Panic over an unhandled exception throw by category. */ static void panic_unhandled_cat(MVMThreadContext *tc, MVMuint32 cat) { /* If it's a control exception, try promoting it to a catch one. */ if (cat != MVM_EX_CAT_CATCH) { MVM_exception_throw_adhoc(tc, "No exception handler located for %s", cat_name(tc, cat)); } else { if (tc->nested_interpreter) fputs("An unhandled exception occurred in a native callback.\n" "This situation is not recoverable, and the program will terminate.\n" "The stack trace below helps indicate which library needs fixing.\n" "The exception was thrown at:\n", stderr); else fprintf(stderr, "No exception handler located for %s\n", cat_name(tc, cat)); MVM_dump_backtrace(tc); if (crash_on_error) abort(); else exit(1); } } /* Panic over an unhandled exception object. */ static void panic_unhandled_ex(MVMThreadContext *tc, MVMException *ex) { char *backtrace; /* If a debug session is running, notify the client. */ MVMROOT(tc, ex, { MVM_debugserver_notify_unhandled_exception(tc, ex); }); /* If it's a control exception, try promoting it to a catch one; use * the category name. */ if (ex->body.category != MVM_EX_CAT_CATCH) panic_unhandled_cat(tc, ex->body.category); /* If there's no message, fall back to category also. */ if (!ex->body.message) panic_unhandled_cat(tc, ex->body.category); /* Otherwise, dump message and a backtrace. */ backtrace = MVM_string_utf8_encode_C_string(tc, ex->body.message); fprintf(stderr, "Unhandled exception: %s\n", backtrace); MVM_free(backtrace); MVM_dump_backtrace(tc); if (crash_on_error) abort(); else exit(1); } /* Checks if we're throwing lexically, and - if yes - if the current HLL has * a handler for unlocated lexical handlers. */ static MVMint32 use_lexical_handler_hll_error(MVMThreadContext *tc, MVMuint8 mode) { return (mode == MVM_EX_THROW_LEX || mode == MVM_EX_THROW_LEX_CALLER) && !MVM_is_null(tc, (MVMObject *)MVM_hll_current(tc)->lexical_handler_not_found_error); } /* Invokes the HLL's handler for unresolved lexical throws. */ static void invoke_lexical_handler_hll_error(MVMThreadContext *tc, MVMint64 cat, LocatedHandler lh) { MVMCode *handler = MVM_hll_current(tc)->lexical_handler_not_found_error; MVMCallStackArgsFromC *args_record = MVM_callstack_allocate_args_from_c(tc, MVM_callsite_get_common(tc, MVM_CALLSITE_ID_INT_INT)); args_record->args.source[0].i64 = cat; args_record->args.source[1].i64 = lh.handler_out_of_dynamic_scope; MVM_frame_dispatch_from_c(tc, handler, args_record, NULL, MVM_RETURN_VOID); } /* Throws an exception by category, searching for a handler according to * the specified mode. If the handler resumes, the resumption result will * be put into resume_result. Leaves the interpreter in a state where it * will next run the instruction of the handler. If there is no handler, * it will panic and exit with a backtrace. */ void MVM_exception_throwcat(MVMThreadContext *tc, MVMuint8 mode, MVMuint32 cat, MVMRegister *resume_result) { LocatedHandler lh = search_for_handler_from(tc, tc->cur_frame, mode, cat, NULL); if (lh.frame == NULL) { if (use_lexical_handler_hll_error(tc, mode)) { invoke_lexical_handler_hll_error(tc, cat, lh); return; } panic_unhandled_cat(tc, cat); } run_handler(tc, lh, NULL, cat, NULL); } void MVM_exception_die(MVMThreadContext *tc, MVMString *str, MVMRegister *rr) { MVMException *ex; MVMROOT(tc, str, { ex = (MVMException *)MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTException); }); ex->body.category = MVM_EX_CAT_CATCH; MVM_ASSIGN_REF(tc, &(ex->common.header), ex->body.message, str); MVM_exception_throwobj(tc, MVM_EX_THROW_DYN, (MVMObject *)ex, rr); } /* Throws the specified exception object, taking the category from it. If * the handler resumes, the resumption result will be put into resume_result. * Leaves the interpreter in a state where it will next run the instruction of * the handler. If there is no handler, it will panic and exit with a backtrace. */ void MVM_exception_throwobj(MVMThreadContext *tc, MVMuint8 mode, MVMObject *ex_obj, MVMRegister *resume_result) { LocatedHandler lh; MVMException *ex; /* The current frame will be assigned as the thrower of the exception, so * force it onto the heap before we begin (promoting it later would mean * outer handler search result would be outdated). */ MVMROOT(tc, ex_obj, { MVM_frame_force_to_heap(tc, tc->cur_frame); }); if (IS_CONCRETE(ex_obj) && REPR(ex_obj)->ID == MVM_REPR_ID_MVMException) ex = (MVMException *)ex_obj; else MVM_exception_throw_adhoc(tc, "Can only throw an exception object"); if (!ex->body.category) ex->body.category = MVM_EX_CAT_CATCH; if (resume_result) { ex->body.resume_addr = *tc->interp_cur_op; /* Ensure that we store label where the JIT should return, if any */ if (tc->jit_return_address != NULL) { ex->body.jit_resume_label = MVM_jit_code_get_current_position(tc, tc->cur_frame->spesh_cand->body.jitcode, tc->cur_frame); } } lh = search_for_handler_from(tc, tc->cur_frame, mode, ex->body.category, ex->body.payload); if (lh.frame == NULL) { if (use_lexical_handler_hll_error(tc, mode)) { invoke_lexical_handler_hll_error(tc, ex->body.category, lh); return; } panic_unhandled_ex(tc, ex); } if (!ex->body.origin) { MVM_ASSIGN_REF(tc, &(ex->common.header), ex->body.origin, tc->cur_frame); ex->body.throw_address = *(tc->interp_cur_op); } run_handler(tc, lh, ex_obj, 0, NULL); } /* Throws an exception of the specified category and with the specified payload. * If a goto or payload handler exists, then no exception object will be created. */ void MVM_exception_throwpayload(MVMThreadContext *tc, MVMuint8 mode, MVMuint32 cat, MVMObject *payload, MVMRegister *resume_result) { LocatedHandler lh = search_for_handler_from(tc, tc->cur_frame, mode, cat, NULL); if (lh.frame == NULL) { if (use_lexical_handler_hll_error(tc, mode)) { invoke_lexical_handler_hll_error(tc, cat, lh); return; } panic_unhandled_cat(tc, cat); } run_handler(tc, lh, NULL, cat, payload); } void MVM_exception_resume(MVMThreadContext *tc, MVMObject *ex_obj) { MVMException *ex; if (IS_CONCRETE(ex_obj) && REPR(ex_obj)->ID == MVM_REPR_ID_MVMException) ex = (MVMException *)ex_obj; else MVM_exception_throw_adhoc(tc, "Can only resume an exception object"); /* Check that everything is in place to do the resumption. */ if (!ex->body.resume_addr) MVM_exception_throw_adhoc(tc, "This exception is not resumable"); MVMFrame *target = ex->body.origin; if (!target) MVM_exception_throw_adhoc(tc, "This exception is not resumable"); if (!in_caller_chain(tc, target)) MVM_exception_throw_adhoc(tc, "Too late to resume this exception"); /* Check that this is the exception we're currently handling. */ if (!tc->active_handlers) MVM_exception_throw_adhoc(tc, "Can only resume an exception in its handler"); if (tc->active_handlers->ex_obj != ex_obj) MVM_exception_throw_adhoc(tc, "Can only resume the current exception"); /* Unwind to the thrower of the exception; set PC and jit entry label. */ MVM_frame_unwind_to(tc, target, ex->body.resume_addr, 0, NULL, ex->body.jit_resume_label); } /* Panics and shuts down the VM. Don't do this unless it's something quite * unrecoverable, and a thread context is either not available or stands a * good chance of being too corrupt to print (or is not relevant information). * Use MVM_oops in the case a thread context is available. * TODO: Some hook for embedders. */ MVM_NO_RETURN void MVM_panic(MVMint32 exitCode, const char *messageFormat, ...) { va_list args; fputs("MoarVM panic: ", stderr); va_start(args, messageFormat); vfprintf(stderr, messageFormat, args); va_end(args); fputc('\n', stderr); if (crash_on_error) abort(); else exit(exitCode); } MVM_NO_RETURN void MVM_panic_allocation_failed(size_t len) { MVM_panic(1, "Memory allocation failed; could not allocate %"MVM_PRSz" bytes", len); } /* A kinder MVM_panic() that doesn't assume our memory is corrupted (but does kill the * process to indicate that we've made an error */ MVM_NO_RETURN void MVM_oops(MVMThreadContext *tc, const char *messageFormat, ...) { va_list args; fprintf(stderr, "MoarVM oops%s: ", !tc ? " with NULL tc" : (MVMObject *) tc->thread_obj == tc->instance->spesh_thread ? " in spesh thread" : (MVMObject *) tc->thread_obj == tc->instance->event_loop_thread ? " in event loop thread" : ""); va_start(args, messageFormat); vfprintf(stderr, messageFormat, args); va_end(args); fputc('\n', stderr); /* Our caller is seriously buggy if tc is NULL */ if (!tc) abort(); MVM_dump_backtrace(tc); fputc('\n', stderr); exit(1); } /* Throws an ad-hoc (untyped) exception. */ MVM_NO_RETURN void MVM_exception_throw_adhoc(MVMThreadContext *tc, const char *messageFormat, ...) { va_list args; va_start(args, messageFormat); MVM_exception_throw_adhoc_free_va(tc, NULL, messageFormat, args); va_end(args); } /* Throws an ad-hoc (untyped) exception. */ MVM_NO_RETURN void MVM_exception_throw_adhoc_va(MVMThreadContext *tc, const char *messageFormat, va_list args) { MVM_exception_throw_adhoc_free_va(tc, NULL, messageFormat, args); } /* Throws an ad-hoc (untyped) exception, taking a NULL-terminated array of * char pointers to deallocate after message construction. */ MVM_NO_RETURN void MVM_exception_throw_adhoc_free(MVMThreadContext *tc, char **waste, const char *messageFormat, ...) { va_list args; va_start(args, messageFormat); MVM_exception_throw_adhoc_free_va(tc, waste, messageFormat, args); va_end(args); } /* Throws an ad-hoc (untyped) exception, taking a NULL-terminated array of * char pointers to deallocate after message construction. */ MVM_NO_RETURN void MVM_exception_throw_adhoc_free_va(MVMThreadContext *tc, char **waste, const char *messageFormat, va_list args) { LocatedHandler lh; MVMException *ex; const char *special = !tc ? " with NULL tc" : (MVMObject *) tc->thread_obj == tc->instance->spesh_thread ? " in spesh thread" : (MVMObject *) tc->thread_obj == tc->instance->event_loop_thread ? " in event loop thread" : NULL; if (special) { fprintf(stderr, "MoarVM exception%s treated as oops: ", special); vfprintf(stderr, messageFormat, args); va_end(args); fputc('\n', stderr); if (!tc) abort(); MVM_dump_backtrace(tc); fputc('\n', stderr); exit(1); } /* The current frame will be assigned as the thrower of the exception, so * force it onto the heap before we begin. */ if (tc->cur_frame) MVM_frame_force_to_heap(tc, tc->cur_frame); /* Create and set up an exception object. */ ex = (MVMException *)MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTException); MVMROOT(tc, ex, { char *c_message = MVM_malloc(1024); int bytes = vsnprintf(c_message, 1024, messageFormat, args); int to_encode = bytes > 1024 ? 1024 : bytes; MVMString *message = MVM_string_utf8_decode(tc, tc->instance->VMString, c_message, to_encode); MVM_free(c_message); /* Clean up after ourselves to avoid leaking C strings. */ if (waste) { while(*waste) MVM_free(*waste++); } MVM_ASSIGN_REF(tc, &(ex->common.header), ex->body.message, message); if (tc->cur_frame) { ex->body.origin = tc->cur_frame; ex->body.throw_address = *(tc->interp_cur_op); } else { ex->body.origin = NULL; } ex->body.category = MVM_EX_CAT_CATCH; }); /* Try to locate a handler, so long as we're in the interpreter. */ if (tc->interp_cur_op) lh = search_for_handler_from(tc, tc->cur_frame, MVM_EX_THROW_DYN, ex->body.category, NULL); else lh.frame = NULL; /* Do we have a handler to unwind to? */ if (lh.frame == NULL) { /* No handler. Should we crash on these? */ if (crash_on_error) { /* Yes, abort. */ vfprintf(stderr, messageFormat, args); fputc('\n', stderr); MVM_dump_backtrace(tc); abort(); } else { /* No, just the usual panic. */ panic_unhandled_ex(tc, ex); } } /* Run the handler, which doesn't actually run it but rather sets up the * interpreter so that when we return to it, we'll be at the handler. */ run_handler(tc, lh, (MVMObject *)ex, MVM_EX_CAT_CATCH, NULL); /* Clear any C stack temporaries that code may have pushed before throwing * the exception, and release any needed mutex. */ MVM_gc_root_temp_pop_all(tc); MVM_tc_release_ex_release_mutex(tc); /* Jump back into the interpreter. */ longjmp(tc->interp_jump, 1); } void MVM_crash_on_error(void) { crash_on_error = 1; } MVMint32 MVM_get_exception_category(MVMThreadContext *tc, MVMObject *ex) { if (IS_CONCRETE(ex) && REPR(ex)->ID == MVM_REPR_ID_MVMException) return ((MVMException *)ex)->body.category; else MVM_exception_throw_adhoc(tc, "getexcategory needs a VMException, got %s (%s)", REPR(ex)->name, MVM_6model_get_debug_name(tc, ex)); } MVMObject * MVM_get_exception_payload(MVMThreadContext *tc, MVMObject *ex) { MVMObject *result; if (IS_CONCRETE(ex) && REPR(ex)->ID == MVM_REPR_ID_MVMException) result = ((MVMException *)ex)->body.payload; else MVM_exception_throw_adhoc(tc, "getexpayload needs a VMException, got %s (%s)", REPR(ex)->name, MVM_6model_get_debug_name(tc, ex)); if (!result) result = tc->instance->VMNull; return result; } MVMString * MVM_get_exception_message(MVMThreadContext *tc, MVMObject *ex) { if (IS_CONCRETE(ex) && REPR(ex)->ID == MVM_REPR_ID_MVMException) return ((MVMException *)ex)->body.message; else MVM_exception_throw_adhoc(tc, "getexmessage needs a VMException, got %s (%s)", REPR(ex)->name, MVM_6model_get_debug_name(tc, ex)); } void MVM_bind_exception_message(MVMThreadContext *tc, MVMObject *ex, MVMString *message) { if (IS_CONCRETE(ex) && REPR(ex)->ID == MVM_REPR_ID_MVMException) { MVM_ASSIGN_REF(tc, &(ex->header), ((MVMException *)ex)->body.message, message); } else { MVM_exception_throw_adhoc(tc, "bindexmessage needs a VMException, got %s (%s)", REPR(ex)->name, MVM_6model_get_debug_name(tc, ex)); } } void MVM_bind_exception_payload(MVMThreadContext *tc, MVMObject *ex, MVMObject *payload) { if (IS_CONCRETE(ex) && REPR(ex)->ID == MVM_REPR_ID_MVMException) { MVM_ASSIGN_REF(tc, &(ex->header), ((MVMException *)ex)->body.payload, payload); } else { MVM_exception_throw_adhoc(tc, "bindexpayload needs a VMException, got %s (%s)", REPR(ex)->name, MVM_6model_get_debug_name(tc, ex)); } } void MVM_bind_exception_category(MVMThreadContext *tc, MVMObject *ex, MVMint32 category) { if (IS_CONCRETE(ex) && REPR(ex)->ID == MVM_REPR_ID_MVMException) ((MVMException *)ex)->body.category = category; else MVM_exception_throw_adhoc(tc, "bindexcategory needs a VMException, got %s (%s)", REPR(ex)->name, MVM_6model_get_debug_name(tc, ex)); } void MVM_exception_returnafterunwind(MVMThreadContext *tc, MVMObject *ex) { if (IS_CONCRETE(ex) && REPR(ex)->ID == MVM_REPR_ID_MVMException) ((MVMException *)ex)->body.return_after_unwind = 1; else MVM_exception_throw_adhoc(tc, "exreturnafterunwind needs a VMException, got %s (%s)", REPR(ex)->name, MVM_6model_get_debug_name(tc, ex)); }