Feature #839

Add code on each line of a backtrace output to the screen

Added by Roger Pack about 3 years ago. Updated 2 days ago.

[ruby-core:20416]
Status:Rejected Start date:12/08/2008
Priority:Normal Due date:
Assignee:Yukihiro Matsumoto % Done:

0%

Category:core
Target version:2.0.0

Description

This patch adds output to unrescued exceptions' output to the command line:

C:\dev\downloads\snap_snapshot>cat bad2.rb
def bad
 raise
end
def good
 bad
end
good

> ruby19 bad2.rb
bad2.rb:2:in `bad': unhandled exception
                 raise
        from bad2.rb:5:in `good'
                 bad
        from bad2.rb:7:in `<main>'
                 good

Wasn't sure if there is a better way to code this up or what not, but here it is.  Similar measures could be applied to 1.8.7.
Thanks!
-=R

add_code_output.diff (2.3 kB) Roger Pack, 12/08/2008 06:29 pm


Related issues

related to ruby-trunk - Feature #1906: Kernel#backtrace: Objectifying Kernel#caller Assigned 08/07/2009

History

Updated by Nobuyoshi Nakada about 3 years ago

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

Updated by Roger Pack about 3 years ago

I'd love to try it out, but am having trouble patching TRUNK with it.  Could you give me a hint as to which revision it is a patch from?  Is it from trunk?
Thanks!
-=R

Updated by Roger Pack about 3 years ago

heh--I see it--revision 20850, from the diff.  That one works for applying it.

You're right--something that didn't require reading files would be nice.
I suppose the only drawback would be...shouldn't tracer output
>> enter x
<< leave x


and if so, wouldn't requiring users to include tracer possibly cause too much console output to be worth the added verbose output?

Here's an interesting related request:

I also wonder if someday something like this would be possible [1]
NoMethodError: undefined method `capitalize' for nil:NilClass
    from /home/pwilliams/projects/tmp/test.rb:18:in main.greet_user(nil, "williams")
    from /home/pwilliams/projects/tmp/test.rb:14:in main.third(nil, "williams")
    from /home/pwilliams/projects/tmp/test.rb:10:in main.second("williams, peter")
    from /home/pwilliams/projects/tmp/test.rb:4:in main.first("Peter Williams")
    from (irb):41
    from :0

I can think of a way to do this using set_trace_func style stuff to track parameters but that's about it...

I'll look at it more sometime.

Thoughts?
-=R

[1] http://barelyenough.org/blog/2005/04/ruby-backtraces/

Updated by Roger Pack about 3 years ago

How about a compromise to propagate SCRIPT_LINES__ after the fact with the appropriate files if they're not in it yet?  I'd be happy to do it, and it thus wouldn't result in a large slowdown [and if files have changed recently, the user will probably be quite aware of that fact].
Or maybe running it -r"something" would be sufficient.

Thoughts?
-=R

Updated by Roger Pack about 3 years ago

I suppose another option would be to only retrieve the line for the topmost line of the backtrace. Thoughts?

Updated by Shyouhei Urabe about 3 years ago

  • Assignee set to Yukihiro Matsumoto
  • Target version set to 1.9.2

Updated by Roger Pack over 2 years ago

I like this patch (nobu's).  Would it be possible to get it applied?
-r

Updated by Roger Pack over 2 years ago

I still like this patch (applied to caller and/or Exception#backtrace).

Who needs to approve it...?

Thanks.
-r

Related previous requests:

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/4822
http://barelyenough.org/blog/2005/04/ruby-backtraces
http://www.ruby-forum.com/topic/198847
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/4822

Updated by Yusuke Endoh almost 2 years ago

Hi,

Matz, what do you think about this ticket and #1906?

There is a patch nobu has written (though I have not tested):
http://redmine.ruby-lang.org/issues/show/839#note-1

But it might take some time to make stable the feature, so please
let us know your opinion or decision as soon as possible.

-- 
Yusuke Endoh <mame@tsg.ne.jp>

Updated by Kazuhiro NISHIYAMA almost 2 years ago

  • Category set to core
  • Target version changed from 1.9.2 to 2.0.0

Updated by Roger Pack almost 2 years ago

Matz if you get a chance to look at this patch, it "objectify's" Exception#backtrace which might be nice.
Thank you.
-roger

Updated by Roger Pack over 1 year ago

Any feedback on this? (objectify Exception#backtrace et al)
Thanks.

Updated by Shyouhei Urabe over 1 year ago

  • Status changed from Open to Assigned

Updated by Motohiro KOSAKI 2 days ago

> Matz if you get a chance to look at this patch, it "objectify's" Exception#backtrace which might be nice. > Thank you. > -roger Can anyone take a feedback? If nothing, I have to close this ticket sadly.

Updated by Thomas Sawyer 2 days ago

If all it is, is to add source code line to backtrace then I don think that's enough. I think the feature people would like to see in this area is an objectified backtrace, e.g. error.objectified_backtrace.each do |b| b.file #=> '/home/pwilliams/projects/tmp/test.rb' b.line #=> 18 b.in #=> "<main>" b.source #=> "greet_user(nil, \"williams\")" b.to_s #=> "from /home/pwilliams/projects/tmp/test.rb:18:in `<main>'" I believe there is a gem called 'callsite' which does something like this. Maybe others too.

Updated by Motohiro KOSAKI 2 days ago

  • Status changed from Assigned to Rejected
> I believe there is a gem called 'callsite' which does something like this. Maybe others too. OK, thank you for giving very useful comment. now I can close this one safely.

Also available in: Atom PDF