mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Warn in verbose mode on defining a finalizer that captures the object
[Feature #15974] Closes: https://github.com/ruby/ruby/pull/2264
This commit is contained in:
parent
efde19ce44
commit
928260c2a6
2 changed files with 69 additions and 0 deletions
43
gc.c
43
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 <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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue