Hi,

At Mon, 8 Dec 2008 18:23:04 +0900,
Roger Pack wrote in [ruby-core:20416]:
> This patch adds output to unrescued exceptions' output to the command line:

Rereading from scripts would have problems:
* they can be removed or changed
* -e and stdin are N/A
* slow.

Instead, isn't it enough only when debugging?

$ ./ruby -rtracer -e 'def foo;raise;end' -e foo
-e:1:in `foo': unhandled exception
		 foo
	from -e:2:in `<main>'


Index: error.c =================================================================== --- error.c (revision 20580) +++ error.c (working copy) @@ -510,16 +510,31 @@ rb_check_backtrace(VALUE bt) long i; static const char err[] = "backtrace must be Array of String"; + extern VALUE rb_cBacktrace; if (!NIL_P(bt)) { - int t = TYPE(bt); - - if (t == T_STRING) return rb_ary_new3(1, bt); - if (t != T_ARRAY) { + if (IMMEDIATE_P(bt)) { + rb_raise(rb_eTypeError, err); + } + switch (BUILTIN_TYPE(bt)) { + case T_STRING: + return rb_ary_new3(1, bt); + case T_ARRAY: + break; + case T_STRUCT: + if (CLASS_OF(bt) == rb_cBacktrace) + return rb_ary_new3(1, bt); + default: rb_raise(rb_eTypeError, err); } for (i=0;i<RARRAY_LEN(bt);i++) { - if (TYPE(RARRAY_PTR(bt)[i]) != T_STRING) { - rb_raise(rb_eTypeError, err); + VALUE a = RARRAY_PTR(bt)[i]; + if (!IMMEDIATE_P(a)) { + switch (BUILTIN_TYPE(a)) { + case T_STRING: continue; + case T_STRUCT: + if (CLASS_OF(a) == rb_cBacktrace) continue; + } } + rb_raise(rb_eTypeError, err); } } Index: eval_error.c =================================================================== --- eval_error.c (revision 20580) +++ eval_error.c (working copy) @@ -64,4 +64,25 @@ set_backtrace(VALUE info, VALUE bt) } +static void +print_debug_line(VALUE *debug_lines, VALUE at) +{ + extern VALUE rb_cBacktrace; + VALUE hash = *debug_lines, lines, line, *p; + + if (TYPE(at) != T_STRUCT || CLASS_OF(at) != rb_cBacktrace) return; + if (!hash) { + if (!rb_const_defined_at(rb_cObject, rb_intern("SCRIPT_LINES__"))) return; + hash = rb_const_get_at(rb_cObject, rb_intern("SCRIPT_LINES__")); + if (TYPE(hash) != T_HASH) return; + *debug_lines = hash; + } + p = RSTRUCT_PTR(at); + lines = rb_hash_lookup(hash, p[0]); + if (NIL_P(lines)) return; + line = rb_ary_entry(lines, NUM2INT(p[1])); + if (NIL_P(line)) return; + warn_printf("\t\t %s", RSTRING_PTR(line)); +} + static void error_print(void) @@ -72,4 +93,5 @@ error_print(void) const char *einfo; long elen; + VALUE debug_lines = 0; if (NIL_P(errinfo)) @@ -99,4 +121,5 @@ error_print(void) VALUE mesg = RARRAY_PTR(errat)[0]; + mesg = rb_check_string_type(mesg); if (NIL_P(mesg)) error_pos(); @@ -121,4 +144,7 @@ error_print(void) if (eclass == rb_eRuntimeError && elen == 0) { warn_print(": unhandled exception\n"); + if (!NIL_P(errat)) { + print_debug_line(&debug_lines, RARRAY_PTR(errat)[0]); + } } else { @@ -166,6 +192,9 @@ error_print(void) for (i = 1; i < len; i++) { - if (TYPE(ptr[i]) == T_STRING) { - warn_printf("\tfrom %s\n", RSTRING_PTR(ptr[i])); + VALUE a = ptr[i], s; + s = rb_check_string_type(a); + if (!NIL_P(s)) { + warn_printf("\tfrom %s\n", RSTRING_PTR(s)); + print_debug_line(&debug_lines, a); } if (skip && i == TRACE_HEAD && len > TRACE_MAX) { Index: vm.c =================================================================== --- vm.c (revision 20580) +++ vm.c (working copy) @@ -33,4 +33,5 @@ VALUE rb_cThread; VALUE rb_cEnv; VALUE rb_mRubyVMFrozenCore; +VALUE rb_cBacktrace; VALUE ruby_vm_global_state_version = 1; @@ -629,4 +630,22 @@ rb_lastline_set(VALUE val) /* backtrace */ +static VALUE +backtrace_to_str(VALUE self) +{ + VALUE *p = RSTRUCT_PTR(self); + VALUE file = p[0], line = p[1], name = p[2]; + VALUE str = p[3]; + if (NIL_P(str)) { + str = rb_sprintf("%s:%d:in `%s'", + NIL_P(file) ? "" : StringValueCStr(file), + NUM2INT(line), StringValueCStr(name)); + if (!OBJ_FROZEN(self) && + (OBJ_UNTRUSTED(self) || rb_safe_level() < 4)) { + p[3] = str; + } + } + return str; +} + int vm_get_sourceline(const rb_control_frame_t *cfp) @@ -654,10 +673,10 @@ static VALUE vm_backtrace_each(rb_thread_t *th, const rb_control_frame_t *limit_cfp, const rb_control_frame_t *cfp, - const char * file, int line_no, VALUE ary) + VALUE ary) { - VALUE str; + VALUE bt, b[3]; + int line_no; while (cfp > limit_cfp) { - str = 0; if (cfp->iseq != 0) { if (cfp->pc != 0) { @@ -665,15 +684,17 @@ vm_backtrace_each(rb_thread_t *th, line_no = vm_get_sourceline(cfp); - file = RSTRING_PTR(iseq->filename); - str = rb_sprintf("%s:%d:in `%s'", - file, line_no, RSTRING_PTR(iseq->name)); - rb_ary_push(ary, str); + b[0] = iseq->filename; + b[1] = INT2NUM(line_no); + b[2] = iseq->name; + bt = rb_class_new_instance(sizeof(b) / sizeof(*b), b, rb_cBacktrace); + rb_ary_push(ary, bt); } } else if (RUBYVM_CFUNC_FRAME_P(cfp)) { - str = rb_sprintf("%s:%d:in `%s'", - file, line_no, - rb_id2name(cfp->method_id)); - rb_ary_push(ary, str); + b[0] = Qnil; + b[1] = INT2FIX(0); + b[2] = rb_id2str(cfp->method_id); + bt = rb_class_new_instance(sizeof(b) / sizeof(*b), b, rb_cBacktrace); + rb_ary_push(ary, bt); } cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp); @@ -704,6 +725,5 @@ vm_backtrace(rb_thread_t *th, int lev) } - ary = vm_backtrace_each(th, RUBY_VM_NEXT_CONTROL_FRAME(cfp), - top_of_cfp, "", 0, ary); + ary = vm_backtrace_each(th, RUBY_VM_NEXT_CONTROL_FRAME(cfp), top_of_cfp, ary); return ary; } @@ -1847,4 +1867,9 @@ Init_VM(void) rb_define_const(rb_cRubyVM, "INSTRUCTION_NAMES", ruby_insns_name_array()); + rb_cBacktrace = rb_struct_define((char*)0, "file", "line", "name", "str", (char*)0); + rb_define_const(rb_cRubyVM, "Backtrace", rb_cBacktrace); + rb_define_method(rb_cBacktrace, "to_str", backtrace_to_str, 0); + rb_define_method(rb_cBacktrace, "inspect", backtrace_to_str, 0); + /* debug functions ::VM::SDR(), ::VM::NSDR() */ #if VMDEBUG
-- Nobu Nakada