From 46bcd7e58c830e046439f3bccbfa745342ea01ff Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sat, 18 Jul 2026 16:50:03 +0900 Subject: [PATCH 01/16] Avoid duplicated `.gdbinit` loads by `RUNRUBY_USE_GDB=true` If we use out-of-source build and have `${BUILD_DIR}/run.gdb` (by `make run.gdb`), `${SOURCE_DIR}/.gdbinit` is loaded twice because `run.gdb` loads `${SOURCE_DIR}/.gdbinit`. If we have `run.gdb`, we don't need `-x ${SOURCE_DIR}/.gdbinit`. There is one more problem for `${BUILD_DIR}/run.gdb` and `${SOURCE_DIR}/.gdbinit`. `source -s misc/gdb.py` in `${SOURCE_DIR}/.gdbinit` is failed when we use out-of-source build and `${BUILD_DIR}/run.gdb` because `${SOURCE_DIR}` isn't included in search path of `source`. If we have `directory $(srcdir)` in `${BUILD_DIR}/run.gdb`, `source -s misc/gdb.py` works in `${SOURCE_DIR}/.gdbinit` that is loaded from `${BUILD_DIR}/run.gdb`. --- common.mk | 5 +++-- tool/runruby.rb | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/common.mk b/common.mk index 4202dbddb2a124..e47d030724310a 100644 --- a/common.mk +++ b/common.mk @@ -1469,8 +1469,9 @@ run.gdb: echo '# handle SIGINT nostop' >> run.gdb echo '# handle SIGPIPE nostop' >> run.gdb echo '# b rb_longjmp' >> run.gdb - echo source $(srcdir)/breakpoints.gdb >> run.gdb - echo source $(srcdir)/.gdbinit >> run.gdb + echo directory $(srcdir) >> run.gdb + echo source -s breakpoints.gdb >> run.gdb + echo source -s .gdbinit >> run.gdb echo 'set $$_exitcode = -999' >> run.gdb echo run >> run.gdb echo 'if $$_exitcode != -999' >> run.gdb diff --git a/tool/runruby.rb b/tool/runruby.rb index 1f7268bd4114b7..d610e2ec89ead7 100755 --- a/tool/runruby.rb +++ b/tool/runruby.rb @@ -163,10 +163,12 @@ def File.realpath(*args) if debugger case debugger when :gdb - debugger = %W'gdb -x #{srcdir}/.gdbinit' + debugger = %W'gdb' if File.exist?(gdb = 'run.gdb') or File.exist?(gdb = File.join(abs_archdir, 'run.gdb')) debugger.push('-x', gdb) + else + debugger.push('-x', "#{srcdir}/.gdbinit") end debugger << '--args' when :lldb From ab62dc07ccba1fce0e9372d3243ca55c33603e78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 02:09:18 +0000 Subject: [PATCH 02/16] Bump the github-actions group across 2 directories with 2 updates Bumps the github-actions group with 2 updates in the / directory: [actions/checkout](https://github.com/actions/checkout) and [taiki-e/install-action](https://github.com/taiki-e/install-action). Bumps the github-actions group with 1 update in the /.github/actions/setup/directories directory: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 7.0.0 to 7.0.1 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0...3d3c42e5aac5ba805825da76410c181273ba90b1) Updates `taiki-e/install-action` from 2.83.4 to 2.84.0 - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/07b4745e0c39a41822af610387492e3e53aa222b...a6b2e2dcd845ddd7f509ce4f3ed3d922b80cc5d9) Updates `actions/checkout` from 7.0.0 to 7.0.1 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0...3d3c42e5aac5ba805825da76410c181273ba90b1) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: taiki-e/install-action dependency-version: 2.84.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: actions/checkout dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/actions/setup/directories/action.yml | 2 +- .github/workflows/annocheck.yml | 2 +- .github/workflows/auto_review_pr.yml | 2 +- .github/workflows/baseruby.yml | 2 +- .github/workflows/bundled_gems.yml | 2 +- .github/workflows/check_dependencies.yml | 2 +- .github/workflows/check_misc.yml | 4 +-- .github/workflows/check_sast.yml | 4 +-- .github/workflows/compilers.yml | 26 ++++++++++---------- .github/workflows/crosscompile.yml | 2 +- .github/workflows/cygwin.yml | 2 +- .github/workflows/default_gems_list.yml | 2 +- .github/workflows/macos.yml | 2 +- .github/workflows/mingw.yml | 2 +- .github/workflows/modgc.yml | 2 +- .github/workflows/parse_y.yml | 2 +- .github/workflows/post_push.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/rust-warnings.yml | 2 +- .github/workflows/scorecards.yml | 2 +- .github/workflows/spec_guards.yml | 2 +- .github/workflows/sync_default_gems.yml | 2 +- .github/workflows/tarball-macos.yml | 2 +- .github/workflows/tarball-test.yml | 2 +- .github/workflows/tarball-ubuntu.yml | 2 +- .github/workflows/tarball-windows.yml | 2 +- .github/workflows/ubuntu.yml | 6 ++--- .github/workflows/wasm.yml | 2 +- .github/workflows/windows.yml | 2 +- .github/workflows/yjit-macos.yml | 2 +- .github/workflows/yjit-ubuntu.yml | 6 ++--- .github/workflows/zjit-macos.yml | 8 +++--- .github/workflows/zjit-ubuntu.yml | 10 ++++---- 33 files changed, 58 insertions(+), 58 deletions(-) diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml index 7b955d4ff0dab2..78b57c17619a51 100644 --- a/.github/actions/setup/directories/action.yml +++ b/.github/actions/setup/directories/action.yml @@ -108,7 +108,7 @@ runs: git config --global init.defaultBranch garbage - if: inputs.checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: path: ${{ inputs.srcdir }} fetch-depth: ${{ inputs.fetch-depth }} diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index 1400dcc38bc546..f58363f76a23e6 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -61,7 +61,7 @@ jobs: - run: id working-directory: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/auto_review_pr.yml b/.github/workflows/auto_review_pr.yml index b3f73a206afeda..866029a487bacb 100644 --- a/.github/workflows/auto_review_pr.yml +++ b/.github/workflows/auto_review_pr.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index ce517b25c586b7..301366f4787583 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -53,7 +53,7 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler: none - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 28df5172ca037a..dce02c03093c7f 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index 7f142eb6a3bb1a..709af602ff908b 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -30,7 +30,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 4cde7b8e294b6a..b3a56c8c8e24e9 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} persist-credentials: false @@ -100,7 +100,7 @@ jobs: { echo version=$2; echo ref=$4; } >> $GITHUB_OUTPUT - name: Checkout rdoc - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: ruby/rdoc ref: ${{ steps.rdoc.outputs.ref }} diff --git a/.github/workflows/check_sast.yml b/.github/workflows/check_sast.yml index 291cc37a1ff257..943bb71685f8a6 100644 --- a/.github/workflows/check_sast.yml +++ b/.github/workflows/check_sast.yml @@ -40,7 +40,7 @@ jobs: security-events: write steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -73,7 +73,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index da0eff2008121a..51f16b09be730a 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -56,7 +56,7 @@ jobs: timeout-minutes: 60 steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github, persist-credentials: false } # Set fetch-depth: 10 so that Launchable can receive commits information. - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } @@ -79,7 +79,7 @@ jobs: timeout-minutes: 60 steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github, persist-credentials: false } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - name: 'GCC 15 LTO' @@ -107,7 +107,7 @@ jobs: timeout-minutes: 60 steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github, persist-credentials: false } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'clang 20', with: { tag: 'clang-20' }, timeout-minutes: 8 } @@ -126,7 +126,7 @@ jobs: timeout-minutes: 60 steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github, persist-credentials: false } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'clang 13', with: { tag: 'clang-13' }, timeout-minutes: 8 } @@ -147,7 +147,7 @@ jobs: timeout-minutes: 60 steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github, persist-credentials: false } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } # -Wno-strict-prototypes is necessary with current clang-15 since @@ -173,7 +173,7 @@ jobs: timeout-minutes: 60 steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github, persist-credentials: false } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'C++20', with: { CXXFLAGS: '-std=c++20 -Werror=pedantic -pedantic-errors -Wno-c++11-long-long' }, timeout-minutes: 8 } @@ -193,7 +193,7 @@ jobs: timeout-minutes: 60 steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github, persist-credentials: false } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'disable-jit', with: { append_configure: '--disable-yjit --disable-zjit' }, timeout-minutes: 8 } @@ -213,7 +213,7 @@ jobs: timeout-minutes: 60 steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github, persist-credentials: false } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'NDEBUG', with: { cppflags: '-DNDEBUG' }, timeout-minutes: 8 } @@ -232,7 +232,7 @@ jobs: timeout-minutes: 60 steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github, persist-credentials: false } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'HASH_DEBUG', with: { cppflags: '-DHASH_DEBUG' }, timeout-minutes: 8 } @@ -252,7 +252,7 @@ jobs: timeout-minutes: 60 steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github, persist-credentials: false } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'USE_LAZY_LOAD', with: { cppflags: '-DUSE_LAZY_LOAD' }, timeout-minutes: 8 } @@ -273,7 +273,7 @@ jobs: timeout-minutes: 60 steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github, persist-credentials: false } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'GC_DEBUG_STRESS_TO_CLASS', with: { cppflags: '-DGC_DEBUG_STRESS_TO_CLASS' }, timeout-minutes: 8 } @@ -292,7 +292,7 @@ jobs: timeout-minutes: 60 steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github, persist-credentials: false } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - { uses: './.github/actions/compilers', name: 'VM_DEBUG_BP_CHECK', with: { cppflags: '-DVM_DEBUG_BP_CHECK' }, timeout-minutes: 8 } @@ -322,7 +322,7 @@ jobs: - 'compileB' - 'compileC' steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github, persist-credentials: false } - uses: ./.github/actions/slack with: diff --git a/.github/workflows/crosscompile.yml b/.github/workflows/crosscompile.yml index 9d08ce96c250ce..79b2868161d7c5 100644 --- a/.github/workflows/crosscompile.yml +++ b/.github/workflows/crosscompile.yml @@ -52,7 +52,7 @@ jobs: )}} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index 126aa8546c69c5..aeb98b95eca222 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -40,7 +40,7 @@ jobs: steps: - run: git config --global core.autocrlf input - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/default_gems_list.yml b/.github/workflows/default_gems_list.yml index b21ff7b42ab9f6..457311239caf76 100644 --- a/.github/workflows/default_gems_list.yml +++ b/.github/workflows/default_gems_list.yml @@ -23,7 +23,7 @@ jobs: if: ${{ github.repository == 'ruby/ruby' }} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index c8e32674832359..c4fd3faa14b8cf 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -70,7 +70,7 @@ jobs: )}} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 91a3571ad4235d..409a835cae5483 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -168,7 +168,7 @@ jobs: [ ${#failed[@]} -eq 0 ] shell: sh - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/modgc.yml b/.github/workflows/modgc.yml index 7e423bc7d139af..d0a980700821a8 100644 --- a/.github/workflows/modgc.yml +++ b/.github/workflows/modgc.yml @@ -53,7 +53,7 @@ jobs: )}} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/parse_y.yml b/.github/workflows/parse_y.yml index 13610a9bad46f9..1087ac946b7b51 100644 --- a/.github/workflows/parse_y.yml +++ b/.github/workflows/parse_y.yml @@ -51,7 +51,7 @@ jobs: )}} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index fb1c324b32aa2d..53a4644b3bfebe 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -35,7 +35,7 @@ jobs: REDMINE_SYS_API_KEY: ${{ secrets.REDMINE_SYS_API_KEY }} if: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/ruby_') }} - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 500 # for notify-slack-commits token: ${{ secrets.MATZBOT_AUTO_UPDATE_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8ec6c75e5e5b1d..daa852fc3cba64 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,7 +18,7 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/rust-warnings.yml b/.github/workflows/rust-warnings.yml index ab209587240a38..6a88a4247d996a 100644 --- a/.github/workflows/rust-warnings.yml +++ b/.github/workflows/rust-warnings.yml @@ -41,7 +41,7 @@ jobs: )}} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index dae9640a9d0f36..bdbd8090d2f719 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -34,7 +34,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 128b96887e030c..6823b94fd9bfb4 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -45,7 +45,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index a5a5f4243840a2..5795d4abf03066 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -34,7 +34,7 @@ jobs: if: ${{ github.repository == 'ruby/ruby' }} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 name: Check out ruby/ruby with: token: ${{ github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tarball-macos.yml b/.github/workflows/tarball-macos.yml index 1302b653f0c401..d248338b916d6c 100644 --- a/.github/workflows/tarball-macos.yml +++ b/.github/workflows/tarball-macos.yml @@ -87,7 +87,7 @@ jobs: "$PREFIX/bin/gem" -v "$PREFIX/bin/bundle" -v if: matrix.test_task == 'check' - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout: .github/actions/slack sparse-checkout-cone-mode: false diff --git a/.github/workflows/tarball-test.yml b/.github/workflows/tarball-test.yml index f27f842332ba4f..99724fd2eff485 100644 --- a/.github/workflows/tarball-test.yml +++ b/.github/workflows/tarball-test.yml @@ -46,7 +46,7 @@ jobs: || contains(github.event.pull_request.labels.*.name, 'Documentation') || (github.event.pull_request.user.login == 'dependabot[bot]') )}} - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 1 # actions/checkout fetches all heads/tags unless > 0 persist-credentials: false diff --git a/.github/workflows/tarball-ubuntu.yml b/.github/workflows/tarball-ubuntu.yml index e458c9f8079517..8d3dbe1037a378 100644 --- a/.github/workflows/tarball-ubuntu.yml +++ b/.github/workflows/tarball-ubuntu.yml @@ -126,7 +126,7 @@ jobs: if: matrix.test_task == 'check' - name: Show .local run: find $HOME/.local -ls - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout: .github/actions/slack sparse-checkout-cone-mode: false diff --git a/.github/workflows/tarball-windows.yml b/.github/workflows/tarball-windows.yml index 75661eef71f469..c3f12fe970cea7 100644 --- a/.github/workflows/tarball-windows.yml +++ b/.github/workflows/tarball-windows.yml @@ -150,7 +150,7 @@ jobs: timeout-minutes: 70 continue-on-error: ${{ matrix.continue-on-error || false }} - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout: .github/actions/slack sparse-checkout-cone-mode: false diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index b3ff647df679a4..da918d06d794c5 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -69,7 +69,7 @@ jobs: )}} steps: &make-steps - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -243,7 +243,7 @@ jobs: )}} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -261,7 +261,7 @@ jobs: - run: make install - name: Checkout ruby-bench - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: ruby/ruby-bench persist-credentials: false diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index e9841ac95e95a4..b4cc2a6c8ff8c1 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -59,7 +59,7 @@ jobs: )}} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 5e6282c36dcbed..43b025e0c8ea4a 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -70,7 +70,7 @@ jobs: bundler: none windows-toolchain: none - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false sparse-checkout-cone-mode: false diff --git a/.github/workflows/yjit-macos.yml b/.github/workflows/yjit-macos.yml index a17bffcf41e709..7d93dbafd92672 100644 --- a/.github/workflows/yjit-macos.yml +++ b/.github/workflows/yjit-macos.yml @@ -65,7 +65,7 @@ jobs: )}} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index c52bc40f215bdf..ed1202c25f77ab 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -41,7 +41,7 @@ jobs: )}} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -75,7 +75,7 @@ jobs: )}} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -130,7 +130,7 @@ jobs: )}} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index f109342fd076e4..b6afbc223af1cd 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -74,7 +74,7 @@ jobs: )}} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -98,7 +98,7 @@ jobs: rustup install ${{ matrix.rust_version }} --profile minimal rustup default ${{ matrix.rust_version }} - - uses: taiki-e/install-action@07b4745e0c39a41822af610387492e3e53aa222b # v2.83.4 + - uses: taiki-e/install-action@a6b2e2dcd845ddd7f509ce4f3ed3d922b80cc5d9 # v2.84.0 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} @@ -204,7 +204,7 @@ jobs: )}} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -226,7 +226,7 @@ jobs: run: echo "MAKEFLAGS=" >> "$GITHUB_ENV" - name: Checkout ruby-bench - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false repository: ruby/ruby-bench diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 31c3c97c507f6f..1f4f663a744f6d 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -46,7 +46,7 @@ jobs: )}} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -129,7 +129,7 @@ jobs: )}} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -142,7 +142,7 @@ jobs: ruby-version: '3.1' bundler: none - - uses: taiki-e/install-action@07b4745e0c39a41822af610387492e3e53aa222b # v2.83.4 + - uses: taiki-e/install-action@a6b2e2dcd845ddd7f509ce4f3ed3d922b80cc5d9 # v2.84.0 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} @@ -278,7 +278,7 @@ jobs: )}} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -296,7 +296,7 @@ jobs: - run: make install - name: Checkout ruby-bench - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: ruby/ruby-bench persist-credentials: false From 7dd0e3d17b1ec782113ebf14e551676dc15d6e09 Mon Sep 17 00:00:00 2001 From: git Date: Tue, 21 Jul 2026 02:29:55 +0000 Subject: [PATCH 03/16] [DOC] Update bundled gems list at ab62dc07ccba1fce0e9372d3243ca5 --- NEWS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 08c75c1a679085..f0ff01ec810043 100644 --- a/NEWS.md +++ b/NEWS.md @@ -120,7 +120,7 @@ releases. * bundler 4.1.0.dev * 4.0.3 to [v4.0.4][bundler-v4.0.4], [v4.0.5][bundler-v4.0.5], [v4.0.6][bundler-v4.0.6], [v4.0.7][bundler-v4.0.7], [v4.0.8][bundler-v4.0.8], [v4.0.9][bundler-v4.0.9], [v4.0.10][bundler-v4.0.10], [v4.0.11][bundler-v4.0.11], [v4.0.12][bundler-v4.0.12], [v4.0.13][bundler-v4.0.13], [v4.0.14][bundler-v4.0.14], [v4.0.15][bundler-v4.0.15], [v4.0.16][bundler-v4.0.16] * erb 6.0.6 - * 6.0.1 to [v6.0.1.1][erb-v6.0.1.1], [v6.0.2][erb-v6.0.2], [v6.0.3][erb-v6.0.3], [v6.0.4][erb-v6.0.4], [v6.0.5][erb-v6.0.5] + * 6.0.1 to [v6.0.1.1][erb-v6.0.1.1], [v6.0.2][erb-v6.0.2], [v6.0.3][erb-v6.0.3], [v6.0.4][erb-v6.0.4], [v6.0.5][erb-v6.0.5], [v6.0.6][erb-v6.0.6] * error_highlight 0.7.2 * ipaddr 1.2.9 * 1.2.8 to [v1.2.9][ipaddr-v1.2.9] @@ -287,6 +287,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [erb-v6.0.3]: https://github.com/ruby/erb/releases/tag/v6.0.3 [erb-v6.0.4]: https://github.com/ruby/erb/releases/tag/v6.0.4 [erb-v6.0.5]: https://github.com/ruby/erb/releases/tag/v6.0.5 +[erb-v6.0.6]: https://github.com/ruby/erb/releases/tag/v6.0.6 [ipaddr-v1.2.9]: https://github.com/ruby/ipaddr/releases/tag/v1.2.9 [json-v2.18.1]: https://github.com/ruby/json/releases/tag/v2.18.1 [json-v2.19.0]: https://github.com/ruby/json/releases/tag/v2.19.0 From 3b53517b5ad8d09b60ed50d9689636c9f8b31441 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 12 Jun 2026 13:59:59 +0900 Subject: [PATCH 04/16] Register error decoration gem autoloads in rb_define_gem_modules The empty modules existed only as feature flags for gem_prelude to require the gems at boot. Autoload entries serve the same flag role and also load the gems on direct constant access. gem_prelude now requires 'syntax_suggest' itself, which only loads core_ext, so that the eager require does not trigger the autoload circularly. Co-Authored-By: Claude Fable 5 --- builtin.c | 11 ++++++++--- depend | 2 ++ gem_prelude.rb | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/builtin.c b/builtin.c index 03c9d03bc3183c..451a278942bcda 100644 --- a/builtin.c +++ b/builtin.c @@ -1,5 +1,7 @@ #include "internal.h" #include "internal/box.h" +#include "internal/string.h" +#include "internal/variable.h" #include "vm_core.h" #include "iseq.h" #include "builtin.h" @@ -91,14 +93,17 @@ rb_define_gem_modules(VALUE flags_value, VALUE _) if (flags->gem) { rb_define_module("Gem"); + // Make the error decoration gems autoload on first constant access. + // gem_prelude.rb loads them for error display via an + // Exception#detailed_message hook. if (flags->error_highlight) { - rb_define_module("ErrorHighlight"); + rb_autoload_str(rb_cObject, rb_intern("ErrorHighlight"), rb_fstring_cstr("error_highlight")); } if (flags->did_you_mean) { - rb_define_module("DidYouMean"); + rb_autoload_str(rb_cObject, rb_intern("DidYouMean"), rb_fstring_cstr("did_you_mean")); } if (flags->syntax_suggest) { - rb_define_module("SyntaxSuggest"); + rb_autoload_str(rb_cObject, rb_intern("SyntaxSuggest"), rb_fstring_cstr("syntax_suggest")); } } diff --git a/depend b/depend index e9e8bdc3fed063..70a18b4c9a35d1 100644 --- a/depend +++ b/depend @@ -1029,6 +1029,7 @@ builtin.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h builtin.$(OBJEXT): $(top_srcdir)/internal/serial.h builtin.$(OBJEXT): $(top_srcdir)/internal/set_table.h builtin.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +builtin.$(OBJEXT): $(top_srcdir)/internal/string.h builtin.$(OBJEXT): $(top_srcdir)/internal/struct.h builtin.$(OBJEXT): $(top_srcdir)/internal/variable.h builtin.$(OBJEXT): $(top_srcdir)/internal/vm.h @@ -1085,6 +1086,7 @@ builtin.$(OBJEXT): {$(VPATH)}builtin_binary.rbbin builtin.$(OBJEXT): {$(VPATH)}config.h builtin.$(OBJEXT): {$(VPATH)}constant.h builtin.$(OBJEXT): {$(VPATH)}defines.h +builtin.$(OBJEXT): {$(VPATH)}encindex.h builtin.$(OBJEXT): {$(VPATH)}encoding.h builtin.$(OBJEXT): {$(VPATH)}id.h builtin.$(OBJEXT): {$(VPATH)}id_table.h diff --git a/gem_prelude.rb b/gem_prelude.rb index 1b78d80c726625..8a4be42fd49f7a 100644 --- a/gem_prelude.rb +++ b/gem_prelude.rb @@ -21,7 +21,7 @@ end if defined?(DidYouMean) begin - require 'syntax_suggest/core_ext' + require 'syntax_suggest' rescue LoadError warn "`syntax_suggest' was not loaded." end if defined?(SyntaxSuggest) From cadf8e7933be1164993531e413a1f1cfd0c84554 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 12 Jun 2026 15:35:25 +0900 Subject: [PATCH 05/16] Lazy load error_highlight, did_you_mean, and syntax_suggest [Feature #21951] Defer loading these gems from boot to the first error display. They only decorate Exception#detailed_message, so requiring them at boot is unnecessary overhead. `ruby -e1` with user-installed gems drops from 114.3ms to 30.2ms on Apple M1 Pro, matching the --disable-* flags for the three gems. exc_detailed_message forces the autoload entries once on the first call, only in the main Ractor, and then re-dispatches to pick up the detailed_message decorators the gems prepend. When the first call arrives through an already-prepended decorator, re-dispatching is skipped to avoid decorating twice. The gems are loaded with the C-level require so that displaying an error neither invokes nor depends on Kernel#require monkeypatches. Co-Authored-By: Claude Fable 5 --- depend | 2 ++ error.c | 42 ++++++++++++++++++++++++++++++++++++++++ gem_prelude.rb | 18 ----------------- test/ruby/test_box.rb | 2 +- test/ruby/test_ractor.rb | 17 ++++++++++++++++ 5 files changed, 62 insertions(+), 19 deletions(-) diff --git a/depend b/depend index 70a18b4c9a35d1..8667ccdb1d4ce4 100644 --- a/depend +++ b/depend @@ -5418,6 +5418,8 @@ error.$(OBJEXT): {$(VPATH)}missing.h error.$(OBJEXT): {$(VPATH)}node.h error.$(OBJEXT): {$(VPATH)}onigmo.h error.$(OBJEXT): {$(VPATH)}oniguruma.h +error.$(OBJEXT): {$(VPATH)}ractor.h +error.$(OBJEXT): {$(VPATH)}ractor_core.h error.$(OBJEXT): {$(VPATH)}ruby_assert.h error.$(OBJEXT): {$(VPATH)}ruby_atomic.h error.$(OBJEXT): {$(VPATH)}rubyparser.h diff --git a/error.c b/error.c index 751300565e2aa8..6898efddb190d1 100644 --- a/error.c +++ b/error.c @@ -47,8 +47,10 @@ #include "ruby/encoding.h" #include "ruby/st.h" #include "ruby/util.h" +#include "internal/vm.h" #include "ruby_assert.h" #include "vm_core.h" +#include "ractor_core.h" #include "yjit.h" #include "zjit.h" @@ -1760,6 +1762,42 @@ exc_message(VALUE exc) return rb_funcallv(exc, idTo_s, 0, 0); } +static VALUE +load_decoration_gem(VALUE feature) +{ + // The C-level require bypasses Kernel#require monkeypatches; + // displaying an error must not invoke or depend on them. + return rb_require_string(feature); +} + +// Load error_highlight, did_you_mean, and syntax_suggest on the first +// error display instead of at boot. rb_define_gem_modules registers an +// autoload entry for each enabled gem; disabled gems have no entry and +// are skipped. Returns whether the caller should re-dispatch to pick up +// the detailed_message decorators the gems prepend. +static bool +lazy_load_decoration_gems(VALUE exc) +{ + static bool done = false; + if (done || !rb_ractor_main_p()) return false; + done = true; + + // When entered through super from a decorator already sitting above + // this method, the caller decorates the result; re-dispatching would + // decorate it twice. + bool redispatch = rb_method_basic_definition_p(CLASS_OF(exc), id_detailed_message); + + static const char *const gems[] = {"ErrorHighlight", "DidYouMean", "SyntaxSuggest"}; + for (size_t i = 0; i < numberof(gems); i++) { + VALUE feature = rb_autoload_p(rb_cObject, rb_intern(gems[i])); + if (NIL_P(feature)) continue; + int state; + rb_protect(load_decoration_gem, feature, &state); + if (state) rb_set_errinfo(Qnil); + } + return redispatch; +} + /* * call-seq: * detailed_message(highlight: false, **kwargs) -> string @@ -1808,6 +1846,10 @@ exc_message(VALUE exc) static VALUE exc_detailed_message(int argc, VALUE *argv, VALUE exc) { + if (lazy_load_decoration_gems(exc)) { + return rb_funcallv_kw(exc, id_detailed_message, argc, argv, RB_PASS_CALLED_KEYWORDS); + } + VALUE opt; rb_scan_args(argc, argv, "0:", &opt); diff --git a/gem_prelude.rb b/gem_prelude.rb index 8a4be42fd49f7a..1a0af96aedff20 100644 --- a/gem_prelude.rb +++ b/gem_prelude.rb @@ -7,21 +7,3 @@ else require 'bundled_gems' end if defined?(Gem) - -begin - require 'error_highlight' -rescue LoadError - warn "`error_highlight' was not loaded." -end if defined?(ErrorHighlight) - -begin - require 'did_you_mean' -rescue LoadError - warn "`did_you_mean' was not loaded." -end if defined?(DidYouMean) - -begin - require 'syntax_suggest' -rescue LoadError - warn "`syntax_suggest' was not loaded." -end if defined?(SyntaxSuggest) diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 3d19bcfc4b2ebb..ab6f53b92ad72e 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -869,7 +869,7 @@ def test_prelude_gems_and_loaded_features assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[1], error[1] assert_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb' - assert_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb' + refute_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb' assert_includes output.grep(/^after:/).join("\n"), '/bundled_gems.rb' assert_includes output.grep(/^after:/).join("\n"), '/error_highlight.rb' end diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index 9c4bcad7f00742..bb33355a139daf 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -401,6 +401,23 @@ def test_ractor_does_not_inherit_fiber_storage RUBY end + def test_detailed_message_in_ractor + # gem_prelude defers loading error_highlight, did_you_mean, and + # syntax_suggest until the first error display, which is not possible + # in non-main Ractors. Exception#detailed_message must still return + # the plain message there. [Feature #21951] + assert_ractor(<<~'RUBY', args: ["--enable=gems"], ignore_stderr: true) + message = Ractor.new do + begin + raise "boom" + rescue => e + e.detailed_message(highlight: false) + end + end.value + assert_include(message, "boom") + RUBY + end + def assert_make_shareable(obj) refute Ractor.shareable?(obj), "object was already shareable" Ractor.make_shareable(obj) From 201dece9d7464a5fba21a8c355da68f09445b748 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 15 Jun 2026 10:00:19 +0900 Subject: [PATCH 06/16] Eager load the error decoration gems from Process.warmup The decoration gems are loaded lazily on the first error display, which in a pre-forking server happens after fork in each child. That loads their code outside copy-on-write shared memory and busts method caches at runtime when they prepend Exception#detailed_message. Process.warmup, called before the first fork by such servers, now loads them eagerly so the prepended decorators land in shared memory and the cache invalidation happens during boot. Co-Authored-By: Claude Fable 5 --- error.c | 54 +++++++++++++++++++++++++++------------ internal/error.h | 1 + process.c | 7 +++++ test/ruby/test_process.rb | 12 +++++++++ 4 files changed, 58 insertions(+), 16 deletions(-) diff --git a/error.c b/error.c index 6898efddb190d1..1dafd6b214580e 100644 --- a/error.c +++ b/error.c @@ -1762,6 +1762,10 @@ exc_message(VALUE exc) return rb_funcallv(exc, idTo_s, 0, 0); } +// Whether error_highlight, did_you_mean, and syntax_suggest have already +// been loaded (lazily on the first error, or eagerly via Process.warmup). +static bool decoration_gems_loaded = false; + static VALUE load_decoration_gem(VALUE feature) { @@ -1770,23 +1774,12 @@ load_decoration_gem(VALUE feature) return rb_require_string(feature); } -// Load error_highlight, did_you_mean, and syntax_suggest on the first -// error display instead of at boot. rb_define_gem_modules registers an -// autoload entry for each enabled gem; disabled gems have no entry and -// are skipped. Returns whether the caller should re-dispatch to pick up -// the detailed_message decorators the gems prepend. -static bool -lazy_load_decoration_gems(VALUE exc) +// Require error_highlight, did_you_mean, and syntax_suggest. +// rb_define_gem_modules registers an autoload entry for each enabled gem; +// disabled gems have no entry and are skipped. +static void +require_decoration_gems(void) { - static bool done = false; - if (done || !rb_ractor_main_p()) return false; - done = true; - - // When entered through super from a decorator already sitting above - // this method, the caller decorates the result; re-dispatching would - // decorate it twice. - bool redispatch = rb_method_basic_definition_p(CLASS_OF(exc), id_detailed_message); - static const char *const gems[] = {"ErrorHighlight", "DidYouMean", "SyntaxSuggest"}; for (size_t i = 0; i < numberof(gems); i++) { VALUE feature = rb_autoload_p(rb_cObject, rb_intern(gems[i])); @@ -1795,9 +1788,38 @@ lazy_load_decoration_gems(VALUE exc) rb_protect(load_decoration_gem, feature, &state); if (state) rb_set_errinfo(Qnil); } +} + +// Load the decoration gems on the first error display instead of at boot. +// Returns whether the caller should re-dispatch to pick up the +// detailed_message decorators the gems prepend. +static bool +lazy_load_decoration_gems(VALUE exc) +{ + if (decoration_gems_loaded || !rb_ractor_main_p()) return false; + decoration_gems_loaded = true; + + // When entered through super from a decorator already sitting above + // this method, the caller decorates the result; re-dispatching would + // decorate it twice. + bool redispatch = rb_method_basic_definition_p(CLASS_OF(exc), id_detailed_message); + + require_decoration_gems(); return redispatch; } +// Load the decoration gems eagerly, e.g. from Process.warmup before a +// pre-forking server forks, so the prepended detailed_message decorators +// land in shared memory and do not bust method caches at runtime. +void +rb_eager_load_detailed_message_extension(void) +{ + if (decoration_gems_loaded || !rb_ractor_main_p()) return; + decoration_gems_loaded = true; + + require_decoration_gems(); +} + /* * call-seq: * detailed_message(highlight: false, **kwargs) -> string diff --git a/internal/error.h b/internal/error.h index fead2aee9562d8..8cda52827360d6 100644 --- a/internal/error.h +++ b/internal/error.h @@ -189,6 +189,7 @@ void rb_bug_without_die(const char *fmt, ...); NORETURN(void rb_no_implicit_conversion(VALUE val, const char *tname)); NORETURN(void rb_cant_convert(VALUE val, const char *tname)); NORETURN(void rb_cant_convert_invalid_return(VALUE val, const char *tname, const char *method_name, VALUE ret)); +void rb_eager_load_detailed_message_extension(void); RUBY_SYMBOL_EXPORT_BEGIN /* error.c (export) */ diff --git a/process.c b/process.c index 82e3e376fe6f1e..34ef4897ec7e29 100644 --- a/process.c +++ b/process.c @@ -8713,11 +8713,18 @@ static VALUE rb_mProcID_Syscall; * * Frees all empty heap pages and increments the allocatable pages counter * by the number of pages freed. * * Invoke +malloc_trim+ if available to free empty malloc pages. + * * Eagerly loads the +error_highlight+, +did_you_mean+, and +syntax_suggest+ + * gems, which are otherwise loaded lazily on the first error display. */ static VALUE proc_warmup(VALUE _) { + // Load the error decoration gems now so that their detailed_message + // decorators land in shared memory before a pre-forking server forks, + // instead of being loaded lazily on the first error at runtime. + rb_eager_load_detailed_message_extension(); + RB_VM_LOCKING() { rb_gc_prepare_heap(); } diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index d99e356e69bfd4..2dc999703cfddc 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -2786,6 +2786,18 @@ def test_warmup_frees_pages end; end + def test_warmup_eager_loads_error_decoration_gems + assert_separately(["--enable=gems"], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + features = ["error_highlight", "did_you_mean", "syntax_suggest"] + assert_empty($LOADED_FEATURES.grep(/\/(#{features.join("|")})\.rb\z/)) + Process.warmup + features.each do |feature| + assert_includes($LOADED_FEATURES.map { File.basename(it, ".rb") }, feature) + end + end; + end + def test_concurrent_group_and_pid_wait # Use a pair of pipes that will make long_pid exit when this test exits, to avoid # leaking temp processes. From 67dc1dfbc8cfdfe1fd7c33e29aec95e0d517c444 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 15 Jun 2026 16:11:33 +0900 Subject: [PATCH 07/16] Preserve the caller's $! while loading the error decoration gems require_decoration_gems is called while an exception is being displayed, so $! holds that exception. rb_protect does not preserve errinfo across the call, and the previous code cleared it to nil whenever a require raised, losing the exception being shown. Save errinfo before the requires and restore it afterward so $! is left untouched whether or not a require fails. --- error.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/error.c b/error.c index 1dafd6b214580e..273cab54b2dde2 100644 --- a/error.c +++ b/error.c @@ -1780,14 +1780,20 @@ load_decoration_gem(VALUE feature) static void require_decoration_gems(void) { + // Loading must not disturb the caller's $!: it is called while + // displaying an exception. rb_protect does not preserve errinfo, so + // save and restore it around the requires, leaving $! untouched + // whether or not a require raises. + VALUE saved_errinfo = rb_errinfo(); static const char *const gems[] = {"ErrorHighlight", "DidYouMean", "SyntaxSuggest"}; for (size_t i = 0; i < numberof(gems); i++) { VALUE feature = rb_autoload_p(rb_cObject, rb_intern(gems[i])); if (NIL_P(feature)) continue; int state; rb_protect(load_decoration_gem, feature, &state); - if (state) rb_set_errinfo(Qnil); + (void)state; } + rb_set_errinfo(saved_errinfo); } // Load the decoration gems on the first error display instead of at boot. From b9d8a39155d324bec23f4fb5a035dd7a33477f30 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 15 Jun 2026 16:11:49 +0900 Subject: [PATCH 08/16] Fix a stale comment in rb_define_gem_modules The comment still described loading via a gem_prelude.rb Exception#detailed_message hook, which no longer exists. error.c now loads the gems on the first error, or eagerly via Process.warmup. --- builtin.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin.c b/builtin.c index 451a278942bcda..094bd18c222168 100644 --- a/builtin.c +++ b/builtin.c @@ -94,8 +94,8 @@ rb_define_gem_modules(VALUE flags_value, VALUE _) if (flags->gem) { rb_define_module("Gem"); // Make the error decoration gems autoload on first constant access. - // gem_prelude.rb loads them for error display via an - // Exception#detailed_message hook. + // error.c loads them for error display on the first error + // (or eagerly via Process.warmup). if (flags->error_highlight) { rb_autoload_str(rb_cObject, rb_intern("ErrorHighlight"), rb_fstring_cstr("error_highlight")); } From 22258b1d5fa2a386a98054dffe7b50448673da8a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 15 Jul 2026 14:40:48 +0900 Subject: [PATCH 09/16] Load the error decoration gems from non-main Ractors too rb_require_string delegates requires from non-main Ractors to the main Ractor via rb_ractor_require, so the main-Ractor guard here was unnecessary. Drop it and claim the load with an atomic exchange, because Ractors run in parallel and the plain bool flag was only safe under that guard. did_you_mean already keeps its state Ractor-safe, so suggestions now appear in non-main Ractors as well. Suggested by John Hawthorn. --- depend | 2 -- error.c | 12 ++++++------ test/ruby/test_ractor.rb | 15 ++++++++------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/depend b/depend index 8667ccdb1d4ce4..70a18b4c9a35d1 100644 --- a/depend +++ b/depend @@ -5418,8 +5418,6 @@ error.$(OBJEXT): {$(VPATH)}missing.h error.$(OBJEXT): {$(VPATH)}node.h error.$(OBJEXT): {$(VPATH)}onigmo.h error.$(OBJEXT): {$(VPATH)}oniguruma.h -error.$(OBJEXT): {$(VPATH)}ractor.h -error.$(OBJEXT): {$(VPATH)}ractor_core.h error.$(OBJEXT): {$(VPATH)}ruby_assert.h error.$(OBJEXT): {$(VPATH)}ruby_atomic.h error.$(OBJEXT): {$(VPATH)}rubyparser.h diff --git a/error.c b/error.c index 273cab54b2dde2..4ff96115f727ee 100644 --- a/error.c +++ b/error.c @@ -50,7 +50,6 @@ #include "internal/vm.h" #include "ruby_assert.h" #include "vm_core.h" -#include "ractor_core.h" #include "yjit.h" #include "zjit.h" @@ -1764,7 +1763,8 @@ exc_message(VALUE exc) // Whether error_highlight, did_you_mean, and syntax_suggest have already // been loaded (lazily on the first error, or eagerly via Process.warmup). -static bool decoration_gems_loaded = false; +// Ractors run in parallel, so the load is claimed by an atomic exchange. +static rb_atomic_t decoration_gems_loaded = 0; static VALUE load_decoration_gem(VALUE feature) @@ -1797,13 +1797,14 @@ require_decoration_gems(void) } // Load the decoration gems on the first error display instead of at boot. +// In non-main Ractors rb_require_string delegates the require to the main +// Ractor, so this works from any Ractor. // Returns whether the caller should re-dispatch to pick up the // detailed_message decorators the gems prepend. static bool lazy_load_decoration_gems(VALUE exc) { - if (decoration_gems_loaded || !rb_ractor_main_p()) return false; - decoration_gems_loaded = true; + if (ATOMIC_EXCHANGE(decoration_gems_loaded, 1)) return false; // When entered through super from a decorator already sitting above // this method, the caller decorates the result; re-dispatching would @@ -1820,8 +1821,7 @@ lazy_load_decoration_gems(VALUE exc) void rb_eager_load_detailed_message_extension(void) { - if (decoration_gems_loaded || !rb_ractor_main_p()) return; - decoration_gems_loaded = true; + if (ATOMIC_EXCHANGE(decoration_gems_loaded, 1)) return; require_decoration_gems(); } diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index bb33355a139daf..2b844f4ceff8ac 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -402,19 +402,20 @@ def test_ractor_does_not_inherit_fiber_storage end def test_detailed_message_in_ractor - # gem_prelude defers loading error_highlight, did_you_mean, and - # syntax_suggest until the first error display, which is not possible - # in non-main Ractors. Exception#detailed_message must still return - # the plain message there. [Feature #21951] + # The error decoration gems (error_highlight, did_you_mean, and + # syntax_suggest) are loaded lazily on the first error display. In a + # non-main Ractor the require is delegated to the main Ractor, so the + # decorations must appear there too. [Feature #21951] assert_ractor(<<~'RUBY', args: ["--enable=gems"], ignore_stderr: true) message = Ractor.new do begin - raise "boom" - rescue => e + 1.timess + rescue NoMethodError => e e.detailed_message(highlight: false) end end.value - assert_include(message, "boom") + assert_include(message, "timess") + assert_include(message, "Did you mean?") RUBY end From d991b06bf88c346533bc201a98d6f760f000526d Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 14 Jul 2026 09:55:14 +0900 Subject: [PATCH 10/16] [ruby/rubygems] Install default bundler executables under the same root as its gemspec `gem update --system` wrote the new default bundler gemspec next to the previous one, but extracted its executables under `Gem.default_dir`. On rubies where `Gem.default_specifications_dir` lives under a different root than `Gem.default_dir`, like Homebrew ruby, activating the default bundler then computes an executable path that does not exist, breaking the `bundle` binstub. Extract the executables and remove the previous default gem under the same root as the gemspec instead. https://github.com/ruby/rubygems/issues/9685 https://github.com/ruby/rubygems/commit/04d53913e4 Co-Authored-By: Claude Fable 5 --- lib/rubygems/commands/setup_command.rb | 22 +++++++++++++++---- .../test_gem_commands_setup_command.rb | 18 +++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index ad4aaab384a0c6..07eb7893a84aa6 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -355,16 +355,30 @@ def install_default_bundler_gem(bin_dir) loaded_from = current_default_spec.loaded_from File.delete(loaded_from) - # Remove previous default gem executables if they were not shadowed by a regular gem - FileUtils.rm_rf current_default_spec.full_gem_path if all_specs_current_version.size == 1 + previous_specs_dir = File.dirname(loaded_from) + + # Remove previous default gem executables if they were not shadowed by a + # regular gem. They live under the same root as the previous default + # gemspec, which is not necessarily default_dir. + if all_specs_current_version.size == 1 + previous_root = File.dirname(File.dirname(previous_specs_dir)) + FileUtils.rm_rf File.join(previous_root, "gems", current_default_spec.full_name) + end - File.dirname(loaded_from) + previous_specs_dir else target_specs_dir = File.join(default_dir, "specifications", "default") mkdir_p target_specs_dir, mode: 0o755 target_specs_dir end + # Root directory that specs_dir belongs to. It may differ from default_dir + # when Gem.default_specifications_dir is customized to live under a + # different root, like Homebrew does. Executables must be extracted under + # the same root as the default gemspec, since that's where activation of + # the default gem will look for them. + bundler_install_dir = File.dirname(File.dirname(specs_dir)) + new_bundler_spec = Gem::Specification.load("bundler.gemspec") full_name = new_bundler_spec.full_name gemspec_path = "#{full_name}.gemspec" @@ -397,7 +411,7 @@ def install_default_bundler_gem(bin_dir) format_executable: options[:format_executable], force: options[:force], bin_dir: bin_dir, - install_dir: default_dir, + install_dir: bundler_install_dir, wrappers: true ) # We need to install only executable and default spec files. diff --git a/test/rubygems/test_gem_commands_setup_command.rb b/test/rubygems/test_gem_commands_setup_command.rb index 7df59ecfc6dbbb..b29a9f59227c3a 100644 --- a/test/rubygems/test_gem_commands_setup_command.rb +++ b/test/rubygems/test_gem_commands_setup_command.rb @@ -241,17 +241,35 @@ def test_install_default_bundler_gem def test_install_default_bundler_gem_with_default_gems_not_installed_at_default_dir @cmd.extend FileUtils + # Simulate Homebrew's layout, where Gem.default_dir is moved to a + # different root, while Gem.default_specifications_dir stays inside the + # ruby install tree. gemhome2 = File.join(@tempdir, "gemhome2") Gem.instance_variable_set(:@default_dir, gemhome2) FileUtils.mkdir_p gemhome2 bin_dir = File.join(gemhome2, "bin") + previous_default_gem_dir = File.join(@gemhome, "gems", "bundler-1.15.4") + write_file File.join(previous_default_gem_dir, "exe", "bundle") + @cmd.install_default_bundler_gem bin_dir # expect to remove other versions of bundler gemspecs on default specification directory. assert_path_not_exist previous_bundler_specification_path assert_path_exist new_bundler_specification_path + + # expect to remove the previous default version executables, which live + # next to the removed gemspec, not under the new default dir. + assert_path_not_exist previous_default_gem_dir + + # expect executables to be installed under the same root as the default + # gemspec, since that's where activation of the default gem looks for them. + spec = Gem::Specification.load(new_bundler_specification_path) + spec.executables.each do |e| + assert_path_exist File.join(@gemhome, "gems", spec.full_name, spec.bindir, e) + end + assert_path_not_exist File.join(gemhome2, "gems", spec.full_name) end def test_install_default_bundler_gem_with_force_flag From 2b3a21a659fce19cb9a2adffff456efd7e508236 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 14 Jul 2026 14:09:52 +0900 Subject: [PATCH 11/16] [ruby/rubygems] Stop writing the default bundler spec twice The default gemspec is already written to the default specifications directory before building the gem, so `installer.write_default_spec` just wrote the same file a second time. Drop the redundant call and let the installer only extract and generate the executables. https://github.com/ruby/rubygems/commit/c69d5ce7c4 --- lib/rubygems/commands/setup_command.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index 07eb7893a84aa6..20a484e3bfd947 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -414,11 +414,11 @@ def install_default_bundler_gem(bin_dir) install_dir: bundler_install_dir, wrappers: true ) - # We need to install only executable and default spec files. - # lib/bundler.rb and lib/bundler/* are available under the site_ruby directory. + # We only need to install the executables here. The default spec was + # already written above, and lib/bundler.rb and lib/bundler/* are + # available under the site_ruby directory. installer.extract_bin installer.generate_bin - installer.write_default_spec ensure FileUtils.rm_f built_gem end From f7bc1aaaafe9c24ecf0d077f5822d5acf1362199 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 14 Jul 2026 14:11:50 +0900 Subject: [PATCH 12/16] [ruby/rubygems] Remove the promoted regular bundler gem from the default dir When a regular gem of the vendored bundler version was already installed, its files were removed from the default gemspec's gems directory instead of from default_dir. On rubies where those roots differ, like Homebrew ruby, the regular gem files were left behind after being promoted to the default gem. Remove them from default_dir, where regular gems actually live. https://github.com/ruby/rubygems/commit/721bcb9691 --- lib/rubygems/commands/setup_command.rb | 15 ++++++++------- test/rubygems/test_gem_commands_setup_command.rb | 9 +++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index 20a484e3bfd947..e6c3c7d0de2a81 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -386,19 +386,20 @@ def install_default_bundler_gem(bin_dir) default_spec_path = File.join(specs_dir, gemspec_path) Gem.write_binary(default_spec_path, new_bundler_spec.to_ruby) - bundler_spec = Gem::Specification.load(default_spec_path) - # Remove gemspec that was same version of vendored bundler. normal_gemspec = File.join(default_dir, "specifications", gemspec_path) if File.file? normal_gemspec File.delete normal_gemspec end - # Remove gem files that were same version of vendored bundler. - if File.directory? bundler_spec.gems_dir - Dir.entries(bundler_spec.gems_dir). - select {|default_gem| File.basename(default_gem) == full_name }. - each {|default_gem| rm_r File.join(bundler_spec.gems_dir, default_gem) } + # Remove gem files that were same version of vendored bundler. A regular + # gem lives under default_dir, which is not necessarily the same root as + # the default gemspec. + normal_gems_dir = File.join(default_dir, "gems") + if File.directory? normal_gems_dir + Dir.entries(normal_gems_dir). + select {|normal_gem| File.basename(normal_gem) == full_name }. + each {|normal_gem| rm_r File.join(normal_gems_dir, normal_gem) } end require_relative "../installer" diff --git a/test/rubygems/test_gem_commands_setup_command.rb b/test/rubygems/test_gem_commands_setup_command.rb index b29a9f59227c3a..ba410d64e635cd 100644 --- a/test/rubygems/test_gem_commands_setup_command.rb +++ b/test/rubygems/test_gem_commands_setup_command.rb @@ -253,6 +253,12 @@ def test_install_default_bundler_gem_with_default_gems_not_installed_at_default_ previous_default_gem_dir = File.join(@gemhome, "gems", "bundler-1.15.4") write_file File.join(previous_default_gem_dir, "exe", "bundle") + # A regular gem of the same version installed at the default dir, which is + # promoted to the default gem and must be removed. Its files live under + # default_dir, not under the default gemspec root. + normal_gem_dir = File.join(gemhome2, "gems", "bundler-#{bundler_version}") + write_file File.join(normal_gem_dir, "lib", "bundler.rb") + @cmd.install_default_bundler_gem bin_dir # expect to remove other versions of bundler gemspecs on default specification directory. @@ -263,6 +269,9 @@ def test_install_default_bundler_gem_with_default_gems_not_installed_at_default_ # next to the removed gemspec, not under the new default dir. assert_path_not_exist previous_default_gem_dir + # expect to remove the promoted regular gem's files under default_dir. + assert_path_not_exist normal_gem_dir + # expect executables to be installed under the same root as the default # gemspec, since that's where activation of the default gem looks for them. spec = Gem::Specification.load(new_bundler_specification_path) From c6c8ec8a30b8178a61a559cb39e66066fb3f5377 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 10 Jul 2026 08:22:35 +0900 Subject: [PATCH 13/16] [ruby/rubygems] Open compact index cache in binary mode when appending CacheFile#append opened the temp file with mode "a", which on Windows is a text-mode stream that rewrites every "\n" as "\r\n". The corruption goes undetected because DigestIO hashes the logical bytes handed to write, not the bytes the OS actually lands on disk, so verify passes and the mangled file is committed. The damage compounds on the next update: the extra carriage returns shift file.size, so the ranged request restarts at the wrong offset and the versions index no longer matches its expected digest, forcing a full re-download every time. The info files keep failing their MD5 check against the raw index bytes, so they are refetched on every resolve. Open the append stream with "ab" so the appended range is written verbatim, matching the binary "wb" used for full writes. https://github.com/ruby/rubygems/commit/9568d9600c --- .../compact_index_client/cache_file.rb | 2 +- .../compact_index_client/cache_file.rb | 2 +- .../compact_index_client/cache_file_spec.rb | 29 +++++++++++++++++++ ...est_gem_compact_index_client_cache_file.rb | 17 +++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 spec/bundler/bundler/compact_index_client/cache_file_spec.rb diff --git a/lib/bundler/compact_index_client/cache_file.rb b/lib/bundler/compact_index_client/cache_file.rb index 299d683438b887..03659aeac19e0a 100644 --- a/lib/bundler/compact_index_client/cache_file.rb +++ b/lib/bundler/compact_index_client/cache_file.rb @@ -103,7 +103,7 @@ def open(write_mode = "wb", perm = @perm, &block) # Returns false without appending when no digests since appending is too error prone to do without digests. def append(data) return false unless digests? - open("a") {|f| f.write data } + open("ab") {|f| f.write data } verify && commit end diff --git a/lib/rubygems/compact_index_client/cache_file.rb b/lib/rubygems/compact_index_client/cache_file.rb index 3656645d822089..00e5f541ddf6f0 100644 --- a/lib/rubygems/compact_index_client/cache_file.rb +++ b/lib/rubygems/compact_index_client/cache_file.rb @@ -99,7 +99,7 @@ def open(write_mode = "wb", perm = @perm, &block) # Returns false without appending when no digests since appending is too error prone to do without digests. def append(data) return false unless digests? - open("a") {|f| f.write data } + open("ab") {|f| f.write data } verify && commit end diff --git a/spec/bundler/bundler/compact_index_client/cache_file_spec.rb b/spec/bundler/bundler/compact_index_client/cache_file_spec.rb new file mode 100644 index 00000000000000..c106b2cc52ad5a --- /dev/null +++ b/spec/bundler/bundler/compact_index_client/cache_file_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "bundler/compact_index_client" +require "bundler/compact_index_client/cache_file" + +RSpec.describe Bundler::CompactIndexClient::CacheFile do + let(:path) { Pathname.new(Dir.mktmpdir("localpath")).join("versions") } + + def sha256(data) + { "sha-256" => Digest::SHA256.base64digest(data) } + end + + it "appends in binary mode so line endings are preserved" do + path.binwrite "created_at: 2026-06-10\n---\n" + + appended = nil + described_class.copy(path) do |file| + file.digests = sha256("created_at: 2026-06-10\n---\nrake 13.0.0\n") + appended = file.append("rake 13.0.0\n") + end + + expect(appended).to be_truthy + # On Windows a text-mode append rewrites the appended LF as CRLF while the + # digest is computed over the pre-write bytes, so verify passes but the file + # on disk is corrupted. Read raw bytes to catch any stray carriage return. + expect(path.binread).not_to include("\r") + expect(path.binread).to eq("created_at: 2026-06-10\n---\nrake 13.0.0\n") + end +end diff --git a/test/rubygems/test_gem_compact_index_client_cache_file.rb b/test/rubygems/test_gem_compact_index_client_cache_file.rb index 193ae327e9c24b..f2e2633646b4a0 100644 --- a/test/rubygems/test_gem_compact_index_client_cache_file.rb +++ b/test/rubygems/test_gem_compact_index_client_cache_file.rb @@ -82,6 +82,23 @@ def test_append_with_matching_digests assert_equal "abcdef", @path.read end + def test_append_writes_in_binary_mode + @path.binwrite "created_at: 2026-06-10\n---\n" + + appended = nil + CacheFile.copy(@path) do |file| + file.digests = sha256("created_at: 2026-06-10\n---\nrake 13.0.0\n") + appended = file.append("rake 13.0.0\n") + end + + assert appended + # On Windows a text-mode append rewrites the appended LF as CRLF while the + # digest is computed over the pre-write bytes, so verify passes but the file + # on disk is corrupted. Read raw bytes to catch any stray carriage return. + refute_includes @path.binread, "\r" + assert_equal "created_at: 2026-06-10\n---\nrake 13.0.0\n", @path.binread + end + def test_append_with_mismatched_digests_keeps_original @path.binwrite "abc" From 21892a1dd73c1766a254f3b365148e63f6cbcbc7 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 15 Jul 2026 16:41:07 +0900 Subject: [PATCH 14/16] [ruby/rubygems] Require tmpdir in compact index cache_file spec The spec calls Dir.mktmpdir in a let block, but neither bundler nor the spec_helper loads tmpdir, so the example raised NoMethodError before running. The sibling updater_spec already requires it explicitly. https://github.com/ruby/rubygems/commit/f9eb1c1b22 --- spec/bundler/bundler/compact_index_client/cache_file_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/bundler/bundler/compact_index_client/cache_file_spec.rb b/spec/bundler/bundler/compact_index_client/cache_file_spec.rb index c106b2cc52ad5a..c9a87c3c2d447a 100644 --- a/spec/bundler/bundler/compact_index_client/cache_file_spec.rb +++ b/spec/bundler/bundler/compact_index_client/cache_file_spec.rb @@ -2,6 +2,7 @@ require "bundler/compact_index_client" require "bundler/compact_index_client/cache_file" +require "tmpdir" RSpec.describe Bundler::CompactIndexClient::CacheFile do let(:path) { Pathname.new(Dir.mktmpdir("localpath")).join("versions") } From 911be9a2c82f80b95b6cc3b98f5f93a3c20cc725 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 15 Jul 2026 19:39:15 +0900 Subject: [PATCH 15/16] [ruby/rubygems] Assign compact index cache_file spec to a shard CI runs bundler specs with `--tag shard_`, so a spec file that is not listed in spec/support/shards.rb runs in no shard at all. Register the new cache_file_spec.rb under shard_d as the file header instructs. https://github.com/ruby/rubygems/commit/537479a45a --- spec/bundler/support/shards.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/bundler/support/shards.rb b/spec/bundler/support/shards.rb index 396ee8049d6b66..9fd67f921b4ff7 100644 --- a/spec/bundler/support/shards.rb +++ b/spec/bundler/support/shards.rb @@ -147,6 +147,7 @@ module Shards "spec/bundler/ci_detector_spec.rb", ], shard_d: [ + "spec/bundler/compact_index_client/cache_file_spec.rb", "spec/bundler/rubygems_ext_spec.rb", "spec/bundler/resolver/cooldown_spec.rb", "spec/install/cooldown_spec.rb", From 40f105bd558f694ad3473d22442466ed168ec9f2 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 16 Jul 2026 18:54:25 +0200 Subject: [PATCH 16/16] Change RStruct to match SHAPE_ID_LAYOUT_EXTENDED THe downside is that we now always reserve one 8B reference for the IMEMO/fields, even for structs that never have any ivars. But it is an acceptable tradeoff given that ivars on structs aren't rare. Co-Authored-By: John Hawthorn --- gc.c | 18 +++--------------- internal/range.h | 8 +++++++- internal/struct.h | 28 +++------------------------- range.c | 15 ++++++++++----- shape.c | 5 +++++ shape.h | 2 +- struct.c | 32 ++++++++++---------------------- test/ruby/test_shapes.rb | 4 ++-- variable.c | 28 +++++++++------------------- yjit/src/cruby.rs | 4 ++-- zjit/src/cruby.rs | 4 ++-- zjit/src/hir/opt_tests.rs | 4 ++-- 12 files changed, 56 insertions(+), 96 deletions(-) diff --git a/gc.c b/gc.c index e03d1362b84eb2..4980c1b8859709 100644 --- a/gc.c +++ b/gc.c @@ -3135,13 +3135,10 @@ static inline bool rb_obj_using_gen_fields_table_p(VALUE obj) { switch (BUILTIN_TYPE(obj)) { + case T_STRUCT: case T_DATA: return false; - case T_STRUCT: - if (!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS)) return false; - break; - default: break; } @@ -3335,9 +3332,7 @@ rb_gc_mark_children(void *objspace, VALUE obj) gc_mark_internal(ptr[i]); } - if (rb_obj_shape_has_fields(obj) && !FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS)) { - gc_mark_internal(RSTRUCT_FIELDS_OBJ(obj)); - } + gc_mark_internal(RSTRUCT_FIELDS_OBJ(obj)); break; } @@ -4287,14 +4282,7 @@ rb_gc_update_object_references(void *objspace, VALUE obj) UPDATE_IF_MOVED(objspace, ptr[i]); } - if (RSTRUCT_EMBED_LEN(obj)) { - if (!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS)) { - UPDATE_IF_MOVED(objspace, ptr[len]); - } - } - else { - UPDATE_IF_MOVED(objspace, RSTRUCT(obj)->as.heap.fields_obj); - } + UPDATE_IF_MOVED(objspace, RSTRUCT(obj)->fields_obj); } break; default: diff --git a/internal/range.h b/internal/range.h index 80493ce13e4008..2e6d0cdac5b0b6 100644 --- a/internal/range.h +++ b/internal/range.h @@ -10,6 +10,9 @@ */ #include "internal/struct.h" /* for RSTRUCT */ +#define RANGE_FL_INIT FL_USER18 +#define RANGE_FL_EXCL FL_USER19 + /* range.c */ static inline VALUE RANGE_BEG(VALUE r); static inline VALUE RANGE_END(VALUE r); @@ -30,7 +33,10 @@ RANGE_END(VALUE r) static inline VALUE RANGE_EXCL(VALUE r) { - return RSTRUCT_GET_RAW(r, 2); + if (FL_TEST_RAW(r, RANGE_FL_INIT)) { + return RBOOL(FL_TEST_RAW(r, RANGE_FL_EXCL)); + } + return Qnil; } VALUE diff --git a/internal/struct.h b/internal/struct.h index d3c81573931cfa..ffe1786a896bac 100644 --- a/internal/struct.h +++ b/internal/struct.h @@ -17,26 +17,20 @@ * If non-zero, the struct is embedded (its contents follow the * header, rather than being on a separately allocated buffer) and * these bits are the length of the Struct. - * 8: RSTRUCT_GEN_FIELDS - * The struct is embedded and has no space left to store the - * IMEMO/fields reference. Any ivar this struct may have will be in - * the generic_fields_tbl. This flag doesn't imply the struct has - * ivars. */ enum { RSTRUCT_EMBED_LEN_MASK = RUBY_FL_USER7 | RUBY_FL_USER6 | RUBY_FL_USER5 | RUBY_FL_USER4 | RUBY_FL_USER3 | RUBY_FL_USER2 | RUBY_FL_USER1, RSTRUCT_EMBED_LEN_SHIFT = (RUBY_FL_USHIFT+1), - RSTRUCT_GEN_FIELDS = RUBY_FL_USER8, }; struct RStruct { struct RBasic basic; + VALUE fields_obj; union { struct { long len; const VALUE *ptr; - VALUE fields_obj; } heap; /* This is a length 1 array because: * 1. GCC has a bug that does not optimize C flexible array members @@ -113,28 +107,12 @@ RSTRUCT_GET_RAW(VALUE st, long k) static inline VALUE RSTRUCT_FIELDS_OBJ(VALUE st) { - const long embed_len = RSTRUCT_EMBED_LEN(st); - VALUE fields_obj; - if (embed_len) { - RUBY_ASSERT(!FL_TEST_RAW(st, RSTRUCT_GEN_FIELDS)); - fields_obj = RSTRUCT_GET_RAW(st, embed_len); - } - else { - fields_obj = RSTRUCT(st)->as.heap.fields_obj; - } - return fields_obj; + return RSTRUCT(st)->fields_obj; } static inline void RSTRUCT_SET_FIELDS_OBJ(VALUE st, VALUE fields_obj) { - const long embed_len = RSTRUCT_EMBED_LEN(st); - if (embed_len) { - RUBY_ASSERT(!FL_TEST_RAW(st, RSTRUCT_GEN_FIELDS)); - RSTRUCT_SET_RAW(st, embed_len, fields_obj); - } - else { - RB_OBJ_WRITE(st, &RSTRUCT(st)->as.heap.fields_obj, fields_obj); - } + RB_OBJ_WRITE(st, &RSTRUCT(st)->fields_obj, fields_obj); } #endif /* INTERNAL_STRUCT_H */ diff --git a/range.c b/range.c index fba73f4a39d1c3..e751c282c32964 100644 --- a/range.c +++ b/range.c @@ -40,9 +40,8 @@ static VALUE r_cover_p(VALUE, VALUE, VALUE, VALUE); #define RANGE_SET_BEG(r, v) (RSTRUCT_SET(r, 0, v)) #define RANGE_SET_END(r, v) (RSTRUCT_SET(r, 1, v)) -#define RANGE_SET_EXCL(r, v) (RSTRUCT_SET(r, 2, v)) -#define EXCL(r) RTEST(RANGE_EXCL(r)) +#define EXCL(r) RTEST(FL_TEST(r, RANGE_FL_EXCL)) static void range_init(VALUE range, VALUE beg, VALUE end, VALUE exclude_end) @@ -56,7 +55,12 @@ range_init(VALUE range, VALUE beg, VALUE end, VALUE exclude_end) rb_raise(rb_eArgError, "bad value for range"); } - RANGE_SET_EXCL(range, exclude_end); + if (RTEST(exclude_end)) { + FL_SET_RAW(range, RANGE_FL_EXCL); + } + + FL_SET_RAW(range, RANGE_FL_INIT); + RANGE_SET_BEG(range, beg); RANGE_SET_END(range, end); @@ -79,7 +83,7 @@ range_modify(VALUE range) { rb_check_frozen(range); /* Ranges are immutable, so that they should be initialized only once. */ - if (RANGE_EXCL(range) != Qnil) { + if (FL_TEST(range, RANGE_FL_INIT)) { rb_name_err_raise("'initialize' called twice", range, ID2SYM(idInitialize)); } } @@ -115,6 +119,7 @@ static VALUE range_initialize_copy(VALUE range, VALUE orig) { range_modify(range); + FL_SET_RAW(range, FL_TEST_RAW(orig, RANGE_FL_EXCL|RANGE_FL_INIT)); rb_struct_init_copy(range, orig); return range; } @@ -2960,7 +2965,7 @@ Init_Range(void) rb_cRange = rb_struct_define_without_accessor( "Range", rb_cObject, range_alloc, - "begin", "end", "excl", NULL); + "begin", "end", NULL); rb_include_module(rb_cRange, rb_mEnumerable); rb_marshal_define_compat(rb_cRange, rb_cObject, range_dumper, range_loader); diff --git a/shape.c b/shape.c index 5c59d2aee22aca..7815591d2d8877 100644 --- a/shape.c +++ b/shape.c @@ -1219,19 +1219,24 @@ rb_shape_expected_layout(VALUE obj) case T_OBJECT: { return SHAPE_ID_LAYOUT_ROBJECT; } + case T_CLASS: case T_MODULE: if (FL_TEST_RAW(obj, RCLASS_BOXABLE)) { return SHAPE_ID_LAYOUT_OTHER; } return SHAPE_ID_LAYOUT_RCLASS; + + case T_STRUCT: case T_DATA: return SHAPE_ID_LAYOUT_EXTENDED; + case T_IMEMO: if (IMEMO_TYPE_P(obj, imemo_fields)) { return SHAPE_ID_LAYOUT_ROBJECT; } return SHAPE_ID_LAYOUT_OTHER; + default: return SHAPE_ID_LAYOUT_OTHER; } diff --git a/shape.h b/shape.h index 5a7dc76c4d12cd..14b9a00bcc8ea5 100644 --- a/shape.h +++ b/shape.h @@ -107,7 +107,7 @@ typedef uint32_t redblack_id_t; enum shape_type { SHAPE_ROOT, SHAPE_IVAR, - SHAPE_OBJ_ID, + SHAPE_OBJ_ID }; struct rb_shape { diff --git a/struct.c b/struct.c index 32da26860b993a..0c6d7330dfc590 100644 --- a/struct.c +++ b/struct.c @@ -818,6 +818,8 @@ struct_heap_alloc(VALUE st, size_t len) return ALLOC_N(VALUE, len); } +STATIC_ASSERT(robject_rstruct_fields_offset, offsetof(struct RObject, as.extended) == offsetof(struct RStruct, fields_obj)); + static VALUE struct_alloc(VALUE klass) { @@ -833,40 +835,26 @@ struct_alloc(VALUE klass) if (n > 0 && n <= embed_len_max && rb_gc_size_allocatable_p(embedded_size)) { flags |= n << RSTRUCT_EMBED_LEN_SHIFT; - if (RCLASS_MAX_IV_COUNT(klass) == 0) { - // We set the flag before calling `NEWOBJ_OF` in case a NEWOBJ tracepoint does - // attempt to write fields. We'll remove it later if no fields was written to. - flags |= RSTRUCT_GEN_FIELDS; - } - - NEWOBJ_OF(st, struct RStruct, klass, flags, embedded_size); - if (RCLASS_MAX_IV_COUNT(klass) == 0) { - if (!rb_obj_shape_has_fields((VALUE)st) - && embedded_size < rb_obj_shape_slot_size((VALUE)st)) { - FL_UNSET_RAW((VALUE)st, RSTRUCT_GEN_FIELDS); - RSTRUCT_SET_FIELDS_OBJ((VALUE)st, 0); - } - } - else { - RSTRUCT_SET_FIELDS_OBJ((VALUE)st, 0); - } - rb_mem_clear((VALUE *)st->as.ary, n); + VALUE st = rb_newobj(GET_EC(), klass, flags, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_EXTENDED, true, embedded_size); + RSTRUCT_SET_FIELDS_OBJ(st, 0); + rb_mem_clear((VALUE *)RSTRUCT(st)->as.ary, n); - return (VALUE)st; + return st; } else { - NEWOBJ_OF(st, struct RStruct, klass, flags, sizeof(struct RStruct)); + VALUE obj = rb_newobj(GET_EC(), klass, flags, ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_EXTENDED, true, sizeof(struct RStruct)); + struct RStruct *st = RSTRUCT(obj); + st->fields_obj = 0; st->as.heap.ptr = NULL; - st->as.heap.fields_obj = 0; st->as.heap.len = 0; st->as.heap.ptr = struct_heap_alloc((VALUE)st, n); rb_mem_clear((VALUE *)st->as.heap.ptr, n); st->as.heap.len = n; - return (VALUE)st; + return obj; } } diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb index 8dc52eee54bf04..9d7b4a34663bfa 100644 --- a/test/ruby/test_shapes.rb +++ b/test/ruby/test_shapes.rb @@ -1108,12 +1108,12 @@ def test_shape_layout assert_equal :extended_or_rdata, RubyVM::Shape.of(Thread.current).layout assert_equal :extended_or_rdata, RubyVM::Shape.of(lambda {}).layout + assert_equal :extended_or_rdata, RubyVM::Shape.of(Struct.new(:x).new(1)).layout + assert_equal :extended_or_rdata, RubyVM::Shape.of(2..3).layout - assert_equal :other, RubyVM::Shape.of(Struct.new(:x).new(1)).layout assert_equal :other, RubyVM::Shape.of([]).layout assert_equal :other, RubyVM::Shape.of("hello").layout assert_equal :other, RubyVM::Shape.of(/foo/).layout - assert_equal :other, RubyVM::Shape.of(2..3).layout assert_equal :other, RubyVM::Shape.of(2**67).layout assert_equal :other, RubyVM::Shape.of(:"aaroniscool#{123}").layout end diff --git a/variable.c b/variable.c index 2f4e4a387faa3a..e0b4d4a45db2a7 100644 --- a/variable.c +++ b/variable.c @@ -1281,10 +1281,9 @@ obj_use_generic_fields_tbl_p(VALUE obj) case T_OBJECT: case T_CLASS: case T_MODULE: + case T_STRUCT: case T_DATA: return false; - case T_STRUCT: - return !FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS); default: return true; } @@ -1311,12 +1310,9 @@ rb_obj_fields(VALUE obj, ID field_name) return RTYPEDDATA(obj)->fields_obj; case T_STRUCT: - if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) { - return RSTRUCT_FIELDS_OBJ(obj); - } - goto generic_fields; + return RSTRUCT_FIELDS_OBJ(obj); + default: - generic_fields: { VALUE fields_obj = 0; @@ -1348,13 +1344,10 @@ rb_free_generic_ivar(VALUE obj) RB_OBJ_WRITE(obj, &RTYPEDDATA(obj)->fields_obj, 0); break; case T_STRUCT: - if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) { - RSTRUCT_SET_FIELDS_OBJ(obj, 0); - break; - } - goto generic_fields; + RSTRUCT_SET_FIELDS_OBJ(obj, 0); + break; + default: - generic_fields: { // Other EC may have stale caches, so fields_obj should be // invalidated and the GC will replace with Qundef @@ -1400,13 +1393,10 @@ rb_obj_set_fields(VALUE obj, VALUE fields_obj, ID field_name, VALUE original_fie RB_OBJ_WRITE(obj, &RTYPEDDATA(obj)->fields_obj, fields_obj); break; case T_STRUCT: - if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) { - RSTRUCT_SET_FIELDS_OBJ(obj, fields_obj); - break; - } - goto generic_fields; + RSTRUCT_SET_FIELDS_OBJ(obj, fields_obj); + break; + default: - generic_fields: { RB_VM_LOCKING() { st_insert(generic_fields_tbl_, (st_data_t)obj, (st_data_t)fields_obj); diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index a5514115f11ad8..7e945f5db7643f 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -761,8 +761,8 @@ mod manual_defs { pub const RUBY_OFFSET_RARRAY_AS_HEAP_PTR: i32 = 32; // struct RArray, subfield "as.heap.ptr" pub const RUBY_OFFSET_RARRAY_AS_ARY: i32 = 16; // struct RArray, subfield "as.ary" - pub const RUBY_OFFSET_RSTRUCT_AS_HEAP_PTR: i32 = 24; // struct RStruct, subfield "as.heap.ptr" - pub const RUBY_OFFSET_RSTRUCT_AS_ARY: i32 = 16; // struct RStruct, subfield "as.ary" + pub const RUBY_OFFSET_RSTRUCT_AS_HEAP_PTR: i32 = 32; // struct RStruct, subfield "as.heap.ptr" + pub const RUBY_OFFSET_RSTRUCT_AS_ARY: i32 = 24; // struct RStruct, subfield "as.ary" pub const RUBY_OFFSET_RSTRING_AS_HEAP_PTR: i32 = 24; // struct RString, subfield "as.heap.ptr" pub const RUBY_OFFSET_RSTRING_AS_ARY: i32 = 24; // struct RString, subfield "as.embed.ary" diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index ec897ac538f15f..c0c242fde8ecd5 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -1212,8 +1212,8 @@ mod manual_defs { pub const RUBY_OFFSET_RARRAY_AS_HEAP_PTR: i32 = 32; // struct RArray, subfield "as.heap.ptr" pub const RUBY_OFFSET_RARRAY_AS_ARY: i32 = 16; // struct RArray, subfield "as.ary" - pub const RUBY_OFFSET_RSTRUCT_AS_HEAP_PTR: i32 = 24; // struct RStruct, subfield "as.heap.ptr" - pub const RUBY_OFFSET_RSTRUCT_AS_ARY: i32 = 16; // struct RStruct, subfield "as.ary" + pub const RUBY_OFFSET_RSTRUCT_AS_HEAP_PTR: i32 = 32; // struct RStruct, subfield "as.heap.ptr" + pub const RUBY_OFFSET_RSTRUCT_AS_ARY: i32 = 24; // struct RStruct, subfield "as.ary" pub const RUBY_OFFSET_RSTRING_AS_HEAP_PTR: i32 = 24; // struct RString, subfield "as.heap.ptr" pub const RUBY_OFFSET_RSTRING_AS_ARY: i32 = 24; // struct RString, subfield "as.embed.ary" diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 376080b560951c..f1faa88b9f1215 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -9352,8 +9352,8 @@ mod hir_opt_tests { v11:HeapBasicObject = GuardType v6, HeapBasicObject v12:CShape = LoadField v11, :shape_id@0x1000 v13:CShape[0x1001] = GuardBitEquals v12, CShape(0x1001) recompile - v14:CAttrIndex[0] = Const CAttrIndex(0) - v15:BasicObject = CCall v11, :rb_ivar_get_at_no_ractor_check@0x1002, v14 + v14:RubyValue = LoadField v11, :fields_obj@0x1002 + v15:BasicObject = LoadField v14, :@a@0x1002 CheckInterrupts Return v15 ");