From 6d17a3e6472b4c6ce81f5308fc07050855114410 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Wed, 16 Jul 2025 17:20:21 +0900 Subject: [PATCH] Prevent ObjectSpace.count_objects from allocating extra arrays `ObjectSpace.count_objects` could cause an unintended array allocation. It returns a hash like `{ :T_ARRAY => 100, :T_STRING => 100, ... }`, so it creates the key symbol (e.g., `:T_STRING`) for the first time. On rare occations, this symbol creation internally allocates a new array for symbol management. This led to a problematic side effect where calling `count_objects` twice in a row could produce inconsistent results: the first call would trigger the hidden array allocation, and the second call would then report an increased count for `:T_ARRAY`. This behavior caused test failures in `test/ruby/test_allocation.rb`, which performs a baseline measurement before an operation and then asserts the exact number of new allocations. https://rubyci.s3.amazonaws.com/openbsd-current/ruby-master/log/20250716T053005Z.fail.html.gz > 1) Failure: > TestAllocation::ProcCall::WithBlock#test_ruby2_keywords [...]: > Expected 1 array allocations for "r2k.(1, a: 2, &block)", but 2 arrays allocated. This change resolves the issue by pre-interning all key symbols used by `ObjectSpace.count_objects` before its counting. This eliminates the side effect and ensures the stability of allocation-sensitive tests. Co-authored-by: Koichi Sasada --- gc.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/gc.c b/gc.c index 4a3623cd75..10bce22639 100644 --- a/gc.c +++ b/gc.c @@ -2498,6 +2498,7 @@ count_objects(int argc, VALUE *argv, VALUE os) { struct count_objects_data data = { 0 }; VALUE hash = Qnil; + VALUE types[T_MASK + 1]; if (rb_check_arity(argc, 0, 1) == 1) { hash = argv[0]; @@ -2505,6 +2506,10 @@ count_objects(int argc, VALUE *argv, VALUE os) rb_raise(rb_eTypeError, "non-hash given"); } + for (size_t i = 0; i <= T_MASK; i++) { + types[i] = type_sym(i); + } + rb_gc_impl_each_object(rb_gc_get_objspace(), count_objects_i, &data); if (NIL_P(hash)) { @@ -2517,9 +2522,9 @@ count_objects(int argc, VALUE *argv, VALUE os) rb_hash_aset(hash, ID2SYM(rb_intern("FREE")), SIZET2NUM(data.freed)); for (size_t i = 0; i <= T_MASK; i++) { - VALUE type = type_sym(i); - if (data.counts[i]) - rb_hash_aset(hash, type, SIZET2NUM(data.counts[i])); + if (data.counts[i]) { + rb_hash_aset(hash, types[i], SIZET2NUM(data.counts[i])); + } } return hash;