From 63c3d76dd38e005b3d675eb0eef2ef5ea4952541 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 17 Jun 2026 10:05:41 +0900 Subject: [PATCH 1/6] Bump the ABI version due to the change to `rb_alloc_tmp_buffer` --- include/ruby/internal/abi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ruby/internal/abi.h b/include/ruby/internal/abi.h index 7ceb8c40b70eef..322761fc683341 100644 --- a/include/ruby/internal/abi.h +++ b/include/ruby/internal/abi.h @@ -24,7 +24,7 @@ * In released versions of Ruby, this number is not defined since teeny * versions of Ruby should guarantee ABI compatibility. */ -#define RUBY_ABI_VERSION 3 +#define RUBY_ABI_VERSION 4 /* Windows does not support weak symbols so ruby_abi_version will not exist * in the shared library. */ From 4b09ce7e031853da50e8bd539adaa75516df1738 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Tue, 16 Jun 2026 13:17:13 -0500 Subject: [PATCH 2/6] [DOC] Doc for Pathname.mountpoint? --- pathname_builtin.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 60ae6d34129b92..8fdf871201cc61 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -533,7 +533,19 @@ def parent self + '..' end - # Returns +true+ if +self+ points to a mountpoint. + # :markup: markdown + # + # call-seq: + # mountpoint? -> true or false + # + # Returns whether the path in `self` points to a mountpoint: + # + # ```ruby + # Pathname('/').mountpoint? # => true + # Pathname('/etc').mountpoint? # => false + # Pathname('nosuch').mountpoint? # => false + # ``` + # def mountpoint? begin stat1 = self.lstat From 16c525e0714487dd1f3990f9e618666d03d60ebc Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Jun 2026 06:12:29 +0900 Subject: [PATCH 3/6] Scale test timeouts on emulated ppc64le/s390x CI runners The qemu-emulated runners are slow enough that subprocess assertion timeouts (assert_separately) flake, e.g. TestObjSpace expiring the 10-second limit and getting killed by SIGTERM. Apply RUBY_TEST_TIMEOUT_SCALE the way macos.yml already does for slower runners. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ubuntu.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 6a05028db34e55..4106f9e60d8afe 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -137,6 +137,13 @@ jobs: run: echo "DFLTCC=0" >> $GITHUB_ENV if: ${{ endsWith(matrix.os, 's390x') }} + # Emulated ppc64le/s390x runners are slow enough that subprocess + # assertion timeouts (e.g. assert_separately) flake. Scale them up, as + # macos.yml does for slower/contended runners. + - name: Set timeout scale for emulated runners + run: echo "RUBY_TEST_TIMEOUT_SCALE=10" >> $GITHUB_ENV + if: ${{ endsWith(matrix.os, 'ppc64le') || endsWith(matrix.os, 's390x') }} + - name: make ${{ matrix.test_task }} run: | test -n "${LAUNCHABLE_STDOUT}" && exec 1> >(tee "${LAUNCHABLE_STDOUT}") From cbd763a46b22c03ba0859b102edb74ff61e21d81 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 17 Jun 2026 09:58:28 +0900 Subject: [PATCH 4/6] Silence llvm-nm "not recognized" noise on LTO bitcode Under -flto the compiler emits raw LLVM bitcode objects, but the configured nm carries --no-llvm-bc (added so it ignores Rust's newer-version bitcode) and rejects them with "not recognized", flooding the clang-22 LTO build log. leaked-globals now drops those objects, still checking the linked library and any ELF objects, and the symbol-prefix probe in configure silences nm's stderr. Co-Authored-By: Claude Opus 4.8 (1M context) --- configure.ac | 2 +- tool/leaked-globals | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 2de91209d61919..8d020771c6d505 100644 --- a/configure.ac +++ b/configure.ac @@ -4163,7 +4163,7 @@ AC_SUBST(JIT_CARGO_SUPPORT)dnl "no" or the cargo profile of the rust code [begin]_group "build section" && { AC_CACHE_CHECK([for prefix of external symbols], rb_cv_symbol_prefix, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[extern void conftest_external(void) {}]], [[]])],[ - rb_cv_symbol_prefix=`$NM conftest.$ac_objext | + rb_cv_symbol_prefix=`$NM conftest.$ac_objext 2>/dev/null | sed -n ['/.*T[ ]\([^ ]*\)conftest_external.*/!d;s//\1/p;q']` ], [rb_cv_symbol_prefix='']) diff --git a/tool/leaked-globals b/tool/leaked-globals index 73da769318ad37..c1a3fb42b8869c 100755 --- a/tool/leaked-globals +++ b/tool/leaked-globals @@ -60,6 +60,23 @@ end # extern, so we allow the Rust one. REPLACE.push("rust_eh_personality") if RUBY_PLATFORM.include?("darwin") +# Under LTO the compiler emits raw LLVM bitcode objects (magic "BC\xC0\xDE", or +# the bitcode wrapper magic). nm carries --no-llvm-bc (added in configure so it +# ignores Rust's newer-version bitcode) and so cannot read them, only emitting +# "not recognized" noise. Drop them; leaked symbols are still checked on the +# linked library and any non-bitcode objects. +if defined?(NM) and NM.include?("--no-llvm-bc") + bitcode = 0 + ARGV.reject! do |n| + next false unless File.file?(n) + case File.binread(n, 4)&.bytes + when [0x42, 0x43, 0xC0, 0xDE], [0xDE, 0xC0, 0x17, 0x0B] + bitcode += 1 + end + end + puts "Skip #{col.skip("#{bitcode} LLVM bitcode object(s)")}" if bitcode > 0 +end + print "Checking leaked global symbols..." STDOUT.flush if soext @@ -105,7 +122,7 @@ Pipe.new(NM + ARGV).each do |line| puts col.fail("leaked") if count.zero? count += 1 puts " #{n}" -end +end unless ARGV.empty? case count when 0 puts col.pass("none") From 49fba4557009567dab8978eb8e6916b26d8f08c8 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 17 Jun 2026 14:30:16 +0900 Subject: [PATCH 5/6] Remove unused return value of gc_malloc_counters_snapshot --- gc/default/default.c | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index 18d7a05b336430..66df18d879a101 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -1047,27 +1047,15 @@ gc_malloc_counters_increase_unsigned(rb_objspace_t *objspace, const struct gc_ma return (size_t)inc; } -static inline int64_t +static inline void gc_malloc_counters_snapshot(rb_objspace_t *objspace, struct gc_malloc_bytes *c) { MALLOC_COUNTERS_LOCK(objspace); gc_counter_t malloc_now = gc_counter_load_relaxed(&c->malloc); gc_counter_t free_now = gc_counter_load_relaxed(&c->free); - gc_counter_t malloc_at = gc_counter_load_relaxed(&c->malloc_at_last_gc); - gc_counter_t free_at = gc_counter_load_relaxed(&c->free_at_last_gc); gc_counter_store_release(&c->malloc_at_last_gc, malloc_now); gc_counter_store_release(&c->free_at_last_gc, free_now); MALLOC_COUNTERS_UNLOCK(objspace); - - gc_counter_t malloc_delta = malloc_now - malloc_at; - gc_counter_t free_delta = free_now - free_at; - - if (malloc_delta >= free_delta) { - return (int64_t)(malloc_delta - free_delta); - } - else { - return -(int64_t)(free_delta - malloc_delta); - } } #define heap_pages_lomem objspace->heap_pages.range[0] @@ -4224,10 +4212,10 @@ gc_sweep_finish(rb_objspace_t *objspace) } } - (void)gc_malloc_counters_snapshot(objspace, &objspace->malloc_counters.counters); + gc_malloc_counters_snapshot(objspace, &objspace->malloc_counters.counters); #if RGENGC_ESTIMATE_OLDMALLOC if (objspace->profile.latest_gc_info & GPR_FLAG_MAJOR_MASK) { - (void)gc_malloc_counters_snapshot(objspace, &objspace->malloc_counters.oldcounters); + gc_malloc_counters_snapshot(objspace, &objspace->malloc_counters.oldcounters); } #endif From cae36665ab7991ecb38cf62bc872a84415549784 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 17 Jun 2026 08:26:47 +0200 Subject: [PATCH 6/6] [ruby/json] ResumableParser#<<: call rb_str_modify before shrinking the buffer Fix: https://github.com/ruby/json/issues/1013 The string may be shared. https://github.com/ruby/json/commit/11ed69439f --- ext/json/parser/parser.c | 9 ++++++--- test/json/resumable_parser_test.rb | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index c216faf4b5a56e..136aab6ae764f5 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -2300,16 +2300,19 @@ static VALUE cResumableParser_feed(VALUE self, VALUE str) const size_t size = parser->state.end - parser->state.start; const size_t consumed = size - remaining; - char *old_ptr = RSTRING_PTR(parser->buffer); if (RB_OBJ_FROZEN_RAW(parser->buffer)) { VALUE new_buffer = rb_obj_hide(rb_str_buf_new(remaining + RSTRING_LEN(str))); - MEMCPY(RSTRING_PTR(new_buffer), old_ptr + consumed, char, remaining); + + char *old_ptr = RSTRING_PTR(parser->buffer); + memcpy(RSTRING_PTR(new_buffer), old_ptr + consumed, remaining); rb_str_set_len(new_buffer, remaining); offset = 0; parser->buffer = new_buffer; } else if (consumed > (size / 2) && size >= 512) { - MEMMOVE(old_ptr, old_ptr + consumed, char, remaining); + rb_str_modify(parser->buffer); + char *old_ptr = RSTRING_PTR(parser->buffer); + memmove(old_ptr, old_ptr + consumed, remaining); rb_str_set_len(parser->buffer, remaining); offset = 0; } diff --git a/test/json/resumable_parser_test.rb b/test/json/resumable_parser_test.rb index 600cd33e5ecf28..52f1356a704f9a 100644 --- a/test/json/resumable_parser_test.rb +++ b/test/json/resumable_parser_test.rb @@ -244,6 +244,21 @@ def test_spill_frames_stack assert_equal expected, @parser.value end + def test_buffer_shrink + doc1 = '{"a":"' + ("x" * 800) + '"} {' # >= 512 bytes + doc2 = '"b":1} ' + + parser = JSON::ResumableParser.new({}) + + parser << doc1 # internal buffer becomes a *shared* string here + parser.parse # consume doc1 -> >50% of a >=512B buffer is now consumed + parser.value + + parser << doc2 # buffer is shrinked + parser.parse + parser.value + end + private def assert_partial_value(expected, json)