objspace: Hide identhash containing internal objs

Inside ObjectSpace.reachable_objects_from we keep an internal identhash
in order to de-duplicate reachable objects when wrapping them as
InternalObject. Previously this hash was not hidden, making it possible
to leak references to those internal objects to Ruby if using
ObjectSpace.each_object.

This commit solves this by hiding the hash. To simplify collection of
values, we instead now just use the hash as a set of visited objects,
and collect an Array (not hidden) of values to be returned.
This commit is contained in:
John Hawthorn 2022-02-09 12:14:51 -08:00
parent a271acf822
commit 05b1944c53
Notes: git 2022-02-10 10:33:06 +09:00
2 changed files with 31 additions and 15 deletions

View File

@ -712,7 +712,7 @@ iow_internal_object_id(VALUE self)
struct rof_data { struct rof_data {
VALUE refs; VALUE refs;
VALUE internals; VALUE values;
}; };
static void static void
@ -723,11 +723,15 @@ reachable_object_from_i(VALUE obj, void *data_ptr)
VALUE val = obj; VALUE val = obj;
if (rb_objspace_markable_object_p(obj)) { if (rb_objspace_markable_object_p(obj)) {
if (rb_objspace_internal_object_p(obj)) { if (NIL_P(rb_hash_lookup(data->refs, key))) {
val = iow_newobj(obj); rb_hash_aset(data->refs, key, Qtrue);
rb_ary_push(data->internals, val);
} if (rb_objspace_internal_object_p(obj)) {
rb_hash_aset(data->refs, key, val); val = iow_newobj(obj);
}
rb_ary_push(data->values, val);
}
} }
} }
@ -785,21 +789,21 @@ static VALUE
reachable_objects_from(VALUE self, VALUE obj) reachable_objects_from(VALUE self, VALUE obj)
{ {
if (rb_objspace_markable_object_p(obj)) { if (rb_objspace_markable_object_p(obj)) {
struct rof_data data; struct rof_data data;
if (rb_typeddata_is_kind_of(obj, &iow_data_type)) { if (rb_typeddata_is_kind_of(obj, &iow_data_type)) {
obj = (VALUE)DATA_PTR(obj); obj = (VALUE)DATA_PTR(obj);
} }
data.refs = rb_ident_hash_new(); data.refs = rb_obj_hide(rb_ident_hash_new());
data.internals = rb_ary_new(); data.values = rb_ary_new();
rb_objspace_reachable_objects_from(obj, reachable_object_from_i, &data); rb_objspace_reachable_objects_from(obj, reachable_object_from_i, &data);
return rb_funcall(data.refs, rb_intern("values"), 0); return data.values;
} }
else { else {
return Qnil; return Qnil;
} }
} }

View File

@ -140,6 +140,18 @@ class TestObjSpace < Test::Unit::TestCase
end; end;
end end
def test_reachable_objects_during_iteration
opts = %w[--disable-gem --disable=frozen-string-literal -robjspace]
assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}"
begin;
ObjectSpace.each_object{|o|
o.inspect
ObjectSpace.reachable_objects_from(Class)
}
end;
end
def test_reachable_objects_from_root def test_reachable_objects_from_root
root_objects = ObjectSpace.reachable_objects_from_root root_objects = ObjectSpace.reachable_objects_from_root