Skip to content

Conversation

@byroot
Copy link
Member

@byroot byroot commented Feb 2, 2026

Fix: #929

I don't know how it's possible, but somehow the self reference is optimized out of the stack. Perhaps because we inline so aggressively that the compiler end up spilling it on the heap.

Actually I get it now, the actual caller is:

static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self)
{
    rb_check_arity(argc, 0, 1);
    VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
    return cState_partial_generate(Vstate, self, generate_json_object, Qfalse);
}

So the GCed variable is VState which indeed is no longer reachable rather quickly. So the fix does checks out.

Repro:

require 'json'

test_data = {
  "flag" => true,
  "data" => 10000.times.map { [1.0] },
  :flag => false,
}

10.times do
  test_data.to_json
end

But in practice the cause was just that the issued warning calls Hash#inspect on a big hash, which triggers GC.

So it can be triggered even more reliably with:

require 'json'

module JSON
  module Common
    def self.on_mixed_keys_hash(...)
      GC.start
    end
  end
end

test_data = {
  "flag" => true,
  "data" => 10000.times.map { [1.0] },
  :flag => false,
}

test_data.to_json

Fix: ruby#929

When calling `cState_partial_generate` from `mHash_to_json` or other
`to_json` funcs, `VState` becomes unreachable very quickly, hence the
compiler may optimize it out of the stack, and make it invisible to
the GC stack scanning. This is particularly liekly given how aggressively
we inline.

Repro:

```ruby
require 'json'

test_data = {
  "flag" => true,
  "data" => 10000.times.map { [1.0] },
  :flag => false,
}

10.times do
  test_data.to_json
end
```

But in practice the cause was just that the issued warning calls
Hash#inspect on a big hash, which triggers GC.

So it can be triggered even more reliably with:

```ruby
require 'json'

module JSON
  module Common
    def self.on_mixed_keys_hash(...)
      GC.start
    end
  end
end

test_data = {
  "flag" => true,
  "data" => 10000.times.map { [1.0] },
  :flag => false,
}

test_data.to_json
```
@byroot byroot force-pushed the fix-bug-929-mixed-keys branch from 37f8a2e to 2cebc2c Compare February 2, 2026 21:33
@byroot byroot merged commit 79b6e16 into ruby:master Feb 3, 2026
41 of 43 checks passed
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.

Large hash with mixed symbol and string key causing segfaults with to_json

1 participant