Initialize as.extended before the fields object is allocated

class_allocate_complex_instance() allocates the object with an extended
shape and fills in as.extended only after allocating the fields object.
A GC during that allocation sees a shape that says "extended" and marks
whatever the reused slot left in as.extended:

  [BUG] try to mark T_NONE object (obj: out-of-heap:..., parent: ... T_OBJECT/(complex) ...)

Before 86951165b8 ("Get rid of ROBJECT_HEAP flag") the mark path keyed
off the ROBJECT_HEAP flag, which was set together with as.extended, so
the window took the embedded path and read nothing.  The shape is now
set at allocation, which makes the window live.

Reproduces with GC.stress on a class whose instances start with a
complex shape, e.g. bootstraptest/test_yjit.rb under `-s`.  Add a
bootstraptest that enables GC.stress itself so a regular btest run
covers it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Koichi SasadaandClaude Opus 5 committed 2026-09-11 12:19:37 +09:00
1 parent 67d83762c6
commit f3ebfe5d36
2 files changed
+14
Notes: git 2026-09-11 03:20:07 +00:00

No files matched your search

+11
View File
@@ -32,3 +32,14 @@ ms = "a".."k"
end
end
}, '[ruby-dev:39453]'
assert_normal_exit %q{
# A class whose instances start with a complex shape: as.extended must be
# initialized before the fields object allocation can trigger a GC.
ivars = 1024.times.map { |i| "@iv_#{i} = #{i}\n" }.join
klass = Class.new
klass.class_eval "def initialize() #{ivars} end"
GC.stress = true
klass.allocate
GC.stress = false
}, 'complex T_OBJECT marked before as.extended is set'
+3
View File
@@ -1231,6 +1231,9 @@ static
VALUE class_allocate_complex_instance(VALUE klass, uint32_t capacity)
{
VALUE obj = rb_newobj_of_with_shape(klass, T_OBJECT, rb_shape_transition_extended(ROOT_COMPLEX_SHAPE_ID), sizeof(struct RObject));
// The shape already says extended, so a GC during the allocation below
// would mark an uninitialized as.extended.
ROBJECT(obj)->as.extended = Qfalse;
VALUE fields_obj = rb_imemo_fields_new_complex(obj, ROOT_COMPLEX_SHAPE_ID, capacity, false);
ROBJECT_SET_EXTENDED(obj, fields_obj);
return obj;