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