Hello,

I have found a bug in ruby's tcltk interface.
Following code demonstrates problem:
---------------------------------
require 'tk'

@proc3=proc{ puts 'proc3, the old one'}

proc2a=proc{
   puts 'proc2 start'
   # IF EXCEPTION IS UNHANDLED, THIS WILL CRASH
   something_not_defined
   puts 'proc2 end'
}

proc2b=proc{
   puts 'proc2 start'
   @proc3=proc{
     puts 'proc3 start'
     # IF EXCEPTION IS UNHANDLED, THIS WILL CRASH
     # WHEN CALLED FROM ANYWHERE
     something_not_defined
     puts 'proc3 end'
   }
   puts 'proc2 end'
}

proc1=proc{
   puts 'proc1 start'
   @dialog1=TkToplevel.new(@root)
   TkButton.new(@dialog1, 'text'=>'proc2a', 'command'=>proc2a).pack
   TkButton.new(@dialog1, 'text'=>'proc2b', 'command'=>proc2b).pack
   @dialog1.title 'dialog 1'
   @dialog1.transient(@parent)
   @dialog1.withdraw
   @dialog1.update
   @dialog1.deiconify
   @dialog1.grab
   @dialog1.wait_destroy
   puts 'proc1 end'
}

def runproc3
   @proc3.call
end

@root=TkRoot.new
TkButton.new(@root, 'text'=>'proc1', 'command'=>proc1).pack
TkButton.new(@root, 'text'=>'runproc3', 'command'=>proc{runproc3}).pack
Tk.mainloop
---------------------------------

To crash follow these steps:
Variant A:
  press button 'proc1' then 'proc2a'.
Variant B:
  press button 'proc1', 'proc2b', close dialog, then press 'runproc3'.

Problem depends on wait_destroy call reentrancy.
Bug happen when exception is raised, but not handled inside
reentrant part.

Problem appears also when Proc object is defined in lexical
environment of part called in reentrant way. (Variant B)

Affected ruby versions:
ruby 1.6.7 (2002-03-19) [i386-linux]

This Windows version seems to survive above example:
ruby 1.6.7 (2002-03-01) [i586-mswin32]

But following code with more deep reentrancy will kill both
windows and linux versions of ruby:
---------------------------------
require 'tk'

class Dialog
   def initialize(parent, &block)
     @parent=parent
     @block=block
   end
   def run
     @dialog=TkToplevel.new(@parent)
     @block.call @dialog
     @dialog.title 'dialog'
     @dialog.transient(@parent)
     @dialog.withdraw
     @dialog.update
     @dialog.deiconify
     @dialog.grab
     @dialog.wait_destroy
   end
   def exit
     @dialog.destroy
   end
end

w1=TkRoot.new
TkButton.new(w1, 'text'=>'button 1',
  'command'=>proc{
   puts 'button1 start'
   d1=Dialog.new(w1){|w2|
    TkButton.new(w2, 'text'=>'button 2',
     'command'=>proc{
      d2=Dialog.new(w2){|w3|
       puts 'button2 start'
       TkButton.new(w3, 'text'=>'button 3',
        'command'=>proc{
         d3=Dialog.new(w3){|w4|
          puts 'button3 start'
          TkLabel.new(w4, 'text'=>'label').pack
          something_not_defined # <-- unhandled exception
          puts 'button3 end'
         }
         d3.run
        }).pack
       puts 'button2 end'
      }
      d2.run
     }).pack
   }
   d1.run
   puts 'button1 end'
}).pack
Tk.mainloop
---------------------------------

To crash: press buttons as they appear.

I think that this is bug in ruby since, when no exception
occurs or it is handled before return from reentrancy, no crash
will happen.


Best regards,

Jakub Travnik,
jabber://jtra / jabber.com