Skip to content

Fix breaking references after GC.compact#2822

Merged
ksss merged 2 commits intoruby:masterfrom
ksss:fix-gc-compact
Jan 23, 2026
Merged

Fix breaking references after GC.compact#2822
ksss merged 2 commits intoruby:masterfrom
ksss:fix-gc-compact

Conversation

@ksss
Copy link
Collaborator

@ksss ksss commented Jan 21, 2026

Although rare, there's a possibility of breaking references after GC.compact if a dynamic symbol happens to be interned into the constant pool.
To fix this issue, I've modified the code to copy the string and intern it as an owned type when a dynamic symbol is passed.
Additionally, we've written a test case that reproduces the problem.

@ksss
Copy link
Collaborator Author

ksss commented Jan 22, 2026

I found that even static symbols can have broken references starting from 4.0, so I fixed it to copy uniquely.

@ksss ksss added this to the RBS 4.0 milestone Jan 22, 2026
@ksss ksss merged commit 85565e2 into ruby:master Jan 23, 2026
21 checks passed
@ksss ksss deleted the fix-gc-compact branch January 23, 2026 06:31
ksss added a commit to ksss/rbs that referenced this pull request Jan 30, 2026
If GC.compact occurs after parsing, strings referenced from the parse result are corrupted.
Below is a reproduction script.
In this case, it is intentionally reproducing a scenario where `GC.compact` occurs during parsing.

```rb
require 'rbs'

module Patch
  def to_i(...)
    super(...)
  end
end

String.prepend(Patch)

TracePoint.new(:call) do |tp|
  GC.start
  GC.start
  GC.compact
end.enable(target: String.instance_method(:to_i))

errors = 0
n = 100
n.times do |i|
  1000.times { |i| "\x00" * (1000); "X#{i}" * 100 }
  len = 500
  var = ("A" * len + "_V#{i}").to_sym
  type = "(42 | #{var}) -> Array[#{var}]"

  result = RBS::Parser.parse_method_type(type, variables: [var])
  if var == result.type.return_type.args.first.name
    print "."
  else
    errors += 1
    print "X"
  end
end

puts "\n#{errors}/n errors (#{(errors * 100.0 / n).round(1)}%)"
exit(errors > 0 ? 1 : 0)
```

To fix this issue, similar to ruby#2822 ,
strings of symbols given as arguments are copied and retained.
If it is an **owned** type, `free()` will also be performed when the constant pool is freed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant