Feature #839

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

Added by Roger Pack over 5 years ago. Updated about 2 years ago.

[ruby-core:20416]
Status:Rejected
Priority:Normal
Assignee:Yukihiro Matsumoto
Category:core
Target version:2.0.0

Description

=begin
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 `'
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
=end

add_code_output.diff Magnifier (2.26 KB) Roger Pack, 12/08/2008 06:29 PM


Related issues

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

History

#1 Updated by Nobuyoshi Nakada over 5 years ago

=begin
Hi,

At Mon, 8 Dec 2008 18:23:04 +0900,
Roger Pack wrote in :

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
'

Index: error.c
===================================================================
--- error.c (revision 20580)
+++ error.c (working copy)
@@ -510,16 +510,31 @@ rbcheckbacktrace(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 == TSTRING) return rbary_new3(1, bt);
  • if (t != T_ARRAY) {
  • if (IMMEDIATE_P(bt)) {
  • rbraise(rbeTypeError, err);
  • }
  • switch (BUILTIN_TYPE(bt)) {
  • case T_STRING:
  • return rbarynew3(1, bt);
  • case T_ARRAY:
  • break;
  • case T_STRUCT:
  • if (CLASSOF(bt) == rbcBacktrace)
  • return rbarynew3(1, bt);
  • default: rbraise(rbeTypeError, err); } for (i=0;i<RARRAY_LEN(bt);i++) {
  • if (TYPE(RARRAYPTR(bt)[i]) != TSTRING) {
  • rbraise(rbeTypeError, err);
  • VALUE a = RARRAY_PTR(bt)[i];
  • if (!IMMEDIATE_P(a)) {
  • switch (BUILTIN_TYPE(a)) {
  • case T_STRING: continue;
  • case T_STRUCT:
  • if (CLASSOF(a) == rbcBacktrace) continue;
  • } }
  •  rb_raise(rb_eTypeError, err);
    

    }
    }

    Index: eval_error.c

    --- evalerror.c (revision 20580)
    +++ eval
    error.c (working copy)
    @@ -64,4 +64,25 @@ set_backtrace(VALUE info, VALUE bt)
    }

    +static void
    +printdebugline(VALUE *debug_lines, VALUE at)
    +{

  • extern VALUE rb_cBacktrace;

  • VALUE hash = *debug_lines, lines, line, *p;
    +

  • if (TYPE(at) != TSTRUCT || CLASSOF(at) != rb_cBacktrace) return;

  • if (!hash) {

  • if (!rbconstdefinedat(rbcObject, rbintern("SCRIPTLINES__"))) return;

  • hash = rbconstgetat(rbcObject, rbintern("SCRIPTLINES__"));

  • if (TYPE(hash) != T_HASH) return;

  • *debug_lines = hash;

  • }

  • p = RSTRUCT_PTR(at);

  • lines = rbhashlookup(hash, p[0]);

  • if (NIL_P(lines)) return;

  • line = rbaryentry(lines, NUM2INT(p[1]));

  • if (NIL_P(line)) return;

  • warnprintf("\t\t %s", RSTRINGPTR(line));
    +}
    +
    static void
    errorprint(void)
    @@ -72,4 +93,5 @@ error
    print(void)
    const char *einfo;
    long elen;

  • VALUE debug_lines = 0;

    if (NILP(errinfo))
    @@ -99,4 +121,5 @@ error
    print(void)
    VALUE mesg = RARRAY_PTR(errat)[0];

  • mesg = rbcheckstringtype(mesg);
    if (NIL
    P(mesg))
    errorpos();
    @@ -121,4 +144,7 @@ error
    print(void)
    if (eclass == rbeRuntimeError && 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 rbcThread;
    VALUE rb
    cEnv;
    VALUE rbmRubyVMFrozenCore;
    +VALUE rb
    cBacktrace;

    VALUE rubyvmglobalstateversion = 1;
    @@ -629,4 +630,22 @@ rblastlineset(VALUE val)
    /* backtrace */

    +static VALUE
    +backtracetostr(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
    vmgetsourceline(const rbcontrolframet *cfp)
    @@ -654,10 +673,10 @@ static VALUE
    vm
    backtraceeach(rbthreadt *th,
    const rb
    controlframet *limitcfp, const rbcontrolframet *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 @@ vmbacktraceeach(rbthreadt *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 (RUBYVMCFUNCFRAME_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 = RUBYVMNEXTCONTROLFRAME(cfp);
    @@ -704,6 +725,5 @@ vmbacktrace(rbthread_t *th, int lev)
    }

  • ary = vmbacktraceeach(th, RUBYVMNEXTCONTROLFRAME(cfp),

  •          top_of_cfp, "", 0, ary);
    
  • ary = vmbacktraceeach(th, RUBYVMNEXTCONTROLFRAME(cfp), topofcfp, ary);
    return ary;
    }
    @@ -1847,4 +1867,9 @@ InitVM(void)
    rb
    defineconst(rbcRubyVM, "INSTRUCTIONNAMES", rubyinsnsnamearray());

  • rbcBacktrace = rbstruct_define((char)0, "file", "line", "name", "str", (char)0);

  • rbdefineconst(rbcRubyVM, "Backtrace", rbcBacktrace);

  • rbdefinemethod(rbcBacktrace, "tostr", backtracetostr, 0);

  • rbdefinemethod(rbcBacktrace, "inspect", backtraceto_str, 0);
    +
    /* debug functions ::VM::SDR(), ::VM::NSDR() */
    #if VMDEBUG

    Nobu Nakada

=end

#2 Updated by Roger Pack over 5 years ago

=begin
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
=end

#3 Updated by Roger Pack over 5 years ago

=begin
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 settracefunc 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/

=end

#4 Updated by Roger Pack over 5 years ago

=begin
How about a compromise to propagate SCRIPTLINES_ 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
=end

#5 Updated by Roger Pack over 5 years ago

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

#6 Updated by Shyouhei Urabe about 5 years ago

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

=begin

=end

#7 Updated by Roger Pack over 4 years ago

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

#8 Updated by Roger Pack over 4 years ago

=begin
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
=end

#9 Updated by Yusuke Endoh about 4 years ago

=begin
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
=end

#10 Updated by Kazuhiro NISHIYAMA about 4 years ago

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

=begin

=end

#11 Updated by Roger Pack about 4 years ago

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

#12 Updated by Roger Pack almost 4 years ago

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

#13 Updated by Shyouhei Urabe over 3 years ago

  • Status changed from Open to Assigned

=begin

=end

#14 Updated by Motohiro KOSAKI about 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

Can anyone take a feedback? If nothing, I have to close this ticket sadly.

#15 Updated by Thomas Sawyer about 2 years 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.

#16 Updated by Motohiro KOSAKI about 2 years 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