Skip to content

Conversation

@ksss
Copy link
Collaborator

@ksss ksss commented 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.

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 #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.

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.
@ksss ksss added this to the RBS 4.0 milestone Jan 30, 2026
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