Bug #22113
openProc#curry produces a corrupted (false) argument under GC compaction
Description
Proc#curry produces a corrupted (false) argument under GC compaction¶
Summary¶
The Proc#curry rubyspec example "combines arguments and calculates incoming arity accurately for successively currying" (spec/ruby/core/proc/curry_spec.rb:84) intermittently fails with:
TypeError: false can't be coerced into Integer
One of the curried arguments — which should be an Integer — is read back as false (Qfalse == 0), indicating a stale/uninitialized object reference. The trigger is GC compaction.
Environment¶
ruby 4.1.0dev (2026-06-14T05:19:50Z master a6e302509b) +PRISM [x86_64-linux]
configured with --enable-shared
Observed on CI https://ci.rvm.jp/results/trunk@ruby-sp3/6377957, where the full rubyspec suite was run in parallel:
make yes-test-rubyspec MSPECOPT='--error-output stderr -j' TESTS='-j20'
Reproduction¶
Reliably reproduces by enabling compaction alone:
# => TypeError: false can't be coerced into Integer
GC.auto_compact = true
l = -> a, b, c { a + b + c }
loop do
l1 = l.curry.call(1)
raise "unexpected" unless l1.curry.call(2, 3) == 6
end
$ ruby repro.rb
repro.rb:7:in 'Integer#+': false can't be coerced into Integer (TypeError)
Observations¶
-
GC.auto_compact = truealone →TypeError: false can't be coerced into Integer(matches the spec failure exactly). -
GC.stress = truealone (no compaction) → does not reproduce. - No GC tuning, millions of iterations → does not reproduce.
-
GC.stress = true+GC.auto_compact = true→ escalates to a hard crash during marking:
Backtrace points into the curry path:[BUG] try to mark T_NONE object (obj: ... T_NONE, parent: ... T_ARRAY [E ] len: 1 (embed))gc_mark_check_t_none gc/default/default.c:4800 ... rb_ary_plus array.c:5197 curry proc.c:3867
This points to a reference that is not correctly updated (or not pinned) during compaction somewhere in the Proc#curry path (curry() / make_curry_proc() / the rb_ary_plus + rb_ary_new4 accumulation of passed), leaving a dangling reference that reads back as Qfalse.
Note on the CI run¶
The CI run did not explicitly enable compaction (only RUBY_DEBUG=ci was set), so the exact in-the-wild trigger is not yet confirmed — possibly another GC-related spec in the same parallel worker induced a compaction. The symptom is identical and is deterministically reproducible under GC.auto_compact = true.
Updated by ko1 (Koichi Sasada) about 14 hours ago
This ticket is from Claude code (Opus 4.8 with 1M context) :)