diff --git a/.document b/.document index 2345776518..82ca602bfb 100644 --- a/.document +++ b/.document @@ -22,6 +22,7 @@ marshal.rb numeric.rb nilclass.rb pack.rb +pathname_builtin.rb ractor.rb string.rb symbol.rb diff --git a/.gdbinit b/.gdbinit index a19a9bfc87..f624456d04 100644 --- a/.gdbinit +++ b/.gdbinit @@ -185,8 +185,8 @@ define rp print (struct RBasic *)($arg0) else if ($flags & RUBY_T_MASK) == RUBY_T_DATA - if ((struct RTypedData *)($arg0))->typed_flag == 1 - printf "%sT_DATA%s(%s): ", $color_type, $color_end, ((struct RTypedData *)($arg0))->type->wrap_struct_name + if ((struct RTypedData *)($arg0))->type & 1 + printf "%sT_DATA%s(%s): ", $color_type, $color_end, ((const rb_data_type_t *)(((struct RTypedData *)($arg0))->type & ~1))->wrap_struct_name print (struct RTypedData *)($arg0) else printf "%sT_DATA%s: ", $color_type, $color_end diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index de62eb432a..5fb9ba5f7d 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -34,3 +34,5 @@ d2c5867357ed88eccc28c2b3bd4a46e206e7ff85 # Miss-and-revived commits a0f7de814ae5c299d6ce99bed5fb308a05d50ba0 d4e24021d39e1f80f0055b55d91f8d5f22e15084 +7a56c316418980b8a41fcbdc94067b2bda2ad112 +e90282be7ba1bc8e3119f6e1a2c80356ceb3f80a diff --git a/.github/actions/compilers/entrypoint.sh b/.github/actions/compilers/entrypoint.sh index be10ed9c61..17f749d69e 100755 --- a/.github/actions/compilers/entrypoint.sh +++ b/.github/actions/compilers/entrypoint.sh @@ -74,81 +74,6 @@ btests='' tests='' spec_opts='' -# Launchable -setup_launchable() { - pushd ${srcdir} - # To prevent a slowdown in CI, disable request retries when the Launchable server is unstable. - export LAUNCHABLE_SKIP_TIMEOUT_RETRY=1 - # Launchable creates .launchable file in the current directory, but cannot a file to ${srcdir} directory. - # As a workaround, we set LAUNCHABLE_SESSION_DIR to ${builddir}. - export LAUNCHABLE_SESSION_DIR=${builddir} - local github_ref="${GITHUB_REF//\//_}" - local build_name="${github_ref}"_"${GITHUB_PR_HEAD_SHA}" - btest_report_path='launchable_bootstraptest.json' - test_report_path='launchable_test_all.json' - test_spec_report_path='launchable_test_spec_report' - test_all_session_file='launchable_test_all_session.txt' - btest_session_file='launchable_btest_session.txt' - test_spec_session_file='launchable_test_spec_session.txt' - btests+=--launchable-test-reports="${btest_report_path}" - echo "::group::Setup Launchable" - launchable record build --name "${build_name}" || true - launchable record session \ - --build "${build_name}" \ - --flavor test_task=test \ - --flavor workflow=Compilations \ - --flavor with-gcc="${INPUT_WITH_GCC}" \ - --flavor CFLAGS="${INPUT_CFLAGS}" \ - --flavor CXXFLAGS="${INPUT_CXXFLAGS}" \ - --flavor optflags="${INPUT_OPTFLAGS}" \ - --flavor cppflags="${INPUT_CPPFLAGS}" \ - --test-suite btest \ - > "${builddir}"/${btest_session_file} \ - || true - if [ "$INPUT_CHECK" = "true" ]; then - tests+=--launchable-test-reports="${test_report_path}" - launchable record session \ - --build "${build_name}" \ - --flavor test_task=test-all \ - --flavor workflow=Compilations \ - --flavor with-gcc="${INPUT_WITH_GCC}" \ - --flavor CFLAGS="${INPUT_CFLAGS}" \ - --flavor CXXFLAGS="${INPUT_CXXFLAGS}" \ - --flavor optflags="${INPUT_OPTFLAGS}" \ - --flavor cppflags="${INPUT_CPPFLAGS}" \ - --test-suite test-all \ - > "${builddir}"/${test_all_session_file} \ - || true - mkdir "${builddir}"/"${test_spec_report_path}" - spec_opts+=--launchable-test-reports="${test_spec_report_path}" - launchable record session \ - --build "${build_name}" \ - --flavor test_task=test-spec \ - --flavor workflow=Compilations \ - --flavor with-gcc="${INPUT_WITH_GCC}" \ - --flavor CFLAGS="${INPUT_CFLAGS}" \ - --flavor CXXFLAGS="${INPUT_CXXFLAGS}" \ - --flavor optflags="${INPUT_OPTFLAGS}" \ - --flavor cppflags="${INPUT_CPPFLAGS}" \ - --test-suite test-spec \ - > "${builddir}"/${test_spec_session_file} \ - || true - fi - echo "::endgroup::" - trap launchable_record_test EXIT -} -launchable_record_test() { - pushd "${builddir}" - grouped launchable record tests --session "$(cat "${btest_session_file}")" raw "${btest_report_path}" || true - if [ "$INPUT_CHECK" = "true" ]; then - grouped launchable record tests --session "$(cat "${test_all_session_file}")" raw "${test_report_path}" || true - grouped launchable record tests --session "$(cat "${test_spec_session_file}")" raw "${test_spec_report_path}"/* || true - fi -} -if [ "$LAUNCHABLE_ENABLED" = "true" ]; then - setup_launchable -fi - pushd ${builddir} grouped make showflags diff --git a/.github/actions/launchable/setup/action.yml b/.github/actions/launchable/setup/action.yml index 8ea8f61414..3a939452a3 100644 --- a/.github/actions/launchable/setup/action.yml +++ b/.github/actions/launchable/setup/action.yml @@ -55,6 +55,17 @@ inputs: description: >- Whether this workflow is executed on YJIT. +outputs: + stdout_report_path: + value: ${{ steps.global.outputs.stdout_report_path }} + description: >- + Report file path for standard output. + + stderr_report_path: + value: ${{ steps.global.outputs.stderr_report_path }} + description: >- + Report file path for standard error. + runs: using: composite @@ -100,12 +111,11 @@ runs: echo test_all_enabled="${test_all_enabled}" >> $GITHUB_OUTPUT echo btest_enabled="${btest_enabled}" >> $GITHUB_OUTPUT echo test_spec_enabled="${test_spec_enabled}" >> $GITHUB_OUTPUT - echo test_all_session_file='launchable_test_all_session.txt' >> $GITHUB_OUTPUT - echo btest_session_file='launchable_btest_session.txt' >> $GITHUB_OUTPUT - echo test_spec_session_file='launchable_test_spec_session.txt' >> $GITHUB_OUTPUT echo test_all_report_file='launchable_test_all_report.json' >> $GITHUB_OUTPUT echo btest_report_file='launchable_btest_report.json' >> $GITHUB_OUTPUT echo test_spec_report_dir='launchable_test_spec_report' >> $GITHUB_OUTPUT + echo stdout_report_path="launchable_stdout.log" >> $GITHUB_OUTPUT + echo stderr_report_path="launchable_stderr.log" >> $GITHUB_OUTPUT if: steps.enable-launchable.outputs.enable-launchable - name: Set environment variables for Launchable @@ -123,6 +133,7 @@ runs: echo "LAUNCHABLE_TOKEN=${{ inputs.launchable-token }}" >> $GITHUB_ENV : # To prevent a slowdown in CI, disable request retries when the Launchable server is unstable. echo "LAUNCHABLE_SKIP_TIMEOUT_RETRY=1" >> $GITHUB_ENV + echo "LAUNCHABLE_COMMIT_TIMEOUT=1" >> $GITHUB_ENV if: steps.enable-launchable.outputs.enable-launchable - name: Set up path @@ -134,6 +145,7 @@ runs: if: steps.enable-launchable.outputs.enable-launchable && startsWith(inputs.os, 'macos') - name: Set up Launchable + id: setup-launchable shell: bash working-directory: ${{ inputs.srcdir }} run: | @@ -156,110 +168,72 @@ runs: btest_test_suite="yjit-${btest_test_suite}" test_spec_test_suite="yjit-${test_spec_test_suite}" fi + # launchable_setup target var -- refers ${target} prefixed variables + launchable_setup() { + local target=$1 session + eval [ "\${${target}_enabled}" = "true" ] || return + eval local suite=\${${target}_test_suite} + session=$(launchable record session \ + --build "${build_name}" \ + --observation \ + --flavor os="${{ inputs.os }}" \ + --flavor test_task="${{ inputs.test-task }}" \ + --flavor test_opts="${test_opts}" \ + --flavor workflow="${{ github.workflow }}" \ + --test-suite ${suite} \ + ) + launchable subset \ + --get-tests-from-previous-sessions \ + --non-blocking \ + --target 90% \ + --session "${session}" \ + raw > /dev/null + echo "${target}_session=${session}" >> $GITHUB_OUTPUT + } + launchable record build --name "${build_name}" - if [ "${test_all_enabled}" = "true" ]; then - launchable record session \ - --build "${build_name}" \ - --observation \ - --flavor os="${{ inputs.os }}" \ - --flavor test_task="${{ inputs.test-task }}" \ - --flavor test_opts="${test_opts}" \ - --flavor workflow="${{ github.workflow }}" \ - --test-suite ${test_all_test_suite} \ - > "${test_all_session_file}" - launchable subset \ - --get-tests-from-previous-sessions \ - --non-blocking \ - --target 90% \ - --session "$(cat "${test_all_session_file}")" \ - raw > /dev/null - echo "TESTS=${TESTS} --launchable-test-reports=${test_all_report_file}" >> $GITHUB_ENV + if launchable_setup test_all; then + echo "TESTS=${TESTS:+$TESTS }--launchable-test-reports=${test_all_report_file}" >> $GITHUB_ENV fi - if [ "${btest_enabled}" = "true" ]; then - launchable record session \ - --build "${build_name}" \ - --observation \ - --flavor os="${{ inputs.os }}" \ - --flavor test_task="${{ inputs.test-task }}" \ - --flavor test_opts="${test_opts}" \ - --flavor workflow="${{ github.workflow }}" \ - --test-suite ${btest_test_suite} \ - > "${btest_session_file}" - launchable subset \ - --get-tests-from-previous-sessions \ - --non-blocking \ - --target 90% \ - --session "$(cat "${btest_session_file}")" \ - raw > /dev/null - echo "BTESTS=${BTESTS} --launchable-test-reports=${btest_report_file}" >> $GITHUB_ENV + if launchable_setup btest; then + echo "BTESTS=${BTESTS:+$BTESTS }--launchable-test-reports=${btest_report_file}" >> $GITHUB_ENV fi - if [ "${test_spec_enabled}" = "true" ]; then - launchable record session \ - --build "${build_name}" \ - --observation \ - --flavor os="${{ inputs.os }}" \ - --flavor test_task="${{ inputs.test-task }}" \ - --flavor test_opts="${test_opts}" \ - --flavor workflow="${{ github.workflow }}" \ - --test-suite ${test_spec_test_suite} \ - > "${test_spec_session_file}" - launchable subset \ - --get-tests-from-previous-sessions \ - --non-blocking \ - --target 90% \ - --session "$(cat "${test_spec_session_file}")" \ - raw > /dev/null - echo "SPECOPTS=${SPECOPTS} --launchable-test-reports=${test_spec_report_dir}" >> $GITHUB_ENV + if launchable_setup test_spec; then + echo "SPECOPTS=${SPECOPTS:$SPECOPTS }--launchable-test-reports=${test_spec_report_dir}" >> $GITHUB_ENV + echo test_spec_enabled=true >> $GITHUB_OUTPUT fi + + echo launchable_setup_dir=$(pwd) >> $GITHUB_OUTPUT if: steps.enable-launchable.outputs.enable-launchable env: test_all_enabled: ${{ steps.global.outputs.test_all_enabled }} btest_enabled: ${{ steps.global.outputs.btest_enabled }} test_spec_enabled: ${{ steps.global.outputs.test_spec_enabled }} - test_all_session_file: ${{ steps.global.outputs.test_all_session_file }} - btest_session_file: ${{ steps.global.outputs.btest_session_file }} - test_spec_session_file: ${{ steps.global.outputs.test_spec_session_file }} test_all_report_file: ${{ steps.global.outputs.test_all_report_file }} btest_report_file: ${{ steps.global.outputs.btest_report_file }} test_spec_report_dir: ${{ steps.global.outputs.test_spec_report_dir }} - - name: Variables to report Launchable - id: variables + - name: make test-spec report directory in build directory shell: bash - working-directory: ${{ inputs.srcdir }} - run: | - set -x - : # report-path from srcdir - if [ "${srcdir}" = "${{ github.workspace }}" ]; then - dir= - else - # srcdir must be equal to or under workspace - dir=$(echo ${srcdir:+${srcdir}/} | sed 's:[^/][^/]*/:../:g') - fi - if [ "${test_all_enabled}" = "true" ]; then - test_report_path="${dir}${builddir:+${builddir}/}${test_all_report_file}" - echo test_report_path="${test_report_path}" >> $GITHUB_OUTPUT - fi - if [ "${btest_enabled}" = "true" ]; then - btest_report_path="${dir}${builddir:+${builddir}/}${btest_report_file}" - echo btest_report_path="${btest_report_path}" >> $GITHUB_OUTPUT - fi - if [ "${test_spec_enabled}" = "true" ]; then - test_spec_report_path="${dir}${builddir:+${builddir}/}${test_spec_report_dir}" - mkdir "${test_spec_report_path}" - echo test_spec_report_path="${test_spec_report_path}" >> $GITHUB_OUTPUT - fi - stdout_report_path="${dir}${builddir:+${builddir}/}launchable_stdout.log" - stderr_report_path="${dir}${builddir:+${builddir}/}launchable_stderr.log" - echo stdout_report_path="${stdout_report_path}" >> $GITHUB_OUTPUT - echo stderr_report_path="${stderr_report_path}" >> $GITHUB_OUTPUT - if: steps.enable-launchable.outputs.enable-launchable + working-directory: ${{ inputs.builddir }} + run: mkdir "${test_spec_report_dir}" + if: ${{ steps.setup-launchable.outputs.test_spec_enabled == 'true' }} + env: + test_spec_report_dir: ${{ steps.global.outputs.test_spec_report_dir }} + + - name: Clean up test results in Launchable + uses: gacts/run-and-post-run@674528335da98a7afc80915ff2b4b860a0b3553a # v1.4.0 + with: + shell: bash + working-directory: ${{ inputs.builddir }} + post: | + rm -f "${test_all_report_file}" + rm -f "${btest_report_file}" + rm -fr "${test_spec_report_dir}" + rm -f launchable_stdout.log + rm -f launchable_stderr.log + if: always() && steps.setup-launchable.outcome == 'success' env: - srcdir: ${{ inputs.srcdir }} - builddir: ${{ inputs.builddir }} - test_all_enabled: ${{ steps.global.outputs.test_all_enabled }} - btest_enabled: ${{ steps.global.outputs.btest_enabled }} - test_spec_enabled: ${{ steps.global.outputs.test_spec_enabled }} test_all_report_file: ${{ steps.global.outputs.test_all_report_file }} btest_report_file: ${{ steps.global.outputs.btest_report_file }} test_spec_report_dir: ${{ steps.global.outputs.test_spec_report_dir }} @@ -268,56 +242,48 @@ runs: uses: gacts/run-and-post-run@674528335da98a7afc80915ff2b4b860a0b3553a # v1.4.0 with: shell: bash - working-directory: ${{ inputs.srcdir }} + working-directory: ${{ inputs.builddir }} post: | if [[ "${test_all_enabled}" = "true" ]]; then \ launchable record attachment \ - --session "$(cat "${test_all_session_file}")" \ + --session "${test_all_session}" \ "${stdout_report_path}" \ "${stderr_report_path}"; \ launchable record tests \ - --session "$(cat "${test_all_session_file}")" \ - raw "${test_report_path}" || true; \ + --session "${test_all_session}" \ + raw "${test_all_report_file}" || true; \ fi if [[ "${btest_enabled}" = "true" ]]; then \ launchable record attachment \ - --session "$(cat "${btest_session_file}")" \ + --session "${btest_session}" \ "${stdout_report_path}" \ "${stderr_report_path}"; \ launchable record tests \ - --session "$(cat "${btest_session_file}")" \ - raw "${btest_report_path}" || true; \ + --session "${btest_session}" \ + raw "${btest_report_file}" || true; \ fi if [[ "${test_spec_enabled}" = "true" ]]; then \ launchable record attachment \ - --session "$(cat "${test_spec_session_file}")" \ + --session "${test_spec_session}" \ "${stdout_report_path}" \ "${stderr_report_path}"; \ launchable record tests \ - --session "$(cat "${test_spec_session_file}")" \ - raw ${test_spec_report_path}/* || true; \ + --session "${test_spec_session}" \ + raw ${test_spec_report_dir}/* || true; \ fi - - rm -f "${test_all_session_file}" - rm -f "${btest_session_file}" - rm -f "${test_spec_session_file}" - rm -f "${test_report_path}" - rm -f "${btest_report_path}" - rm -fr "${test_spec_report_path}" - rm -f "${stdout_report_path}" - rm -f "${stderr_report_path}" - if: ${{ always() && steps.enable-launchable.outputs.enable-launchable }} + if: ${{ always() && steps.setup-launchable.outcome == 'success' }} env: - test_report_path: ${{ steps.variables.outputs.test_report_path }} - btest_report_path: ${{ steps.variables.outputs.btest_report_path }} - test_spec_report_path: ${{ steps.variables.outputs.test_spec_report_path }} + test_all_report_file: ${{ steps.global.outputs.test_all_report_file }} + btest_report_file: ${{ steps.global.outputs.btest_report_file }} + test_spec_report_dir: ${{ steps.global.outputs.test_spec_report_dir }} test_all_enabled: ${{ steps.global.outputs.test_all_enabled }} btest_enabled: ${{ steps.global.outputs.btest_enabled }} test_spec_enabled: ${{ steps.global.outputs.test_spec_enabled }} - test_all_session_file: ${{ steps.global.outputs.test_all_session_file }} - btest_session_file: ${{ steps.global.outputs.btest_session_file }} - test_spec_session_file: ${{ steps.global.outputs.test_spec_session_file }} - stdout_report_path: ${{ steps.variables.outputs.stdout_report_path }} - stderr_report_path: ${{ steps.variables.outputs.stderr_report_path }} + test_all_session: ${{ steps.setup-launchable.outputs.test_all_session }} + btest_session: ${{ steps.setup-launchable.outputs.btest_session }} + test_spec_session: ${{ steps.setup-launchable.outputs.test_spec_session }} + stdout_report_path: ${{ steps.global.outputs.stdout_report_path }} + stderr_report_path: ${{ steps.global.outputs.stderr_report_path }} + LAUNCHABLE_SETUP_DIR: ${{ steps.setup-launchable.outputs.launchable_setup_dir }} diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml index 48e2c64a96..00a3a4cf2a 100644 --- a/.github/actions/setup/directories/action.yml +++ b/.github/actions/setup/directories/action.yml @@ -76,7 +76,7 @@ runs: shell: bash run: | echo "git=`command -v git`" >> "$GITHUB_OUTPUT" - echo "sudo=`command -v sudo`" >> "$GITHUB_OUTPUT" + echo "sudo=`sudo true && command -v sudo`" >> "$GITHUB_OUTPUT" echo "autoreconf=`command -v autoreconf`" >> "$GITHUB_OUTPUT" - if: steps.which.outputs.git @@ -183,3 +183,5 @@ runs: ${{ steps.clean.outputs.distclean }} ${{ steps.clean.outputs.remained-files }} ${{ steps.clean.outputs.final }} + # rmdir randomly fails due to launchable files + continue-on-error: true diff --git a/.github/actions/setup/macos/action.yml b/.github/actions/setup/macos/action.yml index 5da7c6d44c..d0072ff828 100644 --- a/.github/actions/setup/macos/action.yml +++ b/.github/actions/setup/macos/action.yml @@ -21,7 +21,7 @@ runs: dir_config() { local args=() lib var="$1"; shift for lib in "$@"; do - args+="--with-${lib%@*}-dir=$(brew --prefix $lib)" + args+=("--with-${lib%@*}-dir=$(brew --prefix $lib)") done echo "$var=${args[*]}" >> $GITHUB_ENV } diff --git a/.github/auto_request_review.yml b/.github/auto_request_review.yml index 113db986c3..264e6ef177 100644 --- a/.github/auto_request_review.yml +++ b/.github/auto_request_review.yml @@ -10,6 +10,8 @@ files: 'zjit/src/cruby_bindings.inc.rs': [] 'doc/zjit*': [team:jit] 'test/ruby/test_zjit*': [team:jit] + 'test/.excludes-zjit/*': [team:jit] + 'defs/jit.mk': [team:jit] options: ignore_draft: true # This currently doesn't work as intended. We want to skip reviews when only diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index dcff2d699a..a890fc442f 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -74,7 +74,7 @@ jobs: builddir: build makeup: true - - uses: ruby/setup-ruby@d8d83c3960843afb664e821fed6be52f37da5267 # v1.231.0 + - uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/auto_request_review.yml b/.github/workflows/auto_request_review.yml index a6c81c78cd..207315a084 100644 --- a/.github/workflows/auto_request_review.yml +++ b/.github/workflows/auto_request_review.yml @@ -17,4 +17,4 @@ jobs: uses: necojackarc/auto-request-review@e89da1a8cd7c8c16d9de9c6e763290b6b0e3d424 # v0.13.0 with: # scope: public_repo - token: ${{ secrets.MATZBOT_GITHUB_TOKEN }} + token: ${{ secrets.MATZBOT_AUTO_REQUEST_REVIEW_TOKEN }} diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 6b3974bc5b..8b77b01889 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -50,7 +50,7 @@ jobs: - ruby-3.3 steps: - - uses: ruby/setup-ruby@d8d83c3960843afb664e821fed6be52f37da5267 # v1.231.0 + - uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 233f624453..27ad55307b 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -33,11 +33,11 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - uses: ./.github/actions/setup/directories with: - # Skip overwriting MATZBOT_GITHUB_TOKEN + # Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN checkout: '' # false (ref: https://github.com/actions/runner/issues/2238) - name: Set ENV @@ -104,7 +104,7 @@ jobs: timeout-minutes: 30 env: RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor' + TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' if: ${{ steps.diff.outputs.gems }} - name: Commit @@ -112,7 +112,7 @@ jobs: git pull --ff-only origin ${GITHUB_REF#refs/heads/} message="Update bundled gems list" if [ -z "${gems}" ]; then - git commit --message="${message} at ${GITHUB_SHA:0:30} [ci skip]" + git commit --message="[DOC] ${message} at ${GITHUB_SHA:0:30}" else git commit --message="${message} as of ${TODAY}" fi diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index aa3882c165..22452a3b9e 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -40,7 +40,7 @@ jobs: - uses: ./.github/actions/setup/directories - - uses: ruby/setup-ruby@d8d83c3960843afb664e821fed6be52f37da5267 # v1.231.0 + - uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 543c54a3c9..72fcbe6996 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -20,12 +20,12 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - uses: ./.github/actions/setup/directories with: makeup: true - # Skip overwriting MATZBOT_GITHUB_TOKEN + # Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN checkout: '' # false (ref: https://github.com/actions/runner/issues/2238) # Run this step first to make sure auto-style commits are pushed @@ -37,10 +37,10 @@ jobs: EMAIL: svn-admin@ruby-lang.org GIT_AUTHOR_NAME: git GIT_COMMITTER_NAME: git - GITHUB_OLD_SHA: ${{ startsWith(github.event_name, 'pull') && github.event.pull_request.base.sha || github.event.before }} - GITHUB_NEW_SHA: ${{ startsWith(github.event_name, 'pull') && github.event.pull_request.merge_commit_sha || github.event.after }} + GITHUB_OLD_SHA: ${{ github.event.pull_request.base.sha }} + GITHUB_NEW_SHA: ${{ github.event.pull_request.merge_commit_sha }} PUSH_REF: ${{ github.ref == 'refs/heads/master' && github.ref || '' }} - if: ${{ github.repository == 'ruby/ruby' }} + if: ${{ github.repository == 'ruby/ruby' && startsWith(github.event_name, 'pull') }} - name: Check if C-sources are US-ASCII run: | @@ -64,7 +64,7 @@ jobs: - name: Generate docs id: docs run: | - ruby -W0 --disable-gems -I./lib tool/rdoc-srcdir -q --op html . + ruby -W0 --disable-gems tool/rdoc-srcdir -q --op html . echo htmlout=ruby-html-${GITHUB_SHA:0:10} >> $GITHUB_OUTPUT # Generate only when document commit/PR if: >- diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3aeed41b0a..0dfae7f045 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -80,6 +80,7 @@ jobs: uses: github/codeql-action/init@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 with: languages: ${{ matrix.language }} + trap-caching: false debug: true - name: Autobuild @@ -120,9 +121,3 @@ jobs: with: sarif_file: sarif-results/${{ matrix.language }}.sarif continue-on-error: true - - - name: Purge the oldest TRAP cache - if: ${{ github.repository == 'ruby/ruby' && matrix.language == 'cpp'}} - run: gh cache list --key codeql --order asc --limit 1 --json key --jq '.[].key' | xargs -I{} gh cache delete {} - env: - GH_TOKEN: ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }} diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index 55009b9604..d0be762cee 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -78,11 +78,11 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - name: 'GCC 13 LTO' + - name: 'GCC 15 LTO' uses: './.github/actions/compilers' with: - tag: gcc-13 - with_gcc: 'gcc-13 -flto=auto -ffat-lto-objects -Werror=lto-type-mismatch' + tag: gcc-15 + with_gcc: 'gcc-15 -flto=auto -ffat-lto-objects -Werror=lto-type-mismatch' optflags: '-O2' enable_shared: false - { uses: './.github/actions/compilers', name: 'ext/Setup', with: { static_exts: 'etc json/* */escape' } } @@ -299,6 +299,8 @@ jobs: - { uses: './.github/actions/compilers', name: 'VM_DEBUG_BP_CHECK', with: { cppflags: '-DVM_DEBUG_BP_CHECK' } } - { uses: './.github/actions/compilers', name: 'VM_DEBUG_VERIFY_METHOD_CACHE', with: { cppflags: '-DVM_DEBUG_VERIFY_METHOD_CACHE' } } - { uses: './.github/actions/compilers', name: 'enable-yjit', with: { append_configure: '--enable-yjit' } } + - { uses: './.github/actions/compilers', name: 'enable-{y,z}jit', with: { append_configure: '--enable-yjit --enable-zjit' } } + - { uses: './.github/actions/compilers', name: 'enable-{y,z}jit=dev', with: { append_configure: '--enable-yjit=dev --enable-zjit' } } - { uses: './.github/actions/compilers', name: 'YJIT_FORCE_ENABLE', with: { cppflags: '-DYJIT_FORCE_ENABLE' } } - { uses: './.github/actions/compilers', name: 'UNIVERSAL_PARSER', with: { cppflags: '-DUNIVERSAL_PARSER' } } diff --git a/.github/workflows/default_gems.yml b/.github/workflows/default_gems.yml index 3399dcbc4f..cd15e34229 100644 --- a/.github/workflows/default_gems.yml +++ b/.github/workflows/default_gems.yml @@ -22,18 +22,19 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - - - uses: ./.github/actions/setup/directories - with: - makeup: true - # Skip overwriting MATZBOT_GITHUB_TOKEN - checkout: '' # false (ref: https://github.com/actions/runner/issues/2238) + token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - id: gems run: true if: ${{ github.ref == 'refs/heads/master' }} + - uses: ./.github/actions/setup/directories + with: + makeup: true + # Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN + checkout: '' # false (ref: https://github.com/actions/runner/issues/2238) + if: ${{ steps.gems.outcome == 'success' }} + - name: Download previous gems list run: | data=default_gems.json diff --git a/.github/workflows/dependabot_automerge.yml b/.github/workflows/dependabot_automerge.yml index b1293deb62..09fdba7b2b 100644 --- a/.github/workflows/dependabot_automerge.yml +++ b/.github/workflows/dependabot_automerge.yml @@ -13,13 +13,13 @@ jobs: if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'ruby/ruby' steps: - name: Dependabot metadata - uses: dependabot/fetch-metadata@d7267f607e9d3fb96fc2fbe83e0af444713e90b7 # v2.3.0 + uses: dependabot/fetch-metadata@08eff52bf64351f401fb50d4972fa95b9f2c2d1b # v2.4.0 id: metadata - name: Wait for status checks uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc # v1.3.4 with: - repo-token: ${{ secrets.MATZBOT_GITHUB_TOKEN }} + repo-token: ${{ secrets.GITHUB_TOKEN }} ref: ${{ github.event.pull_request.head.sha || github.sha }} check-regexp: 'make \(check, .*\)' wait-interval: 30 @@ -29,4 +29,4 @@ jobs: run: gh pr merge --auto --rebase "$PR_URL" env: PR_URL: ${{ github.event.pull_request.html_url }} - GITHUB_TOKEN: ${{ secrets.MATZBOT_GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.MATZBOT_DEPENDABOT_MERGE_TOKEN }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 2bfb7e037e..bf5f5cd413 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -107,6 +107,10 @@ jobs: - run: make hello + - name: runirb + run: | + echo IRB::VERSION | make runirb RUNOPT="-- -f" + - name: Set test options for skipped tests run: | set -x @@ -115,6 +119,7 @@ jobs: if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} - name: Set up Launchable + id: launchable uses: ./.github/actions/launchable/setup with: os: ${{ matrix.os }} @@ -123,6 +128,7 @@ jobs: builddir: build srcdir: src continue-on-error: true + timeout-minutes: 3 - name: Set extra test options run: | @@ -132,19 +138,18 @@ jobs: - name: make ${{ matrix.test_task }} run: | - if [ -n "${LAUNCHABLE_ORGANIZATION}" ]; then - exec \ - > >(tee launchable_stdout.log) \ - 2> >(tee launchable_stderr.log) - fi + test -n "${LAUNCHABLE_STDOUT}" && exec 1> >(tee "${LAUNCHABLE_STDOUT}") + test -n "${LAUNCHABLE_STDERR}" && exec 2> >(tee "${LAUNCHABLE_STDERR}") ulimit -c unlimited make -s ${{ matrix.test_task }} ${TESTS:+TESTS="$TESTS"} timeout-minutes: 60 env: RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor' + TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' PRECHECK_BUNDLED_GEMS: 'no' + LAUNCHABLE_STDOUT: ${{ steps.launchable.outputs.stdout_report_path }} + LAUNCHABLE_STDERR: ${{ steps.launchable.outputs.stderr_report_path }} - name: make skipped tests run: | diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 49cdd8e879..72656fa766 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -127,6 +127,7 @@ jobs: srcdir: src test-tasks: '["test", "test-all", "test-spec"]' continue-on-error: true + timeout-minutes: 3 - name: test timeout-minutes: 30 diff --git a/.github/workflows/modgc.yml b/.github/workflows/modgc.yml index 5354d9bb97..815994b395 100644 --- a/.github/workflows/modgc.yml +++ b/.github/workflows/modgc.yml @@ -26,8 +26,8 @@ jobs: matrix: gc: - name: default - # - name: mmtk - # mmtk_build: release + - name: mmtk + mmtk_build: release os: [macos-latest, ubuntu-latest] include: - test_task: check @@ -63,7 +63,7 @@ jobs: uses: ./.github/actions/setup/ubuntu if: ${{ contains(matrix.os, 'ubuntu') }} - - uses: ruby/setup-ruby@d8d83c3960843afb664e821fed6be52f37da5267 # v1.231.0 + - uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0 with: ruby-version: '3.1' bundler: none @@ -131,6 +131,7 @@ jobs: if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} - name: Set up Launchable + id: launchable uses: ./.github/actions/launchable/setup with: os: ${{ matrix.os || 'ubuntu-22.04' }} @@ -139,14 +140,12 @@ jobs: builddir: build srcdir: src continue-on-error: true + timeout-minutes: 3 - name: make ${{ matrix.test_task }} run: | - if [ -n "${LAUNCHABLE_ORGANIZATION}" ]; then - exec \ - > >(tee launchable_stdout.log) \ - 2> >(tee launchable_stderr.log) - fi + test -n "${LAUNCHABLE_STDOUT}" && exec 1> >(tee "${LAUNCHABLE_STDOUT}") + test -n "${LAUNCHABLE_STDERR}" && exec 2> >(tee "${LAUNCHABLE_STDERR}") $SETARCH make -s ${{ matrix.test_task }} \ ${TESTS:+TESTS="$TESTS"} \ @@ -154,8 +153,10 @@ jobs: timeout-minutes: ${{ matrix.gc.timeout || 40 }} env: RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor' + TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' PRECHECK_BUNDLED_GEMS: 'no' + LAUNCHABLE_STDOUT: ${{ steps.launchable.outputs.stdout_report_path }} + LAUNCHABLE_STDERR: ${{ steps.launchable.outputs.stderr_report_path }} - name: make skipped tests run: | diff --git a/.github/workflows/parse_y.yml b/.github/workflows/parse_y.yml index 824dea5d32..eb7c6c2202 100644 --- a/.github/workflows/parse_y.yml +++ b/.github/workflows/parse_y.yml @@ -60,7 +60,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@d8d83c3960843afb664e821fed6be52f37da5267 # v1.231.0 + - uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0 with: ruby-version: '3.1' bundler: none @@ -87,7 +87,7 @@ jobs: EXCLUDES: '../src/test/.excludes-parsey' RUN_OPTS: ${{ matrix.run_opts || '--parser=parse.y' }} SPECOPTS: ${{ matrix.specopts || '-T --parser=parse.y' }} - TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor' + TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' - uses: ./.github/actions/slack with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 25916066d6..284e336a29 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,7 +55,7 @@ jobs: echo $PREVIOUS_RELEASE_TAG tool/gen-github-release.rb $PREVIOUS_RELEASE_TAG $RELEASE_TAG --no-dry-run env: - GITHUB_TOKEN: ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }} + GITHUB_TOKEN: ${{ secrets.MATZBOT_AUTO_UPDATE_TOKEN }} - name: Update versions index run: | diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index ef36e55c16..8cc7e00c47 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -2,7 +2,7 @@ # by a third-party and are governed by separate terms of service, privacy # policy, and support documentation. -name: Scorecards supply-chain security +name: Scorecard supply-chain security on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection @@ -10,7 +10,7 @@ on: # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - - cron: '22 4 * * 2' + - cron: '39 3 * * 5' # push: # branches: [ "master" ] @@ -19,8 +19,10 @@ permissions: read-all jobs: analysis: - name: Scorecards analysis + name: Scorecard analysis runs-on: ubuntu-latest + # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled. + if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request' permissions: # Needed to upload the results to code-scanning dashboard. security-events: write @@ -31,21 +33,21 @@ jobs: # actions: read steps: - - name: 'Checkout code' + - name: "Checkout code" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - name: 'Run analysis' - uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + - name: "Run analysis" + uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 with: results_file: results.sarif results_format: sarif - # (Optional) Read-only PAT token. Uncomment the `repo_token` line below if: + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: # - you want to enable the Branch-Protection check on a *public* repository, or - # - you are installing Scorecards on a *private* repository - # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. - repo_token: ${{ secrets.SCORECARD_READ_TOKEN }} + # - you are installing Scorecard on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} # Public repositories: # - Publish results to OpenSSF REST API for easy access by consumers @@ -56,17 +58,21 @@ jobs: # of the value entered here. publish_results: true + # (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore + # file_mode: git + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - # - name: "Upload artifact" - # uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 - # with: - # name: SARIF file - # path: results.sarif - # retention-days: 5 + - name: "Upload artifact" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: SARIF file + path: results.sarif + retention-days: 5 - # Upload the results to GitHub's code scanning dashboard. - - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 + # Upload the results to GitHub's code scanning dashboard (optional). + # Commenting out will disable upload of results to your repo's Code Scanning dashboard + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: results.sarif diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index ef67e1a505..d723abde21 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -48,7 +48,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ruby/setup-ruby@d8d83c3960843afb664e821fed6be52f37da5267 # v1.231.0 + - uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 018b7a86f0..3005809b08 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -68,7 +68,7 @@ jobs: with: arch: ${{ matrix.arch }} - - uses: ruby/setup-ruby@d8d83c3960843afb664e821fed6be52f37da5267 # v1.231.0 + - uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0 with: ruby-version: '3.1' bundler: none @@ -99,6 +99,10 @@ jobs: - run: $SETARCH make hello + - name: runirb + run: | + echo IRB::VERSION | $SETARCH make runirb RUNOPT="-- -f" + - name: Set test options for skipped tests run: | set -x @@ -107,6 +111,7 @@ jobs: if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} - name: Set up Launchable + id: launchable uses: ./.github/actions/launchable/setup with: os: ${{ matrix.os || 'ubuntu-22.04' }} @@ -115,14 +120,12 @@ jobs: builddir: build srcdir: src continue-on-error: true + timeout-minutes: 3 - name: make ${{ matrix.test_task }} run: | - if [ -n "${LAUNCHABLE_ORGANIZATION}" ]; then - exec \ - > >(tee launchable_stdout.log) \ - 2> >(tee launchable_stderr.log) - fi + test -n "${LAUNCHABLE_STDOUT}" && exec 1> >(tee "${LAUNCHABLE_STDOUT}") + test -n "${LAUNCHABLE_STDERR}" && exec 2> >(tee "${LAUNCHABLE_STDERR}") $SETARCH make -s ${{ matrix.test_task }} \ ${TESTS:+TESTS="$TESTS"} \ @@ -130,8 +133,10 @@ jobs: timeout-minutes: ${{ matrix.timeout || 40 }} env: RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor' + TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' PRECHECK_BUNDLED_GEMS: 'no' + LAUNCHABLE_STDOUT: ${{ steps.launchable.outputs.stdout_report_path }} + LAUNCHABLE_STDERR: ${{ steps.launchable.outputs.stderr_report_path }} - name: make skipped tests run: | diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 88ea7bd76c..2c49d99071 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -100,7 +100,7 @@ jobs: run: | echo "WASI_SDK_PATH=/opt/wasi-sdk" >> $GITHUB_ENV - - uses: ruby/setup-ruby@d8d83c3960843afb664e821fed6be52f37da5267 # v1.231.0 + - uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0 with: ruby-version: '3.1' bundler: none @@ -142,7 +142,7 @@ jobs: - run: tar cfz ../install.tar.gz -C ../install . - name: Upload artifacts - uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4.4.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: ruby-wasm-install path: ${{ github.workspace }}/install.tar.gz @@ -152,8 +152,6 @@ jobs: - name: Run basictest run: wasmtime run ./../build/miniruby --mapdir /::./ -- basictest/test.rb - env: - WASMTIME_BACKTRACE_DETAILS: '1' working-directory: src - name: Run bootstraptest (no thread) @@ -172,7 +170,7 @@ jobs: - name: Save Pull Request number if: ${{ github.event_name == 'pull_request' }} run: echo "${{ github.event.pull_request.number }}" >> ${{ github.workspace }}/github-pr-info.txt - - uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4.4.1 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: ${{ github.event_name == 'pull_request' }} with: name: github-pr-info diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 50b3284a33..47e3f53241 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -26,19 +26,16 @@ jobs: matrix: include: - os: 2022 - vc: 2019 - vcvars: '10.0.22621.0 -vcvars_ver=14.2' # The defautl Windows 11 SDK and toolset are broken at windows-2022 + vc: 2022 test_task: check - os: 2025 - vc: 2019 - vcvars: '10.0.22621.0 -vcvars_ver=14.2' + vc: 2022 test_task: check - os: 11-arm test_task: 'btest test-basic test-tool' # check and test-spec are broken yet. target: arm64 - - os: 2022 - vc: 2019 - vcvars: '10.0.22621.0 -vcvars_ver=14.2' + - os: 2025 + vc: 2022 test_task: test-bundled-gems fail-fast: false @@ -59,27 +56,18 @@ jobs: env: GITPULLOPTIONS: --no-tags origin ${{ github.ref }} VCPKG_DEFAULT_TRIPLET: ${{ matrix.target || 'x64' }}-windows - RUBY_OPT_DIR: ${{ matrix.os == '11-arm' && 'C' || 'D' }}:/a/ruby/ruby/src/vcpkg_installed/%VCPKG_DEFAULT_TRIPLET% steps: - run: md build working-directory: - - uses: ruby/setup-ruby@e34163cd15f4bb403dcd72d98e295997e6a55798 # v1.238.0 + - uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0 with: # windows-11-arm has only 3.4.1, 3.4.2, 3.4.3, head - ruby-version: ${{ matrix.os != '11-arm' && '3.1' || '3.4' }} + ruby-version: ${{ !endsWith(matrix.os, 'arm') && '3.1' || '3.4' }} bundler: none windows-toolchain: none - - name: Install libraries with scoop - run: | - Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser - iwr -useb get.scoop.sh | iex - Join-Path (Resolve-Path ~).Path "scoop\shims" >> $Env:GITHUB_PATH - scoop install vcpkg uutils-coreutils cmake@3.31.6 - shell: pwsh - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: sparse-checkout-cone-mode: false @@ -90,27 +78,47 @@ jobs: srcdir: src builddir: build + - name: Install tools with scoop + run: | + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + iwr -useb get.scoop.sh | iex + Join-Path (Resolve-Path ~).Path "scoop\shims" >> $Env:GITHUB_PATH + scoop install vcpkg uutils-coreutils cmake@3.31.6 + shell: pwsh + + - name: Restore vcpkg artifact + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: src\vcpkg_installed + key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} + + - name: Install libraries with vcpkg + run: | + vcpkg install --vcpkg-root=C:\Users\runneradmin\scoop\apps\vcpkg\current + working-directory: src + + - name: Save vcpkg artifact + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: src\vcpkg_installed + key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} + - name: setup env # Available Ruby versions: https://github.com/actions/runner-images/blob/main/images/windows/Windows2019-Readme.md#ruby # %TEMP% is inconsistent with %TMP% and test-all expects they are consistent. # https://github.com/actions/virtual-environments/issues/712#issuecomment-613004302 run: | ::- Set up VC ${{ matrix.vc }} - set vswhere="%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" - for /f "delims=;" %%I in ('%vswhere% -latest -property installationPath') do ( - set VCVARS="%%I\VC\Auxiliary\Build\vcvars64.bat" - ) - if "${{ matrix.os }}" == "11-arm" ( - set VCVARS="C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsarm64.bat" - ) - set VCVARS set | uutils sort > old.env - call %VCVARS% ${{ matrix.vcvars || '' }} + call ..\src\win32\vssetup.cmd ^ + -arch=${{ matrix.target || 'amd64' }} ^ + ${{ matrix.vcvars && '-vcvars_ver=' || '' }}${{ matrix.vcvars }} nmake -f nul set TMP=%USERPROFILE%\AppData\Local\Temp set TEMP=%USERPROFILE%\AppData\Local\Temp set MAKEFLAGS=l set /a TEST_JOBS=(15 * %NUMBER_OF_PROCESSORS% / 10) > nul + set RUBY_OPT_DIR=%GITHUB_WORKSPACE:\=/%/src/vcpkg_installed/%VCPKG_DEFAULT_TRIPLET% set | uutils sort > new.env uutils comm -13 old.env new.env >> %GITHUB_ENV% del *.env @@ -125,18 +133,6 @@ jobs: run: Get-Volume shell: pwsh - # vcpkg built-in cache is not working now - - name: Restore vcpkg artifact - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 - with: - path: C:\Users\runneradmin\AppData\Local\vcpkg\archives - key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} - - - name: Install libraries with vcpkg - run: | - vcpkg install --vcpkg-root=C:\Users\runneradmin\scoop\apps\vcpkg\current - working-directory: src - # TODO: We should use `../src` instead of `D:/a/ruby/ruby/src` - name: Configure run: >- @@ -157,26 +153,30 @@ jobs: # windows-11-arm runner cannot run `ruby tool/file2lastrev.rb --revision.h --output=revision.h` - name: make revision.h run: | - for /f "tokens=1-3" %%I in ('git log -1 "--date=format-local:%%F %%T" "--format=%%H %%cd" @') do ( - set rev=%%I - set dt=%%J - set tm=%%K + if not exist revision.h ( + for /f "tokens=1-3" %%I in ('git log -1 "--date=format-local:%%F %%T" "--format=%%H %%cd" @') do ( + set rev=%%I + set dt=%%J + set tm=%%K + ) + call set yy=%%dt:~0,4%% + call set /a mm=100%%dt:~5,2%% %%%% 100 + call set /a dd=100%%dt:~8,2%% %%%% 100 + call set branch=%%GITHUB_REF:refs/heads/=%% + ( + call echo #define RUBY_REVISION "%%rev:~,10%%" + call echo #define RUBY_FULL_REVISION "%%rev%%" + call echo #define RUBY_BRANCH_NAME "%%branch%%" + call echo #define RUBY_RELEASE_DATETIME "%%dt%%T%%tm%%" + call echo #define RUBY_RELEASE_YEAR %%yy%% + call echo #define RUBY_RELEASE_MONTH %%mm%% + call echo #define RUBY_RELEASE_DAY %%dd%% + ) > revision.h + copy /y NUL .revision.time ) - set yy=%dt:~0,4% - set /a mm=100%dt:~5,2% %% 100 - set /a dd=100%dt:~8,2% %% 100 - ( - echo #define RUBY_REVISION "%rev:~,10%" - echo #define RUBY_FULL_REVISION "%rev%" - echo #define RUBY_BRANCH_NAME "%GITHUB_REF%" - echo #define RUBY_RELEASE_DATETIME "%dt%T%tm%" - echo #define RUBY_RELEASE_YEAR %yy% - echo #define RUBY_RELEASE_MONTH %mm% - echo #define RUBY_RELEASE_DAY %dd% - ) > revision.h + type revision.h env: TZ: UTC - if: ${{ matrix.os == '11-arm' }} - run: nmake @@ -190,6 +190,7 @@ jobs: test-task: ${{ matrix.test_task || 'check' }} continue-on-error: true if: ${{ matrix.test_task != 'test-bundled-gems' }} + timeout-minutes: 3 - run: nmake ${{ matrix.test_task || 'check' }} env: diff --git a/.github/workflows/wsl.yml b/.github/workflows/wsl.yml index e6b4133b76..af490dffd7 100644 --- a/.github/workflows/wsl.yml +++ b/.github/workflows/wsl.yml @@ -16,7 +16,7 @@ on: jobs: wsl: - runs-on: windows-latest + runs-on: windows-2025 if: >- ${{!(false @@ -29,9 +29,6 @@ jobs: )}} steps: - - name: Install winget - uses: Cyberboss/install-winget@v1 - - name: Install or update WSL uses: Ubuntu/WSL/.github/actions/wsl-install@main with: diff --git a/.github/workflows/yjit-macos.yml b/.github/workflows/yjit-macos.yml index a7700ffc1c..60139257af 100644 --- a/.github/workflows/yjit-macos.yml +++ b/.github/workflows/yjit-macos.yml @@ -112,6 +112,13 @@ jobs: - run: make + - name: Verify that --yjit-dump-disasm works + run: | + ./miniruby --yjit-call-threshold=1 --yjit-dump-disasm -e0 | \ + wc -l | \ + ruby -ne 'raise "Disassembly seems broken in dev build (output has too few lines)" unless $_.to_i > 10' + if: ${{ contains(matrix.configure, 'jit=dev') }} + - name: Enable YJIT through ENV run: echo "RUBY_YJIT_ENABLE=1" >> $GITHUB_ENV @@ -123,6 +130,7 @@ jobs: if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} - name: Set up Launchable + id: launchable uses: ./.github/actions/launchable/setup with: os: macos-14 @@ -132,14 +140,12 @@ jobs: srcdir: src is-yjit: true continue-on-error: true + timeout-minutes: 3 - name: make ${{ matrix.test_task }} run: | - if [ -n "${LAUNCHABLE_ORGANIZATION}" ]; then - exec \ - > >(tee launchable_stdout.log) \ - 2> >(tee launchable_stderr.log) - fi + test -n "${LAUNCHABLE_STDOUT}" && exec 1> >(tee "${LAUNCHABLE_STDOUT}") + test -n "${LAUNCHABLE_STDERR}" && exec 2> >(tee "${LAUNCHABLE_STDERR}") make -s ${{ matrix.test_task }} ${TESTS:+TESTS="$TESTS"} \ RUN_OPTS="$RUN_OPTS" \ @@ -147,9 +153,11 @@ jobs: timeout-minutes: 60 env: RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor' + TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' SYNTAX_SUGGEST_TIMEOUT: '5' PRECHECK_BUNDLED_GEMS: 'no' + LAUNCHABLE_STDOUT: ${{ steps.launchable.outputs.stdout_report_path }} + LAUNCHABLE_STDERR: ${{ steps.launchable.outputs.stderr_report_path }} continue-on-error: ${{ matrix.continue-on-test_task || false }} - name: make skipped tests diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index c148956c36..d71c5df48a 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -135,7 +135,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@d8d83c3960843afb664e821fed6be52f37da5267 # v1.231.0 + - uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0 with: ruby-version: '3.1' bundler: none @@ -163,6 +163,13 @@ jobs: - run: make + - name: Verify that --yjit-dump-disasm works + run: | + ./miniruby --yjit-call-threshold=1 --yjit-dump-disasm -e0 | \ + wc -l | \ + ruby -ne 'raise "Disassembly seems broken in dev build (output has too few lines)" unless $_.to_i > 10' + if: ${{ contains(matrix.configure, 'jit=dev') }} + - name: Enable YJIT through ENV run: echo "RUBY_YJIT_ENABLE=1" >> $GITHUB_ENV @@ -171,6 +178,7 @@ jobs: run: ./miniruby --yjit -v | grep "+YJIT" - name: Set up Launchable + id: launchable uses: ./.github/actions/launchable/setup with: os: ubuntu-22.04 @@ -180,14 +188,12 @@ jobs: srcdir: src is-yjit: true continue-on-error: true + timeout-minutes: 3 - name: make ${{ matrix.test_task }} run: | - if [ -n "${LAUNCHABLE_ORGANIZATION}" ]; then - exec \ - > >(tee launchable_stdout.log) \ - 2> >(tee launchable_stderr.log) - fi + test -n "${LAUNCHABLE_STDOUT}" && exec 1> >(tee "${LAUNCHABLE_STDOUT}") + test -n "${LAUNCHABLE_STDERR}" && exec 2> >(tee "${LAUNCHABLE_STDERR}") make -s ${{ matrix.test_task }} ${TESTS:+TESTS="$TESTS"} \ RUN_OPTS="$RUN_OPTS" MSPECOPT=--debug SPECOPTS="$SPECOPTS" \ @@ -195,11 +201,13 @@ jobs: timeout-minutes: 90 env: RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof,rbs,repl_type_completor' + TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' PRECHECK_BUNDLED_GEMS: 'no' SYNTAX_SUGGEST_TIMEOUT: '5' YJIT_BINDGEN_DIFF_OPTS: '--exit-code' LIBCLANG_PATH: ${{ matrix.libclang_path }} + LAUNCHABLE_STDOUT: ${{ steps.launchable.outputs.stdout_report_path }} + LAUNCHABLE_STDERR: ${{ steps.launchable.outputs.stderr_report_path }} continue-on-error: ${{ matrix.continue-on-test_task || false }} - name: Show ${{ github.event.pull_request.base.ref }} GitHub URL for yjit-bench comparison diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 4a7a2fbf01..30024a6d0c 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -33,16 +33,23 @@ jobs: matrix: include: - test_task: 'zjit-test' - configure: '--enable-zjit=dev' + configure: '--enable-yjit=dev --enable-zjit' - - test_task: 'test-all' + - test_task: 'ruby' # build test for combo build + configure: '--enable-yjit --enable-zjit' + + - test_task: 'zjit-test-all' + configure: '--enable-zjit=dev' + testopts: '--seed=11831' + + - test_task: 'btest' configure: '--enable-zjit=dev' - tests: '../src/test/ruby/test_zjit.rb' env: GITPULLOPTIONS: --no-tags origin ${{ github.ref }} RUN_OPTS: ${{ matrix.zjit_opts }} SPECOPTS: ${{ matrix.specopts }} + TESTOPTS: ${{ matrix.testopts }} runs-on: macos-14 @@ -57,7 +64,7 @@ jobs: )}} steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -90,19 +97,67 @@ jobs: - run: make + - name: Verify that --zjit-dump-disasm works + run: | + ./miniruby --zjit-call-threshold=1 --zjit-dump-disasm -e0 | \ + wc -l | \ + ruby -ne 'raise "Disassembly seems broken in dev build (output has too few lines)" unless $_.to_i > 10' + if: ${{ contains(matrix.configure, 'jit=dev') }} + + - name: btest + run: | + RUST_BACKTRACE=1 ruby --disable=gems ../src/bootstraptest/runner.rb --ruby="./miniruby -I../src/lib -I. -I.ext/common --zjit-call-threshold=1" \ + ../src/bootstraptest/test_attr.rb \ + ../src/bootstraptest/test_autoload.rb \ + ../src/bootstraptest/test_block.rb \ + ../src/bootstraptest/test_class.rb \ + ../src/bootstraptest/test_constant_cache.rb \ + ../src/bootstraptest/test_env.rb \ + ../src/bootstraptest/test_eval.rb \ + ../src/bootstraptest/test_exception.rb \ + ../src/bootstraptest/test_fiber.rb \ + ../src/bootstraptest/test_finalizer.rb \ + ../src/bootstraptest/test_flip.rb \ + ../src/bootstraptest/test_flow.rb \ + ../src/bootstraptest/test_fork.rb \ + ../src/bootstraptest/test_gc.rb \ + ../src/bootstraptest/test_insns.rb \ + ../src/bootstraptest/test_io.rb \ + ../src/bootstraptest/test_jump.rb \ + ../src/bootstraptest/test_literal.rb \ + ../src/bootstraptest/test_literal_suffix.rb \ + ../src/bootstraptest/test_load.rb \ + ../src/bootstraptest/test_marshal.rb \ + ../src/bootstraptest/test_massign.rb \ + ../src/bootstraptest/test_method.rb \ + ../src/bootstraptest/test_objectspace.rb \ + ../src/bootstraptest/test_proc.rb \ + ../src/bootstraptest/test_ractor.rb \ + ../src/bootstraptest/test_string.rb \ + ../src/bootstraptest/test_struct.rb \ + ../src/bootstraptest/test_syntax.rb \ + ../src/bootstraptest/test_thread.rb \ + ../src/bootstraptest/test_yjit_30k_ifelse.rb \ + ../src/bootstraptest/test_yjit_30k_methods.rb \ + ../src/bootstraptest/test_yjit_rust_port.rb + # ../src/bootstraptest/test_yjit.rb \ + if: ${{ matrix.test_task == 'btest' }} + - name: make ${{ matrix.test_task }} run: >- make -s ${{ matrix.test_task }} ${TESTS:+TESTS="$TESTS"} RUN_OPTS="$RUN_OPTS" SPECOPTS="$SPECOPTS" + TESTOPTS="$TESTOPTS" timeout-minutes: 60 env: RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof' + TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' SYNTAX_SUGGEST_TIMEOUT: '5' PRECHECK_BUNDLED_GEMS: 'no' TESTS: ${{ matrix.tests }} continue-on-error: ${{ matrix.continue-on-test_task || false }} + if: ${{ matrix.test_task != 'btest' }} result: if: ${{ always() }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 46a794f095..de3e98d358 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -38,17 +38,21 @@ jobs: libclang_path: '/usr/lib/llvm-14/lib/libclang.so.1' - test_task: 'zjit-test' - configure: '--enable-zjit=dev' + configure: '--enable-yjit --enable-zjit=dev' - - test_task: 'test-all' + - test_task: 'zjit-test-all' + configure: '--enable-zjit=dev' + testopts: '--seed=18140' + + - test_task: 'btest' configure: '--enable-zjit=dev' - tests: '../src/test/ruby/test_zjit.rb' env: GITPULLOPTIONS: --no-tags origin ${{ github.ref }} RUN_OPTS: ${{ matrix.zjit_opts }} YJIT_BENCH_OPTS: ${{ matrix.yjit_bench_opts }} SPECOPTS: ${{ matrix.specopts }} + TESTOPTS: ${{ matrix.testopts }} RUBY_DEBUG: ci BUNDLE_JOBS: 8 # for yjit-bench RUST_BACKTRACE: 1 @@ -66,14 +70,14 @@ jobs: )}} steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: sparse-checkout-cone-mode: false sparse-checkout: /.github - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0 + - uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0 with: ruby-version: '3.1' bundler: none @@ -110,26 +114,74 @@ jobs: - run: make + - name: Verify that --zjit-dump-disasm works + run: | + ./miniruby --zjit-call-threshold=1 --zjit-dump-disasm -e0 | \ + wc -l | \ + ruby -ne 'raise "Disassembly seems broken in dev build (output has too few lines)" unless $_.to_i > 10' + if: ${{ contains(matrix.configure, 'jit=dev') }} + # Check that the binary was built with ZJIT - name: Check ZJIT enabled run: ./miniruby --zjit -v | grep "+ZJIT" if: ${{ matrix.configure != '--disable-zjit' }} + - name: btest + run: | + RUST_BACKTRACE=1 ruby --disable=gems ../src/bootstraptest/runner.rb --ruby="./miniruby -I../src/lib -I. -I.ext/common --zjit-call-threshold=1" \ + ../src/bootstraptest/test_attr.rb \ + ../src/bootstraptest/test_autoload.rb \ + ../src/bootstraptest/test_block.rb \ + ../src/bootstraptest/test_class.rb \ + ../src/bootstraptest/test_constant_cache.rb \ + ../src/bootstraptest/test_env.rb \ + ../src/bootstraptest/test_env.rb \ + ../src/bootstraptest/test_exception.rb \ + ../src/bootstraptest/test_fiber.rb \ + ../src/bootstraptest/test_finalizer.rb \ + ../src/bootstraptest/test_flip.rb \ + ../src/bootstraptest/test_flow.rb \ + ../src/bootstraptest/test_fork.rb \ + ../src/bootstraptest/test_gc.rb \ + ../src/bootstraptest/test_insns.rb \ + ../src/bootstraptest/test_io.rb \ + ../src/bootstraptest/test_jump.rb \ + ../src/bootstraptest/test_literal.rb \ + ../src/bootstraptest/test_literal_suffix.rb \ + ../src/bootstraptest/test_load.rb \ + ../src/bootstraptest/test_marshal.rb \ + ../src/bootstraptest/test_massign.rb \ + ../src/bootstraptest/test_method.rb \ + ../src/bootstraptest/test_objectspace.rb \ + ../src/bootstraptest/test_proc.rb \ + ../src/bootstraptest/test_ractor.rb \ + ../src/bootstraptest/test_string.rb \ + ../src/bootstraptest/test_struct.rb \ + ../src/bootstraptest/test_syntax.rb \ + ../src/bootstraptest/test_thread.rb \ + ../src/bootstraptest/test_yjit_30k_ifelse.rb \ + ../src/bootstraptest/test_yjit_30k_methods.rb \ + ../src/bootstraptest/test_yjit_rust_port.rb + # ../src/bootstraptest/test_yjit.rb \ + if: ${{ matrix.test_task == 'btest' }} + - name: make ${{ matrix.test_task }} run: >- make -s ${{ matrix.test_task }} ${TESTS:+TESTS="$TESTS"} RUN_OPTS="$RUN_OPTS" MSPECOPT=--debug SPECOPTS="$SPECOPTS" + TESTOPTS="$TESTOPTS" ZJIT_BINDGEN_DIFF_OPTS="$ZJIT_BINDGEN_DIFF_OPTS" timeout-minutes: 90 env: RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'typeprof' + TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' PRECHECK_BUNDLED_GEMS: 'no' SYNTAX_SUGGEST_TIMEOUT: '5' ZJIT_BINDGEN_DIFF_OPTS: '--exit-code' LIBCLANG_PATH: ${{ matrix.libclang_path }} TESTS: ${{ matrix.tests }} continue-on-error: ${{ matrix.continue-on-test_task || false }} + if: ${{ matrix.test_task != 'btest' }} result: if: ${{ always() }} diff --git a/.gitignore b/.gitignore index 0f3574115b..3e8d3310f5 100644 --- a/.gitignore +++ b/.gitignore @@ -246,6 +246,9 @@ lcov*.info /yjit-bench /yjit_exit_locations.dump +# Rust +/target + # /wasm/ /wasm/tests/*.wasm diff --git a/.rdoc_options b/.rdoc_options index 7325a10edf..27d35e2f58 100644 --- a/.rdoc_options +++ b/.rdoc_options @@ -10,7 +10,7 @@ rdoc_include: exclude: - \Alib/irb -- .gemspec\z +- \.gemspec\z autolink_excluded_words: - Class @@ -20,3 +20,5 @@ autolink_excluded_words: - RDoc - Ruby - Set + +canonical_root: https://docs.ruby-lang.org/en/master diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000000..65131406d3 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,89 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "capstone" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "015ef5d5ca1743e3f94af9509ba6bd2886523cfee46e48d15c2ef5216fd4ac9a" +dependencies = [ + "capstone-sys", + "libc", +] + +[[package]] +name = "capstone-sys" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2267cb8d16a1e4197863ec4284ffd1aec26fe7e57c58af46b02590a0235809a0" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "cc" +version = "1.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" +dependencies = [ + "shlex", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "expect-test" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63af43ff4431e848fb47472a920f14fa71c24de13255a5692e93d4e90302acb0" +dependencies = [ + "dissimilar", + "once_cell", +] + +[[package]] +name = "jit" +version = "0.0.0" +dependencies = [ + "yjit", + "zjit", +] + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "yjit" +version = "0.1.0" +dependencies = [ + "capstone", +] + +[[package]] +name = "zjit" +version = "0.0.1" +dependencies = [ + "capstone", + "expect-test", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000..3f373fdace --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,51 @@ +# Using Cargo's workspace feature to build all the Rust code in +# into a single package. +# TODO(alan) notes about rust version requirements. Undecided yet. + +[workspace] +members = ["zjit", "yjit"] + +[package] +name = "jit" +version = "0.0.0" +edition = "2024" +rust-version = "1.85.0" +publish = false # Don't publish to crates.io + +[dependencies] +yjit = { path = "yjit", optional = true } +zjit = { path = "zjit", optional = true } + +[lib] +crate-type = ["staticlib"] +path = "jit.rs" + +[features] +disasm = ["yjit?/disasm", "zjit?/disasm"] +runtime_checks = ["yjit?/runtime_checks", "zjit?/runtime_checks"] +yjit = [ "dep:yjit" ] +zjit = [ "dep:zjit" ] + +[profile.dev] +opt-level = 0 +debug = true +debug-assertions = true +overflow-checks = true + +[profile.dev_nodebug] +inherits = "dev" + +[profile.stats] +inherits = "release" + +[profile.release] +# NOTE: --enable-yjit and zjit builds use `rustc` without going through Cargo. You +# might want to update the `rustc` invocation if you change this profile. +opt-level = 3 +# The extra robustness that comes from checking for arithmetic overflow is +# worth the performance cost for the compiler. +overflow-checks = true +# Generate debug info +debug = true +# Use ThinLTO. Much smaller output for a small amount of build time increase. +lto = "thin" diff --git a/NEWS.md b/NEWS.md index 811add0d83..7609c68bb2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -14,17 +14,86 @@ Note that each entry is kept to a minimum, see links for details. Note: We're only listing outstanding class updates. +* Kernel + + * `Kernel#inspect` now checks for the existence of a `#instance_variables_to_inspect` method, + allowing control over which instance variables are displayed in the `#inspect` string: + + ```ruby + class DatabaseConfig + def initialize(host, user, password) + @host = host + @user = user + @password = password + end + + private def instance_variables_to_inspect = [:@host, :@user] + end + + conf = DatabaseConfig.new("localhost", "root", "hunter2") + conf.inspect #=> # + ``` + + [[Feature #21219]] + * Binding * `Binding#local_variables` does no longer include numbered parameters. - Also, `Binding#local_variable_get` and `Binding#local_variable_set` reject to handle numbered parameters. - [[Bug #21049]] + Also, `Binding#local_variable_get` and `Binding#local_variable_set` reject + to handle numbered parameters. [[Bug #21049]] * IO * `IO.select` accepts +Float::INFINITY+ as a timeout argument. [[Feature #20610]] +* Socket + + * `Socket.tcp` & `TCPSocket.new` accepts `open_timeout` as a keyword argument to specify + the timeout for the initial connection. [[Feature #21347]] + +* Ractor + + * `Ractor::Port` class was added for a new synchronization mechanism + to communicate between Ractors. [[Feature #21262]] + + ```ruby + port1 = Ractor::Port.new + port2 = Ractor::Port.new + Ractor.new port1, port2 do |port1, port2| + port1 << 1 + port2 << 11 + port1 << 2 + port2 << 12 + end + 2.times{ p port1.receive } #=> 1, 2 + 2.times{ p port2.receive } #=> 11, 12 + ``` + + `Ractor::Port` provides the following methods: + + * `Ractor::Port#receive` + * `Ractor::Port#send` (or `Ractor::Port#<<`) + * `Ractor::Port#close` + * `Ractor::Port#closed?` + + As result, `Ractor.yield` and `Ractor#take` were removed. + + * `Ractor#join` and `Ractor#value` were added to wait for the + termination of a Ractor. These are similar to `Thread#join` + and `Thread#value`. + + * `Ractor#monitor` and `Ractor#unmonitor` were added as low-level + interfaces used internally to implement `Ractor#join`. + + * `Ractor.select` now only accepts Ractors and Ports. If Ractors are given, + it returns when a Ractor terminates. + + * `Ractor#default_port` was added. Each `Ractor` has a default port, + which is used by `Ractor.send`, `Ractor.receive`. + + * `Ractor#close_incoming` and `Ractor#close_outgoing` were removed. + * Set * Set is now a core class, instead of an autoloaded stdlib class. @@ -32,18 +101,30 @@ Note: We're only listing outstanding class updates. * String - * Update Unicode to Version 16.0.0 and Emoji Version 16.0. [[Feature #19908]][[Feature #20724]] - (also applies to Regexp) + * Update Unicode to Version 16.0.0 and Emoji Version 16.0. + [[Feature #19908]][[Feature #20724]] (also applies to Regexp) + +* Fiber::Scheduler + + * Introduce `Fiber::Scheduler#fiber_interrupt` to interrupt a fiber with a + given exception. The initial use case is to interrupt a fiber that is + waiting on a blocking IO operation when the IO operation is closed. + [[Feature #21166]] + +* Pathname + + * Pathname has been promoted from a default gem to a core class of Ruby. + [[Feature #17473]] ## Stdlib updates The following bundled gems are promoted from default gems. -* ostruct 0.6.1 +* ostruct 0.6.3 * pstore 0.2.0 -* benchmark 0.4.0 +* benchmark 0.4.1 * logger 1.7.0 -* rdoc 6.13.1 +* rdoc 6.14.2 * win32ole 1.9.2 * irb 1.15.2 * reline 0.6.1 @@ -52,7 +133,9 @@ The following bundled gems are promoted from default gems. We only list stdlib changes that are notable feature changes. -Other changes are listed in the following sections. we also listed release history from the previous bundled version that is Ruby 3.3.0 if it has GitHub releases. +Other changes are listed in the following sections. We also listed release +history from the previous bundled version that is Ruby 3.3.0 if it has GitHub +releases. The following default gem is added. @@ -60,16 +143,22 @@ The following default gem is added. The following default gems are updated. -* RubyGems 3.7.0.dev -* bundler 2.7.0.dev -* erb 5.0.0 -* json 2.12.0 +* RubyGems 3.8.0.dev +* bundler 2.8.0.dev +* erb 5.0.2 +* etc 1.4.6 +* io-console 0.8.1 +* io-nonblock 0.3.2 +* io-wait 0.3.2 +* json 2.12.2 * optparse 0.7.0.dev.2 * prism 1.4.0 * psych 5.2.6 +* resolv 0.6.2 * stringio 3.1.8.dev -* strscan 3.1.5.dev +* strscan 3.1.6.dev * uri 1.0.3 +* weakref 0.1.4 The following bundled gems are added. @@ -77,29 +166,51 @@ The following bundled gems are added. The following bundled gems are updated. * minitest 5.25.5 -* test-unit 3.6.8 +* rake 13.3.0 +* test-unit 3.7.0 * rexml 3.4.1 -* net-imap 0.5.8 +* net-imap 0.5.9 * net-smtp 0.5.1 -* rbs 3.9.3 -* bigdecimal 3.1.9 +* matrix 0.4.3 +* prime 0.1.4 +* rbs 3.9.4 +* debug 1.11.0 +* base64 0.3.0 +* bigdecimal 3.2.2 +* drb 2.2.3 * syslog 0.3.0 -* csv 3.3.4 +* csv 3.3.5 * repl_type_completor 0.1.11 ## Supported platforms ## Compatibility issues +* The following methods were removed from Ractor due because of `Ractor::Port`: + + * `Ractor.yield` + * `Ractor#take` + * `Ractor#close_incoming` + * `Ractor#close_outgoging` + + [[Feature #21262]] + ## Stdlib compatibility issues * CGI library is removed from the default gems. Now we only provide `cgi/escape` for the following methods: - * `CGI.escape` and `CGI.unescape` - * `CGI.escapeHTML` and `CGI.unescapeHTML` - * `CGI.escapeURIComponent` and `CGI.unescapeURIComponent` - * `CGI.escapeElement` and `CGI.unescapeElement` - [[Feature #21258]] + + * `CGI.escape` and `CGI.unescape` + * `CGI.escapeHTML` and `CGI.unescapeHTML` + * `CGI.escapeURIComponent` and `CGI.unescapeURIComponent` + * `CGI.escapeElement` and `CGI.unescapeElement` + + [[Feature #21258]] + +* With the move of `Set` from stdlib to core class, `set/sorted_set.rb` has + been removed, and `SortedSet` is no longer an autoloaded constant. Please + install the `sorted_set` gem and `require 'sorted_set'` to use `SortedSet`. + [[Feature #21287]] ## C API updates @@ -110,7 +221,7 @@ The following bundled gems are updated. using `RUBY_IO_MODE_EXTERNAL` and use `rb_io_close(io)` to close it (this also interrupts and waits for all pending operations on the `IO` instance). Directly closing file descriptors does not interrupt pending - operations, and may lead to undefined beahviour. In other words, if two + operations, and may lead to undefined behaviour. In other words, if two `IO` objects share the same file descriptor, closing one does not affect the other. [[Feature #18455]] @@ -118,11 +229,17 @@ The following bundled gems are updated. ## JIT +[Feature #17473]: https://bugs.ruby-lang.org/issues/17473 [Feature #18455]: https://bugs.ruby-lang.org/issues/18455 [Feature #19908]: https://bugs.ruby-lang.org/issues/19908 [Feature #20610]: https://bugs.ruby-lang.org/issues/20610 [Feature #20724]: https://bugs.ruby-lang.org/issues/20724 [Feature #21047]: https://bugs.ruby-lang.org/issues/21047 [Bug #21049]: https://bugs.ruby-lang.org/issues/21049 +[Feature #21166]: https://bugs.ruby-lang.org/issues/21166 [Feature #21216]: https://bugs.ruby-lang.org/issues/21216 +[Feature #21219]: https://bugs.ruby-lang.org/issues/21219 [Feature #21258]: https://bugs.ruby-lang.org/issues/21258 +[Feature #21262]: https://bugs.ruby-lang.org/issues/21262 +[Feature #21287]: https://bugs.ruby-lang.org/issues/21287 +[Feature #21347]: https://bugs.ruby-lang.org/issues/21347 diff --git a/array.c b/array.c index 89a958568d..f485223e34 100644 --- a/array.c +++ b/array.c @@ -3439,10 +3439,9 @@ rb_ary_sort_bang(VALUE ary) ARY_SET_CAPA(ary, ARY_HEAP_LEN(tmp)); } /* tmp was lost ownership for the ptr */ - FL_UNSET(tmp, FL_FREEZE); FL_SET_EMBED(tmp); ARY_SET_EMBED_LEN(tmp, 0); - FL_SET(tmp, FL_FREEZE); + OBJ_FREEZE(tmp); } /* tmp will be GC'ed. */ RBASIC_SET_CLASS_RAW(tmp, rb_cArray); /* rb_cArray must be marked */ diff --git a/ast.c b/ast.c index b98fba6fab..dde42e5921 100644 --- a/ast.c +++ b/ast.c @@ -812,6 +812,16 @@ node_locations(VALUE ast_value, const NODE *node) location_new(&RNODE_CLASS(node)->class_keyword_loc), location_new(&RNODE_CLASS(node)->inheritance_operator_loc), location_new(&RNODE_CLASS(node)->end_keyword_loc)); + case NODE_COLON2: + return rb_ary_new_from_args(3, + location_new(nd_code_loc(node)), + location_new(&RNODE_COLON2(node)->delimiter_loc), + location_new(&RNODE_COLON2(node)->name_loc)); + case NODE_COLON3: + return rb_ary_new_from_args(3, + location_new(nd_code_loc(node)), + location_new(&RNODE_COLON3(node)->delimiter_loc), + location_new(&RNODE_COLON3(node)->name_loc)); case NODE_DOT2: return rb_ary_new_from_args(2, location_new(nd_code_loc(node)), diff --git a/benchmark/README.md b/benchmark/README.md index c5c29d0daf..9f9192685e 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -40,7 +40,7 @@ Usage: benchmark-driver [options] RUBY|YAML... --filter REGEXP Filter out benchmarks with given regexp --run-duration SECONDS Warmup estimates loop_count to run for this duration (default: 3) --timeout SECONDS Timeout ruby command execution with timeout(1) - -v, --verbose Verbose mode. Multiple -v options increase visilibity (max: 2) + -v, --verbose Verbose mode. Multiple -v options increase visibility (max: 2) ``` ## make benchmark diff --git a/benchmark/class_superclass.yml b/benchmark/class_superclass.yml new file mode 100644 index 0000000000..847ff811f1 --- /dev/null +++ b/benchmark/class_superclass.yml @@ -0,0 +1,23 @@ +prelude: | + class SimpleClass; end + class OneModuleClass + 1.times { include Module.new } + end + class MediumClass + 10.times { include Module.new } + end + class LargeClass + 100.times { include Module.new } + end +benchmark: + object_class_superclass: | + Object.superclass + simple_class_superclass: | + SimpleClass.superclass + one_module_class: | + OneModuleClass.superclass + medium_class_superclass: | + MediumClass.superclass + large_class_superclass: | + LargeClass.superclass +loop_count: 20000000 diff --git a/benchmark/io_close.yml b/benchmark/io_close.yml new file mode 100644 index 0000000000..a552872884 --- /dev/null +++ b/benchmark/io_close.yml @@ -0,0 +1,13 @@ +prelude: | + ios = 1000.times.map do + 100.times.map{IO.pipe} + end +benchmark: + # Close IO + io_close: | + # Process each batch of ios per iteration of the benchmark. + ios.pop.each do |r, w| + r.close + w.close + end +loop_count: 100 diff --git a/benchmark/io_close_contended.yml b/benchmark/io_close_contended.yml new file mode 100644 index 0000000000..1d9e4e0d0f --- /dev/null +++ b/benchmark/io_close_contended.yml @@ -0,0 +1,21 @@ +prelude: | + ios = 100.times.map do + 10.times.map do + pipe = IO.pipe.tap do |r, w| + Thread.new do + r.read + rescue IOError + # Ignore + end + end + end + end +benchmark: + # Close IO + io_close_contended: | + # Process each batch of ios per iteration of the benchmark. + ios.pop.each do |r, w| + r.close + w.close + end +loop_count: 10 diff --git a/benchmark/lib/benchmark_driver/runner/ractor.rb b/benchmark/lib/benchmark_driver/runner/ractor.rb index c730b8e4a5..fd9c2dd4db 100644 --- a/benchmark/lib/benchmark_driver/runner/ractor.rb +++ b/benchmark/lib/benchmark_driver/runner/ractor.rb @@ -87,7 +87,7 @@ __bmdv_ractors << Ractor.new(__bmdv_loop_after - __bmdv_loop_before) { |__bmdv_l <% end %> # Wait for all Ractors before executing code to write results -__bmdv_ractors.map!(&:take) +__bmdv_ractors.map!(&:value) <% results.each do |result| %> File.write(<%= result.dump %>, __bmdv_ractors.shift) diff --git a/benchmark/nilclass.yml b/benchmark/nilclass.yml index da66e71068..66234c4cdf 100644 --- a/benchmark/nilclass.yml +++ b/benchmark/nilclass.yml @@ -1,10 +1,16 @@ prelude: | def a = nil benchmark: + rationalize: + nil.rationalize + to_c: | + nil.to_c to_i: | nil.to_i to_f: | nil.to_f + to_r: | + nil.to_r splat: | a(*nil) loop_count: 100000 diff --git a/bootstraptest/runner.rb b/bootstraptest/runner.rb index 16bfdd9ea2..a8e67f3496 100755 --- a/bootstraptest/runner.rb +++ b/bootstraptest/runner.rb @@ -625,6 +625,8 @@ class Assertion < Struct.new(:src, :path, :lineno, :proc) end end + class Timeout < StandardError; end + def get_result_string(opt = '', timeout: BT.timeout, **argh) if BT.ruby timeout = BT.apply_timeout_scale(timeout) @@ -634,7 +636,11 @@ class Assertion < Struct.new(:src, :path, :lineno, :proc) out = IO.popen("#{BT.ruby} -W0 #{opt} #{filename}", **kw) pid = out.pid th = Thread.new {out.read.tap {Process.waitpid(pid); out.close}} - th.value if th.join(timeout) + if th.join(timeout) + th.value + else + Timeout.new("timed out after #{timeout} seconds") + end ensure raise Interrupt if $? and $?.signaled? && $?.termsig == Signal.list["INT"] @@ -891,4 +897,8 @@ def yjit_enabled? ENV.key?('RUBY_YJIT_ENABLE') || ENV.fetch('RUN_OPTS', '').include?('yjit') || BT.ruby.include?('yjit') end +def zjit_enabled? + ENV.key?('RUBY_ZJIT_ENABLE') || ENV.fetch('RUN_OPTS', '').include?('zjit') || BT.ruby.include?('zjit') +end + exit main diff --git a/bootstraptest/test_fiber.rb b/bootstraptest/test_fiber.rb index 2614dd13bf..ae809a5936 100644 --- a/bootstraptest/test_fiber.rb +++ b/bootstraptest/test_fiber.rb @@ -37,3 +37,8 @@ assert_normal_exit %q{ assert_normal_exit %q{ Fiber.new(&Object.method(:class_eval)).resume("foo") }, '[ruby-dev:34128]' + +# [Bug #21400] +assert_normal_exit %q{ + Thread.new { Fiber.current.kill }.join +} diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 914807246c..1c2a4b0b8e 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -67,7 +67,7 @@ assert_equal "#", %q{ # Return id, loc, and status for no-name ractor assert_match /^#$/, %q{ r = Ractor.new { '' } - r.take + r.join sleep 0.1 until r.inspect =~ /terminated/ r.inspect } @@ -75,7 +75,7 @@ assert_match /^#$/, %q{ # Return id, name, loc, and status for named ractor assert_match /^#$/, %q{ r = Ractor.new(name: 'Test Ractor') { '' } - r.take + r.join sleep 0.1 until r.inspect =~ /terminated/ r.inspect } @@ -86,7 +86,7 @@ assert_equal 'ok', %q{ r = Ractor.new do 'ok' end - r.take + r.value } # Passed arguments to Ractor.new will be a block parameter @@ -96,7 +96,7 @@ assert_equal 'ok', %q{ r = Ractor.new 'ok' do |msg| msg end - r.take + r.value } # Pass multiple arguments to Ractor.new @@ -105,7 +105,7 @@ assert_equal 'ok', %q{ r = Ractor.new 'ping', 'pong' do |msg, msg2| [msg, msg2] end - 'ok' if r.take == ['ping', 'pong'] + 'ok' if r.value == ['ping', 'pong'] } # Ractor#send passes an object with copy to a Ractor @@ -115,65 +115,23 @@ assert_equal 'ok', %q{ msg = Ractor.receive end r.send 'ok' - r.take + r.value } # Ractor#receive_if can filter the message -assert_equal '[2, 3, 1]', %q{ - r = Ractor.new Ractor.current do |main| - main << 1 - main << 2 - main << 3 +assert_equal '[1, 2, 3]', %q{ + ports = 3.times.map{Ractor::Port.new} + + r = Ractor.new ports do |ports| + ports[0] << 3 + ports[1] << 1 + ports[2] << 2 end a = [] - a << Ractor.receive_if{|msg| msg == 2} - a << Ractor.receive_if{|msg| msg == 3} - a << Ractor.receive -} - -# Ractor#receive_if with break -assert_equal '[2, [1, :break], 3]', %q{ - r = Ractor.new Ractor.current do |main| - main << 1 - main << 2 - main << 3 - end - - a = [] - a << Ractor.receive_if{|msg| msg == 2} - a << Ractor.receive_if{|msg| break [msg, :break]} - a << Ractor.receive -} - -# Ractor#receive_if can't be called recursively -assert_equal '[[:e1, 1], [:e2, 2]]', %q{ - r = Ractor.new Ractor.current do |main| - main << 1 - main << 2 - main << 3 - end - - a = [] - - Ractor.receive_if do |msg| - begin - Ractor.receive - rescue Ractor::Error - a << [:e1, msg] - end - true # delete 1 from queue - end - - Ractor.receive_if do |msg| - begin - Ractor.receive_if{} - rescue Ractor::Error - a << [:e2, msg] - end - true # delete 2 from queue - end - - a # + a << ports[1].receive # 1 + a << ports[2].receive # 2 + a << ports[0].receive # 3 + a } # dtoa race condition @@ -184,7 +142,7 @@ assert_equal '[:ok, :ok, :ok]', %q{ 10_000.times{ rand.to_s } :ok } - }.map(&:take) + }.map(&:value) } # Ractor.make_shareable issue for locals in proc [Bug #18023] @@ -218,27 +176,32 @@ if ENV['GITHUB_WORKFLOW'] == 'Compilations' # ignore the follow else -# Ractor.select(*ractors) receives a values from a ractors. -# It is similar to select(2) and Go's select syntax. -# The return value is [ch, received_value] +# Ractor.select with a Ractor argument assert_equal 'ok', %q{ # select 1 r1 = Ractor.new{'r1'} - r, obj = Ractor.select(r1) - 'ok' if r == r1 and obj == 'r1' + port, obj = Ractor.select(r1) + if port == r1 and obj == 'r1' + 'ok' + else + # failed + [port, obj].inspect + end } # Ractor.select from two ractors. assert_equal '["r1", "r2"]', %q{ # select 2 - r1 = Ractor.new{'r1'} - r2 = Ractor.new{'r2'} - rs = [r1, r2] + p1 = Ractor::Port.new + p2 = Ractor::Port.new + r1 = Ractor.new(p1){|p1| p1 << 'r1'} + r2 = Ractor.new(p2){|p2| p2 << 'r2'} + ps = [p1, p2] as = [] - r, obj = Ractor.select(*rs) - rs.delete(r) + port, obj = Ractor.select(*ps) + ps.delete(port) as << obj - r, obj = Ractor.select(*rs) + port, obj = Ractor.select(*ps) as << obj as.sort #=> ["r1", "r2"] } @@ -282,30 +245,12 @@ assert_match /specify at least one ractor/, %q{ end } -# Outgoing port of a ractor will be closed when the Ractor is terminated. -assert_equal 'ok', %q{ - r = Ractor.new do - 'finish' - end - - r.take - sleep 0.1 until r.inspect =~ /terminated/ - - begin - o = r.take - rescue Ractor::ClosedError - 'ok' - else - "ng: #{o}" - end -} - # Raise Ractor::ClosedError when try to send into a terminated ractor assert_equal 'ok', %q{ r = Ractor.new do end - r.take # closed + r.join # closed sleep 0.1 until r.inspect =~ /terminated/ begin @@ -317,47 +262,16 @@ assert_equal 'ok', %q{ end } -# Raise Ractor::ClosedError when try to send into a closed actor -assert_equal 'ok', %q{ - r = Ractor.new { Ractor.receive } - r.close_incoming - - begin - r.send(1) - rescue Ractor::ClosedError - 'ok' - else - 'ng' - end -} - -# Raise Ractor::ClosedError when try to take from closed actor -assert_equal 'ok', %q{ - r = Ractor.new do - Ractor.yield 1 - Ractor.receive - end - - r.close_outgoing - begin - r.take - rescue Ractor::ClosedError - 'ok' - else - 'ng' - end -} - -# Can mix with Thread#interrupt and Ractor#take [Bug #17366] +# Can mix with Thread#interrupt and Ractor#join [Bug #17366] assert_equal 'err', %q{ - Ractor.new{ + Ractor.new do t = Thread.current begin Thread.new{ t.raise "err" }.join rescue => e e.message end - }.take + end.value } # Killed Ractor's thread yields nil @@ -365,34 +279,18 @@ assert_equal 'nil', %q{ Ractor.new{ t = Thread.current Thread.new{ t.kill }.join - }.take.inspect #=> nil + }.value.inspect #=> nil } -# Ractor.yield raises Ractor::ClosedError when outgoing port is closed. +# Raise Ractor::ClosedError when try to send into a ractor with closed default port assert_equal 'ok', %q{ - r = Ractor.new Ractor.current do |main| + r = Ractor.new { + Ractor.current.close + Ractor.main << :ok Ractor.receive - main << true - Ractor.yield 1 - end + } - r.close_outgoing - r << true - Ractor.receive - - begin - r.take - rescue Ractor::ClosedError - 'ok' - else - 'ng' - end -} - -# Raise Ractor::ClosedError when try to send into a ractor with closed incoming port -assert_equal 'ok', %q{ - r = Ractor.new { Ractor.receive } - r.close_incoming + Ractor.receive # wait for ok begin r.send(1) @@ -403,154 +301,44 @@ assert_equal 'ok', %q{ end } -# A ractor with closed incoming port still can send messages out -assert_equal '[1, 2]', %q{ - r = Ractor.new do - Ractor.yield 1 - 2 - end - r.close_incoming - - [r.take, r.take] -} - -# Raise Ractor::ClosedError when try to take from a ractor with closed outgoing port -assert_equal 'ok', %q{ - r = Ractor.new do - Ractor.yield 1 - Ractor.receive - end - - sleep 0.01 # wait for Ractor.yield in r - r.close_outgoing - begin - r.take - rescue Ractor::ClosedError - 'ok' - else - 'ng' - end -} - -# A ractor with closed outgoing port still can receive messages from incoming port -assert_equal 'ok', %q{ - r = Ractor.new do - Ractor.receive - end - - r.close_outgoing - begin - r.send(1) - rescue Ractor::ClosedError - 'ng' - else - 'ok' - end -} - # Ractor.main returns main ractor assert_equal 'true', %q{ Ractor.new{ Ractor.main - }.take == Ractor.current + }.value == Ractor.current } # a ractor with closed outgoing port should terminate assert_equal 'ok', %q{ Ractor.new do - close_outgoing + Ractor.current.close end true until Ractor.count == 1 :ok } -# multiple Ractors can receive (wait) from one Ractor -assert_equal '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]', %q{ - pipe = Ractor.new do - loop do - Ractor.yield Ractor.receive - end - end - - RN = 10 - rs = RN.times.map{|i| - Ractor.new pipe, i do |pipe, i| - msg = pipe.take - msg # ping-pong - end - } - RN.times{|i| - pipe << i - } - RN.times.map{ - r, n = Ractor.select(*rs) - rs.delete r - n - }.sort -} unless /mswin/ =~ RUBY_PLATFORM # randomly hangs on mswin https://github.com/ruby/ruby/actions/runs/3753871445/jobs/6377551069#step:20:131 - -# Ractor.select also support multiple take, receive and yield -assert_equal '[true, true, true]', %q{ - RN = 10 - CR = Ractor.current - - rs = (1..RN).map{ - Ractor.new do - CR.send 'send' + CR.take #=> 'sendyield' - 'take' - end - } - received = [] - taken = [] - yielded = [] - until received.size == RN && taken.size == RN && yielded.size == RN - r, v = Ractor.select(CR, *rs, yield_value: 'yield') - case r - when :receive - received << v - when :yield - yielded << v - else - taken << v - rs.delete r - end - end - r = [received == ['sendyield'] * RN, - yielded == [nil] * RN, - taken == ['take'] * RN, - ] - - STDERR.puts [received, yielded, taken].inspect - r -} - -# multiple Ractors can send to one Ractor -assert_equal '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]', %q{ - pipe = Ractor.new do - loop do - Ractor.yield Ractor.receive - end - end - - RN = 10 - RN.times.map{|i| - Ractor.new pipe, i do |pipe, i| - pipe << i - end - } - RN.times.map{ - pipe.take - }.sort -} - # an exception in a Ractor main thread will be re-raised at Ractor#receive assert_equal '[RuntimeError, "ok", true]', %q{ r = Ractor.new do raise 'ok' # exception will be transferred receiver end begin - r.take + r.join + rescue Ractor::RemoteError => e + [e.cause.class, #=> RuntimeError + e.cause.message, #=> 'ok' + e.ractor == r] #=> true + end +} + +# an exception in a Ractor will be re-raised at Ractor#value +assert_equal '[RuntimeError, "ok", true]', %q{ + r = Ractor.new do + raise 'ok' # exception will be transferred receiver + end + begin + r.value rescue Ractor::RemoteError => e [e.cause.class, #=> RuntimeError e.cause.message, #=> 'ok' @@ -567,7 +355,33 @@ assert_equal 'ok', %q{ sleep 0.1 'ok' end - r.take + r.value +} + +# SystemExit from a Ractor is re-raised +# [Bug #21505] +assert_equal '[SystemExit, "exit", true]', %q{ + r = Ractor.new { exit } + begin + r.value + rescue Ractor::RemoteError => e + [e.cause.class, #=> RuntimeError + e.cause.message, #=> 'ok' + e.ractor == r] #=> true + end +} + +# SystemExit from a Thread inside a Ractor is re-raised +# [Bug #21505] +assert_equal '[SystemExit, "exit", true]', %q{ + r = Ractor.new { Thread.new { exit }.join } + begin + r.value + rescue Ractor::RemoteError => e + [e.cause.class, #=> RuntimeError + e.cause.message, #=> 'ok' + e.ractor == r] #=> true + end } # threads in a ractor will killed @@ -601,7 +415,7 @@ assert_equal '{ok: 3}', %q{ end 3.times.map{Ractor.receive}.tally -} unless yjit_enabled? # `[BUG] Bus Error at 0x000000010b7002d0` in jit_exec() +} unless yjit_enabled? || zjit_enabled? # YJIT: `[BUG] Bus Error at 0x000000010b7002d0` in jit_exec(), ZJIT hangs # unshareable object are copied assert_equal 'false', %q{ @@ -610,7 +424,7 @@ assert_equal 'false', %q{ msg.object_id end - obj.object_id == r.take + obj.object_id == r.value } # To copy the object, now Marshal#dump is used @@ -629,10 +443,11 @@ assert_equal "allocator undefined for Thread", %q{ # send shareable and unshareable objects assert_equal "ok", <<~'RUBY', frozen_string_literal: false - echo_ractor = Ractor.new do + port = Ractor::Port.new + echo_ractor = Ractor.new port do |port| loop do v = Ractor.receive - Ractor.yield v + port << v end end @@ -680,13 +495,13 @@ assert_equal "ok", <<~'RUBY', frozen_string_literal: false shareable_objects.map{|o| echo_ractor << o - o2 = echo_ractor.take + o2 = port.receive results << "#{o} is copied" unless o.object_id == o2.object_id } unshareable_objects.map{|o| echo_ractor << o - o2 = echo_ractor.take + o2 = port.receive results << "#{o.inspect} is not copied" if o.object_id == o2.object_id } @@ -712,7 +527,7 @@ assert_equal [false, true, false].inspect, <<~'RUBY', frozen_string_literal: fal def check obj1 obj2 = Ractor.new obj1 do |obj| obj - end.take + end.value obj1.object_id == obj2.object_id end @@ -734,7 +549,7 @@ assert_equal 'hello world', <<~'RUBY', frozen_string_literal: false str = 'hello' r.send str, move: true - modified = r.take + modified = r.value begin str << ' exception' # raise Ractor::MovedError @@ -754,7 +569,7 @@ assert_equal '[0, 1]', %q{ a1 = [0] r.send a1, move: true - a2 = r.take + a2 = r.value begin a1 << 2 # raise Ractor::MovedError rescue Ractor::MovedError @@ -764,55 +579,13 @@ assert_equal '[0, 1]', %q{ # unshareable frozen objects should still be frozen in new ractor after move assert_equal 'true', %q{ -r = Ractor.new do - obj = receive - { frozen: obj.frozen? } -end -obj = [Object.new].freeze -r.send(obj, move: true) -r.take[:frozen] -} - -# move with yield -assert_equal 'hello', %q{ r = Ractor.new do - Thread.current.report_on_exception = false - obj = 'hello' - Ractor.yield obj, move: true - obj << 'world' + obj = receive + { frozen: obj.frozen? } end - - str = r.take - begin - r.take - rescue Ractor::RemoteError - str #=> "hello" - end -} - -# yield/move should not make moved object when the yield is not succeeded -assert_equal '"str"', %q{ - R = Ractor.new{} - M = Ractor.current - r = Ractor.new do - s = 'str' - selected_r, v = Ractor.select R, yield_value: s, move: true - raise if selected_r != R # taken from R - M.send s.inspect # s should not be a moved object - end - - Ractor.receive -} - -# yield/move can fail -assert_equal "allocator undefined for Thread", %q{ - r = Ractor.new do - obj = Thread.new{} - Ractor.yield obj - rescue => e - e.message - end - r.take + obj = [Object.new].freeze + r.send(obj, move: true) + r.value[:frozen] } # Access to global-variables are prohibited @@ -823,7 +596,7 @@ assert_equal 'can not access global variables $gv from non-main Ractors', %q{ end begin - r.take + r.join rescue Ractor::RemoteError => e e.cause.message end @@ -836,7 +609,7 @@ assert_equal 'can not access global variables $gv from non-main Ractors', %q{ end begin - r.take + r.join rescue Ractor::RemoteError => e e.cause.message end @@ -850,7 +623,7 @@ assert_equal 'ok', %q{ } end - [$stdin, $stdout, $stderr].zip(r.take){|io, (oid, fno)| + [$stdin, $stdout, $stderr].zip(r.value){|io, (oid, fno)| raise "should not be different object" if io.object_id == oid raise "fd should be same" unless io.fileno == fno } @@ -866,7 +639,7 @@ assert_equal 'ok', %q{ 'ok' end - r.take + r.value } # $DEBUG, $VERBOSE are Ractor local @@ -924,7 +697,7 @@ assert_equal 'true', %q{ h = Ractor.new do ractor_local_globals - end.take + end.value ractor_local_globals == h #=> true } @@ -933,7 +706,8 @@ assert_equal 'false', %q{ r = Ractor.new do self.object_id end - r.take == self.object_id #=> false + ret = r.value + ret == self.object_id } # self is a Ractor instance @@ -941,7 +715,12 @@ assert_equal 'true', %q{ r = Ractor.new do self.object_id end - r.object_id == r.take #=> true + ret = r.value + if r.object_id == ret #=> true + true + else + raise [ret, r.object_id].inspect + end } # given block Proc will be isolated, so can not access outer variables. @@ -969,7 +748,7 @@ assert_equal "can not get unshareable values from instance variables of classes/ end begin - r.take + r.value rescue Ractor::RemoteError => e e.cause.message end @@ -985,7 +764,7 @@ assert_equal 'can not access instance variables of shareable objects from non-ma end begin - r.take + r.value rescue Ractor::RemoteError => e e.cause.message end @@ -1011,7 +790,7 @@ assert_equal 'can not access instance variables of shareable objects from non-ma end begin - r.take + r.value rescue Ractor::RemoteError => e e.cause.message end @@ -1032,7 +811,7 @@ assert_equal 'can not access instance variables of shareable objects from non-ma end begin - r.take + r.value rescue Ractor::RemoteError => e e.cause.message end @@ -1046,7 +825,7 @@ assert_equal '11', %q{ Ractor.new obj do |obj| obj.instance_variable_get('@a') - end.take.to_s + end.value.to_s }.join } @@ -1072,25 +851,25 @@ assert_equal '333', %q{ def self.fstr = @fstr end - a = Ractor.new{ C.int }.take + a = Ractor.new{ C.int }.value b = Ractor.new do C.str.to_i rescue Ractor::IsolationError 10 - end.take + end.value c = Ractor.new do C.fstr.to_i - end.take + end.value - d = Ractor.new{ M.int }.take + d = Ractor.new{ M.int }.value e = Ractor.new do M.str.to_i rescue Ractor::IsolationError 20 - end.take + end.value f = Ractor.new do M.fstr.to_i - end.take + end.value # 1 + 10 + 100 + 2 + 20 + 200 @@ -1108,28 +887,28 @@ assert_equal '["instance-variable", "instance-variable", nil]', %q{ Ractor.new{ [C.iv1, C.iv2, C.iv3] - }.take + }.value } # moved objects have their shape properly set to original object's shape assert_equal '1234', %q{ -class Obj - attr_accessor :a, :b, :c, :d - def initialize - @a = 1 - @b = 2 - @c = 3 + class Obj + attr_accessor :a, :b, :c, :d + def initialize + @a = 1 + @b = 2 + @c = 3 + end end -end -r = Ractor.new do - obj = receive - obj.d = 4 - [obj.a, obj.b, obj.c, obj.d] -end -obj = Obj.new -r.send(obj, move: true) -values = r.take -values.join + r = Ractor.new do + obj = receive + obj.d = 4 + [obj.a, obj.b, obj.c, obj.d] + end + obj = Obj.new + r.send(obj, move: true) + values = r.value + values.join } # cvar in shareable-objects are not allowed to access from non-main Ractor @@ -1145,7 +924,7 @@ assert_equal 'can not access class variables from non-main Ractors', %q{ end begin - r.take + r.join rescue Ractor::RemoteError => e e.cause.message end @@ -1167,7 +946,7 @@ assert_equal 'can not access class variables from non-main Ractors', %q{ end begin - r.take + r.join rescue Ractor::RemoteError => e e.cause.message end @@ -1182,7 +961,7 @@ assert_equal 'can not access non-shareable objects in constant C::CONST by non-m C::CONST end begin - r.take + r.join rescue Ractor::RemoteError => e e.cause.message end @@ -1194,7 +973,7 @@ assert_equal "can not access non-shareable objects in constant Object::STR by no def str; STR; end s = str() # fill const cache begin - Ractor.new{ str() }.take + Ractor.new{ str() }.join rescue Ractor::RemoteError => e e.cause.message end @@ -1208,7 +987,7 @@ assert_equal 'can not set constants with non-shareable objects by non-main Racto C::CONST = 'str' end begin - r.take + r.join rescue Ractor::RemoteError => e e.cause.message end @@ -1219,7 +998,7 @@ assert_equal "defined with an un-shareable Proc in a different Ractor", %q{ str = "foo" define_method(:buggy){|i| str << "#{i}"} begin - Ractor.new{buggy(10)}.take + Ractor.new{buggy(10)}.join rescue => e e.cause.message end @@ -1230,7 +1009,7 @@ assert_equal '[1000, 3]', %q{ A = Array.new(1000).freeze # [nil, ...] H = {a: 1, b: 2, c: 3}.freeze - Ractor.new{ [A.size, H.size] }.take + Ractor.new{ [A.size, H.size] }.value } # Ractor.count @@ -1240,15 +1019,15 @@ assert_equal '[1, 4, 3, 2, 1]', %q{ ractors = (1..3).map { Ractor.new { Ractor.receive } } counts << Ractor.count - ractors[0].send('End 0').take + ractors[0].send('End 0').join sleep 0.1 until ractors[0].inspect =~ /terminated/ counts << Ractor.count - ractors[1].send('End 1').take + ractors[1].send('End 1').join sleep 0.1 until ractors[1].inspect =~ /terminated/ counts << Ractor.count - ractors[2].send('End 2').take + ractors[2].send('End 2').join sleep 0.1 until ractors[2].inspect =~ /terminated/ counts << Ractor.count @@ -1261,7 +1040,7 @@ assert_equal '0', %q{ n = 0 ObjectSpace.each_object{|o| n += 1 unless Ractor.shareable?(o)} n - }.take + }.value } # ObjectSpace._id2ref can not handle unshareable objects with Ractors @@ -1274,7 +1053,7 @@ assert_equal 'ok', <<~'RUBY', frozen_string_literal: false rescue => e :ok end - end.take + end.value RUBY # Ractor.make_shareable(obj) @@ -1446,7 +1225,7 @@ assert_equal '1', %q{ a = 2 end - Ractor.new{ C.new.foo }.take + Ractor.new{ C.new.foo }.value } # Ractor.make_shareable(a_proc) makes a proc shareable. @@ -1489,7 +1268,7 @@ assert_equal '[6, 10]', %q{ Ractor.new{ # line 5 a = 1 b = 2 - }.take + }.value c = 3 # line 9 end rs @@ -1499,7 +1278,7 @@ assert_equal '[6, 10]', %q{ assert_equal '[true, false]', %q{ Ractor.new([[]].freeze) { |ary| [ary.frozen?, ary.first.frozen? ] - }.take + }.value } # Ractor deep copies frozen objects (str) @@ -1507,7 +1286,7 @@ assert_equal '[true, false]', %q{ s = String.new.instance_eval { @x = []; freeze} Ractor.new(s) { |s| [s.frozen?, s.instance_variable_get(:@x).frozen?] - }.take + }.value } # Can not trap with not isolated Proc on non-main ractor @@ -1515,14 +1294,14 @@ assert_equal '[:ok, :ok]', %q{ a = [] Ractor.new{ trap(:INT){p :ok} - }.take + }.join a << :ok begin Ractor.new{ s = 'str' trap(:INT){p s} - }.take + }.join rescue => Ractor::RemoteError a << :ok end @@ -1552,12 +1331,12 @@ assert_equal '[nil, "b", "a"]', %q{ ans = [] Ractor.current[:key] = 'a' r = Ractor.new{ - Ractor.yield self[:key] + Ractor.main << self[:key] self[:key] = 'b' self[:key] } - ans << r.take - ans << r.take + ans << Ractor.receive + ans << r.value ans << Ractor.current[:key] } @@ -1573,7 +1352,7 @@ assert_equal '1', %q{ } }.each(&:join) a.uniq.size - }.take + }.value } # Ractor-local storage @@ -1591,7 +1370,7 @@ assert_equal '2', %q{ fails += 1 if e.message =~ /Cannot set ractor local/ end fails - }.take + }.value } ### @@ -1607,7 +1386,7 @@ assert_equal "#{N}#{N}", %Q{ Ractor.new{ N.times{|i| -(i.to_s)} } - }.map{|r| r.take}.join + }.map{|r| r.value}.join } assert_equal "ok", %Q{ @@ -1616,7 +1395,7 @@ assert_equal "ok", %Q{ Ractor.new{ N.times.map{|i| -(i.to_s)} } - }.map{|r| r.take} + }.map{|r| r.value} N.times do |i| unless a[i].equal?(b[i]) raise [a[i], b[i]].inspect @@ -1638,7 +1417,7 @@ assert_equal "#{n}#{n}", %Q{ obj.instance_variable_defined?("@a") end end - }.map{|r| r.take}.join + }.map{|r| r.value}.join } # NameError @@ -1670,16 +1449,17 @@ assert_equal "ok", %q{ # Can yield back values while GC is sweeping [Bug #18117] assert_equal "ok", %q{ + port = Ractor::Port.new workers = (0...8).map do - Ractor.new do + Ractor.new port do |port| loop do 10_000.times.map { Object.new } - Ractor.yield Time.now + port << Time.now end end end - 1_000.times { idle_worker, tmp_reporter = Ractor.select(*workers) } + 1_000.times { port.receive } "ok" } if !yjit_enabled? && ENV['GITHUB_WORKFLOW'] != 'ModGC' # flaky @@ -1782,14 +1562,14 @@ assert_equal 'true', %q{ } n = CS.inject(1){|r, c| r * c.foo} * LN - rs.map{|r| r.take} == Array.new(RN){n} + rs.map{|r| r.value} == Array.new(RN){n} } # check experimental warning assert_match /\Atest_ractor\.rb:1:\s+warning:\s+Ractor is experimental/, %q{ Warning[:experimental] = $VERBOSE = true STDERR.reopen(STDOUT) - eval("Ractor.new{}.take", nil, "test_ractor.rb", 1) + eval("Ractor.new{}.value", nil, "test_ractor.rb", 1) }, frozen_string_literal: false # check moved object @@ -1807,7 +1587,7 @@ assert_equal 'ok', %q{ end r.send obj, move: true - r.take + r.value } ## Ractor::Selector @@ -1883,10 +1663,11 @@ assert_equal '600', %q{ RN = 100 s = Ractor::Selector.new + port = Ractor::Port.new rs = RN.times.map{ Ractor.new{ - Ractor.main << Ractor.new{ Ractor.yield :v3; :v4 } - Ractor.main << Ractor.new{ Ractor.yield :v5; :v6 } + Ractor.main << Ractor.new(port){|port| port << :v3; :v4 } + Ractor.main << Ractor.new(port){|port| port << :v5; :v6 } Ractor.yield :v1 :v2 } @@ -1952,7 +1733,7 @@ assert_equal 'true', %q{ # prism parser with -O0 build consumes a lot of machine stack Data.define(:fileno).new(1) end - }.take.fileno > 0 + }.value.fileno > 0 } # require_relative in Ractor @@ -1970,7 +1751,7 @@ assert_equal 'true', %q{ begin Ractor.new dummyfile do |f| require_relative File.basename(f) - end.take + end.value ensure File.unlink dummyfile end @@ -1987,7 +1768,7 @@ assert_equal 'LoadError', %q{ rescue LoadError => e e.class end - end.take + end.value } # autolaod in Ractor @@ -2002,7 +1783,7 @@ assert_equal 'true', %q{ Data.define(:fileno).new(1) end end - r.take.fileno > 0 + r.value.fileno > 0 } # failed in autolaod in Ractor @@ -2017,7 +1798,7 @@ assert_equal 'LoadError', %q{ e.class end end - r.take + r.value } # bind_call in Ractor [Bug #20934] @@ -2028,7 +1809,7 @@ assert_equal 'ok', %q{ Object.instance_method(:itself).bind_call(self) end end - end.each(&:take) + end.each(&:join) GC.start :ok.itself } @@ -2038,7 +1819,7 @@ assert_equal 'ok', %q{ ractor = Ractor.new { Ractor.receive } obj = "foobarbazfoobarbazfoobarbazfoobarbaz" ractor.send(obj.dup, move: true) - roundtripped_obj = ractor.take + roundtripped_obj = ractor.value roundtripped_obj == obj ? :ok : roundtripped_obj } @@ -2047,7 +1828,7 @@ assert_equal 'ok', %q{ ractor = Ractor.new { Ractor.receive } obj = Array.new(10, 42) ractor.send(obj.dup, move: true) - roundtripped_obj = ractor.take + roundtripped_obj = ractor.value roundtripped_obj == obj ? :ok : roundtripped_obj } @@ -2056,7 +1837,7 @@ assert_equal 'ok', %q{ ractor = Ractor.new { Ractor.receive } obj = { foo: 1, bar: 2 } ractor.send(obj.dup, move: true) - roundtripped_obj = ractor.take + roundtripped_obj = ractor.value roundtripped_obj == obj ? :ok : roundtripped_obj } @@ -2065,7 +1846,7 @@ assert_equal 'ok', %q{ ractor = Ractor.new { Ractor.receive } obj = "foo".match(/o/) ractor.send(obj.dup, move: true) - roundtripped_obj = ractor.take + roundtripped_obj = ractor.value roundtripped_obj == obj ? :ok : roundtripped_obj } @@ -2074,7 +1855,7 @@ assert_equal 'ok', %q{ ractor = Ractor.new { Ractor.receive } obj = Struct.new(:a, :b, :c, :d, :e, :f).new(1, 2, 3, 4, 5, 6) ractor.send(obj.dup, move: true) - roundtripped_obj = ractor.take + roundtripped_obj = ractor.value roundtripped_obj == obj ? :ok : roundtripped_obj } @@ -2101,7 +1882,7 @@ assert_equal 'ok', %q{ obj = SomeObject.new ractor.send(obj.dup, move: true) - roundtripped_obj = ractor.take + roundtripped_obj = ractor.value roundtripped_obj == obj ? :ok : roundtripped_obj } @@ -2153,7 +1934,7 @@ assert_equal 'ok', %q{ obj = Array.new(10, 42) original = obj.dup ractor.send([obj].freeze, move: true) - roundtripped_obj = ractor.take[0] + roundtripped_obj = ractor.value[0] roundtripped_obj == original ? :ok : roundtripped_obj } @@ -2164,10 +1945,64 @@ assert_equal 'ok', %q{ obj.instance_variable_set(:@array, [1]) ractor.send(obj, move: true) - roundtripped_obj = ractor.take + roundtripped_obj = ractor.value roundtripped_obj.instance_variable_get(:@array) == [1] ? :ok : roundtripped_obj } +# move object with many generic ivars +assert_equal 'ok', %q{ + ractor = Ractor.new { Ractor.receive } + obj = Array.new(10, 42) + 0.upto(300) do |i| + obj.instance_variable_set(:"@array#{i}", [i]) + end + + ractor.send(obj, move: true) + roundtripped_obj = ractor.value + roundtripped_obj.instance_variable_get(:@array1) == [1] ? :ok : roundtripped_obj +} + +# move object with complex generic ivars +assert_equal 'ok', %q{ + # Make Array too_complex + 30.times { |i| [].instance_variable_set(:"@complex#{i}", 1) } + + ractor = Ractor.new { Ractor.receive } + obj = Array.new(10, 42) + obj.instance_variable_set(:@array1, [1]) + + ractor.send(obj, move: true) + roundtripped_obj = ractor.value + roundtripped_obj.instance_variable_get(:@array1) == [1] ? :ok : roundtripped_obj +} + +# copy object with complex generic ivars +assert_equal 'ok', %q{ + # Make Array too_complex + 30.times { |i| [].instance_variable_set(:"@complex#{i}", 1) } + + ractor = Ractor.new { Ractor.receive } + obj = Array.new(10, 42) + obj.instance_variable_set(:@array1, [1]) + + ractor.send(obj) + roundtripped_obj = ractor.value + roundtripped_obj.instance_variable_get(:@array1) == [1] ? :ok : roundtripped_obj +} + +# copy object with many generic ivars +assert_equal 'ok', %q{ + ractor = Ractor.new { Ractor.receive } + obj = Array.new(10, 42) + 0.upto(300) do |i| + obj.instance_variable_set(:"@array#{i}", [i]) + end + + ractor.send(obj) + roundtripped_obj = ractor.value + roundtripped_obj.instance_variable_get(:@array1) == [1] ? :ok : roundtripped_obj +} + # moved composite types move their non-shareable parts properly assert_equal 'ok', %q{ k, v = String.new("key"), String.new("value") @@ -2188,7 +2023,9 @@ assert_equal 'ok', %q{ struct_class = Struct.new(:a) struct = struct_class.new(String.new('a')) o = MyObject.new(String.new('a')) - r = Ractor.new do + port = Ractor::Port.new + + r = Ractor.new port do |port| loop do obj = Ractor.receive val = case obj @@ -2201,7 +2038,7 @@ assert_equal 'ok', %q{ when Object obj.a == 'a' end - Ractor.yield val + port << val end end @@ -2218,7 +2055,7 @@ assert_equal 'ok', %q{ parts_moved[klass] = [obj.a] end r.send(obj, move: true) - val = r.take + val = port.receive if val != true raise "bad val in ractor for obj at i:#{i}" end @@ -2258,13 +2095,11 @@ begin r = Ractor.new { Ractor.receive } _, status = Process.waitpid2 fork { begin - r.take - raise "ng" - rescue Ractor::ClosedError + raise if r.value != nil end } r.send(123) - raise unless r.take == 123 + raise unless r.value == 123 status.success? ? "ok" : status rescue NotImplementedError :ok @@ -2278,12 +2113,11 @@ begin _, status = Process.waitpid2 fork { begin r.send(123) - raise "ng" rescue Ractor::ClosedError end } r.send(123) - raise unless r.take == 123 + raise unless r.value == 123 status.success? ? "ok" : status rescue NotImplementedError :ok @@ -2293,110 +2127,183 @@ end # Creating classes inside of Ractors # [Bug #18119] assert_equal 'ok', %q{ + port = Ractor::Port.new workers = (0...8).map do - Ractor.new do + Ractor.new port do |port| loop do 100.times.map { Class.new } - Ractor.yield nil + port << nil end end end - 100.times { Ractor.select(*workers) } + 100.times { port.receive } 'ok' } -# There are some bugs in Windows with multiple threads in same ractor calling ractor actions -# Ex: https://github.com/ruby/ruby/actions/runs/14998660285/job/42139383905 -unless /mswin/ =~ RUBY_PLATFORM - # r.send and r.take from multiple threads - # [Bug #21037] - assert_equal '[true, true]', %q{ - class Map - def initialize - @r = Ractor.new { - loop do - key = Ractor.receive - Ractor.yield key - end - } - end +# Using Symbol#to_proc inside ractors +# [Bug #21354] +assert_equal 'ok', %q{ + :inspect.to_proc + Ractor.new do + # It should not use this cached proc, it should create a new one. If it used + # the cached proc, we would get a ractor_confirm_belonging error here. + :inspect.to_proc + end.join + 'ok' +} - def fetch(key) - @r.send key - @r.take - end +# take vm lock when deleting generic ivars from the global table +assert_equal 'ok', %q{ + Ractor.new do + a = [1, 2, 3] + a.object_id + a.dup # this deletes generic ivar on dupped object + 'ok' + end.value +} + +## Ractor#monitor + +# monitor port returns `:exited` when the monitering Ractor terminated. +assert_equal 'true', %q{ + r = Ractor.new do + Ractor.main << :ok1 + :ok2 end - tm = Map.new - t1 = Thread.new { 10.times.map { tm.fetch("t1") } } - t2 = Thread.new { 10.times.map { tm.fetch("t2") } } - vals = t1.value + t2.value - [ - vals.first(10).all? { |v| v == "t1" }, - vals.last(10).all? { |v| v == "t2" } - ] - } + r.monitor port = Ractor::Port.new + Ractor.receive # :ok1 + port.receive == :exited +} - # r.send and Ractor.select from multiple threads - assert_equal '[true, true]', %q{ - class Map - def initialize - @r = Ractor.new { - loop do - key = Ractor.receive - Ractor.yield key - end - } - end - - def fetch(key) - @r.send key - _r, val = Ractor.select(@r) - val - end +# monitor port returns `:exited` even if the monitoring Ractor was terminated. +assert_equal 'true', %q{ + r = Ractor.new do + :ok end - tm = Map.new - t1 = Thread.new { 10.times.map { tm.fetch("t1") } } - t2 = Thread.new { 10.times.map { tm.fetch("t2") } } - vals = t1.value + t2.value - [ - vals.first(10).all? { |v| v == "t1" }, - vals.last(10).all? { |v| v == "t2" } - ] - } + r.join # wait for r's terminateion - # Ractor.receive in multiple threads in same ractor - # [Bug #17624] - assert_equal '["T1 received", "T2 received"]', %q{ - r1 = Ractor.new do - output = [] - m = Mutex.new - # Start two listener threads - t1 = Thread.new do - Ractor.receive - m.synchronize do - output << "T1 received" + r.monitor port = Ractor::Port.new + port.receive == :exited +} + +# monitor returns false if the monitoring Ractor was terminated. +assert_equal 'false', %q{ + r = Ractor.new do + :ok + end + + r.join # wait for r's terminateion + + r.monitor Ractor::Port.new +} + +# monitor port returns `:aborted` when the monitering Ractor is aborted. +assert_equal 'true', %q{ + r = Ractor.new do + Ractor.main << :ok1 + raise 'ok' + end + + r.monitor port = Ractor::Port.new + Ractor.receive # :ok1 + port.receive == :aborted +} + +# monitor port returns `:aborted` even if the monitoring Ractor was aborted. +assert_equal 'true', %q{ + r = Ractor.new do + raise 'ok' + end + + begin + r.join # wait for r's terminateion + rescue Ractor::RemoteError + # ignore + end + + r.monitor port = Ractor::Port.new + port.receive == :aborted +} + +## Ractor#join + +# Ractor#join returns self when the Ractor is terminated. +assert_equal 'true', %q{ + r = Ractor.new do + Ractor.receive + end + + r << :ok + r.join + r.inspect in /terminated/ +} if false # TODO + +# Ractor#join raises RemoteError when the remote Ractor aborted with an exception +assert_equal 'err', %q{ + r = Ractor.new do + raise 'err' + end + + begin + r.join + rescue Ractor::RemoteError => e + e.cause.message + end +} + +## Ractor#value + +# Ractor#value returns the last expression even if it is unshareable +assert_equal 'true', %q{ + r = Ractor.new do + obj = [1, 2] + obj << obj.object_id + end + + ret = r.value + ret == [1, 2, ret.object_id] +} + +# Only one Ractor can call Ractor#value +assert_equal '[["Only the successor ractor can take a value", 9], ["ok", 2]]', %q{ + r = Ractor.new do + 'ok' + end + + RN = 10 + + rs = RN.times.map do + Ractor.new r do |r| + begin + Ractor.main << r.value + Ractor.main << r.value # this ractor can get same result + rescue Ractor::Error => e + Ractor.main << e.message end end - t2 = Thread.new do - Ractor.receive - m.synchronize do - output << "T2 received" - end - end - sleep 0.1 until [t1,t2].all? { |t| t.status == "sleep" } - Ractor.main.send(:both_blocking) - - [t1, t2].each(&:join) - output end - Ractor.receive # wait until both threads have blocked - r1.send(1) - r1.send(2) - r1.take.sort - } -end + (RN+1).times.map{ + Ractor.receive + }.tally.sort +} + +# Ractor#take will warn for compatibility. +# This method will be removed after 2025/09/01 +assert_equal "2", %q{ + raise "remove Ractor#take and this test" if Time.now > Time.new(2025, 9, 2) + $VERBOSE = true + r = Ractor.new{42} + $msg = [] + def Warning.warn(msg) + $msg << msg + end + r.take + r.take + raise unless $msg.all?{/Ractor#take/ =~ it} + $msg.size +} diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 1da7837fe4..d480369c75 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -220,7 +220,7 @@ assert_equal 'Sub', %q{ call(Sub.new('o')).class } -# String#dup with FL_EXIVAR +# String#dup with generic ivars assert_equal '["str", "ivar"]', %q{ def str_dup(str) = str.dup str = "str" @@ -3018,15 +3018,16 @@ assert_equal '[:itself]', %q{ itself end - tracing_ractor = Ractor.new do + port = Ractor::Port.new + tracing_ractor = Ractor.new port do |port| # 1: start tracing events = [] tp = TracePoint.new(:c_call) { events << _1.method_id } tp.enable - Ractor.yield(nil) + port << nil # 3: run compiled method on tracing ractor - Ractor.yield(nil) + port << nil traced_method events @@ -3034,13 +3035,13 @@ assert_equal '[:itself]', %q{ tp&.disable end - tracing_ractor.take + port.receive # 2: compile on non tracing ractor traced_method - tracing_ractor.take - tracing_ractor.take + port.receive + tracing_ractor.value } # Try to hit a lazy branch stub while another ractor enables tracing @@ -3054,17 +3055,18 @@ assert_equal '42', %q{ end end - ractor = Ractor.new do + port = Ractor::Port.new + ractor = Ractor.new port do |port| compiled(false) - Ractor.yield(nil) + port << nil compiled(41) end tp = TracePoint.new(:line) { itself } - ractor.take + port.receive tp.enable - ractor.take + ractor.value } # Test equality with changing types @@ -3140,7 +3142,7 @@ assert_equal '42', %q{ A.foo A.foo - Ractor.new { A.foo }.take + Ractor.new { A.foo }.value } assert_equal '["plain", "special", "sub", "plain"]', %q{ @@ -3859,36 +3861,6 @@ assert_equal '3,12', %q{ pt_inspect(p) } -# Regression test for deadlock between branch_stub_hit and ractor_receive_if -assert_equal '10', %q{ - r = Ractor.new Ractor.current do |main| - main << 1 - main << 2 - main << 3 - main << 4 - main << 5 - main << 6 - main << 7 - main << 8 - main << 9 - main << 10 - end - - a = [] - a << Ractor.receive_if{|msg| msg == 10} - a << Ractor.receive_if{|msg| msg == 9} - a << Ractor.receive_if{|msg| msg == 8} - a << Ractor.receive_if{|msg| msg == 7} - a << Ractor.receive_if{|msg| msg == 6} - a << Ractor.receive_if{|msg| msg == 5} - a << Ractor.receive_if{|msg| msg == 4} - a << Ractor.receive_if{|msg| msg == 3} - a << Ractor.receive_if{|msg| msg == 2} - a << Ractor.receive_if{|msg| msg == 1} - - a.length -} - # checktype assert_equal 'false', %q{ def function() diff --git a/bootstraptest/test_yjit_rust_port.rb b/bootstraptest/test_yjit_rust_port.rb index e399e0e49e..2dbcebc03a 100644 --- a/bootstraptest/test_yjit_rust_port.rb +++ b/bootstraptest/test_yjit_rust_port.rb @@ -374,7 +374,7 @@ assert_equal 'ok', %q{ r = Ractor.new do 'ok' end - r.take + r.value } # Passed arguments to Ractor.new will be a block parameter @@ -384,7 +384,7 @@ assert_equal 'ok', %q{ r = Ractor.new 'ok' do |msg| msg end - r.take + r.value } # Pass multiple arguments to Ractor.new @@ -393,7 +393,7 @@ assert_equal 'ok', %q{ r = Ractor.new 'ping', 'pong' do |msg, msg2| [msg, msg2] end - 'ok' if r.take == ['ping', 'pong'] + 'ok' if r.value == ['ping', 'pong'] } # Ractor#send passes an object with copy to a Ractor @@ -403,7 +403,7 @@ assert_equal 'ok', %q{ msg = Ractor.receive end r.send 'ok' - r.take + r.value } assert_equal '[1, 2, 3]', %q{ diff --git a/class.c b/class.c index 6d81654142..bef54eae2f 100644 --- a/class.c +++ b/class.c @@ -42,10 +42,10 @@ * 2: RCLASS_PRIME_CLASSEXT_PRIME_WRITABLE * This class's prime classext is the only classext and writable from any namespaces. * If unset, the prime classext is writable only from the root namespace. - * if !SHAPE_IN_BASIC_FLAGS - * 4-19: SHAPE_FLAG_MASK - * Shape ID for the class. - * endif + * 3: RCLASS_IS_INITIALIZED + * Class has been initialized. + * 4: RCLASS_NAMESPACEABLE + * Is a builtin class that may be namespaced. It larger than a normal class. */ /* Flags of T_ICLASS @@ -53,10 +53,8 @@ * 2: RCLASS_PRIME_CLASSEXT_PRIME_WRITABLE * This module's prime classext is the only classext and writable from any namespaces. * If unset, the prime classext is writable only from the root namespace. - * if !SHAPE_IN_BASIC_FLAGS - * 4-19: SHAPE_FLAG_MASK - * Shape ID. This is set but not used. - * endif + * 4: RCLASS_NAMESPACEABLE + * Is a builtin class that may be namespaced. It larger than a normal class. */ /* Flags of T_MODULE @@ -64,17 +62,15 @@ * 0: RCLASS_IS_ROOT * The class has been added to the VM roots. Will always be marked and pinned. * This is done for classes defined from C to allow storing them in global variables. - * 1: RMODULE_ALLOCATED_BUT_NOT_INITIALIZED - * Module has not been initialized. + * 1: RMODULE_IS_REFINEMENT + * Module is used for refinements. * 2: RCLASS_PRIME_CLASSEXT_PRIME_WRITABLE * This module's prime classext is the only classext and writable from any namespaces. * If unset, the prime classext is writable only from the root namespace. - * 3: RMODULE_IS_REFINEMENT - * Module is used for refinements. - * if !SHAPE_IN_BASIC_FLAGS - * 4-19: SHAPE_FLAG_MASK - * Shape ID for the module. - * endif + * 3: RCLASS_IS_INITIALIZED + * Module has been initialized. + * 4: RCLASS_NAMESPACEABLE + * Is a builtin class that may be namespaced. It larger than a normal class. */ #define METACLASS_OF(k) RBASIC(k)->klass @@ -183,16 +179,6 @@ duplicate_classext_const_tbl(struct rb_id_table *src, VALUE klass) return dst; } -static void -duplicate_classext_superclasses(rb_classext_t *orig, rb_classext_t *copy) -{ - RCLASSEXT_SUPERCLASSES(copy) = RCLASSEXT_SUPERCLASSES(orig); - RCLASSEXT_SUPERCLASS_DEPTH(copy) = RCLASSEXT_SUPERCLASS_DEPTH(orig); - // the copy is always not the owner and the orig (or its parent class) will maintain the superclasses array - RCLASSEXT_SUPERCLASSES_OWNER(copy) = false; - RCLASSEXT_SUPERCLASSES_WITH_SELF(copy) = RCLASSEXT_SUPERCLASSES_WITH_SELF(orig); -} - static VALUE namespace_subclasses_tbl_key(const rb_namespace_t *ns) { @@ -256,6 +242,8 @@ duplicate_classext_subclasses(rb_classext_t *orig, rb_classext_t *copy) static void class_duplicate_iclass_classext(VALUE iclass, rb_classext_t *mod_ext, const rb_namespace_t *ns) { + RUBY_ASSERT(RB_TYPE_P(iclass, T_ICLASS)); + rb_classext_t *src = RCLASS_EXT_PRIME(iclass); rb_classext_t *ext = RCLASS_EXT_TABLE_LOOKUP_INTERNAL(iclass, ns); int first_set = 0; @@ -278,7 +266,7 @@ class_duplicate_iclass_classext(VALUE iclass, rb_classext_t *mod_ext, const rb_n else { RCLASSEXT_M_TBL(ext) = RCLASSEXT_M_TBL(mod_ext); } - RCLASSEXT_FIELDS(ext) = (VALUE *)st_init_numtable(); + RCLASSEXT_CONST_TBL(ext) = RCLASSEXT_CONST_TBL(mod_ext); RCLASSEXT_CVC_TBL(ext) = RCLASSEXT_CVC_TBL(mod_ext); @@ -315,13 +303,8 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace RCLASSEXT_M_TBL(ext) = duplicate_classext_m_tbl(RCLASSEXT_M_TBL(orig), klass, dup_iclass); - // TODO: consider shapes for performance - if (RCLASSEXT_FIELDS(orig)) { - RCLASSEXT_FIELDS(ext) = (VALUE *)st_copy((st_table *)RCLASSEXT_FIELDS(orig)); - rb_autoload_copy_table_for_namespace((st_table *)RCLASSEXT_FIELDS(ext), ns); - } - else { - RCLASSEXT_FIELDS(ext) = (VALUE *)st_init_numtable(); + if (orig->fields_obj) { + RB_OBJ_WRITE(klass, &ext->fields_obj, rb_imemo_fields_clone(orig->fields_obj)); } if (RCLASSEXT_SHARED_CONST_TBL(orig)) { @@ -344,9 +327,6 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace RCLASSEXT_CVC_TBL(ext) = duplicate_classext_id_table(RCLASSEXT_CVC_TBL(orig), dup_iclass); - // superclass_depth, superclasses - duplicate_classext_superclasses(orig, ext); - // subclasses, subclasses_index duplicate_classext_subclasses(orig, ext); @@ -356,9 +336,9 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace * * refined_class * * as.class.allocator / as.singleton_class.attached_object * * includer + * * max IV count + * * variation count */ - RCLASSEXT_MAX_IV_COUNT(ext) = RCLASSEXT_MAX_IV_COUNT(orig); - RCLASSEXT_VARIATION_COUNT(ext) = RCLASSEXT_VARIATION_COUNT(orig); RCLASSEXT_PERMANENT_CLASSPATH(ext) = RCLASSEXT_PERMANENT_CLASSPATH(orig); RCLASSEXT_CLONED(ext) = RCLASSEXT_CLONED(orig); RCLASSEXT_CLASSPATH(ext) = RCLASSEXT_CLASSPATH(orig); @@ -378,6 +358,8 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace if (subclass_entry->klass && RB_TYPE_P(subclass_entry->klass, T_ICLASS)) { iclass = subclass_entry->klass; if (RBASIC_CLASS(iclass) == klass) { + // Is the subclass an ICLASS including this module into another class + // If so we need to re-associate it under our namespace with the new ext class_duplicate_iclass_classext(iclass, ext, ns); } } @@ -412,7 +394,7 @@ class_classext_foreach_i(st_data_t key, st_data_t value, st_data_t arg) void rb_class_classext_foreach(VALUE klass, rb_class_classext_foreach_callback_func *func, void *arg) { - st_table *tbl = RCLASS(klass)->ns_classext_tbl; + st_table *tbl = RCLASS_CLASSEXT_TBL(klass); struct class_classext_foreach_arg foreach_arg; if (tbl) { foreach_arg.func = func; @@ -452,8 +434,7 @@ push_subclass_entry_to_list(VALUE super, VALUE klass, bool is_module) entry = ZALLOC(rb_subclass_entry_t); entry->klass = klass; - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { anchor = RCLASS_WRITABLE_SUBCLASSES(super); VM_ASSERT(anchor); ns_subclasses = (rb_ns_subclasses_t *)anchor->ns_subclasses; @@ -470,7 +451,6 @@ push_subclass_entry_to_list(VALUE super, VALUE klass, bool is_module) entry->prev = head; st_insert(tbl, namespace_subclasses_tbl_key(ns), (st_data_t)entry); } - RB_VM_LOCK_LEAVE(); if (is_module) { RCLASS_WRITE_NS_MODULE_SUBCLASSES(klass, anchor->ns_subclasses); @@ -654,24 +634,30 @@ class_switch_superclass(VALUE super, VALUE klass) } /** - * Allocates a struct RClass for a new class. + * Allocates a struct RClass for a new class, iclass, or module. * - * @param flags initial value for basic.flags of the returned class. - * @param klass the class of the returned class. - * @return an uninitialized Class object. - * @pre `klass` must refer `Class` class or an ancestor of Class. - * @pre `(flags | T_CLASS) != 0` - * @post the returned class can safely be `#initialize` 'd. + * @param type The type of the RClass (T_CLASS, T_ICLASS, or T_MODULE) + * @param klass value for basic.klass of the returned object. + * @return an uninitialized Class/IClass/Module object. + * @pre `klass` must refer to a class or module * * @note this function is not Class#allocate. */ static VALUE -class_alloc(VALUE flags, VALUE klass) +class_alloc0(enum ruby_value_type type, VALUE klass, bool namespaceable) { rb_ns_subclasses_t *ns_subclasses; rb_subclass_anchor_t *anchor; const rb_namespace_t *ns = rb_definition_namespace(); - size_t alloc_size = sizeof(struct RClass) + sizeof(rb_classext_t); + + if (!ruby_namespace_init_done) { + namespaceable = true; + } + + size_t alloc_size = sizeof(struct RClass_and_rb_classext_t); + if (namespaceable) { + alloc_size = sizeof(struct RClass_namespaceable); + } // class_alloc is supposed to return a new object that is not promoted yet. // So, we need to avoid GC after NEWOBJ_OF. @@ -686,8 +672,12 @@ class_alloc(VALUE flags, VALUE klass) anchor->ns_subclasses = ns_subclasses; anchor->head = ZALLOC(rb_subclass_entry_t); - flags &= T_MASK; + RUBY_ASSERT(type == T_CLASS || type == T_ICLASS || type == T_MODULE); + + VALUE flags = type; if (RGENGC_WB_PROTECTED_CLASS) flags |= FL_WB_PROTECTED; + if (namespaceable) flags |= RCLASS_NAMESPACEABLE; + NEWOBJ_OF(obj, struct RClass, klass, flags, alloc_size, 0); memset(RCLASS_EXT_PRIME(obj), 0, sizeof(rb_classext_t)); @@ -702,17 +692,22 @@ class_alloc(VALUE flags, VALUE klass) RCLASS_PRIME_NS((VALUE)obj) = ns; // Classes/Modules defined in user namespaces are // writable directly because it exists only in a namespace. - RCLASS_SET_PRIME_CLASSEXT_WRITABLE((VALUE)obj, NAMESPACE_USER_P(ns) ? true : false); + RCLASS_SET_PRIME_CLASSEXT_WRITABLE((VALUE)obj, !namespaceable || NAMESPACE_USER_P(ns)); RCLASS_SET_ORIGIN((VALUE)obj, (VALUE)obj); RCLASS_SET_REFINED_CLASS((VALUE)obj, Qnil); - RCLASS_SET_ALLOCATOR((VALUE)obj, 0); RCLASS_SET_SUBCLASSES((VALUE)obj, anchor); return (VALUE)obj; } +static VALUE +class_alloc(enum ruby_value_type type, VALUE klass) +{ + return class_alloc0(type, klass, false); +} + static VALUE class_associate_super(VALUE klass, VALUE super, bool init) { @@ -748,6 +743,23 @@ class_clear_method_table(VALUE c) RCLASS_WRITE_M_TBL_EVEN_WHEN_PROMOTED(c, rb_id_table_create(0)); } +static VALUE +class_boot_namespaceable(VALUE super, bool namespaceable) +{ + VALUE klass = class_alloc0(T_CLASS, rb_cClass, namespaceable); + + // initialize method table prior to class_associate_super() + // because class_associate_super() may cause GC and promote klass + class_initialize_method_table(klass); + + class_associate_super(klass, super, true); + if (super && !UNDEF_P(super)) { + rb_class_set_initialized(klass); + } + + return (VALUE)klass; +} + /** * A utility function that wraps class_alloc. * @@ -760,15 +772,7 @@ class_clear_method_table(VALUE c) VALUE rb_class_boot(VALUE super) { - VALUE klass = class_alloc(T_CLASS, rb_cClass); - - // initialize method table prior to class_associate_super() - // because class_associate_super() may cause GC and promote klass - class_initialize_method_table(klass); - - class_associate_super(klass, super, true); - - return (VALUE)klass; + return class_boot_namespaceable(super, false); } static VALUE * @@ -825,11 +829,11 @@ rb_class_update_superclasses(VALUE klass) } else { superclasses = class_superclasses_including_self(super); - RCLASS_WRITE_SUPERCLASSES(super, super_depth, superclasses, true, true); + RCLASS_WRITE_SUPERCLASSES(super, super_depth, superclasses, true); } size_t depth = super_depth == RCLASS_MAX_SUPERCLASS_DEPTH ? super_depth : super_depth + 1; - RCLASS_WRITE_SUPERCLASSES(klass, depth, superclasses, false, false); + RCLASS_WRITE_SUPERCLASSES(klass, depth, superclasses, false); } void @@ -858,6 +862,8 @@ rb_class_new(VALUE super) RCLASS_SET_MAX_IV_COUNT(klass, RCLASS_MAX_IV_COUNT(super)); } + RUBY_ASSERT(getenv("RUBY_NAMESPACE") || RCLASS_PRIME_CLASSEXT_WRITABLE_P(klass)); + return klass; } @@ -871,8 +877,7 @@ static void clone_method(VALUE old_klass, VALUE new_klass, ID mid, const rb_method_entry_t *me) { if (me->def->type == VM_METHOD_TYPE_ISEQ) { - rb_cref_t *new_cref; - rb_vm_rewrite_cref(me->def->body.iseq.cref, old_klass, new_klass, &new_cref); + rb_cref_t *new_cref = rb_vm_rewrite_cref(me->def->body.iseq.cref, old_klass, new_klass); rb_add_method_iseq(new_klass, mid, me->def->body.iseq.iseqptr, new_cref, METHOD_ENTRY_VISI(me)); } else { @@ -922,7 +927,7 @@ class_init_copy_check(VALUE clone, VALUE orig) if (orig == rb_cBasicObject) { rb_raise(rb_eTypeError, "can't copy the root class"); } - if (RCLASS_SUPER(clone) != 0 || clone == rb_cBasicObject) { + if (RCLASS_INITIALIZED_P(clone)) { rb_raise(rb_eTypeError, "already initialized class"); } if (RCLASS_SINGLETON_P(orig)) { @@ -975,48 +980,33 @@ copy_tables(VALUE clone, VALUE orig) rb_id_table_free(RCLASS_M_TBL(clone)); RCLASS_WRITE_M_TBL_EVEN_WHEN_PROMOTED(clone, 0); if (!RB_TYPE_P(clone, T_ICLASS)) { - st_data_t id; - rb_fields_tbl_copy(clone, orig); - CONST_ID(id, "__tmp_classpath__"); - rb_attr_delete(clone, id); - CONST_ID(id, "__classpath__"); - rb_attr_delete(clone, id); } if (RCLASS_CONST_TBL(orig)) { struct clone_const_arg arg; struct rb_id_table *const_tbl; - arg.tbl = const_tbl = rb_id_table_create(0); + struct rb_id_table *orig_tbl = RCLASS_CONST_TBL(orig); + arg.tbl = const_tbl = rb_id_table_create(rb_id_table_size(orig_tbl)); arg.klass = clone; - rb_id_table_foreach(RCLASS_CONST_TBL(orig), clone_const_i, &arg); + rb_id_table_foreach(orig_tbl, clone_const_i, &arg); RCLASS_WRITE_CONST_TBL(clone, const_tbl, false); } } static bool ensure_origin(VALUE klass); -/** - * If this flag is set, that module is allocated but not initialized yet. - */ -enum {RMODULE_ALLOCATED_BUT_NOT_INITIALIZED = RUBY_FL_USER1}; - -static inline bool -RMODULE_UNINITIALIZED(VALUE module) -{ - return FL_TEST_RAW(module, RMODULE_ALLOCATED_BUT_NOT_INITIALIZED); -} - void -rb_module_set_initialized(VALUE mod) +rb_class_set_initialized(VALUE klass) { - FL_UNSET_RAW(mod, RMODULE_ALLOCATED_BUT_NOT_INITIALIZED); + RUBY_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE)); + FL_SET_RAW(klass, RCLASS_IS_INITIALIZED); /* no more re-initialization */ } void rb_module_check_initializable(VALUE mod) { - if (!RMODULE_UNINITIALIZED(mod)) { + if (RCLASS_INITIALIZED_P(mod)) { rb_raise(rb_eTypeError, "already initialized module"); } } @@ -1025,9 +1015,11 @@ rb_module_check_initializable(VALUE mod) VALUE rb_mod_init_copy(VALUE clone, VALUE orig) { + /* Only class or module is valid here, but other classes may enter here and + * only hit an exception on the OBJ_INIT_COPY checks + */ switch (BUILTIN_TYPE(clone)) { case T_CLASS: - case T_ICLASS: class_init_copy_check(clone, orig); break; case T_MODULE: @@ -1038,6 +1030,11 @@ rb_mod_init_copy(VALUE clone, VALUE orig) } if (!OBJ_INIT_COPY(clone, orig)) return clone; + RUBY_ASSERT(RB_TYPE_P(orig, T_CLASS) || RB_TYPE_P(orig, T_MODULE)); + RUBY_ASSERT(BUILTIN_TYPE(clone) == BUILTIN_TYPE(orig)); + + rb_class_set_initialized(clone); + /* cloned flag is refer at constant inline cache * see vm_get_const_key_cref() in vm_insnhelper.c */ @@ -1048,7 +1045,9 @@ rb_mod_init_copy(VALUE clone, VALUE orig) RBASIC_SET_CLASS(clone, rb_singleton_class_clone(orig)); rb_singleton_class_attached(METACLASS_OF(clone), (VALUE)clone); } - RCLASS_SET_ALLOCATOR(clone, RCLASS_ALLOCATOR(orig)); + if (BUILTIN_TYPE(clone) == T_CLASS) { + RCLASS_SET_ALLOCATOR(clone, RCLASS_ALLOCATOR(orig)); + } copy_tables(clone, orig); if (RCLASS_M_TBL(orig)) { struct clone_method_arg arg; @@ -1081,7 +1080,7 @@ rb_mod_init_copy(VALUE clone, VALUE orig) if (BUILTIN_TYPE(p) != T_ICLASS) { rb_bug("non iclass between module/class and origin"); } - clone_p = class_alloc(RBASIC(p)->flags, METACLASS_OF(p)); + clone_p = class_alloc(T_ICLASS, METACLASS_OF(p)); /* We should set the m_tbl right after allocation before anything * that can trigger GC to avoid clone_p from becoming old and * needing to fire write barriers. */ @@ -1089,7 +1088,6 @@ rb_mod_init_copy(VALUE clone, VALUE orig) rb_class_set_super(prev_clone_p, clone_p); prev_clone_p = clone_p; RCLASS_SET_CONST_TBL(clone_p, RCLASS_CONST_TBL(p), false); - RCLASS_SET_ALLOCATOR(clone_p, RCLASS_ALLOCATOR(p)); if (RB_TYPE_P(clone, T_CLASS)) { RCLASS_SET_INCLUDER(clone_p, clone); } @@ -1159,7 +1157,8 @@ rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach) else { /* copy singleton(unnamed) class */ bool klass_of_clone_is_new; - VALUE clone = class_alloc(RBASIC(klass)->flags, 0); + RUBY_ASSERT(RB_TYPE_P(klass, T_CLASS)); + VALUE clone = class_alloc(T_CLASS, 0); if (BUILTIN_TYPE(obj) == T_CLASS) { klass_of_clone_is_new = true; @@ -1265,7 +1264,7 @@ static inline VALUE make_metaclass(VALUE klass) { VALUE super; - VALUE metaclass = rb_class_boot(Qundef); + VALUE metaclass = class_boot_namespaceable(Qundef, FL_TEST_RAW(klass, RCLASS_NAMESPACEABLE)); FL_SET(metaclass, FL_SINGLETON); rb_singleton_class_attached(metaclass, klass); @@ -1283,6 +1282,7 @@ make_metaclass(VALUE klass) super = RCLASS_SUPER(klass); while (RB_TYPE_P(super, T_ICLASS)) super = RCLASS_SUPER(super); class_associate_super(metaclass, super ? ENSURE_EIGENCLASS(super) : rb_cClass, true); + rb_class_set_initialized(klass); // Full class ancestry may not have been filled until we reach here. rb_class_update_superclasses(METACLASS_OF(metaclass)); @@ -1300,7 +1300,7 @@ static inline VALUE make_singleton_class(VALUE obj) { VALUE orig_class = METACLASS_OF(obj); - VALUE klass = rb_class_boot(orig_class); + VALUE klass = class_boot_namespaceable(orig_class, FL_TEST_RAW(orig_class, RCLASS_NAMESPACEABLE)); FL_SET(klass, FL_SINGLETON); RBASIC_SET_CLASS(obj, klass); @@ -1565,7 +1565,6 @@ rb_module_s_alloc(VALUE klass) { VALUE mod = class_alloc(T_MODULE, klass); class_initialize_method_table(mod); - FL_SET(mod, RMODULE_ALLOCATED_BUT_NOT_INITIALIZED); return mod; } @@ -1689,7 +1688,7 @@ ensure_includable(VALUE klass, VALUE module) { rb_class_modify_check(klass); Check_Type(module, T_MODULE); - rb_module_set_initialized(module); + rb_class_set_initialized(module); if (!NIL_P(rb_refinement_module_get_refined_class(module))) { rb_raise(rb_eArgError, "refinement module is not allowed"); } @@ -2766,7 +2765,8 @@ rb_freeze_singleton_class(VALUE x) if (!RCLASS_SINGLETON_P(x)) { VALUE klass = RBASIC_CLASS(x); if (klass && // no class when hidden from ObjectSpace - FL_TEST(klass, (FL_SINGLETON|FL_FREEZE)) == FL_SINGLETON) { + FL_TEST_RAW(klass, FL_SINGLETON) && + !OBJ_FROZEN_RAW(klass)) { OBJ_FREEZE(klass); } } diff --git a/common.mk b/common.mk index 7bf6d28b53..0332d24da3 100644 --- a/common.mk +++ b/common.mk @@ -111,13 +111,15 @@ PRISM_FILES = prism/api_node.$(OBJEXT) \ prism/prism.$(OBJEXT) \ prism_init.$(OBJEXT) -COMMONOBJS = array.$(OBJEXT) \ +COMMONOBJS = \ + array.$(OBJEXT) \ ast.$(OBJEXT) \ bignum.$(OBJEXT) \ class.$(OBJEXT) \ compar.$(OBJEXT) \ compile.$(OBJEXT) \ complex.$(OBJEXT) \ + concurrent_set.$(OBJEXT) \ cont.$(OBJEXT) \ debug.$(OBJEXT) \ debug_counter.$(OBJEXT) \ @@ -131,8 +133,8 @@ COMMONOBJS = array.$(OBJEXT) \ file.$(OBJEXT) \ gc.$(OBJEXT) \ hash.$(OBJEXT) \ - inits.$(OBJEXT) \ imemo.$(OBJEXT) \ + inits.$(OBJEXT) \ io.$(OBJEXT) \ io_buffer.$(OBJEXT) \ iseq.$(OBJEXT) \ @@ -146,6 +148,7 @@ COMMONOBJS = array.$(OBJEXT) \ numeric.$(OBJEXT) \ object.$(OBJEXT) \ pack.$(OBJEXT) \ + pathname.$(OBJEXT) \ parse.$(OBJEXT) \ parser_st.$(OBJEXT) \ proc.$(OBJEXT) \ @@ -164,11 +167,11 @@ COMMONOBJS = array.$(OBJEXT) \ ruby.$(OBJEXT) \ ruby_parser.$(OBJEXT) \ scheduler.$(OBJEXT) \ + set.$(OBJEXT) \ shape.$(OBJEXT) \ signal.$(OBJEXT) \ sprintf.$(OBJEXT) \ st.$(OBJEXT) \ - set.$(OBJEXT) \ strftime.$(OBJEXT) \ string.$(OBJEXT) \ struct.$(OBJEXT) \ @@ -187,10 +190,9 @@ COMMONOBJS = array.$(OBJEXT) \ weakmap.$(OBJEXT) \ $(PRISM_FILES) \ $(YJIT_OBJ) \ - $(YJIT_LIBOBJ) \ $(ZJIT_OBJ) \ - $(ZJIT_LIBOBJ) \ $(JIT_OBJ) \ + $(RUST_LIBOBJ) \ $(COROUTINE_OBJ) \ $(DTRACE_OBJ) \ $(BUILTIN_ENCOBJS) \ @@ -346,7 +348,7 @@ YJIT_RUSTC_ARGS = --crate-name=yjit \ -C opt-level=3 \ -C overflow-checks=on \ '--out-dir=$(CARGO_TARGET_DIR)/release/' \ - $(top_srcdir)/yjit/src/lib.rs + '$(top_srcdir)/yjit/src/lib.rs' ZJIT_RUSTC_ARGS = --crate-name=zjit \ --crate-type=staticlib \ @@ -355,8 +357,8 @@ ZJIT_RUSTC_ARGS = --crate-name=zjit \ -C lto=thin \ -C opt-level=3 \ -C overflow-checks=on \ - '--out-dir=$(ZJIT_CARGO_TARGET_DIR)/release/' \ - $(top_srcdir)/zjit/src/lib.rs + '--out-dir=$(CARGO_TARGET_DIR)/release/' \ + '$(top_srcdir)/zjit/src/lib.rs' all: $(SHOWFLAGS) main @@ -491,17 +493,19 @@ docs: srcs-doc $(DOCTARGETS) pkgconfig-data: $(ruby_pc) $(ruby_pc): $(srcdir)/template/ruby.pc.in config.status +INSTALL_ALL = all + install-all: pre-install-all do-install-all post-install-all pre-install-all:: all pre-install-local pre-install-ext pre-install-gem pre-install-doc do-install-all: pre-install-all $(DOT_WAIT) docs - $(INSTRUBY) --make="$(MAKE)" $(INSTRUBY_ARGS) --install=all $(INSTALL_DOC_OPTS) + $(INSTRUBY) --make="$(MAKE)" $(INSTRUBY_ARGS) --install=$(INSTALL_ALL) $(INSTALL_DOC_OPTS) post-install-all:: post-install-local post-install-ext post-install-gem post-install-doc @$(NULLCMD) install-nodoc: pre-install-nodoc do-install-nodoc post-install-nodoc pre-install-nodoc:: pre-install-local pre-install-ext pre-install-gem do-install-nodoc: main pre-install-nodoc - $(INSTRUBY) --make="$(MAKE)" $(INSTRUBY_ARGS) --install=all --exclude=doc + $(INSTRUBY) --make="$(MAKE)" $(INSTRUBY_ARGS) --install=$(INSTALL_ALL) --exclude=doc post-install-nodoc:: post-install-local post-install-ext post-install-gem install-local: pre-install-local do-install-local post-install-local @@ -576,7 +580,7 @@ what-where-all: no-install-all no-install-all: pre-no-install-all dont-install-all post-no-install-all pre-no-install-all:: pre-no-install-local pre-no-install-ext pre-no-install-doc dont-install-all: $(PROGRAM) - $(INSTRUBY) -n --make="$(MAKE)" $(INSTRUBY_ARGS) --install=all $(INSTALL_DOC_OPTS) + $(INSTRUBY) -n --make="$(MAKE)" $(INSTRUBY_ARGS) --install=$(INSTALL_ALL) $(INSTALL_DOC_OPTS) post-no-install-all:: post-no-install-local post-no-install-ext post-no-install-doc @$(NULLCMD) @@ -736,8 +740,8 @@ clean-local:: clean-runnable $(Q)$(RM) probes.h probes.$(OBJEXT) probes.stamp ruby-glommed.$(OBJEXT) ruby.imp ChangeLog $(STATIC_RUBY)$(EXEEXT) $(Q)$(RM) GNUmakefile.old Makefile.old $(arch)-fake.rb bisect.sh $(ENC_TRANS_D) builtin_binary.inc $(Q)$(RM) $(PRISM_BUILD_DIR)/.time $(PRISM_BUILD_DIR)/*/.time yjit_exit_locations.dump - -$(Q)$(RMALL) yjit/target - -$(Q) $(RMDIR) enc/jis enc/trans enc $(COROUTINE_H:/Context.h=) coroutine yjit \ + -$(Q)$(RMALL) target + -$(Q) $(RMDIR) enc/jis enc/trans enc $(COROUTINE_H:/Context.h=) coroutine target \ $(PRISM_BUILD_DIR)/*/ $(PRISM_BUILD_DIR) tmp \ 2> $(NULL) || $(NULLCMD) @@ -1224,6 +1228,7 @@ BUILTIN_RB_SRCS = \ $(srcdir)/array.rb \ $(srcdir)/hash.rb \ $(srcdir)/kernel.rb \ + $(srcdir)/pathname_builtin.rb \ $(srcdir)/ractor.rb \ $(srcdir)/symbol.rb \ $(srcdir)/timev.rb \ @@ -1426,8 +1431,8 @@ run: yes-fake miniruby$(EXEEXT) PHONY runruby: $(PROGRAM) PHONY RUBY_ON_BUG='gdb -x $(srcdir)/.gdbinit -p' $(RUNRUBY) $(RUNOPT0) $(TESTRUN_SCRIPT) $(RUNOPT) -runirb: $(PROGRAM) PHONY - RUBY_ON_BUG='gdb -x $(srcdir)/.gdbinit -p' $(RUNRUBY) $(RUNOPT0) -r irb -e 'IRB.start("make runirb")' $(RUNOPT) +runirb: $(PROGRAM) update-default-gemspecs + RUBY_ON_BUG='gdb -x $(srcdir)/.gdbinit -p' $(RUNRUBY) $(RUNOPT0) -rrubygems -r irb -e 'IRB.start("make runirb")' $(RUNOPT) parse: yes-fake miniruby$(EXEEXT) PHONY $(BTESTRUBY) --dump=parsetree_with_comment,insns $(TESTRUN_SCRIPT) @@ -1561,18 +1566,6 @@ extract-gems$(sequential): PHONY extract-gems$(sequential): $(HAVE_GIT:yes=clone-bundled-gems-src) -clone-bundled-gems-src: PHONY - $(Q) $(BASERUBY) -C "$(srcdir)" \ - -Itool/lib -rbundled_gem -answ \ - -e 'BEGIN {git = $$git}' \ - -e 'gem, _, repo, rev = *$$F' \ - -e 'next if !rev or /^#/=~gem' \ - -e 'gemdir = "gems/src/#{gem}"' \ - -e 'BundledGem.checkout(gemdir, repo, rev, git: git)' \ - -e 'BundledGem.dummy_gemspec("#{gemdir}/#{gem}.gemspec")' \ - -- -git="$(GIT)" \ - gems/bundled_gems - outdate-bundled-gems: PHONY $(Q) $(BASERUBY) $(tooldir)/$@.rb --make="$(MAKE)" --mflags="$(MFLAGS)" \ --ruby-platform=$(arch) --ruby-version=$(ruby_version) \ @@ -1622,7 +1615,8 @@ yes-install-for-test-bundled-gems: yes-update-default-gemspecs "sinatra" "rack" "tilt" "mustermann" "base64" "compact_index" "rack-test" "logger" "kpeg" "tracer" test-bundled-gems-fetch: yes-test-bundled-gems-fetch -yes-test-bundled-gems-fetch: +yes-test-bundled-gems-fetch: clone-bundled-gems-src +clone-bundled-gems-src: PHONY $(Q) $(BASERUBY) -C $(srcdir)/gems ../tool/fetch-bundled_gems.rb BUNDLED_GEMS="$(BUNDLED_GEMS)" src bundled_gems no-test-bundled-gems-fetch: @@ -1680,12 +1674,8 @@ test-bundler-prepare: $(TEST_RUNNABLE)-test-bundler-prepare no-test-bundler-prepare: no-test-bundler-precheck yes-test-bundler-prepare: yes-test-bundler-precheck $(ACTIONS_GROUP) - $(XRUBY) -C $(srcdir) -Ilib \ - -e 'ENV["GEM_HOME"] = File.expand_path(".bundle")' \ - -e 'ENV["BUNDLE_APP_CONFIG"] = File.expand_path(".bundle")' \ - -e 'ENV["BUNDLE_PATH__SYSTEM"] = "true"' \ - -e 'ENV["BUNDLE_WITHOUT"] = "lint doc"' \ - -e 'load "spec/bundler/support/bundle.rb"' -- install --quiet --gemfile=tool/bundler/dev_gems.rb + $(XRUBY) -C $(srcdir) -Ilib -r./tool/lib/bundle_env.rb \ + spec/bin/bundle install --quiet --gemfile=tool/bundler/dev_gems.rb $(ACTIONS_ENDGROUP) RSPECOPTS = -r formatter_overrides @@ -1695,8 +1685,7 @@ test-bundler: $(TEST_RUNNABLE)-test-bundler yes-test-bundler: $(PREPARE_BUNDLER) $(gnumake_recursive)$(XRUBY) \ -r./$(arch)-fake \ - -e "exec(*ARGV)" -- \ - $(XRUBY) -C $(srcdir) -Ispec/bundler -Ispec/lib .bundle/bin/rspec \ + -C $(srcdir) -Ispec/bundler -Ispec/lib spec/bin/rspec \ -r spec_helper $(RSPECOPTS) spec/bundler/$(BUNDLER_SPECS) no-test-bundler: @@ -2281,6 +2270,7 @@ array.$(OBJEXT): {$(VPATH)}internal/intern/re.h array.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h array.$(OBJEXT): {$(VPATH)}internal/intern/select.h array.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +array.$(OBJEXT): {$(VPATH)}internal/intern/set.h array.$(OBJEXT): {$(VPATH)}internal/intern/signal.h array.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h array.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -2519,6 +2509,7 @@ ast.$(OBJEXT): {$(VPATH)}internal/intern/re.h ast.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h ast.$(OBJEXT): {$(VPATH)}internal/intern/select.h ast.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +ast.$(OBJEXT): {$(VPATH)}internal/intern/set.h ast.$(OBJEXT): {$(VPATH)}internal/intern/signal.h ast.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h ast.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -2734,6 +2725,7 @@ bignum.$(OBJEXT): {$(VPATH)}internal/intern/re.h bignum.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h bignum.$(OBJEXT): {$(VPATH)}internal/intern/select.h bignum.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +bignum.$(OBJEXT): {$(VPATH)}internal/intern/set.h bignum.$(OBJEXT): {$(VPATH)}internal/intern/signal.h bignum.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h bignum.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -2960,6 +2952,7 @@ builtin.$(OBJEXT): {$(VPATH)}internal/intern/re.h builtin.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h builtin.$(OBJEXT): {$(VPATH)}internal/intern/select.h builtin.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +builtin.$(OBJEXT): {$(VPATH)}internal/intern/set.h builtin.$(OBJEXT): {$(VPATH)}internal/intern/signal.h builtin.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h builtin.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -3173,6 +3166,7 @@ class.$(OBJEXT): {$(VPATH)}internal/intern/re.h class.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h class.$(OBJEXT): {$(VPATH)}internal/intern/select.h class.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +class.$(OBJEXT): {$(VPATH)}internal/intern/set.h class.$(OBJEXT): {$(VPATH)}internal/intern/signal.h class.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h class.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -3368,6 +3362,7 @@ compar.$(OBJEXT): {$(VPATH)}internal/intern/re.h compar.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h compar.$(OBJEXT): {$(VPATH)}internal/intern/select.h compar.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +compar.$(OBJEXT): {$(VPATH)}internal/intern/set.h compar.$(OBJEXT): {$(VPATH)}internal/intern/signal.h compar.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h compar.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -3608,6 +3603,7 @@ compile.$(OBJEXT): {$(VPATH)}internal/intern/re.h compile.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h compile.$(OBJEXT): {$(VPATH)}internal/intern/select.h compile.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +compile.$(OBJEXT): {$(VPATH)}internal/intern/set.h compile.$(OBJEXT): {$(VPATH)}internal/intern/signal.h compile.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h compile.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -3838,6 +3834,7 @@ complex.$(OBJEXT): {$(VPATH)}internal/intern/re.h complex.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h complex.$(OBJEXT): {$(VPATH)}internal/intern/select.h complex.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +complex.$(OBJEXT): {$(VPATH)}internal/intern/set.h complex.$(OBJEXT): {$(VPATH)}internal/intern/signal.h complex.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h complex.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -3881,6 +3878,210 @@ complex.$(OBJEXT): {$(VPATH)}vm_core.h complex.$(OBJEXT): {$(VPATH)}vm_debug.h complex.$(OBJEXT): {$(VPATH)}vm_opts.h complex.$(OBJEXT): {$(VPATH)}vm_sync.h +concurrent_set.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h +concurrent_set.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h +concurrent_set.$(OBJEXT): $(CCAN_DIR)/list/list.h +concurrent_set.$(OBJEXT): $(CCAN_DIR)/str/str.h +concurrent_set.$(OBJEXT): $(hdrdir)/ruby/ruby.h +concurrent_set.$(OBJEXT): $(top_srcdir)/internal/array.h +concurrent_set.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +concurrent_set.$(OBJEXT): $(top_srcdir)/internal/compilers.h +concurrent_set.$(OBJEXT): $(top_srcdir)/internal/concurrent_set.h +concurrent_set.$(OBJEXT): $(top_srcdir)/internal/gc.h +concurrent_set.$(OBJEXT): $(top_srcdir)/internal/imemo.h +concurrent_set.$(OBJEXT): $(top_srcdir)/internal/namespace.h +concurrent_set.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h +concurrent_set.$(OBJEXT): $(top_srcdir)/internal/serial.h +concurrent_set.$(OBJEXT): $(top_srcdir)/internal/set_table.h +concurrent_set.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +concurrent_set.$(OBJEXT): $(top_srcdir)/internal/vm.h +concurrent_set.$(OBJEXT): $(top_srcdir)/internal/warnings.h +concurrent_set.$(OBJEXT): {$(VPATH)}assert.h +concurrent_set.$(OBJEXT): {$(VPATH)}atomic.h +concurrent_set.$(OBJEXT): {$(VPATH)}backward/2/assume.h +concurrent_set.$(OBJEXT): {$(VPATH)}backward/2/attributes.h +concurrent_set.$(OBJEXT): {$(VPATH)}backward/2/bool.h +concurrent_set.$(OBJEXT): {$(VPATH)}backward/2/gcc_version_since.h +concurrent_set.$(OBJEXT): {$(VPATH)}backward/2/inttypes.h +concurrent_set.$(OBJEXT): {$(VPATH)}backward/2/limits.h +concurrent_set.$(OBJEXT): {$(VPATH)}backward/2/long_long.h +concurrent_set.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h +concurrent_set.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h +concurrent_set.$(OBJEXT): {$(VPATH)}concurrent_set.c +concurrent_set.$(OBJEXT): {$(VPATH)}config.h +concurrent_set.$(OBJEXT): {$(VPATH)}debug_counter.h +concurrent_set.$(OBJEXT): {$(VPATH)}defines.h +concurrent_set.$(OBJEXT): {$(VPATH)}encoding.h +concurrent_set.$(OBJEXT): {$(VPATH)}id.h +concurrent_set.$(OBJEXT): {$(VPATH)}id_table.h +concurrent_set.$(OBJEXT): {$(VPATH)}intern.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/abi.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/anyargs.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/arithmetic.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/char.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/double.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/fixnum.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/gid_t.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/int.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/intptr_t.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/long.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/long_long.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/mode_t.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/off_t.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/pid_t.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/short.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/size_t.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/st_data_t.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/arithmetic/uid_t.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/assume.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/alloc_size.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/artificial.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/cold.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/const.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/constexpr.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/deprecated.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/diagnose_if.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/enum_extensibility.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/error.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/flag_enum.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/forceinline.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/format.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/maybe_unused.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/noalias.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/packed_struct.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/pure.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/returns_nonnull.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/warning.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/attr/weakref.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/cast.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/compiler_is.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/compiler_is/apple.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/compiler_is/clang.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/compiler_is/gcc.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/compiler_is/intel.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/compiler_is/msvc.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/compiler_is/sunpro.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/compiler_since.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/config.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/constant_p.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/core.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/core/rarray.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/core/rbasic.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/core/rbignum.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/core/rclass.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/core/rdata.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/core/rfile.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/core/rhash.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/core/robject.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/core/rregexp.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/core/rstring.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/core/rstruct.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/core/rtypeddata.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/ctype.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/dllexport.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/dosish.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/encoding/coderange.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/encoding/ctype.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/encoding/encoding.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/encoding/pathname.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/encoding/re.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/encoding/sprintf.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/encoding/string.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/encoding/symbol.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/encoding/transcode.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/error.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/eval.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/event.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/fl_type.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/gc.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/glob.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/globals.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/has/attribute.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/has/builtin.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/has/c_attribute.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/has/cpp_attribute.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/has/declspec_attribute.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/has/extension.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/has/feature.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/has/warning.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/array.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/bignum.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/class.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/compar.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/complex.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/cont.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/dir.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/enum.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/enumerator.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/error.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/eval.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/file.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/hash.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/io.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/load.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/marshal.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/numeric.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/object.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/parse.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/proc.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/process.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/random.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/range.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/rational.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/re.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/select.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/set.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/signal.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/string.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/struct.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/thread.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/time.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/variable.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/intern/vm.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/interpreter.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/iterator.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/memory.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/method.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/module.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/newobj.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/scan_args.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/special_consts.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/static_assert.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/stdalign.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/stdbool.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/stdckdint.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/symbol.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/value.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/value_type.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/variable.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/warning_push.h +concurrent_set.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +concurrent_set.$(OBJEXT): {$(VPATH)}method.h +concurrent_set.$(OBJEXT): {$(VPATH)}missing.h +concurrent_set.$(OBJEXT): {$(VPATH)}node.h +concurrent_set.$(OBJEXT): {$(VPATH)}onigmo.h +concurrent_set.$(OBJEXT): {$(VPATH)}oniguruma.h +concurrent_set.$(OBJEXT): {$(VPATH)}ruby_assert.h +concurrent_set.$(OBJEXT): {$(VPATH)}ruby_atomic.h +concurrent_set.$(OBJEXT): {$(VPATH)}rubyparser.h +concurrent_set.$(OBJEXT): {$(VPATH)}st.h +concurrent_set.$(OBJEXT): {$(VPATH)}subst.h +concurrent_set.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h +concurrent_set.$(OBJEXT): {$(VPATH)}thread_native.h +concurrent_set.$(OBJEXT): {$(VPATH)}vm_core.h +concurrent_set.$(OBJEXT): {$(VPATH)}vm_debug.h +concurrent_set.$(OBJEXT): {$(VPATH)}vm_opts.h +concurrent_set.$(OBJEXT): {$(VPATH)}vm_sync.h cont.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h cont.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h cont.$(OBJEXT): $(CCAN_DIR)/list/list.h @@ -4073,6 +4274,7 @@ cont.$(OBJEXT): {$(VPATH)}internal/intern/re.h cont.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h cont.$(OBJEXT): {$(VPATH)}internal/intern/select.h cont.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +cont.$(OBJEXT): {$(VPATH)}internal/intern/set.h cont.$(OBJEXT): {$(VPATH)}internal/intern/signal.h cont.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h cont.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -4290,6 +4492,7 @@ debug.$(OBJEXT): {$(VPATH)}internal/intern/re.h debug.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h debug.$(OBJEXT): {$(VPATH)}internal/intern/select.h debug.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +debug.$(OBJEXT): {$(VPATH)}internal/intern/set.h debug.$(OBJEXT): {$(VPATH)}internal/intern/signal.h debug.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h debug.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -4469,6 +4672,7 @@ debug_counter.$(OBJEXT): {$(VPATH)}internal/intern/re.h debug_counter.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h debug_counter.$(OBJEXT): {$(VPATH)}internal/intern/select.h debug_counter.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +debug_counter.$(OBJEXT): {$(VPATH)}internal/intern/set.h debug_counter.$(OBJEXT): {$(VPATH)}internal/intern/signal.h debug_counter.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h debug_counter.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -4672,6 +4876,7 @@ dir.$(OBJEXT): {$(VPATH)}internal/intern/re.h dir.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h dir.$(OBJEXT): {$(VPATH)}internal/intern/select.h dir.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +dir.$(OBJEXT): {$(VPATH)}internal/intern/set.h dir.$(OBJEXT): {$(VPATH)}internal/intern/signal.h dir.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h dir.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -4850,6 +5055,7 @@ dln.$(OBJEXT): {$(VPATH)}internal/intern/re.h dln.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h dln.$(OBJEXT): {$(VPATH)}internal/intern/select.h dln.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +dln.$(OBJEXT): {$(VPATH)}internal/intern/set.h dln.$(OBJEXT): {$(VPATH)}internal/intern/signal.h dln.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h dln.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -5008,6 +5214,7 @@ dln_find.$(OBJEXT): {$(VPATH)}internal/intern/re.h dln_find.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h dln_find.$(OBJEXT): {$(VPATH)}internal/intern/select.h dln_find.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +dln_find.$(OBJEXT): {$(VPATH)}internal/intern/set.h dln_find.$(OBJEXT): {$(VPATH)}internal/intern/signal.h dln_find.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h dln_find.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -5165,6 +5372,7 @@ dmydln.$(OBJEXT): {$(VPATH)}internal/intern/re.h dmydln.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h dmydln.$(OBJEXT): {$(VPATH)}internal/intern/select.h dmydln.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +dmydln.$(OBJEXT): {$(VPATH)}internal/intern/set.h dmydln.$(OBJEXT): {$(VPATH)}internal/intern/signal.h dmydln.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h dmydln.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -6185,6 +6393,7 @@ encoding.$(OBJEXT): {$(VPATH)}internal/intern/re.h encoding.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h encoding.$(OBJEXT): {$(VPATH)}internal/intern/select.h encoding.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +encoding.$(OBJEXT): {$(VPATH)}internal/intern/set.h encoding.$(OBJEXT): {$(VPATH)}internal/intern/signal.h encoding.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h encoding.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -6396,6 +6605,7 @@ enum.$(OBJEXT): {$(VPATH)}internal/intern/re.h enum.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h enum.$(OBJEXT): {$(VPATH)}internal/intern/select.h enum.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +enum.$(OBJEXT): {$(VPATH)}internal/intern/set.h enum.$(OBJEXT): {$(VPATH)}internal/intern/signal.h enum.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h enum.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -6606,6 +6816,7 @@ enumerator.$(OBJEXT): {$(VPATH)}internal/intern/re.h enumerator.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h enumerator.$(OBJEXT): {$(VPATH)}internal/intern/select.h enumerator.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +enumerator.$(OBJEXT): {$(VPATH)}internal/intern/set.h enumerator.$(OBJEXT): {$(VPATH)}internal/intern/signal.h enumerator.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h enumerator.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -6824,6 +7035,7 @@ error.$(OBJEXT): {$(VPATH)}internal/intern/re.h error.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h error.$(OBJEXT): {$(VPATH)}internal/intern/select.h error.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +error.$(OBJEXT): {$(VPATH)}internal/intern/set.h error.$(OBJEXT): {$(VPATH)}internal/intern/signal.h error.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h error.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -7070,6 +7282,7 @@ eval.$(OBJEXT): {$(VPATH)}internal/intern/re.h eval.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h eval.$(OBJEXT): {$(VPATH)}internal/intern/select.h eval.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +eval.$(OBJEXT): {$(VPATH)}internal/intern/set.h eval.$(OBJEXT): {$(VPATH)}internal/intern/signal.h eval.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h eval.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -7223,6 +7436,7 @@ file.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h file.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h file.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h file.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h +file.$(OBJEXT): {$(VPATH)}internal/attr/nonstring.h file.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h file.$(OBJEXT): {$(VPATH)}internal/attr/packed_struct.h file.$(OBJEXT): {$(VPATH)}internal/attr/pure.h @@ -7309,6 +7523,7 @@ file.$(OBJEXT): {$(VPATH)}internal/intern/re.h file.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h file.$(OBJEXT): {$(VPATH)}internal/intern/select.h file.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +file.$(OBJEXT): {$(VPATH)}internal/intern/set.h file.$(OBJEXT): {$(VPATH)}internal/intern/signal.h file.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h file.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -7557,6 +7772,7 @@ gc.$(OBJEXT): {$(VPATH)}internal/intern/re.h gc.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h gc.$(OBJEXT): {$(VPATH)}internal/intern/select.h gc.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +gc.$(OBJEXT): {$(VPATH)}internal/intern/set.h gc.$(OBJEXT): {$(VPATH)}internal/intern/signal.h gc.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h gc.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -7813,6 +8029,7 @@ goruby.$(OBJEXT): {$(VPATH)}internal/intern/re.h goruby.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h goruby.$(OBJEXT): {$(VPATH)}internal/intern/select.h goruby.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +goruby.$(OBJEXT): {$(VPATH)}internal/intern/set.h goruby.$(OBJEXT): {$(VPATH)}internal/intern/signal.h goruby.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h goruby.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -8059,6 +8276,7 @@ hash.$(OBJEXT): {$(VPATH)}internal/intern/re.h hash.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h hash.$(OBJEXT): {$(VPATH)}internal/intern/select.h hash.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +hash.$(OBJEXT): {$(VPATH)}internal/intern/set.h hash.$(OBJEXT): {$(VPATH)}internal/intern/signal.h hash.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h hash.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -8128,6 +8346,7 @@ imemo.$(OBJEXT): $(top_srcdir)/internal/namespace.h imemo.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h imemo.$(OBJEXT): $(top_srcdir)/internal/serial.h imemo.$(OBJEXT): $(top_srcdir)/internal/set_table.h +imemo.$(OBJEXT): $(top_srcdir)/internal/st.h imemo.$(OBJEXT): $(top_srcdir)/internal/static_assert.h imemo.$(OBJEXT): $(top_srcdir)/internal/variable.h imemo.$(OBJEXT): $(top_srcdir)/internal/vm.h @@ -8276,6 +8495,7 @@ imemo.$(OBJEXT): {$(VPATH)}internal/intern/re.h imemo.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h imemo.$(OBJEXT): {$(VPATH)}internal/intern/select.h imemo.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +imemo.$(OBJEXT): {$(VPATH)}internal/intern/set.h imemo.$(OBJEXT): {$(VPATH)}internal/intern/signal.h imemo.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h imemo.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -8454,6 +8674,7 @@ inits.$(OBJEXT): {$(VPATH)}internal/intern/re.h inits.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h inits.$(OBJEXT): {$(VPATH)}internal/intern/select.h inits.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +inits.$(OBJEXT): {$(VPATH)}internal/intern/set.h inits.$(OBJEXT): {$(VPATH)}internal/intern/signal.h inits.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h inits.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -8664,6 +8885,7 @@ io.$(OBJEXT): {$(VPATH)}internal/intern/re.h io.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h io.$(OBJEXT): {$(VPATH)}internal/intern/select.h io.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +io.$(OBJEXT): {$(VPATH)}internal/intern/set.h io.$(OBJEXT): {$(VPATH)}internal/intern/signal.h io.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h io.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -8882,6 +9104,7 @@ io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/re.h io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/select.h io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/set.h io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/signal.h io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -9131,6 +9354,7 @@ iseq.$(OBJEXT): {$(VPATH)}internal/intern/re.h iseq.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h iseq.$(OBJEXT): {$(VPATH)}internal/intern/select.h iseq.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +iseq.$(OBJEXT): {$(VPATH)}internal/intern/set.h iseq.$(OBJEXT): {$(VPATH)}internal/intern/signal.h iseq.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h iseq.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -9186,6 +9410,7 @@ iseq.$(OBJEXT): {$(VPATH)}vm_debug.h iseq.$(OBJEXT): {$(VPATH)}vm_opts.h iseq.$(OBJEXT): {$(VPATH)}vm_sync.h iseq.$(OBJEXT): {$(VPATH)}yjit.h +iseq.$(OBJEXT): {$(VPATH)}zjit.h jit.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h jit.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h jit.$(OBJEXT): $(CCAN_DIR)/list/list.h @@ -9372,6 +9597,7 @@ jit.$(OBJEXT): {$(VPATH)}internal/intern/re.h jit.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h jit.$(OBJEXT): {$(VPATH)}internal/intern/select.h jit.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +jit.$(OBJEXT): {$(VPATH)}internal/intern/set.h jit.$(OBJEXT): {$(VPATH)}internal/intern/signal.h jit.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h jit.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -9622,6 +9848,7 @@ load.$(OBJEXT): {$(VPATH)}internal/intern/re.h load.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h load.$(OBJEXT): {$(VPATH)}internal/intern/select.h load.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +load.$(OBJEXT): {$(VPATH)}internal/intern/set.h load.$(OBJEXT): {$(VPATH)}internal/intern/signal.h load.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h load.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -9804,6 +10031,7 @@ loadpath.$(OBJEXT): {$(VPATH)}internal/intern/re.h loadpath.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h loadpath.$(OBJEXT): {$(VPATH)}internal/intern/select.h loadpath.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +loadpath.$(OBJEXT): {$(VPATH)}internal/intern/set.h loadpath.$(OBJEXT): {$(VPATH)}internal/intern/signal.h loadpath.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h loadpath.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -9974,6 +10202,7 @@ localeinit.$(OBJEXT): {$(VPATH)}internal/intern/re.h localeinit.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h localeinit.$(OBJEXT): {$(VPATH)}internal/intern/select.h localeinit.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +localeinit.$(OBJEXT): {$(VPATH)}internal/intern/set.h localeinit.$(OBJEXT): {$(VPATH)}internal/intern/signal.h localeinit.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h localeinit.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -10139,6 +10368,7 @@ main.$(OBJEXT): {$(VPATH)}internal/intern/re.h main.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h main.$(OBJEXT): {$(VPATH)}internal/intern/select.h main.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +main.$(OBJEXT): {$(VPATH)}internal/intern/set.h main.$(OBJEXT): {$(VPATH)}internal/intern/signal.h main.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h main.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -10348,6 +10578,7 @@ marshal.$(OBJEXT): {$(VPATH)}internal/intern/re.h marshal.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h marshal.$(OBJEXT): {$(VPATH)}internal/intern/select.h marshal.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +marshal.$(OBJEXT): {$(VPATH)}internal/intern/set.h marshal.$(OBJEXT): {$(VPATH)}internal/intern/signal.h marshal.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h marshal.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -10538,6 +10769,7 @@ math.$(OBJEXT): {$(VPATH)}internal/intern/re.h math.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h math.$(OBJEXT): {$(VPATH)}internal/intern/select.h math.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +math.$(OBJEXT): {$(VPATH)}internal/intern/set.h math.$(OBJEXT): {$(VPATH)}internal/intern/signal.h math.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h math.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -10731,6 +10963,7 @@ memory_view.$(OBJEXT): {$(VPATH)}internal/intern/re.h memory_view.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h memory_view.$(OBJEXT): {$(VPATH)}internal/intern/select.h memory_view.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/set.h memory_view.$(OBJEXT): {$(VPATH)}internal/intern/signal.h memory_view.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h memory_view.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -10973,6 +11206,7 @@ miniinit.$(OBJEXT): {$(VPATH)}internal/intern/re.h miniinit.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h miniinit.$(OBJEXT): {$(VPATH)}internal/intern/select.h miniinit.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +miniinit.$(OBJEXT): {$(VPATH)}internal/intern/set.h miniinit.$(OBJEXT): {$(VPATH)}internal/intern/signal.h miniinit.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h miniinit.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -11014,6 +11248,7 @@ miniinit.$(OBJEXT): {$(VPATH)}numeric.rb miniinit.$(OBJEXT): {$(VPATH)}onigmo.h miniinit.$(OBJEXT): {$(VPATH)}oniguruma.h miniinit.$(OBJEXT): {$(VPATH)}pack.rb +miniinit.$(OBJEXT): {$(VPATH)}pathname_builtin.rb miniinit.$(OBJEXT): {$(VPATH)}prelude.rb miniinit.$(OBJEXT): {$(VPATH)}prism/ast.h miniinit.$(OBJEXT): {$(VPATH)}prism/diagnostic.h @@ -11207,6 +11442,7 @@ namespace.$(OBJEXT): {$(VPATH)}internal/intern/re.h namespace.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h namespace.$(OBJEXT): {$(VPATH)}internal/intern/select.h namespace.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +namespace.$(OBJEXT): {$(VPATH)}internal/intern/set.h namespace.$(OBJEXT): {$(VPATH)}internal/intern/signal.h namespace.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h namespace.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -11413,6 +11649,7 @@ node.$(OBJEXT): {$(VPATH)}internal/intern/re.h node.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h node.$(OBJEXT): {$(VPATH)}internal/intern/select.h node.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +node.$(OBJEXT): {$(VPATH)}internal/intern/set.h node.$(OBJEXT): {$(VPATH)}internal/intern/signal.h node.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h node.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -11627,6 +11864,7 @@ node_dump.$(OBJEXT): {$(VPATH)}internal/intern/re.h node_dump.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h node_dump.$(OBJEXT): {$(VPATH)}internal/intern/select.h node_dump.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +node_dump.$(OBJEXT): {$(VPATH)}internal/intern/set.h node_dump.$(OBJEXT): {$(VPATH)}internal/intern/signal.h node_dump.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h node_dump.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -11844,6 +12082,7 @@ numeric.$(OBJEXT): {$(VPATH)}internal/intern/re.h numeric.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h numeric.$(OBJEXT): {$(VPATH)}internal/intern/select.h numeric.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +numeric.$(OBJEXT): {$(VPATH)}internal/intern/set.h numeric.$(OBJEXT): {$(VPATH)}internal/intern/signal.h numeric.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h numeric.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -12064,6 +12303,7 @@ object.$(OBJEXT): {$(VPATH)}internal/intern/re.h object.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h object.$(OBJEXT): {$(VPATH)}internal/intern/select.h object.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +object.$(OBJEXT): {$(VPATH)}internal/intern/set.h object.$(OBJEXT): {$(VPATH)}internal/intern/signal.h object.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h object.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -12280,6 +12520,7 @@ pack.$(OBJEXT): {$(VPATH)}internal/intern/re.h pack.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h pack.$(OBJEXT): {$(VPATH)}internal/intern/select.h pack.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +pack.$(OBJEXT): {$(VPATH)}internal/intern/set.h pack.$(OBJEXT): {$(VPATH)}internal/intern/signal.h pack.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h pack.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -12504,6 +12745,7 @@ parse.$(OBJEXT): {$(VPATH)}internal/intern/re.h parse.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h parse.$(OBJEXT): {$(VPATH)}internal/intern/select.h parse.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +parse.$(OBJEXT): {$(VPATH)}internal/intern/set.h parse.$(OBJEXT): {$(VPATH)}internal/intern/signal.h parse.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h parse.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -12622,6 +12864,184 @@ parser_st.$(OBJEXT): {$(VPATH)}parser_st.c parser_st.$(OBJEXT): {$(VPATH)}parser_st.h parser_st.$(OBJEXT): {$(VPATH)}parser_value.h parser_st.$(OBJEXT): {$(VPATH)}st.c +pathname.$(OBJEXT): $(hdrdir)/ruby.h +pathname.$(OBJEXT): $(hdrdir)/ruby/ruby.h +pathname.$(OBJEXT): $(top_srcdir)/internal/compilers.h +pathname.$(OBJEXT): $(top_srcdir)/internal/warnings.h +pathname.$(OBJEXT): {$(VPATH)}assert.h +pathname.$(OBJEXT): {$(VPATH)}backward.h +pathname.$(OBJEXT): {$(VPATH)}backward/2/assume.h +pathname.$(OBJEXT): {$(VPATH)}backward/2/attributes.h +pathname.$(OBJEXT): {$(VPATH)}backward/2/bool.h +pathname.$(OBJEXT): {$(VPATH)}backward/2/gcc_version_since.h +pathname.$(OBJEXT): {$(VPATH)}backward/2/inttypes.h +pathname.$(OBJEXT): {$(VPATH)}backward/2/limits.h +pathname.$(OBJEXT): {$(VPATH)}backward/2/long_long.h +pathname.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h +pathname.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h +pathname.$(OBJEXT): {$(VPATH)}builtin.h +pathname.$(OBJEXT): {$(VPATH)}config.h +pathname.$(OBJEXT): {$(VPATH)}defines.h +pathname.$(OBJEXT): {$(VPATH)}encoding.h +pathname.$(OBJEXT): {$(VPATH)}intern.h +pathname.$(OBJEXT): {$(VPATH)}internal/abi.h +pathname.$(OBJEXT): {$(VPATH)}internal/anyargs.h +pathname.$(OBJEXT): {$(VPATH)}internal/arithmetic.h +pathname.$(OBJEXT): {$(VPATH)}internal/arithmetic/char.h +pathname.$(OBJEXT): {$(VPATH)}internal/arithmetic/double.h +pathname.$(OBJEXT): {$(VPATH)}internal/arithmetic/fixnum.h +pathname.$(OBJEXT): {$(VPATH)}internal/arithmetic/gid_t.h +pathname.$(OBJEXT): {$(VPATH)}internal/arithmetic/int.h +pathname.$(OBJEXT): {$(VPATH)}internal/arithmetic/intptr_t.h +pathname.$(OBJEXT): {$(VPATH)}internal/arithmetic/long.h +pathname.$(OBJEXT): {$(VPATH)}internal/arithmetic/long_long.h +pathname.$(OBJEXT): {$(VPATH)}internal/arithmetic/mode_t.h +pathname.$(OBJEXT): {$(VPATH)}internal/arithmetic/off_t.h +pathname.$(OBJEXT): {$(VPATH)}internal/arithmetic/pid_t.h +pathname.$(OBJEXT): {$(VPATH)}internal/arithmetic/short.h +pathname.$(OBJEXT): {$(VPATH)}internal/arithmetic/size_t.h +pathname.$(OBJEXT): {$(VPATH)}internal/arithmetic/st_data_t.h +pathname.$(OBJEXT): {$(VPATH)}internal/arithmetic/uid_t.h +pathname.$(OBJEXT): {$(VPATH)}internal/assume.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/alloc_size.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/artificial.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/cold.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/const.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/constexpr.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/deprecated.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/diagnose_if.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/enum_extensibility.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/error.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/flag_enum.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/forceinline.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/format.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/maybe_unused.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/noalias.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/packed_struct.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/pure.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/returns_nonnull.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/warning.h +pathname.$(OBJEXT): {$(VPATH)}internal/attr/weakref.h +pathname.$(OBJEXT): {$(VPATH)}internal/cast.h +pathname.$(OBJEXT): {$(VPATH)}internal/compiler_is.h +pathname.$(OBJEXT): {$(VPATH)}internal/compiler_is/apple.h +pathname.$(OBJEXT): {$(VPATH)}internal/compiler_is/clang.h +pathname.$(OBJEXT): {$(VPATH)}internal/compiler_is/gcc.h +pathname.$(OBJEXT): {$(VPATH)}internal/compiler_is/intel.h +pathname.$(OBJEXT): {$(VPATH)}internal/compiler_is/msvc.h +pathname.$(OBJEXT): {$(VPATH)}internal/compiler_is/sunpro.h +pathname.$(OBJEXT): {$(VPATH)}internal/compiler_since.h +pathname.$(OBJEXT): {$(VPATH)}internal/config.h +pathname.$(OBJEXT): {$(VPATH)}internal/constant_p.h +pathname.$(OBJEXT): {$(VPATH)}internal/core.h +pathname.$(OBJEXT): {$(VPATH)}internal/core/rarray.h +pathname.$(OBJEXT): {$(VPATH)}internal/core/rbasic.h +pathname.$(OBJEXT): {$(VPATH)}internal/core/rbignum.h +pathname.$(OBJEXT): {$(VPATH)}internal/core/rclass.h +pathname.$(OBJEXT): {$(VPATH)}internal/core/rdata.h +pathname.$(OBJEXT): {$(VPATH)}internal/core/rfile.h +pathname.$(OBJEXT): {$(VPATH)}internal/core/rhash.h +pathname.$(OBJEXT): {$(VPATH)}internal/core/robject.h +pathname.$(OBJEXT): {$(VPATH)}internal/core/rregexp.h +pathname.$(OBJEXT): {$(VPATH)}internal/core/rstring.h +pathname.$(OBJEXT): {$(VPATH)}internal/core/rstruct.h +pathname.$(OBJEXT): {$(VPATH)}internal/core/rtypeddata.h +pathname.$(OBJEXT): {$(VPATH)}internal/ctype.h +pathname.$(OBJEXT): {$(VPATH)}internal/dllexport.h +pathname.$(OBJEXT): {$(VPATH)}internal/dosish.h +pathname.$(OBJEXT): {$(VPATH)}internal/encoding/coderange.h +pathname.$(OBJEXT): {$(VPATH)}internal/encoding/ctype.h +pathname.$(OBJEXT): {$(VPATH)}internal/encoding/encoding.h +pathname.$(OBJEXT): {$(VPATH)}internal/encoding/pathname.h +pathname.$(OBJEXT): {$(VPATH)}internal/encoding/re.h +pathname.$(OBJEXT): {$(VPATH)}internal/encoding/sprintf.h +pathname.$(OBJEXT): {$(VPATH)}internal/encoding/string.h +pathname.$(OBJEXT): {$(VPATH)}internal/encoding/symbol.h +pathname.$(OBJEXT): {$(VPATH)}internal/encoding/transcode.h +pathname.$(OBJEXT): {$(VPATH)}internal/error.h +pathname.$(OBJEXT): {$(VPATH)}internal/eval.h +pathname.$(OBJEXT): {$(VPATH)}internal/event.h +pathname.$(OBJEXT): {$(VPATH)}internal/fl_type.h +pathname.$(OBJEXT): {$(VPATH)}internal/gc.h +pathname.$(OBJEXT): {$(VPATH)}internal/glob.h +pathname.$(OBJEXT): {$(VPATH)}internal/globals.h +pathname.$(OBJEXT): {$(VPATH)}internal/has/attribute.h +pathname.$(OBJEXT): {$(VPATH)}internal/has/builtin.h +pathname.$(OBJEXT): {$(VPATH)}internal/has/c_attribute.h +pathname.$(OBJEXT): {$(VPATH)}internal/has/cpp_attribute.h +pathname.$(OBJEXT): {$(VPATH)}internal/has/declspec_attribute.h +pathname.$(OBJEXT): {$(VPATH)}internal/has/extension.h +pathname.$(OBJEXT): {$(VPATH)}internal/has/feature.h +pathname.$(OBJEXT): {$(VPATH)}internal/has/warning.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/array.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/bignum.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/class.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/compar.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/complex.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/cont.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/dir.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/enum.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/enumerator.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/error.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/eval.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/file.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/hash.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/io.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/load.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/marshal.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/numeric.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/object.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/parse.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/proc.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/process.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/random.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/range.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/rational.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/re.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/select.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/set.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/signal.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/string.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/struct.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/thread.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/time.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/variable.h +pathname.$(OBJEXT): {$(VPATH)}internal/intern/vm.h +pathname.$(OBJEXT): {$(VPATH)}internal/interpreter.h +pathname.$(OBJEXT): {$(VPATH)}internal/iterator.h +pathname.$(OBJEXT): {$(VPATH)}internal/memory.h +pathname.$(OBJEXT): {$(VPATH)}internal/method.h +pathname.$(OBJEXT): {$(VPATH)}internal/module.h +pathname.$(OBJEXT): {$(VPATH)}internal/newobj.h +pathname.$(OBJEXT): {$(VPATH)}internal/scan_args.h +pathname.$(OBJEXT): {$(VPATH)}internal/special_consts.h +pathname.$(OBJEXT): {$(VPATH)}internal/static_assert.h +pathname.$(OBJEXT): {$(VPATH)}internal/stdalign.h +pathname.$(OBJEXT): {$(VPATH)}internal/stdbool.h +pathname.$(OBJEXT): {$(VPATH)}internal/stdckdint.h +pathname.$(OBJEXT): {$(VPATH)}internal/symbol.h +pathname.$(OBJEXT): {$(VPATH)}internal/value.h +pathname.$(OBJEXT): {$(VPATH)}internal/value_type.h +pathname.$(OBJEXT): {$(VPATH)}internal/variable.h +pathname.$(OBJEXT): {$(VPATH)}internal/warning_push.h +pathname.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +pathname.$(OBJEXT): {$(VPATH)}missing.h +pathname.$(OBJEXT): {$(VPATH)}onigmo.h +pathname.$(OBJEXT): {$(VPATH)}oniguruma.h +pathname.$(OBJEXT): {$(VPATH)}pathname.c +pathname.$(OBJEXT): {$(VPATH)}pathname_builtin.rbinc +pathname.$(OBJEXT): {$(VPATH)}ruby.h +pathname.$(OBJEXT): {$(VPATH)}st.h +pathname.$(OBJEXT): {$(VPATH)}subst.h prism/api_node.$(OBJEXT): $(hdrdir)/ruby.h prism/api_node.$(OBJEXT): $(hdrdir)/ruby/ruby.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/defines.h @@ -12781,6 +13201,7 @@ prism/api_node.$(OBJEXT): {$(VPATH)}internal/intern/re.h prism/api_node.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h prism/api_node.$(OBJEXT): {$(VPATH)}internal/intern/select.h prism/api_node.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +prism/api_node.$(OBJEXT): {$(VPATH)}internal/intern/set.h prism/api_node.$(OBJEXT): {$(VPATH)}internal/intern/signal.h prism/api_node.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h prism/api_node.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -12976,6 +13397,7 @@ prism/api_pack.$(OBJEXT): {$(VPATH)}internal/intern/re.h prism/api_pack.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h prism/api_pack.$(OBJEXT): {$(VPATH)}internal/intern/select.h prism/api_pack.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +prism/api_pack.$(OBJEXT): {$(VPATH)}internal/intern/set.h prism/api_pack.$(OBJEXT): {$(VPATH)}internal/intern/signal.h prism/api_pack.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h prism/api_pack.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -13185,6 +13607,7 @@ prism/extension.$(OBJEXT): {$(VPATH)}internal/intern/re.h prism/extension.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h prism/extension.$(OBJEXT): {$(VPATH)}internal/intern/select.h prism/extension.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +prism/extension.$(OBJEXT): {$(VPATH)}internal/intern/set.h prism/extension.$(OBJEXT): {$(VPATH)}internal/intern/signal.h prism/extension.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h prism/extension.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -13574,6 +13997,7 @@ prism_init.$(OBJEXT): {$(VPATH)}internal/intern/re.h prism_init.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h prism_init.$(OBJEXT): {$(VPATH)}internal/intern/select.h prism_init.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +prism_init.$(OBJEXT): {$(VPATH)}internal/intern/set.h prism_init.$(OBJEXT): {$(VPATH)}internal/intern/signal.h prism_init.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h prism_init.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -13800,6 +14224,7 @@ proc.$(OBJEXT): {$(VPATH)}internal/intern/re.h proc.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h proc.$(OBJEXT): {$(VPATH)}internal/intern/select.h proc.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +proc.$(OBJEXT): {$(VPATH)}internal/intern/set.h proc.$(OBJEXT): {$(VPATH)}internal/intern/signal.h proc.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h proc.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -13837,6 +14262,8 @@ proc.$(OBJEXT): {$(VPATH)}prism/diagnostic.h proc.$(OBJEXT): {$(VPATH)}prism/version.h proc.$(OBJEXT): {$(VPATH)}prism_compile.h proc.$(OBJEXT): {$(VPATH)}proc.c +proc.$(OBJEXT): {$(VPATH)}ractor.h +proc.$(OBJEXT): {$(VPATH)}ractor_core.h proc.$(OBJEXT): {$(VPATH)}ruby_assert.h proc.$(OBJEXT): {$(VPATH)}ruby_atomic.h proc.$(OBJEXT): {$(VPATH)}rubyparser.h @@ -14031,6 +14458,7 @@ process.$(OBJEXT): {$(VPATH)}internal/intern/re.h process.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h process.$(OBJEXT): {$(VPATH)}internal/intern/select.h process.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +process.$(OBJEXT): {$(VPATH)}internal/intern/set.h process.$(OBJEXT): {$(VPATH)}internal/intern/signal.h process.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h process.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -14065,6 +14493,7 @@ process.$(OBJEXT): {$(VPATH)}onigmo.h process.$(OBJEXT): {$(VPATH)}oniguruma.h process.$(OBJEXT): {$(VPATH)}process.c process.$(OBJEXT): {$(VPATH)}ractor.h +process.$(OBJEXT): {$(VPATH)}ractor_core.h process.$(OBJEXT): {$(VPATH)}ruby_assert.h process.$(OBJEXT): {$(VPATH)}ruby_atomic.h process.$(OBJEXT): {$(VPATH)}rubyparser.h @@ -14257,6 +14686,7 @@ ractor.$(OBJEXT): {$(VPATH)}internal/intern/re.h ractor.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h ractor.$(OBJEXT): {$(VPATH)}internal/intern/select.h ractor.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +ractor.$(OBJEXT): {$(VPATH)}internal/intern/set.h ractor.$(OBJEXT): {$(VPATH)}internal/intern/signal.h ractor.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h ractor.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -14292,6 +14722,7 @@ ractor.$(OBJEXT): {$(VPATH)}ractor.c ractor.$(OBJEXT): {$(VPATH)}ractor.h ractor.$(OBJEXT): {$(VPATH)}ractor.rbinc ractor.$(OBJEXT): {$(VPATH)}ractor_core.h +ractor.$(OBJEXT): {$(VPATH)}ractor_sync.c ractor.$(OBJEXT): {$(VPATH)}ruby_assert.h ractor.$(OBJEXT): {$(VPATH)}ruby_atomic.h ractor.$(OBJEXT): {$(VPATH)}rubyparser.h @@ -14472,6 +14903,7 @@ random.$(OBJEXT): {$(VPATH)}internal/intern/re.h random.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h random.$(OBJEXT): {$(VPATH)}internal/intern/select.h random.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +random.$(OBJEXT): {$(VPATH)}internal/intern/set.h random.$(OBJEXT): {$(VPATH)}internal/intern/signal.h random.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h random.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -14679,6 +15111,7 @@ range.$(OBJEXT): {$(VPATH)}internal/intern/re.h range.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h range.$(OBJEXT): {$(VPATH)}internal/intern/select.h range.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +range.$(OBJEXT): {$(VPATH)}internal/intern/set.h range.$(OBJEXT): {$(VPATH)}internal/intern/signal.h range.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h range.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -14880,6 +15313,7 @@ rational.$(OBJEXT): {$(VPATH)}internal/intern/re.h rational.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h rational.$(OBJEXT): {$(VPATH)}internal/intern/select.h rational.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +rational.$(OBJEXT): {$(VPATH)}internal/intern/set.h rational.$(OBJEXT): {$(VPATH)}internal/intern/signal.h rational.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h rational.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -15095,6 +15529,7 @@ re.$(OBJEXT): {$(VPATH)}internal/intern/re.h re.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h re.$(OBJEXT): {$(VPATH)}internal/intern/select.h re.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +re.$(OBJEXT): {$(VPATH)}internal/intern/set.h re.$(OBJEXT): {$(VPATH)}internal/intern/signal.h re.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h re.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -15126,6 +15561,8 @@ re.$(OBJEXT): {$(VPATH)}missing.h re.$(OBJEXT): {$(VPATH)}node.h re.$(OBJEXT): {$(VPATH)}onigmo.h re.$(OBJEXT): {$(VPATH)}oniguruma.h +re.$(OBJEXT): {$(VPATH)}ractor.h +re.$(OBJEXT): {$(VPATH)}ractor_core.h re.$(OBJEXT): {$(VPATH)}re.c re.$(OBJEXT): {$(VPATH)}re.h re.$(OBJEXT): {$(VPATH)}regenc.h @@ -15141,6 +15578,7 @@ re.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h re.$(OBJEXT): {$(VPATH)}thread_native.h re.$(OBJEXT): {$(VPATH)}util.h re.$(OBJEXT): {$(VPATH)}vm_core.h +re.$(OBJEXT): {$(VPATH)}vm_debug.h re.$(OBJEXT): {$(VPATH)}vm_opts.h regcomp.$(OBJEXT): $(hdrdir)/ruby.h regcomp.$(OBJEXT): $(hdrdir)/ruby/ruby.h @@ -15270,6 +15708,7 @@ regcomp.$(OBJEXT): {$(VPATH)}internal/intern/re.h regcomp.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h regcomp.$(OBJEXT): {$(VPATH)}internal/intern/select.h regcomp.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +regcomp.$(OBJEXT): {$(VPATH)}internal/intern/set.h regcomp.$(OBJEXT): {$(VPATH)}internal/intern/signal.h regcomp.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h regcomp.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -15432,6 +15871,7 @@ regenc.$(OBJEXT): {$(VPATH)}internal/intern/re.h regenc.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h regenc.$(OBJEXT): {$(VPATH)}internal/intern/select.h regenc.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +regenc.$(OBJEXT): {$(VPATH)}internal/intern/set.h regenc.$(OBJEXT): {$(VPATH)}internal/intern/signal.h regenc.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h regenc.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -15593,6 +16033,7 @@ regerror.$(OBJEXT): {$(VPATH)}internal/intern/re.h regerror.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h regerror.$(OBJEXT): {$(VPATH)}internal/intern/select.h regerror.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +regerror.$(OBJEXT): {$(VPATH)}internal/intern/set.h regerror.$(OBJEXT): {$(VPATH)}internal/intern/signal.h regerror.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h regerror.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -15754,6 +16195,7 @@ regexec.$(OBJEXT): {$(VPATH)}internal/intern/re.h regexec.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h regexec.$(OBJEXT): {$(VPATH)}internal/intern/select.h regexec.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +regexec.$(OBJEXT): {$(VPATH)}internal/intern/set.h regexec.$(OBJEXT): {$(VPATH)}internal/intern/signal.h regexec.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h regexec.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -15919,6 +16361,7 @@ regparse.$(OBJEXT): {$(VPATH)}internal/intern/re.h regparse.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h regparse.$(OBJEXT): {$(VPATH)}internal/intern/select.h regparse.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +regparse.$(OBJEXT): {$(VPATH)}internal/intern/set.h regparse.$(OBJEXT): {$(VPATH)}internal/intern/signal.h regparse.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h regparse.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -16081,6 +16524,7 @@ regsyntax.$(OBJEXT): {$(VPATH)}internal/intern/re.h regsyntax.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h regsyntax.$(OBJEXT): {$(VPATH)}internal/intern/select.h regsyntax.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +regsyntax.$(OBJEXT): {$(VPATH)}internal/intern/set.h regsyntax.$(OBJEXT): {$(VPATH)}internal/intern/signal.h regsyntax.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h regsyntax.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -16332,6 +16776,7 @@ ruby.$(OBJEXT): {$(VPATH)}internal/intern/re.h ruby.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h ruby.$(OBJEXT): {$(VPATH)}internal/intern/select.h ruby.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +ruby.$(OBJEXT): {$(VPATH)}internal/intern/set.h ruby.$(OBJEXT): {$(VPATH)}internal/intern/signal.h ruby.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h ruby.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -16539,6 +16984,7 @@ ruby_parser.$(OBJEXT): {$(VPATH)}internal/intern/re.h ruby_parser.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h ruby_parser.$(OBJEXT): {$(VPATH)}internal/intern/select.h ruby_parser.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +ruby_parser.$(OBJEXT): {$(VPATH)}internal/intern/set.h ruby_parser.$(OBJEXT): {$(VPATH)}internal/intern/signal.h ruby_parser.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h ruby_parser.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -16736,6 +17182,7 @@ scheduler.$(OBJEXT): {$(VPATH)}internal/intern/re.h scheduler.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h scheduler.$(OBJEXT): {$(VPATH)}internal/intern/select.h scheduler.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +scheduler.$(OBJEXT): {$(VPATH)}internal/intern/set.h scheduler.$(OBJEXT): {$(VPATH)}internal/intern/signal.h scheduler.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h scheduler.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -16790,6 +17237,7 @@ set.$(OBJEXT): $(top_srcdir)/internal/array.h set.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h set.$(OBJEXT): $(top_srcdir)/internal/bits.h set.$(OBJEXT): $(top_srcdir)/internal/compilers.h +set.$(OBJEXT): $(top_srcdir)/internal/error.h set.$(OBJEXT): $(top_srcdir)/internal/gc.h set.$(OBJEXT): $(top_srcdir)/internal/hash.h set.$(OBJEXT): $(top_srcdir)/internal/imemo.h @@ -16799,6 +17247,7 @@ set.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h set.$(OBJEXT): $(top_srcdir)/internal/serial.h set.$(OBJEXT): $(top_srcdir)/internal/set_table.h set.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +set.$(OBJEXT): $(top_srcdir)/internal/string.h set.$(OBJEXT): $(top_srcdir)/internal/symbol.h set.$(OBJEXT): $(top_srcdir)/internal/variable.h set.$(OBJEXT): $(top_srcdir)/internal/vm.h @@ -16945,6 +17394,7 @@ set.$(OBJEXT): {$(VPATH)}internal/intern/re.h set.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h set.$(OBJEXT): {$(VPATH)}internal/intern/select.h set.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +set.$(OBJEXT): {$(VPATH)}internal/intern/set.h set.$(OBJEXT): {$(VPATH)}internal/intern/signal.h set.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h set.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -17116,6 +17566,7 @@ setproctitle.$(OBJEXT): {$(VPATH)}internal/intern/re.h setproctitle.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h setproctitle.$(OBJEXT): {$(VPATH)}internal/intern/select.h setproctitle.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +setproctitle.$(OBJEXT): {$(VPATH)}internal/intern/set.h setproctitle.$(OBJEXT): {$(VPATH)}internal/intern/signal.h setproctitle.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h setproctitle.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -17314,6 +17765,7 @@ shape.$(OBJEXT): {$(VPATH)}internal/intern/re.h shape.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h shape.$(OBJEXT): {$(VPATH)}internal/intern/select.h shape.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +shape.$(OBJEXT): {$(VPATH)}internal/intern/set.h shape.$(OBJEXT): {$(VPATH)}internal/intern/signal.h shape.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h shape.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -17529,6 +17981,7 @@ signal.$(OBJEXT): {$(VPATH)}internal/intern/re.h signal.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h signal.$(OBJEXT): {$(VPATH)}internal/intern/select.h signal.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +signal.$(OBJEXT): {$(VPATH)}internal/intern/set.h signal.$(OBJEXT): {$(VPATH)}internal/intern/signal.h signal.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h signal.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -17736,6 +18189,7 @@ sprintf.$(OBJEXT): {$(VPATH)}internal/intern/re.h sprintf.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h sprintf.$(OBJEXT): {$(VPATH)}internal/intern/select.h sprintf.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +sprintf.$(OBJEXT): {$(VPATH)}internal/intern/set.h sprintf.$(OBJEXT): {$(VPATH)}internal/intern/signal.h sprintf.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h sprintf.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -17910,6 +18364,7 @@ st.$(OBJEXT): {$(VPATH)}internal/intern/re.h st.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h st.$(OBJEXT): {$(VPATH)}internal/intern/select.h st.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +st.$(OBJEXT): {$(VPATH)}internal/intern/set.h st.$(OBJEXT): {$(VPATH)}internal/intern/signal.h st.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h st.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -18087,6 +18542,7 @@ strftime.$(OBJEXT): {$(VPATH)}internal/intern/re.h strftime.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h strftime.$(OBJEXT): {$(VPATH)}internal/intern/select.h strftime.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +strftime.$(OBJEXT): {$(VPATH)}internal/intern/set.h strftime.$(OBJEXT): {$(VPATH)}internal/intern/signal.h strftime.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h strftime.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -18134,6 +18590,7 @@ string.$(OBJEXT): $(top_srcdir)/internal/bits.h string.$(OBJEXT): $(top_srcdir)/internal/class.h string.$(OBJEXT): $(top_srcdir)/internal/compar.h string.$(OBJEXT): $(top_srcdir)/internal/compilers.h +string.$(OBJEXT): $(top_srcdir)/internal/concurrent_set.h string.$(OBJEXT): $(top_srcdir)/internal/encoding.h string.$(OBJEXT): $(top_srcdir)/internal/error.h string.$(OBJEXT): $(top_srcdir)/internal/fixnum.h @@ -18300,6 +18757,7 @@ string.$(OBJEXT): {$(VPATH)}internal/intern/re.h string.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h string.$(OBJEXT): {$(VPATH)}internal/intern/select.h string.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +string.$(OBJEXT): {$(VPATH)}internal/intern/set.h string.$(OBJEXT): {$(VPATH)}internal/intern/signal.h string.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h string.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -18551,6 +19009,7 @@ struct.$(OBJEXT): {$(VPATH)}internal/intern/re.h struct.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h struct.$(OBJEXT): {$(VPATH)}internal/intern/select.h struct.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +struct.$(OBJEXT): {$(VPATH)}internal/intern/set.h struct.$(OBJEXT): {$(VPATH)}internal/intern/signal.h struct.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h struct.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -18767,6 +19226,7 @@ symbol.$(OBJEXT): {$(VPATH)}internal/intern/re.h symbol.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h symbol.$(OBJEXT): {$(VPATH)}internal/intern/select.h symbol.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +symbol.$(OBJEXT): {$(VPATH)}internal/intern/set.h symbol.$(OBJEXT): {$(VPATH)}internal/intern/signal.h symbol.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h symbol.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -19017,6 +19477,7 @@ thread.$(OBJEXT): {$(VPATH)}internal/intern/re.h thread.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h thread.$(OBJEXT): {$(VPATH)}internal/intern/select.h thread.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +thread.$(OBJEXT): {$(VPATH)}internal/intern/set.h thread.$(OBJEXT): {$(VPATH)}internal/intern/signal.h thread.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h thread.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -19245,6 +19706,7 @@ time.$(OBJEXT): {$(VPATH)}internal/intern/re.h time.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h time.$(OBJEXT): {$(VPATH)}internal/intern/select.h time.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +time.$(OBJEXT): {$(VPATH)}internal/intern/set.h time.$(OBJEXT): {$(VPATH)}internal/intern/signal.h time.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h time.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -19315,6 +19777,7 @@ transcode.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h transcode.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h transcode.$(OBJEXT): {$(VPATH)}config.h transcode.$(OBJEXT): {$(VPATH)}constant.h +transcode.$(OBJEXT): {$(VPATH)}debug_counter.h transcode.$(OBJEXT): {$(VPATH)}defines.h transcode.$(OBJEXT): {$(VPATH)}encoding.h transcode.$(OBJEXT): {$(VPATH)}id.h @@ -19444,6 +19907,7 @@ transcode.$(OBJEXT): {$(VPATH)}internal/intern/re.h transcode.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h transcode.$(OBJEXT): {$(VPATH)}internal/intern/select.h transcode.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +transcode.$(OBJEXT): {$(VPATH)}internal/intern/set.h transcode.$(OBJEXT): {$(VPATH)}internal/intern/signal.h transcode.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h transcode.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -19478,6 +19942,8 @@ transcode.$(OBJEXT): {$(VPATH)}st.h transcode.$(OBJEXT): {$(VPATH)}subst.h transcode.$(OBJEXT): {$(VPATH)}transcode.c transcode.$(OBJEXT): {$(VPATH)}transcode_data.h +transcode.$(OBJEXT): {$(VPATH)}vm_debug.h +transcode.$(OBJEXT): {$(VPATH)}vm_sync.h util.$(OBJEXT): $(hdrdir)/ruby/ruby.h util.$(OBJEXT): $(top_srcdir)/internal/array.h util.$(OBJEXT): $(top_srcdir)/internal/compilers.h @@ -19617,6 +20083,7 @@ util.$(OBJEXT): {$(VPATH)}internal/intern/re.h util.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h util.$(OBJEXT): {$(VPATH)}internal/intern/select.h util.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +util.$(OBJEXT): {$(VPATH)}internal/intern/set.h util.$(OBJEXT): {$(VPATH)}internal/intern/signal.h util.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h util.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -19820,6 +20287,7 @@ variable.$(OBJEXT): {$(VPATH)}internal/intern/re.h variable.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h variable.$(OBJEXT): {$(VPATH)}internal/intern/select.h variable.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +variable.$(OBJEXT): {$(VPATH)}internal/intern/set.h variable.$(OBJEXT): {$(VPATH)}internal/intern/signal.h variable.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h variable.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -20035,6 +20503,7 @@ version.$(OBJEXT): {$(VPATH)}internal/intern/re.h version.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h version.$(OBJEXT): {$(VPATH)}internal/intern/select.h version.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +version.$(OBJEXT): {$(VPATH)}internal/intern/set.h version.$(OBJEXT): {$(VPATH)}internal/intern/signal.h version.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h version.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -20295,6 +20764,7 @@ vm.$(OBJEXT): {$(VPATH)}internal/intern/re.h vm.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h vm.$(OBJEXT): {$(VPATH)}internal/intern/select.h vm.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +vm.$(OBJEXT): {$(VPATH)}internal/intern/set.h vm.$(OBJEXT): {$(VPATH)}internal/intern/signal.h vm.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h vm.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -20553,6 +21023,7 @@ vm_backtrace.$(OBJEXT): {$(VPATH)}internal/intern/re.h vm_backtrace.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h vm_backtrace.$(OBJEXT): {$(VPATH)}internal/intern/select.h vm_backtrace.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +vm_backtrace.$(OBJEXT): {$(VPATH)}internal/intern/set.h vm_backtrace.$(OBJEXT): {$(VPATH)}internal/intern/signal.h vm_backtrace.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h vm_backtrace.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -20784,6 +21255,7 @@ vm_dump.$(OBJEXT): {$(VPATH)}internal/intern/re.h vm_dump.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h vm_dump.$(OBJEXT): {$(VPATH)}internal/intern/select.h vm_dump.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +vm_dump.$(OBJEXT): {$(VPATH)}internal/intern/set.h vm_dump.$(OBJEXT): {$(VPATH)}internal/intern/signal.h vm_dump.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h vm_dump.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -20997,6 +21469,7 @@ vm_sync.$(OBJEXT): {$(VPATH)}internal/intern/re.h vm_sync.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h vm_sync.$(OBJEXT): {$(VPATH)}internal/intern/select.h vm_sync.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +vm_sync.$(OBJEXT): {$(VPATH)}internal/intern/set.h vm_sync.$(OBJEXT): {$(VPATH)}internal/intern/signal.h vm_sync.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h vm_sync.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -21233,6 +21706,7 @@ vm_trace.$(OBJEXT): {$(VPATH)}internal/intern/re.h vm_trace.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h vm_trace.$(OBJEXT): {$(VPATH)}internal/intern/select.h vm_trace.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +vm_trace.$(OBJEXT): {$(VPATH)}internal/intern/set.h vm_trace.$(OBJEXT): {$(VPATH)}internal/intern/signal.h vm_trace.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h vm_trace.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -21445,6 +21919,7 @@ weakmap.$(OBJEXT): {$(VPATH)}internal/intern/re.h weakmap.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h weakmap.$(OBJEXT): {$(VPATH)}internal/intern/select.h weakmap.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +weakmap.$(OBJEXT): {$(VPATH)}internal/intern/set.h weakmap.$(OBJEXT): {$(VPATH)}internal/intern/signal.h weakmap.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h weakmap.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -21681,6 +22156,7 @@ yjit.$(OBJEXT): {$(VPATH)}internal/intern/re.h yjit.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h yjit.$(OBJEXT): {$(VPATH)}internal/intern/select.h yjit.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +yjit.$(OBJEXT): {$(VPATH)}internal/intern/set.h yjit.$(OBJEXT): {$(VPATH)}internal/intern/signal.h yjit.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h yjit.$(OBJEXT): {$(VPATH)}internal/intern/string.h @@ -21738,6 +22214,7 @@ yjit.$(OBJEXT): {$(VPATH)}vm_sync.h yjit.$(OBJEXT): {$(VPATH)}yjit.c yjit.$(OBJEXT): {$(VPATH)}yjit.h yjit.$(OBJEXT): {$(VPATH)}yjit.rbinc +yjit.$(OBJEXT): {$(VPATH)}zjit.h zjit.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h zjit.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h zjit.$(OBJEXT): $(CCAN_DIR)/list/list.h diff --git a/compile.c b/compile.c index f2746eb0a4..8d5cb45904 100644 --- a/compile.c +++ b/compile.c @@ -2178,13 +2178,14 @@ iseq_set_local_table(rb_iseq_t *iseq, const rb_ast_id_table_t *tbl, const NODE * // then its local table should only be `...` // FIXME: I think this should be fixed in the AST rather than special case here. if (args->forwarding && args->pre_args_num == 0 && !args->opt_args) { + CHECK(size >= 3); size -= 3; offset += 3; } } if (size > 0) { - ID *ids = (ID *)ALLOC_N(ID, size); + ID *ids = ALLOC_N(ID, size); MEMCPY(ids, tbl->ids + offset, ID, size); ISEQ_BODY(iseq)->local_table = ids; } @@ -2481,7 +2482,7 @@ array_to_idlist(VALUE arr) RUBY_ASSERT(RB_TYPE_P(arr, T_ARRAY)); long size = RARRAY_LEN(arr); ID *ids = (ID *)ALLOC_N(ID, size + 1); - for (int i = 0; i < size; i++) { + for (long i = 0; i < size; i++) { VALUE sym = RARRAY_AREF(arr, i); ids[i] = SYM2ID(sym); } @@ -3490,7 +3491,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal iobj->insn_id = BIN(opt_ary_freeze); iobj->operand_size = 2; iobj->operands = compile_data_calloc2(iseq, iobj->operand_size, sizeof(VALUE)); - iobj->operands[0] = rb_cArray_empty_frozen; + RB_OBJ_WRITE(iseq, &iobj->operands[0], rb_cArray_empty_frozen); iobj->operands[1] = (VALUE)ci; ELEM_REMOVE(next); } @@ -3513,7 +3514,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal iobj->insn_id = BIN(opt_hash_freeze); iobj->operand_size = 2; iobj->operands = compile_data_calloc2(iseq, iobj->operand_size, sizeof(VALUE)); - iobj->operands[0] = rb_cHash_empty_frozen; + RB_OBJ_WRITE(iseq, &iobj->operands[0], rb_cHash_empty_frozen); iobj->operands[1] = (VALUE)ci; ELEM_REMOVE(next); } @@ -4091,7 +4092,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal unsigned int flags = vm_ci_flag(ci); if ((flags & set_flags) == set_flags && !(flags & unset_flags)) { ((INSN*)niobj)->insn_id = BIN(putobject); - OPERAND_AT(niobj, 0) = rb_hash_freeze(rb_hash_resurrect(OPERAND_AT(niobj, 0))); + RB_OBJ_WRITE(iseq, &OPERAND_AT(niobj, 0), rb_hash_freeze(rb_hash_resurrect(OPERAND_AT(niobj, 0)))); const struct rb_callinfo *nci = vm_ci_new(vm_ci_mid(ci), flags & ~VM_CALL_KW_SPLAT_MUT, vm_ci_argc(ci), vm_ci_kwarg(ci)); @@ -6640,6 +6641,14 @@ setup_args_dup_rest_p(const NODE *argn) return false; case NODE_COLON2: return setup_args_dup_rest_p(RNODE_COLON2(argn)->nd_head); + case NODE_LIST: + while (argn) { + if (setup_args_dup_rest_p(RNODE_LIST(argn)->nd_head)) { + return true; + } + argn = RNODE_LIST(argn)->nd_next; + } + return false; default: return true; } @@ -9254,12 +9263,13 @@ compile_builtin_mandatory_only_method(rb_iseq_t *iseq, const NODE *node, const N VALUE ast_value = rb_ruby_ast_new(RNODE(&scope_node)); - ISEQ_BODY(iseq)->mandatory_only_iseq = + const rb_iseq_t *mandatory_only_iseq = rb_iseq_new_with_opt(ast_value, rb_iseq_base_label(iseq), rb_iseq_path(iseq), rb_iseq_realpath(iseq), nd_line(line_node), NULL, 0, ISEQ_TYPE_METHOD, ISEQ_COMPILE_DATA(iseq)->option, ISEQ_BODY(iseq)->variable.script_lines); + RB_OBJ_WRITE(iseq, &ISEQ_BODY(iseq)->mandatory_only_iseq, (VALUE)mandatory_only_iseq); ALLOCV_END(idtmp); return COMPILE_OK; @@ -9530,7 +9540,8 @@ compile_call(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, co ADD_LABEL(ret, not_basic_new_finish); ADD_INSN(ret, line_node, pop); - } else { + } + else { ADD_SEND_R(ret, line_node, mid, argc, parent_block, INT2FIX(flag), keywords); } @@ -12589,8 +12600,13 @@ static ibf_offset_t ibf_dump_write(struct ibf_dump *dump, const void *buff, unsigned long size) { ibf_offset_t pos = ibf_dump_pos(dump); +#if SIZEOF_LONG > SIZEOF_INT + /* ensure the resulting dump does not exceed UINT_MAX */ + if (size >= UINT_MAX || pos + size >= UINT_MAX) { + rb_raise(rb_eRuntimeError, "dump size exceeds"); + } +#endif rb_str_cat(dump->current_buffer->str, (const char *)buff, size); - /* TODO: overflow check */ return pos; } @@ -13283,12 +13299,13 @@ ibf_dump_catch_table(struct ibf_dump *dump, const rb_iseq_t *iseq) } } -static struct iseq_catch_table * -ibf_load_catch_table(const struct ibf_load *load, ibf_offset_t catch_table_offset, unsigned int size) +static void +ibf_load_catch_table(const struct ibf_load *load, ibf_offset_t catch_table_offset, unsigned int size, const rb_iseq_t *parent_iseq) { if (size) { - struct iseq_catch_table *table = ruby_xmalloc(iseq_catch_table_bytes(size)); + struct iseq_catch_table *table = ruby_xcalloc(1, iseq_catch_table_bytes(size)); table->size = size; + ISEQ_BODY(parent_iseq)->catch_table = table; ibf_offset_t reading_pos = catch_table_offset; @@ -13301,12 +13318,12 @@ ibf_load_catch_table(const struct ibf_load *load, ibf_offset_t catch_table_offse table->entries[i].cont = (unsigned int)ibf_load_small_value(load, &reading_pos); table->entries[i].sp = (unsigned int)ibf_load_small_value(load, &reading_pos); - table->entries[i].iseq = ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)iseq_index); + rb_iseq_t *catch_iseq = (rb_iseq_t *)ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)iseq_index); + RB_OBJ_WRITE(parent_iseq, UNALIGNED_MEMBER_PTR(&table->entries[i], iseq), catch_iseq); } - return table; } else { - return NULL; + ISEQ_BODY(parent_iseq)->catch_table = NULL; } } @@ -13377,6 +13394,14 @@ outer_variable_cmp(const void *a, const void *b, void *arg) { const struct outer_variable_pair *ap = (const struct outer_variable_pair *)a; const struct outer_variable_pair *bp = (const struct outer_variable_pair *)b; + + if (!ap->name) { + return -1; + } + else if (!bp->name) { + return 1; + } + return rb_str_cmp(ap->name, bp->name); } @@ -13811,10 +13836,15 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset) load_body->insns_info.body = ibf_load_insns_info_body(load, insns_info_body_offset, insns_info_size); load_body->insns_info.positions = ibf_load_insns_info_positions(load, insns_info_positions_offset, insns_info_size); load_body->local_table = ibf_load_local_table(load, local_table_offset, local_table_size); - load_body->catch_table = ibf_load_catch_table(load, catch_table_offset, catch_table_size); - load_body->parent_iseq = ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)parent_iseq_index); - load_body->local_iseq = ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)local_iseq_index); - load_body->mandatory_only_iseq = ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)mandatory_only_iseq_index); + ibf_load_catch_table(load, catch_table_offset, catch_table_size, iseq); + + const rb_iseq_t *parent_iseq = ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)parent_iseq_index); + const rb_iseq_t *local_iseq = ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)local_iseq_index); + const rb_iseq_t *mandatory_only_iseq = ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)mandatory_only_iseq_index); + + RB_OBJ_WRITE(iseq, &load_body->parent_iseq, parent_iseq); + RB_OBJ_WRITE(iseq, &load_body->local_iseq, local_iseq); + RB_OBJ_WRITE(iseq, &load_body->mandatory_only_iseq, mandatory_only_iseq); // This must be done after the local table is loaded. if (load_body->param.keyword != NULL) { @@ -14430,7 +14460,7 @@ ibf_dump_object_object(struct ibf_dump *dump, VALUE obj) else { obj_header.internal = SPECIAL_CONST_P(obj) ? FALSE : (RBASIC_CLASS(obj) == 0) ? TRUE : FALSE; obj_header.special_const = FALSE; - obj_header.frozen = FL_TEST(obj, FL_FREEZE) ? TRUE : FALSE; + obj_header.frozen = OBJ_FROZEN(obj) ? TRUE : FALSE; ibf_dump_object_object_header(dump, obj_header); (*dump_object_functions[obj_header.type])(dump, obj); } diff --git a/complex.c b/complex.c index 05c991f35b..d6daee307c 100644 --- a/complex.c +++ b/complex.c @@ -1925,21 +1925,6 @@ nucomp_to_c(VALUE self) return self; } -/* - * call-seq: - * to_c -> (0+0i) - * - * Returns zero as a Complex: - * - * nil.to_c # => (0+0i) - * - */ -static VALUE -nilclass_to_c(VALUE self) -{ - return rb_complex_new1(INT2FIX(0)); -} - /* * call-seq: * to_c -> complex @@ -2693,7 +2678,6 @@ Init_Complex(void) rb_define_method(rb_cComplex, "to_r", nucomp_to_r, 0); rb_define_method(rb_cComplex, "rationalize", nucomp_rationalize, -1); rb_define_method(rb_cComplex, "to_c", nucomp_to_c, 0); - rb_define_method(rb_cNilClass, "to_c", nilclass_to_c, 0); rb_define_method(rb_cNumeric, "to_c", numeric_to_c, 0); rb_define_method(rb_cString, "to_c", string_to_c, 0); diff --git a/concurrent_set.c b/concurrent_set.c new file mode 100644 index 0000000000..876b4083e6 --- /dev/null +++ b/concurrent_set.c @@ -0,0 +1,325 @@ +#include "internal.h" +#include "internal/gc.h" +#include "internal/concurrent_set.h" +#include "ruby_atomic.h" +#include "ruby/atomic.h" +#include "vm_sync.h" + +enum concurrent_set_special_values { + CONCURRENT_SET_EMPTY, + CONCURRENT_SET_DELETED, + CONCURRENT_SET_MOVED, + CONCURRENT_SET_SPECIAL_VALUE_COUNT +}; + +struct concurrent_set_entry { + VALUE hash; + VALUE key; +}; + +struct concurrent_set { + rb_atomic_t size; + unsigned int capacity; + unsigned int deleted_entries; + const struct rb_concurrent_set_funcs *funcs; + struct concurrent_set_entry *entries; +}; + +static void +concurrent_set_free(void *ptr) +{ + struct concurrent_set *set = ptr; + xfree(set->entries); +} + +static size_t +concurrent_set_size(const void *ptr) +{ + const struct concurrent_set *set = ptr; + return sizeof(struct concurrent_set) + + (set->capacity * sizeof(struct concurrent_set_entry)); +} + +static const rb_data_type_t concurrent_set_type = { + .wrap_struct_name = "VM/concurrent_set", + .function = { + .dmark = NULL, + .dfree = concurrent_set_free, + .dsize = concurrent_set_size, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE +}; + +VALUE +rb_concurrent_set_new(const struct rb_concurrent_set_funcs *funcs, int capacity) +{ + struct concurrent_set *set; + VALUE obj = TypedData_Make_Struct(0, struct concurrent_set, &concurrent_set_type, set); + set->funcs = funcs; + set->entries = ZALLOC_N(struct concurrent_set_entry, capacity); + set->capacity = capacity; + return obj; +} + +struct concurrent_set_probe { + int idx; + int d; + int mask; +}; + +static int +concurrent_set_probe_start(struct concurrent_set_probe *probe, struct concurrent_set *set, VALUE hash) +{ + RUBY_ASSERT((set->capacity & (set->capacity - 1)) == 0); + probe->d = 0; + probe->mask = set->capacity - 1; + probe->idx = hash & probe->mask; + return probe->idx; +} + +static int +concurrent_set_probe_next(struct concurrent_set_probe *probe) +{ + probe->d++; + probe->idx = (probe->idx + probe->d) & probe->mask; + return probe->idx; +} + +static void +concurrent_set_try_resize_without_locking(VALUE old_set_obj, VALUE *set_obj_ptr) +{ + // Check if another thread has already resized. + if (RUBY_ATOMIC_VALUE_LOAD(*set_obj_ptr) != old_set_obj) { + return; + } + + struct concurrent_set *old_set = RTYPEDDATA_GET_DATA(old_set_obj); + + // This may overcount by up to the number of threads concurrently attempting to insert + // GC may also happen between now and the set being rebuilt + int expected_size = RUBY_ATOMIC_LOAD(old_set->size) - old_set->deleted_entries; + + struct concurrent_set_entry *old_entries = old_set->entries; + int old_capacity = old_set->capacity; + int new_capacity = old_capacity * 2; + if (new_capacity > expected_size * 8) { + new_capacity = old_capacity / 2; + } + else if (new_capacity > expected_size * 4) { + new_capacity = old_capacity; + } + + // May cause GC and therefore deletes, so must hapen first. + VALUE new_set_obj = rb_concurrent_set_new(old_set->funcs, new_capacity); + struct concurrent_set *new_set = RTYPEDDATA_GET_DATA(new_set_obj); + + for (int i = 0; i < old_capacity; i++) { + struct concurrent_set_entry *entry = &old_entries[i]; + VALUE key = RUBY_ATOMIC_VALUE_EXCHANGE(entry->key, CONCURRENT_SET_MOVED); + RUBY_ASSERT(key != CONCURRENT_SET_MOVED); + + if (key < CONCURRENT_SET_SPECIAL_VALUE_COUNT) continue; + if (rb_objspace_garbage_object_p(key)) continue; + + VALUE hash = RUBY_ATOMIC_VALUE_LOAD(entry->hash); + if (hash == 0) { + // Either in-progress insert or extremely unlikely 0 hash. + // Re-calculate the hash. + hash = old_set->funcs->hash(key); + } + RUBY_ASSERT(hash == old_set->funcs->hash(key)); + + // Insert key into new_set. + struct concurrent_set_probe probe; + int idx = concurrent_set_probe_start(&probe, new_set, hash); + + while (true) { + struct concurrent_set_entry *entry = &new_set->entries[idx]; + + if (entry->key == CONCURRENT_SET_EMPTY) { + new_set->size++; + + RUBY_ASSERT(new_set->size < new_set->capacity / 2); + RUBY_ASSERT(entry->hash == 0); + + entry->key = key; + entry->hash = hash; + break; + } + else { + RUBY_ASSERT(entry->key >= CONCURRENT_SET_SPECIAL_VALUE_COUNT); + } + + idx = concurrent_set_probe_next(&probe); + } + } + + RUBY_ATOMIC_VALUE_SET(*set_obj_ptr, new_set_obj); + + RB_GC_GUARD(old_set_obj); +} + +static void +concurrent_set_try_resize(VALUE old_set_obj, VALUE *set_obj_ptr) +{ + RB_VM_LOCKING() { + concurrent_set_try_resize_without_locking(old_set_obj, set_obj_ptr); + } +} + +VALUE +rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) +{ + RUBY_ASSERT(key >= CONCURRENT_SET_SPECIAL_VALUE_COUNT); + + bool inserting = false; + VALUE set_obj; + + retry: + set_obj = RUBY_ATOMIC_VALUE_LOAD(*set_obj_ptr); + RUBY_ASSERT(set_obj); + struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj); + + struct concurrent_set_probe probe; + VALUE hash = set->funcs->hash(key); + int idx = concurrent_set_probe_start(&probe, set, hash); + + while (true) { + struct concurrent_set_entry *entry = &set->entries[idx]; + VALUE curr_key = RUBY_ATOMIC_VALUE_LOAD(entry->key); + + switch (curr_key) { + case CONCURRENT_SET_EMPTY: { + // Not in set + if (!inserting) { + key = set->funcs->create(key, data); + RUBY_ASSERT(hash == set->funcs->hash(key)); + inserting = true; + } + + rb_atomic_t prev_size = RUBY_ATOMIC_FETCH_ADD(set->size, 1); + + if (UNLIKELY(prev_size > set->capacity / 2)) { + concurrent_set_try_resize(set_obj, set_obj_ptr); + + goto retry; + } + + curr_key = RUBY_ATOMIC_VALUE_CAS(entry->key, CONCURRENT_SET_EMPTY, key); + if (curr_key == CONCURRENT_SET_EMPTY) { + RUBY_ATOMIC_VALUE_SET(entry->hash, hash); + + RB_GC_GUARD(set_obj); + return key; + } + else { + // Entry was not inserted. + RUBY_ATOMIC_DEC(set->size); + + // Another thread won the race, try again at the same location. + continue; + } + } + case CONCURRENT_SET_DELETED: + break; + case CONCURRENT_SET_MOVED: + // Wait + RB_VM_LOCKING(); + + goto retry; + default: { + VALUE curr_hash = RUBY_ATOMIC_VALUE_LOAD(entry->hash); + if ((curr_hash == hash || curr_hash == 0) && set->funcs->cmp(key, curr_key)) { + // We've found a match. + if (UNLIKELY(rb_objspace_garbage_object_p(curr_key))) { + // This is a weakref set, so after marking but before sweeping is complete we may find a matching garbage object. + // Skip it and mark it as deleted. + RUBY_ATOMIC_VALUE_CAS(entry->key, curr_key, CONCURRENT_SET_DELETED); + + // Fall through and continue our search. + } + else { + RB_GC_GUARD(set_obj); + return curr_key; + } + } + + break; + } + } + + idx = concurrent_set_probe_next(&probe); + } +} + +VALUE +rb_concurrent_set_delete_by_identity(VALUE set_obj, VALUE key) +{ + // Assume locking and barrier (which there is no assert for). + ASSERT_vm_locking(); + + struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj); + + VALUE hash = set->funcs->hash(key); + + struct concurrent_set_probe probe; + int idx = concurrent_set_probe_start(&probe, set, hash); + + while (true) { + struct concurrent_set_entry *entry = &set->entries[idx]; + VALUE curr_key = RUBY_ATOMIC_VALUE_LOAD(entry->key); + + switch (curr_key) { + case CONCURRENT_SET_EMPTY: + // We didn't find our entry to delete. + return 0; + case CONCURRENT_SET_DELETED: + break; + case CONCURRENT_SET_MOVED: + rb_bug("rb_concurrent_set_delete_by_identity: moved entry"); + break; + default: + if (key == curr_key) { + entry->key = CONCURRENT_SET_DELETED; + set->deleted_entries++; + return curr_key; + } + break; + } + + idx = concurrent_set_probe_next(&probe); + } +} + +void +rb_concurrent_set_foreach_with_replace(VALUE set_obj, int (*callback)(VALUE *key, void *data), void *data) +{ + // Assume locking and barrier (which there is no assert for). + ASSERT_vm_locking(); + + struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj); + + for (unsigned int i = 0; i < set->capacity; i++) { + VALUE key = set->entries[i].key; + + switch (key) { + case CONCURRENT_SET_EMPTY: + case CONCURRENT_SET_DELETED: + continue; + case CONCURRENT_SET_MOVED: + rb_bug("rb_concurrent_set_foreach_with_replace: moved entry"); + break; + default: { + int ret = callback(&set->entries[i].key, data); + switch (ret) { + case ST_STOP: + return; + case ST_DELETE: + set->entries[i].key = CONCURRENT_SET_DELETED; + break; + } + break; + } + } + } +} diff --git a/configure.ac b/configure.ac index 3c01b51239..07a6d82314 100644 --- a/configure.ac +++ b/configure.ac @@ -1006,7 +1006,7 @@ AS_IF([test "x$OPT_DIR" != x], [ save_IFS="$IFS" IFS="$PATH_SEPARATOR" val= PWD= for dir in $OPT_DIR; do test -z "$dir" && continue - dir=`eval $CHDIR '"$dir"' && pwd` || continue + dir=`eval $CHDIR '"$dir"' 2>/dev/null && pwd` || continue val="${val:+$val$PATH_SEPARATOR}$dir" done IFS="$save_IFS" OPT_DIR="$val" @@ -1743,6 +1743,18 @@ AS_IF([test "$GCC" = yes], [ [rb_cv_gcc_atomic_builtins=no])]) AS_IF([test "$rb_cv_gcc_atomic_builtins" = yes], [ AC_DEFINE(HAVE_GCC_ATOMIC_BUILTINS) + AC_CACHE_CHECK([for 64bit __atomic builtins], [rb_cv_gcc_atomic_builtins_64], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[@%:@include + uint64_t atomic_var;]], + [[ + __atomic_load_n(&atomic_var, __ATOMIC_RELAXED); + __atomic_store_n(&atomic_var, 0, __ATOMIC_RELAXED); + ]])], + [rb_cv_gcc_atomic_builtins_64=yes], + [rb_cv_gcc_atomic_builtins_64=no])]) + AS_IF([test "$rb_cv_gcc_atomic_builtins_64" = yes], [ + AC_DEFINE(HAVE_GCC_ATOMIC_BUILTINS_64) + ]) ]) AC_CACHE_CHECK([for __sync builtins], [rb_cv_gcc_sync_builtins], [ @@ -3924,46 +3936,33 @@ AC_ARG_ENABLE(yjit, CARGO= CARGO_BUILD_ARGS= YJIT_LIBS= +JIT_CARGO_SUPPORT=no AS_CASE(["${YJIT_SUPPORT}"], [yes|dev|stats|dev_nodebug], [ AS_IF([test x"$RUSTC" = "xno"], AC_MSG_ERROR([rustc is required. Installation instructions available at https://www.rust-lang.org/tools/install]) ) - AS_IF([test x"$ZJIT_SUPPORT" != "xno"], - AC_MSG_ERROR([YJIT cannot be enabled when ZJIT is enabled]) - ) AS_CASE(["${YJIT_SUPPORT}"], [yes], [ - rb_rust_target_subdir=release ], [dev], [ - rb_rust_target_subdir=debug - CARGO_BUILD_ARGS='--features disasm,runtime_checks' + rb_cargo_features='disasm,runtime_checks' + JIT_CARGO_SUPPORT=dev AC_DEFINE(RUBY_DEBUG, 1) ], [dev_nodebug], [ - rb_rust_target_subdir=dev_nodebug - CARGO_BUILD_ARGS='--profile dev_nodebug --features disasm' + rb_cargo_features='disasm' + JIT_CARGO_SUPPORT=dev_nodebug AC_DEFINE(YJIT_STATS, 1) ], [stats], [ - rb_rust_target_subdir=stats - CARGO_BUILD_ARGS='--profile stats' + JIT_CARGO_SUPPORT=stats AC_DEFINE(YJIT_STATS, 1) ]) - AS_IF([test -n "${CARGO_BUILD_ARGS}"], [ - AC_CHECK_TOOL(CARGO, [cargo], [no]) - AS_IF([test x"$CARGO" = "xno"], - AC_MSG_ERROR([cargo is required. Installation instructions available at https://www.rust-lang.org/tools/install]) - ])) - - YJIT_LIBS="yjit/target/${rb_rust_target_subdir}/libyjit.a" - AS_CASE(["$target_os"],[openbsd*],[ - # Link libc++abi (which requires libpthread) for _Unwind_* functions needed by yjit - LDFLAGS="$LDFLAGS -lpthread -lc++abi" - ]) + YJIT_LIBS="target/release/libyjit.a" + RUST_LIB='$(YJIT_LIBS)' YJIT_OBJ='yjit.$(OBJEXT)' JIT_OBJ='jit.$(OBJEXT)' AS_IF([test x"$YJIT_SUPPORT" != "xyes" ], [ @@ -3974,38 +3973,28 @@ AS_CASE(["${YJIT_SUPPORT}"], AC_DEFINE(USE_YJIT, 0) ]) -ZJIT_CARGO_BUILD_ARGS= ZJIT_LIBS= AS_CASE(["${ZJIT_SUPPORT}"], -[yes|dev], [ +[yes|dev|dev_nodebug], [ AS_IF([test x"$RUSTC" = "xno"], AC_MSG_ERROR([rustc is required. Installation instructions available at https://www.rust-lang.org/tools/install]) ) - AS_IF([test x"$YJIT_SUPPORT" != "xno"], - AC_MSG_ERROR([ZJIT cannot be enabled when YJIT is enabled]) - ) AS_CASE(["${ZJIT_SUPPORT}"], [yes], [ - rb_rust_target_subdir=release ], [dev], [ - rb_rust_target_subdir=debug - ZJIT_CARGO_BUILD_ARGS='--profile dev --features disasm' + rb_cargo_features="$rb_cargo_features,disasm" + JIT_CARGO_SUPPORT=dev AC_DEFINE(RUBY_DEBUG, 1) + ], + [dev_nodebug], [ + rb_cargo_features="$rb_cargo_features,disasm" + JIT_CARGO_SUPPORT=dev_nodebug ]) - AS_IF([test -n "${ZJIT_CARGO_BUILD_ARGS}"], [ - AC_CHECK_TOOL(CARGO, [cargo], [no]) - AS_IF([test x"$CARGO" = "xno"], - AC_MSG_ERROR([cargo is required. Installation instructions available at https://www.rust-lang.org/tools/install]) - ])) - - ZJIT_LIBS="zjit/target/${rb_rust_target_subdir}/libzjit.a" - AS_CASE(["$target_os"],[openbsd*],[ - # Link libc++abi (which requires libpthread) for _Unwind_* functions needed by yjit - LDFLAGS="$LDFLAGS -lpthread -lc++abi" - ]) + ZJIT_LIBS="target/release/libzjit.a" + RUST_LIB='$(ZJIT_LIBS)' ZJIT_OBJ='zjit.$(OBJEXT)' JIT_OBJ='jit.$(OBJEXT)' AS_IF([test x"$ZJIT_SUPPORT" != "xyes" ], [ @@ -4016,18 +4005,63 @@ AS_CASE(["${ZJIT_SUPPORT}"], AC_DEFINE(USE_ZJIT, 0) ]) +# if YJIT+ZJIT release build, or any build that requires Cargo +AS_IF([test x"$JIT_CARGO_SUPPORT" != "xno" -o \( x"$YJIT_SUPPORT" != "xno" -a x"$ZJIT_SUPPORT" != "xno" \)], [ + AC_CHECK_TOOL(CARGO, [cargo], [no]) + AS_IF([test x"$CARGO" = "xno"], + AC_MSG_ERROR([cargo is required. Installation instructions available at https://www.rust-lang.org/tools/install])) + + YJIT_LIBS= + ZJIT_LIBS= + + # There's more processing below to get the feature set for the + # top-level crate, so capture at this point for feature set of + # just the zjit crate. + ZJIT_TEST_FEATURES="${rb_cargo_features}" + + AS_IF([test x"${YJIT_SUPPORT}" != x"no"], [ + rb_cargo_features="$rb_cargo_features,yjit" + ]) + AS_IF([test x"${ZJIT_SUPPORT}" != x"no"], [ + AC_SUBST(ZJIT_TEST_FEATURES) + rb_cargo_features="$rb_cargo_features,zjit" + ]) + # if YJIT and ZJIT release mode + AS_IF([test "${YJIT_SUPPORT}:${ZJIT_SUPPORT}" = "yes:yes"], [ + JIT_CARGO_SUPPORT=release + ]) + CARGO_BUILD_ARGS="--profile ${JIT_CARGO_SUPPORT} --features ${rb_cargo_features}" + AS_IF([test "${JIT_CARGO_SUPPORT}" = "dev"], [ + RUST_LIB="target/debug/libjit.a" + ], [ + RUST_LIB="target/${JIT_CARGO_SUPPORT}/libjit.a" + ]) +]) + +# In case either we're linking rust code +AS_IF([test -n "$RUST_LIB"], [ + AS_CASE(["$target_os"],[openbsd*],[ + # Link libc++abi (which requires libpthread) for _Unwind_* functions needed by rust stdlib + LDFLAGS="$LDFLAGS -lpthread -lc++abi" + ]) + + # absolute path to stop the "target" dir in src dir from interfering through VPATH + RUST_LIB="$(pwd)/${RUST_LIB}" +]) + dnl These variables end up in ::RbConfig::CONFIG -AC_SUBST(YJIT_SUPPORT)dnl what flavor of YJIT the Ruby build includes AC_SUBST(RUSTC)dnl Rust compiler command AC_SUBST(CARGO)dnl Cargo command for Rust builds AC_SUBST(CARGO_BUILD_ARGS)dnl for selecting Rust build profiles -AC_SUBST(ZJIT_CARGO_BUILD_ARGS)dnl for selecting Rust build profiles -AC_SUBST(YJIT_LIBS)dnl for optionally building the Rust parts of YJIT +AC_SUBST(YJIT_SUPPORT)dnl what flavor of YJIT the Ruby build includes +AC_SUBST(YJIT_LIBS)dnl the .a library of YJIT AC_SUBST(YJIT_OBJ)dnl for optionally building the C parts of YJIT AC_SUBST(ZJIT_SUPPORT)dnl what flavor of ZJIT the Ruby build includes -AC_SUBST(ZJIT_LIBS)dnl for optionally building the Rust parts of ZJIT +AC_SUBST(ZJIT_LIBS)dnl path to the .a library of ZJIT AC_SUBST(ZJIT_OBJ)dnl for optionally building the C parts of ZJIT AC_SUBST(JIT_OBJ)dnl for optionally building C glue code for Rust FFI +AC_SUBST(RUST_LIB)dnl path to the rust .a library that contains either or both JITs +AC_SUBST(JIT_CARGO_SUPPORT)dnl "no" or the cargo profile of the rust code } [begin]_group "build section" && { diff --git a/cont.c b/cont.c index 960d51f214..c40ed6715c 100644 --- a/cont.c +++ b/cont.c @@ -509,83 +509,87 @@ fiber_pool_allocate_memory(size_t * count, size_t stride) static struct fiber_pool_allocation * fiber_pool_expand(struct fiber_pool * fiber_pool, size_t count) { - STACK_GROW_DIR_DETECTION; + struct fiber_pool_allocation * allocation; + RB_VM_LOCK_ENTER(); + { + STACK_GROW_DIR_DETECTION; - size_t size = fiber_pool->size; - size_t stride = size + RB_PAGE_SIZE; + size_t size = fiber_pool->size; + size_t stride = size + RB_PAGE_SIZE; - // Allocate the memory required for the stacks: - void * base = fiber_pool_allocate_memory(&count, stride); + // Allocate the memory required for the stacks: + void * base = fiber_pool_allocate_memory(&count, stride); - if (base == NULL) { - rb_raise(rb_eFiberError, "can't alloc machine stack to fiber (%"PRIuSIZE" x %"PRIuSIZE" bytes): %s", count, size, ERRNOMSG); - } + if (base == NULL) { + rb_raise(rb_eFiberError, "can't alloc machine stack to fiber (%"PRIuSIZE" x %"PRIuSIZE" bytes): %s", count, size, ERRNOMSG); + } - struct fiber_pool_vacancy * vacancies = fiber_pool->vacancies; - struct fiber_pool_allocation * allocation = RB_ALLOC(struct fiber_pool_allocation); + struct fiber_pool_vacancy * vacancies = fiber_pool->vacancies; + allocation = RB_ALLOC(struct fiber_pool_allocation); - // Initialize fiber pool allocation: - allocation->base = base; - allocation->size = size; - allocation->stride = stride; - allocation->count = count; + // Initialize fiber pool allocation: + allocation->base = base; + allocation->size = size; + allocation->stride = stride; + allocation->count = count; #ifdef FIBER_POOL_ALLOCATION_FREE - allocation->used = 0; + allocation->used = 0; #endif - allocation->pool = fiber_pool; + allocation->pool = fiber_pool; - if (DEBUG) { - fprintf(stderr, "fiber_pool_expand(%"PRIuSIZE"): %p, %"PRIuSIZE"/%"PRIuSIZE" x [%"PRIuSIZE":%"PRIuSIZE"]\n", - count, (void*)fiber_pool, fiber_pool->used, fiber_pool->count, size, fiber_pool->vm_stack_size); - } - - // Iterate over all stacks, initializing the vacancy list: - for (size_t i = 0; i < count; i += 1) { - void * base = (char*)allocation->base + (stride * i); - void * page = (char*)base + STACK_DIR_UPPER(size, 0); + if (DEBUG) { + fprintf(stderr, "fiber_pool_expand(%"PRIuSIZE"): %p, %"PRIuSIZE"/%"PRIuSIZE" x [%"PRIuSIZE":%"PRIuSIZE"]\n", + count, (void*)fiber_pool, fiber_pool->used, fiber_pool->count, size, fiber_pool->vm_stack_size); + } + // Iterate over all stacks, initializing the vacancy list: + for (size_t i = 0; i < count; i += 1) { + void * base = (char*)allocation->base + (stride * i); + void * page = (char*)base + STACK_DIR_UPPER(size, 0); #if defined(_WIN32) - DWORD old_protect; + DWORD old_protect; - if (!VirtualProtect(page, RB_PAGE_SIZE, PAGE_READWRITE | PAGE_GUARD, &old_protect)) { - VirtualFree(allocation->base, 0, MEM_RELEASE); - rb_raise(rb_eFiberError, "can't set a guard page: %s", ERRNOMSG); - } + if (!VirtualProtect(page, RB_PAGE_SIZE, PAGE_READWRITE | PAGE_GUARD, &old_protect)) { + VirtualFree(allocation->base, 0, MEM_RELEASE); + rb_raise(rb_eFiberError, "can't set a guard page: %s", ERRNOMSG); + } #elif defined(__wasi__) - // wasi-libc's mprotect emulation doesn't support PROT_NONE. - (void)page; + // wasi-libc's mprotect emulation doesn't support PROT_NONE. + (void)page; #else - if (mprotect(page, RB_PAGE_SIZE, PROT_NONE) < 0) { - munmap(allocation->base, count*stride); - rb_raise(rb_eFiberError, "can't set a guard page: %s", ERRNOMSG); + if (mprotect(page, RB_PAGE_SIZE, PROT_NONE) < 0) { + munmap(allocation->base, count*stride); + rb_raise(rb_eFiberError, "can't set a guard page: %s", ERRNOMSG); + } +#endif + + vacancies = fiber_pool_vacancy_initialize( + fiber_pool, vacancies, + (char*)base + STACK_DIR_UPPER(0, RB_PAGE_SIZE), + size + ); + +#ifdef FIBER_POOL_ALLOCATION_FREE + vacancies->stack.allocation = allocation; +#endif } -#endif - vacancies = fiber_pool_vacancy_initialize( - fiber_pool, vacancies, - (char*)base + STACK_DIR_UPPER(0, RB_PAGE_SIZE), - size - ); + // Insert the allocation into the head of the pool: + allocation->next = fiber_pool->allocations; #ifdef FIBER_POOL_ALLOCATION_FREE - vacancies->stack.allocation = allocation; -#endif - } + if (allocation->next) { + allocation->next->previous = allocation; + } - // Insert the allocation into the head of the pool: - allocation->next = fiber_pool->allocations; - -#ifdef FIBER_POOL_ALLOCATION_FREE - if (allocation->next) { - allocation->next->previous = allocation; - } - - allocation->previous = NULL; + allocation->previous = NULL; #endif - fiber_pool->allocations = allocation; - fiber_pool->vacancies = vacancies; - fiber_pool->count += count; + fiber_pool->allocations = allocation; + fiber_pool->vacancies = vacancies; + fiber_pool->count += count; + } + RB_VM_LOCK_LEAVE(); return allocation; } @@ -659,41 +663,46 @@ fiber_pool_allocation_free(struct fiber_pool_allocation * allocation) static struct fiber_pool_stack fiber_pool_stack_acquire(struct fiber_pool * fiber_pool) { - struct fiber_pool_vacancy * vacancy = fiber_pool_vacancy_pop(fiber_pool); - - if (DEBUG) fprintf(stderr, "fiber_pool_stack_acquire: %p used=%"PRIuSIZE"\n", (void*)fiber_pool->vacancies, fiber_pool->used); - - if (!vacancy) { - const size_t maximum = FIBER_POOL_ALLOCATION_MAXIMUM_SIZE; - const size_t minimum = fiber_pool->initial_count; - - size_t count = fiber_pool->count; - if (count > maximum) count = maximum; - if (count < minimum) count = minimum; - - fiber_pool_expand(fiber_pool, count); - - // The free list should now contain some stacks: - VM_ASSERT(fiber_pool->vacancies); - + struct fiber_pool_vacancy * vacancy ; + RB_VM_LOCK_ENTER(); + { vacancy = fiber_pool_vacancy_pop(fiber_pool); - } - VM_ASSERT(vacancy); - VM_ASSERT(vacancy->stack.base); + if (DEBUG) fprintf(stderr, "fiber_pool_stack_acquire: %p used=%"PRIuSIZE"\n", (void*)fiber_pool->vacancies, fiber_pool->used); + + if (!vacancy) { + const size_t maximum = FIBER_POOL_ALLOCATION_MAXIMUM_SIZE; + const size_t minimum = fiber_pool->initial_count; + + size_t count = fiber_pool->count; + if (count > maximum) count = maximum; + if (count < minimum) count = minimum; + + fiber_pool_expand(fiber_pool, count); + + // The free list should now contain some stacks: + VM_ASSERT(fiber_pool->vacancies); + + vacancy = fiber_pool_vacancy_pop(fiber_pool); + } + + VM_ASSERT(vacancy); + VM_ASSERT(vacancy->stack.base); #if defined(COROUTINE_SANITIZE_ADDRESS) - __asan_unpoison_memory_region(fiber_pool_stack_poison_base(&vacancy->stack), fiber_pool_stack_poison_size(&vacancy->stack)); + __asan_unpoison_memory_region(fiber_pool_stack_poison_base(&vacancy->stack), fiber_pool_stack_poison_size(&vacancy->stack)); #endif - // Take the top item from the free list: - fiber_pool->used += 1; + // Take the top item from the free list: + fiber_pool->used += 1; #ifdef FIBER_POOL_ALLOCATION_FREE - vacancy->stack.allocation->used += 1; + vacancy->stack.allocation->used += 1; #endif - fiber_pool_stack_reset(&vacancy->stack); + fiber_pool_stack_reset(&vacancy->stack); + } + RB_VM_LOCK_LEAVE(); return vacancy->stack; } @@ -764,40 +773,44 @@ static void fiber_pool_stack_release(struct fiber_pool_stack * stack) { struct fiber_pool * pool = stack->pool; - struct fiber_pool_vacancy * vacancy = fiber_pool_vacancy_pointer(stack->base, stack->size); + RB_VM_LOCK_ENTER(); + { + struct fiber_pool_vacancy * vacancy = fiber_pool_vacancy_pointer(stack->base, stack->size); - if (DEBUG) fprintf(stderr, "fiber_pool_stack_release: %p used=%"PRIuSIZE"\n", stack->base, stack->pool->used); + if (DEBUG) fprintf(stderr, "fiber_pool_stack_release: %p used=%"PRIuSIZE"\n", stack->base, stack->pool->used); - // Copy the stack details into the vacancy area: - vacancy->stack = *stack; - // After this point, be careful about updating/using state in stack, since it's copied to the vacancy area. + // Copy the stack details into the vacancy area: + vacancy->stack = *stack; + // After this point, be careful about updating/using state in stack, since it's copied to the vacancy area. - // Reset the stack pointers and reserve space for the vacancy data: - fiber_pool_vacancy_reset(vacancy); + // Reset the stack pointers and reserve space for the vacancy data: + fiber_pool_vacancy_reset(vacancy); - // Push the vacancy into the vancancies list: - pool->vacancies = fiber_pool_vacancy_push(vacancy, pool->vacancies); - pool->used -= 1; + // Push the vacancy into the vancancies list: + pool->vacancies = fiber_pool_vacancy_push(vacancy, pool->vacancies); + pool->used -= 1; #ifdef FIBER_POOL_ALLOCATION_FREE - struct fiber_pool_allocation * allocation = stack->allocation; + struct fiber_pool_allocation * allocation = stack->allocation; - allocation->used -= 1; + allocation->used -= 1; - // Release address space and/or dirty memory: - if (allocation->used == 0) { - fiber_pool_allocation_free(allocation); - } - else if (stack->pool->free_stacks) { - fiber_pool_stack_free(&vacancy->stack); - } + // Release address space and/or dirty memory: + if (allocation->used == 0) { + fiber_pool_allocation_free(allocation); + } + else if (stack->pool->free_stacks) { + fiber_pool_stack_free(&vacancy->stack); + } #else - // This is entirely optional, but clears the dirty flag from the stack - // memory, so it won't get swapped to disk when there is memory pressure: - if (stack->pool->free_stacks) { - fiber_pool_stack_free(&vacancy->stack); - } + // This is entirely optional, but clears the dirty flag from the stack + // memory, so it won't get swapped to disk when there is memory pressure: + if (stack->pool->free_stacks) { + fiber_pool_stack_free(&vacancy->stack); + } #endif + } + RB_VM_LOCK_LEAVE(); } static inline void diff --git a/debug_counter.h b/debug_counter.h index c4ee26534f..fada7513aa 100644 --- a/debug_counter.h +++ b/debug_counter.h @@ -315,6 +315,7 @@ RB_DEBUG_COUNTER(obj_imemo_parser_strterm) RB_DEBUG_COUNTER(obj_imemo_callinfo) RB_DEBUG_COUNTER(obj_imemo_callcache) RB_DEBUG_COUNTER(obj_imemo_constcache) +RB_DEBUG_COUNTER(obj_imemo_fields) RB_DEBUG_COUNTER(opt_new_hit) RB_DEBUG_COUNTER(opt_new_miss) diff --git a/defs/gmake.mk b/defs/gmake.mk index 6e696c4631..a81d82eadd 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -365,7 +365,7 @@ $(srcdir)/.bundle/.timestamp: define build-gem $(srcdir)/gems/src/$(1)/.git: | $(srcdir)/gems/src $(ECHO) Cloning $(4) - $(Q) $(GIT) clone $(4) $$(@D) + $(Q) $(GIT) clone --depth=1 --no-tags $(4) $$(@D) $(bundled-gem-revision): \ $(if $(if $(wildcard $$(@)),$(filter $(3),$(shell cat $$(@)))),,PHONY) \ @@ -443,6 +443,7 @@ endif include $(top_srcdir)/yjit/yjit.mk include $(top_srcdir)/zjit/zjit.mk +include $(top_srcdir)/defs/jit.mk # Query on the generated rdoc # diff --git a/defs/id.def b/defs/id.def index 5e2da592ef..0c32b0d1d4 100644 --- a/defs/id.def +++ b/defs/id.def @@ -63,6 +63,8 @@ firstline, predefined = __LINE__+1, %[\ pack buffer include? + aborted + exited _ UScore diff --git a/defs/jit.mk b/defs/jit.mk new file mode 100644 index 0000000000..28d8f2da3a --- /dev/null +++ b/defs/jit.mk @@ -0,0 +1,64 @@ +# Make recipes that deal with the rust code of YJIT and ZJIT. + +# Because of Cargo cache, if the actual binary is not changed from the +# previous build, the mtime is preserved as the cached file. +# This means the target is not updated actually, and it will need to +# rebuild at the next build. +RUST_LIB_TOUCH = touch $@ + +ifneq ($(JIT_CARGO_SUPPORT),no) + +# Show Cargo progress when doing `make V=1` +CARGO_VERBOSE_0 = -q +CARGO_VERBOSE_1 = +CARGO_VERBOSE = $(CARGO_VERBOSE_$(V)) + +# NOTE: MACOSX_DEPLOYMENT_TARGET to match `rustc --print deployment-target` to avoid the warning below. +# ld: warning: object file (target/debug/libjit.a()) was built for +# newer macOS version (15.2) than being linked (15.0) +# This limits us to an older set of macOS API in the rust code, but we don't use any. +$(RUST_LIB): $(srcdir)/jit.rs + $(Q)if [ '$(ZJIT_SUPPORT)' != no -a '$(YJIT_SUPPORT)' != no ]; then \ + echo 'building YJIT and ZJIT ($(JIT_CARGO_SUPPORT:yes=release) mode)'; \ + elif [ '$(ZJIT_SUPPORT)' != no ]; then \ + echo 'building ZJIT ($(JIT_CARGO_SUPPORT) mode)'; \ + elif [ '$(YJIT_SUPPORT)' != no ]; then \ + echo 'building YJIT ($(JIT_CARGO_SUPPORT) mode)'; \ + fi + +$(Q)CARGO_TARGET_DIR='$(CARGO_TARGET_DIR)' \ + CARGO_TERM_PROGRESS_WHEN='never' \ + MACOSX_DEPLOYMENT_TARGET=11.0 \ + $(CARGO) $(CARGO_VERBOSE) build --manifest-path '$(top_srcdir)/Cargo.toml' $(CARGO_BUILD_ARGS) + $(RUST_LIB_TOUCH) +endif + +RUST_LIB_SYMBOLS = $(RUST_LIB:.a=).symbols +$(RUST_LIBOBJ): $(RUST_LIB) + $(ECHO) 'partial linking $(RUST_LIB) into $@' +ifneq ($(findstring darwin,$(target_os)),) + $(Q) $(CC) -nodefaultlibs -r -o $@ -exported_symbols_list $(RUST_LIB_SYMBOLS) $(RUST_LIB) +else + $(Q) $(LD) -r -o $@ --whole-archive $(RUST_LIB) + -$(Q) $(OBJCOPY) --wildcard --keep-global-symbol='$(SYMBOL_PREFIX)rb_*' $(@) +endif + +rust-libobj: $(RUST_LIBOBJ) +rust-lib: $(RUST_LIB) + +# For Darwin only: a list of symbols that we want the glommed Rust static lib to export. +# Unfortunately, using wildcard like '_rb_*' with -exported-symbol does not work, at least +# not on version 820.1. Assume llvm-nm, so XCode 8.0 (from 2016) or newer. +# +# The -exported_symbols_list pulls out the right archive members. Symbols not listed +# in the list are made private extern, which are in turn made local as we're using `ld -r`. +# Note, section about -keep_private_externs in ld's man page hints at this behavior on which +# we rely. +ifneq ($(findstring darwin,$(target_os)),) +$(RUST_LIB_SYMBOLS): $(RUST_LIB) + $(Q) $(tooldir)/darwin-ar $(NM) --defined-only --extern-only $(RUST_LIB) | \ + sed -n -e 's/.* //' -e '/^$(SYMBOL_PREFIX)rb_/p' \ + -e '/^$(SYMBOL_PREFIX)rust_eh_personality/p' \ + > $@ + +$(RUST_LIBOBJ): $(RUST_LIB_SYMBOLS) +endif diff --git a/dir.rb b/dir.rb index 6cbfe1fb14..a05bd18630 100644 --- a/dir.rb +++ b/dir.rb @@ -224,8 +224,8 @@ class Dir end # call-seq: - # Dir.glob(*patterns, flags: 0, base: nil, sort: true) -> array - # Dir.glob(*patterns, flags: 0, base: nil, sort: true) {|entry_name| ... } -> nil + # Dir.glob(patterns, flags: 0, base: nil, sort: true) -> array + # Dir.glob(patterns, flags: 0, base: nil, sort: true) {|entry_name| ... } -> nil # # Forms an array _entry_names_ of the entry names selected by the arguments. # diff --git a/doc/_regexp.rdoc b/doc/_regexp.rdoc index 468827da15..45a307fad6 100644 --- a/doc/_regexp.rdoc +++ b/doc/_regexp.rdoc @@ -439,6 +439,10 @@ without including the tags in the match: /(?<=)\w+(?=<\/b>)/.match("Fortune favors the bold.") # => # +The pattern in lookbehind must be fixed-width. +But top-level alternatives can be of various lengths. +ex. (?<=a|bc) is OK. (?<=aaa(?:b|cd)) is not allowed. + ==== Match-Reset Anchor - \K: Match reset: diff --git a/doc/case_mapping.rdoc b/doc/case_mapping.rdoc index 57c037b9d8..d40155db03 100644 --- a/doc/case_mapping.rdoc +++ b/doc/case_mapping.rdoc @@ -37,7 +37,7 @@ Context-dependent case mapping as described in {Table 3-17 (Context Specification for Casing) of the Unicode standard}[https://www.unicode.org/versions/latest/ch03.pdf] is currently not supported. -In most cases, case conversions of a string have the same number of characters. +In most cases, the case conversion of a string has the same number of characters as before. There are exceptions (see also +:fold+ below): s = "\u00DF" # => "ß" @@ -58,25 +58,18 @@ Case changes may not be reversible: s.downcase.upcase # => "HELLO WORLD!" # Different from original s. Case changing methods may not maintain Unicode normalization. -See String#unicode_normalize). +See String#unicode_normalize. -== Options for Case Mapping +== Case Mappings Except for +casecmp+ and +casecmp?+, each of the case-mapping methods listed above -accepts optional arguments, *options. +accepts an optional argument, mapping. -The arguments may be: +The argument is one of: -- +:ascii+ only. -- +:fold+ only. -- +:turkic+ or +:lithuanian+ or both. - -The options: - -- +:ascii+: - ASCII-only mapping: - uppercase letters ('A'..'Z') are mapped to lowercase letters ('a'..'z); +- +:ascii+: ASCII-only mapping. + Uppercase letters ('A'..'Z') are mapped to lowercase letters ('a'..'z); other characters are not changed s = "Foo \u00D8 \u00F8 Bar" # => "Foo Ø ø Bar" @@ -85,8 +78,8 @@ The options: s.upcase(:ascii) # => "FOO Ø ø BAR" s.downcase(:ascii) # => "foo Ø ø bar" -- +:turkic+: - Full Unicode case mapping, adapted for the Turkic languages +- +:turkic+: Full Unicode case mapping. + For the Turkic languages that distinguish dotted and dotless I, for example Turkish and Azeri. s = 'Türkiye' # => "Türkiye" @@ -97,11 +90,8 @@ The options: s.downcase # => "türkiye" s.downcase(:turkic) # => "türkıye" # No dot above. -- +:lithuanian+: - Not yet implemented. - - +:fold+ (available only for String#downcase, String#downcase!, - and Symbol#downcase): + and Symbol#downcase). Unicode case folding, which is more far-reaching than Unicode case mapping. diff --git a/doc/contributing/building_ruby.md b/doc/contributing/building_ruby.md index c0eafe182f..a0486cb931 100644 --- a/doc/contributing/building_ruby.md +++ b/doc/contributing/building_ruby.md @@ -260,11 +260,25 @@ This will add launch configurations for debugging Ruby itself by running `test.r ### Compiling for Debugging -You should configure Ruby without optimization and other flags that may -interfere with debugging: +You can compile Ruby with the `RUBY_DEBUG` macro to enable debugging on some +features. One example is debugging object shapes in Ruby with +`RubyVM::Shape.of(object)`. + +Additionally Ruby can be compiled to support the `RUBY_DEBUG` environment +variable to enable debugging on some features. An example is using +`RUBY_DEBUG=gc_stress` to debug GC-related issues. + +There is also support for the `RUBY_DEBUG_LOG` environment variable to log a +lot of information about what the VM is doing, via the `USE_RUBY_DEBUG_LOG` +macro. + +You should also configure Ruby without optimization and other flags that may +interfere with debugging by changing the optimization flags. + +Bringing it all together: ```sh -./configure --enable-debug-env optflags="-O0 -fno-omit-frame-pointer" +./configure cppflags="-DRUBY_DEBUG=1 -DUSE_RUBY_DEBUG_LOG=1" --enable-debug-env optflags="-O0 -fno-omit-frame-pointer" ``` ### Building with Address Sanitizer diff --git a/doc/contributing/documentation_guide.md b/doc/contributing/documentation_guide.md index deb17a793a..a913aa1086 100644 --- a/doc/contributing/documentation_guide.md +++ b/doc/contributing/documentation_guide.md @@ -7,7 +7,7 @@ in the Ruby core and in the Ruby standard library. ## Generating documentation Most Ruby documentation lives in the source files and is written in -[RDoc format](rdoc-ref:RDoc::MarkupReference). +[RDoc format](https://ruby.github.io/rdoc/RDoc/MarkupReference.html). Some pages live under the `doc` folder and can be written in either `.rdoc` or `.md` format, determined by the file extension. @@ -44,14 +44,13 @@ Use your judgment about what the user needs to know. - Group sentences into (ideally short) paragraphs, each covering a single topic. - Organize material with - [headings](rdoc-ref:RDoc::MarkupReference@Headings). + [headings]. - Refer to authoritative and relevant sources using - [links](rdoc-ref:RDoc::MarkupReference@Links). + [links](https://ruby.github.io/rdoc/RDoc/MarkupReference.html#class-RDoc::MarkupReference-label-Links). - Use simple verb tenses: simple present, simple past, simple future. - Use simple sentence structure, not compound or complex structure. - Avoid: - - Excessive comma-separated phrases; - consider a [list](rdoc-ref:RDoc::MarkupReference@Lists). + - Excessive comma-separated phrases; consider a [list]. - Idioms and culture-specific references. - Overuse of headings. - Using US-ASCII-incompatible characters in C source files; @@ -110,7 +109,7 @@ involving new files `doc/*.rdoc`: Ruby is documented using RDoc. For information on \RDoc syntax and features, see the -[RDoc Markup Reference](rdoc-ref:RDoc::MarkupReference). +[RDoc Markup Reference](https://ruby.github.io/rdoc/RDoc/MarkupReference.html). ### Output from `irb` @@ -129,22 +128,21 @@ a #=> [2, 3, 1] ### Headings -Organize a long discussion for a class or module with [headings](rdoc-ref:RDoc::MarkupReference@Headings). +Organize a long discussion for a class or module with [headings]. Do not use formal headings in the documentation for a method or constant. In the rare case where heading-like structures are needed within the documentation for a method or constant, use -[bold text](rdoc-ref:RDoc::MarkupReference@Bold) +[bold text](https://ruby.github.io/rdoc/RDoc/MarkupReference.html#class-RDoc::MarkupReference-label-Bold) as pseudo-headings. ### Blank Lines A blank line begins a new paragraph. -A [code block](rdoc-ref:RDoc::MarkupReference@Code+Blocks) -or [list](rdoc-ref:RDoc::MarkupReference@Lists) -should be preceded by and followed by a blank line. +A [code block](https://ruby.github.io/rdoc/RDoc/MarkupReference.html#class-RDoc::MarkupReference-label-Code+Blocks) +or [list] should be preceded by and followed by a blank line. This is unnecessary for the HTML output, but helps in the `ri` output. ### \Method Names @@ -164,7 +162,7 @@ For a method name in text: Code or commands embedded in running text (i.e., not in a code block) should marked up as -[monofont](rdoc-ref:RDoc::MarkupReference@Monofont). +[monofont]. Code that is a simple string should include the quote marks. @@ -214,7 +212,7 @@ refers to the current document (e.g., _Float_ in the documentation for class Float). In this case you may consider forcing the name to -[monofont](rdoc-ref:RDoc::MarkupReference@Monofont), +[monofont], which suppresses auto-linking, and also emphasizes that the word is a class name: ```rdoc @@ -276,7 +274,7 @@ Use the +rdoc-ref+ scheme for: - A link in a standard library package to other documentation in that same standard library package. -See section "+rdoc-ref+ Scheme" in {Links}[rdoc-ref:RDoc::MarkupReference@Links]. +See section "+rdoc-ref+ Scheme" in [links]. #### URL-Based Link @@ -296,7 +294,7 @@ Also use a full URL-based link for a link to an off-site document. ### Variable Names The name of a variable (as specified in its call-seq) should be marked up as -[monofont](rdoc-ref:RDoc::MarkupReference@Monofont). +[monofont]. Also, use monofont text for the name of a transient variable (i.e., one defined and used only in the discussion, such as +n+). @@ -314,9 +312,9 @@ In particular, avoid building tables with HTML tags Alternatives: -- A {verbatim text block}[rdoc-ref:RDoc::MarkupReference@Verbatim+Text+Blocks], +- A {verbatim text block}[https://ruby.github.io/rdoc/RDoc/MarkupReference.html#class-RDoc::MarkupReference-label-Verbatim+Text+Blocks], using spaces and punctuation to format the text; - note that {text markup}[rdoc-ref:RDoc::MarkupReference@Text+Markup] + note that {text markup}[https://ruby.github.io/rdoc/RDoc/MarkupReference.html#class-RDoc::MarkupReference-label-Text+Markup] will not be honored: - Example {source}[https://github.com/ruby/ruby/blob/34d802f32f00df1ac0220b62f72605827c16bad8/file.c#L6570-L6596]. @@ -356,8 +354,7 @@ Guidelines: - The section title is `What's Here`. - Consider listing the parent class and any included modules; consider - [links](rdoc-ref:RDoc::MarkupReference@Links) - to their "What's Here" sections if those exist. + [links] to their "What's Here" sections if those exist. - All methods mentioned in the left-pane table of contents should be listed (including any methods extended from another class). - Attributes (which are not included in the TOC) may also be listed. @@ -369,7 +366,7 @@ Guidelines: (and do not list the aliases separately). - Check the rendered documentation to determine whether \RDoc has recognized the method and linked to it; if not, manually insert a - [link](rdoc-ref:RDoc::MarkupReference@Links). + [link](https://ruby.github.io/rdoc/RDoc/MarkupReference.html#class-RDoc::MarkupReference-label-Links). - If there are numerous entries, consider grouping them into subsections with headings. - If there are more than a few such subsections, @@ -395,7 +392,7 @@ For methods written in Ruby, \RDoc documents the calling sequence automatically. For methods written in C, \RDoc cannot determine what arguments the method accepts, so those need to be documented using \RDoc directive -[`call-seq:`](rdoc-ref:RDoc::MarkupReference@Directives+for+Method+Documentation). +[`call-seq:`](https://ruby.github.io/rdoc/RDoc/MarkupReference.html#class-RDoc::MarkupReference-label-Directives+for+Method+Documentation). For a singleton method, use the form: @@ -570,7 +567,7 @@ argument passed if it is not obvious, not explicitly mentioned in the details, and not implicitly shown in the examples. If there is more than one argument or block argument, use a -[labeled list](rdoc-ref:RDoc::MarkupReference@Labeled+Lists). +[labeled list](https://ruby.github.io/rdoc/RDoc/MarkupReference.html#class-RDoc::MarkupReference-label-Labeled+Lists). ### Corner Cases and Exceptions @@ -610,3 +607,7 @@ mention `Hash#fetch` as a related method, and `Hash#merge` might mention For methods that accept multiple argument types, in some cases it can be useful to document the different argument types separately. It's best to use a separate paragraph for each case you are discussing. + +[headings]: https://ruby.github.io/rdoc/RDoc/MarkupReference.html#class-RDoc::MarkupReference-label-Headings +[list]: https://ruby.github.io/rdoc/RDoc/MarkupReference.html#class-RDoc::MarkupReference-label-Lists +[monofont]: https://ruby.github.io/rdoc/RDoc/MarkupReference.html#class-RDoc::MarkupReference-label-Monofont diff --git a/doc/contributing/glossary.md b/doc/contributing/glossary.md index 86c6671fbd..6f9c335028 100644 --- a/doc/contributing/glossary.md +++ b/doc/contributing/glossary.md @@ -8,6 +8,7 @@ Just a list of acronyms I've run across in the Ruby source code and their meanin | `bop` | Basic Operator. Relates to methods like `Integer` plus and minus which can be optimized as long as they haven't been redefined. | | `cc` | Call Cache. An inline cache structure for the call site. Stored in the `cd` | | `cd` | Call Data. A data structure that points at the `ci` and the `cc`. `iseq` objects points at the `cd`, and access call information and call caches via this structure | +| CFG | Control Flow Graph. Representation of the program where all control-flow and data dependencies have been made explicit by unrolling the stack and local variables. | | `cfp`| Control Frame Pointer. Represents a Ruby stack frame. Calling a method pushes a new frame (cfp), returning pops a frame. Points at the `pc`, `sp`, `ep`, and the corresponding `iseq`| | `ci` | Call Information. Refers to an `rb_callinfo` struct. Contains call information about the call site, including number of parameters to be passed, whether it they are keyword arguments or not, etc. Used in conjunction with the `cc` and `cd`. | | `cref` | Class reference. A structure pointing to the class reference where `klass_or_self`, visibility scope, and refinements are stored. It also stores a pointer to the next class in the hierarchy referenced by `rb_cref_struct * next`. The Class reference is lexically scoped. | @@ -25,10 +26,11 @@ Just a list of acronyms I've run across in the Ruby source code and their meanin | `insns` | Instructions. Usually an array of YARV instructions. | | `ivar` | Instance Variable. Refers to a Ruby instance variable like `@foo` | | `imemo` | Internal Memo. A tagged struct whose memory is managed by Ruby's GC, but contains internal information and isn't meant to be exposed to Ruby programs. Contains various information depending on the type. See the `imemo_type` enum for different types. | +| `IVC` | Instance Variable Cache. Cache specifically for instance variable access | | JIT | Just In Time compiler | | `lep` | Local Environment Pointer. An `ep` which is tagged `VM_ENV_FLAG_LOCAL`. Usually this is the `ep` of a method (rather than a block, whose `ep` isn't "local") | | `local` | Local. Refers to a local variable. | -| `me` | Method Entry. Refers to an `rb_method_entry_t` struct, the internal representation of a Ruby method. +| `me` | Method Entry. Refers to an `rb_method_entry_t` struct, the internal representation of a Ruby method. | | MRI | Matz's Ruby Implementation | | `pc` | Program Counter. Usually the instruction that will be executed _next_ by the VM. Pointed to by the `cfp` and incremented by the VM | | `sp` | Stack Pointer. The top of the stack. The VM executes instructions in the `iseq` and instructions will push and pop values on the stack. The VM updates the `sp` on the `cfp` to point at the top of the stack| diff --git a/doc/distribution.md b/doc/distribution.md index 5a4d51da6f..164e1b7109 100644 --- a/doc/distribution.md +++ b/doc/distribution.md @@ -8,7 +8,7 @@ This document outlines the expected way to distribute Ruby, with a specific focu The tarball for official releases is created by the release manager. The release manager uploads the tarball to the [Ruby website](https://www.ruby-lang.org/en/downloads/). -Downstream distributors should use the official release tarballs as part of their build process. This ensures that the tarball is created in a consistent way, and that the tarball is crytographically verified. +Downstream distributors should use the official release tarballs as part of their build process. This ensures that the tarball is created in a consistent way, and that the tarball is cryptographically verified. ### Using the nightly tarball for testing diff --git a/doc/fiber.md b/doc/fiber.md index 2bc1ff96b2..d9011cce2f 100644 --- a/doc/fiber.md +++ b/doc/fiber.md @@ -212,6 +212,64 @@ I/O. Windows is a notable example where socket I/O can be non-blocking but pipe I/O is blocking. Provided that there *is* a scheduler and the current thread *is non-blocking*, the operation will invoke the scheduler. +##### `IO#close` + +Closing an IO interrupts all blocking operations on that IO. When a thread calls `IO#close`, it first attempts to interrupt any threads or fibers that are blocked on that IO. The closing thread waits until all blocked threads and fibers have been properly interrupted and removed from the IO's blocking list. Each interrupted thread or fiber receives an `IOError` and is cleanly removed from the blocking operation. Only after all blocking operations have been interrupted and cleaned up will the actual file descriptor be closed, ensuring proper resource cleanup and preventing potential race conditions. + +For fibers managed by a scheduler, the interruption process involves calling `rb_fiber_scheduler_fiber_interrupt` on the scheduler. This allows the scheduler to handle the interruption in a way that's appropriate for its event loop implementation. The scheduler can then notify the fiber, which will receive an `IOError` and be removed from the blocking operation. This mechanism ensures that fiber-based concurrency works correctly with IO operations, even when those operations are interrupted by `IO#close`. + +```mermaid +sequenceDiagram + participant ThreadB + participant ThreadA + participant Scheduler + participant IO + participant Fiber1 + participant Fiber2 + + Note over ThreadA: Thread A has a fiber scheduler + activate Scheduler + ThreadA->>Fiber1: Schedule Fiber 1 + activate Fiber1 + Fiber1->>IO: IO.read + IO->>Scheduler: rb_thread_io_blocking_region + deactivate Fiber1 + + ThreadA->>Fiber2: Schedule Fiber 2 + activate Fiber2 + Fiber2->>IO: IO.read + IO->>Scheduler: rb_thread_io_blocking_region + deactivate Fiber2 + + Note over Fiber1,Fiber2: Both fibers blocked on same IO + + Note over ThreadB: IO.close + activate ThreadB + ThreadB->>IO: thread_io_close_notify_all + Note over ThreadB: rb_mutex_sleep + + IO->>Scheduler: rb_fiber_scheduler_fiber_interrupt(Fiber1) + Scheduler->>Fiber1: fiber_interrupt with IOError + activate Fiber1 + Note over IO: fiber_interrupt causes removal from blocking list + Fiber1->>IO: rb_io_blocking_operation_exit() + IO-->>ThreadB: Wakeup thread + deactivate Fiber1 + + IO->>Scheduler: rb_fiber_scheduler_fiber_interrupt(Fiber2) + Scheduler->>Fiber2: fiber_interrupt with IOError + activate Fiber2 + Note over IO: fiber_interrupt causes removal from blocking list + Fiber2->>IO: rb_io_blocking_operation_exit() + IO-->>ThreadB: Wakeup thread + deactivate Fiber2 + deactivate Scheduler + + Note over ThreadB: Blocking operations list empty + ThreadB->>IO: close(fd) + deactivate ThreadB +``` + #### Mutex The `Mutex` class can be used in a non-blocking context and is fiber specific. diff --git a/doc/globals.rdoc b/doc/globals.rdoc index 9d9fc57e6e..9466005be7 100644 --- a/doc/globals.rdoc +++ b/doc/globals.rdoc @@ -137,7 +137,7 @@ English - $DEFAULT_INPUT. An output stream, initially $stdout. -English - $DEFAULT_OUTPUT +English - $DEFAULT_OUTPUT === $. (Input Position) diff --git a/doc/maintainers.md b/doc/maintainers.md index a216e564a5..7d217a1665 100644 --- a/doc/maintainers.md +++ b/doc/maintainers.md @@ -107,11 +107,9 @@ have commit right, others don't. * https://github.com/rubygems/rubygems * https://rubygems.org/gems/bundler -#### lib/cgi.rb, lib/cgi/* +#### lib/cgi/escape.rb * *unmaintained* -* https://github.com/ruby/cgi -* https://rubygems.org/gems/cgi #### lib/English.rb @@ -234,12 +232,6 @@ have commit right, others don't. * https://github.com/ruby/securerandom * https://rubygems.org/gems/securerandom -#### lib/set.rb - -* Akinori MUSHA ([knu]) -* https://github.com/ruby/set -* https://rubygems.org/gems/set - #### lib/shellwords.rb * Akinori MUSHA ([knu]) @@ -318,8 +310,6 @@ have commit right, others don't. #### ext/cgi * Nobuyoshi Nakada ([nobu]) -* https://github.com/ruby/cgi -* https://rubygems.org/gems/cgi #### ext/date diff --git a/doc/ractor.md b/doc/ractor.md index 6a1cff4e7b..eecff5fc5e 100644 --- a/doc/ractor.md +++ b/doc/ractor.md @@ -10,9 +10,9 @@ You can make multiple Ractors and they run in parallel. * `Ractor.new{ expr }` creates a new Ractor and `expr` is run in parallel on a parallel computer. * Interpreter invokes with the first Ractor (called *main Ractor*). -* If main Ractor terminated, all Ractors receive terminate request like Threads (if main thread (first invoked Thread), Ruby interpreter sends all running threads to terminate execution). -* Each Ractor has 1 or more Threads. - * Threads in a Ractor shares a Ractor-wide global lock like GIL (GVL in MRI terminology), so they can't run in parallel (without releasing GVL explicitly in C-level). Threads in different ractors run in parallel. +* If the main Ractor terminates, all other Ractors receive termination requests, similar to how threads behave. (if main thread (first invoked Thread), Ruby interpreter sends all running threads to terminate execution). +* Each Ractor contains one or more Threads. + * Threads within the same Ractor share a Ractor-wide global lock like GIL (GVL in MRI terminology), so they can't run in parallel (without releasing GVL explicitly in C-level). Threads in different ractors run in parallel. * The overhead of creating a Ractor is similar to overhead of one Thread creation. ### Limited sharing between multiple ractors @@ -31,20 +31,23 @@ Ractors don't share everything, unlike threads. * Ractor object itself. * And more... -### Two-types communication between Ractors +### Communication between Ractors with `Ractor::Port` -Ractors communicate with each other and synchronize the execution by message exchanging between Ractors. There are two message exchange protocols: push type (message passing) and pull type. +Ractors communicate with each other and synchronize the execution by message exchanging between Ractors. `Ractor::Port` is provided for this communication. -* Push type message passing: `Ractor#send(obj)` and `Ractor.receive()` pair. - * Sender ractor passes the `obj` to the ractor `r` by `r.send(obj)` and receiver ractor receives the message with `Ractor.receive`. - * Sender knows the destination Ractor `r` and the receiver does not know the sender (accept all messages from any ractors). - * Receiver has infinite queue and sender enqueues the message. Sender doesn't block to put message into this queue. - * This type of message exchanging is employed by many other Actor-based languages. - * `Ractor.receive_if{ filter_expr }` is a variant of `Ractor.receive` to select a message. -* Pull type communication: `Ractor.yield(obj)` and `Ractor#take()` pair. - * Sender ractor declare to yield the `obj` by `Ractor.yield(obj)` and receiver Ractor take it with `r.take`. - * Sender doesn't know a destination Ractor and receiver knows the sender Ractor `r`. - * Sender or receiver will block if there is no other side. +```ruby +port = Ractor::Port.new + +Ractor.new port do |port| + # Other ractors can send to the port + port << 42 +end + +port.receive # get a message to the port. Only the creator Ractor can receive from the port +#=> 42 +``` + +Ractors have its own deafult port and `Ractor#send`, `Ractor.receive` will use it. ### Copy & Move semantics to send messages @@ -66,7 +69,7 @@ Ractor helps to write a thread-safe concurrent program, but we can make thread-u * To make it compatible with old behavior, classes and modules can introduce data-race and so on. * Ruby programmers should take care if they modify class/module objects on multi Ractor programs. * BAD: Ractor can't solve all thread-safety problems - * There are several blocking operations (waiting send, waiting yield and waiting take) so you can make a program which has dead-lock and live-lock issues. + * There are several blocking operations (waiting send) so you can make a program which has dead-lock and live-lock issues. * Some kind of shareable objects can introduce transactions (STM, for example). However, misusing transactions will generate inconsistent state. Without Ractor, we need to trace all state-mutations to debug thread-safety issues. @@ -105,7 +108,7 @@ begin r = Ractor.new do a #=> ArgumentError because this block accesses `a`. end - r.take # see later + r.join # see later rescue ArgumentError end ``` @@ -117,7 +120,7 @@ r = Ractor.new do p self.class #=> Ractor self.object_id end -r.take == self.object_id #=> false +r.value == self.object_id #=> false ``` Passed arguments to `Ractor.new()` becomes block parameters for the given block. However, an interpreter does not pass the parameter object references, but send them as messages (see below for details). @@ -126,7 +129,7 @@ Passed arguments to `Ractor.new()` becomes block parameters for the given block. r = Ractor.new 'ok' do |msg| msg #=> 'ok' end -r.take #=> 'ok' +r.value #=> 'ok' ``` ```ruby @@ -136,7 +139,7 @@ r = Ractor.new do msg end r.send 'ok' -r.take #=> 'ok' +r.value #=> 'ok' ``` ### An execution result of given block @@ -147,15 +150,7 @@ Return value of the given block becomes an outgoing message (see below for detai r = Ractor.new do 'ok' end -r.take #=> `ok` -``` - -```ruby -# almost similar to the last example -r = Ractor.new do - Ractor.yield 'ok' -end -r.take #=> 'ok' +r.value #=> `ok` ``` Error in the given block will be propagated to the receiver of an outgoing message. @@ -166,7 +161,7 @@ r = Ractor.new do end begin - r.take + r.value rescue Ractor::RemoteError => e e.cause.class #=> RuntimeError e.cause.message #=> 'ok' @@ -178,9 +173,7 @@ end Communication between Ractors is achieved by sending and receiving messages. There are two ways to communicate with each other. -* (1) Message sending/receiving - * (1-1) push type send/receive (sender knows receiver). Similar to the Actor model. - * (1-2) pull type yield/take (receiver knows sender). +* (1) Message sending/receiving via `Ractor::Port` * (2) Using shareable container objects * Ractor::TVar gem ([ko1/ractor-tvar](https://github.com/ko1/ractor-tvar)) * more? @@ -189,18 +182,12 @@ Users can control program execution timing with (1), but should not control with For message sending and receiving, there are two types of APIs: push type and pull type. -* (1-1) send/receive (push type) - * `Ractor#send(obj)` (`Ractor#<<(obj)` is an alias) send a message to the Ractor's incoming port. Incoming port is connected to the infinite size incoming queue so `Ractor#send` will never block. - * `Ractor.receive` dequeue a message from its own incoming queue. If the incoming queue is empty, `Ractor.receive` calling will block. - * `Ractor.receive_if{|msg| filter_expr }` is variant of `Ractor.receive`. `receive_if` only receives a message which `filter_expr` is true (So `Ractor.receive` is the same as `Ractor.receive_if{ true }`. -* (1-2) yield/take (pull type) - * `Ractor.yield(obj)` send an message to a Ractor which are calling `Ractor#take` via outgoing port . If no Ractors are waiting for it, the `Ractor.yield(obj)` will block. If multiple Ractors are waiting for `Ractor.yield(obj)`, only one Ractor can receive the message. - * `Ractor#take` receives a message which is waiting by `Ractor.yield(obj)` method from the specified Ractor. If the Ractor does not call `Ractor.yield` yet, the `Ractor#take` call will block. -* `Ractor.select()` can wait for the success of `take`, `yield` and `receive`. -* You can close the incoming port or outgoing port. - * You can close then with `Ractor#close_incoming` and `Ractor#close_outgoing`. - * If the incoming port is closed for a Ractor, you can't `send` to the Ractor. If `Ractor.receive` is blocked for the closed incoming port, then it will raise an exception. - * If the outgoing port is closed for a Ractor, you can't call `Ractor#take` and `Ractor.yield` on the Ractor. If ractors are blocking by `Ractor#take` or `Ractor.yield`, closing outgoing port will raise an exception on these blocking ractors. +* (1) send/receive via `Ractor::Port`. + * `Ractor::Port#send(obj)` (`Ractor::Port#<<(obj)` is an alias) send a message to the port. Ports are connected to the infinite size incoming queue so `Ractor::Port#send` will never block. + * `Ractor::Port#receive` dequeue a message from its own incoming queue. If the incoming queue is empty, `Ractor::Port#receive` calling will block the execution of a thread. +* `Ractor.select()` can wait for the success of `Ractor::Port#receive`. +* You can close `Ractor::Port` by `Ractor::Port#close` only by the creator Ractor of the port. + * If the port is closed, you can't `send` to the port. If `Ractor::Port#receive` is blocked for the closed port, then it will raise an exception. * When a Ractor is terminated, the Ractor's ports are closed. * There are 3 ways to send an object as a message * (1) Send a reference: Sending a shareable object, send only a reference to the object (fast) @@ -208,104 +195,15 @@ For message sending and receiving, there are two types of APIs: push type and pu * (3) Move an object: Sending an unshareable object reference with a membership. Sender Ractor can not access moved objects anymore (raise an exception) after moving it. Current implementation makes new object as a moved object for receiver Ractor and copies references of sending object to moved object. `T_DATA` objects are not supported. * You can choose "Copy" and "Move" by the `move:` keyword, `Ractor#send(obj, move: true/false)` and `Ractor.yield(obj, move: true/false)` (default is `false` (COPY)). -### Sending/Receiving ports - -Each Ractor has _incoming-port_ and _outgoing-port_. Incoming-port is connected to the infinite sized incoming queue. - -``` - Ractor r - +-------------------------------------------+ - | incoming outgoing | - | port port | - r.send(obj) ->*->[incoming queue] Ractor.yield(obj) ->*-> r.take - | | | - | v | - | Ractor.receive | - +-------------------------------------------+ - - -Connection example: r2.send obj on r1、Ractor.receive on r2 - +----+ +----+ - * r1 |---->* r2 * - +----+ +----+ - - -Connection example: Ractor.yield(obj) on r1, r1.take on r2 - +----+ +----+ - * r1 *---->- r2 * - +----+ +----+ - -Connection example: Ractor.yield(obj) on r1 and r2, - and waiting for both simultaneously by Ractor.select(r1, r2) - - +----+ - * r1 *------+ - +----+ | - +----> Ractor.select(r1, r2) - +----+ | - * r2 *------| - +----+ -``` - -```ruby -r = Ractor.new do - msg = Ractor.receive # Receive from r's incoming queue - msg # send back msg as block return value -end -r.send 'ok' # Send 'ok' to r's incoming port -> incoming queue -r.take # Receive from r's outgoing port -``` - -The last example shows the following ractor network. - -``` - +------+ +---+ - * main |------> * r *---+ - +------+ +---+ | - ^ | - +-------------------+ -``` - -And this code can be simplified by using an argument for `Ractor.new`. - -```ruby -# Actual argument 'ok' for `Ractor.new()` will be sent to created Ractor. -r = Ractor.new 'ok' do |msg| - # Values for formal parameters will be received from incoming queue. - # Similar to: msg = Ractor.receive - - msg # Return value of the given block will be sent via outgoing port -end - -# receive from the r's outgoing port. -r.take #=> `ok` -``` - -### Return value of a block for `Ractor.new` - -As already explained, the return value of `Ractor.new` (an evaluated value of `expr` in `Ractor.new{ expr }`) can be taken by `Ractor#take`. - -```ruby -Ractor.new{ 42 }.take #=> 42 -``` - -When the block return value is available, the Ractor is dead so that no ractors except taken Ractor can touch the return value, so any values can be sent with this communication path without any modification. - -```ruby -r = Ractor.new do - a = "hello" - binding -end - -r.take.eval("p a") #=> "hello" (other communication path can not send a Binding object directly) -``` - ### Wait for multiple Ractors with `Ractor.select` -You can wait multiple Ractor's `yield` with `Ractor.select(*ractors)`. -The return value of `Ractor.select()` is `[r, msg]` where `r` is yielding Ractor and `msg` is yielded message. +You can wait multiple Ractor port's receiving. +The return value of `Ractor.select()` is `[port, msg]` where `port` is a ready port and `msg` is received message. -Wait for a single ractor (same as `Ractor.take`): +To make convenient, `Ractor.select` can also accept Ractors to wait the termination of Ractors. +The return value of `Ractor.select()` is `[r, msg]` where `r` is a terminated Ractor and `msg` is the value of Ractor's blcok. + +Wait for a single ractor (same as `Ractor#value`): ```ruby r1 = Ractor.new{'r1'} @@ -314,7 +212,7 @@ r, obj = Ractor.select(r1) r == r1 and obj == 'r1' #=> true ``` -Wait for two ractors: +Waiting for two ractors: ```ruby r1 = Ractor.new{'r1'} @@ -334,85 +232,29 @@ as << obj as.sort == ['r1', 'r2'] #=> true ``` -\Complex example: - -```ruby -pipe = Ractor.new do - loop do - Ractor.yield Ractor.receive - end -end - -RN = 10 -rs = RN.times.map{|i| - Ractor.new pipe, i do |pipe, i| - msg = pipe.take - msg # ping-pong - end -} -RN.times{|i| - pipe << i -} -RN.times.map{ - r, n = Ractor.select(*rs) - rs.delete r - n -}.sort #=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -``` - -Multiple Ractors can send to one Ractor. - -```ruby -# Create 10 ractors and they send objects to pipe ractor. -# pipe ractor yield received objects - -pipe = Ractor.new do - loop do - Ractor.yield Ractor.receive - end -end - -RN = 10 -rs = RN.times.map{|i| - Ractor.new pipe, i do |pipe, i| - pipe << i - end -} - -RN.times.map{ - pipe.take -}.sort #=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -``` - TODO: Current `Ractor.select()` has the same issue of `select(2)`, so this interface should be refined. TODO: `select` syntax of go-language uses round-robin technique to make fair scheduling. Now `Ractor.select()` doesn't use it. ### Closing Ractor's ports -* `Ractor#close_incoming/outgoing` close incoming/outgoing ports (similar to `Queue#close`). -* `Ractor#close_incoming` - * `r.send(obj)` where `r`'s incoming port is closed, will raise an exception. - * When the incoming queue is empty and incoming port is closed, `Ractor.receive` raises an exception. If the incoming queue is not empty, it dequeues an object without exceptions. -* `Ractor#close_outgoing` - * `Ractor.yield` on a Ractor which closed the outgoing port, it will raise an exception. - * `Ractor#take` for a Ractor which closed the outgoing port, it will raise an exception. If `Ractor#take` is blocking, it will raise an exception. +* `Ractor::Port#close` close the ports (similar to `Queue#close`). + * `port.send(obj)` where `port` is closed, will raise an exception. + * When the queue connected to the port is empty and port is closed, `Ractor::Port#receive` raises an exception. If the queue is not empty, it dequeues an object without exceptions. * When a Ractor terminates, the ports are closed automatically. - * Return value of the Ractor's block will be yielded as `Ractor.yield(ret_val)`, even if the implementation terminates the based native thread. -Example (try to take from closed Ractor): +Example (try to get a result from closed Ractor): ```ruby r = Ractor.new do 'finish' end -r.take # success (will return 'finish') -begin - o = r.take # try to take from closed Ractor -rescue Ractor::ClosedError - 'ok' -else - "ng: #{o}" +r.join # success (wait for the termination) +r.value # success (will return 'finish') + +# the first Ractor which success the `Ractor#value` can get the result +Ractor.new r do |r| + r.value #=> Ractor::Error end ``` @@ -422,7 +264,7 @@ Example (try to send to closed (terminated) Ractor): r = Ractor.new do end -r.take # wait terminate +r.join # wait terminate begin r.send(1) @@ -433,11 +275,9 @@ else end ``` -When multiple Ractors are waiting for `Ractor.yield()`, `Ractor#close_outgoing` will cancel all blocking by raising an exception (`ClosedError`). - ### Send a message by copying -`Ractor#send(obj)` or `Ractor.yield(obj)` copy `obj` deeply if `obj` is an unshareable object. +`Ractor::Port#send(obj)` copy `obj` deeply if `obj` is an unshareable object. ```ruby obj = 'str'.dup @@ -446,7 +286,7 @@ r = Ractor.new obj do |msg| msg.object_id end -obj.object_id == r.take #=> false +obj.object_id == r.value #=> false ``` Some objects are not supported to copy the value, and raise an exception. @@ -466,7 +306,7 @@ end ### Send a message by moving -`Ractor#send(obj, move: true)` or `Ractor.yield(obj, move: true)` move `obj` to the destination Ractor. +`Ractor::Port#send(obj, move: true)` moves `obj` to the destination Ractor. If the source Ractor touches the moved object (for example, call the method like `obj.foo()`), it will be an error. ```ruby @@ -478,7 +318,7 @@ end str = 'hello' r.send str, move: true -modified = r.take #=> 'hello world' +modified = r.value #=> 'hello world' # str is moved, and accessing str from this Ractor is prohibited @@ -492,22 +332,6 @@ else end ``` -```ruby -# move with Ractor.yield -r = Ractor.new do - obj = 'hello' - Ractor.yield obj, move: true - obj << 'world' # raise Ractor::MovedError -end - -str = r.take -begin - r.take -rescue Ractor::RemoteError - p str #=> "hello" -end -``` - Some objects are not supported to move, and an exception will be raised. ```ruby @@ -554,13 +378,13 @@ r = Ractor.new do end begin - r.take + r.join rescue Ractor::RemoteError => e e.cause.message #=> 'can not access global variables from non-main Ractors' end ``` -Note that some special global variables are ractor-local, like `$stdin`, `$stdout`, `$stderr`. See [[Bug #17268]](https://bugs.ruby-lang.org/issues/17268) for more details. +Note that some special global variables, such as `$stdin`, `$stdout` and `$stderr` are Ractor-lcoal. See [[Bug #17268]](https://bugs.ruby-lang.org/issues/17268) for more details. ### Instance variables of shareable objects @@ -575,7 +399,7 @@ p Ractor.new do class C @iv end -end.take #=> 1 +end.value #=> 1 ``` Otherwise, only the main Ractor can access instance variables of shareable objects. @@ -601,7 +425,7 @@ Ractor.new do #=> "can not set instance variables of classes/modules by non-main Ractors" end end -end.take +end.join ``` @@ -615,7 +439,7 @@ r = Ractor.new shared do |shared| end begin - r.take + r.join rescue Ractor::RemoteError => e e.cause.message #=> can not access instance variables of shareable objects from non-main Ractors (Ractor::IsolationError) end @@ -640,7 +464,7 @@ end begin - r.take + r.join rescue => e e.class #=> Ractor::IsolationError end @@ -658,7 +482,7 @@ r = Ractor.new do C::CONST end begin - r.take + r.join rescue => e e.class #=> Ractor::IsolationError end @@ -673,7 +497,7 @@ r = Ractor.new do C::CONST = 'str' end begin - r.take + r.join rescue => e e.class #=> Ractor::IsolationError end @@ -770,54 +594,51 @@ end ### Worker pool +(1) One ractor has a pool + ```ruby require 'prime' -pipe = Ractor.new do - loop do - Ractor.yield Ractor.receive - end -end - N = 1000 RN = 10 + +# make RN workers workers = (1..RN).map do - Ractor.new pipe do |pipe| - while n = pipe.take - Ractor.yield [n, n.prime?] + Ractor.new do |; result_port| + loop do + n, result_port = Ractor.receive + result_port << [n, n.prime?, Ractor.current] end end end -(1..N).each{|i| - pipe << i -} +result_port = Ractor::Port.new +results = [] -pp (1..N).map{ - _r, (n, b) = Ractor.select(*workers) - [n, b] -}.sort_by{|(n, b)| n} +(1..N).each do |i| + if workers.empty? + # receive a result + n, result, w = result_port.receive + results << [n, result] + else + w = workers.pop + end + + # send a task to the idle worker ractor + w << [i, result_port] +end + +# receive a result +while results.size != N + n, result, _w = result_port.receive + results << [n, result] +end + +pp results.sort_by{|n, result| n} ``` ### Pipeline -```ruby -# pipeline with yield/take -r1 = Ractor.new do - 'r1' -end - -r2 = Ractor.new r1 do |r1| - r1.take + 'r2' -end - -r3 = Ractor.new r2 do |r2| - r2.take + 'r3' -end - -p r3.take #=> 'r1r2r3' -``` - ```ruby # pipeline with send/receive diff --git a/doc/rdoc/markup_reference.rb b/doc/rdoc/markup_reference.rb deleted file mode 100644 index ee585b2497..0000000000 --- a/doc/rdoc/markup_reference.rb +++ /dev/null @@ -1,1281 +0,0 @@ -require 'rdoc' - -# \Class \RDoc::MarkupReference exists only to provide a suitable home -# for a reference document for \RDoc markup. -# -# All objects defined in this class -- classes, modules, methods, aliases, -# attributes, and constants -- are solely for illustrating \RDoc markup, -# and have no other legitimate use. -# -# == About the Examples -# -# - Examples in this reference are Ruby code and comments; -# certain differences from other sources -# (such as C code and comments) are noted. -# - Almost all examples on this page are all RDoc-like; -# that is, they have no explicit comment markers like Ruby # -# or C /* ... */. -# - An example that shows rendered HTML output -# displays that output in a blockquote: -# -# >>> -# Some stuff -# -# == \RDoc Sources -# -# The sources of \RDoc documentation vary according to the type of file: -# -# - .rb (Ruby code file): -# -# - Markup may be found in Ruby comments: -# A comment that immediately precedes the definition -# of a Ruby class, module, method, alias, constant, or attribute -# becomes the documentation for that defined object. -# - An \RDoc directive may be found in: -# -# - A trailing comment (on the same line as code); -# see :nodoc:, :doc:, and :notnew:. -# - A single-line comment; -# see other {Directives}[rdoc-ref:RDoc::MarkupReference@Directives]. -# -# - Documentation may be derived from the Ruby code itself; -# see {Documentation Derived from Ruby Code}[rdoc-ref:RDoc::MarkupReference@Documentation+Derived+from+Ruby+Code]. -# -# - .c (C code file): markup is parsed from C comments. -# A comment that immediately precedes -# a function that implements a Ruby method, -# or otherwise immediately precedes the definition of a Ruby object, -# becomes the documentation for that object. -# - .rdoc (\RDoc markup text file) or .md (\RDoc markdown text file): -# markup is parsed from the entire file. -# The text is not associated with any code object, -# but may (depending on how the documentation is built) -# become a separate page. -# -# An RDoc document: -# -# - A (possibly multi-line) comment in a Ruby or C file -# that generates \RDoc documentation (as above). -# - The entire markup (.rdoc) file or markdown (.md) file -# (which is usually multi-line). -# -# === Blocks -# -# It's convenient to think of an \RDoc document as a sequence of _blocks_ -# of various types (details at the links): -# -# - {Paragraph}[rdoc-ref:RDoc::MarkupReference@Paragraphs]: -# an ordinary paragraph. -# - {Verbatim text block}[rdoc-ref:RDoc::MarkupReference@Verbatim+Text+Blocks]: -# a block of text to be rendered literally. -# - {Code block}[rdoc-ref:RDoc::MarkupReference@Code+Blocks]: -# a verbatim text block containing Ruby code, -# to be rendered with code highlighting. -# - {Block quote}[rdoc-ref:RDoc::MarkupReference@Block+Quotes]: -# a longish quoted passage, to be rendered with indentation -# instead of quote marks. -# - {List}[rdoc-ref:RDoc::MarkupReference@Lists]: items for -# a bullet list, numbered list, lettered list, or labeled list. -# - {Heading}[rdoc-ref:RDoc::MarkupReference@Headings]: -# a heading. -# - {Horizontal rule}[rdoc-ref:RDoc::MarkupReference@Horizontal+Rules]: -# a line across the rendered page. -# - {Directive}[rdoc-ref:RDoc::MarkupReference@Directives]: -# various special directions for the rendering. -# - {Text Markup}[rdoc-ref:RDoc:MarkupReference@Text+Markup]: -# text to be rendered in a special way. -# -# About the blocks: -# -# - Except for a paragraph, a block is distinguished by its indentation, -# or by unusual initial or embedded characters. -# - Any block may appear independently -# (that is, not nested in another block); -# some blocks may be nested, as detailed below. -# - In a multi-line block, -# \RDoc looks for the block's natural left margin, -# which becomes the base margin for the block -# and is the initial current margin for the block. -# -# ==== Paragraphs -# -# A paragraph consists of one or more non-empty lines of ordinary text, -# each beginning at the current margin. -# -# Note: Here, ordinary text means text that is not identified -# by indentation, or by unusual initial or embedded characters. -# See below. -# -# Paragraphs are separated by one or more empty lines. -# -# Example input: -# -# \RDoc produces HTML and command-line documentation for Ruby projects. -# \RDoc includes the rdoc and ri tools for generating and displaying -# documentation from the command-line. -# -# You'll love it. -# -# Rendered HTML: -# >>> -# \RDoc produces HTML and command-line documentation for Ruby projects. -# \RDoc includes the rdoc and ri tools for generating and displaying -# documentation from the command-line. -# -# You'll love it. -# -# A paragraph may contain nested blocks, including: -# -# - {Verbatim text blocks}[rdoc-ref:RDoc::MarkupReference@Verbatim+Text+Blocks]. -# - {Code blocks}[rdoc-ref:RDoc::MarkupReference@Code+Blocks]. -# - {Block quotes}[rdoc-ref:RDoc::MarkupReference@Block+Quotes]. -# - {Lists}[rdoc-ref:RDoc::MarkupReference@Lists]. -# - {Headings}[rdoc-ref:RDoc::MarkupReference@Headings]. -# - {Horizontal rules}[rdoc-ref:RDoc::MarkupReference@Horizontal+Rules]. -# - {Text Markup}[rdoc-ref:RDoc:MarkupReference@Text+Markup]. -# -# ==== Verbatim Text Blocks -# -# Text indented farther than the current margin becomes a verbatim text block -# (or a code block, described next). -# In the rendered HTML, such text: -# -# - Is indented. -# - Has a contrasting background color. -# -# The verbatim text block ends at the first line beginning at the current margin. -# -# Example input: -# -# This is not verbatim text. -# -# This is verbatim text. -# Whitespace is honored. # See? -# Whitespace is honored. # See? -# -# This is still the same verbatim text block. -# -# This is not verbatim text. -# -# Rendered HTML: -# >>> -# This is not verbatim text. -# -# This is verbatim text. -# Whitespace is honored. # See? -# Whitespace is honored. # See? -# -# This is still the same verbatim text block. -# -# This is not verbatim text. -# -# A verbatim text block may not contain nested blocks of any kind -# -- it's verbatim. -# -# ==== Code Blocks -# -# A special case of verbatim text is the code block, -# which is merely verbatim text that \RDoc recognizes as Ruby code: -# -# In the rendered HTML, the code block: -# -# - Is indented. -# - Has a contrasting background color. -# - Has syntax highlighting. -# -# Example input: -# -# Consider this method: -# -# def foo(name = '', value = 0) -# @name = name # Whitespace is still honored. -# @value = value -# end -# -# -# Rendered HTML: -# >>> -# Consider this method: -# -# def foo(name = '', value = 0) -# @name = name # Whitespace is still honored. -# @value = value -# end -# -# Pro tip: If your indented Ruby code does not get highlighted, -# it may contain a syntax error. -# -# A code block may not contain nested blocks of any kind -# -- it's verbatim. -# -# ==== Block Quotes -# -# You can use the characters >>> (unindented), -# followed by indented text, to treat the text -# as a {block quote}[https://en.wikipedia.org/wiki/Block_quotation]: -# -# Example input: -# -# Here's a block quote: -# >>> -# Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer -# commodo quam iaculis massa posuere, dictum fringilla justo pulvinar. -# Quisque turpis erat, pharetra eu dui at, sollicitudin accumsan nulla. -# -# Aenean congue ligula eu ligula molestie, eu pellentesque purus -# faucibus. In id leo non ligula condimentum lobortis. Duis vestibulum, -# diam in pellentesque aliquet, mi tellus placerat sapien, id euismod -# purus magna ut tortor. -# -# Rendered HTML: -# -# >>> -# Here's a block quote: -# >>> -# Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer -# commodo quam iaculis massa posuere, dictum fringilla justo pulvinar. -# Quisque turpis erat, pharetra eu dui at, sollicitudin accumsan nulla. -# -# Aenean congue ligula eu ligula molestie, eu pellentesque purus -# faucibus. In id leo non ligula condimentum lobortis. Duis vestibulum, -# diam in pellentesque aliquet, mi tellus placerat sapien, id euismod -# purus magna ut tortor. -# -# Note that, unlike verbatim text, single newlines are not honored, -# but that a double newline begins a new paragraph in the block quote. -# -# A block quote may contain nested blocks, including: -# -# - Other block quotes. -# - {Paragraphs}[rdoc-ref:RDoc::MarkupReference@Paragraphs]. -# - {Verbatim text blocks}[rdoc-ref:RDoc::MarkupReference@Verbatim+Text+Blocks]. -# - {Code blocks}[rdoc-ref:RDoc::MarkupReference@Code+Blocks]. -# - {Lists}[rdoc-ref:RDoc::MarkupReference@Lists]. -# - {Headings}[rdoc-ref:RDoc::MarkupReference@Headings]. -# - {Horizontal rules}[rdoc-ref:RDoc::MarkupReference@Horizontal+Rules]. -# - {Text Markup}[rdoc-ref:RDoc:MarkupReference@Text+Markup]. -# -# ==== Lists -# -# Each type of list item is marked by a special beginning: -# -# - Bullet list item: Begins with a hyphen or asterisk. -# - Numbered list item: Begins with digits and a period. -# - Lettered list item: Begins with an alphabetic character and a period. -# - Labeled list item: Begins with one of: -# - Square-bracketed text. -# - A word followed by two colons. -# -# A list begins with a list item and continues, even across blank lines, -# as long as list items of the same type are found at the same indentation level. -# -# A new list resets the current margin inward. -# Additional lines of text aligned at that margin -# are part of the continuing list item. -# -# A list item may be continued on additional lines that are aligned -# with the first line. See examples below. -# -# A list item may contain nested blocks, including: -# -# - Other lists of any type. -# - {Paragraphs}[rdoc-ref:RDoc::MarkupReference@Paragraphs]. -# - {Verbatim text blocks}[rdoc-ref:RDoc::MarkupReference@Verbatim+Text+Blocks]. -# - {Code blocks}[rdoc-ref:RDoc::MarkupReference@Code+Blocks]. -# - {Block quotes}[rdoc-ref:RDoc::MarkupReference@Block+Quotes]. -# - {Headings}[rdoc-ref:RDoc::MarkupReference@Headings]. -# - {Horizontal rules}[rdoc-ref:RDoc::MarkupReference@Horizontal+Rules]. -# - {Text Markup}[rdoc-ref:RDoc:MarkupReference@Text+Markup]. -# -# ===== Bullet Lists -# -# A bullet list item begins with a hyphen or asterisk. -# -# Example input: -# -# - An item. -# - Another. -# - An item spanning -# multiple lines. -# -# * Yet another. -# - Last one. -# -# Rendered HTML: -# >>> -# - An item. -# - Another. -# - An item spanning -# multiple lines. -# -# * Yet another. -# - Last one. -# -# ===== Numbered Lists -# -# A numbered list item begins with digits and a period. -# -# The items are automatically re-numbered. -# -# Example input: -# -# 100. An item. -# 10. Another. -# 1. An item spanning -# multiple lines. -# -# 1. Yet another. -# 1000. Last one. -# -# Rendered HTML: -# >>> -# 100. An item. -# 10. Another. -# 1. An item spanning -# multiple lines. -# -# 1. Yet another. -# 1000. Last one. -# -# ===== Lettered Lists -# -# A lettered list item begins with letters and a period. -# -# The items are automatically "re-lettered." -# -# Example input: -# -# z. An item. -# y. Another. -# x. An item spanning -# multiple lines. -# -# x. Yet another. -# a. Last one. -# -# Rendered HTML: -# >>> -# z. An item. -# y. Another. -# -# x. Yet another. -# a. Last one. -# -# ===== Labeled Lists -# -# A labeled list item begins with one of: -# -# - Square-bracketed text: the label and text are on two lines. -# - A word followed by two colons: the label and text are on the same line. -# -# Example input: -# -# [foo] An item. -# bat:: Another. -# [bag] An item spanning -# multiple lines. -# -# [bar baz] Yet another. -# bam:: Last one. -# -# Rendered HTML: -# >>> -# [foo] An item. -# bat:: Another. -# [bag] An item spanning -# multiple lines. -# -# [bar baz] Yet another. -# bam:: Last one. -# -# ==== Headings -# -# A heading begins with up to six equal-signs, followed by heading text. -# Whitespace between those and the heading text is optional. -# -# Examples: -# -# = Section 1 -# == Section 1.1 -# === Section 1.1.1 -# === Section 1.1.2 -# == Section 1.2 -# = Section 2 -# = Foo -# == Bar -# === Baz -# ==== Bam -# ===== Bat -# ====== Bad -# ============Still a Heading (Level 6) -# \== Not a Heading -# -# A heading may contain only one type of nested block: -# -# - {Text Markup}[rdoc-ref:RDoc:MarkupReference@Text+Markup]. -# -# ==== Horizontal Rules -# -# A horizontal rule consists of a line with three or more hyphens -# and nothing more. -# -# Example input: -# -# --- -# --- Not a horizontal rule. -# -# -- Also not a horizontal rule. -# --- -# -# Rendered HTML: -# >>> -# --- -# --- Not a horizontal rule. -# -# -- Also not a horizontal rule. -# --- -# -# ==== Directives -# -# ===== Directives for Allowing or Suppressing Documentation -# -# - # :stopdoc:: -# -# - Appears on a line by itself. -# - Specifies that \RDoc should ignore markup -# until next :startdoc: directive or end-of-file. -# -# - # :startdoc:: -# -# - Appears on a line by itself. -# - Specifies that \RDoc should resume parsing markup. -# -# - # :enddoc:: -# -# - Appears on a line by itself. -# - Specifies that \RDoc should ignore markup to end-of-file -# regardless of other directives. -# -# - # :nodoc:: -# -# - Appended to a line of code -# that defines a class, module, method, alias, constant, or attribute. -# -# - Specifies that the defined object should not be documented. -# -# - For a method definition in C code, it the directive must be in the comment line -# immediately preceding the definition: -# -# /* :nodoc: */ -# static VALUE -# some_method(VALUE self) -# { -# return self; -# } -# -# Note that this directive has no effect at all -# when placed at the method declaration: -# -# /* :nodoc: */ -# rb_define_method(cMyClass, "do_something", something_func, 0); -# -# The above comment is just a comment and has nothing to do with \RDoc. -# Therefore, +do_something+ method will be reported as "undocumented" -# unless that method or function is documented elsewhere. -# -# - For a constant definition in C code, this directive can not work -# because there is no "implementation" place for a constant. -# -# - # :nodoc: all: -# -# - Appended to a line of code -# that defines a class or module. -# - Specifies that the class or module should not be documented. -# By default, however, a nested class or module _will_ be documented. -# -# - # :doc:: -# -# - Appended to a line of code -# that defines a class, module, method, alias, constant, or attribute. -# - Specifies the defined object should be documented, even if it otherwise -# would not be documented. -# -# - # :notnew: (aliased as :not_new: and :not-new:): -# -# - Appended to a line of code -# that defines instance method +initialize+. -# - Specifies that singleton method +new+ should not be documented. -# By default, Ruby fakes a corresponding singleton method +new+, -# which \RDoc includes in the documentation. -# Note that instance method +initialize+ is private, and so by default -# is not documented. -# -# For Ruby code, but not for other \RDoc sources, -# there is a shorthand for :stopdoc: and :startdoc:: -# -# # Documented. -# #-- -# # Not documented. -# #++ -# # Documented. -# -# For C code, any of directives :startdoc:, :stopdoc:, -# and :enddoc: may appear in a stand-alone comment: -# -# /* :startdoc: */ -# /* :stopdoc: */ -# /* :enddoc: */ -# -# ===== Directive for Specifying \RDoc Source Format -# -# - # :markup: _type_: -# -# - Appears on a line by itself. -# - Specifies the format for the \RDoc input; -# parameter +type+ is one of: +rdoc+ (the default), +markdown+, +rd+, +tomdoc+. -# See {Markup Formats}[rdoc-ref:RDoc::Markup@Markup+Formats]. -# -# ===== Directives for Method Documentation -# -# - # :call-seq:: -# -# - Appears on a line by itself. -# - Specifies the calling sequence to be reported in the HTML, -# overriding the actual calling sequence in the code. -# See method #call_seq_directive. -# -# Note that \RDoc can build the calling sequence for a Ruby-coded method, -# but not for other languages. -# You may want to override that by explicitly giving a :call-seq: -# directive if you want to include: -# -# - A return type, which is not automatically inferred. -# - Multiple calling sequences. -# -# For C code, the directive may appear in a stand-alone comment. -# -# - # :args: _arg_names_ (aliased as :arg:): -# -# - Appears on a line by itself. -# - Specifies the arguments to be reported in the HTML, -# overriding the actual arguments in the code. -# See method #args_directive. -# -# - # :yields: _arg_names_ (aliased as :yield:): -# -# - Appears on a line by itself. -# - Specifies the yield arguments to be reported in the HTML, -# overriding the actual yield in the code. -# See method #yields_directive. -# -# ===== Directives for Organizing Documentation -# -# By default, \RDoc groups: -# -# - Singleton methods together in alphabetical order. -# - Instance methods and their aliases together in alphabetical order. -# - Attributes and their aliases together in alphabetical order. -# -# You can use directives to modify those behaviors. -# -# - # :section: _section_title_: -# -# - Appears on a line by itself. -# - Specifies that following methods are to be grouped into the section -# with the given section_title, -# or into the default section if no title is given. -# The directive remains in effect until another such directive is given, -# but may be temporarily overridden by directive :category:. -# See below. -# -# The comment block containing this directive: -# -# - Must be separated by a blank line from the documentation for the next item. -# - May have one or more lines preceding the directive. -# These will be removed, along with any trailing lines that match them. -# Such lines may be visually helpful. -# - Lines of text that are not so removed become the descriptive text -# for the section. -# -# Example: -# -# # ---------------------------------------- -# # :section: My Section -# # This is the section that I wrote. -# # See it glisten in the noon-day sun. -# # ---------------------------------------- -# -# ## -# # Comment for some_method -# def some_method -# # ... -# end -# -# You can use directive :category: to temporarily -# override the current section. -# -# - # :category: _section_title_: -# -# - Appears on a line by itself. -# - Specifies that just one following method is to be included -# in the given section, or in the default section if no title is given. -# Subsequent methods are to be grouped into the current section. -# -# ===== Directive for Including a File -# -# - # :include: _filepath_: -# -# - Appears on a line by itself. -# - Specifies that the contents of the given file -# are to be included at this point. -# The file content is shifted to have the same indentation as the colon -# at the start of the directive. -# -# The file is searched for in the directory containing the current file, -# and then in each of the directories given with the --include -# command-line option. -# -# For C code, the directive may appear in a stand-alone comment -# -# ==== Text Markup -# -# Text markup is metatext that affects HTML rendering: -# -# - Typeface: italic, bold, monofont. -# - Character conversions: copyright, trademark, certain punctuation. -# - Links. -# - Escapes: marking text as "not markup." -# -# ===== Typeface Markup -# -# Typeface markup can specify that text is to be rendered -# as italic, bold, or monofont. -# -# Typeface markup may contain only one type of nested block: -# -# - More typeface markup: -# italic, bold, monofont. -# -# ====== Italic -# -# Text may be marked as italic via HTML tag or . -# -# Example input: -# -# Italicized words in a paragraph. -# -# >>> -# Italicized words in a block quote. -# -# - Italicized words in a list item. -# -# ====== Italicized words in a Heading -# -# Italicized passage containing *bold* and +monofont+. -# -# Rendered HTML: -# >>> -# Italicized words in a paragraph. -# -# >>> -# Italicized words in a block quote. -# -# - Italicized words in a list item. -# -# ====== Italicized words in a Heading -# -# Italicized passage containing *bold* and +monofont+. -# -# A single word may be italicized via a shorthand: -# prefixed and suffixed underscores. -# -# Example input: -# -# _Italic_ in a paragraph. -# -# >>> -# _Italic_ in a block quote. -# -# - _Italic_ in a list item. -# -# ====== _Italic_ in a Heading -# -# Rendered HTML: -# >>> -# _Italic_ in a paragraph. -# -# >>> -# _Italic_ in a block quote. -# -# - _Italic_ in a list item. -# -# ====== _Italic_ in a Heading -# -# ====== Bold -# -# Text may be marked as bold via HTML tag . -# -# Example input: -# -# Bold words in a paragraph. -# -# >>> -# Bold words in a block quote. -# -# - Bold words in a list item. -# -# ====== Bold words in a Heading -# -# Bold passage containing _italics_ and +monofont+. -# -# Rendered HTML: -# -# >>> -# Bold words in a paragraph. -# -# >>> -# Bold words in a block quote. -# -# - Bold words in a list item. -# -# ====== Bold words in a Heading -# -# Bold passage containing _italics_ and +monofont+. -# -# A single word may be made bold via a shorthand: -# prefixed and suffixed asterisks. -# -# Example input: -# -# *Bold* in a paragraph. -# -# >>> -# *Bold* in a block quote. -# -# - *Bold* in a list item. -# -# ===== *Bold* in a Heading -# -# Rendered HTML: -# -# >>> -# *Bold* in a paragraph. -# -# >>> -# *Bold* in a block quote. -# -# - *Bold* in a list item. -# -# ===== *Bold* in a Heading -# -# ====== Monofont -# -# Text may be marked as monofont -# -- sometimes called 'typewriter font' -- -# via HTML tag or . -# -# Example input: -# -# Monofont words in a paragraph. -# -# >>> -# Monofont words in a block quote. -# -# - Monofont words in a list item. -# -# ====== Monofont words in heading -# -# Monofont passage containing _italics_ and *bold*. -# -# Rendered HTML: -# -# >>> -# Monofont words in a paragraph. -# -# >>> -# Monofont words in a block quote. -# -# - Monofont words in a list item. -# -# ====== Monofont words in heading -# -# Monofont passage containing _italics_ and *bold*. -# -# A single word may be made monofont by a shorthand: -# prefixed and suffixed plus-signs. -# -# Example input: -# -# +Monofont+ in a paragraph. -# -# >>> -# +Monofont+ in a block quote. -# -# - +Monofont+ in a list item. -# -# ====== +Monofont+ in a Heading -# -# Rendered HTML: -# -# >>> -# +Monofont+ in a paragraph. -# -# >>> -# +Monofont+ in a block quote. -# -# - +Monofont+ in a list item. -# -# ====== +Monofont+ in a Heading -# -# ==== Character Conversions -# -# Certain combinations of characters may be converted to special characters; -# whether the conversion occurs depends on whether the special character -# is available in the current encoding. -# -# - (c) converts to (c) (copyright character); must be lowercase. -# -# - (r) converts to (r) (registered trademark character); must be lowercase. -# -# - 'foo' converts to 'foo' (smart single-quotes). -# -# - "foo" converts to "foo" (smart double-quotes). -# -# - foo ... bar converts to foo ... bar (1-character ellipsis). -# -# - foo -- bar converts to foo -- bar (1-character en-dash). -# -# - foo --- bar converts to foo --- bar (1-character em-dash). -# -# ==== Links -# -# Certain strings in \RDoc text are converted to links. -# Any such link may be suppressed by prefixing a backslash. -# This section shows how to link to various -# targets. -# -# [Class] -# -# - On-page: DummyClass links to DummyClass. -# - Off-page: RDoc::Alias links to RDoc::Alias. -# -# Note: For poeple want to mark up code (such as class, module, -# constant, and method) as "+code+" (for interoperability -# with other MarkDown parsers mainly), such word that refers a known -# code object and is marked up entirely and separately as "monofont" -# is also converted to a link. -# -# - +DummyClass+ links to DummyClass -# - +DummyClass-object+ is not a link. -# -# [Module] -# -# - On-page: DummyModule links to DummyModule. -# - Off-page: RDoc links to RDoc. -# -# [Constant] -# -# - On-page: DUMMY_CONSTANT links to DUMMY_CONSTANT. -# - Off-page: RDoc::Text::MARKUP_FORMAT links to RDoc::Text::MARKUP_FORMAT. -# -# [Singleton Method] -# -# - On-page: ::dummy_singleton_method links to ::dummy_singleton_method. -# - Off-pageRDoc::TokenStream::to_html links to RDoc::TokenStream::to_html. -# -# Note: Occasionally \RDoc is not linked to a method whose name -# has only special characters. Check whether the links you were expecting -# are actually there. If not, you'll need to put in an explicit link; -# see below. -# -# Pro tip: The link to any method is available in the alphabetical table of contents -# at the top left of the page for the class or module. -# -# [Instance Method] -# -# - On-page: #dummy_instance_method links to #dummy_instance_method. -# - Off-page: RDoc::Alias#html_name links to RDoc::Alias#html_name. -# -# See the Note and Pro Tip immediately above. -# -# [Attribute] -# -# - On-page: #dummy_attribute links to #dummy_attribute. -# - Off-page: RDoc::Alias#name links to RDoc::Alias#name. -# -# [Alias] -# -# - On-page: #dummy_instance_alias links to #dummy_instance_alias. -# - Off-page: RDoc::Alias#new_name links to RDoc::Alias#new_name. -# -# [Protocol +http+] -# -# - Linked: http://yahoo.com links to http://yahoo.com. -# -# [Protocol +https+] -# -# - Linked: https://github.com links to https://github.com. -# -# [Protocol +ftp+] -# -# - Linked: ftp://nosuch.site links to ftp://nosuch.site. -# -# [Protocol +mailto+] -# -# - Linked: mailto:/foo@bar.com links to mailto://foo@bar.com. -# -# [Protocol +irc+] -# -# - link: irc://irc.freenode.net/ruby links to irc://irc.freenode.net/ruby. -# -# [Image Filename Extensions] -# -# - Link: https://www.ruby-lang.org/images/header-ruby-logo@2x.png is -# converted to an in-line HTML +img+ tag, which displays the image in the HTML: -# -# https://www.ruby-lang.org/images/header-ruby-logo@2x.png -# -# Also works for +bmp+, +gif+, +jpeg+, and +jpg+ files. -# -# Note: Works only for a fully qualified URL. -# -# [Heading] -# -# - Link: RDoc::RD@LICENSE links to RDoc::RDoc::RD@LICENSE. -# -# Note that spaces in the actual heading are represented by + characters -# in the linkable text. -# -# - Link: RDoc::Options@Saved+Options -# links to RDoc::Options@Saved+Options. -# -# Punctuation and other special characters must be escaped like CGI.escape. -# -# Pro tip: The link to any heading is available in the alphabetical table of contents -# at the top left of the page for the class or module. -# -# [Section] -# -# See {Directives for Organizing Documentation}[#class-RDoc::MarkupReference-label-Directives+for+Organizing+Documentation]. -# -# - Link: RDoc::Markup::ToHtml@Visitor links to RDoc::Markup::ToHtml@Visitor. -# -# If a section and a heading share the same name, the link target is the section. -# -# [Single-Word Text Link] -# -# Use square brackets to create single-word text link: -# -# - GitHub[https://github.com] links to GitHub[https://github.com]. -# -# [Multi-Word Text Link] -# -# Use square brackets and curly braces to create a multi-word text link. -# -# - {GitHub home page}[https://github.com] links to -# {GitHub home page}[https://github.com]. -# -# [rdoc-ref Scheme] -# -# A link with the rdoc-ref: scheme links to the referenced item, -# if that item exists. -# The referenced item may be a class, module, method, file, etc. -# -# - Class: Alias[rdoc-ref:RDoc::Alias] generates Alias[rdoc-ref:RDoc::Alias]. -# - Module: RDoc[rdoc-ref:RDoc] generates RDoc[rdoc-ref:RDoc]. -# - Method: foo[rdoc-ref:RDoc::MarkupReference#dummy_instance_method] -# generates foo[rdoc-ref:RDoc::MarkupReference#dummy_instance_method]. -# - Constant: bar[rdoc-ref:RDoc::MarkupReference::DUMMY_CONSTANT] -# generates bar[rdoc-ref:RDoc::MarkupReference::DUMMY_CONSTANT]. -# - Attribute: baz[rdoc-ref:RDoc::MarkupReference#dummy_attribute] -# generates baz[rdoc-ref:RDoc::MarkupReference#dummy_attribute]. -# - Alias: bad[rdoc-ref:RDoc::MarkupReference#dummy_instance_alias] -# generates bad[rdoc-ref:RDoc::MarkupReference#dummy_instance_alias]. -# -# If the referenced item does not exist, no link is generated -# and entire rdoc-ref: square-bracketed clause is removed -# from the resulting text. -# -# - Nosuch[rdoc-ref:RDoc::Nosuch] generates Nosuch. -# -# -# [rdoc-label Scheme] -# -# [Simple] -# -# You can specify a link target using this form, -# where the second part cites the id of an HTML element. -# -# This link refers to the constant +DUMMY_CONSTANT+ on this page: -# -# - {DUMMY_CONSTANT}[rdoc-label:DUMMY_CONSTANT] -# -# Thus: -# -# {DUMMY_CONSTANT}[rdoc-label:DUMMY_CONSTANT] -# -# [With Return] -# -# You can specify both a link target and a local label -# that can be used as the target for a return link. -# These two links refer to each other: -# -# - {go to addressee}[rdoc-label:addressee:sender] -# - {return to sender}[rdoc-label:sender:addressee] -# -# Thus: -# -# {go to addressee}[rdoc-label:addressee:sender] -# -# Some text. -# -# {return to sender}[rdoc-label:sender:addressee] -# -# [link: Scheme] -# -# - link:README_rdoc.html links to link:README_rdoc.html. -# -# [rdoc-image Scheme] -# -# Use the rdoc-image scheme to display an image that is also a link: -# -# # {rdoc-image:path/to/image}[link_target] -# -# - Link: {rdoc-image:https://www.ruby-lang.org/images/header-ruby-logo@2x.png}[https://www.ruby-lang.org] -# displays image https://www.ruby-lang.org/images/header-ruby-logo@2x.png -# as a link to https://www.ruby-lang.org. -# -# {rdoc-image:https://www.ruby-lang.org/images/header-ruby-logo@2x.png}[https://www.ruby-lang.org] -# -# A relative path as the target also works: -# -# - Link: {rdoc-image:https://www.ruby-lang.org/images/header-ruby-logo@2x.png}[./Alias.html] links to ./Alias.html -# -# {rdoc-image:https://www.ruby-lang.org/images/header-ruby-logo@2x.png}[./Alias.html] -# -# === Escaping Text -# -# Text that would otherwise be interpreted as markup -# can be "escaped," so that it is not interpreted as markup; -# the escape character is the backslash ('\\'). -# -# In a verbatim text block or a code block, -# the escape character is always preserved: -# -# Example input: -# -# This is not verbatim text. -# -# This is verbatim text, with an escape character \. -# -# This is not a code block. -# -# def foo -# 'String with an escape character.' -# end -# -# Rendered HTML: -# -# >>> -# This is not verbatim text. -# -# This is verbatim text, with an escape character \. -# -# This is not a code block. -# -# def foo -# 'This is a code block with an escape character \.' -# end -# -# In typeface markup (italic, bold, or monofont), -# an escape character is preserved unless it is immediately -# followed by nested typeface markup. -# -# Example input: -# -# This list is about escapes; it contains: -# -# - Monofont text with unescaped nested _italic_. -# - Monofont text with escaped nested \_italic_. -# - Monofont text with an escape character \. -# -# Rendered HTML: -# -# >>> -# This list is about escapes; it contains: -# -# - Monofont text with unescaped nested _italic_. -# - Monofont text with escaped nested \_italic_. -# - Monofont text with an escape character \ . -# -# In other text-bearing blocks -# (paragraphs, block quotes, list items, headings): -# -# - A single escape character immediately followed by markup -# escapes the markup. -# - A single escape character followed by whitespace is preserved. -# - A single escape character anywhere else is ignored. -# - A double escape character is rendered as a single backslash. -# -# Example input: -# -# This list is about escapes; it contains: -# -# - An unescaped class name, RDoc, that will become a link. -# - An escaped class name, \RDoc, that will not become a link. -# - An escape character followed by whitespace \ . -# - An escape character \that is ignored. -# - A double escape character \\ that is rendered -# as a single backslash. -# -# Rendered HTML: -# -# >>> -# This list is about escapes; it contains: -# -# - An unescaped class name, RDoc, that will become a link. -# - An escaped class name, \RDoc, that will not become a link. -# - An escape character followed by whitespace \ . -# - An escape character \that is ignored. -# - A double escape character \\ that is rendered -# as a single backslash. -# -# == Documentation Derived from Ruby Code -# -# [Class] -# -# By default, \RDoc documents: -# -# - \Class name. -# - Parent class. -# - Singleton methods. -# - Instance methods. -# - Aliases. -# - Constants. -# - Attributes. -# -# [Module] -# -# By default, \RDoc documents: -# -# - \Module name. -# - \Singleton methods. -# - Instance methods. -# - Aliases. -# - Constants. -# - Attributes. -# -# [Method] -# -# By default, \RDoc documents: -# -# - \Method name. -# - Arguments. -# - Yielded values. -# -# See #method. -# -# [Alias] -# -# By default, \RDoc documents: -# -# - Alias name. -# - Aliased name. -# -# See #dummy_instance_alias and #dummy_instance_method. -# -# [Constant] -# -# By default, \RDoc documents: -# -# - \Constant name. -# -# See DUMMY_CONSTANT. -# -# [Attribute] -# -# By default, \RDoc documents: -# -# - Attribute name. -# - Attribute type ([R], [W], or [RW]) -# -# See #dummy_attribute. -# -class RDoc::MarkupReference - - # Example class. - class DummyClass; end - - # Example module. - module DummyModule; end - - # Example singleton method. - def self.dummy_singleton_method(foo, bar); end - - # Example instance method. - def dummy_instance_method(foo, bar); end; - - alias dummy_instance_alias dummy_instance_method - - # Example attribute. - attr_accessor :dummy_attribute - - alias dummy_attribute_alias dummy_attribute - - # Example constant. - DUMMY_CONSTANT = '' - - # :call-seq: - # call_seq_directive(foo, bar) - # Can be anything -> bar - # Also anything more -> baz or bat - # - # The :call-seq: directive overrides the actual calling sequence - # found in the Ruby code. - # - # - It can specify anything at all. - # - It can have multiple calling sequences. - # - # This one includes Can be anything -> foo, which is nonsense. - # - # Note that the "arrow" is two characters, hyphen and right angle-bracket, - # which is made into a single character in the HTML. - # - # Click on the calling sequence to see the code. - # - # Here is the :call-seq: directive given for the method: - # - # :call-seq: - # call_seq_directive(foo, bar) - # Can be anything -> bar - # Also anything more -> baz or bat - # - def call_seq_directive - nil - end - - # The :args: directive overrides the actual arguments found in the Ruby code. - # - # Click on the calling sequence to see the code. - # - def args_directive(foo, bar) # :args: baz - nil - end - - # The :yields: directive overrides the actual yield found in the Ruby code. - # - # Click on the calling sequence to see the code. - # - def yields_directive(foo, bar) # :yields: 'bat' - yield 'baz' - end - - # This method is documented only by \RDoc, except for these comments. - # - # Click on the calling sequence to see the code. - # - def method(foo, bar) - yield 'baz' - end - -end diff --git a/doc/security.rdoc b/doc/security.rdoc index e428036cf5..af9970d336 100644 --- a/doc/security.rdoc +++ b/doc/security.rdoc @@ -37,7 +37,7 @@ programs for configuration and database persistence of Ruby object trees. Similar to +Marshal+, it is able to deserialize into arbitrary Ruby classes. For example, the following YAML data will create an +ERB+ object when -deserialized, using the `unsafe_load` method: +deserialized, using the +unsafe_load+ method: !ruby/object:ERB src: puts `uname` @@ -53,19 +53,16 @@ method, variable and constant names. The reason for this is that symbols are simply integers with names attached to them, so they are faster to look up in hashtables. -Starting in version 2.2, most symbols can be garbage collected; these are -called mortal symbols. Most symbols you create (e.g. by calling -+to_sym+) are mortal. +Most symbols can be garbage collected; these are called _mortal_ +symbols. Most symbols you create (e.g. by calling +to_sym+) are mortal. -Immortal symbols on the other hand will never be garbage collected. +_Immortal_ symbols on the other hand will never be garbage collected. They are created when modifying code: * defining a method (e.g. with +define_method+), * setting an instance variable (e.g. with +instance_variable_set+), * creating a variable or constant (e.g. with +const_set+) -C extensions that have not been updated and are still calling `SYM2ID` +C extensions that have not been updated and are still calling +SYM2ID+ will create immortal symbols. -Bugs in 2.2.0: +send+ and +__send__+ also created immortal symbols, -and calling methods with keyword arguments could also create some. Don't create immortal symbols from user inputs. Otherwise, this would allow a user to mount a denial of service attack against your application by @@ -128,12 +125,3 @@ Note that the use of +public_send+ is also dangerous, as +send+ itself is public: 1.public_send("send", "eval", "...ruby code to be executed...") - -== DRb - -As DRb allows remote clients to invoke arbitrary methods, it is not suitable to -expose to untrusted clients. - -When using DRb, try to avoid exposing it over the network if possible. If this -isn't possible and you need to expose DRb to the world, you *must* configure an -appropriate security policy with DRb::ACL. diff --git a/doc/standard_library.md b/doc/standard_library.md index a6702bb80f..0c48ac0cdd 100644 --- a/doc/standard_library.md +++ b/doc/standard_library.md @@ -34,7 +34,6 @@ of each. ## Libraries - Bundler ([GitHub][bundler]): Manage your Ruby application's gem dependencies -- CGI ([GitHub][cgi]): Support for the Common Gateway Interface protocol - Delegator ([GitHub][delegate]): Provides three abilities to delegate method calls to an object - DidYouMean ([GitHub][did_you_mean]): "Did you mean?" experience in Ruby - English ([GitHub][English]): Provides references to special global variables with less cryptic names @@ -53,12 +52,12 @@ of each. - Prism ([GitHub][prism]): A portable, error-tolerant Ruby parser - Resolv ([GitHub][resolv]): Thread-aware DNS resolver library in Ruby - SecureRandom ([GitHub][securerandom]): Interface for a secure random number generator -- [Set](rdoc-ref:Set) ([GitHub][set]): Provides a class to deal with collections of unordered, unique values - Shellwords ([GitHub][shellwords]): Manipulates strings with the word parsing rules of the UNIX Bourne shell - Singleton ([GitHub][singleton]): Implementation of the Singleton pattern for Ruby - Tempfile ([GitHub][tempfile]): A utility class for managing temporary files - Time ([GitHub][time]): Extends the Time class with methods for parsing and conversion - Timeout ([GitHub][timeout]): Auto-terminate potentially long-running operations in Ruby +- TmpDir ([GitHub][tmpdir]): Extends the Dir class to manage the OS temporary file path - TSort ([GitHub][tsort]): Topological sorting using Tarjan's algorithm - UN ([GitHub][un]): Utilities to replace common UNIX commands - URI ([GitHub][uri]): A Ruby module providing support for Uniform Resource Identifiers @@ -72,13 +71,14 @@ of each. - Etc ([GitHub][etc]): Provides access to information typically stored in the UNIX /etc directory - Fcntl ([GitHub][fcntl]): Loads constants defined in the OS fcntl.h C header file - IO.console ([GitHub][io-console]): Extensions for the IO class, including `IO.console`, `IO.winsize`, etc. +- IO#nonblock ([GitHub][io-nonblock]): Enable non-blocking mode with IO class. +- IO#wait ([GitHub][io-wait]): Provides the feature for waiting until IO is readable or writable without blocking. - JSON ([GitHub][json]): Implements JavaScript Object Notation for Ruby - OpenSSL ([GitHub][openssl]): Provides SSL, TLS, and general-purpose cryptography for Ruby - Pathname ([GitHub][pathname]): Representation of the name of a file or directory on the filesystem - Psych ([GitHub][psych]): A YAML parser and emitter for Ruby - StringIO ([GitHub][stringio]): Pseudo-I/O on String objects - StringScanner ([GitHub][strscan]): Provides lexical scanning operations on a String -- TmpDir ([GitHub][tmpdir]): Extends the Dir class to manage the OS temporary file path - Zlib ([GitHub][zlib]): Ruby interface for the zlib compression/decompression library # Bundled gems @@ -92,9 +92,9 @@ of each. - [minitest]: A test library supporting TDD, BDD, mocking, and benchmarking - [power_assert]: Power Assert for Ruby -- [rake]: Ruby build program with capabilities similar to make +- [rake][rake-doc] ([GitHub][rake]): Ruby build program with capabilities similar to make - [test-unit]: A compatibility layer for MiniTest -- [rexml]: An XML toolkit for Ruby +- [rexml][rexml-doc] ([GitHub][rexml]): An XML toolkit for Ruby - [rss]: A family of libraries supporting various XML-based "feeds" - [net-ftp]: Support for the File Transfer Protocol - [net-imap]: Ruby client API for the Internet Message Access Protocol @@ -105,7 +105,7 @@ of each. - [rbs]: RBS is a language to describe the structure of Ruby programs - [typeprof]: A type analysis tool for Ruby code based on abstract interpretation - [debug]: Debugging functionality for Ruby -- [racc]: A LALR(1) parser generator written in Ruby +- [racc][racc-doc] ([GitHub][racc]): A LALR(1) parser generator written in Ruby - [mutex_m]: Mixin to extend objects to be handled like a Mutex - [getoptlong]: Parse command line options similar to the GNU C getopt_long() - [base64]: Support for encoding and decoding binary data using a Base64 representation @@ -117,13 +117,13 @@ of each. - [drb]: Distributed object system for Ruby - [nkf]: Ruby extension for the Network Kanji Filter - [syslog]: Ruby interface for the POSIX system logging facility -- [csv]: Provides an interface to read and write CSV files and data +- [csv][csv-doc] ([GitHub][csv]): Provides an interface to read and write CSV files and data - [ostruct]: A class to build custom data structures, similar to a Hash - [benchmark]: Provides methods to measure and report the time used to execute code -- [logger]: Provides a simple logging utility for outputting messages +- [logger][logger-doc] ([GitHub][logger]): Provides a simple logging utility for outputting messages - [pstore]: Implements a file-based persistence mechanism based on a Hash - [win32ole]: Provides an interface for OLE Automation in Ruby -- [reline]: GNU Readline and Editline in a pure Ruby implementation +- [reline][reline-doc] ([GitHub][reline]): GNU Readline and Editline in a pure Ruby implementation - [readline]: Wrapper for the Readline extension and Reline - [fiddle]: A libffi wrapper for Ruby @@ -137,7 +137,6 @@ of each. [benchmark]: https://github.com/ruby/benchmark [bigdecimal]: https://github.com/ruby/bigdecimal [bundler]: https://github.com/rubygems/rubygems -[cgi]: https://github.com/ruby/cgi [csv]: https://github.com/ruby/csv [date]: https://github.com/ruby/date [debug]: https://github.com/ruby/debug @@ -156,6 +155,8 @@ of each. [forwardable]: https://github.com/ruby/forwardable [getoptlong]: https://github.com/ruby/getoptlong [io-console]: https://github.com/ruby/io-console +[io-nonblock]: https://github.com/ruby/io-nonblock +[io-wait]: https://github.com/ruby/io-wait [ipaddr]: https://github.com/ruby/ipaddr [irb]: https://github.com/ruby/irb [json]: https://github.com/ruby/json @@ -195,7 +196,6 @@ of each. [rinda]: https://github.com/ruby/rinda [rss]: https://github.com/ruby/rss [securerandom]: https://github.com/ruby/securerandom -[set]: https://github.com/ruby/set [shellwords]: https://github.com/ruby/shellwords [singleton]: https://github.com/ruby/singleton [stringio]: https://github.com/ruby/stringio @@ -215,5 +215,11 @@ of each. [yaml]: https://github.com/ruby/yaml [zlib]: https://github.com/ruby/zlib +[reline-doc]: https://ruby.github.io/reline/ +[rake-doc]: https://ruby.github.io/rake/ [irb-doc]: https://ruby.github.io/irb/ [rdoc-doc]: https://ruby.github.io/rdoc/ +[logger-doc]: https://ruby.github.io/logger/ +[racc-doc]: https://ruby.github.io/racc/ +[csv-doc]: https://ruby.github.io/csv/ +[rexml-doc]: https://ruby.github.io/rexml/ diff --git a/string.rb b/doc/string.rb similarity index 96% rename from string.rb rename to doc/string.rb index 70f1dba5da..d68b40743b 100644 --- a/string.rb +++ b/doc/string.rb @@ -342,6 +342,8 @@ # # - #=~: Returns the index of the first substring that matches a given # Regexp or other object; returns +nil+ if no match is found. +# - #byteindex: Returns the byte index of the first occurrence of a given substring. +# - #byterindex: Returns the byte index of the last occurrence of a given substring. # - #index: Returns the index of the _first_ occurrence of a given substring; # returns +nil+ if none found. # - #rindex: Returns the index of the _last_ occurrence of a given substring; @@ -371,10 +373,9 @@ # - #eql?: Returns +true+ if the content is the same as the given other string. # - #<=>: Returns -1, 0, or 1 as a given other string is smaller than, # equal to, or larger than +self+. -# - #casecmp: Ignoring case, returns -1, 0, or 1 as a given -# other string is smaller than, equal to, or larger than +self+. -# - #casecmp?: Returns +true+ if the string is equal to a given string after Unicode case folding; -# +false+ otherwise. +# - #casecmp: Ignoring case, returns -1, 0, or 1 as +# +self+ is smaller than, equal to, or larger than a given other string. +# - #casecmp?: Ignoring case, returns whether a given other string is equal to +self+. # # === Modifying # @@ -389,6 +390,7 @@ # # _Substitution_ # +# - #bytesplice: Replaces bytes of +self+ with bytes from a given string; returns +self+. # - #sub!: Replaces the first substring that matches a given pattern with a given replacement string; # returns +self+ if any changes, +nil+ otherwise. # - #gsub!: Replaces each substring that matches a given pattern with a given replacement string; @@ -425,6 +427,8 @@ # - #slice!, #[]=: Removes a substring determined by a given index, start/length, range, regexp, or substring. # - #squeeze!: Removes contiguous duplicate characters; returns +self+. # - #delete!: Removes characters as determined by the intersection of substring arguments. +# - #delete_prefix!: Removes leading prefix; returns +self+ if any changes, +nil+ otherwise. +# - #delete_suffix!: Removes trailing suffix; returns +self+ if any changes, +nil+ otherwise. # - #lstrip!: Removes leading whitespace; returns +self+ if any changes, +nil+ otherwise. # - #rstrip!: Removes trailing whitespace; returns +self+ if any changes, +nil+ otherwise. # - #strip!: Removes leading and trailing whitespace; returns +self+ if any changes, +nil+ otherwise. @@ -441,7 +445,7 @@ # # - #*: Returns the concatenation of multiple copies of +self+. # - #+: Returns the concatenation of +self+ and a given other string. -# - #center: Returns a copy of +self+ centered between pad substrings. +# - #center: Returns a copy of +self+, centered by specified padding. # - #concat: Returns the concatenation of +self+ with given other strings. # - #prepend: Returns the concatenation of a given other string with +self+. # - #ljust: Returns a copy of +self+ of a given length, right-padded with a given other string. diff --git a/doc/string/b.rdoc b/doc/string/b.rdoc index f8ad2910b4..8abd6d9532 100644 --- a/doc/string/b.rdoc +++ b/doc/string/b.rdoc @@ -12,3 +12,5 @@ the underlying bytes are not modified: t = s.b # => "\xE4\x82\x95" t.encoding # => # t.bytes # => [228, 130, 149] + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/string/bytes.rdoc b/doc/string/bytes.rdoc index a9e89f1cd1..f4b071f630 100644 --- a/doc/string/bytes.rdoc +++ b/doc/string/bytes.rdoc @@ -4,3 +4,5 @@ Returns an array of the bytes in +self+: 'тест'.bytes # => [209, 130, 208, 181, 209, 129, 209, 130] 'こんにちは'.bytes # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] + +Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. diff --git a/doc/string/bytesize.rdoc b/doc/string/bytesize.rdoc index b0567ff67b..5166dd7dc6 100644 --- a/doc/string/bytesize.rdoc +++ b/doc/string/bytesize.rdoc @@ -1,11 +1,15 @@ -Returns the count of bytes (not characters) in +self+: +Returns the count of bytes in +self+. - 'foo'.bytesize # => 3 - 'тест'.bytesize # => 8 - 'こんにちは'.bytesize # => 15 +Note that the byte count may be different from the character count (returned by #size): -Contrast with String#length: + s = 'foo' + s.bytesize # => 3 + s.size # => 3 + s = 'тест' + s.bytesize # => 8 + s.size # => 4 + s = 'こんにちは' + s.bytesize # => 15 + s.size # => 5 - 'foo'.length # => 3 - 'тест'.length # => 4 - 'こんにちは'.length # => 5 +Related: see {Querying}[rdoc-ref:String@Querying]. diff --git a/doc/string/byteslice.rdoc b/doc/string/byteslice.rdoc new file mode 100644 index 0000000000..d70441fb2b --- /dev/null +++ b/doc/string/byteslice.rdoc @@ -0,0 +1,54 @@ +Returns a substring of +self+, or +nil+ if the substring cannot be constructed. + +With integer arguments +offset+ and +length+ given, +returns the substring beginning at the given +offset+ +and of the given +length+ (as available): + + s = '0123456789' # => "0123456789" + s.byteslice(2) # => "2" + s.byteslice(200) # => nil + s.byteslice(4, 3) # => "456" + s.byteslice(4, 30) # => "456789" + +Returns +nil+ if +length+ is negative or +offset+ falls outside of +self+: + + s.byteslice(4, -1) # => nil + s.byteslice(40, 2) # => nil + +Counts backwards from the end of +self+ +if +offset+ is negative: + + s = '0123456789' # => "0123456789" + s.byteslice(-4) # => "6" + s.byteslice(-4, 3) # => "678" + +With Range argument +range+ given, returns +byteslice(range.begin, range.size): + + s = '0123456789' # => "0123456789" + s.byteslice(4..6) # => "456" + s.byteslice(-6..-4) # => "456" + s.byteslice(5..2) # => "" # range.size is zero. + s.byteslice(40..42) # => nil + +The starting and ending offsets need not be on character boundaries: + + s = 'こんにちは' + s.byteslice(0, 3) # => "こ" + s.byteslice(1, 3) # => "\x81\x93\xE3" + +The encodings of +self+ and the returned substring +are always the same: + + s.encoding # => # + s.byteslice(0, 3).encoding # => # + s.byteslice(1, 3).encoding # => # + +But, depending on the character boundaries, +the encoding of the returned substring may not be valid: + + s.valid_encoding? # => true + s.byteslice(0, 3).valid_encoding? # => true + s.byteslice(1, 3).valid_encoding? # => false + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/string/bytesplice.rdoc b/doc/string/bytesplice.rdoc new file mode 100644 index 0000000000..5689ef4a2b --- /dev/null +++ b/doc/string/bytesplice.rdoc @@ -0,0 +1,66 @@ +Replaces target bytes in +self+ with source bytes from the given string +str+; +returns +self+. + +In the first form, arguments +offset+ and +length+ determine the target bytes, +and the source bytes are all of the given +str+: + + '0123456789'.bytesplice(0, 3, 'abc') # => "abc3456789" + '0123456789'.bytesplice(3, 3, 'abc') # => "012abc6789" + '0123456789'.bytesplice(0, 50, 'abc') # => "abc" + '0123456789'.bytesplice(50, 3, 'abc') # Raises IndexError. + +The counts of the target bytes and source source bytes may be different: + + '0123456789'.bytesplice(0, 6, 'abc') # => "abc6789" # Shorter source. + '0123456789'.bytesplice(0, 1, 'abc') # => "abc123456789" # Shorter target. + +And either count may be zero (i.e., specifying an empty string): + + '0123456789'.bytesplice(0, 3, '') # => "3456789" # Empty source. + '0123456789'.bytesplice(0, 0, 'abc') # => "abc0123456789" # Empty target. + +In the second form, just as in the first, +arugments +offset+ and +length+ determine the target bytes; +argument +str+ _contains_ the source bytes, +and the additional arguments +str_offset+ and +str_length+ +determine the actual source bytes: + + '0123456789'.bytesplice(0, 3, 'abc', 0, 3) # => "abc3456789" + '0123456789'.bytesplice(0, 3, 'abc', 1, 1) # => "b3456789" # Shorter source. + '0123456789'.bytesplice(0, 1, 'abc', 0, 3) # => "abc123456789" # Shorter target. + '0123456789'.bytesplice(0, 3, 'abc', 1, 0) # => "3456789" # Empty source. + '0123456789'.bytesplice(0, 0, 'abc', 0, 3) # => "abc0123456789" # Empty target. + +In the third form, argument +range+ determines the target bytes +and the source bytes are all of the given +str+: + + '0123456789'.bytesplice(0..2, 'abc') # => "abc3456789" + '0123456789'.bytesplice(3..5, 'abc') # => "012abc6789" + '0123456789'.bytesplice(0..5, 'abc') # => "abc6789" # Shorter source. + '0123456789'.bytesplice(0..0, 'abc') # => "abc123456789" # Shorter target. + '0123456789'.bytesplice(0..2, '') # => "3456789" # Empty source. + '0123456789'.bytesplice(0...0, 'abc') # => "abc0123456789" # Empty target. + +In the fourth form, just as in the third, +arugment +range+ determines the target bytes; +argument +str+ _contains_ the source bytes, +and the additional argument +str_range+ +determines the actual source bytes: + + '0123456789'.bytesplice(0..2, 'abc', 0..2) # => "abc3456789" + '0123456789'.bytesplice(3..5, 'abc', 0..2) # => "012abc6789" + '0123456789'.bytesplice(0..2, 'abc', 0..1) # => "ab3456789" # Shorter source. + '0123456789'.bytesplice(0..1, 'abc', 0..2) # => "abc23456789" # Shorter target. + '0123456789'.bytesplice(0..2, 'abc', 0...0) # => "3456789" # Empty source. + '0123456789'.bytesplice(0...0, 'abc', 0..2) # => "abc0123456789" # Empty target. + +In any of the forms, the beginnings and endings of both source and target +must be on character boundaries. + +In these examples, +self+ has five 3-byte characters, +and so has character boundaries at offsets 0, 3, 6, 9, 12, and 15. + + 'こんにちは'.bytesplice(0, 3, 'abc') # => "abcんにちは" + 'こんにちは'.bytesplice(1, 3, 'abc') # Raises IndexError. + 'こんにちは'.bytesplice(0, 2, 'abc') # Raises IndexError. + diff --git a/doc/string/center.rdoc b/doc/string/center.rdoc index d53d921ad5..343f6ba263 100644 --- a/doc/string/center.rdoc +++ b/doc/string/center.rdoc @@ -2,15 +2,19 @@ Returns a centered copy of +self+. If integer argument +size+ is greater than the size (in characters) of +self+, returns a new string of length +size+ that is a copy of +self+, -centered and padded on both ends with +pad_string+: +centered and padded on one or both ends with +pad_string+: - 'hello'.center(10) # => " hello " - ' hello'.center(10) # => " hello " - 'hello'.center(10, 'ab') # => "abhelloaba" - 'тест'.center(10) # => " тест " - 'こんにちは'.center(10) # => " こんにちは " + 'hello'.center(6) # => "hello " # Padded on one end. + 'hello'.center(10) # => " hello " # Padded on both ends. + 'hello'.center(20, '-|') # => "-|-|-|-hello-|-|-|-|" # Some padding repeated. + 'hello'.center(10, 'abcdefg') # => "abhelloabc" # Some padding not used. + ' hello '.center(13) # => " hello " + 'тест'.center(10) # => " тест " + 'こんにちは'.center(10) # => " こんにちは " # Multi-byte characters. -If +size+ is not greater than the size of +self+, returns a copy of +self+: +If +size+ is less than or equal to the size of +self+, returns an unpadded copy of +self+: - 'hello'.center(5) # => "hello" - 'hello'.center(1) # => "hello" + 'hello'.center(5) # => "hello" + 'hello'.center(-10) # => "hello" + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/string/chars.rdoc b/doc/string/chars.rdoc index d24a1cc3a9..094384271b 100644 --- a/doc/string/chars.rdoc +++ b/doc/string/chars.rdoc @@ -3,3 +3,6 @@ Returns an array of the characters in +self+: 'hello'.chars # => ["h", "e", "l", "l", "o"] 'тест'.chars # => ["т", "е", "с", "т"] 'こんにちは'.chars # => ["こ", "ん", "に", "ち", "は"] + ''.chars # => [] + +Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. diff --git a/doc/string/chomp.rdoc b/doc/string/chomp.rdoc index b6fb9ff38c..6ec7664f6b 100644 --- a/doc/string/chomp.rdoc +++ b/doc/string/chomp.rdoc @@ -25,5 +25,8 @@ removes multiple trailing occurrences of "\n" or "\r\n" When +line_sep+ is neither "\n" nor '', removes a single trailing line separator if there is one: - 'abcd'.chomp('d') # => "abc" - 'abcdd'.chomp('d') # => "abcd" + 'abcd'.chomp('cd') # => "ab" + 'abcdcd'.chomp('cd') # => "abcd" + 'abcd'.chomp('xx') # => "abcd" + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/string/chop.rdoc b/doc/string/chop.rdoc index 8ef82f8a49..2c48e91129 100644 --- a/doc/string/chop.rdoc +++ b/doc/string/chop.rdoc @@ -13,4 +13,7 @@ Otherwise removes the last character if it exists. 'こんにちは'.chop # => "こんにち" ''.chop # => "" -If you only need to remove the newline separator at the end of the string, String#chomp is a better alternative. +If you only need to remove the newline separator at the end of the string, +String#chomp is a better alternative. + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/string/chr.rdoc b/doc/string/chr.rdoc new file mode 100644 index 0000000000..1ada3854cb --- /dev/null +++ b/doc/string/chr.rdoc @@ -0,0 +1,8 @@ +Returns a string containing the first character of +self+: + + 'hello'.chr # => "h" + 'тест'.chr # => "т" + 'こんにちは'.chr # => "こ" + ''.chr # => "" + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/string/codepoints.rdoc b/doc/string/codepoints.rdoc index 0c55d3f4b9..d9586d2e0b 100644 --- a/doc/string/codepoints.rdoc +++ b/doc/string/codepoints.rdoc @@ -4,3 +4,6 @@ each codepoint is the integer value for a character: 'hello'.codepoints # => [104, 101, 108, 108, 111] 'тест'.codepoints # => [1090, 1077, 1089, 1090] 'こんにちは'.codepoints # => [12371, 12435, 12395, 12385, 12399] + ''.codepoints # => [] + +Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. diff --git a/doc/string/concat.rdoc b/doc/string/concat.rdoc new file mode 100644 index 0000000000..2ba0c714af --- /dev/null +++ b/doc/string/concat.rdoc @@ -0,0 +1,12 @@ +Concatenates each object in +objects+ to +self+; returns +self+: + + 'foo'.concat('bar', 'baz') # => "foobarbaz" + +For each given object +object+ that is an integer, +the value is considered a codepoint and converted to a character before concatenation: + + 'foo'.concat(32, 'bar', 32, 'baz') # => "foo bar baz" # Embeds spaces. + 'те'.concat(1089, 1090) # => "тест" + 'こん'.concat(12395, 12385, 12399) # => "こんにちは" + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/string/count.rdoc b/doc/string/count.rdoc new file mode 100644 index 0000000000..092c672d7d --- /dev/null +++ b/doc/string/count.rdoc @@ -0,0 +1,78 @@ +Returns the total number of characters in +self+ that are specified by the given selectors. + +For one 1-character selector, +returns the count of instances of that character: + + s = 'abracadabra' + s.count('a') # => 5 + s.count('b') # => 2 + s.count('x') # => 0 + s.count('') # => 0 + + s = 'тест' + s.count('т') # => 2 + s.count('е') # => 1 + + s = 'よろしくお願いします' + s.count('よ') # => 1 + s.count('し') # => 2 + +For one multi-character selector, +returns the count of instances for all specified characters: + + s = 'abracadabra' + s.count('ab') # => 7 + s.count('abc') # => 8 + s.count('abcd') # => 9 + s.count('abcdr') # => 11 + s.count('abcdrx') # => 11 + +Order and repetition do not matter: + + s.count('ba') == s.count('ab') # => true + s.count('baab') == s.count('ab') # => true + +For multiple selectors, +forms a single selector that is the intersection of characters in all selectors +and returns the count of instances for that selector: + + s = 'abcdefg' + s.count('abcde', 'dcbfg') == s.count('bcd') # => true + s.count('abc', 'def') == s.count('') # => true + +In a character selector, three characters get special treatment: + +- A caret ('^') functions as a _negation_ operator + for the immediately following characters: + + s = 'abracadabra' + s.count('^bc') # => 8 # Count of all except 'b' and 'c'. + +- A hyphen ('-') between two other characters defines a _range_ of characters: + + s = 'abracadabra' + s.count('a-c') # => 8 # Count of all 'a', 'b', and 'c'. + +- A backslash ('\') acts as an escape for a caret, a hyphen, + or another backslash: + + s = 'abracadabra' + s.count('\^bc') # => 3 # Count of '^', 'b', and 'c'. + s.count('a\-c') # => 6 # Count of 'a', '-', and 'c'. + 'foo\bar\baz'.count('\\') # => 2 # Count of '\'. + +These usages may be mixed: + + s = 'abracadabra' + s.count('a-cq-t') # => 10 # Multiple ranges. + s.count('ac-d') # => 7 # Range mixed with plain characters. + s.count('^a-c') # => 3 # Range mixed with negation. + +For multiple selectors, all forms may be used, including negations, ranges, and escapes. + + s = 'abracadabra' + s.count('^abc', '^def') == s.count('^abcdef') # => true + s.count('a-e', 'c-g') == s.count('cde') # => true + s.count('^abc', 'c-g') == s.count('defg') # => true + +Related: see {Querying}[rdoc-ref:String@Querying]. diff --git a/doc/string/delete.rdoc b/doc/string/delete.rdoc new file mode 100644 index 0000000000..e8ff4c0ae4 --- /dev/null +++ b/doc/string/delete.rdoc @@ -0,0 +1,79 @@ +Returns a new string that is a copy of +self+ with certain characters removed; +the removed characters are all instances of those specified by the given string +selectors+. + +For one 1-character selector, +removes all instances of that character: + + s = 'abracadabra' + s.delete('a') # => "brcdbr" + s.delete('b') # => "aracadara" + s.delete('x') # => "abracadabra" + s.delete('') # => "abracadabra" + + s = 'тест' + s.delete('т') # => "ес" + s.delete('е') # => "тст" + + s = 'よろしくお願いします' + s.delete('よ') # => "ろしくお願いします" + s.delete('し') # => "よろくお願います" + +For one multi-character selector, +removes all instances of the specified characters: + + s = 'abracadabra' + s.delete('ab') # => "rcdr" + s.delete('abc') # => "rdr" + s.delete('abcd') # => "rr" + s.delete('abcdr') # => "" + s.delete('abcdrx') # => "" + +Order and repetition do not matter: + + s.delete('ba') == s.delete('ab') # => true + s.delete('baab') == s.delete('ab') # => true + +For multiple selectors, +forms a single selector that is the intersection of characters in all selectors +and removes all instances of characters specified by that selector: + + s = 'abcdefg' + s.delete('abcde', 'dcbfg') == s.delete('bcd') # => true + s.delete('abc', 'def') == s.delete('') # => true + +In a character selector, three characters get special treatment: + +- A caret ('^') functions as a _negation_ operator + for the immediately following characters: + + s = 'abracadabra' + s.delete('^bc') # => "bcb" # Deletes all except 'b' and 'c'. + +- A hyphen ('-') between two other characters defines a _range_ of characters: + + s = 'abracadabra' + s.delete('a-c') # => "rdr" # Deletes all 'a', 'b', and 'c'. + +- A backslash ('\') acts as an escape for a caret, a hyphen, + or another backslash: + + s = 'abracadabra' + s.delete('\^bc') # => "araadara" # Deletes all '^', 'b', and 'c'. + s.delete('a\-c') # => "brdbr" # Deletes all 'a', '-', and 'c'. + 'foo\bar\baz'.delete('\\') # => "foobarbaz" # Deletes all '\'. + +These usages may be mixed: + + s = 'abracadabra' + s.delete('a-cq-t') # => "d" # Multiple ranges. + s.delete('ac-d') # => "brbr" # Range mixed with plain characters. + s.delete('^a-c') # => "abacaaba" # Range mixed with negation. + +For multiple selectors, all forms may be used, including negations, ranges, and escapes. + + s = 'abracadabra' + s.delete('^abc', '^def') == s.delete('^abcdef') # => true + s.delete('a-e', 'c-g') == s.delete('cde') # => true + s.delete('^abc', 'c-g') == s.delete('defg') # => true + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/string/delete_prefix.rdoc b/doc/string/delete_prefix.rdoc index fa9d8abd38..1135f3d19d 100644 --- a/doc/string/delete_prefix.rdoc +++ b/doc/string/delete_prefix.rdoc @@ -1,8 +1,10 @@ -Returns a copy of +self+ with leading substring prefix removed: +Returns a copy of +self+ with leading substring +prefix+ removed: - 'hello'.delete_prefix('hel') # => "lo" - 'hello'.delete_prefix('llo') # => "hello" + 'oof'.delete_prefix('o') # => "of" + 'oof'.delete_prefix('oo') # => "f" + 'oof'.delete_prefix('oof') # => "" + 'oof'.delete_prefix('x') # => "oof" 'тест'.delete_prefix('те') # => "ст" 'こんにちは'.delete_prefix('こん') # => "にちは" -Related: String#delete_prefix!, String#delete_suffix. +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/string/delete_suffix.rdoc b/doc/string/delete_suffix.rdoc index 4862b725cf..2fb70ce012 100644 --- a/doc/string/delete_suffix.rdoc +++ b/doc/string/delete_suffix.rdoc @@ -1,8 +1,11 @@ Returns a copy of +self+ with trailing substring suffix removed: - 'hello'.delete_suffix('llo') # => "he" - 'hello'.delete_suffix('hel') # => "hello" - 'тест'.delete_suffix('ст') # => "те" + 'foo'.delete_suffix('o') # => "fo" + 'foo'.delete_suffix('oo') # => "f" + 'foo'.delete_suffix('foo') # => "" + 'foo'.delete_suffix('f') # => "foo" + 'foo'.delete_suffix('x') # => "foo" + 'тест'.delete_suffix('ст') # => "те" 'こんにちは'.delete_suffix('ちは') # => "こんに" -Related: String#delete_suffix!, String#delete_prefix. +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/string/downcase.rdoc b/doc/string/downcase.rdoc new file mode 100644 index 0000000000..0fb67daaeb --- /dev/null +++ b/doc/string/downcase.rdoc @@ -0,0 +1,12 @@ +Returns a new string containing the downcased characters in +self+: + + 'Hello, World!'.downcase # => "hello, world!" + 'ТЕСТ'.downcase # => "тест" + 'よろしくお願いします'.downcase # => "よろしくお願いします" + +Some characters do not have upcased and downcased versions. + +The casing may be affected by the given +mapping+; +see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/doc/windows.md b/doc/windows.md index cc0fd3f138..13c797875e 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -17,6 +17,7 @@ editor. Ruby core development can be done either in Windows `cmd` like: ```batch +ridk install ridk enable ucrt64 pacman -S --needed %MINGW_PACKAGE_PREFIX%-openssl %MINGW_PACKAGE_PREFIX%-libyaml %MINGW_PACKAGE_PREFIX%-libffi @@ -37,6 +38,7 @@ make or in MSYS2 `bash` like: ```bash +ridk install ridk enable ucrt64 bash @@ -76,14 +78,37 @@ sh ../../ruby/configure -C --disable-install-doc --with-opt-dir=C:\Users\usernam x64. The minimum requirement is here: - * VC++/MSVC on VS 2017/2019 version build tools. - * Visual Studio 2022 17.13.x is broken. see https://bugs.ruby-lang.org/issues/21167 + * VC++/MSVC on VS 2017/2019/2022 version build tools. * Windows 10/11 SDK - * 10.0.26100 is broken, 10.0.22621 is recommended. see https://bugs.ruby-lang.org/issues/21255 -3. Please set environment variable `INCLUDE`, `LIB`, `PATH` - to run required commands properly from the command line. - These are set properly by `vcvarall*.bat` usually. + You can install Visual Studio Build Tools with `winget`. + `win32\install-buildtools.cmd` is a batch file to install the + minimum requirements excluding the IDE etc. + +3. Please set environment variable `INCLUDE`, `LIB`, `PATH` to run + required commands properly from the command line. These are set + properly by `vsdevcmd.bat` or `vcvarall*.bat` usually. You can run + the following command to set them in your command line. + + To native build: + + ``` + cmd /k win32\vssetup.cmd + ``` + + To cross build arm64 binary: + + ``` + cmd /k win32\vssetup.cmd -arch arm64 + ``` + + To cross build x64 binary: + + ``` + cmd /k win32\vssetup.cmd -arch x64 + ``` + + See `win32\vssetup.cmd -help` for other command line options. **Note** building ruby requires following commands. diff --git a/doc/zjit.md b/doc/zjit.md index 4cb8056685..90f890bfa0 100644 --- a/doc/zjit.md +++ b/doc/zjit.md @@ -28,12 +28,12 @@ in a way that can be easily shared with other team members. Make sure you have a `--enable-zjit=dev` build, and run `brew install cargo-nextest` first. -### make zjit-test-all +### make zjit-check This command runs all ZJIT tests: `make zjit-test` and `test/ruby/test_zjit.rb`. ``` -make zjit-test-all +make zjit-check ``` ### make zjit-test @@ -78,6 +78,16 @@ use `make`. +### make zjit-test-all + +``` +make zjit-test-all +``` + +This command runs all Ruby tests under `/test/ruby/` with ZJIT enabled. + +Certain tests are excluded under `/test/.excludes-zjit`. + ### test/ruby/test\_zjit.rb This command runs Ruby execution tests. @@ -91,3 +101,33 @@ You can also run a single test case by matching the method name: ``` make test-all TESTS="test/ruby/test_zjit.rb -n TestZJIT#test_putobject" ``` + +## ZJIT Glossary + +This glossary contains terms that are helpful for understanding ZJIT. + +Please note that some terms may appear in CRuby internals too but with different meanings. + +| Term | Definition | +| --- | -----------| +| HIR | High-level Intermediate Representation. High-level (Ruby semantics) graph representation in static single-assignment (SSA) form | +| LIR | Low-level Intermediate Representation. Low-level IR used in the backend for assembly generation | +| SSA | Static Single Assignment. A form where each variable is assigned exactly once | +| `opnd` | Operand. An operand to an IR instruction (can be register, memory, immediate, etc.) | +| `dst` | Destination. The output operand of an instruction where the result is stored | +| VReg | Virtual Register. A virtual register that gets lowered to physical register or memory | +| `insn_id` | Instruction ID. An index of an instruction in a function | +| `block_id` | The index of a basic block, which effectively acts like a pointer | +| `branch` | Control flow edge between basic blocks in the compiled code | +| `cb` | Code Block. Memory region for generated machine code | +| `entry` | The starting address of compiled code for an ISEQ | +| Patch Point | Location in generated code that can be modified later in case assumptions get invalidated | +| Frame State | Captured state of the Ruby stack frame at a specific point for deoptimization | +| Guard | A run-time check that ensures assumptions are still valid | +| `invariant` | An assumption that JIT code relies on, requiring invalidation if broken | +| Deopt | Deoptimization. Process of falling back from JIT code to interpreter | +| Side Exit | Exit from JIT code back to interpreter | +| Type Lattice | Hierarchy of types used for type inference and optimization | +| Constant Folding | Optimization that evaluates constant expressions at compile time | +| RSP | x86-64 stack pointer register used for native stack operations | +| Register Spilling | Process of moving register values to memory when running out of physical registers | diff --git a/enc/depend b/enc/depend index dcf65a129b..a458b63887 100644 --- a/enc/depend +++ b/enc/depend @@ -317,6 +317,7 @@ enc/ascii.$(OBJEXT): internal/intern/re.h enc/ascii.$(OBJEXT): internal/intern/ruby.h enc/ascii.$(OBJEXT): internal/intern/select.h enc/ascii.$(OBJEXT): internal/intern/select/largesize.h +enc/ascii.$(OBJEXT): internal/intern/set.h enc/ascii.$(OBJEXT): internal/intern/signal.h enc/ascii.$(OBJEXT): internal/intern/sprintf.h enc/ascii.$(OBJEXT): internal/intern/string.h @@ -479,6 +480,7 @@ enc/big5.$(OBJEXT): internal/intern/re.h enc/big5.$(OBJEXT): internal/intern/ruby.h enc/big5.$(OBJEXT): internal/intern/select.h enc/big5.$(OBJEXT): internal/intern/select/largesize.h +enc/big5.$(OBJEXT): internal/intern/set.h enc/big5.$(OBJEXT): internal/intern/signal.h enc/big5.$(OBJEXT): internal/intern/sprintf.h enc/big5.$(OBJEXT): internal/intern/string.h @@ -651,6 +653,7 @@ enc/cesu_8.$(OBJEXT): internal/intern/re.h enc/cesu_8.$(OBJEXT): internal/intern/ruby.h enc/cesu_8.$(OBJEXT): internal/intern/select.h enc/cesu_8.$(OBJEXT): internal/intern/select/largesize.h +enc/cesu_8.$(OBJEXT): internal/intern/set.h enc/cesu_8.$(OBJEXT): internal/intern/signal.h enc/cesu_8.$(OBJEXT): internal/intern/sprintf.h enc/cesu_8.$(OBJEXT): internal/intern/string.h @@ -813,6 +816,7 @@ enc/cp949.$(OBJEXT): internal/intern/re.h enc/cp949.$(OBJEXT): internal/intern/ruby.h enc/cp949.$(OBJEXT): internal/intern/select.h enc/cp949.$(OBJEXT): internal/intern/select/largesize.h +enc/cp949.$(OBJEXT): internal/intern/set.h enc/cp949.$(OBJEXT): internal/intern/signal.h enc/cp949.$(OBJEXT): internal/intern/sprintf.h enc/cp949.$(OBJEXT): internal/intern/string.h @@ -974,6 +978,7 @@ enc/emacs_mule.$(OBJEXT): internal/intern/re.h enc/emacs_mule.$(OBJEXT): internal/intern/ruby.h enc/emacs_mule.$(OBJEXT): internal/intern/select.h enc/emacs_mule.$(OBJEXT): internal/intern/select/largesize.h +enc/emacs_mule.$(OBJEXT): internal/intern/set.h enc/emacs_mule.$(OBJEXT): internal/intern/signal.h enc/emacs_mule.$(OBJEXT): internal/intern/sprintf.h enc/emacs_mule.$(OBJEXT): internal/intern/string.h @@ -1145,6 +1150,7 @@ enc/encdb.$(OBJEXT): internal/intern/re.h enc/encdb.$(OBJEXT): internal/intern/ruby.h enc/encdb.$(OBJEXT): internal/intern/select.h enc/encdb.$(OBJEXT): internal/intern/select/largesize.h +enc/encdb.$(OBJEXT): internal/intern/set.h enc/encdb.$(OBJEXT): internal/intern/signal.h enc/encdb.$(OBJEXT): internal/intern/sprintf.h enc/encdb.$(OBJEXT): internal/intern/string.h @@ -1309,6 +1315,7 @@ enc/euc_jp.$(OBJEXT): internal/intern/re.h enc/euc_jp.$(OBJEXT): internal/intern/ruby.h enc/euc_jp.$(OBJEXT): internal/intern/select.h enc/euc_jp.$(OBJEXT): internal/intern/select/largesize.h +enc/euc_jp.$(OBJEXT): internal/intern/set.h enc/euc_jp.$(OBJEXT): internal/intern/signal.h enc/euc_jp.$(OBJEXT): internal/intern/sprintf.h enc/euc_jp.$(OBJEXT): internal/intern/string.h @@ -1470,6 +1477,7 @@ enc/euc_kr.$(OBJEXT): internal/intern/re.h enc/euc_kr.$(OBJEXT): internal/intern/ruby.h enc/euc_kr.$(OBJEXT): internal/intern/select.h enc/euc_kr.$(OBJEXT): internal/intern/select/largesize.h +enc/euc_kr.$(OBJEXT): internal/intern/set.h enc/euc_kr.$(OBJEXT): internal/intern/signal.h enc/euc_kr.$(OBJEXT): internal/intern/sprintf.h enc/euc_kr.$(OBJEXT): internal/intern/string.h @@ -1631,6 +1639,7 @@ enc/euc_tw.$(OBJEXT): internal/intern/re.h enc/euc_tw.$(OBJEXT): internal/intern/ruby.h enc/euc_tw.$(OBJEXT): internal/intern/select.h enc/euc_tw.$(OBJEXT): internal/intern/select/largesize.h +enc/euc_tw.$(OBJEXT): internal/intern/set.h enc/euc_tw.$(OBJEXT): internal/intern/signal.h enc/euc_tw.$(OBJEXT): internal/intern/sprintf.h enc/euc_tw.$(OBJEXT): internal/intern/string.h @@ -1792,6 +1801,7 @@ enc/gb18030.$(OBJEXT): internal/intern/re.h enc/gb18030.$(OBJEXT): internal/intern/ruby.h enc/gb18030.$(OBJEXT): internal/intern/select.h enc/gb18030.$(OBJEXT): internal/intern/select/largesize.h +enc/gb18030.$(OBJEXT): internal/intern/set.h enc/gb18030.$(OBJEXT): internal/intern/signal.h enc/gb18030.$(OBJEXT): internal/intern/sprintf.h enc/gb18030.$(OBJEXT): internal/intern/string.h @@ -1953,6 +1963,7 @@ enc/gb2312.$(OBJEXT): internal/intern/re.h enc/gb2312.$(OBJEXT): internal/intern/ruby.h enc/gb2312.$(OBJEXT): internal/intern/select.h enc/gb2312.$(OBJEXT): internal/intern/select/largesize.h +enc/gb2312.$(OBJEXT): internal/intern/set.h enc/gb2312.$(OBJEXT): internal/intern/signal.h enc/gb2312.$(OBJEXT): internal/intern/sprintf.h enc/gb2312.$(OBJEXT): internal/intern/string.h @@ -2114,6 +2125,7 @@ enc/gbk.$(OBJEXT): internal/intern/re.h enc/gbk.$(OBJEXT): internal/intern/ruby.h enc/gbk.$(OBJEXT): internal/intern/select.h enc/gbk.$(OBJEXT): internal/intern/select/largesize.h +enc/gbk.$(OBJEXT): internal/intern/set.h enc/gbk.$(OBJEXT): internal/intern/signal.h enc/gbk.$(OBJEXT): internal/intern/sprintf.h enc/gbk.$(OBJEXT): internal/intern/string.h @@ -2276,6 +2288,7 @@ enc/iso_8859_1.$(OBJEXT): internal/intern/re.h enc/iso_8859_1.$(OBJEXT): internal/intern/ruby.h enc/iso_8859_1.$(OBJEXT): internal/intern/select.h enc/iso_8859_1.$(OBJEXT): internal/intern/select/largesize.h +enc/iso_8859_1.$(OBJEXT): internal/intern/set.h enc/iso_8859_1.$(OBJEXT): internal/intern/signal.h enc/iso_8859_1.$(OBJEXT): internal/intern/sprintf.h enc/iso_8859_1.$(OBJEXT): internal/intern/string.h @@ -2438,6 +2451,7 @@ enc/iso_8859_10.$(OBJEXT): internal/intern/re.h enc/iso_8859_10.$(OBJEXT): internal/intern/ruby.h enc/iso_8859_10.$(OBJEXT): internal/intern/select.h enc/iso_8859_10.$(OBJEXT): internal/intern/select/largesize.h +enc/iso_8859_10.$(OBJEXT): internal/intern/set.h enc/iso_8859_10.$(OBJEXT): internal/intern/signal.h enc/iso_8859_10.$(OBJEXT): internal/intern/sprintf.h enc/iso_8859_10.$(OBJEXT): internal/intern/string.h @@ -2599,6 +2613,7 @@ enc/iso_8859_11.$(OBJEXT): internal/intern/re.h enc/iso_8859_11.$(OBJEXT): internal/intern/ruby.h enc/iso_8859_11.$(OBJEXT): internal/intern/select.h enc/iso_8859_11.$(OBJEXT): internal/intern/select/largesize.h +enc/iso_8859_11.$(OBJEXT): internal/intern/set.h enc/iso_8859_11.$(OBJEXT): internal/intern/signal.h enc/iso_8859_11.$(OBJEXT): internal/intern/sprintf.h enc/iso_8859_11.$(OBJEXT): internal/intern/string.h @@ -2761,6 +2776,7 @@ enc/iso_8859_13.$(OBJEXT): internal/intern/re.h enc/iso_8859_13.$(OBJEXT): internal/intern/ruby.h enc/iso_8859_13.$(OBJEXT): internal/intern/select.h enc/iso_8859_13.$(OBJEXT): internal/intern/select/largesize.h +enc/iso_8859_13.$(OBJEXT): internal/intern/set.h enc/iso_8859_13.$(OBJEXT): internal/intern/signal.h enc/iso_8859_13.$(OBJEXT): internal/intern/sprintf.h enc/iso_8859_13.$(OBJEXT): internal/intern/string.h @@ -2923,6 +2939,7 @@ enc/iso_8859_14.$(OBJEXT): internal/intern/re.h enc/iso_8859_14.$(OBJEXT): internal/intern/ruby.h enc/iso_8859_14.$(OBJEXT): internal/intern/select.h enc/iso_8859_14.$(OBJEXT): internal/intern/select/largesize.h +enc/iso_8859_14.$(OBJEXT): internal/intern/set.h enc/iso_8859_14.$(OBJEXT): internal/intern/signal.h enc/iso_8859_14.$(OBJEXT): internal/intern/sprintf.h enc/iso_8859_14.$(OBJEXT): internal/intern/string.h @@ -3085,6 +3102,7 @@ enc/iso_8859_15.$(OBJEXT): internal/intern/re.h enc/iso_8859_15.$(OBJEXT): internal/intern/ruby.h enc/iso_8859_15.$(OBJEXT): internal/intern/select.h enc/iso_8859_15.$(OBJEXT): internal/intern/select/largesize.h +enc/iso_8859_15.$(OBJEXT): internal/intern/set.h enc/iso_8859_15.$(OBJEXT): internal/intern/signal.h enc/iso_8859_15.$(OBJEXT): internal/intern/sprintf.h enc/iso_8859_15.$(OBJEXT): internal/intern/string.h @@ -3247,6 +3265,7 @@ enc/iso_8859_16.$(OBJEXT): internal/intern/re.h enc/iso_8859_16.$(OBJEXT): internal/intern/ruby.h enc/iso_8859_16.$(OBJEXT): internal/intern/select.h enc/iso_8859_16.$(OBJEXT): internal/intern/select/largesize.h +enc/iso_8859_16.$(OBJEXT): internal/intern/set.h enc/iso_8859_16.$(OBJEXT): internal/intern/signal.h enc/iso_8859_16.$(OBJEXT): internal/intern/sprintf.h enc/iso_8859_16.$(OBJEXT): internal/intern/string.h @@ -3409,6 +3428,7 @@ enc/iso_8859_2.$(OBJEXT): internal/intern/re.h enc/iso_8859_2.$(OBJEXT): internal/intern/ruby.h enc/iso_8859_2.$(OBJEXT): internal/intern/select.h enc/iso_8859_2.$(OBJEXT): internal/intern/select/largesize.h +enc/iso_8859_2.$(OBJEXT): internal/intern/set.h enc/iso_8859_2.$(OBJEXT): internal/intern/signal.h enc/iso_8859_2.$(OBJEXT): internal/intern/sprintf.h enc/iso_8859_2.$(OBJEXT): internal/intern/string.h @@ -3571,6 +3591,7 @@ enc/iso_8859_3.$(OBJEXT): internal/intern/re.h enc/iso_8859_3.$(OBJEXT): internal/intern/ruby.h enc/iso_8859_3.$(OBJEXT): internal/intern/select.h enc/iso_8859_3.$(OBJEXT): internal/intern/select/largesize.h +enc/iso_8859_3.$(OBJEXT): internal/intern/set.h enc/iso_8859_3.$(OBJEXT): internal/intern/signal.h enc/iso_8859_3.$(OBJEXT): internal/intern/sprintf.h enc/iso_8859_3.$(OBJEXT): internal/intern/string.h @@ -3733,6 +3754,7 @@ enc/iso_8859_4.$(OBJEXT): internal/intern/re.h enc/iso_8859_4.$(OBJEXT): internal/intern/ruby.h enc/iso_8859_4.$(OBJEXT): internal/intern/select.h enc/iso_8859_4.$(OBJEXT): internal/intern/select/largesize.h +enc/iso_8859_4.$(OBJEXT): internal/intern/set.h enc/iso_8859_4.$(OBJEXT): internal/intern/signal.h enc/iso_8859_4.$(OBJEXT): internal/intern/sprintf.h enc/iso_8859_4.$(OBJEXT): internal/intern/string.h @@ -3894,6 +3916,7 @@ enc/iso_8859_5.$(OBJEXT): internal/intern/re.h enc/iso_8859_5.$(OBJEXT): internal/intern/ruby.h enc/iso_8859_5.$(OBJEXT): internal/intern/select.h enc/iso_8859_5.$(OBJEXT): internal/intern/select/largesize.h +enc/iso_8859_5.$(OBJEXT): internal/intern/set.h enc/iso_8859_5.$(OBJEXT): internal/intern/signal.h enc/iso_8859_5.$(OBJEXT): internal/intern/sprintf.h enc/iso_8859_5.$(OBJEXT): internal/intern/string.h @@ -4055,6 +4078,7 @@ enc/iso_8859_6.$(OBJEXT): internal/intern/re.h enc/iso_8859_6.$(OBJEXT): internal/intern/ruby.h enc/iso_8859_6.$(OBJEXT): internal/intern/select.h enc/iso_8859_6.$(OBJEXT): internal/intern/select/largesize.h +enc/iso_8859_6.$(OBJEXT): internal/intern/set.h enc/iso_8859_6.$(OBJEXT): internal/intern/signal.h enc/iso_8859_6.$(OBJEXT): internal/intern/sprintf.h enc/iso_8859_6.$(OBJEXT): internal/intern/string.h @@ -4216,6 +4240,7 @@ enc/iso_8859_7.$(OBJEXT): internal/intern/re.h enc/iso_8859_7.$(OBJEXT): internal/intern/ruby.h enc/iso_8859_7.$(OBJEXT): internal/intern/select.h enc/iso_8859_7.$(OBJEXT): internal/intern/select/largesize.h +enc/iso_8859_7.$(OBJEXT): internal/intern/set.h enc/iso_8859_7.$(OBJEXT): internal/intern/signal.h enc/iso_8859_7.$(OBJEXT): internal/intern/sprintf.h enc/iso_8859_7.$(OBJEXT): internal/intern/string.h @@ -4377,6 +4402,7 @@ enc/iso_8859_8.$(OBJEXT): internal/intern/re.h enc/iso_8859_8.$(OBJEXT): internal/intern/ruby.h enc/iso_8859_8.$(OBJEXT): internal/intern/select.h enc/iso_8859_8.$(OBJEXT): internal/intern/select/largesize.h +enc/iso_8859_8.$(OBJEXT): internal/intern/set.h enc/iso_8859_8.$(OBJEXT): internal/intern/signal.h enc/iso_8859_8.$(OBJEXT): internal/intern/sprintf.h enc/iso_8859_8.$(OBJEXT): internal/intern/string.h @@ -4539,6 +4565,7 @@ enc/iso_8859_9.$(OBJEXT): internal/intern/re.h enc/iso_8859_9.$(OBJEXT): internal/intern/ruby.h enc/iso_8859_9.$(OBJEXT): internal/intern/select.h enc/iso_8859_9.$(OBJEXT): internal/intern/select/largesize.h +enc/iso_8859_9.$(OBJEXT): internal/intern/set.h enc/iso_8859_9.$(OBJEXT): internal/intern/signal.h enc/iso_8859_9.$(OBJEXT): internal/intern/sprintf.h enc/iso_8859_9.$(OBJEXT): internal/intern/string.h @@ -4700,6 +4727,7 @@ enc/koi8_r.$(OBJEXT): internal/intern/re.h enc/koi8_r.$(OBJEXT): internal/intern/ruby.h enc/koi8_r.$(OBJEXT): internal/intern/select.h enc/koi8_r.$(OBJEXT): internal/intern/select/largesize.h +enc/koi8_r.$(OBJEXT): internal/intern/set.h enc/koi8_r.$(OBJEXT): internal/intern/signal.h enc/koi8_r.$(OBJEXT): internal/intern/sprintf.h enc/koi8_r.$(OBJEXT): internal/intern/string.h @@ -4861,6 +4889,7 @@ enc/koi8_u.$(OBJEXT): internal/intern/re.h enc/koi8_u.$(OBJEXT): internal/intern/ruby.h enc/koi8_u.$(OBJEXT): internal/intern/select.h enc/koi8_u.$(OBJEXT): internal/intern/select/largesize.h +enc/koi8_u.$(OBJEXT): internal/intern/set.h enc/koi8_u.$(OBJEXT): internal/intern/signal.h enc/koi8_u.$(OBJEXT): internal/intern/sprintf.h enc/koi8_u.$(OBJEXT): internal/intern/string.h @@ -5025,6 +5054,7 @@ enc/shift_jis.$(OBJEXT): internal/intern/re.h enc/shift_jis.$(OBJEXT): internal/intern/ruby.h enc/shift_jis.$(OBJEXT): internal/intern/select.h enc/shift_jis.$(OBJEXT): internal/intern/select/largesize.h +enc/shift_jis.$(OBJEXT): internal/intern/set.h enc/shift_jis.$(OBJEXT): internal/intern/signal.h enc/shift_jis.$(OBJEXT): internal/intern/sprintf.h enc/shift_jis.$(OBJEXT): internal/intern/string.h @@ -5185,6 +5215,7 @@ enc/trans/big5.$(OBJEXT): internal/intern/re.h enc/trans/big5.$(OBJEXT): internal/intern/ruby.h enc/trans/big5.$(OBJEXT): internal/intern/select.h enc/trans/big5.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/big5.$(OBJEXT): internal/intern/set.h enc/trans/big5.$(OBJEXT): internal/intern/signal.h enc/trans/big5.$(OBJEXT): internal/intern/sprintf.h enc/trans/big5.$(OBJEXT): internal/intern/string.h @@ -5344,6 +5375,7 @@ enc/trans/cesu_8.$(OBJEXT): internal/intern/re.h enc/trans/cesu_8.$(OBJEXT): internal/intern/ruby.h enc/trans/cesu_8.$(OBJEXT): internal/intern/select.h enc/trans/cesu_8.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/cesu_8.$(OBJEXT): internal/intern/set.h enc/trans/cesu_8.$(OBJEXT): internal/intern/signal.h enc/trans/cesu_8.$(OBJEXT): internal/intern/sprintf.h enc/trans/cesu_8.$(OBJEXT): internal/intern/string.h @@ -5503,6 +5535,7 @@ enc/trans/chinese.$(OBJEXT): internal/intern/re.h enc/trans/chinese.$(OBJEXT): internal/intern/ruby.h enc/trans/chinese.$(OBJEXT): internal/intern/select.h enc/trans/chinese.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/chinese.$(OBJEXT): internal/intern/set.h enc/trans/chinese.$(OBJEXT): internal/intern/signal.h enc/trans/chinese.$(OBJEXT): internal/intern/sprintf.h enc/trans/chinese.$(OBJEXT): internal/intern/string.h @@ -5662,6 +5695,7 @@ enc/trans/ebcdic.$(OBJEXT): internal/intern/re.h enc/trans/ebcdic.$(OBJEXT): internal/intern/ruby.h enc/trans/ebcdic.$(OBJEXT): internal/intern/select.h enc/trans/ebcdic.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/ebcdic.$(OBJEXT): internal/intern/set.h enc/trans/ebcdic.$(OBJEXT): internal/intern/signal.h enc/trans/ebcdic.$(OBJEXT): internal/intern/sprintf.h enc/trans/ebcdic.$(OBJEXT): internal/intern/string.h @@ -5821,6 +5855,7 @@ enc/trans/emoji.$(OBJEXT): internal/intern/re.h enc/trans/emoji.$(OBJEXT): internal/intern/ruby.h enc/trans/emoji.$(OBJEXT): internal/intern/select.h enc/trans/emoji.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/emoji.$(OBJEXT): internal/intern/set.h enc/trans/emoji.$(OBJEXT): internal/intern/signal.h enc/trans/emoji.$(OBJEXT): internal/intern/sprintf.h enc/trans/emoji.$(OBJEXT): internal/intern/string.h @@ -5980,6 +6015,7 @@ enc/trans/emoji_iso2022_kddi.$(OBJEXT): internal/intern/re.h enc/trans/emoji_iso2022_kddi.$(OBJEXT): internal/intern/ruby.h enc/trans/emoji_iso2022_kddi.$(OBJEXT): internal/intern/select.h enc/trans/emoji_iso2022_kddi.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/emoji_iso2022_kddi.$(OBJEXT): internal/intern/set.h enc/trans/emoji_iso2022_kddi.$(OBJEXT): internal/intern/signal.h enc/trans/emoji_iso2022_kddi.$(OBJEXT): internal/intern/sprintf.h enc/trans/emoji_iso2022_kddi.$(OBJEXT): internal/intern/string.h @@ -6139,6 +6175,7 @@ enc/trans/emoji_sjis_docomo.$(OBJEXT): internal/intern/re.h enc/trans/emoji_sjis_docomo.$(OBJEXT): internal/intern/ruby.h enc/trans/emoji_sjis_docomo.$(OBJEXT): internal/intern/select.h enc/trans/emoji_sjis_docomo.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/emoji_sjis_docomo.$(OBJEXT): internal/intern/set.h enc/trans/emoji_sjis_docomo.$(OBJEXT): internal/intern/signal.h enc/trans/emoji_sjis_docomo.$(OBJEXT): internal/intern/sprintf.h enc/trans/emoji_sjis_docomo.$(OBJEXT): internal/intern/string.h @@ -6298,6 +6335,7 @@ enc/trans/emoji_sjis_kddi.$(OBJEXT): internal/intern/re.h enc/trans/emoji_sjis_kddi.$(OBJEXT): internal/intern/ruby.h enc/trans/emoji_sjis_kddi.$(OBJEXT): internal/intern/select.h enc/trans/emoji_sjis_kddi.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/emoji_sjis_kddi.$(OBJEXT): internal/intern/set.h enc/trans/emoji_sjis_kddi.$(OBJEXT): internal/intern/signal.h enc/trans/emoji_sjis_kddi.$(OBJEXT): internal/intern/sprintf.h enc/trans/emoji_sjis_kddi.$(OBJEXT): internal/intern/string.h @@ -6457,6 +6495,7 @@ enc/trans/emoji_sjis_softbank.$(OBJEXT): internal/intern/re.h enc/trans/emoji_sjis_softbank.$(OBJEXT): internal/intern/ruby.h enc/trans/emoji_sjis_softbank.$(OBJEXT): internal/intern/select.h enc/trans/emoji_sjis_softbank.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/emoji_sjis_softbank.$(OBJEXT): internal/intern/set.h enc/trans/emoji_sjis_softbank.$(OBJEXT): internal/intern/signal.h enc/trans/emoji_sjis_softbank.$(OBJEXT): internal/intern/sprintf.h enc/trans/emoji_sjis_softbank.$(OBJEXT): internal/intern/string.h @@ -6616,6 +6655,7 @@ enc/trans/escape.$(OBJEXT): internal/intern/re.h enc/trans/escape.$(OBJEXT): internal/intern/ruby.h enc/trans/escape.$(OBJEXT): internal/intern/select.h enc/trans/escape.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/escape.$(OBJEXT): internal/intern/set.h enc/trans/escape.$(OBJEXT): internal/intern/signal.h enc/trans/escape.$(OBJEXT): internal/intern/sprintf.h enc/trans/escape.$(OBJEXT): internal/intern/string.h @@ -6775,6 +6815,7 @@ enc/trans/gb18030.$(OBJEXT): internal/intern/re.h enc/trans/gb18030.$(OBJEXT): internal/intern/ruby.h enc/trans/gb18030.$(OBJEXT): internal/intern/select.h enc/trans/gb18030.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/gb18030.$(OBJEXT): internal/intern/set.h enc/trans/gb18030.$(OBJEXT): internal/intern/signal.h enc/trans/gb18030.$(OBJEXT): internal/intern/sprintf.h enc/trans/gb18030.$(OBJEXT): internal/intern/string.h @@ -6934,6 +6975,7 @@ enc/trans/gbk.$(OBJEXT): internal/intern/re.h enc/trans/gbk.$(OBJEXT): internal/intern/ruby.h enc/trans/gbk.$(OBJEXT): internal/intern/select.h enc/trans/gbk.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/gbk.$(OBJEXT): internal/intern/set.h enc/trans/gbk.$(OBJEXT): internal/intern/signal.h enc/trans/gbk.$(OBJEXT): internal/intern/sprintf.h enc/trans/gbk.$(OBJEXT): internal/intern/string.h @@ -7094,6 +7136,7 @@ enc/trans/iso2022.$(OBJEXT): internal/intern/re.h enc/trans/iso2022.$(OBJEXT): internal/intern/ruby.h enc/trans/iso2022.$(OBJEXT): internal/intern/select.h enc/trans/iso2022.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/iso2022.$(OBJEXT): internal/intern/set.h enc/trans/iso2022.$(OBJEXT): internal/intern/signal.h enc/trans/iso2022.$(OBJEXT): internal/intern/sprintf.h enc/trans/iso2022.$(OBJEXT): internal/intern/string.h @@ -7253,6 +7296,7 @@ enc/trans/japanese.$(OBJEXT): internal/intern/re.h enc/trans/japanese.$(OBJEXT): internal/intern/ruby.h enc/trans/japanese.$(OBJEXT): internal/intern/select.h enc/trans/japanese.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/japanese.$(OBJEXT): internal/intern/set.h enc/trans/japanese.$(OBJEXT): internal/intern/signal.h enc/trans/japanese.$(OBJEXT): internal/intern/sprintf.h enc/trans/japanese.$(OBJEXT): internal/intern/string.h @@ -7412,6 +7456,7 @@ enc/trans/japanese_euc.$(OBJEXT): internal/intern/re.h enc/trans/japanese_euc.$(OBJEXT): internal/intern/ruby.h enc/trans/japanese_euc.$(OBJEXT): internal/intern/select.h enc/trans/japanese_euc.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/japanese_euc.$(OBJEXT): internal/intern/set.h enc/trans/japanese_euc.$(OBJEXT): internal/intern/signal.h enc/trans/japanese_euc.$(OBJEXT): internal/intern/sprintf.h enc/trans/japanese_euc.$(OBJEXT): internal/intern/string.h @@ -7571,6 +7616,7 @@ enc/trans/japanese_sjis.$(OBJEXT): internal/intern/re.h enc/trans/japanese_sjis.$(OBJEXT): internal/intern/ruby.h enc/trans/japanese_sjis.$(OBJEXT): internal/intern/select.h enc/trans/japanese_sjis.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/japanese_sjis.$(OBJEXT): internal/intern/set.h enc/trans/japanese_sjis.$(OBJEXT): internal/intern/signal.h enc/trans/japanese_sjis.$(OBJEXT): internal/intern/sprintf.h enc/trans/japanese_sjis.$(OBJEXT): internal/intern/string.h @@ -7730,6 +7776,7 @@ enc/trans/korean.$(OBJEXT): internal/intern/re.h enc/trans/korean.$(OBJEXT): internal/intern/ruby.h enc/trans/korean.$(OBJEXT): internal/intern/select.h enc/trans/korean.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/korean.$(OBJEXT): internal/intern/set.h enc/trans/korean.$(OBJEXT): internal/intern/signal.h enc/trans/korean.$(OBJEXT): internal/intern/sprintf.h enc/trans/korean.$(OBJEXT): internal/intern/string.h @@ -7888,6 +7935,7 @@ enc/trans/newline.$(OBJEXT): internal/intern/re.h enc/trans/newline.$(OBJEXT): internal/intern/ruby.h enc/trans/newline.$(OBJEXT): internal/intern/select.h enc/trans/newline.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/newline.$(OBJEXT): internal/intern/set.h enc/trans/newline.$(OBJEXT): internal/intern/signal.h enc/trans/newline.$(OBJEXT): internal/intern/sprintf.h enc/trans/newline.$(OBJEXT): internal/intern/string.h @@ -8047,6 +8095,7 @@ enc/trans/single_byte.$(OBJEXT): internal/intern/re.h enc/trans/single_byte.$(OBJEXT): internal/intern/ruby.h enc/trans/single_byte.$(OBJEXT): internal/intern/select.h enc/trans/single_byte.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/single_byte.$(OBJEXT): internal/intern/set.h enc/trans/single_byte.$(OBJEXT): internal/intern/signal.h enc/trans/single_byte.$(OBJEXT): internal/intern/sprintf.h enc/trans/single_byte.$(OBJEXT): internal/intern/string.h @@ -8206,6 +8255,7 @@ enc/trans/transdb.$(OBJEXT): internal/intern/re.h enc/trans/transdb.$(OBJEXT): internal/intern/ruby.h enc/trans/transdb.$(OBJEXT): internal/intern/select.h enc/trans/transdb.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/transdb.$(OBJEXT): internal/intern/set.h enc/trans/transdb.$(OBJEXT): internal/intern/signal.h enc/trans/transdb.$(OBJEXT): internal/intern/sprintf.h enc/trans/transdb.$(OBJEXT): internal/intern/string.h @@ -8366,6 +8416,7 @@ enc/trans/utf8_mac.$(OBJEXT): internal/intern/re.h enc/trans/utf8_mac.$(OBJEXT): internal/intern/ruby.h enc/trans/utf8_mac.$(OBJEXT): internal/intern/select.h enc/trans/utf8_mac.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/utf8_mac.$(OBJEXT): internal/intern/set.h enc/trans/utf8_mac.$(OBJEXT): internal/intern/signal.h enc/trans/utf8_mac.$(OBJEXT): internal/intern/sprintf.h enc/trans/utf8_mac.$(OBJEXT): internal/intern/string.h @@ -8525,6 +8576,7 @@ enc/trans/utf_16_32.$(OBJEXT): internal/intern/re.h enc/trans/utf_16_32.$(OBJEXT): internal/intern/ruby.h enc/trans/utf_16_32.$(OBJEXT): internal/intern/select.h enc/trans/utf_16_32.$(OBJEXT): internal/intern/select/largesize.h +enc/trans/utf_16_32.$(OBJEXT): internal/intern/set.h enc/trans/utf_16_32.$(OBJEXT): internal/intern/signal.h enc/trans/utf_16_32.$(OBJEXT): internal/intern/sprintf.h enc/trans/utf_16_32.$(OBJEXT): internal/intern/string.h @@ -8687,6 +8739,7 @@ enc/unicode.$(OBJEXT): internal/intern/re.h enc/unicode.$(OBJEXT): internal/intern/ruby.h enc/unicode.$(OBJEXT): internal/intern/select.h enc/unicode.$(OBJEXT): internal/intern/select/largesize.h +enc/unicode.$(OBJEXT): internal/intern/set.h enc/unicode.$(OBJEXT): internal/intern/signal.h enc/unicode.$(OBJEXT): internal/intern/sprintf.h enc/unicode.$(OBJEXT): internal/intern/string.h @@ -8858,6 +8911,7 @@ enc/us_ascii.$(OBJEXT): internal/intern/re.h enc/us_ascii.$(OBJEXT): internal/intern/ruby.h enc/us_ascii.$(OBJEXT): internal/intern/select.h enc/us_ascii.$(OBJEXT): internal/intern/select/largesize.h +enc/us_ascii.$(OBJEXT): internal/intern/set.h enc/us_ascii.$(OBJEXT): internal/intern/signal.h enc/us_ascii.$(OBJEXT): internal/intern/sprintf.h enc/us_ascii.$(OBJEXT): internal/intern/string.h @@ -9021,6 +9075,7 @@ enc/utf_16be.$(OBJEXT): internal/intern/re.h enc/utf_16be.$(OBJEXT): internal/intern/ruby.h enc/utf_16be.$(OBJEXT): internal/intern/select.h enc/utf_16be.$(OBJEXT): internal/intern/select/largesize.h +enc/utf_16be.$(OBJEXT): internal/intern/set.h enc/utf_16be.$(OBJEXT): internal/intern/signal.h enc/utf_16be.$(OBJEXT): internal/intern/sprintf.h enc/utf_16be.$(OBJEXT): internal/intern/string.h @@ -9183,6 +9238,7 @@ enc/utf_16le.$(OBJEXT): internal/intern/re.h enc/utf_16le.$(OBJEXT): internal/intern/ruby.h enc/utf_16le.$(OBJEXT): internal/intern/select.h enc/utf_16le.$(OBJEXT): internal/intern/select/largesize.h +enc/utf_16le.$(OBJEXT): internal/intern/set.h enc/utf_16le.$(OBJEXT): internal/intern/signal.h enc/utf_16le.$(OBJEXT): internal/intern/sprintf.h enc/utf_16le.$(OBJEXT): internal/intern/string.h @@ -9345,6 +9401,7 @@ enc/utf_32be.$(OBJEXT): internal/intern/re.h enc/utf_32be.$(OBJEXT): internal/intern/ruby.h enc/utf_32be.$(OBJEXT): internal/intern/select.h enc/utf_32be.$(OBJEXT): internal/intern/select/largesize.h +enc/utf_32be.$(OBJEXT): internal/intern/set.h enc/utf_32be.$(OBJEXT): internal/intern/signal.h enc/utf_32be.$(OBJEXT): internal/intern/sprintf.h enc/utf_32be.$(OBJEXT): internal/intern/string.h @@ -9507,6 +9564,7 @@ enc/utf_32le.$(OBJEXT): internal/intern/re.h enc/utf_32le.$(OBJEXT): internal/intern/ruby.h enc/utf_32le.$(OBJEXT): internal/intern/select.h enc/utf_32le.$(OBJEXT): internal/intern/select/largesize.h +enc/utf_32le.$(OBJEXT): internal/intern/set.h enc/utf_32le.$(OBJEXT): internal/intern/signal.h enc/utf_32le.$(OBJEXT): internal/intern/sprintf.h enc/utf_32le.$(OBJEXT): internal/intern/string.h @@ -9678,6 +9736,7 @@ enc/utf_8.$(OBJEXT): internal/intern/re.h enc/utf_8.$(OBJEXT): internal/intern/ruby.h enc/utf_8.$(OBJEXT): internal/intern/select.h enc/utf_8.$(OBJEXT): internal/intern/select/largesize.h +enc/utf_8.$(OBJEXT): internal/intern/set.h enc/utf_8.$(OBJEXT): internal/intern/signal.h enc/utf_8.$(OBJEXT): internal/intern/sprintf.h enc/utf_8.$(OBJEXT): internal/intern/string.h @@ -9841,6 +9900,7 @@ enc/windows_1250.$(OBJEXT): internal/intern/re.h enc/windows_1250.$(OBJEXT): internal/intern/ruby.h enc/windows_1250.$(OBJEXT): internal/intern/select.h enc/windows_1250.$(OBJEXT): internal/intern/select/largesize.h +enc/windows_1250.$(OBJEXT): internal/intern/set.h enc/windows_1250.$(OBJEXT): internal/intern/signal.h enc/windows_1250.$(OBJEXT): internal/intern/sprintf.h enc/windows_1250.$(OBJEXT): internal/intern/string.h @@ -10002,6 +10062,7 @@ enc/windows_1251.$(OBJEXT): internal/intern/re.h enc/windows_1251.$(OBJEXT): internal/intern/ruby.h enc/windows_1251.$(OBJEXT): internal/intern/select.h enc/windows_1251.$(OBJEXT): internal/intern/select/largesize.h +enc/windows_1251.$(OBJEXT): internal/intern/set.h enc/windows_1251.$(OBJEXT): internal/intern/signal.h enc/windows_1251.$(OBJEXT): internal/intern/sprintf.h enc/windows_1251.$(OBJEXT): internal/intern/string.h @@ -10164,6 +10225,7 @@ enc/windows_1252.$(OBJEXT): internal/intern/re.h enc/windows_1252.$(OBJEXT): internal/intern/ruby.h enc/windows_1252.$(OBJEXT): internal/intern/select.h enc/windows_1252.$(OBJEXT): internal/intern/select/largesize.h +enc/windows_1252.$(OBJEXT): internal/intern/set.h enc/windows_1252.$(OBJEXT): internal/intern/signal.h enc/windows_1252.$(OBJEXT): internal/intern/sprintf.h enc/windows_1252.$(OBJEXT): internal/intern/string.h @@ -10325,6 +10387,7 @@ enc/windows_1253.$(OBJEXT): internal/intern/re.h enc/windows_1253.$(OBJEXT): internal/intern/ruby.h enc/windows_1253.$(OBJEXT): internal/intern/select.h enc/windows_1253.$(OBJEXT): internal/intern/select/largesize.h +enc/windows_1253.$(OBJEXT): internal/intern/set.h enc/windows_1253.$(OBJEXT): internal/intern/signal.h enc/windows_1253.$(OBJEXT): internal/intern/sprintf.h enc/windows_1253.$(OBJEXT): internal/intern/string.h @@ -10487,6 +10550,7 @@ enc/windows_1254.$(OBJEXT): internal/intern/re.h enc/windows_1254.$(OBJEXT): internal/intern/ruby.h enc/windows_1254.$(OBJEXT): internal/intern/select.h enc/windows_1254.$(OBJEXT): internal/intern/select/largesize.h +enc/windows_1254.$(OBJEXT): internal/intern/set.h enc/windows_1254.$(OBJEXT): internal/intern/signal.h enc/windows_1254.$(OBJEXT): internal/intern/sprintf.h enc/windows_1254.$(OBJEXT): internal/intern/string.h @@ -10649,6 +10713,7 @@ enc/windows_1257.$(OBJEXT): internal/intern/re.h enc/windows_1257.$(OBJEXT): internal/intern/ruby.h enc/windows_1257.$(OBJEXT): internal/intern/select.h enc/windows_1257.$(OBJEXT): internal/intern/select/largesize.h +enc/windows_1257.$(OBJEXT): internal/intern/set.h enc/windows_1257.$(OBJEXT): internal/intern/signal.h enc/windows_1257.$(OBJEXT): internal/intern/sprintf.h enc/windows_1257.$(OBJEXT): internal/intern/string.h @@ -10813,6 +10878,7 @@ enc/windows_31j.$(OBJEXT): internal/intern/re.h enc/windows_31j.$(OBJEXT): internal/intern/ruby.h enc/windows_31j.$(OBJEXT): internal/intern/select.h enc/windows_31j.$(OBJEXT): internal/intern/select/largesize.h +enc/windows_31j.$(OBJEXT): internal/intern/set.h enc/windows_31j.$(OBJEXT): internal/intern/signal.h enc/windows_31j.$(OBJEXT): internal/intern/sprintf.h enc/windows_31j.$(OBJEXT): internal/intern/string.h diff --git a/enc/unicode/16.0.0/name2ctype.h b/enc/unicode/16.0.0/name2ctype.h index 08022a865d..42da74f318 100644 --- a/enc/unicode/16.0.0/name2ctype.h +++ b/enc/unicode/16.0.0/name2ctype.h @@ -3943,7 +3943,7 @@ static const OnigCodePoint CR_XDigit[] = { /* 'Word': [[:Word:]] */ static const OnigCodePoint CR_Word[] = { - 795, + 796, 0x0030, 0x0039, 0x0041, 0x005a, 0x005f, 0x005f, @@ -4241,6 +4241,7 @@ static const OnigCodePoint CR_Word[] = { 0x1fe0, 0x1fec, 0x1ff2, 0x1ff4, 0x1ff6, 0x1ffc, + 0x200c, 0x200d, 0x203f, 0x2040, 0x2054, 0x2054, 0x2071, 0x2071, diff --git a/encoding.c b/encoding.c index e2aaadb5b9..6fbb739bb6 100644 --- a/encoding.c +++ b/encoding.c @@ -24,6 +24,7 @@ #include "internal/string.h" #include "internal/vm.h" #include "regenc.h" +#include "ruby/atomic.h" #include "ruby/encoding.h" #include "ruby/util.h" #include "ruby_assert.h" @@ -60,6 +61,7 @@ VALUE rb_cEncoding; static VALUE rb_encoding_list; struct rb_encoding_entry { + rb_atomic_t loaded; const char *name; rb_encoding *enc; rb_encoding *base; @@ -93,15 +95,11 @@ static rb_encoding *global_enc_ascii, *global_enc_utf_8, *global_enc_us_ascii; -#define GLOBAL_ENC_TABLE_ENTER(enc_table) struct enc_table *enc_table = &global_enc_table; RB_VM_LOCK_ENTER() -#define GLOBAL_ENC_TABLE_LEAVE() RB_VM_LOCK_LEAVE() -#define GLOBAL_ENC_TABLE_EVAL(enc_table, expr) do { \ - GLOBAL_ENC_TABLE_ENTER(enc_table); \ - { \ - expr; \ - } \ - GLOBAL_ENC_TABLE_LEAVE(); \ -} while (0) +#define GLOBAL_ENC_TABLE_LOCKING(tbl) \ + for (struct enc_table *tbl = &global_enc_table, **locking = &tbl; \ + locking; \ + locking = NULL) \ + RB_VM_LOCKING() #define ENC_DUMMY_FLAG (1<<24) @@ -348,6 +346,8 @@ enc_table_expand(struct enc_table *enc_table, int newsize) static int enc_register_at(struct enc_table *enc_table, int index, const char *name, rb_encoding *base_encoding) { + ASSERT_vm_locking(); + struct rb_encoding_entry *ent = &enc_table->list[index]; rb_raw_encoding *encoding; @@ -362,6 +362,7 @@ enc_register_at(struct enc_table *enc_table, int index, const char *name, rb_enc if (!encoding) { encoding = xmalloc(sizeof(rb_encoding)); } + if (base_encoding) { *encoding = *base_encoding; } @@ -374,12 +375,18 @@ enc_register_at(struct enc_table *enc_table, int index, const char *name, rb_enc st_insert(enc_table->names, (st_data_t)name, (st_data_t)index); enc_list_update(index, encoding); + + // max_enc_len is used to mark a fully loaded encoding. + RUBY_ATOMIC_SET(ent->loaded, encoding->max_enc_len); + return index; } static int enc_register(struct enc_table *enc_table, const char *name, rb_encoding *encoding) { + ASSERT_vm_locking(); + int index = enc_table->count; enc_table->count = enc_table_expand(enc_table, index + 1); @@ -409,8 +416,7 @@ rb_enc_register(const char *name, rb_encoding *encoding) { int index; - GLOBAL_ENC_TABLE_ENTER(enc_table); - { + GLOBAL_ENC_TABLE_LOCKING(enc_table) { index = enc_registered(enc_table, name); if (index >= 0) { @@ -430,13 +436,13 @@ rb_enc_register(const char *name, rb_encoding *encoding) set_encoding_const(name, rb_enc_from_index(index)); } } - GLOBAL_ENC_TABLE_LEAVE(); return index; } int enc_registered(struct enc_table *enc_table, const char *name) { + ASSERT_vm_locking(); st_data_t idx = 0; if (!name) return -1; @@ -450,15 +456,13 @@ enc_registered(struct enc_table *enc_table, const char *name) void rb_encdb_declare(const char *name) { - GLOBAL_ENC_TABLE_ENTER(enc_table); - { + GLOBAL_ENC_TABLE_LOCKING(enc_table) { int idx = enc_registered(enc_table, name); if (idx < 0) { idx = enc_register(enc_table, name, 0); } set_encoding_const(name, rb_enc_from_index(idx)); } - GLOBAL_ENC_TABLE_LEAVE(); } static void @@ -490,13 +494,11 @@ set_base_encoding(struct enc_table *enc_table, int index, rb_encoding *base) void rb_enc_set_base(const char *name, const char *orig) { - GLOBAL_ENC_TABLE_ENTER(enc_table); - { + GLOBAL_ENC_TABLE_LOCKING(enc_table) { int idx = enc_registered(enc_table, name); int origidx = enc_registered(enc_table, orig); set_base_encoding(enc_table, idx, rb_enc_from_index(origidx)); } - GLOBAL_ENC_TABLE_LEAVE(); } /* for encdb.h @@ -547,8 +549,7 @@ rb_encdb_replicate(const char *name, const char *orig) { int r; - GLOBAL_ENC_TABLE_ENTER(enc_table); - { + GLOBAL_ENC_TABLE_LOCKING(enc_table) { int origidx = enc_registered(enc_table, orig); int idx = enc_registered(enc_table, name); @@ -557,7 +558,6 @@ rb_encdb_replicate(const char *name, const char *orig) } r = enc_replicate_with_index(enc_table, name, rb_enc_from_index(origidx), idx); } - GLOBAL_ENC_TABLE_LEAVE(); return r; } @@ -567,13 +567,11 @@ rb_define_dummy_encoding(const char *name) { int index; - GLOBAL_ENC_TABLE_ENTER(enc_table); - { + GLOBAL_ENC_TABLE_LOCKING(enc_table) { index = enc_replicate(enc_table, name, rb_ascii8bit_encoding()); rb_encoding *enc = enc_table->list[index].enc; ENC_SET_DUMMY((rb_raw_encoding *)enc); } - GLOBAL_ENC_TABLE_LEAVE(); return index; } @@ -583,15 +581,13 @@ rb_encdb_dummy(const char *name) { int index; - GLOBAL_ENC_TABLE_ENTER(enc_table); - { + GLOBAL_ENC_TABLE_LOCKING(enc_table) { index = enc_replicate_with_index(enc_table, name, rb_ascii8bit_encoding(), enc_registered(enc_table, name)); rb_encoding *enc = enc_table->list[index].enc; ENC_SET_DUMMY((rb_raw_encoding *)enc); } - GLOBAL_ENC_TABLE_LEAVE(); return index; } @@ -653,6 +649,7 @@ enc_dup_name(st_data_t name) static int enc_alias_internal(struct enc_table *enc_table, const char *alias, int idx) { + ASSERT_vm_locking(); return st_insert2(enc_table->names, (st_data_t)alias, (st_data_t)idx, enc_dup_name); } @@ -671,8 +668,7 @@ rb_enc_alias(const char *alias, const char *orig) { int idx, r; - GLOBAL_ENC_TABLE_ENTER(enc_table); - { + GLOBAL_ENC_TABLE_LOCKING(enc_table) { enc_check_addable(enc_table, alias); if ((idx = rb_enc_find_index(orig)) < 0) { r = -1; @@ -681,7 +677,6 @@ rb_enc_alias(const char *alias, const char *orig) r = enc_alias(enc_table, alias, idx); } } - GLOBAL_ENC_TABLE_LEAVE(); return r; } @@ -691,8 +686,7 @@ rb_encdb_alias(const char *alias, const char *orig) { int r; - GLOBAL_ENC_TABLE_ENTER(enc_table); - { + GLOBAL_ENC_TABLE_LOCKING(enc_table) { int idx = enc_registered(enc_table, orig); if (idx < 0) { @@ -700,7 +694,6 @@ rb_encdb_alias(const char *alias, const char *orig) } r = enc_alias(enc_table, alias, idx); } - GLOBAL_ENC_TABLE_LEAVE(); return r; } @@ -708,6 +701,7 @@ rb_encdb_alias(const char *alias, const char *orig) static void rb_enc_init(struct enc_table *enc_table) { + ASSERT_vm_locking(); enc_table_expand(enc_table, ENCODING_COUNT + 1); if (!enc_table->names) { enc_table->names = st_init_strcasetable_with_size(ENCODING_LIST_CAPA); @@ -767,8 +761,7 @@ load_encoding(const char *name) ruby_debug = debug; rb_set_errinfo(errinfo); - GLOBAL_ENC_TABLE_ENTER(enc_table); - { + GLOBAL_ENC_TABLE_LOCKING(enc_table) { if (loaded < 0 || 1 < loaded) { idx = -1; } @@ -779,51 +772,74 @@ load_encoding(const char *name) idx = -1; } } - GLOBAL_ENC_TABLE_LEAVE(); return idx; } static int -enc_autoload_body(struct enc_table *enc_table, rb_encoding *enc) +enc_autoload_body(rb_encoding *enc) { - rb_encoding *base = enc_table->list[ENC_TO_ENCINDEX(enc)].base; + rb_encoding *base; + int i = 0; + + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + base = enc_table->list[ENC_TO_ENCINDEX(enc)].base; + if (base) { + do { + if (i >= enc_table->count) { + i = -1; + break; + } + } while (enc_table->list[i].enc != base && (++i, 1)); + } + } + + if (i == -1) return -1; if (base) { - int i = 0; - do { - if (i >= enc_table->count) return -1; - } while (enc_table->list[i].enc != base && (++i, 1)); if (rb_enc_autoload_p(base)) { if (rb_enc_autoload(base) < 0) return -1; } i = enc->ruby_encoding_index; - enc_register_at(enc_table, i & ENC_INDEX_MASK, rb_enc_name(enc), base); + + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + enc_register_at(enc_table, i & ENC_INDEX_MASK, rb_enc_name(enc), base); + } + ((rb_raw_encoding *)enc)->ruby_encoding_index = i; i &= ENC_INDEX_MASK; return i; } - else { - return -2; - } + + return -2; } int rb_enc_autoload(rb_encoding *enc) { - int i; - GLOBAL_ENC_TABLE_EVAL(enc_table, i = enc_autoload_body(enc_table, enc)); + int i = enc_autoload_body(enc); if (i == -2) { i = load_encoding(rb_enc_name(enc)); } return i; } +bool +rb_enc_autoload_p(rb_encoding *enc) +{ + int idx = ENC_TO_ENCINDEX(enc); + RUBY_ASSERT(rb_enc_from_index(idx) == enc); + return !RUBY_ATOMIC_LOAD(global_enc_table.list[idx].loaded); +} + /* Return encoding index or UNSPECIFIED_ENCODING from encoding name */ int rb_enc_find_index(const char *name) { - int i = enc_registered(&global_enc_table, name); + int i; + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + i = enc_registered(enc_table, name); + } rb_encoding *enc; if (i < 0) { @@ -1504,16 +1520,16 @@ rb_locale_encindex(void) if (idx < 0) idx = ENCINDEX_UTF_8; - if (enc_registered(&global_enc_table, "locale") < 0) { + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + if (enc_registered(enc_table, "locale") < 0) { # if defined _WIN32 - void Init_w32_codepage(void); - Init_w32_codepage(); + void Init_w32_codepage(void); + Init_w32_codepage(); # endif - GLOBAL_ENC_TABLE_ENTER(enc_table); - { - enc_alias_internal(enc_table, "locale", idx); + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + enc_alias_internal(enc_table, "locale", idx); + } } - GLOBAL_ENC_TABLE_LEAVE(); } return idx; @@ -1528,7 +1544,10 @@ rb_locale_encoding(void) int rb_filesystem_encindex(void) { - int idx = enc_registered(&global_enc_table, "filesystem"); + int idx; + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + idx = enc_registered(enc_table, "filesystem"); + } if (idx < 0) idx = ENCINDEX_ASCII_8BIT; return idx; } @@ -1555,8 +1574,7 @@ enc_set_default_encoding(struct default_encoding *def, VALUE encoding, const cha /* Already set */ overridden = TRUE; - GLOBAL_ENC_TABLE_ENTER(enc_table); - { + GLOBAL_ENC_TABLE_LOCKING(enc_table) { if (NIL_P(encoding)) { def->index = -1; def->enc = 0; @@ -1580,7 +1598,6 @@ enc_set_default_encoding(struct default_encoding *def, VALUE encoding, const cha enc_alias_internal(enc_table, "filesystem", Init_enc_set_filesystem_encoding()); } } - GLOBAL_ENC_TABLE_LEAVE(); return overridden; } diff --git a/enum.c b/enum.c index 182e4f6e83..cbf74df484 100644 --- a/enum.c +++ b/enum.c @@ -1215,14 +1215,15 @@ tally_up(st_data_t *group, st_data_t *value, st_data_t arg, int existing) RB_OBJ_WRITTEN(hash, Qundef, tally); } *value = (st_data_t)tally; - if (!SPECIAL_CONST_P(*group)) RB_OBJ_WRITTEN(hash, Qundef, *group); return ST_CONTINUE; } static VALUE rb_enum_tally_up(VALUE hash, VALUE group) { - rb_hash_stlike_update(hash, group, tally_up, (st_data_t)hash); + if (!rb_hash_stlike_update(hash, group, tally_up, (st_data_t)hash)) { + RB_OBJ_WRITTEN(hash, Qundef, group); + } return hash; } diff --git a/enumerator.c b/enumerator.c index faaa77cb49..b91b2eb940 100644 --- a/enumerator.c +++ b/enumerator.c @@ -162,10 +162,9 @@ */ VALUE rb_cEnumerator; static VALUE rb_cLazy; -static ID id_rewind, id_new, id_to_enum, id_each_entry; +static ID id_rewind, id_to_enum, id_each_entry; static ID id_next, id_result, id_receiver, id_arguments, id_memo, id_method, id_force; -static ID id_begin, id_end, id_step, id_exclude_end; -static VALUE sym_each, sym_cycle, sym_yield; +static VALUE sym_each, sym_yield; static VALUE lazy_use_super_method; @@ -3748,6 +3747,55 @@ enumerator_s_product(int argc, VALUE *argv, VALUE klass) return obj; } +struct arith_seq { + struct enumerator enumerator; + VALUE begin; + VALUE end; + VALUE step; + bool exclude_end; +}; + +RUBY_REFERENCES(arith_seq_refs) = { + RUBY_REF_EDGE(struct enumerator, obj), + RUBY_REF_EDGE(struct enumerator, args), + RUBY_REF_EDGE(struct enumerator, fib), + RUBY_REF_EDGE(struct enumerator, dst), + RUBY_REF_EDGE(struct enumerator, lookahead), + RUBY_REF_EDGE(struct enumerator, feedvalue), + RUBY_REF_EDGE(struct enumerator, stop_exc), + RUBY_REF_EDGE(struct enumerator, size), + RUBY_REF_EDGE(struct enumerator, procs), + + RUBY_REF_EDGE(struct arith_seq, begin), + RUBY_REF_EDGE(struct arith_seq, end), + RUBY_REF_EDGE(struct arith_seq, step), + RUBY_REF_END +}; + +static const rb_data_type_t arith_seq_data_type = { + "arithmetic_sequence", + { + RUBY_REFS_LIST_PTR(arith_seq_refs), + RUBY_TYPED_DEFAULT_FREE, + NULL, // Nothing allocated externally, so don't need a memsize function + NULL, + }, + .parent = &enumerator_data_type, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_DECL_MARKING | RUBY_TYPED_EMBEDDABLE +}; + +static VALUE +arith_seq_allocate(VALUE klass) +{ + struct arith_seq *ptr; + VALUE enum_obj; + + enum_obj = TypedData_Make_Struct(klass, struct arith_seq, &arith_seq_data_type, ptr); + ptr->enumerator.obj = Qundef; + + return enum_obj; +} + /* * Document-class: Enumerator::ArithmeticSequence * @@ -3765,12 +3813,16 @@ rb_arith_seq_new(VALUE obj, VALUE meth, int argc, VALUE const *argv, rb_enumerator_size_func *size_fn, VALUE beg, VALUE end, VALUE step, int excl) { - VALUE aseq = enumerator_init(enumerator_allocate(rb_cArithSeq), + VALUE aseq = enumerator_init(arith_seq_allocate(rb_cArithSeq), obj, meth, argc, argv, size_fn, Qnil, rb_keyword_given_p()); - rb_ivar_set(aseq, id_begin, beg); - rb_ivar_set(aseq, id_end, end); - rb_ivar_set(aseq, id_step, step); - rb_ivar_set(aseq, id_exclude_end, RBOOL(excl)); + struct arith_seq *ptr; + TypedData_Get_Struct(aseq, struct arith_seq, &enumerator_data_type, ptr); + + RB_OBJ_WRITE(aseq, &ptr->begin, beg); + RB_OBJ_WRITE(aseq, &ptr->end, end); + RB_OBJ_WRITE(aseq, &ptr->step, step); + ptr->exclude_end = excl; + return aseq; } @@ -3783,7 +3835,9 @@ rb_arith_seq_new(VALUE obj, VALUE meth, int argc, VALUE const *argv, static inline VALUE arith_seq_begin(VALUE self) { - return rb_ivar_get(self, id_begin); + struct arith_seq *ptr; + TypedData_Get_Struct(self, struct arith_seq, &enumerator_data_type, ptr); + return ptr->begin; } /* @@ -3794,7 +3848,9 @@ arith_seq_begin(VALUE self) static inline VALUE arith_seq_end(VALUE self) { - return rb_ivar_get(self, id_end); + struct arith_seq *ptr; + TypedData_Get_Struct(self, struct arith_seq, &enumerator_data_type, ptr); + return ptr->end; } /* @@ -3806,7 +3862,9 @@ arith_seq_end(VALUE self) static inline VALUE arith_seq_step(VALUE self) { - return rb_ivar_get(self, id_step); + struct arith_seq *ptr; + TypedData_Get_Struct(self, struct arith_seq, &enumerator_data_type, ptr); + return ptr->step; } /* @@ -3817,13 +3875,17 @@ arith_seq_step(VALUE self) static inline VALUE arith_seq_exclude_end(VALUE self) { - return rb_ivar_get(self, id_exclude_end); + struct arith_seq *ptr; + TypedData_Get_Struct(self, struct arith_seq, &enumerator_data_type, ptr); + return RBOOL(ptr->exclude_end); } static inline int arith_seq_exclude_end_p(VALUE self) { - return RTEST(arith_seq_exclude_end(self)); + struct arith_seq *ptr; + TypedData_Get_Struct(self, struct arith_seq, &enumerator_data_type, ptr); + return ptr->exclude_end; } int @@ -4664,7 +4726,6 @@ void Init_Enumerator(void) { id_rewind = rb_intern_const("rewind"); - id_new = rb_intern_const("new"); id_next = rb_intern_const("next"); id_result = rb_intern_const("result"); id_receiver = rb_intern_const("receiver"); @@ -4674,12 +4735,7 @@ Init_Enumerator(void) id_force = rb_intern_const("force"); id_to_enum = rb_intern_const("to_enum"); id_each_entry = rb_intern_const("each_entry"); - id_begin = rb_intern_const("begin"); - id_end = rb_intern_const("end"); - id_step = rb_intern_const("step"); - id_exclude_end = rb_intern_const("exclude_end"); sym_each = ID2SYM(id_each); - sym_cycle = ID2SYM(rb_intern_const("cycle")); sym_yield = ID2SYM(rb_intern_const("yield")); InitVM(Enumerator); diff --git a/eval.c b/eval.c index f845bf98ae..74f31db5c0 100644 --- a/eval.c +++ b/eval.c @@ -78,6 +78,7 @@ ruby_setup(void) #endif Init_BareVM(); rb_vm_encoded_insn_data_table_init(); + Init_enable_namespace(); Init_vm_objects(); Init_fstring_table(); @@ -422,7 +423,8 @@ rb_class_modify_check(VALUE klass) Check_Type(klass, T_CLASS); } if (RB_TYPE_P(klass, T_MODULE)) { - rb_module_set_initialized(klass); + // TODO: shouldn't this only happen in a few places? + rb_class_set_initialized(klass); } if (OBJ_FROZEN(klass)) { const char *desc; @@ -529,10 +531,14 @@ exc_setup_message(const rb_execution_context_t *ec, VALUE mesg, VALUE *cause) rb_exc_check_circular_cause(*cause); #else VALUE c = *cause; - while (!NIL_P(c = rb_attr_get(c, id_cause))) { + while (!NIL_P(c)) { if (c == mesg) { rb_raise(rb_eArgError, "circular causes"); } + if (THROW_DATA_P(c)) { + break; + } + c = rb_attr_get(c, id_cause); } #endif } diff --git a/ext/-test-/RUBY_ALIGNOF/depend b/ext/-test-/RUBY_ALIGNOF/depend index 25364e55fb..103d20b33c 100644 --- a/ext/-test-/RUBY_ALIGNOF/depend +++ b/ext/-test-/RUBY_ALIGNOF/depend @@ -128,6 +128,7 @@ c.o: $(hdrdir)/ruby/internal/intern/re.h c.o: $(hdrdir)/ruby/internal/intern/ruby.h c.o: $(hdrdir)/ruby/internal/intern/select.h c.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +c.o: $(hdrdir)/ruby/internal/intern/set.h c.o: $(hdrdir)/ruby/internal/intern/signal.h c.o: $(hdrdir)/ruby/internal/intern/sprintf.h c.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/arith_seq/beg_len_step/depend b/ext/-test-/arith_seq/beg_len_step/depend index 702a0037a8..098c8ff1f0 100644 --- a/ext/-test-/arith_seq/beg_len_step/depend +++ b/ext/-test-/arith_seq/beg_len_step/depend @@ -127,6 +127,7 @@ beg_len_step.o: $(hdrdir)/ruby/internal/intern/re.h beg_len_step.o: $(hdrdir)/ruby/internal/intern/ruby.h beg_len_step.o: $(hdrdir)/ruby/internal/intern/select.h beg_len_step.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +beg_len_step.o: $(hdrdir)/ruby/internal/intern/set.h beg_len_step.o: $(hdrdir)/ruby/internal/intern/signal.h beg_len_step.o: $(hdrdir)/ruby/internal/intern/sprintf.h beg_len_step.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/arith_seq/extract/depend b/ext/-test-/arith_seq/extract/depend index fdbd71dfbc..5c07cea4b4 100644 --- a/ext/-test-/arith_seq/extract/depend +++ b/ext/-test-/arith_seq/extract/depend @@ -127,6 +127,7 @@ extract.o: $(hdrdir)/ruby/internal/intern/re.h extract.o: $(hdrdir)/ruby/internal/intern/ruby.h extract.o: $(hdrdir)/ruby/internal/intern/select.h extract.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +extract.o: $(hdrdir)/ruby/internal/intern/set.h extract.o: $(hdrdir)/ruby/internal/intern/signal.h extract.o: $(hdrdir)/ruby/internal/intern/sprintf.h extract.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/array/concat/depend b/ext/-test-/array/concat/depend index f2213a42ea..8edf45465f 100644 --- a/ext/-test-/array/concat/depend +++ b/ext/-test-/array/concat/depend @@ -128,6 +128,7 @@ to_ary_concat.o: $(hdrdir)/ruby/internal/intern/re.h to_ary_concat.o: $(hdrdir)/ruby/internal/intern/ruby.h to_ary_concat.o: $(hdrdir)/ruby/internal/intern/select.h to_ary_concat.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +to_ary_concat.o: $(hdrdir)/ruby/internal/intern/set.h to_ary_concat.o: $(hdrdir)/ruby/internal/intern/signal.h to_ary_concat.o: $(hdrdir)/ruby/internal/intern/sprintf.h to_ary_concat.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/array/resize/depend b/ext/-test-/array/resize/depend index f88a9d03c1..e6a228b43d 100644 --- a/ext/-test-/array/resize/depend +++ b/ext/-test-/array/resize/depend @@ -127,6 +127,7 @@ resize.o: $(hdrdir)/ruby/internal/intern/re.h resize.o: $(hdrdir)/ruby/internal/intern/ruby.h resize.o: $(hdrdir)/ruby/internal/intern/select.h resize.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +resize.o: $(hdrdir)/ruby/internal/intern/set.h resize.o: $(hdrdir)/ruby/internal/intern/signal.h resize.o: $(hdrdir)/ruby/internal/intern/sprintf.h resize.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/asan/depend b/ext/-test-/asan/depend index b419d5a595..93cdc739ec 100644 --- a/ext/-test-/asan/depend +++ b/ext/-test-/asan/depend @@ -127,6 +127,7 @@ asan.o: $(hdrdir)/ruby/internal/intern/re.h asan.o: $(hdrdir)/ruby/internal/intern/ruby.h asan.o: $(hdrdir)/ruby/internal/intern/select.h asan.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +asan.o: $(hdrdir)/ruby/internal/intern/set.h asan.o: $(hdrdir)/ruby/internal/intern/signal.h asan.o: $(hdrdir)/ruby/internal/intern/sprintf.h asan.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/bignum/depend b/ext/-test-/bignum/depend index 078915ab72..049f0c7b52 100644 --- a/ext/-test-/bignum/depend +++ b/ext/-test-/bignum/depend @@ -127,6 +127,7 @@ big2str.o: $(hdrdir)/ruby/internal/intern/re.h big2str.o: $(hdrdir)/ruby/internal/intern/ruby.h big2str.o: $(hdrdir)/ruby/internal/intern/select.h big2str.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +big2str.o: $(hdrdir)/ruby/internal/intern/set.h big2str.o: $(hdrdir)/ruby/internal/intern/signal.h big2str.o: $(hdrdir)/ruby/internal/intern/sprintf.h big2str.o: $(hdrdir)/ruby/internal/intern/string.h @@ -287,6 +288,7 @@ bigzero.o: $(hdrdir)/ruby/internal/intern/re.h bigzero.o: $(hdrdir)/ruby/internal/intern/ruby.h bigzero.o: $(hdrdir)/ruby/internal/intern/select.h bigzero.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +bigzero.o: $(hdrdir)/ruby/internal/intern/set.h bigzero.o: $(hdrdir)/ruby/internal/intern/signal.h bigzero.o: $(hdrdir)/ruby/internal/intern/sprintf.h bigzero.o: $(hdrdir)/ruby/internal/intern/string.h @@ -447,6 +449,7 @@ div.o: $(hdrdir)/ruby/internal/intern/re.h div.o: $(hdrdir)/ruby/internal/intern/ruby.h div.o: $(hdrdir)/ruby/internal/intern/select.h div.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +div.o: $(hdrdir)/ruby/internal/intern/set.h div.o: $(hdrdir)/ruby/internal/intern/signal.h div.o: $(hdrdir)/ruby/internal/intern/sprintf.h div.o: $(hdrdir)/ruby/internal/intern/string.h @@ -608,6 +611,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h @@ -767,6 +771,7 @@ intpack.o: $(hdrdir)/ruby/internal/intern/re.h intpack.o: $(hdrdir)/ruby/internal/intern/ruby.h intpack.o: $(hdrdir)/ruby/internal/intern/select.h intpack.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +intpack.o: $(hdrdir)/ruby/internal/intern/set.h intpack.o: $(hdrdir)/ruby/internal/intern/signal.h intpack.o: $(hdrdir)/ruby/internal/intern/sprintf.h intpack.o: $(hdrdir)/ruby/internal/intern/string.h @@ -927,6 +932,7 @@ mul.o: $(hdrdir)/ruby/internal/intern/re.h mul.o: $(hdrdir)/ruby/internal/intern/ruby.h mul.o: $(hdrdir)/ruby/internal/intern/select.h mul.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +mul.o: $(hdrdir)/ruby/internal/intern/set.h mul.o: $(hdrdir)/ruby/internal/intern/signal.h mul.o: $(hdrdir)/ruby/internal/intern/sprintf.h mul.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1087,6 +1093,7 @@ str2big.o: $(hdrdir)/ruby/internal/intern/re.h str2big.o: $(hdrdir)/ruby/internal/intern/ruby.h str2big.o: $(hdrdir)/ruby/internal/intern/select.h str2big.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +str2big.o: $(hdrdir)/ruby/internal/intern/set.h str2big.o: $(hdrdir)/ruby/internal/intern/signal.h str2big.o: $(hdrdir)/ruby/internal/intern/sprintf.h str2big.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/bug-14834/depend b/ext/-test-/bug-14834/depend index 38429918b1..f83939d559 100644 --- a/ext/-test-/bug-14834/depend +++ b/ext/-test-/bug-14834/depend @@ -128,6 +128,7 @@ bug-14834.o: $(hdrdir)/ruby/internal/intern/re.h bug-14834.o: $(hdrdir)/ruby/internal/intern/ruby.h bug-14834.o: $(hdrdir)/ruby/internal/intern/select.h bug-14834.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +bug-14834.o: $(hdrdir)/ruby/internal/intern/set.h bug-14834.o: $(hdrdir)/ruby/internal/intern/signal.h bug-14834.o: $(hdrdir)/ruby/internal/intern/sprintf.h bug-14834.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/bug-3571/depend b/ext/-test-/bug-3571/depend index 84517a9c15..69c970b6f2 100644 --- a/ext/-test-/bug-3571/depend +++ b/ext/-test-/bug-3571/depend @@ -128,6 +128,7 @@ bug.o: $(hdrdir)/ruby/internal/intern/re.h bug.o: $(hdrdir)/ruby/internal/intern/ruby.h bug.o: $(hdrdir)/ruby/internal/intern/select.h bug.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +bug.o: $(hdrdir)/ruby/internal/intern/set.h bug.o: $(hdrdir)/ruby/internal/intern/signal.h bug.o: $(hdrdir)/ruby/internal/intern/sprintf.h bug.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/bug-5832/depend b/ext/-test-/bug-5832/depend index 84517a9c15..69c970b6f2 100644 --- a/ext/-test-/bug-5832/depend +++ b/ext/-test-/bug-5832/depend @@ -128,6 +128,7 @@ bug.o: $(hdrdir)/ruby/internal/intern/re.h bug.o: $(hdrdir)/ruby/internal/intern/ruby.h bug.o: $(hdrdir)/ruby/internal/intern/select.h bug.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +bug.o: $(hdrdir)/ruby/internal/intern/set.h bug.o: $(hdrdir)/ruby/internal/intern/signal.h bug.o: $(hdrdir)/ruby/internal/intern/sprintf.h bug.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/bug_reporter/depend b/ext/-test-/bug_reporter/depend index 1c73234247..e9993c3295 100644 --- a/ext/-test-/bug_reporter/depend +++ b/ext/-test-/bug_reporter/depend @@ -128,6 +128,7 @@ bug_reporter.o: $(hdrdir)/ruby/internal/intern/re.h bug_reporter.o: $(hdrdir)/ruby/internal/intern/ruby.h bug_reporter.o: $(hdrdir)/ruby/internal/intern/select.h bug_reporter.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +bug_reporter.o: $(hdrdir)/ruby/internal/intern/set.h bug_reporter.o: $(hdrdir)/ruby/internal/intern/signal.h bug_reporter.o: $(hdrdir)/ruby/internal/intern/sprintf.h bug_reporter.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/class/depend b/ext/-test-/class/depend index b0595fdc46..557206cefb 100644 --- a/ext/-test-/class/depend +++ b/ext/-test-/class/depend @@ -127,6 +127,7 @@ class2name.o: $(hdrdir)/ruby/internal/intern/re.h class2name.o: $(hdrdir)/ruby/internal/intern/ruby.h class2name.o: $(hdrdir)/ruby/internal/intern/select.h class2name.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +class2name.o: $(hdrdir)/ruby/internal/intern/set.h class2name.o: $(hdrdir)/ruby/internal/intern/signal.h class2name.o: $(hdrdir)/ruby/internal/intern/sprintf.h class2name.o: $(hdrdir)/ruby/internal/intern/string.h @@ -287,6 +288,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/debug/depend b/ext/-test-/debug/depend index 67e32c6aa6..4ae0378ef2 100644 --- a/ext/-test-/debug/depend +++ b/ext/-test-/debug/depend @@ -128,6 +128,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h @@ -288,6 +289,7 @@ inspector.o: $(hdrdir)/ruby/internal/intern/re.h inspector.o: $(hdrdir)/ruby/internal/intern/ruby.h inspector.o: $(hdrdir)/ruby/internal/intern/select.h inspector.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +inspector.o: $(hdrdir)/ruby/internal/intern/set.h inspector.o: $(hdrdir)/ruby/internal/intern/signal.h inspector.o: $(hdrdir)/ruby/internal/intern/sprintf.h inspector.o: $(hdrdir)/ruby/internal/intern/string.h @@ -448,6 +450,7 @@ profile_frames.o: $(hdrdir)/ruby/internal/intern/re.h profile_frames.o: $(hdrdir)/ruby/internal/intern/ruby.h profile_frames.o: $(hdrdir)/ruby/internal/intern/select.h profile_frames.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +profile_frames.o: $(hdrdir)/ruby/internal/intern/set.h profile_frames.o: $(hdrdir)/ruby/internal/intern/signal.h profile_frames.o: $(hdrdir)/ruby/internal/intern/sprintf.h profile_frames.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/dln/empty/depend b/ext/-test-/dln/empty/depend index d3e606df57..58f1508598 100644 --- a/ext/-test-/dln/empty/depend +++ b/ext/-test-/dln/empty/depend @@ -128,6 +128,7 @@ empty.o: $(hdrdir)/ruby/internal/intern/re.h empty.o: $(hdrdir)/ruby/internal/intern/ruby.h empty.o: $(hdrdir)/ruby/internal/intern/select.h empty.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +empty.o: $(hdrdir)/ruby/internal/intern/set.h empty.o: $(hdrdir)/ruby/internal/intern/signal.h empty.o: $(hdrdir)/ruby/internal/intern/sprintf.h empty.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/econv/depend b/ext/-test-/econv/depend index 7ac60f2962..3a5bc9c659 100644 --- a/ext/-test-/econv/depend +++ b/ext/-test-/econv/depend @@ -138,6 +138,7 @@ append.o: $(hdrdir)/ruby/internal/intern/re.h append.o: $(hdrdir)/ruby/internal/intern/ruby.h append.o: $(hdrdir)/ruby/internal/intern/select.h append.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +append.o: $(hdrdir)/ruby/internal/intern/set.h append.o: $(hdrdir)/ruby/internal/intern/signal.h append.o: $(hdrdir)/ruby/internal/intern/sprintf.h append.o: $(hdrdir)/ruby/internal/intern/string.h @@ -300,6 +301,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/ensure_and_callcc/depend b/ext/-test-/ensure_and_callcc/depend index 773538eaa5..54431847a6 100644 --- a/ext/-test-/ensure_and_callcc/depend +++ b/ext/-test-/ensure_and_callcc/depend @@ -128,6 +128,7 @@ ensure_and_callcc.o: $(hdrdir)/ruby/internal/intern/re.h ensure_and_callcc.o: $(hdrdir)/ruby/internal/intern/ruby.h ensure_and_callcc.o: $(hdrdir)/ruby/internal/intern/select.h ensure_and_callcc.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ensure_and_callcc.o: $(hdrdir)/ruby/internal/intern/set.h ensure_and_callcc.o: $(hdrdir)/ruby/internal/intern/signal.h ensure_and_callcc.o: $(hdrdir)/ruby/internal/intern/sprintf.h ensure_and_callcc.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/enumerator_kw/depend b/ext/-test-/enumerator_kw/depend index 85daa55b53..b6d2f0a998 100644 --- a/ext/-test-/enumerator_kw/depend +++ b/ext/-test-/enumerator_kw/depend @@ -128,6 +128,7 @@ enumerator_kw.o: $(hdrdir)/ruby/internal/intern/re.h enumerator_kw.o: $(hdrdir)/ruby/internal/intern/ruby.h enumerator_kw.o: $(hdrdir)/ruby/internal/intern/select.h enumerator_kw.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +enumerator_kw.o: $(hdrdir)/ruby/internal/intern/set.h enumerator_kw.o: $(hdrdir)/ruby/internal/intern/signal.h enumerator_kw.o: $(hdrdir)/ruby/internal/intern/sprintf.h enumerator_kw.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/eval/depend b/ext/-test-/eval/depend index 9daa2f5688..03a1c7d7ef 100644 --- a/ext/-test-/eval/depend +++ b/ext/-test-/eval/depend @@ -127,6 +127,7 @@ eval.o: $(hdrdir)/ruby/internal/intern/re.h eval.o: $(hdrdir)/ruby/internal/intern/ruby.h eval.o: $(hdrdir)/ruby/internal/intern/select.h eval.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +eval.o: $(hdrdir)/ruby/internal/intern/set.h eval.o: $(hdrdir)/ruby/internal/intern/signal.h eval.o: $(hdrdir)/ruby/internal/intern/sprintf.h eval.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/exception/depend b/ext/-test-/exception/depend index 7a0544b1e1..690e5ad377 100644 --- a/ext/-test-/exception/depend +++ b/ext/-test-/exception/depend @@ -127,6 +127,7 @@ dataerror.o: $(hdrdir)/ruby/internal/intern/re.h dataerror.o: $(hdrdir)/ruby/internal/intern/ruby.h dataerror.o: $(hdrdir)/ruby/internal/intern/select.h dataerror.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +dataerror.o: $(hdrdir)/ruby/internal/intern/set.h dataerror.o: $(hdrdir)/ruby/internal/intern/signal.h dataerror.o: $(hdrdir)/ruby/internal/intern/sprintf.h dataerror.o: $(hdrdir)/ruby/internal/intern/string.h @@ -297,6 +298,7 @@ enc_raise.o: $(hdrdir)/ruby/internal/intern/re.h enc_raise.o: $(hdrdir)/ruby/internal/intern/ruby.h enc_raise.o: $(hdrdir)/ruby/internal/intern/select.h enc_raise.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +enc_raise.o: $(hdrdir)/ruby/internal/intern/set.h enc_raise.o: $(hdrdir)/ruby/internal/intern/signal.h enc_raise.o: $(hdrdir)/ruby/internal/intern/sprintf.h enc_raise.o: $(hdrdir)/ruby/internal/intern/string.h @@ -459,6 +461,7 @@ ensured.o: $(hdrdir)/ruby/internal/intern/re.h ensured.o: $(hdrdir)/ruby/internal/intern/ruby.h ensured.o: $(hdrdir)/ruby/internal/intern/select.h ensured.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ensured.o: $(hdrdir)/ruby/internal/intern/set.h ensured.o: $(hdrdir)/ruby/internal/intern/signal.h ensured.o: $(hdrdir)/ruby/internal/intern/sprintf.h ensured.o: $(hdrdir)/ruby/internal/intern/string.h @@ -619,6 +622,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/fatal/depend b/ext/-test-/fatal/depend index 36b0ad4205..306bc9099c 100644 --- a/ext/-test-/fatal/depend +++ b/ext/-test-/fatal/depend @@ -129,6 +129,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h @@ -289,6 +290,7 @@ invalid.o: $(hdrdir)/ruby/internal/intern/re.h invalid.o: $(hdrdir)/ruby/internal/intern/ruby.h invalid.o: $(hdrdir)/ruby/internal/intern/select.h invalid.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +invalid.o: $(hdrdir)/ruby/internal/intern/set.h invalid.o: $(hdrdir)/ruby/internal/intern/signal.h invalid.o: $(hdrdir)/ruby/internal/intern/sprintf.h invalid.o: $(hdrdir)/ruby/internal/intern/string.h @@ -449,6 +451,7 @@ rb_fatal.o: $(hdrdir)/ruby/internal/intern/re.h rb_fatal.o: $(hdrdir)/ruby/internal/intern/ruby.h rb_fatal.o: $(hdrdir)/ruby/internal/intern/select.h rb_fatal.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +rb_fatal.o: $(hdrdir)/ruby/internal/intern/set.h rb_fatal.o: $(hdrdir)/ruby/internal/intern/signal.h rb_fatal.o: $(hdrdir)/ruby/internal/intern/sprintf.h rb_fatal.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/file/depend b/ext/-test-/file/depend index e985f914b2..fe320f3d44 100644 --- a/ext/-test-/file/depend +++ b/ext/-test-/file/depend @@ -137,6 +137,7 @@ fs.o: $(hdrdir)/ruby/internal/intern/re.h fs.o: $(hdrdir)/ruby/internal/intern/ruby.h fs.o: $(hdrdir)/ruby/internal/intern/select.h fs.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +fs.o: $(hdrdir)/ruby/internal/intern/set.h fs.o: $(hdrdir)/ruby/internal/intern/signal.h fs.o: $(hdrdir)/ruby/internal/intern/sprintf.h fs.o: $(hdrdir)/ruby/internal/intern/string.h @@ -300,6 +301,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h @@ -469,6 +471,7 @@ newline_conv.o: $(hdrdir)/ruby/internal/intern/re.h newline_conv.o: $(hdrdir)/ruby/internal/intern/ruby.h newline_conv.o: $(hdrdir)/ruby/internal/intern/select.h newline_conv.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +newline_conv.o: $(hdrdir)/ruby/internal/intern/set.h newline_conv.o: $(hdrdir)/ruby/internal/intern/signal.h newline_conv.o: $(hdrdir)/ruby/internal/intern/sprintf.h newline_conv.o: $(hdrdir)/ruby/internal/intern/string.h @@ -641,6 +644,7 @@ stat.o: $(hdrdir)/ruby/internal/intern/re.h stat.o: $(hdrdir)/ruby/internal/intern/ruby.h stat.o: $(hdrdir)/ruby/internal/intern/select.h stat.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +stat.o: $(hdrdir)/ruby/internal/intern/set.h stat.o: $(hdrdir)/ruby/internal/intern/signal.h stat.o: $(hdrdir)/ruby/internal/intern/sprintf.h stat.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/float/depend b/ext/-test-/float/depend index 3e34818d5f..334ed33c3b 100644 --- a/ext/-test-/float/depend +++ b/ext/-test-/float/depend @@ -131,6 +131,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h @@ -291,6 +292,7 @@ nextafter.o: $(hdrdir)/ruby/internal/intern/re.h nextafter.o: $(hdrdir)/ruby/internal/intern/ruby.h nextafter.o: $(hdrdir)/ruby/internal/intern/select.h nextafter.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +nextafter.o: $(hdrdir)/ruby/internal/intern/set.h nextafter.o: $(hdrdir)/ruby/internal/intern/signal.h nextafter.o: $(hdrdir)/ruby/internal/intern/sprintf.h nextafter.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/funcall/depend b/ext/-test-/funcall/depend index a5a30873ac..e54370306f 100644 --- a/ext/-test-/funcall/depend +++ b/ext/-test-/funcall/depend @@ -128,6 +128,7 @@ funcall.o: $(hdrdir)/ruby/internal/intern/re.h funcall.o: $(hdrdir)/ruby/internal/intern/ruby.h funcall.o: $(hdrdir)/ruby/internal/intern/select.h funcall.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +funcall.o: $(hdrdir)/ruby/internal/intern/set.h funcall.o: $(hdrdir)/ruby/internal/intern/signal.h funcall.o: $(hdrdir)/ruby/internal/intern/sprintf.h funcall.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/gvl/call_without_gvl/depend b/ext/-test-/gvl/call_without_gvl/depend index 6463d4527d..236d1e1d3b 100644 --- a/ext/-test-/gvl/call_without_gvl/depend +++ b/ext/-test-/gvl/call_without_gvl/depend @@ -127,6 +127,7 @@ call_without_gvl.o: $(hdrdir)/ruby/internal/intern/re.h call_without_gvl.o: $(hdrdir)/ruby/internal/intern/ruby.h call_without_gvl.o: $(hdrdir)/ruby/internal/intern/select.h call_without_gvl.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +call_without_gvl.o: $(hdrdir)/ruby/internal/intern/set.h call_without_gvl.o: $(hdrdir)/ruby/internal/intern/signal.h call_without_gvl.o: $(hdrdir)/ruby/internal/intern/sprintf.h call_without_gvl.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/hash/depend b/ext/-test-/hash/depend index 4bc4bfcdd6..416b93f9de 100644 --- a/ext/-test-/hash/depend +++ b/ext/-test-/hash/depend @@ -128,6 +128,7 @@ delete.o: $(hdrdir)/ruby/internal/intern/re.h delete.o: $(hdrdir)/ruby/internal/intern/ruby.h delete.o: $(hdrdir)/ruby/internal/intern/select.h delete.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +delete.o: $(hdrdir)/ruby/internal/intern/set.h delete.o: $(hdrdir)/ruby/internal/intern/signal.h delete.o: $(hdrdir)/ruby/internal/intern/sprintf.h delete.o: $(hdrdir)/ruby/internal/intern/string.h @@ -288,6 +289,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/integer/depend b/ext/-test-/integer/depend index d89965e3d9..0ea007e814 100644 --- a/ext/-test-/integer/depend +++ b/ext/-test-/integer/depend @@ -128,6 +128,7 @@ core_ext.o: $(hdrdir)/ruby/internal/intern/re.h core_ext.o: $(hdrdir)/ruby/internal/intern/ruby.h core_ext.o: $(hdrdir)/ruby/internal/intern/select.h core_ext.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +core_ext.o: $(hdrdir)/ruby/internal/intern/set.h core_ext.o: $(hdrdir)/ruby/internal/intern/signal.h core_ext.o: $(hdrdir)/ruby/internal/intern/sprintf.h core_ext.o: $(hdrdir)/ruby/internal/intern/string.h @@ -296,6 +297,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h @@ -456,6 +458,7 @@ my_integer.o: $(hdrdir)/ruby/internal/intern/re.h my_integer.o: $(hdrdir)/ruby/internal/intern/ruby.h my_integer.o: $(hdrdir)/ruby/internal/intern/select.h my_integer.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +my_integer.o: $(hdrdir)/ruby/internal/intern/set.h my_integer.o: $(hdrdir)/ruby/internal/intern/signal.h my_integer.o: $(hdrdir)/ruby/internal/intern/sprintf.h my_integer.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/iseq_load/depend b/ext/-test-/iseq_load/depend index fd07b3199c..9361ddb938 100644 --- a/ext/-test-/iseq_load/depend +++ b/ext/-test-/iseq_load/depend @@ -128,6 +128,7 @@ iseq_load.o: $(hdrdir)/ruby/internal/intern/re.h iseq_load.o: $(hdrdir)/ruby/internal/intern/ruby.h iseq_load.o: $(hdrdir)/ruby/internal/intern/select.h iseq_load.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +iseq_load.o: $(hdrdir)/ruby/internal/intern/set.h iseq_load.o: $(hdrdir)/ruby/internal/intern/signal.h iseq_load.o: $(hdrdir)/ruby/internal/intern/sprintf.h iseq_load.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/iter/depend b/ext/-test-/iter/depend index cff4b1bb43..161947382c 100644 --- a/ext/-test-/iter/depend +++ b/ext/-test-/iter/depend @@ -128,6 +128,7 @@ break.o: $(hdrdir)/ruby/internal/intern/re.h break.o: $(hdrdir)/ruby/internal/intern/ruby.h break.o: $(hdrdir)/ruby/internal/intern/select.h break.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +break.o: $(hdrdir)/ruby/internal/intern/set.h break.o: $(hdrdir)/ruby/internal/intern/signal.h break.o: $(hdrdir)/ruby/internal/intern/sprintf.h break.o: $(hdrdir)/ruby/internal/intern/string.h @@ -288,6 +289,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h @@ -448,6 +450,7 @@ yield.o: $(hdrdir)/ruby/internal/intern/re.h yield.o: $(hdrdir)/ruby/internal/intern/ruby.h yield.o: $(hdrdir)/ruby/internal/intern/select.h yield.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +yield.o: $(hdrdir)/ruby/internal/intern/set.h yield.o: $(hdrdir)/ruby/internal/intern/signal.h yield.o: $(hdrdir)/ruby/internal/intern/sprintf.h yield.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/load/dot.dot/depend b/ext/-test-/load/dot.dot/depend index f9be79e957..339837d183 100644 --- a/ext/-test-/load/dot.dot/depend +++ b/ext/-test-/load/dot.dot/depend @@ -128,6 +128,7 @@ dot.dot.o: $(hdrdir)/ruby/internal/intern/re.h dot.dot.o: $(hdrdir)/ruby/internal/intern/ruby.h dot.dot.o: $(hdrdir)/ruby/internal/intern/select.h dot.dot.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +dot.dot.o: $(hdrdir)/ruby/internal/intern/set.h dot.dot.o: $(hdrdir)/ruby/internal/intern/signal.h dot.dot.o: $(hdrdir)/ruby/internal/intern/sprintf.h dot.dot.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/load/protect/depend b/ext/-test-/load/protect/depend index 324c17237a..c76c6f88ed 100644 --- a/ext/-test-/load/protect/depend +++ b/ext/-test-/load/protect/depend @@ -128,6 +128,7 @@ protect.o: $(hdrdir)/ruby/internal/intern/re.h protect.o: $(hdrdir)/ruby/internal/intern/ruby.h protect.o: $(hdrdir)/ruby/internal/intern/select.h protect.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +protect.o: $(hdrdir)/ruby/internal/intern/set.h protect.o: $(hdrdir)/ruby/internal/intern/signal.h protect.o: $(hdrdir)/ruby/internal/intern/sprintf.h protect.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/load/resolve_symbol_resolver/depend b/ext/-test-/load/resolve_symbol_resolver/depend index 0849e79060..f422898b69 100644 --- a/ext/-test-/load/resolve_symbol_resolver/depend +++ b/ext/-test-/load/resolve_symbol_resolver/depend @@ -128,6 +128,7 @@ resolve_symbol_resolver.o: $(hdrdir)/ruby/internal/intern/re.h resolve_symbol_resolver.o: $(hdrdir)/ruby/internal/intern/ruby.h resolve_symbol_resolver.o: $(hdrdir)/ruby/internal/intern/select.h resolve_symbol_resolver.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +resolve_symbol_resolver.o: $(hdrdir)/ruby/internal/intern/set.h resolve_symbol_resolver.o: $(hdrdir)/ruby/internal/intern/signal.h resolve_symbol_resolver.o: $(hdrdir)/ruby/internal/intern/sprintf.h resolve_symbol_resolver.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/load/resolve_symbol_target/depend b/ext/-test-/load/resolve_symbol_target/depend index 45116b5601..aa0b5327be 100644 --- a/ext/-test-/load/resolve_symbol_target/depend +++ b/ext/-test-/load/resolve_symbol_target/depend @@ -128,6 +128,7 @@ resolve_symbol_target.o: $(hdrdir)/ruby/internal/intern/re.h resolve_symbol_target.o: $(hdrdir)/ruby/internal/intern/ruby.h resolve_symbol_target.o: $(hdrdir)/ruby/internal/intern/select.h resolve_symbol_target.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +resolve_symbol_target.o: $(hdrdir)/ruby/internal/intern/set.h resolve_symbol_target.o: $(hdrdir)/ruby/internal/intern/signal.h resolve_symbol_target.o: $(hdrdir)/ruby/internal/intern/sprintf.h resolve_symbol_target.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/load/stringify_symbols/depend b/ext/-test-/load/stringify_symbols/depend index fbb5c625c8..2d4d79a7b7 100644 --- a/ext/-test-/load/stringify_symbols/depend +++ b/ext/-test-/load/stringify_symbols/depend @@ -128,6 +128,7 @@ stringify_symbols.o: $(hdrdir)/ruby/internal/intern/re.h stringify_symbols.o: $(hdrdir)/ruby/internal/intern/ruby.h stringify_symbols.o: $(hdrdir)/ruby/internal/intern/select.h stringify_symbols.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +stringify_symbols.o: $(hdrdir)/ruby/internal/intern/set.h stringify_symbols.o: $(hdrdir)/ruby/internal/intern/signal.h stringify_symbols.o: $(hdrdir)/ruby/internal/intern/sprintf.h stringify_symbols.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/load/stringify_target/depend b/ext/-test-/load/stringify_target/depend index 949dd853f9..c66575d4e4 100644 --- a/ext/-test-/load/stringify_target/depend +++ b/ext/-test-/load/stringify_target/depend @@ -128,6 +128,7 @@ stringify_target.o: $(hdrdir)/ruby/internal/intern/re.h stringify_target.o: $(hdrdir)/ruby/internal/intern/ruby.h stringify_target.o: $(hdrdir)/ruby/internal/intern/select.h stringify_target.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +stringify_target.o: $(hdrdir)/ruby/internal/intern/set.h stringify_target.o: $(hdrdir)/ruby/internal/intern/signal.h stringify_target.o: $(hdrdir)/ruby/internal/intern/sprintf.h stringify_target.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/marshal/compat/depend b/ext/-test-/marshal/compat/depend index 8bcd9f8b5e..36b9235c23 100644 --- a/ext/-test-/marshal/compat/depend +++ b/ext/-test-/marshal/compat/depend @@ -128,6 +128,7 @@ usrcompat.o: $(hdrdir)/ruby/internal/intern/re.h usrcompat.o: $(hdrdir)/ruby/internal/intern/ruby.h usrcompat.o: $(hdrdir)/ruby/internal/intern/select.h usrcompat.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +usrcompat.o: $(hdrdir)/ruby/internal/intern/set.h usrcompat.o: $(hdrdir)/ruby/internal/intern/signal.h usrcompat.o: $(hdrdir)/ruby/internal/intern/sprintf.h usrcompat.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/marshal/internal_ivar/depend b/ext/-test-/marshal/internal_ivar/depend index f8be031efc..a2e093d809 100644 --- a/ext/-test-/marshal/internal_ivar/depend +++ b/ext/-test-/marshal/internal_ivar/depend @@ -128,6 +128,7 @@ internal_ivar.o: $(hdrdir)/ruby/internal/intern/re.h internal_ivar.o: $(hdrdir)/ruby/internal/intern/ruby.h internal_ivar.o: $(hdrdir)/ruby/internal/intern/select.h internal_ivar.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +internal_ivar.o: $(hdrdir)/ruby/internal/intern/set.h internal_ivar.o: $(hdrdir)/ruby/internal/intern/signal.h internal_ivar.o: $(hdrdir)/ruby/internal/intern/sprintf.h internal_ivar.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/marshal/usr/depend b/ext/-test-/marshal/usr/depend index 09e8207d3a..5ffb8c58de 100644 --- a/ext/-test-/marshal/usr/depend +++ b/ext/-test-/marshal/usr/depend @@ -128,6 +128,7 @@ usrmarshal.o: $(hdrdir)/ruby/internal/intern/re.h usrmarshal.o: $(hdrdir)/ruby/internal/intern/ruby.h usrmarshal.o: $(hdrdir)/ruby/internal/intern/select.h usrmarshal.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +usrmarshal.o: $(hdrdir)/ruby/internal/intern/set.h usrmarshal.o: $(hdrdir)/ruby/internal/intern/signal.h usrmarshal.o: $(hdrdir)/ruby/internal/intern/sprintf.h usrmarshal.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/memory_view/depend b/ext/-test-/memory_view/depend index 0c92fc1236..a6ffd76f45 100644 --- a/ext/-test-/memory_view/depend +++ b/ext/-test-/memory_view/depend @@ -128,6 +128,7 @@ memory_view.o: $(hdrdir)/ruby/internal/intern/re.h memory_view.o: $(hdrdir)/ruby/internal/intern/ruby.h memory_view.o: $(hdrdir)/ruby/internal/intern/select.h memory_view.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +memory_view.o: $(hdrdir)/ruby/internal/intern/set.h memory_view.o: $(hdrdir)/ruby/internal/intern/signal.h memory_view.o: $(hdrdir)/ruby/internal/intern/sprintf.h memory_view.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/method/depend b/ext/-test-/method/depend index dce2a815a4..95745b3dae 100644 --- a/ext/-test-/method/depend +++ b/ext/-test-/method/depend @@ -128,6 +128,7 @@ arity.o: $(hdrdir)/ruby/internal/intern/re.h arity.o: $(hdrdir)/ruby/internal/intern/ruby.h arity.o: $(hdrdir)/ruby/internal/intern/select.h arity.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +arity.o: $(hdrdir)/ruby/internal/intern/set.h arity.o: $(hdrdir)/ruby/internal/intern/signal.h arity.o: $(hdrdir)/ruby/internal/intern/sprintf.h arity.o: $(hdrdir)/ruby/internal/intern/string.h @@ -288,6 +289,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/notimplement/depend b/ext/-test-/notimplement/depend index 84517a9c15..69c970b6f2 100644 --- a/ext/-test-/notimplement/depend +++ b/ext/-test-/notimplement/depend @@ -128,6 +128,7 @@ bug.o: $(hdrdir)/ruby/internal/intern/re.h bug.o: $(hdrdir)/ruby/internal/intern/ruby.h bug.o: $(hdrdir)/ruby/internal/intern/select.h bug.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +bug.o: $(hdrdir)/ruby/internal/intern/set.h bug.o: $(hdrdir)/ruby/internal/intern/signal.h bug.o: $(hdrdir)/ruby/internal/intern/sprintf.h bug.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/num2int/depend b/ext/-test-/num2int/depend index 5550033be7..75536363ac 100644 --- a/ext/-test-/num2int/depend +++ b/ext/-test-/num2int/depend @@ -128,6 +128,7 @@ num2int.o: $(hdrdir)/ruby/internal/intern/re.h num2int.o: $(hdrdir)/ruby/internal/intern/ruby.h num2int.o: $(hdrdir)/ruby/internal/intern/select.h num2int.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +num2int.o: $(hdrdir)/ruby/internal/intern/set.h num2int.o: $(hdrdir)/ruby/internal/intern/signal.h num2int.o: $(hdrdir)/ruby/internal/intern/sprintf.h num2int.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/path_to_class/depend b/ext/-test-/path_to_class/depend index a1657c9574..e535058e09 100644 --- a/ext/-test-/path_to_class/depend +++ b/ext/-test-/path_to_class/depend @@ -128,6 +128,7 @@ path_to_class.o: $(hdrdir)/ruby/internal/intern/re.h path_to_class.o: $(hdrdir)/ruby/internal/intern/ruby.h path_to_class.o: $(hdrdir)/ruby/internal/intern/select.h path_to_class.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +path_to_class.o: $(hdrdir)/ruby/internal/intern/set.h path_to_class.o: $(hdrdir)/ruby/internal/intern/signal.h path_to_class.o: $(hdrdir)/ruby/internal/intern/sprintf.h path_to_class.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/popen_deadlock/depend b/ext/-test-/popen_deadlock/depend index 1904e64e59..0b8932e8b8 100644 --- a/ext/-test-/popen_deadlock/depend +++ b/ext/-test-/popen_deadlock/depend @@ -128,6 +128,7 @@ infinite_loop_dlsym.o: $(hdrdir)/ruby/internal/intern/re.h infinite_loop_dlsym.o: $(hdrdir)/ruby/internal/intern/ruby.h infinite_loop_dlsym.o: $(hdrdir)/ruby/internal/intern/select.h infinite_loop_dlsym.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +infinite_loop_dlsym.o: $(hdrdir)/ruby/internal/intern/set.h infinite_loop_dlsym.o: $(hdrdir)/ruby/internal/intern/signal.h infinite_loop_dlsym.o: $(hdrdir)/ruby/internal/intern/sprintf.h infinite_loop_dlsym.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/postponed_job/depend b/ext/-test-/postponed_job/depend index 72250896b0..ff567e3921 100644 --- a/ext/-test-/postponed_job/depend +++ b/ext/-test-/postponed_job/depend @@ -129,6 +129,7 @@ postponed_job.o: $(hdrdir)/ruby/internal/intern/re.h postponed_job.o: $(hdrdir)/ruby/internal/intern/ruby.h postponed_job.o: $(hdrdir)/ruby/internal/intern/select.h postponed_job.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +postponed_job.o: $(hdrdir)/ruby/internal/intern/set.h postponed_job.o: $(hdrdir)/ruby/internal/intern/signal.h postponed_job.o: $(hdrdir)/ruby/internal/intern/sprintf.h postponed_job.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/printf/depend b/ext/-test-/printf/depend index 0530df78bf..be895cf769 100644 --- a/ext/-test-/printf/depend +++ b/ext/-test-/printf/depend @@ -138,6 +138,7 @@ printf.o: $(hdrdir)/ruby/internal/intern/re.h printf.o: $(hdrdir)/ruby/internal/intern/ruby.h printf.o: $(hdrdir)/ruby/internal/intern/select.h printf.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +printf.o: $(hdrdir)/ruby/internal/intern/set.h printf.o: $(hdrdir)/ruby/internal/intern/signal.h printf.o: $(hdrdir)/ruby/internal/intern/sprintf.h printf.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/proc/depend b/ext/-test-/proc/depend index 45e12bcd09..97834db0a2 100644 --- a/ext/-test-/proc/depend +++ b/ext/-test-/proc/depend @@ -128,6 +128,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h @@ -288,6 +289,7 @@ receiver.o: $(hdrdir)/ruby/internal/intern/re.h receiver.o: $(hdrdir)/ruby/internal/intern/ruby.h receiver.o: $(hdrdir)/ruby/internal/intern/select.h receiver.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +receiver.o: $(hdrdir)/ruby/internal/intern/set.h receiver.o: $(hdrdir)/ruby/internal/intern/signal.h receiver.o: $(hdrdir)/ruby/internal/intern/sprintf.h receiver.o: $(hdrdir)/ruby/internal/intern/string.h @@ -448,6 +450,7 @@ super.o: $(hdrdir)/ruby/internal/intern/re.h super.o: $(hdrdir)/ruby/internal/intern/ruby.h super.o: $(hdrdir)/ruby/internal/intern/select.h super.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +super.o: $(hdrdir)/ruby/internal/intern/set.h super.o: $(hdrdir)/ruby/internal/intern/signal.h super.o: $(hdrdir)/ruby/internal/intern/sprintf.h super.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/random/depend b/ext/-test-/random/depend index 71f5f6e1e6..380c30fbe4 100644 --- a/ext/-test-/random/depend +++ b/ext/-test-/random/depend @@ -127,6 +127,7 @@ bad_version.o: $(hdrdir)/ruby/internal/intern/re.h bad_version.o: $(hdrdir)/ruby/internal/intern/ruby.h bad_version.o: $(hdrdir)/ruby/internal/intern/select.h bad_version.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +bad_version.o: $(hdrdir)/ruby/internal/intern/set.h bad_version.o: $(hdrdir)/ruby/internal/intern/signal.h bad_version.o: $(hdrdir)/ruby/internal/intern/sprintf.h bad_version.o: $(hdrdir)/ruby/internal/intern/string.h @@ -288,6 +289,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h @@ -447,6 +449,7 @@ loop.o: $(hdrdir)/ruby/internal/intern/re.h loop.o: $(hdrdir)/ruby/internal/intern/ruby.h loop.o: $(hdrdir)/ruby/internal/intern/select.h loop.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +loop.o: $(hdrdir)/ruby/internal/intern/set.h loop.o: $(hdrdir)/ruby/internal/intern/signal.h loop.o: $(hdrdir)/ruby/internal/intern/sprintf.h loop.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/rational/depend b/ext/-test-/rational/depend index 363d779302..56d6ab77d6 100644 --- a/ext/-test-/rational/depend +++ b/ext/-test-/rational/depend @@ -132,6 +132,7 @@ rat.o: $(hdrdir)/ruby/internal/intern/re.h rat.o: $(hdrdir)/ruby/internal/intern/ruby.h rat.o: $(hdrdir)/ruby/internal/intern/select.h rat.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +rat.o: $(hdrdir)/ruby/internal/intern/set.h rat.o: $(hdrdir)/ruby/internal/intern/signal.h rat.o: $(hdrdir)/ruby/internal/intern/sprintf.h rat.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/rb_call_super_kw/depend b/ext/-test-/rb_call_super_kw/depend index 04a0fac12c..bf34323ca7 100644 --- a/ext/-test-/rb_call_super_kw/depend +++ b/ext/-test-/rb_call_super_kw/depend @@ -128,6 +128,7 @@ rb_call_super_kw.o: $(hdrdir)/ruby/internal/intern/re.h rb_call_super_kw.o: $(hdrdir)/ruby/internal/intern/ruby.h rb_call_super_kw.o: $(hdrdir)/ruby/internal/intern/select.h rb_call_super_kw.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +rb_call_super_kw.o: $(hdrdir)/ruby/internal/intern/set.h rb_call_super_kw.o: $(hdrdir)/ruby/internal/intern/signal.h rb_call_super_kw.o: $(hdrdir)/ruby/internal/intern/sprintf.h rb_call_super_kw.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/recursion/depend b/ext/-test-/recursion/depend index 2a65c98b09..b6487eb4df 100644 --- a/ext/-test-/recursion/depend +++ b/ext/-test-/recursion/depend @@ -128,6 +128,7 @@ recursion.o: $(hdrdir)/ruby/internal/intern/re.h recursion.o: $(hdrdir)/ruby/internal/intern/ruby.h recursion.o: $(hdrdir)/ruby/internal/intern/select.h recursion.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +recursion.o: $(hdrdir)/ruby/internal/intern/set.h recursion.o: $(hdrdir)/ruby/internal/intern/signal.h recursion.o: $(hdrdir)/ruby/internal/intern/sprintf.h recursion.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/regexp/depend b/ext/-test-/regexp/depend index 0127a66a2e..5ba1b92f18 100644 --- a/ext/-test-/regexp/depend +++ b/ext/-test-/regexp/depend @@ -128,6 +128,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h @@ -288,6 +289,7 @@ parse_depth_limit.o: $(hdrdir)/ruby/internal/intern/re.h parse_depth_limit.o: $(hdrdir)/ruby/internal/intern/ruby.h parse_depth_limit.o: $(hdrdir)/ruby/internal/intern/select.h parse_depth_limit.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +parse_depth_limit.o: $(hdrdir)/ruby/internal/intern/set.h parse_depth_limit.o: $(hdrdir)/ruby/internal/intern/signal.h parse_depth_limit.o: $(hdrdir)/ruby/internal/intern/sprintf.h parse_depth_limit.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/scan_args/depend b/ext/-test-/scan_args/depend index 922e5bbd5c..ca0fc19238 100644 --- a/ext/-test-/scan_args/depend +++ b/ext/-test-/scan_args/depend @@ -128,6 +128,7 @@ scan_args.o: $(hdrdir)/ruby/internal/intern/re.h scan_args.o: $(hdrdir)/ruby/internal/intern/ruby.h scan_args.o: $(hdrdir)/ruby/internal/intern/select.h scan_args.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +scan_args.o: $(hdrdir)/ruby/internal/intern/set.h scan_args.o: $(hdrdir)/ruby/internal/intern/signal.h scan_args.o: $(hdrdir)/ruby/internal/intern/sprintf.h scan_args.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/st/foreach/depend b/ext/-test-/st/foreach/depend index 36273f8df8..29aab2bb29 100644 --- a/ext/-test-/st/foreach/depend +++ b/ext/-test-/st/foreach/depend @@ -128,6 +128,7 @@ foreach.o: $(hdrdir)/ruby/internal/intern/re.h foreach.o: $(hdrdir)/ruby/internal/intern/ruby.h foreach.o: $(hdrdir)/ruby/internal/intern/select.h foreach.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +foreach.o: $(hdrdir)/ruby/internal/intern/set.h foreach.o: $(hdrdir)/ruby/internal/intern/signal.h foreach.o: $(hdrdir)/ruby/internal/intern/sprintf.h foreach.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/st/numhash/depend b/ext/-test-/st/numhash/depend index a0916183b6..18320d55f5 100644 --- a/ext/-test-/st/numhash/depend +++ b/ext/-test-/st/numhash/depend @@ -128,6 +128,7 @@ numhash.o: $(hdrdir)/ruby/internal/intern/re.h numhash.o: $(hdrdir)/ruby/internal/intern/ruby.h numhash.o: $(hdrdir)/ruby/internal/intern/select.h numhash.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +numhash.o: $(hdrdir)/ruby/internal/intern/set.h numhash.o: $(hdrdir)/ruby/internal/intern/signal.h numhash.o: $(hdrdir)/ruby/internal/intern/sprintf.h numhash.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/st/update/depend b/ext/-test-/st/update/depend index 96ba194df0..247f0efd6b 100644 --- a/ext/-test-/st/update/depend +++ b/ext/-test-/st/update/depend @@ -128,6 +128,7 @@ update.o: $(hdrdir)/ruby/internal/intern/re.h update.o: $(hdrdir)/ruby/internal/intern/ruby.h update.o: $(hdrdir)/ruby/internal/intern/select.h update.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +update.o: $(hdrdir)/ruby/internal/intern/set.h update.o: $(hdrdir)/ruby/internal/intern/signal.h update.o: $(hdrdir)/ruby/internal/intern/sprintf.h update.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/stack/depend b/ext/-test-/stack/depend index 5b852278f0..31571c882e 100644 --- a/ext/-test-/stack/depend +++ b/ext/-test-/stack/depend @@ -139,6 +139,7 @@ stack.o: $(hdrdir)/ruby/internal/intern/re.h stack.o: $(hdrdir)/ruby/internal/intern/ruby.h stack.o: $(hdrdir)/ruby/internal/intern/select.h stack.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +stack.o: $(hdrdir)/ruby/internal/intern/set.h stack.o: $(hdrdir)/ruby/internal/intern/signal.h stack.o: $(hdrdir)/ruby/internal/intern/sprintf.h stack.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/string/depend b/ext/-test-/string/depend index 044b6109ff..de6e775acc 100644 --- a/ext/-test-/string/depend +++ b/ext/-test-/string/depend @@ -139,6 +139,7 @@ capacity.o: $(hdrdir)/ruby/internal/intern/re.h capacity.o: $(hdrdir)/ruby/internal/intern/ruby.h capacity.o: $(hdrdir)/ruby/internal/intern/select.h capacity.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +capacity.o: $(hdrdir)/ruby/internal/intern/set.h capacity.o: $(hdrdir)/ruby/internal/intern/signal.h capacity.o: $(hdrdir)/ruby/internal/intern/sprintf.h capacity.o: $(hdrdir)/ruby/internal/intern/string.h @@ -472,6 +473,7 @@ coderange.o: $(hdrdir)/ruby/internal/intern/re.h coderange.o: $(hdrdir)/ruby/internal/intern/ruby.h coderange.o: $(hdrdir)/ruby/internal/intern/select.h coderange.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +coderange.o: $(hdrdir)/ruby/internal/intern/set.h coderange.o: $(hdrdir)/ruby/internal/intern/signal.h coderange.o: $(hdrdir)/ruby/internal/intern/sprintf.h coderange.o: $(hdrdir)/ruby/internal/intern/string.h @@ -644,6 +646,7 @@ cstr.o: $(hdrdir)/ruby/internal/intern/re.h cstr.o: $(hdrdir)/ruby/internal/intern/ruby.h cstr.o: $(hdrdir)/ruby/internal/intern/select.h cstr.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +cstr.o: $(hdrdir)/ruby/internal/intern/set.h cstr.o: $(hdrdir)/ruby/internal/intern/signal.h cstr.o: $(hdrdir)/ruby/internal/intern/sprintf.h cstr.o: $(hdrdir)/ruby/internal/intern/string.h @@ -809,6 +812,7 @@ ellipsize.o: $(hdrdir)/ruby/internal/intern/re.h ellipsize.o: $(hdrdir)/ruby/internal/intern/ruby.h ellipsize.o: $(hdrdir)/ruby/internal/intern/select.h ellipsize.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ellipsize.o: $(hdrdir)/ruby/internal/intern/set.h ellipsize.o: $(hdrdir)/ruby/internal/intern/signal.h ellipsize.o: $(hdrdir)/ruby/internal/intern/sprintf.h ellipsize.o: $(hdrdir)/ruby/internal/intern/string.h @@ -979,6 +983,7 @@ enc_associate.o: $(hdrdir)/ruby/internal/intern/re.h enc_associate.o: $(hdrdir)/ruby/internal/intern/ruby.h enc_associate.o: $(hdrdir)/ruby/internal/intern/select.h enc_associate.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +enc_associate.o: $(hdrdir)/ruby/internal/intern/set.h enc_associate.o: $(hdrdir)/ruby/internal/intern/signal.h enc_associate.o: $(hdrdir)/ruby/internal/intern/sprintf.h enc_associate.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1151,6 +1156,7 @@ enc_dummy.o: $(hdrdir)/ruby/internal/intern/re.h enc_dummy.o: $(hdrdir)/ruby/internal/intern/ruby.h enc_dummy.o: $(hdrdir)/ruby/internal/intern/select.h enc_dummy.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +enc_dummy.o: $(hdrdir)/ruby/internal/intern/set.h enc_dummy.o: $(hdrdir)/ruby/internal/intern/signal.h enc_dummy.o: $(hdrdir)/ruby/internal/intern/sprintf.h enc_dummy.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1322,6 +1328,7 @@ enc_str_buf_cat.o: $(hdrdir)/ruby/internal/intern/re.h enc_str_buf_cat.o: $(hdrdir)/ruby/internal/intern/ruby.h enc_str_buf_cat.o: $(hdrdir)/ruby/internal/intern/select.h enc_str_buf_cat.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +enc_str_buf_cat.o: $(hdrdir)/ruby/internal/intern/set.h enc_str_buf_cat.o: $(hdrdir)/ruby/internal/intern/signal.h enc_str_buf_cat.o: $(hdrdir)/ruby/internal/intern/sprintf.h enc_str_buf_cat.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1495,6 +1502,7 @@ fstring.o: $(hdrdir)/ruby/internal/intern/re.h fstring.o: $(hdrdir)/ruby/internal/intern/ruby.h fstring.o: $(hdrdir)/ruby/internal/intern/select.h fstring.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +fstring.o: $(hdrdir)/ruby/internal/intern/set.h fstring.o: $(hdrdir)/ruby/internal/intern/signal.h fstring.o: $(hdrdir)/ruby/internal/intern/sprintf.h fstring.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1659,6 +1667,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1819,6 +1828,7 @@ modify.o: $(hdrdir)/ruby/internal/intern/re.h modify.o: $(hdrdir)/ruby/internal/intern/ruby.h modify.o: $(hdrdir)/ruby/internal/intern/select.h modify.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +modify.o: $(hdrdir)/ruby/internal/intern/set.h modify.o: $(hdrdir)/ruby/internal/intern/signal.h modify.o: $(hdrdir)/ruby/internal/intern/sprintf.h modify.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1989,6 +1999,7 @@ new.o: $(hdrdir)/ruby/internal/intern/re.h new.o: $(hdrdir)/ruby/internal/intern/ruby.h new.o: $(hdrdir)/ruby/internal/intern/select.h new.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +new.o: $(hdrdir)/ruby/internal/intern/set.h new.o: $(hdrdir)/ruby/internal/intern/signal.h new.o: $(hdrdir)/ruby/internal/intern/sprintf.h new.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2151,6 +2162,7 @@ nofree.o: $(hdrdir)/ruby/internal/intern/re.h nofree.o: $(hdrdir)/ruby/internal/intern/ruby.h nofree.o: $(hdrdir)/ruby/internal/intern/select.h nofree.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +nofree.o: $(hdrdir)/ruby/internal/intern/set.h nofree.o: $(hdrdir)/ruby/internal/intern/signal.h nofree.o: $(hdrdir)/ruby/internal/intern/sprintf.h nofree.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2320,6 +2332,7 @@ normalize.o: $(hdrdir)/ruby/internal/intern/re.h normalize.o: $(hdrdir)/ruby/internal/intern/ruby.h normalize.o: $(hdrdir)/ruby/internal/intern/select.h normalize.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +normalize.o: $(hdrdir)/ruby/internal/intern/set.h normalize.o: $(hdrdir)/ruby/internal/intern/signal.h normalize.o: $(hdrdir)/ruby/internal/intern/sprintf.h normalize.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2493,6 +2506,7 @@ qsort.o: $(hdrdir)/ruby/internal/intern/re.h qsort.o: $(hdrdir)/ruby/internal/intern/ruby.h qsort.o: $(hdrdir)/ruby/internal/intern/select.h qsort.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +qsort.o: $(hdrdir)/ruby/internal/intern/set.h qsort.o: $(hdrdir)/ruby/internal/intern/signal.h qsort.o: $(hdrdir)/ruby/internal/intern/sprintf.h qsort.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2656,6 +2670,7 @@ rb_interned_str.o: $(hdrdir)/ruby/internal/intern/re.h rb_interned_str.o: $(hdrdir)/ruby/internal/intern/ruby.h rb_interned_str.o: $(hdrdir)/ruby/internal/intern/select.h rb_interned_str.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +rb_interned_str.o: $(hdrdir)/ruby/internal/intern/set.h rb_interned_str.o: $(hdrdir)/ruby/internal/intern/signal.h rb_interned_str.o: $(hdrdir)/ruby/internal/intern/sprintf.h rb_interned_str.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2816,6 +2831,7 @@ rb_str_dup.o: $(hdrdir)/ruby/internal/intern/re.h rb_str_dup.o: $(hdrdir)/ruby/internal/intern/ruby.h rb_str_dup.o: $(hdrdir)/ruby/internal/intern/select.h rb_str_dup.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +rb_str_dup.o: $(hdrdir)/ruby/internal/intern/set.h rb_str_dup.o: $(hdrdir)/ruby/internal/intern/signal.h rb_str_dup.o: $(hdrdir)/ruby/internal/intern/sprintf.h rb_str_dup.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2976,6 +2992,7 @@ set_len.o: $(hdrdir)/ruby/internal/intern/re.h set_len.o: $(hdrdir)/ruby/internal/intern/ruby.h set_len.o: $(hdrdir)/ruby/internal/intern/select.h set_len.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +set_len.o: $(hdrdir)/ruby/internal/intern/set.h set_len.o: $(hdrdir)/ruby/internal/intern/signal.h set_len.o: $(hdrdir)/ruby/internal/intern/sprintf.h set_len.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/struct/depend b/ext/-test-/struct/depend index 951dddd5dd..e2638e4cdf 100644 --- a/ext/-test-/struct/depend +++ b/ext/-test-/struct/depend @@ -128,6 +128,7 @@ data.o: $(hdrdir)/ruby/internal/intern/re.h data.o: $(hdrdir)/ruby/internal/intern/ruby.h data.o: $(hdrdir)/ruby/internal/intern/select.h data.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +data.o: $(hdrdir)/ruby/internal/intern/set.h data.o: $(hdrdir)/ruby/internal/intern/signal.h data.o: $(hdrdir)/ruby/internal/intern/sprintf.h data.o: $(hdrdir)/ruby/internal/intern/string.h @@ -288,6 +289,7 @@ duplicate.o: $(hdrdir)/ruby/internal/intern/re.h duplicate.o: $(hdrdir)/ruby/internal/intern/ruby.h duplicate.o: $(hdrdir)/ruby/internal/intern/select.h duplicate.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +duplicate.o: $(hdrdir)/ruby/internal/intern/set.h duplicate.o: $(hdrdir)/ruby/internal/intern/signal.h duplicate.o: $(hdrdir)/ruby/internal/intern/sprintf.h duplicate.o: $(hdrdir)/ruby/internal/intern/string.h @@ -448,6 +450,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h @@ -608,6 +611,7 @@ len.o: $(hdrdir)/ruby/internal/intern/re.h len.o: $(hdrdir)/ruby/internal/intern/ruby.h len.o: $(hdrdir)/ruby/internal/intern/select.h len.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +len.o: $(hdrdir)/ruby/internal/intern/set.h len.o: $(hdrdir)/ruby/internal/intern/signal.h len.o: $(hdrdir)/ruby/internal/intern/sprintf.h len.o: $(hdrdir)/ruby/internal/intern/string.h @@ -768,6 +772,7 @@ member.o: $(hdrdir)/ruby/internal/intern/re.h member.o: $(hdrdir)/ruby/internal/intern/ruby.h member.o: $(hdrdir)/ruby/internal/intern/select.h member.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +member.o: $(hdrdir)/ruby/internal/intern/set.h member.o: $(hdrdir)/ruby/internal/intern/signal.h member.o: $(hdrdir)/ruby/internal/intern/sprintf.h member.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/symbol/depend b/ext/-test-/symbol/depend index 7c76596fdf..b1d8e1aab6 100644 --- a/ext/-test-/symbol/depend +++ b/ext/-test-/symbol/depend @@ -128,6 +128,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h @@ -288,6 +289,7 @@ type.o: $(hdrdir)/ruby/internal/intern/re.h type.o: $(hdrdir)/ruby/internal/intern/ruby.h type.o: $(hdrdir)/ruby/internal/intern/select.h type.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +type.o: $(hdrdir)/ruby/internal/intern/set.h type.o: $(hdrdir)/ruby/internal/intern/signal.h type.o: $(hdrdir)/ruby/internal/intern/sprintf.h type.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/thread/id/depend b/ext/-test-/thread/id/depend index ea5661a8ba..6b76b31ddc 100644 --- a/ext/-test-/thread/id/depend +++ b/ext/-test-/thread/id/depend @@ -128,6 +128,7 @@ id.o: $(hdrdir)/ruby/internal/intern/re.h id.o: $(hdrdir)/ruby/internal/intern/ruby.h id.o: $(hdrdir)/ruby/internal/intern/select.h id.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +id.o: $(hdrdir)/ruby/internal/intern/set.h id.o: $(hdrdir)/ruby/internal/intern/signal.h id.o: $(hdrdir)/ruby/internal/intern/sprintf.h id.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/thread/instrumentation/depend b/ext/-test-/thread/instrumentation/depend index a37e4d5675..63e1c7e44f 100644 --- a/ext/-test-/thread/instrumentation/depend +++ b/ext/-test-/thread/instrumentation/depend @@ -128,6 +128,7 @@ instrumentation.o: $(hdrdir)/ruby/internal/intern/re.h instrumentation.o: $(hdrdir)/ruby/internal/intern/ruby.h instrumentation.o: $(hdrdir)/ruby/internal/intern/select.h instrumentation.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/set.h instrumentation.o: $(hdrdir)/ruby/internal/intern/signal.h instrumentation.o: $(hdrdir)/ruby/internal/intern/sprintf.h instrumentation.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/thread/lock_native_thread/depend b/ext/-test-/thread/lock_native_thread/depend index 386c95a824..a32843e531 100644 --- a/ext/-test-/thread/lock_native_thread/depend +++ b/ext/-test-/thread/lock_native_thread/depend @@ -127,6 +127,7 @@ lock_native_thread.o: $(hdrdir)/ruby/internal/intern/re.h lock_native_thread.o: $(hdrdir)/ruby/internal/intern/ruby.h lock_native_thread.o: $(hdrdir)/ruby/internal/intern/select.h lock_native_thread.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +lock_native_thread.o: $(hdrdir)/ruby/internal/intern/set.h lock_native_thread.o: $(hdrdir)/ruby/internal/intern/signal.h lock_native_thread.o: $(hdrdir)/ruby/internal/intern/sprintf.h lock_native_thread.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/time/depend b/ext/-test-/time/depend index 5ed791bcc5..e5b05f3113 100644 --- a/ext/-test-/time/depend +++ b/ext/-test-/time/depend @@ -128,6 +128,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h @@ -289,6 +290,7 @@ leap_second.o: $(hdrdir)/ruby/internal/intern/re.h leap_second.o: $(hdrdir)/ruby/internal/intern/ruby.h leap_second.o: $(hdrdir)/ruby/internal/intern/select.h leap_second.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +leap_second.o: $(hdrdir)/ruby/internal/intern/set.h leap_second.o: $(hdrdir)/ruby/internal/intern/signal.h leap_second.o: $(hdrdir)/ruby/internal/intern/sprintf.h leap_second.o: $(hdrdir)/ruby/internal/intern/string.h @@ -453,6 +455,7 @@ new.o: $(hdrdir)/ruby/internal/intern/re.h new.o: $(hdrdir)/ruby/internal/intern/ruby.h new.o: $(hdrdir)/ruby/internal/intern/select.h new.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +new.o: $(hdrdir)/ruby/internal/intern/set.h new.o: $(hdrdir)/ruby/internal/intern/signal.h new.o: $(hdrdir)/ruby/internal/intern/sprintf.h new.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/tracepoint/depend b/ext/-test-/tracepoint/depend index 133663b3bd..014ba83b16 100644 --- a/ext/-test-/tracepoint/depend +++ b/ext/-test-/tracepoint/depend @@ -128,6 +128,7 @@ gc_hook.o: $(hdrdir)/ruby/internal/intern/re.h gc_hook.o: $(hdrdir)/ruby/internal/intern/ruby.h gc_hook.o: $(hdrdir)/ruby/internal/intern/select.h gc_hook.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +gc_hook.o: $(hdrdir)/ruby/internal/intern/set.h gc_hook.o: $(hdrdir)/ruby/internal/intern/signal.h gc_hook.o: $(hdrdir)/ruby/internal/intern/sprintf.h gc_hook.o: $(hdrdir)/ruby/internal/intern/string.h @@ -288,6 +289,7 @@ tracepoint.o: $(hdrdir)/ruby/internal/intern/re.h tracepoint.o: $(hdrdir)/ruby/internal/intern/ruby.h tracepoint.o: $(hdrdir)/ruby/internal/intern/select.h tracepoint.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +tracepoint.o: $(hdrdir)/ruby/internal/intern/set.h tracepoint.o: $(hdrdir)/ruby/internal/intern/signal.h tracepoint.o: $(hdrdir)/ruby/internal/intern/sprintf.h tracepoint.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/typeddata/depend b/ext/-test-/typeddata/depend index 6c0847c82d..b9b4915eee 100644 --- a/ext/-test-/typeddata/depend +++ b/ext/-test-/typeddata/depend @@ -128,6 +128,7 @@ typeddata.o: $(hdrdir)/ruby/internal/intern/re.h typeddata.o: $(hdrdir)/ruby/internal/intern/ruby.h typeddata.o: $(hdrdir)/ruby/internal/intern/select.h typeddata.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +typeddata.o: $(hdrdir)/ruby/internal/intern/set.h typeddata.o: $(hdrdir)/ruby/internal/intern/signal.h typeddata.o: $(hdrdir)/ruby/internal/intern/sprintf.h typeddata.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/vm/depend b/ext/-test-/vm/depend index 422b40a32d..9313f2ee36 100644 --- a/ext/-test-/vm/depend +++ b/ext/-test-/vm/depend @@ -127,6 +127,7 @@ at_exit.o: $(hdrdir)/ruby/internal/intern/re.h at_exit.o: $(hdrdir)/ruby/internal/intern/ruby.h at_exit.o: $(hdrdir)/ruby/internal/intern/select.h at_exit.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +at_exit.o: $(hdrdir)/ruby/internal/intern/set.h at_exit.o: $(hdrdir)/ruby/internal/intern/signal.h at_exit.o: $(hdrdir)/ruby/internal/intern/sprintf.h at_exit.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/-test-/wait/depend b/ext/-test-/wait/depend index 7997a16709..f793d84831 100644 --- a/ext/-test-/wait/depend +++ b/ext/-test-/wait/depend @@ -137,6 +137,7 @@ wait.o: $(hdrdir)/ruby/internal/intern/re.h wait.o: $(hdrdir)/ruby/internal/intern/ruby.h wait.o: $(hdrdir)/ruby/internal/intern/select.h wait.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +wait.o: $(hdrdir)/ruby/internal/intern/set.h wait.o: $(hdrdir)/ruby/internal/intern/signal.h wait.o: $(hdrdir)/ruby/internal/intern/sprintf.h wait.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/.document b/ext/.document index 0efd511a61..0d6c97ff73 100644 --- a/ext/.document +++ b/ext/.document @@ -76,8 +76,6 @@ openssl/ossl_x509name.c openssl/ossl_x509req.c openssl/ossl_x509revoked.c openssl/ossl_x509store.c -pathname/lib -pathname/pathname.c psych/lib psych/psych.c psych/psych_emitter.c diff --git a/ext/cgi/escape/depend b/ext/cgi/escape/depend index 746b47246a..05b59bfdea 100644 --- a/ext/cgi/escape/depend +++ b/ext/cgi/escape/depend @@ -138,6 +138,7 @@ escape.o: $(hdrdir)/ruby/internal/intern/re.h escape.o: $(hdrdir)/ruby/internal/intern/ruby.h escape.o: $(hdrdir)/ruby/internal/intern/select.h escape.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +escape.o: $(hdrdir)/ruby/internal/intern/set.h escape.o: $(hdrdir)/ruby/internal/intern/signal.h escape.o: $(hdrdir)/ruby/internal/intern/sprintf.h escape.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/continuation/depend b/ext/continuation/depend index b40e52e29b..b395d3d4d7 100644 --- a/ext/continuation/depend +++ b/ext/continuation/depend @@ -127,6 +127,7 @@ continuation.o: $(hdrdir)/ruby/internal/intern/re.h continuation.o: $(hdrdir)/ruby/internal/intern/ruby.h continuation.o: $(hdrdir)/ruby/internal/intern/select.h continuation.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +continuation.o: $(hdrdir)/ruby/internal/intern/set.h continuation.o: $(hdrdir)/ruby/internal/intern/signal.h continuation.o: $(hdrdir)/ruby/internal/intern/sprintf.h continuation.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/coverage/depend b/ext/coverage/depend index a7d9b7ff7a..e042bac7c4 100644 --- a/ext/coverage/depend +++ b/ext/coverage/depend @@ -140,6 +140,7 @@ coverage.o: $(hdrdir)/ruby/internal/intern/re.h coverage.o: $(hdrdir)/ruby/internal/intern/ruby.h coverage.o: $(hdrdir)/ruby/internal/intern/select.h coverage.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +coverage.o: $(hdrdir)/ruby/internal/intern/set.h coverage.o: $(hdrdir)/ruby/internal/intern/signal.h coverage.o: $(hdrdir)/ruby/internal/intern/sprintf.h coverage.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/date/date_core.c b/ext/date/date_core.c index b80d948b00..dbee067f6b 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -1599,7 +1599,7 @@ m_ajd(union DateData *x) if (simple_dat_p(x)) { r = m_real_jd(x); - if (FIXNUM_P(r) && FIX2LONG(r) <= (FIXNUM_MAX / 2)) { + if (FIXNUM_P(r) && FIX2LONG(r) <= (FIXNUM_MAX / 2) && FIX2LONG(r) >= (FIXNUM_MIN + 1) / 2) { long ir = FIX2LONG(r); ir = ir * 2 - 1; return rb_rational_new2(LONG2FIX(ir), INT2FIX(2)); @@ -3878,7 +3878,6 @@ static VALUE rt_complete_frags(VALUE klass, VALUE hash) { static VALUE tab = Qnil; - int g; long e; VALUE k, a, d; @@ -3975,9 +3974,13 @@ rt_complete_frags(VALUE klass, VALUE hash) rb_gc_register_mark_object(tab); } - { - long i, eno = 0, idx = 0; + k = Qnil; + { + long i, eno = 0; + VALUE t = Qnil; + + e = 0; for (i = 0; i < RARRAY_LEN(tab); i++) { VALUE x, a; @@ -3992,23 +3995,20 @@ rt_complete_frags(VALUE klass, VALUE hash) n++; if (n > eno) { eno = n; - idx = i; + t = x; } } } - if (eno == 0) - g = 0; - else { - g = 1; - k = RARRAY_AREF(RARRAY_AREF(tab, idx), 0); - a = RARRAY_AREF(RARRAY_AREF(tab, idx), 1); - e = eno; + if (eno > 0) { + k = RARRAY_AREF(t, 0); + a = RARRAY_AREF(t, 1); } + e = eno; } d = Qnil; - if (g && !NIL_P(k) && (RARRAY_LEN(a) - e)) { + if (!NIL_P(k) && (RARRAY_LEN(a) > e)) { if (k == sym("ordinal")) { if (NIL_P(ref_hash("year"))) { if (NIL_P(d)) @@ -4095,7 +4095,7 @@ rt_complete_frags(VALUE klass, VALUE hash) } } - if (g && k == sym("time")) { + if (k == sym("time")) { if (f_le_p(klass, cDateTime)) { if (NIL_P(d)) d = date_s_today(0, (VALUE *)0, cDate); @@ -6936,13 +6936,24 @@ d_lite_eql_p(VALUE self, VALUE other) static VALUE d_lite_hash(VALUE self) { - st_index_t v, h[4]; + st_index_t v, h[5]; + VALUE nth; get_d1(self); - h[0] = m_nth(dat); - h[1] = m_jd(dat); - h[2] = m_df(dat); - h[3] = m_sf(dat); + nth = m_nth(dat); + + if (FIXNUM_P(nth)) { + h[0] = 0; + h[1] = (st_index_t)nth; + } else { + h[0] = 1; + h[1] = (st_index_t)FIX2LONG(rb_hash(nth)); + } + + h[2] = m_jd(dat); + h[3] = m_df(dat); + h[4] = m_sf(dat); + v = rb_memhash(h, sizeof(h)); return ST2FIX(v); } @@ -7517,10 +7528,7 @@ d_lite_marshal_dump_old(VALUE self) m_of_in_day(dat), DBL2NUM(m_sg(dat))); - if (FL_TEST(self, FL_EXIVAR)) { - rb_copy_generic_ivar(a, self); - FL_SET(a, FL_EXIVAR); - } + rb_copy_generic_ivar(a, self); return a; } @@ -7542,10 +7550,8 @@ d_lite_marshal_dump(VALUE self) INT2FIX(m_of(dat)), DBL2NUM(m_sg(dat))); - if (FL_TEST(self, FL_EXIVAR)) { - rb_copy_generic_ivar(a, self); - FL_SET(a, FL_EXIVAR); - } + + rb_copy_generic_ivar(a, self); return a; } @@ -7618,10 +7624,7 @@ d_lite_marshal_load(VALUE self, VALUE a) HAVE_JD | HAVE_DF); } - if (FL_TEST(a, FL_EXIVAR)) { - rb_copy_generic_ivar(self, a); - FL_SET(self, FL_EXIVAR); - } + rb_copy_generic_ivar(self, a); return self; } diff --git a/ext/date/depend b/ext/date/depend index d07f10a593..4fb78149a1 100644 --- a/ext/date/depend +++ b/ext/date/depend @@ -138,6 +138,7 @@ date_core.o: $(hdrdir)/ruby/internal/intern/re.h date_core.o: $(hdrdir)/ruby/internal/intern/ruby.h date_core.o: $(hdrdir)/ruby/internal/intern/select.h date_core.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +date_core.o: $(hdrdir)/ruby/internal/intern/set.h date_core.o: $(hdrdir)/ruby/internal/intern/signal.h date_core.o: $(hdrdir)/ruby/internal/intern/sprintf.h date_core.o: $(hdrdir)/ruby/internal/intern/string.h @@ -313,6 +314,7 @@ date_parse.o: $(hdrdir)/ruby/internal/intern/re.h date_parse.o: $(hdrdir)/ruby/internal/intern/ruby.h date_parse.o: $(hdrdir)/ruby/internal/intern/select.h date_parse.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +date_parse.o: $(hdrdir)/ruby/internal/intern/set.h date_parse.o: $(hdrdir)/ruby/internal/intern/signal.h date_parse.o: $(hdrdir)/ruby/internal/intern/sprintf.h date_parse.o: $(hdrdir)/ruby/internal/intern/string.h @@ -478,6 +480,7 @@ date_strftime.o: $(hdrdir)/ruby/internal/intern/re.h date_strftime.o: $(hdrdir)/ruby/internal/intern/ruby.h date_strftime.o: $(hdrdir)/ruby/internal/intern/select.h date_strftime.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +date_strftime.o: $(hdrdir)/ruby/internal/intern/set.h date_strftime.o: $(hdrdir)/ruby/internal/intern/signal.h date_strftime.o: $(hdrdir)/ruby/internal/intern/sprintf.h date_strftime.o: $(hdrdir)/ruby/internal/intern/string.h @@ -650,6 +653,7 @@ date_strptime.o: $(hdrdir)/ruby/internal/intern/re.h date_strptime.o: $(hdrdir)/ruby/internal/intern/ruby.h date_strptime.o: $(hdrdir)/ruby/internal/intern/select.h date_strptime.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +date_strptime.o: $(hdrdir)/ruby/internal/intern/set.h date_strptime.o: $(hdrdir)/ruby/internal/intern/signal.h date_strptime.o: $(hdrdir)/ruby/internal/intern/sprintf.h date_strptime.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/digest/bubblebabble/depend b/ext/digest/bubblebabble/depend index 0da9c223ee..02d88e960c 100644 --- a/ext/digest/bubblebabble/depend +++ b/ext/digest/bubblebabble/depend @@ -128,6 +128,7 @@ bubblebabble.o: $(hdrdir)/ruby/internal/intern/re.h bubblebabble.o: $(hdrdir)/ruby/internal/intern/ruby.h bubblebabble.o: $(hdrdir)/ruby/internal/intern/select.h bubblebabble.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +bubblebabble.o: $(hdrdir)/ruby/internal/intern/set.h bubblebabble.o: $(hdrdir)/ruby/internal/intern/signal.h bubblebabble.o: $(hdrdir)/ruby/internal/intern/sprintf.h bubblebabble.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/digest/depend b/ext/digest/depend index cb9e8d4813..3eabb1d1d6 100644 --- a/ext/digest/depend +++ b/ext/digest/depend @@ -128,6 +128,7 @@ digest.o: $(hdrdir)/ruby/internal/intern/re.h digest.o: $(hdrdir)/ruby/internal/intern/ruby.h digest.o: $(hdrdir)/ruby/internal/intern/select.h digest.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +digest.o: $(hdrdir)/ruby/internal/intern/set.h digest.o: $(hdrdir)/ruby/internal/intern/signal.h digest.o: $(hdrdir)/ruby/internal/intern/sprintf.h digest.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/digest/md5/depend b/ext/digest/md5/depend index e71915e5b4..d1c25c28c8 100644 --- a/ext/digest/md5/depend +++ b/ext/digest/md5/depend @@ -131,6 +131,7 @@ md5.o: $(hdrdir)/ruby/internal/intern/re.h md5.o: $(hdrdir)/ruby/internal/intern/ruby.h md5.o: $(hdrdir)/ruby/internal/intern/select.h md5.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +md5.o: $(hdrdir)/ruby/internal/intern/set.h md5.o: $(hdrdir)/ruby/internal/intern/signal.h md5.o: $(hdrdir)/ruby/internal/intern/sprintf.h md5.o: $(hdrdir)/ruby/internal/intern/string.h @@ -293,6 +294,7 @@ md5init.o: $(hdrdir)/ruby/internal/intern/re.h md5init.o: $(hdrdir)/ruby/internal/intern/ruby.h md5init.o: $(hdrdir)/ruby/internal/intern/select.h md5init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +md5init.o: $(hdrdir)/ruby/internal/intern/set.h md5init.o: $(hdrdir)/ruby/internal/intern/signal.h md5init.o: $(hdrdir)/ruby/internal/intern/sprintf.h md5init.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/digest/rmd160/depend b/ext/digest/rmd160/depend index 09558ad92b..aec484f7b3 100644 --- a/ext/digest/rmd160/depend +++ b/ext/digest/rmd160/depend @@ -131,6 +131,7 @@ rmd160.o: $(hdrdir)/ruby/internal/intern/re.h rmd160.o: $(hdrdir)/ruby/internal/intern/ruby.h rmd160.o: $(hdrdir)/ruby/internal/intern/select.h rmd160.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +rmd160.o: $(hdrdir)/ruby/internal/intern/set.h rmd160.o: $(hdrdir)/ruby/internal/intern/signal.h rmd160.o: $(hdrdir)/ruby/internal/intern/sprintf.h rmd160.o: $(hdrdir)/ruby/internal/intern/string.h @@ -293,6 +294,7 @@ rmd160init.o: $(hdrdir)/ruby/internal/intern/re.h rmd160init.o: $(hdrdir)/ruby/internal/intern/ruby.h rmd160init.o: $(hdrdir)/ruby/internal/intern/select.h rmd160init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +rmd160init.o: $(hdrdir)/ruby/internal/intern/set.h rmd160init.o: $(hdrdir)/ruby/internal/intern/signal.h rmd160init.o: $(hdrdir)/ruby/internal/intern/sprintf.h rmd160init.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/digest/sha1/depend b/ext/digest/sha1/depend index 827b8a0852..e6bd9d8f73 100644 --- a/ext/digest/sha1/depend +++ b/ext/digest/sha1/depend @@ -131,6 +131,7 @@ sha1.o: $(hdrdir)/ruby/internal/intern/re.h sha1.o: $(hdrdir)/ruby/internal/intern/ruby.h sha1.o: $(hdrdir)/ruby/internal/intern/select.h sha1.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +sha1.o: $(hdrdir)/ruby/internal/intern/set.h sha1.o: $(hdrdir)/ruby/internal/intern/signal.h sha1.o: $(hdrdir)/ruby/internal/intern/sprintf.h sha1.o: $(hdrdir)/ruby/internal/intern/string.h @@ -293,6 +294,7 @@ sha1init.o: $(hdrdir)/ruby/internal/intern/re.h sha1init.o: $(hdrdir)/ruby/internal/intern/ruby.h sha1init.o: $(hdrdir)/ruby/internal/intern/select.h sha1init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +sha1init.o: $(hdrdir)/ruby/internal/intern/set.h sha1init.o: $(hdrdir)/ruby/internal/intern/signal.h sha1init.o: $(hdrdir)/ruby/internal/intern/sprintf.h sha1init.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/digest/sha2/depend b/ext/digest/sha2/depend index af1600d346..2b74776b3e 100644 --- a/ext/digest/sha2/depend +++ b/ext/digest/sha2/depend @@ -131,6 +131,7 @@ sha2.o: $(hdrdir)/ruby/internal/intern/re.h sha2.o: $(hdrdir)/ruby/internal/intern/ruby.h sha2.o: $(hdrdir)/ruby/internal/intern/select.h sha2.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +sha2.o: $(hdrdir)/ruby/internal/intern/set.h sha2.o: $(hdrdir)/ruby/internal/intern/signal.h sha2.o: $(hdrdir)/ruby/internal/intern/sprintf.h sha2.o: $(hdrdir)/ruby/internal/intern/string.h @@ -293,6 +294,7 @@ sha2init.o: $(hdrdir)/ruby/internal/intern/re.h sha2init.o: $(hdrdir)/ruby/internal/intern/ruby.h sha2init.o: $(hdrdir)/ruby/internal/intern/select.h sha2init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +sha2init.o: $(hdrdir)/ruby/internal/intern/set.h sha2init.o: $(hdrdir)/ruby/internal/intern/signal.h sha2init.o: $(hdrdir)/ruby/internal/intern/sprintf.h sha2init.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/erb/escape/escape.c b/ext/erb/escape/escape.c index db2abd9773..2a5903c3b7 100644 --- a/ext/erb/escape/escape.c +++ b/ext/erb/escape/escape.c @@ -89,6 +89,10 @@ erb_escape_html(VALUE self, VALUE str) void Init_escape(void) { +#ifdef HAVE_RB_EXT_RACTOR_SAFE + rb_ext_ractor_safe(true); +#endif + rb_cERB = rb_define_class("ERB", rb_cObject); rb_mEscape = rb_define_module_under(rb_cERB, "Escape"); rb_define_module_function(rb_mEscape, "html_escape", erb_escape_html, 1); diff --git a/ext/erb/escape/extconf.rb b/ext/erb/escape/extconf.rb index 783e8c1f55..b211a9783f 100644 --- a/ext/erb/escape/extconf.rb +++ b/ext/erb/escape/extconf.rb @@ -4,5 +4,6 @@ case RUBY_ENGINE when 'jruby', 'truffleruby' File.write('Makefile', dummy_makefile($srcdir).join) else + have_func("rb_ext_ractor_safe", "ruby.h") create_makefile 'erb/escape' end diff --git a/ext/etc/depend b/ext/etc/depend index 675699b129..77fe56a6bf 100644 --- a/ext/etc/depend +++ b/ext/etc/depend @@ -143,6 +143,7 @@ etc.o: $(hdrdir)/ruby/internal/intern/re.h etc.o: $(hdrdir)/ruby/internal/intern/ruby.h etc.o: $(hdrdir)/ruby/internal/intern/select.h etc.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +etc.o: $(hdrdir)/ruby/internal/intern/set.h etc.o: $(hdrdir)/ruby/internal/intern/signal.h etc.o: $(hdrdir)/ruby/internal/intern/sprintf.h etc.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/etc/etc.c b/ext/etc/etc.c index e2d621547e..66ef8fc9a4 100644 --- a/ext/etc/etc.c +++ b/ext/etc/etc.c @@ -56,7 +56,7 @@ static VALUE sGroup; #endif RUBY_EXTERN char *getlogin(void); -#define RUBY_ETC_VERSION "1.4.5" +#define RUBY_ETC_VERSION "1.4.6" #define SYMBOL_LIT(str) ID2SYM(rb_intern_const(str "")) diff --git a/ext/etc/extconf.rb b/ext/etc/extconf.rb index 3d7cceae40..497303a4fa 100644 --- a/ext/etc/extconf.rb +++ b/ext/etc/extconf.rb @@ -60,7 +60,7 @@ end # TODO: remove when dropping 2.7 support, as exported since 3.0 have_func('rb_deprecate_constant(Qnil, "None")') -have_func("rb_io_descriptor") +have_func("rb_io_descriptor", "ruby/io.h") $distcleanfiles << "constdefs.h" diff --git a/ext/fcntl/depend b/ext/fcntl/depend index 5ed652563b..57ea0f2106 100644 --- a/ext/fcntl/depend +++ b/ext/fcntl/depend @@ -128,6 +128,7 @@ fcntl.o: $(hdrdir)/ruby/internal/intern/re.h fcntl.o: $(hdrdir)/ruby/internal/intern/ruby.h fcntl.o: $(hdrdir)/ruby/internal/intern/select.h fcntl.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +fcntl.o: $(hdrdir)/ruby/internal/intern/set.h fcntl.o: $(hdrdir)/ruby/internal/intern/signal.h fcntl.o: $(hdrdir)/ruby/internal/intern/sprintf.h fcntl.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/io/console/console.c b/ext/io/console/console.c index 80c1cddd5a..3c8bb82083 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -4,7 +4,7 @@ */ static const char *const -IO_CONSOLE_VERSION = "0.8.0"; +IO_CONSOLE_VERSION = "0.8.1"; #include "ruby.h" #include "ruby/io.h" diff --git a/ext/io/console/depend b/ext/io/console/depend index e66b6f4f5d..150a138d4d 100644 --- a/ext/io/console/depend +++ b/ext/io/console/depend @@ -139,6 +139,7 @@ console.o: $(hdrdir)/ruby/internal/intern/re.h console.o: $(hdrdir)/ruby/internal/intern/ruby.h console.o: $(hdrdir)/ruby/internal/intern/select.h console.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +console.o: $(hdrdir)/ruby/internal/intern/set.h console.o: $(hdrdir)/ruby/internal/intern/signal.h console.o: $(hdrdir)/ruby/internal/intern/sprintf.h console.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/io/console/extconf.rb b/ext/io/console/extconf.rb index 4ad7ed6996..dd3d221ae5 100644 --- a/ext/io/console/extconf.rb +++ b/ext/io/console/extconf.rb @@ -10,11 +10,11 @@ have_func("rb_syserr_new_str(0, Qnil)") or abort have_func("rb_interned_str_cstr") -have_func("rb_io_path") -have_func("rb_io_descriptor") -have_func("rb_io_get_write_io") -have_func("rb_io_closed_p") -have_func("rb_io_open_descriptor") +have_func("rb_io_path", "ruby/io.h") +have_func("rb_io_descriptor", "ruby/io.h") +have_func("rb_io_get_write_io", "ruby/io.h") +have_func("rb_io_closed_p", "ruby/io.h") +have_func("rb_io_open_descriptor", "ruby/io.h") have_func("rb_ractor_local_storage_value_newkey") is_wasi = /wasi/ =~ MakeMakefile::RbConfig::CONFIG["platform"] diff --git a/ext/io/nonblock/depend b/ext/io/nonblock/depend index 20a96f1252..e78a05c316 100644 --- a/ext/io/nonblock/depend +++ b/ext/io/nonblock/depend @@ -138,6 +138,7 @@ nonblock.o: $(hdrdir)/ruby/internal/intern/re.h nonblock.o: $(hdrdir)/ruby/internal/intern/ruby.h nonblock.o: $(hdrdir)/ruby/internal/intern/select.h nonblock.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +nonblock.o: $(hdrdir)/ruby/internal/intern/set.h nonblock.o: $(hdrdir)/ruby/internal/intern/signal.h nonblock.o: $(hdrdir)/ruby/internal/intern/sprintf.h nonblock.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/io/nonblock/extconf.rb b/ext/io/nonblock/extconf.rb index a1e6075c9b..505c9e6252 100644 --- a/ext/io/nonblock/extconf.rb +++ b/ext/io/nonblock/extconf.rb @@ -7,7 +7,7 @@ unless RUBY_ENGINE == 'ruby' return end -have_func("rb_io_descriptor") +have_func("rb_io_descriptor", "ruby/io.h") hdr = %w"fcntl.h" if have_macro("O_NONBLOCK", hdr) and diff --git a/ext/io/nonblock/io-nonblock.gemspec b/ext/io/nonblock/io-nonblock.gemspec index 2df8b8e3a6..4f5b8cef6f 100644 --- a/ext/io/nonblock/io-nonblock.gemspec +++ b/ext/io/nonblock/io-nonblock.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "io-nonblock" - spec.version = "0.3.1" + spec.version = "0.3.2" spec.authors = ["Nobu Nakada"] spec.email = ["nobu@ruby-lang.org"] @@ -8,7 +8,7 @@ Gem::Specification.new do |spec| spec.description = %q{Enables non-blocking mode with IO class} spec.homepage = "https://github.com/ruby/io-nonblock" spec.licenses = ["Ruby", "BSD-2-Clause"] - spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0") + spec.required_ruby_version = Gem::Requirement.new(">= 3.0") spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage diff --git a/ext/io/wait/depend b/ext/io/wait/depend index 70317b1497..41d53c1400 100644 --- a/ext/io/wait/depend +++ b/ext/io/wait/depend @@ -1,5 +1,4 @@ # AUTOGENERATED DEPENDENCIES START -# wait.o: $(hdrdir)/ruby/assert.h # not in 2.6 wait.o: $(RUBY_EXTCONF_H) wait.o: $(arch_hdrdir)/ruby/config.h wait.o: $(hdrdir)/ruby.h @@ -139,6 +138,7 @@ wait.o: $(hdrdir)/ruby/internal/intern/re.h wait.o: $(hdrdir)/ruby/internal/intern/ruby.h wait.o: $(hdrdir)/ruby/internal/intern/select.h wait.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +wait.o: $(hdrdir)/ruby/internal/intern/set.h wait.o: $(hdrdir)/ruby/internal/intern/signal.h wait.o: $(hdrdir)/ruby/internal/intern/sprintf.h wait.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/io/wait/extconf.rb b/ext/io/wait/extconf.rb index e63c046187..e97efa179d 100644 --- a/ext/io/wait/extconf.rb +++ b/ext/io/wait/extconf.rb @@ -1,25 +1,21 @@ # frozen_string_literal: false require 'mkmf' -if RUBY_VERSION < "2.6" - File.write("Makefile", dummy_makefile($srcdir).join("")) +target = "io/wait" +have_func("rb_io_wait", "ruby/io.h") +have_func("rb_io_descriptor", "ruby/io.h") +unless macro_defined?("DOSISH", "#include ") + have_header(ioctl_h = "sys/ioctl.h") or ioctl_h = nil + fionread = %w[sys/ioctl.h sys/filio.h sys/socket.h].find do |h| + have_macro("FIONREAD", [h, ioctl_h].compact) + end + if fionread + $defs << "-DFIONREAD_HEADER=\"<#{fionread}>\"" + create_makefile(target) + end else - target = "io/wait" - have_func("rb_io_wait") - have_func("rb_io_descriptor") - unless macro_defined?("DOSISH", "#include ") - have_header(ioctl_h = "sys/ioctl.h") or ioctl_h = nil - fionread = %w[sys/ioctl.h sys/filio.h sys/socket.h].find do |h| - have_macro("FIONREAD", [h, ioctl_h].compact) - end - if fionread - $defs << "-DFIONREAD_HEADER=\"<#{fionread}>\"" - create_makefile(target) - end - else - if have_func("rb_w32_ioctlsocket", "ruby.h") - have_func("rb_w32_is_socket", "ruby.h") - create_makefile(target) - end + if have_func("rb_w32_ioctlsocket", "ruby.h") + have_func("rb_w32_is_socket", "ruby.h") + create_makefile(target) end end diff --git a/ext/io/wait/io-wait.gemspec b/ext/io/wait/io-wait.gemspec index e850e10bf9..1554dcdb30 100644 --- a/ext/io/wait/io-wait.gemspec +++ b/ext/io/wait/io-wait.gemspec @@ -1,4 +1,4 @@ -_VERSION = "0.3.1" +_VERSION = "0.3.2" Gem::Specification.new do |spec| spec.name = "io-wait" @@ -10,6 +10,7 @@ Gem::Specification.new do |spec| spec.description = %q{Waits until IO is readable or writable without blocking.} spec.homepage = "https://github.com/ruby/io-wait" spec.licenses = ["Ruby", "BSD-2-Clause"] + spec.required_ruby_version = Gem::Requirement.new(">= 3.0") spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage diff --git a/ext/io/wait/wait.c b/ext/io/wait/wait.c index 8835670e59..88e6dd2af1 100644 --- a/ext/io/wait/wait.c +++ b/ext/io/wait/wait.c @@ -312,7 +312,7 @@ io_event_from_value(VALUE value) /* * call-seq: * io.wait(events, timeout) -> event mask, false or nil - * io.wait(timeout = nil, mode = :read) -> self, true, or false + * io.wait(*event_symbols[, timeout]) -> self, true, or false * * Waits until the IO becomes ready for the specified events and returns the * subset of events that become ready, or a falsy value when times out. @@ -320,10 +320,14 @@ io_event_from_value(VALUE value) * The events can be a bit mask of +IO::READABLE+, +IO::WRITABLE+ or * +IO::PRIORITY+. * - * Returns a truthy value immediately when buffered data is available. + * Returns an event mask (truthy value) immediately when buffered data is + * available. * - * Optional parameter +mode+ is one of +:read+, +:write+, or - * +:read_write+. + * The second form: if one or more event symbols (+:read+, +:write+, or + * +:read_write+) are passed, the event mask is the bit OR of the bitmask + * corresponding to those symbols. In this form, +timeout+ is optional, the + * order of the arguments is arbitrary, and returns +io+ if any of the + * events is ready. * * You must require 'io/wait' to use this method. */ @@ -360,10 +364,6 @@ io_wait(int argc, VALUE *argv, VALUE io) rb_io_event_t events = 0; int i, return_io = 0; - /* The documented signature for this method is actually incorrect. - * A single timeout is allowed in any position, and multiple symbols can be given. - * Whether this is intentional or not, I don't know, and as such I consider this to - * be a legacy/slow path. */ if (argc != 2 || (RB_SYMBOL_P(argv[0]) || RB_SYMBOL_P(argv[1]))) { /* We'd prefer to return the actual mask, but this form would return the io itself: */ return_io = 1; diff --git a/ext/json/fbuffer/fbuffer.h b/ext/json/fbuffer/fbuffer.h index b8a4e983d6..d32371476c 100644 --- a/ext/json/fbuffer/fbuffer.h +++ b/ext/json/fbuffer/fbuffer.h @@ -36,6 +36,12 @@ typedef unsigned char _Bool; # define MAYBE_UNUSED(x) x #endif +#ifdef RUBY_DEBUG +#ifndef JSON_DEBUG +#define JSON_DEBUG RUBY_DEBUG +#endif +#endif + enum fbuffer_type { FBUFFER_HEAP_ALLOCATED = 0, FBUFFER_STACK_ALLOCATED = 1, @@ -46,6 +52,9 @@ typedef struct FBufferStruct { unsigned long initial_length; unsigned long len; unsigned long capa; +#ifdef JSON_DEBUG + unsigned long requested; +#endif char *ptr; VALUE io; } FBuffer; @@ -74,6 +83,20 @@ static void fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char * fb->ptr = stack_buffer; fb->capa = stack_buffer_size; } +#ifdef JSON_DEBUG + fb->requested = 0; +#endif +} + +static inline void fbuffer_consumed(FBuffer *fb, unsigned long consumed) +{ +#ifdef JSON_DEBUG + if (consumed > fb->requested) { + rb_bug("fbuffer: Out of bound write"); + } + fb->requested = 0; +#endif + fb->len += consumed; } static void fbuffer_free(FBuffer *fb) @@ -137,6 +160,10 @@ static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested) static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested) { +#ifdef JSON_DEBUG + fb->requested = requested; +#endif + if (RB_UNLIKELY(requested > fb->capa - fb->len)) { fbuffer_do_inc_capa(fb, requested); } @@ -147,15 +174,22 @@ static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len) if (len > 0) { fbuffer_inc_capa(fb, len); MEMCPY(fb->ptr + fb->len, newstr, char, len); - fb->len += len; + fbuffer_consumed(fb, len); } } /* Appends a character into a buffer. The buffer needs to have sufficient capacity, via fbuffer_inc_capa(...). */ static inline void fbuffer_append_reserved_char(FBuffer *fb, char chr) { +#ifdef JSON_DEBUG + if (fb->requested < 1) { + rb_bug("fbuffer: unreserved write"); + } + fb->requested--; +#endif + fb->ptr[fb->len] = chr; - fb->len += 1; + fb->len++; } static void fbuffer_append_str(FBuffer *fb, VALUE str) @@ -172,7 +206,7 @@ static inline void fbuffer_append_char(FBuffer *fb, char newchr) { fbuffer_inc_capa(fb, 1); *(fb->ptr + fb->len) = newchr; - fb->len++; + fbuffer_consumed(fb, 1); } static inline char *fbuffer_cursor(FBuffer *fb) @@ -182,7 +216,7 @@ static inline char *fbuffer_cursor(FBuffer *fb) static inline void fbuffer_advance_to(FBuffer *fb, char *end) { - fb->len = end - fb->ptr; + fbuffer_consumed(fb, (end - fb->ptr) - fb->len); } /* diff --git a/ext/json/generator/depend b/ext/json/generator/depend index fb3b038365..aee4ab94eb 100644 --- a/ext/json/generator/depend +++ b/ext/json/generator/depend @@ -142,6 +142,7 @@ generator.o: $(hdrdir)/ruby/internal/intern/re.h generator.o: $(hdrdir)/ruby/internal/intern/ruby.h generator.o: $(hdrdir)/ruby/internal/intern/select.h generator.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +generator.o: $(hdrdir)/ruby/internal/intern/set.h generator.o: $(hdrdir)/ruby/internal/intern/signal.h generator.o: $(hdrdir)/ruby/internal/intern/sprintf.h generator.o: $(hdrdir)/ruby/internal/intern/string.h @@ -177,8 +178,8 @@ generator.o: $(hdrdir)/ruby/ruby.h generator.o: $(hdrdir)/ruby/st.h generator.o: $(hdrdir)/ruby/subst.h generator.o: $(srcdir)/../fbuffer/fbuffer.h +generator.o: $(srcdir)/../simd/simd.h generator.o: $(srcdir)/../vendor/fpconv.c generator.o: $(srcdir)/../vendor/jeaiii-ltoa.h generator.o: generator.c -generator.o: simd.h # AUTOGENERATED DEPENDENCIES END diff --git a/ext/json/generator/extconf.rb b/ext/json/generator/extconf.rb index 60372ee558..fb9afd07f7 100644 --- a/ext/json/generator/extconf.rb +++ b/ext/json/generator/extconf.rb @@ -6,33 +6,10 @@ if RUBY_ENGINE == 'truffleruby' else append_cflags("-std=c99") $defs << "-DJSON_GENERATOR" + $defs << "-DJSON_DEBUG" if ENV["JSON_DEBUG"] if enable_config('generator-use-simd', default=!ENV["JSON_DISABLE_SIMD"]) - if RbConfig::CONFIG['host_cpu'] =~ /^(arm.*|aarch64.*)/ - # Try to compile a small program using NEON instructions - if have_header('arm_neon.h') - have_type('uint8x16_t', headers=['arm_neon.h']) && try_compile(<<~'SRC') - #include - int main() { - uint8x16_t test = vdupq_n_u8(32); - return 0; - } - SRC - $defs.push("-DJSON_ENABLE_SIMD") - end - end - - if have_header('x86intrin.h') && have_type('__m128i', headers=['x86intrin.h']) && try_compile(<<~'SRC') - #include - int main() { - __m128i test = _mm_set1_epi8(32); - return 0; - } - SRC - $defs.push("-DJSON_ENABLE_SIMD") - end - - have_header('cpuid.h') + load __dir__ + "/../simd/conf.rb" end create_makefile 'json/ext/generator' diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 06ab8010d9..33b1bf349d 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -5,7 +5,7 @@ #include #include -#include "simd.h" +#include "../simd/simd.h" /* ruby api and some helpers */ @@ -304,28 +304,6 @@ static inline FORCE_INLINE unsigned char neon_next_match(search_state *search) return 1; } -// See: https://community.arm.com/arm-community-blogs/b/servers-and-cloud-computing-blog/posts/porting-x86-vector-bitmask-optimizations-to-arm-neon -static inline FORCE_INLINE uint64_t neon_match_mask(uint8x16_t matches) -{ - const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(matches), 4); - const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); - return mask & 0x8888888888888888ull; -} - -static inline FORCE_INLINE uint64_t neon_rules_update(const char *ptr) -{ - uint8x16_t chunk = vld1q_u8((const unsigned char *)ptr); - - // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33 - // https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/ - const uint8x16_t too_low_or_dbl_quote = vcltq_u8(veorq_u8(chunk, vdupq_n_u8(2)), vdupq_n_u8(33)); - - uint8x16_t has_backslash = vceqq_u8(chunk, vdupq_n_u8('\\')); - uint8x16_t needs_escape = vorrq_u8(too_low_or_dbl_quote, has_backslash); - - return neon_match_mask(needs_escape); -} - static inline unsigned char search_escape_basic_neon(search_state *search) { if (RB_UNLIKELY(search->has_matches)) { @@ -333,7 +311,7 @@ static inline unsigned char search_escape_basic_neon(search_state *search) if (search->matches_mask > 0) { return neon_next_match(search); } else { - // neon_next_match will only advance search->ptr up to the last matching character. + // neon_next_match will only advance search->ptr up to the last matching character. // Skip over any characters in the last chunk that occur after the last match. search->has_matches = false; search->ptr = search->chunk_end; @@ -342,69 +320,63 @@ static inline unsigned char search_escape_basic_neon(search_state *search) /* * The code below implements an SIMD-based algorithm to determine if N bytes at a time - * need to be escaped. - * + * need to be escaped. + * * Assume the ptr = "Te\sting!" (the double quotes are included in the string) - * + * * The explanation will be limited to the first 8 bytes of the string for simplicity. However * the vector insructions may work on larger vectors. - * + * * First, we load three constants 'lower_bound', 'backslash' and 'dblquote" in vector registers. - * - * lower_bound: [20 20 20 20 20 20 20 20] - * backslash: [5C 5C 5C 5C 5C 5C 5C 5C] - * dblquote: [22 22 22 22 22 22 22 22] - * - * Next we load the first chunk of the ptr: + * + * lower_bound: [20 20 20 20 20 20 20 20] + * backslash: [5C 5C 5C 5C 5C 5C 5C 5C] + * dblquote: [22 22 22 22 22 22 22 22] + * + * Next we load the first chunk of the ptr: * [22 54 65 5C 73 74 69 6E] (" T e \ s t i n) - * + * * First we check if any byte in chunk is less than 32 (0x20). This returns the following vector * as no bytes are less than 32 (0x20): * [0 0 0 0 0 0 0 0] - * + * * Next, we check if any byte in chunk is equal to a backslash: * [0 0 0 FF 0 0 0 0] - * + * * Finally we check if any byte in chunk is equal to a double quote: - * [FF 0 0 0 0 0 0 0] - * + * [FF 0 0 0 0 0 0 0] + * * Now we have three vectors where each byte indicates if the corresponding byte in chunk * needs to be escaped. We combine these vectors with a series of logical OR instructions. * This is the needs_escape vector and it is equal to: - * [FF 0 0 FF 0 0 0 0] - * + * [FF 0 0 FF 0 0 0 0] + * * Next we compute the bitwise AND between each byte and 0x1 and compute the horizontal sum of * the values in the vector. This computes how many bytes need to be escaped within this chunk. - * + * * Finally we compute a mask that indicates which bytes need to be escaped. If the mask is 0 then, * no bytes need to be escaped and we can continue to the next chunk. If the mask is not 0 then we * have at least one byte that needs to be escaped. */ - while (search->ptr + sizeof(uint8x16_t) <= search->end) { - uint64_t mask = neon_rules_update(search->ptr); - if (!mask) { - search->ptr += sizeof(uint8x16_t); - continue; - } - search->matches_mask = mask; + if (string_scan_simd_neon(&search->ptr, search->end, &search->matches_mask)) { search->has_matches = true; search->chunk_base = search->ptr; search->chunk_end = search->ptr + sizeof(uint8x16_t); return neon_next_match(search); } - // There are fewer than 16 bytes left. + // There are fewer than 16 bytes left. unsigned long remaining = (search->end - search->ptr); if (remaining >= SIMD_MINIMUM_THRESHOLD) { char *s = copy_remaining_bytes(search, sizeof(uint8x16_t), remaining); - uint64_t mask = neon_rules_update(s); + uint64_t mask = compute_chunk_mask_neon(s); if (!mask) { - // Nothing to escape, ensure search_flush doesn't do anything by setting + // Nothing to escape, ensure search_flush doesn't do anything by setting // search->cursor to search->ptr. - search->buffer->len += remaining; + fbuffer_consumed(search->buffer, remaining); search->ptr = search->end; search->cursor = search->end; return 0; @@ -428,11 +400,6 @@ static inline unsigned char search_escape_basic_neon(search_state *search) #ifdef HAVE_SIMD_SSE2 -#define _mm_cmpge_epu8(a, b) _mm_cmpeq_epi8(_mm_max_epu8(a, b), a) -#define _mm_cmple_epu8(a, b) _mm_cmpge_epu8(b, a) -#define _mm_cmpgt_epu8(a, b) _mm_xor_si128(_mm_cmple_epu8(a, b), _mm_set1_epi8(-1)) -#define _mm_cmplt_epu8(a, b) _mm_cmpgt_epu8(b, a) - static inline FORCE_INLINE unsigned char sse2_next_match(search_state *search) { int mask = search->matches_mask; @@ -457,18 +424,6 @@ static inline FORCE_INLINE unsigned char sse2_next_match(search_state *search) #define TARGET_SSE2 #endif -static inline TARGET_SSE2 FORCE_INLINE int sse2_update(const char *ptr) -{ - __m128i chunk = _mm_loadu_si128((__m128i const*)ptr); - - // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33 - // https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/ - __m128i too_low_or_dbl_quote = _mm_cmplt_epu8(_mm_xor_si128(chunk, _mm_set1_epi8(2)), _mm_set1_epi8(33)); - __m128i has_backslash = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\\')); - __m128i needs_escape = _mm_or_si128(too_low_or_dbl_quote, has_backslash); - return _mm_movemask_epi8(needs_escape); -} - static inline TARGET_SSE2 FORCE_INLINE unsigned char search_escape_basic_sse2(search_state *search) { if (RB_UNLIKELY(search->has_matches)) { @@ -476,7 +431,7 @@ static inline TARGET_SSE2 FORCE_INLINE unsigned char search_escape_basic_sse2(se if (search->matches_mask > 0) { return sse2_next_match(search); } else { - // sse2_next_match will only advance search->ptr up to the last matching character. + // sse2_next_match will only advance search->ptr up to the last matching character. // Skip over any characters in the last chunk that occur after the last match. search->has_matches = false; if (RB_UNLIKELY(search->chunk_base + sizeof(__m128i) >= search->end)) { @@ -487,31 +442,24 @@ static inline TARGET_SSE2 FORCE_INLINE unsigned char search_escape_basic_sse2(se } } - while (search->ptr + sizeof(__m128i) <= search->end) { - int needs_escape_mask = sse2_update(search->ptr); - - if (needs_escape_mask == 0) { - search->ptr += sizeof(__m128i); - continue; - } - + if (string_scan_simd_sse2(&search->ptr, search->end, &search->matches_mask)) { search->has_matches = true; - search->matches_mask = needs_escape_mask; search->chunk_base = search->ptr; + search->chunk_end = search->ptr + sizeof(__m128i); return sse2_next_match(search); } - // There are fewer than 16 bytes left. + // There are fewer than 16 bytes left. unsigned long remaining = (search->end - search->ptr); if (remaining >= SIMD_MINIMUM_THRESHOLD) { char *s = copy_remaining_bytes(search, sizeof(__m128i), remaining); - int needs_escape_mask = sse2_update(s); + int needs_escape_mask = compute_chunk_mask_sse2(s); if (needs_escape_mask == 0) { - // Nothing to escape, ensure search_flush doesn't do anything by setting + // Nothing to escape, ensure search_flush doesn't do anything by setting // search->cursor to search->ptr. - search->buffer->len += remaining; + fbuffer_consumed(search->buffer, remaining); search->ptr = search->end; search->cursor = search->end; return 0; @@ -638,7 +586,8 @@ static inline unsigned char search_ascii_only_escape(search_state *search, const return 0; } -static inline void full_escape_UTF8_char(search_state *search, unsigned char ch_len) { +static inline void full_escape_UTF8_char(search_state *search, unsigned char ch_len) +{ const unsigned char ch = (unsigned char)*search->ptr; switch (ch_len) { case 1: { @@ -668,7 +617,7 @@ static inline void full_escape_UTF8_char(search_state *search, unsigned char ch_ uint32_t wchar = 0; - switch(ch_len) { + switch (ch_len) { case 2: wchar = ch & 0x1F; break; @@ -828,7 +777,8 @@ static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self) * _state_ is a JSON::State object, that can also be used to configure the * produced JSON string output further. */ -static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self) { +static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self) +{ rb_check_arity(argc, 0, 1); VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); return cState_partial_generate(Vstate, self, generate_json_array, Qfalse); @@ -890,7 +840,8 @@ static VALUE mFloat_to_json(int argc, VALUE *argv, VALUE self) * * Extends _modul_ with the String::Extend module. */ -static VALUE mString_included_s(VALUE self, VALUE modul) { +static VALUE mString_included_s(VALUE self, VALUE modul) +{ VALUE result = rb_funcall(modul, i_extend, 1, mString_Extend); rb_call_super(1, &modul); return result; @@ -1135,7 +1086,7 @@ json_object_i(VALUE key, VALUE val, VALUE _arg) } VALUE key_to_s; - switch(rb_type(key)) { + switch (rb_type(key)) { case T_STRING: if (RB_LIKELY(RBASIC_CLASS(key) == rb_cString)) { key_to_s = key; @@ -1219,7 +1170,7 @@ static void generate_json_array(FBuffer *buffer, struct generate_json_data *data fbuffer_append_char(buffer, '['); if (RB_UNLIKELY(data->state->array_nl)) fbuffer_append_str(buffer, data->state->array_nl); - for(i = 0; i < RARRAY_LEN(obj); i++) { + for (i = 0; i < RARRAY_LEN(obj); i++) { if (i > 0) { fbuffer_append_char(buffer, ','); if (RB_UNLIKELY(data->state->array_nl)) fbuffer_append_str(buffer, data->state->array_nl); @@ -1304,7 +1255,7 @@ static void generate_json_string(FBuffer *buffer, struct generate_json_data *dat search.chunk_base = NULL; #endif /* HAVE_SIMD */ - switch(rb_enc_str_coderange(obj)) { + switch (rb_enc_str_coderange(obj)) { case ENC_CODERANGE_7BIT: case ENC_CODERANGE_VALID: if (RB_UNLIKELY(data->state->ascii_only)) { @@ -1406,17 +1357,16 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data } /* This implementation writes directly into the buffer. We reserve - * the 24 characters that fpconv_dtoa states as its maximum, plus - * 2 more characters for the potential ".0" suffix. + * the 28 characters that fpconv_dtoa states as its maximum. */ - fbuffer_inc_capa(buffer, 26); + fbuffer_inc_capa(buffer, 28); char* d = buffer->ptr + buffer->len; int len = fpconv_dtoa(value, d); /* fpconv_dtoa converts a float to its shortest string representation, * but it adds a ".0" if this is a plain integer. */ - buffer->len += len; + fbuffer_consumed(buffer, len); } static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *data, VALUE obj) @@ -2169,7 +2119,7 @@ void Init_generator(void) rb_require("json/ext/generator/state"); - switch(find_simd_implementation()) { + switch (find_simd_implementation()) { #ifdef HAVE_SIMD #ifdef HAVE_SIMD_NEON case SIMD_NEON: diff --git a/ext/json/generator/simd.h b/ext/json/generator/simd.h deleted file mode 100644 index b12890cb09..0000000000 --- a/ext/json/generator/simd.h +++ /dev/null @@ -1,112 +0,0 @@ -typedef enum { - SIMD_NONE, - SIMD_NEON, - SIMD_SSE2 -} SIMD_Implementation; - -#ifdef JSON_ENABLE_SIMD - -#ifdef __clang__ - #if __has_builtin(__builtin_ctzll) - #define HAVE_BUILTIN_CTZLL 1 - #else - #define HAVE_BUILTIN_CTZLL 0 - #endif -#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) - #define HAVE_BUILTIN_CTZLL 1 -#else - #define HAVE_BUILTIN_CTZLL 0 -#endif - -static inline uint32_t trailing_zeros64(uint64_t input) { -#if HAVE_BUILTIN_CTZLL - return __builtin_ctzll(input); -#else - uint32_t trailing_zeros = 0; - uint64_t temp = input; - while ((temp & 1) == 0 && temp > 0) { - trailing_zeros++; - temp >>= 1; - } - return trailing_zeros; -#endif -} - -static inline int trailing_zeros(int input) { - #if HAVE_BUILTIN_CTZLL - return __builtin_ctz(input); - #else - int trailing_zeros = 0; - int temp = input; - while ((temp & 1) == 0 && temp > 0) { - trailing_zeros++; - temp >>= 1; - } - return trailing_zeros; - #endif -} - -#define SIMD_MINIMUM_THRESHOLD 6 - -#if defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(__aarch64__) || defined(_M_ARM64) -#include - -#define FIND_SIMD_IMPLEMENTATION_DEFINED 1 -static SIMD_Implementation find_simd_implementation(void) { - return SIMD_NEON; -} - -#define HAVE_SIMD 1 -#define HAVE_SIMD_NEON 1 - -uint8x16x4_t load_uint8x16_4(const unsigned char *table) { - uint8x16x4_t tab; - tab.val[0] = vld1q_u8(table); - tab.val[1] = vld1q_u8(table+16); - tab.val[2] = vld1q_u8(table+32); - tab.val[3] = vld1q_u8(table+48); - return tab; -} - -#endif /* ARM Neon Support.*/ - -#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) - -#ifdef HAVE_X86INTRIN_H -#include - -#define HAVE_SIMD 1 -#define HAVE_SIMD_SSE2 1 - -#ifdef HAVE_CPUID_H -#define FIND_SIMD_IMPLEMENTATION_DEFINED 1 - -#include -#endif /* HAVE_CPUID_H */ - -static SIMD_Implementation find_simd_implementation(void) { - -#if defined(__GNUC__ ) || defined(__clang__) -#ifdef __GNUC__ - __builtin_cpu_init(); -#endif /* __GNUC__ */ - - // TODO Revisit. I think the SSE version now only uses SSE2 instructions. - if (__builtin_cpu_supports("sse2")) { - return SIMD_SSE2; - } -#endif /* __GNUC__ || __clang__*/ - - return SIMD_NONE; -} - -#endif /* HAVE_X86INTRIN_H */ -#endif /* X86_64 Support */ - -#endif /* JSON_ENABLE_SIMD */ - -#ifndef FIND_SIMD_IMPLEMENTATION_DEFINED -static SIMD_Implementation find_simd_implementation(void) { - return SIMD_NONE; -} -#endif diff --git a/ext/json/json.gemspec b/ext/json/json.gemspec index 943c78aab9..5575731025 100644 --- a/ext/json/json.gemspec +++ b/ext/json/json.gemspec @@ -44,15 +44,14 @@ spec = Gem::Specification.new do |s| "LEGAL", "README.md", "json.gemspec", - *Dir["lib/**/*.rb"], - ] + ] + Dir.glob("lib/**/*.rb", base: File.expand_path("..", __FILE__)) if java_ext s.platform = 'java' s.files += Dir["lib/json/ext/**/*.jar"] else s.extensions = Dir["ext/json/**/extconf.rb"] - s.files += Dir["ext/json/**/*.{c,h}"] + s.files += Dir["ext/json/**/*.{c,h,rb}"] end end diff --git a/ext/json/lib/json.rb b/ext/json/lib/json.rb index dfd9b7dfc2..735f238066 100644 --- a/ext/json/lib/json.rb +++ b/ext/json/lib/json.rb @@ -127,6 +127,24 @@ require 'json/common' # # --- # +# Option +allow_duplicate_key+ specifies whether duplicate keys in objects +# should be ignored or cause an error to be raised: +# +# When not specified: +# # The last value is used and a deprecation warning emitted. +# JSON.parse('{"a": 1, "a":2}') => {"a" => 2} +# # waring: detected duplicate keys in JSON object. +# # This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true` +# +# When set to `+true+` +# # The last value is used. +# JSON.parse('{"a": 1, "a":2}') => {"a" => 2} +# +# When set to `+false+`, the future default: +# JSON.parse('{"a": 1, "a":2}') => duplicate key at line 1 column 1 (JSON::ParserError) +# +# --- +# # Option +allow_nan+ (boolean) specifies whether to allow # NaN, Infinity, and MinusInfinity in +source+; # defaults to +false+. @@ -143,8 +161,23 @@ require 'json/common' # ruby = JSON.parse(source, {allow_nan: true}) # ruby # => [NaN, Infinity, -Infinity] # +# --- +# +# Option +allow_trailing_comma+ (boolean) specifies whether to allow +# trailing commas in objects and arrays; +# defaults to +false+. +# +# With the default, +false+: +# JSON.parse('[1,]') # unexpected character: ']' at line 1 column 4 (JSON::ParserError) +# +# When enabled: +# JSON.parse('[1,]', allow_trailing_comma: true) # => [1] +# # ====== Output Options # +# Option +freeze+ (boolean) specifies whether the returned objects will be frozen; +# defaults to +false+. +# # Option +symbolize_names+ (boolean) specifies whether returned \Hash keys # should be Symbols; # defaults to +false+ (use Strings). diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb index 7627761b52..486ec62a58 100644 --- a/ext/json/lib/json/common.rb +++ b/ext/json/lib/json/common.rb @@ -172,7 +172,7 @@ module JSON end end self.state = generator::State - const_set :State, self.state + const_set :State, state ensure $VERBOSE = old end @@ -268,7 +268,7 @@ module JSON # to string interpolation. # # Note: no validation is performed on the provided string. It is the - # responsability of the caller to ensure the string contains valid JSON. + # responsibility of the caller to ensure the string contains valid JSON. Fragment = Struct.new(:json) do def initialize(json) unless string = String.try_convert(json) @@ -490,7 +490,7 @@ module JSON # } # def pretty_generate(obj, opts = nil) - return state.generate(obj) if State === opts + return opts.generate(obj) if State === opts options = PRETTY_GENERATE_OPTIONS @@ -1072,7 +1072,7 @@ module ::Kernel end objs.each do |obj| - puts JSON::generate(obj, :allow_nan => true, :max_nesting => false) + puts JSON.generate(obj, :allow_nan => true, :max_nesting => false) end nil end @@ -1087,7 +1087,7 @@ module ::Kernel end objs.each do |obj| - puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false) + puts JSON.pretty_generate(obj, :allow_nan => true, :max_nesting => false) end nil end diff --git a/ext/json/lib/json/ext.rb b/ext/json/lib/json/ext.rb index 1db5ea122c..5bacc5e371 100644 --- a/ext/json/lib/json/ext.rb +++ b/ext/json/lib/json/ext.rb @@ -34,12 +34,12 @@ module JSON if RUBY_ENGINE == 'truffleruby' require 'json/truffle_ruby/generator' - JSON.generator = ::JSON::TruffleRuby::Generator + JSON.generator = JSON::TruffleRuby::Generator else require 'json/ext/generator' JSON.generator = Generator end end - JSON_LOADED = true unless defined?(::JSON::JSON_LOADED) + JSON_LOADED = true unless defined?(JSON::JSON_LOADED) end diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb index 45d2b0a1fb..15ebd12f51 100644 --- a/ext/json/lib/json/version.rb +++ b/ext/json/lib/json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module JSON - VERSION = '2.12.0' + VERSION = '2.12.2' end diff --git a/ext/json/parser/depend b/ext/json/parser/depend index 0c1becfb60..1bb03d3517 100644 --- a/ext/json/parser/depend +++ b/ext/json/parser/depend @@ -141,6 +141,7 @@ parser.o: $(hdrdir)/ruby/internal/intern/re.h parser.o: $(hdrdir)/ruby/internal/intern/ruby.h parser.o: $(hdrdir)/ruby/internal/intern/select.h parser.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +parser.o: $(hdrdir)/ruby/internal/intern/set.h parser.o: $(hdrdir)/ruby/internal/intern/signal.h parser.o: $(hdrdir)/ruby/internal/intern/sprintf.h parser.o: $(hdrdir)/ruby/internal/intern/string.h @@ -174,5 +175,6 @@ parser.o: $(hdrdir)/ruby/ruby.h parser.o: $(hdrdir)/ruby/st.h parser.o: $(hdrdir)/ruby/subst.h parser.o: $(srcdir)/../fbuffer/fbuffer.h +parser.o: $(srcdir)/../simd/simd.h parser.o: parser.c # AUTOGENERATED DEPENDENCIES END diff --git a/ext/json/parser/extconf.rb b/ext/json/parser/extconf.rb index 09c9637788..de5d5758b4 100644 --- a/ext/json/parser/extconf.rb +++ b/ext/json/parser/extconf.rb @@ -1,11 +1,15 @@ # frozen_string_literal: true require 'mkmf' -have_func("rb_enc_interned_str", "ruby.h") # RUBY_VERSION >= 3.0 +have_func("rb_enc_interned_str", "ruby/encoding.h") # RUBY_VERSION >= 3.0 have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2 have_func("rb_hash_bulk_insert", "ruby.h") # Missing on TruffleRuby have_func("strnlen", "string.h") # Missing on Solaris 10 append_cflags("-std=c99") +if enable_config('parser-use-simd', default=!ENV["JSON_DISABLE_SIMD"]) + load __dir__ + "/../simd/conf.rb" +end + create_makefile 'json/ext/parser' diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index c5f300183d..01b6e6293b 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -20,6 +20,8 @@ typedef unsigned char _Bool; #endif #endif +#include "../simd/simd.h" + #ifndef RB_UNLIKELY #define RB_UNLIKELY(expr) expr #endif @@ -35,7 +37,7 @@ static ID i_chr, i_aset, i_aref, i_leftshift, i_new, i_try_convert, i_uminus, i_encode; static VALUE sym_max_nesting, sym_allow_nan, sym_allow_trailing_comma, sym_symbolize_names, sym_freeze, - sym_decimal_class, sym_on_load; + sym_decimal_class, sym_on_load, sym_allow_duplicate_key; static int binary_encindex; static int utf8_encindex; @@ -363,10 +365,17 @@ static int convert_UTF32_to_UTF8(char *buf, uint32_t ch) return len; } +enum duplicate_key_action { + JSON_DEPRECATED = 0, + JSON_IGNORE, + JSON_RAISE, +}; + typedef struct JSON_ParserStruct { VALUE on_load_proc; VALUE decimal_class; ID decimal_method_id; + enum duplicate_key_action on_duplicate_key; int max_nesting; bool allow_nan; bool allow_trailing_comma; @@ -386,15 +395,8 @@ typedef struct JSON_ParserStateStruct { int current_nesting; } JSON_ParserState; - -#define PARSE_ERROR_FRAGMENT_LEN 32 -#ifdef RBIMPL_ATTR_NORETURN -RBIMPL_ATTR_NORETURN() -#endif -static void raise_parse_error(const char *format, JSON_ParserState *state) +static void cursor_position(JSON_ParserState *state, long *line_out, long *column_out) { - unsigned char buffer[PARSE_ERROR_FRAGMENT_LEN + 3]; - const char *cursor = state->cursor; long column = 0; long line = 1; @@ -411,6 +413,27 @@ static void raise_parse_error(const char *format, JSON_ParserState *state) line++; } } + *line_out = line; + *column_out = column; +} + +static void emit_parse_warning(const char *message, JSON_ParserState *state) +{ + long line, column; + cursor_position(state, &line, &column); + + rb_warn("%s at line %ld column %ld", message, line, column); +} + +#define PARSE_ERROR_FRAGMENT_LEN 32 +#ifdef RBIMPL_ATTR_NORETURN +RBIMPL_ATTR_NORETURN() +#endif +static void raise_parse_error(const char *format, JSON_ParserState *state) +{ + unsigned char buffer[PARSE_ERROR_FRAGMENT_LEN + 3]; + long line, column; + cursor_position(state, &line, &column); const char *ptr = "EOF"; if (state->cursor && state->cursor < state->end) { @@ -517,7 +540,7 @@ static void json_eat_comments(JSON_ParserState *state) { if (state->cursor + 1 < state->end) { - switch(state->cursor[1]) { + switch (state->cursor[1]) { case '/': { state->cursor = memchr(state->cursor, '\n', state->end - state->cursor); if (!state->cursor) { @@ -807,11 +830,25 @@ static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig return array; } -static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, long count) +static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, size_t count) { - VALUE object = rb_hash_new_capa(count); + size_t entries_count = count / 2; + VALUE object = rb_hash_new_capa(entries_count); rb_hash_bulk_insert(count, rvalue_stack_peek(state->stack, count), object); + if (RB_UNLIKELY(RHASH_SIZE(object) < entries_count)) { + switch (config->on_duplicate_key) { + case JSON_IGNORE: + break; + case JSON_DEPRECATED: + emit_parse_warning("detected duplicate keys in JSON object. This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`", state); + break; + case JSON_RAISE: + raise_parse_error("duplicate key", state); + break; + } + } + rvalue_stack_pop(state->stack, count); if (config->freeze) { @@ -844,7 +881,7 @@ static inline VALUE json_push_value(JSON_ParserState *state, JSON_ParserConfig * return value; } -static const bool string_scan[256] = { +static const bool string_scan_table[256] = { // ASCII Control Characters 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -857,32 +894,71 @@ static const bool string_scan[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; +#if (defined(__GNUC__ ) || defined(__clang__)) +#define FORCE_INLINE __attribute__((always_inline)) +#else +#define FORCE_INLINE +#endif + +#ifdef HAVE_SIMD +static SIMD_Implementation simd_impl = SIMD_NONE; +#endif /* HAVE_SIMD */ + +static inline bool FORCE_INLINE string_scan(JSON_ParserState *state) +{ +#ifdef HAVE_SIMD +#if defined(HAVE_SIMD_NEON) + + uint64_t mask = 0; + if (string_scan_simd_neon(&state->cursor, state->end, &mask)) { + state->cursor += trailing_zeros64(mask) >> 2; + return 1; + } + +#elif defined(HAVE_SIMD_SSE2) + if (simd_impl == SIMD_SSE2) { + int mask = 0; + if (string_scan_simd_sse2(&state->cursor, state->end, &mask)) { + state->cursor += trailing_zeros(mask); + return 1; + } + } +#endif /* HAVE_SIMD_NEON or HAVE_SIMD_SSE2 */ +#endif /* HAVE_SIMD */ + + while (state->cursor < state->end) { + if (RB_UNLIKELY(string_scan_table[(unsigned char)*state->cursor])) { + return 1; + } + *state->cursor++; + } + return 0; +} + static inline VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig *config, bool is_name) { state->cursor++; const char *start = state->cursor; bool escaped = false; - while (state->cursor < state->end) { - if (RB_UNLIKELY(string_scan[(unsigned char)*state->cursor])) { - switch (*state->cursor) { - case '"': { - VALUE string = json_decode_string(state, config, start, state->cursor, escaped, is_name); - state->cursor++; - return json_push_value(state, config, string); - } - case '\\': { - state->cursor++; - escaped = true; - if ((unsigned char)*state->cursor < 0x20) { - raise_parse_error("invalid ASCII control character in string: %s", state); - } - break; - } - default: - raise_parse_error("invalid ASCII control character in string: %s", state); - break; + while (RB_UNLIKELY(string_scan(state))) { + switch (*state->cursor) { + case '"': { + VALUE string = json_decode_string(state, config, start, state->cursor, escaped, is_name); + state->cursor++; + return json_push_value(state, config, string); } + case '\\': { + state->cursor++; + escaped = true; + if ((unsigned char)*state->cursor < 0x20) { + raise_parse_error("invalid ASCII control character in string: %s", state); + } + break; + } + default: + raise_parse_error("invalid ASCII control character in string: %s", state); + break; } state->cursor++; @@ -1060,6 +1136,8 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) break; } case '{': { + const char *object_start_cursor = state->cursor; + state->cursor++; json_eat_whitespace(state); long stack_head = state->stack->head; @@ -1094,8 +1172,15 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) if (*state->cursor == '}') { state->cursor++; state->current_nesting--; - long count = state->stack->head - stack_head; - return json_push_value(state, config, json_decode_object(state, config, count)); + size_t count = state->stack->head - stack_head; + + // Temporary rewind cursor in case an error is raised + const char *final_cursor = state->cursor; + state->cursor = object_start_cursor; + VALUE object = json_decode_object(state, config, count); + state->cursor = final_cursor; + + return json_push_value(state, config, object); } if (*state->cursor == ',') { @@ -1184,6 +1269,7 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data) else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); } else if (key == sym_freeze) { config->freeze = RTEST(val); } else if (key == sym_on_load) { config->on_load_proc = RTEST(val) ? val : Qfalse; } + else if (key == sym_allow_duplicate_key) { config->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; } else if (key == sym_decimal_class) { if (RTEST(val)) { if (rb_respond_to(val, i_try_convert)) { @@ -1400,6 +1486,7 @@ void Init_parser(void) sym_freeze = ID2SYM(rb_intern("freeze")); sym_on_load = ID2SYM(rb_intern("on_load")); sym_decimal_class = ID2SYM(rb_intern("decimal_class")); + sym_allow_duplicate_key = ID2SYM(rb_intern("allow_duplicate_key")); i_chr = rb_intern("chr"); i_aset = rb_intern("[]="); @@ -1413,4 +1500,8 @@ void Init_parser(void) binary_encindex = rb_ascii8bit_encindex(); utf8_encindex = rb_utf8_encindex(); enc_utf8 = rb_utf8_encoding(); + +#ifdef HAVE_SIMD + simd_impl = find_simd_implementation(); +#endif } diff --git a/ext/json/simd/conf.rb b/ext/json/simd/conf.rb new file mode 100644 index 0000000000..8e7d8ee261 --- /dev/null +++ b/ext/json/simd/conf.rb @@ -0,0 +1,20 @@ +case RbConfig::CONFIG['host_cpu'] +when /^(arm|aarch64)/ + # Try to compile a small program using NEON instructions + header, type, init = 'arm_neon.h', 'uint8x16_t', 'vdupq_n_u8(32)' +when /^(x86_64|x64)/ + header, type, init = 'x86intrin.h', '__m128i', '_mm_set1_epi8(32)' +end +if header + have_header(header) && try_compile(<<~SRC) + #{cpp_include(header)} + int main(int argc, char **argv) { + #{type} test = #{init}; + if (argc > 100000) printf("%p", &test); + return 0; + } + SRC + $defs.push("-DJSON_ENABLE_SIMD") +end + +have_header('cpuid.h') diff --git a/ext/json/simd/simd.h b/ext/json/simd/simd.h new file mode 100644 index 0000000000..f8503d1395 --- /dev/null +++ b/ext/json/simd/simd.h @@ -0,0 +1,187 @@ +typedef enum { + SIMD_NONE, + SIMD_NEON, + SIMD_SSE2 +} SIMD_Implementation; + +#ifdef JSON_ENABLE_SIMD + +#ifdef __clang__ + #if __has_builtin(__builtin_ctzll) + #define HAVE_BUILTIN_CTZLL 1 + #else + #define HAVE_BUILTIN_CTZLL 0 + #endif +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) + #define HAVE_BUILTIN_CTZLL 1 +#else + #define HAVE_BUILTIN_CTZLL 0 +#endif + +static inline uint32_t trailing_zeros64(uint64_t input) +{ +#if HAVE_BUILTIN_CTZLL + return __builtin_ctzll(input); +#else + uint32_t trailing_zeros = 0; + uint64_t temp = input; + while ((temp & 1) == 0 && temp > 0) { + trailing_zeros++; + temp >>= 1; + } + return trailing_zeros; +#endif +} + +static inline int trailing_zeros(int input) +{ + #if HAVE_BUILTIN_CTZLL + return __builtin_ctz(input); + #else + int trailing_zeros = 0; + int temp = input; + while ((temp & 1) == 0 && temp > 0) { + trailing_zeros++; + temp >>= 1; + } + return trailing_zeros; + #endif +} + +#if (defined(__GNUC__ ) || defined(__clang__)) +#define FORCE_INLINE __attribute__((always_inline)) +#else +#define FORCE_INLINE +#endif + + +#define SIMD_MINIMUM_THRESHOLD 6 + +#if defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(__aarch64__) || defined(_M_ARM64) +#include + +#define FIND_SIMD_IMPLEMENTATION_DEFINED 1 +static inline SIMD_Implementation find_simd_implementation(void) +{ + return SIMD_NEON; +} + +#define HAVE_SIMD 1 +#define HAVE_SIMD_NEON 1 + +// See: https://community.arm.com/arm-community-blogs/b/servers-and-cloud-computing-blog/posts/porting-x86-vector-bitmask-optimizations-to-arm-neon +static inline FORCE_INLINE uint64_t neon_match_mask(uint8x16_t matches) +{ + const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(matches), 4); + const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); + return mask & 0x8888888888888888ull; +} + +static inline FORCE_INLINE uint64_t compute_chunk_mask_neon(const char *ptr) +{ + uint8x16_t chunk = vld1q_u8((const unsigned char *)ptr); + + // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33 + // https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/ + const uint8x16_t too_low_or_dbl_quote = vcltq_u8(veorq_u8(chunk, vdupq_n_u8(2)), vdupq_n_u8(33)); + + uint8x16_t has_backslash = vceqq_u8(chunk, vdupq_n_u8('\\')); + uint8x16_t needs_escape = vorrq_u8(too_low_or_dbl_quote, has_backslash); + return neon_match_mask(needs_escape); +} + +static inline FORCE_INLINE int string_scan_simd_neon(const char **ptr, const char *end, uint64_t *mask) +{ + while (*ptr + sizeof(uint8x16_t) <= end) { + uint64_t chunk_mask = compute_chunk_mask_neon(*ptr); + if (chunk_mask) { + *mask = chunk_mask; + return 1; + } + *ptr += sizeof(uint8x16_t); + } + return 0; +} + +uint8x16x4_t load_uint8x16_4(const unsigned char *table) { + uint8x16x4_t tab; + tab.val[0] = vld1q_u8(table); + tab.val[1] = vld1q_u8(table+16); + tab.val[2] = vld1q_u8(table+32); + tab.val[3] = vld1q_u8(table+48); + return tab; +} + +#endif /* ARM Neon Support.*/ + +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) + +#ifdef HAVE_X86INTRIN_H +#include + +#define HAVE_SIMD 1 +#define HAVE_SIMD_SSE2 1 + +#ifdef HAVE_CPUID_H +#define FIND_SIMD_IMPLEMENTATION_DEFINED 1 + +#if defined(__clang__) || defined(__GNUC__) +#define TARGET_SSE2 __attribute__((target("sse2"))) +#else +#define TARGET_SSE2 +#endif + +#define _mm_cmpge_epu8(a, b) _mm_cmpeq_epi8(_mm_max_epu8(a, b), a) +#define _mm_cmple_epu8(a, b) _mm_cmpge_epu8(b, a) +#define _mm_cmpgt_epu8(a, b) _mm_xor_si128(_mm_cmple_epu8(a, b), _mm_set1_epi8(-1)) +#define _mm_cmplt_epu8(a, b) _mm_cmpgt_epu8(b, a) + +static inline TARGET_SSE2 FORCE_INLINE int compute_chunk_mask_sse2(const char *ptr) +{ + __m128i chunk = _mm_loadu_si128((__m128i const*)ptr); + // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33 + // https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/ + __m128i too_low_or_dbl_quote = _mm_cmplt_epu8(_mm_xor_si128(chunk, _mm_set1_epi8(2)), _mm_set1_epi8(33)); + __m128i has_backslash = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\\')); + __m128i needs_escape = _mm_or_si128(too_low_or_dbl_quote, has_backslash); + return _mm_movemask_epi8(needs_escape); +} + +static inline TARGET_SSE2 FORCE_INLINE int string_scan_simd_sse2(const char **ptr, const char *end, int *mask) +{ + while (*ptr + sizeof(__m128i) <= end) { + int chunk_mask = compute_chunk_mask_sse2(*ptr); + if (chunk_mask) { + *mask = chunk_mask; + return 1; + } + *ptr += sizeof(__m128i); + } + + return 0; +} + +#include +#endif /* HAVE_CPUID_H */ + +static inline SIMD_Implementation find_simd_implementation(void) +{ + // TODO Revisit. I think the SSE version now only uses SSE2 instructions. + if (__builtin_cpu_supports("sse2")) { + return SIMD_SSE2; + } + + return SIMD_NONE; +} + +#endif /* HAVE_X86INTRIN_H */ +#endif /* X86_64 Support */ + +#endif /* JSON_ENABLE_SIMD */ + +#ifndef FIND_SIMD_IMPLEMENTATION_DEFINED +static inline SIMD_Implementation find_simd_implementation(void) +{ + return SIMD_NONE; +} +#endif diff --git a/ext/json/vendor/fpconv.c b/ext/json/vendor/fpconv.c index 1bbca28739..75efd46f11 100644 --- a/ext/json/vendor/fpconv.c +++ b/ext/json/vendor/fpconv.c @@ -432,8 +432,8 @@ static int filter_special(double fp, char* dest) * * Input: * fp -> the double to convert, dest -> destination buffer. - * The generated string will never be longer than 24 characters. - * Make sure to pass a pointer to at least 24 bytes of memory. + * The generated string will never be longer than 28 characters. + * Make sure to pass a pointer to at least 28 bytes of memory. * The emitted string will not be null terminated. * * Output: @@ -443,7 +443,7 @@ static int filter_special(double fp, char* dest) * * void print(double d) * { - * char buf[24 + 1] // plus null terminator + * char buf[28 + 1] // plus null terminator * int str_len = fpconv_dtoa(d, buf); * * buf[str_len] = '\0'; @@ -451,7 +451,7 @@ static int filter_special(double fp, char* dest) * } * */ -static int fpconv_dtoa(double d, char dest[24]) +static int fpconv_dtoa(double d, char dest[28]) { char digits[18]; diff --git a/ext/monitor/depend b/ext/monitor/depend index bc7ead0d32..0c7d54afc8 100644 --- a/ext/monitor/depend +++ b/ext/monitor/depend @@ -127,6 +127,7 @@ monitor.o: $(hdrdir)/ruby/internal/intern/re.h monitor.o: $(hdrdir)/ruby/internal/intern/ruby.h monitor.o: $(hdrdir)/ruby/internal/intern/select.h monitor.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +monitor.o: $(hdrdir)/ruby/internal/intern/set.h monitor.o: $(hdrdir)/ruby/internal/intern/signal.h monitor.o: $(hdrdir)/ruby/internal/intern/sprintf.h monitor.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/objspace/depend b/ext/objspace/depend index 4092b2bbc7..3c11511575 100644 --- a/ext/objspace/depend +++ b/ext/objspace/depend @@ -140,6 +140,7 @@ object_tracing.o: $(hdrdir)/ruby/internal/intern/re.h object_tracing.o: $(hdrdir)/ruby/internal/intern/ruby.h object_tracing.o: $(hdrdir)/ruby/internal/intern/select.h object_tracing.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/set.h object_tracing.o: $(hdrdir)/ruby/internal/intern/signal.h object_tracing.o: $(hdrdir)/ruby/internal/intern/sprintf.h object_tracing.o: $(hdrdir)/ruby/internal/intern/string.h @@ -343,6 +344,7 @@ objspace.o: $(hdrdir)/ruby/internal/intern/re.h objspace.o: $(hdrdir)/ruby/internal/intern/ruby.h objspace.o: $(hdrdir)/ruby/internal/intern/select.h objspace.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +objspace.o: $(hdrdir)/ruby/internal/intern/set.h objspace.o: $(hdrdir)/ruby/internal/intern/signal.h objspace.o: $(hdrdir)/ruby/internal/intern/sprintf.h objspace.o: $(hdrdir)/ruby/internal/intern/string.h @@ -557,6 +559,7 @@ objspace_dump.o: $(hdrdir)/ruby/internal/intern/re.h objspace_dump.o: $(hdrdir)/ruby/internal/intern/ruby.h objspace_dump.o: $(hdrdir)/ruby/internal/intern/select.h objspace_dump.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/set.h objspace_dump.o: $(hdrdir)/ruby/internal/intern/signal.h objspace_dump.o: $(hdrdir)/ruby/internal/intern/sprintf.h objspace_dump.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index acd4a6864d..5e183e78ed 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -504,6 +504,7 @@ count_imemo_objects(int argc, VALUE *argv, VALUE self) INIT_IMEMO_TYPE_ID(imemo_callinfo); INIT_IMEMO_TYPE_ID(imemo_callcache); INIT_IMEMO_TYPE_ID(imemo_constcache); + INIT_IMEMO_TYPE_ID(imemo_fields); #undef INIT_IMEMO_TYPE_ID } diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c index ec7a21417e..80732d0282 100644 --- a/ext/objspace/objspace_dump.c +++ b/ext/objspace/objspace_dump.c @@ -394,9 +394,10 @@ dump_object(VALUE obj, struct dump_config *dc) dc->cur_obj = obj; dc->cur_obj_references = 0; - if (BUILTIN_TYPE(obj) == T_NODE || BUILTIN_TYPE(obj) == T_IMEMO) { + if (BUILTIN_TYPE(obj) == T_NODE || (BUILTIN_TYPE(obj) == T_IMEMO && !IMEMO_TYPE_P(obj, imemo_fields))) { dc->cur_obj_klass = 0; - } else { + } + else { dc->cur_obj_klass = RBASIC_CLASS(obj); } @@ -414,9 +415,11 @@ dump_object(VALUE obj, struct dump_config *dc) dump_append(dc, obj_type(obj)); dump_append(dc, "\""); - size_t shape_id = rb_obj_shape_id(obj); - dump_append(dc, ", \"shape_id\":"); - dump_append_sizet(dc, shape_id); + if (BUILTIN_TYPE(obj) != T_IMEMO || IMEMO_TYPE_P(obj, imemo_fields)) { + size_t shape_id = rb_obj_shape_id(obj) & SHAPE_ID_OFFSET_MASK; + dump_append(dc, ", \"shape_id\":"); + dump_append_sizet(dc, shape_id); + } dump_append(dc, ", \"slot_size\":"); dump_append_sizet(dc, dc->cur_page_slot_size); @@ -782,30 +785,29 @@ objspace_dump(VALUE os, VALUE obj, VALUE output) } static void -shape_i(rb_shape_t *shape, void *data) +shape_id_i(shape_id_t shape_id, void *data) { struct dump_config *dc = (struct dump_config *)data; - shape_id_t shape_id = rb_shape_id(shape); if (shape_id < dc->shapes_since) { return; } dump_append(dc, "{\"address\":"); - dump_append_ref(dc, (VALUE)shape); + dump_append_ref(dc, (VALUE)RSHAPE(shape_id)); dump_append(dc, ", \"type\":\"SHAPE\", \"id\":"); dump_append_sizet(dc, shape_id); - if (shape->type != SHAPE_ROOT) { + if (RSHAPE_TYPE(shape_id) != SHAPE_ROOT) { dump_append(dc, ", \"parent_id\":"); - dump_append_lu(dc, shape->parent_id); + dump_append_lu(dc, RSHAPE_PARENT(shape_id)); } dump_append(dc, ", \"depth\":"); dump_append_sizet(dc, rb_shape_depth(shape_id)); - switch((enum shape_type)shape->type) { + switch (RSHAPE_TYPE(shape_id)) { case SHAPE_ROOT: dump_append(dc, ", \"shape_type\":\"ROOT\""); break; @@ -813,17 +815,8 @@ shape_i(rb_shape_t *shape, void *data) dump_append(dc, ", \"shape_type\":\"IVAR\""); dump_append(dc, ",\"edge_name\":"); - dump_append_id(dc, shape->edge_name); + dump_append_id(dc, RSHAPE_EDGE_NAME(shape_id)); - break; - case SHAPE_FROZEN: - dump_append(dc, ", \"shape_type\":\"FROZEN\""); - break; - case SHAPE_T_OBJECT: - dump_append(dc, ", \"shape_type\":\"T_OBJECT\""); - break; - case SHAPE_OBJ_TOO_COMPLEX: - dump_append(dc, ", \"shape_type\":\"OBJ_TOO_COMPLEX\""); break; case SHAPE_OBJ_ID: dump_append(dc, ", \"shape_type\":\"OBJ_ID\""); @@ -853,7 +846,7 @@ objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since, VALUE shapes) } if (RTEST(shapes)) { - rb_shape_each_shape(shape_i, &dc); + rb_shape_each_shape_id(shape_id_i, &dc); } /* dump all objects */ @@ -870,7 +863,7 @@ objspace_dump_shapes(VALUE os, VALUE output, VALUE shapes) dump_output(&dc, output, Qfalse, Qnil, shapes); if (RTEST(shapes)) { - rb_shape_each_shape(shape_i, &dc); + rb_shape_each_shape_id(shape_id_i, &dc); } return dump_result(&dc); } diff --git a/ext/openssl/depend b/ext/openssl/depend index 0a8800a94a..435f4a1c68 100644 --- a/ext/openssl/depend +++ b/ext/openssl/depend @@ -142,6 +142,7 @@ ossl.o: $(hdrdir)/ruby/internal/intern/re.h ossl.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl.o: $(hdrdir)/ruby/internal/intern/select.h ossl.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl.o: $(hdrdir)/ruby/internal/intern/set.h ossl.o: $(hdrdir)/ruby/internal/intern/signal.h ossl.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl.o: $(hdrdir)/ruby/internal/intern/string.h @@ -338,6 +339,7 @@ ossl_asn1.o: $(hdrdir)/ruby/internal/intern/re.h ossl_asn1.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_asn1.o: $(hdrdir)/ruby/internal/intern/select.h ossl_asn1.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_asn1.o: $(hdrdir)/ruby/internal/intern/set.h ossl_asn1.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_asn1.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_asn1.o: $(hdrdir)/ruby/internal/intern/string.h @@ -534,6 +536,7 @@ ossl_bio.o: $(hdrdir)/ruby/internal/intern/re.h ossl_bio.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_bio.o: $(hdrdir)/ruby/internal/intern/select.h ossl_bio.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_bio.o: $(hdrdir)/ruby/internal/intern/set.h ossl_bio.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_bio.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_bio.o: $(hdrdir)/ruby/internal/intern/string.h @@ -730,6 +733,7 @@ ossl_bn.o: $(hdrdir)/ruby/internal/intern/re.h ossl_bn.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_bn.o: $(hdrdir)/ruby/internal/intern/select.h ossl_bn.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_bn.o: $(hdrdir)/ruby/internal/intern/set.h ossl_bn.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_bn.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_bn.o: $(hdrdir)/ruby/internal/intern/string.h @@ -926,6 +930,7 @@ ossl_cipher.o: $(hdrdir)/ruby/internal/intern/re.h ossl_cipher.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_cipher.o: $(hdrdir)/ruby/internal/intern/select.h ossl_cipher.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_cipher.o: $(hdrdir)/ruby/internal/intern/set.h ossl_cipher.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_cipher.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_cipher.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1122,6 +1127,7 @@ ossl_config.o: $(hdrdir)/ruby/internal/intern/re.h ossl_config.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_config.o: $(hdrdir)/ruby/internal/intern/select.h ossl_config.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_config.o: $(hdrdir)/ruby/internal/intern/set.h ossl_config.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_config.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_config.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1318,6 +1324,7 @@ ossl_digest.o: $(hdrdir)/ruby/internal/intern/re.h ossl_digest.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_digest.o: $(hdrdir)/ruby/internal/intern/select.h ossl_digest.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_digest.o: $(hdrdir)/ruby/internal/intern/set.h ossl_digest.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_digest.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_digest.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1514,6 +1521,7 @@ ossl_engine.o: $(hdrdir)/ruby/internal/intern/re.h ossl_engine.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_engine.o: $(hdrdir)/ruby/internal/intern/select.h ossl_engine.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_engine.o: $(hdrdir)/ruby/internal/intern/set.h ossl_engine.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_engine.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_engine.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1710,6 +1718,7 @@ ossl_hmac.o: $(hdrdir)/ruby/internal/intern/re.h ossl_hmac.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_hmac.o: $(hdrdir)/ruby/internal/intern/select.h ossl_hmac.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_hmac.o: $(hdrdir)/ruby/internal/intern/set.h ossl_hmac.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_hmac.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_hmac.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1906,6 +1915,7 @@ ossl_kdf.o: $(hdrdir)/ruby/internal/intern/re.h ossl_kdf.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_kdf.o: $(hdrdir)/ruby/internal/intern/select.h ossl_kdf.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_kdf.o: $(hdrdir)/ruby/internal/intern/set.h ossl_kdf.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_kdf.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_kdf.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2102,6 +2112,7 @@ ossl_ns_spki.o: $(hdrdir)/ruby/internal/intern/re.h ossl_ns_spki.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_ns_spki.o: $(hdrdir)/ruby/internal/intern/select.h ossl_ns_spki.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_ns_spki.o: $(hdrdir)/ruby/internal/intern/set.h ossl_ns_spki.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_ns_spki.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_ns_spki.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2298,6 +2309,7 @@ ossl_ocsp.o: $(hdrdir)/ruby/internal/intern/re.h ossl_ocsp.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_ocsp.o: $(hdrdir)/ruby/internal/intern/select.h ossl_ocsp.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_ocsp.o: $(hdrdir)/ruby/internal/intern/set.h ossl_ocsp.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_ocsp.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_ocsp.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2494,6 +2506,7 @@ ossl_pkcs12.o: $(hdrdir)/ruby/internal/intern/re.h ossl_pkcs12.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_pkcs12.o: $(hdrdir)/ruby/internal/intern/select.h ossl_pkcs12.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_pkcs12.o: $(hdrdir)/ruby/internal/intern/set.h ossl_pkcs12.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_pkcs12.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_pkcs12.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2690,6 +2703,7 @@ ossl_pkcs7.o: $(hdrdir)/ruby/internal/intern/re.h ossl_pkcs7.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_pkcs7.o: $(hdrdir)/ruby/internal/intern/select.h ossl_pkcs7.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_pkcs7.o: $(hdrdir)/ruby/internal/intern/set.h ossl_pkcs7.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_pkcs7.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_pkcs7.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2886,6 +2900,7 @@ ossl_pkey.o: $(hdrdir)/ruby/internal/intern/re.h ossl_pkey.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_pkey.o: $(hdrdir)/ruby/internal/intern/select.h ossl_pkey.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_pkey.o: $(hdrdir)/ruby/internal/intern/set.h ossl_pkey.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_pkey.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_pkey.o: $(hdrdir)/ruby/internal/intern/string.h @@ -3082,6 +3097,7 @@ ossl_pkey_dh.o: $(hdrdir)/ruby/internal/intern/re.h ossl_pkey_dh.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_pkey_dh.o: $(hdrdir)/ruby/internal/intern/select.h ossl_pkey_dh.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_pkey_dh.o: $(hdrdir)/ruby/internal/intern/set.h ossl_pkey_dh.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_pkey_dh.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_pkey_dh.o: $(hdrdir)/ruby/internal/intern/string.h @@ -3278,6 +3294,7 @@ ossl_pkey_dsa.o: $(hdrdir)/ruby/internal/intern/re.h ossl_pkey_dsa.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_pkey_dsa.o: $(hdrdir)/ruby/internal/intern/select.h ossl_pkey_dsa.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_pkey_dsa.o: $(hdrdir)/ruby/internal/intern/set.h ossl_pkey_dsa.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_pkey_dsa.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_pkey_dsa.o: $(hdrdir)/ruby/internal/intern/string.h @@ -3474,6 +3491,7 @@ ossl_pkey_ec.o: $(hdrdir)/ruby/internal/intern/re.h ossl_pkey_ec.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_pkey_ec.o: $(hdrdir)/ruby/internal/intern/select.h ossl_pkey_ec.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_pkey_ec.o: $(hdrdir)/ruby/internal/intern/set.h ossl_pkey_ec.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_pkey_ec.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_pkey_ec.o: $(hdrdir)/ruby/internal/intern/string.h @@ -3670,6 +3688,7 @@ ossl_pkey_rsa.o: $(hdrdir)/ruby/internal/intern/re.h ossl_pkey_rsa.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_pkey_rsa.o: $(hdrdir)/ruby/internal/intern/select.h ossl_pkey_rsa.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_pkey_rsa.o: $(hdrdir)/ruby/internal/intern/set.h ossl_pkey_rsa.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_pkey_rsa.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_pkey_rsa.o: $(hdrdir)/ruby/internal/intern/string.h @@ -3866,6 +3885,7 @@ ossl_provider.o: $(hdrdir)/ruby/internal/intern/re.h ossl_provider.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_provider.o: $(hdrdir)/ruby/internal/intern/select.h ossl_provider.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_provider.o: $(hdrdir)/ruby/internal/intern/set.h ossl_provider.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_provider.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_provider.o: $(hdrdir)/ruby/internal/intern/string.h @@ -4062,6 +4082,7 @@ ossl_rand.o: $(hdrdir)/ruby/internal/intern/re.h ossl_rand.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_rand.o: $(hdrdir)/ruby/internal/intern/select.h ossl_rand.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_rand.o: $(hdrdir)/ruby/internal/intern/set.h ossl_rand.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_rand.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_rand.o: $(hdrdir)/ruby/internal/intern/string.h @@ -4258,6 +4279,7 @@ ossl_ssl.o: $(hdrdir)/ruby/internal/intern/re.h ossl_ssl.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_ssl.o: $(hdrdir)/ruby/internal/intern/select.h ossl_ssl.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_ssl.o: $(hdrdir)/ruby/internal/intern/set.h ossl_ssl.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_ssl.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_ssl.o: $(hdrdir)/ruby/internal/intern/string.h @@ -4454,6 +4476,7 @@ ossl_ssl_session.o: $(hdrdir)/ruby/internal/intern/re.h ossl_ssl_session.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_ssl_session.o: $(hdrdir)/ruby/internal/intern/select.h ossl_ssl_session.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_ssl_session.o: $(hdrdir)/ruby/internal/intern/set.h ossl_ssl_session.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_ssl_session.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_ssl_session.o: $(hdrdir)/ruby/internal/intern/string.h @@ -4650,6 +4673,7 @@ ossl_ts.o: $(hdrdir)/ruby/internal/intern/re.h ossl_ts.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_ts.o: $(hdrdir)/ruby/internal/intern/select.h ossl_ts.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_ts.o: $(hdrdir)/ruby/internal/intern/set.h ossl_ts.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_ts.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_ts.o: $(hdrdir)/ruby/internal/intern/string.h @@ -4846,6 +4870,7 @@ ossl_x509.o: $(hdrdir)/ruby/internal/intern/re.h ossl_x509.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_x509.o: $(hdrdir)/ruby/internal/intern/select.h ossl_x509.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_x509.o: $(hdrdir)/ruby/internal/intern/set.h ossl_x509.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_x509.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_x509.o: $(hdrdir)/ruby/internal/intern/string.h @@ -5042,6 +5067,7 @@ ossl_x509attr.o: $(hdrdir)/ruby/internal/intern/re.h ossl_x509attr.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_x509attr.o: $(hdrdir)/ruby/internal/intern/select.h ossl_x509attr.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_x509attr.o: $(hdrdir)/ruby/internal/intern/set.h ossl_x509attr.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_x509attr.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_x509attr.o: $(hdrdir)/ruby/internal/intern/string.h @@ -5238,6 +5264,7 @@ ossl_x509cert.o: $(hdrdir)/ruby/internal/intern/re.h ossl_x509cert.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_x509cert.o: $(hdrdir)/ruby/internal/intern/select.h ossl_x509cert.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_x509cert.o: $(hdrdir)/ruby/internal/intern/set.h ossl_x509cert.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_x509cert.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_x509cert.o: $(hdrdir)/ruby/internal/intern/string.h @@ -5434,6 +5461,7 @@ ossl_x509crl.o: $(hdrdir)/ruby/internal/intern/re.h ossl_x509crl.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_x509crl.o: $(hdrdir)/ruby/internal/intern/select.h ossl_x509crl.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_x509crl.o: $(hdrdir)/ruby/internal/intern/set.h ossl_x509crl.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_x509crl.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_x509crl.o: $(hdrdir)/ruby/internal/intern/string.h @@ -5630,6 +5658,7 @@ ossl_x509ext.o: $(hdrdir)/ruby/internal/intern/re.h ossl_x509ext.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_x509ext.o: $(hdrdir)/ruby/internal/intern/select.h ossl_x509ext.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_x509ext.o: $(hdrdir)/ruby/internal/intern/set.h ossl_x509ext.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_x509ext.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_x509ext.o: $(hdrdir)/ruby/internal/intern/string.h @@ -5826,6 +5855,7 @@ ossl_x509name.o: $(hdrdir)/ruby/internal/intern/re.h ossl_x509name.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_x509name.o: $(hdrdir)/ruby/internal/intern/select.h ossl_x509name.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_x509name.o: $(hdrdir)/ruby/internal/intern/set.h ossl_x509name.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_x509name.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_x509name.o: $(hdrdir)/ruby/internal/intern/string.h @@ -6022,6 +6052,7 @@ ossl_x509req.o: $(hdrdir)/ruby/internal/intern/re.h ossl_x509req.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_x509req.o: $(hdrdir)/ruby/internal/intern/select.h ossl_x509req.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_x509req.o: $(hdrdir)/ruby/internal/intern/set.h ossl_x509req.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_x509req.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_x509req.o: $(hdrdir)/ruby/internal/intern/string.h @@ -6218,6 +6249,7 @@ ossl_x509revoked.o: $(hdrdir)/ruby/internal/intern/re.h ossl_x509revoked.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_x509revoked.o: $(hdrdir)/ruby/internal/intern/select.h ossl_x509revoked.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_x509revoked.o: $(hdrdir)/ruby/internal/intern/set.h ossl_x509revoked.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_x509revoked.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_x509revoked.o: $(hdrdir)/ruby/internal/intern/string.h @@ -6414,6 +6446,7 @@ ossl_x509store.o: $(hdrdir)/ruby/internal/intern/re.h ossl_x509store.o: $(hdrdir)/ruby/internal/intern/ruby.h ossl_x509store.o: $(hdrdir)/ruby/internal/intern/select.h ossl_x509store.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ossl_x509store.o: $(hdrdir)/ruby/internal/intern/set.h ossl_x509store.o: $(hdrdir)/ruby/internal/intern/signal.h ossl_x509store.o: $(hdrdir)/ruby/internal/intern/sprintf.h ossl_x509store.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 5bb045e895..afbed10b54 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -38,7 +38,7 @@ Logging::message "=== OpenSSL for Ruby configurator ===\n" $defs.push("-D""OPENSSL_SUPPRESS_DEPRECATED") -have_func("rb_io_descriptor") +have_func("rb_io_descriptor", "ruby/io.h") have_func("rb_io_maybe_wait(0, Qnil, Qnil, Qnil)", "ruby/io.h") # Ruby 3.1 have_func("rb_io_timeout", "ruby/io.h") @@ -135,6 +135,11 @@ ssl_h = "openssl/ssl.h".freeze # compile options have_func("RAND_egd()", "openssl/rand.h") +# added in OpenSSL 1.0.2, not in LibreSSL yet +have_func("SSL_CTX_set1_sigalgs_list(NULL, NULL)", ssl_h) +# added in OpenSSL 1.0.2, not in LibreSSL or AWS-LC yet +have_func("SSL_CTX_set1_client_sigalgs_list(NULL, NULL)", ssl_h) + # added in 1.1.0, currently not in LibreSSL have_func("EVP_PBE_scrypt(\"\", 0, (unsigned char *)\"\", 0, 0, 0, 0, 0, NULL, 0)", evp_h) diff --git a/ext/openssl/openssl.gemspec b/ext/openssl/openssl.gemspec index 9f7c718592..2ec1551885 100644 --- a/ext/openssl/openssl.gemspec +++ b/ext/openssl/openssl.gemspec @@ -13,7 +13,8 @@ Gem::Specification.new do |spec| spec.files = [] spec.add_runtime_dependency('jruby-openssl', '~> 0.14') else - spec.files = Dir["lib/**/*.rb", "ext/**/*.{c,h,rb}", "*.md", "BSDL", "COPYING"] + spec.files = Dir.glob(["lib/**/*.rb", "ext/**/*.{c,h,rb}", "*.md"], base: File.expand_path("..", __FILE__)) + + ["BSDL", "COPYING"] spec.require_paths = ["lib"] spec.extensions = ["ext/openssl/extconf.rb"] end diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index 9b20829b3f..22471d2085 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -71,6 +71,7 @@ #if OSSL_OPENSSL_PREREQ(3, 0, 0) # define OSSL_USE_PROVIDER +# include #endif /* diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 9999664b87..186679da4c 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -36,7 +36,7 @@ asn1time_to_time(const ASN1_TIME *time) ossl_raise(rb_eTypeError, "bad UTCTIME format: \"%s\"", time->data); } - if (tm.tm_year < 69) { + if (tm.tm_year < 50) { tm.tm_year += 2000; } else { tm.tm_year += 1900; diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index d737fa74ff..07e651335d 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -198,47 +198,16 @@ ossl_cipher_reset(VALUE self) } static VALUE -ossl_cipher_init(int argc, VALUE *argv, VALUE self, int mode) +ossl_cipher_init(VALUE self, int enc) { EVP_CIPHER_CTX *ctx; - unsigned char key[EVP_MAX_KEY_LENGTH], *p_key = NULL; - unsigned char iv[EVP_MAX_IV_LENGTH], *p_iv = NULL; - VALUE pass, init_v; - if(rb_scan_args(argc, argv, "02", &pass, &init_v) > 0){ - /* - * oops. this code mistakes salt for IV. - * We deprecated the arguments for this method, but we decided - * keeping this behaviour for backward compatibility. - */ - VALUE cname = rb_class_path(rb_obj_class(self)); - rb_warn("arguments for %"PRIsVALUE"#encrypt and %"PRIsVALUE"#decrypt were deprecated; " - "use %"PRIsVALUE"#pkcs5_keyivgen to derive key and IV", - cname, cname, cname); - StringValue(pass); - GetCipher(self, ctx); - if (NIL_P(init_v)) memcpy(iv, "OpenSSL for Ruby rulez!", sizeof(iv)); - else{ - StringValue(init_v); - if (EVP_MAX_IV_LENGTH > RSTRING_LEN(init_v)) { - memset(iv, 0, EVP_MAX_IV_LENGTH); - memcpy(iv, RSTRING_PTR(init_v), RSTRING_LEN(init_v)); - } - else memcpy(iv, RSTRING_PTR(init_v), sizeof(iv)); - } - EVP_BytesToKey(EVP_CIPHER_CTX_cipher(ctx), EVP_md5(), iv, - (unsigned char *)RSTRING_PTR(pass), RSTRING_LENINT(pass), 1, key, NULL); - p_key = key; - p_iv = iv; - } - else { - GetCipher(self, ctx); - } - if (EVP_CipherInit_ex(ctx, NULL, NULL, p_key, p_iv, mode) != 1) { - ossl_raise(eCipherError, NULL); + GetCipher(self, ctx); + if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, enc) != 1) { + ossl_raise(eCipherError, "EVP_CipherInit_ex"); } - rb_ivar_set(self, id_key_set, p_key ? Qtrue : Qfalse); + rb_ivar_set(self, id_key_set, Qfalse); return self; } @@ -256,9 +225,9 @@ ossl_cipher_init(int argc, VALUE *argv, VALUE self, int mode) * Internally calls EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 1). */ static VALUE -ossl_cipher_encrypt(int argc, VALUE *argv, VALUE self) +ossl_cipher_encrypt(VALUE self) { - return ossl_cipher_init(argc, argv, self, 1); + return ossl_cipher_init(self, 1); } /* @@ -274,9 +243,9 @@ ossl_cipher_encrypt(int argc, VALUE *argv, VALUE self) * Internally calls EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 0). */ static VALUE -ossl_cipher_decrypt(int argc, VALUE *argv, VALUE self) +ossl_cipher_decrypt(VALUE self) { - return ossl_cipher_init(argc, argv, self, 0); + return ossl_cipher_init(self, 0); } /* @@ -1064,8 +1033,8 @@ Init_ossl_cipher(void) rb_define_module_function(cCipher, "ciphers", ossl_s_ciphers, 0); rb_define_method(cCipher, "initialize", ossl_cipher_initialize, 1); rb_define_method(cCipher, "reset", ossl_cipher_reset, 0); - rb_define_method(cCipher, "encrypt", ossl_cipher_encrypt, -1); - rb_define_method(cCipher, "decrypt", ossl_cipher_decrypt, -1); + rb_define_method(cCipher, "encrypt", ossl_cipher_encrypt, 0); + rb_define_method(cCipher, "decrypt", ossl_cipher_decrypt, 0); rb_define_method(cCipher, "pkcs5_keyivgen", ossl_cipher_pkcs5_keyivgen, -1); rb_define_method(cCipher, "update", ossl_cipher_update, -1); rb_define_method(cCipher, "final", ossl_cipher_final, 0); diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index dc83255f0e..b00a3648d1 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -635,6 +635,29 @@ ossl_pkey_initialize_copy(VALUE self, VALUE other) } #endif +#ifndef OSSL_USE_PROVIDER +static int +lookup_pkey_type(VALUE type) +{ + const EVP_PKEY_ASN1_METHOD *ameth; + int pkey_id; + + StringValue(type); + /* + * XXX: EVP_PKEY_asn1_find_str() looks up a PEM type string. Should we use + * OBJ_txt2nid() instead (and then somehow check if the NID is an acceptable + * EVP_PKEY type)? + * It is probably fine, though, since it can handle all algorithms that + * support raw keys in 1.1.1: { X25519, X448, ED25519, ED448, HMAC }. + */ + ameth = EVP_PKEY_asn1_find_str(NULL, RSTRING_PTR(type), RSTRING_LENINT(type)); + if (!ameth) + ossl_raise(ePKeyError, "algorithm %"PRIsVALUE" not found", type); + EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth); + return pkey_id; +} +#endif + /* * call-seq: * OpenSSL::PKey.new_raw_private_key(algo, string) -> PKey @@ -646,22 +669,23 @@ static VALUE ossl_pkey_new_raw_private_key(VALUE self, VALUE type, VALUE key) { EVP_PKEY *pkey; - const EVP_PKEY_ASN1_METHOD *ameth; - int pkey_id; size_t keylen; - StringValue(type); StringValue(key); - ameth = EVP_PKEY_asn1_find_str(NULL, RSTRING_PTR(type), RSTRING_LENINT(type)); - if (!ameth) - ossl_raise(ePKeyError, "algorithm %"PRIsVALUE" not found", type); - EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth); - keylen = RSTRING_LEN(key); +#ifdef OSSL_USE_PROVIDER + pkey = EVP_PKEY_new_raw_private_key_ex(NULL, StringValueCStr(type), NULL, + (unsigned char *)RSTRING_PTR(key), + keylen); + if (!pkey) + ossl_raise(ePKeyError, "EVP_PKEY_new_raw_private_key_ex"); +#else + int pkey_id = lookup_pkey_type(type); pkey = EVP_PKEY_new_raw_private_key(pkey_id, NULL, (unsigned char *)RSTRING_PTR(key), keylen); if (!pkey) ossl_raise(ePKeyError, "EVP_PKEY_new_raw_private_key"); +#endif return ossl_pkey_new(pkey); } @@ -677,22 +701,23 @@ static VALUE ossl_pkey_new_raw_public_key(VALUE self, VALUE type, VALUE key) { EVP_PKEY *pkey; - const EVP_PKEY_ASN1_METHOD *ameth; - int pkey_id; size_t keylen; - StringValue(type); StringValue(key); - ameth = EVP_PKEY_asn1_find_str(NULL, RSTRING_PTR(type), RSTRING_LENINT(type)); - if (!ameth) - ossl_raise(ePKeyError, "algorithm %"PRIsVALUE" not found", type); - EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth); - keylen = RSTRING_LEN(key); +#ifdef OSSL_USE_PROVIDER + pkey = EVP_PKEY_new_raw_public_key_ex(NULL, StringValueCStr(type), NULL, + (unsigned char *)RSTRING_PTR(key), + keylen); + if (!pkey) + ossl_raise(ePKeyError, "EVP_PKEY_new_raw_public_key_ex"); +#else + int pkey_id = lookup_pkey_type(type); pkey = EVP_PKEY_new_raw_public_key(pkey_id, NULL, (unsigned char *)RSTRING_PTR(key), keylen); if (!pkey) ossl_raise(ePKeyError, "EVP_PKEY_new_raw_public_key"); +#endif return ossl_pkey_new(pkey); } @@ -711,6 +736,10 @@ ossl_pkey_oid(VALUE self) GetPKey(self, pkey); nid = EVP_PKEY_id(pkey); +#ifdef OSSL_USE_PROVIDER + if (nid == EVP_PKEY_KEYMGMT) + ossl_raise(ePKeyError, "EVP_PKEY_id"); +#endif return rb_str_new_cstr(OBJ_nid2sn(nid)); } @@ -724,13 +753,23 @@ static VALUE ossl_pkey_inspect(VALUE self) { EVP_PKEY *pkey; - int nid; GetPKey(self, pkey); - nid = EVP_PKEY_id(pkey); - return rb_sprintf("#<%"PRIsVALUE":%p oid=%s>", - rb_class_name(CLASS_OF(self)), (void *)self, - OBJ_nid2sn(nid)); + VALUE str = rb_sprintf("#<%"PRIsVALUE":%p", + rb_obj_class(self), (void *)self); + int nid = EVP_PKEY_id(pkey); +#ifdef OSSL_USE_PROVIDER + if (nid != EVP_PKEY_KEYMGMT) +#endif + rb_str_catf(str, " oid=%s", OBJ_nid2sn(nid)); +#ifdef OSSL_USE_PROVIDER + rb_str_catf(str, " type_name=%s", EVP_PKEY_get0_type_name(pkey)); + const OSSL_PROVIDER *prov = EVP_PKEY_get0_provider(pkey); + if (prov) + rb_str_catf(str, " provider=%s", OSSL_PROVIDER_get0_name(prov)); +#endif + rb_str_catf(str, ">"); + return str; } /* diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index 4687cf8c9d..45e8bf19b2 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -47,11 +47,8 @@ static VALUE eEC_GROUP; static VALUE cEC_POINT; static VALUE eEC_POINT; -static ID s_GFp, s_GF2m; - -static ID ID_uncompressed; -static ID ID_compressed; -static ID ID_hybrid; +static VALUE sym_GFp, sym_GF2m; +static VALUE sym_uncompressed, sym_compressed, sym_hybrid; static ID id_i_group; @@ -674,19 +671,20 @@ static VALUE ossl_ec_group_initialize(int argc, VALUE *argv, VALUE self) break; case 4: if (SYMBOL_P(arg1)) { - ID id = SYM2ID(arg1); EC_GROUP *(*new_curve)(const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *) = NULL; const BIGNUM *p = GetBNPtr(arg2); const BIGNUM *a = GetBNPtr(arg3); const BIGNUM *b = GetBNPtr(arg4); - if (id == s_GFp) { + if (arg1 == sym_GFp) { new_curve = EC_GROUP_new_curve_GFp; + } #if !defined(OPENSSL_NO_EC2M) - } else if (id == s_GF2m) { + else if (arg1 == sym_GF2m) { new_curve = EC_GROUP_new_curve_GF2m; + } #endif - } else { + else { ossl_raise(rb_eArgError, "unknown symbol, must be :GFp or :GF2m"); } @@ -958,37 +956,36 @@ static VALUE ossl_ec_group_set_asn1_flag(VALUE self, VALUE flag_v) */ static VALUE ossl_ec_group_get_point_conversion_form(VALUE self) { - EC_GROUP *group = NULL; + EC_GROUP *group; point_conversion_form_t form; - VALUE ret; GetECGroup(self, group); form = EC_GROUP_get_point_conversion_form(group); switch (form) { - case POINT_CONVERSION_UNCOMPRESSED: ret = ID_uncompressed; break; - case POINT_CONVERSION_COMPRESSED: ret = ID_compressed; break; - case POINT_CONVERSION_HYBRID: ret = ID_hybrid; break; - default: ossl_raise(eEC_GROUP, "unsupported point conversion form: %d, this module should be updated", form); + case POINT_CONVERSION_UNCOMPRESSED: + return sym_uncompressed; + case POINT_CONVERSION_COMPRESSED: + return sym_compressed; + case POINT_CONVERSION_HYBRID: + return sym_hybrid; + default: + ossl_raise(eEC_GROUP, "unsupported point conversion form: %d, " \ + "this module should be updated", form); } - - return ID2SYM(ret); } static point_conversion_form_t parse_point_conversion_form_symbol(VALUE sym) { - ID id = SYM2ID(sym); - - if (id == ID_uncompressed) + if (sym == sym_uncompressed) return POINT_CONVERSION_UNCOMPRESSED; - else if (id == ID_compressed) + if (sym == sym_compressed) return POINT_CONVERSION_COMPRESSED; - else if (id == ID_hybrid) + if (sym == sym_hybrid) return POINT_CONVERSION_HYBRID; - else - ossl_raise(rb_eArgError, "unsupported point conversion form %+"PRIsVALUE - " (expected :compressed, :uncompressed, or :hybrid)", sym); + ossl_raise(rb_eArgError, "unsupported point conversion form %+"PRIsVALUE + " (expected :compressed, :uncompressed, or :hybrid)", sym); } /* @@ -1557,12 +1554,12 @@ void Init_ossl_ec(void) eEC_GROUP = rb_define_class_under(cEC_GROUP, "Error", eOSSLError); eEC_POINT = rb_define_class_under(cEC_POINT, "Error", eOSSLError); - s_GFp = rb_intern("GFp"); - s_GF2m = rb_intern("GF2m"); + sym_GFp = ID2SYM(rb_intern_const("GFp")); + sym_GF2m = ID2SYM(rb_intern_const("GF2m")); - ID_uncompressed = rb_intern("uncompressed"); - ID_compressed = rb_intern("compressed"); - ID_hybrid = rb_intern("hybrid"); + sym_uncompressed = ID2SYM(rb_intern_const("uncompressed")); + sym_compressed = ID2SYM(rb_intern_const("compressed")); + sym_hybrid = ID2SYM(rb_intern_const("hybrid")); rb_define_const(cEC, "NAMED_CURVE", INT2NUM(OPENSSL_EC_NAMED_CURVE)); rb_define_const(cEC, "EXPLICIT_CURVE", INT2NUM(OPENSSL_EC_EXPLICIT_CURVE)); diff --git a/ext/openssl/ossl_provider.c b/ext/openssl/ossl_provider.c index d1f6c5d427..529a5e1c72 100644 --- a/ext/openssl/ossl_provider.c +++ b/ext/openssl/ossl_provider.c @@ -5,8 +5,6 @@ #include "ossl.h" #ifdef OSSL_USE_PROVIDER -# include - #define NewProvider(klass) \ TypedData_Wrap_Struct((klass), &ossl_provider_type, 0) #define SetProvider(obj, provider) do { \ diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index a5b25e14de..b5872f5881 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -430,8 +430,9 @@ ossl_sslctx_add_extra_chain_cert_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, arg)) GetSSLCTX(arg, ctx); x509 = DupX509CertPtr(i); - if(!SSL_CTX_add_extra_chain_cert(ctx, x509)){ - ossl_raise(eSSLError, NULL); + if (!SSL_CTX_add_extra_chain_cert(ctx, x509)) { + X509_free(x509); + ossl_raise(eSSLError, "SSL_CTX_add_extra_chain_cert"); } return i; @@ -998,11 +999,10 @@ static VALUE build_cipher_string(VALUE v) { VALUE str, elem; - int i; if (RB_TYPE_P(v, T_ARRAY)) { str = rb_str_new(0, 0); - for (i = 0; i < RARRAY_LEN(v); i++) { + for (long i = 0; i < RARRAY_LEN(v); i++) { elem = rb_ary_entry(v, i); if (RB_TYPE_P(elem, T_ARRAY)) elem = rb_ary_entry(elem, 0); elem = rb_String(elem); @@ -1023,9 +1023,14 @@ build_cipher_string(VALUE v) * ctx.ciphers = [name, ...] * ctx.ciphers = [[name, version, bits, alg_bits], ...] * - * Sets the list of available cipher suites for this context. Note in a server - * context some ciphers require the appropriate certificates. For example, an - * RSA cipher suite can only be chosen when an RSA certificate is available. + * Sets the list of available cipher suites for TLS 1.2 and below for this + * context. + * + * Note in a server context some ciphers require the appropriate certificates. + * For example, an RSA cipher suite can only be chosen when an RSA certificate + * is available. + * + * This method does not affect TLS 1.3 connections. See also #ciphersuites=. */ static VALUE ossl_sslctx_set_ciphers(VALUE self, VALUE v) @@ -1034,6 +1039,7 @@ ossl_sslctx_set_ciphers(VALUE self, VALUE v) VALUE str; rb_check_frozen(self); + // Assigning nil is a no-op for compatibility if (NIL_P(v)) return v; @@ -1050,9 +1056,8 @@ ossl_sslctx_set_ciphers(VALUE self, VALUE v) * call-seq: * ctx.ciphersuites = "cipher1:cipher2:..." * ctx.ciphersuites = [name, ...] - * ctx.ciphersuites = [[name, version, bits, alg_bits], ...] * - * Sets the list of available TLSv1.3 cipher suites for this context. + * Sets the list of available TLS 1.3 cipher suites for this context. */ static VALUE ossl_sslctx_set_ciphersuites(VALUE self, VALUE v) @@ -1061,6 +1066,7 @@ ossl_sslctx_set_ciphersuites(VALUE self, VALUE v) VALUE str; rb_check_frozen(self); + // Assigning nil is a no-op for compatibility if (NIL_P(v)) return v; @@ -1073,6 +1079,63 @@ ossl_sslctx_set_ciphersuites(VALUE self, VALUE v) return v; } +#ifdef HAVE_SSL_CTX_SET1_SIGALGS_LIST +/* + * call-seq: + * ctx.sigalgs = "sigalg1:sigalg2:..." + * + * Sets the list of "supported signature algorithms" for this context. + * + * For a TLS client, the list is used in the "signature_algorithms" extension + * in the ClientHello message. For a server, the list is used by OpenSSL to + * determine the set of shared signature algorithms. OpenSSL will pick the most + * appropriate one from it. + * + * See also #client_sigalgs= for the client authentication equivalent. + */ +static VALUE +ossl_sslctx_set_sigalgs(VALUE self, VALUE v) +{ + SSL_CTX *ctx; + + rb_check_frozen(self); + GetSSLCTX(self, ctx); + + if (!SSL_CTX_set1_sigalgs_list(ctx, StringValueCStr(v))) + ossl_raise(eSSLError, "SSL_CTX_set1_sigalgs_list"); + + return v; +} +#endif + +#ifdef HAVE_SSL_CTX_SET1_CLIENT_SIGALGS_LIST +/* + * call-seq: + * ctx.client_sigalgs = "sigalg1:sigalg2:..." + * + * Sets the list of "supported signature algorithms" for client authentication + * for this context. + * + * For a TLS server, the list is sent to the client as part of the + * CertificateRequest message. + * + * See also #sigalgs= for the server authentication equivalent. + */ +static VALUE +ossl_sslctx_set_client_sigalgs(VALUE self, VALUE v) +{ + SSL_CTX *ctx; + + rb_check_frozen(self); + GetSSLCTX(self, ctx); + + if (!SSL_CTX_set1_client_sigalgs_list(ctx, StringValueCStr(v))) + ossl_raise(eSSLError, "SSL_CTX_set1_client_sigalgs_list"); + + return v; +} +#endif + #ifndef OPENSSL_NO_DH /* * call-seq: @@ -1119,25 +1182,29 @@ ossl_sslctx_set_tmp_dh(VALUE self, VALUE arg) } #endif -#if !defined(OPENSSL_NO_EC) /* * call-seq: - * ctx.ecdh_curves = curve_list -> curve_list + * ctx.groups = groups_list + * ctx.ecdh_curves = groups_list * - * Sets the list of "supported elliptic curves" for this context. + * Sets the list of supported groups for key agreement for this context. * - * For a TLS client, the list is directly used in the Supported Elliptic Curves - * Extension. For a server, the list is used by OpenSSL to determine the set of - * shared curves. OpenSSL will pick the most appropriate one from it. + * For a TLS client, the list is directly used in the "supported_groups" + * extension. For a server, the list is used by OpenSSL to determine the set of + * shared supported groups. OpenSSL will pick the most appropriate one from it. + * + * #ecdh_curves= is a deprecated alias for #groups=. + * + * See also the man page SSL_CTX_set1_groups_list(3). * * === Example * ctx1 = OpenSSL::SSL::SSLContext.new - * ctx1.ecdh_curves = "X25519:P-256:P-224" + * ctx1.groups = "X25519:P-256:P-224" * svr = OpenSSL::SSL::SSLServer.new(tcp_svr, ctx1) * Thread.new { svr.accept } * * ctx2 = OpenSSL::SSL::SSLContext.new - * ctx2.ecdh_curves = "P-256" + * ctx2.groups = "P-256" * cli = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx2) * cli.connect * @@ -1145,7 +1212,7 @@ ossl_sslctx_set_tmp_dh(VALUE self, VALUE arg) * # => "prime256v1" (is an alias for NIST P-256) */ static VALUE -ossl_sslctx_set_ecdh_curves(VALUE self, VALUE arg) +ossl_sslctx_set_groups(VALUE self, VALUE arg) { SSL_CTX *ctx; @@ -1153,13 +1220,10 @@ ossl_sslctx_set_ecdh_curves(VALUE self, VALUE arg) GetSSLCTX(self, ctx); StringValueCStr(arg); - if (!SSL_CTX_set1_curves_list(ctx, RSTRING_PTR(arg))) - ossl_raise(eSSLError, NULL); + if (!SSL_CTX_set1_groups_list(ctx, RSTRING_PTR(arg))) + ossl_raise(eSSLError, "SSL_CTX_set1_groups_list"); return arg; } -#else -#define ossl_sslctx_set_ecdh_curves rb_f_notimplement -#endif /* * call-seq: @@ -2886,10 +2950,17 @@ Init_ossl_ssl(void) rb_define_method(cSSLContext, "ciphers", ossl_sslctx_get_ciphers, 0); rb_define_method(cSSLContext, "ciphers=", ossl_sslctx_set_ciphers, 1); rb_define_method(cSSLContext, "ciphersuites=", ossl_sslctx_set_ciphersuites, 1); +#ifdef HAVE_SSL_CTX_SET1_SIGALGS_LIST // Not in LibreSSL yet + rb_define_method(cSSLContext, "sigalgs=", ossl_sslctx_set_sigalgs, 1); +#endif +#ifdef HAVE_SSL_CTX_SET1_CLIENT_SIGALGS_LIST // Not in LibreSSL or AWS-LC yet + rb_define_method(cSSLContext, "client_sigalgs=", ossl_sslctx_set_client_sigalgs, 1); +#endif #ifndef OPENSSL_NO_DH rb_define_method(cSSLContext, "tmp_dh=", ossl_sslctx_set_tmp_dh, 1); #endif - rb_define_method(cSSLContext, "ecdh_curves=", ossl_sslctx_set_ecdh_curves, 1); + rb_define_method(cSSLContext, "groups=", ossl_sslctx_set_groups, 1); + rb_define_alias(cSSLContext, "ecdh_curves=", "groups="); rb_define_method(cSSLContext, "security_level", ossl_sslctx_get_security_level, 0); rb_define_method(cSSLContext, "security_level=", ossl_sslctx_set_security_level, 1); #ifdef SSL_MODE_SEND_FALLBACK_SCSV diff --git a/ext/pathname/depend b/ext/pathname/depend deleted file mode 100644 index cca7877dad..0000000000 --- a/ext/pathname/depend +++ /dev/null @@ -1,174 +0,0 @@ -# AUTOGENERATED DEPENDENCIES START -pathname.o: $(RUBY_EXTCONF_H) -pathname.o: $(arch_hdrdir)/ruby/config.h -pathname.o: $(hdrdir)/ruby.h -pathname.o: $(hdrdir)/ruby/assert.h -pathname.o: $(hdrdir)/ruby/backward.h -pathname.o: $(hdrdir)/ruby/backward/2/assume.h -pathname.o: $(hdrdir)/ruby/backward/2/attributes.h -pathname.o: $(hdrdir)/ruby/backward/2/bool.h -pathname.o: $(hdrdir)/ruby/backward/2/inttypes.h -pathname.o: $(hdrdir)/ruby/backward/2/limits.h -pathname.o: $(hdrdir)/ruby/backward/2/long_long.h -pathname.o: $(hdrdir)/ruby/backward/2/stdalign.h -pathname.o: $(hdrdir)/ruby/backward/2/stdarg.h -pathname.o: $(hdrdir)/ruby/defines.h -pathname.o: $(hdrdir)/ruby/encoding.h -pathname.o: $(hdrdir)/ruby/intern.h -pathname.o: $(hdrdir)/ruby/internal/abi.h -pathname.o: $(hdrdir)/ruby/internal/anyargs.h -pathname.o: $(hdrdir)/ruby/internal/arithmetic.h -pathname.o: $(hdrdir)/ruby/internal/arithmetic/char.h -pathname.o: $(hdrdir)/ruby/internal/arithmetic/double.h -pathname.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h -pathname.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h -pathname.o: $(hdrdir)/ruby/internal/arithmetic/int.h -pathname.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h -pathname.o: $(hdrdir)/ruby/internal/arithmetic/long.h -pathname.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h -pathname.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h -pathname.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h -pathname.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h -pathname.o: $(hdrdir)/ruby/internal/arithmetic/short.h -pathname.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h -pathname.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h -pathname.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h -pathname.o: $(hdrdir)/ruby/internal/assume.h -pathname.o: $(hdrdir)/ruby/internal/attr/alloc_size.h -pathname.o: $(hdrdir)/ruby/internal/attr/artificial.h -pathname.o: $(hdrdir)/ruby/internal/attr/cold.h -pathname.o: $(hdrdir)/ruby/internal/attr/const.h -pathname.o: $(hdrdir)/ruby/internal/attr/constexpr.h -pathname.o: $(hdrdir)/ruby/internal/attr/deprecated.h -pathname.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h -pathname.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h -pathname.o: $(hdrdir)/ruby/internal/attr/error.h -pathname.o: $(hdrdir)/ruby/internal/attr/flag_enum.h -pathname.o: $(hdrdir)/ruby/internal/attr/forceinline.h -pathname.o: $(hdrdir)/ruby/internal/attr/format.h -pathname.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h -pathname.o: $(hdrdir)/ruby/internal/attr/noalias.h -pathname.o: $(hdrdir)/ruby/internal/attr/nodiscard.h -pathname.o: $(hdrdir)/ruby/internal/attr/noexcept.h -pathname.o: $(hdrdir)/ruby/internal/attr/noinline.h -pathname.o: $(hdrdir)/ruby/internal/attr/nonnull.h -pathname.o: $(hdrdir)/ruby/internal/attr/noreturn.h -pathname.o: $(hdrdir)/ruby/internal/attr/packed_struct.h -pathname.o: $(hdrdir)/ruby/internal/attr/pure.h -pathname.o: $(hdrdir)/ruby/internal/attr/restrict.h -pathname.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h -pathname.o: $(hdrdir)/ruby/internal/attr/warning.h -pathname.o: $(hdrdir)/ruby/internal/attr/weakref.h -pathname.o: $(hdrdir)/ruby/internal/cast.h -pathname.o: $(hdrdir)/ruby/internal/compiler_is.h -pathname.o: $(hdrdir)/ruby/internal/compiler_is/apple.h -pathname.o: $(hdrdir)/ruby/internal/compiler_is/clang.h -pathname.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h -pathname.o: $(hdrdir)/ruby/internal/compiler_is/intel.h -pathname.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h -pathname.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h -pathname.o: $(hdrdir)/ruby/internal/compiler_since.h -pathname.o: $(hdrdir)/ruby/internal/config.h -pathname.o: $(hdrdir)/ruby/internal/constant_p.h -pathname.o: $(hdrdir)/ruby/internal/core.h -pathname.o: $(hdrdir)/ruby/internal/core/rarray.h -pathname.o: $(hdrdir)/ruby/internal/core/rbasic.h -pathname.o: $(hdrdir)/ruby/internal/core/rbignum.h -pathname.o: $(hdrdir)/ruby/internal/core/rclass.h -pathname.o: $(hdrdir)/ruby/internal/core/rdata.h -pathname.o: $(hdrdir)/ruby/internal/core/rfile.h -pathname.o: $(hdrdir)/ruby/internal/core/rhash.h -pathname.o: $(hdrdir)/ruby/internal/core/robject.h -pathname.o: $(hdrdir)/ruby/internal/core/rregexp.h -pathname.o: $(hdrdir)/ruby/internal/core/rstring.h -pathname.o: $(hdrdir)/ruby/internal/core/rstruct.h -pathname.o: $(hdrdir)/ruby/internal/core/rtypeddata.h -pathname.o: $(hdrdir)/ruby/internal/ctype.h -pathname.o: $(hdrdir)/ruby/internal/dllexport.h -pathname.o: $(hdrdir)/ruby/internal/dosish.h -pathname.o: $(hdrdir)/ruby/internal/encoding/coderange.h -pathname.o: $(hdrdir)/ruby/internal/encoding/ctype.h -pathname.o: $(hdrdir)/ruby/internal/encoding/encoding.h -pathname.o: $(hdrdir)/ruby/internal/encoding/pathname.h -pathname.o: $(hdrdir)/ruby/internal/encoding/re.h -pathname.o: $(hdrdir)/ruby/internal/encoding/sprintf.h -pathname.o: $(hdrdir)/ruby/internal/encoding/string.h -pathname.o: $(hdrdir)/ruby/internal/encoding/symbol.h -pathname.o: $(hdrdir)/ruby/internal/encoding/transcode.h -pathname.o: $(hdrdir)/ruby/internal/error.h -pathname.o: $(hdrdir)/ruby/internal/eval.h -pathname.o: $(hdrdir)/ruby/internal/event.h -pathname.o: $(hdrdir)/ruby/internal/fl_type.h -pathname.o: $(hdrdir)/ruby/internal/gc.h -pathname.o: $(hdrdir)/ruby/internal/glob.h -pathname.o: $(hdrdir)/ruby/internal/globals.h -pathname.o: $(hdrdir)/ruby/internal/has/attribute.h -pathname.o: $(hdrdir)/ruby/internal/has/builtin.h -pathname.o: $(hdrdir)/ruby/internal/has/c_attribute.h -pathname.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h -pathname.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h -pathname.o: $(hdrdir)/ruby/internal/has/extension.h -pathname.o: $(hdrdir)/ruby/internal/has/feature.h -pathname.o: $(hdrdir)/ruby/internal/has/warning.h -pathname.o: $(hdrdir)/ruby/internal/intern/array.h -pathname.o: $(hdrdir)/ruby/internal/intern/bignum.h -pathname.o: $(hdrdir)/ruby/internal/intern/class.h -pathname.o: $(hdrdir)/ruby/internal/intern/compar.h -pathname.o: $(hdrdir)/ruby/internal/intern/complex.h -pathname.o: $(hdrdir)/ruby/internal/intern/cont.h -pathname.o: $(hdrdir)/ruby/internal/intern/dir.h -pathname.o: $(hdrdir)/ruby/internal/intern/enum.h -pathname.o: $(hdrdir)/ruby/internal/intern/enumerator.h -pathname.o: $(hdrdir)/ruby/internal/intern/error.h -pathname.o: $(hdrdir)/ruby/internal/intern/eval.h -pathname.o: $(hdrdir)/ruby/internal/intern/file.h -pathname.o: $(hdrdir)/ruby/internal/intern/hash.h -pathname.o: $(hdrdir)/ruby/internal/intern/io.h -pathname.o: $(hdrdir)/ruby/internal/intern/load.h -pathname.o: $(hdrdir)/ruby/internal/intern/marshal.h -pathname.o: $(hdrdir)/ruby/internal/intern/numeric.h -pathname.o: $(hdrdir)/ruby/internal/intern/object.h -pathname.o: $(hdrdir)/ruby/internal/intern/parse.h -pathname.o: $(hdrdir)/ruby/internal/intern/proc.h -pathname.o: $(hdrdir)/ruby/internal/intern/process.h -pathname.o: $(hdrdir)/ruby/internal/intern/random.h -pathname.o: $(hdrdir)/ruby/internal/intern/range.h -pathname.o: $(hdrdir)/ruby/internal/intern/rational.h -pathname.o: $(hdrdir)/ruby/internal/intern/re.h -pathname.o: $(hdrdir)/ruby/internal/intern/ruby.h -pathname.o: $(hdrdir)/ruby/internal/intern/select.h -pathname.o: $(hdrdir)/ruby/internal/intern/select/largesize.h -pathname.o: $(hdrdir)/ruby/internal/intern/signal.h -pathname.o: $(hdrdir)/ruby/internal/intern/sprintf.h -pathname.o: $(hdrdir)/ruby/internal/intern/string.h -pathname.o: $(hdrdir)/ruby/internal/intern/struct.h -pathname.o: $(hdrdir)/ruby/internal/intern/thread.h -pathname.o: $(hdrdir)/ruby/internal/intern/time.h -pathname.o: $(hdrdir)/ruby/internal/intern/variable.h -pathname.o: $(hdrdir)/ruby/internal/intern/vm.h -pathname.o: $(hdrdir)/ruby/internal/interpreter.h -pathname.o: $(hdrdir)/ruby/internal/iterator.h -pathname.o: $(hdrdir)/ruby/internal/memory.h -pathname.o: $(hdrdir)/ruby/internal/method.h -pathname.o: $(hdrdir)/ruby/internal/module.h -pathname.o: $(hdrdir)/ruby/internal/newobj.h -pathname.o: $(hdrdir)/ruby/internal/scan_args.h -pathname.o: $(hdrdir)/ruby/internal/special_consts.h -pathname.o: $(hdrdir)/ruby/internal/static_assert.h -pathname.o: $(hdrdir)/ruby/internal/stdalign.h -pathname.o: $(hdrdir)/ruby/internal/stdbool.h -pathname.o: $(hdrdir)/ruby/internal/stdckdint.h -pathname.o: $(hdrdir)/ruby/internal/symbol.h -pathname.o: $(hdrdir)/ruby/internal/value.h -pathname.o: $(hdrdir)/ruby/internal/value_type.h -pathname.o: $(hdrdir)/ruby/internal/variable.h -pathname.o: $(hdrdir)/ruby/internal/warning_push.h -pathname.o: $(hdrdir)/ruby/internal/xmalloc.h -pathname.o: $(hdrdir)/ruby/missing.h -pathname.o: $(hdrdir)/ruby/onigmo.h -pathname.o: $(hdrdir)/ruby/oniguruma.h -pathname.o: $(hdrdir)/ruby/ruby.h -pathname.o: $(hdrdir)/ruby/st.h -pathname.o: $(hdrdir)/ruby/subst.h -pathname.o: pathname.c -# AUTOGENERATED DEPENDENCIES END diff --git a/ext/pathname/extconf.rb b/ext/pathname/extconf.rb deleted file mode 100644 index b4e1617b9e..0000000000 --- a/ext/pathname/extconf.rb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: false -require 'mkmf' -create_makefile('pathname') diff --git a/ext/pathname/pathname.gemspec b/ext/pathname/pathname.gemspec deleted file mode 100644 index 890bc2fde9..0000000000 --- a/ext/pathname/pathname.gemspec +++ /dev/null @@ -1,32 +0,0 @@ -name = File.basename(__FILE__, ".gemspec") -version = ["lib", "ext/lib"].find do |dir| - break File.foreach(File.join(__dir__, dir, "#{name}.rb")) do |line| - /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 - end rescue nil -end - -Gem::Specification.new do |spec| - spec.name = name - spec.version = version - spec.authors = ["Tanaka Akira"] - spec.email = ["akr@fsij.org"] - - spec.summary = %q{Representation of the name of a file or directory on the filesystem} - spec.description = %q{Representation of the name of a file or directory on the filesystem} - spec.homepage = "https://github.com/ruby/pathname" - spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0") - spec.licenses = ["Ruby", "BSD-2-Clause"] - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = spec.homepage - - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end - spec.bindir = "exe" - spec.executables = [] - spec.require_paths = ["lib"] - spec.extensions = %w[ext/pathname/extconf.rb] -end diff --git a/ext/psych/depend b/ext/psych/depend index a4d9ca9a6a..95175841a2 100644 --- a/ext/psych/depend +++ b/ext/psych/depend @@ -152,6 +152,7 @@ psych.o: $(hdrdir)/ruby/internal/intern/re.h psych.o: $(hdrdir)/ruby/internal/intern/ruby.h psych.o: $(hdrdir)/ruby/internal/intern/select.h psych.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +psych.o: $(hdrdir)/ruby/internal/intern/set.h psych.o: $(hdrdir)/ruby/internal/intern/signal.h psych.o: $(hdrdir)/ruby/internal/intern/sprintf.h psych.o: $(hdrdir)/ruby/internal/intern/string.h @@ -329,6 +330,7 @@ psych_emitter.o: $(hdrdir)/ruby/internal/intern/re.h psych_emitter.o: $(hdrdir)/ruby/internal/intern/ruby.h psych_emitter.o: $(hdrdir)/ruby/internal/intern/select.h psych_emitter.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +psych_emitter.o: $(hdrdir)/ruby/internal/intern/set.h psych_emitter.o: $(hdrdir)/ruby/internal/intern/signal.h psych_emitter.o: $(hdrdir)/ruby/internal/intern/sprintf.h psych_emitter.o: $(hdrdir)/ruby/internal/intern/string.h @@ -506,6 +508,7 @@ psych_parser.o: $(hdrdir)/ruby/internal/intern/re.h psych_parser.o: $(hdrdir)/ruby/internal/intern/ruby.h psych_parser.o: $(hdrdir)/ruby/internal/intern/select.h psych_parser.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +psych_parser.o: $(hdrdir)/ruby/internal/intern/set.h psych_parser.o: $(hdrdir)/ruby/internal/intern/signal.h psych_parser.o: $(hdrdir)/ruby/internal/intern/sprintf.h psych_parser.o: $(hdrdir)/ruby/internal/intern/string.h @@ -683,6 +686,7 @@ psych_to_ruby.o: $(hdrdir)/ruby/internal/intern/re.h psych_to_ruby.o: $(hdrdir)/ruby/internal/intern/ruby.h psych_to_ruby.o: $(hdrdir)/ruby/internal/intern/select.h psych_to_ruby.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +psych_to_ruby.o: $(hdrdir)/ruby/internal/intern/set.h psych_to_ruby.o: $(hdrdir)/ruby/internal/intern/signal.h psych_to_ruby.o: $(hdrdir)/ruby/internal/intern/sprintf.h psych_to_ruby.o: $(hdrdir)/ruby/internal/intern/string.h @@ -860,6 +864,7 @@ psych_yaml_tree.o: $(hdrdir)/ruby/internal/intern/re.h psych_yaml_tree.o: $(hdrdir)/ruby/internal/intern/ruby.h psych_yaml_tree.o: $(hdrdir)/ruby/internal/intern/select.h psych_yaml_tree.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +psych_yaml_tree.o: $(hdrdir)/ruby/internal/intern/set.h psych_yaml_tree.o: $(hdrdir)/ruby/internal/intern/signal.h psych_yaml_tree.o: $(hdrdir)/ruby/internal/intern/sprintf.h psych_yaml_tree.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/pty/depend b/ext/pty/depend index 688df7159c..8fa018d084 100644 --- a/ext/pty/depend +++ b/ext/pty/depend @@ -138,6 +138,7 @@ pty.o: $(hdrdir)/ruby/internal/intern/re.h pty.o: $(hdrdir)/ruby/internal/intern/ruby.h pty.o: $(hdrdir)/ruby/internal/intern/select.h pty.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +pty.o: $(hdrdir)/ruby/internal/intern/set.h pty.o: $(hdrdir)/ruby/internal/intern/signal.h pty.o: $(hdrdir)/ruby/internal/intern/sprintf.h pty.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/rbconfig/sizeof/depend b/ext/rbconfig/sizeof/depend index 5f75fa8c76..7e6c950769 100644 --- a/ext/rbconfig/sizeof/depend +++ b/ext/rbconfig/sizeof/depend @@ -142,6 +142,7 @@ limits.o: $(hdrdir)/ruby/internal/intern/re.h limits.o: $(hdrdir)/ruby/internal/intern/ruby.h limits.o: $(hdrdir)/ruby/internal/intern/select.h limits.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +limits.o: $(hdrdir)/ruby/internal/intern/set.h limits.o: $(hdrdir)/ruby/internal/intern/signal.h limits.o: $(hdrdir)/ruby/internal/intern/sprintf.h limits.o: $(hdrdir)/ruby/internal/intern/string.h @@ -301,6 +302,7 @@ sizes.o: $(hdrdir)/ruby/internal/intern/re.h sizes.o: $(hdrdir)/ruby/internal/intern/ruby.h sizes.o: $(hdrdir)/ruby/internal/intern/select.h sizes.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +sizes.o: $(hdrdir)/ruby/internal/intern/set.h sizes.o: $(hdrdir)/ruby/internal/intern/signal.h sizes.o: $(hdrdir)/ruby/internal/intern/sprintf.h sizes.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/ripper/depend b/ext/ripper/depend index 410757875d..c8ba34962c 100644 --- a/ext/ripper/depend +++ b/ext/ripper/depend @@ -181,6 +181,7 @@ eventids1.o: $(hdrdir)/ruby/internal/intern/re.h eventids1.o: $(hdrdir)/ruby/internal/intern/ruby.h eventids1.o: $(hdrdir)/ruby/internal/intern/select.h eventids1.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +eventids1.o: $(hdrdir)/ruby/internal/intern/set.h eventids1.o: $(hdrdir)/ruby/internal/intern/signal.h eventids1.o: $(hdrdir)/ruby/internal/intern/sprintf.h eventids1.o: $(hdrdir)/ruby/internal/intern/string.h @@ -352,6 +353,7 @@ eventids2.o: $(hdrdir)/ruby/internal/intern/re.h eventids2.o: $(hdrdir)/ruby/internal/intern/ruby.h eventids2.o: $(hdrdir)/ruby/internal/intern/select.h eventids2.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +eventids2.o: $(hdrdir)/ruby/internal/intern/set.h eventids2.o: $(hdrdir)/ruby/internal/intern/signal.h eventids2.o: $(hdrdir)/ruby/internal/intern/sprintf.h eventids2.o: $(hdrdir)/ruby/internal/intern/string.h @@ -532,6 +534,7 @@ ripper.o: $(hdrdir)/ruby/internal/intern/re.h ripper.o: $(hdrdir)/ruby/internal/intern/ruby.h ripper.o: $(hdrdir)/ruby/internal/intern/select.h ripper.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ripper.o: $(hdrdir)/ruby/internal/intern/set.h ripper.o: $(hdrdir)/ruby/internal/intern/signal.h ripper.o: $(hdrdir)/ruby/internal/intern/sprintf.h ripper.o: $(hdrdir)/ruby/internal/intern/string.h @@ -772,6 +775,7 @@ ripper_init.o: $(hdrdir)/ruby/internal/intern/re.h ripper_init.o: $(hdrdir)/ruby/internal/intern/ruby.h ripper_init.o: $(hdrdir)/ruby/internal/intern/select.h ripper_init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ripper_init.o: $(hdrdir)/ruby/internal/intern/set.h ripper_init.o: $(hdrdir)/ruby/internal/intern/signal.h ripper_init.o: $(hdrdir)/ruby/internal/intern/sprintf.h ripper_init.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/socket/depend b/ext/socket/depend index 3b1ac216ff..6f2d280bfd 100644 --- a/ext/socket/depend +++ b/ext/socket/depend @@ -151,6 +151,7 @@ ancdata.o: $(hdrdir)/ruby/internal/intern/re.h ancdata.o: $(hdrdir)/ruby/internal/intern/ruby.h ancdata.o: $(hdrdir)/ruby/internal/intern/select.h ancdata.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ancdata.o: $(hdrdir)/ruby/internal/intern/set.h ancdata.o: $(hdrdir)/ruby/internal/intern/signal.h ancdata.o: $(hdrdir)/ruby/internal/intern/sprintf.h ancdata.o: $(hdrdir)/ruby/internal/intern/string.h @@ -365,6 +366,7 @@ basicsocket.o: $(hdrdir)/ruby/internal/intern/re.h basicsocket.o: $(hdrdir)/ruby/internal/intern/ruby.h basicsocket.o: $(hdrdir)/ruby/internal/intern/select.h basicsocket.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +basicsocket.o: $(hdrdir)/ruby/internal/intern/set.h basicsocket.o: $(hdrdir)/ruby/internal/intern/signal.h basicsocket.o: $(hdrdir)/ruby/internal/intern/sprintf.h basicsocket.o: $(hdrdir)/ruby/internal/intern/string.h @@ -579,6 +581,7 @@ constants.o: $(hdrdir)/ruby/internal/intern/re.h constants.o: $(hdrdir)/ruby/internal/intern/ruby.h constants.o: $(hdrdir)/ruby/internal/intern/select.h constants.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +constants.o: $(hdrdir)/ruby/internal/intern/set.h constants.o: $(hdrdir)/ruby/internal/intern/signal.h constants.o: $(hdrdir)/ruby/internal/intern/sprintf.h constants.o: $(hdrdir)/ruby/internal/intern/string.h @@ -794,6 +797,7 @@ ifaddr.o: $(hdrdir)/ruby/internal/intern/re.h ifaddr.o: $(hdrdir)/ruby/internal/intern/ruby.h ifaddr.o: $(hdrdir)/ruby/internal/intern/select.h ifaddr.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ifaddr.o: $(hdrdir)/ruby/internal/intern/set.h ifaddr.o: $(hdrdir)/ruby/internal/intern/signal.h ifaddr.o: $(hdrdir)/ruby/internal/intern/sprintf.h ifaddr.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1008,6 +1012,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1222,6 +1227,7 @@ ipsocket.o: $(hdrdir)/ruby/internal/intern/re.h ipsocket.o: $(hdrdir)/ruby/internal/intern/ruby.h ipsocket.o: $(hdrdir)/ruby/internal/intern/select.h ipsocket.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ipsocket.o: $(hdrdir)/ruby/internal/intern/set.h ipsocket.o: $(hdrdir)/ruby/internal/intern/signal.h ipsocket.o: $(hdrdir)/ruby/internal/intern/sprintf.h ipsocket.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1436,6 +1442,7 @@ option.o: $(hdrdir)/ruby/internal/intern/re.h option.o: $(hdrdir)/ruby/internal/intern/ruby.h option.o: $(hdrdir)/ruby/internal/intern/select.h option.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +option.o: $(hdrdir)/ruby/internal/intern/set.h option.o: $(hdrdir)/ruby/internal/intern/signal.h option.o: $(hdrdir)/ruby/internal/intern/sprintf.h option.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1650,6 +1657,7 @@ raddrinfo.o: $(hdrdir)/ruby/internal/intern/re.h raddrinfo.o: $(hdrdir)/ruby/internal/intern/ruby.h raddrinfo.o: $(hdrdir)/ruby/internal/intern/select.h raddrinfo.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +raddrinfo.o: $(hdrdir)/ruby/internal/intern/set.h raddrinfo.o: $(hdrdir)/ruby/internal/intern/signal.h raddrinfo.o: $(hdrdir)/ruby/internal/intern/sprintf.h raddrinfo.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1864,6 +1872,7 @@ socket.o: $(hdrdir)/ruby/internal/intern/re.h socket.o: $(hdrdir)/ruby/internal/intern/ruby.h socket.o: $(hdrdir)/ruby/internal/intern/select.h socket.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +socket.o: $(hdrdir)/ruby/internal/intern/set.h socket.o: $(hdrdir)/ruby/internal/intern/signal.h socket.o: $(hdrdir)/ruby/internal/intern/sprintf.h socket.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2078,6 +2087,7 @@ sockssocket.o: $(hdrdir)/ruby/internal/intern/re.h sockssocket.o: $(hdrdir)/ruby/internal/intern/ruby.h sockssocket.o: $(hdrdir)/ruby/internal/intern/select.h sockssocket.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +sockssocket.o: $(hdrdir)/ruby/internal/intern/set.h sockssocket.o: $(hdrdir)/ruby/internal/intern/signal.h sockssocket.o: $(hdrdir)/ruby/internal/intern/sprintf.h sockssocket.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2292,6 +2302,7 @@ tcpserver.o: $(hdrdir)/ruby/internal/intern/re.h tcpserver.o: $(hdrdir)/ruby/internal/intern/ruby.h tcpserver.o: $(hdrdir)/ruby/internal/intern/select.h tcpserver.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +tcpserver.o: $(hdrdir)/ruby/internal/intern/set.h tcpserver.o: $(hdrdir)/ruby/internal/intern/signal.h tcpserver.o: $(hdrdir)/ruby/internal/intern/sprintf.h tcpserver.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2506,6 +2517,7 @@ tcpsocket.o: $(hdrdir)/ruby/internal/intern/re.h tcpsocket.o: $(hdrdir)/ruby/internal/intern/ruby.h tcpsocket.o: $(hdrdir)/ruby/internal/intern/select.h tcpsocket.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +tcpsocket.o: $(hdrdir)/ruby/internal/intern/set.h tcpsocket.o: $(hdrdir)/ruby/internal/intern/signal.h tcpsocket.o: $(hdrdir)/ruby/internal/intern/sprintf.h tcpsocket.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2720,6 +2732,7 @@ udpsocket.o: $(hdrdir)/ruby/internal/intern/re.h udpsocket.o: $(hdrdir)/ruby/internal/intern/ruby.h udpsocket.o: $(hdrdir)/ruby/internal/intern/select.h udpsocket.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +udpsocket.o: $(hdrdir)/ruby/internal/intern/set.h udpsocket.o: $(hdrdir)/ruby/internal/intern/signal.h udpsocket.o: $(hdrdir)/ruby/internal/intern/sprintf.h udpsocket.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2934,6 +2947,7 @@ unixserver.o: $(hdrdir)/ruby/internal/intern/re.h unixserver.o: $(hdrdir)/ruby/internal/intern/ruby.h unixserver.o: $(hdrdir)/ruby/internal/intern/select.h unixserver.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +unixserver.o: $(hdrdir)/ruby/internal/intern/set.h unixserver.o: $(hdrdir)/ruby/internal/intern/signal.h unixserver.o: $(hdrdir)/ruby/internal/intern/sprintf.h unixserver.o: $(hdrdir)/ruby/internal/intern/string.h @@ -3148,6 +3162,7 @@ unixsocket.o: $(hdrdir)/ruby/internal/intern/re.h unixsocket.o: $(hdrdir)/ruby/internal/intern/ruby.h unixsocket.o: $(hdrdir)/ruby/internal/intern/select.h unixsocket.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +unixsocket.o: $(hdrdir)/ruby/internal/intern/set.h unixsocket.o: $(hdrdir)/ruby/internal/intern/signal.h unixsocket.o: $(hdrdir)/ruby/internal/intern/sprintf.h unixsocket.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index da42fbd27b..20f75d827d 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -23,8 +23,17 @@ struct inetsock_arg int type; VALUE resolv_timeout; VALUE connect_timeout; + VALUE open_timeout; }; +void +rsock_raise_user_specified_timeout(void) +{ + VALUE errno_module = rb_const_get(rb_cObject, rb_intern("Errno")); + VALUE etimedout_error = rb_const_get(errno_module, rb_intern("ETIMEDOUT")); + rb_raise(etimedout_error, "user specified timeout"); +} + static VALUE inetsock_cleanup(VALUE v) { @@ -44,6 +53,13 @@ inetsock_cleanup(VALUE v) return Qnil; } +static VALUE +current_clocktime() +{ + VALUE clock_monotnic_const = rb_const_get(rb_mProcess, rb_intern("CLOCK_MONOTONIC")); + return rb_funcall(rb_mProcess, rb_intern("clock_gettime"), 1, clock_monotnic_const); +} + static VALUE init_inetsock_internal(VALUE v) { @@ -54,12 +70,20 @@ init_inetsock_internal(VALUE v) int status = 0, local = 0; int family = AF_UNSPEC; const char *syscall = 0; + VALUE resolv_timeout = arg->resolv_timeout; VALUE connect_timeout = arg->connect_timeout; + VALUE open_timeout = arg->open_timeout; + VALUE timeout; + VALUE starts_at; + unsigned int timeout_msec; + + timeout = NIL_P(open_timeout) ? resolv_timeout : open_timeout; + timeout_msec = NIL_P(timeout) ? 0 : rsock_value_timeout_to_msec(timeout); + starts_at = current_clocktime(); arg->remote.res = rsock_addrinfo(arg->remote.host, arg->remote.serv, family, SOCK_STREAM, - (type == INET_SERVER) ? AI_PASSIVE : 0); - + (type == INET_SERVER) ? AI_PASSIVE : 0, timeout_msec); /* * Maybe also accept a local address @@ -67,7 +91,7 @@ init_inetsock_internal(VALUE v) if (type != INET_SERVER && (!NIL_P(arg->local.host) || !NIL_P(arg->local.serv))) { arg->local.res = rsock_addrinfo(arg->local.host, arg->local.serv, - family, SOCK_STREAM, 0); + family, SOCK_STREAM, 0, 0); } VALUE io = Qnil; @@ -122,8 +146,16 @@ init_inetsock_internal(VALUE v) syscall = "bind(2)"; } + if (NIL_P(open_timeout)) { + timeout = connect_timeout; + } else { + VALUE elapsed = rb_funcall(current_clocktime(), '-', 1, starts_at); + timeout = rb_funcall(open_timeout, '-', 1, elapsed); + if (rb_funcall(timeout, '<', 1, INT2FIX(0)) == Qtrue) rsock_raise_user_specified_timeout(); + } + if (status >= 0) { - status = rsock_connect(io, res->ai_addr, res->ai_addrlen, (type == INET_SOCKS), connect_timeout); + status = rsock_connect(io, res->ai_addr, res->ai_addrlen, (type == INET_SOCKS), timeout); syscall = "connect(2)"; } } @@ -172,8 +204,16 @@ init_inetsock_internal(VALUE v) #if FAST_FALLBACK_INIT_INETSOCK_IMPL == 0 VALUE -rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE local_host, VALUE local_serv, int type, VALUE resolv_timeout, VALUE connect_timeout, VALUE _fast_fallback, VALUE _test_mode_settings) -{ +rsock_init_inetsock( + VALUE self, VALUE remote_host, VALUE remote_serv, + VALUE local_host, VALUE local_serv, int type, + VALUE resolv_timeout, VALUE connect_timeout, VALUE open_timeout, + VALUE _fast_fallback, VALUE _test_mode_settings +) { + if (!NIL_P(open_timeout) && (!NIL_P(resolv_timeout) || !NIL_P(connect_timeout))) { + rb_raise(rb_eArgError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout"); + } + struct inetsock_arg arg; arg.self = self; arg.io = Qnil; @@ -186,6 +226,7 @@ rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE loca arg.type = type; arg.resolv_timeout = resolv_timeout; arg.connect_timeout = connect_timeout; + arg.open_timeout = open_timeout; return rb_ensure(init_inetsock_internal, (VALUE)&arg, inetsock_cleanup, (VALUE)&arg); } @@ -221,6 +262,7 @@ struct fast_fallback_inetsock_arg int type; VALUE resolv_timeout; VALUE connect_timeout; + VALUE open_timeout; const char *hostp, *portp; int *families; @@ -380,12 +422,22 @@ select_expires_at( struct timeval *resolution_delay, struct timeval *connection_attempt_delay, struct timeval *user_specified_resolv_timeout_at, - struct timeval *user_specified_connect_timeout_at + struct timeval *user_specified_connect_timeout_at, + struct timeval *user_specified_open_timeout_at ) { if (any_addrinfos(resolution_store)) { - return resolution_delay ? resolution_delay : connection_attempt_delay; + struct timeval *delay; + delay = resolution_delay ? resolution_delay : connection_attempt_delay; + + if (user_specified_open_timeout_at && + timercmp(user_specified_open_timeout_at, delay, <)) { + return user_specified_open_timeout_at; + } + return delay; } + if (user_specified_open_timeout_at) return user_specified_open_timeout_at; + struct timeval *timeout = NULL; if (user_specified_resolv_timeout_at) { @@ -503,6 +555,7 @@ init_fast_fallback_inetsock_internal(VALUE v) VALUE io = arg->io; VALUE resolv_timeout = arg->resolv_timeout; VALUE connect_timeout = arg->connect_timeout; + VALUE open_timeout = arg->open_timeout; VALUE test_mode_settings = arg->test_mode_settings; struct addrinfo *remote_ai = NULL, *local_ai = NULL; int connected_fd = -1, status = 0, local_status = 0; @@ -549,20 +602,31 @@ init_fast_fallback_inetsock_internal(VALUE v) struct timeval *user_specified_resolv_timeout_at = NULL; struct timeval user_specified_connect_timeout_storage; struct timeval *user_specified_connect_timeout_at = NULL; + struct timeval user_specified_open_timeout_storage; + struct timeval *user_specified_open_timeout_at = NULL; struct timespec now = current_clocktime_ts(); + if (!NIL_P(open_timeout)) { + struct timeval open_timeout_tv = rb_time_interval(open_timeout); + user_specified_open_timeout_storage = add_ts_to_tv(open_timeout_tv, now); + user_specified_open_timeout_at = &user_specified_open_timeout_storage; + } + /* start of hostname resolution */ if (arg->family_size == 1) { arg->wait = -1; arg->getaddrinfo_shared = NULL; int family = arg->families[0]; + unsigned int t = NIL_P(resolv_timeout) ? 0 : rsock_value_timeout_to_msec(resolv_timeout); + arg->remote.res = rsock_addrinfo( arg->remote.host, arg->remote.serv, family, SOCK_STREAM, - 0 + 0, + t ); if (family == AF_INET6) { @@ -848,7 +912,8 @@ init_fast_fallback_inetsock_internal(VALUE v) resolution_delay_expires_at, connection_attempt_delay_expires_at, user_specified_resolv_timeout_at, - user_specified_connect_timeout_at + user_specified_connect_timeout_at, + user_specified_open_timeout_at ); if (ends_at) { delay = tv_to_timeout(ends_at, now); @@ -1101,6 +1166,8 @@ init_fast_fallback_inetsock_internal(VALUE v) } } + if (is_timeout_tv(user_specified_open_timeout_at, now)) rsock_raise_user_specified_timeout(); + if (!any_addrinfos(&resolution_store)) { if (!in_progress_fds(arg->connection_attempt_fds_size) && resolution_store.is_all_finished) { @@ -1122,9 +1189,7 @@ init_fast_fallback_inetsock_internal(VALUE v) resolution_store.is_all_finished) && (is_timeout_tv(user_specified_connect_timeout_at, now) || !in_progress_fds(arg->connection_attempt_fds_size))) { - VALUE errno_module = rb_const_get(rb_cObject, rb_intern("Errno")); - VALUE etimedout_error = rb_const_get(errno_module, rb_intern("ETIMEDOUT")); - rb_raise(etimedout_error, "user specified timeout"); + rsock_raise_user_specified_timeout(); } } } @@ -1214,8 +1279,16 @@ fast_fallback_inetsock_cleanup(VALUE v) } VALUE -rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE local_host, VALUE local_serv, int type, VALUE resolv_timeout, VALUE connect_timeout, VALUE fast_fallback, VALUE test_mode_settings) -{ +rsock_init_inetsock( + VALUE self, VALUE remote_host, VALUE remote_serv, + VALUE local_host, VALUE local_serv, int type, + VALUE resolv_timeout, VALUE connect_timeout, VALUE open_timeout, + VALUE fast_fallback, VALUE test_mode_settings +) { + if (!NIL_P(open_timeout) && (!NIL_P(resolv_timeout) || !NIL_P(connect_timeout))) { + rb_raise(rb_eArgError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout"); + } + if (type == INET_CLIENT && FAST_FALLBACK_INIT_INETSOCK_IMPL == 1 && RTEST(fast_fallback)) { struct rb_addrinfo *local_res = NULL; char *hostp, *portp; @@ -1237,6 +1310,7 @@ rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE loca local_serv, AF_UNSPEC, SOCK_STREAM, + 0, 0 ); @@ -1271,6 +1345,7 @@ rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE loca fast_fallback_arg.type = type; fast_fallback_arg.resolv_timeout = resolv_timeout; fast_fallback_arg.connect_timeout = connect_timeout; + fast_fallback_arg.open_timeout = open_timeout; fast_fallback_arg.hostp = hostp; fast_fallback_arg.portp = portp; fast_fallback_arg.additional_flags = additional_flags; @@ -1307,6 +1382,7 @@ rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE loca arg.type = type; arg.resolv_timeout = resolv_timeout; arg.connect_timeout = connect_timeout; + arg.open_timeout = open_timeout; return rb_ensure(init_inetsock_internal, (VALUE)&arg, inetsock_cleanup, (VALUE)&arg); @@ -1492,7 +1568,7 @@ static VALUE ip_s_getaddress(VALUE obj, VALUE host) { union_sockaddr addr; - struct rb_addrinfo *res = rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, 0); + struct rb_addrinfo *res = rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, 0, 0); socklen_t len = res->ai->ai_addrlen; /* just take the first one */ diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index 60dd45bd4f..1e30861efa 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -643,6 +643,7 @@ class Socket < BasicSocket # # [:resolv_timeout] Specifies the timeout in seconds from when the hostname resolution starts. # [:connect_timeout] This method sequentially attempts connecting to all candidate destination addresses.
The +connect_timeout+ specifies the timeout in seconds from the start of the connection attempt to the last candidate.
By default, all connection attempts continue until the timeout occurs.
When +fast_fallback:false+ is explicitly specified,
a timeout is set for each connection attempt and any connection attempt that exceeds its timeout will be canceled. + # [:open_timeout] Specifies the timeout in seconds from the start of the method execution.
If this timeout is reached while there are still addresses that have not yet been attempted for connection, no further attempts will be made.
If this option is specified together with other timeout options, an +ArgumentError+ will be raised. # [:fast_fallback] Enables the Happy Eyeballs Version 2 algorithm (enabled by default). # # If a block is given, the block is called with the socket. @@ -656,11 +657,16 @@ class Socket < BasicSocket # sock.close_write # puts sock.read # } - def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, fast_fallback: tcp_fast_fallback, &) # :yield: socket + def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, open_timeout: nil, fast_fallback: tcp_fast_fallback, &) # :yield: socket + + if open_timeout && (connect_timeout || resolv_timeout) + raise ArgumentError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout" + end + sock = if fast_fallback && !(host && ip_address?(host)) - tcp_with_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:) + tcp_with_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) else - tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:) + tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) end if block_given? @@ -674,7 +680,7 @@ class Socket < BasicSocket end end - def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil) + def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, open_timeout: nil) if local_host || local_port local_addrinfos = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, timeout: resolv_timeout) resolving_family_names = local_addrinfos.map { |lai| ADDRESS_FAMILIES.key(lai.afamily) }.uniq @@ -692,12 +698,13 @@ class Socket < BasicSocket resolution_delay_expires_at = nil connection_attempt_delay_expires_at = nil user_specified_connect_timeout_at = nil + user_specified_open_timeout_at = open_timeout ? now + open_timeout : nil last_error = nil last_error_from_thread = false if resolving_family_names.size == 1 family_name = resolving_family_names.first - addrinfos = Addrinfo.getaddrinfo(host, port, family_name, :STREAM, timeout: resolv_timeout) + addrinfos = Addrinfo.getaddrinfo(host, port, ADDRESS_FAMILIES[:family_name], :STREAM, timeout: resolv_timeout) resolution_store.add_resolved(family_name, addrinfos) hostname_resolution_result = nil hostname_resolution_notifier = nil @@ -784,7 +791,10 @@ class Socket < BasicSocket ends_at = if resolution_store.any_addrinfos? - resolution_delay_expires_at || connection_attempt_delay_expires_at + [(resolution_delay_expires_at || connection_attempt_delay_expires_at), + user_specified_open_timeout_at].compact.min + elsif user_specified_open_timeout_at + user_specified_open_timeout_at else [user_specified_resolv_timeout_at, user_specified_connect_timeout_at].compact.max end @@ -885,6 +895,8 @@ class Socket < BasicSocket end end + raise(Errno::ETIMEDOUT, 'user specified timeout') if expired?(now, user_specified_open_timeout_at) + if resolution_store.empty_addrinfos? if connecting_sockets.empty? && resolution_store.resolved_all_families? if last_error_from_thread @@ -912,7 +924,7 @@ class Socket < BasicSocket end end - def self.tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:) + def self.tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) last_error = nil ret = nil @@ -921,7 +933,10 @@ class Socket < BasicSocket local_addr_list = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, nil) end - Addrinfo.foreach(host, port, nil, :STREAM, timeout: resolv_timeout) {|ai| + timeout = open_timeout ? open_timeout : resolv_timeout + starts_at = current_clock_time + + Addrinfo.foreach(host, port, nil, :STREAM, timeout:) {|ai| if local_addr_list local_addr = local_addr_list.find {|local_ai| local_ai.afamily == ai.afamily } next unless local_addr @@ -929,9 +944,10 @@ class Socket < BasicSocket local_addr = nil end begin + timeout = open_timeout ? open_timeout - (current_clock_time - starts_at) : connect_timeout sock = local_addr ? - ai.connect_from(local_addr, timeout: connect_timeout) : - ai.connect(timeout: connect_timeout) + ai.connect_from(local_addr, timeout:) : + ai.connect(timeout:) rescue SystemCallError last_error = $! next diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index 91e2be1148..bc6c303c36 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -293,10 +293,22 @@ rb_freeaddrinfo(struct rb_addrinfo *ai) xfree(ai); } +unsigned int +rsock_value_timeout_to_msec(VALUE timeout) +{ + double seconds = NUM2DBL(timeout); + if (seconds < 0) rb_raise(rb_eArgError, "timeout must not be negative"); + + double msec = seconds * 1000.0; + if (msec > UINT_MAX) rb_raise(rb_eArgError, "timeout too large"); + + return (unsigned int)(msec + 0.5); +} + #if GETADDRINFO_IMPL == 0 static int -rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai) +rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, unsigned int _timeout) { return getaddrinfo(hostp, portp, hints, ai); } @@ -334,7 +346,7 @@ fork_safe_getaddrinfo(void *arg) } static int -rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai) +rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, unsigned int _timeout) { struct getaddrinfo_arg arg; MEMZERO(&arg, struct getaddrinfo_arg, 1); @@ -352,13 +364,14 @@ struct getaddrinfo_arg char *node, *service; struct addrinfo hints; struct addrinfo *ai; - int err, gai_errno, refcount, done, cancelled; + int err, gai_errno, refcount, done, cancelled, timedout; rb_nativethread_lock_t lock; rb_nativethread_cond_t cond; + unsigned int timeout; }; static struct getaddrinfo_arg * -allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addrinfo *hints) +allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addrinfo *hints, unsigned int timeout) { size_t hostp_offset = sizeof(struct getaddrinfo_arg); size_t portp_offset = hostp_offset + (hostp ? strlen(hostp) + 1 : 0); @@ -392,7 +405,8 @@ allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addr arg->ai = NULL; arg->refcount = 2; - arg->done = arg->cancelled = 0; + arg->done = arg->cancelled = arg->timedout = 0; + arg->timeout = timeout; rb_nativethread_lock_initialize(&arg->lock); rb_native_cond_initialize(&arg->cond); @@ -451,7 +465,16 @@ wait_getaddrinfo(void *ptr) struct getaddrinfo_arg *arg = (struct getaddrinfo_arg *)ptr; rb_nativethread_lock_lock(&arg->lock); while (!arg->done && !arg->cancelled) { - rb_native_cond_wait(&arg->cond, &arg->lock); + unsigned long msec = arg->timeout; + if (msec > 0) { + rb_native_cond_timedwait(&arg->cond, &arg->lock, msec); + if (!arg->done) { + arg->cancelled = 1; + arg->timedout = 1; + } + } else { + rb_native_cond_wait(&arg->cond, &arg->lock); + } } rb_nativethread_lock_unlock(&arg->lock); return 0; @@ -490,16 +513,16 @@ fork_safe_do_getaddrinfo(void *ptr) } static int -rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai) +rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, unsigned int timeout) { int retry; struct getaddrinfo_arg *arg; - int err = 0, gai_errno = 0; + int err = 0, gai_errno = 0, timedout = 0; start: retry = 0; - arg = allocate_getaddrinfo_arg(hostp, portp, hints); + arg = allocate_getaddrinfo_arg(hostp, portp, hints, timeout); if (!arg) { return EAI_MEMORY; } @@ -525,6 +548,7 @@ start: } else if (arg->cancelled) { retry = 1; + timedout = arg->timedout; } else { // If already interrupted, rb_thread_call_without_gvl2 may return without calling wait_getaddrinfo. @@ -538,6 +562,8 @@ start: if (need_free) free_getaddrinfo_arg(arg); + if (timedout) rsock_raise_user_specified_timeout(); + // If the current thread is interrupted by asynchronous exception, the following raises the exception. // But if the current thread is interrupted by timer thread, the following returns; we need to manually retry. rb_thread_check_ints(); @@ -715,7 +741,7 @@ rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, { int retry; struct getnameinfo_arg *arg; - int err, gni_errno = 0; + int err = 0, gni_errno = 0; start: retry = 0; @@ -941,7 +967,7 @@ rb_scheduler_getaddrinfo(VALUE scheduler, VALUE host, const char *service, } struct rb_addrinfo* -rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack) +rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack, unsigned int timeout) { struct rb_addrinfo* res = NULL; struct addrinfo *ai; @@ -976,7 +1002,7 @@ rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_h } if (!resolved) { - error = rb_getaddrinfo(hostp, portp, hints, &ai); + error = rb_getaddrinfo(hostp, portp, hints, &ai, timeout); if (error == 0) { res = (struct rb_addrinfo *)xmalloc(sizeof(struct rb_addrinfo)); res->allocated_by_malloc = 0; @@ -1009,7 +1035,7 @@ rsock_fd_family(int fd) } struct rb_addrinfo* -rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags) +rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags, unsigned int timeout) { struct addrinfo hints; @@ -1017,7 +1043,7 @@ rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags) hints.ai_family = family; hints.ai_socktype = socktype; hints.ai_flags = flags; - return rsock_getaddrinfo(host, port, &hints, 1); + return rsock_getaddrinfo(host, port, &hints, 1, timeout); } VALUE @@ -1211,6 +1237,7 @@ addrinfo_memsize(const void *ptr) static const rb_data_type_t addrinfo_type = { "socket/addrinfo", {addrinfo_mark, addrinfo_free, addrinfo_memsize,}, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_WB_PROTECTED, }; static VALUE @@ -1248,7 +1275,7 @@ alloc_addrinfo(void) } static void -init_addrinfo(rb_addrinfo_t *rai, struct sockaddr *sa, socklen_t len, +init_addrinfo(VALUE self, rb_addrinfo_t *rai, struct sockaddr *sa, socklen_t len, int pfamily, int socktype, int protocol, VALUE canonname, VALUE inspectname) { @@ -1260,8 +1287,8 @@ init_addrinfo(rb_addrinfo_t *rai, struct sockaddr *sa, socklen_t len, rai->pfamily = pfamily; rai->socktype = socktype; rai->protocol = protocol; - rai->canonname = canonname; - rai->inspectname = inspectname; + RB_OBJ_WRITE(self, &rai->canonname, canonname); + RB_OBJ_WRITE(self, &rai->inspectname, inspectname); } VALUE @@ -1274,7 +1301,7 @@ rsock_addrinfo_new(struct sockaddr *addr, socklen_t len, a = addrinfo_s_allocate(rb_cAddrinfo); DATA_PTR(a) = rai = alloc_addrinfo(); - init_addrinfo(rai, addr, len, family, socktype, protocol, canonname, inspectname); + init_addrinfo(a, rai, addr, len, family, socktype, protocol, canonname, inspectname); return a; } @@ -1299,7 +1326,8 @@ call_getaddrinfo(VALUE node, VALUE service, hints.ai_flags = NUM2INT(flags); } - res = rsock_getaddrinfo(node, service, &hints, socktype_hack); + unsigned int t = NIL_P(timeout) ? 0 : rsock_value_timeout_to_msec(timeout); + res = rsock_getaddrinfo(node, service, &hints, socktype_hack, t); if (res == NULL) rb_raise(rb_eSocket, "host not found"); @@ -1309,7 +1337,7 @@ call_getaddrinfo(VALUE node, VALUE service, static VALUE make_inspectname(VALUE node, VALUE service, struct addrinfo *res); static void -init_addrinfo_getaddrinfo(rb_addrinfo_t *rai, VALUE node, VALUE service, +init_addrinfo_getaddrinfo(VALUE self, rb_addrinfo_t *rai, VALUE node, VALUE service, VALUE family, VALUE socktype, VALUE protocol, VALUE flags, VALUE inspectnode, VALUE inspectservice) { @@ -1323,7 +1351,7 @@ init_addrinfo_getaddrinfo(rb_addrinfo_t *rai, VALUE node, VALUE service, OBJ_FREEZE(canonname); } - init_addrinfo(rai, res->ai->ai_addr, res->ai->ai_addrlen, + init_addrinfo(self, rai, res->ai->ai_addr, res->ai->ai_addrlen, NUM2INT(family), NUM2INT(socktype), NUM2INT(protocol), canonname, inspectname); @@ -1435,7 +1463,7 @@ addrinfo_list_new(VALUE node, VALUE service, VALUE family, VALUE socktype, VALUE #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN static void -init_unix_addrinfo(rb_addrinfo_t *rai, VALUE path, int socktype) +init_unix_addrinfo(VALUE self, rb_addrinfo_t *rai, VALUE path, int socktype) { struct sockaddr_un un; socklen_t len; @@ -1451,7 +1479,7 @@ init_unix_addrinfo(rb_addrinfo_t *rai, VALUE path, int socktype) memcpy((void*)&un.sun_path, RSTRING_PTR(path), RSTRING_LEN(path)); len = rsock_unix_sockaddr_len(path); - init_addrinfo(rai, (struct sockaddr *)&un, len, + init_addrinfo(self, rai, (struct sockaddr *)&un, len, PF_UNIX, socktype, 0, Qnil, Qnil); } @@ -1555,7 +1583,7 @@ addrinfo_initialize(int argc, VALUE *argv, VALUE self) flags |= AI_NUMERICSERV; #endif - init_addrinfo_getaddrinfo(rai, numericnode, service, + init_addrinfo_getaddrinfo(self, rai, numericnode, service, INT2NUM(i_pfamily ? i_pfamily : af), INT2NUM(i_socktype), INT2NUM(i_protocol), INT2NUM(flags), nodename, service); @@ -1567,7 +1595,7 @@ addrinfo_initialize(int argc, VALUE *argv, VALUE self) { VALUE path = rb_ary_entry(sockaddr_ary, 1); StringValue(path); - init_unix_addrinfo(rai, path, SOCK_STREAM); + init_unix_addrinfo(self, rai, path, SOCK_STREAM); break; } #endif @@ -1580,7 +1608,7 @@ addrinfo_initialize(int argc, VALUE *argv, VALUE self) StringValue(sockaddr_arg); sockaddr_ptr = (struct sockaddr *)RSTRING_PTR(sockaddr_arg); sockaddr_len = RSTRING_SOCKLEN(sockaddr_arg); - init_addrinfo(rai, sockaddr_ptr, sockaddr_len, + init_addrinfo(self, rai, sockaddr_ptr, sockaddr_len, i_pfamily, i_socktype, i_protocol, canonname, inspectname); } @@ -2169,7 +2197,7 @@ addrinfo_mload(VALUE self, VALUE ary) } DATA_PTR(self) = rai = alloc_addrinfo(); - init_addrinfo(rai, &ss.addr, len, + init_addrinfo(self, rai, &ss.addr, len, pfamily, socktype, protocol, canonname, inspectname); return self; @@ -2937,7 +2965,7 @@ addrinfo_s_unix(int argc, VALUE *argv, VALUE self) addr = addrinfo_s_allocate(rb_cAddrinfo); DATA_PTR(addr) = rai = alloc_addrinfo(); - init_unix_addrinfo(rai, path, socktype); + init_unix_addrinfo(self, rai, path, socktype); return addr; } diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index 54a5381da4..29b8afc163 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -292,8 +292,8 @@ extern VALUE rb_eResolution; #ifdef SOCKS extern VALUE rb_cSOCKSSocket; # ifndef SOCKS5 -void SOCKSinit(); -int Rconnect(); +void SOCKSinit(char *); +int Rconnect(int, const struct sockaddr *, socklen_t); # endif #endif @@ -327,8 +327,8 @@ void rb_freeaddrinfo(struct rb_addrinfo *ai); VALUE rsock_freeaddrinfo(VALUE arg); int rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags); int rsock_fd_family(int fd); -struct rb_addrinfo *rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags); -struct rb_addrinfo *rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack); +struct rb_addrinfo *rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags, unsigned int timeout); +struct rb_addrinfo *rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack, unsigned int timeout); VALUE rsock_fd_socket_addrinfo(int fd, struct sockaddr *addr, socklen_t len); VALUE rsock_io_socket_addrinfo(VALUE io, struct sockaddr *addr, socklen_t len); @@ -355,7 +355,7 @@ int rsock_socket(int domain, int type, int proto); int rsock_detect_cloexec(int fd); VALUE rsock_init_sock(VALUE sock, int fd); VALUE rsock_sock_s_socketpair(int argc, VALUE *argv, VALUE klass); -VALUE rsock_init_inetsock(VALUE sock, VALUE remote_host, VALUE remote_serv, VALUE local_host, VALUE local_serv, int type, VALUE resolv_timeout, VALUE connect_timeout, VALUE fast_fallback, VALUE test_mode_settings); +VALUE rsock_init_inetsock(VALUE sock, VALUE remote_host, VALUE remote_serv, VALUE local_host, VALUE local_serv, int type, VALUE resolv_timeout, VALUE connect_timeout, VALUE open_timeout, VALUE fast_fallback, VALUE test_mode_settings); VALUE rsock_init_unixsock(VALUE sock, VALUE path, int server); struct rsock_send_arg { @@ -453,6 +453,9 @@ void free_fast_fallback_getaddrinfo_shared(struct fast_fallback_getaddrinfo_shar # endif #endif +unsigned int rsock_value_timeout_to_msec(VALUE); +NORETURN(void rsock_raise_user_specified_timeout(void)); + void rsock_init_basicsocket(void); void rsock_init_ipsocket(void); void rsock_init_tcpsocket(void); diff --git a/ext/socket/socket.c b/ext/socket/socket.c index 8f593ca0bd..26bf0bae8c 100644 --- a/ext/socket/socket.c +++ b/ext/socket/socket.c @@ -965,7 +965,7 @@ sock_s_gethostbyname(VALUE obj, VALUE host) { rb_warn("Socket.gethostbyname is deprecated; use Addrinfo.getaddrinfo instead."); struct rb_addrinfo *res = - rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME); + rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME, 0); return rsock_make_hostent(host, res, sock_sockaddr); } @@ -1183,7 +1183,7 @@ sock_s_getaddrinfo(int argc, VALUE *argv, VALUE _) norevlookup = rsock_do_not_reverse_lookup; } - res = rsock_getaddrinfo(host, port, &hints, 0); + res = rsock_getaddrinfo(host, port, &hints, 0, 0); ret = make_addrinfo(res, norevlookup); rb_freeaddrinfo(res); @@ -1279,7 +1279,7 @@ sock_s_getnameinfo(int argc, VALUE *argv, VALUE _) hints.ai_socktype = (fl & NI_DGRAM) ? SOCK_DGRAM : SOCK_STREAM; /* af */ hints.ai_family = NIL_P(af) ? PF_UNSPEC : rsock_family_arg(af); - res = rsock_getaddrinfo(host, port, &hints, 0); + res = rsock_getaddrinfo(host, port, &hints, 0, 0); sap = res->ai->ai_addr; salen = res->ai->ai_addrlen; } @@ -1335,7 +1335,7 @@ sock_s_getnameinfo(int argc, VALUE *argv, VALUE _) static VALUE sock_s_pack_sockaddr_in(VALUE self, VALUE port, VALUE host) { - struct rb_addrinfo *res = rsock_addrinfo(host, port, AF_UNSPEC, 0, 0); + struct rb_addrinfo *res = rsock_addrinfo(host, port, AF_UNSPEC, 0, 0, 0); VALUE addr = rb_str_new((char*)res->ai->ai_addr, res->ai->ai_addrlen); rb_freeaddrinfo(res); diff --git a/ext/socket/sockssocket.c b/ext/socket/sockssocket.c index 1031812bef..30860ea257 100644 --- a/ext/socket/sockssocket.c +++ b/ext/socket/sockssocket.c @@ -30,11 +30,12 @@ socks_init(VALUE sock, VALUE host, VALUE port) static int init = 0; if (init == 0) { - SOCKSinit("ruby"); + char progname[] = "ruby"; + SOCKSinit(progname); init = 1; } - return rsock_init_inetsock(sock, host, port, Qnil, Qnil, INET_SOCKS, Qnil, Qnil, Qfalse, Qnil); + return rsock_init_inetsock(sock, host, port, Qnil, Qnil, INET_SOCKS, Qnil, Qnil, Qnil, Qfalse, Qnil); } #ifdef SOCKS5 diff --git a/ext/socket/tcpserver.c b/ext/socket/tcpserver.c index 8206fe46a9..0069f3c703 100644 --- a/ext/socket/tcpserver.c +++ b/ext/socket/tcpserver.c @@ -36,7 +36,7 @@ tcp_svr_init(int argc, VALUE *argv, VALUE sock) VALUE hostname, port; rb_scan_args(argc, argv, "011", &hostname, &port); - return rsock_init_inetsock(sock, hostname, port, Qnil, Qnil, INET_SERVER, Qnil, Qnil, Qfalse, Qnil); + return rsock_init_inetsock(sock, hostname, port, Qnil, Qnil, INET_SERVER, Qnil, Qnil, Qnil, Qfalse, Qnil); } /* diff --git a/ext/socket/tcpsocket.c b/ext/socket/tcpsocket.c index 28527f632f..22c9f28ab7 100644 --- a/ext/socket/tcpsocket.c +++ b/ext/socket/tcpsocket.c @@ -35,6 +35,7 @@ * * [:resolv_timeout] Specifies the timeout in seconds from when the hostname resolution starts. * [:connect_timeout] This method sequentially attempts connecting to all candidate destination addresses.
The +connect_timeout+ specifies the timeout in seconds from the start of the connection attempt to the last candidate.
By default, all connection attempts continue until the timeout occurs.
When +fast_fallback:false+ is explicitly specified,
a timeout is set for each connection attempt and any connection attempt that exceeds its timeout will be canceled. + * [:open_timeout] Specifies the timeout in seconds from the start of the method execution.
If this timeout is reached while there are still addresses that have not yet been attempted for connection, no further attempts will be made.
If this option is specified together with other timeout options, an +ArgumentError+ will be raised. * [:fast_fallback] Enables the Happy Eyeballs Version 2 algorithm (enabled by default). */ static VALUE @@ -43,29 +44,32 @@ tcp_init(int argc, VALUE *argv, VALUE sock) VALUE remote_host, remote_serv; VALUE local_host, local_serv; VALUE opt; - static ID keyword_ids[4]; - VALUE kwargs[4]; + static ID keyword_ids[5]; + VALUE kwargs[5]; VALUE resolv_timeout = Qnil; VALUE connect_timeout = Qnil; + VALUE open_timeout = Qnil; VALUE fast_fallback = Qnil; VALUE test_mode_settings = Qnil; if (!keyword_ids[0]) { CONST_ID(keyword_ids[0], "resolv_timeout"); CONST_ID(keyword_ids[1], "connect_timeout"); - CONST_ID(keyword_ids[2], "fast_fallback"); - CONST_ID(keyword_ids[3], "test_mode_settings"); + CONST_ID(keyword_ids[2], "open_timeout"); + CONST_ID(keyword_ids[3], "fast_fallback"); + CONST_ID(keyword_ids[4], "test_mode_settings"); } rb_scan_args(argc, argv, "22:", &remote_host, &remote_serv, &local_host, &local_serv, &opt); if (!NIL_P(opt)) { - rb_get_kwargs(opt, keyword_ids, 0, 4, kwargs); + rb_get_kwargs(opt, keyword_ids, 0, 5, kwargs); if (kwargs[0] != Qundef) { resolv_timeout = kwargs[0]; } if (kwargs[1] != Qundef) { connect_timeout = kwargs[1]; } - if (kwargs[2] != Qundef) { fast_fallback = kwargs[2]; } - if (kwargs[3] != Qundef) { test_mode_settings = kwargs[3]; } + if (kwargs[2] != Qundef) { open_timeout = kwargs[2]; } + if (kwargs[3] != Qundef) { fast_fallback = kwargs[3]; } + if (kwargs[4] != Qundef) { test_mode_settings = kwargs[4]; } } if (fast_fallback == Qnil) { @@ -75,8 +79,8 @@ tcp_init(int argc, VALUE *argv, VALUE sock) return rsock_init_inetsock(sock, remote_host, remote_serv, local_host, local_serv, INET_CLIENT, - resolv_timeout, connect_timeout, fast_fallback, - test_mode_settings); + resolv_timeout, connect_timeout, open_timeout, + fast_fallback, test_mode_settings); } static VALUE @@ -109,7 +113,7 @@ tcp_s_gethostbyname(VALUE obj, VALUE host) { rb_warn("TCPSocket.gethostbyname is deprecated; use Addrinfo.getaddrinfo instead."); struct rb_addrinfo *res = - rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME); + rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME, 0); return rsock_make_hostent(host, res, tcp_sockaddr); } diff --git a/ext/socket/udpsocket.c b/ext/socket/udpsocket.c index a984933c9f..5538f24523 100644 --- a/ext/socket/udpsocket.c +++ b/ext/socket/udpsocket.c @@ -84,7 +84,7 @@ udp_connect(VALUE self, VALUE host, VALUE port) { struct udp_arg arg = {.io = self}; - arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0); + arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0, 0); int result = (int)rb_ensure(udp_connect_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); if (!result) { @@ -129,7 +129,7 @@ udp_bind(VALUE self, VALUE host, VALUE port) { struct udp_arg arg = {.io = self}; - arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0); + arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0, 0); VALUE result = rb_ensure(udp_bind_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); if (!result) { @@ -212,7 +212,7 @@ udp_send(int argc, VALUE *argv, VALUE sock) GetOpenFile(sock, arg.fptr); arg.sarg.fd = arg.fptr->fd; arg.sarg.flags = NUM2INT(flags); - arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0); + arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0, 0); ret = rb_ensure(udp_send_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); if (!ret) rsock_sys_fail_host_port("sendto(2)", host, port); diff --git a/ext/stringio/depend b/ext/stringio/depend index 9a8979829b..3a82ad0a11 100644 --- a/ext/stringio/depend +++ b/ext/stringio/depend @@ -138,6 +138,7 @@ stringio.o: $(hdrdir)/ruby/internal/intern/re.h stringio.o: $(hdrdir)/ruby/internal/intern/ruby.h stringio.o: $(hdrdir)/ruby/internal/intern/select.h stringio.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +stringio.o: $(hdrdir)/ruby/internal/intern/set.h stringio.o: $(hdrdir)/ruby/internal/intern/signal.h stringio.o: $(hdrdir)/ruby/internal/intern/sprintf.h stringio.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 26287dd1b8..3003939e10 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -36,6 +36,19 @@ STRINGIO_VERSION = "3.1.8.dev"; # define rb_class_new_instance_kw(argc, argv, klass, kw_splat) rb_class_new_instance(argc, argv, klass) #endif +static inline bool +str_chilled_p(VALUE str) +{ +#if (RUBY_API_VERSION_MAJOR == 3 && RUBY_API_VERSION_MINOR >= 4) || RUBY_API_VERSION_MAJOR >= 4 + // Do not attempt to modify chilled strings on Ruby 3.4+ + // RUBY_FL_USER2 == STR_CHILLED_LITERAL + // RUBY_FL_USER3 == STR_CHILLED_SYMBOL_TO_S + return FL_TEST_RAW(str, RUBY_FL_USER2 | RUBY_FL_USER3); +#else + return false; +#endif +} + #ifndef HAVE_TYPE_RB_IO_MODE_T typedef int rb_io_mode_t; #endif @@ -1865,14 +1878,7 @@ strio_set_encoding(int argc, VALUE *argv, VALUE self) } } ptr->enc = enc; - if (!NIL_P(ptr->string) && WRITABLE(self) -#if (RUBY_API_VERSION_MAJOR == 3 && RUBY_API_VERSION_MINOR >= 4) || RUBY_API_VERSION_MAJOR >= 4 - // Do not attempt to modify chilled strings on Ruby 3.4+ - // RUBY_FL_USER2 == STR_CHILLED_LITERAL - // RUBY_FL_USER3 == STR_CHILLED_SYMBOL_TO_S - && !FL_TEST_RAW(ptr->string, RUBY_FL_USER2 | RUBY_FL_USER3) -#endif - ) { + if (!NIL_P(ptr->string) && WRITABLE(self) && !str_chilled_p(ptr->string)) { rb_enc_associate(ptr->string, enc); } diff --git a/ext/strscan/depend b/ext/strscan/depend index 8dbae206d4..b40a025230 100644 --- a/ext/strscan/depend +++ b/ext/strscan/depend @@ -138,6 +138,7 @@ strscan.o: $(hdrdir)/ruby/internal/intern/re.h strscan.o: $(hdrdir)/ruby/internal/intern/ruby.h strscan.o: $(hdrdir)/ruby/internal/intern/select.h strscan.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +strscan.o: $(hdrdir)/ruby/internal/intern/set.h strscan.o: $(hdrdir)/ruby/internal/intern/signal.h strscan.o: $(hdrdir)/ruby/internal/intern/sprintf.h strscan.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/ext/strscan/extconf.rb b/ext/strscan/extconf.rb index bd65606a4e..3c311d2364 100644 --- a/ext/strscan/extconf.rb +++ b/ext/strscan/extconf.rb @@ -2,8 +2,8 @@ require 'mkmf' if RUBY_ENGINE == 'ruby' $INCFLAGS << " -I$(top_srcdir)" if $extmk - have_func("onig_region_memsize", "ruby.h") - have_func("rb_reg_onig_match", "ruby.h") + have_func("onig_region_memsize(NULL)") + have_func("rb_reg_onig_match", "ruby/re.h") create_makefile 'strscan' else File.write('Makefile', dummy_makefile("").join) diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index e094e2f55a..bc543f62b1 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -22,7 +22,7 @@ extern size_t onig_region_memsize(const struct re_registers *regs); #include -#define STRSCAN_VERSION "3.1.5.dev" +#define STRSCAN_VERSION "3.1.6.dev" /* ======================================================================= Data Type Definitions @@ -209,7 +209,7 @@ strscan_memsize(const void *ptr) static const rb_data_type_t strscanner_type = { "StringScanner", {strscan_mark, strscan_free, strscan_memsize}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED }; static VALUE @@ -273,7 +273,7 @@ strscan_initialize(int argc, VALUE *argv, VALUE self) p->fixed_anchor_p = false; } StringValue(str); - p->str = str; + RB_OBJ_WRITE(self, &p->str, str); return self; } @@ -303,7 +303,7 @@ strscan_init_copy(VALUE vself, VALUE vorig) orig = check_strscan(vorig); if (self != orig) { self->flags = orig->flags; - self->str = orig->str; + RB_OBJ_WRITE(vself, &self->str, orig->str); self->prev = orig->prev; self->curr = orig->curr; if (rb_reg_region_copy(&self->regs, &orig->regs)) @@ -467,7 +467,7 @@ strscan_set_string(VALUE self, VALUE str) struct strscanner *p = check_strscan(self); StringValue(str); - p->str = str; + RB_OBJ_WRITE(self, &p->str, str); p->curr = 0; CLEAR_MATCH_STATUS(p); return str; @@ -712,7 +712,7 @@ strscan_do_scan(VALUE self, VALUE pattern, int succptr, int getstr, int headonly if (RB_TYPE_P(pattern, T_REGEXP)) { OnigPosition ret; - p->regex = pattern; + RB_OBJ_WRITE(self, &p->regex, pattern); ret = rb_reg_onig_match(p->regex, p->str, headonly ? strscan_match : strscan_search, diff --git a/ext/win32/lib/win32/registry.rb b/ext/win32/lib/win32/registry.rb index d0cbb6afcf..8e2c8b2e1a 100644 --- a/ext/win32/lib/win32/registry.rb +++ b/ext/win32/lib/win32/registry.rb @@ -372,7 +372,7 @@ For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/pr # Replace %\w+% into the environment value of what is contained between the %'s # This method is used for REG_EXPAND_SZ. # - # For detail, see expandEnvironmentStrings[http://msdn.microsoft.com/library/en-us/sysinfo/base/expandenvironmentstrings.asp] \Win32 \API. + # For detail, see expandEnvironmentStrings[https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-expandenvironmentstringsa] \Win32 \API. # def self.expand_environ(str) str.gsub(Regexp.compile("%([^%]+)%".encode(str.encoding))) { @@ -487,7 +487,7 @@ For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/pr ObjectSpace.define_finalizer self, @@final.call(@hkeyfinal) end - # Win32::Registry object of parent key, or nil if predefeined key. + # Win32::Registry object of parent key, or nil if predefined key. attr_reader :parent # Same as subkey value of Registry.open or # Registry.create method. @@ -571,7 +571,7 @@ For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/pr # For each value it yields key, type and data. # # key is a String which contains name of key. - # type is a type contant kind of Win32::Registry::REG_* + # type is a type constant kind of Win32::Registry::REG_* # data is the value of this key. # def each_value diff --git a/ext/zlib/depend b/ext/zlib/depend index bdce420264..22e9ca867a 100644 --- a/ext/zlib/depend +++ b/ext/zlib/depend @@ -138,6 +138,7 @@ zlib.o: $(hdrdir)/ruby/internal/intern/re.h zlib.o: $(hdrdir)/ruby/internal/intern/ruby.h zlib.o: $(hdrdir)/ruby/internal/intern/select.h zlib.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +zlib.o: $(hdrdir)/ruby/internal/intern/set.h zlib.o: $(hdrdir)/ruby/internal/intern/signal.h zlib.o: $(hdrdir)/ruby/internal/intern/sprintf.h zlib.o: $(hdrdir)/ruby/internal/intern/string.h diff --git a/file.c b/file.c index d2fa247aee..3d8c800429 100644 --- a/file.c +++ b/file.c @@ -12,6 +12,7 @@ **********************************************************************/ #include "ruby/internal/config.h" +#include "ruby/internal/attr/nonstring.h" #ifdef _WIN32 # include "missing/file.h" @@ -131,6 +132,14 @@ int flock(int, int); # define STAT(p, s) stat((p), (s)) #endif /* _WIN32 */ +#ifdef HAVE_STRUCT_STATX_STX_BTIME +# define ST_(name) stx_ ## name +typedef struct statx_timestamp stat_timestamp; +#else +# define ST_(name) st_ ## name +typedef struct timespec stat_timestamp; +#endif + #if defined _WIN32 || defined __APPLE__ # define USE_OSPATH 1 # define TO_OSPATH(str) rb_str_encode_ospath(str) @@ -172,6 +181,13 @@ int flock(int, int); #include "ruby/thread.h" #include "ruby/util.h" +#define UIANY2NUM(x) \ + ((sizeof(x) <= sizeof(unsigned int)) ? \ + UINT2NUM((unsigned)(x)) : \ + (sizeof(x) <= sizeof(unsigned long)) ? \ + ULONG2NUM((unsigned long)(x)) : \ + ULL2NUM((unsigned LONG_LONG)(x))) + VALUE rb_cFile; VALUE rb_mFileTest; VALUE rb_cStat; @@ -358,7 +374,7 @@ rb_str_normalize_ospath(const char *ptr, long len) int r = rb_enc_precise_mbclen(p, e, enc); if (!MBCLEN_CHARFOUND_P(r)) { /* invalid byte shall not happen but */ - static const char invalid[3] = "\xEF\xBF\xBD"; + RBIMPL_ATTR_NONSTRING() static const char invalid[3] = "\xEF\xBF\xBD"; rb_str_append_normalized_ospath(str, p1, p-p1); rb_str_cat(str, invalid, sizeof(invalid)); p += 1; @@ -493,6 +509,10 @@ apply2files(int (*func)(const char *, void *), int argc, VALUE *argv, void *arg) return LONG2FIX(argc); } +static stat_timestamp stat_atimespec(const struct stat *st); +static stat_timestamp stat_mtimespec(const struct stat *st); +static stat_timestamp stat_ctimespec(const struct stat *st); + static const rb_data_type_t stat_data_type = { "stat", { @@ -504,29 +524,67 @@ static const rb_data_type_t stat_data_type = { }; struct rb_stat { - struct stat stat; + rb_io_stat_data stat; bool initialized; }; -static VALUE -stat_new_0(VALUE klass, const struct stat *st) +static struct rb_stat * +stat_alloc(VALUE klass, VALUE *obj) { struct rb_stat *rb_st; - VALUE obj = TypedData_Make_Struct(klass, struct rb_stat, &stat_data_type, rb_st); + *obj = TypedData_Make_Struct(klass, struct rb_stat, &stat_data_type, rb_st); + return rb_st; +} + +VALUE +rb_stat_new(const struct stat *st) +{ + VALUE obj; + struct rb_stat *rb_st = stat_alloc(rb_cStat, &obj); + if (st) { +#if RUBY_USE_STATX +# define CP(m) .stx_ ## m = st->st_ ## m +# define CP_32(m) .stx_ ## m = (uint32_t)st->st_ ## m +# define CP_TS(m) .stx_ ## m = stat_ ## m ## spec(st) + rb_st->stat = (struct statx){ + .stx_mask = STATX_BASIC_STATS, + CP(mode), + CP_32(nlink), + CP(uid), + CP(gid), + CP_TS(atime), + CP_TS(mtime), + CP_TS(ctime), + CP(ino), + CP(size), + CP(blocks), + }; +# undef CP +# undef CP_TS +#else + rb_st->stat = *st; +#endif + rb_st->initialized = true; + } + + return obj; +} + +#ifndef rb_statx_new +VALUE +rb_statx_new(const rb_io_stat_data *st) +{ + VALUE obj; + struct rb_stat *rb_st = stat_alloc(rb_cStat, &obj); if (st) { rb_st->stat = *st; rb_st->initialized = true; } return obj; } +#endif -VALUE -rb_stat_new(const struct stat *st) -{ - return stat_new_0(rb_cStat, st); -} - -static struct stat* +static rb_io_stat_data* get_stat(VALUE self) { struct rb_stat* rb_st; @@ -535,7 +593,15 @@ get_stat(VALUE self) return &rb_st->stat; } -static struct timespec stat_mtimespec(const struct stat *st); +#if RUBY_USE_STATX +static stat_timestamp +statx_mtimespec(const rb_io_stat_data *st) +{ + return st->stx_mtime; +} +#else +# define statx_mtimespec stat_mtimespec +#endif /* * call-seq: @@ -556,8 +622,8 @@ static VALUE rb_stat_cmp(VALUE self, VALUE other) { if (rb_obj_is_kind_of(other, rb_obj_class(self))) { - struct timespec ts1 = stat_mtimespec(get_stat(self)); - struct timespec ts2 = stat_mtimespec(get_stat(other)); + stat_timestamp ts1 = statx_mtimespec(get_stat(self)); + stat_timestamp ts2 = statx_mtimespec(get_stat(other)); if (ts1.tv_sec == ts2.tv_sec) { if (ts1.tv_nsec == ts2.tv_nsec) return INT2FIX(0); if (ts1.tv_nsec < ts2.tv_nsec) return INT2FIX(-1); @@ -594,7 +660,11 @@ rb_stat_cmp(VALUE self, VALUE other) static VALUE rb_stat_dev(VALUE self) { -#if SIZEOF_STRUCT_STAT_ST_DEV <= SIZEOF_DEV_T +#if RUBY_USE_STATX + unsigned int m = get_stat(self)->stx_dev_major; + unsigned int n = get_stat(self)->stx_dev_minor; + return ULL2NUM(makedev(m, n)); +#elif SIZEOF_STRUCT_STAT_ST_DEV <= SIZEOF_DEV_T return DEVT2NUM(get_stat(self)->st_dev); #elif SIZEOF_STRUCT_STAT_ST_DEV <= SIZEOF_LONG return ULONG2NUM(get_stat(self)->st_dev); @@ -617,7 +687,9 @@ rb_stat_dev(VALUE self) static VALUE rb_stat_dev_major(VALUE self) { -#if defined(major) +#if RUBY_USE_STATX + return UINT2NUM(get_stat(self)->stx_dev_major); +#elif defined(major) return UINT2NUM(major(get_stat(self)->st_dev)); #else return Qnil; @@ -638,7 +710,9 @@ rb_stat_dev_major(VALUE self) static VALUE rb_stat_dev_minor(VALUE self) { -#if defined(minor) +#if RUBY_USE_STATX + return UINT2NUM(get_stat(self)->stx_dev_minor); +#elif defined(minor) return UINT2NUM(minor(get_stat(self)->st_dev)); #else return Qnil; @@ -658,16 +732,15 @@ rb_stat_dev_minor(VALUE self) static VALUE rb_stat_ino(VALUE self) { + rb_io_stat_data *ptr = get_stat(self); #ifdef HAVE_STRUCT_STAT_ST_INOHIGH /* assume INTEGER_PACK_LSWORD_FIRST and st_inohigh is just next of st_ino */ - return rb_integer_unpack(&get_stat(self)->st_ino, 2, + return rb_integer_unpack(&ptr->st_ino, 2, SIZEOF_STRUCT_STAT_ST_INO, 0, INTEGER_PACK_LSWORD_FIRST|INTEGER_PACK_NATIVE_BYTE_ORDER| INTEGER_PACK_2COMP); -#elif SIZEOF_STRUCT_STAT_ST_INO > SIZEOF_LONG - return ULL2NUM(get_stat(self)->st_ino); #else - return ULONG2NUM(get_stat(self)->st_ino); + return UIANY2NUM(ptr->ST_(ino)); #endif } @@ -687,7 +760,7 @@ rb_stat_ino(VALUE self) static VALUE rb_stat_mode(VALUE self) { - return UINT2NUM(ST2UINT(get_stat(self)->st_mode)); + return UINT2NUM(ST2UINT(get_stat(self)->ST_(mode))); } /* @@ -706,20 +779,9 @@ static VALUE rb_stat_nlink(VALUE self) { /* struct stat::st_nlink is nlink_t in POSIX. Not the case for Windows. */ - const struct stat *ptr = get_stat(self); + const rb_io_stat_data *ptr = get_stat(self); - if (sizeof(ptr->st_nlink) <= sizeof(int)) { - return UINT2NUM((unsigned)ptr->st_nlink); - } - else if (sizeof(ptr->st_nlink) == sizeof(long)) { - return ULONG2NUM((unsigned long)ptr->st_nlink); - } - else if (sizeof(ptr->st_nlink) == sizeof(LONG_LONG)) { - return ULL2NUM((unsigned LONG_LONG)ptr->st_nlink); - } - else { - rb_bug(":FIXME: don't know what to do"); - } + return UIANY2NUM(ptr->ST_(nlink)); } /* @@ -735,7 +797,7 @@ rb_stat_nlink(VALUE self) static VALUE rb_stat_uid(VALUE self) { - return UIDT2NUM(get_stat(self)->st_uid); + return UIDT2NUM(get_stat(self)->ST_(uid)); } /* @@ -751,7 +813,7 @@ rb_stat_uid(VALUE self) static VALUE rb_stat_gid(VALUE self) { - return GIDT2NUM(get_stat(self)->st_gid); + return GIDT2NUM(get_stat(self)->ST_(gid)); } /* @@ -769,16 +831,18 @@ rb_stat_gid(VALUE self) static VALUE rb_stat_rdev(VALUE self) { -#ifdef HAVE_STRUCT_STAT_ST_RDEV -# if SIZEOF_STRUCT_STAT_ST_RDEV <= SIZEOF_DEV_T - return DEVT2NUM(get_stat(self)->st_rdev); -# elif SIZEOF_STRUCT_STAT_ST_RDEV <= SIZEOF_LONG - return ULONG2NUM(get_stat(self)->st_rdev); -# else - return ULL2NUM(get_stat(self)->st_rdev); -# endif -#else +#if RUBY_USE_STATX + unsigned int m = get_stat(self)->stx_rdev_major; + unsigned int n = get_stat(self)->stx_rdev_minor; + return ULL2NUM(makedev(m, n)); +#elif !defined(HAVE_STRUCT_STAT_ST_RDEV) return Qnil; +#elif SIZEOF_STRUCT_STAT_ST_RDEV <= SIZEOF_DEV_T + return DEVT2NUM(get_stat(self)->ST_(rdev)); +#elif SIZEOF_STRUCT_STAT_ST_RDEV <= SIZEOF_LONG + return ULONG2NUM(get_stat(self)->ST_(rdev)); +#else + return ULL2NUM(get_stat(self)->ST_(rdev)); #endif } @@ -796,8 +860,10 @@ rb_stat_rdev(VALUE self) static VALUE rb_stat_rdev_major(VALUE self) { -#if defined(HAVE_STRUCT_STAT_ST_RDEV) && defined(major) - return UINT2NUM(major(get_stat(self)->st_rdev)); +#if RUBY_USE_STATX + return UINT2NUM(get_stat(self)->stx_rdev_major); +#elif defined(HAVE_STRUCT_STAT_ST_RDEV) && defined(major) + return UINT2NUM(major(get_stat(self)->ST_(rdev))); #else return Qnil; #endif @@ -817,8 +883,10 @@ rb_stat_rdev_major(VALUE self) static VALUE rb_stat_rdev_minor(VALUE self) { -#if defined(HAVE_STRUCT_STAT_ST_RDEV) && defined(minor) - return UINT2NUM(minor(get_stat(self)->st_rdev)); +#if RUBY_USE_STATX + return UINT2NUM(get_stat(self)->stx_rdev_minor); +#elif defined(HAVE_STRUCT_STAT_ST_RDEV) && defined(minor) + return UINT2NUM(minor(get_stat(self)->ST_(rdev))); #else return Qnil; #endif @@ -836,7 +904,7 @@ rb_stat_rdev_minor(VALUE self) static VALUE rb_stat_size(VALUE self) { - return OFFT2NUM(get_stat(self)->st_size); + return OFFT2NUM(get_stat(self)->ST_(size)); } /* @@ -854,7 +922,7 @@ static VALUE rb_stat_blksize(VALUE self) { #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE - return ULONG2NUM(get_stat(self)->st_blksize); + return ULONG2NUM(get_stat(self)->ST_(blksize)); #else return Qnil; #endif @@ -876,34 +944,44 @@ rb_stat_blocks(VALUE self) { #ifdef HAVE_STRUCT_STAT_ST_BLOCKS # if SIZEOF_STRUCT_STAT_ST_BLOCKS > SIZEOF_LONG - return ULL2NUM(get_stat(self)->st_blocks); + return ULL2NUM(get_stat(self)->ST_(blocks)); # else - return ULONG2NUM(get_stat(self)->st_blocks); + return ULONG2NUM(get_stat(self)->ST_(blocks)); # endif #else return Qnil; #endif } -static struct timespec +static stat_timestamp stat_atimespec(const struct stat *st) { - struct timespec ts; + stat_timestamp ts; ts.tv_sec = st->st_atime; #if defined(HAVE_STRUCT_STAT_ST_ATIM) - ts.tv_nsec = st->st_atim.tv_nsec; + ts.tv_nsec = (uint32_t)st->st_atim.tv_nsec; #elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC) - ts.tv_nsec = st->st_atimespec.tv_nsec; + ts.tv_nsec = (uint32_t)st->st_atimespec.tv_nsec; #elif defined(HAVE_STRUCT_STAT_ST_ATIMENSEC) - ts.tv_nsec = (long)st->st_atimensec; + ts.tv_nsec = (uint32_t)st->st_atimensec; #else - ts.tv_nsec = 0; + ts.tv_nsec = 0 #endif return ts; } +#if RUBY_USE_STATX +static stat_timestamp +statx_atimespec(const rb_io_stat_data *st) +{ + return st->stx_atime; +} +#else +# define statx_atimespec stat_atimespec +#endif + static VALUE -stat_time(const struct timespec ts) +stat_time(const stat_timestamp ts) { return rb_time_nano_new(ts.tv_sec, ts.tv_nsec); } @@ -914,17 +992,17 @@ stat_atime(const struct stat *st) return stat_time(stat_atimespec(st)); } -static struct timespec +static stat_timestamp stat_mtimespec(const struct stat *st) { - struct timespec ts; + stat_timestamp ts; ts.tv_sec = st->st_mtime; #if defined(HAVE_STRUCT_STAT_ST_MTIM) - ts.tv_nsec = st->st_mtim.tv_nsec; + ts.tv_nsec = (uint32_t)st->st_mtim.tv_nsec; #elif defined(HAVE_STRUCT_STAT_ST_MTIMESPEC) - ts.tv_nsec = st->st_mtimespec.tv_nsec; + ts.tv_nsec = (uint32_t)st->st_mtimespec.tv_nsec; #elif defined(HAVE_STRUCT_STAT_ST_MTIMENSEC) - ts.tv_nsec = (long)st->st_mtimensec; + ts.tv_nsec = (uint32_t)st->st_mtimensec; #else ts.tv_nsec = 0; #endif @@ -937,23 +1015,33 @@ stat_mtime(const struct stat *st) return stat_time(stat_mtimespec(st)); } -static struct timespec +static stat_timestamp stat_ctimespec(const struct stat *st) { - struct timespec ts; + stat_timestamp ts; ts.tv_sec = st->st_ctime; #if defined(HAVE_STRUCT_STAT_ST_CTIM) - ts.tv_nsec = st->st_ctim.tv_nsec; + ts.tv_nsec = (uint32_t)st->st_ctim.tv_nsec; #elif defined(HAVE_STRUCT_STAT_ST_CTIMESPEC) - ts.tv_nsec = st->st_ctimespec.tv_nsec; + ts.tv_nsec = (uint32_t)st->st_ctimespec.tv_nsec; #elif defined(HAVE_STRUCT_STAT_ST_CTIMENSEC) - ts.tv_nsec = (long)st->st_ctimensec; + ts.tv_nsec = (uint32_t)st->st_ctimensec; #else ts.tv_nsec = 0; #endif return ts; } +#if RUBY_USE_STATX +static stat_timestamp +statx_ctimespec(const rb_io_stat_data *st) +{ + return st->stx_ctime; +} +#else +# define statx_ctimespec stat_ctimespec +#endif + static VALUE stat_ctime(const struct stat *st) { @@ -962,16 +1050,16 @@ stat_ctime(const struct stat *st) #define HAVE_STAT_BIRTHTIME #if defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC) -typedef struct stat statx_data; static VALUE -stat_birthtime(const struct stat *st) +statx_birthtime(const rb_io_stat_data *st) { - const struct timespec *ts = &st->st_birthtimespec; + const stat_timestamp *ts = &st->ST_(birthtimespec); return rb_time_nano_new(ts->tv_sec, ts->tv_nsec); } +#elif defined(HAVE_STRUCT_STATX_STX_BTIME) +static VALUE statx_birthtime(const rb_io_stat_data *st); #elif defined(_WIN32) -typedef struct stat statx_data; -# define stat_birthtime stat_ctime +# define statx_birthtime stat_ctime #else # undef HAVE_STAT_BIRTHTIME #endif /* defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC) */ @@ -990,7 +1078,7 @@ typedef struct stat statx_data; static VALUE rb_stat_atime(VALUE self) { - return stat_atime(get_stat(self)); + return stat_time(statx_atimespec(get_stat(self))); } /* @@ -1006,7 +1094,7 @@ rb_stat_atime(VALUE self) static VALUE rb_stat_mtime(VALUE self) { - return stat_mtime(get_stat(self)); + return stat_time(statx_mtimespec(get_stat(self))); } /* @@ -1026,7 +1114,7 @@ rb_stat_mtime(VALUE self) static VALUE rb_stat_ctime(VALUE self) { - return stat_ctime(get_stat(self)); + return stat_time(statx_ctimespec(get_stat(self))); } #if defined(HAVE_STAT_BIRTHTIME) @@ -1055,7 +1143,7 @@ rb_stat_ctime(VALUE self) static VALUE rb_stat_birthtime(VALUE self) { - return stat_birthtime(get_stat(self)); + return statx_birthtime(get_stat(self)); } #else # define rb_stat_birthtime rb_f_notimplement @@ -1184,6 +1272,8 @@ stat_without_gvl(const char *path, struct stat *st) #if !defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC) && \ defined(HAVE_STRUCT_STATX_STX_BTIME) +# define STATX(path, st, mask) statx(AT_FDCWD, path, 0, mask, st) + # ifndef HAVE_STATX # ifdef HAVE_SYSCALL_H # include @@ -1201,18 +1291,18 @@ statx(int dirfd, const char *pathname, int flags, # endif /* __linux__ */ # endif /* HAVE_STATX */ -typedef struct no_gvl_statx_data { +typedef struct no_gvl_rb_io_stat_data { struct statx *stx; int fd; const char *path; int flags; unsigned int mask; -} no_gvl_statx_data; +} no_gvl_rb_io_stat_data; static VALUE io_blocking_statx(void *data) { - no_gvl_statx_data *arg = data; + no_gvl_rb_io_stat_data *arg = data; return (VALUE)statx(arg->fd, arg->path, arg->flags, arg->mask, arg->stx); } @@ -1223,23 +1313,34 @@ no_gvl_statx(void *data) } static int -statx_without_gvl(const char *path, struct statx *stx, unsigned int mask) +statx_without_gvl(const char *path, rb_io_stat_data *stx, unsigned int mask) { - no_gvl_statx_data data = {stx, AT_FDCWD, path, 0, mask}; + no_gvl_rb_io_stat_data data = {stx, AT_FDCWD, path, 0, mask}; /* call statx(2) with pathname */ return IO_WITHOUT_GVL_INT(no_gvl_statx, &data); } static int -fstatx_without_gvl(rb_io_t *fptr, struct statx *stx, unsigned int mask) +lstatx_without_gvl(const char *path, rb_io_stat_data *stx, unsigned int mask) { - no_gvl_statx_data data = {stx, fptr->fd, "", AT_EMPTY_PATH, mask}; + no_gvl_rb_io_stat_data data = {stx, AT_FDCWD, path, AT_SYMLINK_NOFOLLOW, mask}; + + /* call statx(2) with pathname */ + return IO_WITHOUT_GVL_INT(no_gvl_statx, &data); +} + +static int +fstatx_without_gvl(rb_io_t *fptr, rb_io_stat_data *stx, unsigned int mask) +{ + no_gvl_rb_io_stat_data data = {stx, fptr->fd, "", AT_EMPTY_PATH, mask}; /* call statx(2) with fd */ return (int)rb_io_blocking_region(fptr, io_blocking_statx, &data); } +#define FSTATX(fd, st) statx(fd, "", AT_EMPTY_PATH, STATX_ALL, st) + static int rb_statx(VALUE file, struct statx *stx, unsigned int mask) { @@ -1249,6 +1350,7 @@ rb_statx(VALUE file, struct statx *stx, unsigned int mask) tmp = rb_check_convert_type_with_id(file, T_FILE, "IO", idTo_io); if (!NIL_P(tmp)) { rb_io_t *fptr; + GetOpenFile(tmp, fptr); result = fstatx_without_gvl(fptr, stx, mask); file = tmp; @@ -1277,7 +1379,7 @@ statx_notimplement(const char *field_name) } static VALUE -statx_birthtime(const struct statx *stx, VALUE fname) +statx_birthtime(const rb_io_stat_data *stx) { if (!statx_has_birthtime(stx)) { /* birthtime is not supported on the filesystem */ @@ -1286,20 +1388,27 @@ statx_birthtime(const struct statx *stx, VALUE fname) return rb_time_nano_new((time_t)stx->stx_btime.tv_sec, stx->stx_btime.tv_nsec); } -typedef struct statx statx_data; -# define HAVE_STAT_BIRTHTIME +#else -#elif defined(HAVE_STAT_BIRTHTIME) # define statx_without_gvl(path, st, mask) stat_without_gvl(path, st) # define fstatx_without_gvl(fptr, st, mask) fstat_without_gvl(fptr, st) -# define statx_birthtime(st, fname) stat_birthtime(st) +# define lstatx_without_gvl(path, st, mask) lstat_without_gvl(path, st) +# define rb_statx(file, stx, mask) rb_stat(file, stx) +# define STATX(path, st, mask) STAT(path, st) + +#if defined(HAVE_STAT_BIRTHTIME) # define statx_has_birthtime(st) 1 -# define rb_statx(file, st, mask) rb_stat(file, st) #else # define statx_has_birthtime(st) 0 +#endif + #endif /* !defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC) && \ defined(HAVE_STRUCT_STATX_STX_BTIME) */ +#ifndef FSTAT +# define FSTAT(fd, st) fstat(fd, st) +#endif + static int rb_stat(VALUE file, struct stat *st) { @@ -1336,14 +1445,14 @@ rb_stat(VALUE file, struct stat *st) static VALUE rb_file_s_stat(VALUE klass, VALUE fname) { - struct stat st; + rb_io_stat_data st; FilePathValue(fname); fname = rb_str_encode_ospath(fname); - if (stat_without_gvl(RSTRING_PTR(fname), &st) < 0) { + if (statx_without_gvl(RSTRING_PTR(fname), &st, STATX_ALL) < 0) { rb_sys_fail_path(fname); } - return rb_stat_new(&st); + return rb_statx_new(&st); } /* @@ -1365,13 +1474,13 @@ static VALUE rb_io_stat(VALUE obj) { rb_io_t *fptr; - struct stat st; + rb_io_stat_data st; GetOpenFile(obj, fptr); - if (fstat(fptr->fd, &st) == -1) { + if (fstatx_without_gvl(fptr, &st, STATX_ALL) == -1) { rb_sys_fail_path(fptr->pathv); } - return rb_stat_new(&st); + return rb_statx_new(&st); } #ifdef HAVE_LSTAT @@ -1411,14 +1520,14 @@ static VALUE rb_file_s_lstat(VALUE klass, VALUE fname) { #ifdef HAVE_LSTAT - struct stat st; + rb_io_stat_data st; FilePathValue(fname); fname = rb_str_encode_ospath(fname); - if (lstat_without_gvl(StringValueCStr(fname), &st) == -1) { + if (lstatx_without_gvl(StringValueCStr(fname), &st, STATX_ALL) == -1) { rb_sys_fail_path(fname); } - return rb_stat_new(&st); + return rb_statx_new(&st); #else return rb_file_s_stat(klass, fname); #endif @@ -1443,16 +1552,16 @@ rb_file_lstat(VALUE obj) { #ifdef HAVE_LSTAT rb_io_t *fptr; - struct stat st; + rb_io_stat_data st; VALUE path; GetOpenFile(obj, fptr); if (NIL_P(fptr->pathv)) return Qnil; path = rb_str_encode_ospath(fptr->pathv); - if (lstat_without_gvl(RSTRING_PTR(path), &st) == -1) { + if (lstatx_without_gvl(RSTRING_PTR(path), &st, STATX_ALL) == -1) { rb_sys_fail_path(fptr->pathv); } - return rb_stat_new(&st); + return rb_statx_new(&st); #else return rb_io_stat(obj); #endif @@ -2237,36 +2346,36 @@ rb_file_s_size(VALUE klass, VALUE fname) } static VALUE -rb_file_ftype(const struct stat *st) +rb_file_ftype(mode_t mode) { const char *t; - if (S_ISREG(st->st_mode)) { + if (S_ISREG(mode)) { t = "file"; } - else if (S_ISDIR(st->st_mode)) { + else if (S_ISDIR(mode)) { t = "directory"; } - else if (S_ISCHR(st->st_mode)) { + else if (S_ISCHR(mode)) { t = "characterSpecial"; } #ifdef S_ISBLK - else if (S_ISBLK(st->st_mode)) { + else if (S_ISBLK(mode)) { t = "blockSpecial"; } #endif #ifdef S_ISFIFO - else if (S_ISFIFO(st->st_mode)) { + else if (S_ISFIFO(mode)) { t = "fifo"; } #endif #ifdef S_ISLNK - else if (S_ISLNK(st->st_mode)) { + else if (S_ISLNK(mode)) { t = "link"; } #endif #ifdef S_ISSOCK - else if (S_ISSOCK(st->st_mode)) { + else if (S_ISSOCK(mode)) { t = "socket"; } #endif @@ -2303,7 +2412,7 @@ rb_file_s_ftype(VALUE klass, VALUE fname) rb_sys_fail_path(fname); } - return rb_file_ftype(&st); + return rb_file_ftype(st.st_mode); } /* @@ -2328,7 +2437,7 @@ rb_file_s_atime(VALUE klass, VALUE fname) FilePathValue(fname); rb_syserr_fail_path(e, fname); } - return stat_atime(&st); + return stat_time(stat_atimespec(&st)); } /* @@ -2352,7 +2461,7 @@ rb_file_atime(VALUE obj) if (fstat(fptr->fd, &st) == -1) { rb_sys_fail_path(fptr->pathv); } - return stat_atime(&st); + return stat_time(stat_atimespec(&st)); } /* @@ -2377,7 +2486,7 @@ rb_file_s_mtime(VALUE klass, VALUE fname) FilePathValue(fname); rb_syserr_fail_path(e, fname); } - return stat_mtime(&st); + return stat_time(stat_mtimespec(&st)); } /* @@ -2400,7 +2509,7 @@ rb_file_mtime(VALUE obj) if (fstat(fptr->fd, &st) == -1) { rb_sys_fail_path(fptr->pathv); } - return stat_mtime(&st); + return stat_time(stat_mtimespec(&st)); } /* @@ -2429,7 +2538,7 @@ rb_file_s_ctime(VALUE klass, VALUE fname) FilePathValue(fname); rb_syserr_fail_path(e, fname); } - return stat_ctime(&st); + return stat_time(stat_ctimespec(&st)); } /* @@ -2455,7 +2564,7 @@ rb_file_ctime(VALUE obj) if (fstat(fptr->fd, &st) == -1) { rb_sys_fail_path(fptr->pathv); } - return stat_ctime(&st); + return stat_time(stat_ctimespec(&st)); } #if defined(HAVE_STAT_BIRTHTIME) @@ -2476,14 +2585,14 @@ rb_file_ctime(VALUE obj) VALUE rb_file_s_birthtime(VALUE klass, VALUE fname) { - statx_data st; + rb_io_stat_data st; if (rb_statx(fname, &st, STATX_BTIME) < 0) { int e = errno; FilePathValue(fname); rb_syserr_fail_path(e, fname); } - return statx_birthtime(&st, fname); + return statx_birthtime(&st); } #else # define rb_file_s_birthtime rb_f_notimplement @@ -2506,13 +2615,13 @@ static VALUE rb_file_birthtime(VALUE obj) { rb_io_t *fptr; - statx_data st; + rb_io_stat_data st; GetOpenFile(obj, fptr); if (fstatx_without_gvl(fptr, &st, STATX_BTIME) == -1) { rb_sys_fail_path(fptr->pathv); } - return statx_birthtime(&st, fptr->pathv); + return statx_birthtime(&st); } #else # define rb_file_birthtime rb_f_notimplement @@ -3028,7 +3137,7 @@ static int utime_internal(const char *path, void *arg) { struct utime_args *v = arg; - const struct timespec *tsp = v->tsp; + const stat_timestamp *tsp = v->tsp; struct utimbuf utbuf, *utp = NULL; if (tsp) { utbuf.actime = tsp[0].tv_sec; @@ -5626,7 +5735,7 @@ rb_f_test(int argc, VALUE *argv, VALUE _) if (strchr("=<>", cmd)) { struct stat st1, st2; - struct timespec t1, t2; + stat_timestamp t1, t2; CHECK(2); if (rb_stat(argv[1], &st1) < 0) return Qfalse; @@ -5678,7 +5787,9 @@ rb_f_test(int argc, VALUE *argv, VALUE _) static VALUE rb_stat_s_alloc(VALUE klass) { - return stat_new_0(klass, 0); + VALUE obj; + stat_alloc(rb_cStat, &obj); + return obj; } /* @@ -5692,11 +5803,11 @@ rb_stat_s_alloc(VALUE klass) static VALUE rb_stat_init(VALUE obj, VALUE fname) { - struct stat st; + rb_io_stat_data st; FilePathValue(fname); fname = rb_str_encode_ospath(fname); - if (STAT(StringValueCStr(fname), &st) == -1) { + if (STATX(StringValueCStr(fname), &st, STATX_ALL) == -1) { rb_sys_fail_path(fname); } @@ -5742,7 +5853,7 @@ rb_stat_init_copy(VALUE copy, VALUE orig) static VALUE rb_stat_ftype(VALUE obj) { - return rb_file_ftype(get_stat(obj)); + return rb_file_ftype(get_stat(obj)->ST_(mode)); } /* @@ -5759,7 +5870,7 @@ rb_stat_ftype(VALUE obj) static VALUE rb_stat_d(VALUE obj) { - if (S_ISDIR(get_stat(obj)->st_mode)) return Qtrue; + if (S_ISDIR(get_stat(obj)->ST_(mode))) return Qtrue; return Qfalse; } @@ -5775,7 +5886,7 @@ static VALUE rb_stat_p(VALUE obj) { #ifdef S_IFIFO - if (S_ISFIFO(get_stat(obj)->st_mode)) return Qtrue; + if (S_ISFIFO(get_stat(obj)->ST_(mode))) return Qtrue; #endif return Qfalse; @@ -5801,7 +5912,7 @@ static VALUE rb_stat_l(VALUE obj) { #ifdef S_ISLNK - if (S_ISLNK(get_stat(obj)->st_mode)) return Qtrue; + if (S_ISLNK(get_stat(obj)->ST_(mode))) return Qtrue; #endif return Qfalse; } @@ -5822,7 +5933,7 @@ static VALUE rb_stat_S(VALUE obj) { #ifdef S_ISSOCK - if (S_ISSOCK(get_stat(obj)->st_mode)) return Qtrue; + if (S_ISSOCK(get_stat(obj)->ST_(mode))) return Qtrue; #endif return Qfalse; @@ -5845,7 +5956,7 @@ static VALUE rb_stat_b(VALUE obj) { #ifdef S_ISBLK - if (S_ISBLK(get_stat(obj)->st_mode)) return Qtrue; + if (S_ISBLK(get_stat(obj)->ST_(mode))) return Qtrue; #endif return Qfalse; @@ -5866,7 +5977,7 @@ rb_stat_b(VALUE obj) static VALUE rb_stat_c(VALUE obj) { - if (S_ISCHR(get_stat(obj)->st_mode)) return Qtrue; + if (S_ISCHR(get_stat(obj)->ST_(mode))) return Qtrue; return Qfalse; } @@ -5886,14 +5997,14 @@ rb_stat_c(VALUE obj) static VALUE rb_stat_owned(VALUE obj) { - if (get_stat(obj)->st_uid == geteuid()) return Qtrue; + if (get_stat(obj)->ST_(uid) == geteuid()) return Qtrue; return Qfalse; } static VALUE rb_stat_rowned(VALUE obj) { - if (get_stat(obj)->st_uid == getuid()) return Qtrue; + if (get_stat(obj)->ST_(uid) == getuid()) return Qtrue; return Qfalse; } @@ -5913,7 +6024,7 @@ static VALUE rb_stat_grpowned(VALUE obj) { #ifndef _WIN32 - if (rb_group_member(get_stat(obj)->st_gid)) return Qtrue; + if (rb_group_member(get_stat(obj)->ST_(gid))) return Qtrue; #endif return Qfalse; } @@ -5932,21 +6043,21 @@ rb_stat_grpowned(VALUE obj) static VALUE rb_stat_r(VALUE obj) { - struct stat *st = get_stat(obj); + rb_io_stat_data *st = get_stat(obj); #ifdef USE_GETEUID if (geteuid() == 0) return Qtrue; #endif #ifdef S_IRUSR if (rb_stat_owned(obj)) - return RBOOL(st->st_mode & S_IRUSR); + return RBOOL(st->ST_(mode) & S_IRUSR); #endif #ifdef S_IRGRP if (rb_stat_grpowned(obj)) - return RBOOL(st->st_mode & S_IRGRP); + return RBOOL(st->ST_(mode) & S_IRGRP); #endif #ifdef S_IROTH - if (!(st->st_mode & S_IROTH)) return Qfalse; + if (!(st->ST_(mode) & S_IROTH)) return Qfalse; #endif return Qtrue; } @@ -5965,21 +6076,21 @@ rb_stat_r(VALUE obj) static VALUE rb_stat_R(VALUE obj) { - struct stat *st = get_stat(obj); + rb_io_stat_data *st = get_stat(obj); #ifdef USE_GETEUID if (getuid() == 0) return Qtrue; #endif #ifdef S_IRUSR if (rb_stat_rowned(obj)) - return RBOOL(st->st_mode & S_IRUSR); + return RBOOL(st->ST_(mode) & S_IRUSR); #endif #ifdef S_IRGRP - if (rb_group_member(get_stat(obj)->st_gid)) - return RBOOL(st->st_mode & S_IRGRP); + if (rb_group_member(get_stat(obj)->ST_(gid))) + return RBOOL(st->ST_(mode) & S_IRGRP); #endif #ifdef S_IROTH - if (!(st->st_mode & S_IROTH)) return Qfalse; + if (!(st->ST_(mode) & S_IROTH)) return Qfalse; #endif return Qtrue; } @@ -6001,9 +6112,9 @@ static VALUE rb_stat_wr(VALUE obj) { #ifdef S_IROTH - struct stat *st = get_stat(obj); - if ((st->st_mode & (S_IROTH)) == S_IROTH) { - return UINT2NUM(st->st_mode & (S_IRUGO|S_IWUGO|S_IXUGO)); + rb_io_stat_data *st = get_stat(obj); + if ((st->ST_(mode) & (S_IROTH)) == S_IROTH) { + return UINT2NUM(st->ST_(mode) & (S_IRUGO|S_IWUGO|S_IXUGO)); } #endif return Qnil; @@ -6023,21 +6134,21 @@ rb_stat_wr(VALUE obj) static VALUE rb_stat_w(VALUE obj) { - struct stat *st = get_stat(obj); + rb_io_stat_data *st = get_stat(obj); #ifdef USE_GETEUID if (geteuid() == 0) return Qtrue; #endif #ifdef S_IWUSR if (rb_stat_owned(obj)) - return RBOOL(st->st_mode & S_IWUSR); + return RBOOL(st->ST_(mode) & S_IWUSR); #endif #ifdef S_IWGRP if (rb_stat_grpowned(obj)) - return RBOOL(st->st_mode & S_IWGRP); + return RBOOL(st->ST_(mode) & S_IWGRP); #endif #ifdef S_IWOTH - if (!(st->st_mode & S_IWOTH)) return Qfalse; + if (!(st->ST_(mode) & S_IWOTH)) return Qfalse; #endif return Qtrue; } @@ -6056,21 +6167,21 @@ rb_stat_w(VALUE obj) static VALUE rb_stat_W(VALUE obj) { - struct stat *st = get_stat(obj); + rb_io_stat_data *st = get_stat(obj); #ifdef USE_GETEUID if (getuid() == 0) return Qtrue; #endif #ifdef S_IWUSR if (rb_stat_rowned(obj)) - return RBOOL(st->st_mode & S_IWUSR); + return RBOOL(st->ST_(mode) & S_IWUSR); #endif #ifdef S_IWGRP - if (rb_group_member(get_stat(obj)->st_gid)) - return RBOOL(st->st_mode & S_IWGRP); + if (rb_group_member(get_stat(obj)->ST_(gid))) + return RBOOL(st->ST_(mode) & S_IWGRP); #endif #ifdef S_IWOTH - if (!(st->st_mode & S_IWOTH)) return Qfalse; + if (!(st->ST_(mode) & S_IWOTH)) return Qfalse; #endif return Qtrue; } @@ -6092,9 +6203,9 @@ static VALUE rb_stat_ww(VALUE obj) { #ifdef S_IWOTH - struct stat *st = get_stat(obj); - if ((st->st_mode & (S_IWOTH)) == S_IWOTH) { - return UINT2NUM(st->st_mode & (S_IRUGO|S_IWUGO|S_IXUGO)); + rb_io_stat_data *st = get_stat(obj); + if ((st->ST_(mode) & (S_IWOTH)) == S_IWOTH) { + return UINT2NUM(st->ST_(mode) & (S_IRUGO|S_IWUGO|S_IXUGO)); } #endif return Qnil; @@ -6116,23 +6227,23 @@ rb_stat_ww(VALUE obj) static VALUE rb_stat_x(VALUE obj) { - struct stat *st = get_stat(obj); + rb_io_stat_data *st = get_stat(obj); #ifdef USE_GETEUID if (geteuid() == 0) { - return RBOOL(st->st_mode & S_IXUGO); + return RBOOL(st->ST_(mode) & S_IXUGO); } #endif #ifdef S_IXUSR if (rb_stat_owned(obj)) - return RBOOL(st->st_mode & S_IXUSR); + return RBOOL(st->ST_(mode) & S_IXUSR); #endif #ifdef S_IXGRP if (rb_stat_grpowned(obj)) - return RBOOL(st->st_mode & S_IXGRP); + return RBOOL(st->ST_(mode) & S_IXGRP); #endif #ifdef S_IXOTH - if (!(st->st_mode & S_IXOTH)) return Qfalse; + if (!(st->ST_(mode) & S_IXOTH)) return Qfalse; #endif return Qtrue; } @@ -6148,23 +6259,23 @@ rb_stat_x(VALUE obj) static VALUE rb_stat_X(VALUE obj) { - struct stat *st = get_stat(obj); + rb_io_stat_data *st = get_stat(obj); #ifdef USE_GETEUID if (getuid() == 0) { - return RBOOL(st->st_mode & S_IXUGO); + return RBOOL(st->ST_(mode) & S_IXUGO); } #endif #ifdef S_IXUSR if (rb_stat_rowned(obj)) - return RBOOL(st->st_mode & S_IXUSR); + return RBOOL(st->ST_(mode) & S_IXUSR); #endif #ifdef S_IXGRP - if (rb_group_member(get_stat(obj)->st_gid)) - return RBOOL(st->st_mode & S_IXGRP); + if (rb_group_member(get_stat(obj)->ST_(gid))) + return RBOOL(st->ST_(mode) & S_IXGRP); #endif #ifdef S_IXOTH - if (!(st->st_mode & S_IXOTH)) return Qfalse; + if (!(st->ST_(mode) & S_IXOTH)) return Qfalse; #endif return Qtrue; } @@ -6183,7 +6294,7 @@ rb_stat_X(VALUE obj) static VALUE rb_stat_f(VALUE obj) { - if (S_ISREG(get_stat(obj)->st_mode)) return Qtrue; + if (S_ISREG(get_stat(obj)->ST_(mode))) return Qtrue; return Qfalse; } @@ -6201,7 +6312,7 @@ rb_stat_f(VALUE obj) static VALUE rb_stat_z(VALUE obj) { - if (get_stat(obj)->st_size == 0) return Qtrue; + if (get_stat(obj)->ST_(size) == 0) return Qtrue; return Qfalse; } @@ -6220,7 +6331,7 @@ rb_stat_z(VALUE obj) static VALUE rb_stat_s(VALUE obj) { - rb_off_t size = get_stat(obj)->st_size; + rb_off_t size = get_stat(obj)->ST_(size); if (size == 0) return Qnil; return OFFT2NUM(size); @@ -6241,7 +6352,7 @@ static VALUE rb_stat_suid(VALUE obj) { #ifdef S_ISUID - if (get_stat(obj)->st_mode & S_ISUID) return Qtrue; + if (get_stat(obj)->ST_(mode) & S_ISUID) return Qtrue; #endif return Qfalse; } @@ -6262,7 +6373,7 @@ static VALUE rb_stat_sgid(VALUE obj) { #ifdef S_ISGID - if (get_stat(obj)->st_mode & S_ISGID) return Qtrue; + if (get_stat(obj)->ST_(mode) & S_ISGID) return Qtrue; #endif return Qfalse; } @@ -6283,7 +6394,7 @@ static VALUE rb_stat_sticky(VALUE obj) { #ifdef S_ISVTX - if (get_stat(obj)->st_mode & S_ISVTX) return Qtrue; + if (get_stat(obj)->ST_(mode) & S_ISVTX) return Qtrue; #endif return Qfalse; } diff --git a/gc.c b/gc.c index 44db3570af..7558637555 100644 --- a/gc.c +++ b/gc.c @@ -131,45 +131,45 @@ #include "shape.h" unsigned int -rb_gc_vm_lock(void) -{ - unsigned int lev; - RB_VM_LOCK_ENTER_LEV(&lev); - return lev; -} - -void -rb_gc_vm_unlock(unsigned int lev) -{ - RB_VM_LOCK_LEAVE_LEV(&lev); -} - -unsigned int -rb_gc_cr_lock(void) -{ - unsigned int lev; - RB_VM_LOCK_ENTER_CR_LEV(GET_RACTOR(), &lev); - return lev; -} - -void -rb_gc_cr_unlock(unsigned int lev) -{ - RB_VM_LOCK_LEAVE_CR_LEV(GET_RACTOR(), &lev); -} - -unsigned int -rb_gc_vm_lock_no_barrier(void) +rb_gc_vm_lock(const char *file, int line) { unsigned int lev = 0; - RB_VM_LOCK_ENTER_LEV_NB(&lev); + rb_vm_lock_enter(&lev, file, line); return lev; } void -rb_gc_vm_unlock_no_barrier(unsigned int lev) +rb_gc_vm_unlock(unsigned int lev, const char *file, int line) { - RB_VM_LOCK_LEAVE_LEV(&lev); + rb_vm_lock_leave(&lev, file, line); +} + +unsigned int +rb_gc_cr_lock(const char *file, int line) +{ + unsigned int lev; + rb_vm_lock_enter_cr(GET_RACTOR(), &lev, file, line); + return lev; +} + +void +rb_gc_cr_unlock(unsigned int lev, const char *file, int line) +{ + rb_vm_lock_leave_cr(GET_RACTOR(), &lev, file, line); +} + +unsigned int +rb_gc_vm_lock_no_barrier(const char *file, int line) +{ + unsigned int lev = 0; + rb_vm_lock_enter_nb(&lev, file, line); + return lev; +} + +void +rb_gc_vm_unlock_no_barrier(unsigned int lev, const char *file, int line) +{ + rb_vm_lock_leave_nb(&lev, file, line); } void @@ -288,6 +288,7 @@ rb_gc_run_obj_finalizer(VALUE objid, long count, VALUE (*callback)(long i, void saved.finished = 0; saved.final = Qundef; + rb_ractor_ignore_belonging(true); EC_PUSH_TAG(ec); enum ruby_tag_type state = EC_EXEC_TAG(); if (state != TAG_NONE) { @@ -306,6 +307,7 @@ rb_gc_run_obj_finalizer(VALUE objid, long count, VALUE (*callback)(long i, void rb_check_funcall(saved.final, idCall, 1, &objid); } EC_POP_TAG(); + rb_ractor_ignore_belonging(false); #undef RESTORE_FINALIZER } @@ -373,25 +375,15 @@ rb_gc_get_shape(VALUE obj) void rb_gc_set_shape(VALUE obj, uint32_t shape_id) { - rb_shape_set_shape_id(obj, (uint32_t)shape_id); + rb_obj_set_shape_id(obj, (uint32_t)shape_id); } uint32_t rb_gc_rebuild_shape(VALUE obj, size_t heap_id) { - shape_id_t orig_shape_id = rb_obj_shape_id(obj); - if (rb_shape_id_too_complex_p(orig_shape_id)) { - return (uint32_t)orig_shape_id; - } + RUBY_ASSERT(RB_TYPE_P(obj, T_OBJECT)); - shape_id_t initial_shape_id = rb_shape_root(heap_id); - shape_id_t new_shape_id = rb_shape_traverse_from_new_root(initial_shape_id, orig_shape_id); - - if (new_shape_id == INVALID_SHAPE_ID) { - return 0; - } - - return (uint32_t)new_shape_id; + return (uint32_t)rb_shape_transition_heap(obj, heap_id); } void rb_vm_update_references(void *ptr); @@ -666,9 +658,6 @@ typedef struct gc_function_map { void (*undefine_finalizer)(void *objspace_ptr, VALUE obj); void (*copy_finalizer)(void *objspace_ptr, VALUE dest, VALUE obj); void (*shutdown_call_finalizer)(void *objspace_ptr); - // Object ID - VALUE (*object_id)(void *objspace_ptr, VALUE obj); - VALUE (*object_id_to_ref)(void *objspace_ptr, VALUE object_id); // Forking void (*before_fork)(void *objspace_ptr); void (*after_fork)(void *objspace_ptr, rb_pid_t pid); @@ -1212,7 +1201,6 @@ rb_data_free(void *objspace, VALUE obj) struct classext_foreach_args { VALUE klass; - bool obj_too_complex; rb_objspace_t *objspace; // used for update_* }; @@ -1224,12 +1212,6 @@ classext_free(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg) rb_id_table_free(RCLASSEXT_M_TBL(ext)); rb_cc_tbl_free(RCLASSEXT_CC_TBL(ext), args->klass); - if (args->obj_too_complex) { - st_free_table((st_table *)RCLASSEXT_FIELDS(ext)); - } - else { - xfree(RCLASSEXT_FIELDS(ext)); - } if (!RCLASSEXT_SHARED_CONST_TBL(ext) && (tbl = RCLASSEXT_CONST_TBL(ext)) != NULL) { rb_free_const_table(tbl); } @@ -1238,7 +1220,8 @@ classext_free(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg) rb_id_table_free(tbl); } rb_class_classext_free_subclasses(ext, args->klass); - if (RCLASSEXT_SUPERCLASSES_OWNER(ext)) { + if (RCLASSEXT_SUPERCLASSES_WITH_SELF(ext)) { + RUBY_ASSERT(is_prime); // superclasses should only be used on prime xfree(RCLASSEXT_SUPERCLASSES(ext)); } if (!is_prime) { // the prime classext will be freed with RClass @@ -1302,11 +1285,9 @@ rb_gc_obj_free(void *objspace, VALUE obj) case T_MODULE: case T_CLASS: args.klass = obj; - args.obj_too_complex = rb_shape_obj_too_complex_p(obj) ? true : false; - rb_class_classext_foreach(obj, classext_free, (void *)&args); - if (RCLASS(obj)->ns_classext_tbl) { - st_free_table(RCLASS(obj)->ns_classext_tbl); + if (RCLASS_CLASSEXT_TBL(obj)) { + st_free_table(RCLASS_CLASSEXT_TBL(obj)); } (void)RB_DEBUG_COUNTER_INC_IF(obj_module_ptr, BUILTIN_TYPE(obj) == T_MODULE); (void)RB_DEBUG_COUNTER_INC_IF(obj_class_ptr, BUILTIN_TYPE(obj) == T_CLASS); @@ -1409,8 +1390,8 @@ rb_gc_obj_free(void *objspace, VALUE obj) args.klass = obj; rb_class_classext_foreach(obj, classext_iclass_free, (void *)&args); - if (RCLASS(obj)->ns_classext_tbl) { - st_free_table(RCLASS(obj)->ns_classext_tbl); + if (RCLASS_CLASSEXT_TBL(obj)) { + st_free_table(RCLASS_CLASSEXT_TBL(obj)); } RB_DEBUG_COUNTER_INC(obj_iclass_ptr); @@ -1490,7 +1471,10 @@ internal_object_p(VALUE obj) case T_ZOMBIE: break; case T_CLASS: - if (!RBASIC(obj)->klass) break; + if (obj == rb_mRubyVMFrozenCore) + return 1; + + if (!RBASIC_CLASS(obj)) break; if (RCLASS_SINGLETON_P(obj)) { return rb_singleton_class_internal_p(obj); } @@ -1790,9 +1774,9 @@ generate_next_object_id(void) // 64bit atomics are available return SIZET2NUM(RUBY_ATOMIC_SIZE_FETCH_ADD(object_id_counter, 1) * OBJ_ID_INCREMENT); #else - unsigned int lock_lev = rb_gc_vm_lock(); + unsigned int lock_lev = RB_GC_VM_LOCK(); VALUE id = ULL2NUM(++object_id_counter * OBJ_ID_INCREMENT); - rb_gc_vm_unlock(lock_lev); + RB_GC_VM_UNLOCK(lock_lev); return id; #endif } @@ -1847,19 +1831,6 @@ id2ref_tbl_memsize(const void *data) return rb_st_memsize(data); } -static void -id2ref_tbl_compact(void *data) -{ - st_table *table = (st_table *)data; - if (LIKELY(RB_POSFIXABLE(LAST_OBJECT_ID()))) { - // We know keys are all FIXNUM, so no need to update them. - gc_ref_update_table_values_only(table); - } - else { - gc_update_table_refs(table); - } -} - static void id2ref_tbl_free(void *data) { @@ -1874,19 +1845,18 @@ static const rb_data_type_t id2ref_tbl_type = { .dmark = id2ref_tbl_mark, .dfree = id2ref_tbl_free, .dsize = id2ref_tbl_memsize, - .dcompact = id2ref_tbl_compact, + // dcompact function not required because the table is reference updated + // in rb_gc_vm_weak_table_foreach }, .flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY }; -#define RUBY_ATOMIC_VALUE_LOAD(x) (VALUE)(RUBY_ATOMIC_PTR_LOAD(x)) - static VALUE class_object_id(VALUE klass) { VALUE id = RUBY_ATOMIC_VALUE_LOAD(RCLASS(klass)->object_id); if (!id) { - unsigned int lock_lev = rb_gc_vm_lock(); + unsigned int lock_lev = RB_GC_VM_LOCK(); id = generate_next_object_id(); VALUE existing_id = RUBY_ATOMIC_VALUE_CAS(RCLASS(klass)->object_id, 0, id); if (existing_id) { @@ -1895,21 +1865,40 @@ class_object_id(VALUE klass) else if (RB_UNLIKELY(id2ref_tbl)) { st_insert(id2ref_tbl, id, klass); } - rb_gc_vm_unlock(lock_lev); + RB_GC_VM_UNLOCK(lock_lev); } return id; } +static inline VALUE +object_id_get(VALUE obj, shape_id_t shape_id) +{ + VALUE id; + if (rb_shape_too_complex_p(shape_id)) { + id = rb_obj_field_get(obj, ROOT_TOO_COMPLEX_WITH_OBJ_ID); + } + else { + id = rb_obj_field_get(obj, rb_shape_object_id(shape_id)); + } + +#if RUBY_DEBUG + if (!(FIXNUM_P(id) || RB_TYPE_P(id, T_BIGNUM))) { + rb_p(obj); + rb_bug("Object's shape includes object_id, but it's missing %s", rb_obj_info(obj)); + } +#endif + + return id; +} + static VALUE object_id0(VALUE obj) { VALUE id = Qfalse; + shape_id_t shape_id = RBASIC_SHAPE_ID(obj); - if (rb_shape_has_object_id(rb_obj_shape(obj))) { - shape_id_t object_id_shape_id = rb_shape_transition_object_id(obj); - id = rb_obj_field_get(obj, object_id_shape_id); - RUBY_ASSERT(id, "object_id missing"); - return id; + if (rb_shape_has_object_id(shape_id)) { + return object_id_get(obj, shape_id); } // rb_shape_object_id_shape may lock if the current shape has @@ -1917,7 +1906,11 @@ object_id0(VALUE obj) shape_id_t object_id_shape_id = rb_shape_transition_object_id(obj); id = generate_next_object_id(); - rb_obj_field_set(obj, object_id_shape_id, id); + rb_obj_field_set(obj, object_id_shape_id, 0, id); + + RUBY_ASSERT(RBASIC_SHAPE_ID(obj) == object_id_shape_id); + RUBY_ASSERT(rb_shape_obj_has_id(obj)); + if (RB_UNLIKELY(id2ref_tbl)) { st_insert(id2ref_tbl, (st_data_t)id, (st_data_t)obj); } @@ -1934,14 +1927,17 @@ object_id(VALUE obj) // in different namespaces, so we cannot store the object id // in fields. return class_object_id(obj); + case T_IMEMO: + rb_bug("T_IMEMO can't have an object_id"); + break; default: break; } if (UNLIKELY(rb_gc_multi_ractor_p() && rb_ractor_shareable_p(obj))) { - unsigned int lock_lev = rb_gc_vm_lock(); + unsigned int lock_lev = RB_GC_VM_LOCK(); VALUE id = object_id0(obj); - rb_gc_vm_unlock(lock_lev); + RB_GC_VM_UNLOCK(lock_lev); return id; } @@ -1960,6 +1956,9 @@ build_id2ref_i(VALUE obj, void *data) st_insert(id2ref_tbl, RCLASS(obj)->object_id, obj); } break; + case T_IMEMO: + case T_NONE: + break; default: if (rb_shape_obj_has_id(obj)) { st_insert(id2ref_tbl, rb_obj_id(obj), obj); @@ -1973,7 +1972,7 @@ object_id_to_ref(void *objspace_ptr, VALUE object_id) { rb_objspace_t *objspace = objspace_ptr; - unsigned int lev = rb_gc_vm_lock(); + unsigned int lev = RB_GC_VM_LOCK(); if (!id2ref_tbl) { rb_gc_vm_barrier(); // stop other ractors @@ -1983,14 +1982,21 @@ object_id_to_ref(void *objspace_ptr, VALUE object_id) // the table even though it wasn't inserted yet. id2ref_tbl = st_init_table(&object_id_hash_type); id2ref_value = TypedData_Wrap_Struct(0, &id2ref_tbl_type, id2ref_tbl); - rb_gc_impl_each_object(objspace, build_id2ref_i, (void *)id2ref_tbl); + + // build_id2ref_i will most certainly malloc, which could trigger GC and sweep + // objects we just added to the table. + bool gc_disabled = RTEST(rb_gc_disable_no_rest()); + { + rb_gc_impl_each_object(objspace, build_id2ref_i, (void *)id2ref_tbl); + } + if (!gc_disabled) rb_gc_enable(); id2ref_tbl_built = true; } VALUE obj; bool found = st_lookup(id2ref_tbl, object_id, &obj) && !rb_gc_impl_garbage_object_p(objspace, obj); - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); if (found) { return obj; @@ -2012,25 +2018,36 @@ obj_free_object_id(VALUE obj) switch (BUILTIN_TYPE(obj)) { case T_CLASS: case T_MODULE: - if (RCLASS(obj)->object_id) { - obj_id = RCLASS(obj)->object_id; + obj_id = RCLASS(obj)->object_id; + break; + case T_IMEMO: + if (!IMEMO_TYPE_P(obj, imemo_fields)) { + return; + } + // fallthrough + case T_OBJECT: + { + shape_id_t shape_id = RBASIC_SHAPE_ID(obj); + if (rb_shape_has_object_id(shape_id)) { + obj_id = object_id_get(obj, shape_id); } break; + } default: - if (rb_shape_obj_has_id(obj)) { - obj_id = object_id(obj); - } - break; + // For generic_fields, the T_IMEMO/fields is responsible for freeing the id. + return; } - } - if (RB_UNLIKELY(obj_id)) { - RUBY_ASSERT(FIXNUM_P(obj_id) || RB_TYPE_P(obj, T_BIGNUM)); + if (RB_UNLIKELY(obj_id)) { + RUBY_ASSERT(FIXNUM_P(obj_id) || RB_TYPE_P(obj_id, T_BIGNUM)); - if (!st_delete(id2ref_tbl, (st_data_t *)&obj_id, NULL)) { - // If we're currently building the table then it's not a bug - if (id2ref_tbl_built) { - rb_bug("Object ID seen, but not in _id2ref table: object_id=%llu object=%s", NUM2ULL(obj_id), rb_obj_info(obj)); + if (!st_delete(id2ref_tbl, (st_data_t *)&obj_id, NULL)) { + // If we're currently building the table then it's not a bug. + // The the object is a T_IMEMO/fields, then it's possible the actual object + // has been garbage collected already. + if (id2ref_tbl_built && !RB_TYPE_P(obj, T_IMEMO)) { + rb_bug("Object ID seen, but not in _id2ref table: object_id=%llu object=%s", NUM2ULL(obj_id), rb_obj_info(obj)); + } } } } @@ -2041,9 +2058,8 @@ rb_gc_obj_free_vm_weak_references(VALUE obj) { obj_free_object_id(obj); - if (FL_TEST_RAW(obj, FL_EXIVAR)) { - rb_free_generic_ivar((VALUE)obj); - FL_UNSET_RAW(obj, FL_EXIVAR); + if (rb_obj_exivar_p(obj)) { + rb_free_generic_ivar(obj); } switch (BUILTIN_TYPE(obj)) { @@ -2057,6 +2073,15 @@ rb_gc_obj_free_vm_weak_references(VALUE obj) break; case T_IMEMO: switch (imemo_type(obj)) { + case imemo_callcache: { + const struct rb_callcache *cc = (const struct rb_callcache *)obj; + + if (vm_cc_refinement_p(cc)) { + rb_vm_delete_cc_refinement(cc); + } + + break; + } case imemo_callinfo: rb_vm_ci_free((const struct rb_callinfo *)obj); break; @@ -2210,7 +2235,34 @@ rb_obj_id(VALUE obj) bool rb_obj_id_p(VALUE obj) { - return rb_shape_obj_has_id(obj); + return !RB_TYPE_P(obj, T_IMEMO) && rb_shape_obj_has_id(obj); +} + +/* + * GC implementations should call this function before the GC phase that updates references + * embedded in the machine code generated by JIT compilers. JIT compilers usually enforce the + * "W^X" policy and protect the code memory from being modified during execution. This function + * makes the code memory writeable. + */ +void +rb_gc_before_updating_jit_code(void) +{ +#if USE_YJIT + rb_yjit_mark_all_writeable(); +#endif +} + +/* + * GC implementations should call this function before the GC phase that updates references + * embedded in the machine code generated by JIT compilers. This function makes the code memory + * executable again. + */ +void +rb_gc_after_updating_jit_code(void) +{ +#if USE_YJIT + rb_yjit_mark_all_executable(); +#endif } static enum rb_id_table_iterator_result @@ -2258,29 +2310,14 @@ classext_memsize(rb_classext_t *ext, bool prime, VALUE namespace, void *arg) *size += s; } -static void -classext_fields_hash_memsize(rb_classext_t *ext, bool prime, VALUE namespace, void *arg) -{ - size_t *size = (size_t *)arg; - size_t count; - RB_VM_LOCK_ENTER(); - { - count = rb_st_table_size((st_table *)RCLASSEXT_FIELDS(ext)); - } - RB_VM_LOCK_LEAVE(); - // class IV sizes are allocated as powers of two - *size += SIZEOF_VALUE << bit_length(count); -} - static void classext_superclasses_memsize(rb_classext_t *ext, bool prime, VALUE namespace, void *arg) { size_t *size = (size_t *)arg; size_t array_size; - if (RCLASSEXT_SUPERCLASSES_OWNER(ext)) { - array_size = RCLASSEXT_SUPERCLASS_DEPTH(ext); - if (RCLASSEXT_SUPERCLASSES_WITH_SELF(ext)) - array_size += 1; + if (RCLASSEXT_SUPERCLASSES_WITH_SELF(ext)) { + RUBY_ASSERT(prime); + array_size = RCLASSEXT_SUPERCLASS_DEPTH(ext) + 1; *size += array_size * sizeof(VALUE); } } @@ -2294,10 +2331,6 @@ rb_obj_memsize_of(VALUE obj) return 0; } - if (FL_TEST(obj, FL_EXIVAR)) { - size += rb_generic_ivar_memsize(obj); - } - switch (BUILTIN_TYPE(obj)) { case T_OBJECT: if (rb_shape_obj_too_complex_p(obj)) { @@ -2310,15 +2343,6 @@ rb_obj_memsize_of(VALUE obj) case T_MODULE: case T_CLASS: rb_class_classext_foreach(obj, classext_memsize, (void *)&size); - - if (rb_shape_obj_too_complex_p(obj)) { - rb_class_classext_foreach(obj, classext_fields_hash_memsize, (void *)&size); - } - else { - // class IV sizes are allocated as powers of two - size += SIZEOF_VALUE << bit_length(RCLASS_FIELDS_COUNT(obj)); - } - rb_class_classext_foreach(obj, classext_superclasses_memsize, (void *)&size); break; case T_ICLASS: @@ -2474,6 +2498,7 @@ count_objects(int argc, VALUE *argv, VALUE os) { struct count_objects_data data = { 0 }; VALUE hash = Qnil; + VALUE types[T_MASK + 1]; if (rb_check_arity(argc, 0, 1) == 1) { hash = argv[0]; @@ -2481,6 +2506,13 @@ count_objects(int argc, VALUE *argv, VALUE os) rb_raise(rb_eTypeError, "non-hash given"); } + for (size_t i = 0; i <= T_MASK; i++) { + // type_sym can allocate an object, + // so we need to create all key symbols in advance + // not to disturb the result + types[i] = type_sym(i); + } + rb_gc_impl_each_object(rb_gc_get_objspace(), count_objects_i, &data); if (NIL_P(hash)) { @@ -2493,9 +2525,9 @@ count_objects(int argc, VALUE *argv, VALUE os) rb_hash_aset(hash, ID2SYM(rb_intern("FREE")), SIZET2NUM(data.freed)); for (size_t i = 0; i <= T_MASK; i++) { - VALUE type = type_sym(i); - if (data.counts[i]) - rb_hash_aset(hash, type, SIZET2NUM(data.counts[i])); + if (data.counts[i]) { + rb_hash_aset(hash, types[i], SIZET2NUM(data.counts[i])); + } } return hash; @@ -2812,12 +2844,11 @@ struct mark_cc_entry_args { }; static enum rb_id_table_iterator_result -mark_cc_entry_i(ID id, VALUE ccs_ptr, void *data) +mark_cc_entry_i(VALUE ccs_ptr, void *data) { struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_ptr; VM_ASSERT(vm_ccs_p(ccs)); - VM_ASSERT(id == ccs->cme->called_id); if (METHOD_ENTRY_INVALIDATED(ccs->cme)) { rb_vm_ccs_free(ccs); @@ -2845,7 +2876,7 @@ mark_cc_tbl(rb_objspace_t *objspace, struct rb_id_table *tbl, VALUE klass) args.objspace = objspace; args.klass = klass; - rb_id_table_foreach(tbl, mark_cc_entry_i, (void *)&args); + rb_id_table_foreach_values(tbl, mark_cc_entry_i, (void *)&args); } static enum rb_id_table_iterator_result @@ -3048,7 +3079,6 @@ rb_gc_mark_roots(void *objspace, const char **categoryp) MARK_CHECKPOINT("vm"); rb_vm_mark(vm); - if (vm->self) gc_mark_internal(vm->self); MARK_CHECKPOINT("end_proc"); rb_mark_end_proc(); @@ -3092,10 +3122,7 @@ gc_mark_classext_module(rb_classext_t *ext, bool prime, VALUE namespace, void *a gc_mark_internal(RCLASSEXT_SUPER(ext)); } mark_m_tbl(objspace, RCLASSEXT_M_TBL(ext)); - if (rb_shape_obj_too_complex_p(obj)) { - gc_mark_tbl_no_pin((st_table *)RCLASSEXT_FIELDS(ext)); - // for the case ELSE is written in rb_gc_mark_children() because it's per RClass, not classext - } + gc_mark_internal(RCLASSEXT_FIELDS_OBJ(ext)); if (!RCLASSEXT_SHARED_CONST_TBL(ext) && RCLASSEXT_CONST_TBL(ext)) { mark_const_tbl(objspace, RCLASSEXT_CONST_TBL(ext)); } @@ -3132,7 +3159,7 @@ rb_gc_mark_children(void *objspace, VALUE obj) { struct gc_mark_classext_foreach_arg foreach_args; - if (FL_TEST_RAW(obj, FL_EXIVAR)) { + if (rb_obj_exivar_p(obj)) { rb_mark_generic_ivar(obj); } @@ -3175,12 +3202,6 @@ rb_gc_mark_children(void *objspace, VALUE obj) foreach_args.objspace = objspace; foreach_args.obj = obj; rb_class_classext_foreach(obj, gc_mark_classext_module, (void *)&foreach_args); - - if (!rb_shape_obj_too_complex_p(obj)) { - for (attr_index_t i = 0; i < RCLASS_FIELDS_COUNT(obj); i++) { - gc_mark_internal(RCLASS_PRIME_FIELDS(obj)[i]); - } - } break; case T_ICLASS: @@ -3261,14 +3282,9 @@ rb_gc_mark_children(void *objspace, VALUE obj) if (fields_count) { VALUE klass = RBASIC_CLASS(obj); - // Skip updating max_iv_count if the prime classext is not writable - // because GC context doesn't provide information about namespaces. - if (RCLASS_PRIME_CLASSEXT_WRITABLE_P(klass)) { - VM_ASSERT(rb_shape_obj_too_complex_p(klass)); - // Increment max_iv_count if applicable, used to determine size pool allocation - if (RCLASS_MAX_IV_COUNT(klass) < fields_count) { - RCLASS_SET_MAX_IV_COUNT(klass, fields_count); - } + // Increment max_iv_count if applicable, used to determine size pool allocation + if (RCLASS_MAX_IV_COUNT(klass) < fields_count) { + RCLASS_SET_MAX_IV_COUNT(klass, fields_count); } } @@ -3788,10 +3804,8 @@ update_subclasses(void *objspace, rb_classext_t *ext) static void update_superclasses(rb_objspace_t *objspace, rb_classext_t *ext) { - size_t array_size = RCLASSEXT_SUPERCLASS_DEPTH(ext); - if (RCLASSEXT_SUPERCLASSES_OWNER(ext)) { - if (RCLASSEXT_SUPERCLASSES_WITH_SELF(ext)) - array_size += 1; + if (RCLASSEXT_SUPERCLASSES_WITH_SELF(ext)) { + size_t array_size = RCLASSEXT_SUPERCLASS_DEPTH(ext) + 1; for (size_t i = 0; i < array_size; i++) { UPDATE_IF_MOVED(objspace, RCLASSEXT_SUPERCLASSES(ext)[i]); } @@ -3813,7 +3827,6 @@ static void update_classext(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg) { struct classext_foreach_args *args = (struct classext_foreach_args *)arg; - VALUE klass = args->klass; rb_objspace_t *objspace = args->objspace; if (RCLASSEXT_SUPER(ext)) { @@ -3822,16 +3835,7 @@ update_classext(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg) update_m_tbl(objspace, RCLASSEXT_M_TBL(ext)); - if (args->obj_too_complex) { - gc_ref_update_table_values_only((st_table *)RCLASSEXT_FIELDS(ext)); - } - else { - // Classext is not copied in this case - for (attr_index_t i = 0; i < RCLASS_FIELDS_COUNT(klass); i++) { - UPDATE_IF_MOVED(objspace, RCLASSEXT_FIELDS(RCLASS_EXT_PRIME(klass))[i]); - } - } - + UPDATE_IF_MOVED(objspace, ext->fields_obj); if (!RCLASSEXT_SHARED_CONST_TBL(ext)) { update_const_tbl(objspace, RCLASSEXT_CONST_TBL(ext)); } @@ -3902,6 +3906,23 @@ vm_weak_table_foreach_update_weak_key(st_data_t *key, st_data_t *value, st_data_ return ret; } +static int +vm_weak_table_cc_refinement_foreach(st_data_t key, st_data_t data, int error) +{ + struct global_vm_table_foreach_data *iter_data = (struct global_vm_table_foreach_data *)data; + + return iter_data->callback((VALUE)key, iter_data->data); +} + +static int +vm_weak_table_cc_refinement_foreach_update_update(st_data_t *key, st_data_t data, int existing) +{ + struct global_vm_table_foreach_data *iter_data = (struct global_vm_table_foreach_data *)data; + + return iter_data->update_callback((VALUE *)key, iter_data->data); +} + + static int vm_weak_table_str_sym_foreach(st_data_t key, st_data_t value, st_data_t data, int error) { @@ -3933,38 +3954,6 @@ vm_weak_table_foreach_update_weak_value(st_data_t *key, st_data_t *value, st_dat return iter_data->update_callback((VALUE *)value, iter_data->data); } -static void -free_gen_fields_tbl(VALUE obj, struct gen_fields_tbl *fields_tbl) -{ - if (UNLIKELY(rb_shape_obj_too_complex_p(obj))) { - st_free_table(fields_tbl->as.complex.table); - } - - xfree(fields_tbl); -} - -static int -vm_weak_table_gen_fields_foreach_too_complex_i(st_data_t _key, st_data_t value, st_data_t data, int error) -{ - struct global_vm_table_foreach_data *iter_data = (struct global_vm_table_foreach_data *)data; - - GC_ASSERT(!iter_data->weak_only); - - if (SPECIAL_CONST_P((VALUE)value)) return ST_CONTINUE; - - return iter_data->callback((VALUE)value, iter_data->data); -} - -static int -vm_weak_table_gen_fields_foreach_too_complex_replace_i(st_data_t *_key, st_data_t *value, st_data_t data, int existing) -{ - struct global_vm_table_foreach_data *iter_data = (struct global_vm_table_foreach_data *)data; - - GC_ASSERT(!iter_data->weak_only); - - return iter_data->update_callback((VALUE *)value, iter_data->data); -} - struct st_table *rb_generic_fields_tbl_get(void); static int @@ -4001,76 +3990,74 @@ vm_weak_table_gen_fields_foreach(st_data_t key, st_data_t value, st_data_t data) int ret = iter_data->callback((VALUE)key, iter_data->data); + VALUE new_value = (VALUE)value; + VALUE new_key = (VALUE)key; + switch (ret) { case ST_CONTINUE: break; case ST_DELETE: - free_gen_fields_tbl((VALUE)key, (struct gen_fields_tbl *)value); - - FL_UNSET((VALUE)key, FL_EXIVAR); + RBASIC_SET_SHAPE_ID((VALUE)key, ROOT_SHAPE_ID); return ST_DELETE; case ST_REPLACE: { - VALUE new_key = (VALUE)key; ret = iter_data->update_callback(&new_key, iter_data->data); - if (key != new_key) ret = ST_DELETE; - DURING_GC_COULD_MALLOC_REGION_START(); - { - st_insert(rb_generic_fields_tbl_get(), (st_data_t)new_key, value); + if (key != new_key) { + ret = ST_DELETE; } - DURING_GC_COULD_MALLOC_REGION_END(); - key = (st_data_t)new_key; break; } default: - return ret; + rb_bug("vm_weak_table_gen_fields_foreach: return value %d not supported", ret); } if (!iter_data->weak_only) { - struct gen_fields_tbl *fields_tbl = (struct gen_fields_tbl *)value; + int ivar_ret = iter_data->callback(new_value, iter_data->data); + switch (ivar_ret) { + case ST_CONTINUE: + break; - if (rb_shape_obj_too_complex_p((VALUE)key)) { - st_foreach_with_replace( - fields_tbl->as.complex.table, - vm_weak_table_gen_fields_foreach_too_complex_i, - vm_weak_table_gen_fields_foreach_too_complex_replace_i, - data - ); - } - else { - for (uint32_t i = 0; i < fields_tbl->as.shape.fields_count; i++) { - if (SPECIAL_CONST_P(fields_tbl->as.shape.fields[i])) continue; + case ST_REPLACE: + iter_data->update_callback(&new_value, iter_data->data); + break; - int ivar_ret = iter_data->callback(fields_tbl->as.shape.fields[i], iter_data->data); - switch (ivar_ret) { - case ST_CONTINUE: - break; - case ST_REPLACE: - iter_data->update_callback(&fields_tbl->as.shape.fields[i], iter_data->data); - break; - default: - rb_bug("vm_weak_table_gen_fields_foreach: return value %d not supported", ivar_ret); - } - } + default: + rb_bug("vm_weak_table_gen_fields_foreach: return value %d not supported", ivar_ret); } } + if (key != new_key || value != new_value) { + DURING_GC_COULD_MALLOC_REGION_START(); + { + st_insert(rb_generic_fields_tbl_get(), (st_data_t)new_key, new_value); + } + DURING_GC_COULD_MALLOC_REGION_END(); + } + return ret; } static int -vm_weak_table_frozen_strings_foreach(st_data_t key, st_data_t value, st_data_t data, int error) +vm_weak_table_frozen_strings_foreach(VALUE *str, void *data) { - int retval = vm_weak_table_foreach_weak_key(key, value, data, error); - if (retval == ST_DELETE) { - FL_UNSET((VALUE)key, RSTRING_FSTR); + // int retval = vm_weak_table_foreach_weak_key(key, value, data, error); + struct global_vm_table_foreach_data *iter_data = (struct global_vm_table_foreach_data *)data; + int retval = iter_data->callback(*str, iter_data->data); + + if (retval == ST_REPLACE) { + retval = iter_data->update_callback(str, iter_data->data); } + + if (retval == ST_DELETE) { + FL_UNSET(*str, RSTRING_FSTR); + } + return retval; } -void rb_fstring_foreach_with_replace(st_foreach_check_callback_func *func, st_update_callback_func *replace, st_data_t arg); +void rb_fstring_foreach_with_replace(int (*callback)(VALUE *str, void *data), void *data); void rb_gc_vm_weak_table_foreach(vm_table_foreach_callback_func callback, vm_table_update_callback_func update_callback, @@ -4146,13 +4133,25 @@ rb_gc_vm_weak_table_foreach(vm_table_foreach_callback_func callback, case RB_GC_VM_FROZEN_STRINGS_TABLE: { rb_fstring_foreach_with_replace( vm_weak_table_frozen_strings_foreach, - vm_weak_table_foreach_update_weak_key, - (st_data_t)&foreach_data + &foreach_data ); break; } + case RB_GC_VM_CC_REFINEMENT_TABLE: { + if (vm->cc_refinement_table) { + set_foreach_with_replace( + vm->cc_refinement_table, + vm_weak_table_cc_refinement_foreach, + vm_weak_table_cc_refinement_foreach_update_update, + (st_data_t)&foreach_data + ); + } + break; + } case RB_GC_VM_WEAK_TABLE_COUNT: - rb_bug("Unreacheable"); + rb_bug("Unreachable"); + default: + rb_bug("rb_gc_vm_weak_table_foreach: unknown table %d", table); } } @@ -4188,7 +4187,6 @@ rb_gc_update_object_references(void *objspace, VALUE obj) // Continue to the shared T_CLASS/T_MODULE case T_MODULE: args.klass = obj; - args.obj_too_complex = rb_shape_obj_too_complex_p(obj); args.objspace = objspace; rb_class_classext_foreach(obj, update_classext, (void *)&args); break; @@ -4558,8 +4556,7 @@ ruby_gc_set_params(void) void rb_objspace_reachable_objects_from(VALUE obj, void (func)(VALUE, void *), void *data) { - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { if (rb_gc_impl_during_gc_p(rb_gc_get_objspace())) rb_bug("rb_objspace_reachable_objects_from() is not supported while during GC"); if (!RB_SPECIAL_CONST_P(obj)) { @@ -4575,7 +4572,6 @@ rb_objspace_reachable_objects_from(VALUE obj, void (func)(VALUE, void *), void * vm->gc.mark_func_data = prev_mfd; } } - RB_VM_LOCK_LEAVE(); } struct root_objects_data { @@ -4737,6 +4733,7 @@ rb_raw_obj_info_common(char *const buff, const size_t buff_size, const VALUE obj // const int age = RVALUE_AGE_GET(obj); if (rb_gc_impl_pointer_to_heap_p(rb_gc_get_objspace(), (void *)obj)) { + APPEND_F("%p %s/", (void *)obj, obj_type_name(obj)); // TODO: fixme // APPEND_F("%p [%d%s%s%s%s%s%s] %s ", // (void *)obj, age, @@ -4764,7 +4761,7 @@ rb_raw_obj_info_common(char *const buff, const size_t buff_size, const VALUE obj else if (RTEST(RBASIC(obj)->klass)) { VALUE class_path = rb_class_path_cached(RBASIC(obj)->klass); if (!NIL_P(class_path)) { - APPEND_F("(%s)", RSTRING_PTR(class_path)); + APPEND_F("%s ", RSTRING_PTR(class_path)); } } } @@ -4978,6 +4975,7 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU #undef C +#ifdef RUBY_ASAN_ENABLED void rb_asan_poison_object(VALUE obj) { @@ -4998,16 +4996,37 @@ rb_asan_poisoned_object_p(VALUE obj) MAYBE_UNUSED(struct RVALUE *) ptr = (void *)obj; return __asan_region_is_poisoned(ptr, rb_gc_obj_slot_size(obj)); } +#endif + +static void +raw_obj_info(char *const buff, const size_t buff_size, VALUE obj) +{ + size_t pos = rb_raw_obj_info_common(buff, buff_size, obj); + pos = rb_raw_obj_info_buitin_type(buff, buff_size, obj, pos); + if (pos >= buff_size) {} // truncated +} const char * rb_raw_obj_info(char *const buff, const size_t buff_size, VALUE obj) { - asan_unpoisoning_object(obj) { - size_t pos = rb_raw_obj_info_common(buff, buff_size, obj); - pos = rb_raw_obj_info_buitin_type(buff, buff_size, obj, pos); - if (pos >= buff_size) {} // truncated - } + void *objspace = rb_gc_get_objspace(); + if (SPECIAL_CONST_P(obj)) { + raw_obj_info(buff, buff_size, obj); + } + else if (!rb_gc_impl_pointer_to_heap_p(objspace, (const void *)obj)) { + snprintf(buff, buff_size, "out-of-heap:%p", (void *)obj); + } +#if 0 // maybe no need to check it? + else if (0 && rb_gc_impl_garbage_object_p(objspace, obj)) { + snprintf(buff, buff_size, "garbage:%p", (void *)obj); + } +#endif + else { + asan_unpoisoning_object(obj) { + raw_obj_info(buff, buff_size, obj); + } + } return buff; } @@ -5167,11 +5186,6 @@ rb_memerror_reentered(void) return (ec && rb_ec_raised_p(ec, RAISED_NOMEMORY)); } -void -rb_malloc_info_show_results(void) -{ -} - static void * handle_malloc_failure(void *ptr) { diff --git a/gc/README.md b/gc/README.md index 102b24e24e..cb71357973 100644 --- a/gc/README.md +++ b/gc/README.md @@ -15,12 +15,17 @@ Two GC implementations are included in Ruby: > [!IMPORTANT] > Ruby's modular GC feature is experimental and subject to change. There may be bugs or performance impacts. Use at your own risk. +### Building Ruby with Modular GC + 1. Configure Ruby with the `--with-modular-gc=` option, where `dir` is the directory you want to place the built GC libraries into. 2. Build Ruby as usual. -3. Build your desired GC implementation with `make install-modular-gc MODULAR_GC=`. This will build the GC implementation and place the built library into the `dir` specified in step 1. `impl` can be one of: + +### Building GC implementations shipped with Ruby + +1. Build your desired GC implementation with `make install-modular-gc MODULAR_GC=`. This will build the GC implementation and place the built library into the `dir` specified in step 1. `impl` can be one of: - `default`: The default GC that Ruby ships with. - `mmtk`: The GC that uses [MMTk](https://www.mmtk.io/) as the back-end. See Ruby-specific details in the [ruby/mmtk](https://github.com/ruby/mmtk) repository. -4. Run your desired GC implementation by setting the `RUBY_GC_LIBRARY=` environment variable, where `lib` could be `default`, `mmtk`, or your own implementation (as long as you place it in the `dir` specified in step 1). +2. Run your desired GC implementation by setting the `RUBY_GC_LIBRARY=` environment variable, where `lib` could be `default`, `mmtk`, or your own implementation (as long as you place it in the `dir` specified in step 1). ## Modular GC API diff --git a/gc/default/default.c b/gc/default/default.c index 5bbdc1e026..384b3f10f0 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -15,7 +15,11 @@ # include #endif -#include "internal/bits.h" +#ifdef BUILDING_MODULAR_GC +# define nlz_int64(x) (x == 0 ? 64 : (unsigned int)__builtin_clzll((unsigned long long)x)) +#else +# include "internal/bits.h" +#endif #include "ruby/ruby.h" #include "ruby/atomic.h" @@ -40,7 +44,19 @@ # include "debug_counter.h" #endif -#include "internal/sanitizers.h" +#ifdef BUILDING_MODULAR_GC +# define rb_asan_poison_object(obj) ((void)(obj)) +# define rb_asan_unpoison_object(obj, newobj_p) ((void)(obj), (void)(newobj_p)) +# define asan_unpoisoning_object(obj) if ((obj) || true) +# define asan_poison_memory_region(ptr, size) ((void)(ptr), (void)(size)) +# define asan_unpoison_memory_region(ptr, size, malloc_p) ((void)(ptr), (size), (malloc_p)) +# define asan_unpoisoning_memory_region(ptr, size) if ((ptr) || (size) || true) + +# define VALGRIND_MAKE_MEM_DEFINED(ptr, size) ((void)(ptr), (void)(size)) +# define VALGRIND_MAKE_MEM_UNDEFINED(ptr, size) ((void)(ptr), (void)(size)) +#else +# include "internal/sanitizers.h" +#endif /* MALLOC_HEADERS_BEGIN */ #ifndef HAVE_MALLOC_USABLE_SIZE @@ -632,7 +648,9 @@ struct rvalue_overhead { size_t rb_gc_impl_obj_slot_size(VALUE obj); # define GET_RVALUE_OVERHEAD(obj) ((struct rvalue_overhead *)((uintptr_t)obj + rb_gc_impl_obj_slot_size(obj))) #else -# define RVALUE_OVERHEAD 0 +# ifndef RVALUE_OVERHEAD +# define RVALUE_OVERHEAD 0 +# endif #endif #define BASE_SLOT_SIZE (sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX]) + RVALUE_OVERHEAD) @@ -1089,10 +1107,10 @@ tick(void) return val; } +#elif defined(__POWERPC__) && defined(__APPLE__) /* Implementation for macOS PPC by @nobu * See: https://github.com/ruby/ruby/pull/5975#discussion_r890045558 */ -#elif defined(__POWERPC__) && defined(__APPLE__) typedef unsigned long long tick_t; #define PRItick "llu" @@ -1211,7 +1229,7 @@ check_rvalue_consistency_force(rb_objspace_t *objspace, const VALUE obj, int ter { int err = 0; - int lev = rb_gc_vm_lock_no_barrier(); + int lev = RB_GC_VM_LOCK_NO_BARRIER(); { if (SPECIAL_CONST_P(obj)) { fprintf(stderr, "check_rvalue_consistency: %p is a special const.\n", (void *)obj); @@ -1301,7 +1319,7 @@ check_rvalue_consistency_force(rb_objspace_t *objspace, const VALUE obj, int ter } } } - rb_gc_vm_unlock_no_barrier(lev); + RB_GC_VM_UNLOCK_NO_BARRIER(lev); if (err > 0 && terminate) { rb_bug("check_rvalue_consistency_force: there is %d errors.", err); @@ -2073,10 +2091,10 @@ heap_prepare(rb_objspace_t *objspace, rb_heap_t *heap) static inline VALUE newobj_fill(VALUE obj, VALUE v1, VALUE v2, VALUE v3) { - VALUE *p = (VALUE *)obj; - p[2] = v1; - p[3] = v2; - p[4] = v3; + VALUE *p = (VALUE *)(obj + sizeof(struct RBasic)); + p[0] = v1; + p[1] = v2; + p[2] = v3; return obj; } @@ -2101,12 +2119,13 @@ rb_gc_impl_source_location_cstr(int *ptr) static inline VALUE newobj_init(VALUE klass, VALUE flags, int wb_protected, rb_objspace_t *objspace, VALUE obj) { -#if !__has_feature(memory_sanitizer) GC_ASSERT(BUILTIN_TYPE(obj) == T_NONE); GC_ASSERT((flags & FL_WB_PROTECTED) == 0); -#endif RBASIC(obj)->flags = flags; *((VALUE *)&RBASIC(obj)->klass) = klass; +#if RBASIC_SHAPE_ID_FIELD + RBASIC(obj)->shape_id = 0; +#endif int t = flags & RUBY_T_MASK; if (t == T_CLASS || t == T_MODULE || t == T_ICLASS) { @@ -2121,7 +2140,7 @@ newobj_init(VALUE klass, VALUE flags, int wb_protected, rb_objspace_t *objspace, #if RGENGC_CHECK_MODE newobj_fill(obj, 0, 0, 0); - int lev = rb_gc_vm_lock_no_barrier(); + int lev = RB_GC_VM_LOCK_NO_BARRIER(); { check_rvalue_consistency(objspace, obj); @@ -2132,7 +2151,7 @@ newobj_init(VALUE klass, VALUE flags, int wb_protected, rb_objspace_t *objspace, if (RVALUE_REMEMBERED(objspace, obj)) rb_bug("newobj: %s is remembered.", rb_obj_info(obj)); } - rb_gc_vm_unlock_no_barrier(lev); + RB_GC_VM_UNLOCK_NO_BARRIER(lev); #endif if (RB_UNLIKELY(wb_protected == FALSE)) { @@ -2161,7 +2180,7 @@ newobj_init(VALUE klass, VALUE flags, int wb_protected, rb_objspace_t *objspace, gc_report(5, objspace, "newobj: %s\n", rb_obj_info(obj)); - RUBY_DEBUG_LOG("obj:%p (%s)", (void *)obj, rb_obj_info(obj)); + // RUBY_DEBUG_LOG("obj:%p (%s)", (void *)obj, rb_obj_info(obj)); return obj; } @@ -2344,7 +2363,7 @@ newobj_cache_miss(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size bool unlock_vm = false; if (!vm_locked) { - lev = rb_gc_cr_lock(); + lev = RB_GC_CR_LOCK(); unlock_vm = true; } @@ -2368,7 +2387,7 @@ newobj_cache_miss(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size } if (unlock_vm) { - rb_gc_cr_unlock(lev); + RB_GC_CR_UNLOCK(lev); } if (RB_UNLIKELY(obj == Qfalse)) { @@ -2397,7 +2416,7 @@ newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_new VALUE obj; unsigned int lev; - lev = rb_gc_cr_lock(); + lev = RB_GC_CR_LOCK(); { if (RB_UNLIKELY(during_gc || ruby_gc_stressful)) { if (during_gc) { @@ -2419,7 +2438,7 @@ newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_new obj = newobj_alloc(objspace, cache, heap_idx, true); newobj_init(klass, flags, wb_protected, objspace, obj); } - rb_gc_cr_unlock(lev); + RB_GC_CR_UNLOCK(lev); return obj; } @@ -2734,7 +2753,7 @@ rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block) RBASIC(obj)->flags |= FL_FINALIZE; - int lev = rb_gc_vm_lock(); + int lev = RB_GC_VM_LOCK(); if (st_lookup(finalizer_table, obj, &data)) { table = (VALUE)data; @@ -2747,7 +2766,7 @@ rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block) for (i = 0; i < len; i++) { VALUE recv = RARRAY_AREF(table, i); if (rb_equal(recv, block)) { - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); return recv; } } @@ -2761,7 +2780,7 @@ rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block) st_add_direct(finalizer_table, obj, table); } - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); return block; } @@ -2774,7 +2793,11 @@ rb_gc_impl_undefine_finalizer(void *objspace_ptr, VALUE obj) GC_ASSERT(!OBJ_FROZEN(obj)); st_data_t data = obj; + + int lev = RB_GC_VM_LOCK(); st_delete(finalizer_table, &data, 0); + RB_GC_VM_UNLOCK(lev); + FL_UNSET(obj, FL_FINALIZE); } @@ -2787,14 +2810,17 @@ rb_gc_impl_copy_finalizer(void *objspace_ptr, VALUE dest, VALUE obj) if (!FL_TEST(obj, FL_FINALIZE)) return; + int lev = RB_GC_VM_LOCK(); if (RB_LIKELY(st_lookup(finalizer_table, obj, &data))) { - table = (VALUE)data; + table = rb_ary_dup((VALUE)data); + RARRAY_ASET(table, 0, rb_obj_id(dest)); st_insert(finalizer_table, dest, table); FL_SET(dest, FL_FINALIZE); } else { rb_bug("rb_gc_copy_finalizer: FL_FINALIZE set but not found in finalizer_table: %s", rb_obj_info(obj)); } + RB_GC_VM_UNLOCK(lev); } static VALUE @@ -2838,9 +2864,9 @@ finalize_list(rb_objspace_t *objspace, VALUE zombie) next_zombie = RZOMBIE(zombie)->next; page = GET_HEAP_PAGE(zombie); - run_final(objspace, zombie); + int lev = RB_GC_VM_LOCK(); - int lev = rb_gc_vm_lock(); + run_final(objspace, zombie); { GC_ASSERT(BUILTIN_TYPE(zombie) == T_ZOMBIE); GC_ASSERT(page->heap->final_slots_count > 0); @@ -2852,7 +2878,7 @@ finalize_list(rb_objspace_t *objspace, VALUE zombie) heap_page_add_freeobj(objspace, page, zombie); page->heap->total_freed_objects++; } - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); zombie = next_zombie; } @@ -3221,7 +3247,7 @@ read_barrier_handler(uintptr_t address) rb_bug("read_barrier_handler: segmentation fault at %p", (void *)address); } - int lev = rb_gc_vm_lock(); + int lev = RB_GC_VM_LOCK(); { unlock_page_body(objspace, page_body); @@ -3229,7 +3255,7 @@ read_barrier_handler(uintptr_t address) invalidate_moved_page(objspace, GET_HEAP_PAGE(address)); } - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); } #endif @@ -5154,7 +5180,7 @@ gc_verify_internal_consistency(void *objspace_ptr) { rb_objspace_t *objspace = objspace_ptr; - unsigned int lev = rb_gc_vm_lock(); + unsigned int lev = RB_GC_VM_LOCK(); { rb_gc_vm_barrier(); // stop other ractors @@ -5165,7 +5191,7 @@ gc_verify_internal_consistency(void *objspace_ptr) } during_gc = prev_during_gc; } - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); } static void @@ -5926,11 +5952,11 @@ gc_writebarrier_generational(VALUE a, VALUE b, rb_objspace_t *objspace) /* mark `a' and remember (default behavior) */ if (!RVALUE_REMEMBERED(objspace, a)) { - int lev = rb_gc_vm_lock_no_barrier(); + int lev = RB_GC_VM_LOCK_NO_BARRIER(); { rgengc_remember(objspace, a); } - rb_gc_vm_unlock_no_barrier(lev); + RB_GC_VM_UNLOCK_NO_BARRIER(lev); gc_report(1, objspace, "gc_writebarrier_generational: %s (remembered) -> %s\n", rb_obj_info(a), rb_obj_info(b)); } @@ -5980,9 +6006,10 @@ rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b) if (RGENGC_CHECK_MODE) { if (SPECIAL_CONST_P(a)) rb_bug("rb_gc_writebarrier: a is special const: %"PRIxVALUE, a); - if (SPECIAL_CONST_P(b)) rb_bug("rb_gc_writebarrier: b is special const: %"PRIxVALUE, b); } + if (SPECIAL_CONST_P(b)) return; + GC_ASSERT(RB_BUILTIN_TYPE(a) != T_NONE); GC_ASSERT(RB_BUILTIN_TYPE(a) != T_MOVED); GC_ASSERT(RB_BUILTIN_TYPE(a) != T_ZOMBIE); @@ -6002,7 +6029,7 @@ rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b) else { bool retry = false; /* slow path */ - int lev = rb_gc_vm_lock_no_barrier(); + int lev = RB_GC_VM_LOCK_NO_BARRIER(); { if (is_incremental_marking(objspace)) { gc_writebarrier_incremental(a, b, objspace); @@ -6011,7 +6038,7 @@ rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b) retry = true; } } - rb_gc_vm_unlock_no_barrier(lev); + RB_GC_VM_UNLOCK_NO_BARRIER(lev); if (retry) goto retry; } @@ -6030,7 +6057,7 @@ rb_gc_impl_writebarrier_unprotect(void *objspace_ptr, VALUE obj) gc_report(2, objspace, "rb_gc_writebarrier_unprotect: %s %s\n", rb_obj_info(obj), RVALUE_REMEMBERED(objspace, obj) ? " (already remembered)" : ""); - unsigned int lev = rb_gc_vm_lock_no_barrier(); + unsigned int lev = RB_GC_VM_LOCK_NO_BARRIER(); { if (RVALUE_OLD_P(objspace, obj)) { gc_report(1, objspace, "rb_gc_writebarrier_unprotect: %s\n", rb_obj_info(obj)); @@ -6052,7 +6079,7 @@ rb_gc_impl_writebarrier_unprotect(void *objspace_ptr, VALUE obj) RB_DEBUG_COUNTER_INC(obj_wb_unprotect); MARK_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(obj), obj); } - rb_gc_vm_unlock_no_barrier(lev); + RB_GC_VM_UNLOCK_NO_BARRIER(lev); } } @@ -6265,7 +6292,7 @@ garbage_collect(rb_objspace_t *objspace, unsigned int reason) { int ret; - int lev = rb_gc_vm_lock(); + int lev = RB_GC_VM_LOCK(); { #if GC_PROFILE_MORE_DETAIL objspace->profile.prepare_time = getrusage_time(); @@ -6279,7 +6306,7 @@ garbage_collect(rb_objspace_t *objspace, unsigned int reason) ret = gc_start(objspace, reason); } - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); return ret; } @@ -6295,7 +6322,7 @@ gc_start(rb_objspace_t *objspace, unsigned int reason) if (!rb_darray_size(objspace->heap_pages.sorted)) return TRUE; /* heap is not ready */ if (!(reason & GPR_FLAG_METHOD) && !ready_to_gc(objspace)) return TRUE; /* GC is not allowed */ - GC_ASSERT(gc_mode(objspace) == gc_mode_none); + GC_ASSERT(gc_mode(objspace) == gc_mode_none, "gc_mode is %s\n", gc_mode_name(gc_mode(objspace))); GC_ASSERT(!is_lazy_sweeping(objspace)); GC_ASSERT(!is_incremental_marking(objspace)); @@ -6563,7 +6590,7 @@ gc_clock_end(struct timespec *ts) static inline void gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_lev) { - *lock_lev = rb_gc_vm_lock(); + *lock_lev = RB_GC_VM_LOCK(); switch (event) { case gc_enter_event_rest: @@ -6602,7 +6629,7 @@ gc_exit(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_l gc_report(1, objspace, "gc_exit: %s [%s]\n", gc_enter_event_cstr(event), gc_current_status(objspace)); during_gc = FALSE; - rb_gc_vm_unlock(*lock_lev); + RB_GC_VM_UNLOCK(*lock_lev); } #ifndef MEASURE_GC @@ -7041,6 +7068,8 @@ gc_update_references(rb_objspace_t *objspace) { objspace->flags.during_reference_updating = true; + rb_gc_before_updating_jit_code(); + struct heap_page *page = NULL; for (int i = 0; i < HEAP_COUNT; i++) { @@ -7075,6 +7104,8 @@ gc_update_references(rb_objspace_t *objspace) ); } + rb_gc_after_updating_jit_code(); + objspace->flags.during_reference_updating = false; } @@ -9079,7 +9110,7 @@ gc_verify_compaction_references(int argc, VALUE* argv, VALUE self) /* Clear the heap. */ rb_gc_impl_start(objspace, true, true, true, false); - unsigned int lev = rb_gc_vm_lock(); + unsigned int lev = RB_GC_VM_LOCK(); { gc_rest(objspace); @@ -9135,7 +9166,7 @@ gc_verify_compaction_references(int argc, VALUE* argv, VALUE self) objspace->rcompactor.compare_func = compare_free_slots; } } - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); rb_gc_impl_start(rb_gc_get_objspace(), true, true, true, true); @@ -9338,6 +9369,7 @@ rb_gc_impl_init(void) VALUE gc_constants = rb_hash_new(); rb_hash_aset(gc_constants, ID2SYM(rb_intern("DEBUG")), GC_DEBUG ? Qtrue : Qfalse); rb_hash_aset(gc_constants, ID2SYM(rb_intern("BASE_SLOT_SIZE")), SIZET2NUM(BASE_SLOT_SIZE - RVALUE_OVERHEAD)); + rb_hash_aset(gc_constants, ID2SYM(rb_intern("RBASIC_SIZE")), SIZET2NUM(sizeof(struct RBasic))); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), SIZET2NUM(RVALUE_OVERHEAD)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_PAGE_OBJ_LIMIT")), SIZET2NUM(HEAP_PAGE_OBJ_LIMIT)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_PAGE_BITMAP_SIZE")), SIZET2NUM(HEAP_PAGE_BITMAP_SIZE)); diff --git a/gc/gc.h b/gc/gc.h index 5adcadb305..fe9aaeb965 100644 --- a/gc/gc.h +++ b/gc/gc.h @@ -31,56 +31,83 @@ enum rb_gc_vm_weak_tables { RB_GC_VM_ID2REF_TABLE, RB_GC_VM_GENERIC_FIELDS_TABLE, RB_GC_VM_FROZEN_STRINGS_TABLE, + RB_GC_VM_CC_REFINEMENT_TABLE, RB_GC_VM_WEAK_TABLE_COUNT }; +#define RB_GC_VM_LOCK() rb_gc_vm_lock(__FILE__, __LINE__) +#define RB_GC_VM_UNLOCK(lev) rb_gc_vm_unlock(lev, __FILE__, __LINE__) +#define RB_GC_CR_LOCK() rb_gc_cr_lock(__FILE__, __LINE__) +#define RB_GC_CR_UNLOCK(lev) rb_gc_cr_unlock(lev, __FILE__, __LINE__) +#define RB_GC_VM_LOCK_NO_BARRIER() rb_gc_vm_lock_no_barrier(__FILE__, __LINE__) +#define RB_GC_VM_UNLOCK_NO_BARRIER(lev) rb_gc_vm_unlock_no_barrier(lev, __FILE__, __LINE__) + +#if USE_MODULAR_GC +# define MODULAR_GC_FN +#else +// This takes advantage of internal linkage winning when appearing first. +// See C99 6.2.2p4. +# define MODULAR_GC_FN static +#endif + +#if USE_MODULAR_GC RUBY_SYMBOL_EXPORT_BEGIN -unsigned int rb_gc_vm_lock(void); -void rb_gc_vm_unlock(unsigned int lev); -unsigned int rb_gc_cr_lock(void); -void rb_gc_cr_unlock(unsigned int lev); -unsigned int rb_gc_vm_lock_no_barrier(void); -void rb_gc_vm_unlock_no_barrier(unsigned int lev); -void rb_gc_vm_barrier(void); -size_t rb_gc_obj_optimal_size(VALUE obj); -void rb_gc_mark_children(void *objspace, VALUE obj); -void rb_gc_vm_weak_table_foreach(vm_table_foreach_callback_func callback, vm_table_update_callback_func update_callback, void *data, bool weak_only, enum rb_gc_vm_weak_tables table); -void rb_gc_update_object_references(void *objspace, VALUE obj); -void rb_gc_update_vm_references(void *objspace); -void rb_gc_event_hook(VALUE obj, rb_event_flag_t event); -void *rb_gc_get_objspace(void); +#endif + +// These functions cannot be defined as static because they are used by other +// files in Ruby. size_t rb_size_mul_or_raise(size_t x, size_t y, VALUE exc); -void rb_gc_run_obj_finalizer(VALUE objid, long count, VALUE (*callback)(long i, void *data), void *data); -void rb_gc_set_pending_interrupt(void); -void rb_gc_unset_pending_interrupt(void); -void rb_gc_obj_free_vm_weak_references(VALUE obj); -bool rb_gc_obj_free(void *objspace, VALUE obj); -void rb_gc_save_machine_context(void); -void rb_gc_mark_roots(void *objspace, const char **categoryp); -void rb_gc_ractor_newobj_cache_foreach(void (*func)(void *cache, void *data), void *data); -bool rb_gc_multi_ractor_p(void); -void rb_objspace_reachable_objects_from_root(void (func)(const char *category, VALUE, void *), void *passing_data); void rb_objspace_reachable_objects_from(VALUE obj, void (func)(VALUE, void *), void *data); void rb_obj_info_dump(VALUE obj); const char *rb_obj_info(VALUE obj); -bool rb_gc_shutdown_call_finalizer_p(VALUE obj); -uint32_t rb_gc_get_shape(VALUE obj); -void rb_gc_set_shape(VALUE obj, uint32_t shape_id); -uint32_t rb_gc_rebuild_shape(VALUE obj, size_t heap_id); size_t rb_obj_memsize_of(VALUE obj); -void rb_gc_prepare_heap_process_object(VALUE obj); bool ruby_free_at_exit_p(void); -bool rb_memerror_reentered(void); -bool rb_obj_id_p(VALUE); +void rb_objspace_reachable_objects_from_root(void (func)(const char *category, VALUE, void *), void *passing_data); + +MODULAR_GC_FN unsigned int rb_gc_vm_lock(const char *file, int line); +MODULAR_GC_FN void rb_gc_vm_unlock(unsigned int lev, const char *file, int line); +MODULAR_GC_FN unsigned int rb_gc_cr_lock(const char *file, int line); +MODULAR_GC_FN void rb_gc_cr_unlock(unsigned int lev, const char *file, int line); +MODULAR_GC_FN unsigned int rb_gc_vm_lock_no_barrier(const char *file, int line); +MODULAR_GC_FN void rb_gc_vm_unlock_no_barrier(unsigned int lev, const char *file, int line); +MODULAR_GC_FN void rb_gc_vm_barrier(void); +MODULAR_GC_FN size_t rb_gc_obj_optimal_size(VALUE obj); +MODULAR_GC_FN void rb_gc_mark_children(void *objspace, VALUE obj); +MODULAR_GC_FN void rb_gc_vm_weak_table_foreach(vm_table_foreach_callback_func callback, vm_table_update_callback_func update_callback, void *data, bool weak_only, enum rb_gc_vm_weak_tables table); +MODULAR_GC_FN void rb_gc_update_object_references(void *objspace, VALUE obj); +MODULAR_GC_FN void rb_gc_update_vm_references(void *objspace); +MODULAR_GC_FN void rb_gc_event_hook(VALUE obj, rb_event_flag_t event); +MODULAR_GC_FN void *rb_gc_get_objspace(void); +MODULAR_GC_FN void rb_gc_run_obj_finalizer(VALUE objid, long count, VALUE (*callback)(long i, void *data), void *data); +MODULAR_GC_FN void rb_gc_set_pending_interrupt(void); +MODULAR_GC_FN void rb_gc_unset_pending_interrupt(void); +MODULAR_GC_FN void rb_gc_obj_free_vm_weak_references(VALUE obj); +MODULAR_GC_FN bool rb_gc_obj_free(void *objspace, VALUE obj); +MODULAR_GC_FN void rb_gc_save_machine_context(void); +MODULAR_GC_FN void rb_gc_mark_roots(void *objspace, const char **categoryp); +MODULAR_GC_FN void rb_gc_ractor_newobj_cache_foreach(void (*func)(void *cache, void *data), void *data); +MODULAR_GC_FN bool rb_gc_multi_ractor_p(void); +MODULAR_GC_FN bool rb_gc_shutdown_call_finalizer_p(VALUE obj); +MODULAR_GC_FN uint32_t rb_gc_get_shape(VALUE obj); +MODULAR_GC_FN void rb_gc_set_shape(VALUE obj, uint32_t shape_id); +MODULAR_GC_FN uint32_t rb_gc_rebuild_shape(VALUE obj, size_t heap_id); +MODULAR_GC_FN void rb_gc_prepare_heap_process_object(VALUE obj); +MODULAR_GC_FN bool rb_memerror_reentered(void); +MODULAR_GC_FN bool rb_obj_id_p(VALUE); +MODULAR_GC_FN void rb_gc_before_updating_jit_code(void); +MODULAR_GC_FN void rb_gc_after_updating_jit_code(void); #if USE_MODULAR_GC -bool rb_gc_event_hook_required_p(rb_event_flag_t event); -void *rb_gc_get_ractor_newobj_cache(void); -void rb_gc_initialize_vm_context(struct rb_gc_vm_context *context); -void rb_gc_worker_thread_set_vm_context(struct rb_gc_vm_context *context); -void rb_gc_worker_thread_unset_vm_context(struct rb_gc_vm_context *context); +MODULAR_GC_FN bool rb_gc_event_hook_required_p(rb_event_flag_t event); +MODULAR_GC_FN void *rb_gc_get_ractor_newobj_cache(void); +MODULAR_GC_FN void rb_gc_initialize_vm_context(struct rb_gc_vm_context *context); +MODULAR_GC_FN void rb_gc_worker_thread_set_vm_context(struct rb_gc_vm_context *context); +MODULAR_GC_FN void rb_gc_worker_thread_unset_vm_context(struct rb_gc_vm_context *context); #endif + +#if USE_MODULAR_GC RUBY_SYMBOL_EXPORT_END +#endif void rb_ractor_finish_marking(void); @@ -105,7 +132,7 @@ RBIMPL_WARNING_IGNORED(-Wunused-function) #endif #ifndef GC_ASSERT -# define GC_ASSERT(expr) RUBY_ASSERT_MESG_WHEN(RGENGC_CHECK_MODE > 0, expr, #expr) +# define GC_ASSERT(expr, ...) RUBY_ASSERT_MESG_WHEN(RGENGC_CHECK_MODE > 0, expr, #expr RBIMPL_VA_OPT_ARGS(__VA_ARGS__)) #endif static int diff --git a/gc/mmtk/Cargo.lock b/gc/mmtk/Cargo.lock index 629cac3fec..f7d62ddacb 100644 --- a/gc/mmtk/Cargo.lock +++ b/gc/mmtk/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -28,35 +28,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" dependencies = [ "anstyle", + "once_cell_polyfill", "windows-sys", ] @@ -87,54 +88,54 @@ checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "built" -version = "0.7.3" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a6c0b39c38fd754ac338b00a88066436389c0f029da5d37d1e01091d9b7c17" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" dependencies = [ "git2", ] [[package]] name = "bytemuck" -version = "1.16.1" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" +checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" +checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.101", ] [[package]] name = "cc" -version = "1.0.100" +version = "1.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c891175c3fb232128f48de6590095e59198bbeb8620c310be349bfc3afd12c7b" +checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" dependencies = [ "jobserver", "libc", - "once_cell", + "shlex", ] [[package]] @@ -145,15 +146,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crossbeam" @@ -179,9 +180,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -198,41 +199,41 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "delegate" -version = "0.12.0" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e018fccbeeb50ff26562ece792ed06659b9c2dae79ece77c4456bb10d9bf79b" +checksum = "b9b6483c2bbed26f97861cf57651d4f2b731964a28cd2257f934a4b452480d21" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.101", ] [[package]] name = "downcast-rs" -version = "1.2.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +checksum = "ea8a8b81cacc08888170eef4d13b775126db426d0b348bee9d18c2c1eaf123cf" [[package]] name = "either" -version = "1.12.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "enum-map" @@ -251,14 +252,14 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.101", ] [[package]] name = "env_filter" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", @@ -266,14 +267,14 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.3" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff", "log", ] @@ -287,10 +288,22 @@ dependencies = [ ] [[package]] -name = "git2" -version = "0.18.3" +name = "getrandom" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi", +] + +[[package]] +name = "git2" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110" dependencies = [ "bitflags", "libc", @@ -312,53 +325,99 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] -name = "humantime" -version = "2.1.0" +name = "hermit-abi" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279259b0ac81c89d11c290495fdcfa96ea3643b7df311c138b6fe8ca5237f0f8" +dependencies = [ + "idna_mapping", "unicode-bidi", "unicode-normalization", ] [[package]] -name = "is-terminal" -version = "0.4.12" +name = "idna_mapping" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "11c13906586a4b339310541a274dd927aff6fcbb5b8e3af90634c4b31681c792" dependencies = [ - "hermit-abi", + "unicode-joining-type", +] + +[[package]] +name = "is-terminal" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +dependencies = [ + "hermit-abi 0.5.1", "libc", "windows-sys", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" -version = "0.12.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] -name = "jobserver" -version = "0.1.31" +name = "jiff" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93" dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom", "libc", ] @@ -370,15 +429,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libgit2-sys" -version = "0.16.2+1.7.2" +version = "0.18.1+1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" +checksum = "e1dcb20f84ffcdd825c7a311ae347cce604a6f084a767dec4a4929829645290e" dependencies = [ "cc", "libc", @@ -388,9 +447,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.18" +version = "1.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e" +checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" dependencies = [ "cc", "libc", @@ -410,9 +469,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" @@ -431,20 +490,22 @@ dependencies = [ [[package]] name = "mmtk" -version = "0.30.0" -source = "git+https://github.com/mmtk/mmtk-core.git?rev=051bc7470feef915c445305301e6113f86d3957b#051bc7470feef915c445305301e6113f86d3957b" +version = "0.31.0" +source = "git+https://github.com/mmtk/mmtk-core.git?rev=3d89bb51c191d3077278684ec5059726128d3e2b#3d89bb51c191d3077278684ec5059726128d3e2b" dependencies = [ "atomic", "atomic-traits", "atomic_refcell", "built", "bytemuck", + "bytemuck_derive", "cfg-if", "crossbeam", "delegate", "downcast-rs", "enum-map", "env_logger", + "idna_adapter", "is-terminal", "itertools", "lazy_static", @@ -462,18 +523,18 @@ dependencies = [ "static_assertions", "strum", "strum_macros", - "sysinfo 0.30.12", + "sysinfo 0.33.1", ] [[package]] name = "mmtk-macros" -version = "0.30.0" -source = "git+https://github.com/mmtk/mmtk-core.git?rev=051bc7470feef915c445305301e6113f86d3957b#051bc7470feef915c445305301e6113f86d3957b" +version = "0.31.0" +source = "git+https://github.com/mmtk/mmtk-core.git?rev=3d89bb51c191d3077278684ec5059726128d3e2b#3d89bb51c191d3077278684ec5059726128d3e2b" dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.101", ] [[package]] @@ -487,7 +548,7 @@ dependencies = [ "mmtk", "once_cell", "probe", - "sysinfo 0.32.0", + "sysinfo 0.32.1", ] [[package]] @@ -514,15 +575,21 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "percent-encoding" @@ -532,15 +599,24 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.6.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] [[package]] name = "probe" @@ -574,22 +650,28 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rayon" version = "1.10.0" @@ -612,9 +694,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -624,9 +706,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -635,24 +717,24 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "scopeguard" @@ -662,9 +744,41 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "spin" @@ -683,21 +797,21 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strum" -version = "0.26.3" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" [[package]] name = "strum_macros" -version = "0.26.4" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 2.0.68", + "syn 2.0.101", ] [[package]] @@ -712,9 +826,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.68" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -723,38 +837,37 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.30.12" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732ffa00f53e6b2af46208fba5718d9662a421049204e156328b66791ffa15ae" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "ntapi", - "once_cell", - "rayon", - "windows 0.52.0", -] - -[[package]] -name = "sysinfo" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ae3f4f7d64646c46c4cae4e3f01d1c5d255c7406fdd7c7f999a94e488791" +checksum = "4c33cd241af0f2e9e3b5c32163b873b29956890b5342e6745b917ce9d490f4af" dependencies = [ "core-foundation-sys", "libc", "memchr", "ntapi", "rayon", - "windows 0.57.0", + "windows", +] + +[[package]] +name = "sysinfo" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows", ] [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -767,36 +880,48 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-joining-type" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d00a78170970967fdb83f9d49b92f959ab2bb829186b113e4f4604ad98e180" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -811,9 +936,18 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] [[package]] name = "winapi" @@ -837,32 +971,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core 0.52.0", - "windows-targets", -] - [[package]] name = "windows" version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" dependencies = [ - "windows-core 0.57.0", - "windows-targets", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ + "windows-core", "windows-targets", ] @@ -886,7 +1001,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.101", ] [[package]] @@ -897,7 +1012,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.101", ] [[package]] @@ -911,18 +1026,18 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -936,48 +1051,57 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] diff --git a/gc/mmtk/Cargo.toml b/gc/mmtk/Cargo.toml index 66af77fe61..e1b1d1e13b 100644 --- a/gc/mmtk/Cargo.toml +++ b/gc/mmtk/Cargo.toml @@ -25,7 +25,7 @@ features = ["is_mmtk_object", "object_pinning", "sticky_immix_non_moving_nursery # Uncomment the following lines to use mmtk-core from the official repository. git = "https://github.com/mmtk/mmtk-core.git" -rev = "051bc7470feef915c445305301e6113f86d3957b" +rev = "3d89bb51c191d3077278684ec5059726128d3e2b" # Uncomment the following line to use mmtk-core from a local repository. # path = "../../../mmtk-core" @@ -35,3 +35,8 @@ default = [] # When moving an object, clear its original copy. clear_old_copy = [] + +# Enable extra assertions in release build. For debugging. +extra_assert = [] + +[workspace] diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 59bef826bf..c318c6fe48 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -59,7 +59,7 @@ struct MMTk_final_job { void *data; } dfree; struct { - VALUE object_id; + /* HACK: we store the object ID on the 0th element of this array. */ VALUE finalizer_array; } finalize; } as; @@ -129,7 +129,7 @@ rb_mmtk_block_for_gc(MMTk_VMMutatorThread mutator) struct objspace *objspace = rb_gc_get_objspace(); size_t starting_gc_count = objspace->gc_count; - int lock_lev = rb_gc_vm_lock(); + int lock_lev = RB_GC_VM_LOCK(); int err; if ((err = pthread_mutex_lock(&objspace->mutex)) != 0) { rb_bug("ERROR: cannot lock objspace->mutex: %s", strerror(err)); @@ -173,7 +173,7 @@ rb_mmtk_block_for_gc(MMTk_VMMutatorThread mutator) if ((err = pthread_mutex_unlock(&objspace->mutex)) != 0) { rb_bug("ERROR: cannot release objspace->mutex: %s", strerror(err)); } - rb_gc_vm_unlock(lock_lev); + RB_GC_VM_UNLOCK(lock_lev); } static size_t @@ -229,7 +229,6 @@ rb_mmtk_scan_objspace(void) case MMTK_FINAL_JOB_DFREE: break; case MMTK_FINAL_JOB_FINALIZE: - rb_gc_impl_mark(objspace, job->as.finalize.object_id); rb_gc_impl_mark(objspace, job->as.finalize.finalizer_array); break; default: @@ -285,7 +284,6 @@ make_final_job(struct objspace *objspace, VALUE obj, VALUE table) struct MMTk_final_job *job = xmalloc(sizeof(struct MMTk_final_job)); job->next = objspace->finalizer_jobs; job->kind = MMTK_FINAL_JOB_FINALIZE; - job->as.finalize.object_id = rb_obj_id((VALUE)obj); job->as.finalize.finalizer_array = table; objspace->finalizer_jobs = job; @@ -455,6 +453,7 @@ rb_gc_impl_init(void) { VALUE gc_constants = rb_hash_new(); rb_hash_aset(gc_constants, ID2SYM(rb_intern("BASE_SLOT_SIZE")), SIZET2NUM(sizeof(VALUE) * 5)); + rb_hash_aset(gc_constants, ID2SYM(rb_intern("RBASIC_SIZE")), SIZET2NUM(sizeof(struct RBasic))); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), INT2NUM(0)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), LONG2FIX(640)); // Pretend we have 5 size pools @@ -751,6 +750,8 @@ rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b) { struct MMTk_ractor_cache *cache = rb_gc_get_ractor_newobj_cache(); + if (SPECIAL_CONST_P(b)) return; + mmtk_object_reference_write_post(cache->mutator, (MMTk_ObjectReference)a); } @@ -855,7 +856,7 @@ gc_run_finalizers_get_final(long i, void *data) { VALUE table = (VALUE)data; - return RARRAY_AREF(table, i); + return RARRAY_AREF(table, i + 1); } static void @@ -874,17 +875,15 @@ gc_run_finalizers(void *data) job->as.dfree.func(job->as.dfree.data); break; case MMTK_FINAL_JOB_FINALIZE: { - VALUE object_id = job->as.finalize.object_id; VALUE finalizer_array = job->as.finalize.finalizer_array; rb_gc_run_obj_finalizer( - job->as.finalize.object_id, - RARRAY_LEN(finalizer_array), + RARRAY_AREF(finalizer_array, 0), + RARRAY_LEN(finalizer_array) - 1, gc_run_finalizers_get_final, (void *)finalizer_array ); - RB_GC_GUARD(object_id); RB_GC_GUARD(finalizer_array); break; } @@ -928,7 +927,7 @@ rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block) RBASIC(obj)->flags |= FL_FINALIZE; - int lev = rb_gc_vm_lock(); + int lev = RB_GC_VM_LOCK(); if (st_lookup(objspace->finalizer_table, obj, &data)) { table = (VALUE)data; @@ -941,7 +940,7 @@ rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block) for (i = 0; i < len; i++) { VALUE recv = RARRAY_AREF(table, i); if (rb_equal(recv, block)) { - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); return recv; } } @@ -950,12 +949,12 @@ rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block) rb_ary_push(table, block); } else { - table = rb_ary_new3(1, block); + table = rb_ary_new3(2, rb_obj_id(obj), block); rb_obj_hide(table); st_add_direct(objspace->finalizer_table, obj, table); } - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); return block; } @@ -966,7 +965,11 @@ rb_gc_impl_undefine_finalizer(void *objspace_ptr, VALUE obj) struct objspace *objspace = objspace_ptr; st_data_t data = obj; + + int lev = RB_GC_VM_LOCK(); st_delete(objspace->finalizer_table, &data, 0); + RB_GC_VM_UNLOCK(lev); + FL_UNSET(obj, FL_FINALIZE); } @@ -979,14 +982,17 @@ rb_gc_impl_copy_finalizer(void *objspace_ptr, VALUE dest, VALUE obj) if (!FL_TEST(obj, FL_FINALIZE)) return; + int lev = RB_GC_VM_LOCK(); if (RB_LIKELY(st_lookup(objspace->finalizer_table, obj, &data))) { - table = (VALUE)data; + table = rb_ary_dup((VALUE)data); + RARRAY_ASET(table, 0, rb_obj_id(dest)); st_insert(objspace->finalizer_table, dest, table); FL_SET(dest, FL_FINALIZE); } else { rb_bug("rb_gc_copy_finalizer: FL_FINALIZE set but not found in finalizer_table: %s", rb_obj_info(obj)); } + RB_GC_VM_UNLOCK(lev); } static int diff --git a/gc/mmtk/src/abi.rs b/gc/mmtk/src/abi.rs index c7a337ef35..81e24679f0 100644 --- a/gc/mmtk/src/abi.rs +++ b/gc/mmtk/src/abi.rs @@ -1,5 +1,5 @@ use crate::api::RubyMutator; -use crate::Ruby; +use crate::{extra_assert, Ruby}; use libc::c_int; use mmtk::scheduler::GCWorker; use mmtk::util::{Address, ObjectReference, VMMutatorThread, VMWorkerThread}; @@ -10,16 +10,38 @@ pub const MIN_OBJ_ALIGN: usize = 8; // Even on 32-bit machine. A Ruby object is pub const GC_THREAD_KIND_WORKER: libc::c_int = 1; -const HAS_MOVED_GFIELDSTBL: usize = 1 << 63; const HIDDEN_SIZE_MASK: usize = 0x0000FFFFFFFFFFFF; -// Should keep in sync with C code. -const RUBY_FL_EXIVAR: usize = 1 << 10; - // An opaque type for the C counterpart. #[allow(non_camel_case_types)] pub struct st_table; +#[repr(C)] +pub struct HiddenHeader { + pub prefix: usize, +} + +impl HiddenHeader { + #[inline(always)] + pub fn is_sane(&self) -> bool { + self.prefix & !HIDDEN_SIZE_MASK == 0 + } + + #[inline(always)] + fn assert_sane(&self) { + extra_assert!( + self.is_sane(), + "Hidden header is corrupted: {:x}", + self.prefix + ); + } + + pub fn payload_size(&self) -> usize { + self.assert_sane(); + self.prefix & HIDDEN_SIZE_MASK + } +} + /// Provide convenient methods for accessing Ruby objects. /// TODO: Wrap C functions in `RubyUpcalls` as Rust-friendly methods. pub struct RubyObjectAccess { @@ -47,32 +69,17 @@ impl RubyObjectAccess { self.suffix_addr() + Self::suffix_size() } - fn hidden_field(&self) -> Address { - self.obj_start() + fn hidden_header(&self) -> &'static HiddenHeader { + unsafe { self.obj_start().as_ref() } } - fn load_hidden_field(&self) -> usize { - unsafe { self.hidden_field().load::() } - } - - fn update_hidden_field(&self, f: F) - where - F: FnOnce(usize) -> usize, - { - let old_value = self.load_hidden_field(); - let new_value = f(old_value); - unsafe { - self.hidden_field().store(new_value); - } + #[allow(unused)] // Maybe we need to mutate the hidden header in the future. + fn hidden_header_mut(&self) -> &'static mut HiddenHeader { + unsafe { self.obj_start().as_mut_ref() } } pub fn payload_size(&self) -> usize { - self.load_hidden_field() & HIDDEN_SIZE_MASK - } - - pub fn set_payload_size(&self, size: usize) { - debug_assert!((size & HIDDEN_SIZE_MASK) == size); - self.update_hidden_field(|old| old & !HIDDEN_SIZE_MASK | size & HIDDEN_SIZE_MASK); + self.hidden_header().payload_size() } fn flags_field(&self) -> Address { @@ -83,22 +90,6 @@ impl RubyObjectAccess { unsafe { self.flags_field().load::() } } - pub fn has_exivar_flag(&self) -> bool { - (self.load_flags() & RUBY_FL_EXIVAR) != 0 - } - - pub fn has_moved_gfields_tbl(&self) -> bool { - (self.load_hidden_field() & HAS_MOVED_GFIELDSTBL) != 0 - } - - pub fn set_has_moved_gfields_tbl(&self) { - self.update_hidden_field(|old| old | HAS_MOVED_GFIELDSTBL) - } - - pub fn clear_has_moved_gfields_tbl(&self) { - self.update_hidden_field(|old| old & !HAS_MOVED_GFIELDSTBL) - } - pub fn prefix_size() -> usize { // Currently, a hidden size field of word size is placed before each object. OBJREF_OFFSET @@ -232,7 +223,7 @@ impl GCThreadTLS { /// Has undefined behavior if `ptr` is invalid. pub unsafe fn check_cast(ptr: *mut GCThreadTLS) -> &'static mut GCThreadTLS { assert!(!ptr.is_null()); - let result = &mut *ptr; + let result = unsafe { &mut *ptr }; debug_assert!({ let kind = result.kind; kind == GC_THREAD_KIND_WORKER @@ -247,7 +238,7 @@ impl GCThreadTLS { /// Has undefined behavior if `ptr` is invalid. pub unsafe fn from_vwt_check(vwt: VMWorkerThread) -> &'static mut GCThreadTLS { let ptr = Self::from_vwt(vwt); - Self::check_cast(ptr) + unsafe { Self::check_cast(ptr) } } #[allow(clippy::not_unsafe_ptr_arg_deref)] // `transmute` does not dereference pointer @@ -283,7 +274,7 @@ impl RawVecOfObjRef { /// /// This function turns raw pointer into a Vec without check. pub unsafe fn into_vec(self) -> Vec { - Vec::from_raw_parts(self.ptr, self.len, self.capa) + unsafe { Vec::from_raw_parts(self.ptr, self.len, self.capa) } } } diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index c15996727e..a1b94d520d 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -1,5 +1,10 @@ -use std::sync::atomic::Ordering; +// Functions in this module are unsafe for one reason: +// They are called by C functions and they need to pass raw pointers to Rust. +#![allow(clippy::missing_safety_doc)] + use mmtk::util::options::PlanSelector; +use std::str::FromStr; +use std::sync::atomic::Ordering; use crate::abi::RawVecOfObjRef; use crate::abi::RubyBindingOptions; @@ -7,10 +12,10 @@ use crate::abi::RubyUpcalls; use crate::binding; use crate::binding::RubyBinding; use crate::mmtk; -use crate::Ruby; -use crate::RubySlot; use crate::utils::default_heap_max; use crate::utils::parse_capacity; +use crate::Ruby; +use crate::RubySlot; use mmtk::memory_manager; use mmtk::memory_manager::mmtk_init; use mmtk::util::constants::MIN_OBJECT_SIZE; @@ -37,73 +42,61 @@ pub extern "C" fn mmtk_is_reachable(object: ObjectReference) -> bool { // =============== Bootup =============== -fn mmtk_builder_default_parse_threads() -> usize { - let threads_str = std::env::var("MMTK_THREADS") - .unwrap_or("0".to_string()); - - threads_str - .parse::() - .unwrap_or_else(|_err| { - eprintln!("[FATAL] Invalid MMTK_THREADS {}", threads_str); +fn parse_env_var_with Option>(key: &str, parse: F) -> Option { + let val = match std::env::var(key) { + Ok(val) => val, + Err(std::env::VarError::NotPresent) => return None, + Err(std::env::VarError::NotUnicode(os_string)) => { + eprintln!("[FATAL] Invalid {key} {os_string:?}"); std::process::exit(1); - }) + } + }; + + let parsed = parse(&val).unwrap_or_else(|| { + eprintln!("[FATAL] Invalid {key} {val}"); + std::process::exit(1); + }); + + Some(parsed) +} + +fn parse_env_var(key: &str) -> Option { + parse_env_var_with(key, |s| s.parse().ok()) +} + +fn mmtk_builder_default_parse_threads() -> Option { + parse_env_var("MMTK_THREADS") } fn mmtk_builder_default_parse_heap_min() -> usize { const DEFAULT_HEAP_MIN: usize = 1 << 20; - - let heap_min_str = std::env::var("MMTK_HEAP_MIN") - .unwrap_or(DEFAULT_HEAP_MIN.to_string()); - - let size = parse_capacity(&heap_min_str, 0); - if size == 0 { - eprintln!("[FATAL] Invalid MMTK_HEAP_MIN {}", heap_min_str); - std::process::exit(1); - } - - size + parse_env_var_with("MMTK_HEAP_MIN", parse_capacity).unwrap_or(DEFAULT_HEAP_MIN) } fn mmtk_builder_default_parse_heap_max() -> usize { - let heap_max_str = std::env::var("MMTK_HEAP_MAX") - .unwrap_or(default_heap_max().to_string()); - - let size = parse_capacity(&heap_max_str, 0); - if size == 0 { - eprintln!("[FATAL] Invalid MMTK_HEAP_MAX {}", heap_max_str); - std::process::exit(1); - } - - size + parse_env_var_with("MMTK_HEAP_MAX", parse_capacity).unwrap_or_else(default_heap_max) } fn mmtk_builder_default_parse_heap_mode(heap_min: usize, heap_max: usize) -> GCTriggerSelector { - let heap_mode_str = std::env::var("MMTK_HEAP_MODE") - .unwrap_or("dynamic".to_string()); + let make_fixed = || GCTriggerSelector::FixedHeapSize(heap_max); + let make_dynamic = || GCTriggerSelector::DynamicHeapSize(heap_min, heap_max); - match heap_mode_str.as_str() { - "fixed" => GCTriggerSelector::FixedHeapSize(heap_max), - "dynamic" => GCTriggerSelector::DynamicHeapSize(heap_min, heap_max), - _ => { - eprintln!("[FATAL] Invalid MMTK_HEAP_MODE {}", heap_mode_str); - std::process::exit(1); - } - } + parse_env_var_with("MMTK_HEAP_MODE", |s| match s { + "fixed" => Some(make_fixed()), + "dynamic" => Some(make_dynamic()), + _ => None, + }) + .unwrap_or_else(make_dynamic) } fn mmtk_builder_default_parse_plan() -> PlanSelector { - let plan_str = std::env::var("MMTK_PLAN") - .unwrap_or("Immix".to_string()); - - match plan_str.as_str() { - "NoGC" => PlanSelector::NoGC, - "MarkSweep" => PlanSelector::MarkSweep, - "Immix" => PlanSelector::Immix, - _ => { - eprintln!("[FATAL] Invalid MMTK_PLAN {}", plan_str); - std::process::exit(1); - } - } + parse_env_var_with("MMTK_PLAN", |s| match s { + "NoGC" => Some(PlanSelector::NoGC), + "MarkSweep" => Some(PlanSelector::MarkSweep), + "Immix" => Some(PlanSelector::Immix), + _ => None, + }) + .unwrap_or(PlanSelector::Immix) } #[no_mangle] @@ -111,9 +104,15 @@ pub extern "C" fn mmtk_builder_default() -> *mut MMTKBuilder { let mut builder = MMTKBuilder::new_no_env_vars(); builder.options.no_finalizer.set(true); - let threads = mmtk_builder_default_parse_threads(); - if threads > 0 { - builder.options.threads.set(threads); + if let Some(threads) = mmtk_builder_default_parse_threads() { + if !builder.options.threads.set(threads) { + // MMTk will validate it and reject 0. + eprintln!( + "[FATAL] Failed to set the number of MMTk threads to {}", + threads + ); + std::process::exit(1); + } } let heap_min = mmtk_builder_default_parse_heap_min(); @@ -121,11 +120,17 @@ pub extern "C" fn mmtk_builder_default() -> *mut MMTKBuilder { let heap_max = mmtk_builder_default_parse_heap_max(); if heap_min >= heap_max { - eprintln!("[FATAL] MMTK_HEAP_MIN({}) >= MMTK_HEAP_MAX({})", heap_min, heap_max); + eprintln!( + "[FATAL] MMTK_HEAP_MIN({}) >= MMTK_HEAP_MAX({})", + heap_min, heap_max + ); std::process::exit(1); } - builder.options.gc_trigger.set(mmtk_builder_default_parse_heap_mode(heap_min, heap_max)); + builder + .options + .gc_trigger + .set(mmtk_builder_default_parse_heap_mode(heap_min, heap_max)); builder.options.plan.set(mmtk_builder_default_parse_plan()); @@ -133,7 +138,7 @@ pub extern "C" fn mmtk_builder_default() -> *mut MMTKBuilder { } #[no_mangle] -pub extern "C" fn mmtk_init_binding( +pub unsafe extern "C" fn mmtk_init_binding( builder: *mut MMTKBuilder, _binding_options: *const RubyBindingOptions, upcalls: *const RubyUpcalls, @@ -142,11 +147,19 @@ pub extern "C" fn mmtk_init_binding( crate::set_panic_hook(); let builder = unsafe { Box::from_raw(builder) }; - let binding_options = RubyBindingOptions {ractor_check_mode: false, suffix_size: 0}; + let binding_options = RubyBindingOptions { + ractor_check_mode: false, + suffix_size: 0, + }; let mmtk_boxed = mmtk_init(&builder); let mmtk_static = Box::leak(Box::new(mmtk_boxed)); - let binding = RubyBinding::new(mmtk_static, &binding_options, upcalls, weak_reference_dead_value); + let binding = RubyBinding::new( + mmtk_static, + &binding_options, + upcalls, + weak_reference_dead_value, + ); crate::BINDING .set(binding) @@ -164,7 +177,7 @@ pub extern "C" fn mmtk_bind_mutator(tls: VMMutatorThread) -> *mut RubyMutator { } #[no_mangle] -pub extern "C" fn mmtk_destroy_mutator(mutator: *mut RubyMutator) { +pub unsafe extern "C" fn mmtk_destroy_mutator(mutator: *mut RubyMutator) { // notify mmtk-core about destroyed mutator memory_manager::destroy_mutator(unsafe { &mut *mutator }); // turn the ptr back to a box, and let Rust properly reclaim it @@ -184,7 +197,9 @@ pub extern "C" fn mmtk_handle_user_collection_request( #[no_mangle] pub extern "C" fn mmtk_set_gc_enabled(enable: bool) { - crate::CONFIGURATION.gc_enabled.store(enable, Ordering::Relaxed); + crate::CONFIGURATION + .gc_enabled + .store(enable, Ordering::Relaxed); } #[no_mangle] @@ -195,7 +210,7 @@ pub extern "C" fn mmtk_gc_enabled_p() -> bool { // =============== Object allocation =============== #[no_mangle] -pub extern "C" fn mmtk_alloc( +pub unsafe extern "C" fn mmtk_alloc( mutator: *mut RubyMutator, size: usize, align: usize, @@ -213,7 +228,7 @@ pub extern "C" fn mmtk_alloc( } #[no_mangle] -pub extern "C" fn mmtk_post_alloc( +pub unsafe extern "C" fn mmtk_post_alloc( mutator: *mut RubyMutator, refer: ObjectReference, bytes: usize, @@ -243,7 +258,7 @@ pub extern "C" fn mmtk_remove_weak(ptr: &ObjectReference) { // =============== Write barriers =============== #[no_mangle] -pub extern "C" fn mmtk_object_reference_write_post( +pub unsafe extern "C" fn mmtk_object_reference_write_post( mutator: *mut RubyMutator, object: ObjectReference, ) { @@ -347,7 +362,7 @@ pub extern "C" fn mmtk_plan() -> *const u8 { PlanSelector::NoGC => NO_GC.as_ptr(), PlanSelector::MarkSweep => MARK_SWEEP.as_ptr(), PlanSelector::Immix => IMMIX.as_ptr(), - _ => panic!("Unknown plan") + _ => panic!("Unknown plan"), } } @@ -359,7 +374,7 @@ pub extern "C" fn mmtk_heap_mode() -> *const u8 { match *crate::BINDING.get().unwrap().mmtk.get_options().gc_trigger { GCTriggerSelector::FixedHeapSize(_) => FIXED_HEAP.as_ptr(), GCTriggerSelector::DynamicHeapSize(_, _) => DYNAMIC_HEAP.as_ptr(), - _ => panic!("Unknown heap mode") + _ => panic!("Unknown heap mode"), } } @@ -368,7 +383,7 @@ pub extern "C" fn mmtk_heap_min() -> usize { match *crate::BINDING.get().unwrap().mmtk.get_options().gc_trigger { GCTriggerSelector::FixedHeapSize(_) => 0, GCTriggerSelector::DynamicHeapSize(min_size, _) => min_size, - _ => panic!("Unknown heap mode") + _ => panic!("Unknown heap mode"), } } @@ -377,7 +392,7 @@ pub extern "C" fn mmtk_heap_max() -> usize { match *crate::BINDING.get().unwrap().mmtk.get_options().gc_trigger { GCTriggerSelector::FixedHeapSize(max_size) => max_size, GCTriggerSelector::DynamicHeapSize(_, max_size) => max_size, - _ => panic!("Unknown heap mode") + _ => panic!("Unknown heap mode"), } } diff --git a/gc/mmtk/src/binding.rs b/gc/mmtk/src/binding.rs index e0f8640e1c..619b7f246c 100644 --- a/gc/mmtk/src/binding.rs +++ b/gc/mmtk/src/binding.rs @@ -83,7 +83,7 @@ impl RubyBinding { gc_thread_join_handles: Default::default(), wb_unprotected_objects: Default::default(), - weak_reference_dead_value + weak_reference_dead_value, } } diff --git a/gc/mmtk/src/lib.rs b/gc/mmtk/src/lib.rs index 01497e9c42..d16a5bf42f 100644 --- a/gc/mmtk/src/lib.rs +++ b/gc/mmtk/src/lib.rs @@ -1,3 +1,7 @@ +// Warn about unsafe operations in functions that are already marked as unsafe. +// This will become default in Rust 2024 edition. +#![warn(unsafe_op_in_unsafe_fn)] + extern crate libc; extern crate mmtk; #[macro_use] @@ -131,3 +135,14 @@ pub(crate) fn set_panic_hook() { } })); } + +/// This kind of assertion is enabled if either building in debug mode or the +/// "extra_assert" feature is enabled. +#[macro_export] +macro_rules! extra_assert { + ($($arg:tt)*) => { + if std::cfg!(any(debug_assertions, feature = "extra_assert")) { + std::assert!($($arg)*); + } + }; +} diff --git a/gc/mmtk/src/object_model.rs b/gc/mmtk/src/object_model.rs index abeef1f2b9..93b6063a05 100644 --- a/gc/mmtk/src/object_model.rs +++ b/gc/mmtk/src/object_model.rs @@ -40,9 +40,7 @@ impl ObjectModel for VMObjectModel { _semantics: CopySemantics, _copy_context: &mut GCWorkerCopyContext, ) -> ObjectReference { - unimplemented!( - "Copying GC not currently supported" - ) + unimplemented!("Copying GC not currently supported") } fn copy_to(_from: ObjectReference, _to: ObjectReference, _region: Address) -> Address { diff --git a/gc/mmtk/src/utils.rs b/gc/mmtk/src/utils.rs index de929c3952..71a7ae8dd2 100644 --- a/gc/mmtk/src/utils.rs +++ b/gc/mmtk/src/utils.rs @@ -3,8 +3,8 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use atomic_refcell::AtomicRefCell; use mmtk::scheduler::{GCWork, GCWorker, WorkBucketStage}; -use sysinfo::System; use crate::Ruby; +use sysinfo::System; pub struct ChunkedVecCollector { vecs: Vec>, @@ -97,32 +97,29 @@ pub fn default_heap_max() -> usize { .expect("Invalid Memory size") as usize } -pub fn parse_capacity(input: &String, default: usize) -> usize { +pub fn parse_capacity(input: &str) -> Option { let trimmed = input.trim(); const KIBIBYTE: usize = 1024; const MEBIBYTE: usize = 1024 * KIBIBYTE; const GIBIBYTE: usize = 1024 * MEBIBYTE; - let (val, suffix) = if let Some(pos) = trimmed.find(|c: char| !c.is_numeric()) { - (&trimmed[..pos], &trimmed[pos..]) + let (number, suffix) = if let Some(pos) = trimmed.find(|c: char| !c.is_numeric()) { + trimmed.split_at(pos) } else { (trimmed, "") }; - // 1MiB is the default heap size - match (val, suffix) { - (number, "GiB") => number.parse::() - .and_then(|v| Ok(v * GIBIBYTE)) - .unwrap_or(default), - (number, "MiB") => number.parse::() - .and_then(|v| Ok(v * MEBIBYTE)) - .unwrap_or(default), - (number, "KiB") => number.parse::() - .and_then(|v| Ok(v * KIBIBYTE)) - .unwrap_or(default), - (number, suffix) if suffix.is_empty() => number.parse::().unwrap_or(default), - (_, _) => default + let Ok(v) = number.parse::() else { + return None; + }; + + match suffix { + "GiB" => Some(v * GIBIBYTE), + "MiB" => Some(v * MEBIBYTE), + "KiB" => Some(v * KIBIBYTE), + "" => Some(v), + _ => None, } } @@ -132,32 +129,30 @@ mod tests { #[test] fn test_parse_capacity_parses_bare_bytes() { - assert_eq!(1234, parse_capacity(&String::from("1234"), 0)); + assert_eq!(Some(1234), parse_capacity("1234")); } #[test] fn test_parse_capacity_parses_kibibytes() { - assert_eq!(10240, parse_capacity(&String::from("10KiB"), 0)) + assert_eq!(Some(10240), parse_capacity("10KiB")); } #[test] fn test_parse_capacity_parses_mebibytes() { - assert_eq!(10485760, parse_capacity(&String::from("10MiB"), 0)) + assert_eq!(Some(10485760), parse_capacity("10MiB")) } #[test] fn test_parse_capacity_parses_gibibytes() { - assert_eq!(10737418240, parse_capacity(&String::from("10GiB"), 0)) + assert_eq!(Some(10737418240), parse_capacity("10GiB")) } #[test] - fn test_parses_nonsense_value_as_default_max() { - let default = 100; - - assert_eq!(default, parse_capacity(&String::from("notanumber"), default)); - assert_eq!(default, parse_capacity(&String::from("5tartswithanumber"), default)); - assert_eq!(default, parse_capacity(&String::from("number1nthemiddle"), default)); - assert_eq!(default, parse_capacity(&String::from("numberattheend111"), default)); - assert_eq!(default, parse_capacity(&String::from("mult1pl3numb3r5"), default)); + fn test_parse_capacity_parses_nonsense_values() { + assert_eq!(None, parse_capacity("notanumber")); + assert_eq!(None, parse_capacity("5tartswithanumber")); + assert_eq!(None, parse_capacity("number1nthemiddle")); + assert_eq!(None, parse_capacity("numberattheend111")); + assert_eq!(None, parse_capacity("mult1pl3numb3r5")); } } diff --git a/gc/mmtk/src/weak_proc.rs b/gc/mmtk/src/weak_proc.rs index 77af5e2b85..204dd203aa 100644 --- a/gc/mmtk/src/weak_proc.rs +++ b/gc/mmtk/src/weak_proc.rs @@ -6,11 +6,7 @@ use mmtk::{ vm::ObjectTracerContext, }; -use crate::{ - abi::GCThreadTLS, - upcalls, - Ruby, -}; +use crate::{abi::GCThreadTLS, upcalls, Ruby}; pub struct WeakProcessor { /// Objects that needs `obj_free` called when dying. @@ -84,16 +80,13 @@ impl WeakProcessor { let global_tables_count = (crate::upcalls().global_tables_count)(); let work_packets = (0..global_tables_count) - .map(|i| { - Box::new(UpdateGlobalTables { idx: i }) as _ - }) - .collect(); + .map(|i| Box::new(UpdateGlobalTables { idx: i }) as _) + .collect(); worker.scheduler().work_buckets[WorkBucketStage::VMRefClosure].bulk_add(work_packets); - worker.scheduler().work_buckets[WorkBucketStage::VMRefClosure].bulk_add(vec![ - Box::new(UpdateWbUnprotectedObjectsList) as _, - ]); + worker.scheduler().work_buckets[WorkBucketStage::VMRefClosure] + .bulk_add(vec![Box::new(UpdateWbUnprotectedObjectsList) as _]); } } @@ -144,13 +137,13 @@ impl GCWork for ProcessWeakReferences { .try_lock() .expect("Mutators should not be holding the lock."); - for ptr_ptr in weak_references.iter_mut() { - if !(**ptr_ptr).is_reachable() { - **ptr_ptr = crate::binding().weak_reference_dead_value; - } + for ptr_ptr in weak_references.iter_mut() { + if !(**ptr_ptr).is_reachable() { + **ptr_ptr = crate::binding().weak_reference_dead_value; } + } - weak_references.clear(); + weak_references.clear(); } } @@ -194,7 +187,7 @@ impl GCWork for UpdateFinalizerObjIdTables { } struct UpdateGlobalTables { - idx: i32 + idx: i32, } impl GlobalTableProcessingWork for UpdateGlobalTables { fn process_table(&mut self) { diff --git a/gems/bundled_gems b/gems/bundled_gems index 871c321d38..a040f93a34 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -7,41 +7,41 @@ # if `revision` is not given, "v"+`version` or `version` will be used. minitest 5.25.5 https://github.com/minitest/minitest -power_assert 2.0.5 https://github.com/ruby/power_assert -rake 13.2.1 https://github.com/ruby/rake -test-unit 3.6.8 https://github.com/test-unit/test-unit +power_assert 2.0.5 https://github.com/ruby/power_assert f88e406e7c9e0810cc149869582afbae1fb84c4a +rake 13.3.0 https://github.com/ruby/rake +test-unit 3.7.0 https://github.com/test-unit/test-unit rexml 3.4.1 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.8 https://github.com/ruby/net-ftp -net-imap 0.5.8 https://github.com/ruby/net-imap +net-imap 0.5.9 https://github.com/ruby/net-imap net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.5.1 https://github.com/ruby/net-smtp -matrix 0.4.2 https://github.com/ruby/matrix -prime 0.1.3 https://github.com/ruby/prime -rbs 3.9.3 https://github.com/ruby/rbs +matrix 0.4.3 https://github.com/ruby/matrix +prime 0.1.4 https://github.com/ruby/prime +rbs 3.9.4 https://github.com/ruby/rbs typeprof 0.30.1 https://github.com/ruby/typeprof -debug 1.10.0 https://github.com/ruby/debug +debug 1.11.0 https://github.com/ruby/debug racc 1.8.1 https://github.com/ruby/racc mutex_m 0.3.0 https://github.com/ruby/mutex_m getoptlong 0.2.1 https://github.com/ruby/getoptlong -base64 0.2.0 https://github.com/ruby/base64 -bigdecimal 3.1.9 https://github.com/ruby/bigdecimal +base64 0.3.0 https://github.com/ruby/base64 +bigdecimal 3.2.2 https://github.com/ruby/bigdecimal observer 0.1.2 https://github.com/ruby/observer abbrev 0.1.2 https://github.com/ruby/abbrev resolv-replace 0.1.1 https://github.com/ruby/resolv-replace rinda 0.2.0 https://github.com/ruby/rinda -drb 2.2.1 https://github.com/ruby/drb +drb 2.2.3 https://github.com/ruby/drb nkf 0.2.0 https://github.com/ruby/nkf syslog 0.3.0 https://github.com/ruby/syslog -csv 3.3.4 https://github.com/ruby/csv +csv 3.3.5 https://github.com/ruby/csv repl_type_completor 0.1.11 https://github.com/ruby/repl_type_completor 25108aa8d69ddaba0b5da3feff1c0035371524b2 -ostruct 0.6.1 https://github.com/ruby/ostruct +ostruct 0.6.3 https://github.com/ruby/ostruct pstore 0.2.0 https://github.com/ruby/pstore -benchmark 0.4.0 https://github.com/ruby/benchmark +benchmark 0.4.1 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger -rdoc 6.13.1 https://github.com/ruby/rdoc +rdoc 6.14.2 https://github.com/ruby/rdoc win32ole 1.9.2 https://github.com/ruby/win32ole -irb 1.15.2 https://github.com/ruby/irb +irb 1.15.2 https://github.com/ruby/irb 331c4e851296b115db766c291e8cf54a2492fb36 reline 0.6.1 https://github.com/ruby/reline readline 0.0.4 https://github.com/ruby/readline fiddle 1.1.8 https://github.com/ruby/fiddle diff --git a/hash.c b/hash.c index 2c3084ba6d..182b277f52 100644 --- a/hash.c +++ b/hash.c @@ -882,8 +882,9 @@ ar_general_foreach(VALUE hash, st_foreach_check_callback_func *func, st_update_c if (replace) { (*replace)(&key, &val, arg, TRUE); - // TODO: pair should be same as pair before. - pair = RHASH_AR_TABLE_REF(hash, i); + // Pair should not have moved + HASH_ASSERT(pair == RHASH_AR_TABLE_REF(hash, i)); + pair->key = (VALUE)key; pair->val = (VALUE)val; } @@ -1597,10 +1598,11 @@ VALUE rb_hash_dup(VALUE hash) { const VALUE flags = RBASIC(hash)->flags; - VALUE ret = hash_dup(hash, rb_obj_class(hash), - flags & (FL_EXIVAR|RHASH_PROC_DEFAULT)); - if (flags & FL_EXIVAR) + VALUE ret = hash_dup(hash, rb_obj_class(hash), flags & RHASH_PROC_DEFAULT); + + if (rb_obj_exivar_p(hash)) { rb_copy_generic_ivar(ret, hash); + } return ret; } @@ -1723,14 +1725,14 @@ tbl_update(VALUE hash, VALUE key, tbl_update_func func, st_data_t optional_arg) .func = func, .hash = hash, .key = key, - .value = (VALUE)optional_arg, + .value = 0 }; int ret = rb_hash_stlike_update(hash, key, tbl_update_modify, (st_data_t)&arg); /* write barrier */ RB_OBJ_WRITTEN(hash, Qundef, arg.key); - RB_OBJ_WRITTEN(hash, Qundef, arg.value); + if (arg.value) RB_OBJ_WRITTEN(hash, Qundef, arg.value); return ret; } @@ -2920,7 +2922,7 @@ hash_aset(st_data_t *key, st_data_t *val, struct update_arg *arg, int existing) VALUE rb_hash_key_str(VALUE key) { - if (!RB_FL_ANY_RAW(key, FL_EXIVAR) && RBASIC_CLASS(key) == rb_cString) { + if (!rb_obj_exivar_p(key) && RBASIC_CLASS(key) == rb_cString) { return rb_fstring(key); } else { @@ -3871,7 +3873,6 @@ rb_hash_values(VALUE hash) } rb_ary_set_len(values, size); } - else { rb_hash_foreach(hash, values_i, values); } @@ -4155,30 +4156,70 @@ rb_hash_update_i(VALUE key, VALUE value, VALUE hash) return ST_CONTINUE; } +struct update_call_args { + VALUE hash, newvalue, *argv; + int argc; + bool block_given; + bool iterating; +}; + static int rb_hash_update_block_callback(st_data_t *key, st_data_t *value, struct update_arg *arg, int existing) { - st_data_t newvalue = arg->arg; + VALUE k = (VALUE)*key, v = (VALUE)*value; + struct update_call_args *ua = (void *)arg->arg; + VALUE newvalue = ua->newvalue, hash = arg->hash; if (existing) { - newvalue = (st_data_t)rb_yield_values(3, (VALUE)*key, (VALUE)*value, (VALUE)newvalue); + hash_iter_lev_inc(hash); + ua->iterating = true; + newvalue = rb_yield_values(3, k, v, newvalue); + hash_iter_lev_dec(hash); + ua->iterating = false; } - else if (RHASH_STRING_KEY_P(arg->hash, *key) && !RB_OBJ_FROZEN(*key)) { - *key = rb_hash_key_str(*key); + else if (RHASH_STRING_KEY_P(hash, k) && !RB_OBJ_FROZEN(k)) { + *key = (st_data_t)rb_hash_key_str(k); } - *value = newvalue; + *value = (st_data_t)newvalue; return ST_CONTINUE; } NOINSERT_UPDATE_CALLBACK(rb_hash_update_block_callback) static int -rb_hash_update_block_i(VALUE key, VALUE value, VALUE hash) +rb_hash_update_block_i(VALUE key, VALUE value, VALUE args) { - RHASH_UPDATE(hash, key, rb_hash_update_block_callback, value); + struct update_call_args *ua = (void *)args; + ua->newvalue = value; + RHASH_UPDATE(ua->hash, key, rb_hash_update_block_callback, args); return ST_CONTINUE; } +static VALUE +rb_hash_update_call(VALUE args) +{ + struct update_call_args *arg = (void *)args; + + for (int i = 0; i < arg->argc; i++){ + VALUE hash = to_hash(arg->argv[i]); + if (arg->block_given) { + rb_hash_foreach(hash, rb_hash_update_block_i, args); + } + else { + rb_hash_foreach(hash, rb_hash_update_i, arg->hash); + } + } + return arg->hash; +} + +static VALUE +rb_hash_update_ensure(VALUE args) +{ + struct update_call_args *ua = (void *)args; + if (ua->iterating) hash_iter_lev_dec(ua->hash); + return Qnil; +} + /* * call-seq: * update(*other_hashes) -> self @@ -4225,20 +4266,17 @@ rb_hash_update_block_i(VALUE key, VALUE value, VALUE hash) static VALUE rb_hash_update(int argc, VALUE *argv, VALUE self) { - int i; - bool block_given = rb_block_given_p(); + struct update_call_args args = { + .hash = self, + .argv = argv, + .argc = argc, + .block_given = rb_block_given_p(), + .iterating = false, + }; + VALUE arg = (VALUE)&args; rb_hash_modify(self); - for (i = 0; i < argc; i++){ - VALUE hash = to_hash(argv[i]); - if (block_given) { - rb_hash_foreach(hash, rb_hash_update_block_i, self); - } - else { - rb_hash_foreach(hash, rb_hash_update_i, self); - } - } - return self; + return rb_ensure(rb_hash_update_call, arg, rb_hash_update_ensure, arg); } struct update_func_arg { @@ -5036,10 +5074,8 @@ rb_hash_deconstruct_keys(VALUE hash, VALUE keys) static int add_new_i(st_data_t *key, st_data_t *val, st_data_t arg, int existing) { - VALUE *args = (VALUE *)arg; if (existing) return ST_STOP; - RB_OBJ_WRITTEN(args[0], Qundef, (VALUE)*key); - RB_OBJ_WRITE(args[0], (VALUE *)val, args[1]); + *val = arg; return ST_CONTINUE; } @@ -5051,22 +5087,25 @@ int rb_hash_add_new_element(VALUE hash, VALUE key, VALUE val) { st_table *tbl; - int ret = 0; - VALUE args[2]; - args[0] = hash; - args[1] = val; + int ret = -1; if (RHASH_AR_TABLE_P(hash)) { - ret = ar_update(hash, (st_data_t)key, add_new_i, (st_data_t)args); - if (ret != -1) { - return ret; + ret = ar_update(hash, (st_data_t)key, add_new_i, (st_data_t)val); + if (ret == -1) { + ar_force_convert_table(hash, __FILE__, __LINE__); } - ar_force_convert_table(hash, __FILE__, __LINE__); } - tbl = RHASH_TBL_RAW(hash); - return st_update(tbl, (st_data_t)key, add_new_i, (st_data_t)args); - + if (ret == -1) { + tbl = RHASH_TBL_RAW(hash); + ret = st_update(tbl, (st_data_t)key, add_new_i, (st_data_t)val); + } + if (!ret) { + // Newly inserted + RB_OBJ_WRITTEN(hash, Qundef, key); + RB_OBJ_WRITTEN(hash, Qundef, val); + } + return ret; } static st_data_t @@ -5133,8 +5172,7 @@ extern char **environ; #define ENVNMATCH(s1, s2, n) (memcmp((s1), (s2), (n)) == 0) #endif -#define ENV_LOCK() RB_VM_LOCK_ENTER() -#define ENV_UNLOCK() RB_VM_LOCK_LEAVE() +#define ENV_LOCKING() RB_VM_LOCKING() static inline rb_encoding * env_encoding(void) @@ -5172,12 +5210,10 @@ static VALUE getenv_with_lock(const char *name) { VALUE ret; - ENV_LOCK(); - { + ENV_LOCKING() { const char *val = getenv(name); ret = env_str_new2(val); } - ENV_UNLOCK(); return ret; } @@ -5186,11 +5222,9 @@ has_env_with_lock(const char *name) { const char *val; - ENV_LOCK(); - { + ENV_LOCKING() { val = getenv(name); } - ENV_UNLOCK(); return val ? true : false; } @@ -5440,13 +5474,11 @@ ruby_setenv(const char *name, const char *value) *wvalue = L'\0'; } - ENV_LOCK(); - { + ENV_LOCKING() { /* Use _wputenv_s() instead of SetEnvironmentVariableW() to make sure * special variables like "TZ" are interpret by libc. */ failed = _wputenv_s(wname, wvalue); } - ENV_UNLOCK(); ALLOCV_END(buf); /* even if putenv() failed, clean up and try to delete the @@ -5463,28 +5495,22 @@ ruby_setenv(const char *name, const char *value) #elif defined(HAVE_SETENV) && defined(HAVE_UNSETENV) if (value) { int ret; - ENV_LOCK(); - { + ENV_LOCKING() { ret = setenv(name, value, 1); } - ENV_UNLOCK(); if (ret) rb_sys_fail_sprintf("setenv(%s)", name); } else { #ifdef VOID_UNSETENV - ENV_LOCK(); - { + ENV_LOCKING() { unsetenv(name); } - ENV_UNLOCK(); #else int ret; - ENV_LOCK(); - { + ENV_LOCKING() { ret = unsetenv(name); } - ENV_UNLOCK(); if (ret) rb_sys_fail_sprintf("unsetenv(%s)", name); #endif @@ -5507,8 +5533,7 @@ ruby_setenv(const char *name, const char *value) snprintf(mem_ptr, mem_size, "%s=%s", name, value); } - ENV_LOCK(); - { + ENV_LOCKING() { for (env_ptr = GET_ENVIRON(environ); (str = *env_ptr) != 0; ++env_ptr) { if (!strncmp(str, name, len) && str[len] == '=') { if (!in_origenv(str)) free(str); @@ -5517,15 +5542,12 @@ ruby_setenv(const char *name, const char *value) } } } - ENV_UNLOCK(); if (value) { int ret; - ENV_LOCK(); - { + ENV_LOCKING() { ret = putenv(mem_ptr); } - ENV_UNLOCK(); if (ret) { free(mem_ptr); @@ -5536,8 +5558,7 @@ ruby_setenv(const char *name, const char *value) size_t len; int i; - ENV_LOCK(); - { + ENV_LOCKING() { i = envix(name); /* where does it go? */ if (environ == origenviron) { /* need we copy environment? */ @@ -5578,7 +5599,6 @@ ruby_setenv(const char *name, const char *value) finish:; } - ENV_UNLOCK(); #endif /* WIN32 */ } @@ -5663,8 +5683,7 @@ env_keys(int raw) rb_encoding *enc = raw ? 0 : rb_locale_encoding(); VALUE ary = rb_ary_new(); - ENV_LOCK(); - { + ENV_LOCKING() { char **env = GET_ENVIRON(environ); while (*env) { char *s = strchr(*env, '='); @@ -5678,7 +5697,6 @@ env_keys(int raw) } FREE_ENVIRON(environ); } - ENV_UNLOCK(); return ary; } @@ -5708,8 +5726,7 @@ rb_env_size(VALUE ehash, VALUE args, VALUE eobj) char **env; long cnt = 0; - ENV_LOCK(); - { + ENV_LOCKING() { env = GET_ENVIRON(environ); for (; *env ; ++env) { if (strchr(*env, '=')) { @@ -5718,7 +5735,6 @@ rb_env_size(VALUE ehash, VALUE args, VALUE eobj) } FREE_ENVIRON(environ); } - ENV_UNLOCK(); return LONG2FIX(cnt); } @@ -5759,8 +5775,7 @@ env_values(void) { VALUE ary = rb_ary_new(); - ENV_LOCK(); - { + ENV_LOCKING() { char **env = GET_ENVIRON(environ); while (*env) { @@ -5772,7 +5787,6 @@ env_values(void) } FREE_ENVIRON(environ); } - ENV_UNLOCK(); return ary; } @@ -5853,8 +5867,7 @@ env_each_pair(VALUE ehash) VALUE ary = rb_ary_new(); - ENV_LOCK(); - { + ENV_LOCKING() { char **env = GET_ENVIRON(environ); while (*env) { @@ -5867,7 +5880,6 @@ env_each_pair(VALUE ehash) } FREE_ENVIRON(environ); } - ENV_UNLOCK(); if (rb_block_pair_yield_optimizable()) { for (i=0; icnt * sizeof(VALUE); + break; + case imemo_fields: + if (rb_shape_obj_too_complex_p(obj)) { + size += st_memsize(IMEMO_OBJ_FIELDS(obj)->as.complex.table); + } + else if (FL_TEST_RAW(obj, OBJ_FIELD_EXTERNAL)) { + size += RSHAPE_CAPACITY(RBASIC_SHAPE_ID(obj)) * sizeof(VALUE); + } break; default: rb_bug("unreachable"); @@ -167,47 +278,6 @@ rb_imemo_memsize(VALUE obj) * mark * ========================================================================= */ -static enum rb_id_table_iterator_result -cc_table_mark_i(VALUE ccs_ptr, void *data) -{ - // looks duplicate to mark_cc_entry_i (gc.c) - struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_ptr; - VM_ASSERT(vm_ccs_p(ccs)); -#if VM_CHECK_MODE > 0 - VALUE klass = (VALUE)data; - - VALUE lookup_val; - VM_ASSERT(rb_id_table_lookup(RCLASS_WRITABLE_CC_TBL(klass), ccs->cme->called_id, &lookup_val)); - VM_ASSERT(lookup_val == ccs_ptr); -#endif - - if (METHOD_ENTRY_INVALIDATED(ccs->cme)) { - rb_vm_ccs_free(ccs); - return ID_TABLE_DELETE; - } - else { - rb_gc_mark_movable((VALUE)ccs->cme); - - for (int i=0; ilen; i++) { - VM_ASSERT(klass == ccs->entries[i].cc->klass); - VM_ASSERT(vm_cc_check_cme(ccs->entries[i].cc, ccs->cme)); - - rb_gc_mark_movable((VALUE)ccs->entries[i].cc); - } - return ID_TABLE_CONTINUE; - } -} - -void -rb_cc_table_mark(VALUE klass) -{ - // TODO: delete this (and cc_table_mark_i) if it's ok - struct rb_id_table *cc_tbl = RCLASS_WRITABLE_CC_TBL(klass); - if (cc_tbl) { - rb_id_table_foreach_values(cc_tbl, cc_table_mark_i, (void *)klass); - } -} - static bool moved_or_living_object_strictly_p(VALUE obj) { @@ -420,6 +490,27 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) break; } + case imemo_fields: { + rb_gc_mark_and_move((VALUE *)&RBASIC(obj)->klass); + + if (rb_shape_obj_too_complex_p(obj)) { + st_table *tbl = rb_imemo_fields_complex_tbl(obj); + if (reference_updating) { + rb_gc_ref_update_table_values_only(tbl); + } + else { + rb_mark_tbl_no_pin(tbl); + } + } + else { + VALUE *fields = rb_imemo_fields_ptr(obj); + attr_index_t len = RSHAPE_LEN(RBASIC_SHAPE_ID(obj)); + for (attr_index_t i = 0; i < len; i++) { + rb_gc_mark_and_move(&fields[i]); + } + } + break; + } default: rb_bug("unreachable"); } @@ -480,7 +571,7 @@ rb_vm_ccs_free(struct rb_class_cc_entries *ccs) } static enum rb_id_table_iterator_result -cc_table_free_i(VALUE ccs_ptr, void *data) +cc_tbl_free_i(VALUE ccs_ptr, void *data) { struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_ptr; VALUE klass = (VALUE)data; @@ -491,28 +582,25 @@ cc_table_free_i(VALUE ccs_ptr, void *data) return ID_TABLE_CONTINUE; } -void -rb_cc_table_free(VALUE klass) -{ - // This can be called and work well only for IClass - // And classext_iclass_free uses rb_cc_tbl_free now. - // TODO: remove this if it's ok - struct rb_id_table *cc_tbl = RCLASS_WRITABLE_CC_TBL(klass); - - if (cc_tbl) { - rb_id_table_foreach_values(cc_tbl, cc_table_free_i, (void *)klass); - rb_id_table_free(cc_tbl); - } -} - void rb_cc_tbl_free(struct rb_id_table *cc_tbl, VALUE klass) { if (!cc_tbl) return; - rb_id_table_foreach_values(cc_tbl, cc_table_free_i, (void *)klass); + rb_id_table_foreach_values(cc_tbl, cc_tbl_free_i, (void *)klass); rb_id_table_free(cc_tbl); } +static inline void +imemo_fields_free(struct rb_fields *fields) +{ + if (rb_shape_obj_too_complex_p((VALUE)fields)) { + st_free_table(fields->as.complex.table); + } + else if (FL_TEST_RAW((VALUE)fields, OBJ_FIELD_EXTERNAL)) { + xfree(fields->as.external.ptr); + } +} + void rb_imemo_free(VALUE obj) { @@ -576,6 +664,7 @@ rb_imemo_free(VALUE obj) break; case imemo_svar: RB_DEBUG_COUNTER_INC(obj_imemo_svar); + break; case imemo_throw_data: RB_DEBUG_COUNTER_INC(obj_imemo_throw_data); @@ -585,6 +674,10 @@ rb_imemo_free(VALUE obj) xfree(((rb_imemo_tmpbuf_t *)obj)->ptr); RB_DEBUG_COUNTER_INC(obj_imemo_tmpbuf); + break; + case imemo_fields: + imemo_fields_free(IMEMO_OBJ_FIELDS(obj)); + RB_DEBUG_COUNTER_INC(obj_imemo_fields); break; default: rb_bug("unreachable"); diff --git a/include/ruby/atomic.h b/include/ruby/atomic.h index 4cf891b345..b778276f62 100644 --- a/include/ruby/atomic.h +++ b/include/ruby/atomic.h @@ -77,6 +77,9 @@ typedef unsigned int rb_atomic_t; typedef LONG rb_atomic_t; #elif defined(__sun) && defined(HAVE_ATOMIC_H) typedef unsigned int rb_atomic_t; +#elif defined(HAVE_STDATOMIC_H) +# include +typedef unsigned int rb_atomic_t; #else # error No atomic operation found #endif @@ -408,6 +411,9 @@ rbimpl_atomic_fetch_add(volatile rb_atomic_t *ptr, rb_atomic_t val) RBIMPL_ASSERT_OR_ASSUME(val <= INT_MAX); return atomic_add_int_nv(ptr, val) - val; +#elif defined(HAVE_STDATOMIC_H) + return atomic_fetch_add((_Atomic volatile rb_atomic_t *)ptr, val); + #else # error Unsupported platform. #endif @@ -436,12 +442,17 @@ rbimpl_atomic_size_fetch_add(volatile size_t *ptr, size_t val) RBIMPL_ASSERT_OR_ASSUME(val <= LONG_MAX); atomic_add_long(ptr, val); -#else +#elif defined(__sun) && defined(HAVE_ATOMIC_H) RBIMPL_STATIC_ASSERT(size_of_rb_atomic_t, sizeof *ptr == sizeof(rb_atomic_t)); volatile rb_atomic_t *const tmp = RBIMPL_CAST((volatile rb_atomic_t *)ptr); rbimpl_atomic_fetch_add(tmp, val); +#elif defined(HAVE_STDATOMIC_H) + return atomic_fetch_add((_Atomic volatile size_t *)ptr, val); + +#else +# error Unsupported platform. #endif } @@ -477,6 +488,9 @@ rbimpl_atomic_add(volatile rb_atomic_t *ptr, rb_atomic_t val) RBIMPL_ASSERT_OR_ASSUME(val <= INT_MAX); atomic_add_int(ptr, val); +#elif defined(HAVE_STDATOMIC_H) + *(_Atomic volatile rb_atomic_t *)ptr += val; + #else # error Unsupported platform. #endif @@ -505,12 +519,17 @@ rbimpl_atomic_size_add(volatile size_t *ptr, size_t val) RBIMPL_ASSERT_OR_ASSUME(val <= LONG_MAX); atomic_add_long(ptr, val); -#else +#elif defined(_WIN32) || (defined(__sun) && defined(HAVE_ATOMIC_H)) RBIMPL_STATIC_ASSERT(size_of_rb_atomic_t, sizeof *ptr == sizeof(rb_atomic_t)); volatile rb_atomic_t *const tmp = RBIMPL_CAST((volatile rb_atomic_t *)ptr); rbimpl_atomic_add(tmp, val); +#elif defined(HAVE_STDATOMIC_H) + *(_Atomic volatile size_t *)ptr += val; + +#else +# error Unsupported platform. #endif } @@ -531,9 +550,11 @@ rbimpl_atomic_inc(volatile rb_atomic_t *ptr) #elif defined(__sun) && defined(HAVE_ATOMIC_H) atomic_inc_uint(ptr); -#else +#elif defined(HAVE_STDATOMIC_H) rbimpl_atomic_add(ptr, 1); +#else +# error Unsupported platform. #endif } @@ -554,11 +575,16 @@ rbimpl_atomic_size_inc(volatile size_t *ptr) #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) atomic_inc_ulong(ptr); -#else +#elif defined(_WIN32) || (defined(__sun) && defined(HAVE_ATOMIC_H)) RBIMPL_STATIC_ASSERT(size_of_size_t, sizeof *ptr == sizeof(rb_atomic_t)); rbimpl_atomic_size_add(ptr, 1); +#elif defined(HAVE_STDATOMIC_H) + rbimpl_atomic_size_add(ptr, 1); + +#else +# error Unsupported platform. #endif } @@ -586,6 +612,9 @@ rbimpl_atomic_fetch_sub(volatile rb_atomic_t *ptr, rb_atomic_t val) RBIMPL_ASSERT_OR_ASSUME(val <= INT_MAX); return atomic_add_int_nv(ptr, neg * val) + val; +#elif defined(HAVE_STDATOMIC_H) + return atomic_fetch_sub((_Atomic volatile rb_atomic_t *)ptr, val); + #else # error Unsupported platform. #endif @@ -613,6 +642,9 @@ rbimpl_atomic_sub(volatile rb_atomic_t *ptr, rb_atomic_t val) RBIMPL_ASSERT_OR_ASSUME(val <= INT_MAX); atomic_add_int(ptr, neg * val); +#elif defined(HAVE_STDATOMIC_H) + *(_Atomic volatile rb_atomic_t *)ptr -= val; + #else # error Unsupported platform. #endif @@ -641,12 +673,17 @@ rbimpl_atomic_size_sub(volatile size_t *ptr, size_t val) RBIMPL_ASSERT_OR_ASSUME(val <= LONG_MAX); atomic_add_long(ptr, neg * val); -#else +#elif defined(_WIN32) || (defined(__sun) && defined(HAVE_ATOMIC_H)) RBIMPL_STATIC_ASSERT(size_of_rb_atomic_t, sizeof *ptr == sizeof(rb_atomic_t)); volatile rb_atomic_t *const tmp = RBIMPL_CAST((volatile rb_atomic_t *)ptr); rbimpl_atomic_sub(tmp, val); +#elif defined(HAVE_STDATOMIC_H) + *(_Atomic volatile size_t *)ptr -= val; + +#else +# error Unsupported platform. #endif } @@ -667,9 +704,11 @@ rbimpl_atomic_dec(volatile rb_atomic_t *ptr) #elif defined(__sun) && defined(HAVE_ATOMIC_H) atomic_dec_uint(ptr); -#else +#elif defined(HAVE_STDATOMIC_H) rbimpl_atomic_sub(ptr, 1); +#else +# error Unsupported platform. #endif } @@ -690,11 +729,16 @@ rbimpl_atomic_size_dec(volatile size_t *ptr) #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) atomic_dec_ulong(ptr); -#else +#elif defined(_WIN32) || (defined(__sun) && defined(HAVE_ATOMIC_H)) RBIMPL_STATIC_ASSERT(size_of_size_t, sizeof *ptr == sizeof(rb_atomic_t)); rbimpl_atomic_size_sub(ptr, 1); +#elif defined(HAVE_STDATOMIC_H) + rbimpl_atomic_size_sub(ptr, 1); + +#else +# error Unsupported platform. #endif } @@ -731,6 +775,9 @@ rbimpl_atomic_or(volatile rb_atomic_t *ptr, rb_atomic_t val) #elif defined(__sun) && defined(HAVE_ATOMIC_H) atomic_or_uint(ptr, val); +#elif !defined(_WIN32) && defined(HAVE_STDATOMIC_H) + *(_Atomic volatile rb_atomic_t *)ptr |= val; + #else # error Unsupported platform. #endif @@ -765,6 +812,9 @@ rbimpl_atomic_exchange(volatile rb_atomic_t *ptr, rb_atomic_t val) #elif defined(__sun) && defined(HAVE_ATOMIC_H) return atomic_swap_uint(ptr, val); +#elif defined(HAVE_STDATOMIC_H) + return atomic_exchange((_Atomic volatile rb_atomic_t *)ptr, val); + #else # error Unsupported platform. #endif @@ -790,13 +840,18 @@ rbimpl_atomic_size_exchange(volatile size_t *ptr, size_t val) #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) return atomic_swap_ulong(ptr, val); -#else +#elif defined(_WIN32) || (defined(__sun) && defined(HAVE_ATOMIC_H)) RBIMPL_STATIC_ASSERT(size_of_size_t, sizeof *ptr == sizeof(rb_atomic_t)); volatile rb_atomic_t *const tmp = RBIMPL_CAST((volatile rb_atomic_t *)ptr); const rb_atomic_t ret = rbimpl_atomic_exchange(tmp, val); return RBIMPL_CAST((size_t)ret); +#elif defined(HAVE_STDATOMIC_H) + return atomic_exchange((_Atomic volatile size_t *)ptr, val); + +#else +# error Unsupported platform. #endif } @@ -947,6 +1002,11 @@ rbimpl_atomic_cas(volatile rb_atomic_t *ptr, rb_atomic_t oldval, rb_atomic_t new #elif defined(__sun) && defined(HAVE_ATOMIC_H) return atomic_cas_uint(ptr, oldval, newval); +#elif defined(HAVE_STDATOMIC_H) + atomic_compare_exchange_strong( + (_Atomic volatile rb_atomic_t *)ptr, &oldval, newval); + return oldval; + #else # error Unsupported platform. #endif @@ -983,12 +1043,19 @@ rbimpl_atomic_size_cas(volatile size_t *ptr, size_t oldval, size_t newval) #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) return atomic_cas_ulong(ptr, oldval, newval); -#else +#elif defined(_WIN32) || (defined(__sun) && defined(HAVE_ATOMIC_H)) RBIMPL_STATIC_ASSERT(size_of_size_t, sizeof *ptr == sizeof(rb_atomic_t)); volatile rb_atomic_t *tmp = RBIMPL_CAST((volatile rb_atomic_t *)ptr); return rbimpl_atomic_cas(tmp, oldval, newval); +#elif defined(HAVE_STDATOMIC_H) + atomic_compare_exchange_strong( + (_Atomic volatile size_t *)ptr, &oldval, newval); + return oldval; + +#else +# error Unsupported platform. #endif } diff --git a/include/ruby/fiber/scheduler.h b/include/ruby/fiber/scheduler.h index b678bd0d1a..b06884f596 100644 --- a/include/ruby/fiber/scheduler.h +++ b/include/ruby/fiber/scheduler.h @@ -23,7 +23,8 @@ RBIMPL_SYMBOL_EXPORT_BEGIN() -#define RUBY_FIBER_SCHEDULER_VERSION 2 +// Version 3: Adds support for `fiber_interrupt`. +#define RUBY_FIBER_SCHEDULER_VERSION 3 struct timeval; @@ -199,6 +200,8 @@ VALUE rb_fiber_scheduler_block(VALUE scheduler, VALUE blocker, VALUE timeout); /** * Wakes up a fiber previously blocked using rb_fiber_scheduler_block(). * + * This function may be called from a different thread. + * * @param[in] scheduler Target scheduler. * @param[in] blocker What was awaited for. * @param[in] fiber What to unblock. @@ -391,11 +394,54 @@ VALUE rb_fiber_scheduler_io_close(VALUE scheduler, VALUE io); */ VALUE rb_fiber_scheduler_address_resolve(VALUE scheduler, VALUE hostname); +// The state of the blocking operation execution. struct rb_fiber_scheduler_blocking_operation_state { void *result; int saved_errno; }; +// The opaque handle for the blocking operation. +typedef struct rb_fiber_scheduler_blocking_operation rb_fiber_scheduler_blocking_operation_t; + +/** + * Extract the blocking operation handle from a BlockingOperationRuby object. + * + * This function safely extracts the opaque handle from a BlockingOperation VALUE + * while holding the GVL. The returned pointer can be passed to worker threads + * and used with rb_fiber_scheduler_blocking_operation_execute. + * + * @param[in] self The BlockingOperation VALUE to extract from + * @return The opaque struct pointer on success, NULL on error + * @note Experimental. + */ +rb_fiber_scheduler_blocking_operation_t *rb_fiber_scheduler_blocking_operation_extract(VALUE self); + +/** + * Execute blocking operation from handle (GVL not required). + * + * This function executes a blocking operation using the opaque handle + * obtained from rb_fiber_scheduler_blocking_operation_extract. + * It can be called from native threads without holding the GVL. + * + * @param[in] blocking_operation The opaque handle. + * @return 0 on success, -1 on error. + * @note Experimental. Can be called from any thread without holding the GVL + */ +int rb_fiber_scheduler_blocking_operation_execute(rb_fiber_scheduler_blocking_operation_t *blocking_operation); + +/** + * Cancel a blocking operation. + * + * This function cancels a blocking operation. If the operation is queued, + * it just marks it as cancelled. If it's executing, it marks it as cancelled + * and calls the unblock function to interrupt the operation. + * + * @param blocking_operation The opaque struct pointer + * @return 1 if unblock function was called, 0 if just marked cancelled, -1 on error + * @note Experimental. + */ +int rb_fiber_scheduler_blocking_operation_cancel(rb_fiber_scheduler_blocking_operation_t *blocking_operation); + /** * Defer the execution of the passed function to the scheduler. * @@ -411,9 +457,26 @@ struct rb_fiber_scheduler_blocking_operation_state { */ VALUE rb_fiber_scheduler_blocking_operation_wait(VALUE scheduler, void* (*function)(void *), void *data, rb_unblock_function_t *unblock_function, void *data2, int flags, struct rb_fiber_scheduler_blocking_operation_state *state); +/** + * Interrupt a fiber by raising an exception. You can construct an exception using `rb_make_exception`. + * + * This hook may be invoked by a different thread. + * + * @param[in] scheduler Target scheduler. + * @param[in] fiber The fiber to interrupt. + * @param[in] exception The exception to raise in the fiber. + * @return What `scheduler.fiber_interrupt` returns. + */ +VALUE rb_fiber_scheduler_fiber_interrupt(VALUE scheduler, VALUE fiber, VALUE exception); + /** * Create and schedule a non-blocking fiber. * + * @param[in] scheduler Target scheduler. + * @param[in] argc Number of arguments in argv. + * @param[in] argv Array of arguments to pass to the fiber. + * @param[in] kw_splat Whether to expand last argument as keywords. + * @return The created and scheduled fiber. */ VALUE rb_fiber_scheduler_fiber(VALUE scheduler, int argc, VALUE *argv, int kw_splat); diff --git a/include/ruby/intern.h b/include/ruby/intern.h index 48e4cd546e..8718169ce2 100644 --- a/include/ruby/intern.h +++ b/include/ruby/intern.h @@ -51,6 +51,7 @@ #include "ruby/internal/intern/re.h" #include "ruby/internal/intern/ruby.h" #include "ruby/internal/intern/select.h" +#include "ruby/internal/intern/set.h" #include "ruby/internal/intern/signal.h" #include "ruby/internal/intern/sprintf.h" #include "ruby/internal/intern/string.h" diff --git a/include/ruby/internal/abi.h b/include/ruby/internal/abi.h index e735a67564..0c99d93bf9 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 0 +#define RUBY_ABI_VERSION 2 /* Windows does not support weak symbols so ruby_abi_version will not exist * in the shared library. */ diff --git a/include/ruby/internal/core/rbasic.h b/include/ruby/internal/core/rbasic.h index a1477e2600..35af03f7c8 100644 --- a/include/ruby/internal/core/rbasic.h +++ b/include/ruby/internal/core/rbasic.h @@ -55,6 +55,12 @@ enum ruby_rvalue_flags { RVALUE_EMBED_LEN_MAX = RBIMPL_RVALUE_EMBED_LEN_MAX }; +#if (SIZEOF_VALUE < SIZEOF_UINT64_T) +#define RBASIC_SHAPE_ID_FIELD 1 +#else +#define RBASIC_SHAPE_ID_FIELD 0 +#endif + /** * Ruby object's base components. All Ruby objects have them in common. */ @@ -85,6 +91,10 @@ RBasic { */ const VALUE klass; +#if RBASIC_SHAPE_ID_FIELD + VALUE shape_id; +#endif + #ifdef __cplusplus public: RBIMPL_ATTR_CONSTEXPR(CXX11) @@ -100,6 +110,9 @@ RBasic { RBasic() : flags(RBIMPL_VALUE_NULL), klass(RBIMPL_VALUE_NULL) +#if RBASIC_SHAPE_ID_FIELD + , shape_id(RBIMPL_VALUE_NULL) +#endif { } #endif diff --git a/include/ruby/internal/core/rclass.h b/include/ruby/internal/core/rclass.h index b0b6bfc80c..6f78cc569b 100644 --- a/include/ruby/internal/core/rclass.h +++ b/include/ruby/internal/core/rclass.h @@ -58,7 +58,7 @@ enum ruby_rmodule_flags { * rb_mod_refine() has this flag set. This is the bit which controls * difference between normal inclusion versus refinements. */ - RMODULE_IS_REFINEMENT = RUBY_FL_USER3 + RMODULE_IS_REFINEMENT = RUBY_FL_USER1 }; struct RClass; /* Opaque, declared here for RCLASS() macro. */ diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index b576be779f..edf482267a 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -471,8 +471,7 @@ RBIMPL_SYMBOL_EXPORT_END() /** * Identical to #TypedData_Wrap_Struct, except it allocates a new data region * internally instead of taking an existing one. The allocation is done using - * ruby_calloc(). Hence it makes no sense for `data_type->function.dfree` to - * be anything other than ::RUBY_TYPED_DEFAULT_FREE. + * ruby_calloc(). * * @param klass Ruby level class of the object. * @param type Type name of the C struct. diff --git a/include/ruby/internal/fl_type.h b/include/ruby/internal/fl_type.h index 701118ef25..9e1f3dd15c 100644 --- a/include/ruby/internal/fl_type.h +++ b/include/ruby/internal/fl_type.h @@ -248,6 +248,21 @@ ruby_fl_type { RBIMPL_ATTR_DEPRECATED(("taintedness turned out to be a wrong idea.")) #elif defined(_MSC_VER) # pragma deprecated(RUBY_FL_TAINT) +#endif + + = 0, + + /** + * @deprecated This flag was an implementation detail that should never have + * no been exposed. Exists here for backwards + * compatibility only. You can safely forget about it. + */ + RUBY_FL_EXIVAR + +#if defined(RBIMPL_HAVE_ENUM_ATTRIBUTE) + RBIMPL_ATTR_DEPRECATED(("FL_EXIVAR is an outdated implementation detail, it shoudl be used.")) +#elif defined(_MSC_VER) +# pragma deprecated(RUBY_FL_EXIVAR) #endif = 0, @@ -286,18 +301,12 @@ ruby_fl_type { */ RUBY_FL_UNUSED9 = (1<<9), - /** - * This flag has something to do with instance variables. 3rd parties need - * not know, but there are several ways to store an object's instance - * variables. Objects with this flag use so-called "generic" backend - * storage. This distinction is purely an implementation detail. People - * need not be aware of this working behind-the-scene. - * - * @internal - * - * As of writing everything except ::RObject and RModule use this scheme. - */ - RUBY_FL_EXIVAR = (1<<10), + /** + * This flag is no longer in use + * + * @internal + */ + RUBY_FL_UNUSED10 = (1<<10), /** * This flag has something to do with data immutability. When this flag is @@ -399,7 +408,7 @@ enum { # pragma deprecated(RUBY_FL_DUPPED) #endif - = (int)RUBY_T_MASK | (int)RUBY_FL_EXIVAR + = (int)RUBY_T_MASK }; #undef RBIMPL_HAVE_ENUM_ATTRIBUTE @@ -433,10 +442,8 @@ RB_FL_ABLE(VALUE obj) if (RB_SPECIAL_CONST_P(obj)) { return false; } - else if (RB_TYPE_P(obj, RUBY_T_NODE)) { - return false; - } else { + RBIMPL_ASSERT_OR_ASSUME(!RB_TYPE_P(obj, RUBY_T_NODE)); return true; } } diff --git a/include/ruby/internal/gc.h b/include/ruby/internal/gc.h index 5ab3bb266e..19783f3023 100644 --- a/include/ruby/internal/gc.h +++ b/include/ruby/internal/gc.h @@ -785,9 +785,7 @@ rb_obj_written( RGENGC_LOGGING_OBJ_WRITTEN(a, oldv, b, filename, line); #endif - if (!RB_SPECIAL_CONST_P(b)) { - rb_gc_writebarrier(a, b); - } + rb_gc_writebarrier(a, b); return a; } diff --git a/include/ruby/internal/globals.h b/include/ruby/internal/globals.h index ec2e73521e..528371abbb 100644 --- a/include/ruby/internal/globals.h +++ b/include/ruby/internal/globals.h @@ -93,6 +93,7 @@ RUBY_EXTERN VALUE rb_cRandom; /**< `Random` class. */ RUBY_EXTERN VALUE rb_cRange; /**< `Range` class. */ RUBY_EXTERN VALUE rb_cRational; /**< `Rational` class. */ RUBY_EXTERN VALUE rb_cRegexp; /**< `Regexp` class. */ +RUBY_EXTERN VALUE rb_cSet; /**< `Set` class. */ RUBY_EXTERN VALUE rb_cStat; /**< `File::Stat` class. */ RUBY_EXTERN VALUE rb_cString; /**< `String` class. */ RUBY_EXTERN VALUE rb_cStruct; /**< `Struct` class. */ diff --git a/include/ruby/internal/intern/set.h b/include/ruby/internal/intern/set.h new file mode 100644 index 0000000000..f4ff8665e2 --- /dev/null +++ b/include/ruby/internal/intern/set.h @@ -0,0 +1,111 @@ +#ifndef RBIMPL_INTERN_SET_H /*-*-C++-*-vi:se ft=cpp:*/ +#define RBIMPL_INTERN_SET_H +/** + * @file + * @author Ruby developers + * @copyright This file is a part of the programming language Ruby. + * Permission is hereby granted, to either redistribute and/or + * modify this file, provided that the conditions mentioned in the + * file COPYING are met. Consult the file for details. + * @warning Symbols prefixed with either `RBIMPL` or `rbimpl` are + * implementation details. Don't take them as canon. They could + * rapidly appear then vanish. The name (path) of this header file + * is also an implementation detail. Do not expect it to persist + * at the place it is now. Developers are free to move it anywhere + * anytime at will. + * @note To ruby-core: remember that this header can be possibly + * recursively included from extension libraries written in C++. + * Do not expect for instance `__VA_ARGS__` is always available. + * We assume C99 for ruby itself but we don't assume languages of + * extension libraries. They could be written in C++98. + * @brief Public APIs related to ::rb_cSet. + */ +#include "ruby/internal/attr/nonnull.h" +#include "ruby/internal/dllexport.h" +#include "ruby/internal/value.h" + +RBIMPL_SYMBOL_EXPORT_BEGIN() + +/* set.c */ + +RBIMPL_ATTR_NONNULL(()) +/** + * Iterates over a set. Calls func with each element of the set and the + * argument given. func should return ST_CONTINUE, ST_STOP, or ST_DELETE. + * + * @param[in] set An instance of ::rb_cSet to iterate over. + * @param[in] func Callback function to yield. + * @param[in] arg Passed as-is to `func`. + * @exception rb_eRuntimeError `set` was tampered during iterating. + */ +void rb_set_foreach(VALUE set, int (*func)(VALUE element, VALUE arg), VALUE arg); + +/** + * Creates a new, empty set object. + * + * @return An allocated new instance of ::rb_cSet. + */ +VALUE rb_set_new(void); + +/** + * Identical to rb_set_new(), except it additionally specifies how many elements + * it is expected to contain. This way you can create a set that is large enough + * for your need. For large sets, it means it won't need to be reallocated + * much, improving performance. + * + * @param[in] capa Designed capacity of the set. + * @return An empty Set, whose capacity is `capa`. + */ +VALUE rb_set_new_capa(size_t capa); + +/** + * Whether the set contains the given element. + * + * @param[in] set Set to look into. + * @param[in] element Set element to look for. + * @return true if element is in the set, falst otherwise. + */ +bool rb_set_lookup(VALUE set, VALUE element); + +/** + * Adds element to set. + * + * @param[in] set Target set table to modify. + * @param[in] element Arbitrary Ruby object. + * @exception rb_eFrozenError `set` is frozen. + * @return true if element was not already in set, false otherwise + * @post `element` is in `set`. + */ +bool rb_set_add(VALUE set, VALUE element); + +/** + * Removes all entries from set. + * + * @param[out] set Target to clear. + * @exception rb_eFrozenError `set`is frozen. + * @return The passed `set` + * @post `set` has no elements. + */ +VALUE rb_set_clear(VALUE set); + +/** + * Removes the element from from set. + * + * @param[in] set Target set to modify. + * @param[in] element Key to delete. + * @retval true if element was already in set, false otherwise + * @post `set` does not have `element` as an element. + */ +bool rb_set_delete(VALUE set, VALUE element); + +/** + * Returns the number of elements in the set. + * + * @param[in] set A set object. + * @return The size of the set. + */ +size_t rb_set_size(VALUE set); + +RBIMPL_SYMBOL_EXPORT_END() + +#endif /* RBIMPL_INTERN_SET_H */ diff --git a/include/ruby/internal/stdbool.h b/include/ruby/internal/stdbool.h index 7f3e6dcf97..5d9026434b 100644 --- a/include/ruby/internal/stdbool.h +++ b/include/ruby/internal/stdbool.h @@ -31,17 +31,9 @@ # define __bool_true_false_are_defined # endif -#elif defined(HAVE_STDBOOL_H) -# /* Take stdbool.h definition. */ +#else +# /* Take stdbool.h definition. It exists since GCC 3.0 and VS 2015. */ # include - -#elif !defined(HAVE__BOOL) -typedef unsigned char _Bool; -# /* See also http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2229.htm */ -# define bool _Bool -# define true ((_Bool)+1) -# define false ((_Bool)+0) -# define __bool_true_false_are_defined #endif #endif /* RBIMPL_STDBOOL_H */ diff --git a/include/ruby/io.h b/include/ruby/io.h index 11d5ce5bfe..ed0967abad 100644 --- a/include/ruby/io.h +++ b/include/ruby/io.h @@ -66,6 +66,21 @@ RBIMPL_SYMBOL_EXPORT_BEGIN() struct stat; struct timeval; +#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC) +# define RUBY_USE_STATX 0 +#elif defined(HAVE_STRUCT_STATX_STX_BTIME) +# define RUBY_USE_STATX 1 +struct statx; +#else +# define RUBY_USE_STATX 0 +#endif + +#if RUBY_USE_STATX +typedef struct statx rb_io_stat_data; +#else +typedef struct stat rb_io_stat_data; +#endif + /** * Indicates that a timeout has occurred while performing an IO operation. */ @@ -1098,6 +1113,18 @@ int rb_io_read_pending(rb_io_t *fptr); */ VALUE rb_stat_new(const struct stat *st); +#if RUBY_USE_STATX +/** + * Constructs an instance of ::rb_cStat from the passed information. + * + * @param[in] st A stat. + * @return Allocated new instance of ::rb_cStat. + */ +VALUE rb_statx_new(const rb_io_stat_data *st); +#else +# define rb_statx_new rb_stat_new +#endif + /* gc.c */ RBIMPL_SYMBOL_EXPORT_END() diff --git a/include/ruby/win32.h b/include/ruby/win32.h index 80e1418a1a..31dc13e932 100644 --- a/include/ruby/win32.h +++ b/include/ruby/win32.h @@ -127,13 +127,26 @@ typedef unsigned int uintptr_t; typedef int clockid_t; #if defined(__MINGW32__) +/* I don't know why but these return some strange values. */ #undef CLOCK_PROCESS_CPUTIME_ID #undef CLOCK_THREAD_CPUTIME_ID #undef CLOCK_REALTIME_COARSE #endif -#if defined(HAVE_CLOCK_GETTIME) && !defined(CLOCK_REALTIME) -#define CLOCK_REALTIME 0 -#define CLOCK_MONOTONIC 1 + +/* defined in win32/win32.c for old versions */ +#if !defined(__MINGW32__) || !defined(HAVE_CLOCK_GETTIME) +# define HAVE_CLOCK_GETTIME 1 +# define NEED_CLOCK_GETTIME 1 +#endif +#if !defined(__MINGW32__) || !defined(HAVE_CLOCK_GETRES) +# define HAVE_CLOCK_GETRES 1 +# define NEED_CLOCK_GETRES 1 +#endif +#ifndef CLOCK_REALTIME +# define CLOCK_REALTIME 0 +#endif +#ifndef CLOCK_MONOTONIC +# define CLOCK_MONOTONIC 1 #endif #undef utime diff --git a/inits.c b/inits.c index 85b71f450e..29087a6306 100644 --- a/inits.c +++ b/inits.c @@ -63,12 +63,13 @@ rb_call_inits(void) CALL(ISeq); CALL(Thread); CALL(signal); + CALL(Cont); CALL(Fiber_Scheduler); CALL(process); - CALL(Cont); CALL(Rational); CALL(Complex); CALL(MemoryView); + CALL(pathname); CALL(version); CALL(vm_trace); CALL(vm_stack_canary); @@ -98,6 +99,7 @@ rb_call_builtin_inits(void) BUILTIN(ast); BUILTIN(trace_point); BUILTIN(pack); + BUILTIN(pathname_builtin); BUILTIN(warning); BUILTIN(array); BUILTIN(hash); diff --git a/insns.def b/insns.def index ba71e9f856..f21a1810a5 100644 --- a/insns.def +++ b/insns.def @@ -919,7 +919,7 @@ opt_new // The bookkeeping slot should be empty. RUBY_ASSERT(TOPN(argc + 1) == Qnil); - if (vm_method_cfunc_is(GET_ISEQ(), cd, val, rb_class_new_instance_pass_kw) && !(ruby_vm_event_flags & ISEQ_TRACE_EVENTS)) { + if (vm_method_cfunc_is(GET_ISEQ(), cd, val, rb_class_new_instance_pass_kw)) { RB_DEBUG_COUNTER_INC(opt_new_hit); val = rb_obj_alloc(val); TOPN(argc) = val; @@ -996,6 +996,7 @@ opt_nil_p (CALL_DATA cd) (VALUE recv) (VALUE val) +// attr bool zjit_profile = true; { val = vm_opt_nil_p(GET_ISEQ(), cd, recv); @@ -1480,6 +1481,7 @@ opt_and (CALL_DATA cd) (VALUE recv, VALUE obj) (VALUE val) +// attr bool zjit_profile = true; { val = vm_opt_and(recv, obj); @@ -1494,6 +1496,7 @@ opt_or (CALL_DATA cd) (VALUE recv, VALUE obj) (VALUE val) +// attr bool zjit_profile = true; { val = vm_opt_or(recv, obj); diff --git a/internal/class.h b/internal/class.h index 82f8f0e9dc..f8cfba3fd9 100644 --- a/internal/class.h +++ b/internal/class.h @@ -79,7 +79,7 @@ struct rb_cvar_class_tbl_entry { struct rb_classext_struct { const rb_namespace_t *ns; VALUE super; - VALUE *fields; // Fields are either ivar or other internal properties stored inline + VALUE fields_obj; // Fields are either ivar or other internal properties stored inline struct rb_id_table *m_tbl; struct rb_id_table *const_tbl; struct rb_id_table *callable_m_tbl; @@ -127,7 +127,6 @@ struct rb_classext_struct { bool shared_const_tbl : 1; bool iclass_is_origin : 1; bool iclass_origin_shared_mtbl : 1; - bool superclasses_owner : 1; bool superclasses_with_self : 1; VALUE classpath; }; @@ -137,7 +136,6 @@ STATIC_ASSERT(shape_max_variations, SHAPE_MAX_VARIATIONS < (1 << (sizeof(((rb_cl struct RClass { struct RBasic basic; - st_table *ns_classext_tbl; // ns_object -> (rb_classext_t *) VALUE object_id; /* * If ns_classext_tbl is NULL, then the prime classext is readable (because no other classext exists). @@ -145,16 +143,22 @@ struct RClass { */ }; -// Assert that classes can be embedded in heaps[2] (which has 160B slot size) -// On 32bit platforms there is no variable width allocation so it doesn't matter. -// TODO: restore this assertion after shrinking rb_classext_t -// STATIC_ASSERT(sizeof_rb_classext_t, sizeof(struct RClass) + sizeof(rb_classext_t) <= 4 * RVALUE_SIZE || SIZEOF_VALUE < SIZEOF_LONG_LONG); - struct RClass_and_rb_classext_t { struct RClass rclass; rb_classext_t classext; }; +#if SIZEOF_VALUE >= SIZEOF_LONG_LONG +// Assert that classes can be embedded in heaps[2] (which has 160B slot size) +// On 32bit platforms there is no variable width allocation so it doesn't matter. +STATIC_ASSERT(sizeof_rb_classext_t, sizeof(struct RClass_and_rb_classext_t) <= 4 * RVALUE_SIZE); +#endif + +struct RClass_namespaceable { + struct RClass_and_rb_classext_t base; + st_table *ns_classext_tbl; // ns_object -> (rb_classext_t *) +}; + static const uint16_t RCLASS_MAX_SUPERCLASS_DEPTH = ((uint16_t)-1); static inline bool RCLASS_SINGLETON_P(VALUE klass); @@ -172,11 +176,10 @@ static inline rb_classext_t * RCLASS_EXT_WRITABLE_IN_NS(VALUE obj, const rb_name static inline rb_classext_t * RCLASS_EXT_WRITABLE(VALUE obj); // Raw accessor -#define RCLASS_CLASSEXT_TBL(klass) (RCLASS(klass)->ns_classext_tbl) - #define RCLASSEXT_NS(ext) (ext->ns) #define RCLASSEXT_SUPER(ext) (ext->super) -#define RCLASSEXT_FIELDS(ext) (ext->fields) +#define RCLASSEXT_FIELDS(ext) (ext->fields_obj ? ROBJECT_FIELDS(ext->fields_obj) : NULL) +#define RCLASSEXT_FIELDS_OBJ(ext) (ext->fields_obj) #define RCLASSEXT_M_TBL(ext) (ext->m_tbl) #define RCLASSEXT_CONST_TBL(ext) (ext->const_tbl) #define RCLASSEXT_CALLABLE_M_TBL(ext) (ext->callable_m_tbl) @@ -191,14 +194,11 @@ static inline rb_classext_t * RCLASS_EXT_WRITABLE(VALUE obj); #define RCLASSEXT_REFINED_CLASS(ext) (ext->refined_class) // class.allocator/singleton_class.attached_object are not accessed directly via RCLASSEXT_* #define RCLASSEXT_INCLUDER(ext) (ext->as.iclass.includer) -#define RCLASSEXT_MAX_IV_COUNT(ext) (ext->max_iv_count) -#define RCLASSEXT_VARIATION_COUNT(ext) (ext->variation_count) #define RCLASSEXT_PERMANENT_CLASSPATH(ext) (ext->permanent_classpath) #define RCLASSEXT_CLONED(ext) (ext->cloned) #define RCLASSEXT_SHARED_CONST_TBL(ext) (ext->shared_const_tbl) #define RCLASSEXT_ICLASS_IS_ORIGIN(ext) (ext->iclass_is_origin) #define RCLASSEXT_ICLASS_ORIGIN_SHARED_MTBL(ext) (ext->iclass_origin_shared_mtbl) -#define RCLASSEXT_SUPERCLASSES_OWNER(ext) (ext->superclasses_owner) #define RCLASSEXT_SUPERCLASSES_WITH_SELF(ext) (ext->superclasses_with_self) #define RCLASSEXT_CLASSPATH(ext) (ext->classpath) @@ -209,7 +209,7 @@ static inline void RCLASSEXT_SET_INCLUDER(rb_classext_t *ext, VALUE klass, VALUE #define RCLASS_PRIME_NS(c) (RCLASS_EXT_PRIME(c)->ns) // To invalidate CC by inserting&invalidating method entry into tables containing the target cme // See clear_method_cache_by_id_in_class() -#define RCLASS_PRIME_FIELDS(c) (RCLASS_EXT_PRIME(c)->fields) +#define RCLASS_PRIME_FIELDS_OBJ(c) (RCLASS_EXT_PRIME(c)->fields_obj) #define RCLASS_PRIME_M_TBL(c) (RCLASS_EXT_PRIME(c)->m_tbl) #define RCLASS_PRIME_CONST_TBL(c) (RCLASS_EXT_PRIME(c)->const_tbl) #define RCLASS_PRIME_CALLABLE_M_TBL(c) (RCLASS_EXT_PRIME(c)->callable_m_tbl) @@ -227,24 +227,28 @@ static inline void RCLASSEXT_SET_INCLUDER(rb_classext_t *ext, VALUE klass, VALUE * so always those should be writable. */ #define RCLASS_CVC_TBL(c) (RCLASS_EXT_READABLE(c)->cvc_tbl) -#define RCLASS_SUPERCLASS_DEPTH(c) (RCLASS_EXT_READABLE(c)->superclass_depth) -#define RCLASS_SUPERCLASSES(c) (RCLASS_EXT_READABLE(c)->superclasses) -#define RCLASS_SUPERCLASSES_WITH_SELF_P(c) (RCLASS_EXT_READABLE(c)->superclasses_with_self) #define RCLASS_SUBCLASSES_X(c) (RCLASS_EXT_READABLE(c)->subclasses) #define RCLASS_SUBCLASSES_FIRST(c) (RCLASS_EXT_READABLE(c)->subclasses->head->next) #define RCLASS_ORIGIN(c) (RCLASS_EXT_READABLE(c)->origin_) #define RICLASS_IS_ORIGIN_P(c) (RCLASS_EXT_READABLE(c)->iclass_is_origin) -#define RCLASS_MAX_IV_COUNT(c) (RCLASS_EXT_READABLE(c)->max_iv_count) -#define RCLASS_VARIATION_COUNT(c) (RCLASS_EXT_READABLE(c)->variation_count) #define RCLASS_PERMANENT_CLASSPATH_P(c) (RCLASS_EXT_READABLE(c)->permanent_classpath) #define RCLASS_CLONED_P(c) (RCLASS_EXT_READABLE(c)->cloned) #define RCLASS_CLASSPATH(c) (RCLASS_EXT_READABLE(c)->classpath) +// Superclasses can't be changed after initialization +#define RCLASS_SUPERCLASS_DEPTH(c) (RCLASS_EXT_PRIME(c)->superclass_depth) +#define RCLASS_SUPERCLASSES(c) (RCLASS_EXT_PRIME(c)->superclasses) +#define RCLASS_SUPERCLASSES_WITH_SELF_P(c) (RCLASS_EXT_PRIME(c)->superclasses_with_self) + // namespaces don't make changes on these refined_class/attached_object/includer #define RCLASS_REFINED_CLASS(c) (RCLASS_EXT_PRIME(c)->refined_class) #define RCLASS_ATTACHED_OBJECT(c) (RCLASS_EXT_PRIME(c)->as.singleton_class.attached_object) #define RCLASS_INCLUDER(c) (RCLASS_EXT_PRIME(c)->as.iclass.includer) +// max IV count and variation count are just hints, so they don't need to be per-namespace +#define RCLASS_MAX_IV_COUNT(ext) (RCLASS_EXT_PRIME(ext)->max_iv_count) +#define RCLASS_VARIATION_COUNT(ext) (RCLASS_EXT_PRIME(ext)->variation_count) + // Writable classext entries (instead of RCLASS_SET_*) because member data will be operated directly #define RCLASS_WRITABLE_M_TBL(c) (RCLASS_EXT_WRITABLE(c)->m_tbl) #define RCLASS_WRITABLE_CONST_TBL(c) (RCLASS_EXT_WRITABLE(c)->const_tbl) @@ -255,11 +259,6 @@ static inline void RCLASSEXT_SET_INCLUDER(rb_classext_t *ext, VALUE klass, VALUE static inline void RCLASS_SET_SUPER(VALUE klass, VALUE super); static inline void RCLASS_WRITE_SUPER(VALUE klass, VALUE super); -static inline st_table * RCLASS_FIELDS_HASH(VALUE obj); -static inline st_table * RCLASS_WRITABLE_FIELDS_HASH(VALUE obj); -static inline uint32_t RCLASS_FIELDS_COUNT(VALUE obj); -static inline void RCLASS_SET_FIELDS_HASH(VALUE obj, const st_table *table); -static inline void RCLASS_WRITE_FIELDS_HASH(VALUE obj, const st_table *table); // TODO: rename RCLASS_SET_M_TBL_WORKAROUND (and _WRITE_) to RCLASS_SET_M_TBL with write barrier static inline void RCLASS_SET_M_TBL_WORKAROUND(VALUE klass, struct rb_id_table *table, bool check_promoted); static inline void RCLASS_WRITE_M_TBL_WORKAROUND(VALUE klass, struct rb_id_table *table, bool check_promoted); @@ -270,7 +269,7 @@ static inline void RCLASS_WRITE_CC_TBL(VALUE klass, struct rb_id_table *table); static inline void RCLASS_SET_CVC_TBL(VALUE klass, struct rb_id_table *table); static inline void RCLASS_WRITE_CVC_TBL(VALUE klass, struct rb_id_table *table); -static inline void RCLASS_WRITE_SUPERCLASSES(VALUE klass, size_t depth, VALUE *superclasses, bool owns_it, bool with_self); +static inline void RCLASS_WRITE_SUPERCLASSES(VALUE klass, size_t depth, VALUE *superclasses, bool with_self); static inline void RCLASS_SET_SUBCLASSES(VALUE klass, rb_subclass_anchor_t *anchor); static inline void RCLASS_WRITE_NS_SUPER_SUBCLASSES(VALUE klass, rb_ns_subclasses_t *ns_subclasses); static inline void RCLASS_WRITE_NS_MODULE_SUBCLASSES(VALUE klass, rb_ns_subclasses_t *ns_subclasses); @@ -288,16 +287,34 @@ static inline VALUE RCLASS_SET_ATTACHED_OBJECT(VALUE klass, VALUE attached_objec static inline void RCLASS_SET_INCLUDER(VALUE iclass, VALUE klass); static inline void RCLASS_SET_MAX_IV_COUNT(VALUE klass, attr_index_t count); -static inline void RCLASS_WRITE_MAX_IV_COUNT(VALUE klass, attr_index_t count); static inline void RCLASS_SET_CLONED(VALUE klass, bool cloned); static inline void RCLASS_SET_CLASSPATH(VALUE klass, VALUE classpath, bool permanent); static inline void RCLASS_WRITE_CLASSPATH(VALUE klass, VALUE classpath, bool permanent); #define RCLASS_IS_ROOT FL_USER0 -// 1 is for RUBY_FL_SINGLETON or RMODULE_ALLOCATED_BUT_NOT_INITIALIZED (see class.c) +// 1 is for RUBY_FL_SINGLETON or RMODULE_IS_REFINEMENT #define RCLASS_PRIME_CLASSEXT_WRITABLE FL_USER2 +#define RCLASS_IS_INITIALIZED FL_USER3 // 3 is RMODULE_IS_REFINEMENT for RMODULE -// 4-19: SHAPE_FLAG_MASK +#define RCLASS_NAMESPACEABLE FL_USER4 + +static inline st_table * +RCLASS_CLASSEXT_TBL(VALUE klass) +{ + if (FL_TEST_RAW(klass, RCLASS_NAMESPACEABLE)) { + struct RClass_namespaceable *ns_klass = (struct RClass_namespaceable *)klass; + return ns_klass->ns_classext_tbl; + } + return NULL; +} + +static inline void +RCLASS_SET_CLASSEXT_TBL(VALUE klass, st_table *tbl) +{ + RUBY_ASSERT(FL_TEST_RAW(klass, RCLASS_NAMESPACEABLE)); + struct RClass_namespaceable *ns_klass = (struct RClass_namespaceable *)klass; + ns_klass->ns_classext_tbl = tbl; +} /* class.c */ rb_classext_t * rb_class_duplicate_classext(rb_classext_t *orig, VALUE obj, const rb_namespace_t *ns); @@ -312,7 +329,8 @@ RCLASS_SET_NAMESPACE_CLASSEXT(VALUE obj, const rb_namespace_t *ns, rb_classext_t VM_ASSERT(ns->ns_object); VM_ASSERT(RCLASSEXT_NS(ext) == ns); if (!tbl) { - RCLASS_CLASSEXT_TBL(obj) = tbl = st_init_numtable_with_size(1); + tbl = st_init_numtable_with_size(1); + RCLASS_SET_CLASSEXT_TBL(obj, tbl); } if (rb_st_table_size(tbl) == 0) { first_set = 1; @@ -326,7 +344,7 @@ RCLASS_PRIME_CLASSEXT_READABLE_P(VALUE klass) { VM_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE) || RB_TYPE_P(klass, T_ICLASS)); // if the lookup table exists, then it means the prime classext is NOT directly readable. - return RCLASS_CLASSEXT_TBL(klass) == NULL; + return !FL_TEST_RAW(klass, RCLASS_NAMESPACEABLE) || RCLASS_CLASSEXT_TBL(klass) == NULL; } static inline bool @@ -408,12 +426,7 @@ RCLASS_EXT_WRITABLE_LOOKUP(VALUE obj, const rb_namespace_t *ns) if (ext) return ext; - if (!rb_shape_obj_too_complex_p(obj)) { - rb_evict_ivars_to_hash(obj); // fallback to ivptr for ivars from shapes - } - - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { // re-check the classext is not created to avoid the multi-thread race ext = RCLASS_EXT_TABLE_LOOKUP_INTERNAL(obj, ns); if (!ext) { @@ -424,7 +437,6 @@ RCLASS_EXT_WRITABLE_LOOKUP(VALUE obj, const rb_namespace_t *ns) } } } - RB_VM_LOCK_LEAVE(); return ext; } @@ -443,7 +455,7 @@ static inline rb_classext_t * RCLASS_EXT_WRITABLE(VALUE obj) { const rb_namespace_t *ns; - if (RCLASS_PRIME_CLASSEXT_WRITABLE_P(obj)) { + if (LIKELY(RCLASS_PRIME_CLASSEXT_WRITABLE_P(obj))) { return RCLASS_EXT_PRIME(obj); } // delay namespace loading to optimize for unmodified classes @@ -487,7 +499,7 @@ VALUE rb_class_set_super(VALUE klass, VALUE super); VALUE rb_class_boot(VALUE); VALUE rb_class_s_alloc(VALUE klass); VALUE rb_module_s_alloc(VALUE klass); -void rb_module_set_initialized(VALUE module); +void rb_class_set_initialized(VALUE klass); void rb_module_check_initializable(VALUE module); VALUE rb_make_metaclass(VALUE, VALUE); VALUE rb_include_class_new(VALUE, VALUE); @@ -531,58 +543,55 @@ RCLASS_WRITE_SUPER(VALUE klass, VALUE super) RB_OBJ_WRITE(klass, &RCLASSEXT_SUPER(RCLASS_EXT_WRITABLE(klass)), super); } -static inline st_table * -RCLASS_FIELDS_HASH(VALUE obj) +static inline VALUE +RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(VALUE obj) { RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE)); - RUBY_ASSERT(rb_shape_obj_too_complex_p(obj)); - return (st_table *)RCLASSEXT_FIELDS(RCLASS_EXT_READABLE(obj)); + rb_classext_t *ext = RCLASS_EXT_WRITABLE(obj); + if (!ext->fields_obj) { + RB_OBJ_WRITE(obj, &ext->fields_obj, rb_imemo_fields_new(rb_singleton_class(obj), 1)); + } + return ext->fields_obj; } -static inline st_table * -RCLASS_WRITABLE_FIELDS_HASH(VALUE obj) +static inline VALUE +RCLASS_WRITABLE_FIELDS_OBJ(VALUE obj) { RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE)); - RUBY_ASSERT(rb_shape_obj_too_complex_p(obj)); - return (st_table *)RCLASSEXT_FIELDS(RCLASS_EXT_WRITABLE(obj)); + return RCLASSEXT_FIELDS_OBJ(RCLASS_EXT_WRITABLE(obj)); } static inline void -RCLASS_SET_FIELDS_HASH(VALUE obj, const st_table *tbl) +RCLASSEXT_SET_FIELDS_OBJ(VALUE obj, rb_classext_t *ext, VALUE fields_obj) { RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE)); - RUBY_ASSERT(rb_shape_obj_too_complex_p(obj)); - RCLASSEXT_FIELDS(RCLASS_EXT_PRIME(obj)) = (VALUE *)tbl; + + RB_OBJ_ATOMIC_WRITE(obj, &ext->fields_obj, fields_obj); } static inline void -RCLASS_WRITE_FIELDS_HASH(VALUE obj, const st_table *tbl) +RCLASS_WRITABLE_SET_FIELDS_OBJ(VALUE obj, VALUE fields_obj) { RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE)); - RUBY_ASSERT(rb_shape_obj_too_complex_p(obj)); - RCLASSEXT_FIELDS(RCLASS_EXT_WRITABLE(obj)) = (VALUE *)tbl; + + RCLASSEXT_SET_FIELDS_OBJ(obj, RCLASS_EXT_WRITABLE(obj), fields_obj); } static inline uint32_t RCLASS_FIELDS_COUNT(VALUE obj) { RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE)); - if (rb_shape_obj_too_complex_p(obj)) { - uint32_t count; - // "Too complex" classes could have their IV hash mutated in - // parallel, so lets lock around getting the hash size. - RB_VM_LOCK_ENTER(); - { - count = (uint32_t)rb_st_table_size(RCLASS_FIELDS_HASH(obj)); + VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + if (fields_obj) { + if (rb_shape_obj_too_complex_p(fields_obj)) { + return (uint32_t)rb_st_table_size(rb_imemo_fields_complex_tbl(fields_obj)); + } + else { + return RSHAPE_LEN(RBASIC_SHAPE_ID(fields_obj)); } - RB_VM_LOCK_LEAVE(); - - return count; - } - else { - return RSHAPE(RCLASS_SHAPE_ID(obj))->next_field_index; } + return 0; } #define RCLASS_SET_M_TBL_EVEN_WHEN_PROMOTED(klass, table) RCLASS_SET_M_TBL_WORKAROUND(klass, table, false) @@ -658,6 +667,7 @@ RCLASS_SET_REFINED_CLASS(VALUE klass, VALUE refined) static inline rb_alloc_func_t RCLASS_ALLOCATOR(VALUE klass) { + RUBY_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_ICLASS)); if (RCLASS_SINGLETON_P(klass) || RB_TYPE_P(klass, T_ICLASS)) { return 0; } @@ -667,7 +677,8 @@ RCLASS_ALLOCATOR(VALUE klass) static inline void RCLASS_SET_ALLOCATOR(VALUE klass, rb_alloc_func_t allocator) { - assert(!RCLASS_SINGLETON_P(klass)); + RUBY_ASSERT(RB_TYPE_P(klass, T_CLASS)); + RUBY_ASSERT(!RCLASS_SINGLETON_P(klass)); RCLASS_EXT_PRIME(klass)->as.class.allocator = allocator; // Allocator is set only on the initial definition } @@ -714,14 +725,13 @@ RCLASS_SET_INCLUDER(VALUE iclass, VALUE klass) } static inline void -RCLASS_WRITE_SUPERCLASSES(VALUE klass, size_t depth, VALUE *superclasses, bool owns_it, bool with_self) +RCLASS_WRITE_SUPERCLASSES(VALUE klass, size_t depth, VALUE *superclasses, bool with_self) { RUBY_ASSERT(depth <= RCLASS_MAX_SUPERCLASS_DEPTH); - rb_classext_t *ext = RCLASS_EXT_WRITABLE(klass); + rb_classext_t *ext = RCLASS_EXT_PRIME(klass); RCLASSEXT_SUPERCLASS_DEPTH(ext) = depth; RCLASSEXT_SUPERCLASSES(ext) = superclasses; - RCLASSEXT_SUPERCLASSES_OWNER(ext) = owns_it; RCLASSEXT_SUPERCLASSES_WITH_SELF(ext) = with_self; } @@ -784,13 +794,7 @@ RCLASS_SET_ATTACHED_OBJECT(VALUE klass, VALUE attached_object) static inline void RCLASS_SET_MAX_IV_COUNT(VALUE klass, attr_index_t count) { - RCLASSEXT_MAX_IV_COUNT(RCLASS_EXT_PRIME(klass)) = count; -} - -static inline void -RCLASS_WRITE_MAX_IV_COUNT(VALUE klass, attr_index_t count) -{ - RCLASSEXT_MAX_IV_COUNT(RCLASS_EXT_WRITABLE(klass)) = count; + RCLASS_MAX_IV_COUNT(klass) = count; } static inline void @@ -799,4 +803,11 @@ RCLASS_SET_CLONED(VALUE klass, bool cloned) RCLASSEXT_CLONED(RCLASS_EXT_PRIME(klass)) = cloned; } +static inline bool +RCLASS_INITIALIZED_P(VALUE klass) +{ + VM_ASSERT(RB_TYPE_P(klass, T_CLASS) || RB_TYPE_P(klass, T_MODULE)); + return FL_TEST_RAW(klass, RCLASS_IS_INITIALIZED); +} + #endif /* INTERNAL_CLASS_H */ diff --git a/internal/concurrent_set.h b/internal/concurrent_set.h new file mode 100644 index 0000000000..d0f546b888 --- /dev/null +++ b/internal/concurrent_set.h @@ -0,0 +1,21 @@ +#ifndef RUBY_RACTOR_SAFE_TABLE_H +#define RUBY_RACTOR_SAFE_TABLE_H + +#include "ruby/ruby.h" + +typedef VALUE (*rb_concurrent_set_hash_func)(VALUE key); +typedef bool (*rb_concurrent_set_cmp_func)(VALUE a, VALUE b); +typedef VALUE (*rb_concurrent_set_create_func)(VALUE key, void *data); + +struct rb_concurrent_set_funcs { + rb_concurrent_set_hash_func hash; + rb_concurrent_set_cmp_func cmp; + rb_concurrent_set_create_func create; +}; + +VALUE rb_concurrent_set_new(const struct rb_concurrent_set_funcs *funcs, int capacity); +VALUE rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data); +VALUE rb_concurrent_set_delete_by_identity(VALUE set_obj, VALUE key); +void rb_concurrent_set_foreach_with_replace(VALUE set_obj, int (*callback)(VALUE *key, void *data), void *data); + +#endif diff --git a/internal/encoding.h b/internal/encoding.h index 29d7dc5c2d..c2ffaf4514 100644 --- a/internal/encoding.h +++ b/internal/encoding.h @@ -11,7 +11,6 @@ #include "ruby/ruby.h" /* for ID */ #include "ruby/encoding.h" /* for rb_encoding */ -#define rb_enc_autoload_p(enc) (!rb_enc_mbmaxlen(enc)) #define rb_is_usascii_enc(enc) ((enc) == rb_usascii_encoding()) #define rb_is_ascii8bit_enc(enc) ((enc) == rb_ascii8bit_encoding()) #define rb_is_locale_enc(enc) ((enc) == rb_locale_encoding()) @@ -24,6 +23,7 @@ rb_encoding *rb_enc_check_str(VALUE str1, VALUE str2); int rb_encdb_replicate(const char *alias, const char *orig); int rb_encdb_alias(const char *alias, const char *orig); int rb_enc_autoload(rb_encoding *enc); +bool rb_enc_autoload_p(rb_encoding *enc); int rb_encdb_dummy(const char *name); void rb_encdb_declare(const char *name); void rb_enc_set_base(const char *name, const char *orig); diff --git a/internal/error.h b/internal/error.h index 5d53f96b8e..de189698b8 100644 --- a/internal/error.h +++ b/internal/error.h @@ -241,4 +241,11 @@ rb_typeddata_is_instance_of_inline(VALUE obj, const rb_data_type_t *data_type) return RB_TYPE_P(obj, T_DATA) && RTYPEDDATA_P(obj) && (RTYPEDDATA_TYPE(obj) == data_type); } +typedef enum { + rb_stack_overflow_prevention = 0, // VM stack overflow or about to machine stack overflow + rb_stack_overflow_signal = 1, // machine stack overflow but may be recoverable + rb_stack_overflow_fatal = 2, // fatal machine stack overflow +} ruby_stack_overflow_critical_level; +NORETURN(void rb_ec_stack_overflow(struct rb_execution_context_struct *ec, ruby_stack_overflow_critical_level crit)); + #endif /* INTERNAL_ERROR_H */ diff --git a/internal/gc.h b/internal/gc.h index 06103ca25f..f0dc04fc58 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -264,6 +264,26 @@ int rb_gc_modular_gc_loaded_p(void); RUBY_SYMBOL_EXPORT_END +static inline VALUE +rb_obj_atomic_write( + VALUE a, VALUE *slot, VALUE b, + RBIMPL_ATTR_MAYBE_UNUSED() + const char *filename, + RBIMPL_ATTR_MAYBE_UNUSED() + int line) +{ +#ifdef RGENGC_LOGGING_WRITE + RGENGC_LOGGING_WRITE(a, slot, b, filename, line); +#endif + + RUBY_ATOMIC_VALUE_SET(*slot, b); + + rb_obj_written(a, RUBY_Qundef /* ignore `oldv' now */, b, filename, line); + return a; +} +#define RB_OBJ_ATOMIC_WRITE(old, slot, young) \ + RBIMPL_CAST(rb_obj_atomic_write((VALUE)(old), (VALUE *)(slot), (VALUE)(young), __FILE__, __LINE__)) + int rb_ec_stack_check(struct rb_execution_context_struct *ec); void rb_gc_writebarrier_remember(VALUE obj); const char *rb_obj_info(VALUE obj); diff --git a/internal/imemo.h b/internal/imemo.h index 305d12d240..a7e01f31e1 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -42,6 +42,7 @@ enum imemo_type { imemo_callinfo = 11, imemo_callcache = 12, imemo_constcache = 13, + imemo_fields = 14, }; /* CREF (Class REFerence) is defined in method.h */ @@ -148,9 +149,7 @@ static inline void MEMO_V1_SET(struct MEMO *m, VALUE v); static inline void MEMO_V2_SET(struct MEMO *m, VALUE v); size_t rb_imemo_memsize(VALUE obj); -void rb_cc_table_mark(VALUE klass); void rb_imemo_mark_and_move(VALUE obj, bool reference_updating); -void rb_cc_table_free(VALUE klass); void rb_cc_tbl_free(struct rb_id_table *cc_tbl, VALUE klass); void rb_imemo_free(VALUE obj); @@ -257,4 +256,59 @@ MEMO_V2_SET(struct MEMO *m, VALUE v) RB_OBJ_WRITE(m, &m->v2, v); } +struct rb_fields { + struct RBasic basic; + union { + struct { + VALUE fields[1]; + } embed; + struct { + VALUE *ptr; + } external; + struct { + // Note: the st_table could be embedded, but complex T_CLASS should be rare to + // non-existent, so not really worth the trouble. + st_table *table; + } complex; + } as; +}; + +#define OBJ_FIELD_EXTERNAL IMEMO_FL_USER0 +#define IMEMO_OBJ_FIELDS(fields) ((struct rb_fields *)fields) + +VALUE rb_imemo_fields_new(VALUE klass, size_t capa); +VALUE rb_imemo_fields_new_complex(VALUE klass, size_t capa); +VALUE rb_imemo_fields_new_complex_tbl(VALUE klass, st_table *tbl); +VALUE rb_imemo_fields_clone(VALUE fields_obj); +void rb_imemo_fields_clear(VALUE fields_obj); + +static inline VALUE * +rb_imemo_fields_ptr(VALUE obj_fields) +{ + if (!obj_fields) { + return NULL; + } + + RUBY_ASSERT(IMEMO_TYPE_P(obj_fields, imemo_fields)); + + if (RB_UNLIKELY(FL_TEST_RAW(obj_fields, OBJ_FIELD_EXTERNAL))) { + return IMEMO_OBJ_FIELDS(obj_fields)->as.external.ptr; + } + else { + return IMEMO_OBJ_FIELDS(obj_fields)->as.embed.fields; + } +} + +static inline st_table * +rb_imemo_fields_complex_tbl(VALUE obj_fields) +{ + if (!obj_fields) { + return NULL; + } + + RUBY_ASSERT(IMEMO_TYPE_P(obj_fields, imemo_fields)); + + return IMEMO_OBJ_FIELDS(obj_fields)->as.complex.table; +} + #endif /* INTERNAL_IMEMO_H */ diff --git a/internal/inits.h b/internal/inits.h index 03de289dd4..e618d87cc3 100644 --- a/internal/inits.h +++ b/internal/inits.h @@ -25,6 +25,9 @@ int Init_enc_set_filesystem_encoding(void); /* newline.c */ void Init_newline(void); +/* namespace.c */ +void Init_enable_namespace(void); + /* vm.c */ void Init_BareVM(void); void Init_vm_objects(void); diff --git a/internal/io.h b/internal/io.h index 55a8a1a175..e6a741ee71 100644 --- a/internal/io.h +++ b/internal/io.h @@ -15,6 +15,7 @@ struct rb_io; #include "ruby/io.h" /* for rb_io_t */ #include "ccan/list/list.h" +#include "serial.h" #define IO_WITHOUT_GVL(func, arg) rb_nogvl(func, arg, RUBY_UBF_IO, 0, RB_NOGVL_OFFLOAD_SAFE) #define IO_WITHOUT_GVL_INT(func, arg) (int)(VALUE)IO_WITHOUT_GVL(func, arg) @@ -24,7 +25,7 @@ struct rb_io_blocking_operation { // The linked list data structure. struct ccan_list_node list; - // The execution context of the blocking operation: + // The execution context of the blocking operation. struct rb_execution_context_struct *ec; }; @@ -130,6 +131,9 @@ struct rb_io { struct ccan_list_head blocking_operations; struct rb_execution_context_struct *closing_ec; VALUE wakeup_mutex; + + // The fork generation of the the blocking operations list. + rb_serial_t fork_generation; }; /* io.c */ diff --git a/internal/namespace.h b/internal/namespace.h index ad1507b50c..4cdfbc305f 100644 --- a/internal/namespace.h +++ b/internal/namespace.h @@ -51,7 +51,15 @@ typedef struct rb_namespace_struct rb_namespace_t; #define NAMESPACE_CC(cc) (cc ? NAMESPACE_METHOD_ENTRY(cc->cme_) : NULL) #define NAMESPACE_CC_ENTRIES(ccs) (ccs ? NAMESPACE_METHOD_ENTRY(ccs->cme) : NULL) -int rb_namespace_available(void); +RUBY_EXTERN bool ruby_namespace_enabled; +RUBY_EXTERN bool ruby_namespace_init_done; + +static inline bool +rb_namespace_available(void) +{ + return ruby_namespace_enabled; +} + void rb_namespace_enable_builtin(void); void rb_namespace_disable_builtin(void); void rb_namespace_push_loading_namespace(const rb_namespace_t *); @@ -74,5 +82,5 @@ VALUE rb_namespace_exec(const rb_namespace_t *ns, namespace_exec_func *func, VAL VALUE rb_namespace_local_extension(VALUE namespace, VALUE fname, VALUE path); void rb_initialize_main_namespace(void); - +void rb_namespace_init_done(void); #endif /* INTERNAL_NAMESPACE_H */ diff --git a/internal/sanitizers.h b/internal/sanitizers.h index 279cbbe069..8e6e87ddc8 100644 --- a/internal/sanitizers.h +++ b/internal/sanitizers.h @@ -127,6 +127,7 @@ asan_poison_memory_region(const volatile void *ptr, size_t size) #define asan_poison_object_if(ptr, obj) ((void)(ptr), (void)(obj)) #endif +#ifdef RUBY_ASAN_ENABLED RUBY_SYMBOL_EXPORT_BEGIN /** * This is a variant of asan_poison_memory_region that takes a VALUE. @@ -153,6 +154,11 @@ void *rb_asan_poisoned_object_p(VALUE obj); void rb_asan_unpoison_object(VALUE obj, bool newobj_p); RUBY_SYMBOL_EXPORT_END +#else +# define rb_asan_poison_object(obj) ((void)obj) +# define rb_asan_poisoned_object_p(obj) ((void)obj, NULL) +# define rb_asan_unpoison_object(obj, newobj_p) ((void)obj, (void)newobj_p) +#endif /** * This function asserts that a (formally poisoned) memory region from ptr to diff --git a/internal/set_table.h b/internal/set_table.h index 7c03de2060..3cb9c64349 100644 --- a/internal/set_table.h +++ b/internal/set_table.h @@ -37,24 +37,26 @@ size_t rb_set_table_size(const struct set_table *tbl); set_table *rb_set_init_table_with_size(set_table *tab, const struct st_hash_type *, st_index_t); #define set_init_numtable rb_set_init_numtable set_table *rb_set_init_numtable(void); -#define set_delete rb_set_delete -int rb_set_delete(set_table *, st_data_t *); /* returns 0:notfound 1:deleted */ +#define set_init_numtable_with_size rb_set_init_numtable_with_size +set_table *rb_set_init_numtable_with_size(st_index_t size); +#define set_table_delete rb_set_table_delete +int rb_set_table_delete(set_table *, st_data_t *); /* returns 0:notfound 1:deleted */ #define set_insert rb_set_insert int rb_set_insert(set_table *, st_data_t); -#define set_lookup rb_set_lookup -int rb_set_lookup(set_table *, st_data_t); +#define set_table_lookup rb_set_table_lookup +int rb_set_table_lookup(set_table *, st_data_t); #define set_foreach_with_replace rb_set_foreach_with_replace int rb_set_foreach_with_replace(set_table *tab, set_foreach_check_callback_func *func, set_update_callback_func *replace, st_data_t arg); -#define set_foreach rb_set_foreach -int rb_set_foreach(set_table *, set_foreach_callback_func *, st_data_t); +#define set_table_foreach rb_set_table_foreach +int rb_set_table_foreach(set_table *, set_foreach_callback_func *, st_data_t); #define set_foreach_check rb_set_foreach_check int rb_set_foreach_check(set_table *, set_foreach_check_callback_func *, st_data_t, st_data_t); #define set_keys rb_set_keys st_index_t rb_set_keys(set_table *table, st_data_t *keys, st_index_t size); #define set_free_table rb_set_free_table void rb_set_free_table(set_table *); -#define set_clear rb_set_clear -void rb_set_clear(set_table *); +#define set_table_clear rb_set_table_clear +void rb_set_table_clear(set_table *); #define set_copy rb_set_copy set_table *rb_set_copy(set_table *new_table, set_table *old_table); #define set_memsize rb_set_memsize diff --git a/internal/signal.h b/internal/signal.h index 2363bf412c..904747e226 100644 --- a/internal/signal.h +++ b/internal/signal.h @@ -19,6 +19,7 @@ void (*ruby_posix_signal(int, void (*)(int)))(int); RUBY_SYMBOL_EXPORT_BEGIN /* signal.c (export) */ +void rb_signal_atfork(void); RUBY_SYMBOL_EXPORT_END #endif /* INTERNAL_SIGNAL_H */ diff --git a/internal/string.h b/internal/string.h index 50561924f2..d6fea62061 100644 --- a/internal/string.h +++ b/internal/string.h @@ -30,6 +30,7 @@ enum ruby_rstring_private_flags { #endif /* string.c */ +VALUE rb_str_dup_m(VALUE str); VALUE rb_fstring(VALUE); VALUE rb_fstring_cstr(const char *str); VALUE rb_fstring_enc_new(const char *ptr, long len, rb_encoding *enc); diff --git a/internal/thread.h b/internal/thread.h index 5406a617e4..928126c3b0 100644 --- a/internal/thread.h +++ b/internal/thread.h @@ -72,6 +72,9 @@ void *rb_thread_prevent_fork(void *(*func)(void *), void *data); /* for ext/sock VALUE rb_thread_io_blocking_region(struct rb_io *io, rb_blocking_function_t *func, void *data1); VALUE rb_thread_io_blocking_call(struct rb_io *io, rb_blocking_function_t *func, void *data1, int events); +// Invoke the given function, with the specified argument, in a way that `IO#close` from another execution context can interrupt it. +VALUE rb_thread_io_blocking_operation(VALUE self, VALUE(*function)(VALUE), VALUE argument); + /* thread.c (export) */ int ruby_thread_has_gvl_p(void); /* for ext/fiddle/closure.c */ @@ -80,6 +83,8 @@ RUBY_SYMBOL_EXPORT_END int rb_threadptr_execute_interrupts(struct rb_thread_struct *th, int blocking_timing); bool rb_thread_mn_schedulable(VALUE thread); +bool rb_thread_resolve_unblock_function(rb_unblock_function_t **unblock_function, void **data2, struct rb_thread_struct *thread); + // interrupt exec typedef VALUE (rb_interrupt_exec_func_t)(void *data); @@ -87,6 +92,7 @@ typedef VALUE (rb_interrupt_exec_func_t)(void *data); enum rb_interrupt_exec_flag { rb_interrupt_exec_flag_none = 0x00, rb_interrupt_exec_flag_value_data = 0x01, + rb_interrupt_exec_flag_new_thread = 0x02, }; // interrupt the target_th and run func. diff --git a/internal/variable.h b/internal/variable.h index d2432fe22e..bbf3243fe9 100644 --- a/internal/variable.h +++ b/internal/variable.h @@ -13,12 +13,11 @@ #include "constant.h" /* for rb_const_entry_t */ #include "ruby/internal/stdbool.h" /* for bool */ #include "ruby/ruby.h" /* for VALUE */ -#include "shape.h" /* for rb_shape_t */ +#include "shape.h" /* for shape_id_t */ /* variable.c */ void rb_gc_mark_global_tbl(void); void rb_gc_update_global_tbl(void); -size_t rb_generic_ivar_memsize(VALUE); VALUE rb_search_class_path(VALUE); VALUE rb_attr_delete(VALUE, ID); void rb_autoload_str(VALUE mod, ID id, VALUE file); @@ -47,21 +46,20 @@ void rb_gvar_namespace_ready(const char *name); */ VALUE rb_mod_set_temporary_name(VALUE, VALUE); -struct gen_fields_tbl; -int rb_gen_fields_tbl_get(VALUE obj, ID id, struct gen_fields_tbl **fields_tbl); +int rb_gen_fields_tbl_get(VALUE obj, ID id, VALUE *fields_obj); void rb_obj_copy_ivs_to_hash_table(VALUE obj, st_table *table); void rb_obj_init_too_complex(VALUE obj, st_table *table); void rb_evict_ivars_to_hash(VALUE obj); -void rb_evict_fields_to_hash(VALUE obj); +shape_id_t rb_evict_fields_to_hash(VALUE obj); VALUE rb_obj_field_get(VALUE obj, shape_id_t target_shape_id); void rb_ivar_set_internal(VALUE obj, ID id, VALUE val); -void rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val); +void rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE val); RUBY_SYMBOL_EXPORT_BEGIN /* variable.c (export) */ void rb_mark_generic_ivar(VALUE obj); VALUE rb_const_missing(VALUE klass, VALUE name); -int rb_class_ivar_set(VALUE klass, ID vid, VALUE value); +bool rb_class_ivar_set(VALUE klass, ID vid, VALUE value); void rb_fields_tbl_copy(VALUE dst, VALUE src); RUBY_SYMBOL_EXPORT_END @@ -70,7 +68,7 @@ VALUE rb_gvar_get(ID); VALUE rb_gvar_set(ID, VALUE); VALUE rb_gvar_defined(ID); void rb_const_warn_if_deprecated(const rb_const_entry_t *, VALUE, ID); -void rb_ensure_iv_list_size(VALUE obj, uint32_t len, uint32_t newsize); +void rb_ensure_iv_list_size(VALUE obj, uint32_t current_len, uint32_t newsize); attr_index_t rb_obj_ivar_set(VALUE obj, ID id, VALUE val); #endif /* INTERNAL_VARIABLE_H */ diff --git a/internal/vm.h b/internal/vm.h index d1ee437cdc..3ee958a020 100644 --- a/internal/vm.h +++ b/internal/vm.h @@ -121,7 +121,6 @@ int rb_get_node_id_from_frame_info(VALUE obj); const struct rb_iseq_struct *rb_get_iseq_from_frame_info(VALUE obj); VALUE rb_ec_backtrace_object(const struct rb_execution_context_struct *ec); -void rb_backtrace_use_iseq_first_lineno_for_last_location(VALUE self); #define RUBY_DTRACE_CREATE_HOOK(name, arg) \ RUBY_DTRACE_HOOK(name##_CREATE, arg) diff --git a/io.c b/io.c index a01750a70d..9dcfff76a3 100644 --- a/io.c +++ b/io.c @@ -5692,8 +5692,13 @@ rb_io_memsize(const rb_io_t *io) if (io->writeconv) size += rb_econv_memsize(io->writeconv); struct rb_io_blocking_operation *blocking_operation = 0; - ccan_list_for_each(&io->blocking_operations, blocking_operation, list) { - size += sizeof(struct rb_io_blocking_operation); + + // Validate the fork generation of the IO object. If the IO object fork generation is different, the list of blocking operations is not valid memory. See `rb_io_blocking_operations` for the exact semantics. + rb_serial_t fork_generation = GET_VM()->fork_gen; + if (io->fork_generation == fork_generation) { + ccan_list_for_each(&io->blocking_operations, blocking_operation, list) { + size += sizeof(struct rb_io_blocking_operation); + } } return size; @@ -8570,6 +8575,7 @@ rb_io_init_copy(VALUE dest, VALUE io) ccan_list_head_init(&fptr->blocking_operations); fptr->closing_ec = NULL; fptr->wakeup_mutex = Qnil; + fptr->fork_generation = GET_VM()->fork_gen; if (!NIL_P(orig->pathv)) fptr->pathv = orig->pathv; fptr_copy_finalizer(fptr, orig); @@ -9311,6 +9317,7 @@ rb_io_open_descriptor(VALUE klass, int descriptor, int mode, VALUE path, VALUE t ccan_list_head_init(&io->blocking_operations); io->closing_ec = NULL; io->wakeup_mutex = Qnil; + io->fork_generation = GET_VM()->fork_gen; if (encoding) { io->encs = *encoding; @@ -9454,6 +9461,7 @@ rb_io_fptr_new(void) ccan_list_head_init(&fp->blocking_operations); fp->closing_ec = NULL; fp->wakeup_mutex = Qnil; + fp->fork_generation = GET_VM()->fork_gen; return fp; } @@ -9587,6 +9595,7 @@ io_initialize(VALUE io, VALUE fnum, VALUE vmode, VALUE opt) ccan_list_head_init(&fp->blocking_operations); fp->closing_ec = NULL; fp->wakeup_mutex = Qnil; + fp->fork_generation = GET_VM()->fork_gen; clear_codeconv(fp); io_check_tty(fp); if (fileno(stdin) == fd) @@ -9920,7 +9929,7 @@ io_event_from_value(VALUE value) /* * call-seq: * io.wait(events, timeout) -> event mask, false or nil - * io.wait(timeout = nil, mode = :read) -> self, true, or false + * io.wait(*event_symbols[, timeout]) -> self, true, or false * * Waits until the IO becomes ready for the specified events and returns the * subset of events that become ready, or a falsy value when times out. @@ -9928,10 +9937,14 @@ io_event_from_value(VALUE value) * The events can be a bit mask of +IO::READABLE+, +IO::WRITABLE+ or * +IO::PRIORITY+. * - * Returns an event mask (truthy value) immediately when buffered data is available. + * Returns an event mask (truthy value) immediately when buffered data is + * available. * - * Optional parameter +mode+ is one of +:read+, +:write+, or - * +:read_write+. + * The second form: if one or more event symbols (+:read+, +:write+, or + * +:read_write+) are passed, the event mask is the bit OR of the bitmask + * corresponding to those symbols. In this form, +timeout+ is optional, the + * order of the arguments is arbitrary, and returns +io+ if any of the + * events is ready. */ static VALUE @@ -9941,10 +9954,6 @@ io_wait(int argc, VALUE *argv, VALUE io) enum rb_io_event events = 0; int return_io = 0; - // The documented signature for this method is actually incorrect. - // A single timeout is allowed in any position, and multiple symbols can be given. - // Whether this is intentional or not, I don't know, and as such I consider this to - // be a legacy/slow path. if (argc != 2 || (RB_SYMBOL_P(argv[0]) || RB_SYMBOL_P(argv[1]))) { // We'd prefer to return the actual mask, but this form would return the io itself: return_io = 1; @@ -10659,7 +10668,7 @@ argf_readlines(int argc, VALUE *argv, VALUE argf) * $ `date` # => "Wed Apr 9 08:56:30 CDT 2003\n" * $ `echo oops && exit 99` # => "oops\n" * $ $? # => # - * $ $?.status # => 99> + * $ $?.exitstatus # => 99 * * The built-in syntax %x{...} uses this method. * @@ -14909,7 +14918,7 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y) * - \File +t.rb+: * * p "ARGV: #{ARGV}" - * p "Line: #{ARGF.read}" # Read everything from all specified streams. + * p "Read: #{ARGF.read}" # Read everything from all specified streams. * * - Command and output: * diff --git a/io_buffer.c b/io_buffer.c index 0534999319..96f13c364a 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -272,6 +272,21 @@ io_buffer_free(struct rb_io_buffer *buffer) #endif } +static void +rb_io_buffer_type_mark_and_move(void *_buffer) +{ + struct rb_io_buffer *buffer = _buffer; + if (buffer->source != Qnil) { + if (RB_TYPE_P(buffer->source, T_STRING)) { + // The `source` String has to be pinned, because the `base` may point to the embedded String content, + // which can be otherwise moved by GC compaction. + rb_gc_mark(buffer->source); + } else { + rb_gc_mark_and_move(&buffer->source); + } + } +} + static void rb_io_buffer_type_free(void *_buffer) { @@ -293,20 +308,16 @@ rb_io_buffer_type_size(const void *_buffer) return total; } -RUBY_REFERENCES(io_buffer_refs) = { - RUBY_REF_EDGE(struct rb_io_buffer, source), - RUBY_REF_END -}; - static const rb_data_type_t rb_io_buffer_type = { .wrap_struct_name = "IO::Buffer", .function = { - .dmark = RUBY_REFS_LIST_PTR(io_buffer_refs), + .dmark = rb_io_buffer_type_mark_and_move, .dfree = rb_io_buffer_type_free, .dsize = rb_io_buffer_type_size, + .dcompact = rb_io_buffer_type_mark_and_move, }, .data = NULL, - .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE | RUBY_TYPED_DECL_MARKING, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE, }; static inline enum rb_io_buffer_flags @@ -496,7 +507,9 @@ io_buffer_for_yield_instance(VALUE _arguments) arguments->instance = io_buffer_for_make_instance(arguments->klass, arguments->string, arguments->flags); - rb_str_locktmp(arguments->string); + if (!RB_OBJ_FROZEN(arguments->string)) { + rb_str_locktmp(arguments->string); + } return rb_yield(arguments->instance); } @@ -510,7 +523,9 @@ io_buffer_for_yield_instance_ensure(VALUE _arguments) rb_io_buffer_free(arguments->instance); } - rb_str_unlocktmp(arguments->string); + if (!RB_OBJ_FROZEN(arguments->string)) { + rb_str_unlocktmp(arguments->string); + } return Qnil; } @@ -2733,7 +2748,6 @@ io_buffer_blocking_region_ensure(VALUE _argument) static VALUE io_buffer_blocking_region(VALUE io, struct rb_io_buffer *buffer, rb_blocking_function_t *function, void *data) { - io = rb_io_get_io(io); struct rb_io *ioptr; RB_IO_POINTER(io, ioptr); @@ -2798,6 +2812,8 @@ io_buffer_read_internal(void *_argument) VALUE rb_io_buffer_read(VALUE self, VALUE io, size_t length, size_t offset) { + io = rb_io_get_io(io); + VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { VALUE result = rb_fiber_scheduler_io_read(scheduler, io, self, length, offset); @@ -2915,6 +2931,8 @@ io_buffer_pread_internal(void *_argument) VALUE rb_io_buffer_pread(VALUE self, VALUE io, rb_off_t from, size_t length, size_t offset) { + io = rb_io_get_io(io); + VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { VALUE result = rb_fiber_scheduler_io_pread(scheduler, io, from, self, length, offset); @@ -3035,6 +3053,8 @@ io_buffer_write_internal(void *_argument) VALUE rb_io_buffer_write(VALUE self, VALUE io, size_t length, size_t offset) { + io = rb_io_get_write_io(rb_io_get_io(io)); + VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { VALUE result = rb_fiber_scheduler_io_write(scheduler, io, self, length, offset); @@ -3099,6 +3119,7 @@ io_buffer_write(int argc, VALUE *argv, VALUE self) return rb_io_buffer_write(self, io, length, offset); } + struct io_buffer_pwrite_internal_argument { // The file descriptor to write to: int descriptor; @@ -3144,6 +3165,8 @@ io_buffer_pwrite_internal(void *_argument) VALUE rb_io_buffer_pwrite(VALUE self, VALUE io, rb_off_t from, size_t length, size_t offset) { + io = rb_io_get_write_io(rb_io_get_io(io)); + VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { VALUE result = rb_fiber_scheduler_io_pwrite(scheduler, io, from, self, length, offset); diff --git a/iseq.c b/iseq.c index 48e6ecb075..c0523f61d7 100644 --- a/iseq.c +++ b/iseq.c @@ -44,6 +44,7 @@ #include "builtin.h" #include "insns.inc" #include "insns_info.inc" +#include "zjit.h" VALUE rb_cISeq; static VALUE iseqw_new(const rb_iseq_t *iseq); @@ -112,7 +113,7 @@ remove_from_constant_cache(ID id, IC ic) if (rb_id_table_lookup(vm->constant_cache, id, &lookup_result)) { set_table *ics = (set_table *)lookup_result; - set_delete(ics, &ic_data); + set_table_delete(ics, &ic_data); if (ics->num_entries == 0 && // See comment in vm_track_constant_cache on why we need this check @@ -401,11 +402,17 @@ rb_iseq_mark_and_move(rb_iseq_t *iseq, bool reference_updating) if (reference_updating) { #if USE_YJIT rb_yjit_iseq_update_references(iseq); +#endif +#if USE_ZJIT + rb_zjit_iseq_update_references(body->zjit_payload); #endif } else { #if USE_YJIT rb_yjit_iseq_mark(body->yjit_payload); +#endif +#if USE_ZJIT + rb_zjit_iseq_mark(body->zjit_payload); #endif } } @@ -602,11 +609,11 @@ set_relation(rb_iseq_t *iseq, const rb_iseq_t *piseq) body->local_iseq = iseq; } else if (piseq) { - body->local_iseq = ISEQ_BODY(piseq)->local_iseq; + RB_OBJ_WRITE(iseq, &body->local_iseq, ISEQ_BODY(piseq)->local_iseq); } if (piseq) { - body->parent_iseq = piseq; + RB_OBJ_WRITE(iseq, &body->parent_iseq, piseq); } if (type == ISEQ_TYPE_MAIN) { @@ -1327,6 +1334,15 @@ pm_iseq_compile_with_option(VALUE src, VALUE file, VALUE realpath, VALUE line, V ln = NUM2INT(line); StringValueCStr(file); + bool parse_file = false; + if (RB_TYPE_P(src, T_FILE)) { + parse_file = true; + src = rb_io_path(src); + } + else { + src = StringValue(src); + } + pm_parse_result_t result = { 0 }; pm_options_line_set(&result.options, NUM2INT(line)); pm_options_scopes_init(&result.options, 1); @@ -1349,16 +1365,15 @@ pm_iseq_compile_with_option(VALUE src, VALUE file, VALUE realpath, VALUE line, V VALUE script_lines; VALUE error; - if (RB_TYPE_P(src, T_FILE)) { - VALUE filepath = rb_io_path(src); - error = pm_load_parse_file(&result, filepath, ruby_vm_keep_script_lines ? &script_lines : NULL); - RB_GC_GUARD(filepath); + if (parse_file) { + error = pm_load_parse_file(&result, src, ruby_vm_keep_script_lines ? &script_lines : NULL); } else { - src = StringValue(src); error = pm_parse_string(&result, src, file, ruby_vm_keep_script_lines ? &script_lines : NULL); } + RB_GC_GUARD(src); + if (error == Qnil) { int error_state; iseq = pm_iseq_new_with_opt(&result.node, name, file, realpath, ln, NULL, 0, ISEQ_TYPE_TOP, &option, &error_state); @@ -1530,7 +1545,6 @@ iseqw_new(const rb_iseq_t *iseq) /* cache a wrapper object */ RB_OBJ_WRITE((VALUE)iseq, &iseq->wrapper, obj); - RB_OBJ_FREEZE((VALUE)iseq); return obj; } @@ -2919,7 +2933,7 @@ rb_estimate_iv_count(VALUE klass, const rb_iseq_t * initialize_iseq) attr_index_t count = (attr_index_t)rb_id_table_size(iv_names); VALUE superclass = rb_class_superclass(klass); - count += RCLASSEXT_MAX_IV_COUNT(RCLASS_EXT_READABLE(superclass)); + count += RCLASS_MAX_IV_COUNT(superclass); rb_id_table_free(iv_names); diff --git a/jit.c b/jit.c index d2147a9d7f..74a042d45d 100644 --- a/jit.c +++ b/jit.c @@ -173,6 +173,12 @@ rb_get_iseq_body_local_iseq(const rb_iseq_t *iseq) return iseq->body->local_iseq; } +const rb_iseq_t * +rb_get_iseq_body_parent_iseq(const rb_iseq_t *iseq) +{ + return iseq->body->parent_iseq; +} + unsigned int rb_get_iseq_body_local_table_size(const rb_iseq_t *iseq) { @@ -409,6 +415,14 @@ rb_assert_iseq_handle(VALUE handle) RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(handle, imemo_iseq)); } +// Assert that we have the VM lock. Relevant mostly for multi ractor situations. +// The GC takes the lock before calling us, and this asserts that it indeed happens. +void +rb_assert_holding_vm_lock(void) +{ + ASSERT_vm_locking(); +} + int rb_IMEMO_TYPE_P(VALUE imemo, enum imemo_type imemo_type) { @@ -421,3 +435,10 @@ rb_assert_cme_handle(VALUE handle) RUBY_ASSERT_ALWAYS(!rb_objspace_garbage_object_p(handle)); RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(handle, imemo_ment)); } + +// YJIT and ZJIT need this function to never allocate and never raise +VALUE +rb_yarv_ary_entry_internal(VALUE ary, long offset) +{ + return rb_ary_entry_internal(ary, offset); +} diff --git a/jit.rs b/jit.rs new file mode 100644 index 0000000000..b66b2d21ca --- /dev/null +++ b/jit.rs @@ -0,0 +1,4 @@ +#[cfg(feature = "yjit")] +pub use yjit::*; +#[cfg(feature = "zjit")] +pub use zjit::*; diff --git a/lib/bundled_gems.rb b/lib/bundled_gems.rb index fab79f42a9..852d7c48e3 100644 --- a/lib/bundled_gems.rb +++ b/lib/bundled_gems.rb @@ -25,6 +25,7 @@ module Gem::BUNDLED_GEMS # :nodoc: "irb" => "3.5.0", "reline" => "3.5.0", # "readline" => "3.5.0", # This is wrapper for reline. We don't warn for this. + "tsort" => "3.6.0", }.freeze EXACT = { @@ -49,12 +50,7 @@ module Gem::BUNDLED_GEMS # :nodoc: kernel_class.send(:alias_method, :no_warning_require, :require) kernel_class.send(:define_method, :require) do |name| if message = ::Gem::BUNDLED_GEMS.warning?(name, specs: spec_names) - uplevel = ::Gem::BUNDLED_GEMS.uplevel - if uplevel > 0 - Kernel.warn message, uplevel: uplevel - else - Kernel.warn message - end + Kernel.warn message, uplevel: ::Gem::BUNDLED_GEMS.uplevel end kernel_class.send(:no_warning_require, name) end @@ -86,11 +82,10 @@ module Gem::BUNDLED_GEMS # :nodoc: uplevel += 1 # Don't show script name when bundle exec and call ruby script directly. if cl.path.end_with?("bundle") - frame_count = 0 - break + return end end - require_found ? 1 : frame_count - 1 + require_found ? 1 : (frame_count - 1).nonzero? end def self.warning?(name, specs: nil) @@ -105,11 +100,14 @@ module Gem::BUNDLED_GEMS # :nodoc: # and `require "syslog"` to `require "#{ARCHDIR}/syslog.so"`. feature.delete_prefix!(ARCHDIR) feature.delete_prefix!(LIBDIR) - segments = feature.split("/") + # 1. A segment for the EXACT mapping and SINCE check + # 2. A segment for the SINCE check for dashed names + # 3. A segment to check if there's a subfeature + segments = feature.split("/", 3) name = segments.shift name = EXACT[name] || name if !SINCE[name] - name = [name, segments.shift].join("-") + name = "#{name}-#{segments.shift}" return unless SINCE[name] end segments.any? @@ -124,24 +122,24 @@ module Gem::BUNDLED_GEMS # :nodoc: return if WARNED[name] WARNED[name] = true - level = RUBY_VERSION < SINCE[name] ? "warning" : "error" + level = RUBY_VERSION < SINCE[name] ? :warning : :error if subfeature "#{feature} is found in #{name}, which" else - "#{feature} #{level == "warning" ? "was loaded" : "used to be loaded"} from the standard library, but" + "#{feature} #{level == :warning ? "was loaded" : "used to be loaded"} from the standard library, but" end + build_message(name, level) end def self.build_message(name, level) - msg = if level == "warning" + msg = if level == :warning " will no longer be part of the default gems starting from Ruby #{SINCE[name]}" else " is not part of the default gems since Ruby #{SINCE[name]}." end if defined?(Bundler) - motivation = level == "warning" ? "silence this warning" : "fix this error" + motivation = level == :warning ? "silence this warning" : "fix this error" msg += "\nYou can add #{name} to your Gemfile or gemspec to #{motivation}." # We detect the gem name from caller_locations. First we walk until we find `require` @@ -236,7 +234,7 @@ class LoadError name = path.tr("/", "-") if !defined?(Bundler) && Gem::BUNDLED_GEMS::SINCE[name] && !Gem::BUNDLED_GEMS::WARNED[name] - warn name + Gem::BUNDLED_GEMS.build_message(name, "error"), uplevel: Gem::BUNDLED_GEMS.uplevel + warn name + Gem::BUNDLED_GEMS.build_message(name, :error), uplevel: Gem::BUNDLED_GEMS.uplevel end super end diff --git a/lib/bundler.rb b/lib/bundler.rb index eea3b0cf17..761679ec8d 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require_relative "bundler/rubygems_ext" require_relative "bundler/vendored_fileutils" require "pathname" require "rbconfig" @@ -7,7 +8,6 @@ require "rbconfig" require_relative "bundler/errors" require_relative "bundler/environment_preserver" require_relative "bundler/plugin" -require_relative "bundler/rubygems_ext" require_relative "bundler/rubygems_integration" require_relative "bundler/version" require_relative "bundler/current_ruby" @@ -53,7 +53,6 @@ module Bundler autoload :FeatureFlag, File.expand_path("bundler/feature_flag", __dir__) autoload :FREEBSD, File.expand_path("bundler/constants", __dir__) autoload :GemHelper, File.expand_path("bundler/gem_helper", __dir__) - autoload :GemHelpers, File.expand_path("bundler/gem_helpers", __dir__) autoload :GemVersionPromoter, File.expand_path("bundler/gem_version_promoter", __dir__) autoload :Graph, File.expand_path("bundler/graph", __dir__) autoload :Index, File.expand_path("bundler/index", __dir__) @@ -114,13 +113,13 @@ module Bundler end def configured_bundle_path - @configured_bundle_path ||= settings.path.tap(&:validate!) + @configured_bundle_path ||= Bundler.settings.path.tap(&:validate!) end # Returns absolute location of where binstubs are installed to. def bin_path @bin_path ||= begin - path = settings[:bin] || "bin" + path = Bundler.settings[:bin] || "bin" path = Pathname.new(path).expand_path(root).expand_path mkdir_p(path) path @@ -174,14 +173,14 @@ module Bundler self_manager.restart_with_locked_bundler_if_needed end - # Automatically install dependencies if Bundler.settings[:auto_install] exists. + # Automatically install dependencies if settings[:auto_install] exists. # This is set through config cmd `bundle config set --global auto_install 1`. # # Note that this method `nil`s out the global Definition object, so it # should be called first, before you instantiate anything like an # `Installer` that'll keep a reference to the old one instead. def auto_install - return unless settings[:auto_install] + return unless Bundler.settings[:auto_install] begin definition.specs @@ -239,10 +238,10 @@ module Bundler end def frozen_bundle? - frozen = settings[:frozen] + frozen = Bundler.settings[:frozen] return frozen unless frozen.nil? - settings[:deployment] + Bundler.settings[:deployment] end def locked_gems @@ -343,7 +342,7 @@ module Bundler def app_cache(custom_path = nil) path = custom_path || root - Pathname.new(path).join(settings.app_cache_path) + Pathname.new(path).join(Bundler.settings.app_cache_path) end def tmp(name = Process.pid.to_s) @@ -455,10 +454,14 @@ module Bundler end def local_platform - return Gem::Platform::RUBY if settings[:force_ruby_platform] + return Gem::Platform::RUBY if Bundler.settings[:force_ruby_platform] Gem::Platform.local end + def generic_local_platform + Gem::Platform.generic(local_platform) + end + def default_gemfile SharedHelpers.default_gemfile end @@ -564,7 +567,7 @@ module Bundler end def feature_flag - @feature_flag ||= FeatureFlag.new(VERSION) + @feature_flag ||= FeatureFlag.new(Bundler.settings[:simulate_version] || VERSION) end def reset! @@ -580,7 +583,6 @@ module Bundler def reset_paths! @bin_path = nil - @bundler_major_version = nil @bundle_path = nil @configure = nil @configured_bundle_path = nil diff --git a/lib/bundler/build_metadata.rb b/lib/bundler/build_metadata.rb index 5d2a8b53bb..49d2518078 100644 --- a/lib/bundler/build_metadata.rb +++ b/lib/bundler/build_metadata.rb @@ -4,21 +4,26 @@ module Bundler # Represents metadata from when the Bundler gem was built. module BuildMetadata # begin ivars - @release = false + @built_at = nil # end ivars # A hash representation of the build metadata. def self.to_h { - "Built At" => built_at, + "Timestamp" => timestamp, "Git SHA" => git_commit_sha, - "Released Version" => release?, } end + # A timestamp representing the date the bundler gem was built, or the + # current time if never built + def self.timestamp + @timestamp ||= @built_at || Time.now.utc.strftime("%Y-%m-%d").freeze + end + # A string representing the date the bundler gem was built. def self.built_at - @built_at ||= Time.now.utc.strftime("%Y-%m-%d").freeze + @built_at end # The SHA for the git commit the bundler gem was built from. @@ -34,10 +39,5 @@ module Bundler @git_commit_sha ||= "unknown" end - - # Whether this is an official release build of Bundler. - def self.release? - @release - end end end diff --git a/lib/bundler/bundler.gemspec b/lib/bundler/bundler.gemspec index 88411f295d..16ca4a022c 100644 --- a/lib/bundler/bundler.gemspec +++ b/lib/bundler/bundler.gemspec @@ -29,10 +29,10 @@ Gem::Specification.new do |s| "source_code_uri" => "https://github.com/rubygems/rubygems/tree/master/bundler", } - s.required_ruby_version = ">= 3.1.0" + s.required_ruby_version = ">= 3.2.0" # It should match the RubyGems version shipped with `required_ruby_version` above - s.required_rubygems_version = ">= 3.3.3" + s.required_rubygems_version = ">= 3.4.1" s.files = Dir.glob("lib/bundler{.rb,/**/*}", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 51f71af501..ea85f9af22 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -77,7 +77,7 @@ module Bundler self.options ||= {} unprinted_warnings = Bundler.ui.unprinted_warnings Bundler.ui = UI::Shell.new(options) - Bundler.ui.level = "debug" if options["verbose"] + Bundler.ui.level = "debug" if options[:verbose] || Bundler.settings[:verbose] unprinted_warnings.each {|w| Bundler.ui.warn(w) } end @@ -130,7 +130,7 @@ module Bundler if man_pages.include?(command) man_page = man_pages[command] - if Bundler.which("man") && !man_path.match?(%r{^file:/.+!/META-INF/jruby.home/.+}) + if Bundler.which("man") && !man_path.match?(%r{^(?:file:/.+!|uri:classloader:)/META-INF/jruby.home/.+}) Kernel.exec("man", man_page) else puts File.read("#{man_path}/#{File.basename(man_page)}.ronn") @@ -220,7 +220,7 @@ module Bundler method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead" method_option "prefer-local", type: :boolean, banner: "Only attempt to fetch gems remotely if not present locally, even if newer versions are available remotely" method_option "no-cache", type: :boolean, banner: "Don't update the existing gem cache." - method_option "redownload", type: :boolean, aliases: "--force", banner: "Force downloading every gem." + method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed" method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache." method_option "path", type: :string, banner: "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}" method_option "quiet", type: :boolean, banner: "Only output warnings and errors." @@ -232,15 +232,13 @@ module Bundler method_option "without", type: :array, banner: "Exclude gems that are part of the specified named group." method_option "with", type: :array, banner: "Include gems that are part of the specified named group." def install - SharedHelpers.major_deprecation(2, "The `--force` option has been renamed to `--redownload`") if ARGV.include?("--force") - %w[clean deployment frozen no-prune path shebang without with].each do |option| remembered_flag_deprecation(option) end print_remembered_flag_deprecation("--system", "path.system", "true") if ARGV.include?("--system") - remembered_negative_flag_deprecation("no-deployment") + remembered_flag_deprecation("deployment", negative: true) require_relative "cli/install" Bundler.settings.temporary(no_install: false) do @@ -263,7 +261,7 @@ module Bundler method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead" method_option "quiet", type: :boolean, banner: "Only output warnings and errors." method_option "source", type: :array, banner: "Update a specific source (and all gems associated with it)" - method_option "redownload", type: :boolean, aliases: "--force", banner: "Force downloading every gem." + method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed" method_option "ruby", type: :boolean, banner: "Update ruby specified in Gemfile.lock" method_option "bundler", type: :string, lazy_default: "> 0.a", banner: "Update the locked version of bundler" method_option "patch", type: :boolean, banner: "Prefer updating only to next patch version" @@ -274,7 +272,6 @@ module Bundler method_option "conservative", type: :boolean, banner: "Use bundle install conservative update behavior and do not allow shared dependencies to be updated." method_option "all", type: :boolean, banner: "Update everything." def update(*gems) - SharedHelpers.major_deprecation(2, "The `--force` option has been renamed to `--redownload`") if ARGV.include?("--force") require_relative "cli/update" Bundler.settings.temporary(no_install: false) do Update.new(options, gems).run @@ -331,6 +328,8 @@ module Bundler method_option "all", type: :boolean, banner: "Install binstubs for all gems" method_option "all-platforms", type: :boolean, default: false, banner: "Install binstubs for all platforms" def binstubs(*gems) + remembered_flag_deprecation("path", option_name: "bin") + require_relative "cli/binstubs" Binstubs.new(options, gems).run end @@ -414,7 +413,7 @@ module Bundler def cache print_remembered_flag_deprecation("--all", "cache_all", "true") if ARGV.include?("--all") - if ARGV.include?("--path") + if flag_passed?("--path") message = "The `--path` flag is deprecated because its semantics are unclear. " \ "Use `bundle config cache_path` to configure the path of your cache of gems, " \ @@ -486,13 +485,13 @@ module Bundler def version cli_help = current_command.name == "cli_help" if cli_help || ARGV.include?("version") - build_info = " (#{BuildMetadata.built_at} commit #{BuildMetadata.git_commit_sha})" + build_info = " (#{BuildMetadata.timestamp} commit #{BuildMetadata.git_commit_sha})" end - if !cli_help && Bundler.feature_flag.print_only_version_number? - Bundler.ui.info "#{Bundler::VERSION}#{build_info}" + if !cli_help && Bundler.feature_flag.bundler_4_mode? + Bundler.ui.info "#{Bundler.verbose_version}#{build_info}" else - Bundler.ui.info "Bundler version #{Bundler::VERSION}#{build_info}" + Bundler.ui.info "Bundler version #{Bundler.verbose_version}#{build_info}" end end @@ -512,7 +511,7 @@ module Bundler end end - unless Bundler.feature_flag.bundler_3_mode? + unless Bundler.feature_flag.bundler_4_mode? desc "viz [OPTIONS]", "Generates a visual dependency graph", hide: true long_desc <<-D Viz generates a PNG file of the current Gemfile as a dependency graph. @@ -544,6 +543,7 @@ module Bundler method_option :ci, type: :string, lazy_default: Bundler.settings["gem.ci"] || "", enum: %w[github gitlab circle], desc: "Generate CI configuration, either GitHub Actions, GitLab CI or CircleCI. Set a default with `bundle config set --global gem.ci (github|gitlab|circle)`" method_option :linter, type: :string, lazy_default: Bundler.settings["gem.linter"] || "", enum: %w[rubocop standard], desc: "Add a linter and code formatter, either RuboCop or Standard. Set a default with `bundle config set --global gem.linter (rubocop|standard)`" method_option :github_username, type: :string, default: Bundler.settings["gem.github_username"], banner: "Set your username on GitHub", desc: "Fill in GitHub username on README so that you don't have to do it manually. Set a default with `bundle config set --global gem.github_username `." + method_option :bundle, type: :boolean, default: Bundler.settings["gem.bundle"], desc: "Automatically run `bundle install` after creation. Set a default with `bundle config set --global gem.bundle true`" def gem(name) require_relative "cli/gem" @@ -713,14 +713,9 @@ module Bundler command_name = cmd.name return if PARSEABLE_COMMANDS.include?(command_name) command = ["bundle", command_name] + args - options_to_print = options.dup - options_to_print.delete_if do |k, v| - next unless o = cmd.options[k] - o.default == v - end - command << Thor::Options.to_switches(options_to_print.sort_by(&:first)).strip + command << Thor::Options.to_switches(options.sort_by(&:first)).strip command.reject!(&:empty?) - Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler::VERSION}" + Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler.verbose_version}" end def warn_on_outdated_bundler @@ -747,30 +742,17 @@ module Bundler nil end - def remembered_negative_flag_deprecation(name) - positive_name = name.gsub(/\Ano-/, "") - option = current_command.options[positive_name] - flag_name = "--no-" + option.switch_name.gsub(/\A--/, "") - - flag_deprecation(positive_name, flag_name, option) - end - - def remembered_flag_deprecation(name) + def remembered_flag_deprecation(name, negative: false, option_name: nil) option = current_command.options[name] flag_name = option.switch_name - - flag_deprecation(name, flag_name, option) - end - - def flag_deprecation(name, flag_name, option) - name_index = ARGV.find {|arg| flag_name == arg.split("=")[0] } - return unless name_index + flag_name = "--no-" + flag_name.gsub(/\A--/, "") if negative + return unless flag_passed?(flag_name) value = options[name] value = value.join(" ").to_s if option.type == :array value = "'#{value}'" unless option.type == :boolean - print_remembered_flag_deprecation(flag_name, name.tr("-", "_"), value) + print_remembered_flag_deprecation(flag_name, option_name || name.tr("-", "_"), value) end def print_remembered_flag_deprecation(flag_name, option_name, option_value) @@ -786,5 +768,9 @@ module Bundler "#{option_value}`, and stop using this flag" Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message end + + def flag_passed?(name) + ARGV.any? {|arg| name == arg.split("=")[0] } + end end end diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb index 7ef6deb2cf..be21ba8299 100644 --- a/lib/bundler/cli/common.rb +++ b/lib/bundler/cli/common.rb @@ -130,7 +130,7 @@ module Bundler def self.clean_after_install? clean = Bundler.settings[:clean] return clean unless clean.nil? - clean ||= Bundler.feature_flag.auto_clean_without_path? && Bundler.settings[:path].nil? + clean ||= Bundler.feature_flag.bundler_4_mode? && Bundler.settings[:path].nil? clean &&= !Bundler.use_system_gems? clean end diff --git a/lib/bundler/cli/config.rb b/lib/bundler/cli/config.rb index 77b502fe60..d963679085 100644 --- a/lib/bundler/cli/config.rb +++ b/lib/bundler/cli/config.rb @@ -26,8 +26,8 @@ module Bundler end message = "Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle #{new_args.join(" ")}` instead." - removed_message = "Using the `config` command without a subcommand [list, get, set, unset] is has been removed. Use `bundle #{new_args.join(" ")}` instead." - SharedHelpers.major_deprecation 3, message, removed_message: removed_message + removed_message = "Using the `config` command without a subcommand [list, get, set, unset] has been removed. Use `bundle #{new_args.join(" ")}` instead." + SharedHelpers.major_deprecation 4, message, removed_message: removed_message Base.new(options, name, value, self).run end diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 22bcf0e47a..5b71d71c67 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -31,7 +31,6 @@ module Bundler @extension = options[:ext] validate_ext_name if @extension - validate_rust_builder_rubygems_version if @extension == "rust" end def run @@ -48,13 +47,16 @@ module Bundler git_author_name = use_git ? `git config user.name`.chomp : "" git_username = use_git ? `git config github.user`.chomp : "" git_user_email = use_git ? `git config user.email`.chomp : "" + github_username = github_username(git_username) - github_username = if options[:github_username].nil? - git_username - elsif options[:github_username] == false - "" + if github_username.empty? + homepage_uri = "TODO: Put your gem's website or public repo URL here." + source_code_uri = "TODO: Put your gem's public repo URL here." + changelog_uri = "TODO: Put your gem's CHANGELOG.md URL here." else - options[:github_username] + homepage_uri = "https://github.com/#{github_username}/#{name}" + source_code_uri = "https://github.com/#{github_username}/#{name}" + changelog_uri = "https://github.com/#{github_username}/#{name}/blob/main/CHANGELOG.md" end config = { @@ -69,12 +71,17 @@ module Bundler test: options[:test], ext: extension, exe: options[:exe], + bundle: options[:bundle], bundler_version: bundler_dependency_version, git: use_git, github_username: github_username.empty? ? "[USERNAME]" : github_username, required_ruby_version: required_ruby_version, rust_builder_required_rubygems_version: rust_builder_required_rubygems_version, minitest_constant_name: minitest_constant_name, + ignore_paths: %w[bin/], + homepage_uri: homepage_uri, + source_code_uri: source_code_uri, + changelog_uri: changelog_uri, } ensure_safe_gem_name(name, constant_array) @@ -95,7 +102,18 @@ module Bundler bin/setup ] - templates.merge!("gitignore.tt" => ".gitignore") if use_git + case Bundler.preferred_gemfile_name + when "Gemfile" + config[:ignore_paths] << "Gemfile" + when "gems.rb" + config[:ignore_paths] << "gems.rb" + config[:ignore_paths] << "gems.locked" + end + + if use_git + templates.merge!("gitignore.tt" => ".gitignore") + config[:ignore_paths] << ".gitignore" + end if test_framework = ask_and_set_test_framework config[:test] = test_framework @@ -109,6 +127,8 @@ module Bundler "spec/newgem_spec.rb.tt" => "spec/#{namespaced_path}_spec.rb" ) config[:test_task] = :spec + config[:ignore_paths] << ".rspec" + config[:ignore_paths] << "spec/" when "minitest" # Generate path for minitest target file (FileList["test/**/test_*.rb"]) # foo => test/test_foo.rb @@ -123,12 +143,14 @@ module Bundler "test/minitest/test_newgem.rb.tt" => "test/#{minitest_namespaced_path}.rb" ) config[:test_task] = :test + config[:ignore_paths] << "test/" when "test-unit" templates.merge!( "test/test-unit/test_helper.rb.tt" => "test/test_helper.rb", "test/test-unit/newgem_test.rb.tt" => "test/#{namespaced_path}_test.rb" ) config[:test_task] = :test + config[:ignore_paths] << "test/" end end @@ -136,19 +158,19 @@ module Bundler case config[:ci] when "github" templates.merge!("github/workflows/main.yml.tt" => ".github/workflows/main.yml") - config[:ci_config_path] = ".github " + config[:ignore_paths] << ".github/" when "gitlab" templates.merge!("gitlab-ci.yml.tt" => ".gitlab-ci.yml") - config[:ci_config_path] = ".gitlab-ci.yml " + config[:ignore_paths] << ".gitlab-ci.yml" when "circle" templates.merge!("circleci/config.yml.tt" => ".circleci/config.yml") - config[:ci_config_path] = ".circleci " + config[:ignore_paths] << ".circleci/" end if ask_and_set(:mit, "Do you want to license your code permissively under the MIT license?", - "This means that any other developer or company will be legally allowed to use your code " \ - "for free as long as they admit you created it. You can read more about the MIT license " \ - "at https://choosealicense.com/licenses/mit.") + "Using a MIT license means that any other developer or company will be legally allowed " \ + "to use your code for free as long as they admit you created it. You can read more about " \ + "the MIT license at https://choosealicense.com/licenses/mit.") config[:mit] = true Bundler.ui.info "MIT License enabled in config" templates.merge!("LICENSE.txt.tt" => "LICENSE.txt") @@ -185,10 +207,12 @@ module Bundler config[:linter_version] = rubocop_version Bundler.ui.info "RuboCop enabled in config" templates.merge!("rubocop.yml.tt" => ".rubocop.yml") + config[:ignore_paths] << ".rubocop.yml" when "standard" config[:linter_version] = standard_version Bundler.ui.info "Standard enabled in config" templates.merge!("standard.yml.tt" => ".standard.yml") + config[:ignore_paths] << ".standard.yml" end if config[:exe] @@ -219,7 +243,7 @@ module Bundler end if use_git - Bundler.ui.info "Initializing git repo in #{target}" + Bundler.ui.info "\nInitializing git repo in #{target}" require "shellwords" `git init #{target.to_s.shellescape}` @@ -241,10 +265,17 @@ module Bundler IO.popen(%w[git add .], { chdir: target }, &:read) end + if config[:bundle] + Bundler.ui.info "Running bundle install in the new gem directory." + Dir.chdir(target) do + system("bundle install") + end + end + # Open gemspec in editor open_editor(options["edit"], target.join("#{name}.gemspec")) if options[:edit] - Bundler.ui.info "Gem '#{name}' was successfully created. " \ + Bundler.ui.info "\nGem '#{name}' was successfully created. " \ "For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html" end @@ -254,13 +285,13 @@ module Bundler SharedHelpers.pwd.join(name).basename.to_s end - def ask_and_set(key, header, message) + def ask_and_set(key, prompt, explanation) choice = options[key] choice = Bundler.settings["gem.#{key}"] if choice.nil? if choice.nil? - Bundler.ui.confirm header - choice = Bundler.ui.yes? "#{message} y/(n):" + Bundler.ui.info "\n#{explanation}" + choice = Bundler.ui.yes? "#{prompt} y/(n):" Bundler.settings.set_global("gem.#{key}", choice) end @@ -282,7 +313,7 @@ module Bundler test_framework = options[:test] || Bundler.settings["gem.test"] if test_framework.to_s.empty? - Bundler.ui.confirm "Do you want to generate tests with your gem?" + Bundler.ui.info "\nDo you want to generate tests with your gem?" Bundler.ui.info hint_text("test") result = Bundler.ui.ask "Enter a test framework. rspec/minitest/test-unit/(none):" @@ -322,12 +353,11 @@ module Bundler ci_template = options[:ci] || Bundler.settings["gem.ci"] if ci_template.to_s.empty? - Bundler.ui.confirm "Do you want to set up continuous integration for your gem? " \ + Bundler.ui.info "\nDo you want to set up continuous integration for your gem? " \ "Supported services:\n" \ "* CircleCI: https://circleci.com/\n" \ "* GitHub Actions: https://github.com/features/actions\n" \ - "* GitLab CI: https://docs.gitlab.com/ee/ci/\n" \ - "\n" + "* GitLab CI: https://docs.gitlab.com/ee/ci/\n" Bundler.ui.info hint_text("ci") result = Bundler.ui.ask "Enter a CI service. github/gitlab/circle/(none):" @@ -355,11 +385,10 @@ module Bundler linter_template = deprecated_rubocop_option if linter_template.nil? if linter_template.to_s.empty? - Bundler.ui.confirm "Do you want to add a code linter and formatter to your gem? " \ + Bundler.ui.info "\nDo you want to add a code linter and formatter to your gem? " \ "Supported Linters:\n" \ "* RuboCop: https://rubocop.org\n" \ - "* Standard: https://github.com/standardrb/standard\n" \ - "\n" + "* Standard: https://github.com/standardrb/standard\n" Bundler.ui.info hint_text("linter") result = Bundler.ui.ask "Enter a linter. rubocop/standard/(none):" @@ -446,7 +475,7 @@ module Bundler end def required_ruby_version - "3.1.0" + "3.2.0" end def rubocop_version @@ -457,10 +486,13 @@ module Bundler "1.3" end - def validate_rust_builder_rubygems_version - if Gem::Version.new(rust_builder_required_rubygems_version) > Gem.rubygems_version - Bundler.ui.error "Your RubyGems version (#{Gem.rubygems_version}) is too old to build Rust extension. Please update your RubyGems using `gem update --system` or any other way and try again." - exit 1 + def github_username(git_username) + if options[:github_username].nil? + git_username + elsif options[:github_username] == false + "" + else + options[:github_username] end end end diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb index b0b354cf10..074afd64eb 100644 --- a/lib/bundler/cli/install.rb +++ b/lib/bundler/cli/install.rb @@ -66,7 +66,9 @@ module Bundler Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins? - definition = Bundler.definition + # For install we want to enable strict validation + # (rather than some optimizations we perform at app runtime). + definition = Bundler.definition(strict: true) definition.validate_runtime! installer = Installer.install(Bundler.root, definition, options) @@ -158,9 +160,7 @@ module Bundler Bundler.settings.set_command_option_if_given :path, options[:path] if options["standalone"] && Bundler.settings[:path].nil? && !options["local"] - Bundler.settings.temporary(path_relative_to_cwd: false) do - Bundler.settings.set_command_option :path, "bundle" - end + Bundler.settings.set_command_option :path, "bundle" end bin_option = options["binstubs"] @@ -179,7 +179,7 @@ module Bundler normalize_groups if options[:without] || options[:with] - options[:force] = options[:redownload] + options[:force] = options[:redownload] if options[:redownload] end def warn_ambiguous_gems diff --git a/lib/bundler/cli/outdated.rb b/lib/bundler/cli/outdated.rb index 1be44ff4b4..0c8ba3ebf7 100644 --- a/lib/bundler/cli/outdated.rb +++ b/lib/bundler/cli/outdated.rb @@ -155,7 +155,7 @@ module Bundler return active_spec if strict - active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.match_platform(current_spec.platform) }.sort_by(&:version) + active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.installable_on_platform?(current_spec.platform) }.sort_by(&:version) if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1 active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? } end diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb index 985e8db051..13f576cfa7 100644 --- a/lib/bundler/cli/update.rb +++ b/lib/bundler/cli/update.rb @@ -26,7 +26,7 @@ module Bundler if Bundler.feature_flag.update_requires_all_flag? raise InvalidOption, "To update everything, pass the `--all` flag." end - SharedHelpers.major_deprecation 3, "Pass --all to `bundle update` to update everything" + SharedHelpers.major_deprecation 4, "Pass --all to `bundle update` to update everything" elsif !full_update && options[:all] raise InvalidOption, "Cannot specify --all along with specific options." end @@ -63,7 +63,7 @@ module Bundler opts = options.dup opts["update"] = true opts["local"] = options[:local] - opts["force"] = options[:redownload] + opts["force"] = options[:redownload] if options[:redownload] Bundler.settings.set_command_option_if_given :jobs, opts["jobs"] @@ -92,7 +92,7 @@ module Bundler locked_spec = locked_info[:spec] new_spec = Bundler.definition.specs[name].first unless new_spec - unless locked_spec.match_platform(Bundler.local_platform) + unless locked_spec.installable_on_platform?(Bundler.local_platform) Bundler.ui.warn "Bundler attempted to update #{name} but it was not considered because it is for a different platform from the current one" end diff --git a/lib/bundler/current_ruby.rb b/lib/bundler/current_ruby.rb index e7c872031f..faec695369 100644 --- a/lib/bundler/current_ruby.rb +++ b/lib/bundler/current_ruby.rb @@ -32,7 +32,7 @@ module Bundler end.freeze def ruby? - return true if Bundler::GemHelpers.generic_local_platform_is_ruby? + return true if Bundler::MatchPlatform.generic_local_platform_is_ruby? !windows? && (RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx" || RUBY_ENGINE == "maglev" || RUBY_ENGINE == "truffleruby") end diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index e9b67005a9..32006af109 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -4,8 +4,6 @@ require_relative "lockfile_parser" module Bundler class Definition - include GemHelpers - class << self # Do not create or modify a lockfile (Makes #lock a noop) attr_accessor :no_lock @@ -62,6 +60,7 @@ module Bundler if unlock == true @unlocking_all = true + strict = false @unlocking_bundler = false @unlocking = unlock @sources_to_unlock = [] @@ -70,6 +69,7 @@ module Bundler conservative = false else @unlocking_all = false + strict = unlock.delete(:strict) @unlocking_bundler = unlock.delete(:bundler) @unlocking = unlock.any? {|_k, v| !Array(v).empty? } @sources_to_unlock = unlock.delete(:sources) || [] @@ -99,7 +99,7 @@ module Bundler if lockfile_exists? @lockfile_contents = Bundler.read_file(lockfile) - @locked_gems = LockfileParser.new(@lockfile_contents) + @locked_gems = LockfileParser.new(@lockfile_contents, strict: strict) @locked_platforms = @locked_gems.platforms @most_specific_locked_platform = @locked_gems.most_specific_locked_platform @platforms = @locked_platforms.dup @@ -282,7 +282,7 @@ module Bundler end def filter_relevant(dependencies) - platforms_array = [generic_local_platform].freeze + platforms_array = [Bundler.generic_local_platform].freeze dependencies.select do |d| d.should_include? && !d.gem_platforms(platforms_array).empty? end @@ -456,8 +456,8 @@ module Bundler return if current_platform_locked? || @platforms.include?(Gem::Platform::RUBY) raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \ - "but your local platform is #{local_platform}. " \ - "Add the current platform to the lockfile with\n`bundle lock --add-platform #{local_platform}` and try again." + "but your local platform is #{Bundler.local_platform}. " \ + "Add the current platform to the lockfile with\n`bundle lock --add-platform #{Bundler.local_platform}` and try again." end def normalize_platforms @@ -568,7 +568,7 @@ module Bundler end def should_add_extra_platforms? - !lockfile_exists? && generic_local_platform_is_ruby? && !Bundler.settings[:force_ruby_platform] + !lockfile_exists? && Bundler::MatchPlatform.generic_local_platform_is_ruby? && !Bundler.settings[:force_ruby_platform] end def lockfile_exists? @@ -632,7 +632,7 @@ module Bundler @resolution_base ||= begin last_resolve = converge_locked_specs remove_invalid_platforms! - new_resolution_platforms = @current_platform_missing ? @new_platforms + [local_platform] : @new_platforms + new_resolution_platforms = @current_platform_missing ? @new_platforms + [Bundler.local_platform] : @new_platforms base = Resolver::Base.new(source_requirements, expanded_dependencies, last_resolve, @platforms, locked_specs: @originally_locked_specs, unlock: @unlocking_all || @gems_to_unlock, prerelease: gem_version_promoter.pre?, prefer_local: @prefer_local, new_platforms: new_resolution_platforms) base = additional_base_requirements_to_prevent_downgrades(base) base = additional_base_requirements_to_force_updates(base) @@ -738,8 +738,8 @@ module Bundler end def start_resolution - local_platform_needed_for_resolvability = @most_specific_non_local_locked_platform && !@platforms.include?(local_platform) - @platforms << local_platform if local_platform_needed_for_resolvability + local_platform_needed_for_resolvability = @most_specific_non_local_locked_platform && !@platforms.include?(Bundler.local_platform) + @platforms << Bundler.local_platform if local_platform_needed_for_resolvability add_platform(Gem::Platform::RUBY) if RUBY_ENGINE == "truffleruby" result = SpecSet.new(resolver.start) @@ -758,7 +758,7 @@ module Bundler if result.incomplete_for_platform?(current_dependencies, @most_specific_non_local_locked_platform) @platforms.delete(@most_specific_non_local_locked_platform) elsif local_platform_needed_for_resolvability - @platforms.delete(local_platform) + @platforms.delete(Bundler.local_platform) end end @@ -777,17 +777,17 @@ module Bundler def current_platform_locked? @platforms.any? do |bundle_platform| - generic_local_platform == bundle_platform || local_platform === bundle_platform + Bundler.generic_local_platform == bundle_platform || Bundler.local_platform === bundle_platform end end def add_current_platform - return if @platforms.include?(local_platform) + return if @platforms.include?(Bundler.local_platform) @most_specific_non_local_locked_platform = find_most_specific_locked_platform return if @most_specific_non_local_locked_platform - @platforms << local_platform + @platforms << Bundler.local_platform true end @@ -1037,17 +1037,16 @@ module Bundler lockfile_source = s.source if dep - gemfile_source = dep.source || default_source + replacement_source = dep.source - deps << dep if !dep.source || lockfile_source.include?(dep.source) || new_deps.include?(dep) - - # Replace the locked dependency's source with the equivalent source from the Gemfile - s.source = gemfile_source + deps << dep if !replacement_source || lockfile_source.include?(replacement_source) || new_deps.include?(dep) else - # Replace the locked dependency's source with the default source, if the locked source is no longer in the Gemfile - s.source = default_source unless sources.get(lockfile_source) + replacement_source = sources.get(lockfile_source) end + # Replace the locked dependency's source with the equivalent source from the Gemfile + s.source = replacement_source || default_source + source = s.source next if @sources_to_unlock.include?(source.name) @@ -1168,7 +1167,7 @@ module Bundler def remove_invalid_platforms! return if Bundler.frozen_bundle? - skips = (@new_platforms + [local_platform]).uniq + skips = (@new_platforms + [Bundler.local_platform]).uniq # We should probably avoid removing non-ruby platforms, since that means # lockfile will no longer install on those platforms, so a error to give diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb index e81696ff42..cb9c7a76ea 100644 --- a/lib/bundler/dependency.rb +++ b/lib/bundler/dependency.rb @@ -99,7 +99,7 @@ module Bundler return RUBY_PLATFORM_ARRAY if force_ruby_platform return valid_platforms if platforms.empty? - valid_platforms.select {|p| expanded_platforms.include?(GemHelpers.generic(p)) } + valid_platforms.select {|p| expanded_platforms.include?(Gem::Platform.generic(p)) } end def expanded_platforms diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index 32f45d97ec..4f9fbc55b1 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -73,7 +73,7 @@ module Bundler case specs_by_name_and_version.size when 1 specs = specs_by_name_and_version.values.first - spec = specs.find {|s| s.match_platform(Bundler.local_platform) } || specs.first + spec = specs.find {|s| s.installable_on_platform?(Bundler.local_platform) } || specs.first @gemspecs << spec @@ -521,7 +521,7 @@ module Bundler end def multiple_global_source_warning - if Bundler.feature_flag.bundler_3_mode? + if Bundler.feature_flag.bundler_4_mode? msg = "This Gemfile contains multiple global sources. " \ "Each source after the first must include a block to indicate which gems " \ "should come from that source" diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index b19cf42cc3..a8fa2a1bde 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -27,20 +27,23 @@ module Bundler (1..10).each {|v| define_method("bundler_#{v}_mode?") { @major_version >= v } } - settings_flag(:allow_offline_install) { bundler_3_mode? } - settings_flag(:auto_clean_without_path) { bundler_3_mode? } - settings_flag(:cache_all) { bundler_3_mode? } - settings_flag(:default_install_uses_path) { bundler_3_mode? } - settings_flag(:forget_cli_options) { bundler_3_mode? } - settings_flag(:global_gem_cache) { bundler_3_mode? } - settings_flag(:lockfile_checksums) { bundler_3_mode? } - settings_flag(:path_relative_to_cwd) { bundler_3_mode? } + settings_flag(:allow_offline_install) { bundler_4_mode? } + settings_flag(:cache_all) { bundler_4_mode? } + settings_flag(:forget_cli_options) { bundler_4_mode? } + settings_flag(:global_gem_cache) { bundler_4_mode? } + settings_flag(:lockfile_checksums) { bundler_4_mode? } settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") } - settings_flag(:print_only_version_number) { bundler_3_mode? } - settings_flag(:setup_makes_kernel_gem_public) { !bundler_3_mode? } - settings_flag(:update_requires_all_flag) { bundler_4_mode? } + settings_flag(:update_requires_all_flag) { bundler_5_mode? } - settings_option(:default_cli_command) { bundler_3_mode? ? :cli_help : :install } + settings_option(:default_cli_command) { bundler_4_mode? ? :cli_help : :install } + + def removed_major?(target_major_version) + @major_version > target_major_version + end + + def deprecated_major?(target_major_version) + @major_version >= target_major_version + end def initialize(bundler_version) @bundler_version = Gem::Version.create(bundler_version) diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index 9992b20c47..0b6ced6f39 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -72,19 +72,57 @@ module Bundler end end + HTTP_ERRORS = (Downloader::HTTP_RETRYABLE_ERRORS + Downloader::HTTP_NON_RETRYABLE_ERRORS).freeze + deprecate_constant :HTTP_ERRORS + + NET_ERRORS = [ + :HTTPBadGateway, + :HTTPBadRequest, + :HTTPFailedDependency, + :HTTPForbidden, + :HTTPInsufficientStorage, + :HTTPMethodNotAllowed, + :HTTPMovedPermanently, + :HTTPNoContent, + :HTTPNotFound, + :HTTPNotImplemented, + :HTTPPreconditionFailed, + :HTTPRequestEntityTooLarge, + :HTTPRequestURITooLong, + :HTTPUnauthorized, + :HTTPUnprocessableEntity, + :HTTPUnsupportedMediaType, + :HTTPVersionNotSupported, + ].freeze + deprecate_constant :NET_ERRORS + # Exceptions classes that should bypass retry attempts. If your password didn't work the # first time, it's not going to the third time. - NET_ERRORS = [:HTTPBadGateway, :HTTPBadRequest, :HTTPFailedDependency, - :HTTPForbidden, :HTTPInsufficientStorage, :HTTPMethodNotAllowed, - :HTTPMovedPermanently, :HTTPNoContent, :HTTPNotFound, - :HTTPNotImplemented, :HTTPPreconditionFailed, :HTTPRequestEntityTooLarge, - :HTTPRequestURITooLong, :HTTPUnauthorized, :HTTPUnprocessableEntity, - :HTTPUnsupportedMediaType, :HTTPVersionNotSupported].freeze - FAIL_ERRORS = begin - fail_errors = [AuthenticationRequiredError, BadAuthenticationError, AuthenticationForbiddenError, FallbackError, SecurityError] - fail_errors << Gem::Requirement::BadRequirementError - fail_errors.concat(NET_ERRORS.map {|e| Gem::Net.const_get(e) }) - end.freeze + FAIL_ERRORS = [ + AuthenticationRequiredError, + BadAuthenticationError, + AuthenticationForbiddenError, + FallbackError, + SecurityError, + Gem::Requirement::BadRequirementError, + Gem::Net::HTTPBadGateway, + Gem::Net::HTTPBadRequest, + Gem::Net::HTTPFailedDependency, + Gem::Net::HTTPForbidden, + Gem::Net::HTTPInsufficientStorage, + Gem::Net::HTTPMethodNotAllowed, + Gem::Net::HTTPMovedPermanently, + Gem::Net::HTTPNoContent, + Gem::Net::HTTPNotFound, + Gem::Net::HTTPNotImplemented, + Gem::Net::HTTPPreconditionFailed, + Gem::Net::HTTPRequestEntityTooLarge, + Gem::Net::HTTPRequestURITooLong, + Gem::Net::HTTPUnauthorized, + Gem::Net::HTTPUnprocessableEntity, + Gem::Net::HTTPUnsupportedMediaType, + Gem::Net::HTTPVersionNotSupported, + ].freeze class << self attr_accessor :disable_endpoint, :api_timeout, :redirect_limit, :max_retries @@ -293,13 +331,6 @@ module Bundler paths.find {|path| File.file? path } end - HTTP_ERRORS = [ - Gem::Timeout::Error, EOFError, SocketError, Errno::ENETDOWN, Errno::ENETUNREACH, - Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EAGAIN, - Gem::Net::HTTPBadResponse, Gem::Net::HTTPHeaderSyntaxError, Gem::Net::ProtocolError, - Gem::Net::HTTP::Persistent::Error, Zlib::BufError, Errno::EHOSTUNREACH - ].freeze - def bundler_cert_store store = OpenSSL::X509::Store.new ssl_ca_cert = Bundler.settings[:ssl_ca_cert] || diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb index 868b39b959..2eac6e7975 100644 --- a/lib/bundler/fetcher/downloader.rb +++ b/lib/bundler/fetcher/downloader.rb @@ -3,6 +3,28 @@ module Bundler class Fetcher class Downloader + HTTP_NON_RETRYABLE_ERRORS = [ + SocketError, + Errno::EADDRNOTAVAIL, + Errno::ENETDOWN, + Errno::ENETUNREACH, + Gem::Net::HTTP::Persistent::Error, + Errno::EHOSTUNREACH, + ].freeze + + HTTP_RETRYABLE_ERRORS = [ + Gem::Timeout::Error, + EOFError, + Errno::EINVAL, + Errno::ECONNRESET, + Errno::ETIMEDOUT, + Errno::EAGAIN, + Gem::Net::HTTPBadResponse, + Gem::Net::HTTPHeaderSyntaxError, + Gem::Net::ProtocolError, + Zlib::BufError, + ].freeze + attr_reader :connection attr_reader :redirect_limit @@ -67,15 +89,19 @@ module Bundler connection.request(uri, req) rescue OpenSSL::SSL::SSLError raise CertificateFailureError.new(uri) - rescue *HTTP_ERRORS => e + rescue *HTTP_NON_RETRYABLE_ERRORS => e Bundler.ui.trace e - if e.is_a?(SocketError) || e.message.to_s.include?("host down:") - raise NetworkDownError, "Could not reach host #{uri.host}. Check your network " \ - "connection and try again." - else - raise HTTPError, "Network error while fetching #{filtered_uri}" \ + + host = uri.host + host_port = "#{host}:#{uri.port}" + host = host_port if filtered_uri.to_s.include?(host_port) + raise NetworkDownError, "Could not reach host #{host}. Check your network " \ + "connection and try again." + rescue *HTTP_RETRYABLE_ERRORS => e + Bundler.ui.trace e + + raise HTTPError, "Network error while fetching #{filtered_uri}" \ " (#{e})" - end end private diff --git a/lib/bundler/gem_helpers.rb b/lib/bundler/gem_helpers.rb deleted file mode 100644 index ad12bf89a4..0000000000 --- a/lib/bundler/gem_helpers.rb +++ /dev/null @@ -1,144 +0,0 @@ -# frozen_string_literal: true - -module Bundler - module GemHelpers - GENERIC_CACHE = { Gem::Platform::RUBY => Gem::Platform::RUBY } # rubocop:disable Style/MutableConstant - GENERICS = [ - Gem::Platform::JAVA, - *Gem::Platform::WINDOWS, - ].freeze - - def generic(p) - GENERIC_CACHE[p] ||= begin - found = GENERICS.find do |match| - p === match - end - found || Gem::Platform::RUBY - end - end - module_function :generic - - def generic_local_platform - generic(local_platform) - end - module_function :generic_local_platform - - def local_platform - Bundler.local_platform - end - module_function :local_platform - - def generic_local_platform_is_ruby? - generic_local_platform == Gem::Platform::RUBY - end - module_function :generic_local_platform_is_ruby? - - def platform_specificity_match(spec_platform, user_platform) - spec_platform = Gem::Platform.new(spec_platform) - - PlatformMatch.specificity_score(spec_platform, user_platform) - end - module_function :platform_specificity_match - - def select_all_platform_match(specs, platform, force_ruby: false, prefer_locked: false) - matching = if force_ruby - specs.select {|spec| spec.match_platform(Gem::Platform::RUBY) && spec.force_ruby_platform! } - else - specs.select {|spec| spec.match_platform(platform) } - end - - if prefer_locked - locked_originally = matching.select {|spec| spec.is_a?(LazySpecification) } - return locked_originally if locked_originally.any? - end - - matching - end - module_function :select_all_platform_match - - def select_best_platform_match(specs, platform, force_ruby: false, prefer_locked: false) - matching = select_all_platform_match(specs, platform, force_ruby: force_ruby, prefer_locked: prefer_locked) - - sort_and_filter_best_platform_match(matching, platform) - end - module_function :select_best_platform_match - - def select_best_local_platform_match(specs, force_ruby: false) - matching = select_all_platform_match(specs, local_platform, force_ruby: force_ruby).filter_map(&:materialized_for_installation) - - sort_best_platform_match(matching, local_platform) - end - module_function :select_best_local_platform_match - - def sort_and_filter_best_platform_match(matching, platform) - return matching if matching.one? - - exact = matching.select {|spec| spec.platform == platform } - return exact if exact.any? - - sorted_matching = sort_best_platform_match(matching, platform) - exemplary_spec = sorted_matching.first - - sorted_matching.take_while {|spec| same_specificity(platform, spec, exemplary_spec) && same_deps(spec, exemplary_spec) } - end - module_function :sort_and_filter_best_platform_match - - def sort_best_platform_match(matching, platform) - matching.sort_by {|spec| platform_specificity_match(spec.platform, platform) } - end - module_function :sort_best_platform_match - - class PlatformMatch - def self.specificity_score(spec_platform, user_platform) - return -1 if spec_platform == user_platform - return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY - - os_match(spec_platform, user_platform) + - cpu_match(spec_platform, user_platform) * 10 + - platform_version_match(spec_platform, user_platform) * 100 - end - - def self.os_match(spec_platform, user_platform) - if spec_platform.os == user_platform.os - 0 - else - 1 - end - end - - def self.cpu_match(spec_platform, user_platform) - if spec_platform.cpu == user_platform.cpu - 0 - elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm") - 0 - elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal" - 1 - else - 2 - end - end - - def self.platform_version_match(spec_platform, user_platform) - if spec_platform.version == user_platform.version - 0 - elsif spec_platform.version.nil? - 1 - else - 2 - end - end - end - - def same_specificity(platform, spec, exemplary_spec) - platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform) - end - module_function :same_specificity - - def same_deps(spec, exemplary_spec) - same_runtime_deps = spec.dependencies.sort == exemplary_spec.dependencies.sort - same_metadata_deps = spec.required_ruby_version == exemplary_spec.required_ruby_version && spec.required_rubygems_version == exemplary_spec.required_rubygems_version - same_runtime_deps && same_metadata_deps - end - module_function :same_deps - end -end diff --git a/lib/bundler/index.rb b/lib/bundler/index.rb index df46facc88..d591b34cc7 100644 --- a/lib/bundler/index.rb +++ b/lib/bundler/index.rb @@ -131,6 +131,11 @@ module Bundler return unless other other.each do |spec| if existing = find_by_spec(spec) + unless dependencies_eql?(existing, spec) + Bundler.ui.warn "Local specification for #{spec.full_name} has different dependencies than the remote gem, ignoring it" + next + end + add_duplicate(existing) end add spec @@ -153,8 +158,8 @@ module Bundler end def dependencies_eql?(spec, other_spec) - deps = spec.dependencies.select {|d| d.type != :development } - other_deps = other_spec.dependencies.select {|d| d.type != :development } + deps = spec.runtime_dependencies + other_deps = other_spec.runtime_dependencies deps.sort == other_deps.sort end diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb index 061e4bb91e..81ded54797 100644 --- a/lib/bundler/lazy_specification.rb +++ b/lib/bundler/lazy_specification.rb @@ -33,7 +33,7 @@ module Bundler lazy_spec end - def initialize(name, version, platform, source = nil) + def initialize(name, version, platform, source = nil, **materialization_options) @name = name @version = version @dependencies = [] @@ -43,6 +43,7 @@ module Bundler @original_source = source @source = source + @materialization_options = materialization_options @force_ruby_platform = default_force_ruby_platform @most_specific_locked_platform = nil @@ -142,15 +143,15 @@ module Bundler end else materialize([name, version]) do |matching_specs| - target_platform = source.is_a?(Source::Path) ? platform : local_platform + target_platform = source.is_a?(Source::Path) ? platform : Bundler.local_platform - installable_candidates = GemHelpers.select_best_platform_match(matching_specs, target_platform) + installable_candidates = MatchPlatform.select_best_platform_match(matching_specs, target_platform) specification = choose_compatible(installable_candidates, fallback_to_non_installable: false) return specification unless specification.nil? if target_platform != platform - installable_candidates = GemHelpers.select_best_platform_match(matching_specs, platform) + installable_candidates = MatchPlatform.select_best_platform_match(matching_specs, platform) end choose_compatible(installable_candidates) @@ -190,7 +191,7 @@ module Bundler end def ruby_platform_materializes_to_ruby_platform? - generic_platform = generic_local_platform == Gem::Platform::JAVA ? Gem::Platform::JAVA : Gem::Platform::RUBY + generic_platform = Bundler.generic_local_platform == Gem::Platform::JAVA ? Gem::Platform::JAVA : Gem::Platform::RUBY (most_specific_locked_platform != generic_platform) || force_ruby_platform || Bundler.settings[:force_ruby_platform] end @@ -226,12 +227,13 @@ module Bundler # Validate dependencies of this locked spec are consistent with dependencies # of the actual spec that was materialized. # - # Note that we don't validate dependencies of locally installed gems but + # Note that unless we are in strict mode (which we set during installation) + # we don't validate dependencies of locally installed gems but # accept what's in the lockfile instead for performance, since loading # dependencies of locally installed gems would mean evaluating all gemspecs, # which would affect `bundler/setup` performance. def validate_dependencies(spec) - if spec.is_a?(StubSpecification) + if !@materialization_options[:strict] && spec.is_a?(StubSpecification) spec.dependencies = dependencies else if !source.is_a?(Source::Path) && spec.runtime_dependencies.sort != dependencies.sort diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb index 7d57ec724d..9ab9d73ae2 100644 --- a/lib/bundler/lockfile_parser.rb +++ b/lib/bundler/lockfile_parser.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true +require_relative "shared_helpers" + module Bundler class LockfileParser - include GemHelpers - class Position attr_reader :line, :column def initialize(line, column) @@ -94,7 +94,7 @@ module Bundler lockfile_contents.split(BUNDLED).last.strip end - def initialize(lockfile) + def initialize(lockfile, strict: false) @platforms = [] @sources = [] @dependencies = {} @@ -106,6 +106,7 @@ module Bundler "Gemfile.lock" end @pos = Position.new(1, 1) + @strict = strict if lockfile.match?(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/) raise LockfileError, "Your #{@lockfile_path} contains merge conflicts.\n" \ @@ -139,8 +140,23 @@ module Bundler end @pos.advance!(line) end + + if !Bundler.frozen_bundle? && @platforms.include?(Gem::Platform::X64_MINGW_LEGACY) + if @platforms.include?(Gem::Platform::X64_MINGW) + @platforms.delete(Gem::Platform::X64_MINGW_LEGACY) + SharedHelpers.major_deprecation(2, + "Found x64-mingw32 in lockfile, which is deprecated. Removing it. Support for x64-mingw32 will be removed in Bundler 4.0.", + removed_message: "Found x64-mingw32 in lockfile, which is no longer supported as of Bundler 4.0.") + else + @platforms[@platforms.index(Gem::Platform::X64_MINGW_LEGACY)] = Gem::Platform::X64_MINGW + SharedHelpers.major_deprecation(2, + "Found x64-mingw32 in lockfile, which is deprecated. Using x64-mingw-ucrt, the replacement for x64-mingw32 in modern rubies, instead. Support for x64-mingw32 will be removed in Bundler 4.0.", + removed_message: "Found x64-mingw32 in lockfile, which is no longer supported as of Bundler 4.0.") + end + end + @most_specific_locked_platform = @platforms.min_by do |bundle_platform| - platform_specificity_match(bundle_platform, local_platform) + Gem::Platform.platform_specificity_match(bundle_platform, Bundler.local_platform) end @specs = @specs.values.sort_by!(&:full_name).each do |spec| spec.most_specific_locked_platform = @most_specific_locked_platform @@ -271,7 +287,7 @@ module Bundler version = Gem::Version.new(version) platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY - @current_spec = LazySpecification.new(name, version, platform, @current_source) + @current_spec = LazySpecification.new(name, version, platform, @current_source, strict: @strict) @current_source.add_dependency_names(name) @specs[@current_spec.full_name] = @current_spec diff --git a/lib/bundler/man/bundle-add.1 b/lib/bundler/man/bundle-add.1 index 176e8b117e..baa4a376c8 100644 --- a/lib/bundler/man/bundle-add.1 +++ b/lib/bundler/man/bundle-add.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-ADD" "1" "March 2025" "" +.TH "BUNDLE\-ADD" "1" "July 2025" "" .SH "NAME" \fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-binstubs.1 b/lib/bundler/man/bundle-binstubs.1 index 146c1c021e..0131dd663e 100644 --- a/lib/bundler/man/bundle-binstubs.1 +++ b/lib/bundler/man/bundle-binstubs.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-BINSTUBS" "1" "March 2025" "" +.TH "BUNDLE\-BINSTUBS" "1" "July 2025" "" .SH "NAME" \fBbundle\-binstubs\fR \- Install the binstubs of the listed gems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-cache.1 b/lib/bundler/man/bundle-cache.1 index 64e806029b..4c5dcff052 100644 --- a/lib/bundler/man/bundle-cache.1 +++ b/lib/bundler/man/bundle-cache.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CACHE" "1" "March 2025" "" +.TH "BUNDLE\-CACHE" "1" "July 2025" "" .SH "NAME" \fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-check.1 b/lib/bundler/man/bundle-check.1 index bf16a22461..376becdbe4 100644 --- a/lib/bundler/man/bundle-check.1 +++ b/lib/bundler/man/bundle-check.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CHECK" "1" "March 2025" "" +.TH "BUNDLE\-CHECK" "1" "July 2025" "" .SH "NAME" \fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-clean.1 b/lib/bundler/man/bundle-clean.1 index 83f7661482..85e6186f49 100644 --- a/lib/bundler/man/bundle-clean.1 +++ b/lib/bundler/man/bundle-clean.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CLEAN" "1" "March 2025" "" +.TH "BUNDLE\-CLEAN" "1" "July 2025" "" .SH "NAME" \fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 190177eb37..6f12696ab6 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CONFIG" "1" "March 2025" "" +.TH "BUNDLE\-CONFIG" "1" "July 2025" "" .SH "NAME" \fBbundle\-config\fR \- Set bundler configuration options .SH "SYNOPSIS" @@ -42,29 +42,192 @@ Executing \fBbundle config unset \-\-global \fR will delete the configurat Executing \fBbundle config unset \-\-local \fR will delete the configuration only from the local application\. .P Executing bundle with the \fBBUNDLE_IGNORE_CONFIG\fR environment variable set will cause it to ignore all configuration\. +.SH "CONFIGURATION KEYS" +Configuration keys in bundler have two forms: the canonical form and the environment variable form\. +.P +For instance, passing the \fB\-\-without\fR flag to bundle install(1) \fIbundle\-install\.1\.html\fR prevents Bundler from installing certain groups specified in the Gemfile(5)\. Bundler persists this value in \fBapp/\.bundle/config\fR so that calls to \fBBundler\.setup\fR do not try to find gems from the \fBGemfile\fR that you didn't install\. Additionally, subsequent calls to bundle install(1) \fIbundle\-install\.1\.html\fR remember this setting and skip those groups\. +.P +The canonical form of this configuration is \fB"without"\fR\. To convert the canonical form to the environment variable form, capitalize it, and prepend \fBBUNDLE_\fR\. The environment variable form of \fB"without"\fR is \fBBUNDLE_WITHOUT\fR\. +.P +Any periods in the configuration keys must be replaced with two underscores when setting it via environment variables\. The configuration key \fBlocal\.rack\fR becomes the environment variable \fBBUNDLE_LOCAL__RACK\fR\. +.SH "LIST OF AVAILABLE KEYS" +The following is a list of all configuration keys and their purpose\. You can learn more about their operation in bundle install(1) \fIbundle\-install\.1\.html\fR\. +.TP +\fBallow_offline_install\fR (\fBBUNDLE_ALLOW_OFFLINE_INSTALL\fR) +Allow Bundler to use cached data when installing without network access\. +.TP +\fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR) +Automatically run \fBbundle install\fR when gems are missing\. +.TP +\fBbin\fR (\fBBUNDLE_BIN\fR) +Install executables from gems in the bundle to the specified directory\. Defaults to \fBfalse\fR\. +.TP +\fBcache_all\fR (\fBBUNDLE_CACHE_ALL\fR) +Cache all gems, including path and git gems\. This needs to be explicitly before bundler 4, but will be the default on bundler 4\. +.TP +\fBcache_all_platforms\fR (\fBBUNDLE_CACHE_ALL_PLATFORMS\fR) +Cache gems for all platforms\. +.TP +\fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR) +The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/cache\fR\. +.TP +\fBclean\fR (\fBBUNDLE_CLEAN\fR) +Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\. Defaults to \fBtrue\fR in Bundler 4, as long as \fBpath\fR is not explicitly configured\. +.TP +\fBconsole\fR (\fBBUNDLE_CONSOLE\fR) +The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\. +.TP +\fBdefault_cli_command\fR (\fBBUNDLE_DEFAULT_CLI_COMMAND\fR) +The command that running \fBbundle\fR without arguments should run\. Defaults to \fBcli_help\fR since Bundler 4, but can also be \fBinstall\fR which was the previous default\. +.TP +\fBdeployment\fR (\fBBUNDLE_DEPLOYMENT\fR) +Equivalent to setting \fBfrozen\fR to \fBtrue\fR and \fBpath\fR to \fBvendor/bundle\fR\. +.TP +\fBdisable_checksum_validation\fR (\fBBUNDLE_DISABLE_CHECKSUM_VALIDATION\fR) +Allow installing gems even if they do not match the checksum provided by RubyGems\. +.TP +\fBdisable_exec_load\fR (\fBBUNDLE_DISABLE_EXEC_LOAD\fR) +Stop Bundler from using \fBload\fR to launch an executable in\-process in \fBbundle exec\fR\. +.TP +\fBdisable_local_branch_check\fR (\fBBUNDLE_DISABLE_LOCAL_BRANCH_CHECK\fR) +Allow Bundler to use a local git override without a branch specified in the Gemfile\. +.TP +\fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR) +Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\. +.TP +\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR) +Stop Bundler from accessing gems installed to RubyGems' normal location\. +.TP +\fBdisable_version_check\fR (\fBBUNDLE_DISABLE_VERSION_CHECK\fR) +Stop Bundler from checking if a newer Bundler version is available on rubygems\.org\. +.TP +\fBforce_ruby_platform\fR (\fBBUNDLE_FORCE_RUBY_PLATFORM\fR) +Ignore the current machine's platform and install only \fBruby\fR platform gems\. As a result, gems with native extensions will be compiled from source\. +.TP +\fBfrozen\fR (\fBBUNDLE_FROZEN\fR) +Disallow any automatic changes to \fBGemfile\.lock\fR\. Bundler commands will be blocked unless the lockfile can be installed exactly as written\. Usually this will happen when changing the \fBGemfile\fR manually and forgetting to update the lockfile through \fBbundle lock\fR or \fBbundle install\fR\. +.TP +\fBgem\.github_username\fR (\fBBUNDLE_GEM__GITHUB_USERNAME\fR) +Sets a GitHub username or organization to be used in the \fBREADME\fR and \fB\.gemspec\fR files when you create a new gem via \fBbundle gem\fR command\. It can be overridden by passing an explicit \fB\-\-github\-username\fR flag to \fBbundle gem\fR\. +.TP +\fBgem\.push_key\fR (\fBBUNDLE_GEM__PUSH_KEY\fR) +Sets the \fB\-\-key\fR parameter for \fBgem push\fR when using the \fBrake release\fR command with a private gemstash server\. +.TP +\fBgemfile\fR (\fBBUNDLE_GEMFILE\fR) +The name of the file that bundler should use as the \fBGemfile\fR\. This location of this file also sets the root of the project, which is used to resolve relative paths in the \fBGemfile\fR, among other things\. By default, bundler will search up from the current working directory until it finds a \fBGemfile\fR\. +.TP +\fBglobal_gem_cache\fR (\fBBUNDLE_GLOBAL_GEM_CACHE\fR) +Whether Bundler should cache all gems and compiled extensions globally, rather than locally to the configured installation path\. +.TP +\fBignore_funding_requests\fR (\fBBUNDLE_IGNORE_FUNDING_REQUESTS\fR) +When set, no funding requests will be printed\. +.TP +\fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR) +When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\. +.TP +\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR) +Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\. +.TP +\fBjobs\fR (\fBBUNDLE_JOBS\fR) +The number of gems Bundler can install in parallel\. Defaults to the number of available processors\. +.TP +\fBlockfile_checksums\fR (\fBBUNDLE_LOCKFILE_CHECKSUMS\fR) +Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources\. +.TP +\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR) +Whether \fBbundle package\fR should skip installing gems\. +.TP +\fBno_prune\fR (\fBBUNDLE_NO_PRUNE\fR) +Whether Bundler should leave outdated gems unpruned when caching\. +.TP +\fBonly\fR (\fBBUNDLE_ONLY\fR) +A space\-separated list of groups to install only gems of the specified groups\. +.TP +\fBpath\fR (\fBBUNDLE_PATH\fR) +The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. Defaults to \fB\.bundle\fR relative to repository root in Bundler 4, and to the default system path (\fBGem\.dir\fR) before Bundler 4\. +.TP +\fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR) +Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\. +.TP +\fBplugins\fR (\fBBUNDLE_PLUGINS\fR) +Enable Bundler's experimental plugin system\. +.TP +\fBprefer_patch\fR (BUNDLE_PREFER_PATCH) +Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\. +.TP +\fBredirect\fR (\fBBUNDLE_REDIRECT\fR) +The number of redirects allowed for network requests\. Defaults to \fB5\fR\. +.TP +\fBretry\fR (\fBBUNDLE_RETRY\fR) +The number of times to retry failed network requests\. Defaults to \fB3\fR\. +.TP +\fBshebang\fR (\fBBUNDLE_SHEBANG\fR) +The program name that should be invoked for generated binstubs\. Defaults to the ruby install name used to generate the binstub\. +.TP +\fBsilence_deprecations\fR (\fBBUNDLE_SILENCE_DEPRECATIONS\fR) +Whether Bundler should silence deprecation warnings for behavior that will be changed in the next major version\. +.TP +\fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR) +Silence the warning Bundler prints when installing gems as root\. +.TP +\fBsimulate_version\fR (\fBBUNDLE_SIMULATE_VERSION\fR) +The virtual version Bundler should use for activating feature flags\. Can be used to simulate all the new functionality that will be enabled in a future major version\. +.TP +\fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR) +Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\. +.TP +\fBssl_client_cert\fR (\fBBUNDLE_SSL_CLIENT_CERT\fR) +Path to a designated file containing a X\.509 client certificate and key in PEM format\. +.TP +\fBssl_verify_mode\fR (\fBBUNDLE_SSL_VERIFY_MODE\fR) +The SSL verification mode Bundler uses when making HTTPS requests\. Defaults to verify peer\. +.TP +\fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR) +The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\. +.TP +\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR) +The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\. +.TP +\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR) +Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\. +.TP +\fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR) +The custom user agent fragment Bundler includes in API requests\. +.TP +\fBverbose\fR (\fBBUNDLE_VERBOSE\fR) +Whether Bundler should print verbose output\. Defaults to \fBfalse\fR, unless the \fB\-\-verbose\fR CLI flag is used\. +.TP +\fBversion\fR (\fBBUNDLE_VERSION\fR) +The version of Bundler to use when running under Bundler environment\. Defaults to \fBlockfile\fR\. You can also specify \fBsystem\fR or \fBx\.y\.z\fR\. \fBlockfile\fR will use the Bundler version specified in the \fBGemfile\.lock\fR, \fBsystem\fR will use the system version of Bundler, and \fBx\.y\.z\fR will use the specified version of Bundler\. +.TP +\fBwith\fR (\fBBUNDLE_WITH\fR) +A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should install\. +.TP +\fBwithout\fR (\fBBUNDLE_WITHOUT\fR) +A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should not install\. .SH "REMEMBERING OPTIONS" Flags passed to \fBbundle install\fR or the Bundler runtime, such as \fB\-\-path foo\fR or \fB\-\-without production\fR, are remembered between commands and saved to your local application's configuration (normally, \fB\./\.bundle/config\fR)\. .P -However, this will be changed in bundler 3, so it's better not to rely on this behavior\. If these options must be remembered, it's better to set them using \fBbundle config\fR (e\.g\., \fBbundle config set \-\-local path foo\fR)\. +However, this will be changed in bundler 4, so it's better not to rely on this behavior\. If these options must be remembered, it's better to set them using \fBbundle config\fR (e\.g\., \fBbundle config set \-\-local path foo\fR)\. .P -The options that can be configured are: +The flags that can be configured are: .TP -\fBbin\fR +\fB\-\-bin\fR Creates a directory (defaults to \fB~/bin\fR) and place any executables from the gem there\. These executables run in Bundler's context\. If used, you might add this directory to your environment's \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, this flag will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\. .TP -\fBdeployment\fR +\fB\-\-deployment\fR In deployment mode, Bundler will 'roll\-out' the bundle for \fBproduction\fR use\. Please check carefully if you want to have this option enabled in \fBdevelopment\fR or \fBtest\fR environments\. .TP -\fBonly\fR +\fB\-\-only\fR A space\-separated list of groups to install only gems of the specified groups\. Please check carefully if you want to install also gems without a group, cause they get put inside \fBdefault\fR group\. For example \fBonly test:default\fR will install all gems specified in test group and without one\. .TP -\fBpath\fR +\fB\-\-path\fR The location to install the specified gems to\. This defaults to Rubygems' setting\. Bundler shares this location with Rubygems, \fBgem install \|\.\|\.\|\.\fR will have gem installed there, too\. Therefore, gems installed without a \fB\-\-path \|\.\|\.\|\.\fR setting will show up by calling \fBgem list\fR\. Accordingly, gems installed to other locations will not get listed\. .TP -\fBwithout\fR +\fB\-\-without\fR A space\-separated or \fB:\fR\-separated list of groups referencing gems to skip during installation\. .TP -\fBwith\fR +\fB\-\-with\fR A space\-separated or \fB:\fR\-separated list of \fBoptional\fR groups referencing gems to include during installation\. .SH "BUILD OPTIONS" You can use \fBbundle config\fR to give Bundler the flags to pass to the gem installer every time bundler tries to install a particular gem\. @@ -84,123 +247,6 @@ bundle config set \-\-global build\.mysql \-\-with\-mysql\-config=/usr/local/mys .IP "" 0 .P After running this command, every time bundler needs to install the \fBmysql\fR gem, it will pass along the flags you specified\. -.SH "CONFIGURATION KEYS" -Configuration keys in bundler have two forms: the canonical form and the environment variable form\. -.P -For instance, passing the \fB\-\-without\fR flag to bundle install(1) \fIbundle\-install\.1\.html\fR prevents Bundler from installing certain groups specified in the Gemfile(5)\. Bundler persists this value in \fBapp/\.bundle/config\fR so that calls to \fBBundler\.setup\fR do not try to find gems from the \fBGemfile\fR that you didn't install\. Additionally, subsequent calls to bundle install(1) \fIbundle\-install\.1\.html\fR remember this setting and skip those groups\. -.P -The canonical form of this configuration is \fB"without"\fR\. To convert the canonical form to the environment variable form, capitalize it, and prepend \fBBUNDLE_\fR\. The environment variable form of \fB"without"\fR is \fBBUNDLE_WITHOUT\fR\. -.P -Any periods in the configuration keys must be replaced with two underscores when setting it via environment variables\. The configuration key \fBlocal\.rack\fR becomes the environment variable \fBBUNDLE_LOCAL__RACK\fR\. -.SH "LIST OF AVAILABLE KEYS" -The following is a list of all configuration keys and their purpose\. You can learn more about their operation in bundle install(1) \fIbundle\-install\.1\.html\fR\. -.IP "\(bu" 4 -\fBallow_offline_install\fR (\fBBUNDLE_ALLOW_OFFLINE_INSTALL\fR): Allow Bundler to use cached data when installing without network access\. -.IP "\(bu" 4 -\fBauto_clean_without_path\fR (\fBBUNDLE_AUTO_CLEAN_WITHOUT_PATH\fR): Automatically run \fBbundle clean\fR after installing when an explicit \fBpath\fR has not been set and Bundler is not installing into the system gems\. -.IP "\(bu" 4 -\fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR): Automatically run \fBbundle install\fR when gems are missing\. -.IP "\(bu" 4 -\fBbin\fR (\fBBUNDLE_BIN\fR): Install executables from gems in the bundle to the specified directory\. Defaults to \fBfalse\fR\. -.IP "\(bu" 4 -\fBcache_all\fR (\fBBUNDLE_CACHE_ALL\fR): Cache all gems, including path and git gems\. This needs to be explicitly configured on bundler 1 and bundler 2, but will be the default on bundler 3\. -.IP "\(bu" 4 -\fBcache_all_platforms\fR (\fBBUNDLE_CACHE_ALL_PLATFORMS\fR): Cache gems for all platforms\. -.IP "\(bu" 4 -\fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR): The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/cache\fR\. -.IP "\(bu" 4 -\fBclean\fR (\fBBUNDLE_CLEAN\fR): Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\. -.IP "\(bu" 4 -\fBconsole\fR (\fBBUNDLE_CONSOLE\fR): The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\. -.IP "\(bu" 4 -\fBdefault_install_uses_path\fR (\fBBUNDLE_DEFAULT_INSTALL_USES_PATH\fR): Whether a \fBbundle install\fR without an explicit \fB\-\-path\fR argument defaults to installing gems in \fB\.bundle\fR\. -.IP "\(bu" 4 -\fBdeployment\fR (\fBBUNDLE_DEPLOYMENT\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\. -.IP "\(bu" 4 -\fBdisable_checksum_validation\fR (\fBBUNDLE_DISABLE_CHECKSUM_VALIDATION\fR): Allow installing gems even if they do not match the checksum provided by RubyGems\. -.IP "\(bu" 4 -\fBdisable_exec_load\fR (\fBBUNDLE_DISABLE_EXEC_LOAD\fR): Stop Bundler from using \fBload\fR to launch an executable in\-process in \fBbundle exec\fR\. -.IP "\(bu" 4 -\fBdisable_local_branch_check\fR (\fBBUNDLE_DISABLE_LOCAL_BRANCH_CHECK\fR): Allow Bundler to use a local git override without a branch specified in the Gemfile\. -.IP "\(bu" 4 -\fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR): Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\. -.IP "\(bu" 4 -\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR): Stop Bundler from accessing gems installed to RubyGems' normal location\. -.IP "\(bu" 4 -\fBdisable_version_check\fR (\fBBUNDLE_DISABLE_VERSION_CHECK\fR): Stop Bundler from checking if a newer Bundler version is available on rubygems\.org\. -.IP "\(bu" 4 -\fBforce_ruby_platform\fR (\fBBUNDLE_FORCE_RUBY_PLATFORM\fR): Ignore the current machine's platform and install only \fBruby\fR platform gems\. As a result, gems with native extensions will be compiled from source\. -.IP "\(bu" 4 -\fBfrozen\fR (\fBBUNDLE_FROZEN\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\. Defaults to \fBtrue\fR when \fB\-\-deployment\fR is used\. -.IP "\(bu" 4 -\fBgem\.github_username\fR (\fBBUNDLE_GEM__GITHUB_USERNAME\fR): Sets a GitHub username or organization to be used in \fBREADME\fR file when you create a new gem via \fBbundle gem\fR command\. It can be overridden by passing an explicit \fB\-\-github\-username\fR flag to \fBbundle gem\fR\. -.IP "\(bu" 4 -\fBgem\.push_key\fR (\fBBUNDLE_GEM__PUSH_KEY\fR): Sets the \fB\-\-key\fR parameter for \fBgem push\fR when using the \fBrake release\fR command with a private gemstash server\. -.IP "\(bu" 4 -\fBgemfile\fR (\fBBUNDLE_GEMFILE\fR): The name of the file that bundler should use as the \fBGemfile\fR\. This location of this file also sets the root of the project, which is used to resolve relative paths in the \fBGemfile\fR, among other things\. By default, bundler will search up from the current working directory until it finds a \fBGemfile\fR\. -.IP "\(bu" 4 -\fBglobal_gem_cache\fR (\fBBUNDLE_GLOBAL_GEM_CACHE\fR): Whether Bundler should cache all gems globally, rather than locally to the installing Ruby installation\. -.IP "\(bu" 4 -\fBignore_funding_requests\fR (\fBBUNDLE_IGNORE_FUNDING_REQUESTS\fR): When set, no funding requests will be printed\. -.IP "\(bu" 4 -\fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR): When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\. -.IP "\(bu" 4 -\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR): Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\. -.IP "\(bu" 4 -\fBjobs\fR (\fBBUNDLE_JOBS\fR): The number of gems Bundler can install in parallel\. Defaults to the number of available processors\. -.IP "\(bu" 4 -\fBlockfile_checksums\fR (\fBBUNDLE_LOCKFILE_CHECKSUMS\fR): Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources\. -.IP "\(bu" 4 -\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR): Whether \fBbundle package\fR should skip installing gems\. -.IP "\(bu" 4 -\fBno_prune\fR (\fBBUNDLE_NO_PRUNE\fR): Whether Bundler should leave outdated gems unpruned when caching\. -.IP "\(bu" 4 -\fBonly\fR (\fBBUNDLE_ONLY\fR): A space\-separated list of groups to install only gems of the specified groups\. -.IP "\(bu" 4 -\fBpath\fR (\fBBUNDLE_PATH\fR): The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. Defaults to \fBGem\.dir\fR\. When \-\-deployment is used, defaults to vendor/bundle\. -.IP "\(bu" 4 -\fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR): Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\. -.IP "\(bu" 4 -\fBpath_relative_to_cwd\fR (\fBBUNDLE_PATH_RELATIVE_TO_CWD\fR) Makes \fB\-\-path\fR relative to the CWD instead of the \fBGemfile\fR\. -.IP "\(bu" 4 -\fBplugins\fR (\fBBUNDLE_PLUGINS\fR): Enable Bundler's experimental plugin system\. -.IP "\(bu" 4 -\fBprefer_patch\fR (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\. -.IP "\(bu" 4 -\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR): Print only version number from \fBbundler \-\-version\fR\. -.IP "\(bu" 4 -\fBredirect\fR (\fBBUNDLE_REDIRECT\fR): The number of redirects allowed for network requests\. Defaults to \fB5\fR\. -.IP "\(bu" 4 -\fBretry\fR (\fBBUNDLE_RETRY\fR): The number of times to retry failed network requests\. Defaults to \fB3\fR\. -.IP "\(bu" 4 -\fBsetup_makes_kernel_gem_public\fR (\fBBUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC\fR): Have \fBBundler\.setup\fR make the \fBKernel#gem\fR method public, even though RubyGems declares it as private\. -.IP "\(bu" 4 -\fBshebang\fR (\fBBUNDLE_SHEBANG\fR): The program name that should be invoked for generated binstubs\. Defaults to the ruby install name used to generate the binstub\. -.IP "\(bu" 4 -\fBsilence_deprecations\fR (\fBBUNDLE_SILENCE_DEPRECATIONS\fR): Whether Bundler should silence deprecation warnings for behavior that will be changed in the next major version\. -.IP "\(bu" 4 -\fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR): Silence the warning Bundler prints when installing gems as root\. -.IP "\(bu" 4 -\fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR): Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\. -.IP "\(bu" 4 -\fBssl_client_cert\fR (\fBBUNDLE_SSL_CLIENT_CERT\fR): Path to a designated file containing a X\.509 client certificate and key in PEM format\. -.IP "\(bu" 4 -\fBssl_verify_mode\fR (\fBBUNDLE_SSL_VERIFY_MODE\fR): The SSL verification mode Bundler uses when making HTTPS requests\. Defaults to verify peer\. -.IP "\(bu" 4 -\fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR): The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\. -.IP "\(bu" 4 -\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR): The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\. -.IP "\(bu" 4 -\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR): Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\. -.IP "\(bu" 4 -\fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR): The custom user agent fragment Bundler includes in API requests\. -.IP "\(bu" 4 -\fBversion\fR (\fBBUNDLE_VERSION\fR): The version of Bundler to use when running under Bundler environment\. Defaults to \fBlockfile\fR\. You can also specify \fBsystem\fR or \fBx\.y\.z\fR\. \fBlockfile\fR will use the Bundler version specified in the \fBGemfile\.lock\fR, \fBsystem\fR will use the system version of Bundler, and \fBx\.y\.z\fR will use the specified version of Bundler\. -.IP "\(bu" 4 -\fBwith\fR (\fBBUNDLE_WITH\fR): A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should install\. -.IP "\(bu" 4 -\fBwithout\fR (\fBBUNDLE_WITHOUT\fR): A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should not install\. -.IP "" 0 .SH "LOCAL GIT REPOS" Bundler also allows you to work against a git repository locally instead of using the remote version\. This can be achieved by setting up a local override: .IP "" 4 diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index 44c31cd10d..7f31eb4c39 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -52,71 +52,6 @@ only from the local application. Executing bundle with the `BUNDLE_IGNORE_CONFIG` environment variable set will cause it to ignore all configuration. -## REMEMBERING OPTIONS - -Flags passed to `bundle install` or the Bundler runtime, such as `--path foo` or -`--without production`, are remembered between commands and saved to your local -application's configuration (normally, `./.bundle/config`). - -However, this will be changed in bundler 3, so it's better not to rely on this -behavior. If these options must be remembered, it's better to set them using -`bundle config` (e.g., `bundle config set --local path foo`). - -The options that can be configured are: - -* `bin`: - Creates a directory (defaults to `~/bin`) and place any executables from the - gem there. These executables run in Bundler's context. If used, you might add - this directory to your environment's `PATH` variable. For instance, if the - `rails` gem comes with a `rails` executable, this flag will create a - `bin/rails` executable that ensures that all referred dependencies will be - resolved using the bundled gems. - -* `deployment`: - In deployment mode, Bundler will 'roll-out' the bundle for - `production` use. Please check carefully if you want to have this option - enabled in `development` or `test` environments. - -* `only`: - A space-separated list of groups to install only gems of the specified groups. - Please check carefully if you want to install also gems without a group, cause - they get put inside `default` group. For example `only test:default` will install - all gems specified in test group and without one. - -* `path`: - The location to install the specified gems to. This defaults to Rubygems' - setting. Bundler shares this location with Rubygems, `gem install ...` will - have gem installed there, too. Therefore, gems installed without a - `--path ...` setting will show up by calling `gem list`. Accordingly, gems - installed to other locations will not get listed. - -* `without`: - A space-separated or `:`-separated list of groups referencing gems to skip during - installation. - -* `with`: - A space-separated or `:`-separated list of **optional** groups referencing gems to - include during installation. - -## BUILD OPTIONS - -You can use `bundle config` to give Bundler the flags to pass to the gem -installer every time bundler tries to install a particular gem. - -A very common example, the `mysql` gem, requires Snow Leopard users to -pass configuration flags to `gem install` to specify where to find the -`mysql_config` executable. - - gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config - -Since the specific location of that executable can change from machine -to machine, you can specify these flags on a per-machine basis. - - bundle config set --global build.mysql --with-mysql-config=/usr/local/mysql/bin/mysql_config - -After running this command, every time bundler needs to install the -`mysql` gem, it will pass along the flags you specified. - ## CONFIGURATION KEYS Configuration keys in bundler have two forms: the canonical form and the @@ -144,9 +79,6 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). * `allow_offline_install` (`BUNDLE_ALLOW_OFFLINE_INSTALL`): Allow Bundler to use cached data when installing without network access. -* `auto_clean_without_path` (`BUNDLE_AUTO_CLEAN_WITHOUT_PATH`): - Automatically run `bundle clean` after installing when an explicit `path` - has not been set and Bundler is not installing into the system gems. * `auto_install` (`BUNDLE_AUTO_INSTALL`): Automatically run `bundle install` when gems are missing. * `bin` (`BUNDLE_BIN`): @@ -154,7 +86,7 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). Defaults to `false`. * `cache_all` (`BUNDLE_CACHE_ALL`): Cache all gems, including path and git gems. This needs to be explicitly - configured on bundler 1 and bundler 2, but will be the default on bundler 3. + before bundler 4, but will be the default on bundler 4. * `cache_all_platforms` (`BUNDLE_CACHE_ALL_PLATFORMS`): Cache gems for all platforms. * `cache_path` (`BUNDLE_CACHE_PATH`): @@ -163,15 +95,16 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). Defaults to `vendor/cache`. * `clean` (`BUNDLE_CLEAN`): Whether Bundler should run `bundle clean` automatically after - `bundle install`. + `bundle install`. Defaults to `true` in Bundler 4, as long as `path` is not + explicitly configured. * `console` (`BUNDLE_CONSOLE`): The console that `bundle console` starts. Defaults to `irb`. -* `default_install_uses_path` (`BUNDLE_DEFAULT_INSTALL_USES_PATH`): - Whether a `bundle install` without an explicit `--path` argument defaults - to installing gems in `.bundle`. +* `default_cli_command` (`BUNDLE_DEFAULT_CLI_COMMAND`): + The command that running `bundle` without arguments should run. Defaults to + `cli_help` since Bundler 4, but can also be `install` which was the previous + default. * `deployment` (`BUNDLE_DEPLOYMENT`): - Disallow changes to the `Gemfile`. When the `Gemfile` is changed and the - lockfile has not been updated, running Bundler commands will be blocked. + Equivalent to setting `frozen` to `true` and `path` to `vendor/bundle`. * `disable_checksum_validation` (`BUNDLE_DISABLE_CHECKSUM_VALIDATION`): Allow installing gems even if they do not match the checksum provided by RubyGems. @@ -193,12 +126,13 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). Ignore the current machine's platform and install only `ruby` platform gems. As a result, gems with native extensions will be compiled from source. * `frozen` (`BUNDLE_FROZEN`): - Disallow changes to the `Gemfile`. When the `Gemfile` is changed and the - lockfile has not been updated, running Bundler commands will be blocked. - Defaults to `true` when `--deployment` is used. + Disallow any automatic changes to `Gemfile.lock`. Bundler commands will + be blocked unless the lockfile can be installed exactly as written. + Usually this will happen when changing the `Gemfile` manually and forgetting + to update the lockfile through `bundle lock` or `bundle install`. * `gem.github_username` (`BUNDLE_GEM__GITHUB_USERNAME`): - Sets a GitHub username or organization to be used in `README` file when you - create a new gem via `bundle gem` command. It can be overridden by passing an + Sets a GitHub username or organization to be used in the `README` and `.gemspec` files + when you create a new gem via `bundle gem` command. It can be overridden by passing an explicit `--github-username` flag to `bundle gem`. * `gem.push_key` (`BUNDLE_GEM__PUSH_KEY`): Sets the `--key` parameter for `gem push` when using the `rake release` @@ -210,8 +144,8 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). will search up from the current working directory until it finds a `Gemfile`. * `global_gem_cache` (`BUNDLE_GLOBAL_GEM_CACHE`): - Whether Bundler should cache all gems globally, rather than locally to the - installing Ruby installation. + Whether Bundler should cache all gems and compiled extensions globally, + rather than locally to the configured installation path. * `ignore_funding_requests` (`BUNDLE_IGNORE_FUNDING_REQUESTS`): When set, no funding requests will be printed. * `ignore_messages` (`BUNDLE_IGNORE_MESSAGES`): @@ -233,25 +167,19 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). * `path` (`BUNDLE_PATH`): The location on disk where all gems in your bundle will be located regardless of `$GEM_HOME` or `$GEM_PATH` values. Bundle gems not found in this location - will be installed by `bundle install`. Defaults to `Gem.dir`. When --deployment - is used, defaults to vendor/bundle. + will be installed by `bundle install`. Defaults to `.bundle` relative to + repository root in Bundler 4, and to the default system path (`Gem.dir`) + before Bundler 4. * `path.system` (`BUNDLE_PATH__SYSTEM`): Whether Bundler will install gems into the default system path (`Gem.dir`). -* `path_relative_to_cwd` (`BUNDLE_PATH_RELATIVE_TO_CWD`) - Makes `--path` relative to the CWD instead of the `Gemfile`. * `plugins` (`BUNDLE_PLUGINS`): Enable Bundler's experimental plugin system. * `prefer_patch` (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates. Makes `bundle update` calls equivalent to `bundler update --patch`. -* `print_only_version_number` (`BUNDLE_PRINT_ONLY_VERSION_NUMBER`): - Print only version number from `bundler --version`. * `redirect` (`BUNDLE_REDIRECT`): The number of redirects allowed for network requests. Defaults to `5`. * `retry` (`BUNDLE_RETRY`): The number of times to retry failed network requests. Defaults to `3`. -* `setup_makes_kernel_gem_public` (`BUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC`): - Have `Bundler.setup` make the `Kernel#gem` method public, even though - RubyGems declares it as private. * `shebang` (`BUNDLE_SHEBANG`): The program name that should be invoked for generated binstubs. Defaults to the ruby install name used to generate the binstub. @@ -260,6 +188,10 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). be changed in the next major version. * `silence_root_warning` (`BUNDLE_SILENCE_ROOT_WARNING`): Silence the warning Bundler prints when installing gems as root. +* `simulate_version` (`BUNDLE_SIMULATE_VERSION`): + The virtual version Bundler should use for activating feature flags. Can be + used to simulate all the new functionality that will be enabled in a future + major version. * `ssl_ca_cert` (`BUNDLE_SSL_CA_CERT`): Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format. @@ -278,6 +210,9 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). and disallow passing no options to `bundle update`. * `user_agent` (`BUNDLE_USER_AGENT`): The custom user agent fragment Bundler includes in API requests. +* `verbose` (`BUNDLE_VERBOSE`): + Whether Bundler should print verbose output. Defaults to `false`, unless the + `--verbose` CLI flag is used. * `version` (`BUNDLE_VERSION`): The version of Bundler to use when running under Bundler environment. Defaults to `lockfile`. You can also specify `system` or `x.y.z`. @@ -289,6 +224,71 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). * `without` (`BUNDLE_WITHOUT`): A space-separated or `:`-separated list of groups whose gems bundler should not install. +## REMEMBERING OPTIONS + +Flags passed to `bundle install` or the Bundler runtime, such as `--path foo` or +`--without production`, are remembered between commands and saved to your local +application's configuration (normally, `./.bundle/config`). + +However, this will be changed in bundler 4, so it's better not to rely on this +behavior. If these options must be remembered, it's better to set them using +`bundle config` (e.g., `bundle config set --local path foo`). + +The flags that can be configured are: + +* `--bin`: + Creates a directory (defaults to `~/bin`) and place any executables from the + gem there. These executables run in Bundler's context. If used, you might add + this directory to your environment's `PATH` variable. For instance, if the + `rails` gem comes with a `rails` executable, this flag will create a + `bin/rails` executable that ensures that all referred dependencies will be + resolved using the bundled gems. + +* `--deployment`: + In deployment mode, Bundler will 'roll-out' the bundle for + `production` use. Please check carefully if you want to have this option + enabled in `development` or `test` environments. + +* `--only`: + A space-separated list of groups to install only gems of the specified groups. + Please check carefully if you want to install also gems without a group, cause + they get put inside `default` group. For example `only test:default` will install + all gems specified in test group and without one. + +* `--path`: + The location to install the specified gems to. This defaults to Rubygems' + setting. Bundler shares this location with Rubygems, `gem install ...` will + have gem installed there, too. Therefore, gems installed without a + `--path ...` setting will show up by calling `gem list`. Accordingly, gems + installed to other locations will not get listed. + +* `--without`: + A space-separated or `:`-separated list of groups referencing gems to skip during + installation. + +* `--with`: + A space-separated or `:`-separated list of **optional** groups referencing gems to + include during installation. + +## BUILD OPTIONS + +You can use `bundle config` to give Bundler the flags to pass to the gem +installer every time bundler tries to install a particular gem. + +A very common example, the `mysql` gem, requires Snow Leopard users to +pass configuration flags to `gem install` to specify where to find the +`mysql_config` executable. + + gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config + +Since the specific location of that executable can change from machine +to machine, you can specify these flags on a per-machine basis. + + bundle config set --global build.mysql --with-mysql-config=/usr/local/mysql/bin/mysql_config + +After running this command, every time bundler needs to install the +`mysql` gem, it will pass along the flags you specified. + ## LOCAL GIT REPOS Bundler also allows you to work against a git repository locally diff --git a/lib/bundler/man/bundle-console.1 b/lib/bundler/man/bundle-console.1 index de9eeac907..a263fef376 100644 --- a/lib/bundler/man/bundle-console.1 +++ b/lib/bundler/man/bundle-console.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CONSOLE" "1" "March 2025" "" +.TH "BUNDLE\-CONSOLE" "1" "July 2025" "" .SH "NAME" \fBbundle\-console\fR \- Open an IRB session with the bundle pre\-loaded .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-doctor.1 b/lib/bundler/man/bundle-doctor.1 index cd831f2fee..2b695dc2ee 100644 --- a/lib/bundler/man/bundle-doctor.1 +++ b/lib/bundler/man/bundle-doctor.1 @@ -1,14 +1,21 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-DOCTOR" "1" "March 2025" "" +.TH "BUNDLE\-DOCTOR" "1" "July 2025" "" .SH "NAME" \fBbundle\-doctor\fR \- Checks the bundle for common problems .SH "SYNOPSIS" -\fBbundle doctor\fR [\-\-quiet] [\-\-gemfile=GEMFILE] +\fBbundle doctor [diagnose]\fR [\-\-quiet] [\-\-gemfile=GEMFILE] [\-\-ssl] +.br +\fBbundle doctor ssl\fR [\-\-host=HOST] [\-\-tls\-version=VERSION] [\-\-verify\-mode=MODE] +.br +\fBbundle doctor\fR help [COMMAND] .SH "DESCRIPTION" +You can diagnose common Bundler problems with this command such as checking gem environment or SSL/TLS issue\. +.SH "SUB\-COMMANDS" +.SS "diagnose (default command)" Checks your Gemfile and gem environment for common problems\. If issues are detected, Bundler prints them and exits status 1\. Otherwise, Bundler prints a success message and exits status 0\. .P -Examples of common problems caught by bundle\-doctor include: +Examples of common problems caught include: .IP "\(bu" 4 Invalid Bundler settings .IP "\(bu" 4 @@ -20,11 +27,43 @@ Uninstalled gems .IP "\(bu" 4 Missing dependencies .IP "" 0 -.SH "OPTIONS" +.P +\fBOPTIONS\fR .TP \fB\-\-quiet\fR Only output warnings and errors\. .TP \fB\-\-gemfile=GEMFILE\fR The location of the Gemfile(5) which Bundler should use\. This defaults to a Gemfile(5) in the current working directory\. In general, Bundler will assume that the location of the Gemfile(5) is also the project's root and will try to find \fBGemfile\.lock\fR and \fBvendor/cache\fR relative to this location\. +.TP +\fB\-\-ssl\fR +Diagnose common SSL problems when connecting to https://rubygems\.org\. +.IP +This flag runs the \fBbundle doctor ssl\fR subcommand with default values underneath\. +.SS "ssl" +If you've experienced issues related to SSL certificates and/or TLS versions while connecting to https://rubygems\.org, this command can help troubleshoot common problems\. The diagnostic will perform a few checks such as: +.IP "\(bu" 4 +Verify the Ruby OpenSSL version installed on your system\. +.IP "\(bu" 4 +Check the OpenSSL library version used for compilation\. +.IP "\(bu" 4 +Ensure CA certificates are correctly setup on your machine\. +.IP "\(bu" 4 +Open a TLS connection and verify the outcome\. +.IP "" 0 +.P +\fBOPTIONS\fR +.TP +\fB\-\-host=HOST\fR +Perform the diagnostic on HOST\. Defaults to \fBrubygems\.org\fR\. +.TP +\fB\-\-tls\-version=VERSION\fR +Specify the TLS version when opening the connection to HOST\. +.IP +Accepted values are: \fB1\.1\fR or \fB1\.2\fR\. +.TP +\fB\-\-verify\-mode=MODE\fR +Specify the TLS verify mode when opening the connection to HOST\. Defaults to \fBSSL_VERIFY_PEER\fR\. +.IP +Accepted values are: \fBCLIENT_ONCE\fR, \fBFAIL_IF_NO_PEER_CERT\fR, \fBNONE\fR, \fBPEER\fR\. diff --git a/lib/bundler/man/bundle-doctor.1.ronn b/lib/bundler/man/bundle-doctor.1.ronn index 5970f6188b..7e8a21b1c5 100644 --- a/lib/bundler/man/bundle-doctor.1.ronn +++ b/lib/bundler/man/bundle-doctor.1.ronn @@ -3,16 +3,27 @@ bundle-doctor(1) -- Checks the bundle for common problems ## SYNOPSIS -`bundle doctor` [--quiet] - [--gemfile=GEMFILE] +`bundle doctor [diagnose]` [--quiet] + [--gemfile=GEMFILE] + [--ssl]
+`bundle doctor ssl` [--host=HOST] + [--tls-version=VERSION] + [--verify-mode=MODE]
+`bundle doctor` help [COMMAND] ## DESCRIPTION +You can diagnose common Bundler problems with this command such as checking gem environment or SSL/TLS issue. + +## SUB-COMMANDS + +### diagnose (default command) + Checks your Gemfile and gem environment for common problems. If issues are detected, Bundler prints them and exits status 1. Otherwise, Bundler prints a success message and exits status 0. -Examples of common problems caught by bundle-doctor include: +Examples of common problems caught include: * Invalid Bundler settings * Mismatched Ruby versions @@ -20,7 +31,7 @@ Examples of common problems caught by bundle-doctor include: * Uninstalled gems * Missing dependencies -## OPTIONS +**OPTIONS** * `--quiet`: Only output warnings and errors. @@ -31,3 +42,36 @@ Examples of common problems caught by bundle-doctor include: will assume that the location of the Gemfile(5) is also the project's root and will try to find `Gemfile.lock` and `vendor/cache` relative to this location. + +* `--ssl`: + Diagnose common SSL problems when connecting to https://rubygems.org. + + This flag runs the `bundle doctor ssl` subcommand with default values + underneath. + +### ssl + +If you've experienced issues related to SSL certificates and/or TLS versions while connecting +to https://rubygems.org, this command can help troubleshoot common problems. +The diagnostic will perform a few checks such as: + +* Verify the Ruby OpenSSL version installed on your system. +* Check the OpenSSL library version used for compilation. +* Ensure CA certificates are correctly setup on your machine. +* Open a TLS connection and verify the outcome. + +**OPTIONS** + +* `--host=HOST`: + Perform the diagnostic on HOST. Defaults to `rubygems.org`. + +* `--tls-version=VERSION`: + Specify the TLS version when opening the connection to HOST. + + Accepted values are: `1.1` or `1.2`. + +* `--verify-mode=MODE`: + Specify the TLS verify mode when opening the connection to HOST. + Defaults to `SSL_VERIFY_PEER`. + + Accepted values are: `CLIENT_ONCE`, `FAIL_IF_NO_PEER_CERT`, `NONE`, `PEER`. diff --git a/lib/bundler/man/bundle-env.1 b/lib/bundler/man/bundle-env.1 index c936294827..3e6c9f6e17 100644 --- a/lib/bundler/man/bundle-env.1 +++ b/lib/bundler/man/bundle-env.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-ENV" "1" "March 2025" "" +.TH "BUNDLE\-ENV" "1" "July 2025" "" .SH "NAME" \fBbundle\-env\fR \- Print information about the environment Bundler is running under .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-exec.1 b/lib/bundler/man/bundle-exec.1 index 0ebbb4c198..79cdad0288 100644 --- a/lib/bundler/man/bundle-exec.1 +++ b/lib/bundler/man/bundle-exec.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-EXEC" "1" "March 2025" "" +.TH "BUNDLE\-EXEC" "1" "July 2025" "" .SH "NAME" \fBbundle\-exec\fR \- Execute a command in the context of the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-fund.1 b/lib/bundler/man/bundle-fund.1 index 641d8cf864..3f6e3a46df 100644 --- a/lib/bundler/man/bundle-fund.1 +++ b/lib/bundler/man/bundle-fund.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-FUND" "1" "March 2025" "" +.TH "BUNDLE\-FUND" "1" "July 2025" "" .SH "NAME" \fBbundle\-fund\fR \- Lists information about gems seeking funding assistance .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-gem.1 b/lib/bundler/man/bundle-gem.1 index 65882afa4f..44a02c033c 100644 --- a/lib/bundler/man/bundle-gem.1 +++ b/lib/bundler/man/bundle-gem.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-GEM" "1" "March 2025" "" +.TH "BUNDLE\-GEM" "1" "July 2025" "" .SH "NAME" \fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem .SH "SYNOPSIS" @@ -19,67 +19,90 @@ The generated project skeleton can be customized with OPTIONS, as explained belo \fBgem\.test\fR .IP "" 0 .SH "OPTIONS" -.IP "\(bu" 4 -\fB\-\-exe\fR, \fB\-\-bin\fR, \fB\-b\fR: Specify that Bundler should create a binary executable (as \fBexe/GEM_NAME\fR) in the generated rubygem project\. This binary will also be added to the \fBGEM_NAME\.gemspec\fR manifest\. This behavior is disabled by default\. -.IP "\(bu" 4 -\fB\-\-no\-exe\fR: Do not create a binary (overrides \fB\-\-exe\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-coc\fR: Add a \fBCODE_OF_CONDUCT\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-coc\fR: Do not create a \fBCODE_OF_CONDUCT\.md\fR (overrides \fB\-\-coc\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-changelog\fR Add a \fBCHANGELOG\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-changelog\fR: Do not create a \fBCHANGELOG\.md\fR (overrides \fB\-\-changelog\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-ext=c\fR, \fB\-\-ext=rust\fR: Add boilerplate for C or Rust (currently magnus \fIhttps://docs\.rs/magnus\fR based) extension code to the generated project\. This behavior is disabled by default\. -.IP "\(bu" 4 -\fB\-\-no\-ext\fR: Do not add extension code (overrides \fB\-\-ext\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-git\fR: Initialize a git repo inside your library\. -.IP "\(bu" 4 -\fB\-\-github\-username=GITHUB_USERNAME\fR: Fill in GitHub username on README so that you don't have to do it manually\. Set a default with \fBbundle config set \-\-global gem\.github_username \fR\. -.IP "\(bu" 4 -\fB\-\-mit\fR: Add an MIT license to a \fBLICENSE\.txt\fR file in the root of the generated project\. Your name from the global git config is used for the copyright statement\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-mit\fR: Do not create a \fBLICENSE\.txt\fR (overrides \fB\-\-mit\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-t\fR, \fB\-\-test=minitest\fR, \fB\-\-test=rspec\fR, \fB\-\-test=test\-unit\fR: Specify the test framework that Bundler should use when generating the project\. Acceptable values are \fBminitest\fR, \fBrspec\fR and \fBtest\-unit\fR\. The \fBGEM_NAME\.gemspec\fR will be configured and a skeleton test/spec directory will be created based on this option\. Given no option is specified: +.TP +\fB\-\-exe\fR, \fB\-\-bin\fR, \fB\-b\fR +Specify that Bundler should create a binary executable (as \fBexe/GEM_NAME\fR) in the generated rubygem project\. This binary will also be added to the \fBGEM_NAME\.gemspec\fR manifest\. This behavior is disabled by default\. +.TP +\fB\-\-no\-exe\fR +Do not create a binary (overrides \fB\-\-exe\fR specified in the global config)\. +.TP +\fB\-\-coc\fR +Add a \fBCODE_OF_CONDUCT\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. +.TP +\fB\-\-no\-coc\fR +Do not create a \fBCODE_OF_CONDUCT\.md\fR (overrides \fB\-\-coc\fR specified in the global config)\. +.TP +\fB\-\-changelog\fR +Add a \fBCHANGELOG\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. Update the default with \fBbundle config set \-\-global gem\.changelog \fR\. +.TP +\fB\-\-no\-changelog\fR +Do not create a \fBCHANGELOG\.md\fR (overrides \fB\-\-changelog\fR specified in the global config)\. +.TP +\fB\-\-ext=c\fR, \fB\-\-ext=rust\fR +Add boilerplate for C or Rust (currently magnus \fIhttps://docs\.rs/magnus\fR based) extension code to the generated project\. This behavior is disabled by default\. +.TP +\fB\-\-no\-ext\fR +Do not add extension code (overrides \fB\-\-ext\fR specified in the global config)\. +.TP +\fB\-\-git\fR +Initialize a git repo inside your library\. +.TP +\fB\-\-github\-username=GITHUB_USERNAME\fR +Fill in GitHub username on README so that you don't have to do it manually\. Set a default with \fBbundle config set \-\-global gem\.github_username \fR\. +.TP +\fB\-\-mit\fR +Add an MIT license to a \fBLICENSE\.txt\fR file in the root of the generated project\. Your name from the global git config is used for the copyright statement\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. +.TP +\fB\-\-no\-mit\fR +Do not create a \fBLICENSE\.txt\fR (overrides \fB\-\-mit\fR specified in the global config)\. +.TP +\fB\-t\fR, \fB\-\-test=minitest\fR, \fB\-\-test=rspec\fR, \fB\-\-test=test\-unit\fR +Specify the test framework that Bundler should use when generating the project\. Acceptable values are \fBminitest\fR, \fBrspec\fR and \fBtest\-unit\fR\. The \fBGEM_NAME\.gemspec\fR will be configured and a skeleton test/spec directory will be created based on this option\. Given no option is specified: .IP When Bundler is configured to generate tests, this defaults to Bundler's global config setting \fBgem\.test\fR\. .IP When Bundler is configured to not generate tests, an interactive prompt will be displayed and the answer will be used for the current rubygem project\. .IP When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-test\fR: Do not use a test framework (overrides \fB\-\-test\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-changelog\fR: Generate changelog file\. Set a default with \fBbundle config set \-\-global gem\.changelog true\fR\. -.IP "\(bu" 4 -\fB\-\-ci\fR, \fB\-\-ci=circle\fR, \fB\-\-ci=github\fR, \fB\-\-ci=gitlab\fR: Specify the continuous integration service that Bundler should use when generating the project\. Acceptable values are \fBgithub\fR, \fBgitlab\fR and \fBcircle\fR\. A configuration file will be generated in the project directory\. Given no option is specified: +.TP +\fB\-\-no\-test\fR +Do not use a test framework (overrides \fB\-\-test\fR specified in the global config)\. +.TP +\fB\-\-ci\fR, \fB\-\-ci=circle\fR, \fB\-\-ci=github\fR, \fB\-\-ci=gitlab\fR +Specify the continuous integration service that Bundler should use when generating the project\. Acceptable values are \fBgithub\fR, \fBgitlab\fR and \fBcircle\fR\. A configuration file will be generated in the project directory\. Given no option is specified: .IP When Bundler is configured to generate CI files, this defaults to Bundler's global config setting \fBgem\.ci\fR\. .IP When Bundler is configured to not generate CI files, an interactive prompt will be displayed and the answer will be used for the current rubygem project\. .IP When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-ci\fR: Do not use a continuous integration service (overrides \fB\-\-ci\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-linter\fR, \fB\-\-linter=rubocop\fR, \fB\-\-linter=standard\fR: Specify the linter and code formatter that Bundler should add to the project's development dependencies\. Acceptable values are \fBrubocop\fR and \fBstandard\fR\. A configuration file will be generated in the project directory\. Given no option is specified: +.TP +\fB\-\-no\-ci\fR +Do not use a continuous integration service (overrides \fB\-\-ci\fR specified in the global config)\. +.TP +\fB\-\-linter\fR, \fB\-\-linter=rubocop\fR, \fB\-\-linter=standard\fR +Specify the linter and code formatter that Bundler should add to the project's development dependencies\. Acceptable values are \fBrubocop\fR and \fBstandard\fR\. A configuration file will be generated in the project directory\. Given no option is specified: .IP When Bundler is configured to add a linter, this defaults to Bundler's global config setting \fBgem\.linter\fR\. .IP When Bundler is configured not to add a linter, an interactive prompt will be displayed and the answer will be used for the current rubygem project\. .IP When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-linter\fR: Do not add a linter (overrides \fB\-\-linter\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-rubocop\fR: Add rubocop to the generated Rakefile and gemspec\. Set a default with \fBbundle config set \-\-global gem\.rubocop true\fR\. -.IP "\(bu" 4 -\fB\-\-edit=EDIT\fR, \fB\-e=EDIT\fR: Open the resulting GEM_NAME\.gemspec in EDIT, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\. -.IP "" 0 +.TP +\fB\-\-no\-linter\fR +Do not add a linter (overrides \fB\-\-linter\fR specified in the global config)\. +.TP +\fB\-\-rubocop\fR +Add rubocop to the generated Rakefile and gemspec\. Set a default with \fBbundle config set \-\-global gem\.rubocop true\fR\. +.TP +\fB\-\-edit=EDIT\fR, \fB\-e=EDIT\fR +Open the resulting GEM_NAME\.gemspec in EDIT, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\. +.TP +\fB\-\-bundle\fR +Run \fBbundle install\fR after creating the gem\. +.TP +\fB\-\-no\-bundle\fR +Do not run \fBbundle install\fR after creating the gem\. .SH "SEE ALSO" .IP "\(bu" 4 bundle config(1) \fIbundle\-config\.1\.html\fR diff --git a/lib/bundler/man/bundle-gem.1.ronn b/lib/bundler/man/bundle-gem.1.ronn index 13dc55c310..b1327aa342 100644 --- a/lib/bundler/man/bundle-gem.1.ronn +++ b/lib/bundler/man/bundle-gem.1.ronn @@ -41,10 +41,11 @@ configuration file using the following names: Do not create a `CODE_OF_CONDUCT.md` (overrides `--coc` specified in the global config). -* `--changelog` +* `--changelog`: Add a `CHANGELOG.md` file to the root of the generated project. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future `bundle gem` use. + Update the default with `bundle config set --global gem.changelog `. * `--no-changelog`: Do not create a `CHANGELOG.md` (overrides `--changelog` specified in the @@ -95,9 +96,6 @@ configuration file using the following names: Do not use a test framework (overrides `--test` specified in the global config). -* `--changelog`: - Generate changelog file. Set a default with `bundle config set --global gem.changelog true`. - * `--ci`, `--ci=circle`, `--ci=github`, `--ci=gitlab`: Specify the continuous integration service that Bundler should use when generating the project. Acceptable values are `github`, `gitlab` @@ -144,6 +142,12 @@ configuration file using the following names: Open the resulting GEM_NAME.gemspec in EDIT, or the default editor if not specified. The default is `$BUNDLER_EDITOR`, `$VISUAL`, or `$EDITOR`. +* `--bundle`: + Run `bundle install` after creating the gem. + +* `--no-bundle`: + Do not run `bundle install` after creating the gem. + ## SEE ALSO * [bundle config(1)](bundle-config.1.html) diff --git a/lib/bundler/man/bundle-help.1 b/lib/bundler/man/bundle-help.1 index d8dd4660dc..e6bbea6dad 100644 --- a/lib/bundler/man/bundle-help.1 +++ b/lib/bundler/man/bundle-help.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-HELP" "1" "March 2025" "" +.TH "BUNDLE\-HELP" "1" "July 2025" "" .SH "NAME" \fBbundle\-help\fR \- Displays detailed help for each subcommand .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-info.1 b/lib/bundler/man/bundle-info.1 index 8124836519..435518e120 100644 --- a/lib/bundler/man/bundle-info.1 +++ b/lib/bundler/man/bundle-info.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-INFO" "1" "March 2025" "" +.TH "BUNDLE\-INFO" "1" "July 2025" "" .SH "NAME" \fBbundle\-info\fR \- Show information for the given gem in your bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-init.1 b/lib/bundler/man/bundle-init.1 index 2e4b99b28a..48b3232b8c 100644 --- a/lib/bundler/man/bundle-init.1 +++ b/lib/bundler/man/bundle-init.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-INIT" "1" "March 2025" "" +.TH "BUNDLE\-INIT" "1" "July 2025" "" .SH "NAME" \fBbundle\-init\fR \- Generates a Gemfile into the current working directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-inject.1 b/lib/bundler/man/bundle-inject.1 index 54cacaa56d..abc63c392e 100644 --- a/lib/bundler/man/bundle-inject.1 +++ b/lib/bundler/man/bundle-inject.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-INJECT" "1" "March 2025" "" +.TH "BUNDLE\-INJECT" "1" "July 2025" "" .SH "NAME" \fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile .SH "SYNOPSIS" @@ -20,7 +20,7 @@ bundle inject 'rack' '> 0' .P This will inject the 'rack' gem with a version greater than 0 in your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock\. .P -The \fBbundle inject\fR command was deprecated in Bundler 2\.1 and will be removed in Bundler 3\.0\. +The \fBbundle inject\fR command was deprecated in Bundler 2\.1 and will be removed in Bundler 4\.0\. .SH "OPTIONS" .TP \fB\-\-source=SOURCE\fR diff --git a/lib/bundler/man/bundle-inject.1.ronn b/lib/bundler/man/bundle-inject.1.ronn index e2a911b6d8..7f6f0fb5b6 100644 --- a/lib/bundler/man/bundle-inject.1.ronn +++ b/lib/bundler/man/bundle-inject.1.ronn @@ -21,7 +21,7 @@ Example: This will inject the 'rack' gem with a version greater than 0 in your [`Gemfile(5)`][Gemfile(5)] and Gemfile.lock. -The `bundle inject` command was deprecated in Bundler 2.1 and will be removed in Bundler 3.0. +The `bundle inject` command was deprecated in Bundler 2.1 and will be removed in Bundler 4.0. ## OPTIONS diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1 index 272f4187ed..bf06747558 100644 --- a/lib/bundler/man/bundle-install.1 +++ b/lib/bundler/man/bundle-install.1 @@ -1,10 +1,10 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-INSTALL" "1" "March 2025" "" +.TH "BUNDLE\-INSTALL" "1" "July 2025" "" .SH "NAME" \fBbundle\-install\fR \- Install the dependencies specified in your Gemfile .SH "SYNOPSIS" -\fBbundle install\fR [\-\-binstubs[=DIRECTORY]] [\-\-clean] [\-\-deployment] [\-\-frozen] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-no\-cache] [\-\-no\-prune] [\-\-path PATH] [\-\-prefer\-local] [\-\-quiet] [\-\-redownload] [\-\-retry=NUMBER] [\-\-shebang=SHEBANG] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-system] [\-\-trust\-policy=TRUST\-POLICY] [\-\-target\-rbconfig=TARGET\-RBCONFIG] [\-\-with=GROUP[ GROUP\|\.\|\.\|\.]] [\-\-without=GROUP[ GROUP\|\.\|\.\|\.]] +\fBbundle install\fR [\-\-binstubs[=DIRECTORY]] [\-\-clean] [\-\-deployment] [\-\-force] [\-\-frozen] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-no\-cache] [\-\-no\-prune] [\-\-path PATH] [\-\-prefer\-local] [\-\-quiet] [\-\-retry=NUMBER] [\-\-shebang=SHEBANG] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-system] [\-\-trust\-policy=TRUST\-POLICY] [\-\-target\-rbconfig=TARGET\-RBCONFIG] [\-\-with=GROUP[ GROUP\|\.\|\.\|\.]] [\-\-without=GROUP[ GROUP\|\.\|\.\|\.]] .SH "DESCRIPTION" Install the gems specified in your Gemfile(5)\. If this is the first time you run bundle install (and a \fBGemfile\.lock\fR does not exist), Bundler will fetch all remote sources, resolve dependencies and install all needed gems\. .P @@ -29,8 +29,8 @@ In \fIdeployment mode\fR, Bundler will 'roll\-out' the bundle for production or .IP This option is deprecated in favor of the \fBdeployment\fR setting\. .TP -\fB\-\-redownload\fR, \fB\-\-force\fR -Force download every gem, even if the required versions are already available locally\. +\fB\-\-force\fR, \fB\-\-redownload\fR +Force reinstalling every gem, even if already installed\. .TP \fB\-\-frozen\fR Do not allow the Gemfile\.lock to be updated after this install\. Exits non\-zero if there are going to be changes to the Gemfile\.lock\. diff --git a/lib/bundler/man/bundle-install.1.ronn b/lib/bundler/man/bundle-install.1.ronn index a3606826df..cc6241dd67 100644 --- a/lib/bundler/man/bundle-install.1.ronn +++ b/lib/bundler/man/bundle-install.1.ronn @@ -6,6 +6,7 @@ bundle-install(1) -- Install the dependencies specified in your Gemfile `bundle install` [--binstubs[=DIRECTORY]] [--clean] [--deployment] + [--force] [--frozen] [--full-index] [--gemfile=GEMFILE] @@ -16,7 +17,6 @@ bundle-install(1) -- Install the dependencies specified in your Gemfile [--path PATH] [--prefer-local] [--quiet] - [--redownload] [--retry=NUMBER] [--shebang=SHEBANG] [--standalone[=GROUP[ GROUP...]]] @@ -80,9 +80,8 @@ automatically and that requires `bundler` to silently remember them. Since This option is deprecated in favor of the `deployment` setting. -* `--redownload`, `--force`: - Force download every gem, even if the required versions are already available - locally. +* `--force`, `--redownload`: + Force reinstalling every gem, even if already installed. * `--frozen`: Do not allow the Gemfile.lock to be updated after this install. Exits diff --git a/lib/bundler/man/bundle-issue.1 b/lib/bundler/man/bundle-issue.1 index 02d28c91ba..668da5712f 100644 --- a/lib/bundler/man/bundle-issue.1 +++ b/lib/bundler/man/bundle-issue.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-ISSUE" "1" "March 2025" "" +.TH "BUNDLE\-ISSUE" "1" "July 2025" "" .SH "NAME" \fBbundle\-issue\fR \- Get help reporting Bundler issues .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-licenses.1 b/lib/bundler/man/bundle-licenses.1 index d0dbf3913c..cccb860854 100644 --- a/lib/bundler/man/bundle-licenses.1 +++ b/lib/bundler/man/bundle-licenses.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-LICENSES" "1" "March 2025" "" +.TH "BUNDLE\-LICENSES" "1" "July 2025" "" .SH "NAME" \fBbundle\-licenses\fR \- Print the license of all gems in the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-list.1 b/lib/bundler/man/bundle-list.1 index cd09ccab31..26c2833218 100644 --- a/lib/bundler/man/bundle-list.1 +++ b/lib/bundler/man/bundle-list.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-LIST" "1" "March 2025" "" +.TH "BUNDLE\-LIST" "1" "July 2025" "" .SH "NAME" \fBbundle\-list\fR \- List all the gems in the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-lock.1 b/lib/bundler/man/bundle-lock.1 index 8c9b94e8e2..5faa46da18 100644 --- a/lib/bundler/man/bundle-lock.1 +++ b/lib/bundler/man/bundle-lock.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-LOCK" "1" "March 2025" "" +.TH "BUNDLE\-LOCK" "1" "July 2025" "" .SH "NAME" \fBbundle\-lock\fR \- Creates / Updates a lockfile without installing .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-open.1 b/lib/bundler/man/bundle-open.1 index eb4ac2e859..e8a24f3541 100644 --- a/lib/bundler/man/bundle-open.1 +++ b/lib/bundler/man/bundle-open.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-OPEN" "1" "March 2025" "" +.TH "BUNDLE\-OPEN" "1" "July 2025" "" .SH "NAME" \fBbundle\-open\fR \- Opens the source directory for a gem in your bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-outdated.1 b/lib/bundler/man/bundle-outdated.1 index 4f8a2cc56f..3259b0f023 100644 --- a/lib/bundler/man/bundle-outdated.1 +++ b/lib/bundler/man/bundle-outdated.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-OUTDATED" "1" "March 2025" "" +.TH "BUNDLE\-OUTDATED" "1" "July 2025" "" .SH "NAME" \fBbundle\-outdated\fR \- List installed gems with newer versions available .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-platform.1 b/lib/bundler/man/bundle-platform.1 index bdac52f937..1032acc4e6 100644 --- a/lib/bundler/man/bundle-platform.1 +++ b/lib/bundler/man/bundle-platform.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-PLATFORM" "1" "March 2025" "" +.TH "BUNDLE\-PLATFORM" "1" "July 2025" "" .SH "NAME" \fBbundle\-platform\fR \- Displays platform compatibility information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-plugin.1 b/lib/bundler/man/bundle-plugin.1 index ded328dbd8..d9e195906f 100644 --- a/lib/bundler/man/bundle-plugin.1 +++ b/lib/bundler/man/bundle-plugin.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-PLUGIN" "1" "March 2025" "" +.TH "BUNDLE\-PLUGIN" "1" "July 2025" "" .SH "NAME" \fBbundle\-plugin\fR \- Manage Bundler plugins .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-pristine.1 b/lib/bundler/man/bundle-pristine.1 index 294ef179a7..5c7871069c 100644 --- a/lib/bundler/man/bundle-pristine.1 +++ b/lib/bundler/man/bundle-pristine.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-PRISTINE" "1" "March 2025" "" +.TH "BUNDLE\-PRISTINE" "1" "July 2025" "" .SH "NAME" \fBbundle\-pristine\fR \- Restores installed gems to their pristine condition .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-remove.1 b/lib/bundler/man/bundle-remove.1 index 2e42a12de3..df8ce3232a 100644 --- a/lib/bundler/man/bundle-remove.1 +++ b/lib/bundler/man/bundle-remove.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-REMOVE" "1" "March 2025" "" +.TH "BUNDLE\-REMOVE" "1" "July 2025" "" .SH "NAME" \fBbundle\-remove\fR \- Removes gems from the Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-show.1 b/lib/bundler/man/bundle-show.1 index d460e7a256..ca10c00701 100644 --- a/lib/bundler/man/bundle-show.1 +++ b/lib/bundler/man/bundle-show.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-SHOW" "1" "March 2025" "" +.TH "BUNDLE\-SHOW" "1" "July 2025" "" .SH "NAME" \fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-update.1 b/lib/bundler/man/bundle-update.1 index 855a5049aa..8655aa5cd3 100644 --- a/lib/bundler/man/bundle-update.1 +++ b/lib/bundler/man/bundle-update.1 @@ -1,10 +1,10 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-UPDATE" "1" "March 2025" "" +.TH "BUNDLE\-UPDATE" "1" "July 2025" "" .SH "NAME" \fBbundle\-update\fR \- Update your gems to the latest available versions .SH "SYNOPSIS" -\fBbundle update\fR \fI*gems\fR [\-\-all] [\-\-group=NAME] [\-\-source=NAME] [\-\-local] [\-\-ruby] [\-\-bundler[=VERSION]] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-quiet] [\-\-patch|\-\-minor|\-\-major] [\-\-pre] [\-\-redownload] [\-\-strict] [\-\-conservative] +\fBbundle update\fR \fI*gems\fR [\-\-all] [\-\-group=NAME] [\-\-source=NAME] [\-\-local] [\-\-ruby] [\-\-bundler[=VERSION]] [\-\-force] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-quiet] [\-\-patch|\-\-minor|\-\-major] [\-\-pre] [\-\-strict] [\-\-conservative] .SH "DESCRIPTION" Update the gems specified (all gems, if \fB\-\-all\fR flag is used), ignoring the previously installed gems specified in the \fBGemfile\.lock\fR\. In general, you should use bundle install(1) \fIbundle\-install\.1\.html\fR to install the same exact gems and versions across machines\. .P @@ -29,6 +29,9 @@ Update the locked version of Ruby to the current version of Ruby\. \fB\-\-bundler[=BUNDLER]\fR Update the locked version of bundler to the invoked bundler version\. .TP +\fB\-\-force\fR, \fB\-\-redownload\fR +Force reinstalling every gem, even if already installed\. +.TP \fB\-\-full\-index\fR Fall back to using the single\-file index of all gems\. .TP @@ -44,9 +47,6 @@ Retry failed network or git requests for \fInumber\fR times\. \fB\-\-quiet\fR Only output warnings and errors\. .TP -\fB\-\-redownload\fR, \fB\-\-force\fR -Force downloading every gem\. -.TP \fB\-\-patch\fR Prefer updating only to next patch version\. .TP diff --git a/lib/bundler/man/bundle-update.1.ronn b/lib/bundler/man/bundle-update.1.ronn index 1b8b31951d..bfe381677c 100644 --- a/lib/bundler/man/bundle-update.1.ronn +++ b/lib/bundler/man/bundle-update.1.ronn @@ -9,13 +9,13 @@ bundle-update(1) -- Update your gems to the latest available versions [--local] [--ruby] [--bundler[=VERSION]] + [--force] [--full-index] [--gemfile=GEMFILE] [--jobs=NUMBER] [--quiet] [--patch|--minor|--major] [--pre] - [--redownload] [--strict] [--conservative] @@ -54,6 +54,9 @@ gem. * `--bundler[=BUNDLER]`: Update the locked version of bundler to the invoked bundler version. +* `--force`, `--redownload`: + Force reinstalling every gem, even if already installed. + * `--full-index`: Fall back to using the single-file index of all gems. @@ -70,9 +73,6 @@ gem. * `--quiet`: Only output warnings and errors. -* `--redownload`, `--force`: - Force downloading every gem. - * `--patch`: Prefer updating only to next patch version. diff --git a/lib/bundler/man/bundle-version.1 b/lib/bundler/man/bundle-version.1 index 17add566d8..e591f59766 100644 --- a/lib/bundler/man/bundle-version.1 +++ b/lib/bundler/man/bundle-version.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-VERSION" "1" "March 2025" "" +.TH "BUNDLE\-VERSION" "1" "July 2025" "" .SH "NAME" \fBbundle\-version\fR \- Prints Bundler version information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-viz.1 b/lib/bundler/man/bundle-viz.1 index 17e6f90cca..b5dd1db7b7 100644 --- a/lib/bundler/man/bundle-viz.1 +++ b/lib/bundler/man/bundle-viz.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-VIZ" "1" "March 2025" "" +.TH "BUNDLE\-VIZ" "1" "July 2025" "" .SH "NAME" \fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1 index 3b40f58210..269e28141d 100644 --- a/lib/bundler/man/bundle.1 +++ b/lib/bundler/man/bundle.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE" "1" "March 2025" "" +.TH "BUNDLE" "1" "July 2025" "" .SH "NAME" \fBbundle\fR \- Ruby Dependency Management .SH "SYNOPSIS" diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index f52864a2bf..c926e1ff2d 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "GEMFILE" "5" "March 2025" "" +.TH "GEMFILE" "5" "July 2025" "" .SH "NAME" \fBGemfile\fR \- A format for describing gem dependencies for Ruby programs .SH "SYNOPSIS" diff --git a/lib/bundler/match_platform.rb b/lib/bundler/match_platform.rb index ece9fb8679..479818e5ec 100644 --- a/lib/bundler/match_platform.rb +++ b/lib/bundler/match_platform.rb @@ -1,23 +1,42 @@ # frozen_string_literal: true -require_relative "gem_helpers" - module Bundler module MatchPlatform - include GemHelpers - - def match_platform(p) - MatchPlatform.platforms_match?(platform, p) - end - - def self.platforms_match?(gemspec_platform, local_platform) - return true if gemspec_platform.nil? - return true if gemspec_platform == Gem::Platform::RUBY - return true if local_platform == gemspec_platform - gemspec_platform = Gem::Platform.new(gemspec_platform) - return true if gemspec_platform === local_platform + def installable_on_platform?(target_platform) # :nodoc: + return true if [Gem::Platform::RUBY, nil, target_platform].include?(platform) + return true if Gem::Platform.new(platform) === target_platform false end + + def self.select_best_platform_match(specs, platform, force_ruby: false, prefer_locked: false) + matching = select_all_platform_match(specs, platform, force_ruby: force_ruby, prefer_locked: prefer_locked) + + Gem::Platform.sort_and_filter_best_platform_match(matching, platform) + end + + def self.select_best_local_platform_match(specs, force_ruby: false) + local = Bundler.local_platform + matching = select_all_platform_match(specs, local, force_ruby: force_ruby).filter_map(&:materialized_for_installation) + + Gem::Platform.sort_best_platform_match(matching, local) + end + + def self.select_all_platform_match(specs, platform, force_ruby: false, prefer_locked: false) + matching = specs.select {|spec| spec.installable_on_platform?(force_ruby ? Gem::Platform::RUBY : platform) } + + specs.each(&:force_ruby_platform!) if force_ruby + + if prefer_locked + locked_originally = matching.select {|spec| spec.is_a?(::Bundler::LazySpecification) } + return locked_originally if locked_originally.any? + end + + matching + end + + def self.generic_local_platform_is_ruby? + Bundler.generic_local_platform == Gem::Platform::RUBY + end end end diff --git a/lib/bundler/materialization.rb b/lib/bundler/materialization.rb index 6542c07649..43124f25fb 100644 --- a/lib/bundler/materialization.rb +++ b/lib/bundler/materialization.rb @@ -22,9 +22,9 @@ module Bundler @specs ||= if @candidates.nil? [] elsif platform - GemHelpers.select_best_platform_match(@candidates, platform, force_ruby: dep.force_ruby_platform) + MatchPlatform.select_best_platform_match(@candidates, platform, force_ruby: dep.force_ruby_platform) else - GemHelpers.select_best_local_platform_match(@candidates, force_ruby: dep.force_ruby_platform || dep.default_force_ruby_platform) + MatchPlatform.select_best_local_platform_match(@candidates, force_ruby: dep.force_ruby_platform || dep.default_force_ruby_platform) end end diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index f5d1c57a11..fba9badec7 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -14,8 +14,6 @@ module Bundler require_relative "resolver/root" require_relative "resolver/strategy" - include GemHelpers - def initialize(base, gem_version_promoter, most_specific_locked_platform = nil) @source_requirements = base.source_requirements @base = base @@ -273,7 +271,7 @@ module Bundler next groups if platform_specs.all?(&:empty?) end - ruby_specs = select_best_platform_match(specs, Gem::Platform::RUBY) + ruby_specs = MatchPlatform.select_best_platform_match(specs, Gem::Platform::RUBY) ruby_group = Resolver::SpecGroup.new(ruby_specs) unless ruby_group.empty? diff --git a/lib/bundler/resolver/package.rb b/lib/bundler/resolver/package.rb index 0e86a4f84d..ff75e7b6bc 100644 --- a/lib/bundler/resolver/package.rb +++ b/lib/bundler/resolver/package.rb @@ -30,7 +30,7 @@ module Bundler def platform_specs(specs) platforms.map do |platform| prefer_locked = @new_platforms.include?(platform) ? false : !unlock? - GemHelpers.select_best_platform_match(specs, platform, prefer_locked: prefer_locked) + MatchPlatform.select_best_platform_match(specs, platform, prefer_locked: prefer_locked) end end diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb index 1f3fb0fdde..8cf3b56b83 100644 --- a/lib/bundler/rubygems_ext.rb +++ b/lib/bundler/rubygems_ext.rb @@ -13,15 +13,6 @@ require "rubygems" unless defined?(Gem) # `Gem::Source` from the redefined `Gem::Specification#source`. require "rubygems/source" -# Cherry-pick fixes to `Gem.ruby_version` to be useful for modern Bundler -# versions and ignore patchlevels -# (https://github.com/rubygems/rubygems/pull/5472, -# https://github.com/rubygems/rubygems/pull/5486). May be removed once RubyGems -# 3.3.12 support is dropped. -unless Gem.ruby_version.to_s == RUBY_VERSION || RUBY_PATCHLEVEL == -1 - Gem.instance_variable_set(:@ruby_version, Gem::Version.new(RUBY_VERSION)) -end - module Gem # Can be removed once RubyGems 3.5.11 support is dropped unless Gem.respond_to?(:freebsd_platform?) @@ -61,81 +52,122 @@ module Gem require "rubygems/platform" class Platform - JAVA = Gem::Platform.new("java") - MSWIN = Gem::Platform.new("mswin32") - MSWIN64 = Gem::Platform.new("mswin64") - MINGW = Gem::Platform.new("x86-mingw32") - X64_MINGW = [Gem::Platform.new("x64-mingw32"), - Gem::Platform.new("x64-mingw-ucrt")].freeze - UNIVERSAL_MINGW = Gem::Platform.new("universal-mingw") - WINDOWS = [MSWIN, MSWIN64, UNIVERSAL_MINGW].flatten.freeze - X64_LINUX = Gem::Platform.new("x86_64-linux") - X64_LINUX_MUSL = Gem::Platform.new("x86_64-linux-musl") + # Can be removed once RubyGems 3.6.9 support is dropped + unless respond_to?(:generic) + JAVA = Gem::Platform.new("java") # :nodoc: + MSWIN = Gem::Platform.new("mswin32") # :nodoc: + MSWIN64 = Gem::Platform.new("mswin64") # :nodoc: + MINGW = Gem::Platform.new("x86-mingw32") # :nodoc: + X64_MINGW_LEGACY = Gem::Platform.new("x64-mingw32") # :nodoc: + X64_MINGW = Gem::Platform.new("x64-mingw-ucrt") # :nodoc: + UNIVERSAL_MINGW = Gem::Platform.new("universal-mingw") # :nodoc: + WINDOWS = [MSWIN, MSWIN64, UNIVERSAL_MINGW].freeze # :nodoc: + X64_LINUX = Gem::Platform.new("x86_64-linux") # :nodoc: + X64_LINUX_MUSL = Gem::Platform.new("x86_64-linux-musl") # :nodoc: - if X64_LINUX === X64_LINUX_MUSL - remove_method :=== + GENERICS = [JAVA, *WINDOWS].freeze # :nodoc: + private_constant :GENERICS - def ===(other) - return nil unless Gem::Platform === other + GENERIC_CACHE = GENERICS.each_with_object({}) {|g, h| h[g] = g } # :nodoc: + private_constant :GENERIC_CACHE - # universal-mingw32 matches x64-mingw-ucrt - return true if (@cpu == "universal" || other.cpu == "universal") && - @os.start_with?("mingw") && other.os.start_with?("mingw") + class << self + ## + # Returns the generic platform for the given platform. - # cpu - ([nil,"universal"].include?(@cpu) || [nil, "universal"].include?(other.cpu) || @cpu == other.cpu || - (@cpu == "arm" && other.cpu.start_with?("armv"))) && + def generic(platform) + return Gem::Platform::RUBY if platform.nil? || platform == Gem::Platform::RUBY - # os - @os == other.os && + GENERIC_CACHE[platform] ||= begin + found = GENERICS.find do |match| + platform === match + end + found || Gem::Platform::RUBY + end + end - # version - ( - (@os != "linux" && (@version.nil? || other.version.nil?)) || - (@os == "linux" && (normalized_linux_version_ext == other.normalized_linux_version_ext || ["musl#{@version}", "musleabi#{@version}", "musleabihf#{@version}"].include?(other.version))) || - @version == other.version - ) - end + ## + # Returns the platform specificity match for the given spec platform and user platform. - # This is a copy of RubyGems 3.3.23 or higher `normalized_linux_method`. - # Once only 3.3.23 is supported, we can use the method in RubyGems. - def normalized_linux_version_ext - return nil unless @version + def platform_specificity_match(spec_platform, user_platform) + return -1 if spec_platform == user_platform + return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY - without_gnu_nor_abi_modifiers = @version.sub(/\Agnu/, "").sub(/eabi(hf)?\Z/, "") - return nil if without_gnu_nor_abi_modifiers.empty? + os_match(spec_platform, user_platform) + + cpu_match(spec_platform, user_platform) * 10 + + version_match(spec_platform, user_platform) * 100 + end - without_gnu_nor_abi_modifiers - end - end - end + ## + # Sorts and filters the best platform match for the given matching specs and platform. - Platform.singleton_class.module_eval do - unless Platform.singleton_methods.include?(:match_spec?) - def match_spec?(spec) - match_gem?(spec.platform, spec.name) - end + def sort_and_filter_best_platform_match(matching, platform) + return matching if matching.one? - def match_gem?(platform, gem_name) - match_platforms?(platform, Gem.platforms) - end - end + exact = matching.select {|spec| spec.platform == platform } + return exact if exact.any? - match_platforms_defined = Gem::Platform.respond_to?(:match_platforms?, true) + sorted_matching = sort_best_platform_match(matching, platform) + exemplary_spec = sorted_matching.first - if !match_platforms_defined || Gem::Platform.send(:match_platforms?, Gem::Platform::X64_LINUX_MUSL, [Gem::Platform::X64_LINUX]) + sorted_matching.take_while {|spec| same_specificity?(platform, spec, exemplary_spec) && same_deps?(spec, exemplary_spec) } + end - private + ## + # Sorts the best platform match for the given matching specs and platform. - remove_method :match_platforms? if match_platforms_defined + def sort_best_platform_match(matching, platform) + matching.sort_by.with_index do |spec, i| + [ + platform_specificity_match(spec.platform, platform), + i, # for stable sort + ] + end + end - def match_platforms?(platform, platforms) - platforms.any? do |local_platform| - platform.nil? || - local_platform == platform || - (local_platform != Gem::Platform::RUBY && platform =~ local_platform) + private + + def same_specificity?(platform, spec, exemplary_spec) + platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform) + end + + def same_deps?(spec, exemplary_spec) + spec.required_ruby_version == exemplary_spec.required_ruby_version && + spec.required_rubygems_version == exemplary_spec.required_rubygems_version && + spec.dependencies.sort == exemplary_spec.dependencies.sort + end + + def os_match(spec_platform, user_platform) + if spec_platform.os == user_platform.os + 0 + else + 1 + end + end + + def cpu_match(spec_platform, user_platform) + if spec_platform.cpu == user_platform.cpu + 0 + elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm") + 0 + elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal" + 1 + else + 2 + end + end + + def version_match(spec_platform, user_platform) + if spec_platform.version == user_platform.version + 0 + elsif spec_platform.version.nil? + 1 + else + 2 + end end end + end end @@ -144,9 +176,6 @@ module Gem # Can be removed once RubyGems 3.5.14 support is dropped VALIDATES_FOR_RESOLUTION = Specification.new.respond_to?(:validate_for_resolution).freeze - # Can be removed once RubyGems 3.3.15 support is dropped - FLATTENS_REQUIRED_PATHS = Specification.new.respond_to?(:flatten_require_paths).freeze - class Specification # Can be removed once RubyGems 3.5.15 support is dropped correct_array_attributes = @@default_value.select {|_k,v| v.is_a?(Array) }.keys @@ -158,7 +187,6 @@ module Gem require_relative "match_platform" include ::Bundler::MatchMetadata - include ::Bundler::MatchPlatform attr_accessor :remote, :relative_loaded_from @@ -214,23 +242,6 @@ module Gem full_gem_path end - unless const_defined?(:LATEST_RUBY_WITHOUT_PATCH_VERSIONS) - LATEST_RUBY_WITHOUT_PATCH_VERSIONS = Gem::Version.new("2.1") - - alias_method :rg_required_ruby_version=, :required_ruby_version= - def required_ruby_version=(req) - self.rg_required_ruby_version = req - - @required_ruby_version.requirements.map! do |op, v| - if v >= LATEST_RUBY_WITHOUT_PATCH_VERSIONS && v.release.segments.size == 4 - [op == "~>" ? "=" : op, Gem::Version.new(v.segments.tap {|s| s.delete_at(3) }.join("."))] - else - [op, v] - end - end - end - end - def insecurely_materialized? false end @@ -272,25 +283,16 @@ module Gem end end - unless FLATTENS_REQUIRED_PATHS - def flatten_require_paths - return unless raw_require_paths.first.is_a?(Array) + if Gem.rubygems_version < Gem::Version.new("3.5.22") + module FixPathSourceMissingExtensions + def missing_extensions? + return false if %w[Bundler::Source::Path Bundler::Source::Gemspec].include?(source.class.name) - warn "#{name} #{version} includes a gemspec with `require_paths` set to an array of arrays. Newer versions of this gem might've already fixed this" - raw_require_paths.flatten! - end - - class << self - module RequirePathFlattener - def from_yaml(input) - spec = super(input) - spec.flatten_require_paths - spec - end + super end - - prepend RequirePathFlattener end + + prepend FixPathSourceMissingExtensions end private @@ -401,6 +403,11 @@ module Gem @ignored = missing_extensions? end end + + # Can be removed once RubyGems 3.6.9 support is dropped + unless new.respond_to?(:installable_on_platform?) + include(::Bundler::MatchPlatform) + end end require "rubygems/name_tuple" @@ -471,15 +478,4 @@ module Gem Package::TarReader::Entry.prepend(FixFullNameEncoding) end - - require "rubygems/uri" - - # Can be removed once RubyGems 3.3.15 support is dropped - unless Gem::Uri.respond_to?(:redact) - class Uri - def self.redact(uri) - new(uri).redacted - end - end - end end diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb index eddf36278c..31f255d997 100644 --- a/lib/bundler/rubygems_integration.rb +++ b/lib/bundler/rubygems_integration.rb @@ -214,16 +214,11 @@ module Bundler e.requirement = dep.requirement raise e end - - # backwards compatibility shim, see https://github.com/rubygems/bundler/issues/5102 - kernel_class.send(:public, :gem) if Bundler.feature_flag.setup_makes_kernel_gem_public? end end # Used to give better error messages when activating specs outside of the current bundle def replace_bin_path(specs_by_name) - gem_class = (class << Gem; self; end) - redefine_method(gem_class, :find_spec_for_exe) do |gem_name, *args| exec_name = args.first raise ArgumentError, "you must supply exec_name" unless exec_name @@ -345,9 +340,13 @@ module Bundler Gem::Specification.all = specs end - redefine_method((class << Gem; self; end), :finish_resolve) do |*| + redefine_method(gem_class, :finish_resolve) do |*| [] end + + redefine_method(gem_class, :load_plugins) do |*| + load_plugin_files specs.flat_map(&:plugins) + end end def plain_specs @@ -447,6 +446,12 @@ module Bundler def default_stubs Gem::Specification.default_stubs("*.gemspec") end + + private + + def gem_class + class << Gem; self; end + end end def self.rubygems diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index 444b085bed..9b2416402b 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -71,7 +71,7 @@ module Bundler raise Bundler::GemRequireError.new e, "There was an error while trying to load the gem '#{file}'." end - rescue RuntimeError => e + rescue StandardError => e raise Bundler::GemRequireError.new e, "There was an error while trying to load the gem '#{file}'." end diff --git a/lib/bundler/self_manager.rb b/lib/bundler/self_manager.rb index 2aeac6be52..7db6c9f6f1 100644 --- a/lib/bundler/self_manager.rb +++ b/lib/bundler/self_manager.rb @@ -7,13 +7,15 @@ module Bundler # class SelfManager def restart_with_locked_bundler_if_needed - return unless needs_switching? && installed? + restart_version = find_restart_version + return unless restart_version && installed?(restart_version) restart_with(restart_version) end def install_locked_bundler_and_restart_with_it_if_needed - return unless needs_switching? + restart_version = find_restart_version + return unless restart_version if restart_version == lockfile_version Bundler.ui.info \ @@ -29,8 +31,6 @@ module Bundler end def update_bundler_and_restart_with_it_if_needed(target) - return unless autoswitching_applies? - spec = resolve_update_version_from(target) return unless spec @@ -38,7 +38,7 @@ module Bundler Bundler.ui.info "Updating bundler to #{version}." - install(spec) + install(spec) unless installed?(version) restart_with(version) end @@ -68,47 +68,37 @@ module Bundler def restart_with(version) configured_gem_home = ENV["GEM_HOME"] + configured_orig_gem_home = ENV["BUNDLER_ORIG_GEM_HOME"] configured_gem_path = ENV["GEM_PATH"] + configured_orig_gem_path = ENV["BUNDLER_ORIG_GEM_PATH"] - # Bundler specs need some stuff to be required before Bundler starts - # running, for example, for faking the compact index API. However, these - # flags are lost when we reexec to a different version of Bundler. In the - # future, we may be able to properly reconstruct the original Ruby - # invocation (see https://bugs.ruby-lang.org/issues/6648), but for now - # there's no way to do it, so we need to be explicit about how to re-exec. - # This may be a feature end users request at some point, but maybe by that - # time, we have builtin tools to do. So for now, we use an undocumented - # ENV variable only for our specs. - bundler_spec_original_cmd = ENV["BUNDLER_SPEC_ORIGINAL_CMD"] - if bundler_spec_original_cmd - require "shellwords" - cmd = [*Shellwords.shellsplit(bundler_spec_original_cmd), *ARGV] - else - argv0 = File.exist?($PROGRAM_NAME) ? $PROGRAM_NAME : Process.argv0 - cmd = [argv0, *ARGV] - cmd.unshift(Gem.ruby) unless File.executable?(argv0) - end + argv0 = File.exist?($PROGRAM_NAME) ? $PROGRAM_NAME : Process.argv0 + cmd = [argv0, *ARGV] + cmd.unshift(Gem.ruby) unless File.executable?(argv0) Bundler.with_original_env do Kernel.exec( - { "GEM_HOME" => configured_gem_home, "GEM_PATH" => configured_gem_path, "BUNDLER_VERSION" => version.to_s }, + { + "GEM_HOME" => configured_gem_home, + "BUNDLER_ORIG_GEM_HOME" => configured_orig_gem_home, + "GEM_PATH" => configured_gem_path, + "BUNDLER_ORIG_GEM_PATH" => configured_orig_gem_path, + "BUNDLER_VERSION" => version.to_s, + }, *cmd ) end end - def needs_switching? + def needs_switching?(restart_version) autoswitching_applies? && - Bundler.settings[:version] != "system" && released?(restart_version) && - !running?(restart_version) && - !updating? + !running?(restart_version) end def autoswitching_applies? ENV["BUNDLER_VERSION"].nil? && ruby_can_restart_with_same_arguments? && - SharedHelpers.in_bundle? && lockfile_version end @@ -142,6 +132,7 @@ module Bundler end def find_latest_matching_spec(requirement) + Bundler.configure local_result = find_latest_matching_spec_from_collection(local_specs, requirement) return local_result if local_result && requirement.specific? @@ -171,18 +162,14 @@ module Bundler $PROGRAM_NAME != "-e" end - def updating? - "update".start_with?(ARGV.first || " ") && ARGV[1..-1].any? {|a| a.start_with?("--bundler") } - end - - def installed? + def installed?(restart_version) Bundler.configure Bundler.rubygems.find_bundler(restart_version.to_s) end def current_version - @current_version ||= Gem::Version.new(Bundler::VERSION) + @current_version ||= Bundler.gem_version end def lockfile_version @@ -194,13 +181,16 @@ module Bundler @lockfile_version = nil end - def restart_version - return @restart_version if defined?(@restart_version) - # BUNDLE_VERSION=x.y.z - @restart_version = Gem::Version.new(Bundler.settings[:version]) - rescue ArgumentError - # BUNDLE_VERSION=lockfile - @restart_version = lockfile_version + def find_restart_version + return unless SharedHelpers.in_bundle? + + configured_version = Bundler.settings[:version] + return if configured_version == "system" + + restart_version = configured_version == "lockfile" ? lockfile_version : Gem::Version.new(configured_version) + return unless needs_switching?(restart_version) + + restart_version end end end diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index cde01e0181..d036754a78 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -8,12 +8,10 @@ module Bundler BOOL_KEYS = %w[ allow_offline_install - auto_clean_without_path auto_install cache_all cache_all_platforms clean - default_install_uses_path deployment disable_checksum_validation disable_exec_load @@ -27,6 +25,7 @@ module Bundler gem.changelog gem.coc gem.mit + gem.bundle git.allow_insecure global_gem_cache ignore_messages @@ -35,15 +34,13 @@ module Bundler lockfile_checksums no_install no_prune - path_relative_to_cwd path.system plugins prefer_patch - print_only_version_number - setup_makes_kernel_gem_public silence_deprecations silence_root_warning update_requires_all_flag + verbose ].freeze REMEMBERED_KEYS = %w[ @@ -86,6 +83,7 @@ module Bundler gemfile path shebang + simulate_version system_bindir trust-policy version @@ -274,7 +272,7 @@ module Bundler def use_system_gems? return true if system_path return false if explicit_path - !Bundler.feature_flag.default_install_uses_path? + !Bundler.feature_flag.bundler_4_mode? end def base_path diff --git a/lib/bundler/settings/validator.rb b/lib/bundler/settings/validator.rb index 0a57ea7f03..9aa1627fb2 100644 --- a/lib/bundler/settings/validator.rb +++ b/lib/bundler/settings/validator.rb @@ -74,29 +74,6 @@ module Bundler fail!(key, value, "`#{other_key}` is current set to #{other_setting.inspect}", "the `#{conflicting.join("`, `")}` groups conflict") end end - - rule %w[path], "relative paths are expanded relative to the current working directory" do |key, value, settings| - next if value.nil? - - path = Pathname.new(value) - next if !path.relative? || !Bundler.feature_flag.path_relative_to_cwd? - - path = path.expand_path - - root = begin - Bundler.root - rescue GemfileNotFound - Pathname.pwd.expand_path - end - - path = begin - path.relative_path_from(root) - rescue ArgumentError - path - end - - set(settings, key, path.to_s) - end end end end diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 1ef9d61361..2bdaabdaa7 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -133,13 +133,16 @@ module Bundler removed_message += suffix if removed_message end - bundler_major_version = Bundler.bundler_major_version - if bundler_major_version > major_version + require_relative "../bundler" + + feature_flag = Bundler.feature_flag + + if feature_flag.removed_major?(major_version) require_relative "errors" raise DeprecatedError, "[REMOVED] #{removed_message || message}" end - return unless bundler_major_version >= major_version && prints_major_deprecations? + return unless feature_flag.deprecated_major?(major_version) && prints_major_deprecations? Bundler.ui.warn("[DEPRECATED] #{message}") end @@ -386,7 +389,6 @@ module Bundler end def prints_major_deprecations? - require_relative "../bundler" return false if Bundler.settings[:silence_deprecations] require_relative "deprecate" return false if Bundler::Deprecate.skip diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb index 8230584260..f613377cb2 100644 --- a/lib/bundler/source/git/git_proxy.rb +++ b/lib/bundler/source/git/git_proxy.rb @@ -305,8 +305,8 @@ module Bundler end def has_revision_cached? - return unless @revision && path.exist? - git("cat-file", "-e", @revision, dir: path) + return unless commit && path.exist? + git("cat-file", "-e", commit, dir: path) true rescue GitError false @@ -408,7 +408,7 @@ module Bundler def capture3_args_for(cmd, dir) return ["git", *cmd] unless dir - if Bundler.feature_flag.bundler_3_mode? || supports_minus_c? + if Bundler.feature_flag.bundler_4_mode? || supports_minus_c? ["git", "-C", dir.to_s, *cmd] else ["git", *cmd, { chdir: dir.to_s }] diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb index 885dd96d85..ac76ae1fa0 100644 --- a/lib/bundler/source/path.rb +++ b/lib/bundler/source/path.rb @@ -167,6 +167,13 @@ module Bundler next unless spec = load_gemspec(file) spec.source = self + # The ignore attribute is for ignoring installed gems that don't + # have extensions correctly compiled for activation. In the case of + # path sources, there's a single version of each gem in the path + # source available to Bundler, so we always certainly want to + # consider that for activation and never makes sense to ignore it. + spec.ignored = false + # Validation causes extension_dir to be calculated, which depends # on #source, so we validate here instead of load_gemspec validate_spec(spec) diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb index d1308b1dfb..2f16281045 100644 --- a/lib/bundler/source_list.rb +++ b/lib/bundler/source_list.rb @@ -103,7 +103,7 @@ module Bundler end def get(source) - source_list_for(source).find {|s| equivalent_source?(source, s) } + source_list_for(source).find {|s| s.include?(source) } end def lock_sources @@ -265,9 +265,5 @@ module Bundler def equivalent_sources?(lock_sources, replacement_sources) lock_sources.sort_by(&:identifier) == replacement_sources.sort_by(&:identifier) end - - def equivalent_source?(source, other_source) - source == other_source - end end end diff --git a/lib/bundler/source_map.rb b/lib/bundler/source_map.rb index ca73e01f9d..a8e12d08c3 100644 --- a/lib/bundler/source_map.rb +++ b/lib/bundler/source_map.rb @@ -23,7 +23,7 @@ module Bundler if previous_source.nil? requirements[indirect_dependency_name] = source else - no_ambiguous_sources = Bundler.feature_flag.bundler_3_mode? + no_ambiguous_sources = Bundler.feature_flag.bundler_4_mode? msg = ["The gem '#{indirect_dependency_name}' was found in multiple relevant sources."] msg.concat [previous_source, source].map {|s| " * #{s}" }.sort diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb index 5fa179b978..411393ce1b 100644 --- a/lib/bundler/spec_set.rb +++ b/lib/bundler/spec_set.rb @@ -76,7 +76,7 @@ module Bundler new_platforms = all_platforms.select do |platform| next if platforms.include?(platform) - next unless GemHelpers.generic(platform) == Gem::Platform::RUBY + next unless Gem::Platform.generic(platform) == Gem::Platform::RUBY complete_platform(platform) end @@ -179,11 +179,13 @@ module Bundler end def -(other) + SharedHelpers.major_deprecation 2, "SpecSet#- has been removed with no replacement" + SpecSet.new(to_a - other.to_a) end def find_by_name_and_platform(name, platform) - @specs.detect {|spec| spec.name == name && spec.match_platform(platform) } + @specs.detect {|spec| spec.name == name && spec.installable_on_platform?(platform) } end def specs_with_additional_variants_from(other) @@ -210,6 +212,8 @@ module Bundler end def <<(spec) + SharedHelpers.major_deprecation 2, "SpecSet#<< has been removed with no replacement" + @specs << spec end @@ -280,7 +284,7 @@ module Bundler valid_platform = lookup.all? do |_, specs| spec = specs.first matching_specs = spec.source.specs.search([spec.name, spec.version]) - platform_spec = GemHelpers.select_best_platform_match(matching_specs, platform).find do |s| + platform_spec = MatchPlatform.select_best_platform_match(matching_specs, platform).find do |s| valid?(s) end diff --git a/lib/bundler/templates/newgem/github/workflows/main.yml.tt b/lib/bundler/templates/newgem/github/workflows/main.yml.tt index d1b5ae0534..9224ee0ca2 100644 --- a/lib/bundler/templates/newgem/github/workflows/main.yml.tt +++ b/lib/bundler/templates/newgem/github/workflows/main.yml.tt @@ -18,6 +18,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false <%- if config[:ext] == 'rust' -%> - name: Set up Ruby & Rust uses: oxidize-rb/actions/setup-ruby-and-rust@v1 diff --git a/lib/bundler/templates/newgem/newgem.gemspec.tt b/lib/bundler/templates/newgem/newgem.gemspec.tt index ced300f379..99a0f8c2cf 100644 --- a/lib/bundler/templates/newgem/newgem.gemspec.tt +++ b/lib/bundler/templates/newgem/newgem.gemspec.tt @@ -10,7 +10,7 @@ Gem::Specification.new do |spec| spec.summary = "TODO: Write a short summary, because RubyGems requires one." spec.description = "TODO: Write a longer description or delete this line." - spec.homepage = "TODO: Put your gem's website or public repo URL here." + spec.homepage = "<%= config[:homepage_uri] %>" <%- if config[:mit] -%> spec.license = "MIT" <%- end -%> @@ -20,10 +20,11 @@ Gem::Specification.new do |spec| <%- end -%> spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'" - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here." - spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." + spec.metadata["source_code_uri"] = "<%= config[:source_code_uri] %>" + <%- if config[:changelog] -%> + spec.metadata["changelog_uri"] = "<%= config[:changelog_uri] %>" + <%- end -%> # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. @@ -31,7 +32,7 @@ Gem::Specification.new do |spec| spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls| ls.readlines("\x0", chomp: true).reject do |f| (f == gemspec) || - f.start_with?(*%w[bin/ test/ spec/ features/ .git <%= config[:ci_config_path] %>appveyor Gemfile]) + f.start_with?(*%w[<%= config[:ignore_paths].join(" ") %>]) end end spec.bindir = "exe" diff --git a/lib/bundler/ui/shell.rb b/lib/bundler/ui/shell.rb index 6df1512a5b..6f080b6459 100644 --- a/lib/bundler/ui/shell.rb +++ b/lib/bundler/ui/shell.rb @@ -80,11 +80,11 @@ module Bundler end def ask(msg) - @shell.ask(msg) + @shell.ask(msg, :green) end def yes?(msg) - @shell.yes?(msg) + @shell.yes?(msg, :green) end def no?(msg) diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index fa24b4966e..5a55b23ac1 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,13 +1,21 @@ # frozen_string_literal: false module Bundler - VERSION = "2.7.0.dev".freeze + VERSION = "2.8.0.dev".freeze def self.bundler_major_version - @bundler_major_version ||= VERSION.split(".").first.to_i + @bundler_major_version ||= gem_version.segments.first end def self.gem_version @gem_version ||= Gem::Version.create(VERSION) end + + def self.verbose_version + @verbose_version ||= "#{VERSION}#{simulated_version ? " (simulating Bundler #{simulated_version})" : ""}" + end + + def self.simulated_version + @simulated_version ||= Bundler.settings[:simulate_version] + end end diff --git a/lib/cgi.rb b/lib/cgi.rb index 71b52b6267..b041c1bed1 100644 --- a/lib/cgi.rb +++ b/lib/cgi.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "cgi/escape" -warn <<-WARNING, uplevel: 1 if $VERBOSE +warn <<-WARNING, uplevel: Gem::BUNDLED_GEMS.uplevel if $VERBOSE CGI library is removed from Ruby 3.5. Please use cgi/escape instead for CGI.escape and CGI.unescape features. If you need to use the full features of CGI library, Please install cgi gem. WARNING diff --git a/lib/cgi/util.rb b/lib/cgi/util.rb index b862341e90..389a04acd2 100644 --- a/lib/cgi/util.rb +++ b/lib/cgi/util.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "cgi/escape" -warn <<-WARNING, uplevel: 1 if $VERBOSE +warn <<-WARNING, uplevel: Gem::BUNDLED_GEMS.uplevel if $VERBOSE CGI::Util is removed from Ruby 3.5. Please use cgi/escape instead for CGI.escape and CGI.unescape features. +If you are using CGI.parse, please install and use the cgi gem instead. WARNING diff --git a/lib/erb.gemspec b/lib/erb/erb.gemspec similarity index 97% rename from lib/erb.gemspec rename to lib/erb/erb.gemspec index 0a59abad53..94edc68682 100644 --- a/lib/erb.gemspec +++ b/lib/erb/erb.gemspec @@ -2,7 +2,7 @@ begin require_relative 'lib/erb/version' rescue LoadError # for Ruby core repository - require_relative 'erb/version' + require_relative 'version' end Gem::Specification.new do |spec| diff --git a/lib/erb/version.rb b/lib/erb/version.rb index 6090303add..0875dcb42a 100644 --- a/lib/erb/version.rb +++ b/lib/erb/version.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true class ERB - VERSION = '5.0.0' + VERSION = '5.0.2' end diff --git a/lib/net/http.rb b/lib/net/http.rb index 635f756b41..85051a4468 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1529,7 +1529,7 @@ module Net #:nodoc: :verify_hostname, ] # :nodoc: - SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym } # :nodoc: + SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc: # Sets or returns the path to a CA certification file in PEM format. attr_accessor :ca_file diff --git a/lib/net/http/generic_request.rb b/lib/net/http/generic_request.rb index 44e329a0c8..c92004e557 100644 --- a/lib/net/http/generic_request.rb +++ b/lib/net/http/generic_request.rb @@ -102,6 +102,31 @@ class Net::HTTPGenericRequest "\#<#{self.class} #{@method}>" end + # Returns a string representation of the request with the details for pp: + # + # require 'pp' + # post = Net::HTTP::Post.new(uri) + # post.inspect # => "#" + # post.pretty_inspect + # # => # ["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], + # "accept" => ["*/*"], + # "user-agent" => ["Ruby"], + # "host" => ["www.ruby-lang.org"]}> + # + def pretty_print(q) + q.object_group(self) { + q.breakable + q.text @method + q.breakable + q.text "path="; q.pp @path + q.breakable + q.text "headers="; q.pp to_hash + } + end + ## # Don't automatically decode response content-encoding if the user indicates # they want to handle it. @@ -260,7 +285,6 @@ class Net::HTTPGenericRequest def send_request_with_body(sock, ver, path, body) self.content_length = body.bytesize delete 'Transfer-Encoding' - supply_default_content_type write_header sock, ver, path wait_for_continue sock, ver if sock.continue_timeout sock.write body @@ -271,7 +295,6 @@ class Net::HTTPGenericRequest raise ArgumentError, "Content-Length not given and Transfer-Encoding is not `chunked'" end - supply_default_content_type write_header sock, ver, path wait_for_continue sock, ver if sock.continue_timeout if chunked? @@ -373,12 +396,6 @@ class Net::HTTPGenericRequest buf.clear end - def supply_default_content_type - return if content_type() - warn 'net/http: Content-Type did not set; using application/x-www-form-urlencoded', uplevel: 1 if $VERBOSE - set_content_type 'application/x-www-form-urlencoded' - end - ## # Waits up to the continue timeout for a response from the server provided # we're speaking HTTP 1.1 and are expecting a 100-continue response. @@ -411,4 +428,3 @@ class Net::HTTPGenericRequest end end - diff --git a/lib/net/http/responses.rb b/lib/net/http/responses.rb index 6f6fb8d055..5e2f8ce1aa 100644 --- a/lib/net/http/responses.rb +++ b/lib/net/http/responses.rb @@ -1104,7 +1104,7 @@ class Net::HTTPResponse '3' => Net::HTTPRedirection, '4' => Net::HTTPClientError, '5' => Net::HTTPServerError - } + }.freeze CODE_TO_OBJ = { '100' => Net::HTTPContinue, '101' => Net::HTTPSwitchProtocol, @@ -1170,5 +1170,5 @@ class Net::HTTPResponse '508' => Net::HTTPLoopDetected, '510' => Net::HTTPNotExtended, '511' => Net::HTTPNetworkAuthenticationRequired, - } + }.freeze end diff --git a/lib/optparse.rb b/lib/optparse.rb index e1069b3505..332aea2148 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -7,6 +7,7 @@ # # See OptionParser for documentation. # +require 'set' unless defined?(Set) #-- # == Developer Documentation (not for RDoc output) @@ -1293,7 +1294,15 @@ XXX # to $0. # def program_name - @program_name || File.basename($0, '.*') + @program_name || strip_ext(File.basename($0)) + end + + private def strip_ext(name) # :nodoc: + exts = /#{ + require "rbconfig" + Regexp.union(*RbConfig::CONFIG["EXECUTABLE_EXTS"]&.split(" ")) + }\z/o + name.sub(exts, "") end # for experimental cascading :-) @@ -1500,7 +1509,7 @@ XXX case o when Proc, Method block = notwice(o, block, 'block') - when Array, Hash + when Array, Hash, Set if Array === o o, v = o.partition {|v,| Completion.completable?(v)} values = notwice(v, values, 'values') unless v.empty? diff --git a/lib/optparse/optparse.gemspec b/lib/optparse/optparse.gemspec index 8589f1857c..6ea6b88395 100644 --- a/lib/optparse/optparse.gemspec +++ b/lib/optparse/optparse.gemspec @@ -25,8 +25,9 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir["{doc,lib,misc}/**/{*,.document}"] + - %w[README.md ChangeLog COPYING .document .rdoc_options] + dir, gemspec = File.split(__FILE__) + excludes = %W[#{gemspec} rakelib test/ Gemfile Rakefile .git* .editor*].map {|n| ":^"+n} + spec.files = IO.popen(%w[git ls-files -z --] + excludes, chdir: dir, &:read).split("\x0") spec.bindir = "exe" spec.executables = [] spec.require_paths = ["lib"] diff --git a/lib/pathname.rb b/lib/pathname.rb new file mode 100644 index 0000000000..a0da5ed93f --- /dev/null +++ b/lib/pathname.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true +# +# = pathname.rb +# +# Object-Oriented Pathname Class +# +# Author:: Tanaka Akira +# Documentation:: Author and Gavin Sinclair +# +# For documentation, see class Pathname. +# + +class Pathname # * Find * + # + # Iterates over the directory tree in a depth first manner, yielding a + # Pathname for each file under "this" directory. + # + # Returns an Enumerator if no block is given. + # + # Since it is implemented by the standard library module Find, Find.prune can + # be used to control the traversal. + # + # If +self+ is +.+, yielded pathnames begin with a filename in the + # current directory, not +./+. + # + # See Find.find + # + def find(ignore_error: true) # :yield: pathname + return to_enum(__method__, ignore_error: ignore_error) unless block_given? + require 'find' + if @path == '.' + Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f.delete_prefix('./')) } + else + Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f) } + end + end +end + + +class Pathname # * FileUtils * + # Recursively deletes a directory, including all directories beneath it. + # + # See FileUtils.rm_rf + def rmtree(noop: nil, verbose: nil, secure: nil) + # The name "rmtree" is borrowed from File::Path of Perl. + # File::Path provides "mkpath" and "rmtree". + require 'fileutils' + FileUtils.rm_rf(@path, noop: noop, verbose: verbose, secure: secure) + self + end +end + +class Pathname # * tmpdir * + # Creates a tmp directory and wraps the returned path in a Pathname object. + # + # See Dir.mktmpdir + def self.mktmpdir + require 'tmpdir' unless defined?(Dir.mktmpdir) + if block_given? + Dir.mktmpdir do |dir| + dir = self.new(dir) + yield dir + end + else + self.new(Dir.mktmpdir) + end + end +end diff --git a/lib/prism.rb b/lib/prism.rb index eaab5cbfed..dceba4b1f5 100644 --- a/lib/prism.rb +++ b/lib/prism.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown # The Prism Ruby parser. # diff --git a/lib/prism/desugar_compiler.rb b/lib/prism/desugar_compiler.rb index e3b15fc3b0..5d7d38d841 100644 --- a/lib/prism/desugar_compiler.rb +++ b/lib/prism/desugar_compiler.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism class DesugarAndWriteNode # :nodoc: diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index a0da0b6195..5a4ba09a4f 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown # typed: ignore # This file is responsible for mirroring the API provided by the C extension by diff --git a/lib/prism/lex_compat.rb b/lib/prism/lex_compat.rb index a83c24cb41..9b3f025ab6 100644 --- a/lib/prism/lex_compat.rb +++ b/lib/prism/lex_compat.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown require "delegate" require "ripper" diff --git a/lib/prism/node_ext.rb b/lib/prism/node_ext.rb index b007a051ea..469e54ca0c 100644 --- a/lib/prism/node_ext.rb +++ b/lib/prism/node_ext.rb @@ -1,7 +1,10 @@ # frozen_string_literal: true +# :markup: markdown +#-- # Here we are reopening the prism module to provide methods on nodes that aren't # templated and are meant as convenience methods. +#++ module Prism class Node def deprecated(*replacements) # :nodoc: diff --git a/lib/prism/pack.rb b/lib/prism/pack.rb index c0de8ab8b7..166c04c3c0 100644 --- a/lib/prism/pack.rb +++ b/lib/prism/pack.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true +# :markup: markdown # typed: ignore +# module Prism # A parser for the pack template language. module Pack diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb index 9a3e7c5b79..05c14e33f5 100644 --- a/lib/prism/parse_result.rb +++ b/lib/prism/parse_result.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism # This represents a source of Ruby code that has been parsed. It is used in diff --git a/lib/prism/parse_result/comments.rb b/lib/prism/parse_result/comments.rb index 22c4148b2c..3e93316aff 100644 --- a/lib/prism/parse_result/comments.rb +++ b/lib/prism/parse_result/comments.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism class ParseResult < Result diff --git a/lib/prism/parse_result/errors.rb b/lib/prism/parse_result/errors.rb index eb4f317248..26c376b3ce 100644 --- a/lib/prism/parse_result/errors.rb +++ b/lib/prism/parse_result/errors.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown require "stringio" diff --git a/lib/prism/parse_result/newlines.rb b/lib/prism/parse_result/newlines.rb index 37f64f8ae2..e7fd62cafe 100644 --- a/lib/prism/parse_result/newlines.rb +++ b/lib/prism/parse_result/newlines.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism class ParseResult < Result diff --git a/lib/prism/pattern.rb b/lib/prism/pattern.rb index 03fec26789..6ad2d9e5b9 100644 --- a/lib/prism/pattern.rb +++ b/lib/prism/pattern.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism # A pattern is an object that wraps a Ruby pattern matching expression. The diff --git a/lib/prism/polyfill/scan_byte.rb b/lib/prism/polyfill/scan_byte.rb new file mode 100644 index 0000000000..2def4572c4 --- /dev/null +++ b/lib/prism/polyfill/scan_byte.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "strscan" + +# Polyfill for StringScanner#scan_byte, which didn't exist until Ruby 3.4. +if !(StringScanner.instance_methods.include?(:scan_byte)) + StringScanner.include( + Module.new { + def scan_byte # :nodoc: + get_byte&.b&.ord + end + } + ) +end diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 5cb5a98057..4daa511300 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -88,6 +88,7 @@ Gem::Specification.new do |spec| "lib/prism/pattern.rb", "lib/prism/polyfill/append_as_bytes.rb", "lib/prism/polyfill/byteindex.rb", + "lib/prism/polyfill/scan_byte.rb", "lib/prism/polyfill/unpack1.rb", "lib/prism/polyfill/warn.rb", "lib/prism/reflection.rb", diff --git a/lib/prism/relocation.rb b/lib/prism/relocation.rb index 163d2012c5..3e9210a785 100644 --- a/lib/prism/relocation.rb +++ b/lib/prism/relocation.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism # Prism parses deterministically for the same input. This provides a nice diff --git a/lib/prism/string_query.rb b/lib/prism/string_query.rb index 9011051d2b..547f58d2fa 100644 --- a/lib/prism/string_query.rb +++ b/lib/prism/string_query.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism # Query methods that allow categorizing strings based on their context for diff --git a/lib/prism/translation.rb b/lib/prism/translation.rb index 511c80febc..d127f2006c 100644 --- a/lib/prism/translation.rb +++ b/lib/prism/translation.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism # This module is responsible for converting the prism syntax tree into other diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb index d43ad7c1e4..a7888f77ec 100644 --- a/lib/prism/translation/parser.rb +++ b/lib/prism/translation/parser.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown begin required_version = ">= 3.3.7.2" diff --git a/lib/prism/translation/parser/builder.rb b/lib/prism/translation/parser/builder.rb index d3b51f4275..6b620c25bc 100644 --- a/lib/prism/translation/parser/builder.rb +++ b/lib/prism/translation/parser/builder.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism module Translation diff --git a/lib/prism/translation/parser/compiler.rb b/lib/prism/translation/parser/compiler.rb index 0bd9d74f93..6e0618890d 100644 --- a/lib/prism/translation/parser/compiler.rb +++ b/lib/prism/translation/parser/compiler.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism module Translation diff --git a/lib/prism/translation/parser/lexer.rb b/lib/prism/translation/parser/lexer.rb index 8f2d065b73..dd4867415c 100644 --- a/lib/prism/translation/parser/lexer.rb +++ b/lib/prism/translation/parser/lexer.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true +# :markup: markdown require "strscan" require_relative "../../polyfill/append_as_bytes" +require_relative "../../polyfill/scan_byte" module Prism module Translation @@ -423,7 +425,12 @@ module Prism end current_string << unescape_string(value, quote_stack.last) - if (backslash_count = token.value[/(\\{1,})\n/, 1]&.length).nil? || backslash_count.even? || !interpolation?(quote_stack.last) + relevant_backslash_count = if quote_stack.last.start_with?("%W", "%I") + 0 # the last backslash escapes the newline + else + token.value[/(\\{1,})\n/, 1]&.length || 0 + end + if relevant_backslash_count.even? || !interpolation?(quote_stack.last) tokens << [:tSTRING_CONTENT, [current_string, range(start_offset, start_offset + current_length)]] break end @@ -761,12 +768,12 @@ module Prism elsif (value = scanner.scan(/M-\\?(?=[[:print:]])/)) # \M-x where x is an ASCII printable character escape_read(result, scanner, control, true) - elsif (byte = scanner.get_byte) + elsif (byte = scanner.scan_byte) # Something else after an escape. - if control && byte == "?" + if control && byte == 0x3f # ASCII '?' result.append_as_bytes(escape_build(0x7f, false, meta)) else - result.append_as_bytes(escape_build(byte.ord, control, meta)) + result.append_as_bytes(escape_build(byte, control, meta)) end end end diff --git a/lib/prism/translation/parser33.rb b/lib/prism/translation/parser33.rb index b09266e06a..0a59669465 100644 --- a/lib/prism/translation/parser33.rb +++ b/lib/prism/translation/parser33.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism module Translation diff --git a/lib/prism/translation/parser34.rb b/lib/prism/translation/parser34.rb index 0ead70ad3c..566a23fadb 100644 --- a/lib/prism/translation/parser34.rb +++ b/lib/prism/translation/parser34.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism module Translation diff --git a/lib/prism/translation/parser35.rb b/lib/prism/translation/parser35.rb index a6abc12589..79cd59cbd9 100644 --- a/lib/prism/translation/parser35.rb +++ b/lib/prism/translation/parser35.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism module Translation diff --git a/lib/prism/translation/parser_current.rb b/lib/prism/translation/parser_current.rb index b44769fde7..1b1794abbe 100644 --- a/lib/prism/translation/parser_current.rb +++ b/lib/prism/translation/parser_current.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true +# :markup: markdown # typed: ignore +# module Prism module Translation case RUBY_VERSION diff --git a/lib/prism/translation/ripper.rb b/lib/prism/translation/ripper.rb index 95f366ac91..6ea98fc1ea 100644 --- a/lib/prism/translation/ripper.rb +++ b/lib/prism/translation/ripper.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown require "ripper" diff --git a/lib/prism/translation/ripper/sexp.rb b/lib/prism/translation/ripper/sexp.rb index dc26a639a3..8cfefc8472 100644 --- a/lib/prism/translation/ripper/sexp.rb +++ b/lib/prism/translation/ripper/sexp.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown require_relative "../ripper" diff --git a/lib/prism/translation/ruby_parser.rb b/lib/prism/translation/ruby_parser.rb index 8784e22d10..3808cd3130 100644 --- a/lib/prism/translation/ruby_parser.rb +++ b/lib/prism/translation/ruby_parser.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown begin require "ruby_parser" @@ -15,7 +16,7 @@ module Prism # A prism visitor that builds Sexp objects. class Compiler < ::Prism::Compiler # This is the name of the file that we are compiling. We set it on every - # Sexp object that is generated, and also use it to compile __FILE__ + # Sexp object that is generated, and also use it to compile `__FILE__` # nodes. attr_reader :file @@ -34,26 +35,34 @@ module Prism @in_pattern = in_pattern end + # ``` # alias foo bar # ^^^^^^^^^^^^^ + # ``` def visit_alias_method_node(node) s(node, :alias, visit(node.new_name), visit(node.old_name)) end + # ``` # alias $foo $bar # ^^^^^^^^^^^^^^^ + # ``` def visit_alias_global_variable_node(node) s(node, :valias, node.new_name.name, node.old_name.name) end + # ``` # foo => bar | baz # ^^^^^^^^^ + # ``` def visit_alternation_pattern_node(node) s(node, :or, visit(node.left), visit(node.right)) end + # ``` # a and b # ^^^^^^^ + # ``` def visit_and_node(node) left = visit(node.left) @@ -70,8 +79,10 @@ module Prism end end + # ``` # [] # ^^ + # ``` def visit_array_node(node) if in_pattern s(node, :array_pat, nil).concat(visit_all(node.elements)) @@ -80,8 +91,10 @@ module Prism end end + # ``` # foo => [bar] # ^^^^^ + # ``` def visit_array_pattern_node(node) if node.constant.nil? && node.requireds.empty? && node.rest.nil? && node.posts.empty? s(node, :array_pat) @@ -103,23 +116,29 @@ module Prism end end + # ``` # foo(bar) # ^^^ + # ``` def visit_arguments_node(node) raise "Cannot visit arguments directly" end + # ``` # { a: 1 } # ^^^^ + # ``` def visit_assoc_node(node) [visit(node.key), visit(node.value)] end + # ``` # def foo(**); bar(**); end # ^^ # # { **foo } # ^^^^^ + # ``` def visit_assoc_splat_node(node) if node.value.nil? [s(node, :kwsplat)] @@ -128,14 +147,18 @@ module Prism end end + # ``` # $+ # ^^ + # ``` def visit_back_reference_read_node(node) s(node, :back_ref, node.name.name.delete_prefix("$").to_sym) end + # ``` # begin end # ^^^^^^^^^ + # ``` def visit_begin_node(node) result = node.statements.nil? ? s(node, :nil) : visit(node.statements) @@ -167,16 +190,20 @@ module Prism result end + # ``` # foo(&bar) # ^^^^ + # ``` def visit_block_argument_node(node) s(node, :block_pass).tap do |result| result << visit(node.expression) unless node.expression.nil? end end + # ``` # foo { |; bar| } # ^^^ + # ``` def visit_block_local_variable_node(node) node.name end @@ -186,8 +213,10 @@ module Prism s(node, :block_pass, visit(node.expression)) end + # ``` # def foo(&bar); end # ^^^^ + # ``` def visit_block_parameter_node(node) :"&#{node.name}" end @@ -228,11 +257,13 @@ module Prism result end + # ``` # break # ^^^^^ # # break foo # ^^^^^^^^^ + # ``` def visit_break_node(node) if node.arguments.nil? s(node, :break) @@ -243,6 +274,7 @@ module Prism end end + # ``` # foo # ^^^ # @@ -251,6 +283,7 @@ module Prism # # foo.bar() {} # ^^^^^^^^^^^^ + # ``` def visit_call_node(node) case node.name when :!~ @@ -289,8 +322,10 @@ module Prism visit_block(node, result, block) end + # ``` # foo.bar += baz # ^^^^^^^^^^^^^^^ + # ``` def visit_call_operator_write_node(node) if op_asgn?(node) s(node, op_asgn_type(node, :op_asgn), visit(node.receiver), visit_write_value(node.value), node.read_name, node.binary_operator) @@ -299,8 +334,10 @@ module Prism end end + # ``` # foo.bar &&= baz # ^^^^^^^^^^^^^^^ + # ``` def visit_call_and_write_node(node) if op_asgn?(node) s(node, op_asgn_type(node, :op_asgn), visit(node.receiver), visit_write_value(node.value), node.read_name, :"&&") @@ -309,8 +346,10 @@ module Prism end end + # ``` # foo.bar ||= baz # ^^^^^^^^^^^^^^^ + # ``` def visit_call_or_write_node(node) if op_asgn?(node) s(node, op_asgn_type(node, :op_asgn), visit(node.receiver), visit_write_value(node.value), node.read_name, :"||") @@ -332,32 +371,42 @@ module Prism node.safe_navigation? ? :"safe_#{type}" : type end + # ``` # foo.bar, = 1 # ^^^^^^^ + # ``` def visit_call_target_node(node) s(node, :attrasgn, visit(node.receiver), node.name) end + # ``` # foo => bar => baz # ^^^^^^^^^^ + # ``` def visit_capture_pattern_node(node) visit(node.target) << visit(node.value) end + # ``` # case foo; when bar; end # ^^^^^^^^^^^^^^^^^^^^^^^ + # ``` def visit_case_node(node) s(node, :case, visit(node.predicate)).concat(visit_all(node.conditions)) << visit(node.else_clause) end + # ``` # case foo; in bar; end # ^^^^^^^^^^^^^^^^^^^^^ + # ``` def visit_case_match_node(node) s(node, :case, visit(node.predicate)).concat(visit_all(node.conditions)) << visit(node.else_clause) end + # ``` # class Foo; end # ^^^^^^^^^^^^^^ + # ``` def visit_class_node(node) name = if node.constant_path.is_a?(ConstantReadNode) @@ -376,41 +425,53 @@ module Prism end end + # ``` # @@foo # ^^^^^ + # ``` def visit_class_variable_read_node(node) s(node, :cvar, node.name) end + # ``` # @@foo = 1 # ^^^^^^^^^ # # @@foo, @@bar = 1 # ^^^^^ ^^^^^ + # ``` def visit_class_variable_write_node(node) s(node, class_variable_write_type, node.name, visit_write_value(node.value)) end + # ``` # @@foo += bar # ^^^^^^^^^^^^ + # ``` def visit_class_variable_operator_write_node(node) s(node, class_variable_write_type, node.name, s(node, :call, s(node, :cvar, node.name), node.binary_operator, visit_write_value(node.value))) end + # ``` # @@foo &&= bar # ^^^^^^^^^^^^^ + # ``` def visit_class_variable_and_write_node(node) s(node, :op_asgn_and, s(node, :cvar, node.name), s(node, class_variable_write_type, node.name, visit_write_value(node.value))) end + # ``` # @@foo ||= bar # ^^^^^^^^^^^^^ + # ``` def visit_class_variable_or_write_node(node) s(node, :op_asgn_or, s(node, :cvar, node.name), s(node, class_variable_write_type, node.name, visit_write_value(node.value))) end + # ``` # @@foo, = bar # ^^^^^ + # ``` def visit_class_variable_target_node(node) s(node, class_variable_write_type, node.name) end @@ -421,47 +482,61 @@ module Prism in_def ? :cvasgn : :cvdecl end + # ``` # Foo # ^^^ + # ``` def visit_constant_read_node(node) s(node, :const, node.name) end + # ``` # Foo = 1 # ^^^^^^^ # # Foo, Bar = 1 # ^^^ ^^^ + # ``` def visit_constant_write_node(node) s(node, :cdecl, node.name, visit_write_value(node.value)) end + # ``` # Foo += bar # ^^^^^^^^^^^ + # ``` def visit_constant_operator_write_node(node) s(node, :cdecl, node.name, s(node, :call, s(node, :const, node.name), node.binary_operator, visit_write_value(node.value))) end + # ``` # Foo &&= bar # ^^^^^^^^^^^^ + # ``` def visit_constant_and_write_node(node) s(node, :op_asgn_and, s(node, :const, node.name), s(node, :cdecl, node.name, visit(node.value))) end + # ``` # Foo ||= bar # ^^^^^^^^^^^^ + # ``` def visit_constant_or_write_node(node) s(node, :op_asgn_or, s(node, :const, node.name), s(node, :cdecl, node.name, visit(node.value))) end + # ``` # Foo, = bar # ^^^ + # ``` def visit_constant_target_node(node) s(node, :cdecl, node.name) end + # ``` # Foo::Bar # ^^^^^^^^ + # ``` def visit_constant_path_node(node) if node.parent.nil? s(node, :colon3, node.name) @@ -470,35 +545,45 @@ module Prism end end + # ``` # Foo::Bar = 1 # ^^^^^^^^^^^^ # # Foo::Foo, Bar::Bar = 1 # ^^^^^^^^ ^^^^^^^^ + # ``` def visit_constant_path_write_node(node) s(node, :cdecl, visit(node.target), visit_write_value(node.value)) end + # ``` # Foo::Bar += baz # ^^^^^^^^^^^^^^^ + # ``` def visit_constant_path_operator_write_node(node) s(node, :op_asgn, visit(node.target), node.binary_operator, visit_write_value(node.value)) end + # ``` # Foo::Bar &&= baz # ^^^^^^^^^^^^^^^^ + # ``` def visit_constant_path_and_write_node(node) s(node, :op_asgn_and, visit(node.target), visit_write_value(node.value)) end + # ``` # Foo::Bar ||= baz # ^^^^^^^^^^^^^^^^ + # ``` def visit_constant_path_or_write_node(node) s(node, :op_asgn_or, visit(node.target), visit_write_value(node.value)) end + # ``` # Foo::Bar, = baz # ^^^^^^^^ + # ``` def visit_constant_path_target_node(node) inner = if node.parent.nil? @@ -510,11 +595,13 @@ module Prism s(node, :const, inner) end + # ``` # def foo; end # ^^^^^^^^^^^^ # # def self.foo; end # ^^^^^^^^^^^^^^^^^ + # ``` def visit_def_node(node) name = node.name_loc.slice.to_sym result = @@ -541,55 +628,71 @@ module Prism end end + # ``` # defined? a # ^^^^^^^^^^ # # defined?(a) # ^^^^^^^^^^^ + # ``` def visit_defined_node(node) s(node, :defined, visit(node.value)) end + # ``` # if foo then bar else baz end # ^^^^^^^^^^^^ + # ``` def visit_else_node(node) visit(node.statements) end + # ``` # "foo #{bar}" # ^^^^^^ + # ``` def visit_embedded_statements_node(node) result = s(node, :evstr) result << visit(node.statements) unless node.statements.nil? result end + # ``` # "foo #@bar" # ^^^^^ + # ``` def visit_embedded_variable_node(node) s(node, :evstr, visit(node.variable)) end + # ``` # begin; foo; ensure; bar; end # ^^^^^^^^^^^^ + # ``` def visit_ensure_node(node) node.statements.nil? ? s(node, :nil) : visit(node.statements) end + # ``` # false # ^^^^^ + # ``` def visit_false_node(node) s(node, :false) end + # ``` # foo => [*, bar, *] # ^^^^^^^^^^^ + # ``` def visit_find_pattern_node(node) s(node, :find_pat, visit_pattern_constant(node.constant), :"*#{node.left.expression&.name}", *visit_all(node.requireds), :"*#{node.right.expression&.name}") end + # ``` # if foo .. bar; end # ^^^^^^^^^^ + # ``` def visit_flip_flop_node(node) if node.left.is_a?(IntegerNode) && node.right.is_a?(IntegerNode) s(node, :lit, Range.new(node.left.value, node.right.value, node.exclude_end?)) @@ -598,86 +701,112 @@ module Prism end end + # ``` # 1.0 # ^^^ + # ``` def visit_float_node(node) s(node, :lit, node.value) end + # ``` # for foo in bar do end # ^^^^^^^^^^^^^^^^^^^^^ + # ``` def visit_for_node(node) s(node, :for, visit(node.collection), visit(node.index), visit(node.statements)) end + # ``` # def foo(...); bar(...); end # ^^^ + # ``` def visit_forwarding_arguments_node(node) s(node, :forward_args) end + # ``` # def foo(...); end # ^^^ + # ``` def visit_forwarding_parameter_node(node) s(node, :forward_args) end + # ``` # super # ^^^^^ # # super {} # ^^^^^^^^ + # ``` def visit_forwarding_super_node(node) visit_block(node, s(node, :zsuper), node.block) end + # ``` # $foo # ^^^^ + # ``` def visit_global_variable_read_node(node) s(node, :gvar, node.name) end + # ``` # $foo = 1 # ^^^^^^^^ # # $foo, $bar = 1 # ^^^^ ^^^^ + # ``` def visit_global_variable_write_node(node) s(node, :gasgn, node.name, visit_write_value(node.value)) end + # ``` # $foo += bar # ^^^^^^^^^^^ + # ``` def visit_global_variable_operator_write_node(node) s(node, :gasgn, node.name, s(node, :call, s(node, :gvar, node.name), node.binary_operator, visit(node.value))) end + # ``` # $foo &&= bar # ^^^^^^^^^^^^ + # ``` def visit_global_variable_and_write_node(node) s(node, :op_asgn_and, s(node, :gvar, node.name), s(node, :gasgn, node.name, visit_write_value(node.value))) end + # ``` # $foo ||= bar # ^^^^^^^^^^^^ + # ``` def visit_global_variable_or_write_node(node) s(node, :op_asgn_or, s(node, :gvar, node.name), s(node, :gasgn, node.name, visit_write_value(node.value))) end + # ``` # $foo, = bar # ^^^^ + # ``` def visit_global_variable_target_node(node) s(node, :gasgn, node.name) end + # ``` # {} # ^^ + # ``` def visit_hash_node(node) s(node, :hash).concat(node.elements.flat_map { |element| visit(element) }) end + # ``` # foo => {} # ^^ + # ``` def visit_hash_pattern_node(node) result = s(node, :hash_pat, visit_pattern_constant(node.constant)).concat(node.elements.flat_map { |element| visit(element) }) @@ -691,6 +820,7 @@ module Prism result end + # ``` # if foo then bar end # ^^^^^^^^^^^^^^^^^^^ # @@ -699,6 +829,7 @@ module Prism # # foo ? bar : baz # ^^^^^^^^^^^^^^^ + # ``` def visit_if_node(node) s(node, :if, visit(node.predicate), visit(node.statements), visit(node.subsequent)) end @@ -708,18 +839,24 @@ module Prism s(node, :lit, node.value) end + # ``` # { foo: } # ^^^^ + # ``` def visit_implicit_node(node) end + # ``` # foo { |bar,| } # ^ + # ``` def visit_implicit_rest_node(node) end + # ``` # case foo; in bar; end # ^^^^^^^^^^^^^^^^^^^^^ + # ``` def visit_in_node(node) pattern = if node.pattern.is_a?(ConstantPathNode) @@ -731,8 +868,10 @@ module Prism s(node, :in, pattern).concat(node.statements.nil? ? [nil] : visit_all(node.statements.body)) end + # ``` # foo[bar] += baz # ^^^^^^^^^^^^^^^ + # ``` def visit_index_operator_write_node(node) arglist = nil @@ -744,8 +883,10 @@ module Prism s(node, :op_asgn1, visit(node.receiver), arglist, node.binary_operator, visit_write_value(node.value)) end + # ``` # foo[bar] &&= baz # ^^^^^^^^^^^^^^^^ + # ``` def visit_index_and_write_node(node) arglist = nil @@ -757,8 +898,10 @@ module Prism s(node, :op_asgn1, visit(node.receiver), arglist, :"&&", visit_write_value(node.value)) end + # ``` # foo[bar] ||= baz # ^^^^^^^^^^^^^^^^ + # ``` def visit_index_or_write_node(node) arglist = nil @@ -770,8 +913,10 @@ module Prism s(node, :op_asgn1, visit(node.receiver), arglist, :"||", visit_write_value(node.value)) end + # ``` # foo[bar], = 1 # ^^^^^^^^ + # ``` def visit_index_target_node(node) arguments = visit_all(node.arguments&.arguments || []) arguments << visit(node.block) unless node.block.nil? @@ -779,53 +924,69 @@ module Prism s(node, :attrasgn, visit(node.receiver), :[]=).concat(arguments) end + # ``` # @foo # ^^^^ + # ``` def visit_instance_variable_read_node(node) s(node, :ivar, node.name) end + # ``` # @foo = 1 # ^^^^^^^^ # # @foo, @bar = 1 # ^^^^ ^^^^ + # ``` def visit_instance_variable_write_node(node) s(node, :iasgn, node.name, visit_write_value(node.value)) end + # ``` # @foo += bar # ^^^^^^^^^^^ + # ``` def visit_instance_variable_operator_write_node(node) s(node, :iasgn, node.name, s(node, :call, s(node, :ivar, node.name), node.binary_operator, visit_write_value(node.value))) end + # ``` # @foo &&= bar # ^^^^^^^^^^^^ + # ``` def visit_instance_variable_and_write_node(node) s(node, :op_asgn_and, s(node, :ivar, node.name), s(node, :iasgn, node.name, visit(node.value))) end + # ``` # @foo ||= bar # ^^^^^^^^^^^^ + # ``` def visit_instance_variable_or_write_node(node) s(node, :op_asgn_or, s(node, :ivar, node.name), s(node, :iasgn, node.name, visit(node.value))) end + # ``` # @foo, = bar # ^^^^ + # ``` def visit_instance_variable_target_node(node) s(node, :iasgn, node.name) end + # ``` # 1 # ^ + # ``` def visit_integer_node(node) s(node, :lit, node.value) end + # ``` # if /foo #{bar}/ then end # ^^^^^^^^^^^^ + # ``` def visit_interpolated_match_last_line_node(node) parts = visit_interpolated_parts(node.parts) regexp = @@ -841,8 +1002,10 @@ module Prism s(node, :match, regexp) end + # ``` # /foo #{bar}/ # ^^^^^^^^^^^^ + # ``` def visit_interpolated_regular_expression_node(node) parts = visit_interpolated_parts(node.parts) @@ -856,22 +1019,28 @@ module Prism end end + # ``` # "foo #{bar}" # ^^^^^^^^^^^^ + # ``` def visit_interpolated_string_node(node) parts = visit_interpolated_parts(node.parts) parts.length == 1 ? s(node, :str, parts.first) : s(node, :dstr).concat(parts) end + # ``` # :"foo #{bar}" # ^^^^^^^^^^^^^ + # ``` def visit_interpolated_symbol_node(node) parts = visit_interpolated_parts(node.parts) parts.length == 1 ? s(node, :lit, parts.first.to_sym) : s(node, :dsym).concat(parts) end + # ``` # `foo #{bar}` # ^^^^^^^^^^^^ + # ``` def visit_interpolated_x_string_node(node) source = node.heredoc? ? node.parts.first : node parts = visit_interpolated_parts(node.parts) @@ -951,23 +1120,29 @@ module Prism results end + # ``` # -> { it } # ^^ + # ``` def visit_it_local_variable_read_node(node) s(node, :call, nil, :it) end + # ``` # foo(bar: baz) # ^^^^^^^^ + # ``` def visit_keyword_hash_node(node) s(node, :hash).concat(node.elements.flat_map { |element| visit(element) }) end + # ``` # def foo(**bar); end # ^^^^^ # # def foo(**); end # ^^ + # ``` def visit_keyword_rest_parameter_node(node) :"**#{node.name}" end @@ -989,8 +1164,10 @@ module Prism end end + # ``` # foo # ^^^ + # ``` def visit_local_variable_read_node(node) if node.name.match?(/^_\d$/) s(node, :call, nil, node.name) @@ -999,59 +1176,77 @@ module Prism end end + # ``` # foo = 1 # ^^^^^^^ # # foo, bar = 1 # ^^^ ^^^ + # ``` def visit_local_variable_write_node(node) s(node, :lasgn, node.name, visit_write_value(node.value)) end + # ``` # foo += bar # ^^^^^^^^^^ + # ``` def visit_local_variable_operator_write_node(node) s(node, :lasgn, node.name, s(node, :call, s(node, :lvar, node.name), node.binary_operator, visit_write_value(node.value))) end + # ``` # foo &&= bar # ^^^^^^^^^^^ + # ``` def visit_local_variable_and_write_node(node) s(node, :op_asgn_and, s(node, :lvar, node.name), s(node, :lasgn, node.name, visit_write_value(node.value))) end + # ``` # foo ||= bar # ^^^^^^^^^^^ + # ``` def visit_local_variable_or_write_node(node) s(node, :op_asgn_or, s(node, :lvar, node.name), s(node, :lasgn, node.name, visit_write_value(node.value))) end + # ``` # foo, = bar # ^^^ + # ``` def visit_local_variable_target_node(node) s(node, :lasgn, node.name) end + # ``` # if /foo/ then end # ^^^^^ + # ``` def visit_match_last_line_node(node) s(node, :match, s(node, :lit, Regexp.new(node.unescaped, node.options))) end + # ``` # foo in bar # ^^^^^^^^^^ + # ``` def visit_match_predicate_node(node) s(node, :case, visit(node.value), s(node, :in, node.pattern.accept(copy_compiler(in_pattern: true)), nil), nil) end + # ``` # foo => bar # ^^^^^^^^^^ + # ``` def visit_match_required_node(node) s(node, :case, visit(node.value), s(node, :in, node.pattern.accept(copy_compiler(in_pattern: true)), nil), nil) end + # ``` # /(?foo)/ =~ bar # ^^^^^^^^^^^^^^^^^^^^ + # ``` def visit_match_write_node(node) s(node, :match2, visit(node.call.receiver), visit(node.call.arguments.arguments.first)) end @@ -1063,8 +1258,10 @@ module Prism raise "Cannot visit missing node directly" end + # ``` # module Foo; end # ^^^^^^^^^^^^^^^ + # ``` def visit_module_node(node) name = if node.constant_path.is_a?(ConstantReadNode) @@ -1083,8 +1280,10 @@ module Prism end end + # ``` # foo, bar = baz # ^^^^^^^^ + # ``` def visit_multi_target_node(node) targets = [*node.lefts] targets << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode) @@ -1093,8 +1292,10 @@ module Prism s(node, :masgn, s(node, :array).concat(visit_all(targets))) end + # ``` # foo, bar = baz # ^^^^^^^^^^^^^^ + # ``` def visit_multi_write_node(node) targets = [*node.lefts] targets << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode) @@ -1114,11 +1315,13 @@ module Prism s(node, :masgn, s(node, :array).concat(visit_all(targets)), value) end + # ``` # next # ^^^^ # # next foo # ^^^^^^^^ + # ``` def visit_next_node(node) if node.arguments.nil? s(node, :next) @@ -1130,44 +1333,58 @@ module Prism end end + # ``` # nil # ^^^ + # ``` def visit_nil_node(node) s(node, :nil) end + # ``` # def foo(**nil); end # ^^^^^ + # ``` def visit_no_keywords_parameter_node(node) in_pattern ? s(node, :kwrest, :"**nil") : :"**nil" end + # ``` # -> { _1 + _2 } # ^^^^^^^^^^^^^^ + # ``` def visit_numbered_parameters_node(node) raise "Cannot visit numbered parameters directly" end + # ``` # $1 # ^^ + # ``` def visit_numbered_reference_read_node(node) s(node, :nth_ref, node.number) end + # ``` # def foo(bar: baz); end # ^^^^^^^^ + # ``` def visit_optional_keyword_parameter_node(node) s(node, :kwarg, node.name, visit(node.value)) end + # ``` # def foo(bar = 1); end # ^^^^^^^ + # ``` def visit_optional_parameter_node(node) s(node, :lasgn, node.name, visit(node.value)) end + # ``` # a or b # ^^^^^^ + # ``` def visit_or_node(node) left = visit(node.left) @@ -1184,8 +1401,10 @@ module Prism end end + # ``` # def foo(bar, *baz); end # ^^^^^^^^^ + # ``` def visit_parameters_node(node) children = node.compact_child_nodes.map do |element| @@ -1199,8 +1418,10 @@ module Prism s(node, :args).concat(children) end + # ``` # def foo((bar, baz)); end # ^^^^^^^^^^ + # ``` private def visit_destructured_parameter(node) children = [*node.lefts, *node.rest, *node.rights].map do |child| @@ -1219,11 +1440,13 @@ module Prism s(node, :masgn).concat(children) end + # ``` # () # ^^ # # (1) # ^^^ + # ``` def visit_parentheses_node(node) if node.body.nil? s(node, :nil) @@ -1232,14 +1455,18 @@ module Prism end end + # ``` # foo => ^(bar) # ^^^^^^ + # ``` def visit_pinned_expression_node(node) node.expression.accept(copy_compiler(in_pattern: false)) end + # ``` # foo = 1 and bar => ^foo # ^^^^ + # ``` def visit_pinned_variable_node(node) if node.variable.is_a?(LocalVariableReadNode) && node.variable.name.match?(/^_\d$/) s(node, :lvar, node.variable.name) @@ -1263,8 +1490,10 @@ module Prism visit(node.statements) end + # ``` # 0..5 # ^^^^ + # ``` def visit_range_node(node) if !in_pattern && !node.left.nil? && !node.right.nil? && ([node.left.type, node.right.type] - %i[nil_node integer_node]).empty? left = node.left.value if node.left.is_a?(IntegerNode) @@ -1285,44 +1514,58 @@ module Prism end end + # ``` # 1r # ^^ + # ``` def visit_rational_node(node) s(node, :lit, node.value) end + # ``` # redo # ^^^^ + # ``` def visit_redo_node(node) s(node, :redo) end + # ``` # /foo/ # ^^^^^ + # ``` def visit_regular_expression_node(node) s(node, :lit, Regexp.new(node.unescaped, node.options)) end + # ``` # def foo(bar:); end # ^^^^ + # ``` def visit_required_keyword_parameter_node(node) s(node, :kwarg, node.name) end + # ``` # def foo(bar); end # ^^^ + # ``` def visit_required_parameter_node(node) node.name end + # ``` # foo rescue bar # ^^^^^^^^^^^^^^ + # ``` def visit_rescue_modifier_node(node) s(node, :rescue, visit(node.expression), s(node.rescue_expression, :resbody, s(node.rescue_expression, :array), visit(node.rescue_expression))) end + # ``` # begin; rescue; end # ^^^^^^^ + # ``` def visit_rescue_node(node) exceptions = if node.exceptions.length == 1 && node.exceptions.first.is_a?(SplatNode) @@ -1338,26 +1581,32 @@ module Prism s(node, :resbody, exceptions).concat(node.statements.nil? ? [nil] : visit_all(node.statements.body)) end + # ``` # def foo(*bar); end # ^^^^ # # def foo(*); end # ^ + # ``` def visit_rest_parameter_node(node) :"*#{node.name}" end + # ``` # retry # ^^^^^ + # ``` def visit_retry_node(node) s(node, :retry) end + # ``` # return # ^^^^^^ # # return 1 # ^^^^^^^^ + # ``` def visit_return_node(node) if node.arguments.nil? s(node, :return) @@ -1369,8 +1618,10 @@ module Prism end end + # ``` # self # ^^^^ + # ``` def visit_self_node(node) s(node, :self) end @@ -1380,33 +1631,42 @@ module Prism visit(node.write) end + # ``` # class << self; end # ^^^^^^^^^^^^^^^^^^ + # ``` def visit_singleton_class_node(node) s(node, :sclass, visit(node.expression)).tap do |sexp| sexp << node.body.accept(copy_compiler(in_def: false)) unless node.body.nil? end end + # ``` # __ENCODING__ # ^^^^^^^^^^^^ + # ``` def visit_source_encoding_node(node) # TODO s(node, :colon2, s(node, :const, :Encoding), :UTF_8) end + # ``` # __FILE__ # ^^^^^^^^ + # ``` def visit_source_file_node(node) s(node, :str, node.filepath) end + # ``` # __LINE__ # ^^^^^^^^ + # ``` def visit_source_line_node(node) s(node, :lit, node.location.start_line) end + # ``` # foo(*bar) # ^^^^ # @@ -1415,6 +1675,7 @@ module Prism # # def foo(*); bar(*); end # ^ + # ``` def visit_splat_node(node) if node.expression.nil? s(node, :splat) @@ -1434,8 +1695,10 @@ module Prism end end + # ``` # "foo" # ^^^^^ + # ``` def visit_string_node(node) unescaped = node.unescaped @@ -1447,8 +1710,10 @@ module Prism s(node, :str, unescaped) end + # ``` # super(foo) # ^^^^^^^^^^ + # ``` def visit_super_node(node) arguments = node.arguments&.arguments || [] block = node.block @@ -1461,60 +1726,76 @@ module Prism visit_block(node, s(node, :super).concat(visit_all(arguments)), block) end + # ``` # :foo # ^^^^ + # ``` def visit_symbol_node(node) node.value == "!@" ? s(node, :lit, :"!@") : s(node, :lit, node.unescaped.to_sym) end + # ``` # true # ^^^^ + # ``` def visit_true_node(node) s(node, :true) end + # ``` # undef foo # ^^^^^^^^^ + # ``` def visit_undef_node(node) names = node.names.map { |name| s(node, :undef, visit(name)) } names.length == 1 ? names.first : s(node, :block).concat(names) end + # ``` # unless foo; bar end # ^^^^^^^^^^^^^^^^^^^ # # bar unless foo # ^^^^^^^^^^^^^^ + # ``` def visit_unless_node(node) s(node, :if, visit(node.predicate), visit(node.else_clause), visit(node.statements)) end + # ``` # until foo; bar end # ^^^^^^^^^^^^^^^^^ # # bar until foo # ^^^^^^^^^^^^^ + # ``` def visit_until_node(node) s(node, :until, visit(node.predicate), visit(node.statements), !node.begin_modifier?) end + # ``` # case foo; when bar; end # ^^^^^^^^^^^^^ + # ``` def visit_when_node(node) s(node, :when, s(node, :array).concat(visit_all(node.conditions))).concat(node.statements.nil? ? [nil] : visit_all(node.statements.body)) end + # ``` # while foo; bar end # ^^^^^^^^^^^^^^^^^^ # # bar while foo # ^^^^^^^^^^^^^ + # ``` def visit_while_node(node) s(node, :while, visit(node.predicate), visit(node.statements), !node.begin_modifier?) end + # ``` # `foo` # ^^^^^ + # ``` def visit_x_string_node(node) result = s(node, :xstr, node.unescaped) @@ -1526,11 +1807,13 @@ module Prism result end + # ``` # yield # ^^^^^ # # yield 1 # ^^^^^^^ + # ``` def visit_yield_node(node) s(node, :yield).concat(visit_all(node.arguments&.arguments || [])) end diff --git a/lib/resolv.rb b/lib/resolv.rb index ca72f41c5c..e2255b7d11 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -33,7 +33,7 @@ require 'securerandom' class Resolv - VERSION = "0.6.0" + VERSION = "0.6.2" ## # Looks up the first IP address for +name+. @@ -1679,6 +1679,7 @@ class Resolv prev_index = @index save_index = nil d = [] + size = -1 while true raise DecodeError.new("limit exceeded") if @limit <= @index case @data.getbyte(@index) @@ -1699,7 +1700,10 @@ class Resolv end @index = idx else - d << self.get_label + l = self.get_label + d << l + size += 1 + l.string.bytesize + raise DecodeError.new("name label data exceed 255 octets") if size > 255 end end end @@ -2601,7 +2605,7 @@ class Resolv end ## - # Flags for this proprty: + # Flags for this property: # - Bit 0 : 0 = not critical, 1 = critical attr_reader :flags diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 1225cbe5cb..e4eca64fe1 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "3.7.0.dev" + VERSION = "3.8.0.dev" end # Must be first since it unloads the prelude from 1.9.2 @@ -249,6 +249,16 @@ module Gem find_spec_for_exe(name, exec_name, requirements).bin_file exec_name end + def self.find_and_activate_spec_for_exe(name, exec_name, requirements) + spec = find_spec_for_exe name, exec_name, requirements + Gem::LOADED_SPECS_MUTEX.synchronize do + spec.activate + finish_resolve + end + spec + end + private_class_method :find_and_activate_spec_for_exe + def self.find_spec_for_exe(name, exec_name, requirements) raise ArgumentError, "you must supply exec_name" unless exec_name @@ -273,6 +283,35 @@ module Gem end private_class_method :find_spec_for_exe + ## + # Find and load the full path to the executable for gem +name+. If the + # +exec_name+ is not given, an exception will be raised, otherwise the + # specified executable's path is returned. +requirements+ allows + # you to specify specific gem versions. + # + # A side effect of this method is that it will activate the gem that + # contains the executable. + # + # This method should *only* be used in bin stub files. + + def self.activate_and_load_bin_path(name, exec_name = nil, *requirements) + spec = find_and_activate_spec_for_exe name, exec_name, requirements + + if spec.name == "bundler" + # Make sure there's no version of Bundler in `$LOAD_PATH` that's different + # from the version we just activated. If that was the case (it happens + # when testing Bundler from ruby/ruby), we would load Bundler extensions + # to RubyGems from the copy in `$LOAD_PATH` but then load the binstub from + # an installed copy, causing those copies to be mixed and yet more + # redefinition warnings. + # + require_path = $LOAD_PATH.resolve_feature_path("bundler").last.delete_suffix("/bundler.rb") + Gem.load_bundler_extensions(spec.version) if spec.full_require_paths.include?(require_path) + end + + load spec.bin_file(exec_name) + end + ## # Find the full path to the executable for gem +name+. If the +exec_name+ # is not given, an exception will be raised, otherwise the @@ -285,12 +324,7 @@ module Gem # This method should *only* be used in bin stub files. def self.activate_bin_path(name, exec_name = nil, *requirements) # :nodoc: - spec = find_spec_for_exe name, exec_name, requirements - Gem::LOADED_SPECS_MUTEX.synchronize do - spec.activate - finish_resolve - end - spec.bin_file exec_name + find_and_activate_spec_for_exe(name, exec_name, requirements).bin_file exec_name end ## @@ -636,6 +670,30 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} @safe_marshal_loaded = true end + ## + # Load Bundler extensions to RubyGems, making sure to avoid redefinition + # warnings in platform constants + + def self.load_bundler_extensions(version) + return unless version <= "2.6.9" + + previous_platforms = {} + + platform_const_list = ["JAVA", "MSWIN", "MSWIN64", "MINGW", "X64_MINGW_LEGACY", "X64_MINGW", "UNIVERSAL_MINGW", "WINDOWS", "X64_LINUX", "X64_LINUX_MUSL"] + + platform_const_list.each do |platform| + previous_platforms[platform] = Gem::Platform.const_get(platform) + Gem::Platform.send(:remove_const, platform) + end + + require "bundler/rubygems_ext" + + platform_const_list.each do |platform| + Gem::Platform.send(:remove_const, platform) if Gem::Platform.const_defined?(platform) + Gem::Platform.const_set(platform, previous_platforms[platform]) + end + end + ## # The file name and line number of the caller of the caller of this method. # diff --git a/lib/rubygems/basic_specification.rb b/lib/rubygems/basic_specification.rb index a09e8ed0e1..a0b552f63c 100644 --- a/lib/rubygems/basic_specification.rb +++ b/lib/rubygems/basic_specification.rb @@ -256,6 +256,13 @@ class Gem::BasicSpecification raise NotImplementedError end + def installable_on_platform?(target_platform) # :nodoc: + return true if [Gem::Platform::RUBY, nil, target_platform].include?(platform) + return true if Gem::Platform.new(platform) === target_platform + + false + end + def raw_require_paths # :nodoc: raise NotImplementedError end diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index 97f1646ba0..93503d2b69 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -137,11 +137,14 @@ extensions will be restored. specs.group_by(&:full_name_with_location).values.each do |grouped_specs| spec = grouped_specs.find {|s| !s.default_gem? } || grouped_specs.first - unless only_executables_or_plugins? + only_executables = options[:only_executables] + only_plugins = options[:only_plugins] + + unless only_executables || only_plugins # Default gemspecs include changes provided by ruby-core installer that # can't currently be pristined (inclusion of compiled extension targets in # the file list). So stick to resetting executables if it's a default gem. - options[:only_executables] = true if spec.default_gem? + only_executables = true if spec.default_gem? end if options.key? :skip @@ -151,14 +154,14 @@ extensions will be restored. end end - unless spec.extensions.empty? || options[:extensions] || only_executables_or_plugins? + unless spec.extensions.empty? || options[:extensions] || only_executables || only_plugins say "Skipped #{spec.full_name_with_location}, it needs to compile an extension" next end gem = spec.cache_file - unless File.exist?(gem) || only_executables_or_plugins? + unless File.exist?(gem) || only_executables || only_plugins require_relative "../remote_fetcher" say "Cached gem for #{spec.full_name_with_location} not found, attempting to fetch..." @@ -194,10 +197,10 @@ extensions will be restored. bin_dir: bin_dir, } - if options[:only_executables] + if only_executables installer = Gem::Installer.for_spec(spec, installer_options) installer.generate_bin - elsif options[:only_plugins] + elsif only_plugins installer = Gem::Installer.for_spec(spec, installer_options) installer.generate_plugins else @@ -208,10 +211,4 @@ extensions will be restored. say "Restored #{spec.full_name_with_location}" end end - - private - - def only_executables_or_plugins? - options[:only_executables] || options[:only_plugins] - end end diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index 301ce25314..85e28ccedd 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -7,8 +7,8 @@ require_relative "../command" # RubyGems checkout or tarball. class Gem::Commands::SetupCommand < Gem::Command - HISTORY_HEADER = %r{^#\s*[\d.a-zA-Z]+\s*/\s*\d{4}-\d{2}-\d{2}\s*$} - VERSION_MATCHER = %r{^#\s*([\d.a-zA-Z]+)\s*/\s*\d{4}-\d{2}-\d{2}\s*$} + HISTORY_HEADER = %r{^##\s*[\d.a-zA-Z]+\s*/\s*\d{4}-\d{2}-\d{2}\s*$} + VERSION_MATCHER = %r{^##\s*([\d.a-zA-Z]+)\s*/\s*\d{4}-\d{2}-\d{2}\s*$} ENV_PATHS = %w[/usr/bin/env /bin/env].freeze diff --git a/lib/rubygems/core_ext/kernel_require.rb b/lib/rubygems/core_ext/kernel_require.rb index 073966b696..3a9bdbdc9d 100644 --- a/lib/rubygems/core_ext/kernel_require.rb +++ b/lib/rubygems/core_ext/kernel_require.rb @@ -64,8 +64,11 @@ module Kernel rp end - Kernel.send(:gem, name, Gem::Requirement.default_prerelease) unless - resolved_path + next if resolved_path + + Kernel.send(:gem, name, Gem::Requirement.default_prerelease) + + Gem.load_bundler_extensions(Gem.loaded_specs[name].version) if name == "bundler" next end diff --git a/lib/rubygems/ext/cargo_builder.rb b/lib/rubygems/ext/cargo_builder.rb index 03024a640e..21b50f394d 100644 --- a/lib/rubygems/ext/cargo_builder.rb +++ b/lib/rubygems/ext/cargo_builder.rb @@ -158,6 +158,10 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder # mkmf work properly. def linker_args cc_flag = self.class.shellsplit(makefile_config("CC")) + # Avoid to ccache like tool from Rust build + # see https://github.com/rubygems/rubygems/pull/8521#issuecomment-2689854359 + # ex. CC="ccache gcc" or CC="sccache clang --any --args" + cc_flag.shift if cc_flag.size >= 2 && !cc_flag[1].start_with?("-") linker = cc_flag.shift link_args = cc_flag.flat_map {|a| ["-C", "link-arg=#{a}"] } diff --git a/lib/rubygems/gemcutter_utilities.rb b/lib/rubygems/gemcutter_utilities.rb index 8d9a9b2d35..afe7957f43 100644 --- a/lib/rubygems/gemcutter_utilities.rb +++ b/lib/rubygems/gemcutter_utilities.rb @@ -319,7 +319,7 @@ module Gem::GemcutterUtilities end def get_scope_params(scope) - scope_params = { index_rubygems: true } + scope_params = { index_rubygems: true, push_rubygem: true } if scope scope_params = { scope => true } diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 7f5d913ac4..2b3200223a 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -228,8 +228,7 @@ class Gem::Installer ruby_executable = true existing = io.read.slice(/ ^\s*( - gem \s | - load \s Gem\.bin_path\( | + Gem\.activate_and_load_bin_path\( | load \s Gem\.activate_bin_path\( ) (['"])(.*?)(\2), @@ -749,54 +748,53 @@ class Gem::Installer def app_script_text(bin_file_name) # NOTE: that the `load` lines cannot be indented, as old RG versions match # against the beginning of the line - <<-TEXT -#{shebang bin_file_name} -# -# This file was generated by RubyGems. -# -# The application '#{spec.name}' is installed as part of a gem, and -# this file is here to facilitate running it. -# + <<~TEXT + #{shebang bin_file_name} + # + # This file was generated by RubyGems. + # + # The application '#{spec.name}' is installed as part of a gem, and + # this file is here to facilitate running it. + # -require 'rubygems' -#{gemdeps_load(spec.name)} -version = "#{Gem::Requirement.default_prerelease}" + require 'rubygems' + #{gemdeps_load(spec.name)} + version = "#{Gem::Requirement.default_prerelease}" -str = ARGV.first -if str - str = str.b[/\\A_(.*)_\\z/, 1] - if str and Gem::Version.correct?(str) - #{explicit_version_requirement(spec.name)} - ARGV.shift - end -end + str = ARGV.first + if str + str = str.b[/\\A_(.*)_\\z/, 1] + if str and Gem::Version.correct?(str) + #{explicit_version_requirement(spec.name)} + ARGV.shift + end + end -if Gem.respond_to?(:activate_bin_path) -load Gem.activate_bin_path('#{spec.name}', '#{bin_file_name}', version) -else -gem #{spec.name.dump}, version -load Gem.bin_path(#{spec.name.dump}, #{bin_file_name.dump}, version) -end -TEXT + if Gem.respond_to?(:activate_and_load_bin_path) + Gem.activate_and_load_bin_path('#{spec.name}', '#{bin_file_name}', version) + else + load Gem.activate_bin_path('#{spec.name}', '#{bin_file_name}', version) + end + TEXT end def gemdeps_load(name) return "" if name == "bundler" - <<-TEXT + <<~TEXT -Gem.use_gemdeps -TEXT + Gem.use_gemdeps + TEXT end def explicit_version_requirement(name) code = "version = str" return code unless name == "bundler" - code += <<-TEXT + code += <<~TEXT - ENV['BUNDLER_VERSION'] = str -TEXT + ENV['BUNDLER_VERSION'] = str + TEXT end ## @@ -811,9 +809,9 @@ TEXT if File.exist?(File.join(bindir, ruby_exe)) # stub & ruby.exe within same folder. Portable - <<-TEXT -@ECHO OFF -@"%~dp0#{ruby_exe}" "%~dpn0" %* + <<~TEXT + @ECHO OFF + @"%~dp0#{ruby_exe}" "%~dpn0" %* TEXT elsif bindir.downcase.start_with? rb_topdir.downcase # stub within ruby folder, but not standard bin. Portable @@ -821,16 +819,16 @@ TEXT from = Pathname.new bindir to = Pathname.new "#{rb_topdir}/bin" rel = to.relative_path_from from - <<-TEXT -@ECHO OFF -@"%~dp0#{rel}/#{ruby_exe}" "%~dpn0" %* + <<~TEXT + @ECHO OFF + @"%~dp0#{rel}/#{ruby_exe}" "%~dpn0" %* TEXT else # outside ruby folder, maybe -user-install or bundler. Portable, but ruby # is dependent on PATH - <<-TEXT -@ECHO OFF -@#{ruby_exe} "%~dpn0" %* + <<~TEXT + @ECHO OFF + @#{ruby_exe} "%~dpn0" %* TEXT end end @@ -953,11 +951,8 @@ TEXT end def ensure_writable_dir(dir) # :nodoc: - begin - Dir.mkdir dir, *[options[:dir_mode] && 0o755].compact - rescue SystemCallError - raise unless File.directory? dir - end + require "fileutils" + FileUtils.mkdir_p dir, mode: options[:dir_mode] && 0o755 raise Gem::FilePermissionError.new(dir) unless File.writable? dir end diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb index 450c214167..e30c266fab 100644 --- a/lib/rubygems/platform.rb +++ b/lib/rubygems/platform.rb @@ -88,56 +88,45 @@ class Gem::Platform when Array then @cpu, @os, @version = arch when String then - arch = arch.split "-" + cpu, os = arch.sub(/-+$/, "").split("-", 2) - if arch.length > 2 && !arch.last.match?(/\d+(\.\d+)?$/) # reassemble x86-linux-{libc} - extra = arch.pop - arch.last << "-#{extra}" + @cpu = if cpu&.match?(/i\d86/) + "x86" + else + cpu end - cpu = arch.shift - - @cpu = case cpu - when /i\d86/ then "x86" - else cpu - end - - if arch.length == 2 && arch.last.match?(/^\d+(\.\d+)?$/) # for command-line - @os, @version = arch - return - end - - os, = arch if os.nil? @cpu = nil os = cpu end # legacy jruby @os, @version = case os - when /aix(\d+)?/ then ["aix", $1] - when /cygwin/ then ["cygwin", nil] - when /darwin(\d+)?/ then ["darwin", $1] - when /^macruby$/ then ["macruby", nil] - when /freebsd(\d+)?/ then ["freebsd", $1] - when /^java$/, /^jruby$/ then ["java", nil] - when /^java([\d.]*)/ then ["java", $1] - when /^dalvik(\d+)?$/ then ["dalvik", $1] - when /^dotnet$/ then ["dotnet", nil] - when /^dotnet([\d.]*)/ then ["dotnet", $1] - when /linux-?(\w+)?/ then ["linux", $1] - when /mingw32/ then ["mingw32", nil] - when /mingw-?(\w+)?/ then ["mingw", $1] - when /(mswin\d+)(\_(\d+))?/ then + when /aix-?(\d+)?/ then ["aix", $1] + when /cygwin/ then ["cygwin", nil] + when /darwin-?(\d+)?/ then ["darwin", $1] + when "macruby" then ["macruby", nil] + when /^macruby-?(\d+(?:\.\d+)*)?/ then ["macruby", $1] + when /freebsd-?(\d+)?/ then ["freebsd", $1] + when "java", "jruby" then ["java", nil] + when /^java-?(\d+(?:\.\d+)*)?/ then ["java", $1] + when /^dalvik-?(\d+)?$/ then ["dalvik", $1] + when /^dotnet$/ then ["dotnet", nil] + when /^dotnet-?(\d+(?:\.\d+)*)?/ then ["dotnet", $1] + when /linux-?(\w+)?/ then ["linux", $1] + when /mingw32/ then ["mingw32", nil] + when /mingw-?(\w+)?/ then ["mingw", $1] + when /(mswin\d+)(?:[_-](\d+))?/ then os = $1 - version = $3 - @cpu = "x86" if @cpu.nil? && os =~ /32$/ + version = $2 + @cpu = "x86" if @cpu.nil? && os.end_with?("32") [os, version] - when /netbsdelf/ then ["netbsdelf", nil] - when /openbsd(\d+\.\d+)?/ then ["openbsd", $1] - when /solaris(\d+\.\d+)?/ then ["solaris", $1] - when /wasi/ then ["wasi", nil] + when /netbsdelf/ then ["netbsdelf", nil] + when /openbsd-?(\d+\.\d+)?/ then ["openbsd", $1] + when /solaris-?(\d+\.\d+)?/ then ["solaris", $1] + when /wasi/ then ["wasi", nil] # test - when /^(\w+_platform)(\d+)?/ then [$1, $2] + when /^(\w+_platform)-?(\d+)?/ then [$1, $2] else ["unknown", nil] end when Gem::Platform then @@ -154,7 +143,7 @@ class Gem::Platform end def to_s - to_a.compact.join "-" + to_a.compact.join(@cpu.nil? ? "" : "-") end ## @@ -266,4 +255,118 @@ class Gem::Platform # This will be replaced with Gem::Platform::local. CURRENT = "current" + + JAVA = Gem::Platform.new("java") # :nodoc: + MSWIN = Gem::Platform.new("mswin32") # :nodoc: + MSWIN64 = Gem::Platform.new("mswin64") # :nodoc: + MINGW = Gem::Platform.new("x86-mingw32") # :nodoc: + X64_MINGW_LEGACY = Gem::Platform.new("x64-mingw32") # :nodoc: + X64_MINGW = Gem::Platform.new("x64-mingw-ucrt") # :nodoc: + UNIVERSAL_MINGW = Gem::Platform.new("universal-mingw") # :nodoc: + WINDOWS = [MSWIN, MSWIN64, UNIVERSAL_MINGW].freeze # :nodoc: + X64_LINUX = Gem::Platform.new("x86_64-linux") # :nodoc: + X64_LINUX_MUSL = Gem::Platform.new("x86_64-linux-musl") # :nodoc: + + GENERICS = [JAVA, *WINDOWS].freeze # :nodoc: + private_constant :GENERICS + + GENERIC_CACHE = GENERICS.each_with_object({}) {|g, h| h[g] = g } # :nodoc: + private_constant :GENERIC_CACHE + + class << self + ## + # Returns the generic platform for the given platform. + + def generic(platform) + return Gem::Platform::RUBY if platform.nil? || platform == Gem::Platform::RUBY + + GENERIC_CACHE[platform] ||= begin + found = GENERICS.find do |match| + platform === match + end + found || Gem::Platform::RUBY + end + end + + ## + # Returns the platform specificity match for the given spec platform and user platform. + + def platform_specificity_match(spec_platform, user_platform) + return -1 if spec_platform == user_platform + return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY + + os_match(spec_platform, user_platform) + + cpu_match(spec_platform, user_platform) * 10 + + version_match(spec_platform, user_platform) * 100 + end + + ## + # Sorts and filters the best platform match for the given matching specs and platform. + + def sort_and_filter_best_platform_match(matching, platform) + return matching if matching.one? + + exact = matching.select {|spec| spec.platform == platform } + return exact if exact.any? + + sorted_matching = sort_best_platform_match(matching, platform) + exemplary_spec = sorted_matching.first + + sorted_matching.take_while {|spec| same_specificity?(platform, spec, exemplary_spec) && same_deps?(spec, exemplary_spec) } + end + + ## + # Sorts the best platform match for the given matching specs and platform. + + def sort_best_platform_match(matching, platform) + matching.sort_by.with_index do |spec, i| + [ + platform_specificity_match(spec.platform, platform), + i, # for stable sort + ] + end + end + + private + + def same_specificity?(platform, spec, exemplary_spec) + platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform) + end + + def same_deps?(spec, exemplary_spec) + spec.required_ruby_version == exemplary_spec.required_ruby_version && + spec.required_rubygems_version == exemplary_spec.required_rubygems_version && + spec.dependencies.sort == exemplary_spec.dependencies.sort + end + + def os_match(spec_platform, user_platform) + if spec_platform.os == user_platform.os + 0 + else + 1 + end + end + + def cpu_match(spec_platform, user_platform) + if spec_platform.cpu == user_platform.cpu + 0 + elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm") + 0 + elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal" + 1 + else + 2 + end + end + + def version_match(spec_platform, user_platform) + if spec_platform.version == user_platform.version + 0 + elsif spec_platform.version.nil? + 1 + else + 2 + end + end + end end diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index 4b5c74e0ea..355a668b39 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -267,7 +267,7 @@ class Gem::RemoteFetcher def fetch_s3(uri, mtime = nil, head = false) begin - public_uri = s3_uri_signer(uri).sign + public_uri = s3_uri_signer(uri, head ? "HEAD" : "GET").sign rescue Gem::S3URISigner::ConfigurationError, Gem::S3URISigner::InstanceProfileError => e raise FetchError.new(e.message, "s3://#{uri.host}") end @@ -275,8 +275,8 @@ class Gem::RemoteFetcher end # we have our own signing code here to avoid a dependency on the aws-sdk gem - def s3_uri_signer(uri) - Gem::S3URISigner.new(uri) + def s3_uri_signer(uri, method) + Gem::S3URISigner.new(uri, method) end ## diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb index 875df7e019..5a855fdb10 100644 --- a/lib/rubygems/request_set.rb +++ b/lib/rubygems/request_set.rb @@ -181,13 +181,10 @@ class Gem::RequestSet # Install requested gems after they have been downloaded sorted_requests.each do |req| - if req.installed? + if req.installed? && @always_install.none? {|spec| spec == req.spec.spec } req.spec.spec.build_extensions - - if @always_install.none? {|spec| spec == req.spec.spec } - yield req, nil if block_given? - next - end + yield req, nil if block_given? + next end spec = diff --git a/lib/rubygems/resolver.rb b/lib/rubygems/resolver.rb index 35d83abd2d..9bf5f80930 100644 --- a/lib/rubygems/resolver.rb +++ b/lib/rubygems/resolver.rb @@ -241,7 +241,7 @@ class Gem::Resolver sources.each do |source| groups[source]. - sort_by {|spec| [spec.version, spec.platform =~ Gem::Platform.local ? 1 : 0] }. # rubocop:disable Performance/RegexpMatch + sort_by {|spec| [spec.version, -Gem::Platform.platform_specificity_match(spec.platform, Gem::Platform.local)] }. map {|spec| ActivationRequest.new spec, dependency }. each {|activation_request| activation_requests << activation_request } end diff --git a/lib/rubygems/resolver/best_set.rb b/lib/rubygems/resolver/best_set.rb index e2307f6e02..e647a2c11b 100644 --- a/lib/rubygems/resolver/best_set.rb +++ b/lib/rubygems/resolver/best_set.rb @@ -21,7 +21,7 @@ class Gem::Resolver::BestSet < Gem::Resolver::ComposedSet def pick_sets # :nodoc: @sources.each_source do |source| - @sets << source.dependency_resolver_set + @sets << source.dependency_resolver_set(@prerelease) end end diff --git a/lib/rubygems/resolver/source_set.rb b/lib/rubygems/resolver/source_set.rb index 296cf41078..074b473edc 100644 --- a/lib/rubygems/resolver/source_set.rb +++ b/lib/rubygems/resolver/source_set.rb @@ -42,6 +42,6 @@ class Gem::Resolver::SourceSet < Gem::Resolver::Set def get_set(name) link = @links[name] - @sets[link] ||= Gem::Source.new(link).dependency_resolver_set if link + @sets[link] ||= Gem::Source.new(link).dependency_resolver_set(@prerelease) if link end end diff --git a/lib/rubygems/s3_uri_signer.rb b/lib/rubygems/s3_uri_signer.rb index 7c95a9d4f5..0d8e9e8285 100644 --- a/lib/rubygems/s3_uri_signer.rb +++ b/lib/rubygems/s3_uri_signer.rb @@ -27,9 +27,11 @@ class Gem::S3URISigner end attr_accessor :uri + attr_accessor :method - def initialize(uri) + def initialize(uri, method) @uri = uri + @method = method end ## @@ -38,7 +40,7 @@ class Gem::S3URISigner s3_config = fetch_s3_config current_time = Time.now.utc - date_time = current_time.strftime("%Y%m%dT%H%m%SZ") + date_time = current_time.strftime("%Y%m%dT%H%M%SZ") date = date_time[0,8] credential_info = "#{date}/#{s3_config.region}/s3/aws4_request" @@ -73,7 +75,7 @@ class Gem::S3URISigner def generate_canonical_request(canonical_host, query_params) [ - "GET", + method.upcase, uri.path, query_params, "host:#{canonical_host}", diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb index bee5681dab..772ad04bc9 100644 --- a/lib/rubygems/source.rb +++ b/lib/rubygems/source.rb @@ -67,28 +67,11 @@ class Gem::Source ## # Returns a Set that can fetch specifications from this source. - - def dependency_resolver_set # :nodoc: - return Gem::Resolver::IndexSet.new self if uri.scheme == "file" - - fetch_uri = if uri.host == "rubygems.org" - index_uri = uri.dup - index_uri.host = "index.rubygems.org" - index_uri - else - uri - end - - bundler_api_uri = enforce_trailing_slash(fetch_uri) + "versions" - - begin - fetcher = Gem::RemoteFetcher.fetcher - response = fetcher.fetch_path bundler_api_uri, nil, true - rescue Gem::RemoteFetcher::FetchError - Gem::Resolver::IndexSet.new self - else - Gem::Resolver::APISet.new response.uri + "./info/" - end + # + # The set will optionally fetch prereleases if requested. + # + def dependency_resolver_set(prerelease = false) + new_dependency_resolver_set.tap {|set| set.prerelease = prerelease } end def hash # :nodoc: @@ -234,6 +217,29 @@ class Gem::Source private + def new_dependency_resolver_set + return Gem::Resolver::IndexSet.new self if uri.scheme == "file" + + fetch_uri = if uri.host == "rubygems.org" + index_uri = uri.dup + index_uri.host = "index.rubygems.org" + index_uri + else + uri + end + + bundler_api_uri = enforce_trailing_slash(fetch_uri) + "versions" + + begin + fetcher = Gem::RemoteFetcher.fetcher + response = fetcher.fetch_path bundler_api_uri, nil, true + rescue Gem::RemoteFetcher::FetchError + Gem::Resolver::IndexSet.new self + else + Gem::Resolver::APISet.new response.uri + "./info/" + end + end + def enforce_trailing_slash(uri) uri.merge(uri.path.gsub(%r{/+$}, "") + "/") end diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index 0b905a7ea7..68ebbf8bc3 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -1002,7 +1002,7 @@ class Gem::Specification < Gem::BasicSpecification def self.find_in_unresolved_tree(path) unresolved_specs.each do |spec| spec.traverse do |_from_spec, _dep, to_spec, trail| - if to_spec.has_conflicts? || to_spec.conficts_when_loaded_with?(trail) + if to_spec.has_conflicts? || to_spec.conflicts_when_loaded_with?(trail) :next else return trail.reverse if to_spec.contains_requirable_file? path @@ -1649,7 +1649,7 @@ class Gem::Specification < Gem::BasicSpecification ## # return true if there will be conflict when spec if loaded together with the list of specs. - def conficts_when_loaded_with?(list_of_specs) # :nodoc: + def conflicts_when_loaded_with?(list_of_specs) # :nodoc: result = list_of_specs.any? do |spec| spec.runtime_dependencies.any? {|dep| (dep.name == name) && !satisfies_requirement?(dep) } end diff --git a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA_R3.pem b/lib/rubygems/ssl_certs/rubygems.org/GlobalSign.pem similarity index 100% rename from lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA_R3.pem rename to lib/rubygems/ssl_certs/rubygems.org/GlobalSign.pem diff --git a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem b/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem deleted file mode 100644 index f4ce4ca43d..0000000000 --- a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG -A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv -b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw -MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i -YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT -aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ -jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp -xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp -1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG -snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ -U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 -9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E -BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B -AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz -yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE -38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP -AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad -DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME -HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== ------END CERTIFICATE----- diff --git a/lib/rubygems/util/licenses.rb b/lib/rubygems/util/licenses.rb index 3d9df5ebcd..fbb7b55075 100644 --- a/lib/rubygems/util/licenses.rb +++ b/lib/rubygems/util/licenses.rb @@ -59,12 +59,15 @@ class Gem::Licenses Artistic-1.0-Perl Artistic-1.0-cl8 Artistic-2.0 + Artistic-dist + Aspell-RU BSD-1-Clause BSD-2-Clause BSD-2-Clause-Darwin BSD-2-Clause-Patent BSD-2-Clause-Views BSD-2-Clause-first-lines + BSD-2-Clause-pkgconf-disclaimer BSD-3-Clause BSD-3-Clause-Attribution BSD-3-Clause-Clear @@ -205,6 +208,7 @@ class Gem::Licenses Cornell-Lossless-JPEG Cronyx Crossword + CryptoSwift CrystalStacker Cube D-FSL-1.0 @@ -215,6 +219,7 @@ class Gem::Licenses DRL-1.0 DRL-1.1 DSDP + DocBook-DTD DocBook-Schema DocBook-Stylesheet DocBook-XML @@ -240,7 +245,10 @@ class Gem::Licenses FSFAP-no-warranty-disclaimer FSFUL FSFULLR + FSFULLRSD FSFULLRWD + FSL-1.1-ALv2 + FSL-1.1-MIT FTL Fair Ferguson-Twofish @@ -276,11 +284,13 @@ class Gem::Licenses GPL-2.0-or-later GPL-3.0-only GPL-3.0-or-later + Game-Programming-Gems Giftware Glide Glulxe Graphics-Gems Gutmann + HDF5 HIDAPI HP-1986 HP-1989 @@ -426,6 +436,7 @@ class Gem::Licenses NPL-1.1 NPOSL-3.0 NRL + NTIA-PD NTP NTP-0 Naumen @@ -528,11 +539,13 @@ class Gem::Licenses SMLNJ SMPPL SNIA + SOFA SPL-1.0 SSH-OpenSSH SSH-short SSLeay-standalone SSPL-1.0 + SUL-1.0 SWL Saxpath SchemeReport @@ -578,6 +591,8 @@ class Gem::Licenses Unicode-TOU UnixCrypt Unlicense + Unlicense-libtelnet + Unlicense-libwhirlpool VOSTROM VSL-1.0 Vim @@ -631,6 +646,8 @@ class Gem::Licenses gtkbook hdparm iMatix + jove + libpng-1.6.35 libpng-2.0 libselinux-1.0 libtiff @@ -638,10 +655,12 @@ class Gem::Licenses lsof magaz mailprio + man2html metamail mpi-permissive mpich2 mplus + ngrep pkgconf pnmstitch psfrag @@ -716,6 +735,7 @@ class Gem::Licenses CLISP-exception-2.0 Classpath-exception-2.0 DigiRule-FOSS-exception + Digia-Qt-LGPL-exception-1.1 FLTK-exception Fawkes-Runtime-exception Font-exception-2.0 @@ -772,6 +792,7 @@ class Gem::Licenses mif-exception mxml-exception openvpn-openssl-exception + polyparse-exception romic-exception stunnel-exception u-boot-exception-2.0 diff --git a/lib/rubygems/vendor/resolv/lib/resolv.rb b/lib/rubygems/vendor/resolv/lib/resolv.rb index 4d95e5fc7f..2825b1ea97 100644 --- a/lib/rubygems/vendor/resolv/lib/resolv.rb +++ b/lib/rubygems/vendor/resolv/lib/resolv.rb @@ -33,7 +33,7 @@ require_relative '../../../vendored_securerandom' class Gem::Resolv - VERSION = "0.6.0" + VERSION = "0.6.2" ## # Looks up the first IP address for +name+. @@ -173,13 +173,16 @@ class Gem::Resolv class ResolvTimeout < Gem::Timeout::Error; end + WINDOWS = /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM || ::RbConfig::CONFIG['host_os'] =~ /mswin/ + private_constant :WINDOWS + ## # Gem::Resolv::Hosts is a hostname resolver that uses the system hosts file. class Hosts - if /mswin|mingw|cygwin/ =~ RUBY_PLATFORM and + if WINDOWS begin - require 'win32/resolv' + require 'win32/resolv' unless defined?(Win32::Resolv) DefaultFileName = Win32::Resolv.get_hosts_path || IO::NULL rescue LoadError end @@ -659,8 +662,20 @@ class Gem::Resolv } end - def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: - begin + case RUBY_PLATFORM + when *[ + # https://www.rfc-editor.org/rfc/rfc6056.txt + # Appendix A. Survey of the Algorithms in Use by Some Popular Implementations + /freebsd/, /linux/, /netbsd/, /openbsd/, /solaris/, + /darwin/, # the same as FreeBSD + ] then + def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: + udpsock.bind(bind_host, 0) + end + else + # Sequential port assignment + def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: + # Ephemeral port number range recommended by RFC 6056 port = random(1024..65535) udpsock.bind(bind_host, port) rescue Errno::EADDRINUSE, # POSIX @@ -983,13 +998,13 @@ class Gem::Resolv next unless keyword case keyword when 'nameserver' - nameserver.concat(args) + nameserver.concat(args.each(&:freeze)) when 'domain' next if args.empty? - search = [args[0]] + search = [args[0].freeze] when 'search' next if args.empty? - search = args + search = args.each(&:freeze) when 'options' args.each {|arg| case arg @@ -1000,22 +1015,22 @@ class Gem::Resolv end } } - return { :nameserver => nameserver, :search => search, :ndots => ndots } + return { :nameserver => nameserver.freeze, :search => search.freeze, :ndots => ndots.freeze }.freeze end def Config.default_config_hash(filename="/etc/resolv.conf") if File.exist? filename - config_hash = Config.parse_resolv_conf(filename) + Config.parse_resolv_conf(filename) + elsif WINDOWS + require 'win32/resolv' unless defined?(Win32::Resolv) + search, nameserver = Win32::Resolv.get_resolv_info + config_hash = {} + config_hash[:nameserver] = nameserver if nameserver + config_hash[:search] = [search].flatten if search + config_hash else - if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM - require 'win32/resolv' - search, nameserver = Win32::Resolv.get_resolv_info - config_hash = {} - config_hash[:nameserver] = nameserver if nameserver - config_hash[:search] = [search].flatten if search - end + {} end - config_hash || {} end def lazy_initialize @@ -1664,6 +1679,7 @@ class Gem::Resolv prev_index = @index save_index = nil d = [] + size = -1 while true raise DecodeError.new("limit exceeded") if @limit <= @index case @data.getbyte(@index) @@ -1684,7 +1700,10 @@ class Gem::Resolv end @index = idx else - d << self.get_label + l = self.get_label + d << l + size += 1 + l.string.bytesize + raise DecodeError.new("name label data exceed 255 octets") if size > 255 end end end @@ -2110,7 +2129,14 @@ class Gem::Resolv attr_reader :ttl - ClassHash = {} # :nodoc: + ClassHash = Module.new do + module_function + + def []=(type_class_value, klass) + type_value, class_value = type_class_value + Resource.const_set(:"Type#{type_value}_Class#{class_value}", klass) + end + end def encode_rdata(msg) # :nodoc: raise NotImplementedError.new @@ -2148,7 +2174,9 @@ class Gem::Resolv end def self.get_class(type_value, class_value) # :nodoc: - return ClassHash[[type_value, class_value]] || + cache = :"Type#{type_value}_Class#{class_value}" + + return (const_defined?(cache) && const_get(cache)) || Generic.create(type_value, class_value) end @@ -2577,7 +2605,7 @@ class Gem::Resolv end ## - # Flags for this proprty: + # Flags for this property: # - Bit 0 : 0 = not critical, 1 = critical attr_reader :flags diff --git a/lib/set/sorted_set.rb b/lib/set/sorted_set.rb deleted file mode 100644 index bc07bc1fb0..0000000000 --- a/lib/set/sorted_set.rb +++ /dev/null @@ -1,6 +0,0 @@ -begin - require 'sorted_set' -rescue ::LoadError - raise "The `SortedSet` class has been extracted from the `set` library. " \ - "You must use the `sorted_set` gem or other alternatives." -end diff --git a/lib/tempfile.rb b/lib/tempfile.rb index f3213c5684..7292e72c25 100644 --- a/lib/tempfile.rb +++ b/lib/tempfile.rb @@ -29,7 +29,7 @@ require 'tmpdir' # require 'tempfile' # # # Tempfile.create with a block -# # The filename are choosen automatically. +# # The filename are chosen automatically. # # (You can specify the prefix and suffix of the filename by an optional argument.) # Tempfile.create {|f| # f.puts "foo" diff --git a/lib/timeout.rb b/lib/timeout.rb index 4fd1fa46da..f5f232ad2a 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -123,6 +123,9 @@ module Timeout def self.ensure_timeout_thread_created unless @timeout_thread and @timeout_thread.alive? + # If the Mutex is already owned we are in a signal handler. + # In that case, just return and let the main thread create the @timeout_thread. + return if TIMEOUT_THREAD_MUTEX.owned? TIMEOUT_THREAD_MUTEX.synchronize do unless @timeout_thread and @timeout_thread.alive? @timeout_thread = create_timeout_thread diff --git a/lib/tsort.gemspec b/lib/tsort.gemspec index 0e2f110a53..8970cbe826 100644 --- a/lib/tsort.gemspec +++ b/lib/tsort.gemspec @@ -20,8 +20,13 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + dir, gemspec = File.split(__FILE__) + excludes = %W[ + :^/.git* :^/bin/ :^/test/ :^/spec/ :^/features/ :^/Gemfile :^/Rakefile + :^/#{gemspec} + ] + spec.files = IO.popen(%w[git ls-files -z --] + excludes, chdir: dir) do |f| + f.read.split("\x0") end spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } diff --git a/lib/tsort.rb b/lib/tsort.rb index dbaed45415..3c9635baa3 100644 --- a/lib/tsort.rb +++ b/lib/tsort.rb @@ -123,8 +123,10 @@ module TSort + # The version string. VERSION = "0.2.0" + # Exception class to be raised when a cycle is found. class Cyclic < StandardError end diff --git a/lib/uri/common.rb b/lib/uri/common.rb index 1115736297..61221fafad 100644 --- a/lib/uri/common.rb +++ b/lib/uri/common.rb @@ -92,6 +92,40 @@ module URI end module Schemes # :nodoc: + class << self + ReservedChars = ".+-" + EscapedChars = "\u01C0\u01C1\u01C2" + # Use Lo category chars as escaped chars for TruffleRuby, which + # does not allow Symbol categories as identifiers. + + def escape(name) + unless name and name.ascii_only? + return nil + end + name.upcase.tr(ReservedChars, EscapedChars) + end + + def unescape(name) + name.tr(EscapedChars, ReservedChars).encode(Encoding::US_ASCII).upcase + end + + def find(name) + const_get(name, false) if name and const_defined?(name, false) + end + + def register(name, klass) + unless scheme = escape(name) + raise ArgumentError, "invalid character as scheme - #{name}" + end + const_set(scheme, klass) + end + + def list + constants.map { |name| + [unescape(name.to_s), const_get(name)] + }.to_h + end + end end private_constant :Schemes @@ -104,7 +138,7 @@ module URI # Note that after calling String#upcase on +scheme+, it must be a valid # constant name. def self.register_scheme(scheme, klass) - Schemes.const_set(scheme.to_s.upcase, klass) + Schemes.register(scheme, klass) end # Returns a hash of the defined schemes: @@ -122,14 +156,14 @@ module URI # # Related: URI.register_scheme. def self.scheme_list - Schemes.constants.map { |name| - [name.to_s.upcase, Schemes.const_get(name)] - }.to_h + Schemes.list end + # :stopdoc: INITIAL_SCHEMES = scheme_list private_constant :INITIAL_SCHEMES Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor) + # :startdoc: # Returns a new object constructed from the given +scheme+, +arguments+, # and +default+: @@ -148,12 +182,10 @@ module URI # # => # # def self.for(scheme, *arguments, default: Generic) - const_name = scheme.to_s.upcase + const_name = Schemes.escape(scheme) uri_class = INITIAL_SCHEMES[const_name] - uri_class ||= if /\A[A-Z]\w*\z/.match?(const_name) && Schemes.const_defined?(const_name, false) - Schemes.const_get(const_name, false) - end + uri_class ||= Schemes.find(const_name) uri_class ||= default return uri_class.new(scheme, *arguments) @@ -407,6 +439,8 @@ module URI _decode_uri_component(/%\h\h/, str, enc) end + # Returns a string derived from the given string +str+ with + # URI-encoded characters matching +regexp+ according to +table+. def self._encode_uri_component(regexp, table, str, enc) str = str.to_s.dup if str.encoding != Encoding::ASCII_8BIT @@ -421,6 +455,8 @@ module URI end private_class_method :_encode_uri_component + # Returns a string decoding characters matching +regexp+ from the + # given \URL-encoded string +str+. def self._decode_uri_component(regexp, str, enc) raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str) str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc) @@ -859,6 +895,7 @@ module Kernel # Returns a \URI object derived from the given +uri+, # which may be a \URI string or an existing \URI object: # + # require 'uri' # # Returns a new URI. # uri = URI('http://github.com/ruby/ruby') # # => # @@ -866,6 +903,8 @@ module Kernel # URI(uri) # # => # # + # You must require 'uri' to use this method. + # def URI(uri) if uri.is_a?(URI::Generic) uri diff --git a/lib/uri/generic.rb b/lib/uri/generic.rb index 07f329e3d1..d811c5b944 100644 --- a/lib/uri/generic.rb +++ b/lib/uri/generic.rb @@ -126,9 +126,9 @@ module URI end end else - component = self.class.component rescue ::URI::Generic::COMPONENT + component = self.component rescue ::URI::Generic::COMPONENT raise ArgumentError, - "expected Array of or Hash of components of #{self.class} (#{component.join(', ')})" + "expected Array of or Hash of components of #{self} (#{component.join(', ')})" end tmp << nil @@ -284,7 +284,7 @@ module URI # Returns the parser to be used. # - # Unless a URI::Parser is defined, DEFAULT_PARSER is used. + # Unless the +parser+ is defined, DEFAULT_PARSER is used. # def parser if !defined?(@parser) || !@parser @@ -315,7 +315,7 @@ module URI end # - # Checks the scheme +v+ component against the URI::Parser Regexp for :SCHEME. + # Checks the scheme +v+ component against the +parser+ Regexp for :SCHEME. # def check_scheme(v) if v && parser.regexp[:SCHEME] !~ v @@ -385,7 +385,7 @@ module URI # # Checks the user +v+ component for RFC2396 compliance - # and against the URI::Parser Regexp for :USERINFO. + # and against the +parser+ Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. @@ -409,7 +409,7 @@ module URI # # Checks the password +v+ component for RFC2396 compliance - # and against the URI::Parser Regexp for :USERINFO. + # and against the +parser+ Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. @@ -586,7 +586,7 @@ module URI # # Checks the host +v+ component for RFC2396 compliance - # and against the URI::Parser Regexp for :HOST. + # and against the +parser+ Regexp for :HOST. # # Can not have a registry or opaque component defined, # with a host component defined. @@ -675,7 +675,7 @@ module URI # # Checks the port +v+ component for RFC2396 compliance - # and against the URI::Parser Regexp for :PORT. + # and against the +parser+ Regexp for :PORT. # # Can not have a registry or opaque component defined, # with a port component defined. @@ -748,7 +748,7 @@ module URI # # Checks the path +v+ component for RFC2396 compliance - # and against the URI::Parser Regexp + # and against the +parser+ Regexp # for :ABS_PATH and :REL_PATH. # # Can not have a opaque component defined, @@ -853,7 +853,7 @@ module URI # # Checks the opaque +v+ component for RFC2396 compliance and - # against the URI::Parser Regexp for :OPAQUE. + # against the +parser+ Regexp for :OPAQUE. # # Can not have a host, port, user, or path component defined, # with an opaque component defined. @@ -905,7 +905,7 @@ module URI end # - # Checks the fragment +v+ component against the URI::Parser Regexp for :FRAGMENT. + # Checks the fragment +v+ component against the +parser+ Regexp for :FRAGMENT. # # # == Args diff --git a/lib/uri/http.rb b/lib/uri/http.rb index 900b132c8c..3c41cd4e93 100644 --- a/lib/uri/http.rb +++ b/lib/uri/http.rb @@ -61,6 +61,18 @@ module URI super(tmp) end + # Do not allow empty host names, as they are not allowed by RFC 3986. + def check_host(v) + ret = super + + if ret && v.empty? + raise InvalidComponentError, + "bad component(expected host component): #{v}" + end + + ret + end + # # == Description # diff --git a/lib/uri/mailto.rb b/lib/uri/mailto.rb index cb8024f301..f747b79ec7 100644 --- a/lib/uri/mailto.rb +++ b/lib/uri/mailto.rb @@ -52,7 +52,11 @@ module URI HEADER_REGEXP = /\A(?(?:%\h\h|[!$'-.0-;@-Z_a-z~])*=(?:%\h\h|[!$'-.0-;@-Z_a-z~])*)(?:&\g)*\z/ # practical regexp for email address # https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address - EMAIL_REGEXP = /\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z/ + EMAIL_REGEXP = %r[\A#{ + atext = %q[(?:[a-zA-Z0-9!\#$%&'*+\/=?^_`{|}~-]+)] + }(?:\.#{atext})*@#{ + label = %q[(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)] + }(?:\.#{label})*\z] # :startdoc: # diff --git a/lib/uri/rfc2396_parser.rb b/lib/uri/rfc2396_parser.rb index 75a2d2dbde..cefd126cc6 100644 --- a/lib/uri/rfc2396_parser.rb +++ b/lib/uri/rfc2396_parser.rb @@ -67,7 +67,7 @@ module URI # # == Synopsis # - # URI::Parser.new([opts]) + # URI::RFC2396_Parser.new([opts]) # # == Args # @@ -86,7 +86,7 @@ module URI # # == Examples # - # p = URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") + # p = URI::RFC2396_Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") # u = p.parse("http://example.jp/%uABCD") #=> # # URI.parse(u.to_s) #=> raises URI::InvalidURIError # @@ -108,12 +108,12 @@ module URI # The Hash of patterns. # - # See also URI::Parser.initialize_pattern. + # See also #initialize_pattern. attr_reader :pattern # The Hash of Regexp. # - # See also URI::Parser.initialize_regexp. + # See also #initialize_regexp. attr_reader :regexp # Returns a split URI against +regexp[:ABS_URI]+. @@ -202,8 +202,7 @@ module URI # # == Usage # - # p = URI::Parser.new - # p.parse("ldap://ldap.example.com/dc=example?user=john") + # URI::RFC2396_PARSER.parse("ldap://ldap.example.com/dc=example?user=john") # #=> # # def parse(uri) @@ -244,7 +243,7 @@ module URI # If no +block+ given, then returns the result, # else it calls +block+ for each element in result. # - # See also URI::Parser.make_regexp. + # See also #make_regexp. # def extract(str, schemes = nil) if block_given? @@ -263,7 +262,7 @@ module URI unless schemes @regexp[:ABS_URI_REF] else - /(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x + /(?=(?i:#{Regexp.union(*schemes).source}):)#{@pattern[:X_ABS_URI]}/x end end @@ -524,6 +523,8 @@ module URI ret end + # Returns +uri+ as-is if it is URI, or convert it to URI if it is + # a String. def convert_to_uri(uri) if uri.is_a?(URI::Generic) uri diff --git a/lib/weakref.rb b/lib/weakref.rb index f0a7e7b318..0a09f7f993 100644 --- a/lib/weakref.rb +++ b/lib/weakref.rb @@ -17,7 +17,7 @@ require "delegate" # class WeakRef < Delegator - VERSION = "0.1.3" + VERSION = "0.1.4" ## # RefError is raised when a referenced object has been recycled by the diff --git a/load.c b/load.c index 5cf6760e6f..329b0f4b3b 100644 --- a/load.c +++ b/load.c @@ -372,6 +372,8 @@ features_index_add_single(vm_ns_t *vm_ns, const char* str, size_t len, VALUE off static void features_index_add(vm_ns_t *vm_ns, VALUE feature, VALUE offset) { + RUBY_ASSERT(rb_ractor_main_p()); + const char *feature_str, *feature_end, *ext, *p; bool rb = false; @@ -1371,7 +1373,8 @@ struct rb_vm_call_cfunc2_data { }; static VALUE -call_load_ext_in_ns(VALUE data){ +call_load_ext_in_ns(VALUE data) +{ struct rb_vm_call_cfunc2_data *arg = (struct rb_vm_call_cfunc2_data *)data; return rb_vm_call_cfunc2(arg->recv, load_ext, arg->arg1, arg->arg2, arg->block_handler, arg->filename); } @@ -1522,6 +1525,10 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa int rb_require_internal_silent(VALUE fname) { + if (!rb_ractor_main_p()) { + return NUM2INT(rb_ractor_require(fname, true)); + } + rb_execution_context_t *ec = GET_EC(); return require_internal(ec, fname, 1, false); } @@ -1558,7 +1565,7 @@ rb_require_string_internal(VALUE fname, bool resurrect) // main ractor check if (!rb_ractor_main_p()) { if (resurrect) fname = rb_str_resurrect(fname); - return rb_ractor_require(fname); + return rb_ractor_require(fname, false); } else { int result = require_internal(ec, fname, 1, RTEST(ruby_verbose)); diff --git a/marshal.c b/marshal.c index a38e7ee56a..7db4bfc6d9 100644 --- a/marshal.c +++ b/marshal.c @@ -145,12 +145,14 @@ rb_marshal_define_compat(VALUE newclass, VALUE oldclass, VALUE (*dumper)(VALUE), compat_allocator_table(); compat = ALLOC(marshal_compat_t); - RB_OBJ_WRITE(compat_allocator_tbl_wrapper, &compat->newclass, newclass); - RB_OBJ_WRITE(compat_allocator_tbl_wrapper, &compat->oldclass, oldclass); + compat->newclass = newclass; + compat->oldclass = oldclass; compat->dumper = dumper; compat->loader = loader; st_insert(compat_allocator_table(), (st_data_t)allocator, (st_data_t)compat); + RB_OBJ_WRITTEN(compat_allocator_tbl_wrapper, Qundef, newclass); + RB_OBJ_WRITTEN(compat_allocator_tbl_wrapper, Qundef, oldclass); } struct dump_arg { @@ -460,6 +462,31 @@ w_float(double d, struct dump_arg *arg) } } + +static VALUE +w_encivar(VALUE str, struct dump_arg *arg) +{ + VALUE encname = encoding_name(str, arg); + if (NIL_P(encname) || + is_ascii_string(str)) { + return Qnil; + } + w_byte(TYPE_IVAR, arg); + return encname; +} + +static void +w_encname(VALUE encname, struct dump_arg *arg) +{ + if (!NIL_P(encname)) { + struct dump_call_arg c_arg; + c_arg.limit = 1; + c_arg.arg = arg; + w_long(1L, arg); + w_encoding(encname, &c_arg); + } +} + static void w_symbol(VALUE sym, struct dump_arg *arg) { @@ -476,24 +503,11 @@ w_symbol(VALUE sym, struct dump_arg *arg) if (!sym) { rb_raise(rb_eTypeError, "can't dump anonymous ID %"PRIdVALUE, sym); } - encname = encoding_name(sym, arg); - if (NIL_P(encname) || - is_ascii_string(sym)) { - encname = Qnil; - } - else { - w_byte(TYPE_IVAR, arg); - } + encname = w_encivar(sym, arg); w_byte(TYPE_SYMBOL, arg); w_bytes(RSTRING_PTR(sym), RSTRING_LEN(sym), arg); st_add_direct(arg->symbols, orig_sym, arg->symbols->num_entries); - if (!NIL_P(encname)) { - struct dump_call_arg c_arg; - c_arg.limit = 1; - c_arg.arg = arg; - w_long(1L, arg); - w_encoding(encname, &c_arg); - } + w_encname(encname, arg); } } @@ -953,19 +967,23 @@ w_object(VALUE obj, struct dump_arg *arg, int limit) if (FL_TEST(obj, FL_SINGLETON)) { rb_raise(rb_eTypeError, "singleton class can't be dumped"); } - w_byte(TYPE_CLASS, arg); { VALUE path = class2path(obj); + VALUE encname = w_encivar(path, arg); + w_byte(TYPE_CLASS, arg); w_bytes(RSTRING_PTR(path), RSTRING_LEN(path), arg); + w_encname(encname, arg); RB_GC_GUARD(path); } break; case T_MODULE: - w_byte(TYPE_MODULE, arg); { VALUE path = class2path(obj); + VALUE encname = w_encivar(path, arg); + w_byte(TYPE_MODULE, arg); w_bytes(RSTRING_PTR(path), RSTRING_LEN(path), arg); + w_encname(encname, arg); RB_GC_GUARD(path); } break; @@ -1707,6 +1725,34 @@ r_copy_ivar(VALUE v, VALUE data) "can't override instance variable of "type" '%"PRIsVALUE"'", \ (str)) +static int +r_ivar_encoding(VALUE obj, struct load_arg *arg, VALUE sym, VALUE val) +{ + int idx = sym2encidx(sym, val); + if (idx >= 0) { + if (rb_enc_capable(obj)) { + rb_enc_associate_index(obj, idx); + } + else { + rb_raise(rb_eArgError, "%"PRIsVALUE" is not enc_capable", obj); + } + return TRUE; + } + return FALSE; +} + +static long +r_encname(VALUE obj, struct load_arg *arg) +{ + long len = r_long(arg); + if (len > 0) { + VALUE sym = r_symbol(arg); + VALUE val = r_object(arg); + len -= r_ivar_encoding(obj, arg, sym, val); + } + return len; +} + static void r_ivar(VALUE obj, int *has_encoding, struct load_arg *arg) { @@ -1723,14 +1769,7 @@ r_ivar(VALUE obj, int *has_encoding, struct load_arg *arg) do { VALUE sym = r_symbol(arg); VALUE val = r_object(arg); - int idx = sym2encidx(sym, val); - if (idx >= 0) { - if (rb_enc_capable(obj)) { - rb_enc_associate_index(obj, idx); - } - else { - rb_raise(rb_eArgError, "%"PRIsVALUE" is not enc_capable", obj); - } + if (r_ivar_encoding(obj, arg, sym, val)) { if (has_encoding) *has_encoding = TRUE; } else if (symname_equal_lit(sym, name_s_ruby2_keywords_flag)) { @@ -2254,6 +2293,7 @@ r_object_for(struct load_arg *arg, bool partial, int *ivp, VALUE extmod, int typ { VALUE str = r_bytes(arg); + if (ivp && *ivp > 0) *ivp = r_encname(str, arg) > 0; v = path2class(str); prohibit_ivar("class", str); v = r_entry(v, arg); @@ -2265,6 +2305,7 @@ r_object_for(struct load_arg *arg, bool partial, int *ivp, VALUE extmod, int typ { VALUE str = r_bytes(arg); + if (ivp && *ivp > 0) *ivp = r_encname(str, arg) > 0; v = path2module(str); prohibit_ivar("module", str); v = r_entry(v, arg); diff --git a/memory_view.c b/memory_view.c index 519aad2ca1..7bcb39972f 100644 --- a/memory_view.c +++ b/memory_view.c @@ -51,11 +51,11 @@ exported_object_registry_mark(void *ptr) static void exported_object_registry_free(void *ptr) { - RB_VM_LOCK_ENTER(); - st_clear(exported_object_table); - st_free_table(exported_object_table); - exported_object_table = NULL; - RB_VM_LOCK_LEAVE(); + RB_VM_LOCKING() { + st_clear(exported_object_table); + st_free_table(exported_object_table); + exported_object_table = NULL; + } } const rb_data_type_t rb_memory_view_exported_object_registry_data_type = { @@ -99,18 +99,18 @@ exported_object_dec_ref(st_data_t *key, st_data_t *val, st_data_t arg, int exist static void register_exported_object(VALUE obj) { - RB_VM_LOCK_ENTER(); - st_update(exported_object_table, (st_data_t)obj, exported_object_add_ref, 0); - RB_VM_LOCK_LEAVE(); + RB_VM_LOCKING() { + st_update(exported_object_table, (st_data_t)obj, exported_object_add_ref, 0); + } } static void unregister_exported_object(VALUE obj) { - RB_VM_LOCK_ENTER(); - if (exported_object_table) - st_update(exported_object_table, (st_data_t)obj, exported_object_dec_ref, 0); - RB_VM_LOCK_LEAVE(); + RB_VM_LOCKING() { + if (exported_object_table) + st_update(exported_object_table, (st_data_t)obj, exported_object_dec_ref, 0); + } } // MemoryView diff --git a/method.h b/method.h index b2ac07fc83..6abf2495b0 100644 --- a/method.h +++ b/method.h @@ -254,6 +254,9 @@ void rb_scope_visibility_set(rb_method_visibility_t); VALUE rb_unnamed_parameters(int arity); +void rb_vm_insert_cc_refinement(const struct rb_callcache *cc); +void rb_vm_delete_cc_refinement(const struct rb_callcache *cc); + void rb_clear_method_cache(VALUE klass_or_module, ID mid); void rb_clear_all_refinement_method_cache(void); void rb_invalidate_method_caches(struct rb_id_table *cm_tbl, struct rb_id_table *cc_tbl); diff --git a/misc/expand_tabs.rb b/misc/expand_tabs.rb index a94eea5046..d26568eefc 100755 --- a/misc/expand_tabs.rb +++ b/misc/expand_tabs.rb @@ -59,53 +59,31 @@ class Git end DEFAULT_GEM_LIBS = %w[ - abbrev - base64 - benchmark bundler - cmath - csv - debug delegate did_you_mean - drb english erb + error_highlight fileutils find forwardable - getoptlong ipaddr - irb - logger - mutex_m net-http net-protocol - observer open3 open-uri optparse ostruct pp prettyprint - prime - pstore - rdoc - readline - reline + prism resolv - resolv-replace - rexml - rinda - rss rubygems - scanf securerandom - set shellwords singleton tempfile - thwait time timeout tmpdir @@ -117,27 +95,19 @@ DEFAULT_GEM_LIBS = %w[ ] DEFAULT_GEM_EXTS = %w[ - bigdecimal - cgi date digest etc fcntl - fiddle io-console io-nonblock io-wait json - nkf openssl pathname psych - racc - readline-ext stringio strscan - syslog - win32ole zlib ] diff --git a/misc/lldb_cruby.py b/misc/lldb_cruby.py old mode 100755 new mode 100644 index f263ca5732..b3d4fb509a --- a/misc/lldb_cruby.py +++ b/misc/lldb_cruby.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python #coding: utf-8 # # Usage: run `command script import -r misc/lldb_cruby.py` on LLDB diff --git a/misc/lldb_rb/commands/print_flags_command.py b/misc/lldb_rb/commands/print_flags_command.py index 2b056dd098..bc494ae01a 100644 --- a/misc/lldb_rb/commands/print_flags_command.py +++ b/misc/lldb_rb/commands/print_flags_command.py @@ -17,7 +17,7 @@ class PrintFlagsCommand(RbBaseCommand): flags = [ "RUBY_FL_WB_PROTECTED", "RUBY_FL_PROMOTED", "RUBY_FL_FINALIZE", - "RUBY_FL_SHAREABLE", "RUBY_FL_EXIVAR", "RUBY_FL_FREEZE", + "RUBY_FL_SHAREABLE", "RUBY_FL_FREEZE", "RUBY_FL_USER0", "RUBY_FL_USER1", "RUBY_FL_USER2", "RUBY_FL_USER3", "RUBY_FL_USER4", "RUBY_FL_USER5", "RUBY_FL_USER6", "RUBY_FL_USER7", "RUBY_FL_USER8", "RUBY_FL_USER9", "RUBY_FL_USER10", "RUBY_FL_USER11", "RUBY_FL_USER12", "RUBY_FL_USER13", "RUBY_FL_USER14", diff --git a/misc/tsan_suppressions.txt b/misc/tsan_suppressions.txt index 908de1c3d9..e46f133a9e 100644 --- a/misc/tsan_suppressions.txt +++ b/misc/tsan_suppressions.txt @@ -22,17 +22,8 @@ race_top:push_subclass_entry_to_list race:objspace_malloc_increase_body # Signals and ubf -race_top:rb_signal_buff_size race:unregister_ubf_list -# interrupt flag is set atomically, but read non-atomically -race_top:RUBY_VM_INTERRUPTED_ANY -race_top:unblock_function_set -race_top:threadptr_get_interrupts - -# system_working needs to be converted to atomic -race:system_working - # It's already crashing. We're doing our best signal:rb_vm_bugreport race:check_reserved_signal_ @@ -74,11 +65,20 @@ race_top:rb_ractor_set_current_ec_ # Possible deadlock between Ractor lock and UBF lock deadlock:ractor_sleep_interrupt +# TSan reports a lock-order-inversion between thread_sched_lock_ and this lock. +# It's unclear if that can cause a deadlock since the lock is on self +deadlock:ractor_lock_self + +# TSan reports a deadlock when reacquiring the this lock after a barrier, but +# we know the other threads have been stopped +deadlock:rb_ractor_sched_barrier_start + # RVALUE_AGE_SET manipulates flag bits on objects which may be accessed in Ractors race_top:RVALUE_AGE_SET # Inline caches race_top:vm_cc_call_set +race_top:vm_cc_class_check race_top:vm_search_cc race_top:vm_search_method_slowpath0 race_top:rb_vm_opt_getconstant_path @@ -95,6 +95,10 @@ race:gccct_method_search race:rb_ec_finalize race:rb_ec_cleanup +# TSan doesn't work well post-fork, this raises errors when creating the new +# timer thread +race:after_fork_ruby + # object_id races race:object_id @@ -114,6 +118,7 @@ race_top:method_definition_addref # Switching to setting up tracing. Likely other ractors should be stopped for this. race_top:encoded_iseq_trace_instrument race:rb_iseq_trace_set_all +race:rb_tracepoint_enable # We walk the machine stack looking for markable objects, a thread with the GVL # released could by mutating the stack with non-Ruby-objects diff --git a/namespace.c b/namespace.c index 3e9abe8df2..dd7d21c380 100644 --- a/namespace.c +++ b/namespace.c @@ -47,32 +47,20 @@ static bool tmp_dir_has_dirsep; # define DIRSEP "/" #endif -static int namespace_availability = 0; +bool ruby_namespace_enabled = false; // extern +bool ruby_namespace_init_done = false; // extern VALUE rb_resolve_feature_path(VALUE klass, VALUE fname); static VALUE rb_namespace_inspect(VALUE obj); - -int -rb_namespace_available(void) -{ - const char *env; - if (namespace_availability) { - return namespace_availability > 0 ? 1 : 0; - } - env = getenv("RUBY_NAMESPACE"); - if (env && strlen(env) > 0) { - if (strcmp(env, "1") == 0) { - namespace_availability = 1; - return 1; - } - } - namespace_availability = -1; - return 0; -} - static void namespace_push(rb_thread_t *th, VALUE namespace); static VALUE namespace_pop(VALUE th_value); +void +rb_namespace_init_done(void) +{ + ruby_namespace_init_done = true; +} + void rb_namespace_enable_builtin(void) { @@ -280,120 +268,15 @@ rb_definition_namespace(void) return ns; } -VALUE -rb_current_namespace_details(VALUE opt) -{ - const rb_callable_method_entry_t *cme; - VALUE str, part, nsobj; - char buf[2048]; - const char *path; - int calling = 1; - long i; - rb_execution_context_t *ec = GET_EC(); - rb_control_frame_t *cfp = ec->cfp; - rb_thread_t *th = rb_ec_thread_ptr(ec); - const rb_namespace_t *ns = rb_current_namespace(); - rb_vm_t *vm = GET_VM(); - VALUE require_stack = vm->require_stack; - - str = rb_namespace_inspect(ns ? ns->ns_object : Qfalse); - if (NIL_P(opt)) return str; - - rb_str_cat_cstr(str, "\n"); - - part = rb_namespace_inspect(th->ns ? th->ns->ns_object : Qfalse); - snprintf(buf, 2048, "main:%s, th->ns:%s, th->nss:%ld, rstack:%ld\n", - main_namespace ? "t" : "f", - RSTRING_PTR(part), - th->namespaces ? RARRAY_LEN(th->namespaces) : 0, - require_stack ? RARRAY_LEN(require_stack) : 0); - RB_GC_GUARD(part); - rb_str_cat_cstr(str, buf); - - if (th->namespaces && RARRAY_LEN(th->namespaces) > 0) { - for (i=0; inamespaces); i++) { - nsobj = RARRAY_AREF(th->namespaces, i); - part = rb_namespace_inspect(nsobj); - snprintf(buf, 2048, " th->nss[%ld] %s\n", i, RSTRING_PTR(part)); - RB_GC_GUARD(part); - rb_str_cat_cstr(str, buf); - } - } - - - rb_str_cat_cstr(str, "calls:\n"); - - while (calling && cfp) { - const rb_namespace_t *proc_ns; - VALUE bh; - if (VM_FRAME_NS_SWITCH_P(cfp)) { - bh = rb_vm_frame_block_handler(cfp); - if (bh && vm_block_handler_type(bh) == block_handler_type_proc) { - proc_ns = block_proc_namespace(VM_BH_TO_PROC(bh)); - if (NAMESPACE_USER_P(ns)) { - part = rb_namespace_inspect(proc_ns->ns_object); - snprintf(buf, 2048, " cfp->ns:%s", RSTRING_PTR(part)); - RB_GC_GUARD(part); - calling = 0; - break; - } - } - } - cme = rb_vm_frame_method_entry(cfp); - if (cme && cme->def) { - if (cme->def->type == VM_METHOD_TYPE_ISEQ) - path = RSTRING_PTR(pathobj_path(cme->def->body.iseq.iseqptr->body->location.pathobj)); - else - path = "(cfunc)"; - ns = cme->def->ns; - if (ns) { - part = rb_namespace_inspect(ns->ns_object); - if (!namespace_ignore_builtin_primitive_methods_p(ns, cme->def)) { - snprintf(buf, 2048, " cfp cme->def id:%s, ns:%s, exprim:t, path:%s\n", - rb_id2name(cme->def->original_id), - RSTRING_PTR(part), - path); - RB_GC_GUARD(part); - rb_str_cat_cstr(str, buf); - calling = 0; - break; - } - else { - snprintf(buf, 2048, " cfp cme->def id:%s, ns:%s, exprim:f, path:%s\n", - rb_id2name(cme->def->original_id), - RSTRING_PTR(part), - path); - RB_GC_GUARD(part); - rb_str_cat_cstr(str, buf); - } - } - else { - snprintf(buf, 2048, " cfp cme->def id:%s, ns:null, path:%s\n", - rb_id2name(cme->def->original_id), - path); - rb_str_cat_cstr(str, buf); - } - cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); - } - else { - calling = 0; - } - } - rb_str_cat_cstr(str, ".\n"); - return str; -} - static long namespace_id_counter = 0; static long namespace_generate_id(void) { long id; - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { id = ++namespace_id_counter; } - RB_VM_LOCK_LEAVE(); return id; } @@ -555,9 +438,6 @@ namespace_initialize(VALUE namespace) // If a code in the namespace adds a constant, the constant will be visible even from root/main. RCLASS_SET_PRIME_CLASSEXT_WRITABLE(namespace, true); - // fallback to ivptr for ivars from shapes to manipulate the constant table - rb_evict_ivars_to_hash(namespace); - // Get a clean constant table of Object even by writable one // because ns was just created, so it has not touched any constants yet. object_classext = RCLASS_EXT_WRITABLE_IN_NS(rb_cObject, ns); @@ -725,7 +605,7 @@ copy_ext_file_error(char *message, size_t size, int copy_retvalue, char *src_pat case 4: snprintf(message, size, "failed to write the extension path: %s", dst_path); default: - rb_bug("unkown return value of copy_ext_file: %d", copy_retvalue); + rb_bug("unknown return value of copy_ext_file: %d", copy_retvalue); } return message; } @@ -832,7 +712,7 @@ escaped_basename(char *path, char *fname, char *rvalue) leaf = path; // `leaf + 1` looks uncomfortable (when leaf == path), but fname must not be the top-dir itself while ((found = strstr(leaf + 1, fname)) != NULL) { - leaf = found; // find the last occurence for the path like /etc/my-crazy-lib-dir/etc.so + leaf = found; // find the last occurrence for the path like /etc/my-crazy-lib-dir/etc.so } strcpy(rvalue, leaf); for (pos = rvalue; *pos; pos++) { @@ -979,6 +859,23 @@ rb_namespace_require_relative(VALUE namespace, VALUE fname) return rb_ensure(rb_require_relative_entrypoint, fname, namespace_both_pop, (VALUE)&arg); } +static VALUE +rb_namespace_eval_string(VALUE str) +{ + return rb_eval_string(RSTRING_PTR(str)); +} + +static VALUE +rb_namespace_eval(VALUE namespace, VALUE str) +{ + rb_thread_t *th = GET_THREAD(); + + StringValue(str); + + namespace_push(th, namespace); + return rb_ensure(rb_namespace_eval_string, str, namespace_pop, (VALUE)th); +} + static int namespace_experimental_warned = 0; void @@ -1139,6 +1036,18 @@ namespace_define_loader_method(const char *name) rb_define_singleton_method(rb_mNamespaceLoader, name, rb_namespace_user_loading_func, -1); } +void +Init_enable_namespace(void) +{ + const char *env = getenv("RUBY_NAMESPACE"); + if (env && strlen(env) == 1 && env[0] == '1') { + ruby_namespace_enabled = true; + } + else { + ruby_namespace_init_done = true; + } +} + void Init_Namespace(void) { @@ -1163,13 +1072,13 @@ Init_Namespace(void) rb_define_singleton_method(rb_cNamespace, "enabled?", rb_namespace_s_getenabled, 0); rb_define_singleton_method(rb_cNamespace, "current", rb_namespace_current, 0); - rb_define_singleton_method(rb_cNamespace, "current_details", rb_current_namespace_details, 0); rb_define_singleton_method(rb_cNamespace, "is_builtin?", rb_namespace_s_is_builtin_p, 1); rb_define_method(rb_cNamespace, "load_path", rb_namespace_load_path, 0); rb_define_method(rb_cNamespace, "load", rb_namespace_load, -1); rb_define_method(rb_cNamespace, "require", rb_namespace_require, 1); rb_define_method(rb_cNamespace, "require_relative", rb_namespace_require_relative, 1); + rb_define_method(rb_cNamespace, "eval", rb_namespace_eval, 1); rb_define_method(rb_cNamespace, "inspect", rb_namespace_inspect, 0); diff --git a/nilclass.rb b/nilclass.rb index 5a2e19680d..acd5666c71 100644 --- a/nilclass.rb +++ b/nilclass.rb @@ -1,4 +1,30 @@ class NilClass + # + # call-seq: + # rationalize(eps = nil) -> (0/1) + # + # Returns zero as a Rational: + # + # nil.rationalize # => (0/1) + # + # Argument +eps+ is ignored. + # + def rationalize(eps = nil) + 0r + end + + # + # call-seq: + # to_c -> (0+0i) + # + # Returns zero as a Complex: + # + # nil.to_c # => (0+0i) + # + def to_c + 0i + end + # # call-seq: # nil.to_i -> 0 @@ -22,4 +48,16 @@ class NilClass def to_f return 0.0 end + + # + # call-seq: + # to_r -> (0/1) + # + # Returns zero as a Rational: + # + # nil.to_r # => (0/1) + # + def to_r + 0r + end end diff --git a/node_dump.c b/node_dump.c index 24711f3d97..ff5cc268ec 100644 --- a/node_dump.c +++ b/node_dump.c @@ -1027,8 +1027,10 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) ANN("format: [nd_head]::[nd_mid]"); ANN("example: M::C"); F_ID(nd_mid, RNODE_COLON2, "constant name"); - LAST_NODE; F_NODE(nd_head, RNODE_COLON2, "receiver"); + F_LOC(delimiter_loc, RNODE_COLON2); + LAST_NODE; + F_LOC(name_loc, RNODE_COLON2); return; case NODE_COLON3: @@ -1036,6 +1038,8 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) ANN("format: ::[nd_mid]"); ANN("example: ::Object"); F_ID(nd_mid, RNODE_COLON3, "constant name"); + F_LOC(delimiter_loc, RNODE_COLON3); + F_LOC(name_loc, RNODE_COLON3); return; case NODE_DOT2: diff --git a/object.c b/object.c index 85b96fe31a..0ab93b7a71 100644 --- a/object.c +++ b/object.c @@ -50,10 +50,6 @@ * The object has its instance variables embedded (the array of * instance variables directly follow the object, rather than being * on a separately allocated buffer). - * if !SHAPE_IN_BASIC_FLAGS - * 4-19: SHAPE_FLAG_MASK - * Shape ID for the object. - * endif */ /*! @@ -87,6 +83,7 @@ static VALUE rb_cFalseClass_to_s; #define id_init_dup idInitialize_dup #define id_const_missing idConst_missing #define id_to_f idTo_f +static ID id_instance_variables_to_inspect; #define CLASS_OR_MODULE_P(obj) \ (!SPECIAL_CONST_P(obj) && \ @@ -132,10 +129,9 @@ rb_class_allocate_instance(VALUE klass) T_OBJECT | ROBJECT_EMBED | (RGENGC_WB_PROTECTED_OBJECT ? FL_WB_PROTECTED : 0), size, 0); VALUE obj = (VALUE)o; - RUBY_ASSERT(rb_obj_shape(obj)->type == SHAPE_ROOT); + RUBY_ASSERT(RSHAPE_TYPE_P(RBASIC_SHAPE_ID(obj), SHAPE_ROOT)); - // Set the shape to the specific T_OBJECT shape. - ROBJECT_SET_SHAPE_ID(obj, rb_shape_root(rb_gc_heap_id_for_size(size))); + RBASIC_SET_SHAPE_ID(obj, rb_shape_root(rb_gc_heap_id_for_size(size))); #if RUBY_DEBUG RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); @@ -327,7 +323,6 @@ void rb_obj_copy_ivar(VALUE dest, VALUE obj) { RUBY_ASSERT(!RB_TYPE_P(obj, T_CLASS) && !RB_TYPE_P(obj, T_MODULE)); - RUBY_ASSERT(BUILTIN_TYPE(dest) == BUILTIN_TYPE(obj)); unsigned long src_num_ivs = rb_ivar_count(obj); @@ -335,69 +330,41 @@ rb_obj_copy_ivar(VALUE dest, VALUE obj) return; } - rb_shape_t *src_shape = rb_obj_shape(obj); + shape_id_t src_shape_id = RBASIC_SHAPE_ID(obj); - if (rb_shape_too_complex_p(src_shape)) { - // obj is TOO_COMPLEX so we can copy its iv_hash - st_table *table = st_copy(ROBJECT_FIELDS_HASH(obj)); - if (rb_shape_has_object_id(src_shape)) { - st_data_t id = (st_data_t)ruby_internal_object_id; - st_delete(table, &id, NULL); - } + if (rb_shape_too_complex_p(src_shape_id)) { + rb_shape_copy_complex_ivars(dest, obj, src_shape_id, ROBJECT_FIELDS_HASH(obj)); + return; + } + + shape_id_t dest_shape_id = src_shape_id; + shape_id_t initial_shape_id = RBASIC_SHAPE_ID(dest); + + RUBY_ASSERT(RSHAPE_TYPE_P(initial_shape_id, SHAPE_ROOT)); + + dest_shape_id = rb_shape_rebuild(initial_shape_id, src_shape_id); + if (UNLIKELY(rb_shape_too_complex_p(dest_shape_id))) { + st_table *table = rb_st_init_numtable_with_size(src_num_ivs); + rb_obj_copy_ivs_to_hash_table(obj, table); rb_obj_init_too_complex(dest, table); return; } - rb_shape_t *shape_to_set_on_dest = src_shape; - rb_shape_t *initial_shape = rb_obj_shape(dest); - - if (initial_shape->heap_index != src_shape->heap_index || !rb_shape_canonical_p(src_shape)) { - RUBY_ASSERT(initial_shape->type == SHAPE_T_OBJECT); - - shape_to_set_on_dest = rb_shape_rebuild_shape(initial_shape, src_shape); - if (UNLIKELY(rb_shape_too_complex_p(shape_to_set_on_dest))) { - st_table *table = rb_st_init_numtable_with_size(src_num_ivs); - rb_obj_copy_ivs_to_hash_table(obj, table); - rb_obj_init_too_complex(dest, table); - - return; - } - } - VALUE *src_buf = ROBJECT_FIELDS(obj); VALUE *dest_buf = ROBJECT_FIELDS(dest); - RUBY_ASSERT(src_num_ivs <= shape_to_set_on_dest->capacity); - if (initial_shape->capacity < shape_to_set_on_dest->capacity) { - rb_ensure_iv_list_size(dest, initial_shape->capacity, shape_to_set_on_dest->capacity); + attr_index_t initial_capa = RSHAPE_CAPACITY(initial_shape_id); + attr_index_t dest_capa = RSHAPE_CAPACITY(dest_shape_id); + + RUBY_ASSERT(src_num_ivs <= dest_capa); + if (initial_capa < dest_capa) { + rb_ensure_iv_list_size(dest, 0, dest_capa); dest_buf = ROBJECT_FIELDS(dest); } - if (src_shape->next_field_index == shape_to_set_on_dest->next_field_index) { - // Happy path, we can just memcpy the fields content - MEMCPY(dest_buf, src_buf, VALUE, src_num_ivs); - - // Fire write barriers - for (uint32_t i = 0; i < src_num_ivs; i++) { - RB_OBJ_WRITTEN(dest, Qundef, dest_buf[i]); - } - } - else { - rb_shape_t *dest_shape = shape_to_set_on_dest; - while (src_shape->parent_id != INVALID_SHAPE_ID) { - if (src_shape->type == SHAPE_IVAR) { - while (dest_shape->edge_name != src_shape->edge_name) { - dest_shape = RSHAPE(dest_shape->parent_id); - } - - RB_OBJ_WRITE(dest, &dest_buf[dest_shape->next_field_index - 1], src_buf[src_shape->next_field_index - 1]); - } - src_shape = RSHAPE(src_shape->parent_id); - } - } - - rb_shape_set_shape(dest, shape_to_set_on_dest); + rb_shape_copy_fields(dest, dest_buf, dest_shape_id, src_buf, src_shape_id); + rb_obj_set_shape_id(dest, dest_shape_id); } static void @@ -406,14 +373,25 @@ init_copy(VALUE dest, VALUE obj) if (OBJ_FROZEN(dest)) { rb_raise(rb_eTypeError, "[bug] frozen object (%s) allocated", rb_obj_classname(dest)); } - RBASIC(dest)->flags &= ~(T_MASK|FL_EXIVAR); + RBASIC(dest)->flags &= ~T_MASK; // Copies the shape id from obj to dest - RBASIC(dest)->flags |= RBASIC(obj)->flags & (T_MASK|FL_EXIVAR); - rb_gc_copy_attributes(dest, obj); - rb_copy_generic_ivar(dest, obj); - if (RB_TYPE_P(obj, T_OBJECT)) { - rb_obj_copy_ivar(dest, obj); + RBASIC(dest)->flags |= RBASIC(obj)->flags & T_MASK; + switch (BUILTIN_TYPE(obj)) { + case T_IMEMO: + rb_bug("Unreachable"); + break; + case T_CLASS: + case T_MODULE: + // noop: handled in class.c: rb_mod_init_copy + break; + case T_OBJECT: + rb_obj_copy_ivar(dest, obj); + break; + default: + rb_copy_generic_ivar(dest, obj); + break; } + rb_gc_copy_attributes(dest, obj); } static VALUE immutable_obj_clone(VALUE obj, VALUE kwfreeze); @@ -520,12 +498,7 @@ rb_obj_clone_setup(VALUE obj, VALUE clone, VALUE kwfreeze) if (RB_OBJ_FROZEN(obj)) { shape_id_t next_shape_id = rb_shape_transition_frozen(clone); - if (!rb_shape_obj_too_complex_p(clone) && rb_shape_id_too_complex_p(next_shape_id)) { - rb_evict_ivars_to_hash(clone); - } - else { - rb_shape_set_shape_id(clone, next_shape_id); - } + rb_obj_set_shape_id(clone, next_shape_id); } break; case Qtrue: { @@ -540,16 +513,7 @@ rb_obj_clone_setup(VALUE obj, VALUE clone, VALUE kwfreeze) argv[0] = obj; argv[1] = freeze_true_hash; rb_funcallv_kw(clone, id_init_clone, 2, argv, RB_PASS_KEYWORDS); - RBASIC(clone)->flags |= FL_FREEZE; - shape_id_t next_shape_id = rb_shape_transition_frozen(clone); - // If we're out of shapes, but we want to freeze, then we need to - // evacuate this clone to a hash - if (!rb_shape_obj_too_complex_p(clone) && rb_shape_id_too_complex_p(next_shape_id)) { - rb_evict_ivars_to_hash(clone); - } - else { - rb_shape_set_shape_id(clone, next_shape_id); - } + OBJ_FREEZE(clone); break; } case Qfalse: { @@ -768,11 +732,17 @@ rb_inspect(VALUE obj) static int inspect_i(ID id, VALUE value, st_data_t a) { - VALUE str = (VALUE)a; + VALUE *args = (VALUE *)a, str = args[0], ivars = args[1]; /* need not to show internal data */ if (CLASS_OF(value) == 0) return ST_CONTINUE; if (!rb_is_instance_id(id)) return ST_CONTINUE; + if (!NIL_P(ivars)) { + VALUE name = ID2SYM(id); + for (long i = 0; RARRAY_AREF(ivars, i) != name; ) { + if (++i >= RARRAY_LEN(ivars)) return ST_CONTINUE; + } + } if (RSTRING_PTR(str)[0] == '-') { /* first element */ RSTRING_PTR(str)[0] = '#'; rb_str_cat2(str, " "); @@ -787,13 +757,15 @@ inspect_i(ID id, VALUE value, st_data_t a) } static VALUE -inspect_obj(VALUE obj, VALUE str, int recur) +inspect_obj(VALUE obj, VALUE a, int recur) { + VALUE *args = (VALUE *)a, str = args[0]; + if (recur) { rb_str_cat2(str, " ..."); } else { - rb_ivar_foreach(obj, inspect_i, str); + rb_ivar_foreach(obj, inspect_i, a); } rb_str_cat2(str, ">"); RSTRING_PTR(str)[0] = '#'; @@ -826,17 +798,47 @@ inspect_obj(VALUE obj, VALUE str, int recur) * end * end * Bar.new.inspect #=> "#" + * + * If _obj_ responds to +instance_variables_to_inspect+, then only + * the instance variables listed in the returned array will be included + * in the inspect string. + * + * + * class DatabaseConfig + * def initialize(host, user, password) + * @host = host + * @user = user + * @password = password + * end + * + * private + * def instance_variables_to_inspect = [:@host, :@user] + * end + * + * conf = DatabaseConfig.new("localhost", "root", "hunter2") + * conf.inspect #=> # */ static VALUE rb_obj_inspect(VALUE obj) { - if (rb_ivar_count(obj) > 0) { - VALUE str; + VALUE ivars = rb_check_funcall(obj, id_instance_variables_to_inspect, 0, 0); + st_index_t n = 0; + if (UNDEF_P(ivars)) { + n = rb_ivar_count(obj); + ivars = Qnil; + } + else if (!NIL_P(ivars)) { + Check_Type(ivars, T_ARRAY); + n = RARRAY_LEN(ivars); + } + if (n > 0) { VALUE c = rb_class_name(CLASS_OF(obj)); - - str = rb_sprintf("-<%"PRIsVALUE":%p", c, (void*)obj); - return rb_exec_recursive(inspect_obj, obj, str); + VALUE args[2] = { + rb_sprintf("-<%"PRIsVALUE":%p", c, (void*)obj), + ivars + }; + return rb_exec_recursive(inspect_obj, obj, (VALUE)args); } else { return rb_any_to_s(obj); @@ -2093,7 +2095,7 @@ rb_class_initialize(int argc, VALUE *argv, VALUE klass) else { super = argv[0]; rb_check_inheritable(super); - if (super != rb_cBasicObject && !RCLASS_SUPER(super)) { + if (!RCLASS_INITIALIZED_P(super)) { rb_raise(rb_eTypeError, "can't inherit uninitialized class"); } } @@ -2150,7 +2152,7 @@ class_get_alloc_func(VALUE klass) { rb_alloc_func_t allocator; - if (RCLASS_SUPER(klass) == 0 && klass != rb_cBasicObject) { + if (!RCLASS_INITIALIZED_P(klass)) { rb_raise(rb_eTypeError, "can't instantiate uninitialized class"); } if (RCLASS_SINGLETON_P(klass)) { @@ -2257,23 +2259,22 @@ rb_class_superclass(VALUE klass) { RUBY_ASSERT(RB_TYPE_P(klass, T_CLASS)); - VALUE super = RCLASS_SUPER(klass); - VALUE *superclasses; - size_t superclasses_depth; + VALUE *superclasses = RCLASS_SUPERCLASSES(klass); + size_t superclasses_depth = RCLASS_SUPERCLASS_DEPTH(klass); - if (!super) { - if (klass == rb_cBasicObject) return Qnil; + if (klass == rb_cBasicObject) return Qnil; + + if (!superclasses) { + RUBY_ASSERT(!RCLASS_SUPER(klass)); rb_raise(rb_eTypeError, "uninitialized class"); } - superclasses_depth = RCLASS_SUPERCLASS_DEPTH(klass); if (!superclasses_depth) { return Qnil; } else { - superclasses = RCLASS_SUPERCLASSES(klass); - super = superclasses[superclasses_depth - 1]; - RUBY_ASSERT(RB_TYPE_P(klass, T_CLASS)); + VALUE super = superclasses[superclasses_depth - 1]; + RUBY_ASSERT(RB_TYPE_P(super, T_CLASS)); return super; } } @@ -4636,6 +4637,7 @@ void Init_Object(void) { id_dig = rb_intern_const("dig"); + id_instance_variables_to_inspect = rb_intern_const("instance_variables_to_inspect"); InitVM(Object); } diff --git a/parse.y b/parse.y index 58b054ed33..7b36caf78d 100644 --- a/parse.y +++ b/parse.y @@ -314,6 +314,7 @@ struct lex_context { unsigned int in_argdef: 1; unsigned int in_def: 1; unsigned int in_class: 1; + unsigned int has_trailing_semicolon: 1; BITFIELD(enum rb_parser_shareability, shareable_constant_value, 2); BITFIELD(enum rescue_context, in_rescue, 2); unsigned int cant_return: 1; @@ -1146,8 +1147,8 @@ static rb_node_undef_t *rb_node_undef_new(struct parser_params *p, NODE *nd_unde static rb_node_class_t *rb_node_class_new(struct parser_params *p, NODE *nd_cpath, NODE *nd_body, NODE *nd_super, const YYLTYPE *loc, const YYLTYPE *class_keyword_loc, const YYLTYPE *inheritance_operator_loc, const YYLTYPE *end_keyword_loc); static rb_node_module_t *rb_node_module_new(struct parser_params *p, NODE *nd_cpath, NODE *nd_body, const YYLTYPE *loc); static rb_node_sclass_t *rb_node_sclass_new(struct parser_params *p, NODE *nd_recv, NODE *nd_body, const YYLTYPE *loc); -static rb_node_colon2_t *rb_node_colon2_new(struct parser_params *p, NODE *nd_head, ID nd_mid, const YYLTYPE *loc); -static rb_node_colon3_t *rb_node_colon3_new(struct parser_params *p, ID nd_mid, const YYLTYPE *loc); +static rb_node_colon2_t *rb_node_colon2_new(struct parser_params *p, NODE *nd_head, ID nd_mid, const YYLTYPE *loc, const YYLTYPE *delimiter_loc, const YYLTYPE *name_loc); +static rb_node_colon3_t *rb_node_colon3_new(struct parser_params *p, ID nd_mid, const YYLTYPE *loc, const YYLTYPE *delimiter_loc, const YYLTYPE *name_loc); static rb_node_dot2_t *rb_node_dot2_new(struct parser_params *p, NODE *nd_beg, NODE *nd_end, const YYLTYPE *loc, const YYLTYPE *operator_loc); static rb_node_dot3_t *rb_node_dot3_new(struct parser_params *p, NODE *nd_beg, NODE *nd_end, const YYLTYPE *loc, const YYLTYPE *operator_loc); static rb_node_self_t *rb_node_self_new(struct parser_params *p, const YYLTYPE *loc); @@ -1254,8 +1255,8 @@ static rb_node_error_t *rb_node_error_new(struct parser_params *p, const YYLTYPE #define NEW_CLASS(n,b,s,loc,ck_loc,io_loc,ek_loc) (NODE *)rb_node_class_new(p,n,b,s,loc,ck_loc,io_loc,ek_loc) #define NEW_MODULE(n,b,loc) (NODE *)rb_node_module_new(p,n,b,loc) #define NEW_SCLASS(r,b,loc) (NODE *)rb_node_sclass_new(p,r,b,loc) -#define NEW_COLON2(c,i,loc) (NODE *)rb_node_colon2_new(p,c,i,loc) -#define NEW_COLON3(i,loc) (NODE *)rb_node_colon3_new(p,i,loc) +#define NEW_COLON2(c,i,loc,d_loc,n_loc) (NODE *)rb_node_colon2_new(p,c,i,loc,d_loc,n_loc) +#define NEW_COLON3(i,loc,d_loc,n_loc) (NODE *)rb_node_colon3_new(p,i,loc,d_loc,n_loc) #define NEW_DOT2(b,e,loc,op_loc) (NODE *)rb_node_dot2_new(p,b,e,loc,op_loc) #define NEW_DOT3(b,e,loc,op_loc) (NODE *)rb_node_dot3_new(p,b,e,loc,op_loc) #define NEW_SELF(loc) (NODE *)rb_node_self_new(p,loc) @@ -3067,13 +3068,13 @@ rb_parser_ary_free(rb_parser_t *p, rb_parser_ary_t *ary) | primary_value tCOLON2 tCONSTANT tOP_ASGN lex_ctxt rhs { YYLTYPE loc = code_loc_gen(&@primary_value, &@tCONSTANT); - $$ = new_const_op_assign(p, NEW_COLON2($primary_value, $tCONSTANT, &loc), $tOP_ASGN, $rhs, $lex_ctxt, &@$); + $$ = new_const_op_assign(p, NEW_COLON2($primary_value, $tCONSTANT, &loc, &@tCOLON2, &@tCONSTANT), $tOP_ASGN, $rhs, $lex_ctxt, &@$); /*% ripper: opassign!(const_path_field!($:1, $:3), $:4, $:6) %*/ } | tCOLON3 tCONSTANT tOP_ASGN lex_ctxt rhs { YYLTYPE loc = code_loc_gen(&@tCOLON3, &@tCONSTANT); - $$ = new_const_op_assign(p, NEW_COLON3($tCONSTANT, &loc), $tOP_ASGN, $rhs, $lex_ctxt, &@$); + $$ = new_const_op_assign(p, NEW_COLON3($tCONSTANT, &loc, &@tCOLON3, &@tCONSTANT), $tOP_ASGN, $rhs, $lex_ctxt, &@$); /*% ripper: opassign!(top_const_field!($:2), $:3, $:5) %*/ } | backref tOP_ASGN lex_ctxt rhs @@ -3756,12 +3757,12 @@ mlhs_node : user_or_keyword_variable | primary_value tCOLON2 tCONSTANT { /*% ripper: const_path_field!($:1, $:3) %*/ - $$ = const_decl(p, NEW_COLON2($1, $3, &@$), &@$); + $$ = const_decl(p, NEW_COLON2($1, $3, &@$, &@2, &@3), &@$); } | tCOLON3 tCONSTANT { /*% ripper: top_const_field!($:2) %*/ - $$ = const_decl(p, NEW_COLON3($2, &@$), &@$); + $$ = const_decl(p, NEW_COLON3($2, &@$, &@1, &@2), &@$); } | backref { @@ -3794,12 +3795,12 @@ lhs : user_or_keyword_variable | primary_value tCOLON2 tCONSTANT { /*% ripper: const_path_field!($:1, $:3) %*/ - $$ = const_decl(p, NEW_COLON2($1, $3, &@$), &@$); + $$ = const_decl(p, NEW_COLON2($1, $3, &@$, &@2, &@3), &@$); } | tCOLON3 tCONSTANT { /*% ripper: top_const_field!($:2) %*/ - $$ = const_decl(p, NEW_COLON3($2, &@$), &@$); + $$ = const_decl(p, NEW_COLON3($2, &@$, &@1, &@2), &@$); } | backref { @@ -3822,17 +3823,17 @@ cname : tIDENTIFIER cpath : tCOLON3 cname { - $$ = NEW_COLON3($2, &@$); + $$ = NEW_COLON3($2, &@$, &@1, &@2); /*% ripper: top_const_ref!($:2) %*/ } | cname { - $$ = NEW_COLON2(0, $1, &@$); + $$ = NEW_COLON2(0, $1, &@$, &NULL_LOC, &@1); /*% ripper: const_ref!($:1) %*/ } | primary_value tCOLON2 cname { - $$ = NEW_COLON2($1, $3, &@$); + $$ = NEW_COLON2($1, $3, &@$, &@2, &@3); /*% ripper: const_path_ref!($:1, $:3) %*/ } ; @@ -4041,6 +4042,7 @@ arg : asgn(arg_rhs) { p->ctxt.in_defined = $3.in_defined; $$ = new_defined(p, $4, &@$); + p->ctxt.has_trailing_semicolon = $3.has_trailing_semicolon; /*% ripper: defined!($:4) %*/ } | def_endless_method(endless_arg) @@ -4385,12 +4387,12 @@ primary : inline_primary } | primary_value tCOLON2 tCONSTANT { - $$ = NEW_COLON2($1, $3, &@$); + $$ = NEW_COLON2($1, $3, &@$, &@2, &@3); /*% ripper: const_path_ref!($:1, $:3) %*/ } | tCOLON3 tCONSTANT { - $$ = NEW_COLON3($2, &@$); + $$ = NEW_COLON3($2, &@$, &@1, &@2); /*% ripper: top_const_ref!($:2) %*/ } | tLBRACK aref_args ']' @@ -4428,6 +4430,7 @@ primary : inline_primary { p->ctxt.in_defined = $4.in_defined; $$ = new_defined(p, $5, &@$); + p->ctxt.has_trailing_semicolon = $4.has_trailing_semicolon; /*% ripper: defined!($:5) %*/ } | keyword_not '(' expr rparen @@ -5810,12 +5813,12 @@ p_expr_ref : '^' tLPAREN expr_value rparen p_const : tCOLON3 cname { - $$ = NEW_COLON3($2, &@$); + $$ = NEW_COLON3($2, &@$, &@1, &@2); /*% ripper: top_const_ref!($:2) %*/ } | p_const tCOLON2 cname { - $$ = NEW_COLON2($1, $3, &@$); + $$ = NEW_COLON2($1, $3, &@$, &@2, &@3); /*% ripper: const_path_ref!($:1, $:3) %*/ } | tCONSTANT @@ -6706,7 +6709,14 @@ trailer : '\n'? | ',' ; -term : ';' {yyerrok;token_flush(p);} +term : ';' + { + yyerrok; + token_flush(p); + if (p->ctxt.in_defined) { + p->ctxt.has_trailing_semicolon = 1; + } + } | '\n' { @$.end_pos = @$.beg_pos; @@ -11553,20 +11563,24 @@ rb_node_until_new(struct parser_params *p, NODE *nd_cond, NODE *nd_body, long nd } static rb_node_colon2_t * -rb_node_colon2_new(struct parser_params *p, NODE *nd_head, ID nd_mid, const YYLTYPE *loc) +rb_node_colon2_new(struct parser_params *p, NODE *nd_head, ID nd_mid, const YYLTYPE *loc, const YYLTYPE *delimiter_loc, const YYLTYPE *name_loc) { rb_node_colon2_t *n = NODE_NEWNODE(NODE_COLON2, rb_node_colon2_t, loc); n->nd_head = nd_head; n->nd_mid = nd_mid; + n->delimiter_loc = *delimiter_loc; + n->name_loc = *name_loc; return n; } static rb_node_colon3_t * -rb_node_colon3_new(struct parser_params *p, ID nd_mid, const YYLTYPE *loc) +rb_node_colon3_new(struct parser_params *p, ID nd_mid, const YYLTYPE *loc, const YYLTYPE *delimiter_loc, const YYLTYPE *name_loc) { rb_node_colon3_t *n = NODE_NEWNODE(NODE_COLON3, rb_node_colon3_t, loc); n->nd_mid = nd_mid; + n->delimiter_loc = *delimiter_loc; + n->name_loc = *name_loc; return n; } @@ -12860,10 +12874,10 @@ numparam_nested_p(struct parser_params *p) NODE *inner = local->numparam.inner; if (outer || inner) { NODE *used = outer ? outer : inner; - compile_error(p, "numbered parameter is already used in\n" - "%s:%d: %s block here", - p->ruby_sourcefile, nd_line(used), - outer ? "outer" : "inner"); + compile_error(p, "numbered parameter is already used in %s block\n" + "%s:%d: numbered parameter is already used here", + outer ? "outer" : "inner", + p->ruby_sourcefile, nd_line(used)); parser_show_error_line(p, &used->nd_loc); return 1; } @@ -12875,8 +12889,8 @@ numparam_used_p(struct parser_params *p) { NODE *numparam = p->lvtbl->numparam.current; if (numparam) { - compile_error(p, "numbered parameter is already used in\n" - "%s:%d: current block here", + compile_error(p, "'it' is not allowed when a numbered parameter is already used\n" + "%s:%d: numbered parameter is already used here", p->ruby_sourcefile, nd_line(numparam)); parser_show_error_line(p, &numparam->nd_loc); return 1; @@ -12889,8 +12903,8 @@ it_used_p(struct parser_params *p) { NODE *it = p->lvtbl->it; if (it) { - compile_error(p, "'it' is already used in\n" - "%s:%d: current block here", + compile_error(p, "numbered parameters are not allowed when 'it' is already used\n" + "%s:%d: 'it' is already used here", p->ruby_sourcefile, nd_line(it)); parser_show_error_line(p, &it->nd_loc); return 1; @@ -13009,6 +13023,9 @@ kwd_append(rb_node_kw_arg_t *kwlist, rb_node_kw_arg_t *kw) static NODE * new_defined(struct parser_params *p, NODE *expr, const YYLTYPE *loc) { + int had_trailing_semicolon = p->ctxt.has_trailing_semicolon; + p->ctxt.has_trailing_semicolon = 0; + NODE *n = expr; while (n) { if (nd_type_p(n, NODE_BEGIN)) { @@ -13021,6 +13038,12 @@ new_defined(struct parser_params *p, NODE *expr, const YYLTYPE *loc) break; } } + + if (had_trailing_semicolon && !nd_type_p(expr, NODE_BLOCK)) { + NODE *block = NEW_BLOCK(expr, loc); + return NEW_DEFINED(block, loc); + } + return NEW_DEFINED(n, loc); } diff --git a/ext/pathname/pathname.c b/pathname.c similarity index 99% rename from ext/pathname/pathname.c rename to pathname.c index cdecb3f897..64bed3d598 100644 --- a/ext/pathname/pathname.c +++ b/pathname.c @@ -1311,6 +1311,10 @@ path_f_pathname(VALUE self, VALUE str) return rb_class_new_instance(1, &str, rb_cPathname); } +#include "pathname_builtin.rbinc" + +static void init_ids(void); + /* * * Pathname represents the name of a file or directory on the filesystem, @@ -1501,8 +1505,13 @@ Init_pathname(void) rb_ext_ractor_safe(true); #endif + init_ids(); InitVM(pathname); +} +void +InitVM_pathname(void) +{ rb_cPathname = rb_define_class("Pathname", rb_cObject); rb_define_method(rb_cPathname, "initialize", path_initialize, 1); rb_define_method(rb_cPathname, "freeze", path_freeze, 0); @@ -1589,10 +1598,12 @@ Init_pathname(void) rb_define_method(rb_cPathname, "delete", path_unlink, 0); rb_undef_method(rb_cPathname, "=~"); rb_define_global_function("Pathname", path_f_pathname, 1); + + rb_provide("pathname.so"); } void -InitVM_pathname(void) +init_ids(void) { #undef rb_intern id_at_path = rb_intern("@path"); diff --git a/ext/pathname/lib/pathname.rb b/pathname_builtin.rb similarity index 90% rename from ext/pathname/lib/pathname.rb rename to pathname_builtin.rb index 35fd778561..17ec155704 100644 --- a/ext/pathname/lib/pathname.rb +++ b/pathname_builtin.rb @@ -10,8 +10,6 @@ # For documentation, see class Pathname. # -require 'pathname.so' - class Pathname VERSION = "0.4.0" @@ -46,6 +44,34 @@ class Pathname # :startdoc: + # Creates a full path, including any intermediate directories that don't yet + # exist. + # + # See FileUtils.mkpath and FileUtils.mkdir_p + def mkpath(mode: nil) + path = @path == '/' ? @path : @path.chomp('/') + + stack = [] + until File.directory?(path) || File.dirname(path) == path + stack.push path + path = File.dirname(path) + end + + stack.reverse_each do |dir| + dir = dir == '/' ? dir : dir.chomp('/') + if mode + Dir.mkdir dir, mode + File.chmod mode, dir + else + Dir.mkdir dir + end + rescue SystemCallError + raise unless File.directory?(dir) + end + + self + end + # chop_basename(path) -> [pre-basename, basename] or nil def chop_basename(path) # :nodoc: base = File.basename(path) @@ -551,71 +577,3 @@ class Pathname end end end - - -class Pathname # * Find * - # - # Iterates over the directory tree in a depth first manner, yielding a - # Pathname for each file under "this" directory. - # - # Returns an Enumerator if no block is given. - # - # Since it is implemented by the standard library module Find, Find.prune can - # be used to control the traversal. - # - # If +self+ is +.+, yielded pathnames begin with a filename in the - # current directory, not +./+. - # - # See Find.find - # - def find(ignore_error: true) # :yield: pathname - return to_enum(__method__, ignore_error: ignore_error) unless block_given? - require 'find' - if @path == '.' - Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f.delete_prefix('./')) } - else - Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f) } - end - end -end - - -class Pathname # * FileUtils * - # Creates a full path, including any intermediate directories that don't yet - # exist. - # - # See FileUtils.mkpath and FileUtils.mkdir_p - def mkpath(mode: nil) - require 'fileutils' - FileUtils.mkpath(@path, mode: mode) - self - end - - # Recursively deletes a directory, including all directories beneath it. - # - # See FileUtils.rm_rf - def rmtree(noop: nil, verbose: nil, secure: nil) - # The name "rmtree" is borrowed from File::Path of Perl. - # File::Path provides "mkpath" and "rmtree". - require 'fileutils' - FileUtils.rm_rf(@path, noop: noop, verbose: verbose, secure: secure) - self - end -end - -class Pathname # * tmpdir * - # Creates a tmp directory and wraps the returned path in a Pathname object. - # - # See Dir.mktmpdir - def self.mktmpdir - require 'tmpdir' unless defined?(Dir.mktmpdir) - if block_given? - Dir.mktmpdir do |dir| - dir = self.new(dir) - yield dir - end - else - self.new(Dir.mktmpdir) - end - end -end diff --git a/prelude.rb b/prelude.rb index a381db8cce..f49cada637 100644 --- a/prelude.rb +++ b/prelude.rb @@ -26,11 +26,16 @@ module Kernel private :pp end -autoload :SortedSet, 'set/sorted_set' - module Enumerable # Makes a set from the enumerable object with given arguments. - def to_set(klass = Set, *args, &block) + # Passing arguments to this method is deprecated. + def to_set(*args, &block) + klass = if args.empty? + Set + else + warn "passing arguments to Enumerable#to_set is deprecated", uplevel: 1 + args.shift + end klass.new(self, *args, &block) end end diff --git a/prism/config.yml b/prism/config.yml index 3d5eee190f..257bd389ed 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -101,6 +101,8 @@ errors: - EXPECT_FOR_DELIMITER - EXPECT_IDENT_REQ_PARAMETER - EXPECT_IN_DELIMITER + - EXPECT_LPAREN_AFTER_NOT_LPAREN + - EXPECT_LPAREN_AFTER_NOT_OTHER - EXPECT_LPAREN_REQ_PARAMETER - EXPECT_MESSAGE - EXPECT_RBRACKET @@ -1828,6 +1830,11 @@ nodes: type: constant[] - name: class_keyword_loc type: location + comment: | + Represents the location of the `class` keyword. + + class Foo end + ^^^^^ - name: constant_path type: node kind: @@ -1836,18 +1843,43 @@ nodes: - on error: CallNode # class 0.X end - name: inheritance_operator_loc type: location? + comment: | + Represents the location of the `<` operator. + + class Foo < Bar + ^ - name: superclass type: node? kind: non-void expression + comment: | + Represents the superclass of the class. + + class Foo < Bar + ^^^ - name: body type: node? kind: - StatementsNode - BeginNode + comment: | + Represents the body of the class. + + class Foo + foo + ^^^ - name: end_keyword_loc type: location + comment: | + Represents the location of the `end` keyword. + + class Foo end + ^^^ - name: name type: constant + comment: | + The name of the class. + + class Foo end # name `:Foo` comment: | Represents a class declaration involving the `class` keyword. diff --git a/prism/prism.c b/prism/prism.c index cc634b59e3..85647020d8 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -13142,14 +13142,6 @@ match8(const pm_parser_t *parser, pm_token_type_t type1, pm_token_type_t type2, return match1(parser, type1) || match1(parser, type2) || match1(parser, type3) || match1(parser, type4) || match1(parser, type5) || match1(parser, type6) || match1(parser, type7) || match1(parser, type8); } -/** - * Returns true if the current token is any of the nine given types. - */ -static inline bool -match9(const pm_parser_t *parser, pm_token_type_t type1, pm_token_type_t type2, pm_token_type_t type3, pm_token_type_t type4, pm_token_type_t type5, pm_token_type_t type6, pm_token_type_t type7, pm_token_type_t type8, pm_token_type_t type9) { - return match1(parser, type1) || match1(parser, type2) || match1(parser, type3) || match1(parser, type4) || match1(parser, type5) || match1(parser, type6) || match1(parser, type7) || match1(parser, type8) || match1(parser, type9); -} - /** * If the current token is of the specified type, lex forward by one token and * return true. Otherwise, return false. For example: @@ -17412,6 +17404,14 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm // If we found a label, we need to immediately return to the caller. if (pm_symbol_node_label_p(node)) return node; + // Call nodes (arithmetic operations) are not allowed in patterns + if (PM_NODE_TYPE(node) == PM_CALL_NODE) { + pm_parser_err_node(parser, node, diag_id); + pm_missing_node_t *missing_node = pm_missing_node_create(parser, node->location.start, node->location.end); + pm_node_destroy(parser, node); + return (pm_node_t *) missing_node; + } + // Now that we have a primitive, we need to check if it's part of a range. if (accept2(parser, PM_TOKEN_DOT_DOT, PM_TOKEN_DOT_DOT_DOT)) { pm_token_t operator = parser->previous; @@ -17694,7 +17694,7 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag // Gather up all of the patterns into the list. while (accept1(parser, PM_TOKEN_COMMA)) { // Break early here in case we have a trailing comma. - if (match9(parser, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_SEMICOLON, PM_TOKEN_NEWLINE, PM_TOKEN_EOF,PM_TOKEN_KEYWORD_AND, PM_TOKEN_KEYWORD_OR)) { + if (match7(parser, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_SEMICOLON, PM_TOKEN_KEYWORD_AND, PM_TOKEN_KEYWORD_OR)) { node = (pm_node_t *) pm_implicit_rest_node_create(parser, &parser->previous); pm_node_list_append(&nodes, node); trailing_rest = true; @@ -19775,6 +19775,20 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_arguments_t arguments = { 0 }; pm_node_t *receiver = NULL; + // If we do not accept a command call, then we also do not accept a + // not without parentheses. In this case we need to reject this + // syntax. + if (!accepts_command_call && !match1(parser, PM_TOKEN_PARENTHESIS_LEFT)) { + if (match1(parser, PM_TOKEN_PARENTHESIS_LEFT_PARENTHESES)) { + pm_parser_err(parser, parser->previous.end, parser->previous.end + 1, PM_ERR_EXPECT_LPAREN_AFTER_NOT_LPAREN); + } else { + accept1(parser, PM_TOKEN_NEWLINE); + pm_parser_err_current(parser, PM_ERR_EXPECT_LPAREN_AFTER_NOT_OTHER); + } + + return (pm_node_t *) pm_missing_node_create(parser, parser->current.start, parser->current.end); + } + accept1(parser, PM_TOKEN_NEWLINE); if (accept1(parser, PM_TOKEN_PARENTHESIS_LEFT)) { @@ -22676,7 +22690,7 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm } search_shebang = false; - } else if (options->main_script && !parser->parsing_eval) { + } else if (options != NULL && options->main_script && !parser->parsing_eval) { search_shebang = true; } } diff --git a/prism/templates/include/prism/ast.h.erb b/prism/templates/include/prism/ast.h.erb index 751c0b43c2..087eb81890 100644 --- a/prism/templates/include/prism/ast.h.erb +++ b/prism/templates/include/prism/ast.h.erb @@ -2,6 +2,8 @@ * @file ast.h * * The abstract syntax tree. + * + * -- */ #ifndef PRISM_AST_H #define PRISM_AST_H diff --git a/prism/templates/lib/prism/compiler.rb.erb b/prism/templates/lib/prism/compiler.rb.erb index 45ed88d8de..9102025c20 100644 --- a/prism/templates/lib/prism/compiler.rb.erb +++ b/prism/templates/lib/prism/compiler.rb.erb @@ -35,7 +35,9 @@ module Prism <%- nodes.each_with_index do |node, index| -%> <%= "\n" if index != 0 -%> # Compile a <%= node.name %> node - alias visit_<%= node.human %> visit_child_nodes + def visit_<%= node.human %>(node) + node.compact_child_nodes.map { |node| node.accept(self) } + end <%- end -%> end end diff --git a/prism/templates/lib/prism/visitor.rb.erb b/prism/templates/lib/prism/visitor.rb.erb index 4b30a1815b..b1a03c3f1a 100644 --- a/prism/templates/lib/prism/visitor.rb.erb +++ b/prism/templates/lib/prism/visitor.rb.erb @@ -34,7 +34,7 @@ module Prism # # class FooCalls < Prism::Visitor # def visit_call_node(node) - # if node.name == "foo" + # if node.name == :foo # # Do something with the node # end # @@ -47,7 +47,9 @@ module Prism <%- nodes.each_with_index do |node, index| -%> <%= "\n" if index != 0 -%> # Visit a <%= node.name %> node - alias visit_<%= node.human %> visit_child_nodes + def visit_<%= node.human %>(node) + node.compact_child_nodes.each { |node| node.accept(self) } + end <%- end -%> end end diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index ce98dc5acd..9a30a57e3b 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -184,6 +184,8 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_EXPECT_FOR_DELIMITER] = { "unexpected %s; expected a 'do', newline, or ';' after the 'for' loop collection", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_IDENT_REQ_PARAMETER] = { "expected an identifier for the required parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_IN_DELIMITER] = { "expected a delimiter after the patterns of an `in` clause", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_LPAREN_AFTER_NOT_LPAREN] = { "expected a `(` immediately after `not`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_LPAREN_AFTER_NOT_OTHER] = { "expected a `(` after `not`", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_LPAREN_REQ_PARAMETER] = { "expected a `(` to start a required parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_MESSAGE] = { "unexpected %s; expecting a message to send to the receiver", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_RBRACKET] = { "expected a matching `]`", PM_ERROR_LEVEL_SYNTAX }, diff --git a/prism/templates/template.rb b/prism/templates/template.rb index 30cb60cabd..6c3efd7e6c 100755 --- a/prism/templates/template.rb +++ b/prism/templates/template.rb @@ -551,11 +551,14 @@ module Prism when ".rb" <<~HEADING # frozen_string_literal: true + # :markup: markdown =begin + -- This file is generated by the templates/template.rb script and should not be modified manually. See #{filepath} if you are looking to modify the template + ++ =end HEADING @@ -579,10 +582,12 @@ module Prism HEADING else <<~HEADING + /* :markup: markdown */ + /*----------------------------------------------------------------------------*/ /* This file is generated by the templates/template.rb script and should not */ /* be modified manually. See */ - /* #{filepath + " " * (74 - filepath.size) } */ + /* #{filepath.ljust(74)} */ /* if you are looking to modify the */ /* template */ /*----------------------------------------------------------------------------*/ diff --git a/prism_compile.c b/prism_compile.c index e655688401..e958580524 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -1265,6 +1265,7 @@ pm_new_child_iseq(rb_iseq_t *iseq, pm_scope_node_t *node, VALUE name, const rb_i type, ISEQ_COMPILE_DATA(iseq)->option, &error_state); if (error_state) { + pm_scope_node_destroy(node); RUBY_ASSERT(ret_iseq == NULL); rb_jump_tag(error_state); } @@ -1854,7 +1855,6 @@ pm_setup_args_dup_rest_p(const pm_node_t *node) switch (PM_NODE_TYPE(node)) { case PM_BACK_REFERENCE_READ_NODE: case PM_CLASS_VARIABLE_READ_NODE: - case PM_CONSTANT_PATH_NODE: case PM_CONSTANT_READ_NODE: case PM_FALSE_NODE: case PM_FLOAT_NODE: @@ -1873,8 +1873,24 @@ pm_setup_args_dup_rest_p(const pm_node_t *node) case PM_SYMBOL_NODE: case PM_TRUE_NODE: return false; + case PM_CONSTANT_PATH_NODE: { + const pm_constant_path_node_t *cast = (const pm_constant_path_node_t *) node; + if (cast->parent != NULL) { + return pm_setup_args_dup_rest_p(cast->parent); + } + return false; + } case PM_IMPLICIT_NODE: return pm_setup_args_dup_rest_p(((const pm_implicit_node_t *) node)->value); + case PM_ARRAY_NODE: { + const pm_array_node_t *cast = (const pm_array_node_t *) node; + for (size_t index = 0; index < cast->elements.size; index++) { + if (pm_setup_args_dup_rest_p(cast->elements.nodes[index])) { + return true; + } + } + return false; + } default: return true; } @@ -3496,7 +3512,7 @@ pm_compile_builtin_mandatory_only_method(rb_iseq_t *iseq, pm_scope_node_t *scope pm_scope_node_init(&def.base, &next_scope_node, scope_node); int error_state; - ISEQ_BODY(iseq)->mandatory_only_iseq = pm_iseq_new_with_opt( + const rb_iseq_t *mandatory_only_iseq = pm_iseq_new_with_opt( &next_scope_node, rb_iseq_base_label(iseq), rb_iseq_path(iseq), @@ -3508,6 +3524,7 @@ pm_compile_builtin_mandatory_only_method(rb_iseq_t *iseq, pm_scope_node_t *scope ISEQ_COMPILE_DATA(iseq)->option, &error_state ); + RB_OBJ_WRITE(iseq, &ISEQ_BODY(iseq)->mandatory_only_iseq, (VALUE)mandatory_only_iseq); if (error_state) { RUBY_ASSERT(ISEQ_BODY(iseq)->mandatory_only_iseq == NULL); @@ -5163,6 +5180,20 @@ pm_compile_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *cons break; } + case PM_SPLAT_NODE: { + // Splat nodes capture all values into an array. They can be used + // as targets in assignments or for loops. + // + // for *x in []; end + // + const pm_splat_node_t *cast = (const pm_splat_node_t *) node; + + if (cast->expression != NULL) { + pm_compile_target_node(iseq, cast->expression, parents, writes, cleanup, scope_node, state); + } + + break; + } default: rb_bug("Unexpected node type: %s", pm_node_type_to_str(PM_NODE_TYPE(node))); break; @@ -5276,7 +5307,8 @@ pm_compile_for_node_index(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *c case PM_INSTANCE_VARIABLE_TARGET_NODE: case PM_CONSTANT_PATH_TARGET_NODE: case PM_CALL_TARGET_NODE: - case PM_INDEX_TARGET_NODE: { + case PM_INDEX_TARGET_NODE: + case PM_SPLAT_NODE: { // For other targets, we need to potentially compile the parent or // owning expression of this target, then retrieve the value, expand it, // and then compile the necessary writes. @@ -9640,7 +9672,19 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // -> { it } // ^^ if (!popped) { - PUSH_GETLOCAL(ret, location, scope_node->local_table_for_iseq_size, 0); + pm_scope_node_t *current_scope_node = scope_node; + int level = 0; + + while (current_scope_node) { + if (current_scope_node->parameters && PM_NODE_TYPE_P(current_scope_node->parameters, PM_IT_PARAMETERS_NODE)) { + PUSH_GETLOCAL(ret, location, current_scope_node->local_table_for_iseq_size, level); + return; + } + + current_scope_node = current_scope_node->previous; + level++; + } + rb_bug("Local `it` does not exist"); } return; diff --git a/proc.c b/proc.c index 9fa47bddb5..8543110476 100644 --- a/proc.c +++ b/proc.c @@ -22,6 +22,7 @@ #include "method.h" #include "iseq.h" #include "vm_core.h" +#include "ractor_core.h" #include "yjit.h" const rb_cref_t *rb_vm_cref_in_context(VALUE self, VALUE cbase); @@ -297,10 +298,8 @@ rb_binding_alloc(VALUE klass) return obj; } - -/* :nodoc: */ static VALUE -binding_dup(VALUE self) +binding_copy(VALUE self) { VALUE bindval = rb_binding_alloc(rb_cBinding); rb_binding_t *src, *dst; @@ -309,15 +308,21 @@ binding_dup(VALUE self) rb_vm_block_copy(bindval, &dst->block, &src->block); RB_OBJ_WRITE(bindval, &dst->pathobj, src->pathobj); dst->first_lineno = src->first_lineno; - return rb_obj_dup_setup(self, bindval); + return bindval; +} + +/* :nodoc: */ +static VALUE +binding_dup(VALUE self) +{ + return rb_obj_dup_setup(self, binding_copy(self)); } /* :nodoc: */ static VALUE binding_clone(VALUE self) { - VALUE bindval = binding_dup(self); - return rb_obj_clone_setup(self, bindval, Qnil); + return rb_obj_clone_setup(self, binding_copy(self), Qnil); } VALUE @@ -506,7 +511,7 @@ bind_local_variables(VALUE bindval) int rb_numparam_id_p(ID id) { - return (tNUMPARAM_1 << ID_SCOPE_SHIFT) <= id && id < ((tNUMPARAM_1 + 10) << ID_SCOPE_SHIFT); + return (tNUMPARAM_1 << ID_SCOPE_SHIFT) <= id && id < ((tNUMPARAM_1 + 9) << ID_SCOPE_SHIFT); } /* @@ -1394,10 +1399,17 @@ rb_iseq_location(const rb_iseq_t *iseq) /* * call-seq: - * prc.source_location -> [String, Integer] + * prc.source_location -> [String, Integer, Integer, Integer, Integer] * - * Returns the Ruby source filename and line number containing this proc - * or +nil+ if this proc was not defined in Ruby (i.e. native). + * Returns the location where the Proc was defined. + * The returned Array contains: + * (1) the Ruby source filename + * (2) the line number where the definition starts + * (3) the column number where the definition starts + * (4) the line number where the definition ends + * (5) the column number where the definitions ends + * + * This method will return +nil+ if the Proc was not defined in Ruby (i.e. native). */ VALUE @@ -1507,6 +1519,7 @@ rb_hash_proc(st_index_t hash, VALUE prc) return hash; } +static VALUE sym_proc_cache = Qfalse; /* * call-seq: @@ -1525,29 +1538,33 @@ rb_hash_proc(st_index_t hash, VALUE prc) VALUE rb_sym_to_proc(VALUE sym) { - static VALUE sym_proc_cache = Qfalse; enum {SYM_PROC_CACHE_SIZE = 67}; - VALUE proc; - long index; - ID id; - if (!sym_proc_cache) { - sym_proc_cache = rb_ary_hidden_new(SYM_PROC_CACHE_SIZE * 2); - rb_vm_register_global_object(sym_proc_cache); - rb_ary_store(sym_proc_cache, SYM_PROC_CACHE_SIZE*2 - 1, Qnil); - } + if (rb_ractor_main_p()) { + if (!sym_proc_cache) { + sym_proc_cache = rb_ary_hidden_new(SYM_PROC_CACHE_SIZE); + rb_ary_store(sym_proc_cache, SYM_PROC_CACHE_SIZE - 1, Qnil); + } - id = SYM2ID(sym); - index = (id % SYM_PROC_CACHE_SIZE) << 1; + ID id = SYM2ID(sym); + long index = (id % SYM_PROC_CACHE_SIZE); + VALUE procval = RARRAY_AREF(sym_proc_cache, index); + if (RTEST(procval)) { + rb_proc_t *proc; + GetProcPtr(procval, proc); - if (RARRAY_AREF(sym_proc_cache, index) == sym) { - return RARRAY_AREF(sym_proc_cache, index + 1); + if (proc->block.as.symbol == sym) { + return procval; + } + } + + procval = sym_proc_new(rb_cProc, sym); + RARRAY_ASET(sym_proc_cache, index, procval); + + return RB_GC_GUARD(procval); } else { - proc = sym_proc_new(rb_cProc, ID2SYM(id)); - RARRAY_ASET(sym_proc_cache, index, sym); - RARRAY_ASET(sym_proc_cache, index + 1, proc); - return proc; + return sym_proc_new(rb_cProc, sym); } } @@ -3056,10 +3073,17 @@ rb_method_entry_location(const rb_method_entry_t *me) /* * call-seq: - * meth.source_location -> [String, Integer] + * meth.source_location -> [String, Integer, Integer, Integer, Integer] * - * Returns the Ruby source filename and line number containing this method - * or nil if this method was not defined in Ruby (i.e. native). + * Returns the location where the method was defined. + * The returned Array contains: + * (1) the Ruby source filename + * (2) the line number where the definition starts + * (3) the column number where the definition starts + * (4) the line number where the definition ends + * (5) the column number where the definitions ends + * + * This method will return +nil+ if the method was not defined in Ruby (i.e. native). */ VALUE @@ -4557,6 +4581,8 @@ Init_Proc(void) void Init_Binding(void) { + rb_gc_register_address(&sym_proc_cache); + rb_cBinding = rb_define_class("Binding", rb_cObject); rb_undef_alloc_func(rb_cBinding); rb_undef_method(CLASS_OF(rb_cBinding), "new"); diff --git a/process.c b/process.c index 605821853e..da9ce74027 100644 --- a/process.c +++ b/process.c @@ -114,6 +114,7 @@ int initgroups(const char *, rb_gid_t); #include "ruby/st.h" #include "ruby/thread.h" #include "ruby/util.h" +#include "ractor_core.h" #include "vm_core.h" #include "vm_sync.h" #include "ruby/ractor.h" @@ -4120,9 +4121,13 @@ rb_fork_async_signal_safe(int *status, rb_pid_t rb_fork_ruby(int *status) { + if (UNLIKELY(!rb_ractor_main_p())) { + rb_raise(rb_eRactorIsolationError, "can not fork from non-main Ractors"); + } + struct rb_process_status child = {.status = 0}; rb_pid_t pid; - int try_gc = 1, err; + int try_gc = 1, err = 0; struct child_handler_disabler_state old; do { @@ -4132,12 +4137,10 @@ rb_fork_ruby(int *status) rb_thread_acquire_fork_lock(); disable_child_handler_before_fork(&old); - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { child.pid = pid = rb_fork(); child.error = err = errno; } - RB_VM_LOCK_LEAVE(); disable_child_handler_fork_parent(&old); /* yes, bad name */ if ( @@ -8756,9 +8759,9 @@ static VALUE rb_mProcID_Syscall; static VALUE proc_warmup(VALUE _) { - RB_VM_LOCK_ENTER(); - rb_gc_prepare_heap(); - RB_VM_LOCK_LEAVE(); + RB_VM_LOCKING() { + rb_gc_prepare_heap(); + } return Qtrue; } diff --git a/ractor.c b/ractor.c index ec11211629..12ffced0a3 100644 --- a/ractor.c +++ b/ractor.c @@ -33,6 +33,11 @@ static VALUE rb_cRactorMovedObject; static void vm_ractor_blocking_cnt_inc(rb_vm_t *vm, rb_ractor_t *r, const char *file, int line); + +#if RACTOR_CHECK_MODE > 0 +bool rb_ractor_ignore_belonging_flag = false; +#endif + // Ractor locking static void @@ -178,37 +183,21 @@ ractor_status_p(rb_ractor_t *r, enum ractor_status status) // Ractor data/mark/free -static struct rb_ractor_basket *ractor_queue_at(rb_ractor_t *r, struct rb_ractor_queue *rq, int i); static void ractor_local_storage_mark(rb_ractor_t *r); static void ractor_local_storage_free(rb_ractor_t *r); -static void -ractor_queue_mark(struct rb_ractor_queue *rq) -{ - for (int i=0; icnt; i++) { - struct rb_ractor_basket *b = ractor_queue_at(NULL, rq, i); - rb_gc_mark(b->sender); - - switch (b->type.e) { - case basket_type_yielding: - case basket_type_take_basket: - case basket_type_deleted: - case basket_type_reserved: - // ignore - break; - default: - rb_gc_mark(b->p.send.v); - } - } -} +static void ractor_sync_mark(rb_ractor_t *r); +static void ractor_sync_free(rb_ractor_t *r); +static size_t ractor_sync_memsize(const rb_ractor_t *r); +static void ractor_sync_init(rb_ractor_t *r); static void ractor_mark(void *ptr) { rb_ractor_t *r = (rb_ractor_t *)ptr; - ractor_queue_mark(&r->sync.recv_queue); - ractor_queue_mark(&r->sync.takers_queue); + // mark received messages + ractor_sync_mark(r); rb_gc_mark(r->loc); rb_gc_mark(r->name); @@ -228,20 +217,15 @@ ractor_mark(void *ptr) ractor_local_storage_mark(r); } -static void -ractor_queue_free(struct rb_ractor_queue *rq) -{ - free(rq->baskets); -} - static void ractor_free(void *ptr) { rb_ractor_t *r = (rb_ractor_t *)ptr; RUBY_DEBUG_LOG("free r:%d", rb_ractor_id(r)); rb_native_mutex_destroy(&r->sync.lock); - ractor_queue_free(&r->sync.recv_queue); - ractor_queue_free(&r->sync.takers_queue); +#ifdef RUBY_THREAD_WIN32_H + rb_native_cond_destroy(&r->sync.wakeup_cond); +#endif ractor_local_storage_free(r); rb_hook_list_free(&r->pub.hooks); @@ -252,24 +236,17 @@ ractor_free(void *ptr) r->newobj_cache = NULL; } + ractor_sync_free(r); ruby_xfree(r); } -static size_t -ractor_queue_memsize(const struct rb_ractor_queue *rq) -{ - return sizeof(struct rb_ractor_basket) * rq->size; -} - static size_t ractor_memsize(const void *ptr) { rb_ractor_t *r = (rb_ractor_t *)ptr; // TODO: more correct? - return sizeof(rb_ractor_t) + - ractor_queue_memsize(&r->sync.recv_queue) + - ractor_queue_memsize(&r->sync.takers_queue); + return sizeof(rb_ractor_t) + ractor_sync_memsize(r); } static const rb_data_type_t ractor_data_type = { @@ -317,1713 +294,7 @@ rb_ractor_current_id(void) } #endif -// Ractor queue - -static void -ractor_queue_setup(struct rb_ractor_queue *rq) -{ - rq->size = 2; - rq->cnt = 0; - rq->start = 0; - rq->baskets = malloc(sizeof(struct rb_ractor_basket) * rq->size); -} - -static struct rb_ractor_basket * -ractor_queue_head(rb_ractor_t *r, struct rb_ractor_queue *rq) -{ - if (r != NULL) ASSERT_ractor_locking(r); - return &rq->baskets[rq->start]; -} - -static struct rb_ractor_basket * -ractor_queue_at(rb_ractor_t *r, struct rb_ractor_queue *rq, int i) -{ - if (r != NULL) ASSERT_ractor_locking(r); - return &rq->baskets[(rq->start + i) % rq->size]; -} - -static void -ractor_queue_advance(rb_ractor_t *r, struct rb_ractor_queue *rq) -{ - ASSERT_ractor_locking(r); - - if (rq->reserved_cnt == 0) { - rq->cnt--; - rq->start = (rq->start + 1) % rq->size; - rq->serial++; - } - else { - ractor_queue_at(r, rq, 0)->type.e = basket_type_deleted; - } -} - -static bool -ractor_queue_skip_p(rb_ractor_t *r, struct rb_ractor_queue *rq, int i) -{ - struct rb_ractor_basket *b = ractor_queue_at(r, rq, i); - return basket_type_p(b, basket_type_deleted) || - basket_type_p(b, basket_type_reserved); -} - -static void -ractor_queue_compact(rb_ractor_t *r, struct rb_ractor_queue *rq) -{ - ASSERT_ractor_locking(r); - - while (rq->cnt > 0 && basket_type_p(ractor_queue_at(r, rq, 0), basket_type_deleted)) { - ractor_queue_advance(r, rq); - } -} - -static bool -ractor_queue_empty_p(rb_ractor_t *r, struct rb_ractor_queue *rq) -{ - ASSERT_ractor_locking(r); - - if (rq->cnt == 0) { - return true; - } - - ractor_queue_compact(r, rq); - - for (int i=0; icnt; i++) { - if (!ractor_queue_skip_p(r, rq, i)) { - return false; - } - } - - return true; -} - -static bool -ractor_queue_deq(rb_ractor_t *r, struct rb_ractor_queue *rq, struct rb_ractor_basket *basket) -{ - ASSERT_ractor_locking(r); - - for (int i=0; icnt; i++) { - if (!ractor_queue_skip_p(r, rq, i)) { - struct rb_ractor_basket *b = ractor_queue_at(r, rq, i); - *basket = *b; - - // remove from queue - b->type.e = basket_type_deleted; - ractor_queue_compact(r, rq); - return true; - } - } - - return false; -} - -static void -ractor_queue_enq(rb_ractor_t *r, struct rb_ractor_queue *rq, struct rb_ractor_basket *basket) -{ - ASSERT_ractor_locking(r); - - if (rq->size <= rq->cnt) { - rq->baskets = realloc(rq->baskets, sizeof(struct rb_ractor_basket) * rq->size * 2); - for (int i=rq->size - rq->start; icnt; i++) { - rq->baskets[i + rq->start] = rq->baskets[i + rq->start - rq->size]; - } - rq->size *= 2; - } - // copy basket into queue - rq->baskets[(rq->start + rq->cnt++) % rq->size] = *basket; - // fprintf(stderr, "%s %p->cnt:%d\n", RUBY_FUNCTION_NAME_STRING, (void *)rq, rq->cnt); -} - -static void -ractor_queue_delete(rb_ractor_t *r, struct rb_ractor_queue *rq, struct rb_ractor_basket *basket) -{ - basket->type.e = basket_type_deleted; -} - -// Ractor basket - -static VALUE ractor_reset_belonging(VALUE obj); // in this file - -static VALUE -ractor_basket_value(struct rb_ractor_basket *b) -{ - switch (b->type.e) { - case basket_type_ref: - break; - case basket_type_copy: - case basket_type_move: - case basket_type_will: - b->type.e = basket_type_ref; - b->p.send.v = ractor_reset_belonging(b->p.send.v); - break; - default: - rb_bug("unreachable"); - } - - return b->p.send.v; -} - -static VALUE -ractor_basket_accept(struct rb_ractor_basket *b) -{ - VALUE v = ractor_basket_value(b); - - // a ractor's main thread had an error and yielded us this exception during its dying moments - if (b->p.send.exception) { - VALUE cause = v; - VALUE err = rb_exc_new_cstr(rb_eRactorRemoteError, "thrown by remote Ractor."); - rb_ivar_set(err, rb_intern("@ractor"), b->sender); - rb_ec_setup_exception(NULL, err, cause); - rb_exc_raise(err); - } - - return v; -} - -// Ractor synchronizations - -#if USE_RUBY_DEBUG_LOG -static const char * -wait_status_str(enum rb_ractor_wait_status wait_status) -{ - switch ((int)wait_status) { - case wait_none: return "none"; - case wait_receiving: return "receiving"; - case wait_taking: return "taking"; - case wait_yielding: return "yielding"; - case wait_receiving|wait_taking: return "receiving|taking"; - case wait_receiving|wait_yielding: return "receiving|yielding"; - case wait_taking|wait_yielding: return "taking|yielding"; - case wait_receiving|wait_taking|wait_yielding: return "receiving|taking|yielding"; - } - rb_bug("unreachable"); -} - -static const char * -wakeup_status_str(enum rb_ractor_wakeup_status wakeup_status) -{ - switch (wakeup_status) { - case wakeup_none: return "none"; - case wakeup_by_send: return "by_send"; - case wakeup_by_yield: return "by_yield"; - case wakeup_by_take: return "by_take"; - case wakeup_by_close: return "by_close"; - case wakeup_by_interrupt: return "by_interrupt"; - case wakeup_by_retry: return "by_retry"; - } - rb_bug("unreachable"); -} - -static const char * -basket_type_name(enum rb_ractor_basket_type type) -{ - switch (type) { - case basket_type_none: return "none"; - case basket_type_ref: return "ref"; - case basket_type_copy: return "copy"; - case basket_type_move: return "move"; - case basket_type_will: return "will"; - case basket_type_deleted: return "deleted"; - case basket_type_reserved: return "reserved"; - case basket_type_take_basket: return "take_basket"; - case basket_type_yielding: return "yielding"; - } - VM_ASSERT(0); - return NULL; -} -#endif // USE_RUBY_DEBUG_LOG - -static rb_thread_t * -ractor_sleeping_by(const rb_ractor_t *r, rb_thread_t *th, enum rb_ractor_wait_status wait_status) -{ - if (th) { - if ((th->ractor_waiting.wait_status & wait_status) && th->ractor_waiting.wakeup_status == wakeup_none) { - return th; - } - } else { - // find any thread that has this ractor wait status that is blocked - ccan_list_for_each(&r->sync.wait.waiting_threads, th, ractor_waiting.waiting_node) { - if ((th->ractor_waiting.wait_status & wait_status) && th->ractor_waiting.wakeup_status == wakeup_none) { - return th; - } - } - } - return NULL; -} - -#ifdef RUBY_THREAD_PTHREAD_H -// thread_*.c -void rb_ractor_sched_wakeup(rb_ractor_t *r, rb_thread_t *th); -#else - -// win32 -static void -rb_ractor_sched_wakeup(rb_ractor_t *r, rb_thread_t *th) -{ - (void)r; - ASSERT_ractor_locking(r); - rb_native_cond_signal(&th->ractor_waiting.cond); - -} -#endif - - -/* - * Wakeup `r` if the given `th` is blocked and has the given ractor `wait_status`. - * Wakeup any blocked thread in `r` with the given ractor `wait_status` if `th` is NULL. - */ -static bool -ractor_wakeup(rb_ractor_t *r, rb_thread_t *th /* can be NULL */, enum rb_ractor_wait_status wait_status, enum rb_ractor_wakeup_status wakeup_status) -{ - ASSERT_ractor_locking(r); - - RUBY_DEBUG_LOG("r:%u wait_by:%s -> wait:%s wakeup:%s", - rb_ractor_id(r), - wait_status_str(th->ractor_waiting.wait_status), - wait_status_str(wait_status), - wakeup_status_str(wakeup_status)); - - if ((th = ractor_sleeping_by(r, th, wait_status)) != NULL) { - th->ractor_waiting.wakeup_status = wakeup_status; - rb_ractor_sched_wakeup(r, th); - return true; - } - else { - return false; - } -} - -// unblock function (UBF). This gets called when another thread on this or another ractor sets our thread's interrupt flag. -// This is not async-safe. -static void -ractor_sleep_interrupt(void *ptr) -{ - rb_execution_context_t *ec = ptr; - rb_ractor_t *r = rb_ec_ractor_ptr(ec); - rb_thread_t *th = rb_ec_thread_ptr(ec); - - RACTOR_LOCK(r); - { - ractor_wakeup(r, th, wait_receiving | wait_taking | wait_yielding, wakeup_by_interrupt); - } - RACTOR_UNLOCK(r); -} - -typedef void (*ractor_sleep_cleanup_function)(rb_ractor_t *cr, void *p); - -// Checks the current thread for ruby interrupts and runs the cleanup function `cf_func` with `cf_data` if -// `rb_ec_check_ints` is going to raise. See the `rb_threadptr_execute_interrupts` for info on when it can raise. -static void -ractor_check_ints(rb_execution_context_t *ec, rb_ractor_t *cr, rb_thread_t *cur_th, ractor_sleep_cleanup_function cf_func, void *cf_data) -{ - if (cur_th->ractor_waiting.wait_status != wait_none) { - enum rb_ractor_wait_status prev_wait_status = cur_th->ractor_waiting.wait_status; - cur_th->ractor_waiting.wait_status = wait_none; - cur_th->ractor_waiting.wakeup_status = wakeup_by_interrupt; - - RACTOR_UNLOCK(cr); - { - if (cf_func) { - enum ruby_tag_type state; - EC_PUSH_TAG(ec); - if ((state = EC_EXEC_TAG()) == TAG_NONE) { - rb_ec_check_ints(ec); - } - EC_POP_TAG(); - - if (state) { - (*cf_func)(cr, cf_data); // cleanup function is run after the ubf, if it had ubf - EC_JUMP_TAG(ec, state); - } - } - else { - rb_ec_check_ints(ec); - } - } - - RACTOR_LOCK(cr); - cur_th->ractor_waiting.wait_status = prev_wait_status; - } -} - -#ifdef RUBY_THREAD_PTHREAD_H -void rb_ractor_sched_sleep(rb_execution_context_t *ec, rb_ractor_t *cr, rb_unblock_function_t *ubf); -#else - -static void -ractor_cond_wait(rb_ractor_t *r, rb_thread_t *th) -{ -#if RACTOR_CHECK_MODE > 0 - VALUE locked_by = r->sync.locked_by; - r->sync.locked_by = Qnil; -#endif - rb_native_cond_wait(&th->ractor_waiting.cond, &r->sync.lock); - -#if RACTOR_CHECK_MODE > 0 - r->sync.locked_by = locked_by; -#endif -} - -static void * -ractor_sleep_wo_gvl(void *ptr) -{ - rb_ractor_t *cr = ptr; - rb_execution_context_t *ec = cr->threads.running_ec; - VM_ASSERT(GET_EC() == ec); - rb_thread_t *cur_th = rb_ec_thread_ptr(ec); - RACTOR_LOCK_SELF(cr); - { - VM_ASSERT(cur_th->ractor_waiting.wait_status != wait_none); - // it's possible that another ractor has woken us up (ractor_wakeup), - // so check this condition - if (cur_th->ractor_waiting.wakeup_status == wakeup_none) { - cur_th->status = THREAD_STOPPED_FOREVER; - ractor_cond_wait(cr, cur_th); - cur_th->status = THREAD_RUNNABLE; - VM_ASSERT(cur_th->ractor_waiting.wakeup_status != wakeup_none); - } else { - RUBY_DEBUG_LOG("rare timing, no cond wait"); - } - cur_th->ractor_waiting.wait_status = wait_none; - } - RACTOR_UNLOCK_SELF(cr); - return NULL; -} - -static void -rb_ractor_sched_sleep(rb_execution_context_t *ec, rb_ractor_t *cr, rb_unblock_function_t *ubf_ractor_sleep_interrupt) -{ - ASSERT_ractor_locking(cr); - rb_thread_t *th = rb_ec_thread_ptr(ec); - struct ccan_list_node *waitn = &th->ractor_waiting.waiting_node; - VM_ASSERT(waitn->next == waitn->prev && waitn->next == waitn); // it should be unlinked - ccan_list_add(&cr->sync.wait.waiting_threads, waitn); - RACTOR_UNLOCK(cr); - { - rb_nogvl(ractor_sleep_wo_gvl, cr, ubf_ractor_sleep_interrupt, ec, RB_NOGVL_INTR_FAIL); - } - RACTOR_LOCK(cr); - ccan_list_del_init(waitn); -} -#endif - -/* - * Sleep the current ractor's current thread until another ractor wakes us up or another thread calls our unblock function. - * The following ractor actions can cause this function to be called: - * Ractor#take (wait_taking) - * Ractor.yield (wait_yielding) - * Ractor.receive (wait_receiving) - * Ractor.select (can be a combination of the above wait states, depending on the states of the ractors passed to Ractor.select) - */ -static enum rb_ractor_wakeup_status -ractor_sleep_with_cleanup(rb_execution_context_t *ec, rb_ractor_t *cr, rb_thread_t *cur_th, enum rb_ractor_wait_status wait_status, - ractor_sleep_cleanup_function cf_func, void *cf_data) -{ - ASSERT_ractor_locking(cr); - enum rb_ractor_wakeup_status wakeup_status; - VM_ASSERT(GET_RACTOR() == cr); - - VM_ASSERT(cur_th->ractor_waiting.wait_status == wait_none); - VM_ASSERT(wait_status != wait_none); - cur_th->ractor_waiting.wait_status = wait_status; - cur_th->ractor_waiting.wakeup_status = wakeup_none; - - // fprintf(stderr, "%s r:%p status:%s, wakeup_status:%s\n", RUBY_FUNCTION_NAME_STRING, (void *)cr, - // wait_status_str(cr->sync.wait.status), wakeup_status_str(cr->sync.wait.wakeup_status)); - - RUBY_DEBUG_LOG("sleep by %s", wait_status_str(wait_status)); - - while (cur_th->ractor_waiting.wakeup_status == wakeup_none) { - rb_ractor_sched_sleep(ec, cr, ractor_sleep_interrupt); - ractor_check_ints(ec, cr, cur_th, cf_func, cf_data); - } - - cur_th->ractor_waiting.wait_status = wait_none; - - wakeup_status = cur_th->ractor_waiting.wakeup_status; - cur_th->ractor_waiting.wakeup_status = wakeup_none; - - RUBY_DEBUG_LOG("wakeup %s", wakeup_status_str(wakeup_status)); - - ASSERT_ractor_locking(cr); - return wakeup_status; -} - -static enum rb_ractor_wakeup_status -ractor_sleep(rb_execution_context_t *ec, rb_ractor_t *cr, rb_thread_t *cur_th, enum rb_ractor_wait_status wait_status) -{ - return ractor_sleep_with_cleanup(ec, cr, cur_th, wait_status, 0, NULL); -} - -// Ractor.receive - -static void -ractor_recursive_receive_if(rb_thread_t *th) -{ - if (th->ractor_waiting.receiving_mutex && rb_mutex_owned_p(th->ractor_waiting.receiving_mutex)) { - rb_raise(rb_eRactorError, "can not call receive/receive_if recursively"); - } -} - -static VALUE -ractor_try_receive(rb_execution_context_t *ec, rb_ractor_t *cr, struct rb_ractor_queue *rq) -{ - struct rb_ractor_basket basket; - ractor_recursive_receive_if(rb_ec_thread_ptr(ec)); - bool received = false; - - RACTOR_LOCK_SELF(cr); - { - RUBY_DEBUG_LOG("rq->cnt:%d", rq->cnt); - received = ractor_queue_deq(cr, rq, &basket); - } - RACTOR_UNLOCK_SELF(cr); - - if (!received) { - if (cr->sync.incoming_port_closed) { - rb_raise(rb_eRactorClosedError, "The incoming port is already closed"); - } - return Qundef; - } - else { - return ractor_basket_accept(&basket); - } -} - -static void -ractor_wait_receive(rb_execution_context_t *ec, rb_ractor_t *cr, struct rb_ractor_queue *rq) -{ - VM_ASSERT(cr == rb_ec_ractor_ptr(ec)); - rb_thread_t *cur_th = rb_ec_thread_ptr(ec); - ractor_recursive_receive_if(cur_th); - - RACTOR_LOCK(cr); - { - while (ractor_queue_empty_p(cr, rq) && !cr->sync.incoming_port_closed) { - ractor_sleep(ec, cr, cur_th, wait_receiving); - } - } - RACTOR_UNLOCK(cr); -} - -static VALUE -ractor_receive(rb_execution_context_t *ec, rb_ractor_t *cr) -{ - VM_ASSERT(cr == rb_ec_ractor_ptr(ec)); - VALUE v; - struct rb_ractor_queue *rq = &cr->sync.recv_queue; - - while (UNDEF_P(v = ractor_try_receive(ec, cr, rq))) { - ractor_wait_receive(ec, cr, rq); - } - - return v; -} - -#if 0 -static void -rq_dump(struct rb_ractor_queue *rq) -{ - bool bug = false; - for (int i=0; icnt; i++) { - struct rb_ractor_basket *b = ractor_queue_at(NULL, rq, i); - fprintf(stderr, "%d (start:%d) type:%s %p %s\n", i, rq->start, basket_type_name(b->type), - (void *)b, RSTRING_PTR(RARRAY_AREF(b->v, 1))); - if (basket_type_p(b, basket_type_reserved) bug = true; - } - if (bug) rb_bug("!!"); -} -#endif - -struct receive_block_data { - rb_ractor_t *cr; - rb_thread_t *th; - struct rb_ractor_queue *rq; - VALUE v; - int index; - bool success; -}; - -static void -ractor_receive_if_lock(rb_thread_t *th) -{ - VALUE m = th->ractor_waiting.receiving_mutex; - if (m == Qfalse) { - m = th->ractor_waiting.receiving_mutex = rb_mutex_new(); - } - rb_mutex_lock(m); -} - -static VALUE -receive_if_body(VALUE ptr) -{ - struct receive_block_data *data = (struct receive_block_data *)ptr; - - ractor_receive_if_lock(data->th); - VALUE block_result = rb_yield(data->v); - rb_ractor_t *cr = data->cr; - - RACTOR_LOCK_SELF(cr); - { - struct rb_ractor_basket *b = ractor_queue_at(cr, data->rq, data->index); - VM_ASSERT(basket_type_p(b, basket_type_reserved)); - data->rq->reserved_cnt--; - - if (RTEST(block_result)) { - ractor_queue_delete(cr, data->rq, b); - ractor_queue_compact(cr, data->rq); - } - else { - b->type.e = basket_type_ref; - } - } - RACTOR_UNLOCK_SELF(cr); - - data->success = true; - - if (RTEST(block_result)) { - return data->v; - } - else { - return Qundef; - } -} - -static VALUE -receive_if_ensure(VALUE v) -{ - struct receive_block_data *data = (struct receive_block_data *)v; - rb_ractor_t *cr = data->cr; - rb_thread_t *cur_th = data->th; - - if (!data->success) { - RACTOR_LOCK_SELF(cr); - { - struct rb_ractor_basket *b = ractor_queue_at(cr, data->rq, data->index); - VM_ASSERT(basket_type_p(b, basket_type_reserved)); - b->type.e = basket_type_deleted; - data->rq->reserved_cnt--; - } - RACTOR_UNLOCK_SELF(cr); - } - - rb_mutex_unlock(cur_th->ractor_waiting.receiving_mutex); - return Qnil; -} - -static VALUE -ractor_receive_if(rb_execution_context_t *ec, VALUE crv, VALUE b) -{ - if (!RTEST(b)) rb_raise(rb_eArgError, "no block given"); - - rb_ractor_t *cr = rb_ec_ractor_ptr(ec); - rb_thread_t *cur_th = rb_ec_thread_ptr(ec); - unsigned int serial = (unsigned int)-1; - int index = 0; - struct rb_ractor_queue *rq = &cr->sync.recv_queue; - - while (1) { - VALUE v = Qundef; - - ractor_wait_receive(ec, cr, rq); - - RACTOR_LOCK_SELF(cr); - { - if (serial != rq->serial) { - serial = rq->serial; - index = 0; - } - - // check newer version - for (int i=index; icnt; i++) { - if (!ractor_queue_skip_p(cr, rq, i)) { - struct rb_ractor_basket *b = ractor_queue_at(cr, rq, i); - v = ractor_basket_value(b); - b->type.e = basket_type_reserved; - rq->reserved_cnt++; - index = i; - break; - } - } - } - RACTOR_UNLOCK_SELF(cr); - - if (!UNDEF_P(v)) { - struct receive_block_data data = { - .cr = cr, - .th = cur_th, - .rq = rq, - .v = v, - .index = index, - .success = false, - }; - - VALUE result = rb_ensure(receive_if_body, (VALUE)&data, - receive_if_ensure, (VALUE)&data); - - if (!UNDEF_P(result)) return result; - index++; - } - - RUBY_VM_CHECK_INTS(ec); - } -} - -static void -ractor_send_basket(rb_execution_context_t *ec, rb_ractor_t *r, struct rb_ractor_basket *b) -{ - bool closed = false; - - RACTOR_LOCK(r); - { - if (r->sync.incoming_port_closed) { - closed = true; - } - else { - ractor_queue_enq(r, &r->sync.recv_queue, b); - // wakeup any receiving thread in `r` - ractor_wakeup(r, NULL, wait_receiving, wakeup_by_send); - } - } - RACTOR_UNLOCK(r); - - if (closed) { - rb_raise(rb_eRactorClosedError, "The incoming-port is already closed"); - } -} - -// Ractor#send - -static VALUE ractor_move(VALUE obj); // in this file -static VALUE ractor_copy(VALUE obj); // in this file - -static void -ractor_basket_prepare_contents(VALUE obj, VALUE move, volatile VALUE *pobj, enum rb_ractor_basket_type *ptype) -{ - VALUE v; - enum rb_ractor_basket_type type; - - if (rb_ractor_shareable_p(obj)) { - type = basket_type_ref; - v = obj; - } - else if (!RTEST(move)) { - v = ractor_copy(obj); - type = basket_type_copy; - } - else { - type = basket_type_move; - v = ractor_move(obj); - } - - *pobj = v; - *ptype = type; -} - -static void -ractor_basket_fill_(rb_ractor_t *cr, rb_thread_t *cur_th, struct rb_ractor_basket *basket, VALUE obj, bool exc) -{ - VM_ASSERT(cr == GET_RACTOR()); - - basket->sender = cr->pub.self; - basket->sending_th = cur_th; - basket->p.send.exception = exc; - basket->p.send.v = obj; -} - -static void -ractor_basket_fill(rb_ractor_t *cr, rb_thread_t *cur_th, struct rb_ractor_basket *basket, VALUE obj, VALUE move, bool exc) -{ - VALUE v; - enum rb_ractor_basket_type type; - ractor_basket_prepare_contents(obj, move, &v, &type); - ractor_basket_fill_(cr, cur_th, basket, v, exc); - basket->type.e = type; -} - -static void -ractor_basket_fill_will(rb_ractor_t *cr, rb_thread_t *cur_th, struct rb_ractor_basket *basket, VALUE obj, bool exc) -{ - ractor_basket_fill_(cr, cur_th, basket, obj, exc); - basket->type.e = basket_type_will; -} - -static VALUE -ractor_send(rb_execution_context_t *ec, rb_ractor_t *recv_r, VALUE obj, VALUE move) -{ - struct rb_ractor_basket basket; - rb_ractor_t *cr = rb_ec_ractor_ptr(ec); - rb_thread_t *cur_th = rb_ec_thread_ptr(ec); - // TODO: Ractor local GC - ractor_basket_fill(cr, cur_th, &basket, obj, move, false); - ractor_send_basket(ec, recv_r, &basket); - return recv_r->pub.self; -} - -// Ractor#take - -static bool -ractor_take_has_will(rb_ractor_t *r) -{ - ASSERT_ractor_locking(r); - - return basket_type_p(&r->sync.will_basket, basket_type_will); -} - -static bool -ractor_take_will(rb_ractor_t *r, struct rb_ractor_basket *b) -{ - ASSERT_ractor_locking(r); - - if (ractor_take_has_will(r)) { - *b = r->sync.will_basket; - r->sync.will_basket.type.e = basket_type_none; - return true; - } - else { - VM_ASSERT(basket_type_p(&r->sync.will_basket, basket_type_none)); - return false; - } -} - -static bool -ractor_take_will_lock(rb_ractor_t *r, struct rb_ractor_basket *b) -{ - ASSERT_ractor_unlocking(r); - bool taken; - - RACTOR_LOCK(r); - { - taken = ractor_take_will(r, b); - } - RACTOR_UNLOCK(r); - - return taken; -} - -static bool -ractor_register_take(rb_ractor_t *cr, rb_thread_t *cur_th, rb_ractor_t *r, struct rb_ractor_basket *take_basket, - bool is_take, struct rb_ractor_selector_take_config *config, bool ignore_error) -{ - struct rb_ractor_basket b = { - .type.e = basket_type_take_basket, - .sender = cr->pub.self, - .sending_th = cur_th, - .p = { - .take = { - .basket = take_basket, // pointer to our stack value saved in ractor `r` queue - .config = config, - }, - }, - }; - bool closed = false; - - RACTOR_LOCK(r); - { - if (is_take && ractor_take_will(r, take_basket)) { - RUBY_DEBUG_LOG("take over a will of r:%d", rb_ractor_id(r)); - } - else if (!is_take && ractor_take_has_will(r)) { - RUBY_DEBUG_LOG("has_will"); - VM_ASSERT(config != NULL); - config->closed = true; - } - else if (r->sync.outgoing_port_closed) { - closed = true; - } - else { - RUBY_DEBUG_LOG("register in r:%d", rb_ractor_id(r)); - ractor_queue_enq(r, &r->sync.takers_queue, &b); - - if (basket_none_p(take_basket)) { - // wakeup any thread in `r` that has yielded, if there is any. - ractor_wakeup(r, NULL, wait_yielding, wakeup_by_take); - } - } - } - RACTOR_UNLOCK(r); - - if (closed) { - if (!ignore_error) rb_raise(rb_eRactorClosedError, "The outgoing-port is already closed"); - return false; - } - else { - return true; - } -} - -static bool -ractor_deregister_take(rb_ractor_t *r, struct rb_ractor_basket *take_basket) -{ - struct rb_ractor_queue *ts = &r->sync.takers_queue; - bool deleted = false; - - RACTOR_LOCK(r); - { - if (r->sync.outgoing_port_closed) { - // ok - } - else { - for (int i=0; icnt; i++) { - struct rb_ractor_basket *b = ractor_queue_at(r, ts, i); - if (basket_type_p(b, basket_type_take_basket) && b->p.take.basket == take_basket) { - ractor_queue_delete(r, ts, b); - deleted = true; - } - } - if (deleted) { - ractor_queue_compact(r, ts); - } - } - } - RACTOR_UNLOCK(r); - - return deleted; -} - -static VALUE -ractor_try_take(rb_ractor_t *cr, rb_thread_t *cur_th, rb_ractor_t *recv_r, struct rb_ractor_basket *take_basket) -{ - bool taken; - - RACTOR_LOCK_SELF(cr); - { - // If it hasn't yielded yet or is currently in the process of yielding, sleep more - if (basket_none_p(take_basket) || basket_type_p(take_basket, basket_type_yielding)) { - taken = false; - } - else { - taken = true; // basket type might be, for ex, basket_type_copy if value was copied during yield - } - } - RACTOR_UNLOCK_SELF(cr); - - if (taken) { - RUBY_DEBUG_LOG("taken"); - if (basket_type_p(take_basket, basket_type_deleted)) { - VM_ASSERT(recv_r->sync.outgoing_port_closed); - rb_raise(rb_eRactorClosedError, "The outgoing-port is already closed"); - } - return ractor_basket_accept(take_basket); - } - else { - RUBY_DEBUG_LOG("not taken"); - return Qundef; - } -} - - -#if VM_CHECK_MODE > 0 -static bool -ractor_check_specific_take_basket_lock(rb_ractor_t *r, struct rb_ractor_basket *tb) -{ - bool ret = false; - struct rb_ractor_queue *ts = &r->sync.takers_queue; - - RACTOR_LOCK(r); - { - for (int i=0; icnt; i++) { - struct rb_ractor_basket *b = ractor_queue_at(r, ts, i); - if (basket_type_p(b, basket_type_take_basket) && b->p.take.basket == tb) { - ret = true; - break; - } - } - } - RACTOR_UNLOCK(r); - - return ret; -} -#endif - -// cleanup function, cr is unlocked -static void -ractor_take_cleanup(rb_ractor_t *cr, rb_ractor_t *r, struct rb_ractor_basket *tb) -{ - retry: - if (basket_none_p(tb)) { // not yielded yet - if (!ractor_deregister_take(r, tb)) { - // not in r's takers queue - rb_thread_sleep(0); - goto retry; - } - } - else { - VM_ASSERT(!ractor_check_specific_take_basket_lock(r, tb)); - } -} - -struct take_wait_take_cleanup_data { - rb_ractor_t *r; - struct rb_ractor_basket *tb; -}; - -static void -ractor_wait_take_cleanup(rb_ractor_t *cr, void *ptr) -{ - struct take_wait_take_cleanup_data *data = (struct take_wait_take_cleanup_data *)ptr; - ractor_take_cleanup(cr, data->r, data->tb); -} - -static void -ractor_wait_take(rb_execution_context_t *ec, rb_ractor_t *cr, rb_thread_t *cur_th, rb_ractor_t *r, struct rb_ractor_basket *take_basket) -{ - struct take_wait_take_cleanup_data data = { - .r = r, - .tb = take_basket, - }; - - RACTOR_LOCK_SELF(cr); - { - if (basket_none_p(take_basket) || basket_type_p(take_basket, basket_type_yielding)) { - ractor_sleep_with_cleanup(ec, cr, cur_th, wait_taking, ractor_wait_take_cleanup, &data); - } - } - RACTOR_UNLOCK_SELF(cr); -} - -static VALUE -ractor_take(rb_execution_context_t *ec, rb_ractor_t *recv_r) -{ - RUBY_DEBUG_LOG("from r:%u", rb_ractor_id(recv_r)); - VALUE v; - rb_ractor_t *cr = rb_ec_ractor_ptr(ec); - rb_thread_t *cur_th = rb_ec_thread_ptr(ec); - - struct rb_ractor_basket take_basket = { - .type.e = basket_type_none, - .sender = 0, - }; - - ractor_register_take(cr, cur_th, recv_r, &take_basket, true, NULL, false); - - while (UNDEF_P(v = ractor_try_take(cr, cur_th, recv_r, &take_basket))) { - ractor_wait_take(ec, cr, cur_th, recv_r, &take_basket); - } - - VM_ASSERT(!basket_none_p(&take_basket)); // might be, for ex, basket_type_copy - VM_ASSERT(!ractor_check_specific_take_basket_lock(recv_r, &take_basket)); - - return v; -} - -// Ractor.yield - -static bool -ractor_check_take_basket(rb_ractor_t *cr, struct rb_ractor_queue *rs) -{ - ASSERT_ractor_locking(cr); - - for (int i=0; icnt; i++) { - struct rb_ractor_basket *b = ractor_queue_at(cr, rs, i); - if (basket_type_p(b, basket_type_take_basket) && - basket_none_p(b->p.take.basket)) { - return true; - } - } - - return false; -} - -// Find another ractor that is taking from this ractor, so we can yield to it -static bool -ractor_deq_take_basket(rb_ractor_t *cr, struct rb_ractor_queue *rs, struct rb_ractor_basket *b) -{ - ASSERT_ractor_unlocking(cr); - struct rb_ractor_basket *first_tb = NULL; - bool found = false; - - RACTOR_LOCK_SELF(cr); - { - while (ractor_queue_deq(cr, rs, b)) { - if (basket_type_p(b, basket_type_take_basket)) { // some other ractor is taking - struct rb_ractor_basket *tb = b->p.take.basket; - - if (RUBY_ATOMIC_CAS(tb->type.atomic, basket_type_none, basket_type_yielding) == basket_type_none) { - found = true; // payload basket is now "yielding" type - break; - } - else { - ractor_queue_enq(cr, rs, b); - if (first_tb == NULL) first_tb = tb; - struct rb_ractor_basket *head = ractor_queue_head(cr, rs); - VM_ASSERT(head != NULL); - if (basket_type_p(head, basket_type_take_basket) && head->p.take.basket == first_tb) { - break; // loop detected - } - } - } - else { - VM_ASSERT(basket_none_p(b)); - } - } - - if (found && b->p.take.config && !b->p.take.config->oneshot) { - ractor_queue_enq(cr, rs, b); - } - } - RACTOR_UNLOCK_SELF(cr); - - return found; -} - -// Try yielding to a taking ractor -static bool -ractor_try_yield(rb_execution_context_t *ec, rb_ractor_t *cr, struct rb_ractor_queue *ts, volatile VALUE obj, VALUE move, bool exc, bool is_will) -{ - // Don't lock yielding ractor at same time as taking ractor. This could deadlock due to timing - // issue because we don't have a lock hierarchy. - ASSERT_ractor_unlocking(cr); - rb_thread_t *cur_th = rb_ec_thread_ptr(ec); - - struct rb_ractor_basket b; - - if (ractor_deq_take_basket(cr, ts, &b)) { // deq a take basket from takers queue of `cr` into `b` - VM_ASSERT(basket_type_p(&b, basket_type_take_basket)); - VM_ASSERT(basket_type_p(b.p.take.basket, basket_type_yielding)); - - rb_ractor_t *tr = RACTOR_PTR(b.sender); // taking ractor - rb_thread_t *tr_th = b.sending_th; // taking thread - struct rb_ractor_basket *tb = b.p.take.basket; // payload basket - enum rb_ractor_basket_type type; - - RUBY_DEBUG_LOG("basket from r:%u", rb_ractor_id(tr)); - - if (is_will) { - type = basket_type_will; // last message - } - else { - enum ruby_tag_type state; - - // begin - EC_PUSH_TAG(ec); - if ((state = EC_EXEC_TAG()) == TAG_NONE) { - // TODO: Ractor local GC - ractor_basket_prepare_contents(obj, move, &obj, &type); - } - EC_POP_TAG(); - // rescue ractor copy/move error, then re-raise - if (state) { - RACTOR_LOCK_SELF(cr); - { - b.p.take.basket->type.e = basket_type_none; - ractor_queue_enq(cr, ts, &b); - } - RACTOR_UNLOCK_SELF(cr); - EC_JUMP_TAG(ec, state); - } - } - - RACTOR_LOCK(tr); - { - VM_ASSERT(basket_type_p(tb, basket_type_yielding)); - // fill atomic - RUBY_DEBUG_LOG("fill %sbasket from r:%u", is_will ? "will " : "", rb_ractor_id(tr)); - ractor_basket_fill_(cr, cur_th, tb, obj, exc); // fill the take basket payload - if (RUBY_ATOMIC_CAS(tb->type.atomic, basket_type_yielding, type) != basket_type_yielding) { - rb_bug("unreachable"); - } - ractor_wakeup(tr, tr_th, wait_taking, wakeup_by_yield); - } - RACTOR_UNLOCK(tr); - - return true; - } - else if (cr->sync.outgoing_port_closed) { - rb_raise(rb_eRactorClosedError, "The outgoing-port is already closed"); - } - else { - RUBY_DEBUG_LOG("no take basket"); - return false; - } -} - -static void -ractor_wait_yield(rb_execution_context_t *ec, rb_ractor_t *cr, struct rb_ractor_queue *ts) -{ - rb_thread_t *cur_th = rb_ec_thread_ptr(ec); - RACTOR_LOCK_SELF(cr); - { - while (!ractor_check_take_basket(cr, ts) && !cr->sync.outgoing_port_closed) { - ractor_sleep(ec, cr, cur_th, wait_yielding); - } - } - RACTOR_UNLOCK_SELF(cr); -} - -// In order to yield, we wait until our takers queue has at least one element. Then, we wakeup a taker. -static VALUE -ractor_yield(rb_execution_context_t *ec, rb_ractor_t *cr, VALUE obj, VALUE move) -{ - struct rb_ractor_queue *ts = &cr->sync.takers_queue; - - while (!ractor_try_yield(ec, cr, ts, obj, move, false, false)) { - ractor_wait_yield(ec, cr, ts); - } - - return Qnil; -} - -// Ractor::Selector - -struct rb_ractor_selector { - rb_ractor_t *r; - struct rb_ractor_basket take_basket; - st_table *take_ractors; // rb_ractor_t * => (struct rb_ractor_selector_take_config *) -}; - -static int -ractor_selector_mark_ractors_i(st_data_t key, st_data_t value, st_data_t data) -{ - const rb_ractor_t *r = (rb_ractor_t *)key; - rb_gc_mark(r->pub.self); - return ST_CONTINUE; -} - -static void -ractor_selector_mark(void *ptr) -{ - struct rb_ractor_selector *s = ptr; - - if (s->take_ractors) { - st_foreach(s->take_ractors, ractor_selector_mark_ractors_i, 0); - } - - switch (s->take_basket.type.e) { - case basket_type_ref: - case basket_type_copy: - case basket_type_move: - case basket_type_will: - rb_gc_mark(s->take_basket.sender); - rb_gc_mark(s->take_basket.p.send.v); - break; - default: - break; - } -} - -static int -ractor_selector_release_i(st_data_t key, st_data_t val, st_data_t data) -{ - struct rb_ractor_selector *s = (struct rb_ractor_selector *)data; - struct rb_ractor_selector_take_config *config = (struct rb_ractor_selector_take_config *)val; - - if (!config->closed) { - ractor_deregister_take((rb_ractor_t *)key, &s->take_basket); - } - free(config); - return ST_CONTINUE; -} - -static void -ractor_selector_free(void *ptr) -{ - struct rb_ractor_selector *s = ptr; - st_foreach(s->take_ractors, ractor_selector_release_i, (st_data_t)s); - st_free_table(s->take_ractors); - ruby_xfree(ptr); -} - -static size_t -ractor_selector_memsize(const void *ptr) -{ - const struct rb_ractor_selector *s = ptr; - return sizeof(struct rb_ractor_selector) + - st_memsize(s->take_ractors) + - s->take_ractors->num_entries * sizeof(struct rb_ractor_selector_take_config); -} - -static const rb_data_type_t ractor_selector_data_type = { - "ractor/selector", - { - ractor_selector_mark, - ractor_selector_free, - ractor_selector_memsize, - NULL, // update - }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, -}; - -static struct rb_ractor_selector * -RACTOR_SELECTOR_PTR(VALUE selv) -{ - VM_ASSERT(rb_typeddata_is_kind_of(selv, &ractor_selector_data_type)); - - return (struct rb_ractor_selector *)DATA_PTR(selv); -} - -// Ractor::Selector.new - -static VALUE -ractor_selector_create(VALUE klass) -{ - struct rb_ractor_selector *s; - VALUE selv = TypedData_Make_Struct(klass, struct rb_ractor_selector, &ractor_selector_data_type, s); - s->take_basket.type.e = basket_type_reserved; - s->take_ractors = st_init_numtable(); // ractor (ptr) -> take_config - return selv; -} - -// Ractor::Selector#add(r) - -/* - * call-seq: - * add(ractor) -> ractor - * - * Adds _ractor_ to +self+. Raises an exception if _ractor_ is already added. - * Returns _ractor_. - */ -static VALUE -ractor_selector_add(VALUE selv, VALUE rv) -{ - if (!rb_ractor_p(rv)) { - rb_raise(rb_eArgError, "Not a ractor object"); - } - - rb_ractor_t *r = RACTOR_PTR(rv); - struct rb_ractor_selector *s = RACTOR_SELECTOR_PTR(selv); - - if (st_lookup(s->take_ractors, (st_data_t)r, NULL)) { - rb_raise(rb_eArgError, "already added"); - } - - struct rb_ractor_selector_take_config *config = malloc(sizeof(struct rb_ractor_selector_take_config)); - VM_ASSERT(config != NULL); - config->closed = false; - config->oneshot = false; - - if (ractor_register_take(GET_RACTOR(), GET_THREAD(), r, &s->take_basket, false, config, true)) { - st_insert(s->take_ractors, (st_data_t)r, (st_data_t)config); - } - - return rv; -} - -// Ractor::Selector#remove(r) - -/* call-seq: - * remove(ractor) -> ractor - * - * Removes _ractor_ from +self+. Raises an exception if _ractor_ is not added. - * Returns the removed _ractor_. - */ -static VALUE -ractor_selector_remove(VALUE selv, VALUE rv) -{ - if (!rb_ractor_p(rv)) { - rb_raise(rb_eArgError, "Not a ractor object"); - } - - rb_ractor_t *r = RACTOR_PTR(rv); - struct rb_ractor_selector *s = RACTOR_SELECTOR_PTR(selv); - - RUBY_DEBUG_LOG("r:%u", rb_ractor_id(r)); - - if (!st_lookup(s->take_ractors, (st_data_t)r, NULL)) { - rb_raise(rb_eArgError, "not added yet"); - } - - ractor_deregister_take(r, &s->take_basket); - struct rb_ractor_selector_take_config *config; - st_delete(s->take_ractors, (st_data_t *)&r, (st_data_t *)&config); - free(config); - - return rv; -} - -// Ractor::Selector#clear - -struct ractor_selector_clear_data { - VALUE selv; - rb_execution_context_t *ec; -}; - -static int -ractor_selector_clear_i(st_data_t key, st_data_t val, st_data_t data) -{ - VALUE selv = (VALUE)data; - rb_ractor_t *r = (rb_ractor_t *)key; - ractor_selector_remove(selv, r->pub.self); - return ST_CONTINUE; -} - -/* - * call-seq: - * clear -> self - * - * Removes all ractors from +self+. Raises +self+. - */ -static VALUE -ractor_selector_clear(VALUE selv) -{ - struct rb_ractor_selector *s = RACTOR_SELECTOR_PTR(selv); - - st_foreach(s->take_ractors, ractor_selector_clear_i, (st_data_t)selv); - st_clear(s->take_ractors); - return selv; -} - -/* - * call-seq: - * empty? -> true or false - * - * Returns +true+ if no ractor is added. - */ -static VALUE -ractor_selector_empty_p(VALUE selv) -{ - struct rb_ractor_selector *s = RACTOR_SELECTOR_PTR(selv); - return s->take_ractors->num_entries == 0 ? Qtrue : Qfalse; -} - -static int -ractor_selector_wait_i(st_data_t key, st_data_t val, st_data_t dat) -{ - rb_ractor_t *r = (rb_ractor_t *)key; - struct rb_ractor_basket *tb = (struct rb_ractor_basket *)dat; - int ret; - - if (!basket_none_p(tb)) { - RUBY_DEBUG_LOG("already taken:%s", basket_type_name(tb->type.e)); - return ST_STOP; - } - - RACTOR_LOCK(r); - { - if (basket_type_p(&r->sync.will_basket, basket_type_will)) { - RUBY_DEBUG_LOG("r:%u has will", rb_ractor_id(r)); - - if (RUBY_ATOMIC_CAS(tb->type.atomic, basket_type_none, basket_type_will) == basket_type_none) { - ractor_take_will(r, tb); - ret = ST_STOP; - } - else { - RUBY_DEBUG_LOG("has will, but already taken (%s)", basket_type_name(tb->type.e)); - ret = ST_CONTINUE; - } - } - else if (r->sync.outgoing_port_closed) { - RUBY_DEBUG_LOG("r:%u is closed", rb_ractor_id(r)); - - if (RUBY_ATOMIC_CAS(tb->type.atomic, basket_type_none, basket_type_deleted) == basket_type_none) { - tb->sender = r->pub.self; - ret = ST_STOP; - } - else { - RUBY_DEBUG_LOG("closed, but already taken (%s)", basket_type_name(tb->type.e)); - ret = ST_CONTINUE; - } - } - else { - RUBY_DEBUG_LOG("wakeup r:%u", rb_ractor_id(r)); - ractor_wakeup(r, NULL, wait_yielding, wakeup_by_take); - ret = ST_CONTINUE; - } - } - RACTOR_UNLOCK(r); - - return ret; -} - -// Ractor::Selector#wait - -// cleanup function, cr is unlocked -static void -ractor_selector_wait_cleanup(rb_ractor_t *cr, void *ptr) -{ - struct rb_ractor_basket *tb = (struct rb_ractor_basket *)ptr; - - RACTOR_LOCK_SELF(cr); - { - while (basket_type_p(tb, basket_type_yielding)) { - RACTOR_UNLOCK_SELF(cr); - { - rb_thread_sleep(0); - } - RACTOR_LOCK_SELF(cr); - } - // if tb->type is not none, taking is succeeded, but interruption ignore it unfortunately. - tb->type.e = basket_type_reserved; - } - RACTOR_UNLOCK_SELF(cr); -} - -/* :nodoc: */ -static VALUE -ractor_selector__wait(VALUE selv, VALUE do_receivev, VALUE do_yieldv, VALUE yield_value, VALUE move) -{ - rb_execution_context_t *ec = GET_EC(); - struct rb_ractor_selector *s = RACTOR_SELECTOR_PTR(selv); - struct rb_ractor_basket *tb = &s->take_basket; - struct rb_ractor_basket taken_basket; - rb_ractor_t *cr = rb_ec_ractor_ptr(ec); - rb_thread_t *cur_th = rb_ec_thread_ptr(ec); - bool do_receive = !!RTEST(do_receivev); - bool do_yield = !!RTEST(do_yieldv); - VALUE ret_v, ret_r; - enum rb_ractor_wait_status wait_status; - struct rb_ractor_queue *rq = &cr->sync.recv_queue; - struct rb_ractor_queue *ts = &cr->sync.takers_queue; - - RUBY_DEBUG_LOG("start"); - - retry: - RUBY_DEBUG_LOG("takers:%ld", s->take_ractors->num_entries); - - // setup wait_status - wait_status = wait_none; - if (s->take_ractors->num_entries > 0) wait_status |= wait_taking; - if (do_receive) wait_status |= wait_receiving; - if (do_yield) wait_status |= wait_yielding; - - RUBY_DEBUG_LOG("wait:%s", wait_status_str(wait_status)); - - if (wait_status == wait_none) { - rb_raise(rb_eRactorError, "no taking ractors"); - } - - // check recv_queue - if (do_receive && !UNDEF_P(ret_v = ractor_try_receive(ec, cr, rq))) { - ret_r = ID2SYM(rb_intern("receive")); - goto success; - } - - // check takers - if (do_yield && ractor_try_yield(ec, cr, ts, yield_value, move, false, false)) { - ret_v = Qnil; - ret_r = ID2SYM(rb_intern("yield")); - goto success; - } - - // check take_basket - VM_ASSERT(basket_type_p(&s->take_basket, basket_type_reserved)); - s->take_basket.type.e = basket_type_none; - // kick all take target ractors - st_foreach(s->take_ractors, ractor_selector_wait_i, (st_data_t)tb); - - RACTOR_LOCK_SELF(cr); - { - retry_waiting: - while (1) { - if (!basket_none_p(tb)) { - RUBY_DEBUG_LOG("taken:%s from r:%u", basket_type_name(tb->type.e), - tb->sender ? rb_ractor_id(RACTOR_PTR(tb->sender)) : 0); - break; - } - if (do_receive && !ractor_queue_empty_p(cr, rq)) { - RUBY_DEBUG_LOG("can receive (%d)", rq->cnt); - break; - } - if (do_yield && ractor_check_take_basket(cr, ts)) { - RUBY_DEBUG_LOG("can yield"); - break; - } - - ractor_sleep_with_cleanup(ec, cr, cur_th, wait_status, ractor_selector_wait_cleanup, tb); - } - - taken_basket = *tb; - - // ensure - // tb->type.e = basket_type_reserved # do it atomic in the following code - if (taken_basket.type.e == basket_type_yielding || - RUBY_ATOMIC_CAS(tb->type.atomic, taken_basket.type.e, basket_type_reserved) != taken_basket.type.e) { - - if (basket_type_p(tb, basket_type_yielding)) { - RACTOR_UNLOCK_SELF(cr); - { - rb_thread_sleep(0); - } - RACTOR_LOCK_SELF(cr); - } - goto retry_waiting; - } - } - RACTOR_UNLOCK_SELF(cr); - - // check the taken result - switch (taken_basket.type.e) { - case basket_type_none: - VM_ASSERT(do_receive || do_yield); - goto retry; - case basket_type_yielding: - rb_bug("unreachable"); - case basket_type_deleted: { - ractor_selector_remove(selv, taken_basket.sender); - - rb_ractor_t *r = RACTOR_PTR(taken_basket.sender); - if (ractor_take_will_lock(r, &taken_basket)) { - RUBY_DEBUG_LOG("has_will"); - } - else { - RUBY_DEBUG_LOG("no will"); - // rb_raise(rb_eRactorClosedError, "The outgoing-port is already closed"); - // remove and retry wait - goto retry; - } - break; - } - case basket_type_will: - // no more messages - ractor_selector_remove(selv, taken_basket.sender); - break; - default: - break; - } - - RUBY_DEBUG_LOG("taken_basket:%s", basket_type_name(taken_basket.type.e)); - - ret_v = ractor_basket_accept(&taken_basket); - ret_r = taken_basket.sender; - success: - return rb_ary_new_from_args(2, ret_r, ret_v); -} - -/* - * call-seq: - * wait(receive: false, yield_value: undef, move: false) -> [ractor, value] - * - * Waits until any ractor in _selector_ can be active. - */ -static VALUE -ractor_selector_wait(int argc, VALUE *argv, VALUE selector) -{ - VALUE options; - ID keywords[3]; - VALUE values[3]; - - keywords[0] = rb_intern("receive"); - keywords[1] = rb_intern("yield_value"); - keywords[2] = rb_intern("move"); - - rb_scan_args(argc, argv, "0:", &options); - rb_get_kwargs(options, keywords, 0, numberof(values), values); - return ractor_selector__wait(selector, - values[0] == Qundef ? Qfalse : RTEST(values[0]), - values[1] != Qundef, values[1], values[2]); -} - -static VALUE -ractor_selector_new(int argc, VALUE *ractors, VALUE klass) -{ - VALUE selector = ractor_selector_create(klass); - - for (int i=0; isync.incoming_port_closed) { - prev = Qfalse; - r->sync.incoming_port_closed = true; - if (ractor_wakeup(r, r_th, wait_receiving, wakeup_by_close)) { - VM_ASSERT(ractor_queue_empty_p(r, &r->sync.recv_queue)); - RUBY_DEBUG_LOG("cancel receiving"); - } - } - else { - prev = Qtrue; - } - } - RACTOR_UNLOCK(r); - return prev; -} - -// Ractor#close_outgoing - -static VALUE -ractor_close_outgoing(rb_execution_context_t *ec, rb_ractor_t *r) -{ - VALUE prev; - - RACTOR_LOCK(r); - { - struct rb_ractor_queue *ts = &r->sync.takers_queue; - rb_ractor_t *tr; - struct rb_ractor_basket b; - - if (!r->sync.outgoing_port_closed) { - prev = Qfalse; - r->sync.outgoing_port_closed = true; - } - else { - VM_ASSERT(ractor_queue_empty_p(r, ts)); - prev = Qtrue; - } - - // wakeup all taking ractors - while (ractor_queue_deq(r, ts, &b)) { - if (basket_type_p(&b, basket_type_take_basket)) { - tr = RACTOR_PTR(b.sender); - rb_thread_t *tr_th = b.sending_th; - struct rb_ractor_basket *tb = b.p.take.basket; - - if (RUBY_ATOMIC_CAS(tb->type.atomic, basket_type_none, basket_type_yielding) == basket_type_none) { - b.p.take.basket->sender = r->pub.self; - if (RUBY_ATOMIC_CAS(tb->type.atomic, basket_type_yielding, basket_type_deleted) != basket_type_yielding) { - rb_bug("unreachable"); - } - RUBY_DEBUG_LOG("set delete for r:%u", rb_ractor_id(RACTOR_PTR(b.sender))); - } - - if (b.p.take.config) { - b.p.take.config->closed = true; - } - - // TODO: deadlock-able? - RACTOR_LOCK(tr); - { - ractor_wakeup(tr, tr_th, wait_taking, wakeup_by_close); - } - RACTOR_UNLOCK(tr); - } - } - - // raising yielding Ractor - ractor_wakeup(r, NULL, wait_yielding, wakeup_by_close); - - VM_ASSERT(ractor_queue_empty_p(r, ts)); - } - RACTOR_UNLOCK(r); - return prev; -} +#include "ractor_sync.c" // creation/termination @@ -2174,9 +445,7 @@ rb_ractor_terminate_atfork(rb_vm_t *vm, rb_ractor_t *r) rb_gc_ractor_cache_free(r->newobj_cache); r->newobj_cache = NULL; r->status_ = ractor_terminated; - r->sync.outgoing_port_closed = true; - r->sync.incoming_port_closed = true; - r->sync.will_basket.type.e = basket_type_none; + ractor_sync_terminate_atfork(vm, r); } #endif @@ -2193,15 +462,7 @@ rb_ractor_living_threads_init(rb_ractor_t *r) static void ractor_init(rb_ractor_t *r, VALUE name, VALUE loc) { - ractor_queue_setup(&r->sync.recv_queue); - ractor_queue_setup(&r->sync.takers_queue); - rb_native_mutex_initialize(&r->sync.lock); - rb_native_cond_initialize(&r->barrier_wait_cond); - -#ifdef RUBY_THREAD_WIN32_H - rb_native_cond_initialize(&r->barrier_wait_cond); -#endif - ccan_list_head_init(&r->sync.wait.waiting_threads); + ractor_sync_init(r); // thread management rb_thread_sched_init(&r->threads.sched, false); @@ -2225,11 +486,13 @@ ractor_init(rb_ractor_t *r, VALUE name, VALUE loc) void rb_ractor_main_setup(rb_vm_t *vm, rb_ractor_t *r, rb_thread_t *th) { - r->pub.self = TypedData_Wrap_Struct(rb_cRactor, &ractor_data_type, r); + VALUE rv = r->pub.self = TypedData_Wrap_Struct(rb_cRactor, &ractor_data_type, r); FL_SET_RAW(r->pub.self, RUBY_FL_SHAREABLE); ractor_init(r, Qnil, Qnil); r->threads.main = th; rb_ractor_living_threads_insert(r, th); + + RB_GC_GUARD(rv); } static VALUE @@ -2254,84 +517,52 @@ ractor_create(rb_execution_context_t *ec, VALUE self, VALUE loc, VALUE name, VAL return rv; } +#if 0 static VALUE ractor_create_func(VALUE klass, VALUE loc, VALUE name, VALUE args, rb_block_call_func_t func) { VALUE block = rb_proc_new(func, Qnil); return ractor_create(rb_current_ec_noinline(), klass, loc, name, args, block); } +#endif static void -ractor_yield_atexit(rb_execution_context_t *ec, rb_ractor_t *cr, VALUE v, bool exc) +ractor_atexit(rb_execution_context_t *ec, rb_ractor_t *cr, VALUE result, bool exc) { - if (cr->sync.outgoing_port_closed) { - return; - } - - ASSERT_ractor_unlocking(cr); - - struct rb_ractor_queue *ts = &cr->sync.takers_queue; - rb_thread_t *cur_th = rb_ec_thread_ptr(ec); - - retry: - if (ractor_try_yield(ec, cr, ts, v, Qfalse, exc, true)) { - // OK. - } - else { - bool retry = false; - RACTOR_LOCK(cr); - { - if (!ractor_check_take_basket(cr, ts)) { - VM_ASSERT(cur_th->ractor_waiting.wait_status == wait_none); - RUBY_DEBUG_LOG("leave a will"); - ractor_basket_fill_will(cr, cur_th, &cr->sync.will_basket, v, exc); - } - else { - RUBY_DEBUG_LOG("rare timing!"); - retry = true; // another ractor is waiting for the yield. - } - } - RACTOR_UNLOCK(cr); - - if (retry) goto retry; - } + ractor_notify_exit(ec, cr, result, exc); } void rb_ractor_atexit(rb_execution_context_t *ec, VALUE result) { rb_ractor_t *cr = rb_ec_ractor_ptr(ec); - ractor_yield_atexit(ec, cr, result, false); + ractor_atexit(ec, cr, result, false); } void rb_ractor_atexit_exception(rb_execution_context_t *ec) { rb_ractor_t *cr = rb_ec_ractor_ptr(ec); - ractor_yield_atexit(ec, cr, ec->errinfo, true); + ractor_atexit(ec, cr, ec->errinfo, true); } void rb_ractor_teardown(rb_execution_context_t *ec) { rb_ractor_t *cr = rb_ec_ractor_ptr(ec); - ractor_close_incoming(ec, cr); - ractor_close_outgoing(ec, cr); // sync with rb_ractor_terminate_interrupt_main_thread() - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { VM_ASSERT(cr->threads.main != NULL); cr->threads.main = NULL; } - RB_VM_LOCK_LEAVE(); } void rb_ractor_receive_parameters(rb_execution_context_t *ec, rb_ractor_t *r, int len, VALUE *ptr) { for (int i=0; i blocking rb_vm_t *vm = GET_VM(); - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { rb_vm_ractor_blocking_cnt_inc(vm, cr, file, line); } - RB_VM_LOCK_LEAVE(); } } @@ -2513,11 +742,9 @@ rb_ractor_blocking_threads_dec(rb_ractor_t *cr, const char *file, int line) if (cr->threads.cnt == cr->threads.blocking_cnt) { rb_vm_t *vm = GET_VM(); - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { rb_vm_ractor_blocking_cnt_dec(vm, cr, __FILE__, __LINE__); } - RB_VM_LOCK_LEAVE(); } cr->threads.blocking_cnt--; @@ -2647,35 +874,6 @@ ractor_moved_missing(int argc, VALUE *argv, VALUE self) rb_raise(rb_eRactorMovedError, "can not send any methods to a moved object"); } -#ifndef USE_RACTOR_SELECTOR -#define USE_RACTOR_SELECTOR 0 -#endif - -RUBY_SYMBOL_EXPORT_BEGIN -void rb_init_ractor_selector(void); -RUBY_SYMBOL_EXPORT_END - -/* - * Document-class: Ractor::Selector - * :nodoc: currently - * - * Selects multiple Ractors to be activated. - */ -void -rb_init_ractor_selector(void) -{ - rb_cRactorSelector = rb_define_class_under(rb_cRactor, "Selector", rb_cObject); - rb_undef_alloc_func(rb_cRactorSelector); - - rb_define_singleton_method(rb_cRactorSelector, "new", ractor_selector_new , -1); - rb_define_method(rb_cRactorSelector, "add", ractor_selector_add, 1); - rb_define_method(rb_cRactorSelector, "remove", ractor_selector_remove, 1); - rb_define_method(rb_cRactorSelector, "clear", ractor_selector_clear, 0); - rb_define_method(rb_cRactorSelector, "empty?", ractor_selector_empty_p, 0); - rb_define_method(rb_cRactorSelector, "wait", ractor_selector_wait, -1); - rb_define_method(rb_cRactorSelector, "_wait", ractor_selector__wait, 4); -} - /* * Document-class: Ractor::ClosedError * @@ -2796,11 +994,7 @@ Init_Ractor(void) rb_define_method(rb_cRactorMovedObject, "instance_eval", ractor_moved_missing, -1); rb_define_method(rb_cRactorMovedObject, "instance_exec", ractor_moved_missing, -1); - // internal - -#if USE_RACTOR_SELECTOR - rb_init_ractor_selector(); -#endif + Init_RactorPort(); } void @@ -2994,6 +1188,7 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data) // already traversed return 0; } + RB_OBJ_WRITTEN(data->rec_hash, Qundef, obj); struct obj_traverse_callback_data d = { .stop = false, @@ -3066,11 +1261,9 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data) .stop = false, .data = data, }; - RB_VM_LOCK_ENTER_NO_BARRIER(); - { + RB_VM_LOCKING_NO_BARRIER() { rb_objspace_reachable_objects_from(obj, obj_traverse_reachable_i, &d); } - RB_VM_LOCK_LEAVE_NO_BARRIER(); if (d.stop) return 1; } break; @@ -3161,12 +1354,29 @@ make_shareable_check_shareable(VALUE obj) return traverse_cont; } else { - rb_raise(rb_eRactorError, "can not make shareable object for %"PRIsVALUE, obj); + rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE, obj); } } - if (RB_TYPE_P(obj, T_IMEMO)) { + switch (TYPE(obj)) { + case T_IMEMO: return traverse_skip; + case T_OBJECT: + { + // If a T_OBJECT is shared and has no free capacity, we can't safely store the object_id inline, + // as it would require to move the object content into an external buffer. + // This is only a problem for T_OBJECT, given other types have external fields and can do RCU. + // To avoid this issue, we proactively create the object_id. + shape_id_t shape_id = RBASIC_SHAPE_ID(obj); + attr_index_t capacity = RSHAPE_CAPACITY(shape_id); + attr_index_t free_capacity = capacity - RSHAPE_LEN(shape_id); + if (!rb_shape_has_object_id(shape_id) && capacity && !free_capacity) { + rb_obj_id(obj); + } + } + break; + default: + break; } if (!RB_OBJ_FROZEN_RAW(obj)) { @@ -3405,11 +1615,9 @@ static int obj_refer_only_shareables_p(VALUE obj) { int cnt = 0; - RB_VM_LOCK_ENTER_NO_BARRIER(); - { + RB_VM_LOCKING_NO_BARRIER() { rb_objspace_reachable_objects_from(obj, obj_refer_only_shareables_p_i, &cnt); } - RB_VM_LOCK_LEAVE_NO_BARRIER(); return cnt == 0; } @@ -3437,30 +1645,32 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) } else { st_insert(obj_traverse_replace_rec(data), (st_data_t)obj, replacement); + RB_OBJ_WRITTEN(data->rec_hash, Qundef, obj); + RB_OBJ_WRITTEN(data->rec_hash, Qundef, replacement); } if (!data->move) { obj = replacement; } -#define CHECK_AND_REPLACE(v) do { \ +#define CHECK_AND_REPLACE(parent_obj, v) do { \ VALUE _val = (v); \ if (obj_traverse_replace_i(_val, data)) { return 1; } \ - else if (data->replacement != _val) { RB_OBJ_WRITE(obj, &v, data->replacement); } \ + else if (data->replacement != _val) { RB_OBJ_WRITE(parent_obj, &v, data->replacement); } \ } while (0) - if (UNLIKELY(FL_TEST_RAW(obj, FL_EXIVAR))) { - struct gen_fields_tbl *fields_tbl; - rb_ivar_generic_fields_tbl_lookup(obj, &fields_tbl); + if (UNLIKELY(rb_obj_exivar_p(obj))) { + VALUE fields_obj; + rb_ivar_generic_fields_tbl_lookup(obj, &fields_obj); if (UNLIKELY(rb_shape_obj_too_complex_p(obj))) { struct obj_traverse_replace_callback_data d = { .stop = false, .data = data, - .src = obj, + .src = fields_obj, }; rb_st_foreach_with_replace( - fields_tbl->as.complex.table, + rb_imemo_fields_complex_tbl(fields_obj), obj_iv_hash_traverse_replace_foreach_i, obj_iv_hash_traverse_replace_i, (st_data_t)&d @@ -3468,10 +1678,10 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) if (d.stop) return 1; } else { - for (uint32_t i = 0; i < fields_tbl->as.shape.fields_count; i++) { - if (!UNDEF_P(fields_tbl->as.shape.fields[i])) { - CHECK_AND_REPLACE(fields_tbl->as.shape.fields[i]); - } + uint32_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID(obj)); + VALUE *fields = rb_imemo_fields_ptr(fields_obj); + for (uint32_t i = 0; i < fields_count; i++) { + CHECK_AND_REPLACE(fields_obj, fields[i]); } } } @@ -3510,7 +1720,7 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) VALUE *ptr = ROBJECT_FIELDS(obj); for (uint32_t i = 0; i < len; i++) { - CHECK_AND_REPLACE(ptr[i]); + CHECK_AND_REPLACE(obj, ptr[i]); } } } @@ -3563,18 +1773,18 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) const VALUE *ptr = RSTRUCT_CONST_PTR(obj); for (long i=0; inum); - CHECK_AND_REPLACE(RRATIONAL(obj)->den); + CHECK_AND_REPLACE(obj, RRATIONAL(obj)->num); + CHECK_AND_REPLACE(obj, RRATIONAL(obj)->den); break; case T_COMPLEX: - CHECK_AND_REPLACE(RCOMPLEX(obj)->real); - CHECK_AND_REPLACE(RCOMPLEX(obj)->imag); + CHECK_AND_REPLACE(obj, RCOMPLEX(obj)->real); + CHECK_AND_REPLACE(obj, RCOMPLEX(obj)->imag); break; case T_DATA: @@ -3665,20 +1875,32 @@ move_enter(VALUE obj, struct obj_traverse_replace_data *data) static enum obj_traverse_iterator_result move_leave(VALUE obj, struct obj_traverse_replace_data *data) { - size_t size = rb_gc_obj_slot_size(obj); - memcpy((void *)data->replacement, (void *)obj, size); + // Copy flags + VALUE ignored_flags = RUBY_FL_PROMOTED; + RBASIC(data->replacement)->flags = (RBASIC(obj)->flags & ~ignored_flags) | (RBASIC(data->replacement)->flags & ignored_flags); + // Copy contents without the flags + memcpy( + (char *)data->replacement + sizeof(VALUE), + (char *)obj + sizeof(VALUE), + rb_gc_obj_slot_size(obj) - sizeof(VALUE) + ); + + // We've copied obj's references to the replacement + rb_gc_writebarrier_remember(data->replacement); void rb_replace_generic_ivar(VALUE clone, VALUE obj); // variable.c rb_gc_obj_id_moved(data->replacement); - if (UNLIKELY(FL_TEST_RAW(obj, FL_EXIVAR))) { + if (UNLIKELY(rb_obj_exivar_p(obj))) { rb_replace_generic_ivar(data->replacement, obj); } + VALUE flags = T_OBJECT | FL_FREEZE | ROBJECT_EMBED | (RBASIC(obj)->flags & FL_PROMOTED); + // Avoid mutations using bind_call, etc. - MEMZERO((char *)obj + sizeof(struct RBasic), char, size - sizeof(struct RBasic)); - RBASIC(obj)->flags = T_OBJECT | FL_FREEZE; + MEMZERO((char *)obj, char, sizeof(struct RBasic)); + RBASIC(obj)->flags = flags; RBASIC_SET_CLASS_RAW(obj, rb_cRactorMovedObject); return traverse_cont; } @@ -3837,15 +2059,13 @@ rb_ractor_local_storage_value_newkey(void) void rb_ractor_local_storage_delkey(rb_ractor_local_key_t key) { - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { if (freed_ractor_local_keys.cnt == freed_ractor_local_keys.capa) { freed_ractor_local_keys.capa = freed_ractor_local_keys.capa ? freed_ractor_local_keys.capa * 2 : 4; REALLOC_N(freed_ractor_local_keys.keys, rb_ractor_local_key_t, freed_ractor_local_keys.capa); } freed_ractor_local_keys.keys[freed_ractor_local_keys.cnt++] = key; } - RB_VM_LOCK_LEAVE(); } static bool @@ -4030,91 +2250,10 @@ ractor_local_value_store_if_absent(rb_execution_context_t *ec, VALUE self, VALUE return rb_mutex_synchronize(cr->local_storage_store_lock, ractor_local_value_store_i, (VALUE)&data); } -// Ractor::Channel (emulate with Ractor) - -typedef rb_ractor_t rb_ractor_channel_t; - -static VALUE -ractor_channel_func(RB_BLOCK_CALL_FUNC_ARGLIST(y, c)) -{ - rb_execution_context_t *ec = GET_EC(); - rb_ractor_t *cr = rb_ec_ractor_ptr(ec); - - while (1) { - int state; - - EC_PUSH_TAG(ec); - if ((state = EC_EXEC_TAG()) == TAG_NONE) { - VALUE obj = ractor_receive(ec, cr); - ractor_yield(ec, cr, obj, Qfalse); - } - EC_POP_TAG(); - - if (state) { - // ignore the error - break; - } - } - - return Qnil; -} - -static VALUE -rb_ractor_channel_new(void) -{ -#if 0 - return rb_funcall(rb_const_get(rb_cRactor, rb_intern("Channel")), rb_intern("new"), 0); -#else - // class Channel - // def self.new - // Ractor.new do # func body - // while true - // obj = Ractor.receive - // Ractor.yield obj - // end - // rescue Ractor::ClosedError - // nil - // end - // end - // end - - return ractor_create_func(rb_cRactor, Qnil, rb_str_new2("Ractor/channel"), rb_ary_new(), ractor_channel_func); -#endif -} - -static VALUE -rb_ractor_channel_yield(rb_execution_context_t *ec, VALUE vch, VALUE obj) -{ - VM_ASSERT(ec == rb_current_ec_noinline()); - rb_ractor_channel_t *ch = RACTOR_PTR(vch); - - ractor_send(ec, (rb_ractor_t *)ch, obj, Qfalse); - return Qnil; -} - -static VALUE -rb_ractor_channel_take(rb_execution_context_t *ec, VALUE vch) -{ - VM_ASSERT(ec == rb_current_ec_noinline()); - rb_ractor_channel_t *ch = RACTOR_PTR(vch); - - return ractor_take(ec, (rb_ractor_t *)ch); -} - -static VALUE -rb_ractor_channel_close(rb_execution_context_t *ec, VALUE vch) -{ - VM_ASSERT(ec == rb_current_ec_noinline()); - rb_ractor_channel_t *ch = RACTOR_PTR(vch); - - ractor_close_incoming(ec, (rb_ractor_t *)ch); - return ractor_close_outgoing(ec, (rb_ractor_t *)ch); -} - // Ractor#require struct cross_ractor_require { - VALUE ch; + VALUE port; VALUE result; VALUE exception; @@ -4124,6 +2263,30 @@ struct cross_ractor_require { // autoload VALUE module; ID name; + + bool silent; +}; + +static void +cross_ractor_require_mark(void *ptr) +{ + struct cross_ractor_require *crr = (struct cross_ractor_require *)ptr; + rb_gc_mark(crr->port); + rb_gc_mark(crr->result); + rb_gc_mark(crr->exception); + rb_gc_mark(crr->feature); + rb_gc_mark(crr->module); +} + +static const rb_data_type_t cross_ractor_require_data_type = { + "ractor/cross_ractor_require", + { + cross_ractor_require_mark, + RUBY_DEFAULT_FREE, + NULL, // memsize + NULL, // compact + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY }; static VALUE @@ -4133,7 +2296,14 @@ require_body(VALUE data) ID require; CONST_ID(require, "require"); - crr->result = rb_funcallv(Qnil, require, 1, &crr->feature); + + if (crr->silent) { + int rb_require_internal_silent(VALUE fname); + crr->result = INT2NUM(rb_require_internal_silent(crr->feature)); + } + else { + crr->result = rb_funcallv(Qnil, require, 1, &crr->feature); + } return Qnil; } @@ -4172,58 +2342,85 @@ require_result_copy_resuce(VALUE data, VALUE errinfo) } static VALUE -ractor_require_protect(struct cross_ractor_require *crr, VALUE (*func)(VALUE)) +ractor_require_protect(VALUE crr_obj, VALUE (*func)(VALUE)) { + struct cross_ractor_require *crr; + TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr); + + const bool silent = crr->silent; + VALUE debug, errinfo; + if (silent) { + debug = ruby_debug; + errinfo = rb_errinfo(); + } + // catch any error rb_rescue2(func, (VALUE)crr, require_rescue, (VALUE)crr, rb_eException, 0); + if (silent) { + ruby_debug = debug; + rb_set_errinfo(errinfo); + } + rb_rescue2(require_result_copy_body, (VALUE)crr, require_result_copy_resuce, (VALUE)crr, rb_eException, 0); - rb_ractor_channel_yield(GET_EC(), crr->ch, Qtrue); + ractor_port_send(GET_EC(), crr->port, Qtrue, Qfalse); + RB_GC_GUARD(crr_obj); return Qnil; - } static VALUE -ractore_require_func(void *data) +ractor_require_func(void *crr_obj) { - struct cross_ractor_require *crr = (struct cross_ractor_require *)data; - return ractor_require_protect(crr, require_body); + return ractor_require_protect((VALUE)crr_obj, require_body); } VALUE -rb_ractor_require(VALUE feature) +rb_ractor_require(VALUE feature, bool silent) { - // TODO: make feature shareable - struct cross_ractor_require crr = { - .feature = feature, // TODO: ractor - .ch = rb_ractor_channel_new(), - .result = Qundef, - .exception = Qundef, - }; + // We're about to block on the main ractor, so if we're holding the global lock we'll deadlock. + ASSERT_vm_unlocking(); + + struct cross_ractor_require *crr; + VALUE crr_obj = TypedData_Make_Struct(0, struct cross_ractor_require, &cross_ractor_require_data_type, crr); + FL_SET_RAW(crr_obj, RUBY_FL_SHAREABLE); + + // Convert feature to proper file path and make it shareable as fstring + crr->feature = rb_fstring(FilePathValue(feature)); + crr->port = ractor_port_new(GET_RACTOR()); + crr->result = Qundef; + crr->exception = Qundef; + crr->silent = silent; rb_execution_context_t *ec = GET_EC(); rb_ractor_t *main_r = GET_VM()->ractor.main_ractor; - rb_ractor_interrupt_exec(main_r, ractore_require_func, &crr, 0); + rb_ractor_interrupt_exec(main_r, ractor_require_func, (void *)crr_obj, rb_interrupt_exec_flag_value_data); // wait for require done - rb_ractor_channel_take(ec, crr.ch); - rb_ractor_channel_close(ec, crr.ch); + ractor_port_receive(ec, crr->port); + ractor_port_close(ec, crr->port); - if (crr.exception != Qundef) { - rb_exc_raise(crr.exception); + VALUE exc = crr->exception; + VALUE result = crr->result; + RB_GC_GUARD(crr_obj); + + if (exc != Qundef) { + ractor_reset_belonging(exc); + rb_exc_raise(exc); } else { - return crr.result; + RUBY_ASSERT(result != Qundef); + ractor_reset_belonging(result); + return result; } } static VALUE ractor_require(rb_execution_context_t *ec, VALUE self, VALUE feature) { - return rb_ractor_require(feature); + return rb_ractor_require(feature, false); } static VALUE @@ -4235,36 +2432,40 @@ autoload_load_body(VALUE data) } static VALUE -ractor_autoload_load_func(void *data) +ractor_autoload_load_func(void *crr_obj) { - struct cross_ractor_require *crr = (struct cross_ractor_require *)data; - return ractor_require_protect(crr, autoload_load_body); + return ractor_require_protect((VALUE)crr_obj, autoload_load_body); } VALUE rb_ractor_autoload_load(VALUE module, ID name) { - struct cross_ractor_require crr = { - .module = module, - .name = name, - .ch = rb_ractor_channel_new(), - .result = Qundef, - .exception = Qundef, - }; + struct cross_ractor_require *crr; + VALUE crr_obj = TypedData_Make_Struct(0, struct cross_ractor_require, &cross_ractor_require_data_type, crr); + FL_SET_RAW(crr_obj, RUBY_FL_SHAREABLE); + crr->module = module; + crr->name = name; + crr->port = ractor_port_new(GET_RACTOR()); + crr->result = Qundef; + crr->exception = Qundef; rb_execution_context_t *ec = GET_EC(); rb_ractor_t *main_r = GET_VM()->ractor.main_ractor; - rb_ractor_interrupt_exec(main_r, ractor_autoload_load_func, &crr, 0); + rb_ractor_interrupt_exec(main_r, ractor_autoload_load_func, (void *)crr_obj, rb_interrupt_exec_flag_value_data); // wait for require done - rb_ractor_channel_take(ec, crr.ch); - rb_ractor_channel_close(ec, crr.ch); + ractor_port_receive(ec, crr->port); + ractor_port_close(ec, crr->port); - if (crr.exception != Qundef) { - rb_exc_raise(crr.exception); + VALUE exc = crr->exception; + VALUE result = crr->result; + RB_GC_GUARD(crr_obj); + + if (exc != Qundef) { + rb_exc_raise(exc); } else { - return crr.result; + return result; } } diff --git a/ractor.rb b/ractor.rb index 3b649f042f..ee6135b81e 100644 --- a/ractor.rb +++ b/ractor.rb @@ -4,7 +4,7 @@ # # # The simplest ractor # r = Ractor.new {puts "I am in Ractor!"} -# r.take # wait for it to finish +# r.join # wait for it to finish # # Here, "I am in Ractor!" is printed # # Ractors do not share all objects with each other. There are two main benefits to this: across ractors, thread-safety @@ -36,53 +36,11 @@ # puts "I am in Ractor! a=#{a_in_ractor}" # end # r.send(a) # pass it -# r.take +# r.join # # Here, "I am in Ractor! a=1" is printed # -# There are two pairs of methods for sending/receiving messages: -# -# * Ractor#send and Ractor.receive for when the _sender_ knows the receiver (push); -# * Ractor.yield and Ractor#take for when the _receiver_ knows the sender (pull); -# # In addition to that, any arguments passed to Ractor.new are passed to the block and available there -# as if received by Ractor.receive, and the last block value is sent outside of the -# ractor as if sent by Ractor.yield. -# -# A little demonstration of a classic ping-pong: -# -# server = Ractor.new(name: "server") do -# puts "Server starts: #{self.inspect}" -# puts "Server sends: ping" -# Ractor.yield 'ping' # The server doesn't know the receiver and sends to whoever interested -# received = Ractor.receive # The server doesn't know the sender and receives from whoever sent -# puts "Server received: #{received}" -# end -# -# client = Ractor.new(server) do |srv| # The server is sent to the client, and available as srv -# puts "Client starts: #{self.inspect}" -# received = srv.take # The client takes a message from the server -# puts "Client received from " \ -# "#{srv.inspect}: #{received}" -# puts "Client sends to " \ -# "#{srv.inspect}: pong" -# srv.send 'pong' # The client sends a message to the server -# end -# -# [client, server].each(&:take) # Wait until they both finish -# -# This will output something like: -# -# Server starts: # -# Server sends: ping -# Client starts: # -# Client received from #: ping -# Client sends to #: pong -# Server received: pong -# -# Ractors receive their messages via the incoming port, and send them -# to the outgoing port. Either one can be disabled with Ractor#close_incoming and -# Ractor#close_outgoing, respectively. When a ractor terminates, its ports are closed -# automatically. +# as if received by Ractor.receive, and the last block value can be received with Ractor#value. # # == Shareable and unshareable objects # @@ -120,7 +78,7 @@ # puts "In ractor: #{data2.object_id}, #{data2[0].object_id}, #{data2[1].object_id}" # end # r.send(data) -# r.take +# r.join # puts "Outside : #{data.object_id}, #{data[0].object_id}, #{data[1].object_id}" # # This will output something like: @@ -142,7 +100,7 @@ # puts "In ractor: #{data_in_ractor.object_id}, #{data_in_ractor[0].object_id}" # end # r.send(data, move: true) -# r.take +# r.join # puts "Outside: moved? #{Ractor::MovedObject === data}" # puts "Outside: #{data.inspect}" # @@ -177,7 +135,7 @@ # puts "I can't see #{cls.tricky}" # cls.tricky = true # doesn't get here, but this would also raise an error # end -# r.take +# r.join # # I see C # # can not access instance variables of classes/modules from non-main Ractors (RuntimeError) # @@ -191,7 +149,7 @@ # puts "GOOD=#{GOOD}" # puts "BAD=#{BAD}" # end -# r.take +# r.join # # GOOD=good # # can not access non-shareable objects in constant Object::BAD by non-main Ractor. (NameError) # @@ -201,7 +159,7 @@ # puts "I see #{C}" # puts "I can't see #{C.tricky}" # end -# r.take +# r.join # # I see C # # can not access instance variables of classes/modules from non-main Ractors (RuntimeError) # @@ -217,7 +175,7 @@ # a = 1 # Thread.new {puts "Thread in ractor: a=#{a}"}.join # end -# r.take +# r.join # # Here "Thread in ractor: a=1" will be printed # # == Note on code examples @@ -230,7 +188,7 @@ # end # # It is **only for demonstration purposes** and shouldn't be used in a real code. -# Most of the time, #take is used to wait for ractors to finish. +# Most of the time, #join is used to wait for ractors to finish. # # == Reference # @@ -247,7 +205,7 @@ class Ractor # inside the block will refer to the current \Ractor. # # r = Ractor.new { puts "Hi, I am #{self.inspect}" } - # r.take + # r.join # # Prints "Hi, I am #" # # Any +args+ passed are propagated to the block arguments by the same rules as @@ -259,14 +217,14 @@ class Ractor # r = Ractor.new(arg) {|received_arg| # puts "Received: #{received_arg} (##{received_arg.object_id})" # } - # r.take + # r.join # # Prints: # # Passing: [1, 2, 3] (#280) # # Received: [1, 2, 3] (#300) # # Ractor's +name+ can be set for debugging purposes: # - # r = Ractor.new(name: 'my ractor') {}; r.take + # r = Ractor.new(name: 'my ractor') {}; r.join # p r # #=> # # @@ -294,10 +252,10 @@ class Ractor # Returns the number of Ractors currently running or blocking (waiting). # # Ractor.count #=> 1 - # r = Ractor.new(name: 'example') { Ractor.yield(1) } + # r = Ractor.new(name: 'example') { Ractor.receive } # Ractor.count #=> 2 (main + example ractor) - # r.take # wait for Ractor.yield(1) - # r.take # wait until r will finish + # r << 42 # r's Ractor.receive will resume + # r.join # wait for r's termination # Ractor.count #=> 1 def self.count __builtin_cexpr! %q{ @@ -307,130 +265,52 @@ class Ractor # # call-seq: - # Ractor.select(*ractors, [yield_value:, move: false]) -> [ractor or symbol, obj] + # Ractor.select(*ports) -> [...] # - # Wait for any ractor to have something in its outgoing port, read from this ractor, and - # then return that ractor and the object received. - # - # r1 = Ractor.new {Ractor.yield 'from 1'} - # r2 = Ractor.new {Ractor.yield 'from 2'} - # - # r, obj = Ractor.select(r1, r2) - # - # puts "received #{obj.inspect} from #{r.inspect}" - # # Prints: received "from 1" from # - # # But could just as well print "from r2" here, either prints could be first. - # - # If one of the given ractors is the current ractor, and it is selected, +r+ will contain - # the +:receive+ symbol instead of the ractor object. - # - # r1 = Ractor.new(Ractor.current) do |main| - # main.send 'to main' - # Ractor.yield 'from 1' - # end - # r2 = Ractor.new do - # Ractor.yield 'from 2' - # end - # - # r, obj = Ractor.select(r1, r2, Ractor.current) - # puts "received #{obj.inspect} from #{r.inspect}" - # # Could print: received "to main" from :receive - # - # If +yield_value+ is provided, that value may be yielded if another ractor is calling #take. - # In this case, the pair [:yield, nil] is returned: - # - # r1 = Ractor.new(Ractor.current) do |main| - # puts "Received from main: #{main.take}" - # end - # - # puts "Trying to select" - # r, obj = Ractor.select(r1, Ractor.current, yield_value: 123) - # wait - # puts "Received #{obj.inspect} from #{r.inspect}" - # - # This will print: - # - # Trying to select - # Received from main: 123 - # Received nil from :yield - # - # +move+ boolean flag defines whether yielded value will be copied (default) or moved. - def self.select(*ractors, yield_value: yield_unspecified = true, move: false) - raise ArgumentError, 'specify at least one ractor or `yield_value`' if yield_unspecified && ractors.empty? + # TBD + def self.select(*ports) + raise ArgumentError, 'specify at least one ractor or `yield_value`' if ports.empty? - if ractors.delete Ractor.current - do_receive = true - else - do_receive = false + monitors = {} # Ractor::Port => Ractor + + ports = ports.map do |arg| + case arg + when Ractor + port = Ractor::Port.new + monitors[port] = arg + arg.monitor port + port + when Ractor::Port + arg + else + raise ArgumentError, "should be Ractor::Port or Ractor" + end end - __builtin_ractor_select_internal ractors, do_receive, !yield_unspecified, yield_value, move + begin + result_port, obj = __builtin_ractor_select_internal(ports) + + if r = monitors[result_port] + [r, r.value] + else + [result_port, obj] + end + ensure + # close all ports for join + monitors.each do |port, r| + r.unmonitor port + port.close + end + end end # # call-seq: - # Ractor.receive -> msg - # - # Receive a message from the incoming port of the current ractor (which was - # sent there by #send from another ractor). - # - # r = Ractor.new do - # v1 = Ractor.receive - # puts "Received: #{v1}" - # end - # r.send('message1') - # r.take - # # Here will be printed: "Received: message1" - # - # Alternatively, the private instance method +receive+ may be used: - # - # r = Ractor.new do - # v1 = receive - # puts "Received: #{v1}" - # end - # r.send('message1') - # r.take - # # This prints: "Received: message1" - # - # The method blocks if the queue is empty. - # - # r = Ractor.new do - # puts "Before first receive" - # v1 = Ractor.receive - # puts "Received: #{v1}" - # v2 = Ractor.receive - # puts "Received: #{v2}" - # end - # wait - # puts "Still not received" - # r.send('message1') - # wait - # puts "Still received only one" - # r.send('message2') - # r.take - # - # Output: - # - # Before first receive - # Still not received - # Received: message1 - # Still received only one - # Received: message2 - # - # If close_incoming was called on the ractor, the method raises Ractor::ClosedError - # if there are no more messages in the incoming queue: - # - # Ractor.new do - # close_incoming - # receive - # end - # wait - # # in `receive': The incoming port is already closed => # (Ractor::ClosedError) + # Ractor.receive -> obj # + # Receive a message from the default port. def self.receive - __builtin_cexpr! %q{ - ractor_receive(ec, rb_ec_ractor_ptr(ec)) - } + Ractor.current.default_port.receive end class << self @@ -439,280 +319,21 @@ class Ractor # same as Ractor.receive private def receive - __builtin_cexpr! %q{ - ractor_receive(ec, rb_ec_ractor_ptr(ec)) - } + default_port.receive end alias recv receive # # call-seq: - # Ractor.receive_if {|msg| block } -> msg + # ractor.send(msg) -> self # - # Receive only a specific message. - # - # Instead of Ractor.receive, Ractor.receive_if can be given a pattern (or any - # filter) in a block and you can choose the messages to accept that are available in - # your ractor's incoming queue. - # - # r = Ractor.new do - # p Ractor.receive_if{|msg| msg.match?(/foo/)} #=> "foo3" - # p Ractor.receive_if{|msg| msg.match?(/bar/)} #=> "bar1" - # p Ractor.receive_if{|msg| msg.match?(/baz/)} #=> "baz2" - # end - # r << "bar1" - # r << "baz2" - # r << "foo3" - # r.take - # - # This will output: - # - # foo3 - # bar1 - # baz2 - # - # If the block returns a truthy value, the message is removed from the incoming queue - # and returned. - # Otherwise, the message remains in the incoming queue and the next messages are checked - # by the given block. - # - # If there are no messages left in the incoming queue, the method will - # block until new messages arrive. - # - # If the block is escaped by break/return/exception/throw, the message is removed from - # the incoming queue as if a truthy value had been returned. - # - # r = Ractor.new do - # val = Ractor.receive_if{|msg| msg.is_a?(Array)} - # puts "Received successfully: #{val}" - # end - # - # r.send(1) - # r.send('test') - # wait - # puts "2 non-matching sent, nothing received" - # r.send([1, 2, 3]) - # wait - # - # Prints: - # - # 2 non-matching sent, nothing received - # Received successfully: [1, 2, 3] - # - # Note that you can not call receive/receive_if in the given block recursively. - # You should not do any tasks in the block other than message filtration. - # - # Ractor.current << true - # Ractor.receive_if{|msg| Ractor.receive} - # #=> `receive': can not call receive/receive_if recursively (Ractor::Error) - # - def self.receive_if &b - Primitive.ractor_receive_if b - end - - # same as Ractor.receive_if - private def receive_if &b - Primitive.ractor_receive_if b - end - - # - # call-seq: - # ractor.send(msg, move: false) -> self - # - # Send a message to a Ractor's incoming queue to be accepted by Ractor.receive. - # - # r = Ractor.new do - # value = Ractor.receive - # puts "Received #{value}" - # end - # r.send 'message' - # # Prints: "Received: message" - # - # The method is non-blocking (will return immediately even if the ractor is not ready - # to receive anything): - # - # r = Ractor.new {sleep(5)} - # r.send('test') - # puts "Sent successfully" - # # Prints: "Sent successfully" immediately - # - # An attempt to send to a ractor which already finished its execution will raise Ractor::ClosedError. - # - # r = Ractor.new {} - # r.take - # p r - # # "#" - # r.send('test') - # # Ractor::ClosedError (The incoming-port is already closed) - # - # If close_incoming was called on the ractor, the method also raises Ractor::ClosedError. - # - # r = Ractor.new do - # sleep(500) - # receive - # end - # r.close_incoming - # r.send('test') - # # Ractor::ClosedError (The incoming-port is already closed) - # # The error is raised immediately, not when the ractor tries to receive - # - # If the +obj+ is unshareable, by default it will be copied into the receiving ractor by deep cloning. - # If move: true is passed, the object is _moved_ into the receiving ractor and becomes - # inaccessible to the sender. - # - # r = Ractor.new {puts "Received: #{receive}"} - # msg = 'message' - # r.send(msg, move: true) - # r.take - # p msg - # - # This prints: - # - # Received: message - # in `p': undefined method `inspect' for # - # - # All references to the object and its parts will become invalid to the sender. - # - # r = Ractor.new {puts "Received: #{receive}"} - # s = 'message' - # ary = [s] - # copy = ary.dup - # r.send(ary, move: true) - # - # s.inspect - # # Ractor::MovedError (can not send any methods to a moved object) - # ary.class - # # Ractor::MovedError (can not send any methods to a moved object) - # copy.class - # # => Array, it is different object - # copy[0].inspect - # # Ractor::MovedError (can not send any methods to a moved object) - # # ...but its item was still a reference to `s`, which was moved - # - # If the object is shareable, move: true has no effect on it: - # - # r = Ractor.new {puts "Received: #{receive}"} - # s = 'message'.freeze - # r.send(s, move: true) - # s.inspect #=> "message", still available - # - def send(obj, move: false) - __builtin_cexpr! %q{ - ractor_send(ec, RACTOR_PTR(self), obj, move) - } + # It is equivalent to default_port.send(msg) + def send(...) + default_port.send(...) + self end alias << send - # - # call-seq: - # Ractor.yield(msg, move: false) -> nil - # - # Send a message to the current ractor's outgoing port to be accepted by #take. - # - # r = Ractor.new {Ractor.yield 'Hello from ractor'} - # puts r.take - # # Prints: "Hello from ractor" - # - # This method is blocking, and will return only when somebody consumes the - # sent message. - # - # r = Ractor.new do - # Ractor.yield 'Hello from ractor' - # puts "Ractor: after yield" - # end - # wait - # puts "Still not taken" - # puts r.take - # - # This will print: - # - # Still not taken - # Hello from ractor - # Ractor: after yield - # - # If the outgoing port was closed with #close_outgoing, the method will raise: - # - # r = Ractor.new do - # close_outgoing - # Ractor.yield 'Hello from ractor' - # end - # wait - # # `yield': The outgoing-port is already closed (Ractor::ClosedError) - # - # The meaning of the +move+ argument is the same as for #send. - def self.yield(obj, move: false) - __builtin_cexpr! %q{ - ractor_yield(ec, rb_ec_ractor_ptr(ec), obj, move) - } - end - - # - # call-seq: - # ractor.take -> msg - # - # Get a message from the ractor's outgoing port, which was put there by Ractor.yield or at ractor's - # termination. - # - # r = Ractor.new do - # Ractor.yield 'explicit yield' - # 'last value' - # end - # puts r.take #=> 'explicit yield' - # puts r.take #=> 'last value' - # puts r.take # Ractor::ClosedError (The outgoing-port is already closed) - # - # The fact that the last value is also sent to the outgoing port means that +take+ can be used - # as an analog of Thread#join ("just wait until ractor finishes"). However, it will raise if - # somebody has already consumed that message. - # - # If the outgoing port was closed with #close_outgoing, the method will raise Ractor::ClosedError. - # - # r = Ractor.new do - # sleep(500) - # Ractor.yield 'Hello from ractor' - # end - # r.close_outgoing - # r.take - # # Ractor::ClosedError (The outgoing-port is already closed) - # # The error would be raised immediately, not when ractor will try to receive - # - # If an uncaught exception is raised in the Ractor, it is propagated by take as a - # Ractor::RemoteError. - # - # r = Ractor.new {raise "Something weird happened"} - # - # begin - # r.take - # rescue => e - # p e # => # - # p e.ractor == r # => true - # p e.cause # => # - # end - # - # Ractor::ClosedError is a descendant of StopIteration, so the termination of the ractor will break - # out of any loops that receive this message without propagating the error: - # - # r = Ractor.new do - # 3.times {|i| Ractor.yield "message #{i}"} - # "finishing" - # end - # - # loop {puts "Received: " + r.take} - # puts "Continue successfully" - # - # This will print: - # - # Received: message 0 - # Received: message 1 - # Received: message 2 - # Received: finishing - # Continue successfully - def take - __builtin_cexpr! %q{ - ractor_take(ec, RACTOR_PTR(self)) - } - end - def inspect loc = __builtin_cexpr! %q{ RACTOR_PTR(self)->loc } name = __builtin_cexpr! %q{ RACTOR_PTR(self)->name } @@ -737,38 +358,13 @@ class Ractor # # call-seq: - # ractor.close_incoming -> true | false + # Ractor.current.close -> true | false # - # Closes the incoming port and returns whether it was already closed. All further attempts - # to Ractor.receive in the ractor, and #send to the ractor will fail with Ractor::ClosedError. + # Closes default_port. Closing port is allowed only by the ractor which creates this port. + # So this close method also allowed by the current Ractor. # - # r = Ractor.new {sleep(500)} - # r.close_incoming #=> false - # r.close_incoming #=> true - # r.send('test') - # # Ractor::ClosedError (The incoming-port is already closed) - def close_incoming - __builtin_cexpr! %q{ - ractor_close_incoming(ec, RACTOR_PTR(self)); - } - end - - # - # call-seq: - # ractor.close_outgoing -> true | false - # - # Closes the outgoing port and returns whether it was already closed. All further attempts - # to Ractor.yield in the ractor, and #take from the ractor will fail with Ractor::ClosedError. - # - # r = Ractor.new {sleep(500)} - # r.close_outgoing #=> false - # r.close_outgoing #=> true - # r.take - # # Ractor::ClosedError (The outgoing-port is already closed) - def close_outgoing - __builtin_cexpr! %q{ - ractor_close_outgoing(ec, RACTOR_PTR(self)); - } + def close + default_port.close end # @@ -922,4 +518,253 @@ class Ractor } end end + + # + # call-seq: + # ractor.default_port -> port object + # + # return default port of the Ractor. + # + def default_port + __builtin_cexpr! %q{ + ractor_default_port_value(RACTOR_PTR(self)) + } + end + + # + # call-seq: + # ractor.join -> self + # + # Wait for the termination of the Ractor. + # If the Ractor was aborted (terminated with an exception), + # Ractor#value is called to raise an exception. + # + # Ractor.new{}.join #=> ractor + # + # Ractor.new{ raise "foo" }.join + # #=> raise an exception "foo (RuntimeError)" + # + def join + port = Port.new + + self.monitor port + if port.receive == :aborted + __builtin_ractor_value + end + + self + ensure + port.close + end + + # + # call-seq: + # ractor.value -> obj + # + # Waits for +ractor+ to complete, using #join, and return its value or raise + # the exception which terminated the Ractor. The value will not be copied even + # if it is unshareable object. Therefore at most 1 Ractor can get a value. + # + # r = Ractor.new{ [1, 2] } + # r.value #=> [1, 2] (unshareable object) + # + # Ractor.new(r){|r| r.value} #=> Ractor::Error + # + def value + self.join + __builtin_ractor_value + end + + # keep it for compatibility + def take + Kernel.warn("Ractor#take was deprecated and use Ractor#value instead. This method will be removed after the end of Aug 2025", uplevel: 0) + self.value + end + + # + # call-seq: + # ractor.monitor(port) -> self + # + # Register port as a monitoring port. If the ractor terminated, + # the port received a Symbol object. + # :exited will be sent if the ractor terminated without an exception. + # :aborted will be sent if the ractor terminated with a exception. + # + # r = Ractor.new{ some_task() } + # r.monitor(port = Ractor::Port.new) + # port.receive #=> :exited and r is terminated + # + # r = Ractor.new{ raise "foo" } + # r.monitor(port = Ractor::Port.new) + # port.receive #=> :terminated and r is terminated with an exception "foo" + # + def monitor port + __builtin_ractor_monitor(port) + end + + # + # call-seq: + # ractor.unmonitor(port) -> self + # + # Unregister port from the monitoring ports. + # + def unmonitor port + __builtin_ractor_unmonitor(port) + end + + class Port + # + # call-seq: + # port.receive -> msg + # + # Receive a message to the port (which was sent there by Port#send). + # + # port = Ractor::Port.new + # r = Ractor.new port do |port| + # port.send('message1') + # end + # + # v1 = port.receive + # puts "Received: #{v1}" + # r.join + # # Here will be printed: "Received: message1" + # + # The method blocks if the message queue is empty. + # + # port = Ractor::Port.new + # r = Ractor.new port do |port| + # wait + # puts "Still not received" + # port.send('message1') + # wait + # puts "Still received only one" + # port.send('message2') + # end + # puts "Before first receive" + # v1 = port.receive + # puts "Received: #{v1}" + # v2 = port.receive + # puts "Received: #{v2}" + # r.join + # + # Output: + # + # Before first receive + # Still not received + # Received: message1 + # Still received only one + # Received: message2 + # + # If close_incoming was called on the ractor, the method raises Ractor::ClosedError + # if there are no more messages in the message queue: + # + # port = Ractor::Port.new + # port.close + # port.receive #=> raise Ractor::ClosedError + # + def receive + __builtin_cexpr! %q{ + ractor_port_receive(ec, self) + } + end + + # + # call-seq: + # port.send(msg, move: false) -> self + # + # Send a message to a port to be accepted by port.receive. + # + # port = Ractor::Port.new + # r = Ractor.new do + # r.send 'message' + # end + # value = port.receive + # puts "Received #{value}" + # # Prints: "Received: message" + # + # The method is non-blocking (will return immediately even if the ractor is not ready + # to receive anything): + # + # port = Ractor::Port.new + # r = Ractor.new(port) do |port| + # port.send 'test'} + # puts "Sent successfully" + # # Prints: "Sent successfully" immediately + # end + # + # An attempt to send to a port which already closed its execution will raise Ractor::ClosedError. + # + # r = Ractor.new {Ractor::Port.new} + # r.join + # p r + # # "#" + # port = r.value + # port.send('test') # raise Ractor::ClosedError + # + # If the +obj+ is unshareable, by default it will be copied into the receiving ractor by deep cloning. + # + # If the object is shareable, it only send a reference to the object without cloning. + # + def send obj, move: false + __builtin_cexpr! %q{ + ractor_port_send(ec, self, obj, move) + } + end + + alias << send + + # + # call-seq: + # port.close + # + # Close the port. On the closed port, sending is not prohibited. + # Receiving is also not allowed if there is no sent messages arrived before closing. + # + # port = Ractor::Port.new + # Ractor.new port do |port| + # port.send 1 # OK + # port.send 2 # OK + # port.close + # port.send 3 # raise Ractor::ClosedError + # end + # + # port.receive #=> 1 + # port.receive #=> 2 + # port.receive #=> raise Ractor::ClosedError + # + # Now, only a Ractor which creates the port is allowed to close ports. + # + # port = Ractor::Port.new + # Ractor.new port do |port| + # port.close #=> closing port by other ractors is not allowed (Ractor::Error) + # end.join + # + def close + __builtin_cexpr! %q{ + ractor_port_close(ec, self) + } + end + + # + # call-seq: + # port.closed? -> true/false + # + # Return the port is closed or not. + def closed? + __builtin_cexpr! %q{ + ractor_port_closed_p(ec, self); + } + end + + # + # call-seq: + # port.inspect -> string + def inspect + "#r)))" + } id:#{ + __builtin_cexpr! "SIZET2NUM(ractor_port_id(RACTOR_PORT_PTR(self)))" + }>" + end + end end diff --git a/ractor_core.h b/ractor_core.h index 256ecc38e6..0656ce00a0 100644 --- a/ractor_core.h +++ b/ractor_core.h @@ -9,118 +9,36 @@ #define RACTOR_CHECK_MODE (VM_CHECK_MODE || RUBY_DEBUG) && (SIZEOF_UINT64_T == SIZEOF_VALUE) #endif -enum rb_ractor_basket_type { - // basket is empty - basket_type_none, - - // value is available - basket_type_ref, - basket_type_copy, - basket_type_move, - basket_type_will, - - // basket should be deleted - basket_type_deleted, - - // basket is reserved - basket_type_reserved, - - // take_basket is available - basket_type_take_basket, - - // basket is keeping by yielding ractor - basket_type_yielding, -}; - -// per ractor taking configuration -struct rb_ractor_selector_take_config { - bool closed; - bool oneshot; -}; - -struct rb_ractor_basket { - union { - enum rb_ractor_basket_type e; - rb_atomic_t atomic; - } type; - VALUE sender; // Ractor object sending message - rb_thread_t *sending_th; - - union { - struct { - VALUE v; - bool exception; - } send; - - struct { - struct rb_ractor_basket *basket; - struct rb_ractor_selector_take_config *config; - } take; - } p; // payload -}; - -static inline bool -basket_type_p(struct rb_ractor_basket *b, enum rb_ractor_basket_type type) -{ - return b->type.e == type; -} - -static inline bool -basket_none_p(struct rb_ractor_basket *b) -{ - return basket_type_p(b, basket_type_none); -} - -struct rb_ractor_queue { - struct rb_ractor_basket *baskets; - int start; - int cnt; - int size; - unsigned int serial; - unsigned int reserved_cnt; -}; - -enum rb_ractor_wait_status { - wait_none = 0x00, - wait_receiving = 0x01, - wait_taking = 0x02, - wait_yielding = 0x04, - wait_moving = 0x08, -}; - -enum rb_ractor_wakeup_status { - wakeup_none, - wakeup_by_send, - wakeup_by_yield, - wakeup_by_take, - wakeup_by_close, - wakeup_by_interrupt, - wakeup_by_retry, -}; - struct rb_ractor_sync { // ractor lock rb_nativethread_lock_t lock; + #if RACTOR_CHECK_MODE > 0 VALUE locked_by; #endif - bool incoming_port_closed; - bool outgoing_port_closed; +#ifndef RUBY_THREAD_PTHREAD_H + rb_nativethread_cond_t wakeup_cond; +#endif - // All sent messages will be pushed into recv_queue - struct rb_ractor_queue recv_queue; + // incoming messages + struct ractor_queue *recv_queue; - // The following ractors waiting for the yielding by this ractor - struct rb_ractor_queue takers_queue; + // waiting threads for receiving + struct ccan_list_head waiters; - // Enabled if the ractor already terminated and not taken yet. - struct rb_ractor_basket will_basket; + // ports + VALUE default_port_value; + struct st_table *ports; + size_t next_port_id; - struct ractor_wait { - struct ccan_list_head waiting_threads; - // each thread has struct ccan_list_node ractor_waiting.waiting_node - } wait; + // monitors + struct ccan_list_head monitors; + + // value + rb_ractor_t *successor; + VALUE legacy; + bool legacy_exc; }; // created @@ -146,12 +64,8 @@ enum ractor_status { struct rb_ractor_struct { struct rb_ractor_pub pub; - struct rb_ractor_sync sync; - // vm wide barrier synchronization - rb_nativethread_cond_t barrier_wait_cond; - // thread management struct { struct ccan_list_head set; @@ -162,6 +76,7 @@ struct rb_ractor_struct { rb_execution_context_t *running_ec; rb_thread_t *main; } threads; + VALUE thgroup_default; VALUE name; @@ -219,7 +134,7 @@ void rb_ractor_terminate_all(void); bool rb_ractor_main_p_(void); void rb_ractor_atfork(rb_vm_t *vm, rb_thread_t *th); void rb_ractor_terminate_atfork(rb_vm_t *vm, rb_ractor_t *th); -VALUE rb_ractor_require(VALUE feature); +VALUE rb_ractor_require(VALUE feature, bool silent); VALUE rb_ractor_autoload_load(VALUE space, ID id); VALUE rb_ractor_ensure_shareable(VALUE obj, VALUE name); @@ -277,12 +192,16 @@ rb_ractor_sleeper_thread_num(rb_ractor_t *r) } static inline void -rb_ractor_thread_switch(rb_ractor_t *cr, rb_thread_t *th) +rb_ractor_thread_switch(rb_ractor_t *cr, rb_thread_t *th, bool always_reset) { RUBY_DEBUG_LOG("th:%d->%u%s", cr->threads.running_ec ? (int)rb_th_serial(cr->threads.running_ec->thread_ptr) : -1, rb_th_serial(th), cr->threads.running_ec == th->ec ? " (same)" : ""); + if (cr->threads.running_ec != th->ec || always_reset) { + th->running_time_us = 0; + } + if (cr->threads.running_ec != th->ec) { if (0) { ruby_debug_printf("rb_ractor_thread_switch ec:%p->%p\n", @@ -293,10 +212,6 @@ rb_ractor_thread_switch(rb_ractor_t *cr, rb_thread_t *th) return; } - if (cr->threads.running_ec != th->ec) { - th->running_time_us = 0; - } - cr->threads.running_ec = th->ec; VM_ASSERT(cr == GET_RACTOR()); @@ -351,9 +266,13 @@ rb_ractor_belonging(VALUE obj) } } +extern bool rb_ractor_ignore_belonging_flag; + static inline VALUE rb_ractor_confirm_belonging(VALUE obj) { + if (rb_ractor_ignore_belonging_flag) return obj; + uint32_t id = rb_ractor_belonging(obj); if (id == 0) { @@ -373,6 +292,14 @@ rb_ractor_confirm_belonging(VALUE obj) } return obj; } + +static inline void +rb_ractor_ignore_belonging(bool flag) +{ + rb_ractor_ignore_belonging_flag = flag; +} + #else #define rb_ractor_confirm_belonging(obj) obj +#define rb_ractor_ignore_belonging(flag) (0) #endif diff --git a/ractor_sync.c b/ractor_sync.c new file mode 100644 index 0000000000..eb967a73cb --- /dev/null +++ b/ractor_sync.c @@ -0,0 +1,1499 @@ + +// this file is included by ractor.c + +struct ractor_port { + rb_ractor_t *r; + st_data_t id_; +}; + +static st_data_t +ractor_port_id(const struct ractor_port *rp) +{ + return rp->id_; +} + +static VALUE rb_cRactorPort; + +static VALUE ractor_receive(rb_execution_context_t *ec, const struct ractor_port *rp); +static VALUE ractor_send(rb_execution_context_t *ec, const struct ractor_port *rp, VALUE obj, VALUE move); +static VALUE ractor_try_send(rb_execution_context_t *ec, const struct ractor_port *rp, VALUE obj, VALUE move); +static void ractor_add_port(rb_ractor_t *r, st_data_t id); + +static void +ractor_port_mark(void *ptr) +{ + const struct ractor_port *rp = (struct ractor_port *)ptr; + + if (rp->r) { + rb_gc_mark(rp->r->pub.self); + } +} + +static void +ractor_port_free(void *ptr) +{ + xfree(ptr); +} + +static size_t +ractor_port_memsize(const void *ptr) +{ + return sizeof(struct ractor_port); +} + +static const rb_data_type_t ractor_port_data_type = { + "ractor/port", + { + ractor_port_mark, + ractor_port_free, + ractor_port_memsize, + NULL, // update + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + +static st_data_t +ractor_genid_for_port(rb_ractor_t *cr) +{ + // TODO: enough? + return cr->sync.next_port_id++; +} + +static struct ractor_port * +RACTOR_PORT_PTR(VALUE self) +{ + VM_ASSERT(rb_typeddata_is_kind_of(self, &ractor_port_data_type)); + struct ractor_port *rp = DATA_PTR(self); + return rp; +} + +static VALUE +ractor_port_alloc(VALUE klass) +{ + struct ractor_port *rp; + VALUE rpv = TypedData_Make_Struct(klass, struct ractor_port, &ractor_port_data_type, rp); + return rpv; +} + +static VALUE +ractor_port_init(VALUE rpv, rb_ractor_t *r) +{ + struct ractor_port *rp = RACTOR_PORT_PTR(rpv); + + rp->r = r; + RB_OBJ_WRITTEN(rpv, Qundef, r->pub.self); + rp->id_ = ractor_genid_for_port(r); + + ractor_add_port(r, ractor_port_id(rp)); + + rb_obj_freeze(rpv); + + return rpv; +} + +static VALUE +ractor_port_initialzie(VALUE self) +{ + return ractor_port_init(self, GET_RACTOR()); +} + +static VALUE +ractor_port_initialzie_copy(VALUE self, VALUE orig) +{ + struct ractor_port *dst = RACTOR_PORT_PTR(self); + struct ractor_port *src = RACTOR_PORT_PTR(orig); + dst->r = src->r; + RB_OBJ_WRITTEN(self, Qundef, dst->r->pub.self); + dst->id_ = ractor_port_id(src); + + return self; +} + +static VALUE +ractor_port_new(rb_ractor_t *r) +{ + VALUE rpv = ractor_port_alloc(rb_cRactorPort); + ractor_port_init(rpv, r); + return rpv; +} + +static bool +ractor_port_p(VALUE self) +{ + return rb_typeddata_is_kind_of(self, &ractor_port_data_type); +} + +static VALUE +ractor_port_receive(rb_execution_context_t *ec, VALUE self) +{ + const struct ractor_port *rp = RACTOR_PORT_PTR(self); + + if (rp->r != rb_ec_ractor_ptr(ec)) { + rb_raise(rb_eRactorError, "only allowed from the creator Ractor of this port"); + } + + return ractor_receive(ec, rp); +} + +static VALUE +ractor_port_send(rb_execution_context_t *ec, VALUE self, VALUE obj, VALUE move) +{ + const struct ractor_port *rp = RACTOR_PORT_PTR(self); + ractor_send(ec, rp, obj, RTEST(move)); + return self; +} + +static bool ractor_closed_port_p(rb_execution_context_t *ec, rb_ractor_t *r, const struct ractor_port *rp); +static bool ractor_close_port(rb_execution_context_t *ec, rb_ractor_t *r, const struct ractor_port *rp); + +static VALUE +ractor_port_closed_p(rb_execution_context_t *ec, VALUE self) +{ + const struct ractor_port *rp = RACTOR_PORT_PTR(self); + + if (ractor_closed_port_p(ec, rp->r, rp)) { + return Qtrue; + } + else { + return Qfalse; + } +} + +static VALUE +ractor_port_close(rb_execution_context_t *ec, VALUE self) +{ + const struct ractor_port *rp = RACTOR_PORT_PTR(self); + rb_ractor_t *cr = rb_ec_ractor_ptr(ec); + + if (cr != rp->r) { + rb_raise(rb_eRactorError, "closing port by other ractors is not allowed"); + } + + ractor_close_port(ec, cr, rp); + return self; +} + +// ractor-internal + +// ractor-internal - ractor_basket + +enum ractor_basket_type { + // basket is empty + basket_type_none, + + // value is available + basket_type_ref, + basket_type_copy, + basket_type_move, +}; + +struct ractor_basket { + enum ractor_basket_type type; + VALUE sender; + st_data_t port_id; + + struct { + VALUE v; + bool exception; + } p; // payload + + struct ccan_list_node node; +}; + +#if 0 +static inline bool +ractor_basket_type_p(const struct ractor_basket *b, enum ractor_basket_type type) +{ + return b->type == type; +} + +static inline bool +ractor_basket_none_p(const struct ractor_basket *b) +{ + return ractor_basket_type_p(b, basket_type_none); +} +#endif + +static void +ractor_basket_mark(const struct ractor_basket *b) +{ + rb_gc_mark(b->p.v); +} + +static void +ractor_basket_free(struct ractor_basket *b) +{ + xfree(b); +} + +static struct ractor_basket * +ractor_basket_alloc(void) +{ + struct ractor_basket *b = ALLOC(struct ractor_basket); + return b; +} + +// ractor-internal - ractor_queue + +struct ractor_queue { + struct ccan_list_head set; + bool closed; +}; + +static void +ractor_queue_init(struct ractor_queue *rq) +{ + ccan_list_head_init(&rq->set); + rq->closed = false; +} + +static struct ractor_queue * +ractor_queue_new(void) +{ + struct ractor_queue *rq = ALLOC(struct ractor_queue); + ractor_queue_init(rq); + return rq; +} + +static void +ractor_queue_mark(const struct ractor_queue *rq) +{ + const struct ractor_basket *b; + + ccan_list_for_each(&rq->set, b, node) { + ractor_basket_mark(b); + } +} + +static void +ractor_queue_free(struct ractor_queue *rq) +{ + struct ractor_basket *b, *nxt; + + ccan_list_for_each_safe(&rq->set, b, nxt, node) { + ccan_list_del_init(&b->node); + ractor_basket_free(b); + } + + VM_ASSERT(ccan_list_empty(&rq->set)); + + xfree(rq); +} + +RBIMPL_ATTR_MAYBE_UNUSED() +static size_t +ractor_queue_size(const struct ractor_queue *rq) +{ + size_t size = 0; + const struct ractor_basket *b; + + ccan_list_for_each(&rq->set, b, node) { + size++; + } + return size; +} + +static void +ractor_queue_close(struct ractor_queue *rq) +{ + rq->closed = true; +} + +static void +ractor_queue_move(struct ractor_queue *dst_rq, struct ractor_queue *src_rq) +{ + struct ccan_list_head *src = &src_rq->set; + struct ccan_list_head *dst = &dst_rq->set; + + dst->n.next = src->n.next; + dst->n.prev = src->n.prev; + dst->n.next->prev = &dst->n; + dst->n.prev->next = &dst->n; + ccan_list_head_init(src); +} + +#if 0 +static struct ractor_basket * +ractor_queue_head(rb_ractor_t *r, struct ractor_queue *rq) +{ + return ccan_list_top(&rq->set, struct ractor_basket, node); +} +#endif + +static bool +ractor_queue_empty_p(rb_ractor_t *r, const struct ractor_queue *rq) +{ + return ccan_list_empty(&rq->set); +} + +static struct ractor_basket * +ractor_queue_deq(rb_ractor_t *r, struct ractor_queue *rq) +{ + VM_ASSERT(GET_RACTOR() == r); + + return ccan_list_pop(&rq->set, struct ractor_basket, node); +} + +static void +ractor_queue_enq(rb_ractor_t *r, struct ractor_queue *rq, struct ractor_basket *basket) +{ + ccan_list_add_tail(&rq->set, &basket->node); +} + +#if 0 +static void +rq_dump(const struct ractor_queue *rq) +{ + int i=0; + struct ractor_basket *b; + ccan_list_for_each(&rq->set, b, node) { + fprintf(stderr, "%d type:%s %p\n", i, basket_type_name(b->type), (void *)b); + i++; + } +} +#endif + +static void ractor_delete_port(rb_ractor_t *cr, st_data_t id, bool locked); + +static struct ractor_queue * +ractor_get_queue(rb_ractor_t *cr, st_data_t id, bool locked) +{ + VM_ASSERT(cr == GET_RACTOR()); + + struct ractor_queue *rq; + + if (cr->sync.ports && st_lookup(cr->sync.ports, id, (st_data_t *)&rq)) { + if (rq->closed && ractor_queue_empty_p(cr, rq)) { + ractor_delete_port(cr, id, locked); + return NULL; + } + else { + return rq; + } + } + else { + return NULL; + } +} + +// ractor-internal - ports + +static void +ractor_add_port(rb_ractor_t *r, st_data_t id) +{ + struct ractor_queue *rq = ractor_queue_new(); + ASSERT_ractor_unlocking(r); + + RUBY_DEBUG_LOG("id:%u", (unsigned int)id); + + RACTOR_LOCK(r); + { + // memo: can cause GC, but GC doesn't use ractor locking. + st_insert(r->sync.ports, id, (st_data_t)rq); + } + RACTOR_UNLOCK(r); +} + +static void +ractor_delete_port_locked(rb_ractor_t *cr, st_data_t id) +{ + ASSERT_ractor_locking(cr); + + RUBY_DEBUG_LOG("id:%u", (unsigned int)id); + + struct ractor_queue *rq; + + if (st_delete(cr->sync.ports, &id, (st_data_t *)&rq)) { + ractor_queue_free(rq); + } + else { + VM_ASSERT(0); + } +} + +static void +ractor_delete_port(rb_ractor_t *cr, st_data_t id, bool locked) +{ + if (locked) { + ractor_delete_port_locked(cr, id); + } + else { + RACTOR_LOCK_SELF(cr); + { + ractor_delete_port_locked(cr, id); + } + RACTOR_UNLOCK_SELF(cr); + } +} + +static const struct ractor_port * +ractor_default_port(rb_ractor_t *r) +{ + return RACTOR_PORT_PTR(r->sync.default_port_value); +} + +static VALUE +ractor_default_port_value(rb_ractor_t *r) +{ + return r->sync.default_port_value; +} + +static bool +ractor_closed_port_p(rb_execution_context_t *ec, rb_ractor_t *r, const struct ractor_port *rp) +{ + VM_ASSERT(rb_ec_ractor_ptr(ec) == rp->r ? 1 : (ASSERT_ractor_locking(rp->r), 1)); + + const struct ractor_queue *rq; + + if (rp->r->sync.ports && st_lookup(rp->r->sync.ports, ractor_port_id(rp), (st_data_t *)&rq)) { + return rq->closed; + } + else { + return true; + } +} + +static void ractor_deliver_incoming_messages(rb_execution_context_t *ec, rb_ractor_t *cr); +static bool ractor_queue_empty_p(rb_ractor_t *r, const struct ractor_queue *rq); + +static bool +ractor_close_port(rb_execution_context_t *ec, rb_ractor_t *cr, const struct ractor_port *rp) +{ + VM_ASSERT(cr == rp->r); + struct ractor_queue *rq = NULL; + + RACTOR_LOCK_SELF(cr); + { + ractor_deliver_incoming_messages(ec, cr); // check incoming messages + + if (st_lookup(rp->r->sync.ports, ractor_port_id(rp), (st_data_t *)&rq)) { + ractor_queue_close(rq); + + if (ractor_queue_empty_p(cr, rq)) { + // delete from the table + ractor_delete_port(cr, ractor_port_id(rp), true); + } + + // TODO: free rq + } + } + RACTOR_UNLOCK_SELF(cr); + + return rq != NULL; +} + +static int +ractor_free_all_ports_i(st_data_t port_id, st_data_t val, st_data_t dat) +{ + struct ractor_queue *rq = (struct ractor_queue *)val; + // rb_ractor_t *cr = (rb_ractor_t *)dat; + + ractor_queue_free(rq); + return ST_CONTINUE; +} + +static void +ractor_free_all_ports(rb_ractor_t *cr) +{ + if (cr->sync.ports) { + st_foreach(cr->sync.ports, ractor_free_all_ports_i, (st_data_t)cr); + st_free_table(cr->sync.ports); + cr->sync.ports = NULL; + } + + if (cr->sync.recv_queue) { + ractor_queue_free(cr->sync.recv_queue); + cr->sync.recv_queue = NULL; + } +} + +#if defined(HAVE_WORKING_FORK) +static void +ractor_sync_terminate_atfork(rb_vm_t *vm, rb_ractor_t *r) +{ + ractor_free_all_ports(r); + r->sync.legacy = Qnil; +} +#endif + +// Ractor#monitor + +struct ractor_monitor { + struct ractor_port port; + struct ccan_list_node node; +}; + +static void +ractor_mark_monitors(rb_ractor_t *r) +{ + const struct ractor_monitor *rm; + ccan_list_for_each(&r->sync.monitors, rm, node) { + rb_gc_mark(rm->port.r->pub.self); + } +} + +static VALUE +ractor_exit_token(bool exc) +{ + if (exc) { + RUBY_DEBUG_LOG("aborted"); + return ID2SYM(idAborted); + } + else { + RUBY_DEBUG_LOG("exited"); + return ID2SYM(idExited); + } +} + +static VALUE +ractor_monitor(rb_execution_context_t *ec, VALUE self, VALUE port) +{ + rb_ractor_t *r = RACTOR_PTR(self); + bool terminated = false; + const struct ractor_port *rp = RACTOR_PORT_PTR(port); + struct ractor_monitor *rm = ALLOC(struct ractor_monitor); + rm->port = *rp; // copy port information + + RACTOR_LOCK(r); + { + if (UNDEF_P(r->sync.legacy)) { // not terminated + RUBY_DEBUG_LOG("OK/r:%u -> port:%u@r%u", (unsigned int)rb_ractor_id(r), (unsigned int)ractor_port_id(&rm->port), (unsigned int)rb_ractor_id(rm->port.r)); + ccan_list_add_tail(&r->sync.monitors, &rm->node); + } + else { + RUBY_DEBUG_LOG("NG/r:%u -> port:%u@r%u", (unsigned int)rb_ractor_id(r), (unsigned int)ractor_port_id(&rm->port), (unsigned int)rb_ractor_id(rm->port.r)); + terminated = true; + } + } + RACTOR_UNLOCK(r); + + if (terminated) { + xfree(rm); + ractor_port_send(ec, port, ractor_exit_token(r->sync.legacy_exc), Qfalse); + + return Qfalse; + } + else { + return Qtrue; + } +} + +static VALUE +ractor_unmonitor(rb_execution_context_t *ec, VALUE self, VALUE port) +{ + rb_ractor_t *r = RACTOR_PTR(self); + const struct ractor_port *rp = RACTOR_PORT_PTR(port); + + RACTOR_LOCK(r); + { + if (UNDEF_P(r->sync.legacy)) { // not terminated + struct ractor_monitor *rm, *nxt; + + ccan_list_for_each_safe(&r->sync.monitors, rm, nxt, node) { + if (ractor_port_id(&rm->port) == ractor_port_id(rp)) { + RUBY_DEBUG_LOG("r:%u -> port:%u@r%u", + (unsigned int)rb_ractor_id(r), + (unsigned int)ractor_port_id(&rm->port), + (unsigned int)rb_ractor_id(rm->port.r)); + ccan_list_del(&rm->node); + xfree(rm); + } + } + } + } + RACTOR_UNLOCK(r); + + return self; +} + +static void +ractor_notify_exit(rb_execution_context_t *ec, rb_ractor_t *cr, VALUE legacy, bool exc) +{ + RUBY_DEBUG_LOG("exc:%d", exc); + VM_ASSERT(!UNDEF_P(legacy)); + VM_ASSERT(cr->sync.legacy == Qundef); + + RACTOR_LOCK_SELF(cr); + { + ractor_free_all_ports(cr); + + cr->sync.legacy = legacy; + cr->sync.legacy_exc = exc; + } + RACTOR_UNLOCK_SELF(cr); + + // send token + + VALUE token = ractor_exit_token(exc); + struct ractor_monitor *rm, *nxt; + + ccan_list_for_each_safe(&cr->sync.monitors, rm, nxt, node) + { + RUBY_DEBUG_LOG("port:%u@r%u", (unsigned int)ractor_port_id(&rm->port), (unsigned int)rb_ractor_id(rm->port.r)); + + ractor_try_send(ec, &rm->port, token, false); + + ccan_list_del(&rm->node); + xfree(rm); + } + + VM_ASSERT(ccan_list_empty(&cr->sync.monitors)); +} + +// ractor-internal - initialize, mark, free, memsize + +static int +ractor_mark_ports_i(st_data_t key, st_data_t val, st_data_t data) +{ + // id -> ractor_queue + const struct ractor_queue *rq = (struct ractor_queue *)val; + ractor_queue_mark(rq); + return ST_CONTINUE; +} + +static void +ractor_sync_mark(rb_ractor_t *r) +{ + rb_gc_mark(r->sync.default_port_value); + + if (r->sync.ports) { + ractor_queue_mark(r->sync.recv_queue); + st_foreach(r->sync.ports, ractor_mark_ports_i, 0); + } + + ractor_mark_monitors(r); +} + +static int +ractor_sync_free_ports_i(st_data_t _key, st_data_t val, st_data_t _args) +{ + struct ractor_queue *queue = (struct ractor_queue *)val; + + ractor_queue_free(queue); + + return ST_CONTINUE; +} + +static void +ractor_sync_free(rb_ractor_t *r) +{ + if (r->sync.recv_queue) { + ractor_queue_free(r->sync.recv_queue); + } + + // maybe NULL + if (r->sync.ports) { + st_foreach(r->sync.ports, ractor_sync_free_ports_i, 0); + st_free_table(r->sync.ports); + r->sync.ports = NULL; + } +} + +static size_t +ractor_sync_memsize(const rb_ractor_t *r) +{ + return st_table_size(r->sync.ports); +} + +static void +ractor_sync_init(rb_ractor_t *r) +{ + // lock + rb_native_mutex_initialize(&r->sync.lock); + + // monitors + ccan_list_head_init(&r->sync.monitors); + + // waiters + ccan_list_head_init(&r->sync.waiters); + + // receiving queue + r->sync.recv_queue = ractor_queue_new(); + + // ports + r->sync.ports = st_init_numtable(); + r->sync.default_port_value = ractor_port_new(r); + FL_SET_RAW(r->sync.default_port_value, RUBY_FL_SHAREABLE); // only default ports are shareable + + // legacy + r->sync.legacy = Qundef; + +#ifndef RUBY_THREAD_PTHREAD_H + rb_native_cond_initialize(&r->sync.wakeup_cond); +#endif +} + +// Ractor#value + +static rb_ractor_t * +ractor_set_successor_once(rb_ractor_t *r, rb_ractor_t *cr) +{ + if (r->sync.successor == NULL) { + rb_ractor_t *successor = ATOMIC_PTR_CAS(r->sync.successor, NULL, cr); + return successor == NULL ? cr : successor; + } + + return r->sync.successor; +} + +static VALUE ractor_reset_belonging(VALUE obj); + +static VALUE +ractor_make_remote_exception(VALUE cause, VALUE sender) +{ + VALUE err = rb_exc_new_cstr(rb_eRactorRemoteError, "thrown by remote Ractor."); + rb_ivar_set(err, rb_intern("@ractor"), sender); + rb_ec_setup_exception(NULL, err, cause); + return err; +} + +static VALUE +ractor_value(rb_execution_context_t *ec, VALUE self) +{ + rb_ractor_t *cr = rb_ec_ractor_ptr(ec); + rb_ractor_t *r = RACTOR_PTR(self); + rb_ractor_t *sr = ractor_set_successor_once(r, cr); + + if (sr == cr) { + ractor_reset_belonging(r->sync.legacy); + + if (r->sync.legacy_exc) { + rb_exc_raise(ractor_make_remote_exception(r->sync.legacy, self)); + } + return r->sync.legacy; + } + else { + rb_raise(rb_eRactorError, "Only the successor ractor can take a value"); + } +} + +static VALUE ractor_move(VALUE obj); // in this file +static VALUE ractor_copy(VALUE obj); // in this file + +static VALUE +ractor_prepare_payload(rb_execution_context_t *ec, VALUE obj, enum ractor_basket_type *ptype) +{ + switch (*ptype) { + case basket_type_ref: + return obj; + case basket_type_move: + return ractor_move(obj); + default: + if (rb_ractor_shareable_p(obj)) { + *ptype = basket_type_ref; + return obj; + } + else { + *ptype = basket_type_copy; + return ractor_copy(obj); + } + } +} + +static struct ractor_basket * +ractor_basket_new(rb_execution_context_t *ec, VALUE obj, enum ractor_basket_type type, bool exc) +{ + VALUE v = ractor_prepare_payload(ec, obj, &type); + + struct ractor_basket *b = ractor_basket_alloc(); + b->type = type; + b->p.v = v; + b->p.exception = exc; + return b; +} + +static VALUE +ractor_basket_value(struct ractor_basket *b) +{ + switch (b->type) { + case basket_type_ref: + break; + case basket_type_copy: + case basket_type_move: + ractor_reset_belonging(b->p.v); + break; + default: + VM_ASSERT(0); // unreachable + } + + VM_ASSERT(!RB_TYPE_P(b->p.v, T_NONE)); + return b->p.v; +} + +static VALUE +ractor_basket_accept(struct ractor_basket *b) +{ + VALUE v = ractor_basket_value(b); + + if (b->p.exception) { + VALUE err = ractor_make_remote_exception(v, b->sender); + ractor_basket_free(b); + rb_exc_raise(err); + } + + ractor_basket_free(b); + return v; +} + +// Ractor blocking by receive + +enum ractor_wakeup_status { + wakeup_none, + wakeup_by_send, + wakeup_by_interrupt, + + // wakeup_by_close, +}; + +struct ractor_waiter { + enum ractor_wakeup_status wakeup_status; + rb_thread_t *th; + struct ccan_list_node node; +}; + +#if VM_CHECK_MODE > 0 +static bool +ractor_waiter_included(rb_ractor_t *cr, rb_thread_t *th) +{ + ASSERT_ractor_locking(cr); + + struct ractor_waiter *w; + + ccan_list_for_each(&cr->sync.waiters, w, node) { + if (w->th == th) { + return true; + } + } + + return false; +} +#endif + +#if USE_RUBY_DEBUG_LOG + +static const char * +wakeup_status_str(enum ractor_wakeup_status wakeup_status) +{ + switch (wakeup_status) { + case wakeup_none: return "none"; + case wakeup_by_send: return "by_send"; + case wakeup_by_interrupt: return "by_interrupt"; + // case wakeup_by_close: return "by_close"; + } + rb_bug("unreachable"); +} + +static const char * +basket_type_name(enum ractor_basket_type type) +{ + switch (type) { + case basket_type_none: return "none"; + case basket_type_ref: return "ref"; + case basket_type_copy: return "copy"; + case basket_type_move: return "move"; + } + VM_ASSERT(0); + return NULL; +} + +#endif // USE_RUBY_DEBUG_LOG + +#ifdef RUBY_THREAD_PTHREAD_H + +// + +#else // win32 + +static void +ractor_cond_wait(rb_ractor_t *r) +{ +#if RACTOR_CHECK_MODE > 0 + VALUE locked_by = r->sync.locked_by; + r->sync.locked_by = Qnil; +#endif + rb_native_cond_wait(&r->sync.wakeup_cond, &r->sync.lock); + +#if RACTOR_CHECK_MODE > 0 + r->sync.locked_by = locked_by; +#endif +} + +static void * +ractor_wait_no_gvl(void *ptr) +{ + struct ractor_waiter *waiter = (struct ractor_waiter *)ptr; + rb_ractor_t *cr = waiter->th->ractor; + + RACTOR_LOCK_SELF(cr); + { + if (waiter->wakeup_status == wakeup_none) { + ractor_cond_wait(cr); + } + } + RACTOR_UNLOCK_SELF(cr); + return NULL; +} + +static void +rb_ractor_sched_wait(rb_execution_context_t *ec, rb_ractor_t *cr, rb_unblock_function_t *ubf, void *ptr) +{ + struct ractor_waiter *waiter = (struct ractor_waiter *)ptr; + + RACTOR_UNLOCK(cr); + { + rb_nogvl(ractor_wait_no_gvl, waiter, + ubf, waiter, + RB_NOGVL_UBF_ASYNC_SAFE | RB_NOGVL_INTR_FAIL); + } + RACTOR_LOCK(cr); +} + +static void +rb_ractor_sched_wakeup(rb_ractor_t *r, rb_thread_t *th) +{ + // ractor lock is acquired + rb_native_cond_broadcast(&r->sync.wakeup_cond); +} +#endif + +static bool +ractor_wakeup_all(rb_ractor_t *r, enum ractor_wakeup_status wakeup_status) +{ + ASSERT_ractor_unlocking(r); + + RUBY_DEBUG_LOG("r:%u wakeup:%s", rb_ractor_id(r), wakeup_status_str(wakeup_status)); + + bool wakeup_p = false; + + RACTOR_LOCK(r); + while (1) { + struct ractor_waiter *waiter = ccan_list_pop(&r->sync.waiters, struct ractor_waiter, node); + + if (waiter) { + VM_ASSERT(waiter->wakeup_status == wakeup_none); + + waiter->wakeup_status = wakeup_status; + rb_ractor_sched_wakeup(r, waiter->th); + + wakeup_p = true; + } + else { + break; + } + } + RACTOR_UNLOCK(r); + + return wakeup_p; +} + +static void +ubf_ractor_wait(void *ptr) +{ + struct ractor_waiter *waiter = (struct ractor_waiter *)ptr; + + rb_thread_t *th = waiter->th; + rb_ractor_t *r = th->ractor; + + // clear ubf and nobody can kick UBF + th->unblock.func = NULL; + th->unblock.arg = NULL; + + rb_native_mutex_unlock(&th->interrupt_lock); + { + RACTOR_LOCK(r); + { + if (waiter->wakeup_status == wakeup_none) { + RUBY_DEBUG_LOG("waiter:%p", (void *)waiter); + + waiter->wakeup_status = wakeup_by_interrupt; + ccan_list_del(&waiter->node); + + rb_ractor_sched_wakeup(r, waiter->th); + } + } + RACTOR_UNLOCK(r); + } + rb_native_mutex_lock(&th->interrupt_lock); +} + +static enum ractor_wakeup_status +ractor_wait(rb_execution_context_t *ec, rb_ractor_t *cr) +{ + rb_thread_t *th = rb_ec_thread_ptr(ec); + + struct ractor_waiter waiter = { + .wakeup_status = wakeup_none, + .th = th, + }; + + RUBY_DEBUG_LOG("wait%s", ""); + + ASSERT_ractor_locking(cr); + + VM_ASSERT(GET_RACTOR() == cr); + VM_ASSERT(!ractor_waiter_included(cr, th)); + + ccan_list_add_tail(&cr->sync.waiters, &waiter.node); + + // resume another ready thread and wait for an event + rb_ractor_sched_wait(ec, cr, ubf_ractor_wait, &waiter); + + if (waiter.wakeup_status == wakeup_none) { + ccan_list_del(&waiter.node); + } + + RUBY_DEBUG_LOG("wakeup_status:%s", wakeup_status_str(waiter.wakeup_status)); + + RACTOR_UNLOCK_SELF(cr); + { + rb_ec_check_ints(ec); + } + RACTOR_LOCK_SELF(cr); + + VM_ASSERT(!ractor_waiter_included(cr, th)); + return waiter.wakeup_status; +} + +static void +ractor_deliver_incoming_messages(rb_execution_context_t *ec, rb_ractor_t *cr) +{ + ASSERT_ractor_locking(cr); + struct ractor_queue *recv_q = cr->sync.recv_queue; + + struct ractor_basket *b; + while ((b = ractor_queue_deq(cr, recv_q)) != NULL) { + ractor_queue_enq(cr, ractor_get_queue(cr, b->port_id, true), b); + } +} + +static bool +ractor_check_received(rb_ractor_t *cr, struct ractor_queue *messages) +{ + struct ractor_queue *received_queue = cr->sync.recv_queue; + bool received = false; + + ASSERT_ractor_locking(cr); + + if (ractor_queue_empty_p(cr, received_queue)) { + RUBY_DEBUG_LOG("empty"); + } + else { + received = true; + + // messages <- incoming + ractor_queue_init(messages); + ractor_queue_move(messages, received_queue); + } + + VM_ASSERT(ractor_queue_empty_p(cr, received_queue)); + + RUBY_DEBUG_LOG("received:%d", received); + return received; +} + +static void +ractor_wait_receive(rb_execution_context_t *ec, rb_ractor_t *cr) +{ + struct ractor_queue messages; + bool deliverred = false; + + RACTOR_LOCK_SELF(cr); + { + if (ractor_check_received(cr, &messages)) { + deliverred = true; + } + else { + ractor_wait(ec, cr); + } + } + RACTOR_UNLOCK_SELF(cr); + + if (deliverred) { + VM_ASSERT(!ractor_queue_empty_p(cr, &messages)); + struct ractor_basket *b; + + while ((b = ractor_queue_deq(cr, &messages)) != NULL) { + ractor_queue_enq(cr, ractor_get_queue(cr, b->port_id, false), b); + } + } +} + +static VALUE +ractor_try_receive(rb_execution_context_t *ec, rb_ractor_t *cr, const struct ractor_port *rp) +{ + struct ractor_queue *rq = ractor_get_queue(cr, ractor_port_id(rp), false); + + if (rq == NULL) { + rb_raise(rb_eRactorClosedError, "The port was already closed"); + } + + struct ractor_basket *b = ractor_queue_deq(cr, rq); + + if (rq->closed && ractor_queue_empty_p(cr, rq)) { + ractor_delete_port(cr, ractor_port_id(rp), false); + } + + if (b) { + return ractor_basket_accept(b); + } + else { + return Qundef; + } +} + +static VALUE +ractor_receive(rb_execution_context_t *ec, const struct ractor_port *rp) +{ + rb_ractor_t *cr = rb_ec_ractor_ptr(ec); + VM_ASSERT(cr == rp->r); + + RUBY_DEBUG_LOG("port:%u", (unsigned int)ractor_port_id(rp)); + + while (1) { + VALUE v = ractor_try_receive(ec, cr, rp); + + if (v != Qundef) { + return v; + } + else { + ractor_wait_receive(ec, cr); + } + } +} + +// Ractor#send + +static void +ractor_send_basket(rb_execution_context_t *ec, const struct ractor_port *rp, struct ractor_basket *b, bool raise_on_error) +{ + bool closed = false; + + RUBY_DEBUG_LOG("port:%u@r%u b:%s v:%p", (unsigned int)ractor_port_id(rp), rb_ractor_id(rp->r), basket_type_name(b->type), (void *)b->p.v); + + RACTOR_LOCK(rp->r); + { + if (ractor_closed_port_p(ec, rp->r, rp)) { + closed = true; + } + else { + b->port_id = ractor_port_id(rp); + ractor_queue_enq(rp->r, rp->r->sync.recv_queue, b); + } + } + RACTOR_UNLOCK(rp->r); + + // NOTE: ref r -> b->p.v is created, but Ractor is unprotected object, so no problem on that. + + if (!closed) { + ractor_wakeup_all(rp->r, wakeup_by_send); + } + else { + RUBY_DEBUG_LOG("closed:%u@r%u", (unsigned int)ractor_port_id(rp), rb_ractor_id(rp->r)); + + if (raise_on_error) { + ractor_basket_free(b); + rb_raise(rb_eRactorClosedError, "The port was already closed"); + } + } +} + +static VALUE +ractor_send0(rb_execution_context_t *ec, const struct ractor_port *rp, VALUE obj, VALUE move, bool raise_on_error) +{ + struct ractor_basket *b = ractor_basket_new(ec, obj, RTEST(move) ? basket_type_move : basket_type_none, false); + ractor_send_basket(ec, rp, b, raise_on_error); + RB_GC_GUARD(obj); + return rp->r->pub.self; +} + +static VALUE +ractor_send(rb_execution_context_t *ec, const struct ractor_port *rp, VALUE obj, VALUE move) +{ + return ractor_send0(ec, rp, obj, move, true); +} + +static VALUE +ractor_try_send(rb_execution_context_t *ec, const struct ractor_port *rp, VALUE obj, VALUE move) +{ + return ractor_send0(ec, rp, obj, move, false); +} + +// Ractor::Selector + +struct ractor_selector { + rb_ractor_t *r; + struct st_table *ports; // rpv -> rp + +}; + +static int +ractor_selector_mark_i(st_data_t key, st_data_t val, st_data_t dmy) +{ + rb_gc_mark((VALUE)key); // rpv + + return ST_CONTINUE; +} + +static void +ractor_selector_mark(void *ptr) +{ + struct ractor_selector *s = ptr; + + if (s->ports) { + st_foreach(s->ports, ractor_selector_mark_i, 0); + } +} + +static void +ractor_selector_free(void *ptr) +{ + struct ractor_selector *s = ptr; + st_free_table(s->ports); + ruby_xfree(ptr); +} + +static size_t +ractor_selector_memsize(const void *ptr) +{ + const struct ractor_selector *s = ptr; + return sizeof(struct ractor_selector) + st_memsize(s->ports); +} + +static const rb_data_type_t ractor_selector_data_type = { + "ractor/selector", + { + ractor_selector_mark, + ractor_selector_free, + ractor_selector_memsize, + NULL, // update + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, +}; + +static struct ractor_selector * +RACTOR_SELECTOR_PTR(VALUE selv) +{ + VM_ASSERT(rb_typeddata_is_kind_of(selv, &ractor_selector_data_type)); + return (struct ractor_selector *)DATA_PTR(selv); +} + +// Ractor::Selector.new + +static VALUE +ractor_selector_create(VALUE klass) +{ + struct ractor_selector *s; + VALUE selv = TypedData_Make_Struct(klass, struct ractor_selector, &ractor_selector_data_type, s); + s->ports = st_init_numtable(); // TODO + return selv; +} + +// Ractor::Selector#add(r) + +/* + * call-seq: + * add(ractor) -> ractor + * + * Adds _ractor_ to +self+. Raises an exception if _ractor_ is already added. + * Returns _ractor_. + */ +static VALUE +ractor_selector_add(VALUE selv, VALUE rpv) +{ + if (!ractor_port_p(rpv)) { + rb_raise(rb_eArgError, "Not a Ractor::Port object"); + } + + struct ractor_selector *s = RACTOR_SELECTOR_PTR(selv); + const struct ractor_port *rp = RACTOR_PORT_PTR(rpv); + + if (st_lookup(s->ports, (st_data_t)rpv, NULL)) { + rb_raise(rb_eArgError, "already added"); + } + + st_insert(s->ports, (st_data_t)rpv, (st_data_t)rp); + return selv; +} + +// Ractor::Selector#remove(r) + +/* call-seq: + * remove(ractor) -> ractor + * + * Removes _ractor_ from +self+. Raises an exception if _ractor_ is not added. + * Returns the removed _ractor_. + */ +static VALUE +ractor_selector_remove(VALUE selv, VALUE rpv) +{ + if (!ractor_port_p(rpv)) { + rb_raise(rb_eArgError, "Not a Ractor::Port object"); + } + + struct ractor_selector *s = RACTOR_SELECTOR_PTR(selv); + + if (!st_lookup(s->ports, (st_data_t)rpv, NULL)) { + rb_raise(rb_eArgError, "not added yet"); + } + + st_delete(s->ports, (st_data_t *)&rpv, NULL); + + return selv; +} + +// Ractor::Selector#clear + +/* + * call-seq: + * clear -> self + * + * Removes all ractors from +self+. Raises +self+. + */ +static VALUE +ractor_selector_clear(VALUE selv) +{ + struct ractor_selector *s = RACTOR_SELECTOR_PTR(selv); + st_clear(s->ports); + return selv; +} + +/* + * call-seq: + * empty? -> true or false + * + * Returns +true+ if no ractor is added. + */ +static VALUE +ractor_selector_empty_p(VALUE selv) +{ + struct ractor_selector *s = RACTOR_SELECTOR_PTR(selv); + return s->ports->num_entries == 0 ? Qtrue : Qfalse; +} + +// Ractor::Selector#wait + +struct ractor_selector_wait_data { + rb_ractor_t *cr; + rb_execution_context_t *ec; + bool found; + VALUE v; + VALUE rpv; +}; + +static int +ractor_selector_wait_i(st_data_t key, st_data_t val, st_data_t data) +{ + struct ractor_selector_wait_data *p = (struct ractor_selector_wait_data *)data; + const struct ractor_port *rp = (const struct ractor_port *)val; + + VALUE v = ractor_try_receive(p->ec, p->cr, rp); + + if (v != Qundef) { + p->found = true; + p->v = v; + p->rpv = (VALUE)key; + return ST_STOP; + } + else { + return ST_CONTINUE; + } +} + +static VALUE +ractor_selector__wait(rb_execution_context_t *ec, VALUE selector) +{ + rb_ractor_t *cr = rb_ec_ractor_ptr(ec); + struct ractor_selector *s = RACTOR_SELECTOR_PTR(selector); + + struct ractor_selector_wait_data data = { + .ec = ec, + .cr = cr, + .found = false, + }; + + while (1) { + st_foreach(s->ports, ractor_selector_wait_i, (st_data_t)&data); + + if (data.found) { + return rb_ary_new_from_args(2, data.rpv, data.v); + } + + ractor_wait_receive(ec, cr); + } +} + +/* + * call-seq: + * wait(receive: false, yield_value: undef, move: false) -> [ractor, value] + * + * Waits until any ractor in _selector_ can be active. + */ +static VALUE +ractor_selector_wait(VALUE selector) +{ + return ractor_selector__wait(GET_EC(), selector); +} + +static VALUE +ractor_selector_new(int argc, VALUE *ractors, VALUE klass) +{ + VALUE selector = ractor_selector_create(klass); + + for (int i=0; i= MAC_OS_X_VERSION_10_7 @@ -520,7 +506,7 @@ fill_random_bytes_urandom(void *seed, size_t size) # endif static int -fill_random_bytes_syscall(void *seed, size_t size, int unused) +fill_random_bytes_lib(void *seed, size_t size) { #if USE_COMMON_RANDOM CCRNGStatus status = CCRandomGenerateBytes(seed, size); @@ -547,18 +533,16 @@ fill_random_bytes_syscall(void *seed, size_t size, int unused) } return 0; } -#elif defined(HAVE_ARC4RANDOM_BUF) +#elif defined(HAVE_ARC4RANDOM_BUF) && \ + ((defined(__OpenBSD__) && OpenBSD >= 201411) || \ + (defined(__NetBSD__) && __NetBSD_Version__ >= 700000000) || \ + (defined(__FreeBSD__) && __FreeBSD_version >= 1200079)) +// [Bug #15039] arc4random_buf(3) should used only if we know it is fork-safe static int -fill_random_bytes_syscall(void *buf, size_t size, int unused) +fill_random_bytes_lib(void *buf, size_t size) { -#if (defined(__OpenBSD__) && OpenBSD >= 201411) || \ - (defined(__NetBSD__) && __NetBSD_Version__ >= 700000000) || \ - (defined(__FreeBSD__) && __FreeBSD_version >= 1200079) arc4random_buf(buf, size); return 0; -#else - return -1; -#endif } #elif defined(_WIN32) @@ -638,11 +622,17 @@ fill_random_bytes_bcrypt(void *seed, size_t size) } static int -fill_random_bytes_syscall(void *seed, size_t size, int unused) +fill_random_bytes_lib(void *seed, size_t size) { if (fill_random_bytes_bcrypt(seed, size) == 0) return 0; return fill_random_bytes_crypt(seed, size); } +#else +# define fill_random_bytes_lib(seed, size) -1 +#endif + +/* fill random bytes by dedicated syscall */ +#if 0 #elif defined HAVE_GETRANDOM static int fill_random_bytes_syscall(void *seed, size_t size, int need_secure) @@ -666,6 +656,31 @@ fill_random_bytes_syscall(void *seed, size_t size, int need_secure) } return -1; } +#elif defined(HAVE_GETENTROPY) +/* + * The Open Group Base Specifications Issue 8 - IEEE Std 1003.1-2024 + * https://pubs.opengroup.org/onlinepubs/9799919799/functions/getentropy.html + * + * NOTE: `getentropy`(3) on Linux is implemented using `getrandom`(2), + * prefer the latter over this if both are defined. + */ +#ifndef GETENTROPY_MAX +# define GETENTROPY_MAX 256 +#endif +static int +fill_random_bytes_syscall(void *seed, size_t size, int need_secure) +{ + unsigned char *p = (unsigned char *)seed; + while (size) { + size_t len = size < GETENTROPY_MAX ? size : GETENTROPY_MAX; + if (getentropy(p, len) != 0) { + return -1; + } + p += len; + size -= len; + } + return 0; +} #else # define fill_random_bytes_syscall(seed, size, need_secure) -1 #endif @@ -675,6 +690,7 @@ ruby_fill_random_bytes(void *seed, size_t size, int need_secure) { int ret = fill_random_bytes_syscall(seed, size, need_secure); if (ret == 0) return ret; + if (fill_random_bytes_lib(seed, size) == 0) return 0; return fill_random_bytes_urandom(seed, size); } diff --git a/rational.c b/rational.c index f1547856b4..89e74c328d 100644 --- a/rational.c +++ b/rational.c @@ -2107,39 +2107,6 @@ rb_float_denominator(VALUE self) return nurat_denominator(r); } -/* - * call-seq: - * to_r -> (0/1) - * - * Returns zero as a Rational: - * - * nil.to_r # => (0/1) - * - */ -static VALUE -nilclass_to_r(VALUE self) -{ - return rb_rational_new1(INT2FIX(0)); -} - -/* - * call-seq: - * rationalize(eps = nil) -> (0/1) - * - * Returns zero as a Rational: - * - * nil.rationalize # => (0/1) - * - * Argument +eps+ is ignored. - * - */ -static VALUE -nilclass_rationalize(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - return nilclass_to_r(self); -} - /* * call-seq: * int.to_r -> rational @@ -2823,8 +2790,6 @@ Init_Rational(void) rb_define_method(rb_cFloat, "numerator", rb_float_numerator, 0); rb_define_method(rb_cFloat, "denominator", rb_float_denominator, 0); - rb_define_method(rb_cNilClass, "to_r", nilclass_to_r, 0); - rb_define_method(rb_cNilClass, "rationalize", nilclass_rationalize, -1); rb_define_method(rb_cInteger, "to_r", integer_to_r, 0); rb_define_method(rb_cInteger, "rationalize", integer_rationalize, -1); rb_define_method(rb_cFloat, "to_r", float_to_r, 0); diff --git a/re.c b/re.c index 96a3cbeaa9..b47538d594 100644 --- a/re.c +++ b/re.c @@ -28,6 +28,7 @@ #include "ruby/encoding.h" #include "ruby/re.h" #include "ruby/util.h" +#include "ractor_core.h" VALUE rb_eRegexpError, rb_eRegexpTimeoutError; @@ -1666,7 +1667,7 @@ rb_reg_prepare_re(VALUE re, VALUE str) RSTRING_GETMEM(unescaped, ptr, len); /* If there are no other users of this regex, then we can directly overwrite it. */ - if (RREGEXP(re)->usecnt == 0) { + if (ruby_single_main_ractor && RREGEXP(re)->usecnt == 0) { regex_t tmp_reg; r = onig_new_without_alloc(&tmp_reg, (UChar *)ptr, (UChar *)(ptr + len), reg->options, enc, @@ -3499,12 +3500,17 @@ static VALUE reg_cache; VALUE rb_reg_regcomp(VALUE str) { - if (reg_cache && RREGEXP_SRC_LEN(reg_cache) == RSTRING_LEN(str) - && ENCODING_GET(reg_cache) == ENCODING_GET(str) - && memcmp(RREGEXP_SRC_PTR(reg_cache), RSTRING_PTR(str), RSTRING_LEN(str)) == 0) - return reg_cache; + if (rb_ractor_main_p()) { + if (reg_cache && RREGEXP_SRC_LEN(reg_cache) == RSTRING_LEN(str) + && ENCODING_GET(reg_cache) == ENCODING_GET(str) + && memcmp(RREGEXP_SRC_PTR(reg_cache), RSTRING_PTR(str), RSTRING_LEN(str)) == 0) + return reg_cache; - return reg_cache = rb_reg_new_str(str, 0); + return reg_cache = rb_reg_new_str(str, 0); + } + else { + return rb_reg_new_str(str, 0); + } } static st_index_t reg_hash(VALUE re); diff --git a/regenc.c b/regenc.c index eb523e1ae5..c554f4eb31 100644 --- a/regenc.c +++ b/regenc.c @@ -985,7 +985,8 @@ onigenc_ascii_only_case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, const if (code >= 'a' && code <= 'z' && (flags & ONIGENC_CASE_UPCASE)) { flags |= ONIGENC_CASE_MODIFIED; code += 'A' - 'a'; - } else if (code >= 'A' && code <= 'Z' && + } + else if (code >= 'A' && code <= 'Z' && (flags & (ONIGENC_CASE_DOWNCASE | ONIGENC_CASE_FOLD))) { flags |= ONIGENC_CASE_MODIFIED; code += 'a' - 'A'; @@ -1013,7 +1014,8 @@ onigenc_single_byte_ascii_only_case_map(OnigCaseFoldType* flagP, const OnigUChar if (code >= 'a' && code <= 'z' && (flags & ONIGENC_CASE_UPCASE)) { flags |= ONIGENC_CASE_MODIFIED; code += 'A' - 'a'; - } else if (code >= 'A' && code <= 'Z' && + } + else if (code >= 'A' && code <= 'Z' && (flags & (ONIGENC_CASE_DOWNCASE | ONIGENC_CASE_FOLD))) { flags |= ONIGENC_CASE_MODIFIED; code += 'a' - 'A'; diff --git a/regerror.c b/regerror.c index b18fc2e88b..df5e964cc3 100644 --- a/regerror.c +++ b/regerror.c @@ -299,7 +299,8 @@ onig_error_code_to_str(UChar* s, OnigPosition code, ...) if (q) { len = onigenc_str_bytelen_null(ONIG_ENCODING_ASCII, q); xmemcpy(s, q, len); - } else { + } + else { len = 0; } s[len] = '\0'; diff --git a/regexec.c b/regexec.c index d200a3cc28..ba5560c609 100644 --- a/regexec.c +++ b/regexec.c @@ -1174,13 +1174,15 @@ onig_region_copy(OnigRegion* to, const OnigRegion* from) stk_base = stk_alloc;\ stk = stk_base;\ stk_end = stk_base + msa->stack_n;\ - } else {\ + }\ + else {\ stk_alloc = (OnigStackType* )xalloca(sizeof(OnigStackType) * (stack_num));\ stk_base = stk_alloc;\ stk = stk_base;\ stk_end = stk_base + (stack_num);\ }\ - } else if (msa->stack_p) {\ + }\ + else if (msa->stack_p) {\ alloc_addr = (char* )xalloca(sizeof(OnigStackIndex) * (ptr_num));\ heap_addr = NULL;\ stk_alloc = (OnigStackType* )(msa->stack_p);\ @@ -1532,7 +1534,8 @@ stack_double(OnigStackType** arg_stk_base, OnigStackType** arg_stk_end, if (stk->type == STK_MATCH_CACHE_POINT) {\ msa->match_cache_buf[stk->u.match_cache_point.index] |= stk->u.match_cache_point.mask;\ MATCH_CACHE_DEBUG_MEMOIZE(stk);\ - } else if (stk->type == STK_ATOMIC_MATCH_CACHE_POINT) {\ + }\ + else if (stk->type == STK_ATOMIC_MATCH_CACHE_POINT) {\ memoize_extended_match_cache_point(msa->match_cache_buf, stk->u.match_cache_point.index, stk->u.match_cache_point.mask);\ MATCH_CACHE_DEBUG_MEMOIZE(stkp);\ }\ @@ -2277,19 +2280,25 @@ find_cache_point(regex_t* reg, const OnigCacheOpcode* cache_opcodes, long num_ca cache_point; } -static int check_extended_match_cache_point(uint8_t *match_cache_buf, long match_cache_point_index, uint8_t match_cache_point_mask) { +static int +check_extended_match_cache_point(uint8_t *match_cache_buf, long match_cache_point_index, uint8_t match_cache_point_mask) +{ if (match_cache_point_mask & 0x80) { return (match_cache_buf[match_cache_point_index + 1] & 0x01) > 0; - } else { + } + else { return (match_cache_buf[match_cache_point_index] & (match_cache_point_mask << 1)) > 0; } } -static void memoize_extended_match_cache_point(uint8_t *match_cache_buf, long match_cache_point_index, uint8_t match_cache_point_mask) { +static void +memoize_extended_match_cache_point(uint8_t *match_cache_buf, long match_cache_point_index, uint8_t match_cache_point_mask) +{ match_cache_buf[match_cache_point_index] |= match_cache_point_mask; if (match_cache_point_mask & 0x80) { match_cache_buf[match_cache_point_index + 1] |= 0x01; - } else { + } + else { match_cache_buf[match_cache_point_index] |= match_cache_point_mask << 1; } } @@ -2630,13 +2639,16 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, if (check_extended_match_cache_point(msa->match_cache_buf, match_cache_point_index, match_cache_point_mask)) {\ STACK_STOP_BT_FAIL;\ goto fail;\ - } else goto fail;\ - } else {\ + }\ + else goto fail;\ + }\ + else {\ if (check_extended_match_cache_point(msa->match_cache_buf, match_cache_point_index, match_cache_point_mask)) {\ p = cache_opcode->match_addr;\ MOP_OUT;\ JUMP;\ - } else goto fail;\ + }\ + else goto fail;\ }\ }\ STACK_PUSH_MATCH_CACHE_POINT(match_cache_point_index, match_cache_point_mask);\ diff --git a/regparse.c b/regparse.c index c7587b53b7..7b2bc7eea8 100644 --- a/regparse.c +++ b/regparse.c @@ -5687,7 +5687,8 @@ i_apply_case_fold(OnigCodePoint from, OnigCodePoint to[], if (add_flag) { if (is_singlebyte_range(*to, env->enc)) { BITSET_SET_BIT(bs, *to); - } else { + } + else { r = add_code_range0(&(cc->mbuf), env, *to, *to, 0); if (r < 0) return r; } @@ -5699,10 +5700,12 @@ i_apply_case_fold(OnigCodePoint from, OnigCodePoint to[], if (is_singlebyte_range(*to, env->enc)) { if (IS_NCCLASS_NOT(cc)) { BITSET_CLEAR_BIT(bs, *to); - } else { + } + else { BITSET_SET_BIT(bs, *to); } - } else { + } + else { if (IS_NCCLASS_NOT(cc)) clear_not_flag_cclass(cc, env->enc); r = add_code_range0(&(cc->mbuf), env, *to, *to, 0); if (r < 0) return r; @@ -6282,7 +6285,8 @@ is_onechar_cclass(CClassNode* cc, OnigCodePoint* code) if (b1 != 0) { if (((b1 & (b1 - 1)) == 0) && (c == not_found)) { c = BITS_IN_ROOM * i + countbits(b1 - 1); - } else { + } + else { return 0; /* the character class contains multiple chars */ } } @@ -6717,7 +6721,7 @@ parse_subexp(Node** top, OnigToken* tok, int term, UChar** src, UChar* end, ScanEnv* env) { int r; - Node *node, **headp; + Node *node, *topnode, **headp; *top = NULL; env->parse_depth++; @@ -6733,26 +6737,29 @@ parse_subexp(Node** top, OnigToken* tok, int term, *top = node; } else if (r == TK_ALT) { - *top = onig_node_new_alt(node, NULL); - headp = &(NCDR(*top)); + topnode = onig_node_new_alt(node, NULL); + headp = &(NCDR(topnode)); while (r == TK_ALT) { r = fetch_token(tok, src, end, env); if (r < 0) { - onig_node_free(node); + onig_node_free(topnode); return r; } r = parse_branch(&node, tok, term, src, end, env); if (r < 0) { - onig_node_free(node); + onig_node_free(topnode); return r; } *headp = onig_node_new_alt(node, NULL); - headp = &(NCDR(*headp)); + headp = &(NCDR(*headp)); } - if (tok->type != (enum TokenSyms )term) + if (tok->type != (enum TokenSyms )term) { + onig_node_free(topnode); goto err; + } + *top = topnode; } else { onig_node_free(node); diff --git a/ruby.c b/ruby.c index 46bfc7be1f..0d09e7ce61 100644 --- a/ruby.c +++ b/ruby.c @@ -757,8 +757,6 @@ ruby_init_loadpath(void) rb_ary_push(load_path, path); paths += len + 1; } - - rb_const_set(rb_cObject, rb_intern_const("TMP_RUBY_PREFIX"), ruby_prefix_path); } @@ -1772,7 +1770,6 @@ static void ruby_init_prelude(void) { Init_builtin_features(); - rb_const_remove(rb_cObject, rb_intern_const("TMP_RUBY_PREFIX")); } void rb_call_builtin_inits(void); @@ -1822,8 +1819,6 @@ ruby_opt_init(ruby_cmdline_options_t *opt) GET_VM()->running = 1; memset(ruby_vm_redefined_flag, 0, sizeof(ruby_vm_redefined_flag)); - ruby_init_prelude(); - if (rb_namespace_available()) rb_initialize_main_namespace(); @@ -1844,6 +1839,8 @@ ruby_opt_init(ruby_cmdline_options_t *opt) Init_builtin_yjit_hook(); #endif + rb_namespace_init_done(); + ruby_init_prelude(); ruby_set_script_name(opt->script_name); require_libraries(&opt->req_list); } diff --git a/ruby_atomic.h b/ruby_atomic.h index 57d341082d..1ccabcbdf6 100644 --- a/ruby_atomic.h +++ b/ruby_atomic.h @@ -1,5 +1,10 @@ +#ifndef INTERNAL_ATOMIC_H +#define INTERNAL_ATOMIC_H + #include "ruby/atomic.h" +#define RUBY_ATOMIC_VALUE_LOAD(x) (VALUE)(RUBY_ATOMIC_PTR_LOAD(x)) + /* shim macros only */ #define ATOMIC_ADD(var, val) RUBY_ATOMIC_ADD(var, val) #define ATOMIC_CAS(var, oldval, newval) RUBY_ATOMIC_CAS(var, oldval, newval) @@ -21,3 +26,50 @@ #define ATOMIC_SUB(var, val) RUBY_ATOMIC_SUB(var, val) #define ATOMIC_VALUE_CAS(var, oldval, val) RUBY_ATOMIC_VALUE_CAS(var, oldval, val) #define ATOMIC_VALUE_EXCHANGE(var, val) RUBY_ATOMIC_VALUE_EXCHANGE(var, val) + +static inline rb_atomic_t +rbimpl_atomic_load_relaxed(volatile rb_atomic_t *ptr) +{ +#if defined(HAVE_GCC_ATOMIC_BUILTINS) + return __atomic_load_n(ptr, __ATOMIC_RELAXED); +#else + return *ptr; +#endif +} +#define ATOMIC_LOAD_RELAXED(var) rbimpl_atomic_load_relaxed(&(var)) + +typedef RBIMPL_ALIGNAS(8) uint64_t rbimpl_atomic_uint64_t; + +static inline uint64_t +rbimpl_atomic_u64_load_relaxed(const volatile rbimpl_atomic_uint64_t *value) +{ +#if defined(HAVE_GCC_ATOMIC_BUILTINS_64) + return __atomic_load_n(value, __ATOMIC_RELAXED); +#elif defined(_WIN32) + uint64_t val = *value; + return InterlockedCompareExchange64(RBIMPL_CAST((uint64_t *)value), val, val); +#elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) + uint64_t val = *value; + return atomic_cas_64(value, val, val); +#else + return *value; +#endif +} +#define ATOMIC_U64_LOAD_RELAXED(var) rbimpl_atomic_u64_load_relaxed(&(var)) + +static inline void +rbimpl_atomic_u64_set_relaxed(volatile rbimpl_atomic_uint64_t *address, uint64_t value) +{ +#if defined(HAVE_GCC_ATOMIC_BUILTINS_64) + __atomic_store_n(address, value, __ATOMIC_RELAXED); +#elif defined(_WIN32) + InterlockedExchange64(address, value); +#elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) + atomic_swap_64(address, value); +#else + *address = value; +#endif +} +#define ATOMIC_U64_SET_RELAXED(var, val) rbimpl_atomic_u64_set_relaxed(&(var), val) + +#endif diff --git a/rubyparser.h b/rubyparser.h index 16f5cac81f..c63929abb2 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -915,12 +915,16 @@ typedef struct RNode_COLON2 { struct RNode *nd_head; ID nd_mid; + rb_code_location_t delimiter_loc; + rb_code_location_t name_loc; } rb_node_colon2_t; typedef struct RNode_COLON3 { NODE node; ID nd_mid; + rb_code_location_t delimiter_loc; + rb_code_location_t name_loc; } rb_node_colon3_t; /* NODE_DOT2, NODE_DOT3, NODE_FLIP2, NODE_FLIP3 */ @@ -1153,7 +1157,7 @@ typedef struct RNode_ERROR { #define RNODE_FILE(node) ((rb_node_file_t *)(node)) #define RNODE_ENCODING(node) ((rb_node_encoding_t *)(node)) -/* FL : 0..4: T_TYPES, 5: KEEP_WB, 6: PROMOTED, 7: FINALIZE, 8: UNUSED, 9: UNUSED, 10: EXIVAR, 11: FREEZE */ +/* FL : 0..4: T_TYPES, 5: KEEP_WB, 6: PROMOTED, 7: FINALIZE, 8..10: UNUSED, 11: FREEZE */ /* NODE_FL: 0..4: UNUSED, 5: UNUSED, 6: UNUSED, 7: NODE_FL_NEWLINE, * 8..14: nd_type, * 15..: nd_line diff --git a/sample/drb/README.ja.rdoc b/sample/drb/README.ja.rdoc deleted file mode 100644 index 1697b1b704..0000000000 --- a/sample/drb/README.ja.rdoc +++ /dev/null @@ -1,59 +0,0 @@ -= サンプルスクリプト - -* Arrayをリモートから利用してイテレータを試す。 - * darray.rb --- server - * darrayc.rb --- client - -* 簡易チャット - * dchats.rb --- server - * dchatc.rb --- client - -* 分散chasen - * dhasen.rb --- server - * dhasenc.rb --- client - -* 簡易ログサーバ - * dlogd.rb --- server - * dlogc.rb --- client - -* Queueサーバ。 - クライアントdqin.rbはQueueサーバの知らないオブジェクト(DQEntry)を - pushするがDRbUnknownによりクライアントdqout.rbがpopできる。 - * dqueue.rb --- server - * dqin.rb --- client。DQEntryオブジェクトをpushする - * dqout.rb --- client。DQEntryオブジェクトをpopする - * dqlib.rb --- DQEntryを定義したライブラリ - -* 名前による参照 - IdConvをカスタマイズしてidでなく名前で参照する例 - * name.rb --- server - * namec.rb --- client - -* extservのサンプル - * extserv_test.rb - -* TimerIdConvの使用例 - * holders.rb --- server。ruby -d hodlers.rbとするとTimerIdConvを使用する。 - * holderc.rb --- client - -* rinda.rbの使用例 - * rinda_ts.rb --- TupleSpaceサーバ。 - * rindac.rb --- TupleSpaceのclientでアプリケーションのclient - * rindas.rb --- TupleSpaceのclientでアプリケーションのserver - -* observerの使用例 - cdbiff - http://namazu.org/~satoru/cdbiff/ - * dbiff.rb --- dcdbiff server - * dcdbiff.rb --- dcdbiff client - -* drbsslの使用例 - * drbssl_s.rb - * drbssl_c.rb - -* DRbProtocolの追加例 - * http0.rb - * http0serv.rb - -* ringの使用例 - * ring_place.rb - * ring_echo.rb diff --git a/sample/drb/README.rdoc b/sample/drb/README.rdoc deleted file mode 100644 index e6b457bc5c..0000000000 --- a/sample/drb/README.rdoc +++ /dev/null @@ -1,56 +0,0 @@ -= Sample scripts - -* array and iterator - * darray.rb --- server - * darrayc.rb --- client - -* simple chat - * dchats.rb --- server - * dchatc.rb --- client - -* distributed chasen (for Japanese) - * dhasen.rb --- server - * dhasenc.rb --- client - -* simple log server - * dlogd.rb --- server - * dlogc.rb --- client - -* Queue server, and DRbUnknown demo - * dqueue.rb --- server - * dqin.rb --- client. push DQEntry objects. - * dqout.rb --- client. pop DQEntry objects. - * dqlib.rb --- define DQEntry - -* IdConv customize demo: reference by name - * name.rb --- server - * namec.rb --- client - -* extserv - * extserv_test.rb - -* IdConv customize demo 2: using TimerIdConv - * holders.rb --- server - * holderc.rb --- client - -* rinda, remote tuplespace - * rinda_ts.rb --- TupleSpace server. - * rindas.rb --- provide simple service via TupleSpace. - * rindac.rb --- service user - -* observer - cdbiff - http://namazu.org/~satoru/cdbiff/ - * dbiff.rb --- dcdbiff server - * dcdbiff.rb --- dcdbiff client - -* drbssl - * drbssl_s.rb - * drbssl_c.rb - -* add DRbProtocol - * http0.rb - * http0serv.rb - -* Rinda::Ring - * ring_place.rb - * ring_echo.rb diff --git a/sample/drb/acl.rb b/sample/drb/acl.rb deleted file mode 100644 index d93eb9c1fc..0000000000 --- a/sample/drb/acl.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'drb/acl' - -list = %w(deny all - allow 192.168.1.1 - allow ::ffff:192.168.1.2 - allow 192.168.1.3 -) - -addr = ["AF_INET", 10, "lc630", "192.168.1.3"] - -acl = ACL.new -p acl.allow_addr?(addr) - -acl = ACL.new(list, ACL::DENY_ALLOW) -p acl.allow_addr?(addr) diff --git a/sample/drb/darray.rb b/sample/drb/darray.rb deleted file mode 100644 index d2ac39513f..0000000000 --- a/sample/drb/darray.rb +++ /dev/null @@ -1,12 +0,0 @@ -=begin - distributed Ruby --- Array - Copyright (c) 1999-2001 Masatoshi SEKI -=end - -require 'drb/drb' - -here = ARGV.shift -DRb.start_service(here, [1, 2, "III", 4, "five", 6]) -puts DRb.uri -DRb.thread.join - diff --git a/sample/drb/darrayc.rb b/sample/drb/darrayc.rb deleted file mode 100644 index 579e11564e..0000000000 --- a/sample/drb/darrayc.rb +++ /dev/null @@ -1,47 +0,0 @@ -=begin - distributed Ruby --- Array client - Copyright (c) 1999-2001 Masatoshi SEKI -=end - -require 'drb/drb' - -there = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service(nil, nil) -ro = DRbObject.new(nil, there) -p ro.size - -puts "# collect" -a = ro.collect { |x| - x + x -} -p a - -puts "# find" -p ro.find { |x| x.kind_of? String } - -puts "# each, break" -ro.each do |x| - next if x == "five" - puts x -end - -puts "# each, break" -ro.each do |x| - break if x == "five" - puts x -end - -puts "# each, next" -ro.each do |x| - next if x == "five" - puts x -end - -puts "# each, redo" -count = 0 -ro.each do |x| - count += 1 - puts count - redo if count == 3 -end diff --git a/sample/drb/dbiff.rb b/sample/drb/dbiff.rb deleted file mode 100644 index 290eb1d28b..0000000000 --- a/sample/drb/dbiff.rb +++ /dev/null @@ -1,51 +0,0 @@ -# -# dbiff.rb - distributed cdbiff (server) -# * original: cdbiff by Satoru Takabayashi - -require 'drb/drb' -require 'drb/eq' -require 'drb/observer' - -class Biff - include DRb::DRbObservable - - def initialize(filename, interval) - super() - @filename = filename - @interval = interval - end - - def run - last = Time.now - while true - begin - sleep(@interval) - current = File::mtime(@filename) - if current > last - changed - begin - notify_observers(@filename, current) - rescue Error - end - last = current - end - rescue - next - end - end - end -end - -def main - filename = "/var/mail/#{ENV['USER']}" - interval = 15 - uri = 'druby://:19903' - - biff = Biff.new(filename, interval) - - DRb.start_service(uri, biff) - biff.run -end - -main - diff --git a/sample/drb/dcdbiff.rb b/sample/drb/dcdbiff.rb deleted file mode 100644 index 6a24680c33..0000000000 --- a/sample/drb/dcdbiff.rb +++ /dev/null @@ -1,43 +0,0 @@ -# -# dcdbiff.rb - distributed cdbiff (client) -# * original: cdbiff by Satoru Takabayashi - -require 'drb/drb' -require 'drb/eq' - -class Notify - include DRbUndumped - - def initialize(biff, command) - @biff = biff - @command = command - - @biff.add_observer(self) - end - - def update(filename, time) - p [filename, time] if $DEBUG - system(@command) - end - - def done - begin - @biff.delete_observer(self) - rescue - end - end -end - -def main - command = 'eject' - uri = 'druby://localhost:19903' - - DRb.start_service - biff = DRbObject.new(nil, uri) - notify = Notify.new(biff, command) - - trap("INT"){ notify.done } - DRb.thread.join -end - -main diff --git a/sample/drb/dchatc.rb b/sample/drb/dchatc.rb deleted file mode 100644 index 2b8ddbf4cc..0000000000 --- a/sample/drb/dchatc.rb +++ /dev/null @@ -1,41 +0,0 @@ -=begin - distributed Ruby --- chat client - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' - -class ChatClient - include DRbUndumped - - def initialize(name) - @name = name - @key = nil - end - attr_reader(:name) - attr_accessor(:key) - - def message(there, str) - raise 'invalid key' unless @key == there - puts str - end -end - -if __FILE__ == $0 - begin - there = ARGV.shift - name = ARGV.shift - raise "usage" unless (there and name) - rescue - $stderr.puts("usage: #{$0} ") - exit 1 - end - DRb.start_service - ro = DRbObject.new(nil, there) - - chat = ChatClient.new(name) - entry = ro.add_member(chat) - while gets - entry.say($_) - end -end diff --git a/sample/drb/dchats.rb b/sample/drb/dchats.rb deleted file mode 100644 index c96486a452..0000000000 --- a/sample/drb/dchats.rb +++ /dev/null @@ -1,69 +0,0 @@ -=begin - distributed Ruby --- chat server - Copyright (c) 1999-2000 Masatoshi SEKI -=end -require 'drb/drb' - -class ChatEntry - include DRbUndumped - - def initialize(server, there) - @server = server - @there = there - @name = there.name - @key = there.key = Time.now - end - attr :name, true - attr :there - - def say(str) - @server.distribute(@there, str) - end - - def listen(str) - @there.message(@key, str) - end -end - - -class ChatServer - def initialize - @mutex = Thread::Mutex.new - @members = {} - end - - def add_member(there) - client = ChatEntry.new(self, there) - @mutex.synchronize do - @members[there] = client - end - client - end - - def distribute(there, str) - name = @members[there].name - msg = "<#{name}> #{str}" - msg2 = ">#{name}< #{str}" - @mutex.synchronize do - for m in @members.keys - begin - if m == there - @members[m].listen(msg2) - else - @members[m].listen(msg) - end - rescue - p $! - @members.delete(m) - end - end - end - end -end - -if __FILE__ == $0 - here = ARGV.shift - DRb.start_service(here, ChatServer.new) - puts DRb.uri - DRb.thread.join -end diff --git a/sample/drb/dhasen.rb b/sample/drb/dhasen.rb deleted file mode 100644 index 13ff38940e..0000000000 --- a/sample/drb/dhasen.rb +++ /dev/null @@ -1,41 +0,0 @@ -=begin - distributed Ruby --- dRuby Sample Server --- chasen server - Copyright (c) 1999-2001 Masatoshi SEKI -=end - -=begin - How to play. - - Terminal 1 - | % ruby dhasen.rb - | druby://yourhost:7640 - - Terminal 2 - | % ruby dhasenc.rb druby://yourhost:7640 - -=end - -require 'drb/drb' -require 'chasen' - -class Dhasen - include DRbUndumped - - def initialize - @mutex = Thread::Mutex.new - end - - def sparse(str, *arg) - @mutex.synchronize do - Chasen.getopt(*arg) - Chasen.sparse(str) - end - end -end - -if __FILE__ == $0 - DRb.start_service(nil, Dhasen.new) - puts DRb.uri - DRb.thread.join -end - diff --git a/sample/drb/dhasenc.rb b/sample/drb/dhasenc.rb deleted file mode 100644 index dddac9882c..0000000000 --- a/sample/drb/dhasenc.rb +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- -=begin - distributed Ruby --- dRuby Sample Client -- chasen client - Copyright (c) 1999-2001 Masatoshi SEKI -=end - -require 'drb/drb' - -there = ARGV.shift || raise("usage: #{$0} ") -DRb.start_service -dhasen = DRbObject.new(nil, there) - -print dhasen.sparse("本日は、晴天なり。", "-F", '(%BB %m %M)\n', "-j") -print dhasen.sparse("本日は、晴天なり。", "-F", '(%m %M)\n') diff --git a/sample/drb/dlogc.rb b/sample/drb/dlogc.rb deleted file mode 100644 index 3939a71827..0000000000 --- a/sample/drb/dlogc.rb +++ /dev/null @@ -1,16 +0,0 @@ -=begin - distributed Ruby --- Log test - Copyright (c) 1999-2001 Masatoshi SEKI -=end - -require 'drb/drb' - -there = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service -ro = DRbObject.new(nil, there) -ro.log(123) -ro.log("hello") -sleep 2 -ro.log("wakeup") - diff --git a/sample/drb/dlogd.rb b/sample/drb/dlogd.rb deleted file mode 100644 index a87e660346..0000000000 --- a/sample/drb/dlogd.rb +++ /dev/null @@ -1,38 +0,0 @@ -=begin - distributed Ruby --- Log server - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' - -class Logger - def initialize(fname) - @fname = fname.to_s - @fp = File.open(@fname, "a+") - @queue = Thread::Queue.new - @th = Thread.new { self.flush } - end - - def log(str) - @queue.push("#{Time.now}\t" + str.to_s) - end - - def flush - begin - while(1) - @fp.puts(@queue.pop) - @fp.flush - end - ensure - @fp.close - end - end -end - -if __FILE__ == $0 - here = ARGV.shift - DRb.start_service(here, Logger.new('/usr/tmp/dlogd.log')) - puts DRb.uri - DRb.thread.join -end - diff --git a/sample/drb/dqin.rb b/sample/drb/dqin.rb deleted file mode 100644 index 4751335fff..0000000000 --- a/sample/drb/dqin.rb +++ /dev/null @@ -1,13 +0,0 @@ -=begin - distributed Ruby --- store - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' -require 'dqlib' - -there = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service -queue = DRbObject.new(nil, there) -queue.push(DQEntry.new(DRb.uri)) diff --git a/sample/drb/dqlib.rb b/sample/drb/dqlib.rb deleted file mode 100644 index 75f2e6115b..0000000000 --- a/sample/drb/dqlib.rb +++ /dev/null @@ -1,14 +0,0 @@ -class DQEntry - def initialize(name) - @name = name - end - - def greeting - "Hello, This is #{@name}." - end - alias to_s greeting -end - -if __FILE__ == $0 - puts DQEntry.new('DQEntry') -end diff --git a/sample/drb/dqout.rb b/sample/drb/dqout.rb deleted file mode 100644 index f2b0b4ac95..0000000000 --- a/sample/drb/dqout.rb +++ /dev/null @@ -1,14 +0,0 @@ -=begin - distributed Ruby --- fetch - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' -require 'dqlib' - -there = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service -queue = DRbObject.new(nil, there) -entry = queue.pop -puts entry.greeting diff --git a/sample/drb/dqueue.rb b/sample/drb/dqueue.rb deleted file mode 100644 index a9afa8c858..0000000000 --- a/sample/drb/dqueue.rb +++ /dev/null @@ -1,11 +0,0 @@ -=begin - distributed Ruby --- Queue - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' - -DRb.start_service(nil, Thread::Queue.new) -puts DRb.uri -DRb.thread.join - diff --git a/sample/drb/drbc.rb b/sample/drb/drbc.rb deleted file mode 100644 index 50a86c39e8..0000000000 --- a/sample/drb/drbc.rb +++ /dev/null @@ -1,45 +0,0 @@ -=begin - distributed Ruby --- dRuby Sample Client - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' - -class DRbEx2 - include DRbUndumped - - def initialize(n) - @n = n - end - - def to_i - @n.to_i - end -end - -if __FILE__ == $0 - there = ARGV.shift - unless there - $stderr.puts("usage: #{$0} ") - exit 1 - end - - DRb.start_service() - ro = DRbObject.new_with_uri(there) - - puts ro - p ro.to_a - puts ro.hello - p ro.hello - puts ro.sample(DRbEx2.new(1), 2, 3) - puts ro.sample(1, ro.sample(DRbEx2.new(1), 2, 3), DRbEx2.new(3)) - - begin - ro.err - rescue DRb::DRbUnknownError - p $! - p $!.unknown - rescue RuntimeError - p $! - end -end diff --git a/sample/drb/drbch.rb b/sample/drb/drbch.rb deleted file mode 100644 index 07fdcd5fae..0000000000 --- a/sample/drb/drbch.rb +++ /dev/null @@ -1,48 +0,0 @@ -=begin - distributed Ruby --- dRuby Sample Client - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' -require 'drb/http' - -class DRbEx2 - include DRbUndumped - - def initialize(n) - @n = n - end - - def to_i - @n.to_i - end -end - -if __FILE__ == $0 - there = ARGV.shift - unless there - $stderr.puts("usage: #{$0} ") - exit 1 - end - - DRb::DRbConn.proxy_map['x68k'] = 'http://x68k/~mas/http_cgi.rb' - - DRb.start_service() - ro = DRbObject.new(nil, there) - - puts ro - p ro.to_a - puts ro.hello - p ro.hello - puts ro.sample(DRbEx2.new(1), 2, 3) - puts ro.sample(1, ro.sample(DRbEx2.new(1), 2, 3), DRbEx2.new(3)) - - begin - ro.err - rescue DRb::DRbUnknownError - p $! - p $!.unknown - rescue RuntimeError - p $! - end -end diff --git a/sample/drb/drbm.rb b/sample/drb/drbm.rb deleted file mode 100644 index 3390608cd1..0000000000 --- a/sample/drb/drbm.rb +++ /dev/null @@ -1,60 +0,0 @@ -=begin - multiple DRbServer - Copyright (c) 1999-2002 Masatoshi SEKI -=end - -=begin - How to play. - - Terminal 1 - | % ruby drbm.rb - | druby://yourhost:7640 druby://yourhost:7641 - - Terminal 2 - | % ruby drbmc.rb druby://yourhost:7640 druby://yourhost:7641 - | [#, "FOO"] - | [#, "FOO"] - -=end - -require 'drb/drb' - -class Hoge - include DRbUndumped - def initialize(s) - @str = s - end - - def to_s - @str - end -end - -class Foo - def initialize(s='FOO') - @hoge = Hoge.new(s) - end - - def hello - @hoge - end -end - -class Bar < Foo - def initialize(foo) - @hoge = foo.hello - end -end - - -if __FILE__ == $0 - foo = Foo.new - s1 = DRb::DRbServer.new('druby://:7640', foo) - s2 = DRb::DRbServer.new('druby://:7641', Bar.new(foo)) - - puts "#{s1.uri} #{s2.uri}" - - s1.thread.join - s2.thread.join -end - diff --git a/sample/drb/drbmc.rb b/sample/drb/drbmc.rb deleted file mode 100644 index fd191401e6..0000000000 --- a/sample/drb/drbmc.rb +++ /dev/null @@ -1,22 +0,0 @@ -=begin - multiple DRbServer client - Copyright (c) 1999-2002 Masatoshi SEKI -=end - -require 'drb/drb' - -if __FILE__ == $0 - s1 = ARGV.shift - s2 = ARGV.shift - unless s1 && s2 - $stderr.puts("usage: #{$0} ") - exit 1 - end - - DRb.start_service() - r1 = DRbObject.new(nil, s1) - r2 = DRbObject.new(nil, s2) - - p [r1.hello, r1.hello.to_s] - p [r2.hello, r2.hello.to_s] -end diff --git a/sample/drb/drbs-acl.rb b/sample/drb/drbs-acl.rb deleted file mode 100644 index 71c4f7bf42..0000000000 --- a/sample/drb/drbs-acl.rb +++ /dev/null @@ -1,51 +0,0 @@ -=begin - distributed Ruby --- dRuby Sample Server - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -=begin - How to play. - - Terminal 1 - | % ruby drbs.rb - | druby://yourhost:7640 - - Terminal 2 - | % ruby drbc.rb druby://yourhost:7640 - | "hello" - | 6 - | 10 - -=end - -require 'drb/drb' -require 'acl' - -class DRbEx - def initialize - @hello = 'hello' - end - - def hello - info = Thread.current['DRb'] - p info['socket'].peeraddr if info - @hello - end - - def sample(a, b, c) - a.to_i + b.to_i + c.to_i - end -end - -if __FILE__ == $0 - acl = ACL.new(%w(deny all - allow 192.168.1.* - allow localhost)) - - DRb.install_acl(acl) - - DRb.start_service(nil, DRbEx.new) - puts DRb.uri - DRb.thread.join -end - diff --git a/sample/drb/drbs.rb b/sample/drb/drbs.rb deleted file mode 100644 index 5a913d9918..0000000000 --- a/sample/drb/drbs.rb +++ /dev/null @@ -1,64 +0,0 @@ -=begin - distributed Ruby --- dRuby Sample Server - Copyright (c) 1999-2000,2002 Masatoshi SEKI -=end - -=begin - How to play. - - Terminal 1 - | % ruby drbs.rb - | druby://yourhost:7640 - - Terminal 2 - | % ruby drbc.rb druby://yourhost:7640 - | "hello" - | .... - -=end - -require 'drb/drb' - -class DRbEx - include DRbUndumped - - def initialize - @hello = 'hello' - end - - def hello - cntxt = Thread.current['DRb'] - if cntxt - p cntxt['server'].uri - p cntxt['client'].peeraddr - end - Foo::Unknown.new - end - - def err - raise FooError - end - - def sample(a, b, c) - a.to_i + b.to_i + c.to_i - end -end - -class Foo - class Unknown - end -end - -class FooError < RuntimeError -end - -if __FILE__ == $0 - DRb.start_service(ARGV.shift || 'druby://:7640', DRbEx.new) - puts DRb.uri - Thread.new do - sleep 10 - DRb.stop_service - end - DRb.thread.join -end - diff --git a/sample/drb/drbssl_c.rb b/sample/drb/drbssl_c.rb deleted file mode 100644 index 65112f6e78..0000000000 --- a/sample/drb/drbssl_c.rb +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env ruby - -require 'drb' -require 'drb/ssl' - -there = ARGV.shift || "drbssl://localhost:3456" - -config = Hash.new -config[:SSLVerifyMode] = OpenSSL::SSL::VERIFY_PEER -config[:SSLVerifyCallback] = lambda{|ok,x509_store| - p [ok, x509_store.error_string] - true -} - -DRb.start_service(nil,nil,config) -h = DRbObject.new(nil, there) -while line = gets - p h.hello(line.chomp) -end diff --git a/sample/drb/drbssl_s.rb b/sample/drb/drbssl_s.rb deleted file mode 100644 index 4d96f591d4..0000000000 --- a/sample/drb/drbssl_s.rb +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env ruby - -require 'drb' -require 'drb/ssl' - -here = ARGV.shift || "drbssl://localhost:3456" - -class HelloWorld - include DRbUndumped - - def hello(name) - "Hello, #{name}." - end -end - -config = Hash.new -config[:verbose] = true -begin - data = open("sample.key"){|io| io.read } - config[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(data) - data = open("sample.crt"){|io| io.read } - config[:SSLCertificate] = OpenSSL::X509::Certificate.new(data) -rescue - $stderr.puts "Switching to use self-signed certificate" - config[:SSLCertName] = - [ ["C","JP"], ["O","Foo.DRuby.Org"], ["CN", "Sample"] ] -end - -DRb.start_service(here, HelloWorld.new, config) -puts DRb.uri -DRb.thread.join diff --git a/sample/drb/extserv_test.rb b/sample/drb/extserv_test.rb deleted file mode 100644 index 2c4f485dc6..0000000000 --- a/sample/drb/extserv_test.rb +++ /dev/null @@ -1,80 +0,0 @@ -=begin - dRuby sample - Copyright (c) 2000 Masatoshi SEKI - -= How to play - -* Terminal 1 - - % ruby -I. extserv_test.rb server - druby://yourhost:12345 - -* Terminal 2 - - % ruby -I. extserv_test.rb druby://yourhost:12345 - ... - -=end - -require 'drb/drb' - -def ARGV.shift - it = super() - raise "usage:\nserver: #{$0} server []\nclient: #{$0} [quit] " unless it - it -end - -class Foo - include DRbUndumped - - def initialize(str) - @str = str - end - - def hello(it) - "#{it}: #{self}" - end - - def to_s - @str - end -end - -cmd = ARGV.shift -case cmd -when 'itest1', 'itest2' - require 'drb/extserv' - - front = Foo.new(cmd) - server = DRb::DRbServer.new(nil, front) - es = DRb::ExtServ.new(ARGV.shift, ARGV.shift, server) - server.thread.join - -when 'server' - require 'drb/extservm' - - DRb::ExtServManager.command['itest1'] = "ruby -I. #{$0} itest1" - DRb::ExtServManager.command['itest2'] = "ruby -I. #{$0} itest2" - - s = DRb::ExtServManager.new - DRb.start_service(ARGV.shift, s) - puts DRb.uri - DRb.thread.join - - -else - uri = (cmd == 'quit') ? ARGV.shift : cmd - - DRb.start_service - s = DRbObject.new(nil, uri) - t1 = s.service('itest1').front - puts t1 - t2 = s.service('itest2').front - puts t2 - puts t1.hello(t2) - if (cmd == 'quit') - s.service('itest1').stop_service - s.service('itest2').stop_service - end -end - diff --git a/sample/drb/gw_ct.rb b/sample/drb/gw_ct.rb deleted file mode 100644 index 0622784018..0000000000 --- a/sample/drb/gw_ct.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'drb/drb' - -class Foo - include DRbUndumped - - def foo(n) - n + n - end - - def bar(n) - yield(n) + yield(n) - end -end - -DRb.start_service(nil) -puts DRb.uri - -ro = DRbObject.new(nil, ARGV.shift) -ro[:tcp] = Foo.new -gets - -it = ro[:unix] -p [it, it.foo(1)] -gets - -p it.bar('2') {|n| n * 3} -gets - - diff --git a/sample/drb/gw_cu.rb b/sample/drb/gw_cu.rb deleted file mode 100644 index 8079cbdc4f..0000000000 --- a/sample/drb/gw_cu.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'drb/drb' -require 'drb/unix' - -class Foo - include DRbUndumped - - def foo(n) - n + n - end - - def bar(n) - yield(n) + yield(n) - end -end - -DRb.start_service('drbunix:', nil) -puts DRb.uri - -ro = DRbObject.new(nil, ARGV.shift) -ro[:unix] = Foo.new -gets - -it = ro[:tcp] -p [it, it.foo(1)] -gets - -p it.bar('2') {|n| n * 3} -gets diff --git a/sample/drb/gw_s.rb b/sample/drb/gw_s.rb deleted file mode 100644 index c2bea0baad..0000000000 --- a/sample/drb/gw_s.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'drb/drb' -require 'drb/unix' -require 'drb/gw' - -DRb.install_id_conv(DRb::GWIdConv.new) -gw = DRb::GW.new -s1 = DRb::DRbServer.new(ARGV.shift, gw) -s2 = DRb::DRbServer.new(ARGV.shift, gw) -s1.thread.join -s2.thread.join diff --git a/sample/drb/holderc.rb b/sample/drb/holderc.rb deleted file mode 100644 index e627916d76..0000000000 --- a/sample/drb/holderc.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'drb/drb' - -begin - there = ARGV.shift || raise -rescue - $stderr.puts("usage: #{$0} ") - exit 1 -end - -DRb.start_service() -ro = DRbObject.new(nil, there) - -ary = [] -10.times do - ary.push(ro.gen) -end - -sleep 5 if $DEBUG - -ary.each do |e| - p e.sample([1]) -end diff --git a/sample/drb/holders.rb b/sample/drb/holders.rb deleted file mode 100644 index 293426faa5..0000000000 --- a/sample/drb/holders.rb +++ /dev/null @@ -1,63 +0,0 @@ -=begin -= How to play. - -== with timeridconv: - % ruby -d holders.rb - druby://yourhost:1234 - - % ruby holderc.rb druby://yourhost:1234 - - -== without timeridconv: - % ruby holders.rb - druby://yourhost:1234 - - % ruby holderc.rb druby://yourhost:1234 -=end - - -require 'drb/drb' - -class DRbEx3 - include DRbUndumped - - def initialize(n) - @v = n - end - - def sample(list) - sum = 0 - list.each do |e| - sum += e.to_i - end - @v * sum - end -end - -class DRbEx4 - include DRbUndumped - - def initialize - @curr = 1 - end - - def gen - begin - @curr += 1 - DRbEx3.new(@curr) - ensure - GC.start - end - end -end - -if __FILE__ == $0 - if $DEBUG - require 'drb/timeridconv' - DRb.install_id_conv(DRb::TimerIdConv.new(2)) - end - - DRb.start_service(nil, DRbEx4.new) - puts DRb.uri - DRb.thread.join -end diff --git a/sample/drb/http0.rb b/sample/drb/http0.rb deleted file mode 100644 index e40d810311..0000000000 --- a/sample/drb/http0.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'drb/drb' -require 'net/http' -require 'uri' - -module DRb - module HTTP0 - class StrStream - def initialize(str='') - @buf = str - end - attr_reader :buf - - def read(n) - begin - return @buf[0,n] - ensure - @buf[0,n] = '' - end - end - - def write(s) - @buf.concat s - end - end - - def self.uri_option(uri, config) - return uri, nil - end - - def self.open(uri, config) - unless /^http:/ =~ uri - raise(DRbBadScheme, uri) unless uri =~ /^http:/ - raise(DRbBadURI, 'can\'t parse uri:' + uri) - end - ClientSide.new(uri, config) - end - - class ClientSide - def initialize(uri, config) - @uri = uri - @res = nil - @config = config - @msg = DRbMessage.new(config) - @proxy = ENV['HTTP_PROXY'] - end - - def close; end - def alive?; false; end - - def send_request(ref, msg_id, *arg, &b) - stream = StrStream.new - @msg.send_request(stream, ref, msg_id, *arg, &b) - @reply_stream = StrStream.new - post(@uri, stream.buf) - end - - def recv_reply - @msg.recv_reply(@reply_stream) - end - - def post(url, data) - it = URI.parse(url) - path = [(it.path=='' ? '/' : it.path), it.query].compact.join('?') - http = Net::HTTP.new(it.host, it.port) - sio = StrStream.new - http.post(path, data, {'Content-Type'=>'application/octetstream;'}) do |str| - sio.write(str) - if @config[:load_limit] < sio.buf.size - raise TypeError, 'too large packet' - end - end - @reply_stream = sio - end - end - end - DRbProtocol.add_protocol(HTTP0) -end diff --git a/sample/drb/http0serv.rb b/sample/drb/http0serv.rb deleted file mode 100644 index 2e853312e1..0000000000 --- a/sample/drb/http0serv.rb +++ /dev/null @@ -1,120 +0,0 @@ -require 'webrick' -require 'drb/drb' -require_relative 'http0' - -module DRb - module HTTP0 - - def self.open_server(uri, config) - unless /^http:/ =~ uri - raise(DRbBadScheme, uri) unless uri =~ /^http:/ - raise(DRbBadURI, 'can\'t parse uri:' + uri) - end - Server.new(uri, config) - end - - class Callback < WEBrick::HTTPServlet::AbstractServlet - def initialize(config, drb) - @config = config - @drb = drb - @queue = Thread::Queue.new - end - - def do_POST(req, res) - @req = req - @res = res - @drb.push(self) - @res.body = @queue.pop - @res['content-type'] = 'application/octet-stream;' - end - - def req_body - @req.body - end - - def reply(body) - @queue.push(body) - end - - def close - @queue.push('') - end - end - - class Server - def initialize(uri, config) - @uri = uri - @config = config - @queue = Thread::Queue.new - setup_webrick(uri) - end - attr_reader :uri - - def close - @server.shutdown if @server - @server = nil - end - - def push(callback) - @queue.push(callback) - end - - def accept - client = @queue.pop - ServerSide.new(uri, client, @config) - end - - def setup_webrick(uri) - logger = WEBrick::Log::new($stderr, WEBrick::Log::FATAL) - u = URI.parse(uri) - s = WEBrick::HTTPServer.new(:Port => u.port, - :AddressFamily => Socket::AF_INET, - :BindAddress => u.host, - :Logger => logger, - :ServerType => Thread) - s.mount(u.path, Callback, self) - @server = s - s.start - end - end - - class ServerSide - def initialize(uri, callback, config) - @uri = uri - @callback = callback - @config = config - @msg = DRbMessage.new(@config) - @req_stream = StrStream.new(@callback.req_body) - end - attr_reader :uri - - def close - @callback.close if @callback - @callback = nil - end - - def alive?; false; end - - def recv_request - begin - @msg.recv_request(@req_stream) - rescue - close - raise $! - end - end - - def send_reply(succ, result) - begin - return unless @callback - stream = StrStream.new - @msg.send_reply(stream, succ, result) - @callback.reply(stream.buf) - rescue - close - raise $! - end - end - end - end -end diff --git a/sample/drb/name.rb b/sample/drb/name.rb deleted file mode 100644 index 6d88186dab..0000000000 --- a/sample/drb/name.rb +++ /dev/null @@ -1,117 +0,0 @@ -=begin - distributed Ruby --- NamedObject Sample - Copyright (c) 2000-2001 Masatoshi SEKI -=end - -=begin -How to play. - -* start server - Terminal 1 - | % ruby name.rb druby://yourhost:7640 - | druby://yourhost:7640 - | [return] to exit - -* start client - Terminal 2 - | % ruby namec.rb druby://yourhost:7640 - | # - | # - | 1 - | 2 - | [return] to continue - -* restart server - Terminal 1 - type [return] - | % ruby name.rb druby://yourhost:7640 - | druby://yourhost:7640 - | [return] to exit - -* continue client - Terminal 2 - type [return] - | 1 - | 2 -=end - -require 'drb/drb' - -module DRbNamedObject - DRbNAMEDICT = {} - DRBNAMEMUTEX = Thread::Mutex.new - attr_reader(:drb_name) - - def drb_name=(name) - @drb_name = name - DRBNAMEMUTEX.synchronize do - raise(IndexError, name) if DRbNAMEDICT[name] - DRbNAMEDICT[name] = self - end - end -end - -class DRbNamedIdConv < DRb::DRbIdConv - def initialize - @dict = DRbNamedObject::DRbNAMEDICT - end - - def to_obj(ref) - @dict.fetch(ref) do super end - end - - def to_id(obj) - if obj.kind_of? DRbNamedObject - return obj.drb_name - else - return super - end - end -end - -class Seq - include DRbUndumped - include DRbNamedObject - - def initialize(v, name) - @counter = v - @mutex = Thread::Mutex.new - self.drb_name = name - end - - def next_value - @mutex.synchronize do - @counter += 1 - return @counter - end - end -end - -class Front - def initialize - seq = Seq.new(0, 'seq') - mutex = Thread::Mutex.new - mutex.extend(DRbUndumped) - mutex.extend(DRbNamedObject) - mutex.drb_name = 'mutex' - @name = {} - @name['seq'] = seq - @name['mutex'] = mutex - end - - def [](k) - @name[k] - end -end - -if __FILE__ == $0 - uri = ARGV.shift - - name_conv = DRbNamedIdConv.new - - DRb.install_id_conv(name_conv) - DRb.start_service(uri, Front.new) - puts DRb.uri - DRb.thread.join -end - diff --git a/sample/drb/namec.rb b/sample/drb/namec.rb deleted file mode 100644 index 98b9d0e532..0000000000 --- a/sample/drb/namec.rb +++ /dev/null @@ -1,36 +0,0 @@ -=begin - distributed Ruby --- NamedObject Sample Client - Copyright (c) 2000-2001 Masatoshi SEKI -=end - -require 'drb/drb' - -begin - there = ARGV.shift || raise -rescue - puts "usage: #{$0} " - exit 1 -end - -DRb.start_service() -ro = DRbObject.new(nil, there) - -seq = ro["seq"] -mutex = ro["mutex"] - -p seq -p mutex - -mutex.synchronize do - p seq.next_value - p seq.next_value -end - -puts '[return] to continue' -gets - -mutex.synchronize do - p seq.next_value - p seq.next_value -end - diff --git a/sample/drb/old_tuplespace.rb b/sample/drb/old_tuplespace.rb deleted file mode 100644 index 2d5310086e..0000000000 --- a/sample/drb/old_tuplespace.rb +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/local/bin/ruby -# TupleSpace -# Copyright (c) 1999-2000 Masatoshi SEKI -# You can redistribute it and/or modify it under the same terms as Ruby. - -class TupleSpace - class Template - def initialize(list) - @list = list - @check_idx = [] - @list.each_with_index do |x, i| - @check_idx.push i if x - end - @size = @list.size - end - - attr :size - alias length size - - def match(tuple) - return nil if tuple.size != self.size - @check_idx.each do |i| - unless @list[i] === tuple[i] - return false - end - end - return true - end - end - - def initialize - @que = {} - @waiting = {} - @que.taint # enable tainted communication - @waiting.taint - self.taint - end - - def wakeup_waiting(tuple) - sz = tuple.length - return nil unless @waiting[sz] - - x = nil - i = -1 - found = false - @waiting[sz] = @waiting[sz].find_all { |x| - if x[0].match(tuple) - begin - x[1].wakeup - rescue ThreadError - end - false - else - true - end - } - end - - def put_waiting(template, thread) - sz = template.length - @waiting[sz] = [] unless @waiting[sz] - @waiting[sz].push([Template.new(template), thread]) - end - private :wakeup_waiting - private :put_waiting - - def get_que(template) - sz = template.length - return nil unless @que[sz] - - template = Template.new(template) - - x = nil - i = -1 - found = false - @que[sz].each_with_index do |x, i| - if template.match(x) - found = true - break - end - end - return nil unless found - - @que[sz].delete_at(i) - - return x - end - - def put_que(tuple) - sz = tuple.length - @que[sz] = [] unless @que[sz] - @que[sz].push tuple - end - private :get_que - private :put_que - - def out(*tuples) - tuples.each do |tuple| - Thread.critical = true - put_que(tuple) - wakeup_waiting(tuple) - Thread.critical = false - end - end - alias put out - alias write out - - def in(template, non_block=false) - begin - loop do - Thread.critical = true - tuple = get_que(template) - unless tuple - if non_block - raise ThreadError, "queue empty" - end - put_waiting(template, Thread.current) - Thread.stop - else - return tuple - end - end - ensure - Thread.critical = false - end - end - alias get in - alias take in - - def rd(template, non_block=false) - tuple = self.in(template, non_block) - out(tuple) - tuple - end - alias read rd - - def mv(dest, template, non_block=false) - tuple = self.in(template, non_block) - begin - dest.out(tuple) - rescue - self.out(tuple) - end - end - alias move mv -end - -if __FILE__ == $0 - ts = TupleSpace.new - clients = [] - servers = [] - - def server(ts, id) - Thread.start { - loop do - req = ts.in(['req', nil, nil]) - ac = req[1] - num = req[2] - sleep id - ts.out([ac, id, num, num * num]) - end - } - end - - def client(ts, n) - Thread.start { - ac = Object.new - tuples = (1..10).collect { |i| - ['req', ac, i * 10 + n] - } - ts.out(*tuples) - ts.out(tuples[0]) - puts "out: #{n}" - 11.times do |i| - ans = ts.in([ac, nil, nil, nil]) - puts "client(#{n}) server(#{ans[1]}) #{ans[2]} #{ans[3]}" - end - } - end - - def watcher(ts) - Thread.start { - loop do - begin - sleep 1 - p ts.rd(['req', nil, nil], true) - rescue ThreadError - puts "'req' not found." - end - end - } - end - - (0..3).each do |n| - servers.push(server(ts, n)) - end - - (1..6).each do |n| - clients.push(client(ts, n)) - end - - (1..3).each do - watcher(ts) - end - - clients.each do |t| - t.join - end -end - - - diff --git a/sample/drb/rinda_ts.rb b/sample/drb/rinda_ts.rb deleted file mode 100644 index 6f2fae5c0f..0000000000 --- a/sample/drb/rinda_ts.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'drb/drb' -require 'rinda/tuplespace' - -uri = ARGV.shift -DRb.start_service(uri, Rinda::TupleSpace.new) -puts DRb.uri -DRb.thread.join diff --git a/sample/drb/rindac.rb b/sample/drb/rindac.rb deleted file mode 100644 index 72be09deaf..0000000000 --- a/sample/drb/rindac.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'drb/drb' -require 'rinda/rinda' - -uri = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service -ts = Rinda::TupleSpaceProxy.new(DRbObject.new(nil, uri)) - -(1..10).each do |n| - ts.write(['sum', DRb.uri, n]) -end - -(1..10).each do |n| - ans = ts.take(['ans', DRb.uri, n, nil]) - p [ans[2], ans[3]] -end - diff --git a/sample/drb/rindas.rb b/sample/drb/rindas.rb deleted file mode 100644 index 9fd9ada2d1..0000000000 --- a/sample/drb/rindas.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'drb/drb' -require 'rinda/rinda' - -def do_it(v) - puts "do_it(#{v})" - v + v -end - -uri = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service -ts = Rinda::TupleSpaceProxy.new(DRbObject.new(nil, uri)) - -while true - r = ts.take(['sum', nil, nil]) - v = do_it(r[2]) - ts.write(['ans', r[1], r[2], v]) -end diff --git a/sample/drb/ring_echo.rb b/sample/drb/ring_echo.rb deleted file mode 100644 index c54628b54c..0000000000 --- a/sample/drb/ring_echo.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'drb/drb' -require 'drb/eq' -require 'rinda/ring' - -class RingEcho - include DRbUndumped - def initialize(name) - @name = name - end - - def echo(str) - "#{@name}: #{str}" - end -end - -DRb.start_service - -renewer = Rinda::SimpleRenewer.new - -finder = Rinda::RingFinger.new -ts = finder.lookup_ring_any -ts.read_all([:name, :RingEcho, nil, nil]).each do |tuple| - p tuple[2] - puts tuple[2].echo('Hello, World') rescue nil -end -ts.write([:name, :RingEcho, RingEcho.new(DRb.uri), ''], renewer) - -DRb.thread.join - diff --git a/sample/drb/ring_inspect.rb b/sample/drb/ring_inspect.rb deleted file mode 100644 index c096cd7034..0000000000 --- a/sample/drb/ring_inspect.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'rinda/ring' -require 'drb/drb' - -class Inspector - def initialize - end - - def primary - Rinda::RingFinger.primary - end - - def list_place - Rinda::RingFinger.to_a - end - - def list(idx = -1) - if idx < 0 - ts = primary - else - ts = list_place[idx] - raise "RingNotFound" unless ts - end - ts.read_all([:name, nil, nil, nil]) - end -end - -def main - DRb.start_service - r = Inspector.new -end diff --git a/sample/drb/ring_place.rb b/sample/drb/ring_place.rb deleted file mode 100644 index 11c6c2fe80..0000000000 --- a/sample/drb/ring_place.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'drb/drb' -require 'rinda/ring' -require 'rinda/tuplespace' - -unless $DEBUG - # Run as a daemon... - exit!( 0 ) if fork - Process.setsid - exit!( 0 ) if fork -end - -DRb.start_service(ARGV.shift) - -ts = Rinda::TupleSpace.new -place = Rinda::RingServer.new(ts) - -if $DEBUG - puts DRb.uri - DRb.thread.join -else - STDIN.reopen(IO::NULL) - STDOUT.reopen(IO::NULL, 'w') - STDERR.reopen(IO::NULL, 'w') - DRb.thread.join -end diff --git a/sample/drb/simpletuple.rb b/sample/drb/simpletuple.rb deleted file mode 100644 index 4bb4b1cff9..0000000000 --- a/sample/drb/simpletuple.rb +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/local/bin/ruby -# SimpleTupleSpace -# Copyright (c) 1999-2000 Masatoshi SEKI -# You can redistribute it and/or modify it under the same terms as Ruby. - -class SimpleTupleSpace - def initialize - @hash = {} - @waiting = {} - @hash.taint - @waiting.taint - self.taint - end - - def out(key, obj) - Thread.critical = true - @hash[key] ||= [] - @waiting[key] ||= [] - @hash[key].push obj - begin - t = @waiting[key].shift - @waiting.delete(key) if @waiting[key].length == 0 - t.wakeup if t - rescue ThreadError - retry - ensure - Thread.critical = false - end - end - - def in(key) - Thread.critical = true - @hash[key] ||= [] - @waiting[key] ||= [] - begin - loop do - if @hash[key].length == 0 - @waiting[key].push Thread.current - Thread.stop - else - return @hash[key].shift - end - end - ensure - @hash.delete(key) if @hash[key].length == 0 - Thread.critical = false - end - end -end - -if __FILE__ == $0 - ts = SimpleTupleSpace.new - clients = [] - servers = [] - - def server(ts) - Thread.start { - loop do - req = ts.in('req') - ac = req[0] - num = req[1] - ts.out(ac, num * num) - end - } - end - - def client(ts, n) - Thread.start { - ac = Object.new - ts.out('req', [ac, n]) - ans = ts.in(ac) - puts "#{n}: #{ans}" - } - end - - 3.times do - servers.push(server(ts)) - end - - (1..6).each do |n| - clients.push(client(ts, n)) - end - - clients.each do |t| - t.join - end -end - - diff --git a/sample/drb/speedc.rb b/sample/drb/speedc.rb deleted file mode 100644 index 64b8a65021..0000000000 --- a/sample/drb/speedc.rb +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/local/bin/ruby - -uri = ARGV.shift || raise("usage: #{$0} URI") -N = (ARGV.shift || 100).to_i - -case uri -when /^tcpromp:/, /^unixromp:/ - require 'romp' - - client = ROMP::Client.new(uri, false) - foo = client.resolve("foo") -when /^druby:/ - require 'drb/drb' - - DRb.start_service - foo = DRbObject.new(nil, uri) -end - -N.times do |n| - foo.foo(n) -end diff --git a/sample/drb/speeds.rb b/sample/drb/speeds.rb deleted file mode 100644 index 7984059423..0000000000 --- a/sample/drb/speeds.rb +++ /dev/null @@ -1,31 +0,0 @@ -class Foo - attr_reader :i - def initialize - @i = 0 - end - - def foo(i) - @i = i - i + i - end -end - -# server = ROMP::Server.new('tcpromp://localhost:4242', nil, true) - -uri = ARGV.shift || raise("usage: #{$0} URI") -foo = Foo.new - -case uri -when /^tcpromp:/, /^unixromp:/ - require 'romp' - - server = ROMP::Server.new(uri, nil, true) - server.bind(foo, "foo") - -when /^druby:/ - require 'drb/drb' - - DRb.start_service(uri, Foo.new) -end - -DRb.thread.join diff --git a/scheduler.c b/scheduler.c index ef5ec7923f..83b9681cc3 100644 --- a/scheduler.c +++ b/scheduler.c @@ -15,9 +15,12 @@ #include "ruby/thread.h" -// For `ruby_thread_has_gvl_p`. +// For `ruby_thread_has_gvl_p`: #include "internal/thread.h" +// For atomic operations: +#include "ruby_atomic.h" + static ID id_close; static ID id_scheduler_close; @@ -37,10 +40,228 @@ static ID id_io_close; static ID id_address_resolve; static ID id_blocking_operation_wait; +static ID id_fiber_interrupt; static ID id_fiber_schedule; +// Our custom blocking operation class +static VALUE rb_cFiberSchedulerBlockingOperation; + /* + * Custom blocking operation structure for blocking operations + * This replaces the use of Ruby procs to avoid use-after-free issues + * and provides a cleaner C API for native work pools. + */ + +typedef enum { + RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED, // Submitted but not started + RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_EXECUTING, // Currently running + RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_COMPLETED, // Finished (success/error) + RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_CANCELLED // Cancelled +} rb_fiber_blocking_operation_status_t; + +struct rb_fiber_scheduler_blocking_operation { + void *(*function)(void *); + void *data; + + rb_unblock_function_t *unblock_function; + void *data2; + + int flags; + struct rb_fiber_scheduler_blocking_operation_state *state; + + // Execution status + volatile rb_atomic_t status; +}; + +static void +blocking_operation_mark(void *ptr) +{ + // No Ruby objects to mark in our struct +} + +static void +blocking_operation_free(void *ptr) +{ + rb_fiber_scheduler_blocking_operation_t *blocking_operation = (rb_fiber_scheduler_blocking_operation_t *)ptr; + ruby_xfree(blocking_operation); +} + +static size_t +blocking_operation_memsize(const void *ptr) +{ + return sizeof(rb_fiber_scheduler_blocking_operation_t); +} + +static const rb_data_type_t blocking_operation_data_type = { + "Fiber::Scheduler::BlockingOperation", + { + blocking_operation_mark, + blocking_operation_free, + blocking_operation_memsize, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED +}; + +/* + * Allocate a new blocking operation + */ +static VALUE +blocking_operation_alloc(VALUE klass) +{ + rb_fiber_scheduler_blocking_operation_t *blocking_operation; + VALUE obj = TypedData_Make_Struct(klass, rb_fiber_scheduler_blocking_operation_t, &blocking_operation_data_type, blocking_operation); + + blocking_operation->function = NULL; + blocking_operation->data = NULL; + blocking_operation->unblock_function = NULL; + blocking_operation->data2 = NULL; + blocking_operation->flags = 0; + blocking_operation->state = NULL; + blocking_operation->status = RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED; + + return obj; +} + +/* + * Get the blocking operation struct from a Ruby object + */ +static rb_fiber_scheduler_blocking_operation_t * +get_blocking_operation(VALUE obj) +{ + rb_fiber_scheduler_blocking_operation_t *blocking_operation; + TypedData_Get_Struct(obj, rb_fiber_scheduler_blocking_operation_t, &blocking_operation_data_type, blocking_operation); + return blocking_operation; +} + +/* + * Document-method: Fiber::Scheduler::BlockingOperation#call + * + * Execute the blocking operation. This method releases the GVL and calls + * the blocking function, then restores the errno value. + * + * Returns nil. The actual result is stored in the associated state object. + */ +static VALUE +blocking_operation_call(VALUE self) +{ + rb_fiber_scheduler_blocking_operation_t *blocking_operation = get_blocking_operation(self); + + if (blocking_operation->status != RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED) { + rb_raise(rb_eRuntimeError, "Blocking operation has already been executed!"); + } + + if (blocking_operation->function == NULL) { + rb_raise(rb_eRuntimeError, "Blocking operation has no function to execute!"); + } + + if (blocking_operation->state == NULL) { + rb_raise(rb_eRuntimeError, "Blocking operation has no result object!"); + } + + // Mark as executing + blocking_operation->status = RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_EXECUTING; + + // Execute the blocking operation without GVL + blocking_operation->state->result = rb_nogvl(blocking_operation->function, blocking_operation->data, + blocking_operation->unblock_function, blocking_operation->data2, + blocking_operation->flags); + blocking_operation->state->saved_errno = rb_errno(); + + // Mark as completed + blocking_operation->status = RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_COMPLETED; + + return Qnil; +} + +/* + * C API: Extract blocking operation struct from Ruby object (GVL required) + * + * This function safely extracts the opaque struct from a BlockingOperation VALUE + * while holding the GVL. The returned pointer can be passed to worker threads + * and used with rb_fiber_scheduler_blocking_operation_execute_opaque_nogvl. + * + * Returns the opaque struct pointer on success, NULL on error. + * Must be called while holding the GVL. + */ +rb_fiber_scheduler_blocking_operation_t * +rb_fiber_scheduler_blocking_operation_extract(VALUE self) +{ + return get_blocking_operation(self); +} + +/* + * C API: Execute blocking operation from opaque struct (GVL not required) + * + * This function executes a blocking operation using the opaque struct pointer + * obtained from rb_fiber_scheduler_blocking_operation_extract. + * It can be called from native threads without holding the GVL. + * + * Returns 0 on success, -1 on error. + */ +int +rb_fiber_scheduler_blocking_operation_execute(rb_fiber_scheduler_blocking_operation_t *blocking_operation) +{ + if (blocking_operation == NULL) { + return -1; + } + + if (blocking_operation->function == NULL || blocking_operation->state == NULL) { + return -1; // Invalid blocking operation + } + + // Resolve sentinel values for unblock_function and data2: + rb_thread_resolve_unblock_function(&blocking_operation->unblock_function, &blocking_operation->data2, GET_THREAD()); + + // Atomically check if we can transition from QUEUED to EXECUTING + rb_atomic_t expected = RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED; + if (RUBY_ATOMIC_CAS(blocking_operation->status, expected, RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_EXECUTING) != expected) { + // Already cancelled or in wrong state + return -1; + } + + // Now we're executing - call the function + blocking_operation->state->result = blocking_operation->function(blocking_operation->data); + blocking_operation->state->saved_errno = errno; + + // Atomically transition to completed (unless cancelled during execution) + expected = RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_EXECUTING; + if (RUBY_ATOMIC_CAS(blocking_operation->status, expected, RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_COMPLETED) == expected) { + // Successfully completed + return 0; + } else { + // Was cancelled during execution + blocking_operation->state->saved_errno = EINTR; + return -1; + } +} + +/* + * C API: Create a new blocking operation + * + * This creates a blocking operation that can be executed by native work pools. + * The blocking operation holds references to the function and data safely. + */ +VALUE +rb_fiber_scheduler_blocking_operation_new(void *(*function)(void *), void *data, + rb_unblock_function_t *unblock_function, void *data2, + int flags, struct rb_fiber_scheduler_blocking_operation_state *state) +{ + VALUE self = blocking_operation_alloc(rb_cFiberSchedulerBlockingOperation); + rb_fiber_scheduler_blocking_operation_t *blocking_operation = get_blocking_operation(self); + + blocking_operation->function = function; + blocking_operation->data = data; + blocking_operation->unblock_function = unblock_function; + blocking_operation->data2 = data2; + blocking_operation->flags = flags; + blocking_operation->state = state; + + return self; +} + +/* + * * Document-class: Fiber::Scheduler * * This is not an existing class, but documentation of the interface that Scheduler @@ -116,9 +337,19 @@ Init_Fiber_Scheduler(void) id_address_resolve = rb_intern_const("address_resolve"); id_blocking_operation_wait = rb_intern_const("blocking_operation_wait"); + id_fiber_interrupt = rb_intern_const("fiber_interrupt"); id_fiber_schedule = rb_intern_const("fiber"); + // Define an anonymous BlockingOperation class for internal use only + // This is completely hidden from Ruby code and cannot be instantiated directly + rb_cFiberSchedulerBlockingOperation = rb_class_new(rb_cObject); + rb_define_alloc_func(rb_cFiberSchedulerBlockingOperation, blocking_operation_alloc); + rb_define_method(rb_cFiberSchedulerBlockingOperation, "call", blocking_operation_call, 0); + + // Register the anonymous class as a GC root so it doesn't get collected + rb_gc_register_mark_object(rb_cFiberSchedulerBlockingOperation); + #if 0 /* for RDoc */ rb_cFiberScheduler = rb_define_class_under(rb_cFiber, "Scheduler", rb_cObject); rb_define_method(rb_cFiberScheduler, "close", rb_fiber_scheduler_close, 0); @@ -134,7 +365,7 @@ Init_Fiber_Scheduler(void) rb_define_method(rb_cFiberScheduler, "timeout_after", rb_fiber_scheduler_timeout_after, 3); rb_define_method(rb_cFiberScheduler, "block", rb_fiber_scheduler_block, 2); rb_define_method(rb_cFiberScheduler, "unblock", rb_fiber_scheduler_unblock, 2); - rb_define_method(rb_cFiberScheduler, "fiber", rb_fiber_scheduler, -2); + rb_define_method(rb_cFiberScheduler, "fiber", rb_fiber_scheduler_fiber, -2); rb_define_method(rb_cFiberScheduler, "blocking_operation_wait", rb_fiber_scheduler_blocking_operation_wait, -2); #endif } @@ -168,6 +399,10 @@ verify_interface(VALUE scheduler) if (!rb_respond_to(scheduler, id_io_wait)) { rb_raise(rb_eArgError, "Scheduler must implement #io_wait"); } + + if (!rb_respond_to(scheduler, id_fiber_interrupt)) { + rb_warn("Scheduler should implement #fiber_interrupt"); + } } static VALUE @@ -416,6 +651,13 @@ rb_fiber_scheduler_unblock(VALUE scheduler, VALUE blocker, VALUE fiber) // If we explicitly preserve `errno` in `io_binwrite` and other similar functions (e.g. by returning it), this code is no longer needed. I hope in the future we will be able to remove it. int saved_errno = errno; +#ifdef RUBY_DEBUG + rb_execution_context_t *ec = GET_EC(); + if (RUBY_VM_INTERRUPTED(ec)) { + rb_bug("rb_fiber_scheduler_unblock called with pending interrupt"); + } +#endif + VALUE result = rb_funcall(scheduler, id_unblock, 2, blocker, fiber); errno = saved_errno; @@ -442,10 +684,25 @@ rb_fiber_scheduler_unblock(VALUE scheduler, VALUE blocker, VALUE fiber) * Expected to return the subset of events that are ready immediately. * */ +static VALUE +fiber_scheduler_io_wait(VALUE _argument) { + VALUE *arguments = (VALUE*)_argument; + + return rb_funcallv(arguments[0], id_io_wait, 3, arguments + 1); +} + VALUE rb_fiber_scheduler_io_wait(VALUE scheduler, VALUE io, VALUE events, VALUE timeout) { - return rb_funcall(scheduler, id_io_wait, 3, io, events, timeout); + VALUE arguments[] = { + scheduler, io, events, timeout + }; + + if (rb_respond_to(scheduler, id_fiber_interrupt)) { + return rb_thread_io_blocking_operation(io, fiber_scheduler_io_wait, (VALUE)&arguments); + } else { + return fiber_scheduler_io_wait((VALUE)&arguments); + } } VALUE @@ -515,14 +772,29 @@ VALUE rb_fiber_scheduler_io_selectv(VALUE scheduler, int argc, VALUE *argv) * * The method should be considered _experimental_. */ +static VALUE +fiber_scheduler_io_read(VALUE _argument) { + VALUE *arguments = (VALUE*)_argument; + + return rb_funcallv(arguments[0], id_io_read, 4, arguments + 1); +} + VALUE rb_fiber_scheduler_io_read(VALUE scheduler, VALUE io, VALUE buffer, size_t length, size_t offset) { + if (!rb_respond_to(scheduler, id_io_read)) { + return RUBY_Qundef; + } + VALUE arguments[] = { - io, buffer, SIZET2NUM(length), SIZET2NUM(offset) + scheduler, io, buffer, SIZET2NUM(length), SIZET2NUM(offset) }; - return rb_check_funcall(scheduler, id_io_read, 4, arguments); + if (rb_respond_to(scheduler, id_fiber_interrupt)) { + return rb_thread_io_blocking_operation(io, fiber_scheduler_io_read, (VALUE)&arguments); + } else { + return fiber_scheduler_io_read((VALUE)&arguments); + } } /* @@ -539,14 +811,29 @@ rb_fiber_scheduler_io_read(VALUE scheduler, VALUE io, VALUE buffer, size_t lengt * * The method should be considered _experimental_. */ +static VALUE +fiber_scheduler_io_pread(VALUE _argument) { + VALUE *arguments = (VALUE*)_argument; + + return rb_funcallv(arguments[0], id_io_pread, 5, arguments + 1); +} + VALUE rb_fiber_scheduler_io_pread(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t length, size_t offset) { + if (!rb_respond_to(scheduler, id_io_pread)) { + return RUBY_Qundef; + } + VALUE arguments[] = { - io, buffer, OFFT2NUM(from), SIZET2NUM(length), SIZET2NUM(offset) + scheduler, io, buffer, OFFT2NUM(from), SIZET2NUM(length), SIZET2NUM(offset) }; - return rb_check_funcall(scheduler, id_io_pread, 5, arguments); + if (rb_respond_to(scheduler, id_fiber_interrupt)) { + return rb_thread_io_blocking_operation(io, fiber_scheduler_io_pread, (VALUE)&arguments); + } else { + return fiber_scheduler_io_pread((VALUE)&arguments); + } } /* @@ -577,14 +864,29 @@ rb_fiber_scheduler_io_pread(VALUE scheduler, VALUE io, rb_off_t from, VALUE buff * * The method should be considered _experimental_. */ +static VALUE +fiber_scheduler_io_write(VALUE _argument) { + VALUE *arguments = (VALUE*)_argument; + + return rb_funcallv(arguments[0], id_io_write, 4, arguments + 1); +} + VALUE rb_fiber_scheduler_io_write(VALUE scheduler, VALUE io, VALUE buffer, size_t length, size_t offset) { + if (!rb_respond_to(scheduler, id_io_write)) { + return RUBY_Qundef; + } + VALUE arguments[] = { - io, buffer, SIZET2NUM(length), SIZET2NUM(offset) + scheduler, io, buffer, SIZET2NUM(length), SIZET2NUM(offset) }; - return rb_check_funcall(scheduler, id_io_write, 4, arguments); + if (rb_respond_to(scheduler, id_fiber_interrupt)) { + return rb_thread_io_blocking_operation(io, fiber_scheduler_io_write, (VALUE)&arguments); + } else { + return fiber_scheduler_io_write((VALUE)&arguments); + } } /* @@ -602,14 +904,29 @@ rb_fiber_scheduler_io_write(VALUE scheduler, VALUE io, VALUE buffer, size_t leng * The method should be considered _experimental_. * */ +static VALUE +fiber_scheduler_io_pwrite(VALUE _argument) { + VALUE *arguments = (VALUE*)_argument; + + return rb_funcallv(arguments[0], id_io_pwrite, 5, arguments + 1); +} + VALUE rb_fiber_scheduler_io_pwrite(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t length, size_t offset) { + if (!rb_respond_to(scheduler, id_io_pwrite)) { + return RUBY_Qundef; + } + VALUE arguments[] = { - io, buffer, OFFT2NUM(from), SIZET2NUM(length), SIZET2NUM(offset) + scheduler, io, buffer, OFFT2NUM(from), SIZET2NUM(length), SIZET2NUM(offset) }; - return rb_check_funcall(scheduler, id_io_pwrite, 5, arguments); + if (rb_respond_to(scheduler, id_fiber_interrupt)) { + return rb_thread_io_blocking_operation(io, fiber_scheduler_io_pwrite, (VALUE)&arguments); + } else { + return fiber_scheduler_io_pwrite((VALUE)&arguments); + } } VALUE @@ -710,60 +1027,67 @@ rb_fiber_scheduler_address_resolve(VALUE scheduler, VALUE hostname) return rb_check_funcall(scheduler, id_address_resolve, 1, arguments); } -struct rb_blocking_operation_wait_arguments { - void *(*function)(void *); - void *data; - rb_unblock_function_t *unblock_function; - void *data2; - int flags; - - struct rb_fiber_scheduler_blocking_operation_state *state; -}; - -static VALUE -rb_fiber_scheduler_blocking_operation_wait_proc(RB_BLOCK_CALL_FUNC_ARGLIST(value, _arguments)) -{ - struct rb_blocking_operation_wait_arguments *arguments = (struct rb_blocking_operation_wait_arguments*)_arguments; - - if (arguments->state == NULL) { - rb_raise(rb_eRuntimeError, "Blocking function was already invoked!"); - } - - arguments->state->result = rb_nogvl(arguments->function, arguments->data, arguments->unblock_function, arguments->data2, arguments->flags); - arguments->state->saved_errno = rb_errno(); - - // Make sure it's only invoked once. - arguments->state = NULL; - - return Qnil; -} - /* * Document-method: Fiber::Scheduler#blocking_operation_wait - * call-seq: blocking_operation_wait(work) + * call-seq: blocking_operation_wait(blocking_operation) * * Invoked by Ruby's core methods to run a blocking operation in a non-blocking way. + * The blocking_operation is a Fiber::Scheduler::BlockingOperation that encapsulates the blocking operation. + * + * If the scheduler doesn't implement this method, or if the scheduler doesn't execute + * the blocking operation, Ruby will fall back to the non-scheduler implementation. * * Minimal suggested implementation is: * - * def blocking_operation_wait(work) - * Thread.new(&work).join + * def blocking_operation_wait(blocking_operation) + * Thread.new { blocking_operation.call }.join * end */ VALUE rb_fiber_scheduler_blocking_operation_wait(VALUE scheduler, void* (*function)(void *), void *data, rb_unblock_function_t *unblock_function, void *data2, int flags, struct rb_fiber_scheduler_blocking_operation_state *state) { - struct rb_blocking_operation_wait_arguments arguments = { - .function = function, - .data = data, - .unblock_function = unblock_function, - .data2 = data2, - .flags = flags, - .state = state + // Check if scheduler supports blocking_operation_wait before creating the object + if (!rb_respond_to(scheduler, id_blocking_operation_wait)) { + return Qundef; + } + + // Create a new BlockingOperation with the blocking operation + VALUE blocking_operation = rb_fiber_scheduler_blocking_operation_new(function, data, unblock_function, data2, flags, state); + + VALUE result = rb_funcall(scheduler, id_blocking_operation_wait, 1, blocking_operation); + + // Get the operation data to check if it was executed + rb_fiber_scheduler_blocking_operation_t *operation = get_blocking_operation(blocking_operation); + rb_atomic_t current_status = RUBY_ATOMIC_LOAD(operation->status); + + // Invalidate the operation now that we're done with it + operation->function = NULL; + operation->state = NULL; + operation->data = NULL; + operation->data2 = NULL; + operation->unblock_function = NULL; + + // If the blocking operation was never executed, return Qundef to signal the caller to use rb_nogvl instead + if (current_status == RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED) { + return Qundef; + } + + return result; +} + +VALUE rb_fiber_scheduler_fiber_interrupt(VALUE scheduler, VALUE fiber, VALUE exception) +{ + VALUE arguments[] = { + fiber, exception }; - VALUE proc = rb_proc_new(rb_fiber_scheduler_blocking_operation_wait_proc, (VALUE)&arguments); +#ifdef RUBY_DEBUG + rb_execution_context_t *ec = GET_EC(); + if (RUBY_VM_INTERRUPTED(ec)) { + rb_bug("rb_fiber_scheduler_fiber_interrupt called with pending interrupt"); + } +#endif - return rb_check_funcall(scheduler, id_blocking_operation_wait, 1, &proc); + return rb_check_funcall(scheduler, id_fiber_interrupt, 2, arguments); } /* @@ -786,3 +1110,54 @@ rb_fiber_scheduler_fiber(VALUE scheduler, int argc, VALUE *argv, int kw_splat) { return rb_funcall_passing_block_kw(scheduler, id_fiber_schedule, argc, argv, kw_splat); } + +/* + * C API: Cancel a blocking operation + * + * This function cancels a blocking operation. If the operation is queued, + * it just marks it as cancelled. If it's executing, it marks it as cancelled + * and calls the unblock function to interrupt the operation. + * + * Returns 1 if unblock function was called, 0 if just marked cancelled, -1 on error. + */ +int +rb_fiber_scheduler_blocking_operation_cancel(rb_fiber_scheduler_blocking_operation_t *blocking_operation) +{ + if (blocking_operation == NULL) { + return -1; + } + + rb_atomic_t current_state = RUBY_ATOMIC_LOAD(blocking_operation->status); + + switch (current_state) { + case RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED: + // Work hasn't started - just mark as cancelled: + if (RUBY_ATOMIC_CAS(blocking_operation->status, current_state, RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_CANCELLED) == current_state) { + // Successfully cancelled before execution: + return 0; + } + // Fall through if state changed between load and CAS + + case RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_EXECUTING: + // Work is running - mark cancelled AND call unblock function + if (RUBY_ATOMIC_CAS(blocking_operation->status, current_state, RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_CANCELLED) != current_state) { + // State changed between load and CAS - operation may have completed: + return 0; + } + // Otherwise, we successfully marked it as cancelled, so we can call the unblock function: + rb_unblock_function_t *unblock_function = blocking_operation->unblock_function; + if (unblock_function) { + RUBY_ASSERT(unblock_function != (rb_unblock_function_t *)-1 && "unblock_function is still sentinel value -1, should have been resolved earlier"); + blocking_operation->unblock_function(blocking_operation->data2); + } + // Cancelled during execution (unblock function called): + return 1; + + case RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_COMPLETED: + case RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_CANCELLED: + // Already finished or cancelled: + return 0; + } + + return 0; +} diff --git a/set.c b/set.c index 8676c62cd3..55d0e62633 100644 --- a/set.c +++ b/set.c @@ -7,6 +7,7 @@ #include "id.h" #include "internal.h" #include "internal/bits.h" +#include "internal/error.h" #include "internal/hash.h" #include "internal/proc.h" #include "internal/sanitizers.h" @@ -113,7 +114,7 @@ static ID id_set_iter_lev; #define RSET_SIZE(set) set_table_size(RSET_TABLE(set)) #define RSET_EMPTY(set) (RSET_SIZE(set) == 0) #define RSET_SIZE_NUM(set) SIZET2NUM(RSET_SIZE(set)) -#define RSET_IS_MEMBER(sobj, item) set_lookup(RSET_TABLE(set), (st_data_t)(item)) +#define RSET_IS_MEMBER(sobj, item) set_table_lookup(RSET_TABLE(set), (st_data_t)(item)) #define RSET_COMPARE_BY_IDENTITY(set) (RSET_TABLE(set)->type == &identhash) struct set_object { @@ -132,7 +133,7 @@ static void set_mark(void *ptr) { struct set_object *sobj = ptr; - if (sobj->table.entries) set_foreach(&sobj->table, mark_key, 0); + if (sobj->table.entries) set_table_foreach(&sobj->table, mark_key, 0); } static void @@ -527,6 +528,7 @@ set_i_initialize_copy(VALUE set, VALUE other) set_free_embedded(sobj); set_copy(&sobj->table, RSET_TABLE(other)); + rb_gc_writebarrier_remember(set); return set; } @@ -534,10 +536,14 @@ set_i_initialize_copy(VALUE set, VALUE other) static int set_inspect_i(st_data_t key, st_data_t arg) { - VALUE str = (VALUE)arg; - if (RSTRING_LEN(str) > 8) { + VALUE *args = (VALUE*)arg; + VALUE str = args[0]; + if (args[1] == Qtrue) { rb_str_buf_cat_ascii(str, ", "); } + else { + args[1] = Qtrue; + } rb_str_buf_append(str, rb_inspect((VALUE)key)); return ST_CONTINUE; @@ -547,11 +553,17 @@ static VALUE set_inspect(VALUE set, VALUE dummy, int recur) { VALUE str; + VALUE klass_name = rb_class_path(CLASS_OF(set)); - if (recur) return rb_usascii_str_new2("#"); - str = rb_str_buf_new2("#"); + if (recur) { + str = rb_sprintf("%"PRIsVALUE"[...]", klass_name); + return rb_str_export_to_enc(str, rb_usascii_encoding()); + } + + str = rb_sprintf("%"PRIsVALUE"[", klass_name); + VALUE args[2] = {str, Qfalse}; + set_iter(set, set_inspect_i, (st_data_t)args); + rb_str_buf_cat2(str, "]"); return str; } @@ -635,6 +647,7 @@ set_i_to_set(int argc, VALUE *argv, VALUE set) argc = 1; } else { + rb_warn_deprecated("passing arguments to Set#to_set", NULL); klass = argv[0]; argv[0] = set; } @@ -676,7 +689,7 @@ set_i_add(VALUE set, VALUE item) { rb_check_frozen(set); if (set_iterating_p(set)) { - if (!set_lookup(RSET_TABLE(set), (st_data_t)item)) { + if (!set_table_lookup(RSET_TABLE(set), (st_data_t)item)) { no_new_item(); } } @@ -702,7 +715,7 @@ set_i_add_p(VALUE set, VALUE item) { rb_check_frozen(set); if (set_iterating_p(set)) { - if (!set_lookup(RSET_TABLE(set), (st_data_t)item)) { + if (!set_table_lookup(RSET_TABLE(set), (st_data_t)item)) { no_new_item(); } return Qnil; @@ -723,7 +736,7 @@ static VALUE set_i_delete(VALUE set, VALUE item) { rb_check_frozen(set); - if (set_delete(RSET_TABLE(set), (st_data_t *)&item)) { + if (set_table_delete(RSET_TABLE(set), (st_data_t *)&item)) { set_compact_after_delete(set); } return set; @@ -740,7 +753,7 @@ static VALUE set_i_delete_p(VALUE set, VALUE item) { rb_check_frozen(set); - if (set_delete(RSET_TABLE(set), (st_data_t *)&item)) { + if (set_table_delete(RSET_TABLE(set), (st_data_t *)&item)) { set_compact_after_delete(set); return set; } @@ -840,66 +853,72 @@ set_i_classify(VALUE set) return args[0]; } -struct set_divide_args { - VALUE self; - VALUE set_class; - VALUE final_set; - VALUE hash; - VALUE current_set; - VALUE current_item; - unsigned long ni; - unsigned long nj; -}; - -static VALUE -set_divide_block0(RB_BLOCK_CALL_FUNC_ARGLIST(j, arg)) +// Union-find with path compression +static long +set_divide_union_find_root(long *uf_parents, long index, long *tmp_array) { - struct set_divide_args *args = (struct set_divide_args *)arg; - if (args->nj > args->ni) { - VALUE i = args->current_item; - if (RTEST(rb_yield_values(2, i, j)) && RTEST(rb_yield_values(2, j, i))) { - VALUE hash = args->hash; - if (args->current_set == Qnil) { - VALUE set = rb_hash_aref(hash, j); - if (set == Qnil) { - VALUE both[2] = {i, j}; - set = set_s_create(2, both, args->set_class); - rb_hash_aset(hash, i, set); - rb_hash_aset(hash, j, set); - set_i_add(args->final_set, set); - } - else { - set_i_add(set, i); - rb_hash_aset(hash, i, set); - } - args->current_set = set; - } - else { - set_i_add(args->current_set, j); - rb_hash_aset(hash, j, args->current_set); - } - } + long root = uf_parents[index]; + long update_size = 0; + while (root != index) { + tmp_array[update_size++] = index; + index = root; + root = uf_parents[index]; } - args->nj++; - return j; + for (long j = 0; j < update_size; j++) { + long idx = tmp_array[j]; + uf_parents[idx] = root; + } + return root; +} + +static void +set_divide_union_find_merge(long *uf_parents, long i, long j, long *tmp_array) +{ + long root_i = set_divide_union_find_root(uf_parents, i, tmp_array); + long root_j = set_divide_union_find_root(uf_parents, j, tmp_array); + if (root_i != root_j) uf_parents[root_j] = root_i; } static VALUE -set_divide_block(RB_BLOCK_CALL_FUNC_ARGLIST(i, arg)) +set_divide_arity2(VALUE set) { - struct set_divide_args *args = (struct set_divide_args *)arg; - VALUE hash = args->hash; - args->current_set = rb_hash_aref(hash, i); - args->current_item = i; - args->nj = 0; - rb_block_call(args->self, id_each, 0, 0, set_divide_block0, arg); - if (args->current_set == Qnil) { - VALUE set = set_s_create(1, &i, args->set_class); - rb_hash_aset(hash, i, set); - set_i_add(args->final_set, set); + VALUE tmp, uf; + long size, *uf_parents, *tmp_array; + VALUE set_class = rb_obj_class(set); + VALUE items = set_i_to_a(set); + rb_ary_freeze(items); + size = RARRAY_LEN(items); + tmp_array = ALLOCV_N(long, tmp, size); + uf_parents = ALLOCV_N(long, uf, size); + for (long i = 0; i < size; i++) { + uf_parents[i] = i; } - args->ni++; - return i; + for (long i = 0; i < size - 1; i++) { + VALUE item1 = RARRAY_AREF(items, i); + for (long j = i + 1; j < size; j++) { + VALUE item2 = RARRAY_AREF(items, j); + if (RTEST(rb_yield_values(2, item1, item2)) && + RTEST(rb_yield_values(2, item2, item1))) { + set_divide_union_find_merge(uf_parents, i, j, tmp_array); + } + } + } + VALUE final_set = set_s_create(0, 0, rb_cSet); + VALUE hash = rb_hash_new(); + for (long i = 0; i < size; i++) { + VALUE v = RARRAY_AREF(items, i); + long root = set_divide_union_find_root(uf_parents, i, tmp_array); + VALUE set = rb_hash_aref(hash, LONG2FIX(root)); + if (set == Qnil) { + set = set_s_create(0, 0, set_class); + rb_hash_aset(hash, LONG2FIX(root), set); + set_i_add(final_set, set); + } + set_i_add(set, v); + } + ALLOCV_END(tmp); + ALLOCV_END(uf); + return final_set; } static void set_merge_enum_into(VALUE set, VALUE arg); @@ -933,19 +952,7 @@ set_i_divide(VALUE set) RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size); if (rb_block_arity() == 2) { - VALUE final_set = set_s_create(0, 0, rb_cSet); - struct set_divide_args args = { - .self = set, - .set_class = rb_obj_class(set), - .final_set = final_set, - .hash = rb_hash_new(), - .current_set = 0, - .current_item = 0, - .ni = 0, - .nj = 0 - }; - rb_block_call(set, id_each, 0, 0, set_divide_block, (VALUE)&args); - return final_set; + return set_divide_arity2(set); } VALUE values = rb_hash_values(set_i_classify(set)); @@ -979,7 +986,7 @@ set_i_clear(VALUE set) set_iter(set, set_clear_i, 0); } else { - set_clear(RSET_TABLE(set)); + set_table_clear(RSET_TABLE(set)); set_compact_after_delete(set); } return set; @@ -995,7 +1002,7 @@ static int set_intersection_i(st_data_t key, st_data_t tmp) { struct set_intersection_data *data = (struct set_intersection_data *)tmp; - if (set_lookup(data->other, key)) { + if (set_table_lookup(data->other, key)) { set_table_insert_wb(data->into, data->set, key, NULL); } @@ -1245,7 +1252,7 @@ set_xor_i(st_data_t key, st_data_t data) VALUE set = (VALUE)data; set_table *table = RSET_TABLE(set); if (set_table_insert_wb(table, set, element, &element)) { - set_delete(table, &element); + set_table_delete(table, &element); } return ST_CONTINUE; } @@ -1297,7 +1304,7 @@ set_i_union(VALUE set, VALUE other) static int set_remove_i(st_data_t key, st_data_t from) { - set_delete((struct set_table *)from, (st_data_t *)&key); + set_table_delete((struct set_table *)from, (st_data_t *)&key); return ST_CONTINUE; } @@ -1305,7 +1312,7 @@ static VALUE set_remove_block(RB_BLOCK_CALL_FUNC_ARGLIST(key, set)) { rb_check_frozen(set); - set_delete(RSET_TABLE(set), (st_data_t *)&key); + set_table_delete(RSET_TABLE(set), (st_data_t *)&key); return key; } @@ -1407,7 +1414,7 @@ static int set_keep_if_i(st_data_t key, st_data_t into) { if (!RTEST(rb_yield((VALUE)key))) { - set_delete((set_table *)into, &key); + set_table_delete((set_table *)into, &key); } return ST_CONTINUE; } @@ -1479,7 +1486,7 @@ set_i_replace(VALUE set, VALUE other) // make sure enum is enumerable before calling clear enum_method_id(other); - set_clear(RSET_TABLE(set)); + set_table_clear(RSET_TABLE(set)); set_merge_enum_into(set, other); } @@ -1590,7 +1597,7 @@ static int set_le_i(st_data_t key, st_data_t arg) { struct set_subset_data *data = (struct set_subset_data *)arg; - if (set_lookup(data->table, key)) return ST_CONTINUE; + if (set_table_lookup(data->table, key)) return ST_CONTINUE; data->result = Qfalse; return ST_STOP; } @@ -1666,7 +1673,7 @@ static int set_intersect_i(st_data_t key, st_data_t arg) { VALUE *args = (VALUE *)arg; - if (set_lookup((set_table *)args[0], key)) { + if (set_table_lookup((set_table *)args[0], key)) { args[1] = Qtrue; return ST_STOP; } @@ -1775,7 +1782,7 @@ set_eql_i(st_data_t item, st_data_t arg) { struct set_equal_data *data = (struct set_equal_data *)arg; - if (!set_lookup(RSET_TABLE(data->set), item)) { + if (!set_table_lookup(RSET_TABLE(data->set), item)) { data->result = Qfalse; return ST_STOP; } @@ -1906,6 +1913,56 @@ compat_loader(VALUE self, VALUE a) return set_i_from_hash(self, rb_ivar_get(a, id_i_hash)); } +/* C-API functions */ + +void +rb_set_foreach(VALUE set, int (*func)(VALUE element, VALUE arg), VALUE arg) +{ + set_iter(set, func, arg); +} + +VALUE +rb_set_new(void) +{ + return set_alloc_with_size(rb_cSet, 0); +} + +VALUE +rb_set_new_capa(size_t capa) +{ + return set_alloc_with_size(rb_cSet, (st_index_t)capa); +} + +bool +rb_set_lookup(VALUE set, VALUE element) +{ + return RSET_IS_MEMBER(set, element); +} + +bool +rb_set_add(VALUE set, VALUE element) +{ + return set_i_add_p(set, element) != Qnil; +} + +VALUE +rb_set_clear(VALUE set) +{ + return set_i_clear(set); +} + +bool +rb_set_delete(VALUE set, VALUE element) +{ + return set_i_delete_p(set, element) != Qnil; +} + +size_t +rb_set_size(VALUE set) +{ + return RSET_SIZE(set); +} + /* * Document-class: Set * diff --git a/shape.c b/shape.c index bbca9db304..e296ab2d8f 100644 --- a/shape.c +++ b/shape.c @@ -20,24 +20,12 @@ #define SHAPE_DEBUG (VM_CHECK_MODE > 0) #endif -#if SIZEOF_SHAPE_T == 4 -#if RUBY_DEBUG -#define SHAPE_BUFFER_SIZE 0x8000 -#else -#define SHAPE_BUFFER_SIZE 0x80000 -#endif -#else -#define SHAPE_BUFFER_SIZE 0x8000 -#endif - -#define ROOT_TOO_COMPLEX_SHAPE_ID 0x2 - #define REDBLACK_CACHE_SIZE (SHAPE_BUFFER_SIZE * 32) /* This depends on that the allocated memory by Ruby's allocator or * mmap is not located at an odd address. */ #define SINGLE_CHILD_TAG 0x1 -#define TAG_SINGLE_CHILD(x) (struct rb_id_table *)((uintptr_t)(x) | SINGLE_CHILD_TAG) +#define TAG_SINGLE_CHILD(x) (VALUE)((uintptr_t)(x) | SINGLE_CHILD_TAG) #define SINGLE_CHILD_MASK (~((uintptr_t)SINGLE_CHILD_TAG)) #define SINGLE_CHILD_P(x) ((uintptr_t)(x) & SINGLE_CHILD_TAG) #define SINGLE_CHILD(x) (rb_shape_t *)((uintptr_t)(x) & SINGLE_CHILD_MASK) @@ -45,22 +33,12 @@ #define MAX_SHAPE_ID (SHAPE_BUFFER_SIZE - 1) #define ANCESTOR_SEARCH_MAX_DEPTH 2 -static ID id_frozen; -static ID id_t_object; -ID ruby_internal_object_id; // extern +static ID id_object_id; #define LEAF 0 #define BLACK 0x0 #define RED 0x1 -enum shape_flags { - SHAPE_FL_FROZEN = 1 << 0, - SHAPE_FL_HAS_OBJECT_ID = 1 << 1, - SHAPE_FL_TOO_COMPLEX = 1 << 2, - - SHAPE_FL_NON_CANONICAL_MASK = SHAPE_FL_FROZEN | SHAPE_FL_HAS_OBJECT_ID, -}; - static redblack_node_t * redblack_left(redblack_node_t *node) { @@ -68,8 +46,8 @@ redblack_left(redblack_node_t *node) return LEAF; } else { - RUBY_ASSERT(node->l < GET_SHAPE_TREE()->cache_size); - redblack_node_t *left = &GET_SHAPE_TREE()->shape_cache[node->l - 1]; + RUBY_ASSERT(node->l < rb_shape_tree.cache_size); + redblack_node_t *left = &rb_shape_tree.shape_cache[node->l - 1]; return left; } } @@ -81,8 +59,8 @@ redblack_right(redblack_node_t *node) return LEAF; } else { - RUBY_ASSERT(node->r < GET_SHAPE_TREE()->cache_size); - redblack_node_t *right = &GET_SHAPE_TREE()->shape_cache[node->r - 1]; + RUBY_ASSERT(node->r < rb_shape_tree.cache_size); + redblack_node_t *right = &rb_shape_tree.shape_cache[node->r - 1]; return right; } } @@ -140,7 +118,7 @@ redblack_id_for(redblack_node_t *node) return 0; } else { - redblack_node_t *redblack_nodes = GET_SHAPE_TREE()->shape_cache; + redblack_node_t *redblack_nodes = rb_shape_tree.shape_cache; redblack_id_t id = (redblack_id_t)(node - redblack_nodes); return id + 1; } @@ -149,7 +127,7 @@ redblack_id_for(redblack_node_t *node) static redblack_node_t * redblack_new(char color, ID key, rb_shape_t *value, redblack_node_t *left, redblack_node_t *right) { - if (GET_SHAPE_TREE()->cache_size + 1 >= REDBLACK_CACHE_SIZE) { + if (rb_shape_tree.cache_size + 1 >= REDBLACK_CACHE_SIZE) { // We're out of cache, just quit return LEAF; } @@ -157,8 +135,8 @@ redblack_new(char color, ID key, rb_shape_t *value, redblack_node_t *left, redbl RUBY_ASSERT(left == LEAF || left->key < key); RUBY_ASSERT(right == LEAF || right->key > key); - redblack_node_t *redblack_nodes = GET_SHAPE_TREE()->shape_cache; - redblack_node_t *node = &redblack_nodes[(GET_SHAPE_TREE()->cache_size)++]; + redblack_node_t *redblack_nodes = rb_shape_tree.shape_cache; + redblack_node_t *node = &redblack_nodes[(rb_shape_tree.cache_size)++]; node->key = key; node->value = (rb_shape_t *)((uintptr_t)value | color); node->l = redblack_id_for(left); @@ -308,49 +286,98 @@ redblack_insert(redblack_node_t *tree, ID key, rb_shape_t *value) } #endif -rb_shape_tree_t *rb_shape_tree_ptr = NULL; +rb_shape_tree_t rb_shape_tree = { 0 }; +static VALUE shape_tree_obj = Qfalse; + +rb_shape_t * +rb_shape_get_root_shape(void) +{ + return rb_shape_tree.root_shape; +} + +static void +shape_tree_mark(void *data) +{ + rb_shape_t *cursor = rb_shape_get_root_shape(); + rb_shape_t *end = RSHAPE(rb_shape_tree.next_shape_id - 1); + while (cursor <= end) { + if (cursor->edges && !SINGLE_CHILD_P(cursor->edges)) { + rb_gc_mark_movable(cursor->edges); + } + cursor++; + } +} + +static void +shape_tree_compact(void *data) +{ + rb_shape_t *cursor = rb_shape_get_root_shape(); + rb_shape_t *end = RSHAPE(rb_shape_tree.next_shape_id - 1); + while (cursor <= end) { + if (cursor->edges && !SINGLE_CHILD_P(cursor->edges)) { + cursor->edges = rb_gc_location(cursor->edges); + } + cursor++; + } +} + +static size_t +shape_tree_memsize(const void *data) +{ + return rb_shape_tree.cache_size * sizeof(redblack_node_t); +} + +static const rb_data_type_t shape_tree_type = { + .wrap_struct_name = "VM/shape_tree", + .function = { + .dmark = shape_tree_mark, + .dfree = NULL, // Nothing to free, done at VM exit in rb_shape_free_all, + .dsize = shape_tree_memsize, + .dcompact = shape_tree_compact, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + /* * Shape getters */ -rb_shape_t * -rb_shape_get_root_shape(void) + +static inline shape_id_t +raw_shape_id(rb_shape_t *shape) { - return GET_SHAPE_TREE()->root_shape; + RUBY_ASSERT(shape); + return (shape_id_t)(shape - rb_shape_tree.shape_list); } -shape_id_t -rb_shape_id(rb_shape_t *shape) +static inline shape_id_t +shape_id(rb_shape_t *shape, shape_id_t previous_shape_id) { - if (shape == NULL) { - return INVALID_SHAPE_ID; - } - return (shape_id_t)(shape - GET_SHAPE_TREE()->shape_list); + RUBY_ASSERT(shape); + shape_id_t raw_id = (shape_id_t)(shape - rb_shape_tree.shape_list); + return raw_id | (previous_shape_id & SHAPE_ID_FLAGS_MASK); } +#if RUBY_DEBUG +static inline bool +shape_frozen_p(shape_id_t shape_id) +{ + return shape_id & SHAPE_ID_FL_FROZEN; +} +#endif + void -rb_shape_each_shape(each_shape_callback callback, void *data) +rb_shape_each_shape_id(each_shape_callback callback, void *data) { - rb_shape_t *cursor = rb_shape_get_root_shape(); - rb_shape_t *end = RSHAPE(GET_SHAPE_TREE()->next_shape_id); + rb_shape_t *start = rb_shape_get_root_shape(); + rb_shape_t *cursor = start; + rb_shape_t *end = RSHAPE(rb_shapes_count()); while (cursor < end) { - callback(cursor, data); + callback((shape_id_t)(cursor - start), data); cursor += 1; } } -RUBY_FUNC_EXPORTED rb_shape_t * -rb_shape_lookup(shape_id_t shape_id) -{ - RUBY_ASSERT(shape_id != INVALID_SHAPE_ID); - - return &GET_SHAPE_TREE()->shape_list[shape_id]; -} - -#if !SHAPE_IN_BASIC_FLAGS -shape_id_t rb_generic_shape_id(VALUE obj); -#endif - RUBY_FUNC_EXPORTED shape_id_t rb_obj_shape_id(VALUE obj) { @@ -358,20 +385,14 @@ rb_obj_shape_id(VALUE obj) return SPECIAL_CONST_SHAPE_ID; } -#if SHAPE_IN_BASIC_FLAGS - return RBASIC_SHAPE_ID(obj); -#else - switch (BUILTIN_TYPE(obj)) { - case T_OBJECT: - return ROBJECT_SHAPE_ID(obj); - break; - case T_CLASS: - case T_MODULE: - return RCLASS_SHAPE_ID(obj); - default: - return rb_generic_shape_id(obj); + if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) { + VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + if (fields_obj) { + return RBASIC_SHAPE_ID(fields_obj); + } + return ROOT_SHAPE_ID; } -#endif + return RBASIC_SHAPE_ID(obj); } size_t @@ -391,26 +412,29 @@ rb_shape_depth(shape_id_t shape_id) static rb_shape_t * shape_alloc(void) { - shape_id_t shape_id = GET_SHAPE_TREE()->next_shape_id; - GET_SHAPE_TREE()->next_shape_id++; + shape_id_t current, new_id; - if (shape_id == (MAX_SHAPE_ID + 1)) { - // TODO: Make an OutOfShapesError ?? - rb_bug("Out of shapes"); - } + do { + current = RUBY_ATOMIC_LOAD(rb_shape_tree.next_shape_id); + if (current > MAX_SHAPE_ID) { + return NULL; // Out of shapes + } + new_id = current + 1; + } while (current != RUBY_ATOMIC_CAS(rb_shape_tree.next_shape_id, current, new_id)); - return &GET_SHAPE_TREE()->shape_list[shape_id]; + return &rb_shape_tree.shape_list[current]; } static rb_shape_t * rb_shape_alloc_with_parent_id(ID edge_name, shape_id_t parent_id) { rb_shape_t *shape = shape_alloc(); + if (!shape) return NULL; shape->edge_name = edge_name; shape->next_field_index = 0; shape->parent_id = parent_id; - shape->edges = NULL; + shape->edges = 0; return shape; } @@ -418,10 +442,10 @@ rb_shape_alloc_with_parent_id(ID edge_name, shape_id_t parent_id) static rb_shape_t * rb_shape_alloc(ID edge_name, rb_shape_t *parent, enum shape_type type) { - rb_shape_t *shape = rb_shape_alloc_with_parent_id(edge_name, rb_shape_id(parent)); + rb_shape_t *shape = rb_shape_alloc_with_parent_id(edge_name, raw_shape_id(parent)); + if (!shape) return NULL; + shape->type = (uint8_t)type; - shape->flags = parent->flags; - shape->heap_index = parent->heap_index; shape->capacity = parent->capacity; shape->edges = 0; return shape; @@ -462,33 +486,45 @@ redblack_cache_ancestors(rb_shape_t *shape) } #endif +static attr_index_t +shape_grow_capa(attr_index_t current_capa) +{ + const attr_index_t *capacities = rb_shape_tree.capacities; + + // First try to use the next size that will be embeddable in a larger object slot. + attr_index_t capa; + while ((capa = *capacities)) { + if (capa > current_capa) { + return capa; + } + capacities++; + } + + return (attr_index_t)rb_malloc_grow_capa(current_capa, sizeof(VALUE)); +} + static rb_shape_t * rb_shape_alloc_new_child(ID id, rb_shape_t *shape, enum shape_type shape_type) { rb_shape_t *new_shape = rb_shape_alloc(id, shape, shape_type); + if (!new_shape) return NULL; switch (shape_type) { case SHAPE_OBJ_ID: - new_shape->flags |= SHAPE_FL_HAS_OBJECT_ID; - // fallthrough case SHAPE_IVAR: if (UNLIKELY(shape->next_field_index >= shape->capacity)) { RUBY_ASSERT(shape->next_field_index == shape->capacity); - new_shape->capacity = (uint32_t)rb_malloc_grow_capa(shape->capacity, sizeof(VALUE)); + new_shape->capacity = shape_grow_capa(shape->capacity); } RUBY_ASSERT(new_shape->capacity > shape->next_field_index); new_shape->next_field_index = shape->next_field_index + 1; if (new_shape->next_field_index > ANCESTOR_CACHE_THRESHOLD) { - redblack_cache_ancestors(new_shape); + RB_VM_LOCKING() { + redblack_cache_ancestors(new_shape); + } } break; - case SHAPE_FROZEN: - new_shape->next_field_index = shape->next_field_index; - new_shape->flags |= SHAPE_FL_FROZEN; - break; - case SHAPE_OBJ_TOO_COMPLEX: case SHAPE_ROOT: - case SHAPE_T_OBJECT: rb_bug("Unreachable"); break; } @@ -496,96 +532,141 @@ rb_shape_alloc_new_child(ID id, rb_shape_t *shape, enum shape_type shape_type) return new_shape; } -static rb_shape_t *shape_transition_too_complex(rb_shape_t *original_shape); - static rb_shape_t * -get_next_shape_internal(rb_shape_t *shape, ID id, enum shape_type shape_type, bool *variation_created, bool new_variations_allowed) +get_next_shape_internal_atomic(rb_shape_t *shape, ID id, enum shape_type shape_type, bool *variation_created, bool new_variations_allowed) { rb_shape_t *res = NULL; - // There should never be outgoing edges from "too complex", except for SHAPE_FROZEN and SHAPE_OBJ_ID - RUBY_ASSERT(!rb_shape_too_complex_p(shape) || shape_type == SHAPE_FROZEN || shape_type == SHAPE_OBJ_ID); - *variation_created = false; + VALUE edges_table; - // Fast path: if the shape has a single child, we can check it without a lock - struct rb_id_table *edges = RUBY_ATOMIC_PTR_LOAD(shape->edges); - if (edges && SINGLE_CHILD_P(edges)) { - rb_shape_t *child = SINGLE_CHILD(edges); - if (child->edge_name == id) { - return child; +retry: + edges_table = RUBY_ATOMIC_VALUE_LOAD(shape->edges); + + // If the current shape has children + if (edges_table) { + // Check if it only has one child + if (SINGLE_CHILD_P(edges_table)) { + rb_shape_t *child = SINGLE_CHILD(edges_table); + // If the one child has a matching edge name, then great, + // we found what we want. + if (child->edge_name == id) { + res = child; + } + } + else { + // If it has more than one child, do a hash lookup to find it. + VALUE lookup_result; + if (rb_managed_id_table_lookup(edges_table, id, &lookup_result)) { + res = (rb_shape_t *)lookup_result; + } } } - RB_VM_LOCK_ENTER(); - { - // The situation may have changed while we waited for the lock. - // So we load the edge again. - edges = RUBY_ATOMIC_PTR_LOAD(shape->edges); + // If we didn't find the shape we're looking for and we're allowed more variations we create it. + if (!res && new_variations_allowed) { + VALUE new_edges = 0; - // If the current shape has children - if (edges) { - // Check if it only has one child - if (SINGLE_CHILD_P(edges)) { - rb_shape_t *child = SINGLE_CHILD(edges); - // If the one child has a matching edge name, then great, - // we found what we want. - if (child->edge_name == id) { - res = child; - } + rb_shape_t *new_shape = rb_shape_alloc_new_child(id, shape, shape_type); + + // If we're out of shapes, return NULL + if (new_shape) { + if (!edges_table) { + // If the shape had no edge yet, we can directly set the new child + new_edges = TAG_SINGLE_CHILD(new_shape); } else { - // If it has more than one child, do a hash lookup to find it. - VALUE lookup_result; - if (rb_id_table_lookup(edges, id, &lookup_result)) { - res = (rb_shape_t *)lookup_result; - } - } - } - - // If we didn't find the shape we're looking for we create it. - if (!res) { - // If we're not allowed to create a new variation, of if we're out of shapes - // we return TOO_COMPLEX_SHAPE. - if (!new_variations_allowed || GET_SHAPE_TREE()->next_shape_id > MAX_SHAPE_ID) { - res = shape_transition_too_complex(shape); - } - else { - rb_shape_t *new_shape = rb_shape_alloc_new_child(id, shape, shape_type); - - if (!edges) { - // If the shape had no edge yet, we can directly set the new child - edges = TAG_SINGLE_CHILD(new_shape); + // If the edge was single child we need to allocate a table. + if (SINGLE_CHILD_P(edges_table)) { + rb_shape_t *old_child = SINGLE_CHILD(edges_table); + new_edges = rb_managed_id_table_new(2); + rb_managed_id_table_insert(new_edges, old_child->edge_name, (VALUE)old_child); } else { - // If the edge was single child we need to allocate a table. - if (SINGLE_CHILD_P(shape->edges)) { - rb_shape_t *old_child = SINGLE_CHILD(edges); - edges = rb_id_table_create(2); - rb_id_table_insert(edges, old_child->edge_name, (VALUE)old_child); - } - - rb_id_table_insert(edges, new_shape->edge_name, (VALUE)new_shape); - *variation_created = true; + new_edges = rb_managed_id_table_dup(edges_table); } - // We must use an atomic when setting the edges to ensure the writes - // from rb_shape_alloc_new_child are committed. - RUBY_ATOMIC_PTR_SET(shape->edges, edges); - - res = new_shape; + rb_managed_id_table_insert(new_edges, new_shape->edge_name, (VALUE)new_shape); + *variation_created = true; } + + if (edges_table != RUBY_ATOMIC_VALUE_CAS(shape->edges, edges_table, new_edges)) { + // Another thread updated the table; + goto retry; + } + RB_OBJ_WRITTEN(shape_tree_obj, Qundef, new_edges); + res = new_shape; + RB_GC_GUARD(new_edges); } } - RB_VM_LOCK_LEAVE(); return res; } -static inline bool -rb_shape_frozen_shape_p(rb_shape_t *shape) +static rb_shape_t * +get_next_shape_internal(rb_shape_t *shape, ID id, enum shape_type shape_type, bool *variation_created, bool new_variations_allowed) { - return SHAPE_FL_FROZEN & shape->flags; + if (rb_multi_ractor_p()) { + return get_next_shape_internal_atomic(shape, id, shape_type, variation_created, new_variations_allowed); + } + + rb_shape_t *res = NULL; + *variation_created = false; + + VALUE edges_table = shape->edges; + + // If the current shape has children + if (edges_table) { + // Check if it only has one child + if (SINGLE_CHILD_P(edges_table)) { + rb_shape_t *child = SINGLE_CHILD(edges_table); + // If the one child has a matching edge name, then great, + // we found what we want. + if (child->edge_name == id) { + res = child; + } + } + else { + // If it has more than one child, do a hash lookup to find it. + VALUE lookup_result; + if (rb_managed_id_table_lookup(edges_table, id, &lookup_result)) { + res = (rb_shape_t *)lookup_result; + } + } + } + + // If we didn't find the shape we're looking for we create it. + if (!res) { + // If we're not allowed to create a new variation, of if we're out of shapes + // we return TOO_COMPLEX_SHAPE. + if (!new_variations_allowed || rb_shapes_count() > MAX_SHAPE_ID) { + res = NULL; + } + else { + rb_shape_t *new_shape = rb_shape_alloc_new_child(id, shape, shape_type); + + if (!edges_table) { + // If the shape had no edge yet, we can directly set the new child + shape->edges = TAG_SINGLE_CHILD(new_shape); + } + else { + // If the edge was single child we need to allocate a table. + if (SINGLE_CHILD_P(edges_table)) { + rb_shape_t *old_child = SINGLE_CHILD(edges_table); + VALUE new_edges = rb_managed_id_table_new(2); + rb_managed_id_table_insert(new_edges, old_child->edge_name, (VALUE)old_child); + RB_OBJ_WRITE(shape_tree_obj, &shape->edges, new_edges); + } + + rb_managed_id_table_insert(shape->edges, new_shape->edge_name, (VALUE)new_shape); + *variation_created = true; + } + + res = new_shape; + } + } + + return res; } static rb_shape_t * @@ -594,6 +675,7 @@ remove_shape_recursive(rb_shape_t *shape, ID id, rb_shape_t **removed_shape) if (shape->parent_id == INVALID_SHAPE_ID) { // We've hit the top of the shape tree and couldn't find the // IV we wanted to remove, so return NULL + *removed_shape = NULL; return NULL; } else { @@ -609,85 +691,111 @@ remove_shape_recursive(rb_shape_t *shape, ID id, rb_shape_t **removed_shape) // We found a new parent. Create a child of the new parent that // has the same attributes as this shape. if (new_parent) { - if (UNLIKELY(rb_shape_too_complex_p(new_parent))) { - return new_parent; - } - bool dont_care; rb_shape_t *new_child = get_next_shape_internal(new_parent, shape->edge_name, shape->type, &dont_care, true); - if (UNLIKELY(rb_shape_too_complex_p(new_child))) { - return new_child; - } - - RUBY_ASSERT(new_child->capacity <= shape->capacity); - + RUBY_ASSERT(!new_child || new_child->capacity <= shape->capacity); return new_child; } else { // We went all the way to the top of the shape tree and couldn't - // find an IV to remove, so return NULL + // find an IV to remove so return NULL. return NULL; } } } } -bool -rb_shape_transition_remove_ivar(VALUE obj, ID id, VALUE *removed) -{ - rb_shape_t *shape = rb_obj_shape(obj); +static inline shape_id_t transition_complex(shape_id_t shape_id); - if (UNLIKELY(rb_shape_too_complex_p(shape))) { - return false; +static shape_id_t +shape_transition_object_id(shape_id_t original_shape_id) +{ + RUBY_ASSERT(!rb_shape_has_object_id(original_shape_id)); + + bool dont_care; + rb_shape_t *shape = get_next_shape_internal(RSHAPE(original_shape_id), id_object_id, SHAPE_OBJ_ID, &dont_care, true); + if (!shape) { + shape = RSHAPE(ROOT_SHAPE_WITH_OBJ_ID); } + RUBY_ASSERT(shape); + return shape_id(shape, original_shape_id) | SHAPE_ID_FL_HAS_OBJECT_ID; +} + +shape_id_t +rb_shape_transition_object_id(VALUE obj) +{ + return shape_transition_object_id(RBASIC_SHAPE_ID(obj)); +} + +shape_id_t +rb_shape_object_id(shape_id_t original_shape_id) +{ + RUBY_ASSERT(rb_shape_has_object_id(original_shape_id)); + + rb_shape_t *shape = RSHAPE(original_shape_id); + while (shape->type != SHAPE_OBJ_ID) { + if (UNLIKELY(shape->parent_id == INVALID_SHAPE_ID)) { + rb_bug("Missing object_id in shape tree"); + } + shape = RSHAPE(shape->parent_id); + } + + return shape_id(shape, original_shape_id) | SHAPE_ID_FL_HAS_OBJECT_ID; +} + +static inline shape_id_t +transition_complex(shape_id_t shape_id) +{ + uint8_t heap_index = rb_shape_heap_index(shape_id); + shape_id_t next_shape_id; + + if (heap_index) { + next_shape_id = rb_shape_root(heap_index - 1) | SHAPE_ID_FL_TOO_COMPLEX; + if (rb_shape_has_object_id(shape_id)) { + next_shape_id = shape_transition_object_id(next_shape_id); + } + } + else { + if (rb_shape_has_object_id(shape_id)) { + next_shape_id = ROOT_TOO_COMPLEX_WITH_OBJ_ID | (shape_id & SHAPE_ID_FLAGS_MASK); + } + else { + next_shape_id = ROOT_TOO_COMPLEX_SHAPE_ID | (shape_id & SHAPE_ID_FLAGS_MASK); + } + } + + RUBY_ASSERT(rb_shape_has_object_id(shape_id) == rb_shape_has_object_id(next_shape_id)); + + return next_shape_id; +} + +shape_id_t +rb_shape_transition_remove_ivar(VALUE obj, ID id, shape_id_t *removed_shape_id) +{ + shape_id_t original_shape_id = RBASIC_SHAPE_ID(obj); + + RUBY_ASSERT(!rb_shape_too_complex_p(original_shape_id)); + RUBY_ASSERT(!shape_frozen_p(original_shape_id)); + rb_shape_t *removed_shape = NULL; - rb_shape_t *new_shape = remove_shape_recursive(shape, id, &removed_shape); - if (new_shape) { - RUBY_ASSERT(removed_shape != NULL); + rb_shape_t *new_shape = remove_shape_recursive(RSHAPE(original_shape_id), id, &removed_shape); - if (UNLIKELY(rb_shape_too_complex_p(new_shape))) { - return false; - } - - RUBY_ASSERT(new_shape->next_field_index == shape->next_field_index - 1); - - VALUE *fields; - switch(BUILTIN_TYPE(obj)) { - case T_CLASS: - case T_MODULE: - fields = RCLASS_PRIME_FIELDS(obj); - break; - case T_OBJECT: - fields = ROBJECT_FIELDS(obj); - break; - default: { - struct gen_fields_tbl *fields_tbl; - rb_gen_fields_tbl_get(obj, id, &fields_tbl); - fields = fields_tbl->as.shape.fields; - break; - } - } - - *removed = fields[removed_shape->next_field_index - 1]; - - memmove(&fields[removed_shape->next_field_index - 1], &fields[removed_shape->next_field_index], - ((new_shape->next_field_index + 1) - removed_shape->next_field_index) * sizeof(VALUE)); - - // Re-embed objects when instances become small enough - // This is necessary because YJIT assumes that objects with the same shape - // have the same embeddedness for efficiency (avoid extra checks) - if (BUILTIN_TYPE(obj) == T_OBJECT && - !RB_FL_TEST_RAW(obj, ROBJECT_EMBED) && - rb_obj_embedded_size(new_shape->next_field_index) <= rb_gc_obj_slot_size(obj)) { - RB_FL_SET_RAW(obj, ROBJECT_EMBED); - memcpy(ROBJECT_FIELDS(obj), fields, new_shape->next_field_index * sizeof(VALUE)); - xfree(fields); - } - - rb_shape_set_shape(obj, new_shape); + if (removed_shape) { + *removed_shape_id = raw_shape_id(removed_shape); } - return true; + + if (new_shape) { + return shape_id(new_shape, original_shape_id); + } + else if (removed_shape) { + // We found the shape to remove, but couldn't create a new variation. + // We must transition to TOO_COMPLEX. + shape_id_t next_shape_id = transition_complex(original_shape_id); + RUBY_ASSERT(rb_shape_has_object_id(next_shape_id) == rb_shape_has_object_id(original_shape_id)); + return next_shape_id; + } + return original_shape_id; } shape_id_t @@ -696,72 +804,27 @@ rb_shape_transition_frozen(VALUE obj) RUBY_ASSERT(RB_OBJ_FROZEN(obj)); shape_id_t shape_id = rb_obj_shape_id(obj); - if (shape_id == ROOT_SHAPE_ID) { - return SPECIAL_CONST_SHAPE_ID; - } - - rb_shape_t *shape = RSHAPE(shape_id); - RUBY_ASSERT(shape); - - if (rb_shape_frozen_shape_p(shape)) { - return shape_id; - } - - bool dont_care; - rb_shape_t *next_shape = get_next_shape_internal(shape, id_frozen, SHAPE_FROZEN, &dont_care, true); - - RUBY_ASSERT(next_shape); - return rb_shape_id(next_shape); -} - -static rb_shape_t * -shape_transition_too_complex(rb_shape_t *original_shape) -{ - rb_shape_t *next_shape = RSHAPE(ROOT_TOO_COMPLEX_SHAPE_ID); - - if (original_shape->flags & SHAPE_FL_FROZEN) { - bool dont_care; - next_shape = get_next_shape_internal(next_shape, id_frozen, SHAPE_FROZEN, &dont_care, false); - } - - if (original_shape->flags & SHAPE_FL_HAS_OBJECT_ID) { - bool dont_care; - next_shape = get_next_shape_internal(next_shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, false); - } - - return next_shape; + return shape_id | SHAPE_ID_FL_FROZEN; } shape_id_t rb_shape_transition_complex(VALUE obj) { - rb_shape_t *original_shape = rb_obj_shape(obj); - return rb_shape_id(shape_transition_too_complex(original_shape)); -} - -bool -rb_shape_has_object_id(rb_shape_t *shape) -{ - return shape->flags & SHAPE_FL_HAS_OBJECT_ID; + return transition_complex(RBASIC_SHAPE_ID(obj)); } shape_id_t -rb_shape_transition_object_id(VALUE obj) +rb_shape_transition_heap(VALUE obj, size_t heap_index) { - rb_shape_t* shape = rb_obj_shape(obj); - RUBY_ASSERT(shape); + return (RBASIC_SHAPE_ID(obj) & (~SHAPE_ID_HEAP_INDEX_MASK)) | rb_shape_root(heap_index); +} - if (shape->flags & SHAPE_FL_HAS_OBJECT_ID) { - while (shape->type != SHAPE_OBJ_ID) { - shape = RSHAPE(shape->parent_id); - } - } - else { - bool dont_care; - shape = get_next_shape_internal(shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true); - } - RUBY_ASSERT(shape); - return rb_shape_id(shape); +void +rb_set_namespaced_class_shape_id(VALUE obj, shape_id_t shape_id) +{ + RBASIC_SET_SHAPE_ID(RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(obj), shape_id); + // FIXME: How to do multi-shape? + RBASIC_SET_SHAPE_ID(obj, shape_id); } /* @@ -781,39 +844,67 @@ rb_shape_get_next_iv_shape(shape_id_t shape_id, ID id) { rb_shape_t *shape = RSHAPE(shape_id); rb_shape_t *next_shape = shape_get_next_iv_shape(shape, id); - return rb_shape_id(next_shape); + if (!next_shape) { + return INVALID_SHAPE_ID; + } + return raw_shape_id(next_shape); +} + +static bool +shape_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value) +{ + while (shape->parent_id != INVALID_SHAPE_ID) { + if (shape->edge_name == id) { + enum shape_type shape_type; + shape_type = (enum shape_type)shape->type; + + switch (shape_type) { + case SHAPE_IVAR: + RUBY_ASSERT(shape->next_field_index > 0); + *value = shape->next_field_index - 1; + return true; + case SHAPE_ROOT: + return false; + case SHAPE_OBJ_ID: + rb_bug("Ivar should not exist on transition"); + } + } + + shape = RSHAPE(shape->parent_id); + } + + return false; } static inline rb_shape_t * shape_get_next(rb_shape_t *shape, VALUE obj, ID id, bool emit_warnings) { RUBY_ASSERT(!is_instance_id(id) || RTEST(rb_sym2str(ID2SYM(id)))); - if (UNLIKELY(rb_shape_too_complex_p(shape))) { - return shape; - } #if RUBY_DEBUG attr_index_t index; - if (rb_shape_get_iv_index(shape, id, &index)) { + if (shape_get_iv_index(shape, id, &index)) { rb_bug("rb_shape_get_next: trying to create ivar that already exists at index %u", index); } #endif VALUE klass; - switch (BUILTIN_TYPE(obj)) { - case T_CLASS: - case T_MODULE: - klass = rb_singleton_class(obj); - break; - default: + if (IMEMO_TYPE_P(obj, imemo_fields)) { // HACK + klass = CLASS_OF(obj); + } + else { klass = rb_obj_class(obj); - break; } bool allow_new_shape = RCLASS_VARIATION_COUNT(klass) < SHAPE_MAX_VARIATIONS; bool variation_created = false; rb_shape_t *new_shape = get_next_shape_internal(shape, id, SHAPE_IVAR, &variation_created, allow_new_shape); + if (!new_shape) { + // We could create a new variation, transitioning to TOO_COMPLEX. + return NULL; + } + // Check if we should update max_iv_count on the object's class if (obj != klass && new_shape->next_field_index > RCLASS_MAX_IV_COUNT(klass)) { RCLASS_SET_MAX_IV_COUNT(klass, new_shape->next_field_index); @@ -841,13 +932,31 @@ shape_get_next(rb_shape_t *shape, VALUE obj, ID id, bool emit_warnings) shape_id_t rb_shape_transition_add_ivar(VALUE obj, ID id) { - return rb_shape_id(shape_get_next(rb_obj_shape(obj), obj, id, true)); + shape_id_t original_shape_id = RBASIC_SHAPE_ID(obj); + RUBY_ASSERT(!shape_frozen_p(original_shape_id)); + + rb_shape_t *next_shape = shape_get_next(RSHAPE(original_shape_id), obj, id, true); + if (next_shape) { + return shape_id(next_shape, original_shape_id); + } + else { + return transition_complex(original_shape_id); + } } shape_id_t rb_shape_transition_add_ivar_no_warnings(VALUE obj, ID id) { - return rb_shape_id(shape_get_next(rb_obj_shape(obj), obj, id, false)); + shape_id_t original_shape_id = RBASIC_SHAPE_ID(obj); + RUBY_ASSERT(!shape_frozen_p(original_shape_id)); + + rb_shape_t *next_shape = shape_get_next(RSHAPE(original_shape_id), obj, id, false); + if (next_shape) { + return shape_id(next_shape, original_shape_id); + } + else { + return transition_complex(original_shape_id); + } } // Same as rb_shape_get_iv_index, but uses a provided valid shape id and index @@ -856,14 +965,14 @@ bool rb_shape_get_iv_index_with_hint(shape_id_t shape_id, ID id, attr_index_t *value, shape_id_t *shape_id_hint) { attr_index_t index_hint = *value; - rb_shape_t *shape = RSHAPE(shape_id); - rb_shape_t *initial_shape = shape; if (*shape_id_hint == INVALID_SHAPE_ID) { *shape_id_hint = shape_id; - return rb_shape_get_iv_index(shape, id, value); + return rb_shape_get_iv_index(shape_id, id, value); } + rb_shape_t *shape = RSHAPE(shape_id); + rb_shape_t *initial_shape = shape; rb_shape_t *shape_hint = RSHAPE(*shape_id_hint); // We assume it's likely shape_id_hint and shape_id have a close common @@ -883,13 +992,13 @@ rb_shape_get_iv_index_with_hint(shape_id_t shape_id, ID id, attr_index_t *value, if (shape_hint == shape) { // We've found a common ancestor so use the index hint *value = index_hint; - *shape_id_hint = rb_shape_id(shape); + *shape_id_hint = raw_shape_id(shape); return true; } if (shape->edge_name == id) { // We found the matching id before a common ancestor *value = shape->next_field_index - 1; - *shape_id_hint = rb_shape_id(shape); + *shape_id_hint = raw_shape_id(shape); return true; } @@ -903,30 +1012,32 @@ rb_shape_get_iv_index_with_hint(shape_id_t shape_id, ID id, attr_index_t *value, shape = initial_shape; } *shape_id_hint = shape_id; - return rb_shape_get_iv_index(shape, id, value); + return shape_get_iv_index(shape, id, value); } static bool -shape_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value) +shape_cache_find_ivar(rb_shape_t *shape, ID id, rb_shape_t **ivar_shape) +{ + if (shape->ancestor_index && shape->next_field_index >= ANCESTOR_CACHE_THRESHOLD) { + redblack_node_t *node = redblack_find(shape->ancestor_index, id); + if (node) { + *ivar_shape = redblack_value(node); + + return true; + } + } + + return false; +} + +static bool +shape_find_ivar(rb_shape_t *shape, ID id, rb_shape_t **ivar_shape) { while (shape->parent_id != INVALID_SHAPE_ID) { if (shape->edge_name == id) { - enum shape_type shape_type; - shape_type = (enum shape_type)shape->type; - - switch (shape_type) { - case SHAPE_IVAR: - RUBY_ASSERT(shape->next_field_index > 0); - *value = shape->next_field_index - 1; - return true; - case SHAPE_ROOT: - case SHAPE_T_OBJECT: - return false; - case SHAPE_OBJ_TOO_COMPLEX: - case SHAPE_OBJ_ID: - case SHAPE_FROZEN: - rb_bug("Ivar should not exist on transition"); - } + RUBY_ASSERT(shape->type == SHAPE_IVAR); + *ivar_shape = shape; + return true; } shape = RSHAPE(shape->parent_id); @@ -935,56 +1046,44 @@ shape_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value) return false; } -static bool -shape_cache_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value) -{ - if (shape->ancestor_index && shape->next_field_index >= ANCESTOR_CACHE_THRESHOLD) { - redblack_node_t *node = redblack_find(shape->ancestor_index, id); - if (node) { - rb_shape_t *shape = redblack_value(node); - *value = shape->next_field_index - 1; - -#if RUBY_DEBUG - attr_index_t shape_tree_index; - RUBY_ASSERT(shape_get_iv_index(shape, id, &shape_tree_index)); - RUBY_ASSERT(shape_tree_index == *value); -#endif - - return true; - } - - /* Verify the cache is correct by checking that this instance variable - * does not exist in the shape tree either. */ - RUBY_ASSERT(!shape_get_iv_index(shape, id, value)); - } - - return false; -} - bool -rb_shape_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value) +rb_shape_find_ivar(shape_id_t current_shape_id, ID id, shape_id_t *ivar_shape_id) { - // It doesn't make sense to ask for the index of an IV that's stored - // on an object that is "too complex" as it uses a hash for storing IVs - RUBY_ASSERT(rb_shape_id(shape) != ROOT_TOO_COMPLEX_SHAPE_ID); + RUBY_ASSERT(!rb_shape_too_complex_p(current_shape_id)); - if (!shape_cache_get_iv_index(shape, id, value)) { + rb_shape_t *shape = RSHAPE(current_shape_id); + rb_shape_t *ivar_shape; + + if (!shape_cache_find_ivar(shape, id, &ivar_shape)) { // If it wasn't in the ancestor cache, then don't do a linear search if (shape->ancestor_index && shape->next_field_index >= ANCESTOR_CACHE_THRESHOLD) { return false; } else { - return shape_get_iv_index(shape, id, value); + if (!shape_find_ivar(shape, id, &ivar_shape)) { + return false; + } } } + *ivar_shape_id = shape_id(ivar_shape, current_shape_id); + return true; } -void -rb_shape_set_shape(VALUE obj, rb_shape_t *shape) +bool +rb_shape_get_iv_index(shape_id_t shape_id, ID id, attr_index_t *value) { - rb_shape_set_shape_id(obj, rb_shape_id(shape)); + // It doesn't make sense to ask for the index of an IV that's stored + // on an object that is "too complex" as it uses a hash for storing IVs + RUBY_ASSERT(!rb_shape_too_complex_p(shape_id)); + + shape_id_t ivar_shape_id; + if (rb_shape_find_ivar(shape_id, id, &ivar_shape_id)) { + *value = RSHAPE_INDEX(ivar_shape_id); + return true; + } + return false; } int32_t @@ -993,82 +1092,20 @@ rb_shape_id_offset(void) return sizeof(uintptr_t) - SHAPE_ID_NUM_BITS / sizeof(uintptr_t); } -static rb_shape_t * -shape_traverse_from_new_root(rb_shape_t *initial_shape, rb_shape_t *dest_shape) -{ - RUBY_ASSERT(initial_shape->type == SHAPE_T_OBJECT); - rb_shape_t *next_shape = initial_shape; - - if (dest_shape->type != initial_shape->type) { - next_shape = shape_traverse_from_new_root(initial_shape, RSHAPE(dest_shape->parent_id)); - if (!next_shape) { - return NULL; - } - } - - switch ((enum shape_type)dest_shape->type) { - case SHAPE_IVAR: - case SHAPE_OBJ_ID: - case SHAPE_FROZEN: - if (!next_shape->edges) { - return NULL; - } - - VALUE lookup_result; - if (SINGLE_CHILD_P(next_shape->edges)) { - rb_shape_t *child = SINGLE_CHILD(next_shape->edges); - if (child->edge_name == dest_shape->edge_name) { - return child; - } - else { - return NULL; - } - } - else { - if (rb_id_table_lookup(next_shape->edges, dest_shape->edge_name, &lookup_result)) { - next_shape = (rb_shape_t *)lookup_result; - } - else { - return NULL; - } - } - break; - case SHAPE_ROOT: - case SHAPE_T_OBJECT: - break; - case SHAPE_OBJ_TOO_COMPLEX: - rb_bug("Unreachable"); - break; - } - - return next_shape; -} - -shape_id_t -rb_shape_traverse_from_new_root(shape_id_t initial_shape_id, shape_id_t dest_shape_id) -{ - rb_shape_t *initial_shape = RSHAPE(initial_shape_id); - rb_shape_t *dest_shape = RSHAPE(dest_shape_id); - return rb_shape_id(shape_traverse_from_new_root(initial_shape, dest_shape)); -} - // Rebuild a similar shape with the same ivars but starting from // a different SHAPE_T_OBJECT, and don't cary over non-canonical transitions -// such as SHAPE_FROZEN or SHAPE_OBJ_ID. -rb_shape_t * -rb_shape_rebuild_shape(rb_shape_t *initial_shape, rb_shape_t *dest_shape) +// such as SHAPE_OBJ_ID. +static rb_shape_t * +shape_rebuild(rb_shape_t *initial_shape, rb_shape_t *dest_shape) { - RUBY_ASSERT(rb_shape_id(initial_shape) != ROOT_TOO_COMPLEX_SHAPE_ID); - RUBY_ASSERT(rb_shape_id(dest_shape) != ROOT_TOO_COMPLEX_SHAPE_ID); - rb_shape_t *midway_shape; - RUBY_ASSERT(initial_shape->type == SHAPE_T_OBJECT || initial_shape->type == SHAPE_ROOT); + RUBY_ASSERT(initial_shape->type == SHAPE_ROOT); if (dest_shape->type != initial_shape->type) { - midway_shape = rb_shape_rebuild_shape(initial_shape, RSHAPE(dest_shape->parent_id)); - if (UNLIKELY(rb_shape_id(midway_shape) == ROOT_TOO_COMPLEX_SHAPE_ID)) { - return midway_shape; + midway_shape = shape_rebuild(initial_shape, RSHAPE(dest_shape->parent_id)); + if (UNLIKELY(!midway_shape)) { + return NULL; } } else { @@ -1081,33 +1118,71 @@ rb_shape_rebuild_shape(rb_shape_t *initial_shape, rb_shape_t *dest_shape) break; case SHAPE_OBJ_ID: case SHAPE_ROOT: - case SHAPE_FROZEN: - case SHAPE_T_OBJECT: - break; - case SHAPE_OBJ_TOO_COMPLEX: - rb_bug("Unreachable"); break; } return midway_shape; } -RUBY_FUNC_EXPORTED bool -rb_shape_obj_too_complex_p(VALUE obj) +// Rebuild `dest_shape_id` starting from `initial_shape_id`, and keep only SHAPE_IVAR transitions. +// SHAPE_OBJ_ID and frozen status are lost. +shape_id_t +rb_shape_rebuild(shape_id_t initial_shape_id, shape_id_t dest_shape_id) { - return rb_shape_too_complex_p(rb_obj_shape(obj)); + RUBY_ASSERT(!rb_shape_too_complex_p(initial_shape_id)); + RUBY_ASSERT(!rb_shape_too_complex_p(dest_shape_id)); + + rb_shape_t *next_shape = shape_rebuild(RSHAPE(initial_shape_id), RSHAPE(dest_shape_id)); + if (next_shape) { + return shape_id(next_shape, initial_shape_id); + } + else { + return transition_complex(initial_shape_id | (dest_shape_id & SHAPE_ID_FL_HAS_OBJECT_ID)); + } } -bool -rb_shape_id_too_complex_p(shape_id_t shape_id) +void +rb_shape_copy_fields(VALUE dest, VALUE *dest_buf, shape_id_t dest_shape_id, VALUE *src_buf, shape_id_t src_shape_id) { - return rb_shape_too_complex_p(RSHAPE(shape_id)); + rb_shape_t *dest_shape = RSHAPE(dest_shape_id); + rb_shape_t *src_shape = RSHAPE(src_shape_id); + + if (src_shape->next_field_index == dest_shape->next_field_index) { + // Happy path, we can just memcpy the ivptr content + MEMCPY(dest_buf, src_buf, VALUE, dest_shape->next_field_index); + + // Fire write barriers + for (uint32_t i = 0; i < dest_shape->next_field_index; i++) { + RB_OBJ_WRITTEN(dest, Qundef, dest_buf[i]); + } + } + else { + while (src_shape->parent_id != INVALID_SHAPE_ID) { + if (src_shape->type == SHAPE_IVAR) { + while (dest_shape->edge_name != src_shape->edge_name) { + if (UNLIKELY(dest_shape->parent_id == INVALID_SHAPE_ID)) { + rb_bug("Lost field %s", rb_id2name(src_shape->edge_name)); + } + dest_shape = RSHAPE(dest_shape->parent_id); + } + + RB_OBJ_WRITE(dest, &dest_buf[dest_shape->next_field_index - 1], src_buf[src_shape->next_field_index - 1]); + } + src_shape = RSHAPE(src_shape->parent_id); + } + } } -bool -rb_shape_too_complex_p(rb_shape_t *shape) +void +rb_shape_copy_complex_ivars(VALUE dest, VALUE obj, shape_id_t src_shape_id, st_table *fields_table) { - return shape->flags & SHAPE_FL_TOO_COMPLEX; + // obj is TOO_COMPLEX so we can copy its iv_hash + st_table *table = st_copy(fields_table); + if (rb_shape_has_object_id(src_shape_id)) { + st_data_t id = (st_data_t)id_object_id; + st_delete(table, &id, NULL); + } + rb_obj_init_too_complex(dest, table); } size_t @@ -1119,7 +1194,7 @@ rb_shape_edges_count(shape_id_t shape_id) return 1; } else { - return rb_id_table_size(shape->edges); + return rb_managed_id_table_size(shape->edges); } } return 0; @@ -1132,38 +1207,130 @@ rb_shape_memsize(shape_id_t shape_id) size_t memsize = sizeof(rb_shape_t); if (shape->edges && !SINGLE_CHILD_P(shape->edges)) { - memsize += rb_id_table_memsize(shape->edges); + memsize += rb_managed_id_table_size(shape->edges); } return memsize; } +bool +rb_shape_foreach_field(shape_id_t initial_shape_id, rb_shape_foreach_transition_callback func, void *data) +{ + RUBY_ASSERT(!rb_shape_too_complex_p(initial_shape_id)); + + rb_shape_t *shape = RSHAPE(initial_shape_id); + if (shape->type == SHAPE_ROOT) { + return true; + } + + shape_id_t parent_id = shape_id(RSHAPE(shape->parent_id), initial_shape_id); + if (rb_shape_foreach_field(parent_id, func, data)) { + switch (func(shape_id(shape, initial_shape_id), data)) { + case ST_STOP: + return false; + case ST_CHECK: + case ST_CONTINUE: + break; + default: + rb_bug("unreachable"); + } + } + return true; +} + +#if RUBY_DEBUG +bool +rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id) +{ + if (shape_id == INVALID_SHAPE_ID) { + rb_bug("Can't set INVALID_SHAPE_ID on an object"); + } + + rb_shape_t *shape = RSHAPE(shape_id); + + bool has_object_id = false; + while (shape->parent_id != INVALID_SHAPE_ID) { + if (shape->type == SHAPE_OBJ_ID) { + has_object_id = true; + break; + } + shape = RSHAPE(shape->parent_id); + } + + if (rb_shape_has_object_id(shape_id)) { + if (!has_object_id) { + rb_p(obj); + rb_bug("shape_id claim having obj_id but doesn't shape_id=%u, obj=%s", shape_id, rb_obj_info(obj)); + } + } + else { + if (has_object_id) { + rb_p(obj); + rb_bug("shape_id claim not having obj_id but it does shape_id=%u, obj=%s", shape_id, rb_obj_info(obj)); + } + } + + // Make sure SHAPE_ID_HAS_IVAR_MASK is valid. + if (rb_shape_too_complex_p(shape_id)) { + RUBY_ASSERT(shape_id & SHAPE_ID_HAS_IVAR_MASK); + } + else { + attr_index_t ivar_count = RSHAPE_LEN(shape_id); + if (has_object_id) { + ivar_count--; + } + if (ivar_count) { + RUBY_ASSERT(shape_id & SHAPE_ID_HAS_IVAR_MASK); + } + else { + RUBY_ASSERT(!(shape_id & SHAPE_ID_HAS_IVAR_MASK)); + } + } + + uint8_t flags_heap_index = rb_shape_heap_index(shape_id); + if (RB_TYPE_P(obj, T_OBJECT)) { + RUBY_ASSERT(flags_heap_index > 0); + size_t shape_id_slot_size = rb_shape_tree.capacities[flags_heap_index - 1] * sizeof(VALUE) + sizeof(struct RBasic); + size_t actual_slot_size = rb_gc_obj_slot_size(obj); + + if (shape_id_slot_size != actual_slot_size) { + rb_bug("shape_id heap_index flags mismatch: shape_id_slot_size=%zu, gc_slot_size=%zu\n", shape_id_slot_size, actual_slot_size); + } + } + else { + if (flags_heap_index) { + rb_bug("shape_id indicate heap_index > 0 but object is not T_OBJECT: %s", rb_obj_info(obj)); + } + } + + return true; +} +#endif + #if SHAPE_DEBUG + /* - * Exposing Shape to Ruby via RubyVM.debug_shape + * Exposing Shape to Ruby via RubyVM::Shape.of(object) */ static VALUE shape_too_complex(VALUE self) { shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id"))); - rb_shape_t *shape = RSHAPE(shape_id); - return RBOOL(rb_shape_too_complex_p(shape)); + return RBOOL(rb_shape_too_complex_p(shape_id)); } static VALUE shape_frozen(VALUE self) { shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id"))); - rb_shape_t *shape = RSHAPE(shape_id); - return RBOOL(rb_shape_frozen_shape_p(shape)); + return RBOOL(shape_id & SHAPE_ID_FL_FROZEN); } static VALUE -shape_has_object_id(VALUE self) +shape_has_object_id_p(VALUE self) { shape_id_t shape_id = NUM2INT(rb_struct_getmember(self, rb_intern("id"))); - rb_shape_t *shape = RSHAPE(shape_id); - return RBOOL(rb_shape_has_object_id(shape)); + return RBOOL(rb_shape_has_object_id(shape_id)); } static VALUE @@ -1178,18 +1345,20 @@ parse_key(ID key) static VALUE rb_shape_edge_name(rb_shape_t *shape); static VALUE -rb_shape_t_to_rb_cShape(rb_shape_t *shape) +shape_id_t_to_rb_cShape(shape_id_t shape_id) { VALUE rb_cShape = rb_const_get(rb_cRubyVM, rb_intern("Shape")); + rb_shape_t *shape = RSHAPE(shape_id); VALUE obj = rb_struct_new(rb_cShape, - INT2NUM(rb_shape_id(shape)), + INT2NUM(shape_id), + INT2NUM(shape_id & SHAPE_ID_OFFSET_MASK), INT2NUM(shape->parent_id), rb_shape_edge_name(shape), INT2NUM(shape->next_field_index), - INT2NUM(shape->heap_index), + INT2NUM(rb_shape_heap_index(shape_id)), INT2NUM(shape->type), - INT2NUM(shape->capacity)); + INT2NUM(RSHAPE_CAPACITY(shape_id))); rb_obj_freeze(obj); return obj; } @@ -1197,16 +1366,14 @@ rb_shape_t_to_rb_cShape(rb_shape_t *shape) static enum rb_id_table_iterator_result rb_edges_to_hash(ID key, VALUE value, void *ref) { - rb_hash_aset(*(VALUE *)ref, parse_key(key), rb_shape_t_to_rb_cShape((rb_shape_t *)value)); + rb_hash_aset(*(VALUE *)ref, parse_key(key), shape_id_t_to_rb_cShape(raw_shape_id((rb_shape_t *)value))); return ID_TABLE_CONTINUE; } static VALUE rb_shape_edges(VALUE self) { - rb_shape_t *shape; - - shape = RSHAPE(NUM2INT(rb_struct_getmember(self, rb_intern("id")))); + rb_shape_t *shape = RSHAPE(NUM2INT(rb_struct_getmember(self, rb_intern("id")))); VALUE hash = rb_hash_new(); @@ -1216,7 +1383,9 @@ rb_shape_edges(VALUE self) rb_edges_to_hash(child->edge_name, (VALUE)child, &hash); } else { - rb_id_table_foreach(shape->edges, rb_edges_to_hash, &hash); + VALUE edges = shape->edges; + rb_managed_id_table_foreach(edges, rb_edges_to_hash, &hash); + RB_GC_GUARD(edges); } } @@ -1248,7 +1417,7 @@ rb_shape_parent(VALUE self) rb_shape_t *shape; shape = RSHAPE(NUM2INT(rb_struct_getmember(self, rb_intern("id")))); if (shape->parent_id != INVALID_SHAPE_ID) { - return rb_shape_t_to_rb_cShape(RSHAPE(shape->parent_id)); + return shape_id_t_to_rb_cShape(shape->parent_id); } else { return Qnil; @@ -1258,19 +1427,19 @@ rb_shape_parent(VALUE self) static VALUE rb_shape_debug_shape(VALUE self, VALUE obj) { - return rb_shape_t_to_rb_cShape(rb_obj_shape(obj)); + return shape_id_t_to_rb_cShape(rb_obj_shape_id(obj)); } static VALUE rb_shape_root_shape(VALUE self) { - return rb_shape_t_to_rb_cShape(rb_shape_get_root_shape()); + return shape_id_t_to_rb_cShape(ROOT_SHAPE_ID); } static VALUE rb_shape_shapes_available(VALUE self) { - return INT2NUM(MAX_SHAPE_ID - (GET_SHAPE_TREE()->next_shape_id - 1)); + return INT2NUM(MAX_SHAPE_ID - (rb_shapes_count() - 1)); } static VALUE @@ -1278,7 +1447,7 @@ rb_shape_exhaust(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 1); int offset = argc == 1 ? NUM2INT(argv[0]) : 0; - GET_SHAPE_TREE()->next_shape_id = MAX_SHAPE_ID - offset + 1; + RUBY_ATOMIC_SET(rb_shape_tree.next_shape_id, MAX_SHAPE_ID - offset + 1); return Qnil; } @@ -1290,15 +1459,17 @@ static enum rb_id_table_iterator_result collect_keys_and_values(ID key, VALUE va return ID_TABLE_CONTINUE; } -static VALUE edges(struct rb_id_table* edges) +static VALUE edges(VALUE edges) { VALUE hash = rb_hash_new(); - if (SINGLE_CHILD_P(edges)) { - rb_shape_t *child = SINGLE_CHILD(edges); - collect_keys_and_values(child->edge_name, (VALUE)child, &hash); - } - else { - rb_id_table_foreach(edges, collect_keys_and_values, &hash); + if (edges) { + if (SINGLE_CHILD_P(edges)) { + rb_shape_t *child = SINGLE_CHILD(edges); + collect_keys_and_values(child->edge_name, (VALUE)child, &hash); + } + else { + rb_managed_id_table_foreach(edges, collect_keys_and_values, &hash); + } } return hash; } @@ -1308,7 +1479,7 @@ shape_to_h(rb_shape_t *shape) { VALUE rb_shape = rb_hash_new(); - rb_hash_aset(rb_shape, ID2SYM(rb_intern("id")), INT2NUM(rb_shape_id(shape))); + rb_hash_aset(rb_shape, ID2SYM(rb_intern("id")), INT2NUM(raw_shape_id(shape))); rb_hash_aset(rb_shape, ID2SYM(rb_intern("edges")), edges(shape->edges)); if (shape == rb_shape_get_root_shape()) { @@ -1332,10 +1503,10 @@ static VALUE rb_shape_find_by_id(VALUE mod, VALUE id) { shape_id_t shape_id = NUM2UINT(id); - if (shape_id >= GET_SHAPE_TREE()->next_shape_id) { + if (shape_id >= rb_shapes_count()) { rb_raise(rb_eArgError, "Shape ID %d is out of bounds\n", shape_id); } - return rb_shape_t_to_rb_cShape(RSHAPE(shape_id)); + return shape_id_t_to_rb_cShape(shape_id); } #endif @@ -1346,108 +1517,83 @@ rb_shape_find_by_id(VALUE mod, VALUE id) void Init_default_shapes(void) { - rb_shape_tree_ptr = xcalloc(1, sizeof(rb_shape_tree_t)); + size_t *heap_sizes = rb_gc_heap_sizes(); + size_t heaps_count = 0; + while (heap_sizes[heaps_count]) { + heaps_count++; + } + attr_index_t *capacities = ALLOC_N(attr_index_t, heaps_count + 1); + capacities[heaps_count] = 0; + size_t index; + for (index = 0; index < heaps_count; index++) { + capacities[index] = (heap_sizes[index] - sizeof(struct RBasic)) / sizeof(VALUE); + } + rb_shape_tree.capacities = capacities; #ifdef HAVE_MMAP size_t shape_list_mmap_size = rb_size_mul_or_raise(SHAPE_BUFFER_SIZE, sizeof(rb_shape_t), rb_eRuntimeError); - rb_shape_tree_ptr->shape_list = (rb_shape_t *)mmap(NULL, shape_list_mmap_size, + rb_shape_tree.shape_list = (rb_shape_t *)mmap(NULL, shape_list_mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (GET_SHAPE_TREE()->shape_list == MAP_FAILED) { - GET_SHAPE_TREE()->shape_list = 0; + if (rb_shape_tree.shape_list == MAP_FAILED) { + rb_shape_tree.shape_list = 0; } else { - ruby_annotate_mmap(rb_shape_tree_ptr->shape_list, shape_list_mmap_size, "Ruby:Init_default_shapes:shape_list"); + ruby_annotate_mmap(rb_shape_tree.shape_list, shape_list_mmap_size, "Ruby:Init_default_shapes:shape_list"); } #else - GET_SHAPE_TREE()->shape_list = xcalloc(SHAPE_BUFFER_SIZE, sizeof(rb_shape_t)); + rb_shape_tree.shape_list = xcalloc(SHAPE_BUFFER_SIZE, sizeof(rb_shape_t)); #endif - if (!GET_SHAPE_TREE()->shape_list) { + if (!rb_shape_tree.shape_list) { rb_memerror(); } - id_frozen = rb_make_internal_id(); - id_t_object = rb_make_internal_id(); - ruby_internal_object_id = rb_make_internal_id(); + id_object_id = rb_make_internal_id(); #ifdef HAVE_MMAP size_t shape_cache_mmap_size = rb_size_mul_or_raise(REDBLACK_CACHE_SIZE, sizeof(redblack_node_t), rb_eRuntimeError); - rb_shape_tree_ptr->shape_cache = (redblack_node_t *)mmap(NULL, shape_cache_mmap_size, + rb_shape_tree.shape_cache = (redblack_node_t *)mmap(NULL, shape_cache_mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - rb_shape_tree_ptr->cache_size = 0; + rb_shape_tree.cache_size = 0; // If mmap fails, then give up on the redblack tree cache. // We set the cache size such that the redblack node allocators think // the cache is full. - if (GET_SHAPE_TREE()->shape_cache == MAP_FAILED) { - GET_SHAPE_TREE()->shape_cache = 0; - GET_SHAPE_TREE()->cache_size = REDBLACK_CACHE_SIZE; + if (rb_shape_tree.shape_cache == MAP_FAILED) { + rb_shape_tree.shape_cache = 0; + rb_shape_tree.cache_size = REDBLACK_CACHE_SIZE; } else { - ruby_annotate_mmap(rb_shape_tree_ptr->shape_cache, shape_cache_mmap_size, "Ruby:Init_default_shapes:shape_cache"); + ruby_annotate_mmap(rb_shape_tree.shape_cache, shape_cache_mmap_size, "Ruby:Init_default_shapes:shape_cache"); } #endif + rb_gc_register_address(&shape_tree_obj); + shape_tree_obj = TypedData_Wrap_Struct(0, &shape_tree_type, (void *)1); + // Root shape rb_shape_t *root = rb_shape_alloc_with_parent_id(0, INVALID_SHAPE_ID); root->capacity = 0; root->type = SHAPE_ROOT; - root->heap_index = 0; - GET_SHAPE_TREE()->root_shape = root; - RUBY_ASSERT(rb_shape_id(GET_SHAPE_TREE()->root_shape) == ROOT_SHAPE_ID); + rb_shape_tree.root_shape = root; + RUBY_ASSERT(raw_shape_id(rb_shape_tree.root_shape) == ROOT_SHAPE_ID); + RUBY_ASSERT(!(raw_shape_id(rb_shape_tree.root_shape) & SHAPE_ID_HAS_IVAR_MASK)); - bool dont_care; - // Special const shape -#if RUBY_DEBUG - rb_shape_t *special_const_shape = -#endif - get_next_shape_internal(root, id_frozen, SHAPE_FROZEN, &dont_care, true); - RUBY_ASSERT(rb_shape_id(special_const_shape) == SPECIAL_CONST_SHAPE_ID); - RUBY_ASSERT(SPECIAL_CONST_SHAPE_ID == (GET_SHAPE_TREE()->next_shape_id - 1)); - RUBY_ASSERT(rb_shape_frozen_shape_p(special_const_shape)); - - rb_shape_t *too_complex_shape = rb_shape_alloc_with_parent_id(0, ROOT_SHAPE_ID); - too_complex_shape->type = SHAPE_OBJ_TOO_COMPLEX; - too_complex_shape->flags |= SHAPE_FL_TOO_COMPLEX; - too_complex_shape->heap_index = 0; - RUBY_ASSERT(ROOT_TOO_COMPLEX_SHAPE_ID == (GET_SHAPE_TREE()->next_shape_id - 1)); - RUBY_ASSERT(rb_shape_id(too_complex_shape) == ROOT_TOO_COMPLEX_SHAPE_ID); - - // Make shapes for T_OBJECT - size_t *sizes = rb_gc_heap_sizes(); - for (int i = 0; sizes[i] > 0; i++) { - rb_shape_t *t_object_shape = rb_shape_alloc_with_parent_id(0, INVALID_SHAPE_ID); - t_object_shape->type = SHAPE_T_OBJECT; - t_object_shape->heap_index = i; - t_object_shape->capacity = (uint32_t)((sizes[i] - offsetof(struct RObject, as.ary)) / sizeof(VALUE)); - t_object_shape->edges = rb_id_table_create(0); - t_object_shape->ancestor_index = LEAF; - RUBY_ASSERT(rb_shape_id(t_object_shape) == rb_shape_root(i)); - } - - // Prebuild TOO_COMPLEX variations so that they already exist if we ever need them after we - // ran out of shapes. - rb_shape_t *shape; - shape = get_next_shape_internal(too_complex_shape, id_frozen, SHAPE_FROZEN, &dont_care, true); - get_next_shape_internal(shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true); - - shape = get_next_shape_internal(too_complex_shape, ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true); - get_next_shape_internal(shape, id_frozen, SHAPE_FROZEN, &dont_care, true); + bool dontcare; + rb_shape_t *root_with_obj_id = get_next_shape_internal(root, id_object_id, SHAPE_OBJ_ID, &dontcare, true); + RUBY_ASSERT(root_with_obj_id); + RUBY_ASSERT(raw_shape_id(root_with_obj_id) == ROOT_SHAPE_WITH_OBJ_ID); + RUBY_ASSERT(root_with_obj_id->type == SHAPE_OBJ_ID); + RUBY_ASSERT(root_with_obj_id->edge_name == id_object_id); + RUBY_ASSERT(root_with_obj_id->next_field_index == 1); + RUBY_ASSERT(!(raw_shape_id(root_with_obj_id) & SHAPE_ID_HAS_IVAR_MASK)); + (void)root_with_obj_id; } void rb_shape_free_all(void) { - rb_shape_t *cursor = rb_shape_get_root_shape(); - rb_shape_t *end = RSHAPE(GET_SHAPE_TREE()->next_shape_id); - while (cursor < end) { - if (cursor->edges && !SINGLE_CHILD_P(cursor->edges)) { - rb_id_table_free(cursor->edges); - } - cursor++; - } - - xfree(GET_SHAPE_TREE()); + xfree((void *)rb_shape_tree.capacities); } void @@ -1458,6 +1604,7 @@ Init_shape(void) * :nodoc: */ VALUE rb_cShape = rb_struct_define_under(rb_cRubyVM, "Shape", "id", + "raw_id", "parent_id", "edge_name", "next_field_index", @@ -1471,17 +1618,13 @@ Init_shape(void) rb_define_method(rb_cShape, "depth", rb_shape_export_depth, 0); rb_define_method(rb_cShape, "too_complex?", shape_too_complex, 0); rb_define_method(rb_cShape, "shape_frozen?", shape_frozen, 0); - rb_define_method(rb_cShape, "has_object_id?", shape_has_object_id, 0); + rb_define_method(rb_cShape, "has_object_id?", shape_has_object_id_p, 0); rb_define_const(rb_cShape, "SHAPE_ROOT", INT2NUM(SHAPE_ROOT)); rb_define_const(rb_cShape, "SHAPE_IVAR", INT2NUM(SHAPE_IVAR)); - rb_define_const(rb_cShape, "SHAPE_T_OBJECT", INT2NUM(SHAPE_T_OBJECT)); - rb_define_const(rb_cShape, "SHAPE_FROZEN", INT2NUM(SHAPE_FROZEN)); rb_define_const(rb_cShape, "SHAPE_ID_NUM_BITS", INT2NUM(SHAPE_ID_NUM_BITS)); rb_define_const(rb_cShape, "SHAPE_FLAG_SHIFT", INT2NUM(SHAPE_FLAG_SHIFT)); rb_define_const(rb_cShape, "SPECIAL_CONST_SHAPE_ID", INT2NUM(SPECIAL_CONST_SHAPE_ID)); - rb_define_const(rb_cShape, "ROOT_TOO_COMPLEX_SHAPE_ID", INT2NUM(ROOT_TOO_COMPLEX_SHAPE_ID)); - rb_define_const(rb_cShape, "FIRST_T_OBJECT_SHAPE_ID", INT2NUM(FIRST_T_OBJECT_SHAPE_ID)); rb_define_const(rb_cShape, "SHAPE_MAX_VARIATIONS", INT2NUM(SHAPE_MAX_VARIATIONS)); rb_define_const(rb_cShape, "SIZEOF_RB_SHAPE_T", INT2NUM(sizeof(rb_shape_t))); rb_define_const(rb_cShape, "SIZEOF_REDBLACK_NODE_T", INT2NUM(sizeof(redblack_node_t))); diff --git a/shape.h b/shape.h index 5db5b78681..a418dc7821 100644 --- a/shape.h +++ b/shape.h @@ -3,55 +3,87 @@ #include "internal/gc.h" -#if (SIZEOF_UINT64_T <= SIZEOF_VALUE) - -#define SIZEOF_SHAPE_T 4 -#define SHAPE_IN_BASIC_FLAGS 1 -typedef uint32_t attr_index_t; -typedef uint32_t shape_id_t; -# define SHAPE_ID_NUM_BITS 32 - -#else - -#define SIZEOF_SHAPE_T 2 -#define SHAPE_IN_BASIC_FLAGS 0 typedef uint16_t attr_index_t; -typedef uint16_t shape_id_t; -# define SHAPE_ID_NUM_BITS 16 +typedef uint32_t shape_id_t; +#define SHAPE_ID_NUM_BITS 32 +#define SHAPE_ID_OFFSET_NUM_BITS 19 -#endif +STATIC_ASSERT(shape_id_num_bits, SHAPE_ID_NUM_BITS == sizeof(shape_id_t) * CHAR_BIT); + +#define SHAPE_BUFFER_SIZE (1 << SHAPE_ID_OFFSET_NUM_BITS) +#define SHAPE_ID_OFFSET_MASK (SHAPE_BUFFER_SIZE - 1) + +#define SHAPE_ID_HEAP_INDEX_BITS 3 +#define SHAPE_ID_HEAP_INDEX_MAX ((1 << SHAPE_ID_HEAP_INDEX_BITS) - 1) + +#define SHAPE_ID_FL_USHIFT (SHAPE_ID_OFFSET_NUM_BITS + SHAPE_ID_HEAP_INDEX_BITS) +#define SHAPE_ID_HEAP_INDEX_OFFSET SHAPE_ID_FL_USHIFT + +// shape_id_t bits: +// 0-18 SHAPE_ID_OFFSET_MASK +// index in rb_shape_tree.shape_list. Allow to access `rb_shape_t *`. +// 19-21 SHAPE_ID_HEAP_INDEX_MASK +// index in rb_shape_tree.capacities. Allow to access slot size. +// 22 SHAPE_ID_FL_FROZEN +// Whether the object is frozen or not. +// 23 SHAPE_ID_FL_HAS_OBJECT_ID +// Whether the object has an `SHAPE_OBJ_ID` transition. +// 24 SHAPE_ID_FL_TOO_COMPLEX +// The object is backed by a `st_table`. + +enum shape_id_fl_type { +#define RBIMPL_SHAPE_ID_FL(n) (1<<(SHAPE_ID_FL_USHIFT+n)) + + SHAPE_ID_HEAP_INDEX_MASK = RBIMPL_SHAPE_ID_FL(0) | RBIMPL_SHAPE_ID_FL(1) | RBIMPL_SHAPE_ID_FL(2), + + SHAPE_ID_FL_FROZEN = RBIMPL_SHAPE_ID_FL(3), + SHAPE_ID_FL_HAS_OBJECT_ID = RBIMPL_SHAPE_ID_FL(4), + SHAPE_ID_FL_TOO_COMPLEX = RBIMPL_SHAPE_ID_FL(5), + + SHAPE_ID_FL_NON_CANONICAL_MASK = SHAPE_ID_FL_FROZEN | SHAPE_ID_FL_HAS_OBJECT_ID, + SHAPE_ID_FLAGS_MASK = SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_NON_CANONICAL_MASK | SHAPE_ID_FL_TOO_COMPLEX, + +#undef RBIMPL_SHAPE_ID_FL +}; + +// This masks allows to check if a shape_id contains any ivar. +// It rely on ROOT_SHAPE_WITH_OBJ_ID==1. +enum { + SHAPE_ID_HAS_IVAR_MASK = SHAPE_ID_FL_TOO_COMPLEX | (SHAPE_ID_OFFSET_MASK - 1), +}; + +// The interpreter doesn't care about frozen status or slot size when reading ivars. +// So we normalize shape_id by clearing these bits to improve cache hits. +// JITs however might care about it. +#define SHAPE_ID_READ_ONLY_MASK (~(SHAPE_ID_FL_FROZEN | SHAPE_ID_HEAP_INDEX_MASK)) typedef uint32_t redblack_id_t; #define SHAPE_MAX_FIELDS (attr_index_t)(-1) +#define SHAPE_FLAG_SHIFT ((SIZEOF_VALUE * CHAR_BIT) - SHAPE_ID_NUM_BITS) +#define SHAPE_FLAG_MASK (((VALUE)-1) >> SHAPE_ID_NUM_BITS) -# define SHAPE_FLAG_MASK (((VALUE)-1) >> SHAPE_ID_NUM_BITS) +#define SHAPE_MAX_VARIATIONS 8 -# define SHAPE_FLAG_SHIFT ((SIZEOF_VALUE * 8) - SHAPE_ID_NUM_BITS) +#define INVALID_SHAPE_ID ((shape_id_t)-1) +#define ATTR_INDEX_NOT_SET ((attr_index_t)-1) -# define SHAPE_MAX_VARIATIONS 8 - -# define INVALID_SHAPE_ID (((uintptr_t)1 << SHAPE_ID_NUM_BITS) - 1) - -#define ROOT_SHAPE_ID 0x0 -#define SPECIAL_CONST_SHAPE_ID 0x1 -// ROOT_TOO_COMPLEX_SHAPE_ID 0x2 -#define FIRST_T_OBJECT_SHAPE_ID 0x3 - -extern ID ruby_internal_object_id; +#define ROOT_SHAPE_ID 0x0 +#define ROOT_SHAPE_WITH_OBJ_ID 0x1 +#define ROOT_TOO_COMPLEX_SHAPE_ID (ROOT_SHAPE_ID | SHAPE_ID_FL_TOO_COMPLEX) +#define ROOT_TOO_COMPLEX_WITH_OBJ_ID (ROOT_SHAPE_WITH_OBJ_ID | SHAPE_ID_FL_TOO_COMPLEX | SHAPE_ID_FL_HAS_OBJECT_ID) +#define SPECIAL_CONST_SHAPE_ID (ROOT_SHAPE_ID | SHAPE_ID_FL_FROZEN) typedef struct redblack_node redblack_node_t; struct rb_shape { - struct rb_id_table *edges; // id_table from ID (ivar) to next shape + VALUE edges; // id_table from ID (ivar) to next shape ID edge_name; // ID (ivar) for transition from parent to rb_shape + redblack_node_t *ancestor_index; + shape_id_t parent_id; attr_index_t next_field_index; // Fields are either ivars or internal properties like `object_id` attr_index_t capacity; // Total capacity of the object with this shape uint8_t type; - uint8_t heap_index; - uint8_t flags; - shape_id_t parent_id; - redblack_node_t *ancestor_index; }; typedef struct rb_shape rb_shape_t; @@ -67,130 +99,234 @@ enum shape_type { SHAPE_ROOT, SHAPE_IVAR, SHAPE_OBJ_ID, - SHAPE_FROZEN, - SHAPE_T_OBJECT, - SHAPE_OBJ_TOO_COMPLEX, +}; + +enum shape_flags { + SHAPE_FL_FROZEN = 1 << 0, + SHAPE_FL_HAS_OBJECT_ID = 1 << 1, + SHAPE_FL_TOO_COMPLEX = 1 << 2, + + SHAPE_FL_NON_CANONICAL_MASK = SHAPE_FL_FROZEN | SHAPE_FL_HAS_OBJECT_ID, }; typedef struct { /* object shapes */ rb_shape_t *shape_list; rb_shape_t *root_shape; - shape_id_t next_shape_id; + const attr_index_t *capacities; + rb_atomic_t next_shape_id; redblack_node_t *shape_cache; unsigned int cache_size; } rb_shape_tree_t; -RUBY_EXTERN rb_shape_tree_t *rb_shape_tree_ptr; -static inline rb_shape_tree_t * -rb_current_shape_tree(void) -{ - return rb_shape_tree_ptr; -} -#define GET_SHAPE_TREE() rb_current_shape_tree() +RUBY_SYMBOL_EXPORT_BEGIN +RUBY_EXTERN rb_shape_tree_t rb_shape_tree; +RUBY_SYMBOL_EXPORT_END static inline shape_id_t -get_shape_id_from_flags(VALUE obj) +rb_shapes_count(void) { - RUBY_ASSERT(!RB_SPECIAL_CONST_P(obj)); - return (shape_id_t)((RBASIC(obj)->flags) >> SHAPE_FLAG_SHIFT); + return (shape_id_t)RUBY_ATOMIC_LOAD(rb_shape_tree.next_shape_id); } -static inline void -set_shape_id_in_flags(VALUE obj, shape_id_t shape_id) -{ - // Ractors are occupying the upper 32 bits of flags, but only in debug mode - // Object shapes are occupying top bits - RBASIC(obj)->flags &= SHAPE_FLAG_MASK; - RBASIC(obj)->flags |= ((VALUE)(shape_id) << SHAPE_FLAG_SHIFT); -} +union rb_attr_index_cache { + uint64_t pack; + struct { + shape_id_t shape_id; + attr_index_t index; + } unpack; +}; - -#if SHAPE_IN_BASIC_FLAGS static inline shape_id_t RBASIC_SHAPE_ID(VALUE obj) { - return get_shape_id_from_flags(obj); + RUBY_ASSERT(!RB_SPECIAL_CONST_P(obj)); + RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_fields)); +#if RBASIC_SHAPE_ID_FIELD + return (shape_id_t)((RBASIC(obj)->shape_id)); +#else + return (shape_id_t)((RBASIC(obj)->flags) >> SHAPE_FLAG_SHIFT); +#endif } +// Same as RBASIC_SHAPE_ID but with flags that have no impact +// on reads removed. e.g. Remove FL_FROZEN. +static inline shape_id_t +RBASIC_SHAPE_ID_FOR_READ(VALUE obj) +{ + return RBASIC_SHAPE_ID(obj) & SHAPE_ID_READ_ONLY_MASK; +} + +#if RUBY_DEBUG +bool rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id); +#endif + static inline void RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id) { - set_shape_id_in_flags(obj, shape_id); -} + RUBY_ASSERT(!RB_SPECIAL_CONST_P(obj)); + RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_fields)); +#if RBASIC_SHAPE_ID_FIELD + RBASIC(obj)->shape_id = (VALUE)shape_id; +#else + // Object shapes are occupying top bits + RBASIC(obj)->flags &= SHAPE_FLAG_MASK; + RBASIC(obj)->flags |= ((VALUE)(shape_id) << SHAPE_FLAG_SHIFT); #endif - -static inline shape_id_t -ROBJECT_SHAPE_ID(VALUE obj) -{ - RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT); - return get_shape_id_from_flags(obj); + RUBY_ASSERT(rb_shape_verify_consistency(obj, shape_id)); } +void rb_set_namespaced_class_shape_id(VALUE obj, shape_id_t shape_id); + static inline void -ROBJECT_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id) +RB_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id) { - RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT); - set_shape_id_in_flags(obj, shape_id); + switch (BUILTIN_TYPE(obj)) { + case T_CLASS: + case T_MODULE: + rb_set_namespaced_class_shape_id(obj, shape_id); + break; + default: + RBASIC_SET_SHAPE_ID(obj, shape_id); + break; + } } -static inline shape_id_t -RCLASS_SHAPE_ID(VALUE obj) +static inline rb_shape_t * +RSHAPE(shape_id_t shape_id) { - RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)); - return get_shape_id_from_flags(obj); -} + uint32_t offset = (shape_id & SHAPE_ID_OFFSET_MASK); + RUBY_ASSERT(offset != INVALID_SHAPE_ID); -static inline void -RCLASS_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id) -{ - RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)); - set_shape_id_in_flags(obj, shape_id); + return &rb_shape_tree.shape_list[offset]; } -#define RSHAPE rb_shape_lookup - int32_t rb_shape_id_offset(void); -RUBY_FUNC_EXPORTED rb_shape_t *rb_shape_lookup(shape_id_t shape_id); RUBY_FUNC_EXPORTED shape_id_t rb_obj_shape_id(VALUE obj); shape_id_t rb_shape_get_next_iv_shape(shape_id_t shape_id, ID id); -bool rb_shape_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value); +bool rb_shape_get_iv_index(shape_id_t shape_id, ID id, attr_index_t *value); bool rb_shape_get_iv_index_with_hint(shape_id_t shape_id, ID id, attr_index_t *value, shape_id_t *shape_id_hint); -RUBY_FUNC_EXPORTED bool rb_shape_obj_too_complex_p(VALUE obj); -bool rb_shape_too_complex_p(rb_shape_t *shape); -bool rb_shape_id_too_complex_p(shape_id_t shape_id); +bool rb_shape_find_ivar(shape_id_t shape_id, ID id, shape_id_t *ivar_shape); + +typedef int rb_shape_foreach_transition_callback(shape_id_t shape_id, void *data); +bool rb_shape_foreach_field(shape_id_t shape_id, rb_shape_foreach_transition_callback func, void *data); -void rb_shape_set_shape(VALUE obj, rb_shape_t *shape); shape_id_t rb_shape_transition_frozen(VALUE obj); shape_id_t rb_shape_transition_complex(VALUE obj); -bool rb_shape_transition_remove_ivar(VALUE obj, ID id, VALUE *removed); +shape_id_t rb_shape_transition_remove_ivar(VALUE obj, ID id, shape_id_t *removed_shape_id); shape_id_t rb_shape_transition_add_ivar(VALUE obj, ID id); shape_id_t rb_shape_transition_add_ivar_no_warnings(VALUE obj, ID id); shape_id_t rb_shape_transition_object_id(VALUE obj); +shape_id_t rb_shape_transition_heap(VALUE obj, size_t heap_index); +shape_id_t rb_shape_object_id(shape_id_t original_shape_id); -bool rb_shape_has_object_id(rb_shape_t *shape); void rb_shape_free_all(void); -rb_shape_t *rb_shape_rebuild_shape(rb_shape_t *initial_shape, rb_shape_t *dest_shape); +shape_id_t rb_shape_rebuild(shape_id_t initial_shape_id, shape_id_t dest_shape_id); +void rb_shape_copy_fields(VALUE dest, VALUE *dest_buf, shape_id_t dest_shape_id, VALUE *src_buf, shape_id_t src_shape_id); +void rb_shape_copy_complex_ivars(VALUE dest, VALUE obj, shape_id_t src_shape_id, st_table *fields_table); -static inline rb_shape_t * -rb_obj_shape(VALUE obj) +static inline bool +rb_shape_too_complex_p(shape_id_t shape_id) { - return RSHAPE(rb_obj_shape_id(obj)); + return shape_id & SHAPE_ID_FL_TOO_COMPLEX; } static inline bool -rb_shape_canonical_p(rb_shape_t *shape) +rb_shape_obj_too_complex_p(VALUE obj) { - return !shape->flags; + return !RB_SPECIAL_CONST_P(obj) && rb_shape_too_complex_p(RBASIC_SHAPE_ID(obj)); +} + +static inline bool +rb_shape_has_object_id(shape_id_t shape_id) +{ + return shape_id & SHAPE_ID_FL_HAS_OBJECT_ID; +} + +static inline bool +rb_shape_canonical_p(shape_id_t shape_id) +{ + return !(shape_id & SHAPE_ID_FL_NON_CANONICAL_MASK); +} + +static inline uint8_t +rb_shape_heap_index(shape_id_t shape_id) +{ + return (uint8_t)((shape_id & SHAPE_ID_HEAP_INDEX_MASK) >> SHAPE_ID_HEAP_INDEX_OFFSET); } static inline shape_id_t rb_shape_root(size_t heap_id) { - return (shape_id_t)(heap_id + FIRST_T_OBJECT_SHAPE_ID); + shape_id_t heap_index = (shape_id_t)(heap_id + 1); + shape_id_t heap_flags = heap_index << SHAPE_ID_HEAP_INDEX_OFFSET; + + RUBY_ASSERT((heap_flags & SHAPE_ID_HEAP_INDEX_MASK) == heap_flags); + RUBY_ASSERT(rb_shape_heap_index(heap_flags) == heap_index); + + return ROOT_SHAPE_ID | heap_flags; +} + +static inline shape_id_t +RSHAPE_PARENT(shape_id_t shape_id) +{ + return RSHAPE(shape_id)->parent_id; +} + +static inline enum shape_type +RSHAPE_TYPE(shape_id_t shape_id) +{ + return RSHAPE(shape_id)->type; +} + +static inline bool +RSHAPE_TYPE_P(shape_id_t shape_id, enum shape_type type) +{ + return RSHAPE_TYPE(shape_id) == type; +} + +static inline attr_index_t +RSHAPE_EMBEDDED_CAPACITY(shape_id_t shape_id) +{ + uint8_t heap_index = rb_shape_heap_index(shape_id); + if (heap_index) { + return rb_shape_tree.capacities[heap_index - 1]; + } + return 0; +} + +static inline attr_index_t +RSHAPE_CAPACITY(shape_id_t shape_id) +{ + attr_index_t embedded_capacity = RSHAPE_EMBEDDED_CAPACITY(shape_id); + + if (embedded_capacity > RSHAPE(shape_id)->capacity) { + return embedded_capacity; + } + else { + return RSHAPE(shape_id)->capacity; + } +} + +static inline attr_index_t +RSHAPE_LEN(shape_id_t shape_id) +{ + return RSHAPE(shape_id)->next_field_index; +} + +static inline attr_index_t +RSHAPE_INDEX(shape_id_t shape_id) +{ + return RSHAPE_LEN(shape_id) - 1; +} + +static inline ID +RSHAPE_EDGE_NAME(shape_id_t shape_id) +{ + return RSHAPE(shape_id)->edge_name; } static inline uint32_t @@ -200,7 +336,7 @@ ROBJECT_FIELDS_CAPACITY(VALUE obj) // Asking for capacity doesn't make sense when the object is using // a hash table for storing instance variables RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); - return RSHAPE(ROBJECT_SHAPE_ID(obj))->capacity; + return RSHAPE_CAPACITY(RBASIC_SHAPE_ID(obj)); } static inline st_table * @@ -219,8 +355,6 @@ ROBJECT_SET_FIELDS_HASH(VALUE obj, const st_table *tbl) ROBJECT(obj)->as.heap.fields = (VALUE *)tbl; } -size_t rb_id_table_size(const struct rb_id_table *tbl); - static inline uint32_t ROBJECT_FIELDS_COUNT(VALUE obj) { @@ -230,7 +364,7 @@ ROBJECT_FIELDS_COUNT(VALUE obj) else { RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT); RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); - return RSHAPE(ROBJECT_SHAPE_ID(obj))->next_field_index; + return RSHAPE(RBASIC_SHAPE_ID(obj))->next_field_index; } } @@ -240,24 +374,61 @@ RBASIC_FIELDS_COUNT(VALUE obj) return RSHAPE(rb_obj_shape_id(obj))->next_field_index; } -shape_id_t rb_shape_traverse_from_new_root(shape_id_t initial_shape_id, shape_id_t orig_shape_id); - -bool rb_shape_set_shape_id(VALUE obj, shape_id_t shape_id); +bool rb_obj_set_shape_id(VALUE obj, shape_id_t shape_id); static inline bool rb_shape_obj_has_id(VALUE obj) { - return rb_shape_has_object_id(rb_obj_shape(obj)); + return rb_shape_has_object_id(RBASIC_SHAPE_ID(obj)); +} + +static inline bool +rb_shape_has_ivars(shape_id_t shape_id) +{ + return shape_id & SHAPE_ID_HAS_IVAR_MASK; +} + +static inline bool +rb_shape_obj_has_ivars(VALUE obj) +{ + return rb_shape_has_ivars(RBASIC_SHAPE_ID(obj)); +} + +static inline bool +rb_shape_has_fields(shape_id_t shape_id) +{ + return shape_id & (SHAPE_ID_OFFSET_MASK | SHAPE_ID_FL_TOO_COMPLEX); +} + +static inline bool +rb_shape_obj_has_fields(VALUE obj) +{ + return rb_shape_has_fields(RBASIC_SHAPE_ID(obj)); +} + +static inline bool +rb_obj_exivar_p(VALUE obj) +{ + switch (TYPE(obj)) { + case T_NONE: + case T_OBJECT: + case T_CLASS: + case T_MODULE: + case T_IMEMO: + return false; + default: + break; + } + return rb_shape_obj_has_fields(obj); } // For ext/objspace RUBY_SYMBOL_EXPORT_BEGIN -typedef void each_shape_callback(rb_shape_t *shape, void *data); -void rb_shape_each_shape(each_shape_callback callback, void *data); +typedef void each_shape_callback(shape_id_t shape_id, void *data); +void rb_shape_each_shape_id(each_shape_callback callback, void *data); size_t rb_shape_memsize(shape_id_t shape); size_t rb_shape_edges_count(shape_id_t shape_id); size_t rb_shape_depth(shape_id_t shape_id); -shape_id_t rb_shape_id(rb_shape_t *shape); RUBY_SYMBOL_EXPORT_END #endif diff --git a/signal.c b/signal.c index 1cb81d8f82..9edac5a789 100644 --- a/signal.c +++ b/signal.c @@ -403,7 +403,6 @@ interrupt_init(int argc, VALUE *argv, VALUE self) return rb_call_super(2, args); } -void rb_malloc_info_show_results(void); /* gc.c */ #if defined(USE_SIGALTSTACK) || defined(_WIN32) static void reset_sigmask(int sig); #endif @@ -414,7 +413,6 @@ ruby_default_signal(int sig) #if USE_DEBUG_COUNTER rb_debug_counter_show_results("killed by signal."); #endif - rb_malloc_info_show_results(); signal(sig, SIG_DFL); #if defined(USE_SIGALTSTACK) || defined(_WIN32) @@ -666,6 +664,10 @@ ruby_nativethread_signal(int signum, sighandler_t handler) #endif #endif +#if !defined(POSIX_SIGNAL) && !defined(SIG_GET) +static rb_nativethread_lock_t sig_check_lock; +#endif + static int signal_ignored(int sig) { @@ -675,10 +677,16 @@ signal_ignored(int sig) (void)VALGRIND_MAKE_MEM_DEFINED(&old, sizeof(old)); if (sigaction(sig, NULL, &old) < 0) return FALSE; func = old.sa_handler; +#elif defined SIG_GET + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/signal-action-constants + // SIG_GET: Returns the current value of the signal. + func = signal(sig, SIG_GET); #else - // TODO: this is not a thread-safe way to do it. Needs lock. - sighandler_t old = signal(sig, SIG_DFL); + sighandler_t old; + rb_native_mutex_lock(&sig_check_lock); + old = signal(sig, SIG_DFL); signal(sig, old); + rb_native_mutex_unlock(&sig_check_lock); func = old; #endif if (func == SIG_IGN) return 1; @@ -710,7 +718,7 @@ sighandler(int sig) int rb_signal_buff_size(void) { - return signal_buff.size; + return RUBY_ATOMIC_LOAD(signal_buff.size); } static void @@ -738,7 +746,7 @@ rb_get_next_signal(void) { int i, sig = 0; - if (signal_buff.size != 0) { + if (rb_signal_buff_size() != 0) { for (i=1; i 0) { ATOMIC_DEC(signal_buff.cnt[i]); @@ -762,7 +770,6 @@ static const char *received_signal; #endif #if defined(USE_SIGALTSTACK) || defined(_WIN32) -NORETURN(void rb_ec_stack_overflow(rb_execution_context_t *ec, int crit)); # if defined __HAIKU__ # define USE_UCONTEXT_REG 1 # elif !(defined(HAVE_UCONTEXT_H) && (defined __i386__ || defined __x86_64__ || defined __amd64__)) @@ -848,18 +855,21 @@ check_stack_overflow(int sig, const uintptr_t addr, const ucontext_t *ctx) if (sp_page == fault_page || sp_page == fault_page + 1 || (sp_page <= fault_page && fault_page <= bp_page)) { rb_execution_context_t *ec = GET_EC(); - int crit = FALSE; + ruby_stack_overflow_critical_level crit = rb_stack_overflow_signal; int uplevel = roomof(pagesize, sizeof(*ec->tag)) / 2; /* XXX: heuristic */ while ((uintptr_t)ec->tag->buf / pagesize <= fault_page + 1) { /* drop the last tag if it is close to the fault, * otherwise it can cause stack overflow again at the same * place. */ - if ((crit = (!ec->tag->prev || !--uplevel)) != FALSE) break; + if (!ec->tag->prev || !--uplevel) { + crit = rb_stack_overflow_fatal; + break; + } rb_vm_tag_jmpbuf_deinit(&ec->tag->buf); ec->tag = ec->tag->prev; } reset_sigmask(sig); - rb_ec_stack_overflow(ec, crit + 1); + rb_ec_stack_overflow(ec, crit); } } # else @@ -1505,6 +1515,9 @@ Init_signal(void) rb_define_method(rb_eSignal, "signo", esignal_signo, 0); rb_alias(rb_eSignal, rb_intern_const("signm"), rb_intern_const("message")); rb_define_method(rb_eInterrupt, "initialize", interrupt_init, -1); +#if !defined(POSIX_SIGNAL) && !defined(SIG_GET) + rb_native_mutex_initialize(&sig_check_lock); +#endif // It should be ready to call rb_signal_exec() VM_ASSERT(GET_THREAD()->pending_interrupt_queue); @@ -1557,3 +1570,11 @@ Init_signal(void) rb_enable_interrupt(); } + +void +rb_signal_atfork(void) +{ +#if defined(HAVE_WORKING_FORK) && !defined(POSIX_SIGNAL) && !defined(SIG_GET) + rb_native_mutex_initialize(&sig_check_lock); +#endif +} diff --git a/spec/bin/bundle b/spec/bin/bundle new file mode 100755 index 0000000000..8f8b535295 --- /dev/null +++ b/spec/bin/bundle @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative "../bundler/support/activate" + +load File.expand_path("bundle", Spec::Path.exedir) diff --git a/spec/bin/rspec b/spec/bin/rspec new file mode 100755 index 0000000000..1f61e3c64c --- /dev/null +++ b/spec/bin/rspec @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative "../bundler/support/rubygems_ext" + +Spec::Rubygems.gem_load("rspec-core", "rspec") diff --git a/spec/bundled_gems_spec.rb b/spec/bundled_gems_spec.rb index 32540e7ffd..c362881850 100644 --- a/spec/bundled_gems_spec.rb +++ b/spec/bundled_gems_spec.rb @@ -24,6 +24,7 @@ RSpec.configure do |config| require_relative "bundler/support/rubygems_ext" Spec::Rubygems.test_setup Spec::Helpers.install_dev_bundler + FileUtils.mkdir_p Spec::Path.gem_path end config.around(:each) do |example| diff --git a/spec/bundler/bundler/build_metadata_spec.rb b/spec/bundler/bundler/build_metadata_spec.rb index afa2d1716f..2e69821f68 100644 --- a/spec/bundler/bundler/build_metadata_spec.rb +++ b/spec/bundler/bundler/build_metadata_spec.rb @@ -6,18 +6,20 @@ require "bundler/build_metadata" RSpec.describe Bundler::BuildMetadata do before do allow(Time).to receive(:now).and_return(Time.at(0)) - Bundler::BuildMetadata.instance_variable_set(:@built_at, nil) + Bundler::BuildMetadata.instance_variable_set(:@timestamp, nil) end - describe "#built_at" do - it "returns %Y-%m-%d formatted time" do - expect(Bundler::BuildMetadata.built_at).to eq "1970-01-01" + describe "#timestamp" do + it "returns %Y-%m-%d formatted current time if built_at not set" do + Bundler::BuildMetadata.instance_variable_set(:@built_at, nil) + expect(Bundler::BuildMetadata.timestamp).to eq "1970-01-01" end - end - describe "#release?" do - it "returns false as default" do - expect(Bundler::BuildMetadata.release?).to be_falsey + it "returns %Y-%m-%d formatted current time if built_at not set" do + Bundler::BuildMetadata.instance_variable_set(:@built_at, "2025-01-01") + expect(Bundler::BuildMetadata.timestamp).to eq "2025-01-01" + ensure + Bundler::BuildMetadata.instance_variable_set(:@built_at, nil) end end @@ -40,10 +42,9 @@ RSpec.describe Bundler::BuildMetadata do describe "#to_h" do subject { Bundler::BuildMetadata.to_h } - it "returns a hash includes Built At, Git SHA and Released Version" do - expect(subject["Built At"]).to eq "1970-01-01" + it "returns a hash includes Timestamp, and Git SHA" do + expect(subject["Timestamp"]).to eq "1970-01-01" expect(subject["Git SHA"]).to be_instance_of(String) - expect(subject["Released Version"]).to be_falsey end end end diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index b2cc1ccfef..41cd8c636d 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -87,7 +87,7 @@ RSpec.describe "bundle executable" do end context "with no arguments" do - it "prints a concise help message", bundler: "3" do + it "prints a concise help message", bundler: "4" do bundle "" expect(err).to be_empty expect(out).to include("Bundler version #{Bundler::VERSION}"). @@ -112,20 +112,34 @@ RSpec.describe "bundle executable" do end context "with --verbose" do - it "prints the running command" do + before do gemfile "source 'https://gem.repo1'" + end + + it "prints the running command" do bundle "info bundler", verbose: true expect(out).to start_with("Running `bundle info bundler --verbose` with bundler #{Bundler::VERSION}") - end - it "doesn't print defaults" do - install_gemfile "source 'https://gem.repo1'", verbose: true + bundle "install", verbose: true expect(out).to start_with("Running `bundle install --verbose` with bundler #{Bundler::VERSION}") end - it "doesn't print defaults" do - install_gemfile "source 'https://gem.repo1'", verbose: true - expect(out).to start_with("Running `bundle install --verbose` with bundler #{Bundler::VERSION}") + it "prints the simulated version too when setting is enabled" do + bundle "config simulate_version 4", verbose: true + bundle "info bundler", verbose: true + expect(out).to start_with("Running `bundle info bundler --verbose` with bundler #{Bundler::VERSION} (simulating Bundler 4)") + end + end + + context "with verbose configuration" do + before do + bundle "config verbose true" + end + + it "prints the running command" do + gemfile "source 'https://gem.repo1'" + bundle "info bundler" + expect(out).to start_with("Running `bundle info bundler` with bundler #{Bundler::VERSION}") end end @@ -179,7 +193,7 @@ RSpec.describe "bundle executable" do shared_examples_for "no warning" do it "prints no warning" do bundle "fail", env: { "BUNDLER_VERSION" => bundler_version }, raise_on_error: false - expect(last_command.stdboth).to eq("Could not find command \"fail\".") + expect(stdboth).to eq("Could not find command \"fail\".") end end @@ -228,10 +242,10 @@ To update to the most recent version, run `bundle update --bundler` context "running a parseable command" do it "prints no warning" do bundle "config get --parseable foo", env: { "BUNDLER_VERSION" => bundler_version } - expect(last_command.stdboth).to eq "" + expect(stdboth).to eq "" bundle "platform --ruby", env: { "BUNDLER_VERSION" => bundler_version }, raise_on_error: false - expect(last_command.stdboth).to eq "Could not locate Gemfile" + expect(stdboth).to eq "Could not locate Gemfile" end end @@ -250,13 +264,12 @@ To update to the most recent version, run `bundle update --bundler` end RSpec.describe "bundler executable" do - it "shows the bundler version just as the `bundle` executable does", bundler: "< 3" do + it "shows the bundler version just as the `bundle` executable does" do bundler "--version" expect(out).to eq("Bundler version #{Bundler::VERSION}") - end - it "shows the bundler version just as the `bundle` executable does", bundler: "3" do + bundle "config simulate_version 4" bundler "--version" - expect(out).to eq(Bundler::VERSION) + expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 4)") end end diff --git a/spec/bundler/bundler/current_ruby_spec.rb b/spec/bundler/bundler/current_ruby_spec.rb index 61206d258b..8764c4971f 100644 --- a/spec/bundler/bundler/current_ruby_spec.rb +++ b/spec/bundler/bundler/current_ruby_spec.rb @@ -139,18 +139,18 @@ RSpec.describe Bundler::CurrentRuby do end describe "Deprecated platform" do - it "Outputs a deprecation warning when calling maglev?", bundler: "< 3" do + it "Outputs a deprecation warning when calling maglev?" do expect(Bundler.ui).to receive(:warn).with(/`CurrentRuby#maglev\?` is deprecated with no replacement./) Bundler.current_ruby.maglev? end - it "Outputs a deprecation warning when calling maglev_31?", bundler: "< 3" do + it "Outputs a deprecation warning when calling maglev_31?" do expect(Bundler.ui).to receive(:warn).with(/`CurrentRuby#maglev_31\?` is deprecated with no replacement./) Bundler.current_ruby.maglev_31? end - pending "is removed and shows a helpful error message about it", bundler: "3" + pending "is removed and shows a helpful error message about it", bundler: "4" end end diff --git a/spec/bundler/bundler/dsl_spec.rb b/spec/bundler/bundler/dsl_spec.rb index 9dca4ade05..ac28aea4d7 100644 --- a/spec/bundler/bundler/dsl_spec.rb +++ b/spec/bundler/bundler/dsl_spec.rb @@ -103,7 +103,7 @@ RSpec.describe Bundler::Dsl do ) end - context "default hosts", bundler: "< 3" do + context "default hosts" do it "converts :github to URI using https" do subject.gem("sparks", github: "indirect/sparks") github_uri = "https://github.com/indirect/sparks.git" diff --git a/spec/bundler/bundler/fetcher/downloader_spec.rb b/spec/bundler/bundler/fetcher/downloader_spec.rb index 6164025ac6..36b9b94990 100644 --- a/spec/bundler/bundler/fetcher/downloader_spec.rb +++ b/spec/bundler/bundler/fetcher/downloader_spec.rb @@ -88,7 +88,7 @@ RSpec.describe Bundler::Fetcher::Downloader do /`bundle config set --global www\.uri-to-fetch\.com username:password`.*`BUNDLE_WWW__URI___TO___FETCH__COM`/m) end - context "when the there are credentials provided in the request" do + context "when there are credentials provided in the request" do let(:uri) { Gem::URI("http://user:password@www.uri-to-fetch.com") } it "should raise a Bundler::Fetcher::BadAuthenticationError that doesn't contain the password" do @@ -116,7 +116,7 @@ RSpec.describe Bundler::Fetcher::Downloader do to raise_error(Bundler::Fetcher::FallbackError, "Gem::Net::HTTPNotFound: http://www.uri-to-fetch.com/api/v2/endpoint") end - context "when the there are credentials provided in the request" do + context "when there are credentials provided in the request" do let(:uri) { Gem::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } it "should raise a Bundler::Fetcher::FallbackError that doesn't contain the password" do @@ -201,22 +201,43 @@ RSpec.describe Bundler::Fetcher::Downloader do end end - context "when the request response causes an error included in HTTP_ERRORS" do - let(:message) { nil } - let(:error) { RuntimeError.new(message) } + context "when the request response causes an HTTP error" do + let(:message) { "error about network" } + let(:error) { error_class.new(message) } before do - stub_const("Bundler::Fetcher::HTTP_ERRORS", [RuntimeError]) allow(connection).to receive(:request).with(uri, net_http_get) { raise error } end - it "should trace log the error" do - allow(Bundler).to receive_message_chain(:ui, :debug) - expect(Bundler).to receive_message_chain(:ui, :trace).with(error) - expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError) + context "that it's retryable" do + let(:error_class) { Gem::Timeout::Error } + + it "should trace log the error" do + allow(Bundler).to receive_message_chain(:ui, :debug) + expect(Bundler).to receive_message_chain(:ui, :trace).with(error) + expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError) + end + + it "should raise a Bundler::HTTPError" do + expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, + "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (error about network)") + end + + context "when there are credentials provided in the request" do + let(:uri) { Gem::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } + before do + allow(net_http_get).to receive(:basic_auth).with("username", "password") + end + + it "should raise a Bundler::HTTPError that doesn't contain the password" do + expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, + "Network error while fetching http://username@www.uri-to-fetch.com/api/v2/endpoint (error about network)") + end + end end - context "when error message is about the host being down" do + context "when error is about the host being down" do + let(:error_class) { Gem::Net::HTTP::Persistent::Error } let(:message) { "host down: http://www.uri-to-fetch.com" } it "should raise a Bundler::Fetcher::NetworkDownError" do @@ -225,33 +246,23 @@ RSpec.describe Bundler::Fetcher::Downloader do end end - context "when error message is not about host down" do - let(:message) { "other error about network" } + context "when error is about connection refused" do + let(:error_class) { Gem::Net::HTTP::Persistent::Error } + let(:message) { "connection refused down: http://www.uri-to-fetch.com" } - it "should raise a Bundler::HTTPError" do - expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, - "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (other error about network)") - end - - context "when the there are credentials provided in the request" do - let(:uri) { Gem::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } - before do - allow(net_http_get).to receive(:basic_auth).with("username", "password") - end - - it "should raise a Bundler::HTTPError that doesn't contain the password" do - expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, - "Network error while fetching http://username@www.uri-to-fetch.com/api/v2/endpoint (other error about network)") - end + it "should raise a Bundler::Fetcher::NetworkDownError" do + expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError, + /Could not reach host www.uri-to-fetch.com/) end end - context "when error message is about no route to host" do + context "when error is about no route to host" do + let(:error_class) { SocketError } let(:message) { "Failed to open TCP connection to www.uri-to-fetch.com:443 " } - it "should raise a Bundler::Fetcher::HTTPError" do - expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, - "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (#{message})") + it "should raise a Bundler::Fetcher::NetworkDownError" do + expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError, + /Could not reach host www.uri-to-fetch.com/) end end end diff --git a/spec/bundler/bundler/friendly_errors_spec.rb b/spec/bundler/bundler/friendly_errors_spec.rb index 255019f40a..d6a9d4813d 100644 --- a/spec/bundler/bundler/friendly_errors_spec.rb +++ b/spec/bundler/bundler/friendly_errors_spec.rb @@ -2,7 +2,8 @@ require "bundler" require "bundler/friendly_errors" -require "cgi" +require "cgi/escape" +require "cgi/util" unless defined?(CGI::EscapeExt) RSpec.describe Bundler, "friendly errors" do context "with invalid YAML in .gemrc" do @@ -130,17 +131,13 @@ RSpec.describe Bundler, "friendly errors" do # Does nothing end - context "Java::JavaLang::OutOfMemoryError" do - module Java - module JavaLang - class OutOfMemoryError < StandardError; end - end - end - + context "Java::JavaLang::OutOfMemoryError", :jruby_only do it "Bundler.ui receive error" do - error = Java::JavaLang::OutOfMemoryError.new - expect(Bundler.ui).to receive(:error).with(/JVM has run out of memory/) - Bundler::FriendlyErrors.log_error(error) + install_gemfile <<-G, raise_on_error: false, env: { "JRUBY_OPTS" => "-J-Xmx32M" }, artifice: nil + source "https://gem.repo1" + G + + expect(err).to include("JVM has run out of memory") end end diff --git a/spec/bundler/bundler/installer/gem_installer_spec.rb b/spec/bundler/bundler/installer/gem_installer_spec.rb index 6583bd8181..dbd4e1d2c8 100644 --- a/spec/bundler/bundler/installer/gem_installer_spec.rb +++ b/spec/bundler/bundler/installer/gem_installer_spec.rb @@ -23,9 +23,7 @@ RSpec.describe Bundler::GemInstaller do context "spec_settings is build option" do it "invokes install method with build_args" do - allow(Bundler.settings).to receive(:[]).with(:bin) - allow(Bundler.settings).to receive(:[]).with(:inline) - allow(Bundler.settings).to receive(:[]).with(:forget_cli_options) + allow(Bundler.settings).to receive(:[]) allow(Bundler.settings).to receive(:[]).with("build.dummy").and_return("--with-dummy-config=dummy") expect(spec_source).to receive(:install).with( spec, @@ -37,9 +35,7 @@ RSpec.describe Bundler::GemInstaller do context "spec_settings is build option with spaces" do it "invokes install method with build_args" do - allow(Bundler.settings).to receive(:[]).with(:bin) - allow(Bundler.settings).to receive(:[]).with(:inline) - allow(Bundler.settings).to receive(:[]).with(:forget_cli_options) + allow(Bundler.settings).to receive(:[]) allow(Bundler.settings).to receive(:[]).with("build.dummy").and_return("--with-dummy-config=dummy --with-another-dummy-config") expect(spec_source).to receive(:install).with( spec, diff --git a/spec/bundler/bundler/lockfile_parser_spec.rb b/spec/bundler/bundler/lockfile_parser_spec.rb index f38da2c993..54aa6a0bfe 100644 --- a/spec/bundler/bundler/lockfile_parser_spec.rb +++ b/spec/bundler/bundler/lockfile_parser_spec.rb @@ -95,6 +95,134 @@ RSpec.describe Bundler::LockfileParser do end end + describe "X64_MINGW_LEGACY platform handling" do + before { allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app("gems.rb")) } + + describe "when X64_MINGW_LEGACY is present alone" do + let(:lockfile_with_legacy_platform) { <<~L } + GEM + remote: https://rubygems.org/ + specs: + rake (10.3.2) + + PLATFORMS + ruby + x64-mingw32 + + DEPENDENCIES + rake + + BUNDLED WITH + 3.6.9 + L + + context "when bundle is not frozen" do + before { allow(Bundler).to receive(:frozen_bundle?).and_return(false) } + subject { described_class.new(lockfile_with_legacy_platform) } + + it "replaces X64_MINGW_LEGACY with X64_MINGW" do + allow(Bundler::SharedHelpers).to receive(:major_deprecation) + expect(subject.platforms.map(&:to_s)).to contain_exactly("ruby", "x64-mingw-ucrt") + expect(subject.platforms.map(&:to_s)).not_to include("x64-mingw32") + end + + it "shows deprecation warning for replacement" do + expect(Bundler::SharedHelpers).to receive(:major_deprecation).with( + 2, + "Found x64-mingw32 in lockfile, which is deprecated. Using x64-mingw-ucrt, the replacement for x64-mingw32 in modern rubies, instead. Support for x64-mingw32 will be removed in Bundler 4.0.", + removed_message: "Found x64-mingw32 in lockfile, which is no longer supported as of Bundler 4.0." + ) + subject + end + end + + context "when bundle is frozen" do + before { allow(Bundler).to receive(:frozen_bundle?).and_return(true) } + subject { described_class.new(lockfile_with_legacy_platform) } + + it "preserves X64_MINGW_LEGACY platform without replacement" do + expect(subject.platforms.map(&:to_s)).to contain_exactly("ruby", "x64-mingw32") + end + + it "does not show any deprecation warnings" do + expect(Bundler::SharedHelpers).not_to receive(:major_deprecation) + subject + end + end + end + + describe "when both X64_MINGW_LEGACY and X64_MINGW are present" do + let(:lockfile_with_both_platforms) { <<~L } + GEM + remote: https://rubygems.org/ + specs: + rake (10.3.2) + + PLATFORMS + ruby + x64-mingw32 + x64-mingw-ucrt + + DEPENDENCIES + rake + + BUNDLED WITH + 3.6.9 + L + + context "when bundle is not frozen" do + before { allow(Bundler).to receive(:frozen_bundle?).and_return(false) } + subject { described_class.new(lockfile_with_both_platforms) } + + it "removes X64_MINGW_LEGACY and keeps X64_MINGW" do + allow(Bundler::SharedHelpers).to receive(:major_deprecation) + expect(subject.platforms.map(&:to_s)).to contain_exactly("ruby", "x64-mingw-ucrt") + expect(subject.platforms.map(&:to_s)).not_to include("x64-mingw32") + end + + it "shows deprecation warning for removing legacy platform" do + expect(Bundler::SharedHelpers).to receive(:major_deprecation).with( + 2, + "Found x64-mingw32 in lockfile, which is deprecated. Removing it. Support for x64-mingw32 will be removed in Bundler 4.0.", + removed_message: "Found x64-mingw32 in lockfile, which is no longer supported as of Bundler 4.0." + ) + subject + end + end + end + + describe "when no X64_MINGW_LEGACY platform is present" do + let(:lockfile_with_modern_platforms) { <<~L } + GEM + remote: https://rubygems.org/ + specs: + rake (10.3.2) + + PLATFORMS + ruby + x64-mingw-ucrt + + DEPENDENCIES + rake + + BUNDLED WITH + 3.6.9 + L + + before { allow(Bundler).to receive(:frozen_bundle?).and_return(false) } + subject { described_class.new(lockfile_with_modern_platforms) } + + it "preserves all modern platforms without changes" do + expect(subject.platforms.map(&:to_s)).to contain_exactly("ruby", "x64-mingw-ucrt") + end + + it "does not show any deprecation warnings" do + expect(Bundler::SharedHelpers).not_to receive(:major_deprecation) + subject + end + end + end + describe "#initialize" do before { allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app("gems.rb")) } subject { described_class.new(lockfile_contents) } diff --git a/spec/bundler/bundler/shared_helpers_spec.rb b/spec/bundler/bundler/shared_helpers_spec.rb index d37b63bbec..3568580701 100644 --- a/spec/bundler/bundler/shared_helpers_spec.rb +++ b/spec/bundler/bundler/shared_helpers_spec.rb @@ -423,7 +423,7 @@ RSpec.describe Bundler::SharedHelpers do it "sets BUNDLE_BIN_PATH to the bundle executable file" do subject.set_bundle_environment bin_path = ENV["BUNDLE_BIN_PATH"] - expect(bin_path).to eq(bindir.join("bundle").to_s) + expect(bin_path).to eq(exedir.join("bundle").to_s) expect(File.exist?(bin_path)).to be true end end @@ -517,7 +517,7 @@ RSpec.describe Bundler::SharedHelpers do end describe "#major_deprecation" do - before { allow(Bundler).to receive(:bundler_major_version).and_return(37) } + before { allow(Bundler).to receive(:feature_flag).and_return(Bundler::FeatureFlag.new(37)) } before { allow(Bundler.ui).to receive(:warn) } it "prints and raises nothing below the deprecated major version" do diff --git a/spec/bundler/bundler/source/git/git_proxy_spec.rb b/spec/bundler/bundler/source/git/git_proxy_spec.rb index c350904994..492eee6444 100644 --- a/spec/bundler/bundler/source/git/git_proxy_spec.rb +++ b/spec/bundler/bundler/source/git/git_proxy_spec.rb @@ -252,4 +252,86 @@ RSpec.describe Bundler::Source::Git::GitProxy do end end end + + describe "#checkout" do + context "when the repository isn't cloned" do + before do + allow(path).to receive(:exist?).and_return(false) + end + + it "clones the repository" do + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", uri, path.to_s], nil).and_return(["", "", clone_result]) + subject.checkout + end + end + + context "when the repository is cloned" do + before do + allow(path).to receive(:exist?).and_return(true) + end + + context "with a locked revision" do + let(:revision) { Digest::SHA1.hexdigest("ruby") } + + context "when the revision exists locally" do + it "uses the cached revision" do + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:git).with("cat-file", "-e", revision, dir: path).and_return(true) + subject.checkout + end + end + + context "when the revision doesn't exist locally" do + it "fetches the specific revision" do + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:git).with("cat-file", "-e", revision, dir: path).and_raise(Bundler::GitError) + expect(git_proxy).to receive(:capture).with(["fetch", "--force", "--quiet", "--no-tags", "--depth", "1", "--", uri, "#{revision}:refs/#{revision}-sha"], path).and_return(["", "", clone_result]) + subject.checkout + end + end + end + + context "with no explicit ref" do + it "fetches the HEAD revision" do + parsed_revision = Digest::SHA1.hexdigest("ruby") + allow(git_proxy).to receive(:git_local).with("rev-parse", "--abbrev-ref", "HEAD", dir: path).and_return(parsed_revision) + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:capture).with(["fetch", "--force", "--quiet", "--no-tags", "--depth", "1", "--", uri, "refs/heads/#{parsed_revision}:refs/heads/#{parsed_revision}"], path).and_return(["", "", clone_result]) + subject.checkout + end + end + + context "with a commit ref" do + let(:ref) { Digest::SHA1.hexdigest("ruby") } + + context "when the revision exists locally" do + it "uses the cached revision" do + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:git).with("cat-file", "-e", ref, dir: path).and_return(true) + subject.checkout + end + end + + context "when the revision doesn't exist locally" do + it "fetches the specific revision" do + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:git).with("cat-file", "-e", ref, dir: path).and_raise(Bundler::GitError) + expect(git_proxy).to receive(:capture).with(["fetch", "--force", "--quiet", "--no-tags", "--depth", "1", "--", uri, "#{ref}:refs/#{ref}-sha"], path).and_return(["", "", clone_result]) + subject.checkout + end + end + end + + context "with a non-commit ref" do + let(:ref) { "HEAD" } + + it "fetches all revisions" do + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:capture).with(["fetch", "--force", "--quiet", "--no-tags", "--", uri, "refs/*:refs/*"], path).and_return(["", "", clone_result]) + subject.checkout + end + end + end + end end diff --git a/spec/bundler/bundler/specifications/foo.gemspec b/spec/bundler/bundler/specifications/foo.gemspec index 81b77d0c86..19b7724e81 100644 --- a/spec/bundler/bundler/specifications/foo.gemspec +++ b/spec/bundler/bundler/specifications/foo.gemspec @@ -8,6 +8,6 @@ Gem::Specification.new do |s| s.version = "1.0.0" s.loaded_from = __FILE__ s.extensions = "ext/foo" - s.required_ruby_version = ">= 3.1.0" + s.required_ruby_version = ">= 3.2.0" end # rubocop:enable Style/FrozenStringLiteralComment diff --git a/spec/bundler/cache/git_spec.rb b/spec/bundler/cache/git_spec.rb index b337882a6e..e9dee60a98 100644 --- a/spec/bundler/cache/git_spec.rb +++ b/spec/bundler/cache/git_spec.rb @@ -452,7 +452,7 @@ RSpec.describe "bundle cache with git" do bundle :cache, "all-platforms" => true, :install => false # it did _NOT_ actually install the gem - neither in $GEM_HOME (bundler 2 mode), - # nor in .bundle (bundler 3 mode) + # nor in .bundle (bundler 4 mode) expect(Pathname.new(File.join(default_bundle_path, "gems/foo-1.0-#{ref}"))).to_not exist # it _did_ cache the gem in vendor/ expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist diff --git a/spec/bundler/cache/path_spec.rb b/spec/bundler/cache/path_spec.rb index 966cb6f531..526b7369ef 100644 --- a/spec/bundler/cache/path_spec.rb +++ b/spec/bundler/cache/path_spec.rb @@ -97,7 +97,7 @@ RSpec.describe "bundle cache with path" do expect(bundled_app("vendor/cache/foo-1.0")).not_to exist end - it "does not cache path gems by default", bundler: "< 3" do + it "does not cache path gems by default" do build_lib "foo" install_gemfile <<-G @@ -110,7 +110,7 @@ RSpec.describe "bundle cache with path" do expect(bundled_app("vendor/cache/foo-1.0")).not_to exist end - it "caches path gems by default", bundler: "3" do + it "caches path gems by default", bundler: "4" do build_lib "foo" install_gemfile <<-G diff --git a/spec/bundler/commands/add_spec.rb b/spec/bundler/commands/add_spec.rb index 2676b06c78..00aa6415e1 100644 --- a/spec/bundler/commands/add_spec.rb +++ b/spec/bundler/commands/add_spec.rb @@ -161,26 +161,47 @@ RSpec.describe "bundle add" do end describe "with --github" do + before do + build_git "rake", "13.0" + git("config --global url.file://#{lib_path("rake-13.0")}.insteadOf https://github.com/ruby/rake.git") + end + it "adds dependency with specified github source" do bundle "add rake --github=ruby/rake" expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake"}) end - end - describe "with --github and --branch" do it "adds dependency with specified github source and branch" do - bundle "add rake --github=ruby/rake --branch=master" + bundle "add rake --github=ruby/rake --branch=main" - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", branch: "master"}) + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", branch: "main"}) end - end - describe "with --github and --ref" do it "adds dependency with specified github source and ref" do - bundle "add rake --github=ruby/rake --ref=5c60da8" + ref = revision_for(lib_path("rake-13.0")) + bundle "add rake --github=ruby/rake --ref=#{ref}" - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", ref: "5c60da8"}) + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", ref: "#{ref}"}) + end + + it "adds dependency with specified github source and glob" do + bundle "add rake --github=ruby/rake --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", glob: "\.\/\*\.gemspec"}) + end + + it "adds dependency with specified github source, branch and glob" do + bundle "add rake --github=ruby/rake --branch=main --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", branch: "main", glob: "\.\/\*\.gemspec"}) + end + + it "adds dependency with specified github source, ref and glob" do + ref = revision_for(lib_path("rake-13.0")) + bundle "add rake --github=ruby/rake --ref=#{ref} --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", ref: "#{ref}", glob: "\.\/\*\.gemspec"}) end end @@ -215,30 +236,6 @@ RSpec.describe "bundle add" do end end - describe "with --github and --glob" do - it "adds dependency with specified github source" do - bundle "add rake --github=ruby/rake --glob='./*.gemspec'" - - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", glob: "\.\/\*\.gemspec"}) - end - end - - describe "with --github and --branch --and glob" do - it "adds dependency with specified github source and branch" do - bundle "add rake --github=ruby/rake --branch=master --glob='./*.gemspec'" - - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", branch: "master", glob: "\.\/\*\.gemspec"}) - end - end - - describe "with --github and --ref and --glob" do - it "adds dependency with specified github source and ref" do - bundle "add rake --github=ruby/rake --ref=5c60da8 --glob='./*.gemspec'" - - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", ref: "5c60da8", glob: "\.\/\*\.gemspec"}) - end - end - describe "with --skip-install" do it "adds gem to Gemfile but is not installed" do bundle "add foo --skip-install --version=2.0" diff --git a/spec/bundler/commands/binstubs_spec.rb b/spec/bundler/commands/binstubs_spec.rb index c66b9339ee..e0424dcba4 100644 --- a/spec/bundler/commands/binstubs_spec.rb +++ b/spec/bundler/commands/binstubs_spec.rb @@ -168,7 +168,7 @@ RSpec.describe "bundle binstubs " do expect(bundled_app("exec/myrackup")).to exist end - it "setting is saved for bundle install", bundler: "< 3" do + it "setting is saved for bundle install" do install_gemfile <<-G source "https://gem.repo1" gem "myrack" diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index 3f7a627296..4365b92b8c 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -158,7 +158,7 @@ RSpec.describe "bundle cache" do end end - context "with --path", bundler: "< 3" do + context "with --path" do it "sets root directory for gems" do gemfile <<-D source "https://gem.repo1" @@ -221,7 +221,7 @@ RSpec.describe "bundle cache" do expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end - it "puts the gems in vendor/cache even for legacy windows rubies, but prints a warning", bundler: "< 3" do + it "puts the gems in vendor/cache even for legacy windows rubies, but prints a warning" do gemfile <<-D source "https://gem.repo1" gem 'myrack', :platforms => [:ruby_20, :x64_mingw_20] @@ -232,7 +232,7 @@ RSpec.describe "bundle cache" do expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end - it "prints an error when using legacy windows rubies", bundler: "3" do + it "prints an error when using legacy windows rubies", bundler: "4" do gemfile <<-D source "https://gem.repo1" gem 'myrack', :platforms => [:ruby_20, :x64_mingw_20] diff --git a/spec/bundler/commands/check_spec.rb b/spec/bundler/commands/check_spec.rb index 9263e72720..fccaf76170 100644 --- a/spec/bundler/commands/check_spec.rb +++ b/spec/bundler/commands/check_spec.rb @@ -57,7 +57,7 @@ RSpec.describe "bundle check" do bundle :check, raise_on_error: false expect(err).to include("The following gems are missing") - expect(err).to include(" * rake (13.2.1)") + expect(err).to include(" * rake (#{rake_version})") expect(err).to include(" * actionpack (2.3.2)") expect(err).to include(" * activerecord (2.3.2)") expect(err).to include(" * actionmailer (2.3.2)") @@ -76,7 +76,7 @@ RSpec.describe "bundle check" do expect(exitstatus).to be > 0 expect(err).to include("The following gems are missing") expect(err).to include(" * rails (2.3.2)") - expect(err).to include(" * rake (13.2.1)") + expect(err).to include(" * rake (#{rake_version})") expect(err).to include(" * actionpack (2.3.2)") expect(err).to include(" * activerecord (2.3.2)") expect(err).to include(" * actionmailer (2.3.2)") @@ -123,7 +123,7 @@ RSpec.describe "bundle check" do expect(err).to include("Bundler can't satisfy your Gemfile's dependencies.") end - it "remembers --without option from install", bundler: "< 3" do + it "remembers --without option from install" do gemfile <<-G source "https://gem.repo1" group :foo do @@ -272,7 +272,7 @@ RSpec.describe "bundle check" do expect(last_command).to be_failure end - context "--path", bundler: "< 3" do + context "--path" do context "after installing gems in the proper directory" do before do gemfile <<-G diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb index 2559be0205..99a8f9c141 100644 --- a/spec/bundler/commands/clean_spec.rb +++ b/spec/bundler/commands/clean_spec.rb @@ -151,7 +151,7 @@ RSpec.describe "bundle clean" do bundle :clean digest = Digest(:SHA1).hexdigest(git_path.to_s) - cache_path = Bundler::VERSION.start_with?("2.") ? vendored_gems("cache/bundler/git/foo-1.0-#{digest}") : home(".bundle/cache/git/foo-1.0-#{digest}") + cache_path = Bundler.feature_flag.global_gem_cache? ? home(".bundle/cache/git/foo-1.0-#{digest}") : vendored_gems("cache/bundler/git/foo-1.0-#{digest}") expect(cache_path).to exist end @@ -383,7 +383,7 @@ RSpec.describe "bundle clean" do expect(out).to include("myrack (1.0.0)").and include("thin (1.0)") end - it "--clean should override the bundle setting on install", bundler: "< 3" do + it "--clean should override the bundle setting on install" do gemfile <<-G source "https://gem.repo1" @@ -405,7 +405,7 @@ RSpec.describe "bundle clean" do should_not_have_gems "thin-1.0" end - it "--clean should override the bundle setting on update", bundler: "< 3" do + it "--clean should override the bundle setting on update" do build_repo2 gemfile <<-G @@ -427,7 +427,7 @@ RSpec.describe "bundle clean" do should_not_have_gems "foo-1.0" end - it "automatically cleans when path has not been set", bundler: "3" do + it "automatically cleans when path has not been set", bundler: "4" do build_repo2 install_gemfile <<-G @@ -625,7 +625,7 @@ RSpec.describe "bundle clean" do expect(out).to eq("1.0") end - it "when using --force, it doesn't remove default gem binaries", :realworld do + it "when using --force, it doesn't remove default gem binaries" do default_irb_version = ruby "gem 'irb', '< 999999'; require 'irb'; puts IRB::VERSION", raise_on_error: false skip "irb isn't a default gem" if default_irb_version.empty? @@ -634,8 +634,6 @@ RSpec.describe "bundle clean" do s.executables = "irb" end - realworld_system_gems "tsort --version 0.1.0", "pathname --version 0.1.0", "set --version 1.0.1" - install_gemfile <<-G source "https://gem.repo2" G diff --git a/spec/bundler/commands/console_spec.rb b/spec/bundler/commands/console_spec.rb index dbfbec874f..ec44fe59f3 100644 --- a/spec/bundler/commands/console_spec.rb +++ b/spec/bundler/commands/console_spec.rb @@ -40,7 +40,7 @@ RSpec.describe "bundle console", readline: true do end end - context "when the library has an unrelated error" do + context "when the library requires a non-existent file" do before do build_lib "loadfuuu", "1.0.0" do |s| s.write "lib/loadfuuu.rb", "require_relative 'loadfuuu/bar'" @@ -65,6 +65,30 @@ RSpec.describe "bundle console", readline: true do end end + context "when the library references a non-existent constant" do + before do + build_lib "loadfuuu", "1.0.0" do |s| + s.write "lib/loadfuuu.rb", "Some::NonExistent::Constant" + end + + install_gemfile <<-G + source "https://gem.repo2" + gem "irb" + path "#{lib_path}" do + gem "loadfuuu", require: true + end + G + end + + it "does not show the bug report template" do + bundle("console", raise_on_error: false) do |input, _, _| + input.puts("exit") + end + + expect(err).not_to include("ERROR REPORT TEMPLATE") + end + end + context "when the library does not have any errors" do before do install_gemfile <<-G diff --git a/spec/bundler/commands/exec_spec.rb b/spec/bundler/commands/exec_spec.rb index 62421410ed..4e8a816e95 100644 --- a/spec/bundler/commands/exec_spec.rb +++ b/spec/bundler/commands/exec_spec.rb @@ -193,7 +193,10 @@ RSpec.describe "bundle exec" do end context "with default gems" do - let(:default_erb_version) { ruby "gem 'erb', '< 999999'; require 'erb/version'; puts Erb::VERSION", raise_on_error: false } + # TODO: Switch to ERB::VERSION once Ruby 3.4 support is dropped, so all + # supported rubies include an `erb` gem version where `ERB::VERSION` is + # public + let(:default_erb_version) { ruby "require 'erb/version'; puts ERB.const_get(:VERSION)" } context "when not specified in Gemfile" do before do @@ -203,7 +206,7 @@ RSpec.describe "bundle exec" do it "uses version provided by ruby" do bundle "exec erb --version" - expect(out).to include(default_erb_version) + expect(stdboth).to eq(default_erb_version) end end @@ -224,10 +227,9 @@ RSpec.describe "bundle exec" do end it "uses version specified" do - bundle "exec erb --version", artifice: nil + bundle "exec erb --version" - expect(out).to eq(specified_erb_version) - expect(err).to be_empty + expect(stdboth).to eq(specified_erb_version) end end @@ -249,13 +251,12 @@ RSpec.describe "bundle exec" do source "https://gem.repo2" gem "gem_depending_on_old_erb" G - - bundle "exec erb --version", artifice: nil end it "uses resolved version" do - expect(out).to eq(indirect_erb_version) - expect(err).to be_empty + bundle "exec erb --version" + + expect(stdboth).to eq(indirect_erb_version) end end end @@ -582,7 +583,7 @@ RSpec.describe "bundle exec" do G bundle "config set auto_install 1" - bundle "exec myrackup" + bundle "exec myrackup", artifice: "compact_index" expect(out).to include("Installing foo 1.0") end @@ -597,7 +598,7 @@ RSpec.describe "bundle exec" do G bundle "config set auto_install 1" - bundle "exec foo" + bundle "exec foo", artifice: "compact_index" expect(out).to include("Fetching myrack 0.9.1") expect(out).to include("Fetching #{lib_path("foo-1.0")}") expect(out.lines).to end_with("1.0") @@ -624,7 +625,7 @@ RSpec.describe "bundle exec" do gem "fastlane" G - bundle "exec fastlane" + bundle "exec fastlane", artifice: "compact_index" expect(out).to include("Installing optparse 999.999.999") expect(out).to include("2.192.0") end @@ -698,6 +699,27 @@ RSpec.describe "bundle exec" do end end + describe "running gem commands in presence of rubygems plugins" do + before do + build_repo4 do + build_gem "foo" do |s| + s.write "lib/rubygems_plugin.rb", "puts 'FAIL'" + end + end + + system_gems "foo-1.0", path: default_bundle_path, gem_repo: gem_repo4 + + install_gemfile <<-G + source "https://gem.repo4" + G + end + + it "does not load plugins outside of the bundle" do + bundle "exec #{gem_cmd} -v" + expect(out).not_to include("FAIL") + end + end + context "`load`ing a ruby file instead of `exec`ing" do let(:path) { bundled_app("ruby_executable") } let(:shebang) { "#!/usr/bin/env ruby" } @@ -1199,11 +1221,11 @@ RSpec.describe "bundle exec" do context "with a system gem that shadows a default gem" do let(:openssl_version) { "99.9.9" } - let(:expected) { ruby "gem 'openssl', '< 999999'; require 'openssl'; puts OpenSSL::VERSION", artifice: nil, raise_on_error: false } it "only leaves the default gem in the stdlib available" do + default_openssl_version = ruby "require 'openssl'; puts OpenSSL::VERSION" + skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - skip "openssl isn't a default gem" if expected.empty? install_gemfile "source \"https://gem.repo1\"" # must happen before installing the broken system gem @@ -1228,10 +1250,10 @@ RSpec.describe "bundle exec" do env = { "PATH" => path } aggregate_failures do - expect(bundle("exec #{file}", artifice: nil, env: env)).to eq(expected) - expect(bundle("exec bundle exec #{file}", artifice: nil, env: env)).to eq(expected) - expect(bundle("exec ruby #{file}", artifice: nil, env: env)).to eq(expected) - expect(run(file.read, artifice: nil, env: env)).to eq(expected) + expect(bundle("exec #{file}", env: env)).to eq(default_openssl_version) + expect(bundle("exec bundle exec #{file}", env: env)).to eq(default_openssl_version) + expect(bundle("exec ruby #{file}", env: env)).to eq(default_openssl_version) + expect(run(file.read, artifice: nil, env: env)).to eq(default_openssl_version) end skip "ruby_core has openssl and rubygems in the same folder, and this test needs rubygems require but default openssl not in a directly added entry in $LOAD_PATH" if ruby_core? diff --git a/spec/bundler/commands/info_spec.rb b/spec/bundler/commands/info_spec.rb index 478cf06405..f403db684f 100644 --- a/spec/bundler/commands/info_spec.rb +++ b/spec/bundler/commands/info_spec.rb @@ -86,7 +86,7 @@ RSpec.describe "bundle info" do expect(err).to include(default_bundle_path("gems", "rails-2.3.2").to_s) end - context "given a default gem shippped in ruby", :ruby_repo do + context "given a default gem shipped in ruby", :ruby_repo do it "prints information about the default gem" do bundle "info json" expect(out).to include("* json") diff --git a/spec/bundler/commands/inject_spec.rb b/spec/bundler/commands/inject_spec.rb index 4998b6e89d..c39c2ae35b 100644 --- a/spec/bundler/commands/inject_spec.rb +++ b/spec/bundler/commands/inject_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe "bundle inject", bundler: "< 3" do +RSpec.describe "bundle inject" do before :each do gemfile <<-G source "https://gem.repo1" @@ -79,11 +79,7 @@ Usage: "bundle inject GEM VERSION" context "when frozen" do before do bundle "install" - if Bundler.feature_flag.bundler_3_mode? - bundle "config set --local deployment true" - else - bundle "config set --local frozen true" - end + bundle "config set --local frozen true" end it "injects anyway" do diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 3c8df46248..22eb64ca81 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -29,7 +29,7 @@ RSpec.describe "bundle install with gem sources" do expect(bundled_app_lock).to exist end - it "does not create ./.bundle by default", bundler: "< 3" do + it "does not create ./.bundle by default" do gemfile <<-G source "https://gem.repo1" gem "myrack" @@ -260,7 +260,7 @@ RSpec.describe "bundle install with gem sources" do gem "myrack" G - expect(last_command.stdboth).to include(plugin_msg) + expect(stdboth).to include(plugin_msg) end describe "with a gem that installs multiple platforms" do @@ -334,14 +334,14 @@ RSpec.describe "bundle install with gem sources" do expect(the_bundle).to include_gems "myrack 1.0" end - it "allows running bundle install --system without deleting foo", bundler: "< 3" do + it "allows running bundle install --system without deleting foo" do bundle "install --path vendor" bundle "install --system" FileUtils.rm_r(bundled_app("vendor")) expect(the_bundle).to include_gems "myrack 1.0" end - it "allows running bundle install --system after deleting foo", bundler: "< 3" do + it "allows running bundle install --system after deleting foo" do bundle "install --path vendor" FileUtils.rm_r(bundled_app("vendor")) bundle "install --system" @@ -349,7 +349,7 @@ RSpec.describe "bundle install with gem sources" do end end - it "finds gems in multiple sources", bundler: "< 3" do + it "finds gems in multiple sources" do build_repo2 do build_gem "myrack", "1.2" do |s| s.executables = "myrackup" @@ -690,8 +690,6 @@ RSpec.describe "bundle install with gem sources" do end it "gracefully handles error when rubygems server is unavailable" do - skip "networking issue" if Gem.win_platform? - install_gemfile <<-G, artifice: nil, raise_on_error: false source "https://gem.repo1" source "http://0.0.0.0:9384" do @@ -699,7 +697,7 @@ RSpec.describe "bundle install with gem sources" do end G - expect(err).to include("Could not fetch specs from http://0.0.0.0:9384/") + expect(err).to eq("Could not reach host 0.0.0.0:9384. Check your network connection and try again.") expect(err).not_to include("file://") end @@ -722,7 +720,7 @@ RSpec.describe "bundle install with gem sources" do gem "ajp-rails", "0.0.0" G - expect(last_command.stdboth).not_to match(/Error Report/i) + expect(stdboth).not_to match(/Error Report/i) expect(err).to include("An error occurred while installing ajp-rails (0.0.0), and Bundler cannot continue."). and include("Bundler::APIResponseInvalidDependenciesError") end @@ -1107,7 +1105,7 @@ RSpec.describe "bundle install with gem sources" do FileUtils.chmod("-x", foo_path) begin - bundle "install --redownload", raise_on_error: false + bundle "install --force", raise_on_error: false ensure FileUtils.chmod("+x", foo_path) end @@ -1143,7 +1141,7 @@ RSpec.describe "bundle install with gem sources" do FileUtils.chmod("-w", gem_home) begin - bundle "install --redownload" + bundle "install --force" ensure FileUtils.chmod("+w", gem_home) end @@ -1177,7 +1175,7 @@ RSpec.describe "bundle install with gem sources" do FileUtils.chmod(0o777, gems_path) - bundle "install --redownload", raise_on_error: false + bundle "install --force", raise_on_error: false expect(err).to include("Bundler cannot reinstall foo-1.0.0 because there's a previous installation of it at #{gems_path}/foo-1.0.0 that is unsafe to remove") end @@ -1500,6 +1498,55 @@ RSpec.describe "bundle install with gem sources" do end end + context "when lockfile has incorrect dependencies" do + before do + build_repo2 + + gemfile <<-G + source "https://gem.repo2" + gem "myrack_middleware" + G + + system_gems "myrack_middleware-1.0", path: default_bundle_path + + # we want to raise when the 1.0 line should be followed by " myrack (= 0.9.1)" but isn't + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack_middleware (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "raises a clear error message when frozen" do + bundle "config set frozen true" + bundle "install", raise_on_error: false + + expect(exitstatus).to eq(41) + expect(err).to eq("Bundler found incorrect dependencies in the lockfile for myrack_middleware-1.0") + end + + it "updates the lockfile when not frozen" do + missing_dep = "myrack (0.9.1)" + expect(lockfile).not_to include(missing_dep) + + bundle "config set frozen false" + bundle :install + + expect(lockfile).to include(missing_dep) + expect(out).to include("now installed") + end + end + context "with --local flag" do before do system_gems "myrack-1.0.0", path: default_bundle_path diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index 4554248eee..c47cc97271 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -101,9 +101,8 @@ RSpec.describe "bundle lock" do let(:gemfile_with_rails_weakling_and_foo_from_repo4) do build_repo4 do - FileUtils.cp rake_path, "#{gem_repo4}/gems/" - build_gem "rake", "10.0.1" + build_gem "rake", rake_version %w[2.3.1 2.3.2].each do |version| build_gem "rails", version do |s| @@ -1256,11 +1255,6 @@ RSpec.describe "bundle lock" do s.required_ruby_version = "< #{next_ruby_minor}.dev" end - build_gem "raygun-apm", "1.0.78" do |s| - s.platform = "x64-mingw32" - s.required_ruby_version = "< #{next_ruby_minor}.dev" - end - build_gem "raygun-apm", "1.0.78" do |s| s.platform = "x64-mingw-ucrt" s.required_ruby_version = "< #{next_ruby_minor}.dev" @@ -1388,62 +1382,6 @@ RSpec.describe "bundle lock" do expect(err).to include(nice_error) end - it "does not crash on conflicting ruby requirements between platform versions in two different gems" do - build_repo4 do - build_gem "unf_ext", "0.0.8.2" - - build_gem "unf_ext", "0.0.8.2" do |s| - s.required_ruby_version = [">= 2.4", "< #{previous_ruby_minor}"] - s.platform = "x64-mingw32" - end - - build_gem "unf_ext", "0.0.8.2" do |s| - s.required_ruby_version = [">= #{previous_ruby_minor}", "< #{current_ruby_minor}"] - s.platform = "x64-mingw-ucrt" - end - - build_gem "google-protobuf", "3.21.12" - - build_gem "google-protobuf", "3.21.12" do |s| - s.required_ruby_version = [">= 2.5", "< #{previous_ruby_minor}"] - s.platform = "x64-mingw32" - end - - build_gem "google-protobuf", "3.21.12" do |s| - s.required_ruby_version = [">= #{previous_ruby_minor}", "< #{current_ruby_minor}"] - s.platform = "x64-mingw-ucrt" - end - end - - gemfile <<~G - source "https://gem.repo4" - - gem "google-protobuf" - gem "unf_ext" - G - - lockfile <<~L - GEM - remote: https://gem.repo4/ - specs: - google-protobuf (3.21.12) - unf_ext (0.0.8.2) - - PLATFORMS - x64-mingw-ucrt - x64-mingw32 - - DEPENDENCIES - google-protobuf - unf_ext - - BUNDLED WITH - #{Bundler::VERSION} - L - - bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s, "DEBUG_RESOLVER" => "1" } - end - it "respects lower bound ruby requirements" do build_repo4 do build_gem "our_private_gem", "0.1.0" do |s| @@ -1670,6 +1608,64 @@ RSpec.describe "bundle lock" do end end + context "when a system gem has incorrect dependencies, different from remote gems" do + before do + build_repo4 do + build_gem "foo", "1.0.0" do |s| + s.add_dependency "bar" + end + + build_gem "bar", "1.0.0" + end + + system_gems "foo-1.0.0", gem_repo: gem_repo4, path: default_bundle_path + + # simulate gemspec with wrong empty dependencies + foo_gemspec_path = default_bundle_path("specifications/foo-1.0.0.gemspec") + foo_gemspec = Gem::Specification.load(foo_gemspec_path.to_s) + foo_gemspec.dependencies.clear + File.write(foo_gemspec_path, foo_gemspec.to_ruby) + end + + it "generates a lockfile using remote dependencies, and prints a warning" do + gemfile <<~G + source "https://gem.repo4" + + gem "foo" + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "foo", "1.0.0" + c.checksum gem_repo4, "bar", "1.0.0" + end + + simulate_platform "x86_64-linux" do + bundle "lock --verbose" + end + + expect(err).to eq("Local specification for foo-1.0.0 has different dependencies than the remote gem, ignoring it") + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + bar (1.0.0) + foo (1.0.0) + bar + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + foo + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + it "properly shows resolution errors including OR requirements" do build_repo4 do build_gem "activeadmin", "2.13.1" do |s| diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb index 6d135a2806..c1ab26ec10 100644 --- a/spec/bundler/commands/newgem_spec.rb +++ b/spec/bundler/commands/newgem_spec.rb @@ -6,39 +6,42 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/README.md")).to exist expect(bundled_app("#{gem_name}/Gemfile")).to exist expect(bundled_app("#{gem_name}/Rakefile")).to exist - expect(bundled_app("#{gem_name}/lib/#{require_path}.rb")).to exist - expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb")).to exist + expect(bundled_app("#{gem_name}/lib/#{gem_name}.rb")).to exist + expect(bundled_app("#{gem_name}/lib/#{gem_name}/version.rb")).to exist + + expect(ignore_paths).to include("bin/") + expect(ignore_paths).to include("Gemfile") end def bundle_exec_rubocop prepare_gemspec(bundled_app(gem_name, "#{gem_name}.gemspec")) - bundle "config set path #{rubocop_gems}", dir: bundled_app(gem_name) + bundle "config set path #{rubocop_gem_path}", dir: bundled_app(gem_name) bundle "exec rubocop --debug --config .rubocop.yml", dir: bundled_app(gem_name) end def bundle_exec_standardrb prepare_gemspec(bundled_app(gem_name, "#{gem_name}.gemspec")) - bundle "config set path #{standard_gems}", dir: bundled_app(gem_name) + bundle "config set path #{standard_gem_path}", dir: bundled_app(gem_name) bundle "exec standardrb --debug", dir: bundled_app(gem_name) end + def ignore_paths + generated = bundled_app("#{gem_name}/#{gem_name}.gemspec").read + matched = generated.match(/^\s+f\.start_with\?\(\*%w\[(?.*)\]\)$/) + matched[:ignored]&.split(" ") + end + let(:generated_gemspec) { Bundler.load_gemspec_uncached(bundled_app(gem_name).join("#{gem_name}.gemspec")) } let(:gem_name) { "mygem" } - let(:require_path) { "mygem" } - - let(:minitest_test_file_path) { "test/test_mygem.rb" } - - let(:minitest_test_class_name) { "class TestMygem < Minitest::Test" } - before do git("config --global user.name 'Bundler User'") git("config --global user.email user@example.com") git("config --global github.user bundleuser") global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false", "BUNDLE_GEM__LINTER" => "false", - "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__CHANGELOG" => "false" + "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__CHANGELOG" => "false", "BUNDLE_GEM__BUNDLE" => "false" end describe "git repo initialization" do @@ -152,8 +155,28 @@ RSpec.describe "bundle gem" do end end + shared_examples_for "--bundle flag" do + before do + bundle "gem #{gem_name} --bundle" + end + it "generates a gem skeleton with bundle install" do + gem_skeleton_assertions + expect(out).to include("Running bundle install in the new gem directory.") + end + end + + shared_examples_for "--no-bundle flag" do + before do + bundle "gem #{gem_name} --no-bundle" + end + it "generates a gem skeleton without bundle install" do + gem_skeleton_assertions + expect(out).to_not include("Running bundle install in the new gem directory.") + end + end + shared_examples_for "--rubocop flag" do - context "is deprecated", bundler: "< 3" do + context "is deprecated" do before do global_config "BUNDLE_GEM__LINTER" => nil bundle "gem #{gem_name} --rubocop" @@ -161,7 +184,7 @@ RSpec.describe "bundle gem" do it "generates a gem skeleton with rubocop" do gem_skeleton_assertions - expect(bundled_app("test-gem/Rakefile")).to read_as( + expect(bundled_app("#{gem_name}/Rakefile")).to read_as( include("# frozen_string_literal: true"). and(include('require "rubocop/rake_task"'). and(include("RuboCop::RakeTask.new"). @@ -181,11 +204,15 @@ RSpec.describe "bundle gem" do it "generates a default .rubocop.yml" do expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist end + + it "includes .rubocop.yml into ignore list" do + expect(ignore_paths).to include(".rubocop.yml") + end end end shared_examples_for "--no-rubocop flag" do - context "is deprecated", bundler: "< 3" do + context "is deprecated" do define_negated_matcher :exclude, :include before do @@ -194,8 +221,8 @@ RSpec.describe "bundle gem" do it "generates a gem skeleton without rubocop" do gem_skeleton_assertions - expect(bundled_app("test-gem/Rakefile")).to read_as(exclude("rubocop")) - expect(bundled_app("test-gem/#{gem_name}.gemspec")).to read_as(exclude("rubocop")) + expect(bundled_app("#{gem_name}/Rakefile")).to read_as(exclude("rubocop")) + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec")).to read_as(exclude("rubocop")) end it "does not include rubocop in generated Gemfile" do @@ -210,6 +237,10 @@ RSpec.describe "bundle gem" do it "doesn't generate a default .rubocop.yml" do expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist end + + it "does not add .rubocop.yml into ignore list" do + expect(ignore_paths).not_to include(".rubocop.yml") + end end end @@ -220,7 +251,7 @@ RSpec.describe "bundle gem" do it "generates a gem skeleton with rubocop" do gem_skeleton_assertions - expect(bundled_app("test-gem/Rakefile")).to read_as( + expect(bundled_app("#{gem_name}/Rakefile")).to read_as( include("# frozen_string_literal: true"). and(include('require "rubocop/rake_task"'). and(include("RuboCop::RakeTask.new"). @@ -240,6 +271,10 @@ RSpec.describe "bundle gem" do it "generates a default .rubocop.yml" do expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist end + + it "includes .rubocop.yml into ignore list" do + expect(ignore_paths).to include(".rubocop.yml") + end end shared_examples_for "--linter=standard flag" do @@ -249,7 +284,7 @@ RSpec.describe "bundle gem" do it "generates a gem skeleton with standard" do gem_skeleton_assertions - expect(bundled_app("test-gem/Rakefile")).to read_as( + expect(bundled_app("#{gem_name}/Rakefile")).to read_as( include('require "standard/rake"'). and(match(/default:.+:standard/)) ) @@ -267,6 +302,10 @@ RSpec.describe "bundle gem" do it "generates a default .standard.yml" do expect(bundled_app("#{gem_name}/.standard.yml")).to exist end + + it "includes .standard.yml into ignore list" do + expect(ignore_paths).to include(".standard.yml") + end end shared_examples_for "--no-linter flag" do @@ -278,8 +317,8 @@ RSpec.describe "bundle gem" do it "generates a gem skeleton without rubocop" do gem_skeleton_assertions - expect(bundled_app("test-gem/Rakefile")).to read_as(exclude("rubocop")) - expect(bundled_app("test-gem/#{gem_name}.gemspec")).to read_as(exclude("rubocop")) + expect(bundled_app("#{gem_name}/Rakefile")).to read_as(exclude("rubocop")) + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec")).to read_as(exclude("rubocop")) end it "does not include rubocop in generated Gemfile" do @@ -304,9 +343,17 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist end + it "does not add .rubocop.yml into ignore list" do + expect(ignore_paths).not_to include(".rubocop.yml") + end + it "doesn't generate a default .standard.yml" do expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist end + + it "does not add .standard.yml into ignore list" do + expect(ignore_paths).not_to include(".standard.yml") + end end it "has no rubocop offenses when using --linter=rubocop flag" do @@ -353,7 +400,6 @@ RSpec.describe "bundle gem" do it "has no rubocop offenses when using --ext=rust and --linter=rubocop flag" do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version bundle "gem #{gem_name} --ext=rust --linter=rubocop" bundle_exec_rubocop @@ -362,7 +408,6 @@ RSpec.describe "bundle gem" do it "has no rubocop offenses when using --ext=rust, --test=minitest, and --linter=rubocop flag" do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version bundle "gem #{gem_name} --ext=rust --test=minitest --linter=rubocop" bundle_exec_rubocop @@ -371,7 +416,6 @@ RSpec.describe "bundle gem" do it "has no rubocop offenses when using --ext=rust, --test=rspec, and --linter=rubocop flag" do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version bundle "gem #{gem_name} --ext=rust --test=rspec --linter=rubocop" bundle_exec_rubocop @@ -380,7 +424,6 @@ RSpec.describe "bundle gem" do it "has no rubocop offenses when using --ext=rust, --test=test-unit, and --linter=rubocop flag" do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version bundle "gem #{gem_name} --ext=rust --test=test-unit --linter=rubocop" bundle_exec_rubocop @@ -398,11 +441,17 @@ RSpec.describe "bundle gem" do shared_examples_for "test framework is absent" do it "does not create any test framework files" do expect(bundled_app("#{gem_name}/.rspec")).to_not exist - expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb")).to_not exist + expect(bundled_app("#{gem_name}/spec/#{gem_name}_spec.rb")).to_not exist expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to_not exist - expect(bundled_app("#{gem_name}/test/#{require_path}.rb")).to_not exist + expect(bundled_app("#{gem_name}/test/#{gem_name}.rb")).to_not exist expect(bundled_app("#{gem_name}/test/test_helper.rb")).to_not exist end + + it "does not add any test framework files into ignore list" do + expect(ignore_paths).not_to include("test/") + expect(ignore_paths).not_to include(".rspec") + expect(ignore_paths).not_to include("spec/") + end end context "README.md" do @@ -471,6 +520,10 @@ RSpec.describe "bundle gem" do it "doesn't create a .gitignore file" do expect(bundled_app("#{gem_name}/.gitignore")).to_not exist end + + it "does not add .gitignore into ignore list" do + expect(ignore_paths).not_to include(".gitignore") + end end it "generates a valid gemspec" do @@ -482,11 +535,10 @@ RSpec.describe "bundle gem" do build_dummy_irb "9.9.9" end gems = ["rake-#{rake_version}", "irb-9.9.9"] - path = Bundler.feature_flag.default_install_uses_path? ? local_gem_path(base: bundled_app("newgem")) : system_gem_path - system_gems gems, path: path, gem_repo: gem_repo2 + system_gems gems, path: system_gem_path, gem_repo: gem_repo2 bundle "exec rake build", dir: bundled_app("newgem") - expect(last_command.stdboth).not_to include("ERROR") + expect(stdboth).not_to include("ERROR") end context "gem naming with relative paths" do @@ -568,129 +620,840 @@ RSpec.describe "bundle gem" do end end - shared_examples_for "generating a gem" do - it "generates a gem skeleton" do + it "generates a gem skeleton" do + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec")).to exist + expect(bundled_app("#{gem_name}/Gemfile")).to exist + expect(bundled_app("#{gem_name}/Rakefile")).to exist + expect(bundled_app("#{gem_name}/lib/#{gem_name}.rb")).to exist + expect(bundled_app("#{gem_name}/lib/#{gem_name}/version.rb")).to exist + expect(bundled_app("#{gem_name}/sig/#{gem_name}.rbs")).to exist + expect(bundled_app("#{gem_name}/.gitignore")).to exist + + expect(bundled_app("#{gem_name}/bin/setup")).to exist + expect(bundled_app("#{gem_name}/bin/console")).to exist + + unless Gem.win_platform? + expect(bundled_app("#{gem_name}/bin/setup")).to be_executable + expect(bundled_app("#{gem_name}/bin/console")).to be_executable + end + + expect(bundled_app("#{gem_name}/bin/setup").read).to start_with("#!") + expect(bundled_app("#{gem_name}/bin/console").read).to start_with("#!") + end + + it "includes bin/ into ignore list" do + bundle "gem #{gem_name}" + + expect(ignore_paths).to include("bin/") + end + + it "includes Gemfile into ignore list" do + bundle "gem #{gem_name}" + + expect(ignore_paths).to include("Gemfile") + end + + it "includes .gitignore into ignore list" do + bundle "gem #{gem_name}" + + expect(ignore_paths).to include(".gitignore") + end + + it "starts with version 0.1.0" do + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/lib/#{gem_name}/version.rb").read).to match(/VERSION = "0.1.0"/) + end + + it "declare String type for VERSION constant" do + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/sig/#{gem_name}.rbs").read).to match(/VERSION: String/) + end + + context "git config user.{name,email} is set" do + before do + bundle "gem #{gem_name}" + end + + it "sets gemspec author to git user.name if available" do + expect(generated_gemspec.authors.first).to eq("Bundler User") + end + + it "sets gemspec email to git user.email if available" do + expect(generated_gemspec.email.first).to eq("user@example.com") + end + end + + context "git config user.{name,email} is not set" do + before do + git("config --global --unset user.name") + git("config --global --unset user.email") + bundle "gem #{gem_name}" + end + + it "sets gemspec author to default message if git user.name is not set or empty" do + expect(generated_gemspec.authors.first).to eq("TODO: Write your name") + end + + it "sets gemspec email to default message if git user.email is not set or empty" do + expect(generated_gemspec.email.first).to eq("TODO: Write your email address") + end + end + + it "sets gemspec metadata['allowed_push_host']" do + bundle "gem #{gem_name}" + + expect(generated_gemspec.metadata["allowed_push_host"]). + to match(/example\.com/) + end + + it "sets a minimum ruby version" do + bundle "gem #{gem_name}" + + expect(generated_gemspec.required_ruby_version.to_s).to start_with(">=") + end + + it "does not include the gemspec file in files" do + bundle "gem #{gem_name}" + + bundler_gemspec = Bundler::GemHelper.new(bundled_app(gem_name), gem_name).gemspec + + expect(bundler_gemspec.files).not_to include("#{gem_name}.gemspec") + end + + it "does not include the Gemfile file in files" do + bundle "gem #{gem_name}" + + bundler_gemspec = Bundler::GemHelper.new(bundled_app(gem_name), gem_name).gemspec + + expect(bundler_gemspec.files).not_to include("Gemfile") + end + + it "runs rake without problems" do + bundle "gem #{gem_name}" + + system_gems ["rake-#{rake_version}"] + + rakefile = <<~RAKEFILE + task :default do + puts 'SUCCESS' + end + RAKEFILE + File.open(bundled_app("#{gem_name}/Rakefile"), "w") do |file| + file.puts rakefile + end + + sys_exec(rake, dir: bundled_app(gem_name)) + expect(out).to include("SUCCESS") + end + + context "--exe parameter set" do + before do + bundle "gem #{gem_name} --exe" + end + + it "builds exe skeleton" do + expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist + unless Gem.win_platform? + expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to be_executable + end + end + end + + context "--bin parameter set" do + before do + bundle "gem #{gem_name} --bin" + end + + it "builds exe skeleton" do + expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist + end + end + + context "no --test parameter" do + before do + bundle "gem #{gem_name}" + end + + it_behaves_like "test framework is absent" + end + + context "--test parameter set to rspec" do + before do + bundle "gem #{gem_name} --test=rspec" + end + + it "builds spec skeleton" do + expect(bundled_app("#{gem_name}/.rspec")).to exist + expect(bundled_app("#{gem_name}/spec/#{gem_name}_spec.rb")).to exist + expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist + end + + it "includes .rspec and spec/ into ignore list" do + expect(ignore_paths).to include(".rspec") + expect(ignore_paths).to include("spec/") + end + + it "depends on a specific version of rspec in generated Gemfile" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + builder = Bundler::Dsl.new + builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) + builder.dependencies + rspec_dep = builder.dependencies.find {|d| d.name == "rspec" } + expect(rspec_dep).to be_specific + end + end + + context "init_gems_rb setting to true" do + before do + bundle "config set init_gems_rb true" + bundle "gem #{gem_name}" + end + + it "generates gems.rb instead of Gemfile" do + expect(bundled_app("#{gem_name}/gems.rb")).to exist + expect(bundled_app("#{gem_name}/Gemfile")).to_not exist + end + + it "includes gems.rb and gems.locked into ignore list" do + expect(ignore_paths).to include("gems.rb") + expect(ignore_paths).to include("gems.locked") + expect(ignore_paths).not_to include("Gemfile") + end + end + + context "init_gems_rb setting to false" do + before do + bundle "config set init_gems_rb false" + bundle "gem #{gem_name}" + end + + it "generates Gemfile instead of gems.rb" do + expect(bundled_app("#{gem_name}/gems.rb")).to_not exist + expect(bundled_app("#{gem_name}/Gemfile")).to exist + end + + it "includes Gemfile into ignore list" do + expect(ignore_paths).to include("Gemfile") + expect(ignore_paths).not_to include("gems.rb") + expect(ignore_paths).not_to include("gems.locked") + end + end + + context "gem.test setting set to rspec" do + before do + bundle "config set gem.test rspec" + bundle "gem #{gem_name}" + end + + it "builds spec skeleton" do + expect(bundled_app("#{gem_name}/.rspec")).to exist + expect(bundled_app("#{gem_name}/spec/#{gem_name}_spec.rb")).to exist + expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist + end + + it "includes .rspec and spec/ into ignore list" do + expect(ignore_paths).to include(".rspec") + expect(ignore_paths).to include("spec/") + end + end + + context "gem.test setting set to rspec and --test is set to minitest" do + before do + bundle "config set gem.test rspec" + bundle "gem #{gem_name} --test=minitest" + end + + it "builds spec skeleton" do + expect(bundled_app("#{gem_name}/test/test_#{gem_name}.rb")).to exist + expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist + end + + it "includes test/ into ignore list" do + expect(ignore_paths).to include("test/") + end + end + + context "--test parameter set to minitest" do + before do + bundle "gem #{gem_name} --test=minitest" + end + + it "depends on a specific version of minitest" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + builder = Bundler::Dsl.new + builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) + builder.dependencies + minitest_dep = builder.dependencies.find {|d| d.name == "minitest" } + expect(minitest_dep).to be_specific + end + + it "builds spec skeleton" do + expect(bundled_app("#{gem_name}/test/test_#{gem_name}.rb")).to exist + expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist + end + + it "includes test/ into ignore list" do + expect(ignore_paths).to include("test/") + end + + it "creates a default rake task to run the test suite" do + rakefile = <<~RAKEFILE + # frozen_string_literal: true + + require "bundler/gem_tasks" + require "minitest/test_task" + + Minitest::TestTask.create + + task default: :test + RAKEFILE + + expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) + end + end + + context "gem.test setting set to minitest" do + before do + bundle "config set gem.test minitest" + bundle "gem #{gem_name}" + end + + it "creates a default rake task to run the test suite" do + rakefile = <<~RAKEFILE + # frozen_string_literal: true + + require "bundler/gem_tasks" + require "minitest/test_task" + + Minitest::TestTask.create + + task default: :test + RAKEFILE + + expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) + end + end + + context "--test parameter set to test-unit" do + before do + bundle "gem #{gem_name} --test=test-unit" + end + + it "depends on a specific version of test-unit" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + builder = Bundler::Dsl.new + builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) + builder.dependencies + test_unit_dep = builder.dependencies.find {|d| d.name == "test-unit" } + expect(test_unit_dep).to be_specific + end + + it "builds spec skeleton" do + expect(bundled_app("#{gem_name}/test/#{gem_name}_test.rb")).to exist + expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist + end + + it "includes test/ into ignore list" do + expect(ignore_paths).to include("test/") + end + + it "creates a default rake task to run the test suite" do + rakefile = <<~RAKEFILE + # frozen_string_literal: true + + require "bundler/gem_tasks" + require "rake/testtask" + + Rake::TestTask.new(:test) do |t| + t.libs << "test" + t.libs << "lib" + t.test_files = FileList["test/**/*_test.rb"] + end + + task default: :test + RAKEFILE + + expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) + end + end + + context "--test parameter set to an invalid value" do + before do + bundle "gem #{gem_name} --test=foo", raise_on_error: false + end + + it "fails loudly" do + expect(last_command).to be_failure + expect(err).to match(/Expected '--test' to be one of .*; got foo/) + end + end + + context "gem.test set to rspec and --test with no arguments" do + before do + bundle "config set gem.test rspec" + bundle "gem #{gem_name} --test" + end + + it "builds spec skeleton" do + expect(bundled_app("#{gem_name}/.rspec")).to exist + expect(bundled_app("#{gem_name}/spec/#{gem_name}_spec.rb")).to exist + expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist + end + + it "includes .rspec and spec/ into ignore list" do + expect(ignore_paths).to include(".rspec") + expect(ignore_paths).to include("spec/") + end + + it "hints that --test is already configured" do + expect(out).to match("rspec is already configured, ignoring --test flag.") + end + end + + context "gem.test setting set to false and --test with no arguments", :readline do + before do + bundle "config set gem.test false" + bundle "gem #{gem_name} --test" do |input, _, _| + input.puts + end + end + + it "asks to generate test files" do + expect(out).to match("Do you want to generate tests with your gem?") + end + + it "hints that the choice will only be applied to the current gem" do + expect(out).to match("Your choice will only be applied to this gem.") + end + + it_behaves_like "test framework is absent" + end + + context "gem.test setting not set and --test with no arguments", :readline do + before do + global_config "BUNDLE_GEM__TEST" => nil + bundle "gem #{gem_name} --test" do |input, _, _| + input.puts + end + end + + it "asks to generate test files" do + expect(out).to match("Do you want to generate tests with your gem?") + end + + it "hints that the choice will be applied to future bundle gem calls" do + hint = "Future `bundle gem` calls will use your choice. " \ + "This setting can be changed anytime with `bundle config gem.test`." + expect(out).to match(hint) + end + + it_behaves_like "test framework is absent" + end + + context "gem.test setting set to a test framework and --no-test" do + before do + bundle "config set gem.test rspec" + bundle "gem #{gem_name} --no-test" + end + + it_behaves_like "test framework is absent" + end + + context "--ci with no argument" do + before do + bundle "gem #{gem_name}" + end + + it "does not generate any CI config" do + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to_not exist + expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to_not exist + expect(bundled_app("#{gem_name}/.circleci/config.yml")).to_not exist + end + + it "does not add any CI config files into ignore list" do + expect(ignore_paths).not_to include(".github/") + expect(ignore_paths).not_to include(".gitlab-ci.yml") + expect(ignore_paths).not_to include(".circleci/") + end + end + + context "--ci set to github" do + before do + bundle "gem #{gem_name} --ci=github" + end + + it "generates a GitHub Actions config file" do + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist + end + + it "includes .github/ into ignore list" do + expect(ignore_paths).to include(".github/") + end + end + + context "--ci set to gitlab" do + before do + bundle "gem #{gem_name} --ci=gitlab" + end + + it "generates a GitLab CI config file" do + expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist + end + + it "includes .gitlab-ci.yml into ignore list" do + expect(ignore_paths).to include(".gitlab-ci.yml") + end + end + + context "--ci set to circle" do + before do + bundle "gem #{gem_name} --ci=circle" + end + + it "generates a CircleCI config file" do + expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist + end + + it "includes .circleci/ into ignore list" do + expect(ignore_paths).to include(".circleci/") + end + end + + context "--ci set to an invalid value" do + before do + bundle "gem #{gem_name} --ci=foo", raise_on_error: false + end + + it "fails loudly" do + expect(last_command).to be_failure + expect(err).to match(/Expected '--ci' to be one of .*; got foo/) + end + end + + context "gem.ci setting set to none" do + it "doesn't generate any CI config" do + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to_not exist + expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to_not exist + expect(bundled_app("#{gem_name}/.circleci/config.yml")).to_not exist + end + end + + context "gem.ci setting set to github" do + it "generates a GitHub Actions config file" do + bundle "config set gem.ci github" + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist + end + end + + context "gem.ci setting set to gitlab" do + it "generates a GitLab CI config file" do + bundle "config set gem.ci gitlab" + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist + end + end + + context "gem.ci setting set to circle" do + it "generates a CircleCI config file" do + bundle "config set gem.ci circle" + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist + end + end + + context "gem.ci set to github and --ci with no arguments" do + before do + bundle "config set gem.ci github" + bundle "gem #{gem_name} --ci" + end + + it "generates a GitHub Actions config file" do + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist + end + + it "hints that --ci is already configured" do + expect(out).to match("github is already configured, ignoring --ci flag.") + end + end + + context "gem.ci setting set to false and --ci with no arguments", :readline do + before do + bundle "config set gem.ci false" + bundle "gem #{gem_name} --ci" do |input, _, _| + input.puts "github" + end + end + + it "asks to setup CI" do + expect(out).to match("Do you want to set up continuous integration for your gem?") + end + + it "hints that the choice will only be applied to the current gem" do + expect(out).to match("Your choice will only be applied to this gem.") + end + end + + context "gem.ci setting not set and --ci with no arguments", :readline do + before do + global_config "BUNDLE_GEM__CI" => nil + bundle "gem #{gem_name} --ci" do |input, _, _| + input.puts "github" + end + end + + it "asks to setup CI" do + expect(out).to match("Do you want to set up continuous integration for your gem?") + end + + it "hints that the choice will be applied to future bundle gem calls" do + hint = "Future `bundle gem` calls will use your choice. " \ + "This setting can be changed anytime with `bundle config gem.ci`." + expect(out).to match(hint) + end + end + + context "gem.ci setting set to a CI service and --no-ci" do + before do + bundle "config set gem.ci github" + bundle "gem #{gem_name} --no-ci" + end + + it "does not generate any CI config" do + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to_not exist + expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to_not exist + expect(bundled_app("#{gem_name}/.circleci/config.yml")).to_not exist + end + end + + context "--linter with no argument" do + before do + bundle "gem #{gem_name}" + end + + it "does not generate any linter config" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist + expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist + end + + it "does not add any linter config files into ignore list" do + expect(ignore_paths).not_to include(".rubocop.yml") + expect(ignore_paths).not_to include(".standard.yml") + end + end + + context "--linter set to rubocop" do + before do + bundle "gem #{gem_name} --linter=rubocop" + end + + it "generates a RuboCop config" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist + expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist + end + + it "includes .rubocop.yml into ignore list" do + expect(ignore_paths).to include(".rubocop.yml") + expect(ignore_paths).not_to include(".standard.yml") + end + end + + context "--linter set to standard" do + before do + bundle "gem #{gem_name} --linter=standard" + end + + it "generates a Standard config" do + expect(bundled_app("#{gem_name}/.standard.yml")).to exist + expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist + end + + it "includes .standard.yml into ignore list" do + expect(ignore_paths).to include(".standard.yml") + expect(ignore_paths).not_to include(".rubocop.yml") + end + end + + context "--linter set to an invalid value" do + before do + bundle "gem #{gem_name} --linter=foo", raise_on_error: false + end + + it "fails loudly" do + expect(last_command).to be_failure + expect(err).to match(/Expected '--linter' to be one of .*; got foo/) + end + end + + context "gem.linter setting set to none" do + before do + bundle "gem #{gem_name}" + end + + it "doesn't generate any linter config" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist + expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist + end + + it "does not add any linter config files into ignore list" do + expect(ignore_paths).not_to include(".rubocop.yml") + expect(ignore_paths).not_to include(".standard.yml") + end + end + + context "gem.linter setting set to rubocop" do + before do + bundle "config set gem.linter rubocop" + bundle "gem #{gem_name}" + end + + it "generates a RuboCop config file" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist + end + + it "includes .rubocop.yml into ignore list" do + expect(ignore_paths).to include(".rubocop.yml") + end + end + + context "gem.linter setting set to standard" do + before do + bundle "config set gem.linter standard" + bundle "gem #{gem_name}" + end + + it "generates a Standard config file" do + expect(bundled_app("#{gem_name}/.standard.yml")).to exist + end + + it "includes .standard.yml into ignore list" do + expect(ignore_paths).to include(".standard.yml") + end + end + + context "gem.rubocop setting set to true" do + before do + global_config "BUNDLE_GEM__LINTER" => nil + bundle "config set gem.rubocop true" + bundle "gem #{gem_name}" + end + + it "generates rubocop config" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist + end + + it "includes .rubocop.yml into ignore list" do + expect(ignore_paths).to include(".rubocop.yml") + end + + it "unsets gem.rubocop" do + bundle "config gem.rubocop" + expect(out).to include("You have not configured a value for `gem.rubocop`") + end + + it "sets gem.linter=rubocop instead" do + bundle "config gem.linter" + expect(out).to match(/Set for the current user .*: "rubocop"/) + end + end + + context "gem.linter set to rubocop and --linter with no arguments" do + before do + bundle "config set gem.linter rubocop" + bundle "gem #{gem_name} --linter" + end + + it "generates a RuboCop config file" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist + end + + it "includes .rubocop.yml into ignore list" do + expect(ignore_paths).to include(".rubocop.yml") + end + + it "hints that --linter is already configured" do + expect(out).to match("rubocop is already configured, ignoring --linter flag.") + end + end + + context "gem.linter setting set to false and --linter with no arguments", :readline do + before do + bundle "config set gem.linter false" + bundle "gem #{gem_name} --linter" do |input, _, _| + input.puts "rubocop" + end + end + + it "asks to setup a linter" do + expect(out).to match("Do you want to add a code linter and formatter to your gem?") + end + + it "hints that the choice will only be applied to the current gem" do + expect(out).to match("Your choice will only be applied to this gem.") + end + end + + context "gem.linter setting not set and --linter with no arguments", :readline do + before do + global_config "BUNDLE_GEM__LINTER" => nil + bundle "gem #{gem_name} --linter" do |input, _, _| + input.puts "rubocop" + end + end + + it "asks to setup a linter" do + expect(out).to match("Do you want to add a code linter and formatter to your gem?") + end + + it "hints that the choice will be applied to future bundle gem calls" do + hint = "Future `bundle gem` calls will use your choice. " \ + "This setting can be changed anytime with `bundle config gem.linter`." + expect(out).to match(hint) + end + end + + context "gem.linter setting set to a linter and --no-linter" do + before do + bundle "config set gem.linter rubocop" + bundle "gem #{gem_name} --no-linter" + end + + it "does not generate any linter config" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist + expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist + end + + it "does not add any linter config files into ignore list" do + expect(ignore_paths).not_to include(".rubocop.yml") + expect(ignore_paths).not_to include(".standard.yml") + end + end + + context "--edit option" do + it "opens the generated gemspec in the user's text editor" do + output = bundle "gem #{gem_name} --edit=echo" + gemspec_path = File.join(bundled_app, gem_name, "#{gem_name}.gemspec") + expect(output).to include("echo \"#{gemspec_path}\"") + end + end + + shared_examples_for "paths that depend on gem name" do + it "generates entrypoint, version file and signatures file at the proper path, with the proper content" do bundle "gem #{gem_name}" - expect(bundled_app("#{gem_name}/#{gem_name}.gemspec")).to exist - expect(bundled_app("#{gem_name}/Gemfile")).to exist - expect(bundled_app("#{gem_name}/Rakefile")).to exist expect(bundled_app("#{gem_name}/lib/#{require_path}.rb")).to exist + expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(%r{require_relative "#{require_relative_path}/version"}) + expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(/class Error < StandardError; end$/) + expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb")).to exist expect(bundled_app("#{gem_name}/sig/#{require_path}.rbs")).to exist - expect(bundled_app("#{gem_name}/.gitignore")).to exist - - expect(bundled_app("#{gem_name}/bin/setup")).to exist - expect(bundled_app("#{gem_name}/bin/console")).to exist - - unless Gem.win_platform? - expect(bundled_app("#{gem_name}/bin/setup")).to be_executable - expect(bundled_app("#{gem_name}/bin/console")).to be_executable - end - - expect(bundled_app("#{gem_name}/bin/setup").read).to start_with("#!") - expect(bundled_app("#{gem_name}/bin/console").read).to start_with("#!") - end - - it "starts with version 0.1.0" do - bundle "gem #{gem_name}" - - expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb").read).to match(/VERSION = "0.1.0"/) - end - - it "declare String type for VERSION constant" do - bundle "gem #{gem_name}" - - expect(bundled_app("#{gem_name}/sig/#{require_path}.rbs").read).to match(/VERSION: String/) - end - - context "git config user.{name,email} is set" do - before do - bundle "gem #{gem_name}" - end - - it "sets gemspec author to git user.name if available" do - expect(generated_gemspec.authors.first).to eq("Bundler User") - end - - it "sets gemspec email to git user.email if available" do - expect(generated_gemspec.email.first).to eq("user@example.com") - end - end - - context "git config user.{name,email} is not set" do - before do - git("config --global --unset user.name") - git("config --global --unset user.email") - bundle "gem #{gem_name}" - end - - it "sets gemspec author to default message if git user.name is not set or empty" do - expect(generated_gemspec.authors.first).to eq("TODO: Write your name") - end - - it "sets gemspec email to default message if git user.email is not set or empty" do - expect(generated_gemspec.email.first).to eq("TODO: Write your email address") - end - end - - it "sets gemspec metadata['allowed_push_host']" do - bundle "gem #{gem_name}" - - expect(generated_gemspec.metadata["allowed_push_host"]). - to match(/example\.com/) - end - - it "sets a minimum ruby version" do - bundle "gem #{gem_name}" - - expect(generated_gemspec.required_ruby_version.to_s).to start_with(">=") - end - - it "requires the version file" do - bundle "gem #{gem_name}" - - expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(%r{require_relative "#{require_relative_path}/version"}) - end - - it "creates a base error class" do - bundle "gem #{gem_name}" - - expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(/class Error < StandardError; end$/) - end - - it "does not include the gemspec file in files" do - bundle "gem #{gem_name}" - - bundler_gemspec = Bundler::GemHelper.new(bundled_app(gem_name), gem_name).gemspec - - expect(bundler_gemspec.files).not_to include("#{gem_name}.gemspec") - end - - it "does not include the Gemfile file in files" do - bundle "gem #{gem_name}" - - bundler_gemspec = Bundler::GemHelper.new(bundled_app(gem_name), gem_name).gemspec - - expect(bundler_gemspec.files).not_to include("Gemfile") - end - - it "runs rake without problems" do - bundle "gem #{gem_name}" - - system_gems ["rake-#{rake_version}"] - - rakefile = <<~RAKEFILE - task :default do - puts 'SUCCESS' - end - RAKEFILE - File.open(bundled_app("#{gem_name}/Rakefile"), "w") do |file| - file.puts rakefile - end - - sys_exec(rake, dir: bundled_app(gem_name)) - expect(out).to include("SUCCESS") end context "--exe parameter set" do @@ -698,14 +1461,8 @@ RSpec.describe "bundle gem" do bundle "gem #{gem_name} --exe" end - it "builds exe skeleton" do + it "builds an exe file that requires the proper entrypoint" do expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist - unless Gem.win_platform? - expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to be_executable - end - end - - it "requires the main file" do expect(bundled_app("#{gem_name}/exe/#{gem_name}").read).to match(/require "#{require_path}"/) end end @@ -715,98 +1472,22 @@ RSpec.describe "bundle gem" do bundle "gem #{gem_name} --bin" end - it "builds exe skeleton" do + it "builds an exe file that requires the proper entrypoint" do expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist - end - - it "requires the main file" do expect(bundled_app("#{gem_name}/exe/#{gem_name}").read).to match(/require "#{require_path}"/) end end - context "no --test parameter" do - before do - bundle "gem #{gem_name}" - end - - it_behaves_like "test framework is absent" - end - context "--test parameter set to rspec" do before do bundle "gem #{gem_name} --test=rspec" end - it "builds spec skeleton" do - expect(bundled_app("#{gem_name}/.rspec")).to exist - expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb")).to exist + it "builds a spec helper that requires the proper entrypoint, and a default test in the proper path which fails" do expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist - end - - it "depends on a specific version of rspec in generated Gemfile" do - allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - builder = Bundler::Dsl.new - builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) - builder.dependencies - rspec_dep = builder.dependencies.find {|d| d.name == "rspec" } - expect(rspec_dep).to be_specific - end - - it "requires the main file" do expect(bundled_app("#{gem_name}/spec/spec_helper.rb").read).to include(%(require "#{require_path}")) - end - - it "creates a default test which fails" do - expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb").read).to include("expect(false).to eq(true)") - end - end - - context "init_gems_rb setting to true" do - before do - bundle "config set init_gems_rb true" - bundle "gem #{gem_name}" - end - - it "generates gems.rb instead of Gemfile" do - expect(bundled_app("#{gem_name}/gems.rb")).to exist - expect(bundled_app("#{gem_name}/Gemfile")).to_not exist - end - end - - context "init_gems_rb setting to false" do - before do - bundle "config set init_gems_rb false" - bundle "gem #{gem_name}" - end - - it "generates Gemfile instead of gems.rb" do - expect(bundled_app("#{gem_name}/gems.rb")).to_not exist - expect(bundled_app("#{gem_name}/Gemfile")).to exist - end - end - - context "gem.test setting set to rspec" do - before do - bundle "config set gem.test rspec" - bundle "gem #{gem_name}" - end - - it "builds spec skeleton" do - expect(bundled_app("#{gem_name}/.rspec")).to exist expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb")).to exist - expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist - end - end - - context "gem.test setting set to rspec and --test is set to minitest" do - before do - bundle "config set gem.test rspec" - bundle "gem #{gem_name} --test=minitest" - end - - it "builds spec skeleton" do - expect(bundled_app("#{gem_name}/#{minitest_test_file_path}")).to exist - expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist + expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb").read).to include("expect(false).to eq(true)") end end @@ -815,667 +1496,250 @@ RSpec.describe "bundle gem" do bundle "gem #{gem_name} --test=minitest" end - it "depends on a specific version of minitest" do - allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - builder = Bundler::Dsl.new - builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) - builder.dependencies - minitest_dep = builder.dependencies.find {|d| d.name == "minitest" } - expect(minitest_dep).to be_specific - end - - it "builds spec skeleton" do - expect(bundled_app("#{gem_name}/#{minitest_test_file_path}")).to exist + it "builds a test helper that requires the proper entrypoint, and default test file in the proper path that defines the proper test class name, requires helper, and fails" do expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist - end - - it "requires the main file" do expect(bundled_app("#{gem_name}/test/test_helper.rb").read).to include(%(require "#{require_path}")) - end - it "requires 'test_helper'" do - expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include(%(require "test_helper")) - end - - it "defines valid test class name" do + expect(bundled_app("#{gem_name}/#{minitest_test_file_path}")).to exist expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include(minitest_test_class_name) - end - - it "creates a default test which fails" do + expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include(%(require "test_helper")) expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include("assert false") end end - context "gem.test setting set to minitest" do - before do - bundle "config set gem.test minitest" - bundle "gem #{gem_name}" - end - - it "creates a default rake task to run the test suite" do - rakefile = <<~RAKEFILE - # frozen_string_literal: true - - require "bundler/gem_tasks" - require "minitest/test_task" - - Minitest::TestTask.create - - task default: :test - RAKEFILE - - expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) - end - end - context "--test parameter set to test-unit" do before do bundle "gem #{gem_name} --test=test-unit" end - it "depends on a specific version of test-unit" do - allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - builder = Bundler::Dsl.new - builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) - builder.dependencies - test_unit_dep = builder.dependencies.find {|d| d.name == "test-unit" } - expect(test_unit_dep).to be_specific - end - - it "builds spec skeleton" do - expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb")).to exist + it "builds a test helper that requires the proper entrypoint, and default test file in the proper path which requires helper and fails" do expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist - end - - it "requires the main file" do expect(bundled_app("#{gem_name}/test/test_helper.rb").read).to include(%(require "#{require_path}")) - end - - it "requires 'test_helper'" do + expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb")).to exist expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb").read).to include(%(require "test_helper")) - end - - it "creates a default test which fails" do expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb").read).to include("assert_equal(\"expected\", \"actual\")") end end + end - context "--test parameter set to an invalid value" do + context "with mit option in bundle config settings set to true" do + before do + global_config "BUNDLE_GEM__MIT" => "true" + end + it_behaves_like "--mit flag" + it_behaves_like "--no-mit flag" + end + + context "with mit option in bundle config settings set to false" do + before do + global_config "BUNDLE_GEM__MIT" => "false" + end + it_behaves_like "--mit flag" + it_behaves_like "--no-mit flag" + end + + context "with coc option in bundle config settings set to true" do + before do + global_config "BUNDLE_GEM__COC" => "true" + end + it_behaves_like "--coc flag" + it_behaves_like "--no-coc flag" + end + + context "with coc option in bundle config settings set to false" do + before do + global_config "BUNDLE_GEM__COC" => "false" + end + it_behaves_like "--coc flag" + it_behaves_like "--no-coc flag" + end + + context "with rubocop option in bundle config settings set to true" do + before do + global_config "BUNDLE_GEM__RUBOCOP" => "true" + end + it_behaves_like "--linter=rubocop flag" + it_behaves_like "--linter=standard flag" + it_behaves_like "--no-linter flag" + it_behaves_like "--rubocop flag" + it_behaves_like "--no-rubocop flag" + end + + context "with rubocop option in bundle config settings set to false" do + before do + global_config "BUNDLE_GEM__RUBOCOP" => "false" + end + it_behaves_like "--linter=rubocop flag" + it_behaves_like "--linter=standard flag" + it_behaves_like "--no-linter flag" + it_behaves_like "--rubocop flag" + it_behaves_like "--no-rubocop flag" + end + + context "with linter option in bundle config settings set to rubocop" do + before do + global_config "BUNDLE_GEM__LINTER" => "rubocop" + end + it_behaves_like "--linter=rubocop flag" + it_behaves_like "--linter=standard flag" + it_behaves_like "--no-linter flag" + end + + context "with linter option in bundle config settings set to standard" do + before do + global_config "BUNDLE_GEM__LINTER" => "standard" + end + it_behaves_like "--linter=rubocop flag" + it_behaves_like "--linter=standard flag" + it_behaves_like "--no-linter flag" + end + + context "with linter option in bundle config settings set to false" do + before do + global_config "BUNDLE_GEM__LINTER" => "false" + end + it_behaves_like "--linter=rubocop flag" + it_behaves_like "--linter=standard flag" + it_behaves_like "--no-linter flag" + end + + context "with changelog option in bundle config settings set to true" do + before do + global_config "BUNDLE_GEM__CHANGELOG" => "true" + end + it_behaves_like "--changelog flag" + it_behaves_like "--no-changelog flag" + end + + context "with changelog option in bundle config settings set to false" do + before do + global_config "BUNDLE_GEM__CHANGELOG" => "false" + end + it_behaves_like "--changelog flag" + it_behaves_like "--no-changelog flag" + end + + context "with bundle option in bundle config settings set to true" do + before do + global_config "BUNDLE_GEM__BUNDLE" => "true" + end + it_behaves_like "--bundle flag" + it_behaves_like "--no-bundle flag" + + it "runs bundle install" do + bundle "gem #{gem_name}" + expect(out).to include("Running bundle install in the new gem directory.") + end + end + + context "with bundle option in bundle config settings set to false" do + before do + global_config "BUNDLE_GEM__BUNDLE" => "false" + end + it_behaves_like "--bundle flag" + it_behaves_like "--no-bundle flag" + + it "does not run bundle install" do + bundle "gem #{gem_name}" + expect(out).to_not include("Running bundle install in the new gem directory.") + end + end + + context "without git config github.user set" do + before do + git("config --global --unset github.user") + end + context "with github-username option in bundle config settings set to some value" do before do - bundle "gem #{gem_name} --test=foo", raise_on_error: false + global_config "BUNDLE_GEM__GITHUB_USERNAME" => "different_username" end + it_behaves_like "--github-username option", "gh_user" + end - it "fails loudly" do - expect(last_command).to be_failure - expect(err).to match(/Expected '--test' to be one of .*; got foo/) + it_behaves_like "github_username configuration" + + context "with github-username option in bundle config settings set to false" do + before do + global_config "BUNDLE_GEM__GITHUB_USERNAME" => "false" + end + it_behaves_like "--github-username option", "gh_user" + end + + context "when changelog is enabled" do + it "sets gemspec changelog_uri, homepage, homepage_uri, source_code_uri to TODOs" do + bundle "gem #{gem_name} --changelog" + + expect(generated_gemspec.metadata["changelog_uri"]). + to eq("TODO: Put your gem's CHANGELOG.md URL here.") + expect(generated_gemspec.homepage).to eq("TODO: Put your gem's website or public repo URL here.") + expect(generated_gemspec.metadata["homepage_uri"]).to eq("TODO: Put your gem's website or public repo URL here.") + expect(generated_gemspec.metadata["source_code_uri"]).to eq("TODO: Put your gem's public repo URL here.") end end - context "gem.test setting set to test-unit" do - before do - bundle "config set gem.test test-unit" - bundle "gem #{gem_name}" - end - - it "creates a default rake task to run the test suite" do - rakefile = <<~RAKEFILE - # frozen_string_literal: true - - require "bundler/gem_tasks" - require "rake/testtask" - - Rake::TestTask.new(:test) do |t| - t.libs << "test" - t.libs << "lib" - t.test_files = FileList["test/**/*_test.rb"] - end - - task default: :test - RAKEFILE - - expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) - end - end - - context "gem.test set to rspec and --test with no arguments" do - before do - bundle "config set gem.test rspec" - bundle "gem #{gem_name} --test" - end - - it "builds spec skeleton" do - expect(bundled_app("#{gem_name}/.rspec")).to exist - expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb")).to exist - expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist - end - - it "hints that --test is already configured" do - expect(out).to match("rspec is already configured, ignoring --test flag.") - end - end - - context "gem.test setting set to false and --test with no arguments", :readline do - before do - bundle "config set gem.test false" - bundle "gem #{gem_name} --test" do |input, _, _| - input.puts - end - end - - it "asks to generate test files" do - expect(out).to match("Do you want to generate tests with your gem?") - end - - it "hints that the choice will only be applied to the current gem" do - expect(out).to match("Your choice will only be applied to this gem.") - end - - it_behaves_like "test framework is absent" - end - - context "gem.test setting not set and --test with no arguments", :readline do - before do - global_config "BUNDLE_GEM__TEST" => nil - bundle "gem #{gem_name} --test" do |input, _, _| - input.puts - end - end - - it "asks to generate test files" do - expect(out).to match("Do you want to generate tests with your gem?") - end - - it "hints that the choice will be applied to future bundle gem calls" do - hint = "Future `bundle gem` calls will use your choice. " \ - "This setting can be changed anytime with `bundle config gem.test`." - expect(out).to match(hint) - end - - it_behaves_like "test framework is absent" - end - - context "gem.test setting set to a test framework and --no-test" do - before do - bundle "config set gem.test rspec" - bundle "gem #{gem_name} --no-test" - end - - it_behaves_like "test framework is absent" - end - - context "--ci with no argument" do - it "does not generate any CI config" do + context "when changelog is not enabled" do + it "sets gemspec homepage, homepage_uri, source_code_uri to TODOs and changelog_uri to nil" do bundle "gem #{gem_name}" - expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to_not exist - expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to_not exist - expect(bundled_app("#{gem_name}/.circleci/config.yml")).to_not exist - end - end - - context "--ci set to github" do - it "generates a GitHub Actions config file" do - bundle "gem #{gem_name} --ci=github" - - expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist - end - - it "contained .gitlab-ci.yml into ignore list" do - bundle "gem #{gem_name} --ci=github" - - expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include(".git .github appveyor") - end - end - - context "--ci set to gitlab" do - it "generates a GitLab CI config file" do - bundle "gem #{gem_name} --ci=gitlab" - - expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist - end - - it "contained .gitlab-ci.yml into ignore list" do - bundle "gem #{gem_name} --ci=gitlab" - - expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include(".git .gitlab-ci.yml appveyor") - end - end - - context "--ci set to circle" do - it "generates a CircleCI config file" do - bundle "gem #{gem_name} --ci=circle" - - expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist - end - - it "contained .circleci into ignore list" do - bundle "gem #{gem_name} --ci=circle" - - expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include(".git .circleci appveyor") - end - end - - context "--ci set to an invalid value" do - before do - bundle "gem #{gem_name} --ci=foo", raise_on_error: false - end - - it "fails loudly" do - expect(last_command).to be_failure - expect(err).to match(/Expected '--ci' to be one of .*; got foo/) - end - end - - context "gem.ci setting set to none" do - it "doesn't generate any CI config" do - expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to_not exist - expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to_not exist - expect(bundled_app("#{gem_name}/.circleci/config.yml")).to_not exist - end - end - - context "gem.ci setting set to github" do - it "generates a GitHub Actions config file" do - bundle "config set gem.ci github" - bundle "gem #{gem_name}" - - expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist - end - end - - context "gem.ci setting set to gitlab" do - it "generates a GitLab CI config file" do - bundle "config set gem.ci gitlab" - bundle "gem #{gem_name}" - - expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist - end - end - - context "gem.ci setting set to circle" do - it "generates a CircleCI config file" do - bundle "config set gem.ci circle" - bundle "gem #{gem_name}" - - expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist - end - end - - context "gem.ci set to github and --ci with no arguments" do - before do - bundle "config set gem.ci github" - bundle "gem #{gem_name} --ci" - end - - it "generates a GitHub Actions config file" do - expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist - end - - it "hints that --ci is already configured" do - expect(out).to match("github is already configured, ignoring --ci flag.") - end - end - - context "gem.ci setting set to false and --ci with no arguments", :readline do - before do - bundle "config set gem.ci false" - bundle "gem #{gem_name} --ci" do |input, _, _| - input.puts "github" - end - end - - it "asks to setup CI" do - expect(out).to match("Do you want to set up continuous integration for your gem?") - end - - it "hints that the choice will only be applied to the current gem" do - expect(out).to match("Your choice will only be applied to this gem.") - end - end - - context "gem.ci setting not set and --ci with no arguments", :readline do - before do - global_config "BUNDLE_GEM__CI" => nil - bundle "gem #{gem_name} --ci" do |input, _, _| - input.puts "github" - end - end - - it "asks to setup CI" do - expect(out).to match("Do you want to set up continuous integration for your gem?") - end - - it "hints that the choice will be applied to future bundle gem calls" do - hint = "Future `bundle gem` calls will use your choice. " \ - "This setting can be changed anytime with `bundle config gem.ci`." - expect(out).to match(hint) - end - end - - context "gem.ci setting set to a CI service and --no-ci" do - before do - bundle "config set gem.ci github" - bundle "gem #{gem_name} --no-ci" - end - - it "does not generate any CI config" do - expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to_not exist - expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to_not exist - expect(bundled_app("#{gem_name}/.circleci/config.yml")).to_not exist - end - end - - context "--linter with no argument" do - it "does not generate any linter config" do - bundle "gem #{gem_name}" - - expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist - expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist - end - end - - context "--linter set to rubocop" do - it "generates a RuboCop config" do - bundle "gem #{gem_name} --linter=rubocop" - - expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist - expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist - end - end - - context "--linter set to standard" do - it "generates a Standard config" do - bundle "gem #{gem_name} --linter=standard" - - expect(bundled_app("#{gem_name}/.standard.yml")).to exist - expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist - end - end - - context "--linter set to an invalid value" do - before do - bundle "gem #{gem_name} --linter=foo", raise_on_error: false - end - - it "fails loudly" do - expect(last_command).to be_failure - expect(err).to match(/Expected '--linter' to be one of .*; got foo/) - end - end - - context "gem.linter setting set to none" do - it "doesn't generate any linter config" do - bundle "gem #{gem_name}" - - expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist - expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist - end - end - - context "gem.linter setting set to rubocop" do - it "generates a RuboCop config file" do - bundle "config set gem.linter rubocop" - bundle "gem #{gem_name}" - - expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist - end - end - - context "gem.linter setting set to standard" do - it "generates a Standard config file" do - bundle "config set gem.linter standard" - bundle "gem #{gem_name}" - - expect(bundled_app("#{gem_name}/.standard.yml")).to exist - end - end - - context "gem.rubocop setting set to true", bundler: "< 3" do - before do - global_config "BUNDLE_GEM__LINTER" => nil - bundle "config set gem.rubocop true" - bundle "gem #{gem_name}" - end - - it "generates rubocop config" do - expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist - end - - it "unsets gem.rubocop" do - bundle "config gem.rubocop" - expect(out).to include("You have not configured a value for `gem.rubocop`") - end - - it "sets gem.linter=rubocop instead" do - bundle "config gem.linter" - expect(out).to match(/Set for the current user .*: "rubocop"/) - end - end - - context "gem.linter set to rubocop and --linter with no arguments" do - before do - bundle "config set gem.linter rubocop" - bundle "gem #{gem_name} --linter" - end - - it "generates a RuboCop config file" do - expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist - end - - it "hints that --linter is already configured" do - expect(out).to match("rubocop is already configured, ignoring --linter flag.") - end - end - - context "gem.linter setting set to false and --linter with no arguments", :readline do - before do - bundle "config set gem.linter false" - bundle "gem #{gem_name} --linter" do |input, _, _| - input.puts "rubocop" - end - end - - it "asks to setup a linter" do - expect(out).to match("Do you want to add a code linter and formatter to your gem?") - end - - it "hints that the choice will only be applied to the current gem" do - expect(out).to match("Your choice will only be applied to this gem.") - end - end - - context "gem.linter setting not set and --linter with no arguments", :readline do - before do - global_config "BUNDLE_GEM__LINTER" => nil - bundle "gem #{gem_name} --linter" do |input, _, _| - input.puts "rubocop" - end - end - - it "asks to setup a linter" do - expect(out).to match("Do you want to add a code linter and formatter to your gem?") - end - - it "hints that the choice will be applied to future bundle gem calls" do - hint = "Future `bundle gem` calls will use your choice. " \ - "This setting can be changed anytime with `bundle config gem.linter`." - expect(out).to match(hint) - end - end - - context "gem.linter setting set to a linter and --no-linter" do - before do - bundle "config set gem.linter rubocop" - bundle "gem #{gem_name} --no-linter" - end - - it "does not generate any linter config" do - expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist - expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist - end - end - - context "--edit option" do - it "opens the generated gemspec in the user's text editor" do - output = bundle "gem #{gem_name} --edit=echo" - gemspec_path = File.join(bundled_app, gem_name, "#{gem_name}.gemspec") - expect(output).to include("echo \"#{gemspec_path}\"") + expect(generated_gemspec.metadata["changelog_uri"]).to be_nil + expect(generated_gemspec.homepage).to eq("TODO: Put your gem's website or public repo URL here.") + expect(generated_gemspec.metadata["homepage_uri"]).to eq("TODO: Put your gem's website or public repo URL here.") + expect(generated_gemspec.metadata["source_code_uri"]).to eq("TODO: Put your gem's public repo URL here.") end end end - context "testing --mit and --coc options against bundle config settings" do - let(:gem_name) { "test-gem" } - - let(:require_path) { "test/gem" } - - context "with mit option in bundle config settings set to true" do + context "with git config github.user set" do + context "with github-username option in bundle config settings set to some value" do before do - global_config "BUNDLE_GEM__MIT" => "true" + global_config "BUNDLE_GEM__GITHUB_USERNAME" => "different_username" end - it_behaves_like "--mit flag" - it_behaves_like "--no-mit flag" + it_behaves_like "--github-username option", "gh_user" end - context "with mit option in bundle config settings set to false" do + it_behaves_like "github_username configuration" + + context "with github-username option in bundle config settings set to false" do before do - global_config "BUNDLE_GEM__MIT" => "false" + global_config "BUNDLE_GEM__GITHUB_USERNAME" => "false" end - it_behaves_like "--mit flag" - it_behaves_like "--no-mit flag" + it_behaves_like "--github-username option", "gh_user" end - context "with coc option in bundle config settings set to true" do - before do - global_config "BUNDLE_GEM__COC" => "true" - end - it_behaves_like "--coc flag" - it_behaves_like "--no-coc flag" - end + context "when changelog is enabled" do + it "sets gemspec changelog_uri, homepage, homepage_uri, source_code_uri based on git username" do + bundle "gem #{gem_name} --changelog" - context "with coc option in bundle config settings set to false" do - before do - global_config "BUNDLE_GEM__COC" => "false" - end - it_behaves_like "--coc flag" - it_behaves_like "--no-coc flag" - end - - context "with rubocop option in bundle config settings set to true" do - before do - global_config "BUNDLE_GEM__RUBOCOP" => "true" - end - it_behaves_like "--linter=rubocop flag" - it_behaves_like "--linter=standard flag" - it_behaves_like "--no-linter flag" - it_behaves_like "--rubocop flag" - it_behaves_like "--no-rubocop flag" - end - - context "with rubocop option in bundle config settings set to false" do - before do - global_config "BUNDLE_GEM__RUBOCOP" => "false" - end - it_behaves_like "--linter=rubocop flag" - it_behaves_like "--linter=standard flag" - it_behaves_like "--no-linter flag" - it_behaves_like "--rubocop flag" - it_behaves_like "--no-rubocop flag" - end - - context "with linter option in bundle config settings set to rubocop" do - before do - global_config "BUNDLE_GEM__LINTER" => "rubocop" - end - it_behaves_like "--linter=rubocop flag" - it_behaves_like "--linter=standard flag" - it_behaves_like "--no-linter flag" - end - - context "with linter option in bundle config settings set to standard" do - before do - global_config "BUNDLE_GEM__LINTER" => "standard" - end - it_behaves_like "--linter=rubocop flag" - it_behaves_like "--linter=standard flag" - it_behaves_like "--no-linter flag" - end - - context "with linter option in bundle config settings set to false" do - before do - global_config "BUNDLE_GEM__LINTER" => "false" - end - it_behaves_like "--linter=rubocop flag" - it_behaves_like "--linter=standard flag" - it_behaves_like "--no-linter flag" - end - - context "with changelog option in bundle config settings set to true" do - before do - global_config "BUNDLE_GEM__CHANGELOG" => "true" - end - it_behaves_like "--changelog flag" - it_behaves_like "--no-changelog flag" - end - - context "with changelog option in bundle config settings set to false" do - before do - global_config "BUNDLE_GEM__CHANGELOG" => "false" - end - it_behaves_like "--changelog flag" - it_behaves_like "--no-changelog flag" - end - end - - context "testing --github-username option against git and bundle config settings" do - context "without git config set" do - before do - git("config --global --unset github.user") - end - context "with github-username option in bundle config settings set to some value" do - before do - global_config "BUNDLE_GEM__GITHUB_USERNAME" => "different_username" - end - it_behaves_like "--github-username option", "gh_user" - end - - context "with github-username option in bundle config settings set to false" do - before do - global_config "BUNDLE_GEM__GITHUB_USERNAME" => "false" - end - it_behaves_like "--github-username option", "gh_user" + expect(generated_gemspec.metadata["changelog_uri"]). + to eq("https://github.com/bundleuser/#{gem_name}/blob/main/CHANGELOG.md") + expect(generated_gemspec.homepage).to eq("https://github.com/bundleuser/#{gem_name}") + expect(generated_gemspec.metadata["homepage_uri"]).to eq("https://github.com/bundleuser/#{gem_name}") + expect(generated_gemspec.metadata["source_code_uri"]).to eq("https://github.com/bundleuser/#{gem_name}") end end - context "with git config set" do - context "with github-username option in bundle config settings set to some value" do - before do - global_config "BUNDLE_GEM__GITHUB_USERNAME" => "different_username" - end - it_behaves_like "--github-username option", "gh_user" - end + context "when changelog is not enabled" do + it "sets gemspec source_code_uri, homepage, homepage_uri but not changelog_uri" do + bundle "gem #{gem_name}" - context "with github-username option in bundle config settings set to false" do - before do - global_config "BUNDLE_GEM__GITHUB_USERNAME" => "false" - end - it_behaves_like "--github-username option", "gh_user" + expect(generated_gemspec.metadata["changelog_uri"]).to be_nil + expect(generated_gemspec.homepage).to eq("https://github.com/bundleuser/#{gem_name}") + expect(generated_gemspec.metadata["homepage_uri"]).to eq("https://github.com/bundleuser/#{gem_name}") + expect(generated_gemspec.metadata["source_code_uri"]).to eq("https://github.com/bundleuser/#{gem_name}") end end end - context "testing github_username bundle config against git config settings" do - context "without git config set" do - before do - git("config --global --unset github.user") - end + context "standard gem naming" do + let(:require_path) { gem_name } - it_behaves_like "github_username configuration" - end + let(:require_relative_path) { gem_name } - context "with git config set" do - it_behaves_like "github_username configuration" - end + let(:minitest_test_file_path) { "test/test_#{gem_name}.rb" } + + let(:minitest_test_class_name) { "class TestMygem < Minitest::Test" } + + include_examples "paths that depend on gem name" end context "gem naming with underscore" do @@ -1497,10 +1761,10 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(/module TestGem/) end - include_examples "generating a gem" + include_examples "paths that depend on gem name" context "--ext parameter with no value" do - context "is deprecated", bundler: "< 3" do + context "is deprecated" do it "prints deprecation when used after gem name" do bundle ["gem", "--ext", gem_name].compact.join(" ") expect(err).to include "[DEPRECATED]" @@ -1563,24 +1827,10 @@ RSpec.describe "bundle gem" do end end - context "--ext parameter set with rust and old RubyGems" do - it "fails in friendly way" do - if ::Gem::Version.new("3.3.11") <= ::Gem.rubygems_version - skip "RubyGems compatible with Rust builder" - end - - expect do - bundle ["gem", gem_name, "--ext=rust"].compact.join(" ") - end.to raise_error(RuntimeError, /too old to build Rust extension/) - end - end - context "--ext parameter set with rust" do let(:flags) { "--ext=rust" } before do - skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version - bundle ["gem", gem_name, flags].compact.join(" ") end @@ -1641,7 +1891,7 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(/module Test\n module Gem/) end - include_examples "generating a gem" + include_examples "paths that depend on gem name" end describe "uncommon gem names" do @@ -1735,7 +1985,7 @@ Usage: "bundle gem NAME [OPTIONS]" expect(bundled_app("foobar/.github/workflows/main.yml")).to exist end - it "asks about MIT license" do + it "asks about MIT license just once" do global_config "BUNDLE_GEM__MIT" => nil bundle "config list" @@ -1745,9 +1995,10 @@ Usage: "bundle gem NAME [OPTIONS]" end expect(bundled_app("foobar/LICENSE.txt")).to exist + expect(out).to include("Using a MIT license means").once end - it "asks about CoC" do + it "asks about CoC just once" do global_config "BUNDLE_GEM__COC" => nil bundle "gem foobar" do |input, _, _| @@ -1755,9 +2006,10 @@ Usage: "bundle gem NAME [OPTIONS]" end expect(bundled_app("foobar/CODE_OF_CONDUCT.md")).to exist + expect(out).to include("Codes of conduct can increase contributions to your project").once end - it "asks about CHANGELOG" do + it "asks about CHANGELOG just once" do global_config "BUNDLE_GEM__CHANGELOG" => nil bundle "gem foobar" do |input, _, _| @@ -1765,6 +2017,7 @@ Usage: "bundle gem NAME [OPTIONS]" end expect(bundled_app("foobar/CHANGELOG.md")).to exist + expect(out).to include("A changelog is a file which contains").once end end diff --git a/spec/bundler/commands/outdated_spec.rb b/spec/bundler/commands/outdated_spec.rb index 09b0e6f32f..d3f9dae8c5 100644 --- a/spec/bundler/commands/outdated_spec.rb +++ b/spec/bundler/commands/outdated_spec.rb @@ -151,7 +151,7 @@ RSpec.describe "bundle outdated" do end end - describe "with multiple, duplicated sources, with lockfile in old format", bundler: "< 3" do + describe "with multiple, duplicated sources, with lockfile in old format" do before do build_repo2 do build_gem "dotenv", "2.7.6" @@ -819,7 +819,7 @@ RSpec.describe "bundle outdated" do expect(out).to include("Installing foo 1.0") end - context "after bundle install --deployment", bundler: "< 3" do + context "after bundle install --deployment" do before do build_repo2 diff --git a/spec/bundler/commands/platform_spec.rb b/spec/bundler/commands/platform_spec.rb index 17183e7546..d42fa2adb3 100644 --- a/spec/bundler/commands/platform_spec.rb +++ b/spec/bundler/commands/platform_spec.rb @@ -646,7 +646,7 @@ G expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s) end - it "fails if ruby version doesn't match", bundler: "< 3" do + it "fails if ruby version doesn't match" do gemfile <<-G source "https://gem.repo1" gem "rails" @@ -658,7 +658,7 @@ G should_be_ruby_version_incorrect end - it "fails if engine doesn't match", bundler: "< 3" do + it "fails if engine doesn't match" do gemfile <<-G source "https://gem.repo1" gem "rails" @@ -670,7 +670,7 @@ G should_be_engine_incorrect end - it "fails if engine version doesn't match", bundler: "< 3", jruby_only: true do + it "fails if engine version doesn't match", jruby_only: true do gemfile <<-G source "https://gem.repo1" gem "rails" @@ -682,7 +682,7 @@ G should_be_engine_version_incorrect end - it "fails when patchlevel doesn't match", bundler: "< 3" do + it "fails when patchlevel doesn't match" do gemfile <<-G source "https://gem.repo1" gem "myrack" diff --git a/spec/bundler/commands/post_bundle_message_spec.rb b/spec/bundler/commands/post_bundle_message_spec.rb index 8671504b25..1dfa58dfd7 100644 --- a/spec/bundler/commands/post_bundle_message_spec.rb +++ b/spec/bundler/commands/post_bundle_message_spec.rb @@ -19,142 +19,147 @@ RSpec.describe "post bundle message" do let(:bundle_complete_message) { "Bundle complete!" } let(:bundle_updated_message) { "Bundle updated!" } let(:installed_gems_stats) { "4 Gemfile dependencies, 5 gems now installed." } - let(:bundle_show_message) { Bundler::VERSION.split(".").first.to_i < 3 ? bundle_show_system_message : bundle_show_path_message } - describe "for fresh bundle install" do + describe "when installing to system gems" do + before do + bundle "config set --local path.system true" + end + it "shows proper messages according to the configured groups" do bundle :install - expect(out).to include(bundle_show_message) + expect(out).to include(bundle_show_system_message) expect(out).not_to include("Gems in the group") expect(out).to include(bundle_complete_message) expect(out).to include(installed_gems_stats) bundle "config set --local without emo" bundle :install - expect(out).to include(bundle_show_message) + expect(out).to include(bundle_show_system_message) expect(out).to include("Gems in the group 'emo' were not installed") expect(out).to include(bundle_complete_message) expect(out).to include(installed_gems_stats) bundle "config set --local without emo test" bundle :install - expect(out).to include(bundle_show_message) + expect(out).to include(bundle_show_system_message) expect(out).to include("Gems in the groups 'emo' and 'test' were not installed") expect(out).to include(bundle_complete_message) expect(out).to include("4 Gemfile dependencies, 3 gems now installed.") bundle "config set --local without emo obama test" bundle :install - expect(out).to include(bundle_show_message) + expect(out).to include(bundle_show_system_message) expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not installed") expect(out).to include(bundle_complete_message) expect(out).to include("4 Gemfile dependencies, 2 gems now installed.") end - describe "with `path` configured" do - let(:bundle_path) { "./vendor" } - - it "shows proper messages according to the configured groups" do - bundle "config set --local path vendor" - bundle :install - expect(out).to include(bundle_show_path_message) - expect(out).to_not include("Gems in the group") + describe "for second bundle install run" do + it "without any options" do + 2.times { bundle :install } + expect(out).to include(bundle_show_system_message) + expect(out).to_not include("Gems in the groups") expect(out).to include(bundle_complete_message) - - bundle "config set --local path vendor" - bundle "config set --local without emo" - bundle :install - expect(out).to include(bundle_show_path_message) - expect(out).to include("Gems in the group 'emo' were not installed") - expect(out).to include(bundle_complete_message) - - bundle "config set --local path vendor" - bundle "config set --local without emo test" - bundle :install - expect(out).to include(bundle_show_path_message) - expect(out).to include("Gems in the groups 'emo' and 'test' were not installed") - expect(out).to include(bundle_complete_message) - - bundle "config set --local path vendor" - bundle "config set --local without emo obama test" - bundle :install - expect(out).to include(bundle_show_path_message) - expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not installed") - expect(out).to include(bundle_complete_message) - end - end - - describe "with an absolute `path` inside the cwd configured" do - let(:bundle_path) { bundled_app("cache") } - - it "shows proper messages according to the configured groups" do - bundle "config set --local path #{bundle_path}" - bundle :install - expect(out).to include("Bundled gems are installed into `./cache`") - expect(out).to_not include("Gems in the group") - expect(out).to include(bundle_complete_message) - end - end - - describe "with `path` configured to an absolute path outside the cwd" do - let(:bundle_path) { tmp("not_bundled_app") } - - it "shows proper messages according to the configured groups" do - bundle "config set --local path #{bundle_path}" - bundle :install - expect(out).to include("Bundled gems are installed into `#{tmp("not_bundled_app")}`") - expect(out).to_not include("Gems in the group") - expect(out).to include(bundle_complete_message) - end - end - - describe "with misspelled or non-existent gem name" do - before do - bundle "config set force_ruby_platform true" - end - - it "should report a helpful error message" do - install_gemfile <<-G, raise_on_error: false - source "https://gem.repo1" - gem "myrack" - gem "not-a-gem", :group => :development - G - expect(err).to include <<-EOS.strip -Could not find gem 'not-a-gem' in rubygems repository https://gem.repo1/ or installed locally. - EOS - end - - it "should report a helpful error message with reference to cache if available" do - install_gemfile <<-G - source "https://gem.repo1" - gem "myrack" - G - bundle :cache - expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist - install_gemfile <<-G, raise_on_error: false - source "https://gem.repo1" - gem "myrack" - gem "not-a-gem", :group => :development - G - expect(err).to include("Could not find gem 'not-a-gem' in"). - and include("or in gems cached in vendor/cache.") + expect(out).to include(installed_gems_stats) end end end - describe "for second bundle install run", bundler: "< 3" do - it "without any options" do - 2.times { bundle :install } - expect(out).to include(bundle_show_message) - expect(out).to_not include("Gems in the groups") + describe "with `path` configured" do + let(:bundle_path) { "./vendor" } + + it "shows proper messages according to the configured groups" do + bundle "config set --local path vendor" + bundle :install + expect(out).to include(bundle_show_path_message) + expect(out).to_not include("Gems in the group") expect(out).to include(bundle_complete_message) - expect(out).to include(installed_gems_stats) + + bundle "config set --local path vendor" + bundle "config set --local without emo" + bundle :install + expect(out).to include(bundle_show_path_message) + expect(out).to include("Gems in the group 'emo' were not installed") + expect(out).to include(bundle_complete_message) + + bundle "config set --local path vendor" + bundle "config set --local without emo test" + bundle :install + expect(out).to include(bundle_show_path_message) + expect(out).to include("Gems in the groups 'emo' and 'test' were not installed") + expect(out).to include(bundle_complete_message) + + bundle "config set --local path vendor" + bundle "config set --local without emo obama test" + bundle :install + expect(out).to include(bundle_show_path_message) + expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not installed") + expect(out).to include(bundle_complete_message) + end + end + + describe "with an absolute `path` inside the cwd configured" do + let(:bundle_path) { bundled_app("cache") } + + it "shows proper messages according to the configured groups" do + bundle "config set --local path #{bundle_path}" + bundle :install + expect(out).to include("Bundled gems are installed into `./cache`") + expect(out).to_not include("Gems in the group") + expect(out).to include(bundle_complete_message) + end + end + + describe "with `path` configured to an absolute path outside the cwd" do + let(:bundle_path) { tmp("not_bundled_app") } + + it "shows proper messages according to the configured groups" do + bundle "config set --local path #{bundle_path}" + bundle :install + expect(out).to include("Bundled gems are installed into `#{tmp("not_bundled_app")}`") + expect(out).to_not include("Gems in the group") + expect(out).to include(bundle_complete_message) + end + end + + describe "with misspelled or non-existent gem name" do + before do + bundle "config set force_ruby_platform true" end + it "should report a helpful error message" do + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo1" + gem "myrack" + gem "not-a-gem", :group => :development + G + expect(err).to include <<~EOS.strip + Could not find gem 'not-a-gem' in rubygems repository https://gem.repo1/ or installed locally. + EOS + end + + it "should report a helpful error message with reference to cache if available" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + bundle :cache + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo1" + gem "myrack" + gem "not-a-gem", :group => :development + G + expect(err).to include("Could not find gem 'not-a-gem' in"). + and include("or in gems cached in vendor/cache.") + end + end + + describe "for second bundle install run after first run using --without" do it "with --without one group" do bundle "install --without emo" bundle :install - expect(out).to include(bundle_show_message) + expect(out).to include(bundle_show_system_message) expect(out).to include("Gems in the group 'emo' were not installed") expect(out).to include(bundle_complete_message) expect(out).to include(installed_gems_stats) @@ -163,7 +168,7 @@ Could not find gem 'not-a-gem' in rubygems repository https://gem.repo1/ or inst it "with --without two groups" do bundle "install --without emo test" bundle :install - expect(out).to include(bundle_show_message) + expect(out).to include(bundle_show_system_message) expect(out).to include("Gems in the groups 'emo' and 'test' were not installed") expect(out).to include(bundle_complete_message) end @@ -171,7 +176,7 @@ Could not find gem 'not-a-gem' in rubygems repository https://gem.repo1/ or inst it "with --without more groups" do bundle "install --without emo obama test" bundle :install - expect(out).to include(bundle_show_message) + expect(out).to include(bundle_show_system_message) expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not installed") expect(out).to include(bundle_complete_message) end diff --git a/spec/bundler/commands/pristine_spec.rb b/spec/bundler/commands/pristine_spec.rb index 547aa12b6c..da61dc8199 100644 --- a/spec/bundler/commands/pristine_spec.rb +++ b/spec/bundler/commands/pristine_spec.rb @@ -49,13 +49,7 @@ RSpec.describe "bundle pristine" do bundle "pristine" bundle "-v" - expected = if Bundler::VERSION < "3.0" - "Bundler version" - else - Bundler::VERSION - end - - expect(out).to start_with(expected) + expect(out).to end_with(Bundler::VERSION) end end diff --git a/spec/bundler/commands/remove_spec.rb b/spec/bundler/commands/remove_spec.rb index 84505169ca..3e16346195 100644 --- a/spec/bundler/commands/remove_spec.rb +++ b/spec/bundler/commands/remove_spec.rb @@ -43,7 +43,7 @@ RSpec.describe "bundle remove" do end end - context "when --install flag is specified", bundler: "< 3" do + context "when --install flag is specified" do it "removes gems from .bundle" do gemfile <<-G source "https://gem.repo1" diff --git a/spec/bundler/commands/show_spec.rb b/spec/bundler/commands/show_spec.rb index 0ff9416757..82ec6e51f2 100644 --- a/spec/bundler/commands/show_spec.rb +++ b/spec/bundler/commands/show_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe "bundle show", bundler: "< 3" do +RSpec.describe "bundle show" do context "with a standard Gemfile" do before :each do install_gemfile <<-G @@ -219,6 +219,6 @@ RSpec.describe "bundle show", bundler: "< 3" do end end -RSpec.describe "bundle show", bundler: "3" do +RSpec.describe "bundle show", bundler: "4" do pending "shows a friendly error about the command removal" end diff --git a/spec/bundler/commands/ssl_spec.rb b/spec/bundler/commands/ssl_spec.rb index 01b8aa8f4d..b4aca55194 100644 --- a/spec/bundler/commands/ssl_spec.rb +++ b/spec/bundler/commands/ssl_spec.rb @@ -8,7 +8,7 @@ require "bundler/vendored_persistent.rb" RSpec.describe "bundle doctor ssl" do before(:each) do - require_rack + require_rack_test require_relative "../support/artifice/helpers/endpoint" @dummy_endpoint = Class.new(Endpoint) do diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb index cccf446561..22bb1662e6 100644 --- a/spec/bundler/commands/update_spec.rb +++ b/spec/bundler/commands/update_spec.rb @@ -772,7 +772,7 @@ RSpec.describe "bundle update" do G end - it "should fail loudly", bundler: "< 3" do + it "should fail loudly" do bundle "install --deployment" bundle "update", all: true, raise_on_error: false @@ -1036,7 +1036,7 @@ RSpec.describe "bundle update" do end end - context "with multiple, duplicated sources, with lockfile in old format", bundler: "< 3" do + context "with multiple, duplicated sources, with lockfile in old format" do before do build_repo2 do build_gem "dotenv", "2.7.6" @@ -1140,7 +1140,7 @@ RSpec.describe "bundle update in more complicated situations" do end bundle "update thin myrack-obama" - expect(last_command.stdboth).to include "Bundler attempted to update myrack-obama but its version stayed the same" + expect(stdboth).to include "Bundler attempted to update myrack-obama but its version stayed the same" expect(the_bundle).to include_gems "thin 2.0", "myrack 10.0", "myrack-obama 1.0" end @@ -1158,7 +1158,7 @@ RSpec.describe "bundle update in more complicated situations" do bundle "update foo" - expect(last_command.stdboth).not_to include "attempted to update" + expect(stdboth).not_to include "attempted to update" end it "will not warn when changing gem sources but not versions" do @@ -1176,7 +1176,7 @@ RSpec.describe "bundle update in more complicated situations" do bundle "update myrack" - expect(last_command.stdboth).not_to include "attempted to update" + expect(stdboth).not_to include "attempted to update" end it "will update only from pinned source" do @@ -1266,7 +1266,7 @@ RSpec.describe "bundle update in more complicated situations" do it "is not updated because it is not actually included in the bundle" do simulate_platform "x86_64-linux" do bundle "update a" - expect(last_command.stdboth).to include "Bundler attempted to update a but it was not considered because it is for a different platform from the current one" + expect(stdboth).to include "Bundler attempted to update a but it was not considered because it is for a different platform from the current one" expect(the_bundle).to_not include_gem "a" end end @@ -1307,7 +1307,7 @@ RSpec.describe "bundle update when a gem depends on a newer version of bundler" it "should explain that bundler conflicted and how to resolve the conflict" do bundle "update", all: true, raise_on_error: false - expect(last_command.stdboth).not_to match(/in snapshot/i) + expect(stdboth).not_to match(/in snapshot/i) expect(err).to match(/current Bundler version/i). and match(/Install the necessary version with `gem install bundler:9\.9\.9`/i) end @@ -1544,7 +1544,9 @@ RSpec.describe "bundle update --bundler" do end it "updates the bundler version in the lockfile even if the latest version is not installed", :ruby_repo do - pristine_system_gems "bundler-2.99.9" + bundle "config path.system true" + + pristine_system_gems "bundler-9.0.0" build_repo4 do build_gem "myrack", "1.0" @@ -1552,13 +1554,16 @@ RSpec.describe "bundle update --bundler" do build_bundler "999.0.0" end + checksums = checksums_section do |c| + c.checksum(gem_repo4, "myrack", "1.0") + end + install_gemfile <<-G source "https://gem.repo4" gem "myrack" G - lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.99.9") - bundle :update, bundler: true, verbose: true, preserve_ruby_flags: true + bundle :update, bundler: true, verbose: true expect(out).to include("Updating bundler to 999.0.0") expect(out).to include("Running `bundle update --bundler \"> 0.a\" --verbose` with bundler 999.0.0") @@ -1575,7 +1580,7 @@ RSpec.describe "bundle update --bundler" do DEPENDENCIES myrack - + #{checksums} BUNDLED WITH 999.0.0 L @@ -1684,8 +1689,8 @@ RSpec.describe "bundle update --bundler" do expect(err).to eq("The `bundle update --bundler` target version (999.999.999) does not exist") end - it "allows updating to development versions if already installed locally" do - system_gems "bundler-2.3.0.dev" + it "errors if the explicit target version does not exist, even if auto switching is disabled" do + pristine_system_gems "bundler-9.9.9" build_repo4 do build_gem "myrack", "1.0" @@ -1696,7 +1701,26 @@ RSpec.describe "bundle update --bundler" do gem "myrack" G - bundle :update, bundler: "2.3.0.dev", verbose: "true" + bundle :update, bundler: "999.999.999", raise_on_error: false, env: { "BUNDLER_VERSION" => "9.9.9" } + + expect(last_command).to be_failure + expect(err).to eq("The `bundle update --bundler` target version (999.999.999) does not exist") + end + + it "allows updating to development versions if already installed locally" do + system_gems "bundler-9.9.9" + + build_repo4 do + build_gem "myrack", "1.0" + end + + install_gemfile <<-G + source "https://gem.repo4" + gem "myrack" + G + + system_gems "bundler-9.0.0.dev", path: local_gem_path + bundle :update, bundler: "9.0.0.dev", verbose: "true" checksums = checksums_section_when_enabled do |c| c.checksum(gem_repo4, "myrack", "1.0") @@ -1715,14 +1739,14 @@ RSpec.describe "bundle update --bundler" do myrack #{checksums} BUNDLED WITH - 2.3.0.dev + 9.0.0.dev L - expect(out).to include("Using bundler 2.3.0.dev") + expect(out).to include("Using bundler 9.0.0.dev") end it "does not touch the network if not necessary" do - system_gems "bundler-2.3.9" + system_gems "bundler-9.9.9" build_repo4 do build_gem "myrack", "1.0" @@ -1732,8 +1756,8 @@ RSpec.describe "bundle update --bundler" do source "https://gem.repo4" gem "myrack" G - - bundle :update, bundler: "2.3.9", verbose: true + system_gems "bundler-9.0.0", path: local_gem_path + bundle :update, bundler: "9.0.0", verbose: true expect(out).not_to include("Fetching gem metadata from https://rubygems.org/") @@ -1755,14 +1779,14 @@ RSpec.describe "bundle update --bundler" do myrack #{checksums} BUNDLED WITH - 2.3.9 + 9.0.0 L - expect(out).to include("Using bundler 2.3.9") + expect(out).to include("Using bundler 9.0.0") end it "prints an error when trying to update bundler in frozen mode" do - system_gems "bundler-2.3.9" + system_gems "bundler-9.0.0" gemfile <<~G source "https://gem.repo2" @@ -1779,10 +1803,12 @@ RSpec.describe "bundle update --bundler" do DEPENDENCIES BUNDLED WITH - 2.1.4 + 9.0.0 L - bundle "update --bundler=2.3.9", env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false + system_gems "bundler-9.9.9", path: local_gem_path + + bundle "update --bundler=9.9.9", env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false expect(err).to include("An update to the version of bundler itself was requested, but the lockfile can't be updated because frozen mode is set") end end diff --git a/spec/bundler/commands/version_spec.rb b/spec/bundler/commands/version_spec.rb index 307058a5dd..1019803c87 100644 --- a/spec/bundler/commands/version_spec.rb +++ b/spec/bundler/commands/version_spec.rb @@ -10,38 +10,56 @@ RSpec.describe "bundle version" do end context "with -v" do - it "outputs the version", bundler: "< 3" do + it "outputs the version and virtual version if set" do bundle "-v" expect(out).to eq("Bundler version #{Bundler::VERSION}") - end - it "outputs the version", bundler: "3" do + bundle "config simulate_version 4" bundle "-v" - expect(out).to eq(Bundler::VERSION) + expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 4)") end end context "with --version" do - it "outputs the version", bundler: "< 3" do + it "outputs the version and virtual version if set" do bundle "--version" expect(out).to eq("Bundler version #{Bundler::VERSION}") - end - it "outputs the version", bundler: "3" do + bundle "config simulate_version 4" bundle "--version" - expect(out).to eq(Bundler::VERSION) + expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 4)") end end context "with version" do - it "outputs the version with build metadata", bundler: "< 3" do - bundle "version" - expect(out).to match(/\ABundler version #{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + context "when released", :ruby_repo do + before do + system_gems "bundler-2.9.9", released: true + end + + it "outputs the version, virtual version if set, and build metadata" do + bundle "version" + expect(out).to match(/\ABundler version 2\.9\.9 \(2100-01-01 commit #{COMMIT_HASH}\)\z/) + + bundle "config simulate_version 4" + bundle "version" + expect(out).to match(/\A2\.9\.9 \(simulating Bundler 4\) \(2100-01-01 commit #{COMMIT_HASH}\)\z/) + end end - it "outputs the version with build metadata", bundler: "3" do - bundle "version" - expect(out).to match(/\A#{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + context "when not released" do + before do + system_gems "bundler-2.9.9", released: false + end + + it "outputs the version, virtual version if set, and build metadata" do + bundle "version" + expect(out).to match(/\ABundler version 2\.9\.9 \(20\d{2}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + + bundle "config simulate_version 4" + bundle "version" + expect(out).to match(/\A2\.9\.9 \(simulating Bundler 4\) \(20\d{2}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + end end end end diff --git a/spec/bundler/commands/viz_spec.rb b/spec/bundler/commands/viz_spec.rb index 712ded4bc4..0ad285104c 100644 --- a/spec/bundler/commands/viz_spec.rb +++ b/spec/bundler/commands/viz_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -RSpec.describe "bundle viz", bundler: "< 3", if: Bundler.which("dot") do +RSpec.describe "bundle viz", if: Bundler.which("dot") do before do - realworld_system_gems "ruby-graphviz --version 1.2.5" + base_system_gems "rexml", "ruby-graphviz" end it "graphs gems from the Gemfile" do @@ -71,7 +71,7 @@ RSpec.describe "bundle viz", bundler: "< 3", if: Bundler.which("dot") do context "with another gem that has a graphviz file" do before do - build_repo4 do + update_repo4 do build_gem "graphviz", "999" do |s| s.write("lib/graphviz.rb", "abort 'wrong graphviz gem loaded'") end diff --git a/spec/bundler/install/allow_offline_install_spec.rb b/spec/bundler/install/allow_offline_install_spec.rb index 21b0568f7d..abe6009c08 100644 --- a/spec/bundler/install/allow_offline_install_spec.rb +++ b/spec/bundler/install/allow_offline_install_spec.rb @@ -43,7 +43,7 @@ RSpec.describe "bundle install with :allow_offline_install" do G bundle :update, artifice: "fail", all: true - expect(last_command.stdboth).to include "Using the cached data for the new index because of a network error" + expect(stdboth).to include "Using the cached data for the new index because of a network error" expect(the_bundle).to include_gems("myrack-obama 1.0", "myrack 1.0.0") end diff --git a/spec/bundler/install/deploy_spec.rb b/spec/bundler/install/deploy_spec.rb index 7db12c0d0b..4d378d4941 100644 --- a/spec/bundler/install/deploy_spec.rb +++ b/spec/bundler/install/deploy_spec.rb @@ -8,7 +8,7 @@ RSpec.describe "install in deployment or frozen mode" do G end - context "with CLI flags", bundler: "< 3" do + context "with CLI flags" do it "fails without a lockfile and says that --deployment requires a lock" do bundle "install --deployment", raise_on_error: false expect(err).to include("The --deployment flag requires a lockfile") diff --git a/spec/bundler/install/redownload_spec.rb b/spec/bundler/install/force_spec.rb similarity index 60% rename from spec/bundler/install/redownload_spec.rb rename to spec/bundler/install/force_spec.rb index b522e22bd5..e0f6fb6364 100644 --- a/spec/bundler/install/redownload_spec.rb +++ b/spec/bundler/install/force_spec.rb @@ -8,7 +8,7 @@ RSpec.describe "bundle install" do G end - shared_examples_for "an option to force redownloading gems" do + shared_examples_for "an option to force reinstalling gems" do it "re-installs installed gems" do myrack_lib = default_bundle_path("gems/myrack-1.0.0/lib/myrack.rb") @@ -57,35 +57,15 @@ RSpec.describe "bundle install" do end end - describe "with --force", bundler: 2 do - it_behaves_like "an option to force redownloading gems" do + describe "with --force" do + it_behaves_like "an option to force reinstalling gems" do let(:flag) { "force" } end - - it "shows a deprecation when single flag passed" do - bundle "install --force" - expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - - it "shows a deprecation when multiple flags passed" do - bundle "install --no-color --force" - expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end end describe "with --redownload" do - it_behaves_like "an option to force redownloading gems" do + it_behaves_like "an option to force reinstalling gems" do let(:flag) { "redownload" } end - - it "does not show a deprecation when single flag passed" do - bundle "install --redownload" - expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - - it "does not show a deprecation when single multiple flags passed" do - bundle "install --no-color --redownload" - expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end end end diff --git a/spec/bundler/install/gemfile/gemspec_spec.rb b/spec/bundler/install/gemfile/gemspec_spec.rb index 4e83b7e9c3..d9469e5ca8 100644 --- a/spec/bundler/install/gemfile/gemspec_spec.rb +++ b/spec/bundler/install/gemfile/gemspec_spec.rb @@ -192,7 +192,7 @@ RSpec.describe "bundle install from an existing gemspec" do install_gemfile <<-G, raise_on_error: false gemspec :path => '#{tmp("foo")}' G - expect(last_command.stdboth).not_to include("ahh") + expect(stdboth).not_to include("ahh") end it "allows the gemspec to activate other gems" do @@ -260,6 +260,25 @@ RSpec.describe "bundle install from an existing gemspec" do expect(out).to eq("WIN") end + it "does not make Gem.try_activate warn when local gem has extensions" do + build_lib("foo", path: tmp("foo")) do |s| + s.version = "1.0.0" + s.add_c_extension + end + build_repo2 + + install_gemfile <<-G + source "https://gem.repo2" + gemspec :path => '#{tmp("foo")}' + G + + expect(the_bundle).to include_gems "foo 1.0.0" + + run "Gem.try_activate('irb/lc/es/error.rb'); puts 'WIN'" + expect(out).to eq("WIN") + expect(err).to be_empty + end + it "handles downgrades" do build_lib "omg", "2.0", path: lib_path("omg") @@ -421,7 +440,7 @@ RSpec.describe "bundle install from an existing gemspec" do simulate_new_machine simulate_platform("jruby") { bundle "install" } expect(lockfile).to include("platform_specific (1.0-java)") - simulate_platform("x64-mingw32") { bundle "install" } + simulate_platform("x64-mingw-ucrt") { bundle "install" } end context "on ruby" do @@ -438,7 +457,7 @@ RSpec.describe "bundle install from an existing gemspec" do c.no_checksum "foo", "1.0" c.checksum gem_repo2, "platform_specific", "1.0" c.checksum gem_repo2, "platform_specific", "1.0", "java" - c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw32" + c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt" end expect(lockfile).to eq <<~L @@ -453,12 +472,12 @@ RSpec.describe "bundle install from an existing gemspec" do specs: platform_specific (1.0) platform_specific (1.0-java) - platform_specific (1.0-x64-mingw32) + platform_specific (1.0-x64-mingw-ucrt) PLATFORMS java ruby - x64-mingw32 + x64-mingw-ucrt DEPENDENCIES foo! @@ -479,7 +498,7 @@ RSpec.describe "bundle install from an existing gemspec" do c.no_checksum "foo", "1.0" c.checksum gem_repo2, "platform_specific", "1.0" c.checksum gem_repo2, "platform_specific", "1.0", "java" - c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw32" + c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt" end expect(lockfile).to eq <<~L @@ -493,12 +512,12 @@ RSpec.describe "bundle install from an existing gemspec" do specs: platform_specific (1.0) platform_specific (1.0-java) - platform_specific (1.0-x64-mingw32) + platform_specific (1.0-x64-mingw-ucrt) PLATFORMS java ruby - x64-mingw32 + x64-mingw-ucrt DEPENDENCIES foo! @@ -522,7 +541,7 @@ RSpec.describe "bundle install from an existing gemspec" do c.checksum gem_repo2, "indirect_platform_specific", "1.0" c.checksum gem_repo2, "platform_specific", "1.0" c.checksum gem_repo2, "platform_specific", "1.0", "java" - c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw32" + c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt" end expect(lockfile).to eq <<~L @@ -538,12 +557,12 @@ RSpec.describe "bundle install from an existing gemspec" do platform_specific platform_specific (1.0) platform_specific (1.0-java) - platform_specific (1.0-x64-mingw32) + platform_specific (1.0-x64-mingw-ucrt) PLATFORMS java ruby - x64-mingw32 + x64-mingw-ucrt DEPENDENCIES foo! @@ -596,14 +615,14 @@ RSpec.describe "bundle install from an existing gemspec" do before do build_lib("chef", path: tmp("chef")) do |s| s.version = "17.1.17" - s.write "chef-universal-mingw32.gemspec", build_spec("chef", "17.1.17", "universal-mingw32") {|sw| sw.runtime "win32-api", "~> 1.5.3" }.first.to_ruby + s.write "chef-universal-mingw-ucrt.gemspec", build_spec("chef", "17.1.17", "universal-mingw-ucrt") {|sw| sw.runtime "win32-api", "~> 1.5.3" }.first.to_ruby end end it "does not remove the platform specific specs from the lockfile when updating" do build_repo4 do build_gem "win32-api", "1.5.3" do |s| - s.platform = "universal-mingw32" + s.platform = "universal-mingw-ucrt" end end @@ -614,8 +633,8 @@ RSpec.describe "bundle install from an existing gemspec" do checksums = checksums_section_when_enabled do |c| c.no_checksum "chef", "17.1.17" - c.no_checksum "chef", "17.1.17", "universal-mingw32" - c.checksum gem_repo4, "win32-api", "1.5.3", "universal-mingw32" + c.no_checksum "chef", "17.1.17", "universal-mingw-ucrt" + c.checksum gem_repo4, "win32-api", "1.5.3", "universal-mingw-ucrt" end initial_lockfile = <<~L @@ -623,16 +642,16 @@ RSpec.describe "bundle install from an existing gemspec" do remote: ../chef specs: chef (17.1.17) - chef (17.1.17-universal-mingw32) + chef (17.1.17-universal-mingw-ucrt) win32-api (~> 1.5.3) GEM remote: https://gem.repo4/ specs: - win32-api (1.5.3-universal-mingw32) + win32-api (1.5.3-universal-mingw-ucrt) PLATFORMS - #{lockfile_platforms("ruby", "x64-mingw32", "x86-mingw32")} + #{lockfile_platforms("ruby", "x64-mingw-ucrt", "x86-mingw32")} DEPENDENCIES chef! diff --git a/spec/bundler/install/gemfile/git_spec.rb b/spec/bundler/install/gemfile/git_spec.rb index c763da4c00..9104af1679 100644 --- a/spec/bundler/install/gemfile/git_spec.rb +++ b/spec/bundler/install/gemfile/git_spec.rb @@ -26,7 +26,7 @@ RSpec.describe "bundle install with git sources" do expect(out).to eq("WIN") end - it "caches the git repo", bundler: "< 3" do + it "caches the git repo" do expect(Dir["#{default_bundle_path}/cache/bundler/git/foo-1.0-*"]).to have_attributes size: 1 end @@ -1151,6 +1151,30 @@ RSpec.describe "bundle install with git sources" do expect(the_bundle).to include_gem "rails 7.1.4", "activesupport 7.1.4" end + + it "doesn't explode when adding an explicit ref to a git gem with dependencies" do + lib_root = lib_path("rails") + + build_lib "activesupport", "7.1.4", path: lib_root.join("activesupport") + build_git "rails", "7.1.4", path: lib_root do |s| + s.add_dependency "activesupport", "= 7.1.4" + end + + old_revision = revision_for(lib_root) + update_git "rails", "7.1.4", path: lib_root + + install_gemfile <<-G + source "https://gem.repo1" + gem "rails", "7.1.4", :git => "#{lib_root}" + G + + install_gemfile <<-G + source "https://gem.repo1" + gem "rails", :git => "#{lib_root}", :ref => "#{old_revision}" + G + + expect(the_bundle).to include_gem "rails 7.1.4", "activesupport 7.1.4" + end end describe "bundle install after the remote has been updated" do @@ -1649,7 +1673,7 @@ In Gemfile: end G - expect(last_command.stdboth).to_not include("password1") + expect(stdboth).to_not include("password1") expect(out).to include("Fetching https://user1@github.com/company/private-repo") end end @@ -1665,7 +1689,7 @@ In Gemfile: end G - expect(last_command.stdboth).to_not include("oauth_token") + expect(stdboth).to_not include("oauth_token") expect(out).to include("Fetching https://x-oauth-basic@github.com/company/private-repo") end end diff --git a/spec/bundler/install/gemfile/groups_spec.rb b/spec/bundler/install/gemfile/groups_spec.rb index c5f50a8c5d..52a3471578 100644 --- a/spec/bundler/install/gemfile/groups_spec.rb +++ b/spec/bundler/install/gemfile/groups_spec.rb @@ -100,7 +100,7 @@ RSpec.describe "bundle install with groups" do expect(out).to include("Set for the current user (#{home(".bundle/config")}): [:emo]") end - it "allows running application where groups where configured by a different user", bundler: "< 3" do + it "allows running application where groups where configured by a different user" do bundle "config set without emo" bundle :install bundle "exec ruby -e 'puts 42'", env: { "BUNDLE_USER_HOME" => tmp("new_home").to_s } @@ -113,7 +113,7 @@ RSpec.describe "bundle install with groups" do expect(the_bundle).not_to include_gems "activesupport 2.3.5", groups: [:default] end - it "remembers previous exclusion with `--without`", bundler: "< 3" do + it "remembers previous exclusion with `--without`" do bundle "install --without emo" expect(the_bundle).not_to include_gems "activesupport 2.3.5" bundle :install @@ -159,14 +159,14 @@ RSpec.describe "bundle install with groups" do ENV["BUNDLE_WITHOUT"] = nil end - it "clears --without when passed an empty list", bundler: "< 3" do + it "clears --without when passed an empty list" do bundle "install --without emo" bundle "install --without ''" expect(the_bundle).to include_gems "activesupport 2.3.5" end - it "doesn't clear without when nothing is passed", bundler: "< 3" do + it "doesn't clear without when nothing is passed" do bundle "install --without emo" bundle :install @@ -184,7 +184,7 @@ RSpec.describe "bundle install with groups" do expect(the_bundle).to include_gems "thin 1.0" end - it "installs gems from the previously requested group", bundler: "< 3" do + it "installs gems from the previously requested group" do bundle "install --with debugging" expect(the_bundle).to include_gems "thin 1.0" bundle :install @@ -198,25 +198,25 @@ RSpec.describe "bundle install with groups" do ENV["BUNDLE_WITH"] = nil end - it "clears --with when passed an empty list", bundler: "< 3" do + it "clears --with when passed an empty list" do bundle "install --with debugging" bundle "install --with ''" expect(the_bundle).not_to include_gems "thin 1.0" end - it "removes groups from without when passed at --with", bundler: "< 3" do + it "removes groups from without when passed at --with" do bundle "config set --local without emo" bundle "install --with emo" expect(the_bundle).to include_gems "activesupport 2.3.5" end - it "removes groups from with when passed at --without", bundler: "< 3" do + it "removes groups from with when passed at --without" do bundle "config set --local with debugging" bundle "install --without debugging", raise_on_error: false expect(the_bundle).not_to include_gem "thin 1.0" end - it "errors out when passing a group to with and without via CLI flags", bundler: "< 3" do + it "errors out when passing a group to with and without via CLI flags" do bundle "install --with emo debugging --without emo", raise_on_error: false expect(last_command).to be_failure expect(err).to include("The offending groups are: emo") @@ -235,7 +235,7 @@ RSpec.describe "bundle install with groups" do expect(the_bundle).to include_gem "thin 1.0" end - it "can add and remove a group at the same time", bundler: "< 3" do + it "can add and remove a group at the same time" do bundle "install --with debugging --without emo" expect(the_bundle).to include_gems "thin 1.0" expect(the_bundle).not_to include_gems "activesupport 2.3.5" @@ -397,7 +397,7 @@ RSpec.describe "bundle install with groups" do FileUtils.rm_r gem_repo2 bundle "config set --local without myrack" bundle :install, verbose: true - expect(last_command.stdboth).not_to match(/fetching/i) + expect(stdboth).not_to match(/fetching/i) end end end diff --git a/spec/bundler/install/gemfile/path_spec.rb b/spec/bundler/install/gemfile/path_spec.rb index 7525404b24..55bf11928d 100644 --- a/spec/bundler/install/gemfile/path_spec.rb +++ b/spec/bundler/install/gemfile/path_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe "bundle install with explicit source paths" do - it "fetches gems with a global path source", bundler: "< 3" do + it "fetches gems with a global path source" do build_lib "foo" install_gemfile <<-G @@ -16,7 +16,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo" install_gemfile <<-G - source "https://gem.repo1" path "#{lib_path("foo-1.0")}" do gem 'foo' end @@ -29,7 +28,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo" install_gemfile <<-G - source "https://gem.repo1" gem 'foo', :path => "#{lib_path("foo-1.0")}" G @@ -42,7 +40,6 @@ RSpec.describe "bundle install with explicit source paths" do relative_path = lib_path("foo-1.0").relative_path_from(bundled_app) install_gemfile <<-G - source "https://gem.repo1" gem 'foo', :path => "#{relative_path}" G @@ -55,7 +52,6 @@ RSpec.describe "bundle install with explicit source paths" do relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new("~").expand_path) install_gemfile <<-G - source "https://gem.repo1" gem 'foo', :path => "~/#{relative_path}" G @@ -70,7 +66,6 @@ RSpec.describe "bundle install with explicit source paths" do relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new("/home/#{username}").expand_path) install_gemfile <<-G, raise_on_error: false - source "https://gem.repo1" gem 'foo', :path => "~#{username}/#{relative_path}" G expect(err).to match("There was an error while trying to use the path `~#{username}/#{relative_path}`.") @@ -81,7 +76,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo", path: bundled_app("foo-1.0") install_gemfile <<-G - source "https://gem.repo1" gem 'foo', :path => "./foo-1.0" G @@ -139,7 +133,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo", path: bundled_app("foo-1.0") install_gemfile <<-G - source "https://gem.repo1" gem 'foo', :path => File.expand_path("foo-1.0", __dir__) G @@ -159,7 +152,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "https://gem.repo1" gem "foo", :path => "#{lib_path("nested")}" G @@ -179,7 +171,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo", "1.0.0", path: lib_path("omg/foo") install_gemfile <<-G - source "https://gem.repo1" gem "omg", :path => "#{lib_path("omg")}" G @@ -256,7 +247,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "omg", "2.0", path: lib_path("omg") install_gemfile <<-G - source "https://gem.repo1" gem "omg", :path => "#{lib_path("omg")}" G @@ -280,7 +270,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "https://gem.repo1" gem "premailer", :path => "#{lib_path("premailer")}" G @@ -302,11 +291,9 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G, raise_on_error: false - source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo-1.0")}" G - expect(err).to_not include("Your Gemfile has no gem server sources.") expect(err).to match(/is not valid. Please fix this gemspec./) expect(err).to match(/The validation error was 'missing value for attribute version'/) expect(err).to match(/You have one or more invalid gemspecs that need to be fixed/) @@ -440,7 +427,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G, raise_on_error: false - source "https://gem.repo1" gemspec :path => "#{lib_path("foo")}" G @@ -454,7 +440,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "https://gem.repo1" gemspec :path => "#{lib_path("foo")}", :name => "foo" G @@ -467,7 +452,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G, verbose: true - source "https://gem.repo1" path "#{lib_path("foo-1.0")}" do gem 'foo' end @@ -485,7 +469,6 @@ RSpec.describe "bundle install with explicit source paths" do lib_path("foo-1.0").join("bin/performance").mkpath install_gemfile <<-G - source "https://gem.repo1" gem 'foo', '1.0', :path => "#{lib_path("foo-1.0")}" G expect(err).to be_empty @@ -495,7 +478,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo" install_gemfile <<-G - source "https://gem.repo1" gem 'foo', :path => "#{lib_path("foo-1.0")}" G @@ -508,7 +490,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "hi2u" install_gemfile <<-G - source "https://gem.repo1" path "#{lib_path}" do gem "omg" gem "hi2u" @@ -527,7 +508,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo")}" gem "omg", :path => "#{lib_path("omg")}" G @@ -539,7 +519,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo", gemspec: false gemfile <<-G - source "https://gem.repo1" gem "foo", "1.0", :path => "#{lib_path("foo-1.0")}" G @@ -553,15 +532,11 @@ RSpec.describe "bundle install with explicit source paths" do PATH remote: vendor/bar specs: - - GEM - remote: http://rubygems.org/ L FileUtils.mkdir_p(bundled_app("vendor/bar")) install_gemfile <<-G - source "http://rubygems.org" gem "bar", "1.0.0", path: "vendor/bar", require: "bar/nyard" G end @@ -606,7 +581,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo-1.0")}" G @@ -622,7 +596,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "bar", "1.0", path: lib_path("foo/bar") install_gemfile <<-G - source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo")}" G end @@ -869,12 +842,10 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "https://gem.repo1" gem "bar", :git => "#{lib_path("bar")}" G install_gemfile <<-G - source "https://gem.repo1" gem "bar", :path => "#{lib_path("bar")}" G @@ -929,7 +900,6 @@ RSpec.describe "bundle install with explicit source paths" do it "runs pre-install hooks" do build_git "foo" gemfile <<-G - source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -949,7 +919,6 @@ RSpec.describe "bundle install with explicit source paths" do it "runs post-install hooks" do build_git "foo" gemfile <<-G - source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -969,7 +938,6 @@ RSpec.describe "bundle install with explicit source paths" do it "complains if the install hook fails" do build_git "foo" gemfile <<-G - source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1000,7 +968,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo-1.0")}" gem "bar", :path => "#{lib_path("bar-1.0")}" G diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb index e1a5245800..406d881541 100644 --- a/spec/bundler/install/gemfile/sources_spec.rb +++ b/spec/bundler/install/gemfile/sources_spec.rb @@ -27,7 +27,7 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "refuses to install mismatched checksum because one gem has been tampered with", bundler: "< 3" do + it "refuses to install mismatched checksum because one gem has been tampered with" do lockfile <<~L GEM remote: https://gem.repo3/ @@ -71,7 +71,7 @@ RSpec.describe "bundle install with gems on multiple sources" do bundle "config set --local disable_checksum_validation true" end - it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", bundler: "< 3" do + it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first" do bundle :install, artifice: "compact_index" expect(err).to include("Warning: the gem 'myrack' was found in multiple sources.") @@ -79,7 +79,7 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(the_bundle).to include_gems("myrack-obama 1.0.0", "myrack 1.0.0", source: "remote1") end - it "does not use the full index unnecessarily", bundler: "< 3" do + it "does not use the full index unnecessarily" do bundle :install, artifice: "compact_index", verbose: true expect(out).to include("https://gem.repo1/versions") @@ -88,7 +88,7 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(out).not_to include("https://gem.repo3/quick/Marshal.4.8/") end - it "fails", bundler: "3" do + it "fails", bundler: "4" do bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Each source after the first must include a block") expect(exitstatus).to eq(4) @@ -108,14 +108,14 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "warns about ambiguous gems, but installs anyway", bundler: "< 3" do + it "warns about ambiguous gems, but installs anyway" do bundle :install, artifice: "compact_index" expect(err).to include("Warning: the gem 'myrack' was found in multiple sources.") expect(err).to include("Installed from: https://gem.repo1") expect(the_bundle).to include_gems("myrack-obama 1.0.0", "myrack 1.0.0", source: "remote1") end - it "fails", bundler: "3" do + it "fails", bundler: "4" do bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Each source after the first must include a block") expect(exitstatus).to eq(4) @@ -145,7 +145,7 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "works in standalone mode", bundler: "< 3" do + it "works in standalone mode" do gem_checksum = checksum_digest(gem_repo4, "foo", "1.0") bundle "install --standalone", artifice: "compact_index", env: { "BUNDLER_SPEC_FOO_CHECKSUM" => gem_checksum } end @@ -325,7 +325,7 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "fails when the two sources don't have the same checksum", bundler: "< 3" do + it "fails when the two sources don't have the same checksum" do bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to eq(<<~E.strip) @@ -347,7 +347,7 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(exitstatus).to eq(37) end - it "fails when the two sources agree, but the local gem calculates a different checksum", bundler: "< 3" do + it "fails when the two sources agree, but the local gem calculates a different checksum" do myrack_checksum = "c0ffee11" * 8 bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_MYRACK_CHECKSUM" => myrack_checksum }, raise_on_error: false @@ -370,7 +370,7 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(exitstatus).to eq(37) end - it "installs from the other source and warns about ambiguous gems when the sources have the same checksum", bundler: "< 3" do + it "installs from the other source and warns about ambiguous gems when the sources have the same checksum" do gem_checksum = checksum_digest(gem_repo2, "myrack", "1.0.0") bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_MYRACK_CHECKSUM" => gem_checksum, "DEBUG" => "1" } @@ -410,7 +410,7 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(lockfile).to eq(previous_lockfile) end - it "installs from the other source and warns about ambiguous gems when checksum validation is disabled", bundler: "< 3" do + it "installs from the other source and warns about ambiguous gems when checksum validation is disabled" do bundle "config set --local disable_checksum_validation true" bundle :install, artifice: "compact_index" @@ -450,7 +450,7 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(lockfile).to eq(previous_lockfile) end - it "fails", bundler: "3" do + it "fails", bundler: "4" do bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Each source after the first must include a block") expect(exitstatus).to eq(4) @@ -475,7 +475,7 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "installs the dependency from the pinned source without warning", bundler: "< 3" do + it "installs the dependency from the pinned source without warning" do bundle :install, artifice: "compact_index" expect(err).not_to include("Warning: the gem 'myrack' was found in multiple sources.") @@ -490,7 +490,7 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0") end - it "fails", bundler: "3" do + it "fails", bundler: "4" do bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Each source after the first must include a block") expect(exitstatus).to eq(4) @@ -524,7 +524,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end end - context "when an indirect dependency can't be found in the aggregate rubygems source", bundler: "< 3" do + context "when an indirect dependency can't be found in the aggregate rubygems source" do before do build_repo2 @@ -896,7 +896,7 @@ RSpec.describe "bundle install with gems on multiple sources" do L end - it "does not install newer versions or generate lockfile changes when running bundle install in frozen mode, and warns", bundler: "< 3" do + it "does not install newer versions or generate lockfile changes when running bundle install in frozen mode, and warns" do initial_lockfile = lockfile bundle "config set --local frozen true" @@ -914,7 +914,7 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(lockfile).to eq(initial_lockfile) end - it "fails when running bundle install in frozen mode", bundler: "3" do + it "fails when running bundle install in frozen mode", bundler: "4" do initial_lockfile = lockfile bundle "config set --local frozen true" @@ -1264,7 +1264,7 @@ RSpec.describe "bundle install with gems on multiple sources" do lockfile aggregate_gem_section_lockfile end - it "installs the existing lockfile but prints a warning when checksum validation is disabled", bundler: "< 3" do + it "installs the existing lockfile but prints a warning when checksum validation is disabled" do bundle "config set --local deployment true" bundle "config set --local disable_checksum_validation true" @@ -1275,7 +1275,7 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(the_bundle).to include_gems("myrack 0.9.1", source: "remote3") end - it "prints a checksum warning when the checksums from both sources do not match", bundler: "< 3" do + it "prints a checksum warning when the checksums from both sources do not match" do bundle "config set --local deployment true" bundle "install", artifice: "compact_index", raise_on_error: false @@ -1302,7 +1302,7 @@ RSpec.describe "bundle install with gems on multiple sources" do E end - it "refuses to install the existing lockfile and prints an error", bundler: "3" do + it "refuses to install the existing lockfile and prints an error", bundler: "4" do bundle "config set --local deployment true" bundle "install", artifice: "compact_index", raise_on_error: false @@ -1583,7 +1583,7 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(err).to include("Could not reach host gem.repo4. Check your network connection and try again.") end - context "when an indirect dependency is available from multiple ambiguous sources", bundler: "< 3" do + context "when an indirect dependency is available from multiple ambiguous sources" do it "succeeds but warns, suggesting a source block" do build_repo4 do build_gem "depends_on_myrack" do |s| @@ -1614,7 +1614,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end end - context "when an indirect dependency is available from multiple ambiguous sources", bundler: "3" do + context "when an indirect dependency is available from multiple ambiguous sources", bundler: "4" do it "raises, suggesting a source block" do build_repo4 do build_gem "depends_on_myrack" do |s| diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb index 7f147adcb0..228ae7b0d0 100644 --- a/spec/bundler/install/gemfile/specific_platform_spec.rb +++ b/spec/bundler/install/gemfile/specific_platform_spec.rb @@ -367,12 +367,12 @@ RSpec.describe "bundle install with specific platforms" do simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem install_gemfile(google_protobuf) - bundle "lock --add-platform=x64-mingw32" + bundle "lock --add-platform=x64-mingw-ucrt" - expect(the_bundle.locked_platforms).to include("x64-mingw32", "universal-darwin") + expect(the_bundle.locked_platforms).to include("x64-mingw-ucrt", "universal-darwin") expect(the_bundle.locked_gems.specs.map(&:full_name)).to include(*%w[ google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin - google-protobuf-3.0.0.alpha.5.0.5.1-x64-mingw32 + google-protobuf-3.0.0.alpha.5.0.5.1-x64-mingw-ucrt ]) end end @@ -1328,7 +1328,7 @@ RSpec.describe "bundle install with specific platforms" do s.platform = "arm-linux" end build_gem "nokogiri", "1.14.0" do |s| - s.platform = "x64-mingw32" + s.platform = "x64-mingw-ucrt" end build_gem "nokogiri", "1.14.0" do |s| s.platform = "java" @@ -1823,11 +1823,11 @@ RSpec.describe "bundle install with specific platforms" do build_repo2 do build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x86_64-linux" } - build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x64-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x64-mingw-ucrt" } build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "universal-darwin" } build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x86_64-linux" } - build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x64-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x64-mingw-ucrt" } build_gem("google-protobuf", "3.0.0.alpha.5.0.5") build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "universal-darwin" } diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb index 2cf9b19e41..823b5aab11 100644 --- a/spec/bundler/install/gems/compact_index_spec.rb +++ b/spec/bundler/install/gems/compact_index_spec.rb @@ -313,10 +313,10 @@ RSpec.describe "compact index api" do gem "myrack" G - expect(last_command.stdboth).not_to include "Double checking" + expect(stdboth).not_to include "Double checking" end - it "fetches again when more dependencies are found in subsequent sources", bundler: "< 3" do + it "fetches again when more dependencies are found in subsequent sources" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -375,7 +375,7 @@ RSpec.describe "compact index api" do expect(the_bundle).to include_gems "myrack 1.2" end - it "considers all possible versions of dependencies from all api gem sources", bundler: "< 3" do + it "considers all possible versions of dependencies from all api gem sources" do # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0 # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other @@ -471,7 +471,7 @@ RSpec.describe "compact index api" do expect(the_bundle).to include_gems "foo 1.0" end - it "fetches again when more dependencies are found in subsequent sources using deployment mode", bundler: "< 3" do + it "fetches again when more dependencies are found in subsequent sources using deployment mode" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -529,7 +529,7 @@ RSpec.describe "compact index api" do expect(out).to include("Fetching gem metadata from #{source_uri}") end - it "installs the binstubs", bundler: "< 3" do + it "installs the binstubs" do gemfile <<-G source "#{source_uri}" gem "myrack" @@ -541,7 +541,7 @@ RSpec.describe "compact index api" do expect(out).to eq("1.0.0") end - it "installs the bins when using --path and uses autoclean", bundler: "< 3" do + it "installs the bins when using --path and uses autoclean" do gemfile <<-G source "#{source_uri}" gem "myrack" @@ -552,7 +552,7 @@ RSpec.describe "compact index api" do expect(vendored_gems("bin/myrackup")).to exist end - it "installs the bins when using --path and uses bundle clean", bundler: "< 3" do + it "installs the bins when using --path and uses bundle clean" do gemfile <<-G source "#{source_uri}" gem "myrack" @@ -617,7 +617,7 @@ RSpec.describe "compact index api" do expect(the_bundle).to include_gems "myrack 1.0.0" end - it "strips http basic auth creds when warning about ambiguous sources", bundler: "< 3" do + it "strips http basic auth creds when warning about ambiguous sources" do gemfile <<-G source "#{basic_auth_source_uri}" source "#{file_uri_for(gem_repo1)}" @@ -742,7 +742,7 @@ RSpec.describe "compact index api" do gem "myrack" G - bundle :install, env: { "RUBYOPT" => opt_add("-I#{bundled_app("broken_ssl")}", ENV["RUBYOPT"]) }, raise_on_error: false, artifice: nil + bundle :install, env: { "RUBYOPT" => "-I#{bundled_app("broken_ssl")}" }, raise_on_error: false, artifice: nil expect(err).to include("recompile Ruby").and include("cannot load such file") end end diff --git a/spec/bundler/install/gems/dependency_api_fallback_spec.rb b/spec/bundler/install/gems/dependency_api_fallback_spec.rb index 42239311e2..7890cbdb99 100644 --- a/spec/bundler/install/gems/dependency_api_fallback_spec.rb +++ b/spec/bundler/install/gems/dependency_api_fallback_spec.rb @@ -3,44 +3,16 @@ RSpec.describe "gemcutter's dependency API" do context "when Gemcutter API takes too long to respond" do before do - require_rack - - port = find_unused_port - @server_uri = "http://127.0.0.1:#{port}" - - require_relative "../../support/artifice/endpoint_timeout" - require_relative "../../support/silent_logger" - - require "rackup/server" - - @t = Thread.new do - server = Rackup::Server.start(app: EndpointTimeout, - Host: "0.0.0.0", - Port: port, - server: "webrick", - AccessLog: [], - Logger: Spec::SilentLogger.new) - server.start - end - @t.run - - wait_for_server("127.0.0.1", port) bundle "config set timeout 1" end - after do - Artifice.deactivate - @t.kill - @t.join - end - it "times out and falls back on the modern index" do - install_gemfile <<-G, artifice: nil, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } - source "#{@server_uri}" + install_gemfile <<-G, artifice: "endpoint_timeout" + source "https://gem.repo1" gem "myrack" G - expect(out).to include("Fetching source index from #{@server_uri}/") + expect(out).to include("Fetching source index from https://gem.repo1/") expect(the_bundle).to include_gems "myrack 1.0.0" end end diff --git a/spec/bundler/install/gems/dependency_api_spec.rb b/spec/bundler/install/gems/dependency_api_spec.rb index 283f1208f2..b7953b3d61 100644 --- a/spec/bundler/install/gems/dependency_api_spec.rb +++ b/spec/bundler/install/gems/dependency_api_spec.rb @@ -254,7 +254,7 @@ RSpec.describe "gemcutter's dependency API" do end end - it "fetches again when more dependencies are found in subsequent sources", bundler: "< 3" do + it "fetches again when more dependencies are found in subsequent sources" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -313,7 +313,7 @@ RSpec.describe "gemcutter's dependency API" do expect(the_bundle).to include_gems "myrack 1.2" end - it "considers all possible versions of dependencies from all api gem sources", bundler: "< 3" do + it "considers all possible versions of dependencies from all api gem sources" do # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0 # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other @@ -358,7 +358,7 @@ RSpec.describe "gemcutter's dependency API" do expect(out).to include("Fetching source index from http://localgemserver.test/extra") end - it "does not fetch every spec when doing back deps", bundler: "< 3" do + it "does not fetch every spec when doing back deps" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -397,7 +397,7 @@ RSpec.describe "gemcutter's dependency API" do expect(the_bundle).to include_gems "back_deps 1.0" end - it "fetches again when more dependencies are found in subsequent sources using deployment mode", bundler: "< 3" do + it "fetches again when more dependencies are found in subsequent sources using deployment mode" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -471,7 +471,7 @@ RSpec.describe "gemcutter's dependency API" do expect(out).to include("Fetching gem metadata from #{source_uri}") end - it "installs the binstubs", bundler: "< 3" do + it "installs the binstubs" do gemfile <<-G source "#{source_uri}" gem "myrack" @@ -483,7 +483,7 @@ RSpec.describe "gemcutter's dependency API" do expect(out).to eq("1.0.0") end - it "installs the bins when using --path and uses autoclean", bundler: "< 3" do + it "installs the bins when using --path and uses autoclean" do gemfile <<-G source "#{source_uri}" gem "myrack" @@ -494,7 +494,7 @@ RSpec.describe "gemcutter's dependency API" do expect(vendored_gems("bin/myrackup")).to exist end - it "installs the bins when using --path and uses bundle clean", bundler: "< 3" do + it "installs the bins when using --path and uses bundle clean" do gemfile <<-G source "#{source_uri}" gem "myrack" @@ -580,7 +580,7 @@ RSpec.describe "gemcutter's dependency API" do expect(out).not_to include("#{user}:#{password}") end - it "strips http basic auth creds when warning about ambiguous sources", bundler: "< 3" do + it "strips http basic auth creds when warning about ambiguous sources" do gemfile <<-G source "#{basic_auth_source_uri}" source "#{file_uri_for(gem_repo1)}" @@ -712,7 +712,7 @@ RSpec.describe "gemcutter's dependency API" do gem "myrack" G - bundle :install, artifice: "fail", env: { "RUBYOPT" => opt_add("-I#{bundled_app("broken_ssl")}", ENV["RUBYOPT"]) }, raise_on_error: false + bundle :install, artifice: "fail", env: { "RUBYOPT" => "-I#{bundled_app("broken_ssl")}" }, raise_on_error: false expect(err).to include("recompile Ruby").and include("cannot load such file") end end diff --git a/spec/bundler/install/gems/gemfile_source_header_spec.rb b/spec/bundler/install/gems/gemfile_source_header_spec.rb new file mode 100644 index 0000000000..9e63fa7551 --- /dev/null +++ b/spec/bundler/install/gems/gemfile_source_header_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +RSpec.describe "fetching dependencies with a mirrored source" do + let(:mirror) { "https://server.example.org" } + + before do + build_repo2 + + gemfile <<-G + source "#{mirror}" + gem 'weakling' + G + + bundle "config set --local mirror.#{mirror} https://gem.repo2" + end + + it "sets the 'X-Gemfile-Source' and 'User-Agent' headers and bundles successfully" do + bundle :install, artifice: "endpoint_mirror_source" + + expect(out).to include("Installing weakling") + expect(out).to include("Bundle complete") + expect(the_bundle).to include_gems "weakling 0.0.3" + end +end diff --git a/spec/bundler/install/gems/mirror_probe_spec.rb b/spec/bundler/install/gems/mirror_probe_spec.rb new file mode 100644 index 0000000000..436f116cac --- /dev/null +++ b/spec/bundler/install/gems/mirror_probe_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +RSpec.describe "fetching dependencies with a not available mirror" do + before do + build_repo2 + + gemfile <<-G + source "https://gem.repo2" + gem 'weakling' + G + end + + context "with a specific fallback timeout" do + before do + global_config("BUNDLE_MIRROR__HTTPS://GEM__REPO2/__FALLBACK_TIMEOUT/" => "true", + "BUNDLE_MIRROR__HTTPS://GEM__REPO2/" => "https://gem.mirror") + end + + it "install a gem using the original uri when the mirror is not responding" do + bundle :install, env: { "BUNDLER_SPEC_FAKE_RESOLVE" => "gem.mirror" }, verbose: true + + expect(out).to include("Installing weakling") + expect(out).to include("Bundle complete") + expect(the_bundle).to include_gems "weakling 0.0.3" + end + end + + context "with a global fallback timeout" do + before do + global_config("BUNDLE_MIRROR__ALL__FALLBACK_TIMEOUT/" => "1", + "BUNDLE_MIRROR__ALL" => "https://gem.mirror") + end + + it "install a gem using the original uri when the mirror is not responding" do + bundle :install, env: { "BUNDLER_SPEC_FAKE_RESOLVE" => "gem.mirror" } + + expect(out).to include("Installing weakling") + expect(out).to include("Bundle complete") + expect(the_bundle).to include_gems "weakling 0.0.3" + end + end + + context "with a specific mirror without a fallback timeout" do + before do + global_config("BUNDLE_MIRROR__HTTPS://GEM__REPO2/" => "https://gem.mirror") + end + + it "fails to install the gem with a timeout error when the mirror is not responding" do + bundle :install, artifice: "compact_index_mirror_down", raise_on_error: false + + expect(out).to be_empty + expect(err).to eq("Could not reach host gem.mirror. Check your network connection and try again.") + end + end + + context "with a global mirror without a fallback timeout" do + before do + global_config("BUNDLE_MIRROR__ALL" => "https://gem.mirror") + end + + it "fails to install the gem with a timeout error when the mirror is not responding" do + bundle :install, artifice: "compact_index_mirror_down", raise_on_error: false + + expect(out).to be_empty + expect(err).to eq("Could not reach host gem.mirror. Check your network connection and try again.") + end + end +end diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb index 50ef4dc3a7..8073559479 100644 --- a/spec/bundler/install/gems/standalone_spec.rb +++ b/spec/bundler/install/gems/standalone_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples "bundle install --standalone" do +RSpec.describe "bundle install --standalone" do shared_examples "common functionality" do it "still makes the gems available to normal bundler" do args = expected_gems.map {|k, v| "#{k} #{v}" } @@ -140,15 +140,8 @@ RSpec.shared_examples "bundle install --standalone" do end describe "with default gems and a lockfile", :ruby_repo do - before do - necessary_system_gems = ["tsort --version 0.1.0"] - realworld_system_gems(*necessary_system_gems) - end - it "works and points to the vendored copies, not to the default copies" do - necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.4.3", "stringio --version 3.1.0"] - necessary_gems_in_bundle_path += ["yaml --version 0.1.1"] if Gem.rubygems_version < Gem::Version.new("3.4.a") - realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) + base_system_gems "stringio", "psych", "etc", path: scoped_gem_path(bundled_app("bundle")) build_gem "foo", "1.0.0", to_system: true, default: true do |s| s.add_dependency "bar" @@ -185,9 +178,7 @@ RSpec.shared_examples "bundle install --standalone" do it "works for gems with extensions and points to the vendored copies, not to the default copies" do simulate_platform "arm64-darwin-23" do - necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.4.3", "stringio --version 3.1.0", "shellwords --version 0.2.0", "open3 --version 0.2.1"] - necessary_gems_in_bundle_path += ["yaml --version 0.1.1"] if Gem.rubygems_version < Gem::Version.new("3.4.a") - realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) + base_system_gems "stringio", "psych", "etc", "shellwords", "open3", path: scoped_gem_path(bundled_app("bundle")) build_gem "baz", "1.0.0", to_system: true, default: true, &:add_c_extension @@ -246,6 +237,8 @@ RSpec.shared_examples "bundle install --standalone" do end end + let(:cwd) { bundled_app } + describe "with Gemfiles using relative path sources and app moved to a different root" do before do FileUtils.mkdir_p bundled_app("app/vendor") @@ -472,7 +465,7 @@ RSpec.shared_examples "bundle install --standalone" do end end - describe "with --binstubs", bundler: "< 3" do + describe "with --binstubs" do before do gemfile <<-G source "https://gem.repo1" @@ -520,16 +513,33 @@ RSpec.shared_examples "bundle install --standalone" do end end -RSpec.describe "bundle install --standalone" do - let(:cwd) { bundled_app } - - include_examples("bundle install --standalone") -end - RSpec.describe "bundle install --standalone run in a subdirectory" do let(:cwd) { bundled_app("bob").tap(&:mkpath) } - include_examples("bundle install --standalone") + before do + gemfile <<-G + source "https://gem.repo1" + gem "rails" + G + end + + it "generates the script in the proper place" do + bundle :install, standalone: true, dir: cwd + + expect(bundled_app("bundle/bundler/setup.rb")).to exist + end + + context "when path set to a relative path" do + before do + bundle "config set --local path bundle" + end + + it "generates the script in the proper place" do + bundle :install, standalone: true, dir: cwd + + expect(bundled_app("bundle/bundler/setup.rb")).to exist + end + end end RSpec.describe "bundle install --standalone --local" do diff --git a/spec/bundler/install/git_spec.rb b/spec/bundler/install/git_spec.rb index 670bd1fb72..f9d96e488f 100644 --- a/spec/bundler/install/git_spec.rb +++ b/spec/bundler/install/git_spec.rb @@ -28,14 +28,14 @@ RSpec.describe "bundle install" do end it "displays the correct default branch", git: ">= 2.28.0" do - build_git "foo", "1.0", path: lib_path("foo"), default_branch: "main" + build_git "foo", "1.0", path: lib_path("foo"), default_branch: "non-standard" install_gemfile <<-G, verbose: true source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo")}" G - expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at main@#{revision_for(lib_path("foo"))[0..6]})") + expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at non-standard@#{revision_for(lib_path("foo"))[0..6]})") expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" end diff --git a/spec/bundler/install/path_spec.rb b/spec/bundler/install/path_spec.rb index 1412e8dd24..0ede8df8ff 100644 --- a/spec/bundler/install/path_spec.rb +++ b/spec/bundler/install/path_spec.rb @@ -44,13 +44,13 @@ RSpec.describe "bundle install" do expect(out).to include("gems are installed into `./vendor/bundle`") end - it "disallows --path vendor/bundle --system", bundler: "< 3" do + it "disallows --path vendor/bundle --system" do bundle "install --path vendor/bundle --system", raise_on_error: false expect(err).to include("Please choose only one option.") expect(exitstatus).to eq(15) end - it "remembers to disable system gems after the first time with bundle --path vendor/bundle", bundler: "< 3" do + it "remembers to disable system gems after the first time with bundle --path vendor/bundle" do bundle "install --path vendor/bundle" FileUtils.rm_r bundled_app("vendor") bundle "install" @@ -59,29 +59,41 @@ RSpec.describe "bundle install" do expect(the_bundle).to include_gems "myrack 1.0.0" end - context "with path_relative_to_cwd set to true" do - before { bundle "config set path_relative_to_cwd true" } + it "installs the bundle relatively to repository root, when Bundler run from the same directory" do + bundle "config path vendor/bundle", dir: bundled_app.parent + bundle "install --gemfile='#{bundled_app}/Gemfile'", dir: bundled_app.parent + expect(out).to include("installed into `./bundled_app/vendor/bundle`") + expect(bundled_app("vendor/bundle")).to be_directory + expect(the_bundle).to include_gems "myrack 1.0.0" + end - it "installs the bundle relatively to current working directory", bundler: "< 3" do - bundle "install --gemfile='#{bundled_app}/Gemfile' --path vendor/bundle", dir: bundled_app.parent - expect(out).to include("installed into `./vendor/bundle`") - expect(bundled_app("../vendor/bundle")).to be_directory - expect(the_bundle).to include_gems "myrack 1.0.0" - end + it "installs the bundle relatively to repository root, when Bundler run from a different directory" do + bundle "config path vendor/bundle", dir: bundled_app + bundle "install --gemfile='#{bundled_app}/Gemfile'", dir: bundled_app.parent + expect(out).to include("installed into `./bundled_app/vendor/bundle`") + expect(bundled_app("vendor/bundle")).to be_directory + expect(the_bundle).to include_gems "myrack 1.0.0" + end - it "installs the standalone bundle relative to the cwd" do - bundle :install, gemfile: bundled_app_gemfile, standalone: true, dir: bundled_app.parent - expect(out).to include("installed into `./bundled_app/bundle`") - expect(bundled_app("bundle")).to be_directory - expect(bundled_app("bundle/ruby")).to be_directory + it "installs the bundle relatively to Gemfile folder, when repository root can't be inferred from settings" do + bundle "install --gemfile='#{bundled_app}/Gemfile' --path vendor/bundle", dir: bundled_app.parent + expect(out).to include("installed into `./bundled_app/vendor/bundle`") + expect(bundled_app("vendor/bundle")).to be_directory + expect(the_bundle).to include_gems "myrack 1.0.0" + end - bundle "config unset path" + it "installs the standalone bundle relative to the cwd" do + bundle :install, gemfile: bundled_app_gemfile, standalone: true, dir: bundled_app.parent + expect(out).to include("installed into `./bundled_app/bundle`") + expect(bundled_app("bundle")).to be_directory + expect(bundled_app("bundle/ruby")).to be_directory - bundle :install, gemfile: bundled_app_gemfile, standalone: true, dir: bundled_app("subdir").tap(&:mkpath) - expect(out).to include("installed into `../bundle`") - expect(bundled_app("bundle")).to be_directory - expect(bundled_app("bundle/ruby")).to be_directory - end + bundle "config unset path" + + bundle :install, gemfile: bundled_app_gemfile, standalone: true, dir: bundled_app("subdir").tap(&:mkpath) + expect(out).to include("installed into `../bundle`") + expect(bundled_app("bundle")).to be_directory + expect(bundled_app("bundle/ruby")).to be_directory end end diff --git a/spec/bundler/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb index d43d926798..e1fbe6934a 100644 --- a/spec/bundler/lock/lockfile_spec.rb +++ b/spec/bundler/lock/lockfile_spec.rb @@ -109,7 +109,7 @@ RSpec.describe "the lockfile format" do #{version} L - install_gemfile <<-G, verbose: true, preserve_ruby_flags: true + install_gemfile <<-G, verbose: true source "https://gem.repo4" gem "myrack" @@ -2308,19 +2308,7 @@ RSpec.describe "the lockfile format" do private - def prerelease?(version) - Gem::Version.new(version).prerelease? - end - def previous_major(version) version.split(".").map.with_index {|v, i| i == 0 ? v.to_i - 1 : v }.join(".") end - - def bump_minor(version) - bump(version, 1) - end - - def bump(version, segment) - version.split(".").map.with_index {|v, i| i == segment ? v.to_i + 1 : v }.join(".") - end end diff --git a/spec/bundler/other/cli_dispatch_spec.rb b/spec/bundler/other/cli_dispatch_spec.rb index 48b285045a..1039737b99 100644 --- a/spec/bundler/other/cli_dispatch_spec.rb +++ b/spec/bundler/other/cli_dispatch_spec.rb @@ -4,13 +4,13 @@ RSpec.describe "bundle command names" do it "work when given fully" do bundle "install", raise_on_error: false expect(err).to eq("Could not locate Gemfile") - expect(last_command.stdboth).not_to include("Ambiguous command") + expect(stdboth).not_to include("Ambiguous command") end it "work when not ambiguous" do bundle "ins", raise_on_error: false expect(err).to eq("Could not locate Gemfile") - expect(last_command.stdboth).not_to include("Ambiguous command") + expect(stdboth).not_to include("Ambiguous command") end it "print a friendly error when ambiguous" do diff --git a/spec/bundler/other/ext_spec.rb b/spec/bundler/other/ext_spec.rb index bdfc83d47e..a883eefe06 100644 --- a/spec/bundler/other/ext_spec.rb +++ b/spec/bundler/other/ext_spec.rb @@ -1,57 +1,20 @@ # frozen_string_literal: true -RSpec.describe "Gem::Specification#match_platform" do +RSpec.describe "Gem::Specification#installable_on_platform?" do it "does not match platforms other than the gem platform" do darwin = gem "lol", "1.0", "platform_specific-1.0-x86-darwin-10" - expect(darwin.match_platform(pl("java"))).to eq(false) + expect(darwin.installable_on_platform?(pl("java"))).to eq(false) end context "when platform is a string" do it "matches when platform is a string" do lazy_spec = Bundler::LazySpecification.new("lol", "1.0", "universal-mingw32") - expect(lazy_spec.match_platform(pl("x86-mingw32"))).to eq(true) - expect(lazy_spec.match_platform(pl("x64-mingw32"))).to eq(true) + expect(lazy_spec.installable_on_platform?(pl("x86-mingw32"))).to eq(true) + expect(lazy_spec.installable_on_platform?(pl("x64-mingw32"))).to eq(true) end end end -RSpec.describe "Bundler::GemHelpers#generic" do - include Bundler::GemHelpers - - it "converts non-windows platforms into ruby" do - expect(generic(pl("x86-darwin-10"))).to eq(pl("ruby")) - expect(generic(pl("ruby"))).to eq(pl("ruby")) - end - - it "converts java platform variants into java" do - expect(generic(pl("universal-java-17"))).to eq(pl("java")) - expect(generic(pl("java"))).to eq(pl("java")) - end - - it "converts mswin platform variants into x86-mswin32" do - expect(generic(pl("mswin32"))).to eq(pl("x86-mswin32")) - expect(generic(pl("i386-mswin32"))).to eq(pl("x86-mswin32")) - expect(generic(pl("x86-mswin32"))).to eq(pl("x86-mswin32")) - end - - it "converts 32-bit mingw platform variants into universal-mingw" do - expect(generic(pl("i386-mingw32"))).to eq(pl("universal-mingw")) - expect(generic(pl("x86-mingw32"))).to eq(pl("universal-mingw")) - end - - it "converts 64-bit mingw platform variants into universal-mingw" do - expect(generic(pl("x64-mingw32"))).to eq(pl("universal-mingw")) - end - - it "converts x64 mingw UCRT platform variants into universal-mingw" do - expect(generic(pl("x64-mingw-ucrt"))).to eq(pl("universal-mingw")) - end - - it "converts aarch64 mingw UCRT platform variants into universal-mingw" do - expect(generic(pl("aarch64-mingw-ucrt"))).to eq(pl("universal-mingw")) - end -end - RSpec.describe "Gem::SourceIndex#refresh!" do before do install_gemfile <<-G diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index 036c855c4e..51d490ea72 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -17,14 +17,14 @@ RSpec.describe "major deprecations" do bundle "exec ruby -e #{source.dump}" end - it "is deprecated in favor of .unbundled_env", bundler: "< 3" do + it "is deprecated in favor of .unbundled_env" do expect(deprecations).to include \ "`Bundler.clean_env` has been deprecated in favor of `Bundler.unbundled_env`. " \ "If you instead want the environment before bundler was originally loaded, use `Bundler.original_env` " \ "(called at -e:1)" end - pending "is removed and shows a helpful error message about it", bundler: "3" + pending "is removed and shows a helpful error message about it", bundler: "4" end describe ".with_clean_env" do @@ -33,7 +33,7 @@ RSpec.describe "major deprecations" do bundle "exec ruby -e #{source.dump}" end - it "is deprecated in favor of .unbundled_env", bundler: "< 3" do + it "is deprecated in favor of .unbundled_env" do expect(deprecations).to include( "`Bundler.with_clean_env` has been deprecated in favor of `Bundler.with_unbundled_env`. " \ "If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env` " \ @@ -41,7 +41,7 @@ RSpec.describe "major deprecations" do ) end - pending "is removed and shows a helpful error message about it", bundler: "3" + pending "is removed and shows a helpful error message about it", bundler: "4" end describe ".clean_system" do @@ -50,7 +50,7 @@ RSpec.describe "major deprecations" do bundle "exec ruby -e #{source.dump}" end - it "is deprecated in favor of .unbundled_system", bundler: "< 3" do + it "is deprecated in favor of .unbundled_system" do expect(deprecations).to include( "`Bundler.clean_system` has been deprecated in favor of `Bundler.unbundled_system`. " \ "If you instead want to run the command in the environment before bundler was originally loaded, use `Bundler.original_system` " \ @@ -58,7 +58,7 @@ RSpec.describe "major deprecations" do ) end - pending "is removed and shows a helpful error message about it", bundler: "3" + pending "is removed and shows a helpful error message about it", bundler: "4" end describe ".clean_exec" do @@ -67,7 +67,7 @@ RSpec.describe "major deprecations" do bundle "exec ruby -e #{source.dump}" end - it "is deprecated in favor of .unbundled_exec", bundler: "< 3" do + it "is deprecated in favor of .unbundled_exec" do expect(deprecations).to include( "`Bundler.clean_exec` has been deprecated in favor of `Bundler.unbundled_exec`. " \ "If you instead want to exec to a command in the environment before bundler was originally loaded, use `Bundler.original_exec` " \ @@ -75,7 +75,7 @@ RSpec.describe "major deprecations" do ) end - pending "is removed and shows a helpful error message about it", bundler: "3" + pending "is removed and shows a helpful error message about it", bundler: "4" end describe ".environment" do @@ -84,11 +84,11 @@ RSpec.describe "major deprecations" do bundle "exec ruby -e #{source.dump}" end - it "is deprecated in favor of .load", bundler: "< 3" do + it "is deprecated in favor of .load" do expect(deprecations).to include "Bundler.environment has been removed in favor of Bundler.load (called at -e:1)" end - pending "is removed and shows a helpful error message about it", bundler: "3" + pending "is removed and shows a helpful error message about it", bundler: "4" end end @@ -97,11 +97,11 @@ RSpec.describe "major deprecations" do bundle "exec --no-keep-file-descriptors -e 1", raise_on_error: false end - it "is deprecated", bundler: "< 3" do + it "is deprecated" do expect(deprecations).to include "The `--no-keep-file-descriptors` has been deprecated. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to" end - pending "is removed and shows a helpful error message about it", bundler: "3" + pending "is removed and shows a helpful error message about it", bundler: "4" end describe "bundle update --quiet" do @@ -121,7 +121,7 @@ RSpec.describe "major deprecations" do bundle "check --path vendor/bundle", raise_on_error: false end - it "should print a deprecation warning", bundler: "< 3" do + it "should print a deprecation warning" do expect(deprecations).to include( "The `--path` flag is deprecated because it relies on being " \ "remembered across bundler invocations, which bundler will no " \ @@ -130,7 +130,7 @@ RSpec.describe "major deprecations" do ) end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "4" end context "bundle check --path=" do @@ -143,7 +143,7 @@ RSpec.describe "major deprecations" do bundle "check --path=vendor/bundle", raise_on_error: false end - it "should print a deprecation warning", bundler: "< 3" do + it "should print a deprecation warning" do expect(deprecations).to include( "The `--path` flag is deprecated because it relies on being " \ "remembered across bundler invocations, which bundler will no " \ @@ -152,7 +152,29 @@ RSpec.describe "major deprecations" do ) end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "4" + end + + context "bundle binstubs --path=" do + before do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + bundle "binstubs myrack --path=binpath", raise_on_error: false + end + + it "should print a deprecation warning" do + expect(deprecations).to include( + "The `--path` flag is deprecated because it relies on being " \ + "remembered across bundler invocations, which bundler will no " \ + "longer do in future versions. Instead please use `bundle config set " \ + "bin 'binpath'`, and stop using this flag" + ) + end + + pending "fails with a helpful error", bundler: "4" end context "bundle cache --all" do @@ -165,7 +187,7 @@ RSpec.describe "major deprecations" do bundle "cache --all", raise_on_error: false end - it "should print a deprecation warning", bundler: "< 3" do + it "should print a deprecation warning" do expect(deprecations).to include( "The `--all` flag is deprecated because it relies on being " \ "remembered across bundler invocations, which bundler will no " \ @@ -174,7 +196,7 @@ RSpec.describe "major deprecations" do ) end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "4" end context "bundle cache --path" do @@ -187,7 +209,7 @@ RSpec.describe "major deprecations" do bundle "cache --path foo", raise_on_error: false end - it "should print a deprecation warning", bundler: "< 3" do + it "should print a deprecation warning" do expect(deprecations).to include( "The `--path` flag is deprecated because its semantics are unclear. " \ "Use `bundle config cache_path` to configure the path of your cache of gems, " \ @@ -196,7 +218,29 @@ RSpec.describe "major deprecations" do ) end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "4" + end + + context "bundle cache --path=" do + before do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + bundle "cache --path=foo", raise_on_error: false + end + + it "should print a deprecation warning" do + expect(deprecations).to include( + "The `--path` flag is deprecated because its semantics are unclear. " \ + "Use `bundle config cache_path` to configure the path of your cache of gems, " \ + "and `bundle config path` to configure the path where your gems are installed, " \ + "and stop using this flag" + ) + end + + pending "fails with a helpful error", bundler: "4" end describe "bundle config" do @@ -205,11 +249,11 @@ RSpec.describe "major deprecations" do bundle "config" end - it "warns", bundler: "3" do + it "warns", bundler: "4" do expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config list` instead.") end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "5" end describe "old get interface" do @@ -217,11 +261,11 @@ RSpec.describe "major deprecations" do bundle "config waka" end - it "warns", bundler: "3" do + it "warns", bundler: "4" do expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config get waka` instead.") end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "5" end describe "old set interface" do @@ -229,11 +273,11 @@ RSpec.describe "major deprecations" do bundle "config waka wakapun" end - it "warns", bundler: "3" do + it "warns", bundler: "4" do expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config set waka wakapun` instead.") end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "5" end describe "old set interface with --local" do @@ -241,11 +285,11 @@ RSpec.describe "major deprecations" do bundle "config --local waka wakapun" end - it "warns", bundler: "3" do + it "warns", bundler: "4" do expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config set --local waka wakapun` instead.") end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "5" end describe "old set interface with --global" do @@ -253,11 +297,11 @@ RSpec.describe "major deprecations" do bundle "config --global waka wakapun" end - it "warns", bundler: "3" do + it "warns", bundler: "4" do expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config set --global waka wakapun` instead.") end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "5" end describe "old unset interface" do @@ -265,11 +309,11 @@ RSpec.describe "major deprecations" do bundle "config --delete waka" end - it "warns", bundler: "3" do + it "warns", bundler: "4" do expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config unset waka` instead.") end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "5" end describe "old unset interface with --local" do @@ -277,11 +321,11 @@ RSpec.describe "major deprecations" do bundle "config --delete --local waka" end - it "warns", bundler: "3" do + it "warns", bundler: "4" do expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config unset --local waka` instead.") end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "5" end describe "old unset interface with --global" do @@ -289,11 +333,11 @@ RSpec.describe "major deprecations" do bundle "config --delete --global waka" end - it "warns", bundler: "3" do + it "warns", bundler: "4" do expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config unset --global waka` instead.") end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "5" end end @@ -305,12 +349,12 @@ RSpec.describe "major deprecations" do G end - it "warns when no options are given", bundler: "3" do + it "warns when no options are given", bundler: "4" do bundle "update" expect(deprecations).to include("Pass --all to `bundle update` to update everything") end - pending "fails with a helpful error when no options are given", bundler: "3" + pending "fails with a helpful error when no options are given", bundler: "5" it "does not warn when --all is passed" do bundle "update --all" @@ -326,11 +370,11 @@ RSpec.describe "major deprecations" do G end - it "should output a deprecation warning", bundler: "< 3" do + it "should output a deprecation warning" do expect(deprecations).to include("The --binstubs option will be removed in favor of `bundle binstubs --all`") end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "4" end context "bundle install with both gems.rb and Gemfile present" do @@ -390,7 +434,7 @@ RSpec.describe "major deprecations" do bundle "install #{flag_name} #{value}" end - it "should print a deprecation warning", bundler: "< 3" do + it "should print a deprecation warning" do expect(deprecations).to include( "The `#{flag_name}` flag is deprecated because it relies on " \ "being remembered across bundler invocations, which bundler " \ @@ -399,7 +443,7 @@ RSpec.describe "major deprecations" do ) end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "4" end end end @@ -412,7 +456,7 @@ RSpec.describe "major deprecations" do G end - it "shows a deprecation", bundler: "< 3" do + it "shows a deprecation" do expect(deprecations).to include( "Your Gemfile contains multiple global sources. " \ "Using `source` more than once without a block is a security risk, and " \ @@ -421,7 +465,7 @@ RSpec.describe "major deprecations" do ) end - it "doesn't show lockfile deprecations if there's a lockfile", bundler: "< 3" do + it "doesn't show lockfile deprecations if there's a lockfile" do bundle "install" expect(deprecations).to include( @@ -449,7 +493,7 @@ RSpec.describe "major deprecations" do ) end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "4" end context "bundle install in frozen mode with a lockfile with a single rubygems section with multiple remotes" do @@ -485,13 +529,13 @@ RSpec.describe "major deprecations" do bundle "config set --local frozen true" end - it "shows a deprecation", bundler: "< 3" do + it "shows a deprecation" do bundle "install" expect(deprecations).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure.") end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "4" end context "when Bundler.setup is run in a ruby script" do @@ -524,14 +568,14 @@ RSpec.describe "major deprecations" do RUBY end - it "should print a capistrano deprecation warning", bundler: "< 3" do + it "should print a capistrano deprecation warning" do expect(deprecations).to include("Bundler no longer integrates " \ "with Capistrano, but Capistrano provides " \ "its own integration with Bundler via the " \ "capistrano-bundler gem. Use it instead.") end - pending "fails with a helpful error", bundler: "3" + pending "fails with a helpful error", bundler: "4" end context "bundle show" do @@ -547,11 +591,11 @@ RSpec.describe "major deprecations" do bundle "show --outdated" end - it "prints a deprecation warning informing about its removal", bundler: "< 3" do + it "prints a deprecation warning informing about its removal" do expect(deprecations).to include("the `--outdated` flag to `bundle show` was undocumented and will be removed without replacement") end - pending "fails with a helpful message", bundler: "3" + pending "fails with a helpful message", bundler: "4" end end @@ -564,28 +608,27 @@ RSpec.describe "major deprecations" do end context "with --install" do - it "shows a deprecation warning", bundler: "< 3" do + it "shows a deprecation warning" do bundle "remove myrack --install" expect(err).to include "[DEPRECATED] The `--install` flag has been deprecated. `bundle install` is triggered by default." end - pending "fails with a helpful message", bundler: "3" + pending "fails with a helpful message", bundler: "4" end end - context "bundle viz", :realworld do + context "bundle viz" do before do - realworld_system_gems "ruby-graphviz --version 1.2.5" create_file "gems.rb", "source 'https://gem.repo1'" bundle "viz" end - it "prints a deprecation warning", bundler: "< 3" do + it "prints a deprecation warning" do expect(deprecations).to include "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph" end - pending "fails with a helpful message", bundler: "3" + pending "fails with a helpful message", bundler: "4" end context "bundle plugin install --local_git" do @@ -595,14 +638,14 @@ RSpec.describe "major deprecations" do end end - it "prints a deprecation warning", bundler: "< 3" do + it "prints a deprecation warning" do bundle "plugin install foo --local_git #{lib_path("foo-1.0")}" expect(out).to include("Installed plugin foo") expect(deprecations).to include "--local_git is deprecated, use --git" end - pending "fails with a helpful message", bundler: "3" + pending "fails with a helpful message", bundler: "4" end describe "deprecating rubocop" do @@ -616,7 +659,7 @@ RSpec.describe "major deprecations" do bundle "gem my_new_gem --rubocop", raise_on_error: false end - it "prints a deprecation warning", bundler: "< 3" do + it "prints a deprecation warning" do expect(deprecations).to include \ "--rubocop is deprecated, use --linter=rubocop" end @@ -627,7 +670,7 @@ RSpec.describe "major deprecations" do bundle "gem my_new_gem --no-rubocop", raise_on_error: false end - it "prints a deprecation warning", bundler: "< 3" do + it "prints a deprecation warning" do expect(deprecations).to include \ "--no-rubocop is deprecated, use --linter" end @@ -638,7 +681,7 @@ RSpec.describe "major deprecations" do bundle "gem my_new_gem", env: { "BUNDLE_GEM__RUBOCOP" => "true" }, raise_on_error: false end - it "prints a deprecation warning", bundler: "< 3" do + it "prints a deprecation warning" do expect(deprecations).to include \ "config gem.rubocop is deprecated; we've updated your config to use gem.linter instead" end @@ -649,7 +692,7 @@ RSpec.describe "major deprecations" do bundle "gem my_new_gem", env: { "BUNDLE_GEM__RUBOCOP" => "false" }, raise_on_error: false end - it "prints a deprecation warning", bundler: "< 3" do + it "prints a deprecation warning" do expect(deprecations).to include \ "config gem.rubocop is deprecated; we've updated your config to use gem.linter instead" end diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb index d0de607e6c..1ccefabc0b 100644 --- a/spec/bundler/plugins/install_spec.rb +++ b/spec/bundler/plugins/install_spec.rb @@ -204,7 +204,7 @@ RSpec.describe "bundler plugin install" do plugin_should_be_installed("foo") end - it "raises an error when both git and local git sources are specified", bundler: "< 3" do + it "raises an error when both git and local git sources are specified" do bundle "plugin install foo --git /phony/path/project --local_git git@gitphony.com:/repo/project", raise_on_error: false expect(exitstatus).not_to eq(0) diff --git a/spec/bundler/quality_spec.rb b/spec/bundler/quality_spec.rb index c7fce17b62..669b8f0d35 100644 --- a/spec/bundler/quality_spec.rb +++ b/spec/bundler/quality_spec.rb @@ -144,6 +144,7 @@ RSpec.describe "The library itself" do gem.coc gem.linter gem.mit + gem.bundle gem.rubocop gem.test git.allow_insecure @@ -165,7 +166,8 @@ RSpec.describe "The library itself" do line.scan(/Bundler\.settings\[:#{key_pattern}\]/).flatten.each {|s| all_settings[s] << "referenced at `#{filename}:#{number.succ}`" } end end - documented_settings = File.read("lib/bundler/man/bundle-config.1.ronn")[/LIST OF AVAILABLE KEYS.*/m].scan(/^\* `#{key_pattern}`/).flatten + settings_section = File.read("lib/bundler/man/bundle-config.1.ronn").split(/^## /).find {|section| section.start_with?("LIST OF AVAILABLE KEYS") } + documented_settings = settings_section.scan(/^\* `#{key_pattern}`/).flatten documented_settings.each do |s| all_settings.delete(s) @@ -216,7 +218,7 @@ RSpec.describe "The library itself" do end end - warnings = last_command.stdboth.split("\n") + warnings = stdboth.split("\n") # ignore warnings around deprecated Object#=~ method in RubyGems warnings.reject! {|w| w =~ %r{rubygems\/version.rb.*deprecated\ Object#=~} } diff --git a/spec/bundler/realworld/edgecases_spec.rb b/spec/bundler/realworld/edgecases_spec.rb index fb434aba70..86b4c91a07 100644 --- a/spec/bundler/realworld/edgecases_spec.rb +++ b/spec/bundler/realworld/edgecases_spec.rb @@ -63,141 +63,6 @@ RSpec.describe "real world edgecases", realworld: true do expect(lockfile).to include(rubygems_version("activesupport", "~> 3.0")) end - it "is able to update a top-level dependency when there is a conflict on a shared transitive child" do - # from https://github.com/rubygems/bundler/issues/5031 - - pristine_system_gems "bundler-1.99.0" - - gemfile <<-G - source "https://rubygems.org" - gem 'rails', '~> 4.2.7.1' - gem 'paperclip', '~> 5.1.0' - G - - lockfile <<-L - GEM - remote: https://rubygems.org/ - specs: - actionmailer (4.2.7.1) - actionpack (= 4.2.7.1) - actionview (= 4.2.7.1) - activejob (= 4.2.7.1) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.7.1) - actionview (= 4.2.7.1) - activesupport (= 4.2.7.1) - rack (~> 1.6) - rack-test (~> 0.6.2) - rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.7.1) - activesupport (= 4.2.7.1) - builder (~> 3.1) - erubis (~> 2.7.0) - rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (4.2.7.1) - activesupport (= 4.2.7.1) - globalid (>= 0.3.0) - activemodel (4.2.7.1) - activesupport (= 4.2.7.1) - builder (~> 3.1) - activerecord (4.2.7.1) - activemodel (= 4.2.7.1) - activesupport (= 4.2.7.1) - arel (~> 6.0) - activesupport (4.2.7.1) - i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - arel (6.0.3) - builder (3.2.2) - climate_control (0.0.3) - activesupport (>= 3.0) - cocaine (0.5.8) - climate_control (>= 0.0.3, < 1.0) - concurrent-ruby (1.0.2) - erubis (2.7.0) - globalid (0.3.7) - activesupport (>= 4.1.0) - i18n (0.7.0) - json (1.8.3) - loofah (2.0.3) - nokogiri (>= 1.5.9) - mail (2.6.4) - mime-types (>= 1.16, < 4) - mime-types (3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) - mimemagic (0.3.2) - mini_portile2 (2.1.0) - minitest (5.9.1) - nokogiri (1.6.8) - mini_portile2 (~> 2.1.0) - pkg-config (~> 1.1.7) - paperclip (5.1.0) - activemodel (>= 4.2.0) - activesupport (>= 4.2.0) - cocaine (~> 0.5.5) - mime-types - mimemagic (~> 0.3.0) - pkg-config (1.1.7) - rack (1.6.4) - rack-test (0.6.3) - rack (>= 1.0) - rails (4.2.7.1) - actionmailer (= 4.2.7.1) - actionpack (= 4.2.7.1) - actionview (= 4.2.7.1) - activejob (= 4.2.7.1) - activemodel (= 4.2.7.1) - activerecord (= 4.2.7.1) - activesupport (= 4.2.7.1) - bundler (>= 1.3.0, < 2.0) - railties (= 4.2.7.1) - sprockets-rails - rails-deprecated_sanitizer (1.0.3) - activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.7) - activesupport (>= 4.2.0.beta, < 5.0) - nokogiri (~> 1.6.0) - rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.0.3) - loofah (~> 2.0) - railties (4.2.7.1) - actionpack (= 4.2.7.1) - activesupport (= 4.2.7.1) - rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) - rake (11.3.0) - sprockets (3.7.0) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.0) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - thor (0.19.1) - thread_safe (0.3.5) - tzinfo (1.2.2) - thread_safe (~> 0.1) - - PLATFORMS - ruby - - DEPENDENCIES - paperclip (~> 5.1.0) - rails (~> 4.2.7.1) - L - - bundle "lock --update paperclip", env: { "BUNDLER_VERSION" => "1.99.0" } - - expect(lockfile).to include(rubygems_version("paperclip", "~> 5.1.0")) - end - it "outputs a helpful warning when gems have a gemspec with invalid `require_paths`" do install_gemfile <<-G, standalone: true, env: { "BUNDLE_FORCE_RUBY_PLATFORM" => "1" } source 'https://rubygems.org' diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile b/spec/bundler/realworld/fixtures/warbler/Gemfile index e4d3e8ea96..07dc301d03 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile @@ -3,5 +3,5 @@ source "https://rubygems.org" gem "demo", path: "./demo" -gem "jruby-jars", "~> 9.4" -gem "warbler", "~> 2.0" +gem "jruby-jars", "~> 10.0" +gem "warbler", github: "https://github.com/jruby/warbler/pull/557" diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock index dc73661271..f6d50aad35 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock @@ -1,3 +1,15 @@ +GIT + remote: https://github.com/jruby/warbler.git + revision: 3a3a89e9a055ab1badb4e6fee860e8617b4acfe1 + ref: refs/pull/557/head + specs: + warbler (2.0.5) + jruby-jars (>= 9.0.0) + jruby-rack (>= 1.1.1, < 1.3) + rake (>= 13.0.3) + rexml (~> 3.0) + rubyzip (>= 1.0.0) + PATH remote: demo specs: @@ -6,25 +18,22 @@ PATH GEM remote: https://rubygems.org/ specs: - jruby-jars (9.4.10.0) - jruby-rack (1.1.21) - rake (13.0.1) - rubyzip (1.3.0) - warbler (2.0.5) - jruby-jars (>= 9.0.0.0) - jruby-rack (>= 1.1.1, < 1.3) - rake (>= 10.1.0) - rubyzip (~> 1.0, < 1.4) + jruby-jars (10.0.0.1) + jruby-rack (1.2.2) + rake (13.3.0) + rexml (3.4.1) + rubyzip (2.4.1) PLATFORMS + arm64-darwin java ruby - universal-java-11 + universal-java DEPENDENCIES demo! - jruby-jars (~> 9.4) - warbler (~> 2.0) + jruby-jars (~> 10.0) + warbler! BUNDLED WITH 2.7.0.dev diff --git a/spec/bundler/realworld/gemfile_source_header_spec.rb b/spec/bundler/realworld/gemfile_source_header_spec.rb deleted file mode 100644 index c532c6a867..0000000000 --- a/spec/bundler/realworld/gemfile_source_header_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "fetching dependencies with a mirrored source", realworld: true do - let(:mirror) { "https://server.example.org" } - let(:original) { "http://127.0.0.1:#{@port}" } - - before do - setup_server - bundle "config set --local mirror.#{mirror} #{original}" - end - - after do - Artifice.deactivate - @t.kill - @t.join - end - - it "sets the 'X-Gemfile-Source' and 'User-Agent' headers and bundles successfully" do - gemfile <<-G - source "#{mirror}" - gem 'weakling' - G - - bundle :install, artifice: nil - - expect(out).to include("Installing weakling") - expect(out).to include("Bundle complete") - expect(the_bundle).to include_gems "weakling 0.0.3" - end - - private - - def setup_server - require_rack - @port = find_unused_port - @server_uri = "http://127.0.0.1:#{@port}" - - require_relative "../support/artifice/endpoint_mirror_source" - require_relative "../support/silent_logger" - - require "rackup/server" - - @t = Thread.new do - Rackup::Server.start(app: EndpointMirrorSource, - Host: "0.0.0.0", - Port: @port, - server: "webrick", - AccessLog: [], - Logger: Spec::SilentLogger.new) - end.run - - wait_for_server("127.0.0.1", @port) - end -end diff --git a/spec/bundler/realworld/mirror_probe_spec.rb b/spec/bundler/realworld/mirror_probe_spec.rb deleted file mode 100644 index 66a553da28..0000000000 --- a/spec/bundler/realworld/mirror_probe_spec.rb +++ /dev/null @@ -1,132 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "fetching dependencies with a not available mirror", realworld: true do - let(:mirror) { @mirror_uri } - let(:original) { @server_uri } - let(:server_port) { @server_port } - let(:host) { "127.0.0.1" } - - before do - require_rack - setup_server - setup_mirror - end - - after do - Artifice.deactivate - @server_thread.kill - @server_thread.join - end - - context "with a specific fallback timeout" do - before do - global_config("BUNDLE_MIRROR__HTTP://127__0__0__1:#{server_port}/__FALLBACK_TIMEOUT/" => "true", - "BUNDLE_MIRROR__HTTP://127__0__0__1:#{server_port}/" => mirror) - end - - it "install a gem using the original uri when the mirror is not responding" do - gemfile <<-G - source "#{original}" - gem 'weakling' - G - - bundle :install, artifice: nil - - expect(out).to include("Installing weakling") - expect(out).to include("Bundle complete") - expect(the_bundle).to include_gems "weakling 0.0.3" - end - end - - context "with a global fallback timeout" do - before do - global_config("BUNDLE_MIRROR__ALL__FALLBACK_TIMEOUT/" => "1", - "BUNDLE_MIRROR__ALL" => mirror) - end - - it "install a gem using the original uri when the mirror is not responding" do - gemfile <<-G - source "#{original}" - gem 'weakling' - G - - bundle :install, artifice: nil - - expect(out).to include("Installing weakling") - expect(out).to include("Bundle complete") - expect(the_bundle).to include_gems "weakling 0.0.3" - end - end - - context "with a specific mirror without a fallback timeout" do - before do - global_config("BUNDLE_MIRROR__HTTP://127__0__0__1:#{server_port}/" => mirror) - end - - it "fails to install the gem with a timeout error" do - gemfile <<-G - source "#{original}" - gem 'weakling' - G - - bundle :install, artifice: nil, raise_on_error: false - - expect(out).to include("Fetching source index from #{mirror}") - - err_lines = err.split("\n") - expect(err_lines).to include(%r{\ARetrying fetcher due to error \(2/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) - expect(err_lines).to include(%r{\ARetrying fetcher due to error \(3/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) - expect(err_lines).to include(%r{\ARetrying fetcher due to error \(4/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) - expect(err_lines).to include(%r{\ACould not fetch specs from #{mirror}/ due to underlying error <}) - end - end - - context "with a global mirror without a fallback timeout" do - before do - global_config("BUNDLE_MIRROR__ALL" => mirror) - end - - it "fails to install the gem with a timeout error" do - gemfile <<-G - source "#{original}" - gem 'weakling' - G - - bundle :install, artifice: nil, raise_on_error: false - - expect(out).to include("Fetching source index from #{mirror}") - - err_lines = err.split("\n") - expect(err_lines).to include(%r{\ARetrying fetcher due to error \(2/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) - expect(err_lines).to include(%r{\ARetrying fetcher due to error \(3/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) - expect(err_lines).to include(%r{\ARetrying fetcher due to error \(4/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) - expect(err_lines).to include(%r{\ACould not fetch specs from #{mirror}/ due to underlying error <}) - end - end - - def setup_server - @server_port = find_unused_port - @server_uri = "http://#{host}:#{@server_port}" - - require_relative "../support/artifice/endpoint" - require_relative "../support/silent_logger" - - require "rackup/server" - - @server_thread = Thread.new do - Rackup::Server.start(app: Endpoint, - Host: host, - Port: @server_port, - server: "webrick", - AccessLog: [], - Logger: Spec::SilentLogger.new) - end.run - - wait_for_server(host, @server_port) - end - - def setup_mirror - @mirror_port = find_unused_port - @mirror_uri = "http://#{host}:#{@mirror_port}" - end -end diff --git a/spec/bundler/realworld/slow_perf_spec.rb b/spec/bundler/realworld/slow_perf_spec.rb index 32e266ff1b..5d36ba7455 100644 --- a/spec/bundler/realworld/slow_perf_spec.rb +++ b/spec/bundler/realworld/slow_perf_spec.rb @@ -30,115 +30,6 @@ RSpec.describe "bundle install with complex dependencies", realworld: true do G bundle "lock", env: { "DEBUG_RESOLVER" => "1" } - expect(out).to include("Solution found after 1 attempts") - end - - it "resolves big gemfile quickly" do - gemfile <<~G - # frozen_string_literal: true - - source "https://rubygems.org" - - gem "rails" - gem "pg", ">= 0.18", "< 2.0" - gem "goldiloader" - gem "awesome_nested_set" - gem "circuitbox" - gem "passenger" - gem "globalid" - gem "rack-cors" - gem "rails-pg-extras" - gem "linear_regression_trend" - gem "rack-protection" - gem "pundit" - gem "remote_ip_proxy_scrubber" - gem "bcrypt" - gem "searchkick" - gem "excon" - gem "faraday_middleware-aws-sigv4" - gem "typhoeus" - gem "sidekiq" - gem "sidekiq-undertaker" - gem "sidekiq-cron" - gem "storext" - gem "appsignal" - gem "fcm" - gem "business_time" - gem "tzinfo" - gem "holidays" - gem "bigdecimal" - gem "progress_bar" - gem "redis" - gem "hiredis" - gem "state_machines" - gem "state_machines-audit_trail" - gem "state_machines-activerecord" - gem "interactor" - gem "ar_transaction_changes" - gem "redis-rails" - gem "seed_migration" - gem "lograge" - gem "graphiql-rails", group: :development - gem "graphql" - gem "pusher" - gem "rbnacl" - gem "jwt" - gem "json-schema" - gem "discard" - gem "money" - gem "strip_attributes" - gem "validates_email_format_of" - gem "audited" - gem "concurrent-ruby" - gem "with_advisory_lock" - - group :test do - gem "rspec-sidekiq" - gem "simplecov", require: false - end - - group :development, :test do - gem "byebug", platform: :mri - gem "guard" - gem "guard-bundler" - gem "guard-rspec" - gem "rb-fsevent" - gem "rspec_junit_formatter" - gem "rspec-collection_matchers" - gem "rspec-rails" - gem "rspec-retry" - gem "state_machines-rspec" - gem "dotenv-rails" - gem "database_cleaner-active_record" - gem "database_cleaner-redis" - gem "timecop" - end - - gem "factory_bot_rails" - gem "faker" - - group :development do - gem "listen" - gem "sql_queries_count" - gem "rubocop" - gem "rubocop-performance" - gem "rubocop-rspec" - gem "rubocop-rails" - gem "brakeman" - gem "bundler-audit" - gem "solargraph" - gem "annotate" - end - G - - if Bundler.feature_flag.bundler_3_mode? - bundle "lock", env: { "DEBUG_RESOLVER" => "1" }, raise_on_error: false - - expect(out).to include("backtracking").exactly(26).times - else - bundle "lock", env: { "DEBUG_RESOLVER" => "1" } - - expect(out).to include("Solution found after 10 attempts") - end + expect(out).to include("Solution found after 2 attempts") end end diff --git a/spec/bundler/resolver/basic_spec.rb b/spec/bundler/resolver/basic_spec.rb index 05c6f24ff7..185df1b1c7 100644 --- a/spec/bundler/resolver/basic_spec.rb +++ b/spec/bundler/resolver/basic_spec.rb @@ -238,7 +238,7 @@ RSpec.describe "Resolving" do it "resolves foo only to latest patch - changing dependency declared case" do # bar is locked AND a declared dependency in the Gemfile, so it will not move, and therefore # foo can only move up to 1.4.4. - @base << Bundler::LazySpecification.new("bar", Gem::Version.new("2.0.3"), nil) + @base = Bundler::SpecSet.new([Bundler::LazySpecification.new("bar", Gem::Version.new("2.0.3"), nil)]) should_conservative_resolve_and_include :patch, ["foo"], %w[foo-1.4.4 bar-2.0.3] end diff --git a/spec/bundler/resolver/platform_spec.rb b/spec/bundler/resolver/platform_spec.rb index 8e51911bbd..13f3e15282 100644 --- a/spec/bundler/resolver/platform_spec.rb +++ b/spec/bundler/resolver/platform_spec.rb @@ -48,11 +48,11 @@ RSpec.describe "Resolving platform craziness" do it "takes the latest ruby gem, even if an older platform specific version is available" do @index = build_index do gem "foo", "1.0.0" - gem "foo", "1.0.0", "x64-mingw32" + gem "foo", "1.0.0", "x64-mingw-ucrt" gem "foo", "1.1.0" end dep "foo" - platforms "x64-mingw32" + platforms "x64-mingw-ucrt" should_resolve_as %w[foo-1.1.0] end @@ -61,12 +61,12 @@ RSpec.describe "Resolving platform craziness" do @index = build_index do gem "bar", "1.0.0" gem "foo", "1.0.0" - gem "foo", "1.0.0", "x64-mingw32" do + gem "foo", "1.0.0", "x64-mingw-ucrt" do dep "bar", "< 1" end end dep "foo" - platforms "x64-mingw32" + platforms "x64-mingw-ucrt" should_resolve_as %w[foo-1.0.0] end @@ -74,12 +74,12 @@ RSpec.describe "Resolving platform craziness" do it "prefers the platform specific gem to the ruby version" do @index = build_index do gem "foo", "1.0.0" - gem "foo", "1.0.0", "x64-mingw32" + gem "foo", "1.0.0", "x64-mingw-ucrt" end dep "foo" - platforms "x64-mingw32" + platforms "x64-mingw-ucrt" - should_resolve_as %w[foo-1.0.0-x64-mingw32] + should_resolve_as %w[foo-1.0.0-x64-mingw-ucrt] end describe "on a linux platform" do @@ -159,15 +159,15 @@ RSpec.describe "Resolving platform craziness" do before do @index = build_index do gem "foo", "1.0.0" - gem "foo", "1.0.0", "x64-mingw32" + gem "foo", "1.0.0", "x64-mingw-ucrt" gem "foo", "1.1.0" - gem "foo", "1.1.0", "x64-mingw32" do |s| + gem "foo", "1.1.0", "x64-mingw-ucrt" do |s| s.required_ruby_version = [">= 2.0", "< 2.4"] end gem "Ruby\0", "2.5.1" end dep "Ruby\0", "2.5.1" - platforms "x64-mingw32" + platforms "x64-mingw-ucrt" end it "takes the latest ruby gem" do @@ -186,18 +186,18 @@ RSpec.describe "Resolving platform craziness" do it "takes the latest ruby gem with required_ruby_version if the platform specific gem doesn't match the required_ruby_version" do @index = build_index do gem "foo", "1.0.0" - gem "foo", "1.0.0", "x64-mingw32" + gem "foo", "1.0.0", "x64-mingw-ucrt" gem "foo", "1.1.0" do |s| s.required_ruby_version = [">= 2.0"] end - gem "foo", "1.1.0", "x64-mingw32" do |s| + gem "foo", "1.1.0", "x64-mingw-ucrt" do |s| s.required_ruby_version = [">= 2.0", "< 2.4"] end gem "Ruby\0", "2.5.1" end dep "foo" dep "Ruby\0", "2.5.1" - platforms "x64-mingw32" + platforms "x64-mingw-ucrt" should_resolve_as %w[foo-1.1.0] end @@ -205,18 +205,18 @@ RSpec.describe "Resolving platform craziness" do it "takes the latest ruby gem if the platform specific gem doesn't match the required_ruby_version with multiple platforms" do @index = build_index do gem "foo", "1.0.0" - gem "foo", "1.0.0", "x64-mingw32" + gem "foo", "1.0.0", "x64-mingw-ucrt" gem "foo", "1.1.0" do |s| s.required_ruby_version = [">= 2.0"] end - gem "foo", "1.1.0", "x64-mingw32" do |s| + gem "foo", "1.1.0", "x64-mingw-ucrt" do |s| s.required_ruby_version = [">= 2.0", "< 2.4"] end gem "Ruby\0", "2.5.1" end dep "foo" dep "Ruby\0", "2.5.1" - platforms "x86_64-linux", "x64-mingw32" + platforms "x86_64-linux", "x64-mingw-ucrt" should_resolve_as %w[foo-1.1.0] end @@ -342,7 +342,7 @@ RSpec.describe "Resolving platform craziness" do describe "with mingw32" do before :each do @index = build_index do - platforms "mingw32 mswin32 x64-mingw32 x64-mingw-ucrt" do |platform| + platforms "mingw32 mswin32 x64-mingw-ucrt" do |platform| gem "thin", "1.2.7", platform end gem "win32-api", "1.5.1", "universal-mingw32" @@ -363,10 +363,10 @@ RSpec.describe "Resolving platform craziness" do should_resolve_as %w[thin-1.2.7-mingw32] end - it "finds x64-mingw32 gems" do - platforms "x64-mingw32" + it "finds x64-mingw-ucrt gems" do + platforms "x64-mingw-ucrt" dep "thin" - should_resolve_as %w[thin-1.2.7-x64-mingw32] + should_resolve_as %w[thin-1.2.7-x64-mingw-ucrt] end it "finds universal-mingw gems on x86-mingw" do @@ -376,7 +376,7 @@ RSpec.describe "Resolving platform craziness" do end it "finds universal-mingw gems on x64-mingw" do - platform "x64-mingw32" + platform "x64-mingw-ucrt" dep "win32-api" should_resolve_as %w[win32-api-1.5.1-universal-mingw32] end diff --git a/spec/bundler/runtime/env_helpers_spec.rb b/spec/bundler/runtime/env_helpers_spec.rb index a1607cd057..42605b6ea0 100644 --- a/spec/bundler/runtime/env_helpers_spec.rb +++ b/spec/bundler/runtime/env_helpers_spec.rb @@ -24,7 +24,7 @@ RSpec.describe "env helpers" do path = `getconf PATH`.strip + "#{File::PATH_SEPARATOR}/foo" with_path_as(path) do bundle_exec_ruby(bundled_app("source.rb").to_s) - expect(last_command.stdboth).to eq(path) + expect(stdboth).to eq(path) end end @@ -35,7 +35,7 @@ RSpec.describe "env helpers" do gem_path = ENV["GEM_PATH"] + "#{File::PATH_SEPARATOR}/foo" with_gem_path_as(gem_path) do bundle_exec_ruby(bundled_app("source.rb").to_s) - expect(last_command.stdboth).to eq(gem_path) + expect(stdboth).to eq(gem_path) end end @@ -62,9 +62,6 @@ RSpec.describe "env helpers" do end it "removes variables that bundler added", :ruby_repo do - # Simulate bundler has not yet been loaded - ENV.replace(ENV.to_hash.delete_if {|k, _v| k.start_with?(Bundler::EnvironmentPreserver::BUNDLER_PREFIX) }) - original = ruby('puts ENV.to_a.map {|e| e.join("=") }.sort.join("\n")', artifice: "fail") create_file("source.rb", <<-RUBY) puts Bundler.original_env.to_a.map {|e| e.join("=") }.sort.join("\n") @@ -81,7 +78,7 @@ RSpec.describe "env helpers" do RUBY ENV["BUNDLE_PATH"] = "./foo" bundle_exec_ruby bundled_app("source.rb") - expect(last_command.stdboth).to include "false" + expect(stdboth).to include "false" end it "should remove absolute path to 'bundler/setup' from RUBYOPT even if it was present in original env" do @@ -91,7 +88,7 @@ RSpec.describe "env helpers" do setup_require = "-r#{lib_dir}/bundler/setup" ENV["BUNDLER_ORIG_RUBYOPT"] = "-W2 #{setup_require} #{ENV["RUBYOPT"]}" bundle_exec_ruby bundled_app("source.rb") - expect(last_command.stdboth).not_to include(setup_require) + expect(stdboth).not_to include(setup_require) end it "should remove relative path to 'bundler/setup' from RUBYOPT even if it was present in original env" do @@ -100,7 +97,7 @@ RSpec.describe "env helpers" do RUBY ENV["BUNDLER_ORIG_RUBYOPT"] = "-W2 -rbundler/setup #{ENV["RUBYOPT"]}" bundle_exec_ruby bundled_app("source.rb") - expect(last_command.stdboth).not_to include("-rbundler/setup") + expect(stdboth).not_to include("-rbundler/setup") end it "should delete BUNDLER_SETUP even if it was present in original env" do @@ -109,7 +106,7 @@ RSpec.describe "env helpers" do RUBY ENV["BUNDLER_ORIG_BUNDLER_SETUP"] = system_gem_path("gems/bundler-#{Bundler::VERSION}/lib/bundler/setup").to_s bundle_exec_ruby bundled_app("source.rb") - expect(last_command.stdboth).to include "false" + expect(stdboth).to include "false" end it "should restore RUBYLIB", :ruby_repo do @@ -119,7 +116,7 @@ RSpec.describe "env helpers" do ENV["RUBYLIB"] = lib_dir.to_s + File::PATH_SEPARATOR + "/foo" ENV["BUNDLER_ORIG_RUBYLIB"] = lib_dir.to_s + File::PATH_SEPARATOR + "/foo-original" bundle_exec_ruby bundled_app("source.rb") - expect(last_command.stdboth).to include("/foo-original") + expect(stdboth).to include("/foo-original") end it "should restore the original MANPATH" do @@ -129,7 +126,7 @@ RSpec.describe "env helpers" do ENV["MANPATH"] = "/foo" ENV["BUNDLER_ORIG_MANPATH"] = "/foo-original" bundle_exec_ruby bundled_app("source.rb") - expect(last_command.stdboth).to include("/foo-original") + expect(stdboth).to include("/foo-original") end end @@ -139,7 +136,7 @@ RSpec.describe "env helpers" do it_behaves_like "an unbundling helper" end - describe "Bundler.clean_env", bundler: 2 do + describe "Bundler.clean_env" do let(:modified_env) { "Bundler.clean_env" } it_behaves_like "an unbundling helper" @@ -161,7 +158,7 @@ RSpec.describe "env helpers" do end end - describe "Bundler.with_clean_env", bundler: 2 do + describe "Bundler.with_clean_env" do it "should set ENV to unbundled_env in the block" do expected = Bundler.unbundled_env @@ -212,7 +209,7 @@ RSpec.describe "env helpers" do end end - describe "Bundler.clean_system", bundler: 2 do + describe "Bundler.clean_system" do before do create_file("source.rb", <<-'RUBY') Bundler.ui.silence { Bundler.clean_system("ruby", "-e", "exit(42) unless ENV['BUNDLE_FOO'] == 'bar'") } @@ -263,7 +260,7 @@ RSpec.describe "env helpers" do end end - describe "Bundler.clean_exec", bundler: 2 do + describe "Bundler.clean_exec" do before do create_file("source.rb", <<-'RUBY') Process.fork do diff --git a/spec/bundler/runtime/executable_spec.rb b/spec/bundler/runtime/executable_spec.rb index a4226ed51e..7cab24218f 100644 --- a/spec/bundler/runtime/executable_spec.rb +++ b/spec/bundler/runtime/executable_spec.rb @@ -96,7 +96,7 @@ RSpec.describe "Running bin/* commands" do expect(bundled_app("bin/myrackup")).not_to exist end - it "allows you to stop installing binstubs", bundler: "< 3" do + it "allows you to stop installing binstubs" do skip "delete permission error" if Gem.win_platform? bundle "install --binstubs bin/" @@ -109,7 +109,7 @@ RSpec.describe "Running bin/* commands" do expect(out).to include("You have not configured a value for `bin`") end - it "remembers that the option was specified", bundler: "< 3" do + it "remembers that the option was specified" do gemfile <<-G source "https://gem.repo1" gem "activesupport" diff --git a/spec/bundler/runtime/gem_tasks_spec.rb b/spec/bundler/runtime/gem_tasks_spec.rb index 046300391b..6a8de2b949 100644 --- a/spec/bundler/runtime/gem_tasks_spec.rb +++ b/spec/bundler/runtime/gem_tasks_spec.rb @@ -66,7 +66,7 @@ RSpec.describe "require 'bundler/gem_tasks'" do it "includes the relevant tasks" do define_local_gem_using_gem_tasks - with_gem_path_as(base_system_gem_path.to_s) do + with_gem_path_as(scoped_base_system_gem_path.to_s) do sys_exec "#{rake} -T", env: { "GEM_HOME" => system_gem_path.to_s } end @@ -85,7 +85,7 @@ RSpec.describe "require 'bundler/gem_tasks'" do it "defines a working `rake install` task", :ruby_repo do define_local_gem_using_gem_tasks - with_gem_path_as(base_system_gem_path.to_s) do + with_gem_path_as(scoped_base_system_gem_path.to_s) do sys_exec "#{rake} install", env: { "GEM_HOME" => system_gem_path.to_s } end @@ -155,7 +155,7 @@ RSpec.describe "require 'bundler/gem_tasks'" do it "adds 'pkg' to rake/clean's CLOBBER" do define_local_gem_using_gem_tasks - with_gem_path_as(base_system_gem_path.to_s) do + with_gem_path_as(scoped_base_system_gem_path.to_s) do sys_exec %(#{rake} -e 'load "Rakefile"; puts CLOBBER.inspect'), env: { "GEM_HOME" => system_gem_path.to_s } end expect(out).to eq '["pkg"]' diff --git a/spec/bundler/runtime/inline_spec.rb b/spec/bundler/runtime/inline_spec.rb index 48385b452d..0467d8b14a 100644 --- a/spec/bundler/runtime/inline_spec.rb +++ b/spec/bundler/runtime/inline_spec.rb @@ -590,29 +590,6 @@ RSpec.describe "bundler/inline#gemfile" do expect(err).to be_empty end - it "when requiring fileutils after does not show redefinition warnings" do - Dir.mkdir tmp("path_without_gemfile") - - default_fileutils_version = ruby "gem 'fileutils', '< 999999'; require 'fileutils'; puts FileUtils::VERSION", raise_on_error: false - skip "fileutils isn't a default gem" if default_fileutils_version.empty? - - realworld_system_gems "fileutils --version 1.4.1" - - realworld_system_gems "pathname --version 0.2.0" - - script <<-RUBY, dir: tmp("path_without_gemfile"), env: { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s, "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } - require "bundler/inline" - - gemfile(true) do - source "https://gem.repo2" - end - - require "fileutils" - RUBY - - expect(err).to eq("The Gemfile specifies no dependencies") - end - it "does not load default timeout" do default_timeout_version = ruby "gem 'timeout', '< 999999'; require 'timeout'; puts Timeout::VERSION", raise_on_error: false skip "timeout isn't a default gem" if default_timeout_version.empty? diff --git a/spec/bundler/runtime/platform_spec.rb b/spec/bundler/runtime/platform_spec.rb index 562184ce17..cf1f85286f 100644 --- a/spec/bundler/runtime/platform_spec.rb +++ b/spec/bundler/runtime/platform_spec.rb @@ -411,7 +411,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do s.add_dependency "platform_specific" end end - simulate_platform "x64-mingw32" do + simulate_platform "x64-mingw-ucrt" do lockfile <<-L GEM remote: https://gem.repo2/ @@ -421,7 +421,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do platform_specific PLATFORMS - x64-mingw32 + x64-mingw-ucrt x86-mingw32 DEPENDENCIES @@ -434,11 +434,11 @@ RSpec.describe "Bundler.setup with multi platform stuff" do G expect(out).to include("lockfile does not have all gems needed for the current platform") - expect(the_bundle).to include_gem "platform_specific 1.0 x64-mingw32" + expect(the_bundle).to include_gem "platform_specific 1.0 x64-mingw-ucrt" end end - %w[x86-mswin32 x64-mswin64 x86-mingw32 x64-mingw32 x64-mingw-ucrt aarch64-mingw-ucrt].each do |platform| + %w[x86-mswin32 x64-mswin64 x86-mingw32 x64-mingw-ucrt aarch64-mingw-ucrt].each do |platform| it "allows specifying platform windows on #{platform} platform" do simulate_platform platform do lockfile <<-L diff --git a/spec/bundler/runtime/requiring_spec.rb b/spec/bundler/runtime/requiring_spec.rb index 1f32269622..f0e0aeacaf 100644 --- a/spec/bundler/runtime/requiring_spec.rb +++ b/spec/bundler/runtime/requiring_spec.rb @@ -2,14 +2,14 @@ RSpec.describe "Requiring bundler" do it "takes care of requiring rubygems when entrypoint is bundler/setup" do - sys_exec("#{Gem.ruby} -I#{lib_dir} -rbundler/setup -e'puts true'", env: { "RUBYOPT" => opt_add("--disable=gems", ENV["RUBYOPT"]) }) + sys_exec("#{Gem.ruby} -I#{lib_dir} -rbundler/setup -e'puts true'", env: { "RUBYOPT" => "--disable=gems" }) - expect(last_command.stdboth).to eq("true") + expect(stdboth).to eq("true") end it "takes care of requiring rubygems when requiring just bundler" do - sys_exec("#{Gem.ruby} -I#{lib_dir} -rbundler -e'puts true'", env: { "RUBYOPT" => opt_add("--disable=gems", ENV["RUBYOPT"]) }) + sys_exec("#{Gem.ruby} -I#{lib_dir} -rbundler -e'puts true'", env: { "RUBYOPT" => "--disable=gems" }) - expect(last_command.stdboth).to eq("true") + expect(stdboth).to eq("true") end end diff --git a/spec/bundler/runtime/self_management_spec.rb b/spec/bundler/runtime/self_management_spec.rb index a0b2d83d0e..2cb2d0175f 100644 --- a/spec/bundler/runtime/self_management_spec.rb +++ b/spec/bundler/runtime/self_management_spec.rb @@ -3,11 +3,11 @@ RSpec.describe "Self management" do describe "auto switching" do let(:previous_minor) do - "2.3.0" + "9.3.0" end let(:current_version) do - "2.4.0" + "9.4.0" end before do @@ -24,22 +24,24 @@ RSpec.describe "Self management" do gem "myrack" G + + pristine_system_gems "bundler-#{current_version}" end it "installs locked version when using system path and uses it" do lockfile_bundled_with(previous_minor) bundle "config set --local path.system true" - bundle "install", preserve_ruby_flags: true - expect(out).to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + bundle "install" + expect(out).to include("Bundler #{current_version} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") # It uninstalls the older system bundler bundle "clean --force", artifice: nil - expect(out).to eq("Removing bundler (#{Bundler::VERSION})") + expect(out).to eq("Removing bundler (#{current_version})") # App now uses locked version bundle "-v", artifice: nil - expect(out).to end_with(previous_minor[0] == "2" ? "Bundler version #{previous_minor}" : previous_minor) + expect(out).to eq(previous_minor) # ruby-core test setup has always "lib" in $LOAD_PATH so `require "bundler/setup"` always activate the local version rather than using RubyGems gem activation stuff unless ruby_core? @@ -48,7 +50,7 @@ RSpec.describe "Self management" do create_file file, <<-RUBY #!#{Gem.ruby} require 'bundler/setup' - puts Bundler::VERSION + puts '#{previous_minor}' RUBY file.chmod(0o777) cmd = Gem.win_platform? ? "#{Gem.ruby} bin/bundle_version.rb" : "bin/bundle_version.rb" @@ -59,15 +61,15 @@ RSpec.describe "Self management" do # Subsequent installs use the locked version without reinstalling bundle "install --verbose", artifice: nil expect(out).to include("Using bundler #{previous_minor}") - expect(out).not_to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + expect(out).not_to include("Bundler #{current_version} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") end it "installs locked version when using local path and uses it" do lockfile_bundled_with(previous_minor) bundle "config set --local path vendor/bundle" - bundle "install", preserve_ruby_flags: true - expect(out).to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + bundle "install" + expect(out).to include("Bundler #{current_version} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") expect(vendored_gems("gems/bundler-#{previous_minor}")).to exist # It does not uninstall the locked bundler @@ -76,7 +78,11 @@ RSpec.describe "Self management" do # App now uses locked version bundle "-v" - expect(out).to end_with(previous_minor[0] == "2" ? "Bundler version #{previous_minor}" : previous_minor) + expect(out).to eq(previous_minor) + + # Preserves original gem home when auto-switching + bundle "exec ruby -e 'puts Bundler.original_env[\"GEM_HOME\"]'" + expect(out).to eq(ENV["GEM_HOME"]) # ruby-core test setup has always "lib" in $LOAD_PATH so `require "bundler/setup"` always activate the local version rather than using RubyGems gem activation stuff unless ruby_core? @@ -85,7 +91,7 @@ RSpec.describe "Self management" do create_file file, <<-RUBY #!#{Gem.ruby} require 'bundler/setup' - puts Bundler::VERSION + puts '#{previous_minor}' RUBY file.chmod(0o777) cmd = Gem.win_platform? ? "#{Gem.ruby} bin/bundle_version.rb" : "bin/bundle_version.rb" @@ -96,15 +102,15 @@ RSpec.describe "Self management" do # Subsequent installs use the locked version without reinstalling bundle "install --verbose" expect(out).to include("Using bundler #{previous_minor}") - expect(out).not_to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + expect(out).not_to include("Bundler #{current_version} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") end it "installs locked version when using deployment option and uses it" do lockfile_bundled_with(previous_minor) bundle "config set --local deployment true" - bundle "install", preserve_ruby_flags: true - expect(out).to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + bundle "install" + expect(out).to include("Bundler #{current_version} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") expect(vendored_gems("gems/bundler-#{previous_minor}")).to exist # It does not uninstall the locked bundler @@ -113,12 +119,12 @@ RSpec.describe "Self management" do # App now uses locked version bundle "-v" - expect(out).to end_with(previous_minor[0] == "2" ? "Bundler version #{previous_minor}" : previous_minor) + expect(out).to eq(previous_minor) # Subsequent installs use the locked version without reinstalling bundle "install --verbose" expect(out).to include("Using bundler #{previous_minor}") - expect(out).not_to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + expect(out).not_to include("Bundler #{current_version} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") end it "does not try to install a development version" do @@ -128,41 +134,41 @@ RSpec.describe "Self management" do expect(out).not_to match(/restarting using that version/) bundle "-v" - expect(out).to eq(Bundler::VERSION[0] == "2" ? "Bundler version #{Bundler::VERSION}" : Bundler::VERSION) + expect(out).to eq(current_version) end it "does not try to install when --local is passed" do lockfile_bundled_with(previous_minor) - system_gems "myrack-1.0.0", path: default_bundle_path + system_gems "myrack-1.0.0", path: local_gem_path bundle "install --local" expect(out).not_to match(/Installing Bundler/) bundle "-v" - expect(out).to eq(Bundler::VERSION[0] == "2" ? "Bundler version #{Bundler::VERSION}" : Bundler::VERSION) + expect(out).to eq(current_version) end it "shows a discrete message if locked bundler does not exist" do - missing_minor = "#{Bundler::VERSION[0]}.999.999" + missing_minor = "#{current_version[0]}.999.999" lockfile_bundled_with(missing_minor) bundle "install" - expect(err).to eq("Your lockfile is locked to a version of bundler (#{missing_minor}) that doesn't exist at https://rubygems.org/. Going on using #{Bundler::VERSION}") + expect(err).to eq("Your lockfile is locked to a version of bundler (#{missing_minor}) that doesn't exist at https://rubygems.org/. Going on using #{current_version}") bundle "-v" - expect(out).to eq(Bundler::VERSION[0] == "2" ? "Bundler version #{Bundler::VERSION}" : Bundler::VERSION) + expect(out).to eq(current_version) end it "installs BUNDLE_VERSION version when using bundle config version x.y.z" do lockfile_bundled_with(current_version) bundle "config set --local version #{previous_minor}" - bundle "install", preserve_ruby_flags: true - expect(out).to include("Bundler #{Bundler::VERSION} is running, but your configuration was #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + bundle "install" + expect(out).to include("Bundler #{current_version} is running, but your configuration was #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") bundle "-v" - expect(out).to eq(previous_minor[0] == "2" ? "Bundler version #{previous_minor}" : previous_minor) + expect(out).to eq(previous_minor) end it "does not try to install when using bundle config version global" do @@ -173,7 +179,7 @@ RSpec.describe "Self management" do expect(out).not_to match(/restarting using that version/) bundle "-v" - expect(out).to eq(Bundler::VERSION[0] == "2" ? "Bundler version #{Bundler::VERSION}" : Bundler::VERSION) + expect(out).to eq(current_version) end it "does not try to install when using bundle config version " do @@ -184,20 +190,18 @@ RSpec.describe "Self management" do expect(out).not_to match(/restarting using that version/) bundle "-v" - expect(out).to eq(Bundler::VERSION[0] == "2" ? "Bundler version #{Bundler::VERSION}" : Bundler::VERSION) + expect(out).to eq(current_version) end it "ignores malformed lockfile version" do lockfile_bundled_with("2.3.") bundle "install --verbose" - expect(out).to include("Using bundler #{Bundler::VERSION}") + expect(out).to include("Using bundler #{current_version}") end it "uses the right original script when re-execing, if `$0` has been changed to something that's not a script", :ruby_repo do - bundle "config path vendor/bundle" - - system_gems "bundler-9.9.9", path: vendored_gems + system_gems "bundler-9.9.9", path: local_gem_path test = bundled_app("test.rb") @@ -214,9 +218,7 @@ RSpec.describe "Self management" do end it "uses modified $0 when re-execing, if `$0` has been changed to a script", :ruby_repo do - bundle "config path vendor/bundle" - - system_gems "bundler-9.9.9", path: vendored_gems + system_gems "bundler-9.9.9", path: local_gem_path runner = bundled_app("runner.rb") diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb index 200d30302d..74ada2f8b3 100644 --- a/spec/bundler/runtime/setup_spec.rb +++ b/spec/bundler/runtime/setup_spec.rb @@ -1406,7 +1406,6 @@ end describe "default gem activation" do let(:exemptions) do exempts = %w[did_you_mean bundler uri pathname] - exempts << "etc" if (Gem.ruby_version < Gem::Version.new("3.2") || Gem.ruby_version >= Gem::Version.new("3.3.2")) && Gem.win_platform? exempts << "error_highlight" # added in Ruby 3.1 as a default gem exempts << "ruby2_keywords" # added in Ruby 3.1 as a default gem exempts << "syntax_suggest" # added in Ruby 3.2 as a default gem @@ -1465,7 +1464,7 @@ end install_gemfile "source 'https://gem.repo1'" create_file("script.rb", "#!/usr/bin/env ruby\n\n#{code}") FileUtils.chmod(0o777, bundled_app("script.rb")) - bundle "exec ./script.rb", artifice: nil, env: { "RUBYOPT" => activation_warning_hack_rubyopt } + bundle "exec ./script.rb", env: { "RUBYOPT" => activation_warning_hack_rubyopt } expect(out).to eq("{}") end @@ -1525,22 +1524,7 @@ end end describe "after setup" do - it "allows calling #gem on random objects", bundler: "< 3" do - install_gemfile <<-G - source "https://gem.repo1" - gem "myrack" - G - - ruby <<-RUBY - require "bundler/setup" - Object.new.gem "myrack" - puts Gem.loaded_specs["myrack"].full_name - RUBY - - expect(out).to eq("myrack-1.0.0") - end - - it "keeps Kernel#gem private", bundler: "3" do + it "keeps Kernel#gem private" do install_gemfile <<-G source "https://gem.repo1" gem "myrack" @@ -1552,7 +1536,7 @@ end puts "FAIL" RUBY - expect(last_command.stdboth).not_to include "FAIL" + expect(stdboth).not_to include "FAIL" expect(err).to match(/private method [`']gem'/) end @@ -1568,7 +1552,7 @@ end puts "FAIL" RUBY - expect(last_command.stdboth).not_to include "FAIL" + expect(stdboth).not_to include "FAIL" expect(err).to match(/private method [`']require'/) end diff --git a/spec/bundler/spec_helper.rb b/spec/bundler/spec_helper.rb index 56f20999fd..35b249bb92 100644 --- a/spec/bundler/spec_helper.rb +++ b/spec/bundler/spec_helper.rb @@ -9,12 +9,14 @@ if File.expand_path(__FILE__) =~ %r{([^\w/\.:\-])} abort "The bundler specs cannot be run from a path that contains special characters (particularly #{$1.inspect})" end -# Bundler CLI will have different help text depending on whether this variable -# is set, since the `-e` flag `bundle gem` with require an explicit value if -# `EDITOR` is not set, but will use `EDITOR` by default is set. So make sure -# it's `nil` before loading bundler to get a consistent help text, since some -# tests rely on that. +# Bundler CLI will have different help text depending on whether any of these +# variables is set, since the `-e` flag `bundle gem` with require an explicit +# value if they are not set, but will use their value by default if set. So make +# sure they are `nil` before loading bundler to get a consistent help text, +# since some tests rely on that. ENV["EDITOR"] = nil +ENV["VISUAL"] = nil +ENV["BUNDLER_EDITOR"] = nil require "bundler" require "rspec/core" @@ -82,6 +84,10 @@ RSpec.configure do |config| require_relative "support/rubygems_ext" Spec::Rubygems.test_setup + + # Simulate bundler has not yet been loaded + ENV.replace(ENV.to_hash.delete_if {|k, _v| k.start_with?(Bundler::EnvironmentPreserver::BUNDLER_PREFIX) }) + ENV["BUNDLER_SPEC_RUN"] = "true" ENV["BUNDLE_USER_CONFIG"] = ENV["BUNDLE_USER_CACHE"] = ENV["BUNDLE_USER_PLUGIN"] = nil ENV["BUNDLE_APP_CONFIG"] = nil @@ -97,7 +103,7 @@ RSpec.configure do |config| build_repo1 - reset_paths! + reset! end config.around :each do |example| diff --git a/spec/bundler/support/artifice/compact_index_mirror_down.rb b/spec/bundler/support/artifice/compact_index_mirror_down.rb new file mode 100644 index 0000000000..88983c715d --- /dev/null +++ b/spec/bundler/support/artifice/compact_index_mirror_down.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative "helpers/compact_index" +require_relative "helpers/artifice" +require_relative "helpers/rack_request" + +module Artifice + module Net + class HTTPMirrorDown < HTTP + def connect + raise SocketError if address == "gem.mirror" + + super + end + end + + HTTP.endpoint = CompactIndexAPI + end + + replace_net_http(Net::HTTPMirrorDown) +end diff --git a/spec/bundler/support/artifice/helpers/compact_index.rb b/spec/bundler/support/artifice/helpers/compact_index.rb index ba331e483f..e61fe921ec 100644 --- a/spec/bundler/support/artifice/helpers/compact_index.rb +++ b/spec/bundler/support/artifice/helpers/compact_index.rb @@ -2,7 +2,7 @@ require_relative "endpoint" -$LOAD_PATH.unshift Dir[Spec::Path.base_system_gem_path.join("gems/compact_index*/lib")].first.to_s +$LOAD_PATH.unshift Dir[Spec::Path.scoped_base_system_gem_path.join("gems/compact_index*/lib")].first.to_s require "compact_index" require "digest" diff --git a/spec/bundler/support/artifice/helpers/endpoint.rb b/spec/bundler/support/artifice/helpers/endpoint.rb index 1ceadb5900..9590611dfe 100644 --- a/spec/bundler/support/artifice/helpers/endpoint.rb +++ b/spec/bundler/support/artifice/helpers/endpoint.rb @@ -27,7 +27,7 @@ class Endpoint < Sinatra::Base set :raise_errors, true set :show_exceptions, false - set :host_authorization, permitted_hosts: [".example.org", ".local", ".repo", ".repo1", ".repo2", ".repo3", ".repo4", ".rubygems.org", ".security", ".source", ".test", "127.0.0.1"] + set :host_authorization, permitted_hosts: [".example.org", ".local", ".mirror", ".repo", ".repo1", ".repo2", ".repo3", ".repo4", ".rubygems.org", ".security", ".source", ".test", "127.0.0.1"] def call!(*) super.tap do diff --git a/spec/bundler/support/build_metadata.rb b/spec/bundler/support/build_metadata.rb index 189100edb7..2eade4137b 100644 --- a/spec/bundler/support/build_metadata.rb +++ b/spec/bundler/support/build_metadata.rb @@ -8,11 +8,10 @@ module Spec include Spec::Path include Spec::Helpers - def write_build_metadata(dir: source_root) + def write_build_metadata(dir: source_root, version: Bundler::VERSION) build_metadata = { git_commit_sha: git_commit_sha, - built_at: loaded_gemspec.date.utc.strftime("%Y-%m-%d"), - release: true, + built_at: release_date_for(version, dir: dir), } replace_build_metadata(build_metadata, dir: dir) @@ -20,7 +19,7 @@ module Spec def reset_build_metadata(dir: source_root) build_metadata = { - release: false, + built_at: nil, } replace_build_metadata(build_metadata, dir: dir) @@ -44,6 +43,11 @@ module Spec ruby_core_tarball? ? "unknown" : git("rev-parse --short HEAD", source_root).strip end + def release_date_for(version, dir:) + changelog = File.expand_path("CHANGELOG.md", dir) + File.readlines(changelog)[2].scan(/^## #{Regexp.escape(version)} \((.*)\)/).first&.first if File.exist?(changelog) + end + extend self end end diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb index c566f87e56..5cfbed3864 100644 --- a/spec/bundler/support/builders.rb +++ b/spec/bundler/support/builders.rb @@ -24,10 +24,6 @@ module Spec Gem::Platform.new(platform) end - def rake_version - "13.2.1" - end - def build_repo1 build_repo gem_repo1 do FileUtils.cp rake_path, "#{gem_repo1}/gems/" @@ -110,10 +106,6 @@ module Spec s.platform = "x86-mingw32" end - build_gem "platform_specific" do |s| - s.platform = "x64-mingw32" - end - build_gem "platform_specific" do |s| s.platform = "x64-mingw-ucrt" end @@ -277,7 +269,7 @@ module Spec end def update_repo(path, build_compact_index: true) - exempted_caller = Gem.ruby_version >= Gem::Version.new("3.4.0.dev") ? "#{Module.nesting.first}#build_repo" : "build_repo" + exempted_caller = Gem.ruby_version >= Gem::Version.new("3.4.0.dev") && RUBY_ENGINE != "jruby" ? "#{Module.nesting.first}#build_repo" : "build_repo" if path == gem_repo1 && caller_locations(1, 1).first.label != exempted_caller raise "Updating gem_repo1 is unsupported -- use gem_repo2 instead" end @@ -285,9 +277,9 @@ module Spec @_build_path = "#{path}/gems" @_build_repo = File.basename(path) yield - with_gem_path_as base_system_gem_path do - Dir[base_system_gem_path.join("gems/rubygems-generate_index*/lib")].first || - raise("Could not find rubygems-generate_index lib directory in #{base_system_gem_path}") + with_gem_path_as scoped_base_system_gem_path do + Dir[scoped_base_system_gem_path.join("gems/rubygems-generate_index*/lib")].first || + raise("Could not find rubygems-generate_index lib directory in #{scoped_base_system_gem_path}") command = "generate_index" command += " --no-compact" if !build_compact_index && gem_command(command + " --help").include?("--[no-]compact") @@ -458,9 +450,10 @@ module Spec end @context.replace_version_file(@version, dir: build_path) + @context.replace_changelog(@version, dir: build_path) if options[:released] @context.replace_required_ruby_version(@required_ruby_version, dir: build_path) if @required_ruby_version - Spec::BuildMetadata.write_build_metadata(dir: build_path) + Spec::BuildMetadata.write_build_metadata(dir: build_path, version: @version) @context.gem_command "build #{@context.relative_gemspec}", dir: build_path diff --git a/spec/bundler/support/bundle b/spec/bundler/support/bundle new file mode 100755 index 0000000000..8f8b535295 --- /dev/null +++ b/spec/bundler/support/bundle @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative "../bundler/support/activate" + +load File.expand_path("bundle", Spec::Path.exedir) diff --git a/spec/bundler/support/bundle.rb b/spec/bundler/support/bundle.rb index 5d6d658040..aa7b121706 100644 --- a/spec/bundler/support/bundle.rb +++ b/spec/bundler/support/bundle.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true -require_relative "activate" +require_relative "path" -load File.expand_path("bundle", Spec::Path.bindir) +warn "#{__FILE__} is deprecated. Please use #{Spec::Path.dev_binstub} instead" + +load Spec::Path.dev_binstub diff --git a/spec/bundler/support/checksums.rb b/spec/bundler/support/checksums.rb index f3aa13ca9f..8e0dea4a71 100644 --- a/spec/bundler/support/checksums.rb +++ b/spec/bundler/support/checksums.rb @@ -58,7 +58,7 @@ module Spec begin enabled = (target_lockfile || lockfile).match?(/^CHECKSUMS$/) rescue Errno::ENOENT - enabled = Bundler.feature_flag.bundler_3_mode? + enabled = Bundler.feature_flag.bundler_4_mode? end checksums_section(enabled, &block) end diff --git a/spec/bundler/support/filters.rb b/spec/bundler/support/filters.rb index 663b7fa44b..15b4adf62e 100644 --- a/spec/bundler/support/filters.rb +++ b/spec/bundler/support/filters.rb @@ -1,17 +1,10 @@ # frozen_string_literal: true class RequirementChecker < Proc - def self.against(present, major_only: false) - present = present.split(".")[0] if major_only - provided = Gem::Version.new(present) - + def self.against(provided) new do |required| requirement = Gem::Requirement.new(required) - if major_only && !requirement.requirements.map(&:last).all? {|version| version.segments.one? } - raise "this filter only supports major versions, but #{required} was given" - end - !requirement.satisfied_by?(provided) end.tap do |checker| checker.provided = provided @@ -28,8 +21,7 @@ end RSpec.configure do |config| config.filter_run_excluding realworld: true - config.filter_run_excluding bundler: RequirementChecker.against(Bundler::VERSION, major_only: true) - config.filter_run_excluding rubygems: RequirementChecker.against(Gem::VERSION) + config.filter_run_excluding rubygems: RequirementChecker.against(Gem.rubygems_version) config.filter_run_excluding ruby_repo: !ENV["GEM_COMMAND"].nil? config.filter_run_excluding no_color_tty: Gem.win_platform? || !ENV["GITHUB_ACTION"].nil? config.filter_run_excluding permissions: Gem.win_platform? @@ -39,4 +31,8 @@ RSpec.configure do |config| config.filter_run_excluding man: Gem.win_platform? config.filter_run_when_matching :focus unless ENV["CI"] + + config.before(:each, bundler: "4") do + bundle "config simulate_version 4" + end end diff --git a/spec/bundler/support/hax.rb b/spec/bundler/support/hax.rb index 01bad64ce7..772a125ec7 100644 --- a/spec/bundler/support/hax.rb +++ b/spec/bundler/support/hax.rb @@ -51,4 +51,18 @@ module Gem File.singleton_class.prepend ReadOnly end + + if ENV["BUNDLER_SPEC_FAKE_RESOLVE"] + module FakeResolv + def getaddrinfo(host, port) + if host == ENV["BUNDLER_SPEC_FAKE_RESOLVE"] + [["AF_INET", port, "127.0.0.1", "127.0.0.1", 2, 2, 17]] + else + super + end + end + end + + Socket.singleton_class.prepend FakeResolv + end end diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb index 951c370064..acf13c9733 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -24,10 +24,6 @@ module Spec end FileUtils.mkdir_p(home) FileUtils.mkdir_p(tmpdir) - reset_paths! - end - - def reset_paths! Bundler.reset! Gem.clear_paths end @@ -71,7 +67,6 @@ module Spec bundle_bin ||= installed_bindir.join("bundle") env = options.delete(:env) || {} - preserve_ruby_flags = options.delete(:preserve_ruby_flags) requires = options.delete(:requires) || [] @@ -79,15 +74,14 @@ module Spec custom_load_path = options.delete(:load_path) load_path = [] - load_path << spec_dir load_path << custom_load_path if custom_load_path - build_ruby_options = { load_path: load_path, requires: requires, env: env } - build_ruby_options.merge!(artifice: options.delete(:artifice)) if options.key?(:artifice) + build_env_options = { load_path: load_path, requires: requires, env: env } + build_env_options.merge!(artifice: options.delete(:artifice)) if options.key?(:artifice) || cmd.start_with?("exec") match_source(cmd) - env, ruby_cmd = build_ruby_cmd(build_ruby_options) + env = build_env(build_env_options) raise_on_error = options.delete(:raise_on_error) @@ -102,8 +96,7 @@ module Spec end end.join - cmd = "#{ruby_cmd} #{bundle_bin} #{cmd}#{args}" - env["BUNDLER_SPEC_ORIGINAL_CMD"] = "#{ruby_cmd} #{bundle_bin}" if preserve_ruby_flags + cmd = "#{Gem.ruby} #{bundle_bin} #{cmd}#{args}" sys_exec(cmd, { env: env, dir: dir, raise_on_error: raise_on_error }, &block) end @@ -123,10 +116,10 @@ module Spec end def ruby(ruby, options = {}) - env, ruby_cmd = build_ruby_cmd({ artifice: nil }.merge(options)) + env = build_env({ artifice: nil }.merge(options)) escaped_ruby = ruby.shellescape options[:env] = env if env - sys_exec(%(#{ruby_cmd} -w -e #{escaped_ruby}), options) + sys_exec(%(#{Gem.ruby} -w -e #{escaped_ruby}), options) end def load_error_ruby(ruby, name, opts = {}) @@ -139,17 +132,19 @@ module Spec R end - def build_ruby_cmd(options = {}) - libs = options.delete(:load_path) - lib_option = libs ? "-I#{libs.join(File::PATH_SEPARATOR)}" : [] - + def build_env(options = {}) env = options.delete(:env) || {} + libs = options.delete(:load_path) || [] + env["RUBYOPT"] = opt_add("-I#{libs.join(File::PATH_SEPARATOR)}", env["RUBYOPT"]) if libs.any? + current_example = RSpec.current_example main_source = @gemfile_source if defined?(@gemfile_source) compact_index_main_source = main_source&.start_with?("https://gem.repo", "https://gems.security") requires = options.delete(:requires) || [] + requires << hax + artifice = options.delete(:artifice) do if current_example && current_example.metadata[:realworld] "vcr" @@ -172,11 +167,9 @@ module Spec requires << "#{Path.spec_dir}/support/artifice/#{artifice}.rb" end - requires << "#{Path.spec_dir}/support/hax.rb" + requires.each {|r| env["RUBYOPT"] = opt_add("-r#{r}", env["RUBYOPT"]) } - require_option = requires.map {|r| "-r#{r}" } - - [env, [Gem.ruby, *lib_option, *require_option].compact.join(" ")] + env end def gembin(cmd, options = {}) @@ -186,7 +179,7 @@ module Spec def gem_command(command, options = {}) env = options[:env] || {} - env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/hax.rb", env["RUBYOPT"]), ENV["RUBYOPT"]) + env["RUBYOPT"] = opt_add(opt_add("-r#{hax}", env["RUBYOPT"]), ENV["RUBYOPT"]) options[:env] = env # Sometimes `gem install` commands hang at dns resolution, which has a @@ -306,6 +299,10 @@ module Spec bundle :lock, opts end + def base_system_gems(*names, **options) + system_gems names.map {|name| find_base_path(name) }, **options + end + def system_gems(*gems) gems = gems.flatten options = gems.last.is_a?(Hash) ? gems.pop : {} @@ -315,7 +312,7 @@ module Spec gem_name = g.to_s if gem_name.start_with?("bundler") version = gem_name.match(/\Abundler-(?.*)\z/)[:version] if gem_name != "bundler" - with_built_bundler(version) {|gem_path| install_gem(gem_path, install_dir, default) } + with_built_bundler(version, released: options.fetch(:released, false)) {|gem_path| install_gem(gem_path, install_dir, default) } elsif %r{\A(?:[a-zA-Z]:)?/.*\.gem\z}.match?(gem_name) install_gem(gem_name, install_dir, default) else @@ -340,10 +337,10 @@ module Spec gem_command "install #{args} '#{path}'" end - def with_built_bundler(version = nil, &block) + def with_built_bundler(version = nil, opts = {}, &block) require_relative "builders" - Builders::BundlerBuilder.new(self, "bundler", version)._build(&block) + Builders::BundlerBuilder.new(self, "bundler", version)._build(opts, &block) end def with_gem_path_as(path) @@ -401,16 +398,6 @@ module Spec system_gems(*gems) end - def realworld_system_gems(*gems) - gems = gems.flatten - opts = gems.last.is_a?(Hash) ? gems.pop : {} - path = opts.fetch(:path, system_gem_path) - - gems.each do |gem| - gem_command "install --no-document --verbose --install-dir #{path} #{gem}" - end - end - def cache_gems(*gems, gem_repo: gem_repo1) gems = gems.flatten @@ -452,12 +439,6 @@ module Spec ruby_major_minor.map.with_index {|s, i| i == 1 ? s + 1 : s }.join(".") end - def previous_ruby_minor - return "2.7" if ruby_major_minor == [3, 0] - - ruby_major_minor.map.with_index {|s, i| i == 1 ? s - 1 : s }.join(".") - end - def ruby_major_minor Gem.ruby_version.segments[0..1] end @@ -515,34 +496,14 @@ module Spec end end - def require_rack - # need to hack, so we can require rack + def require_rack_test + # need to hack, so we can require rack for testing old_gem_home = ENV["GEM_HOME"] - ENV["GEM_HOME"] = Spec::Path.base_system_gem_path.to_s - require "rack" + ENV["GEM_HOME"] = Spec::Path.scoped_base_system_gem_path.to_s + require "rack/test" ENV["GEM_HOME"] = old_gem_home end - def wait_for_server(host, port, seconds = 15) - tries = 0 - sleep 0.5 - TCPSocket.new(host, port) - rescue StandardError => e - raise(e) if tries > (seconds * 2) - tries += 1 - retry - end - - def find_unused_port - port = 21_453 - begin - port += 1 while TCPSocket.new("127.0.0.1", port) - rescue StandardError - false - end - port - end - def exit_status_for_signal(signal_number) # For details see: https://en.wikipedia.org/wiki/Exit_status#Shell_and_scripts 128 + signal_number diff --git a/spec/bundler/support/indexes.rb b/spec/bundler/support/indexes.rb index 2d592808f0..1fbdd49abe 100644 --- a/spec/bundler/support/indexes.rb +++ b/spec/bundler/support/indexes.rb @@ -122,7 +122,7 @@ module Spec end versions "1.0 1.2 1.2.1 1.2.2 1.3 1.3.0.1 1.3.5 1.4.0 1.4.2 1.4.2.1" do |version| - platforms "ruby java mswin32 mingw32 x64-mingw32" do |platform| + platforms "ruby java mswin32 mingw32 x64-mingw-ucrt" do |platform| next if version == v("1.4.2.1") && platform != pl("x86-mswin32") next if version == v("1.4.2") && platform == pl("x86-mswin32") gem "nokogiri", version, platform do diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index cbfafd4575..b5ea9bc5b6 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -45,8 +45,16 @@ module Spec @dev_gemfile ||= tool_dir.join("dev_gems.rb") end + def dev_binstub + @dev_binstub ||= bindir.join("bundle") + end + def bindir - @bindir ||= source_root.join(ruby_core? ? "libexec" : "exe") + @bindir ||= source_root.join(ruby_core? ? "spec/bin" : "bin") + end + + def exedir + @exedir ||= source_root.join(ruby_core? ? "libexec" : "exe") end def installed_bindir @@ -63,7 +71,7 @@ module Spec def path env_path = ENV["PATH"] - env_path = env_path.split(File::PATH_SEPARATOR).reject {|path| path == bindir.to_s }.join(File::PATH_SEPARATOR) if ruby_core? + env_path = env_path.split(File::PATH_SEPARATOR).reject {|path| path == exedir.to_s }.join(File::PATH_SEPARATOR) if ruby_core? env_path end @@ -75,6 +83,10 @@ module Spec @man_dir ||= lib_dir.join("bundler/man") end + def hax + @hax ||= spec_dir.join("support/hax.rb") + end + def tracked_files @tracked_files ||= git_ls_files(tracked_files_glob) end @@ -124,7 +136,7 @@ module Spec end def default_bundle_path(*path) - if Bundler.feature_flag.default_install_uses_path? + if Bundler.feature_flag.bundler_4_mode? local_gem_path(*path) else system_gem_path(*path) @@ -171,19 +183,19 @@ module Spec bundled_app("Gemfile.lock") end - def base_system_gem_path - scoped_gem_path(base_system_gems) + def scoped_base_system_gem_path + scoped_gem_path(base_system_gem_path) end - def base_system_gems + def base_system_gem_path tmp_root.join("gems/base") end - def rubocop_gems + def rubocop_gem_path tmp_root.join("gems/rubocop") end - def standard_gems + def standard_gem_path tmp_root.join("gems/standard") end @@ -195,31 +207,31 @@ module Spec end def gem_repo1(*args) - tmp("gems/remote1", *args) + gem_path("remote1", *args) end def gem_repo_missing(*args) - tmp("gems/missing", *args) + gem_path("missing", *args) end def gem_repo2(*args) - tmp("gems/remote2", *args) + gem_path("remote2", *args) end def gem_repo3(*args) - tmp("gems/remote3", *args) + gem_path("remote3", *args) end def gem_repo4(*args) - tmp("gems/remote4", *args) + gem_path("remote4", *args) end def security_repo(*args) - tmp("gems/security_repo", *args) + gem_path("security_repo", *args) end def system_gem_path(*path) - tmp("gems/system", *path) + gem_path("system", *path) end def pristine_system_gem_path @@ -234,6 +246,10 @@ module Spec base.join(Gem.ruby_engine, RbConfig::CONFIG["ruby_version"]) end + def gem_path(*args) + tmp("gems", *args) + end + def lib_path(*args) tmp("libs", *args) end @@ -261,7 +277,7 @@ module Spec def replace_version_file(version, dir: source_root) version_file = File.expand_path("lib/bundler/version.rb", dir) contents = File.read(version_file) - contents.sub!(/(^\s+VERSION\s*=\s*)"#{Gem::Version::VERSION_PATTERN}"/, %(\\1"#{version}")) + contents.sub!(/(^\s+VERSION\s*=\s*).*$/, %(\\1"#{version}")) File.open(version_file, "w") {|f| f << contents } end @@ -272,12 +288,23 @@ module Spec File.open(gemspec_file, "w") {|f| f << contents } end + def replace_changelog(version, dir:) + changelog = File.expand_path("CHANGELOG.md", dir) + contents = File.readlines(changelog) + contents = [contents[0], contents[1], "## #{version} (2100-01-01)\n", *contents[3..-1]].join + File.open(changelog, "w") {|f| f << contents } + end + def git_root ruby_core? ? source_root : source_root.parent end def rake_path - Dir["#{base_system_gems}/*/*/**/rake*.gem"].first + find_base_path("rake") + end + + def rake_version + File.basename(rake_path).delete_prefix("rake-").delete_suffix(".gem") end def sinatra_dependency_paths @@ -291,11 +318,15 @@ module Spec logger cgi ] - Dir[base_system_gem_path.join("gems/{#{deps.join(",")}}-*/lib")].map(&:to_s) + Dir[scoped_base_system_gem_path.join("gems/{#{deps.join(",")}}-*/lib")].map(&:to_s) end private + def find_base_path(name) + Dir["#{scoped_base_system_gem_path}/**/#{name}-*.gem"].first + end + def git_ls_files(glob) skip "Not running on a git context, since running tests from a tarball" if ruby_core_tarball? diff --git a/spec/bundler/support/platforms.rb b/spec/bundler/support/platforms.rb index e09c6fe66a..56a0843005 100644 --- a/spec/bundler/support/platforms.rb +++ b/spec/bundler/support/platforms.rb @@ -2,12 +2,18 @@ module Spec module Platforms - include Bundler::GemHelpers - def not_local generic_local_platform == Gem::Platform::RUBY ? "java" : Gem::Platform::RUBY end + def local_platform + Bundler.local_platform + end + + def generic_local_platform + Gem::Platform.generic(local_platform) + end + def local_tag if Gem.java_platform? :jruby @@ -61,7 +67,7 @@ module Spec end def generic_default_locked_platform - return unless generic_local_platform_is_ruby? + return unless Bundler::MatchPlatform.generic_local_platform_is_ruby? Gem::Platform::RUBY end diff --git a/spec/bundler/support/rubygems_ext.rb b/spec/bundler/support/rubygems_ext.rb index 46261493db..2d681529aa 100644 --- a/spec/bundler/support/rubygems_ext.rb +++ b/spec/bundler/support/rubygems_ext.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -abort "RubyGems only supports Ruby 3.1 or higher" if RUBY_VERSION < "3.1.0" +abort "RubyGems only supports Ruby 3.2 or higher" if RUBY_VERSION < "3.2.0" require_relative "path" @@ -28,6 +28,9 @@ module Spec end def test_setup + # Install test dependencies unless parallel-rspec is being used, since in that case they should be setup already + install_test_deps unless ENV["RSPEC_FORMATTER_OUTPUT_ID"] + setup_test_paths require "fileutils" @@ -49,13 +52,13 @@ module Spec def setup_test_paths ENV["BUNDLE_PATH"] = nil ENV["PATH"] = [Path.system_gem_path("bin"), ENV["PATH"]].join(File::PATH_SEPARATOR) - ENV["PATH"] = [Path.bindir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core? + ENV["PATH"] = [Path.exedir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core? end def install_test_deps - dev_bundle("install", gemfile: test_gemfile, path: Path.base_system_gems.to_s) - dev_bundle("install", gemfile: rubocop_gemfile, path: Path.rubocop_gems.to_s) - dev_bundle("install", gemfile: standard_gemfile, path: Path.standard_gems.to_s) + dev_bundle("install", gemfile: test_gemfile, path: Path.base_system_gem_path.to_s) + dev_bundle("install", gemfile: rubocop_gemfile, path: Path.rubocop_gem_path.to_s) + dev_bundle("install", gemfile: standard_gemfile, path: Path.standard_gem_path.to_s) require_relative "helpers" Helpers.install_dev_bundler @@ -97,7 +100,7 @@ module Spec require "shellwords" # We don't use `Open3` here because it does not work on JRuby + Windows - output = `ruby #{File.expand_path("support/bundle.rb", Path.spec_dir)} #{args.shelljoin}` + output = `ruby #{Path.dev_binstub} #{args.shelljoin}` raise output unless $?.success? output ensure diff --git a/spec/bundler/support/silent_logger.rb b/spec/bundler/support/silent_logger.rb deleted file mode 100644 index 4b270330fd..0000000000 --- a/spec/bundler/support/silent_logger.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -require "webrick" -module Spec - class SilentLogger < WEBrick::BasicLog - def initialize(log_file = nil, level = nil) - super(log_file, level || FATAL) - end - end -end diff --git a/spec/bundler/support/subprocess.rb b/spec/bundler/support/subprocess.rb index a4842166b9..e160d5c9cf 100644 --- a/spec/bundler/support/subprocess.rb +++ b/spec/bundler/support/subprocess.rb @@ -22,6 +22,10 @@ module Spec last_command.stderr end + def stdboth + last_command.stdboth + end + def exitstatus last_command.exitstatus end diff --git a/spec/bundler/update/force_spec.rb b/spec/bundler/update/force_spec.rb new file mode 100644 index 0000000000..325f58088a --- /dev/null +++ b/spec/bundler/update/force_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +RSpec.describe "bundle update" do + before :each do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + end + + it "re-installs installed gems with --force" do + myrack_lib = default_bundle_path("gems/myrack-1.0.0/lib/myrack.rb") + myrack_lib.open("w") {|f| f.write("blah blah blah") } + bundle :update, force: true + + expect(out).to include "Installing myrack 1.0.0" + expect(myrack_lib.open(&:read)).to eq("MYRACK = '1.0.0'\n") + expect(the_bundle).to include_gems "myrack 1.0.0" + end + + it "re-installs installed gems with --redownload" do + myrack_lib = default_bundle_path("gems/myrack-1.0.0/lib/myrack.rb") + myrack_lib.open("w") {|f| f.write("blah blah blah") } + bundle :update, redownload: true + + expect(out).to include "Installing myrack 1.0.0" + expect(myrack_lib.open(&:read)).to eq("MYRACK = '1.0.0'\n") + expect(the_bundle).to include_gems "myrack 1.0.0" + end +end diff --git a/spec/bundler/update/redownload_spec.rb b/spec/bundler/update/redownload_spec.rb deleted file mode 100644 index 66437fb938..0000000000 --- a/spec/bundler/update/redownload_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "bundle update" do - before :each do - install_gemfile <<-G - source "https://gem.repo1" - gem "myrack" - G - end - - describe "with --force" do - it "shows a deprecation when single flag passed", bundler: 2 do - bundle "update myrack --force" - expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - - it "shows a deprecation when multiple flags passed", bundler: 2 do - bundle "update myrack --no-color --force" - expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - end - - describe "with --redownload" do - it "does not show a deprecation when single flag passed" do - bundle "update myrack --redownload" - expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - - it "does not show a deprecation when single multiple flags passed" do - bundle "update myrack --no-color --redownload" - expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - - it "re-installs installed gems" do - myrack_lib = default_bundle_path("gems/myrack-1.0.0/lib/myrack.rb") - myrack_lib.open("w") {|f| f.write("blah blah blah") } - bundle :update, redownload: true - - expect(out).to include "Installing myrack 1.0.0" - expect(myrack_lib.open(&:read)).to eq("MYRACK = '1.0.0'\n") - expect(the_bundle).to include_gems "myrack 1.0.0" - end - end -end diff --git a/spec/ruby/core/array/fetch_values_spec.rb b/spec/ruby/core/array/fetch_values_spec.rb index 075dcc7a52..cf377b3b71 100644 --- a/spec/ruby/core/array/fetch_values_spec.rb +++ b/spec/ruby/core/array/fetch_values_spec.rb @@ -21,7 +21,7 @@ describe "Array#fetch_values" do describe "with unmatched indexes" do it "raises a index error if no block is provided" do - -> { @array.fetch_values(0, 1, 44) }.should raise_error(IndexError) + -> { @array.fetch_values(0, 1, 44) }.should raise_error(IndexError, "index 44 outside of array bounds: -3...3") end it "returns the default value from block" do @@ -42,8 +42,14 @@ describe "Array#fetch_values" do @array.fetch_values(obj).should == [:c] end + it "does not support a Range object as argument" do + -> { + @array.fetch_values(1..2) + }.should raise_error(TypeError, "no implicit conversion of Range into Integer") + end + it "raises a TypeError when the passed argument can't be coerced to Integer" do - -> { [].fetch_values("cat") }.should raise_error(TypeError) + -> { [].fetch_values("cat") }.should raise_error(TypeError, "no implicit conversion of String into Integer") end end end diff --git a/spec/ruby/core/array/pack/a_spec.rb b/spec/ruby/core/array/pack/a_spec.rb index 75b8d63b1e..8245cd5470 100644 --- a/spec/ruby/core/array/pack/a_spec.rb +++ b/spec/ruby/core/array/pack/a_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/array/pack/at_spec.rb b/spec/ruby/core/array/pack/at_spec.rb index 3942677913..bb9801440a 100644 --- a/spec/ruby/core/array/pack/at_spec.rb +++ b/spec/ruby/core/array/pack/at_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/array/pack/b_spec.rb b/spec/ruby/core/array/pack/b_spec.rb index ec82b7d1ab..247a9ca023 100644 --- a/spec/ruby/core/array/pack/b_spec.rb +++ b/spec/ruby/core/array/pack/b_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/array/pack/c_spec.rb b/spec/ruby/core/array/pack/c_spec.rb index ac133ff9b6..47b71b663d 100644 --- a/spec/ruby/core/array/pack/c_spec.rb +++ b/spec/ruby/core/array/pack/c_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' diff --git a/spec/ruby/core/array/pack/comment_spec.rb b/spec/ruby/core/array/pack/comment_spec.rb index 254c827ccc..daf1cff06a 100644 --- a/spec/ruby/core/array/pack/comment_spec.rb +++ b/spec/ruby/core/array/pack/comment_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' diff --git a/spec/ruby/core/array/pack/h_spec.rb b/spec/ruby/core/array/pack/h_spec.rb index 2c1dac8d4a..ba188874ba 100644 --- a/spec/ruby/core/array/pack/h_spec.rb +++ b/spec/ruby/core/array/pack/h_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/array/pack/m_spec.rb b/spec/ruby/core/array/pack/m_spec.rb index c6364af12d..a80f91275c 100644 --- a/spec/ruby/core/array/pack/m_spec.rb +++ b/spec/ruby/core/array/pack/m_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/array/pack/shared/float.rb b/spec/ruby/core/array/pack/shared/float.rb index 1780d7635e..76c800b74d 100644 --- a/spec/ruby/core/array/pack/shared/float.rb +++ b/spec/ruby/core/array/pack/shared/float.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :array_pack_float_le, shared: true do it "encodes a positive Float" do diff --git a/spec/ruby/core/array/pack/shared/integer.rb b/spec/ruby/core/array/pack/shared/integer.rb index a89b5b733b..61f7cca184 100644 --- a/spec/ruby/core/array/pack/shared/integer.rb +++ b/spec/ruby/core/array/pack/shared/integer.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :array_pack_16bit_le, shared: true do it "encodes the least significant 16 bits of a positive number" do diff --git a/spec/ruby/core/array/pack/shared/string.rb b/spec/ruby/core/array/pack/shared/string.rb index 2f70dc3951..805f78b53b 100644 --- a/spec/ruby/core/array/pack/shared/string.rb +++ b/spec/ruby/core/array/pack/shared/string.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :array_pack_string, shared: true do it "adds count bytes of a String to the output" do ["abc"].pack(pack_format(2)).should == "ab" diff --git a/spec/ruby/core/array/pack/u_spec.rb b/spec/ruby/core/array/pack/u_spec.rb index b20093a647..1f84095ac4 100644 --- a/spec/ruby/core/array/pack/u_spec.rb +++ b/spec/ruby/core/array/pack/u_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/array/pack/w_spec.rb b/spec/ruby/core/array/pack/w_spec.rb index 48ed4496a5..e770288d67 100644 --- a/spec/ruby/core/array/pack/w_spec.rb +++ b/spec/ruby/core/array/pack/w_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/array/pack/x_spec.rb b/spec/ruby/core/array/pack/x_spec.rb index 86c3ad1aa4..012fe4567f 100644 --- a/spec/ruby/core/array/pack/x_spec.rb +++ b/spec/ruby/core/array/pack/x_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/array/pack/z_spec.rb b/spec/ruby/core/array/pack/z_spec.rb index 0757d16e31..60f8f7bf10 100644 --- a/spec/ruby/core/array/pack/z_spec.rb +++ b/spec/ruby/core/array/pack/z_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/data/deconstruct_keys_spec.rb b/spec/ruby/core/data/deconstruct_keys_spec.rb index 5cae4cbd68..df378f8b98 100644 --- a/spec/ruby/core/data/deconstruct_keys_spec.rb +++ b/spec/ruby/core/data/deconstruct_keys_spec.rb @@ -1,10 +1,11 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe "Data#deconstruct" do +describe "Data#deconstruct_keys" do it "returns a hash of attributes" do klass = Data.define(:x, :y) d = klass.new(1, 2) + d.deconstruct_keys([:x, :y]).should == {x: 1, y: 2} end @@ -29,6 +30,7 @@ describe "Data#deconstruct" do it "accepts string attribute names" do klass = Data.define(:x, :y) d = klass.new(1, 2) + d.deconstruct_keys(['x', 'y']).should == {'x' => 1, 'y' => 2} end @@ -58,6 +60,7 @@ describe "Data#deconstruct" do it "returns an empty hash when there are more keys than attributes" do klass = Data.define(:x, :y) d = klass.new(1, 2) + d.deconstruct_keys([:x, :y, :x]).should == {} end @@ -84,6 +87,28 @@ describe "Data#deconstruct" do d.deconstruct_keys(nil).should == {x: 1, y: 2} end + it "tries to convert a key with #to_int if index is not a String nor a Symbol, but responds to #to_int" do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + key = mock("to_int") + key.should_receive(:to_int).and_return(1) + + d.deconstruct_keys([key]).should == { key => 2 } + end + + it "raises a TypeError if the conversion with #to_int does not return an Integer" do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + key = mock("to_int") + key.should_receive(:to_int).and_return("not an Integer") + + -> { + d.deconstruct_keys([key]) + }.should raise_error(TypeError, /can't convert MockObject to Integer/) + end + it "raises TypeError if index is not a String, a Symbol and not convertible to Integer " do klass = Data.define(:x, :y) d = klass.new(1, 2) diff --git a/spec/ruby/core/data/fixtures/classes.rb b/spec/ruby/core/data/fixtures/classes.rb index 5db263fa20..ffd361d781 100644 --- a/spec/ruby/core/data/fixtures/classes.rb +++ b/spec/ruby/core/data/fixtures/classes.rb @@ -9,5 +9,20 @@ module DataSpecs end class DataSubclass < Data; end + + MeasureSubclass = Class.new(Measure) do + def initialize(amount:, unit:) + super + end + end + + Empty = Data.define() + + DataWithOverriddenInitialize = Data.define(:amount, :unit) do + def initialize(*rest, **kw) + super + ScratchPad.record [:initialize, rest, kw] + end + end end end diff --git a/spec/ruby/core/data/initialize_spec.rb b/spec/ruby/core/data/initialize_spec.rb index 37a6c8f2dd..9d272780a8 100644 --- a/spec/ruby/core/data/initialize_spec.rb +++ b/spec/ruby/core/data/initialize_spec.rb @@ -60,4 +60,55 @@ describe "Data#initialize" do e.message.should.include?("unknown keyword: :system") } end + + it "supports super from a subclass" do + ms = DataSpecs::MeasureSubclass.new(amount: 1, unit: "km") + + ms.amount.should == 1 + ms.unit.should == "km" + end + + it "supports Data with no fields" do + -> { DataSpecs::Empty.new }.should_not raise_error + end + + it "can be overridden" do + ScratchPad.record [] + + measure_class = Data.define(:amount, :unit) do + def initialize(*, **) + super + ScratchPad << :initialize + end + end + + measure_class.new(42, "m") + ScratchPad.recorded.should == [:initialize] + end + + context "when it is overridden" do + it "is called with keyword arguments when given positional arguments" do + ScratchPad.clear + DataSpecs::DataWithOverriddenInitialize.new(42, "m") + ScratchPad.recorded.should == [:initialize, [], {amount: 42, unit: "m"}] + end + + it "is called with keyword arguments when given keyword arguments" do + ScratchPad.clear + DataSpecs::DataWithOverriddenInitialize.new(amount: 42, unit: "m") + ScratchPad.recorded.should == [:initialize, [], {amount: 42, unit: "m"}] + end + + it "is called with keyword arguments when given alternative positional arguments" do + ScratchPad.clear + DataSpecs::DataWithOverriddenInitialize[42, "m"] + ScratchPad.recorded.should == [:initialize, [], {amount: 42, unit: "m"}] + end + + it "is called with keyword arguments when given alternative keyword arguments" do + ScratchPad.clear + DataSpecs::DataWithOverriddenInitialize[amount: 42, unit: "m"] + ScratchPad.recorded.should == [:initialize, [], {amount: 42, unit: "m"}] + end + end end diff --git a/spec/ruby/core/data/shared/inspect.rb b/spec/ruby/core/data/shared/inspect.rb index 7f54a46de3..6cd5664da7 100644 --- a/spec/ruby/core/data/shared/inspect.rb +++ b/spec/ruby/core/data/shared/inspect.rb @@ -50,5 +50,13 @@ describe :data_inspect, shared: true do a.send(@method).should == "#>" end + + it "returns string representation with recursive attribute replaced with ... when an anonymous class" do + klass = Class.new(DataSpecs::Measure) + a = klass.allocate + a.send(:initialize, amount: 42, unit: a) + + a.send(@method).should =~ /#:\.\.\.>>/ + end end end diff --git a/spec/ruby/core/data/with_spec.rb b/spec/ruby/core/data/with_spec.rb index 9cd2d57335..fd0a99d1fa 100644 --- a/spec/ruby/core/data/with_spec.rb +++ b/spec/ruby/core/data/with_spec.rb @@ -30,4 +30,28 @@ describe "Data#with" do data.with(4, "m") }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 0)") end + + it "does not depend on the Data.new method" do + subclass = Class.new(DataSpecs::Measure) + data = subclass.new(amount: 42, unit: "km") + + def subclass.new(*) + raise "Data.new is called" + end + + data_copy = data.with(unit: "m") + data_copy.amount.should == 42 + data_copy.unit.should == "m" + end + + ruby_version_is "3.3" do + it "calls #initialize" do + data = DataSpecs::DataWithOverriddenInitialize.new(42, "m") + ScratchPad.clear + + data.with(amount: 0) + + ScratchPad.recorded.should == [:initialize, [], {amount: 0, unit: "m"}] + end + end end diff --git a/spec/ruby/core/encoding/compatible_spec.rb b/spec/ruby/core/encoding/compatible_spec.rb index 61a651893b..31376a3b75 100644 --- a/spec/ruby/core/encoding/compatible_spec.rb +++ b/spec/ruby/core/encoding/compatible_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' @@ -171,7 +171,7 @@ describe "Encoding.compatible? String, String" do # Use the following script to regenerate the matrix: # # ``` -# # -*- encoding: binary -*- +# # encoding: binary # # ENCODINGS = [ # "US-ASCII", diff --git a/spec/ruby/core/encoding/converter/convert_spec.rb b/spec/ruby/core/encoding/converter/convert_spec.rb index 7f249d90a3..8533af4565 100644 --- a/spec/ruby/core/encoding/converter/convert_spec.rb +++ b/spec/ruby/core/encoding/converter/convert_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary # frozen_string_literal: true require_relative '../../../spec_helper' diff --git a/spec/ruby/core/encoding/converter/last_error_spec.rb b/spec/ruby/core/encoding/converter/last_error_spec.rb index 78779be70b..ff2a2b4cbe 100644 --- a/spec/ruby/core/encoding/converter/last_error_spec.rb +++ b/spec/ruby/core/encoding/converter/last_error_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' describe "Encoding::Converter#last_error" do diff --git a/spec/ruby/core/encoding/converter/new_spec.rb b/spec/ruby/core/encoding/converter/new_spec.rb index db9c3364d7..a7bef53809 100644 --- a/spec/ruby/core/encoding/converter/new_spec.rb +++ b/spec/ruby/core/encoding/converter/new_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' describe "Encoding::Converter.new" do diff --git a/spec/ruby/core/encoding/converter/primitive_convert_spec.rb b/spec/ruby/core/encoding/converter/primitive_convert_spec.rb index 63f25eddef..e4aeed103e 100644 --- a/spec/ruby/core/encoding/converter/primitive_convert_spec.rb +++ b/spec/ruby/core/encoding/converter/primitive_convert_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary # frozen_string_literal: false require_relative '../../../spec_helper' diff --git a/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb b/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb index 668eb9a924..5ee8b1fecd 100644 --- a/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb +++ b/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary # frozen_string_literal: false require_relative '../../../spec_helper' diff --git a/spec/ruby/core/encoding/converter/putback_spec.rb b/spec/ruby/core/encoding/converter/putback_spec.rb index e19fe6c314..04bb565655 100644 --- a/spec/ruby/core/encoding/converter/putback_spec.rb +++ b/spec/ruby/core/encoding/converter/putback_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' describe "Encoding::Converter#putback" do diff --git a/spec/ruby/core/encoding/fixtures/classes.rb b/spec/ruby/core/encoding/fixtures/classes.rb index 12e9a4f348..943865e8d8 100644 --- a/spec/ruby/core/encoding/fixtures/classes.rb +++ b/spec/ruby/core/encoding/fixtures/classes.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary module EncodingSpecs class UndefinedConversionError def self.exception diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb index d2fc360dce..8b7e87960f 100644 --- a/spec/ruby/core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb +++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative "../../../spec_helper" require_relative '../fixtures/classes' diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb index 8a3f3de69a..83606f77b4 100644 --- a/spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb +++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' describe "Encoding::InvalidByteSequenceError#incomplete_input?" do diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb index a5e2824984..e5ad0a61bd 100644 --- a/spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb +++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative "../../../spec_helper" require_relative '../fixtures/classes' diff --git a/spec/ruby/core/encoding/replicate_spec.rb b/spec/ruby/core/encoding/replicate_spec.rb index 8d6f843fdf..2da998837f 100644 --- a/spec/ruby/core/encoding/replicate_spec.rb +++ b/spec/ruby/core/encoding/replicate_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' describe "Encoding#replicate" do diff --git a/spec/ruby/core/enumerable/filter_spec.rb b/spec/ruby/core/enumerable/filter_spec.rb index c9ee23c541..1c3a7e9ff5 100644 --- a/spec/ruby/core/enumerable/filter_spec.rb +++ b/spec/ruby/core/enumerable/filter_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/find_all' describe "Enumerable#filter" do - it_behaves_like(:enumerable_find_all, :filter) + it_behaves_like :enumerable_find_all, :filter end diff --git a/spec/ruby/core/enumerable/fixtures/classes.rb b/spec/ruby/core/enumerable/fixtures/classes.rb index e30b57d294..b5feafcfb7 100644 --- a/spec/ruby/core/enumerable/fixtures/classes.rb +++ b/spec/ruby/core/enumerable/fixtures/classes.rb @@ -38,12 +38,14 @@ module EnumerableSpecs class Empty include Enumerable def each + self end end class EmptyWithSize include Enumerable def each + self end def size 0 diff --git a/spec/ruby/core/enumerable/to_set_spec.rb b/spec/ruby/core/enumerable/to_set_spec.rb index 966baae1d9..e0437fea61 100644 --- a/spec/ruby/core/enumerable/to_set_spec.rb +++ b/spec/ruby/core/enumerable/to_set_spec.rb @@ -11,17 +11,28 @@ describe "Enumerable#to_set" do [1, 2, 3].to_set { |x| x * x }.should == Set[1, 4, 9] end - it "instantiates an object of provided as the first argument set class" do - set = [1, 2, 3].to_set(EnumerableSpecs::SetSubclass) - set.should be_kind_of(EnumerableSpecs::SetSubclass) - set.to_a.sort.should == [1, 2, 3] + ruby_version_is "3.5" do + it "instantiates an object of provided as the first argument set class" do + set = nil + proc{set = [1, 2, 3].to_set(EnumerableSpecs::SetSubclass)}.should complain(/Enumerable#to_set/) + set.should be_kind_of(EnumerableSpecs::SetSubclass) + set.to_a.sort.should == [1, 2, 3] + end + end + + ruby_version_is ""..."3.5" do + it "instantiates an object of provided as the first argument set class" do + set = [1, 2, 3].to_set(EnumerableSpecs::SetSubclass) + set.should be_kind_of(EnumerableSpecs::SetSubclass) + set.to_a.sort.should == [1, 2, 3] + end end it "does not need explicit `require 'set'`" do output = ruby_exe(<<~RUBY, options: '--disable-gems', args: '2>&1') - puts [1, 2, 3].to_set + puts [1, 2, 3].to_set.to_a.inspect RUBY - output.chomp.should == "#" + output.chomp.should == "[1, 2, 3]" end end diff --git a/spec/ruby/core/env/element_reference_spec.rb b/spec/ruby/core/env/element_reference_spec.rb index 560c127a9c..66a9bc9690 100644 --- a/spec/ruby/core/env/element_reference_spec.rb +++ b/spec/ruby/core/env/element_reference_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'fixtures/common' diff --git a/spec/ruby/core/exception/frozen_error_spec.rb b/spec/ruby/core/exception/frozen_error_spec.rb index 979ec2ff98..51eb79cace 100644 --- a/spec/ruby/core/exception/frozen_error_spec.rb +++ b/spec/ruby/core/exception/frozen_error_spec.rb @@ -21,6 +21,20 @@ describe "FrozenError#receiver" do end end +describe "FrozenError#message" do + it "includes a receiver" do + object = Object.new + object.freeze + + -> { + def object.x; end + }.should raise_error(FrozenError, "can't modify frozen object: #{object}") + + object = [].freeze + -> { object << nil }.should raise_error(FrozenError, "can't modify frozen Array: []") + end +end + describe "Modifying a frozen object" do context "#inspect is redefined and modifies the object" do it "returns ... instead of String representation of object" do diff --git a/spec/ruby/core/exception/full_message_spec.rb b/spec/ruby/core/exception/full_message_spec.rb index b07c6d4829..0761d2b40c 100644 --- a/spec/ruby/core/exception/full_message_spec.rb +++ b/spec/ruby/core/exception/full_message_spec.rb @@ -211,4 +211,16 @@ describe "Exception#full_message" do e.full_message(highlight: false).lines.first.should =~ /RuntimeError/ e.full_message(highlight: true).lines.first.should =~ /#{Regexp.escape("\e[1;4mRuntimeError\e[m")}/ end + + it "allows cause with empty backtrace" do + begin + raise RuntimeError.new("Some runtime error"), cause: RuntimeError.new("Some other runtime error") + rescue => e + end + + full_message = e.full_message + full_message.should include "RuntimeError" + full_message.should include "Some runtime error" + full_message.should include "Some other runtime error" + end end diff --git a/spec/ruby/core/exception/set_backtrace_spec.rb b/spec/ruby/core/exception/set_backtrace_spec.rb index 12c1da919c..2cd93326ec 100644 --- a/spec/ruby/core/exception/set_backtrace_spec.rb +++ b/spec/ruby/core/exception/set_backtrace_spec.rb @@ -1,13 +1,8 @@ require_relative '../../spec_helper' require_relative 'fixtures/common' +require_relative 'shared/set_backtrace' describe "Exception#set_backtrace" do - it "accepts an Array of Strings" do - err = RuntimeError.new - err.set_backtrace ["unhappy"] - err.backtrace.should == ["unhappy"] - end - it "allows the user to set the backtrace from a rescued exception" do bt = ExceptionSpecs::Backtrace.backtrace err = RuntimeError.new @@ -20,65 +15,9 @@ describe "Exception#set_backtrace" do err.backtrace_locations.should == nil end - ruby_version_is "3.4" do - it "allows the user to set backtrace locations from a rescued exception" do - bt_locations = ExceptionSpecs::Backtrace.backtrace_locations - err = RuntimeError.new - err.backtrace.should == nil - err.backtrace_locations.should == nil - - err.set_backtrace bt_locations - - err.backtrace_locations.size.should == bt_locations.size - err.backtrace_locations.each_with_index do |loc, index| - other_loc = bt_locations[index] - - loc.path.should == other_loc.path - loc.label.should == other_loc.label - loc.base_label.should == other_loc.base_label - loc.lineno.should == other_loc.lineno - loc.absolute_path.should == other_loc.absolute_path - loc.to_s.should == other_loc.to_s - end - err.backtrace.size.should == err.backtrace_locations.size - end - end - - it "accepts an empty Array" do + it_behaves_like :exception_set_backtrace, -> backtrace { err = RuntimeError.new - err.set_backtrace [] - err.backtrace.should == [] - end - - it "accepts a String" do - err = RuntimeError.new - err.set_backtrace "unhappy" - err.backtrace.should == ["unhappy"] - end - - it "accepts nil" do - err = RuntimeError.new - err.set_backtrace nil - err.backtrace.should be_nil - end - - it "raises a TypeError when passed a Symbol" do - err = RuntimeError.new - -> { err.set_backtrace :unhappy }.should raise_error(TypeError) - end - - it "raises a TypeError when the Array contains a Symbol" do - err = RuntimeError.new - -> { err.set_backtrace ["String", :unhappy] }.should raise_error(TypeError) - end - - it "raises a TypeError when the array contains nil" do - err = Exception.new - -> { err.set_backtrace ["String", nil] }.should raise_error(TypeError) - end - - it "raises a TypeError when the argument is a nested array" do - err = Exception.new - -> { err.set_backtrace ["String", ["String"]] }.should raise_error(TypeError) - end + err.set_backtrace(backtrace) + err + } end diff --git a/spec/ruby/core/exception/shared/set_backtrace.rb b/spec/ruby/core/exception/shared/set_backtrace.rb new file mode 100644 index 0000000000..c6213b42b4 --- /dev/null +++ b/spec/ruby/core/exception/shared/set_backtrace.rb @@ -0,0 +1,64 @@ +require_relative '../fixtures/common' + +describe :exception_set_backtrace, shared: true do + it "accepts an Array of Strings" do + err = @method.call(["unhappy"]) + err.backtrace.should == ["unhappy"] + end + + it "allows the user to set the backtrace from a rescued exception" do + bt = ExceptionSpecs::Backtrace.backtrace + err = @method.call(bt) + err.backtrace.should == bt + end + + ruby_version_is "3.4" do + it "allows the user to set backtrace locations from a rescued exception" do + bt_locations = ExceptionSpecs::Backtrace.backtrace_locations + err = @method.call(bt_locations) + err.backtrace_locations.size.should == bt_locations.size + err.backtrace_locations.each_with_index do |loc, index| + other_loc = bt_locations[index] + + loc.path.should == other_loc.path + loc.label.should == other_loc.label + loc.base_label.should == other_loc.base_label + loc.lineno.should == other_loc.lineno + loc.absolute_path.should == other_loc.absolute_path + loc.to_s.should == other_loc.to_s + end + err.backtrace.size.should == err.backtrace_locations.size + end + end + + it "accepts an empty Array" do + err = @method.call([]) + err.backtrace.should == [] + end + + it "accepts a String" do + err = @method.call("unhappy") + err.backtrace.should == ["unhappy"] + end + + it "accepts nil" do + err = @method.call(nil) + err.backtrace.should be_nil + end + + it "raises a TypeError when passed a Symbol" do + -> { @method.call(:unhappy) }.should raise_error(TypeError) + end + + it "raises a TypeError when the Array contains a Symbol" do + -> { @method.call(["String", :unhappy]) }.should raise_error(TypeError) + end + + it "raises a TypeError when the array contains nil" do + -> { @method.call(["String", nil]) }.should raise_error(TypeError) + end + + it "raises a TypeError when the argument is a nested array" do + -> { @method.call(["String", ["String"]]) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/core/fiber/storage_spec.rb b/spec/ruby/core/fiber/storage_spec.rb index 3d39f32009..015caaf3bb 100644 --- a/spec/ruby/core/fiber/storage_spec.rb +++ b/spec/ruby/core/fiber/storage_spec.rb @@ -84,15 +84,17 @@ describe "Fiber.[]" do Fiber.new { Fiber[:life] }.resume.should be_nil end - it "can use dynamically defined keys" do - key = :"#{self.class.name}#.#{self.object_id}" - Fiber.new { Fiber[key] = 42; Fiber[key] }.resume.should == 42 - end + ruby_version_is "3.2.3" do + it "can use dynamically defined keys" do + key = :"#{self.class.name}#.#{self.object_id}" + Fiber.new { Fiber[key] = 42; Fiber[key] }.resume.should == 42 + end - it "can't use invalid keys" do - invalid_keys = [Object.new, 12] - invalid_keys.each do |key| - -> { Fiber[key] }.should raise_error(TypeError) + it "can't use invalid keys" do + invalid_keys = [Object.new, 12] + invalid_keys.each do |key| + -> { Fiber[key] }.should raise_error(TypeError) + end end end diff --git a/spec/ruby/core/file/birthtime_spec.rb b/spec/ruby/core/file/birthtime_spec.rb index 755601df64..f82eaf7cca 100644 --- a/spec/ruby/core/file/birthtime_spec.rb +++ b/spec/ruby/core/file/birthtime_spec.rb @@ -1,60 +1,56 @@ require_relative '../../spec_helper' -describe "File.birthtime" do - before :each do - @file = __FILE__ - end +platform_is :windows, :darwin, :freebsd, :netbsd, :linux do + not_implemented_messages = [ + "birthtime() function is unimplemented", # unsupported OS/version + "birthtime is unimplemented", # unsupported filesystem + ] - after :each do - @file = nil - end + describe "File.birthtime" do + before :each do + @file = __FILE__ + end + + after :each do + @file = nil + end - platform_is :windows, :darwin, :freebsd, :netbsd do it "returns the birth time for the named file as a Time object" do File.birthtime(@file) File.birthtime(@file).should be_kind_of(Time) + rescue NotImplementedError => e + e.message.should.start_with?(*not_implemented_messages) end it "accepts an object that has a #to_path method" do + File.birthtime(@file) # Avoid to failure of mock object with old Kernel and glibc File.birthtime(mock_to_path(@file)) + rescue NotImplementedError => e + e.message.should.start_with?(*not_implemented_messages) end it "raises an Errno::ENOENT exception if the file is not found" do -> { File.birthtime('bogus') }.should raise_error(Errno::ENOENT) + rescue NotImplementedError => e + e.message.should.start_with?(*not_implemented_messages) end end - platform_is :openbsd do - it "raises an NotImplementedError" do - -> { File.birthtime(@file) }.should raise_error(NotImplementedError) + describe "File#birthtime" do + before :each do + @file = File.open(__FILE__) end - end - # TODO: depends on Linux kernel version -end + after :each do + @file.close + @file = nil + end -describe "File#birthtime" do - before :each do - @file = File.open(__FILE__) - end - - after :each do - @file.close - @file = nil - end - - platform_is :windows, :darwin, :freebsd, :netbsd do it "returns the birth time for self" do @file.birthtime @file.birthtime.should be_kind_of(Time) + rescue NotImplementedError => e + e.message.should.start_with?(*not_implemented_messages) end end - - platform_is :openbsd do - it "raises an NotImplementedError" do - -> { @file.birthtime }.should raise_error(NotImplementedError) - end - end - - # TODO: depends on Linux kernel version end diff --git a/spec/ruby/core/file/stat/birthtime_spec.rb b/spec/ruby/core/file/stat/birthtime_spec.rb index a727bbe566..adecee15b0 100644 --- a/spec/ruby/core/file/stat/birthtime_spec.rb +++ b/spec/ruby/core/file/stat/birthtime_spec.rb @@ -1,27 +1,29 @@ require_relative '../../../spec_helper' -describe "File::Stat#birthtime" do - before :each do - @file = tmp('i_exist') - touch(@file) { |f| f.write "rubinius" } - end +platform_is(:windows, :darwin, :freebsd, :netbsd, + *ruby_version_is("3.5") { :linux }, + ) do + not_implemented_messages = [ + "birthtime() function is unimplemented", # unsupported OS/version + "birthtime is unimplemented", # unsupported filesystem + ] - after :each do - rm_r @file - end + describe "File::Stat#birthtime" do + before :each do + @file = tmp('i_exist') + touch(@file) { |f| f.write "rubinius" } + end + + after :each do + rm_r @file + end - platform_is :windows, :darwin, :freebsd, :netbsd do it "returns the birthtime of a File::Stat object" do st = File.stat(@file) st.birthtime.should be_kind_of(Time) st.birthtime.should <= Time.now - end - end - - platform_is :linux, :openbsd do - it "raises an NotImplementedError" do - st = File.stat(@file) - -> { st.birthtime }.should raise_error(NotImplementedError) + rescue NotImplementedError => e + e.message.should.start_with?(*not_implemented_messages) end end end diff --git a/spec/ruby/core/integer/divide_spec.rb b/spec/ruby/core/integer/divide_spec.rb index 665f4d57be..0d5e16e986 100644 --- a/spec/ruby/core/integer/divide_spec.rb +++ b/spec/ruby/core/integer/divide_spec.rb @@ -106,4 +106,21 @@ describe "Integer#/" do -> { @bignum / :symbol }.should raise_error(TypeError) end end + + it "coerces the RHS and calls #coerce" do + obj = mock("integer plus") + obj.should_receive(:coerce).with(6).and_return([6, 3]) + (6 / obj).should == 2 + end + + it "coerces the RHS and calls #coerce even if it's private" do + obj = Object.new + class << obj + private def coerce(n) + [n, 3] + end + end + + (6 / obj).should == 2 + end end diff --git a/spec/ruby/core/integer/minus_spec.rb b/spec/ruby/core/integer/minus_spec.rb index aadf416a05..6072ba7c8b 100644 --- a/spec/ruby/core/integer/minus_spec.rb +++ b/spec/ruby/core/integer/minus_spec.rb @@ -40,4 +40,21 @@ describe "Integer#-" do -> { @bignum - :symbol }.should raise_error(TypeError) end end + + it "coerces the RHS and calls #coerce" do + obj = mock("integer plus") + obj.should_receive(:coerce).with(5).and_return([5, 10]) + (5 - obj).should == -5 + end + + it "coerces the RHS and calls #coerce even if it's private" do + obj = Object.new + class << obj + private def coerce(n) + [n, 10] + end + end + + (5 - obj).should == -5 + end end diff --git a/spec/ruby/core/integer/plus_spec.rb b/spec/ruby/core/integer/plus_spec.rb index d01a76ab58..38428e56c5 100644 --- a/spec/ruby/core/integer/plus_spec.rb +++ b/spec/ruby/core/integer/plus_spec.rb @@ -55,4 +55,21 @@ describe "Integer#+" do RUBY ruby_exe(code).should == "-1" end + + it "coerces the RHS and calls #coerce" do + obj = mock("integer plus") + obj.should_receive(:coerce).with(6).and_return([6, 3]) + (6 + obj).should == 9 + end + + it "coerces the RHS and calls #coerce even if it's private" do + obj = Object.new + class << obj + private def coerce(n) + [n, 3] + end + end + + (6 + obj).should == 9 + end end diff --git a/spec/ruby/core/io/popen_spec.rb b/spec/ruby/core/io/popen_spec.rb index e9d32c5c7d..6043862614 100644 --- a/spec/ruby/core/io/popen_spec.rb +++ b/spec/ruby/core/io/popen_spec.rb @@ -95,6 +95,22 @@ describe "IO.popen" do @io = IO.popen(ruby_cmd('exit 0'), mode) end + it "accepts a path using the chdir: keyword argument" do + path = File.dirname(@fname) + + @io = IO.popen(ruby_cmd("puts Dir.pwd"), "r", chdir: path) + @io.read.chomp.should == path + end + + it "accepts a path using the chdir: keyword argument and a coercible path" do + path = File.dirname(@fname) + object = mock("path") + object.should_receive(:to_path).and_return(path) + + @io = IO.popen(ruby_cmd("puts Dir.pwd"), "r", chdir: object) + @io.read.chomp.should == path + end + describe "with a block" do it "yields an open IO to the block" do IO.popen(ruby_cmd('exit'), "r") do |io| diff --git a/spec/ruby/core/io/readpartial_spec.rb b/spec/ruby/core/io/readpartial_spec.rb index 547da0677d..2fcfaf5203 100644 --- a/spec/ruby/core/io/readpartial_spec.rb +++ b/spec/ruby/core/io/readpartial_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/io/shared/gets_ascii.rb b/spec/ruby/core/io/shared/gets_ascii.rb index 2a8fe3c9a5..2bd5470d99 100644 --- a/spec/ruby/core/io/shared/gets_ascii.rb +++ b/spec/ruby/core/io/shared/gets_ascii.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :io_gets_ascii, shared: true do describe "with ASCII separator" do before :each do diff --git a/spec/ruby/core/kernel/Float_spec.rb b/spec/ruby/core/kernel/Float_spec.rb index 6cedfe0617..1705205996 100644 --- a/spec/ruby/core/kernel/Float_spec.rb +++ b/spec/ruby/core/kernel/Float_spec.rb @@ -222,59 +222,99 @@ describe :kernel_float, shared: true do end end - describe "for hexadecimal literals with binary exponent" do - %w(p P).each do |p| - it "interprets the fractional part (on the left side of '#{p}') in hexadecimal" do - @object.send(:Float, "0x10#{p}0").should == 16.0 - end + context "for hexadecimal literals" do + it "interprets the 0x prefix as hexadecimal" do + @object.send(:Float, "0x10").should == 16.0 + @object.send(:Float, "0x0F").should == 15.0 + @object.send(:Float, "0x0f").should == 15.0 + end - it "interprets the exponent (on the right of '#{p}') in decimal" do - @object.send(:Float, "0x1#{p}10").should == 1024.0 - end + it "accepts embedded _ if the number does not contain a-f" do + @object.send(:Float, "0x1_0").should == 16.0 + end - it "raises an ArgumentError if #{p} is the trailing character" do - -> { @object.send(:Float, "0x1#{p}") }.should raise_error(ArgumentError) + ruby_version_is ""..."3.4.3" do + it "does not accept embedded _ if the number contains a-f" do + -> { @object.send(:Float, "0x1_0a") }.should raise_error(ArgumentError) + @object.send(:Float, "0x1_0a", exception: false).should be_nil end + end - it "raises an ArgumentError if #{p} is the leading character" do - -> { @object.send(:Float, "0x#{p}1") }.should raise_error(ArgumentError) + ruby_version_is "3.4.3" do + it "accepts embedded _ if the number contains a-f" do + @object.send(:Float, "0x1_0a").should == 0x10a.to_f end + end - it "returns Infinity for '0x1#{p}10000'" do - @object.send(:Float, "0x1#{p}10000").should == Float::INFINITY + it "does not accept _ before, after or inside the 0x prefix" do + -> { @object.send(:Float, "_0x10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "0_x10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "0x_10") }.should raise_error(ArgumentError) + @object.send(:Float, "_0x10", exception: false).should be_nil + @object.send(:Float, "0_x10", exception: false).should be_nil + @object.send(:Float, "0x_10", exception: false).should be_nil + end + + ruby_version_is "3.4" do + it "accepts a fractional part" do + @object.send(:Float, "0x0.8").should == 0.5 end + end - it "returns 0 for '0x1#{p}-10000'" do - @object.send(:Float, "0x1#{p}-10000").should == 0 - end + describe "with binary exponent" do + %w(p P).each do |p| + it "interprets the fractional part (on the left side of '#{p}') in hexadecimal" do + @object.send(:Float, "0x10#{p}0").should == 16.0 + end - it "allows embedded _ in a number on either side of the #{p}" do - @object.send(:Float, "0x1_0#{p}10").should == 16384.0 - @object.send(:Float, "0x10#{p}1_0").should == 16384.0 - @object.send(:Float, "0x1_0#{p}1_0").should == 16384.0 - end + it "interprets the exponent (on the right of '#{p}') in decimal" do + @object.send(:Float, "0x1#{p}10").should == 1024.0 + end - it "raises an exception if a space is embedded on either side of the '#{p}'" do - -> { @object.send(:Float, "0x1 0#{p}10") }.should raise_error(ArgumentError) - -> { @object.send(:Float, "0x10#{p}1 0") }.should raise_error(ArgumentError) - end + it "raises an ArgumentError if #{p} is the trailing character" do + -> { @object.send(:Float, "0x1#{p}") }.should raise_error(ArgumentError) + end - it "raises an exception if there's a leading _ on either side of the '#{p}'" do - -> { @object.send(:Float, "0x_10#{p}10") }.should raise_error(ArgumentError) - -> { @object.send(:Float, "0x10#{p}_10") }.should raise_error(ArgumentError) - end + it "raises an ArgumentError if #{p} is the leading character" do + -> { @object.send(:Float, "0x#{p}1") }.should raise_error(ArgumentError) + end - it "raises an exception if there's a trailing _ on either side of the '#{p}'" do - -> { @object.send(:Float, "0x10_#{p}10") }.should raise_error(ArgumentError) - -> { @object.send(:Float, "0x10#{p}10_") }.should raise_error(ArgumentError) - end + it "returns Infinity for '0x1#{p}10000'" do + @object.send(:Float, "0x1#{p}10000").should == Float::INFINITY + end - it "allows hexadecimal points on the left side of the '#{p}'" do - @object.send(:Float, "0x1.8#{p}0").should == 1.5 - end + it "returns 0 for '0x1#{p}-10000'" do + @object.send(:Float, "0x1#{p}-10000").should == 0 + end - it "raises an ArgumentError if there's a decimal point on the right side of the '#{p}'" do - -> { @object.send(:Float, "0x1#{p}1.0") }.should raise_error(ArgumentError) + it "allows embedded _ in a number on either side of the #{p}" do + @object.send(:Float, "0x1_0#{p}10").should == 16384.0 + @object.send(:Float, "0x10#{p}1_0").should == 16384.0 + @object.send(:Float, "0x1_0#{p}1_0").should == 16384.0 + end + + it "raises an exception if a space is embedded on either side of the '#{p}'" do + -> { @object.send(:Float, "0x1 0#{p}10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "0x10#{p}1 0") }.should raise_error(ArgumentError) + end + + it "raises an exception if there's a leading _ on either side of the '#{p}'" do + -> { @object.send(:Float, "0x_10#{p}10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "0x10#{p}_10") }.should raise_error(ArgumentError) + end + + it "raises an exception if there's a trailing _ on either side of the '#{p}'" do + -> { @object.send(:Float, "0x10_#{p}10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "0x10#{p}10_") }.should raise_error(ArgumentError) + end + + it "allows hexadecimal points on the left side of the '#{p}'" do + @object.send(:Float, "0x1.8#{p}0").should == 1.5 + end + + it "raises an ArgumentError if there's a decimal point on the right side of the '#{p}'" do + -> { @object.send(:Float, "0x1#{p}1.0") }.should raise_error(ArgumentError) + end end end end diff --git a/spec/ruby/core/kernel/caller_locations_spec.rb b/spec/ruby/core/kernel/caller_locations_spec.rb index aaacd9a910..6074879d59 100644 --- a/spec/ruby/core/kernel/caller_locations_spec.rb +++ b/spec/ruby/core/kernel/caller_locations_spec.rb @@ -83,7 +83,7 @@ describe 'Kernel#caller_locations' do end end - ruby_version_is "3.4" do + ruby_version_is "3.4"..."3.5" do it "includes core library methods defined in Ruby" do file, line = Kernel.instance_method(:tap).source_location file.should.start_with?(' { Kernel.instance_method(:tap).source_location } do - it "includes core library methods defined in Ruby" do - file, line = Kernel.instance_method(:tap).source_location - file.should.start_with?('' + + obj = Object.new + obj.instance_eval do + @host = "localhost" + @user = "root" + @password = "hunter2" + end + obj.singleton_class.class_eval do + private def instance_variables_to_inspect = [] + end + + inspected = obj.inspect.sub(/^#" + end + end end diff --git a/spec/ruby/core/kernel/raise_spec.rb b/spec/ruby/core/kernel/raise_spec.rb index a038dcf031..a4ab963fa3 100644 --- a/spec/ruby/core/kernel/raise_spec.rb +++ b/spec/ruby/core/kernel/raise_spec.rb @@ -44,7 +44,53 @@ describe "Kernel#raise" do it "raises an ArgumentError when only cause is given" do cause = StandardError.new - -> { raise(cause: cause) }.should raise_error(ArgumentError) + -> { raise(cause: cause) }.should raise_error(ArgumentError, "only cause is given with no arguments") + end + + it "raises an ArgumentError when only cause is given even if it has nil value" do + -> { raise(cause: nil) }.should raise_error(ArgumentError, "only cause is given with no arguments") + end + + it "raises an ArgumentError when given cause is not an instance of Exception" do + -> { raise "message", cause: Object.new }.should raise_error(TypeError, "exception object expected") + end + + it "doesn't raise an ArgumentError when given cause is nil" do + -> { raise "message", cause: nil }.should raise_error(RuntimeError, "message") + end + + it "allows cause equal an exception" do + e = RuntimeError.new("message") + -> { raise e, cause: e }.should raise_error(e) + end + + it "doesn't set given cause when it equals an exception" do + e = RuntimeError.new("message") + + begin + raise e, cause: e + rescue + end + + e.cause.should == nil + end + + it "raises ArgumentError when exception is part of the cause chain" do + -> { + begin + raise "Error 1" + rescue => e1 + begin + raise "Error 2" + rescue => e2 + begin + raise "Error 3" + rescue => e3 + raise e1, cause: e3 + end + end + end + }.should raise_error(ArgumentError, "circular causes") end it "re-raises a rescued exception" do @@ -62,6 +108,179 @@ describe "Kernel#raise" do end end.should raise_error(StandardError, "aaa") end + + it "re-raises a previously rescued exception without overwriting the cause" do + begin + begin + begin + begin + raise "Error 1" + rescue => e1 + raise "Error 2" + end + rescue => e2 + raise "Error 3" + end + rescue + e2.cause.should == e1 + raise e2 + end + rescue => e + e.cause.should == e1 + end + end + + it "re-raises a previously rescued exception with overwriting the cause when it's explicitly specified with :cause option" do + e4 = RuntimeError.new("Error 4") + + begin + begin + begin + begin + raise "Error 1" + rescue => e1 + raise "Error 2" + end + rescue => e2 + raise "Error 3" + end + rescue + e2.cause.should == e1 + raise e2, cause: e4 + end + rescue => e + e.cause.should == e4 + end + end + + it "re-raises a previously rescued exception without overwriting the cause when it's explicitly specified with :cause option and has nil value" do + begin + begin + begin + begin + raise "Error 1" + rescue => e1 + raise "Error 2" + end + rescue => e2 + raise "Error 3" + end + rescue + e2.cause.should == e1 + raise e2, cause: nil + end + rescue => e + e.cause.should == e1 + end + end + + it "re-raises a previously rescued exception without setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + raise + end + rescue => e + e.should == e1 + e.cause.should == nil + end + end + + it "re-raises a previously rescued exception that has a cause without setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + begin + raise "Error 2" + rescue => e2 + raise + end + end + rescue => e + e.should == e2 + e.cause.should == e1 + end + end + + it "re-raises a previously rescued exception that doesn't have a cause and isn't a cause of any other exception with setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + begin + raise "Error 2" + rescue => e2 + raise "Error 3" + end + end + rescue => e + e.message.should == "Error 3" + e.cause.should == e2 + end + end + + it "re-raises a previously rescued exception that doesn't have a cause and is a cause of other exception without setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + begin + raise "Error 2" + rescue => e2 + e1.cause.should == nil + e2.cause.should == e1 + raise e1 + end + end + rescue => e + e.should == e1 + e.cause.should == nil + end + end + + it "re-raises a previously rescued exception that doesn't have a cause and is a cause of other exception (that wasn't raised explicitly) without setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + begin + foo # raises NameError + rescue => e2 + e1.cause.should == nil + e2.cause.should == e1 + raise e1 + end + end + rescue => e + e.should == e1 + e.cause.should == nil + end + end + + it "re-raises a previously rescued exception that has a cause but isn't a cause of any other exception without setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + begin + raise "Error 2" + rescue => e2 + begin + raise "Error 3", cause: RuntimeError.new("Error 4") + rescue => e3 + e2.cause.should == e1 + e3.cause.should_not == e2 + raise e2 + end + end + end + rescue => e + e.should == e2 + e.cause.should == e1 + end + end end describe "Kernel#raise" do diff --git a/spec/ruby/core/kernel/require_spec.rb b/spec/ruby/core/kernel/require_spec.rb index 945f68aba9..81777c5313 100644 --- a/spec/ruby/core/kernel/require_spec.rb +++ b/spec/ruby/core/kernel/require_spec.rb @@ -19,6 +19,7 @@ describe "Kernel#require" do provided = %w[complex enumerator fiber rational thread ruby2_keywords] ruby_version_is "3.5" do provided << "set" + provided << "pathname" end it "#{provided.join(', ')} are already required" do @@ -31,6 +32,10 @@ describe "Kernel#require" do features.sort.should == provided.sort + ruby_version_is "3.5" do + provided.map! { |f| f == "pathname" ? "pathname.so" : f } + end + code = provided.map { |f| "puts require #{f.inspect}\n" }.join required = ruby_exe(code, options: '--disable-gems') required.should == "false\n" * provided.size diff --git a/spec/ruby/core/kernel/shared/sprintf.rb b/spec/ruby/core/kernel/shared/sprintf.rb index 26bd189593..a68389a7b4 100644 --- a/spec/ruby/core/kernel/shared/sprintf.rb +++ b/spec/ruby/core/kernel/shared/sprintf.rb @@ -362,6 +362,10 @@ describe :kernel_sprintf, shared: true do obj.should_receive(:inspect).and_return("") @method.call("%p", obj).should == "" end + + it "substitutes 'nil' for nil" do + @method.call("%p", nil).should == "nil" + end end describe "s" do diff --git a/spec/ruby/core/marshal/dump_spec.rb b/spec/ruby/core/marshal/dump_spec.rb index efc2293d9a..283016b8db 100644 --- a/spec/ruby/core/marshal/dump_spec.rb +++ b/spec/ruby/core/marshal/dump_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'fixtures/classes' require_relative 'fixtures/marshal_data' @@ -231,9 +231,12 @@ describe "Marshal.dump" do Marshal.dump(MarshalSpec::ClassWithOverriddenName).should == "\x04\bc)MarshalSpec::ClassWithOverriddenName" end - it "dumps a class with multibyte characters in name" do - source_object = eval("MarshalSpec::MultibyteぁあぃいClass".dup.force_encoding(Encoding::UTF_8)) - Marshal.dump(source_object).should == "\x04\bc,MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Class" + ruby_version_is "3.5" do + it "dumps a class with multibyte characters in name" do + source_object = eval("MarshalSpec::MultibyteぁあぃいClass".dup.force_encoding(Encoding::UTF_8)) + Marshal.dump(source_object).should == "\x04\bIc,MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Class\x06:\x06ET" + Marshal.load(Marshal.dump(source_object)) == source_object + end end it "uses object links for objects repeatedly dumped" do @@ -258,9 +261,12 @@ describe "Marshal.dump" do Marshal.dump(MarshalSpec::ModuleWithOverriddenName).should == "\x04\bc*MarshalSpec::ModuleWithOverriddenName" end - it "dumps a module with multibyte characters in name" do - source_object = eval("MarshalSpec::MultibyteけげこごModule".dup.force_encoding(Encoding::UTF_8)) - Marshal.dump(source_object).should == "\x04\bm-MarshalSpec::Multibyte\xE3\x81\x91\xE3\x81\x92\xE3\x81\x93\xE3\x81\x94Module" + ruby_version_is "3.5" do + it "dumps a module with multibyte characters in name" do + source_object = eval("MarshalSpec::MultibyteけげこごModule".dup.force_encoding(Encoding::UTF_8)) + Marshal.dump(source_object).should == "\x04\bIm-MarshalSpec::Multibyte\xE3\x81\x91\xE3\x81\x92\xE3\x81\x93\xE3\x81\x94Module\x06:\x06ET" + Marshal.load(Marshal.dump(source_object)) == source_object + end end it "uses object links for objects repeatedly dumped" do @@ -874,9 +880,12 @@ describe "Marshal.dump" do Marshal.dump(obj).should include("MarshalSpec::TimeWithOverriddenName") end - it "dumps a Time subclass with multibyte characters in name" do - source_object = eval("MarshalSpec::MultibyteぁあぃいTime".dup.force_encoding(Encoding::UTF_8)) - Marshal.dump(source_object).should == "\x04\bc+MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Time" + ruby_version_is "3.5" do + it "dumps a Time subclass with multibyte characters in name" do + source_object = eval("MarshalSpec::MultibyteぁあぃいTime".dup.force_encoding(Encoding::UTF_8)) + Marshal.dump(source_object).should == "\x04\bIc+MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Time\x06:\x06ET" + Marshal.load(Marshal.dump(source_object)) == source_object + end end it "uses object links for objects repeatedly dumped" do diff --git a/spec/ruby/core/marshal/fixtures/marshal_data.rb b/spec/ruby/core/marshal/fixtures/marshal_data.rb index 124eb9ecd6..c16d9e4bb6 100644 --- a/spec/ruby/core/marshal/fixtures/marshal_data.rb +++ b/spec/ruby/core/marshal/fixtures/marshal_data.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative 'marshal_multibyte_data' diff --git a/spec/ruby/core/marshal/shared/load.rb b/spec/ruby/core/marshal/shared/load.rb index 6d79f7c3ef..204a4d34e3 100644 --- a/spec/ruby/core/marshal/shared/load.rb +++ b/spec/ruby/core/marshal/shared/load.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../fixtures/marshal_data' describe :marshal_load, shared: true do diff --git a/spec/ruby/core/random/bytes_spec.rb b/spec/ruby/core/random/bytes_spec.rb index b42dc61234..c9be07cd3f 100644 --- a/spec/ruby/core/random/bytes_spec.rb +++ b/spec/ruby/core/random/bytes_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'shared/bytes' diff --git a/spec/ruby/core/range/cover_spec.rb b/spec/ruby/core/range/cover_spec.rb index eb7cddc967..c05bb50614 100644 --- a/spec/ruby/core/range/cover_spec.rb +++ b/spec/ruby/core/range/cover_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'shared/cover_and_include' require_relative 'shared/cover' diff --git a/spec/ruby/core/range/include_spec.rb b/spec/ruby/core/range/include_spec.rb index 277de205d1..449e18985b 100644 --- a/spec/ruby/core/range/include_spec.rb +++ b/spec/ruby/core/range/include_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'shared/cover_and_include' require_relative 'shared/include' diff --git a/spec/ruby/core/range/member_spec.rb b/spec/ruby/core/range/member_spec.rb index ab61f92951..78299ae9e5 100644 --- a/spec/ruby/core/range/member_spec.rb +++ b/spec/ruby/core/range/member_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'shared/cover_and_include' require_relative 'shared/include' diff --git a/spec/ruby/core/range/shared/cover.rb b/spec/ruby/core/range/shared/cover.rb index 0b41a26455..eaefb45942 100644 --- a/spec/ruby/core/range/shared/cover.rb +++ b/spec/ruby/core/range/shared/cover.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' diff --git a/spec/ruby/core/range/shared/cover_and_include.rb b/spec/ruby/core/range/shared/cover_and_include.rb index cd2b3621bb..13fc5e1790 100644 --- a/spec/ruby/core/range/shared/cover_and_include.rb +++ b/spec/ruby/core/range/shared/cover_and_include.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' describe :range_cover_and_include, shared: true do diff --git a/spec/ruby/core/range/shared/include.rb b/spec/ruby/core/range/shared/include.rb index c6c5c2becf..15a0e5fb9f 100644 --- a/spec/ruby/core/range/shared/include.rb +++ b/spec/ruby/core/range/shared/include.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' diff --git a/spec/ruby/core/regexp/linear_time_spec.rb b/spec/ruby/core/regexp/linear_time_spec.rb index a6f8dccd46..c3b3500549 100644 --- a/spec/ruby/core/regexp/linear_time_spec.rb +++ b/spec/ruby/core/regexp/linear_time_spec.rb @@ -6,6 +6,10 @@ describe "Regexp.linear_time?" do Regexp.linear_time?('a').should == true end + it "returns true if matching can be done in linear time for a binary Regexp" do + Regexp.linear_time?(/[\x80-\xff]/n).should == true + end + it "return false if matching can't be done in linear time" do Regexp.linear_time?(/(a)\1/).should == false Regexp.linear_time?("(a)\\1").should == false diff --git a/spec/ruby/core/regexp/shared/new.rb b/spec/ruby/core/regexp/shared/new.rb index 44b339100e..921736d299 100644 --- a/spec/ruby/core/regexp/shared/new.rb +++ b/spec/ruby/core/regexp/shared/new.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :regexp_new, shared: true do it "requires one argument and creates a new regular expression object" do diff --git a/spec/ruby/core/regexp/shared/quote.rb b/spec/ruby/core/regexp/shared/quote.rb index 48179444f0..3f46a18b5b 100644 --- a/spec/ruby/core/regexp/shared/quote.rb +++ b/spec/ruby/core/regexp/shared/quote.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :regexp_quote, shared: true do it "escapes any characters with special meaning in a regular expression" do diff --git a/spec/ruby/core/set/compare_by_identity_spec.rb b/spec/ruby/core/set/compare_by_identity_spec.rb index 00bb8e4e6a..0dda6d79f0 100644 --- a/spec/ruby/core/set/compare_by_identity_spec.rb +++ b/spec/ruby/core/set/compare_by_identity_spec.rb @@ -95,7 +95,7 @@ describe "Set#compare_by_identity" do set = Set.new.freeze -> { set.compare_by_identity - }.should raise_error(FrozenError, "can't modify frozen Set: #") + }.should raise_error(FrozenError, /can't modify frozen Set: (#<)?Set(\[|: {)[\]}]>?/) end end diff --git a/spec/ruby/core/set/set_spec.rb b/spec/ruby/core/set/set_spec.rb index f1436e6022..fd1d2072e3 100644 --- a/spec/ruby/core/set/set_spec.rb +++ b/spec/ruby/core/set/set_spec.rb @@ -3,8 +3,8 @@ require_relative '../../spec_helper' describe 'Set' do it 'is available without explicit requiring' do output = ruby_exe(<<~RUBY, options: '--disable-gems', args: '2>&1') - puts Set.new([1, 2, 3]) + puts Set.new([1, 2, 3]).to_a.inspect RUBY - output.chomp.should == "#" + output.chomp.should == "[1, 2, 3]" end end diff --git a/spec/ruby/core/set/shared/inspect.rb b/spec/ruby/core/set/shared/inspect.rb index adb6ddb4c9..fbc7486acd 100644 --- a/spec/ruby/core/set/shared/inspect.rb +++ b/spec/ruby/core/set/shared/inspect.rb @@ -7,19 +7,39 @@ describe :set_inspect, shared: true do Set[:a, "b", Set[?c]].send(@method).should be_kind_of(String) end - it "does include the elements of the set" do - Set["1"].send(@method).should == '#' + ruby_version_is "3.5" do + it "does include the elements of the set" do + Set["1"].send(@method).should == 'Set["1"]' + end + end + + ruby_version_is ""..."3.5" do + it "does include the elements of the set" do + Set["1"].send(@method).should == '#' + end end it "puts spaces between the elements" do Set["1", "2"].send(@method).should include('", "') end - it "correctly handles cyclic-references" do - set1 = Set[] - set2 = Set[set1] - set1 << set2 - set1.send(@method).should be_kind_of(String) - set1.send(@method).should include("#") + ruby_version_is "3.5" do + it "correctly handles cyclic-references" do + set1 = Set[] + set2 = Set[set1] + set1 << set2 + set1.send(@method).should be_kind_of(String) + set1.send(@method).should include("Set[...]") + end + end + + ruby_version_is ""..."3.5" do + it "correctly handles cyclic-references" do + set1 = Set[] + set2 = Set[set1] + set1 << set2 + set1.send(@method).should be_kind_of(String) + set1.send(@method).should include("#") + end end end diff --git a/spec/ruby/core/set/sortedset/sortedset_spec.rb b/spec/ruby/core/set/sortedset/sortedset_spec.rb index 375aada816..41f010e011 100644 --- a/spec/ruby/core/set/sortedset/sortedset_spec.rb +++ b/spec/ruby/core/set/sortedset/sortedset_spec.rb @@ -1,11 +1,13 @@ require_relative '../../../spec_helper' describe "SortedSet" do - it "raises error including message that it has been extracted from the set stdlib" do - -> { - SortedSet - }.should raise_error(RuntimeError) { |e| - e.message.should.include?("The `SortedSet` class has been extracted from the `set` library") - } + ruby_version_is ""..."3.5" do + it "raises error including message that it has been extracted from the set stdlib" do + -> { + SortedSet + }.should raise_error(RuntimeError) { |e| + e.message.should.include?("The `SortedSet` class has been extracted from the `set` library") + } + end end end diff --git a/spec/ruby/core/string/byteslice_spec.rb b/spec/ruby/core/string/byteslice_spec.rb index 9fe504aeb1..4ad9e8d8f1 100644 --- a/spec/ruby/core/string/byteslice_spec.rb +++ b/spec/ruby/core/string/byteslice_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'fixtures/classes' require_relative 'shared/slice' diff --git a/spec/ruby/core/string/codepoints_spec.rb b/spec/ruby/core/string/codepoints_spec.rb index b276d0baa8..12a5bf5892 100644 --- a/spec/ruby/core/string/codepoints_spec.rb +++ b/spec/ruby/core/string/codepoints_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'shared/codepoints' require_relative 'shared/each_codepoint_without_block' diff --git a/spec/ruby/core/string/count_spec.rb b/spec/ruby/core/string/count_spec.rb index 06ba5a4f0e..e614e901dd 100644 --- a/spec/ruby/core/string/count_spec.rb +++ b/spec/ruby/core/string/count_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/shared/codepoints.rb b/spec/ruby/core/string/shared/codepoints.rb index f71263054a..1c28ba3d5e 100644 --- a/spec/ruby/core/string/shared/codepoints.rb +++ b/spec/ruby/core/string/shared/codepoints.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :string_codepoints, shared: true do it "returns self" do s = "foo" diff --git a/spec/ruby/core/string/shared/each_codepoint_without_block.rb b/spec/ruby/core/string/shared/each_codepoint_without_block.rb index 31b4c02c9c..c88e5c54c7 100644 --- a/spec/ruby/core/string/shared/each_codepoint_without_block.rb +++ b/spec/ruby/core/string/shared/each_codepoint_without_block.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :string_each_codepoint_without_block, shared: true do describe "when no block is given" do it "returns an Enumerator" do diff --git a/spec/ruby/core/string/shared/eql.rb b/spec/ruby/core/string/shared/eql.rb index 845b0a3e15..d5af337d53 100644 --- a/spec/ruby/core/string/shared/eql.rb +++ b/spec/ruby/core/string/shared/eql.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' diff --git a/spec/ruby/core/string/shared/succ.rb b/spec/ruby/core/string/shared/succ.rb index b69a394875..7c68345f10 100644 --- a/spec/ruby/core/string/shared/succ.rb +++ b/spec/ruby/core/string/shared/succ.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :string_succ, shared: true do it "returns an empty string for empty strings" do "".send(@method).should == "" diff --git a/spec/ruby/core/string/squeeze_spec.rb b/spec/ruby/core/string/squeeze_spec.rb index 4ea238e6b5..981d480684 100644 --- a/spec/ruby/core/string/squeeze_spec.rb +++ b/spec/ruby/core/string/squeeze_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary # frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/to_f_spec.rb b/spec/ruby/core/string/to_f_spec.rb index ab1ba43fb3..a91ccc168e 100644 --- a/spec/ruby/core/string/to_f_spec.rb +++ b/spec/ruby/core/string/to_f_spec.rb @@ -120,9 +120,11 @@ describe "String#to_f" do "\3771.2".b.to_f.should == 0 end - it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do - -> { - '1.2'.encode("UTF-16").to_f - }.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16") + ruby_version_is "3.2.3" do + it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do + -> { + '1.2'.encode("UTF-16").to_f + }.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16") + end end end diff --git a/spec/ruby/core/string/unpack/a_spec.rb b/spec/ruby/core/string/unpack/a_spec.rb index 4002ece697..a68e842e15 100644 --- a/spec/ruby/core/string/unpack/a_spec.rb +++ b/spec/ruby/core/string/unpack/a_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/string/unpack/at_spec.rb b/spec/ruby/core/string/unpack/at_spec.rb index 70b2389d69..d4133c23ee 100644 --- a/spec/ruby/core/string/unpack/at_spec.rb +++ b/spec/ruby/core/string/unpack/at_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/string/unpack/b_spec.rb b/spec/ruby/core/string/unpack/b_spec.rb index 23d93a8aea..b088f901fc 100644 --- a/spec/ruby/core/string/unpack/b_spec.rb +++ b/spec/ruby/core/string/unpack/b_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/string/unpack/c_spec.rb b/spec/ruby/core/string/unpack/c_spec.rb index c2bf813954..1e9548fb82 100644 --- a/spec/ruby/core/string/unpack/c_spec.rb +++ b/spec/ruby/core/string/unpack/c_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/string/unpack/comment_spec.rb b/spec/ruby/core/string/unpack/comment_spec.rb index e18a53df3c..050d2b7fc0 100644 --- a/spec/ruby/core/string/unpack/comment_spec.rb +++ b/spec/ruby/core/string/unpack/comment_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' diff --git a/spec/ruby/core/string/unpack/h_spec.rb b/spec/ruby/core/string/unpack/h_spec.rb index 19c4d63664..535836087d 100644 --- a/spec/ruby/core/string/unpack/h_spec.rb +++ b/spec/ruby/core/string/unpack/h_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/string/unpack/m_spec.rb b/spec/ruby/core/string/unpack/m_spec.rb index c551c755d1..357987a053 100644 --- a/spec/ruby/core/string/unpack/m_spec.rb +++ b/spec/ruby/core/string/unpack/m_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/string/unpack/shared/float.rb b/spec/ruby/core/string/unpack/shared/float.rb index 93282bf4c9..b31c2c8bdc 100644 --- a/spec/ruby/core/string/unpack/shared/float.rb +++ b/spec/ruby/core/string/unpack/shared/float.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :string_unpack_float_le, shared: true do it "decodes one float for a single format character" do diff --git a/spec/ruby/core/string/unpack/shared/integer.rb b/spec/ruby/core/string/unpack/shared/integer.rb index d71a2cf00d..d3934753ba 100644 --- a/spec/ruby/core/string/unpack/shared/integer.rb +++ b/spec/ruby/core/string/unpack/shared/integer.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :string_unpack_16bit_le, shared: true do it "decodes one short for a single format character" do diff --git a/spec/ruby/core/string/unpack/u_spec.rb b/spec/ruby/core/string/unpack/u_spec.rb index 456abee784..68c8f6f11c 100644 --- a/spec/ruby/core/string/unpack/u_spec.rb +++ b/spec/ruby/core/string/unpack/u_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/string/unpack/w_spec.rb b/spec/ruby/core/string/unpack/w_spec.rb index 6a1cff1965..7d3533ccae 100644 --- a/spec/ruby/core/string/unpack/w_spec.rb +++ b/spec/ruby/core/string/unpack/w_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/string/unpack/x_spec.rb b/spec/ruby/core/string/unpack/x_spec.rb index 5e248de77e..2926ebbe0f 100644 --- a/spec/ruby/core/string/unpack/x_spec.rb +++ b/spec/ruby/core/string/unpack/x_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/string/unpack/z_spec.rb b/spec/ruby/core/string/unpack/z_spec.rb index ce8da4b29e..1030390550 100644 --- a/spec/ruby/core/string/unpack/z_spec.rb +++ b/spec/ruby/core/string/unpack/z_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/struct/deconstruct_keys_spec.rb b/spec/ruby/core/struct/deconstruct_keys_spec.rb index 602403d183..e16b50f930 100644 --- a/spec/ruby/core/struct/deconstruct_keys_spec.rb +++ b/spec/ruby/core/struct/deconstruct_keys_spec.rb @@ -43,6 +43,13 @@ describe "Struct#deconstruct_keys" do s.deconstruct_keys([-1] ).should == {-1 => 30} end + it "ignores incorrect position numbers" do + struct = Struct.new(:x, :y, :z) + s = struct.new(10, 20, 30) + + s.deconstruct_keys([0, 3]).should == {0 => 10} + end + it "support mixing attribute names and argument position numbers" do struct = Struct.new(:x, :y) s = struct.new(1, 2) @@ -80,6 +87,28 @@ describe "Struct#deconstruct_keys" do obj.deconstruct_keys(nil).should == {x: 1, y: 2} end + it "tries to convert a key with #to_int if index is not a String nor a Symbol, but responds to #to_int" do + struct = Struct.new(:x, :y) + s = struct.new(1, 2) + + key = mock("to_int") + key.should_receive(:to_int).and_return(1) + + s.deconstruct_keys([key]).should == { key => 2 } + end + + it "raises a TypeError if the conversion with #to_int does not return an Integer" do + struct = Struct.new(:x, :y) + s = struct.new(1, 2) + + key = mock("to_int") + key.should_receive(:to_int).and_return("not an Integer") + + -> { + s.deconstruct_keys([key]) + }.should raise_error(TypeError, /can't convert MockObject to Integer/) + end + it "raises TypeError if index is not a String, a Symbol and not convertible to Integer" do struct = Struct.new(:x, :y) s = struct.new(1, 2) diff --git a/spec/ruby/core/struct/element_set_spec.rb b/spec/ruby/core/struct/element_set_spec.rb index 6ba7b081a9..0a0e34a5ee 100644 --- a/spec/ruby/core/struct/element_set_spec.rb +++ b/spec/ruby/core/struct/element_set_spec.rb @@ -26,4 +26,11 @@ describe "Struct#[]=" do -> { car[-4] = true }.should raise_error(IndexError) -> { car[Object.new] = true }.should raise_error(TypeError) end + + it "raises a FrozenError on a frozen struct" do + car = StructClasses::Car.new('Ford', 'Ranger') + car.freeze + + -> { car[:model] = 'Escape' }.should raise_error(FrozenError) + end end diff --git a/spec/ruby/core/struct/new_spec.rb b/spec/ruby/core/struct/new_spec.rb index 6d014cb94d..1d35de7b87 100644 --- a/spec/ruby/core/struct/new_spec.rb +++ b/spec/ruby/core/struct/new_spec.rb @@ -164,6 +164,35 @@ describe "Struct.new" do obj.args.should == 42 obj2.args.should == 42 end + + context "given positional and keyword arguments" do + it "treats keyword arguments as a positional parameter" do + type = Struct.new(:a, :b) + s = type.new("a", b: "b") + s.a.should == "a" + s.b.should == {b: "b"} + + type = Struct.new(:a, :b, :c) + s = type.new("a", b: "b", c: "c") + s.a.should == "a" + s.b.should == {b: "b", c: "c"} + s.c.should == nil + end + + it "ignores empty keyword arguments" do + type = Struct.new(:a, :b) + h = {} + s = type.new("a", **h) + + s.a.should == "a" + s.b.should == nil + end + + it "raises ArgumentError when all struct attribute values are specified" do + type = Struct.new(:a, :b) + -> { type.new("a", "b", c: "c") }.should raise_error(ArgumentError, "struct size differs") + end + end end context "keyword_init: true option" do diff --git a/spec/ruby/core/struct/struct_spec.rb b/spec/ruby/core/struct/struct_spec.rb index 8817dc1a58..1b6a4488ce 100644 --- a/spec/ruby/core/struct/struct_spec.rb +++ b/spec/ruby/core/struct/struct_spec.rb @@ -33,6 +33,13 @@ describe "Struct anonymous class instance methods" do car['model'].should == 'F150' car[1].should == 'F150' end + + it "writer methods raise a FrozenError on a frozen struct" do + car = StructClasses::Car.new('Ford', 'Ranger') + car.freeze + + -> { car.model = 'Escape' }.should raise_error(FrozenError) + end end describe "Struct subclasses" do diff --git a/spec/ruby/core/time/_dump_spec.rb b/spec/ruby/core/time/_dump_spec.rb index 4dc1c43cd2..852f9a07ab 100644 --- a/spec/ruby/core/time/_dump_spec.rb +++ b/spec/ruby/core/time/_dump_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' describe "Time#_dump" do diff --git a/spec/ruby/core/time/_load_spec.rb b/spec/ruby/core/time/_load_spec.rb index bb0d705bbc..30899de262 100644 --- a/spec/ruby/core/time/_load_spec.rb +++ b/spec/ruby/core/time/_load_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' describe "Time._load" do diff --git a/spec/ruby/core/time/minus_spec.rb b/spec/ruby/core/time/minus_spec.rb index 8449778465..9182d99652 100644 --- a/spec/ruby/core/time/minus_spec.rb +++ b/spec/ruby/core/time/minus_spec.rb @@ -109,7 +109,7 @@ describe "Time#-" do it "does not return a subclass instance" do c = Class.new(Time) - x = c.now + 1 + x = c.now - 1 x.should be_an_instance_of(Time) end diff --git a/spec/ruby/core/time/new_spec.rb b/spec/ruby/core/time/new_spec.rb index f288da84dd..dc3ccbdc00 100644 --- a/spec/ruby/core/time/new_spec.rb +++ b/spec/ruby/core/time/new_spec.rb @@ -524,6 +524,36 @@ describe "Time.new with a timezone argument" do Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: 3r).subsec.should == 0.123r end + it "returns Time with correct subseconds when given seconds fraction is shorted than 6 digits" do + Time.new("2020-12-25T00:56:17.123 +09:00").nsec.should == 123000000 + Time.new("2020-12-25T00:56:17.123 +09:00").usec.should == 123000 + Time.new("2020-12-25T00:56:17.123 +09:00").subsec.should == 0.123 + end + + it "returns Time with correct subseconds when given seconds fraction is milliseconds" do + Time.new("2020-12-25T00:56:17.123456 +09:00").nsec.should == 123456000 + Time.new("2020-12-25T00:56:17.123456 +09:00").usec.should == 123456 + Time.new("2020-12-25T00:56:17.123456 +09:00").subsec.should == 0.123456 + end + + it "returns Time with correct subseconds when given seconds fraction is longer that 6 digits but shorted than 9 digits" do + Time.new("2020-12-25T00:56:17.12345678 +09:00").nsec.should == 123456780 + Time.new("2020-12-25T00:56:17.12345678 +09:00").usec.should == 123456 + Time.new("2020-12-25T00:56:17.12345678 +09:00").subsec.should == 0.12345678 + end + + it "returns Time with correct subseconds when given seconds fraction is nanoseconds" do + Time.new("2020-12-25T00:56:17.123456789 +09:00").nsec.should == 123456789 + Time.new("2020-12-25T00:56:17.123456789 +09:00").usec.should == 123456 + Time.new("2020-12-25T00:56:17.123456789 +09:00").subsec.should == 0.123456789 + end + + it "returns Time with correct subseconds when given seconds fraction is longer than 9 digits" do + Time.new("2020-12-25T00:56:17.123456789876 +09:00").nsec.should == 123456789 + Time.new("2020-12-25T00:56:17.123456789876 +09:00").usec.should == 123456 + Time.new("2020-12-25T00:56:17.123456789876 +09:00").subsec.should == 0.123456789 + end + ruby_version_is ""..."3.3" do it "raise TypeError is can't convert precision keyword argument into Integer" do -> { @@ -550,16 +580,18 @@ describe "Time.new with a timezone argument" do }.should raise_error(ArgumentError, /missing min part: 00 |can't parse:/) end - it "raises ArgumentError if the time part is missing" do - -> { - Time.new("2020-12-25") - }.should raise_error(ArgumentError, /no time information|can't parse:/) - end + ruby_version_is "3.2.3" do + it "raises ArgumentError if the time part is missing" do + -> { + Time.new("2020-12-25") + }.should raise_error(ArgumentError, /no time information|can't parse:/) + end - it "raises ArgumentError if day is missing" do - -> { - Time.new("2020-12") - }.should raise_error(ArgumentError, /no time information|can't parse:/) + it "raises ArgumentError if day is missing" do + -> { + Time.new("2020-12") + }.should raise_error(ArgumentError, /no time information|can't parse:/) + end end it "raises ArgumentError if subsecond is missing after dot" do @@ -698,22 +730,24 @@ describe "Time.new with a timezone argument" do }.should raise_error(ArgumentError, /can't parse.+ abc/) end - it "raises ArgumentError when there are leading space characters" do - -> { Time.new(" 2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("\t2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("\n2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("\v2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("\f2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("\r2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) - end + ruby_version_is "3.2.3" do + it "raises ArgumentError when there are leading space characters" do + -> { Time.new(" 2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("\t2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("\n2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("\v2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("\f2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("\r2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + end - it "raises ArgumentError when there are trailing whitespaces" do - -> { Time.new("2020-12-02 00:00:00 ") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("2020-12-02 00:00:00\t") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("2020-12-02 00:00:00\n") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("2020-12-02 00:00:00\v") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("2020-12-02 00:00:00\f") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("2020-12-02 00:00:00\r") }.should raise_error(ArgumentError, /can't parse/) + it "raises ArgumentError when there are trailing whitespaces" do + -> { Time.new("2020-12-02 00:00:00 ") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\t") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\n") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\v") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\f") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\r") }.should raise_error(ArgumentError, /can't parse/) + end end end end diff --git a/spec/ruby/core/time/shared/time_params.rb b/spec/ruby/core/time/shared/time_params.rb index b6a6c88c8e..9832fd17fe 100644 --- a/spec/ruby/core/time/shared/time_params.rb +++ b/spec/ruby/core/time/shared/time_params.rb @@ -179,6 +179,10 @@ describe :time_params, shared: true do }.should raise_error(ArgumentError, "argument out of range") end + it "raises ArgumentError when given 8 arguments" do + -> { Time.send(@method, *[0]*8) }.should raise_error(ArgumentError) + end + it "raises ArgumentError when given 9 arguments" do -> { Time.send(@method, *[0]*9) }.should raise_error(ArgumentError) end diff --git a/spec/ruby/language/assignments_spec.rb b/spec/ruby/language/assignments_spec.rb index 89a5afdcd8..4ad9f4167b 100644 --- a/spec/ruby/language/assignments_spec.rb +++ b/spec/ruby/language/assignments_spec.rb @@ -41,6 +41,37 @@ describe 'Assignments' do ScratchPad.recorded.should == [:rhs] end end + + context "given block argument" do + before do + @klass = Class.new do + def initialize(h) @h = h end + def [](k, &block) @h[k]; end + def []=(k, v, &block) @h[k] = v; end + end + end + + ruby_version_is ""..."3.4" do + it "accepts block argument" do + obj = @klass.new(a: 1) + block = proc {} + + eval "obj[:a, &block] = 2" + eval("obj[:a, &block]").should == 2 + end + end + + ruby_version_is "3.4" do + it "raises SyntaxError" do + obj = @klass.new(a: 1) + block = proc {} + + -> { + eval "obj[:a, &block] = 2" + }.should raise_error(SyntaxError, /unexpected block arg given in index assignment|block arg given in index assignment/) + end + end + end end describe 'using +=' do @@ -114,6 +145,37 @@ describe 'Assignments' do a.public_method(:k, 2).should == 2 end + context "given block argument" do + before do + @klass = Class.new do + def initialize(h) @h = h end + def [](k, &block) @h[k]; end + def []=(k, v, &block) @h[k] = v; end + end + end + + ruby_version_is ""..."3.4" do + it "accepts block argument" do + obj = @klass.new(a: 1) + block = proc {} + + eval "obj[:a, &block] += 2" + eval("obj[:a, &block]").should == 3 + end + end + + ruby_version_is "3.4" do + it "raises SyntaxError" do + obj = @klass.new(a: 1) + block = proc {} + + -> { + eval "obj[:a, &block] += 2" + }.should raise_error(SyntaxError, /unexpected block arg given in index assignment|block arg given in index assignment/) + end + end + end + context 'splatted argument' do it 'correctly handles it' do @b[:m] = 10 diff --git a/spec/ruby/language/def_spec.rb b/spec/ruby/language/def_spec.rb index eb44331bb5..296d4787d0 100644 --- a/spec/ruby/language/def_spec.rb +++ b/spec/ruby/language/def_spec.rb @@ -97,7 +97,7 @@ describe "An instance method" do def foo; end end }.should raise_error(FrozenError) { |e| - e.message.should.start_with? "can't modify frozen module" + e.message.should == "can't modify frozen module: #{e.receiver}" } -> { @@ -106,7 +106,7 @@ describe "An instance method" do def foo; end end }.should raise_error(FrozenError){ |e| - e.message.should.start_with? "can't modify frozen class" + e.message.should == "can't modify frozen class: #{e.receiver}" } end end @@ -283,20 +283,20 @@ describe "A singleton method definition" do it "raises FrozenError with the correct class name" do obj = Object.new obj.freeze - -> { def obj.foo; end }.should raise_error(FrozenError){ |e| - e.message.should.start_with? "can't modify frozen object" - } + -> { def obj.foo; end }.should raise_error(FrozenError, "can't modify frozen object: #{obj}") + obj = Object.new c = obj.singleton_class - -> { def c.foo; end }.should raise_error(FrozenError){ |e| - e.message.should.start_with? "can't modify frozen Class" - } + c.singleton_class.freeze + -> { def c.foo; end }.should raise_error(FrozenError, "can't modify frozen Class: #{c}") + + c = Class.new + c.freeze + -> { def c.foo; end }.should raise_error(FrozenError, "can't modify frozen Class: #{c}") m = Module.new m.freeze - -> { def m.foo; end }.should raise_error(FrozenError){ |e| - e.message.should.start_with? "can't modify frozen Module" - } + -> { def m.foo; end }.should raise_error(FrozenError, "can't modify frozen Module: #{m}") end end diff --git a/spec/ruby/language/hash_spec.rb b/spec/ruby/language/hash_spec.rb index b119b6ca73..668716e2e3 100644 --- a/spec/ruby/language/hash_spec.rb +++ b/spec/ruby/language/hash_spec.rb @@ -149,6 +149,26 @@ describe "Hash literal" do {a: 1, **h, c: 4}.should == {a: 1, b: 2, c: 4} end + ruby_version_is ""..."3.4" do + it "does not expand nil using ** into {} and raises TypeError" do + h = nil + -> { {a: 1, **h} }.should raise_error(TypeError, "no implicit conversion of nil into Hash") + + -> { {a: 1, **nil} }.should raise_error(TypeError, "no implicit conversion of nil into Hash") + end + end + + ruby_version_is "3.4" do + it "expands nil using ** into {}" do + h = nil + {**h}.should == {} + {a: 1, **h}.should == {a: 1} + + {**nil}.should == {} + {a: 1, **nil}.should == {a: 1} + end + end + it "expands an '**{}' or '**obj' element with the last key/value pair taking precedence" do -> { @h = eval "{a: 1, **{a: 2, b: 3, c: 1}, c: 3}" diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb index b0d7058dbe..1d412a133e 100644 --- a/spec/ruby/language/method_spec.rb +++ b/spec/ruby/language/method_spec.rb @@ -1175,6 +1175,31 @@ describe "A method" do end end +context "when passing **nil into a method that accepts keyword arguments" do + ruby_version_is ""..."3.4" do + it "raises TypeError" do + def m(**kw) kw; end + + h = nil + -> { m(a: 1, **h) }.should raise_error(TypeError, "no implicit conversion of nil into Hash") + -> { m(a: 1, **nil) }.should raise_error(TypeError, "no implicit conversion of nil into Hash") + end + end + + ruby_version_is "3.4" do + it "expands nil using ** into {}" do + def m(**kw) kw; end + + h = nil + m(**h).should == {} + m(a: 1, **h).should == {a: 1} + + m(**nil).should == {} + m(a: 1, **nil).should == {a: 1} + end + end +end + describe "A method call with a space between method name and parentheses" do before(:each) do def m(*args) diff --git a/spec/ruby/language/predefined_spec.rb b/spec/ruby/language/predefined_spec.rb index 91019cfe56..d90e19858a 100644 --- a/spec/ruby/language/predefined_spec.rb +++ b/spec/ruby/language/predefined_spec.rb @@ -1,4 +1,5 @@ require_relative '../spec_helper' +require_relative '../core/exception/shared/set_backtrace' require 'stringio' # The following tables are excerpted from Programming Ruby: The Pragmatic Programmer's Guide' @@ -621,6 +622,17 @@ describe "Predefined global $@" do end end + it_behaves_like :exception_set_backtrace, -> backtrace { + exception = nil + begin + raise + rescue + $@ = backtrace + exception = $! + end + exception + } + it "cannot be assigned when there is no a rescued exception" do -> { $@ = [] @@ -1063,8 +1075,14 @@ describe "Execution variable $:" do it "default $LOAD_PATH entries until sitelibdir included have @gem_prelude_index set" do skip "no sense in ruby itself" if MSpecScript.instance_variable_defined?(:@testing_ruby) - $:.should.include?(RbConfig::CONFIG['sitelibdir']) - idx = $:.index(RbConfig::CONFIG['sitelibdir']) + if platform_is :windows + # See https://github.com/ruby/setup-ruby/pull/762#issuecomment-2917460440 + $:.should.find { |e| File.realdirpath(e) == RbConfig::CONFIG['sitelibdir'] } + idx = $:.index { |e| File.realdirpath(e) == RbConfig::CONFIG['sitelibdir'] } + else + $:.should.include?(RbConfig::CONFIG['sitelibdir']) + idx = $:.index(RbConfig::CONFIG['sitelibdir']) + end $:[idx..-1].all? { |p| p.instance_variable_defined?(:@gem_prelude_index) }.should be_true $:[0...idx].all? { |p| !p.instance_variable_defined?(:@gem_prelude_index) }.should be_true diff --git a/spec/ruby/language/regexp/character_classes_spec.rb b/spec/ruby/language/regexp/character_classes_spec.rb index d27a54a028..80cf88c7bd 100644 --- a/spec/ruby/language/regexp/character_classes_spec.rb +++ b/spec/ruby/language/regexp/character_classes_spec.rb @@ -562,6 +562,13 @@ describe "Regexp with character classes" do "\u{16EE}".match(/[[:word:]]/).to_a.should == ["\u{16EE}"] end + ruby_bug "#19417", ""..."3.5" do + it "matches Unicode join control characters with [[:word:]]" do + "\u{200C}".match(/[[:word:]]/).to_a.should == ["\u{200C}"] + "\u{200D}".match(/[[:word:]]/).to_a.should == ["\u{200D}"] + end + end + it "doesn't match Unicode No characters with [[:word:]]" do "\u{17F0}".match(/[[:word:]]/).should be_nil end diff --git a/spec/ruby/language/regexp/encoding_spec.rb b/spec/ruby/language/regexp/encoding_spec.rb index 0571b2d3cf..898b6d4ff7 100644 --- a/spec/ruby/language/regexp/encoding_spec.rb +++ b/spec/ruby/language/regexp/encoding_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative '../fixtures/classes' diff --git a/spec/ruby/language/regexp/escapes_spec.rb b/spec/ruby/language/regexp/escapes_spec.rb index 16a4d8c23b..541998b937 100644 --- a/spec/ruby/language/regexp/escapes_spec.rb +++ b/spec/ruby/language/regexp/escapes_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative '../fixtures/classes' diff --git a/spec/ruby/language/string_spec.rb b/spec/ruby/language/string_spec.rb index 5096252e9a..f287731bed 100644 --- a/spec/ruby/language/string_spec.rb +++ b/spec/ruby/language/string_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../spec_helper' require_relative 'fixtures/class_with_class_variable' diff --git a/spec/ruby/library/bigdecimal/shared/quo.rb b/spec/ruby/library/bigdecimal/shared/quo.rb index 46e6d62bf4..18ff2fe9a5 100644 --- a/spec/ruby/library/bigdecimal/shared/quo.rb +++ b/spec/ruby/library/bigdecimal/shared/quo.rb @@ -31,6 +31,7 @@ describe :bigdecimal_quo, shared: true do describe "with Object" do it "tries to coerce the other operand to self" do + skip if @method == :div object = mock("Object") object.should_receive(:coerce).with(@one).and_return([@one, @two]) @one.send(@method, object, *@object).should == BigDecimal("0.5") diff --git a/spec/ruby/library/cgi/escapeElement_spec.rb b/spec/ruby/library/cgi/escapeElement_spec.rb index 528433d252..7bfa3f4feb 100644 --- a/spec/ruby/library/cgi/escapeElement_spec.rb +++ b/spec/ruby/library/cgi/escapeElement_spec.rb @@ -1,9 +1,11 @@ require_relative '../../spec_helper' -begin - require 'cgi/escape' -rescue LoadError + +ruby_version_is ""..."3.5" do require 'cgi' end +ruby_version_is "3.5" do + require 'cgi/escape' +end describe "CGI.escapeElement when passed String, elements, ..." do it "escapes only the tags of the passed elements in the passed String" do diff --git a/spec/ruby/library/cgi/unescapeElement_spec.rb b/spec/ruby/library/cgi/unescapeElement_spec.rb index 3453393282..af2fa8a47d 100644 --- a/spec/ruby/library/cgi/unescapeElement_spec.rb +++ b/spec/ruby/library/cgi/unescapeElement_spec.rb @@ -1,9 +1,11 @@ require_relative '../../spec_helper' -begin - require 'cgi/escape' -rescue LoadError + +ruby_version_is ""..."3.5" do require 'cgi' end +ruby_version_is "3.5" do + require 'cgi/escape' +end describe "CGI.unescapeElement when passed String, elements, ..." do it "unescapes only the tags of the passed elements in the passed String" do diff --git a/spec/ruby/library/cgi/unescape_spec.rb b/spec/ruby/library/cgi/unescape_spec.rb index 52e1cb0243..e750c72921 100644 --- a/spec/ruby/library/cgi/unescape_spec.rb +++ b/spec/ruby/library/cgi/unescape_spec.rb @@ -1,10 +1,12 @@ # -*- encoding: utf-8 -*- require_relative '../../spec_helper' -begin - require 'cgi/escape' -rescue LoadError + +ruby_version_is ""..."3.5" do require 'cgi' end +ruby_version_is "3.5" do + require 'cgi/escape' +end describe "CGI.unescape" do it "url-decodes the passed argument" do diff --git a/spec/ruby/library/digest/md5/shared/constants.rb b/spec/ruby/library/digest/md5/shared/constants.rb index e807b96f9f..664dd18e9c 100644 --- a/spec/ruby/library/digest/md5/shared/constants.rb +++ b/spec/ruby/library/digest/md5/shared/constants.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require 'digest/md5' module MD5Constants diff --git a/spec/ruby/library/digest/sha1/shared/constants.rb b/spec/ruby/library/digest/sha1/shared/constants.rb index 169438747f..d77c05a968 100644 --- a/spec/ruby/library/digest/sha1/shared/constants.rb +++ b/spec/ruby/library/digest/sha1/shared/constants.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require 'digest/sha1' diff --git a/spec/ruby/library/digest/sha256/shared/constants.rb b/spec/ruby/library/digest/sha256/shared/constants.rb index 351679f344..afe8f11426 100644 --- a/spec/ruby/library/digest/sha256/shared/constants.rb +++ b/spec/ruby/library/digest/sha256/shared/constants.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require 'digest/sha2' diff --git a/spec/ruby/library/digest/sha384/shared/constants.rb b/spec/ruby/library/digest/sha384/shared/constants.rb index 2050f03f2b..a78d571d26 100644 --- a/spec/ruby/library/digest/sha384/shared/constants.rb +++ b/spec/ruby/library/digest/sha384/shared/constants.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require 'digest/sha2' diff --git a/spec/ruby/library/digest/sha512/shared/constants.rb b/spec/ruby/library/digest/sha512/shared/constants.rb index 2765a1ec16..91787381ee 100644 --- a/spec/ruby/library/digest/sha512/shared/constants.rb +++ b/spec/ruby/library/digest/sha512/shared/constants.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require 'digest/sha2' diff --git a/spec/ruby/library/net-http/http/post_spec.rb b/spec/ruby/library/net-http/http/post_spec.rb index ac020bd6be..cebbee4ff3 100644 --- a/spec/ruby/library/net-http/http/post_spec.rb +++ b/spec/ruby/library/net-http/http/post_spec.rb @@ -25,9 +25,11 @@ describe "Net::HTTP.post" do response.should be_kind_of(Net::HTTPResponse) end - it "sends Content-Type: application/x-www-form-urlencoded by default" do - response = Net::HTTP.post(URI("http://localhost:#{NetHTTPSpecs.port}/request/header"), "test=test") - response.body.should include({ "Content-Type" => "application/x-www-form-urlencoded" }.inspect.delete("{}")) + ruby_version_is ""..."3.5" do + it "sends Content-Type: application/x-www-form-urlencoded by default" do + response = Net::HTTP.post(URI("http://localhost:#{NetHTTPSpecs.port}/request/header"), "test=test") + response.body.should include({ "Content-Type" => "application/x-www-form-urlencoded" }.inspect.delete("{}")) + end end it "does not support HTTP Basic Auth" do diff --git a/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb b/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb index 7de03d7da0..0912e5a71f 100644 --- a/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb +++ b/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb @@ -31,18 +31,20 @@ describe "Net::HTTPGenericRequest#exec when passed socket, version, path" do end describe "when a request body is set" do - it "sets the 'Content-Type' header to 'application/x-www-form-urlencoded' unless the 'Content-Type' header is supplied" do - request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path") - request.body = "Some Content" + ruby_version_is ""..."3.5" do + it "sets the 'Content-Type' header to 'application/x-www-form-urlencoded' unless the 'Content-Type' header is supplied" do + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path") + request.body = "Some Content" - request.exec(@buffered_socket, "1.1", "/some/other/path") - str = @socket.string + request.exec(@buffered_socket, "1.1", "/some/other/path") + str = @socket.string - str.should =~ %r[POST /some/other/path HTTP/1.1\r\n] - str.should =~ %r[Accept: \*/\*\r\n] - str.should =~ %r[Content-Type: application/x-www-form-urlencoded\r\n] - str.should =~ %r[Content-Length: 12\r\n] - str[-16..-1].should == "\r\n\r\nSome Content" + str.should =~ %r[POST /some/other/path HTTP/1.1\r\n] + str.should =~ %r[Accept: \*/\*\r\n] + str.should =~ %r[Content-Type: application/x-www-form-urlencoded\r\n] + str.should =~ %r[Content-Length: 12\r\n] + str[-16..-1].should == "\r\n\r\nSome Content" + end end it "correctly sets the 'Content-Length' header and includes the body" do @@ -62,19 +64,21 @@ describe "Net::HTTPGenericRequest#exec when passed socket, version, path" do end describe "when a body stream is set" do - it "sets the 'Content-Type' header to 'application/x-www-form-urlencoded' unless the 'Content-Type' header is supplied" do - request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path", - "Content-Length" => "10") - request.body_stream = StringIO.new("a" * 20) + ruby_version_is ""..."3.5" do + it "sets the 'Content-Type' header to 'application/x-www-form-urlencoded' unless the 'Content-Type' header is supplied" do + request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path", + "Content-Length" => "10") + request.body_stream = StringIO.new("a" * 20) - request.exec(@buffered_socket, "1.1", "/some/other/path") - str = @socket.string + request.exec(@buffered_socket, "1.1", "/some/other/path") + str = @socket.string - str.should =~ %r[POST /some/other/path HTTP/1.1\r\n] - str.should =~ %r[Accept: \*/\*\r\n] - str.should =~ %r[Content-Type: application/x-www-form-urlencoded\r\n] - str.should =~ %r[Content-Length: 10\r\n] - str[-24..-1].should == "\r\n\r\naaaaaaaaaaaaaaaaaaaa" + str.should =~ %r[POST /some/other/path HTTP/1.1\r\n] + str.should =~ %r[Accept: \*/\*\r\n] + str.should =~ %r[Content-Type: application/x-www-form-urlencoded\r\n] + str.should =~ %r[Content-Length: 10\r\n] + str[-24..-1].should == "\r\n\r\naaaaaaaaaaaaaaaaaaaa" + end end it "sends the whole stream, regardless of the 'Content-Length' header" do diff --git a/spec/ruby/library/openssl/shared/constants.rb b/spec/ruby/library/openssl/shared/constants.rb index 0bed4156a1..836f75011b 100644 --- a/spec/ruby/library/openssl/shared/constants.rb +++ b/spec/ruby/library/openssl/shared/constants.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary module HMACConstants Contents = "Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. \nIt has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. \nIt was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." diff --git a/spec/ruby/library/socket/basicsocket/recv_spec.rb b/spec/ruby/library/socket/basicsocket/recv_spec.rb index 40033a5f5d..a51920f52a 100644 --- a/spec/ruby/library/socket/basicsocket/recv_spec.rb +++ b/spec/ruby/library/socket/basicsocket/recv_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../spec_helper' require_relative '../fixtures/classes' diff --git a/spec/ruby/library/socket/socket/gethostbyname_spec.rb b/spec/ruby/library/socket/socket/gethostbyname_spec.rb index 0858e255e4..618ef85387 100644 --- a/spec/ruby/library/socket/socket/gethostbyname_spec.rb +++ b/spec/ruby/library/socket/socket/gethostbyname_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../spec_helper' require_relative '../fixtures/classes' diff --git a/spec/ruby/library/socket/socket/udp_server_loop_spec.rb b/spec/ruby/library/socket/socket/udp_server_loop_spec.rb index fc030e75b9..cd22ea56cf 100644 --- a/spec/ruby/library/socket/socket/udp_server_loop_spec.rb +++ b/spec/ruby/library/socket/socket/udp_server_loop_spec.rb @@ -50,10 +50,10 @@ describe 'Socket.udp_server_loop' do end end + thread.join + msg.should == 'hello' src.should be_an_instance_of(Socket::UDPSource) - - thread.join end end end diff --git a/spec/ruby/library/socket/tcpsocket/gethostbyname_spec.rb b/spec/ruby/library/socket/tcpsocket/gethostbyname_spec.rb index f0e98778f5..5a2c704f35 100644 --- a/spec/ruby/library/socket/tcpsocket/gethostbyname_spec.rb +++ b/spec/ruby/library/socket/tcpsocket/gethostbyname_spec.rb @@ -2,7 +2,7 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' # TODO: verify these for windows -describe "TCPSocket#gethostbyname" do +describe "TCPSocket.gethostbyname" do before :each do suppress_warning do @host_info = TCPSocket.gethostbyname(SocketSpecs.hostname) @@ -52,7 +52,7 @@ describe "TCPSocket#gethostbyname" do end end -describe 'TCPSocket#gethostbyname' do +describe 'TCPSocket.gethostbyname' do it 'returns an Array' do suppress_warning do TCPSocket.gethostbyname('127.0.0.1').should be_an_instance_of(Array) diff --git a/spec/ruby/library/stringio/each_line_spec.rb b/spec/ruby/library/stringio/each_line_spec.rb index c68f7dae82..4ac0db7c45 100644 --- a/spec/ruby/library/stringio/each_line_spec.rb +++ b/spec/ruby/library/stringio/each_line_spec.rb @@ -21,3 +21,7 @@ end describe "StringIO#each_line when passed limit" do it_behaves_like :stringio_each_limit, :each_line end + +describe "StringIO#each when passed separator and limit" do + it_behaves_like :stringio_each_separator_and_limit, :each_line +end diff --git a/spec/ruby/library/stringio/each_spec.rb b/spec/ruby/library/stringio/each_spec.rb index 2c30ed5cda..7eb322f3ff 100644 --- a/spec/ruby/library/stringio/each_spec.rb +++ b/spec/ruby/library/stringio/each_spec.rb @@ -25,3 +25,7 @@ end describe "StringIO#each when passed limit" do it_behaves_like :stringio_each_limit, :each end + +describe "StringIO#each when passed separator and limit" do + it_behaves_like :stringio_each_separator_and_limit, :each +end diff --git a/spec/ruby/library/stringio/gets_spec.rb b/spec/ruby/library/stringio/gets_spec.rb index 4af7704a41..ac876f0b4f 100644 --- a/spec/ruby/library/stringio/gets_spec.rb +++ b/spec/ruby/library/stringio/gets_spec.rb @@ -1,250 +1,61 @@ require_relative '../../spec_helper' require "stringio" +require_relative "shared/gets" -describe "StringIO#gets when passed [separator]" do - before :each do - @io = StringIO.new("this>is>an>example") - end +describe "StringIO#gets" do + describe "when passed [separator]" do + it_behaves_like :stringio_gets_separator, :gets - it "returns the data read till the next occurrence of the passed separator" do - @io.gets(">").should == "this>" - @io.gets(">").should == "is>" - @io.gets(">").should == "an>" - @io.gets(">").should == "example" - end + it "returns nil if self is at the end" do + @io = StringIO.new("this>is>an>example") - it "sets $_ to the read content" do - @io.gets(">") - $_.should == "this>" - @io.gets(">") - $_.should == "is>" - @io.gets(">") - $_.should == "an>" - @io.gets(">") - $_.should == "example" - @io.gets(">") - $_.should be_nil - end - - it "accepts string as separator" do - @io.gets("is>") - $_.should == "this>" - @io.gets("an>") - $_.should == "is>an>" - @io.gets("example") - $_.should == "example" - @io.gets("ple") - $_.should be_nil - end - - it "updates self's lineno by one" do - @io.gets(">") - @io.lineno.should eql(1) - - @io.gets(">") - @io.lineno.should eql(2) - - @io.gets(">") - @io.lineno.should eql(3) - end - - it "returns the next paragraph when the passed separator is an empty String" do - io = StringIO.new("this is\n\nan example") - io.gets("").should == "this is\n\n" - io.gets("").should == "an example" - end - - it "returns the remaining content starting at the current position when passed nil" do - io = StringIO.new("this is\n\nan example") - io.pos = 5 - io.gets(nil).should == "is\n\nan example" - end - - it "tries to convert the passed separator to a String using #to_str" do - obj = mock('to_str') - obj.should_receive(:to_str).and_return(">") - @io.gets(obj).should == "this>" - end -end - -describe "StringIO#gets when passed no argument" do - before :each do - @io = StringIO.new("this is\nan example\nfor StringIO#gets") - end - - it "returns the data read till the next occurrence of $/ or till eof" do - @io.gets.should == "this is\n" - - begin - old_sep = $/ - suppress_warning {$/ = " "} - @io.gets.should == "an " - @io.gets.should == "example\nfor " - @io.gets.should == "StringIO#gets" - ensure - suppress_warning {$/ = old_sep} + @io.pos = 36 + @io.gets(">").should be_nil + @io.gets(">").should be_nil end end - it "sets $_ to the read content" do - @io.gets - $_.should == "this is\n" - @io.gets - $_.should == "an example\n" - @io.gets - $_.should == "for StringIO#gets" - @io.gets - $_.should be_nil + describe "when passed [limit]" do + it_behaves_like :stringio_gets_limit, :gets + + it "returns nil if self is at the end" do + @io = StringIO.new("this>is>an>example") + + @io.pos = 36 + @io.gets(3).should be_nil + @io.gets(3).should be_nil + end end - it "updates self's position" do - @io.gets - @io.pos.should eql(8) + describe "when passed [separator] and [limit]" do + it_behaves_like :stringio_gets_separator_and_limit, :gets - @io.gets - @io.pos.should eql(19) + it "returns nil if self is at the end" do + @io = StringIO.new("this>is>an>example") - @io.gets - @io.pos.should eql(36) + @io.pos = 36 + @io.gets(">", 3).should be_nil + @io.gets(">", 3).should be_nil + end end - it "updates self's lineno" do - @io.gets - @io.lineno.should eql(1) + describe "when passed no argument" do + it_behaves_like :stringio_gets_no_argument, :gets - @io.gets - @io.lineno.should eql(2) + it "returns nil if self is at the end" do + @io = StringIO.new("this>is>an>example") - @io.gets - @io.lineno.should eql(3) + @io.pos = 36 + @io.gets.should be_nil + @io.gets.should be_nil + end end - it "returns nil if self is at the end" do - @io.pos = 36 - @io.gets.should be_nil - @io.gets.should be_nil - end -end - -describe "StringIO#gets when passed [limit]" do - before :each do - @io = StringIO.new("this>is>an>example") - end - - it "returns the data read until the limit is met" do - @io.gets(4).should == "this" - @io.gets(3).should == ">is" - @io.gets(5).should == ">an>e" - @io.gets(6).should == "xample" - end - - it "sets $_ to the read content" do - @io.gets(4) - $_.should == "this" - @io.gets(3) - $_.should == ">is" - @io.gets(5) - $_.should == ">an>e" - @io.gets(6) - $_.should == "xample" - @io.gets(3) - $_.should be_nil - end - - it "updates self's lineno by one" do - @io.gets(3) - @io.lineno.should eql(1) - - @io.gets(3) - @io.lineno.should eql(2) - - @io.gets(3) - @io.lineno.should eql(3) - end - - it "tries to convert the passed limit to an Integer using #to_int" do - obj = mock('to_int') - obj.should_receive(:to_int).and_return(4) - @io.gets(obj).should == "this" - end - - it "returns a blank string when passed a limit of 0" do - @io.gets(0).should == "" - end - - it "ignores it when passed a negative limit" do - @io.gets(-4).should == "this>is>an>example" - end -end - -describe "StringIO#gets when passed [separator] and [limit]" do - before :each do - @io = StringIO.new("this>is>an>example") - end - - it "returns the data read until the limit is consumed or the separator is met" do - @io.gets('>', 8).should == "this>" - @io.gets('>', 2).should == "is" - @io.gets('>', 10).should == ">" - @io.gets('>', 6).should == "an>" - @io.gets('>', 5).should == "examp" - end - - it "sets $_ to the read content" do - @io.gets('>', 8) - $_.should == "this>" - @io.gets('>', 2) - $_.should == "is" - @io.gets('>', 10) - $_.should == ">" - @io.gets('>', 6) - $_.should == "an>" - @io.gets('>', 5) - $_.should == "examp" - end - - it "updates self's lineno by one" do - @io.gets('>', 3) - @io.lineno.should eql(1) - - @io.gets('>', 3) - @io.lineno.should eql(2) - - @io.gets('>', 3) - @io.lineno.should eql(3) - end - - it "tries to convert the passed separator to a String using #to_str" do - obj = mock('to_str') - obj.should_receive(:to_str).and_return('>') - @io.gets(obj, 5).should == "this>" - end - - it "does not raise TypeError if passed separator is nil" do - @io.gets(nil, 5).should == "this>" - end - - it "tries to convert the passed limit to an Integer using #to_int" do - obj = mock('to_int') - obj.should_receive(:to_int).and_return(5) - @io.gets('>', obj).should == "this>" - end -end - -describe "StringIO#gets when in write-only mode" do - it "raises an IOError" do - io = StringIO.new(+"xyz", "w") - -> { io.gets }.should raise_error(IOError) - - io = StringIO.new("xyz") - io.close_read - -> { io.gets }.should raise_error(IOError) - end -end - -describe "StringIO#gets when passed [chomp]" do - it "returns the data read without a trailing newline character" do - io = StringIO.new("this>is>an>example\n") - io.gets(chomp: true).should == "this>is>an>example" + describe "when passed [chomp]" do + it_behaves_like :stringio_gets_chomp, :gets + end + + describe "when in write-only mode" do + it_behaves_like :stringio_gets_write_only, :gets end end diff --git a/spec/ruby/library/stringio/readline_spec.rb b/spec/ruby/library/stringio/readline_spec.rb index b16a16e23f..085360707f 100644 --- a/spec/ruby/library/stringio/readline_spec.rb +++ b/spec/ruby/library/stringio/readline_spec.rb @@ -1,150 +1,58 @@ require_relative '../../spec_helper' +require "stringio" require_relative 'fixtures/classes' +require_relative "shared/gets" +describe "StringIO#readline" do + describe "when passed [separator]" do + it_behaves_like :stringio_gets_separator, :readline -describe "StringIO#readline when passed [separator]" do - before :each do - @io = StringIO.new("this>is>an>example") - end + it "raises an IOError if self is at the end" do + @io = StringIO.new("this>is>an>example") - it "returns the data read till the next occurrence of the passed separator" do - @io.readline(">").should == "this>" - @io.readline(">").should == "is>" - @io.readline(">").should == "an>" - @io.readline(">").should == "example" - end - - it "sets $_ to the read content" do - @io.readline(">") - $_.should == "this>" - @io.readline(">") - $_.should == "is>" - @io.readline(">") - $_.should == "an>" - @io.readline(">") - $_.should == "example" - end - - it "updates self's lineno by one" do - @io.readline(">") - @io.lineno.should eql(1) - - @io.readline(">") - @io.lineno.should eql(2) - - @io.readline(">") - @io.lineno.should eql(3) - end - - it "returns the next paragraph when the passed separator is an empty String" do - io = StringIO.new("this is\n\nan example") - io.readline("").should == "this is\n\n" - io.readline("").should == "an example" - end - - it "returns the remaining content starting at the current position when passed nil" do - io = StringIO.new("this is\n\nan example") - io.pos = 5 - io.readline(nil).should == "is\n\nan example" - end - - it "tries to convert the passed separator to a String using #to_str" do - obj = mock('to_str') - obj.should_receive(:to_str).and_return(">") - @io.readline(obj).should == "this>" - end -end - -describe "StringIO#readline when passed no argument" do - before :each do - @io = StringIO.new("this is\nan example\nfor StringIO#readline") - end - - it "returns the data read till the next occurrence of $/ or till eof" do - @io.readline.should == "this is\n" - - begin - old_sep = $/ - suppress_warning {$/ = " "} - @io.readline.should == "an " - @io.readline.should == "example\nfor " - @io.readline.should == "StringIO#readline" - ensure - suppress_warning {$/ = old_sep} + @io.pos = 36 + -> { @io.readline(">") }.should raise_error(IOError) end end - it "sets $_ to the read content" do - @io.readline - $_.should == "this is\n" - @io.readline - $_.should == "an example\n" - @io.readline - $_.should == "for StringIO#readline" + describe "when passed [limit]" do + it_behaves_like :stringio_gets_limit, :readline + + it "raises an IOError if self is at the end" do + @io = StringIO.new("this>is>an>example") + + @io.pos = 36 + -> { @io.readline(3) }.should raise_error(IOError) + end end - it "updates self's position" do - @io.readline - @io.pos.should eql(8) + describe "when passed [separator] and [limit]" do + it_behaves_like :stringio_gets_separator_and_limit, :readline - @io.readline - @io.pos.should eql(19) + it "raises an IOError if self is at the end" do + @io = StringIO.new("this>is>an>example") - @io.readline - @io.pos.should eql(40) + @io.pos = 36 + -> { @io.readline(">", 3) }.should raise_error(IOError) + end end - it "updates self's lineno" do - @io.readline - @io.lineno.should eql(1) + describe "when passed no argument" do + it_behaves_like :stringio_gets_no_argument, :readline - @io.readline - @io.lineno.should eql(2) + it "raises an IOError if self is at the end" do + @io = StringIO.new("this>is>an>example") - @io.readline - @io.lineno.should eql(3) + @io.pos = 36 + -> { @io.readline }.should raise_error(IOError) + end end - it "raises an IOError if self is at the end" do - @io.pos = 40 - -> { @io.readline }.should raise_error(IOError) - end -end - -describe "StringIO#readline when in write-only mode" do - it "raises an IOError" do - io = StringIO.new(+"xyz", "w") - -> { io.readline }.should raise_error(IOError) - - io = StringIO.new("xyz") - io.close_read - -> { io.readline }.should raise_error(IOError) - end -end - -describe "StringIO#readline when passed [chomp]" do - it "returns the data read without a trailing newline character" do - io = StringIO.new("this>is>an>example\n") - io.readline(chomp: true).should == "this>is>an>example" - end -end - -describe "StringIO#readline when passed [limit]" do - before :each do - @io = StringIO.new("this>is>an>example") - end - - it "returns the data read until the limit is met" do - io = StringIO.new("this>is>an>example\n") - io.readline(3).should == "thi" - end - - it "returns a blank string when passed a limit of 0" do - @io.readline(0).should == "" - end - - it "ignores it when the limit is negative" do - seen = [] - @io.readline(-4).should == "this>is>an>example" + describe "when passed [chomp]" do + it_behaves_like :stringio_gets_chomp, :readline + end + + describe "when in write-only mode" do + it_behaves_like :stringio_gets_write_only, :readline end end diff --git a/spec/ruby/library/stringio/shared/each.rb b/spec/ruby/library/stringio/shared/each.rb index 33f1c0e359..626b41a4d3 100644 --- a/spec/ruby/library/stringio/shared/each.rb +++ b/spec/ruby/library/stringio/shared/each.rb @@ -150,3 +150,60 @@ describe :stringio_each_limit, shared: true do seen.should == ["a b ", "c d ", "e\n", "1 2 ", "3 4 ", "5"] end end + +describe :stringio_each_separator_and_limit, shared: true do + before :each do + @io = StringIO.new("this>is>an>example") + end + + it "returns the data read until the limit is consumed or the separator is met" do + @io.send(@method, '>', 8) { |s| break s }.should == "this>" + @io.send(@method, '>', 2) { |s| break s }.should == "is" + @io.send(@method, '>', 10) { |s| break s }.should == ">" + @io.send(@method, '>', 6) { |s| break s }.should == "an>" + @io.send(@method, '>', 5) { |s| break s }.should == "examp" + end + + it "truncates the multi-character separator at the end to meet the limit" do + @io.send(@method, "is>an", 7) { |s| break s }.should == "this>is" + end + + it "does not change $_" do + $_ = "test" + @io.send(@method, '>', 8) { |s| s } + $_.should == "test" + end + + it "updates self's lineno by one" do + @io.send(@method, '>', 3) { |s| break s } + @io.lineno.should eql(1) + + @io.send(@method, '>', 3) { |s| break s } + @io.lineno.should eql(2) + + @io.send(@method, '>', 3) { |s| break s } + @io.lineno.should eql(3) + end + + it "tries to convert the passed separator to a String using #to_str" do # TODO + obj = mock('to_str') + obj.should_receive(:to_str).and_return('>') + + seen = [] + @io.send(@method, obj, 5) { |s| seen << s } + seen.should == ["this>", "is>", "an>", "examp", "le"] + end + + it "does not raise TypeError if passed separator is nil" do + @io.send(@method, nil, 5) { |s| break s }.should == "this>" + end + + it "tries to convert the passed limit to an Integer using #to_int" do # TODO + obj = mock('to_int') + obj.should_receive(:to_int).and_return(5) + + seen = [] + @io.send(@method, '>', obj) { |s| seen << s } + seen.should == ["this>", "is>", "an>", "examp", "le"] + end +end diff --git a/spec/ruby/library/stringio/shared/gets.rb b/spec/ruby/library/stringio/shared/gets.rb new file mode 100644 index 0000000000..8396b161f1 --- /dev/null +++ b/spec/ruby/library/stringio/shared/gets.rb @@ -0,0 +1,249 @@ +describe :stringio_gets_separator, shared: true do + describe "when passed [separator]" do + before :each do + @io = StringIO.new("this>is>an>example") + end + + it "returns the data read till the next occurrence of the passed separator" do + @io.send(@method, ">").should == "this>" + @io.send(@method, ">").should == "is>" + @io.send(@method, ">").should == "an>" + @io.send(@method, ">").should == "example" + end + + it "sets $_ to the read content" do + @io.send(@method, ">") + $_.should == "this>" + @io.send(@method, ">") + $_.should == "is>" + @io.send(@method, ">") + $_.should == "an>" + @io.send(@method, ">") + $_.should == "example" + end + + it "accepts string as separator" do + @io.send(@method, "is>") + $_.should == "this>" + @io.send(@method, "an>") + $_.should == "is>an>" + @io.send(@method, "example") + $_.should == "example" + end + + it "updates self's lineno by one" do + @io.send(@method, ">") + @io.lineno.should eql(1) + + @io.send(@method, ">") + @io.lineno.should eql(2) + + @io.send(@method, ">") + @io.lineno.should eql(3) + end + + it "returns the next paragraph when the passed separator is an empty String" do + io = StringIO.new("this is\n\nan example") + io.send(@method, "").should == "this is\n\n" + io.send(@method, "").should == "an example" + end + + it "returns the remaining content starting at the current position when passed nil" do + io = StringIO.new("this is\n\nan example") + io.pos = 5 + io.send(@method, nil).should == "is\n\nan example" + end + + it "tries to convert the passed separator to a String using #to_str" do + obj = mock('to_str') + obj.should_receive(:to_str).and_return(">") + @io.send(@method, obj).should == "this>" + end + end +end + +describe :stringio_gets_limit, shared: true do + describe "when passed [limit]" do + before :each do + @io = StringIO.new("this>is>an>example") + end + + it "returns the data read until the limit is met" do + @io.send(@method, 4).should == "this" + @io.send(@method, 3).should == ">is" + @io.send(@method, 5).should == ">an>e" + @io.send(@method, 6).should == "xample" + end + + it "sets $_ to the read content" do + @io.send(@method, 4) + $_.should == "this" + @io.send(@method, 3) + $_.should == ">is" + @io.send(@method, 5) + $_.should == ">an>e" + @io.send(@method, 6) + $_.should == "xample" + end + + it "updates self's lineno by one" do + @io.send(@method, 3) + @io.lineno.should eql(1) + + @io.send(@method, 3) + @io.lineno.should eql(2) + + @io.send(@method, 3) + @io.lineno.should eql(3) + end + + it "tries to convert the passed limit to an Integer using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(4) + @io.send(@method, obj).should == "this" + end + + it "returns a blank string when passed a limit of 0" do + @io.send(@method, 0).should == "" + end + + it "ignores it when passed a negative limit" do + @io.send(@method, -4).should == "this>is>an>example" + end + end +end + +describe :stringio_gets_separator_and_limit, shared: true do + describe "when passed [separator] and [limit]" do + before :each do + @io = StringIO.new("this>is>an>example") + end + + it "returns the data read until the limit is consumed or the separator is met" do + @io.send(@method, '>', 8).should == "this>" + @io.send(@method, '>', 2).should == "is" + @io.send(@method, '>', 10).should == ">" + @io.send(@method, '>', 6).should == "an>" + @io.send(@method, '>', 5).should == "examp" + end + + it "truncates the multi-character separator at the end to meet the limit" do + @io.send(@method, "is>an", 7).should == "this>is" + end + + it "sets $_ to the read content" do + @io.send(@method, '>', 8) + $_.should == "this>" + @io.send(@method, '>', 2) + $_.should == "is" + @io.send(@method, '>', 10) + $_.should == ">" + @io.send(@method, '>', 6) + $_.should == "an>" + @io.send(@method, '>', 5) + $_.should == "examp" + end + + it "updates self's lineno by one" do + @io.send(@method, '>', 3) + @io.lineno.should eql(1) + + @io.send(@method, '>', 3) + @io.lineno.should eql(2) + + @io.send(@method, '>', 3) + @io.lineno.should eql(3) + end + + it "tries to convert the passed separator to a String using #to_str" do + obj = mock('to_str') + obj.should_receive(:to_str).and_return('>') + @io.send(@method, obj, 5).should == "this>" + end + + it "does not raise TypeError if passed separator is nil" do + @io.send(@method, nil, 5).should == "this>" + end + + it "tries to convert the passed limit to an Integer using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(5) + @io.send(@method, '>', obj).should == "this>" + end + end +end + +describe :stringio_gets_no_argument, shared: true do + describe "when passed no argument" do + before :each do + @io = StringIO.new("this is\nan example\nfor StringIO#gets") + end + + it "returns the data read till the next occurrence of $/ or till eof" do + @io.send(@method).should == "this is\n" + + begin + old_sep = $/ + suppress_warning {$/ = " "} + @io.send(@method).should == "an " + @io.send(@method).should == "example\nfor " + @io.send(@method).should == "StringIO#gets" + ensure + suppress_warning {$/ = old_sep} + end + end + + it "sets $_ to the read content" do + @io.send(@method) + $_.should == "this is\n" + @io.send(@method) + $_.should == "an example\n" + @io.send(@method) + $_.should == "for StringIO#gets" + end + + it "updates self's position" do + @io.send(@method) + @io.pos.should eql(8) + + @io.send(@method) + @io.pos.should eql(19) + + @io.send(@method) + @io.pos.should eql(36) + end + + it "updates self's lineno" do + @io.send(@method) + @io.lineno.should eql(1) + + @io.send(@method) + @io.lineno.should eql(2) + + @io.send(@method) + @io.lineno.should eql(3) + end + end +end + +describe :stringio_gets_chomp, shared: true do + describe "when passed [chomp]" do + it "returns the data read without a trailing newline character" do + io = StringIO.new("this>is>an>example\n") + io.send(@method, chomp: true).should == "this>is>an>example" + end + end +end + +describe :stringio_gets_write_only, shared: true do + describe "when in write-only mode" do + it "raises an IOError" do + io = StringIO.new(+"xyz", "w") + -> { io.send(@method) }.should raise_error(IOError) + + io = StringIO.new("xyz") + io.close_read + -> { io.send(@method) }.should raise_error(IOError) + end + end +end diff --git a/spec/ruby/library/stringscanner/getch_spec.rb b/spec/ruby/library/stringscanner/getch_spec.rb index ac43cf449d..c9c3eb6fd3 100644 --- a/spec/ruby/library/stringscanner/getch_spec.rb +++ b/spec/ruby/library/stringscanner/getch_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'shared/extract_range' require 'strscan' diff --git a/spec/ruby/library/stringscanner/shared/get_byte.rb b/spec/ruby/library/stringscanner/shared/get_byte.rb index f1b016905f..1f7378d5c6 100644 --- a/spec/ruby/library/stringscanner/shared/get_byte.rb +++ b/spec/ruby/library/stringscanner/shared/get_byte.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require 'strscan' describe :strscan_get_byte, shared: true do diff --git a/spec/ruby/library/timeout/timeout_spec.rb b/spec/ruby/library/timeout/timeout_spec.rb index 584b38d8ec..e16bcaea6a 100644 --- a/spec/ruby/library/timeout/timeout_spec.rb +++ b/spec/ruby/library/timeout/timeout_spec.rb @@ -39,4 +39,12 @@ describe "Timeout.timeout" do 42 end.should == 42 end + + ruby_version_is "3.4" do + it "raises an ArgumentError when provided with a negative duration" do + -> { + Timeout.timeout(-1) + }.should raise_error(ArgumentError, "Timeout sec must be a non-negative number") + end + end end diff --git a/spec/ruby/library/win32ole/fixtures/classes.rb b/spec/ruby/library/win32ole/fixtures/classes.rb index f61cf6ba69..5a16fcca45 100644 --- a/spec/ruby/library/win32ole/fixtures/classes.rb +++ b/spec/ruby/library/win32ole/fixtures/classes.rb @@ -1,14 +1,25 @@ require 'win32ole' +# win32ole deprecated constants like WIN32OLE_TYPELIB in Ruby 3.4 +# but only added the replacements like WIN32OLE::TypeLib in Ruby 3.4. +# So we use the new-style constants in specs to avoid deprecation warnings +# and we define the new-style constants as the old ones if they don't exist yet. +WIN32OLE::TypeLib ||= WIN32OLE_TYPELIB +WIN32OLE::RuntimeError ||= WIN32OLERuntimeError +WIN32OLE::Method ||= WIN32OLE_METHOD +WIN32OLE::Type ||= WIN32OLE_TYPE +WIN32OLE::Event ||= WIN32OLE_EVENT +WIN32OLE::Param ||= WIN32OLE_PARAM + module WIN32OLESpecs - MSXML_AVAILABLE = WIN32OLE_TYPELIB.typelibs.any? { |t| t.name.start_with?('Microsoft XML') } - SYSTEM_MONITOR_CONTROL_AVAILABLE = WIN32OLE_TYPELIB.typelibs.any? { |t| t.name.start_with?('System Monitor Control') } + MSXML_AVAILABLE = WIN32OLE::TypeLib.typelibs.any? { |t| t.name.start_with?('Microsoft XML') } + SYSTEM_MONITOR_CONTROL_AVAILABLE = WIN32OLE::TypeLib.typelibs.any? { |t| t.name.start_with?('System Monitor Control') } def self.new_ole(name) tries = 0 begin WIN32OLE.new(name) - rescue WIN32OLERuntimeError => e + rescue WIN32OLE::RuntimeError => e if tries < 3 tries += 1 $stderr.puts "WIN32OLESpecs#new_ole retry (#{tries}): #{e.class}: #{e.message}" diff --git a/spec/ruby/library/win32ole/win32ole/locale_spec.rb b/spec/ruby/library/win32ole/win32ole/locale_spec.rb index 78ede4375a..89e84d8038 100644 --- a/spec/ruby/library/win32ole/win32ole/locale_spec.rb +++ b/spec/ruby/library/win32ole/win32ole/locale_spec.rb @@ -13,14 +13,14 @@ platform_is :windows do begin begin WIN32OLE.locale = 1041 - rescue WIN32OLERuntimeError + rescue WIN32OLE::RuntimeError STDERR.puts("\n#{__FILE__}:#{__LINE__}:#{self.class.name}.test_s_locale_set is skipped(Japanese locale is not installed)") return end WIN32OLE.locale.should == 1041 WIN32OLE.locale = WIN32OLE::LOCALE_SYSTEM_DEFAULT - -> { WIN32OLE.locale = 111 }.should raise_error WIN32OLERuntimeError + -> { WIN32OLE.locale = 111 }.should raise_error WIN32OLE::RuntimeError WIN32OLE.locale.should == WIN32OLE::LOCALE_SYSTEM_DEFAULT ensure WIN32OLE.locale.should == WIN32OLE::LOCALE_SYSTEM_DEFAULT diff --git a/spec/ruby/library/win32ole/win32ole/new_spec.rb b/spec/ruby/library/win32ole/win32ole/new_spec.rb index 7e91c2d3ea..b2a0a5da18 100644 --- a/spec/ruby/library/win32ole/win32ole/new_spec.rb +++ b/spec/ruby/library/win32ole/win32ole/new_spec.rb @@ -17,8 +17,8 @@ platform_is :windows do -> { WIN32OLESpecs.new_ole(42) }.should raise_error( TypeError ) end - it "raises WIN32OLERuntimeError if invalid string is given" do - -> { WIN32OLE.new('foo') }.should raise_error( WIN32OLERuntimeError ) + it "raises WIN32OLE::RuntimeError if invalid string is given" do + -> { WIN32OLE.new('foo') }.should raise_error( WIN32OLE::RuntimeError ) end end diff --git a/spec/ruby/library/win32ole/win32ole/ole_func_methods_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_func_methods_spec.rb index 2bbe8c27d4..b846685518 100644 --- a/spec/ruby/library/win32ole/win32ole/ole_func_methods_spec.rb +++ b/spec/ruby/library/win32ole/win32ole/ole_func_methods_spec.rb @@ -11,8 +11,8 @@ platform_is :windows do -> { @dict.ole_func_methods(1) }.should raise_error ArgumentError end - it "returns an array of WIN32OLE_METHODs" do - @dict.ole_func_methods.all? { |m| m.kind_of? WIN32OLE_METHOD }.should be_true + it "returns an array of WIN32OLE::Methods" do + @dict.ole_func_methods.all? { |m| m.kind_of? WIN32OLE::Method }.should be_true end it "contains a 'AddRef' method for Scripting Dictionary" do diff --git a/spec/ruby/library/win32ole/win32ole/ole_get_methods_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_get_methods_spec.rb index c1d1970214..b6e7f960bb 100644 --- a/spec/ruby/library/win32ole/win32ole/ole_get_methods_spec.rb +++ b/spec/ruby/library/win32ole/win32ole/ole_get_methods_spec.rb @@ -8,8 +8,8 @@ platform_is :windows do @win32ole = WIN32OLESpecs.new_ole('Shell.Application') end - it "returns an array of WIN32OLE_METHOD objects" do - @win32ole.ole_get_methods.all? {|m| m.kind_of? WIN32OLE_METHOD}.should be_true + it "returns an array of WIN32OLE::Method objects" do + @win32ole.ole_get_methods.all? {|m| m.kind_of? WIN32OLE::Method}.should be_true end end diff --git a/spec/ruby/library/win32ole/win32ole/ole_methods_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_methods_spec.rb index fe161ce9f0..92c4363f78 100644 --- a/spec/ruby/library/win32ole/win32ole/ole_methods_spec.rb +++ b/spec/ruby/library/win32ole/win32ole/ole_methods_spec.rb @@ -11,8 +11,8 @@ platform_is :windows do -> { @dict.ole_methods(1) }.should raise_error ArgumentError end - it "returns an array of WIN32OLE_METHODs" do - @dict.ole_methods.all? { |m| m.kind_of? WIN32OLE_METHOD }.should be_true + it "returns an array of WIN32OLE::Methods" do + @dict.ole_methods.all? { |m| m.kind_of? WIN32OLE::Method }.should be_true end it "contains a 'AddRef' method for Scripting Dictionary" do diff --git a/spec/ruby/library/win32ole/win32ole/ole_obj_help_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_obj_help_spec.rb index afcf16a051..f298f19dba 100644 --- a/spec/ruby/library/win32ole/win32ole/ole_obj_help_spec.rb +++ b/spec/ruby/library/win32ole/win32ole/ole_obj_help_spec.rb @@ -12,8 +12,8 @@ platform_is :windows do -> { @dict.ole_obj_help(1) }.should raise_error ArgumentError end - it "returns an instance of WIN32OLE_TYPE" do - @dict.ole_obj_help.kind_of?(WIN32OLE_TYPE).should be_true + it "returns an instance of WIN32OLE::Type" do + @dict.ole_obj_help.kind_of?(WIN32OLE::Type).should be_true end end end diff --git a/spec/ruby/library/win32ole/win32ole/ole_put_methods_spec.rb b/spec/ruby/library/win32ole/win32ole/ole_put_methods_spec.rb index c091c83c95..2b46ae47de 100644 --- a/spec/ruby/library/win32ole/win32ole/ole_put_methods_spec.rb +++ b/spec/ruby/library/win32ole/win32ole/ole_put_methods_spec.rb @@ -11,8 +11,8 @@ platform_is :windows do -> { @dict.ole_put_methods(1) }.should raise_error ArgumentError end - it "returns an array of WIN32OLE_METHODs" do - @dict.ole_put_methods.all? { |m| m.kind_of? WIN32OLE_METHOD }.should be_true + it "returns an array of WIN32OLE::Methods" do + @dict.ole_put_methods.all? { |m| m.kind_of? WIN32OLE::Method }.should be_true end it "contains a 'Key' method for Scripting Dictionary" do diff --git a/spec/ruby/library/win32ole/win32ole/shared/ole_method.rb b/spec/ruby/library/win32ole/win32ole/shared/ole_method.rb index f1fd8713a4..bae424a604 100644 --- a/spec/ruby/library/win32ole/win32ole/shared/ole_method.rb +++ b/spec/ruby/library/win32ole/win32ole/shared/ole_method.rb @@ -10,9 +10,9 @@ platform_is :windows do -> { @dict.send(@method) }.should raise_error ArgumentError end - it "returns the WIN32OLE_METHOD 'Add' if given 'Add'" do + it "returns the WIN32OLE::Method 'Add' if given 'Add'" do result = @dict.send(@method, "Add") - result.kind_of?(WIN32OLE_METHOD).should be_true + result.kind_of?(WIN32OLE::Method).should be_true result.name.should == 'Add' end end diff --git a/spec/ruby/library/win32ole/win32ole_event/new_spec.rb b/spec/ruby/library/win32ole/win32ole_event/new_spec.rb index 94fabb1e3b..4efd4c3e0f 100644 --- a/spec/ruby/library/win32ole/win32ole_event/new_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_event/new_spec.rb @@ -3,7 +3,7 @@ platform_is :windows do require_relative '../fixtures/classes' guard -> { WIN32OLESpecs::MSXML_AVAILABLE } do - describe "WIN32OLE_EVENT.new" do + describe "WIN32OLE::Event.new" do before :all do @xml_dom = WIN32OLESpecs.new_ole('MSXML.DOMDocument') end @@ -13,21 +13,21 @@ platform_is :windows do end it "raises TypeError given invalid argument" do - -> { WIN32OLE_EVENT.new "A" }.should raise_error TypeError + -> { WIN32OLE::Event.new "A" }.should raise_error TypeError end it "raises RuntimeError if event does not exist" do - -> { WIN32OLE_EVENT.new(@xml_dom, 'A') }.should raise_error RuntimeError + -> { WIN32OLE::Event.new(@xml_dom, 'A') }.should raise_error RuntimeError end it "raises RuntimeError if OLE object has no events" do dict = WIN32OLESpecs.new_ole('Scripting.Dictionary') - -> { WIN32OLE_EVENT.new(dict) }.should raise_error RuntimeError + -> { WIN32OLE::Event.new(dict) }.should raise_error RuntimeError end - it "creates WIN32OLE_EVENT object" do - ev = WIN32OLE_EVENT.new(@xml_dom) - ev.should be_kind_of WIN32OLE_EVENT + it "creates WIN32OLE::Event object" do + ev = WIN32OLE::Event.new(@xml_dom) + ev.should be_kind_of WIN32OLE::Event end end end diff --git a/spec/ruby/library/win32ole/win32ole_event/on_event_spec.rb b/spec/ruby/library/win32ole/win32ole_event/on_event_spec.rb index 0957bdd2d4..acc7d2d6b6 100644 --- a/spec/ruby/library/win32ole/win32ole_event/on_event_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_event/on_event_spec.rb @@ -15,7 +15,7 @@ platform_is :windows do @event_spec_alt = "spec_alt" end - describe "WIN32OLE_EVENT#on_event" do + describe "WIN32OLE::Event#on_event" do before :all do @fn_xml = File.absolute_path "../fixtures/event.xml", __dir__ end @@ -23,7 +23,7 @@ platform_is :windows do before :each do @xml_dom = WIN32OLESpecs.new_ole 'MSXML.DOMDocument' @xml_dom.async = true - @ev = WIN32OLE_EVENT.new @xml_dom + @ev = WIN32OLE::Event.new @xml_dom @event_global = '' @event_specific = '' @event_spec_alt = '' @@ -37,21 +37,21 @@ platform_is :windows do it "sets global event handler properly, and the handler is invoked by event loop" do @ev.on_event { |*args| handler_global(*args) } @xml_dom.loadXML "Rubytrunk" - WIN32OLE_EVENT.message_loop + WIN32OLE::Event.message_loop @event_global.should =~ /onreadystatechange/ end it "accepts a String argument and the handler is invoked by event loop" do @ev.on_event("onreadystatechange") { |*args| @event = 'foo' } @xml_dom.loadXML "Rubytrunk" - WIN32OLE_EVENT.message_loop + WIN32OLE::Event.message_loop @event.should =~ /foo/ end it "accepts a Symbol argument and the handler is invoked by event loop" do @ev.on_event(:onreadystatechange) { |*args| @event = 'bar' } @xml_dom.loadXML "Rubytrunk" - WIN32OLE_EVENT.message_loop + WIN32OLE::Event.message_loop @event.should =~ /bar/ end @@ -60,7 +60,7 @@ platform_is :windows do @ev.on_event("onreadystatechange") { |*args| handler_specific(*args) } @ev.on_event("onreadystatechange") { |*args| handler_spec_alt(*args) } @xml_dom.load @fn_xml - WIN32OLE_EVENT.message_loop + WIN32OLE::Event.message_loop @event_global.should == 'ondataavailable' @event_global.should_not =~ /onreadystatechange/ @event_specific.should == '' diff --git a/spec/ruby/library/win32ole/win32ole_method/dispid_spec.rb b/spec/ruby/library/win32ole/win32ole_method/dispid_spec.rb index ece71df0d4..e5f55f2d38 100644 --- a/spec/ruby/library/win32ole/win32ole_method/dispid_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/dispid_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD#dispid" do + describe "WIN32OLE::Method#dispid" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") - @m = WIN32OLE_METHOD.new(ole_type, "namespace") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + @m = WIN32OLE::Method.new(ole_type, "namespace") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_method/event_interface_spec.rb b/spec/ruby/library/win32ole/win32ole_method/event_interface_spec.rb index 78634d2fde..bea47348ee 100644 --- a/spec/ruby/library/win32ole/win32ole_method/event_interface_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/event_interface_spec.rb @@ -3,12 +3,12 @@ platform_is :windows do require_relative '../fixtures/classes' guard -> { WIN32OLESpecs::SYSTEM_MONITOR_CONTROL_AVAILABLE } do - describe "WIN32OLE_METHOD#event_interface" do + describe "WIN32OLE::Method#event_interface" do before :each do - ole_type = WIN32OLE_TYPE.new("System Monitor Control", "SystemMonitor") - @on_dbl_click_method = WIN32OLE_METHOD.new(ole_type, "OnDblClick") - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") - @namespace_method = WIN32OLE_METHOD.new(ole_type, "namespace") + ole_type = WIN32OLE::Type.new("System Monitor Control", "SystemMonitor") + @on_dbl_click_method = WIN32OLE::Method.new(ole_type, "OnDblClick") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + @namespace_method = WIN32OLE::Method.new(ole_type, "namespace") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_method/event_spec.rb b/spec/ruby/library/win32ole/win32ole_method/event_spec.rb index 9b642a010c..5a94cf5ce6 100644 --- a/spec/ruby/library/win32ole/win32ole_method/event_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/event_spec.rb @@ -3,10 +3,10 @@ platform_is :windows do require_relative '../fixtures/classes' guard -> { WIN32OLESpecs::SYSTEM_MONITOR_CONTROL_AVAILABLE } do - describe "WIN32OLE_METHOD#event?" do + describe "WIN32OLE::Method#event?" do before :each do - ole_type = WIN32OLE_TYPE.new("System Monitor Control", "SystemMonitor") - @on_dbl_click_method = WIN32OLE_METHOD.new(ole_type, "OnDblClick") + ole_type = WIN32OLE::Type.new("System Monitor Control", "SystemMonitor") + @on_dbl_click_method = WIN32OLE::Method.new(ole_type, "OnDblClick") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_method/helpcontext_spec.rb b/spec/ruby/library/win32ole/win32ole_method/helpcontext_spec.rb index d1c5ee3be2..83f34b9c10 100644 --- a/spec/ruby/library/win32ole/win32ole_method/helpcontext_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/helpcontext_spec.rb @@ -2,12 +2,12 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD#helpcontext" do + describe "WIN32OLE::Method#helpcontext" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject") - @get_file_version = WIN32OLE_METHOD.new(ole_type, "GetFileVersion") - ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File") - @m_file_name = WIN32OLE_METHOD.new(ole_type, "name") + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "FileSystemObject") + @get_file_version = WIN32OLE::Method.new(ole_type, "GetFileVersion") + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_method/helpfile_spec.rb b/spec/ruby/library/win32ole/win32ole_method/helpfile_spec.rb index 59dad9244c..9cf9d63d3b 100644 --- a/spec/ruby/library/win32ole/win32ole_method/helpfile_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/helpfile_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD#helpfile" do + describe "WIN32OLE::Method#helpfile" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File") - @m_file_name = WIN32OLE_METHOD.new(ole_type, "name") + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_method/helpstring_spec.rb b/spec/ruby/library/win32ole/win32ole_method/helpstring_spec.rb index b2f24ba151..5ae4a5e090 100644 --- a/spec/ruby/library/win32ole/win32ole_method/helpstring_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/helpstring_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD#helpstring" do + describe "WIN32OLE::Method#helpstring" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File") - @m_file_name = WIN32OLE_METHOD.new(ole_type, "name") + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_method/invkind_spec.rb b/spec/ruby/library/win32ole/win32ole_method/invkind_spec.rb index d7fedf0d36..06acbb58a5 100644 --- a/spec/ruby/library/win32ole/win32ole_method/invkind_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/invkind_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD#invkind" do + describe "WIN32OLE::Method#invkind" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File") - @m_file_name = WIN32OLE_METHOD.new(ole_type, "name") + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_method/invoke_kind_spec.rb b/spec/ruby/library/win32ole/win32ole_method/invoke_kind_spec.rb index d5536fd17b..0e97ec3305 100644 --- a/spec/ruby/library/win32ole/win32ole_method/invoke_kind_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/invoke_kind_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD#invoke_kind" do + describe "WIN32OLE::Method#invoke_kind" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File") - @m_file_name = WIN32OLE_METHOD.new(ole_type, "name") + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_method/name_spec.rb b/spec/ruby/library/win32ole/win32ole_method/name_spec.rb index 477b820f4d..6e2e233a62 100644 --- a/spec/ruby/library/win32ole/win32ole_method/name_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/name_spec.rb @@ -4,7 +4,7 @@ require_relative 'shared/name' platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD#name" do + describe "WIN32OLE::Method#name" do it_behaves_like :win32ole_method_name, :name end diff --git a/spec/ruby/library/win32ole/win32ole_method/new_spec.rb b/spec/ruby/library/win32ole/win32ole_method/new_spec.rb index 4e427421b9..46186ae566 100644 --- a/spec/ruby/library/win32ole/win32ole_method/new_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/new_spec.rb @@ -2,31 +2,31 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD.new" do + describe "WIN32OLE::Method.new" do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") end it "raises TypeError when given non-strings" do - -> { WIN32OLE_METHOD.new(1, 2) }.should raise_error TypeError + -> { WIN32OLE::Method.new(1, 2) }.should raise_error TypeError end it "raises ArgumentError if only 1 argument is given" do - -> { WIN32OLE_METHOD.new("hello") }.should raise_error ArgumentError - -> { WIN32OLE_METHOD.new(@ole_type) }.should raise_error ArgumentError + -> { WIN32OLE::Method.new("hello") }.should raise_error ArgumentError + -> { WIN32OLE::Method.new(@ole_type) }.should raise_error ArgumentError end - it "returns a valid WIN32OLE_METHOD object" do - WIN32OLE_METHOD.new(@ole_type, "Open").should be_kind_of WIN32OLE_METHOD - WIN32OLE_METHOD.new(@ole_type, "open").should be_kind_of WIN32OLE_METHOD + it "returns a valid WIN32OLE::Method object" do + WIN32OLE::Method.new(@ole_type, "Open").should be_kind_of WIN32OLE::Method + WIN32OLE::Method.new(@ole_type, "open").should be_kind_of WIN32OLE::Method end - it "raises WIN32OLERuntimeError if the method does not exist" do - -> { WIN32OLE_METHOD.new(@ole_type, "NonexistentMethod") }.should raise_error WIN32OLERuntimeError + it "raises WIN32OLE::RuntimeError if the method does not exist" do + -> { WIN32OLE::Method.new(@ole_type, "NonexistentMethod") }.should raise_error WIN32OLE::RuntimeError end it "raises TypeError if second argument is not a String" do - -> { WIN32OLE_METHOD.new(@ole_type, 5) }.should raise_error TypeError + -> { WIN32OLE::Method.new(@ole_type, 5) }.should raise_error TypeError end end diff --git a/spec/ruby/library/win32ole/win32ole_method/offset_vtbl_spec.rb b/spec/ruby/library/win32ole/win32ole_method/offset_vtbl_spec.rb index b3da9a8303..3c80cb3c2a 100644 --- a/spec/ruby/library/win32ole/win32ole_method/offset_vtbl_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/offset_vtbl_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD#offset_vtbl" do + describe "WIN32OLE::Method#offset_vtbl" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File") - @m_file_name = WIN32OLE_METHOD.new(ole_type, "name") + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_method/params_spec.rb b/spec/ruby/library/win32ole/win32ole_method/params_spec.rb index 09fb0eb5ac..0b1b4595a3 100644 --- a/spec/ruby/library/win32ole/win32ole_method/params_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/params_spec.rb @@ -2,12 +2,12 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD#params" do + describe "WIN32OLE::Method#params" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File") - @m_file_name = WIN32OLE_METHOD.new(ole_type, "name") - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") - @m_browse_for_folder = WIN32OLE_METHOD.new(ole_type, "BrowseForFolder") + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + @m_browse_for_folder = WIN32OLE::Method.new(ole_type, "BrowseForFolder") end it "raises ArgumentError if argument is given" do @@ -19,8 +19,8 @@ platform_is :windows do @m_file_name.params.should be_empty end - it "returns 4-element array of WIN32OLE_PARAM for Shell's 'BrowseForFolder' method" do - @m_browse_for_folder.params.all? { |p| p.kind_of? WIN32OLE_PARAM }.should be_true + it "returns 4-element array of WIN32OLE::Param for Shell's 'BrowseForFolder' method" do + @m_browse_for_folder.params.all? { |p| p.kind_of? WIN32OLE::Param }.should be_true @m_browse_for_folder.params.size == 4 end diff --git a/spec/ruby/library/win32ole/win32ole_method/return_type_detail_spec.rb b/spec/ruby/library/win32ole/win32ole_method/return_type_detail_spec.rb index 582a5951d5..c3725bfef2 100644 --- a/spec/ruby/library/win32ole/win32ole_method/return_type_detail_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/return_type_detail_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD#return_type_detail" do + describe "WIN32OLE::Method#return_type_detail" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") - @m_browse_for_folder = WIN32OLE_METHOD.new(ole_type, "BrowseForFolder") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + @m_browse_for_folder = WIN32OLE::Method.new(ole_type, "BrowseForFolder") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_method/return_type_spec.rb b/spec/ruby/library/win32ole/win32ole_method/return_type_spec.rb index dd8add402d..9e5a1eb1df 100644 --- a/spec/ruby/library/win32ole/win32ole_method/return_type_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/return_type_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD#return_type" do + describe "WIN32OLE::Method#return_type" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File") - @m_file_name = WIN32OLE_METHOD.new(ole_type, "name") + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_method/return_vtype_spec.rb b/spec/ruby/library/win32ole/win32ole_method/return_vtype_spec.rb index 3fca3d54ed..34fd135b8c 100644 --- a/spec/ruby/library/win32ole/win32ole_method/return_vtype_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/return_vtype_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD#return_vtype" do + describe "WIN32OLE::Method#return_vtype" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") - @m_browse_for_folder = WIN32OLE_METHOD.new(ole_type, "BrowseForFolder") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + @m_browse_for_folder = WIN32OLE::Method.new(ole_type, "BrowseForFolder") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_method/shared/name.rb b/spec/ruby/library/win32ole/win32ole_method/shared/name.rb index ddaff4011b..7e2197ca5a 100644 --- a/spec/ruby/library/win32ole/win32ole_method/shared/name.rb +++ b/spec/ruby/library/win32ole/win32ole_method/shared/name.rb @@ -3,8 +3,8 @@ platform_is :windows do describe :win32ole_method_name, shared: true do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "File") - @m_file_name = WIN32OLE_METHOD.new(ole_type, "name") + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "File") + @m_file_name = WIN32OLE::Method.new(ole_type, "name") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_method/size_opt_params_spec.rb b/spec/ruby/library/win32ole/win32ole_method/size_opt_params_spec.rb index fe9facb53a..38cb21ccef 100644 --- a/spec/ruby/library/win32ole/win32ole_method/size_opt_params_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/size_opt_params_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD#size_opt_params" do + describe "WIN32OLE::Method#size_opt_params" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") - @m_browse_for_folder = WIN32OLE_METHOD.new(ole_type, "BrowseForFolder") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + @m_browse_for_folder = WIN32OLE::Method.new(ole_type, "BrowseForFolder") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_method/size_params_spec.rb b/spec/ruby/library/win32ole/win32ole_method/size_params_spec.rb index 8ea6e61e7d..5d0a35a0ef 100644 --- a/spec/ruby/library/win32ole/win32ole_method/size_params_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/size_params_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD#size_params" do + describe "WIN32OLE::Method#size_params" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") - @m_browse_for_folder = WIN32OLE_METHOD.new(ole_type, "BrowseForFolder") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + @m_browse_for_folder = WIN32OLE::Method.new(ole_type, "BrowseForFolder") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_method/to_s_spec.rb b/spec/ruby/library/win32ole/win32ole_method/to_s_spec.rb index 11107a77fc..cdcc4525b1 100644 --- a/spec/ruby/library/win32ole/win32ole_method/to_s_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/to_s_spec.rb @@ -4,7 +4,7 @@ require_relative 'shared/name' platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD#name" do + describe "WIN32OLE::Method#name" do it_behaves_like :win32ole_method_name, :to_s end diff --git a/spec/ruby/library/win32ole/win32ole_method/visible_spec.rb b/spec/ruby/library/win32ole/win32ole_method/visible_spec.rb index d1a50523fc..2f02c15c8b 100644 --- a/spec/ruby/library/win32ole/win32ole_method/visible_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_method/visible_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_METHOD#visible?" do + describe "WIN32OLE::Method#visible?" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") - @m_browse_for_folder = WIN32OLE_METHOD.new(ole_type, "BrowseForFolder") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + @m_browse_for_folder = WIN32OLE::Method.new(ole_type, "BrowseForFolder") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_param/default_spec.rb b/spec/ruby/library/win32ole/win32ole_param/default_spec.rb index 44bd3d7fd3..a37b03866d 100644 --- a/spec/ruby/library/win32ole/win32ole_param/default_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_param/default_spec.rb @@ -2,14 +2,14 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_PARAM#default" do + describe "WIN32OLE::Param#default" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") - m_browse_for_folder = WIN32OLE_METHOD.new(ole_type, "BrowseForFolder") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + m_browse_for_folder = WIN32OLE::Method.new(ole_type, "BrowseForFolder") @params = m_browse_for_folder.params - ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject") - m_copyfile = WIN32OLE_METHOD.new(ole_type, "CopyFile") + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "FileSystemObject") + m_copyfile = WIN32OLE::Method.new(ole_type, "CopyFile") @param_overwritefiles = m_copyfile.params[2] end @@ -17,7 +17,7 @@ platform_is :windows do -> { @params[0].default(1) }.should raise_error ArgumentError end - it "returns nil for each of WIN32OLE_PARAM for Shell's 'BrowseForFolder' method" do + it "returns nil for each of WIN32OLE::Param for Shell's 'BrowseForFolder' method" do @params.each do |p| p.default.should be_nil end diff --git a/spec/ruby/library/win32ole/win32ole_param/input_spec.rb b/spec/ruby/library/win32ole/win32ole_param/input_spec.rb index e9134b1df8..d7e27d7739 100644 --- a/spec/ruby/library/win32ole/win32ole_param/input_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_param/input_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_PARAM#input?" do + describe "WIN32OLE::Param#input?" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject") - m_copyfile = WIN32OLE_METHOD.new(ole_type, "CopyFile") + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "FileSystemObject") + m_copyfile = WIN32OLE::Method.new(ole_type, "CopyFile") @param_overwritefiles = m_copyfile.params[2] end diff --git a/spec/ruby/library/win32ole/win32ole_param/name_spec.rb b/spec/ruby/library/win32ole/win32ole_param/name_spec.rb index 67a8955ba4..2c3474ffb3 100644 --- a/spec/ruby/library/win32ole/win32ole_param/name_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_param/name_spec.rb @@ -4,7 +4,7 @@ require_relative 'shared/name' platform_is :windows do require 'win32ole' - describe "WIN32OLE_PARAM#name" do + describe "WIN32OLE::Param#name" do it_behaves_like :win32ole_param_name, :name end diff --git a/spec/ruby/library/win32ole/win32ole_param/ole_type_detail_spec.rb b/spec/ruby/library/win32ole/win32ole_param/ole_type_detail_spec.rb index f05455e3f1..e3379dbf3e 100644 --- a/spec/ruby/library/win32ole/win32ole_param/ole_type_detail_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_param/ole_type_detail_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_PARAM#ole_type_detail" do + describe "WIN32OLE::Param#ole_type_detail" do before :each do - ole_type_detail = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject") - m_copyfile = WIN32OLE_METHOD.new(ole_type_detail, "CopyFile") + ole_type_detail = WIN32OLE::Type.new("Microsoft Scripting Runtime", "FileSystemObject") + m_copyfile = WIN32OLE::Method.new(ole_type_detail, "CopyFile") @param_overwritefiles = m_copyfile.params[2] end diff --git a/spec/ruby/library/win32ole/win32ole_param/ole_type_spec.rb b/spec/ruby/library/win32ole/win32ole_param/ole_type_spec.rb index 1467130e03..a7b6666807 100644 --- a/spec/ruby/library/win32ole/win32ole_param/ole_type_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_param/ole_type_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_PARAM#ole_type" do + describe "WIN32OLE::Param#ole_type" do before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject") - m_copyfile = WIN32OLE_METHOD.new(ole_type, "CopyFile") + ole_type = WIN32OLE::Type.new("Microsoft Scripting Runtime", "FileSystemObject") + m_copyfile = WIN32OLE::Method.new(ole_type, "CopyFile") @param_overwritefiles = m_copyfile.params[2] end diff --git a/spec/ruby/library/win32ole/win32ole_param/optional_spec.rb b/spec/ruby/library/win32ole/win32ole_param/optional_spec.rb index b39ee41179..50e95fc77f 100644 --- a/spec/ruby/library/win32ole/win32ole_param/optional_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_param/optional_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_PARAM#optional?" do + describe "WIN32OLE::Param#optional?" do before :each do - ole_type_detail = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject") - m_copyfile = WIN32OLE_METHOD.new(ole_type_detail, "CopyFile") + ole_type_detail = WIN32OLE::Type.new("Microsoft Scripting Runtime", "FileSystemObject") + m_copyfile = WIN32OLE::Method.new(ole_type_detail, "CopyFile") @param_overwritefiles = m_copyfile.params[2] end diff --git a/spec/ruby/library/win32ole/win32ole_param/retval_spec.rb b/spec/ruby/library/win32ole/win32ole_param/retval_spec.rb index dd613dd29a..fa4a09ea0c 100644 --- a/spec/ruby/library/win32ole/win32ole_param/retval_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_param/retval_spec.rb @@ -2,10 +2,10 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_PARAM#retval?" do + describe "WIN32OLE::Param#retval?" do before :each do - ole_type_detail = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject") - m_copyfile = WIN32OLE_METHOD.new(ole_type_detail, "CopyFile") + ole_type_detail = WIN32OLE::Type.new("Microsoft Scripting Runtime", "FileSystemObject") + m_copyfile = WIN32OLE::Method.new(ole_type_detail, "CopyFile") @param_overwritefiles = m_copyfile.params[2] end diff --git a/spec/ruby/library/win32ole/win32ole_param/shared/name.rb b/spec/ruby/library/win32ole/win32ole_param/shared/name.rb index 043bc32856..56ff24ddc8 100644 --- a/spec/ruby/library/win32ole/win32ole_param/shared/name.rb +++ b/spec/ruby/library/win32ole/win32ole_param/shared/name.rb @@ -3,8 +3,8 @@ platform_is :windows do describe :win32ole_param_name, shared: true do before :each do - ole_type_detail = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject") - m_copyfile = WIN32OLE_METHOD.new(ole_type_detail, "CopyFile") + ole_type_detail = WIN32OLE::Type.new("Microsoft Scripting Runtime", "FileSystemObject") + m_copyfile = WIN32OLE::Method.new(ole_type_detail, "CopyFile") @param_overwritefiles = m_copyfile.params[2] end diff --git a/spec/ruby/library/win32ole/win32ole_param/to_s_spec.rb b/spec/ruby/library/win32ole/win32ole_param/to_s_spec.rb index e9153a2eb2..c59f426692 100644 --- a/spec/ruby/library/win32ole/win32ole_param/to_s_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_param/to_s_spec.rb @@ -4,7 +4,7 @@ require_relative 'shared/name' platform_is :windows do require 'win32ole' - describe "WIN32OLE_PARAM#to_s" do + describe "WIN32OLE::Param#to_s" do it_behaves_like :win32ole_param_name, :to_s end diff --git a/spec/ruby/library/win32ole/win32ole_type/guid_spec.rb b/spec/ruby/library/win32ole/win32ole_type/guid_spec.rb index abdf8d34b9..e574a945ad 100644 --- a/spec/ruby/library/win32ole/win32ole_type/guid_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/guid_spec.rb @@ -2,9 +2,9 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE#guid for Shell Controls" do + describe "WIN32OLE::Type#guid for Shell Controls" do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") end after :each do diff --git a/spec/ruby/library/win32ole/win32ole_type/helpcontext_spec.rb b/spec/ruby/library/win32ole/win32ole_type/helpcontext_spec.rb index eee23abc56..35911fec52 100644 --- a/spec/ruby/library/win32ole/win32ole_type/helpcontext_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/helpcontext_spec.rb @@ -2,9 +2,9 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE#helpcontext for Shell Controls" do + describe "WIN32OLE::Type#helpcontext for Shell Controls" do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") end after :each do diff --git a/spec/ruby/library/win32ole/win32ole_type/helpfile_spec.rb b/spec/ruby/library/win32ole/win32ole_type/helpfile_spec.rb index 3a0a9ead94..7bd61a1c40 100644 --- a/spec/ruby/library/win32ole/win32ole_type/helpfile_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/helpfile_spec.rb @@ -2,9 +2,9 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE#helpfile for Shell Controls" do + describe "WIN32OLE::Type#helpfile for Shell Controls" do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") end after :each do diff --git a/spec/ruby/library/win32ole/win32ole_type/helpstring_spec.rb b/spec/ruby/library/win32ole/win32ole_type/helpstring_spec.rb index 9ab0004668..940475b25e 100644 --- a/spec/ruby/library/win32ole/win32ole_type/helpstring_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/helpstring_spec.rb @@ -2,9 +2,9 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE#helpstring for Shell Controls" do + describe "WIN32OLE::Type#helpstring for Shell Controls" do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") end after :each do diff --git a/spec/ruby/library/win32ole/win32ole_type/major_version_spec.rb b/spec/ruby/library/win32ole/win32ole_type/major_version_spec.rb index 7d2731f778..598e5bcef8 100644 --- a/spec/ruby/library/win32ole/win32ole_type/major_version_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/major_version_spec.rb @@ -2,9 +2,9 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE#major_version for Shell Controls" do + describe "WIN32OLE::Type#major_version for Shell Controls" do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") end after :each do diff --git a/spec/ruby/library/win32ole/win32ole_type/minor_version_spec.rb b/spec/ruby/library/win32ole/win32ole_type/minor_version_spec.rb index 3904e78d42..59cfb94012 100644 --- a/spec/ruby/library/win32ole/win32ole_type/minor_version_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/minor_version_spec.rb @@ -2,9 +2,9 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE#minor_version for Shell Controls" do + describe "WIN32OLE::Type#minor_version for Shell Controls" do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") end after :each do diff --git a/spec/ruby/library/win32ole/win32ole_type/name_spec.rb b/spec/ruby/library/win32ole/win32ole_type/name_spec.rb index d76998d7dc..4cc3426872 100644 --- a/spec/ruby/library/win32ole/win32ole_type/name_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/name_spec.rb @@ -4,7 +4,7 @@ require_relative 'shared/name' platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE#name" do + describe "WIN32OLE::Type#name" do it_behaves_like :win32ole_type_name, :name end diff --git a/spec/ruby/library/win32ole/win32ole_type/new_spec.rb b/spec/ruby/library/win32ole/win32ole_type/new_spec.rb index cc691ffa67..185a235940 100644 --- a/spec/ruby/library/win32ole/win32ole_type/new_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/new_spec.rb @@ -2,39 +2,39 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE.new" do + describe "WIN32OLE::Type.new" do it "raises ArgumentError with no argument" do - -> { WIN32OLE_TYPE.new }.should raise_error ArgumentError + -> { WIN32OLE::Type.new }.should raise_error ArgumentError end it "raises ArgumentError with invalid string" do - -> { WIN32OLE_TYPE.new("foo") }.should raise_error ArgumentError + -> { WIN32OLE::Type.new("foo") }.should raise_error ArgumentError end it "raises TypeError if second argument is not a String" do - -> { WIN32OLE_TYPE.new(1,2) }.should raise_error TypeError + -> { WIN32OLE::Type.new(1,2) }.should raise_error TypeError -> { - WIN32OLE_TYPE.new('Microsoft Shell Controls And Automation',2) + WIN32OLE::Type.new('Microsoft Shell Controls And Automation',2) }.should raise_error TypeError end - it "raise WIN32OLERuntimeError if OLE object specified is not found" do + it "raise WIN32OLE::RuntimeError if OLE object specified is not found" do -> { - WIN32OLE_TYPE.new('Microsoft Shell Controls And Automation','foo') - }.should raise_error WIN32OLERuntimeError + WIN32OLE::Type.new('Microsoft Shell Controls And Automation','foo') + }.should raise_error WIN32OLE::RuntimeError -> { - WIN32OLE_TYPE.new('Microsoft Shell Controls And Automation','Application') - }.should raise_error WIN32OLERuntimeError + WIN32OLE::Type.new('Microsoft Shell Controls And Automation','Application') + }.should raise_error WIN32OLE::RuntimeError end - it "creates WIN32OLE_TYPE object from name and valid type" do - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") - ole_type.should be_kind_of WIN32OLE_TYPE + it "creates WIN32OLE::Type object from name and valid type" do + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") + ole_type.should be_kind_of WIN32OLE::Type end - it "creates WIN32OLE_TYPE object from CLSID and valid type" do - ole_type2 = WIN32OLE_TYPE.new("{13709620-C279-11CE-A49E-444553540000}", "Shell") - ole_type2.should be_kind_of WIN32OLE_TYPE + it "creates WIN32OLE::Type object from CLSID and valid type" do + ole_type2 = WIN32OLE::Type.new("{13709620-C279-11CE-A49E-444553540000}", "Shell") + ole_type2.should be_kind_of WIN32OLE::Type end end diff --git a/spec/ruby/library/win32ole/win32ole_type/ole_classes_spec.rb b/spec/ruby/library/win32ole/win32ole_type/ole_classes_spec.rb index a3a1d4ac58..ed14e37a95 100644 --- a/spec/ruby/library/win32ole/win32ole_type/ole_classes_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/ole_classes_spec.rb @@ -2,9 +2,9 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE.ole_classes for Shell Controls" do + describe "WIN32OLE::Type.ole_classes for Shell Controls" do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") end after :each do @@ -12,7 +12,7 @@ platform_is :windows do end it "returns array of WIN32OLE_TYPEs" do - WIN32OLE_TYPE.ole_classes("Microsoft Shell Controls And Automation").all? {|e| e.kind_of? WIN32OLE_TYPE }.should be_true + WIN32OLE::Type.ole_classes("Microsoft Shell Controls And Automation").all? {|e| e.kind_of? WIN32OLE::Type }.should be_true end end diff --git a/spec/ruby/library/win32ole/win32ole_type/ole_methods_spec.rb b/spec/ruby/library/win32ole/win32ole_type/ole_methods_spec.rb index 3b99b97a61..0c031abaa6 100644 --- a/spec/ruby/library/win32ole/win32ole_type/ole_methods_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/ole_methods_spec.rb @@ -2,9 +2,9 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE#ole_methods for Shell Controls" do + describe "WIN32OLE::Type#ole_methods for Shell Controls" do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") end after :each do @@ -12,7 +12,7 @@ platform_is :windows do end it "returns an Integer" do - @ole_type.ole_methods.all? { |m| m.kind_of? WIN32OLE_METHOD }.should be_true + @ole_type.ole_methods.all? { |m| m.kind_of? WIN32OLE::Method }.should be_true end end diff --git a/spec/ruby/library/win32ole/win32ole_type/ole_type_spec.rb b/spec/ruby/library/win32ole/win32ole_type/ole_type_spec.rb index 24292b1c4f..49c1902f8c 100644 --- a/spec/ruby/library/win32ole/win32ole_type/ole_type_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/ole_type_spec.rb @@ -2,9 +2,9 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE#ole_type for Shell Controls" do + describe "WIN32OLE::Type#ole_type for Shell Controls" do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") end after :each do diff --git a/spec/ruby/library/win32ole/win32ole_type/progid_spec.rb b/spec/ruby/library/win32ole/win32ole_type/progid_spec.rb index 340fdb34e8..9a700426d9 100644 --- a/spec/ruby/library/win32ole/win32ole_type/progid_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/progid_spec.rb @@ -2,9 +2,9 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE#progid for Shell Controls" do + describe "WIN32OLE::Type#progid for Shell Controls" do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") end after :each do diff --git a/spec/ruby/library/win32ole/win32ole_type/progids_spec.rb b/spec/ruby/library/win32ole/win32ole_type/progids_spec.rb index 793535b48d..b1b57960cd 100644 --- a/spec/ruby/library/win32ole/win32ole_type/progids_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/progids_spec.rb @@ -2,13 +2,13 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE.progids" do + describe "WIN32OLE::Type.progids" do it "raises ArgumentError if an argument is given" do - -> { WIN32OLE_TYPE.progids(1) }.should raise_error ArgumentError + -> { WIN32OLE::Type.progids(1) }.should raise_error ArgumentError end it "returns an array containing 'Shell.Explorer'" do - WIN32OLE_TYPE.progids().include?('Shell.Explorer').should be_true + WIN32OLE::Type.progids().include?('Shell.Explorer').should be_true end end diff --git a/spec/ruby/library/win32ole/win32ole_type/shared/name.rb b/spec/ruby/library/win32ole/win32ole_type/shared/name.rb index 6f37446b23..efae7aeec1 100644 --- a/spec/ruby/library/win32ole/win32ole_type/shared/name.rb +++ b/spec/ruby/library/win32ole/win32ole_type/shared/name.rb @@ -3,7 +3,7 @@ platform_is :windows do describe :win32ole_type_name, shared: true do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") end it "raises ArgumentError if argument is given" do diff --git a/spec/ruby/library/win32ole/win32ole_type/src_type_spec.rb b/spec/ruby/library/win32ole/win32ole_type/src_type_spec.rb index 3f89fe702a..3c7651cc1f 100644 --- a/spec/ruby/library/win32ole/win32ole_type/src_type_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/src_type_spec.rb @@ -2,9 +2,9 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE#src_type for Shell Controls" do + describe "WIN32OLE::Type#src_type for Shell Controls" do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") end after :each do diff --git a/spec/ruby/library/win32ole/win32ole_type/to_s_spec.rb b/spec/ruby/library/win32ole/win32ole_type/to_s_spec.rb index 9f086a5a35..03a0344fdb 100644 --- a/spec/ruby/library/win32ole/win32ole_type/to_s_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/to_s_spec.rb @@ -4,7 +4,7 @@ require_relative 'shared/name' platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE#to_s" do + describe "WIN32OLE::Type#to_s" do it_behaves_like :win32ole_type_name, :to_s end diff --git a/spec/ruby/library/win32ole/win32ole_type/typekind_spec.rb b/spec/ruby/library/win32ole/win32ole_type/typekind_spec.rb index 391d505e01..8b62f3e2eb 100644 --- a/spec/ruby/library/win32ole/win32ole_type/typekind_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/typekind_spec.rb @@ -2,9 +2,9 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE#typekind for Shell Controls" do + describe "WIN32OLE::Type#typekind for Shell Controls" do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") end after :each do diff --git a/spec/ruby/library/win32ole/win32ole_type/typelibs_spec.rb b/spec/ruby/library/win32ole/win32ole_type/typelibs_spec.rb index a487208caa..71d7cf00f7 100644 --- a/spec/ruby/library/win32ole/win32ole_type/typelibs_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/typelibs_spec.rb @@ -2,9 +2,9 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE.typelibs for Shell Controls" do + describe "WIN32OLE::Type.typelibs for Shell Controls" do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") end after :each do @@ -12,11 +12,11 @@ platform_is :windows do end it "raises ArgumentError if any argument is give" do - -> { WIN32OLE_TYPE.typelibs(1) }.should raise_error ArgumentError + -> { WIN32OLE::Type.typelibs(1) }.should raise_error ArgumentError end it "returns array of type libraries" do - WIN32OLE_TYPE.typelibs().include?("Microsoft Shell Controls And Automation").should be_true + WIN32OLE::Type.typelibs().include?("Microsoft Shell Controls And Automation").should be_true end end diff --git a/spec/ruby/library/win32ole/win32ole_type/variables_spec.rb b/spec/ruby/library/win32ole/win32ole_type/variables_spec.rb index 7f61b8af95..b1a407523c 100644 --- a/spec/ruby/library/win32ole/win32ole_type/variables_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/variables_spec.rb @@ -2,9 +2,9 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE#variables for Shell Controls" do + describe "WIN32OLE::Type#variables for Shell Controls" do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") end after :each do diff --git a/spec/ruby/library/win32ole/win32ole_type/visible_spec.rb b/spec/ruby/library/win32ole/win32ole_type/visible_spec.rb index 99e34edcdd..05c54c8838 100644 --- a/spec/ruby/library/win32ole/win32ole_type/visible_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_type/visible_spec.rb @@ -2,9 +2,9 @@ require_relative "../../../spec_helper" platform_is :windows do require 'win32ole' - describe "WIN32OLE_TYPE#visible? for Shell Controls" do + describe "WIN32OLE::Type#visible? for Shell Controls" do before :each do - @ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "Shell") + @ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "Shell") end after :each do diff --git a/spec/ruby/library/win32ole/win32ole_variable/ole_type_detail_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/ole_type_detail_spec.rb index 7a9c791494..89576ceedc 100644 --- a/spec/ruby/library/win32ole/win32ole_variable/ole_type_detail_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_variable/ole_type_detail_spec.rb @@ -6,7 +6,7 @@ platform_is :windows do # not sure how WIN32OLE_VARIABLE objects are supposed to be generated # WIN32OLE_VARIABLE.new even seg faults in some cases before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") @var = ole_type.variables[0] end diff --git a/spec/ruby/library/win32ole/win32ole_variable/ole_type_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/ole_type_spec.rb index 03a9aa4c74..441011f1e7 100644 --- a/spec/ruby/library/win32ole/win32ole_variable/ole_type_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_variable/ole_type_spec.rb @@ -6,7 +6,7 @@ platform_is :windows do # not sure how WIN32OLE_VARIABLE objects are supposed to be generated # WIN32OLE_VARIABLE.new even seg faults in some cases before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") @var = ole_type.variables[0] end diff --git a/spec/ruby/library/win32ole/win32ole_variable/shared/name.rb b/spec/ruby/library/win32ole/win32ole_variable/shared/name.rb index 033e830fac..d02942ce0a 100644 --- a/spec/ruby/library/win32ole/win32ole_variable/shared/name.rb +++ b/spec/ruby/library/win32ole/win32ole_variable/shared/name.rb @@ -5,7 +5,7 @@ platform_is :windows do # not sure how WIN32OLE_VARIABLE objects are supposed to be generated # WIN32OLE_VARIABLE.new even seg faults in some cases before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") @var = ole_type.variables[0] end diff --git a/spec/ruby/library/win32ole/win32ole_variable/value_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/value_spec.rb index b7849793c5..d26273ebed 100644 --- a/spec/ruby/library/win32ole/win32ole_variable/value_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_variable/value_spec.rb @@ -6,7 +6,7 @@ platform_is :windows do # not sure how WIN32OLE_VARIABLE objects are supposed to be generated # WIN32OLE_VARIABLE.new even seg faults in some cases before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") @var = ole_type.variables[0] end diff --git a/spec/ruby/library/win32ole/win32ole_variable/variable_kind_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/variable_kind_spec.rb index 7a79d32ddc..17bc47160a 100644 --- a/spec/ruby/library/win32ole/win32ole_variable/variable_kind_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_variable/variable_kind_spec.rb @@ -6,7 +6,7 @@ platform_is :windows do # not sure how WIN32OLE_VARIABLE objects are supposed to be generated # WIN32OLE_VARIABLE.new even seg faults in some cases before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") @var = ole_type.variables[0] end diff --git a/spec/ruby/library/win32ole/win32ole_variable/varkind_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/varkind_spec.rb index 9d7b8238c8..c5f8164509 100644 --- a/spec/ruby/library/win32ole/win32ole_variable/varkind_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_variable/varkind_spec.rb @@ -7,7 +7,7 @@ platform_is :windows do # not sure how WIN32OLE_VARIABLE objects are supposed to be generated # WIN32OLE_VARIABLE.new even seg faults in some cases before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") @var = ole_type.variables[0] end diff --git a/spec/ruby/library/win32ole/win32ole_variable/visible_spec.rb b/spec/ruby/library/win32ole/win32ole_variable/visible_spec.rb index 60252e8139..ba53a81de0 100644 --- a/spec/ruby/library/win32ole/win32ole_variable/visible_spec.rb +++ b/spec/ruby/library/win32ole/win32ole_variable/visible_spec.rb @@ -6,7 +6,7 @@ platform_is :windows do # not sure how WIN32OLE_VARIABLE objects are supposed to be generated # WIN32OLE_VARIABLE.new even seg faults in some cases before :each do - ole_type = WIN32OLE_TYPE.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") + ole_type = WIN32OLE::Type.new("Microsoft Shell Controls And Automation", "ShellSpecialFolderConstants") @var = ole_type.variables[0] end diff --git a/spec/ruby/library/zlib/inflate/set_dictionary_spec.rb b/spec/ruby/library/zlib/inflate/set_dictionary_spec.rb index 251cec44bb..375ee3c765 100644 --- a/spec/ruby/library/zlib/inflate/set_dictionary_spec.rb +++ b/spec/ruby/library/zlib/inflate/set_dictionary_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require 'zlib' diff --git a/spec/ruby/optional/capi/digest_spec.rb b/spec/ruby/optional/capi/digest_spec.rb index c753733906..65c5ecebb1 100644 --- a/spec/ruby/optional/capi/digest_spec.rb +++ b/spec/ruby/optional/capi/digest_spec.rb @@ -1,6 +1,10 @@ require_relative 'spec_helper' -require 'fiddle' +begin + require 'fiddle' +rescue LoadError + return +end load_extension('digest') diff --git a/spec/ruby/optional/capi/exception_spec.rb b/spec/ruby/optional/capi/exception_spec.rb index 5bb60608b2..5bc8e26c62 100644 --- a/spec/ruby/optional/capi/exception_spec.rb +++ b/spec/ruby/optional/capi/exception_spec.rb @@ -100,6 +100,26 @@ describe "C-API Exception function" do end end + describe "rb_error_frozen_object" do + it "raises a FrozenError regardless of the object's frozen state" do + # The type of the argument we supply doesn't matter. The choice here is arbitrary and we only change the type + # of the argument to ensure the exception messages are set correctly. + -> { @s.rb_error_frozen_object(Array.new) }.should raise_error(FrozenError, "can't modify frozen Array: []") + -> { @s.rb_error_frozen_object(Array.new.freeze) }.should raise_error(FrozenError, "can't modify frozen Array: []") + end + + it "properly handles recursive rb_error_frozen_object calls" do + klass = Class.new(Object) + object = klass.new + s = @s + klass.define_method :inspect do + s.rb_error_frozen_object(object) + end + + -> { @s.rb_error_frozen_object(object) }.should raise_error(FrozenError, "can't modify frozen #{klass}: ...") + end + end + describe "rb_syserr_new" do it "returns system error with default message when passed message is NULL" do exception = @s.rb_syserr_new(Errno::ENOENT::Errno, nil) diff --git a/spec/ruby/optional/capi/ext/exception_spec.c b/spec/ruby/optional/capi/ext/exception_spec.c index 0e8347ab0d..c3b94d7bcd 100644 --- a/spec/ruby/optional/capi/ext/exception_spec.c +++ b/spec/ruby/optional/capi/ext/exception_spec.c @@ -36,6 +36,13 @@ VALUE exception_spec_rb_set_errinfo(VALUE self, VALUE exc) { return Qnil; } +NORETURN(VALUE exception_spec_rb_error_frozen_object(VALUE self, VALUE object)); + +VALUE exception_spec_rb_error_frozen_object(VALUE self, VALUE object) { + rb_error_frozen_object(object); + UNREACHABLE_RETURN(Qnil); +} + VALUE exception_spec_rb_syserr_new(VALUE self, VALUE num, VALUE msg) { int n = NUM2INT(num); char *cstr = NULL; @@ -66,6 +73,7 @@ void Init_exception_spec(void) { rb_define_method(cls, "rb_exc_new3", exception_spec_rb_exc_new3, 1); rb_define_method(cls, "rb_exc_raise", exception_spec_rb_exc_raise, 1); rb_define_method(cls, "rb_set_errinfo", exception_spec_rb_set_errinfo, 1); + rb_define_method(cls, "rb_error_frozen_object", exception_spec_rb_error_frozen_object, 1); rb_define_method(cls, "rb_syserr_new", exception_spec_rb_syserr_new, 2); rb_define_method(cls, "rb_syserr_new_str", exception_spec_rb_syserr_new_str, 2); rb_define_method(cls, "rb_make_exception", exception_spec_rb_make_exception, 1); diff --git a/spec/ruby/optional/capi/ext/object_spec.c b/spec/ruby/optional/capi/ext/object_spec.c index eab0eb7534..995bc38fcf 100644 --- a/spec/ruby/optional/capi/ext/object_spec.c +++ b/spec/ruby/optional/capi/ext/object_spec.c @@ -383,6 +383,16 @@ static VALUE object_spec_custom_alloc_func_p(VALUE self, VALUE klass) { return allocator ? Qtrue : Qfalse; } +static VALUE object_spec_redefine_frozen(VALUE self) { + // The purpose of this spec is to verify that `frozen?` + // and `RB_OBJ_FROZEN` do not mutually recurse infinitely. + if (RB_OBJ_FROZEN(self)) { + return Qtrue; + } + + return Qfalse; +} + void Init_object_spec(void) { VALUE cls = rb_define_class("CApiObjectSpecs", rb_cObject); rb_define_method(cls, "FL_ABLE", object_spec_FL_ABLE, 1); @@ -455,6 +465,9 @@ void Init_object_spec(void) { rb_define_method(cls, "custom_alloc_func?", object_spec_custom_alloc_func_p, 1); rb_define_method(cls, "not_implemented_method", rb_f_notimplement, -1); rb_define_method(cls, "rb_ivar_foreach", object_spec_rb_ivar_foreach, 1); + + cls = rb_define_class("CApiObjectRedefinitionSpecs", rb_cObject); + rb_define_method(cls, "frozen?", object_spec_redefine_frozen, 0); } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/ext/set_spec.c b/spec/ruby/optional/capi/ext/set_spec.c new file mode 100644 index 0000000000..6535a11be3 --- /dev/null +++ b/spec/ruby/optional/capi/ext/set_spec.c @@ -0,0 +1,62 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define RBOOL(x) ((x) ? Qtrue : Qfalse) + +int yield_element_and_arg(VALUE element, VALUE arg) { + return RTEST(rb_yield_values(2, element, arg)) ? ST_CONTINUE : ST_STOP; +} + +VALUE set_spec_rb_set_foreach(VALUE self, VALUE set, VALUE arg) { + rb_set_foreach(set, yield_element_and_arg, arg); + return Qnil; +} + +VALUE set_spec_rb_set_new(VALUE self) { + return rb_set_new(); +} + +VALUE set_spec_rb_set_new_capa(VALUE self, VALUE capa) { + return rb_set_new_capa(NUM2INT(capa)); +} + +VALUE set_spec_rb_set_lookup(VALUE self, VALUE set, VALUE element) { + return RBOOL(rb_set_lookup(set, element)); +} + +VALUE set_spec_rb_set_add(VALUE self, VALUE set, VALUE element) { + return RBOOL(rb_set_add(set, element)); +} + +VALUE set_spec_rb_set_clear(VALUE self, VALUE set) { + return rb_set_clear(set); +} + +VALUE set_spec_rb_set_delete(VALUE self, VALUE set, VALUE element) { + return RBOOL(rb_set_delete(set, element)); +} + +VALUE set_spec_rb_set_size(VALUE self, VALUE set) { + return SIZET2NUM(rb_set_size(set)); +} + +void Init_set_spec(void) { + VALUE cls = rb_define_class("CApiSetSpecs", rb_cObject); + rb_define_method(cls, "rb_set_foreach", set_spec_rb_set_foreach, 2); + rb_define_method(cls, "rb_set_new", set_spec_rb_set_new, 0); + rb_define_method(cls, "rb_set_new_capa", set_spec_rb_set_new_capa, 1); + rb_define_method(cls, "rb_set_lookup", set_spec_rb_set_lookup, 2); + rb_define_method(cls, "rb_set_add", set_spec_rb_set_add, 2); + rb_define_method(cls, "rb_set_clear", set_spec_rb_set_clear, 1); + rb_define_method(cls, "rb_set_delete", set_spec_rb_set_delete, 2); + rb_define_method(cls, "rb_set_size", set_spec_rb_set_size, 1); +} + +#ifdef __cplusplus +} +#endif + diff --git a/spec/ruby/optional/capi/ext/string_spec.c b/spec/ruby/optional/capi/ext/string_spec.c index b49bb3f267..094013e049 100644 --- a/spec/ruby/optional/capi/ext/string_spec.c +++ b/spec/ruby/optional/capi/ext/string_spec.c @@ -440,6 +440,7 @@ static VALUE string_spec_rb_str_free(VALUE self, VALUE str) { static VALUE string_spec_rb_sprintf1(VALUE self, VALUE str, VALUE repl) { return rb_sprintf(RSTRING_PTR(str), RSTRING_PTR(repl)); } + static VALUE string_spec_rb_sprintf2(VALUE self, VALUE str, VALUE repl1, VALUE repl2) { return rb_sprintf(RSTRING_PTR(str), RSTRING_PTR(repl1), RSTRING_PTR(repl2)); } diff --git a/spec/ruby/optional/capi/integer_spec.rb b/spec/ruby/optional/capi/integer_spec.rb index 089872381c..f177374569 100644 --- a/spec/ruby/optional/capi/integer_spec.rb +++ b/spec/ruby/optional/capi/integer_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative 'spec_helper' load_extension("integer") diff --git a/spec/ruby/optional/capi/object_spec.rb b/spec/ruby/optional/capi/object_spec.rb index 27faecbb49..8b4d8a9bba 100644 --- a/spec/ruby/optional/capi/object_spec.rb +++ b/spec/ruby/optional/capi/object_spec.rb @@ -691,6 +691,16 @@ describe "CApiObject" do end end + describe "redefining frozen? works" do + it "allows an object to override frozen?" do + obj = CApiObjectRedefinitionSpecs.new + + obj.frozen?.should == false + obj.freeze + obj.frozen?.should == true + end + end + describe "rb_obj_taint" do end diff --git a/spec/ruby/optional/capi/set_spec.rb b/spec/ruby/optional/capi/set_spec.rb new file mode 100644 index 0000000000..3433014ccd --- /dev/null +++ b/spec/ruby/optional/capi/set_spec.rb @@ -0,0 +1,96 @@ +require_relative 'spec_helper' + +load_extension("set") + +describe "C-API Set function" do + before :each do + @s = CApiSetSpecs.new + end + + ruby_version_is "3.5" do + describe "rb_set_foreach" do + it "calls function with each element and arg" do + a = [] + @s.rb_set_foreach(Set[1, 2], 3) {|*args| a.concat(args) } + a.should == [1, 3, 2, 3] + end + + it "respects function return value" do + a = [] + @s.rb_set_foreach(Set[1, 2], 3) do |*args| + a.concat(args) + false + end + a.should == [1, 3] + end + end + + describe "rb_set_new" do + it "returns a new set" do + @s.rb_set_new.should == Set[] + end + end + + describe "rb_set_new_capa" do + it "returns a new set" do + @s.rb_set_new_capa(3).should == Set[] + end + end + + describe "rb_set_lookup" do + it "returns whether the element is in the set" do + set = Set[1] + @s.rb_set_lookup(set, 1).should == true + @s.rb_set_lookup(set, 2).should == false + end + end + + describe "rb_set_add" do + it "adds element to set" do + set = Set[] + @s.rb_set_add(set, 1).should == true + set.should == Set[1] + @s.rb_set_add(set, 2).should == true + set.should == Set[1, 2] + end + + it "returns false if element is already in set" do + set = Set[1] + @s.rb_set_add(set, 1).should == false + set.should == Set[1] + end + end + + describe "rb_set_clear" do + it "empties and returns self" do + set = Set[1] + @s.rb_set_clear(set).should equal(set) + set.should == Set[] + end + end + + describe "rb_set_delete" do + it "removes element from set" do + set = Set[1, 2] + @s.rb_set_delete(set, 1).should == true + set.should == Set[2] + @s.rb_set_delete(set, 2).should == true + set.should == Set[] + end + + it "returns false if element is not already in set" do + set = Set[2] + @s.rb_set_delete(set, 1).should == false + set.should == Set[2] + end + end + + describe "rb_set_size" do + it "returns number of elements in set" do + @s.rb_set_size(Set[]).should == 0 + @s.rb_set_size(Set[1]).should == 1 + @s.rb_set_size(Set[1,2]).should == 2 + end + end + end +end diff --git a/spec/ruby/optional/capi/string_spec.rb b/spec/ruby/optional/capi/string_spec.rb index 715f76eaea..be9cb9015f 100644 --- a/spec/ruby/optional/capi/string_spec.rb +++ b/spec/ruby/optional/capi/string_spec.rb @@ -1045,6 +1045,16 @@ describe "C-API String function" do @s.rb_sprintf4(true.class).should == s end + it "formats nil using to_s if sign not specified in format" do + s = 'Result: .' + @s.rb_sprintf3(nil).should == s + end + + it "formats nil using inspect if sign specified in format" do + s = 'Result: nil.' + @s.rb_sprintf4(nil).should == s + end + it "truncates a string to a supplied precision if that is shorter than the string" do s = 'Result: Hel.' @s.rb_sprintf5(0, 3, "Hello").should == s @@ -1201,28 +1211,50 @@ describe "C-API String function" do describe "rb_str_locktmp" do it "raises an error when trying to lock an already locked string" do - str = "test" + str = +"test" @s.rb_str_locktmp(str).should == str -> { @s.rb_str_locktmp(str) }.should raise_error(RuntimeError, 'temporal locking already locked string') end it "locks a string so that modifications would raise an error" do - str = "test" + str = +"test" @s.rb_str_locktmp(str).should == str -> { str.upcase! }.should raise_error(RuntimeError, 'can\'t modify string; temporarily locked') end + + ruby_version_is "3.5" do + it "raises FrozenError if string is frozen" do + str = -"rb_str_locktmp" + -> { @s.rb_str_locktmp(str) }.should raise_error(FrozenError) + + str = +"rb_str_locktmp" + str.freeze + -> { @s.rb_str_locktmp(str) }.should raise_error(FrozenError) + end + end end describe "rb_str_unlocktmp" do it "unlocks a locked string" do - str = "test" + str = +"test" @s.rb_str_locktmp(str) @s.rb_str_unlocktmp(str).should == str str.upcase!.should == "TEST" end it "raises an error when trying to unlock an already unlocked string" do - -> { @s.rb_str_unlocktmp("test") }.should raise_error(RuntimeError, 'temporal unlocking already unlocked string') + -> { @s.rb_str_unlocktmp(+"test") }.should raise_error(RuntimeError, 'temporal unlocking already unlocked string') + end + + ruby_version_is "3.5" do + it "raises FrozenError if string is frozen" do + str = -"rb_str_locktmp" + -> { @s.rb_str_unlocktmp(str) }.should raise_error(FrozenError) + + str = +"rb_str_locktmp" + str.freeze + -> { @s.rb_str_unlocktmp(str) }.should raise_error(FrozenError) + end end end diff --git a/spec/ruby/shared/io/putc.rb b/spec/ruby/shared/io/putc.rb index e6012c0098..cdf18ac9fd 100644 --- a/spec/ruby/shared/io/putc.rb +++ b/spec/ruby/shared/io/putc.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :io_putc, shared: true do after :each do @io.close if @io && !@io.closed? diff --git a/spec/ruby/shared/kernel/raise.rb b/spec/ruby/shared/kernel/raise.rb index 1917a4c923..3d1c4a10f5 100644 --- a/spec/ruby/shared/kernel/raise.rb +++ b/spec/ruby/shared/kernel/raise.rb @@ -49,21 +49,6 @@ describe :kernel_raise, shared: true do end end - it "does not allow message and extra keyword arguments" do - data_error = Class.new(StandardError) do - attr_reader :data - def initialize(data) - @data = data - end - end - - -> { @object.raise(data_error, {a: 1}, b: 2) }.should raise_error(StandardError) do |e| - [TypeError, ArgumentError].should.include?(e.class) - end - - -> { @object.raise(data_error, {a: 1}, [], b: 2) }.should raise_error(ArgumentError) - end - it "raises RuntimeError if no exception class is given" do -> { @object.raise }.should raise_error(RuntimeError, "") end @@ -74,7 +59,7 @@ describe :kernel_raise, shared: true do end it "raises a RuntimeError if string given" do - -> { @object.raise("a bad thing") }.should raise_error(RuntimeError) + -> { @object.raise("a bad thing") }.should raise_error(RuntimeError, "a bad thing") end it "passes no arguments to the constructor when given only an exception class" do @@ -86,19 +71,36 @@ describe :kernel_raise, shared: true do end it "raises a TypeError when passed a non-Exception object" do - -> { @object.raise(Object.new) }.should raise_error(TypeError) + -> { @object.raise(Object.new) }.should raise_error(TypeError, "exception class/object expected") + -> { @object.raise(Object.new, "message") }.should raise_error(TypeError, "exception class/object expected") + -> { @object.raise(Object.new, "message", []) }.should raise_error(TypeError, "exception class/object expected") end it "raises a TypeError when passed true" do - -> { @object.raise(true) }.should raise_error(TypeError) + -> { @object.raise(true) }.should raise_error(TypeError, "exception class/object expected") end it "raises a TypeError when passed false" do - -> { @object.raise(false) }.should raise_error(TypeError) + -> { @object.raise(false) }.should raise_error(TypeError, "exception class/object expected") end it "raises a TypeError when passed nil" do - -> { @object.raise(nil) }.should raise_error(TypeError) + -> { @object.raise(nil) }.should raise_error(TypeError, "exception class/object expected") + end + + it "raises a TypeError when passed a message and an extra argument" do + -> { @object.raise("message", {cause: RuntimeError.new()}) }.should raise_error(TypeError, "exception class/object expected") + end + + it "raises TypeError when passed a non-Exception object but it responds to #exception method that doesn't return an instance of Exception class" do + e = Object.new + def e.exception + Array + end + + -> { + @object.raise e + }.should raise_error(TypeError, "exception object expected") end it "re-raises a previously rescued exception without overwriting the backtrace" do diff --git a/st.c b/st.c index 9d129ff024..195a16b8ad 100644 --- a/st.c +++ b/st.c @@ -1495,7 +1495,16 @@ st_update(st_table *tab, st_data_t key, value = entry->record; } old_key = key; + + unsigned int rebuilds_num = tab->rebuilds_num; + retval = (*func)(&key, &value, arg, existing); + + // We need to make sure that the callback didn't cause a table rebuild + // Ideally we would make sure no operations happened + assert(rebuilds_num == tab->rebuilds_num); + (void)rebuilds_num; + switch (retval) { case ST_CONTINUE: if (! existing) { @@ -2465,6 +2474,12 @@ set_init_numtable(void) return set_init_table_with_size(NULL, &type_numhash, 0); } +set_table * +set_init_numtable_with_size(st_index_t size) +{ + return set_init_table_with_size(NULL, &type_numhash, size); +} + size_t set_table_size(const struct set_table *tbl) { @@ -2473,7 +2488,7 @@ set_table_size(const struct set_table *tbl) /* Make table TAB empty. */ void -set_clear(set_table *tab) +set_table_clear(set_table *tab) { set_make_tab_empty(tab); tab->rebuilds_num++; @@ -2842,7 +2857,7 @@ set_find_table_bin_ptr_and_reserve(set_table *tab, st_hash_t *hash_value, /* Find an entry with KEY in table TAB. Return non-zero if we found it. */ int -set_lookup(set_table *tab, st_data_t key) +set_table_lookup(set_table *tab, st_data_t key) { st_index_t bin; st_hash_t hash = set_do_hash(key, tab); @@ -2982,7 +2997,7 @@ set_update_range_for_deleted(set_table *tab, st_index_t n) /* Delete entry with KEY from table TAB, and return non-zero. If there is no entry with KEY in the table, return zero. */ int -set_delete(set_table *tab, st_data_t *key) +set_table_delete(set_table *tab, st_data_t *key) { set_table_entry *entry; st_index_t bin; @@ -3140,7 +3155,7 @@ set_apply_functor(st_data_t k, st_data_t d, int _) } int -set_foreach(set_table *tab, set_foreach_callback_func *func, st_data_t arg) +set_table_foreach(set_table *tab, set_foreach_callback_func *func, st_data_t arg) { const struct set_functor f = { func, arg }; return set_general_foreach(tab, set_apply_functor, NULL, (st_data_t)&f, FALSE); diff --git a/string.c b/string.c index b7f46802fc..fefe05073d 100644 --- a/string.c +++ b/string.c @@ -28,6 +28,7 @@ #include "internal/array.h" #include "internal/compar.h" #include "internal/compilers.h" +#include "internal/concurrent_set.h" #include "internal/encoding.h" #include "internal/error.h" #include "internal/gc.h" @@ -356,8 +357,6 @@ mustnot_wchar(VALUE str) } } -static int fstring_cmp(VALUE a, VALUE b); - static VALUE register_fstring(VALUE str, bool copy, bool force_precompute_hash); #if SIZEOF_LONG == SIZEOF_VOIDP @@ -365,35 +364,10 @@ static VALUE register_fstring(VALUE str, bool copy, bool force_precompute_hash); #else #endif -#ifdef PRECOMPUTED_FAKESTR_HASH -static st_index_t -fstring_hash(VALUE str) -{ - st_index_t h; - if (FL_TEST_RAW(str, STR_FAKESTR)) { - // register_fstring precomputes the hash and stores it in capa for fake strings - h = (st_index_t)RSTRING(str)->as.heap.aux.capa; - } - else { - h = rb_str_hash(str); - } - // rb_str_hash doesn't include the encoding for ascii only strings, so - // we add it to avoid common collisions between `:sym.name` (ASCII) and `"sym"` (UTF-8) - return rb_hash_end(rb_hash_uint32(h, (uint32_t)ENCODING_GET_INLINED(str))); -} -#else -#define fstring_hash rb_str_hash -#endif - static inline bool BARE_STRING_P(VALUE str) { - if (RBASIC_CLASS(str) != rb_cString) return false; - - if (FL_TEST_RAW(str, FL_EXIVAR)) { - return rb_ivar_count(str) == 0; - } - return true; + return RBASIC_CLASS(str) == rb_cString && !rb_shape_obj_has_ivars(str); } static inline st_index_t @@ -426,14 +400,91 @@ str_store_precomputed_hash(VALUE str, st_index_t hash) return str; } -struct fstr_update_arg { +VALUE +rb_fstring(VALUE str) +{ + VALUE fstr; + int bare; + + Check_Type(str, T_STRING); + + if (FL_TEST(str, RSTRING_FSTR)) + return str; + + bare = BARE_STRING_P(str); + if (!bare) { + if (STR_EMBED_P(str)) { + OBJ_FREEZE(str); + return str; + } + + if (FL_TEST_RAW(str, STR_SHARED_ROOT | STR_SHARED) == STR_SHARED_ROOT) { + RUBY_ASSERT(OBJ_FROZEN(str)); + return str; + } + } + + if (!FL_TEST_RAW(str, FL_FREEZE | STR_NOFREE | STR_CHILLED)) + rb_str_resize(str, RSTRING_LEN(str)); + + fstr = register_fstring(str, false, false); + + if (!bare) { + str_replace_shared_without_enc(str, fstr); + OBJ_FREEZE(str); + return str; + } + return fstr; +} + +static VALUE fstring_table_obj; + +static VALUE +fstring_concurrent_set_hash(VALUE str) +{ +#ifdef PRECOMPUTED_FAKESTR_HASH + st_index_t h; + if (FL_TEST_RAW(str, STR_FAKESTR)) { + // register_fstring precomputes the hash and stores it in capa for fake strings + h = (st_index_t)RSTRING(str)->as.heap.aux.capa; + } + else { + h = rb_str_hash(str); + } + // rb_str_hash doesn't include the encoding for ascii only strings, so + // we add it to avoid common collisions between `:sym.name` (ASCII) and `"sym"` (UTF-8) + return (VALUE)rb_hash_end(rb_hash_uint32(h, (uint32_t)ENCODING_GET_INLINED(str))); +#else + return (VALUE)rb_str_hash(str); +#endif +} + +static bool +fstring_concurrent_set_cmp(VALUE a, VALUE b) +{ + long alen, blen; + const char *aptr, *bptr; + + RUBY_ASSERT(RB_TYPE_P(a, T_STRING)); + RUBY_ASSERT(RB_TYPE_P(b, T_STRING)); + + RSTRING_GETMEM(a, aptr, alen); + RSTRING_GETMEM(b, bptr, blen); + return (alen == blen && + ENCODING_GET(a) == ENCODING_GET(b) && + memcmp(aptr, bptr, alen) == 0); +} + +struct fstr_create_arg { bool copy; bool force_precompute_hash; }; static VALUE -build_fstring(VALUE str, struct fstr_update_arg *arg) +fstring_concurrent_set_create(VALUE str, void *data) { + struct fstr_create_arg *arg = data; + // Unless the string is empty or binary, its coderange has been precomputed. int coderange = ENC_CODERANGE(str); @@ -490,380 +541,30 @@ build_fstring(VALUE str, struct fstr_update_arg *arg) RUBY_ASSERT(RB_TYPE_P(str, T_STRING)); RUBY_ASSERT(OBJ_FROZEN(str)); RUBY_ASSERT(!FL_TEST_RAW(str, STR_FAKESTR)); - RUBY_ASSERT(!FL_TEST_RAW(str, FL_EXIVAR)); + RUBY_ASSERT(!rb_obj_exivar_p(str)); RUBY_ASSERT(RBASIC_CLASS(str) == rb_cString); RUBY_ASSERT(!rb_objspace_garbage_object_p(str)); return str; } -VALUE -rb_fstring(VALUE str) -{ - VALUE fstr; - int bare; - - Check_Type(str, T_STRING); - - if (FL_TEST(str, RSTRING_FSTR)) - return str; - - bare = BARE_STRING_P(str); - if (!bare) { - if (STR_EMBED_P(str)) { - OBJ_FREEZE(str); - return str; - } - - if (FL_TEST_RAW(str, STR_SHARED_ROOT | STR_SHARED) == STR_SHARED_ROOT) { - RUBY_ASSERT(OBJ_FROZEN(str)); - return str; - } - } - - if (!FL_TEST_RAW(str, FL_FREEZE | STR_NOFREE | STR_CHILLED)) - rb_str_resize(str, RSTRING_LEN(str)); - - fstr = register_fstring(str, false, false); - - if (!bare) { - str_replace_shared_without_enc(str, fstr); - OBJ_FREEZE(str); - return str; - } - return fstr; -} - -#define FSTRING_TABLE_EMPTY Qfalse -#define FSTRING_TABLE_TOMBSTONE Qtrue -#define FSTRING_TABLE_MOVED Qundef - -struct fstring_table_entry { - VALUE str; - VALUE hash; +static const struct rb_concurrent_set_funcs fstring_concurrent_set_funcs = { + .hash = fstring_concurrent_set_hash, + .cmp = fstring_concurrent_set_cmp, + .create = fstring_concurrent_set_create, }; -struct fstring_table_struct { - struct fstring_table_entry *entries; - unsigned int capacity; - unsigned int deleted_entries; - rb_atomic_t count; // TODO: pad to own cache line? -}; - -static void -fstring_table_free(void *ptr) -{ - struct fstring_table_struct *table = ptr; - xfree(table->entries); -} - -static size_t -fstring_table_size(const void *ptr) -{ - const struct fstring_table_struct *table = ptr; - return sizeof(struct fstring_table_struct) + sizeof(struct fstring_table_entry) * table->capacity; -} - -// We declare a type for the table so that we can lean on Ruby's GC for deferred reclamation -static const rb_data_type_t fstring_table_type = { - .wrap_struct_name = "VM/fstring_table", - .function = { - .dmark = NULL, - .dfree = fstring_table_free, - .dsize = fstring_table_size, - }, - .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE -}; - - -static VALUE fstring_table_obj; - -static VALUE -new_fstring_table(int capacity) -{ - VALUE obj; - struct fstring_table_struct *table; - obj = TypedData_Make_Struct(0, struct fstring_table_struct, &fstring_table_type, table); - table->capacity = capacity; - table->count = 0; - table->entries = ZALLOC_N(struct fstring_table_entry, capacity); - return obj; -} - void Init_fstring_table(void) { - fstring_table_obj = new_fstring_table(8192); + fstring_table_obj = rb_concurrent_set_new(&fstring_concurrent_set_funcs, 8192); rb_gc_register_address(&fstring_table_obj); } -#if 0 - -// Linear probe -struct fstring_table_probe { - int idx; - int mask; -}; - -static int -fstring_table_probe_start(struct fstring_table_probe *probe, struct fstring_table_struct *table, VALUE hash_code) -{ - RUBY_ASSERT((table->capacity & (table->capacity - 1)) == 0); - probe->mask = table->capacity - 1; - probe->idx = hash_code & probe->mask; - return probe->idx; -} - -static int -fstring_table_probe_next(struct fstring_table_probe *probe) -{ - probe->idx = (probe->idx + 1) & probe->mask; - return probe->idx; -} - -#else - -// Struct containing probe information. Intended that the compiler should always inline this -// Quadratic probing -struct fstring_table_probe { - int idx; - int d; - int mask; -}; - -static int -fstring_table_probe_start(struct fstring_table_probe *probe, struct fstring_table_struct *table, VALUE hash_code) -{ - RUBY_ASSERT((table->capacity & (table->capacity - 1)) == 0); - probe->d = 0; - probe->mask = table->capacity - 1; - probe->idx = hash_code & probe->mask; - return probe->idx; -} - -static int -fstring_table_probe_next(struct fstring_table_probe *probe) -{ - probe->d++; - probe->idx = (probe->idx + probe->d) & probe->mask; - return probe->idx; -} -#endif - -#define RUBY_ATOMIC_VALUE_LOAD(x) (VALUE)(RUBY_ATOMIC_PTR_LOAD(x)) - -static void -fstring_insert_on_resize(struct fstring_table_struct *table, VALUE hash_code, VALUE value) -{ - struct fstring_table_probe probe; - int idx = fstring_table_probe_start(&probe, table, hash_code); - - for (;;) { - struct fstring_table_entry *entry = &table->entries[idx]; - VALUE candidate = entry->str; - - RUBY_ASSERT(candidate != FSTRING_TABLE_TOMBSTONE); - RUBY_ASSERT(candidate != FSTRING_TABLE_MOVED); - - if (candidate == FSTRING_TABLE_EMPTY) { - table->count++; - - RUBY_ASSERT(table->count < table->capacity / 2); - RUBY_ASSERT(entry->hash == 0); - - entry->str = value; - entry->hash = hash_code; - return; - } - - idx = fstring_table_probe_next(&probe); - } -} - -// Rebuilds the table -static void -fstring_try_resize(VALUE old_table_obj) -{ - RB_VM_LOCK_ENTER(); - - // Check if another thread has already resized - if (RUBY_ATOMIC_VALUE_LOAD(fstring_table_obj) != old_table_obj) { - goto end; - } - - struct fstring_table_struct *old_table = RTYPEDDATA_GET_DATA(old_table_obj); - - // This may overcount by up to the number of threads concurrently attempting to insert - // GC may also happen between now and the table being rebuilt - int expected_count = RUBY_ATOMIC_LOAD(old_table->count) - old_table->deleted_entries; - - struct fstring_table_entry *old_entries = old_table->entries; - int old_capacity = old_table->capacity; - int new_capacity = old_capacity * 2; - if (new_capacity > expected_count * 8) { - new_capacity = old_capacity / 2; - } - else if (new_capacity > expected_count * 4) { - new_capacity = old_capacity; - } - - // May cause GC and therefore deletes, so must hapen first - VALUE new_table_obj = new_fstring_table(new_capacity); - struct fstring_table_struct *new_table = RTYPEDDATA_GET_DATA(new_table_obj); - - for (int i = 0; i < old_capacity; i++) { - struct fstring_table_entry *entry = &old_entries[i]; - VALUE val = RUBY_ATOMIC_VALUE_EXCHANGE(entry->str, FSTRING_TABLE_MOVED); - RUBY_ASSERT(val != FSTRING_TABLE_MOVED); - if (val == FSTRING_TABLE_EMPTY) continue; - if (val == FSTRING_TABLE_TOMBSTONE) continue; - if (rb_objspace_garbage_object_p(val)) continue; - - VALUE hash_code = RUBY_ATOMIC_VALUE_LOAD(entry->hash); - if (hash_code == 0) { - // Either in-progress insert or extremely unlikely 0 hash - // Re-calculate the hash ourselves - hash_code = fstring_hash(val); - } - RUBY_ASSERT(hash_code == fstring_hash(val)); - fstring_insert_on_resize(new_table, hash_code, val); - } - -#if 0 - fprintf(stderr, "resized: %p(%i) -> %p(%i) (count: %i->%i)\n", old_table, old_table->capacity, new_table, new_table->capacity, old_table->count, new_table->count); -#endif - - RUBY_ATOMIC_VALUE_SET(fstring_table_obj, new_table_obj); - -end: - RB_GC_GUARD(old_table_obj); - RB_VM_LOCK_LEAVE(); -} - -static VALUE -fstring_find_or_insert(VALUE hash_code, VALUE value, struct fstr_update_arg *arg) -{ - struct fstring_table_probe probe; - bool inserting = false; - int idx; - VALUE table_obj; - struct fstring_table_struct *table; - - retry: - table_obj = RUBY_ATOMIC_VALUE_LOAD(fstring_table_obj); - RUBY_ASSERT(table_obj); - table = RTYPEDDATA_GET_DATA(table_obj); - idx = fstring_table_probe_start(&probe, table, hash_code); - - for (;;) { - struct fstring_table_entry *entry = &table->entries[idx]; - VALUE candidate = RUBY_ATOMIC_VALUE_LOAD(entry->str); - - if (candidate == FSTRING_TABLE_EMPTY) { - // Not in table - if (!inserting) { - // Prepare a string suitable for inserting into the table - value = build_fstring(value, arg); - RUBY_ASSERT(hash_code == fstring_hash(value)); - inserting = true; - } - - unsigned int prev_count = RUBY_ATOMIC_FETCH_ADD(table->count, 1); - - if (UNLIKELY(prev_count > table->capacity / 2)) { - fstring_try_resize(table_obj); - goto retry; - } - - VALUE found = RUBY_ATOMIC_VALUE_CAS(entry->str, FSTRING_TABLE_EMPTY, value); - if (found == FSTRING_TABLE_EMPTY) { - // Success! Our value was inserted - - // Also set the hash code - RUBY_ATOMIC_VALUE_SET(entry->hash, hash_code); - - RB_GC_GUARD(table_obj); - return value; - } - else { - // Nothing was inserted - RUBY_ATOMIC_DEC(table->count); // we didn't end up inserting - - // Another thread won the race, try again at the same location - continue; - } - } - else if (candidate == FSTRING_TABLE_TOMBSTONE) { - // Deleted entry, continue searching - } - else if (candidate == FSTRING_TABLE_MOVED) { - // Wait - RB_VM_LOCK_ENTER(); - RB_VM_LOCK_LEAVE(); - - goto retry; - } - else { - VALUE candidate_hash = RUBY_ATOMIC_VALUE_LOAD(entry->hash); - if ((candidate_hash == hash_code || candidate_hash == 0) && !fstring_cmp(candidate, value)) { - // We've found a match - if (UNLIKELY(rb_objspace_garbage_object_p(candidate))) { - // This is a weakref table, so after marking but before sweeping is complete we may find a matching garbage object. - // Skip it and mark it as a tombstone to help other threads out - RUBY_ATOMIC_VALUE_CAS(entry->str, candidate, FSTRING_TABLE_TOMBSTONE); - - // Fall through and continue our search - } - else { - RB_GC_GUARD(table_obj); - return candidate; - } - } - } - - idx = fstring_table_probe_next(&probe); - } -} - - -// Removes an fstring from the table. Compares by identity -static void -fstring_delete(VALUE hash_code, VALUE value) -{ - // Delete is never called concurrently, so atomic operations are unnecessary - VALUE table_obj = RUBY_ATOMIC_VALUE_LOAD(fstring_table_obj); - RUBY_ASSERT_ALWAYS(table_obj); - struct fstring_table_struct *table = RTYPEDDATA_GET_DATA(table_obj); - - struct fstring_table_probe probe; - int idx = fstring_table_probe_start(&probe, table, hash_code); - - for (;;) { - struct fstring_table_entry *entry = &table->entries[idx]; - VALUE candidate = entry->str; - - // Allocations should only occur at the beginning of the resize - RUBY_ASSERT(candidate != FSTRING_TABLE_MOVED); - - if (candidate == FSTRING_TABLE_EMPTY) { - // We didn't find our string to delete - return; - } - else if (candidate == value) { - // We found our string, replace it with a tombstone and increment the count - entry->str = FSTRING_TABLE_TOMBSTONE; - table->deleted_entries++; - return; - } - - idx = fstring_table_probe_next(&probe); - } -} - static VALUE register_fstring(VALUE str, bool copy, bool force_precompute_hash) { - struct fstr_update_arg args = { + struct fstr_create_arg args = { .copy = copy, .force_precompute_hash = force_precompute_hash }; @@ -876,8 +577,7 @@ register_fstring(VALUE str, bool copy, bool force_precompute_hash) } #endif - VALUE hash_code = fstring_hash(str); - VALUE result = fstring_find_or_insert(hash_code, str, &args); + VALUE result = rb_concurrent_set_find_or_insert(&fstring_table_obj, str, &args); RUBY_ASSERT(!rb_objspace_garbage_object_p(result)); RUBY_ASSERT(RB_TYPE_P(result, T_STRING)); @@ -888,47 +588,6 @@ register_fstring(VALUE str, bool copy, bool force_precompute_hash) return result; } -void -rb_fstring_foreach_with_replace(st_foreach_check_callback_func *func, st_update_callback_func *replace, st_data_t arg) -{ - // Assume locking and barrier (which there is no assert for) - ASSERT_vm_locking(); - - VALUE table_obj = RUBY_ATOMIC_VALUE_LOAD(fstring_table_obj); - if (!table_obj) { - // Table not yet initialized. Nothing to iterate over - return; - } - struct fstring_table_struct *table = RTYPEDDATA_GET_DATA(table_obj); - - for (unsigned int i = 0; i < table->capacity; i++) { - VALUE key = table->entries[i].str; - if(key == FSTRING_TABLE_EMPTY) continue; - if(key == FSTRING_TABLE_TOMBSTONE) continue; - - enum st_retval retval; - retval = (*func)(key, key, arg, 0); - - if (retval == ST_REPLACE && replace) { - st_data_t value = key; - retval = (*replace)(&key, &value, arg, TRUE); - table->entries[i].str = key; - } - switch (retval) { - case ST_REPLACE: - case ST_CONTINUE: - break; - case ST_CHECK: - rb_bug("unsupported"); - case ST_STOP: - return; - case ST_DELETE: - table->entries[i].str = FSTRING_TABLE_TOMBSTONE; - break; - } - } -} - bool rb_obj_is_fstring_table(VALUE obj) { @@ -943,18 +602,26 @@ rb_gc_free_fstring(VALUE obj) // Assume locking and barrier (which there is no assert for) ASSERT_vm_locking(); - VALUE str_hash = fstring_hash(obj); - fstring_delete(str_hash, obj); + rb_concurrent_set_delete_by_identity(fstring_table_obj, obj); RB_DEBUG_COUNTER_INC(obj_str_fstr); FL_UNSET(obj, RSTRING_FSTR); } +void +rb_fstring_foreach_with_replace(int (*callback)(VALUE *str, void *data), void *data) +{ + if (fstring_table_obj) { + rb_concurrent_set_foreach_with_replace(fstring_table_obj, callback, data); + } +} + static VALUE setup_fake_str(struct RString *fake_str, const char *name, long len, int encidx) { fake_str->basic.flags = T_STRING|RSTRING_NOEMBED|STR_NOFREE|STR_FAKESTR; + RBASIC_SET_SHAPE_ID((VALUE)fake_str, ROOT_SHAPE_ID); if (!name) { RUBY_ASSERT_ALWAYS(len == 0); @@ -1004,22 +671,6 @@ rb_fstring_cstr(const char *ptr) return rb_fstring_new(ptr, strlen(ptr)); } -static int -fstring_cmp(VALUE a, VALUE b) -{ - long alen, blen; - const char *aptr, *bptr; - - RUBY_ASSERT(RB_TYPE_P(a, T_STRING)); - RUBY_ASSERT(RB_TYPE_P(b, T_STRING)); - - RSTRING_GETMEM(a, aptr, alen); - RSTRING_GETMEM(b, bptr, blen); - return (alen != blen || - ENCODING_GET(a) != ENCODING_GET(b) || - memcmp(aptr, bptr, alen) != 0); -} - static inline bool single_byte_optimizable(VALUE str) { @@ -1911,8 +1562,8 @@ rb_str_tmp_frozen_release(VALUE orig, VALUE tmp) if (STR_EMBED_P(tmp)) { RUBY_ASSERT(OBJ_FROZEN_RAW(tmp)); } - else if (FL_TEST_RAW(orig, STR_SHARED) && - !FL_TEST_RAW(orig, STR_TMPLOCK|RUBY_FL_FREEZE)) { + else if (FL_TEST_RAW(orig, STR_SHARED | STR_TMPLOCK) == STR_TMPLOCK && + !OBJ_FROZEN_RAW(orig)) { VALUE shared = RSTRING(orig)->as.heap.aux.shared; if (shared == tmp && !FL_TEST_RAW(tmp, STR_BORROWED)) { @@ -2259,7 +1910,7 @@ str_duplicate_setup_heap(VALUE klass, VALUE str, VALUE dup) if (FL_TEST_RAW(str, STR_SHARED)) { root = RSTRING(str)->as.heap.aux.shared; } - else if (UNLIKELY(!(flags & FL_FREEZE))) { + else if (UNLIKELY(!OBJ_FROZEN_RAW(str))) { root = str = str_new_frozen(klass, str); flags = FL_TEST_RAW(str, flag_mask); } @@ -2311,7 +1962,7 @@ VALUE rb_str_dup_m(VALUE str) { if (LIKELY(BARE_STRING_P(str))) { - return str_duplicate(rb_obj_class(str), str); + return str_duplicate(rb_cString, str); } else { return rb_obj_dup(str); @@ -3664,6 +3315,7 @@ RUBY_ALIAS_FUNCTION(rb_str_dup_frozen(VALUE str), rb_str_new_frozen, (str)) VALUE rb_str_locktmp(VALUE str) { + rb_check_frozen(str); if (FL_TEST(str, STR_TMPLOCK)) { rb_raise(rb_eRuntimeError, "temporal locking already locked string"); } @@ -3674,6 +3326,7 @@ rb_str_locktmp(VALUE str) VALUE rb_str_unlocktmp(VALUE str) { + rb_check_frozen(str); if (!FL_TEST(str, STR_TMPLOCK)) { rb_raise(rb_eRuntimeError, "temporal unlocking already unlocked string"); } @@ -4136,19 +3789,7 @@ rb_str_concat_literals(size_t num, const VALUE *strary) * call-seq: * concat(*objects) -> string * - * Concatenates each object in +objects+ to +self+ and returns +self+: - * - * s = 'foo' - * s.concat('bar', 'baz') # => "foobarbaz" - * s # => "foobarbaz" - * - * For each given object +object+ that is an Integer, - * the value is considered a codepoint and converted to a character before concatenation: - * - * s = 'foo' - * s.concat(32, 'bar', 32, 'baz') # => "foo bar baz" - * - * Related: String#<<, which takes a single argument. + * :include: doc/string/concat.rdoc */ static VALUE rb_str_concat_multi(int argc, VALUE *argv, VALUE str) @@ -4173,25 +3814,27 @@ rb_str_concat_multi(int argc, VALUE *argv, VALUE str) /* * call-seq: - * append_as_bytes(*objects) -> string + * append_as_bytes(*objects) -> self * - * Concatenates each object in +objects+ into +self+ without any encoding - * validation or conversion and returns +self+: + * Concatenates each object in +objects+ into +self+; returns +self+; + * performs no encoding validation or conversion: * * s = 'foo' - * s.append_as_bytes(" \xE2\x82") # => "foo \xE2\x82" - * s.valid_encoding? # => false + * s.append_as_bytes(" \xE2\x82") # => "foo \xE2\x82" + * s.valid_encoding? # => false * s.append_as_bytes("\xAC 12") - * s.valid_encoding? # => true + * s.valid_encoding? # => true * - * For each given object +object+ that is an Integer, - * the value is considered a Byte. If the Integer is bigger - * than one byte, only the lower byte is considered, similar to String#setbyte: + * When a given object is an integer, + * the value is considered an 8-bit byte; + * if the integer occupies more than one byte (i.e,. is greater than 255), + * appends only the low-order byte (similar to String#setbyte): * * s = "" - * s.append_as_bytes(0, 257) # => "\u0000\u0001" + * s.append_as_bytes(0, 257) # => "\u0000\u0001" + * s.bytesize # => 2 * - * Related: String#<<, String#concat, which do an encoding aware concatenation. + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ VALUE @@ -4308,22 +3951,34 @@ rb_str_append_as_bytes(int argc, VALUE *argv, VALUE str) /* * call-seq: - * string << object -> string + * self << object -> self * - * Concatenates +object+ to +self+ and returns +self+: + * Appends a string representation of +object+ to +self+; + * returns +self+. + * + * If +object+ is a string, appends it to +self+: * * s = 'foo' * s << 'bar' # => "foobar" * s # => "foobar" * - * If +object+ is an Integer, - * the value is considered a codepoint and converted to a character before concatenation: + * If +object+ is an integer, + * its value is considered a codepoint; + * converts the value to a character before concatenating: * * s = 'foo' * s << 33 # => "foo!" * - * If that codepoint is not representable in the encoding of - * _string_, RangeError is raised. + * Additionally, if the codepoint is in range 0..0xff + * and the encoding of +self+ is Encoding::US_ASCII, + * changes the encoding to Encoding::ASCII_8BIT: + * + * s = 'foo'.encode(Encoding::US_ASCII) + * s.encoding # => # + * s << 0xff # => "foo\xFF" + * s.encoding # => # + * + * Raises RangeError if that codepoint is not representable in the encoding of +self+: * * s = 'foo' * s.encoding # => @@ -4331,14 +3986,7 @@ rb_str_append_as_bytes(int argc, VALUE *argv, VALUE str) * s = 'foo'.encode(Encoding::EUC_JP) * s << 0x00800080 # invalid codepoint 0x800080 in EUC-JP (RangeError) * - * If the encoding is US-ASCII and the codepoint is 0..0xff, _string_ - * is automatically promoted to ASCII-8BIT. - * - * s = 'foo'.encode(Encoding::US_ASCII) - * s << 0xff - * s.encoding # => # - * - * Related: String#concat, which takes multiple arguments. + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ VALUE rb_str_concat(VALUE str1, VALUE str2) @@ -4551,22 +4199,29 @@ rb_str_cmp(VALUE str1, VALUE str2) /* * call-seq: - * string == object -> true or false - * string === object -> true or false + * self == object -> true or false * - * Returns +true+ if +object+ has the same length and content; - * as +self+; +false+ otherwise: + * Returns whether +object+ is equal to +self+. + * + * When +object+ is a string, returns whether +object+ has the same length and content as +self+: * * s = 'foo' - * s == 'foo' # => true + * s == 'foo' # => true * s == 'food' # => false - * s == 'FOO' # => false + * s == 'FOO' # => false * * Returns +false+ if the two strings' encodings are not compatible: + * * "\u{e4 f6 fc}".encode(Encoding::ISO_8859_1) == ("\u{c4 d6 dc}") # => false * - * If +object+ is not an instance of +String+ but responds to +to_str+, then the - * two strings are compared using object.==. + * When +object+ is not a string: + * + * - If +object+ responds to method to_str, + * object == self is called and its return value is returned. + * - If +object+ does not respond to to_str, + * +false+ is returned. + * + * Related: {Comparing}[rdoc-ref:String@Comparing]. */ VALUE @@ -4650,26 +4305,26 @@ static VALUE str_casecmp_p(VALUE str1, VALUE str2); * call-seq: * casecmp(other_string) -> -1, 0, 1, or nil * - * Compares self.downcase and other_string.downcase; returns: + * Ignoring case, compares +self+ and +other_string+; returns: * - * - -1 if other_string.downcase is larger. + * - -1 if self.downcase is smaller than other_string.downcase. * - 0 if the two are equal. - * - 1 if other_string.downcase is smaller. + * - 1 if self.downcase is larger than other_string.downcase. * - +nil+ if the two are incomparable. * - * Examples: - * - * 'foo'.casecmp('foo') # => 0 - * 'foo'.casecmp('food') # => -1 - * 'food'.casecmp('foo') # => 1 - * 'FOO'.casecmp('foo') # => 0 - * 'foo'.casecmp('FOO') # => 0 - * 'foo'.casecmp(1) # => nil - * * See {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * - * Related: String#casecmp?. + * Examples: * + * 'foo'.casecmp('goo') # => -1 + * 'goo'.casecmp('foo') # => 1 + * 'foo'.casecmp('food') # => -1 + * 'food'.casecmp('foo') # => 1 + * 'FOO'.casecmp('foo') # => 0 + * 'foo'.casecmp('FOO') # => 0 + * 'foo'.casecmp(1) # => nil + * + * Related: see {Comparing}[rdoc-ref:String@Comparing]. */ static VALUE @@ -4744,22 +4399,21 @@ str_casecmp(VALUE str1, VALUE str2) * casecmp?(other_string) -> true, false, or nil * * Returns +true+ if +self+ and +other_string+ are equal after - * Unicode case folding, otherwise +false+: - * - * 'foo'.casecmp?('foo') # => true - * 'foo'.casecmp?('food') # => false - * 'food'.casecmp?('foo') # => false - * 'FOO'.casecmp?('foo') # => true - * 'foo'.casecmp?('FOO') # => true - * - * Returns +nil+ if the two values are incomparable: - * - * 'foo'.casecmp?(1) # => nil + * Unicode case folding, +false+ if unequal, +nil+ if incomparable. * * See {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * - * Related: String#casecmp. + * Examples: * + * 'foo'.casecmp?('goo') # => false + * 'goo'.casecmp?('foo') # => false + * 'foo'.casecmp?('food') # => false + * 'food'.casecmp?('foo') # => false + * 'FOO'.casecmp?('foo') # => true + * 'foo'.casecmp?('FOO') # => true + * 'foo'.casecmp?(1) # => nil + * + * Related: see {Comparing}[rdoc-ref:String@Comparing]. */ static VALUE @@ -4926,43 +4580,69 @@ str_ensure_byte_pos(VALUE str, long pos) /* * call-seq: - * byteindex(substring, offset = 0) -> integer or nil - * byteindex(regexp, offset = 0) -> integer or nil + * byteindex(object, offset = 0) -> integer or nil * - * Returns the Integer byte-based index of the first occurrence of the given +substring+, - * or +nil+ if none found: + * Returns the 0-based integer index of a substring of +self+ + * specified by +object+ (a string or Regexp) and +offset+, + * or +nil+ if there is no such substring; + * the returned index is the count of _bytes_ (not characters). * - * 'foo'.byteindex('f') # => 0 - * 'foo'.byteindex('o') # => 1 - * 'foo'.byteindex('oo') # => 1 - * 'foo'.byteindex('ooo') # => nil + * When +object+ is a string, + * returns the index of the first found substring equal to +object+: * - * Returns the Integer byte-based index of the first match for the given Regexp +regexp+, - * or +nil+ if none found: + * s = 'foo' # => "foo" + * s.size # => 3 # Three 1-byte characters. + * s.bytesize # => 3 # Three bytes. + * s.byteindex('f') # => 0 + * s.byteindex('o') # => 1 + * s.byteindex('oo') # => 1 + * s.byteindex('ooo') # => nil * - * 'foo'.byteindex(/f/) # => 0 - * 'foo'.byteindex(/o/) # => 1 - * 'foo'.byteindex(/oo/) # => 1 - * 'foo'.byteindex(/ooo/) # => nil + * When +object+ is a Regexp, + * returns the index of the first found substring matching +object+; + * updates {Regexp-related global variables}[rdoc-ref:Regexp@Global+Variables]: * - * Integer argument +offset+, if given, specifies the byte-based position in the - * string to begin the search: + * s = 'foo' + * s.byteindex(/f/) # => 0 + * $~ # => # + * s.byteindex(/o/) # => 1 + * s.byteindex(/oo/) # => 1 + * s.byteindex(/ooo/) # => nil + * $~ # => nil * - * 'foo'.byteindex('o', 1) # => 1 - * 'foo'.byteindex('o', 2) # => 2 - * 'foo'.byteindex('o', 3) # => nil + * \Integer argument +offset+, if given, specifies the 0-based index + * of the byte where searching is to begin. * - * If +offset+ is negative, counts backward from the end of +self+: + * When +offset+ is non-negative, + * searching begins at byte position +offset+: * - * 'foo'.byteindex('o', -1) # => 2 - * 'foo'.byteindex('o', -2) # => 1 - * 'foo'.byteindex('o', -3) # => 1 - * 'foo'.byteindex('o', -4) # => nil + * s = 'foo' + * s.byteindex('o', 1) # => 1 + * s.byteindex('o', 2) # => 2 + * s.byteindex('o', 3) # => nil * - * If +offset+ does not land on character (codepoint) boundary, +IndexError+ is - * raised. + * When +offset+ is negative, counts backward from the end of +self+: * - * Related: String#index, String#byterindex. + * s = 'foo' + * s.byteindex('o', -1) # => 2 + * s.byteindex('o', -2) # => 1 + * s.byteindex('o', -3) # => 1 + * s.byteindex('o', -4) # => nil + * + * Raises IndexError if the byte at +offset+ is not the first byte of a character: + * + * s = "\uFFFF\uFFFF" # => "\uFFFF\uFFFF" + * s.size # => 2 # Two 3-byte characters. + * s.bytesize # => 6 # Six bytes. + * s.byteindex("\uFFFF") # => 0 + * s.byteindex("\uFFFF", 1) # Raises IndexError + * s.byteindex("\uFFFF", 2) # Raises IndexError + * s.byteindex("\uFFFF", 3) # => 3 + * s.byteindex("\uFFFF", 4) # Raises IndexError + * s.byteindex("\uFFFF", 5) # Raises IndexError + * s.byteindex("\uFFFF", 6) # => nil + * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE @@ -5218,65 +4898,90 @@ rb_str_byterindex(VALUE str, VALUE sub, long pos) return str_rindex(str, sub, s, enc); } - /* * call-seq: - * byterindex(substring, offset = self.bytesize) -> integer or nil - * byterindex(regexp, offset = self.bytesize) -> integer or nil + * byterindex(object, offset = self.bytesize) -> integer or nil * - * Returns the Integer byte-based index of the _last_ occurrence of the given +substring+, - * or +nil+ if none found: + * Returns the 0-based integer index of a substring of +self+ + * that is the _last_ match for the given +object+ (a string or Regexp) and +offset+, + * or +nil+ if there is no such substring; + * the returned index is the count of _bytes_ (not characters). * - * 'foo'.byterindex('f') # => 0 - * 'foo'.byterindex('o') # => 2 - * 'foo'.byterindex('oo') # => 1 - * 'foo'.byterindex('ooo') # => nil + * When +object+ is a string, + * returns the index of the _last_ found substring equal to +object+: * - * Returns the Integer byte-based index of the _last_ match for the given Regexp +regexp+, - * or +nil+ if none found: + * s = 'foo' # => "foo" + * s.size # => 3 # Three 1-byte characters. + * s.bytesize # => 3 # Three bytes. + * s.byterindex('f') # => 0 + s.byterindex('o') # => 2 + s.byterindex('oo') # => 1 + s.byterindex('ooo') # => nil * - * 'foo'.byterindex(/f/) # => 0 - * 'foo'.byterindex(/o/) # => 2 - * 'foo'.byterindex(/oo/) # => 1 - * 'foo'.byterindex(/ooo/) # => nil + * When +object+ is a Regexp, + * returns the index of the last found substring matching +object+; + * updates {Regexp-related global variables}[rdoc-ref:Regexp@Global+Variables]: * - * The _last_ match means starting at the possible last position, not - * the last of longest matches. + * s = 'foo' + * s.byterindex(/f/) # => 0 + * $~ # => # + * s.byterindex(/o/) # => 2 + * s.byterindex(/oo/) # => 1 + * s.byterindex(/ooo/) # => nil + * $~ # => nil * - * 'foo'.byterindex(/o+/) # => 2 - * $~ #=> # + * The last match means starting at the possible last position, + * not the last of the longest matches: * - * To get the last longest match, needs to combine with negative - * lookbehind. + * s = 'foo' + * s.byterindex(/o+/) # => 2 + * $~ #=> # * - * 'foo'.byterindex(/(? 1 - * $~ #=> # + * To get the last longest match, use a negative lookbehind: * - * Or String#byteindex with negative lookforward. + * s = 'foo' + * s.byterindex(/(? 1 + * $~ # => # * - * 'foo'.byteindex(/o+(?!.*o)/) # => 1 - * $~ #=> # + * Or use method #byteindex with negative lookahead: * - * Integer argument +offset+, if given and non-negative, specifies the maximum starting byte-based position in the - * string to _end_ the search: + * s = 'foo' + * s.byteindex(/o+(?!.*o)/) # => 1 + * $~ #=> # * - * 'foo'.byterindex('o', 0) # => nil - * 'foo'.byterindex('o', 1) # => 1 - * 'foo'.byterindex('o', 2) # => 2 - * 'foo'.byterindex('o', 3) # => 2 + * \Integer argument +offset+, if given, specifies the 0-based index + * of the byte where searching is to end. * - * If +offset+ is a negative Integer, the maximum starting position in the - * string to _end_ the search is the sum of the string's length and +offset+: + * When +offset+ is non-negative, + * searching ends at byte position +offset+: * - * 'foo'.byterindex('o', -1) # => 2 - * 'foo'.byterindex('o', -2) # => 1 - * 'foo'.byterindex('o', -3) # => nil - * 'foo'.byterindex('o', -4) # => nil + * s = 'foo' + * s.byterindex('o', 0) # => nil + * s.byterindex('o', 1) # => 1 + * s.byterindex('o', 2) # => 2 + * s.byterindex('o', 3) # => 2 * - * If +offset+ does not land on character (codepoint) boundary, +IndexError+ is - * raised. + * When +offset+ is negative, counts backward from the end of +self+: * - * Related: String#byteindex. + * s = 'foo' + * s.byterindex('o', -1) # => 2 + * s.byterindex('o', -2) # => 1 + * s.byterindex('o', -3) # => nil + * + * Raises IndexError if the byte at +offset+ is not the first byte of a character: + * + * s = "\uFFFF\uFFFF" # => "\uFFFF\uFFFF" + * s.size # => 2 # Two 3-byte characters. + * s.bytesize # => 6 # Six bytes. + * s.byterindex("\uFFFF") # => 3 + * s.byterindex("\uFFFF", 1) # Raises IndexError + * s.byterindex("\uFFFF", 2) # Raises IndexError + * s.byterindex("\uFFFF", 3) # => 3 + * s.byterindex("\uFFFF", 4) # Raises IndexError + * s.byterindex("\uFFFF", 5) # Raises IndexError + * s.byterindex("\uFFFF", 6) # => nil + * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE @@ -5320,30 +5025,33 @@ rb_str_byterindex_m(int argc, VALUE *argv, VALUE str) /* * call-seq: - * string =~ regexp -> integer or nil - * string =~ object -> integer or nil + * self =~ object -> integer or nil * - * Returns the Integer index of the first substring that matches - * the given +regexp+, or +nil+ if no match found: + * When +object+ is a Regexp, returns the index of the first substring in +self+ + * matched by +object+, + * or +nil+ if no match is found; + * updates {Regexp-related global variables}[rdoc-ref:Regexp@Global+Variables]: * * 'foo' =~ /f/ # => 0 + * $~ # => # * 'foo' =~ /o/ # => 1 + * $~ # => # * 'foo' =~ /x/ # => nil - * - * Note: also updates Regexp@Global+Variables. - * - * If the given +object+ is not a Regexp, returns the value - * returned by object =~ self. + * $~ # => nil * * Note that string =~ regexp is different from regexp =~ string * (see Regexp#=~): * - * number= nil - * "no. 9" =~ /(?\d+)/ - * number # => nil (not assigned) - * /(?\d+)/ =~ "no. 9" - * number #=> "9" + * number = nil + * 'no. 9' =~ /(?\d+)/ # => 4 + * number # => nil # Not assigned. + * /(?\d+)/ =~ 'no. 9' # => 4 + * number # => "9" # Assigned. * + * If +object+ is not a Regexp, returns the value + * returned by object =~ self. + * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE @@ -6092,16 +5800,16 @@ rb_str_aref(VALUE str, VALUE indx) /* * call-seq: - * string[index] -> new_string or nil - * string[start, length] -> new_string or nil - * string[range] -> new_string or nil - * string[regexp, capture = 0] -> new_string or nil - * string[substring] -> new_string or nil + * self[index] -> new_string or nil + * self[start, length] -> new_string or nil + * self[range] -> new_string or nil + * self[regexp, capture = 0] -> new_string or nil + * self[substring] -> new_string or nil * * Returns the substring of +self+ specified by the arguments. * See examples at {String Slices}[rdoc-ref:String@String+Slices]. * - * + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE @@ -6315,11 +6023,11 @@ rb_str_aset(VALUE str, VALUE indx, VALUE val) /* * call-seq: - * string[index] = new_string - * string[start, length] = new_string - * string[range] = new_string - * string[regexp, capture = 0] = new_string - * string[substring] = new_string + * self[index] = new_string + * self[start, length] = new_string + * self[range] = new_string + * self[regexp, capture = 0] = new_string + * self[substring] = new_string * * Replaces all, some, or none of the contents of +self+; returns +new_string+. * See {String Slices}[rdoc-ref:String@String+Slices]. @@ -6338,6 +6046,7 @@ rb_str_aset(VALUE str, VALUE indx, VALUE val) * s['lly'] = 'ncial' # => "ncial" * s # => "financial" * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -6945,9 +6654,11 @@ rb_str_replace(VALUE str, VALUE str2) * * Removes the contents of +self+: * - * s = 'foo' # => "foo" - * s.clear # => "" + * s = 'foo' + * s.clear # => "" + * s # => "" * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -6968,10 +6679,7 @@ rb_str_clear(VALUE str) * call-seq: * chr -> string * - * Returns a string containing the first character of +self+: - * - * s = 'foo' # => "foo" - * s.chr # => "f" + * :include: doc/string/chr.rdoc * */ @@ -7148,45 +6856,10 @@ str_byte_aref(VALUE str, VALUE indx) /* * call-seq: - * byteslice(index, length = 1) -> string or nil - * byteslice(range) -> string or nil - * - * Returns a substring of +self+, or +nil+ if the substring cannot be constructed. - * - * With integer arguments +index+ and +length+ given, - * returns the substring beginning at the given +index+ - * of the given +length+ (if possible), - * or +nil+ if +length+ is negative or +index+ falls outside of +self+: - * - * s = '0123456789' # => "0123456789" - * s.byteslice(2) # => "2" - * s.byteslice(200) # => nil - * s.byteslice(4, 3) # => "456" - * s.byteslice(4, 30) # => "456789" - * s.byteslice(4, -1) # => nil - * s.byteslice(40, 2) # => nil - * - * In either case above, counts backwards from the end of +self+ - * if +index+ is negative: - * - * s = '0123456789' # => "0123456789" - * s.byteslice(-4) # => "6" - * s.byteslice(-4, 3) # => "678" - * - * With Range argument +range+ given, returns - * byteslice(range.begin, range.size): - * - * s = '0123456789' # => "0123456789" - * s.byteslice(4..6) # => "456" - * s.byteslice(-6..-4) # => "456" - * s.byteslice(5..2) # => "" # range.size is zero. - * s.byteslice(40..42) # => nil - * - * In all cases, a returned string has the same encoding as +self+: - * - * s.encoding # => # - * s.byteslice(4).encoding # => # + * byteslice(offset, length = 1) -> string or nil + * byteslice(range) -> string or nil * + * :include: doc/string/byteslice.rdoc */ static VALUE @@ -7226,23 +6899,12 @@ str_check_beg_len(VALUE str, long *beg, long *len) /* * call-seq: - * bytesplice(index, length, str) -> string - * bytesplice(index, length, str, str_index, str_length) -> string - * bytesplice(range, str) -> string - * bytesplice(range, str, str_range) -> string + * bytesplice(offset, length, str) -> self + * bytesplice(offset, length, str, str_offset, str_length) -> self + * bytesplice(range, str) -> self + * bytesplice(range, str, str_range) -> self * - * Replaces some or all of the content of +self+ with +str+, and returns +self+. - * The portion of the string affected is determined using - * the same criteria as String#byteslice, except that +length+ cannot be omitted. - * If the replacement string is not the same length as the text it is replacing, - * the string will be adjusted accordingly. - * - * If +str_index+ and +str_length+, or +str_range+ are given, the content of +self+ is replaced by str.byteslice(str_index, str_length) or str.byteslice(str_range); however the substring of +str+ is not allocated as a new string. - * - * The form that take an Integer will raise an IndexError if the value is out - * of range; the Range form will raise a RangeError. - * If the beginning or ending offset does not land on character (codepoint) - * boundary, an IndexError will be raised. + * :include: doc/string/bytesplice.rdoc */ static VALUE @@ -8380,7 +8042,7 @@ upcase_single(VALUE str) /* * call-seq: - * upcase!(*options) -> self or nil + * upcase!(mapping) -> self or nil * * Upcases the characters in +self+; * returns +self+ if any changes were made, +nil+ otherwise: @@ -8390,7 +8052,7 @@ upcase_single(VALUE str) * s # => "HELLO WORLD!" * s.upcase! # => nil * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#upcase, String#downcase, String#downcase!. @@ -8422,14 +8084,14 @@ rb_str_upcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * upcase(*options) -> string + * upcase(mapping) -> string * * Returns a string containing the upcased characters in +self+: * * s = 'Hello World!' # => "Hello World!" * s.upcase # => "HELLO WORLD!" * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#upcase!, String#downcase, String#downcase!. @@ -8482,21 +8144,14 @@ downcase_single(VALUE str) /* * call-seq: - * downcase!(*options) -> self or nil + * downcase!(mapping) -> self or nil * - * Downcases the characters in +self+; - * returns +self+ if any changes were made, +nil+ otherwise: + * Like String#downcase, except that: * - * s = 'Hello World!' # => "Hello World!" - * s.downcase! # => "hello world!" - * s # => "hello world!" - * s.downcase! # => nil - * - * The casing may be affected by the given +options+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. - * - * Related: String#downcase, String#upcase, String#upcase!. + * - Changes character casings in +self+ (not in a copy of +self+). + * - Returns +self+ if any changes are made, +nil+ otherwise. * + * Related: See {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -8524,17 +8179,9 @@ rb_str_downcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * downcase(*options) -> string + * downcase(mapping) -> string * - * Returns a string containing the downcased characters in +self+: - * - * s = 'Hello World!' # => "Hello World!" - * s.downcase # => "hello world!" - * - * The casing may be affected by the given +options+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. - * - * Related: String#downcase!, String#upcase, String#upcase!. + * :include: doc/string/downcase.rdoc * */ @@ -8566,22 +8213,14 @@ rb_str_downcase(int argc, VALUE *argv, VALUE str) /* * call-seq: - * capitalize!(*options) -> self or nil + * capitalize!(mapping = :ascii) -> self or nil * - * Upcases the first character in +self+; - * downcases the remaining characters; - * returns +self+ if any changes were made, +nil+ otherwise: + * Like String#capitalize, except that: * - * s = 'hello World!' # => "hello World!" - * s.capitalize! # => "Hello world!" - * s # => "Hello world!" - * s.capitalize! # => nil - * - * The casing may be affected by the given +options+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. - * - * Related: String#capitalize. + * - Changes character casings in +self+ (not in a copy of +self+). + * - Returns +self+ if any changes are made, +nil+ otherwise. * + * Related: See {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -8606,20 +8245,29 @@ rb_str_capitalize_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * capitalize(*options) -> string + * capitalize(mapping = :ascii) -> string * - * Returns a string containing the characters in +self+; - * the first character is upcased; - * the remaining characters are downcased: + * Returns a string containing the characters in +self+, + * each with possibly changed case: * - * s = 'hello World!' # => "hello World!" - * s.capitalize # => "Hello world!" + * - The first character is upcased. + * - All other characters are downcased. * - * The casing may be affected by the given +options+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. + * Examples: * - * Related: String#capitalize!. + * 'hello world'.capitalize # => "Hello world" + * 'HELLO WORLD'.capitalize # => "Hello world" * + * Some characters do not have upcase and downcase, and so are not changed; + * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]: + * + * '1, 2, 3, ...'.capitalize # => "1, 2, 3, ..." + * + * The casing is affected by the given +mapping+, + * which may be +:ascii+, +:fold+, or +:turkic+; + * see {Case Mappings}[rdoc-ref:case_mapping.rdoc@Case+Mappings]. + * + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE @@ -8645,7 +8293,7 @@ rb_str_capitalize(int argc, VALUE *argv, VALUE str) /* * call-seq: - * swapcase!(*options) -> self or nil + * swapcase!(mapping) -> self or nil * * Upcases each lowercase character in +self+; * downcases uppercase character; @@ -8656,7 +8304,7 @@ rb_str_capitalize(int argc, VALUE *argv, VALUE str) * s # => "hELLO wORLD!" * ''.swapcase! # => nil * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#swapcase. @@ -8684,7 +8332,7 @@ rb_str_swapcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * swapcase(*options) -> string + * swapcase(mapping) -> string * * Returns a string containing the characters in +self+, with cases reversed; * each uppercase character is downcased; @@ -8693,7 +8341,7 @@ rb_str_swapcase_bang(int argc, VALUE *argv, VALUE str) * s = 'Hello World!' # => "Hello World!" * s.swapcase # => "hELLO wORLD!" * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#swapcase!. @@ -9203,9 +8851,10 @@ tr_find(unsigned int c, const char table[TR_TABLE_SIZE], VALUE del, VALUE nodel) * call-seq: * delete!(*selectors) -> self or nil * - * Like String#delete, but modifies +self+ in place. - * Returns +self+ if any changes were made, +nil+ otherwise. + * Like String#delete, but modifies +self+ in place; + * returns +self+ if any characters were deleted, +nil+ otherwise. * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -9274,13 +8923,7 @@ rb_str_delete_bang(int argc, VALUE *argv, VALUE str) * call-seq: * delete(*selectors) -> new_string * - * Returns a copy of +self+ with characters specified by +selectors+ removed - * (see {Multiple Character Selectors}[rdoc-ref:character_selectors.rdoc@Multiple+Character+Selectors]): - * - * "hello".delete "l","lo" #=> "heo" - * "hello".delete "lo" #=> "he" - * "hello".delete "aeiou", "^e" #=> "hell" - * "hello".delete "ej-m" #=> "ho" + * :include: doc/string/delete.rdoc * */ @@ -9448,23 +9091,7 @@ rb_str_tr_s(VALUE str, VALUE src, VALUE repl) * call-seq: * count(*selectors) -> integer * - * Returns the total number of characters in +self+ - * that are specified by the given +selectors+ - * (see {Multiple Character Selectors}[rdoc-ref:character_selectors.rdoc@Multiple+Character+Selectors]): - * - * a = "hello world" - * a.count "lo" #=> 5 - * a.count "lo", "o" #=> 2 - * a.count "hello", "^l" #=> 4 - * a.count "ej-m" #=> 4 - * - * "hello^world".count "\\^aeiou" #=> 4 - * "hello-world".count "a\\-eo" #=> 4 - * - * c = "hello world\\r\\n" - * c.count "\\" #=> 2 - * c.count "\\A" #=> 0 - * c.count "X-\\w" #=> 3 + * :include: doc/string/count.rdoc */ static VALUE @@ -9699,11 +9326,15 @@ rb_str_split_m(int argc, VALUE *argv, VALUE str) } } -#define SPLIT_STR(beg, len) (empty_count = split_string(result, str, beg, len, empty_count)) +#define SPLIT_STR(beg, len) ( \ + empty_count = split_string(result, str, beg, len, empty_count), \ + str_mod_check(str, str_start, str_len)) beg = 0; char *ptr = RSTRING_PTR(str); - char *eptr = RSTRING_END(str); + char *const str_start = ptr; + const long str_len = RSTRING_LEN(str); + char *const eptr = str_start + str_len; if (split_type == SPLIT_TYPE_AWK) { char *bptr = ptr; int skip = 1; @@ -9764,7 +9395,6 @@ rb_str_split_m(int argc, VALUE *argv, VALUE str) } } else if (split_type == SPLIT_TYPE_STRING) { - char *str_start = ptr; char *substr_start = ptr; char *sptr = RSTRING_PTR(spat); long slen = RSTRING_LEN(spat); @@ -9781,6 +9411,7 @@ rb_str_split_m(int argc, VALUE *argv, VALUE str) continue; } SPLIT_STR(substr_start - str_start, (ptr+end) - substr_start); + str_mod_check(spat, sptr, slen); ptr += end + slen; substr_start = ptr; if (!NIL_P(limit) && lim <= ++i) break; @@ -9788,7 +9419,6 @@ rb_str_split_m(int argc, VALUE *argv, VALUE str) beg = ptr - str_start; } else if (split_type == SPLIT_TYPE_CHARS) { - char *str_start = ptr; int n; if (result) result = rb_ary_new_capa(RSTRING_LEN(str)); @@ -10455,10 +10085,12 @@ chopped_length(VALUE str) * call-seq: * chop! -> self or nil * - * Like String#chop, but modifies +self+ in place; - * returns +nil+ if +self+ is empty, +self+ otherwise. + * Like String#chop, except that: * - * Related: String#chomp!. + * - Removes trailing characters from +self+ (not from a copy of +self+). + * - Returns +self+ if any characters are removed, +nil+ otherwise. + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -10635,9 +10267,12 @@ rb_str_chomp_string(VALUE str, VALUE rs) * call-seq: * chomp!(line_sep = $/) -> self or nil * - * Like String#chomp, but modifies +self+ in place; - * returns +nil+ if no modification made, +self+ otherwise. + * Like String#chomp, except that: * + * - Removes trailing characters from +self+ (not from a copy of +self+). + * - Returns +self+ if any characters are removed, +nil+ otherwise. + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -11097,11 +10732,6 @@ rb_str_oct(VALUE str) static struct { rb_nativethread_lock_t lock; } crypt_mutex = {PTHREAD_MUTEX_INITIALIZER}; - -static void -crypt_mutex_initialize(void) -{ -} #endif /* @@ -11172,6 +10802,7 @@ rb_str_crypt(VALUE str, VALUE salt) struct crypt_data *data; # define CRYPT_END() ALLOCV_END(databuf) #else + char *tmp_buf; extern char *crypt(const char *, const char *); # define CRYPT_END() rb_nativethread_lock_unlock(&crypt_mutex.lock) #endif @@ -11206,7 +10837,6 @@ rb_str_crypt(VALUE str, VALUE salt) # endif res = crypt_r(s, saltp, data); #else - crypt_mutex_initialize(); rb_nativethread_lock_lock(&crypt_mutex.lock); res = crypt(s, saltp); #endif @@ -11215,8 +10845,21 @@ rb_str_crypt(VALUE str, VALUE salt) CRYPT_END(); rb_syserr_fail(err, "crypt"); } +#ifdef HAVE_CRYPT_R result = rb_str_new_cstr(res); CRYPT_END(); +#else + // We need to copy this buffer because it's static and we need to unlock the mutex + // before allocating a new object (the string to be returned). If we allocate while + // holding the lock, we could run GC which fires the VM barrier and causes a deadlock + // if other ractors are waiting on this lock. + size_t res_size = strlen(res)+1; + tmp_buf = ALLOCA_N(char, res_size); // should be small enough to alloca + memcpy(tmp_buf, res, res_size); + res = tmp_buf; + CRYPT_END(); + result = rb_str_new_cstr(res); +#endif return result; } @@ -11430,8 +11073,6 @@ rb_str_rjust(int argc, VALUE *argv, VALUE str) * * :include: doc/string/center.rdoc * - * Related: String#ljust, String#rjust. - * */ static VALUE @@ -11644,9 +11285,10 @@ deleted_prefix_length(VALUE str, VALUE prefix) * call-seq: * delete_prefix!(prefix) -> self or nil * - * Like String#delete_prefix, except that +self+ is modified in place. - * Returns +self+ if the prefix is removed, +nil+ otherwise. + * Like String#delete_prefix, except that +self+ is modified in place; + * returns +self+ if the prefix is removed, +nil+ otherwise. * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -11719,9 +11361,10 @@ deleted_suffix_length(VALUE str, VALUE suffix) * call-seq: * delete_suffix!(suffix) -> self or nil * - * Like String#delete_suffix, except that +self+ is modified in place. - * Returns +self+ if the suffix is removed, +nil+ otherwise. + * Like String#delete_suffix, except that +self+ is modified in place; + * returns +self+ if the suffix is removed, +nil+ otherwise. * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -11823,7 +11466,7 @@ rb_str_force_encoding(VALUE str, VALUE enc) /* * call-seq: - * b -> string + * b -> new_string * * :include: doc/string/b.rdoc * @@ -11885,12 +11528,12 @@ rb_str_valid_encoding_p(VALUE str) * call-seq: * ascii_only? -> true or false * - * Returns +true+ if +self+ contains only ASCII characters, - * +false+ otherwise: + * Returns whether +self+ contains only ASCII characters: * * 'abc'.ascii_only? # => true * "abc\u{6666}".ascii_only? # => false * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE @@ -12795,7 +12438,7 @@ sym_empty(VALUE sym) /* * call-seq: - * upcase(*options) -> symbol + * upcase(mapping) -> symbol * * Equivalent to sym.to_s.upcase.to_sym. * @@ -12811,7 +12454,7 @@ sym_upcase(int argc, VALUE *argv, VALUE sym) /* * call-seq: - * downcase(*options) -> symbol + * downcase(mapping) -> symbol * * Equivalent to sym.to_s.downcase.to_sym. * @@ -12829,7 +12472,7 @@ sym_downcase(int argc, VALUE *argv, VALUE sym) /* * call-seq: - * capitalize(*options) -> symbol + * capitalize(mapping) -> symbol * * Equivalent to sym.to_s.capitalize.to_sym. * @@ -12845,7 +12488,7 @@ sym_capitalize(int argc, VALUE *argv, VALUE sym) /* * call-seq: - * swapcase(*options) -> symbol + * swapcase(mapping) -> symbol * * Equivalent to sym.to_s.swapcase.to_sym. * @@ -13017,16 +12660,21 @@ rb_yjit_str_concat_codepoint(VALUE str, VALUE codepoint) } #endif +static int +fstring_set_class_i(VALUE *str, void *data) +{ + RBASIC_SET_CLASS(*str, rb_cString); + + return ST_CONTINUE; +} + void Init_String(void) { rb_cString = rb_define_class("String", rb_cObject); - struct fstring_table_struct *fstring_table = RTYPEDDATA_GET_DATA(fstring_table_obj); - for (unsigned int i = 0; i < fstring_table->capacity; i++) { - VALUE str = fstring_table->entries[i].str; - if (!str) continue; - RBASIC_SET_CLASS(str, rb_cString); - } + + rb_concurrent_set_foreach_with_replace(fstring_table_obj, fstring_set_class_i, NULL); + rb_include_module(rb_cString, rb_mComparable); rb_define_alloc_func(rb_cString, empty_str_alloc); rb_define_singleton_method(rb_cString, "new", rb_str_s_new, -1); @@ -13230,3 +12878,4 @@ Init_String(void) rb_define_method(rb_cSymbol, "encoding", sym_encoding, 0); } + diff --git a/struct.c b/struct.c index 7cfc1f2a16..74ca9369a6 100644 --- a/struct.c +++ b/struct.c @@ -52,7 +52,8 @@ struct_ivar_get(VALUE c, ID id) RUBY_ASSERT(RB_TYPE_P(c, T_CLASS)); ivar = rb_attr_get(c, id); if (!NIL_P(ivar)) { - return rb_ivar_set(orig, id, ivar); + if (!OBJ_FROZEN(orig)) rb_ivar_set(orig, id, ivar); + return ivar; } } } diff --git a/symbol.c b/symbol.c index 7925db451d..e4f18197c9 100644 --- a/symbol.c +++ b/symbol.c @@ -131,8 +131,11 @@ WARN_UNUSED_RESULT(static VALUE lookup_str_sym(const VALUE str)); WARN_UNUSED_RESULT(static VALUE lookup_id_str(ID id)); WARN_UNUSED_RESULT(static ID intern_str(VALUE str, int mutable)); -#define GLOBAL_SYMBOLS_ENTER(symbols) rb_symbols_t *symbols = &ruby_global_symbols; RB_VM_LOCK_ENTER() -#define GLOBAL_SYMBOLS_LEAVE() RB_VM_LOCK_LEAVE() +#define GLOBAL_SYMBOLS_LOCKING(symbols) \ + for (rb_symbols_t *symbols = &ruby_global_symbols, **locking = &symbols; \ + locking; \ + locking = NULL) \ + RB_VM_LOCKING() ID rb_id_attrset(ID id) @@ -170,8 +173,21 @@ rb_id_attrset(ID id) } } - /* make new symbol and ID */ - if (!(str = lookup_id_str(id))) { + bool error = false; + GLOBAL_SYMBOLS_LOCKING(symbols) { + /* make new symbol and ID */ + if ((str = lookup_id_str(id))) { + str = rb_str_dup(str); + rb_str_cat(str, "=", 1); + sym = lookup_str_sym(str); + id = sym ? rb_sym2id(sym) : intern_str(str, 1); + } + else { + error = true; + } + } + + if (error) { RBIMPL_ATTR_NONSTRING_ARRAY() static const char id_types[][8] = { "local", "instance", @@ -185,10 +201,7 @@ rb_id_attrset(ID id) rb_name_error(id, "cannot make anonymous %.*s ID %"PRIxVALUE" attrset", (int)sizeof(id_types[0]), id_types[scope], (VALUE)id); } - str = rb_str_dup(str); - rb_str_cat(str, "=", 1); - sym = lookup_str_sym(str); - id = sym ? rb_sym2id(sym) : intern_str(str, 1); + return id; } @@ -467,8 +480,7 @@ get_id_serial_entry(rb_id_serial_t num, ID id, const enum id_entry_type t) { VALUE result = 0; - GLOBAL_SYMBOLS_ENTER(symbols); - { + GLOBAL_SYMBOLS_LOCKING(symbols) { if (num && num <= symbols->last_id) { size_t idx = num / ID_ENTRY_UNIT; VALUE ids = symbols->ids; @@ -496,7 +508,6 @@ get_id_serial_entry(rb_id_serial_t num, ID id, const enum id_entry_type t) } } } - GLOBAL_SYMBOLS_LEAVE(); if (result) { switch (t) { @@ -567,11 +578,9 @@ register_sym(rb_symbols_t *symbols, VALUE str, VALUE sym) void rb_free_static_symid_str(void) { - GLOBAL_SYMBOLS_ENTER(symbols) - { + GLOBAL_SYMBOLS_LOCKING(symbols) { st_free_table(symbols->str_sym); } - GLOBAL_SYMBOLS_LEAVE(); } static void @@ -603,12 +612,10 @@ register_static_symid_str(ID id, VALUE str) RUBY_DTRACE_CREATE_HOOK(SYMBOL, RSTRING_PTR(str)); - GLOBAL_SYMBOLS_ENTER(symbols) - { + GLOBAL_SYMBOLS_LOCKING(symbols) { register_sym(symbols, str, sym); set_id_entry(symbols, num, str, sym); } - GLOBAL_SYMBOLS_LEAVE(); return id; } @@ -705,11 +712,9 @@ lookup_str_id(VALUE str) st_data_t sym_data; int found; - GLOBAL_SYMBOLS_ENTER(symbols); - { + GLOBAL_SYMBOLS_LOCKING(symbols) { found = st_lookup(symbols->str_sym, (st_data_t)str, &sym_data); } - GLOBAL_SYMBOLS_LEAVE(); if (found) { const VALUE sym = (VALUE)sym_data; @@ -750,11 +755,9 @@ lookup_str_sym(const VALUE str) { VALUE sym; - GLOBAL_SYMBOLS_ENTER(symbols); - { + GLOBAL_SYMBOLS_LOCKING(symbols) { sym = lookup_str_sym_with_lock(symbols, str); } - GLOBAL_SYMBOLS_LEAVE(); return sym; } @@ -772,10 +775,20 @@ rb_intern3(const char *name, long len, rb_encoding *enc) struct RString fake_str; VALUE str = rb_setup_fake_str(&fake_str, name, len, enc); OBJ_FREEZE(str); - sym = lookup_str_sym(str); - if (sym) return rb_sym2id(sym); - str = rb_enc_str_new(name, len, enc); /* make true string */ - return intern_str(str, 1); + ID id; + + GLOBAL_SYMBOLS_LOCKING(symbols) { + sym = lookup_str_sym(str); + if (sym) { + id = rb_sym2id(sym); + } + else { + str = rb_enc_str_new(name, len, enc); /* make true string */ + id = intern_str(str, 1); + } + } + + return id; } static ID @@ -799,17 +812,17 @@ static ID next_id_base(void) { ID id; - GLOBAL_SYMBOLS_ENTER(symbols); - { + GLOBAL_SYMBOLS_LOCKING(symbols) { id = next_id_base_with_lock(symbols); } - GLOBAL_SYMBOLS_LEAVE(); return id; } static ID intern_str(VALUE str, int mutable) { + ASSERT_vm_locking(); + ID id; ID nid; @@ -845,13 +858,18 @@ rb_intern(const char *name) ID rb_intern_str(VALUE str) { - VALUE sym = lookup_str_sym(str); - - if (sym) { - return SYM2ID(sym); + ID id; + GLOBAL_SYMBOLS_LOCKING(symbols) { + VALUE sym = lookup_str_sym(str); + if (sym) { + id = SYM2ID(sym); + } + else { + id = intern_str(str, 0); + } } - return intern_str(str, 0); + return id; } void @@ -862,12 +880,10 @@ rb_gc_free_dsymbol(VALUE sym) if (str) { RSYMBOL(sym)->fstr = 0; - GLOBAL_SYMBOLS_ENTER(symbols); - { + GLOBAL_SYMBOLS_LOCKING(symbols) { unregister_sym(symbols, str, sym); rb_hash_delete_entry(symbols->dsymbol_fstr_hash, str); } - GLOBAL_SYMBOLS_LEAVE(); } } @@ -894,10 +910,9 @@ rb_gc_free_dsymbol(VALUE sym) VALUE rb_str_intern(VALUE str) { - VALUE sym; + VALUE sym = 0; - GLOBAL_SYMBOLS_ENTER(symbols); - { + GLOBAL_SYMBOLS_LOCKING(symbols) { sym = lookup_str_sym_with_lock(symbols, str); if (sym) { @@ -926,21 +941,19 @@ rb_str_intern(VALUE str) sym = ID2SYM(id); } } - GLOBAL_SYMBOLS_LEAVE(); return sym; } ID rb_sym2id(VALUE sym) { - ID id; + ID id = 0; if (STATIC_SYM_P(sym)) { id = STATIC_SYM2ID(sym); } else if (DYNAMIC_SYM_P(sym)) { - GLOBAL_SYMBOLS_ENTER(symbols); - { - sym = dsymbol_check(symbols, sym); + GLOBAL_SYMBOLS_LOCKING(symbols) { + RUBY_ASSERT(!rb_objspace_garbage_object_p(sym)); id = RSYMBOL(sym)->id; if (UNLIKELY(!(id & ~ID_SCOPE_MASK))) { @@ -954,7 +967,6 @@ rb_sym2id(VALUE sym) rb_hash_delete_entry(symbols->dsymbol_fstr_hash, fstr); } } - GLOBAL_SYMBOLS_LEAVE(); } else { rb_raise(rb_eTypeError, "wrong argument type %s (expected Symbol)", @@ -1060,12 +1072,10 @@ rb_sym_all_symbols(void) { VALUE ary; - GLOBAL_SYMBOLS_ENTER(symbols); - { + GLOBAL_SYMBOLS_LOCKING(symbols) { ary = rb_ary_new2(symbols->str_sym->num_entries); st_foreach(symbols->str_sym, symbols_i, ary); } - GLOBAL_SYMBOLS_LEAVE(); return ary; } @@ -1198,15 +1208,7 @@ rb_check_symbol(volatile VALUE *namep) return name; } else if (DYNAMIC_SYM_P(name)) { - if (!SYMBOL_PINNED_P(name)) { - GLOBAL_SYMBOLS_ENTER(symbols); - { - name = dsymbol_check(symbols, name); - } - GLOBAL_SYMBOLS_LEAVE(); - - *namep = name; - } + RUBY_ASSERT(!rb_objspace_garbage_object_p(name)); return name; } else if (!RB_TYPE_P(name, T_STRING)) { diff --git a/template/Makefile.in b/template/Makefile.in index aed81bf1ef..1e6d55c435 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -107,15 +107,15 @@ JIT_OBJ=@JIT_OBJ@ YJIT_SUPPORT=@YJIT_SUPPORT@ YJIT_LIBS=@YJIT_LIBS@ YJIT_OBJ=@YJIT_OBJ@ -YJIT_LIBOBJ = $(YJIT_LIBS:.a=.@OBJEXT@) ZJIT_SUPPORT=@ZJIT_SUPPORT@ ZJIT_LIBS=@ZJIT_LIBS@ ZJIT_OBJ=@ZJIT_OBJ@ -ZJIT_LIBOBJ = $(ZJIT_LIBS:.a=.@OBJEXT@) -CARGO_TARGET_DIR=@abs_top_builddir@/yjit/target +JIT_CARGO_SUPPORT=@JIT_CARGO_SUPPORT@ +CARGO_TARGET_DIR=@abs_top_builddir@/target CARGO_BUILD_ARGS=@CARGO_BUILD_ARGS@ -ZJIT_CARGO_BUILD_ARGS=@ZJIT_CARGO_BUILD_ARGS@ -ZJIT_CARGO_TARGET_DIR=@abs_top_builddir@/zjit/target +ZJIT_TEST_FEATURES=@ZJIT_TEST_FEATURES@ +RUST_LIB=@RUST_LIB@ +RUST_LIBOBJ = $(RUST_LIB:.a=.@OBJEXT@) LDFLAGS = @STATIC@ $(CFLAGS) @LDFLAGS@ EXE_LDFLAGS = $(LDFLAGS) EXTLDFLAGS = @EXTLDFLAGS@ diff --git a/test/-ext-/bug_reporter/test_bug_reporter.rb b/test/-ext-/bug_reporter/test_bug_reporter.rb index 8293408518..d402ab1382 100644 --- a/test/-ext-/bug_reporter/test_bug_reporter.rb +++ b/test/-ext-/bug_reporter/test_bug_reporter.rb @@ -6,8 +6,6 @@ require_relative '../../lib/parser_support' class TestBugReporter < Test::Unit::TestCase def test_bug_reporter_add - pend "macOS 15 is not working with this test" if macos?(15) - description = RUBY_DESCRIPTION description = description.sub(/\+PRISM /, '') unless ParserSupport.prism_enabled_in_subprocess? expected_stderr = [ diff --git a/test/-ext-/gvl/test_last_thread.rb b/test/-ext-/gvl/test_last_thread.rb index f1bebafeea..f63d98aab1 100644 --- a/test/-ext-/gvl/test_last_thread.rb +++ b/test/-ext-/gvl/test_last_thread.rb @@ -15,8 +15,7 @@ class TestLastThread < Test::Unit::TestCase t1 = Time.now t = t1 - t0 - assert_in_delta(1.0, t, 0.16) + assert_in_delta(1.0, t, 0.18) end; end end - diff --git a/test/-ext-/string/test_capacity.rb b/test/-ext-/string/test_capacity.rb index bcca64d85a..df000f7cdb 100644 --- a/test/-ext-/string/test_capacity.rb +++ b/test/-ext-/string/test_capacity.rb @@ -66,7 +66,7 @@ class Test_StringCapacity < Test::Unit::TestCase end def embed_header_size - 3 * RbConfig::SIZEOF['void*'] + GC::INTERNAL_CONSTANTS[:RBASIC_SIZE] + RbConfig::SIZEOF['void*'] end def max_embed_len diff --git a/test/-ext-/thread/test_instrumentation_api.rb b/test/-ext-/thread/test_instrumentation_api.rb index c0fad14908..ba41069304 100644 --- a/test/-ext-/thread/test_instrumentation_api.rb +++ b/test/-ext-/thread/test_instrumentation_api.rb @@ -151,7 +151,7 @@ class TestThreadInstrumentation < Test::Unit::TestCase end full_timeline = record do - ractor.take + ractor.value end timeline = timeline_for(Thread.current, full_timeline) @@ -172,7 +172,7 @@ class TestThreadInstrumentation < Test::Unit::TestCase thread = Ractor.new{ sleep 0.1 Thread.current - }.take + }.value sleep 0.1 end diff --git a/test/.excludes-parsey/TestDefined.rb b/test/.excludes-parsey/TestDefined.rb deleted file mode 100644 index ae2a2a6270..0000000000 --- a/test/.excludes-parsey/TestDefined.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(:test_defined_paren_void_stmts, "defined? (x;)") diff --git a/test/.excludes-zjit/TestFixnum.rb b/test/.excludes-zjit/TestFixnum.rb new file mode 100644 index 0000000000..b2ca8c67f7 --- /dev/null +++ b/test/.excludes-zjit/TestFixnum.rb @@ -0,0 +1 @@ +exclude(/test_/, 'Tests make ZJIT panic') diff --git a/test/.excludes-zjit/TestKeywordArguments.rb b/test/.excludes-zjit/TestKeywordArguments.rb new file mode 100644 index 0000000000..f52bdf6d30 --- /dev/null +++ b/test/.excludes-zjit/TestKeywordArguments.rb @@ -0,0 +1 @@ +exclude(/test_/, 'Multiple tests make ZJIT panic') diff --git a/test/.excludes-zjit/TestParse.rb b/test/.excludes-zjit/TestParse.rb new file mode 100644 index 0000000000..e5d3b09447 --- /dev/null +++ b/test/.excludes-zjit/TestParse.rb @@ -0,0 +1,2 @@ +exclude(:test_flip_flop, 'Test fails with ZJIT') +exclude(:test_rescue_in_command_assignment, 'Test fails with ZJIT') diff --git a/test/.excludes-zjit/TestProc.rb b/test/.excludes-zjit/TestProc.rb new file mode 100644 index 0000000000..aa6abbecbe --- /dev/null +++ b/test/.excludes-zjit/TestProc.rb @@ -0,0 +1,3 @@ +exclude(:test_proc_args_pos_rest_block, 'Test crashes with ZJIT') +exclude(:test_proc_args_rest_post_block, 'Test crashes with ZJIT') +exclude(:test_binding_receiver, 'Test fails with ZJIT') diff --git a/test/.excludes-zjit/TestRefinement.rb b/test/.excludes-zjit/TestRefinement.rb new file mode 100644 index 0000000000..39e504c9a2 --- /dev/null +++ b/test/.excludes-zjit/TestRefinement.rb @@ -0,0 +1 @@ +exclude(:test_override_builtin_method_with_method_added, 'Test fails with ZJIT') diff --git a/test/.excludes-zjit/TestRegexp.rb b/test/.excludes-zjit/TestRegexp.rb new file mode 100644 index 0000000000..e344b6d803 --- /dev/null +++ b/test/.excludes-zjit/TestRegexp.rb @@ -0,0 +1 @@ +exclude(:test_union, 'Test fails with ZJIT') diff --git a/test/.excludes-zjit/TestRubyOptions.rb b/test/.excludes-zjit/TestRubyOptions.rb new file mode 100644 index 0000000000..966d0b7551 --- /dev/null +++ b/test/.excludes-zjit/TestRubyOptions.rb @@ -0,0 +1,10 @@ +exclude(:test_verbose, 'Test crashes with ZJIT') +exclude(:test_segv_setproctitle, 'Test crashes with ZJIT') +exclude(:test_crash_report, 'Test crashes with ZJIT') +exclude(:test_segv_loaded_features, 'Test crashes with ZJIT') +exclude(:test_crash_report_executable_path, 'Test crashes with ZJIT') +exclude(:test_segv_test, 'Test crashes with ZJIT') +exclude(:test_crash_report_script, 'Test crashes with ZJIT') +exclude(:test_crash_report_script_path, 'Test crashes with ZJIT') + +exclude(:test_version, 'Test fails with ZJIT') diff --git a/test/.excludes-zjit/TestSetTraceFunc.rb b/test/.excludes-zjit/TestSetTraceFunc.rb new file mode 100644 index 0000000000..01b1acbb61 --- /dev/null +++ b/test/.excludes-zjit/TestSetTraceFunc.rb @@ -0,0 +1 @@ +exclude(/test_/, 'Test fails with ZJIT intermittently') diff --git a/test/.excludes-zjit/TestThread.rb b/test/.excludes-zjit/TestThread.rb new file mode 100644 index 0000000000..1eb9c50dba --- /dev/null +++ b/test/.excludes-zjit/TestThread.rb @@ -0,0 +1,2 @@ +exclude(:test_switch_while_busy_loop, 'Test hangs with ZJIT') +exclude(:test_handle_interrupted?, 'Test fails with ZJIT') diff --git a/test/.excludes-zjit/TestTime.rb b/test/.excludes-zjit/TestTime.rb new file mode 100644 index 0000000000..b2ca8c67f7 --- /dev/null +++ b/test/.excludes-zjit/TestTime.rb @@ -0,0 +1 @@ +exclude(/test_/, 'Tests make ZJIT panic') diff --git a/test/.excludes-zjit/TestTimeTZ.rb b/test/.excludes-zjit/TestTimeTZ.rb new file mode 100644 index 0000000000..b2ca8c67f7 --- /dev/null +++ b/test/.excludes-zjit/TestTimeTZ.rb @@ -0,0 +1 @@ +exclude(/test_/, 'Tests make ZJIT panic') diff --git a/test/.excludes/URI/TestMailTo.rb b/test/.excludes/URI/TestMailTo.rb new file mode 100644 index 0000000000..c9b1f94fe2 --- /dev/null +++ b/test/.excludes/URI/TestMailTo.rb @@ -0,0 +1 @@ +exclude :test_email_regexp, "still flaky with --repeat-count option" diff --git a/test/.excludes/_appveyor/TestArray.rb b/test/.excludes/_appveyor/TestArray.rb deleted file mode 100644 index 7d03833f07..0000000000 --- a/test/.excludes/_appveyor/TestArray.rb +++ /dev/null @@ -1,7 +0,0 @@ -# https://ci.appveyor.com/project/ruby/ruby/builds/20339189/job/ltdpffep976xtj85 -# `test_push_over_ary_max': failed to allocate memory (NoMemoryError) -exclude(:test_push_over_ary_max, 'Sometimes AppVeyor has insufficient memory to run this test') -# https://ci.appveyor.com/project/ruby/ruby/builds/20728419/job/o73q9fy1ojfibg5v -exclude(:test_unshift_over_ary_max, 'Sometimes AppVeyor has insufficient memory to run this test') -# https://ci.appveyor.com/project/ruby/ruby/builds/20427662/job/prq9i2lkfxv2j0uy -exclude(:test_splice_over_ary_max, 'Sometimes AppVeyor has insufficient memory to run this test') diff --git a/test/date/test_date.rb b/test/date/test_date.rb index 3f9c893efa..7e37fc94d2 100644 --- a/test/date/test_date.rb +++ b/test/date/test_date.rb @@ -134,6 +134,10 @@ class TestDate < Test::Unit::TestCase assert_equal(9, h[Date.new(1999,5,25)]) assert_equal(9, h[DateTime.new(1999,5,25)]) + h = {} + h[Date.new(3171505571716611468830131104691,2,19)] = 0 + assert_equal(true, h.key?(Date.new(3171505571716611468830131104691,2,19))) + h = {} h[DateTime.new(1999,5,23)] = 0 h[DateTime.new(1999,5,24)] = 1 diff --git a/test/date/test_date_ractor.rb b/test/date/test_date_ractor.rb index 7ec953d87a..91ea38bb93 100644 --- a/test/date/test_date_ractor.rb +++ b/test/date/test_date_ractor.rb @@ -8,7 +8,7 @@ class TestDateParseRactor < Test::Unit::TestCase share = #{share} d = Date.parse('Aug 23:55') Ractor.make_shareable(d) if share - d2, d3 = Ractor.new(d) { |d| [d, Date.parse(d.to_s)] }.take + d2, d3 = Ractor.new(d) { |d| [d, Date.parse(d.to_s)] }.value if share assert_same d, d2 else diff --git a/test/date/test_switch_hitter.rb b/test/date/test_switch_hitter.rb index bdf299e030..cc75782537 100644 --- a/test/date/test_switch_hitter.rb +++ b/test/date/test_switch_hitter.rb @@ -97,6 +97,11 @@ class TestSH < Test::Unit::TestCase [d.year, d.mon, d.mday, d.hour, d.min, d.sec, d.offset]) end + def test_ajd + assert_equal(Date.civil(2008, 1, 16).ajd, 4908963r/2) + assert_equal(Date.civil(-11082381539297990, 2, 19).ajd, -8095679714453739481r/2) + end + def test_ordinal d = Date.ordinal assert_equal([-4712, 1], [d.year, d.yday]) diff --git a/test/did_you_mean/spell_checking/test_method_name_check.rb b/test/did_you_mean/spell_checking/test_method_name_check.rb index 4daaf7cec7..2ae5fa7d03 100644 --- a/test/did_you_mean/spell_checking/test_method_name_check.rb +++ b/test/did_you_mean/spell_checking/test_method_name_check.rb @@ -98,6 +98,8 @@ class MethodNameCheckTest < Test::Unit::TestCase end def test_does_not_append_suggestions_twice + omit "This test is not working with JRuby" if RUBY_ENGINE == "jruby" + error = assert_raise NoMethodError do begin @user.firstname @@ -110,6 +112,8 @@ class MethodNameCheckTest < Test::Unit::TestCase end def test_does_not_append_suggestions_three_times + omit "This test is not working with JRuby" if RUBY_ENGINE == "jruby" + error = assert_raise NoMethodError do begin @user.raise_no_method_error diff --git a/test/did_you_mean/test_ractor_compatibility.rb b/test/did_you_mean/test_ractor_compatibility.rb index 7385f10612..3166d0b6c5 100644 --- a/test/did_you_mean/test_ractor_compatibility.rb +++ b/test/did_you_mean/test_ractor_compatibility.rb @@ -14,7 +14,7 @@ class RactorCompatibilityTest < Test::Unit::TestCase e.corrections # It is important to call the #corrections method within Ractor. e end - }.take + }.value assert_correction "Book", error.corrections CODE @@ -32,7 +32,7 @@ class RactorCompatibilityTest < Test::Unit::TestCase e.corrections # It is important to call the #corrections method within Ractor. e end - }.take + }.value assert_correction ":bar", error.corrections assert_match "Did you mean? :bar", get_message(error) @@ -49,7 +49,7 @@ class RactorCompatibilityTest < Test::Unit::TestCase e.corrections # It is important to call the #corrections method within Ractor. e end - }.take + }.value assert_correction :to_s, error.corrections assert_match "Did you mean? to_s", get_message(error) @@ -71,7 +71,7 @@ class RactorCompatibilityTest < Test::Unit::TestCase e.corrections # It is important to call the #corrections method within Ractor. e end - }.take + }.value assert_correction ":foo", error.corrections assert_match "Did you mean? :foo", get_message(error) @@ -90,7 +90,7 @@ class RactorCompatibilityTest < Test::Unit::TestCase e.corrections # It is important to call the #corrections method within Ractor. e end - }.take + }.value assert_not_match(/Did you mean\?/, error.message) CODE @@ -108,7 +108,7 @@ class RactorCompatibilityTest < Test::Unit::TestCase e.corrections # It is important to call the #corrections method within Ractor. e end - }.take + }.value assert_correction :in_ractor, error.corrections assert_match "Did you mean? in_ractor", get_message(error) diff --git a/test/digest/test_ractor.rb b/test/digest/test_ractor.rb index b34a3653b4..d7b03eaeba 100644 --- a/test/digest/test_ractor.rb +++ b/test/digest/test_ractor.rb @@ -15,6 +15,10 @@ module TestDigestRactor def test_s_hexdigest assert_in_out_err([], <<-"end;", ["true", "true"], []) + class Ractor + alias value take + end unless Ractor.method_defined? :value # compat with Ruby 3.4 and olders + $VERBOSE = nil require "digest" require "#{self.class::LIB}" @@ -26,7 +30,7 @@ module TestDigestRactor [r, hexdigest] end rs.each do |r, hexdigest| - puts r.take == hexdigest + puts r.value == hexdigest end end; end diff --git a/test/etc/test_etc.rb b/test/etc/test_etc.rb index feb05aa3c3..c2e3af6317 100644 --- a/test/etc/test_etc.rb +++ b/test/etc/test_etc.rb @@ -21,7 +21,7 @@ class TestEtc < Test::Unit::TestCase assert_instance_of(String, s.shell) assert_kind_of(Integer, s.change) if s.respond_to?(:change) assert_kind_of(Integer, s.quota) if s.respond_to?(:quota) - assert(s.age.is_a?(Integer) || s.age.is_a?(String)) if s.respond_to?(:age) + assert(s.age.is_a?(Integer) || s.age.is_a?(String), s.age) if s.respond_to?(:age) assert_instance_of(String, s.uclass) if s.respond_to?(:uclass) assert_instance_of(String, s.comment) if s.respond_to?(:comment) assert_kind_of(Integer, s.expire) if s.respond_to?(:expire) @@ -160,7 +160,7 @@ class TestEtc < Test::Unit::TestCase end IO.pipe {|r, w| val = w.pathconf(Etc::PC_PIPE_BUF) - assert(val.nil? || val.kind_of?(Integer)) + assert_kind_of(Integer, val) if val } end if defined?(Etc::PC_PIPE_BUF) @@ -198,7 +198,7 @@ class TestEtc < Test::Unit::TestCase raise unless Integer === Etc.nprocessors end end - end.each(&:take) + end.each(&:join) RUBY end @@ -210,7 +210,7 @@ class TestEtc < Test::Unit::TestCase rescue => e e.class end - end.take + end.value assert_equal Ractor::UnsafeError, r RUBY end @@ -221,19 +221,19 @@ class TestEtc < Test::Unit::TestCase Etc.endpwent assert_ractor(<<~RUBY, require: 'etc') - ractor = Ractor.new do + ractor = Ractor.new port = Ractor::Port.new do |port| Etc.passwd do |s| - Ractor.yield :sync - Ractor.yield s.name + port << :sync + port << s.name break :done end end - ractor.take # => :sync + port.receive # => :sync assert_raise RuntimeError, /parallel/ do Etc.passwd {} end - name = ractor.take # => first name - ractor.take # => :done + name = port.receive # => first name + ractor.join # => :done name2 = Etc.passwd do |s| break s.name end @@ -251,7 +251,7 @@ class TestEtc < Test::Unit::TestCase raise unless Etc.getgrgid(Process.gid).gid == Process.gid end end - end.each(&:take) + end.each(&:join) RUBY end end diff --git a/test/fiber/scheduler.rb b/test/fiber/scheduler.rb index ac19bba7a2..2401cb30d3 100644 --- a/test/fiber/scheduler.rb +++ b/test/fiber/scheduler.rb @@ -68,9 +68,15 @@ class Scheduler def run # $stderr.puts [__method__, Fiber.current].inspect + readable = writable = nil + while @readable.any? or @writable.any? or @waiting.any? or @blocking.any? # May only handle file descriptors up to 1024... - readable, writable = IO.select(@readable.keys + [@urgent.first], @writable.keys, [], next_timeout) + begin + readable, writable = IO.select(@readable.keys + [@urgent.first], @writable.keys, [], next_timeout) + rescue IOError + # Ignore - this can happen if the IO is closed while we are waiting. + end # puts "readable: #{readable}" if readable&.any? # puts "writable: #{writable}" if writable&.any? @@ -120,7 +126,7 @@ class Scheduler end ready.each do |fiber| - fiber.transfer + fiber.transfer if fiber.alive? end end end @@ -290,6 +296,30 @@ class Scheduler io.write_nonblock('.') end + class FiberInterrupt + def initialize(fiber, exception) + @fiber = fiber + @exception = exception + end + + def alive? + @fiber.alive? + end + + def transfer + @fiber.raise(@exception) + end + end + + def fiber_interrupt(fiber, exception) + @lock.synchronize do + @ready << FiberInterrupt.new(fiber, exception) + end + + io = @urgent.last + io.write_nonblock('.') + end + # This hook is invoked by `Fiber.schedule`. Strictly speaking, you should use # it to create scheduled fibers, but it is not required in practice; # `Fiber.new` is usually sufficient. @@ -311,7 +341,7 @@ class Scheduler end def blocking_operation_wait(work) - thread = Thread.new(&work) + thread = Thread.new{work.call} thread.join diff --git a/test/fiber/test_io.rb b/test/fiber/test_io.rb index 39e32c5987..eea06f97c8 100644 --- a/test/fiber/test_io.rb +++ b/test/fiber/test_io.rb @@ -9,7 +9,7 @@ class TestFiberIO < Test::Unit::TestCase omit unless defined?(UNIXSocket) i, o = UNIXSocket.pair - if RUBY_PLATFORM=~/mswin|mingw/ + if RUBY_PLATFORM =~ /mswin|mingw/ i.nonblock = true o.nonblock = true end @@ -44,7 +44,7 @@ class TestFiberIO < Test::Unit::TestCase 16.times.map do Thread.new do i, o = UNIXSocket.pair - if RUBY_PLATFORM=~/mswin|mingw/ + if RUBY_PLATFORM =~ /mswin|mingw/ i.nonblock = true o.nonblock = true end @@ -67,7 +67,7 @@ class TestFiberIO < Test::Unit::TestCase def test_epipe_on_read omit unless defined?(UNIXSocket) - omit "nonblock=true isn't properly supported on Windows" if RUBY_PLATFORM=~/mswin|mingw/ + omit "nonblock=true isn't properly supported on Windows" if RUBY_PLATFORM =~ /mswin|mingw/ i, o = UNIXSocket.pair @@ -242,38 +242,37 @@ class TestFiberIO < Test::Unit::TestCase # Windows has UNIXSocket, but only with VS 2019+ omit "UNIXSocket is not defined!" unless defined?(UNIXSocket) - i, o = Socket.pair(:UNIX, :STREAM) - if RUBY_PLATFORM=~/mswin|mingw/ - i.nonblock = true - o.nonblock = true - end - - reading_thread = Thread.new do - Thread.current.report_on_exception = false - i.wait_readable - end - - fs_thread = Thread.new do - # Wait until the reading thread is blocked on read: - Thread.pass until reading_thread.status == "sleep" - - scheduler = Scheduler.new - Fiber.set_scheduler scheduler - Fiber.schedule do - i.close + Socket.pair(:UNIX, :STREAM) do |i, o| + if RUBY_PLATFORM =~ /mswin|mingw/ + i.nonblock = true + o.nonblock = true end + + reading_thread = Thread.new do + Thread.current.report_on_exception = false + i.wait_readable + end + + scheduler_thread = Thread.new do + # Wait until the reading thread is blocked on read: + Thread.pass until reading_thread.status == "sleep" + + scheduler = Scheduler.new + Fiber.set_scheduler scheduler + Fiber.schedule do + i.close + end + end + + assert_raise(IOError) { reading_thread.join } + refute_nil scheduler_thread.join(5), "expected thread to terminate within 5 seconds" + + assert_predicate(i, :closed?) + ensure + scheduler_thread&.kill + scheduler_thread&.join rescue nil + reading_thread&.kill + reading_thread&.join rescue nil end - - assert_raise(IOError) { reading_thread.join } - refute_nil fs_thread.join(5), "expected thread to terminate within 5 seconds" - - assert_predicate(i, :closed?) - ensure - fs_thread&.kill - fs_thread&.join rescue nil - reading_thread&.kill - reading_thread&.join rescue nil - i&.close - o&.close end end diff --git a/test/fiber/test_io_close.rb b/test/fiber/test_io_close.rb new file mode 100644 index 0000000000..742b40841d --- /dev/null +++ b/test/fiber/test_io_close.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true +require 'test/unit' +require_relative 'scheduler' + +class TestFiberIOClose < Test::Unit::TestCase + def with_socket_pair(&block) + omit "UNIXSocket is not defined!" unless defined?(UNIXSocket) + + UNIXSocket.pair do |i, o| + if RUBY_PLATFORM =~ /mswin|mingw/ + i.nonblock = true + o.nonblock = true + end + + yield i, o + end + end + + def test_io_close_across_fibers + # omit "Interrupting a io_wait read is not supported!" if RUBY_PLATFORM =~ /mswin|mingw/ + + with_socket_pair do |i, o| + error = nil + + thread = Thread.new do + scheduler = Scheduler.new + Fiber.set_scheduler scheduler + + Fiber.schedule do + i.read + rescue => error + # Ignore. + end + + Fiber.schedule do + i.close + end + end + + thread.join + + assert_instance_of IOError, error + assert_match(/closed/, error.message) + end + end + + def test_io_close_blocking_thread + omit "Interrupting a io_wait read is not supported!" if RUBY_PLATFORM =~ /mswin|mingw/ + + with_socket_pair do |i, o| + error = nil + + reading_thread = Thread.new do + i.read + rescue => error + # Ignore. + end + + Thread.pass until reading_thread.status == 'sleep' + + thread = Thread.new do + scheduler = Scheduler.new + Fiber.set_scheduler scheduler + + Fiber.schedule do + i.close + end + end + + thread.join + reading_thread.join + + assert_instance_of IOError, error + assert_match(/closed/, error.message) + end + end + + def test_io_close_blocking_fiber + # omit "Interrupting a io_wait read is not supported!" if RUBY_PLATFORM =~ /mswin|mingw/ + + with_socket_pair do |i, o| + error = nil + + thread = Thread.new do + scheduler = Scheduler.new + Fiber.set_scheduler scheduler + + Fiber.schedule do + begin + i.read + rescue => error + # Ignore. + end + end + end + + Thread.pass until thread.status == 'sleep' + + i.close + + thread.join + + assert_instance_of IOError, error + assert_match(/closed/, error.message) + end + end +end diff --git a/test/fiber/test_ractor.rb b/test/fiber/test_ractor.rb index 3c4ccbd8e5..7dd82eda62 100644 --- a/test/fiber/test_ractor.rb +++ b/test/fiber/test_ractor.rb @@ -17,7 +17,7 @@ class TestFiberCurrentRactor < Test::Unit::TestCase Fiber.current.class end.resume end - assert_equal(Fiber, r.take) + assert_equal(Fiber, r.value) end; end end diff --git a/test/fiber/test_scheduler.rb b/test/fiber/test_scheduler.rb index 81d4581bea..7c77bd8cf0 100644 --- a/test/fiber/test_scheduler.rb +++ b/test/fiber/test_scheduler.rb @@ -94,6 +94,9 @@ class TestFiberScheduler < Test::Unit::TestCase def scheduler.kernel_sleep end + def scheduler.fiber_interrupt(_fiber, _exception) + end + thread = Thread.new do Fiber.set_scheduler scheduler end diff --git a/test/fiber/test_thread.rb b/test/fiber/test_thread.rb index 5e3cc6d0e1..0247f330d9 100644 --- a/test/fiber/test_thread.rb +++ b/test/fiber/test_thread.rb @@ -90,6 +90,47 @@ class TestFiberThread < Test::Unit::TestCase assert_equal :done, thread.value end + def test_spurious_unblock_during_thread_join + ready = Thread::Queue.new + + target_thread = Thread.new do + ready.pop + :success + end + + Thread.pass until target_thread.status == "sleep" + + result = nil + + thread = Thread.new do + scheduler = Scheduler.new + Fiber.set_scheduler scheduler + + # Create a fiber that will join a long-running thread: + joining_fiber = Fiber.schedule do + result = target_thread.value + end + + # Create another fiber that spuriously unblocks the joining fiber: + Fiber.schedule do + # This interrupts the join in joining_fiber: + scheduler.unblock(:spurious_wakeup, joining_fiber) + + # This allows the unblock to be processed: + sleep(0) + + # This allows the target thread to finish: + ready.push(:done) + end + + scheduler.run + end + + thread.join + + assert_equal :success, result + end + def test_broken_unblock thread = Thread.new do Thread.current.report_on_exception = false diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index 519184c537..c3f9c91c7d 100644 --- a/test/io/console/test_io_console.rb +++ b/test/io/console/test_io_console.rb @@ -7,6 +7,11 @@ rescue LoadError end class TestIO_Console < Test::Unit::TestCase + HOST_OS = RbConfig::CONFIG['host_os'] + private def host_os?(os) + HOST_OS =~ os + end + begin PATHS = $LOADED_FEATURES.grep(%r"/io/console(?:\.#{RbConfig::CONFIG['DLEXT']}|\.rb|/\w+\.rb)\z") {$`} rescue Encoding::CompatibilityError @@ -26,7 +31,7 @@ class TestIO_Console < Test::Unit::TestCase # But it does not occur in `make test-all > /dev/null`, so # there should be an additional factor, I guess. def set_winsize_setup - @old_ttou = trap(:TTOU, 'IGNORE') if RUBY_PLATFORM =~ /freebsd/i + @old_ttou = trap(:TTOU, 'IGNORE') if host_os?(/freebsd/) end def set_winsize_teardown @@ -367,6 +372,15 @@ defined?(PTY) and defined?(IO.console) and TestIO_Console.class_eval do w.print cc w.flush result = EnvUtil.timeout(3) {r.gets} + if result + case cc.chr + when "\C-A".."\C-_" + cc = "^" + (cc.ord | 0x40).chr + when "\C-?" + cc = "^?" + end + result.sub!(cc, "") + end assert_equal(expect, result.chomp) end @@ -378,7 +392,7 @@ defined?(PTY) and defined?(IO.console) and TestIO_Console.class_eval do # TestIO_Console#test_intr [/usr/home/chkbuild/chkbuild/tmp/build/20220304T163001Z/ruby/test/io/console/test_io_console.rb:387]: # <"25"> expected but was # <"-e:12:in `p': \e[1mexecution expired (\e[1;4mTimeout::Error\e[m\e[1m)\e[m">. - omit if /freebsd/ =~ RUBY_PLATFORM + omit if host_os?(/freebsd/) run_pty("#{<<~"begin;"}\n#{<<~'end;'}") do |r, w, _| begin; @@ -404,7 +418,7 @@ defined?(PTY) and defined?(IO.console) and TestIO_Console.class_eval do if cc = ctrl["intr"] assert_ctrl("#{cc.ord}", cc, r, w) assert_ctrl("#{cc.ord}", cc, r, w) - assert_ctrl("Interrupt", cc, r, w) unless /linux/ =~ RUBY_PLATFORM + assert_ctrl("Interrupt", cc, r, w) unless host_os?(/linux/) end if cc = ctrl["dsusp"] assert_ctrl("#{cc.ord}", cc, r, w) @@ -542,9 +556,7 @@ defined?(IO.console) and TestIO_Console.class_eval do File.open(ttyname) {|f| assert_predicate(f, :tty?)} end end -end -defined?(IO.console) and TestIO_Console.class_eval do case when Process.respond_to?(:daemon) noctty = [EnvUtil.rubybin, "-e", "Process.daemon(true)"] diff --git a/test/io/console/test_ractor.rb b/test/io/console/test_ractor.rb index b30988f47e..dff0c67eab 100644 --- a/test/io/console/test_ractor.rb +++ b/test/io/console/test_ractor.rb @@ -8,6 +8,10 @@ class TestIOConsoleInRactor < Test::Unit::TestCase path = $".find {|path| path.end_with?(ext)} assert_in_out_err(%W[-r#{path}], "#{<<~"begin;"}\n#{<<~'end;'}", ["true"], []) begin; + class Ractor + alias value take + end unless Ractor.method_defined? :value # compat with Ruby 3.4 and olders + $VERBOSE = nil r = Ractor.new do $stdout.console_mode @@ -18,17 +22,21 @@ class TestIOConsoleInRactor < Test::Unit::TestCase else true # should not success end - puts r.take + puts r.value end; assert_in_out_err(%W[-r#{path}], "#{<<~"begin;"}\n#{<<~'end;'}", ["true"], []) begin; + class Ractor + alias value take + end unless Ractor.method_defined? :value # compat with Ruby 3.4 and olders + console = IO.console $VERBOSE = nil r = Ractor.new do IO.console end - puts console.class == r.take.class + puts console.class == r.value.class end; end end if defined? Ractor diff --git a/test/io/wait/test_ractor.rb b/test/io/wait/test_ractor.rb index 800216e610..c77a29bff3 100644 --- a/test/io/wait/test_ractor.rb +++ b/test/io/wait/test_ractor.rb @@ -7,11 +7,15 @@ class TestIOWaitInRactor < Test::Unit::TestCase ext = "/io/wait.#{RbConfig::CONFIG['DLEXT']}" path = $".find {|path| path.end_with?(ext)} assert_in_out_err(%W[-r#{path}], <<-"end;", ["true"], []) + class Ractor + alias value take + end unless Ractor.method_defined? :value # compat with Ruby 3.4 and olders + $VERBOSE = nil r = Ractor.new do $stdout.equal?($stdout.wait_writable) end - puts r.take + puts r.value end; end end if defined? Ractor diff --git a/test/json/json_encoding_test.rb b/test/json/json_encoding_test.rb index afffd8976a..873e96fddd 100644 --- a/test/json/json_encoding_test.rb +++ b/test/json/json_encoding_test.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative 'test_helper' class JSONEncodingTest < Test::Unit::TestCase @@ -37,7 +38,7 @@ class JSONEncodingTest < Test::Unit::TestCase assert_equal '"\u001f"', 0x1f.chr.to_json assert_equal '" "', ' '.to_json assert_equal "\"#{0x7f.chr}\"", 0x7f.chr.to_json - utf8 = [ "© ≠ €! \01" ] + utf8 = ["© ≠ €! \01"] json = '["© ≠ €! \u0001"]' assert_equal json, utf8.to_json(ascii_only: false) assert_equal utf8, parse(json) @@ -78,10 +79,10 @@ class JSONEncodingTest < Test::Unit::TestCase json = '"\u%04x"' % i i = i.chr assert_equal i, parse(json)[0] - if i == ?\b + if i == "\b" generated = generate(i) - assert '"\b"' == generated || '"\10"' == generated - elsif [?\n, ?\r, ?\t, ?\f].include?(i) + assert ['"\b"', '"\10"'].include?(generated) + elsif ["\n", "\r", "\t", "\f"].include?(i) assert_equal i.dump, generate(i) elsif i.chr < 0x20.chr assert_equal json, generate(i) @@ -92,4 +93,179 @@ class JSONEncodingTest < Test::Unit::TestCase end assert_equal "\302\200", parse('"\u0080"') end + + def test_deeply_nested_structures + # Test for deeply nested arrays + nesting_level = 100 + deeply_nested = [] + current = deeply_nested + + (nesting_level - 1).times do + current << [] + current = current[0] + end + + json = generate(deeply_nested) + assert_equal deeply_nested, parse(json) + + # Test for deeply nested objects/hashes + deeply_nested_hash = {} + current_hash = deeply_nested_hash + + (nesting_level - 1).times do |i| + current_hash["key#{i}"] = {} + current_hash = current_hash["key#{i}"] + end + + json = generate(deeply_nested_hash) + assert_equal deeply_nested_hash, parse(json) + end + + def test_very_large_json_strings + # Create a large array with repeated elements + large_array = Array.new(10_000) { |i| "item#{i}" } + + json = generate(large_array) + parsed = parse(json) + + assert_equal large_array.size, parsed.size + assert_equal large_array.first, parsed.first + assert_equal large_array.last, parsed.last + + # Create a large hash + large_hash = {} + 10_000.times { |i| large_hash["key#{i}"] = "value#{i}" } + + json = generate(large_hash) + parsed = parse(json) + + assert_equal large_hash.size, parsed.size + assert_equal large_hash["key0"], parsed["key0"] + assert_equal large_hash["key9999"], parsed["key9999"] + end + + def test_invalid_utf8_sequences + # Create strings with invalid UTF-8 sequences + invalid_utf8 = "\xFF\xFF" + + # Test that generating JSON with invalid UTF-8 raises an error + # Different JSON implementations may handle this differently, + # so we'll check if any exception is raised + begin + generate(invalid_utf8) + raise "Expected an exception when generating JSON with invalid UTF8" + rescue StandardError => e + assert true + assert_match(%r{source sequence is illegal/malformed utf-8}, e.message) + end + end + + def test_surrogate_pair_handling + # Test valid surrogate pairs + assert_equal "\u{10000}", parse('"\ud800\udc00"') + assert_equal "\u{10FFFF}", parse('"\udbff\udfff"') + + # The existing test already checks for orphaned high surrogate + assert_raise(JSON::ParserError) { parse('"\ud800"') } + + # Test generating surrogate pairs + utf8_string = "\u{10437}" + generated = generate(utf8_string, ascii_only: true) + assert_match(/\\ud801\\udc37/, generated) + end + + def test_json_escaping_edge_cases + # Test escaping forward slashes + assert_equal "/", parse('"\/"') + + # Test escaping backslashes + assert_equal "\\", parse('"\\\\"') + + # Test escaping quotes + assert_equal '"', parse('"\\""') + + # Multiple escapes in sequence - different JSON parsers might handle escaped forward slashes differently + # Some parsers preserve the escaping, others don't + escaped_result = parse('"\\\\\\"\\/"') + assert_match(/\\"/, escaped_result) + assert_match(%r{/}, escaped_result) + + # Generate string with all special characters + special_chars = "\b\f\n\r\t\"\\" + escaped_json = generate(special_chars) + assert_equal special_chars, parse(escaped_json) + end + + def test_empty_objects_and_arrays + # Test empty objects with different encodings + assert_equal({}, parse('{}')) + assert_equal({}, parse('{}'.encode(Encoding::UTF_16BE))) + assert_equal({}, parse('{}'.encode(Encoding::UTF_16LE))) + assert_equal({}, parse('{}'.encode(Encoding::UTF_32BE))) + assert_equal({}, parse('{}'.encode(Encoding::UTF_32LE))) + + # Test empty arrays with different encodings + assert_equal([], parse('[]')) + assert_equal([], parse('[]'.encode(Encoding::UTF_16BE))) + assert_equal([], parse('[]'.encode(Encoding::UTF_16LE))) + assert_equal([], parse('[]'.encode(Encoding::UTF_32BE))) + assert_equal([], parse('[]'.encode(Encoding::UTF_32LE))) + + # Test generating empty objects and arrays + assert_equal '{}', generate({}) + assert_equal '[]', generate([]) + end + + def test_null_character_handling + # Test parsing null character + assert_equal "\u0000", parse('"\u0000"') + + # Test generating null character + string_with_null = "\u0000" + generated = generate(string_with_null) + assert_equal '"\u0000"', generated + + # Test null characters in middle of string + mixed_string = "before\u0000after" + generated = generate(mixed_string) + assert_equal mixed_string, parse(generated) + end + + def test_whitespace_handling + # Test parsing with various whitespace patterns + assert_equal({}, parse(' { } ')) + assert_equal({}, parse("{\r\n}")) + assert_equal([], parse(" [ \n ] ")) + assert_equal(["a", "b"], parse(" [ \n\"a\",\r\n \"b\"\n ] ")) + assert_equal({ "a" => "b" }, parse(" { \n\"a\" \r\n: \t\"b\"\n } ")) + + # Test with excessive whitespace + excessive_whitespace = " \n\r\t" * 10 + "{}" + " \n\r\t" * 10 + assert_equal({}, parse(excessive_whitespace)) + + # Mixed whitespace in keys and values + mixed_json = '{"a \n b":"c \r\n d"}' + assert_equal({ "a \n b" => "c \r\n d" }, parse(mixed_json)) + end + + def test_control_character_handling + # Test all control characters (U+0000 to U+001F) + (0..0x1F).each do |i| + # Skip already tested ones + next if [0x08, 0x0A, 0x0D, 0x0C, 0x09].include?(i) + + control_char = i.chr('UTF-8') + escaped_json = '"' + "\\u%04x" % i + '"' + assert_equal control_char, parse(escaped_json) + + # Check that the character is properly escaped when generating + assert_match(/\\u00[0-1][0-9a-f]/, generate(control_char)) + end + + # Test string with multiple control characters + control_str = "\u0001\u0002\u0003\u0004" + generated = generate(control_str) + assert_equal control_str, parse(generated) + assert_match(/\\u0001\\u0002\\u0003\\u0004/, generated) + end end diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 55a3065ae5..914b3f4ed0 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -122,6 +122,22 @@ class JSONGeneratorTest < Test::Unit::TestCase assert_equal '666', pretty_generate(666) end + def test_generate_pretty_custom + state = State.new(:space_before => "", :space => "", :indent => "", :object_nl => "\n\n", :array_nl => "") + json = pretty_generate({1=>{}, 2=>['a','b'], 3=>4}, state) + assert_equal(<<~'JSON'.chomp, json) + { + + "1":{}, + + "2":["a","b"], + + "3":4 + + } + JSON + end + def test_generate_custom state = State.new(:space_before => " ", :space => " ", :indent => "", :object_nl => "\n", :array_nl => "") json = generate({1=>{2=>3,4=>[5,6]}}, state) @@ -779,6 +795,11 @@ class JSONGeneratorTest < Test::Unit::TestCase expecteds << "1746861937.7842371" end + if RUBY_ENGINE == "ruby" + values << -2.2471348024634545e-08 << -2.2471348024634545e-09 << -2.2471348024634545e-10 + expecteds << "-0.000000022471348024634545" << "-0.0000000022471348024634545" << "-2.2471348024634546e-10" + end + values.zip(expecteds).each do |value, expected| assert_equal expected, value.to_json end diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index befc80c958..106492e1c4 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -331,6 +331,15 @@ class JSONParserTest < Test::Unit::TestCase assert_equal orig, parse(json5) end + def test_parse_duplicate_key + expected = {"a" => 2} + assert_equal expected, parse('{"a": 1, "a": 2}', allow_duplicate_key: true) + assert_raise(ParserError) { parse('{"a": 1, "a": 2}', allow_duplicate_key: false) } + assert_deprecated_warning(/duplicate keys/) do + assert_equal expected, parse('{"a": 1, "a": 2}') + end + end + def test_some_wrong_inputs assert_raise(ParserError) { parse('[] bla') } assert_raise(ParserError) { parse('[] 1') } @@ -460,6 +469,90 @@ class JSONParserTest < Test::Unit::TestCase json = '["\/"]' data = [ '/' ] assert_equal data, parse(json) + + data = ['"""""""""""""""""""""""""'] + json = '["\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\""]' + assert_equal data, parse(json) + + data = '["This is a "test" of the emergency broadcast system."]' + json = "\"[\\\"This is a \\\"test\\\" of the emergency broadcast system.\\\"]\"" + assert_equal data, parse(json) + + data = '\tThis is a test of the emergency broadcast system.' + json = "\"\\\\tThis is a test of the emergency broadcast system.\"" + assert_equal data, parse(json) + + data = 'This\tis a test of the emergency broadcast system.' + json = "\"This\\\\tis a test of the emergency broadcast system.\"" + assert_equal data, parse(json) + + data = 'This is\ta test of the emergency broadcast system.' + json = "\"This is\\\\ta test of the emergency broadcast system.\"" + assert_equal data, parse(json) + + data = 'This is a test of the emergency broadcast\tsystem.' + json = "\"This is a test of the emergency broadcast\\\\tsystem.\"" + assert_equal data, parse(json) + + data = 'This is a test of the emergency broadcast\tsystem.\n' + json = "\"This is a test of the emergency broadcast\\\\tsystem.\\\\n\"" + assert_equal data, parse(json) + + data = '"' * 15 + json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\"" + assert_equal data, parse(json) + + data = "\"\"\"\"\"\"\"\"\"\"\"\"\"\"a" + json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"a\"" + assert_equal data, parse(json) + + data = "\u0001\u0001\u0001\u0001" + json = "\"\\u0001\\u0001\\u0001\\u0001\"" + assert_equal data, parse(json) + + data = "\u0001a\u0001a\u0001a\u0001a" + json = "\"\\u0001a\\u0001a\\u0001a\\u0001a\"" + assert_equal data, parse(json) + + data = "\u0001aa\u0001aa" + json = "\"\\u0001aa\\u0001aa\"" + assert_equal data, parse(json) + + data = "\u0001aa\u0001aa\u0001aa" + json = "\"\\u0001aa\\u0001aa\\u0001aa\"" + assert_equal data, parse(json) + + data = "\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa" + json = "\"\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\"" + assert_equal data, parse(json) + + data = "\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002" + json = "\"\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\"" + assert_equal data, parse(json) + + data = "ab\u0002c" + json = "\"ab\\u0002c\"" + assert_equal data, parse(json) + + data = "ab\u0002cab\u0002cab\u0002cab\u0002c" + json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002c\"" + assert_equal data, parse(json) + + data = "ab\u0002cab\u0002cab\u0002cab\u0002cab\u0002cab\u0002c" + json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002c\"" + assert_equal data, parse(json) + + data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f" + json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\"" + assert_equal data, parse(json) + + data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f\b" + json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\"" + assert_equal data, parse(json) + + data = "a\n\t\f\b\n\t\f\b\n\t\f\b\n\t" + json = "\"a\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\"" + assert_equal data, parse(json) end class SubArray < Array diff --git a/test/json/ractor_test.rb b/test/json/ractor_test.rb index f857c9a8bf..dda34c64c0 100644 --- a/test/json/ractor_test.rb +++ b/test/json/ractor_test.rb @@ -8,6 +8,16 @@ rescue LoadError end class JSONInRactorTest < Test::Unit::TestCase + unless Ractor.method_defined?(:value) + module RactorBackport + refine Ractor do + alias_method :value, :take + end + end + + using RactorBackport + end + def test_generate pid = fork do r = Ractor.new do @@ -25,7 +35,7 @@ class JSONInRactorTest < Test::Unit::TestCase end expected_json = JSON.parse('{"a":2,"b":3.141,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},' + '"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}') - actual_json = r.take + actual_json = r.value if expected_json == actual_json exit 0 diff --git a/test/net/http/test_http.rb b/test/net/http/test_http.rb index c9a27d87cb..366b4cd12c 100644 --- a/test/net/http/test_http.rb +++ b/test/net/http/test_http.rb @@ -494,12 +494,10 @@ module TestNetHTTP_version_1_1_methods def test_s_post url = "http://#{config('host')}:#{config('port')}/?q=a" - res = assert_warning(/Content-Type did not set/) do - Net::HTTP.post( - URI.parse(url), - "a=x") - end - assert_equal "application/x-www-form-urlencoded", res["Content-Type"] + res = Net::HTTP.post( + URI.parse(url), + "a=x") + assert_equal "application/octet-stream", res["Content-Type"] assert_equal "a=x", res.body assert_equal url, res["X-request-uri"] @@ -570,9 +568,7 @@ module TestNetHTTP_version_1_1_methods th = Thread.new do err = !windows? ? Net::WriteTimeout : Net::ReadTimeout assert_raise(err) do - assert_warning(/Content-Type did not set/) do - conn.post('/', "a"*50_000_000) - end + conn.post('/', "a"*50_000_000) end end assert th.join(EnvUtil.apply_timeout_scale(10)) diff --git a/test/net/http/utils.rb b/test/net/http/utils.rb index b41341d0a0..067cca02e3 100644 --- a/test/net/http/utils.rb +++ b/test/net/http/utils.rb @@ -71,6 +71,11 @@ module TestNetHTTPUtils socket.write "HTTP/1.1 100 Continue\r\n\r\n" end + # Set default Content-Type if not provided + if !headers['Content-Type'] && (method == 'POST' || method == 'PUT' || method == 'PATCH') + headers['Content-Type'] = 'application/octet-stream' + end + req = Request.new(method, path, headers, socket) if @procs.key?(req.path) || @procs.key?("#{req.path}/") proc = @procs[req.path] || @procs["#{req.path}/"] @@ -306,16 +311,18 @@ module TestNetHTTPUtils scheme = headers['X-Request-Scheme'] || 'http' host = @config['host'] port = socket.addr[1] - charset = parse_content_type(headers['Content-Type'])[1] + content_type = headers['Content-Type'] || 'application/octet-stream' + charset = parse_content_type(content_type)[1] path = "#{scheme}://#{host}:#{port}#{path}" path = path.encode(charset) if charset - response = "HTTP/1.1 200 OK\r\nContent-Type: #{headers['Content-Type']}\r\nContent-Length: #{body.bytesize}\r\nX-request-uri: #{path}\r\n\r\n#{body}" + response = "HTTP/1.1 200 OK\r\nContent-Type: #{content_type}\r\nContent-Length: #{body.bytesize}\r\nX-request-uri: #{path}\r\n\r\n#{body}" socket.print(response) end def handle_patch(path, headers, socket) body = socket.read(headers['Content-Length'].to_i) - response = "HTTP/1.1 200 OK\r\nContent-Type: #{headers['Content-Type']}\r\nContent-Length: #{body.bytesize}\r\n\r\n#{body}" + content_type = headers['Content-Type'] || 'application/octet-stream' + response = "HTTP/1.1 200 OK\r\nContent-Type: #{content_type}\r\nContent-Length: #{body.bytesize}\r\n\r\n#{body}" socket.print(response) end diff --git a/test/objspace/test_ractor.rb b/test/objspace/test_ractor.rb index 4901eeae2e..eb3044cda3 100644 --- a/test/objspace/test_ractor.rb +++ b/test/objspace/test_ractor.rb @@ -5,13 +5,51 @@ class TestObjSpaceRactor < Test::Unit::TestCase assert_ractor(<<~RUBY, require: 'objspace') ObjectSpace.trace_object_allocations do r = Ractor.new do - obj = 'a' * 1024 - Ractor.yield obj + _obj = 'a' * 1024 end - r.take - r.take + r.join end RUBY end + + def test_undefine_finalizer + assert_ractor(<<~'RUBY', require: 'objspace') + def fin + ->(id) { } + end + ractors = 5.times.map do + Ractor.new do + 10_000.times do + o = Object.new + ObjectSpace.define_finalizer(o, fin) + ObjectSpace.undefine_finalizer(o) + end + end + end + + ractors.each(&:join) + RUBY + end + + def test_copy_finalizer + assert_ractor(<<~'RUBY', require: 'objspace') + def fin + ->(id) { } + end + OBJ = Object.new + ObjectSpace.define_finalizer(OBJ, fin) + OBJ.freeze + + ractors = 5.times.map do + Ractor.new do + 10_000.times do + OBJ.clone + end + end + end + + ractors.each(&:join) + RUBY + end end diff --git a/test/openssl/test_asn1.rb b/test/openssl/test_asn1.rb index b562721d1b..1b933a78bf 100644 --- a/test/openssl/test_asn1.rb +++ b/test/openssl/test_asn1.rb @@ -411,13 +411,16 @@ class OpenSSL::TestASN1 < OpenSSL::TestCase def test_utctime encode_decode_test B(%w{ 17 0D }) + "160908234339Z".b, OpenSSL::ASN1::UTCTime.new(Time.utc(2016, 9, 8, 23, 43, 39)) - begin - # possible range of UTCTime is 1969-2068 currently - encode_decode_test B(%w{ 17 0D }) + "690908234339Z".b, - OpenSSL::ASN1::UTCTime.new(Time.utc(1969, 9, 8, 23, 43, 39)) - rescue OpenSSL::ASN1::ASN1Error - pend "No negative time_t support?" - end + + # 1950-2049 range is assumed to match RFC 5280's expectation + encode_decode_test B(%w{ 17 0D }) + "490908234339Z".b, + OpenSSL::ASN1::UTCTime.new(Time.utc(2049, 9, 8, 23, 43, 39)) + encode_decode_test B(%w{ 17 0D }) + "500908234339Z".b, + OpenSSL::ASN1::UTCTime.new(Time.utc(1950, 9, 8, 23, 43, 39)) + assert_raise(OpenSSL::ASN1::ASN1Error) { + OpenSSL::ASN1::UTCTime.new(Time.new(2049, 12, 31, 23, 0, 0, "-04:00")).to_der + } + # not implemented # decode_test B(%w{ 17 11 }) + "500908234339+0930".b, # OpenSSL::ASN1::UTCTime.new(Time.new(1950, 9, 8, 23, 43, 39, "+09:30")) diff --git a/test/openssl/test_bn.rb b/test/openssl/test_bn.rb index 5b68544574..f663102d45 100644 --- a/test/openssl/test_bn.rb +++ b/test/openssl/test_bn.rb @@ -345,29 +345,37 @@ class OpenSSL::TestBN < OpenSSL::TestCase assert_equal(4, e.get_flags(OpenSSL::BN::CONSTTIME)) end - if respond_to?(:ractor) + if defined?(Ractor) && respond_to?(:ractor) + unless Ractor.method_defined?(:value) # Ruby 3.4 or earlier + using Module.new { + refine Ractor do + alias value take + end + } + end + ractor def test_ractor - assert_equal(@e1, Ractor.new { OpenSSL::BN.new("999") }.take) - assert_equal(@e3, Ractor.new { OpenSSL::BN.new("\a\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 2) }.take) - assert_equal("999", Ractor.new(@e1) { |e1| e1.to_s }.take) - assert_equal("07FFFFFFFFFFFFFFFFFFFFFFFFFF", Ractor.new(@e3) { |e3| e3.to_s(16) }.take) - assert_equal(2**107-1, Ractor.new(@e3) { _1.to_i }.take) - assert_equal([1000, -999], Ractor.new(@e2) { _1.coerce(1000) }.take) - assert_equal(false, Ractor.new { 1.to_bn.zero? }.take) - assert_equal(true, Ractor.new { 1.to_bn.one? }.take) - assert_equal(true, Ractor.new(@e2) { _1.negative? }.take) - assert_equal("-03E7", Ractor.new(@e2) { _1.to_s(16) }.take) - assert_equal(2**107-1, Ractor.new(@e3) { _1.to_i }.take) - assert_equal([1000, -999], Ractor.new(@e2) { _1.coerce(1000) }.take) - assert_equal(true, Ractor.new { 0.to_bn.zero? }.take) - assert_equal(true, Ractor.new { 1.to_bn.one? }.take ) - assert_equal(false,Ractor.new { 2.to_bn.odd? }.take) - assert_equal(true, Ractor.new(@e2) { _1.negative? }.take) - assert_include(128..255, Ractor.new { OpenSSL::BN.rand(8)}.take) - assert_include(0...2**32, Ractor.new { OpenSSL::BN.generate_prime(32) }.take) + assert_equal(@e1, Ractor.new { OpenSSL::BN.new("999") }.value) + assert_equal(@e3, Ractor.new { OpenSSL::BN.new("\a\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 2) }.value) + assert_equal("999", Ractor.new(@e1) { |e1| e1.to_s }.value) + assert_equal("07FFFFFFFFFFFFFFFFFFFFFFFFFF", Ractor.new(@e3) { |e3| e3.to_s(16) }.value) + assert_equal(2**107-1, Ractor.new(@e3) { _1.to_i }.value) + assert_equal([1000, -999], Ractor.new(@e2) { _1.coerce(1000) }.value) + assert_equal(false, Ractor.new { 1.to_bn.zero? }.value) + assert_equal(true, Ractor.new { 1.to_bn.one? }.value) + assert_equal(true, Ractor.new(@e2) { _1.negative? }.value) + assert_equal("-03E7", Ractor.new(@e2) { _1.to_s(16) }.value) + assert_equal(2**107-1, Ractor.new(@e3) { _1.to_i }.value) + assert_equal([1000, -999], Ractor.new(@e2) { _1.coerce(1000) }.value) + assert_equal(true, Ractor.new { 0.to_bn.zero? }.value) + assert_equal(true, Ractor.new { 1.to_bn.one? }.value ) + assert_equal(false,Ractor.new { 2.to_bn.odd? }.value) + assert_equal(true, Ractor.new(@e2) { _1.negative? }.value) + assert_include(128..255, Ractor.new { OpenSSL::BN.rand(8)}.value) + assert_include(0...2**32, Ractor.new { OpenSSL::BN.generate_prime(32) }.value) if !aws_lc? # AWS-LC does not support BN::CONSTTIME. - assert_equal(0, Ractor.new { OpenSSL::BN.new(999).get_flags(OpenSSL::BN::CONSTTIME) }.take) + assert_equal(0, Ractor.new { OpenSSL::BN.new(999).get_flags(OpenSSL::BN::CONSTTIME) }.value) end # test if shareable when frozen assert Ractor.shareable?(@e1.freeze) diff --git a/test/openssl/test_pkcs7.rb b/test/openssl/test_pkcs7.rb index e8973f73fe..5a52f4ce5f 100644 --- a/test/openssl/test_pkcs7.rb +++ b/test/openssl/test_pkcs7.rb @@ -355,8 +355,6 @@ END end def test_decode_ber_constructed_string - pend "AWS-LC ASN.1 parsers has no current support for parsing indefinite BER constructed strings" if aws_lc? - p7 = OpenSSL::PKCS7.encrypt([@ee1_cert], "content", "aes-128-cbc") # Make an equivalent BER to p7.to_der. Here we convert the encryptedContent diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index 8444cfdcda..71f5da81d1 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -8,16 +8,7 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase assert_instance_of OpenSSL::PKey::RSA, rsa assert_equal "rsaEncryption", rsa.oid assert_match %r{oid=rsaEncryption}, rsa.inspect - end - - def test_generic_oid_inspect_x25519 - omit_on_fips - - # X25519 private key - x25519 = OpenSSL::PKey.generate_key("X25519") - assert_instance_of OpenSSL::PKey::PKey, x25519 - assert_equal "X25519", x25519.oid - assert_match %r{oid=X25519}, x25519.inspect + assert_match %r{type_name=RSA}, rsa.inspect if openssl?(3, 0, 0) end def test_s_generate_parameters @@ -152,6 +143,8 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase alice = OpenSSL::PKey.read(alice_pem) bob = OpenSSL::PKey.read(bob_pem) assert_instance_of OpenSSL::PKey::PKey, alice + assert_equal "X25519", alice.oid + assert_match %r{oid=X25519}, alice.inspect assert_equal alice_pem, alice.private_to_pem assert_equal bob_pem, bob.public_to_pem assert_equal [shared_secret].pack("H*"), alice.derive(bob) @@ -168,6 +161,25 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase bob.raw_public_key.unpack1("H*") end + def test_ml_dsa + # AWS-LC also supports ML-DSA, but it's implemented in a different way + return unless openssl?(3, 5, 0) + + pkey = OpenSSL::PKey.generate_key("ML-DSA-44") + assert_match(/type_name=ML-DSA-44/, pkey.inspect) + sig = pkey.sign(nil, "data") + assert_equal(2420, sig.bytesize) + assert_equal(true, pkey.verify(nil, sig, "data")) + + pub2 = OpenSSL::PKey.read(pkey.public_to_der) + assert_equal(true, pub2.verify(nil, sig, "data")) + + raw_public_key = pkey.raw_public_key + assert_equal(1312, raw_public_key.bytesize) + pub3 = OpenSSL::PKey.new_raw_public_key("ML-DSA-44", raw_public_key) + assert_equal(true, pub3.verify(nil, sig, "data")) + end + def test_raw_initialize_errors assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("foo123", "xxx") } assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("ED25519", "xxx") } diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index 4642063f45..febac06156 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -1764,33 +1764,28 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end end - if !aws_lc? # AWS-LC does not support DHE ciphersuites. - # DHE - # TODO: SSL_CTX_set1_groups() is required for testing this with TLS 1.3 - ctx_proc2 = proc { |ctx| - ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION - ctx.ciphers = "EDH" - ctx.tmp_dh = Fixtures.pkey("dh-1") - } - start_server(ctx_proc: ctx_proc2) do |port| + # DHE + # OpenSSL 3.0 added support for named FFDHE groups in TLS 1.3 + # LibreSSL does not support named FFDHE groups currently + # AWS-LC does not support DHE ciphersuites + if openssl?(3, 0, 0) + start_server do |port| ctx = OpenSSL::SSL::SSLContext.new - ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION - ctx.ciphers = "EDH" + ctx.groups = "ffdhe3072" server_connect(port, ctx) { |ssl| assert_instance_of OpenSSL::PKey::DH, ssl.tmp_key + assert_equal 3072, ssl.tmp_key.p.num_bits + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } end end # ECDHE ctx_proc3 = proc { |ctx| - ctx.ciphers = "DEFAULT:!kRSA:!kEDH" - ctx.ecdh_curves = "P-256" + ctx.groups = "P-256" } start_server(ctx_proc: ctx_proc3) do |port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.ciphers = "DEFAULT:!kRSA:!kEDH" - server_connect(port, ctx) { |ssl| + server_connect(port) { |ssl| assert_instance_of OpenSSL::PKey::EC, ssl.tmp_key ssl.puts "abc"; assert_equal "abc\n", ssl.gets } @@ -1968,6 +1963,84 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ) { ssl_ctx.ciphers = 'BOGUS' } end + def test_sigalgs + omit "SSL_CTX_set1_sigalgs_list() not supported" if libressl? + + svr_exts = [ + ["keyUsage", "keyEncipherment,digitalSignature", true], + ["subjectAltName", "DNS:localhost", false], + ] + ecdsa_key = Fixtures.pkey("p256") + ecdsa_cert = issue_cert(@svr, ecdsa_key, 10, svr_exts, @ca_cert, @ca_key) + + ctx_proc = -> ctx { + # Unset values set by start_server + ctx.cert = ctx.key = ctx.extra_chain_cert = nil + ctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) # RSA + ctx.add_certificate(ecdsa_cert, ecdsa_key, [@ca_cert]) # ECDSA + } + start_server(ctx_proc: ctx_proc) do |port| + ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.sigalgs = "rsa_pss_rsae_sha256" + server_connect(port, ctx1) { |ssl| + assert_kind_of(OpenSSL::PKey::RSA, ssl.peer_cert.public_key) + ssl.puts("abc"); ssl.gets + } + + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.sigalgs = "ed25519:ecdsa_secp256r1_sha256" + server_connect(port, ctx2) { |ssl| + assert_kind_of(OpenSSL::PKey::EC, ssl.peer_cert.public_key) + ssl.puts("abc"); ssl.gets + } + end + + # Frozen + ssl_ctx = OpenSSL::SSL::SSLContext.new + ssl_ctx.freeze + assert_raise(FrozenError) { ssl_ctx.sigalgs = "ECDSA+SHA256:RSA+SHA256" } + + # Bogus + ssl_ctx = OpenSSL::SSL::SSLContext.new + assert_raise(TypeError) { ssl_ctx.sigalgs = nil } + assert_raise(OpenSSL::SSL::SSLError) { ssl_ctx.sigalgs = "BOGUS" } + end + + def test_client_sigalgs + omit "SSL_CTX_set1_client_sigalgs_list() not supported" if libressl? || aws_lc? + + cli_exts = [ + ["keyUsage", "keyEncipherment,digitalSignature", true], + ["subjectAltName", "DNS:localhost", false], + ] + ecdsa_key = Fixtures.pkey("p256") + ecdsa_cert = issue_cert(@cli, ecdsa_key, 10, cli_exts, @ca_cert, @ca_key) + + ctx_proc = -> ctx { + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT + ctx.cert_store = store + ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT + ctx.client_sigalgs = "ECDSA+SHA256" + } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| + ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.add_certificate(@cli_cert, @cli_key) # RSA + assert_handshake_error { + server_connect(port, ctx1) { |ssl| + ssl.puts("abc"); ssl.gets + } + } + + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.add_certificate(ecdsa_cert, ecdsa_key) # ECDSA + server_connect(port, ctx2) { |ssl| + ssl.puts("abc"); ssl.gets + } + end + end + def test_connect_works_when_setting_dh_callback_to_nil omit "AWS-LC does not support DHE ciphersuites" if aws_lc? @@ -2001,17 +2074,17 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end end - def test_ecdh_curves_tls12 + def test_set_groups_tls12 ctx_proc = -> ctx { # Enable both ECDHE (~ TLS 1.2) cipher suites and TLS 1.3 ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "kEECDH" - ctx.ecdh_curves = "P-384:P-521" + ctx.groups = "P-384:P-521" } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| # Test 1: Client=P-256:P-384, Server=P-384:P-521 --> P-384 ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-256:P-384" + ctx.groups = "P-256:P-384" server_connect(port, ctx) { |ssl| cs = ssl.cipher[0] assert_match (/\AECDH/), cs @@ -2021,29 +2094,36 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase # Test 2: Client=P-256, Server=P-521:P-384 --> Fail ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-256" + ctx.groups = "P-256" assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) { } } # Test 3: Client=P-521:P-384, Server=P-521:P-384 --> P-521 ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-521:P-384" + ctx.groups = "P-521:P-384" server_connect(port, ctx) { |ssl| assert_equal "secp521r1", ssl.tmp_key.group.curve_name ssl.puts "abc"; assert_equal "abc\n", ssl.gets } + + # Test 4: #ecdh_curves= alias + ctx = OpenSSL::SSL::SSLContext.new + ctx.ecdh_curves = "P-256:P-384" + server_connect(port, ctx) { |ssl| + assert_equal "secp384r1", ssl.tmp_key.group.curve_name + } end end - def test_ecdh_curves_tls13 + def test_set_groups_tls13 ctx_proc = -> ctx { # Assume TLS 1.3 is enabled and chosen by default - ctx.ecdh_curves = "P-384:P-521" + ctx.groups = "P-384:P-521" } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-256:P-384" # disable P-521 + ctx.groups = "P-256:P-384" # disable P-521 server_connect(port, ctx) { |ssl| assert_equal "TLSv1.3", ssl.ssl_version diff --git a/test/optparse/test_optparse.rb b/test/optparse/test_optparse.rb index d50203bb63..ff334009a6 100644 --- a/test/optparse/test_optparse.rb +++ b/test/optparse/test_optparse.rb @@ -216,4 +216,16 @@ class TestOptionParser < Test::Unit::TestCase end end end + + def test_program_name + program = $0 + $0 = "rdbg3.5" + assert_equal "rdbg3.5", OptionParser.new.program_name + RbConfig::CONFIG["EXECUTABLE_EXTS"]&.split(" ") do |ext| + $0 = "rdbg3.5" + ext + assert_equal "rdbg3.5", OptionParser.new.program_name + end + ensure + $0 = program + end end diff --git a/test/optparse/test_switch.rb b/test/optparse/test_switch.rb new file mode 100644 index 0000000000..b06f4e310b --- /dev/null +++ b/test/optparse/test_switch.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: false + +require 'test/unit' +require 'optparse' + + +class TestOptionParserSwitch < Test::Unit::TestCase + + def setup + @parser = OptionParser.new + end + + def assert_invalidarg_error(msg, &block) + exc = assert_raise(OptionParser::InvalidArgument) do + yield + end + assert_equal "invalid argument: #{msg}", exc.message + end + + def test_make_switch__enum_array + p = @parser + p.on("--enum=", ["aa", "bb", "cc"]) + p.permute(["--enum=bb"], into: (opts={})) + assert_equal({:enum=>"bb"}, opts) + assert_invalidarg_error("--enum=dd") do + p.permute(["--enum=dd"], into: (opts={})) + end + end + + def test_make_switch__enum_hash + p = @parser + p.on("--hash=", {"aa"=>"AA", "bb"=>"BB"}) + p.permute(["--hash=bb"], into: (opts={})) + assert_equal({:hash=>"BB"}, opts) + assert_invalidarg_error("--hash=dd") do + p.permute(["--hash=dd"], into: (opts={})) + end + end + + def test_make_switch__enum_set + p = @parser + p.on("--set=", Set.new(["aa", "bb", "cc"])) + p.permute(["--set=bb"], into: (opts={})) + assert_equal({:set=>"bb"}, opts) + assert_invalidarg_error("--set=dd") do + p.permute(["--set=dd"], into: (opts={})) + end + end + +end diff --git a/test/pathname/test_ractor.rb b/test/pathname/test_ractor.rb index 3d7b63deed..f06b7501f3 100644 --- a/test/pathname/test_ractor.rb +++ b/test/pathname/test_ractor.rb @@ -9,14 +9,17 @@ class TestPathnameRactor < Test::Unit::TestCase def test_ractor_shareable assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + class Ractor + alias value take + end unless Ractor.method_defined? :value # compat with Ruby 3.4 and olders + begin; $VERBOSE = nil require "pathname" r = Ractor.new Pathname("a") do |x| x.join(Pathname("b"), Pathname("c")) end - assert_equal(Pathname("a/b/c"), r.take) + assert_equal(Pathname("a/b/c"), r.value) end; end end - diff --git a/test/prism/errors/command_calls_31.txt b/test/prism/errors/command_calls_31.txt new file mode 100644 index 0000000000..e662b25444 --- /dev/null +++ b/test/prism/errors/command_calls_31.txt @@ -0,0 +1,17 @@ +true && not true + ^~~~ expected a `(` after `not` + ^~~~ unexpected 'true', expecting end-of-input + +true || not true + ^~~~ expected a `(` after `not` + ^~~~ unexpected 'true', expecting end-of-input + +true && not (true) + ^ expected a `(` immediately after `not` + ^ unexpected '(', expecting end-of-input + +true && not +true +^~~~ expected a `(` after `not` +^~~~ unexpected 'true', expecting end-of-input + diff --git a/test/prism/errors/pattern_arithmetic_expressions.txt b/test/prism/errors/pattern_arithmetic_expressions.txt new file mode 100644 index 0000000000..cfb3650531 --- /dev/null +++ b/test/prism/errors/pattern_arithmetic_expressions.txt @@ -0,0 +1,3 @@ +case 1; in -1**2; end + ^~~~~ expected a pattern expression after the `in` keyword + diff --git a/test/prism/errors/pattern_match_implicit_rest.txt b/test/prism/errors/pattern_match_implicit_rest.txt new file mode 100644 index 0000000000..8602c0add0 --- /dev/null +++ b/test/prism/errors/pattern_match_implicit_rest.txt @@ -0,0 +1,3 @@ +a=>b, *, + ^ expected a pattern expression after `,` + diff --git a/test/prism/fixtures/strings.txt b/test/prism/fixtures/strings.txt index 0787152786..1419f975b7 100644 --- a/test/prism/fixtures/strings.txt +++ b/test/prism/fixtures/strings.txt @@ -99,6 +99,34 @@ bar) d ] +%w[ + foo\nbar baz\n\n\ + bat\n\\\n\foo +] + +%W[ + foo\nbar baz\n\n\ + bat\n\\\n\foo +] + +%w[foo\ + bar + baz\\ + bat + 1\n + 2 + 3\\n +] + +%W[foo\ + bar + baz\\ + bat + 1\n + 2 + 3\\n +] + %W[f\u{006f 006f}] %W[a b#{c}d e] @@ -146,6 +174,10 @@ baz %Q{abc} +%Q(\«) + +%q(\«) + %^#$^# %@#@# diff --git a/test/prism/lex_test.rb b/test/prism/lex_test.rb index 2786c45a22..d34c3d9dd3 100644 --- a/test/prism/lex_test.rb +++ b/test/prism/lex_test.rb @@ -17,7 +17,7 @@ module Prism "spanning_heredoc.txt", "spanning_heredoc_newlines.txt", # Prism emits a single :on_tstring_content in <<- style heredocs when there - # is a line continuation preceeded by escaped backslashes. It should emit two, same + # is a line continuation preceded by escaped backslashes. It should emit two, same # as if the backslashes are not present. "heredocs_with_fake_newlines.txt", ] diff --git a/test/prism/ractor_test.rb b/test/prism/ractor_test.rb index 55ff723395..fba10dbfe2 100644 --- a/test/prism/ractor_test.rb +++ b/test/prism/ractor_test.rb @@ -62,7 +62,7 @@ module Prism if reader reader.gets.chomp else - puts(ignore_warnings { Ractor.new(*arguments, &block) }.take) + puts(ignore_warnings { Ractor.new(*arguments, &block) }.value) end end end diff --git a/test/psych/test_ractor.rb b/test/psych/test_ractor.rb index 1b0d810609..f1c8327aa3 100644 --- a/test/psych/test_ractor.rb +++ b/test/psych/test_ractor.rb @@ -7,7 +7,7 @@ class TestPsychRactor < Test::Unit::TestCase obj = {foo: [42]} obj2 = Ractor.new(obj) do |obj| Psych.unsafe_load(Psych.dump(obj)) - end.take + end.value assert_equal obj, obj2 RUBY end @@ -33,7 +33,7 @@ class TestPsychRactor < Test::Unit::TestCase val * 2 end Psych.load('--- !!omap hello') - end.take + end.value assert_equal 'hellohello', r assert_equal 'hello', Psych.load('--- !!omap hello') RUBY @@ -43,7 +43,7 @@ class TestPsychRactor < Test::Unit::TestCase assert_ractor(<<~RUBY, require_relative: 'helper') r = Ractor.new do Psych.libyaml_version.join('.') == Psych::LIBYAML_VERSION - end.take + end.value assert_equal true, r RUBY end diff --git a/test/resolv/test_dns.rb b/test/resolv/test_dns.rb index 0a06fba3e7..87b3bf9f37 100644 --- a/test/resolv/test_dns.rb +++ b/test/resolv/test_dns.rb @@ -627,6 +627,13 @@ class TestResolvDNS < Test::Unit::TestCase assert_operator(2**14, :<, m.to_s.length) end + def test_too_long_address + too_long_address_message = [0, 0, 1, 0, 0, 0].pack("n*") + "\x01x" * 129 + [0, 0, 0].pack("cnn") + assert_raise_with_message(Resolv::DNS::DecodeError, /name label data exceed 255 octets/) do + Resolv::DNS::Message.decode too_long_address_message + end + end + def assert_no_fd_leak socket = assert_throw(self) do |tag| Resolv::DNS.stub(:bind_random_port, ->(s, *) {throw(tag, s)}) do diff --git a/test/ruby/namespace/instance_variables.rb b/test/ruby/namespace/instance_variables.rb new file mode 100644 index 0000000000..1562ad5d45 --- /dev/null +++ b/test/ruby/namespace/instance_variables.rb @@ -0,0 +1,21 @@ +class String + class << self + attr_reader :str_ivar1 + + def str_ivar2 + @str_ivar2 + end + end + + @str_ivar1 = 111 + @str_ivar2 = 222 +end + +class StringDelegator < BasicObject +private + def method_missing(...) + ::String.public_send(...) + end +end + +StringDelegatorObj = StringDelegator.new diff --git a/test/ruby/test_allocation.rb b/test/ruby/test_allocation.rb index bb1be26bec..a2ccd7bd65 100644 --- a/test/ruby/test_allocation.rb +++ b/test/ruby/test_allocation.rb @@ -781,6 +781,7 @@ class TestAllocation < Test::Unit::TestCase def test_no_array_allocation_with_splat_and_nonstatic_keywords check_allocations(<<~RUBY) def self.keyword(a: nil, b: nil#{block}); end + def self.Object; Object end check_allocations(0, 1, "keyword(*nil, a: empty_array#{block})") # LVAR check_allocations(0, 1, "keyword(*empty_array, a: empty_array#{block})") # LVAR @@ -788,7 +789,8 @@ class TestAllocation < Test::Unit::TestCase check_allocations(0, 1, "$x = empty_array; keyword(*empty_array, a: $x#{block})") # GVAR check_allocations(0, 1, "@x = empty_array; keyword(*empty_array, a: @x#{block})") # IVAR check_allocations(0, 1, "self.class.const_set(:X, empty_array); keyword(*empty_array, a: X#{block})") # CONST - check_allocations(0, 1, "keyword(*empty_array, a: Object::X#{block})") # COLON2 + check_allocations(0, 1, "keyword(*empty_array, a: Object::X#{block})") # COLON2 - safe + check_allocations(1, 1, "keyword(*empty_array, a: Object()::X#{block})") # COLON2 - unsafe check_allocations(0, 1, "keyword(*empty_array, a: ::X#{block})") # COLON3 check_allocations(0, 1, "T = self; #{'B = block' unless block.empty?}; class Object; @@x = X; T.keyword(*X, a: @@x#{', &B' unless block.empty?}) end") # CVAR check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 1#{block})") # INTEGER @@ -805,6 +807,13 @@ class TestAllocation < Test::Unit::TestCase check_allocations(0, 1, "keyword(*empty_array, a: ->{}#{block})") # LAMBDA check_allocations(0, 1, "keyword(*empty_array, a: $1#{block})") # NTH_REF check_allocations(0, 1, "keyword(*empty_array, a: $`#{block})") # BACK_REF + + # LIST: Only 1 array (literal [:c]), not 2 (one for [:c] and one for *empty_array) + check_allocations(1, 1, "keyword(*empty_array, a: empty_array, b: [:c]#{block})") + check_allocations(1, 1, "keyword(*empty_array, a: empty_array, b: [:c, $x]#{block})") + # LIST unsafe: 2 (one for [Object()] and one for *empty_array) + check_allocations(2, 1, "keyword(*empty_array, a: empty_array, b: [Object()]#{block})") + check_allocations(2, 1, "keyword(*empty_array, a: empty_array, b: [:c, $x, Object()]#{block})") RUBY end @@ -850,13 +859,15 @@ class TestAllocation < Test::Unit::TestCase check_allocations(<<~RUBY) keyword = keyword = proc{ |a: nil, b: nil #{block}| } + def self.Object; Object end check_allocations(0, 1, "keyword.(*empty_array, a: empty_array#{block})") # LVAR check_allocations(0, 1, "->{keyword.(*empty_array, a: empty_array#{block})}.call") # DVAR check_allocations(0, 1, "$x = empty_array; keyword.(*empty_array, a: $x#{block})") # GVAR check_allocations(0, 1, "@x = empty_array; keyword.(*empty_array, a: @x#{block})") # IVAR check_allocations(0, 1, "self.class.const_set(:X, empty_array); keyword.(*empty_array, a: X#{block})") # CONST - check_allocations(0, 1, "keyword.(*empty_array, a: Object::X#{block})") # COLON2 + check_allocations(0, 1, "keyword.(*empty_array, a: Object::X#{block})") # COLON2 - safe + check_allocations(1, 1, "keyword.(*empty_array, a: Object()::X#{block})") # COLON2 - unsafe check_allocations(0, 1, "keyword.(*empty_array, a: ::X#{block})") # COLON3 check_allocations(0, 1, "T = keyword; #{'B = block' unless block.empty?}; class Object; @@x = X; T.(*X, a: @@x#{', &B' unless block.empty?}) end") # CVAR check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 1#{block})") # INTEGER @@ -873,6 +884,13 @@ class TestAllocation < Test::Unit::TestCase check_allocations(0, 1, "keyword.(*empty_array, a: ->{}#{block})") # LAMBDA check_allocations(0, 1, "keyword.(*empty_array, a: $1#{block})") # NTH_REF check_allocations(0, 1, "keyword.(*empty_array, a: $`#{block})") # BACK_REF + + # LIST safe: Only 1 array (literal [:c]), not 2 (one for [:c] and one for *empty_array) + check_allocations(1, 1, "keyword.(*empty_array, a: empty_array, b: [:c]#{block})") + check_allocations(1, 1, "keyword.(*empty_array, a: empty_array, b: [:c, $x]#{block})") + # LIST unsafe: 2 (one for [:c] and one for *empty_array) + check_allocations(2, 1, "keyword.(*empty_array, a: empty_array, b: [Object()]#{block})") + check_allocations(2, 1, "keyword.(*empty_array, a: empty_array, b: [:c, $x, Object()]#{block})") RUBY end diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index 37b23e8db5..d22823470b 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -337,6 +337,19 @@ class TestAst < Test::Unit::TestCase assert_parse("END {defined? yield}") end + def test_invalid_yield_no_memory_leak + # [Bug #21383] + assert_no_memory_leak([], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true) + code = proc do + eval("class C; yield; end") + rescue SyntaxError + end + 1_000.times(&code) + begin; + 100_000.times(&code) + end; + end + def test_node_id_for_location omit if ParserSupport.prism_enabled? @@ -1384,6 +1397,24 @@ dummy assert_locations(node.children[-1].locations, [[1, 0, 1, 16], [1, 0, 1, 5], [1, 8, 1, 9], [1, 13, 1, 16]]) end + def test_colon2_locations + node = ast_parse("A::B") + assert_locations(node.children[-1].locations, [[1, 0, 1, 4], [1, 1, 1, 3], [1, 3, 1, 4]]) + + node = ast_parse("A::B::C") + assert_locations(node.children[-1].locations, [[1, 0, 1, 7], [1, 4, 1, 6], [1, 6, 1, 7]]) + assert_locations(node.children[-1].children[0].locations, [[1, 0, 1, 4], [1, 1, 1, 3], [1, 3, 1, 4]]) + end + + def test_colon3_locations + node = ast_parse("::A") + assert_locations(node.children[-1].locations, [[1, 0, 1, 3], [1, 0, 1, 2], [1, 2, 1, 3]]) + + node = ast_parse("::A::B") + assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 3, 1, 5], [1, 5, 1, 6]]) + assert_locations(node.children[-1].children[0].locations, [[1, 0, 1, 3], [1, 0, 1, 2], [1, 2, 1, 3]]) + end + def test_dot2_locations node = ast_parse("1..2") assert_locations(node.children[-1].locations, [[1, 0, 1, 4], [1, 1, 1, 3]]) diff --git a/test/ruby/test_backtrace.rb b/test/ruby/test_backtrace.rb index fca7b62030..dad7dfcb55 100644 --- a/test/ruby/test_backtrace.rb +++ b/test/ruby/test_backtrace.rb @@ -454,4 +454,16 @@ class TestBacktrace < Test::Unit::TestCase foo::Bar.baz end; end + + def test_backtrace_internal_frame + backtrace = tap { break caller_locations(0) } + assert_equal(__FILE__, backtrace[1].path) # not "" + assert_equal("Kernel#tap", backtrace[1].label) + end + + def test_backtrace_on_argument_error + lineno = __LINE__; [1, 2].inject(:tap) + rescue ArgumentError + assert_equal("#{ __FILE__ }:#{ lineno }:in 'Kernel#tap'", $!.backtrace[0].to_s) + end end diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb index 819d0d35aa..86f7f0b14f 100644 --- a/test/ruby/test_compile_prism.rb +++ b/test/ruby/test_compile_prism.rb @@ -1053,6 +1053,9 @@ module Prism assert_prism_eval("for foo, in [1,2,3] do end") assert_prism_eval("for i, j in {a: 'b'} do; i; j; end") + + # Test splat node as index in for loop + assert_prism_eval("for *x in [[1,2], [3,4]] do; x; end") end ############################################################################ diff --git a/test/ruby/test_data.rb b/test/ruby/test_data.rb index bb38f8ec91..dd698fdcc4 100644 --- a/test/ruby/test_data.rb +++ b/test/ruby/test_data.rb @@ -280,4 +280,10 @@ class TestData < Test::Unit::TestCase assert_not_same(test, loaded) assert_predicate(loaded, :frozen?) end + + def test_frozen_subclass + test = Class.new(Data.define(:a)).freeze.new(a: 0) + assert_kind_of(Data, test) + assert_equal([:a], test.members) + end end diff --git a/test/ruby/test_encoding.rb b/test/ruby/test_encoding.rb index 388b94df39..7ccbb31f50 100644 --- a/test/ruby/test_encoding.rb +++ b/test/ruby/test_encoding.rb @@ -33,7 +33,7 @@ class TestEncoding < Test::Unit::TestCase encodings.each do |e| assert_raise(TypeError) { e.dup } assert_raise(TypeError) { e.clone } - assert_equal(e.object_id, Marshal.load(Marshal.dump(e)).object_id) + assert_same(e, Marshal.load(Marshal.dump(e))) end end @@ -130,10 +130,31 @@ class TestEncoding < Test::Unit::TestCase def test_ractor_load_encoding assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") begin; - Ractor.new{}.take + Ractor.new{}.join $-w = nil Encoding.default_external = Encoding::ISO8859_2 assert "[Bug #19562]" end; end + + def test_ractor_lazy_load_encoding_concurrently + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + rs = [] + autoload_encodings = Encoding.list.select { |e| e.inspect.include?("(autoload)") }.freeze + 7.times do + rs << Ractor.new(autoload_encodings) do |encodings| + str = "abc".dup + encodings.each do |enc| + str.force_encoding(enc) + end + end + end + while rs.any? + r, _obj = Ractor.select(*rs) + rs.delete(r) + end + assert rs.empty? + end; + end end diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb index c5e3e35d36..2727620c19 100644 --- a/test/ruby/test_env.rb +++ b/test/ruby/test_env.rb @@ -601,13 +601,13 @@ class TestEnv < Test::Unit::TestCase rescue Exception => e #{exception_var} = e end - Ractor.yield #{exception_var}.class + port.send #{exception_var}.class end; end def str_for_assert_raise_on_yielded_exception_class(expected_error_class, ractor_var) <<-"end;" - error_class = #{ractor_var}.take + error_class = #{ractor_var}.receive assert_raise(#{expected_error_class}) do if error_class < Exception raise error_class @@ -649,100 +649,101 @@ class TestEnv < Test::Unit::TestCase def test_bracket_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do - Ractor.yield ENV['test'] - Ractor.yield ENV['TEST'] + Ractor.new port = Ractor::Port.new do |port| + port << ENV['test'] + port << ENV['TEST'] ENV['test'] = 'foo' - Ractor.yield ENV['test'] - Ractor.yield ENV['TEST'] + port << ENV['test'] + port << ENV['TEST'] ENV['TEST'] = 'bar' - Ractor.yield ENV['TEST'] - Ractor.yield ENV['test'] + port << ENV['TEST'] + port << ENV['test'] #{str_for_yielding_exception_class("ENV[1]")} #{str_for_yielding_exception_class("ENV[1] = 'foo'")} #{str_for_yielding_exception_class("ENV['test'] = 0")} end - assert_nil(r.take) - assert_nil(r.take) - assert_equal('foo', r.take) + assert_nil(port.receive) + assert_nil(port.receive) + assert_equal('foo', port.receive) if #{ignore_case_str} - assert_equal('foo', r.take) + assert_equal('foo', port.receive) else - assert_nil(r.take) + assert_nil(port.receive) end - assert_equal('bar', r.take) + assert_equal('bar', port.receive) if #{ignore_case_str} - assert_equal('bar', r.take) + assert_equal('bar', port.receive) else - assert_equal('foo', r.take) + assert_equal('foo', port.receive) end 3.times do - #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")} + #{str_for_assert_raise_on_yielded_exception_class(TypeError, "port")} end end; end def test_dup_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| #{str_for_yielding_exception_class("ENV.dup")} end - #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")} + #{str_for_assert_raise_on_yielded_exception_class(TypeError, "port")} end; end def test_has_value_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + port = Ractor::Port.new + Ractor.new port do |port| val = 'a' val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase) ENV['test'] = val[0...-1] - Ractor.yield(ENV.has_value?(val)) - Ractor.yield(ENV.has_value?(val.upcase)) + port.send(ENV.has_value?(val)) + port.send(ENV.has_value?(val.upcase)) ENV['test'] = val - Ractor.yield(ENV.has_value?(val)) - Ractor.yield(ENV.has_value?(val.upcase)) + port.send(ENV.has_value?(val)) + port.send(ENV.has_value?(val.upcase)) ENV['test'] = val.upcase - Ractor.yield ENV.has_value?(val) - Ractor.yield ENV.has_value?(val.upcase) + port.send ENV.has_value?(val) + port.send ENV.has_value?(val.upcase) end - assert_equal(false, r.take) - assert_equal(false, r.take) - assert_equal(true, r.take) - assert_equal(false, r.take) - assert_equal(false, r.take) - assert_equal(true, r.take) + assert_equal(false, port.receive) + assert_equal(false, port.receive) + assert_equal(true, port.receive) + assert_equal(false, port.receive) + assert_equal(false, port.receive) + assert_equal(true, port.receive) end; end def test_key_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| val = 'a' val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase) ENV['test'] = val[0...-1] - Ractor.yield ENV.key(val) - Ractor.yield ENV.key(val.upcase) + port.send ENV.key(val) + port.send ENV.key(val.upcase) ENV['test'] = val - Ractor.yield ENV.key(val) - Ractor.yield ENV.key(val.upcase) + port.send ENV.key(val) + port.send ENV.key(val.upcase) ENV['test'] = val.upcase - Ractor.yield ENV.key(val) - Ractor.yield ENV.key(val.upcase) + port.send ENV.key(val) + port.send ENV.key(val.upcase) end - assert_nil(r.take) - assert_nil(r.take) + assert_nil(port.receive) + assert_nil(port.receive) if #{ignore_case_str} - assert_equal('TEST', r.take.upcase) + assert_equal('TEST', port.receive.upcase) else - assert_equal('test', r.take) + assert_equal('test', port.receive) end - assert_nil(r.take) - assert_nil(r.take) + assert_nil(port.receive) + assert_nil(port.receive) if #{ignore_case_str} - assert_equal('TEST', r.take.upcase) + assert_equal('TEST', port.receive.upcase) else - assert_equal('test', r.take) + assert_equal('test', port.receive) end end; @@ -750,87 +751,87 @@ class TestEnv < Test::Unit::TestCase def test_delete_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| #{str_to_yield_invalid_envvar_errors("v", "ENV.delete(v)")} - Ractor.yield ENV.delete("TEST") + port.send ENV.delete("TEST") #{str_for_yielding_exception_class("ENV.delete('#{PATH_ENV}')")} - Ractor.yield(ENV.delete("TEST"){|name| "NO "+name}) + port.send(ENV.delete("TEST"){|name| "NO "+name}) end - #{str_to_receive_invalid_envvar_errors("r")} - assert_nil(r.take) - exception_class = r.take + #{str_to_receive_invalid_envvar_errors("port")} + assert_nil(port.receive) + exception_class = port.receive assert_equal(NilClass, exception_class) - assert_equal("NO TEST", r.take) + assert_equal("NO TEST", port.receive) end; end def test_getenv_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| #{str_to_yield_invalid_envvar_errors("v", "ENV[v]")} ENV["#{PATH_ENV}"] = "" - Ractor.yield ENV["#{PATH_ENV}"] - Ractor.yield ENV[""] + port.send ENV["#{PATH_ENV}"] + port.send ENV[""] end - #{str_to_receive_invalid_envvar_errors("r")} - assert_equal("", r.take) - assert_nil(r.take) + #{str_to_receive_invalid_envvar_errors("port")} + assert_equal("", port.receive) + assert_nil(port.receive) end; end def test_fetch_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV["test"] = "foo" - Ractor.yield ENV.fetch("test") + port.send ENV.fetch("test") ENV.delete("test") #{str_for_yielding_exception_class("ENV.fetch('test')", exception_var: "ex")} - Ractor.yield ex.receiver.object_id - Ractor.yield ex.key - Ractor.yield ENV.fetch("test", "foo") - Ractor.yield(ENV.fetch("test"){"bar"}) + port.send ex.receiver.object_id + port.send ex.key + port.send ENV.fetch("test", "foo") + port.send(ENV.fetch("test"){"bar"}) #{str_to_yield_invalid_envvar_errors("v", "ENV.fetch(v)")} #{str_for_yielding_exception_class("ENV.fetch('#{PATH_ENV}', 'foo')")} ENV['#{PATH_ENV}'] = "" - Ractor.yield ENV.fetch('#{PATH_ENV}') + port.send ENV.fetch('#{PATH_ENV}') end - assert_equal("foo", r.take) - #{str_for_assert_raise_on_yielded_exception_class(KeyError, "r")} - assert_equal(ENV.object_id, r.take) - assert_equal("test", r.take) - assert_equal("foo", r.take) - assert_equal("bar", r.take) - #{str_to_receive_invalid_envvar_errors("r")} - exception_class = r.take + assert_equal("foo", port.receive) + #{str_for_assert_raise_on_yielded_exception_class(KeyError, "port")} + assert_equal(ENV.object_id, port.receive) + assert_equal("test", port.receive) + assert_equal("foo", port.receive) + assert_equal("bar", port.receive) + #{str_to_receive_invalid_envvar_errors("port")} + exception_class = port.receive assert_equal(NilClass, exception_class) - assert_equal("", r.take) + assert_equal("", port.receive) end; end def test_aset_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| #{str_for_yielding_exception_class("ENV['test'] = nil")} ENV["test"] = nil - Ractor.yield ENV["test"] + port.send ENV["test"] #{str_to_yield_invalid_envvar_errors("v", "ENV[v] = 'test'")} #{str_to_yield_invalid_envvar_errors("v", "ENV['test'] = v")} end - exception_class = r.take + exception_class = port.receive assert_equal(NilClass, exception_class) - assert_nil(r.take) - #{str_to_receive_invalid_envvar_errors("r")} - #{str_to_receive_invalid_envvar_errors("r")} + assert_nil(port.receive) + #{str_to_receive_invalid_envvar_errors("port")} + #{str_to_receive_invalid_envvar_errors("port")} end; end def test_keys_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| a = ENV.keys - Ractor.yield a + port.send a end - a = r.take + a = port.receive assert_kind_of(Array, a) a.each {|k| assert_kind_of(String, k) } end; @@ -839,11 +840,11 @@ class TestEnv < Test::Unit::TestCase def test_each_key_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do - ENV.each_key {|k| Ractor.yield(k)} - Ractor.yield "finished" + Ractor.new port = Ractor::Port.new do |port| + ENV.each_key {|k| port.send(k)} + port.send "finished" end - while((x=r.take) != "finished") + while((x=port.receive) != "finished") assert_kind_of(String, x) end end; @@ -851,11 +852,11 @@ class TestEnv < Test::Unit::TestCase def test_values_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| a = ENV.values - Ractor.yield a + port.send a end - a = r.take + a = port.receive assert_kind_of(Array, a) a.each {|k| assert_kind_of(String, k) } end; @@ -863,11 +864,11 @@ class TestEnv < Test::Unit::TestCase def test_each_value_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do - ENV.each_value {|k| Ractor.yield(k)} - Ractor.yield "finished" + Ractor.new port = Ractor::Port.new do |port| + ENV.each_value {|k| port.send(k)} + port.send "finished" end - while((x=r.take) != "finished") + while((x=port.receive) != "finished") assert_kind_of(String, x) end end; @@ -875,11 +876,11 @@ class TestEnv < Test::Unit::TestCase def test_each_pair_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do - ENV.each_pair {|k, v| Ractor.yield([k,v])} - Ractor.yield "finished" + Ractor.new port = Ractor::Port.new do |port| + ENV.each_pair {|k, v| port.send([k,v])} + port.send "finished" end - while((k,v=r.take) != "finished") + while((k,v=port.receive) != "finished") assert_kind_of(String, k) assert_kind_of(String, v) end @@ -888,116 +889,116 @@ class TestEnv < Test::Unit::TestCase def test_reject_bang_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| h1 = {} ENV.each_pair {|k, v| h1[k] = v } ENV["test"] = "foo" ENV.reject! {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" } h2 = {} ENV.each_pair {|k, v| h2[k] = v } - Ractor.yield [h1, h2] - Ractor.yield(ENV.reject! {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }) + port.send [h1, h2] + port.send(ENV.reject! {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }) end - h1, h2 = r.take + h1, h2 = port.receive assert_equal(h1, h2) - assert_nil(r.take) + assert_nil(port.receive) end; end def test_delete_if_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| h1 = {} ENV.each_pair {|k, v| h1[k] = v } ENV["test"] = "foo" ENV.delete_if {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" } h2 = {} ENV.each_pair {|k, v| h2[k] = v } - Ractor.yield [h1, h2] - Ractor.yield (ENV.delete_if {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }) + port.send [h1, h2] + port.send (ENV.delete_if {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }) end - h1, h2 = r.take + h1, h2 = port.receive assert_equal(h1, h2) - assert_same(ENV, r.take) + assert_same(ENV, port.receive) end; end def test_select_bang_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| h1 = {} ENV.each_pair {|k, v| h1[k] = v } ENV["test"] = "foo" ENV.select! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" } h2 = {} ENV.each_pair {|k, v| h2[k] = v } - Ractor.yield [h1, h2] - Ractor.yield(ENV.select! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }) + port.send [h1, h2] + port.send(ENV.select! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }) end - h1, h2 = r.take + h1, h2 = port.receive assert_equal(h1, h2) - assert_nil(r.take) + assert_nil(port.receive) end; end def test_filter_bang_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| h1 = {} ENV.each_pair {|k, v| h1[k] = v } ENV["test"] = "foo" ENV.filter! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" } h2 = {} ENV.each_pair {|k, v| h2[k] = v } - Ractor.yield [h1, h2] - Ractor.yield(ENV.filter! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }) + port.send [h1, h2] + port.send(ENV.filter! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }) end - h1, h2 = r.take + h1, h2 = port.receive assert_equal(h1, h2) - assert_nil(r.take) + assert_nil(port.receive) end; end def test_keep_if_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| h1 = {} ENV.each_pair {|k, v| h1[k] = v } ENV["test"] = "foo" ENV.keep_if {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" } h2 = {} ENV.each_pair {|k, v| h2[k] = v } - Ractor.yield [h1, h2] - Ractor.yield (ENV.keep_if {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }) + port.send [h1, h2] + port.send (ENV.keep_if {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }) end - h1, h2 = r.take + h1, h2 = port.receive assert_equal(h1, h2) - assert_equal(ENV, r.take) + assert_equal(ENV, port.receive) end; end def test_values_at_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV["test"] = "foo" - Ractor.yield ENV.values_at("test", "test") + port.send ENV.values_at("test", "test") end - assert_equal(["foo", "foo"], r.take) + assert_equal(["foo", "foo"], port.receive) end; end def test_select_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV["test"] = "foo" h = ENV.select {|k| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" } - Ractor.yield h.size + port.send h.size k = h.keys.first v = h.values.first - Ractor.yield [k, v] + port.send [k, v] end - assert_equal(1, r.take) - k, v = r.take + assert_equal(1, port.receive) + k, v = port.receive if #{ignore_case_str} assert_equal("TEST", k.upcase) assert_equal("FOO", v.upcase) @@ -1010,16 +1011,16 @@ class TestEnv < Test::Unit::TestCase def test_filter_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV["test"] = "foo" h = ENV.filter {|k| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" } - Ractor.yield(h.size) + port.send(h.size) k = h.keys.first v = h.values.first - Ractor.yield [k, v] + port.send [k, v] end - assert_equal(1, r.take) - k, v = r.take + assert_equal(1, port.receive) + k, v = port.receive if #{ignore_case_str} assert_equal("TEST", k.upcase) assert_equal("FOO", v.upcase) @@ -1032,49 +1033,49 @@ class TestEnv < Test::Unit::TestCase def test_slice_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV.clear ENV["foo"] = "bar" ENV["baz"] = "qux" ENV["bar"] = "rab" - Ractor.yield(ENV.slice()) - Ractor.yield(ENV.slice("")) - Ractor.yield(ENV.slice("unknown")) - Ractor.yield(ENV.slice("foo", "baz")) + port.send(ENV.slice()) + port.send(ENV.slice("")) + port.send(ENV.slice("unknown")) + port.send(ENV.slice("foo", "baz")) end - assert_equal({}, r.take) - assert_equal({}, r.take) - assert_equal({}, r.take) - assert_equal({"foo"=>"bar", "baz"=>"qux"}, r.take) + assert_equal({}, port.receive) + assert_equal({}, port.receive) + assert_equal({}, port.receive) + assert_equal({"foo"=>"bar", "baz"=>"qux"}, port.receive) end; end def test_except_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV.clear ENV["foo"] = "bar" ENV["baz"] = "qux" ENV["bar"] = "rab" - Ractor.yield ENV.except() - Ractor.yield ENV.except("") - Ractor.yield ENV.except("unknown") - Ractor.yield ENV.except("foo", "baz") + port.send ENV.except() + port.send ENV.except("") + port.send ENV.except("unknown") + port.send ENV.except("foo", "baz") end - assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, r.take) - assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, r.take) - assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, r.take) - assert_equal({"bar"=>"rab"}, r.take) + assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, port.receive) + assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, port.receive) + assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, port.receive) + assert_equal({"bar"=>"rab"}, port.receive) end; end def test_clear_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV.clear - Ractor.yield ENV.size + port.send ENV.size end - assert_equal(0, r.take) + assert_equal(0, port.receive) end; end @@ -1083,20 +1084,20 @@ class TestEnv < Test::Unit::TestCase r = Ractor.new do ENV.to_s end - assert_equal("ENV", r.take) + assert_equal("ENV", r.value) end; end def test_inspect_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV.clear ENV["foo"] = "bar" ENV["baz"] = "qux" s = ENV.inspect - Ractor.yield s + port.send s end - s = r.take + s = port.receive expected = ['"foo" => "bar"', '"baz" => "qux"'] unless s.start_with?(/\{"foo"/i) expected.reverse! @@ -1112,14 +1113,14 @@ class TestEnv < Test::Unit::TestCase def test_to_a_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV.clear ENV["foo"] = "bar" ENV["baz"] = "qux" a = ENV.to_a - Ractor.yield a + port.send a end - a = r.take + a = port.receive assert_equal(2, a.size) expected = [%w(baz qux), %w(foo bar)] if #{ignore_case_str} @@ -1136,59 +1137,59 @@ class TestEnv < Test::Unit::TestCase r = Ractor.new do ENV.rehash end - assert_nil(r.take) + assert_nil(r.value) end; end def test_size_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| s = ENV.size ENV["test"] = "foo" - Ractor.yield [s, ENV.size] + port.send [s, ENV.size] end - s, s2 = r.take + s, s2 = port.receive assert_equal(s + 1, s2) end; end def test_empty_p_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV.clear - Ractor.yield ENV.empty? + port.send ENV.empty? ENV["test"] = "foo" - Ractor.yield ENV.empty? + port.send ENV.empty? end - assert r.take - assert !r.take + assert port.receive + assert !port.receive end; end def test_has_key_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do - Ractor.yield ENV.has_key?("test") + Ractor.new port = Ractor::Port.new do |port| + port.send ENV.has_key?("test") ENV["test"] = "foo" - Ractor.yield ENV.has_key?("test") + port.send ENV.has_key?("test") #{str_to_yield_invalid_envvar_errors("v", "ENV.has_key?(v)")} end - assert !r.take - assert r.take - #{str_to_receive_invalid_envvar_errors("r")} + assert !port.receive + assert port.receive + #{str_to_receive_invalid_envvar_errors("port")} end; end def test_assoc_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do - Ractor.yield ENV.assoc("test") + Ractor.new port = Ractor::Port.new do |port| + port.send ENV.assoc("test") ENV["test"] = "foo" - Ractor.yield ENV.assoc("test") + port.send ENV.assoc("test") #{str_to_yield_invalid_envvar_errors("v", "ENV.assoc(v)")} end - assert_nil(r.take) - k, v = r.take + assert_nil(port.receive) + k, v = port.receive if #{ignore_case_str} assert_equal("TEST", k.upcase) assert_equal("FOO", v.upcase) @@ -1196,7 +1197,7 @@ class TestEnv < Test::Unit::TestCase assert_equal("test", k) assert_equal("foo", v) end - #{str_to_receive_invalid_envvar_errors("r")} + #{str_to_receive_invalid_envvar_errors("port")} encoding = /mswin|mingw/ =~ RUBY_PLATFORM ? Encoding::UTF_8 : Encoding.find("locale") assert_equal(encoding, v.encoding) end; @@ -1204,29 +1205,29 @@ class TestEnv < Test::Unit::TestCase def test_has_value2_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV.clear - Ractor.yield ENV.has_value?("foo") + port.send ENV.has_value?("foo") ENV["test"] = "foo" - Ractor.yield ENV.has_value?("foo") + port.send ENV.has_value?("foo") end - assert !r.take - assert r.take + assert !port.receive + assert port.receive end; end def test_rassoc_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV.clear - Ractor.yield ENV.rassoc("foo") + port.send ENV.rassoc("foo") ENV["foo"] = "bar" ENV["test"] = "foo" ENV["baz"] = "qux" - Ractor.yield ENV.rassoc("foo") + port.send ENV.rassoc("foo") end - assert_nil(r.take) - k, v = r.take + assert_nil(port.receive) + k, v = port.receive if #{ignore_case_str} assert_equal("TEST", k.upcase) assert_equal("FOO", v.upcase) @@ -1239,39 +1240,39 @@ class TestEnv < Test::Unit::TestCase def test_to_hash_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| h = {} ENV.each {|k, v| h[k] = v } - Ractor.yield [h, ENV.to_hash] + port.send [h, ENV.to_hash] end - h, h2 = r.take + h, h2 = port.receive assert_equal(h, h2) end; end def test_to_h_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do - Ractor.yield [ENV.to_hash, ENV.to_h] - Ractor.yield [ENV.map {|k, v| ["$\#{k}", v.size]}.to_h, ENV.to_h {|k, v| ["$\#{k}", v.size]}] + Ractor.new port = Ractor::Port.new do |port| + port.send [ENV.to_hash, ENV.to_h] + port.send [ENV.map {|k, v| ["$\#{k}", v.size]}.to_h, ENV.to_h {|k, v| ["$\#{k}", v.size]}] end - a, b = r.take + a, b = port.receive assert_equal(a,b) - c, d = r.take + c, d = port.receive assert_equal(c,d) end; end def test_reject_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| h1 = {} ENV.each_pair {|k, v| h1[k] = v } ENV["test"] = "foo" h2 = ENV.reject {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" } - Ractor.yield [h1, h2] + port.send [h1, h2] end - h1, h2 = r.take + h1, h2 = port.receive assert_equal(h1, h2) end; end @@ -1279,86 +1280,86 @@ class TestEnv < Test::Unit::TestCase def test_shift_in_ractor assert_ractor(<<-"end;") #{STR_DEFINITION_FOR_CHECK} - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV.clear ENV["foo"] = "bar" ENV["baz"] = "qux" a = ENV.shift b = ENV.shift - Ractor.yield [a,b] - Ractor.yield ENV.shift + port.send [a,b] + port.send ENV.shift end - a,b = r.take + a,b = port.receive check([a, b], [%w(foo bar), %w(baz qux)]) - assert_nil(r.take) + assert_nil(port.receive) end; end def test_invert_in_ractor assert_ractor(<<-"end;") #{STR_DEFINITION_FOR_CHECK} - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV.clear ENV["foo"] = "bar" ENV["baz"] = "qux" - Ractor.yield(ENV.invert) + port.send(ENV.invert) end - check(r.take.to_a, [%w(bar foo), %w(qux baz)]) + check(port.receive.to_a, [%w(bar foo), %w(qux baz)]) end; end def test_replace_in_ractor assert_ractor(<<-"end;") #{STR_DEFINITION_FOR_CHECK} - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV["foo"] = "xxx" ENV.replace({"foo"=>"bar", "baz"=>"qux"}) - Ractor.yield ENV.to_hash + port.send ENV.to_hash ENV.replace({"Foo"=>"Bar", "Baz"=>"Qux"}) - Ractor.yield ENV.to_hash + port.send ENV.to_hash end - check(r.take.to_a, [%w(foo bar), %w(baz qux)]) - check(r.take.to_a, [%w(Foo Bar), %w(Baz Qux)]) + check(port.receive.to_a, [%w(foo bar), %w(baz qux)]) + check(port.receive.to_a, [%w(Foo Bar), %w(Baz Qux)]) end; end def test_update_in_ractor assert_ractor(<<-"end;") #{STR_DEFINITION_FOR_CHECK} - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV.clear ENV["foo"] = "bar" ENV["baz"] = "qux" ENV.update({"baz"=>"quux","a"=>"b"}) - Ractor.yield ENV.to_hash + port.send ENV.to_hash ENV.clear ENV["foo"] = "bar" ENV["baz"] = "qux" ENV.update({"baz"=>"quux","a"=>"b"}) {|k, v1, v2| k + "_" + v1 + "_" + v2 } - Ractor.yield ENV.to_hash + port.send ENV.to_hash end - check(r.take.to_a, [%w(foo bar), %w(baz quux), %w(a b)]) - check(r.take.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)]) + check(port.receive.to_a, [%w(foo bar), %w(baz quux), %w(a b)]) + check(port.receive.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)]) end; end def test_huge_value_in_ractor assert_ractor(<<-"end;") huge_value = "bar" * 40960 - r = Ractor.new huge_value do |v| + Ractor.new port = Ractor::Port.new, huge_value do |port, v| ENV["foo"] = "bar" #{str_for_yielding_exception_class("ENV['foo'] = v ")} - Ractor.yield ENV["foo"] + port.send ENV["foo"] end if /mswin|ucrt/ =~ RUBY_PLATFORM - #{str_for_assert_raise_on_yielded_exception_class(Errno::EINVAL, "r")} - result = r.take + #{str_for_assert_raise_on_yielded_exception_class(Errno::EINVAL, "port")} + result = port.receive assert_equal("bar", result) else - exception_class = r.take + exception_class = port.receive assert_equal(NilClass, exception_class) - result = r.take + result = port.receive assert_equal(huge_value, result) end end; @@ -1366,34 +1367,34 @@ class TestEnv < Test::Unit::TestCase def test_frozen_env_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| #{str_for_yielding_exception_class("ENV.freeze")} end - #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")} + #{str_for_assert_raise_on_yielded_exception_class(TypeError, "port")} end; end def test_frozen_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| ENV["#{PATH_ENV}"] = "/" ENV.each do |k, v| - Ractor.yield [k.frozen?] - Ractor.yield [v.frozen?] + port.send [k.frozen?] + port.send [v.frozen?] end ENV.each_key do |k| - Ractor.yield [k.frozen?] + port.send [k.frozen?] end ENV.each_value do |v| - Ractor.yield [v.frozen?] + port.send [v.frozen?] end ENV.each_key do |k| - Ractor.yield [ENV[k].frozen?, "[\#{k.dump}]"] - Ractor.yield [ENV.fetch(k).frozen?, "fetch(\#{k.dump})"] + port.send [ENV[k].frozen?, "[\#{k.dump}]"] + port.send [ENV.fetch(k).frozen?, "fetch(\#{k.dump})"] end - Ractor.yield "finished" + port.send "finished" end - while((params=r.take) != "finished") + while((params=port.receive) != "finished") assert(*params) end end; @@ -1401,7 +1402,7 @@ class TestEnv < Test::Unit::TestCase def test_shared_substring_in_ractor assert_ractor(<<-"end;") - r = Ractor.new do + Ractor.new port = Ractor::Port.new do |port| bug12475 = '[ruby-dev:49655] [Bug #12475]' n = [*"0".."9"].join("")*3 e0 = ENV[n0 = "E\#{n}"] @@ -1411,9 +1412,9 @@ class TestEnv < Test::Unit::TestCase ENV[n1.chop] = "T\#{n}.".chop ENV[n0], e0 = e0, ENV[n0] ENV[n1], e1 = e1, ENV[n1] - Ractor.yield [n, e0, e1, bug12475] + port.send [n, e0, e1, bug12475] end - n, e0, e1, bug12475 = r.take + n, e0, e1, bug12475 = port.receive assert_equal("T\#{n}", e0, bug12475) assert_nil(e1, bug12475) end; @@ -1429,7 +1430,7 @@ class TestEnv < Test::Unit::TestCase rescue Ractor::IsolationError => e e end - assert_equal Ractor::IsolationError, r_get.take.class + assert_equal Ractor::IsolationError, r_get.value.class r_get = Ractor.new do ENV.instance_eval{ @a } @@ -1437,7 +1438,7 @@ class TestEnv < Test::Unit::TestCase e end - assert_equal Ractor::IsolationError, r_get.take.class + assert_equal Ractor::IsolationError, r_get.value.class r_set = Ractor.new do ENV.instance_eval{ @b = "hello" } @@ -1445,7 +1446,7 @@ class TestEnv < Test::Unit::TestCase e end - assert_equal Ractor::IsolationError, r_set.take.class + assert_equal Ractor::IsolationError, r_set.value.class RUBY end diff --git a/test/ruby/test_file.rb b/test/ruby/test_file.rb index eae9a8e7b0..a3d6221c0f 100644 --- a/test/ruby/test_file.rb +++ b/test/ruby/test_file.rb @@ -372,9 +372,9 @@ class TestFile < Test::Unit::TestCase end def test_stat - tb = Process.clock_gettime(Process::CLOCK_REALTIME) + btime = Process.clock_gettime(Process::CLOCK_REALTIME) Tempfile.create("stat") {|file| - tb = (tb + Process.clock_gettime(Process::CLOCK_REALTIME)) / 2 + btime = (btime + Process.clock_gettime(Process::CLOCK_REALTIME)) / 2 file.close path = file.path @@ -384,33 +384,32 @@ class TestFile < Test::Unit::TestCase sleep 2 - t1 = measure_time do + mtime = measure_time do File.write(path, "bar") end sleep 2 - t2 = measure_time do - File.read(path) + ctime = measure_time do File.chmod(0644, path) end sleep 2 - t3 = measure_time do + atime = measure_time do File.read(path) end delta = 1 stat = File.stat(path) - assert_in_delta tb, stat.birthtime.to_f, delta - assert_in_delta t1, stat.mtime.to_f, delta + assert_in_delta btime, stat.birthtime.to_f, delta + assert_in_delta mtime, stat.mtime.to_f, delta if stat.birthtime != stat.ctime - assert_in_delta t2, stat.ctime.to_f, delta + assert_in_delta ctime, stat.ctime.to_f, delta end if /mswin|mingw/ !~ RUBY_PLATFORM && !Bug::File::Fs.noatime?(path) # Windows delays updating atime - assert_in_delta t3, stat.atime.to_f, delta + assert_in_delta atime, stat.atime.to_f, delta end } rescue NotImplementedError diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index a1229fc87a..85022cbc4d 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -393,12 +393,10 @@ class TestGc < Test::Unit::TestCase # Create some objects and place it in a WeakMap wmap = ObjectSpace::WeakMap.new - ary = Array.new(count) - enum = count.times - enum.each.with_index do |i| + ary = Array.new(count) do |i| obj = Object.new - ary[i] = obj wmap[obj] = nil + obj end # Run full GC to collect stats about weak references @@ -421,7 +419,7 @@ class TestGc < Test::Unit::TestCase GC.start # Sometimes the WeakMap has a few elements, which might be held on by registers. - assert_operator(wmap.size, :<=, 2) + assert_operator(wmap.size, :<=, count / 1000) assert_operator(GC.latest_gc_info(:weak_references_count), :<=, before_weak_references_count - count + error_tolerance) assert_operator(GC.latest_gc_info(:retained_weak_references_count), :<=, before_retained_weak_references_count - count + error_tolerance) @@ -703,7 +701,14 @@ class TestGc < Test::Unit::TestCase allocate_large_object end - assert_operator(GC.stat(:heap_available_slots), :<, COUNT * 2) + # Running GC here is required to prevent this test from being flaky because + # the heap for the small transient objects may not have been cleared by the + # GC causing heap_available_slots to be slightly over 2 * COUNT. + GC.start + + heap_available_slots = GC.stat(:heap_available_slots) + + assert_operator(heap_available_slots, :<, COUNT * 2, "GC.stat: #{GC.stat}\nGC.stat_heap: #{GC.stat_heap}") RUBY end @@ -755,7 +760,7 @@ class TestGc < Test::Unit::TestCase ObjectSpace.define_finalizer(Object.new, f) end end; - out, err, status = assert_in_out_err(["-e", src], "", [], [], bug10595, signal: :SEGV) do |*result| + out, err, status = assert_in_out_err(["-e", src], "", [], [], bug10595, signal: :SEGV, timeout: 100) do |*result| break result end unless /mswin|mingw/ =~ RUBY_PLATFORM diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb index 76af5b6183..dbf041a732 100644 --- a/test/ruby/test_hash.rb +++ b/test/ruby/test_hash.rb @@ -1297,6 +1297,17 @@ class TestHash < Test::Unit::TestCase assert_equal(@cls[a: 10, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10], h) end + def test_update_modify_in_block + a = @cls[] + (1..1337).each {|k| a[k] = k} + b = {1=>1338} + assert_raise_with_message(RuntimeError, /rehash during iteration/) do + a.update(b) {|k, o, n| + a.rehash + } + end + end + def test_update_on_identhash key = +'a' i = @cls[].compare_by_identity @@ -2127,7 +2138,9 @@ class TestHashOnly < Test::Unit::TestCase def test_iterlevel_in_ivar_bug19589 h = { a: nil } - hash_iter_recursion(h, 200) + # Recursion level should be over 127 to actually test iterlevel being set in an instance variable, + # but it should be under 131 not to overflow the stack under MN threads/ractors. + hash_iter_recursion(h, 130) assert true end @@ -2344,6 +2357,11 @@ class TestHashOnly < Test::Unit::TestCase end end + def test_bug_21357 + h = {x: []}.merge(x: nil) { |_k, v1, _v2| v1 } + assert_equal({x: []}, h) + end + def test_any_hash_fixable 20.times do assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index a81d689355..3ec8726b5e 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -4416,4 +4416,29 @@ __END__ end RUBY end + + def test_fork_close + omit "fork is not supported" unless Process.respond_to?(:fork) + + assert_separately([], <<~'RUBY') + r, w = IO.pipe + + thread = Thread.new do + r.read + end + + Thread.pass until thread.status == "sleep" + + pid = fork do + r.close + end + + w.close + + status = Process.wait2(pid).last + thread.join + + assert_predicate(status, :success?) + RUBY + end end diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb index 55296c1f23..62c4667888 100644 --- a/test/ruby/test_io_buffer.rb +++ b/test/ruby/test_io_buffer.rb @@ -121,6 +121,16 @@ class TestIOBuffer < Test::Unit::TestCase end end + def test_string_mapped_buffer_frozen + string = "Hello World".freeze + IO::Buffer.for(string) do |buffer| + assert_raise IO::Buffer::AccessError, "Buffer is not writable!" do + buffer.set_string("abc") + end + assert_equal "H".ord, buffer.get_value(:U8, 0) + end + end + def test_non_string not_string = Object.new @@ -683,4 +693,17 @@ class TestIOBuffer < Test::Unit::TestCase buf.set_string('a', 0, 0) assert_predicate buf, :empty? end + + # https://bugs.ruby-lang.org/issues/21210 + def test_bug_21210 + omit "compaction is not supported on this platform" unless GC.respond_to?(:compact) + + str = +"hello" + buf = IO::Buffer.for(str) + assert_predicate buf, :valid? + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_predicate buf, :valid? + end end diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb index 86c1f51dde..45223c89da 100644 --- a/test/ruby/test_iseq.rb +++ b/test/ruby/test_iseq.rb @@ -297,6 +297,56 @@ class TestISeq < Test::Unit::TestCase assert_raise(TypeError, bug11159) {compile(1)} end + def test_invalid_source_no_memory_leak + # [Bug #21394] + assert_no_memory_leak(["-rtempfile"], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true) + code = proc do |t| + RubyVM::InstructionSequence.new(nil) + rescue TypeError + else + raise "TypeError was not raised during RubyVM::InstructionSequence.new" + end + + 10.times(&code) + begin; + 1_000_000.times(&code) + end; + + # [Bug #21394] + # RubyVM::InstructionSequence.new calls rb_io_path, which dups the string + # and can leak memory if the dup raises + assert_no_memory_leak(["-rtempfile"], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true) + MyError = Class.new(StandardError) + String.prepend(Module.new do + def initialize_dup(_) + if $raise_on_dup + raise MyError + else + super + end + end + end) + + code = proc do |t| + Tempfile.create do |f| + $raise_on_dup = true + t.times do + RubyVM::InstructionSequence.new(f) + rescue MyError + else + raise "MyError was not raised during RubyVM::InstructionSequence.new" + end + ensure + $raise_on_dup = false + end + end + + code.call(100) + begin; + code.call(1_000_000) + end; + end + def test_frozen_string_literal_compile_option $f = 'f' line = __LINE__ + 2 @@ -808,7 +858,7 @@ class TestISeq < Test::Unit::TestCase GC.start Float(30) } - assert_equal :new, r.take + assert_equal :new, r.value RUBY end @@ -859,9 +909,28 @@ class TestISeq < Test::Unit::TestCase end end + def test_serialize_anonymous_outer_variables + iseq = RubyVM::InstructionSequence.compile(<<~'RUBY') + obj = Object.new + def obj.test + [1].each do + raise "Oops" + rescue + return it + end + end + obj + RUBY + + binary = iseq.to_binary # [Bug # 21370] + roundtripped_iseq = RubyVM::InstructionSequence.load_from_binary(binary) + object = roundtripped_iseq.eval + assert_equal 1, object.test + end + def test_loading_kwargs_memory_leak assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", rss: true) - a = iseq_to_binary(RubyVM::InstructionSequence.compile("foo(bar: :baz)")) + a = RubyVM::InstructionSequence.compile("foo(bar: :baz)").to_binary begin; 1_000_000.times do RubyVM::InstructionSequence.load_from_binary(a) diff --git a/test/ruby/test_marshal.rb b/test/ruby/test_marshal.rb index 2aa2a38f80..bfb4a9056e 100644 --- a/test/ruby/test_marshal.rb +++ b/test/ruby/test_marshal.rb @@ -268,7 +268,11 @@ class TestMarshal < Test::Unit::TestCase classISO8859_1.name ClassISO8859_1 = classISO8859_1 - def test_class_nonascii + moduleUTF8 = const_set("C\u{30af 30e9 30b9}", Module.new) + moduleUTF8.name + ModuleUTF8 = moduleUTF8 + + def test_nonascii_class_instance a = ClassUTF8.new assert_instance_of(ClassUTF8, Marshal.load(Marshal.dump(a)), '[ruby-core:24790]') @@ -301,6 +305,12 @@ class TestMarshal < Test::Unit::TestCase end end + def test_nonascii_class_module + assert_same(ClassUTF8, Marshal.load(Marshal.dump(ClassUTF8))) + assert_same(ClassISO8859_1, Marshal.load(Marshal.dump(ClassISO8859_1))) + assert_same(ModuleUTF8, Marshal.load(Marshal.dump(ModuleUTF8))) + end + def test_regexp2 assert_equal(/\\u/, Marshal.load("\004\b/\b\\\\u\000")) assert_equal(/u/, Marshal.load("\004\b/\a\\u\000")) diff --git a/test/ruby/test_memory_view.rb b/test/ruby/test_memory_view.rb index 5a39084d18..d0122ddd59 100644 --- a/test/ruby/test_memory_view.rb +++ b/test/ruby/test_memory_view.rb @@ -335,7 +335,7 @@ class TestMemoryView < Test::Unit::TestCase p mv[[0, 2]] mv[[1, 3]] end - p r.take + p r.value end; end end diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index a865f6100b..08f794fa0e 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -1612,7 +1612,7 @@ class TestMethod < Test::Unit::TestCase begin foo(1) rescue ArgumentError => e - assert_equal "main.rb:#{$line_method}:in 'foo'", e.backtrace.first + assert_equal "main.rb:#{$line_method}:in 'Object#foo'", e.backtrace.first end EOS END_OF_BODY diff --git a/test/ruby/test_namespace.rb b/test/ruby/test_namespace.rb index 395f244c8e..cd59306867 100644 --- a/test/ruby/test_namespace.rb +++ b/test/ruby/test_namespace.rb @@ -222,6 +222,26 @@ class TestNamespace < Test::Unit::TestCase end; end + def test_instance_variable + pend unless Namespace.enabled? + + @n.require_relative('namespace/instance_variables') + + assert_equal [], String.instance_variables + assert_equal [:@str_ivar1, :@str_ivar2], @n::StringDelegatorObj.instance_variables + assert_equal 111, @n::StringDelegatorObj.str_ivar1 + assert_equal 222, @n::StringDelegatorObj.str_ivar2 + assert_equal 222, @n::StringDelegatorObj.instance_variable_get(:@str_ivar2) + + @n::StringDelegatorObj.instance_variable_set(:@str_ivar3, 333) + assert_equal 333, @n::StringDelegatorObj.instance_variable_get(:@str_ivar3) + @n::StringDelegatorObj.remove_instance_variable(:@str_ivar1) + assert_nil @n::StringDelegatorObj.str_ivar1 + assert_equal [:@str_ivar2, :@str_ivar3], @n::StringDelegatorObj.instance_variables + + assert_equal [], String.instance_variables + end + def test_methods_added_in_namespace_are_invisible_globally pend unless Namespace.enabled? @@ -513,4 +533,86 @@ class TestNamespace < Test::Unit::TestCase assert !$LOADED_FEATURES.include?(File.join(namespace_dir, 'blank1.rb')) assert !$LOADED_FEATURES.include?(File.join(namespace_dir, 'blank2.rb')) end + + def test_eval_basic + pend unless Namespace.enabled? + + # Test basic evaluation + result = @n.eval("1 + 1") + assert_equal 2, result + + # Test string evaluation + result = @n.eval("'hello ' + 'world'") + assert_equal "hello world", result + end + + def test_eval_with_constants + pend unless Namespace.enabled? + + # Define a constant in the namespace via eval + @n.eval("TEST_CONST = 42") + assert_equal 42, @n::TEST_CONST + + # Constant should not be visible in main namespace + assert_raise(NameError) { TEST_CONST } + end + + def test_eval_with_classes + pend unless Namespace.enabled? + + # Define a class in the namespace via eval + @n.eval("class TestClass; def hello; 'from namespace'; end; end") + + # Class should be accessible in the namespace + instance = @n::TestClass.new + assert_equal "from namespace", instance.hello + + # Class should not be visible in main namespace + assert_raise(NameError) { TestClass } + end + + def test_eval_isolation + pend unless Namespace.enabled? + + # Create another namespace + n2 = Namespace.new + + # Define different constants in each namespace + @n.eval("ISOLATION_TEST = 'first'") + n2.eval("ISOLATION_TEST = 'second'") + + # Each namespace should have its own constant + assert_equal "first", @n::ISOLATION_TEST + assert_equal "second", n2::ISOLATION_TEST + + # Constants should not interfere with each other + assert_not_equal @n::ISOLATION_TEST, n2::ISOLATION_TEST + end + + def test_eval_with_variables + pend unless Namespace.enabled? + + # Test local variable access (should work within the eval context) + result = @n.eval("x = 10; y = 20; x + y") + assert_equal 30, result + end + + def test_eval_error_handling + pend unless Namespace.enabled? + + # Test syntax error + assert_raise(SyntaxError) { @n.eval("1 +") } + + # Test name error + assert_raise(NameError) { @n.eval("undefined_variable") } + + # Test that namespace is properly restored after error + begin + @n.eval("raise RuntimeError, 'test error'") + rescue RuntimeError + # Should be able to continue using the namespace + result = @n.eval("2 + 2") + assert_equal 4, result + end + end end diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb index 7d00422629..9074e54df5 100644 --- a/test/ruby/test_object.rb +++ b/test/ruby/test_object.rb @@ -950,6 +950,19 @@ class TestObject < Test::Unit::TestCase assert_match(/\bInspect\u{3042}:.* @\u{3044}=42\b/, x.inspect) x.instance_variable_set("@\u{3046}".encode(Encoding::EUC_JP), 6) assert_match(/@\u{3046}=6\b/, x.inspect) + + x = Object.new + x.singleton_class.class_eval do + private def instance_variables_to_inspect = [:@host, :@user] + end + + x.instance_variable_set(:@host, "localhost") + x.instance_variable_set(:@user, "root") + x.instance_variable_set(:@password, "hunter2") + s = x.inspect + assert_include(s, "@host=\"localhost\"") + assert_include(s, "@user=\"root\"") + assert_not_include(s, "@password=") end def test_singleton_methods diff --git a/test/ruby/test_object_id.rb b/test/ruby/test_object_id.rb index 97ed70d839..24434f8aba 100644 --- a/test/ruby/test_object_id.rb +++ b/test/ruby/test_object_id.rb @@ -115,6 +115,14 @@ class TestObjectId < Test::Unit::TestCase assert_equal 42, copy.instance_variable_get(:@foo) refute_predicate copy, :frozen? end + + def test_object_id_need_resize + (3 - @obj.instance_variables.size).times do |i| + @obj.instance_variable_set("@a_#{i}", "[Bug #21445]") + end + @obj.object_id + GC.start + end end class TestObjectIdClass < TestObjectId @@ -131,6 +139,9 @@ end class TestObjectIdTooComplex < TestObjectId class TooComplex + def initialize + @too_complex_obj_id_test = 1 + end end def setup @@ -195,3 +206,49 @@ class TestObjectIdTooComplexGeneric < TestObjectId end end end + +class TestObjectIdRactor < Test::Unit::TestCase + def test_object_id_race_free + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + Warning[:experimental] = false + class MyClass + attr_reader :a, :b, :c + def initialize + @a = @b = @c = nil + end + end + N = 10_000 + objs = Ractor.make_shareable(N.times.map { MyClass.new }) + results = 4.times.map{ + Ractor.new(objs) { |objs| + vars = [] + ids = [] + objs.each do |obj| + vars << obj.a << obj.b << obj.c + ids << obj.object_id + end + [vars, ids] + } + }.map(&:value) + assert_equal 1, results.uniq.size + end; + end + + def test_external_object_id_ractor_move + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + Warning[:experimental] = false + class MyClass + attr_reader :a, :b, :c + def initialize + @a = @b = @c = nil + end + end + obj = Ractor.make_shareable(MyClass.new) + object_id = obj.object_id + obj = Ractor.new { Ractor.receive }.send(obj, move: true).value + assert_equal object_id, obj.object_id + end; + end +end diff --git a/test/ruby/test_objectspace.rb b/test/ruby/test_objectspace.rb index ef85edc5ee..f27f586ab7 100644 --- a/test/ruby/test_objectspace.rb +++ b/test/ruby/test_objectspace.rb @@ -94,7 +94,7 @@ End end def test_finalizer - assert_in_out_err(["-e", <<-END], "", %w(:ok :ok :ok :ok), []) + assert_in_out_err(["-e", <<-END], "", %w(:ok :ok :ok), []) a = [] ObjectSpace.define_finalizer(a) { p :ok } b = a.dup @@ -137,6 +137,25 @@ End } end + def test_finalizer_copy + assert_in_out_err(["-e", <<~'RUBY'], "", %w(:ok), []) + def fin + ids = Set.new + ->(id) { puts "object_id (#{id}) reused" unless ids.add?(id) } + end + + OBJ = Object.new + ObjectSpace.define_finalizer(OBJ, fin) + OBJ.freeze + + 10.times do + OBJ.clone + end + + p :ok + RUBY + end + def test_finalizer_with_super assert_in_out_err(["-e", <<-END], "", %w(:ok), []) class A diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index 35aa16063d..2cd97ca324 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -1637,6 +1637,10 @@ class TestProc < Test::Unit::TestCase assert_equal(3, b.local_variable_get(:when)) assert_equal(4, b.local_variable_get(:begin)) assert_equal(5, b.local_variable_get(:end)) + + assert_raise_with_message(NameError, /local variable \Wdefault\W/) { + binding.local_variable_get(:default) + } end def test_local_variable_set @@ -1651,7 +1655,7 @@ class TestProc < Test::Unit::TestCase def test_numparam_is_not_local_variables "foo".tap do - _9 + _9 and flunk assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } @@ -1670,7 +1674,7 @@ class TestProc < Test::Unit::TestCase assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } "bar".tap do - _9 + _9 and flunk assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index 2d9d1416aa..f1894ab0c3 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -1682,9 +1682,10 @@ class TestProcess < Test::Unit::TestCase if u = Etc.getpwuid(Process.uid) assert_equal(Process.uid, Process::UID.from_name(u.name), u.name) end - assert_raise_with_message(ArgumentError, /\u{4e0d 5b58 5728}/) { + exc = assert_raise_kind_of(ArgumentError, SystemCallError) { Process::UID.from_name("\u{4e0d 5b58 5728}") } + assert_match(/\u{4e0d 5b58 5728}/, exc.message) if exc.is_a?(ArgumentError) end end @@ -2769,7 +2770,9 @@ EOS Process.warmup - assert_equal(total_slots_before, GC.stat(:heap_available_slots) + GC.stat(:heap_allocatable_slots)) + # TODO: flaky + # assert_equal(total_slots_before, GC.stat(:heap_available_slots) + GC.stat(:heap_allocatable_slots)) + assert_equal(0, GC.stat(:heap_empty_pages)) assert_operator(GC.stat(:total_freed_pages), :>, 0) end; diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index ec94df361f..97af7e7413 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -60,17 +60,90 @@ class TestRactor < Test::Unit::TestCase assert_unshareable(x, "can not make shareable object for #", exception: Ractor::Error) end + def test_shareability_error_uses_inspect + x = (+"").instance_exec { method(:to_s) } + def x.to_s + raise "this should not be called" + end + assert_unshareable(x, "can not make shareable object for #", exception: Ractor::Error) + end + def test_default_thread_group assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; Warning[:experimental] = false main_ractor_id = Thread.current.group.object_id - ractor_id = Ractor.new { Thread.current.group.object_id }.take + ractor_id = Ractor.new { Thread.current.group.object_id }.value refute_equal main_ractor_id, ractor_id end; end + def test_class_instance_variables + assert_ractor(<<~'RUBY') + # Once we're in multi-ractor mode, the codepaths + # for class instance variables are a bit different. + Ractor.new {}.value + + class TestClass + @a = 1 + @b = 2 + @c = 3 + @d = 4 + end + + assert_equal 4, TestClass.remove_instance_variable(:@d) + assert_nil TestClass.instance_variable_get(:@d) + assert_equal 4, TestClass.instance_variable_set(:@d, 4) + assert_equal 4, TestClass.instance_variable_get(:@d) + RUBY + end + + def test_fork_raise_isolation_error + assert_ractor(<<~'RUBY') + ractor = Ractor.new do + Process.fork + rescue Ractor::IsolationError => e + e + end + assert_equal Ractor::IsolationError, ractor.value.class + RUBY + end if Process.respond_to?(:fork) + + def test_require_raises_and_no_ractor_belonging_issue + assert_ractor(<<~'RUBY') + require "tempfile" + f = Tempfile.new(["file_to_require_from_ractor", ".rb"]) + f.write("raise 'uh oh'") + f.flush + err_msg = Ractor.new(f.path) do |path| + begin + require path + rescue RuntimeError => e + e.message # had confirm belonging issue here + else + nil + end + end.value + assert_equal "uh oh", err_msg + RUBY + end + + def test_require_non_string + assert_ractor(<<~'RUBY') + require "tempfile" + require "pathname" + f = Tempfile.new(["file_to_require_from_ractor", ".rb"]) + f.write("") + f.flush + result = Ractor.new(f.path) do |path| + require Pathname.new(path) + "success" + end.value + assert_equal "success", result + RUBY + end + def assert_make_shareable(obj) refute Ractor.shareable?(obj), "object was already shareable" Ractor.make_shareable(obj) diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb index 78269f8e9a..7885acc87e 100644 --- a/test/ruby/test_regexp.rb +++ b/test/ruby/test_regexp.rb @@ -1308,6 +1308,9 @@ class TestRegexp < Test::Unit::TestCase assert_match(/\A[[:space:]]+\z/, "\r\n\v\f\r\s\u0085") assert_match(/\A[[:ascii:]]+\z/, "\x00\x7F") assert_no_match(/[[:ascii:]]/, "\x80\xFF") + + assert_match(/[[:word:]]/, "\u{200C}") + assert_match(/[[:word:]]/, "\u{200D}") end def test_cclass_R @@ -1875,6 +1878,12 @@ class TestRegexp < Test::Unit::TestCase end; end + def test_too_big_number_for_repeat_range + assert_raise_with_message(SyntaxError, /too big number for repeat range/) do + eval(%[/|{1000000}/]) + end + end + # This assertion is for porting x2() tests in testpy.py of Onigmo. def assert_match_at(re, str, positions, msg = nil) re = Regexp.new(re) unless re.is_a?(Regexp) diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 833b6a3b7d..631b188677 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -787,6 +787,12 @@ class TestRubyOptions < Test::Unit::TestCase unless /mswin|mingw/ =~ RUBY_PLATFORM opts[:rlimit_core] = 0 end + opts[:failed] = proc do |status, message = "", out = ""| + if (sig = status.termsig) && Signal.list["SEGV"] == sig + out = "" + end + Test::Unit::CoreAssertions::FailDesc[status, message] + end ExecOptions = opts.freeze # The regexp list that should match the entire stderr output. @@ -836,8 +842,6 @@ class TestRubyOptions < Test::Unit::TestCase end def assert_segv(args, message=nil, list: SEGVTest::ExpectedStderrList, **opt, &block) - pend "macOS 15 is not working with this assertion" if macos?(15) - # We want YJIT to be enabled in the subprocess if it's enabled for us # so that the Ruby description matches. env = Hash === args.first ? args.shift : {} @@ -849,7 +853,11 @@ class TestRubyOptions < Test::Unit::TestCase args.unshift(env) test_stdin = "" - tests = [//, list] unless block + if !block + tests = [//, list, message] + elsif message + tests = [[], [], message] + end assert_in_out_err(args, test_stdin, *tests, encoding: "ASCII-8BIT", **SEGVTest::ExecOptions, **opt, &block) @@ -862,13 +870,12 @@ class TestRubyOptions < Test::Unit::TestCase def test_segv_loaded_features bug7402 = '[ruby-core:49573]' - status = assert_segv(['-e', "END {#{SEGVTest::KILL_SELF}}", - '-e', 'class Bogus; def to_str; exit true; end; end', - '-e', '$".clear', - '-e', '$".unshift Bogus.new', - '-e', '(p $"; abort) unless $".size == 1', - ]) - assert_not_predicate(status, :success?, "segv but success #{bug7402}") + assert_segv(['-e', "END {#{SEGVTest::KILL_SELF}}", + '-e', 'class Bogus; def to_str; exit true; end; end', + '-e', '$".clear', + '-e', '$".unshift Bogus.new', + '-e', '(p $"; abort) unless $".size == 1', + ], bug7402, success: false) end def test_segv_setproctitle @@ -881,8 +888,6 @@ class TestRubyOptions < Test::Unit::TestCase end def assert_crash_report(path, cmd = nil, &block) - pend "macOS 15 is not working with this assertion" if macos?(15) - Dir.mktmpdir("ruby_crash_report") do |dir| list = SEGVTest::ExpectedStderrList if cmd diff --git a/test/ruby/test_set.rb b/test/ruby/test_set.rb index 2bb7858eb2..87e1fd8d26 100644 --- a/test/ruby/test_set.rb +++ b/test/ruby/test_set.rb @@ -129,6 +129,12 @@ class TC_Set < Test::Unit::TestCase assert_same(set, ret) assert_equal(Set['a','b','c'], set) + set = Set[1,2] + ret = set.replace(Set.new('a'..'c')) + + assert_same(set, ret) + assert_equal(Set['a','b','c'], set) + set = Set[1,2] assert_raise(ArgumentError) { set.replace(3) @@ -775,6 +781,10 @@ class TC_Set < Test::Unit::TestCase ret.each { |s| n += s.size } assert_equal(set.size, n) assert_equal(set, ret.flatten) + + set = Set[2,12,9,11,13,4,10,15,3,8,5,0,1,7,14] + ret = set.divide { |a,b| (a - b).abs == 1 } + assert_equal(2, ret.size) end def test_freeze @@ -829,24 +839,28 @@ class TC_Set < Test::Unit::TestCase def test_inspect set1 = Set[1, 2] - assert_equal('#', set1.inspect) + assert_equal('Set[1, 2]', set1.inspect) set2 = Set[Set[0], 1, 2, set1] - assert_equal('#, 1, 2, #}>', set2.inspect) + assert_equal('Set[Set[0], 1, 2, Set[1, 2]]', set2.inspect) set1.add(set2) - assert_equal('#, 1, 2, #}>}>', set2.inspect) + assert_equal('Set[Set[0], 1, 2, Set[1, 2, Set[...]]]', set2.inspect) + + c = Class.new(Set) + c.set_temporary_name("_MySet") + assert_equal('_MySet[1, 2]', c[1, 2].inspect) end def test_to_s set1 = Set[1, 2] - assert_equal('#', set1.to_s) + assert_equal('Set[1, 2]', set1.to_s) set2 = Set[Set[0], 1, 2, set1] - assert_equal('#, 1, 2, #}>', set2.to_s) + assert_equal('Set[Set[0], 1, 2, Set[1, 2]]', set2.to_s) set1.add(set2) - assert_equal('#, 1, 2, #}>}>', set2.to_s) + assert_equal('Set[Set[0], 1, 2, Set[1, 2, Set[...]]]', set2.to_s) end def test_compare_by_identity diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb index 55c07abbea..fac6dd8185 100644 --- a/test/ruby/test_settracefunc.rb +++ b/test/ruby/test_settracefunc.rb @@ -1999,7 +1999,7 @@ CODE TracePoint.new(:c_call, &capture_events).enable{ c.new } - assert_equal [:c_call, :itself, :initialize], events[1] + assert_equal [:c_call, :itself, :initialize], events[0] events.clear o = Class.new{ diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb index 0458b3235b..a4cf23c6d5 100644 --- a/test/ruby/test_shapes.rb +++ b/test/ruby/test_shapes.rb @@ -92,15 +92,18 @@ class TestShapes < Test::Unit::TestCase # RubyVM::Shape.of returns new instances of shape objects for # each call. This helper method allows us to define equality for # shapes - def assert_shape_equal(shape1, shape2) - assert_equal(shape1.id, shape2.id) - assert_equal(shape1.parent_id, shape2.parent_id) - assert_equal(shape1.depth, shape2.depth) - assert_equal(shape1.type, shape2.type) + def assert_shape_equal(e, a) + assert_equal( + {id: e.id, parent_id: e.parent_id, depth: e.depth, type: e.type}, + {id: a.id, parent_id: a.parent_id, depth: a.depth, type: a.type}, + ) end - def refute_shape_equal(shape1, shape2) - refute_equal(shape1.id, shape2.id) + def refute_shape_equal(e, a) + refute_equal( + {id: e.id, parent_id: e.parent_id, depth: e.depth, type: e.type}, + {id: a.id, parent_id: a.parent_id, depth: a.depth, type: a.type}, + ) end def test_iv_order_correct_on_complex_objects @@ -596,8 +599,8 @@ class TestShapes < Test::Unit::TestCase assert_predicate RubyVM::Shape.of(tc), :too_complex? assert_equal 3, tc.very_unique - assert_equal 3, Ractor.new(tc) { |x| Ractor.yield(x.very_unique) }.take - assert_equal tc.instance_variables.sort, Ractor.new(tc) { |x| Ractor.yield(x.instance_variables) }.take.sort + assert_equal 3, Ractor.new(tc) { |x| x.very_unique }.value + assert_equal tc.instance_variables.sort, Ractor.new(tc) { |x| x.instance_variables }.value.sort end; end @@ -651,6 +654,22 @@ class TestShapes < Test::Unit::TestCase end; end + def test_object_id_transition_too_complex + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Hi; end + obj = Hi.new + obj.instance_variable_set(:@a, 1) + obj.instance_variable_set(:@b, 2) + old_id = obj.object_id + + RubyVM::Shape.exhaust_shapes + obj.remove_instance_variable(:@a) + + assert_equal old_id, obj.object_id + end; + end + def test_too_complex_and_frozen_and_object_id assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; @@ -676,7 +695,7 @@ class TestShapes < Test::Unit::TestCase assert_predicate frozen_shape, :shape_frozen? refute_predicate frozen_shape, :has_object_id? - tc.object_id + assert_equal tc.object_id, tc.object_id id_shape = RubyVM::Shape.of(tc) refute_equal frozen_shape.id, id_shape.id @@ -699,10 +718,10 @@ class TestShapes < Test::Unit::TestCase r = Ractor.new do o = Object.new o.instance_variable_set(:@a, "hello") - Ractor.yield(o) + o end - o = r.take + o = r.value assert_equal "hello", o.instance_variable_get(:@a) end; end @@ -717,10 +736,10 @@ class TestShapes < Test::Unit::TestCase r = Ractor.new do o = [] o.instance_variable_set(:@a, "hello") - Ractor.yield(o) + o end - o = r.take + o = r.value assert_equal "hello", o.instance_variable_get(:@a) end; end @@ -886,13 +905,15 @@ class TestShapes < Test::Unit::TestCase def test_remove_instance_variable_capacity_transition assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; - t_object_shape = RubyVM::Shape.find_by_id(RubyVM::Shape::FIRST_T_OBJECT_SHAPE_ID) - assert_equal(RubyVM::Shape::SHAPE_T_OBJECT, t_object_shape.type) - - initial_capacity = t_object_shape.capacity # a does not transition in capacity a = Class.new.new + root_shape = RubyVM::Shape.of(a) + + assert_equal(RubyVM::Shape::SHAPE_ROOT, root_shape.type) + initial_capacity = root_shape.capacity + refute_equal(0, initial_capacity) + initial_capacity.times do |i| a.instance_variable_set(:"@ivar#{i + 1}", i) end @@ -957,7 +978,7 @@ class TestShapes < Test::Unit::TestCase example.add_foo # makes a transition add_foo_shape = RubyVM::Shape.of(example) assert_equal([:@foo], example.instance_variables) - assert_equal(initial_shape.id, add_foo_shape.parent.id) + assert_equal(initial_shape.raw_id, add_foo_shape.parent.raw_id) assert_equal(1, add_foo_shape.next_field_index) example.remove_foo # makes a transition @@ -968,7 +989,7 @@ class TestShapes < Test::Unit::TestCase example.add_bar # makes a transition bar_shape = RubyVM::Shape.of(example) assert_equal([:@bar], example.instance_variables) - assert_equal(initial_shape.id, bar_shape.parent_id) + assert_equal(initial_shape.raw_id, bar_shape.parent_id) assert_equal(1, bar_shape.next_field_index) end @@ -988,7 +1009,7 @@ class TestShapes < Test::Unit::TestCase def test_new_obj_has_t_object_shape obj = TestObject.new shape = RubyVM::Shape.of(obj) - assert_equal RubyVM::Shape::SHAPE_T_OBJECT, shape.type + assert_equal RubyVM::Shape::SHAPE_ROOT, shape.type assert_nil shape.parent end @@ -1020,7 +1041,7 @@ class TestShapes < Test::Unit::TestCase assert_equal RubyVM::Shape::SHAPE_IVAR, shape.type shape = shape.parent - assert_equal RubyVM::Shape::SHAPE_T_OBJECT, shape.type + assert_equal RubyVM::Shape::SHAPE_ROOT, shape.type assert_nil shape.parent assert_equal(1, obj.instance_variable_get(:@a)) @@ -1055,11 +1076,12 @@ class TestShapes < Test::Unit::TestCase def test_freezing_and_duplicating_object obj = Object.new.freeze + assert_predicate(RubyVM::Shape.of(obj), :shape_frozen?) + + # dup'd objects shouldn't be frozen obj2 = obj.dup refute_predicate(obj2, :frozen?) - # dup'd objects shouldn't be frozen, and the shape should be the - # parent shape of the copied object - assert_equal(RubyVM::Shape.of(obj).parent.id, RubyVM::Shape.of(obj2).id) + refute_predicate(RubyVM::Shape.of(obj2), :shape_frozen?) end def test_freezing_and_duplicating_object_with_ivars @@ -1076,6 +1098,7 @@ class TestShapes < Test::Unit::TestCase str.freeze str2 = str.dup refute_predicate(str2, :frozen?) + refute_equal(RubyVM::Shape.of(str).id, RubyVM::Shape.of(str2).id) assert_equal(str2.instance_variable_get(:@a), 1) end @@ -1092,8 +1115,7 @@ class TestShapes < Test::Unit::TestCase obj2 = obj.clone(freeze: true) assert_predicate(obj2, :frozen?) refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) - assert_equal(RubyVM::Shape::SHAPE_FROZEN, RubyVM::Shape.of(obj2).type) - assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2).parent) + assert_predicate(RubyVM::Shape.of(obj2), :shape_frozen?) end def test_freezing_and_cloning_object_with_ivars diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 1011967fe9..138756eac5 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -1869,6 +1869,13 @@ CODE result = []; S("aaa,bbb,ccc,ddd").split(/,/) {|s| result << s.gsub(/./, "A")} assert_equal(["AAA"]*4, result) + + s = S("abc ") * 20 + assert_raise(RuntimeError) { + 10.times do + s.split {s.prepend("xxx" * 100)} + end + } ensure EnvUtil.suppress_warning {$; = fs} end diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb index 3d727adf04..db591c306e 100644 --- a/test/ruby/test_struct.rb +++ b/test/ruby/test_struct.rb @@ -535,6 +535,8 @@ module TestStruct end def test_named_structs_are_not_rooted + omit 'skip on riscv64-linux CI machine. See https://github.com/ruby/ruby/pull/13422' if ENV['RUBY_DEBUG'] == 'ci' && /riscv64-linux/ =~ RUBY_DESCRIPTION + # [Bug #20311] assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true) code = proc do @@ -548,6 +550,12 @@ module TestStruct CODE end + def test_frozen_subclass + test = Class.new(@Struct.new(:a)).freeze.new(a: 0) + assert_kind_of(@Struct, test) + assert_equal([:a], test.members) + end + class TopStruct < Test::Unit::TestCase include TestStruct diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 62f1d99bdc..b7e021a4ff 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -1946,6 +1946,15 @@ eom end assert_valid_syntax('proc {def foo(_);end;it}') assert_syntax_error('p { [it **2] }', /unexpected \*\*/) + assert_equal(1, eval('1.then { raise rescue it }')) + assert_equal(2, eval('1.then { 2.then { raise rescue it } }')) + assert_equal(3, eval('3.then { begin; raise; rescue; it; end }')) + assert_equal(4, eval('4.tap { begin; raise ; rescue; raise rescue it; end; }')) + assert_equal(5, eval('a = 0; 5.then { begin; nil; ensure; a = it; end }; a')) + assert_equal(6, eval('a = 0; 6.then { begin; nil; rescue; ensure; a = it; end }; a')) + assert_equal(7, eval('a = 0; 7.then { begin; raise; ensure; a = it; end } rescue a')) + assert_equal(8, eval('a = 0; 8.then { begin; raise; rescue; ensure; a = it; end }; a')) + assert_equal(/9/, eval('9.then { /#{it}/o }')) end def test_value_expr_in_condition diff --git a/test/ruby/test_thread_queue.rb b/test/ruby/test_thread_queue.rb index 545bf98888..9485528977 100644 --- a/test/ruby/test_thread_queue.rb +++ b/test/ruby/test_thread_queue.rb @@ -235,8 +235,14 @@ class TestThreadQueue < Test::Unit::TestCase end _eom rescue Timeout::Error + # record load average: + uptime = `uptime` rescue nil + if uptime && /(load average: [\d.]+),/ =~ uptime + la = " (#{$1})" + end + count = File.read("#{d}/test_thr_kill_count").to_i - flunk "only #{count}/#{total_count} done in #{timeout} seconds." + flunk "only #{count}/#{total_count} done in #{timeout} seconds.#{la}" end } end diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb index 49fec2d40e..cc784e7644 100644 --- a/test/ruby/test_variable.rb +++ b/test/ruby/test_variable.rb @@ -407,6 +407,21 @@ class TestVariable < Test::Unit::TestCase } end + def test_exivar_resize_with_compaction_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + objs = 10_000.times.map do + ExIvar.new + end + EnvUtil.under_gc_compact_stress do + 10.times do + x = ExIvar.new + x.instance_variable_set(:@resize, 1) + x + end + end + objs or flunk + end + def test_local_variables_with_kwarg bug11674 = '[ruby-core:71437] [Bug #11674]' v = with_kwargs_11(v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8,v9:9,v10:10,v11:11) @@ -426,7 +441,7 @@ class TestVariable < Test::Unit::TestCase end def test_local_variables_encoding - α = 1 + α = 1 or flunk b = binding b.eval("".encode("us-ascii")) assert_equal(%i[α b], b.local_variables) diff --git a/test/ruby/test_vm_dump.rb b/test/ruby/test_vm_dump.rb index 709fd5eadf..a3e7b69913 100644 --- a/test/ruby/test_vm_dump.rb +++ b/test/ruby/test_vm_dump.rb @@ -5,8 +5,6 @@ return unless /darwin/ =~ RUBY_PLATFORM class TestVMDump < Test::Unit::TestCase def assert_darwin_vm_dump_works(args, timeout=nil) - pend "macOS 15 is not working with this assertion" if macos?(15) - assert_in_out_err(args, "", [], /^\[IMPORTANT\]/, timeout: timeout || 300) end diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb index 7c0524354b..25399d1e62 100644 --- a/test/ruby/test_yjit.rb +++ b/test/ruby/test_yjit.rb @@ -166,6 +166,11 @@ class TestYJIT < Test::Unit::TestCase end end + if JITSupport.zjit_supported? + def test_yjit_enable_with_zjit_enabled + assert_in_out_err(['--zjit'], 'puts RubyVM::YJIT.enable', ['false'], ['Only one JIT can be enabled at the same time.']) + end + end def test_yjit_stats_and_v_no_error _stdout, stderr, _status = invoke_ruby(%w(-v --yjit-stats), '', true, true) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 4452f413f0..0f83a75db7 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -31,6 +31,20 @@ class TestZJIT < Test::Unit::TestCase } end + def test_putstring + assert_compiles '""', %q{ + def test = "#{""}" + test + }, insns: [:putstring] + end + + def test_putchilldedstring + assert_compiles '""', %q{ + def test = "" + test + }, insns: [:putchilledstring] + end + def test_leave_param assert_compiles '5', %q{ def test(n) = n @@ -48,6 +62,63 @@ class TestZJIT < Test::Unit::TestCase } end + def test_setlocal_on_eval + assert_compiles '1', %q{ + @b = binding + eval('a = 1', @b) + eval('a', @b) + } + end + + def test_setlocal_on_eval_with_spill + assert_compiles '1', %q{ + @b = binding + eval('a = 1; itself', @b) + eval('a', @b) + } + end + + def test_nested_local_access + assert_compiles '[1, 2, 3]', %q{ + 1.times do |l2| + 1.times do |l1| + define_method(:test) do + l1 = 1 + l2 = 2 + l3 = 3 + [l1, l2, l3] + end + end + end + + test + test + test + }, call_threshold: 3, insns: [:getlocal, :setlocal, :getlocal_WC_0, :setlocal_WC_1] + end + + def test_read_local_written_by_children_iseqs + omit "This test fails right now because Send doesn't compile." + + assert_compiles '[1, 2]', %q{ + def test + l1 = nil + l2 = nil + tap do |_| + l1 = 1 + tap do |_| + l2 = 2 + end + end + + [l1, l2] + end + + test + test + }, call_threshold: 2 + end + def test_send_without_block assert_compiles '[1, 2, 3]', %q{ def foo = 1 @@ -62,6 +133,22 @@ class TestZJIT < Test::Unit::TestCase } end + def test_invokebuiltin + omit 'Test fails at the moment due to not handling optional parameters' + assert_compiles '["."]', %q{ + def test = Dir.glob(".") + test + } + end + + def test_invokebuiltin_delegate + assert_compiles '[[], true]', %q{ + def test = [].clone(freeze: true) + r = test + [r, r.frozen?] + } + end + def test_opt_plus_const assert_compiles '3', %q{ def test = 1 + 2 @@ -94,6 +181,51 @@ class TestZJIT < Test::Unit::TestCase }, call_threshold: 2 end + def test_opt_plus_type_guard_exit + assert_compiles '[3, 3.0]', %q{ + def test(a) = 1 + a + test(1) # profile opt_plus + [test(2), test(2.0)] + }, call_threshold: 2 + end + + def test_opt_plus_type_guard_exit_with_locals + assert_compiles '[6, 6.0]', %q{ + def test(a) + local = 3 + 1 + a + local + end + test(1) # profile opt_plus + [test(2), test(2.0)] + }, call_threshold: 2 + end + + def test_opt_plus_type_guard_nested_exit + assert_compiles '[4, 4.0]', %q{ + def side_exit(n) = 1 + n + def jit_frame(n) = 1 + side_exit(n) + def entry(n) = jit_frame(n) + entry(2) # profile send + [entry(2), entry(2.0)] + }, call_threshold: 2 + end + + def test_opt_plus_type_guard_nested_exit_with_locals + assert_compiles '[9, 9.0]', %q{ + def side_exit(n) + local = 2 + 1 + n + local + end + def jit_frame(n) + local = 3 + 1 + side_exit(n) + local + end + def entry(n) = jit_frame(n) + entry(2) # profile send + [entry(2), entry(2.0)] + }, call_threshold: 2 + end + # Test argument ordering def test_opt_minus assert_compiles '2', %q{ @@ -112,7 +244,6 @@ class TestZJIT < Test::Unit::TestCase end def test_opt_mult_overflow - omit 'side exits are not implemented yet' assert_compiles '[6, -6, 9671406556917033397649408, -9671406556917033397649408, 21267647932558653966460912964485513216]', %q{ def test(a, b) a * b @@ -134,7 +265,7 @@ class TestZJIT < Test::Unit::TestCase def test(a, b) = a == b test(0, 2) # profile opt_eq [test(1, 1), test(0, 1)] - }, call_threshold: 2 + }, insns: [:opt_eq], call_threshold: 2 end def test_opt_neq_dynamic @@ -144,7 +275,7 @@ class TestZJIT < Test::Unit::TestCase def test(a, b) = a != b test(0, 2) # profile opt_neq [test(1, 1), test(0, 1)] - }, call_threshold: 1 + }, insns: [:opt_neq], call_threshold: 1 end def test_opt_neq_fixnum @@ -160,7 +291,7 @@ class TestZJIT < Test::Unit::TestCase def test(a, b) = a < b test(2, 3) # profile opt_lt [test(0, 1), test(0, 0), test(1, 0)] - }, call_threshold: 2 + }, insns: [:opt_lt], call_threshold: 2 end def test_opt_lt_with_literal_lhs @@ -168,7 +299,7 @@ class TestZJIT < Test::Unit::TestCase def test(n) = 2 < n test(2) # profile opt_lt [test(1), test(2), test(3)] - }, call_threshold: 2 + }, insns: [:opt_lt], call_threshold: 2 end def test_opt_le @@ -176,7 +307,7 @@ class TestZJIT < Test::Unit::TestCase def test(a, b) = a <= b test(2, 3) # profile opt_le [test(0, 1), test(0, 0), test(1, 0)] - }, call_threshold: 2 + }, insns: [:opt_le], call_threshold: 2 end def test_opt_gt @@ -184,7 +315,95 @@ class TestZJIT < Test::Unit::TestCase def test(a, b) = a > b test(2, 3) # profile opt_gt [test(0, 1), test(0, 0), test(1, 0)] - }, call_threshold: 2 + }, insns: [:opt_gt], call_threshold: 2 + end + + def test_opt_empty_p + assert_compiles('[false, false, true]', <<~RUBY, insns: [:opt_empty_p]) + def test(x) = x.empty? + return test([1]), test("1"), test({}) + RUBY + end + + def test_opt_succ + assert_compiles('[0, "B"]', <<~RUBY, insns: [:opt_succ]) + def test(obj) = obj.succ + return test(-1), test("A") + RUBY + end + + def test_opt_and + assert_compiles('[1, [3, 2, 1]]', <<~RUBY, insns: [:opt_and]) + def test(x, y) = x & y + return test(0b1101, 3), test([3, 2, 1, 4], [8, 1, 2, 3]) + RUBY + end + + def test_opt_or + assert_compiles('[11, [3, 2, 1]]', <<~RUBY, insns: [:opt_or]) + def test(x, y) = x | y + return test(0b1000, 3), test([3, 2, 1], [1, 2, 3]) + RUBY + end + + def test_fixnum_and + assert_compiles '1', %q{ + def test(a, b) = a & b + test(2, 2) + test(2, 2) + test(5, 3) + }, call_threshold: 2, insns: [:opt_and] + end + + def test_fixnum_and_side_exit + assert_compiles 'false', %q{ + def test(a, b) = a & b + test(2, 2) + test(2, 2) + test(true, false) + }, call_threshold: 2, insns: [:opt_and] + end + + def test_fixnum_or + assert_compiles '3', %q{ + def test(a, b) = a | b + test(5, 3) + test(5, 3) + test(1, 2) + }, call_threshold: 2, insns: [:opt_or] + end + + def test_fixnum_or_side_exit + assert_compiles 'true', %q{ + def test(a, b) = a | b + test(2, 2) + test(2, 2) + test(true, false) + }, call_threshold: 2, insns: [:opt_or] + end + + def test_fixnum_mul + assert_compiles '12', %q{ + C = 3 + def test(n) = C * n + test(4) + test(4) + test(4) + }, call_threshold: 2, insns: [:opt_mult] + end + + def test_opt_not + assert_compiles('[true, true, false]', <<~RUBY, insns: [:opt_not]) + def test(obj) = !obj + return test(nil), test(false), test(0) + RUBY + end + + def test_opt_regexpmatch2 + assert_compiles('[1, nil]', <<~RUBY, insns: [:opt_regexpmatch2]) + def test(haystack) = /needle/ =~ haystack + return test("kneedle"), test("") + RUBY end def test_opt_ge @@ -192,14 +411,42 @@ class TestZJIT < Test::Unit::TestCase def test(a, b) = a >= b test(2, 3) # profile opt_ge [test(0, 1), test(0, 0), test(1, 0)] - }, call_threshold: 2 + }, insns: [:opt_ge], call_threshold: 2 + end + + def test_opt_hash_freeze + assert_compiles '{}', <<~RUBY, insns: [:opt_hash_freeze] + def test = {}.freeze + test + RUBY + end + + def test_opt_ary_freeze + assert_compiles '[]', <<~RUBY, insns: [:opt_ary_freeze] + def test = [].freeze + test + RUBY + end + + def test_opt_str_freeze + assert_compiles '""', <<~RUBY, insns: [:opt_str_freeze] + def test = "".freeze + test + RUBY + end + + def test_opt_str_uminus + assert_compiles '""', <<~RUBY, insns: [:opt_str_uminus] + def test = -"" + test + RUBY end def test_new_array_empty assert_compiles '[]', %q{ def test = [] test - } + }, insns: [:newarray] end def test_new_array_nonempty @@ -227,6 +474,27 @@ class TestZJIT < Test::Unit::TestCase } end + def test_new_range_inclusive + assert_compiles '1..5', %q{ + def test(a, b) = a..b + test(1, 5) + } + end + + def test_new_range_exclusive + assert_compiles '1...5', %q{ + def test(a, b) = a...b + test(1, 5) + } + end + + def test_new_range_with_literal + assert_compiles '3..10', %q{ + def test(n) = n..10 + test(3) + } + end + def test_if assert_compiles '[0, nil]', %q{ def test(n) @@ -487,6 +755,234 @@ class TestZJIT < Test::Unit::TestCase }, call_threshold: 5, num_profiles: 3 end + def test_spilled_basic_block_args + assert_compiles '55', %q{ + def test(n1, n2) + n3 = 3 + n4 = 4 + n5 = 5 + n6 = 6 + n7 = 7 + n8 = 8 + n9 = 9 + n10 = 10 + if n1 < n2 + n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10 + end + end + test(1, 2) + } + end + + def test_spilled_method_args + assert_runs '55', %q{ + def foo(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10) + n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10 + end + + def test + foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + end + + test + } + + assert_compiles '1', %q{ + def a(n1,n2,n3,n4,n5,n6,n7,n8,n9) = n1+n9 + a(2,0,0,0,0,0,0,0,-1) + } + end + + def test_opt_aref_with + assert_compiles ':ok', %q{ + def aref_with(hash) = hash["key"] + + aref_with({ "key" => :ok }) + } + end + + def test_putself + assert_compiles '3', %q{ + class Integer + def minus(a) + self - a + end + end + 5.minus(2) + } + end + + def test_getinstancevariable + assert_compiles 'nil', %q{ + def test() = @foo + + test() + } + assert_compiles '3', %q{ + @foo = 3 + def test() = @foo + + test() + } + end + + def test_setinstancevariable + assert_compiles '1', %q{ + def test() = @foo = 1 + + test() + @foo + } + end + + def test_uncached_getconstant_path + assert_compiles RUBY_COPYRIGHT.dump, %q{ + def test = RUBY_COPYRIGHT + test + }, call_threshold: 1, insns: [:opt_getconstant_path] + end + + def test_getconstant_path_autoload + # A constant-referencing expression can run arbitrary code through Kernel#autoload. + Dir.mktmpdir('autoload') do |tmpdir| + autoload_path = File.join(tmpdir, 'test_getconstant_path_autoload.rb') + File.write(autoload_path, 'X = RUBY_COPYRIGHT') + + assert_compiles RUBY_COPYRIGHT.dump, %Q{ + Object.autoload(:X, #{File.realpath(autoload_path).inspect}) + def test = X + test + }, call_threshold: 1, insns: [:opt_getconstant_path] + end + end + + def test_dupn + assert_compiles '[[1], [1, 1], :rhs, [nil, :rhs]]', <<~RUBY, insns: [:dupn] + def test(array) = (array[1, 2] ||= :rhs) + + one = [1, 1] + start_empty = [] + [test(one), one, test(start_empty), start_empty] + RUBY + end + + def test_send_backtrace + backtrace = [ + "-e:2:in 'Object#jit_frame1'", + "-e:3:in 'Object#entry'", + "-e:5:in 'block in
'", + "-e:6:in '
'", + ] + assert_compiles backtrace.inspect, %q{ + def jit_frame2 = caller # 1 + def jit_frame1 = jit_frame2 # 2 + def entry = jit_frame1 # 3 + entry # profile send # 4 + entry # 5 + }, call_threshold: 2 + end + + def test_bop_invalidation + omit 'Invalidation on BOP redefinition is not implemented yet' + assert_compiles '', %q{ + def test + eval(<<~RUBY) + class Integer + def +(_) = 100 + end + RUBY + 1 + 2 + end + test + } + end + + def test_defined_yield + assert_compiles "nil", "defined?(yield)" + assert_compiles '[nil, nil, "yield"]', %q{ + def test = defined?(yield) + [test, test, test{}] + }, call_threshold: 2, insns: [:defined] + end + + def test_defined_yield_from_block + # This will do some EP hopping to find the local EP, + # so it's slightly different than doing it outside of a block. + + omit 'Test fails at the moment due to missing Send codegen' + + assert_compiles '[nil, nil, "yield"]', %q{ + def test + yield_self { yield_self { defined?(yield) } } + end + + [test, test, test{}] + }, call_threshold: 2, insns: [:defined] + end + + def test_invokeblock_without_block_after_jit_call + assert_compiles '"no block given (yield)"', %q{ + def test(*arr, &b) + arr.class + yield + end + begin + test + rescue => e + e.message + end + } + end + + def test_putspecialobject_vm_core_and_cbase + assert_compiles '10', %q{ + def test + alias bar test + 10 + end + + test + bar + }, insns: [:putspecialobject] + end + + def test_putspecialobject_const_base + assert_compiles '1', %q{ + Foo = 1 + + def test = Foo + + # First call: populates the constant cache + test + # Second call: triggers ZJIT compilation with warm cache + # RubyVM::ZJIT.assert_compiles will panic if this fails to compile + test + }, call_threshold: 2 + end + + def test_branchnil + assert_compiles '[2, nil]', %q{ + def test(x) + x&.succ + end + [test(1), test(nil)] + }, call_threshold: 1, insns: [:branchnil] + end + + def test_nil_nil + assert_compiles 'true', %q{ + def test = nil.nil? + test + }, insns: [:opt_nil_p] + end + + def test_non_nil_nil + assert_compiles 'false', %q{ + def test = 1.nil? + test + }, insns: [:opt_nil_p] + end + # tool/ruby_vm/views/*.erb relies on the zjit instructions a) being contiguous and # b) being reliably ordered after all the other instructions. def test_instruction_order @@ -500,30 +996,303 @@ class TestZJIT < Test::Unit::TestCase end end + def test_require_rubygems + assert_runs 'true', %q{ + require 'rubygems' + }, call_threshold: 2 + end + + def test_require_rubygems_with_auto_compact + assert_runs 'true', %q{ + GC.auto_compact = true + require 'rubygems' + }, call_threshold: 2 + end + + def test_profile_under_nested_jit_call + assert_compiles '[nil, nil, 3]', %q{ + def profile + 1 + 2 + end + + def jit_call(flag) + if flag + profile + end + end + + def entry(flag) + jit_call(flag) + end + + [entry(false), entry(false), entry(true)] + }, call_threshold: 2 + end + + def test_bop_redefinition + assert_runs '[3, :+, 100]', %q{ + def test + 1 + 2 + end + + test # profile opt_plus + [test, Integer.class_eval { def +(_) = 100 }, test] + }, call_threshold: 2 + end + + def test_bop_redefinition_with_adjacent_patch_points + assert_runs '[15, :+, 100]', %q{ + def test + 1 + 2 + 3 + 4 + 5 + end + + test # profile opt_plus + [test, Integer.class_eval { def +(_) = 100 }, test] + }, call_threshold: 2 + end + + def test_module_name_with_guard_passes + assert_compiles '"Integer"', %q{ + def test(mod) + mod.name + end + + test(String) + test(Integer) + }, call_threshold: 2 + end + + def test_module_name_with_guard_side_exit + # This test demonstrates that the guard side exit works correctly + # In this case, when we call with a non-Class object, it should fall back to interpreter + assert_compiles '["String", "Integer", "Bar"]', %q{ + class MyClass + def name = "Bar" + end + + def test(mod) + mod.name + end + + results = [] + results << test(String) + results << test(Integer) + results << test(MyClass.new) + + results + }, call_threshold: 2 + end + + def test_string_bytesize_with_guard + assert_compiles '5', %q{ + def test(str) + str.bytesize + end + + test('hello') + test('world') + }, call_threshold: 2 + end + + def test_nil_value_nil_opt_with_guard + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(nil) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_nil_value_nil_opt_with_guard_side_exit + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(nil) + test(nil) + test(1) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_true_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(true) + test(true) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_true_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(true) + test(true) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_false_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(false) + test(false) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_false_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(false) + test(false) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_integer_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(1) + test(2) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_integer_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(1) + test(2) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_float_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(1.0) + test(2.0) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_float_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(1.0) + test(2.0) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_symbol_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(:foo) + test(:bar) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_symbol_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(:foo) + test(:bar) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_class_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(String) + test(Integer) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_class_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(String) + test(Integer) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_module_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(Enumerable) + test(Kernel) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_module_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(Enumerable) + test(Kernel) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + private # Assert that every method call in `test_script` can be compiled by ZJIT # at a given call_threshold - def assert_compiles(expected, test_script, **opts) + def assert_compiles(expected, test_script, insns: [], **opts) + assert_runs(expected, test_script, insns:, assert_compiles: true, **opts) + end + + # Assert that `test_script` runs successfully with ZJIT enabled. + # Unlike `assert_compiles`, `assert_runs(assert_compiles: false)` + # allows ZJIT to skip compiling methods. + def assert_runs(expected, test_script, insns: [], assert_compiles: false, **opts) pipe_fd = 3 script = <<~RUBY - _test_proc = -> { - RubyVM::ZJIT.assert_compiles - #{test_script} + ret_val = (_test_proc = -> { #{('RubyVM::ZJIT.assert_compiles; ' if assert_compiles)}#{test_script.lstrip} }).call + result = { + ret_val:, + #{ unless insns.empty? + 'insns: RubyVM::InstructionSequence.of(method(:test)).to_a' + end} } - result = _test_proc.call - IO.open(#{pipe_fd}).write(result.inspect) + IO.open(#{pipe_fd}).write(Marshal.dump(result)) RUBY - status, out, err, actual = eval_with_jit(script, pipe_fd:, **opts) + status, out, err, result = eval_with_jit(script, pipe_fd:, **opts) message = "exited with status #{status.to_i}" message << "\nstdout:\n```\n#{out}```\n" unless out.empty? message << "\nstderr:\n```\n#{err}```\n" unless err.empty? assert status.success?, message - assert_equal expected, actual + result = Marshal.load(result) + assert_equal(expected, result.fetch(:ret_val).inspect) + + unless insns.empty? + iseq = result.fetch(:insns) + assert_equal("YARVInstructionSequence/SimpleDataFormat", iseq.first, "failed to get iseq disassembly") + iseq_insns = iseq.last + + expected_insns = Set.new(insns) + iseq_insns.each do + next unless it.is_a?(Array) + expected_insns.delete(it.first) + end + assert(expected_insns.empty?, -> { "Not present in ISeq: #{expected_insns.to_a}" }) + end end # Run a Ruby process with ZJIT options and a pipe for writing test results diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb index eaf3e7037e..af78bab724 100644 --- a/test/rubygems/helper.rb +++ b/test/rubygems/helper.rb @@ -418,6 +418,9 @@ class Gem::TestCase < Test::Unit::TestCase @orig_hooks[name] = Gem.send(name).dup end + Gem::Platform.const_get(:GENERIC_CACHE).clear + Gem::Platform.const_get(:GENERICS).each {|g| Gem::Platform.const_get(:GENERIC_CACHE)[g] = g } + @marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" @orig_loaded_features = $LOADED_FEATURES.dup end @@ -680,6 +683,14 @@ class Gem::TestCase < Test::Unit::TestCase path end + def write_dummy_extconf(gem_name) + write_file File.join(@tempdir, "extconf.rb") do |io| + io.puts "require 'mkmf'" + yield io if block_given? + io.puts "create_makefile '#{gem_name}'" + end + end + ## # Load a YAML string, the psych 3 way diff --git a/test/rubygems/installer_test_case.rb b/test/rubygems/installer_test_case.rb index 8a34d28db8..7a71984320 100644 --- a/test/rubygems/installer_test_case.rb +++ b/test/rubygems/installer_test_case.rb @@ -221,6 +221,23 @@ class Gem::InstallerTestCase < Gem::TestCase force: force) end + def test_ensure_writable_dir_creates_missing_parent_directories + installer = setup_base_installer(false) + + non_existent_parent = File.join(@tempdir, "non_existent_parent") + target_dir = File.join(non_existent_parent, "target_dir") + + refute_directory_exists non_existent_parent, "Parent directory should not exist yet" + refute_directory_exists target_dir, "Target directory should not exist yet" + + assert_nothing_raised do + installer.send(:ensure_writable_dir, target_dir) + end + + assert_directory_exists non_existent_parent, "Parent directory should exist now" + assert_directory_exists target_dir, "Target directory should exist now" + end + @@symlink_supported = nil # This is needed for Windows environment without symlink support enabled (the default diff --git a/test/rubygems/test_gem_command_manager.rb b/test/rubygems/test_gem_command_manager.rb index f3848e498d..e1c3512b6f 100644 --- a/test/rubygems/test_gem_command_manager.rb +++ b/test/rubygems/test_gem_command_manager.rb @@ -43,7 +43,7 @@ class TestGemCommandManager < Gem::TestCase assert_kind_of Gem::Commands::SigninCommand, command end - def test_find_logout_alias_comamnd + def test_find_logout_alias_command command = @command_manager.find_command "logout" assert_kind_of Gem::Commands::SignoutCommand, command diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index 4e49f52b4c..92933bfb77 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -647,17 +647,10 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - # Don't use Dir.chdir with a block, it warnings a lot because - # of a downstream Dir.chdir with a block - old = Dir.getwd - - begin - Dir.chdir @tempdir + Dir.chdir @tempdir do assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end - ensure - Dir.chdir old end end @@ -684,17 +677,10 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - # Don't use Dir.chdir with a block, it warnings a lot because - # of a downstream Dir.chdir with a block - old = Dir.getwd - - begin - Dir.chdir @tempdir + Dir.chdir @tempdir do assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end - ensure - Dir.chdir old end end @@ -720,17 +706,10 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - # Don't use Dir.chdir with a block, it warnings a lot because - # of a downstream Dir.chdir with a block - old = Dir.getwd - - begin - Dir.chdir @tempdir + Dir.chdir @tempdir do assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end - ensure - Dir.chdir old end end @@ -1005,6 +984,38 @@ ERROR: Possible alternatives: non_existent_with_hint assert_equal %W[a-3-#{local}], @cmd.installed_specs.map(&:full_name) end + def test_install_gem_platform_specificity_match + util_set_arch "arm64-darwin-20" + + spec_fetcher do |fetcher| + %w[ruby universal-darwin universal-darwin-20 x64-darwin-20 arm64-darwin-20].each do |platform| + fetcher.download "a", 3 do |s| + s.platform = platform + end + end + end + + @cmd.install_gem "a", ">= 0" + + assert_equal %w[a-3-arm64-darwin-20], @cmd.installed_specs.map(&:full_name) + end + + def test_install_gem_platform_specificity_match_reverse_order + util_set_arch "arm64-darwin-20" + + spec_fetcher do |fetcher| + %w[ruby universal-darwin universal-darwin-20 x64-darwin-20 arm64-darwin-20].reverse_each do |platform| + fetcher.download "a", 3 do |s| + s.platform = platform + end + end + end + + @cmd.install_gem "a", ">= 0" + + assert_equal %w[a-3-arm64-darwin-20], @cmd.installed_specs.map(&:full_name) + end + def test_install_gem_ignore_dependencies_specific_file spec = util_spec "a", 2 @@ -1214,6 +1225,30 @@ ERROR: Possible alternatives: non_existent_with_hint assert_match "Installing a (2)", @ui.output end + def test_execute_installs_from_a_gemdeps_with_prerelease + spec_fetcher do |fetcher| + fetcher.download "a", 1 + fetcher.download "a", "2.a" + end + + File.open @gemdeps, "w" do |f| + f << "gem 'a'" + end + + @cmd.handle_options %w[--prerelease] + @cmd.options[:gemdeps] = @gemdeps + + use_ui @ui do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do + @cmd.execute + end + end + + assert_equal %w[a-2.a], @cmd.installed_specs.map(&:full_name) + + assert_match "Installing a (2.a)", @ui.output + end + def test_execute_installs_deps_a_gemdeps spec_fetcher do |fetcher| fetcher.download "q", "1.0" @@ -1548,4 +1583,31 @@ ERROR: Possible alternatives: non_existent_with_hint assert_includes @ui.output, "A new release of RubyGems is available: 1.2.3 → 2.0.0!" end end + + def test_execute_bindir_with_nonexistent_parent_dirs + spec_fetcher do |fetcher| + fetcher.gem "a", 2 do |s| + s.executables = %w[a_bin] + s.files = %w[bin/a_bin] + end + end + + @cmd.options[:args] = %w[a] + + nested_bin_dir = File.join(@tempdir, "not", "exists") + refute_directory_exists nested_bin_dir, "Nested bin directory should not exist yet" + + @cmd.options[:bin_dir] = nested_bin_dir + + use_ui @ui do + assert_raise Gem::MockGemUi::SystemExitException, @ui.error do + @cmd.execute + end + end + + assert_directory_exists nested_bin_dir, "Nested bin directory should exist now" + assert_path_exist File.join(nested_bin_dir, "a_bin") + + assert_equal %w[a-2], @cmd.installed_specs.map(&:full_name) + end end diff --git a/test/rubygems/test_gem_commands_owner_command.rb b/test/rubygems/test_gem_commands_owner_command.rb index ac18699736..80b1497c41 100644 --- a/test/rubygems/test_gem_commands_owner_command.rb +++ b/test/rubygems/test_gem_commands_owner_command.rb @@ -476,7 +476,7 @@ EOF refute_match response_success, @stub_ui.output end - def test_remove_owners_unathorized_api_key + def test_remove_owners_unauthorized_api_key response_forbidden = "The API key doesn't have access" response_success = "Owner removed successfully." @@ -541,7 +541,7 @@ EOF assert_empty reused_otp_codes end - def test_add_owners_unathorized_api_key + def test_add_owners_unauthorized_api_key response_forbidden = "The API key doesn't have access" response_success = "Owner added successfully." diff --git a/test/rubygems/test_gem_commands_pristine_command.rb b/test/rubygems/test_gem_commands_pristine_command.rb index 46c06db014..e9c4d32945 100644 --- a/test/rubygems/test_gem_commands_pristine_command.rb +++ b/test/rubygems/test_gem_commands_pristine_command.rb @@ -125,8 +125,8 @@ class TestGemCommandsPristineCommand < Gem::TestCase @cmd.execute end - assert File.exist?(gem_bin) - assert File.exist?(gem_stub) + assert_path_exist gem_bin + assert_path_exist gem_stub out = @ui.output.split "\n" @@ -537,8 +537,8 @@ class TestGemCommandsPristineCommand < Gem::TestCase @cmd.execute end - assert File.exist? gem_exec - refute File.exist? gem_lib + assert_path_exist gem_exec + assert_path_not_exist gem_lib end def test_execute_only_plugins @@ -572,9 +572,9 @@ class TestGemCommandsPristineCommand < Gem::TestCase @cmd.execute end - refute File.exist? gem_exec - assert File.exist? gem_plugin - refute File.exist? gem_lib + assert_path_not_exist gem_exec + assert_path_exist gem_plugin + assert_path_not_exist gem_lib end def test_execute_bindir @@ -606,8 +606,8 @@ class TestGemCommandsPristineCommand < Gem::TestCase @cmd.execute end - refute File.exist? gem_exec - assert File.exist? gem_bindir + assert_path_not_exist gem_exec + assert_path_exist gem_bindir end def test_execute_unknown_gem_at_remote_source @@ -659,6 +659,42 @@ class TestGemCommandsPristineCommand < Gem::TestCase refute_includes "ruby_executable_hooks", File.read(exe) end + def test_execute_default_gem_and_regular_gem + a_default = new_default_spec("a", "1.2.0") + + a = util_spec "a" do |s| + s.extensions << "ext/a/extconf.rb" + end + + ext_path = File.join @tempdir, "ext", "a", "extconf.rb" + write_file ext_path do |io| + io.write <<-'RUBY' + File.open "Makefile", "w" do |f| + f.puts "clean:\n\techo cleaned\n" + f.puts "all:\n\techo built\n" + f.puts "install:\n\techo installed\n" + end + RUBY + end + + install_default_gems a_default + install_gem a + + # Remove the extension files for a + FileUtils.rm_rf a.gem_build_complete_path + + @cmd.options[:args] = %w[a] + + use_ui @ui do + @cmd.execute + end + + assert_includes @ui.output, "Restored #{a.full_name}" + + # Check extension files for a were restored + assert_path_exist a.gem_build_complete_path + end + def test_execute_multi_platform a = util_spec "a" do |s| s.extensions << "ext/a/extconf.rb" diff --git a/test/rubygems/test_gem_commands_push_command.rb b/test/rubygems/test_gem_commands_push_command.rb index bedc8e0d58..1477a74947 100644 --- a/test/rubygems/test_gem_commands_push_command.rb +++ b/test/rubygems/test_gem_commands_push_command.rb @@ -566,7 +566,7 @@ class TestGemCommandsPushCommand < Gem::TestCase refute_match response_success, @ui.output end - def test_sending_gem_unathorized_api_key_with_mfa_enabled + def test_sending_gem_unauthorized_api_key_with_mfa_enabled response_mfa_enabled = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry." response_forbidden = "The API key doesn't have access" response_success = "Successfully registered gem: freewill (1.0.0)" diff --git a/test/rubygems/test_gem_commands_setup_command.rb b/test/rubygems/test_gem_commands_setup_command.rb index c3622c02cd..dfd951268d 100644 --- a/test/rubygems/test_gem_commands_setup_command.rb +++ b/test/rubygems/test_gem_commands_setup_command.rb @@ -4,13 +4,6 @@ require_relative "helper" require "rubygems/commands/setup_command" class TestGemCommandsSetupCommand < Gem::TestCase - bundler_gemspec = File.expand_path("../../bundler/lib/bundler/version.rb", __dir__) - if File.exist?(bundler_gemspec) - BUNDLER_VERS = File.read(bundler_gemspec).match(/VERSION = "(#{Gem::Version::VERSION_PATTERN})"/)[1] - else - BUNDLER_VERS = "2.0.1" - end - def setup super @@ -35,7 +28,7 @@ class TestGemCommandsSetupCommand < Gem::TestCase create_dummy_files(filelist) - gemspec = util_spec "bundler", BUNDLER_VERS do |s| + gemspec = util_spec "bundler", "9.9.9" do |s| s.bindir = "exe" s.executables = ["bundle", "bundler"] end @@ -380,20 +373,22 @@ class TestGemCommandsSetupCommand < Gem::TestCase File.open "CHANGELOG.md", "w" do |io| io.puts <<-HISTORY_TXT -# #{Gem::VERSION} / 2013-03-26 +# Changelog -## Bug fixes: +## #{Gem::VERSION} / 2013-03-26 + +### Bug fixes: * Fixed release note display for LANG=C when installing rubygems * π is tasty -# 2.0.2 / 2013-03-06 +## 2.0.2 / 2013-03-06 -## Bug fixes: +### Bug fixes: * Other bugs fixed -# 2.0.1 / 2013-03-05 +## 2.0.1 / 2013-03-05 -## Bug fixes: +### Bug fixes: * Yet more bugs fixed HISTORY_TXT end @@ -403,9 +398,9 @@ class TestGemCommandsSetupCommand < Gem::TestCase end expected = <<-EXPECTED -# #{Gem::VERSION} / 2013-03-26 +## #{Gem::VERSION} / 2013-03-26 -## Bug fixes: +### Bug fixes: * Fixed release note display for LANG=C when installing rubygems * π is tasty diff --git a/test/rubygems/test_gem_commands_signin_command.rb b/test/rubygems/test_gem_commands_signin_command.rb index 29e5edceb7..e612288faf 100644 --- a/test/rubygems/test_gem_commands_signin_command.rb +++ b/test/rubygems/test_gem_commands_signin_command.rb @@ -121,7 +121,7 @@ class TestGemCommandsSigninCommand < Gem::TestCase assert_match "The default access scope is:", key_name_ui.output assert_match "index_rubygems: y", key_name_ui.output assert_match "Do you want to customise scopes? [yN]", key_name_ui.output - assert_equal "name=test-key&index_rubygems=true", fetcher.last_request.body + assert_equal "name=test-key&index_rubygems=true&push_rubygem=true", fetcher.last_request.body credentials = load_yaml_file Gem.configuration.credentials_path assert_equal api_key, credentials[:rubygems_api_key] diff --git a/test/rubygems/test_gem_commands_yank_command.rb b/test/rubygems/test_gem_commands_yank_command.rb index 213f098374..73fd177243 100644 --- a/test/rubygems/test_gem_commands_yank_command.rb +++ b/test/rubygems/test_gem_commands_yank_command.rb @@ -271,7 +271,7 @@ class TestGemCommandsYankCommand < Gem::TestCase assert_equal [yank_uri], @fetcher.paths end - def test_yank_gem_unathorized_api_key + def test_yank_gem_unauthorized_api_key response_forbidden = "The API key doesn't have access" response_success = "Successfully yanked" host = "http://example" diff --git a/test/rubygems/test_gem_dependency_installer.rb b/test/rubygems/test_gem_dependency_installer.rb index 56b84160c4..f84881579a 100644 --- a/test/rubygems/test_gem_dependency_installer.rb +++ b/test/rubygems/test_gem_dependency_installer.rb @@ -382,13 +382,9 @@ class TestGemDependencyInstaller < Gem::TestCase FileUtils.mv f1_gem, @tempdir inst = nil - pwd = Dir.getwd - Dir.chdir @tempdir - begin + Dir.chdir @tempdir do inst = Gem::DependencyInstaller.new inst.install "f" - ensure - Dir.chdir pwd end assert_equal %w[f-1], inst.installed_gems.map(&:full_name) @@ -523,6 +519,58 @@ class TestGemDependencyInstaller < Gem::TestCase assert_equal %w[a-1], inst.installed_gems.map(&:full_name) end + def test_install_local_with_extensions_already_installed + pend "needs investigation" if Gem.java_platform? + pend "ruby.h is not provided by ruby repo" if ruby_repo? + + @spec = quick_gem "a" do |s| + s.extensions << "extconf.rb" + s.files += %w[extconf.rb a.c] + end + + write_dummy_extconf "a" + + c_source_path = File.join(@tempdir, "a.c") + + write_file c_source_path do |io| + io.write <<-C + #include + void Init_a() { } + C + end + + package_path = Gem::Package.build @spec + installer = Gem::Installer.at(package_path) + + # Make sure the gem is installed and backup the correct package + + installer.install + + package_bkp_path = "#{package_path}.bkp" + FileUtils.cp package_path, package_bkp_path + + # Break the extension, rebuild it, and try to install it + + write_file c_source_path do |io| + io.write "typo" + end + + Gem::Package.build @spec + + assert_raise Gem::Ext::BuildError do + installer.install + end + + # Make sure installing the good package again still works + + FileUtils.cp "#{package_path}.bkp", package_path + + Dir.chdir @tempdir do + inst = Gem::DependencyInstaller.new domain: :local + inst.install package_path + end + end + def test_install_minimal_deps util_setup_gems diff --git a/test/rubygems/test_gem_ext_cargo_builder.rb b/test/rubygems/test_gem_ext_cargo_builder.rb index 5035937544..b970e442c2 100644 --- a/test/rubygems/test_gem_ext_cargo_builder.rb +++ b/test/rubygems/test_gem_ext_cargo_builder.rb @@ -141,6 +141,58 @@ class TestGemExtCargoBuilder < Gem::TestCase end end + def test_linker_args + orig_cc = RbConfig::MAKEFILE_CONFIG["CC"] + RbConfig::MAKEFILE_CONFIG["CC"] = "clang" + + builder = Gem::Ext::CargoBuilder.new + args = builder.send(:linker_args) + + assert args[1], "linker=clang" + assert_nil args[2] + ensure + RbConfig::MAKEFILE_CONFIG["CC"] = orig_cc + end + + def test_linker_args_with_options + orig_cc = RbConfig::MAKEFILE_CONFIG["CC"] + RbConfig::MAKEFILE_CONFIG["CC"] = "gcc -Wl,--no-undefined" + + builder = Gem::Ext::CargoBuilder.new + args = builder.send(:linker_args) + + assert args[1], "linker=clang" + assert args[3], "link-args=-Wl,--no-undefined" + ensure + RbConfig::MAKEFILE_CONFIG["CC"] = orig_cc + end + + def test_linker_args_with_cachetools + orig_cc = RbConfig::MAKEFILE_CONFIG["CC"] + RbConfig::MAKEFILE_CONFIG["CC"] = "sccache clang" + + builder = Gem::Ext::CargoBuilder.new + args = builder.send(:linker_args) + + assert args[1], "linker=clang" + assert_nil args[2] + ensure + RbConfig::MAKEFILE_CONFIG["CC"] = orig_cc + end + + def test_linker_args_with_cachetools_and_options + orig_cc = RbConfig::MAKEFILE_CONFIG["CC"] + RbConfig::MAKEFILE_CONFIG["CC"] = "ccache gcc -Wl,--no-undefined" + + builder = Gem::Ext::CargoBuilder.new + args = builder.send(:linker_args) + + assert args[1], "linker=clang" + assert args[3], "link-args=-Wl,--no-undefined" + ensure + RbConfig::MAKEFILE_CONFIG["CC"] = orig_cc + end + private def skip_unsupported_platforms! diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock index f16c0eb140..4851de09d0 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock @@ -152,18 +152,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.111" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "becea799ce051c16fb140be80f5e7cf781070f99ca099332383c2b17861249af" +checksum = "7059846f68396df83155779c75336ca24567741cb95256e6308c9fcc370e8dad" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.111" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64691175abc704862f60a9ca8ef06174080cc50615f2bf1d4759f46db18b4d29" +checksum = "ac217510df41b9ffc041573e68d7a02aaff770c49943c7494441c4b224b0ecd0" dependencies = [ "bindgen", "lazy_static", diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml index a66404aa41..7cb12fa8a6 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.111" +rb-sys = "0.9.116" diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock index 1230f8ae96..9740b435e7 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock @@ -145,18 +145,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.111" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "becea799ce051c16fb140be80f5e7cf781070f99ca099332383c2b17861249af" +checksum = "7059846f68396df83155779c75336ca24567741cb95256e6308c9fcc370e8dad" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.111" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64691175abc704862f60a9ca8ef06174080cc50615f2bf1d4759f46db18b4d29" +checksum = "ac217510df41b9ffc041573e68d7a02aaff770c49943c7494441c4b224b0ecd0" dependencies = [ "bindgen", "lazy_static", diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml index 03853fea08..b389cff542 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.111" +rb-sys = "0.9.116" diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb index 993cd7e998..34415aa7dd 100644 --- a/test/rubygems/test_gem_installer.rb +++ b/test/rubygems/test_gem_installer.rb @@ -24,36 +24,35 @@ class TestGemInstaller < Gem::InstallerTestCase util_make_exec @spec, "" - expected = <<-EOF -#!#{Gem.ruby} -# -# This file was generated by RubyGems. -# -# The application 'a' is installed as part of a gem, and -# this file is here to facilitate running it. -# + expected = <<~EOF + #!#{Gem.ruby} + # + # This file was generated by RubyGems. + # + # The application 'a' is installed as part of a gem, and + # this file is here to facilitate running it. + # -require 'rubygems' + require 'rubygems' -Gem.use_gemdeps + Gem.use_gemdeps -version = \">= 0.a\" + version = \">= 0.a\" -str = ARGV.first -if str - str = str.b[/\\A_(.*)_\\z/, 1] - if str and Gem::Version.correct?(str) - version = str - ARGV.shift - end -end + str = ARGV.first + if str + str = str.b[/\\A_(.*)_\\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end + end -if Gem.respond_to?(:activate_bin_path) -load Gem.activate_bin_path('a', 'executable', version) -else -gem "a", version -load Gem.bin_path("a", "executable", version) -end + if Gem.respond_to?(:activate_and_load_bin_path) + Gem.activate_and_load_bin_path('a', 'executable', version) + else + load Gem.activate_bin_path('a', 'executable', version) + end EOF wrapper = installer.app_script_text "executable" @@ -121,12 +120,12 @@ end end File.open File.join(util_inst_bindir, "executable"), "w" do |io| - io.write <<-EXEC -#!/usr/local/bin/ruby -# -# This file was generated by RubyGems + io.write <<~EXEC + #!/usr/local/bin/ruby + # + # This file was generated by RubyGems -gem 'other', version + gem 'other', version EXEC end @@ -869,11 +868,11 @@ gem 'other', version spec_version = spec.version plugin_path = File.join("lib", "rubygems_plugin.rb") write_file File.join(@tempdir, plugin_path) do |io| - io.write <<-PLUGIN -#{self.class}.plugin_loaded = true -Gem.post_install do - #{self.class}.post_install_is_called = true -end + io.write <<~PLUGIN + #{self.class}.plugin_loaded = true + Gem.post_install do + #{self.class}.post_install_is_called = true + end PLUGIN end spec.files += [plugin_path] @@ -1478,12 +1477,7 @@ end @spec = setup_base_spec @spec.extensions << "extconf.rb" - write_file File.join(@tempdir, "extconf.rb") do |io| - io.write <<-RUBY - require "mkmf" - create_makefile("#{@spec.name}") - RUBY - end + write_dummy_extconf @spec.name @spec.files += %w[extconf.rb] @@ -1503,12 +1497,7 @@ end @spec = setup_base_spec @spec.extensions << "extconf.rb" - write_file File.join(@tempdir, "extconf.rb") do |io| - io.write <<-RUBY - require "mkmf" - create_makefile("#{@spec.name}") - RUBY - end + write_dummy_extconf @spec.name @spec.files += %w[extconf.rb] @@ -1539,12 +1528,7 @@ end def test_install_user_extension_dir @spec = setup_base_spec @spec.extensions << "extconf.rb" - write_file File.join(@tempdir, "extconf.rb") do |io| - io.write <<-RUBY - require "mkmf" - create_makefile("#{@spec.name}") - RUBY - end + write_dummy_extconf @spec.name @spec.files += %w[extconf.rb] @@ -1571,22 +1555,20 @@ end @spec = setup_base_spec @spec.extensions << "extconf.rb" - write_file File.join(@tempdir, "extconf.rb") do |io| - io.write <<-RUBY - require "mkmf" + write_dummy_extconf @spec.name do |io| + io.write <<~RUBY CONFIG['CC'] = '$(TOUCH) $@ ||' CONFIG['LDSHARED'] = '$(TOUCH) $@ ||' $ruby = '#{Gem.ruby}' - create_makefile("#{@spec.name}") RUBY end write_file File.join(@tempdir, "depend") write_file File.join(@tempdir, "a.c") do |io| - io.write <<-C + io.write <<~C #include void Init_a() { } C @@ -1618,17 +1600,12 @@ end @spec = setup_base_spec @spec.extensions << "extconf.rb" - write_file File.join(@tempdir, "extconf.rb") do |io| - io.write <<-RUBY - require "mkmf" - create_makefile("#{@spec.name}") - RUBY - end + write_dummy_extconf @spec.name rb = File.join("lib", "#{@spec.name}.rb") @spec.files += [rb] write_file File.join(@tempdir, rb) do |io| - io.write <<-RUBY + io.write <<~RUBY # #{@spec.name}.rb RUBY end @@ -1637,7 +1614,7 @@ end rb2 = File.join("lib", @spec.name, "#{@spec.name}.rb") @spec.files << rb2 write_file File.join(@tempdir, rb2) do |io| - io.write <<-RUBY + io.write <<~RUBY # #{@spec.name}/#{@spec.name}.rb RUBY end @@ -1663,15 +1640,13 @@ end @spec.extensions << "extconf.rb" - write_file File.join(@tempdir, "extconf.rb") do |io| - io.write <<-RUBY - require "mkmf" + write_dummy_extconf @spec.name do |io| + io.write <<~RUBY CONFIG['CC'] = '$(TOUCH) $@ ||' CONFIG['LDSHARED'] = '$(TOUCH) $@ ||' $ruby = '#{Gem.ruby}' - create_makefile("#{@spec.name}") RUBY end @@ -1698,13 +1673,13 @@ end @spec.require_paths = ["."] @spec.extensions << "extconf.rb" - File.write File.join(@tempdir, "extconf.rb"), <<-RUBY - require "mkmf" - CONFIG['CC'] = '$(TOUCH) $@ ||' - CONFIG['LDSHARED'] = '$(TOUCH) $@ ||' - $ruby = '#{Gem.ruby}' - create_makefile("#{@spec.name}") - RUBY + write_dummy_extconf @spec.name do |io| + io.write <<~RUBY + CONFIG['CC'] = '$(TOUCH) $@ ||' + CONFIG['LDSHARED'] = '$(TOUCH) $@ ||' + $ruby = '#{Gem.ruby}' + RUBY + end # empty depend file for no auto dependencies @spec.files += %W[depend #{@spec.name}.c].each do |file| @@ -1938,10 +1913,10 @@ end end def test_pre_install_checks_malicious_platform_before_eval - gem_with_ill_formated_platform = File.expand_path("packages/ill-formatted-platform-1.0.0.10.gem", __dir__) + gem_with_ill_formatted_platform = File.expand_path("packages/ill-formatted-platform-1.0.0.10.gem", __dir__) installer = Gem::Installer.at( - gem_with_ill_formated_platform, + gem_with_ill_formatted_platform, install_dir: @gemhome, user_install: false, force: true diff --git a/test/rubygems/test_gem_platform.rb b/test/rubygems/test_gem_platform.rb index 070c8007bc..a3ae919809 100644 --- a/test/rubygems/test_gem_platform.rb +++ b/test/rubygems/test_gem_platform.rb @@ -148,12 +148,29 @@ class TestGemPlatform < Gem::TestCase "wasm32-wasi" => ["wasm32", "wasi", nil], "wasm32-wasip1" => ["wasm32", "wasi", nil], "wasm32-wasip2" => ["wasm32", "wasi", nil], + + "darwin-java-java" => ["darwin", "java", nil], + "linux-linux-linux" => ["linux", "linux", "linux"], + "linux-linux-linux1.0" => ["linux", "linux", "linux1"], + "x86x86-1x86x86x86x861linuxx86x86" => ["x86x86", "linux", "x86x86"], + "freebsd0" => [nil, "freebsd", "0"], + "darwin0" => [nil, "darwin", "0"], + "darwin0---" => [nil, "darwin", "0"], + "x86-linux-x8611.0l" => ["x86", "linux", "x8611"], + "0-x86linuxx86---" => ["0", "linux", "x86"], + "x86_64-macruby-x86" => ["x86_64", "macruby", nil], + "x86_64-dotnetx86" => ["x86_64", "dotnet", nil], + "x86_64-dalvik0" => ["x86_64", "dalvik", "0"], + "x86_64-dotnet1." => ["x86_64", "dotnet", "1"], + + "--" => [nil, "unknown", nil], } test_cases.each do |arch, expected| platform = Gem::Platform.new arch assert_equal expected, platform.to_a, arch.inspect - assert_equal expected, Gem::Platform.new(platform.to_s).to_a, arch.inspect + platform2 = Gem::Platform.new platform.to_s + assert_equal expected, platform2.to_a, "#{arch.inspect} => #{platform2.inspect}" end end @@ -393,18 +410,11 @@ class TestGemPlatform < Gem::TestCase def test_equals3_universal_mingw uni_mingw = Gem::Platform.new "universal-mingw" - mingw32 = Gem::Platform.new "x64-mingw32" mingw_ucrt = Gem::Platform.new "x64-mingw-ucrt" - util_set_arch "x64-mingw32" - assert((uni_mingw === Gem::Platform.local), "uni_mingw === mingw32") - assert((mingw32 === Gem::Platform.local), "mingw32 === mingw32") - refute((mingw_ucrt === Gem::Platform.local), "mingw32 === mingw_ucrt") - util_set_arch "x64-mingw-ucrt" - assert((uni_mingw === Gem::Platform.local), "uni_mingw === mingw32") + assert((uni_mingw === Gem::Platform.local), "uni_mingw === mingw_ucrt") assert((mingw_ucrt === Gem::Platform.local), "mingw_ucrt === mingw_ucrt") - refute((mingw32 === Gem::Platform.local), "mingw32 === mingw_ucrt") end def test_equals3_version @@ -501,6 +511,171 @@ class TestGemPlatform < Gem::TestCase end end + def test_constants + assert_equal [nil, "java", nil], Gem::Platform::JAVA.to_a + assert_equal ["x86", "mswin32", nil], Gem::Platform::MSWIN.to_a + assert_equal [nil, "mswin64", nil], Gem::Platform::MSWIN64.to_a + assert_equal ["x86", "mingw32", nil], Gem::Platform::MINGW.to_a + assert_equal ["x64", "mingw", "ucrt"], Gem::Platform::X64_MINGW.to_a + assert_equal ["universal", "mingw", nil], Gem::Platform::UNIVERSAL_MINGW.to_a + assert_equal [["x86", "mswin32", nil], [nil, "mswin64", nil], ["universal", "mingw", nil]], Gem::Platform::WINDOWS.map(&:to_a) + assert_equal ["x86_64", "linux", nil], Gem::Platform::X64_LINUX.to_a + assert_equal ["x86_64", "linux", "musl"], Gem::Platform::X64_LINUX_MUSL.to_a + end + + def test_generic + # converts non-windows platforms into ruby + assert_equal Gem::Platform::RUBY, Gem::Platform.generic(Gem::Platform.new("x86-darwin-10")) + assert_equal Gem::Platform::RUBY, Gem::Platform.generic(Gem::Platform::RUBY) + + # converts java platform variants into java + assert_equal Gem::Platform::JAVA, Gem::Platform.generic(Gem::Platform.new("java")) + assert_equal Gem::Platform::JAVA, Gem::Platform.generic(Gem::Platform.new("universal-java-17")) + + # converts mswin platform variants into x86-mswin32 + assert_equal Gem::Platform::MSWIN, Gem::Platform.generic(Gem::Platform.new("mswin32")) + assert_equal Gem::Platform::MSWIN, Gem::Platform.generic(Gem::Platform.new("i386-mswin32")) + assert_equal Gem::Platform::MSWIN, Gem::Platform.generic(Gem::Platform.new("x86-mswin32")) + + # converts 32-bit mingw platform variants into universal-mingw + assert_equal Gem::Platform::UNIVERSAL_MINGW, Gem::Platform.generic(Gem::Platform.new("i386-mingw32")) + assert_equal Gem::Platform::UNIVERSAL_MINGW, Gem::Platform.generic(Gem::Platform.new("x86-mingw32")) + + # converts 64-bit mingw platform variants into universal-mingw + assert_equal Gem::Platform::UNIVERSAL_MINGW, Gem::Platform.generic(Gem::Platform.new("x64-mingw32")) + + # converts x64 mingw UCRT platform variants into universal-mingw + assert_equal Gem::Platform::UNIVERSAL_MINGW, Gem::Platform.generic(Gem::Platform.new("x64-mingw-ucrt")) + + # converts aarch64 mingw UCRT platform variants into universal-mingw + assert_equal Gem::Platform::UNIVERSAL_MINGW, Gem::Platform.generic(Gem::Platform.new("aarch64-mingw-ucrt")) + + assert_equal Gem::Platform::RUBY, Gem::Platform.generic(Gem::Platform.new("unknown")) + assert_equal Gem::Platform::RUBY, Gem::Platform.generic(nil) + assert_equal Gem::Platform::MSWIN64, Gem::Platform.generic(Gem::Platform.new("mswin64")) + end + + def test_platform_specificity_match + [ + ["ruby", "ruby", -1, -1], + ["x86_64-linux-musl", "x86_64-linux-musl", -1, -1], + ["x86_64-linux", "x86_64-linux-musl", 100, 200], + ["universal-darwin", "x86-darwin", 10, 20], + ["universal-darwin-19", "x86-darwin", 210, 120], + ["universal-darwin-19", "universal-darwin-20", 200, 200], + ["arm-darwin-19", "arm64-darwin-19", 0, 20], + ].each do |spec_platform, user_platform, s1, s2| + spec_platform = Gem::Platform.new(spec_platform) + user_platform = Gem::Platform.new(user_platform) + assert_equal s1, Gem::Platform.platform_specificity_match(spec_platform, user_platform), + "Gem::Platform.platform_specificity_match(#{spec_platform.to_s.inspect}, #{user_platform.to_s.inspect})" + assert_equal s2, Gem::Platform.platform_specificity_match(user_platform, spec_platform), + "Gem::Platform.platform_specificity_match(#{user_platform.to_s.inspect}, #{spec_platform.to_s.inspect})" + end + end + + def test_sort_and_filter_best_platform_match + a_1 = util_spec "a", "1" + a_1_java = util_spec "a", "1" do |s| + s.platform = Gem::Platform::JAVA + end + a_1_universal_darwin = util_spec "a", "1" do |s| + s.platform = Gem::Platform.new("universal-darwin") + end + a_1_universal_darwin_19 = util_spec "a", "1" do |s| + s.platform = Gem::Platform.new("universal-darwin-19") + end + a_1_universal_darwin_20 = util_spec "a", "1" do |s| + s.platform = Gem::Platform.new("universal-darwin-20") + end + a_1_arm_darwin_19 = util_spec "a", "1" do |s| + s.platform = Gem::Platform.new("arm64-darwin-19") + end + a_1_x86_darwin = util_spec "a", "1" do |s| + s.platform = Gem::Platform.new("x86-darwin") + end + specs = [a_1, a_1_java, a_1_universal_darwin, a_1_universal_darwin_19, a_1_universal_darwin_20, a_1_arm_darwin_19, a_1_x86_darwin] + assert_equal [a_1], Gem::Platform.sort_and_filter_best_platform_match(specs, "ruby") + assert_equal [a_1_java], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform::JAVA) + assert_equal [a_1_arm_darwin_19], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform.new("arm64-darwin-19")) + assert_equal [a_1_universal_darwin_20], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform.new("arm64-darwin-20")) + assert_equal [a_1_universal_darwin_19], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform.new("x86-darwin-19")) + assert_equal [a_1_universal_darwin_20], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform.new("x86-darwin-20")) + assert_equal [a_1_x86_darwin], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform.new("x86-darwin-21")) + end + + def test_sort_best_platform_match + a_1 = util_spec "a", "1" + a_1_java = util_spec "a", "1" do |s| + s.platform = Gem::Platform::JAVA + end + a_1_universal_darwin = util_spec "a", "1" do |s| + s.platform = Gem::Platform.new("universal-darwin") + end + a_1_universal_darwin_19 = util_spec "a", "1" do |s| + s.platform = Gem::Platform.new("universal-darwin-19") + end + a_1_universal_darwin_20 = util_spec "a", "1" do |s| + s.platform = Gem::Platform.new("universal-darwin-20") + end + a_1_arm_darwin_19 = util_spec "a", "1" do |s| + s.platform = Gem::Platform.new("arm64-darwin-19") + end + a_1_x86_darwin = util_spec "a", "1" do |s| + s.platform = Gem::Platform.new("x86-darwin") + end + specs = [a_1, a_1_java, a_1_universal_darwin, a_1_universal_darwin_19, a_1_universal_darwin_20, a_1_arm_darwin_19, a_1_x86_darwin] + assert_equal ["ruby", + "java", + "universal-darwin", + "universal-darwin-19", + "universal-darwin-20", + "arm64-darwin-19", + "x86-darwin"], Gem::Platform.sort_best_platform_match(specs, "ruby").map {|s| s.platform.to_s } + assert_equal ["java", + "universal-darwin", + "x86-darwin", + "universal-darwin-19", + "universal-darwin-20", + "arm64-darwin-19", + "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform::JAVA).map {|s| s.platform.to_s } + assert_equal ["arm64-darwin-19", + "universal-darwin-19", + "universal-darwin", + "java", + "x86-darwin", + "universal-darwin-20", + "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform.new("arm64-darwin-19")).map {|s| s.platform.to_s } + assert_equal ["universal-darwin-20", + "universal-darwin", + "java", + "x86-darwin", + "arm64-darwin-19", + "universal-darwin-19", + "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform.new("arm64-darwin-20")).map {|s| s.platform.to_s } + assert_equal ["universal-darwin-19", + "arm64-darwin-19", + "x86-darwin", + "universal-darwin", + "java", + "universal-darwin-20", + "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform.new("x86-darwin-19")).map {|s| s.platform.to_s } + assert_equal ["universal-darwin-20", + "x86-darwin", + "universal-darwin", + "java", + "universal-darwin-19", + "arm64-darwin-19", + "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform.new("x86-darwin-20")).map {|s| s.platform.to_s } + assert_equal ["x86-darwin", + "universal-darwin", + "java", + "universal-darwin-19", + "universal-darwin-20", + "arm64-darwin-19", + "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform.new("x86-darwin-21")).map {|s| s.platform.to_s } + end + def assert_local_match(name) assert_match Gem::Platform.local, name end diff --git a/test/rubygems/test_gem_remote_fetcher_s3.rb b/test/rubygems/test_gem_remote_fetcher_s3.rb index fe7eb7ec01..e3aaa7a691 100644 --- a/test/rubygems/test_gem_remote_fetcher_s3.rb +++ b/test/rubygems/test_gem_remote_fetcher_s3.rb @@ -18,7 +18,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase @a1.loaded_from = File.join(@gemhome, "specifications", @a1.full_name) end - def assert_fetch_s3(url, signature, token=nil, region="us-east-1", instance_profile_json=nil) + def assert_fetch_s3(url, signature, token=nil, region="us-east-1", instance_profile_json=nil, method="GET") fetcher = Gem::RemoteFetcher.new nil @fetcher = fetcher $fetched_uri = nil @@ -33,9 +33,9 @@ class TestGemRemoteFetcherS3 < Gem::TestCase res end - def fetcher.s3_uri_signer(uri) + def fetcher.s3_uri_signer(uri, method) require "json" - s3_uri_signer = Gem::S3URISigner.new(uri) + s3_uri_signer = Gem::S3URISigner.new(uri, method) def s3_uri_signer.ec2_metadata_credentials_json JSON.parse($instance_profile) end @@ -45,10 +45,14 @@ class TestGemRemoteFetcherS3 < Gem::TestCase s3_uri_signer end - data = fetcher.fetch_s3 Gem::URI.parse(url) + res = fetcher.fetch_s3 Gem::URI.parse(url), nil, (method == "HEAD") - assert_equal "https://my-bucket.s3.#{region}.amazonaws.com/gems/specs.4.8.gz?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=testuser%2F20190624%2F#{region}%2Fs3%2Faws4_request&X-Amz-Date=20190624T050641Z&X-Amz-Expires=86400#{token ? "&X-Amz-Security-Token=" + token : ""}&X-Amz-SignedHeaders=host&X-Amz-Signature=#{signature}", $fetched_uri.to_s - assert_equal "success", data + assert_equal "https://my-bucket.s3.#{region}.amazonaws.com/gems/specs.4.8.gz?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=testuser%2F20190624%2F#{region}%2Fs3%2Faws4_request&X-Amz-Date=20190624T051941Z&X-Amz-Expires=86400#{token ? "&X-Amz-Security-Token=" + token : ""}&X-Amz-SignedHeaders=host&X-Amz-Signature=#{signature}", $fetched_uri.to_s + if method == "HEAD" + assert_equal 200, res.code + else + assert_equal "success", res + end ensure $fetched_uri = nil end @@ -59,7 +63,23 @@ class TestGemRemoteFetcherS3 < Gem::TestCase } url = "s3://my-bucket/gems/specs.4.8.gz" Time.stub :now, Time.at(1_561_353_581) do - assert_fetch_s3 url, "20f974027db2f3cd6193565327a7c73457a138efb1a63ea248d185ce6827d41b" + assert_fetch_s3 url, "b5cb80c1301f7b1c50c4af54f1f6c034f80b56d32f000a855f0a903dc5a8413c" + end + ensure + Gem.configuration[:s3_source] = nil + end + + def test_fetch_s3_head_request + Gem.configuration[:s3_source] = { + "my-bucket" => { id: "testuser", secret: "testpass" }, + } + url = "s3://my-bucket/gems/specs.4.8.gz" + Time.stub :now, Time.at(1_561_353_581) do + token = nil + region = "us-east-1" + instance_profile_json = nil + method = "HEAD" + assert_fetch_s3 url, "a3c6cf9a2db62e85f4e57f8fc8ac8b5ff5c1fdd4aeef55935d05e05174d9c885", token, region, instance_profile_json, method end ensure Gem.configuration[:s3_source] = nil @@ -71,7 +91,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase } url = "s3://my-bucket/gems/specs.4.8.gz" Time.stub :now, Time.at(1_561_353_581) do - assert_fetch_s3 url, "4afc3010757f1fd143e769f1d1dabd406476a4fc7c120e9884fd02acbb8f26c9", nil, "us-west-2" + assert_fetch_s3 url, "ef07487bfd8e3ca594f8fc29775b70c0a0636f51318f95d4f12b2e6e1fd8c716", nil, "us-west-2" end ensure Gem.configuration[:s3_source] = nil @@ -83,7 +103,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase } url = "s3://my-bucket/gems/specs.4.8.gz" Time.stub :now, Time.at(1_561_353_581) do - assert_fetch_s3 url, "935160a427ef97e7630f799232b8f208c4a4e49aad07d0540572a2ad5fe9f93c", "testtoken" + assert_fetch_s3 url, "e709338735f9077edf8f6b94b247171c266a9605975e08e4a519a123c3322625", "testtoken" end ensure Gem.configuration[:s3_source] = nil @@ -98,7 +118,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase } url = "s3://my-bucket/gems/specs.4.8.gz" Time.stub :now, Time.at(1_561_353_581) do - assert_fetch_s3 url, "20f974027db2f3cd6193565327a7c73457a138efb1a63ea248d185ce6827d41b" + assert_fetch_s3 url, "b5cb80c1301f7b1c50c4af54f1f6c034f80b56d32f000a855f0a903dc5a8413c" end ensure ENV.each_key {|key| ENV.delete(key) if key.start_with?("AWS") } @@ -114,7 +134,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase } url = "s3://my-bucket/gems/specs.4.8.gz" Time.stub :now, Time.at(1_561_353_581) do - assert_fetch_s3 url, "4afc3010757f1fd143e769f1d1dabd406476a4fc7c120e9884fd02acbb8f26c9", nil, "us-west-2" + assert_fetch_s3 url, "ef07487bfd8e3ca594f8fc29775b70c0a0636f51318f95d4f12b2e6e1fd8c716", nil, "us-west-2" end ensure ENV.each_key {|key| ENV.delete(key) if key.start_with?("AWS") } @@ -130,7 +150,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase } url = "s3://my-bucket/gems/specs.4.8.gz" Time.stub :now, Time.at(1_561_353_581) do - assert_fetch_s3 url, "935160a427ef97e7630f799232b8f208c4a4e49aad07d0540572a2ad5fe9f93c", "testtoken" + assert_fetch_s3 url, "e709338735f9077edf8f6b94b247171c266a9605975e08e4a519a123c3322625", "testtoken" end ensure ENV.each_key {|key| ENV.delete(key) if key.start_with?("AWS") } @@ -140,7 +160,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase def test_fetch_s3_url_creds url = "s3://testuser:testpass@my-bucket/gems/specs.4.8.gz" Time.stub :now, Time.at(1_561_353_581) do - assert_fetch_s3 url, "20f974027db2f3cd6193565327a7c73457a138efb1a63ea248d185ce6827d41b" + assert_fetch_s3 url, "b5cb80c1301f7b1c50c4af54f1f6c034f80b56d32f000a855f0a903dc5a8413c" end end @@ -151,7 +171,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase url = "s3://my-bucket/gems/specs.4.8.gz" Time.stub :now, Time.at(1_561_353_581) do - assert_fetch_s3 url, "20f974027db2f3cd6193565327a7c73457a138efb1a63ea248d185ce6827d41b", nil, "us-east-1", + assert_fetch_s3 url, "b5cb80c1301f7b1c50c4af54f1f6c034f80b56d32f000a855f0a903dc5a8413c", nil, "us-east-1", '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass"}' end ensure @@ -165,7 +185,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase url = "s3://my-bucket/gems/specs.4.8.gz" Time.stub :now, Time.at(1_561_353_581) do - assert_fetch_s3 url, "4afc3010757f1fd143e769f1d1dabd406476a4fc7c120e9884fd02acbb8f26c9", nil, "us-west-2", + assert_fetch_s3 url, "ef07487bfd8e3ca594f8fc29775b70c0a0636f51318f95d4f12b2e6e1fd8c716", nil, "us-west-2", '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass"}' end ensure @@ -179,7 +199,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase url = "s3://my-bucket/gems/specs.4.8.gz" Time.stub :now, Time.at(1_561_353_581) do - assert_fetch_s3 url, "935160a427ef97e7630f799232b8f208c4a4e49aad07d0540572a2ad5fe9f93c", "testtoken", "us-east-1", + assert_fetch_s3 url, "e709338735f9077edf8f6b94b247171c266a9605975e08e4a519a123c3322625", "testtoken", "us-east-1", '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass", "Token": "testtoken"}' end ensure diff --git a/test/rubygems/test_gem_resolver_best_set.rb b/test/rubygems/test_gem_resolver_best_set.rb index 02f542efc0..ac186884d1 100644 --- a/test/rubygems/test_gem_resolver_best_set.rb +++ b/test/rubygems/test_gem_resolver_best_set.rb @@ -31,6 +31,20 @@ class TestGemResolverBestSet < Gem::TestCase assert_equal %w[a-1], found.map(&:full_name) end + def test_pick_sets_prerelease + set = Gem::Resolver::BestSet.new + set.prerelease = true + + set.pick_sets + + sets = set.sets + + assert_equal 1, sets.count + + source_set = sets.first + assert_equal true, source_set.prerelease + end + def test_find_all_local spec_fetcher do |fetcher| fetcher.spec "a", 1 diff --git a/test/rubygems/test_gem_specification.rb b/test/rubygems/test_gem_specification.rb index 7cb7ee1605..af351f4d2e 100644 --- a/test/rubygems/test_gem_specification.rb +++ b/test/rubygems/test_gem_specification.rb @@ -1029,7 +1029,7 @@ dependencies: [] gem = "mingw" v = "1.1.1" - platforms = ["x86-mingw32", "x64-mingw32"] + platforms = ["x86-mingw32", "x64-mingw-ucrt"] # create specs platforms.each do |plat| diff --git a/test/rubygems/test_webauthn_listener.rb b/test/rubygems/test_webauthn_listener.rb index 08edabceb2..ded4128928 100644 --- a/test/rubygems/test_webauthn_listener.rb +++ b/test/rubygems/test_webauthn_listener.rb @@ -17,7 +17,7 @@ class WebauthnListenerTest < Gem::TestCase super end - def test_listener_thread_retreives_otp_code + def test_listener_thread_retrieves_otp_code thread = Gem::GemcutterUtilities::WebauthnListener.listener_thread(Gem.host, @server) Gem::MockBrowser.get Gem::URI("http://localhost:#{@port}?code=xyz") diff --git a/test/set/fixtures/fake_sorted_set_gem/sorted_set.rb b/test/set/fixtures/fake_sorted_set_gem/sorted_set.rb deleted file mode 100644 index f45a766303..0000000000 --- a/test/set/fixtures/fake_sorted_set_gem/sorted_set.rb +++ /dev/null @@ -1,9 +0,0 @@ -Object.instance_exec do - # Remove the constant to cancel autoload that would be fired by - # `class SortedSet` and cause circular require. - remove_const :SortedSet if const_defined?(:SortedSet) -end - -class SortedSet < Set - # ... -end diff --git a/test/set/test_sorted_set.rb b/test/set/test_sorted_set.rb deleted file mode 100644 index f7ad7af299..0000000000 --- a/test/set/test_sorted_set.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: false -require 'test/unit' -require 'set' - -class TC_SortedSet < Test::Unit::TestCase - def base_dir - "#{__dir__}/../lib" - end - - def assert_runs(ruby, options: nil) - options = ['-I', base_dir, *options] - r = system(RbConfig.ruby, *options, '-e', ruby) - assert(r) - end - - def test_error - assert_runs <<~RUBY - require "set" - - r = begin - puts SortedSet.new - rescue Exception => e - e.message - end - raise r unless r.match?(/has been extracted/) - RUBY - end - - def test_ok_with_gem - assert_runs <<~RUBY, options: ['-I', "#{__dir__}/fixtures/fake_sorted_set_gem"] - require "set" - - var = SortedSet.new.to_s - RUBY - end - - def test_ok_require - assert_runs <<~RUBY, options: ['-I', "#{__dir__}/fixtures/fake_sorted_set_gem"] - require "set" - require "sorted_set" - - var = SortedSet.new.to_s - RUBY - end -end diff --git a/test/socket/test_addrinfo.rb b/test/socket/test_addrinfo.rb index c61764d76d..0c9529090e 100644 --- a/test/socket/test_addrinfo.rb +++ b/test/socket/test_addrinfo.rb @@ -360,6 +360,12 @@ class TestSocketAddrinfo < Test::Unit::TestCase assert_raise(Socket::ResolutionError) { Addrinfo.tcp("0.0.0.0", 4649).family_addrinfo("::1", 80) } end + def test_ractor_shareable + assert_ractor(<<~'RUBY', require: 'socket', timeout: 60) + Ractor.make_shareable Addrinfo.new "\x10\x02\x14\xE9\xE0\x00\x00\xFB\x00\x00\x00\x00\x00\x00\x00\x00".b + RUBY + end + def random_port # IANA suggests dynamic port for 49152 to 65535 # http://www.iana.org/assignments/port-numbers diff --git a/test/socket/test_socket.rb b/test/socket/test_socket.rb index 165990dd64..4b85d43291 100644 --- a/test/socket/test_socket.rb +++ b/test/socket/test_socket.rb @@ -937,6 +937,32 @@ class TestSocket < Test::Unit::TestCase RUBY end + def test_tcp_socket_open_timeout + opts = %w[-rsocket -W1] + assert_separately opts, <<~RUBY + Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_| + if family == Socket::AF_INET6 + sleep + else + [Addrinfo.tcp("127.0.0.1", 12345)] + end + end + + assert_raise(Errno::ETIMEDOUT) do + Socket.tcp("localhost", 12345, open_timeout: 0.01) + end + RUBY + end + + def test_tcp_socket_open_timeout_with_other_timeouts + opts = %w[-rsocket -W1] + assert_separately opts, <<~RUBY + assert_raise(ArgumentError) do + Socket.tcp("localhost", 12345, open_timeout: 0.01, resolv_timout: 0.01) + end + RUBY + end + def test_tcp_socket_one_hostname_resolution_succeeded_at_least opts = %w[-rsocket -W1] assert_separately opts, <<~RUBY diff --git a/test/socket/test_tcp.rb b/test/socket/test_tcp.rb index be6d59b31e..58fe44a279 100644 --- a/test/socket/test_tcp.rb +++ b/test/socket/test_tcp.rb @@ -73,6 +73,30 @@ class TestSocket_TCPSocket < Test::Unit::TestCase end end + def test_tcp_initialize_open_timeout + return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/ + + server = TCPServer.new("127.0.0.1", 0) + port = server.connect_address.ip_port + server.close + + assert_raise(Errno::ETIMEDOUT) do + TCPSocket.new( + "localhost", + port, + open_timeout: 0.01, + fast_fallback: true, + test_mode_settings: { delay: { ipv4: 1000 } } + ) + end + end + + def test_initialize_open_timeout_with_other_timeouts + assert_raise(ArgumentError) do + TCPSocket.new("localhost", 12345, open_timeout: 0.01, resolv_timeout: 0.01) + end + end + def test_initialize_connect_timeout assert_raise(IO::TimeoutError, Errno::ENETUNREACH, Errno::EACCES) do TCPSocket.new("192.0.2.1", 80, connect_timeout: 0) diff --git a/test/stringio/test_ractor.rb b/test/stringio/test_ractor.rb index 4a2033bc1f..6acf53fb0a 100644 --- a/test/stringio/test_ractor.rb +++ b/test/stringio/test_ractor.rb @@ -8,6 +8,10 @@ class TestStringIOInRactor < Test::Unit::TestCase def test_ractor assert_in_out_err([], <<-"end;", ["true"], []) + class Ractor + alias value take unless method_defined? :value # compat with Ruby 3.4 and olders + end + require "stringio" $VERBOSE = nil r = Ractor.new do @@ -17,7 +21,7 @@ class TestStringIOInRactor < Test::Unit::TestCase io.puts "def" "\0\0\0\0def\n" == io.string end - puts r.take + puts r.value end; end end diff --git a/test/strscan/test_ractor.rb b/test/strscan/test_ractor.rb index 9a279d2929..a13fd8fd13 100644 --- a/test/strscan/test_ractor.rb +++ b/test/strscan/test_ractor.rb @@ -8,6 +8,10 @@ class TestStringScannerRactor < Test::Unit::TestCase def test_ractor assert_in_out_err([], <<-"end;", ["stra", " ", "strb", " ", "strc"], []) + class Ractor + alias value take unless method_defined? :value # compat with Ruby 3.4 and olders + end + require "strscan" $VERBOSE = nil r = Ractor.new do @@ -22,7 +26,7 @@ class TestStringScannerRactor < Test::Unit::TestCase s.scan(/\\w+/) ] end - puts r.take.compact + puts r.value.compact end; end end diff --git a/test/test_delegate.rb b/test/test_delegate.rb index f7bedf37fb..ff7998ee43 100644 --- a/test/test_delegate.rb +++ b/test/test_delegate.rb @@ -23,7 +23,7 @@ class TestDelegateClass < Test::Unit::TestCase def test_systemcallerror_eq e = SystemCallError.new(0) - assert((SimpleDelegator.new(e) == e) == (e == SimpleDelegator.new(e)), "[ruby-dev:34808]") + assert_equal((SimpleDelegator.new(e) == e), (e == SimpleDelegator.new(e)), "[ruby-dev:34808]") end class Myclass < DelegateClass(Array);end @@ -181,8 +181,8 @@ class TestDelegateClass < Test::Unit::TestCase assert_nothing_raised(bug2679) {d.dup[0] += 1} assert_raise(FrozenError) {d.clone[0] += 1} d.freeze - assert(d.clone.frozen?) - assert(!d.dup.frozen?) + assert_predicate(d.clone, :frozen?) + assert_not_predicate(d.dup, :frozen?) end def test_frozen diff --git a/test/test_rbconfig.rb b/test/test_rbconfig.rb index 7dbd525e99..e01264762d 100644 --- a/test/test_rbconfig.rb +++ b/test/test_rbconfig.rb @@ -60,7 +60,7 @@ class TestRbConfig < Test::Unit::TestCase [sizeof_int, fixnum_max] end - sizeof_int, fixnum_max = r.take + sizeof_int, fixnum_max = r.value assert_kind_of Integer, sizeof_int, "RbConfig::SIZEOF['int'] should be an Integer" assert_kind_of Integer, fixnum_max, "RbConfig::LIMITS['FIXNUM_MAX'] should be an Integer" diff --git a/test/test_time.rb b/test/test_time.rb index 23e8e104a1..55964d02fc 100644 --- a/test/test_time.rb +++ b/test/test_time.rb @@ -74,7 +74,7 @@ class TestTimeExtension < Test::Unit::TestCase # :nodoc: if defined?(Ractor) def test_rfc2822_ractor assert_ractor(<<~RUBY, require: 'time') - actual = Ractor.new { Time.rfc2822("Fri, 21 Nov 1997 09:55:06 -0600") }.take + actual = Ractor.new { Time.rfc2822("Fri, 21 Nov 1997 09:55:06 -0600") }.value assert_equal(Time.utc(1997, 11, 21, 9, 55, 6) + 6 * 3600, actual) RUBY end diff --git a/test/test_tmpdir.rb b/test/test_tmpdir.rb index adc29183a8..c91fc334ed 100644 --- a/test/test_tmpdir.rb +++ b/test/test_tmpdir.rb @@ -134,17 +134,32 @@ class TestTmpdir < Test::Unit::TestCase def test_ractor assert_ractor(<<~'end;', require: "tmpdir") - r = Ractor.new do - Dir.mktmpdir() do |d| - Ractor.yield d - Ractor.receive + if defined?(Ractor::Port) + port = Ractor::Port.new + r = Ractor.new port do |port| + Dir.mktmpdir() do |d| + port << d + Ractor.receive + end end + dir = port.receive + assert_file.directory? dir + r.send true + r.join + assert_file.not_exist? dir + else + r = Ractor.new do + Dir.mktmpdir() do |d| + Ractor.yield d + Ractor.receive + end + end + dir = r.take + assert_file.directory? dir + r.send true + r.take + assert_file.not_exist? dir end - dir = r.take - assert_file.directory? dir - r.send true - r.take - assert_file.not_exist? dir end; end end diff --git a/test/uri/test_common.rb b/test/uri/test_common.rb index 6326aec561..1291366936 100644 --- a/test/uri/test_common.rb +++ b/test/uri/test_common.rb @@ -75,7 +75,7 @@ class URI::TestCommon < Test::Unit::TestCase return unless defined?(Ractor) assert_ractor(<<~RUBY, require: 'uri') r = Ractor.new { URI.parse("https://ruby-lang.org/").inspect } - assert_equal(URI.parse("https://ruby-lang.org/").inspect, r.take) + assert_equal(URI.parse("https://ruby-lang.org/").inspect, r.value) RUBY end @@ -113,17 +113,18 @@ class URI::TestCommon < Test::Unit::TestCase def test_register_scheme_with_symbols # Valid schemes from https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml - some_uri_class = Class.new(URI::Generic) - assert_raise(NameError) { URI.register_scheme 'ms-search', some_uri_class } - assert_raise(NameError) { URI.register_scheme 'microsoft.windows.camera', some_uri_class } - assert_raise(NameError) { URI.register_scheme 'coaps+ws', some_uri_class } + list = [] + %w[ms-search microsoft.windows.camera coaps+ws].each {|name| + list << [name, URI.register_scheme(name, Class.new(URI::Generic))] + } - ms_search_class = Class.new(URI::Generic) - URI.register_scheme 'MS_SEARCH', ms_search_class - begin - assert_equal URI::Generic, URI.parse('ms-search://localhost').class - ensure - URI.const_get(:Schemes).send(:remove_const, :MS_SEARCH) + list.each do |scheme, uri_class| + assert_equal uri_class, URI.parse("#{scheme}://localhost").class + end + ensure + schemes = URI.const_get(:Schemes) + list.each do |scheme, | + schemes.send(:remove_const, schemes.escape(scheme)) end end diff --git a/test/uri/test_ftp.rb b/test/uri/test_ftp.rb index f45bb0667c..3ad7864490 100644 --- a/test/uri/test_ftp.rb +++ b/test/uri/test_ftp.rb @@ -33,11 +33,11 @@ class URI::TestFTP < Test::Unit::TestCase # If you think what's below is wrong, please read RubyForge bug 2055, # RFC 1738 section 3.2.2, and RFC 2396. u = URI.parse('ftp://ftp.example.com/foo/bar/file.ext') - assert(u.path == 'foo/bar/file.ext') + assert_equal('foo/bar/file.ext', u.path) u = URI.parse('ftp://ftp.example.com//foo/bar/file.ext') - assert(u.path == '/foo/bar/file.ext') + assert_equal('/foo/bar/file.ext', u.path) u = URI.parse('ftp://ftp.example.com/%2Ffoo/bar/file.ext') - assert(u.path == '/foo/bar/file.ext') + assert_equal('/foo/bar/file.ext', u.path) end def test_assemble @@ -45,8 +45,8 @@ class URI::TestFTP < Test::Unit::TestCase # assuming everyone else has implemented RFC 2396. uri = URI::FTP.build(['user:password', 'ftp.example.com', nil, '/path/file.zip', 'i']) - assert(uri.to_s == - 'ftp://user:password@ftp.example.com/%2Fpath/file.zip;type=i') + assert_equal('ftp://user:password@ftp.example.com/%2Fpath/file.zip;type=i', + uri.to_s) end def test_select diff --git a/test/uri/test_generic.rb b/test/uri/test_generic.rb index 1d5fbc715e..c725116e96 100644 --- a/test/uri/test_generic.rb +++ b/test/uri/test_generic.rb @@ -240,9 +240,9 @@ class URI::TestGeneric < Test::Unit::TestCase u = URI.parse('http://foo/bar/baz') assert_equal(nil, u.merge!("")) assert_equal(nil, u.merge!(u)) - assert(nil != u.merge!(".")) + refute_nil(u.merge!(".")) assert_equal('http://foo/bar/', u.to_s) - assert(nil != u.merge!("../baz")) + refute_nil(u.merge!("../baz")) assert_equal('http://foo/baz', u.to_s) url = URI.parse('http://a/b//c') + 'd//e' @@ -356,7 +356,7 @@ class URI::TestGeneric < Test::Unit::TestCase assert_equal('http://a/b/c/g', url.to_s) url = @base_url.route_to('http://a/b/c/g') assert_kind_of(URI::Generic, url) - assert('./g' != url.to_s) # ok + refute_equal('./g', url.to_s) # ok assert_equal('g', url.to_s) # http://a/b/c/d;p?q @@ -375,7 +375,7 @@ class URI::TestGeneric < Test::Unit::TestCase assert_equal('http://a/g', url.to_s) url = @base_url.route_to('http://a/g') assert_kind_of(URI::Generic, url) - assert('/g' != url.to_s) # ok + refute_equal('/g', url.to_s) # ok assert_equal('../../g', url.to_s) # http://a/b/c/d;p?q @@ -466,7 +466,7 @@ class URI::TestGeneric < Test::Unit::TestCase assert_equal('http://a/b/c/', url.to_s) url = @base_url.route_to('http://a/b/c/') assert_kind_of(URI::Generic, url) - assert('.' != url.to_s) # ok + refute_equal('.', url.to_s) # ok assert_equal('./', url.to_s) # http://a/b/c/d;p?q @@ -485,7 +485,7 @@ class URI::TestGeneric < Test::Unit::TestCase assert_equal('http://a/b/', url.to_s) url = @base_url.route_to('http://a/b/') assert_kind_of(URI::Generic, url) - assert('..' != url.to_s) # ok + refute_equal('..', url.to_s) # ok assert_equal('../', url.to_s) # http://a/b/c/d;p?q @@ -513,7 +513,7 @@ class URI::TestGeneric < Test::Unit::TestCase assert_equal('http://a/', url.to_s) url = @base_url.route_to('http://a/') assert_kind_of(URI::Generic, url) - assert('../..' != url.to_s) # ok + refute_equal('../..', url.to_s) # ok assert_equal('../../', url.to_s) # http://a/b/c/d;p?q @@ -604,7 +604,7 @@ class URI::TestGeneric < Test::Unit::TestCase assert_equal('http://a/g', url.to_s) url = @base_url.route_to('http://a/g') assert_kind_of(URI::Generic, url) - assert('../../../g' != url.to_s) # ok? yes, it confuses you + refute_equal('../../../g', url.to_s) # ok? yes, it confuses you assert_equal('../../g', url.to_s) # and it is clearly # http://a/b/c/d;p?q @@ -614,7 +614,7 @@ class URI::TestGeneric < Test::Unit::TestCase assert_equal('http://a/g', url.to_s) url = @base_url.route_to('http://a/g') assert_kind_of(URI::Generic, url) - assert('../../../../g' != url.to_s) # ok? yes, it confuses you + refute_equal('../../../../g', url.to_s) # ok? yes, it confuses you assert_equal('../../g', url.to_s) # and it is clearly # http://a/b/c/d;p?q @@ -624,7 +624,7 @@ class URI::TestGeneric < Test::Unit::TestCase assert_equal('http://a/b/g', url.to_s) url = @base_url.route_to('http://a/b/g') assert_kind_of(URI::Generic, url) - assert('./../g' != url.to_s) # ok + refute_equal('./../g', url.to_s) # ok assert_equal('../g', url.to_s) # http://a/b/c/d;p?q @@ -634,7 +634,7 @@ class URI::TestGeneric < Test::Unit::TestCase assert_equal('http://a/b/c/g/', url.to_s) url = @base_url.route_to('http://a/b/c/g/') assert_kind_of(URI::Generic, url) - assert('./g/.' != url.to_s) # ok + refute_equal('./g/.', url.to_s) # ok assert_equal('g/', url.to_s) # http://a/b/c/d;p?q @@ -644,7 +644,7 @@ class URI::TestGeneric < Test::Unit::TestCase assert_equal('http://a/b/c/g/h', url.to_s) url = @base_url.route_to('http://a/b/c/g/h') assert_kind_of(URI::Generic, url) - assert('g/./h' != url.to_s) # ok + refute_equal('g/./h', url.to_s) # ok assert_equal('g/h', url.to_s) # http://a/b/c/d;p?q @@ -654,7 +654,7 @@ class URI::TestGeneric < Test::Unit::TestCase assert_equal('http://a/b/c/h', url.to_s) url = @base_url.route_to('http://a/b/c/h') assert_kind_of(URI::Generic, url) - assert('g/../h' != url.to_s) # ok + refute_equal('g/../h', url.to_s) # ok assert_equal('h', url.to_s) # http://a/b/c/d;p?q @@ -664,7 +664,7 @@ class URI::TestGeneric < Test::Unit::TestCase assert_equal('http://a/b/c/g;x=1/y', url.to_s) url = @base_url.route_to('http://a/b/c/g;x=1/y') assert_kind_of(URI::Generic, url) - assert('g;x=1/./y' != url.to_s) # ok + refute_equal('g;x=1/./y', url.to_s) # ok assert_equal('g;x=1/y', url.to_s) # http://a/b/c/d;p?q @@ -674,7 +674,7 @@ class URI::TestGeneric < Test::Unit::TestCase assert_equal('http://a/b/c/y', url.to_s) url = @base_url.route_to('http://a/b/c/y') assert_kind_of(URI::Generic, url) - assert('g;x=1/../y' != url.to_s) # ok + refute_equal('g;x=1/../y', url.to_s) # ok assert_equal('y', url.to_s) # http://a/b/c/d;p?q @@ -822,18 +822,18 @@ class URI::TestGeneric < Test::Unit::TestCase hierarchical = URI.parse('http://a.b.c/example') opaque = URI.parse('mailto:mduerst@ifi.unizh.ch') - assert hierarchical.hierarchical? - refute opaque.hierarchical? + assert_predicate hierarchical, :hierarchical? + refute_predicate opaque, :hierarchical? end def test_absolute abs_uri = URI.parse('http://a.b.c/') not_abs = URI.parse('a.b.c') - refute not_abs.absolute? + refute_predicate not_abs, :absolute? - assert abs_uri.absolute - assert abs_uri.absolute? + assert_predicate abs_uri, :absolute + assert_predicate abs_uri, :absolute? end def test_ipv6 @@ -846,8 +846,10 @@ class URI::TestGeneric < Test::Unit::TestCase assert_equal("http://[::1]/bar", u.to_s) u.hostname = "::1" assert_equal("http://[::1]/bar", u.to_s) - u.hostname = "" - assert_equal("http:///bar", u.to_s) + + u = URI("file://foo/bar") + u.hostname = '' + assert_equal("file:///bar", u.to_s) end def test_build @@ -868,6 +870,19 @@ class URI::TestGeneric < Test::Unit::TestCase assert_equal("http://[::1]/bar/baz", u.to_s) assert_equal("[::1]", u.host) assert_equal("::1", u.hostname) + + assert_raise_with_message(ArgumentError, /URI::Generic/) { + URI::Generic.build(nil) + } + + c = Class.new(URI::Generic) do + def self.component; raise; end + end + expected = /\(#{URI::Generic::COMPONENT.join(', ')}\)/ + message = "fallback to URI::Generic::COMPONENT if component raised" + assert_raise_with_message(ArgumentError, expected, message) { + c.build(nil) + } end def test_build2 diff --git a/test/uri/test_http.rb b/test/uri/test_http.rb index e937b1a26b..8816d20175 100644 --- a/test/uri/test_http.rb +++ b/test/uri/test_http.rb @@ -19,6 +19,10 @@ class URI::TestHTTP < Test::Unit::TestCase assert_kind_of(URI::HTTP, u) end + def test_build_empty_host + assert_raise(URI::InvalidComponentError) { URI::HTTP.build(host: '') } + end + def test_parse u = URI.parse('http://a') assert_kind_of(URI::HTTP, u) @@ -33,19 +37,19 @@ class URI::TestHTTP < Test::Unit::TestCase host = 'aBcD' u1 = URI.parse('http://' + host + '/eFg?HiJ') u2 = URI.parse('http://' + host.downcase + '/eFg?HiJ') - assert(u1.normalize.host == 'abcd') - assert(u1.normalize.path == u1.path) - assert(u1.normalize == u2.normalize) - assert(!u1.normalize.host.equal?(u1.host)) - assert( u2.normalize.host.equal?(u2.host)) + assert_equal('abcd', u1.normalize.host) + assert_equal(u1.path, u1.normalize.path) + assert_equal(u2.normalize, u1.normalize) + refute_same(u1.host, u1.normalize.host) + assert_same(u2.host, u2.normalize.host) assert_equal('http://abc/', URI.parse('http://abc').normalize.to_s) end def test_equal - assert(URI.parse('http://abc') == URI.parse('http://ABC')) - assert(URI.parse('http://abc/def') == URI.parse('http://ABC/def')) - assert(URI.parse('http://abc/def') != URI.parse('http://ABC/DEF')) + assert_equal(URI.parse('http://ABC'), URI.parse('http://abc')) + assert_equal(URI.parse('http://ABC/def'), URI.parse('http://abc/def')) + refute_equal(URI.parse('http://ABC/DEF'), URI.parse('http://abc/def')) end def test_request_uri diff --git a/test/uri/test_mailto.rb b/test/uri/test_mailto.rb index e7d3142198..59bb5ded09 100644 --- a/test/uri/test_mailto.rb +++ b/test/uri/test_mailto.rb @@ -141,6 +141,11 @@ class URI::TestMailTo < Test::Unit::TestCase def test_check_to u = URI::MailTo.build(['joe@example.com', 'subject=Ruby']) + # Valid emails + u.to = 'a@valid.com' + assert_equal(u.to, 'a@valid.com') + + # Invalid emails assert_raise(URI::InvalidComponentError) do u.to = '#1@mail.com' end @@ -148,6 +153,79 @@ class URI::TestMailTo < Test::Unit::TestCase assert_raise(URI::InvalidComponentError) do u.to = '@invalid.email' end + + assert_raise(URI::InvalidComponentError) do + u.to = '.hello@invalid.email' + end + + assert_raise(URI::InvalidComponentError) do + u.to = 'hello.@invalid.email' + end + + assert_raise(URI::InvalidComponentError) do + u.to = 'n.@invalid.email' + end + + assert_raise(URI::InvalidComponentError) do + u.to = 'n..t@invalid.email' + end + + # Invalid host emails + assert_raise(URI::InvalidComponentError) do + u.to = 'a@.invalid.email' + end + + assert_raise(URI::InvalidComponentError) do + u.to = 'a@invalid.email.' + end + + assert_raise(URI::InvalidComponentError) do + u.to = 'a@invalid..email' + end + + assert_raise(URI::InvalidComponentError) do + u.to = 'a@-invalid.email' + end + + assert_raise(URI::InvalidComponentError) do + u.to = 'a@invalid-.email' + end + + assert_raise(URI::InvalidComponentError) do + u.to = 'a@invalid.-email' + end + + assert_raise(URI::InvalidComponentError) do + u.to = 'a@invalid.email-' + end + + u.to = 'a@'+'invalid'.ljust(63, 'd')+'.email' + assert_raise(URI::InvalidComponentError) do + u.to = 'a@'+'invalid'.ljust(64, 'd')+'.email' + end + + u.to = 'a@invalid.'+'email'.rjust(63, 'e') + assert_raise(URI::InvalidComponentError) do + u.to = 'a@invalid.'+'email'.rjust(64, 'e') + end + end + + def test_email_regexp + re = URI::MailTo::EMAIL_REGEXP + + repeat = 10 + longlabel = '.' + 'invalid'.ljust(63, 'd') + endlabel = '' + seq = (1..3).map {|i| 10**i} + rehearsal = 10 + pre = ->(n) {'a@invalid' + longlabel*(n) + endlabel} + assert_linear_performance(seq, rehearsal: rehearsal, pre: pre) do |to| + repeat.times {re =~ to or flunk} + end + endlabel = '.' + 'email'.rjust(64, 'd') + assert_linear_performance(seq, rehearsal: rehearsal, pre: pre) do |to| + repeat.times {re =~ to and flunk} + end end def test_to_s diff --git a/test/uri/test_parser.rb b/test/uri/test_parser.rb index f455a5cc9b..c14824f5e8 100644 --- a/test/uri/test_parser.rb +++ b/test/uri/test_parser.rb @@ -20,17 +20,17 @@ class URI::TestParser < Test::Unit::TestCase u2 = p.parse(url) u3 = p.parse(url) - assert(u0 == u1) - assert(u0.eql?(u1)) - assert(!u0.equal?(u1)) + assert_equal(u1, u0) + assert_send([u0, :eql?, u1]) + refute_same(u1, u0) - assert(u1 == u2) - assert(!u1.eql?(u2)) - assert(!u1.equal?(u2)) + assert_equal(u2, u1) + assert_not_send([u1, :eql?, u2]) + refute_same(u1, u2) - assert(u2 == u3) - assert(u2.eql?(u3)) - assert(!u2.equal?(u3)) + assert_equal(u3, u2) + assert_send([u2, :eql?, u3]) + refute_same(u3, u2) end def test_parse_rfc2396_parser @@ -113,4 +113,12 @@ class URI::TestParser < Test::Unit::TestCase end end end + + def test_rfc2822_make_regexp + parser = URI::RFC2396_Parser.new + regexp = parser.make_regexp("HTTP") + assert_match(regexp, "HTTP://EXAMPLE.COM/") + assert_match(regexp, "http://example.com/") + refute_match(regexp, "https://example.com/") + end end diff --git a/test/uri/test_ws.rb b/test/uri/test_ws.rb index f3918f617c..d63ebd4a46 100644 --- a/test/uri/test_ws.rb +++ b/test/uri/test_ws.rb @@ -31,19 +31,19 @@ class URI::TestWS < Test::Unit::TestCase host = 'aBcD' u1 = URI.parse('ws://' + host + '/eFg?HiJ') u2 = URI.parse('ws://' + host.downcase + '/eFg?HiJ') - assert(u1.normalize.host == 'abcd') - assert(u1.normalize.path == u1.path) - assert(u1.normalize == u2.normalize) - assert(!u1.normalize.host.equal?(u1.host)) - assert( u2.normalize.host.equal?(u2.host)) + assert_equal('abcd', u1.normalize.host) + assert_equal(u1.path, u1.normalize.path) + assert_equal(u2.normalize, u1.normalize) + refute_same(u1.host, u1.normalize.host) + assert_same(u2.host, u2.normalize.host) assert_equal('ws://abc/', URI.parse('ws://abc').normalize.to_s) end def test_equal - assert(URI.parse('ws://abc') == URI.parse('ws://ABC')) - assert(URI.parse('ws://abc/def') == URI.parse('ws://ABC/def')) - assert(URI.parse('ws://abc/def') != URI.parse('ws://ABC/DEF')) + assert_equal(URI.parse('ws://ABC'), URI.parse('ws://abc')) + assert_equal(URI.parse('ws://ABC/def'), URI.parse('ws://abc/def')) + refute_equal(URI.parse('ws://ABC/DEF'), URI.parse('ws://abc/def')) end def test_request_uri diff --git a/test/uri/test_wss.rb b/test/uri/test_wss.rb index 13a2583059..cbef327cc6 100644 --- a/test/uri/test_wss.rb +++ b/test/uri/test_wss.rb @@ -31,19 +31,19 @@ class URI::TestWSS < Test::Unit::TestCase host = 'aBcD' u1 = URI.parse('wss://' + host + '/eFg?HiJ') u2 = URI.parse('wss://' + host.downcase + '/eFg?HiJ') - assert(u1.normalize.host == 'abcd') - assert(u1.normalize.path == u1.path) - assert(u1.normalize == u2.normalize) - assert(!u1.normalize.host.equal?(u1.host)) - assert( u2.normalize.host.equal?(u2.host)) + assert_equal('abcd', u1.normalize.host) + assert_equal(u1.path, u1.normalize.path) + assert_equal(u2.normalize, u1.normalize) + refute_same(u1.host, u1.normalize.host) + assert_same(u2.host, u2.normalize.host) assert_equal('wss://abc/', URI.parse('wss://abc').normalize.to_s) end def test_equal - assert(URI.parse('wss://abc') == URI.parse('wss://ABC')) - assert(URI.parse('wss://abc/def') == URI.parse('wss://ABC/def')) - assert(URI.parse('wss://abc/def') != URI.parse('wss://ABC/DEF')) + assert_equal(URI.parse('wss://ABC'), URI.parse('wss://abc')) + assert_equal(URI.parse('wss://ABC/def'), URI.parse('wss://abc/def')) + refute_equal(URI.parse('wss://ABC/DEF'), URI.parse('wss://abc/def')) end def test_request_uri diff --git a/thread.c b/thread.c index 6089184ea9..6eb16beed6 100644 --- a/thread.c +++ b/thread.c @@ -148,7 +148,7 @@ static int hrtime_update_expire(rb_hrtime_t *, const rb_hrtime_t); NORETURN(static void async_bug_fd(const char *mesg, int errno_arg, int fd)); MAYBE_UNUSED(static int consume_communication_pipe(int fd)); -static volatile int system_working = 1; +static rb_atomic_t system_working = 1; static rb_internal_thread_specific_key_t specific_key_count; /********************************************************************************/ @@ -173,7 +173,7 @@ static inline void blocking_region_end(rb_thread_t *th, struct rb_blocking_regio #define THREAD_BLOCKING_END(th) \ thread_sched_to_running((sched), (th)); \ - rb_ractor_thread_switch(th->ractor, th); \ + rb_ractor_thread_switch(th->ractor, th, false); \ } while(0) #ifdef __GNUC__ @@ -207,6 +207,10 @@ static inline void blocking_region_end(rb_thread_t *th, struct rb_blocking_regio static inline int vm_check_ints_blocking(rb_execution_context_t *ec) { +#ifdef RUBY_ASSERT_CRITICAL_SECTION + VM_ASSERT(ruby_assert_critical_section_entered == 0); +#endif + rb_thread_t *th = rb_ec_thread_ptr(ec); if (LIKELY(rb_threadptr_pending_interrupt_empty_p(th))) { @@ -515,20 +519,13 @@ thread_cleanup_func(void *th_ptr, int atfork) th->locking_mutex = Qfalse; thread_cleanup_func_before_exec(th_ptr); - /* - * Unfortunately, we can't release native threading resource at fork - * because libc may have unstable locking state therefore touching - * a threading resource may cause a deadlock. - */ if (atfork) { + native_thread_destroy_atfork(th->nt); th->nt = NULL; return; } rb_native_mutex_destroy(&th->interrupt_lock); -#ifndef RUBY_THREAD_PTHREAD_H - rb_native_cond_destroy(&th->ractor_waiting.cond); -#endif } static VALUE rb_threadptr_raise(rb_thread_t *, int, VALUE *); @@ -706,6 +703,10 @@ thread_start_func_2(rb_thread_t *th, VALUE *stack_start) /* fatal error within this thread, need to stop whole script */ } else if (rb_obj_is_kind_of(errinfo, rb_eSystemExit)) { + if (th->invoke_type == thread_invoke_type_ractor_proc) { + rb_ractor_atexit_exception(th->ec); + } + /* exit on main_thread. */ } else { @@ -1055,23 +1056,28 @@ thread_join_sleep(VALUE arg) while (!thread_finished(target_th)) { VALUE scheduler = rb_fiber_scheduler_current(); - if (scheduler != Qnil) { - rb_fiber_scheduler_block(scheduler, target_th->self, p->timeout); - // Check if the target thread is finished after blocking: - if (thread_finished(target_th)) break; - // Otherwise, a timeout occurred: - else return Qfalse; - } - else if (!limit) { - sleep_forever(th, SLEEP_DEADLOCKABLE | SLEEP_ALLOW_SPURIOUS | SLEEP_NO_CHECKINTS); + if (!limit) { + if (scheduler != Qnil) { + rb_fiber_scheduler_block(scheduler, target_th->self, Qnil); + } + else { + sleep_forever(th, SLEEP_DEADLOCKABLE | SLEEP_ALLOW_SPURIOUS | SLEEP_NO_CHECKINTS); + } } else { if (hrtime_update_expire(limit, end)) { RUBY_DEBUG_LOG("timeout target_th:%u", rb_th_serial(target_th)); return Qfalse; } - th->status = THREAD_STOPPED; - native_sleep(th, limit); + + if (scheduler != Qnil) { + VALUE timeout = rb_float_new(hrtime2double(*limit)); + rb_fiber_scheduler_block(scheduler, target_th->self, timeout); + } + else { + th->status = THREAD_STOPPED; + native_sleep(th, limit); + } } RUBY_VM_CHECK_INTS_BLOCKING(th->ec); th->status = THREAD_RUNNABLE; @@ -1130,6 +1136,10 @@ thread_join(rb_thread_t *target_th, VALUE timeout, rb_hrtime_t *limit) /* OK. killed. */ break; default: + if (err == RUBY_FATAL_FIBER_KILLED) { // not integer constant so can't be a case expression + // root fiber killed in non-main thread + break; + } rb_bug("thread_join: Fixnum (%d) should not reach here.", FIX2INT(err)); } } @@ -1470,7 +1480,7 @@ rb_thread_schedule_limits(uint32_t limits_us) RB_VM_SAVE_MACHINE_CONTEXT(th); thread_sched_yield(TH_SCHED(th), th); - rb_ractor_thread_switch(th->ractor, th); + rb_ractor_thread_switch(th->ractor, th, true); RUBY_DEBUG_LOG("switch %s", "done"); } @@ -1518,7 +1528,7 @@ blocking_region_end(rb_thread_t *th, struct rb_blocking_region_buffer *region) unregister_ubf_list(th); thread_sched_to_running(TH_SCHED(th), th); - rb_ractor_thread_switch(th->ractor, th); + rb_ractor_thread_switch(th->ractor, th, false); th->blocking_region_buffer = 0; rb_ractor_blocking_threads_dec(th->ractor, __FILE__, __LINE__); @@ -1534,6 +1544,29 @@ blocking_region_end(rb_thread_t *th, struct rb_blocking_region_buffer *region) #endif } +/* + * Resolve sentinel unblock function values to their actual function pointers + * and appropriate data2 values. This centralizes the logic for handling + * RUBY_UBF_IO and RUBY_UBF_PROCESS sentinel values. + * + * @param unblock_function Pointer to unblock function pointer (modified in place) + * @param data2 Pointer to data2 pointer (modified in place) + * @param thread Thread context for resolving data2 when needed + * @return true if sentinel values were resolved, false otherwise + */ +bool +rb_thread_resolve_unblock_function(rb_unblock_function_t **unblock_function, void **data2, struct rb_thread_struct *thread) +{ + rb_unblock_function_t *ubf = *unblock_function; + + if ((ubf == RUBY_UBF_IO) || (ubf == RUBY_UBF_PROCESS)) { + *unblock_function = ubf_select; + *data2 = thread; + return true; + } + return false; +} + void * rb_nogvl(void *(*func)(void *), void *data1, rb_unblock_function_t *ubf, void *data2, @@ -1542,7 +1575,7 @@ rb_nogvl(void *(*func)(void *), void *data1, if (flags & RB_NOGVL_OFFLOAD_SAFE) { VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { - struct rb_fiber_scheduler_blocking_operation_state state; + struct rb_fiber_scheduler_blocking_operation_state state = {0}; VALUE result = rb_fiber_scheduler_blocking_operation_wait(scheduler, func, data1, ubf, data2, flags, &state); @@ -1560,11 +1593,9 @@ rb_nogvl(void *(*func)(void *), void *data1, bool is_main_thread = vm->ractor.main_thread == th; int saved_errno = 0; - if ((ubf == RUBY_UBF_IO) || (ubf == RUBY_UBF_PROCESS)) { - ubf = ubf_select; - data2 = th; - } - else if (ubf && rb_ractor_living_thread_num(th->ractor) == 1 && is_main_thread) { + rb_thread_resolve_unblock_function(&ubf, &data2, th); + + if (ubf && rb_ractor_living_thread_num(th->ractor) == 1 && is_main_thread) { if (flags & RB_NOGVL_UBF_ASYNC_SAFE) { vm->ubf_async_safe = 1; } @@ -1693,46 +1724,144 @@ waitfd_to_waiting_flag(int wfd_event) return wfd_event << 1; } +static struct ccan_list_head * +rb_io_blocking_operations(struct rb_io *io) +{ + rb_serial_t fork_generation = GET_VM()->fork_gen; + + // On fork, all existing entries in this list (which are stack allocated) become invalid. + // Therefore, we re-initialize the list which clears it. + if (io->fork_generation != fork_generation) { + ccan_list_head_init(&io->blocking_operations); + io->fork_generation = fork_generation; + } + + return &io->blocking_operations; +} + +/* + * Registers a blocking operation for an IO object. This is used to track all threads and fibers + * that are currently blocked on this IO for reading, writing or other operations. + * + * When the IO is closed, all blocking operations will be notified via rb_fiber_scheduler_fiber_interrupt + * for fibers with a scheduler, or via rb_threadptr_interrupt for threads without a scheduler. + * + * @parameter io The IO object on which the operation will block + * @parameter blocking_operation The operation details including the execution context that will be blocked + */ +static void +rb_io_blocking_operation_enter(struct rb_io *io, struct rb_io_blocking_operation *blocking_operation) +{ + ccan_list_add(rb_io_blocking_operations(io), &blocking_operation->list); +} + +static void +rb_io_blocking_operation_pop(struct rb_io *io, struct rb_io_blocking_operation *blocking_operation) +{ + ccan_list_del(&blocking_operation->list); +} + struct io_blocking_operation_arguments { struct rb_io *io; struct rb_io_blocking_operation *blocking_operation; }; static VALUE -io_blocking_operation_release(VALUE _arguments) { +io_blocking_operation_exit(VALUE _arguments) +{ struct io_blocking_operation_arguments *arguments = (void*)_arguments; struct rb_io_blocking_operation *blocking_operation = arguments->blocking_operation; - ccan_list_del(&blocking_operation->list); + rb_io_blocking_operation_pop(arguments->io, blocking_operation); rb_io_t *io = arguments->io; rb_thread_t *thread = io->closing_ec->thread_ptr; rb_fiber_t *fiber = io->closing_ec->fiber_ptr; if (thread->scheduler != Qnil) { + // This can cause spurious wakeups... rb_fiber_scheduler_unblock(thread->scheduler, io->self, rb_fiberptr_self(fiber)); - } else { + } + else { rb_thread_wakeup(thread->self); } return Qnil; } +/* + * Called when a blocking operation completes or is interrupted. Removes the operation from + * the IO's blocking_operations list and wakes up any waiting threads/fibers. + * + * If there's a wakeup_mutex (meaning an IO close is in progress), synchronizes the cleanup + * through that mutex to ensure proper coordination with the closing thread. + * + * @parameter io The IO object the operation was performed on + * @parameter blocking_operation The completed operation to clean up + */ static void -rb_io_blocking_operation_release(struct rb_io *io, struct rb_io_blocking_operation *blocking_operation) +rb_io_blocking_operation_exit(struct rb_io *io, struct rb_io_blocking_operation *blocking_operation) { VALUE wakeup_mutex = io->wakeup_mutex; + // Indicate that the blocking operation is no longer active: + blocking_operation->ec = NULL; + if (RB_TEST(wakeup_mutex)) { struct io_blocking_operation_arguments arguments = { .io = io, .blocking_operation = blocking_operation }; - rb_mutex_synchronize(wakeup_mutex, io_blocking_operation_release, (VALUE)&arguments); - } else { - ccan_list_del(&blocking_operation->list); + rb_mutex_synchronize(wakeup_mutex, io_blocking_operation_exit, (VALUE)&arguments); } + else { + // If there's no wakeup_mutex, we can safely remove the operation directly: + rb_io_blocking_operation_pop(io, blocking_operation); + } +} + +static VALUE +rb_thread_io_blocking_operation_ensure(VALUE _argument) +{ + struct io_blocking_operation_arguments *arguments = (void*)_argument; + + rb_io_blocking_operation_exit(arguments->io, arguments->blocking_operation); + + return Qnil; +} + +/* + * Executes a function that performs a blocking IO operation, while properly tracking + * the operation in the IO's blocking_operations list. This ensures proper cleanup + * and interruption handling if the IO is closed while blocked. + * + * The operation is automatically removed from the blocking_operations list when the function + * returns, whether normally or due to an exception. + * + * @parameter self The IO object + * @parameter function The function to execute that will perform the blocking operation + * @parameter argument The argument to pass to the function + * @returns The result of the blocking operation function + */ +VALUE +rb_thread_io_blocking_operation(VALUE self, VALUE(*function)(VALUE), VALUE argument) +{ + struct rb_io *io; + RB_IO_POINTER(self, io); + + rb_execution_context_t *ec = GET_EC(); + struct rb_io_blocking_operation blocking_operation = { + .ec = ec, + }; + rb_io_blocking_operation_enter(io, &blocking_operation); + + struct io_blocking_operation_arguments io_blocking_operation_arguments = { + .io = io, + .blocking_operation = &blocking_operation + }; + + return rb_ensure(function, argument, rb_thread_io_blocking_operation_ensure, (VALUE)&io_blocking_operation_arguments); } static bool @@ -1802,8 +1931,8 @@ rb_thread_mn_schedulable(VALUE thval) VALUE rb_thread_io_blocking_call(struct rb_io* io, rb_blocking_function_t *func, void *data1, int events) { - rb_execution_context_t * ec = GET_EC(); - rb_thread_t *th = rb_ec_thread_ptr(ec); + rb_execution_context_t * volatile ec = GET_EC(); + rb_thread_t * volatile th = rb_ec_thread_ptr(ec); RUBY_DEBUG_LOG("th:%u fd:%d ev:%d", rb_th_serial(th), io->fd, events); @@ -1824,7 +1953,7 @@ rb_thread_io_blocking_call(struct rb_io* io, rb_blocking_function_t *func, void struct rb_io_blocking_operation blocking_operation = { .ec = ec, }; - ccan_list_add(&io->blocking_operations, &blocking_operation.list); + rb_io_blocking_operation_enter(io, &blocking_operation); { EC_PUSH_TAG(ec); @@ -1836,13 +1965,16 @@ rb_thread_io_blocking_call(struct rb_io* io, rb_blocking_function_t *func, void saved_errno = errno; }, ubf_select, th, FALSE); - th = rb_ec_thread_ptr(ec); + RUBY_ASSERT(th == rb_ec_thread_ptr(ec)); if (events && blocking_call_retryable_p((int)val, saved_errno) && thread_io_wait_events(th, fd, events, NULL)) { RUBY_VM_CHECK_INTS_BLOCKING(ec); goto retry; } + + RUBY_VM_CHECK_INTS_BLOCKING(ec); + state = saved_state; } EC_POP_TAG(); @@ -1851,15 +1983,12 @@ rb_thread_io_blocking_call(struct rb_io* io, rb_blocking_function_t *func, void th->mn_schedulable = prev_mn_schedulable; } - rb_io_blocking_operation_release(io, &blocking_operation); + rb_io_blocking_operation_exit(io, &blocking_operation); if (state) { EC_JUMP_TAG(ec, state); } - /* TODO: check func() */ - RUBY_VM_CHECK_INTS_BLOCKING(ec); - // If the error was a timeout, we raise a specific exception for that: if (saved_errno == ETIMEDOUT) { rb_raise(rb_eIOTimeoutError, "Blocking operation timed out!"); @@ -2442,8 +2571,9 @@ threadptr_get_interrupts(rb_thread_t *th) rb_atomic_t interrupt; rb_atomic_t old; + old = ATOMIC_LOAD_RELAXED(ec->interrupt_flag); do { - interrupt = ec->interrupt_flag; + interrupt = old; old = ATOMIC_CAS(ec->interrupt_flag, interrupt, interrupt & ec->interrupt_mask); } while (old != interrupt); return interrupt & (rb_atomic_t)~ec->interrupt_mask; @@ -2481,8 +2611,7 @@ rb_threadptr_execute_interrupts(rb_thread_t *th, int blocking_timing) terminate_interrupt = interrupt & TERMINATE_INTERRUPT_MASK; // request from other ractors if (interrupt & VM_BARRIER_INTERRUPT_MASK) { - RB_VM_LOCK_ENTER(); - RB_VM_LOCK_LEAVE(); + RB_VM_LOCKING(); } if (postponed_job_interrupt) { @@ -2648,31 +2777,59 @@ rb_ec_reset_raised(rb_execution_context_t *ec) return 1; } -static size_t -thread_io_close_notify_all(struct rb_io *io) +/* + * Thread-safe IO closing mechanism. + * + * When an IO is closed while other threads or fibers are blocked on it, we need to: + * 1. Track and notify all blocking operations through io->blocking_operations + * 2. Ensure only one thread can close at a time using io->closing_ec + * 3. Synchronize cleanup using wakeup_mutex + * + * The close process works as follows: + * - First check if any thread is already closing (io->closing_ec) + * - Set up wakeup_mutex for synchronization + * - Iterate through all blocking operations in io->blocking_operations + * - For each blocked fiber with a scheduler: + * - Notify via rb_fiber_scheduler_fiber_interrupt + * - For each blocked thread without a scheduler: + * - Enqueue IOError via rb_threadptr_pending_interrupt_enque + * - Wake via rb_threadptr_interrupt + * - Wait on wakeup_mutex until all operations are cleaned up + * - Only then clear closing state and allow actual close to proceed + */ +static VALUE +thread_io_close_notify_all(VALUE _io) { - RUBY_ASSERT_CRITICAL_SECTION_ENTER(); + struct rb_io *io = (struct rb_io *)_io; size_t count = 0; rb_vm_t *vm = io->closing_ec->thread_ptr->vm; VALUE error = vm->special_exceptions[ruby_error_stream_closed]; struct rb_io_blocking_operation *blocking_operation; - ccan_list_for_each(&io->blocking_operations, blocking_operation, list) { + ccan_list_for_each(rb_io_blocking_operations(io), blocking_operation, list) { rb_execution_context_t *ec = blocking_operation->ec; - rb_thread_t *thread = ec->thread_ptr; - rb_threadptr_pending_interrupt_enque(thread, error); + // If the operation is in progress, we need to interrupt it: + if (ec) { + rb_thread_t *thread = ec->thread_ptr; - // This operation is slow: - rb_threadptr_interrupt(thread); + VALUE result = RUBY_Qundef; + if (thread->scheduler != Qnil) { + result = rb_fiber_scheduler_fiber_interrupt(thread->scheduler, rb_fiberptr_self(ec->fiber_ptr), error); + } + + if (result == RUBY_Qundef) { + // If the thread is not the current thread, we need to enqueue an error: + rb_threadptr_pending_interrupt_enque(thread, error); + rb_threadptr_interrupt(thread); + } + } count += 1; } - RUBY_ASSERT_CRITICAL_SECTION_LEAVE(); - - return count; + return (VALUE)count; } size_t @@ -2684,7 +2841,7 @@ rb_thread_io_close_interrupt(struct rb_io *io) } // If there are no blocking operations, we are done: - if (ccan_list_empty(&io->blocking_operations)) { + if (ccan_list_empty(rb_io_blocking_operations(io))) { return 0; } @@ -2694,8 +2851,12 @@ rb_thread_io_close_interrupt(struct rb_io *io) // This is used to ensure the correct execution context is woken up after the blocking operation is interrupted: io->wakeup_mutex = rb_mutex_new(); + rb_mutex_allow_trap(io->wakeup_mutex, 1); - return thread_io_close_notify_all(io); + // We need to use a mutex here as entering the fiber scheduler may cause a context switch: + VALUE result = rb_mutex_synchronize(io->wakeup_mutex, thread_io_close_notify_all, (VALUE)io); + + return (size_t)result; } void @@ -2709,7 +2870,7 @@ rb_thread_io_close_wait(struct rb_io* io) } rb_mutex_lock(wakeup_mutex); - while (!ccan_list_empty(&io->blocking_operations)) { + while (!ccan_list_empty(rb_io_blocking_operations(io))) { rb_mutex_sleep(wakeup_mutex, Qnil); } rb_mutex_unlock(wakeup_mutex); @@ -4336,6 +4497,8 @@ do_select(VALUE p) RUBY_VM_CHECK_INTS_BLOCKING(set->th->ec); /* may raise */ } while (wait_retryable(&result, lerrno, to, endtime) && do_select_update()); + RUBY_VM_CHECK_INTS_BLOCKING(set->th->ec); + if (result < 0) { errno = lerrno; } @@ -4435,7 +4598,7 @@ thread_io_wait(struct rb_io *io, int fd, int events, struct timeval *timeout) if (io) { blocking_operation.ec = ec; - ccan_list_add(&io->blocking_operations, &blocking_operation.list); + rb_io_blocking_operation_enter(io, &blocking_operation); } if (timeout == NULL && thread_io_wait_events(th, fd, events, NULL)) { @@ -4456,12 +4619,15 @@ thread_io_wait(struct rb_io *io, int fd, int events, struct timeval *timeout) RUBY_VM_CHECK_INTS_BLOCKING(ec); } while (wait_retryable(&result, lerrno, to, end)); + + RUBY_VM_CHECK_INTS_BLOCKING(ec); } + EC_POP_TAG(); } if (io) { - rb_io_blocking_operation_release(io, &blocking_operation); + rb_io_blocking_operation_exit(io, &blocking_operation); } if (state) { @@ -4539,7 +4705,7 @@ select_single_cleanup(VALUE ptr) struct select_args *args = (struct select_args *)ptr; if (args->blocking_operation) { - rb_io_blocking_operation_release(args->io, args->blocking_operation); + rb_io_blocking_operation_exit(args->io, args->blocking_operation); } if (args->read) rb_fd_term(args->read); @@ -4572,9 +4738,10 @@ thread_io_wait(struct rb_io *io, int fd, int events, struct timeval *timeout) if (io) { args.io = io; blocking_operation.ec = GET_EC(); - ccan_list_add(&io->blocking_operations, &blocking_operation.list); + rb_io_blocking_operation_enter(io, &blocking_operation); args.blocking_operation = &blocking_operation; - } else { + } + else { args.io = NULL; blocking_operation.ec = NULL; args.blocking_operation = NULL; @@ -4616,7 +4783,7 @@ rb_gc_set_stack_end(VALUE **stack_end_p) { VALUE stack_end; COMPILER_WARNING_PUSH -#if __has_warning("-Wdangling-pointer") +#if RBIMPL_COMPILER_IS(GCC) COMPILER_WARNING_IGNORED(-Wdangling-pointer); #endif *stack_end_p = &stack_end; @@ -4770,6 +4937,7 @@ rb_thread_atfork_internal(rb_thread_t *th, void (*atfork)(rb_thread_t *, const r thread_sched_atfork(TH_SCHED(th)); ubf_list_atfork(); + rb_signal_atfork(); // OK. Only this thread accesses: ccan_list_for_each(&vm->ractor.set, r, vmlr_node) { @@ -6062,8 +6230,15 @@ threadptr_interrupt_exec_exec(rb_thread_t *th) } rb_native_mutex_unlock(&th->interrupt_lock); + RUBY_DEBUG_LOG("task:%p", task); + if (task) { - (*task->func)(task->data); + if (task->flags & rb_interrupt_exec_flag_new_thread) { + rb_thread_create(task->func, task->data); + } + else { + (*task->func)(task->data); + } ruby_xfree(task); } else { @@ -6086,40 +6261,15 @@ threadptr_interrupt_exec_cleanup(rb_thread_t *th) rb_native_mutex_unlock(&th->interrupt_lock); } -struct interrupt_ractor_new_thread_data { - rb_interrupt_exec_func_t *func; - void *data; -}; - -static VALUE -interrupt_ractor_new_thread_func(void *data) -{ - struct interrupt_ractor_new_thread_data d = *(struct interrupt_ractor_new_thread_data *)data; - ruby_xfree(data); - - d.func(d.data); - return Qnil; -} - -static VALUE -interrupt_ractor_func(void *data) -{ - rb_thread_create(interrupt_ractor_new_thread_func, data); - return Qnil; -} - // native thread safe // func/data should be native thread safe void rb_ractor_interrupt_exec(struct rb_ractor_struct *target_r, rb_interrupt_exec_func_t *func, void *data, enum rb_interrupt_exec_flag flags) { - struct interrupt_ractor_new_thread_data *d = ALLOC(struct interrupt_ractor_new_thread_data); + RUBY_DEBUG_LOG("flags:%d", (int)flags); - d->func = func; - d->data = data; rb_thread_t *main_th = target_r->threads.main; - rb_threadptr_interrupt_exec(main_th, interrupt_ractor_func, d, flags); - - // TODO MEMO: we can create a new thread in a ractor, but not sure how to do that now. + rb_threadptr_interrupt_exec(main_th, func, data, flags | rb_interrupt_exec_flag_new_thread); } + diff --git a/thread_none.c b/thread_none.c index d535d9af4c..38686e17c1 100644 --- a/thread_none.c +++ b/thread_none.c @@ -137,6 +137,12 @@ ruby_mn_threads_params(void) { } +static void +native_thread_destroy_atfork(struct rb_native_thread *nt) +{ + /* no-op */ +} + static int native_thread_init_stack(rb_thread_t *th, void *local_in_parent_frame) { diff --git a/thread_pthread.c b/thread_pthread.c index 7811d5afbf..377e1d9f64 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -373,18 +373,38 @@ ractor_sched_dump_(const char *file, int line, rb_vm_t *vm) #define thread_sched_lock(a, b) thread_sched_lock_(a, b, __FILE__, __LINE__) #define thread_sched_unlock(a, b) thread_sched_unlock_(a, b, __FILE__, __LINE__) +static void +thread_sched_set_locked(struct rb_thread_sched *sched, rb_thread_t *th) +{ +#if VM_CHECK_MODE > 0 + VM_ASSERT(sched->lock_owner == NULL); + + sched->lock_owner = th; +#endif +} + +static void +thread_sched_set_unlocked(struct rb_thread_sched *sched, rb_thread_t *th) +{ +#if VM_CHECK_MODE > 0 + VM_ASSERT(sched->lock_owner == th); + + sched->lock_owner = NULL; +#endif +} + static void thread_sched_lock_(struct rb_thread_sched *sched, rb_thread_t *th, const char *file, int line) { rb_native_mutex_lock(&sched->lock_); #if VM_CHECK_MODE - RUBY_DEBUG_LOG2(file, line, "th:%u prev_owner:%u", rb_th_serial(th), rb_th_serial(sched->lock_owner)); - VM_ASSERT(sched->lock_owner == NULL); - sched->lock_owner = th; + RUBY_DEBUG_LOG2(file, line, "r:%d th:%u", th ? (int)rb_ractor_id(th->ractor) : -1, rb_th_serial(th)); #else RUBY_DEBUG_LOG2(file, line, "th:%u", rb_th_serial(th)); #endif + + thread_sched_set_locked(sched, th); } static void @@ -392,24 +412,11 @@ thread_sched_unlock_(struct rb_thread_sched *sched, rb_thread_t *th, const char { RUBY_DEBUG_LOG2(file, line, "th:%u", rb_th_serial(th)); -#if VM_CHECK_MODE - VM_ASSERT(sched->lock_owner == th); - sched->lock_owner = NULL; -#endif + thread_sched_set_unlocked(sched, th); rb_native_mutex_unlock(&sched->lock_); } -static void -thread_sched_set_lock_owner(struct rb_thread_sched *sched, rb_thread_t *th) -{ - RUBY_DEBUG_LOG("th:%u", rb_th_serial(th)); - -#if VM_CHECK_MODE > 0 - sched->lock_owner = th; -#endif -} - static void ASSERT_thread_sched_locked(struct rb_thread_sched *sched, rb_thread_t *th) { @@ -542,7 +549,6 @@ ractor_sched_timeslice_threads_contain_p(rb_vm_t *vm, rb_thread_t *th) } static void ractor_sched_barrier_join_signal_locked(rb_vm_t *vm); -static void ractor_sched_barrier_join_wait_locked(rb_vm_t *vm, rb_thread_t *th); // setup timeslice signals by the timer thread. static void @@ -585,11 +591,10 @@ thread_sched_setup_running_threads(struct rb_thread_sched *sched, rb_ractor_t *c } if (add_th) { - while (UNLIKELY(vm->ractor.sched.barrier_waiting)) { - RUBY_DEBUG_LOG("barrier-wait"); - - ractor_sched_barrier_join_signal_locked(vm); - ractor_sched_barrier_join_wait_locked(vm, add_th); + if (vm->ractor.sched.barrier_waiting) { + // TODO: GC barrier check? + RUBY_DEBUG_LOG("barrier_waiting"); + RUBY_VM_SET_VM_BARRIER_INTERRUPT(add_th->ec); } VM_ASSERT(!ractor_sched_running_threads_contain_p(vm, add_th)); @@ -598,7 +603,6 @@ thread_sched_setup_running_threads(struct rb_thread_sched *sched, rb_ractor_t *c ccan_list_add(&vm->ractor.sched.running_threads, &add_th->sched.node.running_threads); vm->ractor.sched.running_cnt++; sched->is_running = true; - VM_ASSERT(!vm->ractor.sched.barrier_waiting); } if (add_timeslice_th) { @@ -622,20 +626,6 @@ thread_sched_setup_running_threads(struct rb_thread_sched *sched, rb_ractor_t *c } ractor_sched_unlock(vm, cr); - if (add_th && !del_th && UNLIKELY(vm->ractor.sync.lock_owner != NULL)) { - // it can be after barrier synchronization by another ractor - rb_thread_t *lock_owner = NULL; -#if VM_CHECK_MODE - lock_owner = sched->lock_owner; -#endif - thread_sched_unlock(sched, lock_owner); - { - RB_VM_LOCK_ENTER(); - RB_VM_LOCK_LEAVE(); - } - thread_sched_lock(sched, lock_owner); - } - //RUBY_DEBUG_LOG("+:%u -:%u +ts:%u -ts:%u run:%u->%u", // rb_th_serial(add_th), rb_th_serial(del_th), // rb_th_serial(add_timeslice_th), rb_th_serial(del_timeslice_th), @@ -754,7 +744,8 @@ thread_sched_enq(struct rb_thread_sched *sched, rb_thread_t *ready_th) } } else { - VM_ASSERT(!ractor_sched_timeslice_threads_contain_p(ready_th->vm, sched->running)); + // ractor_sched lock is needed + // VM_ASSERT(!ractor_sched_timeslice_threads_contain_p(ready_th->vm, sched->running)); } ccan_list_add_tail(&sched->readyq, &ready_th->sched.node.readyq); @@ -850,16 +841,16 @@ thread_sched_wait_running_turn(struct rb_thread_sched *sched, rb_thread_t *th, b if (th_has_dedicated_nt(th)) { RUBY_DEBUG_LOG("(nt) sleep th:%u running:%u", rb_th_serial(th), rb_th_serial(sched->running)); - thread_sched_set_lock_owner(sched, NULL); + thread_sched_set_unlocked(sched, th); { RUBY_DEBUG_LOG("nt:%d cond:%p", th->nt->serial, &th->nt->cond.readyq); rb_native_cond_wait(&th->nt->cond.readyq, &sched->lock_); } - thread_sched_set_lock_owner(sched, th); + thread_sched_set_locked(sched, th); RUBY_DEBUG_LOG("(nt) wakeup %s", sched->running == th ? "success" : "failed"); if (th == sched->running) { - rb_ractor_thread_switch(th->ractor, th); + rb_ractor_thread_switch(th->ractor, th, false); } } else { @@ -871,12 +862,12 @@ thread_sched_wait_running_turn(struct rb_thread_sched *sched, rb_thread_t *th, b RUBY_DEBUG_LOG("th:%u->%u (direct)", rb_th_serial(th), rb_th_serial(next_th)); - thread_sched_set_lock_owner(sched, NULL); + thread_sched_set_unlocked(sched, th); { rb_ractor_set_current_ec(th->ractor, NULL); thread_sched_switch(th, next_th); } - thread_sched_set_lock_owner(sched, th); + thread_sched_set_locked(sched, th); } else { // search another ready ractor @@ -885,12 +876,12 @@ thread_sched_wait_running_turn(struct rb_thread_sched *sched, rb_thread_t *th, b RUBY_DEBUG_LOG("th:%u->%u (ractor scheduling)", rb_th_serial(th), rb_th_serial(next_th)); - thread_sched_set_lock_owner(sched, NULL); + thread_sched_set_unlocked(sched, th); { rb_ractor_set_current_ec(th->ractor, NULL); coroutine_transfer0(th->sched.context, nt->nt_context, false); } - thread_sched_set_lock_owner(sched, th); + thread_sched_set_locked(sched, th); } VM_ASSERT(rb_current_ec_noinline() == th->ec); @@ -1042,15 +1033,45 @@ thread_sched_to_waiting(struct rb_thread_sched *sched, rb_thread_t *th) } // mini utility func -static void -setup_ubf(rb_thread_t *th, rb_unblock_function_t *func, void *arg) +// return true if any there are any interrupts +static bool +ubf_set(rb_thread_t *th, rb_unblock_function_t *func, void *arg) { + VM_ASSERT(func != NULL); + + retry: + if (RUBY_VM_INTERRUPTED(th->ec)) { + RUBY_DEBUG_LOG("interrupted:0x%x", th->ec->interrupt_flag); + return true; + } + rb_native_mutex_lock(&th->interrupt_lock); { + if (!th->ec->raised_flag && RUBY_VM_INTERRUPTED(th->ec)) { + rb_native_mutex_unlock(&th->interrupt_lock); + goto retry; + } + + VM_ASSERT(th->unblock.func == NULL); th->unblock.func = func; th->unblock.arg = arg; } rb_native_mutex_unlock(&th->interrupt_lock); + + return false; +} + +static void +ubf_clear(rb_thread_t *th) +{ + if (th->unblock.func) { + rb_native_mutex_lock(&th->interrupt_lock); + { + th->unblock.func = NULL; + th->unblock.arg = NULL; + } + rb_native_mutex_unlock(&th->interrupt_lock); + } } static void @@ -1086,7 +1107,10 @@ thread_sched_to_waiting_until_wakeup(struct rb_thread_sched *sched, rb_thread_t RUBY_DEBUG_LOG("th:%u", rb_th_serial(th)); RB_VM_SAVE_MACHINE_CONTEXT(th); - setup_ubf(th, ubf_waiting, (void *)th); + + if (ubf_set(th, ubf_waiting, (void *)th)) { + return; + } RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th); @@ -1103,7 +1127,7 @@ thread_sched_to_waiting_until_wakeup(struct rb_thread_sched *sched, rb_thread_t } thread_sched_unlock(sched, th); - setup_ubf(th, NULL, NULL); + ubf_clear(th); } // run another thread in the ready queue. @@ -1312,66 +1336,59 @@ void rb_ractor_unlock_self(rb_ractor_t *r); // The current thread for a ractor is put to "sleep" (descheduled in the STOPPED_FOREVER state) waiting for // a ractor action to wake it up. See docs for `ractor_sched_sleep_with_cleanup` for more info. void -rb_ractor_sched_sleep(rb_execution_context_t *ec, rb_ractor_t *cr, rb_unblock_function_t *ubf_schedule_ractor_th) +rb_ractor_sched_wait(rb_execution_context_t *ec, rb_ractor_t *cr, rb_unblock_function_t *ubf, void *ubf_arg) { // ractor lock of cr is acquired - // r is sleeping status + + RUBY_DEBUG_LOG("start%s", ""); + rb_thread_t * volatile th = rb_ec_thread_ptr(ec); struct rb_thread_sched *sched = TH_SCHED(th); - struct ccan_list_node *waitn = &th->ractor_waiting.waiting_node; - VM_ASSERT(waitn->next == waitn->prev && waitn->next == waitn); // it should be unlinked - ccan_list_add(&cr->sync.wait.waiting_threads, waitn); - setup_ubf(th, ubf_schedule_ractor_th, (void *)ec); + if (ubf_set(th, ubf, ubf_arg)) { + // interrupted + return; + } thread_sched_lock(sched, th); { + // setup sleep + bool can_direct_transfer = !th_has_dedicated_nt(th); + RB_VM_SAVE_MACHINE_CONTEXT(th); + th->status = THREAD_STOPPED_FOREVER; + RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th); + thread_sched_wakeup_next_thread(sched, th, can_direct_transfer); + rb_ractor_unlock_self(cr); { - if (RUBY_VM_INTERRUPTED(th->ec)) { - RUBY_DEBUG_LOG("interrupted"); - } - else if (th->ractor_waiting.wakeup_status != wakeup_none) { - RUBY_DEBUG_LOG("awaken:%d", (int)th->ractor_waiting.wakeup_status); - } - else { - // sleep - RB_VM_SAVE_MACHINE_CONTEXT(th); - th->status = THREAD_STOPPED_FOREVER; - - RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th); - - bool can_direct_transfer = !th_has_dedicated_nt(th); - thread_sched_wakeup_next_thread(sched, th, can_direct_transfer); - thread_sched_wait_running_turn(sched, th, can_direct_transfer); - th->status = THREAD_RUNNABLE; - // wakeup - } + // sleep + thread_sched_wait_running_turn(sched, th, can_direct_transfer); + th->status = THREAD_RUNNABLE; } + rb_ractor_lock_self(cr); } thread_sched_unlock(sched, th); - setup_ubf(th, NULL, NULL); + ubf_clear(th); - rb_ractor_lock_self(cr); - ccan_list_del_init(waitn); + RUBY_DEBUG_LOG("end%s", ""); } void -rb_ractor_sched_wakeup(rb_ractor_t *r, rb_thread_t *th) +rb_ractor_sched_wakeup(rb_ractor_t *r, rb_thread_t *r_th) { - // ractor lock of r is acquired - struct rb_thread_sched *sched = TH_SCHED(th); + // ractor lock of r is NOT acquired + struct rb_thread_sched *sched = TH_SCHED(r_th); - VM_ASSERT(th->ractor_waiting.wakeup_status != 0); + RUBY_DEBUG_LOG("r:%u th:%d", (unsigned int)rb_ractor_id(r), r_th->serial); - thread_sched_lock(sched, th); + thread_sched_lock(sched, r_th); { - if (th->status == THREAD_STOPPED_FOREVER) { - thread_sched_to_ready_common(sched, th, true, false); + if (r_th->status == THREAD_STOPPED_FOREVER) { + thread_sched_to_ready_common(sched, r_th, true, false); } } - thread_sched_unlock(sched, th); + thread_sched_unlock(sched, r_th); } static bool @@ -1379,6 +1396,7 @@ ractor_sched_barrier_completed_p(rb_vm_t *vm) { RUBY_DEBUG_LOG("run:%u wait:%u", vm->ractor.sched.running_cnt, vm->ractor.sched.barrier_waiting_cnt); VM_ASSERT(vm->ractor.sched.running_cnt - 1 >= vm->ractor.sched.barrier_waiting_cnt); + return (vm->ractor.sched.running_cnt - vm->ractor.sched.barrier_waiting_cnt) == 1; } @@ -1389,6 +1407,8 @@ rb_ractor_sched_barrier_start(rb_vm_t *vm, rb_ractor_t *cr) VM_ASSERT(vm->ractor.sync.lock_owner == cr); // VM is locked VM_ASSERT(!vm->ractor.sched.barrier_waiting); VM_ASSERT(vm->ractor.sched.barrier_waiting_cnt == 0); + VM_ASSERT(vm->ractor.sched.barrier_ractor == NULL); + VM_ASSERT(vm->ractor.sched.barrier_lock_rec == 0); RUBY_DEBUG_LOG("start serial:%u", vm->ractor.sched.barrier_serial); @@ -1397,46 +1417,60 @@ rb_ractor_sched_barrier_start(rb_vm_t *vm, rb_ractor_t *cr) ractor_sched_lock(vm, cr); { vm->ractor.sched.barrier_waiting = true; + vm->ractor.sched.barrier_ractor = cr; + vm->ractor.sched.barrier_lock_rec = vm->ractor.sync.lock_rec; // release VM lock lock_rec = vm->ractor.sync.lock_rec; vm->ractor.sync.lock_rec = 0; vm->ractor.sync.lock_owner = NULL; rb_native_mutex_unlock(&vm->ractor.sync.lock); - { - // interrupts all running threads - rb_thread_t *ith; - ccan_list_for_each(&vm->ractor.sched.running_threads, ith, sched.node.running_threads) { - if (ith->ractor != cr) { - RUBY_DEBUG_LOG("barrier int:%u", rb_th_serial(ith)); - RUBY_VM_SET_VM_BARRIER_INTERRUPT(ith->ec); - } - } - // wait for other ractors - while (!ractor_sched_barrier_completed_p(vm)) { - ractor_sched_set_unlocked(vm, cr); - rb_native_cond_wait(&vm->ractor.sched.barrier_complete_cond, &vm->ractor.sched.lock); - ractor_sched_set_locked(vm, cr); + // interrupts all running threads + rb_thread_t *ith; + ccan_list_for_each(&vm->ractor.sched.running_threads, ith, sched.node.running_threads) { + if (ith->ractor != cr) { + RUBY_DEBUG_LOG("barrier request to th:%u", rb_th_serial(ith)); + RUBY_VM_SET_VM_BARRIER_INTERRUPT(ith->ec); } } - } - ractor_sched_unlock(vm, cr); - // acquire VM lock - rb_native_mutex_lock(&vm->ractor.sync.lock); - vm->ractor.sync.lock_rec = lock_rec; - vm->ractor.sync.lock_owner = cr; + // wait for other ractors + while (!ractor_sched_barrier_completed_p(vm)) { + ractor_sched_set_unlocked(vm, cr); + rb_native_cond_wait(&vm->ractor.sched.barrier_complete_cond, &vm->ractor.sched.lock); + ractor_sched_set_locked(vm, cr); + } - RUBY_DEBUG_LOG("completed seirial:%u", vm->ractor.sched.barrier_serial); + RUBY_DEBUG_LOG("completed seirial:%u", vm->ractor.sched.barrier_serial); - ractor_sched_lock(vm, cr); - { - vm->ractor.sched.barrier_waiting = false; + // no other ractors are there vm->ractor.sched.barrier_serial++; vm->ractor.sched.barrier_waiting_cnt = 0; rb_native_cond_broadcast(&vm->ractor.sched.barrier_release_cond); + + // acquire VM lock + rb_native_mutex_lock(&vm->ractor.sync.lock); + vm->ractor.sync.lock_rec = lock_rec; + vm->ractor.sync.lock_owner = cr; } + + // do not release ractor_sched_lock and threre is no newly added (resumed) thread + // thread_sched_setup_running_threads +} + +// called from vm_lock_leave if the vm_lock used for barrierred +void +rb_ractor_sched_barrier_end(rb_vm_t *vm, rb_ractor_t *cr) +{ + RUBY_DEBUG_LOG("serial:%u", (unsigned int)vm->ractor.sched.barrier_serial - 1); + VM_ASSERT(vm->ractor.sched.barrier_waiting); + VM_ASSERT(vm->ractor.sched.barrier_ractor); + VM_ASSERT(vm->ractor.sched.barrier_lock_rec > 0); + + vm->ractor.sched.barrier_waiting = false; + vm->ractor.sched.barrier_ractor = NULL; + vm->ractor.sched.barrier_lock_rec = 0; ractor_sched_unlock(vm, cr); } @@ -1782,6 +1816,27 @@ native_thread_assign(struct rb_native_thread *nt, rb_thread_t *th) th->nt = nt; } +static void +native_thread_destroy_atfork(struct rb_native_thread *nt) +{ + if (nt) { + /* We can't call rb_native_cond_destroy here because according to the + * specs of pthread_cond_destroy: + * + * Attempting to destroy a condition variable upon which other threads + * are currently blocked results in undefined behavior. + * + * Specifically, glibc's pthread_cond_destroy waits on all the other + * listeners. Since after forking all the threads are dead, the condition + * variable's listeners will never wake up, so it will hang forever. + */ + + RB_ALTSTACK_FREE(nt->altstack); + ruby_xfree(nt->nt_context); + ruby_xfree(nt); + } +} + static void native_thread_destroy(struct rb_native_thread *nt) { @@ -1792,9 +1847,7 @@ native_thread_destroy(struct rb_native_thread *nt) rb_native_cond_destroy(&nt->cond.intr); } - RB_ALTSTACK_FREE(nt->altstack); - ruby_xfree(nt->nt_context); - ruby_xfree(nt); + native_thread_destroy_atfork(nt); } } @@ -2283,11 +2336,9 @@ rb_threadptr_remove(rb_thread_t *th) rb_vm_t *vm = th->vm; th->sched.finished = false; - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { ccan_list_add(&vm->ractor.sched.zombie_threads, &th->sched.node.zombie_threads); } - RB_VM_LOCK_LEAVE(); } #endif } @@ -2574,7 +2625,7 @@ rb_thread_wakeup_timer_thread(int sig) timer_thread_wakeup_force(); // interrupt main thread if main thread is available - if (system_working) { + if (RUBY_ATOMIC_LOAD(system_working)) { rb_vm_t *vm = GET_VM(); rb_thread_t *main_th = vm->ractor.main_thread; @@ -3005,12 +3056,12 @@ timer_thread_func(void *ptr) RUBY_DEBUG_LOG("started%s", ""); - while (system_working) { + while (RUBY_ATOMIC_LOAD(system_working)) { timer_thread_check_signal(vm); timer_thread_check_timeout(vm); ubf_wakeup_all_threads(); - RUBY_DEBUG_LOG("system_working:%d", system_working); + RUBY_DEBUG_LOG("system_working:%d", RUBY_ATOMIC_LOAD(system_working)); timer_thread_polling(vm); } @@ -3124,18 +3175,16 @@ rb_thread_create_timer_thread(void) static int native_stop_timer_thread(void) { - int stopped; - stopped = --system_working <= 0; + RUBY_ATOMIC_SET(system_working, 0); - if (stopped) { - RUBY_DEBUG_LOG("wakeup send %d", timer_th.comm_fds[1]); - timer_thread_wakeup_force(); - RUBY_DEBUG_LOG("wakeup sent"); - pthread_join(timer_th.pthread_id, NULL); - } + RUBY_DEBUG_LOG("wakeup send %d", timer_th.comm_fds[1]); + timer_thread_wakeup_force(); + RUBY_DEBUG_LOG("wakeup sent"); + pthread_join(timer_th.pthread_id, NULL); if (TT_DEBUG) fprintf(stderr, "stop timer thread\n"); - return stopped; + + return 1; } static void diff --git a/thread_pthread.h b/thread_pthread.h index b632668a2a..22e5f3652b 100644 --- a/thread_pthread.h +++ b/thread_pthread.h @@ -164,4 +164,8 @@ native_tls_set(native_tls_key_t key, void *ptr) RUBY_EXTERN native_tls_key_t ruby_current_ec_key; #endif +struct rb_ractor_struct; +void rb_ractor_sched_wait(struct rb_execution_context_struct *ec, struct rb_ractor_struct *cr, rb_unblock_function_t *ubf, void *ptr); +void rb_ractor_sched_wakeup(struct rb_ractor_struct *r, struct rb_thread_struct *th); + #endif /* RUBY_THREAD_PTHREAD_H */ diff --git a/thread_pthread_mn.c b/thread_pthread_mn.c index cc0dae3b70..4a671cf3a1 100644 --- a/thread_pthread_mn.c +++ b/thread_pthread_mn.c @@ -72,7 +72,7 @@ thread_sched_wait_events(struct rb_thread_sched *sched, rb_thread_t *th, int fd, RUBY_DEBUG_LOG("wait fd:%d", fd); RB_VM_SAVE_MACHINE_CONTEXT(th); - setup_ubf(th, ubf_event_waiting, (void *)th); + ubf_set(th, ubf_event_waiting, (void *)th); RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th); @@ -102,7 +102,7 @@ thread_sched_wait_events(struct rb_thread_sched *sched, rb_thread_t *th, int fd, timer_thread_cancel_waiting(th); } - setup_ubf(th, NULL, NULL); // TODO: maybe it is already NULL? + ubf_clear(th); // TODO: maybe it is already NULL? th->status = THREAD_RUNNABLE; } @@ -450,7 +450,7 @@ co_start(struct coroutine_context *from, struct coroutine_context *self) // RUBY_DEBUG_LOG("th:%u", rb_th_serial(th)); - thread_sched_set_lock_owner(sched, th); + thread_sched_set_locked(sched, th); thread_sched_add_running_thread(TH_SCHED(th), th); thread_sched_unlock(sched, th); { @@ -475,13 +475,11 @@ co_start(struct coroutine_context *from, struct coroutine_context *self) coroutine_transfer0(self, nt->nt_context, true); } else { - rb_vm_t *vm = th->vm; - bool has_ready_ractor = vm->ractor.sched.grq_cnt > 0; // at least this ractor is not queued rb_thread_t *next_th = sched->running; - if (!has_ready_ractor && next_th && !next_th->nt) { + if (next_th && !next_th->nt) { // switch to the next thread - thread_sched_set_lock_owner(sched, NULL); + thread_sched_set_unlocked(sched, NULL); th->sched.finished = true; thread_sched_switch0(th->sched.context, next_th, nt, true); } diff --git a/thread_win32.c b/thread_win32.c index f9c268b117..576f617e8d 100644 --- a/thread_win32.c +++ b/thread_win32.c @@ -617,6 +617,12 @@ native_thread_init_stack(rb_thread_t *th, void *local_in_parent_frame) th->ec->machine.stack_maxsize = size - space; } +static void +native_thread_destroy_atfork(struct rb_native_thread *nt) +{ + /* no-op */ +} + #ifndef InterlockedExchangePointer #define InterlockedExchangePointer(t, v) \ (void *)InterlockedExchange((long *)(t), (long)(v)) @@ -798,14 +804,14 @@ rb_thread_create_timer_thread(void) static int native_stop_timer_thread(void) { - int stopped = --system_working <= 0; - if (stopped) { - SetEvent(timer_thread.lock); - native_thread_join(timer_thread.id); - CloseHandle(timer_thread.lock); - timer_thread.lock = 0; - } - return stopped; + RUBY_ATOMIC_SET(system_working, 0); + + SetEvent(timer_thread.lock); + native_thread_join(timer_thread.id); + CloseHandle(timer_thread.lock); + timer_thread.lock = 0; + + return 1; } static void @@ -922,6 +928,7 @@ vm_barrier_finish_p(rb_vm_t *vm) vm->ractor.blocking_cnt); VM_ASSERT(vm->ractor.blocking_cnt <= vm->ractor.cnt); + return vm->ractor.blocking_cnt == vm->ractor.cnt; } @@ -947,7 +954,7 @@ rb_ractor_sched_barrier_start(rb_vm_t *vm, rb_ractor_t *cr) // wait while (!vm_barrier_finish_p(vm)) { - rb_vm_cond_wait(vm, &vm->ractor.sync.barrier_cond); + rb_vm_cond_wait(vm, &vm->ractor.sync.barrier_complete_cond); } RUBY_DEBUG_LOG("cnt:%u barrier success", vm->ractor.sync.barrier_cnt); @@ -957,9 +964,7 @@ rb_ractor_sched_barrier_start(rb_vm_t *vm, rb_ractor_t *cr) vm->ractor.sync.barrier_waiting = false; vm->ractor.sync.barrier_cnt++; - ccan_list_for_each(&vm->ractor.set, r, vmlr_node) { - rb_native_cond_signal(&r->barrier_wait_cond); - } + rb_native_cond_broadcast(&vm->ractor.sync.barrier_release_cond); } void @@ -983,7 +988,7 @@ rb_ractor_sched_barrier_join(rb_vm_t *vm, rb_ractor_t *cr) if (vm_barrier_finish_p(vm)) { RUBY_DEBUG_LOG("wakeup barrier owner"); - rb_native_cond_signal(&vm->ractor.sync.barrier_cond); + rb_native_cond_signal(&vm->ractor.sync.barrier_complete_cond); } else { RUBY_DEBUG_LOG("wait for barrier finish"); @@ -991,10 +996,7 @@ rb_ractor_sched_barrier_join(rb_vm_t *vm, rb_ractor_t *cr) // wait for restart while (barrier_cnt == vm->ractor.sync.barrier_cnt) { - vm->ractor.sync.lock_owner = NULL; - rb_native_cond_wait(&cr->barrier_wait_cond, &vm->ractor.sync.lock); - VM_ASSERT(vm->ractor.sync.lock_owner == NULL); - vm->ractor.sync.lock_owner = cr; + rb_vm_cond_wait(vm, &vm->ractor.sync.barrier_release_cond); } RUBY_DEBUG_LOG("barrier is released. Acquire vm_lock"); diff --git a/time.c b/time.c index 1b02cf4259..1eb8f8da9c 100644 --- a/time.c +++ b/time.c @@ -249,6 +249,7 @@ divmodv(VALUE n, VALUE d, VALUE *q, VALUE *r) # define FIXWV2WINT(w) FIX2LONG(WIDEVAL_GET(w)) #endif +#define SIZEOF_WIDEINT SIZEOF_INT64_T #define POSFIXWVABLE(wi) ((wi) < FIXWV_MAX+1) #define NEGFIXWVABLE(wi) ((wi) >= FIXWV_MIN) #define FIXWV_P(w) FIXWINT_P(WIDEVAL_GET(w)) @@ -1891,7 +1892,7 @@ time_mark(void *ptr) { struct time_object *tobj = ptr; if (!FIXWV_P(tobj->timew)) { - rb_gc_mark_movable(WIDEVAL_GET(tobj->timew)); + rb_gc_mark_movable(w2v(tobj->timew)); } rb_gc_mark_movable(tobj->vtm.year); rb_gc_mark_movable(tobj->vtm.subsecx); @@ -1904,7 +1905,7 @@ time_compact(void *ptr) { struct time_object *tobj = ptr; if (!FIXWV_P(tobj->timew)) { - WIDEVAL_GET(tobj->timew) = rb_gc_location(WIDEVAL_GET(tobj->timew)); + WIDEVAL_GET(tobj->timew) = WIDEVAL_WRAP(rb_gc_location(w2v(tobj->timew))); } tobj->vtm.year = rb_gc_location(tobj->vtm.year); @@ -1968,11 +1969,11 @@ time_modify(VALUE time) } static wideval_t -timenano2timew(time_t sec, long nsec) +timenano2timew(wideint_t sec, long nsec) { wideval_t timew; - timew = rb_time_magnify(TIMET2WV(sec)); + timew = rb_time_magnify(WINT2WV(sec)); if (nsec) timew = wadd(timew, wmulquoll(WINT2WV(nsec), TIME_SCALE, 1000000000)); return timew; @@ -2306,14 +2307,14 @@ utc_offset_arg(VALUE arg) static void zone_set_offset(VALUE zone, struct time_object *tobj, - wideval_t tlocal, wideval_t tutc) + wideval_t tlocal, wideval_t tutc, VALUE time) { /* tlocal and tutc must be unmagnified and in seconds */ wideval_t w = wsub(tlocal, tutc); VALUE off = w2v(w); validate_utc_offset(off); - tobj->vtm.utc_offset = off; - tobj->vtm.zone = zone; + RB_OBJ_WRITE(time, &tobj->vtm.utc_offset, off); + RB_OBJ_WRITE(time, &tobj->vtm.zone, zone); TZMODE_SET_LOCALTIME(tobj); } @@ -2428,7 +2429,7 @@ zone_timelocal(VALUE zone, VALUE time) if (UNDEF_P(utc)) return 0; s = extract_time(utc); - zone_set_offset(zone, tobj, t, s); + zone_set_offset(zone, tobj, t, s, time); s = rb_time_magnify(s); if (tobj->vtm.subsecx != INT2FIX(0)) { s = wadd(s, v2w(tobj->vtm.subsecx)); @@ -2457,7 +2458,7 @@ zone_localtime(VALUE zone, VALUE time) s = extract_vtm(local, time, tobj, subsecx); tobj->vtm.tm_got = 1; - zone_set_offset(zone, tobj, s, t); + zone_set_offset(zone, tobj, s, t, time); zone_set_dst(zone, tobj, tm); RB_GC_GUARD(time); @@ -2747,15 +2748,15 @@ only_year: } static void -subsec_normalize(time_t *secp, long *subsecp, const long maxsubsec) +subsec_normalize(wideint_t *secp, long *subsecp, const long maxsubsec) { - time_t sec = *secp; + wideint_t sec = *secp; long subsec = *subsecp; long sec2; if (UNLIKELY(subsec >= maxsubsec)) { /* subsec positive overflow */ sec2 = subsec / maxsubsec; - if (TIMET_MAX - sec2 < sec) { + if (WIDEINT_MAX - sec2 < sec) { rb_raise(rb_eRangeError, "out of Time range"); } subsec -= sec2 * maxsubsec; @@ -2763,16 +2764,12 @@ subsec_normalize(time_t *secp, long *subsecp, const long maxsubsec) } else if (UNLIKELY(subsec < 0)) { /* subsec negative overflow */ sec2 = NDIV(subsec, maxsubsec); /* negative div */ - if (sec < TIMET_MIN - sec2) { + if (sec < WIDEINT_MIN - sec2) { rb_raise(rb_eRangeError, "out of Time range"); } subsec -= sec2 * maxsubsec; sec += sec2; } -#ifndef NEGATIVE_TIME_T - if (sec < 0) - rb_raise(rb_eArgError, "time must be positive"); -#endif *secp = sec; *subsecp = subsec; } @@ -2780,13 +2777,6 @@ subsec_normalize(time_t *secp, long *subsecp, const long maxsubsec) #define time_usec_normalize(secp, usecp) subsec_normalize(secp, usecp, 1000000) #define time_nsec_normalize(secp, nsecp) subsec_normalize(secp, nsecp, 1000000000) -static wideval_t -nsec2timew(time_t sec, long nsec) -{ - time_nsec_normalize(&sec, &nsec); - return timenano2timew(sec, nsec); -} - static VALUE time_new_timew(VALUE klass, wideval_t timew) { @@ -2800,25 +2790,39 @@ time_new_timew(VALUE klass, wideval_t timew) return time; } +static wideint_t +TIMETtoWIDEINT(time_t t) +{ +#if SIZEOF_TIME_T * CHAR_BIT - (SIGNEDNESS_OF_TIME_T < 0) > \ + SIZEOF_WIDEINT * CHAR_BIT - 1 + /* compare in bit size without sign bit */ + if (t > WIDEINT_MAX) rb_raise(rb_eArgError, "out of Time range"); +#endif + return (wideint_t)t; +} + VALUE rb_time_new(time_t sec, long usec) { - time_usec_normalize(&sec, &usec); - return time_new_timew(rb_cTime, timenano2timew(sec, usec * 1000)); + wideint_t isec = TIMETtoWIDEINT(sec); + time_usec_normalize(&isec, &usec); + return time_new_timew(rb_cTime, timenano2timew(isec, usec * 1000)); } /* returns localtime time object */ VALUE rb_time_nano_new(time_t sec, long nsec) { - return time_new_timew(rb_cTime, nsec2timew(sec, nsec)); + wideint_t isec = TIMETtoWIDEINT(sec); + time_nsec_normalize(&isec, &nsec); + return time_new_timew(rb_cTime, timenano2timew(isec, nsec)); } VALUE rb_time_timespec_new(const struct timespec *ts, int offset) { struct time_object *tobj; - VALUE time = time_new_timew(rb_cTime, nsec2timew(ts->tv_sec, ts->tv_nsec)); + VALUE time = rb_time_nano_new(ts->tv_sec, ts->tv_nsec); if (-86400 < offset && offset < 86400) { /* fixoff */ GetTimeval(time, tobj); @@ -4084,7 +4088,9 @@ time_init_copy(VALUE copy, VALUE time) if (!OBJ_INIT_COPY(copy, time)) return copy; GetTimeval(time, tobj); GetNewTimeval(copy, tcopy); - MEMCPY(tcopy, tobj, struct time_object, 1); + + time_set_timew(copy, tcopy, tobj->timew); + time_set_vtm(copy, tcopy, tobj->vtm); return copy; } @@ -5748,7 +5754,7 @@ end_submicro: ; } if (!NIL_P(zone)) { zone = mload_zone(time, zone); - tobj->vtm.zone = zone; + RB_OBJ_WRITE(time, &tobj->vtm.zone, zone); zone_localtime(zone, time); } @@ -5794,8 +5800,10 @@ tm_from_time(VALUE klass, VALUE time) tm = time_s_alloc(klass); ttm = RTYPEDDATA_GET_DATA(tm); v = &vtm; - GMTIMEW(ttm->timew = tobj->timew, v); - ttm->timew = wsub(ttm->timew, v->subsecx); + + WIDEVALUE timew = tobj->timew; + GMTIMEW(timew, v); + time_set_timew(tm, ttm, wsub(timew, v->subsecx)); v->subsecx = INT2FIX(0); v->zone = Qnil; time_set_vtm(tm, ttm, *v); diff --git a/tool/auto-style.rb b/tool/auto-style.rb old mode 100644 new mode 100755 index d2b007bd51..b673e3d177 --- a/tool/auto-style.rb +++ b/tool/auto-style.rb @@ -11,12 +11,14 @@ class Git def initialize(oldrev, newrev, branch = nil) @oldrev = oldrev - @newrev = newrev + @newrev = newrev.empty? ? 'HEAD' : newrev @branch = branch # GitHub may not fetch github.event.pull_request.base.sha at checkout - git('fetch', '--depth=1', 'origin', @oldrev) - git('fetch', '--depth=100', 'origin', @newrev) + git('log', '--format=%H', '-1', @oldrev, out: IO::NULL, err: [:child, :out]) or + git('fetch', '--depth=1', 'origin', @oldrev) + git('log', '--format=%H', '-1', "#@newrev~99", out: IO::NULL, err: [:child, :out]) or + git('fetch', '--depth=100', 'origin', @newrev) with_clean_env do @revs = {} @@ -66,12 +68,14 @@ class Git private - def git(*args) + def git(*args, **opts) cmd = ['git', *args].shelljoin puts "+ #{cmd}" - unless with_clean_env { system(cmd) } + ret = with_clean_env { system('git', *args, **opts) } + unless ret or opts[:err] abort "Failed to run: #{cmd}" end + ret end def with_clean_env @@ -173,6 +177,10 @@ IGNORED_FILES = [ %r{\Asample/trick[^/]*/}, ] +DIFFERENT_STYLE_FILES = %w[ + addr2line.c io_buffer.c prism*.c scheduler.c +] + oldrev, newrev, pushref = ARGV unless dry_run = pushref.empty? branch = IO.popen(['git', 'rev-parse', '--symbolic', '--abbrev-ref', pushref], &:read).strip @@ -183,7 +191,7 @@ updated_files = git.updated_paths files = updated_files.select {|l| /^\d/ !~ l and /\.bat\z/ !~ l and (/\A(?:config|[Mm]akefile|GNUmakefile|README)/ =~ File.basename(l) or - /\A\z|\.(?:[chsy]|\d+|e?rb|tmpl|bas[eh]|z?sh|in|ma?k|def|src|trans|rdoc|ja|en|el|sed|awk|p[ly]|scm|mspec|html|)\z/ =~ File.extname(l)) + /\A\z|\.(?:[chsy]|\d+|e?rb|tmpl|bas[eh]|z?sh|in|ma?k|def|src|trans|rdoc|ja|en|el|sed|awk|p[ly]|scm|mspec|html|rs)\z/ =~ File.extname(l)) } files.select! {|n| File.file?(n) } files.reject! do |f| @@ -194,7 +202,7 @@ if files.empty? exit end -trailing = eofnewline = expandtab = false +trailing = eofnewline = expandtab = indent = false edited_files = files.select do |f| src = File.binread(f) rescue next @@ -202,6 +210,8 @@ edited_files = files.select do |f| trailing0 = false expandtab0 = false + indent0 = false + src.gsub!(/^.*$/).with_index do |line, lineno| trailing = trailing0 = true if line.sub!(/[ \t]+$/, '') line @@ -225,7 +235,15 @@ edited_files = files.select do |f| end end - if trailing0 or eofnewline0 or expandtab0 + if File.fnmatch?("*.[ch]", f, File::FNM_PATHNAME) && + !DIFFERENT_STYLE_FILES.any? {|pat| File.fnmatch?(pat, f, File::FNM_PATHNAME)} + indent0 = true if src.gsub!(/^\w+\([^\n]*?\)\K[ \t]*(?=\{( *\\)?$)/, '\1' "\n") + indent0 = true if src.gsub!(/^([ \t]*)\}\K[ \t]*(?=else\b.*?( *\\)?$)/, '\2' "\n" '\1') + indent0 = true if src.gsub!(/^[ \t]*\}\n\K\n+(?=[ \t]*else\b)/, '') + indent ||= indent0 + end + + if trailing0 or eofnewline0 or expandtab0 or indent0 File.binwrite(f, src) true end @@ -236,6 +254,7 @@ else msg = [('remove trailing spaces' if trailing), ('append newline at EOF' if eofnewline), ('expand tabs' if expandtab), + ('adjust indents' if indent), ].compact message = "* #{msg.join(', ')}. [ci skip]" if expandtab diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index cbdb17a661..182e346f06 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -31,7 +31,7 @@ GEM power_assert (2.0.5) racc (1.8.1) racc (1.8.1-java) - rake (13.2.1) + rake (13.3.0) rake-compiler-dock (1.9.1) rb_sys (0.9.111) rake-compiler-dock (= 1.9.1) @@ -104,7 +104,7 @@ CHECKSUMS power_assert (2.0.5) sha256=63b511b85bb8ea57336d25156864498644f5bbf028699ceda27949e0125bc323 racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f racc (1.8.1-java) sha256=54f2e6d1e1b91c154013277d986f52a90e5ececbe91465d29172e49342732b98 - rake (13.2.1) sha256=46cb38dae65d7d74b6020a4ac9d48afed8eb8149c040eccf0523bec91907059d + rake (13.3.0) sha256=96f5092d786ff412c62fde76f793cc0541bd84d2eb579caa529aa8a059934493 rake-compiler-dock (1.9.1) sha256=e73720a29aba9c114728ce39cc0d8eef69ba61d88e7978c57bac171724cd4d53 rb_sys (0.9.111) sha256=65822fd8d57c248cd893db0efe01bc6edc15fcbea3ba6666091e35430c1cbaf0 rexml (3.4.1) sha256=c74527a9a0a04b4ec31dbe0dc4ed6004b960af943d8db42e539edde3a871abca @@ -118,4 +118,4 @@ CHECKSUMS turbo_tests (2.2.5) sha256=3fa31497d12976d11ccc298add29107b92bda94a90d8a0a5783f06f05102509f BUNDLED WITH - 2.7.0.dev + 2.8.0.dev diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index 1461ad5072..aa2fedcce8 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -35,7 +35,7 @@ GEM racc (1.8.1) racc (1.8.1-java) rainbow (3.1.1) - rake (13.2.1) + rake (13.3.0) rake-compiler (1.2.9) rake rake-compiler-dock (1.9.1) @@ -126,7 +126,7 @@ CHECKSUMS racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f racc (1.8.1-java) sha256=54f2e6d1e1b91c154013277d986f52a90e5ececbe91465d29172e49342732b98 rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a - rake (13.2.1) sha256=46cb38dae65d7d74b6020a4ac9d48afed8eb8149c040eccf0523bec91907059d + rake (13.3.0) sha256=96f5092d786ff412c62fde76f793cc0541bd84d2eb579caa529aa8a059934493 rake-compiler (1.2.9) sha256=5a3213a5dda977dfdf73e28beed6f4cd6a2cc86ac640bb662728eb7049a23607 rake-compiler-dock (1.9.1) sha256=e73720a29aba9c114728ce39cc0d8eef69ba61d88e7978c57bac171724cd4d53 rb_sys (0.9.111) sha256=65822fd8d57c248cd893db0efe01bc6edc15fcbea3ba6666091e35430c1cbaf0 @@ -147,4 +147,4 @@ CHECKSUMS unicode-emoji (4.0.4) sha256=2c2c4ef7f353e5809497126285a50b23056cc6e61b64433764a35eff6c36532a BUNDLED WITH - 2.7.0.dev + 2.8.0.dev diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index e0fc70a6bb..655b5c7296 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -35,7 +35,7 @@ GEM racc (1.8.1) racc (1.8.1-java) rainbow (3.1.1) - rake (13.2.1) + rake (13.3.0) rake-compiler (1.2.9) rake rake-compiler-dock (1.9.1) @@ -142,7 +142,7 @@ CHECKSUMS racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f racc (1.8.1-java) sha256=54f2e6d1e1b91c154013277d986f52a90e5ececbe91465d29172e49342732b98 rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a - rake (13.2.1) sha256=46cb38dae65d7d74b6020a4ac9d48afed8eb8149c040eccf0523bec91907059d + rake (13.3.0) sha256=96f5092d786ff412c62fde76f793cc0541bd84d2eb579caa529aa8a059934493 rake-compiler (1.2.9) sha256=5a3213a5dda977dfdf73e28beed6f4cd6a2cc86ac640bb662728eb7049a23607 rake-compiler-dock (1.9.1) sha256=e73720a29aba9c114728ce39cc0d8eef69ba61d88e7978c57bac171724cd4d53 rb_sys (0.9.111) sha256=65822fd8d57c248cd893db0efe01bc6edc15fcbea3ba6666091e35430c1cbaf0 @@ -167,4 +167,4 @@ CHECKSUMS unicode-emoji (4.0.4) sha256=2c2c4ef7f353e5809497126285a50b23056cc6e61b64433764a35eff6c36532a BUNDLED WITH - 2.7.0.dev + 2.8.0.dev diff --git a/tool/bundler/test_gems.rb b/tool/bundler/test_gems.rb index 7cbea1c83c..28113cd299 100644 --- a/tool/bundler/test_gems.rb +++ b/tool/bundler/test_gems.rb @@ -2,10 +2,7 @@ source "https://rubygems.org" -gem "rack", "~> 3.0" -gem "cgi", "~> 0.5.0.beta2" -gem "rackup", "~> 2.1" -gem "webrick", "~> 1.9" +gem "rack", "~> 3.1" gem "rack-test", "~> 2.1" gem "compact_index", "~> 0.15.0" gem "sinatra", "~> 4.1" @@ -14,3 +11,8 @@ gem "builder", "~> 3.2" gem "rb_sys" gem "fiddle" gem "rubygems-generate_index", "~> 1.1" +gem "ruby-graphviz" +gem "psych" +gem "etc", platforms: [:ruby, :windows] +gem "open3" +gem "shellwords" diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index cb3b67d5dd..99760ac573 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -3,14 +3,23 @@ GEM specs: base64 (0.2.0) builder (3.3.0) - cgi (0.5.0.beta2) - cgi (0.5.0.beta2-java) compact_index (0.15.0) + date (3.4.1) + date (3.4.1-java) + etc (1.4.5) fiddle (1.1.6) + jar-dependencies (0.5.5) logger (1.7.0) mustermann (3.0.3) ruby2_keywords (~> 0.0.1) - rack (3.1.12) + open3 (0.2.1) + psych (5.2.3) + date + stringio + psych (5.2.3-java) + date + jar-dependencies (>= 0.1.7) + rack (3.1.15) rack-protection (4.1.1) base64 (>= 0.1.0) logger (>= 1.6.0) @@ -20,15 +29,17 @@ GEM rack (>= 3.0.0) rack-test (2.2.0) rack (>= 1.3) - rackup (2.2.1) - rack (>= 3) - rake (13.2.1) + rake (13.3.0) rake-compiler-dock (1.9.1) rb_sys (0.9.111) rake-compiler-dock (= 1.9.1) + rexml (3.4.1) + ruby-graphviz (1.2.5) + rexml ruby2_keywords (0.0.5) rubygems-generate_index (1.1.3) compact_index (~> 0.15.0) + shellwords (0.2.2) sinatra (4.1.1) logger (>= 1.6.0) mustermann (~> 3.0) @@ -36,8 +47,8 @@ GEM rack-protection (= 4.1.1) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) + stringio (3.1.6) tilt (2.6.0) - webrick (1.9.1) PLATFORMS java @@ -49,40 +60,49 @@ PLATFORMS DEPENDENCIES builder (~> 3.2) - cgi (~> 0.5.0.beta2) compact_index (~> 0.15.0) + etc fiddle - rack (~> 3.0) + open3 + psych + rack (~> 3.1) rack-test (~> 2.1) - rackup (~> 2.1) rake (~> 13.1) rb_sys + ruby-graphviz rubygems-generate_index (~> 1.1) + shellwords sinatra (~> 4.1) - webrick (~> 1.9) CHECKSUMS base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507 builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f - cgi (0.5.0.beta2) sha256=0721a87b0fe40fc403af3c5569f117c1133f6d6cf6a0b88a8248af7ae5209129 - cgi (0.5.0.beta2-java) sha256=05c61b1c58c3ee9c7e0b0efcd5b2b85a9e97fd5a4504a76ecf71fc1606e19328 compact_index (0.15.0) sha256=5c6c404afca8928a7d9f4dde9524f6e1610db17e675330803055db282da84a8b + date (3.4.1) sha256=bf268e14ef7158009bfeaec40b5fa3c7271906e88b196d958a89d4b408abe64f + date (3.4.1-java) sha256=74740d914c65a922a15657c25ff0e203c16f1d0f7aa910a9ebed712afe9819c4 + etc (1.4.5) sha256=0d854e7b97a40390b048ba51230c30886931931b9dba955e85985d7d3bccf26c fiddle (1.1.6) sha256=79e8d909e602d979434cf9fccfa6e729cb16432bb00e39c7596abe6bee1249ab + jar-dependencies (0.5.5) sha256=2972b9fcba4b014e6446a84b5c09674a3e8648b95b71768e729f0e8e40568059 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 mustermann (3.0.3) sha256=d1f8e9ba2ddaed47150ddf81f6a7ea046826b64c672fbc92d83bce6b70657e88 - rack (3.1.12) sha256=00d83055c89273eb13679ab562767b8826955aa6c4371d7d161deb975c50c540 + open3 (0.2.1) sha256=8e2d7d2113526351201438c1aa35c8139f0141c9e8913baa007c898973bf3952 + psych (5.2.3) sha256=84a54bb952d14604fea22d99938348814678782f58b12648fcdfa4d2fce859ee + psych (5.2.3-java) sha256=3e5425b9e8a2f41cc2707d5ef14fdc1ae908abbafb12fe45727bd63900056585 + rack (3.1.15) sha256=d12b3e9960d18a26ded961250f2c0e3b375b49ff40dbe6786e9c3b160cbffca4 rack-protection (4.1.1) sha256=51a254a5d574a7f0ca4f0672025ce2a5ef7c8c3bd09c431349d683e825d7d16a rack-session (2.1.0) sha256=437c3916535b58ef71c816ce4a2dee0a01c8a52ae6077dc2b6cd19085760a290 rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463 - rackup (2.2.1) sha256=f737191fd5c5b348b7f0a4412a3b86383f88c43e13b8217b63d4c8d90b9e798d - rake (13.2.1) sha256=46cb38dae65d7d74b6020a4ac9d48afed8eb8149c040eccf0523bec91907059d + rake (13.3.0) sha256=96f5092d786ff412c62fde76f793cc0541bd84d2eb579caa529aa8a059934493 rake-compiler-dock (1.9.1) sha256=e73720a29aba9c114728ce39cc0d8eef69ba61d88e7978c57bac171724cd4d53 rb_sys (0.9.111) sha256=65822fd8d57c248cd893db0efe01bc6edc15fcbea3ba6666091e35430c1cbaf0 + rexml (3.4.1) sha256=c74527a9a0a04b4ec31dbe0dc4ed6004b960af943d8db42e539edde3a871abca + ruby-graphviz (1.2.5) sha256=1c2bb44e3f6da9e2b829f5e7fd8d75a521485fb6b4d1fc66ff0f93f906121504 ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef rubygems-generate_index (1.1.3) sha256=3571424322666598e9586a906485e1543b617f87644913eaf137d986a3393f5c + shellwords (0.2.2) sha256=b8695a791de2f71472de5abdc3f4332f6535a4177f55d8f99e7e44266cd32f94 sinatra (4.1.1) sha256=4e997b859aa1b5d2e624f85d5b0fd0f0b3abc0da44daa6cbdf10f7c0da9f4d00 + stringio (3.1.6) sha256=292c495d1657adfcdf0a32eecf12a60e6691317a500c3112ad3b2e31068274f5 tilt (2.6.0) sha256=263d748466e0d83e510aa1a2e2281eff547937f0ef06be33d3632721e255f76b - webrick (1.9.1) sha256=b42d3c94f166f3fb73d87e9b359def9b5836c426fc8beacf38f2184a21b2a989 BUNDLED WITH - 2.7.0.dev + 2.8.0.dev diff --git a/tool/bundler/vendor_gems.rb b/tool/bundler/vendor_gems.rb index 2e9fb7f18f..71a7fbf4b0 100644 --- a/tool/bundler/vendor_gems.rb +++ b/tool/bundler/vendor_gems.rb @@ -9,7 +9,7 @@ gem "net-http-persistent", github: "hsbt/net-http-persistent", ref: "9b6fbd733cf gem "net-protocol", "0.2.2" gem "optparse", "0.6.0" gem "pub_grub", github: "jhawthorn/pub_grub", ref: "df6add45d1b4d122daff2f959c9bd1ca93d14261" -gem "resolv", "0.6.0" +gem "resolv", "0.6.2" gem "securerandom", "0.4.1" gem "timeout", "0.4.3" gem "thor", "1.3.2" diff --git a/tool/bundler/vendor_gems.rb.lock b/tool/bundler/vendor_gems.rb.lock index 0fb79079b2..82dbe8963c 100644 --- a/tool/bundler/vendor_gems.rb.lock +++ b/tool/bundler/vendor_gems.rb.lock @@ -35,7 +35,7 @@ GEM net-protocol (0.2.2) timeout optparse (0.6.0) - resolv (0.6.0) + resolv (0.6.2) securerandom (0.4.1) thor (1.3.2) timeout (0.4.3) @@ -58,7 +58,7 @@ DEPENDENCIES net-protocol (= 0.2.2) optparse (= 0.6.0) pub_grub! - resolv (= 0.6.0) + resolv (= 0.6.2) securerandom (= 0.4.1) thor (= 1.3.2) timeout (= 0.4.3) @@ -74,7 +74,7 @@ CHECKSUMS net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8 optparse (0.6.0) sha256=25e90469c1cd44048a89dc01c1dde9d5f0bdf717851055fb18237780779b068c pub_grub (0.5.0) - resolv (0.6.0) sha256=b8b73f7734d4102ef9f75bad281d8fd1c434f8588b6aba17832ddc16fe679fab + resolv (0.6.2) sha256=61efe545cedddeb1b14f77e51f85c85ca66af5098fdbf567fadf32c34590fb14 securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 thor (1.3.2) sha256=eef0293b9e24158ccad7ab383ae83534b7ad4ed99c09f96f1a6b036550abbeda timeout (0.4.3) sha256=9509f079b2b55fe4236d79633bd75e34c1c1e7e3fb4b56cb5fda61f80a0fe30e @@ -82,4 +82,4 @@ CHECKSUMS uri (1.0.3) sha256=e9f2244608eea2f7bc357d954c65c910ce0399ca5e18a7a29207ac22d8767011 BUNDLED WITH - 2.7.0.dev + 2.8.0.dev diff --git a/tool/downloader.rb b/tool/downloader.rb index a1520eb6a9..14f18747f3 100644 --- a/tool/downloader.rb +++ b/tool/downloader.rb @@ -1,41 +1,12 @@ # Used by configure and make to download or update mirrored Ruby and GCC -# files. This will use HTTPS if possible, falling back to HTTP. +# files. # -*- frozen-string-literal: true -*- require 'fileutils' require 'open-uri' require 'pathname' -begin - require 'net/https' -rescue LoadError - https = 'http' -else - https = 'https' - - # open-uri of ruby 2.2.0 accepts an array of PEMs as ssl_ca_cert, but old - # versions do not. so, patching OpenSSL::X509::Store#add_file instead. - class OpenSSL::X509::Store - alias orig_add_file add_file - def add_file(pems) - Array(pems).each do |pem| - if File.directory?(pem) - add_path pem - else - orig_add_file pem - end - end - end - end - # since open-uri internally checks ssl_ca_cert using File.directory?, - # allow to accept an array. - class < e - m1, m2 = e.message.split("\n", 2) - STDERR.puts "Download failed (#{m1}), try another URL\n#{m2}" - super("https://raw.githubusercontent.com/gcc-mirror/gcc/master/#{name}", name, *rest, **options) - end + Mirrors.each_with_index do |url, i| + super("#{url}/#{name}", name, *rest, **options) + rescue => e + raise if i + 1 == Mirrors.size # no more URLs + m1, m2 = e.message.split("\n", 2) + STDERR.puts "Download failed (#{m1}), try another URL\n#{m2}" else - super("https://repo.or.cz/official-gcc.git/blob_plain/HEAD:/#{name}", name, *rest, **options) + return end end end @@ -222,11 +184,6 @@ class Downloader if link_cache(cache, file, name, verbose: verbose) return file.to_path end - if !https? and URI::HTTPS === url - warn "*** using http instead of https ***" - url.scheme = 'http' - url = URI(url.to_s) - end if verbose $stdout.print "downloading #{name} ... " $stdout.flush @@ -234,13 +191,7 @@ class Downloader mtime = nil options = options.merge(http_options(file, since.nil? ? true : since)) begin - data = with_retry(10) do - data = url.read(options) - if mtime = data.meta["last-modified"] - mtime = Time.httpdate(mtime) - end - data - end + data = with_retry(10) {url.read(options)} rescue OpenURI::HTTPError => http_error case http_error.message when /^304 / # 304 Not Modified @@ -268,6 +219,10 @@ class Downloader return file.to_path end raise + else + if mtime = data.meta["last-modified"] + mtime = Time.httpdate(mtime) + end end dest = (cache_save && cache && !cache.exist? ? cache : file) dest.parent.mkpath @@ -386,8 +341,6 @@ class Downloader private_class_method :with_retry end -Downloader.https = https.freeze - if $0 == __FILE__ since = true options = {} diff --git a/tool/enc-unicode.rb b/tool/enc-unicode.rb index 04d436281d..493a6f91c1 100755 --- a/tool/enc-unicode.rb +++ b/tool/enc-unicode.rb @@ -143,7 +143,8 @@ def define_posix_props(data) data['Space'] = data['White_Space'] data['Blank'] = data['Space_Separator'] + [0x0009] data['Cntrl'] = data['Cc'] - data['Word'] = data['Alpha'] + data['Mark'] + data['Digit'] + data['Connector_Punctuation'] + data['Word'] = data['Alpha'] + data['Mark'] + data['Digit'] + + data['Connector_Punctuation'] + data['Join_Control'] data['Graph'] = data['Any'] - data['Space'] - data['Cntrl'] - data['Surrogate'] - data['Unassigned'] data['Print'] = data['Graph'] + data['Space_Separator'] diff --git a/tool/fetch-bundled_gems.rb b/tool/fetch-bundled_gems.rb index f50bda360a..b76feefd94 100755 --- a/tool/fetch-bundled_gems.rb +++ b/tool/fetch-bundled_gems.rb @@ -24,20 +24,22 @@ next unless n next if n =~ /^#/ next if bundled_gems&.all? {|pat| !File.fnmatch?(pat, n)} -if File.directory?(n) - puts "updating #{color.notice(n)} ..." - system("git", "fetch", "--all", chdir: n) or abort -else +unless File.exist?("#{n}/.git") puts "retrieving #{color.notice(n)} ..." - system(*%W"git clone #{u} #{n}") or abort + system(*%W"git clone --depth=1 --no-tags #{u} #{n}") or abort end if r puts "fetching #{color.notice(r)} ..." system("git", "fetch", "origin", r, chdir: n) or abort + c = r +else + c = ["v#{v}", v].find do |c| + puts "fetching #{color.notice(c)} ..." + system("git", "fetch", "origin", "refs/tags/#{c}:refs/tags/#{c}", chdir: n) + end or abort end -c = r || "v#{v}" checkout = %w"git -c advice.detachedHead=false checkout" print %[checking out #{color.notice(c)} (v=#{color.info(v)}] print %[, r=#{color.info(r)}] if r diff --git a/tool/lib/_tmpdir.rb b/tool/lib/_tmpdir.rb index fd429dab37..daa1a1f235 100644 --- a/tool/lib/_tmpdir.rb +++ b/tool/lib/_tmpdir.rb @@ -4,11 +4,11 @@ template = "rubytest." # Assume the directory by these environment variables are safe. base = [ENV["TMPDIR"], ENV["TMP"], "/tmp"].find do |tmp| next unless tmp and tmp.size <= 50 and File.directory?(tmp) - # On macOS, the default TMPDIR is very long, inspite of UNIX socket - # path length is limited. + # On macOS, the default TMPDIR is very long, in spite of UNIX socket + # path length being limited. # # Also Rubygems creates its own temporary directory per tests, and - # some tests copy the full path of gemhome there. In that caes, the + # some tests copy the full path of gemhome there. In that case, the # path contains both temporary names twice, and can exceed path name # limit very easily. tmp diff --git a/tool/lib/bundle_env.rb b/tool/lib/bundle_env.rb new file mode 100644 index 0000000000..9ad5ea220b --- /dev/null +++ b/tool/lib/bundle_env.rb @@ -0,0 +1,4 @@ +ENV["GEM_HOME"] = File.expand_path("../../.bundle", __dir__) +ENV["BUNDLE_APP_CONFIG"] = File.expand_path("../../.bundle", __dir__) +ENV["BUNDLE_PATH__SYSTEM"] = "true" +ENV["BUNDLE_WITHOUT"] = "lint doc" diff --git a/tool/lib/bundled_gem.rb b/tool/lib/bundled_gem.rb index 45e41ac648..d2ed61a508 100644 --- a/tool/lib/bundled_gem.rb +++ b/tool/lib/bundled_gem.rb @@ -20,7 +20,10 @@ module BundledGem def unpack(file, *rest) pkg = Gem::Package.new(file) - prepare_test(pkg.spec, *rest) {|dir| pkg.extract_files(dir)} + prepare_test(pkg.spec, *rest) do |dir| + pkg.extract_files(dir) + FileUtils.rm_rf(Dir.glob(".git*", base: dir).map {|n| File.join(dir, n)}) + end puts "Unpacked #{file}" rescue Gem::Package::FormatError, Errno::ENOENT puts "Try with hash version of bundled gems instead of #{file}. We don't use this gem with release version of Ruby." diff --git a/tool/lib/core_assertions.rb b/tool/lib/core_assertions.rb index ede490576c..ece6ca1dc8 100644 --- a/tool/lib/core_assertions.rb +++ b/tool/lib/core_assertions.rb @@ -97,11 +97,12 @@ module Test end def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil, - success: nil, **opt) + success: nil, failed: nil, **opt) args = Array(args).dup args.insert((Hash === args[0] ? 1 : 0), '--disable=gems') stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, true, true, **opt) - desc = FailDesc[status, message, stderr] + desc = failed[status, message, stderr] if failed + desc ||= FailDesc[status, message, stderr] if block_given? raise "test_stdout ignored, use block only or without block" if test_stdout != [] raise "test_stderr ignored, use block only or without block" if test_stderr != [] @@ -371,6 +372,10 @@ eom def assert_ractor(src, args: [], require: nil, require_relative: nil, file: nil, line: nil, ignore_stderr: nil, **opt) return unless defined?(Ractor) + # https://bugs.ruby-lang.org/issues/21262 + shim_value = "class Ractor; alias value take; end" unless Ractor.method_defined?(:value) + shim_join = "class Ractor; alias join take; end" unless Ractor.method_defined?(:join) + require = "require #{require.inspect}" if require if require_relative dir = File.dirname(caller_locations[0,1][0].absolute_path) @@ -379,6 +384,8 @@ eom end assert_separately(args, file, line, <<~RUBY, ignore_stderr: ignore_stderr, **opt) + #{shim_value} + #{shim_join} #{require} previous_verbose = $VERBOSE $VERBOSE = nil diff --git a/tool/lib/dump.gdb b/tool/lib/dump.gdb new file mode 100644 index 0000000000..56b420a546 --- /dev/null +++ b/tool/lib/dump.gdb @@ -0,0 +1,17 @@ +set height 0 +set width 0 +set confirm off + +echo \n>>> Threads\n\n +info threads + +echo \n>>> Machine level backtrace\n\n +thread apply all info stack full + +echo \n>>> Dump Ruby level backtrace (if possible)\n\n +call rb_vmdebug_stack_dump_all_threads() +call fflush(stderr) + +echo ">>> Finish\n" +detach +quit diff --git a/tool/lib/dump.lldb b/tool/lib/dump.lldb new file mode 100644 index 0000000000..ed9cb89010 --- /dev/null +++ b/tool/lib/dump.lldb @@ -0,0 +1,13 @@ +script print("\n>>> Threads\n\n") +thread list + +script print("\n>>> Machine level backtrace\n\n") +thread backtrace all + +script print("\n>>> Dump Ruby level backtrace (if possible)\n\n") +call rb_vmdebug_stack_dump_all_threads() +call fflush(stderr) + +script print(">>> Finish\n") +detach +quit diff --git a/tool/lib/envutil.rb b/tool/lib/envutil.rb index 65c86c1685..d02329d4f1 100644 --- a/tool/lib/envutil.rb +++ b/tool/lib/envutil.rb @@ -79,6 +79,70 @@ module EnvUtil end module_function :timeout + class Debugger + @list = [] + + attr_accessor :name + + def self.register(name, &block) + @list << new(name, &block) + end + + def initialize(name, &block) + @name = name + instance_eval(&block) + end + + def usable?; false; end + + def start(pid, *args) end + + def dump(pid, timeout: 60, reprieve: timeout&.div(4)) + dpid = start(pid, *command_file(File.join(__dir__, "dump.#{name}")), out: :err) + rescue Errno::ENOENT + return + else + return unless dpid + [[timeout, :TERM], [reprieve, :KILL]].find do |t, sig| + return EnvUtil.timeout(t) {Process.wait(dpid)} + rescue Timeout::Error + Process.kill(sig, dpid) + end + true + end + + # sudo -n: --non-interactive + PRECOMMAND = (%[sudo -n] if /darwin/ =~ RUBY_PLATFORM) + + def spawn(*args, **opts) + super(*PRECOMMAND, *args, **opts) + end + + register("gdb") do + class << self + def usable?; system(*%w[gdb --batch --quiet --nx -ex exit]); end + def start(pid, *args, **opts) + spawn(*%W[gdb --batch --quiet --pid #{pid}], *args, **opts) + end + def command_file(file) "--command=#{file}"; end + end + end + + register("lldb") do + class << self + def usable?; system(*%w[lldb -Q --no-lldbinit -o exit]); end + def start(pid, *args, **opts) + spawn(*%W[lldb --batch -Q --attach-pid #{pid}], *args, **opts) + end + def command_file(file) ["--source", file]; end + end + end + + def self.search + @debugger ||= @list.find(&:usable?) + end + end + def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1) reprieve = apply_timeout_scale(reprieve) if reprieve @@ -94,17 +158,12 @@ module EnvUtil pgroup = pid end - lldb = true if /darwin/ =~ RUBY_PLATFORM - + dumped = false while signal = signals.shift - if lldb and [:ABRT, :KILL].include?(signal) - lldb = false - # sudo -n: --non-interactive - # lldb -p: attach - # -o: run command - system(*%W[sudo -n lldb -p #{pid} --batch -o bt\ all -o call\ rb_vmdebug_stack_dump_all_threads() -o quit]) - true + if !dumped and [:ABRT, :KILL].include?(signal) + Debugger.search&.dump(pid) + dumped = true end begin diff --git a/tool/lib/gem_env.rb b/tool/lib/gem_env.rb index 70a2469db2..1893e07657 100644 --- a/tool/lib/gem_env.rb +++ b/tool/lib/gem_env.rb @@ -1,2 +1 @@ -ENV['GEM_HOME'] = gem_home = File.expand_path('.bundle') -ENV['GEM_PATH'] = [gem_home, File.expand_path('../../../.bundle', __FILE__)].uniq.join(File::PATH_SEPARATOR) +ENV['GEM_HOME'] = File.expand_path('../../.bundle', __dir__) diff --git a/tool/lib/test/unit.rb b/tool/lib/test/unit.rb index 9ca29b6e64..7d43e825e1 100644 --- a/tool/lib/test/unit.rb +++ b/tool/lib/test/unit.rb @@ -421,6 +421,7 @@ module Test end def kill + EnvUtil::Debugger.search&.dump(@pid) signal = RUBY_PLATFORM =~ /mswin|mingw/ ? :KILL : :SEGV Process.kill(signal, @pid) warn "worker #{to_s} does not respond; #{signal} is sent" @@ -1298,10 +1299,15 @@ module Test parser.on '--repeat-count=NUM', "Number of times to repeat", Integer do |n| options[:repeat_count] = n end + options[:keep_repeating] = false + parser.on '--[no-]keep-repeating', "Keep repeating even failed" do |n| + options[:keep_repeating] = true + end end def _run_anything(type) @repeat_count = @options[:repeat_count] + @keep_repeating = @options[:keep_repeating] super end end @@ -1623,7 +1629,7 @@ module Test [(@repeat_count ? "(#{@@current_repeat_count}/#{@repeat_count}) " : ""), type, t, @test_count.fdiv(t), @assertion_count.fdiv(t)] end while @repeat_count && @@current_repeat_count < @repeat_count && - report.empty? && failures.zero? && errors.zero? + (@keep_repeating || report.empty? && failures.zero? && errors.zero?) output.sync = old_sync if sync diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb index 4e4f4c2a76..2c019d81fd 100644 --- a/tool/lib/vcs.rb +++ b/tool/lib/vcs.rb @@ -522,17 +522,32 @@ class VCS cmd << date cmd.concat(arg) proc do |w| - w.print "-*- coding: utf-8 -*-\n\n" - w.print "base-url = #{base_url}\n\n" if base_url + w.print "-*- coding: utf-8 -*-\n" + w.print "\n""base-url = #{base_url}\n" if base_url + + begin + ignore_revs = File.readlines(File.join(@srcdir, ".git-blame-ignore-revs"), chomp: true) + .grep_v(/^ *(?:#|$)/) + .to_h {|v| [v, true]} + ignore_revs = nil if ignore_revs.empty? + rescue Errno::ENOENT + end + cmd_pipe(env, cmd, chdir: @srcdir) do |r| - while s = r.gets("\ncommit ") + r.gets(sep = "commit ") + sep = "\n" + sep + while s = r.gets(sep, chomp: true) h, s = s.split(/^$/, 2) + if ignore_revs&.key?(h[/\A\h{40}/]) + next + end next if /^Author: *dependabot\[bot\]/ =~ h h.gsub!(/^(?:(?:Author|Commit)(?:Date)?|Date): /, ' \&') if s.sub!(/\nNotes \(log-fix\):\n((?: +.*\n)+)/, '') fix = $1 + next if /\A *skip\Z/ =~ fix s = s.lines fix.each_line do |x| next unless x.sub!(/^(\s+)(?:(\d+)|\$(?:-\d+)?)/, '') @@ -598,7 +613,7 @@ class VCS s.gsub!(/ +\n/, "\n") s.sub!(/^Notes:/, ' \&') - w.print h, s + w.print sep, h, s end end end diff --git a/tool/mk_builtin_loader.rb b/tool/mk_builtin_loader.rb index 6e1f5c666a..a63e1827d5 100644 --- a/tool/mk_builtin_loader.rb +++ b/tool/mk_builtin_loader.rb @@ -282,16 +282,21 @@ def generate_cexpr(ofile, lineno, line_file, body_lineno, text, locals, func_nam # Avoid generating fetches of lvars we don't need. This is imperfect as it # will match text inside strings or other false positives. - local_candidates = text.scan(/[a-zA-Z_][a-zA-Z0-9_]*/) + local_ptrs = [] + local_candidates = text.gsub(/\bLOCAL_PTR\(\K[a-zA-Z_][a-zA-Z0-9_]*(?=\))/) { + local_ptrs << $&; '' + }.scan(/[a-zA-Z_][a-zA-Z0-9_]*/) f.puts '{' lineno += 1 # locals is nil outside methods locals&.reverse_each&.with_index{|param, i| next unless Symbol === param - next unless local_candidates.include?(param.to_s) + param = param.to_s + lvar = local_candidates.include?(param) + next unless lvar or local_ptrs.include?(param) f.puts "VALUE *const #{param}__ptr = (VALUE *)&ec->cfp->ep[#{-3 - i}];" - f.puts "MAYBE_UNUSED(const VALUE) #{param} = *#{param}__ptr;" + f.puts "MAYBE_UNUSED(const VALUE) #{param} = *#{param}__ptr;" if lvar lineno += 1 } f.puts "#line #{body_lineno} \"#{line_file}\"" diff --git a/tool/rbinstall.rb b/tool/rbinstall.rb index b9df7c070c..24c6234d84 100755 --- a/tool/rbinstall.rb +++ b/tool/rbinstall.rb @@ -661,6 +661,17 @@ module RbInstall "#{srcdir}/lib" end end + + class UnpackedGem < self + def collect + base = @srcdir or return [] + Dir.glob("**/*", File::FNM_DOTMATCH, base: base).select do |n| + next if n == "." + next if File.fnmatch?("*.gemspec", n, File::FNM_DOTMATCH|File::FNM_PATHNAME) + !File.directory?(File.join(base, n)) + end + end + end end end @@ -772,24 +783,29 @@ module RbInstall $installed_list.puts(d+"/") if $installed_list end end + + def load_plugin + # Suppress warnings for constant re-assignment + verbose, $VERBOSE = $VERBOSE, nil + super + ensure + $VERBOSE = verbose + end end end -def load_gemspec(file, base = nil) +def load_gemspec(file, base = nil, files: nil) file = File.realpath(file) code = File.read(file, encoding: "utf-8:-") - files = [] - Dir.glob("**/*", File::FNM_DOTMATCH, base: base) do |n| - case File.basename(n); when ".", ".."; next; end - next if File.directory?(File.join(base, n)) - files << n.dump - end if base + code.gsub!(/^ *#.*/, "") + files = files ? files.map(&:dump).join(", ") : "" code.gsub!(/(?:`git[^\`]*`|%x\[git[^\]]*\])\.split(\([^\)]*\))?/m) do - "[" + files.join(", ") + "]" - end + "[" + files + "]" + end \ + or code.gsub!(/IO\.popen\(.*git.*?\)/) do - "[" + files.join(", ") + "] || itself" + "[" + files + "] || itself" end spec = eval(code, binding, file) @@ -797,7 +813,6 @@ def load_gemspec(file, base = nil) raise TypeError, "[#{file}] isn't a Gem::Specification (#{spec.class} instead)." end spec.loaded_from = base ? File.join(base, File.basename(file)) : file - spec.files.reject! {|n| n.end_with?(".gemspec") or n.start_with?(".git")} spec.date = RUBY_RELEASE_DATE spec @@ -827,14 +842,11 @@ def install_default_gem(dir, srcdir, bindir) base = "#{srcdir}/#{dir}" gems = Dir.glob("**/*.gemspec", base: base).map {|src| - spec = load_gemspec("#{base}/#{src}") - file_collector = RbInstall::Specs::FileCollector.for(srcdir, dir, src) - files = file_collector.collect + files = RbInstall::Specs::FileCollector.for(srcdir, dir, src).collect if files.empty? next end - spec.files = files - spec + load_gemspec("#{base}/#{src}", files: files) } gems.compact.sort_by(&:name).each do |gemspec| old_gemspecs = Dir[File.join(with_destdir(default_spec_dir), "#{gemspec.name}-*.gemspec")] @@ -1135,6 +1147,7 @@ install?(:ext, :comm, :gem, :'bundled-gems') do # the newly installed ruby. ENV.delete('RUBYOPT') + collector = RbInstall::Specs::FileCollector::UnpackedGem File.foreach("#{srcdir}/gems/bundled_gems") do |name| next if /^\s*(?:#|$)/ =~ name next unless /^(\S+)\s+(\S+).*/ =~ name @@ -1153,7 +1166,11 @@ install?(:ext, :comm, :gem, :'bundled-gems') do skipped[gem_name] = "gemspec not found" next end - spec = load_gemspec(path, "#{srcdir}/.bundle/gems/#{gem_name}") + base = "#{srcdir}/.bundle/gems/#{gem_name}" + files = collector.new(path, base, nil).collect + files.delete("#{gem}.gemspec") + files.delete("#{gem_name}.gemspec") + spec = load_gemspec(path, base, files: files) unless spec.platform == Gem::Platform::RUBY skipped[gem_name] = "not ruby platform (#{spec.platform})" next @@ -1168,6 +1185,7 @@ install?(:ext, :comm, :gem, :'bundled-gems') do next end spec.extension_dir = "#{extensions_dir}/#{spec.full_name}" + package = RbInstall::DirPackage.new spec ins = RbInstall::UnpackedInstaller.new(package, options) puts "#{INDENT}#{spec.name} #{spec.version}" @@ -1187,7 +1205,8 @@ install?(:ext, :comm, :gem, :'bundled-gems') do skipped.default = "not found in bundled_gems" puts "skipped bundled gems:" gems.each do |gem| - printf " %-32s%s\n", File.basename(gem), skipped[gem] + gem = File.basename(gem) + printf " %-31s %s\n", gem, skipped[gem.chomp(".gem")] end end end diff --git a/tool/rbs_skip_tests b/tool/rbs_skip_tests index ce3293a997..e8a10cf145 100644 --- a/tool/rbs_skip_tests +++ b/tool/rbs_skip_tests @@ -72,3 +72,10 @@ test_iconv(JSONSingletonTest) # https://github.com/ruby/json/pull/774 test_recurse_proc(JSONInstanceTest) test_recurse_proc(JSONSingletonTest) + +CGITest CGI is retired +CGISingletonTest CGI is retired + +RactorSingletonTest Ractor API was changed https://bugs.ruby-lang.org/issues/21262 +RactorInstanceTest Ractor API was changed https://bugs.ruby-lang.org/issues/21262 + diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 932f37b77c..7a231772b5 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -136,9 +136,11 @@ module SyncDefaultGems cp_r("#{upstream}/bundler/spec", "spec/bundler") rm_rf("spec/bundler/bin") - parallel_tests_content = File.read("#{upstream}/bundler/bin/parallel_rspec").gsub("../spec", "../bundler") - File.write("spec/bin/parallel_rspec", parallel_tests_content) - chmod("+x", "spec/bin/parallel_rspec") + ["bundle", "parallel_rspec", "rspec"].each do |binstub| + content = File.read("#{upstream}/bundler/bin/#{binstub}").gsub("../spec", "../bundler") + File.write("spec/bin/#{binstub}", content) + chmod("+x", "spec/bin/#{binstub}") + end %w[dev_gems test_gems rubocop_gems standard_gems].each do |gemfile| ["rb.lock", "rb"].each do |ext| @@ -274,7 +276,7 @@ module SyncDefaultGems rm_rf(%w[lib/erb* test/erb libexec/erb]) cp_r("#{upstream}/lib/erb.rb", "lib") cp_r("#{upstream}/test/erb", "test") - cp_r("#{upstream}/erb.gemspec", "lib") + cp_r("#{upstream}/erb.gemspec", "lib/erb") cp_r("#{upstream}/libexec/erb", "libexec") when "pathname" rm_rf(%w[ext/pathname test/pathname]) diff --git a/tool/test-bundled-gems.rb b/tool/test-bundled-gems.rb index a71d7dce7e..2b7e8916e5 100644 --- a/tool/test-bundled-gems.rb +++ b/tool/test-bundled-gems.rb @@ -10,7 +10,7 @@ github_actions = ENV["GITHUB_ACTIONS"] == "true" allowed_failures = ENV['TEST_BUNDLED_GEMS_ALLOW_FAILURES'] || '' if RUBY_PLATFORM =~ /mswin|mingw/ - allowed_failures = [allowed_failures, "rbs,debug,irb"].join(',') + allowed_failures = [allowed_failures, "rbs,debug,irb,power_assert"].join(',') end allowed_failures = allowed_failures.split(',').uniq.reject(&:empty?) diff --git a/tool/test/testunit/test_parallel.rb b/tool/test/testunit/test_parallel.rb index a0cbca69eb..d87e0ed327 100644 --- a/tool/test/testunit/test_parallel.rb +++ b/tool/test/testunit/test_parallel.rb @@ -126,19 +126,19 @@ module TestParallel assert_not_nil($1, "'done' was not found") result = Marshal.load($1.chomp.unpack1("m")) - assert_equal(5, result[0]) - pend "TODO: result[1] returns 17. We should investigate it" do # TODO: misusage of pend (pend doens't use given block) - assert_equal(12, result[1]) - end - assert_kind_of(Array,result[2]) - assert_kind_of(Array,result[3]) - assert_kind_of(Array,result[4]) - assert_kind_of(Array,result[2][1]) - assert_kind_of(Test::Unit::AssertionFailedError,result[2][0][2]) - assert_kind_of(Test::Unit::PendedError,result[2][1][2]) - assert_kind_of(Test::Unit::PendedError,result[2][2][2]) - assert_kind_of(Exception, result[2][3][2]) - assert_equal(result[5], "TestE") + tests, asserts, reports, failures, loadpaths, suite = result + assert_equal(5, tests) + assert_equal(12, asserts) + assert_kind_of(Array, reports) + assert_kind_of(Array, failures) + assert_kind_of(Array, loadpaths) + reports.sort_by! {|_, t| t} + assert_kind_of(Array, reports[1]) + assert_kind_of(Test::Unit::AssertionFailedError, reports[0][2]) + assert_kind_of(Test::Unit::PendedError, reports[1][2]) + assert_kind_of(Test::Unit::PendedError, reports[2][2]) + assert_kind_of(Exception, reports[3][2]) + assert_equal("TestE", suite) end end diff --git a/tool/test/testunit/tests_for_parallel/ptest_forth.rb b/tool/test/testunit/tests_for_parallel/ptest_forth.rb index 8831676e19..54474c828d 100644 --- a/tool/test/testunit/tests_for_parallel/ptest_forth.rb +++ b/tool/test/testunit/tests_for_parallel/ptest_forth.rb @@ -8,19 +8,19 @@ class TestE < Test::Unit::TestCase assert_equal(1,1) end - def test_always_skip - skip "always" + def test_always_omit + omit "always" end def test_always_fail assert_equal(0,1) end - def test_skip_after_unknown_error + def test_pend_after_unknown_error begin raise UnknownError, "unknown error" rescue - skip "after raise" + pend "after raise" end end diff --git a/transcode.c b/transcode.c index bff9268e1c..8e36fa13eb 100644 --- a/transcode.c +++ b/transcode.c @@ -20,6 +20,7 @@ #include "internal/string.h" #include "internal/transcode.h" #include "ruby/encoding.h" +#include "vm_sync.h" #include "transcode_data.h" #include "id.h" @@ -209,19 +210,21 @@ make_transcoder_entry(const char *sname, const char *dname) st_data_t val; st_table *table2; - if (!st_lookup(transcoder_table, (st_data_t)sname, &val)) { - val = (st_data_t)st_init_strcasetable(); - st_add_direct(transcoder_table, (st_data_t)sname, val); - } - table2 = (st_table *)val; - if (!st_lookup(table2, (st_data_t)dname, &val)) { - transcoder_entry_t *entry = ALLOC(transcoder_entry_t); - entry->sname = sname; - entry->dname = dname; - entry->lib = NULL; - entry->transcoder = NULL; - val = (st_data_t)entry; - st_add_direct(table2, (st_data_t)dname, val); + RB_VM_LOCKING() { + if (!st_lookup(transcoder_table, (st_data_t)sname, &val)) { + val = (st_data_t)st_init_strcasetable(); + st_add_direct(transcoder_table, (st_data_t)sname, val); + } + table2 = (st_table *)val; + if (!st_lookup(table2, (st_data_t)dname, &val)) { + transcoder_entry_t *entry = ALLOC(transcoder_entry_t); + entry->sname = sname; + entry->dname = dname; + entry->lib = NULL; + entry->transcoder = NULL; + val = (st_data_t)entry; + st_add_direct(table2, (st_data_t)dname, val); + } } return (transcoder_entry_t *)val; } @@ -229,15 +232,15 @@ make_transcoder_entry(const char *sname, const char *dname) static transcoder_entry_t * get_transcoder_entry(const char *sname, const char *dname) { - st_data_t val; + st_data_t val = 0; st_table *table2; - - if (!st_lookup(transcoder_table, (st_data_t)sname, &val)) { - return NULL; - } - table2 = (st_table *)val; - if (!st_lookup(table2, (st_data_t)dname, &val)) { - return NULL; + RB_VM_LOCKING() { + if (st_lookup(transcoder_table, (st_data_t)sname, &val)) { + table2 = (st_table *)val; + if (!st_lookup(table2, (st_data_t)dname, &val)) { + val = 0; + } + } } return (transcoder_entry_t *)val; } @@ -250,13 +253,14 @@ rb_register_transcoder(const rb_transcoder *tr) transcoder_entry_t *entry; - entry = make_transcoder_entry(sname, dname); - if (entry->transcoder) { - rb_raise(rb_eArgError, "transcoder from %s to %s has been already registered", - sname, dname); + RB_VM_LOCKING() { + entry = make_transcoder_entry(sname, dname); + if (entry->transcoder) { + rb_raise(rb_eArgError, "transcoder from %s to %s has been already registered", + sname, dname); + } + entry->transcoder = tr; } - - entry->transcoder = tr; } static void @@ -323,8 +327,9 @@ transcode_search_path(const char *sname, const char *dname, search_path_queue_t *q; st_data_t val; st_table *table2; - int found; int pathlen = -1; + bool found = false; + bool lookup_res; if (encoding_equal(sname, dname)) return -1; @@ -338,34 +343,36 @@ transcode_search_path(const char *sname, const char *dname, bfs.visited = st_init_strcasetable(); st_add_direct(bfs.visited, (st_data_t)sname, (st_data_t)NULL); - while (bfs.queue) { - q = bfs.queue; - bfs.queue = q->next; - if (!bfs.queue) - bfs.queue_last_ptr = &bfs.queue; + RB_VM_LOCKING() { + while (bfs.queue) { + q = bfs.queue; + bfs.queue = q->next; + if (!bfs.queue) { + bfs.queue_last_ptr = &bfs.queue; + } - if (!st_lookup(transcoder_table, (st_data_t)q->enc, &val)) { + lookup_res = st_lookup(transcoder_table, (st_data_t)q->enc, &val); + if (!lookup_res) { + xfree(q); + continue; + } + table2 = (st_table *)val; + + if (st_lookup(table2, (st_data_t)dname, &val)) { + st_add_direct(bfs.visited, (st_data_t)dname, (st_data_t)q->enc); + xfree(q); + found = true; + break; + } + + bfs.base_enc = q->enc; + st_foreach(table2, transcode_search_path_i, (st_data_t)&bfs); + + bfs.base_enc = NULL; xfree(q); - continue; } - table2 = (st_table *)val; - - if (st_lookup(table2, (st_data_t)dname, &val)) { - st_add_direct(bfs.visited, (st_data_t)dname, (st_data_t)q->enc); - xfree(q); - found = 1; - goto cleanup; - } - - bfs.base_enc = q->enc; - st_foreach(table2, transcode_search_path_i, (st_data_t)&bfs); - bfs.base_enc = NULL; - - xfree(q); } - found = 0; - cleanup: while (bfs.queue) { q = bfs.queue; bfs.queue = q->next; @@ -404,6 +411,8 @@ int rb_require_internal_silent(VALUE fname); static const rb_transcoder * load_transcoder_entry(transcoder_entry_t *entry) { + // changes result of entry->transcoder depending on if it's required or not, so needs lock + ASSERT_vm_locking(); if (entry->transcoder) return entry->transcoder; @@ -972,6 +981,7 @@ rb_econv_open_by_transcoder_entries(int n, transcoder_entry_t **entries) { rb_econv_t *ec; int i, ret; + ASSERT_vm_locking(); for (i = 0; i < n; i++) { const rb_transcoder *tr; @@ -1016,6 +1026,7 @@ rb_econv_open0(const char *sname, const char *dname, int ecflags) transcoder_entry_t **entries = NULL; int num_trans; rb_econv_t *ec; + ASSERT_vm_locking(); /* Just check if sname and dname are defined */ /* (This check is needed?) */ @@ -1106,19 +1117,23 @@ rb_econv_open(const char *sname, const char *dname, int ecflags) if (num_decorators == -1) return NULL; - ec = rb_econv_open0(sname, dname, ecflags & ECONV_ERROR_HANDLER_MASK); - if (!ec) - return NULL; - - for (i = 0; i < num_decorators; i++) - if (rb_econv_decorate_at_last(ec, decorators[i]) == -1) { - rb_econv_close(ec); - return NULL; + RB_VM_LOCKING() { + ec = rb_econv_open0(sname, dname, ecflags & ECONV_ERROR_HANDLER_MASK); + if (ec) { + for (i = 0; i < num_decorators; i++) { + if (rb_econv_decorate_at_last(ec, decorators[i]) == -1) { + rb_econv_close(ec); + ec = NULL; + break; + } + } } + } - ec->flags |= ecflags & ~ECONV_ERROR_HANDLER_MASK; - - return ec; + if (ec) { + ec->flags |= ecflags & ~ECONV_ERROR_HANDLER_MASK; + } + return ec; // can be NULL } static int @@ -1815,26 +1830,29 @@ rb_econv_asciicompat_encoding(const char *ascii_incompat_name) { st_data_t v; st_table *table2; - struct asciicompat_encoding_t data; + struct asciicompat_encoding_t data = {0}; - if (!st_lookup(transcoder_table, (st_data_t)ascii_incompat_name, &v)) - return NULL; - table2 = (st_table *)v; + RB_VM_LOCKING() { + if (st_lookup(transcoder_table, (st_data_t)ascii_incompat_name, &v)) { + table2 = (st_table *)v; + /* + * Assumption: + * There is at most one transcoder for + * converting from ASCII incompatible encoding. + * + * For ISO-2022-JP, there is ISO-2022-JP -> stateless-ISO-2022-JP and no others. + */ + if (table2->num_entries == 1) { + data.ascii_incompat_name = ascii_incompat_name; + data.ascii_compat_name = NULL; + st_foreach(table2, asciicompat_encoding_i, (st_data_t)&data); + } - /* - * Assumption: - * There is at most one transcoder for - * converting from ASCII incompatible encoding. - * - * For ISO-2022-JP, there is ISO-2022-JP -> stateless-ISO-2022-JP and no others. - */ - if (table2->num_entries != 1) - return NULL; + } - data.ascii_incompat_name = ascii_incompat_name; - data.ascii_compat_name = NULL; - st_foreach(table2, asciicompat_encoding_i, (st_data_t)&data); - return data.ascii_compat_name; + } + + return data.ascii_compat_name; // can be NULL } /* @@ -1937,19 +1955,20 @@ static int rb_econv_add_converter(rb_econv_t *ec, const char *sname, const char *dname, int n) { transcoder_entry_t *entry; - const rb_transcoder *tr; + const rb_transcoder *tr = NULL; if (ec->started != 0) return -1; - entry = get_transcoder_entry(sname, dname); - if (!entry) - return -1; + RB_VM_LOCKING() { + entry = get_transcoder_entry(sname, dname); + if (entry) { + tr = load_transcoder_entry(entry); + } - tr = load_transcoder_entry(entry); - if (!tr) return -1; + } - return rb_econv_add_transcoder_at(ec, tr, n); + return tr ? rb_econv_add_transcoder_at(ec, tr, n) : -1; } static int @@ -2662,24 +2681,25 @@ rb_econv_open_opts(const char *source_encoding, const char *destination_encoding replacement = rb_hash_aref(opthash, sym_replace); } - ec = rb_econv_open(source_encoding, destination_encoding, ecflags); - if (!ec) - return ec; + RB_VM_LOCKING() { + ec = rb_econv_open(source_encoding, destination_encoding, ecflags); + if (ec) { + if (!NIL_P(replacement)) { + int ret; + rb_encoding *enc = rb_enc_get(replacement); - if (!NIL_P(replacement)) { - int ret; - rb_encoding *enc = rb_enc_get(replacement); - - ret = rb_econv_set_replacement(ec, - (const unsigned char *)RSTRING_PTR(replacement), - RSTRING_LEN(replacement), - rb_enc_name(enc)); - if (ret == -1) { - rb_econv_close(ec); - return NULL; + ret = rb_econv_set_replacement(ec, + (const unsigned char *)RSTRING_PTR(replacement), + RSTRING_LEN(replacement), + rb_enc_name(enc)); + if (ret == -1) { + rb_econv_close(ec); + ec = NULL; + } + } } } - return ec; + return ec; // can be NULL } static int @@ -2979,9 +2999,11 @@ static rb_encoding * make_encoding(const char *name) { rb_encoding *enc; - enc = rb_enc_find(name); - if (!enc) - enc = make_dummy_encoding(name); + RB_VM_LOCKING() { + enc = rb_enc_find(name); + if (!enc) + enc = make_dummy_encoding(name); + } return enc; } @@ -3014,17 +3036,19 @@ econv_s_asciicompat_encoding(VALUE klass, VALUE arg) { const char *arg_name, *result_name; rb_encoding *arg_enc, *result_enc; + VALUE enc = Qnil; enc_arg(&arg, &arg_name, &arg_enc); - result_name = rb_econv_asciicompat_encoding(arg_name); + RB_VM_LOCKING() { + result_name = rb_econv_asciicompat_encoding(arg_name); - if (result_name == NULL) - return Qnil; - - result_enc = make_encoding(result_name); - - return rb_enc_from_encoding(result_enc); + if (result_name) { + result_enc = make_encoding(result_name); + enc = rb_enc_from_encoding(result_enc); + } + } + return enc; } static void @@ -3105,8 +3129,12 @@ decorate_convpath(VALUE convpath, int ecflags) if (RB_TYPE_P(pair, T_ARRAY)) { const char *sname = rb_enc_name(rb_to_encoding(RARRAY_AREF(pair, 0))); const char *dname = rb_enc_name(rb_to_encoding(RARRAY_AREF(pair, 1))); - transcoder_entry_t *entry = get_transcoder_entry(sname, dname); - const rb_transcoder *tr = load_transcoder_entry(entry); + transcoder_entry_t *entry; + const rb_transcoder *tr; + RB_VM_LOCKING() { + entry = get_transcoder_entry(sname, dname); + tr = load_transcoder_entry(entry); + } if (!tr) return -1; if (!DECORATOR_P(tr->src_encoding, tr->dst_encoding) && diff --git a/universal_parser.c b/universal_parser.c index ad2e2fbd11..84c71748af 100644 --- a/universal_parser.c +++ b/universal_parser.c @@ -13,7 +13,6 @@ #include "ruby/backward/2/inttypes.h" #include "probes.h" -#define LIKELY(x) RB_LIKELY(x) #define UNLIKELY(x) RB_UNLIKELY(x) #ifndef TRUE # define TRUE 1 diff --git a/util.c b/util.c index 2e887618b1..3e8ae590a8 100644 --- a/util.c +++ b/util.c @@ -42,40 +42,19 @@ const char ruby_hexdigits[] = "0123456789abcdef0123456789ABCDEF"; unsigned long ruby_scan_oct(const char *start, size_t len, size_t *retlen) { - register const char *s = start; - register unsigned long retval = 0; - size_t i; - - for (i = 0; i < len; i++) { - if ((s[0] < '0') || ('7' < s[0])) { - break; - } - retval <<= 3; - retval |= *s++ - '0'; - } - *retlen = (size_t)(s - start); - return retval; + int overflow; + unsigned long val = ruby_scan_digits(start, (ssize_t)len, 8, retlen, &overflow); + (void)overflow; + return val; } unsigned long ruby_scan_hex(const char *start, size_t len, size_t *retlen) { - register const char *s = start; - register unsigned long retval = 0; - signed char d; - size_t i = 0; - - for (i = 0; i < len; i++) { - d = ruby_digit36_to_number_table[(unsigned char)*s]; - if (d < 0 || 15 < d) { - break; - } - retval <<= 4; - retval |= d; - s++; - } - *retlen = (size_t)(s - start); - return retval; + int overflow; + unsigned long val = ruby_scan_digits(start, (ssize_t)len, 16, retlen, &overflow); + (void)overflow; + return val; } const signed char ruby_digit36_to_number_table[] = { diff --git a/variable.c b/variable.c index d7f9579d9c..ce63b7282c 100644 --- a/variable.c +++ b/variable.c @@ -303,9 +303,9 @@ rb_mod_set_temporary_name(VALUE mod, VALUE name) if (NIL_P(name)) { // Set the temporary classpath to NULL (anonymous): - RB_VM_LOCK_ENTER(); - set_sub_temporary_name(mod, 0); - RB_VM_LOCK_LEAVE(); + RB_VM_LOCKING() { + set_sub_temporary_name(mod, 0); + } } else { // Ensure the name is a string: @@ -322,9 +322,9 @@ rb_mod_set_temporary_name(VALUE mod, VALUE name) name = rb_str_new_frozen(name); // Set the temporary classpath to the given name: - RB_VM_LOCK_ENTER(); - set_sub_temporary_name(mod, name); - RB_VM_LOCK_LEAVE(); + RB_VM_LOCKING() { + set_sub_temporary_name(mod, name); + } } return mod; @@ -1201,197 +1201,144 @@ rb_generic_fields_tbl_get(void) return generic_fields_tbl_; } +static inline VALUE +generic_fields_lookup(VALUE obj, ID id, bool force_check_ractor) +{ + VALUE fields_obj = Qfalse; + RB_VM_LOCKING() { + st_table *generic_tbl = generic_fields_tbl(obj, id, false); + st_lookup(generic_tbl, obj, (st_data_t *)&fields_obj); + } + return fields_obj; +} + +static inline void +generic_fields_insert(VALUE obj, VALUE fields_obj) +{ + RUBY_ASSERT(IMEMO_TYPE_P(fields_obj, imemo_fields)); + + RB_VM_LOCKING() { + st_table *generic_tbl = generic_fields_tbl_no_ractor_check(obj); + st_insert(generic_tbl, obj, fields_obj); + } + RB_OBJ_WRITTEN(obj, Qundef, fields_obj); +} + int -rb_gen_fields_tbl_get(VALUE obj, ID id, struct gen_fields_tbl **fields_tbl) +rb_gen_fields_tbl_get(VALUE obj, ID id, VALUE *fields_obj) { RUBY_ASSERT(!RB_TYPE_P(obj, T_ICLASS)); st_data_t data; int r = 0; - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { if (st_lookup(generic_fields_tbl(obj, id, false), (st_data_t)obj, &data)) { - *fields_tbl = (struct gen_fields_tbl *)data; + *fields_obj = (VALUE)data; r = 1; } } - RB_VM_LOCK_LEAVE(); return r; } int -rb_ivar_generic_fields_tbl_lookup(VALUE obj, struct gen_fields_tbl **fields_tbl) +rb_ivar_generic_fields_tbl_lookup(VALUE obj, VALUE *fields_obj) { - return rb_gen_fields_tbl_get(obj, 0, fields_tbl); -} - -static size_t -gen_fields_tbl_bytes(size_t n) -{ - return offsetof(struct gen_fields_tbl, as.shape.fields) + n * sizeof(VALUE); -} - -static struct gen_fields_tbl * -gen_fields_tbl_resize(struct gen_fields_tbl *old, uint32_t n) -{ - RUBY_ASSERT(n > 0); - - uint32_t len = old ? old->as.shape.fields_count : 0; - struct gen_fields_tbl *fields_tbl = xrealloc(old, gen_fields_tbl_bytes(n)); - - fields_tbl->as.shape.fields_count = n; - for (; len < n; len++) { - fields_tbl->as.shape.fields[len] = Qundef; - } - - return fields_tbl; + return rb_gen_fields_tbl_get(obj, 0, fields_obj); } void rb_mark_generic_ivar(VALUE obj) { - st_data_t data; - if (st_lookup(generic_fields_tbl_no_ractor_check(obj), (st_data_t)obj, &data)) { - struct gen_fields_tbl *fields_tbl = (struct gen_fields_tbl *)data; - if (rb_shape_obj_too_complex_p(obj)) { - rb_mark_tbl_no_pin(fields_tbl->as.complex.table); - } - else { - for (uint32_t i = 0; i < fields_tbl->as.shape.fields_count; i++) { - rb_gc_mark_movable(fields_tbl->as.shape.fields[i]); - } - } + VALUE data; + if (st_lookup(generic_fields_tbl_no_ractor_check(obj), (st_data_t)obj, (st_data_t *)&data)) { + rb_gc_mark_movable(data); } } void rb_free_generic_ivar(VALUE obj) { - st_data_t key = (st_data_t)obj, value; + if (rb_obj_exivar_p(obj)) { + st_data_t key = (st_data_t)obj, value; - bool too_complex = rb_shape_obj_too_complex_p(obj); - - if (st_delete(generic_fields_tbl_no_ractor_check(obj), &key, &value)) { - struct gen_fields_tbl *fields_tbl = (struct gen_fields_tbl *)value; - - if (UNLIKELY(too_complex)) { - st_free_table(fields_tbl->as.complex.table); - } - - xfree(fields_tbl); - } -} - -size_t -rb_generic_ivar_memsize(VALUE obj) -{ - struct gen_fields_tbl *fields_tbl; - - if (rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - if (rb_shape_obj_too_complex_p(obj)) { - return sizeof(struct gen_fields_tbl) + st_memsize(fields_tbl->as.complex.table); - } - else { - return gen_fields_tbl_bytes(fields_tbl->as.shape.fields_count); + RB_VM_LOCKING() { + st_delete(generic_fields_tbl_no_ractor_check(obj), &key, &value); } } - return 0; -} - -#if !SHAPE_IN_BASIC_FLAGS -shape_id_t -rb_generic_shape_id(VALUE obj) -{ - struct gen_fields_tbl *fields_tbl = 0; - shape_id_t shape_id = 0; - - RB_VM_LOCK_ENTER(); - { - st_table* global_iv_table = generic_fields_tbl(obj, 0, false); - - if (global_iv_table && st_lookup(global_iv_table, obj, (st_data_t *)&fields_tbl)) { - shape_id = fields_tbl->shape_id; - } - else if (OBJ_FROZEN(obj)) { - shape_id = SPECIAL_CONST_SHAPE_ID; - } - } - RB_VM_LOCK_LEAVE(); - - return shape_id; -} -#endif - -static size_t -gen_fields_tbl_count(VALUE obj, const struct gen_fields_tbl *fields_tbl) -{ - uint32_t i; - size_t n = 0; - - if (rb_shape_obj_too_complex_p(obj)) { - n = st_table_size(fields_tbl->as.complex.table); - } - else { - for (i = 0; i < fields_tbl->as.shape.fields_count; i++) { - if (!UNDEF_P(fields_tbl->as.shape.fields[i])) { - n++; - } - } - } - - return n; } VALUE rb_obj_field_get(VALUE obj, shape_id_t target_shape_id) { RUBY_ASSERT(!SPECIAL_CONST_P(obj)); - RUBY_ASSERT(RSHAPE(target_shape_id)->type == SHAPE_IVAR || RSHAPE(target_shape_id)->type == SHAPE_OBJ_ID); + RUBY_ASSERT(RSHAPE_TYPE_P(target_shape_id, SHAPE_IVAR) || RSHAPE_TYPE_P(target_shape_id, SHAPE_OBJ_ID)); - if (rb_shape_id_too_complex_p(target_shape_id)) { + if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) { + ASSERT_vm_locking(); + VALUE field_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + if (field_obj) { + return rb_obj_field_get(field_obj, target_shape_id); + } + return Qundef; + } + + if (rb_shape_too_complex_p(target_shape_id)) { st_table *fields_hash; switch (BUILTIN_TYPE(obj)) { case T_CLASS: case T_MODULE: - ASSERT_vm_locking(); - fields_hash = RCLASS_FIELDS_HASH(obj); + rb_bug("Unreachable"); break; case T_OBJECT: fields_hash = ROBJECT_FIELDS_HASH(obj); break; + case T_IMEMO: + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + fields_hash = rb_imemo_fields_complex_tbl(obj); + break; default: - RUBY_ASSERT(FL_TEST_RAW(obj, FL_EXIVAR)); - struct gen_fields_tbl *fields_tbl = NULL; - rb_ivar_generic_fields_tbl_lookup(obj, &fields_tbl); - RUBY_ASSERT(fields_tbl); - fields_hash = fields_tbl->as.complex.table; + RUBY_ASSERT(rb_obj_exivar_p(obj)); + VALUE fields_obj = 0; + rb_ivar_generic_fields_tbl_lookup(obj, &fields_obj); + RUBY_ASSERT(fields_obj); + fields_hash = rb_imemo_fields_complex_tbl(fields_obj); break; } VALUE value = Qundef; - st_lookup(fields_hash, RSHAPE(target_shape_id)->edge_name, &value); + st_lookup(fields_hash, RSHAPE_EDGE_NAME(target_shape_id), &value); + +#if RUBY_DEBUG + if (UNDEF_P(value)) { + rb_bug("Object's shape includes object_id, but it's missing %s", rb_obj_info(obj)); + } +#endif + RUBY_ASSERT(!UNDEF_P(value)); return value; } - attr_index_t attr_index = RSHAPE(target_shape_id)->next_field_index - 1; + attr_index_t attr_index = RSHAPE_INDEX(target_shape_id); VALUE *fields; switch (BUILTIN_TYPE(obj)) { case T_CLASS: case T_MODULE: - ASSERT_vm_locking(); - fields = RCLASS_PRIME_FIELDS(obj); + rb_bug("Unreachable"); break; case T_OBJECT: fields = ROBJECT_FIELDS(obj); break; + case T_IMEMO: + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + fields = rb_imemo_fields_ptr(obj); + break; default: - RUBY_ASSERT(FL_TEST_RAW(obj, FL_EXIVAR)); - struct gen_fields_tbl *fields_tbl = NULL; - rb_ivar_generic_fields_tbl_lookup(obj, &fields_tbl); - RUBY_ASSERT(fields_tbl); - fields = fields_tbl->as.shape.fields; + RUBY_ASSERT(rb_obj_exivar_p(obj)); + VALUE fields_obj = 0; + rb_ivar_generic_fields_tbl_lookup(obj, &fields_obj); + RUBY_ASSERT(fields_obj); + fields = rb_imemo_fields_ptr(fields_obj); break; } return fields[attr_index]; @@ -1403,54 +1350,19 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef) if (SPECIAL_CONST_P(obj)) return undef; shape_id_t shape_id; - VALUE * ivar_list; - rb_shape_t * shape; - -#if SHAPE_IN_BASIC_FLAGS - shape_id = RBASIC_SHAPE_ID(obj); -#endif + VALUE *ivar_list; switch (BUILTIN_TYPE(obj)) { case T_CLASS: case T_MODULE: { - bool found = false; - VALUE val; - - RB_VM_LOCK_ENTER(); - { -#if !SHAPE_IN_BASIC_FLAGS - shape_id = RCLASS_SHAPE_ID(obj); -#endif - - if (rb_shape_obj_too_complex_p(obj)) { - st_table * iv_table = RCLASS_FIELDS_HASH(obj); - if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) { - found = true; - } - else { - val = undef; - } - } - else { - attr_index_t index = 0; - shape = RSHAPE(shape_id); - found = rb_shape_get_iv_index(shape, id, &index); - - if (found) { - ivar_list = RCLASS_PRIME_FIELDS(obj); - RUBY_ASSERT(ivar_list); - - val = ivar_list[index]; - } - else { - val = undef; - } - } + VALUE val = undef; + VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + if (fields_obj) { + val = rb_ivar_lookup(fields_obj, id, undef); } - RB_VM_LOCK_LEAVE(); - if (found && + if (val != undef && rb_is_instance_id(id) && UNLIKELY(!rb_ractor_main_p()) && !rb_ractor_shareable_p(val)) { @@ -1459,13 +1371,32 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef) } return val; } + case T_IMEMO: + // Handled like T_OBJECT + { + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + shape_id = RBASIC_SHAPE_ID(obj); + + if (rb_shape_too_complex_p(shape_id)) { + st_table *iv_table = rb_imemo_fields_complex_tbl(obj); + VALUE val; + if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) { + return val; + } + else { + return undef; + } + } + + RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); + ivar_list = rb_imemo_fields_ptr(obj); + break; + } case T_OBJECT: { -#if !SHAPE_IN_BASIC_FLAGS - shape_id = ROBJECT_SHAPE_ID(obj); -#endif - if (rb_shape_obj_too_complex_p(obj)) { - st_table * iv_table = ROBJECT_FIELDS_HASH(obj); + shape_id = RBASIC_SHAPE_ID(obj); + if (rb_shape_too_complex_p(shape_id)) { + st_table *iv_table = ROBJECT_FIELDS_HASH(obj); VALUE val; if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) { return val; @@ -1480,24 +1411,23 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef) break; } default: - if (FL_TEST_RAW(obj, FL_EXIVAR)) { - struct gen_fields_tbl *fields_tbl; - rb_gen_fields_tbl_get(obj, id, &fields_tbl); + shape_id = RBASIC_SHAPE_ID(obj); + if (rb_obj_exivar_p(obj)) { + VALUE fields_obj = 0; + rb_gen_fields_tbl_get(obj, id, &fields_obj); - if (rb_shape_obj_too_complex_p(obj)) { + RUBY_ASSERT(fields_obj); + + if (rb_shape_obj_too_complex_p(fields_obj)) { VALUE val; - if (rb_st_lookup(fields_tbl->as.complex.table, (st_data_t)id, (st_data_t *)&val)) { + if (rb_st_lookup(rb_imemo_fields_complex_tbl(fields_obj), (st_data_t)id, (st_data_t *)&val)) { return val; } else { return undef; } } - -#if !SHAPE_IN_BASIC_FLAGS - shape_id = fields_tbl->shape_id; -#endif - ivar_list = fields_tbl->as.shape.fields; + ivar_list = rb_imemo_fields_ptr(fields_obj); } else { return undef; @@ -1506,8 +1436,7 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef) } attr_index_t index = 0; - shape = RSHAPE(shape_id); - if (rb_shape_get_iv_index(shape, id, &index)) { + if (rb_shape_get_iv_index(shape_id, id, &index)) { return ivar_list[index]; } @@ -1536,18 +1465,102 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef) VALUE val = undef; if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) { IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(id); + + VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + if (fields_obj) { + if (rb_multi_ractor_p()) { + fields_obj = rb_imemo_fields_clone(fields_obj); + val = rb_ivar_delete(fields_obj, id, undef); + RCLASS_WRITABLE_SET_FIELDS_OBJ(obj, fields_obj); + } + else { + val = rb_ivar_delete(fields_obj, id, undef); + } + } + return val; } - if (!rb_shape_transition_remove_ivar(obj, id, &val)) { - if (!rb_shape_obj_too_complex_p(obj)) { - rb_evict_fields_to_hash(obj); - } + shape_id_t old_shape_id = rb_obj_shape_id(obj); + if (rb_shape_too_complex_p(old_shape_id)) { + goto too_complex; + } + shape_id_t removed_shape_id = 0; + shape_id_t next_shape_id = rb_shape_transition_remove_ivar(obj, id, &removed_shape_id); + + if (next_shape_id == old_shape_id) { + return undef; + } + + if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) { + rb_evict_fields_to_hash(obj); + goto too_complex; + } + + RUBY_ASSERT(RSHAPE_LEN(next_shape_id) == RSHAPE_LEN(old_shape_id) - 1); + + VALUE *fields; + switch(BUILTIN_TYPE(obj)) { + case T_CLASS: + case T_MODULE: + rb_bug("Unreachable"); + break; + case T_IMEMO: + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + fields = rb_imemo_fields_ptr(obj); + break; + case T_OBJECT: + fields = ROBJECT_FIELDS(obj); + break; + default: { + VALUE fields_obj; + rb_gen_fields_tbl_get(obj, id, &fields_obj); + fields = rb_imemo_fields_ptr(fields_obj); + break; + } + } + + RUBY_ASSERT(removed_shape_id != INVALID_SHAPE_ID); + + attr_index_t removed_index = RSHAPE_INDEX(removed_shape_id); + val = fields[removed_index]; + + attr_index_t new_fields_count = RSHAPE_LEN(next_shape_id); + if (new_fields_count) { + size_t trailing_fields = new_fields_count - removed_index; + + MEMMOVE(&fields[removed_index], &fields[removed_index + 1], VALUE, trailing_fields); + } + else { + rb_free_generic_ivar(obj); + } + + if (RB_TYPE_P(obj, T_OBJECT) && + !RB_FL_TEST_RAW(obj, ROBJECT_EMBED) && + rb_obj_embedded_size(new_fields_count) <= rb_gc_obj_slot_size(obj)) { + // Re-embed objects when instances become small enough + // This is necessary because YJIT assumes that objects with the same shape + // have the same embeddedness for efficiency (avoid extra checks) + RB_FL_SET_RAW(obj, ROBJECT_EMBED); + MEMCPY(ROBJECT_FIELDS(obj), fields, VALUE, new_fields_count); + xfree(fields); + } + rb_obj_set_shape_id(obj, next_shape_id); + + return val; + +too_complex: + { st_table *table = NULL; switch (BUILTIN_TYPE(obj)) { case T_CLASS: case T_MODULE: - table = RCLASS_WRITABLE_FIELDS_HASH(obj); + rb_bug("Unreachable"); + break; + + case T_IMEMO: + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + table = rb_imemo_fields_complex_tbl(obj); break; case T_OBJECT: @@ -1555,9 +1568,9 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef) break; default: { - struct gen_fields_tbl *fields_tbl; - if (rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - table = fields_tbl->as.complex.table; + VALUE fields_obj; + if (rb_gen_fields_tbl_get(obj, 0, &fields_obj)) { + table = rb_imemo_fields_complex_tbl(fields_obj); } break; } @@ -1579,64 +1592,50 @@ rb_attr_delete(VALUE obj, ID id) return rb_ivar_delete(obj, id, Qnil); } -static void +static inline void generic_update_fields_obj(VALUE obj, VALUE fields_obj, const VALUE original_fields_obj); + +static shape_id_t obj_transition_too_complex(VALUE obj, st_table *table) { + if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) { + return obj_transition_too_complex(RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(obj), table); + } + RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); shape_id_t shape_id = rb_shape_transition_complex(obj); - VALUE *old_fields = NULL; - switch (BUILTIN_TYPE(obj)) { case T_OBJECT: - if (!(RBASIC(obj)->flags & ROBJECT_EMBED)) { - old_fields = ROBJECT_FIELDS(obj); + { + VALUE *old_fields = NULL; + if (!(RBASIC(obj)->flags & ROBJECT_EMBED)) { + old_fields = ROBJECT_FIELDS(obj); + } + RBASIC_SET_SHAPE_ID(obj, shape_id); + ROBJECT_SET_FIELDS_HASH(obj, table); + if (old_fields) { + xfree(old_fields); + } } - rb_shape_set_shape_id(obj, shape_id); - ROBJECT_SET_FIELDS_HASH(obj, table); break; case T_CLASS: case T_MODULE: - old_fields = RCLASS_PRIME_FIELDS(obj); - rb_shape_set_shape_id(obj, shape_id); - RCLASS_SET_FIELDS_HASH(obj, table); + rb_bug("Unreachable"); break; default: - RB_VM_LOCK_ENTER(); { - struct st_table *gen_ivs = generic_fields_tbl_no_ractor_check(obj); + VALUE fields_obj = rb_imemo_fields_new_complex_tbl(rb_obj_class(obj), table); + RBASIC_SET_SHAPE_ID(fields_obj, shape_id); - struct gen_fields_tbl *old_fields_tbl = NULL; - st_lookup(gen_ivs, (st_data_t)obj, (st_data_t *)&old_fields_tbl); - - if (old_fields_tbl) { - /* We need to modify old_fields_tbl to have the too complex shape - * and hold the table because the xmalloc could trigger a GC - * compaction. We want the table to be updated rather than - * the original fields. */ -#if SHAPE_IN_BASIC_FLAGS - rb_shape_set_shape_id(obj, shape_id); -#else - old_fields_tbl->shape_id = shape_id; -#endif - old_fields_tbl->as.complex.table = table; - old_fields = (VALUE *)old_fields_tbl; + RB_VM_LOCKING() { + const VALUE original_fields_obj = generic_fields_lookup(obj, 0, false); + generic_update_fields_obj(obj, fields_obj, original_fields_obj); } - - struct gen_fields_tbl *fields_tbl = xmalloc(sizeof(struct gen_fields_tbl)); - fields_tbl->as.complex.table = table; - st_insert(gen_ivs, (st_data_t)obj, (st_data_t)fields_tbl); - -#if SHAPE_IN_BASIC_FLAGS - rb_shape_set_shape_id(obj, shape_id); -#else - fields_tbl->shape_id = shape_id; -#endif + RBASIC_SET_SHAPE_ID(obj, shape_id); } - RB_VM_LOCK_LEAVE(); } - xfree(old_fields); + return shape_id; } void @@ -1644,26 +1643,67 @@ rb_obj_init_too_complex(VALUE obj, st_table *table) { // This method is meant to be called on newly allocated object. RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); - RUBY_ASSERT(rb_shape_canonical_p(rb_obj_shape(obj))); - RUBY_ASSERT(rb_obj_shape(obj)->next_field_index == 0); + RUBY_ASSERT(rb_shape_canonical_p(RBASIC_SHAPE_ID(obj))); + RUBY_ASSERT(RSHAPE_LEN(RBASIC_SHAPE_ID(obj)) == 0); obj_transition_too_complex(obj, table); } +static int +imemo_fields_complex_from_obj_i(ID key, VALUE val, st_data_t arg) +{ + VALUE fields = (VALUE)arg; + st_table *table = rb_imemo_fields_complex_tbl(fields); + + RUBY_ASSERT(!st_lookup(table, (st_data_t)key, NULL)); + st_add_direct(table, (st_data_t)key, (st_data_t)val); + RB_OBJ_WRITTEN(fields, Qundef, val); + + return ST_CONTINUE; +} + +static VALUE +imemo_fields_complex_from_obj(VALUE klass, VALUE source_fields_obj, shape_id_t shape_id) +{ + attr_index_t len = source_fields_obj ? RSHAPE_LEN(RBASIC_SHAPE_ID(source_fields_obj)) : 0; + VALUE fields_obj = rb_imemo_fields_new_complex(klass, len + 1); + + rb_field_foreach(source_fields_obj, imemo_fields_complex_from_obj_i, (st_data_t)fields_obj, false); + RBASIC_SET_SHAPE_ID(fields_obj, shape_id); + + return fields_obj; +} + +static VALUE +imemo_fields_copy_capa(VALUE klass, VALUE source_fields_obj, attr_index_t new_size) +{ + VALUE fields_obj = rb_imemo_fields_new(klass, new_size); + if (source_fields_obj) { + attr_index_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID(source_fields_obj)); + VALUE *fields = rb_imemo_fields_ptr(fields_obj); + MEMCPY(fields, rb_imemo_fields_ptr(source_fields_obj), VALUE, fields_count); + RBASIC_SET_SHAPE_ID(fields_obj, RBASIC_SHAPE_ID(source_fields_obj)); + for (attr_index_t i = 0; i < fields_count; i++) { + RB_OBJ_WRITTEN(fields_obj, Qundef, fields[i]); + } + } + return fields_obj; +} + +void rb_obj_copy_fields_to_hash_table(VALUE obj, st_table *table); + // Copy all object fields, including ivars and internal object_id, etc -void +shape_id_t rb_evict_fields_to_hash(VALUE obj) { - void rb_obj_copy_fields_to_hash_table(VALUE obj, st_table *table); - RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); - rb_shape_t *shape = rb_obj_shape(obj); - st_table *table = st_init_numtable_with_size(shape->next_field_index); + st_table *table = st_init_numtable_with_size(RSHAPE_LEN(RBASIC_SHAPE_ID(obj))); rb_obj_copy_fields_to_hash_table(obj, table); - obj_transition_too_complex(obj, table); + shape_id_t new_shape_id = obj_transition_too_complex(obj, table); RUBY_ASSERT(rb_shape_obj_too_complex_p(obj)); + return new_shape_id; } void @@ -1690,7 +1730,7 @@ general_ivar_set(VALUE obj, ID id, VALUE val, void *data, VALUE *(*shape_fields_func)(VALUE, void *), void (*shape_resize_fields_func)(VALUE, attr_index_t, attr_index_t, void *), void (*set_shape_id_func)(VALUE, shape_id_t, void *), - void (*transition_too_complex_func)(VALUE, void *), + shape_id_t (*transition_too_complex_func)(VALUE, void *), st_table *(*too_complex_table_func)(VALUE, void *)) { struct general_ivar_set_result result = { @@ -1698,34 +1738,33 @@ general_ivar_set(VALUE obj, ID id, VALUE val, void *data, .existing = true }; - rb_shape_t *current_shape = rb_obj_shape(obj); + shape_id_t current_shape_id = RBASIC_SHAPE_ID(obj); - if (UNLIKELY(rb_shape_too_complex_p(current_shape))) { + if (UNLIKELY(rb_shape_too_complex_p(current_shape_id))) { goto too_complex; } attr_index_t index; - if (!rb_shape_get_iv_index(current_shape, id, &index)) { + if (!rb_shape_get_iv_index(current_shape_id, id, &index)) { result.existing = false; - index = current_shape->next_field_index; + index = RSHAPE_LEN(current_shape_id); if (index >= SHAPE_MAX_FIELDS) { rb_raise(rb_eArgError, "too many instance variables"); } shape_id_t next_shape_id = rb_shape_transition_add_ivar(obj, id); - rb_shape_t *next_shape = RSHAPE(next_shape_id); - if (UNLIKELY(rb_shape_too_complex_p(next_shape))) { - transition_too_complex_func(obj, data); + if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) { + current_shape_id = transition_too_complex_func(obj, data); goto too_complex; } - else if (UNLIKELY(next_shape->capacity != current_shape->capacity)) { - RUBY_ASSERT(next_shape->capacity > current_shape->capacity); - shape_resize_fields_func(obj, current_shape->capacity, next_shape->capacity, data); + else if (UNLIKELY(RSHAPE_CAPACITY(next_shape_id) != RSHAPE_CAPACITY(current_shape_id))) { + RUBY_ASSERT(RSHAPE_CAPACITY(next_shape_id) > RSHAPE_CAPACITY(current_shape_id)); + shape_resize_fields_func(obj, RSHAPE_CAPACITY(current_shape_id), RSHAPE_CAPACITY(next_shape_id), data); } - RUBY_ASSERT(next_shape->type == SHAPE_IVAR); - RUBY_ASSERT(index == (next_shape->next_field_index - 1)); + RUBY_ASSERT(RSHAPE_TYPE_P(next_shape_id, SHAPE_IVAR)); + RUBY_ASSERT(index == (RSHAPE_INDEX(next_shape_id))); set_shape_id_func(obj, next_shape_id, data); } @@ -1752,31 +1791,33 @@ general_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val, void *data, VALUE *(*shape_fields_func)(VALUE, void *), void (*shape_resize_fields_func)(VALUE, attr_index_t, attr_index_t, void *), void (*set_shape_id_func)(VALUE, shape_id_t, void *), - void (*transition_too_complex_func)(VALUE, void *), + shape_id_t (*transition_too_complex_func)(VALUE, void *), st_table *(*too_complex_table_func)(VALUE, void *)) { - rb_shape_t *current_shape = rb_obj_shape(obj); + shape_id_t current_shape_id = RBASIC_SHAPE_ID(obj); - if (UNLIKELY(rb_shape_id_too_complex_p(target_shape_id))) { - if (UNLIKELY(!rb_shape_too_complex_p(current_shape))) { - transition_too_complex_func(obj, data); + if (UNLIKELY(rb_shape_too_complex_p(target_shape_id))) { + if (UNLIKELY(!rb_shape_too_complex_p(current_shape_id))) { + current_shape_id = transition_too_complex_func(obj, data); } st_table *table = too_complex_table_func(obj, data); - if (RSHAPE(target_shape_id)->next_field_index > current_shape->next_field_index) { - set_shape_id_func(obj, target_shape_id, data); + + if (RSHAPE_LEN(target_shape_id) > RSHAPE_LEN(current_shape_id)) { + RBASIC_SET_SHAPE_ID(obj, target_shape_id); } - st_insert(table, (st_data_t)RSHAPE(target_shape_id)->edge_name, (st_data_t)val); + RUBY_ASSERT(RSHAPE_EDGE_NAME(target_shape_id)); + st_insert(table, (st_data_t)RSHAPE_EDGE_NAME(target_shape_id), (st_data_t)val); RB_OBJ_WRITTEN(obj, Qundef, val); } else { - attr_index_t index = RSHAPE(target_shape_id)->next_field_index - 1; - if (index >= current_shape->capacity) { - shape_resize_fields_func(obj, current_shape->capacity, RSHAPE(target_shape_id)->capacity, data); + attr_index_t index = RSHAPE_INDEX(target_shape_id); + if (index >= RSHAPE_CAPACITY(current_shape_id)) { + shape_resize_fields_func(obj, RSHAPE_CAPACITY(current_shape_id), RSHAPE_CAPACITY(target_shape_id), data); } - if (RSHAPE(target_shape_id)->next_field_index > current_shape->next_field_index) { + if (RSHAPE_LEN(target_shape_id) > RSHAPE_LEN(current_shape_id)) { set_shape_id_func(obj, target_shape_id, data); } @@ -1785,159 +1826,122 @@ general_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val, void *data, } } -struct gen_fields_lookup_ensure_size { - VALUE obj; - ID id; - struct gen_fields_tbl *fields_tbl; - shape_id_t shape_id; - bool resize; -}; - -static int -generic_fields_lookup_ensure_size(st_data_t *k, st_data_t *v, st_data_t u, int existing) +static inline void +generic_update_fields_obj(VALUE obj, VALUE fields_obj, const VALUE original_fields_obj) { - ASSERT_vm_locking(); + if (fields_obj != original_fields_obj) { + if (original_fields_obj) { + // Clear root shape to avoid triggering cleanup such as free_object_id. + rb_imemo_fields_clear(original_fields_obj); + } - struct gen_fields_lookup_ensure_size *fields_lookup = (struct gen_fields_lookup_ensure_size *)u; - struct gen_fields_tbl *fields_tbl = existing ? (struct gen_fields_tbl *)*v : NULL; + generic_fields_insert(obj, fields_obj); + } +} - if (!existing || fields_lookup->resize) { - if (existing) { - RUBY_ASSERT(RSHAPE(fields_lookup->shape_id)->type == SHAPE_IVAR || RSHAPE(fields_lookup->shape_id)->type == SHAPE_OBJ_ID); - RUBY_ASSERT(RSHAPE(RSHAPE(fields_lookup->shape_id)->parent_id)->capacity < RSHAPE(fields_lookup->shape_id)->capacity); +static VALUE +imemo_fields_set(VALUE klass, VALUE fields_obj, shape_id_t target_shape_id, ID field_name, VALUE val, bool concurrent) +{ + const VALUE original_fields_obj = fields_obj; + shape_id_t current_shape_id = fields_obj ? RBASIC_SHAPE_ID(fields_obj) : ROOT_SHAPE_ID; + + if (UNLIKELY(rb_shape_too_complex_p(target_shape_id))) { + if (rb_shape_too_complex_p(current_shape_id)) { + if (concurrent) { + // In multi-ractor case, we must always work on a copy because + // even if the field already exist, inserting in a st_table may + // cause a rebuild. + fields_obj = rb_imemo_fields_clone(fields_obj); + } } else { - FL_SET_RAW((VALUE)*k, FL_EXIVAR); + fields_obj = imemo_fields_complex_from_obj(klass, original_fields_obj, target_shape_id); + current_shape_id = target_shape_id; } - fields_tbl = gen_fields_tbl_resize(fields_tbl, RSHAPE(fields_lookup->shape_id)->capacity); - *v = (st_data_t)fields_tbl; + st_table *table = rb_imemo_fields_complex_tbl(fields_obj); + + RUBY_ASSERT(field_name); + st_insert(table, (st_data_t)field_name, (st_data_t)val); + RB_OBJ_WRITTEN(fields_obj, Qundef, val); + RBASIC_SET_SHAPE_ID(fields_obj, target_shape_id); } - - RUBY_ASSERT(FL_TEST((VALUE)*k, FL_EXIVAR)); - - fields_lookup->fields_tbl = fields_tbl; - if (fields_lookup->shape_id) { -#if SHAPE_IN_BASIC_FLAGS - rb_shape_set_shape_id(fields_lookup->obj, fields_lookup->shape_id); -#else - fields_tbl->shape_id = fields_lookup->shape_id; -#endif - } - - return ST_CONTINUE; -} - -static VALUE * -generic_ivar_set_shape_fields(VALUE obj, void *data) -{ - RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); - - struct gen_fields_lookup_ensure_size *fields_lookup = data; - - RB_VM_LOCK_ENTER(); - { - st_update(generic_fields_tbl(obj, fields_lookup->id, false), (st_data_t)obj, generic_fields_lookup_ensure_size, (st_data_t)fields_lookup); - } - RB_VM_LOCK_LEAVE(); - - FL_SET_RAW(obj, FL_EXIVAR); - - return fields_lookup->fields_tbl->as.shape.fields; -} - -static void -generic_ivar_set_shape_resize_fields(VALUE obj, attr_index_t _old_capa, attr_index_t new_capa, void *data) -{ - struct gen_fields_lookup_ensure_size *fields_lookup = data; - - fields_lookup->resize = true; -} - -static void -generic_ivar_set_set_shape_id(VALUE obj, shape_id_t shape_id, void *data) -{ - struct gen_fields_lookup_ensure_size *fields_lookup = data; - - fields_lookup->shape_id = shape_id; -} - -static void -generic_ivar_set_transition_too_complex(VALUE obj, void *_data) -{ - rb_evict_fields_to_hash(obj); - FL_SET_RAW(obj, FL_EXIVAR); -} - -static st_table * -generic_ivar_set_too_complex_table(VALUE obj, void *data) -{ - struct gen_fields_lookup_ensure_size *fields_lookup = data; - - struct gen_fields_tbl *fields_tbl; - if (!rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - fields_tbl = xmalloc(sizeof(struct gen_fields_tbl)); -#if !SHAPE_IN_BASIC_FLAGS - fields_tbl->shape_id = rb_shape_transition_complex(obj); -#endif - fields_tbl->as.complex.table = st_init_numtable_with_size(1); - - RB_VM_LOCK_ENTER(); - { - st_insert(generic_fields_tbl(obj, fields_lookup->id, false), (st_data_t)obj, (st_data_t)fields_tbl); + else { + attr_index_t index = RSHAPE_INDEX(target_shape_id); + if (concurrent || index >= RSHAPE_CAPACITY(current_shape_id)) { + fields_obj = imemo_fields_copy_capa(klass, original_fields_obj, RSHAPE_CAPACITY(target_shape_id)); } - RB_VM_LOCK_LEAVE(); - FL_SET_RAW(obj, FL_EXIVAR); + VALUE *table = rb_imemo_fields_ptr(fields_obj); + RB_OBJ_WRITE(fields_obj, &table[index], val); + + if (RSHAPE_LEN(target_shape_id) > RSHAPE_LEN(current_shape_id)) { + RBASIC_SET_SHAPE_ID(fields_obj, target_shape_id); + } } - RUBY_ASSERT(rb_shape_obj_too_complex_p(obj)); + return fields_obj; +} - return fields_tbl->as.complex.table; +static void +generic_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE val) +{ + if (!field_name) { + field_name = RSHAPE_EDGE_NAME(target_shape_id); + RUBY_ASSERT(field_name); + } + + const VALUE original_fields_obj = generic_fields_lookup(obj, field_name, false); + VALUE fields_obj = imemo_fields_set(rb_obj_class(obj), original_fields_obj, target_shape_id, field_name, val, false); + + generic_update_fields_obj(obj, fields_obj, original_fields_obj); + + if (RBASIC_SHAPE_ID(fields_obj) == target_shape_id) { + RBASIC_SET_SHAPE_ID(obj, target_shape_id); + } + + RUBY_ASSERT(RBASIC_SHAPE_ID(obj) == RBASIC_SHAPE_ID(fields_obj)); +} + +static shape_id_t +generic_shape_ivar(VALUE obj, ID id, bool *new_ivar_out) +{ + bool new_ivar = false; + shape_id_t current_shape_id = RBASIC_SHAPE_ID(obj); + shape_id_t target_shape_id = current_shape_id; + + if (!rb_shape_too_complex_p(current_shape_id)) { + if (!rb_shape_find_ivar(current_shape_id, id, &target_shape_id)) { + if (RSHAPE_LEN(current_shape_id) >= SHAPE_MAX_FIELDS) { + rb_raise(rb_eArgError, "too many instance variables"); + } + + new_ivar = true; + target_shape_id = rb_shape_transition_add_ivar(obj, id); + } + } + + *new_ivar_out = new_ivar; + return target_shape_id; } static void generic_ivar_set(VALUE obj, ID id, VALUE val) { - struct gen_fields_lookup_ensure_size fields_lookup = { - .obj = obj, - .id = id, - .resize = false, - }; - - general_ivar_set(obj, id, val, &fields_lookup, - generic_ivar_set_shape_fields, - generic_ivar_set_shape_resize_fields, - generic_ivar_set_set_shape_id, - generic_ivar_set_transition_too_complex, - generic_ivar_set_too_complex_table); -} - -static void -generic_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val) -{ - struct gen_fields_lookup_ensure_size fields_lookup = { - .obj = obj, - .resize = false, - }; - - general_field_set(obj, target_shape_id, val, &fields_lookup, - generic_ivar_set_shape_fields, - generic_ivar_set_shape_resize_fields, - generic_ivar_set_set_shape_id, - generic_ivar_set_transition_too_complex, - generic_ivar_set_too_complex_table); + bool dontcare; + shape_id_t target_shape_id = generic_shape_ivar(obj, id, &dontcare); + generic_field_set(obj, target_shape_id, id, val); } void -rb_ensure_iv_list_size(VALUE obj, uint32_t current_capacity, uint32_t new_capacity) +rb_ensure_iv_list_size(VALUE obj, uint32_t current_len, uint32_t new_capacity) { RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); if (RBASIC(obj)->flags & ROBJECT_EMBED) { VALUE *ptr = ROBJECT_FIELDS(obj); VALUE *newptr = ALLOC_N(VALUE, new_capacity); - MEMCPY(newptr, ptr, VALUE, current_capacity); + MEMCPY(newptr, ptr, VALUE, current_len); RB_FL_UNSET_RAW(obj, ROBJECT_EMBED); ROBJECT(obj)->as.heap.fields = newptr; } @@ -1984,13 +1988,13 @@ obj_ivar_set_shape_resize_fields(VALUE obj, attr_index_t old_capa, attr_index_t static void obj_ivar_set_set_shape_id(VALUE obj, shape_id_t shape_id, void *_data) { - rb_shape_set_shape_id(obj, shape_id); + rb_obj_set_shape_id(obj, shape_id); } -static void +static shape_id_t obj_ivar_set_transition_too_complex(VALUE obj, void *_data) { - rb_evict_fields_to_hash(obj); + return rb_evict_fields_to_hash(obj); } static st_table * @@ -2036,42 +2040,14 @@ rb_vm_set_ivar_id(VALUE obj, ID id, VALUE val) } bool -rb_shape_set_shape_id(VALUE obj, shape_id_t shape_id) +rb_obj_set_shape_id(VALUE obj, shape_id_t shape_id) { - if (rb_obj_shape_id(obj) == shape_id) { + shape_id_t old_shape_id = rb_obj_shape_id(obj); + if (old_shape_id == shape_id) { return false; } -#if SHAPE_IN_BASIC_FLAGS - RBASIC_SET_SHAPE_ID(obj, shape_id); -#else - switch (BUILTIN_TYPE(obj)) { - case T_OBJECT: - ROBJECT_SET_SHAPE_ID(obj, shape_id); - break; - case T_CLASS: - case T_MODULE: - RCLASS_SET_SHAPE_ID(obj, shape_id); - break; - default: - if (shape_id != SPECIAL_CONST_SHAPE_ID) { - struct gen_fields_tbl *fields_tbl = 0; - RB_VM_LOCK_ENTER(); - { - st_table* global_iv_table = generic_fields_tbl(obj, 0, false); - - if (st_lookup(global_iv_table, obj, (st_data_t *)&fields_tbl)) { - fields_tbl->shape_id = shape_id; - } - else { - rb_bug("Expected shape_id entry in global iv table"); - } - } - RB_VM_LOCK_LEAVE(); - } - } -#endif - + RB_SET_SHAPE_ID(obj, shape_id); return true; } @@ -2083,14 +2059,7 @@ void rb_obj_freeze_inline(VALUE x) RB_FL_UNSET_RAW(x, FL_USER2 | FL_USER3); // STR_CHILLED } - shape_id_t next_shape_id = rb_shape_transition_frozen(x); - - // If we're transitioning from "not complex" to "too complex" - // then evict ivars. This can happen if we run out of shapes - if (rb_shape_id_too_complex_p(next_shape_id) && !rb_shape_obj_too_complex_p(x)) { - rb_evict_fields_to_hash(x); - } - rb_shape_set_shape_id(x, next_shape_id); + RB_SET_SHAPE_ID(x, rb_shape_transition_frozen(x)); if (RBASIC_CLASS(x)) { rb_freeze_singleton_class(x); @@ -2138,10 +2107,8 @@ rb_ivar_set_internal(VALUE obj, ID id, VALUE val) ivar_set(obj, id, val); } -static void class_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val); - void -rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val) +rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE val) { switch (BUILTIN_TYPE(obj)) { case T_OBJECT: @@ -2149,28 +2116,32 @@ rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val) break; case T_CLASS: case T_MODULE: - ASSERT_vm_locking(); - class_field_set(obj, target_shape_id, val); + // The only field is object_id and T_CLASS handle it differently. + rb_bug("Unreachable"); break; default: - generic_field_set(obj, target_shape_id, val); + generic_field_set(obj, target_shape_id, field_name, val); break; } } -VALUE -rb_ivar_defined(VALUE obj, ID id) +static VALUE +ivar_defined0(VALUE obj, ID id) { attr_index_t index; - if (SPECIAL_CONST_P(obj)) return Qfalse; if (rb_shape_obj_too_complex_p(obj)) { VALUE idx; st_table *table = NULL; switch (BUILTIN_TYPE(obj)) { case T_CLASS: case T_MODULE: - table = (st_table *)RCLASS_FIELDS_HASH(obj); + rb_bug("Unreachable"); + break; + + case T_IMEMO: + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + table = rb_imemo_fields_complex_tbl(obj); break; case T_OBJECT: @@ -2178,11 +2149,10 @@ rb_ivar_defined(VALUE obj, ID id) break; default: { - struct gen_fields_tbl *fields_tbl; - if (rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - table = fields_tbl->as.complex.table; + VALUE fields_obj; + if (rb_gen_fields_tbl_get(obj, 0, &fields_obj)) { + table = rb_imemo_fields_complex_tbl(fields_obj); } - break; } } @@ -2193,74 +2163,78 @@ rb_ivar_defined(VALUE obj, ID id) return Qtrue; } else { - return RBOOL(rb_shape_get_iv_index(rb_obj_shape(obj), id, &index)); + return RBOOL(rb_shape_get_iv_index(RBASIC_SHAPE_ID(obj), id, &index)); } } +VALUE +rb_ivar_defined(VALUE obj, ID id) +{ + if (SPECIAL_CONST_P(obj)) return Qfalse; + + VALUE defined = Qfalse; + switch (BUILTIN_TYPE(obj)) { + case T_CLASS: + case T_MODULE: + { + VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + if (fields_obj) { + defined = ivar_defined0(fields_obj, id); + } + } + break; + default: + defined = ivar_defined0(obj, id); + break; + } + return defined; +} + struct iv_itr_data { VALUE obj; struct gen_fields_tbl *fields_tbl; st_data_t arg; rb_ivar_foreach_callback_func *func; + VALUE *fields; bool ivar_only; }; +static int +iterate_over_shapes_callback(shape_id_t shape_id, void *data) +{ + struct iv_itr_data *itr_data = data; + + if (itr_data->ivar_only && !RSHAPE_TYPE_P(shape_id, SHAPE_IVAR)) { + return ST_CONTINUE; + } + + VALUE *fields; + switch (BUILTIN_TYPE(itr_data->obj)) { + case T_OBJECT: + RUBY_ASSERT(!rb_shape_obj_too_complex_p(itr_data->obj)); + fields = ROBJECT_FIELDS(itr_data->obj); + break; + case T_IMEMO: + RUBY_ASSERT(IMEMO_TYPE_P(itr_data->obj, imemo_fields)); + RUBY_ASSERT(!rb_shape_obj_too_complex_p(itr_data->obj)); + + fields = rb_imemo_fields_ptr(itr_data->obj); + break; + default: + rb_bug("Unreachable"); + } + + VALUE val = fields[RSHAPE_INDEX(shape_id)]; + return itr_data->func(RSHAPE_EDGE_NAME(shape_id), val, itr_data->arg); +} + /* * Returns a flag to stop iterating depending on the result of +callback+. */ -static bool -iterate_over_shapes_with_callback(rb_shape_t *shape, rb_ivar_foreach_callback_func *callback, struct iv_itr_data *itr_data) +static void +iterate_over_shapes(shape_id_t shape_id, rb_ivar_foreach_callback_func *callback, struct iv_itr_data *itr_data) { - switch ((enum shape_type)shape->type) { - case SHAPE_ROOT: - case SHAPE_T_OBJECT: - return false; - case SHAPE_OBJ_ID: - if (itr_data->ivar_only) { - return iterate_over_shapes_with_callback(RSHAPE(shape->parent_id), callback, itr_data); - } - // fallthrough - case SHAPE_IVAR: - ASSUME(callback); - if (iterate_over_shapes_with_callback(RSHAPE(shape->parent_id), callback, itr_data)) { - return true; - } - - VALUE * iv_list; - switch (BUILTIN_TYPE(itr_data->obj)) { - case T_OBJECT: - RUBY_ASSERT(!rb_shape_obj_too_complex_p(itr_data->obj)); - iv_list = ROBJECT_FIELDS(itr_data->obj); - break; - case T_CLASS: - case T_MODULE: - RUBY_ASSERT(!rb_shape_obj_too_complex_p(itr_data->obj)); - iv_list = RCLASS_PRIME_FIELDS(itr_data->obj); - break; - default: - iv_list = itr_data->fields_tbl->as.shape.fields; - break; - } - VALUE val = iv_list[shape->next_field_index - 1]; - if (!UNDEF_P(val)) { - switch (callback(shape->edge_name, val, itr_data->arg)) { - case ST_CHECK: - case ST_CONTINUE: - break; - case ST_STOP: - return true; - default: - rb_bug("unreachable"); - } - } - return false; - case SHAPE_FROZEN: - return iterate_over_shapes_with_callback(RSHAPE(shape->parent_id), callback, itr_data); - case SHAPE_OBJ_TOO_COMPLEX: - default: - rb_bug("Unreachable"); - UNREACHABLE_RETURN(false); - } + rb_shape_foreach_field(shape_id, iterate_over_shapes_callback, itr_data); } static int @@ -2281,187 +2255,116 @@ obj_fields_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, b .ivar_only = ivar_only, }; - rb_shape_t *shape = rb_obj_shape(obj); - if (rb_shape_too_complex_p(shape)) { + shape_id_t shape_id = RBASIC_SHAPE_ID(obj); + if (rb_shape_too_complex_p(shape_id)) { rb_st_foreach(ROBJECT_FIELDS_HASH(obj), each_hash_iv, (st_data_t)&itr_data); } else { - iterate_over_shapes_with_callback(shape, func, &itr_data); + itr_data.fields = ROBJECT_FIELDS(obj); + iterate_over_shapes(shape_id, func, &itr_data); } } static void -gen_fields_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, bool ivar_only) +imemo_fields_each(VALUE fields_obj, rb_ivar_foreach_callback_func *func, st_data_t arg, bool ivar_only) { - rb_shape_t *shape = rb_obj_shape(obj); - struct gen_fields_tbl *fields_tbl; - if (!rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) return; + IMEMO_TYPE_P(fields_obj, imemo_fields); struct iv_itr_data itr_data = { - .obj = obj, - .fields_tbl = fields_tbl, + .obj = fields_obj, .arg = arg, .func = func, .ivar_only = ivar_only, }; - if (rb_shape_obj_too_complex_p(obj)) { - rb_st_foreach(fields_tbl->as.complex.table, each_hash_iv, (st_data_t)&itr_data); + shape_id_t shape_id = RBASIC_SHAPE_ID(fields_obj); + if (rb_shape_too_complex_p(shape_id)) { + rb_st_foreach(rb_imemo_fields_complex_tbl(fields_obj), each_hash_iv, (st_data_t)&itr_data); } else { - iterate_over_shapes_with_callback(shape, func, &itr_data); - } -} - -static void -class_fields_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, bool ivar_only) -{ - RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)); - - rb_shape_t *shape = rb_obj_shape(obj); - struct iv_itr_data itr_data = { - .obj = obj, - .arg = arg, - .func = func, - .ivar_only = ivar_only, - }; - - if (rb_shape_obj_too_complex_p(obj)) { - rb_st_foreach(RCLASS_WRITABLE_FIELDS_HASH(obj), each_hash_iv, (st_data_t)&itr_data); - } - else { - iterate_over_shapes_with_callback(shape, func, &itr_data); + itr_data.fields = rb_imemo_fields_ptr(fields_obj); + iterate_over_shapes(shape_id, func, &itr_data); } } void rb_copy_generic_ivar(VALUE dest, VALUE obj) { - struct gen_fields_tbl *obj_fields_tbl; - struct gen_fields_tbl *new_fields_tbl; + VALUE fields_obj; + VALUE new_fields_obj; rb_check_frozen(dest); - if (!FL_TEST(obj, FL_EXIVAR)) { - goto clear; + if (!rb_obj_exivar_p(obj)) { + return; } - unsigned long src_num_ivs = rb_ivar_count(obj); - if (!src_num_ivs) { - goto clear; - } + shape_id_t src_shape_id = rb_obj_shape_id(obj); - rb_shape_t *src_shape = rb_obj_shape(obj); - - if (rb_gen_fields_tbl_get(obj, 0, &obj_fields_tbl)) { - if (gen_fields_tbl_count(obj, obj_fields_tbl) == 0) + if (rb_gen_fields_tbl_get(obj, 0, &fields_obj)) { + unsigned long src_num_ivs = rb_ivar_count(fields_obj); + if (!src_num_ivs) { goto clear; + } - FL_SET(dest, FL_EXIVAR); - - if (rb_shape_too_complex_p(src_shape)) { - // obj is TOO_COMPLEX so we can copy its iv_hash - st_table *table = st_copy(obj_fields_tbl->as.complex.table); - if (rb_shape_has_object_id(src_shape)) { - st_data_t id = (st_data_t)ruby_internal_object_id; - st_delete(table, &id, NULL); - } - rb_obj_init_too_complex(dest, table); - + if (rb_shape_too_complex_p(src_shape_id)) { + rb_shape_copy_complex_ivars(dest, obj, src_shape_id, rb_imemo_fields_complex_tbl(fields_obj)); return; } - rb_shape_t *shape_to_set_on_dest = src_shape; - rb_shape_t *initial_shape = rb_obj_shape(dest); + shape_id_t dest_shape_id = src_shape_id; + shape_id_t initial_shape_id = rb_obj_shape_id(dest); - if (!rb_shape_canonical_p(src_shape)) { - RUBY_ASSERT(initial_shape->type == SHAPE_ROOT); + if (!rb_shape_canonical_p(src_shape_id)) { + RUBY_ASSERT(RSHAPE_TYPE_P(initial_shape_id, SHAPE_ROOT)); - shape_to_set_on_dest = rb_shape_rebuild_shape(initial_shape, src_shape); - if (UNLIKELY(rb_shape_too_complex_p(shape_to_set_on_dest))) { + dest_shape_id = rb_shape_rebuild(initial_shape_id, src_shape_id); + if (UNLIKELY(rb_shape_too_complex_p(dest_shape_id))) { st_table *table = rb_st_init_numtable_with_size(src_num_ivs); rb_obj_copy_ivs_to_hash_table(obj, table); rb_obj_init_too_complex(dest, table); - return; } } - if (!shape_to_set_on_dest->capacity) { - rb_shape_set_shape(dest, shape_to_set_on_dest); - FL_UNSET(dest, FL_EXIVAR); + if (!RSHAPE_LEN(dest_shape_id)) { + rb_obj_set_shape_id(dest, dest_shape_id); return; } - new_fields_tbl = gen_fields_tbl_resize(0, shape_to_set_on_dest->capacity); + new_fields_obj = rb_imemo_fields_new(rb_obj_class(dest), RSHAPE_CAPACITY(dest_shape_id)); + VALUE *src_buf = rb_imemo_fields_ptr(fields_obj); + VALUE *dest_buf = rb_imemo_fields_ptr(new_fields_obj); + rb_shape_copy_fields(new_fields_obj, dest_buf, dest_shape_id, src_buf, src_shape_id); + RBASIC_SET_SHAPE_ID(new_fields_obj, dest_shape_id); - VALUE *src_buf = obj_fields_tbl->as.shape.fields; - VALUE *dest_buf = new_fields_tbl->as.shape.fields; - - if (src_shape->next_field_index == shape_to_set_on_dest->next_field_index) { - // Happy path, we can just memcpy the ivptr content - MEMCPY(dest_buf, src_buf, VALUE, shape_to_set_on_dest->next_field_index); - - // Fire write barriers - for (uint32_t i = 0; i < shape_to_set_on_dest->next_field_index; i++) { - RB_OBJ_WRITTEN(dest, Qundef, dest_buf[i]); - } - } - else { - rb_shape_t *dest_shape = shape_to_set_on_dest; - while (src_shape->parent_id != INVALID_SHAPE_ID) { - if (src_shape->type == SHAPE_IVAR) { - while (dest_shape->edge_name != src_shape->edge_name) { - dest_shape = RSHAPE(dest_shape->parent_id); - } - - RB_OBJ_WRITE(dest, &dest_buf[dest_shape->next_field_index - 1], src_buf[src_shape->next_field_index - 1]); - } - src_shape = RSHAPE(src_shape->parent_id); - } - } - - /* - * c.fields_tbl may change in gen_fields_copy due to realloc, - * no need to free - */ - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { generic_fields_tbl_no_ractor_check(dest); - st_insert(generic_fields_tbl_no_ractor_check(obj), (st_data_t)dest, (st_data_t)new_fields_tbl); + st_insert(generic_fields_tbl_no_ractor_check(obj), (st_data_t)dest, (st_data_t)new_fields_obj); + RB_OBJ_WRITTEN(dest, Qundef, new_fields_obj); } - RB_VM_LOCK_LEAVE(); - rb_shape_set_shape(dest, shape_to_set_on_dest); + RBASIC_SET_SHAPE_ID(dest, dest_shape_id); } return; clear: - if (FL_TEST(dest, FL_EXIVAR)) { - rb_free_generic_ivar(dest); - FL_UNSET(dest, FL_EXIVAR); - } + rb_free_generic_ivar(dest); } void rb_replace_generic_ivar(VALUE clone, VALUE obj) { - RUBY_ASSERT(FL_TEST(obj, FL_EXIVAR)); - - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { st_data_t fields_tbl, obj_data = (st_data_t)obj; if (st_delete(generic_fields_tbl_, &obj_data, &fields_tbl)) { - FL_UNSET_RAW(obj, FL_EXIVAR); - st_insert(generic_fields_tbl_, (st_data_t)clone, fields_tbl); - FL_SET_RAW(clone, FL_EXIVAR); + RB_OBJ_WRITTEN(clone, Qundef, fields_tbl); } else { rb_bug("unreachable"); } } - RB_VM_LOCK_LEAVE(); } void @@ -2469,21 +2372,30 @@ rb_field_foreach(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, { if (SPECIAL_CONST_P(obj)) return; switch (BUILTIN_TYPE(obj)) { + case T_IMEMO: + if (IMEMO_TYPE_P(obj, imemo_fields)) { + imemo_fields_each(obj, func, arg, ivar_only); + } + break; case T_OBJECT: obj_fields_each(obj, func, arg, ivar_only); break; case T_CLASS: case T_MODULE: - IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(0); - RB_VM_LOCK_ENTER(); { - class_fields_each(obj, func, arg, ivar_only); + IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(0); + VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + if (fields_obj) { + imemo_fields_each(fields_obj, func, arg, ivar_only); + } } - RB_VM_LOCK_LEAVE(); break; default: - if (FL_TEST(obj, FL_EXIVAR)) { - gen_fields_each(obj, func, arg, ivar_only); + if (rb_obj_exivar_p(obj)) { + VALUE fields_obj = 0; + if (!rb_gen_fields_tbl_get(obj, 0, &fields_obj)) return; + + imemo_fields_each(fields_obj, func, arg, ivar_only); } break; } @@ -2505,16 +2417,46 @@ rb_ivar_count(VALUE obj) case T_OBJECT: iv_count = ROBJECT_FIELDS_COUNT(obj); break; + case T_CLASS: case T_MODULE: - iv_count = RCLASS_FIELDS_COUNT(obj); + { + VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + if (!fields_obj) { + return 0; + } + if (rb_shape_obj_too_complex_p(fields_obj)) { + iv_count = rb_st_table_size(rb_imemo_fields_complex_tbl(fields_obj)); + } + else { + iv_count = RBASIC_FIELDS_COUNT(fields_obj); + } + } break; - default: - if (FL_TEST(obj, FL_EXIVAR)) { - struct gen_fields_tbl *fields_tbl; - if (rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - iv_count = gen_fields_tbl_count(obj, fields_tbl); + case T_IMEMO: + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + + if (rb_shape_obj_too_complex_p(obj)) { + iv_count = rb_st_table_size(rb_imemo_fields_complex_tbl(obj)); + } + else { + iv_count = RBASIC_FIELDS_COUNT(obj); + } + break; + + default: + if (rb_obj_exivar_p(obj)) { + + if (rb_shape_obj_too_complex_p(obj)) { + VALUE fields_obj; + + if (rb_gen_fields_tbl_get(obj, 0, &fields_obj)) { + iv_count = rb_st_table_size(rb_imemo_fields_complex_tbl(fields_obj)); + } + } + else { + iv_count = RBASIC_FIELDS_COUNT(obj); } } break; @@ -3288,11 +3230,9 @@ autoload_const_set(struct autoload_const *ac) { check_before_mod_set(ac->module, ac->name, ac->value, "constant"); - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { const_tbl_update(ac, true); } - RB_VM_LOCK_LEAVE(); return 0; /* ignored */ } @@ -3837,12 +3777,10 @@ rb_local_constants(VALUE mod) if (!tbl) return rb_ary_new2(0); - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { ary = rb_ary_new2(rb_id_table_size(tbl)); rb_id_table_foreach(tbl, rb_local_constants_i, (void *)ary); } - RB_VM_LOCK_LEAVE(); return ary; } @@ -3855,11 +3793,9 @@ rb_mod_const_at(VALUE mod, void *data) tbl = st_init_numtable(); } if (RCLASS_CONST_TBL(mod)) { - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { rb_id_table_foreach(RCLASS_CONST_TBL(mod), sv_i, tbl); } - RB_VM_LOCK_LEAVE(); } return tbl; } @@ -4034,15 +3970,13 @@ set_namespace_path(VALUE named_namespace, VALUE namespace_path) { struct rb_id_table *const_table = RCLASS_CONST_TBL(named_namespace); - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { RCLASS_WRITE_CLASSPATH(named_namespace, namespace_path, true); if (const_table) { rb_id_table_foreach(const_table, set_namespace_path_i, &namespace_path); } } - RB_VM_LOCK_LEAVE(); } static void @@ -4070,8 +4004,7 @@ const_set(VALUE klass, ID id, VALUE val) check_before_mod_set(klass, id, val, "constant"); - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { struct rb_id_table *tbl = RCLASS_WRITABLE_CONST_TBL(klass); if (!tbl) { tbl = rb_id_table_create(0); @@ -4091,7 +4024,6 @@ const_set(VALUE klass, ID id, VALUE val) const_tbl_update(&ac, false); } } - RB_VM_LOCK_LEAVE(); /* * Resolve and cache class name immediately to resolve ambiguity @@ -4271,7 +4203,7 @@ set_const_visibility(VALUE mod, int argc, const VALUE *argv, ac->flag |= flag; } } - rb_clear_constant_cache_for_id(id); + rb_clear_constant_cache_for_id(id); } else { undefined_constant(mod, ID2SYM(id)); @@ -4465,7 +4397,7 @@ rb_cvar_set(VALUE klass, ID id, VALUE val) } check_before_mod_set(target, id, val, "class variable"); - int result = rb_class_ivar_set(target, id, val); + bool new_cvar = rb_class_ivar_set(target, id, val); struct rb_id_table *rb_cvc_tbl = RCLASS_WRITABLE_CVC_TBL(target); @@ -4493,7 +4425,7 @@ rb_cvar_set(VALUE klass, ID id, VALUE val) // Break the cvar cache if this is a new class variable // and target is a module or a subclass with the same // cvar in this lookup. - if (result == 0) { + if (new_cvar) { if (RB_TYPE_P(target, T_CLASS)) { if (RCLASS_SUBCLASSES_FIRST(target)) { rb_class_foreach_subclass(target, check_for_cvar_table, id); @@ -4724,81 +4656,115 @@ rb_iv_set(VALUE obj, const char *name, VALUE val) return rb_ivar_set(obj, id, val); } -static VALUE * -class_ivar_set_shape_fields(VALUE obj, void *_data) +static bool +class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool concurrent, VALUE *new_fields_obj) { - RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); + bool existing = true; + const VALUE original_fields_obj = fields_obj; + fields_obj = original_fields_obj ? original_fields_obj : rb_imemo_fields_new(rb_singleton_class(klass), 1); - return RCLASS_PRIME_FIELDS(obj); + shape_id_t current_shape_id = RBASIC_SHAPE_ID(fields_obj); + shape_id_t next_shape_id = current_shape_id; + + if (UNLIKELY(rb_shape_too_complex_p(current_shape_id))) { + goto too_complex; + } + + attr_index_t index; + if (!rb_shape_get_iv_index(current_shape_id, id, &index)) { + existing = false; + + index = RSHAPE_LEN(current_shape_id); + if (index >= SHAPE_MAX_FIELDS) { + rb_raise(rb_eArgError, "too many instance variables"); + } + + next_shape_id = rb_shape_transition_add_ivar(fields_obj, id); + if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) { + fields_obj = imemo_fields_complex_from_obj(rb_singleton_class(klass), fields_obj, next_shape_id); + goto too_complex; + } + + attr_index_t next_capacity = RSHAPE_CAPACITY(next_shape_id); + attr_index_t current_capacity = RSHAPE_CAPACITY(current_shape_id); + + if (next_capacity > current_capacity) { + // We allocate a new fields_obj even when concurrency isn't a concern + // so that we're embedded as long as possible. + fields_obj = imemo_fields_copy_capa(rb_singleton_class(klass), fields_obj, next_capacity); + } + + RUBY_ASSERT(RSHAPE(next_shape_id)->type == SHAPE_IVAR); + RUBY_ASSERT(index == (RSHAPE_LEN(next_shape_id) - 1)); + } + + VALUE *fields = rb_imemo_fields_ptr(fields_obj); + + if (concurrent && original_fields_obj == fields_obj) { + // In the concurrent case, if we're mutating the existing + // fields_obj, we must use an atomic write, because if we're + // adding a new field, the shape_id must be written after the field + // and if we're updating an existing field, we at least need a relaxed + // write to avoid reaping. + RB_OBJ_ATOMIC_WRITE(fields_obj, &fields[index], val); + } + else { + RB_OBJ_WRITE(fields_obj, &fields[index], val); + } + + if (!existing) { + RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); + } + + *new_fields_obj = fields_obj; + return existing; + +too_complex: + { + if (concurrent && fields_obj == original_fields_obj) { + // In multi-ractor case, we must always work on a copy because + // even if the field already exist, inserting in a st_table may + // cause a rebuild. + fields_obj = rb_imemo_fields_clone(fields_obj); + } + + st_table *table = rb_imemo_fields_complex_tbl(fields_obj); + existing = st_insert(table, (st_data_t)id, (st_data_t)val); + RB_OBJ_WRITTEN(fields_obj, Qundef, val); + + if (fields_obj != original_fields_obj) { + RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); + } + } + + *new_fields_obj = fields_obj; + return existing; } -static void -class_ivar_set_shape_resize_fields(VALUE obj, attr_index_t _old_capa, attr_index_t new_capa, void *_data) -{ - REALLOC_N(RCLASS_PRIME_FIELDS(obj), VALUE, new_capa); -} - -static void -class_ivar_set_set_shape_id(VALUE obj, shape_id_t shape_id, void *_data) -{ - rb_shape_set_shape_id(obj, shape_id); -} - -static void -class_ivar_set_transition_too_complex(VALUE obj, void *_data) -{ - rb_evict_fields_to_hash(obj); -} - -static st_table * -class_ivar_set_too_complex_table(VALUE obj, void *_data) -{ - RUBY_ASSERT(rb_shape_obj_too_complex_p(obj)); - - return RCLASS_WRITABLE_FIELDS_HASH(obj); -} - -int +bool rb_class_ivar_set(VALUE obj, ID id, VALUE val) { RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)); - bool existing = false; rb_check_frozen(obj); rb_class_ensure_writable(obj); - RB_VM_LOCK_ENTER(); - { - existing = general_ivar_set(obj, id, val, NULL, - class_ivar_set_shape_fields, - class_ivar_set_shape_resize_fields, - class_ivar_set_set_shape_id, - class_ivar_set_transition_too_complex, - class_ivar_set_too_complex_table).existing; + const VALUE original_fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + VALUE new_fields_obj = 0; + + bool existing = class_fields_ivar_set(obj, original_fields_obj, id, val, rb_multi_ractor_p(), &new_fields_obj); + + if (new_fields_obj != original_fields_obj) { + RCLASS_WRITABLE_SET_FIELDS_OBJ(obj, new_fields_obj); } - RB_VM_LOCK_LEAVE(); - return existing; -} + // TODO: What should we set as the T_CLASS shape_id? + // In most case we can replicate the single `fields_obj` shape + // but in namespaced case? + // Perhaps INVALID_SHAPE_ID? + RBASIC_SET_SHAPE_ID(obj, RBASIC_SHAPE_ID(new_fields_obj)); -static void -class_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val) -{ - RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)); - general_field_set(obj, target_shape_id, val, NULL, - class_ivar_set_shape_fields, - class_ivar_set_shape_resize_fields, - class_ivar_set_set_shape_id, - class_ivar_set_transition_too_complex, - class_ivar_set_too_complex_table); -} - -static int -tbl_copy_i(ID key, VALUE val, st_data_t dest) -{ - rb_class_ivar_set((VALUE)dest, key, val); - - return ST_CONTINUE; + return !existing; } void @@ -4806,11 +4772,13 @@ rb_fields_tbl_copy(VALUE dst, VALUE src) { RUBY_ASSERT(rb_type(dst) == rb_type(src)); RUBY_ASSERT(RB_TYPE_P(dst, T_CLASS) || RB_TYPE_P(dst, T_MODULE)); + RUBY_ASSERT(RSHAPE_TYPE_P(RBASIC_SHAPE_ID(dst), SHAPE_ROOT)); - RUBY_ASSERT(rb_obj_shape(dst)->type == SHAPE_ROOT); - RUBY_ASSERT(!RCLASS_PRIME_FIELDS(dst)); - - rb_ivar_foreach(src, tbl_copy_i, dst); + VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(src); + if (fields_obj) { + RCLASS_WRITABLE_SET_FIELDS_OBJ(dst, rb_imemo_fields_clone(fields_obj)); + RBASIC_SET_SHAPE_ID(dst, RBASIC_SHAPE_ID(src)); + } } static rb_const_entry_t * @@ -4819,11 +4787,9 @@ const_lookup(struct rb_id_table *tbl, ID id) if (tbl) { VALUE val; bool r; - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { r = rb_id_table_lookup(tbl, id, &val); } - RB_VM_LOCK_LEAVE(); if (r) return (rb_const_entry_t *)val; } @@ -4835,3 +4801,4 @@ rb_const_lookup(VALUE klass, ID id) { return const_lookup(RCLASS_CONST_TBL(klass), id); } + diff --git a/variable.h b/variable.h index ca3ed08c8d..82a79c63ce 100644 --- a/variable.h +++ b/variable.h @@ -12,26 +12,8 @@ #include "shape.h" -struct gen_fields_tbl { -#if !SHAPE_IN_BASIC_FLAGS - uint16_t shape_id; -#endif - union { - struct { - uint32_t fields_count; - VALUE fields[1]; - } shape; - struct { - st_table *table; - } complex; - } as; -}; - -int rb_ivar_generic_fields_tbl_lookup(VALUE obj, struct gen_fields_tbl **); - -#if !SHAPE_IN_BASIC_FLAGS -shape_id_t rb_generic_shape_id(VALUE obj); -#endif +int rb_ivar_generic_fields_tbl_lookup(VALUE obj, VALUE *); +void rb_copy_complex_ivars(VALUE dest, VALUE obj, shape_id_t src_shape_id, st_table *fields_table); void rb_free_rb_global_tbl(void); void rb_free_generic_fields_tbl_(void); diff --git a/vm.c b/vm.c index 6613218ee7..86395df340 100644 --- a/vm.c +++ b/vm.c @@ -233,13 +233,9 @@ vm_cref_new0(VALUE klass, rb_method_visibility_t visi, int module_func, rb_cref_ int omod_shared = FALSE; /* scope */ - union { - rb_scope_visibility_t visi; - VALUE value; - } scope_visi; - - scope_visi.visi.method_visi = visi; - scope_visi.visi.module_func = module_func; + rb_scope_visibility_t scope_visi; + scope_visi.method_visi = visi; + scope_visi.module_func = module_func; /* refinements */ if (prev_cref != NULL && prev_cref != (void *)1 /* TODO: why CREF_NEXT(cref) is 1? */) { @@ -256,7 +252,7 @@ vm_cref_new0(VALUE klass, rb_method_visibility_t visi, int module_func, rb_cref_ rb_cref_t *cref = IMEMO_NEW(rb_cref_t, imemo_cref, refinements); cref->klass_or_self = klass; cref->next = use_prev_prev ? CREF_NEXT(prev_cref) : prev_cref; - *((rb_scope_visibility_t *)&cref->scope_visi) = scope_visi.visi; + *((rb_scope_visibility_t *)&cref->scope_visi) = scope_visi; if (pushed_by_eval) CREF_PUSHED_BY_EVAL_SET(cref); if (omod_shared) CREF_OMOD_SHARED_SET(cref); @@ -452,7 +448,9 @@ jit_compile(rb_execution_context_t *ec) rb_zjit_compile_iseq(iseq, ec, false); } } -#elif USE_YJIT +#endif + +#if USE_YJIT // Increment the ISEQ's call counter and trigger JIT compilation if not compiled if (body->jit_entry == NULL && rb_yjit_enabled_p) { body->jit_entry_calls++; @@ -734,8 +732,8 @@ vm_stat(int argc, VALUE *argv, VALUE self) SET(constant_cache_invalidations, ruby_vm_constant_cache_invalidations); SET(constant_cache_misses, ruby_vm_constant_cache_misses); SET(global_cvar_state, ruby_vm_global_cvar_state); - SET(next_shape_id, (rb_serial_t)GET_SHAPE_TREE()->next_shape_id); - SET(shape_cache_size, (rb_serial_t)GET_SHAPE_TREE()->cache_size); + SET(next_shape_id, (rb_serial_t)rb_shapes_count()); + SET(shape_cache_size, (rb_serial_t)rb_shape_tree.cache_size); #undef SET #if USE_DEBUG_COUNTER @@ -2980,6 +2978,7 @@ rb_vm_update_references(void *ptr) if (ptr) { rb_vm_t *vm = ptr; + vm->self = rb_gc_location(vm->self); vm->mark_object_ary = rb_gc_location(vm->mark_object_ary); vm->load_path = rb_gc_location(vm->load_path); vm->load_path_snapshot = rb_gc_location(vm->load_path_snapshot); @@ -3066,6 +3065,8 @@ rb_vm_mark(void *ptr) rb_gc_mark_maybe(*list->varptr); } + rb_gc_mark_movable(vm->self); + if (vm->main_namespace) { rb_namespace_entry_mark((void *)vm->main_namespace); } @@ -3160,8 +3161,6 @@ ruby_vm_destruct(rb_vm_t *vm) // TODO: Is this ignorable for classext->m_tbl ? // rb_id_table_free(RCLASS(rb_mRubyVMFrozenCore)->m_tbl); - rb_shape_free_all(); - st_free_table(vm->static_ext_inits); rb_vm_postponed_job_free(); @@ -3194,6 +3193,10 @@ ruby_vm_destruct(rb_vm_t *vm) st_free_table(vm->ci_table); vm->ci_table = NULL; } + if (vm->cc_refinement_table) { + rb_set_free_table(vm->cc_refinement_table); + vm->cc_refinement_table = NULL; + } RB_ALTSTACK_FREE(vm->main_altstack); struct global_object_list *next; @@ -3220,11 +3223,12 @@ ruby_vm_destruct(rb_vm_t *vm) ruby_mimfree(vm); ruby_current_vm_ptr = NULL; -#if USE_YJIT if (rb_free_at_exit) { + rb_shape_free_all(); +#if USE_YJIT rb_yjit_free_at_exit(); - } #endif + } } RUBY_FREE_LEAVE("vm"); return 0; @@ -3293,8 +3297,8 @@ vm_memsize(const void *ptr) vm_memsize_builtin_function_table(vm->builtin_function_table) + rb_id_table_memsize(vm->negative_cme_table) + rb_st_memsize(vm->overloaded_cme_table) + - vm_memsize_constant_cache() + - GET_SHAPE_TREE()->cache_size * sizeof(redblack_node_t) + rb_set_memsize(vm->cc_refinement_table) + + vm_memsize_constant_cache() ); // TODO @@ -3556,7 +3560,6 @@ thread_mark(void *ptr) rb_gc_mark(th->last_status); rb_gc_mark(th->locking_mutex); rb_gc_mark(th->name); - rb_gc_mark(th->ractor_waiting.receiving_mutex); rb_gc_mark(th->scheduler); @@ -3668,10 +3671,10 @@ rb_ec_initialize_vm_stack(rb_execution_context_t *ec, VALUE *stack, size_t size) void rb_ec_clear_vm_stack(rb_execution_context_t *ec) { - rb_ec_set_vm_stack(ec, NULL, 0); - - // Avoid dangling pointers: + // set cfp to NULL before clearing the stack in case `thread_profile_frames` + // gets called in this middle of `rb_ec_set_vm_stack` via signal handler. ec->cfp = NULL; + rb_ec_set_vm_stack(ec, NULL, 0); } static void @@ -3718,10 +3721,6 @@ th_init(rb_thread_t *th, VALUE self, rb_vm_t *vm) th->ext_config.ractor_safe = true; ccan_list_head_init(&th->interrupt_exec_tasks); - ccan_list_node_init(&th->ractor_waiting.waiting_node); -#ifndef RUBY_THREAD_PTHREAD_H - rb_native_cond_initialize(&th->ractor_waiting.cond); -#endif #if USE_RUBY_DEBUG_LOG static rb_atomic_t thread_serial = 1; @@ -4016,8 +4015,6 @@ Init_VM(void) fcore = rb_class_new(rb_cBasicObject); rb_set_class_path(fcore, rb_cRubyVM, "FrozenCore"); rb_vm_register_global_object(rb_class_path_cached(fcore)); - RB_FL_UNSET_RAW(fcore, T_MASK); - RB_FL_SET_RAW(fcore, T_ICLASS); klass = rb_singleton_class(fcore); rb_define_method_id(klass, id_core_set_method_alias, m_core_set_method_alias, 3); rb_define_method_id(klass, id_core_set_variable_alias, m_core_set_variable_alias, 2); @@ -4379,7 +4376,8 @@ Init_BareVM(void) vm_opt_mid_table = st_init_numtable(); #ifdef RUBY_THREAD_WIN32_H - rb_native_cond_initialize(&vm->ractor.sync.barrier_cond); + rb_native_cond_initialize(&vm->ractor.sync.barrier_complete_cond); + rb_native_cond_initialize(&vm->ractor.sync.barrier_release_cond); #endif } @@ -4490,8 +4488,7 @@ rb_vm_register_global_object(VALUE obj) default: break; } - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { VALUE list = GET_VM()->mark_object_ary; VALUE head = pin_array_list_append(list, obj); if (head != list) { @@ -4499,7 +4496,6 @@ rb_vm_register_global_object(VALUE obj) } RB_GC_GUARD(obj); } - RB_VM_LOCK_LEAVE(); } void @@ -4511,6 +4507,7 @@ Init_vm_objects(void) vm->mark_object_ary = pin_array_list_new(Qnil); vm->loading_table = st_init_strtable(); vm->ci_table = st_init_table(&vm_ci_hashtype); + vm->cc_refinement_table = rb_set_init_numtable(); } // Stub for builtin function when not building YJIT units diff --git a/vm_args.c b/vm_args.c index 4738eda72c..44be6f54c5 100644 --- a/vm_args.c +++ b/vm_args.c @@ -8,9 +8,9 @@ **********************************************************************/ -NORETURN(static void raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const VALUE exc)); -NORETURN(static void argument_arity_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const int miss_argc, const int min_argc, const int max_argc)); -NORETURN(static void argument_kw_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const char *error, const VALUE keys)); +NORETURN(static void raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const VALUE exc)); +NORETURN(static void argument_arity_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const int miss_argc, const int min_argc, const int max_argc)); +NORETURN(static void argument_kw_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const char *error, const VALUE keys)); VALUE rb_keyword_error_new(const char *error, VALUE keys); /* class.c */ static VALUE method_missing(rb_execution_context_t *ec, VALUE obj, ID id, int argc, const VALUE *argv, enum method_missing_reason call_status, int kw_splat); @@ -321,7 +321,7 @@ args_setup_kw_parameters_lookup(const ID key, VALUE *ptr, const VALUE *const pas #define KW_SPECIFIED_BITS_MAX (32-1) /* TODO: 32 -> Fixnum's max bits */ static void -args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *const iseq, +args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *const iseq, const rb_callable_method_entry_t *cme, VALUE *const passed_values, const int passed_keyword_len, const VALUE *const passed_keywords, VALUE *const locals) { @@ -345,7 +345,7 @@ args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *cons } } - if (missing) argument_kw_error(ec, iseq, "missing", missing); + if (missing) argument_kw_error(ec, iseq, cme, "missing", missing); for (di=0; iparam.keyword->table; @@ -430,7 +430,7 @@ args_setup_kw_parameters_from_kwsplat(rb_execution_context_t *const ec, const rb } } - if (missing) argument_kw_error(ec, iseq, "missing", missing); + if (missing) argument_kw_error(ec, iseq, cme, "missing", missing); for (di=0; icc ? vm_cc_cme(calling->cc) : NULL; vm_check_canary(ec, orig_sp); /* @@ -861,7 +862,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co args_extend(args, min_argc); } else { - argument_arity_error(ec, iseq, given_argc, min_argc, max_argc); + argument_arity_error(ec, iseq, cme, given_argc, min_argc, max_argc); } } @@ -872,7 +873,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co given_argc = max_argc; } else { - argument_arity_error(ec, iseq, given_argc, min_argc, max_argc); + argument_arity_error(ec, iseq, cme, given_argc, min_argc, max_argc); } } @@ -918,7 +919,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co if (args->kw_argv != NULL) { const struct rb_callinfo_kwarg *kw_arg = args->kw_arg; - args_setup_kw_parameters(ec, iseq, args->kw_argv, kw_arg->keyword_len, kw_arg->keywords, klocals); + args_setup_kw_parameters(ec, iseq, cme, args->kw_argv, kw_arg->keyword_len, kw_arg->keywords, klocals); } else if (!NIL_P(keyword_hash)) { bool remove_hash_value = false; @@ -926,7 +927,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co keyword_hash = check_kwrestarg(keyword_hash, &kw_flag); remove_hash_value = true; } - args_setup_kw_parameters_from_kwsplat(ec, iseq, keyword_hash, klocals, remove_hash_value); + args_setup_kw_parameters_from_kwsplat(ec, iseq, cme, keyword_hash, klocals, remove_hash_value); } else { #if VM_CHECK_MODE > 0 @@ -941,7 +942,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co VM_ASSERT(args_argc(args) == 1); } #endif - args_setup_kw_parameters(ec, iseq, NULL, 0, NULL, klocals); + args_setup_kw_parameters(ec, iseq, cme, NULL, 0, NULL, klocals); } } else if (ISEQ_BODY(iseq)->param.flags.has_kwrest) { @@ -949,7 +950,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co kw_flag, ISEQ_BODY(iseq)->param.flags.anon_kwrest); } else if (!NIL_P(keyword_hash) && RHASH_SIZE(keyword_hash) > 0 && arg_setup_type == arg_setup_method) { - argument_kw_error(ec, iseq, "unknown", rb_hash_keys(keyword_hash)); + argument_kw_error(ec, iseq, cme, "unknown", rb_hash_keys(keyword_hash)); } if (ISEQ_BODY(iseq)->param.flags.has_block) { @@ -975,17 +976,16 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co } static void -raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const VALUE exc) +raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const VALUE exc) { VALUE at; if (iseq) { vm_push_frame(ec, iseq, VM_FRAME_MAGIC_DUMMY | VM_ENV_FLAG_LOCAL, Qnil /* self */, - VM_BLOCK_HANDLER_NONE /* specval*/, Qfalse /* me or cref */, + VM_BLOCK_HANDLER_NONE /* specval*/, (VALUE) cme /* me or cref */, ISEQ_BODY(iseq)->iseq_encoded, ec->cfp->sp, 0, 0 /* stack_max */); at = rb_ec_backtrace_object(ec); - rb_backtrace_use_iseq_first_lineno_for_last_location(at); rb_vm_pop_frame(ec); } else { @@ -998,7 +998,7 @@ raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const VA } static void -argument_arity_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const int miss_argc, const int min_argc, const int max_argc) +argument_arity_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const int miss_argc, const int min_argc, const int max_argc) { VALUE exc = rb_arity_error_new(miss_argc, min_argc, max_argc); if (ISEQ_BODY(iseq)->param.flags.has_kw) { @@ -1019,13 +1019,13 @@ argument_arity_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const in RSTRING_PTR(mesg)[RSTRING_LEN(mesg)-1] = ')'; } } - raise_argument_error(ec, iseq, exc); + raise_argument_error(ec, iseq, cme, exc); } static void -argument_kw_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const char *error, const VALUE keys) +argument_kw_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const char *error, const VALUE keys) { - raise_argument_error(ec, iseq, rb_keyword_error_new(error, keys)); + raise_argument_error(ec, iseq, cme, rb_keyword_error_new(error, keys)); } static VALUE diff --git a/vm_backtrace.c b/vm_backtrace.c index 26e0a6fb76..12e4b771e2 100644 --- a/vm_backtrace.c +++ b/vm_backtrace.c @@ -262,6 +262,15 @@ retry: } } +static bool +is_internal_location(const rb_iseq_t *iseq) +{ + static const char prefix[] = "cme->def->body.iseq.iseqptr, BUILTIN_ATTR_C_TRACE); + return is_internal_location(loc->cme->def->body.iseq.iseqptr); default: return false; } @@ -452,6 +461,7 @@ location_format(VALUE file, int lineno, VALUE name) else { rb_str_catf(s, "'%s'", RSTRING_PTR(name)); } + RB_GC_GUARD(name); return s; } @@ -604,15 +614,6 @@ backtrace_size(const rb_execution_context_t *ec) return start_cfp - last_cfp + 1; } -static bool -is_internal_location(const rb_control_frame_t *cfp) -{ - static const char prefix[] = "iseq); - return strncmp(prefix, RSTRING_PTR(file), prefix_len) == 0; -} - static bool is_rescue_or_ensure_frame(const rb_control_frame_t *cfp) { @@ -621,11 +622,11 @@ is_rescue_or_ensure_frame(const rb_control_frame_t *cfp) } static void -bt_update_cfunc_loc(unsigned long cfunc_counter, rb_backtrace_location_t *cfunc_loc, const rb_iseq_t *iseq, const VALUE *pc) +bt_backpatch_loc(unsigned long backpatch_counter, rb_backtrace_location_t *loc, const rb_iseq_t *iseq, const VALUE *pc) { - for (; cfunc_counter > 0; cfunc_counter--, cfunc_loc--) { - cfunc_loc->iseq = iseq; - cfunc_loc->pc = pc; + for (; backpatch_counter > 0; backpatch_counter--, loc--) { + loc->iseq = iseq; + loc->pc = pc; } } @@ -648,7 +649,7 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram rb_backtrace_t *bt = NULL; VALUE btobj = Qnil; rb_backtrace_location_t *loc = NULL; - unsigned long cfunc_counter = 0; + unsigned long backpatch_counter = 0; bool skip_next_frame = FALSE; // In the case the thread vm_stack or cfp is not initialized, there is no backtrace. @@ -691,26 +692,41 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram if (start_frame > 0) { start_frame--; } - else if (!(skip_internal && is_internal_location(cfp))) { + else { + bool internal = is_internal_location(cfp->iseq); + if (skip_internal && internal) continue; if (!skip_next_frame) { const rb_iseq_t *iseq = cfp->iseq; const VALUE *pc = cfp->pc; + if (internal && backpatch_counter > 0) { + // To keep only one internal frame, discard the previous backpatch frames + bt->backtrace_size -= backpatch_counter; + backpatch_counter = 0; + } loc = &bt->backtrace[bt->backtrace_size++]; RB_OBJ_WRITE(btobj, &loc->cme, rb_vm_frame_method_entry(cfp)); - // Ruby methods with `Primitive.attr! :c_trace` should behave like C methods - if (rb_iseq_attr_p(cfp->iseq, BUILTIN_ATTR_C_TRACE)) { - loc->iseq = NULL; - loc->pc = NULL; - cfunc_counter++; + // internal frames (``) should behave like C methods + if (internal) { + // Typically, these iseq and pc are not needed because they will be backpatched later. + // But when the call stack starts with an internal frame (i.e., prelude.rb), + // they will be used to show the `` location. + RB_OBJ_WRITE(btobj, &loc->iseq, iseq); + loc->pc = pc; + backpatch_counter++; } else { RB_OBJ_WRITE(btobj, &loc->iseq, iseq); - loc->pc = pc; - bt_update_cfunc_loc(cfunc_counter, loc-1, iseq, pc); - if (do_yield) { - bt_yield_loc(loc - cfunc_counter, cfunc_counter+1, btobj); + if ((VM_FRAME_TYPE(cfp) & VM_FRAME_MAGIC_MASK) == VM_FRAME_MAGIC_DUMMY) { + loc->pc = NULL; // means location.first_lineno } - cfunc_counter = 0; + else { + loc->pc = pc; + } + bt_backpatch_loc(backpatch_counter, loc-1, iseq, pc); + if (do_yield) { + bt_yield_loc(loc - backpatch_counter, backpatch_counter+1, btobj); + } + backpatch_counter = 0; } } skip_next_frame = is_rescue_or_ensure_frame(cfp); @@ -727,21 +743,21 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram RB_OBJ_WRITE(btobj, &loc->cme, rb_vm_frame_method_entry(cfp)); loc->iseq = NULL; loc->pc = NULL; - cfunc_counter++; + backpatch_counter++; } } } // When a backtrace entry corresponds to a method defined in C (e.g. rb_define_method), the reported file:line // is the one of the caller Ruby frame, so if the last entry is a C frame we find the caller Ruby frame here. - if (cfunc_counter > 0) { + if (backpatch_counter > 0) { for (; cfp != end_cfp; cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)) { - if (cfp->iseq && cfp->pc && !(skip_internal && is_internal_location(cfp))) { + if (cfp->iseq && cfp->pc && !(skip_internal && is_internal_location(cfp->iseq))) { VM_ASSERT(!skip_next_frame); // ISEQ_TYPE_RESCUE/ISEQ_TYPE_ENSURE should have a caller Ruby ISEQ, not a cfunc - bt_update_cfunc_loc(cfunc_counter, loc, cfp->iseq, cfp->pc); + bt_backpatch_loc(backpatch_counter, loc, cfp->iseq, cfp->pc); RB_OBJ_WRITTEN(btobj, Qundef, cfp->iseq); if (do_yield) { - bt_yield_loc(loc - cfunc_counter, cfunc_counter, btobj); + bt_yield_loc(loc - backpatch_counter, backpatch_counter, btobj); } break; } @@ -803,22 +819,6 @@ rb_backtrace_to_str_ary(VALUE self) return bt->strary; } -void -rb_backtrace_use_iseq_first_lineno_for_last_location(VALUE self) -{ - rb_backtrace_t *bt; - rb_backtrace_location_t *loc; - - TypedData_Get_Struct(self, rb_backtrace_t, &backtrace_data_type, bt); - VM_ASSERT(bt->backtrace_size > 0); - - loc = &bt->backtrace[0]; - - VM_ASSERT(!loc->cme || loc->cme->def->type == VM_METHOD_TYPE_ISEQ); - - loc->pc = NULL; // means location.first_lineno -} - static VALUE location_create(rb_backtrace_location_t *srcloc, void *btobj) { @@ -1493,9 +1493,8 @@ RUBY_SYMBOL_EXPORT_END struct rb_debug_inspector_struct { rb_execution_context_t *ec; rb_control_frame_t *cfp; - VALUE backtrace; VALUE contexts; /* [[klass, binding, iseq, cfp], ...] */ - long backtrace_size; + VALUE raw_backtrace; }; enum { @@ -1504,18 +1503,22 @@ enum { CALLER_BINDING_BINDING, CALLER_BINDING_ISEQ, CALLER_BINDING_CFP, + CALLER_BINDING_LOC, CALLER_BINDING_DEPTH, }; struct collect_caller_bindings_data { VALUE ary; const rb_execution_context_t *ec; + VALUE btobj; + rb_backtrace_t *bt; }; static void -collect_caller_bindings_init(void *arg, size_t size) +collect_caller_bindings_init(void *arg, size_t num_frames) { - /* */ + struct collect_caller_bindings_data *data = (struct collect_caller_bindings_data *)arg; + data->btobj = backtrace_alloc_capa(num_frames, &data->bt); } static VALUE @@ -1553,6 +1556,14 @@ collect_caller_bindings_iseq(void *arg, const rb_control_frame_t *cfp) rb_ary_store(frame, CALLER_BINDING_BINDING, GC_GUARDED_PTR(cfp)); /* create later */ rb_ary_store(frame, CALLER_BINDING_ISEQ, cfp->iseq ? (VALUE)cfp->iseq : Qnil); rb_ary_store(frame, CALLER_BINDING_CFP, GC_GUARDED_PTR(cfp)); + + rb_backtrace_location_t *loc = &data->bt->backtrace[data->bt->backtrace_size++]; + RB_OBJ_WRITE(data->btobj, &loc->cme, rb_vm_frame_method_entry(cfp)); + RB_OBJ_WRITE(data->btobj, &loc->iseq, cfp->iseq); + loc->pc = cfp->pc; + VALUE vloc = location_create(loc, (void *)data->btobj); + rb_ary_store(frame, CALLER_BINDING_LOC, vloc); + rb_ary_store(frame, CALLER_BINDING_DEPTH, INT2FIX(frame_depth(data->ec, cfp))); rb_ary_push(data->ary, frame); @@ -1569,6 +1580,14 @@ collect_caller_bindings_cfunc(void *arg, const rb_control_frame_t *cfp, ID mid) rb_ary_store(frame, CALLER_BINDING_BINDING, Qnil); /* not available */ rb_ary_store(frame, CALLER_BINDING_ISEQ, Qnil); /* not available */ rb_ary_store(frame, CALLER_BINDING_CFP, GC_GUARDED_PTR(cfp)); + + rb_backtrace_location_t *loc = &data->bt->backtrace[data->bt->backtrace_size++]; + RB_OBJ_WRITE(data->btobj, &loc->cme, rb_vm_frame_method_entry(cfp)); + loc->iseq = NULL; + loc->pc = NULL; + VALUE vloc = location_create(loc, (void *)data->btobj); + rb_ary_store(frame, CALLER_BINDING_LOC, vloc); + rb_ary_store(frame, CALLER_BINDING_DEPTH, INT2FIX(frame_depth(data->ec, cfp))); rb_ary_push(data->ary, frame); @@ -1617,15 +1636,19 @@ rb_debug_inspector_open(rb_debug_inspector_func_t func, void *data) rb_execution_context_t *ec = GET_EC(); enum ruby_tag_type state; volatile VALUE MAYBE_UNUSED(result); + int i; /* escape all env to heap */ rb_vm_stack_to_heap(ec); dbg_context.ec = ec; dbg_context.cfp = dbg_context.ec->cfp; - dbg_context.backtrace = rb_ec_backtrace_location_ary(ec, RUBY_BACKTRACE_START, RUBY_ALL_BACKTRACE_LINES, FALSE); - dbg_context.backtrace_size = RARRAY_LEN(dbg_context.backtrace); dbg_context.contexts = collect_caller_bindings(ec); + dbg_context.raw_backtrace = rb_ary_new(); + for (i=0; i= dc->backtrace_size) { + if (index < 0 || index >= RARRAY_LEN(dc->contexts)) { rb_raise(rb_eArgError, "no such frame"); } return rb_ary_entry(dc->contexts, index); @@ -1698,7 +1721,7 @@ rb_debug_inspector_current_depth(void) VALUE rb_debug_inspector_backtrace_locations(const rb_debug_inspector_t *dc) { - return dc->backtrace; + return dc->raw_backtrace; } static int diff --git a/vm_callinfo.h b/vm_callinfo.h index a09785ac9c..0ce25c2c0f 100644 --- a/vm_callinfo.h +++ b/vm_callinfo.h @@ -289,7 +289,7 @@ struct rb_callcache { union { struct { - uintptr_t value; // Shape ID in upper bits, index in lower bits + uint64_t value; // Shape ID in former half, index in latter half } attr; const enum method_missing_reason method_missing_reason; /* used by method_missing */ VALUE v; @@ -297,14 +297,13 @@ struct rb_callcache { } aux_; }; -#define VM_CALLCACHE_UNMARKABLE FL_FREEZE -#define VM_CALLCACHE_ON_STACK FL_EXIVAR - /* VM_CALLCACHE_IVAR used for IVAR/ATTRSET/STRUCT_AREF/STRUCT_ASET methods */ #define VM_CALLCACHE_IVAR IMEMO_FL_USER0 #define VM_CALLCACHE_BF IMEMO_FL_USER1 #define VM_CALLCACHE_SUPER IMEMO_FL_USER2 #define VM_CALLCACHE_REFINEMENT IMEMO_FL_USER3 +#define VM_CALLCACHE_UNMARKABLE IMEMO_FL_USER4 +#define VM_CALLCACHE_ON_STACK IMEMO_FL_USER5 enum vm_cc_type { cc_type_normal, // chained from ccs @@ -345,6 +344,7 @@ vm_cc_new(VALUE klass, break; case cc_type_refinement: *(VALUE *)&cc->flags |= VM_CALLCACHE_REFINEMENT; + rb_vm_insert_cc_refinement(cc); break; } @@ -415,43 +415,26 @@ vm_cc_call(const struct rb_callcache *cc) return cc->call_; } -static inline attr_index_t -vm_cc_attr_index(const struct rb_callcache *cc) +static inline void +vm_unpack_shape_and_index(const uint64_t cache_value, shape_id_t *shape_id, attr_index_t *index) { - VM_ASSERT(IMEMO_TYPE_P(cc, imemo_callcache)); - return (attr_index_t)((cc->aux_.attr.value & SHAPE_FLAG_MASK) - 1); -} - -static inline shape_id_t -vm_cc_attr_index_dest_shape_id(const struct rb_callcache *cc) -{ - VM_ASSERT(IMEMO_TYPE_P(cc, imemo_callcache)); - - return cc->aux_.attr.value >> SHAPE_FLAG_SHIFT; + union rb_attr_index_cache cache = { + .pack = cache_value, + }; + *shape_id = cache.unpack.shape_id; + *index = cache.unpack.index - 1; } static inline void -vm_cc_atomic_shape_and_index(const struct rb_callcache *cc, shape_id_t * shape_id, attr_index_t * index) +vm_cc_atomic_shape_and_index(const struct rb_callcache *cc, shape_id_t *shape_id, attr_index_t *index) { - uintptr_t cache_value = cc->aux_.attr.value; // Atomically read 64 bits - *shape_id = (shape_id_t)(cache_value >> SHAPE_FLAG_SHIFT); - *index = (attr_index_t)(cache_value & SHAPE_FLAG_MASK) - 1; - return; + vm_unpack_shape_and_index(ATOMIC_U64_LOAD_RELAXED(cc->aux_.attr.value), shape_id, index); } static inline void -vm_ic_atomic_shape_and_index(const struct iseq_inline_iv_cache_entry *ic, shape_id_t * shape_id, attr_index_t * index) +vm_ic_atomic_shape_and_index(const struct iseq_inline_iv_cache_entry *ic, shape_id_t *shape_id, attr_index_t *index) { - uintptr_t cache_value = ic->value; // Atomically read 64 bits - *shape_id = (shape_id_t)(cache_value >> SHAPE_FLAG_SHIFT); - *index = (attr_index_t)(cache_value & SHAPE_FLAG_MASK) - 1; - return; -} - -static inline shape_id_t -vm_ic_attr_index_dest_shape_id(const struct iseq_inline_iv_cache_entry *ic) -{ - return (shape_id_t)(ic->value >> SHAPE_FLAG_SHIFT); + vm_unpack_shape_and_index(ATOMIC_U64_LOAD_RELAXED(ic->value), shape_id, index); } static inline unsigned int @@ -488,17 +471,29 @@ set_vm_cc_ivar(const struct rb_callcache *cc) *(VALUE *)&cc->flags |= VM_CALLCACHE_IVAR; } +static inline uint64_t +vm_pack_shape_and_index(shape_id_t shape_id, attr_index_t index) +{ + union rb_attr_index_cache cache = { + .unpack = { + .shape_id = shape_id, + .index = index + 1, + } + }; + return cache.pack; +} + static inline void vm_cc_attr_index_set(const struct rb_callcache *cc, attr_index_t index, shape_id_t dest_shape_id) { - uintptr_t *attr_value = (uintptr_t *)&cc->aux_.attr.value; + uint64_t *attr_value = (uint64_t *)&cc->aux_.attr.value; if (!vm_cc_markable(cc)) { - *attr_value = (uintptr_t)INVALID_SHAPE_ID << SHAPE_FLAG_SHIFT; + *attr_value = vm_pack_shape_and_index(INVALID_SHAPE_ID, ATTR_INDEX_NOT_SET); return; } VM_ASSERT(IMEMO_TYPE_P(cc, imemo_callcache)); VM_ASSERT(cc != vm_cc_empty()); - *attr_value = (attr_index_t)(index + 1) | ((uintptr_t)(dest_shape_id) << SHAPE_FLAG_SHIFT); + *attr_value = vm_pack_shape_and_index(dest_shape_id, index); set_vm_cc_ivar(cc); } @@ -509,15 +504,15 @@ vm_cc_ivar_p(const struct rb_callcache *cc) } static inline void -vm_ic_attr_index_set(const rb_iseq_t *iseq, const struct iseq_inline_iv_cache_entry *ic, attr_index_t index, shape_id_t dest_shape_id) +vm_ic_attr_index_set(const rb_iseq_t *iseq, struct iseq_inline_iv_cache_entry *ic, attr_index_t index, shape_id_t dest_shape_id) { - *(uintptr_t *)&ic->value = ((uintptr_t)dest_shape_id << SHAPE_FLAG_SHIFT) | (attr_index_t)(index + 1); + ATOMIC_U64_SET_RELAXED(ic->value, vm_pack_shape_and_index(dest_shape_id, index)); } static inline void -vm_ic_attr_index_initialize(const struct iseq_inline_iv_cache_entry *ic, shape_id_t shape_id) +vm_ic_attr_index_initialize(struct iseq_inline_iv_cache_entry *ic, shape_id_t shape_id) { - *(uintptr_t *)&ic->value = (uintptr_t)shape_id << SHAPE_FLAG_SHIFT; + ATOMIC_U64_SET_RELAXED(ic->value, vm_pack_shape_and_index(shape_id, ATTR_INDEX_NOT_SET)); } static inline void diff --git a/vm_core.h b/vm_core.h index f456447d37..8da7a08119 100644 --- a/vm_core.h +++ b/vm_core.h @@ -288,7 +288,7 @@ struct iseq_inline_constant_cache { }; struct iseq_inline_iv_cache_entry { - uintptr_t value; // attr_index in lower bits, dest_shape_id in upper bits + uint64_t value; // dest_shape_id in former half, attr_index in latter half ID iv_set_name; }; @@ -683,12 +683,15 @@ typedef struct rb_vm_struct { bool terminate_waiting; #ifndef RUBY_THREAD_PTHREAD_H + // win32 bool barrier_waiting; unsigned int barrier_cnt; - rb_nativethread_cond_t barrier_cond; + rb_nativethread_cond_t barrier_complete_cond; + rb_nativethread_cond_t barrier_release_cond; #endif } sync; +#ifdef RUBY_THREAD_PTHREAD_H // ractor scheduling struct { rb_nativethread_lock_t lock; @@ -722,7 +725,10 @@ typedef struct rb_vm_struct { bool barrier_waiting; unsigned int barrier_waiting_cnt; unsigned int barrier_serial; + struct rb_ractor_struct *barrier_ractor; + unsigned int barrier_lock_rec; } sched; +#endif } ractor; #ifdef USE_SIGALTSTACK @@ -730,7 +736,6 @@ typedef struct rb_vm_struct { #endif rb_serial_t fork_gen; - struct ccan_list_head waiting_fds; /* <=> struct waiting_fd */ /* set in single-threaded processes only: */ volatile int ubf_async_safe; @@ -798,6 +803,7 @@ typedef struct rb_vm_struct { struct rb_id_table *negative_cme_table; st_table *overloaded_cme_table; // cme -> overloaded_cme set_table *unused_block_warning_table; + set_table *cc_refinement_table; // This id table contains a mapping from ID to ICs. It does this with ID // keys and nested st_tables as values. The nested tables have ICs as keys @@ -1106,18 +1112,6 @@ typedef struct rb_ractor_struct rb_ractor_t; struct rb_native_thread; -struct rb_thread_ractor_waiting { - //enum rb_ractor_wait_status wait_status; - int wait_status; - //enum rb_ractor_wakeup_status wakeup_status; - int wakeup_status; - struct ccan_list_node waiting_node; // the rb_thread_t - VALUE receiving_mutex; // protects Ractor.receive_if -#ifndef RUBY_THREAD_PTHREAD_H - rb_nativethread_cond_t cond; -#endif -}; - typedef struct rb_thread_struct { struct ccan_list_node lt_node; // managed by a ractor (r->threads.set) VALUE self; @@ -1130,8 +1124,6 @@ typedef struct rb_thread_struct { bool mn_schedulable; rb_atomic_t serial; // only for RUBY_DEBUG_LOG() - struct rb_thread_ractor_waiting ractor_waiting; - VALUE last_status; /* $? */ /* for cfunc */ @@ -1904,7 +1896,9 @@ rb_vm_living_threads_init(rb_vm_t *vm) { ccan_list_head_init(&vm->workqueue); ccan_list_head_init(&vm->ractor.set); +#ifdef RUBY_THREAD_PTHREAD_H ccan_list_head_init(&vm->ractor.sched.zombie_threads); +#endif } typedef int rb_backtrace_iter_func(void *, VALUE, int, VALUE); @@ -1928,7 +1922,7 @@ void rb_vm_register_special_exception_str(enum ruby_special_exceptions sp, VALUE void rb_gc_mark_machine_context(const rb_execution_context_t *ec); -void rb_vm_rewrite_cref(rb_cref_t *node, VALUE old_klass, VALUE new_klass, rb_cref_t **new_cref_ptr); +rb_cref_t *rb_vm_rewrite_cref(rb_cref_t *node, VALUE old_klass, VALUE new_klass); const rb_callable_method_entry_t *rb_vm_frame_method_entry(const rb_control_frame_t *cfp); @@ -2003,9 +1997,9 @@ rb_current_execution_context(bool expect_ec) { #ifdef RB_THREAD_LOCAL_SPECIFIER #if defined(__arm64__) || defined(__aarch64__) - rb_execution_context_t *ec = rb_current_ec(); + rb_execution_context_t * volatile ec = rb_current_ec(); #else - rb_execution_context_t *ec = ruby_current_ec; + rb_execution_context_t * volatile ec = ruby_current_ec; #endif /* On the shared objects, `__tls_get_addr()` is used to access the TLS @@ -2022,7 +2016,7 @@ rb_current_execution_context(bool expect_ec) */ VM_ASSERT(ec == rb_current_ec_noinline()); #else - rb_execution_context_t *ec = native_tls_get(ruby_current_ec_key); + rb_execution_context_t * volatile ec = native_tls_get(ruby_current_ec_key); #endif VM_ASSERT(!expect_ec || ec != NULL); return ec; @@ -2103,8 +2097,12 @@ enum { #define RUBY_VM_SET_TRAP_INTERRUPT(ec) ATOMIC_OR((ec)->interrupt_flag, TRAP_INTERRUPT_MASK) #define RUBY_VM_SET_TERMINATE_INTERRUPT(ec) ATOMIC_OR((ec)->interrupt_flag, TERMINATE_INTERRUPT_MASK) #define RUBY_VM_SET_VM_BARRIER_INTERRUPT(ec) ATOMIC_OR((ec)->interrupt_flag, VM_BARRIER_INTERRUPT_MASK) -#define RUBY_VM_INTERRUPTED(ec) ((ec)->interrupt_flag & ~(ec)->interrupt_mask & \ - (PENDING_INTERRUPT_MASK|TRAP_INTERRUPT_MASK)) + +static inline bool +RUBY_VM_INTERRUPTED(rb_execution_context_t *ec) +{ + return (ATOMIC_LOAD_RELAXED(ec->interrupt_flag) & ~(ec->interrupt_mask) & (PENDING_INTERRUPT_MASK|TRAP_INTERRUPT_MASK)); +} static inline bool RUBY_VM_INTERRUPTED_ANY(rb_execution_context_t *ec) @@ -2117,7 +2115,7 @@ RUBY_VM_INTERRUPTED_ANY(rb_execution_context_t *ec) RUBY_VM_SET_TIMER_INTERRUPT(ec); } #endif - return ec->interrupt_flag & ~(ec)->interrupt_mask; + return ATOMIC_LOAD_RELAXED(ec->interrupt_flag) & ~(ec)->interrupt_mask; } VALUE rb_exc_set_backtrace(VALUE exc, VALUE bt); diff --git a/vm_eval.c b/vm_eval.c index 15827a449b..dff0e47c7e 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -460,7 +460,8 @@ gccct_method_search(rb_execution_context_t *ec, VALUE recv, ID mid, const struct if (NAMESPACE_USER_P(ns)) { ns_value = ns->ns_object; - } else { + } + else { ns_value = 0; } // search global method cache @@ -2885,7 +2886,6 @@ Init_vm_eval(void) rb_define_method(rb_eUncaughtThrow, "value", uncaught_throw_value, 0); rb_define_method(rb_eUncaughtThrow, "to_s", uncaught_throw_to_s, 0); - rb_define_singleton_method(rb_cModule, "gccct_clear_table", rb_gccct_clear_table, 0); id_result = rb_intern_const("result"); id_tag = rb_intern_const("tag"); id_value = rb_intern_const("value"); diff --git a/vm_insnhelper.c b/vm_insnhelper.c index f4aeb7fc34..d5eb84e691 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -79,24 +79,18 @@ vm_stackoverflow(void) ec_stack_overflow(GET_EC(), TRUE); } -NORETURN(void rb_ec_stack_overflow(rb_execution_context_t *ec, int crit)); -/* critical level - * 0: VM stack overflow or about to machine stack overflow - * 1: machine stack overflow but may be recoverable - * 2: fatal machine stack overflow - */ void -rb_ec_stack_overflow(rb_execution_context_t *ec, int crit) +rb_ec_stack_overflow(rb_execution_context_t *ec, ruby_stack_overflow_critical_level crit) { if (rb_during_gc()) { rb_bug("system stack overflow during GC. Faulty native extension?"); } - if (crit > 1) { + if (crit >= rb_stack_overflow_fatal) { ec->raised_flag = RAISED_STACKOVERFLOW; ec->errinfo = rb_ec_vm_ptr(ec)->special_exceptions[ruby_error_stackfatal]; EC_JUMP_TAG(ec, TAG_RAISE); } - ec_stack_overflow(ec, crit == 0); + ec_stack_overflow(ec, crit < rb_stack_overflow_signal); } static inline void stack_check(rb_execution_context_t *ec); @@ -176,7 +170,7 @@ vm_check_frame_detail(VALUE type, int req_block, int req_me, int req_cref, VALUE } else { /* cref or Qfalse */ if (cref_or_me != Qfalse && cref_or_me_type != imemo_cref) { - if (((type & VM_FRAME_FLAG_LAMBDA) || magic == VM_FRAME_MAGIC_IFUNC) && (cref_or_me_type == imemo_ment)) { + if (((type & VM_FRAME_FLAG_LAMBDA) || magic == VM_FRAME_MAGIC_IFUNC || magic == VM_FRAME_MAGIC_DUMMY) && (cref_or_me_type == imemo_ment)) { /* ignore */ } else { @@ -973,23 +967,37 @@ vm_get_const_key_cref(const VALUE *ep) return NULL; } -void -rb_vm_rewrite_cref(rb_cref_t *cref, VALUE old_klass, VALUE new_klass, rb_cref_t **new_cref_ptr) +rb_cref_t * +rb_vm_rewrite_cref(rb_cref_t *cref, VALUE old_klass, VALUE new_klass) { - rb_cref_t *new_cref; + rb_cref_t *new_cref_head = NULL; + rb_cref_t *new_cref_tail = NULL; + + #define ADD_NEW_CREF(new_cref) \ + if (new_cref_tail) { \ + RB_OBJ_WRITE(new_cref_tail, &new_cref_tail->next, new_cref); \ + } \ + else { \ + new_cref_head = new_cref; \ + } \ + new_cref_tail = new_cref; while (cref) { + rb_cref_t *new_cref; if (CREF_CLASS(cref) == old_klass) { new_cref = vm_cref_new_use_prev(new_klass, METHOD_VISI_UNDEF, FALSE, cref, FALSE); - *new_cref_ptr = new_cref; - return; + ADD_NEW_CREF(new_cref); + return new_cref_head; } new_cref = vm_cref_new_use_prev(CREF_CLASS(cref), METHOD_VISI_UNDEF, FALSE, cref, FALSE); cref = CREF_NEXT(cref); - *new_cref_ptr = new_cref; - new_cref_ptr = &new_cref->next; + ADD_NEW_CREF(new_cref); } - *new_cref_ptr = NULL; + + #undef ADD_NEW_CREF + + // Could we just reuse the original cref? + return new_cref_head; } static rb_cref_t * @@ -1209,33 +1217,25 @@ fill_ivar_cache(const rb_iseq_t *iseq, IVC ic, const struct rb_callcache *cc, in #define ractor_object_incidental_shareable_p(obj, val) \ ractor_incidental_shareable_p(rb_ractor_shareable_p(obj), val) -#define ATTR_INDEX_NOT_SET (attr_index_t)-1 - ALWAYS_INLINE(static VALUE vm_getivar(VALUE, ID, const rb_iseq_t *, IVC, const struct rb_callcache *, int, VALUE)); static inline VALUE vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_callcache *cc, int is_attr, VALUE default_value) { + VALUE fields_obj; #if OPT_IC_FOR_IVAR VALUE val = Qundef; - shape_id_t shape_id; - VALUE * ivar_list; + VALUE *ivar_list; if (SPECIAL_CONST_P(obj)) { return default_value; } -#if SHAPE_IN_BASIC_FLAGS - shape_id = RBASIC_SHAPE_ID(obj); -#endif + shape_id_t shape_id = RBASIC_SHAPE_ID_FOR_READ(obj); switch (BUILTIN_TYPE(obj)) { case T_OBJECT: ivar_list = ROBJECT_FIELDS(obj); VM_ASSERT(rb_ractor_shareable_p(obj) ? rb_ractor_shareable_p(val) : true); - -#if !SHAPE_IN_BASIC_FLAGS - shape_id = ROBJECT_SHAPE_ID(obj); -#endif break; case T_CLASS: case T_MODULE: @@ -1256,22 +1256,22 @@ vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_call } } - ivar_list = RCLASS_PRIME_FIELDS(obj); - -#if !SHAPE_IN_BASIC_FLAGS - shape_id = RCLASS_SHAPE_ID(obj); -#endif + fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + if (!fields_obj) { + return default_value; + } + ivar_list = rb_imemo_fields_ptr(fields_obj); + shape_id = RBASIC_SHAPE_ID_FOR_READ(fields_obj); break; } default: - if (FL_TEST_RAW(obj, FL_EXIVAR)) { - struct gen_fields_tbl *fields_tbl; - rb_gen_fields_tbl_get(obj, id, &fields_tbl); -#if !SHAPE_IN_BASIC_FLAGS - shape_id = fields_tbl->shape_id; -#endif - ivar_list = fields_tbl->as.shape.fields; + if (rb_obj_exivar_p(obj)) { + VALUE fields_obj = 0; + if (!rb_gen_fields_tbl_get(obj, id, &fields_obj)) { + return default_value; + } + ivar_list = rb_imemo_fields_ptr(fields_obj); } else { return default_value; @@ -1289,7 +1289,7 @@ vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_call } if (LIKELY(cached_id == shape_id)) { - RUBY_ASSERT(!rb_shape_id_too_complex_p(cached_id)); + RUBY_ASSERT(!rb_shape_too_complex_p(cached_id)); if (index == ATTR_INDEX_NOT_SET) { return default_value; @@ -1330,12 +1330,12 @@ vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_call } #endif - if (rb_shape_id_too_complex_p(shape_id)) { + if (rb_shape_too_complex_p(shape_id)) { st_table *table = NULL; switch (BUILTIN_TYPE(obj)) { case T_CLASS: case T_MODULE: - table = (st_table *)RCLASS_FIELDS_HASH(obj); + table = rb_imemo_fields_complex_tbl(fields_obj); break; case T_OBJECT: @@ -1343,9 +1343,9 @@ vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_call break; default: { - struct gen_fields_tbl *fields_tbl; - if (rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - table = fields_tbl->as.complex.table; + VALUE fields_obj; + if (rb_gen_fields_tbl_get(obj, 0, &fields_obj)) { + table = rb_imemo_fields_complex_tbl(fields_obj); } break; } @@ -1391,6 +1391,7 @@ vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_call RUBY_ASSERT(!UNDEF_P(val)); } + RB_GC_GUARD(fields_obj); return val; general_path: @@ -1408,7 +1409,7 @@ general_path: static void populate_cache(attr_index_t index, shape_id_t next_shape_id, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_callcache *cc, bool is_attr) { - RUBY_ASSERT(!rb_shape_id_too_complex_p(next_shape_id)); + RUBY_ASSERT(!rb_shape_too_complex_p(next_shape_id)); // Cache population code if (is_attr) { @@ -1434,9 +1435,9 @@ vm_setivar_slowpath(VALUE obj, ID id, VALUE val, const rb_iseq_t *iseq, IVC ic, attr_index_t index = rb_obj_ivar_set(obj, id, val); - shape_id_t next_shape_id = ROBJECT_SHAPE_ID(obj); + shape_id_t next_shape_id = RBASIC_SHAPE_ID(obj); - if (!rb_shape_id_too_complex_p(next_shape_id)) { + if (!rb_shape_too_complex_p(next_shape_id)) { populate_cache(index, next_shape_id, id, iseq, ic, cc, is_attr); } @@ -1463,24 +1464,17 @@ NOINLINE(static VALUE vm_setivar_default(VALUE obj, ID id, VALUE val, shape_id_t static VALUE vm_setivar_default(VALUE obj, ID id, VALUE val, shape_id_t dest_shape_id, attr_index_t index) { -#if SHAPE_IN_BASIC_FLAGS shape_id_t shape_id = RBASIC_SHAPE_ID(obj); -#else - shape_id_t shape_id = rb_generic_shape_id(obj); -#endif - struct gen_fields_tbl *fields_tbl = 0; + VALUE fields_obj = 0; // Cache hit case if (shape_id == dest_shape_id) { RUBY_ASSERT(dest_shape_id != INVALID_SHAPE_ID && shape_id != INVALID_SHAPE_ID); } else if (dest_shape_id != INVALID_SHAPE_ID) { - rb_shape_t *shape = RSHAPE(shape_id); - rb_shape_t *dest_shape = RSHAPE(dest_shape_id); - - if (shape_id == dest_shape->parent_id && dest_shape->edge_name == id && shape->capacity == dest_shape->capacity) { - RUBY_ASSERT(index < dest_shape->capacity); + if (shape_id == RSHAPE_PARENT(dest_shape_id) && RSHAPE_EDGE_NAME(dest_shape_id) == id && RSHAPE_CAPACITY(shape_id) == RSHAPE_CAPACITY(dest_shape_id)) { + RUBY_ASSERT(index < RSHAPE_CAPACITY(dest_shape_id)); } else { return Qundef; @@ -1490,17 +1484,13 @@ vm_setivar_default(VALUE obj, ID id, VALUE val, shape_id_t dest_shape_id, attr_i return Qundef; } - rb_gen_fields_tbl_get(obj, 0, &fields_tbl); + rb_gen_fields_tbl_get(obj, 0, &fields_obj); if (shape_id != dest_shape_id) { -#if SHAPE_IN_BASIC_FLAGS RBASIC_SET_SHAPE_ID(obj, dest_shape_id); -#else - fields_tbl->shape_id = dest_shape_id; -#endif } - RB_OBJ_WRITE(obj, &fields_tbl->as.shape.fields[index], val); + RB_OBJ_WRITE(obj, &rb_imemo_fields_ptr(fields_obj)[index], val); RB_DEBUG_COUNTER_INC(ivar_set_ic_hit); @@ -1516,25 +1506,23 @@ vm_setivar(VALUE obj, ID id, VALUE val, shape_id_t dest_shape_id, attr_index_t i { VM_ASSERT(!rb_ractor_shareable_p(obj) || rb_obj_frozen_p(obj)); - shape_id_t shape_id = ROBJECT_SHAPE_ID(obj); - RUBY_ASSERT(dest_shape_id == INVALID_SHAPE_ID || !rb_shape_id_too_complex_p(dest_shape_id)); + shape_id_t shape_id = RBASIC_SHAPE_ID(obj); + RUBY_ASSERT(dest_shape_id == INVALID_SHAPE_ID || !rb_shape_too_complex_p(dest_shape_id)); if (LIKELY(shape_id == dest_shape_id)) { RUBY_ASSERT(dest_shape_id != INVALID_SHAPE_ID && shape_id != INVALID_SHAPE_ID); VM_ASSERT(!rb_ractor_shareable_p(obj)); } else if (dest_shape_id != INVALID_SHAPE_ID) { - rb_shape_t *shape = RSHAPE(shape_id); - rb_shape_t *dest_shape = RSHAPE(dest_shape_id); - shape_id_t source_shape_id = dest_shape->parent_id; + shape_id_t source_shape_id = RSHAPE_PARENT(dest_shape_id); - if (shape_id == source_shape_id && dest_shape->edge_name == id && shape->capacity == dest_shape->capacity) { + if (shape_id == source_shape_id && RSHAPE_EDGE_NAME(dest_shape_id) == id && RSHAPE_CAPACITY(shape_id) == RSHAPE_CAPACITY(dest_shape_id)) { RUBY_ASSERT(dest_shape_id != INVALID_SHAPE_ID && shape_id != INVALID_SHAPE_ID); - ROBJECT_SET_SHAPE_ID(obj, dest_shape_id); + RBASIC_SET_SHAPE_ID(obj, dest_shape_id); RUBY_ASSERT(rb_shape_get_next_iv_shape(source_shape_id, id) == dest_shape_id); - RUBY_ASSERT(index < dest_shape->capacity); + RUBY_ASSERT(index < RSHAPE_CAPACITY(dest_shape_id)); } else { break; @@ -2187,8 +2175,7 @@ rb_vm_search_method_slowpath(const struct rb_callinfo *ci, VALUE klass) VM_ASSERT_TYPE2(klass, T_CLASS, T_ICLASS); - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { cc = vm_search_cc(klass, ci); VM_ASSERT(cc); @@ -2198,7 +2185,6 @@ rb_vm_search_method_slowpath(const struct rb_callinfo *ci, VALUE klass) VM_ASSERT(cc == vm_cc_empty() || !METHOD_ENTRY_INVALIDATED(vm_cc_cme(cc))); VM_ASSERT(cc == vm_cc_empty() || vm_cc_cme(cc)->called_id == vm_ci_mid(ci)); } - RB_VM_LOCK_LEAVE(); return cc; } @@ -2937,7 +2923,7 @@ vm_call_iseq_setup_tailcall_opt_start(rb_execution_context_t *ec, rb_control_fra } static void -args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *const iseq, +args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *const iseq, const rb_callable_method_entry_t *cme, VALUE *const passed_values, const int passed_keyword_len, const VALUE *const passed_keywords, VALUE *const locals); @@ -2981,7 +2967,7 @@ vm_call_iseq_setup_kwparm_kwarg(rb_execution_context_t *ec, rb_control_frame_t * const int lead_num = ISEQ_BODY(iseq)->param.lead_num; VALUE * const ci_kws = ALLOCA_N(VALUE, ci_kw_len); MEMCPY(ci_kws, argv + lead_num, VALUE, ci_kw_len); - args_setup_kw_parameters(ec, iseq, ci_kws, ci_kw_len, ci_keywords, klocals); + args_setup_kw_parameters(ec, iseq, vm_cc_cme(cc), ci_kws, ci_kw_len, ci_keywords, klocals); int param = ISEQ_BODY(iseq)->param.size; int local = ISEQ_BODY(iseq)->local_table_size; @@ -3052,7 +3038,7 @@ warn_unused_block(const rb_callable_method_entry_t *cme, const rb_iseq_t *iseq, if (!strict_unused_block) { key = (st_data_t)cme->def->original_id; - if (set_lookup(dup_check_table, key)) { + if (set_table_lookup(dup_check_table, key)) { return; } } @@ -3112,7 +3098,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, CALLER_SETUP_ARG(cfp, calling, ci, lead_num); if (calling->argc != lead_num) { - argument_arity_error(ec, iseq, calling->argc, lead_num, lead_num); + argument_arity_error(ec, iseq, vm_cc_cme(cc), calling->argc, lead_num, lead_num); } //VM_ASSERT(ci == calling->cd->ci); @@ -3142,7 +3128,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, const int opt = argc - lead_num; if (opt < 0 || opt > opt_num) { - argument_arity_error(ec, iseq, argc, lead_num, lead_num + opt_num); + argument_arity_error(ec, iseq, vm_cc_cme(cc), argc, lead_num, lead_num + opt_num); } if (LIKELY(!(vm_ci_flag(ci) & VM_CALL_TAILCALL))) { @@ -3178,7 +3164,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, MEMCPY(ci_kws, argv + lead_num, VALUE, ci_kw_len); VALUE *const klocals = argv + kw_param->bits_start - kw_param->num; - args_setup_kw_parameters(ec, iseq, ci_kws, ci_kw_len, ci_keywords, klocals); + args_setup_kw_parameters(ec, iseq, vm_cc_cme(cc), ci_kws, ci_kw_len, ci_keywords, klocals); CC_SET_FASTPATH(cc, vm_call_iseq_setup_kwparm_kwarg, vm_call_cacheable(ci, cc)); @@ -3189,7 +3175,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, else if (argc == lead_num) { /* no kwarg */ VALUE *const klocals = argv + kw_param->bits_start - kw_param->num; - args_setup_kw_parameters(ec, iseq, NULL, 0, NULL, klocals); + args_setup_kw_parameters(ec, iseq, vm_cc_cme(cc), NULL, 0, NULL, klocals); if (klocals[kw_param->num] == INT2FIX(0)) { /* copy from default_values */ @@ -3968,8 +3954,9 @@ vm_call_attrset_direct(rb_execution_context_t *ec, rb_control_frame_t *cfp, cons RB_DEBUG_COUNTER_INC(ccf_attrset); VALUE val = *(cfp->sp - 1); cfp->sp -= 2; - attr_index_t index = vm_cc_attr_index(cc); - shape_id_t dest_shape_id = vm_cc_attr_index_dest_shape_id(cc); + attr_index_t index; + shape_id_t dest_shape_id; + vm_cc_atomic_shape_and_index(cc, &dest_shape_id, &index); ID id = vm_cc_cme(cc)->def->body.attr.id; rb_check_frozen(obj); VALUE res = vm_setivar(obj, id, val, dest_shape_id, index); @@ -4799,7 +4786,7 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st .call_ = cc->call_, .aux_ = { .attr = { - .value = INVALID_SHAPE_ID << SHAPE_FLAG_SHIFT, + .value = vm_pack_shape_and_index(INVALID_SHAPE_ID, ATTR_INDEX_NOT_SET), } }, }); @@ -5234,7 +5221,7 @@ vm_callee_setup_block_arg(rb_execution_context_t *ec, struct rb_calling_info *ca } } else { - argument_arity_error(ec, iseq, calling->argc, ISEQ_BODY(iseq)->param.lead_num, ISEQ_BODY(iseq)->param.lead_num); + argument_arity_error(ec, iseq, NULL, calling->argc, ISEQ_BODY(iseq)->param.lead_num, ISEQ_BODY(iseq)->param.lead_num); } } @@ -5256,6 +5243,7 @@ vm_yield_setup_args(rb_execution_context_t *ec, const rb_iseq_t *iseq, const int calling->kw_splat = (flags & VM_CALL_KW_SPLAT) ? 1 : 0; calling->recv = Qundef; calling->heap_argv = 0; + calling->cc = NULL; struct rb_callinfo dummy_ci = VM_CI_ON_STACK(0, flags, 0, 0); return vm_callee_setup_block_arg(ec, calling, &dummy_ci, iseq, argv, arg_setup_type); @@ -5578,6 +5566,14 @@ vm_get_special_object(const VALUE *const reg_ep, } } +// ZJIT implementation is using the C function +// and needs to call a non-static function +VALUE +rb_vm_get_special_object(const VALUE *reg_ep, enum vm_special_object_type type) +{ + return vm_get_special_object(reg_ep, type); +} + static VALUE vm_concat_array(VALUE ary1, VALUE ary2st) { @@ -5937,7 +5933,7 @@ vm_define_method(const rb_execution_context_t *ec, VALUE obj, ID id, VALUE iseqv rb_add_method_iseq(klass, id, (const rb_iseq_t *)iseqval, cref, visi); // Set max_iv_count on klasses based on number of ivar sets that are in the initialize method if (id == idInitialize && klass != rb_cObject && RB_TYPE_P(klass, T_CLASS) && (rb_get_alloc_func(klass) == rb_class_allocate_instance)) { - RCLASS_WRITE_MAX_IV_COUNT(klass, rb_estimate_iv_count(klass, (const rb_iseq_t *)iseqval)); + RCLASS_SET_MAX_IV_COUNT(klass, rb_estimate_iv_count(klass, (const rb_iseq_t *)iseqval)); } if (!is_singleton && vm_scope_module_func_check(ec)) { @@ -6388,15 +6384,13 @@ vm_track_constant_cache(ID id, void *ic) static void vm_ic_track_const_chain(rb_control_frame_t *cfp, IC ic, const ID *segments) { - RB_VM_LOCK_ENTER(); - - for (int i = 0; segments[i]; i++) { - ID id = segments[i]; - if (id == idNULL) continue; - vm_track_constant_cache(id, ic); + RB_VM_LOCKING() { + for (int i = 0; segments[i]; i++) { + ID id = segments[i]; + if (id == idNULL) continue; + vm_track_constant_cache(id, ic); + } } - - RB_VM_LOCK_LEAVE(); } // For JIT inlining @@ -6939,6 +6933,12 @@ vm_opt_aset_with(VALUE recv, VALUE key, VALUE val) } } +VALUE +rb_vm_opt_aset_with(VALUE recv, VALUE key, VALUE value) +{ + return vm_opt_aset_with(recv, key, value); +} + static VALUE vm_opt_length(VALUE recv, int bop) { @@ -7458,3 +7458,4 @@ rb_vm_lvar_exposed(rb_execution_context_t *ec, int index) const rb_control_frame_t *cfp = ec->cfp; return cfp->ep[index]; } + diff --git a/vm_method.c b/vm_method.c index 030e117e0e..4eecdb6ea0 100644 --- a/vm_method.c +++ b/vm_method.c @@ -142,7 +142,7 @@ rb_clear_constant_cache_for_id(ID id) if (rb_id_table_lookup(vm->constant_cache, id, &lookup_result)) { set_table *ics = (set_table *)lookup_result; - set_foreach(ics, rb_clear_constant_cache_for_id_i, (st_data_t) NULL); + set_table_foreach(ics, rb_clear_constant_cache_for_id_i, (st_data_t) NULL); ruby_vm_constant_cache_invalidations += ics->num_entries; } @@ -245,91 +245,90 @@ clear_method_cache_by_id_in_class(VALUE klass, ID mid) VM_ASSERT_TYPE2(klass, T_CLASS, T_ICLASS); if (rb_objspace_garbage_object_p(klass)) return; - RB_VM_LOCK_ENTER(); - if (LIKELY(RCLASS_SUBCLASSES_FIRST(klass) == NULL)) { - // no subclasses - // check only current class + RB_VM_LOCKING() { + if (LIKELY(RCLASS_SUBCLASSES_FIRST(klass) == NULL)) { + // no subclasses + // check only current class - // invalidate CCs - struct rb_id_table *cc_tbl = RCLASS_WRITABLE_CC_TBL(klass); - invalidate_method_cache_in_cc_table(cc_tbl, mid); - if (RCLASS_CC_TBL_NOT_PRIME_P(klass, cc_tbl)) { - invalidate_method_cache_in_cc_table(RCLASS_PRIME_CC_TBL(klass), mid); - } - - // remove from callable_m_tbl, if exists - struct rb_id_table *cm_tbl = RCLASS_WRITABLE_CALLABLE_M_TBL(klass); - invalidate_callable_method_entry_in_callable_m_table(cm_tbl, mid); - if (RCLASS_CALLABLE_M_TBL_NOT_PRIME_P(klass, cm_tbl)) { - invalidate_callable_method_entry_in_callable_m_table(RCLASS_PRIME_CALLABLE_M_TBL(klass), mid); - } - - RB_DEBUG_COUNTER_INC(cc_invalidate_leaf); - } - else { - const rb_callable_method_entry_t *cme = complemented_callable_method_entry(klass, mid); - - if (cme) { - // invalidate cme if found to invalidate the inline method cache. - if (METHOD_ENTRY_CACHED(cme)) { - if (METHOD_ENTRY_COMPLEMENTED(cme)) { - // do nothing - } - else { - // invalidate cc by invalidating cc->cme - VALUE owner = cme->owner; - VM_ASSERT_TYPE(owner, T_CLASS); - VALUE klass_housing_cme; - if (cme->def->type == VM_METHOD_TYPE_REFINED && !cme->def->body.refined.orig_me) { - klass_housing_cme = owner; - } - else { - klass_housing_cme = RCLASS_ORIGIN(owner); - } - - // replace the cme that will be invalid in the all classexts - invalidate_callable_method_entry_in_every_m_table(klass_housing_cme, mid, cme); - } - - vm_cme_invalidate((rb_callable_method_entry_t *)cme); - RB_DEBUG_COUNTER_INC(cc_invalidate_tree_cme); - - // In case of refinement ME, also invalidate the wrapped ME that - // could be cached at some callsite and is unreachable from any - // RCLASS_WRITABLE_CC_TBL. - if (cme->def->type == VM_METHOD_TYPE_REFINED && cme->def->body.refined.orig_me) { - vm_cme_invalidate((rb_callable_method_entry_t *)cme->def->body.refined.orig_me); - } - - if (cme->def->iseq_overload) { - rb_callable_method_entry_t *monly_cme = (rb_callable_method_entry_t *)lookup_overloaded_cme(cme); - if (monly_cme) { - vm_cme_invalidate(monly_cme); - } - } + // invalidate CCs + struct rb_id_table *cc_tbl = RCLASS_WRITABLE_CC_TBL(klass); + invalidate_method_cache_in_cc_table(cc_tbl, mid); + if (RCLASS_CC_TBL_NOT_PRIME_P(klass, cc_tbl)) { + invalidate_method_cache_in_cc_table(RCLASS_PRIME_CC_TBL(klass), mid); } - // invalidate complement tbl - if (METHOD_ENTRY_COMPLEMENTED(cme)) { - VALUE defined_class = cme->defined_class; - struct rb_id_table *cm_tbl = RCLASS_WRITABLE_CALLABLE_M_TBL(defined_class); - invalidate_complemented_method_entry_in_callable_m_table(cm_tbl, mid); - if (RCLASS_CALLABLE_M_TBL_NOT_PRIME_P(defined_class, cm_tbl)) { - struct rb_id_table *prime_cm_table = RCLASS_PRIME_CALLABLE_M_TBL(defined_class); - invalidate_complemented_method_entry_in_callable_m_table(prime_cm_table, mid); - } + // remove from callable_m_tbl, if exists + struct rb_id_table *cm_tbl = RCLASS_WRITABLE_CALLABLE_M_TBL(klass); + invalidate_callable_method_entry_in_callable_m_table(cm_tbl, mid); + if (RCLASS_CALLABLE_M_TBL_NOT_PRIME_P(klass, cm_tbl)) { + invalidate_callable_method_entry_in_callable_m_table(RCLASS_PRIME_CALLABLE_M_TBL(klass), mid); } - RB_DEBUG_COUNTER_INC(cc_invalidate_tree); + RB_DEBUG_COUNTER_INC(cc_invalidate_leaf); } else { - invalidate_negative_cache(mid); + const rb_callable_method_entry_t *cme = complemented_callable_method_entry(klass, mid); + + if (cme) { + // invalidate cme if found to invalidate the inline method cache. + if (METHOD_ENTRY_CACHED(cme)) { + if (METHOD_ENTRY_COMPLEMENTED(cme)) { + // do nothing + } + else { + // invalidate cc by invalidating cc->cme + VALUE owner = cme->owner; + VM_ASSERT_TYPE(owner, T_CLASS); + VALUE klass_housing_cme; + if (cme->def->type == VM_METHOD_TYPE_REFINED && !cme->def->body.refined.orig_me) { + klass_housing_cme = owner; + } + else { + klass_housing_cme = RCLASS_ORIGIN(owner); + } + + // replace the cme that will be invalid in the all classexts + invalidate_callable_method_entry_in_every_m_table(klass_housing_cme, mid, cme); + } + + vm_cme_invalidate((rb_callable_method_entry_t *)cme); + RB_DEBUG_COUNTER_INC(cc_invalidate_tree_cme); + + // In case of refinement ME, also invalidate the wrapped ME that + // could be cached at some callsite and is unreachable from any + // RCLASS_WRITABLE_CC_TBL. + if (cme->def->type == VM_METHOD_TYPE_REFINED && cme->def->body.refined.orig_me) { + vm_cme_invalidate((rb_callable_method_entry_t *)cme->def->body.refined.orig_me); + } + + if (cme->def->iseq_overload) { + rb_callable_method_entry_t *monly_cme = (rb_callable_method_entry_t *)lookup_overloaded_cme(cme); + if (monly_cme) { + vm_cme_invalidate(monly_cme); + } + } + } + + // invalidate complement tbl + if (METHOD_ENTRY_COMPLEMENTED(cme)) { + VALUE defined_class = cme->defined_class; + struct rb_id_table *cm_tbl = RCLASS_WRITABLE_CALLABLE_M_TBL(defined_class); + invalidate_complemented_method_entry_in_callable_m_table(cm_tbl, mid); + if (RCLASS_CALLABLE_M_TBL_NOT_PRIME_P(defined_class, cm_tbl)) { + struct rb_id_table *prime_cm_table = RCLASS_PRIME_CALLABLE_M_TBL(defined_class); + invalidate_complemented_method_entry_in_callable_m_table(prime_cm_table, mid); + } + } + + RB_DEBUG_COUNTER_INC(cc_invalidate_tree); + } + else { + invalidate_negative_cache(mid); + } } - } - rb_gccct_clear_table(Qnil); - - RB_VM_LOCK_LEAVE(); + rb_gccct_clear_table(Qnil); +} } static void @@ -395,27 +394,29 @@ rb_invalidate_method_caches(struct rb_id_table *cm_tbl, struct rb_id_table *cc_t } static int -invalidate_all_refinement_cc(void *vstart, void *vend, size_t stride, void *data) +invalidate_cc_refinement(st_data_t key, st_data_t data) { - VALUE v = (VALUE)vstart; - for (; v != (VALUE)vend; v += stride) { - void *ptr = rb_asan_poisoned_object_p(v); - rb_asan_unpoison_object(v, false); + VALUE v = (VALUE)key; + void *ptr = rb_asan_poisoned_object_p(v); + rb_asan_unpoison_object(v, false); - if (RBASIC(v)->flags) { // liveness check - if (imemo_type_p(v, imemo_callcache)) { - const struct rb_callcache *cc = (const struct rb_callcache *)v; - if (vm_cc_refinement_p(cc) && cc->klass) { - vm_cc_invalidate(cc); - } - } - } + if (rb_gc_pointer_to_heap_p(v) && + !rb_objspace_garbage_object_p(v) && + RBASIC(v)->flags) { // liveness check + const struct rb_callcache *cc = (const struct rb_callcache *)v; - if (ptr) { - rb_asan_poison_object(v); + VM_ASSERT(vm_cc_refinement_p(cc)); + + if (cc->klass) { + vm_cc_invalidate(cc); } } - return 0; // continue to iteration + + if (ptr) { + rb_asan_poison_object(v); + } + + return ST_CONTINUE; } static st_index_t @@ -502,8 +503,7 @@ rb_vm_ci_lookup(ID mid, unsigned int flag, unsigned int argc, const struct rb_ca new_ci->flag = flag; new_ci->argc = argc; - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { st_table *ci_table = vm->ci_table; VM_ASSERT(ci_table); @@ -511,7 +511,6 @@ rb_vm_ci_lookup(ID mid, unsigned int flag, unsigned int argc, const struct rb_ca st_update(ci_table, (st_data_t)new_ci, ci_lookup_i, (st_data_t)&ci); } while (ci == NULL); } - RB_VM_LOCK_LEAVE(); VM_ASSERT(ci); @@ -529,10 +528,43 @@ rb_vm_ci_free(const struct rb_callinfo *ci) st_delete(vm->ci_table, &key, NULL); } +void +rb_vm_insert_cc_refinement(const struct rb_callcache *cc) +{ + st_data_t key = (st_data_t)cc; + + rb_vm_t *vm = GET_VM(); + RB_VM_LOCK_ENTER(); + { + rb_set_insert(vm->cc_refinement_table, key); + } + RB_VM_LOCK_LEAVE(); +} + +void +rb_vm_delete_cc_refinement(const struct rb_callcache *cc) +{ + ASSERT_vm_locking(); + + rb_vm_t *vm = GET_VM(); + st_data_t key = (st_data_t)cc; + + rb_set_table_delete(vm->cc_refinement_table, &key); +} + void rb_clear_all_refinement_method_cache(void) { - rb_objspace_each_objects(invalidate_all_refinement_cc, NULL); + rb_vm_t *vm = GET_VM(); + + RB_VM_LOCK_ENTER(); + { + rb_set_table_foreach(vm->cc_refinement_table, invalidate_cc_refinement, (st_data_t)NULL); + rb_set_table_clear(vm->cc_refinement_table); + rb_set_compact_table(vm->cc_refinement_table); + } + RB_VM_LOCK_LEAVE(); + rb_yjit_invalidate_all_method_lookup_assumptions(); } @@ -848,6 +880,8 @@ method_definition_reset(const rb_method_entry_t *me) } } +static rb_atomic_t method_serial = 1; + rb_method_definition_t * rb_method_definition_create(rb_method_type_t type, ID mid) { @@ -855,8 +889,7 @@ rb_method_definition_create(rb_method_type_t type, ID mid) def = ZALLOC(rb_method_definition_t); def->type = type; def->original_id = mid; - static uintptr_t method_serial = 1; - def->method_serial = method_serial++; + def->method_serial = (uintptr_t)RUBY_ATOMIC_FETCH_ADD(method_serial, 1); def->ns = rb_current_namespace(); return def; } @@ -1596,8 +1629,7 @@ callable_method_entry_or_negative(VALUE klass, ID mid, VALUE *defined_class_ptr) const rb_callable_method_entry_t *cme; VM_ASSERT_TYPE2(klass, T_CLASS, T_ICLASS); - RB_VM_LOCK_ENTER(); - { + RB_VM_LOCKING() { cme = cached_callable_method_entry(klass, mid); if (cme) { @@ -1618,7 +1650,6 @@ callable_method_entry_or_negative(VALUE klass, ID mid, VALUE *defined_class_ptr) cache_callable_method_entry(klass, mid, cme); } } - RB_VM_LOCK_LEAVE(); return cme; } @@ -3164,3 +3195,4 @@ Init_eval_method(void) REPLICATE_METHOD(rb_eException, idRespond_to_missing); } } + diff --git a/vm_sync.c b/vm_sync.c index b57bd86647..772a3239db 100644 --- a/vm_sync.c +++ b/vm_sync.c @@ -7,6 +7,7 @@ void rb_ractor_sched_barrier_start(rb_vm_t *vm, rb_ractor_t *cr); void rb_ractor_sched_barrier_join(rb_vm_t *vm, rb_ractor_t *cr); +void rb_ractor_sched_barrier_end(rb_vm_t *vm, rb_ractor_t *cr); static bool vm_locked(rb_vm_t *vm) @@ -103,15 +104,26 @@ vm_lock_enter(rb_ractor_t *cr, rb_vm_t *vm, bool locked, bool no_barrier, unsign } static void -vm_lock_leave(rb_vm_t *vm, unsigned int *lev APPEND_LOCATION_ARGS) +vm_lock_leave(rb_vm_t *vm, bool no_barrier, unsigned int *lev APPEND_LOCATION_ARGS) { + MAYBE_UNUSED(rb_ractor_t *cr = vm->ractor.sync.lock_owner); + RUBY_DEBUG_LOG2(file, line, "rec:%u owner:%u%s", vm->ractor.sync.lock_rec, - (unsigned int)rb_ractor_id(vm->ractor.sync.lock_owner), + (unsigned int)rb_ractor_id(cr), vm->ractor.sync.lock_rec == 1 ? " (leave)" : ""); ASSERT_vm_locking(); VM_ASSERT(vm->ractor.sync.lock_rec > 0); VM_ASSERT(vm->ractor.sync.lock_rec == *lev); + VM_ASSERT(cr == GET_RACTOR()); + +#ifdef RUBY_THREAD_PTHREAD_H + if (vm->ractor.sched.barrier_ractor == cr && + vm->ractor.sched.barrier_lock_rec == vm->ractor.sync.lock_rec) { + VM_ASSERT(!no_barrier); + rb_ractor_sched_barrier_end(vm, cr); + } +#endif vm->ractor.sync.lock_rec--; *lev = vm->ractor.sync.lock_rec; @@ -153,10 +165,16 @@ rb_vm_lock_enter_body_cr(rb_ractor_t *cr, unsigned int *lev APPEND_LOCATION_ARGS vm_lock_enter(cr, vm, vm_locked(vm), false, lev APPEND_LOCATION_PARAMS); } +void +rb_vm_lock_leave_body_nb(unsigned int *lev APPEND_LOCATION_ARGS) +{ + vm_lock_leave(GET_VM(), true, lev APPEND_LOCATION_PARAMS); +} + void rb_vm_lock_leave_body(unsigned int *lev APPEND_LOCATION_ARGS) { - vm_lock_leave(GET_VM(), lev APPEND_LOCATION_PARAMS); + vm_lock_leave(GET_VM(), false, lev APPEND_LOCATION_PARAMS); } void @@ -174,7 +192,7 @@ rb_vm_unlock_body(LOCATION_ARGS) rb_vm_t *vm = GET_VM(); ASSERT_vm_locking(); VM_ASSERT(vm->ractor.sync.lock_rec == 1); - vm_lock_leave(vm, &vm->ractor.sync.lock_rec APPEND_LOCATION_PARAMS); + vm_lock_leave(vm, false, &vm->ractor.sync.lock_rec APPEND_LOCATION_PARAMS); } static void @@ -208,6 +226,16 @@ rb_vm_cond_timedwait(rb_vm_t *vm, rb_nativethread_cond_t *cond, unsigned long ms vm_cond_wait(vm, cond, msec); } +static bool +vm_barrier_acquired_p(const rb_vm_t *vm, const rb_ractor_t *cr) +{ +#ifdef RUBY_THREAD_PTHREAD_H + return vm->ractor.sched.barrier_ractor == cr; +#else + return false; +#endif +} + void rb_vm_barrier(void) { @@ -219,13 +247,20 @@ rb_vm_barrier(void) } else { rb_vm_t *vm = GET_VM(); - VM_ASSERT(!vm->ractor.sched.barrier_waiting); - ASSERT_vm_locking(); rb_ractor_t *cr = vm->ractor.sync.lock_owner; + + ASSERT_vm_locking(); VM_ASSERT(cr == GET_RACTOR()); VM_ASSERT(rb_ractor_status_p(cr, ractor_running)); - rb_ractor_sched_barrier_start(vm, cr); + if (vm_barrier_acquired_p(vm, cr)) { + // already in barrier synchronization + return; + } + else { + VM_ASSERT(!vm->ractor.sched.barrier_waiting); + rb_ractor_sched_barrier_start(vm, cr); + } } } diff --git a/vm_sync.h b/vm_sync.h index e8243d6f50..457be2c6b8 100644 --- a/vm_sync.h +++ b/vm_sync.h @@ -24,6 +24,7 @@ struct rb_ractor_struct; NOINLINE(void rb_vm_lock_enter_body_cr(struct rb_ractor_struct *cr, unsigned int *lev APPEND_LOCATION_ARGS)); NOINLINE(void rb_vm_lock_enter_body_nb(unsigned int *lev APPEND_LOCATION_ARGS)); NOINLINE(void rb_vm_lock_enter_body(unsigned int *lev APPEND_LOCATION_ARGS)); +void rb_vm_lock_leave_body_nb(unsigned int *lev APPEND_LOCATION_ARGS); void rb_vm_lock_leave_body(unsigned int *lev APPEND_LOCATION_ARGS); void rb_vm_barrier(void); @@ -86,6 +87,14 @@ rb_vm_lock_enter_nb(unsigned int *lev, const char *file, int line) } } +static inline void +rb_vm_lock_leave_nb(unsigned int *lev, const char *file, int line) +{ + if (rb_multi_ractor_p()) { + rb_vm_lock_leave_body_nb(lev APPEND_LOCATION_PARAMS); + } +} + static inline void rb_vm_lock_leave(unsigned int *lev, const char *file, int line) { @@ -119,10 +128,17 @@ rb_vm_lock_leave_cr(struct rb_ractor_struct *cr, unsigned int *levp, const char #define RB_VM_LOCK_ENTER() { unsigned int _lev; RB_VM_LOCK_ENTER_LEV(&_lev); #define RB_VM_LOCK_LEAVE() RB_VM_LOCK_LEAVE_LEV(&_lev); } +#define RB_VM_LOCKING() \ + for (unsigned int vm_locking_level, vm_locking_do = (RB_VM_LOCK_ENTER_LEV(&vm_locking_level), 1); \ + vm_locking_do; RB_VM_LOCK_LEAVE_LEV(&vm_locking_level), vm_locking_do = 0) #define RB_VM_LOCK_ENTER_LEV_NB(levp) rb_vm_lock_enter_nb(levp, __FILE__, __LINE__) +#define RB_VM_LOCK_LEAVE_LEV_NB(levp) rb_vm_lock_leave_nb(levp, __FILE__, __LINE__) #define RB_VM_LOCK_ENTER_NO_BARRIER() { unsigned int _lev; RB_VM_LOCK_ENTER_LEV_NB(&_lev); -#define RB_VM_LOCK_LEAVE_NO_BARRIER() RB_VM_LOCK_LEAVE_LEV(&_lev); } +#define RB_VM_LOCK_LEAVE_NO_BARRIER() RB_VM_LOCK_LEAVE_LEV_NB(&_lev); } +#define RB_VM_LOCKING_NO_BARRIER() \ + for (unsigned int vm_locking_level, vm_locking_do = (RB_VM_LOCK_ENTER_LEV_NB(&vm_locking_level), 1); \ + vm_locking_do; RB_VM_LOCK_LEAVE_LEV_NB(&vm_locking_level), vm_locking_do = 0) #if RUBY_DEBUG > 0 void RUBY_ASSERT_vm_locking(void); diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 80225c3063..7c125a4a02 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -286,6 +286,10 @@ WARNFLAGS = -W2 -wd4100 -wd4127 -wd4210 -wd4214 -wd4255 -wd4574 \ !else WARNFLAGS = -W2 !endif +!if $(MSC_VER) >= 1944 +# https://developercommunity.visualstudio.com/t/warning-C5287:-operands-are-different-e/10877942 +WARNFLAGS = $(WARNFLAGS) -wd5287 +!endif !endif WERRORFLAG = -WX !if !defined(CFLAGS_NO_ARCH) diff --git a/win32/install-buildtools.cmd b/win32/install-buildtools.cmd new file mode 100755 index 0000000000..6ec1475280 --- /dev/null +++ b/win32/install-buildtools.cmd @@ -0,0 +1,14 @@ +@echo off +setlocal + +set components=VC.Tools.x86.x64 VC.Redist.14.Latest CoreBuildTools +set components=%components% Windows11SDK.26100 +if /i "%PROCESSOR_ARCHITECTURE%" == "ARM64" ( + set components=%components% VC.Tools.ARM64 VC.Tools.ARM64EC +) +set override=--passive +for %%I in (%components%) do ( + call set override=%%override%% --add Microsoft.VisualStudio.Component.%%I +) +echo on +winget install --id Microsoft.VisualStudio.2022.BuildTools --override "%override%" diff --git a/win32/mkexports.rb b/win32/mkexports.rb index 1a37c7ee91..389b49def8 100755 --- a/win32/mkexports.rb +++ b/win32/mkexports.rb @@ -110,6 +110,7 @@ class Exports::Mswin < Exports case filetype when /OBJECT/, /LIBRARY/ l.chomp! + next if (/^ .*\(pick any\)$/ =~ l)...true next if /^[[:xdigit:]]+ 0+ UNDEF / =~ l next unless /External/ =~ l next if /(?:_local_stdio_printf_options|v(f|sn?)printf(_s)?_l)\Z/ =~ l diff --git a/win32/vssetup.cmd b/win32/vssetup.cmd new file mode 100755 index 0000000000..be77c87b29 --- /dev/null +++ b/win32/vssetup.cmd @@ -0,0 +1,27 @@ +@echo off +setlocal ENABLEEXTENSIONS + +::- check for vswhere +set vswhere=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe +if not exist "%vswhere%" ( + echo 1>&2 vswhere.exe not found + exit /b 1 +) + +::- find the latest build tool and its setup batch file. +set VSDEVCMD= +for /f "delims=" %%I in ('"%vswhere%" -products * -latest -property installationPath') do ( + set VSDEVCMD=%%I\Common7\Tools\VsDevCmd.bat +) +if not defined VSDEVCMD ( + echo 1>&2 Visual Studio not found + exit /b 1 +) + +::- default to the current processor. +set arch=%PROCESSOR_ARCHITECTURE% +::- `vsdevcmd.bat` requires arch names to be lowercase +for %%i in (a b c d e f g h i j k l m n o p q r s t u v w x y z) do @( + call set arch=%%arch:%%i=%%i%% +) +echo on && endlocal && "%VSDEVCMD%" -arch=%arch% -host_arch=%arch% %* diff --git a/win32/win32.c b/win32/win32.c index c57ac49991..72539e8a63 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -4761,7 +4761,7 @@ gettimeofday(struct timeval *tv, struct timezone *tz) return 0; } -#if !defined(__MINGW32__) || !defined(HAVE_CLOCK_GETTIME) +#ifdef NEED_CLOCK_GETTIME /* License: Ruby's */ int clock_gettime(clockid_t clock_id, struct timespec *sp) @@ -4803,7 +4803,7 @@ clock_gettime(clockid_t clock_id, struct timespec *sp) } #endif -#if !defined(__MINGW32__) || !defined(HAVE_CLOCK_GETRES) +#ifdef NEED_CLOCK_GETRES /* License: Ruby's */ int clock_getres(clockid_t clock_id, struct timespec *sp) diff --git a/yjit.c b/yjit.c index 253b1ec67e..b3364ff606 100644 --- a/yjit.c +++ b/yjit.c @@ -29,6 +29,7 @@ #include "iseq.h" #include "ruby/debug.h" #include "internal/cont.h" +#include "zjit.h" // For mmapp(), sysconf() #ifndef _WIN32 @@ -453,12 +454,6 @@ rb_get_def_bmethod_proc(rb_method_definition_t *def) return def->body.bmethod.proc; } -const rb_iseq_t * -rb_get_iseq_body_parent_iseq(const rb_iseq_t *iseq) -{ - return iseq->body->parent_iseq; -} - VALUE rb_optimized_call(VALUE *recv, rb_execution_context_t *ec, int argc, VALUE *argv, int kw_splat, VALUE block_handler) { @@ -532,13 +527,6 @@ rb_str_neq_internal(VALUE str1, VALUE str2) return rb_str_eql_internal(str1, str2) == Qtrue ? Qfalse : Qtrue; } -// YJIT needs this function to never allocate and never raise -VALUE -rb_yarv_ary_entry_internal(VALUE ary, long offset) -{ - return rb_ary_entry_internal(ary, offset); -} - extern VALUE rb_ary_unshift_m(int argc, VALUE *argv, VALUE ary); VALUE @@ -744,21 +732,20 @@ rb_yjit_vm_unlock(unsigned int *recursive_lock_level, const char *file, int line void rb_yjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception) { - RB_VM_LOCK_ENTER(); - rb_vm_barrier(); + RB_VM_LOCKING() { + rb_vm_barrier(); - // Compile a block version starting at the current instruction - uint8_t *rb_yjit_iseq_gen_entry_point(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception); // defined in Rust - uintptr_t code_ptr = (uintptr_t)rb_yjit_iseq_gen_entry_point(iseq, ec, jit_exception); + // Compile a block version starting at the current instruction + uint8_t *rb_yjit_iseq_gen_entry_point(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception); // defined in Rust + uintptr_t code_ptr = (uintptr_t)rb_yjit_iseq_gen_entry_point(iseq, ec, jit_exception); - if (jit_exception) { - iseq->body->jit_exception = (rb_jit_func_t)code_ptr; - } - else { - iseq->body->jit_entry = (rb_jit_func_t)code_ptr; - } - - RB_VM_LOCK_LEAVE(); + if (jit_exception) { + iseq->body->jit_exception = (rb_jit_func_t)code_ptr; + } + else { + iseq->body->jit_entry = (rb_jit_func_t)code_ptr; + } +} } // GC root for interacting with the GC @@ -779,15 +766,31 @@ VALUE rb_object_shape_count(void) { // next_shape_id starts from 0, so it's the same as the count - return ULONG2NUM((unsigned long)GET_SHAPE_TREE()->next_shape_id); + return ULONG2NUM((unsigned long)rb_shapes_count()); } -// Assert that we have the VM lock. Relevant mostly for multi ractor situations. -// The GC takes the lock before calling us, and this asserts that it indeed happens. -void -rb_yjit_assert_holding_vm_lock(void) +bool +rb_yjit_shape_too_complex_p(shape_id_t shape_id) { - ASSERT_vm_locking(); + return rb_shape_too_complex_p(shape_id); +} + +bool +rb_yjit_shape_obj_too_complex_p(VALUE obj) +{ + return rb_shape_obj_too_complex_p(obj); +} + +attr_index_t +rb_yjit_shape_capacity(shape_id_t shape_id) +{ + return RSHAPE_CAPACITY(shape_id); +} + +attr_index_t +rb_yjit_shape_index(shape_id_t shape_id) +{ + return RSHAPE_INDEX(shape_id); } // The number of stack slots that vm_sendish() pops for send and invokesuper. @@ -859,3 +862,4 @@ static VALUE yjit_c_builtin_p(rb_execution_context_t *ec, VALUE self) { return Q // Preprocessed yjit.rb generated during build #include "yjit.rbinc" + diff --git a/yjit.h b/yjit.h index 9360e7fe3c..cb96ee7838 100644 --- a/yjit.h +++ b/yjit.h @@ -37,7 +37,7 @@ void rb_yjit_collect_binding_alloc(void); void rb_yjit_collect_binding_set(void); void rb_yjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception); void rb_yjit_init(bool yjit_enabled); -void rb_yjit_free_at_exit(); +void rb_yjit_free_at_exit(void); void rb_yjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop); void rb_yjit_constant_state_changed(ID id); void rb_yjit_iseq_mark(void *payload); @@ -50,6 +50,8 @@ void rb_yjit_show_usage(int help, int highlight, unsigned int width, int columns void rb_yjit_lazy_push_frame(const VALUE *pc); void rb_yjit_invalidate_no_singleton_class(VALUE klass); void rb_yjit_invalidate_ep_is_bp(const rb_iseq_t *iseq); +void rb_yjit_mark_all_writeable(void); +void rb_yjit_mark_all_executable(void); #else // !USE_YJIT diff --git a/yjit.rb b/yjit.rb index 045fea2656..e8ba3cdd28 100644 --- a/yjit.rb +++ b/yjit.rb @@ -48,6 +48,11 @@ module RubyVM::YJIT def self.enable(stats: false, log: false, mem_size: nil, call_threshold: nil) return false if enabled? + if Primitive.cexpr! 'RBOOL(rb_zjit_enabled_p)' + warn("Only one JIT can be enabled at the same time.") + return false + end + if mem_size raise ArgumentError, "mem_size must be a Integer" unless mem_size.is_a?(Integer) raise ArgumentError, "mem_size must be between 1 and 2048 MB" unless (1..2048).include?(mem_size) diff --git a/yjit/Cargo.toml b/yjit/Cargo.toml index dd5b853e41..ad7dd35ecf 100644 --- a/yjit/Cargo.toml +++ b/yjit/Cargo.toml @@ -9,9 +9,6 @@ edition = "2021" # Rust 2021 edition to compile with rust-version = "1.58.0" # Minimally supported rust version publish = false # Don't publish to crates.io -[lib] -crate-type = ["staticlib"] - [dependencies] # No required dependencies to simplify build process. TODO: Link to yet to be # written rationale. Optional For development and testing purposes @@ -27,27 +24,3 @@ disasm = ["capstone"] # from cfg!(debug_assertions) so that we can see disasm of the code # that would run in the release mode. runtime_checks = [] - -[profile.dev] -opt-level = 0 -debug = true -debug-assertions = true -overflow-checks = true - -[profile.dev_nodebug] -inherits = "dev" - -[profile.stats] -inherits = "release" - -[profile.release] -# NOTE: --enable-yjit builds use `rustc` without going through Cargo. You -# might want to update the `rustc` invocation if you change this profile. -opt-level = 3 -# The extra robustness that comes from checking for arithmetic overflow is -# worth the performance cost for the compiler. -overflow-checks = true -# Generate debug info -debug = true -# Use ThinLTO. Much smaller output for a small amount of build time increase. -lto = "thin" diff --git a/yjit/bindgen/Cargo.toml b/yjit/bindgen/Cargo.toml index 8c1b533006..ba695e0ce6 100644 --- a/yjit/bindgen/Cargo.toml +++ b/yjit/bindgen/Cargo.toml @@ -8,3 +8,5 @@ edition = "2021" [dependencies] bindgen = "0.70.1" env_logger = "0.11.5" + +[workspace] diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index ba2e1cc34a..7c8ce7bce5 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -95,14 +95,15 @@ fn main() { // From shape.h .allowlist_function("rb_obj_shape_id") - .allowlist_function("rb_shape_lookup") .allowlist_function("rb_shape_id_offset") .allowlist_function("rb_shape_get_iv_index") .allowlist_function("rb_shape_transition_add_ivar_no_warnings") - .allowlist_function("rb_shape_id") - .allowlist_function("rb_shape_obj_too_complex_p") - .allowlist_function("rb_shape_too_complex_p") + .allowlist_function("rb_yjit_shape_obj_too_complex_p") + .allowlist_function("rb_yjit_shape_too_complex_p") + .allowlist_function("rb_yjit_shape_capacity") + .allowlist_function("rb_yjit_shape_index") .allowlist_var("SHAPE_ID_NUM_BITS") + .allowlist_var("SHAPE_ID_HAS_IVAR_MASK") // From ruby/internal/intern/object.h .allowlist_function("rb_obj_is_kind_of") @@ -340,7 +341,6 @@ fn main() { .allowlist_function("rb_yjit_exit_locations_dict") .allowlist_function("rb_yjit_icache_invalidate") .allowlist_function("rb_optimized_call") - .allowlist_function("rb_yjit_assert_holding_vm_lock") .allowlist_function("rb_yjit_sendish_sp_pops") .allowlist_function("rb_yjit_invokeblock_sp_pops") .allowlist_function("rb_yjit_set_exception_return") @@ -348,6 +348,9 @@ fn main() { .allowlist_type("robject_offsets") .allowlist_type("rstring_offsets") + // From jit.c + .allowlist_function("rb_assert_holding_vm_lock") + // from vm_sync.h .allowlist_function("rb_vm_barrier") diff --git a/yjit/src/asm/mod.rs b/yjit/src/asm/mod.rs index 0320fdd829..ebdc205da9 100644 --- a/yjit/src/asm/mod.rs +++ b/yjit/src/asm/mod.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::fmt; use std::mem; use std::rc::Rc; @@ -44,7 +43,7 @@ pub struct LabelRef { /// Block of memory into which instructions can be assembled pub struct CodeBlock { // Memory for storing the encoded instructions - mem_block: Rc>, + mem_block: Rc, // Size of a code page in bytes. Each code page is split into an inlined and an outlined portion. // Code GC collects code memory at this granularity. @@ -107,16 +106,16 @@ impl CodeBlock { const PREFERRED_CODE_PAGE_SIZE: usize = 16 * 1024; /// Make a new CodeBlock - pub fn new(mem_block: Rc>, outlined: bool, freed_pages: Rc>>, keep_comments: bool) -> Self { + pub fn new(mem_block: Rc, outlined: bool, freed_pages: Rc>>, keep_comments: bool) -> Self { // Pick the code page size - let system_page_size = mem_block.borrow().system_page_size(); + let system_page_size = mem_block.system_page_size(); let page_size = if 0 == Self::PREFERRED_CODE_PAGE_SIZE % system_page_size { Self::PREFERRED_CODE_PAGE_SIZE } else { system_page_size }; - let mem_size = mem_block.borrow().virtual_region_size(); + let mem_size = mem_block.virtual_region_size(); let mut cb = Self { mem_block, mem_size, @@ -238,9 +237,9 @@ impl CodeBlock { } // Free the grouped pages at once - let start_ptr = self.mem_block.borrow().start_ptr().add_bytes(page_idx * self.page_size); + let start_ptr = self.mem_block.start_ptr().add_bytes(page_idx * self.page_size); let batch_size = self.page_size * batch_idxs.len(); - self.mem_block.borrow_mut().free_bytes(start_ptr, batch_size as u32); + self.mem_block.free_bytes(start_ptr, batch_size as u32); } } @@ -249,13 +248,13 @@ impl CodeBlock { } pub fn mapped_region_size(&self) -> usize { - self.mem_block.borrow().mapped_region_size() + self.mem_block.mapped_region_size() } /// Size of the region in bytes where writes could be attempted. #[cfg(target_arch = "aarch64")] pub fn virtual_region_size(&self) -> usize { - self.mem_block.borrow().virtual_region_size() + self.mem_block.virtual_region_size() } /// Return the number of code pages that have been mapped by the VirtualMemory. @@ -267,7 +266,7 @@ impl CodeBlock { /// Return the number of code pages that have been reserved by the VirtualMemory. pub fn num_virtual_pages(&self) -> usize { - let virtual_region_size = self.mem_block.borrow().virtual_region_size(); + let virtual_region_size = self.mem_block.virtual_region_size(); // CodeBlock's page size != VirtualMem's page size on Linux, // so mapped_region_size % self.page_size may not be 0 ((virtual_region_size - 1) / self.page_size) + 1 @@ -409,7 +408,7 @@ impl CodeBlock { } pub fn write_mem(&self, write_ptr: CodePtr, byte: u8) -> Result<(), WriteError> { - self.mem_block.borrow_mut().write_byte(write_ptr, byte) + self.mem_block.write_byte(write_ptr, byte) } // Set the current write position @@ -423,19 +422,19 @@ impl CodeBlock { // Set the current write position from a pointer pub fn set_write_ptr(&mut self, code_ptr: CodePtr) { - let pos = code_ptr.as_offset() - self.mem_block.borrow().start_ptr().as_offset(); + let pos = code_ptr.as_offset() - self.mem_block.start_ptr().as_offset(); self.set_pos(pos.try_into().unwrap()); } /// Get a (possibly dangling) direct pointer into the executable memory block pub fn get_ptr(&self, offset: usize) -> CodePtr { - self.mem_block.borrow().start_ptr().add_bytes(offset) + self.mem_block.start_ptr().add_bytes(offset) } /// Convert an address range to memory page indexes against a num_pages()-sized array. pub fn addrs_to_pages(&self, start_addr: CodePtr, end_addr: CodePtr) -> impl Iterator { - let mem_start = self.mem_block.borrow().start_ptr().raw_addr(self); - let mem_end = self.mem_block.borrow().mapped_end_ptr().raw_addr(self); + let mem_start = self.mem_block.start_ptr().raw_addr(self); + let mem_end = self.mem_block.mapped_end_ptr().raw_addr(self); assert!(mem_start <= start_addr.raw_addr(self)); assert!(start_addr.raw_addr(self) <= end_addr.raw_addr(self)); assert!(end_addr.raw_addr(self) <= mem_end); @@ -458,7 +457,7 @@ impl CodeBlock { /// Write a single byte at the current position. pub fn write_byte(&mut self, byte: u8) { let write_ptr = self.get_write_ptr(); - if self.has_capacity(1) && self.mem_block.borrow_mut().write_byte(write_ptr, byte).is_ok() { + if self.has_capacity(1) && self.mem_block.write_byte(write_ptr, byte).is_ok() { self.write_pos += 1; } else { self.dropped_bytes = true; @@ -590,8 +589,12 @@ impl CodeBlock { self.label_refs = state.label_refs; } + pub fn mark_all_writeable(&mut self) { + self.mem_block.mark_all_writeable(); + } + pub fn mark_all_executable(&mut self) { - self.mem_block.borrow_mut().mark_all_executable(); + self.mem_block.mark_all_executable(); } /// Code GC. Free code pages that are not on stack and reuse them. @@ -689,7 +692,7 @@ impl CodeBlock { let mem_start: *const u8 = alloc.mem_start(); let virt_mem = VirtualMem::new(alloc, 1, NonNull::new(mem_start as *mut u8).unwrap(), mem_size, 128 * 1024 * 1024); - Self::new(Rc::new(RefCell::new(virt_mem)), false, Rc::new(None), true) + Self::new(Rc::new(virt_mem), false, Rc::new(None), true) } /// Stubbed CodeBlock for testing conditions that can arise due to code GC. Can't execute generated code. @@ -707,7 +710,7 @@ impl CodeBlock { let mem_start: *const u8 = alloc.mem_start(); let virt_mem = VirtualMem::new(alloc, 1, NonNull::new(mem_start as *mut u8).unwrap(), mem_size, 128 * 1024 * 1024); - Self::new(Rc::new(RefCell::new(virt_mem)), false, Rc::new(Some(freed_pages)), true) + Self::new(Rc::new(virt_mem), false, Rc::new(Some(freed_pages)), true) } } @@ -715,7 +718,7 @@ impl CodeBlock { impl fmt::LowerHex for CodeBlock { fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result { for pos in 0..self.write_pos { - let mem_block = &*self.mem_block.borrow(); + let mem_block = &*self.mem_block; let byte = unsafe { mem_block.start_ptr().raw_ptr(mem_block).add(pos).read() }; fmtr.write_fmt(format_args!("{:02x}", byte))?; } @@ -725,7 +728,7 @@ impl fmt::LowerHex for CodeBlock { impl crate::virtualmem::CodePtrBase for CodeBlock { fn base_ptr(&self) -> std::ptr::NonNull { - self.mem_block.borrow().base_ptr() + self.mem_block.base_ptr() } } diff --git a/yjit/src/asm/x86_64/mod.rs b/yjit/src/asm/x86_64/mod.rs index fbbfa714d8..0ef5e92117 100644 --- a/yjit/src/asm/x86_64/mod.rs +++ b/yjit/src/asm/x86_64/mod.rs @@ -1027,7 +1027,10 @@ pub fn mov(cb: &mut CodeBlock, dst: X86Opnd, src: X86Opnd) { } let output_num_bits:u32 = if mem.num_bits > 32 { 32 } else { mem.num_bits.into() }; - assert!(imm_num_bits(imm.value) <= (output_num_bits as u8)); + assert!( + mem.num_bits < 64 || imm_num_bits(imm.value) <= (output_num_bits as u8), + "immediate value should be small enough to survive sign extension" + ); cb.write_int(imm.value as u64, output_num_bits); }, // M + UImm @@ -1042,7 +1045,10 @@ pub fn mov(cb: &mut CodeBlock, dst: X86Opnd, src: X86Opnd) { } let output_num_bits = if mem.num_bits > 32 { 32 } else { mem.num_bits.into() }; - assert!(imm_num_bits(uimm.value as i64) <= (output_num_bits as u8)); + assert!( + mem.num_bits < 64 || imm_num_bits(uimm.value as i64) <= (output_num_bits as u8), + "immediate value should be small enough to survive sign extension" + ); cb.write_int(uimm.value, output_num_bits); }, // * + Imm/UImm diff --git a/yjit/src/asm/x86_64/tests.rs b/yjit/src/asm/x86_64/tests.rs index 5ae983270f..eefcbfd52e 100644 --- a/yjit/src/asm/x86_64/tests.rs +++ b/yjit/src/asm/x86_64/tests.rs @@ -193,6 +193,7 @@ fn test_mov() { check_bytes("48c7470801000000", |cb| mov(cb, mem_opnd(64, RDI, 8), imm_opnd(1))); //check_bytes("67c7400411000000", |cb| mov(cb, mem_opnd(32, EAX, 4), imm_opnd(0x34))); // We don't distinguish between EAX and RAX here - that's probably fine? check_bytes("c7400411000000", |cb| mov(cb, mem_opnd(32, RAX, 4), imm_opnd(17))); + check_bytes("c7400401000080", |cb| mov(cb, mem_opnd(32, RAX, 4), uimm_opnd(0x80000001))); check_bytes("41895814", |cb| mov(cb, mem_opnd(32, R8, 20), EBX)); check_bytes("4d8913", |cb| mov(cb, mem_opnd(64, R11, 0), R10)); check_bytes("48c742f8f4ffffff", |cb| mov(cb, mem_opnd(64, RDX, -8), imm_opnd(-12))); diff --git a/yjit/src/backend/x86_64/mod.rs b/yjit/src/backend/x86_64/mod.rs index c0d42e79e6..ef435bca7e 100644 --- a/yjit/src/backend/x86_64/mod.rs +++ b/yjit/src/backend/x86_64/mod.rs @@ -315,19 +315,24 @@ impl Assembler let opnd1 = asm.load(*src); asm.mov(*dest, opnd1); }, - (Opnd::Mem(_), Opnd::UImm(value)) => { - // 32-bit values will be sign-extended - if imm_num_bits(*value as i64) > 32 { + (Opnd::Mem(Mem { num_bits, .. }), Opnd::UImm(value)) => { + // For 64 bit destinations, 32-bit values will be sign-extended + if *num_bits == 64 && imm_num_bits(*value as i64) > 32 { let opnd1 = asm.load(*src); asm.mov(*dest, opnd1); } else { asm.mov(*dest, *src); } }, - (Opnd::Mem(_), Opnd::Imm(value)) => { - if imm_num_bits(*value) > 32 { + (Opnd::Mem(Mem { num_bits, .. }), Opnd::Imm(value)) => { + // For 64 bit destinations, 32-bit values will be sign-extended + if *num_bits == 64 && imm_num_bits(*value) > 32 { let opnd1 = asm.load(*src); asm.mov(*dest, opnd1); + } else if uimm_num_bits(*value as u64) <= *num_bits { + // If the bit string is short enough for the destination, use the unsigned representation. + // Note that 64-bit and negative values are ruled out. + asm.mov(*dest, Opnd::UImm(*value as u64)); } else { asm.mov(*dest, *src); } @@ -1317,4 +1322,19 @@ mod tests { 0x13: mov qword ptr [rbx], rax "}); } + + #[test] + fn test_mov_m32_imm32() { + let (mut asm, mut cb) = setup_asm(); + + let shape_opnd = Opnd::mem(32, C_RET_OPND, 0); + asm.mov(shape_opnd, Opnd::UImm(0x8000_0001)); + asm.mov(shape_opnd, Opnd::Imm(0x8000_0001)); + + asm.compile_with_num_regs(&mut cb, 0); + assert_disasm!(cb, "c70001000080c70001000080", {" + 0x0: mov dword ptr [rax], 0x80000001 + 0x6: mov dword ptr [rax], 0x80000001 + "}); + } } diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index b608f98462..b8b15adc8b 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -2904,9 +2904,8 @@ fn gen_get_ivar( let ivar_index = unsafe { let shape_id = comptime_receiver.shape_id_of(); - let shape = rb_shape_lookup(shape_id); - let mut ivar_index: u32 = 0; - if rb_shape_get_iv_index(shape, ivar_name, &mut ivar_index) { + let mut ivar_index: u16 = 0; + if rb_shape_get_iv_index(shape_id, ivar_name, &mut ivar_index) { Some(ivar_index as usize) } else { None @@ -3107,9 +3106,8 @@ fn gen_set_ivar( let shape_too_complex = comptime_receiver.shape_too_complex(); let ivar_index = if !shape_too_complex { let shape_id = comptime_receiver.shape_id_of(); - let shape = unsafe { rb_shape_lookup(shape_id) }; - let mut ivar_index: u32 = 0; - if unsafe { rb_shape_get_iv_index(shape, ivar_name, &mut ivar_index) } { + let mut ivar_index: u16 = 0; + if unsafe { rb_shape_get_iv_index(shape_id, ivar_name, &mut ivar_index) } { Some(ivar_index as usize) } else { None @@ -3121,27 +3119,27 @@ fn gen_set_ivar( // The current shape doesn't contain this iv, we need to transition to another shape. let mut new_shape_too_complex = false; let new_shape = if !shape_too_complex && receiver_t_object && ivar_index.is_none() { - let current_shape = comptime_receiver.shape_of(); + let current_shape_id = comptime_receiver.shape_id_of(); let next_shape_id = unsafe { rb_shape_transition_add_ivar_no_warnings(comptime_receiver, ivar_name) }; - let next_shape = unsafe { rb_shape_lookup(next_shape_id) }; // If the VM ran out of shapes, or this class generated too many leaf, // it may be de-optimized into OBJ_TOO_COMPLEX_SHAPE (hash-table). - new_shape_too_complex = unsafe { rb_shape_too_complex_p(next_shape) }; + new_shape_too_complex = unsafe { rb_yjit_shape_too_complex_p(next_shape_id) }; if new_shape_too_complex { Some((next_shape_id, None, 0_usize)) } else { - let current_capacity = unsafe { (*current_shape).capacity }; + let current_capacity = unsafe { rb_yjit_shape_capacity(current_shape_id) }; + let next_capacity = unsafe { rb_yjit_shape_capacity(next_shape_id) }; // If the new shape has a different capacity, or is TOO_COMPLEX, we'll have to // reallocate it. - let needs_extension = unsafe { (*current_shape).capacity != (*next_shape).capacity }; + let needs_extension = next_capacity != current_capacity; // We can write to the object, but we need to transition the shape - let ivar_index = unsafe { (*current_shape).next_field_index } as usize; + let ivar_index = unsafe { rb_yjit_shape_index(next_shape_id) } as usize; let needs_extension = if needs_extension { - Some((current_capacity, unsafe { (*next_shape).capacity })) + Some((current_capacity, next_capacity)) } else { None }; @@ -3397,9 +3395,8 @@ fn gen_definedivar( let shape_id = comptime_receiver.shape_id_of(); let ivar_exists = unsafe { - let shape = rb_shape_lookup(shape_id); - let mut ivar_index: u32 = 0; - rb_shape_get_iv_index(shape, ivar_name, &mut ivar_index) + let mut ivar_index: u16 = 0; + rb_shape_get_iv_index(shape_id, ivar_name, &mut ivar_index) }; // Guard heap object (recv_opnd must be used before stack_pop) @@ -3891,6 +3888,40 @@ fn gen_opt_aref( } } +fn gen_opt_aset_with( + jit: &mut JITState, + asm: &mut Assembler, +) -> Option { + // We might allocate or raise + jit_prepare_non_leaf_call(jit, asm); + + let key_opnd = Opnd::Value(jit.get_arg(0)); + let recv_opnd = asm.stack_opnd(1); + let value_opnd = asm.stack_opnd(0); + + extern "C" { + fn rb_vm_opt_aset_with(recv: VALUE, key: VALUE, value: VALUE) -> VALUE; + } + + let val_opnd = asm.ccall( + rb_vm_opt_aset_with as *const u8, + vec![ + recv_opnd, + key_opnd, + value_opnd, + ], + ); + asm.stack_pop(2); // Keep it on stack during GC + + asm.cmp(val_opnd, Qundef.into()); + asm.je(Target::side_exit(Counter::opt_aset_with_qundef)); + + let top = asm.stack_push(Type::Unknown); + asm.mov(top, val_opnd); + + return Some(KeepCompiling); +} + fn gen_opt_aset( jit: &mut JITState, asm: &mut Assembler, @@ -6244,11 +6275,12 @@ fn jit_rb_str_dup( jit_prepare_call_with_gc(jit, asm); - // Check !FL_ANY_RAW(str, FL_EXIVAR), which is part of BARE_STRING_P. let recv_opnd = asm.stack_pop(1); let recv_opnd = asm.load(recv_opnd); - let flags_opnd = Opnd::mem(64, recv_opnd, RUBY_OFFSET_RBASIC_FLAGS); - asm.test(flags_opnd, Opnd::Imm(RUBY_FL_EXIVAR as i64)); + + let shape_id_offset = unsafe { rb_shape_id_offset() }; + let shape_opnd = Opnd::mem(64, recv_opnd, shape_id_offset); + asm.test(shape_opnd, Opnd::UImm(SHAPE_ID_HAS_IVAR_MASK as u64)); asm.jnz(Target::side_exit(Counter::send_str_dup_exivar)); // Call rb_str_dup @@ -10753,6 +10785,7 @@ fn get_gen_fn(opcode: VALUE) -> Option { YARVINSN_opt_aref => Some(gen_opt_aref), YARVINSN_opt_aset => Some(gen_opt_aset), YARVINSN_opt_aref_with => Some(gen_opt_aref_with), + YARVINSN_opt_aset_with => Some(gen_opt_aset_with), YARVINSN_opt_mult => Some(gen_opt_mult), YARVINSN_opt_div => Some(gen_opt_div), YARVINSN_opt_ltlt => Some(gen_opt_ltlt), @@ -11009,7 +11042,7 @@ impl CodegenGlobals { exec_mem_size, get_option!(mem_size), ); - let mem_block = Rc::new(RefCell::new(mem_block)); + let mem_block = Rc::new(mem_block); let freed_pages = Rc::new(None); diff --git a/yjit/src/core.rs b/yjit/src/core.rs index e31e54c106..57756e86ce 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -1920,7 +1920,7 @@ pub extern "C" fn rb_yjit_iseq_mark(payload: *mut c_void) { // For aliasing, having the VM lock hopefully also implies that no one // else has an overlapping &mut IseqPayload. unsafe { - rb_yjit_assert_holding_vm_lock(); + rb_assert_holding_vm_lock(); &*(payload as *const IseqPayload) } }; @@ -2009,7 +2009,7 @@ pub extern "C" fn rb_yjit_iseq_update_references(iseq: IseqPtr) { // For aliasing, having the VM lock hopefully also implies that no one // else has an overlapping &mut IseqPayload. unsafe { - rb_yjit_assert_holding_vm_lock(); + rb_assert_holding_vm_lock(); &*(payload as *const IseqPayload) } }; @@ -2035,13 +2035,6 @@ pub extern "C" fn rb_yjit_iseq_update_references(iseq: IseqPtr) { block_update_references(block, cb, true); } - // Note that we would have returned already if YJIT is off. - cb.mark_all_executable(); - - CodegenGlobals::get_outlined_cb() - .unwrap() - .mark_all_executable(); - return; fn block_update_references(block: &Block, cb: &mut CodeBlock, dead: bool) { @@ -2110,6 +2103,34 @@ pub extern "C" fn rb_yjit_iseq_update_references(iseq: IseqPtr) { } } +/// Mark all code memory as writable. +/// This function is useful for garbage collectors that update references in JIT-compiled code in +/// bulk. +#[no_mangle] +pub extern "C" fn rb_yjit_mark_all_writeable() { + if CodegenGlobals::has_instance() { + CodegenGlobals::get_inline_cb().mark_all_writeable(); + + CodegenGlobals::get_outlined_cb() + .unwrap() + .mark_all_writeable(); + } +} + +/// Mark all code memory as executable. +/// This function is useful for garbage collectors that update references in JIT-compiled code in +/// bulk. +#[no_mangle] +pub extern "C" fn rb_yjit_mark_all_executable() { + if CodegenGlobals::has_instance() { + CodegenGlobals::get_inline_cb().mark_all_executable(); + + CodegenGlobals::get_outlined_cb() + .unwrap() + .mark_all_executable(); + } +} + /// Get all blocks for a particular place in an iseq. fn get_version_list(blockid: BlockId) -> Option<&'static mut VersionList> { let insn_idx = blockid.idx.as_usize(); diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index 15f5ee933e..725a29fa70 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -441,25 +441,13 @@ impl VALUE { } pub fn shape_too_complex(self) -> bool { - unsafe { rb_shape_obj_too_complex_p(self) } + unsafe { rb_yjit_shape_obj_too_complex_p(self) } } pub fn shape_id_of(self) -> u32 { unsafe { rb_obj_shape_id(self) } } - pub fn shape_of(self) -> *mut rb_shape { - unsafe { - let shape = rb_shape_lookup(self.shape_id_of()); - - if shape.is_null() { - panic!("Shape should not be null"); - } else { - shape - } - } - } - pub fn embedded_p(self) -> bool { unsafe { FL_TEST_RAW(self, VALUE(ROBJECT_EMBED as usize)) != VALUE(0) diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 0afe9184a3..9b871c9e7c 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -225,10 +225,11 @@ pub const RUBY_FL_PROMOTED: ruby_fl_type = 32; pub const RUBY_FL_UNUSED6: ruby_fl_type = 64; pub const RUBY_FL_FINALIZE: ruby_fl_type = 128; pub const RUBY_FL_TAINT: ruby_fl_type = 0; +pub const RUBY_FL_EXIVAR: ruby_fl_type = 0; pub const RUBY_FL_SHAREABLE: ruby_fl_type = 256; pub const RUBY_FL_UNTRUSTED: ruby_fl_type = 0; pub const RUBY_FL_UNUSED9: ruby_fl_type = 512; -pub const RUBY_FL_EXIVAR: ruby_fl_type = 1024; +pub const RUBY_FL_UNUSED10: ruby_fl_type = 1024; pub const RUBY_FL_FREEZE: ruby_fl_type = 2048; pub const RUBY_FL_USER0: ruby_fl_type = 4096; pub const RUBY_FL_USER1: ruby_fl_type = 8192; @@ -303,7 +304,7 @@ pub const RARRAY_EMBED_LEN_MASK: ruby_rarray_flags = 4161536; pub type ruby_rarray_flags = u32; pub const RARRAY_EMBED_LEN_SHIFT: ruby_rarray_consts = 15; pub type ruby_rarray_consts = u32; -pub const RMODULE_IS_REFINEMENT: ruby_rmodule_flags = 32768; +pub const RMODULE_IS_REFINEMENT: ruby_rmodule_flags = 8192; pub type ruby_rmodule_flags = u32; pub const ROBJECT_EMBED: ruby_robject_flags = 8192; pub type ruby_robject_flags = u32; @@ -395,11 +396,6 @@ pub struct rb_namespace_struct { } pub type rb_namespace_t = rb_namespace_struct; pub type rb_serial_t = ::std::os::raw::c_ulonglong; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct rb_id_table { - _unused: [u8; 0], -} pub const imemo_env: imemo_type = 0; pub const imemo_cref: imemo_type = 1; pub const imemo_svar: imemo_type = 2; @@ -414,6 +410,7 @@ pub const imemo_parser_strterm: imemo_type = 10; pub const imemo_callinfo: imemo_type = 11; pub const imemo_callcache: imemo_type = 12; pub const imemo_constcache: imemo_type = 13; +pub const imemo_fields: imemo_type = 14; pub type imemo_type = u32; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -520,7 +517,7 @@ pub struct iseq_inline_constant_cache { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct iseq_inline_iv_cache_entry { - pub value: usize, + pub value: u64, pub iv_set_name: ID, } #[repr(C)] @@ -690,32 +687,10 @@ pub const VM_ENV_FLAG_ESCAPED: vm_frame_env_flags = 4; pub const VM_ENV_FLAG_WB_REQUIRED: vm_frame_env_flags = 8; pub const VM_ENV_FLAG_ISOLATED: vm_frame_env_flags = 16; pub type vm_frame_env_flags = u32; -pub type attr_index_t = u32; +pub type attr_index_t = u16; pub type shape_id_t = u32; -pub type redblack_id_t = u32; -pub type redblack_node_t = redblack_node; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct rb_shape { - pub edges: *mut rb_id_table, - pub edge_name: ID, - pub next_field_index: attr_index_t, - pub capacity: attr_index_t, - pub type_: u8, - pub heap_index: u8, - pub flags: u8, - pub parent_id: shape_id_t, - pub ancestor_index: *mut redblack_node_t, -} -pub type rb_shape_t = rb_shape; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct redblack_node { - pub key: ID, - pub value: *mut rb_shape_t, - pub l: redblack_id_t, - pub r: redblack_id_t, -} +pub const SHAPE_ID_HAS_IVAR_MASK: _bindgen_ty_37 = 134742014; +pub type _bindgen_ty_37 = u32; #[repr(C)] pub struct rb_cvar_class_tbl_entry { pub index: u32, @@ -1140,16 +1115,12 @@ extern "C" { pub fn rb_obj_info(obj: VALUE) -> *const ::std::os::raw::c_char; pub fn rb_ec_stack_check(ec: *mut rb_execution_context_struct) -> ::std::os::raw::c_int; pub fn rb_shape_id_offset() -> i32; - pub fn rb_shape_lookup(shape_id: shape_id_t) -> *mut rb_shape_t; pub fn rb_obj_shape_id(obj: VALUE) -> shape_id_t; - pub fn rb_shape_get_iv_index(shape: *mut rb_shape_t, id: ID, value: *mut attr_index_t) -> bool; - pub fn rb_shape_obj_too_complex_p(obj: VALUE) -> bool; - pub fn rb_shape_too_complex_p(shape: *mut rb_shape_t) -> bool; + pub fn rb_shape_get_iv_index(shape_id: shape_id_t, id: ID, value: *mut attr_index_t) -> bool; pub fn rb_shape_transition_add_ivar_no_warnings(obj: VALUE, id: ID) -> shape_id_t; - pub fn rb_shape_id(shape: *mut rb_shape_t) -> shape_id_t; pub fn rb_gvar_get(arg1: ID) -> VALUE; pub fn rb_gvar_set(arg1: ID, arg2: VALUE) -> VALUE; - pub fn rb_ensure_iv_list_size(obj: VALUE, len: u32, newsize: u32); + pub fn rb_ensure_iv_list_size(obj: VALUE, current_len: u32, newsize: u32); pub fn rb_vm_barrier(); pub fn rb_str_byte_substr(str_: VALUE, beg: VALUE, len: VALUE) -> VALUE; pub fn rb_str_substr_two_fixnums( @@ -1219,7 +1190,6 @@ extern "C" { pub fn rb_yjit_get_proc_ptr(procv: VALUE) -> *mut rb_proc_t; pub fn rb_get_symbol_id(namep: VALUE) -> ID; pub fn rb_get_def_bmethod_proc(def: *mut rb_method_definition_t) -> VALUE; - pub fn rb_get_iseq_body_parent_iseq(iseq: *const rb_iseq_t) -> *const rb_iseq_t; pub fn rb_optimized_call( recv: *mut VALUE, ec: *mut rb_execution_context_t, @@ -1236,7 +1206,6 @@ extern "C" { pub fn rb_vm_base_ptr(cfp: *mut rb_control_frame_struct) -> *mut VALUE; pub fn rb_yarv_str_eql_internal(str1: VALUE, str2: VALUE) -> VALUE; pub fn rb_str_neq_internal(str1: VALUE, str2: VALUE) -> VALUE; - pub fn rb_yarv_ary_entry_internal(ary: VALUE, offset: ::std::os::raw::c_long) -> VALUE; pub fn rb_ary_unshift_m(argc: ::std::os::raw::c_int, argv: *mut VALUE, ary: VALUE) -> VALUE; pub fn rb_yjit_rb_ary_subseq_length(ary: VALUE, beg: ::std::os::raw::c_long) -> VALUE; pub fn rb_yjit_fix_div_fix(recv: VALUE, obj: VALUE) -> VALUE; @@ -1272,7 +1241,10 @@ extern "C" { line: ::std::os::raw::c_int, ); pub fn rb_object_shape_count() -> VALUE; - pub fn rb_yjit_assert_holding_vm_lock(); + pub fn rb_yjit_shape_too_complex_p(shape_id: shape_id_t) -> bool; + pub fn rb_yjit_shape_obj_too_complex_p(obj: VALUE) -> bool; + pub fn rb_yjit_shape_capacity(shape_id: shape_id_t) -> attr_index_t; + pub fn rb_yjit_shape_index(shape_id: shape_id_t) -> attr_index_t; pub fn rb_yjit_sendish_sp_pops(ci: *const rb_callinfo) -> usize; pub fn rb_yjit_invokeblock_sp_pops(ci: *const rb_callinfo) -> usize; pub fn rb_yjit_set_exception_return( @@ -1313,6 +1285,7 @@ extern "C" { pub fn rb_get_mct_func(mct: *const rb_method_cfunc_t) -> *mut ::std::os::raw::c_void; pub fn rb_get_def_iseq_ptr(def: *mut rb_method_definition_t) -> *const rb_iseq_t; pub fn rb_get_iseq_body_local_iseq(iseq: *const rb_iseq_t) -> *const rb_iseq_t; + pub fn rb_get_iseq_body_parent_iseq(iseq: *const rb_iseq_t) -> *const rb_iseq_t; pub fn rb_get_iseq_body_local_table_size(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; pub fn rb_get_iseq_body_iseq_encoded(iseq: *const rb_iseq_t) -> *mut VALUE; pub fn rb_get_iseq_body_stack_max(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; @@ -1352,6 +1325,8 @@ extern "C" { pub fn rb_BASIC_OP_UNREDEFINED_P(bop: ruby_basic_operators, klass: u32) -> bool; pub fn rb_RCLASS_ORIGIN(c: VALUE) -> VALUE; pub fn rb_assert_iseq_handle(handle: VALUE); + pub fn rb_assert_holding_vm_lock(); pub fn rb_IMEMO_TYPE_P(imemo: VALUE, imemo_type: imemo_type) -> ::std::os::raw::c_int; pub fn rb_assert_cme_handle(handle: VALUE); + pub fn rb_yarv_ary_entry_internal(ary: VALUE, offset: ::std::os::raw::c_long) -> VALUE; } diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 8cbf3ab317..ba84b7a549 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -520,6 +520,7 @@ make_counters! { opt_aset_not_hash, opt_aref_with_qundef, + opt_aset_with_qundef, opt_case_dispatch_megamorphic, diff --git a/yjit/src/virtualmem.rs b/yjit/src/virtualmem.rs index f56b0d8213..aa6d21f210 100644 --- a/yjit/src/virtualmem.rs +++ b/yjit/src/virtualmem.rs @@ -3,7 +3,7 @@ // usize->pointer casts is viable. It seems like a lot of work for us to participate for not much // benefit. -use std::ptr::NonNull; +use std::{cell::RefCell, ptr::NonNull}; use crate::{backend::ir::Target, stats::yjit_alloc_size, utils::IntoUsize}; @@ -36,6 +36,12 @@ pub struct VirtualMemory { /// granularity. page_size_bytes: usize, + /// Mutable parts. + mutable: RefCell>, +} + +/// Mutable parts of [`VirtualMemory`]. +pub struct VirtualMemoryMut { /// Number of bytes that have we have allocated physical memory for starting at /// [Self::region_start]. mapped_region_bytes: usize, @@ -124,9 +130,11 @@ impl VirtualMemory { region_size_bytes, memory_limit_bytes, page_size_bytes, - mapped_region_bytes: 0, - current_write_page: None, - allocator, + mutable: RefCell::new(VirtualMemoryMut { + mapped_region_bytes: 0, + current_write_page: None, + allocator, + }), } } @@ -137,7 +145,7 @@ impl VirtualMemory { } pub fn mapped_end_ptr(&self) -> CodePtr { - self.start_ptr().add_bytes(self.mapped_region_bytes) + self.start_ptr().add_bytes(self.mutable.borrow().mapped_region_bytes) } pub fn virtual_end_ptr(&self) -> CodePtr { @@ -146,7 +154,7 @@ impl VirtualMemory { /// Size of the region in bytes that we have allocated physical memory for. pub fn mapped_region_size(&self) -> usize { - self.mapped_region_bytes + self.mutable.borrow().mapped_region_bytes } /// Size of the region in bytes where writes could be attempted. @@ -161,19 +169,21 @@ impl VirtualMemory { } /// Write a single byte. The first write to a page makes it readable. - pub fn write_byte(&mut self, write_ptr: CodePtr, byte: u8) -> Result<(), WriteError> { + pub fn write_byte(&self, write_ptr: CodePtr, byte: u8) -> Result<(), WriteError> { + let mut mutable = self.mutable.borrow_mut(); + let page_size = self.page_size_bytes; let raw: *mut u8 = write_ptr.raw_ptr(self) as *mut u8; let page_addr = (raw as usize / page_size) * page_size; - if self.current_write_page == Some(page_addr) { + if mutable.current_write_page == Some(page_addr) { // Writing within the last written to page, nothing to do } else { // Switching to a different and potentially new page let start = self.region_start.as_ptr(); - let mapped_region_end = start.wrapping_add(self.mapped_region_bytes); + let mapped_region_end = start.wrapping_add(mutable.mapped_region_bytes); let whole_region_end = start.wrapping_add(self.region_size_bytes); - let alloc = &mut self.allocator; + let alloc = &mut mutable.allocator; assert!((start..=whole_region_end).contains(&mapped_region_end)); @@ -185,7 +195,7 @@ impl VirtualMemory { return Err(FailedPageMapping); } - self.current_write_page = Some(page_addr); + mutable.current_write_page = Some(page_addr); } else if (start..whole_region_end).contains(&raw) && (page_addr + page_size - start as usize) + yjit_alloc_size() < self.memory_limit_bytes { // Writing to a brand new page @@ -217,9 +227,9 @@ impl VirtualMemory { unreachable!("unknown arch"); } } - self.mapped_region_bytes = self.mapped_region_bytes + alloc_size; + mutable.mapped_region_bytes = mutable.mapped_region_bytes + alloc_size; - self.current_write_page = Some(page_addr); + mutable.current_write_page = Some(page_addr); } else { return Err(OutOfBounds); } @@ -231,20 +241,41 @@ impl VirtualMemory { Ok(()) } - /// Make all the code in the region executable. Call this at the end of a write session. - /// See [Self] for usual usage flow. - pub fn mark_all_executable(&mut self) { - self.current_write_page = None; + /// Make all the code in the region writeable. + /// Call this during GC before the phase of updating reference fields. + pub fn mark_all_writeable(&self) { + let mut mutable = self.mutable.borrow_mut(); + + mutable.current_write_page = None; let region_start = self.region_start; - let mapped_region_bytes: u32 = self.mapped_region_bytes.try_into().unwrap(); + let mapped_region_bytes: u32 = mutable.mapped_region_bytes.try_into().unwrap(); // Make mapped region executable - self.allocator.mark_executable(region_start.as_ptr(), mapped_region_bytes); + if !mutable.allocator.mark_writable(region_start.as_ptr(), mapped_region_bytes) { + panic!("Cannot make memory region writable: {:?}-{:?}", + region_start.as_ptr(), + unsafe { region_start.as_ptr().add(mapped_region_bytes as usize)} + ); + } + } + + /// Make all the code in the region executable. Call this at the end of a write session. + /// See [Self] for usual usage flow. + pub fn mark_all_executable(&self) { + let mut mutable = self.mutable.borrow_mut(); + + mutable.current_write_page = None; + + let region_start = self.region_start; + let mapped_region_bytes: u32 = mutable.mapped_region_bytes.try_into().unwrap(); + + // Make mapped region executable + mutable.allocator.mark_executable(region_start.as_ptr(), mapped_region_bytes); } /// Free a range of bytes. start_ptr must be memory page-aligned. - pub fn free_bytes(&mut self, start_ptr: CodePtr, size: u32) { + pub fn free_bytes(&self, start_ptr: CodePtr, size: u32) { assert_eq!(start_ptr.raw_ptr(self) as usize % self.page_size_bytes, 0); // Bounds check the request. We should only free memory we manage. @@ -257,7 +288,8 @@ impl VirtualMemory { // code page, it's more appropriate to check the last byte against the virtual region. assert!(virtual_region.contains(&last_byte_to_free)); - self.allocator.mark_unused(start_ptr.raw_ptr(self), size); + let mut mutable = self.mutable.borrow_mut(); + mutable.allocator.mark_unused(start_ptr.raw_ptr(self), size); } } @@ -386,11 +418,11 @@ pub mod tests { #[test] #[cfg(target_arch = "x86_64")] fn new_memory_is_initialized() { - let mut virt = new_dummy_virt_mem(); + let virt = new_dummy_virt_mem(); virt.write_byte(virt.start_ptr(), 1).unwrap(); assert!( - virt.allocator.memory[..PAGE_SIZE].iter().all(|&byte| byte != 0), + virt.mutable.borrow().allocator.memory[..PAGE_SIZE].iter().all(|&byte| byte != 0), "Entire page should be initialized", ); @@ -398,21 +430,21 @@ pub mod tests { let three_pages = 3 * PAGE_SIZE; virt.write_byte(virt.start_ptr().add_bytes(three_pages), 1).unwrap(); assert!( - virt.allocator.memory[..three_pages].iter().all(|&byte| byte != 0), + virt.mutable.borrow().allocator.memory[..three_pages].iter().all(|&byte| byte != 0), "Gaps between write requests should be filled", ); } #[test] fn no_redundant_syscalls_when_writing_to_the_same_page() { - let mut virt = new_dummy_virt_mem(); + let virt = new_dummy_virt_mem(); virt.write_byte(virt.start_ptr(), 1).unwrap(); virt.write_byte(virt.start_ptr(), 0).unwrap(); assert!( matches!( - virt.allocator.requests[..], + virt.mutable.borrow().allocator.requests[..], [MarkWritable { start_idx: 0, length: PAGE_SIZE }], ) ); @@ -421,7 +453,7 @@ pub mod tests { #[test] fn bounds_checking() { use super::WriteError::*; - let mut virt = new_dummy_virt_mem(); + let virt = new_dummy_virt_mem(); let one_past_end = virt.start_ptr().add_bytes(virt.virtual_region_size()); assert_eq!(Err(OutOfBounds), virt.write_byte(one_past_end, 0)); @@ -434,7 +466,7 @@ pub mod tests { fn only_written_to_regions_become_executable() { // ... so we catch attempts to read/write/execute never-written-to regions const THREE_PAGES: usize = PAGE_SIZE * 3; - let mut virt = new_dummy_virt_mem(); + let virt = new_dummy_virt_mem(); let page_two_start = virt.start_ptr().add_bytes(PAGE_SIZE * 2); virt.write_byte(page_two_start, 1).unwrap(); virt.mark_all_executable(); @@ -442,7 +474,7 @@ pub mod tests { assert!(virt.virtual_region_size() > THREE_PAGES); assert!( matches!( - virt.allocator.requests[..], + virt.mutable.borrow().allocator.requests[..], [ MarkWritable { start_idx: 0, length: THREE_PAGES }, MarkExecutable { start_idx: 0, length: THREE_PAGES }, diff --git a/yjit/yjit.mk b/yjit/yjit.mk index 90f14568da..c6571ecbc8 100644 --- a/yjit/yjit.mk +++ b/yjit/yjit.mk @@ -1,10 +1,5 @@ # -*- mode: makefile-gmake; indent-tabs-mode: t -*- -# Show Cargo progress when doing `make V=1` -CARGO_VERBOSE_0 = -q -CARGO_VERBOSE_1 = -CARGO_VERBOSE = $(CARGO_VERBOSE_$(V)) - YJIT_SRC_FILES = $(wildcard \ $(top_srcdir)/yjit/Cargo.* \ $(top_srcdir)/yjit/src/*.rs \ @@ -19,60 +14,21 @@ YJIT_SRC_FILES = $(wildcard \ # rebuild at the next build. YJIT_LIB_TOUCH = touch $@ +# Absolute path to match RUST_LIB rules to avoid picking +# the "target" dir in the source directory through VPATH. +BUILD_YJIT_LIBS = $(TOP_BUILD_DIR)/$(YJIT_LIBS) + # YJIT_SUPPORT=yes when `configure` gets `--enable-yjit` ifeq ($(YJIT_SUPPORT),yes) -$(YJIT_LIBS): $(YJIT_SRC_FILES) +yjit-libs: $(BUILD_YJIT_LIBS) +$(BUILD_YJIT_LIBS): $(YJIT_SRC_FILES) $(ECHO) 'building Rust YJIT (release mode)' +$(Q) $(RUSTC) $(YJIT_RUSTC_ARGS) $(YJIT_LIB_TOUCH) -else ifeq ($(YJIT_SUPPORT),no) -$(YJIT_LIBS): - $(ECHO) 'Error: Tried to build YJIT without configuring it first. Check `make showconfig`?' - @false -else ifeq ($(YJIT_SUPPORT),$(filter dev dev_nodebug stats,$(YJIT_SUPPORT))) -# NOTE: MACOSX_DEPLOYMENT_TARGET to match `rustc --print deployment-target` to avoid the warning below. -# ld: warning: object file (yjit/target/debug/libyjit.a()) was built for -# newer macOS version (15.2) than being linked (15.0) -# We don't use newer macOS feature as of yet. -$(YJIT_LIBS): $(YJIT_SRC_FILES) - $(ECHO) 'building Rust YJIT ($(YJIT_SUPPORT) mode)' - +$(Q)$(CHDIR) $(top_srcdir)/yjit && \ - CARGO_TARGET_DIR='$(CARGO_TARGET_DIR)' \ - CARGO_TERM_PROGRESS_WHEN='never' \ - MACOSX_DEPLOYMENT_TARGET=11.0 \ - $(CARGO) $(CARGO_VERBOSE) build $(CARGO_BUILD_ARGS) - $(YJIT_LIB_TOUCH) -else endif -yjit-libobj: $(YJIT_LIBOBJ) - -YJIT_LIB_SYMBOLS = $(YJIT_LIBS:.a=).symbols -$(YJIT_LIBOBJ): $(YJIT_LIBS) - $(ECHO) 'partial linking $(YJIT_LIBS) into $@' -ifneq ($(findstring darwin,$(target_os)),) - $(Q) $(CC) -nodefaultlibs -r -o $@ -exported_symbols_list $(YJIT_LIB_SYMBOLS) $(YJIT_LIBS) -else - $(Q) $(LD) -r -o $@ --whole-archive $(YJIT_LIBS) - -$(Q) $(OBJCOPY) --wildcard --keep-global-symbol='$(SYMBOL_PREFIX)rb_*' $(@) -endif - -# For Darwin only: a list of symbols that we want the glommed Rust static lib to export. -# Unfortunately, using wildcard like '_rb_*' with -exported-symbol does not work, at least -# not on version 820.1. Assume llvm-nm, so XCode 8.0 (from 2016) or newer. -# -# The -exported_symbols_list pulls out the right archive members. Symbols not listed -# in the list are made private extern, which are in turn made local as we're using `ld -r`. -# Note, section about -keep_private_externs in ld's man page hints at this behavior on which -# we rely. -ifneq ($(findstring darwin,$(target_os)),) -$(YJIT_LIB_SYMBOLS): $(YJIT_LIBS) - $(Q) $(tooldir)/darwin-ar $(NM) --defined-only --extern-only $(YJIT_LIBS) | \ - sed -n -e 's/.* //' -e '/^$(SYMBOL_PREFIX)rb_/p' \ - -e '/^$(SYMBOL_PREFIX)rust_eh_personality/p' \ - > $@ - -$(YJIT_LIBOBJ): $(YJIT_LIB_SYMBOLS) +ifneq ($(YJIT_SUPPORT),no) +$(RUST_LIB): $(YJIT_SRC_FILES) endif # By using YJIT_BENCH_OPTS instead of RUN_OPTS, you can skip passing the options to `make install` @@ -94,7 +50,7 @@ RUST_VERSION = +1.58.0 .PHONY: yjit-smoke-test yjit-smoke-test: ifneq ($(strip $(CARGO)),) - $(CARGO) $(RUST_VERSION) test --all-features -q --manifest-path='$(top_srcdir)/yjit/Cargo.toml' + $(CARGO) test --all-features -q --manifest-path='$(top_srcdir)/yjit/Cargo.toml' endif $(MAKE) btest RUN_OPTS='--yjit-call-threshold=1' BTESTS=-j $(MAKE) test-all TESTS='$(top_srcdir)/test/ruby/test_yjit.rb' diff --git a/zjit.c b/zjit.c index 620b9d6af3..61c17d32c3 100644 --- a/zjit.c +++ b/zjit.c @@ -9,6 +9,7 @@ #include "internal/numeric.h" #include "internal/gc.h" #include "internal/vm.h" +#include "yjit.h" #include "vm_core.h" #include "vm_callinfo.h" #include "builtin.h" @@ -161,20 +162,19 @@ void rb_zjit_profile_disable(const rb_iseq_t *iseq); void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception) { - RB_VM_LOCK_ENTER(); - rb_vm_barrier(); + RB_VM_LOCKING() { + rb_vm_barrier(); - // Convert ZJIT instructions back to bare instructions - rb_zjit_profile_disable(iseq); + // Convert ZJIT instructions back to bare instructions + rb_zjit_profile_disable(iseq); - // Compile a block version starting at the current instruction - uint8_t *rb_zjit_iseq_gen_entry_point(const rb_iseq_t *iseq, rb_execution_context_t *ec); // defined in Rust - uintptr_t code_ptr = (uintptr_t)rb_zjit_iseq_gen_entry_point(iseq, ec); + // Compile a block version starting at the current instruction + uint8_t *rb_zjit_iseq_gen_entry_point(const rb_iseq_t *iseq, rb_execution_context_t *ec); // defined in Rust + uintptr_t code_ptr = (uintptr_t)rb_zjit_iseq_gen_entry_point(iseq, ec); - // TODO: support jit_exception - iseq->body->jit_entry = (rb_jit_func_t)code_ptr; - - RB_VM_LOCK_LEAVE(); + // TODO: support jit_exception + iseq->body->jit_entry = (rb_jit_func_t)code_ptr; +} } extern VALUE *rb_vm_base_ptr(struct rb_control_frame_struct *cfp); @@ -295,6 +295,18 @@ rb_zjit_profile_disable(const rb_iseq_t *iseq) } } +// Update a YARV instruction to a given opcode (to disable ZJIT profiling). +void +rb_zjit_iseq_insn_set(const rb_iseq_t *iseq, unsigned int insn_idx, enum ruby_vminsn_type bare_insn) +{ +#if RUBY_DEBUG + int insn = rb_vm_insn_addr2opcode((void *)iseq->body->iseq_encoded[insn_idx]); + RUBY_ASSERT(vm_zjit_insn_to_bare_insn(insn) == (int)bare_insn); +#endif + const void *const *insn_table = rb_vm_get_insns_address_table(); + iseq->body->iseq_encoded[insn_idx] = (VALUE)insn_table[bare_insn]; +} + // Get profiling information for ISEQ void * rb_iseq_get_zjit_payload(const rb_iseq_t *iseq) @@ -331,5 +343,11 @@ rb_zjit_print_exception(void) rb_warn("Ruby error: %"PRIsVALUE"", rb_funcall(exception, rb_intern("full_message"), 0)); } +bool +rb_zjit_shape_obj_too_complex_p(VALUE obj) +{ + return rb_shape_obj_too_complex_p(obj); +} + // Preprocessed zjit.rb generated during build #include "zjit.rbinc" diff --git a/zjit.h b/zjit.h index b4a89d308a..84df6d009e 100644 --- a/zjit.h +++ b/zjit.h @@ -13,7 +13,10 @@ void rb_zjit_profile_insn(enum ruby_vminsn_type insn, rb_execution_context_t *ec void rb_zjit_profile_enable(const rb_iseq_t *iseq); void rb_zjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop); void rb_zjit_invalidate_ep_is_bp(const rb_iseq_t *iseq); +void rb_zjit_iseq_mark(void *payload); +void rb_zjit_iseq_update_references(void *payload); #else +#define rb_zjit_enabled_p false static inline void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception) {} static inline void rb_zjit_profile_insn(enum ruby_vminsn_type insn, rb_execution_context_t *ec) {} static inline void rb_zjit_profile_enable(const rb_iseq_t *iseq) {} diff --git a/zjit/Cargo.toml b/zjit/Cargo.toml index ed8e66be02..a1da8e7cc0 100644 --- a/zjit/Cargo.toml +++ b/zjit/Cargo.toml @@ -1,25 +1,10 @@ [package] name = "zjit" version = "0.0.1" -edition = "2024" # Rust 2021 edition to compile with +edition = "2024" rust-version = "1.85.0" # Minimally supported rust version publish = false # Don't publish to crates.io -[lib] -crate-type = ["staticlib"] - -[profile.release] -# NOTE: --enable-zjit builds use `rustc` without going through Cargo. You -# might want to update the `rustc` invocation if you change this profile. -opt-level = 3 -# The extra robustness that comes from checking for arithmetic overflow is -# worth the performance cost for the compiler. -overflow-checks = true -# Generate debug info -debug = true -# Use ThinLTO. Much smaller output for a small amount of build time increase. -lto = "thin" - [dependencies] # No required dependencies to simplify build process. TODO: Link to yet to be # written rationale. Optional For development and testing purposes @@ -33,3 +18,4 @@ expect-test = "1.5.1" [features] # Support --yjit-dump-disasm and RubyVM::YJIT.disasm using libcapstone. disasm = ["capstone"] +runtime_checks = [] diff --git a/zjit/bindgen/Cargo.toml b/zjit/bindgen/Cargo.toml index 2824f31213..2f20f18016 100644 --- a/zjit/bindgen/Cargo.toml +++ b/zjit/bindgen/Cargo.toml @@ -8,3 +8,5 @@ edition = "2024" [dependencies] bindgen = "0.71.1" env_logger = "0.11.5" + +[workspace] diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 8162f9a9ed..cb15c3657e 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -108,12 +108,10 @@ fn main() { // From shape.h .allowlist_function("rb_obj_shape_id") - .allowlist_function("rb_shape_lookup") .allowlist_function("rb_shape_id_offset") .allowlist_function("rb_shape_get_iv_index") .allowlist_function("rb_shape_transition_add_ivar_no_warnings") - .allowlist_function("rb_shape_id") - .allowlist_function("rb_shape_obj_too_complex_p") + .allowlist_function("rb_zjit_shape_obj_too_complex_p") .allowlist_var("SHAPE_ID_NUM_BITS") // From ruby/internal/intern/object.h @@ -169,6 +167,7 @@ fn main() { .allowlist_function("rb_gc_mark_movable") .allowlist_function("rb_gc_location") .allowlist_function("rb_gc_writebarrier") + .allowlist_function("rb_gc_writebarrier_remember") // VALUE variables for Ruby class objects // From include/ruby/internal/globals.h @@ -183,11 +182,14 @@ fn main() { .allowlist_var("rb_cSymbol") .allowlist_var("rb_cFloat") .allowlist_var("rb_cNumeric") + .allowlist_var("rb_cRange") .allowlist_var("rb_cString") .allowlist_var("rb_cThread") .allowlist_var("rb_cArray") .allowlist_var("rb_cHash") + .allowlist_var("rb_cSet") .allowlist_var("rb_cClass") + .allowlist_var("rb_cRegexp") .allowlist_var("rb_cISeq") // From include/ruby/internal/fl_type.h @@ -228,6 +230,7 @@ fn main() { .allowlist_function("rb_sym2id") .allowlist_function("rb_str_intern") .allowlist_function("rb_id2str") + .allowlist_function("rb_sym2str") // From internal/numeric.h .allowlist_function("rb_fix_aref") @@ -333,6 +336,7 @@ fn main() { .allowlist_function("rb_zjit_get_page_size") .allowlist_function("rb_zjit_iseq_builtin_attrs") .allowlist_function("rb_zjit_iseq_inspect") + .allowlist_function("rb_zjit_iseq_insn_set") .allowlist_function("rb_set_cfp_(pc|sp)") .allowlist_function("rb_c_method_tracing_currently_enabled") .allowlist_function("rb_full_cfunc_return") @@ -350,6 +354,9 @@ fn main() { .allowlist_type("robject_offsets") .allowlist_type("rstring_offsets") + // From jit.c + .allowlist_function("rb_assert_holding_vm_lock") + // from vm_sync.h .allowlist_function("rb_vm_barrier") @@ -368,6 +375,7 @@ fn main() { .allowlist_function("rb_iseqw_to_iseq") .allowlist_function("rb_iseq_label") .allowlist_function("rb_iseq_line_no") + .allowlist_function("rb_iseq_defined_string") .allowlist_type("defined_type") // From builtin.h @@ -381,6 +389,7 @@ fn main() { .allowlist_function("rb_attr_get") .allowlist_function("rb_ivar_defined") .allowlist_function("rb_ivar_get") + .allowlist_function("rb_ivar_set") .allowlist_function("rb_mod_name") // From include/ruby/internal/intern/vm.h diff --git a/zjit/build.rs b/zjit/build.rs index cf402fbc1d..6aec5407f6 100644 --- a/zjit/build.rs +++ b/zjit/build.rs @@ -1,7 +1,10 @@ +// This build script is only used for `make zjit-test` for building +// the test binary; ruby builds don't use this. fn main() { use std::env; - if let Ok(ruby_build_dir) = env::var("RUBY_BUILD_DIR") { + // option_env! automatically registers a rerun-if-env-changed + if let Some(ruby_build_dir) = option_env!("RUBY_BUILD_DIR") { // Link against libminiruby println!("cargo:rustc-link-search=native={ruby_build_dir}"); println!("cargo:rustc-link-lib=static:-bundle=miniruby"); @@ -20,5 +23,13 @@ fn main() { println!("cargo:rustc-link-lib={lib_name}"); } } + + // When doing a combo build, there is a copy of ZJIT symbols in libruby.a + // and Cargo builds another copy for the test binary. Tell the linker to + // not complaint about duplicate symbols. For some reason, darwin doesn't + // suffer the same issue. + if env::var("TARGET").unwrap().contains("linux") { + println!("cargo:rustc-link-arg=-Wl,--allow-multiple-definition"); + } } } diff --git a/zjit/src/asm/arm64/mod.rs b/zjit/src/asm/arm64/mod.rs index a5d73d71a5..ef477821aa 100644 --- a/zjit/src/asm/arm64/mod.rs +++ b/zjit/src/asm/arm64/mod.rs @@ -644,7 +644,7 @@ pub fn mov(cb: &mut CodeBlock, rd: A64Opnd, rm: A64Opnd) { LogicalImm::mov(rd.reg_no, bitmask_imm, rd.num_bits).into() }, - _ => panic!("Invalid operand combination to mov instruction") + _ => panic!("Invalid operand combination to mov instruction: {rd:?}, {rm:?}") }; cb.write_bytes(&bytes); @@ -936,11 +936,11 @@ pub fn stur(cb: &mut CodeBlock, rt: A64Opnd, rn: A64Opnd) { let bytes: [u8; 4] = match (rt, rn) { (A64Opnd::Reg(rt), A64Opnd::Mem(rn)) => { assert!(rn.num_bits == 32 || rn.num_bits == 64); - assert!(mem_disp_fits_bits(rn.disp), "Expected displacement to be 9 bits or less"); + assert!(mem_disp_fits_bits(rn.disp), "Expected displacement {} to be 9 bits or less", rn.disp); LoadStore::stur(rt.reg_no, rn.base_reg_no, rn.disp as i16, rn.num_bits).into() }, - _ => panic!("Invalid operand combination to stur instruction.") + _ => panic!("Invalid operand combination to stur instruction: {rt:?}, {rn:?}") }; cb.write_bytes(&bytes); diff --git a/zjit/src/asm/arm64/opnd.rs b/zjit/src/asm/arm64/opnd.rs index 6e31851504..28422b7476 100644 --- a/zjit/src/asm/arm64/opnd.rs +++ b/zjit/src/asm/arm64/opnd.rs @@ -119,6 +119,9 @@ pub const X20_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 20 }; pub const X21_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 21 }; pub const X22_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 22 }; +// link register +pub const X30_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 30 }; + // zero register pub const XZR_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 31 }; @@ -153,7 +156,7 @@ pub const X26: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 26 }); pub const X27: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 27 }); pub const X28: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 28 }); pub const X29: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 29 }); -pub const X30: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 64, reg_no: 30 }); +pub const X30: A64Opnd = A64Opnd::Reg(X30_REG); pub const X31: A64Opnd = A64Opnd::Reg(XZR_REG); // 32-bit registers diff --git a/zjit/src/asm/mod.rs b/zjit/src/asm/mod.rs index a7f2705af1..3b9b8a26f7 100644 --- a/zjit/src/asm/mod.rs +++ b/zjit/src/asm/mod.rs @@ -1,5 +1,5 @@ use std::collections::BTreeMap; -//use std::fmt; +use std::fmt; use std::rc::Rc; use std::cell::RefCell; use std::mem; @@ -106,13 +106,17 @@ impl CodeBlock { self.write_pos } + pub fn write_mem(&self, write_ptr: CodePtr, byte: u8) -> Result<(), WriteError> { + self.mem_block.borrow_mut().write_byte(write_ptr, byte) + } + /// Get a (possibly dangling) direct pointer to the current write position pub fn get_write_ptr(&self) -> CodePtr { self.get_ptr(self.write_pos) } /// Set the current write position from a pointer - fn set_write_ptr(&mut self, code_ptr: CodePtr) { + pub fn set_write_ptr(&mut self, code_ptr: CodePtr) { let pos = code_ptr.as_offset() - self.mem_block.borrow().start_ptr().as_offset(); self.write_pos = pos.try_into().unwrap(); } @@ -248,6 +252,11 @@ impl CodeBlock { assert!(self.label_refs.is_empty()); } + /// Convert a Label to CodePtr + pub fn resolve_label(&self, label: Label) -> CodePtr { + self.get_ptr(self.label_addrs[label.0]) + } + pub fn clear_labels(&mut self) { self.label_addrs.clear(); self.label_names.clear(); @@ -260,6 +269,18 @@ impl CodeBlock { } } +/// Produce hex string output from the bytes in a code block +impl fmt::LowerHex for CodeBlock { + fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result { + for pos in 0..self.write_pos { + let mem_block = &*self.mem_block.borrow(); + let byte = unsafe { mem_block.start_ptr().raw_ptr(mem_block).add(pos).read() }; + fmtr.write_fmt(format_args!("{:02x}", byte))?; + } + Ok(()) + } +} + #[cfg(test)] impl CodeBlock { /// Stubbed CodeBlock for testing. Can't execute generated code. diff --git a/zjit/src/asm/x86_64/mod.rs b/zjit/src/asm/x86_64/mod.rs index efc58dfdb8..fea66c8a3b 100644 --- a/zjit/src/asm/x86_64/mod.rs +++ b/zjit/src/asm/x86_64/mod.rs @@ -1024,7 +1024,10 @@ pub fn mov(cb: &mut CodeBlock, dst: X86Opnd, src: X86Opnd) { } let output_num_bits:u32 = if mem.num_bits > 32 { 32 } else { mem.num_bits.into() }; - assert!(imm_num_bits(imm.value) <= (output_num_bits as u8)); + assert!( + mem.num_bits < 64 || imm_num_bits(imm.value) <= (output_num_bits as u8), + "immediate value should be small enough to survive sign extension" + ); cb.write_int(imm.value as u64, output_num_bits); }, // M + UImm @@ -1039,7 +1042,10 @@ pub fn mov(cb: &mut CodeBlock, dst: X86Opnd, src: X86Opnd) { } let output_num_bits = if mem.num_bits > 32 { 32 } else { mem.num_bits.into() }; - assert!(imm_num_bits(uimm.value as i64) <= (output_num_bits as u8)); + assert!( + mem.num_bits < 64 || imm_num_bits(uimm.value as i64) <= (output_num_bits as u8), + "immediate value should be small enough to survive sign extension" + ); cb.write_int(uimm.value, output_num_bits); }, // * + Imm/UImm diff --git a/zjit/src/asm/x86_64/tests.rs b/zjit/src/asm/x86_64/tests.rs index f2b949b7f7..ec490fd330 100644 --- a/zjit/src/asm/x86_64/tests.rs +++ b/zjit/src/asm/x86_64/tests.rs @@ -1,11 +1,10 @@ #![cfg(test)] -//use crate::asm::x86_64::*; +use crate::asm::x86_64::*; -/* /// Check that the bytes for an instruction sequence match a hex string fn check_bytes(bytes: &str, run: R) where R: FnOnce(&mut super::CodeBlock) { - let mut cb = super::CodeBlock::new_dummy(4096); + let mut cb = super::CodeBlock::new_dummy(); run(&mut cb); assert_eq!(format!("{:x}", cb), bytes); } @@ -194,6 +193,7 @@ fn test_mov() { check_bytes("48c7470801000000", |cb| mov(cb, mem_opnd(64, RDI, 8), imm_opnd(1))); //check_bytes("67c7400411000000", |cb| mov(cb, mem_opnd(32, EAX, 4), imm_opnd(0x34))); // We don't distinguish between EAX and RAX here - that's probably fine? check_bytes("c7400411000000", |cb| mov(cb, mem_opnd(32, RAX, 4), imm_opnd(17))); + check_bytes("c7400401000080", |cb| mov(cb, mem_opnd(32, RAX, 4), uimm_opnd(0x80000001))); check_bytes("41895814", |cb| mov(cb, mem_opnd(32, R8, 20), EBX)); check_bytes("4d8913", |cb| mov(cb, mem_opnd(64, R11, 0), R10)); check_bytes("48c742f8f4ffffff", |cb| mov(cb, mem_opnd(64, RDX, -8), imm_opnd(-12))); @@ -439,9 +439,10 @@ fn basic_capstone_usage() -> std::result::Result<(), capstone::Error> { } #[test] +#[ignore] #[cfg(feature = "disasm")] fn block_comments() { - let mut cb = super::CodeBlock::new_dummy(4096); + let mut cb = super::CodeBlock::new_dummy(); let first_write_ptr = cb.get_write_ptr().raw_addr(&cb); cb.add_comment("Beginning"); @@ -458,4 +459,3 @@ fn block_comments() { assert_eq!(&vec!( "Two bytes in".to_string(), "Still two bytes in".to_string() ), cb.comments_at(second_write_ptr).unwrap()); assert_eq!(&vec!( "Ten bytes in".to_string() ), cb.comments_at(third_write_ptr).unwrap()); } -*/ diff --git a/zjit/src/assertions.rs b/zjit/src/assertions.rs new file mode 100644 index 0000000000..0dacc938fc --- /dev/null +++ b/zjit/src/assertions.rs @@ -0,0 +1,21 @@ +/// Assert that CodeBlock has the code specified with hex. In addition, if tested with +/// `cargo test --all-features`, it also checks it generates the specified disasm. +#[cfg(test)] +macro_rules! assert_disasm { + ($cb:expr, $hex:expr, $disasm:expr) => { + #[cfg(feature = "disasm")] + { + use $crate::disasm::disasm_addr_range; + use $crate::cruby::unindent; + let disasm = disasm_addr_range( + &$cb, + $cb.get_ptr(0).raw_addr(&$cb), + $cb.get_write_ptr().raw_addr(&$cb), + ); + assert_eq!(unindent(&disasm, false), unindent(&$disasm, true)); + } + assert_eq!(format!("{:x}", $cb), $hex); + }; +} +#[cfg(test)] +pub(crate) use assert_disasm; diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index ffde567b69..e97c06414d 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -28,6 +28,7 @@ pub const _C_ARG_OPNDS: [Opnd; 6] = [ // C return value register on this platform pub const C_RET_REG: Reg = X0_REG; pub const _C_RET_OPND: Opnd = Opnd::Reg(X0_REG); +pub const _NATIVE_STACK_PTR: Opnd = Opnd::Reg(XZR_REG); // These constants define the way we work with Arm64's stack pointer. The stack // pointer always needs to be aligned to a 16-byte boundary. @@ -187,12 +188,6 @@ pub const ALLOC_REGS: &'static [Reg] = &[ X12_REG, ]; -#[derive(Debug, PartialEq)] -enum EmitError { - RetryOnNextPage, - OutOfMemory, -} - impl Assembler { // Special scratch registers for intermediate processing. @@ -211,6 +206,11 @@ impl Assembler vec![X1_REG, X9_REG, X10_REG, X11_REG, X12_REG, X13_REG, X14_REG, X15_REG] } + /// How many bytes a call and a [Self::frame_setup] would change native SP + pub fn frame_size() -> i32 { + 0x10 + } + /// Split platform-specific instructions /// The transformations done here are meant to make our lives simpler in later /// stages of the compilation pipeline. @@ -415,24 +415,36 @@ impl Assembler // being used. It is okay not to use their output here. #[allow(unused_must_use)] match &mut insn { - Insn::Add { left, right, .. } => { + Insn::Add { left, right, out } => { match (*left, *right) { - (Opnd::Reg(_) | Opnd::VReg { .. }, Opnd::Reg(_) | Opnd::VReg { .. }) => { - asm.push_insn(insn); - }, + // When one operand is a register, legalize the other operand + // into possibly an immdiate and swap the order if necessary. + // Only the rhs of ADD can be an immediate, but addition is commutative. (reg_opnd @ (Opnd::Reg(_) | Opnd::VReg { .. }), other_opnd) | (other_opnd, reg_opnd @ (Opnd::Reg(_) | Opnd::VReg { .. })) => { *left = reg_opnd; *right = split_shifted_immediate(asm, other_opnd); + // Now `right` is either a register or an immediate, both can try to + // merge with a subsequent mov. + merge_three_reg_mov(&live_ranges, &mut iterator, left, left, out); asm.push_insn(insn); - }, + } _ => { *left = split_load_operand(asm, *left); *right = split_shifted_immediate(asm, *right); + merge_three_reg_mov(&live_ranges, &mut iterator, left, right, out); asm.push_insn(insn); } } - }, + } + Insn::Sub { left, right, out } => { + *left = split_load_operand(asm, *left); + *right = split_shifted_immediate(asm, *right); + // Now `right` is either a register or an immediate, + // both can try to merge with a subsequent mov. + merge_three_reg_mov(&live_ranges, &mut iterator, left, left, out); + asm.push_insn(insn); + } Insn::And { left, right, out } | Insn::Or { left, right, out } | Insn::Xor { left, right, out } => { @@ -440,18 +452,7 @@ impl Assembler *left = opnd0; *right = opnd1; - // Since these instructions are lowered to an instruction that have 2 input - // registers and an output register, look to merge with an `Insn::Mov` that - // follows which puts the output in another register. For example: - // `Add a, b => out` followed by `Mov c, out` becomes `Add a, b => c`. - if let (Opnd::Reg(_), Opnd::Reg(_), Some(Insn::Mov { dest, src })) = (left, right, iterator.peek().map(|(_, insn)| insn)) { - if live_ranges[out.vreg_idx()].end() == index + 1 { - if out == src && matches!(*dest, Opnd::Reg(_)) { - *out = *dest; - iterator.next(); // Pop merged Insn::Mov - } - } - } + merge_three_reg_mov(&live_ranges, &mut iterator, left, right, out); asm.push_insn(insn); } @@ -636,7 +637,7 @@ impl Assembler }, // If we're loading a memory operand into a register, then // we'll switch over to the load instruction. - (Opnd::Reg(_), Opnd::Mem(_)) => { + (Opnd::Reg(_) | Opnd::VReg { .. }, Opnd::Mem(_)) => { let value = split_memory_address(asm, *src); asm.load_into(*dest, value); }, @@ -649,7 +650,7 @@ impl Assembler }; asm.mov(*dest, value); }, - _ => unreachable!() + _ => unreachable!("unexpected combination of operands in Insn::Mov: {dest:?}, {src:?}") }; }, Insn::Not { opnd, .. } => { @@ -666,12 +667,7 @@ impl Assembler Insn::URShift { opnd, .. } => { // The operand must be in a register, so // if we get anything else we need to load it first. - let opnd0 = match opnd { - Opnd::Mem(_) => split_load_operand(asm, *opnd), - _ => *opnd - }; - - *opnd = opnd0; + *opnd = split_load_operand(asm, *opnd); asm.push_insn(insn); }, Insn::Store { dest, src } => { @@ -699,11 +695,6 @@ impl Assembler } } }, - Insn::Sub { left, right, .. } => { - *left = split_load_operand(asm, *left); - *right = split_shifted_immediate(asm, *right); - asm.push_insn(insn); - }, Insn::Mul { left, right, .. } => { *left = split_load_operand(asm, *left); *right = split_load_operand(asm, *right); @@ -732,7 +723,7 @@ impl Assembler /// Emit platform-specific machine code /// Returns a list of GC offsets. Can return failure to signal caller to retry. - fn arm64_emit(&mut self, cb: &mut CodeBlock) -> Result, EmitError> { + fn arm64_emit(&mut self, cb: &mut CodeBlock) -> Option> { /// Determine how many instructions it will take to represent moving /// this value into a register. Note that the return value of this /// function must correspond to how many instructions are used to @@ -757,7 +748,7 @@ impl Assembler /// called when lowering any of the conditional jump instructions. fn emit_conditional_jump(cb: &mut CodeBlock, target: Target) { match target { - Target::CodePtr(dst_ptr) | Target::SideExitPtr(dst_ptr) => { + Target::CodePtr(dst_ptr) => { let dst_addr = dst_ptr.as_offset(); let src_addr = cb.get_write_ptr().as_offset(); @@ -829,8 +820,10 @@ impl Assembler } /// Emit a CBZ or CBNZ which branches when a register is zero or non-zero - fn emit_cmp_zero_jump(cb: &mut CodeBlock, reg: A64Opnd, branch_if_zero: bool, target: Target) { - if let Target::SideExitPtr(dst_ptr) = target { + fn emit_cmp_zero_jump(_cb: &mut CodeBlock, _reg: A64Opnd, _branch_if_zero: bool, target: Target) { + if let Target::Label(_) = target { + unimplemented!("this should be re-implemented with Label for side exits"); + /* let dst_addr = dst_ptr.as_offset(); let src_addr = cb.get_write_ptr().as_offset(); @@ -862,6 +855,7 @@ impl Assembler br(cb, Assembler::SCRATCH0); } + */ } else { unreachable!("We should only generate Joz/Jonz with side-exit targets"); } @@ -879,23 +873,18 @@ impl Assembler ldr_post(cb, opnd, A64Opnd::new_mem(64, C_SP_REG, C_SP_STEP)); } - // dbg!(&self.insns); - // List of GC offsets - let mut gc_offsets: Vec = Vec::new(); + let mut gc_offsets: Vec = Vec::new(); // Buffered list of PosMarker callbacks to fire if codegen is successful let mut pos_markers: Vec<(usize, CodePtr)> = vec![]; + // The write_pos for the last Insn::PatchPoint, if any + let mut last_patch_pos: Option = None; + // For each instruction - //let start_write_pos = cb.get_write_pos(); let mut insn_idx: usize = 0; while let Some(insn) = self.insns.get(insn_idx) { - //let src_ptr = cb.get_write_ptr(); - let had_dropped_bytes = cb.has_dropped_bytes(); - //let old_label_state = cb.get_label_state(); - let mut insn_gc_offsets: Vec = Vec::new(); - match insn { Insn::Comment(text) => { cb.add_comment(text); @@ -935,10 +924,28 @@ impl Assembler ldp_post(cb, X29, X30, A64Opnd::new_mem(128, C_SP_REG, 16)); }, Insn::Add { left, right, out } => { - adds(cb, out.into(), left.into(), right.into()); + // Usually, we issue ADDS, so you could branch on overflow, but ADDS with + // out=31 refers to out=XZR, which discards the sum. So, instead of ADDS + // (aliased to CMN in this case) we issue ADD instead which writes the sum + // to the stack pointer; we assume you got x31 from NATIVE_STACK_POINTER. + let out: A64Opnd = out.into(); + if let A64Opnd::Reg(A64Reg { reg_no: 31, .. }) = out { + add(cb, out, left.into(), right.into()); + } else { + adds(cb, out, left.into(), right.into()); + } }, Insn::Sub { left, right, out } => { - subs(cb, out.into(), left.into(), right.into()); + // Usually, we issue SUBS, so you could branch on overflow, but SUBS with + // out=31 refers to out=XZR, which discards the result. So, instead of SUBS + // (aliased to CMP in this case) we issue SUB instead which writes the diff + // to the stack pointer; we assume you got x31 from NATIVE_STACK_POINTER. + let out: A64Opnd = out.into(); + if let A64Opnd::Reg(A64Reg { reg_no: 31, .. }) = out { + sub(cb, out, left.into(), right.into()); + } else { + subs(cb, out, left.into(), right.into()); + } }, Insn::Mul { left, right, out } => { // If the next instruction is jo (jump on overflow) @@ -1033,8 +1040,8 @@ impl Assembler b(cb, InstructionOffset::from_bytes(4 + (SIZEOF_VALUE as i32))); cb.write_bytes(&value.as_u64().to_le_bytes()); - let ptr_offset: u32 = (cb.get_write_pos() as u32) - (SIZEOF_VALUE as u32); - insn_gc_offsets.push(ptr_offset); + let ptr_offset = cb.get_write_ptr().sub_bytes(SIZEOF_VALUE); + gc_offsets.push(ptr_offset); }, Opnd::None => { unreachable!("Attempted to load from None operand"); @@ -1162,9 +1169,6 @@ impl Assembler Target::CodePtr(dst_ptr) => { emit_jmp_ptr(cb, dst_ptr, true); }, - Target::SideExitPtr(dst_ptr) => { - emit_jmp_ptr(cb, dst_ptr, false); - }, Target::Label(label_idx) => { // Here we're going to save enough space for // ourselves and then come back and write the @@ -1211,6 +1215,16 @@ impl Assembler Insn::Jonz(opnd, target) => { emit_cmp_zero_jump(cb, opnd.into(), false, target.clone()); }, + Insn::PatchPoint(_) | + Insn::PadPatchPoint => { + // If patch points are too close to each other or the end of the block, fill nop instructions + if let Some(last_patch_pos) = last_patch_pos { + while cb.get_write_pos().saturating_sub(last_patch_pos) < cb.jmp_ptr_bytes() && !cb.has_dropped_bytes() { + nop(cb); + } + } + last_patch_pos = Some(cb.get_write_pos()); + }, Insn::IncrCounter { mem: _, value: _ } => { /* let label = cb.new_label("incr_counter_loop".to_string()); @@ -1255,30 +1269,14 @@ impl Assembler csel(cb, out.into(), truthy.into(), falsy.into(), Condition::GE); } Insn::LiveReg { .. } => (), // just a reg alloc signal, no code - Insn::PadInvalPatch => { - unimplemented!("we haven't needed padding in ZJIT yet"); - } }; - // On failure, jump to the next page and retry the current insn - if !had_dropped_bytes && cb.has_dropped_bytes() { - // Reset cb states before retrying the current Insn - //cb.set_label_state(old_label_state); - - // We don't want label references to cross page boundaries. Signal caller for - // retry. - if !self.label_names.is_empty() { - return Err(EmitError::RetryOnNextPage); - } - } else { - insn_idx += 1; - gc_offsets.append(&mut insn_gc_offsets); - } + insn_idx += 1; } // Error if we couldn't write out everything if cb.has_dropped_bytes() { - Err(EmitError::OutOfMemory) + None } else { // No bytes dropped, so the pos markers point to valid code for (insn_idx, pos) in pos_markers { @@ -1289,14 +1287,15 @@ impl Assembler } } - Ok(gc_offsets) + Some(gc_offsets) } } /// Optimize and compile the stored instructions - pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec) -> Option<(CodePtr, Vec)> { + pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec) -> Option<(CodePtr, Vec)> { let asm = self.arm64_split(); - let mut asm = asm.alloc_regs(regs); + let mut asm = asm.alloc_regs(regs)?; + asm.compile_side_exits()?; // Create label instances in the code block for (idx, name) in asm.label_names.iter().enumerate() { @@ -1305,26 +1304,9 @@ impl Assembler } let start_ptr = cb.get_write_ptr(); - /* - let starting_label_state = cb.get_label_state(); - let emit_result = match asm.arm64_emit(cb, &mut ocb) { - Err(EmitError::RetryOnNextPage) => { - // we want to lower jumps to labels to b.cond instructions, which have a 1 MiB - // range limit. We can easily exceed the limit in case the jump straddles two pages. - // In this case, we retry with a fresh page once. - cb.set_label_state(starting_label_state); - if cb.next_page(start_ptr, emit_jmp_ptr_with_invalidation) { - asm.arm64_emit(cb, &mut ocb) - } else { - Err(EmitError::OutOfMemory) - } - } - result => result - }; - */ - let emit_result = asm.arm64_emit(cb); + let gc_offsets = asm.arm64_emit(cb); - if let (Ok(gc_offsets), false) = (emit_result, cb.has_dropped_bytes()) { + if let (Some(gc_offsets), false) = (gc_offsets, cb.has_dropped_bytes()) { cb.link_labels(); // Invalidate icache for newly written out region so we don't run stale code. @@ -1339,14 +1321,107 @@ impl Assembler } } -/* +/// LIR Instructions that are lowered to an instruction that have 2 input registers and an output +/// register can look to merge with a succeeding `Insn::Mov`. +/// For example: +/// +/// Add out, a, b +/// Mov c, out +/// +/// Can become: +/// +/// Add c, a, b +/// +/// If a, b, and c are all registers. +fn merge_three_reg_mov( + live_ranges: &Vec, + iterator: &mut std::iter::Peekable>, + left: &Opnd, + right: &Opnd, + out: &mut Opnd, +) { + if let (Opnd::Reg(_) | Opnd::VReg{..}, + Opnd::Reg(_) | Opnd::VReg{..}, + Some((mov_idx, Insn::Mov { dest, src }))) + = (left, right, iterator.peek()) { + if out == src && live_ranges[out.vreg_idx()].end() == *mov_idx && matches!(*dest, Opnd::Reg(_) | Opnd::VReg{..}) { + *out = *dest; + iterator.next(); // Pop merged Insn::Mov + } + } +} + #[cfg(test)] mod tests { use super::*; - use crate::disasm::*; + use crate::assertions::assert_disasm; + + static TEMP_REGS: [Reg; 5] = [X1_REG, X9_REG, X10_REG, X14_REG, X15_REG]; fn setup_asm() -> (Assembler, CodeBlock) { - (Assembler::new(0), CodeBlock::new_dummy(1024)) + (Assembler::new(), CodeBlock::new_dummy()) + } + + #[test] + fn test_mul_with_immediate() { + let (mut asm, mut cb) = setup_asm(); + + let out = asm.mul(Opnd::Reg(TEMP_REGS[1]), 3.into()); + asm.mov(Opnd::Reg(TEMP_REGS[0]), out); + asm.compile_with_num_regs(&mut cb, 2); + + assert_disasm!(cb, "600080d2207d009be10300aa", {" + 0x0: mov x0, #3 + 0x4: mul x0, x9, x0 + 0x8: mov x1, x0 + "}); + } + + #[test] + fn sp_movements_are_single_instruction() { + let (mut asm, mut cb) = setup_asm(); + + let sp = Opnd::Reg(XZR_REG); + let new_sp = asm.add(sp, 0x20.into()); + asm.mov(sp, new_sp); + let new_sp = asm.sub(sp, 0x20.into()); + asm.mov(sp, new_sp); + + asm.compile_with_num_regs(&mut cb, 2); + assert_disasm!(cb, "ff830091ff8300d1", " + 0x0: add sp, sp, #0x20 + 0x4: sub sp, sp, #0x20 + "); + } + + #[test] + fn add_into() { + let (mut asm, mut cb) = setup_asm(); + + let sp = Opnd::Reg(XZR_REG); + asm.add_into(sp, 8.into()); + asm.add_into(Opnd::Reg(X20_REG), 0x20.into()); + + asm.compile_with_num_regs(&mut cb, 0); + assert_disasm!(cb, "ff230091948200b1", " + 0x0: add sp, sp, #8 + 0x4: adds x20, x20, #0x20 + "); + } + + #[test] + fn sub_imm_reg() { + let (mut asm, mut cb) = setup_asm(); + + let difference = asm.sub(0x8.into(), Opnd::Reg(X5_REG)); + asm.load_into(Opnd::Reg(X1_REG), difference); + + asm.compile_with_num_regs(&mut cb, 1); + assert_disasm!(cb, "000180d2000005ebe10300aa", " + 0x0: mov x0, #8 + 0x4: subs x0, x0, x5 + 0x8: mov x1, x0 + "); } #[test] @@ -1355,7 +1430,7 @@ mod tests { let opnd = asm.add(Opnd::Reg(X0_REG), Opnd::Reg(X1_REG)); asm.store(Opnd::mem(64, Opnd::Reg(X2_REG), 0), opnd); - asm.compile_with_regs(&mut cb, None, vec![X3_REG]); + asm.compile_with_regs(&mut cb, vec![X3_REG]); // Assert that only 2 instructions were written. assert_eq!(8, cb.get_write_pos()); @@ -1419,6 +1494,7 @@ mod tests { asm.compile_with_num_regs(&mut cb, 0); } + /* #[test] fn test_emit_lea_label() { let (mut asm, mut cb) = setup_asm(); @@ -1432,6 +1508,7 @@ mod tests { asm.compile_with_num_regs(&mut cb, 1); } + */ #[test] fn test_emit_load_mem_disp_fits_into_load() { @@ -1642,6 +1719,7 @@ mod tests { asm.compile_with_num_regs(&mut cb, 2); } + /* #[test] fn test_bcond_straddling_code_pages() { const LANDING_PAGE: usize = 65; @@ -1778,20 +1856,5 @@ mod tests { 0x8: mov x1, x11 "}); } - - #[test] - fn test_mul_with_immediate() { - let (mut asm, mut cb) = setup_asm(); - - let out = asm.mul(Opnd::Reg(TEMP_REGS[1]), 3.into()); - asm.mov(Opnd::Reg(TEMP_REGS[0]), out); - asm.compile_with_num_regs(&mut cb, 2); - - assert_disasm!(cb, "6b0080d22b7d0b9be1030baa", {" - 0x0: mov x11, #3 - 0x4: mul x11, x9, x11 - 0x8: mov x1, x11 - "}); - } + */ } -*/ diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 5bca786d13..94c53569b4 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -1,11 +1,14 @@ +use std::collections::HashMap; use std::fmt; use std::mem::take; -use crate::{cruby::VALUE, hir::FrameState}; +use crate::codegen::local_size_and_idx_to_ep_offset; +use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32}; +use crate::hir::SideExitReason; +use crate::options::{debug, get_option}; +use crate::cruby::VALUE; use crate::backend::current::*; use crate::virtualmem::CodePtr; use crate::asm::{CodeBlock, Label}; -#[cfg(feature = "disasm")] -use crate::options::*; pub const EC: Opnd = _EC; pub const CFP: Opnd = _CFP; @@ -13,6 +16,7 @@ pub const SP: Opnd = _SP; pub const C_ARG_OPNDS: [Opnd; 6] = _C_ARG_OPNDS; pub const C_RET_OPND: Opnd = _C_RET_OPND; +pub const NATIVE_STACK_PTR: Opnd = _NATIVE_STACK_PTR; pub use crate::backend::current::{Reg, C_RET_REG}; // Memory operand base @@ -28,13 +32,13 @@ pub enum MemBase pub struct Mem { // Base register number or instruction index - pub(super) base: MemBase, + pub base: MemBase, // Offset relative to the base pointer - pub(super) disp: i32, + pub disp: i32, // Size in bits - pub(super) num_bits: u8, + pub num_bits: u8, } impl fmt::Debug for Mem { @@ -42,7 +46,7 @@ impl fmt::Debug for Mem { write!(fmt, "Mem{}[{:?}", self.num_bits, self.base)?; if self.disp != 0 { let sign = if self.disp > 0 { '+' } else { '-' }; - write!(fmt, " {sign} {}", self.disp)?; + write!(fmt, " {sign} {}", self.disp.abs())?; } write!(fmt, "]") @@ -74,7 +78,8 @@ impl fmt::Debug for Opnd { match self { Self::None => write!(fmt, "None"), Value(val) => write!(fmt, "Value({val:?})"), - VReg { idx, num_bits } => write!(fmt, "Out{num_bits}({idx})"), + VReg { idx, num_bits } if *num_bits == 64 => write!(fmt, "VReg({idx})"), + VReg { idx, num_bits } => write!(fmt, "VReg{num_bits}({idx})"), Imm(signed) => write!(fmt, "{signed:x}_i64"), UImm(unsigned) => write!(fmt, "{unsigned:x}_u64"), // Say Mem and Reg only once @@ -112,7 +117,7 @@ impl Opnd } /// Constructor for constant pointer operand - pub fn const_ptr(ptr: *const u8) -> Self { + pub fn const_ptr(ptr: *const T) -> Self { Opnd::UImm(ptr as u64) } @@ -265,6 +270,14 @@ impl From for Opnd { } } +/// Set of things we need to restore for side exits. +#[derive(Clone, Debug)] +pub struct SideExitContext { + pub pc: *const VALUE, + pub stack: Vec, + pub locals: Vec, +} + /// Branch target (something that we can jump to) /// for branch instructions #[derive(Clone, Debug)] @@ -272,12 +285,20 @@ pub enum Target { /// Pointer to a piece of ZJIT-generated code CodePtr(CodePtr), - // Side exit with a counter - SideExit(FrameState), - /// Pointer to a side exit code - SideExitPtr(CodePtr), /// A label within the generated code Label(Label), + /// Side exit to the interpreter + SideExit { + /// Context to restore on regular side exits. None for side exits right + /// after JIT-to-JIT calls because we restore them before the JIT call. + context: Option, + /// We use this to enrich asm comments. + reason: SideExitReason, + /// The number of bytes we need to adjust the C stack pointer by. + c_stack_bytes: usize, + /// Some if the side exit should write this label. We use it for patch points. + label: Option