From: Rocky Bernstein Date: 2010-03-18T19:52:41+09:00 Subject: [ruby-core:28742] Re: When a trace hook raises an exception, should it terminate the program? --000e0cd51e2c8fc36304821107bd Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Let me clarify a bit because I think some of the facts (some by me) may hav= e been misrepresented. I came across this in implementing a debugger command called "raise". The purpose of this is to force an exception to occur outside of the debugger o= r in the debugged program. And in fact this does work. Sort of.. $ rbdbgr xyz.rb -- (xyz.rb:1) begin (rbdbgr): list 1 -> begin 2 x =3D 1 3 rescue =3D> e 4 puts "rescued #{e.inspect}" 5 end (rbdbgr): step -- (xyz.rb:2) x =3D 1 (rbdbgr): list 1 begin 2 -> x =3D 1 3 rescue =3D> e 4 puts "rescued #{e.inspect}" 5 end (rbdbgr): raise RuntimeError rescued # $ The "sort of" part is that in Ruby 1.9 the implementation of tracing sets = a bit in the thread structure which turns off tracing into the tracer (or debugging into the debugger). When an exception that passes from hook to non-hook occurs, this bit doesn't get cleared. Changing the program that was originally posted will show this better: s =3D Proc.new { |event, file, lineno, mid, binding, klass| puts "#{event} #{mid} #{lineno}" unless $x $x =3D true raise RuntimeError end } begin set_trace_func(s) puts "after trace func" rescue puts "Rescued" end puts "After begin" Run this in Ruby 1.9 and perhaps it is more apparent that the program continues to the end. In the original posting if you look carefully you'll see that the rescue did run *after* the trace hook issued a raise inside th= e begin block. It is the *second* raise issued in the hook function that terminates the program because the debugged program is no longer in a rescue'd block. From what you describe from JRuby, it sounds like it has a similar behavior possibly because it is implemented underneath in a similar way. So rationalizing this as "this makes sense because this happened in between statements and not in the program" I don't think is accurately capturing what's going inside either interpreter. One of the uses of a trace hook is in a debugger where it is normal to have code run in between the debugged program's statements which have lasting effect. For example that's how variables can get changed. So again, how is raising an exception from a trace hook defined? Or how should it be defined? Trace hooks in general could be undefined so anything you want to do is oka= y including not implement them. That would cover the current behavior. Similarly it could be that if if a hook raises is a exception that it doesn't catch you get undefined behavior. That too would cover the current behavior. But declaring that an uncaught exception in a trace hook terminates a program does not accurately describe how Ruby 1.9 (and probably JRuby) currently behave. On Thu, Mar 18, 2010 at 1:59 AM, Charles Oliver Nutter wrote: > On Wed, Mar 17, 2010 at 5:42 AM, Rocky Bernstein > wrote: > > In Ruby 1.8 and the Ruby 1.9 trunk when running a trace hook that raise= s > an > > exception not caught by the hook, the program terminates -- whether or > not > > there is a rescue further up the call stack, i.e. in the non-hook body. > > > > Ruby 1.9 also gives a stack trace, while Ruby 1.8 doesn't. > > This is a pretty peculiar situation. What *should* happen? With the > hook set, if it starts raising exceptions, you start getting errors > happening not in particular methods but in the "inbetween" space > between method calls and lines and pretty much everything. In your > example, even if it rescues, it would hit a c-call event (for the > puts) and raise again before printing anything out. > > Perhaps the specification could say that a ruby-land set_trace_func is > wrapped with an implicit rescue and disabled if an error is raised? Or > something? > > > How do JRuby, IronRuby and Rubinius and other Rubies handle this? And > what > > do the specifications say? > > JRuby also terminates. Note that the trace output is slightly > different, since we have different call sequences for some things: > > ~/projects/jruby =E2=9E=94 jruby --debug hook_thing.rb > line :8 (the first event that fires after the hook is set) > c-call =3D=3D=3D :8 (this is the rescue calling =3D=3D=3D) > c-call backtrace :1 (this is the toplevel of JRuby trying to get the > backtrace) > c-call first :1 (fallback code because backtrace errored too?) > Exception in thread "main" c-call first :1 (finally, we give up) > > There's so many calls and line events happening here, it's pretty much > impossible for anything good to come out of it. Catastrophic failure. > > As for other Rubies...I don't know if IronRuby supports > set_trace_func, but Rubinius and MacRuby do not. > > > If it is the case that raising an exception in a trace hook > unconditionally > > terminates the hooked program, programmers that want to write a trace > hook > > that plays nice with programs it hooks against should wrap the hook in = a > > begin/rescue block. Or make sure your code never raises an uncaught > > exception. > > That seems wise. > > - Charlie > > --000e0cd51e2c8fc36304821107bd Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Let me clarify a bit because I think some of the facts (some by me) may hav= e been misrepresented.

I came across this in implementing a debugger= command called "raise". The purpose of this is to force an excep= tion to occur outside of the debugger or in the debugged program. And in fa= ct this does work. Sort of..

$ rbdbgr xyz.rb
-- (xyz.rb:1)
begin
(rbdbgr): list
=C2=A0 1= =C2=A0 ->=C2=A0=C2=A0 begin
=C2=A0 2=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0= =C2=A0 =C2=A0 x =3D 1
=C2=A0 3=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 resc= ue =3D> e
=C2=A0 4=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0 puts &= quot;rescued #{e.inspect}"
=C2=A0 5=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0= =C2=A0 end
(rbdbgr): step
-- (xyz.rb:2)
x =3D 1
(rbdbgr): list
=C2=A0 1=C2=A0=C2=A0=C2=A0 = =C2=A0=C2=A0=C2=A0 begin
=C2=A0 2=C2=A0 ->=C2=A0=C2=A0=C2=A0 =C2=A0x = =3D 1
=C2=A0 3=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 rescue =3D> e
= =C2=A0 4=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0 puts "rescued #{e= .inspect}"
=C2=A0 5=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 end
(rb= dbgr): raise RuntimeError
rescued #<RuntimeError: RuntimeError>
$

The "sort of&= quot; part is=C2=A0 that in Ruby 1.9 the implementation of tracing sets a b= it in the thread structure which turns off tracing into the tracer (or debu= gging into the debugger). When an exception that passes from hook to non-ho= ok occurs, this bit doesn't get cleared.

Changing the program that was originally posted will show this better:<= br>
s =3D Proc.new {
=C2=A0 |event, file, lineno, mid, binding, klas= s|
=C2=A0 puts "#{event} #{mid} #{lineno}"
=C2=A0 unless $x=
=C2=A0=C2=A0=C2=A0 $x =3D true
=C2=A0=C2=A0=C2=A0 raise RuntimeError
=C2=A0 end
}
begin
=C2= =A0 set_trace_func(s)
=C2=A0 puts "after trace func"
rescue=
=C2=A0 puts "Rescued"
end
puts "After begin"=

Run this in Ruby 1.9 and perhaps it is more apparent that the progr= am continues to the end. In the original posting if you look carefully you&= #39;ll see that the rescue did run *after* the trace hook issued a raise in= side the begin block. It is the *second* raise issued in the hook function = that terminates the program because the debugged program is no longer in a = rescue'd block.

From what you describe from JRuby, it sounds like it has a similar beha= vior possibly because it is implemented underneath in a similar way. So rat= ionalizing this as "this makes sense because this happened in between = statements and not in the program" I don't think is accurately cap= turing what's going inside either interpreter.

One of the uses of a trace hook is in a debugger where it is normal to = have code run in between the debugged program's statements which have l= asting effect. For example that's how variables can get changed.

So again, how is raising an exception from a trace hook defined? Or how= should it be defined?

Trace hooks in general could be undefined so= anything you want to do is okay including not implement them. That would c= over the current behavior.

Similarly it could be that if if a hook raises is a exception that it d= oesn't catch you get undefined behavior. That too would cover the curre= nt behavior.

But declaring that an uncaught exception in a trace hoo= k terminates a program does not accurately describe how Ruby 1.9 (and proba= bly JRuby) currently behave.


On Thu, Mar 18, 2010 at 1:59 AM, Charles= Oliver Nutter <headius@headius.com> wrote:
On Wed, Mar 17, 2010 at 5:42 AM, Rocky Bernstein <rockyb@rubyforge.org> wrote:
> In Ruby 1.8 and the Ruby 1.9 trunk when running a trace hook that rais= es an
> exception not caught by the hook, the program terminates -- whether or= not
> there is a rescue further up the call stack, i.e. in the non-hook body= .
>
> Ruby 1.9 also gives a stack trace, while Ruby 1.8 doesn't.

This is a pretty peculiar situation. What *should* happen? With the hook set, if it starts raising exceptions, you start getting errors
happening not in particular methods but in the "inbetween" space<= br> between method calls and lines and pretty much everything. In your
example, even if it rescues, it would hit a c-call event (for the
puts) and raise again before printing anything out.

Perhaps the specification could say that a ruby-land set_trace_func is
wrapped with an implicit rescue and disabled if an error is raised? Or
something?

> How do JRuby, IronRuby and Rubinius and other Rubies handle this? And = what
> do the specifications say?

JRuby also terminates. Note that the trace output is slightly
different, since we have different call sequences for some things:

~/projects/jruby =E2=9E=94 jruby --debug hook_thing.rb
line =C2=A0:8 (the first event that fires after the hook is set)
c-call =3D=3D=3D :8 (this is the rescue calling =3D=3D=3D)
c-call backtrace :1 (this is the toplevel of JRuby trying to get the backtr= ace)
c-call first :1 (fallback code because backtrace errored too?)
Exception in thread "main" c-call first :1 (finally, we give up)<= br>
There's so many calls and line events happening here, it's pretty m= uch
impossible for anything good to come out of it. Catastrophic failure.

As for other Rubies...I don't know if IronRuby supports
set_trace_func, but Rubinius and MacRuby do not.

> If it is the case that raising an exception in a trace hook unconditio= nally
> terminates the hooked program, programmers that want to write a trace = hook
> that plays nice with programs it hooks against should wrap the hook in= a
> begin/rescue block. Or make sure your code never raises an uncaught > exception.

That seems wise.

- Charlie


--000e0cd51e2c8fc36304821107bd--