diff --git a/gc.c b/gc.c index a1984f83f0..bb6b0d8ea0 100644 --- a/gc.c +++ b/gc.c @@ -2954,6 +2954,42 @@ should_be_finalizable(VALUE obj) rb_check_frozen(obj); } +struct should_not_capture_data { + VALUE obj; + VALUE set; + bool found; +}; + +static void +should_not_capture_callback(const VALUE child, struct should_not_capture_data *data) +{ + if (child == data->obj) + data->found = true; + + if (data->found) + return; + + // Maintain a set of objects already searched, so that we don't follow a cycle + VALUE key = rb_obj_id(child); + if (rb_hash_has_key(data->set, key)) + return; + rb_hash_aset(data->set, key, Qtrue); + + rb_objspace_reachable_objects_from(child, (void (*)(unsigned long, void *)) &should_not_capture_callback, (void *)data); +} + +static void +should_not_capture(VALUE block, VALUE obj) +{ + struct should_not_capture_data data; + data.obj = obj; + data.set = rb_hash_new(); + data.found = false; + rb_objspace_reachable_objects_from(block, (void (*)(unsigned long, void *)) &should_not_capture_callback, (void *)&data); + if (data.found) + rb_warn("object is reachable from finalizer - it may never be run"); +} + /* * call-seq: * ObjectSpace.define_finalizer(obj, aProc=proc()) @@ -2963,6 +2999,10 @@ should_be_finalizable(VALUE obj) * as an argument to aProc. If aProc is a lambda or * method, make sure it can be called with a single argument. * + * In verbose mode (-w) a warning will be issued if + * the object is reachable from aProc, which may prevent + * finalization. + * */ static VALUE @@ -2979,6 +3019,9 @@ define_final(int argc, VALUE *argv, VALUE os) should_be_callable(block); } + if (ruby_verbose) + should_not_capture(block, obj); + return define_final0(obj, block); } diff --git a/spec/ruby/core/objectspace/define_finalizer_spec.rb b/spec/ruby/core/objectspace/define_finalizer_spec.rb index b7e47473a0..3a9132ef93 100644 --- a/spec/ruby/core/objectspace/define_finalizer_spec.rb +++ b/spec/ruby/core/objectspace/define_finalizer_spec.rb @@ -65,4 +65,30 @@ describe "ObjectSpace.define_finalizer" do ruby_exe(code).lines.sort.should == ["finalized1\n", "finalized2\n"] end + + it "warns in verbose mode if it is self-referencing" do + code = <<-RUBY + obj = "Test" + handler = Proc.new { puts "finalized" } + ObjectSpace.define_finalizer(obj, handler) + exit 0 + RUBY + + ruby_exe(code, :options => "-w", :args => "2>&1").should include("warning: object is reachable from finalizer - it may never be run") + end + + it "warns in verbose mode if it is indirectly self-referencing" do + code = <<-RUBY + def scoped(indirect) + Proc.new { puts "finalized" } + end + obj = "Test" + indirect = [obj] + handler = scoped(indirect) + ObjectSpace.define_finalizer(obj, handler) + exit 0 + RUBY + + ruby_exe(code, :options => "-w", :args => "2>&1").should include("warning: object is reachable from finalizer - it may never be run") + end end