Project

General

Profile

Actions

Bug #22113

open

Proc#curry produces a corrupted (false) argument under GC compaction

Bug #22113: Proc#curry produces a corrupted (false) argument under GC compaction

Added by ko1 (Koichi Sasada) about 14 hours ago. Updated about 14 hours ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:125758]

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 = true aloneTypeError: false can't be coerced into Integer (matches the spec failure exactly).
  • GC.stress = true alone (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:
    [BUG] try to mark T_NONE object (obj: ... T_NONE, parent: ... T_ARRAY [E  ] len: 1 (embed))
    
    Backtrace points into the curry path:
    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 Actions #1 [ruby-core:125759]

This ticket is from Claude code (Opus 4.8 with 1M context) :)

Actions

Also available in: PDF Atom