Hi.

noreply / rubyforge.org wrote:
(2006/01/01 19:04)

>Bugs item #3160, was opened at 2006-01-01 09:53
>You can respond by visiting: 
>http://rubyforge.org/tracker/?func=detail&atid=1698&aid=3160&group_id=426
>
>Category: Core
>Group: None
>Status: Open
>Resolution: None
>Priority: 3
>Submitted By: Dan Griffin (dangriffin)
>Assigned to: Nobody (None)
>Summary: Call to NoMethodError#message hangs ruby
>
>Initial Comment:
>The attached program demonstrates what I think is some kind of stack overflow in Ruby. A deliberate bug in the Cell#cause_hang method causes the interpreter to freeze when the Test::Unit framework attempts to get the message for the NoMethodError exception.
>
>This has been tested on 1.8.2 and 1.8.4 on Windows.
>
>----------------------------------------------------------------------
>
>You can respond by visiting: 
>http://rubyforge.org/tracker/?func=detail&atid=1698&aid=3160&group_id=426

Minimum reproducable code is this.

/////////////////////////////////////////

Cell = Struct.new(:row, :col)

n = 9 # increase this, it will take longer time

cols  = Array.new(n) { [] }
rows  = Array.new(n) { [] }
boxes = Array.new(n) { Array.new(n) { Cell.new } }

n.times {|x|
  n.times {|y|
    cell = boxes[x][y]
    cols[x] << cell
    rows[y] << cell
    cell.col = cols[x]
    cell.row = rows[y]
  }
}

boxes[0][0].dummy

/////////////////////////////////////////

This is because name_err_mesg_to_str (error.c) calls rb_inspect
and checks length of messege after all recursion is done.

So some kind of this patch will prevent the unnessesary
recursion. (this is adhok patch! I think commitable patch
must be more cleaner)

Index: error.c
===================================================================
RCS file: /src/ruby/error.c,v
retrieving revision 1.119
diff -u -p -r1.119 error.c
--- error.c	28 Sep 2005 03:51:52 -0000	1.119
+++ error.c	2 Jan 2006 06:14:40 -0000
@@ -698,7 +698,9 @@ name_err_mesg_to_str(VALUE obj)
 	    desc = "false";
 	    break;
 	  default:
+	    rb_thread_local_aset(rb_thread_current(), rb_intern("__abort_if_too_long__"), Qtrue);
 	    d = rb_protect(rb_inspect, obj, 0);
+	    rb_thread_local_aset(rb_thread_current(), rb_intern("__abort_if_too_long__"), Qfalse);
 	    if (NIL_P(d) || RSTRING(d)->len > 65) {
 		d = rb_any_to_s(obj);
 	    }
Index: eval.c
===================================================================
RCS file: /src/ruby/eval.c,v
retrieving revision 1.863
diff -u -p -r1.863 eval.c
--- eval.c	31 Dec 2005 13:57:20 -0000	1.863
+++ eval.c	2 Jan 2006 06:12:24 -0000
@@ -13080,11 +13080,12 @@ recursive_pop(void)
 VALUE
 rb_exec_recursive(VALUE (*func)(VALUE, VALUE, int), VALUE obj, VALUE arg)
 {
+    VALUE result;
+    
     if (recursive_check(obj)) {
-	return (*func)(obj, arg, Qtrue);
+	result = (*func)(obj, arg, Qtrue);
     }
     else {
-	VALUE result;
 	int state;
 
 	recursive_push(obj);
@@ -13095,6 +13096,12 @@ rb_exec_recursive(VALUE (*func)(VALUE, V
 	POP_TAG();
 	recursive_pop();
 	if (state) JUMP_TAG(state);
-	return result;
     }
+
+    if (RTEST(rb_thread_local_aref(rb_thread_current(), rb_intern("__abort_if_too_long__")))) {
+	if (RSTRING(result)->len > 65) {
+	    rb_raise(rb_eRuntimeError, "foo");
+	}
+    }
+    return result;
 }