diff options
author | Chris Seaton <chris@chrisseaton.com> | 2019-07-02 14:19:15 +0100 |
---|---|---|
committer | Nobuyoshi Nakada <nobu@ruby-lang.org> | 2019-07-03 04:05:22 +0900 |
commit | 928260c2a613bbdd4402c300e0bf86ae7562e52a (patch) | |
tree | 00e025542b7ae654ea1bc2dd249851f7d912f9b7 | |
parent | efde19ce440f8656c3ce631a1d2a56e830961e9d (diff) | |
download | ruby-928260c2a613bbdd4402c300e0bf86ae7562e52a.tar.gz |
Warn in verbose mode on defining a finalizer that captures the object
[Feature #15974]
Closes: https://github.com/ruby/ruby/pull/2264
-rw-r--r-- | gc.c | 43 | ||||
-rw-r--r-- | spec/ruby/core/objectspace/define_finalizer_spec.rb | 26 |
2 files changed, 69 insertions, 0 deletions
@@ -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 <i>aProc</i>. If <i>aProc</i> is a lambda or * method, make sure it can be called with a single argument. * + * In verbose mode (<code>-w</code>) a warning will be issued if + * the object is reachable from <i>aProc</i>, 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 |