diff --git a/ext/rbs_extension/legacy_location.c b/ext/rbs_extension/legacy_location.c index 603136291..365f7f255 100644 --- a/ext/rbs_extension/legacy_location.c +++ b/ext/rbs_extension/legacy_location.c @@ -171,10 +171,11 @@ static VALUE location_end_pos(VALUE self) { static rbs_constant_id_t rbs_constant_pool_insert_ruby_symbol(VALUE symbol) { VALUE name = rb_sym2str(symbol); - // Constants inserted here will never be freed, but that's acceptable because: - // 1. Most symbols passed into here will be the ones already inserted into the constant pool by `parser.c`. - // 2. Methods like `add_required_child` and `add_optional_child` will usually only get called with a few different symbols. - return rbs_constant_pool_insert_constant(RBS_GLOBAL_CONSTANT_POOL, (const uint8_t *) RSTRING_PTR(name), RSTRING_LEN(name)); + // To prevent memory compaction from breaking references to strings, + // strings are copied and memory managed with an owned type. + uint8_t *copied_name = malloc(RSTRING_LEN(name)); + memcpy((void *) copied_name, RSTRING_PTR(name), RSTRING_LEN(name)); + return rbs_constant_pool_insert_owned(RBS_GLOBAL_CONSTANT_POOL, copied_name, RSTRING_LEN(name)); } static VALUE location_add_required_child(VALUE self, VALUE name, VALUE start, VALUE end) { diff --git a/test/rbs/location_test.rb b/test/rbs/location_test.rb index 5d8e8e4d2..520028ea2 100644 --- a/test/rbs/location_test.rb +++ b/test/rbs/location_test.rb @@ -161,6 +161,34 @@ def test_sub_buffer_local_location end end + def test_gc_compaction + GC.disable + buffer = buffer() + locations = 10.times.map do |i| + Location.new(buffer, 0, 10).tap do |loc| + loc.add_required_child(:static_0, 0...1) + loc.add_required_child(:static_1, 1...2) + loc.add_required_child(:static_2, 2...3) + 3.times do |j| + loc.add_required_child(:"dynamic_#{i}_#{j}", j...j+1) + end + end + end + GC.start + GC.start + GC.compact + locations.each_with_index do |loc, i| + assert_instance_of Location, loc[:static_0] + assert_instance_of Location, loc[:static_1] + assert_instance_of Location, loc[:static_2] + 3.times do |j| + assert_instance_of Location, loc[:"dynamic_#{i}_#{j}"] + end + end + ensure + GC.enable + end + private def buffer(content: nil)