diff options
1222 files changed, 50363 insertions, 10757 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 6803edff9b..05ff204541 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -26,8 +26,9 @@ environment: # is limited, and compatibility issues that happen only in newer versions are rare. # You may test some other stuff on GitHub Actions instead. - build: vs - vs: 120 + vs: 120 # Visual Studio 2013 ssl: OpenSSL-v111 + # The worker image name. This is NOT the Visual Studio version we're using here. APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 GEMS_FOR_TEST: "" RELINE_TEST_ENCODING: "UTF-8" @@ -47,6 +48,7 @@ for: - cd C:\Tools\vcpkg - git pull -q - .\bootstrap-vcpkg.bat + - ps: Start-FileDownload 'https://github.com/microsoft/vcpkg-tool/releases/download/2023-08-09/vcpkg.exe' -FileName 'C:\Tools\vcpkg\vcpkg.exe' - cd %APPVEYOR_BUILD_FOLDER% - vcpkg --triplet %Platform%-windows install --x-use-aria2 libffi libyaml readline zlib - CALL SET vcvars=%%^VS%VS%COMNTOOLS^%%..\..\VC\vcvarsall.bat @@ -75,6 +77,7 @@ for: - attrib +r /s /d - mkdir %Platform%-mswin_%vs% build_script: + - set HAVE_GIT=no - cd %APPVEYOR_BUILD_FOLDER% - cd %Platform%-mswin_%vs% - >- @@ -91,7 +94,7 @@ for: - nmake -l "TESTOPTS=-v -q" btest - nmake -l "TESTOPTS=-v -q" test-basic - >- - nmake -l "TESTOPTS=-v --timeout-scale=3.0 + nmake -l "TESTOPTS=--timeout-scale=3.0 --excludes=../test/excludes/_appveyor -j%JOBS% --exclude win32ole --exclude test_bignum @@ -102,7 +105,7 @@ for: # separately execute tests without -j which may crash worker with -j. - >- nmake -l - "TESTOPTS=-v --timeout-scale=3.0 --excludes=../test/excludes/_appveyor" + "TESTOPTS=--timeout-scale=3.0 --excludes=../test/excludes/_appveyor" TESTS=" ../test/win32ole ../test/ruby/test_bignum.rb diff --git a/.cirrus.yml b/.cirrus.yml deleted file mode 100644 index 6c47159921..0000000000 --- a/.cirrus.yml +++ /dev/null @@ -1,133 +0,0 @@ -# This CI is used to test Arm cases. We can set the maximum 16 tasks. -# The entire testing design is inspired from .github/workflows/compilers.yml. - -# By default, Cirrus mounts an empty volume to `/tmp` -# which triggers all sorts of warnings like "system temporary path is world-writable: /tmp". -# Lets workaround it by specifying a custom volume mount point. -env: - CIRRUS_VOLUME: /cirrus-ci-volume - LANG: C.UTF-8 - -task: - name: Arm64 Graviton2 / $CC - skip: "changesIncludeOnly('doc/**', '**.{md,rdoc,ronn,[1-8]}', '.document')" - arm_container: - # We use the arm64 images at https://github.com/ruby/ruby-ci-image/pkgs/container/ruby-ci-image . - image: ghcr.io/ruby/ruby-ci-image:$CC - # Define the used cpu core in each matrix task. We can use total 16 cpu - # cores in entire matrix. [cpu] = [total cpu: 16] / [number of tasks] - cpu: 8 - # We can request maximum 4 GB per cpu. - # [memory per task] = [memory per cpu: 4 GB] * [cpu] - memory: 32G - env: - CIRRUS_CLONE_DEPTH: 50 - optflags: '-O1' - debugflags: '-ggdb3' - RUBY_PREFIX: /tmp/ruby-prefix - RUBY_DEBUG: ci rgengc - RUBY_TESTOPTS: >- - -q - --color=always - --tty=no - matrix: - CC: clang-12 - CC: gcc-11 - id_script: id - set_env_script: - # Set `GNUMAKEFLAGS`, because the flags are GNU make specific. Note using - # the `make` environment variable used in compilers.yml causes some rubygems - # tests to fail. - # https://github.com/rubygems/rubygems/issues/4921 - - echo "GNUMAKEFLAGS=-s -j$((1 + $CIRRUS_CPU))" >> "$CIRRUS_ENV" - - cat "$CIRRUS_ENV" - # Arm containers are executed in AWS's EKS, and it's not yet supporting IPv6 - # See https://github.com/aws/containers-roadmap/issues/835 - disable_ipv6_script: sudo ./tool/disable_ipv6.sh - autogen_script: ./autogen.sh - configure_script: >- - ./configure -C - --enable-debug-env - --disable-install-doc - --with-ext=-test-/cxxanyargs,+ - --prefix="$RUBY_PREFIX" - make_extract-extlibs_script: make extract-extlibs - make_incs_script: make incs - make_script: make - make_leaked-globals_script: make leaked-globals - make_test_script: make test - make_install_script: make install - install_gems_for_test_script: $RUBY_PREFIX/bin/gem install --no-doc timezone tzinfo - make_test-tool_script: make test-tool - make_test-all_script: make test-all - make_test-spec_script: make test-spec - -# The following is to test YJIT on ARM64 CPUs available on Cirrus CI -yjit_task: - name: Arm64 Graviton2 / $CC YJIT - auto_cancellation: $CIRRUS_BRANCH != 'master' - skip: "changesIncludeOnly('doc/**', '**.{md,rdoc,ronn,[1-8]}', '.document')" - arm_container: - # We use the arm64 images at https://github.com/ruby/ruby-ci-image/pkgs/container/ruby-ci-image . - image: ghcr.io/ruby/ruby-ci-image:$CC - # Define the used cpu core in each matrix task. We can use total 16 cpu - # cores in entire matrix. [cpu] = [total cpu: 16] / [number of tasks] - cpu: 8 - # We can request maximum 4 GB per cpu. - # [memory per task] = [memory per cpu: 4 GB] * [cpu] - memory: 32G - env: - CIRRUS_CLONE_DEPTH: 50 - optflags: '-O1' - debugflags: '-ggdb3' - RUBY_PREFIX: /tmp/ruby-prefix - RUBY_DEBUG: ci rgengc - RUBY_TESTOPTS: >- - -q - --color=always - --tty=no - matrix: - - CC: clang-12 - configure: --enable-yjit=dev - rustup_init: --default-toolchain=1.58.0 - - CC: gcc-11 - configure: --enable-yjit - id_script: id - set_env_script: - # Set `GNUMAKEFLAGS`, because the flags are GNU make specific. Note using - # the `make` environment variable used in compilers.yml causes some rubygems - # tests to fail. - # https://github.com/rubygems/rubygems/issues/4921 - - echo "GNUMAKEFLAGS=-s -j$((1 + $CIRRUS_CPU))" >> "$CIRRUS_ENV" - - echo RUST_BACKTRACE=1 >> "$CIRRUS_ENV" - - cat "$CIRRUS_ENV" - # Arm containers are executed in AWS's EKS, and it's not yet supporting IPv6 - # See https://github.com/aws/containers-roadmap/issues/835 - disable_ipv6_script: sudo ./tool/disable_ipv6.sh - install_rust_script: - - sudo apt-get update -y - - sudo apt-get install -y curl - - "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y $rustup_init" - autogen_script: ./autogen.sh - configure_script: >- - source $HOME/.cargo/env && ./configure -C - --enable-debug-env - --disable-install-doc - --with-ext=-test-/cxxanyargs,+ - --prefix="$RUBY_PREFIX" - $configure - make_miniruby_script: source $HOME/.cargo/env && make miniruby - make_bindgen_script: | - if [[ "$CC" = "clang-12" ]]; then - source $HOME/.cargo/env && make yjit-bindgen - else - echo "only running bindgen on clang image" - fi - boot_miniruby_script: ./miniruby --yjit-call-threshold=1 -e0 - test_dump_insns_script: ./miniruby --yjit-call-threshold=1 --yjit-dump-insns -e0 - output_stats_script: ./miniruby --yjit-call-threshold=1 --yjit-stats -e0 - full_build_script: source $HOME/.cargo/env && make - cargo_test_script: source $HOME/.cargo/env && cd yjit && cargo test - make_test_script: source $HOME/.cargo/env && make test RUN_OPTS="--yjit-call-threshold=1 --yjit-verify-ctx" - make_test_all_script: source $HOME/.cargo/env && make test-all RUN_OPTS="--yjit-call-threshold=1 --yjit-verify-ctx" TESTOPTS="$RUBY_TESTOPTS" - make_test_spec_script: source $HOME/.cargo/env && make test-spec RUN_OPTS="--yjit-call-threshold=1 --yjit-verify-ctx" @@ -18,6 +18,7 @@ gc.rb io.rb kernel.rb marshal.rb +mjit.rb numeric.rb nilclass.rb pack.rb @@ -28,6 +29,7 @@ timev.rb thread_sync.rb trace_point.rb warning.rb +yjit.rb # the lib/ directory (which has its own .document file) lib diff --git a/.github/auto_request_review.yml b/.github/auto_request_review.yml deleted file mode 100644 index d058b6ca00..0000000000 --- a/.github/auto_request_review.yml +++ /dev/null @@ -1,9 +0,0 @@ -files: - 'yjit*': [team:yjit] - 'yjit/**/*': [team:yjit] - 'doc/yjit/*': [team:yjit] - 'bootstraptest/test_yjit*': [team:yjit] - 'test/ruby/test_yjit*': [team:yjit] - '.github/workflows/yjit*': [team:yjit] -options: - ignore_draft: true diff --git a/.github/workflows/auto_request_review.yml b/.github/workflows/auto_request_review.yml deleted file mode 100644 index f837905fe6..0000000000 --- a/.github/workflows/auto_request_review.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Auto Request Review -on: - pull_request_target: - types: [opened, ready_for_review, reopened] - -permissions: - contents: read - -jobs: - auto-request-review: - name: Auto Request Review - runs-on: ubuntu-latest - if: ${{ github.repository == 'ruby/ruby' }} - steps: - - name: Request review based on files changes and/or groups the author belongs to - uses: necojackarc/auto-request-review@b5e81876454003a4ccb9b89cb205c67d77d7035b # v0.8.0 - with: - # scope: public_repo - token: ${{ secrets.MATZBOT_GITHUB_TOKEN }} diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 620847597d..ebaafe3bf0 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -4,19 +4,24 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} @@ -28,7 +33,7 @@ permissions: jobs: baseruby: name: BASERUBY - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} strategy: matrix: @@ -43,12 +48,12 @@ jobs: - ruby-3.1 steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 - - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: .downloaded-cache key: downloaded-cache - - uses: ruby/setup-ruby@c7079efafd956afb5d823e8999c2506e1053aefa # v1.126.0 + - uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 with: ruby-version: ${{ matrix.ruby }} bundler: none @@ -60,7 +65,7 @@ jobs: - run: make incs - run: make all - run: make test - - uses: ruby/action-slack@b6882ea6ef8f556f9f9af9ec1220d3f1ced74acf # v3.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 942988c7d8..070c0fa1dd 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -2,10 +2,17 @@ name: bundled_gems on: push: + branches: [ "master" ] paths: - '.github/workflows/bundled_gems.yml' - 'gems/bundled_gems' pull_request: + branches: [ "master" ] + paths: + - '.github/workflows/bundled_gems.yml' + - 'gems/bundled_gems' + merge_group: + branches: [ "master" ] paths: - '.github/workflows/bundled_gems.yml' - 'gems/bundled_gems' @@ -34,9 +41,9 @@ jobs: echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV echo "TODAY=$(date +%F)" >> $GITHUB_ENV - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: .downloaded-cache key: downloaded-cache-${{ github.sha }} @@ -81,7 +88,7 @@ jobs: news.sub!(/^\*( +)The following default gems are now bundled gems\.\n+\K(?: \1\*( +).*\n)*/) do mark = "#{$1} *#{$2}" added.map {|g, v|"#{mark}#{g} #{v}\n"}.join("") - end or next if added + end or next unless added.empty? File.write("NEWS.md", news) end shell: ruby {0} @@ -143,3 +150,17 @@ jobs: GIT_AUTHOR_NAME: git GIT_COMMITTER_NAME: git if: ${{ github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull') && steps.show.outcome == 'failure' }} + + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 + with: + payload: | + { + "ci": "GitHub Actions", + "env": "${{ github.workflow }} / update", + "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", + "commit": "${{ github.sha }}", + "branch": "${{ github.ref_name }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot + if: ${{ failure() && github.event_name == 'push' }} diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index cf9b5e8b60..79b2916feb 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -3,19 +3,24 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} @@ -28,7 +33,7 @@ jobs: update-deps: strategy: matrix: - os: [ubuntu-20.04] + os: [ubuntu-22.04] fail-fast: true runs-on: ${{ matrix.os }} if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} @@ -41,15 +46,14 @@ jobs: if: ${{ contains(matrix.os, 'ubuntu') }} - name: Install libraries run: | - brew upgrade brew install gmp libffi openssl@1.1 zlib autoconf automake libtool readline if: ${{ contains(matrix.os, 'macos') }} - name: git config run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 - - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: .downloaded-cache key: downloaded-cache @@ -59,7 +63,7 @@ jobs: - run: make all golf - run: ruby tool/update-deps --fix - run: git diff --no-ext-diff --ignore-submodules --exit-code - - uses: ruby/action-slack@b6882ea6ef8f556f9f9af9ec1220d3f1ced74acf # v3.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml deleted file mode 100644 index 2a8cfabefd..0000000000 --- a/.github/workflows/check_misc.yml +++ /dev/null @@ -1,122 +0,0 @@ -name: Miscellaneous checks -on: [push, pull_request] - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -jobs: - checks: - permissions: - contents: write # for Git to git push - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 - - name: Check if C-sources are US-ASCII - run: | - ! grep -r -n '[^ -~]' -- *.[chy] include internal win32/*.[ch] - - name: Check for trailing spaces - run: | - ! git grep -n '[ ]$' -- '*.rb' '*.[chy]' - ! git grep -n '^[ ][ ]*$' -- '*.md' - - name: Check for bash specific substitution in configure.ac - run: | - ! git grep -n '\${[A-Za-z_0-9]*/' -- configure.ac - - name: Check for header macros - run: | - ! for header in ruby/*.h; do \ - git grep -l -F -e $header -e HAVE_`echo $header | tr a-z./ A-Z__` -- . > /dev/null || echo $header - done | grep -F . - working-directory: include - - - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 - with: - path: .downloaded-cache - key: downloaded-cache-${{ github.sha }} - restore-keys: | - downloaded-cache - - - name: Download previous gems list - run: | - data=default_gems.json - mkdir -p .downloaded-cache - ln -s .downloaded-cache/$data . - curl -O -R -z ./$data https://stdgems.org/$data - - - name: Make default gems list - run: | - #!ruby - require 'rubygems' - $:.unshift "lib" - rgver = File.foreach("lib/rubygems.rb") do |line| - break $1 if /^\s*VERSION\s*=\s*"([^"]+)"/ =~ line - end - gems = Dir.glob("{ext,lib}/**/*.gemspec").map do |f| - spec = Gem::Specification.load(f) - "#{spec.name} #{spec.version}" - end.sort - File.open("gems/default_gems", "w") do |f| - f.puts "RubyGems #{rgver}" - f.puts gems - end - shell: ruby --disable=gems {0} - - - name: Maintain updated gems list in NEWS - run: | - #!ruby - require 'json' - news = File.read("NEWS.md") - prev = news[/since the \*+(\d+\.\d+\.\d+)\*+/, 1] - prevs = [prev, prev.sub(/\.\d+\z/, '')] - %W[default].each do |type| - last = JSON.parse(File.read("#{type}_gems.json"))['gems'].filter_map do |g| - v = g['versions'].values_at(*prevs).compact.first - g = g['gem'] - g = 'RubyGems' if g == 'rubygems' - [g, v] if v - end.to_h - changed = File.foreach("gems/#{type}_gems").filter_map do |l| - next if l.start_with?("#") - g, v = l.split(" ", 3) - [g, v] unless last[g] == v - end - news.sub!(/^\*( +)The following #{type} gems? are updated\.\n+\K(?: \1\* .*\n)*/) do - mark = "#{$1} * " - changed.map {|g, v|"#{mark}#{g} #{v}\n"}.join("") - end or next - File.write("NEWS.md", news) - end - shell: ruby {0} - - - name: Check diffs - id: diff - run: | - git diff --color --no-ext-diff --ignore-submodules --exit-code NEWS.md - continue-on-error: true - - name: Commit - run: | - git pull --ff-only origin ${GITHUB_REF#refs/heads/} - git commit --message="Update default gems list at ${GITHUB_SHA:0:30} [ci skip]" NEWS.md - git push origin ${GITHUB_REF#refs/heads/} - env: - EMAIL: svn-admin@ruby-lang.org - GIT_AUTHOR_NAME: git - GIT_COMMITTER_NAME: git - if: ${{ github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull') && steps.diff.outcome == 'failure' }} - - - uses: ruby/action-slack@b6882ea6ef8f556f9f9af9ec1220d3f1ced74acf # v3.0.0 - with: - payload: | - { - "ci": "GitHub Actions", - "env": "${{ github.workflow }}", - "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", - "commit": "${{ github.sha }}", - "branch": "${{ github.ref_name }}" - } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() && github.event_name == 'push' }} diff --git a/.github/workflows/cirrus-notify.yml b/.github/workflows/cirrus-notify.yml deleted file mode 100644 index 5edba0ae8f..0000000000 --- a/.github/workflows/cirrus-notify.yml +++ /dev/null @@ -1,46 +0,0 @@ -on: - check_suite: - type: ['completed'] -name: Cirrus CI failure notification - -permissions: - contents: read - -jobs: - cirrus-notify: - name: After Cirrus CI Failure - if: >- - github.event.check_suite.app.name == 'Cirrus CI' - && github.event.check_suite.conclusion != 'success' - && github.event.check_suite.conclusion != 'cancelled' - && github.event.check_suite.conclusion != 'skipped' - && github.event.check_suite.head_branch == 'master' - runs-on: ubuntu-latest - steps: - - uses: octokit/request-action@52ce92ce3185e00e2425f043c3e9509121929aea # v2.x - id: get_failed_check_run - with: - route: GET /repos/${{ github.repository }}/check-suites/${{ github.event.check_suite.id }}/check-runs?status=completed - mediaType: '{"previews": ["antiope"]}' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Dump check_runs - env: - CHECK_RUNS: ${{ steps.get_failed_check_run.outputs.data }} - run: echo "$CHECK_RUNS" - - uses: ruby/action-slack@b6882ea6ef8f556f9f9af9ec1220d3f1ced74acf # v3.0.0 - with: - payload: | - { - "ci": "Cirrus CI", - "env": "Cirrus CI", - "url": "${{ fromJson(steps.get_failed_check_run.outputs.data).check_runs[0].html_url }}", - "commit": "${{ github.event.check_suite.head_commit.id }}", - "branch": "${{ github.event.check_suite.head_branch }}" - } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 643d6ddf92..8dba76fbe2 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -4,19 +4,17 @@ on: # push: # paths-ignore: # - 'doc/**' + # - '**/man' # - '**.md' # - '**.rdoc' # - '**/.document' - # - '**.[1-8]' - # - '**.ronn' # pull_request: # paths-ignore: # - 'doc/**' + # - '**/man' # - '**.md' # - '**.rdoc' # - '**/.document' - # - '**.[1-8]' - # - '**.ronn' schedule: - cron: '0 12 * * *' workflow_dispatch: @@ -51,9 +49,9 @@ jobs: sudo apt-get install --no-install-recommends -q -y build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev bison autoconf ruby - name: Checkout repository - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: .downloaded-cache key: downloaded-cache @@ -62,15 +60,16 @@ jobs: run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb - name: Initialize CodeQL - uses: github/codeql-action/init@b2a92eb56d8cb930006a1c6ed86b0782dd8a4297 # v2.1.35 + uses: github/codeql-action/init@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.37 with: config-file: ./.github/codeql/codeql-config.yml + trap-caching: false - name: Set ENV run: echo "GNUMAKEFLAGS=-j$((1 + $(nproc --all)))" >> $GITHUB_ENV - name: Autobuild - uses: github/codeql-action/autobuild@b2a92eb56d8cb930006a1c6ed86b0782dd8a4297 # v2.1.35 + uses: github/codeql-action/autobuild@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.37 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@b2a92eb56d8cb930006a1c6ed86b0782dd8a4297 # v2.1.35 + uses: github/codeql-action/analyze@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.37 diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index b6db2138fd..caf12cc0f4 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -4,19 +4,22 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' pull_request: paths-ignore: - 'doc/**' - - '**.md' + - '**/man' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} @@ -77,27 +80,13 @@ jobs: - { name: gcc-9, env: { default_cc: gcc-9 } } - { name: gcc-8, env: { default_cc: gcc-8 } } - { name: gcc-7, env: { default_cc: gcc-7 } } - - { name: gcc-6, env: { default_cc: gcc-6 } } - - { name: gcc-5, env: { default_cc: gcc-5 } } - - { name: gcc-4.8, env: { default_cc: gcc-4.8 } } - - name: 'gcc-11 LTO' - container: gcc-11 + - name: 'gcc-13 LTO' + container: gcc-13 env: - default_cc: 'gcc-11 -flto=auto -ffat-lto-objects' + default_cc: 'gcc-13 -flto=auto -ffat-lto-objects -Werror=lto-type-mismatch' optflags: '-O2' shared: disable # check: true - - name: 'gcc-11 annocheck' - container: gcc-11 - env: - # Minimal flags to pass the check. - default_cc: 'gcc-11 -O2 -fcf-protection -Wa,--generate-missing-build-notes=yes' - LDFLAGS: '-Wl,-z,now' - # FIXME: Drop skipping options - # https://bugs.ruby-lang.org/issues/18061 - # https://sourceware.org/annobin/annobin.html/Test-pie.html - TEST_ANNOCHECK_OPTS: "--skip-pie" - check: true - { name: clang-16, env: { default_cc: clang-16 } } - { name: clang-15, env: { default_cc: clang-15 } } - { name: clang-14, env: { default_cc: clang-14 } } @@ -105,17 +94,15 @@ jobs: - { name: clang-12, env: { default_cc: clang-12 } } - { name: clang-11, env: { default_cc: clang-11 } } - { name: clang-10, env: { default_cc: clang-10 } } - - { name: clang-9, env: { default_cc: clang-9 } } - - { name: clang-8, env: { default_cc: clang-8 } } - - { name: clang-7, env: { default_cc: clang-7 } } - - { name: clang-6.0, env: { default_cc: clang-6.0 } } - - { name: clang-5.0, env: { default_cc: clang-5.0 } } - - { name: clang-4.0, env: { default_cc: clang-4.0 } } - - { name: clang-3.9, env: { default_cc: clang-3.9 } } - - name: 'clang-14 LTO' - container: clang-14 + # llvm-objcopy<=9 doesn't have --wildcard. It compiles, but leaves Rust symbols in libyjit.o. + - { name: clang-9, env: { default_cc: clang-9, append_configure: '--disable-yjit' } } + - { name: clang-8, env: { default_cc: clang-8, append_configure: '--disable-yjit' } } + - { name: clang-7, env: { default_cc: clang-7, append_configure: '--disable-yjit' } } + - { name: clang-6.0, env: { default_cc: clang-6.0, append_configure: '--disable-yjit' } } + - name: 'clang-16 LTO' + container: clang-16 env: - default_cc: 'clang-14 -flto=auto' + default_cc: 'clang-16 -flto=auto' optflags: '-O2' shared: disable # check: true @@ -181,7 +168,11 @@ jobs: # - { name: VM_CHECK_MODE, env: { cppflags: '-DVM_CHECK_MODE' } } - { name: USE_EMBED_CI=0, env: { cppflags: '-DUSE_EMBED_CI=0' } } - - { name: USE_FLONUM=0, env: { cppflags: '-DUSE_FLONUM=0' } } + - name: USE_FLONUM=0, + env: + cppflags: '-DUSE_FLONUM=0' + # yjit requires FLONUM for the pointer tagging scheme + append_configure: '--disable-yjit' # - { name: USE_GC_MALLOC_OBJ_INFO_DETAILS, env: { cppflags: '-DUSE_GC_MALLOC_OBJ_INFO_DETAILS' } } - { name: USE_LAZY_LOAD, env: { cppflags: '-DUSE_LAZY_LOAD' } } # - { name: USE_RINCGC=0, env: { cppflags: '-DUSE_RINCGC=0' } } @@ -234,10 +225,10 @@ jobs: - name: setenv run: | echo "GNUMAKEFLAGS=-sj$((1 + $(nproc --all)))" >> $GITHUB_ENV - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache @@ -271,7 +262,7 @@ jobs: - run: make test-annocheck if: ${{ matrix.entry.check && endsWith(matrix.entry.name, 'annocheck') }} - - uses: ruby/action-slack@b6882ea6ef8f556f9f9af9ec1220d3f1ced74acf # v3.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 92ea46388e..d8dc58b119 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -3,19 +3,24 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} @@ -30,8 +35,9 @@ jobs: matrix: test_task: ["check"] # "test-bundler-parallel", "test-bundled-gems" os: - - macos-11 - - macos-12 + - macos-13 + - macos-14 + - macos-15 fail-fast: false env: GITPULLOPTIONS: --no-tags origin ${{github.ref}} @@ -44,22 +50,21 @@ jobs: run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache - name: Install libraries run: | - brew upgrade brew install gmp libffi openssl@1.1 zlib autoconf automake libtool readline bison working-directory: src - name: Set ENV run: | echo "MAKEFLAGS=-j$((1 + $(sysctl -n hw.activecpu)))" >> $GITHUB_ENV - echo "PATH="/usr/local/opt/bison/bin:$PATH"" >> $GITHUB_ENV + echo "PATH="/usr/local/opt/bison/bin:/opt/homebrew/opt/bison/bin:$PATH"" >> $GITHUB_ENV - run: ./autogen.sh working-directory: src - name: Run configure @@ -89,7 +94,7 @@ jobs: PRECHECK_BUNDLED_GEMS: "no" if: ${{ matrix.test_task == 'check' && matrix.skipped_tests != '' }} continue-on-error: ${{ matrix.continue-on-skipped_tests || false }} - - uses: ruby/action-slack@b6882ea6ef8f556f9f9af9ec1220d3f1ced74acf # v3.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index c5c24d9c17..0df917d3d8 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -3,19 +3,24 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} @@ -60,15 +65,15 @@ jobs: git config --global core.eol lf git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache - name: Set up Ruby & MSYS2 - uses: ruby/setup-ruby@c7079efafd956afb5d823e8999c2506e1053aefa # v1.126.0 + uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 with: ruby-version: ${{ matrix.base_ruby }} - name: set env @@ -154,7 +159,7 @@ jobs: make ${{ StartsWith(matrix.test_task, 'spec/') && matrix.test_task || 'test-spec' }} if: ${{matrix.test_task == 'check' || matrix.test_task == 'test-spec' || StartsWith(matrix.test_task, 'spec/')}} - - uses: ruby/action-slack@b6882ea6ef8f556f9f9af9ec1220d3f1ced74acf # v3.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { diff --git a/.github/workflows/mjit-bindgen.yml b/.github/workflows/mjit-bindgen.yml index f6d3ff793a..26f8a1b2aa 100644 --- a/.github/workflows/mjit-bindgen.yml +++ b/.github/workflows/mjit-bindgen.yml @@ -3,19 +3,24 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} @@ -31,7 +36,7 @@ jobs: include: - task: mjit-bindgen fail-fast: false - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} steps: - run: mkdir build @@ -47,21 +52,21 @@ jobs: build-essential \ libssl-dev libyaml-dev libreadline6-dev \ zlib1g-dev libncurses5-dev libffi-dev \ - libclang1-10 \ + libclang1-14 \ bison autoconf sudo apt-get install -q -y pkg-config || : - name: Set up Ruby - uses: ruby/setup-ruby@c7079efafd956afb5d823e8999c2506e1053aefa # v1.126.0 + uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 with: ruby-version: '3.1' - name: git config run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache @@ -80,7 +85,7 @@ jobs: - run: make ${{ matrix.task }} - run: git diff --exit-code working-directory: src - - uses: ruby/action-slack@b6882ea6ef8f556f9f9af9ec1220d3f1ced74acf # v3.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { diff --git a/.github/workflows/mjit.yml b/.github/workflows/mjit.yml index f2fd9ad076..6f7181489a 100644 --- a/.github/workflows/mjit.yml +++ b/.github/workflows/mjit.yml @@ -3,12 +3,20 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + pull_request: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - '**.[1-8]' - '**.ronn' - pull_request: + merge_group: paths-ignore: - 'doc/**' - '**.md' @@ -49,10 +57,10 @@ jobs: run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache @@ -71,10 +79,9 @@ jobs: - run: make incs - run: make - run: sudo make -s install - - run: sudo apt-get install gdb # used by test / test-all failure - name: Run test run: | - ulimit -c unlimited + unset GNUMAKEFLAGS make -s test RUN_OPTS="$RUN_OPTS" timeout-minutes: 60 # - name: Run test-all @@ -84,10 +91,10 @@ jobs: # timeout-minutes: 60 - name: Run test-spec run: | - ulimit -c unlimited + unset GNUMAKEFLAGS make -s test-spec RUN_OPTS="$RUN_OPTS" timeout-minutes: 60 - - uses: ruby/action-slack@b6882ea6ef8f556f9f9af9ec1220d3f1ced74acf # v3.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000000..5d4474d978 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,18 @@ +name: Start release workflow +on: + push: + tags: + - '*' + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Build release package + run: | + curl -L -X POST \ + -H "Authorization: Bearer ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }}" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/ruby/actions/dispatches \ + -d '{"event_type": "${{ github.ref }}"}' diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 8236a2cd1a..c12a95362d 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -32,12 +32,12 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d # v2.0.6 + uses: ossf/scorecard-action@ea651e62978af7915d09fe2e282747c798bf2dab # v2.4.1 with: results_file: results.sarif results_format: sarif @@ -59,7 +59,7 @@ jobs: # 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@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: SARIF file path: results.sarif @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@b2a92eb56d8cb930006a1c6ed86b0782dd8a4297 # v2.1.27 + uses: github/codeql-action/upload-sarif@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2.1.27 with: sarif_file: results.sarif diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 2936e892a3..4521195a2b 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -6,9 +6,10 @@ on: - 'spec/**' - '!spec/*.md' pull_request: - paths-ignore: + paths: - 'spec/**' - '!spec/*.md' + merge_group: concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} @@ -20,28 +21,42 @@ permissions: jobs: rubyspec: name: Rubyspec - runs-on: ubuntu-20.04 - if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} + + runs-on: ubuntu-22.04 + + if: >- + ${{!(false + || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') + || contains(github.event.pull_request.title, '[DOC]') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') + || (github.event_name == 'push' && github.actor == 'dependabot[bot]') + )}} + strategy: matrix: # Specs from ruby/spec should still run on all supported Ruby versions. # This also ensures the needed ruby_version_is guards are there, see spec/README.md. ruby: - - ruby-2.7 - ruby-3.1 + - ruby-3.2 steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 - - uses: ruby/setup-ruby@c7079efafd956afb5d823e8999c2506e1053aefa # v1.126.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 with: ruby-version: ${{ matrix.ruby }} bundler: none + - run: gem install webrick + - run: ruby ../mspec/bin/mspec working-directory: spec/ruby env: CHECK_LEAKS: true - - uses: ruby/action-slack@b6882ea6ef8f556f9f9af9ec1220d3f1ced74acf # v3.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { @@ -53,4 +68,4 @@ jobs: } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() && github.event_name == 'push' }} + if: ${{ failure() }} diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index f5b259c84a..4fbca1170e 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -3,19 +3,24 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} @@ -41,7 +46,6 @@ jobs: - test_task: check configure: "--enable-shared --enable-load-relative" - test_task: test-all TESTS=--repeat-count=2 - - test_task: test-syntax-suggest - test_task: test-bundler-parallel - test_task: test-bundled-gems fail-fast: false @@ -49,7 +53,7 @@ jobs: GITPULLOPTIONS: --no-tags origin ${{github.ref}} RUBY_DEBUG: ci SETARCH: ${{ matrix.arch && format('setarch {0}', matrix.arch) }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} steps: - run: mkdir build @@ -75,10 +79,10 @@ jobs: run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache @@ -123,7 +127,7 @@ jobs: TESTS: ${{ matrix.skipped_tests }} if: ${{ matrix.test_task == 'check' && matrix.skipped_tests != '' }} continue-on-error: ${{ matrix.continue-on-skipped_tests || false }} - - uses: ruby/action-slack@b6882ea6ef8f556f9f9af9ec1220d3f1ced74acf # v3.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 3114078256..27920b5821 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -3,19 +3,24 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} @@ -45,7 +50,7 @@ jobs: WASI_SDK_VERSION_MINOR: 0 BINARYEN_VERSION: 109 WASMTIME_VERSION: v0.33.0 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} steps: - run: mkdir build @@ -54,7 +59,7 @@ jobs: run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Install libraries @@ -86,6 +91,18 @@ jobs: echo "WASI_SDK_PATH=/opt/wasi-sdk" >> $GITHUB_ENV - run: ./autogen.sh working-directory: src + + - uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 + with: + ruby-version: '3.0' + bundler: none + + - name: Download config.guess with wasi version + run: | + rm tool/config.guess tool/config.sub + ruby tool/downloader.rb -d tool -e gnu config.guess config.sub + working-directory: src + - name: Run configure run: | ../src/configure \ @@ -110,6 +127,20 @@ jobs: ruby ./bootstraptest/runner.rb --ruby="$(which wasmtime) run $PWD/../build/ruby --mapdir /::./ -- " --verbose "--sets=$NO_THREAD_TESTS" working-directory: src + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 + with: + payload: | + { + "ci": "GitHub Actions", + "env": "${{ github.workflow }} / ${{ matrix.name }}", + "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", + "commit": "${{ github.sha }}", + "branch": "${{ github.ref_name }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot + if: ${{ failure() && github.event_name == 'push' }} + defaults: run: working-directory: build diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 674f627ef9..c2bd4881c2 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -3,19 +3,24 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} @@ -29,85 +34,68 @@ jobs: strategy: matrix: include: - - vs: 2019 - vs: 2022 + vcvers: -vcvars_ver=14.2 fail-fast: false - runs-on: windows-${{ matrix.vs < 2022 && '2019' || matrix.vs }} + runs-on: windows-${{ matrix.vs }} if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} name: VisualStudio ${{ matrix.vs }} env: GITPULLOPTIONS: --no-tags origin ${{github.ref}} PATCH: C:\msys64\usr\bin\patch.exe - OS_VER: windows-${{ matrix.vs < 2022 && '2019' || matrix.vs }} + OS_VER: windows-${{ matrix.vs }} steps: - run: md build working-directory: - - uses: msys2/setup-msys2@d40200dc2db4c351366b048a9565ad82919e1c24 # v2 + - uses: msys2/setup-msys2@61f9e5e925871ba6c9e3e8da24ede83ea27fa91f # v2.27.0 id: setup-msys2 with: update: true - install: >- - patch - if: ${{ env.OS_VER != 'windows-2019' }} + install: bison patch - name: patch path shell: msys2 {0} run: echo PATCH=$(cygpath -wa $(command -v patch)) >> $GITHUB_ENV if: ${{ steps.setup-msys2.outcome == 'success' }} - - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 - with: - path: C:\vcpkg\downloads - key: ${{ runner.os }}-vcpkg-download-${{ env.OS_VER }}-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-vcpkg-download-${{ env.OS_VER }}- - ${{ runner.os }}-vcpkg-download- - - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: C:\vcpkg\installed - key: ${{ runner.os }}-vcpkg-installed-${{ matrix.os }}-${{ github.sha }} + key: ${{ runner.os }}-vcpkg-installed-windows-${{ matrix.vs }}-${{ github.sha }} restore-keys: | - ${{ runner.os }}-vcpkg-installed-${{ matrix.os }}- - ${{ runner.os }}-vcpkg-installed- + ${{ runner.os }}-vcpkg-installed-windows-${{ matrix.vs }}- + ${{ runner.os }}-vcpkg-installed-windows- - name: Install libraries with vcpkg run: | + iex "& {$(irm get.scoop.sh)} -RunAsAdmin" + Join-Path (Resolve-Path ~).Path "scoop\shims" >> $Env:GITHUB_PATH + scoop install cmake@3.31.6 vcpkg --triplet x64-windows install libffi libyaml openssl readline zlib - - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 - with: - path: C:\Users\runneradmin\AppData\Local\Temp\chocolatey - key: ${{ runner.os }}-chocolatey-${{ env.OS_VER }}-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-chocolatey-${{ env.OS_VER }}- - ${{ runner.os }}-chocolatey- - - name: Install libraries with chocolatey - run: | - # Using Choco-Install for retries, but it doesn't detect failures properly - # if you pass multiple package names in a single command. - Choco-Install -PackageName winflexbison3 - shell: pwsh + shell: + pwsh - name: git config run: | git config --global core.autocrlf false git config --global core.eol lf git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache - name: setup env # %TEMP% is inconsistent with %TMP% and test-all expects they are consistent. # https://github.com/actions/virtual-environments/issues/712#issuecomment-613004302 + # msys2/setup-msys2 installs MSYS2 to D:/a/_temp/msys64/usr/bin run: | - set VS=${{ matrix.vs }} - set VCVARS=${{ matrix.vcvars }} + set Path=D:/a/_temp/msys64/usr/bin;%Path% if not "%VCVARS%" == "" goto :vcset - set VCVARS="C:\Program Files (x86)\Microsoft Visual Studio\%VS%\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - if not exist %VCVARS% set VCVARS="C:\Program Files\Microsoft Visual Studio\%VS%\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + set VCVARS="C:\Program Files (x86)\Microsoft Visual Studio\${{ matrix.vs }}\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + if not exist %VCVARS% set VCVARS="C:\Program Files\Microsoft Visual Studio\${{ matrix.vs }}\Enterprise\VC\Auxiliary\Build\vcvars64.bat" :vcset set | C:\msys64\usr\bin\sort > old.env - call %VCVARS% + call %VCVARS% ${{ matrix.vcvers || ''}} set TMP=%USERPROFILE%\AppData\Local\Temp set TEMP=%USERPROFILE%\AppData\Local\Temp set /a TEST_JOBS=(15 * %NUMBER_OF_PROCESSORS% / 10) > nul @@ -132,7 +120,7 @@ jobs: - run: nmake extract-extlibs - run: nmake env: - YACC: win_bison + YACC: bison.exe - run: nmake test timeout-minutes: 5 - run: nmake test-spec @@ -141,7 +129,7 @@ jobs: env: RUBY_TESTOPTS: -j${{env.TEST_JOBS}} --job-status=normal timeout-minutes: 60 - - uses: ruby/action-slack@b6882ea6ef8f556f9f9af9ec1220d3f1ced74acf # v3.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index 011420c157..0b7b9046e9 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -3,19 +3,24 @@ on: push: paths-ignore: - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' pull_request: paths-ignore: - 'doc/**' + - '**/man' + - '**.md' + - '**.rdoc' + - '**/.document' + merge_group: + paths-ignore: + - 'doc/**' + - '**/man' - '**.md' - '**.rdoc' - '**/.document' - - '**.[1-8]' - - '**.ronn' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} @@ -28,9 +33,9 @@ jobs: cargo: name: Rust cargo test # GitHub Action's image seems to already contain a Rust 1.58.0. - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # For now we can't run cargo test --offline because it complains about the # capstone dependency, even though the dependency is optional #- run: cargo test --offline @@ -47,9 +52,10 @@ jobs: fail-fast: false matrix: include: - - test_task: "yjit-bindgen" - hint: "To fix: use patch in logs" - configure: "--with-gcc=clang-12 --enable-yjit=dev" + - test_task: 'yjit-bindgen' + hint: 'To fix: use patch in logs' + configure: '--with-gcc=clang-14 --enable-yjit=dev' + libclang_path: '/usr/lib/llvm-14/lib/libclang.so.1' - test_task: "check" # YJIT should be automatically built in release mode on x86-64 Linux with rustc present @@ -79,7 +85,7 @@ jobs: YJIT_BENCH_OPTS: ${{ matrix.yjit_bench_opts }} RUBY_DEBUG: ci BUNDLE_JOBS: 8 # for yjit-bench - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} steps: - run: mkdir build @@ -96,10 +102,10 @@ jobs: run: | git config --global advice.detachedHead 0 git config --global init.defaultBranch garbage - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: src/.downloaded-cache key: downloaded-cache @@ -137,6 +143,7 @@ jobs: RUBY_TESTOPTS: "-q --tty=no" TEST_BUNDLED_GEMS_ALLOW_FAILURES: "" PRECHECK_BUNDLED_GEMS: "no" + LIBCLANG_PATH: ${{ matrix.libclang_path }} continue-on-error: ${{ matrix.test_task == 'yjit-bench' }} - name: Show ${{ github.event.pull_request.base.ref }} GitHub URL for yjit-bench comparison run: echo "https://github.com/${BASE_REPO}/commit/${BASE_SHA}" @@ -144,7 +151,7 @@ jobs: BASE_REPO: ${{ github.event.pull_request.base.repo.full_name }} BASE_SHA: ${{ github.event.pull_request.base.sha }} if: ${{ matrix.test_task == 'yjit-bench' && startsWith(github.event_name, 'pull') }} - - uses: ruby/action-slack@b6882ea6ef8f556f9f9af9ec1220d3f1ced74acf # v3.0.0 + - uses: ruby/action-slack@0bd85c72233cdbb6a0fe01d37aaeff1d21b5fce1 # v3.2.1 with: payload: | { diff --git a/.gitignore b/.gitignore index 535c4fb789..99d32a1825 100644 --- a/.gitignore +++ b/.gitignore @@ -238,7 +238,7 @@ lcov*.info # MJIT /include/ruby-*/*/rb_mjit_min_header-*.h -/lib/mjit/instruction.rb +/lib/ruby_vm/mjit/instruction.rb /mjit_config.h /rb_mjit_header.h diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 665feba914..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,235 +0,0 @@ -# -*- YAML -*- -# Copyright (C) 2011 Urabe, Shyouhei. All rights reserved. -# -# This file is a part of the programming language Ruby. Permission is hereby -# granted, to either redistribute or modify this file, provided that the -# conditions mentioned in the file COPYING are met. Consult the file for -# details. - -# We only manage non-amd64 free pipelines. -# https://docs.travis-ci.com/user/billing-overview/ - -language: c - -os: linux - -if: commit_message !~ /\[DOC\]/ - -dist: focal - -git: - quiet: true - -cache: - ccache: true - directories: - - $HOME/config_2nd - - $HOME/.downloaded-cache - -env: - global: - # The tests skipped in `make test-all`. - - TEST_ALL_SKIPPED_TESTS= - # The tests executed separately by `make test-all`. - - TEST_ALL_SEPARATED_TESTS= - # Reset timestamps early - - _=$(touch NEWS && find . -type f -exec touch -r NEWS {} +) - - CONFIGURE_TTY=no - - CCACHE_COMPILERCHECK=none - - CCACHE_NOCOMPRESS=1 - - CCACHE_MAXSIZE=512Mi - - NPROC="`nproc`" - # JOBS and SETARCH are overridden when necessary; see below. - - JOBS=-j$((1+${NPROC})) - - SETARCH= - - RUBY_PREFIX=/tmp/ruby-prefix - - GEMS_FOR_TEST='timezone tzinfo' - # https://github.com/travis-ci/travis-build/blob/e411371dda21430a60f61b8f3f57943d2fe4d344/lib/travis/build/bash/travis_apt_get_options.bash#L7 - - travis_apt_get_options='--allow-downgrades --allow-remove-essential --allow-change-held-packages' - - travis_apt_get_options="-yq --no-install-suggests --no-install-recommends $travis_apt_get_options" - # -O1 is faster than -O3 in our tests. - - optflags=-O1 - # -g0 disables backtraces when SEGV. Do not set that. - - debugflags=-ggdb3 - -.org.ruby-lang.ci.matrix-definitions: - - - &gcc-10 - compiler: gcc-10 - before_install: - - tool/travis_retry.sh sudo bash -c "rm -rf '${TRAVIS_ROOT}/var/lib/apt/lists/'* && exec apt-get update -yq" - - >- - tool/travis_retry.sh sudo -E apt-get $travis_apt_get_options install - ccache - gcc-10 - g++-10 - libffi-dev - libncurses-dev - libncursesw5-dev - libreadline-dev - libssl-dev - libyaml-dev - openssl - zlib1g-dev - - # -------- - - - &arm64-linux - name: arm64-linux - arch: arm64 - <<: *gcc-10 - - - &ppc64le-linux - name: ppc64le-linux - arch: ppc64le - <<: *gcc-10 - - - &s390x-linux - name: s390x-linux - arch: s390x - <<: *gcc-10 - - - &arm32-linux - name: arm32-linux - arch: arm64 - # https://packages.ubuntu.com/focal/crossbuild-essential-armhf - compiler: arm-linux-gnueabihf-gcc - env: - - SETARCH='setarch linux32 --verbose --32bit' - # The "TestReadline#test_interrupt_in_other_thread" started failing on arm32 - # from https://www.travis-ci.com/github/ruby/ruby/jobs/529005145 - - TEST_ALL_SKIPPED_TESTS=test_interrupt_in_other_thread - before_install: - - sudo dpkg --add-architecture armhf - - tool/travis_retry.sh sudo bash -c "rm -rf '${TRAVIS_ROOT}/var/lib/apt/lists/'* && exec apt-get update -yq" - - >- - tool/travis_retry.sh sudo -E apt-get $travis_apt_get_options install - ccache - crossbuild-essential-armhf - libc6:armhf - libstdc++-10-dev:armhf - libffi-dev:armhf - libncurses-dev:armhf - libncursesw5-dev:armhf - libreadline-dev:armhf - libssl-dev:armhf - libyaml-dev:armhf - linux-libc-dev:armhf - zlib1g-dev:armhf - -matrix: - include: - # Build every commit (Allowed Failures): - - <<: *arm32-linux - # Comment out as the 2nd arm64 pipeline is unstable. - # - <<: *arm64-linux - - <<: *ppc64le-linux - - <<: *s390x-linux - allow_failures: - # We see multiple errors indicating errors on the Travis environment itself in a short while: - # https://app.travis-ci.com/github/ruby/ruby/jobs/544382885 - # https://app.travis-ci.com/github/ruby/ruby/jobs/544361370 - # It's not a fault of Ruby's arm32 support but just Travis arm32 seems unsable. - - name: arm32-linux - # - name: arm64-linux - # We see "Some worker was crashed." in about 40% of recent ppc64le-linux jobs - # e.g. https://app.travis-ci.com/github/ruby/ruby/jobs/530959548 - - name: ppc64le-linux - # Tentatively disable, because often hungs up **after** all tests - # have finished successfully and saving caches. - - name: s390x-linux - fast_finish: true - -before_script: - - . tool/ci_functions.sh - - |- - if [ -n "${TEST_ALL_SKIPPED_TESTS}" ]; then - TEST_ALL_OPTS="${TEST_ALL_OPTS} $(ci_to_excluded_test_opts "${TEST_ALL_SKIPPED_TESTS}")" - if [ -z "${TEST_ALL_SEPARATED_TESTS}" ]; then - TEST_ALL_SEPARATED_TESTS="${TEST_ALL_SKIPPED_TESTS}" - fi - fi - - |- - if [ -n "${TEST_ALL_SEPARATED_TESTS}" ]; then - TEST_ALL_OPTS_SEPARATED="$(ci_to_included_test_opts "${TEST_ALL_SEPARATED_TESTS}")" - fi - - echo TEST_ALL_OPTS="${TEST_ALL_OPTS}" TEST_ALL_OPTS_SEPARATED="${TEST_ALL_OPTS_SEPARATED}" - - rm -fr .ext autom4te.cache - - |- - [ -d ~/.downloaded-cache ] || - mkdir ~/.downloaded-cache - - ln -s ~/.downloaded-cache - - "> config.status" - - "> .rbconfig.time" - - sed -f tool/prereq.status template/Makefile.in common.mk > Makefile - - make -s $JOBS up - - make -s $JOBS srcs - - rm -f config.status Makefile rbconfig.rb .rbconfig.time - - |- - if [ -d ~/config_2nd ]; then - cp -pr ~/config_2nd build - else - mkdir build - fi - - mkdir config_1st config_2nd gems/src - - chmod -R a-w . - - chmod -R u+w build config_1st config_2nd gems/src - - cd build - - |- - case "$CC" in - gcc*) CC="ccache $CC${GCC_FLAGS:+ }$GCC_FLAGS -fno-diagnostics-color";; - clang*) CC="ccache $CC${GCC_FLAGS:+ }$GCC_FLAGS -fno-color-diagnostics";; - esac - - |- - [ ! -f config.cache ] || - [ "$CC" = "`sed -n s/^ac_cv_prog_CC=//p config.cache`" ] || - (set -x; exec rm config.cache) - - $SETARCH ../configure -C --disable-install-doc --prefix=$RUBY_PREFIX $CONFIG_FLAG - - cp -pr config.cache config.status .ext/include ../config_1st - - $SETARCH make reconfig - - cp -pr config.cache config.status .ext/include ../config_2nd - - (cd .. && exec diff -ru config_1st config_2nd) - - chmod u+w .. - - rm -rf ~/config_2nd - - mv ../config_2nd ~ - - chmod u-w .. - - $SETARCH make -s $JOBS - - make -s install - - |- - [ -z "${GEMS_FOR_TEST}" ] || - $RUBY_PREFIX/bin/gem install --no-document $GEMS_FOR_TEST - - echo "raise 'do not load ~/.irbrc in test'" > ~/.irbrc - -script: - - $SETARCH make -s test -o showflags TESTOPTS="${TESTOPTS=$JOBS -q --tty=no}" - - ../tool/travis_wait.sh $SETARCH make -s test-all -o exts TESTOPTS="$JOBS -q --tty=no ${TEST_ALL_OPTS}" RUBYOPT="-w" - # Run the failing tests separately returning ok status to check if it works, - # visualize them. - - | - if [ -n "${TEST_ALL_OPTS_SEPARATED}" ]; then - $SETARCH make -s test-all -o exts TESTOPTS="$JOBS -v --tty=no ${TEST_ALL_OPTS_SEPARATED}" RUBYOPT="-w" || : - fi - - $SETARCH make -s test-spec MSPECOPT=-ff # not using `-j` because sometimes `mspec -j` silently dies - - $SETARCH make -s -o showflags leaked-globals - -# We enable Travis on the specific branches or forked repositories here. -if: (repo = ruby/ruby AND (branch = master OR branch =~ /^ruby_\d_\d$/)) OR repo != ruby/ruby - -# We want to be notified when something happens. -notifications: - irc: - channels: - - "chat.freenode.net#ruby-core" - on_success: change # [always|never|change] # default: always - on_failure: always # [always|never|change] # default: always - template: - - "%{message} by @%{author}: See %{build_url}" - - webhooks: - urls: - - secure: mRsoS/UbqDkKkW5p3AEqM27d4SZnV6Gsylo3bm8T/deltQzTsGzZwrm7OIBXZv0UFZdE68XmPlyHfZFLSP2V9QZ7apXMf9/vw0GtcSe1gchtnjpAPF6lYBn7nMCbVPPx9cS0dwL927fjdRM1vj7IKZ2bk4F0lAJ25R25S6teqdk= # ruby-lang slack: ruby/simpler-alerts-bot (travis) - on_success: never - on_failure: always - - email: - - jaruga@ruby-lang.org @@ -979,7 +979,6 @@ mentioned below. {MIT License}[rdoc-label:label-MIT+License] [lib/rubygems/resolver/molinillo] -[lib/bundler/vendor/molinillo] molinillo is under the following license. @@ -988,6 +987,15 @@ mentioned below. {MIT License}[rdoc-label:label-MIT+License] +[lib/bundler/vendor/pub_grub] + + pub_grub is under the following license. + + >>> + Copyright (c) 2018 John Hawthorn + + {MIT License}[rdoc-label:label-MIT+License] + [lib/bundler/vendor/connection_pool] connection_pool is under the following license. @@ -90,14 +90,6 @@ Note that each entry is kept to a minimum, see links for details. foo(k: 1) ``` -* `eval` and related methods are able to generate code coverage. Enabled using - `Coverage.setup(:all)` or `Coverage.setup(eval: true)`. [[Feature #19008]] - -* `Coverage.supported?(mode)` enables detection of what coverage modes are - supported. [[Feature #19026]] - -## Command line options - ## Core classes updates Note: We're only listing outstanding class updates. @@ -111,8 +103,8 @@ Note: We're only listing outstanding class updates. fiber. [[Feature #19078]] Existing Thread and Fiber local variables can be tricky to use. - Thread local variables are shared between all fibers, making it - hard to isolate, while Fiber local variables can be hard to + Thread-local variables are shared between all fibers, making it + hard to isolate, while Fiber-local variables can be hard to share. It is often desirable to define unit of execution ("execution context") such that some state is shared between all fibers and threads created in that context. This is what Fiber @@ -159,11 +151,8 @@ Note: We're only listing outstanding class updates. STDIN.read # => Blocking operation timed out! (IO::TimeoutError) ``` -* UNIXSocket - - * Add support for UNIXSocket on Windows. Emulate anonymous sockets. Add - support for File.socket? and File::Stat#socket? where possible. - [[Feature #19135]] + * Introduce `IO.new(..., path:)` and promote `File#path` to `IO#path`. + [[Feature #19036]] * Class @@ -187,6 +176,15 @@ Note: We're only listing outstanding class updates. similar to Struct and partially shares an implementation, but has more lean and strict API. [[Feature #16122]] + ```ruby + Measure = Data.define(:amount, :unit) + distance = Measure.new(100, 'km') #=> #<data Measure amount=100, unit="km"> + weight = Measure.new(amount: 50, unit: 'kg') #=> #<data Measure amount=50, unit="kg"> + weight.with(amount: 40) #=> #<data Measure amount=40, unit="kg"> + weight.amount #=> 50 + weight.amount = 40 #=> NoMethodError: undefined method `amount=' + ``` + * Encoding * Encoding#replicate has been deprecated and will be removed in 3.3. [[Feature #18949]] @@ -194,6 +192,8 @@ Note: We're only listing outstanding class updates. try to dynamically guess the endian based on a byte order mark. Use `Encoding::UTF_16BE`/`UTF_16LE` and `Encoding::UTF_32BE`/`UTF_32LE` instead. This change speeds up getting the encoding of a String. [[Feature #18949]] + * Limit maximum encoding set size by 256. + If exceeding maximum size, `EncodingError` will be raised. [[Feature #18949]] * Enumerator @@ -243,6 +243,13 @@ Note: We're only listing outstanding class updates. * Regexp + * The cache-based optimization is introduced. + Many (but not all) Regexp matching is now in linear time, which + will prevent regular expression denial of service (ReDoS) + vulnerability. [[Feature #19104]] + + * Regexp.linear_time? is introduced. [[Feature #19194]] + * Regexp.new now supports passing the regexp flags not only as an Integer, but also as a String. Unknown flags raise ArgumentError. Otherwise, anything other than `true`, `false`, `nil` or Integer will be warned. @@ -313,17 +320,6 @@ Note: We're only listing outstanding class updates. * Set is now available as a built-in class without the need for `require "set"`. [[Feature #16989]] It is currently autoloaded via the Set constant or a call to Enumerable#to_set. -* Socket - - * Added the following constants for supported platforms. - * `SO_INCOMING_CPU` - * `SO_INCOMING_NAPI_ID` - * `SO_RTABLE` - * `SO_SETFIB` - * `SO_USER_COOKIE` - * `TCP_KEEPALIVE` - * `TCP_CONNECTION_INFO` - * String * String#byteindex and String#byterindex have been added. [[Feature #13110]] @@ -337,24 +333,35 @@ Note: We're only listing outstanding class updates. * A Struct class can also be initialized with keyword arguments without `keyword_init: true` on Struct.new [[Feature #16806]] + ```ruby + Post = Struct.new(:id, :name) + Post.new(1, "hello") #=> #<struct Post id=1, name="hello"> + # From Ruby 3.2, the following code also works without keyword_init: true. + Post.new(id: 1, name: "hello") #=> #<struct Post id=1, name="hello"> + ``` + * Thread - * Thread#each_caller_location is added. [[Feature #16663]] + * Thread.each_caller_location is added. [[Feature #16663]] * Thread::Queue - * Thread::Queue.pop(timeout: sec) is added. [[Feature #18774]] + * Thread::Queue#pop(timeout: sec) is added. [[Feature #18774]] * Thread::SizedQueue - * Thread::SizedQueue.pop(timeout: sec) is added. [[Feature #18774]] - * Thread::SizedQueue.push(timeout: sec) is added. [[Feature #18944]] + * Thread::SizedQueue#pop(timeout: sec) is added. [[Feature #18774]] + * Thread::SizedQueue#push(timeout: sec) is added. [[Feature #18944]] * Time * Time#deconstruct_keys is added, allowing to use Time instances in pattern-matching expressions [[Feature #19071]] + * Time.new now can parse a string like generated by Time#inspect + and return a Time instance based on the given argument. + [[Feature #18033]] + * SyntaxError * SyntaxError#path has been added. [[Feature #19138]] @@ -363,8 +370,8 @@ Note: We're only listing outstanding class updates. * TracePoint#binding now returns `nil` for `c_call`/`c_return` TracePoints. [[Bug #18487]] * TracePoint#enable `target_thread` keyword argument now defaults to the - current thread if `target` and `target_line` keyword arguments are not - passed. [[Bug #16889]] + current thread if a block is given and `target` and `target_line` keyword + arguments are not passed. [[Bug #16889]] * UnboundMethod @@ -377,10 +384,52 @@ Note: We're only listing outstanding class updates. `"#<UnboundMethod: Kernel#object_id()>"` (was `"#<UnboundMethod: String(Kernel)#object_id()>"`). +* GC + + * Expose `need_major_gc` via `GC.latest_gc_info`. [GH-6791] + +* ObjectSpace + + * `ObjectSpace.dump_all` dump shapes as well. [GH-6868] + ## Stdlib updates +* Bundler + + * Bundler now uses [PubGrub] resolver instead of [Molinillo] for performance improvement. + * Add --ext=rust support to bundle gem for creating simple gems with Rust extensions. + [[GH-rubygems-6149]] + * Make cloning git repos faster [[GH-rubygems-4475]] + +* RubyGems + + * Add mswin support for cargo builder. [[GH-rubygems-6167]] + +* CGI + + * `CGI.escapeURIComponent` and `CGI.unescapeURIComponent` are added. + [[Feature #18822]] + +* Coverage + + * `Coverage.setup` now accepts `eval: true`. By this, `eval` and related methods are + able to generate code coverage. [[Feature #19008]] + + * `Coverage.supported?(mode)` enables detection of what coverage modes are + supported. [[Feature #19026]] + +* Date + + * Added `Date#deconstruct_keys` and `DateTime#deconstruct_keys` same as [[Feature #19071]] + * ERB + * `ERB::Util.html_escape` is made faster than `CGI.escapeHTML`. + * It no longer allocates a String object when no character needs to be escaped. + * It skips calling `#to_s` method when an argument is already a String. + * `ERB::Escape.html_escape` is added as an alias to `ERB::Util.html_escape`, + which has not been monkey-patched by Rails. + * `ERB::Util.url_encode` is made faster using `CGI.escapeURIComponent`. * `-S` option is removed from `erb` command. * FileUtils @@ -388,24 +437,63 @@ Note: We're only listing outstanding class updates. * Add FileUtils.ln_sr method and `relative:` option to FileUtils.ln_s. [[Feature #18925]] +* IRB + + * debug.gem integration commands have been added: `debug`, `break`, `catch`, + `next`, `delete`, `step`, `continue`, `finish`, `backtrace`, `info` + * They work even if you don't have `gem "debug"` in your Gemfile. + * See also: [What's new in Ruby 3.2's IRB?](https://st0012.dev/whats-new-in-ruby-3-2-irb) + * More Pry-like commands and features have been added. + * `edit` and `show_cmds` (like Pry's `help`) are added. + * `ls` takes `-g` or `-G` option to filter out outputs. + * `show_source` is aliased from `$` and accepts unquoted inputs. + * `whereami` is aliased from `@`. + +* Net::Protocol + + * Improve `Net::BufferedIO` performance. [[GH-net-protocol-14]] + +* Pathname + + * Added `Pathname#lutime`. [[GH-pathname-20]] + +* Socket + + * Added the following constants for supported platforms. + * `SO_INCOMING_CPU` + * `SO_INCOMING_NAPI_ID` + * `SO_RTABLE` + * `SO_SETFIB` + * `SO_USER_COOKIE` + * `TCP_KEEPALIVE` + * `TCP_CONNECTION_INFO` + * SyntaxSuggest * The feature of `syntax_suggest` formerly `dead_end` is integrated in Ruby. [[Feature #18159]] +* UNIXSocket + + * Add support for UNIXSocket on Windows. Emulate anonymous sockets. Add + support for File.socket? and File::Stat#socket? where possible. + [[Feature #19135]] + * The following default gems are updated. - * RubyGems 3.4.0.dev + * RubyGems 3.4.1 + * abbrev 0.1.1 * benchmark 0.2.1 * bigdecimal 3.1.3 - * bundler 2.4.0.dev + * bundler 2.4.1 * cgi 0.3.6 * csv 3.2.6 - * date 3.3.1 + * date 3.3.3 * delegate 0.3.0 - * did_you_mean 1.6.2 + * did_you_mean 1.6.3 * digest 3.1.1 * drb 2.1.1 + * english 0.7.2 * erb 4.0.2 * error_highlight 0.5.1 * etc 1.4.2 @@ -414,39 +502,46 @@ Note: We're only listing outstanding class updates. * fileutils 1.7.0 * forwardable 1.3.3 * getoptlong 0.2.0 - * io-console 0.5.11 + * io-console 0.6.0 * io-nonblock 0.2.0 - * io-wait 0.3.0.pre + * io-wait 0.3.0 * ipaddr 1.2.5 - * irb 1.6.0 + * irb 1.6.2 * json 2.6.3 - * logger 1.5.2 + * logger 1.5.3 * mutex_m 0.1.2 - * net-http 0.3.1 + * net-http 0.4.0 * net-protocol 0.2.1 * nkf 0.1.2 * open-uri 0.3.0 - * openssl 3.1.0.pre - * optparse 0.3.0 + * open3 0.1.2 + * openssl 3.1.0 + * optparse 0.3.1 * ostruct 0.5.5 * pathname 0.2.1 * pp 0.4.0 * pstore 0.1.2 * psych 5.0.1 - * racc 1.6.1 + * racc 1.6.2 * rdoc 6.5.0 - * reline 0.3.1 + * readline-ext 0.1.5 + * reline 0.3.2 * resolv 0.2.2 - * securerandom 0.2.1 + * resolv-replace 0.1.1 + * securerandom 0.2.2 * set 1.0.3 * stringio 3.0.4 * strscan 3.0.5 - * syntax_suggest 1.0.1 + * syntax_suggest 1.0.2 + * syslog 0.1.1 + * tempfile 0.1.3 + * time 0.2.1 * timeout 0.3.1 * tmpdir 0.1.3 * tsort 0.1.1 * un 0.2.1 * uri 0.12.0 + * weakref 0.1.2 * win32ole 1.8.9 * yaml 0.2.1 * zlib 3.0.0 @@ -454,23 +549,29 @@ Note: We're only listing outstanding class updates. * The following bundled gems are updated. * minitest 5.16.3 - * power_assert 2.0.2 - * test-unit 3.5.5 + * power_assert 2.0.3 + * test-unit 3.5.7 * net-ftp 0.2.0 - * net-imap 0.3.1 + * net-imap 0.3.4 * net-pop 0.1.2 * net-smtp 0.3.3 - * rbs 2.8.1 + * rbs 2.8.2 * typeprof 0.21.3 - * debug 1.7.0 + * debug 1.7.1 + +See GitHub releases like [GitHub Releases of Logger](https://github.com/ruby/logger/releases) or changelog for details of the default gems or bundled gems. -* The following default gems are now bundled gems. +## Supported platforms + +* WebAssembly/WASI is added. See [wasm/README.md] and [ruby.wasm] for more details. [[Feature #18462]] ## Compatibility issues * `String#to_c` currently treat a sequence of underscores as an end of Complex string. [[Bug #19087]] +* Now `ENV.clone` raises `TypeError` as well as `ENV.dup` [[Bug #17767]] + ### Removed constants The following deprecated constants are removed. @@ -508,9 +609,9 @@ The following deprecated methods are removed. ### Constant lookup when defining a class/module * When defining a class/module directly under the Object class by class/module - statement, if there is already a class/module with the same name, the statement - was handled as "open class" in Ruby 3.1 or before. Since Ruby 3.2, a new class - is defined instead. [[Feature #18832]] + statement, if there is already a class/module defined by `Module#include` + with the same name, the statement was handled as "open class" in Ruby 3.1 or before. + Since Ruby 3.2, a new class is defined instead. [[Feature #18832]] ## Stdlib compatibility issues @@ -534,6 +635,10 @@ The following deprecated methods are removed. [[Feature #18571]] +* Check cookie name/path/domain characters in `CGI::Cookie`. [[CVE-2021-33621]] + +* `URI.parse` return empty string in host instead of nil. [[sec-156615]] + ## C API updates ### Updated C APIs @@ -542,9 +647,9 @@ The following APIs are updated. * PRNG update - `rb_random_interface_t` updated and versioned. - Extension libraries which use this interface and built for older versions. - Also `init_int32` function needs to be defined. + `rb_random_interface_t` in ruby/random.h updated and versioned. + Extension libraries which use this interface and built for older + versions need to rebuild with adding `init_int32` function. ### Added C APIs @@ -556,6 +661,7 @@ The following APIs are updated. * `RUBY_INTERNAL_THREAD_EVENT_RESUMED` * `RUBY_INTERNAL_THREAD_EVENT_SUSPENDED` * `RUBY_INTERNAL_THREAD_EVENT_EXITED` +* `rb_debug_inspector_current_depth` and `rb_debug_inspector_frame_depth` are added for debuggers. ### Removed C APIs @@ -574,11 +680,25 @@ The following deprecated APIs are removed. New keys, `:constant_cache_invalidations` and `:constant_cache_misses`, were introduced to help with use cases for `:global_constant_state`. [[Feature #18589]] +* The cache-based optimization for Regexp matching is introduced. + [[Feature #19104]] +* [Variable Width Allocation](https://shopify.engineering/ruby-variable-width-allocation) + is now enabled by default. [[Feature #18239]] +* Added a new instance variable caching mechanism, called object shapes, which + improves inline cache hits for most objects and allows us to generate very + efficient JIT code. Objects whose instance variables are defined in a + consistent order will see the most performance benefits. + [[Feature #18776]] +* Speed up marking instruction sequences by using a bitmap to find "markable" + objects. This change results in faster major collections. + [[Feature #18875]] ## JIT ### YJIT +* YJIT is no longer experimental + * Has been tested on production workloads for over a year and proven to be quite stable. * YJIT now supports both x86-64 and arm64/aarch64 CPUs on Linux, MacOS, BSD and other UNIX platforms. * This release brings support for Mac M1/M2, AWS Graviton and Raspberry Pi 4. * Building YJIT now requires Rust 1.58.0+. [[Feature #18481]] @@ -598,12 +718,12 @@ The following deprecated APIs are removed. * Simply run ruby with `--yjit-stats` to compute and dump stats (incurs some run-time overhead). * YJIT is now optimized to take advantage of object shapes. [[Feature #18776]] * Take advantage of finer-grained constant invalidation to invalidate less code when defining new constants. [[Feature #18589]] -* The default `--yjit-exec-mem-size` is changed to 128 (MiB). +* The default `--yjit-exec-mem-size` is changed to 64 (MiB). * The default `--yjit-call-threshold` is changed to 30. ### MJIT -* The MJIT compiler is re-implemented in Ruby as a standard library `mjit`. +* The MJIT compiler is re-implemented in Ruby as `ruby_vm/mjit/compiler`. * MJIT compiler is executed under a forked Ruby process instead of doing it in a native thread called MJIT worker. [[Feature #18968]] * As a result, Microsoft Visual Studio (MSWIN) is no longer supported. @@ -611,68 +731,90 @@ The following deprecated APIs are removed. * Rename `--mjit-min-calls` to `--mjit-call-threshold`. * Change default `--mjit-max-cache` back from 10000 to 100. -[Feature #12005]: https://bugs.ruby-lang.org/issues/12005 -[Feature #12084]: https://bugs.ruby-lang.org/issues/12084 -[Feature #12655]: https://bugs.ruby-lang.org/issues/12655 -[Feature #12737]: https://bugs.ruby-lang.org/issues/12737 -[Feature #13110]: https://bugs.ruby-lang.org/issues/13110 -[Feature #14332]: https://bugs.ruby-lang.org/issues/14332 -[Feature #15231]: https://bugs.ruby-lang.org/issues/15231 -[Feature #15357]: https://bugs.ruby-lang.org/issues/15357 -[Bug #15928]: https://bugs.ruby-lang.org/issues/15928 -[Feature #16122]: https://bugs.ruby-lang.org/issues/16122 -[Feature #16131]: https://bugs.ruby-lang.org/issues/16131 -[Bug #16466]: https://bugs.ruby-lang.org/issues/16466 -[Feature #16663]: https://bugs.ruby-lang.org/issues/16663 -[Feature #16806]: https://bugs.ruby-lang.org/issues/16806 -[Bug #16889]: https://bugs.ruby-lang.org/issues/16889 -[Bug #16908]: https://bugs.ruby-lang.org/issues/16908 -[Feature #16989]: https://bugs.ruby-lang.org/issues/16989 -[Feature #17351]: https://bugs.ruby-lang.org/issues/17351 -[Feature #17391]: https://bugs.ruby-lang.org/issues/17391 -[Bug #17545]: https://bugs.ruby-lang.org/issues/17545 -[Feature #17837]: https://bugs.ruby-lang.org/issues/17837 -[Feature #17881]: https://bugs.ruby-lang.org/issues/17881 -[Feature #18159]: https://bugs.ruby-lang.org/issues/18159 -[Feature #18351]: https://bugs.ruby-lang.org/issues/18351 -[Feature #18367]: https://bugs.ruby-lang.org/issues/18367 -[Bug #18435]: https://bugs.ruby-lang.org/issues/18435 -[Feature #18481]: https://bugs.ruby-lang.org/issues/18481 -[Bug #18487]: https://bugs.ruby-lang.org/issues/18487 -[Feature #18564]: https://bugs.ruby-lang.org/issues/18564 -[Feature #18571]: https://bugs.ruby-lang.org/issues/18571 -[Feature #18585]: https://bugs.ruby-lang.org/issues/18585 -[Feature #18589]: https://bugs.ruby-lang.org/issues/18589 -[Feature #18595]: https://bugs.ruby-lang.org/issues/18595 -[Feature #18598]: https://bugs.ruby-lang.org/issues/18598 -[Bug #18625]: https://bugs.ruby-lang.org/issues/18625 -[Feature #18630]: https://bugs.ruby-lang.org/issues/18630 -[Bug #18633]: https://bugs.ruby-lang.org/issues/18633 -[Feature #18639]: https://bugs.ruby-lang.org/issues/18639 -[Feature #18685]: https://bugs.ruby-lang.org/issues/18685 -[Bug #18729]: https://bugs.ruby-lang.org/issues/18729 -[Bug #18751]: https://bugs.ruby-lang.org/issues/18751 -[Feature #18774]: https://bugs.ruby-lang.org/issues/18774 -[Feature #18776]: https://bugs.ruby-lang.org/issues/18776 -[Bug #18782]: https://bugs.ruby-lang.org/issues/18782 -[Feature #18788]: https://bugs.ruby-lang.org/issues/18788 -[Feature #18798]: https://bugs.ruby-lang.org/issues/18798 -[Feature #18809]: https://bugs.ruby-lang.org/issues/18809 -[Feature #18821]: https://bugs.ruby-lang.org/issues/18821 -[Feature #18824]: https://bugs.ruby-lang.org/issues/18824 -[Feature #18832]: https://bugs.ruby-lang.org/issues/18832 -[Feature #18925]: https://bugs.ruby-lang.org/issues/18925 -[Feature #18944]: https://bugs.ruby-lang.org/issues/18944 -[Feature #18949]: https://bugs.ruby-lang.org/issues/18949 -[Feature #18968]: https://bugs.ruby-lang.org/issues/18968 -[Feature #19008]: https://bugs.ruby-lang.org/issues/19008 -[Feature #19013]: https://bugs.ruby-lang.org/issues/19013 -[Feature #19026]: https://bugs.ruby-lang.org/issues/19026 -[Feature #19060]: https://bugs.ruby-lang.org/issues/19060 -[Feature #19070]: https://bugs.ruby-lang.org/issues/19070 -[Feature #19071]: https://bugs.ruby-lang.org/issues/19071 -[Feature #19078]: https://bugs.ruby-lang.org/issues/19078 -[Bug #19087]: https://bugs.ruby-lang.org/issues/19087 -[Bug #19100]: https://bugs.ruby-lang.org/issues/19100 -[Feature #19135]: https://bugs.ruby-lang.org/issues/19135 -[Feature #19138]: https://bugs.ruby-lang.org/issues/19138 +[Feature #12005]: https://bugs.ruby-lang.org/issues/12005 +[Feature #12084]: https://bugs.ruby-lang.org/issues/12084 +[Feature #12655]: https://bugs.ruby-lang.org/issues/12655 +[Feature #12737]: https://bugs.ruby-lang.org/issues/12737 +[Feature #13110]: https://bugs.ruby-lang.org/issues/13110 +[Feature #14332]: https://bugs.ruby-lang.org/issues/14332 +[Feature #15231]: https://bugs.ruby-lang.org/issues/15231 +[Feature #15357]: https://bugs.ruby-lang.org/issues/15357 +[Bug #15928]: https://bugs.ruby-lang.org/issues/15928 +[Feature #16122]: https://bugs.ruby-lang.org/issues/16122 +[Feature #16131]: https://bugs.ruby-lang.org/issues/16131 +[Bug #16466]: https://bugs.ruby-lang.org/issues/16466 +[Feature #16663]: https://bugs.ruby-lang.org/issues/16663 +[Feature #16806]: https://bugs.ruby-lang.org/issues/16806 +[Bug #16889]: https://bugs.ruby-lang.org/issues/16889 +[Bug #16908]: https://bugs.ruby-lang.org/issues/16908 +[Feature #16989]: https://bugs.ruby-lang.org/issues/16989 +[Feature #17351]: https://bugs.ruby-lang.org/issues/17351 +[Feature #17391]: https://bugs.ruby-lang.org/issues/17391 +[Bug #17545]: https://bugs.ruby-lang.org/issues/17545 +[Bug #17767]: https://bugs.ruby-lang.org/issues/17767 +[Feature #17837]: https://bugs.ruby-lang.org/issues/17837 +[Feature #17881]: https://bugs.ruby-lang.org/issues/17881 +[Feature #18033]: https://bugs.ruby-lang.org/issues/18033 +[Feature #18159]: https://bugs.ruby-lang.org/issues/18159 +[Feature #18239]: https://bugs.ruby-lang.org/issues/18239#note-17 +[Feature #18351]: https://bugs.ruby-lang.org/issues/18351 +[Feature #18367]: https://bugs.ruby-lang.org/issues/18367 +[Bug #18435]: https://bugs.ruby-lang.org/issues/18435 +[Feature #18462]: https://bugs.ruby-lang.org/issues/18462 +[Feature #18481]: https://bugs.ruby-lang.org/issues/18481 +[Bug #18487]: https://bugs.ruby-lang.org/issues/18487 +[Feature #18564]: https://bugs.ruby-lang.org/issues/18564 +[Feature #18571]: https://bugs.ruby-lang.org/issues/18571 +[Feature #18585]: https://bugs.ruby-lang.org/issues/18585 +[Feature #18589]: https://bugs.ruby-lang.org/issues/18589 +[Feature #18595]: https://bugs.ruby-lang.org/issues/18595 +[Feature #18598]: https://bugs.ruby-lang.org/issues/18598 +[Bug #18625]: https://bugs.ruby-lang.org/issues/18625 +[Feature #18630]: https://bugs.ruby-lang.org/issues/18630 +[Bug #18633]: https://bugs.ruby-lang.org/issues/18633 +[Feature #18639]: https://bugs.ruby-lang.org/issues/18639 +[Feature #18685]: https://bugs.ruby-lang.org/issues/18685 +[Bug #18729]: https://bugs.ruby-lang.org/issues/18729 +[Bug #18751]: https://bugs.ruby-lang.org/issues/18751 +[Feature #18774]: https://bugs.ruby-lang.org/issues/18774 +[Feature #18776]: https://bugs.ruby-lang.org/issues/18776 +[Bug #18782]: https://bugs.ruby-lang.org/issues/18782 +[Feature #18788]: https://bugs.ruby-lang.org/issues/18788 +[Feature #18798]: https://bugs.ruby-lang.org/issues/18798 +[Feature #18809]: https://bugs.ruby-lang.org/issues/18809 +[Feature #18821]: https://bugs.ruby-lang.org/issues/18821 +[Feature #18822]: https://bugs.ruby-lang.org/issues/18822 +[Feature #18824]: https://bugs.ruby-lang.org/issues/18824 +[Feature #18832]: https://bugs.ruby-lang.org/issues/18832 +[Feature #18875]: https://bugs.ruby-lang.org/issues/18875 +[Feature #18925]: https://bugs.ruby-lang.org/issues/18925 +[Feature #18944]: https://bugs.ruby-lang.org/issues/18944 +[Feature #18949]: https://bugs.ruby-lang.org/issues/18949 +[Feature #18968]: https://bugs.ruby-lang.org/issues/18968 +[Feature #19008]: https://bugs.ruby-lang.org/issues/19008 +[Feature #19013]: https://bugs.ruby-lang.org/issues/19013 +[Feature #19026]: https://bugs.ruby-lang.org/issues/19026 +[Feature #19036]: https://bugs.ruby-lang.org/issues/19036 +[Feature #19060]: https://bugs.ruby-lang.org/issues/19060 +[Feature #19070]: https://bugs.ruby-lang.org/issues/19070 +[Feature #19071]: https://bugs.ruby-lang.org/issues/19071 +[Feature #19078]: https://bugs.ruby-lang.org/issues/19078 +[Bug #19087]: https://bugs.ruby-lang.org/issues/19087 +[Bug #19100]: https://bugs.ruby-lang.org/issues/19100 +[Feature #19104]: https://bugs.ruby-lang.org/issues/19104 +[Feature #19135]: https://bugs.ruby-lang.org/issues/19135 +[Feature #19138]: https://bugs.ruby-lang.org/issues/19138 +[Feature #19194]: https://bugs.ruby-lang.org/issues/19194 +[Molinillo]: https://github.com/CocoaPods/Molinillo +[PubGrub]: https://github.com/jhawthorn/pub_grub +[GH-net-protocol-14]: https://github.com/ruby/net-protocol/pull/14 +[GH-pathname-20]: https://github.com/ruby/pathname/pull/20 +[GH-6791]: https://github.com/ruby/ruby/pull/6791 +[GH-6868]: https://github.com/ruby/ruby/pull/6868 +[GH-rubygems-4475]: https://github.com/rubygems/rubygems/pull/4475 +[GH-rubygems-6149]: https://github.com/rubygems/rubygems/pull/6149 +[GH-rubygems-6167]: https://github.com/rubygems/rubygems/pull/6167 +[sec-156615]: https://hackerone.com/reports/156615 +[CVE-2021-33621]: https://www.ruby-lang.org/en/news/2022/11/22/http-response-splitting-in-cgi-cve-2021-33621/ +[wasm/README.md]: https://github.com/ruby/ruby/blob/master/wasm/README.md +[ruby.wasm]: https://github.com/ruby/ruby.wasm diff --git a/README.ja.md b/README.ja.md index bb69c09055..93c0131690 100644 --- a/README.ja.md +++ b/README.ja.md @@ -4,7 +4,6 @@ [](https://github.com/ruby/ruby/actions?query=workflow%3A"Windows") [](https://ci.appveyor.com/project/ruby/ruby/branch/master) [](https://app.travis-ci.com/ruby/ruby) -[](https://cirrus-ci.com/github/ruby/ruby/master) # Rubyとは @@ -4,7 +4,6 @@ [](https://github.com/ruby/ruby/actions?query=workflow%3A"Windows") [](https://ci.appveyor.com/project/ruby/ruby/branch/master) [](https://app.travis-ci.com/ruby/ruby) -[](https://cirrus-ci.com/github/ruby/ruby/master) # What is Ruby? diff --git a/addr2line.c b/addr2line.c index 0d45ec9414..e5f25293e2 100644 --- a/addr2line.c +++ b/addr2line.c @@ -159,12 +159,15 @@ typedef struct obj_info { struct dwarf_section debug_info; struct dwarf_section debug_line; struct dwarf_section debug_ranges; + struct dwarf_section debug_str_offsets; + struct dwarf_section debug_addr; struct dwarf_section debug_rnglists; struct dwarf_section debug_str; + struct dwarf_section debug_line_str; struct obj_info *next; } obj_info_t; -#define DWARF_SECTION_COUNT 6 +#define DWARF_SECTION_COUNT 9 static struct dwarf_section * obj_dwarf_section_at(obj_info_t *obj, int n) @@ -174,8 +177,11 @@ obj_dwarf_section_at(obj_info_t *obj, int n) &obj->debug_info, &obj->debug_line, &obj->debug_ranges, + &obj->debug_str_offsets, + &obj->debug_addr, &obj->debug_rnglists, - &obj->debug_str + &obj->debug_str, + &obj->debug_line_str }; if (n < 0 || DWARF_SECTION_COUNT <= n) { abort(); @@ -248,39 +254,51 @@ get_nth_dirname(unsigned long dir, const char *p) return p; } +static const char *parse_ver5_debug_line_header(const char *p, int idx, uint8_t format, obj_info_t *obj, const char **out_path, uint64_t *out_directory_index); + static void -fill_filename(int file, const char *include_directories, const char *filenames, line_info_t *line, obj_info_t *obj) +fill_filename(int file, uint8_t format, uint16_t version, const char *include_directories, const char *filenames, line_info_t *line, obj_info_t *obj) { int i; const char *p = filenames; const char *filename; unsigned long dir; - for (i = 1; i <= file; i++) { - filename = p; - if (!*p) { - /* Need to output binary file name? */ - kprintf("Unexpected file number %d in %s at %tx\n", - file, binary_filename, filenames - obj->mapped); - return; - } - while (*p) p++; - p++; - dir = uleb128(&p); - /* last modified. */ - uleb128(&p); - /* size of the file. */ - uleb128(&p); - - if (i == file) { - line->filename = filename; - line->dirname = get_nth_dirname(dir, include_directories); - } + if (version >= 5) { + const char *path; + uint64_t directory_index = -1; + parse_ver5_debug_line_header(filenames, file, format, obj, &path, &directory_index); + line->filename = path; + parse_ver5_debug_line_header(include_directories, (int)directory_index, format, obj, &path, NULL); + line->dirname = path; + } + else { + for (i = 1; i <= file; i++) { + filename = p; + if (!*p) { + /* Need to output binary file name? */ + kprintf("Unexpected file number %d in %s at %tx\n", + file, binary_filename, filenames - obj->mapped); + return; + } + while (*p) p++; + p++; + dir = uleb128(&p); + /* last modified. */ + uleb128(&p); + /* size of the file. */ + uleb128(&p); + + if (i == file) { + line->filename = filename; + line->dirname = get_nth_dirname(dir, include_directories); + } + } } } static void fill_line(int num_traces, void **traces, uintptr_t addr, int file, int line, - const char *include_directories, const char *filenames, + uint8_t format, uint16_t version, const char *include_directories, const char *filenames, obj_info_t *obj, line_info_t *lines, int offset) { int i; @@ -290,7 +308,7 @@ fill_line(int num_traces, void **traces, uintptr_t addr, int file, int line, /* We assume one line code doesn't result >100 bytes of native code. We may want more reliable way eventually... */ if (addr < a && a < addr + 100) { - fill_filename(file, include_directories, filenames, &lines[i], obj); + fill_filename(file, format, version, include_directories, filenames, &lines[i], obj); lines[i].line = line; } } @@ -315,7 +333,7 @@ struct LineNumberProgramHeader { }; static int -parse_debug_line_header(const char **pp, struct LineNumberProgramHeader *header) +parse_debug_line_header(obj_info_t *obj, const char **pp, struct LineNumberProgramHeader *header) { const char *p = *pp; header->unit_length = *(uint32_t *)p; @@ -332,7 +350,13 @@ parse_debug_line_header(const char **pp, struct LineNumberProgramHeader *header) header->version = *(uint16_t *)p; p += sizeof(uint16_t); - if (header->version > 4) return -1; + if (header->version > 5) return -1; + + if (header->version >= 5) { + /* address_size = *(uint8_t *)p++; */ + /* segment_selector_size = *(uint8_t *)p++; */ + p += 2; + } header->header_length = header->format == 4 ? *(uint32_t *)p : *(uint64_t *)p; p += header->format; @@ -353,20 +377,27 @@ parse_debug_line_header(const char **pp, struct LineNumberProgramHeader *header) /* header->standard_opcode_lengths = (uint8_t *)p - 1; */ p += header->opcode_base - 1; - header->include_directories = p; + if (header->version >= 5) { + header->include_directories = p; + p = parse_ver5_debug_line_header(p, -1, header->format, obj, NULL, NULL); + header->filenames = p; + } + else { + header->include_directories = p; - /* temporary measure for compress-debug-sections */ - if (p >= header->cu_end) return -1; + /* temporary measure for compress-debug-sections */ + if (p >= header->cu_end) return -1; - /* skip include directories */ - while (*p) { - p = memchr(p, '\0', header->cu_end - p); - if (!p) return -1; - p++; - } - p++; + /* skip include directories */ + while (*p) { + p = memchr(p, '\0', header->cu_end - p); + if (!p) return -1; + p++; + } + p++; - header->filenames = p; + header->filenames = p; + } *pp = header->cu_start; @@ -392,13 +423,15 @@ parse_debug_line_cu(int num_traces, void **traces, const char **debug_line, /* int epilogue_begin = 0; */ /* unsigned int isa = 0; */ - if (parse_debug_line_header(&p, &header)) + if (parse_debug_line_header(obj, &p, &header)) return -1; is_stmt = header.default_is_stmt; #define FILL_LINE() \ do { \ fill_line(num_traces, traces, addr, file, line, \ + header.format, \ + header.version, \ header.include_directories, \ header.filenames, \ obj, lines, offset); \ @@ -827,16 +860,23 @@ enum { VAL_cstr = 1, VAL_data = 2, VAL_uint = 3, - VAL_int = 4 + VAL_int = 4, + VAL_addr = 5 }; # define ABBREV_TABLE_SIZE 256 typedef struct { obj_info_t *obj; const char *file; + uint8_t current_version; const char *current_cu; uint64_t current_low_pc; + uint64_t current_str_offsets_base; + uint64_t current_addr_base; + uint64_t current_rnglists_base; const char *debug_line_cu_end; + uint8_t debug_line_format; + uint16_t debug_line_version; const char *debug_line_files; const char *debug_line_directories; const char *p; @@ -861,6 +901,7 @@ typedef struct { const char *ptr; uint64_t uint64; int64_t int64; + uint64_t addr_idx; } as; uint64_t off; uint64_t at; @@ -976,6 +1017,9 @@ debug_info_reader_init(DebugInfoReader *reader, obj_info_t *obj) reader->pend = obj->debug_info.ptr + obj->debug_info.size; reader->debug_line_cu_end = obj->debug_line.ptr; reader->current_low_pc = 0; + reader->current_str_offsets_base = 0; + reader->current_addr_base = 0; + reader->current_rnglists_base = 0; } static void @@ -1020,10 +1064,12 @@ di_read_debug_line_cu(DebugInfoReader *reader) struct LineNumberProgramHeader header; p = (const char *)reader->debug_line_cu_end; - if (parse_debug_line_header(&p, &header)) + if (parse_debug_line_header(reader->obj, &p, &header)) return -1; reader->debug_line_cu_end = (char *)header.cu_end; + reader->debug_line_format = header.format; + reader->debug_line_version = header.version; reader->debug_line_directories = (char *)header.include_directories; reader->debug_line_files = (char *)header.filenames; @@ -1031,6 +1077,13 @@ di_read_debug_line_cu(DebugInfoReader *reader) } static void +set_addr_idx_value(DebugInfoValue *v, uint64_t n) +{ + v->as.addr_idx = n; + v->type = VAL_addr; +} + +static void set_uint_value(DebugInfoValue *v, uint64_t n) { v->as.uint64 = n; @@ -1077,19 +1130,39 @@ get_cstr_value(DebugInfoValue *v) } } +static const char * +resolve_strx(DebugInfoReader *reader, uint64_t idx) +{ + const char *p = reader->obj->debug_str_offsets.ptr + reader->current_str_offsets_base; + uint64_t off; + if (reader->format == 4) { + off = ((uint32_t *)p)[idx]; + } + else { + off = ((uint64_t *)p)[idx]; + } + return reader->obj->debug_str.ptr + off; +} + +static void +debug_info_reader_read_addr_value(DebugInfoReader *reader, DebugInfoValue *v) +{ + if (reader->address_size == 4) { + set_uint_value(v, read_uint32(&reader->p)); + } else if (reader->address_size == 8) { + set_uint_value(v, read_uint64(&reader->p)); + } else { + fprintf(stderr,"unknown address_size:%d", reader->address_size); + abort(); + } +} + static void debug_info_reader_read_value(DebugInfoReader *reader, uint64_t form, DebugInfoValue *v) { switch (form) { case DW_FORM_addr: - if (reader->address_size == 4) { - set_uint_value(v, read_uint32(&reader->p)); - } else if (reader->address_size == 8) { - set_uint_value(v, read_uint64(&reader->p)); - } else { - fprintf(stderr,"unknown address_size:%d", reader->address_size); - abort(); - } + debug_info_reader_read_addr_value(reader, v); break; case DW_FORM_block2: v->size = read_uint16(&reader->p); @@ -1141,13 +1214,19 @@ debug_info_reader_read_value(DebugInfoReader *reader, uint64_t form, DebugInfoVa set_uint_value(v, read_uleb128(reader)); break; case DW_FORM_ref_addr: - if (reader->format == 4) { - set_uint_value(v, read_uint32(&reader->p)); - } else if (reader->format == 8) { - set_uint_value(v, read_uint64(&reader->p)); + if (reader->current_version <= 2) { + // DWARF Version 2 specifies that references have + // the same size as an address on the target system + debug_info_reader_read_addr_value(reader, v); } else { - fprintf(stderr,"unknown format:%d", reader->format); - abort(); + if (reader->format == 4) { + set_uint_value(v, read_uint32(&reader->p)); + } else if (reader->format == 8) { + set_uint_value(v, read_uint64(&reader->p)); + } else { + fprintf(stderr,"unknown format:%d", reader->format); + abort(); + } } break; case DW_FORM_ref1: @@ -1189,11 +1268,10 @@ debug_info_reader_read_value(DebugInfoReader *reader, uint64_t form, DebugInfoVa set_uint_value(v, 1); break; case DW_FORM_strx: - set_uint_value(v, uleb128(&reader->p)); + set_cstr_value(v, resolve_strx(reader, uleb128(&reader->p))); break; case DW_FORM_addrx: - /* TODO: read .debug_addr */ - set_uint_value(v, uleb128(&reader->p)); + set_addr_idx_value(v, uleb128(&reader->p)); break; case DW_FORM_ref_sup4: set_uint_value(v, read_uint32(&reader->p)); @@ -1208,8 +1286,7 @@ debug_info_reader_read_value(DebugInfoReader *reader, uint64_t form, DebugInfoVa reader->p += v->size; break; case DW_FORM_line_strp: - set_uint_value(v, read_uint(reader)); - /* *p = reader->file + reader->line->sh_offset + ret; */ + set_cstrp_value(v, reader->obj->debug_line_str.ptr, read_uint(reader)); break; case DW_FORM_ref_sig8: set_uint_value(v, read_uint64(&reader->p)); @@ -1227,28 +1304,28 @@ debug_info_reader_read_value(DebugInfoReader *reader, uint64_t form, DebugInfoVa set_uint_value(v, read_uint64(&reader->p)); break; case DW_FORM_strx1: - set_uint_value(v, read_uint8(&reader->p)); + set_cstr_value(v, resolve_strx(reader, read_uint8(&reader->p))); break; case DW_FORM_strx2: - set_uint_value(v, read_uint16(&reader->p)); + set_cstr_value(v, resolve_strx(reader, read_uint16(&reader->p))); break; case DW_FORM_strx3: - set_uint_value(v, read_uint24(&reader->p)); + set_cstr_value(v, resolve_strx(reader, read_uint24(&reader->p))); break; case DW_FORM_strx4: - set_uint_value(v, read_uint32(&reader->p)); + set_cstr_value(v, resolve_strx(reader, read_uint32(&reader->p))); break; case DW_FORM_addrx1: - set_uint_value(v, read_uint8(&reader->p)); + set_addr_idx_value(v, read_uint8(&reader->p)); break; case DW_FORM_addrx2: - set_uint_value(v, read_uint16(&reader->p)); + set_addr_idx_value(v, read_uint16(&reader->p)); break; case DW_FORM_addrx3: - set_uint_value(v, read_uint24(&reader->p)); + set_addr_idx_value(v, read_uint24(&reader->p)); break; case DW_FORM_addrx4: - set_uint_value(v, read_uint32(&reader->p)); + set_addr_idx_value(v, read_uint32(&reader->p)); break; case 0: goto fail; @@ -1376,6 +1453,76 @@ di_skip_records(DebugInfoReader *reader) } } +typedef struct addr_header { + const char *ptr; + uint64_t unit_length; + uint8_t format; + uint8_t address_size; + /* uint8_t segment_selector_size; */ +} addr_header_t; + +static void +addr_header_init(obj_info_t *obj, addr_header_t *header) { + const char *p = obj->debug_addr.ptr; + + header->ptr = p; + + if (!p) return; + + header->unit_length = *(uint32_t *)p; + p += sizeof(uint32_t); + + header->format = 4; + if (header->unit_length == 0xffffffff) { + header->unit_length = *(uint64_t *)p; + p += sizeof(uint64_t); + header->format = 8; + } + + p += 2; /* version */ + header->address_size = *p++; + p++; /* segment_selector_size */ +} + +static uint64_t +read_addr(addr_header_t *header, uint64_t addr_base, uint64_t idx) { + if (header->address_size == 4) { + return ((uint32_t*)(header->ptr + addr_base))[idx]; + } + else { + return ((uint64_t*)(header->ptr + addr_base))[idx]; + } +} + +typedef struct rnglists_header { + uint64_t unit_length; + uint8_t format; + uint8_t address_size; + uint32_t offset_entry_count; +} rnglists_header_t; + +static void +rnglists_header_init(obj_info_t *obj, rnglists_header_t *header) { + const char *p = obj->debug_rnglists.ptr; + + if (!p) return; + + header->unit_length = *(uint32_t *)p; + p += sizeof(uint32_t); + + header->format = 4; + if (header->unit_length == 0xffffffff) { + header->unit_length = *(uint64_t *)p; + p += sizeof(uint64_t); + header->format = 8; + } + + p += 2; /* version */ + header->address_size = *p++; + p++; /* segment_selector_size */ + header->offset_entry_count = *(uint32_t *)p; +} + typedef struct { uint64_t low_pc; uint64_t high_pc; @@ -1386,24 +1533,31 @@ typedef struct { } ranges_t; static void -ranges_set(ranges_t *ptr, DebugInfoValue *v) +ranges_set(ranges_t *ptr, DebugInfoValue *v, addr_header_t *addr_header, uint64_t addr_base) { + uint64_t n = 0; + if (v->type == VAL_uint) { + n = v->as.uint64; + } + else if (v->type == VAL_addr) { + n = read_addr(addr_header, addr_base, v->as.addr_idx); + } switch (v->at) { case DW_AT_low_pc: - ptr->low_pc = v->as.uint64; + ptr->low_pc = n; ptr->low_pc_set = true; break; case DW_AT_high_pc: if (v->form == DW_FORM_addr) { - ptr->high_pc = v->as.uint64; + ptr->high_pc = n; } else { - ptr->high_pc = ptr->low_pc + v->as.uint64; + ptr->high_pc = ptr->low_pc + n; } ptr->high_pc_set = true; break; case DW_AT_ranges: - ptr->ranges = v->as.uint64; + ptr->ranges = n; ptr->ranges_set = true; break; } @@ -1425,7 +1579,7 @@ read_dw_form_addr(DebugInfoReader *reader, const char **ptr) } static uintptr_t -ranges_include(DebugInfoReader *reader, ranges_t *ptr, uint64_t addr) +ranges_include(DebugInfoReader *reader, ranges_t *ptr, uint64_t addr, rnglists_header_t *rnglists_header) { if (ptr->high_pc_set) { if (ptr->ranges_set || !ptr->low_pc_set) { @@ -1440,8 +1594,21 @@ ranges_include(DebugInfoReader *reader, ranges_t *ptr, uint64_t addr) const char *p; uint64_t base = ptr->low_pc_set ? ptr->low_pc : reader->current_low_pc; bool base_valid = true; - if (reader->obj->debug_rnglists.ptr) { - p = reader->obj->debug_rnglists.ptr + ptr->ranges; + if (reader->current_version >= 5) { + if (rnglists_header->offset_entry_count == 0) { + // DW_FORM_sec_offset + p = reader->obj->debug_rnglists.ptr + ptr->ranges + reader->current_rnglists_base; + } + else { + // DW_FORM_rnglistx + const char *offset_array = reader->obj->debug_rnglists.ptr + reader->current_rnglists_base; + if (rnglists_header->format == 4) { + p = offset_array + ((uint32_t *)offset_array)[ptr->ranges]; + } + else { + p = offset_array + ((uint64_t *)offset_array)[ptr->ranges]; + } + } for (;;) { uint8_t rle = read_uint8(&p); uintptr_t from = 0, to = 0; @@ -1551,6 +1718,7 @@ di_read_cu(DebugInfoReader *reader) } reader->cu_end = reader->p + unit_length; version = read_uint16(&reader->p); + reader->current_version = version; if (version > 5) { return -1; } @@ -1583,16 +1751,45 @@ di_read_cu(DebugInfoReader *reader) break; } + reader->current_str_offsets_base = 0; + reader->current_addr_base = 0; + reader->current_rnglists_base = 0; + + DebugInfoValue low_pc = {{}}; /* enumerate abbrev */ for (;;) { DebugInfoValue v = {{}}; if (!di_read_record(reader, &v)) break; switch (v.at) { case DW_AT_low_pc: - reader->current_low_pc = v.as.uint64; + // clang may output DW_AT_addr_base after DW_AT_low_pc. + // We need to resolve the DW_FORM_addr* after DW_AT_addr_base is parsed. + low_pc = v; + break; + case DW_AT_str_offsets_base: + reader->current_str_offsets_base = v.as.uint64; + break; + case DW_AT_addr_base: + reader->current_addr_base = v.as.uint64; + break; + case DW_AT_rnglists_base: + reader->current_rnglists_base = v.as.uint64; break; } } + // Resolve the DW_FORM_addr of DW_AT_low_pc + switch (low_pc.type) { + case VAL_uint: + reader->current_low_pc = low_pc.as.uint64; + break; + case VAL_addr: + { + addr_header_t header; + addr_header_init(reader->obj, &header); + reader->current_low_pc = read_addr(&header, reader->current_addr_base, low_pc.as.addr_idx); + } + break; + } } while (0); #endif return 0; @@ -1646,6 +1843,13 @@ read_abstract_origin(DebugInfoReader *reader, uint64_t form, uint64_t abstract_o static void debug_info_read(DebugInfoReader *reader, int num_traces, void **traces, line_info_t *lines, int offset) { + + addr_header_t addr_header = {}; + addr_header_init(reader->obj, &addr_header); + + rnglists_header_t rnglists_header = {}; + rnglists_header_init(reader->obj, &rnglists_header); + while (reader->p < reader->cu_end) { DIE die; ranges_t ranges = {}; @@ -1672,7 +1876,7 @@ debug_info_read(DebugInfoReader *reader, int num_traces, void **traces, line.sname = get_cstr_value(&v); break; case DW_AT_call_file: - fill_filename((int)v.as.uint64, reader->debug_line_directories, reader->debug_line_files, &line, reader->obj); + fill_filename((int)v.as.uint64, reader->debug_line_format, reader->debug_line_version, reader->debug_line_directories, reader->debug_line_files, &line, reader->obj); break; case DW_AT_call_line: line.line = (int)v.as.uint64; @@ -1680,7 +1884,7 @@ debug_info_read(DebugInfoReader *reader, int num_traces, void **traces, case DW_AT_low_pc: case DW_AT_high_pc: case DW_AT_ranges: - ranges_set(&ranges, &v); + ranges_set(&ranges, &v, &addr_header, reader->current_addr_base); break; case DW_AT_declaration: goto skip_die; @@ -1697,9 +1901,9 @@ debug_info_read(DebugInfoReader *reader, int num_traces, void **traces, for (int i=offset; i < num_traces; i++) { uintptr_t addr = (uintptr_t)traces[i]; uintptr_t offset = addr - reader->obj->base_addr + reader->obj->vmaddr; - uintptr_t saddr = ranges_include(reader, &ranges, offset); + uintptr_t saddr = ranges_include(reader, &ranges, offset, &rnglists_header); if (saddr) { - /* fprintf(stderr, "%d:%tx: %d %lx->%lx %x %s: %s/%s %d %s %s %s\n",__LINE__,die.pos, i,addr,offset, die.tag,line.sname,line.dirname,line.filename,line.line,reader->obj->path,line.sname,lines[i].sname); */ + /* fprintf(stdout, "%d:%tx: %d %lx->%lx %x %s: %s/%s %d %s %s %s\n",__LINE__,die.pos, i,addr,offset, die.tag,line.sname,line.dirname,line.filename,line.line,reader->obj->path,line.sname,lines[i].sname); */ if (lines[i].sname) { line_info_t *lp = malloc(sizeof(line_info_t)); memcpy(lp, &lines[i], sizeof(line_info_t)); @@ -1718,6 +1922,54 @@ debug_info_read(DebugInfoReader *reader, int num_traces, void **traces, } } +// This function parses the following attributes of Line Number Program Header in DWARF 5: +// +// * directory_entry_format_count +// * directory_entry_format +// * directories_count +// * directories +// +// or +// +// * file_name_entry_format_count +// * file_name_entry_format +// * file_names_count +// * file_names +// +// It records DW_LNCT_path and DW_LNCT_directory_index at the index "idx". +static const char * +parse_ver5_debug_line_header(const char *p, int idx, uint8_t format, obj_info_t *obj, const char **out_path, uint64_t *out_directory_index) { + int i, j; + int entry_format_count = *(uint8_t *)p++; + const char *entry_format = p; + + /* skip the part of entry_format */ + for (i = 0; i < entry_format_count * 2; i++) uleb128(&p); + + int entry_count = (int)uleb128(&p); + + DebugInfoReader reader; + debug_info_reader_init(&reader, obj); + reader.format = format; + reader.p = p; + for (j = 0; j < entry_count; j++) { + const char *format = entry_format; + for (i = 0; i < entry_format_count; i++) { + DebugInfoValue v = {{}}; + unsigned long dw_lnct = uleb128(&format); + unsigned long dw_form = uleb128(&format); + debug_info_reader_read_value(&reader, dw_form, &v); + if (dw_lnct == 1 /* DW_LNCT_path */ && v.type == VAL_cstr && out_path) + *out_path = v.as.ptr + v.off; + if (dw_lnct == 2 /* DW_LNCT_directory_index */ && v.type == VAL_uint && out_directory_index) + *out_directory_index = v.as.uint64; + } + if (j == idx) return 0; + } + + return reader.p; +} + #ifdef USE_ELF static unsigned long uncompress_debug_section(ElfW(Shdr) *shdr, char *file, char **ptr) @@ -1846,8 +2098,11 @@ fill_lines(int num_traces, void **traces, int check_debuglink, ".debug_info", ".debug_line", ".debug_ranges", + ".debug_str_offsets", + ".debug_addr", ".debug_rnglists", - ".debug_str" + ".debug_str", + ".debug_line_str" }; for (j=0; j < DWARF_SECTION_COUNT; j++) { @@ -2103,8 +2358,11 @@ found_mach_header: "__debug_info", "__debug_line", "__debug_ranges", + "__debug_str_offsets", + "__debug_addr", "__debug_rnglists", - "__debug_str" + "__debug_str", + "__debug_line_str", }; struct LP(segment_command) *scmd = (struct LP(segment_command) *)lcmd; if (strcmp(scmd->segname, "__TEXT") == 0) { @@ -2302,6 +2560,7 @@ rb_dump_backtrace_with_lines(int num_traces, void **traces) obj_info_t *obj = NULL; /* 2 is NULL + main executable */ void **dladdr_fbases = (void **)calloc(num_traces+2, sizeof(void *)); + #ifdef HAVE_MAIN_EXE_PATH char *main_path = NULL; /* used on printing backtrace */ ssize_t len; @@ -226,8 +226,15 @@ ary_embeddable_p(long capa) bool rb_ary_embeddable_p(VALUE ary) { - // if the array is shared or a shared root then it's not moveable - return !(ARY_SHARED_P(ary) || ARY_SHARED_ROOT_P(ary)); + /* An array cannot be turned embeddable when the array is: + * - Shared root: other objects may point to the buffer of this array + * so we cannot make it embedded. + * - Frozen: this array may also be a shared root without the shared root + * flag. + * - Shared: we don't want to re-embed an array that points to a shared + * root (to save memory). + */ + return !(ARY_SHARED_ROOT_P(ary) || OBJ_FROZEN(ary) || ARY_SHARED_P(ary)); } size_t @@ -242,7 +249,7 @@ rb_ary_size_as_embedded(VALUE ary) real_size = ary_embed_size(ARY_HEAP_CAPA(ary)); } else { - real_size = sizeof(struct RString); + real_size = sizeof(struct RArray); } return real_size; } @@ -3729,8 +3736,8 @@ rb_ary_bsearch_index(VALUE ary) const VALUE zero = INT2FIX(0); switch (rb_cmpint(rb_funcallv(v, id_cmp, 1, &zero), v, zero)) { case 0: return INT2FIX(mid); - case 1: smaller = 1; break; - case -1: smaller = 0; + case 1: smaller = 0; break; + case -1: smaller = 1; } } else { @@ -8732,7 +8739,7 @@ rb_ary_deconstruct(VALUE ary) * * - #pop: Removes and returns the last element. * - #shift: Removes and returns the first element. - * - #compact!: Removes all non-+nil+ elements. + * - #compact!: Removes all +nil+ elements. * - #delete: Removes elements equal to a given object. * - #delete_at: Removes the element at a given offset. * - #delete_if: Removes elements specified by a given block. @@ -202,6 +202,11 @@ static VALUE node_id_for_backtrace_location(rb_execution_context_t *ec, VALUE module, VALUE location) { int node_id; + + if (!rb_frame_info_p(location)) { + rb_raise(rb_eTypeError, "Thread::Backtrace::Location object expected"); + } + node_id = rb_get_node_id_from_frame_info(location); if (node_id == -1) { return Qnil; @@ -20,21 +20,47 @@ module RubyVM::AbstractSyntaxTree # call-seq: - # RubyVM::AbstractSyntaxTree.parse(string) -> RubyVM::AbstractSyntaxTree::Node + # RubyVM::AbstractSyntaxTree.parse(string, keep_script_lines: false, error_tolerant: false, keep_tokens: false) -> RubyVM::AbstractSyntaxTree::Node # # Parses the given _string_ into an abstract syntax tree, # returning the root node of that tree. # - # SyntaxError is raised if the given _string_ is invalid syntax. - # # RubyVM::AbstractSyntaxTree.parse("x = 1 + 2") # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-1:9> + # + # If <tt>keep_script_lines: true</tt> option is provided, the text of the parsed + # source is associated with nodes and is available via Node#script_lines. + # + # If <tt>keep_tokens: true</tt> option is provided, Node#tokens are populated. + # + # SyntaxError is raised if the given _string_ is invalid syntax. To overwrite this + # behavior, <tt>error_tolerant: true</tt> can be provided. In this case, the parser + # will produce a tree where expressions with syntax errors would be represented by + # Node with <tt>type=:ERROR</tt>. + # + # root = RubyVM::AbstractSyntaxTree.parse("x = 1; p(x; y=2") + # # <internal:ast>:33:in `parse': syntax error, unexpected ';', expecting ')' (SyntaxError) + # # x = 1; p(x; y=2 + # # ^ + # + # root = RubyVM::AbstractSyntaxTree.parse("x = 1; p(x; y=2", error_tolerant: true) + # # (SCOPE@1:0-1:15 + # # tbl: [:x, :y] + # # args: nil + # # body: (BLOCK@1:0-1:15 (LASGN@1:0-1:5 :x (LIT@1:4-1:5 1)) (ERROR@1:7-1:11) (LASGN@1:12-1:15 :y (LIT@1:14-1:15 2)))) + # root.children.last.children + # # [(LASGN@1:0-1:5 :x (LIT@1:4-1:5 1)), + # # (ERROR@1:7-1:11), + # # (LASGN@1:12-1:15 :y (LIT@1:14-1:15 2))] + # + # Note that parsing continues even after the errored expresion. + # def self.parse string, keep_script_lines: false, error_tolerant: false, keep_tokens: false Primitive.ast_s_parse string, keep_script_lines, error_tolerant, keep_tokens end # call-seq: - # RubyVM::AbstractSyntaxTree.parse_file(pathname) -> RubyVM::AbstractSyntaxTree::Node + # RubyVM::AbstractSyntaxTree.parse_file(pathname, keep_script_lines: false, error_tolerant: false, keep_tokens: false) -> RubyVM::AbstractSyntaxTree::Node # # Reads the file from _pathname_, then parses it like ::parse, # returning the root node of the abstract syntax tree. @@ -44,13 +70,15 @@ module RubyVM::AbstractSyntaxTree # # RubyVM::AbstractSyntaxTree.parse_file("my-app/app.rb") # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-31:3> + # + # See ::parse for explanation of keyword argument meaning and usage. def self.parse_file pathname, keep_script_lines: false, error_tolerant: false, keep_tokens: false Primitive.ast_s_parse_file pathname, keep_script_lines, error_tolerant, keep_tokens end # call-seq: - # RubyVM::AbstractSyntaxTree.of(proc) -> RubyVM::AbstractSyntaxTree::Node - # RubyVM::AbstractSyntaxTree.of(method) -> RubyVM::AbstractSyntaxTree::Node + # RubyVM::AbstractSyntaxTree.of(proc, keep_script_lines: false, error_tolerant: false, keep_tokens: false) -> RubyVM::AbstractSyntaxTree::Node + # RubyVM::AbstractSyntaxTree.of(method, keep_script_lines: false, error_tolerant: false, keep_tokens: false) -> RubyVM::AbstractSyntaxTree::Node # # Returns AST nodes of the given _proc_ or _method_. # @@ -63,6 +91,8 @@ module RubyVM::AbstractSyntaxTree # # RubyVM::AbstractSyntaxTree.of(method(:hello)) # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-3:3> + # + # See ::parse for explanation of keyword argument meaning and usage. def self.of body, keep_script_lines: false, error_tolerant: false, keep_tokens: false Primitive.ast_s_of body, keep_script_lines, error_tolerant, keep_tokens end diff --git a/benchmark/time_parse.yml b/benchmark/time_parse.yml index a6d6948b9c..6060b58bc6 100644 --- a/benchmark/time_parse.yml +++ b/benchmark/time_parse.yml @@ -6,3 +6,5 @@ benchmark: - Time.iso8601(iso8601) - Time.parse(iso8601) - Time.parse(inspect) + - Time.new(iso8601) rescue Time.iso8601(iso8601) + - Time.new(inspect) rescue Time.parse(inspect) @@ -4184,7 +4184,6 @@ rb_int_parse_cstr(const char *str, ssize_t len, char **endp, size_t *ndigits, } if (!c || ISSPACE(c)) --str; if (end) len = end - str; - ASSERT_LEN(); } c = *str; c = conv_digit(c); @@ -4587,11 +4586,14 @@ big_shift3(VALUE x, int lshift_p, size_t shift_numdigits, int shift_numbits) if (lshift_p) { if (LONG_MAX < shift_numdigits) { - rb_raise(rb_eArgError, "too big number"); + too_big: + rb_raise(rb_eRangeError, "shift width too big"); } s1 = shift_numdigits; s2 = shift_numbits; + if ((size_t)s1 != shift_numdigits) goto too_big; xn = BIGNUM_LEN(x); + if (LONG_MAX/SIZEOF_BDIGIT <= xn+s1) goto too_big; z = bignew(xn+s1+1, BIGNUM_SIGN(x)); zds = BDIGITS(z); BDIGITS_ZERO(zds, s1); diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 70bbaf5434..67e66b03ee 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -480,7 +480,6 @@ assert_equal 'ok', %q{ } # multiple Ractors can receive (wait) from one Ractor -yjit_enabled = ENV.key?('RUBY_YJIT_ENABLE') || ENV.fetch('RUN_OPTS', '').include?('yjit') assert_equal '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]', %q{ pipe = Ractor.new do loop do @@ -503,7 +502,7 @@ assert_equal '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]', %q{ rs.delete r n }.sort -} unless yjit_enabled # flaky with YJIT https://github.com/ruby/ruby/actions/runs/3603398545/jobs/6071549328#step:18:33 +} 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{ @@ -1474,7 +1473,7 @@ assert_equal "#{N}#{N}", %Q{ } # enc_table -assert_equal "#{N/10}", %Q{ +assert_equal "100", %Q{ Ractor.new do loop do Encoding.find("test-enc-#{rand(5_000)}").inspect @@ -1483,7 +1482,7 @@ assert_equal "#{N/10}", %Q{ end src = Encoding.find("UTF-8") - #{N/10}.times{|i| + 100.times{|i| src.replicate("test-enc-\#{i}") } } @@ -1543,7 +1542,7 @@ assert_equal "ok", %q{ 1_000.times { idle_worker, tmp_reporter = Ractor.select(*workers) } "ok" -} unless yjit_enabled # flaky with YJIT https://github.com/ruby/ruby/actions/runs/3575374374/jobs/6011846425 +} assert_equal "ok", %q{ def foo(*); ->{ super }; end diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 349417595f..5c655b8f25 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -1,3 +1,33 @@ +# Regression test for yielding with autosplat to block with +# optional parameters. https://github.com/Shopify/yjit/issues/313 +assert_equal '[:a, :b, :a, :b]', %q{ + def yielder(arg) = yield(arg) + yield(arg) + + yielder([:a, :b]) do |c = :c, d = :d| + [c, d] + end +} + +# Regression test for GC mishap while doing shape transition +assert_equal '[:ok]', %q{ + # [Bug #19601] + class RegressionTest + def initialize + @a = @b = @fourth_ivar_does_shape_transition = nil + end + + def extender + @first_extended_ivar = [:ok] + end + end + + GC.stress = true + + # Used to crash due to GC run in rb_ensure_iv_list_size() + # not marking the newly allocated [:ok]. + RegressionTest.new.extender.itself +} + assert_equal 'true', %q{ # regression test for tracking type of locals for too long def local_setting_cmp(five) @@ -221,6 +251,8 @@ assert_equal 'string', %q{ # Check that exceptions work when getting global variable assert_equal 'rescued', %q{ + Warning[:deprecated] = true + module Warning def warn(message) raise @@ -1141,6 +1173,38 @@ assert_equal '42', %q{ run } +# splatting an empty array on a specialized method +assert_equal 'ok', %q{ + def run + "ok".to_s(*[]) + end + + run + run +} + +# splatting an single element array on a specialized method +assert_equal '[1]', %q{ + def run + [].<<(*[1]) + end + + run + run +} + +# specialized method with wrong args +assert_equal 'ok', %q{ + def run(x) + "bad".to_s(123) if x + rescue + :ok + end + + run(false) + run(true) +} + # getinstancevariable on Symbol assert_equal '[nil, nil]', %q{ # @foo to exercise the getinstancevariable instruction @@ -2173,7 +2237,7 @@ assert_equal '[[:c_return, :String, :string_alias, "events_to_str"]]', %q{ events.compiled(events) events -} +} unless defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # MJIT calls extra Ruby methods # test enabling a TracePoint that targets a particular line in a C method call assert_equal '[true]', %q{ @@ -2255,7 +2319,7 @@ assert_equal '[[:c_call, :itself]]', %q{ tp.enable { shouldnt_compile } events -} +} unless defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # MJIT calls extra Ruby methods # test enabling c_return tracing before compiling assert_equal '[[:c_return, :itself, main]]', %q{ @@ -2270,7 +2334,7 @@ assert_equal '[[:c_return, :itself, main]]', %q{ tp.enable { shouldnt_compile } events -} +} unless defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # MJIT calls extra Ruby methods # test c_call invalidation assert_equal '[[:c_call, :itself]]', %q{ @@ -3456,3 +3520,11 @@ assert_equal 'ok', %q{ cw(4) } + +assert_normal_exit %{ + class Bug20997 + def foo(&) = self.class.name(&) + + new.foo + end +} @@ -404,6 +404,30 @@ class_init_copy_check(VALUE clone, VALUE orig) } } +struct cvc_table_copy_ctx { + VALUE clone; + struct rb_id_table * new_table; +}; + +static enum rb_id_table_iterator_result +cvc_table_copy(ID id, VALUE val, void *data) { + struct cvc_table_copy_ctx *ctx = (struct cvc_table_copy_ctx *)data; + struct rb_cvar_class_tbl_entry * orig_entry; + orig_entry = (struct rb_cvar_class_tbl_entry *)val; + + struct rb_cvar_class_tbl_entry *ent; + + ent = ALLOC(struct rb_cvar_class_tbl_entry); + ent->class_value = ctx->clone; + ent->cref = orig_entry->cref; + ent->global_cvar_state = orig_entry->global_cvar_state; + rb_id_table_insert(ctx->new_table, id, (VALUE)ent); + + RB_OBJ_WRITTEN(ctx->clone, Qundef, ent->cref); + + return ID_TABLE_CONTINUE; +} + static void copy_tables(VALUE clone, VALUE orig) { @@ -411,6 +435,17 @@ copy_tables(VALUE clone, VALUE orig) rb_free_const_table(RCLASS_CONST_TBL(clone)); RCLASS_CONST_TBL(clone) = 0; } + if (RCLASS_CVC_TBL(orig)) { + struct rb_id_table *rb_cvc_tbl = RCLASS_CVC_TBL(orig); + struct rb_id_table *rb_cvc_tbl_dup = rb_id_table_create(rb_id_table_size(rb_cvc_tbl)); + + struct cvc_table_copy_ctx ctx; + ctx.clone = clone; + ctx.new_table = rb_cvc_tbl_dup; + rb_id_table_foreach(rb_cvc_tbl, cvc_table_copy, &ctx); + RCLASS_CVC_TBL(clone) = rb_cvc_tbl_dup; + } + rb_id_table_free(RCLASS_M_TBL(clone)); RCLASS_M_TBL(clone) = 0; if (!RB_TYPE_P(clone, T_ICLASS)) { st_data_t id; @@ -923,7 +958,7 @@ rb_define_class_under(VALUE outer, const char *name, VALUE super) } VALUE -rb_define_class_id_under(VALUE outer, ID id, VALUE super) +rb_define_class_id_under_no_pin(VALUE outer, ID id, VALUE super) { VALUE klass; @@ -940,8 +975,6 @@ rb_define_class_id_under(VALUE outer, ID id, VALUE super) " (%"PRIsVALUE" is given but was %"PRIsVALUE")", outer, rb_id2str(id), RCLASS_SUPER(klass), super); } - /* Class may have been defined in Ruby and not pin-rooted */ - rb_vm_add_root_module(klass); return klass; } @@ -953,12 +986,19 @@ rb_define_class_id_under(VALUE outer, ID id, VALUE super) rb_set_class_path_string(klass, outer, rb_id2str(id)); rb_const_set(outer, id, klass); rb_class_inherited(super, klass); - rb_vm_add_root_module(klass); return klass; } VALUE +rb_define_class_id_under(VALUE outer, ID id, VALUE super) +{ + VALUE klass = rb_define_class_id_under_no_pin(outer, id, super); + rb_vm_add_root_module(klass); + return klass; +} + +VALUE rb_module_s_alloc(VALUE klass) { VALUE mod = class_alloc(T_MODULE, klass); @@ -1105,8 +1145,8 @@ rb_include_module(VALUE klass, VALUE module) iclass = iclass->next; } - int do_include = 1; while (iclass) { + int do_include = 1; VALUE check_class = iclass->klass; /* During lazy sweeping, iclass->klass could be a dead object that * has not yet been swept. */ @@ -1173,7 +1213,7 @@ static int do_include_modules_at(const VALUE klass, VALUE c, VALUE module, int search_super, bool check_cyclic) { VALUE p, iclass, origin_stack = 0; - int method_changed = 0, add_subclass; + int method_changed = 0; long origin_len; VALUE klass_origin = RCLASS_ORIGIN(klass); VALUE original_klass = klass; @@ -1237,7 +1277,6 @@ do_include_modules_at(const VALUE klass, VALUE c, VALUE module, int search_super iclass = rb_include_class_new(module, super_class); c = RCLASS_SET_SUPER(c, iclass); RCLASS_SET_INCLUDER(iclass, klass); - add_subclass = TRUE; if (module != RCLASS_ORIGIN(module)) { if (!origin_stack) origin_stack = rb_ary_hidden_new(2); VALUE origin[2] = {iclass, RCLASS_ORIGIN(module)}; @@ -1248,14 +1287,11 @@ do_include_modules_at(const VALUE klass, VALUE c, VALUE module, int search_super RCLASS_SET_ORIGIN(RARRAY_AREF(origin_stack, (origin_len -= 2)), iclass); RICLASS_SET_ORIGIN_SHARED_MTBL(iclass); rb_ary_resize(origin_stack, origin_len); - add_subclass = FALSE; } - if (add_subclass) { - VALUE m = module; - if (BUILTIN_TYPE(m) == T_ICLASS) m = METACLASS_OF(m); - rb_module_add_to_subclasses_list(m, iclass); - } + VALUE m = module; + if (BUILTIN_TYPE(m) == T_ICLASS) m = METACLASS_OF(m); + rb_module_add_to_subclasses_list(m, iclass); if (BUILTIN_TYPE(klass) == T_MODULE && FL_TEST(klass, RMODULE_IS_REFINEMENT)) { VALUE refined_class = @@ -1572,6 +1608,27 @@ class_descendants(VALUE klass, bool immediate_only) * A.subclasses #=> [D, B] * B.subclasses #=> [C] * C.subclasses #=> [] + * + * Anonymous subclasses (not associated with a constant) are + * returned, too: + * + * c = Class.new(A) + * A.subclasses # => [#<Class:0x00007f003c77bd78>, D, B] + * + * Note that the parent does not hold references to subclasses + * and doesn't prevent them from being garbage collected. This + * means that the subclass might disappear when all references + * to it are dropped: + * + * # drop the reference to subclass, it can be garbage-collected now + * c = nil + * + * A.subclasses + * # It can be + * # => [#<Class:0x00007f003c77bd78>, D, B] + * # ...or just + * # => [D, B] + * # ...depending on whether garbage collector was run */ VALUE @@ -43,7 +43,7 @@ RUN_OPTS = --disable-gems # GITPULLOPTIONS = --no-tags -INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir) -I$(srcdir) -I$(UNICODE_HDR_DIR) +INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir) -I$(srcdir) -I$(UNICODE_HDR_DIR) $(incflags) GEM_HOME = GEM_PATH = @@ -224,6 +224,7 @@ MAKE_LINK = $(MINIRUBY) -rfileutils -e "include FileUtils::Verbose" \ YJIT_RUSTC_ARGS = --crate-name=yjit \ --crate-type=staticlib \ --edition=2021 \ + -g \ -C opt-level=3 \ -C overflow-checks=on \ '--out-dir=$(CARGO_TARGET_DIR)/release/' \ @@ -234,9 +235,9 @@ all: $(SHOWFLAGS) main docs main: $(SHOWFLAGS) exts $(ENCSTATIC:static=lib)encs @$(NULLCMD) -main: $(srcdir)/lib/mjit/instruction.rb -srcs: $(srcdir)/lib/mjit/instruction.rb -$(srcdir)/lib/mjit/instruction.rb: $(tooldir)/insns2vm.rb $(tooldir)/ruby_vm/views/lib/mjit/instruction.rb.erb $(srcdir)/insns.def +main: $(srcdir)/lib/ruby_vm/mjit/instruction.rb +srcs: $(srcdir)/lib/ruby_vm/mjit/instruction.rb +$(srcdir)/lib/ruby_vm/mjit/instruction.rb: $(tooldir)/insns2vm.rb $(tooldir)/ruby_vm/views/lib/ruby_vm/mjit/instruction.rb.erb $(srcdir)/insns.def $(ECHO) generating $@ $(Q) $(BASERUBY) -Ku $(tooldir)/insns2vm.rb --basedir="$(srcdir)" $(INSNS2VMOPT) $@ @@ -639,7 +640,7 @@ 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)$(RMALL) yjit/target - -$(Q) $(RMDIR) enc/jis enc/trans enc $(COROUTINE_H:/Context.h=) coroutine 2> $(NULL) || $(NULLCMD) + -$(Q) $(RMDIR) enc/jis enc/trans enc $(COROUTINE_H:/Context.h=) coroutine yjit 2> $(NULL) || $(NULLCMD) bin/clean-runnable:: PHONY $(Q)$(CHDIR) bin 2>$(NULL) && $(RM) $(PROGRAM) $(WPROGRAM) $(GORUBY)$(EXEEXT) bin/*.$(DLEXT) 2>$(NULL) || $(NULLCMD) @@ -765,7 +766,7 @@ clean-spec: PHONY -$(Q) $(RMDIRS) $(RUBYSPEC_CAPIEXT) 2> $(NULL) || $(NULLCMD) -$(Q) $(RMALL) rubyspec_temp -check: main $(DOT_WAIT) test $(DOT_WAIT) test-tool $(DOT_WAIT) test-all $(DOT_WAIT) test-spec +check: main $(DOT_WAIT) test $(DOT_WAIT) test-tool $(DOT_WAIT) test-all $(ECHO) check succeeded -$(Q) : : "run only on sh"; \ if [ x"$(GIT)" != x ] && $(CHDIR) "$(srcdir)" && \ @@ -789,10 +790,11 @@ $(arch:noarch=ignore)-fake.rb: $(srcdir)/template/fake.rb.in $(tooldir)/generic_ $(ECHO) generating $@ $(Q) $(CPP) -DRUBY_EXPORT $(INCFLAGS) $(CPPFLAGS) "$(srcdir)/version.c" | \ $(BOOTSTRAPRUBY) "$(tooldir)/generic_erb.rb" -o $@ "$(srcdir)/template/fake.rb.in" \ - i=- srcdir="$(srcdir)" BASERUBY="$(BASERUBY)" + i=- srcdir="$(srcdir)" BASERUBY="$(BASERUBY)" \ + LIBPATHENV="$(LIBPATHENV)" PRELOADENV="$(PRELOADENV)" LIBRUBY_SO="$(LIBRUBY_SO)" noarch-fake.rb: # prerequisite of yes-fake - touch $@ + $(Q) exit > $@ btest: $(TEST_RUNNABLE)-btest no-btest: PHONY @@ -901,6 +903,8 @@ yes-test-spec: test-spec-precheck $(ACTIONS_ENDGROUP) no-test-spec: +check: $(DOT_WAIT) test-spec + RUNNABLE = $(LIBRUBY_RELATIVE:no=un)-runnable runnable: $(RUNNABLE) prog $(tooldir)/mkrunnable.rb PHONY $(Q) $(MINIRUBY) $(tooldir)/mkrunnable.rb -v $(EXTOUT) @@ -1220,7 +1224,7 @@ $(srcdir)/revision.h$(no_baseruby:no=~disabled~): $(REVISION_H) $(REVISION_H)$(no_baseruby:no=~disabled~): $(Q) $(BASERUBY) $(tooldir)/file2lastrev.rb -q --revision.h --srcdir="$(srcdir)" --output=revision.h --timestamp=$@ $(REVISION_H)$(yes_baseruby:yes=~disabled~): - $(Q) touch $@ + $(Q) exit > $@ $(srcdir)/ext/ripper/ripper.c: $(srcdir)/ext/ripper/tools/preproc.rb $(srcdir)/parse.y $(srcdir)/defs/id.def $(srcdir)/ext/ripper/depend $(ECHO) generating $@ @@ -1356,7 +1360,7 @@ after-update:: extract-gems update-src:: $(Q) $(RM) $(REVISION_H) revision.h "$(srcdir)/$(REVISION_H)" "$(srcdir)/revision.h" - $(Q) touch "$(srcdir)/revision.h" + $(Q) exit > "$(srcdir)/revision.h" update-remote:: update-src update-download update-download:: $(ALWAYS_UPDATE_UNICODE:yes=update-unicode) @@ -1372,7 +1376,6 @@ update-config_files: PHONY refresh-gems: update-bundled_gems prepare-gems prepare-gems: $(HAVE_BASERUBY:yes=update-gems) $(HAVE_BASERUBY:yes=extract-gems) -prepare-gems: $(DOT_WAIT) $(HAVE_BASERUBY:yes=outdate-bundled-gems) extract-gems: $(HAVE_BASERUBY:yes=update-gems) update-gems$(gnumake:yes=-sequential): PHONY @@ -1434,7 +1437,7 @@ no-test-bundled-gems-prepare: no-test-bundled-gems-precheck yes-test-bundled-gems-prepare: yes-test-bundled-gems-precheck $(ACTIONS_GROUP) $(XRUBY) -C "$(srcdir)" bin/gem install --no-document \ - --install-dir .bundle --conservative "bundler" "minitest:~> 5" "test-unit" "rake" "hoe" "yard" "pry" "packnga" "rexml" "json-schema" "test-unit-rr" + --install-dir .bundle --conservative "bundler" "minitest:~> 5" "test-unit" "rake" "hoe" "rexml" "json-schema:5.1.0" "test-unit-rr" $(ACTIONS_ENDGROUP) PREPARE_BUNDLED_GEMS = test-bundled-gems-prepare @@ -1453,6 +1456,7 @@ test-syntax-suggest-precheck: $(TEST_RUNNABLE)-test-syntax-suggest-precheck no-test-syntax-suggest-precheck: yes-test-syntax-suggest-precheck: main +test-syntax-suggest-prepare: $(TEST_RUNNABLE)-test-syntax-suggest-prepare no-test-syntax-suggest-prepare: no-test-syntax-suggest-precheck yes-test-syntax-suggest-prepare: yes-test-syntax-suggest-precheck $(ACTIONS_GROUP) @@ -1462,12 +1466,15 @@ yes-test-syntax-suggest-prepare: yes-test-syntax-suggest-precheck RSPECOPTS = SYNTAX_SUGGEST_SPECS = +PREPARE_SYNTAX_SUGGEST = test-syntax-suggest-prepare test-syntax-suggest: $(TEST_RUNNABLE)-test-syntax-suggest -yes-test-syntax-suggest: yes-test-syntax-suggest-prepare +yes-test-syntax-suggest: yes-$(PREPARE_SYNTAX_SUGGEST) $(XRUBY) -C $(srcdir) -Ispec/syntax_suggest .bundle/bin/rspec \ --require spec_helper $(RSPECOPTS) spec/syntax_suggest/$(SYNTAX_SUGGEST_SPECS) no-test-syntax-suggest: +check: $(DOT_WAIT) $(TEST_RUNNABLE)-$(PREPARE_SYNTAX_SUGGEST) test-syntax-suggest + test-bundler-precheck: $(TEST_RUNNABLE)-test-bundler-precheck no-test-bundler-precheck: yes-test-bundler-precheck: main $(arch)-fake.rb @@ -1613,7 +1620,7 @@ $(UNICODE_SRC_DATA_DIR)/$(ALWAYS_UPDATE_UNICODE:yes=.unicode-tables.time): \ touch-unicode-files: $(MAKEDIRS) $(UNICODE_SRC_DATA_DIR) - touch $(UNICODE_SRC_DATA_DIR)/.unicode-tables.time $(UNICODE_DATA_HEADERS) + $(Q) $(TOUCH) $(UNICODE_SRC_DATA_DIR)/.unicode-tables.time $(UNICODE_DATA_HEADERS) UNICODE_TABLES_DATA_FILES = \ $(UNICODE_SRC_DATA_DIR)/UnicodeData.txt \ @@ -1625,7 +1632,7 @@ UNICODE_TABLES_DEPENDENTS = $(UNICODE_TABLES_DEPENDENTS_1:noneyes=force) UNICODE_TABLES_TIMESTAMP = yes $(UNICODE_SRC_DATA_DIR)/.unicode-tables.$(UNICODE_TABLES_DEPENDENTS:none=time): $(Q) $(MAKEDIRS) $(@D) - touch $(@) || $(NULLCMD) + $(Q) exit > $(@) || $(NULLCMD) $(UNICODE_SRC_DATA_DIR)/.unicode-tables.$(UNICODE_TABLES_DEPENDENTS:force=time): \ $(tooldir)/generic_erb.rb \ $(srcdir)/template/unicode_norm_gen.tmpl \ @@ -3214,8 +3221,13 @@ compile.$(OBJEXT): {$(VPATH)}vm_callinfo.h compile.$(OBJEXT): {$(VPATH)}vm_core.h compile.$(OBJEXT): {$(VPATH)}vm_debug.h compile.$(OBJEXT): {$(VPATH)}vm_opts.h +complex.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h +complex.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h +complex.$(OBJEXT): $(CCAN_DIR)/list/list.h +complex.$(OBJEXT): $(CCAN_DIR)/str/str.h complex.$(OBJEXT): $(hdrdir)/ruby/ruby.h complex.$(OBJEXT): $(top_srcdir)/internal/array.h +complex.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h complex.$(OBJEXT): $(top_srcdir)/internal/bignum.h complex.$(OBJEXT): $(top_srcdir)/internal/bits.h complex.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -3223,6 +3235,7 @@ complex.$(OBJEXT): $(top_srcdir)/internal/compilers.h complex.$(OBJEXT): $(top_srcdir)/internal/complex.h complex.$(OBJEXT): $(top_srcdir)/internal/fixnum.h complex.$(OBJEXT): $(top_srcdir)/internal/gc.h +complex.$(OBJEXT): $(top_srcdir)/internal/imemo.h complex.$(OBJEXT): $(top_srcdir)/internal/math.h complex.$(OBJEXT): $(top_srcdir)/internal/numeric.h complex.$(OBJEXT): $(top_srcdir)/internal/object.h @@ -3233,6 +3246,7 @@ complex.$(OBJEXT): $(top_srcdir)/internal/variable.h complex.$(OBJEXT): $(top_srcdir)/internal/vm.h complex.$(OBJEXT): $(top_srcdir)/internal/warnings.h complex.$(OBJEXT): {$(VPATH)}assert.h +complex.$(OBJEXT): {$(VPATH)}atomic.h complex.$(OBJEXT): {$(VPATH)}backward/2/assume.h complex.$(OBJEXT): {$(VPATH)}backward/2/attributes.h complex.$(OBJEXT): {$(VPATH)}backward/2/bool.h @@ -3390,11 +3404,18 @@ complex.$(OBJEXT): {$(VPATH)}internal/value_type.h complex.$(OBJEXT): {$(VPATH)}internal/variable.h complex.$(OBJEXT): {$(VPATH)}internal/warning_push.h complex.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +complex.$(OBJEXT): {$(VPATH)}method.h complex.$(OBJEXT): {$(VPATH)}missing.h +complex.$(OBJEXT): {$(VPATH)}node.h complex.$(OBJEXT): {$(VPATH)}ruby_assert.h +complex.$(OBJEXT): {$(VPATH)}ruby_atomic.h complex.$(OBJEXT): {$(VPATH)}shape.h complex.$(OBJEXT): {$(VPATH)}st.h complex.$(OBJEXT): {$(VPATH)}subst.h +complex.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h +complex.$(OBJEXT): {$(VPATH)}thread_native.h +complex.$(OBJEXT): {$(VPATH)}vm_core.h +complex.$(OBJEXT): {$(VPATH)}vm_opts.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 @@ -3405,12 +3426,14 @@ cont.$(OBJEXT): $(top_srcdir)/internal/array.h cont.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h cont.$(OBJEXT): $(top_srcdir)/internal/compilers.h cont.$(OBJEXT): $(top_srcdir)/internal/cont.h +cont.$(OBJEXT): $(top_srcdir)/internal/error.h cont.$(OBJEXT): $(top_srcdir)/internal/gc.h cont.$(OBJEXT): $(top_srcdir)/internal/imemo.h cont.$(OBJEXT): $(top_srcdir)/internal/proc.h cont.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h cont.$(OBJEXT): $(top_srcdir)/internal/serial.h cont.$(OBJEXT): $(top_srcdir)/internal/static_assert.h +cont.$(OBJEXT): $(top_srcdir)/internal/string.h cont.$(OBJEXT): $(top_srcdir)/internal/variable.h cont.$(OBJEXT): $(top_srcdir)/internal/vm.h cont.$(OBJEXT): $(top_srcdir)/internal/warnings.h @@ -3431,6 +3454,7 @@ cont.$(OBJEXT): {$(VPATH)}constant.h cont.$(OBJEXT): {$(VPATH)}cont.c cont.$(OBJEXT): {$(VPATH)}debug_counter.h cont.$(OBJEXT): {$(VPATH)}defines.h +cont.$(OBJEXT): {$(VPATH)}encoding.h cont.$(OBJEXT): {$(VPATH)}eval_intern.h cont.$(OBJEXT): {$(VPATH)}fiber/scheduler.h cont.$(OBJEXT): {$(VPATH)}gc.h @@ -3508,6 +3532,15 @@ cont.$(OBJEXT): {$(VPATH)}internal/core/rtypeddata.h cont.$(OBJEXT): {$(VPATH)}internal/ctype.h cont.$(OBJEXT): {$(VPATH)}internal/dllexport.h cont.$(OBJEXT): {$(VPATH)}internal/dosish.h +cont.$(OBJEXT): {$(VPATH)}internal/encoding/coderange.h +cont.$(OBJEXT): {$(VPATH)}internal/encoding/ctype.h +cont.$(OBJEXT): {$(VPATH)}internal/encoding/encoding.h +cont.$(OBJEXT): {$(VPATH)}internal/encoding/pathname.h +cont.$(OBJEXT): {$(VPATH)}internal/encoding/re.h +cont.$(OBJEXT): {$(VPATH)}internal/encoding/sprintf.h +cont.$(OBJEXT): {$(VPATH)}internal/encoding/string.h +cont.$(OBJEXT): {$(VPATH)}internal/encoding/symbol.h +cont.$(OBJEXT): {$(VPATH)}internal/encoding/transcode.h cont.$(OBJEXT): {$(VPATH)}internal/error.h cont.$(OBJEXT): {$(VPATH)}internal/eval.h cont.$(OBJEXT): {$(VPATH)}internal/event.h @@ -3583,6 +3616,8 @@ cont.$(OBJEXT): {$(VPATH)}method.h cont.$(OBJEXT): {$(VPATH)}missing.h cont.$(OBJEXT): {$(VPATH)}mjit.h cont.$(OBJEXT): {$(VPATH)}node.h +cont.$(OBJEXT): {$(VPATH)}onigmo.h +cont.$(OBJEXT): {$(VPATH)}oniguruma.h cont.$(OBJEXT): {$(VPATH)}ractor.h cont.$(OBJEXT): {$(VPATH)}ractor_core.h cont.$(OBJEXT): {$(VPATH)}ruby_assert.h @@ -5869,10 +5904,16 @@ enum.$(OBJEXT): {$(VPATH)}st.h enum.$(OBJEXT): {$(VPATH)}subst.h enum.$(OBJEXT): {$(VPATH)}symbol.h enum.$(OBJEXT): {$(VPATH)}util.h +enumerator.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h +enumerator.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h +enumerator.$(OBJEXT): $(CCAN_DIR)/list/list.h +enumerator.$(OBJEXT): $(CCAN_DIR)/str/str.h enumerator.$(OBJEXT): $(hdrdir)/ruby/ruby.h enumerator.$(OBJEXT): $(top_srcdir)/internal/array.h +enumerator.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h enumerator.$(OBJEXT): $(top_srcdir)/internal/bignum.h enumerator.$(OBJEXT): $(top_srcdir)/internal/bits.h +enumerator.$(OBJEXT): $(top_srcdir)/internal/class.h enumerator.$(OBJEXT): $(top_srcdir)/internal/compilers.h enumerator.$(OBJEXT): $(top_srcdir)/internal/enumerator.h enumerator.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -5890,6 +5931,7 @@ enumerator.$(OBJEXT): $(top_srcdir)/internal/struct.h enumerator.$(OBJEXT): $(top_srcdir)/internal/vm.h enumerator.$(OBJEXT): $(top_srcdir)/internal/warnings.h enumerator.$(OBJEXT): {$(VPATH)}assert.h +enumerator.$(OBJEXT): {$(VPATH)}atomic.h enumerator.$(OBJEXT): {$(VPATH)}backward/2/assume.h enumerator.$(OBJEXT): {$(VPATH)}backward/2/attributes.h enumerator.$(OBJEXT): {$(VPATH)}backward/2/bool.h @@ -5904,6 +5946,7 @@ enumerator.$(OBJEXT): {$(VPATH)}defines.h enumerator.$(OBJEXT): {$(VPATH)}encoding.h enumerator.$(OBJEXT): {$(VPATH)}enumerator.c enumerator.$(OBJEXT): {$(VPATH)}id.h +enumerator.$(OBJEXT): {$(VPATH)}id_table.h enumerator.$(OBJEXT): {$(VPATH)}intern.h enumerator.$(OBJEXT): {$(VPATH)}internal.h enumerator.$(OBJEXT): {$(VPATH)}internal/abi.h @@ -6055,13 +6098,20 @@ enumerator.$(OBJEXT): {$(VPATH)}internal/value_type.h enumerator.$(OBJEXT): {$(VPATH)}internal/variable.h enumerator.$(OBJEXT): {$(VPATH)}internal/warning_push.h enumerator.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +enumerator.$(OBJEXT): {$(VPATH)}method.h enumerator.$(OBJEXT): {$(VPATH)}missing.h +enumerator.$(OBJEXT): {$(VPATH)}node.h enumerator.$(OBJEXT): {$(VPATH)}onigmo.h enumerator.$(OBJEXT): {$(VPATH)}oniguruma.h enumerator.$(OBJEXT): {$(VPATH)}ruby_assert.h +enumerator.$(OBJEXT): {$(VPATH)}ruby_atomic.h enumerator.$(OBJEXT): {$(VPATH)}shape.h enumerator.$(OBJEXT): {$(VPATH)}st.h enumerator.$(OBJEXT): {$(VPATH)}subst.h +enumerator.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h +enumerator.$(OBJEXT): {$(VPATH)}thread_native.h +enumerator.$(OBJEXT): {$(VPATH)}vm_core.h +enumerator.$(OBJEXT): {$(VPATH)}vm_opts.h error.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h error.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h error.$(OBJEXT): $(CCAN_DIR)/list/list.h @@ -7030,6 +7080,7 @@ goruby.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h goruby.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h goruby.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h goruby.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h +goruby.$(OBJEXT): {$(VPATH)}internal/attr/nonstring.h goruby.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h goruby.$(OBJEXT): {$(VPATH)}internal/attr/pure.h goruby.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h @@ -8533,7 +8584,6 @@ loadpath.$(OBJEXT): {$(VPATH)}internal/warning_push.h loadpath.$(OBJEXT): {$(VPATH)}internal/xmalloc.h loadpath.$(OBJEXT): {$(VPATH)}loadpath.c loadpath.$(OBJEXT): {$(VPATH)}missing.h -loadpath.$(OBJEXT): {$(VPATH)}revision.h loadpath.$(OBJEXT): {$(VPATH)}st.h loadpath.$(OBJEXT): {$(VPATH)}subst.h loadpath.$(OBJEXT): {$(VPATH)}verconf.h @@ -8868,8 +8918,13 @@ main.$(OBJEXT): {$(VPATH)}missing.h main.$(OBJEXT): {$(VPATH)}st.h main.$(OBJEXT): {$(VPATH)}subst.h main.$(OBJEXT): {$(VPATH)}vm_debug.h +marshal.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h +marshal.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h +marshal.$(OBJEXT): $(CCAN_DIR)/list/list.h +marshal.$(OBJEXT): $(CCAN_DIR)/str/str.h marshal.$(OBJEXT): $(hdrdir)/ruby/ruby.h marshal.$(OBJEXT): $(top_srcdir)/internal/array.h +marshal.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h marshal.$(OBJEXT): $(top_srcdir)/internal/bignum.h marshal.$(OBJEXT): $(top_srcdir)/internal/bits.h marshal.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -8879,6 +8934,7 @@ marshal.$(OBJEXT): $(top_srcdir)/internal/error.h marshal.$(OBJEXT): $(top_srcdir)/internal/fixnum.h marshal.$(OBJEXT): $(top_srcdir)/internal/gc.h marshal.$(OBJEXT): $(top_srcdir)/internal/hash.h +marshal.$(OBJEXT): $(top_srcdir)/internal/imemo.h marshal.$(OBJEXT): $(top_srcdir)/internal/numeric.h marshal.$(OBJEXT): $(top_srcdir)/internal/object.h marshal.$(OBJEXT): $(top_srcdir)/internal/serial.h @@ -8891,6 +8947,7 @@ marshal.$(OBJEXT): $(top_srcdir)/internal/variable.h marshal.$(OBJEXT): $(top_srcdir)/internal/vm.h marshal.$(OBJEXT): $(top_srcdir)/internal/warnings.h marshal.$(OBJEXT): {$(VPATH)}assert.h +marshal.$(OBJEXT): {$(VPATH)}atomic.h marshal.$(OBJEXT): {$(VPATH)}backward/2/assume.h marshal.$(OBJEXT): {$(VPATH)}backward/2/attributes.h marshal.$(OBJEXT): {$(VPATH)}backward/2/bool.h @@ -8906,6 +8963,7 @@ marshal.$(OBJEXT): {$(VPATH)}constant.h marshal.$(OBJEXT): {$(VPATH)}defines.h marshal.$(OBJEXT): {$(VPATH)}encindex.h marshal.$(OBJEXT): {$(VPATH)}encoding.h +marshal.$(OBJEXT): {$(VPATH)}id.h marshal.$(OBJEXT): {$(VPATH)}id_table.h marshal.$(OBJEXT): {$(VPATH)}intern.h marshal.$(OBJEXT): {$(VPATH)}internal.h @@ -8946,6 +9004,7 @@ marshal.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h marshal.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h marshal.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h marshal.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h +marshal.$(OBJEXT): {$(VPATH)}internal/attr/nonstring.h marshal.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h marshal.$(OBJEXT): {$(VPATH)}internal/attr/pure.h marshal.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h @@ -9061,13 +9120,21 @@ marshal.$(OBJEXT): {$(VPATH)}internal/xmalloc.h marshal.$(OBJEXT): {$(VPATH)}io.h marshal.$(OBJEXT): {$(VPATH)}marshal.c marshal.$(OBJEXT): {$(VPATH)}marshal.rbinc +marshal.$(OBJEXT): {$(VPATH)}method.h marshal.$(OBJEXT): {$(VPATH)}missing.h +marshal.$(OBJEXT): {$(VPATH)}node.h marshal.$(OBJEXT): {$(VPATH)}onigmo.h marshal.$(OBJEXT): {$(VPATH)}oniguruma.h +marshal.$(OBJEXT): {$(VPATH)}ruby_assert.h +marshal.$(OBJEXT): {$(VPATH)}ruby_atomic.h marshal.$(OBJEXT): {$(VPATH)}shape.h marshal.$(OBJEXT): {$(VPATH)}st.h marshal.$(OBJEXT): {$(VPATH)}subst.h +marshal.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h +marshal.$(OBJEXT): {$(VPATH)}thread_native.h marshal.$(OBJEXT): {$(VPATH)}util.h +marshal.$(OBJEXT): {$(VPATH)}vm_core.h +marshal.$(OBJEXT): {$(VPATH)}vm_opts.h math.$(OBJEXT): $(hdrdir)/ruby/ruby.h math.$(OBJEXT): $(top_srcdir)/internal/bignum.h math.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -9243,12 +9310,16 @@ math.$(OBJEXT): {$(VPATH)}shape.h math.$(OBJEXT): {$(VPATH)}st.h math.$(OBJEXT): {$(VPATH)}subst.h memory_view.$(OBJEXT): $(hdrdir)/ruby/ruby.h +memory_view.$(OBJEXT): $(top_srcdir)/internal/compilers.h +memory_view.$(OBJEXT): $(top_srcdir)/internal/gc.h memory_view.$(OBJEXT): $(top_srcdir)/internal/hash.h memory_view.$(OBJEXT): $(top_srcdir)/internal/variable.h +memory_view.$(OBJEXT): $(top_srcdir)/internal/warnings.h memory_view.$(OBJEXT): {$(VPATH)}assert.h memory_view.$(OBJEXT): {$(VPATH)}backward/2/assume.h memory_view.$(OBJEXT): {$(VPATH)}backward/2/attributes.h memory_view.$(OBJEXT): {$(VPATH)}backward/2/bool.h +memory_view.$(OBJEXT): {$(VPATH)}backward/2/gcc_version_since.h memory_view.$(OBJEXT): {$(VPATH)}backward/2/inttypes.h memory_view.$(OBJEXT): {$(VPATH)}backward/2/limits.h memory_view.$(OBJEXT): {$(VPATH)}backward/2/long_long.h @@ -9488,6 +9559,7 @@ miniinit.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h miniinit.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h miniinit.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h miniinit.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h +miniinit.$(OBJEXT): {$(VPATH)}internal/attr/nonstring.h miniinit.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h miniinit.$(OBJEXT): {$(VPATH)}internal/attr/pure.h miniinit.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h @@ -10455,8 +10527,13 @@ numeric.$(OBJEXT): {$(VPATH)}shape.h numeric.$(OBJEXT): {$(VPATH)}st.h numeric.$(OBJEXT): {$(VPATH)}subst.h numeric.$(OBJEXT): {$(VPATH)}util.h +object.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h +object.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h +object.$(OBJEXT): $(CCAN_DIR)/list/list.h +object.$(OBJEXT): $(CCAN_DIR)/str/str.h object.$(OBJEXT): $(hdrdir)/ruby/ruby.h object.$(OBJEXT): $(top_srcdir)/internal/array.h +object.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h object.$(OBJEXT): $(top_srcdir)/internal/bignum.h object.$(OBJEXT): $(top_srcdir)/internal/bits.h object.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -10465,6 +10542,7 @@ object.$(OBJEXT): $(top_srcdir)/internal/error.h object.$(OBJEXT): $(top_srcdir)/internal/eval.h object.$(OBJEXT): $(top_srcdir)/internal/fixnum.h object.$(OBJEXT): $(top_srcdir)/internal/gc.h +object.$(OBJEXT): $(top_srcdir)/internal/imemo.h object.$(OBJEXT): $(top_srcdir)/internal/inits.h object.$(OBJEXT): $(top_srcdir)/internal/numeric.h object.$(OBJEXT): $(top_srcdir)/internal/object.h @@ -10477,6 +10555,7 @@ object.$(OBJEXT): $(top_srcdir)/internal/variable.h object.$(OBJEXT): $(top_srcdir)/internal/vm.h object.$(OBJEXT): $(top_srcdir)/internal/warnings.h object.$(OBJEXT): {$(VPATH)}assert.h +object.$(OBJEXT): {$(VPATH)}atomic.h object.$(OBJEXT): {$(VPATH)}backward/2/assume.h object.$(OBJEXT): {$(VPATH)}backward/2/attributes.h object.$(OBJEXT): {$(VPATH)}backward/2/bool.h @@ -10645,22 +10724,31 @@ object.$(OBJEXT): {$(VPATH)}internal/variable.h object.$(OBJEXT): {$(VPATH)}internal/warning_push.h object.$(OBJEXT): {$(VPATH)}internal/xmalloc.h object.$(OBJEXT): {$(VPATH)}kernel.rbinc +object.$(OBJEXT): {$(VPATH)}method.h object.$(OBJEXT): {$(VPATH)}missing.h object.$(OBJEXT): {$(VPATH)}nilclass.rbinc +object.$(OBJEXT): {$(VPATH)}node.h object.$(OBJEXT): {$(VPATH)}object.c object.$(OBJEXT): {$(VPATH)}onigmo.h object.$(OBJEXT): {$(VPATH)}oniguruma.h object.$(OBJEXT): {$(VPATH)}probes.dmyh object.$(OBJEXT): {$(VPATH)}probes.h +object.$(OBJEXT): {$(VPATH)}ruby_assert.h +object.$(OBJEXT): {$(VPATH)}ruby_atomic.h object.$(OBJEXT): {$(VPATH)}shape.h object.$(OBJEXT): {$(VPATH)}st.h object.$(OBJEXT): {$(VPATH)}subst.h +object.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h +object.$(OBJEXT): {$(VPATH)}thread_native.h object.$(OBJEXT): {$(VPATH)}util.h object.$(OBJEXT): {$(VPATH)}variable.h +object.$(OBJEXT): {$(VPATH)}vm_core.h +object.$(OBJEXT): {$(VPATH)}vm_opts.h pack.$(OBJEXT): $(hdrdir)/ruby/ruby.h pack.$(OBJEXT): $(top_srcdir)/internal/array.h pack.$(OBJEXT): $(top_srcdir)/internal/bits.h pack.$(OBJEXT): $(top_srcdir)/internal/compilers.h +pack.$(OBJEXT): $(top_srcdir)/internal/gc.h pack.$(OBJEXT): $(top_srcdir)/internal/static_assert.h pack.$(OBJEXT): $(top_srcdir)/internal/string.h pack.$(OBJEXT): $(top_srcdir)/internal/symbol.h @@ -11069,6 +11157,7 @@ proc.$(OBJEXT): $(top_srcdir)/internal/compilers.h proc.$(OBJEXT): $(top_srcdir)/internal/error.h proc.$(OBJEXT): $(top_srcdir)/internal/eval.h proc.$(OBJEXT): $(top_srcdir)/internal/gc.h +proc.$(OBJEXT): $(top_srcdir)/internal/hash.h proc.$(OBJEXT): $(top_srcdir)/internal/imemo.h proc.$(OBJEXT): $(top_srcdir)/internal/object.h proc.$(OBJEXT): $(top_srcdir)/internal/proc.h @@ -11492,6 +11581,7 @@ ractor.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h ractor.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h ractor.$(OBJEXT): $(CCAN_DIR)/list/list.h ractor.$(OBJEXT): $(CCAN_DIR)/str/str.h +ractor.$(OBJEXT): $(hdrdir)/ruby.h ractor.$(OBJEXT): $(hdrdir)/ruby/ruby.h ractor.$(OBJEXT): $(top_srcdir)/internal/array.h ractor.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h @@ -11687,6 +11777,7 @@ ractor.$(OBJEXT): {$(VPATH)}internal/warning_push.h ractor.$(OBJEXT): {$(VPATH)}internal/xmalloc.h ractor.$(OBJEXT): {$(VPATH)}method.h ractor.$(OBJEXT): {$(VPATH)}missing.h +ractor.$(OBJEXT): {$(VPATH)}mjit.h ractor.$(OBJEXT): {$(VPATH)}node.h ractor.$(OBJEXT): {$(VPATH)}onigmo.h ractor.$(OBJEXT): {$(VPATH)}oniguruma.h @@ -11715,6 +11806,7 @@ random.$(OBJEXT): $(top_srcdir)/internal/bignum.h random.$(OBJEXT): $(top_srcdir)/internal/bits.h random.$(OBJEXT): $(top_srcdir)/internal/compilers.h random.$(OBJEXT): $(top_srcdir)/internal/fixnum.h +random.$(OBJEXT): $(top_srcdir)/internal/gc.h random.$(OBJEXT): $(top_srcdir)/internal/numeric.h random.$(OBJEXT): $(top_srcdir)/internal/random.h random.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h @@ -14315,6 +14407,7 @@ signal.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h signal.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h signal.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h signal.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h +signal.$(OBJEXT): {$(VPATH)}internal/attr/nonstring.h signal.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h signal.$(OBJEXT): {$(VPATH)}internal/attr/pure.h signal.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h @@ -14805,6 +14898,7 @@ st.$(OBJEXT): {$(VPATH)}internal/variable.h st.$(OBJEXT): {$(VPATH)}internal/warning_push.h st.$(OBJEXT): {$(VPATH)}internal/xmalloc.h st.$(OBJEXT): {$(VPATH)}missing.h +st.$(OBJEXT): {$(VPATH)}ruby_assert.h st.$(OBJEXT): {$(VPATH)}st.c st.$(OBJEXT): {$(VPATH)}st.h st.$(OBJEXT): {$(VPATH)}subst.h @@ -15070,6 +15164,7 @@ string.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h string.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h string.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h string.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h +string.$(OBJEXT): {$(VPATH)}internal/attr/nonstring.h string.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h string.$(OBJEXT): {$(VPATH)}internal/attr/pure.h string.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h @@ -15511,6 +15606,7 @@ symbol.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h symbol.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h symbol.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h symbol.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h +symbol.$(OBJEXT): {$(VPATH)}internal/attr/nonstring.h symbol.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h symbol.$(OBJEXT): {$(VPATH)}internal/attr/pure.h symbol.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h @@ -1243,6 +1243,32 @@ new_adjust_body(rb_iseq_t *iseq, LABEL *label, int line) return adjust; } +static void +iseq_insn_each_markable_object(INSN *insn, void (*func)(VALUE *, VALUE), VALUE data) +{ + const char *types = insn_op_types(insn->insn_id); + for (int j = 0; types[j]; j++) { + char type = types[j]; + switch (type) { + case TS_CDHASH: + case TS_ISEQ: + case TS_VALUE: + case TS_IC: // constant path array + case TS_CALLDATA: // ci is stored. + func(&OPERAND_AT(insn, j), data); + break; + default: + break; + } + } +} + +static void +iseq_insn_each_object_write_barrier(VALUE *obj_ptr, VALUE iseq) +{ + RB_OBJ_WRITTEN(iseq, Qundef, *obj_ptr); +} + static INSN * new_insn_core(rb_iseq_t *iseq, const NODE *line_node, int insn_id, int argc, VALUE *argv) @@ -1260,6 +1286,9 @@ new_insn_core(rb_iseq_t *iseq, const NODE *line_node, iobj->operands = argv; iobj->operand_size = argc; iobj->sc_state = 0; + + iseq_insn_each_markable_object(iobj, iseq_insn_each_object_write_barrier, (VALUE)iseq); + return iobj; } @@ -1832,6 +1861,7 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, const NODE *cons EXPECT_NODE("iseq_set_arguments", node_args, NODE_ARGS, COMPILE_NG); + body->param.flags.ruby2_keywords = args->ruby2_keywords; body->param.lead_num = arg_size = (int)args->pre_args_num; if (body->param.lead_num > 0) body->param.flags.has_lead = TRUE; debugs(" - argc: %d\n", body->param.lead_num); @@ -2630,9 +2660,11 @@ iseq_set_exception_table(rb_iseq_t *iseq) struct iseq_catch_table_entry *entry; ISEQ_BODY(iseq)->catch_table = NULL; - if (NIL_P(ISEQ_COMPILE_DATA(iseq)->catch_table_ary)) return COMPILE_OK; - tlen = (int)RARRAY_LEN(ISEQ_COMPILE_DATA(iseq)->catch_table_ary); - tptr = RARRAY_CONST_PTR_TRANSIENT(ISEQ_COMPILE_DATA(iseq)->catch_table_ary); + + VALUE catch_table_ary = ISEQ_COMPILE_DATA(iseq)->catch_table_ary; + if (NIL_P(catch_table_ary)) return COMPILE_OK; + tlen = (int)RARRAY_LEN(catch_table_ary); + tptr = RARRAY_CONST_PTR_TRANSIENT(catch_table_ary); if (tlen > 0) { struct iseq_catch_table *table = xmalloc(iseq_catch_table_bytes(tlen)); @@ -2668,6 +2700,8 @@ iseq_set_exception_table(rb_iseq_t *iseq) RB_OBJ_WRITE(iseq, &ISEQ_COMPILE_DATA(iseq)->catch_table_ary, 0); /* free */ } + RB_GC_GUARD(catch_table_ary); + return COMPILE_OK; } @@ -2806,7 +2840,6 @@ remove_unreachable_chunk(rb_iseq_t *iseq, LINK_ELEMENT *i) break; } else if ((lab = find_destination((INSN *)i)) != 0) { - if (lab->unremovable) break; unref_counts[lab->label_no]++; } } @@ -2823,8 +2856,7 @@ remove_unreachable_chunk(rb_iseq_t *iseq, LINK_ELEMENT *i) /* do nothing */ } else if (IS_ADJUST(i)) { - LABEL *dest = ((ADJUST *)i)->label; - if (dest && dest->unremovable) return 0; + return 0; } end = i; } while ((i = i->next) != 0); @@ -4588,7 +4620,12 @@ compile_array_1(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node) } else { CHECK(COMPILE_(ret, "array element", node, FALSE)); - ADD_INSN1(ret, node, newarray, INT2FIX(1)); + if (keyword_node_p(node)) { + ADD_INSN1(ret, node, newarraykwsplat, INT2FIX(1)); + } + else { + ADD_INSN1(ret, node, newarray, INT2FIX(1)); + } } return 1; @@ -5039,12 +5076,17 @@ compile_massign_lhs(rb_iseq_t *iseq, LINK_ANCHOR *const pre, LINK_ANCHOR *const CHECK(COMPILE_POPPED(pre, "masgn lhs (NODE_ATTRASGN)", node)); + bool safenav_call = false; LINK_ELEMENT *insn_element = LAST_ELEMENT(pre); iobj = (INSN *)get_prev_insn((INSN *)insn_element); /* send insn */ ASSUME(iobj); - ELEM_REMOVE(LAST_ELEMENT(pre)); - ELEM_REMOVE((LINK_ELEMENT *)iobj); - pre->last = iobj->link.prev; + ELEM_REMOVE(insn_element); + if (!IS_INSN_ID(iobj, send)) { + safenav_call = true; + iobj = (INSN *)get_prev_insn(iobj); + ELEM_INSERT_NEXT(&iobj->link, insn_element); + } + (pre->last = iobj->link.prev)->next = 0; const struct rb_callinfo *ci = (struct rb_callinfo *)OPERAND_AT(iobj, 0); int argc = vm_ci_argc(ci) + 1; @@ -5063,7 +5105,9 @@ compile_massign_lhs(rb_iseq_t *iseq, LINK_ANCHOR *const pre, LINK_ANCHOR *const return COMPILE_NG; } - ADD_ELEM(lhs, (LINK_ELEMENT *)iobj); + iobj->link.prev = lhs->last; + lhs->last->next = &iobj->link; + for (lhs->last = &iobj->link; lhs->last->next; lhs->last = lhs->last->next); if (vm_ci_flag(ci) & VM_CALL_ARGS_SPLAT) { int argc = vm_ci_argc(ci); ci = ci_argc_set(iseq, ci, argc - 1); @@ -5072,9 +5116,11 @@ compile_massign_lhs(rb_iseq_t *iseq, LINK_ANCHOR *const pre, LINK_ANCHOR *const INSERT_BEFORE_INSN1(iobj, line_node, newarray, INT2FIX(1)); INSERT_BEFORE_INSN(iobj, line_node, concatarray); } - ADD_INSN(lhs, line_node, pop); - if (argc != 1) { + if (!safenav_call) { ADD_INSN(lhs, line_node, pop); + if (argc != 1) { + ADD_INSN(lhs, line_node, pop); + } } for (int i=0; i < argc; i++) { ADD_INSN(post, line_node, pop); @@ -7611,7 +7657,6 @@ compile_next(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in add_ensure_iseq(ret, iseq, 0); ADD_INSNL(ret, line_node, jump, ISEQ_COMPILE_DATA(iseq)->end_label); ADD_ADJUST_RESTORE(ret, splabel); - splabel->unremovable = FALSE; if (!popped) { ADD_INSN(ret, line_node, putnil); @@ -9276,7 +9321,8 @@ compile_attrasgn(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node /* optimization shortcut * obj["literal"] = value -> opt_aset_with(obj, "literal", value) */ - if (mid == idASET && !private_recv_p(node) && node->nd_args && + if (!ISEQ_COMPILE_DATA(iseq)->in_masgn && + mid == idASET && !private_recv_p(node) && node->nd_args && nd_type_p(node->nd_args, NODE_LIST) && node->nd_args->nd_alen == 2 && nd_type_p(node->nd_args->nd_head, NODE_STR) && ISEQ_COMPILE_DATA(iseq)->current_block == NULL && @@ -9474,7 +9520,10 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no } case NODE_MASGN:{ + bool prev_in_masgn = ISEQ_COMPILE_DATA(iseq)->in_masgn; + ISEQ_COMPILE_DATA(iseq)->in_masgn = true; compile_massign(iseq, ret, node, popped); + ISEQ_COMPILE_DATA(iseq)->in_masgn = prev_in_masgn; break; } @@ -9716,7 +9765,12 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no case NODE_LIT:{ debugp_param("lit", node->nd_lit); if (!popped) { - ADD_INSN1(ret, node, putobject, node->nd_lit); + if (UNLIKELY(node->nd_lit == rb_mRubyVMFrozenCore)) { + ADD_INSN1(ret, node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); // [Bug #20569] + } + else { + ADD_INSN1(ret, node, putobject, node->nd_lit); + } RB_OBJ_WRITTEN(iseq, Qundef, node->nd_lit); } break; @@ -10734,6 +10788,12 @@ iseq_build_kw(rb_iseq_t *iseq, VALUE params, VALUE keywords) return keyword; } +static void +iseq_insn_each_object_mark(VALUE *obj_ptr, VALUE _) +{ + rb_gc_mark(*obj_ptr); +} + void rb_iseq_mark_insn_storage(struct iseq_compile_data_storage *storage) { @@ -10760,29 +10820,7 @@ rb_iseq_mark_insn_storage(struct iseq_compile_data_storage *storage) iobj = (INSN *)&storage->buff[pos]; if (iobj->operands) { - int j; - const char *types = insn_op_types(iobj->insn_id); - - for (j = 0; types[j]; j++) { - char type = types[j]; - switch (type) { - case TS_CDHASH: - case TS_ISEQ: - case TS_VALUE: - case TS_IC: // constant path array - case TS_CALLDATA: // ci is stored. - { - VALUE op = OPERAND_AT(iobj, j); - - if (!SPECIAL_CONST_P(op)) { - rb_gc_mark(op); - } - } - break; - default: - break; - } - } + iseq_insn_each_markable_object(iobj, iseq_insn_each_object_mark, (VALUE)0); } pos += (int)size; } @@ -12310,7 +12348,7 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset) verify_call_cache(iseq); RB_GC_GUARD(dummy_frame); - rb_vm_pop_frame(ec); + rb_vm_pop_frame_no_int(ec); } struct ibf_dump_iseq_list_arg @@ -13061,7 +13099,7 @@ ibf_dump_memsize(const void *ptr) static const rb_data_type_t ibf_dump_type = { "ibf_dump", {ibf_dump_mark, ibf_dump_free, ibf_dump_memsize,}, - 0, 0, RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY }; static void @@ -2116,8 +2116,11 @@ nucomp_convert(VALUE klass, VALUE a1, VALUE a2, int raise) return a1; /* should raise exception for consistency */ if (!k_numeric_p(a1)) { - if (!raise) - return rb_protect(to_complex, a1, NULL); + if (!raise) { + a1 = rb_protect(to_complex, a1, NULL); + rb_set_errinfo(Qnil); + return a1; + } return to_complex(a1); } } diff --git a/configure.ac b/configure.ac index ae560297ad..220392d120 100644 --- a/configure.ac +++ b/configure.ac @@ -214,15 +214,23 @@ AS_CASE(["/${rb_CC} "], [*clang*], [ # Ditto for LLVM. Note however that llvm-as is a LLVM-IR to LLVM bitcode # assembler that does not target your machine native binary. - : ${LD:="${CC}"} # ... try -fuse-ld=lld ? - RUBY_CHECK_PROG_FOR_CC([AR], [s/clang/llvm-ar/]) -# RUBY_CHECK_PROG_FOR_CC([AS], [s/clang/llvm-as/]) + + # Xcode has its own version tools that may be incompatible with + # genuine LLVM tools, use the tools in the same directory. + + AS_IF([$rb_CC -E -dM -xc - < /dev/null | grep -F __apple_build_version__ > /dev/null], + [llvm_prefix=], [llvm_prefix=llvm-]) + # AC_PREPROC_IFELSE cannot be used before AC_USE_SYSTEM_EXTENSIONS + + RUBY_CHECK_PROG_FOR_CC([LD], [s/clang/ld/]) # ... maybe try lld ? + RUBY_CHECK_PROG_FOR_CC([AR], [s/clang/${llvm_prefix}ar/]) +# RUBY_CHECK_PROG_FOR_CC([AS], [s/clang/${llvm_prefix}as/]) RUBY_CHECK_PROG_FOR_CC([CXX], [s/clang/clang++/]) - RUBY_CHECK_PROG_FOR_CC([NM], [s/clang/llvm-nm/]) - RUBY_CHECK_PROG_FOR_CC([OBJCOPY], [s/clang/llvm-objcopy/]) - RUBY_CHECK_PROG_FOR_CC([OBJDUMP], [s/clang/llvm-objdump/]) - RUBY_CHECK_PROG_FOR_CC([RANLIB], [s/clang/llvm-ranlib/]) - RUBY_CHECK_PROG_FOR_CC([STRIP], [s/clang/llvm-strip/]) + RUBY_CHECK_PROG_FOR_CC([NM], [s/clang/${llvm_prefix}nm/]) + RUBY_CHECK_PROG_FOR_CC([OBJCOPY], [s/clang/${llvm_prefix}objcopy/]) + RUBY_CHECK_PROG_FOR_CC([OBJDUMP], [s/clang/${llvm_prefix}objdump/]) + RUBY_CHECK_PROG_FOR_CC([RANLIB], [s/clang/${llvm_prefix}ranlib/]) + RUBY_CHECK_PROG_FOR_CC([STRIP], [s/clang/${llvm_prefix}strip/]) ]) AS_UNSET(rb_CC) AS_UNSET(rb_dummy) @@ -366,12 +374,6 @@ AS_CASE(["$target_os"], [AC_MSG_RESULT(yes)], [AC_MSG_RESULT(no) AC_MSG_ERROR([Unsupported OS X version is required])]) - AC_CACHE_CHECK([if thread-local storage is supported], [rb_cv_tls_supported], - [AC_LINK_IFELSE([AC_LANG_PROGRAM([[int __thread conftest;]])], - [rb_cv_tls_supported=yes], - [rb_cv_tls_supported=no])]) - AS_IF([test x"$rb_cv_tls_supported" != xyes], - [AC_DEFINE(RB_THREAD_LOCAL_SPECIFIER_IS_UNSUPPORTED)]) ]) RUBY_MINGW32 @@ -391,6 +393,13 @@ AS_IF([test "$GCC" = yes], [ AS_IF([test "$gcc_major" -lt 4], [ AC_MSG_ERROR([too old GCC: $gcc_major.$gcc_minor]) ]) + + AC_CACHE_CHECK([if thread-local storage is supported], [rb_cv_tls_supported], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[int __thread conftest;]])], + [rb_cv_tls_supported=yes], + [rb_cv_tls_supported=no])]) + AS_IF([test x"$rb_cv_tls_supported" != xyes], + [AC_DEFINE(RB_THREAD_LOCAL_SPECIFIER_IS_UNSUPPORTED)]) ], [ linker_flag= ]) @@ -524,7 +533,9 @@ AS_IF([test "$cross_compiling:$ac_cv_prog_DTRACE" = no: -a -n "$ac_tool_prefix"] AC_CHECK_PROGS(DOT, dot) AC_CHECK_PROGS(DOXYGEN, doxygen) +tool_warned=$ac_tool_warned ac_tool_warned=no AC_CHECK_TOOL(PKG_CONFIG, pkg-config) +ac_tool_warned=$tool_warned AS_IF([test -z "$PKG_CONFIG"], [], ["$PKG_CONFIG" --print-errors --version > /dev/null 2>&1], [], [ @@ -964,9 +975,10 @@ AS_IF([test "x$OPT_DIR" != x], [ LDFLAGS="${LDFLAGS:+$LDFLAGS }$val" DLDFLAGS="${DLDFLAGS:+$DLDFLAGS }$val" LDFLAGS_OPTDIR="$val" - CPPFLAGS="${CPPFLAGS:+$CPPFLAGS }"`echo "$OPT_DIR" | tr "${PATH_SEPARATOR}" '\012' | + INCFLAGS="${INCFLAGS:+$INCFLAGS }"`echo "$OPT_DIR" | tr "${PATH_SEPARATOR}" '\012' | sed '/^$/d;s|^|-I|;s|$|/include|' | tr '\012' ' ' | sed 's/ *$//'` ]) +AC_SUBST(incflags, "$INCFLAGS") test -z "${ac_env_CFLAGS_set}" -a -n "${cflags+set}" && eval CFLAGS="\"$cflags $ARCH_FLAG\"" test -z "${ac_env_CXXFLAGS_set}" -a -n "${cxxflags+set}" && eval CXXFLAGS="\"$cxxflags $ARCH_FLAG\"" @@ -1191,8 +1203,6 @@ main() ac_cv_func_gmtime_r=yes rb_cv_large_fd_select=yes ac_cv_type_struct_timeval=yes - ac_cv_func_clock_gettime=yes - ac_cv_func_clock_getres=yes ac_cv_func_malloc_usable_size=no ac_cv_type_off_t=yes ac_cv_sizeof_off_t=8 @@ -1338,6 +1348,8 @@ AC_CHECK_HEADERS(syscall.h) AC_CHECK_HEADERS(time.h) AC_CHECK_HEADERS(ucontext.h) AC_CHECK_HEADERS(utime.h) +AC_CHECK_HEADERS(stdatomic.h) + AS_CASE("$target_cpu", [x64|x86_64|i[3-6]86*], [ AC_CHECK_HEADERS(x86intrin.h) ]) @@ -1354,6 +1366,8 @@ AC_ARG_WITH([jemalloc], [with_jemalloc=$withval], [with_jemalloc=no]) AS_IF([test "x$with_jemalloc" != xno],[ # find jemalloc header first + save_CPPFLAGS="${CPPFLAGS}" + CPPFLAGS="${INCFLAGS} ${CPPFLAGS}" malloc_header= AC_CHECK_HEADER(jemalloc/jemalloc.h, [malloc_header=jemalloc/jemalloc.h], [ AC_CHECK_HEADER(jemalloc.h, [malloc_header=jemalloc.h]) @@ -1385,6 +1399,8 @@ AS_IF([test "x$with_jemalloc" != xno],[ done done ]) + CPPFLAGS="${save_CPPFLAGS}" + unset save_CPPFLAGS with_jemalloc=${rb_cv_jemalloc_library} AS_CASE(["$with_jemalloc"], [no], @@ -1995,6 +2011,7 @@ AC_CHECK_FUNCS(_longjmp) # used for AC_ARG_WITH(setjmp-type) test x$ac_cv_func__longjmp = xno && ac_cv_func__setjmp=no AC_CHECK_FUNCS(arc4random_buf) AC_CHECK_FUNCS(atan2l atan2f) +AC_CHECK_DECLS(atomic_signal_fence, [], [], [#include <stdatomic.h>]) AC_CHECK_FUNCS(chmod) AC_CHECK_FUNCS(chown) AC_CHECK_FUNCS(chroot) @@ -3049,7 +3066,7 @@ AC_SUBST(EXTOBJS) [darwin*], [ : ${LDSHARED='$(CC) -dynamic -bundle'} : ${DLDSHARED='$(CC) -dynamiclib'} : ${LDFLAGS=""} - : ${LIBPATHENV=DYLD_FALLBACK_LIBRARY_PATH} + : ${LIBPATHENV=DYLD_LIBRARY_PATH} : ${PRELOADENV=DYLD_INSERT_LIBRARIES} AS_IF([test x"$enable_shared" = xyes], [ # Resolve symbols from libruby.dylib when --enable-shared @@ -3352,6 +3369,10 @@ AS_IF([test x"$cross_compiling" = xyes], [ AC_SUBST(XRUBY_RUBYLIBDIR) AC_SUBST(XRUBY_RUBYHDRDIR) PREP='$(arch)-fake.rb' + AS_CASE(["$enable_shared:$EXTSTATIC:$target_os"], [no::darwin*], [ + # darwin target requires miniruby for linking ext bundles + PREP="$PREP"' miniruby$(EXEEXT)' + ]) RUNRUBY_COMMAND='$(MINIRUBY) -I`cd $(srcdir)/lib; pwd`' RUNRUBY='$(RUNRUBY_COMMAND)' XRUBY='$(MINIRUBY)' @@ -3785,7 +3806,7 @@ AS_IF([test "$cross_compiling" = no], dnl build YJIT in release mode if rustc >= 1.58.0 is present and we are on a supported platform AC_ARG_ENABLE(yjit, AS_HELP_STRING([--enable-yjit], - [enable experimental in-process JIT compiler that requires Rust build tools [default=no]]), + [enable in-process JIT compiler that requires Rust build tools. enabled by default on supported platforms if rustc 1.58.0+ is available]), [YJIT_SUPPORT=$enableval], [AS_CASE(["$enable_jit_support:$YJIT_TARGET_OK:$YJIT_RUSTC_OK"], [yes:yes:yes|:yes:yes], [ @@ -29,6 +29,7 @@ extern int madvise(caddr_t, size_t, int); #include "gc.h" #include "internal.h" #include "internal/cont.h" +#include "internal/error.h" #include "internal/proc.h" #include "internal/sanitizers.h" #include "internal/warnings.h" @@ -1254,6 +1255,8 @@ jit_cont_new(rb_execution_context_t *ec) static void jit_cont_free(struct rb_jit_cont *cont) { + if (!cont) return; + rb_native_mutex_lock(&jit_cont_lock); if (cont == first_jit_cont) { first_jit_cont = cont->next; @@ -1578,7 +1581,7 @@ cont_restore_1(rb_context_t *cont) cont_restore_thread(cont); /* restore machine stack */ -#ifdef _M_AMD64 +#if defined(_M_AMD64) && !defined(__MINGW64__) { /* workaround for x64 SEH */ jmp_buf buf; @@ -1958,7 +1961,7 @@ rb_cont_call(int argc, VALUE *argv, VALUE contval) * the current thread, blocking and non-blocking fibers' behavior is identical. * * Ruby doesn't provide a scheduler class: it is expected to be implemented by - * the user and correspond to Fiber::SchedulerInterface. + * the user and correspond to Fiber::Scheduler. * * There is also Fiber.schedule method, which is expected to immediately perform * the given block in a non-blocking manner. Its actual implementation is up to @@ -2068,21 +2071,33 @@ fiber_storage_get(rb_fiber_t *fiber) return storage; } +static void +storage_access_must_be_from_same_fiber(VALUE self) +{ + rb_fiber_t *fiber = fiber_ptr(self); + rb_fiber_t *current = fiber_current(); + if (fiber != current) { + rb_raise(rb_eArgError, "Fiber storage can only be accessed from the Fiber it belongs to"); + } +} + /** - * call-seq: Fiber.current.storage -> hash (dup) + * call-seq: fiber.storage -> hash (dup) * - * Returns a copy of the storage hash for the current fiber. + * Returns a copy of the storage hash for the fiber. The method can only be called on the + * Fiber.current. */ static VALUE rb_fiber_storage_get(VALUE self) { + storage_access_must_be_from_same_fiber(self); return rb_obj_dup(fiber_storage_get(fiber_ptr(self))); } static int fiber_storage_validate_each(VALUE key, VALUE value, VALUE _argument) { - rb_check_id(&key); + Check_Type(key, T_SYMBOL); return ST_CONTINUE; } @@ -2105,16 +2120,17 @@ fiber_storage_validate(VALUE value) } /** - * call-seq: Fiber.current.storage = hash + * call-seq: fiber.storage = hash * - * Sets the storage hash for the current fiber. This feature is experimental - * and may change in the future. + * Sets the storage hash for the fiber. This feature is experimental + * and may change in the future. The method can only be called on the + * Fiber.current. * * You should be careful about using this method as you may inadvertently clear * important fiber-storage state. You should mostly prefer to assign specific - * keys in the storage using Fiber#[]=. + * keys in the storage using Fiber::[]=. * - * You can also use Fiber.new(storage: nil) to create a fiber with an empty + * You can also use <tt>Fiber.new(storage: nil)</tt> to create a fiber with an empty * storage. * * Example: @@ -2128,6 +2144,12 @@ fiber_storage_validate(VALUE value) static VALUE rb_fiber_storage_set(VALUE self, VALUE value) { + if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL)) { + rb_category_warn(RB_WARN_CATEGORY_EXPERIMENTAL, + "Fiber#storage= is experimental and may be removed in the future!"); + } + + storage_access_must_be_from_same_fiber(self); fiber_storage_validate(value); fiber_ptr(self)->cont.saved_ec.storage = rb_obj_dup(value); @@ -2137,18 +2159,17 @@ rb_fiber_storage_set(VALUE self, VALUE value) /** * call-seq: Fiber[key] -> value * - * Returns the value of the fiber-local variable identified by +key+. + * Returns the value of the fiber storage variable identified by +key+. * * The +key+ must be a symbol, and the value is set by Fiber#[]= or * Fiber#store. * - * See also Fiber[]=. + * See also Fiber::[]=. */ static VALUE rb_fiber_storage_aref(VALUE class, VALUE key) { - ID id = rb_check_id(&key); - if (!id) return Qnil; + Check_Type(key, T_SYMBOL); VALUE storage = fiber_storage_get(fiber_current()); @@ -2160,18 +2181,17 @@ rb_fiber_storage_aref(VALUE class, VALUE key) /** * call-seq: Fiber[key] = value * - * Assign +value+ to the fiber-local variable identified by +key+. + * Assign +value+ to the fiber storage variable identified by +key+. * The variable is created if it doesn't exist. * * +key+ must be a Symbol, otherwise a TypeError is raised. * - * See also Fiber[]. + * See also Fiber::[]. */ static VALUE rb_fiber_storage_aset(VALUE class, VALUE key, VALUE value) { - ID id = rb_check_id(&key); - if (!id) return Qnil; + Check_Type(key, T_SYMBOL); VALUE storage = fiber_storage_get(fiber_current()); @@ -2185,9 +2205,6 @@ fiber_initialize(VALUE self, VALUE proc, struct fiber_pool * fiber_pool, unsigne // The default, inherit storage (dup) from the current fiber: storage = inherit_fiber_storage(); } - else if (storage == Qfalse) { - storage = current_fiber_storage(); - } else /* nil, hash, etc. */ { fiber_storage_validate(storage); storage = rb_obj_dup(storage); @@ -2299,18 +2316,6 @@ rb_fiber_initialize_kw(int argc, VALUE* argv, VALUE self, int kw_splat) * end.resume * Fiber[:x] # => 1 * - * If the <tt>storage</tt> is <tt>false</tt>, this function uses the current - * fiber's storage by reference. This is used for Enumerator to create - * hidden fiber. - * - * Fiber[:count] = 0 - * enumerator = Enumerator.new do |y| - * loop{y << (Fiber[:count] += 1)} - * end - * Fiber[:count] # => 0 - * enumerator.next # => 1 - * Fiber[:count] # => 1 - * * If the given <tt>storage</tt> is <tt>nil</tt>, this function will lazy * initialize the internal storage, which starts as an empty hash. * @@ -2322,7 +2327,7 @@ rb_fiber_initialize_kw(int argc, VALUE* argv, VALUE self, int kw_splat) * Otherwise, the given <tt>storage</tt> is used as the new fiber's storage, * and it must be an instance of Hash. * - * Explicitly using `storage: true/false` is currently experimental and may + * Explicitly using <tt>storage: true</tt> is currently experimental and may * change in the future. */ static VALUE @@ -2334,7 +2339,7 @@ rb_fiber_initialize(int argc, VALUE* argv, VALUE self) VALUE rb_fiber_new_storage(rb_block_call_func_t func, VALUE obj, VALUE storage) { - return fiber_initialize(fiber_alloc(rb_cFiber), rb_proc_new(func, obj), rb_fiber_pool_default(Qnil), 1, storage); + return fiber_initialize(fiber_alloc(rb_cFiber), rb_proc_new(func, obj), rb_fiber_pool_default(Qnil), 0, storage); } VALUE @@ -2394,7 +2399,7 @@ rb_fiber_s_schedule_kw(int argc, VALUE* argv, int kw_splat) * * Note that the behavior described above is how the method is <em>expected</em> * to behave, actual behavior is up to the current scheduler's implementation of - * Fiber::SchedulerInterface#fiber method. Ruby doesn't enforce this method to + * Fiber::Scheduler#fiber method. Ruby doesn't enforce this method to * behave in any particular way. * * If the scheduler is not set, the method raises @@ -2413,7 +2418,7 @@ rb_fiber_s_schedule(int argc, VALUE *argv, VALUE obj) * * Returns the Fiber scheduler, that was last set for the current thread with Fiber.set_scheduler. * Returns +nil+ if no scheduler is set (which is the default), and non-blocking fibers' - # behavior is the same as blocking. + * behavior is the same as blocking. * (see "Non-blocking fibers" section in class docs for details about the scheduler concept). * */ @@ -2447,7 +2452,7 @@ rb_fiber_current_scheduler(VALUE klass) * thread will call scheduler's +close+ method on finalization (allowing the scheduler to * properly manage all non-finished fibers). * - * +scheduler+ can be an object of any class corresponding to Fiber::SchedulerInterface. Its + * +scheduler+ can be an object of any class corresponding to Fiber::Scheduler. Its * implementation is up to the user. * * See also the "Non-blocking fibers" section in class docs. @@ -2794,7 +2799,8 @@ rb_fiber_blocking(VALUE class) // If we are already blocking, this is essentially a no-op: if (fiber->blocking) { return rb_yield(fiber_value); - } else { + } + else { return rb_ensure(fiber_blocking_yield, fiber_value, fiber_blocking_ensure, fiber_value); } } diff --git a/coroutine/asyncify/Context.h b/coroutine/asyncify/Context.h index 7dba829a1d..71791a4004 100644 --- a/coroutine/asyncify/Context.h +++ b/coroutine/asyncify/Context.h @@ -13,6 +13,7 @@ #include <stddef.h> #include <stdio.h> +#include <stdint.h> #include "wasm/asyncify.h" #include "wasm/machine.h" #include "wasm/fiber.h" @@ -47,10 +48,13 @@ static inline void coroutine_initialize_main(struct coroutine_context * context) static inline void coroutine_initialize(struct coroutine_context *context, coroutine_start start, void *stack, size_t size) { - if (ASYNCIFY_CORO_DEBUG) fprintf(stderr, "[%s] entry (context = %p, stack = %p ... %p)\n", __func__, context, stack, (char *)stack + size); + // Linear stack pointer must be always aligned down to 16 bytes. + // https://github.com/WebAssembly/tool-conventions/blob/c74267a5897c1bdc9aa60adeaf41816387d3cd12/BasicCABI.md#the-linear-stack + uintptr_t sp = ((uintptr_t)stack + size) & ~0xF; + if (ASYNCIFY_CORO_DEBUG) fprintf(stderr, "[%s] entry (context = %p, stack = %p ... %p)\n", __func__, context, stack, (char *)sp); rb_wasm_init_context(&context->fc, coroutine_trampoline, start, context); // record the initial stack pointer position to restore it after resumption - context->current_sp = (char *)stack + size; + context->current_sp = (char *)sp; context->stack_base = stack; context->size = size; } diff --git a/cygwin/GNUmakefile.in b/cygwin/GNUmakefile.in index 43e92a27f0..f342d2fcf7 100644 --- a/cygwin/GNUmakefile.in +++ b/cygwin/GNUmakefile.in @@ -2,6 +2,9 @@ gnumake = yes include Makefile +MUNICODE_FLAG := $(if $(filter mingw%,$(target_os)),-municode) +override EXE_LDFLAGS += $(MUNICODE_FLAG) + DLLWRAP = @DLLWRAP@ --target=$(target_os) --driver-name="$(CC)" windres-cpp := $(CPP) -xc windres-cpp := --preprocessor=$(firstword $(windres-cpp)) \ @@ -63,7 +66,7 @@ $(PROGRAM): $(RUBY_INSTALL_NAME).res.$(OBJEXT) $(WPROGRAM): $(RUBYW_INSTALL_NAME).res.$(OBJEXT) @rm -f $@ $(ECHO) linking $@ - $(Q) $(PURIFY) $(CC) -mwindows -e $(SYMBOL_PREFIX)mainCRTStartup $(LDFLAGS) $(XLDFLAGS) \ + $(Q) $(PURIFY) $(CC) $(MUNICODE_FLAG) -mwindows -e $(SYMBOL_PREFIX)mainCRTStartup $(LDFLAGS) $(XLDFLAGS) \ $(MAINOBJ) $(EXTOBJS) $(LIBRUBYARG) $(LIBS) -o $@ $(STUBPROGRAM): $(RUBY_INSTALL_NAME).res.$(OBJEXT) diff --git a/debug_counter.h b/debug_counter.h index b0047685f0..6e0b8dee60 100644 --- a/debug_counter.h +++ b/debug_counter.h @@ -243,6 +243,7 @@ RB_DEBUG_COUNTER(obj_wb_unprotect) RB_DEBUG_COUNTER(obj_obj_embed) RB_DEBUG_COUNTER(obj_obj_transient) RB_DEBUG_COUNTER(obj_obj_ptr) +RB_DEBUG_COUNTER(obj_obj_too_complex) RB_DEBUG_COUNTER(obj_str_ptr) RB_DEBUG_COUNTER(obj_str_embed) diff --git a/defs/gmake.mk b/defs/gmake.mk index dc9d31f49e..54fef6685f 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -27,7 +27,7 @@ TEST_DEPENDS := $(filter-out commit $(TEST_TARGETS),$(MAKECMDGOALS)) TEST_TARGETS := $(patsubst great,exam,$(TEST_TARGETS)) TEST_DEPENDS := $(filter-out great $(TEST_TARGETS),$(TEST_DEPENDS)) TEST_TARGETS := $(patsubst exam,check,$(TEST_TARGETS)) -TEST_TARGETS := $(patsubst check,test-spec test-all test-tool test-short,$(TEST_TARGETS)) +TEST_TARGETS := $(patsubst check,test-syntax-suggest test-spec test-all test-tool test-short,$(TEST_TARGETS)) TEST_TARGETS := $(patsubst test-rubyspec,test-spec,$(TEST_TARGETS)) TEST_DEPENDS := $(filter-out exam check test-spec $(TEST_TARGETS),$(TEST_DEPENDS)) TEST_TARGETS := $(patsubst love,check,$(TEST_TARGETS)) @@ -40,6 +40,7 @@ TEST_TARGETS := $(patsubst test-short,btest-ruby test-knownbug test-basic,$(TEST TEST_TARGETS := $(patsubst test-bundled-gems,test-bundled-gems-run,$(TEST_TARGETS)) TEST_TARGETS := $(patsubst test-bundled-gems-run,test-bundled-gems-run $(PREPARE_BUNDLED_GEMS),$(TEST_TARGETS)) TEST_TARGETS := $(patsubst test-bundled-gems-prepare,test-bundled-gems-prepare $(PRECHECK_BUNDLED_GEMS) test-bundled-gems-fetch,$(TEST_TARGETS)) +TEST_TARGETS := $(patsubst test-syntax-suggest,test-syntax-suggest $(PREPARE_SYNTAX_SUGGEST),$(TEST_TARGETS)) TEST_DEPENDS := $(filter-out test-short $(TEST_TARGETS),$(TEST_DEPENDS)) TEST_DEPENDS += $(if $(filter great exam love check,$(MAKECMDGOALS)),all exts) endif @@ -83,7 +84,8 @@ endif ORDERED_TEST_TARGETS := $(filter $(TEST_TARGETS), \ btest-ruby test-knownbug test-basic \ test-testframework test-tool test-ruby test-all \ - test-spec test-bundler-prepare test-bundler test-bundler-parallel \ + test-spec test-syntax-suggest-prepare test-syntax-suggest \ + test-bundler-prepare test-bundler test-bundler-parallel \ test-bundled-gems-precheck test-bundled-gems-fetch \ test-bundled-gems-prepare test-bundled-gems-run \ ) diff --git a/defs/id.def b/defs/id.def index a3a383f532..ebf00506ea 100644 --- a/defs/id.def +++ b/defs/id.def @@ -76,6 +76,7 @@ firstline, predefined = __LINE__+1, %[\ "/*NULL*/" NULL empty? eql? + default respond_to? Respond_to respond_to_missing? Respond_to_missing <IFUNC> diff --git a/doc/.document b/doc/.document index 5ef2d99651..f589dda07c 100644 --- a/doc/.document +++ b/doc/.document @@ -6,3 +6,4 @@ NEWS syntax optparse rdoc +yjit diff --git a/doc/encodings.rdoc b/doc/encodings.rdoc index c61ab11e9a..1f3c54d740 100644 --- a/doc/encodings.rdoc +++ b/doc/encodings.rdoc @@ -467,12 +467,13 @@ These keyword-value pairs specify encoding options: with a carriage-return character (<tt>"\r"</tt>). - <tt>:crlf_newline: true</tt>: Replace each line-feed character (<tt>"\n"</tt>) with a carriage-return/line-feed string (<tt>"\r\n"</tt>). - - <tt>:universal_newline: true</tt>: Replace each carriage-return/line-feed string + - <tt>:universal_newline: true</tt>: Replace each carriage-return + character (<tt>"\r"</tt>) and each carriage-return/line-feed string (<tt>"\r\n"</tt>) with a line-feed character (<tt>"\n"</tt>). Examples: - s = "\n \r\n" # => "\n \r\n" - s.encode('ASCII', cr_newline: true) # => "\r \r\r" - s.encode('ASCII', crlf_newline: true) # => "\r\n \r\r\n" - s.encode('ASCII', universal_newline: true) # => "\n \n" + s = "\n \r \r\n" # => "\n \r \r\n" + s.encode('ASCII', cr_newline: true) # => "\r \r \r\r" + s.encode('ASCII', crlf_newline: true) # => "\r\n \r \r\r\n" + s.encode('ASCII', universal_newline: true) # => "\n \n \n" diff --git a/doc/mjit.md b/doc/mjit.md deleted file mode 100644 index 9622a3bc26..0000000000 --- a/doc/mjit.md +++ /dev/null @@ -1,78 +0,0 @@ -# MJIT - -This document has some tips that might be useful when you work on MJIT. - -## Supported platforms - -The following platforms are either tested on CI or assumed to work. - -* OS: Linux, macOS -* Arch: x86\_64, aarch64, arm64, i686, i386 - -### Not supported - -The MJIT support for the following platforms is no longer maintained. - -* OS: Windows (mswin, MinGW), Solaris -* Arch: SPARC, s390x - -### Architectures - -## Bindgen - -If you see an "MJIT bindgen" GitHub Actions failure, please commit the `git diff` shown on the failed job. - -Refer to the following instructions for doing the same thing locally. -Similar to `make yjit-bindgen`, `make mjit-bindgen` requires libclang. -See also: [mjit-bindgen.yml](../.github/workflows/mjit-bindgen.yml) - -macOS seems to have libclang by default, but I'm not sure how to deal with 32bit architectures. -For now, you may generate c\_64.rb with a 64bit binary, and then manually modify c\_32.rb accordingly. - -### x86\_64-linux - -```sh -sudo apt install \ - build-essential \ - libssl-dev libyaml-dev libreadline6-dev \ - zlib1g-dev libncurses5-dev libffi-dev \ - libclang1 -./autogen.sh -./configure --enable-yjit=dev_nodebug --disable-install-doc -make -j -make mjit-bindgen -``` - -### i686-linux - -```sh -sudo dpkg --add-architecture i386 -sudo apt install \ - crossbuild-essential:i386 \ - libssl-dev:i386 libyaml-dev:i386 libreadline6-dev:i386 \ - zlib1g-dev:i386 libncurses5-dev:i386 libffi-dev:i386 \ - libclang1:i386 -./autogen.sh -./configure --disable-install-doc -make -j -make mjit-bindgen -``` - -Note that you cannot run x86\_64 bindgen with an i686 binary, and vice versa. -Also, when you install libclang1:i386, libclang1 will be uninstalled. -You can have only either of these at a time. - -## Local development - -### Always run make install - -Always run `make install` before running MJIT. It could easily cause a SEGV if you don't. -MJIT looks for the installed header for security reasons. - -### --mjit-debug vs --mjit-debug=-ggdb3 - -`--mjit-debug=[flags]` allows you to specify arbitrary flags while keeping other compiler flags like `-O3`, -which is useful for profiling benchmarks. - -`--mjit-debug` alone, on the other hand, disables `-O3` and adds debug flags. -If you're debugging MJIT, what you need to use is not `--mjit-debug=-ggdb3` but `--mjit-debug`. diff --git a/doc/mjit/mjit.md b/doc/mjit/mjit.md new file mode 100644 index 0000000000..6f19ab3ea7 --- /dev/null +++ b/doc/mjit/mjit.md @@ -0,0 +1,39 @@ +# MJIT + +This document has some tips that might be useful when you work on MJIT. + +## Supported platforms + +The following platforms are either tested on CI or assumed to work. + +* OS: Linux, macOS +* Arch: x86\_64, aarch64, arm64, i686, i386 + +### Not supported + +The MJIT support for the following platforms is no longer maintained. + +* OS: Windows (mswin, MinGW), Solaris +* Arch: SPARC, s390x + +## Developing MJIT + +### Bindgen + +If you see an "MJIT bindgen" GitHub Actions failure, please commit the `git diff` shown on the failed job. + +For doing the same thing locally, run `make mjit-bindgen` after installing libclang. +macOS seems to have libclang by default. On Ubuntu, you can install it with `apt install libclang1`. + +### Always run make install + +Always run `make install` before running MJIT. It could easily cause a SEGV if you don't. +MJIT looks for the installed header for security reasons. + +### --mjit-debug vs --mjit-debug=-ggdb3 + +`--mjit-debug=[flags]` allows you to specify arbitrary flags while keeping other compiler flags like `-O3`, +which is useful for profiling benchmarks. + +`--mjit-debug` alone, on the other hand, disables `-O3` and adds debug flags. +If you're debugging MJIT, what you need to use is not `--mjit-debug=-ggdb3` but `--mjit-debug`. diff --git a/doc/net-http/examples.rdoc b/doc/net-http/examples.rdoc index dd4acecda6..c1366e7ad1 100644 --- a/doc/net-http/examples.rdoc +++ b/doc/net-http/examples.rdoc @@ -10,9 +10,10 @@ Many code examples here use these example websites: Some examples also assume these variables: - uri = URI('https://jsonplaceholder.typicode.com') + uri = URI('https://jsonplaceholder.typicode.com/') uri.freeze # Examples may not modify. hostname = uri.hostname # => "jsonplaceholder.typicode.com" + path = uri.path # => "/" port = uri.port # => 443 So that example requests may be written as: diff --git a/doc/net-http/included_getters.rdoc b/doc/net-http/included_getters.rdoc new file mode 100644 index 0000000000..7ac327f4b4 --- /dev/null +++ b/doc/net-http/included_getters.rdoc @@ -0,0 +1,3 @@ +This class also includes (indirectly) module Net::HTTPHeader, +which gives access to its +{methods for getting headers}[rdoc-ref:Net::HTTPHeader@Getters]. diff --git a/doc/packed_data.rdoc b/doc/packed_data.rdoc index 9ba585c90b..ec13b24c69 100644 --- a/doc/packed_data.rdoc +++ b/doc/packed_data.rdoc @@ -60,6 +60,10 @@ Any directive may be followed by either of these modifiers: Note: Directives in <tt>%w[A a Z m]</tt> use +count+ differently; see {String Directives}[rdoc-ref:packed_data.rdoc@String+Directives]. +If elements don't fit the provided directive, only least significant bits are encoded: + + [257].pack("C").unpack("C") # => [1] + === Packing \Method \Method Array#pack accepts optional keyword argument diff --git a/doc/syntax/literals.rdoc b/doc/syntax/literals.rdoc index 5e10e6a140..b641433249 100644 --- a/doc/syntax/literals.rdoc +++ b/doc/syntax/literals.rdoc @@ -277,6 +277,12 @@ the content. Note that empty lines and lines consisting solely of literal tabs and spaces will be ignored for the purposes of determining indentation, but escaped tabs and spaces are considered non-indentation characters. +For the purpose of measuring an indentation, a horizontal tab is regarded as a +sequence of one to eight spaces such that the column position corresponding to +its end is a multiple of eight. The amount to be removed is counted in terms +of the number of spaces. If the boundary appears in the middle of a tab, that +tab is not removed. + A heredoc allows interpolation and escaped characters. You may disable interpolation and escaping by surrounding the opening identifier with single quotes: diff --git a/doc/yjit/yjit.md b/doc/yjit/yjit.md index 1af5aeb417..67b2ffa5f0 100644 --- a/doc/yjit/yjit.md +++ b/doc/yjit/yjit.md @@ -9,9 +9,9 @@ YJIT - Yet Another Ruby JIT =========================== YJIT is a lightweight, minimalistic Ruby JIT built inside CRuby. -It lazily compiles code using a Basic Block Versioning (BBV) architecture. The target use case is that of servers running -Ruby on Rails, an area where MJIT has not yet managed to deliver speedups. -YJIT is currently supported for macOS and Linux on x86-64 and arm64/aarch64 CPUs. +It lazily compiles code using a Basic Block Versioning (BBV) architecture. +The target use case is that of servers running Ruby on Rails. +YJIT is currently supported for macOS, Linux and BSD on x86-64 and arm64/aarch64 CPUs. This project is open source and falls under the same license as CRuby. <p align="center"><b> @@ -20,6 +20,8 @@ This project is open source and falls under the same license as CRuby. </b></p> If you wish to learn more about the approach taken, here are some conference talks and publications: +- RubyKaigi 2022 keynote: [Stories from developing YJIT](https://www.youtube.com/watch?v=EMchdR9C8XM) +- RubyKaigi 2022 talk: [Building a Lightweight IR and Backend for YJIT](https://www.youtube.com/watch?v=BbLGqTxTRp0) - RubyKaigi 2021 talk: [YJIT: Building a New JIT Compiler Inside CRuby](https://www.youtube.com/watch?v=PBVLf3yfMs8) - Blog post: [YJIT: Building a New JIT Compiler Inside CRuby](https://pointersgonewild.com/2021/06/02/yjit-building-a-new-jit-compiler-inside-cruby/) - VMIL 2021 paper: [YJIT: A Basic Block Versioning JIT Compiler for CRuby](https://dl.acm.org/doi/10.1145/3486606.3486781) @@ -52,8 +54,7 @@ series = {VMIL 2021} ## Current Limitations -YJIT may not be suitable for certain applications. It currently only supports macOS and Linux on x86-64 and arm64/aarch64 CPUs. YJIT will use more memory than the Ruby interpreter because the JIT compiler needs to generate machine code in memory and maintain additional state -information. +YJIT may not be suitable for certain applications. It currently only supports macOS and Linux on x86-64 and arm64/aarch64 CPUs. YJIT will use more memory than the Ruby interpreter because the JIT compiler needs to generate machine code in memory and maintain additional state information. You can change how much executable memory is allocated using [YJIT's command-line options](#command-line-options). There is a slight performance tradeoff because allocating less executable memory could result in the generated machine code being collected more often. ## Installation @@ -75,44 +76,53 @@ To install the Rust build toolchain, we suggest following the [recommended insta Start by cloning the `ruby/ruby` repository: -``` +```sh git clone https://github.com/ruby/ruby yjit cd yjit ``` -The YJIT `ruby` binary can be built with either GCC or Clang. It can be built either in dev (debug) mode or in release mode. For maximum performance, compile YJIT in release mode with GCC. More detailed build instructions are provided in the [Ruby README](https://github.com/ruby/ruby#how-to-compile-and-install). +The YJIT `ruby` binary can be built with either GCC or Clang. It can be built either in dev (debug) mode or in release mode. For maximum performance, compile YJIT in release mode with GCC. More detailed build instructions are provided in the [Ruby README](https://github.com/ruby/ruby#how-to-build). -``` +```sh # Configure in release mode for maximum performance, build and install ./autogen.sh ./configure --enable-yjit --prefix=$HOME/.rubies/ruby-yjit --disable-install-doc -make -j install +make -j && make install ``` or -``` -# Configure in dev (debug) mode for development, build and install +```sh +# Configure in lower-performance dev (debug) mode for development, build and install ./autogen.sh ./configure --enable-yjit=dev --prefix=$HOME/.rubies/ruby-yjit --disable-install-doc -make -j install +make -j && make install ``` -On macOS, you may need to specify where to find some libraries: +Dev mode includes extended YJIT statistics, but can be slow. For only statistics you can configure in stats mode: +```sh +# Configure in extended-stats mode without slow runtime checks, build and install +./autogen.sh +./configure --enable-yjit=stats --prefix=$HOME/.rubies/ruby-yjit --disable-install-doc +make -j && make install ``` + +On macOS, you may need to specify where to find some libraries: + +```sh # Install dependencies brew install openssl readline libyaml # Configure in dev (debug) mode for development, build and install ./autogen.sh ./configure --enable-yjit=dev --prefix=$HOME/.rubies/ruby-yjit --disable-install-doc --with-opt-dir="$(brew --prefix openssl):$(brew --prefix readline):$(brew --prefix libyaml)" -make -j install +make -j && make install ``` Typically configure will choose the default C compiler. To specify the C compiler, use -``` +```sh # Choosing a specific c compiler export CC=/path/to/my/chosen/c/compiler ``` @@ -121,7 +131,7 @@ before running `./configure`. You can test that YJIT works correctly by running: -``` +```sh # Quick tests found in /bootstraptest make btest @@ -136,14 +146,14 @@ make -j test-all Once YJIT is built, you can either use `./miniruby` from within your build directory, or switch to the YJIT version of `ruby` by using the `chruby` tool: -``` +```sh chruby ruby-yjit ruby myscript.rb ``` You can dump statistics about compilation and execution by running YJIT with the `--yjit-stats` command-line option: -``` +```sh ./miniruby --yjit-stats myscript.rb ``` @@ -155,11 +165,9 @@ YJIT supports all command-line options supported by upstream CRuby, but also add - `--yjit`: enable YJIT (disabled by default) - `--yjit-call-threshold=N`: number of calls after which YJIT begins to compile a function (default 30) -- `--yjit-exec-mem-size=N`: size of the executable memory block to allocate, in MiB (default 128 MiB) -- `--yjit-stats`: produce statistics after the execution of a program -- `--yjit-trace-exits`: produce a Marshal dump of backtraces from specific exits. Automatically enables `--yjit-stats` (must configure and build with `--enable-yjit=stats` to use this) -- `--yjit-max-versions=N`: maximum number of versions to generate per basic block (default 4) -- `--yjit-greedy-versioning`: greedy versioning mode (disabled by default, may increase code size) +- `--yjit-exec-mem-size=N`: size of the executable memory block to allocate, in MiB (default 64 MiB) +- `--yjit-stats`: print statistics after the execution of a program (incurs a run-time cost) +- `--yjit-trace-exits`: produce a Marshal dump of backtraces from specific exits. Automatically enables `--yjit-stats` Note that there is also an environment variable `RUBY_YJIT_ENABLE` which can be used to enable YJIT. This can be useful for some deployment scripts where specifying an extra command-line option to Ruby is not practical. @@ -168,28 +176,79 @@ This can be useful for some deployment scripts where specifying an extra command We have collected a set of benchmarks and implemented a simple benchmarking harness in the [yjit-bench](https://github.com/Shopify/yjit-bench) repository. This benchmarking harness is designed to disable CPU frequency scaling, set process affinity and disable address space randomization so that the variance between benchmarking runs will be as small as possible. Please kindly note that we are at an early stage in this project. -### Performance Tips +## Performance Tips for Production Deployments + +While YJIT options default to what we think would work well for most workloads, +they might not necessarily be the best configuration for your application. + +This section covers tips on improving YJIT performance in case YJIT does not +speed up your application in production. + +### Increasing --yjit-exec-mem-size + +When JIT code size (`RubyVM::YJIT.runtime_stats[:code_region_size]`) reaches this value, +YJIT triggers "code GC" that frees all JIT code and starts recompiling everything. +Compiling code takes some time, so scheduling code GC too frequently slows down your application. +Increasing `--yjit-exec-mem-size` may speed up your application if `RubyVM::YJIT.runtime_stats[:code_gc_count]` is not 0 or 1. + +### Running workers as long as possible + +It's helpful to call the same code as many times as possible before a process restarts. +If a process is killed too frequently, the time taken for compiling methods may outweigh +the speedup obtained by compiling them. + +You should monitor the number of requests each process has served. +If you're periodically killing worker processes, e.g. with `unicorn-worker-killer` or `puma_worker_killer`, +you may want to reduce the killing frequency or increase the limit. + +## Saving YJIT Memory Usage + +YJIT allocates memory for JIT code and metadata. Enabling YJIT generally results in more memory usage. -This section contains tips on writing Ruby code that will run as fast as possible on YJIT. Some of this advice is based on current limitations of YJIT, while other advice is broadly applicable. It probably won't be practical to apply these tips everywhere in your codebase, but you can profile your code using a tool such as [stackprof](https://github.com/tmm1/stackprof) and refactor the specific methods that make up the largest fractions of the execution time. +This section goes over tips on minimizing YJIT memory usage in case it uses more than your capacity. -- Use exceptions for error recovery only, not as part of normal control-flow +### Increasing --yjit-call-threshold + +As of Ruby 3.2, `--yjit-call-threshold` defaults to 30. With this default, some applications end up +compiling methods that are used only during the application boot. Increasing this option may help +you reduce the size of JIT code and metadata. It's worth trying different values like `--yjit-call-threshold=100`. + +Note that increasing the value too much may result in compiling code too late. +You should monitor how many requests each worker processes before it's restarted. For example, +if each process only handles 1000 requests, `--yjit-call-threshold=1000` might be too large for your application. + +### Decreasing --yjit-exec-mem-size + +`--yjit-exec-mem-size` specifies the JIT code size, but YJIT also uses memory for its metadata, +which often consumes more memory than JIT code. Generally, YJIT adds memory overhead by roughly +3-4x of `--yjit-exec-mem-size` in production as of Ruby 3.2. You should multiply that by the number +of worker processes to estimate the worst case memory overhead. + +Running code GC adds overhead, but it could be still faster than recovering from a whole process killed by OOM. + +## Code Optimization Tips + +This section contains tips on writing Ruby code that will run as fast as possible on YJIT. Some of this advice is based on current limitations of YJIT, while other advice is broadly applicable. It probably won't be practical to apply these tips everywhere in your codebase. You should ideally start by profiling your application using a tool such as [stackprof](https://github.com/tmm1/stackprof) so that you can determine which methods make up most of the execution time. You can then refactor the specific methods that make up the largest fractions of the execution time. We do not recommend modifying your entire codebase based on the current limitations of YJIT. + +- Avoid using `OpenStruct` - Avoid redefining basic integer operations (i.e. +, -, <, >, etc.) - Avoid redefining the meaning of `nil`, equality, etc. - Avoid allocating objects in the hot parts of your code -- Use while loops if you can, instead of `integer.times` - Minimize layers of indirection - Avoid classes that wrap objects if you can - Avoid methods that just call another method, trivial one liner methods -- CRuby method calls are costly. Favor larger methods over smaller methods. - Try to write code so that the same variables always have the same type +- Use `while` loops if you can, instead of C methods like `Array#each` + - This is not idiomatic Ruby, but could help in hot methods +- CRuby method calls are costly. Avoid things such as methods that only return a value from a hash or return a constant. You can also use the `--yjit-stats` command-line option to see which bytecodes cause YJIT to exit, and refactor your code to avoid using these instructions in the hottest methods of your code. ### Other Statistics -If you compile Ruby with `RUBY_DEBUG` and/or `YJIT_STATS` defined and run with `--yjit --yjit-stats`, YJIT will track and return performance statistics in `RubyVM::YJIT.runtime_stats`. +If you run `ruby` with `--yjit --yjit-stats`, YJIT will track and return performance statistics in `RubyVM::YJIT.runtime_stats`. -``` +```rb $ RUBYOPT="--yjit --yjit-stats" irb irb(main):001:0> RubyVM::YJIT.runtime_stats => @@ -205,16 +264,24 @@ irb(main):001:0> RubyVM::YJIT.runtime_stats Some of the counters include: -:exec_instruction - how many Ruby bytecode instructions have been executed -:binding_allocations - number of bindings allocated -:binding_set - number of variables set via a binding -:vm_insns_count - number of instructions executed by the Ruby interpreter -:compiled_iseq_count - number of bytecode sequences compiled +* :exec_instruction - how many Ruby bytecode instructions have been executed +* :binding_allocations - number of bindings allocated +* :binding_set - number of variables set via a binding +* :code_gc_count - number of garbage collections of compiled code since process start +* :vm_insns_count - number of instructions executed by the Ruby interpreter +* :compiled_iseq_count - number of bytecode sequences compiled +* :inline_code_size - size in bytes of compiled YJIT blocks +* :outline_code_size - size in bytes of YJIT error-handling compiled code +* :side_exit_count - number of side exits taken at runtime +* :total_exit_count - number of exits, including side exits, taken at runtime +* :avg_len_in_yjit - avg. number of instructions in compiled blocks before exiting to interpreter Counters starting with "exit_" show reasons for YJIT code taking a side exit (return to the interpreter.) See yjit_hacking.md for more details. Performance counter names are not guaranteed to remain the same between Ruby versions. If you're curious what one does, it's usually best to search the source code for it — but it may change in a later Ruby version. +The printed text after a --yjit-stats run includes other information that may be named differently than the information in runtime_stats. + ## Contributing We welcome open source contributors. You should feel free to open new issues to report bugs or just to ask questions. @@ -242,8 +309,6 @@ The YJIT source code is divided between: - `yjit/src/options.rs`: handling of command-line options - `yjit/bindgen/src/main.rs`: C bindings exposed to the Rust codebase through bindgen - `yjit/src/cruby.rs`: C bindings manually exposed to the Rust codebase -- `misc/test_yjit_asm.sh`: script to compile and run the in-memory assembler tests -- `misc/yjit_asm_tests.c`: tests for the in-memory assembler The core of CRuby's interpreter logic is found in: - `insns.def`: defines Ruby's bytecode instructions (gets compiled into `vm.inc`) @@ -274,38 +339,38 @@ There are 3 test suites: The tests can be run in parallel like this: -``` +```sh make -j test-all RUN_OPTS="--yjit-call-threshold=1" ``` Or single-threaded like this, to more easily identify which specific test is failing: -``` +```sh make test-all TESTOPTS=--verbose RUN_OPTS="--yjit-call-threshold=1" ``` To debug a single test in `test-all`: -``` +```sh make test-all TESTS='test/-ext-/marshal/test_usrmarshal.rb' RUNRUBYOPT=--debugger=lldb RUN_OPTS="--yjit-call-threshold=1" ``` You can also run one specific test in `btest`: -``` +```sh make btest BTESTS=bootstraptest/test_ractor.rb RUN_OPTS="--yjit-call-threshold=1" ``` There are shortcuts to run/debug your own test/repro in `test.rb`: -``` +```sh make run # runs ./miniruby test.rb make lldb # launches ./miniruby test.rb in lldb ``` You can use the Intel syntax for disassembly in LLDB, keeping it consistent with YJIT's disassembly: -``` +```sh echo "settings set target.x86-disassembly-flavor intel" >> ~/.lldbinit ``` @@ -316,7 +381,7 @@ instructions below, but there are a few caveats listed further down. First, install Rosetta: -``` +```sh $ softwareupdate --install-rosetta ``` @@ -324,13 +389,13 @@ Now any command can be run with Rosetta via the `arch` command line tool. Then you can start your shell in an x86 environment: -``` +```sh $ arch -x86_64 zsh ``` You can double check your current architecture via the `arch` command: -``` +```sh $ arch -x86_64 zsh $ arch i386 @@ -338,7 +403,7 @@ i386 You may need to set the default target for `rustc` to x86-64, e.g. -``` +```sh $ rustup default stable-x86_64-apple-darwin ``` diff --git a/enc/Makefile.in b/enc/Makefile.in index dd8ca1b528..9d0c367134 100644 --- a/enc/Makefile.in +++ b/enc/Makefile.in @@ -51,7 +51,7 @@ optflags = @optflags@ debugflags = @debugflags@ warnflags = @warnflags@ CCDLFLAGS = @CCDLFLAGS@ -INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir) -I$(top_srcdir) +INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir) -I$(top_srcdir) @incflags@ DEFS = @DEFS@ CPPFLAGS = @CPPFLAGS@ -DONIG_ENC_REGISTER=rb_enc_register LDFLAGS = @LDFLAGS@ diff --git a/enc/cesu_8.c b/enc/cesu_8.c index decbb928f4..75f62df280 100644 --- a/enc/cesu_8.c +++ b/enc/cesu_8.c @@ -42,6 +42,8 @@ #define VALID_CODE_LIMIT 0x0010ffff #define utf8_islead(c) ((UChar )((c) & 0xc0) != 0x80) +#define utf16_is_high_surrogate(v) ((v >> 10) == 0x36) +#define utf16_is_low_surrogate(v) ((v >> 10) == 0x37) static const int EncLen_CESU8[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -283,6 +285,12 @@ is_mbc_newline(const UChar* p, const UChar* end, OnigEncoding enc) return 0; } +static int +utf8_decode_3byte_sequence(const UChar* p) +{ + return ((p[0] & 0xF) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f); +} + static OnigCodePoint mbc_to_code(const UChar* p, const UChar* end, OnigEncoding enc) { @@ -295,11 +303,11 @@ mbc_to_code(const UChar* p, const UChar* end, OnigEncoding enc) case 2: return ((p[0] & 0x1F) << 6) | (p[1] & 0x3f); case 3: - return ((p[0] & 0xF) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f); + return utf8_decode_3byte_sequence(p); case 6: { - int high = ((p[0] & 0xF) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f); - int low = ((p[3] & 0xF) << 12) | ((p[4] & 0x3f) << 6) | (p[5] & 0x3f); + int high = utf8_decode_3byte_sequence(p); + int low = utf8_decode_3byte_sequence(p + 3); return ((high & 0x03ff) << 10) + (low & 0x03ff) + 0x10000; } } @@ -410,7 +418,6 @@ get_ctype_code_range(OnigCtype ctype, OnigCodePoint *sb_out, return onigenc_unicode_ctype_code_range(ctype, ranges); } - static UChar* left_adjust_char_head(const UChar* start, const UChar* s, const UChar* end, OnigEncoding enc ARG_UNUSED) { @@ -420,6 +427,14 @@ left_adjust_char_head(const UChar* start, const UChar* s, const UChar* end, Onig p = s; while (!utf8_islead(*p) && p > start) p--; + + if (p > start && s - p == 2 && utf16_is_low_surrogate(utf8_decode_3byte_sequence(p))) { + const UChar *p_surrogate_pair = p - 1; + while (!utf8_islead(*p_surrogate_pair) && p_surrogate_pair > start) p_surrogate_pair--; + if (p - p_surrogate_pair == 3 && utf16_is_high_surrogate(utf8_decode_3byte_sequence(p_surrogate_pair))) { + return (UChar* )p_surrogate_pair; + } + } return (UChar* )p; } diff --git a/enc/depend b/enc/depend index 60c5a3ebb2..973ad93010 100644 --- a/enc/depend +++ b/enc/depend @@ -7018,6 +7018,7 @@ enc/trans/iso2022.$(OBJEXT): internal/attr/nodiscard.h enc/trans/iso2022.$(OBJEXT): internal/attr/noexcept.h enc/trans/iso2022.$(OBJEXT): internal/attr/noinline.h enc/trans/iso2022.$(OBJEXT): internal/attr/nonnull.h +enc/trans/iso2022.$(OBJEXT): internal/attr/nonstring.h enc/trans/iso2022.$(OBJEXT): internal/attr/noreturn.h enc/trans/iso2022.$(OBJEXT): internal/attr/pure.h enc/trans/iso2022.$(OBJEXT): internal/attr/restrict.h diff --git a/encoding.c b/encoding.c index b8e7f790b8..2f4b47bdfa 100644 --- a/encoding.c +++ b/encoding.c @@ -56,9 +56,8 @@ int rb_encdb_alias(const char *alias, const char *orig); static ID id_encoding; VALUE rb_cEncoding; -#define DEFAULT_ENCODING_LIST_CAPA 128 -static VALUE rb_default_encoding_list; -static VALUE rb_additional_encoding_list; +#define ENCODING_LIST_CAPA 256 +static VALUE rb_encoding_list; struct rb_encoding_entry { const char *name; @@ -67,9 +66,8 @@ struct rb_encoding_entry { }; static struct enc_table { - struct rb_encoding_entry *list; + struct rb_encoding_entry list[ENCODING_LIST_CAPA]; int count; - int size; st_table *names; } global_enc_table; @@ -128,47 +126,25 @@ enc_new(rb_encoding *encoding) static void enc_list_update(int index, rb_raw_encoding *encoding) { - if (index < DEFAULT_ENCODING_LIST_CAPA) { - VALUE list = rb_default_encoding_list; - if (list && NIL_P(rb_ary_entry(list, index))) { - /* initialize encoding data */ - rb_ary_store(list, index, enc_new(encoding)); - } - } - else { - RB_VM_LOCK_ENTER(); - { - VALUE list = rb_additional_encoding_list; - if (list && NIL_P(rb_ary_entry(list, index))) { - /* initialize encoding data */ - rb_ary_store(list, index - DEFAULT_ENCODING_LIST_CAPA, enc_new(encoding)); - } - } - RB_VM_LOCK_LEAVE(); + RUBY_ASSERT(index < ENCODING_LIST_CAPA); + + VALUE list = rb_encoding_list; + if (list && NIL_P(rb_ary_entry(list, index))) { + /* initialize encoding data */ + rb_ary_store(list, index, enc_new(encoding)); } } static VALUE enc_list_lookup(int idx) { - VALUE list, enc; + VALUE list, enc = Qnil; - if (idx < DEFAULT_ENCODING_LIST_CAPA) { - if (!(list = rb_default_encoding_list)) { - rb_bug("rb_enc_from_encoding_index(%d): no rb_default_encoding_list", idx); - } + if (idx < ENCODING_LIST_CAPA) { + list = rb_encoding_list; + RUBY_ASSERT(list); enc = rb_ary_entry(list, idx); } - else { - RB_VM_LOCK_ENTER(); - { - if (!(list = rb_additional_encoding_list)) { - rb_bug("rb_enc_from_encoding_index(%d): no rb_additional_encoding_list", idx); - } - enc = rb_ary_entry(list, idx - DEFAULT_ENCODING_LIST_CAPA); - } - RB_VM_LOCK_LEAVE(); - } if (NIL_P(enc)) { rb_bug("rb_enc_from_encoding_index(%d): not created yet", idx); @@ -345,16 +321,10 @@ rb_find_encoding(VALUE enc) static int enc_table_expand(struct enc_table *enc_table, int newsize) { - struct rb_encoding_entry *ent; - int count = newsize; - - if (enc_table->size >= newsize) return newsize; - newsize = (newsize + 7) / 8 * 8; - ent = REALLOC_N(enc_table->list, struct rb_encoding_entry, newsize); - memset(ent + enc_table->size, 0, sizeof(*ent)*(newsize - enc_table->size)); - enc_table->list = ent; - enc_table->size = newsize; - return count; + if (newsize > ENCODING_LIST_CAPA) { + rb_raise(rb_eEncodingError, "too many encoding (> %d)", ENCODING_LIST_CAPA); + } + return newsize; } static int @@ -413,17 +383,7 @@ enc_from_index(struct enc_table *enc_table, int index) rb_encoding * rb_enc_from_index(int index) { - rb_encoding *enc; - - switch (index) { - case ENCINDEX_ASCII_8BIT: return global_enc_ascii; - case ENCINDEX_UTF_8: return global_enc_utf_8; - case ENCINDEX_US_ASCII: return global_enc_us_ascii; - default: - GLOBAL_ENC_TABLE_EVAL(enc_table, - enc = enc_from_index(enc_table, index)); - return enc; - } + return enc_from_index(&global_enc_table, index); } int @@ -462,7 +422,7 @@ enc_registered(struct enc_table *enc_table, const char *name) st_data_t idx = 0; if (!name) return -1; - if (!enc_table->list) return -1; + if (!enc_table->names) return -1; if (st_lookup(enc_table->names, (st_data_t)name, &idx)) { return (int)idx; } @@ -484,11 +444,14 @@ rb_encdb_declare(const char *name) } static void -enc_check_duplication(struct enc_table *enc_table, const char *name) +enc_check_addable(struct enc_table *enc_table, const char *name) { if (enc_registered(enc_table, name) >= 0) { rb_raise(rb_eArgError, "encoding %s is already registered", name); } + else if (!valid_encoding_name_p(name)) { + rb_raise(rb_eArgError, "invalid encoding name: %s", name); + } } static rb_encoding* @@ -524,11 +487,7 @@ rb_enc_set_base(const char *name, const char *orig) int rb_enc_set_dummy(int index) { - rb_encoding *enc; - - GLOBAL_ENC_TABLE_EVAL(enc_table, - enc = enc_table->list[index].enc); - + rb_encoding *enc = global_enc_table.list[index].enc; ENC_SET_DUMMY((rb_raw_encoding *)enc); return index; } @@ -538,7 +497,7 @@ enc_replicate(struct enc_table *enc_table, const char *name, rb_encoding *encodi { int idx; - enc_check_duplication(enc_table, name); + enc_check_addable(enc_table, name); idx = enc_register(enc_table, name, encoding); if (idx < 0) rb_raise(rb_eArgError, "invalid encoding name: %s", name); set_base_encoding(enc_table, idx, encoding); @@ -727,7 +686,7 @@ rb_enc_alias(const char *alias, const char *orig) GLOBAL_ENC_TABLE_ENTER(enc_table); { - enc_check_duplication(enc_table, alias); + enc_check_addable(enc_table, alias); if ((idx = rb_enc_find_index(orig)) < 0) { r = -1; } @@ -764,7 +723,7 @@ rb_enc_init(struct enc_table *enc_table) { enc_table_expand(enc_table, ENCODING_COUNT + 1); if (!enc_table->names) { - enc_table->names = st_init_strcasetable(); + enc_table->names = st_init_strcasetable_with_size(ENCODING_LIST_CAPA); } #define OnigEncodingASCII_8BIT OnigEncodingASCII #define ENC_REGISTER(enc) enc_register_at(enc_table, ENCINDEX_##enc, rb_enc_name(&OnigEncoding##enc), &OnigEncoding##enc) @@ -877,11 +836,9 @@ rb_enc_autoload(rb_encoding *enc) int rb_enc_find_index(const char *name) { - int i; + int i = enc_registered(&global_enc_table, name); rb_encoding *enc; - GLOBAL_ENC_TABLE_EVAL(enc_table, i = enc_registered(enc_table, name)); - if (i < 0) { i = load_encoding(name); } @@ -1368,10 +1325,7 @@ enc_names(VALUE self) args[0] = (VALUE)rb_to_encoding_index(self); args[1] = rb_ary_new2(0); - - GLOBAL_ENC_TABLE_EVAL(enc_table, - st_foreach(enc_table->names, enc_names_i, (st_data_t)args)); - + st_foreach(global_enc_table.names, enc_names_i, (st_data_t)args); return args[1]; } @@ -1397,14 +1351,7 @@ static VALUE enc_list(VALUE klass) { VALUE ary = rb_ary_new2(0); - - RB_VM_LOCK_ENTER(); - { - rb_ary_replace(ary, rb_default_encoding_list); - rb_ary_concat(ary, rb_additional_encoding_list); - } - RB_VM_LOCK_LEAVE(); - + rb_ary_replace(ary, rb_encoding_list); return ary; } @@ -1553,15 +1500,17 @@ rb_locale_encindex(void) if (idx < 0) idx = ENCINDEX_UTF_8; - GLOBAL_ENC_TABLE_ENTER(enc_table); - if (enc_registered(enc_table, "locale") < 0) { + if (enc_registered(&global_enc_table, "locale") < 0) { # if defined _WIN32 void Init_w32_codepage(void); Init_w32_codepage(); # endif - enc_alias_internal(enc_table, "locale", idx); + GLOBAL_ENC_TABLE_ENTER(enc_table); + { + enc_alias_internal(enc_table, "locale", idx); + } + GLOBAL_ENC_TABLE_LEAVE(); } - GLOBAL_ENC_TABLE_LEAVE(); return idx; } @@ -1575,13 +1524,8 @@ rb_locale_encoding(void) int rb_filesystem_encindex(void) { - int idx; - - GLOBAL_ENC_TABLE_EVAL(enc_table, - idx = enc_registered(enc_table, "filesystem")); - - if (idx < 0) - idx = ENCINDEX_ASCII_8BIT; + int idx = enc_registered(&global_enc_table, "filesystem"); + if (idx < 0) idx = ENCINDEX_ASCII_8BIT; return idx; } @@ -1612,7 +1556,14 @@ enc_set_default_encoding(struct default_encoding *def, VALUE encoding, const cha if (NIL_P(encoding)) { def->index = -1; def->enc = 0; - st_insert(enc_table->names, (st_data_t)strdup(name), + char *name_dup = strdup(name); + + st_data_t existing_name = (st_data_t)name_dup; + if (st_delete(enc_table->names, &existing_name, NULL)) { + xfree((void *)existing_name); + } + + st_insert(enc_table->names, (st_data_t)name_dup, (st_data_t)UNSPECIFIED_ENCODING); } else { @@ -1872,15 +1823,8 @@ rb_enc_name_list_i(st_data_t name, st_data_t idx, st_data_t arg) static VALUE rb_enc_name_list(VALUE klass) { - VALUE ary; - - GLOBAL_ENC_TABLE_ENTER(enc_table); - { - ary = rb_ary_new2(enc_table->names->num_entries); - st_foreach(enc_table->names, rb_enc_name_list_i, (st_data_t)ary); - } - GLOBAL_ENC_TABLE_LEAVE(); - + VALUE ary = rb_ary_new2(global_enc_table.names->num_entries); + st_foreach(global_enc_table.names, rb_enc_name_list_i, (st_data_t)ary); return ary; } @@ -1926,8 +1870,7 @@ rb_enc_aliases(VALUE klass) aliases[0] = rb_hash_new(); aliases[1] = rb_ary_new(); - GLOBAL_ENC_TABLE_EVAL(enc_table, - st_foreach(enc_table->names, rb_enc_aliases_enc_i, (st_data_t)aliases)); + st_foreach(global_enc_table.names, rb_enc_aliases_enc_i, (st_data_t)aliases); return aliases[0]; } @@ -1996,13 +1939,7 @@ Init_Encoding(void) struct enc_table *enc_table = &global_enc_table; - if (DEFAULT_ENCODING_LIST_CAPA < enc_table->count) rb_bug("DEFAULT_ENCODING_LIST_CAPA is too small"); - - list = rb_additional_encoding_list = rb_ary_new(); - RBASIC_CLEAR_CLASS(list); - rb_gc_register_mark_object(list); - - list = rb_default_encoding_list = rb_ary_new2(DEFAULT_ENCODING_LIST_CAPA); + list = rb_encoding_list = rb_ary_new2(ENCODING_LIST_CAPA); RBASIC_CLEAR_CLASS(list); rb_gc_register_mark_object(list); @@ -2024,5 +1961,5 @@ Init_encodings(void) void rb_enc_foreach_name(int (*func)(st_data_t name, st_data_t idx, st_data_t arg), st_data_t arg) { - GLOBAL_ENC_TABLE_EVAL(enc_table, st_foreach(enc_table->names, func, arg)); + st_foreach(global_enc_table.names, func, arg); } @@ -4459,7 +4459,7 @@ sum_iter(VALUE i, struct enum_sum_memo *memo) } else switch (TYPE(memo->v)) { default: sum_iter_some_value(i, memo); return; - case T_FLOAT: sum_iter_Kahan_Babuska(i, memo); return; + case T_FLOAT: case T_FIXNUM: case T_BIGNUM: case T_RATIONAL: diff --git a/enumerator.c b/enumerator.c index a0c51b585c..d587b63d32 100644 --- a/enumerator.c +++ b/enumerator.c @@ -20,6 +20,7 @@ #include "id.h" #include "internal.h" +#include "internal/class.h" #include "internal/enumerator.h" #include "internal/error.h" #include "internal/hash.h" @@ -72,6 +73,8 @@ * puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" } * # => ["0:foo", "1:bar", "2:baz"] * + * == External Iteration + * * An Enumerator can also be used as an external iterator. * For example, Enumerator#next returns the next value of the iterator * or raises StopIteration if the Enumerator is at the end. @@ -82,15 +85,44 @@ * puts e.next # => 3 * puts e.next # raises StopIteration * - * Note that enumeration sequence by +next+, +next_values+, +peek+ and - * +peek_values+ do not affect other non-external - * enumeration methods, unless the underlying iteration method itself has - * side-effect, e.g. IO#each_line. + * +next+, +next_values+, +peek+ and +peek_values+ are the only methods + * which use external iteration (and Array#zip(Enumerable-not-Array) which uses +next+). + * + * These methods do not affect other internal enumeration methods, + * unless the underlying iteration method itself has side-effect, e.g. IO#each_line. + * + * External iteration differs *significantly* from internal iteration + * due to using a Fiber: + * - The Fiber adds some overhead compared to internal enumeration. + * - The stacktrace will only include the stack from the Enumerator, not above. + * - Fiber-local variables are *not* inherited inside the Enumerator Fiber, + * which instead starts with no Fiber-local variables. + * - Fiber storage variables *are* inherited and are designed + * to handle Enumerator Fibers. Assigning to a Fiber storage variable + * only affects the current Fiber, so if you want to change state + * in the caller Fiber of the Enumerator Fiber, you need to use an + * extra indirection (e.g., use some object in the Fiber storage + * variable and mutate some ivar of it). + * + * Concretely: + * Thread.current[:fiber_local] = 1 + * Fiber[:storage_var] = 1 + * e = Enumerator.new do |y| + * p Thread.current[:fiber_local] # for external iteration: nil, for internal iteration: 1 + * p Fiber[:storage_var] # => 1, inherited + * Fiber[:storage_var] += 1 + * y << 42 + * end + * + * p e.next # => 42 + * p Fiber[:storage_var] # => 1 (it ran in a different Fiber) * - * Moreover, implementation typically uses fibers so performance could be - * slower and exception stacktraces different than expected. + * e.each { p _1 } + * p Fiber[:storage_var] # => 2 (it ran in the same Fiber/"stack" as the current Fiber) * - * You can use this to implement an internal iterator as follows: + * == Convert External Iteration to Internal Iteration + * + * You can use an external iterator to implement an internal iterator as follows: * * def ext_each(e) * while true @@ -132,7 +164,10 @@ static VALUE sym_each, sym_cycle, sym_yield; static VALUE lazy_use_super_method; +extern ID ruby_static_id_cause; + #define id_call idCall +#define id_cause ruby_static_id_cause #define id_each idEach #define id_eqq idEqq #define id_initialize idInitialize @@ -766,8 +801,7 @@ next_init(VALUE obj, struct enumerator *e) { VALUE curr = rb_fiber_current(); e->dst = curr; - // We inherit the fiber storage by reference, not by copy, by specifying Qfalse here. - e->fib = rb_fiber_new_storage(next_i, obj, Qfalse); + e->fib = rb_fiber_new(next_i, obj); e->lookahead = Qundef; } @@ -776,8 +810,16 @@ get_next_values(VALUE obj, struct enumerator *e) { VALUE curr, vs; - if (e->stop_exc) - rb_exc_raise(e->stop_exc); + if (e->stop_exc) { + VALUE exc = e->stop_exc; + VALUE result = rb_attr_get(exc, id_result); + VALUE mesg = rb_attr_get(exc, idMesg); + if (!NIL_P(mesg)) mesg = rb_str_dup(mesg); + VALUE stop_exc = rb_exc_new_str(rb_eStopIteration, mesg); + rb_ivar_set(stop_exc, id_cause, exc); + rb_ivar_set(stop_exc, id_result, result); + rb_exc_raise(stop_exc); + } curr = rb_fiber_current(); @@ -3403,7 +3445,7 @@ enumerator_plus(VALUE obj, VALUE eobj) * * The method used against each enumerable object is `each_entry` * instead of `each` so that the product of N enumerable objects - * yields exactly N arguments in each iteration. + * yields an array of exactly N elements in each iteration. * * When no enumerator is given, it calls a given block once yielding * an empty argument list. @@ -3481,9 +3523,16 @@ enum_product_allocate(VALUE klass) * e.size #=> 6 */ static VALUE -enum_product_initialize(VALUE obj, VALUE enums) +enum_product_initialize(int argc, VALUE *argv, VALUE obj) { struct enum_product *ptr; + VALUE enums = Qnil, options = Qnil; + + rb_scan_args(argc, argv, "*:", &enums, &options); + + if (!NIL_P(options) && !RHASH_EMPTY_P(options)) { + rb_exc_raise(rb_keyword_error_new("unknown", rb_hash_keys(options))); + } rb_check_frozen(obj); TypedData_Get_Struct(obj, struct enum_product, &enum_product_data_type, ptr); @@ -3589,7 +3638,7 @@ product_each(VALUE obj, struct product_state *pstate) rb_block_call(eobj, id_each_entry, 0, NULL, product_each_i, (VALUE)pstate); } else { - rb_funcallv(pstate->block, id_call, pstate->argc, pstate->argv); + rb_funcall(pstate->block, id_call, 1, rb_ary_new_from_values(pstate->argc, pstate->argv)); } return obj; @@ -3687,6 +3736,7 @@ enum_product_inspect(VALUE obj) /* * call-seq: * Enumerator.product(*enums) -> enumerator + * Enumerator.product(*enums) { |elts| ... } -> enumerator * * Generates a new enumerator object that generates a Cartesian * product of given enumerable objects. This is equivalent to @@ -3695,18 +3745,29 @@ enum_product_inspect(VALUE obj) * e = Enumerator.product(1..3, [4, 5]) * e.to_a #=> [[1, 4], [1, 5], [2, 4], [2, 5], [3, 4], [3, 5]] * e.size #=> 6 + * + * When a block is given, calls the block with each N-element array + * generated and returns +nil+. */ static VALUE -enumerator_s_product(VALUE klass, VALUE enums) +enumerator_s_product(int argc, VALUE *argv, VALUE klass) { - VALUE obj = enum_product_initialize(enum_product_allocate(rb_cEnumProduct), enums); + VALUE enums = Qnil, options = Qnil, block = Qnil; - if (rb_block_given_p()) { - return enum_product_run(obj, rb_block_proc()); + rb_scan_args(argc, argv, "*:&", &enums, &options, &block); + + if (!NIL_P(options) && !RHASH_EMPTY_P(options)) { + rb_exc_raise(rb_keyword_error_new("unknown", rb_hash_keys(options))); } - else { - return obj; + + VALUE obj = enum_product_initialize(argc, argv, enum_product_allocate(rb_cEnumProduct)); + + if (!NIL_P(block)) { + enum_product_run(obj, block); + return Qnil; } + + return obj; } /* @@ -4586,7 +4647,7 @@ InitVM_Enumerator(void) /* Product */ rb_cEnumProduct = rb_define_class_under(rb_cEnumerator, "Product", rb_cEnumerator); rb_define_alloc_func(rb_cEnumProduct, enum_product_allocate); - rb_define_method(rb_cEnumProduct, "initialize", enum_product_initialize, -2); + rb_define_method(rb_cEnumProduct, "initialize", enum_product_initialize, -1); rb_define_method(rb_cEnumProduct, "initialize_copy", enum_product_init_copy, 1); rb_define_method(rb_cEnumProduct, "each", enum_product_each, 0); rb_define_method(rb_cEnumProduct, "size", enum_product_size, 0); @@ -4597,7 +4658,7 @@ InitVM_Enumerator(void) rb_undef_method(rb_cEnumProduct, "next_values"); rb_undef_method(rb_cEnumProduct, "peek"); rb_undef_method(rb_cEnumProduct, "peek_values"); - rb_define_singleton_method(rb_cEnumerator, "product", enumerator_s_product, -2); + rb_define_singleton_method(rb_cEnumerator, "product", enumerator_s_product, -1); /* ArithmeticSequence */ rb_cArithSeq = rb_define_class_under(rb_cEnumerator, "ArithmeticSequence", rb_cEnumerator); @@ -420,7 +420,7 @@ rb_warn(const char *fmt, ...) void rb_category_warn(rb_warning_category_t category, const char *fmt, ...) { - if (!NIL_P(ruby_verbose)) { + if (!NIL_P(ruby_verbose) && rb_warning_category_enabled_p(category)) { with_warning_string(mesg, 0, fmt) { rb_warn_category(mesg, rb_warning_category_to_name(category)); } @@ -452,7 +452,7 @@ rb_warning(const char *fmt, ...) void rb_category_warning(rb_warning_category_t category, const char *fmt, ...) { - if (RTEST(ruby_verbose)) { + if (RTEST(ruby_verbose) && rb_warning_category_enabled_p(category)) { with_warning_string(mesg, 0, fmt) { rb_warn_category(mesg, rb_warning_category_to_name(category)); } @@ -1818,7 +1818,9 @@ name_err_init_attr(VALUE exc, VALUE recv, VALUE method) cfp = rb_vm_get_ruby_level_next_cfp(ec, cfp); rb_ivar_set(exc, id_name, method); err_init_recv(exc, recv); - if (cfp) rb_ivar_set(exc, id_iseq, rb_iseqw_new(cfp->iseq)); + if (cfp && VM_FRAME_TYPE(cfp) != VM_FRAME_MAGIC_DUMMY) { + rb_ivar_set(exc, id_iseq, rb_iseqw_new(cfp->iseq)); + } return exc; } @@ -2959,6 +2961,8 @@ ivar_copy_i(st_data_t key, st_data_t val, st_data_t exc) return ST_CONTINUE; } +void rb_exc_check_circular_cause(VALUE exc); + static VALUE exception_loader(VALUE exc, VALUE obj) { @@ -2973,6 +2977,8 @@ exception_loader(VALUE exc, VALUE obj) rb_ivar_foreach(obj, ivar_copy_i, exc); + rb_exc_check_circular_cause(exc); + if (rb_attr_get(exc, id_bt) == rb_attr_get(exc, id_bt_locations)) { rb_ivar_set(exc, id_bt_locations, Qnil); } @@ -3024,12 +3030,16 @@ Init_Exception(void) rb_eSyntaxError = rb_define_class("SyntaxError", rb_eScriptError); rb_define_method(rb_eSyntaxError, "initialize", syntax_error_initialize, -1); + /* RDoc will use literal name value while parsing rb_attr, + * and will render `idPath` as an attribute name without this trick */ + ID path = idPath; + /* the path failed to parse */ - rb_attr(rb_eSyntaxError, idPath, TRUE, FALSE, FALSE); + rb_attr(rb_eSyntaxError, path, TRUE, FALSE, FALSE); rb_eLoadError = rb_define_class("LoadError", rb_eScriptError); /* the path failed to load */ - rb_attr(rb_eLoadError, idPath, TRUE, FALSE, FALSE); + rb_attr(rb_eLoadError, path, TRUE, FALSE, FALSE); rb_eNotImpError = rb_define_class("NotImplementedError", rb_eScriptError); @@ -537,12 +537,16 @@ exc_setup_message(const rb_execution_context_t *ec, VALUE mesg, VALUE *cause) } if (!nocircular && !NIL_P(*cause) && !UNDEF_P(*cause) && *cause != mesg) { +#if 0 /* maybe critical for some cases */ + rb_exc_check_circular_cause(*cause); +#else VALUE c = *cause; while (!NIL_P(c = rb_attr_get(c, id_cause))) { if (c == mesg) { rb_raise(rb_eArgError, "circular causes"); } } +#endif } return mesg; } @@ -643,6 +647,10 @@ rb_ec_setup_exception(const rb_execution_context_t *ec, VALUE mesg, VALUE cause) cause = get_ec_errinfo(ec); } if (cause != mesg) { + if (THROW_DATA_P(cause)) { + cause = Qnil; + } + rb_ivar_set(mesg, id_cause, cause); } } @@ -1769,6 +1777,16 @@ rb_obj_extend(int argc, VALUE *argv, VALUE obj) return obj; } +VALUE +rb_top_main_class(const char *method) +{ + VALUE klass = GET_THREAD()->top_wrapper; + + if (!klass) return rb_cObject; + rb_warning("main.%s in the wrapped load is effective only in wrapper module", method); + return klass; +} + /* * call-seq: * include(module, ...) -> self @@ -1781,13 +1799,7 @@ rb_obj_extend(int argc, VALUE *argv, VALUE obj) static VALUE top_include(int argc, VALUE *argv, VALUE self) { - rb_thread_t *th = GET_THREAD(); - - if (th->top_wrapper) { - rb_warning("main.include in the wrapped load is effective only in wrapper module"); - return rb_mod_include(argc, argv, th->top_wrapper); - } - return rb_mod_include(argc, argv, rb_cObject); + return rb_mod_include(argc, argv, rb_top_main_class("include")); } /* diff --git a/eval_error.c b/eval_error.c index e02f54e8e6..9806683000 100644 --- a/eval_error.c +++ b/eval_error.c @@ -291,6 +291,17 @@ show_cause(VALUE errinfo, VALUE str, VALUE opt, VALUE highlight, VALUE reverse, } void +rb_exc_check_circular_cause(VALUE exc) +{ + VALUE cause = exc, shown_causes = 0; + do { + if (shown_cause_p(cause, &shown_causes)) { + rb_raise(rb_eArgError, "circular causes"); + } + } while (!NIL_P(cause = rb_attr_get(cause, id_cause))); +} + +void rb_error_write(VALUE errinfo, VALUE emesg, VALUE errat, VALUE str, VALUE opt, VALUE highlight, VALUE reverse) { volatile VALUE eclass; @@ -444,7 +455,12 @@ exiting_split(VALUE errinfo, volatile int *exitcode, volatile int *sigstatus) if (NIL_P(errinfo)) return 0; - if (rb_obj_is_kind_of(errinfo, rb_eSystemExit)) { + if (THROW_DATA_P(errinfo)) { + int throw_state = ((const struct vm_throw_data *)errinfo)->throw_state; + ex = throw_state & VM_THROW_STATE_MASK; + result |= EXITING_WITH_STATUS; + } + else if (rb_obj_is_kind_of(errinfo, rb_eSystemExit)) { ex = sysexit_status(errinfo); result |= EXITING_WITH_STATUS; } diff --git a/ext/-test-/string/fstring.c b/ext/-test-/string/fstring.c index 2374319fe3..64f079251d 100644 --- a/ext/-test-/string/fstring.c +++ b/ext/-test-/string/fstring.c @@ -12,13 +12,13 @@ bug_s_fstring(VALUE self, VALUE str) VALUE bug_s_rb_enc_interned_str(VALUE self, VALUE encoding) { - return rb_enc_interned_str("foo", 3, RDATA(encoding)->data); + return rb_enc_interned_str("foo", 3, NIL_P(encoding) ? NULL : RDATA(encoding)->data); } VALUE bug_s_rb_enc_str_new(VALUE self, VALUE encoding) { - return rb_enc_str_new("foo", 3, RDATA(encoding)->data); + return rb_enc_str_new("foo", 3, NIL_P(encoding) ? NULL : RDATA(encoding)->data); } void diff --git a/ext/-test-/string/set_len.c b/ext/-test-/string/set_len.c index 219cea404c..049da2cdb5 100644 --- a/ext/-test-/string/set_len.c +++ b/ext/-test-/string/set_len.c @@ -7,8 +7,18 @@ bug_str_set_len(VALUE str, VALUE len) return str; } +static VALUE +bug_str_append(VALUE str, VALUE addendum) +{ + StringValue(addendum); + rb_str_modify_expand(str, RSTRING_LEN(addendum)); + memcpy(RSTRING_END(str), RSTRING_PTR(addendum), RSTRING_LEN(addendum)); + return str; +} + void Init_string_set_len(VALUE klass) { rb_define_method(klass, "set_len", bug_str_set_len, 1); + rb_define_method(klass, "append", bug_str_append, 1); } diff --git a/ext/date/date.gemspec b/ext/date/date.gemspec index 2bf28ffea5..660353ebc5 100644 --- a/ext/date/date.gemspec +++ b/ext/date/date.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |s| s.extensions = "ext/date/extconf.rb" end - s.required_ruby_version = ">= 2.4.0" + s.required_ruby_version = ">= 2.6.0" s.authors = ["Tadayoshi Funaba"] s.email = [nil] diff --git a/ext/date/date_core.c b/ext/date/date_core.c index e58da719e0..21367c0ddf 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -27,6 +27,10 @@ static VALUE eDateError; static VALUE half_days_in_day, day_in_nanoseconds; static double positive_inf, negative_inf; +// used by deconstruct_keys +static VALUE sym_year, sym_month, sym_day, sym_yday, sym_wday; +static VALUE sym_hour, sym_min, sym_sec, sym_sec_fraction, sym_zone; + #define f_boolcast(x) ((x) ? Qtrue : Qfalse) #define f_abs(x) rb_funcall(x, rb_intern("abs"), 0) @@ -60,7 +64,8 @@ static VALUE datetime_initialize(int argc, VALUE *argv, VALUE self); #define RETURN_FALSE_UNLESS_NUMERIC(obj) if(!RTEST(rb_obj_is_kind_of((obj), rb_cNumeric))) return Qfalse inline static void -check_numeric(VALUE obj, const char* field) { +check_numeric(VALUE obj, const char* field) +{ if(!RTEST(rb_obj_is_kind_of(obj, rb_cNumeric))) { rb_raise(rb_eTypeError, "invalid %s (not numeric)", field); } @@ -7432,6 +7437,96 @@ d_lite_jisx0301(VALUE self) return strftimev(fmt, self, set_tmx); } +static VALUE +deconstruct_keys(VALUE self, VALUE keys, int is_datetime) +{ + VALUE h = rb_hash_new(); + long i; + + get_d1(self); + + if (NIL_P(keys)) { + rb_hash_aset(h, sym_year, m_real_year(dat)); + rb_hash_aset(h, sym_month, INT2FIX(m_mon(dat))); + rb_hash_aset(h, sym_day, INT2FIX(m_mday(dat))); + rb_hash_aset(h, sym_yday, INT2FIX(m_yday(dat))); + rb_hash_aset(h, sym_wday, INT2FIX(m_wday(dat))); + if (is_datetime) { + rb_hash_aset(h, sym_hour, INT2FIX(m_hour(dat))); + rb_hash_aset(h, sym_min, INT2FIX(m_min(dat))); + rb_hash_aset(h, sym_sec, INT2FIX(m_sec(dat))); + rb_hash_aset(h, sym_sec_fraction, m_sf_in_sec(dat)); + rb_hash_aset(h, sym_zone, m_zone(dat)); + } + + return h; + } + if (!RB_TYPE_P(keys, T_ARRAY)) { + rb_raise(rb_eTypeError, + "wrong argument type %"PRIsVALUE" (expected Array or nil)", + rb_obj_class(keys)); + + } + + for (i=0; i<RARRAY_LEN(keys); i++) { + VALUE key = RARRAY_AREF(keys, i); + + if (sym_year == key) rb_hash_aset(h, key, m_real_year(dat)); + if (sym_month == key) rb_hash_aset(h, key, INT2FIX(m_mon(dat))); + if (sym_day == key) rb_hash_aset(h, key, INT2FIX(m_mday(dat))); + if (sym_yday == key) rb_hash_aset(h, key, INT2FIX(m_yday(dat))); + if (sym_wday == key) rb_hash_aset(h, key, INT2FIX(m_wday(dat))); + if (is_datetime) { + if (sym_hour == key) rb_hash_aset(h, key, INT2FIX(m_hour(dat))); + if (sym_min == key) rb_hash_aset(h, key, INT2FIX(m_min(dat))); + if (sym_sec == key) rb_hash_aset(h, key, INT2FIX(m_sec(dat))); + if (sym_sec_fraction == key) rb_hash_aset(h, key, m_sf_in_sec(dat)); + if (sym_zone == key) rb_hash_aset(h, key, m_zone(dat)); + } + } + return h; +} + +/* + * call-seq: + * deconstruct_keys(array_of_names_or_nil) -> hash + * + * Returns a hash of the name/value pairs, to use in pattern matching. + * Possible keys are: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, + * <tt>:wday</tt>, <tt>:yday</tt>. + * + * Possible usages: + * + * d = Date.new(2022, 10, 5) + * + * if d in wday: 3, day: ..7 # uses deconstruct_keys underneath + * puts "first Wednesday of the month" + * end + * #=> prints "first Wednesday of the month" + * + * case d + * in year: ...2022 + * puts "too old" + * in month: ..9 + * puts "quarter 1-3" + * in wday: 1..5, month: + * puts "working day in month #{month}" + * end + * #=> prints "working day in month 10" + * + * Note that deconstruction by pattern can also be combined with class check: + * + * if d in Date(wday: 3, day: ..7) + * puts "first Wednesday of the month" + * end + * + */ +static VALUE +d_lite_deconstruct_keys(VALUE self, VALUE keys) +{ + return deconstruct_keys(self, keys, /* is_datetime=false */ 0); +} + #ifndef NDEBUG /* :nodoc: */ static VALUE @@ -8740,6 +8835,47 @@ dt_lite_jisx0301(int argc, VALUE *argv, VALUE self) iso8601_timediv(self, n)); } +/* + * call-seq: + * deconstruct_keys(array_of_names_or_nil) -> hash + * + * Returns a hash of the name/value pairs, to use in pattern matching. + * Possible keys are: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, + * <tt>:wday</tt>, <tt>:yday</tt>, <tt>:hour</tt>, <tt>:min</tt>, + * <tt>:sec</tt>, <tt>:sec_fraction</tt>, <tt>:zone</tt>. + * + * Possible usages: + * + * dt = DateTime.new(2022, 10, 5, 13, 30) + * + * if d in wday: 1..5, hour: 10..18 # uses deconstruct_keys underneath + * puts "Working time" + * end + * #=> prints "Working time" + * + * case dt + * in year: ...2022 + * puts "too old" + * in month: ..9 + * puts "quarter 1-3" + * in wday: 1..5, month: + * puts "working day in month #{month}" + * end + * #=> prints "working day in month 10" + * + * Note that deconstruction by pattern can also be combined with class check: + * + * if d in DateTime(wday: 1..5, hour: 10..18, day: ..7) + * puts "Working time, first week of the month" + * end + * + */ +static VALUE +dt_lite_deconstruct_keys(VALUE self, VALUE keys) +{ + return deconstruct_keys(self, keys, /* is_datetime=true */ 1); +} + /* conversions */ #define f_subsec(x) rb_funcall(x, rb_intern("subsec"), 0) @@ -9370,6 +9506,17 @@ Init_date_core(void) id_ge_p = rb_intern_const(">="); id_eqeq_p = rb_intern_const("=="); + sym_year = ID2SYM(rb_intern_const("year")); + sym_month = ID2SYM(rb_intern_const("month")); + sym_yday = ID2SYM(rb_intern_const("yday")); + sym_wday = ID2SYM(rb_intern_const("wday")); + sym_day = ID2SYM(rb_intern_const("day")); + sym_hour = ID2SYM(rb_intern_const("hour")); + sym_min = ID2SYM(rb_intern_const("min")); + sym_sec = ID2SYM(rb_intern_const("sec")); + sym_sec_fraction = ID2SYM(rb_intern_const("sec_fraction")); + sym_zone = ID2SYM(rb_intern_const("zone")); + half_days_in_day = rb_rational_new2(INT2FIX(1), INT2FIX(2)); #if (LONG_MAX / DAY_IN_SECONDS) > SECOND_IN_NANOSECONDS @@ -9691,6 +9838,8 @@ Init_date_core(void) rb_define_method(cDate, "httpdate", d_lite_httpdate, 0); rb_define_method(cDate, "jisx0301", d_lite_jisx0301, 0); + rb_define_method(cDate, "deconstruct_keys", d_lite_deconstruct_keys, 1); + #ifndef NDEBUG rb_define_method(cDate, "marshal_dump_old", d_lite_marshal_dump_old, 0); #endif @@ -9901,6 +10050,8 @@ Init_date_core(void) rb_define_method(cDateTime, "rfc3339", dt_lite_rfc3339, -1); rb_define_method(cDateTime, "jisx0301", dt_lite_jisx0301, -1); + rb_define_method(cDateTime, "deconstruct_keys", dt_lite_deconstruct_keys, 1); + /* conversions */ rb_define_method(rb_cTime, "to_time", time_to_time, 0); diff --git a/ext/date/date_strptime.c b/ext/date/date_strptime.c index 7b06a31471..f731629df1 100644 --- a/ext/date/date_strptime.c +++ b/ext/date/date_strptime.c @@ -10,28 +10,15 @@ static const char *day_names[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", - "Sun", "Mon", "Tue", "Wed", - "Thu", "Fri", "Sat" }; +static const int ABBREVIATED_DAY_NAME_LENGTH = 3; static const char *month_names[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" -}; - -static const char *merid_names[] = { - "am", "pm", - "a.m.", "p.m." -}; - -static const char *extz_pats[] = { - ":z", - "::z", - ":::z" }; +static const int ABBREVIATED_MONTH_NAME_LENGTH = 3; #define sizeof_array(o) (sizeof o / sizeof o[0]) @@ -75,7 +62,7 @@ num_pattern_p(const char *s) #define NUM_PATTERN_P() num_pattern_p(&fmt[fi + 1]) static long -read_digits(const char *s, VALUE *n, size_t width) +read_digits(const char *s, size_t slen, VALUE *n, size_t width) { size_t l; @@ -83,7 +70,7 @@ read_digits(const char *s, VALUE *n, size_t width) return 0; l = 0; - while (ISDIGIT(s[l])) { + while (l < slen && ISDIGIT(s[l])) { if (++l == width) break; } @@ -131,7 +118,7 @@ do { \ #define READ_DIGITS(n,w) \ do { \ size_t l; \ - l = read_digits(&str[si], &n, w); \ + l = read_digits(&str[si], slen - si, &n, w); \ if (l == 0) \ fail(); \ si += l; \ @@ -161,6 +148,12 @@ do { \ VALUE date_zone_to_diff(VALUE); +static inline int +head_match_p(size_t len, const char *name, const char *str, size_t slen, size_t si) +{ + return slen - si >= len && strncasecmp(name, &str[si], len) == 0; +} + static size_t date__strptime_internal(const char *str, size_t slen, const char *fmt, size_t flen, VALUE hash) @@ -168,9 +161,18 @@ date__strptime_internal(const char *str, size_t slen, size_t si, fi; int c; +#define HEAD_MATCH_P(len, name) head_match_p(len, name, str, slen, si) si = fi = 0; while (fi < flen) { + if (isspace((unsigned char)fmt[fi])) { + while (si < slen && isspace((unsigned char)str[si])) + si++; + while (++fi < flen && isspace((unsigned char)fmt[fi])); + continue; + } + + if (si >= slen) fail(); switch (fmt[fi]) { case '%': @@ -194,12 +196,11 @@ date__strptime_internal(const char *str, size_t slen, { int i; - for (i = 0; i < (int)sizeof_array(extz_pats); i++) - if (strncmp(extz_pats[i], &fmt[fi], - strlen(extz_pats[i])) == 0) { - fi += i; - goto again; - } + for (i = 1; i < 3 && fi + i < flen && fmt[fi+i] == ':'; ++i); + if (fmt[fi+i] == 'z') { + fi += i - 1; + goto again; + } fail(); } @@ -209,10 +210,12 @@ date__strptime_internal(const char *str, size_t slen, int i; for (i = 0; i < (int)sizeof_array(day_names); i++) { - size_t l = strlen(day_names[i]); - if (strncasecmp(day_names[i], &str[si], l) == 0) { + const char *day_name = day_names[i]; + size_t l = strlen(day_name); + if (HEAD_MATCH_P(l, day_name) || + HEAD_MATCH_P(l = ABBREVIATED_DAY_NAME_LENGTH, day_name)) { si += l; - set_hash("wday", INT2FIX(i % 7)); + set_hash("wday", INT2FIX(i)); goto matched; } } @@ -225,10 +228,12 @@ date__strptime_internal(const char *str, size_t slen, int i; for (i = 0; i < (int)sizeof_array(month_names); i++) { - size_t l = strlen(month_names[i]); - if (strncasecmp(month_names[i], &str[si], l) == 0) { + const char *month_name = month_names[i]; + size_t l = strlen(month_name); + if (HEAD_MATCH_P(l, month_name) || + HEAD_MATCH_P(l = ABBREVIATED_MONTH_NAME_LENGTH, month_name)) { si += l; - set_hash("mon", INT2FIX((i % 12) + 1)); + set_hash("mon", INT2FIX(i + 1)); goto matched; } } @@ -402,18 +407,19 @@ date__strptime_internal(const char *str, size_t slen, case 'P': case 'p': + if (slen - si < 2) fail(); { - int i; - - for (i = 0; i < 4; i++) { - size_t l = strlen(merid_names[i]); - if (strncasecmp(merid_names[i], &str[si], l) == 0) { - si += l; - set_hash("_merid", INT2FIX((i % 2) == 0 ? 0 : 12)); - goto matched; - } + char c = str[si]; + const int hour = (c == 'P' || c == 'p') ? 12 : 0; + if (!hour && !(c == 'A' || c == 'a')) fail(); + if ((c = str[si+1]) == '.') { + if (slen - si < 4 || str[si+3] != '.') fail(); + c = str[si += 2]; } - fail(); + if (!(c == 'M' || c == 'm')) fail(); + si += 2; + set_hash("_merid", INT2FIX(hour)); + goto matched; } case 'Q': @@ -587,7 +593,7 @@ date__strptime_internal(const char *str, size_t slen, b = rb_backref_get(); rb_match_busy(b); - m = f_match(pat, rb_usascii_str_new2(&str[si])); + m = f_match(pat, rb_usascii_str_new(&str[si], slen - si)); if (!NIL_P(m)) { VALUE s, l, o; @@ -619,22 +625,13 @@ date__strptime_internal(const char *str, size_t slen, if (str[si] != '%') fail(); si++; - if (fi < flen) - if (str[si] != fmt[fi]) + if (fi < flen) { + if (si >= slen || str[si] != fmt[fi]) fail(); - si++; + si++; + } goto matched; } - case ' ': - case '\t': - case '\n': - case '\v': - case '\f': - case '\r': - while (isspace((unsigned char)str[si])) - si++; - fi++; - break; default: ordinal: if (str[si] != fmt[fi]) diff --git a/ext/date/lib/date.rb b/ext/date/lib/date.rb index 3a1dd132e7..a9fe3ce4b0 100644 --- a/ext/date/lib/date.rb +++ b/ext/date/lib/date.rb @@ -4,7 +4,7 @@ require 'date_core' class Date - VERSION = "3.3.1" # :nodoc: + VERSION = "3.3.3" # :nodoc: # call-seq: # infinite? -> false diff --git a/ext/extmk.rb b/ext/extmk.rb index cab9a519c1..4e77a7167b 100755 --- a/ext/extmk.rb +++ b/ext/extmk.rb @@ -136,6 +136,14 @@ def extract_makefile(makefile, keep = true) true end +def create_makefile(target, srcprefix = nil) + if $static and target.include?("/") + base = File.basename(target) + $defs << "-DInit_#{base}=Init_#{target.tr('/', '_')}" + end + super +end + def extmake(target, basedir = 'ext', maybestatic = true) FileUtils.mkpath target unless File.directory?(target) begin @@ -163,8 +171,6 @@ def extmake(target, basedir = 'ext', maybestatic = true) $mdir = target $srcdir = File.join($top_srcdir, basedir, $mdir) $preload = nil - $objs = [] - $srcs = [] $extso = [] makefile = "./Makefile" static = $static @@ -198,7 +204,7 @@ def extmake(target, basedir = 'ext', maybestatic = true) begin $extconf_h = nil ok &&= extract_makefile(makefile) - old_objs = $objs + old_objs = $objs || [] old_cleanfiles = $distcleanfiles | $cleanfiles conf = ["#{$srcdir}/makefile.rb", "#{$srcdir}/extconf.rb"].find {|f| File.exist?(f)} if (!ok || ($extconf_h && !File.exist?($extconf_h)) || @@ -261,6 +267,8 @@ def extmake(target, basedir = 'ext', maybestatic = true) unless $destdir.to_s.empty? or $mflags.defined?("DESTDIR") args += ["DESTDIR=" + relative_from($destdir, "../"+prefix)] end + $objs ||= [] + $srcs ||= [] if $static and ok and !$objs.empty? and !noinstall args += ["static"] $extlist.push [(maybestatic ? $static : false), target, $target, $preload] @@ -549,7 +557,13 @@ extend Module.new { end def create_makefile(*args, &block) - return super unless @gemname + unless @gemname + if $static and (target = args.first).include?("/") + base = File.basename(target) + $defs << "-DInit_#{base}=Init_#{target.tr('/', '_')}" + end + return super + end super(*args) do |conf| conf.find do |s| s.sub!(%r(^(srcdir *= *)\$\(top_srcdir\)/\.bundle/gems/[^/]+(?=/))) { diff --git a/ext/io/console/depend b/ext/io/console/depend index 06ccdde70d..36747ef583 100644 --- a/ext/io/console/depend +++ b/ext/io/console/depend @@ -185,7 +185,7 @@ win32_vk.inc: win32_vk.list -e 'n=$$F[1] and (n.strip!; /\AVK_/=~n) and' \ -e 'puts(%[#ifndef #{n}\n# define #{n} UNDEFINED_VK\n#endif])' \ $< && \ - gperf --ignore-case -E -C -P -p -j1 -i 1 -g -o -t -K ofs -N console_win32_vk -k* $< \ + gperf --ignore-case -L ANSI-C -E -C -P -p -j1 -i 1 -g -o -t -K ofs -N console_win32_vk -k* $< \ | sed -f $(top_srcdir)/tool/gperf.sed \ ) > $(@F) diff --git a/ext/io/console/io-console.gemspec b/ext/io/console/io-console.gemspec index aa57f8ac52..d26a757b01 100644 --- a/ext/io/console/io-console.gemspec +++ b/ext/io/console/io-console.gemspec @@ -1,5 +1,5 @@ # -*- ruby -*- -_VERSION = "0.5.11" +_VERSION = "0.6.0" Gem::Specification.new do |s| s.name = "io-console" diff --git a/ext/io/console/win32_vk.inc b/ext/io/console/win32_vk.inc index cbec7bef15..d15b1219fb 100644 --- a/ext/io/console/win32_vk.inc +++ b/ext/io/console/win32_vk.inc @@ -480,7 +480,7 @@ # define VK_OEM_CLEAR UNDEFINED_VK #endif /* ANSI-C code produced by gperf version 3.1 */ -/* Command-line: gperf --ignore-case -E -C -P -p -j1 -i 1 -g -o -t -K ofs -N console_win32_vk -k'*' win32_vk.list */ +/* Command-line: gperf --ignore-case -L ANSI-C -E -C -P -p -j1 -i 1 -g -o -t -K ofs -N console_win32_vk -k'*' win32_vk.list */ #if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ @@ -509,18 +509,17 @@ #error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gperf@gnu.org>." #endif -#define gperf_offsetof(s, n) (short)offsetof(struct s##_t, s##_str##n) #line 1 "win32_vk.list" struct vktable {short ofs; unsigned short vk;}; -static const struct vktable *console_win32_vk(/*const char *, unsigned int*/); +static const struct vktable *console_win32_vk(const char *, size_t); #line 5 "win32_vk.list" struct vktable; /* maximum key range = 245, duplicates = 0 */ #ifndef GPERF_DOWNCASE #define GPERF_DOWNCASE 1 -static unsigned char gperf_downcase[256] = +static const unsigned char gperf_downcase[256] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, @@ -1007,368 +1006,368 @@ console_win32_vk (register const char *str, register size_t len) {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 40 "win32_vk.list" - {gperf_offsetof(stringpool, 12), VK_UP}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str12, VK_UP}, #line 52 "win32_vk.list" - {gperf_offsetof(stringpool, 13), VK_APPS}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str13, VK_APPS}, #line 159 "win32_vk.list" - {gperf_offsetof(stringpool, 14), VK_CRSEL}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str14, VK_CRSEL}, #line 34 "win32_vk.list" - {gperf_offsetof(stringpool, 15), VK_SPACE}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str15, VK_SPACE}, #line 95 "win32_vk.list" - {gperf_offsetof(stringpool, 16), VK_SCROLL}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str16, VK_SCROLL}, #line 29 "win32_vk.list" - {gperf_offsetof(stringpool, 17), VK_ESCAPE}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str17, VK_ESCAPE}, #line 9 "win32_vk.list" - {gperf_offsetof(stringpool, 18), VK_CANCEL}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str18, VK_CANCEL}, #line 32 "win32_vk.list" - {gperf_offsetof(stringpool, 19), VK_ACCEPT}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str19, VK_ACCEPT}, #line 66 "win32_vk.list" - {gperf_offsetof(stringpool, 20), VK_SEPARATOR}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str20, VK_SEPARATOR}, #line 43 "win32_vk.list" - {gperf_offsetof(stringpool, 21), VK_SELECT}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str21, VK_SELECT}, #line 18 "win32_vk.list" - {gperf_offsetof(stringpool, 22), VK_CONTROL}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str22, VK_CONTROL}, #line 166 "win32_vk.list" - {gperf_offsetof(stringpool, 23), VK_OEM_CLEAR}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str23, VK_OEM_CLEAR}, #line 145 "win32_vk.list" - {gperf_offsetof(stringpool, 24), VK_OEM_RESET}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str24, VK_OEM_RESET}, #line 155 "win32_vk.list" - {gperf_offsetof(stringpool, 25), VK_OEM_AUTO}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str25, VK_OEM_AUTO}, #line 151 "win32_vk.list" - {gperf_offsetof(stringpool, 26), VK_OEM_CUSEL}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str26, VK_OEM_CUSEL}, {-1}, #line 22 "win32_vk.list" - {gperf_offsetof(stringpool, 28), VK_KANA}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str28, VK_KANA}, #line 127 "win32_vk.list" - {gperf_offsetof(stringpool, 29), VK_OEM_PLUS}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str29, VK_OEM_PLUS}, #line 35 "win32_vk.list" - {gperf_offsetof(stringpool, 30), VK_PRIOR}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str30, VK_PRIOR}, #line 152 "win32_vk.list" - {gperf_offsetof(stringpool, 31), VK_OEM_ATTN}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str31, VK_OEM_ATTN}, #line 20 "win32_vk.list" - {gperf_offsetof(stringpool, 32), VK_PAUSE}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str32, VK_PAUSE}, #line 13 "win32_vk.list" - {gperf_offsetof(stringpool, 33), VK_BACK}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str33, VK_BACK}, #line 144 "win32_vk.list" - {gperf_offsetof(stringpool, 34), VK_PACKET}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str34, VK_PACKET}, #line 105 "win32_vk.list" - {gperf_offsetof(stringpool, 35), VK_RCONTROL}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str35, VK_RCONTROL}, #line 104 "win32_vk.list" - {gperf_offsetof(stringpool, 36), VK_LCONTROL}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str36, VK_LCONTROL}, #line 37 "win32_vk.list" - {gperf_offsetof(stringpool, 37), VK_END}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str37, VK_END}, #line 38 "win32_vk.list" - {gperf_offsetof(stringpool, 38), VK_HOME}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str38, VK_HOME}, #line 44 "win32_vk.list" - {gperf_offsetof(stringpool, 39), VK_PRINT}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str39, VK_PRINT}, #line 94 "win32_vk.list" - {gperf_offsetof(stringpool, 40), VK_NUMLOCK}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str40, VK_NUMLOCK}, #line 39 "win32_vk.list" - {gperf_offsetof(stringpool, 41), VK_LEFT}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str41, VK_LEFT}, #line 25 "win32_vk.list" - {gperf_offsetof(stringpool, 42), VK_JUNJA}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str42, VK_JUNJA}, #line 19 "win32_vk.list" - {gperf_offsetof(stringpool, 43), VK_MENU}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str43, VK_MENU}, #line 150 "win32_vk.list" - {gperf_offsetof(stringpool, 44), VK_OEM_WSCTRL}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str44, VK_OEM_WSCTRL}, #line 156 "win32_vk.list" - {gperf_offsetof(stringpool, 45), VK_OEM_ENLW}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str45, VK_OEM_ENLW}, #line 36 "win32_vk.list" - {gperf_offsetof(stringpool, 46), VK_NEXT}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str46, VK_NEXT}, #line 51 "win32_vk.list" - {gperf_offsetof(stringpool, 47), VK_RWIN}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str47, VK_RWIN}, #line 50 "win32_vk.list" - {gperf_offsetof(stringpool, 48), VK_LWIN}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str48, VK_LWIN}, #line 21 "win32_vk.list" - {gperf_offsetof(stringpool, 49), VK_CAPITAL}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str49, VK_CAPITAL}, #line 49 "win32_vk.list" - {gperf_offsetof(stringpool, 50), VK_HELP}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str50, VK_HELP}, #line 164 "win32_vk.list" - {gperf_offsetof(stringpool, 51), VK_NONAME}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str51, VK_NONAME}, #line 8 "win32_vk.list" - {gperf_offsetof(stringpool, 52), VK_RBUTTON}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str52, VK_RBUTTON}, #line 7 "win32_vk.list" - {gperf_offsetof(stringpool, 53), VK_LBUTTON}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str53, VK_LBUTTON}, #line 96 "win32_vk.list" - {gperf_offsetof(stringpool, 54), VK_OEM_NEC_EQUAL}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str54, VK_OEM_NEC_EQUAL}, {-1}, #line 47 "win32_vk.list" - {gperf_offsetof(stringpool, 56), VK_INSERT}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str56, VK_INSERT}, #line 27 "win32_vk.list" - {gperf_offsetof(stringpool, 57), VK_HANJA}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str57, VK_HANJA}, {-1}, {-1}, #line 46 "win32_vk.list" - {gperf_offsetof(stringpool, 60), VK_SNAPSHOT}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str60, VK_SNAPSHOT}, #line 158 "win32_vk.list" - {gperf_offsetof(stringpool, 61), VK_ATTN}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str61, VK_ATTN}, #line 14 "win32_vk.list" - {gperf_offsetof(stringpool, 62), VK_TAB}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str62, VK_TAB}, #line 157 "win32_vk.list" - {gperf_offsetof(stringpool, 63), VK_OEM_BACKTAB}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str63, VK_OEM_BACKTAB}, #line 143 "win32_vk.list" - {gperf_offsetof(stringpool, 64), VK_ICO_CLEAR}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str64, VK_ICO_CLEAR}, #line 30 "win32_vk.list" - {gperf_offsetof(stringpool, 65), VK_CONVERT}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str65, VK_CONVERT}, #line 16 "win32_vk.list" - {gperf_offsetof(stringpool, 66), VK_RETURN}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str66, VK_RETURN}, #line 146 "win32_vk.list" - {gperf_offsetof(stringpool, 67), VK_OEM_JUMP}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str67, VK_OEM_JUMP}, {-1}, {-1}, {-1}, #line 111 "win32_vk.list" - {gperf_offsetof(stringpool, 71), VK_BROWSER_STOP}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str71, VK_BROWSER_STOP}, #line 26 "win32_vk.list" - {gperf_offsetof(stringpool, 72), VK_FINAL}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str72, VK_FINAL}, #line 163 "win32_vk.list" - {gperf_offsetof(stringpool, 73), VK_ZOOM}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str73, VK_ZOOM}, #line 28 "win32_vk.list" - {gperf_offsetof(stringpool, 74), VK_KANJI}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str74, VK_KANJI}, #line 48 "win32_vk.list" - {gperf_offsetof(stringpool, 75), VK_DELETE}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str75, VK_DELETE}, #line 128 "win32_vk.list" - {gperf_offsetof(stringpool, 76), VK_OEM_COMMA}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str76, VK_OEM_COMMA}, #line 67 "win32_vk.list" - {gperf_offsetof(stringpool, 77), VK_SUBTRACT}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str77, VK_SUBTRACT}, {-1}, #line 10 "win32_vk.list" - {gperf_offsetof(stringpool, 79), VK_MBUTTON}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str79, VK_MBUTTON}, #line 78 "win32_vk.list" - {gperf_offsetof(stringpool, 80), VK_F9}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str80, VK_F9}, #line 17 "win32_vk.list" - {gperf_offsetof(stringpool, 81), VK_SHIFT}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str81, VK_SHIFT}, #line 103 "win32_vk.list" - {gperf_offsetof(stringpool, 82), VK_RSHIFT}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str82, VK_RSHIFT}, #line 102 "win32_vk.list" - {gperf_offsetof(stringpool, 83), VK_LSHIFT}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str83, VK_LSHIFT}, #line 65 "win32_vk.list" - {gperf_offsetof(stringpool, 84), VK_ADD}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str84, VK_ADD}, #line 31 "win32_vk.list" - {gperf_offsetof(stringpool, 85), VK_NONCONVERT}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str85, VK_NONCONVERT}, #line 160 "win32_vk.list" - {gperf_offsetof(stringpool, 86), VK_EXSEL}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str86, VK_EXSEL}, #line 126 "win32_vk.list" - {gperf_offsetof(stringpool, 87), VK_OEM_1}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str87, VK_OEM_1}, #line 138 "win32_vk.list" - {gperf_offsetof(stringpool, 88), VK_OEM_AX}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str88, VK_OEM_AX}, #line 108 "win32_vk.list" - {gperf_offsetof(stringpool, 89), VK_BROWSER_BACK}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str89, VK_BROWSER_BACK}, #line 137 "win32_vk.list" - {gperf_offsetof(stringpool, 90), VK_OEM_8}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str90, VK_OEM_8}, #line 129 "win32_vk.list" - {gperf_offsetof(stringpool, 91), VK_OEM_MINUS}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str91, VK_OEM_MINUS}, #line 162 "win32_vk.list" - {gperf_offsetof(stringpool, 92), VK_PLAY}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str92, VK_PLAY}, #line 131 "win32_vk.list" - {gperf_offsetof(stringpool, 93), VK_OEM_2}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str93, VK_OEM_2}, #line 15 "win32_vk.list" - {gperf_offsetof(stringpool, 94), VK_CLEAR}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str94, VK_CLEAR}, #line 99 "win32_vk.list" - {gperf_offsetof(stringpool, 95), VK_OEM_FJ_TOUROKU}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str95, VK_OEM_FJ_TOUROKU}, #line 147 "win32_vk.list" - {gperf_offsetof(stringpool, 96), VK_OEM_PA1}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str96, VK_OEM_PA1}, #line 140 "win32_vk.list" - {gperf_offsetof(stringpool, 97), VK_ICO_HELP}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str97, VK_ICO_HELP}, #line 112 "win32_vk.list" - {gperf_offsetof(stringpool, 98), VK_BROWSER_SEARCH}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str98, VK_BROWSER_SEARCH}, #line 53 "win32_vk.list" - {gperf_offsetof(stringpool, 99), VK_SLEEP}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str99, VK_SLEEP}, {-1}, #line 70 "win32_vk.list" - {gperf_offsetof(stringpool, 101), VK_F1}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str101, VK_F1}, #line 148 "win32_vk.list" - {gperf_offsetof(stringpool, 102), VK_OEM_PA2}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str102, VK_OEM_PA2}, #line 154 "win32_vk.list" - {gperf_offsetof(stringpool, 103), VK_OEM_COPY}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str103, VK_OEM_COPY}, #line 77 "win32_vk.list" - {gperf_offsetof(stringpool, 104), VK_F8}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str104, VK_F8}, #line 88 "win32_vk.list" - {gperf_offsetof(stringpool, 105), VK_F19}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str105, VK_F19}, #line 41 "win32_vk.list" - {gperf_offsetof(stringpool, 106), VK_RIGHT}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str106, VK_RIGHT}, #line 71 "win32_vk.list" - {gperf_offsetof(stringpool, 107), VK_F2}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str107, VK_F2}, #line 135 "win32_vk.list" - {gperf_offsetof(stringpool, 108), VK_OEM_6}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str108, VK_OEM_6}, #line 87 "win32_vk.list" - {gperf_offsetof(stringpool, 109), VK_F18}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str109, VK_F18}, {-1}, #line 117 "win32_vk.list" - {gperf_offsetof(stringpool, 111), VK_VOLUME_UP}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str111, VK_VOLUME_UP}, {-1}, {-1}, #line 120 "win32_vk.list" - {gperf_offsetof(stringpool, 114), VK_MEDIA_STOP}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str114, VK_MEDIA_STOP}, #line 130 "win32_vk.list" - {gperf_offsetof(stringpool, 115), VK_OEM_PERIOD}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str115, VK_OEM_PERIOD}, {-1}, #line 161 "win32_vk.list" - {gperf_offsetof(stringpool, 117), VK_EREOF}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str117, VK_EREOF}, {-1}, {-1}, {-1}, #line 114 "win32_vk.list" - {gperf_offsetof(stringpool, 121), VK_BROWSER_HOME}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str121, VK_BROWSER_HOME}, #line 75 "win32_vk.list" - {gperf_offsetof(stringpool, 122), VK_F6}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str122, VK_F6}, {-1}, #line 110 "win32_vk.list" - {gperf_offsetof(stringpool, 124), VK_BROWSER_REFRESH}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str124, VK_BROWSER_REFRESH}, {-1}, #line 165 "win32_vk.list" - {gperf_offsetof(stringpool, 126), VK_PA1}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str126, VK_PA1}, #line 142 "win32_vk.list" - {gperf_offsetof(stringpool, 127), VK_PROCESSKEY}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str127, VK_PROCESSKEY}, #line 68 "win32_vk.list" - {gperf_offsetof(stringpool, 128), VK_DECIMAL}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str128, VK_DECIMAL}, #line 132 "win32_vk.list" - {gperf_offsetof(stringpool, 129), VK_OEM_3}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str129, VK_OEM_3}, #line 107 "win32_vk.list" - {gperf_offsetof(stringpool, 130), VK_RMENU}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str130, VK_RMENU}, #line 106 "win32_vk.list" - {gperf_offsetof(stringpool, 131), VK_LMENU}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str131, VK_LMENU}, #line 98 "win32_vk.list" - {gperf_offsetof(stringpool, 132), VK_OEM_FJ_MASSHOU}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str132, VK_OEM_FJ_MASSHOU}, #line 54 "win32_vk.list" - {gperf_offsetof(stringpool, 133), VK_NUMPAD0}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str133, VK_NUMPAD0}, #line 24 "win32_vk.list" - {gperf_offsetof(stringpool, 134), VK_HANGUL}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str134, VK_HANGUL}, #line 63 "win32_vk.list" - {gperf_offsetof(stringpool, 135), VK_NUMPAD9}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str135, VK_NUMPAD9}, #line 23 "win32_vk.list" - {gperf_offsetof(stringpool, 136), VK_HANGEUL}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str136, VK_HANGEUL}, #line 134 "win32_vk.list" - {gperf_offsetof(stringpool, 137), VK_OEM_5}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str137, VK_OEM_5}, #line 149 "win32_vk.list" - {gperf_offsetof(stringpool, 138), VK_OEM_PA3}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str138, VK_OEM_PA3}, #line 115 "win32_vk.list" - {gperf_offsetof(stringpool, 139), VK_VOLUME_MUTE}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str139, VK_VOLUME_MUTE}, #line 133 "win32_vk.list" - {gperf_offsetof(stringpool, 140), VK_OEM_4}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str140, VK_OEM_4}, #line 122 "win32_vk.list" - {gperf_offsetof(stringpool, 141), VK_LAUNCH_MAIL}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str141, VK_LAUNCH_MAIL}, #line 97 "win32_vk.list" - {gperf_offsetof(stringpool, 142), VK_OEM_FJ_JISHO}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str142, VK_OEM_FJ_JISHO}, #line 72 "win32_vk.list" - {gperf_offsetof(stringpool, 143), VK_F3}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str143, VK_F3}, #line 101 "win32_vk.list" - {gperf_offsetof(stringpool, 144), VK_OEM_FJ_ROYA}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str144, VK_OEM_FJ_ROYA}, #line 100 "win32_vk.list" - {gperf_offsetof(stringpool, 145), VK_OEM_FJ_LOYA}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str145, VK_OEM_FJ_LOYA}, {-1}, #line 42 "win32_vk.list" - {gperf_offsetof(stringpool, 147), VK_DOWN}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str147, VK_DOWN}, {-1}, #line 153 "win32_vk.list" - {gperf_offsetof(stringpool, 149), VK_OEM_FINISH}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str149, VK_OEM_FINISH}, {-1}, #line 74 "win32_vk.list" - {gperf_offsetof(stringpool, 151), VK_F5}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str151, VK_F5}, {-1}, #line 136 "win32_vk.list" - {gperf_offsetof(stringpool, 153), VK_OEM_7}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str153, VK_OEM_7}, #line 73 "win32_vk.list" - {gperf_offsetof(stringpool, 154), VK_F4}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str154, VK_F4}, #line 86 "win32_vk.list" - {gperf_offsetof(stringpool, 155), VK_F17}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str155, VK_F17}, #line 55 "win32_vk.list" - {gperf_offsetof(stringpool, 156), VK_NUMPAD1}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str156, VK_NUMPAD1}, #line 141 "win32_vk.list" - {gperf_offsetof(stringpool, 157), VK_ICO_00}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str157, VK_ICO_00}, {-1}, #line 62 "win32_vk.list" - {gperf_offsetof(stringpool, 159), VK_NUMPAD8}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str159, VK_NUMPAD8}, {-1}, {-1}, #line 56 "win32_vk.list" - {gperf_offsetof(stringpool, 162), VK_NUMPAD2}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str162, VK_NUMPAD2}, {-1}, #line 124 "win32_vk.list" - {gperf_offsetof(stringpool, 164), VK_LAUNCH_APP1}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str164, VK_LAUNCH_APP1}, #line 109 "win32_vk.list" - {gperf_offsetof(stringpool, 165), VK_BROWSER_FORWARD}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str165, VK_BROWSER_FORWARD}, {-1}, #line 76 "win32_vk.list" - {gperf_offsetof(stringpool, 167), VK_F7}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str167, VK_F7}, {-1}, {-1}, #line 125 "win32_vk.list" - {gperf_offsetof(stringpool, 170), VK_LAUNCH_APP2}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str170, VK_LAUNCH_APP2}, #line 64 "win32_vk.list" - {gperf_offsetof(stringpool, 171), VK_MULTIPLY}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str171, VK_MULTIPLY}, {-1}, {-1}, #line 45 "win32_vk.list" - {gperf_offsetof(stringpool, 174), VK_EXECUTE}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str174, VK_EXECUTE}, {-1}, #line 113 "win32_vk.list" - {gperf_offsetof(stringpool, 176), VK_BROWSER_FAVORITES}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str176, VK_BROWSER_FAVORITES}, #line 60 "win32_vk.list" - {gperf_offsetof(stringpool, 177), VK_NUMPAD6}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str177, VK_NUMPAD6}, {-1}, #line 85 "win32_vk.list" - {gperf_offsetof(stringpool, 179), VK_F16}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str179, VK_F16}, {-1}, {-1}, #line 79 "win32_vk.list" - {gperf_offsetof(stringpool, 182), VK_F10}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str182, VK_F10}, {-1}, {-1}, #line 116 "win32_vk.list" - {gperf_offsetof(stringpool, 185), VK_VOLUME_DOWN}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str185, VK_VOLUME_DOWN}, {-1}, {-1}, #line 89 "win32_vk.list" - {gperf_offsetof(stringpool, 188), VK_F20}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str188, VK_F20}, #line 119 "win32_vk.list" - {gperf_offsetof(stringpool, 189), VK_MEDIA_PREV_TRACK}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str189, VK_MEDIA_PREV_TRACK}, {-1}, #line 33 "win32_vk.list" - {gperf_offsetof(stringpool, 191), VK_MODECHANGE}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str191, VK_MODECHANGE}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 83 "win32_vk.list" - {gperf_offsetof(stringpool, 197), VK_F14}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str197, VK_F14}, #line 57 "win32_vk.list" - {gperf_offsetof(stringpool, 198), VK_NUMPAD3}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str198, VK_NUMPAD3}, #line 11 "win32_vk.list" - {gperf_offsetof(stringpool, 199), VK_XBUTTON1}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str199, VK_XBUTTON1}, {-1}, {-1}, {-1}, #line 93 "win32_vk.list" - {gperf_offsetof(stringpool, 203), VK_F24}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str203, VK_F24}, {-1}, #line 12 "win32_vk.list" - {gperf_offsetof(stringpool, 205), VK_XBUTTON2}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str205, VK_XBUTTON2}, #line 59 "win32_vk.list" - {gperf_offsetof(stringpool, 206), VK_NUMPAD5}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str206, VK_NUMPAD5}, {-1}, {-1}, #line 58 "win32_vk.list" - {gperf_offsetof(stringpool, 209), VK_NUMPAD4}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str209, VK_NUMPAD4}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 121 "win32_vk.list" - {gperf_offsetof(stringpool, 215), VK_MEDIA_PLAY_PAUSE}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str215, VK_MEDIA_PLAY_PAUSE}, {-1}, #line 123 "win32_vk.list" - {gperf_offsetof(stringpool, 217), VK_LAUNCH_MEDIA_SELECT}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str217, VK_LAUNCH_MEDIA_SELECT}, #line 80 "win32_vk.list" - {gperf_offsetof(stringpool, 218), VK_F11}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str218, VK_F11}, {-1}, #line 139 "win32_vk.list" - {gperf_offsetof(stringpool, 220), VK_OEM_102}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str220, VK_OEM_102}, #line 118 "win32_vk.list" - {gperf_offsetof(stringpool, 221), VK_MEDIA_NEXT_TRACK}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str221, VK_MEDIA_NEXT_TRACK}, #line 61 "win32_vk.list" - {gperf_offsetof(stringpool, 222), VK_NUMPAD7}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str222, VK_NUMPAD7}, {-1}, #line 90 "win32_vk.list" - {gperf_offsetof(stringpool, 224), VK_F21}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str224, VK_F21}, {-1}, #line 82 "win32_vk.list" - {gperf_offsetof(stringpool, 226), VK_F13}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str226, VK_F13}, {-1}, {-1}, #line 81 "win32_vk.list" - {gperf_offsetof(stringpool, 229), VK_F12}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str229, VK_F12}, {-1}, {-1}, #line 92 "win32_vk.list" - {gperf_offsetof(stringpool, 232), VK_F23}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str232, VK_F23}, {-1}, {-1}, #line 91 "win32_vk.list" - {gperf_offsetof(stringpool, 235), VK_F22}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str235, VK_F22}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 84 "win32_vk.list" - {gperf_offsetof(stringpool, 242), VK_F15}, + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str242, VK_F15}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, #line 69 "win32_vk.list" - {gperf_offsetof(stringpool, 256), VK_DIVIDE} + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str256, VK_DIVIDE} }; if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) diff --git a/ext/io/console/win32_vk.list b/ext/io/console/win32_vk.list index 7909a4d1f0..5df3d6da57 100644 --- a/ext/io/console/win32_vk.list +++ b/ext/io/console/win32_vk.list @@ -1,6 +1,6 @@ %{ struct vktable {short ofs; unsigned short vk;}; -static const struct vktable *console_win32_vk(/*!ANSI{*/const char *, unsigned int/*}!ANSI*/); +static const struct vktable *console_win32_vk(const char *, size_t); %} struct vktable %% diff --git a/ext/io/wait/extconf.rb b/ext/io/wait/extconf.rb index eecdcce99f..c6230b7783 100644 --- a/ext/io/wait/extconf.rb +++ b/ext/io/wait/extconf.rb @@ -5,7 +5,7 @@ if RUBY_VERSION < "2.6" File.write("Makefile", dummy_makefile($srcdir).join("")) else target = "io/wait" - have_func("rb_io_wait") + have_func("rb_io_wait", "ruby/io.h") unless macro_defined?("DOSISH", "#include <ruby.h>") 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| diff --git a/ext/io/wait/io-wait.gemspec b/ext/io/wait/io-wait.gemspec index 7b73d64200..ebc1f6f5c7 100644 --- a/ext/io/wait/io-wait.gemspec +++ b/ext/io/wait/io-wait.gemspec @@ -1,4 +1,4 @@ -_VERSION = "0.3.0.pre" +_VERSION = "0.3.0" Gem::Specification.new do |spec| spec.name = "io-wait" diff --git a/ext/json/parser/extconf.rb b/ext/json/parser/extconf.rb index feb586e1b4..4723a02aee 100644 --- a/ext/json/parser/extconf.rb +++ b/ext/json/parser/extconf.rb @@ -1,8 +1,8 @@ # frozen_string_literal: false require 'mkmf' -have_func("rb_enc_raise", "ruby.h") -have_func("rb_enc_interned_str", "ruby.h") +have_func("rb_enc_raise", "ruby/encoding.h") +have_func("rb_enc_interned_str", "ruby/encoding.h") # checking if String#-@ (str_uminus) dedupes... ' begin diff --git a/ext/objspace/depend b/ext/objspace/depend index f83607236a..52797664e0 100644 --- a/ext/objspace/depend +++ b/ext/objspace/depend @@ -158,12 +158,14 @@ object_tracing.o: $(hdrdir)/ruby/missing.h object_tracing.o: $(hdrdir)/ruby/ruby.h object_tracing.o: $(hdrdir)/ruby/st.h object_tracing.o: $(hdrdir)/ruby/subst.h +object_tracing.o: $(top_srcdir)/gc.h object_tracing.o: $(top_srcdir)/internal.h object_tracing.o: object_tracing.c object_tracing.o: objspace.h objspace.o: $(RUBY_EXTCONF_H) objspace.o: $(arch_hdrdir)/ruby/config.h objspace.o: $(hdrdir)/ruby/assert.h +objspace.o: $(hdrdir)/ruby/atomic.h objspace.o: $(hdrdir)/ruby/backward.h objspace.o: $(hdrdir)/ruby/backward/2/assume.h objspace.o: $(hdrdir)/ruby/backward/2/attributes.h @@ -336,10 +338,16 @@ objspace.o: $(hdrdir)/ruby/regex.h objspace.o: $(hdrdir)/ruby/ruby.h objspace.o: $(hdrdir)/ruby/st.h objspace.o: $(hdrdir)/ruby/subst.h +objspace.o: $(hdrdir)/ruby/thread_native.h +objspace.o: $(top_srcdir)/ccan/check_type/check_type.h +objspace.o: $(top_srcdir)/ccan/container_of/container_of.h +objspace.o: $(top_srcdir)/ccan/list/list.h +objspace.o: $(top_srcdir)/ccan/str/str.h objspace.o: $(top_srcdir)/gc.h objspace.o: $(top_srcdir)/id_table.h objspace.o: $(top_srcdir)/internal.h objspace.o: $(top_srcdir)/internal/array.h +objspace.o: $(top_srcdir)/internal/basic_operators.h objspace.o: $(top_srcdir)/internal/class.h objspace.o: $(top_srcdir)/internal/compilers.h objspace.o: $(top_srcdir)/internal/gc.h @@ -348,10 +356,17 @@ objspace.o: $(top_srcdir)/internal/imemo.h objspace.o: $(top_srcdir)/internal/sanitizers.h objspace.o: $(top_srcdir)/internal/serial.h objspace.o: $(top_srcdir)/internal/static_assert.h +objspace.o: $(top_srcdir)/internal/vm.h objspace.o: $(top_srcdir)/internal/warnings.h +objspace.o: $(top_srcdir)/method.h objspace.o: $(top_srcdir)/node.h +objspace.o: $(top_srcdir)/ruby_assert.h +objspace.o: $(top_srcdir)/ruby_atomic.h objspace.o: $(top_srcdir)/shape.h objspace.o: $(top_srcdir)/symbol.h +objspace.o: $(top_srcdir)/thread_pthread.h +objspace.o: $(top_srcdir)/vm_core.h +objspace.o: $(top_srcdir)/vm_opts.h objspace.o: objspace.c objspace.o: {$(VPATH)}id.h objspace_dump.o: $(RUBY_EXTCONF_H) @@ -540,6 +555,7 @@ objspace_dump.o: $(top_srcdir)/id_table.h objspace_dump.o: $(top_srcdir)/internal.h objspace_dump.o: $(top_srcdir)/internal/array.h objspace_dump.o: $(top_srcdir)/internal/basic_operators.h +objspace_dump.o: $(top_srcdir)/internal/class.h objspace_dump.o: $(top_srcdir)/internal/compilers.h objspace_dump.o: $(top_srcdir)/internal/gc.h objspace_dump.o: $(top_srcdir)/internal/hash.h diff --git a/ext/objspace/lib/objspace.rb b/ext/objspace/lib/objspace.rb index f8a66d8d32..6865fdda4c 100644 --- a/ext/objspace/lib/objspace.rb +++ b/ext/objspace/lib/objspace.rb @@ -12,9 +12,9 @@ module ObjectSpace module_function # call-seq: - # ObjectSpace.dump(obj[, output: :string]) # => "{ ... }" - # ObjectSpace.dump(obj, output: :file) # => #<File:/tmp/rubyobj20131125-88733-1xkfmpv.json> - # ObjectSpace.dump(obj, output: :stdout) # => nil + # ObjectSpace.dump(obj[, output: :string]) -> "{ ... }" + # ObjectSpace.dump(obj, output: :file) -> #<File:/tmp/rubyobj20131125-88733-1xkfmpv.json> + # ObjectSpace.dump(obj, output: :stdout) -> nil # # Dump the contents of a ruby object as JSON. # @@ -43,48 +43,46 @@ module ObjectSpace end - # call-seq: - # ObjectSpace.dump_all([output: :file]) # => #<File:/tmp/rubyheap20131125-88469-laoj3v.json> - # ObjectSpace.dump_all(output: :stdout) # => nil - # ObjectSpace.dump_all(output: :string) # => "{...}\n{...}\n..." - # ObjectSpace.dump_all(output: - # File.open('heap.json','w')) # => #<File:heap.json> - # ObjectSpace.dump_all(output: :string, - # since: 42) # => "{...}\n{...}\n..." + # call-seq: + # ObjectSpace.dump_all([output: :file]) -> #<File:/tmp/rubyheap20131125-88469-laoj3v.json> + # ObjectSpace.dump_all(output: :stdout) -> nil + # ObjectSpace.dump_all(output: :string) -> "{...}\n{...}\n..." + # ObjectSpace.dump_all(output: File.open('heap.json','w')) -> #<File:heap.json> + # ObjectSpace.dump_all(output: :string, since: 42) -> "{...}\n{...}\n..." # - # Dump the contents of the ruby heap as JSON. + # Dump the contents of the ruby heap as JSON. # - #. _full__ must be a boolean. If true all heap slots are dumped including the empty ones (T_NONE). + # _full_ must be a boolean. If true all heap slots are dumped including the empty ones (T_NONE). # - # _since_ must be a non-negative integer or +nil+. + # _since_ must be a non-negative integer or +nil+. # - # If _since_ is a positive integer, only objects of that generation and - # newer generations are dumped. The current generation can be accessed using - # GC::count. Objects that were allocated without object allocation tracing enabled - # are ignored. See ::trace_object_allocations for more information and - # examples. + # If _since_ is a positive integer, only objects of that generation and + # newer generations are dumped. The current generation can be accessed using + # GC::count. Objects that were allocated without object allocation tracing enabled + # are ignored. See ::trace_object_allocations for more information and + # examples. # - # If _since_ is omitted or is +nil+, all objects are dumped. + # If _since_ is omitted or is +nil+, all objects are dumped. # - # _shapes_ must be a boolean or a non-negative integer. + # _shapes_ must be a boolean or a non-negative integer. # - # If _shapes_ is a positive integer, only shapes newer than the provided - # shape id are dumped. The current shape_id can be accessed using +RubyVM.stat(:next_shape_id)+. + # If _shapes_ is a positive integer, only shapes newer than the provided + # shape id are dumped. The current shape_id can be accessed using <tt>RubyVM.stat(:next_shape_id)</tt>. # - # If _shapes_ is +false+, no shapes are dumped. + # If _shapes_ is +false+, no shapes are dumped. # - # To only dump objects allocated past a certain point you can combine _since_ and _shapes_: - # ObjectSpace.trace_object_allocations - # GC.start - # gc_generation = GC.count - # shape_generation = RubyVM.stat(:next_shape_id) - #. call_method_to_instrument - # ObjectSpace.dump_all(since: gc_generation, shapes: shape_generation) + # To only dump objects allocated past a certain point you can combine _since_ and _shapes_: + # ObjectSpace.trace_object_allocations + # GC.start + # gc_generation = GC.count + # shape_generation = RubyVM.stat(:next_shape_id) + # call_method_to_instrument + # ObjectSpace.dump_all(since: gc_generation, shapes: shape_generation) # - # This method is only expected to work with C Ruby. - # This is an experimental method and is subject to change. - # In particular, the function signature and output format are - # not guaranteed to be compatible in future versions of ruby. + # This method is only expected to work with C Ruby. + # This is an experimental method and is subject to change. + # In particular, the function signature and output format are + # not guaranteed to be compatible in future versions of ruby. def dump_all(output: :file, full: false, since: nil, shapes: true) out = case output when :file, nil @@ -107,18 +105,16 @@ module ObjectSpace end # call-seq: - # ObjectSpace.dump_shapes([output: :file]) # => #<File:/tmp/rubyshapes20131125-88469-laoj3v.json> - # ObjectSpace.dump_shapes(output: :stdout) # => nil - # ObjectSpace.dump_shapes(output: :string) # => "{...}\n{...}\n..." - # ObjectSpace.dump_shapes(output: - # File.open('shapes.json','w')) # => #<File:shapes.json> - # ObjectSpace.dump_all(output: :string, - # since: 42) # => "{...}\n{...}\n..." + # ObjectSpace.dump_shapes([output: :file]) -> #<File:/tmp/rubyshapes20131125-88469-laoj3v.json> + # ObjectSpace.dump_shapes(output: :stdout) -> nil + # ObjectSpace.dump_shapes(output: :string) -> "{...}\n{...}\n..." + # ObjectSpace.dump_shapes(output: File.open('shapes.json','w')) -> #<File:shapes.json> + # ObjectSpace.dump_all(output: :string, since: 42) -> "{...}\n{...}\n..." # # Dump the contents of the ruby shape tree as JSON. # # If _shapes_ is a positive integer, only shapes newer than the provided - # shape id are dumped. The current shape_id can be accessed using +RubyVM.stat(:next_shape_id)+. + # shape id are dumped. The current shape_id can be accessed using <tt>RubyVM.stat(:next_shape_id)</tt>. # # This method is only expected to work with C Ruby. # This is an experimental method and is subject to change. diff --git a/ext/objspace/object_tracing.c b/ext/objspace/object_tracing.c index 0bf866a8f1..8c54d51eab 100644 --- a/ext/objspace/object_tracing.c +++ b/ext/objspace/object_tracing.c @@ -13,6 +13,7 @@ **********************************************************************/ +#include "gc.h" #include "internal.h" #include "ruby/debug.h" #include "objspace.h" @@ -121,6 +122,10 @@ freeobj_i(VALUE tpval, void *data) st_data_t v; struct allocation_info *info; + /* Modifying the st table can cause allocations, which can trigger GC. + * Since freeobj_i is called during GC, it must not trigger another GC. */ + VALUE gc_disabled = rb_gc_disable_no_rest(); + if (arg->keep_remains) { if (st_lookup(arg->object_table, obj, &v)) { info = (struct allocation_info *)v; @@ -135,6 +140,8 @@ freeobj_i(VALUE tpval, void *data) ruby_xfree(info); } } + + if (gc_disabled == Qfalse) rb_gc_enable(); } static int diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c index 5e2b417fbe..c3cc9a1e7b 100644 --- a/ext/objspace/objspace_dump.c +++ b/ext/objspace/objspace_dump.c @@ -13,8 +13,10 @@ **********************************************************************/ #include "gc.h" +#include "id_table.h" #include "internal.h" #include "internal/array.h" +#include "internal/class.h" #include "internal/hash.h" #include "internal/string.h" #include "internal/sanitizers.h" @@ -383,7 +385,11 @@ dump_object(VALUE obj, struct dump_config *dc) dc->cur_obj = obj; dc->cur_obj_references = 0; - dc->cur_obj_klass = BUILTIN_TYPE(obj) == T_NODE ? 0 : RBASIC_CLASS(obj); + if (BUILTIN_TYPE(obj) == T_NODE || BUILTIN_TYPE(obj) == T_IMEMO) { + dc->cur_obj_klass = 0; + } else { + dc->cur_obj_klass = RBASIC_CLASS(obj); + } if (dc->partial_dump && (!ainfo || ainfo->generation < dc->since)) { return; @@ -400,10 +406,8 @@ dump_object(VALUE obj, struct dump_config *dc) dump_append(dc, "\""); size_t shape_id = rb_shape_get_shape_id(obj); - if (shape_id) { - dump_append(dc, ", \"shape_id\":"); - dump_append_sizet(dc, shape_id); - } + 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); @@ -494,6 +498,9 @@ dump_object(VALUE obj, struct dump_config *dc) break; case T_CLASS: + dump_append(dc, ", \"variation_count\":"); + dump_append_d(dc, RCLASS_EXT(obj)->variation_count); + case T_MODULE: if (rb_class_get_superclass(obj)) { dump_append(dc, ", \"superclass\":"); @@ -538,7 +545,10 @@ dump_object(VALUE obj, struct dump_config *dc) case T_OBJECT: dump_append(dc, ", \"ivars\":"); - dump_append_lu(dc, ROBJECT_IV_CAPACITY(obj)); + dump_append_lu(dc, ROBJECT_IV_COUNT(obj)); + if (rb_shape_obj_too_complex(obj)) { + dump_append(dc, ", \"too_complex_shape\":true"); + } break; case T_FILE: @@ -686,6 +696,7 @@ dump_result(struct dump_config *dc) } } +/* :nodoc: */ static VALUE objspace_dump(VALUE os, VALUE obj, VALUE output) { @@ -726,7 +737,7 @@ shape_i(rb_shape_t *shape, void *data) dump_append_sizet(dc, rb_shape_depth(shape)); dump_append(dc, ", \"shape_type\":"); - switch(shape->type) { + switch((enum shape_type)shape->type) { case SHAPE_ROOT: dump_append(dc, "\"ROOT\""); break; @@ -753,6 +764,9 @@ shape_i(rb_shape_t *shape, void *data) case SHAPE_T_OBJECT: dump_append(dc, "\"T_OBJECT\""); break; + case SHAPE_OBJ_TOO_COMPLEX: + dump_append(dc, "\"OBJ_TOO_COMPLEX\""); + break; default: rb_bug("[objspace] unexpected shape type"); } @@ -766,6 +780,7 @@ shape_i(rb_shape_t *shape, void *data) dump_append(dc, "}\n"); } +/* :nodoc: */ static VALUE objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since, VALUE shapes) { @@ -788,6 +803,7 @@ objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since, VALUE shapes) return dump_result(&dc); } +/* :nodoc: */ static VALUE objspace_dump_shapes(VALUE os, VALUE output, VALUE shapes) { diff --git a/ext/openssl/History.md b/ext/openssl/History.md index a4f6bd7fd6..1e0df7dd87 100644 --- a/ext/openssl/History.md +++ b/ext/openssl/History.md @@ -1,3 +1,53 @@ +Version 3.1.0 +============= + +Ruby/OpenSSL 3.1 will be maintained for the lifetime of Ruby 3.2. + +Merged bug fixes in 2.2.3 and 3.0.2. Among the new features and changes are: + +Notable changes +--------------- + +* Add `OpenSSL::SSL::SSLContext#ciphersuites=` to allow setting TLS 1.3 cipher + suites. + [[GitHub #493]](https://github.com/ruby/openssl/pull/493) +* Add `OpenSSL::SSL::SSLSocket#export_keying_material` for exporting keying + material of the session, as defined in RFC 5705. + [[GitHub #530]](https://github.com/ruby/openssl/pull/530) +* Add `OpenSSL::SSL::SSLContext#keylog_cb=` for setting the TLS key logging + callback, which is useful for supporting NSS's SSLKEYLOGFILE debugging output. + [[GitHub #536]](https://github.com/ruby/openssl/pull/536) +* Remove the default digest algorithm from `OpenSSL::OCSP::BasicResponse#sign` + and `OpenSSL::OCSP::Request#sign`. Omitting the 5th parameter of these + methods used to be equivalent of specifying SHA-1. This default value is now + removed and we will let the underlying OpenSSL library decide instead. + [[GitHub #507]](https://github.com/ruby/openssl/pull/507) +* Add `OpenSSL::BN#mod_sqrt`. + [[GitHub #553]](https://github.com/ruby/openssl/pull/553) +* Allow calling `OpenSSL::Cipher#update` with an empty string. This was + prohibited to workaround an ancient bug in OpenSSL. + [[GitHub #568]](https://github.com/ruby/openssl/pull/568) +* Fix build on platforms without socket support, such as WASI. `OpenSSL::SSL` + will not be defined if OpenSSL is compiled with `OPENSSL_NO_SOCK`. + [[GitHub #558]](https://github.com/ruby/openssl/pull/558) +* Improve support for recent LibreSSL versions. This includes HKDF support in + LibreSSL 3.6 and Ed25519 support in LibreSSL 3.7. + + +Version 3.0.2 +============= + +Merged changes in 2.2.3. Additionally, the following issues are fixed by this +release. + +Bug fixes +--------- + +* Fix OpenSSL::PKey::EC#check_key not working correctly on OpenSSL 3.0. + [[GitHub #563]](https://github.com/ruby/openssl/issues/563) + [[GitHub #580]](https://github.com/ruby/openssl/pull/580) + + Version 3.0.1 ============= @@ -124,6 +174,21 @@ Notable changes [[GitHub #342]](https://github.com/ruby/openssl/issues/342) +Version 2.2.3 +============= + +Bug fixes +--------- + +* Fix serveral methods in OpenSSL::PKey::EC::Point attempting to raise an error + with an incorrect class, which would end up with a TypeError. + [[GitHub #570]](https://github.com/ruby/openssl/pull/570) +* Fix OpenSSL::PKey::EC::Point#eql? and OpenSSL::PKey::EC::Group#eql? + incorrectly treated OpenSSL's internal errors as "not equal". + [[GitHub #564]](https://github.com/ruby/openssl/pull/564) +* Fix build with LibreSSL 3.5 or later. + + Version 2.2.2 ============= diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index fd96533569..bc3e4d3a21 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -25,8 +25,9 @@ Logging::message "=== OpenSSL for Ruby configurator ===\n" if with_config("debug") or enable_config("debug") $defs.push("-DOSSL_DEBUG") end +$defs.push("-D""OPENSSL_SUPPRESS_DEPRECATED") -have_func("rb_io_maybe_wait") # Ruby 3.1 +have_func("rb_io_maybe_wait(0, Qnil, Qnil, Qnil)", "ruby/io.h") # Ruby 3.1 Logging::message "=== Checking for system dependent stuff... ===\n" have_library("nsl", "t_open") @@ -126,7 +127,7 @@ ts_h = "openssl/ts.h".freeze ssl_h = "openssl/ssl.h".freeze # compile options -have_func("RAND_egd", "openssl/rand.h") +have_func("RAND_egd()", "openssl/rand.h") engines = %w{dynamic 4758cca aep atalla chil cswift nuron sureware ubsec padlock capi gmp gost cryptodev} engines.each { |name| @@ -137,66 +138,59 @@ engines.each { |name| if !have_struct_member("SSL", "ctx", "openssl/ssl.h") || is_libressl $defs.push("-DHAVE_OPAQUE_OPENSSL") end -have_func("EVP_MD_CTX_new", evp_h) -have_func("EVP_MD_CTX_free", evp_h) -have_func("EVP_MD_CTX_pkey_ctx", evp_h) -have_func("X509_STORE_get_ex_data", x509_h) -have_func("X509_STORE_set_ex_data", x509_h) +have_func("EVP_MD_CTX_new()", evp_h) +have_func("EVP_MD_CTX_free(NULL)", evp_h) +have_func("EVP_MD_CTX_pkey_ctx(NULL)", evp_h) +have_func("X509_STORE_get_ex_data(NULL, 0)", x509_h) +have_func("X509_STORE_set_ex_data(NULL, 0, NULL)", x509_h) have_func("X509_STORE_get_ex_new_index(0, NULL, NULL, NULL, NULL)", x509_h) -have_func("X509_CRL_get0_signature", x509_h) -have_func("X509_REQ_get0_signature", x509_h) -have_func("X509_REVOKED_get0_serialNumber", x509_h) -have_func("X509_REVOKED_get0_revocationDate", x509_h) -have_func("X509_get0_tbs_sigalg", x509_h) -have_func("X509_STORE_CTX_get0_untrusted", x509_h) -have_func("X509_STORE_CTX_get0_cert", x509_h) -have_func("X509_STORE_CTX_get0_chain", x509_h) -have_func("OCSP_SINGLERESP_get0_id", "openssl/ocsp.h") -have_func("SSL_CTX_get_ciphers", ssl_h) -have_func("X509_up_ref", x509_h) -have_func("X509_CRL_up_ref", x509_h) -have_func("X509_STORE_up_ref", x509_h) -have_func("SSL_SESSION_up_ref", ssl_h) -have_func("EVP_PKEY_up_ref", evp_h) +have_func("X509_CRL_get0_signature(NULL, NULL, NULL)", x509_h) +have_func("X509_REQ_get0_signature(NULL, NULL, NULL)", x509_h) +have_func("X509_REVOKED_get0_serialNumber(NULL)", x509_h) +have_func("X509_REVOKED_get0_revocationDate(NULL)", x509_h) +have_func("X509_get0_tbs_sigalg(NULL)", x509_h) +have_func("X509_STORE_CTX_get0_untrusted(NULL)", x509_h) +have_func("X509_STORE_CTX_get0_cert(NULL)", x509_h) +have_func("X509_STORE_CTX_get0_chain(NULL)", x509_h) +have_func("OCSP_SINGLERESP_get0_id(NULL)", "openssl/ocsp.h") +have_func("SSL_CTX_get_ciphers(NULL)", ssl_h) +have_func("X509_up_ref(NULL)", x509_h) +have_func("X509_CRL_up_ref(NULL)", x509_h) +have_func("X509_STORE_up_ref(NULL)", x509_h) +have_func("SSL_SESSION_up_ref(NULL)", ssl_h) +have_func("EVP_PKEY_up_ref(NULL)", evp_h) have_func("SSL_CTX_set_min_proto_version(NULL, 0)", ssl_h) -have_func("SSL_CTX_get_security_level", ssl_h) -have_func("X509_get0_notBefore", x509_h) -have_func("SSL_SESSION_get_protocol_version", ssl_h) -have_func("TS_STATUS_INFO_get0_status", ts_h) -have_func("TS_STATUS_INFO_get0_text", ts_h) -have_func("TS_STATUS_INFO_get0_failure_info", ts_h) +have_func("SSL_CTX_get_security_level(NULL)", ssl_h) +have_func("X509_get0_notBefore(NULL)", x509_h) +have_func("SSL_SESSION_get_protocol_version(NULL)", ssl_h) +have_func("TS_STATUS_INFO_get0_status(NULL)", ts_h) +have_func("TS_STATUS_INFO_get0_text(NULL)", ts_h) +have_func("TS_STATUS_INFO_get0_failure_info(NULL)", ts_h) have_func("TS_VERIFY_CTS_set_certs(NULL, NULL)", ts_h) -have_func("TS_VERIFY_CTX_set_store", ts_h) -have_func("TS_VERIFY_CTX_add_flags", ts_h) -have_func("TS_RESP_CTX_set_time_cb", ts_h) -have_func("EVP_PBE_scrypt", evp_h) -have_func("SSL_CTX_set_post_handshake_auth", ssl_h) +have_func("TS_VERIFY_CTX_set_store(NULL, NULL)", ts_h) +have_func("TS_VERIFY_CTX_add_flags(NULL, 0)", ts_h) +have_func("TS_RESP_CTX_set_time_cb(NULL, NULL, NULL)", ts_h) +have_func("EVP_PBE_scrypt(\"\", 0, (unsigned char *)\"\", 0, 0, 0, 0, 0, NULL, 0)", evp_h) +have_func("SSL_CTX_set_post_handshake_auth(NULL, 0)", ssl_h) # added in 1.1.1 -have_func("EVP_PKEY_check", evp_h) -have_func("EVP_PKEY_new_raw_private_key", evp_h) -have_func("SSL_CTX_set_ciphersuites", ssl_h) +have_func("EVP_PKEY_check(NULL)", evp_h) +have_func("EVP_PKEY_new_raw_private_key(0, NULL, (unsigned char *)\"\", 0)", evp_h) +have_func("SSL_CTX_set_ciphersuites(NULL, \"\")", ssl_h) # added in 3.0.0 -openssl_3 = -have_func("SSL_set0_tmp_dh_pkey", ssl_h) -have_func("ERR_get_error_all", "openssl/err.h") +have_func("SSL_set0_tmp_dh_pkey(NULL, NULL)", ssl_h) +have_func("ERR_get_error_all(NULL, NULL, NULL, NULL, NULL)", "openssl/err.h") have_func("TS_VERIFY_CTX_set_certs(NULL, NULL)", ts_h) -have_func("SSL_CTX_load_verify_file", ssl_h) -have_func("BN_check_prime", "openssl/bn.h") -have_func("EVP_MD_CTX_get0_md", evp_h) -have_func("EVP_MD_CTX_get_pkey_ctx", evp_h) -have_func("EVP_PKEY_eq", evp_h) -have_func("EVP_PKEY_dup", evp_h) +have_func("SSL_CTX_load_verify_file(NULL, \"\")", ssl_h) +have_func("BN_check_prime(NULL, NULL, NULL)", "openssl/bn.h") +have_func("EVP_MD_CTX_get0_md(NULL)", evp_h) +have_func("EVP_MD_CTX_get_pkey_ctx(NULL)", evp_h) +have_func("EVP_PKEY_eq(NULL, NULL)", evp_h) +have_func("EVP_PKEY_dup(NULL)", evp_h) Logging::message "=== Checking done. ===\n" -if openssl_3 - if $warnflags&.sub!(/-W\K(?=deprecated-declarations)/, 'no-') - $warnflags << " -Wno-incompatible-pointer-types-discards-qualifiers" - end -end - create_header create_makefile("openssl") Logging::message "Done.\n" diff --git a/ext/openssl/lib/openssl/ssl.rb b/ext/openssl/lib/openssl/ssl.rb index a9103ecd27..ea8bb2a18e 100644 --- a/ext/openssl/lib/openssl/ssl.rb +++ b/ext/openssl/lib/openssl/ssl.rb @@ -11,6 +11,9 @@ =end require "openssl/buffering" + +if defined?(OpenSSL::SSL) + require "io/nonblock" require "ipaddr" require "socket" @@ -540,3 +543,5 @@ YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 end end end + +end diff --git a/ext/openssl/lib/openssl/version.rb b/ext/openssl/lib/openssl/version.rb index 80597c0743..4163f55064 100644 --- a/ext/openssl/lib/openssl/version.rb +++ b/ext/openssl/lib/openssl/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module OpenSSL - VERSION = "3.1.0.pre" + VERSION = "3.1.0" end diff --git a/ext/openssl/openssl.gemspec b/ext/openssl/openssl.gemspec index d3d8be05db..8d83b69193 100644 --- a/ext/openssl/openssl.gemspec +++ b/ext/openssl/openssl.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "openssl" - spec.version = "3.1.0.pre" + spec.version = "3.1.0" spec.authors = ["Martin Bosslet", "SHIBATA Hiroshi", "Zachary Scott", "Kazuki Yamaguchi"] spec.email = ["ruby-core@ruby-lang.org"] spec.summary = %q{OpenSSL provides SSL, TLS and general purpose cryptography.} diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index 2ab8aeaebb..facb80aa73 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -52,6 +52,12 @@ (LIBRESSL_VERSION_NUMBER >= ((maj << 28) | (min << 20) | (pat << 12))) #endif +#if OSSL_OPENSSL_PREREQ(3, 0, 0) +# define OSSL_3_const const +#else +# define OSSL_3_const /* const */ +#endif + #if !defined(OPENSSL_NO_ENGINE) && !OSSL_OPENSSL_PREREQ(3, 0, 0) # define OSSL_USE_ENGINE #endif diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index d9c7891433..cb8fbc3ca2 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -384,8 +384,7 @@ ossl_cipher_update(int argc, VALUE *argv, VALUE self) StringValue(data); in = (unsigned char *)RSTRING_PTR(data); - if ((in_len = RSTRING_LEN(data)) == 0) - ossl_raise(rb_eArgError, "data must not be empty"); + in_len = RSTRING_LEN(data); GetCipher(self, ctx); out_len = in_len+EVP_CIPHER_CTX_block_size(ctx); if (out_len <= 0) { diff --git a/ext/openssl/ossl_kdf.c b/ext/openssl/ossl_kdf.c index 7fa38b865e..0d25a7304b 100644 --- a/ext/openssl/ossl_kdf.c +++ b/ext/openssl/ossl_kdf.c @@ -3,7 +3,7 @@ * Copyright (C) 2007, 2017 Ruby/OpenSSL Project Authors */ #include "ossl.h" -#if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) +#if OSSL_OPENSSL_PREREQ(1, 1, 0) || OSSL_LIBRESSL_PREREQ(3, 6, 0) # include <openssl/kdf.h> #endif @@ -141,7 +141,7 @@ kdf_scrypt(int argc, VALUE *argv, VALUE self) } #endif -#if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) +#if OSSL_OPENSSL_PREREQ(1, 1, 0) || OSSL_LIBRESSL_PREREQ(3, 6, 0) /* * call-seq: * KDF.hkdf(ikm, salt:, info:, length:, hash:) -> String @@ -305,7 +305,7 @@ Init_ossl_kdf(void) #if defined(HAVE_EVP_PBE_SCRYPT) rb_define_module_function(mKDF, "scrypt", kdf_scrypt, -1); #endif -#if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) +#if OSSL_OPENSSL_PREREQ(1, 1, 0) || OSSL_LIBRESSL_PREREQ(3, 6, 0) rb_define_module_function(mKDF, "hkdf", kdf_hkdf, -1); #endif } diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index ec39e8bd77..476256679b 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -951,7 +951,7 @@ ossl_pkey_sign(int argc, VALUE *argv, VALUE self) rb_jump_tag(state); } } -#if OPENSSL_VERSION_NUMBER >= 0x10101000 && !defined(LIBRESSL_VERSION_NUMBER) +#if OSSL_OPENSSL_PREREQ(1, 1, 1) || OSSL_LIBRESSL_PREREQ(3, 4, 0) if (EVP_DigestSign(ctx, NULL, &siglen, (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data)) < 1) { EVP_MD_CTX_free(ctx); @@ -1056,7 +1056,7 @@ ossl_pkey_verify(int argc, VALUE *argv, VALUE self) rb_jump_tag(state); } } -#if OPENSSL_VERSION_NUMBER >= 0x10101000 && !defined(LIBRESSL_VERSION_NUMBER) +#if OSSL_OPENSSL_PREREQ(1, 1, 1) || OSSL_LIBRESSL_PREREQ(3, 4, 0) ret = EVP_DigestVerify(ctx, (unsigned char *)RSTRING_PTR(sig), RSTRING_LEN(sig), (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data)); diff --git a/ext/openssl/ossl_pkey.h b/ext/openssl/ossl_pkey.h index 38fb9fad10..10669b824c 100644 --- a/ext/openssl/ossl_pkey.h +++ b/ext/openssl/ossl_pkey.h @@ -92,7 +92,7 @@ void Init_ossl_ec(void); */ \ static VALUE ossl_##_keytype##_get_##_name(VALUE self) \ { \ - _type *obj; \ + const _type *obj; \ const BIGNUM *bn; \ \ Get##_type(self, obj); \ diff --git a/ext/openssl/ossl_pkey_dh.c b/ext/openssl/ossl_pkey_dh.c index 696455dcfd..83c41378fe 100644 --- a/ext/openssl/ossl_pkey_dh.c +++ b/ext/openssl/ossl_pkey_dh.c @@ -178,7 +178,7 @@ ossl_dh_initialize_copy(VALUE self, VALUE other) static VALUE ossl_dh_is_public(VALUE self) { - DH *dh; + OSSL_3_const DH *dh; const BIGNUM *bn; GetDH(self, dh); @@ -197,14 +197,14 @@ ossl_dh_is_public(VALUE self) static VALUE ossl_dh_is_private(VALUE self) { - DH *dh; + OSSL_3_const DH *dh; const BIGNUM *bn; GetDH(self, dh); DH_get0_key(dh, NULL, &bn); #if !defined(OPENSSL_NO_ENGINE) - return (bn || DH_get0_engine(dh)) ? Qtrue : Qfalse; + return (bn || DH_get0_engine((DH *)dh)) ? Qtrue : Qfalse; #else return bn ? Qtrue : Qfalse; #endif @@ -223,7 +223,7 @@ ossl_dh_is_private(VALUE self) static VALUE ossl_dh_export(VALUE self) { - DH *dh; + OSSL_3_const DH *dh; BIO *out; VALUE str; @@ -252,7 +252,7 @@ ossl_dh_export(VALUE self) static VALUE ossl_dh_to_der(VALUE self) { - DH *dh; + OSSL_3_const DH *dh; unsigned char *p; long len; VALUE str; @@ -280,7 +280,7 @@ ossl_dh_to_der(VALUE self) static VALUE ossl_dh_get_params(VALUE self) { - DH *dh; + OSSL_3_const DH *dh; VALUE hash; const BIGNUM *p, *q, *g, *pub_key, *priv_key; diff --git a/ext/openssl/ossl_pkey_dsa.c b/ext/openssl/ossl_pkey_dsa.c index 25404aa7f5..b097f8c9d2 100644 --- a/ext/openssl/ossl_pkey_dsa.c +++ b/ext/openssl/ossl_pkey_dsa.c @@ -24,7 +24,7 @@ } while (0) static inline int -DSA_HAS_PRIVATE(DSA *dsa) +DSA_HAS_PRIVATE(OSSL_3_const DSA *dsa) { const BIGNUM *bn; DSA_get0_key(dsa, NULL, &bn); @@ -32,7 +32,7 @@ DSA_HAS_PRIVATE(DSA *dsa) } static inline int -DSA_PRIVATE(VALUE obj, DSA *dsa) +DSA_PRIVATE(VALUE obj, OSSL_3_const DSA *dsa) { return DSA_HAS_PRIVATE(dsa) || OSSL_PKEY_IS_PRIVATE(obj); } @@ -179,7 +179,7 @@ ossl_dsa_initialize_copy(VALUE self, VALUE other) static VALUE ossl_dsa_is_public(VALUE self) { - DSA *dsa; + const DSA *dsa; const BIGNUM *bn; GetDSA(self, dsa); @@ -198,7 +198,7 @@ ossl_dsa_is_public(VALUE self) static VALUE ossl_dsa_is_private(VALUE self) { - DSA *dsa; + OSSL_3_const DSA *dsa; GetDSA(self, dsa); @@ -225,7 +225,7 @@ ossl_dsa_is_private(VALUE self) static VALUE ossl_dsa_export(int argc, VALUE *argv, VALUE self) { - DSA *dsa; + OSSL_3_const DSA *dsa; GetDSA(self, dsa); if (DSA_HAS_PRIVATE(dsa)) @@ -244,7 +244,7 @@ ossl_dsa_export(int argc, VALUE *argv, VALUE self) static VALUE ossl_dsa_to_der(VALUE self) { - DSA *dsa; + OSSL_3_const DSA *dsa; GetDSA(self, dsa); if (DSA_HAS_PRIVATE(dsa)) @@ -265,7 +265,7 @@ ossl_dsa_to_der(VALUE self) static VALUE ossl_dsa_get_params(VALUE self) { - DSA *dsa; + OSSL_3_const DSA *dsa; VALUE hash; const BIGNUM *p, *q, *g, *pub_key, *priv_key; diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index 06d59c2a4f..92842f95ac 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -227,7 +227,7 @@ ossl_ec_key_initialize_copy(VALUE self, VALUE other) static VALUE ossl_ec_key_get_group(VALUE self) { - EC_KEY *ec; + OSSL_3_const EC_KEY *ec; const EC_GROUP *group; GetEC(self, ec); @@ -272,7 +272,7 @@ ossl_ec_key_set_group(VALUE self, VALUE group_v) */ static VALUE ossl_ec_key_get_private_key(VALUE self) { - EC_KEY *ec; + OSSL_3_const EC_KEY *ec; const BIGNUM *bn; GetEC(self, ec); @@ -323,7 +323,7 @@ static VALUE ossl_ec_key_set_private_key(VALUE self, VALUE private_key) */ static VALUE ossl_ec_key_get_public_key(VALUE self) { - EC_KEY *ec; + OSSL_3_const EC_KEY *ec; const EC_POINT *point; GetEC(self, ec); @@ -375,7 +375,7 @@ static VALUE ossl_ec_key_set_public_key(VALUE self, VALUE public_key) */ static VALUE ossl_ec_key_is_public(VALUE self) { - EC_KEY *ec; + OSSL_3_const EC_KEY *ec; GetEC(self, ec); @@ -391,7 +391,7 @@ static VALUE ossl_ec_key_is_public(VALUE self) */ static VALUE ossl_ec_key_is_private(VALUE self) { - EC_KEY *ec; + OSSL_3_const EC_KEY *ec; GetEC(self, ec); @@ -411,7 +411,7 @@ static VALUE ossl_ec_key_is_private(VALUE self) static VALUE ossl_ec_key_export(int argc, VALUE *argv, VALUE self) { - EC_KEY *ec; + OSSL_3_const EC_KEY *ec; GetEC(self, ec); if (EC_KEY_get0_public_key(ec) == NULL) @@ -431,7 +431,7 @@ ossl_ec_key_export(int argc, VALUE *argv, VALUE self) static VALUE ossl_ec_key_to_der(VALUE self) { - EC_KEY *ec; + OSSL_3_const EC_KEY *ec; GetEC(self, ec); if (EC_KEY_get0_public_key(ec) == NULL) @@ -483,16 +483,28 @@ static VALUE ossl_ec_key_check_key(VALUE self) #ifdef HAVE_EVP_PKEY_CHECK EVP_PKEY *pkey; EVP_PKEY_CTX *pctx; - int ret; + const EC_KEY *ec; GetPKey(self, pkey); + GetEC(self, ec); pctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); if (!pctx) - ossl_raise(eDHError, "EVP_PKEY_CTX_new"); - ret = EVP_PKEY_public_check(pctx); + ossl_raise(eECError, "EVP_PKEY_CTX_new"); + + if (EC_KEY_get0_private_key(ec) != NULL) { + if (EVP_PKEY_check(pctx) != 1) { + EVP_PKEY_CTX_free(pctx); + ossl_raise(eECError, "EVP_PKEY_check"); + } + } + else { + if (EVP_PKEY_public_check(pctx) != 1) { + EVP_PKEY_CTX_free(pctx); + ossl_raise(eECError, "EVP_PKEY_public_check"); + } + } + EVP_PKEY_CTX_free(pctx); - if (ret != 1) - ossl_raise(eECError, "EVP_PKEY_public_check"); #else EC_KEY *ec; @@ -668,10 +680,11 @@ static VALUE ossl_ec_group_eql(VALUE a, VALUE b) GetECGroup(a, group1); GetECGroup(b, group2); - if (EC_GROUP_cmp(group1, group2, ossl_bn_ctx) == 1) - return Qfalse; - - return Qtrue; + switch (EC_GROUP_cmp(group1, group2, ossl_bn_ctx)) { + case 0: return Qtrue; + case 1: return Qfalse; + default: ossl_raise(eEC_GROUP, "EC_GROUP_cmp"); + } } /* @@ -1232,10 +1245,13 @@ static VALUE ossl_ec_point_eql(VALUE a, VALUE b) GetECPoint(b, point2); GetECGroup(group_v1, group); - if (EC_POINT_cmp(group, point1, point2, ossl_bn_ctx) == 1) - return Qfalse; + switch (EC_POINT_cmp(group, point1, point2, ossl_bn_ctx)) { + case 0: return Qtrue; + case 1: return Qfalse; + default: ossl_raise(eEC_POINT, "EC_POINT_cmp"); + } - return Qtrue; + UNREACHABLE; } /* @@ -1253,7 +1269,7 @@ static VALUE ossl_ec_point_is_at_infinity(VALUE self) switch (EC_POINT_is_at_infinity(group, point)) { case 1: return Qtrue; case 0: return Qfalse; - default: ossl_raise(cEC_POINT, "EC_POINT_is_at_infinity"); + default: ossl_raise(eEC_POINT, "EC_POINT_is_at_infinity"); } UNREACHABLE; @@ -1274,7 +1290,7 @@ static VALUE ossl_ec_point_is_on_curve(VALUE self) switch (EC_POINT_is_on_curve(group, point, ossl_bn_ctx)) { case 1: return Qtrue; case 0: return Qfalse; - default: ossl_raise(cEC_POINT, "EC_POINT_is_on_curve"); + default: ossl_raise(eEC_POINT, "EC_POINT_is_on_curve"); } UNREACHABLE; @@ -1297,7 +1313,7 @@ static VALUE ossl_ec_point_make_affine(VALUE self) rb_warn("OpenSSL::PKey::EC::Point#make_affine! is deprecated"); #if !OSSL_OPENSSL_PREREQ(3, 0, 0) if (EC_POINT_make_affine(group, point, ossl_bn_ctx) != 1) - ossl_raise(cEC_POINT, "EC_POINT_make_affine"); + ossl_raise(eEC_POINT, "EC_POINT_make_affine"); #endif return self; @@ -1316,7 +1332,7 @@ static VALUE ossl_ec_point_invert(VALUE self) GetECPointGroup(self, group); if (EC_POINT_invert(group, point, ossl_bn_ctx) != 1) - ossl_raise(cEC_POINT, "EC_POINT_invert"); + ossl_raise(eEC_POINT, "EC_POINT_invert"); return self; } @@ -1334,7 +1350,7 @@ static VALUE ossl_ec_point_set_to_infinity(VALUE self) GetECPointGroup(self, group); if (EC_POINT_set_to_infinity(group, point) != 1) - ossl_raise(cEC_POINT, "EC_POINT_set_to_infinity"); + ossl_raise(eEC_POINT, "EC_POINT_set_to_infinity"); return self; } diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c index 4d66010f49..072adabe62 100644 --- a/ext/openssl/ossl_pkey_rsa.c +++ b/ext/openssl/ossl_pkey_rsa.c @@ -24,7 +24,7 @@ } while (0) static inline int -RSA_HAS_PRIVATE(RSA *rsa) +RSA_HAS_PRIVATE(OSSL_3_const RSA *rsa) { const BIGNUM *e, *d; @@ -33,7 +33,7 @@ RSA_HAS_PRIVATE(RSA *rsa) } static inline int -RSA_PRIVATE(VALUE obj, RSA *rsa) +RSA_PRIVATE(VALUE obj, OSSL_3_const RSA *rsa) { return RSA_HAS_PRIVATE(rsa) || OSSL_PKEY_IS_PRIVATE(obj); } @@ -174,7 +174,7 @@ ossl_rsa_initialize_copy(VALUE self, VALUE other) static VALUE ossl_rsa_is_public(VALUE self) { - RSA *rsa; + OSSL_3_const RSA *rsa; GetRSA(self, rsa); /* @@ -193,7 +193,7 @@ ossl_rsa_is_public(VALUE self) static VALUE ossl_rsa_is_private(VALUE self) { - RSA *rsa; + OSSL_3_const RSA *rsa; GetRSA(self, rsa); @@ -203,7 +203,7 @@ ossl_rsa_is_private(VALUE self) static int can_export_rsaprivatekey(VALUE self) { - RSA *rsa; + OSSL_3_const RSA *rsa; const BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp; GetRSA(self, rsa); @@ -453,7 +453,7 @@ ossl_rsa_verify_pss(int argc, VALUE *argv, VALUE self) static VALUE ossl_rsa_get_params(VALUE self) { - RSA *rsa; + OSSL_3_const RSA *rsa; VALUE hash; const BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp; diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 478ff869a4..f63992664a 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -11,11 +11,15 @@ */ #include "ossl.h" +#ifndef OPENSSL_NO_SOCK #define numberof(ary) (int)(sizeof(ary)/sizeof((ary)[0])) +#if !defined(OPENSSL_NO_NEXTPROTONEG) && !OSSL_IS_LIBRESSL +# define OSSL_USE_NEXTPROTONEG +#endif + #if !defined(TLS1_3_VERSION) && \ - defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER >= 0x3020000fL + OSSL_LIBRESSL_PREREQ(3, 2, 0) && !OSSL_LIBRESSL_PREREQ(3, 4, 0) # define TLS1_3_VERSION 0x0304 #endif @@ -30,7 +34,6 @@ } while (0) VALUE mSSL; -static VALUE mSSLExtConfig; static VALUE eSSLError; VALUE cSSLContext; VALUE cSSLSocket; @@ -291,7 +294,7 @@ ossl_tmp_dh_callback(SSL *ssl, int is_export, int keylength) if (!pkey) return NULL; - return EVP_PKEY_get0_DH(pkey); + return (DH *)EVP_PKEY_get0_DH(pkey); } #endif /* OPENSSL_NO_DH */ @@ -703,7 +706,7 @@ ssl_npn_select_cb_common(SSL *ssl, VALUE cb, const unsigned char **out, return SSL_TLSEXT_ERR_OK; } -#ifndef OPENSSL_NO_NEXTPROTONEG +#ifdef OSSL_USE_NEXTPROTONEG static int ssl_npn_advertise_cb(SSL *ssl, const unsigned char **out, unsigned int *outlen, void *arg) @@ -900,7 +903,7 @@ ossl_sslctx_setup(VALUE self) val = rb_attr_get(self, id_i_verify_depth); if(!NIL_P(val)) SSL_CTX_set_verify_depth(ctx, NUM2INT(val)); -#ifndef OPENSSL_NO_NEXTPROTONEG +#ifdef OSSL_USE_NEXTPROTONEG val = rb_attr_get(self, id_i_npn_protocols); if (!NIL_P(val)) { VALUE encoded = ssl_encode_npn_protocols(val); @@ -1538,7 +1541,6 @@ ossl_sslctx_flush_sessions(int argc, VALUE *argv, VALUE self) /* * SSLSocket class */ -#ifndef OPENSSL_NO_SOCK static inline int ssl_started(SSL *ssl) { @@ -2446,7 +2448,7 @@ ossl_ssl_get_client_ca_list(VALUE self) return ossl_x509name_sk2ary(ca); } -# ifndef OPENSSL_NO_NEXTPROTONEG +# ifdef OSSL_USE_NEXTPROTONEG /* * call-seq: * ssl.npn_protocol => String | nil @@ -2566,6 +2568,7 @@ Init_ossl_ssl(void) rb_mWaitWritable = rb_define_module_under(rb_cIO, "WaitWritable"); #endif +#ifndef OPENSSL_NO_SOCK id_call = rb_intern_const("call"); ID_callback_state = rb_intern_const("callback_state"); @@ -2588,16 +2591,6 @@ Init_ossl_ssl(void) */ mSSL = rb_define_module_under(mOSSL, "SSL"); - /* Document-module: OpenSSL::ExtConfig - * - * This module contains configuration information about the SSL extension, - * for example if socket support is enabled, or the host name TLS extension - * is enabled. Constants in this module will always be defined, but contain - * +true+ or +false+ values depending on the configuration of your OpenSSL - * installation. - */ - mSSLExtConfig = rb_define_module_under(mOSSL, "ExtConfig"); - /* Document-class: OpenSSL::SSL::SSLError * * Generic error class raised by SSLSocket and SSLContext. @@ -2760,8 +2753,6 @@ Init_ossl_ssl(void) */ rb_attr(cSSLContext, rb_intern_const("session_remove_cb"), 1, 1, Qfalse); - rb_define_const(mSSLExtConfig, "HAVE_TLSEXT_HOST_NAME", Qtrue); - /* * A callback invoked whenever a new handshake is initiated on an * established connection. May be used to disable renegotiation entirely. @@ -2782,7 +2773,7 @@ Init_ossl_ssl(void) * end */ rb_attr(cSSLContext, rb_intern_const("renegotiation_cb"), 1, 1, Qfalse); -#ifndef OPENSSL_NO_NEXTPROTONEG +#ifdef OSSL_USE_NEXTPROTONEG /* * An Enumerable of Strings. Each String represents a protocol to be * advertised as the list of supported protocols for Next Protocol @@ -2952,11 +2943,6 @@ Init_ossl_ssl(void) * Document-class: OpenSSL::SSL::SSLSocket */ cSSLSocket = rb_define_class_under(mSSL, "SSLSocket", rb_cObject); -#ifdef OPENSSL_NO_SOCK - rb_define_const(mSSLExtConfig, "OPENSSL_NO_SOCK", Qtrue); - rb_define_method(cSSLSocket, "initialize", rb_f_notimplement, -1); -#else - rb_define_const(mSSLExtConfig, "OPENSSL_NO_SOCK", Qfalse); rb_define_alloc_func(cSSLSocket, ossl_ssl_s_alloc); rb_define_method(cSSLSocket, "initialize", ossl_ssl_initialize, -1); rb_undef_method(cSSLSocket, "initialize_copy"); @@ -2988,10 +2974,9 @@ Init_ossl_ssl(void) rb_define_method(cSSLSocket, "tmp_key", ossl_ssl_tmp_key, 0); rb_define_method(cSSLSocket, "alpn_protocol", ossl_ssl_alpn_protocol, 0); rb_define_method(cSSLSocket, "export_keying_material", ossl_ssl_export_keying_material, -1); -# ifndef OPENSSL_NO_NEXTPROTONEG +# ifdef OSSL_USE_NEXTPROTONEG rb_define_method(cSSLSocket, "npn_protocol", ossl_ssl_npn_protocol, 0); # endif -#endif rb_define_const(mSSL, "VERIFY_NONE", INT2NUM(SSL_VERIFY_NONE)); rb_define_const(mSSL, "VERIFY_PEER", INT2NUM(SSL_VERIFY_PEER)); @@ -3153,4 +3138,5 @@ Init_ossl_ssl(void) DefIVarID(io); DefIVarID(context); DefIVarID(hostname); +#endif /* !defined(OPENSSL_NO_SOCK) */ } diff --git a/ext/openssl/ossl_ssl_session.c b/ext/openssl/ossl_ssl_session.c index 92eb1365fe..139a474b04 100644 --- a/ext/openssl/ossl_ssl_session.c +++ b/ext/openssl/ossl_ssl_session.c @@ -4,6 +4,7 @@ #include "ossl.h" +#ifndef OPENSSL_NO_SOCK VALUE cSSLSession; static VALUE eSSLSession; @@ -299,6 +300,7 @@ static VALUE ossl_ssl_session_to_text(VALUE self) return ossl_membio2str(out); } +#endif /* !defined(OPENSSL_NO_SOCK) */ void Init_ossl_ssl_session(void) { @@ -307,6 +309,7 @@ void Init_ossl_ssl_session(void) mSSL = rb_define_module_under(mOSSL, "SSL"); eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); #endif +#ifndef OPENSSL_NO_SOCK cSSLSession = rb_define_class_under(mSSL, "Session", rb_cObject); eSSLSession = rb_define_class_under(cSSLSession, "SessionError", eOSSLError); @@ -324,4 +327,5 @@ void Init_ossl_ssl_session(void) rb_define_method(cSSLSession, "to_der", ossl_ssl_session_to_der, 0); rb_define_method(cSSLSession, "to_pem", ossl_ssl_session_to_pem, 0); rb_define_method(cSSLSession, "to_text", ossl_ssl_session_to_text, 0); +#endif /* !defined(OPENSSL_NO_SOCK) */ } diff --git a/ext/racc/cparse/cparse.c b/ext/racc/cparse/cparse.c index f71ed2bba9..f752eb7749 100644 --- a/ext/racc/cparse/cparse.c +++ b/ext/racc/cparse/cparse.c @@ -7,8 +7,6 @@ This library is free software. You can distribute/modify this program under the same terms of ruby. - $originalId: cparse.c,v 1.8 2006/07/06 11:39:46 aamine Exp $ - */ #include <ruby.h> @@ -24,7 +22,7 @@ Important Constants ----------------------------------------------------------------------- */ -#define RACC_VERSION "1.4.15" +#define RACC_VERSION "1.6.2" #define DEFAULT_TOKEN -1 #define ERROR_TOKEN 1 diff --git a/ext/readline/readline-ext.gemspec b/ext/readline/readline-ext.gemspec index 0c6f70ba91..1e16edbfe6 100644 --- a/ext/readline/readline-ext.gemspec +++ b/ext/readline/readline-ext.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "readline-ext" - spec.version = "0.1.4" + spec.version = "0.1.5" spec.authors = ["Yukihiro Matsumoto"] spec.email = ["matz@ruby-lang.org"] diff --git a/ext/ripper/lib/ripper/lexer.rb b/ext/ripper/lib/ripper/lexer.rb index 19c59e2ccc..a0f1cbeaa8 100644 --- a/ext/ripper/lib/ripper/lexer.rb +++ b/ext/ripper/lib/ripper/lexer.rb @@ -228,7 +228,7 @@ class Ripper def on_heredoc_end(tok) @buf.push Elem.new([lineno(), column()], __callee__, tok, state()) - @buf = @stack.pop + @buf = @stack.pop unless @stack.empty? end def _push_token(tok) @@ -242,7 +242,12 @@ class Ripper end def on_error2(mesg, elem) - @errors.push Elem.new(elem.pos, __callee__, elem.tok, elem.state, mesg) + if elem + elem = Elem.new(elem.pos, __callee__, elem.tok, elem.state, mesg) + else + elem = Elem.new([lineno(), column()], __callee__, token(), state(), mesg) + end + @errors.push elem end PARSER_EVENTS.grep(/_error\z/) do |e| arity = PARSER_EVENT_TABLE.fetch(e) diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index 6196638bd2..eecdc7d4b8 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -1,7 +1,11 @@ # frozen_string_literal: true require 'socket.so' -require 'io/wait' + +unless IO.method_defined?(:wait_writable, false) + # It's only required on older Rubies < v3.2: + require 'io/wait' +end class Addrinfo # creates an Addrinfo object from the arguments. diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index 269edc4dad..45b4cad38f 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -287,8 +287,9 @@ numeric_getaddrinfo(const char *node, const char *service, void rb_freeaddrinfo(struct rb_addrinfo *ai) { - if (!ai->allocated_by_malloc) - freeaddrinfo(ai->ai); + if (!ai->allocated_by_malloc) { + if (ai->ai) freeaddrinfo(ai->ai); + } else { struct addrinfo *ai1, *ai2; ai1 = ai->ai; diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index f82103a160..0054766dac 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -1340,8 +1340,9 @@ strio_getline(struct getline_arg *arg, struct StringIO *ptr) str = strio_substr(ptr, ptr->pos, e - s - w, enc); } else { - if (n < e - s) { - if (e - s < 1024) { + if (n < e - s + arg->chomp) { + /* unless chomping, RS at the end does not matter */ + if (e - s < 1024 || n == e - s) { for (p = s; p + n <= e; ++p) { if (MEMCMP(p, RSTRING_PTR(str), char, n) == 0) { e = p + n; diff --git a/ext/strscan/extconf.rb b/ext/strscan/extconf.rb index b53b63e455..3c311d2364 100644 --- a/ext/strscan/extconf.rb +++ b/ext/strscan/extconf.rb @@ -2,7 +2,8 @@ require 'mkmf' if RUBY_ENGINE == 'ruby' $INCFLAGS << " -I$(top_srcdir)" if $extmk - have_func("onig_region_memsize", "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 9b646ab678..16d669d8a5 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 <stdbool.h> -#define STRSCAN_VERSION "3.0.5" +#define STRSCAN_VERSION "3.0.7" /* ======================================================================= Data Type Definitions @@ -539,6 +539,68 @@ adjust_register_position(struct strscanner *p, long position) } } +/* rb_reg_onig_match is available in Ruby 3.3 and later. */ +#ifndef HAVE_RB_REG_ONIG_MATCH +static OnigPosition +rb_reg_onig_match(VALUE re, VALUE str, + OnigPosition (*match)(regex_t *reg, VALUE str, struct re_registers *regs, void *args), + void *args, struct re_registers *regs) +{ + regex_t *reg = rb_reg_prepare_re(re, str); + + bool tmpreg = reg != RREGEXP_PTR(re); + if (!tmpreg) RREGEXP(re)->usecnt++; + + OnigPosition result = match(reg, str, regs, args); + + if (!tmpreg) RREGEXP(re)->usecnt--; + if (tmpreg) { + if (RREGEXP(re)->usecnt) { + onig_free(reg); + } + else { + onig_free(RREGEXP_PTR(re)); + RREGEXP_PTR(re) = reg; + } + } + + if (result < 0) { + if (result != ONIG_MISMATCH) { + rb_raise(ScanError, "regexp buffer overflow"); + } + } + + return result; +} +#endif + +static OnigPosition +strscan_match(regex_t *reg, VALUE str, struct re_registers *regs, void *args_ptr) +{ + struct strscanner *p = (struct strscanner *)args_ptr; + + return onig_match(reg, + match_target(p), + (UChar* )(CURPTR(p) + S_RESTLEN(p)), + (UChar* )CURPTR(p), + regs, + ONIG_OPTION_NONE); +} + +static OnigPosition +strscan_search(regex_t *reg, VALUE str, struct re_registers *regs, void *args_ptr) +{ + struct strscanner *p = (struct strscanner *)args_ptr; + + return onig_search(reg, + match_target(p), + (UChar *)(CURPTR(p) + S_RESTLEN(p)), + (UChar *)CURPTR(p), + (UChar *)(CURPTR(p) + S_RESTLEN(p)), + regs, + ONIG_OPTION_NONE); +} + static VALUE strscan_do_scan(VALUE self, VALUE pattern, int succptr, int getstr, int headonly) { @@ -560,47 +622,14 @@ strscan_do_scan(VALUE self, VALUE pattern, int succptr, int getstr, int headonly } if (RB_TYPE_P(pattern, T_REGEXP)) { - regex_t *rb_reg_prepare_re(VALUE re, VALUE str); - regex_t *re; - long ret; - int tmpreg; - p->regex = pattern; - re = rb_reg_prepare_re(pattern, p->str); - tmpreg = re != RREGEXP_PTR(pattern); - if (!tmpreg) RREGEXP(pattern)->usecnt++; - - if (headonly) { - ret = onig_match(re, - match_target(p), - (UChar* )(CURPTR(p) + S_RESTLEN(p)), - (UChar* )CURPTR(p), - &(p->regs), - ONIG_OPTION_NONE); - } - else { - ret = onig_search(re, - match_target(p), - (UChar* )(CURPTR(p) + S_RESTLEN(p)), - (UChar* )CURPTR(p), - (UChar* )(CURPTR(p) + S_RESTLEN(p)), - &(p->regs), - ONIG_OPTION_NONE); - } - if (!tmpreg) RREGEXP(pattern)->usecnt--; - if (tmpreg) { - if (RREGEXP(pattern)->usecnt) { - onig_free(re); - } - else { - onig_free(RREGEXP_PTR(pattern)); - RREGEXP_PTR(pattern) = re; - } - } + OnigPosition ret = rb_reg_onig_match(pattern, + p->str, + headonly ? strscan_match : strscan_search, + (void *)p, + &(p->regs)); - if (ret == -2) rb_raise(ScanError, "regexp buffer overflow"); - if (ret < 0) { - /* not matched */ + if (ret == ONIG_MISMATCH) { return Qnil; } } @@ -1038,8 +1067,9 @@ strscan_empty_p(VALUE self) * This method is obsolete; use #eos? instead. * * s = StringScanner.new('test string') - * s.eos? # These two - * s.rest? # are opposites. + * # These two are opposites + * s.eos? # => false + * s.rest? # => true */ static VALUE strscan_rest_p(VALUE self) @@ -1501,7 +1531,9 @@ strscan_named_captures(VALUE self) named_captures_data data; data.self = self; data.captures = rb_hash_new(); - onig_foreach_name(RREGEXP_PTR(p->regex), named_captures_iter, &data); + if (!RB_NIL_P(p->regex)) { + onig_foreach_name(RREGEXP_PTR(p->regex), named_captures_iter, &data); + } return data.captures; } diff --git a/ext/syslog/syslog.gemspec b/ext/syslog/syslog.gemspec index 8f73f5ad0d..6aa2e9570d 100644 --- a/ext/syslog/syslog.gemspec +++ b/ext/syslog/syslog.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "syslog" - spec.version = "0.1.0" + spec.version = "0.1.1" spec.authors = ["Akinori MUSHA"] spec.email = ["knu@idaemons.org"] diff --git a/ext/win32/lib/win32/registry.rb b/ext/win32/lib/win32/registry.rb index b5b99ff684..bda8bb012f 100644 --- a/ext/win32/lib/win32/registry.rb +++ b/ext/win32/lib/win32/registry.rb @@ -69,7 +69,11 @@ For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/pr WCHAR_NUL = "\0".encode(WCHAR).freeze WCHAR_CR = "\r".encode(WCHAR).freeze WCHAR_SIZE = WCHAR_NUL.bytesize - LOCALE = Encoding.find(Encoding.locale_charmap) + begin + LOCALE = Encoding.find(Encoding.locale_charmap) + rescue ArgumentError + LOCALE = Encoding::UTF_8 + end class Registry @@ -740,14 +744,11 @@ For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/pr # method returns. # def write(name, type, data) - termsize = 0 case type when REG_SZ, REG_EXPAND_SZ - data = data.encode(WCHAR) - termsize = WCHAR_SIZE + data = data.encode(WCHAR) << WCHAR_NUL when REG_MULTI_SZ data = data.to_a.map {|s| s.encode(WCHAR)}.join(WCHAR_NUL) << WCHAR_NUL - termsize = WCHAR_SIZE when REG_BINARY, REG_NONE data = data.to_s when REG_DWORD @@ -759,7 +760,7 @@ For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/pr else raise TypeError, "Unsupported type #{Registry.type2name(type)}" end - API.SetValue(@hkey, name, type, data, data.bytesize + termsize) + API.SetValue(@hkey, name, type, data, data.bytesize) end # @@ -574,6 +574,7 @@ struct RMoved { VALUE flags; VALUE dummy; VALUE destination; + shape_id_t original_shape_id; }; #define RMOVED(obj) ((struct RMoved *)(obj)) @@ -2473,6 +2474,7 @@ rb_objspace_set_event_hook(const rb_event_flag_t event) static void gc_event_hook_body(rb_execution_context_t *ec, rb_objspace_t *objspace, const rb_event_flag_t event, VALUE data) { + if (UNLIKELY(!ec->cfp)) return; const VALUE *pc = ec->cfp->pc; if (pc && VM_FRAME_RUBYFRAME_P(ec->cfp)) { /* increment PC because source line is calculated with PC-1 */ @@ -2794,6 +2796,12 @@ newobj_alloc(rb_objspace_t *objspace, rb_ractor_t *cr, size_t size_pool_idx, boo return obj; } +static void +newobj_zero_slot(VALUE obj) +{ + memset((char *)obj + sizeof(struct RBasic), 0, rb_gc_obj_slot_size(obj) - sizeof(struct RBasic)); +} + ALWAYS_INLINE(static VALUE newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_t *cr, int wb_protected, size_t size_pool_idx)); static inline VALUE @@ -2824,7 +2832,7 @@ newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_t * #endif newobj_init(klass, flags, wb_protected, objspace, obj); - gc_event_hook_prep(objspace, RUBY_INTERNAL_EVENT_NEWOBJ, obj, newobj_fill(obj, 0, 0, 0)); + gc_event_hook_prep(objspace, RUBY_INTERNAL_EVENT_NEWOBJ, obj, newobj_zero_slot(obj)); } RB_VM_LOCK_LEAVE_CR_LEV(cr, &lev); @@ -2965,6 +2973,7 @@ rb_class_instance_allocate_internal(VALUE klass, VALUE flags, bool wb_protected) ROBJECT_SET_SHAPE_ID(obj, ROBJECT_SHAPE_ID(obj) + SIZE_POOL_COUNT); #if RUBY_DEBUG + RUBY_ASSERT(!rb_shape_obj_too_complex(obj)); VALUE *ptr = ROBJECT_IVPTR(obj); for (size_t i = 0; i < ROBJECT_IV_CAPACITY(obj); i++) { ptr[i] = Qundef; @@ -3451,7 +3460,11 @@ obj_free(rb_objspace_t *objspace, VALUE obj) switch (BUILTIN_TYPE(obj)) { case T_OBJECT: - if (RANY(obj)->as.basic.flags & ROBJECT_EMBED) { + if (rb_shape_obj_too_complex(obj)) { + RB_DEBUG_COUNTER_INC(obj_obj_too_complex); + st_free_table(ROBJECT_IV_HASH(obj)); + } + else if (RANY(obj)->as.basic.flags & ROBJECT_EMBED) { RB_DEBUG_COUNTER_INC(obj_obj_embed); } else if (ROBJ_TRANSIENT_P(obj)) { @@ -3649,7 +3662,7 @@ obj_free(rb_objspace_t *objspace, VALUE obj) cc_table_free(objspace, obj, FALSE); rb_class_remove_from_module_subclasses(obj); rb_class_remove_from_super_subclasses(obj); -#if !USE_RVARGC +#if !RCLASS_EXT_EMBEDDED xfree(RCLASS_EXT(obj)); #endif @@ -4345,16 +4358,20 @@ run_finalizer(rb_objspace_t *objspace, VALUE obj, VALUE table) VALUE objid; VALUE final; rb_control_frame_t *cfp; + VALUE *sp; long finished; } saved; + rb_execution_context_t * volatile ec = GET_EC(); #define RESTORE_FINALIZER() (\ ec->cfp = saved.cfp, \ + ec->cfp->sp = saved.sp, \ ec->errinfo = saved.errinfo) saved.errinfo = ec->errinfo; saved.objid = rb_obj_id(obj); saved.cfp = ec->cfp; + saved.sp = ec->cfp->sp; saved.finished = 0; saved.final = Qundef; @@ -4713,6 +4730,7 @@ id2ref(VALUE objid) } } +/* :nodoc: */ static VALUE os_id2ref(VALUE os, VALUE objid) { @@ -4874,7 +4892,10 @@ obj_memsize_of(VALUE obj, int use_all_types) switch (BUILTIN_TYPE(obj)) { case T_OBJECT: - if (!(RBASIC(obj)->flags & ROBJECT_EMBED)) { + if (rb_shape_obj_too_complex(obj)) { + size += rb_st_memsize(ROBJECT_IV_HASH(obj)); + } + else if (!(RBASIC(obj)->flags & ROBJECT_EMBED)) { size += ROBJECT_IV_CAPACITY(obj) * sizeof(VALUE); } break; @@ -5221,6 +5242,8 @@ unlock_page_body(rb_objspace_t *objspace, struct heap_page_body *body) static bool try_move(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *free_page, VALUE src) { + GC_ASSERT(gc_is_moveable_obj(objspace, src)); + struct heap_page *src_page = GET_HEAP_PAGE(src); if (!free_page) { return false; @@ -5229,35 +5252,33 @@ try_move(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *free_page, /* We should return true if either src is successfully moved, or src is * unmoveable. A false return will cause the sweeping cursor to be * incremented to the next page, and src will attempt to move again */ - if (gc_is_moveable_obj(objspace, src)) { - GC_ASSERT(MARKED_IN_BITMAP(GET_HEAP_MARK_BITS(src), src)); - - asan_unlock_freelist(free_page); - VALUE dest = (VALUE)free_page->freelist; - asan_lock_freelist(free_page); - asan_unpoison_object(dest, false); - if (!dest) { - /* if we can't get something from the freelist then the page must be - * full */ - return false; - } - free_page->freelist = RANY(dest)->as.free.next; - - GC_ASSERT(RB_BUILTIN_TYPE(dest) == T_NONE); + GC_ASSERT(MARKED_IN_BITMAP(GET_HEAP_MARK_BITS(src), src)); + + asan_unlock_freelist(free_page); + VALUE dest = (VALUE)free_page->freelist; + asan_lock_freelist(free_page); + asan_unpoison_object(dest, false); + if (!dest) { + /* if we can't get something from the freelist then the page must be + * full */ + return false; + } + free_page->freelist = RANY(dest)->as.free.next; - if (src_page->slot_size > free_page->slot_size) { - objspace->rcompactor.moved_down_count_table[BUILTIN_TYPE(src)]++; - } - else if (free_page->slot_size > src_page->slot_size) { - objspace->rcompactor.moved_up_count_table[BUILTIN_TYPE(src)]++; - } - objspace->rcompactor.moved_count_table[BUILTIN_TYPE(src)]++; - objspace->rcompactor.total_moved++; + GC_ASSERT(RB_BUILTIN_TYPE(dest) == T_NONE); - gc_move(objspace, src, dest, src_page->slot_size, free_page->slot_size); - gc_pin(objspace, src); - free_page->free_slots--; + if (src_page->slot_size > free_page->slot_size) { + objspace->rcompactor.moved_down_count_table[BUILTIN_TYPE(src)]++; + } + else if (free_page->slot_size > src_page->slot_size) { + objspace->rcompactor.moved_up_count_table[BUILTIN_TYPE(src)]++; } + objspace->rcompactor.moved_count_table[BUILTIN_TYPE(src)]++; + objspace->rcompactor.total_moved++; + + gc_move(objspace, src, dest, src_page->slot_size, free_page->slot_size); + gc_pin(objspace, src); + free_page->free_slots--; return true; } @@ -5834,18 +5855,13 @@ gc_sweep_finish_size_pool(rb_objspace_t *objspace, rb_size_pool_t *size_pool) size_t swept_slots = size_pool->freed_slots + size_pool->empty_slots; size_t min_free_slots = (size_t)(total_slots * gc_params.heap_free_slots_min_ratio); - /* Some size pools may have very few pages (or even no pages). These size pools - * should still have allocatable pages. */ - if (min_free_slots < gc_params.heap_init_slots) { - min_free_slots = gc_params.heap_init_slots; - } /* If we don't have enough slots and we have pages on the tomb heap, move * pages from the tomb heap to the eden heap. This may prevent page * creation thrashing (frequently allocating and deallocting pages) and * GC thrashing (running GC more frequently than required). */ struct heap_page *resurrected_page; - while (swept_slots < min_free_slots && + while ((swept_slots < min_free_slots || swept_slots < gc_params.heap_init_slots) && (resurrected_page = heap_page_resurrect(objspace, size_pool))) { swept_slots += resurrected_page->free_slots; @@ -5853,6 +5869,17 @@ gc_sweep_finish_size_pool(rb_objspace_t *objspace, rb_size_pool_t *size_pool) heap_add_freepage(heap, resurrected_page); } + /* Some size pools may have very few pages (or even no pages). These size pools + * should still have allocatable pages. */ + if (min_free_slots < gc_params.heap_init_slots && swept_slots < gc_params.heap_init_slots) { + int multiple = size_pool->slot_size / BASE_SLOT_SIZE; + size_t extra_slots = gc_params.heap_init_slots - swept_slots; + size_t extend_page_count = CEILDIV(extra_slots * multiple, HEAP_PAGE_OBJ_LIMIT); + if (extend_page_count > size_pool->allocatable_pages) { + size_pool_allocatable_pages_set(objspace, size_pool, extend_page_count); + } + } + if (swept_slots < min_free_slots) { bool grow_heap = is_full_marking(objspace); @@ -6080,10 +6107,19 @@ invalidate_moved_plane(rb_objspace_t *objspace, struct heap_page *page, uintptr_ object = rb_gc_location(forwarding_object); + shape_id_t original_shape_id = 0; + if (RB_TYPE_P(object, T_OBJECT)) { + original_shape_id = RMOVED(forwarding_object)->original_shape_id; + } + gc_move(objspace, object, forwarding_object, GET_HEAP_PAGE(object)->slot_size, page->slot_size); /* forwarding_object is now our actual object, and "object" * is the free slot for the original page */ + if (original_shape_id) { + ROBJECT_SET_SHAPE_ID(forwarding_object, original_shape_id); + } + struct heap_page *orig_page = GET_HEAP_PAGE(object); orig_page->free_slots++; heap_page_add_freeobj(objspace, orig_page, object); @@ -7183,6 +7219,8 @@ gc_mark_imemo(rb_objspace_t *objspace, VALUE obj) } } +static void mark_cvc_tbl(rb_objspace_t *objspace, VALUE klass); + static void gc_mark_children(rb_objspace_t *objspace, VALUE obj) { @@ -7229,6 +7267,7 @@ gc_mark_children(rb_objspace_t *objspace, VALUE obj) if (!RCLASS_EXT(obj)) break; mark_m_tbl(objspace, RCLASS_M_TBL(obj)); + mark_cvc_tbl(objspace, obj); cc_table_mark(objspace, obj); for (attr_index_t i = 0; i < RCLASS_IV_COUNT(obj); i++) { gc_mark(objspace, RCLASS_IVPTR(obj)[i]); @@ -7296,14 +7335,23 @@ gc_mark_children(rb_objspace_t *objspace, VALUE obj) case T_OBJECT: { - const VALUE * const ptr = ROBJECT_IVPTR(obj); - - uint32_t i, len = ROBJECT_IV_COUNT(obj); - for (i = 0; i < len; i++) { - gc_mark(objspace, ptr[i]); + rb_shape_t *shape = rb_shape_get_shape_by_id(ROBJECT_SHAPE_ID(obj)); + if (rb_shape_obj_too_complex(obj)) { + mark_tbl_no_pin(objspace, ROBJECT_IV_HASH(obj)); } + else { + const VALUE * const ptr = ROBJECT_IVPTR(obj); - rb_shape_t *shape = rb_shape_get_shape_by_id(ROBJECT_SHAPE_ID(obj)); + uint32_t i, len = ROBJECT_IV_COUNT(obj); + for (i = 0; i < len; i++) { + gc_mark(objspace, ptr[i]); + } + + if (LIKELY(during_gc) && + ROBJ_TRANSIENT_P(obj)) { + rb_transient_heap_mark(obj, ptr); + } + } if (shape) { VALUE klass = RBASIC_CLASS(obj); @@ -7313,11 +7361,6 @@ gc_mark_children(rb_objspace_t *objspace, VALUE obj) RCLASS_EXT(klass)->max_iv_count = num_of_ivs; } } - - if (LIKELY(during_gc) && - ROBJ_TRANSIENT_P(obj)) { - rb_transient_heap_mark(obj, ptr); - } } break; @@ -8420,20 +8463,25 @@ gc_compact_destination_pool(rb_objspace_t *objspace, rb_size_pool_t *src_pool, V size_t idx = 0; switch (BUILTIN_TYPE(src)) { - case T_ARRAY: - obj_size = rb_ary_size_as_embedded(src); - break; + case T_ARRAY: + obj_size = rb_ary_size_as_embedded(src); + break; - case T_OBJECT: + case T_OBJECT: + if (rb_shape_obj_too_complex(src)) { + return &size_pools[0]; + } + else { obj_size = rb_obj_embedded_size(ROBJECT_IV_CAPACITY(src)); - break; + } + break; - case T_STRING: - obj_size = rb_str_size_as_embedded(src); - break; + case T_STRING: + obj_size = rb_str_size_as_embedded(src); + break; - default: - return src_pool; + default: + return src_pool; } if (rb_gc_size_allocatable_p(obj_size)){ @@ -8446,13 +8494,30 @@ static bool gc_compact_move(rb_objspace_t *objspace, rb_heap_t *heap, rb_size_pool_t *size_pool, VALUE src) { GC_ASSERT(BUILTIN_TYPE(src) != T_MOVED); + GC_ASSERT(gc_is_moveable_obj(objspace, src)); - rb_heap_t *dheap = SIZE_POOL_EDEN_HEAP(gc_compact_destination_pool(objspace, size_pool, src)); + rb_size_pool_t *dest_pool = gc_compact_destination_pool(objspace, size_pool, src); + rb_heap_t *dheap = SIZE_POOL_EDEN_HEAP(dest_pool); + rb_shape_t *new_shape = NULL; + rb_shape_t *orig_shape = NULL; if (gc_compact_heap_cursors_met_p(dheap)) { return dheap != heap; } + if (RB_TYPE_P(src, T_OBJECT)) { + orig_shape = rb_shape_get_shape(src); + if (dheap != heap && !rb_shape_obj_too_complex(src)) { + rb_shape_t *initial_shape = rb_shape_get_shape_by_id((shape_id_t)((dest_pool - size_pools) + SIZE_POOL_COUNT)); + new_shape = rb_shape_traverse_from_new_root(initial_shape, orig_shape); + + if (!new_shape) { + dest_pool = size_pool; + dheap = heap; + } + } + } + while (!try_move(objspace, dheap, dheap->free_pages, src)) { struct gc_sweep_context ctx = { .page = dheap->sweeping_page, @@ -8474,9 +8539,18 @@ gc_compact_move(rb_objspace_t *objspace, rb_heap_t *heap, rb_size_pool_t *size_p dheap->sweeping_page = ccan_list_next(&dheap->pages, dheap->sweeping_page, page_node); if (gc_compact_heap_cursors_met_p(dheap)) { - return false; + return dheap != heap; + } + } + + if (orig_shape) { + if (new_shape) { + VALUE dest = rb_gc_location(src); + rb_shape_set_shape(dest, new_shape); } + RMOVED(src)->original_shape_id = rb_shape_id(orig_shape); } + return true; } @@ -8494,9 +8568,11 @@ gc_compact_plane(rb_objspace_t *objspace, rb_size_pool_t *size_pool, rb_heap_t * if (bitset & 1) { objspace->rcompactor.considered_count_table[BUILTIN_TYPE(vp)]++; - if (!gc_compact_move(objspace, heap, size_pool, vp)) { - //the cursors met. bubble up - return false; + if (gc_is_moveable_obj(objspace, vp)) { + if (!gc_compact_move(objspace, heap, size_pool, vp)) { + //the cursors met. bubble up + return false; + } } } p += slot_size; @@ -9205,10 +9281,23 @@ rb_gc_register_address(VALUE *addr) rb_objspace_t *objspace = &rb_objspace; struct gc_list *tmp; + VALUE obj = *addr; + tmp = ALLOC(struct gc_list); tmp->next = global_list; tmp->varptr = addr; global_list = tmp; + + /* + * Because some C extensions have assignment-then-register bugs, + * we guard `obj` here so that it would not get swept defensively. + */ + RB_GC_GUARD(obj); + if (0 && !SPECIAL_CONST_P(obj)) { + rb_warn("Object is assigned to registering address already: %"PRIsVALUE, + rb_obj_class(obj)); + rb_print_backtrace(); + } } void @@ -9846,6 +9935,17 @@ gc_is_moveable_obj(rb_objspace_t *objspace, VALUE obj) return FALSE; } +/* Used in places that could malloc, which can cause the GC to run. We need to + * temporarily disable the GC to allow the malloc to happen. */ +#define COULD_MALLOC_REGION_START() \ + GC_ASSERT(during_gc); \ + VALUE _already_disabled = rb_gc_disable_no_rest(); \ + during_gc = false; + +#define COULD_MALLOC_REGION_END() \ + during_gc = true; \ + if (_already_disabled == Qfalse) rb_objspace_gc_enable(objspace); + static VALUE gc_move(rb_objspace_t *objspace, VALUE scan, VALUE free, size_t src_slot_size, size_t slot_size) { @@ -9874,11 +9974,12 @@ gc_move(rb_objspace_t *objspace, VALUE scan, VALUE free, size_t src_slot_size, s CLEAR_IN_BITMAP(GET_HEAP_MARKING_BITS((VALUE)src), (VALUE)src); if (FL_TEST((VALUE)src, FL_EXIVAR)) { - /* Same deal as below. Generic ivars are held in st tables. - * Resizing the table could cause a GC to happen and we can't allow it */ - VALUE already_disabled = rb_gc_disable_no_rest(); - rb_mv_generic_ivar((VALUE)src, (VALUE)dest); - if (already_disabled == Qfalse) rb_objspace_gc_enable(objspace); + /* Resizing the st table could cause a malloc */ + COULD_MALLOC_REGION_START(); + { + rb_mv_generic_ivar((VALUE)src, (VALUE)dest); + } + COULD_MALLOC_REGION_END(); } st_data_t srcid = (st_data_t)src, id; @@ -9887,13 +9988,13 @@ gc_move(rb_objspace_t *objspace, VALUE scan, VALUE free, size_t src_slot_size, s * the object to object id mapping. */ if (st_lookup(objspace->obj_to_id_tbl, srcid, &id)) { gc_report(4, objspace, "Moving object with seen id: %p -> %p\n", (void *)src, (void *)dest); - /* inserting in the st table can cause the GC to run. We need to - * prevent re-entry in to the GC since `gc_move` is running in the GC, - * so temporarily disable the GC around the st table mutation */ - VALUE already_disabled = rb_gc_disable_no_rest(); - st_delete(objspace->obj_to_id_tbl, &srcid, 0); - st_insert(objspace->obj_to_id_tbl, (st_data_t)dest, id); - if (already_disabled == Qfalse) rb_objspace_gc_enable(objspace); + /* Resizing the st table could cause a malloc */ + COULD_MALLOC_REGION_START(); + { + st_delete(objspace->obj_to_id_tbl, &srcid, 0); + st_insert(objspace->obj_to_id_tbl, (st_data_t)dest, id); + } + COULD_MALLOC_REGION_END(); } /* Move the object */ @@ -10042,14 +10143,17 @@ gc_ref_update_object(rb_objspace_t *objspace, VALUE v) { VALUE *ptr = ROBJECT_IVPTR(v); -#if USE_RVARGC - uint32_t numiv = ROBJECT_IV_CAPACITY(v); + if (rb_shape_obj_too_complex(v)) { + rb_gc_update_tbl_refs(ROBJECT_IV_HASH(v)); + return; + } +#if USE_RVARGC size_t slot_size = rb_gc_obj_slot_size(v); - size_t embed_size = rb_obj_embedded_size(numiv); + size_t embed_size = rb_obj_embedded_size(ROBJECT_IV_CAPACITY(v)); if (slot_size >= embed_size && !RB_FL_TEST_RAW(v, ROBJECT_EMBED)) { // Object can be re-embedded - memcpy(ROBJECT(v)->as.ary, ptr, sizeof(VALUE) * numiv); + memcpy(ROBJECT(v)->as.ary, ptr, sizeof(VALUE) * ROBJECT_IV_COUNT(v)); RB_FL_SET_RAW(v, ROBJECT_EMBED); if (ROBJ_TRANSIENT_P(v)) { ROBJ_TRANSIENT_UNSET(v); @@ -10058,10 +10162,6 @@ gc_ref_update_object(rb_objspace_t *objspace, VALUE v) xfree(ptr); } ptr = ROBJECT(v)->as.ary; - size_t size_pool_shape_id = size_pool_idx_for_size(embed_size); - rb_shape_t * initial_shape = rb_shape_get_shape_by_id((shape_id_t)size_pool_shape_id + SIZE_POOL_COUNT); - rb_shape_t * new_shape = rb_shape_rebuild_shape(initial_shape, rb_shape_get_shape(v)); - rb_shape_set_shape(v, new_shape); } #endif @@ -10392,9 +10492,14 @@ static enum rb_id_table_iterator_result update_cvc_tbl_i(VALUE cvc_entry, void *data) { struct rb_cvar_class_tbl_entry *entry; + rb_objspace_t * objspace = (rb_objspace_t *)data; entry = (struct rb_cvar_class_tbl_entry *)cvc_entry; + if (entry->cref) { + TYPED_UPDATE_IF_MOVED(objspace, rb_cref_t *, entry->cref); + } + entry->class_value = rb_gc_location(entry->class_value); return ID_TABLE_CONTINUE; @@ -10410,6 +10515,28 @@ update_cvc_tbl(rb_objspace_t *objspace, VALUE klass) } static enum rb_id_table_iterator_result +mark_cvc_tbl_i(VALUE cvc_entry, void *data) +{ + struct rb_cvar_class_tbl_entry *entry; + + entry = (struct rb_cvar_class_tbl_entry *)cvc_entry; + + RUBY_ASSERT(entry->cref == 0 || (BUILTIN_TYPE((VALUE)entry->cref) == T_IMEMO && IMEMO_TYPE_P(entry->cref, imemo_cref))); + rb_gc_mark((VALUE) entry->cref); + + return ID_TABLE_CONTINUE; +} + +static void +mark_cvc_tbl(rb_objspace_t *objspace, VALUE klass) +{ + struct rb_id_table *tbl = RCLASS_CVC_TBL(klass); + if (tbl) { + rb_id_table_foreach_values(tbl, mark_cvc_tbl_i, objspace); + } +} + +static enum rb_id_table_iterator_result update_const_table(VALUE value, void *data) { rb_const_entry_t *ce = (rb_const_entry_t *)value; @@ -10536,16 +10663,18 @@ gc_update_object_references(rb_objspace_t *objspace, VALUE obj) #if USE_RVARGC VALUE new_root = any->as.string.as.heap.aux.shared; rb_str_update_shared_ary(obj, old_root, new_root); +#endif + } - // if, after move the string is not embedded, and can fit in the - // slot it's been placed in, then re-embed it - if (rb_gc_obj_slot_size(obj) >= rb_str_size_as_embedded(obj)) { - if (!STR_EMBED_P(obj) && rb_str_reembeddable_p(obj)) { - rb_str_make_embedded(obj); - } +#if USE_RVARGC + /* If, after move the string is not embedded, and can fit in the + * slot it's been placed in, then re-embed it. */ + if (rb_gc_obj_slot_size(obj) >= rb_str_size_as_embedded(obj)) { + if (!STR_EMBED_P(obj) && rb_str_reembeddable_p(obj)) { + rb_str_make_embedded(obj); } -#endif } +#endif break; } @@ -10721,9 +10850,9 @@ gc_update_references(rb_objspace_t *objspace) #if GC_CAN_COMPILE_COMPACTION /* * call-seq: - * GC.latest_compact_info -> {:considered=>{:T_CLASS=>11}, :moved=>{:T_CLASS=>11}} + * GC.latest_compact_info -> hash * - * Returns information about object moved in the most recent GC compaction. + * Returns information about object moved in the most recent \GC compaction. * * The returned hash has two keys :considered and :moved. The hash for * :considered lists the number of objects that were considered for movement @@ -10827,13 +10956,13 @@ heap_check_moved_i(void *vstart, void *vend, size_t stride, void *data) * This function compacts objects together in Ruby's heap. It eliminates * unused space (or fragmentation) in the heap by moving objects in to that * unused space. This function returns a hash which contains statistics about - * which objects were moved. See `GC.latest_gc_info` for details about + * which objects were moved. See <tt>GC.latest_gc_info</tt> for details about * compaction statistics. * * This method is implementation specific and not expected to be implemented * in any implementation besides MRI. * - * To test whether GC compaction is supported, use the idiom: + * To test whether \GC compaction is supported, use the idiom: * * GC.respond_to?(:compact) */ @@ -11644,24 +11773,25 @@ get_envparam_double(const char *name, double *default_value, double lower_bound, } static void -gc_set_initial_pages(void) +gc_set_initial_pages(rb_objspace_t *objspace) { - size_t min_pages; - rb_objspace_t *objspace = &rb_objspace; - gc_rest(objspace); - min_pages = gc_params.heap_init_slots / HEAP_PAGE_OBJ_LIMIT; - - size_t pages_per_class = (min_pages - heap_eden_total_pages(objspace)) / SIZE_POOL_COUNT; - for (int i = 0; i < SIZE_POOL_COUNT; i++) { rb_size_pool_t *size_pool = &size_pools[i]; - heap_add_pages(objspace, size_pool, SIZE_POOL_EDEN_HEAP(size_pool), pages_per_class); + if (gc_params.heap_init_slots > size_pool->eden_heap.total_slots) { + size_t slots = gc_params.heap_init_slots - size_pool->eden_heap.total_slots; + int multiple = size_pool->slot_size / BASE_SLOT_SIZE; + size_pool->allocatable_pages = slots * multiple / HEAP_PAGE_OBJ_LIMIT; + } + else { + /* We already have more slots than heap_init_slots allows, so + * prevent creating more pages. */ + size_pool->allocatable_pages = 0; + } } - - heap_add_pages(objspace, &size_pools[0], SIZE_POOL_EDEN_HEAP(&size_pools[0]), min_pages - heap_eden_total_pages(objspace)); + heap_pages_expand_sorted(objspace); } /* @@ -11717,7 +11847,7 @@ ruby_gc_set_params(void) /* RUBY_GC_HEAP_INIT_SLOTS */ if (get_envparam_size("RUBY_GC_HEAP_INIT_SLOTS", &gc_params.heap_init_slots, 0)) { - gc_set_initial_pages(); + gc_set_initial_pages(objspace); } get_envparam_double("RUBY_GC_HEAP_GROWTH_FACTOR", &gc_params.growth_factor, 1.0, 0.0, FALSE); @@ -12133,6 +12263,16 @@ objspace_malloc_prepare(rb_objspace_t *objspace, size_t size) return size; } +static bool +malloc_during_gc_p(rb_objspace_t *objspace) +{ + /* malloc is not allowed during GC when we're not using multiple ractors + * (since ractors can run while another thread is sweeping) and when we + * have the GVL (since if we don't have the GVL, we'll try to acquire the + * GVL which will block and ensure the other thread finishes GC). */ + return during_gc && !rb_multi_ractor_p() && ruby_thread_has_gvl_p(); +} + static inline void * objspace_malloc_fixup(rb_objspace_t *objspace, void *mem, size_t size) { @@ -12191,12 +12331,24 @@ objspace_malloc_fixup(rb_objspace_t *objspace, void *mem, size_t size) } \ } while (0) +static void +check_malloc_not_in_gc(rb_objspace_t *objspace, const char *msg) +{ + if (UNLIKELY(malloc_during_gc_p(objspace))) { + dont_gc_on(); + during_gc = false; + rb_bug("Cannot %s during GC", msg); + } +} + /* these shouldn't be called directly. * objspace_* functions do not check allocation size. */ static void * objspace_xmalloc0(rb_objspace_t *objspace, size_t size) { + check_malloc_not_in_gc(objspace, "malloc"); + void *mem; size = objspace_malloc_prepare(objspace, size); @@ -12214,6 +12366,8 @@ xmalloc2_size(const size_t count, const size_t elsize) static void * objspace_xrealloc(rb_objspace_t *objspace, void *ptr, size_t new_size, size_t old_size) { + check_malloc_not_in_gc(objspace, "realloc"); + void *mem; if (!ptr) return objspace_xmalloc0(objspace, new_size); @@ -12451,6 +12605,13 @@ ruby_xmalloc2_body(size_t n, size_t size) static void * objspace_xcalloc(rb_objspace_t *objspace, size_t size) { + if (UNLIKELY(malloc_during_gc_p(objspace))) { + rb_warn("calloc during GC detected, this could cause crashes if it triggers another GC"); +#if RGENGC_CHECK_MODE || RUBY_DEBUG + rb_bug("Cannot calloc during GC"); +#endif + } + void *mem; size = objspace_malloc_prepare(objspace, size); @@ -12505,8 +12666,16 @@ ruby_xrealloc2_body(void *ptr, size_t n, size_t size) void ruby_sized_xfree(void *x, size_t size) { - if (x) { - objspace_xfree(&rb_objspace, x, size); + if (LIKELY(x)) { + /* It's possible for a C extension's pthread destructor function set by pthread_key_create + * to be called after ruby_vm_destruct and attempt to free memory. Fall back to mimfree in + * that case. */ + if (LIKELY(GET_VM())) { + objspace_xfree(&rb_objspace, x, size); + } + else { + ruby_mimfree(x); + } } } @@ -12700,12 +12869,47 @@ wmap_mark_map(st_data_t key, st_data_t val, st_data_t arg) } #endif +static int +wmap_replace_ref(st_data_t *key, st_data_t *value, st_data_t _argp, int existing) +{ + *key = rb_gc_location((VALUE)*key); + + VALUE *values = (VALUE *)*value; + VALUE size = values[0]; + + for (VALUE index = 1; index <= size; index++) { + values[index] = rb_gc_location(values[index]); + } + + return ST_CONTINUE; +} + +static int +wmap_foreach_replace(st_data_t key, st_data_t value, st_data_t _argp, int error) +{ + if (rb_gc_location((VALUE)key) != (VALUE)key) { + return ST_REPLACE; + } + + VALUE *values = (VALUE *)value; + VALUE size = values[0]; + + for (VALUE index = 1; index <= size; index++) { + VALUE val = values[index]; + if (rb_gc_location(val) != val) { + return ST_REPLACE; + } + } + + return ST_CONTINUE; +} + static void wmap_compact(void *ptr) { struct weakmap *w = ptr; if (w->wmap2obj) rb_gc_update_tbl_refs(w->wmap2obj); - if (w->obj2wmap) rb_gc_update_tbl_refs(w->obj2wmap); + if (w->obj2wmap) st_foreach_with_replace(w->obj2wmap, wmap_foreach_replace, wmap_replace_ref, (st_data_t)NULL); w->final = rb_gc_location(w->final); } @@ -12734,6 +12938,7 @@ wmap_free(void *ptr) st_foreach(w->obj2wmap, wmap_free_map, 0); st_free_table(w->obj2wmap); st_free_table(w->wmap2obj); + xfree(w); } static int @@ -12802,25 +13007,40 @@ wmap_live_p(rb_objspace_t *objspace, VALUE obj) } static int -wmap_final_func(st_data_t *key, st_data_t *value, st_data_t arg, int existing) +wmap_remove_inverse_ref(st_data_t *key, st_data_t *val, st_data_t arg, int existing) { - VALUE wmap, *ptr, size, i, j; if (!existing) return ST_STOP; - wmap = (VALUE)arg, ptr = (VALUE *)*value; - for (i = j = 1, size = ptr[0]; i <= size; ++i) { - if (ptr[i] != wmap) { - ptr[j++] = ptr[i]; - } - } - if (j == 1) { - ruby_sized_xfree(ptr, i * sizeof(VALUE)); + + VALUE old_ref = (VALUE)arg; + + VALUE *values = (VALUE *)*val; + VALUE size = values[0]; + + if (size == 1) { + // fast path, we only had one backref + RUBY_ASSERT(values[1] == old_ref); + ruby_sized_xfree(values, 2 * sizeof(VALUE)); return ST_DELETE; } - if (j < i) { - SIZED_REALLOC_N(ptr, VALUE, j + 1, i); - ptr[0] = j; - *value = (st_data_t)ptr; + + bool found = false; + VALUE index = 1; + for (; index <= size; index++) { + if (values[index] == old_ref) { + found = true; + break; + } } + if (!found) return ST_STOP; + + if (size > index) { + MEMMOVE(&values[index], &values[index + 1], VALUE, size - index); + } + + size -= 1; + values[0] = size; + SIZED_REALLOC_N(values, VALUE, size + 1, size + 2); + *val = (st_data_t)values; return ST_CONTINUE; } @@ -12853,7 +13073,7 @@ wmap_finalize(RB_BLOCK_CALL_FUNC_ARGLIST(objid, self)) wmap = (st_data_t)obj; if (st_delete(w->wmap2obj, &wmap, &orig)) { wmap = (st_data_t)obj; - st_update(w->obj2wmap, orig, wmap_final_func, wmap); + st_update(w->obj2wmap, orig, wmap_remove_inverse_ref, wmap); } return self; } @@ -13069,6 +13289,14 @@ wmap_aset_update(st_data_t *key, st_data_t *val, st_data_t arg, int existing) VALUE size, *ptr, *optr; if (existing) { size = (ptr = optr = (VALUE *)*val)[0]; + + for (VALUE index = 1; index <= size; index++) { + if (ptr[index] == (VALUE)arg) { + // The reference was already registered. + return ST_STOP; + } + } + ++size; SIZED_REALLOC_N(ptr, VALUE, size + 1, size); } @@ -13084,6 +13312,23 @@ wmap_aset_update(st_data_t *key, st_data_t *val, st_data_t arg, int existing) return ST_CONTINUE; } +struct wmap_aset_replace_args { + VALUE new_value; + VALUE old_value; +}; + +static int +wmap_aset_replace_value(st_data_t *key, st_data_t *val, st_data_t _args, int existing) +{ + struct wmap_aset_replace_args *args = (struct wmap_aset_replace_args *)_args; + + if (existing) { + args->old_value = *val; + } + *val = (st_data_t)args->new_value; + return ST_CONTINUE; +} + /* Creates a weak reference from the given key to the given value */ static VALUE wmap_aset(VALUE self, VALUE key, VALUE value) @@ -13098,8 +13343,25 @@ wmap_aset(VALUE self, VALUE key, VALUE value) define_final0(key, w->final); } - st_update(w->obj2wmap, (st_data_t)value, wmap_aset_update, key); - st_insert(w->wmap2obj, (st_data_t)key, (st_data_t)value); + struct wmap_aset_replace_args aset_args = { + .new_value = value, + .old_value = Qundef, + }; + st_update(w->wmap2obj, (st_data_t)key, wmap_aset_replace_value, (st_data_t)&aset_args); + + // If the value is unchanged, we have nothing to do. + if (value != aset_args.old_value) { + if (!UNDEF_P(aset_args.old_value) && FL_ABLE(aset_args.old_value)) { + // That key existed and had an inverse reference, we need to clear the outdated inverse reference. + st_update(w->obj2wmap, (st_data_t)aset_args.old_value, wmap_remove_inverse_ref, key); + } + + if (FL_ABLE(value)) { + // If the value has no finalizer, we don't need to keep the inverse reference + st_update(w->obj2wmap, (st_data_t)value, wmap_aset_update, key); + } + } + return nonspecial_obj_id(value); } @@ -13399,7 +13661,7 @@ gc_prof_set_heap_info(rb_objspace_t *objspace) * call-seq: * GC::Profiler.clear -> nil * - * Clears the GC profiler data. + * Clears the \GC profiler data. * */ @@ -13713,7 +13975,7 @@ gc_profile_total_time(VALUE self) * call-seq: * GC::Profiler.enabled? -> true or false * - * The current status of GC profile mode. + * The current status of \GC profile mode. */ static VALUE @@ -13727,7 +13989,7 @@ gc_profile_enable_get(VALUE self) * call-seq: * GC::Profiler.enable -> nil * - * Starts the GC profiler. + * Starts the \GC profiler. * */ @@ -13744,7 +14006,7 @@ gc_profile_enable(VALUE _) * call-seq: * GC::Profiler.disable -> nil * - * Stops the GC profiler. + * Stops the \GC profiler. * */ @@ -14049,7 +14311,7 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU { const rb_method_entry_t *me = &RANY(obj)->as.imemo.ment; - APPEND_F(":%s (%s%s%s%s) type:%s alias:%d owner:%p defined_class:%p", + APPEND_F(":%s (%s%s%s%s) type:%s aliased:%d owner:%p defined_class:%p", rb_id2name(me->called_id), METHOD_ENTRY_VISI(me) == METHOD_VISI_PUBLIC ? "pub" : METHOD_ENTRY_VISI(me) == METHOD_VISI_PRIVATE ? "pri" : "pro", @@ -14057,7 +14319,7 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU METHOD_ENTRY_CACHED(me) ? ",cc" : "", METHOD_ENTRY_INVALIDATED(me) ? ",inv" : "", me->def ? rb_method_type_name(me->def->type) : "NULL", - me->def ? me->def->alias_count : -1, + me->def ? me->def->aliased : -1, (void *)me->owner, // obj_info(me->owner), (void *)me->defined_class); //obj_info(me->defined_class))); @@ -14467,7 +14729,7 @@ Init_GC(void) { VALUE opts; - /* GC build options */ + /* \GC build options */ rb_define_const(rb_mGC, "OPTS", opts = rb_ary_new()); #define OPT(o) if (o) rb_ary_push(opts, rb_fstring_lit(#o)) OPT(GC_DEBUG); @@ -116,8 +116,6 @@ int ruby_get_stack_grow_direction(volatile VALUE *addr); const char *rb_obj_info(VALUE obj); const char *rb_raw_obj_info(char *const buff, const size_t buff_size, VALUE obj); -VALUE rb_gc_disable_no_rest(void); - struct rb_thread_struct; size_t rb_size_pool_slot_size(unsigned char pool_id); @@ -142,6 +140,8 @@ void rb_objspace_each_objects_without_setup( size_t rb_gc_obj_slot_size(VALUE obj); +VALUE rb_gc_disable_no_rest(void); + RUBY_SYMBOL_EXPORT_END #endif /* RUBY_GC_H */ @@ -6,7 +6,7 @@ # Some of the underlying methods are also available via the ObjectSpace # module. # -# You may obtain information about the operation of the GC through +# You may obtain information about the operation of the \GC through # GC::Profiler. module GC @@ -24,7 +24,7 @@ module GC # # def GC.start(full_mark: true, immediate_sweep: true); end # - # Use full_mark: false to perform a minor GC. + # Use full_mark: false to perform a minor \GC. # Use immediate_sweep: false to defer sweeping (use lazy sweep). # # Note: These keyword arguments are implementation and version dependent. They @@ -67,7 +67,7 @@ module GC # call-seq: # GC.stress -> integer, true or false # - # Returns current status of GC stress mode. + # Returns current status of \GC stress mode. def self.stress Primitive.gc_stress_get end @@ -75,9 +75,9 @@ module GC # call-seq: # GC.stress = flag -> flag # - # Updates the GC stress mode. + # Updates the \GC stress mode. # - # When stress mode is enabled, the GC is invoked at every GC opportunity: + # When stress mode is enabled, the \GC is invoked at every \GC opportunity: # all memory and object allocations. # # Enabling stress mode will degrade performance, it is only for debugging. @@ -93,9 +93,9 @@ module GC # call-seq: # GC.count -> Integer # - # The number of times GC occurred. + # The number of times \GC occurred. # - # It returns the number of times GC occurred since the process started. + # It returns the number of times \GC occurred since the process started. def self.count Primitive.gc_count end @@ -105,12 +105,12 @@ module GC # GC.stat(hash) -> Hash # GC.stat(:key) -> Numeric # - # Returns a Hash containing information about the GC. + # Returns a Hash containing information about the \GC. # # The contents of the hash are implementation specific and may change in # the future without notice. # - # The hash includes information about internal statistics about GC such as: + # The hash includes information about internal statistics about \GC such as: # # [count] # The total number of garbage collections ran since application start @@ -123,7 +123,7 @@ module GC # The number of pages that can fit into the buffer that holds references to # all pages # [heap_allocatable_pages] - # The total number of pages the application could allocate without additional GC + # The total number of pages the application could allocate without additional \GC # [heap_available_slots] # The total number of slots in all `:heap_allocated_pages` # [heap_live_slots] @@ -133,7 +133,7 @@ module GC # [heap_final_slots] # The total number of slots with pending finalizers to be run # [heap_marked_slots] - # The total number of objects marked in the last GC + # The total number of objects marked in the last \GC # [heap_eden_pages] # The total number of pages which contain at least one live slot # [heap_tomb_pages] @@ -147,9 +147,9 @@ module GC # [total_freed_objects] # The cumulative number of objects freed since application start # [malloc_increase_bytes] - # Amount of memory allocated on the heap for objects. Decreased by any GC + # Amount of memory allocated on the heap for objects. Decreased by any \GC # [malloc_increase_bytes_limit] - # When `:malloc_increase_bytes` crosses this limit, GC is triggered + # When `:malloc_increase_bytes` crosses this limit, \GC is triggered # [minor_gc_count] # The total number of minor garbage collections run since process start # [major_gc_count] @@ -165,15 +165,15 @@ module GC # The total number of objects without write barriers # [remembered_wb_unprotected_objects_limit] # When `:remembered_wb_unprotected_objects` crosses this limit, - # major GC is triggered + # major \GC is triggered # [old_objects] # Number of live, old objects which have survived at least 3 garbage collections # [old_objects_limit] - # When `:old_objects` crosses this limit, major GC is triggered + # When `:old_objects` crosses this limit, major \GC is triggered # [oldmalloc_increase_bytes] - # Amount of memory allocated on the heap for objects. Decreased by major GC + # Amount of memory allocated on the heap for objects. Decreased by major \GC # [oldmalloc_increase_bytes_limit] - # When `:old_malloc_increase_bytes` crosses this limit, major GC is triggered + # When `:old_malloc_increase_bytes` crosses this limit, major \GC is triggered # # If the optional argument, hash, is given, # it is overwritten and returned. @@ -191,7 +191,7 @@ module GC # GC.stat_heap(heap_name, hash) -> Hash # GC.stat_heap(heap_name, :key) -> Numeric # - # Returns information for memory pools in the GC. + # Returns information for memory pools in the \GC. # # If the first optional argument, +heap_name+, is passed in and not +nil+, it # returns a +Hash+ containing information about the particular memory pool. @@ -218,12 +218,12 @@ module GC Primitive.gc_stat_heap heap_name, hash_or_key end - # call-seq: - # GC.latest_gc_info -> {:gc_by=>:newobj} + # call-seq: + # GC.latest_gc_info -> hash # GC.latest_gc_info(hash) -> hash # GC.latest_gc_info(:major_by) -> :malloc # - # Returns information about the most recent garbage collection. + # Returns information about the most recent garbage collection. # # If the optional argument, hash, is given, # it is overwritten and returned. @@ -244,7 +244,7 @@ module GC # # This function expands the heap to ensure room to move all objects, # compacts the heap to make sure everything moves, updates all references, - # then performs a full GC. If any object contains a reference to a T_MOVED + # then performs a full \GC. If any object contains a reference to a T_MOVED # object, that object should be pushed on the mark stack, and will # make a SEGV. def self.verify_compaction_references(toward: nil, double_heap: false, expand_heap: false) @@ -255,9 +255,9 @@ module GC # call-seq: # GC.measure_total_time = true/false # - # Enable to measure GC time. + # Enable to measure \GC time. # You can get the result with <tt>GC.stat(:time)</tt>. - # Note that GC time measurement can cause some performance overhead. + # Note that \GC time measurement can cause some performance overhead. def self.measure_total_time=(flag) Primitive.cstmt! %{ rb_objspace.flags.measure_gc = RTEST(flag) ? TRUE : FALSE; @@ -279,7 +279,7 @@ module GC # call-seq: # GC.total_time -> int # - # Return measured GC total time in nano seconds. + # Return measured \GC total time in nano seconds. def self.total_time Primitive.cexpr! %{ ULL2NUM(rb_objspace.profile.total_time_ns) diff --git a/gems/bundled_gems b/gems/bundled_gems index 64923c906d..d37d869d41 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -1,16 +1,16 @@ # gem-name version-to-bundle repository-url [optional-commit-hash-to-test-or-defaults-to-v-version] -minitest 5.16.3 https://github.com/seattlerb/minitest -power_assert 2.0.2 https://github.com/ruby/power_assert +minitest 5.25.1 https://github.com/seattlerb/minitest +power_assert 2.0.3 https://github.com/ruby/power_assert rake 13.0.6 https://github.com/ruby/rake -test-unit 3.5.5 https://github.com/test-unit/test-unit -rexml 3.2.5 https://github.com/ruby/rexml -rss 0.2.9 https://github.com/ruby/rss -net-ftp 0.2.0 https://github.com/ruby/net-ftp -net-imap 0.3.1 https://github.com/ruby/net-imap +test-unit 3.5.7 https://github.com/test-unit/test-unit +rexml 3.3.9 https://github.com/ruby/rexml +rss 0.3.1 https://github.com/ruby/rss +net-ftp 0.2.1 https://github.com/ruby/net-ftp +net-imap 0.3.9 https://github.com/ruby/net-imap net-pop 0.1.2 https://github.com/ruby/net-pop -net-smtp 0.3.3 https://github.com/ruby/net-smtp +net-smtp 0.3.4 https://github.com/ruby/net-smtp matrix 0.4.2 https://github.com/ruby/matrix prime 0.1.2 https://github.com/ruby/prime -rbs 2.8.1 https://github.com/ruby/rbs 57c07bb9abf86e95694224174ade7521b593926a +rbs 2.8.2 https://github.com/ruby/rbs typeprof 0.21.3 https://github.com/ruby/typeprof -debug 1.7.0 https://github.com/ruby/debug +debug 1.7.1 https://github.com/ruby/debug diff --git a/gems/lib/core_assertions.rb b/gems/lib/core_assertions.rb new file mode 100644 index 0000000000..7334063885 --- /dev/null +++ b/gems/lib/core_assertions.rb @@ -0,0 +1 @@ +require_relative "../../tool/lib/core_assertions.rb" diff --git a/gems/lib/envutil.rb b/gems/lib/envutil.rb new file mode 100644 index 0000000000..d684c22cf2 --- /dev/null +++ b/gems/lib/envutil.rb @@ -0,0 +1 @@ +require_relative "../../tool/lib/envutil.rb" diff --git a/tool/dummy-rake-compiler/rake/extensiontask.rb b/gems/lib/rake/extensiontask.rb index 62b7ff8018..fdbe8d8874 100644 --- a/tool/dummy-rake-compiler/rake/extensiontask.rb +++ b/gems/lib/rake/extensiontask.rb @@ -1,8 +1,11 @@ +require "rake/tasklib" unless defined?(Rake::TaskLib) + module Rake class ExtensionTask < TaskLib def initialize(...) - task :compile do + task :compile do |args| puts "Dummy `compile` task defined in #{__FILE__}" + puts "#{args.name} => #{args.prereqs.join(' ')}" end end end @@ -28,6 +28,7 @@ #include "internal.h" #include "internal/array.h" #include "internal/bignum.h" +#include "internal/basic_operators.h" #include "internal/class.h" #include "internal/cont.h" #include "internal/error.h" @@ -93,9 +94,11 @@ rb_hash_freeze(VALUE hash) VALUE rb_cHash; static VALUE envtbl; -static ID id_hash, id_default, id_flatten_bang; +static ID id_hash, id_flatten_bang; static ID id_hash_iter_lev; +#define id_default idDefault + VALUE rb_hash_set_ifnone(VALUE hash, VALUE ifnone) { @@ -218,7 +221,7 @@ obj_any_hash(VALUE obj) return FIX2LONG(hval); } -static st_index_t +st_index_t rb_any_hash(VALUE a) { return any_hash(a, obj_any_hash); @@ -358,6 +361,9 @@ const struct st_hash_type rb_hashtype_ident = { rb_ident_hash, }; +#define RHASH_IDENTHASH_P(hash) (RHASH_TYPE(hash) == &identhash) +#define RHASH_STRING_KEY_P(hash, key) (!RHASH_IDENTHASH_P(hash) && (rb_obj_class(key) == rb_cString)) + typedef st_index_t st_hash_t; /* @@ -764,31 +770,39 @@ ar_free_and_clear_table(VALUE hash) HASH_ASSERT(RHASH_TRANSIENT_P(hash) == 0); } -static void -ar_try_convert_table(VALUE hash) -{ - if (!RHASH_AR_TABLE_P(hash)) return; - - const unsigned size = RHASH_AR_TABLE_SIZE(hash); +void rb_st_add_direct_with_hash(st_table *tab, st_data_t key, st_data_t value, st_hash_t hash); // st.c - st_table *new_tab; - st_index_t i; +enum ar_each_key_type { + ar_each_key_copy, + ar_each_key_cmp, + ar_each_key_insert, +}; - if (size < RHASH_AR_TABLE_MAX_SIZE) { - return; +static inline int +ar_each_key(ar_table *ar, int max, enum ar_each_key_type type, st_data_t *dst_keys, st_table *new_tab, st_hash_t *hashes) +{ + for (int i = 0; i < max; i++) { + ar_table_pair *pair = &ar->pairs[i]; + + switch (type) { + case ar_each_key_copy: + dst_keys[i] = pair->key; + break; + case ar_each_key_cmp: + if (dst_keys[i] != pair->key) return 1; + break; + case ar_each_key_insert: + if (UNDEF_P(pair->key)) continue; // deleted entry + rb_st_add_direct_with_hash(new_tab, pair->key, pair->val, hashes[i]); + break; + } } - new_tab = st_init_table_with_size(&objhash, size * 2); - - for (i = 0; i < RHASH_AR_TABLE_MAX_BOUND; i++) { - ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i); - st_add_direct(new_tab, pair->key, pair->val); - } - ar_free_and_clear_table(hash); - RHASH_ST_TABLE_SET(hash, new_tab); - return; + return 0; } + + static st_table * ar_force_convert_table(VALUE hash, const char *file, int line) { @@ -799,22 +813,32 @@ ar_force_convert_table(VALUE hash, const char *file, int line) } if (RHASH_AR_TABLE(hash)) { - unsigned i, bound = RHASH_AR_TABLE_BOUND(hash); - -#if defined(RHASH_CONVERT_TABLE_DEBUG) && RHASH_CONVERT_TABLE_DEBUG - rb_obj_info_dump(hash); - fprintf(stderr, "force_convert: %s:%d\n", file, line); - RB_DEBUG_COUNTER_INC(obj_hash_force_convert); -#endif + ar_table *ar = RHASH_AR_TABLE(hash); + st_hash_t hashes[RHASH_AR_TABLE_MAX_SIZE]; + unsigned int bound, size; + + // prepare hash values + do { + st_data_t keys[RHASH_AR_TABLE_MAX_SIZE]; + bound = RHASH_AR_TABLE_BOUND(hash); + size = RHASH_AR_TABLE_SIZE(hash); + ar_each_key(ar, bound, ar_each_key_copy, keys, NULL, NULL); + + for (unsigned int i = 0; i < bound; i++) { + // do_hash calls #hash method and it can modify hash object + hashes[i] = UNDEF_P(keys[i]) ? 0 : ar_do_hash(keys[i]); + } - new_tab = st_init_table_with_size(&objhash, RHASH_AR_TABLE_SIZE(hash)); + // check if modified + if (UNLIKELY(!RHASH_AR_TABLE_P(hash))) return RHASH_ST_TABLE(hash); + if (UNLIKELY(RHASH_AR_TABLE_BOUND(hash) != bound)) continue; + if (UNLIKELY(ar_each_key(ar, bound, ar_each_key_cmp, keys, NULL, NULL))) continue; + } while (0); - for (i = 0; i < bound; i++) { - if (ar_cleared_entry(hash, i)) continue; - ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i); - st_add_direct(new_tab, pair->key, pair->val); - } + // make st + new_tab = st_init_table_with_size(&objhash, size); + ar_each_key(ar, bound, ar_each_key_insert, NULL, new_tab, hashes); ar_free_and_clear_table(hash); } else { @@ -1406,13 +1430,19 @@ iter_lev_in_ivar_set(VALUE hash, int lev) rb_ivar_set_internal(hash, id_hash_iter_lev, INT2FIX(lev)); } -static int +static inline int iter_lev_in_flags(VALUE hash) { unsigned int u = (unsigned int)((RBASIC(hash)->flags >> RHASH_LEV_SHIFT) & RHASH_LEV_MAX); return (int)u; } +static inline void +iter_lev_in_flags_set(VALUE hash, int lev) +{ + RBASIC(hash)->flags = ((RBASIC(hash)->flags & ~RHASH_LEV_MASK) | ((VALUE)lev << RHASH_LEV_SHIFT)); +} + static int RHASH_ITER_LEV(VALUE hash) { @@ -1436,7 +1466,7 @@ hash_iter_lev_inc(VALUE hash) } else { lev += 1; - RBASIC(hash)->flags = ((RBASIC(hash)->flags & ~RHASH_LEV_MASK) | ((VALUE)lev << RHASH_LEV_SHIFT)); + iter_lev_in_flags_set(hash, lev); if (lev == RHASH_LEV_MAX) { iter_lev_in_ivar_set(hash, lev); } @@ -1454,7 +1484,7 @@ hash_iter_lev_dec(VALUE hash) } else { HASH_ASSERT(lev > 0); - RBASIC(hash)->flags = ((RBASIC(hash)->flags & ~RHASH_LEV_MASK) | ((lev-1) << RHASH_LEV_SHIFT)); + iter_lev_in_flags_set(hash, lev - 1); } } @@ -1533,6 +1563,16 @@ rb_hash_foreach(VALUE hash, rb_foreach_func *func, VALUE farg) hash_verify(hash); } +void rb_st_compact_table(st_table *tab); + +static void +compact_after_delete(VALUE hash) +{ + if (RHASH_ITER_LEV(hash) == 0 && RHASH_ST_TABLE_P(hash)) { + rb_st_compact_table(RHASH_ST_TABLE(hash)); + } +} + static VALUE hash_alloc_flags(VALUE klass, VALUE flags, VALUE ifnone) { @@ -1705,7 +1745,7 @@ rb_hash_stlike_update(VALUE hash, st_data_t key, st_update_callback_func *func, if (RHASH_AR_TABLE_P(hash)) { int result = ar_update(hash, key, func, arg); if (result == -1) { - ar_try_convert_table(hash); + ar_force_convert_table(hash, __FILE__, __LINE__); } else { return result; @@ -2070,12 +2110,26 @@ call_default_proc(VALUE proc, VALUE hash, VALUE key) return rb_proc_call_with_block(proc, 2, args, Qnil); } +static bool +rb_hash_default_unredefined(VALUE hash) +{ + VALUE klass = RBASIC_CLASS(hash); + if (LIKELY(klass == rb_cHash)) { + return !!BASIC_OP_UNREDEFINED_P(BOP_DEFAULT, HASH_REDEFINED_OP_FLAG); + } + else { + return LIKELY(rb_method_basic_definition_p(klass, id_default)); + } +} + VALUE rb_hash_default_value(VALUE hash, VALUE key) { - if (LIKELY(rb_method_basic_definition_p(CLASS_OF(hash), id_default))) { + RUBY_ASSERT(RB_TYPE_P(hash, T_HASH)); + + if (LIKELY(rb_hash_default_unredefined(hash))) { VALUE ifnone = RHASH_IFNONE(hash); - if (!FL_TEST(hash, RHASH_PROC_DEFAULT)) return ifnone; + if (LIKELY(!FL_TEST_RAW(hash, RHASH_PROC_DEFAULT))) return ifnone; if (UNDEF_P(key)) return Qnil; return call_default_proc(ifnone, hash, key); } @@ -2446,6 +2500,7 @@ rb_hash_delete_m(VALUE hash, VALUE key) val = rb_hash_delete_entry(hash, key); if (!UNDEF_P(val)) { + compact_after_delete(hash); return val; } else { @@ -2566,6 +2621,7 @@ rb_hash_delete_if(VALUE hash) rb_hash_modify_check(hash); if (!RHASH_TABLE_EMPTY_P(hash)) { rb_hash_foreach(hash, delete_if_i, hash); + compact_after_delete(hash); } return hash; } @@ -2629,6 +2685,7 @@ rb_hash_reject(VALUE hash) result = hash_dup_with_compare_by_id(hash); if (!RHASH_EMPTY_P(hash)) { rb_hash_foreach(result, delete_if_i, result); + compact_after_delete(result); } return result; } @@ -2688,6 +2745,7 @@ rb_hash_except(int argc, VALUE *argv, VALUE hash) key = argv[i]; rb_hash_delete(result, key); } + compact_after_delete(result); return result; } @@ -2785,6 +2843,7 @@ rb_hash_select(VALUE hash) result = hash_dup_with_compare_by_id(hash); if (!RHASH_EMPTY_P(hash)) { rb_hash_foreach(result, keep_if_i, result); + compact_after_delete(result); } return result; } @@ -2876,6 +2935,7 @@ rb_hash_clear(VALUE hash) } else { st_clear(RHASH_ST_TABLE(hash)); + compact_after_delete(hash); } return hash; @@ -2949,7 +3009,7 @@ rb_hash_aset(VALUE hash, VALUE key, VALUE val) ar_alloc_table(hash); } - if (RHASH_TYPE(hash) == &identhash || rb_obj_class(key) != rb_cString) { + if (!RHASH_STRING_KEY_P(hash, key)) { RHASH_UPDATE_ITER(hash, iter_lev, key, hash_aset, val); } else { @@ -3321,6 +3381,7 @@ rb_hash_transform_keys_bang(int argc, VALUE *argv, VALUE hash) rb_ary_clear(pairs); rb_hash_clear(new_keys); } + compact_after_delete(hash); return hash; } @@ -3371,6 +3432,7 @@ rb_hash_transform_values(VALUE hash) if (!RHASH_EMPTY_P(hash)) { rb_hash_stlike_foreach_with_replace(result, transform_values_foreach_func, transform_values_foreach_replace, result); + compact_after_delete(result); } return result; @@ -3907,18 +3969,9 @@ rb_hash_invert(VALUE hash) } static int -rb_hash_update_callback(st_data_t *key, st_data_t *value, struct update_arg *arg, int existing) -{ - *value = arg->arg; - return ST_CONTINUE; -} - -NOINSERT_UPDATE_CALLBACK(rb_hash_update_callback) - -static int rb_hash_update_i(VALUE key, VALUE value, VALUE hash) { - RHASH_UPDATE(hash, key, rb_hash_update_callback, value); + rb_hash_aset(hash, key, value); return ST_CONTINUE; } @@ -3930,6 +3983,9 @@ rb_hash_update_block_callback(st_data_t *key, st_data_t *value, struct update_ar if (existing) { newvalue = (st_data_t)rb_yield_values(3, (VALUE)*key, (VALUE)*value, (VALUE)newvalue); } + else if (RHASH_STRING_KEY_P(arg->hash, *key) && !RB_OBJ_FROZEN(*key)) { + *key = rb_hash_key_str(*key); + } *value = newvalue; return ST_CONTINUE; } @@ -4182,7 +4238,7 @@ rb_hash_assoc(VALUE hash, VALUE key) table = RHASH_ST_TABLE(hash); orighash = table->type; - if (orighash != &identhash) { + if (!RHASH_IDENTHASH_P(hash)) { VALUE value; struct reset_hash_type_arg ensure_arg; struct st_hash_type assochash; @@ -4442,7 +4498,7 @@ rb_hash_compare_by_id(VALUE hash) MJIT_FUNC_EXPORTED VALUE rb_hash_compare_by_id_p(VALUE hash) { - return RBOOL(RHASH_ST_TABLE_P(hash) && RHASH_ST_TABLE(hash)->type == &identhash); + return RBOOL(RHASH_IDENTHASH_P(hash)); } VALUE @@ -4760,7 +4816,7 @@ rb_hash_add_new_element(VALUE hash, VALUE key, VALUE val) if (ret != -1) { return ret; } - ar_try_convert_table(hash); + 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); @@ -4939,7 +4995,7 @@ env_name(volatile VALUE *s) static VALUE env_aset(VALUE nm, VALUE val); static void -reset_by_modified_env(const char *nam) +reset_by_modified_env(const char *nam, const char *val) { /* * ENV['TZ'] = nil has a special meaning. @@ -4948,7 +5004,7 @@ reset_by_modified_env(const char *nam) * This hack might works only on Linux glibc. */ if (ENVMATCH(nam, TZ_ENV)) { - ruby_reset_timezone(); + ruby_reset_timezone(val); } } @@ -4956,7 +5012,7 @@ static VALUE env_delete(VALUE name) { const char *nam = env_name(name); - reset_by_modified_env(nam); + reset_by_modified_env(nam, NULL); VALUE val = getenv_with_lock(nam); if (!NIL_P(val)) { @@ -5416,7 +5472,7 @@ env_aset(VALUE nm, VALUE val) get_env_ptr(value, val); ruby_setenv(name, value); - reset_by_modified_env(name); + reset_by_modified_env(name, value); return val; } @@ -5971,24 +6027,23 @@ env_to_s(VALUE _) static VALUE env_inspect(VALUE _) { - VALUE i; VALUE str = rb_str_buf_new2("{"); + rb_encoding *enc = env_encoding(); ENV_LOCK(); { char **env = GET_ENVIRON(environ); while (*env) { - char *s = strchr(*env, '='); + const char *s = strchr(*env, '='); if (env != environ) { rb_str_buf_cat2(str, ", "); } if (s) { - rb_str_buf_cat2(str, "\""); - rb_str_buf_cat(str, *env, s-*env); - rb_str_buf_cat2(str, "\"=>"); - i = rb_inspect(rb_str_new2(s+1)); - rb_str_buf_append(str, i); + rb_str_buf_append(str, rb_str_inspect(env_enc_str_new(*env, s-*env, enc))); + rb_str_buf_cat2(str, "=>"); + s++; + rb_str_buf_append(str, rb_str_inspect(env_enc_str_new(s, strlen(s), enc))); } env++; } @@ -7164,7 +7219,6 @@ void Init_Hash(void) { id_hash = rb_intern_const("hash"); - id_default = rb_intern_const("default"); id_flatten_bang = rb_intern_const("flatten!"); id_hash_iter_lev = rb_make_internal_id(); diff --git a/id_table.h b/id_table.h index 9d9eb5648e..f72e2d1d92 100644 --- a/id_table.h +++ b/id_table.h @@ -19,7 +19,6 @@ struct rb_id_table *rb_id_table_create(size_t size); void rb_id_table_free(struct rb_id_table *tbl); void rb_id_table_clear(struct rb_id_table *tbl); -size_t rb_id_table_size(const struct rb_id_table *tbl); size_t rb_id_table_memsize(const struct rb_id_table *tbl); int rb_id_table_insert(struct rb_id_table *tbl, ID id, VALUE val); @@ -33,4 +32,8 @@ void rb_id_table_foreach(struct rb_id_table *tbl, rb_id_table_foreach_func_t *fu void rb_id_table_foreach_values(struct rb_id_table *tbl, rb_id_table_foreach_values_func_t *func, void *data); void rb_id_table_foreach_values_with_replace(struct rb_id_table *tbl, rb_id_table_foreach_values_func_t *func, rb_id_table_update_value_callback_func_t *replace, void *data); +RUBY_SYMBOL_EXPORT_BEGIN +size_t rb_id_table_size(const struct rb_id_table *tbl); +RUBY_SYMBOL_EXPORT_END + #endif /* RUBY_ID_TABLE_H */ diff --git a/include/ruby/internal/abi.h b/include/ruby/internal/abi.h index d67aa0d509..44111a0055 100644 --- a/include/ruby/internal/abi.h +++ b/include/ruby/internal/abi.h @@ -31,6 +31,7 @@ #if defined(HAVE_FUNC_WEAK) && !defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__) # define RUBY_DLN_CHECK_ABI #endif +#endif /* RUBY_ABI_VERSION */ #ifdef RUBY_DLN_CHECK_ABI @@ -41,7 +42,11 @@ extern "C" { RUBY_FUNC_EXPORTED unsigned long long __attribute__((weak)) ruby_abi_version(void) { +# ifdef RUBY_ABI_VERSION return RUBY_ABI_VERSION; +# else + return 0; +# endif } # ifdef __cplusplus @@ -51,5 +56,3 @@ ruby_abi_version(void) #endif #endif - -#endif diff --git a/include/ruby/internal/anyargs.h b/include/ruby/internal/anyargs.h index e3e1b6166d..e4c6d155cc 100644 --- a/include/ruby/internal/anyargs.h +++ b/include/ruby/internal/anyargs.h @@ -84,12 +84,15 @@ #elif defined(_WIN32) || defined(__CYGWIN__) # /* Skip due to [Bug #16134] */ +# define RBIMPL_CAST_FN_PTR 1 #elif ! RBIMPL_HAS_ATTRIBUTE(transparent_union) # /* :TODO: improve here, please find a way to support. */ +# define RBIMPL_CAST_FN_PTR 1 #elif ! defined(HAVE_VA_ARGS_MACRO) # /* :TODO: improve here, please find a way to support. */ +# define RBIMPL_CAST_FN_PTR 1 #else # /** @cond INTERNAL_MACRO */ @@ -348,6 +351,25 @@ RBIMPL_ANYARGS_DECL(rb_define_method, VALUE, const char *) #endif /* __cplusplus */ +#if defined(RBIMPL_CAST_FN_PTR) && !defined(__cplusplus) +/* In C23, K&R style prototypes are gone and so `void foo(ANYARGS)` became + * equivalent to `void foo(void)` unlike in earlier versions. This is a problem + * for rb_define_* functions since that makes all valid functions one can pass + * trip -Wincompatible-pointer-types, which we treat as errors. This is mostly + * not a problem for the __builtin_choose_expr path, but outside of that we + * need to add a cast for compatibility. + */ +#define rb_define_method(klass, mid, func, arity) rb_define_method((klass), (mid), (VALUE (*)(ANYARGS))(func), (arity)) +#define rb_define_method_id(klass, mid, func, arity) rb_define_method_id((klass), (mid), (VALUE (*)(ANYARGS))(func), (arity)) +#define rb_define_singleton_method(obj, mid, func, arity) rb_define_singleton_method((obj), (mid), (VALUE (*)(ANYARGS))(func), (arity)) +#define rb_define_protected_method(klass, mid, func, arity) rb_define_protected_method((klass), (mid), (VALUE (*)(ANYARGS))(func), (arity)) +#define rb_define_private_method(klass, mid, func, arity) rb_define_private_method((klass), (mid), (VALUE (*)(ANYARGS))(func), (arity)) +#define rb_define_module_function(mod, mid, func, arity) rb_define_module_function((mod), (mid), (VALUE (*)(ANYARGS))(func), (arity)) +#define rb_define_global_function(mid, func, arity) rb_define_global_function((mid), (VALUE (*)(ANYARGS))(func), (arity)) + +#undef RBIMPL_CAST_FN_PTR +#endif /* defined(RBIMPL_CAST_FN_PTR) && !defined(__cplusplus) */ + /** * This macro is to properly cast a function parameter of *_define_method * family. It has been around since 1.x era so you can maximise backwards diff --git a/include/ruby/internal/attr/nonstring.h b/include/ruby/internal/attr/nonstring.h new file mode 100644 index 0000000000..de26e926d4 --- /dev/null +++ b/include/ruby/internal/attr/nonstring.h @@ -0,0 +1,32 @@ +#ifndef RBIMPL_ATTR_NONSTRING_H /*-*-C++-*-vi:se ft=cpp:*/ +#define RBIMPL_ATTR_NONSTRING_H +/** + * @file + * @author Ruby developers <ruby-core@ruby-lang.org> + * @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 Defines #RBIMPL_ATTR_NONSTRING. + */ +#include "ruby/internal/has/attribute.h" + +/** Wraps (or simulates) `__attribute__((nonstring))` */ +#if RBIMPL_HAS_ATTRIBUTE(nonstring) +# define RBIMPL_ATTR_NONSTRING() __attribute__((nonstring)) +#else +# define RBIMPL_ATTR_NONSTRING() /* void */ +#endif + +#endif /* RBIMPL_ATTR_NONSTRING_H */ diff --git a/include/ruby/internal/core/robject.h b/include/ruby/internal/core/robject.h index f51c524081..b1c2e1b0a9 100644 --- a/include/ruby/internal/core/robject.h +++ b/include/ruby/internal/core/robject.h @@ -37,8 +37,8 @@ /** * Convenient casting macro. * - * @param obj An object, which is in fact an ::RRegexp. - * @return The passed object casted to ::RRegexp. + * @param obj An object, which is in fact an ::RObject. + * @return The passed object casted to ::RObject. */ #define ROBJECT(obj) RBIMPL_CAST((struct RObject *)(obj)) /** @cond INTERNAL_MACRO */ diff --git a/include/ruby/internal/gc.h b/include/ruby/internal/gc.h index 66fc14e511..054e4b0f9c 100644 --- a/include/ruby/internal/gc.h +++ b/include/ruby/internal/gc.h @@ -26,10 +26,15 @@ RBIMPL_SYMBOL_EXPORT_BEGIN() /** - * Inform the garbage collector that `valptr` points to a live Ruby object that - * should not be moved. Note that extensions should use this API on global - * constants instead of assuming constants defined in Ruby are always alive. - * Ruby code can remove global constants. + * Inform the garbage collector that the global or static variable pointed by + * `valptr` stores a live Ruby object that should not be moved. Note that + * extensions should use this API on global constants instead of assuming + * constants defined in Ruby are always alive. Ruby code can remove global + * constants. + * + * Because this registration itself has a possibility to trigger a GC, this + * function must be called before any GC-able objects is assigned to the + * address pointed by `valptr`. */ void rb_gc_register_address(VALUE *valptr); diff --git a/include/ruby/internal/intern/cont.h b/include/ruby/internal/intern/cont.h index 1ec78793b7..32647f48aa 100644 --- a/include/ruby/internal/intern/cont.h +++ b/include/ruby/internal/intern/cont.h @@ -45,11 +45,7 @@ VALUE rb_fiber_new(rb_block_call_func_t func, VALUE callback_obj); * If the given storage is Qundef or Qtrue, this function is equivalent to * rb_fiber_new() which inherits storage from the current fiber. * - * If the given storage is Qfalse, this function uses the current fiber's - * storage by reference. - * - * Specifying either Qtrue or Qfalse is experimental and may be changed in the - * future. + * Specifying Qtrue is experimental and may be changed in the future. * * If the given storage is Qnil, this function will lazy initialize the * internal storage which starts of empty (without any inheritance). diff --git a/include/ruby/io/buffer.h b/include/ruby/io/buffer.h index 88e5598066..e4b855d8e7 100644 --- a/include/ruby/io/buffer.h +++ b/include/ruby/io/buffer.h @@ -56,14 +56,10 @@ enum rb_io_buffer_endian { RB_IO_BUFFER_LITTLE_ENDIAN = 4, RB_IO_BUFFER_BIG_ENDIAN = 8, -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - RB_IO_BUFFER_HOST_ENDIAN = RB_IO_BUFFER_LITTLE_ENDIAN, -#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#if defined(WORDS_BIGENDIAN) RB_IO_BUFFER_HOST_ENDIAN = RB_IO_BUFFER_BIG_ENDIAN, -#elif REG_DWORD == REG_DWORD_LITTLE_ENDIAN +#else RB_IO_BUFFER_HOST_ENDIAN = RB_IO_BUFFER_LITTLE_ENDIAN, -#elif REG_DWORD == REG_DWORD_BIG_ENDIAN - RB_IO_BUFFER_HOST_ENDIAN = RB_IO_BUFFER_BIG_ENDIAN, #endif RB_IO_BUFFER_NETWORK_ENDIAN = RB_IO_BUFFER_BIG_ENDIAN diff --git a/include/ruby/onigmo.h b/include/ruby/onigmo.h index 348c4ec08f..8d7c601703 100644 --- a/include/ruby/onigmo.h +++ b/include/ruby/onigmo.h @@ -854,6 +854,8 @@ OnigPosition onig_search_gpos(OnigRegex, const OnigUChar* str, const OnigUChar* ONIG_EXTERN OnigPosition onig_match(OnigRegex, const OnigUChar* str, const OnigUChar* end, const OnigUChar* at, OnigRegion* region, OnigOptionType option); ONIG_EXTERN +int onig_check_linear_time(OnigRegex reg); +ONIG_EXTERN OnigRegion* onig_region_new(void); ONIG_EXTERN void onig_region_init(OnigRegion* region); diff --git a/include/ruby/st.h b/include/ruby/st.h index 1e4bb80686..f35ab43603 100644 --- a/include/ruby/st.h +++ b/include/ruby/st.h @@ -98,6 +98,8 @@ struct st_table { enum st_retval {ST_CONTINUE, ST_STOP, ST_DELETE, ST_CHECK, ST_REPLACE}; +size_t rb_st_table_size(const struct st_table *tbl); +#define st_table_size rb_st_table_size st_table *rb_st_init_table(const struct st_hash_type *); #define st_init_table rb_st_init_table st_table *rb_st_init_table_with_size(const struct st_hash_type *, st_index_t); diff --git a/include/ruby/win32.h b/include/ruby/win32.h index 197eb8a802..18de3a17d8 100644 --- a/include/ruby/win32.h +++ b/include/ruby/win32.h @@ -125,8 +125,15 @@ typedef unsigned int uintptr_t; #define O_SHARE_DELETE 0x20000000 /* for rb_w32_open(), rb_w32_wopen() */ typedef int clockid_t; +#if defined(__MINGW32__) +#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 +#endif #undef utime #undef lseek diff --git a/internal/basic_operators.h b/internal/basic_operators.h index 8f4458a68b..2cd9f50073 100644 --- a/internal/basic_operators.h +++ b/internal/basic_operators.h @@ -35,6 +35,7 @@ enum ruby_basic_operators { BOP_AND, BOP_OR, BOP_CMP, + BOP_DEFAULT, BOP_LAST_ }; @@ -58,6 +59,6 @@ MJIT_SYMBOL_EXPORT_END #define FALSE_REDEFINED_OP_FLAG (1 << 11) #define PROC_REDEFINED_OP_FLAG (1 << 12) -#define BASIC_OP_UNREDEFINED_P(op, klass) (LIKELY(ruby_vm_redefined_flag[(op)]&(klass)) == 0) +#define BASIC_OP_UNREDEFINED_P(op, klass) (LIKELY((ruby_vm_redefined_flag[(op)]&(klass)) == 0)) #endif diff --git a/internal/class.h b/internal/class.h index 8080725634..63917e867f 100644 --- a/internal/class.h +++ b/internal/class.h @@ -15,6 +15,9 @@ #include "ruby/intern.h" /* for rb_alloc_func_t */ #include "ruby/ruby.h" /* for struct RBasic */ #include "shape.h" +#include "ruby_assert.h" +#include "vm_core.h" +#include "method.h" /* for rb_cref_t */ #ifdef RCLASS_SUPER # undef RCLASS_SUPER @@ -29,6 +32,7 @@ struct rb_subclass_entry { struct rb_cvar_class_tbl_entry { uint32_t index; rb_serial_t global_cvar_state; + const rb_cref_t * cref; VALUE class_value; }; @@ -53,6 +57,7 @@ struct rb_classext_struct { rb_alloc_func_t allocator; const VALUE includer; uint32_t max_iv_count; + uint32_t variation_count; #if !SHAPE_IN_BASIC_FLAGS shape_id_t shape_id; #endif @@ -114,6 +119,7 @@ void rb_class_foreach_subclass(VALUE klass, void (*f)(VALUE, VALUE), VALUE); void rb_class_detach_subclasses(VALUE); void rb_class_detach_module_subclasses(VALUE); void rb_class_remove_from_module_subclasses(VALUE); +VALUE rb_define_class_id_under_no_pin(VALUE outer, ID id, VALUE super); VALUE rb_obj_methods(int argc, const VALUE *argv, VALUE obj); VALUE rb_obj_protected_methods(int argc, const VALUE *argv, VALUE obj); VALUE rb_obj_private_methods(int argc, const VALUE *argv, VALUE obj); diff --git a/internal/eval.h b/internal/eval.h index 73bb656d96..e594d8516d 100644 --- a/internal/eval.h +++ b/internal/eval.h @@ -21,6 +21,7 @@ extern ID ruby_static_id_status; VALUE rb_refinement_module_get_refined_class(VALUE module); void rb_class_modify_check(VALUE); NORETURN(VALUE rb_f_raise(int argc, VALUE *argv)); +VALUE rb_top_main_class(const char *method); /* eval_error.c */ VALUE rb_get_backtrace(VALUE info); diff --git a/internal/gc.h b/internal/gc.h index 5b2b9e8f70..e54a5dce9d 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -14,7 +14,6 @@ #include "internal/compilers.h" /* for __has_attribute */ #include "ruby/ruby.h" /* for rb_event_flag_t */ -#include "shape.h" struct rb_execution_context_struct; /* in vm_core.h */ struct rb_objspace; /* in vm_core.h */ @@ -68,7 +67,9 @@ struct rb_objspace; /* in vm_core.h */ rb_obj_write((VALUE)(a), UNALIGNED_MEMBER_ACCESS((VALUE *)(slot)), \ (VALUE)(b), __FILE__, __LINE__) -#if USE_RVARGC && SHAPE_IN_BASIC_FLAGS +// We use SIZE_POOL_COUNT number of shape IDs for transitions out of different size pools +// The next available shapd ID will be the SPECIAL_CONST_SHAPE_ID +#if USE_RVARGC && (SIZEOF_UINT64_T == SIZEOF_VALUE) # define SIZE_POOL_COUNT 5 #else # define SIZE_POOL_COUNT 1 diff --git a/internal/hash.h b/internal/hash.h index 657e5eff3c..64832c9610 100644 --- a/internal/hash.h +++ b/internal/hash.h @@ -73,6 +73,7 @@ VALUE rb_hash_default_value(VALUE hash, VALUE key); VALUE rb_hash_set_default_proc(VALUE hash, VALUE proc); long rb_dbl_long_hash(double d); st_table *rb_init_identtable(void); +st_index_t rb_any_hash(VALUE a); VALUE rb_to_hash_type(VALUE obj); VALUE rb_hash_key_str(VALUE); VALUE rb_hash_values(VALUE hash); diff --git a/internal/thread.h b/internal/thread.h index 6394f88d34..c3e54de683 100644 --- a/internal/thread.h +++ b/internal/thread.h @@ -29,6 +29,7 @@ VALUE rb_get_coverages(void); int rb_get_coverage_mode(void); VALUE rb_default_coverage(int); VALUE rb_thread_shield_new(void); +bool rb_thread_shield_owned(VALUE self); VALUE rb_thread_shield_wait(VALUE self); VALUE rb_thread_shield_release(VALUE self); VALUE rb_thread_shield_destroy(VALUE self); diff --git a/internal/time.h b/internal/time.h index a3bf0587ec..e21b3574f6 100644 --- a/internal/time.h +++ b/internal/time.h @@ -28,7 +28,10 @@ struct timeval rb_time_timeval(VALUE); RUBY_SYMBOL_EXPORT_BEGIN /* time.c (export) */ void ruby_reset_leap_second_info(void); -void ruby_reset_timezone(void); +#ifdef RBIMPL_ATTR_DEPRECATED_INTERNAL_ONLY +RBIMPL_ATTR_DEPRECATED_INTERNAL_ONLY() +#endif +void ruby_reset_timezone(const char *); RUBY_SYMBOL_EXPORT_END #endif /* INTERNAL_TIME_H */ diff --git a/internal/variable.h b/internal/variable.h index e59a0f1924..6dec6a6759 100644 --- a/internal/variable.h +++ b/internal/variable.h @@ -38,6 +38,7 @@ static inline void ROBJ_TRANSIENT_UNSET(VALUE obj); struct gen_ivtbl; int rb_gen_ivtbl_get(VALUE obj, ID id, struct gen_ivtbl **ivtbl); +int rb_obj_evacuate_ivs_to_hash_table(ID key, VALUE val, st_data_t arg); RUBY_SYMBOL_EXPORT_BEGIN /* variable.c (export) */ @@ -56,7 +57,7 @@ VALUE rb_gvar_defined(ID); void rb_const_warn_if_deprecated(const rb_const_entry_t *, VALUE, ID); rb_shape_t * rb_grow_iv_list(VALUE obj); void rb_ensure_iv_list_size(VALUE obj, uint32_t len, uint32_t newsize); -struct gen_ivtbl * rb_ensure_generic_iv_list_size(VALUE obj, uint32_t newsize); +struct gen_ivtbl *rb_ensure_generic_iv_list_size(VALUE obj, rb_shape_t *shape, uint32_t newsize); attr_index_t rb_obj_ivar_set(VALUE obj, ID id, VALUE val); MJIT_SYMBOL_EXPORT_END @@ -1148,7 +1148,8 @@ io_internal_wait(VALUE thread, rb_io_t *fptr, int error, int events, struct time if (ready > 0) { return ready; - } else if (ready == 0) { + } + else if (ready == 0) { errno = ETIMEDOUT; return -1; } @@ -1176,7 +1177,8 @@ internal_read_func(void *ptr) if (io_again_p(errno)) { if (io_internal_wait(iis->th, iis->fptr, errno, RB_WAITFD_IN, iis->timeout) == -1) { return -1; - } else { + } + else { goto retry; } } @@ -1211,7 +1213,8 @@ internal_write_func(void *ptr) if (io_again_p(e)) { if (io_internal_wait(iis->th, iis->fptr, errno, RB_WAITFD_OUT, iis->timeout) == -1) { return -1; - } else { + } + else { goto retry; } } @@ -1240,7 +1243,8 @@ internal_writev_func(void *ptr) if (io_again_p(errno)) { if (io_internal_wait(iis->th, iis->fptr, errno, RB_WAITFD_OUT, iis->timeout) == -1) { return -1; - } else { + } + else { goto retry; } } @@ -1320,14 +1324,15 @@ rb_io_write_memory(rb_io_t *fptr, const void *buf, size_t count) static ssize_t rb_writev_internal(rb_io_t *fptr, const struct iovec *iov, int iovcnt) { + if (!iovcnt) return 0; + VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { - for (int i = 0; i < iovcnt; i += 1) { - VALUE result = rb_fiber_scheduler_io_write_memory(scheduler, fptr->self, iov[i].iov_base, iov[i].iov_len, 0); + // This path assumes at least one `iov`: + VALUE result = rb_fiber_scheduler_io_write_memory(scheduler, fptr->self, iov[0].iov_base, iov[0].iov_len, 0); - if (!UNDEF_P(result)) { - return rb_fiber_scheduler_io_result_apply(result); - } + if (!UNDEF_P(result)) { + return rb_fiber_scheduler_io_result_apply(result); } } @@ -1788,13 +1793,11 @@ io_binwrite_string(VALUE arg) // Write as much as possible: ssize_t result = io_binwrite_string_internal(p->fptr, ptr, remaining); - // If only the internal buffer is written, result will be zero [bytes of given data written]. This means we - // should try again. if (result == 0) { - errno = EWOULDBLOCK; + // If only the internal buffer is written, result will be zero [bytes of given data written]. This means we + // should try again immediately. } - - if (result > 0) { + else if (result > 0) { if ((size_t)result == remaining) break; ptr += result; remaining -= result; @@ -2034,7 +2037,7 @@ io_binwritev_internal(VALUE arg) while (remaining) { long result = rb_writev_internal(fptr, iov, iovcnt); - if (result > 0) { + if (result >= 0) { offset += result; if (fptr->wbuf.ptr && fptr->wbuf.len) { if (offset < (size_t)fptr->wbuf.len) { @@ -3679,13 +3682,11 @@ io_write_nonblock(rb_execution_context_t *ec, VALUE io, VALUE str, VALUE ex) * call-seq: * read(maxlen = nil, out_string = nil) -> new_string, out_string, or nil * - * Reads bytes from the stream, (in binary mode); - * the stream must be opened for reading + * Reads bytes from the stream; the stream must be opened for reading * (see {Access Modes}[rdoc-ref:File@Access+Modes]): * - * - If +maxlen+ is +nil+, reads all bytes. - * - Otherwise reads +maxlen+ bytes, if available. - * - Otherwise reads all bytes. + * - If +maxlen+ is +nil+, reads all bytes using the stream's data mode. + * - Otherwise reads up to +maxlen+ bytes in binary mode. * * Returns a string (either a new string or the given +out_string+) * containing the bytes read. @@ -3804,8 +3805,33 @@ rscheck(const char *rsptr, long rslen, VALUE rs) rb_raise(rb_eRuntimeError, "rs modified"); } +static const char * +search_delim(const char *p, long len, int delim, rb_encoding *enc) +{ + if (rb_enc_mbminlen(enc) == 1) { + p = memchr(p, delim, len); + if (p) return p + 1; + } + else { + const char *end = p + len; + while (p < end) { + int r = rb_enc_precise_mbclen(p, end, enc); + if (!MBCLEN_CHARFOUND_P(r)) { + p += rb_enc_mbminlen(enc); + continue; + } + int n = MBCLEN_CHARFOUND_LEN(r); + if (rb_enc_mbc_to_codepoint(p, end, enc) == (unsigned int)delim) { + return p + n; + } + p += n; + } + } + return NULL; +} + static int -appendline(rb_io_t *fptr, int delim, VALUE *strp, long *lp) +appendline(rb_io_t *fptr, int delim, VALUE *strp, long *lp, rb_encoding *enc) { VALUE str = *strp; long limit = *lp; @@ -3820,9 +3846,9 @@ appendline(rb_io_t *fptr, int delim, VALUE *strp, long *lp) p = READ_CHAR_PENDING_PTR(fptr); if (0 < limit && limit < searchlen) searchlen = (int)limit; - e = memchr(p, delim, searchlen); + e = search_delim(p, searchlen, delim, enc); if (e) { - int len = (int)(e-p+1); + int len = (int)(e-p); if (NIL_P(str)) *strp = str = rb_str_new(p, len); else @@ -3862,8 +3888,8 @@ appendline(rb_io_t *fptr, int delim, VALUE *strp, long *lp) long last; if (limit > 0 && pending > limit) pending = limit; - e = memchr(p, delim, pending); - if (e) pending = e - p + 1; + e = search_delim(p, pending, delim, enc); + if (e) pending = e - p; if (!NIL_P(str)) { last = RSTRING_LEN(str); rb_str_resize(str, last + pending); @@ -4123,16 +4149,26 @@ rb_io_getline_0(VALUE rs, long limit, int chomp, rb_io_t *fptr) rsptr = RSTRING_PTR(rs); rslen = RSTRING_LEN(rs); } + newline = '\n'; + } + else if (rb_enc_mbminlen(enc) == 1) { + rsptr = RSTRING_PTR(rs); + newline = (unsigned char)rsptr[rslen - 1]; } else { + rs = rb_str_encode(rs, rb_enc_from_encoding(enc), 0, Qnil); rsptr = RSTRING_PTR(rs); + const char *e = rsptr + rslen; + const char *last = rb_enc_prev_char(rsptr, e, e, enc); + int n; + newline = rb_enc_codepoint_len(last, e, &n, enc); + if (last + n != e) rb_raise(rb_eArgError, "broken separator"); } - newline = (unsigned char)rsptr[rslen - 1]; - chomp_cr = chomp && rslen == 1 && newline == '\n'; + chomp_cr = chomp && newline == '\n' && rslen == rb_enc_mbminlen(enc); } /* MS - Optimization */ - while ((c = appendline(fptr, newline, &str, &limit)) != EOF) { + while ((c = appendline(fptr, newline, &str, &limit, enc)) != EOF) { const char *s, *p, *pp, *e; if (c == newline) { @@ -4154,8 +4190,8 @@ rb_io_getline_0(VALUE rs, long limit, int chomp, rb_io_t *fptr) if (limit == 0) { s = RSTRING_PTR(str); p = RSTRING_END(str); - pp = rb_enc_left_char_head(s, p-1, p, enc); - if (extra_limit && + pp = rb_enc_prev_char(s, p, p, enc); + if (extra_limit && pp && MBCLEN_NEEDMORE_P(rb_enc_precise_mbclen(pp, p, enc))) { /* relax the limit while incomplete character. * extra_limit limits the relax length */ @@ -7949,7 +7985,7 @@ popen_finish(VALUE port, VALUE klass) if (NIL_P(port)) { /* child */ if (rb_block_given_p()) { - rb_yield(Qnil); + rb_protect(rb_yield, Qnil, NULL); rb_io_flush(rb_ractor_stdout()); rb_io_flush(rb_ractor_stderr()); _exit(0); @@ -8900,7 +8936,6 @@ io_puts_ary(VALUE ary, VALUE out, int recur) VALUE rb_io_puts(int argc, const VALUE *argv, VALUE out) { - int i, n; VALUE line, args[2]; /* if no argument given, print newline. */ @@ -8908,22 +8943,30 @@ rb_io_puts(int argc, const VALUE *argv, VALUE out) rb_io_write(out, rb_default_rs); return Qnil; } - for (i=0; i<argc; i++) { + for (int i = 0; i < argc; i++) { + // Convert the argument to a string: if (RB_TYPE_P(argv[i], T_STRING)) { line = argv[i]; - goto string; } - if (rb_exec_recursive(io_puts_ary, argv[i], out)) { + else if (rb_exec_recursive(io_puts_ary, argv[i], out)) { continue; } - line = rb_obj_as_string(argv[i]); - string: - n = 0; - args[n++] = line; - if (RSTRING_LEN(line) == 0 || - !rb_str_end_with_asciichar(line, '\n')) { + else { + line = rb_obj_as_string(argv[i]); + } + + // Write the line: + int n = 0; + if (RSTRING_LEN(line) == 0) { args[n++] = rb_default_rs; } + else { + args[n++] = line; + if (!rb_str_end_with_asciichar(line, '\n')) { + args[n++] = rb_default_rs; + } + } + rb_io_writev(out, n, args); } @@ -13052,6 +13095,7 @@ copy_stream_fallback_body(VALUE arg) while (1) { long numwrote; long l; + rb_str_make_independent(buf); if (stp->copy_length < (rb_off_t)0) { l = buflen; } @@ -14779,6 +14823,8 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y) * - +:binmode+: If a truthy value, specifies the mode as binary, text-only otherwise. * - +:autoclose+: If a truthy value, specifies that the +fd+ will close * when the stream closes; otherwise it remains open. + * - +:path:+ If a string value is provided, it is used in #inspect and is available as + * #path method. * * Also available are the options offered in String#encode, * which may control conversion between external internal encoding. @@ -15501,13 +15547,12 @@ Init_IO(void) rb_gvar_ractor_local("$>"); rb_gvar_ractor_local("$stderr"); - rb_stdin = rb_io_prep_stdin(); - rb_stdout = rb_io_prep_stdout(); - rb_stderr = rb_io_prep_stderr(); - rb_global_variable(&rb_stdin); + rb_stdin = rb_io_prep_stdin(); rb_global_variable(&rb_stdout); + rb_stdout = rb_io_prep_stdout(); rb_global_variable(&rb_stderr); + rb_stderr = rb_io_prep_stderr(); orig_stdout = rb_stdout; orig_stderr = rb_stderr; diff --git a/io_buffer.c b/io_buffer.c index 82590be902..87b51c0b8c 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -59,7 +59,8 @@ io_buffer_map_memory(size_t size, int flags) int mmap_flags = MAP_ANONYMOUS; if (flags & RB_IO_BUFFER_SHARED) { mmap_flags |= MAP_SHARED; - } else { + } + else { mmap_flags |= MAP_PRIVATE; } @@ -74,7 +75,7 @@ io_buffer_map_memory(size_t size, int flags) } static void -io_buffer_map_file(struct rb_io_buffer *data, int descriptor, size_t size, rb_off_t offset, enum rb_io_buffer_flags flags) +io_buffer_map_file(struct rb_io_buffer *buffer, int descriptor, size_t size, rb_off_t offset, enum rb_io_buffer_flags flags) { #if defined(_WIN32) HANDLE file = (HANDLE)_get_osfhandle(descriptor); @@ -83,7 +84,7 @@ io_buffer_map_file(struct rb_io_buffer *data, int descriptor, size_t size, rb_of DWORD protect = PAGE_READONLY, access = FILE_MAP_READ; if (flags & RB_IO_BUFFER_READONLY) { - data->flags |= RB_IO_BUFFER_READONLY; + buffer->flags |= RB_IO_BUFFER_READONLY; } else { protect = PAGE_READWRITE; @@ -95,12 +96,12 @@ io_buffer_map_file(struct rb_io_buffer *data, int descriptor, size_t size, rb_of if (flags & RB_IO_BUFFER_PRIVATE) { access |= FILE_MAP_COPY; - data->flags |= RB_IO_BUFFER_PRIVATE; + buffer->flags |= RB_IO_BUFFER_PRIVATE; } else { - // This buffer refers to external data. - data->flags |= RB_IO_BUFFER_EXTERNAL; - data->flags |= RB_IO_BUFFER_SHARED; + // This buffer refers to external buffer. + buffer->flags |= RB_IO_BUFFER_EXTERNAL; + buffer->flags |= RB_IO_BUFFER_SHARED; } void *base = MapViewOfFile(mapping, access, (DWORD)(offset >> 32), (DWORD)(offset & 0xFFFFFFFF), size); @@ -110,24 +111,24 @@ io_buffer_map_file(struct rb_io_buffer *data, int descriptor, size_t size, rb_of rb_sys_fail("io_buffer_map_file:MapViewOfFile"); } - data->mapping = mapping; + buffer->mapping = mapping; #else int protect = PROT_READ, access = 0; if (flags & RB_IO_BUFFER_READONLY) { - data->flags |= RB_IO_BUFFER_READONLY; + buffer->flags |= RB_IO_BUFFER_READONLY; } else { protect |= PROT_WRITE; } if (flags & RB_IO_BUFFER_PRIVATE) { - data->flags |= RB_IO_BUFFER_PRIVATE; + buffer->flags |= RB_IO_BUFFER_PRIVATE; } else { - // This buffer refers to external data. - data->flags |= RB_IO_BUFFER_EXTERNAL; - data->flags |= RB_IO_BUFFER_SHARED; + // This buffer refers to external buffer. + buffer->flags |= RB_IO_BUFFER_EXTERNAL; + buffer->flags |= RB_IO_BUFFER_SHARED; access |= MAP_SHARED; } @@ -138,10 +139,10 @@ io_buffer_map_file(struct rb_io_buffer *data, int descriptor, size_t size, rb_of } #endif - data->base = base; - data->size = size; + buffer->base = base; + buffer->size = size; - data->flags |= RB_IO_BUFFER_MAPPED; + buffer->flags |= RB_IO_BUFFER_MAPPED; } static inline void @@ -171,18 +172,18 @@ io_buffer_experimental(void) } static void -io_buffer_zero(struct rb_io_buffer *data) +io_buffer_zero(struct rb_io_buffer *buffer) { - data->base = NULL; - data->size = 0; + buffer->base = NULL; + buffer->size = 0; #if defined(_WIN32) - data->mapping = NULL; + buffer->mapping = NULL; #endif - data->source = Qnil; + buffer->source = Qnil; } static void -io_buffer_initialize(struct rb_io_buffer *data, void *base, size_t size, enum rb_io_buffer_flags flags, VALUE source) +io_buffer_initialize(struct rb_io_buffer *buffer, void *base, size_t size, enum rb_io_buffer_flags flags, VALUE source) { if (base) { // If we are provided a pointer, we use it. @@ -205,41 +206,41 @@ io_buffer_initialize(struct rb_io_buffer *data, void *base, size_t size, enum rb return; } - data->base = base; - data->size = size; - data->flags = flags; - data->source = source; + buffer->base = base; + buffer->size = size; + buffer->flags = flags; + buffer->source = source; } static int -io_buffer_free(struct rb_io_buffer *data) +io_buffer_free(struct rb_io_buffer *buffer) { - if (data->base) { - if (data->flags & RB_IO_BUFFER_INTERNAL) { - free(data->base); + if (buffer->base) { + if (buffer->flags & RB_IO_BUFFER_INTERNAL) { + free(buffer->base); } - if (data->flags & RB_IO_BUFFER_MAPPED) { - io_buffer_unmap(data->base, data->size); + if (buffer->flags & RB_IO_BUFFER_MAPPED) { + io_buffer_unmap(buffer->base, buffer->size); } // Previously we had this, but we found out due to the way GC works, we // can't refer to any other Ruby objects here. - // if (RB_TYPE_P(data->source, T_STRING)) { - // rb_str_unlocktmp(data->source); + // if (RB_TYPE_P(buffer->source, T_STRING)) { + // rb_str_unlocktmp(buffer->source); // } - data->base = NULL; + buffer->base = NULL; #if defined(_WIN32) - if (data->mapping) { - CloseHandle(data->mapping); - data->mapping = NULL; + if (buffer->mapping) { + CloseHandle(buffer->mapping); + buffer->mapping = NULL; } #endif - data->size = 0; - data->flags = 0; - data->source = Qnil; + buffer->size = 0; + buffer->flags = 0; + buffer->source = Qnil; return 1; } @@ -248,30 +249,30 @@ io_buffer_free(struct rb_io_buffer *data) } void -rb_io_buffer_type_mark(void *_data) +rb_io_buffer_type_mark(void *_buffer) { - struct rb_io_buffer *data = _data; - rb_gc_mark(data->source); + struct rb_io_buffer *buffer = _buffer; + rb_gc_mark(buffer->source); } void -rb_io_buffer_type_free(void *_data) +rb_io_buffer_type_free(void *_buffer) { - struct rb_io_buffer *data = _data; + struct rb_io_buffer *buffer = _buffer; - io_buffer_free(data); + io_buffer_free(buffer); - free(data); + free(buffer); } size_t -rb_io_buffer_type_size(const void *_data) +rb_io_buffer_type_size(const void *_buffer) { - const struct rb_io_buffer *data = _data; + const struct rb_io_buffer *buffer = _buffer; size_t total = sizeof(struct rb_io_buffer); - if (data->flags) { - total += data->size; + if (buffer->flags) { + total += buffer->size; } return total; @@ -288,31 +289,138 @@ static const rb_data_type_t rb_io_buffer_type = { .flags = RUBY_TYPED_FREE_IMMEDIATELY, }; +// Extract an offset argument, which must be a positive integer. +static inline size_t +io_buffer_extract_offset(VALUE argument) +{ + if (rb_int_negative_p(argument)) { + rb_raise(rb_eArgError, "Offset can't be negative!"); + } + + return NUM2SIZET(argument); +} + +// Extract a length argument, which must be a positive integer. +// Length is generally considered a mutable property of an object and +// semantically should be considered a subset of "size" as a concept. +static inline size_t +io_buffer_extract_length(VALUE argument) +{ + if (rb_int_negative_p(argument)) { + rb_raise(rb_eArgError, "Length can't be negative!"); + } + + return NUM2SIZET(argument); +} + +// Extract a size argument, which must be a positive integer. +// Size is generally considered an immutable property of an object. +static inline size_t +io_buffer_extract_size(VALUE argument) +{ + if (rb_int_negative_p(argument)) { + rb_raise(rb_eArgError, "Size can't be negative!"); + } + + return NUM2SIZET(argument); +} + +// Compute the default length for a buffer, given an offset into that buffer. +// The default length is the size of the buffer minus the offset. The offset +// must be less than the size of the buffer otherwise the length will be +// invalid; in that case, an ArgumentError exception will be raised. +static inline size_t +io_buffer_default_length(const struct rb_io_buffer *buffer, size_t offset) +{ + if (offset > buffer->size) { + rb_raise(rb_eArgError, "The given offset is bigger than the buffer size!"); + } + + // Note that the "length" is computed by the size the offset. + return buffer->size - offset; +} + +// Extract the optional length and offset arguments, returning the buffer. +// The length and offset are optional, but if they are provided, they must be +// positive integers. If the length is not provided, the default length is +// computed from the buffer size and offset. If the offset is not provided, it +// defaults to zero. +static inline struct rb_io_buffer * +io_buffer_extract_length_offset(VALUE self, int argc, VALUE argv[], size_t *length, size_t *offset) +{ + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); + + if (argc >= 2) { + *offset = io_buffer_extract_offset(argv[1]); + } + else { + *offset = 0; + } + + if (argc >= 1 && !NIL_P(argv[0])) { + *length = io_buffer_extract_length(argv[0]); + } + else { + *length = io_buffer_default_length(buffer, *offset); + } + + return buffer; +} + +// Extract the optional offset and length arguments, returning the buffer. +// Similar to `io_buffer_extract_length_offset` but with the order of +// arguments reversed. +static inline struct rb_io_buffer * +io_buffer_extract_offset_length(VALUE self, int argc, VALUE argv[], size_t *offset, size_t *length) +{ + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); + + if (argc >= 1) { + *offset = io_buffer_extract_offset(argv[0]); + } + else { + *offset = 0; + } + + if (argc >= 2) { + *length = io_buffer_extract_length(argv[1]); + } + else { + *length = io_buffer_default_length(buffer, *offset); + } + + return buffer; +} + VALUE rb_io_buffer_type_allocate(VALUE self) { - struct rb_io_buffer *data = NULL; - VALUE instance = TypedData_Make_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + VALUE instance = TypedData_Make_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - io_buffer_zero(data); + io_buffer_zero(buffer); return instance; } -static VALUE -io_buffer_for_make_instance(VALUE klass, VALUE string) +static VALUE io_buffer_for_make_instance(VALUE klass, VALUE string, enum rb_io_buffer_flags flags) { VALUE instance = rb_io_buffer_type_allocate(klass); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, buffer); - enum rb_io_buffer_flags flags = RB_IO_BUFFER_EXTERNAL; + flags |= RB_IO_BUFFER_EXTERNAL; if (RB_OBJ_FROZEN(string)) flags |= RB_IO_BUFFER_READONLY; - io_buffer_initialize(data, RSTRING_PTR(string), RSTRING_LEN(string), flags, string); + if (!(flags & RB_IO_BUFFER_READONLY)) + rb_str_modify(string); + + io_buffer_initialize(buffer, RSTRING_PTR(string), RSTRING_LEN(string), flags, string); return instance; } @@ -321,6 +429,7 @@ struct io_buffer_for_yield_instance_arguments { VALUE klass; VALUE string; VALUE instance; + enum rb_io_buffer_flags flags; }; static VALUE @@ -328,9 +437,9 @@ io_buffer_for_yield_instance(VALUE _arguments) { struct io_buffer_for_yield_instance_arguments *arguments = (struct io_buffer_for_yield_instance_arguments *)_arguments; - rb_str_locktmp(arguments->string); + arguments->instance = io_buffer_for_make_instance(arguments->klass, arguments->string, arguments->flags); - arguments->instance = io_buffer_for_make_instance(arguments->klass, arguments->string); + rb_str_locktmp(arguments->string); return rb_yield(arguments->instance); } @@ -357,14 +466,15 @@ io_buffer_for_yield_instance_ensure(VALUE _arguments) * Creates a IO::Buffer from the given string's memory. Without a block a * frozen internal copy of the string is created efficiently and used as the * buffer source. When a block is provided, the buffer is associated directly - * with the string's internal data and updating the buffer will update the + * with the string's internal buffer and updating the buffer will update the * string. * * Until #free is invoked on the buffer, either explicitly or via the garbage * collector, the source string will be locked and cannot be modified. * * If the string is frozen, it will create a read-only buffer which cannot be - * modified. + * modified. If the string is shared, it may trigger a copy-on-write when + * using the block form. * * string = 'test' * buffer = IO::Buffer.for(string) @@ -396,6 +506,7 @@ rb_io_buffer_type_for(VALUE klass, VALUE string) .klass = klass, .string = string, .instance = Qnil, + .flags = 0, }; return rb_ensure(io_buffer_for_yield_instance, (VALUE)&arguments, io_buffer_for_yield_instance_ensure, (VALUE)&arguments); @@ -403,7 +514,7 @@ rb_io_buffer_type_for(VALUE klass, VALUE string) else { // This internally returns the source string if it's already frozen. string = rb_str_tmp_frozen_acquire(string); - return io_buffer_for_make_instance(klass, string); + return io_buffer_for_make_instance(klass, string, RB_IO_BUFFER_READONLY); } } @@ -412,10 +523,10 @@ rb_io_buffer_new(void *base, size_t size, enum rb_io_buffer_flags flags) { VALUE instance = rb_io_buffer_type_allocate(rb_cIOBuffer); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, buffer); - io_buffer_initialize(data, base, size, flags, Qnil); + io_buffer_initialize(buffer, base, size, flags, Qnil); return instance; } @@ -427,12 +538,12 @@ rb_io_buffer_map(VALUE io, size_t size, rb_off_t offset, enum rb_io_buffer_flags VALUE instance = rb_io_buffer_type_allocate(rb_cIOBuffer); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, buffer); int descriptor = rb_io_descriptor(io); - io_buffer_map_file(data, descriptor, size, offset, flags); + io_buffer_map_file(buffer, descriptor, size, offset, flags); return instance; } @@ -486,7 +597,7 @@ io_buffer_map(int argc, VALUE *argv, VALUE klass) size_t size; if (argc >= 2 && !RB_NIL_P(argv[1])) { - size = RB_NUM2SIZE(argv[1]); + size = io_buffer_extract_size(argv[1]); } else { rb_off_t file_size = rb_file_size(io); @@ -505,6 +616,7 @@ io_buffer_map(int argc, VALUE *argv, VALUE klass) } } + // This is the file offset, not the buffer offset: rb_off_t offset = 0; if (argc >= 3) { offset = NUM2OFFT(argv[2]); @@ -562,13 +674,12 @@ rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self) rb_check_arity(argc, 0, 2); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); size_t size; - if (argc > 0) { - size = RB_NUM2SIZE(argv[0]); + size = io_buffer_extract_size(argv[0]); } else { size = RUBY_IO_BUFFER_DEFAULT_SIZE; @@ -582,7 +693,7 @@ rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self) flags |= io_flags_for_size(size); } - io_buffer_initialize(data, NULL, size, flags, Qnil); + io_buffer_initialize(buffer, NULL, size, flags, Qnil); return self; } @@ -617,11 +728,11 @@ io_buffer_validate_slice(VALUE source, void *base, size_t size) } static int -io_buffer_validate(struct rb_io_buffer *data) +io_buffer_validate(struct rb_io_buffer *buffer) { - if (data->source != Qnil) { + if (buffer->source != Qnil) { // Only slices incur this overhead, unfortunately... better safe than sorry! - return io_buffer_validate_slice(data->source, data->base, data->size); + return io_buffer_validate_slice(buffer->source, buffer->base, buffer->size); } else { return 1; @@ -640,47 +751,47 @@ io_buffer_validate(struct rb_io_buffer *data) VALUE rb_io_buffer_to_s(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); VALUE result = rb_str_new_cstr("#<"); rb_str_append(result, rb_class_name(CLASS_OF(self))); - rb_str_catf(result, " %p+%"PRIdSIZE, data->base, data->size); + rb_str_catf(result, " %p+%"PRIdSIZE, buffer->base, buffer->size); - if (data->base == NULL) { + if (buffer->base == NULL) { rb_str_cat2(result, " NULL"); } - if (data->flags & RB_IO_BUFFER_EXTERNAL) { + if (buffer->flags & RB_IO_BUFFER_EXTERNAL) { rb_str_cat2(result, " EXTERNAL"); } - if (data->flags & RB_IO_BUFFER_INTERNAL) { + if (buffer->flags & RB_IO_BUFFER_INTERNAL) { rb_str_cat2(result, " INTERNAL"); } - if (data->flags & RB_IO_BUFFER_MAPPED) { + if (buffer->flags & RB_IO_BUFFER_MAPPED) { rb_str_cat2(result, " MAPPED"); } - if (data->flags & RB_IO_BUFFER_SHARED) { + if (buffer->flags & RB_IO_BUFFER_SHARED) { rb_str_cat2(result, " SHARED"); } - if (data->flags & RB_IO_BUFFER_LOCKED) { + if (buffer->flags & RB_IO_BUFFER_LOCKED) { rb_str_cat2(result, " LOCKED"); } - if (data->flags & RB_IO_BUFFER_READONLY) { + if (buffer->flags & RB_IO_BUFFER_READONLY) { rb_str_cat2(result, " READONLY"); } - if (data->source != Qnil) { + if (buffer->source != Qnil) { rb_str_cat2(result, " SLICE"); } - if (!io_buffer_validate(data)) { + if (!io_buffer_validate(buffer)) { rb_str_cat2(result, " INVALID"); } @@ -730,15 +841,15 @@ io_buffer_hexdump(VALUE string, size_t width, char *base, size_t size, int first static VALUE rb_io_buffer_hexdump(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); VALUE result = Qnil; - if (io_buffer_validate(data) && data->base) { - result = rb_str_buf_new(data->size*3 + (data->size/16)*12 + 1); + if (io_buffer_validate(buffer) && buffer->base) { + result = rb_str_buf_new(buffer->size*3 + (buffer->size/16)*12 + 1); - io_buffer_hexdump(result, 16, data->base, data->size, 1); + io_buffer_hexdump(result, 16, buffer->base, buffer->size, 1); } return result; @@ -747,15 +858,15 @@ rb_io_buffer_hexdump(VALUE self) VALUE rb_io_buffer_inspect(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); VALUE result = rb_io_buffer_to_s(self); - if (io_buffer_validate(data)) { - // Limit the maximum size genearted by inspect. - if (data->size <= 256) { - io_buffer_hexdump(result, 16, data->base, data->size, 0); + if (io_buffer_validate(buffer)) { + // Limit the maximum size generated by inspect. + if (buffer->size <= 256) { + io_buffer_hexdump(result, 16, buffer->base, buffer->size, 0); } } @@ -771,16 +882,16 @@ rb_io_buffer_inspect(VALUE self) VALUE rb_io_buffer_size(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return SIZET2NUM(data->size); + return SIZET2NUM(buffer->size); } /* * call-seq: valid? -> true or false * - * Returns whether the buffer data is accessible. + * Returns whether the buffer buffer is accessible. * * A buffer becomes invalid if it is a slice of another buffer which has been * freed. @@ -788,10 +899,10 @@ rb_io_buffer_size(VALUE self) static VALUE rb_io_buffer_valid_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return RBOOL(io_buffer_validate(data)); + return RBOOL(io_buffer_validate(buffer)); } /* @@ -803,10 +914,10 @@ rb_io_buffer_valid_p(VALUE self) static VALUE rb_io_buffer_null_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return RBOOL(data->base == NULL); + return RBOOL(buffer->base == NULL); } /* @@ -819,10 +930,10 @@ rb_io_buffer_null_p(VALUE self) static VALUE rb_io_buffer_empty_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return RBOOL(data->size == 0); + return RBOOL(buffer->size == 0); } /* @@ -839,10 +950,10 @@ rb_io_buffer_empty_p(VALUE self) static VALUE rb_io_buffer_external_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return RBOOL(data->flags & RB_IO_BUFFER_EXTERNAL); + return RBOOL(buffer->flags & RB_IO_BUFFER_EXTERNAL); } /* @@ -864,10 +975,10 @@ rb_io_buffer_external_p(VALUE self) static VALUE rb_io_buffer_internal_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return RBOOL(data->flags & RB_IO_BUFFER_INTERNAL); + return RBOOL(buffer->flags & RB_IO_BUFFER_INTERNAL); } /* @@ -886,10 +997,10 @@ rb_io_buffer_internal_p(VALUE self) static VALUE rb_io_buffer_mapped_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return RBOOL(data->flags & RB_IO_BUFFER_MAPPED); + return RBOOL(buffer->flags & RB_IO_BUFFER_MAPPED); } /* @@ -902,10 +1013,10 @@ rb_io_buffer_mapped_p(VALUE self) static VALUE rb_io_buffer_shared_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return RBOOL(data->flags & RB_IO_BUFFER_SHARED); + return RBOOL(buffer->flags & RB_IO_BUFFER_SHARED); } /* @@ -927,19 +1038,19 @@ rb_io_buffer_shared_p(VALUE self) static VALUE rb_io_buffer_locked_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return RBOOL(data->flags & RB_IO_BUFFER_LOCKED); + return RBOOL(buffer->flags & RB_IO_BUFFER_LOCKED); } int rb_io_buffer_readonly_p(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - return data->flags & RB_IO_BUFFER_READONLY; + return buffer->flags & RB_IO_BUFFER_READONLY; } /* @@ -956,32 +1067,44 @@ io_buffer_readonly_p(VALUE self) return RBOOL(rb_io_buffer_readonly_p(self)); } -VALUE -rb_io_buffer_lock(VALUE self) +static void +io_buffer_lock(struct rb_io_buffer *buffer) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); - - if (data->flags & RB_IO_BUFFER_LOCKED) { + if (buffer->flags & RB_IO_BUFFER_LOCKED) { rb_raise(rb_eIOBufferLockedError, "Buffer already locked!"); } - data->flags |= RB_IO_BUFFER_LOCKED; - - return self; + buffer->flags |= RB_IO_BUFFER_LOCKED; } VALUE -rb_io_buffer_unlock(VALUE self) +rb_io_buffer_lock(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); + + io_buffer_lock(buffer); - if (!(data->flags & RB_IO_BUFFER_LOCKED)) { + return self; +} + +static void +io_buffer_unlock(struct rb_io_buffer *buffer) +{ + if (!(buffer->flags & RB_IO_BUFFER_LOCKED)) { rb_raise(rb_eIOBufferLockedError, "Buffer not locked!"); } - data->flags &= ~RB_IO_BUFFER_LOCKED; + buffer->flags &= ~RB_IO_BUFFER_LOCKED; +} + +VALUE +rb_io_buffer_unlock(VALUE self) +{ + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); + + io_buffer_unlock(buffer); return self; } @@ -989,11 +1112,11 @@ rb_io_buffer_unlock(VALUE self) int rb_io_buffer_try_unlock(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - if (data->flags & RB_IO_BUFFER_LOCKED) { - data->flags &= ~RB_IO_BUFFER_LOCKED; + if (buffer->flags & RB_IO_BUFFER_LOCKED) { + buffer->flags &= ~RB_IO_BUFFER_LOCKED; return 1; } @@ -1035,18 +1158,18 @@ rb_io_buffer_try_unlock(VALUE self) VALUE rb_io_buffer_locked(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - if (data->flags & RB_IO_BUFFER_LOCKED) { + if (buffer->flags & RB_IO_BUFFER_LOCKED) { rb_raise(rb_eIOBufferLockedError, "Buffer already locked!"); } - data->flags |= RB_IO_BUFFER_LOCKED; + buffer->flags |= RB_IO_BUFFER_LOCKED; VALUE result = rb_yield(self); - data->flags &= ~RB_IO_BUFFER_LOCKED; + buffer->flags &= ~RB_IO_BUFFER_LOCKED; return result; } @@ -1081,41 +1204,45 @@ rb_io_buffer_locked(VALUE self) VALUE rb_io_buffer_free(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - if (data->flags & RB_IO_BUFFER_LOCKED) { + if (buffer->flags & RB_IO_BUFFER_LOCKED) { rb_raise(rb_eIOBufferLockedError, "Buffer is locked!"); } - io_buffer_free(data); + io_buffer_free(buffer); return self; } +// Validate that access to the buffer is within bounds, assuming you want to +// access length bytes from the specified offset. static inline void -io_buffer_validate_range(struct rb_io_buffer *data, size_t offset, size_t length) +io_buffer_validate_range(struct rb_io_buffer *buffer, size_t offset, size_t length) { - if (offset + length > data->size) { - rb_raise(rb_eArgError, "Specified offset+length exceeds data size!"); + // We assume here that offset + length won't overflow: + if (offset + length > buffer->size) { + rb_raise(rb_eArgError, "Specified offset+length is bigger than the buffer size!"); } } static VALUE -rb_io_buffer_slice(struct rb_io_buffer *data, VALUE self, size_t offset, size_t length) +rb_io_buffer_slice(struct rb_io_buffer *buffer, VALUE self, size_t offset, size_t length) { - io_buffer_validate_range(data, offset, length); + io_buffer_validate_range(buffer, offset, length); VALUE instance = rb_io_buffer_type_allocate(rb_class_of(self)); struct rb_io_buffer *slice = NULL; TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, slice); - slice->base = (char*)data->base + offset; + slice->flags |= (buffer->flags & RB_IO_BUFFER_READONLY); + slice->base = (char*)buffer->base + offset; slice->size = length; // The source should be the root buffer: - if (data->source != Qnil) - slice->source = data->source; + if (buffer->source != Qnil) + slice->source = buffer->source; else slice->source = self; @@ -1123,7 +1250,7 @@ rb_io_buffer_slice(struct rb_io_buffer *data, VALUE self, size_t offset, size_t } /* - * call-seq: slice([offset = 0, [length]]) -> io_buffer + * call-seq: slice([offset, [length]]) -> io_buffer * * Produce another IO::Buffer which is a slice (or view into) the current one * starting at +offset+ bytes and going for +length+ bytes. @@ -1183,44 +1310,24 @@ io_buffer_slice(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 2); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + size_t offset, length; + struct rb_io_buffer *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length); - size_t offset = 0, length = 0; - - if (argc > 0) { - if (rb_int_negative_p(argv[0])) { - rb_raise(rb_eArgError, "Offset can't be negative!"); - } - - offset = NUM2SIZET(argv[0]); - } - - if (argc > 1) { - if (rb_int_negative_p(argv[1])) { - rb_raise(rb_eArgError, "Length can't be negative!"); - } - - length = NUM2SIZET(argv[1]); - } else { - length = data->size - offset; - } - - return rb_io_buffer_slice(data, self, offset, length); + return rb_io_buffer_slice(buffer, self, offset, length); } int rb_io_buffer_get_bytes(VALUE self, void **base, size_t *size) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - if (io_buffer_validate(data)) { - if (data->base) { - *base = data->base; - *size = data->size; + if (io_buffer_validate(buffer)) { + if (buffer->base) { + *base = buffer->base; + *size = buffer->size; - return data->flags; + return buffer->flags; } } @@ -1231,19 +1338,20 @@ rb_io_buffer_get_bytes(VALUE self, void **base, size_t *size) } static inline void -io_buffer_get_bytes_for_writing(struct rb_io_buffer *data, void **base, size_t *size) +io_buffer_get_bytes_for_writing(struct rb_io_buffer *buffer, void **base, size_t *size) { - if (data->flags & RB_IO_BUFFER_READONLY) { + if (buffer->flags & RB_IO_BUFFER_READONLY || + (!NIL_P(buffer->source) && OBJ_FROZEN(buffer->source))) { rb_raise(rb_eIOBufferAccessError, "Buffer is not writable!"); } - if (!io_buffer_validate(data)) { + if (!io_buffer_validate(buffer)) { rb_raise(rb_eIOBufferInvalidatedError, "Buffer is invalid!"); } - if (data->base) { - *base = data->base; - *size = data->size; + if (buffer->base) { + *base = buffer->base; + *size = buffer->size; return; } @@ -1254,22 +1362,22 @@ io_buffer_get_bytes_for_writing(struct rb_io_buffer *data, void **base, size_t * void rb_io_buffer_get_bytes_for_writing(VALUE self, void **base, size_t *size) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - io_buffer_get_bytes_for_writing(data, base, size); + io_buffer_get_bytes_for_writing(buffer, base, size); } static void -io_buffer_get_bytes_for_reading(struct rb_io_buffer *data, const void **base, size_t *size) +io_buffer_get_bytes_for_reading(struct rb_io_buffer *buffer, const void **base, size_t *size) { - if (!io_buffer_validate(data)) { + if (!io_buffer_validate(buffer)) { rb_raise(rb_eIOBufferInvalidatedError, "Buffer has been invalidated!"); } - if (data->base) { - *base = data->base; - *size = data->size; + if (buffer->base) { + *base = buffer->base; + *size = buffer->size; return; } @@ -1280,10 +1388,10 @@ io_buffer_get_bytes_for_reading(struct rb_io_buffer *data, const void **base, si void rb_io_buffer_get_bytes_for_reading(VALUE self, const void **base, size_t *size) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - io_buffer_get_bytes_for_reading(data, base, size); + io_buffer_get_bytes_for_reading(buffer, base, size); } /* @@ -1308,10 +1416,10 @@ rb_io_buffer_get_bytes_for_reading(VALUE self, const void **base, size_t *size) VALUE rb_io_buffer_transfer(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - if (data->flags & RB_IO_BUFFER_LOCKED) { + if (buffer->flags & RB_IO_BUFFER_LOCKED) { rb_raise(rb_eIOBufferLockedError, "Cannot transfer ownership of locked buffer!"); } @@ -1319,91 +1427,96 @@ rb_io_buffer_transfer(VALUE self) struct rb_io_buffer *transferred; TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, transferred); - *transferred = *data; - io_buffer_zero(data); + *transferred = *buffer; + io_buffer_zero(buffer); return instance; } static void -io_buffer_resize_clear(struct rb_io_buffer *data, void* base, size_t size) +io_buffer_resize_clear(struct rb_io_buffer *buffer, void* base, size_t size) { - if (size > data->size) { - memset((unsigned char*)base+data->size, 0, size - data->size); + if (size > buffer->size) { + memset((unsigned char*)base+buffer->size, 0, size - buffer->size); } } static void -io_buffer_resize_copy(struct rb_io_buffer *data, size_t size) +io_buffer_resize_copy(struct rb_io_buffer *buffer, size_t size) { // Slow path: struct rb_io_buffer resized; io_buffer_initialize(&resized, NULL, size, io_flags_for_size(size), Qnil); - if (data->base) { - size_t preserve = data->size; + if (buffer->base) { + size_t preserve = buffer->size; if (preserve > size) preserve = size; - memcpy(resized.base, data->base, preserve); + memcpy(resized.base, buffer->base, preserve); - io_buffer_resize_clear(data, resized.base, size); + io_buffer_resize_clear(buffer, resized.base, size); } - io_buffer_free(data); - *data = resized; + io_buffer_free(buffer); + *buffer = resized; } void rb_io_buffer_resize(VALUE self, size_t size) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - if (data->flags & RB_IO_BUFFER_LOCKED) { + if (buffer->flags & RB_IO_BUFFER_LOCKED) { rb_raise(rb_eIOBufferLockedError, "Cannot resize locked buffer!"); } - if (data->base == NULL) { - io_buffer_initialize(data, NULL, size, io_flags_for_size(size), Qnil); + if (buffer->base == NULL) { + io_buffer_initialize(buffer, NULL, size, io_flags_for_size(size), Qnil); return; } - if (data->flags & RB_IO_BUFFER_EXTERNAL) { + if (buffer->flags & RB_IO_BUFFER_EXTERNAL) { rb_raise(rb_eIOBufferAccessError, "Cannot resize external buffer!"); } #if defined(HAVE_MREMAP) && defined(MREMAP_MAYMOVE) - if (data->flags & RB_IO_BUFFER_MAPPED) { - void *base = mremap(data->base, data->size, size, MREMAP_MAYMOVE); + if (buffer->flags & RB_IO_BUFFER_MAPPED) { + void *base = mremap(buffer->base, buffer->size, size, MREMAP_MAYMOVE); if (base == MAP_FAILED) { rb_sys_fail("rb_io_buffer_resize:mremap"); } - io_buffer_resize_clear(data, base, size); + io_buffer_resize_clear(buffer, base, size); - data->base = base; - data->size = size; + buffer->base = base; + buffer->size = size; return; } #endif - if (data->flags & RB_IO_BUFFER_INTERNAL) { - void *base = realloc(data->base, size); + if (buffer->flags & RB_IO_BUFFER_INTERNAL) { + if (size == 0) { + io_buffer_free(buffer); + return; + } + + void *base = realloc(buffer->base, size); if (!base) { rb_sys_fail("rb_io_buffer_resize:realloc"); } - io_buffer_resize_clear(data, base, size); + io_buffer_resize_clear(buffer, base, size); - data->base = base; - data->size = size; + buffer->base = base; + buffer->size = size; return; } - io_buffer_resize_copy(data, size); + io_buffer_resize_copy(buffer, size); } /* @@ -1427,7 +1540,7 @@ rb_io_buffer_resize(VALUE self, size_t size) static VALUE io_buffer_resize(VALUE self, VALUE size) { - rb_io_buffer_resize(self, NUM2SIZET(size)); + rb_io_buffer_resize(self, io_buffer_extract_size(size)); return self; } @@ -1565,9 +1678,9 @@ IO_BUFFER_DECLARE_TYPE(F64, double, RB_IO_BUFFER_BIG_ENDIAN, DBL2NUM, NUM2DBL, r #undef IO_BUFFER_DECLARE_TYPE static inline size_t -io_buffer_data_type_size(ID data_type) +io_buffer_buffer_type_size(ID buffer_type) { -#define IO_BUFFER_DATA_TYPE_SIZE(name) if (data_type == RB_IO_BUFFER_DATA_TYPE_##name) return RB_IO_BUFFER_DATA_TYPE_##name##_SIZE; +#define IO_BUFFER_DATA_TYPE_SIZE(name) if (buffer_type == RB_IO_BUFFER_DATA_TYPE_##name) return RB_IO_BUFFER_DATA_TYPE_##name##_SIZE; IO_BUFFER_DATA_TYPE_SIZE(U8) IO_BUFFER_DATA_TYPE_SIZE(S8) IO_BUFFER_DATA_TYPE_SIZE(u16) @@ -1593,10 +1706,10 @@ io_buffer_data_type_size(ID data_type) /* * call-seq: - * size_of(data_type) -> byte size - * size_of(array of data_type) -> byte size + * size_of(buffer_type) -> byte size + * size_of(array of buffer_type) -> byte size * - * Returns the size of the given data type(s) in bytes. + * Returns the size of the given buffer type(s) in bytes. * * Example: * @@ -1604,23 +1717,24 @@ io_buffer_data_type_size(ID data_type) * IO::Buffer.size_of([:u32, :u32]) # => 8 */ static VALUE -io_buffer_size_of(VALUE klass, VALUE data_type) +io_buffer_size_of(VALUE klass, VALUE buffer_type) { - if (RB_TYPE_P(data_type, T_ARRAY)) { + if (RB_TYPE_P(buffer_type, T_ARRAY)) { size_t total = 0; - for (long i = 0; i < RARRAY_LEN(data_type); i++) { - total += io_buffer_data_type_size(RB_SYM2ID(RARRAY_AREF(data_type, i))); + for (long i = 0; i < RARRAY_LEN(buffer_type); i++) { + total += io_buffer_buffer_type_size(RB_SYM2ID(RARRAY_AREF(buffer_type, i))); } return SIZET2NUM(total); - } else { - return SIZET2NUM(io_buffer_data_type_size(RB_SYM2ID(data_type))); + } + else { + return SIZET2NUM(io_buffer_buffer_type_size(RB_SYM2ID(buffer_type))); } } static inline VALUE -rb_io_buffer_get_value(const void* base, size_t size, ID data_type, size_t *offset) +rb_io_buffer_get_value(const void* base, size_t size, ID buffer_type, size_t *offset) { -#define IO_BUFFER_GET_VALUE(name) if (data_type == RB_IO_BUFFER_DATA_TYPE_##name) return io_buffer_read_##name(base, size, offset); +#define IO_BUFFER_GET_VALUE(name) if (buffer_type == RB_IO_BUFFER_DATA_TYPE_##name) return io_buffer_read_##name(base, size, offset); IO_BUFFER_GET_VALUE(U8) IO_BUFFER_GET_VALUE(S8) @@ -1649,9 +1763,9 @@ rb_io_buffer_get_value(const void* base, size_t size, ID data_type, size_t *offs } /* - * call-seq: get_value(data_type, offset) -> numeric + * call-seq: get_value(buffer_type, offset) -> numeric * - * Read from buffer a value of +type+ at +offset+. +data_type+ should be one + * Read from buffer a value of +type+ at +offset+. +buffer_type+ should be one * of symbols: * * * +:U8+: unsigned integer, 1 byte @@ -1673,8 +1787,8 @@ rb_io_buffer_get_value(const void* base, size_t size, ID data_type, size_t *offs * * +:f64+: double, 8 bytes, little-endian * * +:F64+: double, 8 bytes, big-endian * - * A data type refers specifically to the type of binary data that is stored - * in the buffer. For example, a +:u32+ data type is a 32-bit unsigned + * A buffer type refers specifically to the type of binary buffer that is stored + * in the buffer. For example, a +:u32+ buffer type is a 32-bit unsigned * integer in little-endian format. * * Example: @@ -1689,7 +1803,7 @@ io_buffer_get_value(VALUE self, VALUE type, VALUE _offset) { const void *base; size_t size; - size_t offset = NUM2SIZET(_offset); + size_t offset = io_buffer_extract_offset(_offset); rb_io_buffer_get_bytes_for_reading(self, &base, &size); @@ -1697,9 +1811,9 @@ io_buffer_get_value(VALUE self, VALUE type, VALUE _offset) } /* - * call-seq: get_values(data_types, offset) -> array + * call-seq: get_values(buffer_types, offset) -> array * - * Similar to #get_value, except that it can handle multiple data types and + * Similar to #get_value, except that it can handle multiple buffer types and * returns an array of values. * * Example: @@ -1709,22 +1823,22 @@ io_buffer_get_value(VALUE self, VALUE type, VALUE _offset) * # => [1.5, 2.5] */ static VALUE -io_buffer_get_values(VALUE self, VALUE data_types, VALUE _offset) +io_buffer_get_values(VALUE self, VALUE buffer_types, VALUE _offset) { - size_t offset = NUM2SIZET(_offset); + size_t offset = io_buffer_extract_offset(_offset); const void *base; size_t size; rb_io_buffer_get_bytes_for_reading(self, &base, &size); - if (!RB_TYPE_P(data_types, T_ARRAY)) { - rb_raise(rb_eArgError, "Argument data_types should be an array!"); + if (!RB_TYPE_P(buffer_types, T_ARRAY)) { + rb_raise(rb_eArgError, "Argument buffer_types should be an array!"); } - VALUE array = rb_ary_new_capa(RARRAY_LEN(data_types)); + VALUE array = rb_ary_new_capa(RARRAY_LEN(buffer_types)); - for (long i = 0; i < RARRAY_LEN(data_types); i++) { - VALUE type = rb_ary_entry(data_types, i); + for (long i = 0; i < RARRAY_LEN(buffer_types); i++) { + VALUE type = rb_ary_entry(buffer_types, i); VALUE value = rb_io_buffer_get_value(base, size, RB_SYM2ID(type), &offset); rb_ary_push(array, value); } @@ -1732,12 +1846,46 @@ io_buffer_get_values(VALUE self, VALUE data_types, VALUE _offset) return array; } +// Extract a count argument, which must be a positive integer. +// Count is generally considered relative to the number of things. +static inline size_t +io_buffer_extract_count(VALUE argument) +{ + if (rb_int_negative_p(argument)) { + rb_raise(rb_eArgError, "Count can't be negative!"); + } + + return NUM2SIZET(argument); +} + +static inline void +io_buffer_extract_offset_count(ID buffer_type, size_t size, int argc, VALUE *argv, size_t *offset, size_t *count) +{ + if (argc >= 1) { + *offset = io_buffer_extract_offset(argv[0]); + } + else { + *offset = 0; + } + + if (argc >= 2) { + *count = io_buffer_extract_count(argv[1]); + } + else { + if (*offset > size) { + rb_raise(rb_eArgError, "The given offset is bigger than the buffer size!"); + } + + *count = (size - *offset) / io_buffer_buffer_type_size(buffer_type); + } +} + /* * call-seq: - * each(data_type, [offset, [count]]) {|offset, value| ...} -> self - * each(data_type, [offset, [count]]) -> enumerator + * each(buffer_type, [offset, [count]]) {|offset, value| ...} -> self + * each(buffer_type, [offset, [count]]) -> enumerator * - * Iterates over the buffer, yielding each +value+ of +data_type+ starting + * Iterates over the buffer, yielding each +value+ of +buffer_type+ starting * from +offset+. * * If +count+ is given, only +count+ values will be yielded. @@ -1760,30 +1908,20 @@ io_buffer_each(int argc, VALUE *argv, VALUE self) rb_io_buffer_get_bytes_for_reading(self, &base, &size); - ID data_type; + ID buffer_type; if (argc >= 1) { - data_type = RB_SYM2ID(argv[0]); - } else { - data_type = RB_IO_BUFFER_DATA_TYPE_U8; + buffer_type = RB_SYM2ID(argv[0]); } - - size_t offset; - if (argc >= 2) { - offset = NUM2SIZET(argv[1]); - } else { - offset = 0; + else { + buffer_type = RB_IO_BUFFER_DATA_TYPE_U8; } - size_t count; - if (argc >= 3) { - count = NUM2SIZET(argv[2]); - } else { - count = (size - offset) / io_buffer_data_type_size(data_type); - } + size_t offset, count; + io_buffer_extract_offset_count(buffer_type, size, argc-1, argv+1, &offset, &count); for (size_t i = 0; i < count; i++) { size_t current_offset = offset; - VALUE value = rb_io_buffer_get_value(base, size, data_type, &offset); + VALUE value = rb_io_buffer_get_value(base, size, buffer_type, &offset); rb_yield_values(2, SIZET2NUM(current_offset), value); } @@ -1791,9 +1929,9 @@ io_buffer_each(int argc, VALUE *argv, VALUE self) } /* - * call-seq: values(data_type, [offset, [count]]) -> array + * call-seq: values(buffer_type, [offset, [count]]) -> array * - * Returns an array of values of +data_type+ starting from +offset+. + * Returns an array of values of +buffer_type+ starting from +offset+. * * If +count+ is given, only +count+ values will be returned. * @@ -1810,31 +1948,21 @@ io_buffer_values(int argc, VALUE *argv, VALUE self) rb_io_buffer_get_bytes_for_reading(self, &base, &size); - ID data_type; + ID buffer_type; if (argc >= 1) { - data_type = RB_SYM2ID(argv[0]); - } else { - data_type = RB_IO_BUFFER_DATA_TYPE_U8; + buffer_type = RB_SYM2ID(argv[0]); } - - size_t offset; - if (argc >= 2) { - offset = NUM2SIZET(argv[1]); - } else { - offset = 0; + else { + buffer_type = RB_IO_BUFFER_DATA_TYPE_U8; } - size_t count; - if (argc >= 3) { - count = NUM2SIZET(argv[2]); - } else { - count = (size - offset) / io_buffer_data_type_size(data_type); - } + size_t offset, count; + io_buffer_extract_offset_count(buffer_type, size, argc-1, argv+1, &offset, &count); VALUE array = rb_ary_new_capa(count); for (size_t i = 0; i < count; i++) { - VALUE value = rb_io_buffer_get_value(base, size, data_type, &offset); + VALUE value = rb_io_buffer_get_value(base, size, buffer_type, &offset); rb_ary_push(array, value); } @@ -1868,19 +1996,8 @@ io_buffer_each_byte(int argc, VALUE *argv, VALUE self) rb_io_buffer_get_bytes_for_reading(self, &base, &size); - size_t offset; - if (argc >= 2) { - offset = NUM2SIZET(argv[1]); - } else { - offset = 0; - } - - size_t count; - if (argc >= 3) { - count = NUM2SIZET(argv[2]); - } else { - count = (size - offset); - } + size_t offset, count; + io_buffer_extract_offset_count(RB_IO_BUFFER_DATA_TYPE_U8, size, argc-1, argv+1, &offset, &count); for (size_t i = 0; i < count; i++) { unsigned char *value = (unsigned char *)base + i + offset; @@ -1891,9 +2008,9 @@ io_buffer_each_byte(int argc, VALUE *argv, VALUE self) } static inline void -rb_io_buffer_set_value(const void* base, size_t size, ID data_type, size_t *offset, VALUE value) +rb_io_buffer_set_value(const void* base, size_t size, ID buffer_type, size_t *offset, VALUE value) { -#define IO_BUFFER_SET_VALUE(name) if (data_type == RB_IO_BUFFER_DATA_TYPE_##name) {io_buffer_write_##name(base, size, offset, value); return;} +#define IO_BUFFER_SET_VALUE(name) if (buffer_type == RB_IO_BUFFER_DATA_TYPE_##name) {io_buffer_write_##name(base, size, offset, value); return;} IO_BUFFER_SET_VALUE(U8); IO_BUFFER_SET_VALUE(S8); @@ -1956,7 +2073,7 @@ io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value) { void *base; size_t size; - size_t offset = NUM2SIZET(_offset); + size_t offset = io_buffer_extract_offset(_offset); rb_io_buffer_get_bytes_for_writing(self, &base, &size); @@ -1966,9 +2083,9 @@ io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value) } /* - * call-seq: set_values(data_types, offset, values) -> offset + * call-seq: set_values(buffer_types, offset, values) -> offset * - * Write +values+ of +data_types+ at +offset+ to the buffer. +data_types+ + * Write +values+ of +buffer_types+ at +offset+ to the buffer. +buffer_types+ * should be an array of symbols as described in #get_value. +values+ should * be an array of values to write. * @@ -1982,28 +2099,28 @@ io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value) * # 0x00000000 01 00 02 00 00 00 00 00 ........ */ static VALUE -io_buffer_set_values(VALUE self, VALUE data_types, VALUE _offset, VALUE values) +io_buffer_set_values(VALUE self, VALUE buffer_types, VALUE _offset, VALUE values) { - if (!RB_TYPE_P(data_types, T_ARRAY)) { - rb_raise(rb_eArgError, "Argument data_types should be an array!"); + if (!RB_TYPE_P(buffer_types, T_ARRAY)) { + rb_raise(rb_eArgError, "Argument buffer_types should be an array!"); } if (!RB_TYPE_P(values, T_ARRAY)) { rb_raise(rb_eArgError, "Argument values should be an array!"); } - if (RARRAY_LEN(data_types) != RARRAY_LEN(values)) { - rb_raise(rb_eArgError, "Argument data_types and values should have the same length!"); + if (RARRAY_LEN(buffer_types) != RARRAY_LEN(values)) { + rb_raise(rb_eArgError, "Argument buffer_types and values should have the same length!"); } - size_t offset = NUM2SIZET(_offset); + size_t offset = io_buffer_extract_offset(_offset); void *base; size_t size; rb_io_buffer_get_bytes_for_writing(self, &base, &size); - for (long i = 0; i < RARRAY_LEN(data_types); i++) { - VALUE type = rb_ary_entry(data_types, i); + for (long i = 0; i < RARRAY_LEN(buffer_types); i++) { + VALUE type = rb_ary_entry(buffer_types, i); VALUE value = rb_ary_entry(values, i); rb_io_buffer_set_value(base, size, RB_SYM2ID(type), &offset, value); } @@ -2012,16 +2129,16 @@ io_buffer_set_values(VALUE self, VALUE data_types, VALUE _offset, VALUE values) } static void -io_buffer_memcpy(struct rb_io_buffer *data, size_t offset, const void *source_base, size_t source_offset, size_t source_size, size_t length) +io_buffer_memcpy(struct rb_io_buffer *buffer, size_t offset, const void *source_base, size_t source_offset, size_t source_size, size_t length) { void *base; size_t size; - io_buffer_get_bytes_for_writing(data, &base, &size); + io_buffer_get_bytes_for_writing(buffer, &base, &size); - io_buffer_validate_range(data, offset, length); + io_buffer_validate_range(buffer, offset, length); if (source_offset + length > source_size) { - rb_raise(rb_eArgError, "The computed source range exceeds the size of the source!"); + rb_raise(rb_eArgError, "The computed source range exceeds the size of the source buffer!"); } memcpy((unsigned char*)base+offset, (unsigned char*)source_base+source_offset, length); @@ -2029,23 +2146,20 @@ io_buffer_memcpy(struct rb_io_buffer *data, size_t offset, const void *source_ba // (offset, length, source_offset) -> length static VALUE -io_buffer_copy_from(struct rb_io_buffer *data, const void *source_base, size_t source_size, int argc, VALUE *argv) +io_buffer_copy_from(struct rb_io_buffer *buffer, const void *source_base, size_t source_size, int argc, VALUE *argv) { - size_t offset; + size_t offset = 0; size_t length; size_t source_offset; // The offset we copy into the buffer: if (argc >= 1) { - offset = NUM2SIZET(argv[0]); - } - else { - offset = 0; + offset = io_buffer_extract_offset(argv[0]); } // The offset we start from within the string: if (argc >= 3) { - source_offset = NUM2SIZET(argv[2]); + source_offset = io_buffer_extract_offset(argv[2]); if (source_offset > source_size) { rb_raise(rb_eArgError, "The given source offset is bigger than the source itself!"); @@ -2057,14 +2171,14 @@ io_buffer_copy_from(struct rb_io_buffer *data, const void *source_base, size_t s // The length we are going to copy: if (argc >= 2 && !RB_NIL_P(argv[1])) { - length = NUM2SIZET(argv[1]); + length = io_buffer_extract_length(argv[1]); } else { // Default to the source offset -> source size: length = source_size - source_offset; } - io_buffer_memcpy(data, offset, source_base, source_offset, source_size, length); + io_buffer_memcpy(buffer, offset, source_base, source_offset, source_size, length); return SIZET2NUM(length); } @@ -2089,24 +2203,24 @@ io_buffer_copy_from(struct rb_io_buffer *data, const void *source_base, size_t s static VALUE rb_io_buffer_initialize_copy(VALUE self, VALUE source) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); const void *source_base; size_t source_size; rb_io_buffer_get_bytes_for_reading(source, &source_base, &source_size); - io_buffer_initialize(data, NULL, source_size, io_flags_for_size(source_size), Qnil); + io_buffer_initialize(buffer, NULL, source_size, io_flags_for_size(source_size), Qnil); - return io_buffer_copy_from(data, source_base, source_size, 0, NULL); + return io_buffer_copy_from(buffer, source_base, source_size, 0, NULL); } /* * call-seq: * copy(source, [offset, [length, [source_offset]]]) -> size * - * Efficiently copy data from a source IO::Buffer into the buffer, + * Efficiently copy buffer from a source IO::Buffer into the buffer, * at +offset+ using +memcpy+. For copying String instances, see #set_string. * * buffer = IO::Buffer.new(32) @@ -2116,22 +2230,22 @@ rb_io_buffer_initialize_copy(VALUE self, VALUE source) * # 0x00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ * * * buffer.copy(IO::Buffer.for("test"), 8) - * # => 4 -- size of data copied + * # => 4 -- size of buffer copied * buffer * # => * # #<IO::Buffer 0x0000555f5cf8fe40+32 INTERNAL> * # 0x00000000 00 00 00 00 00 00 00 00 74 65 73 74 00 00 00 00 ........test.... * # 0x00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ * * - * #copy can be used to put data into strings associated with buffer: + * #copy can be used to put buffer into strings associated with buffer: * - * string= "data: " - * # => "data: " + * string= "buffer: " + * # => "buffer: " * buffer = IO::Buffer.for(string) * buffer.copy(IO::Buffer.for("test"), 5) * # => 4 * string - * # => "data:test" + * # => "buffer:test" * * Attempt to copy into a read-only buffer will fail: * @@ -2149,20 +2263,20 @@ rb_io_buffer_initialize_copy(VALUE self, VALUE source) * File.read('test.txt') * # => "boom" * - * Attempt to copy the data which will need place outside of buffer's + * Attempt to copy the buffer which will need place outside of buffer's * bounds will fail: * * buffer = IO::Buffer.new(2) * buffer.copy(IO::Buffer.for('test'), 0) - * # in `copy': Specified offset+length exceeds source size! (ArgumentError) + * # in `copy': Specified offset+length is bigger than the buffer size! (ArgumentError) */ static VALUE io_buffer_copy(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 1, 4); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); VALUE source = argv[0]; const void *source_base; @@ -2170,7 +2284,7 @@ io_buffer_copy(int argc, VALUE *argv, VALUE self) rb_io_buffer_get_bytes_for_reading(source, &source_base, &source_size); - return io_buffer_copy_from(data, source_base, source_size, argc-1, argv+1); + return io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1); } /* @@ -2192,33 +2306,22 @@ io_buffer_get_string(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 3); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + size_t offset, length; + struct rb_io_buffer *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length); const void *base; size_t size; - io_buffer_get_bytes_for_reading(data, &base, &size); - - size_t offset = 0; - size_t length = size; - rb_encoding *encoding = rb_ascii8bit_encoding(); - - if (argc >= 1) { - offset = NUM2SIZET(argv[0]); - } - - if (argc >= 2 && !RB_NIL_P(argv[1])) { - length = NUM2SIZET(argv[1]); - } - else { - length = size - offset; - } + io_buffer_get_bytes_for_reading(buffer, &base, &size); + rb_encoding *encoding; if (argc >= 3) { encoding = rb_find_encoding(argv[2]); } + else { + encoding = rb_ascii8bit_encoding(); + } - io_buffer_validate_range(data, offset, length); + io_buffer_validate_range(buffer, offset, length); return rb_enc_str_new((const char*)base + offset, length, encoding); } @@ -2226,7 +2329,7 @@ io_buffer_get_string(int argc, VALUE *argv, VALUE self) /* * call-seq: set_string(string, [offset, [length, [source_offset]]]) -> size * - * Efficiently copy data from a source String into the buffer, + * Efficiently copy buffer from a source String into the buffer, * at +offset+ using +memcpy+. * * buf = IO::Buffer.new(8) @@ -2234,7 +2337,7 @@ io_buffer_get_string(int argc, VALUE *argv, VALUE self) * # #<IO::Buffer 0x0000557412714a20+8 INTERNAL> * # 0x00000000 00 00 00 00 00 00 00 00 ........ * - * # set data starting from offset 1, take 2 bytes starting from string's + * # set buffer starting from offset 1, take 2 bytes starting from string's * # second * buf.set_string('test', 1, 2, 1) * # => 2 @@ -2251,28 +2354,28 @@ io_buffer_set_string(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 1, 4); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); VALUE string = rb_str_to_str(argv[0]); const void *source_base = RSTRING_PTR(string); size_t source_size = RSTRING_LEN(string); - return io_buffer_copy_from(data, source_base, source_size, argc-1, argv+1); + return io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1); } void rb_io_buffer_clear(VALUE self, uint8_t value, size_t offset, size_t length) { + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); + void *base; size_t size; + io_buffer_get_bytes_for_writing(buffer, &base, &size); - rb_io_buffer_get_bytes_for_writing(self, &base, &size); - - if (offset + length > size) { - rb_raise(rb_eArgError, "The given offset + length out of bounds!"); - } + io_buffer_validate_range(buffer, offset, length); memset((char*)base + offset, value, length); } @@ -2313,26 +2416,13 @@ io_buffer_clear(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 3); - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); - uint8_t value = 0; if (argc >= 1) { value = NUM2UINT(argv[0]); } - size_t offset = 0; - if (argc >= 2) { - offset = NUM2SIZET(argv[1]); - } - - size_t length; - if (argc >= 3) { - length = NUM2SIZET(argv[2]); - } - else { - length = data->size - offset; - } + size_t offset, length; + io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length); rb_io_buffer_clear(self, value, offset, length); @@ -2364,18 +2454,91 @@ io_buffer_default_size(size_t page_size) return platform_agnostic_default_size; } +struct io_buffer_blocking_region_argument { + struct rb_io_buffer *buffer; + rb_blocking_function_t *function; + void *data; + int descriptor; +}; + +static VALUE +io_buffer_blocking_region_begin(VALUE _argument) +{ + struct io_buffer_blocking_region_argument *argument = (void*)_argument; + + return rb_thread_io_blocking_region(argument->function, argument->data, argument->descriptor); +} + +static VALUE +io_buffer_blocking_region_ensure(VALUE _argument) +{ + struct io_buffer_blocking_region_argument *argument = (void*)_argument; + + io_buffer_unlock(argument->buffer); + + return Qnil; +} + +static VALUE +io_buffer_blocking_region(struct rb_io_buffer *buffer, rb_blocking_function_t *function, void *data, int descriptor) +{ + struct io_buffer_blocking_region_argument argument = { + .buffer = buffer, + .function = function, + .data = data, + .descriptor = descriptor, + }; + + // If the buffer is already locked, we can skip the ensure (unlock): + if (buffer->flags & RB_IO_BUFFER_LOCKED) { + return io_buffer_blocking_region_begin((VALUE)&argument); + } + else { + // The buffer should be locked for the duration of the blocking region: + io_buffer_lock(buffer); + + return rb_ensure(io_buffer_blocking_region_begin, (VALUE)&argument, io_buffer_blocking_region_ensure, (VALUE)&argument); + } +} + struct io_buffer_read_internal_argument { int descriptor; - void *base; + + // The base pointer to read from: + char *base; + // The size of the buffer: size_t size; + + // The minimum number of bytes to read: + size_t length; }; static VALUE io_buffer_read_internal(void *_argument) { + size_t total = 0; struct io_buffer_read_internal_argument *argument = _argument; - ssize_t result = read(argument->descriptor, argument->base, argument->size); - return rb_fiber_scheduler_io_result(result, errno); + + while (true) { + ssize_t result = read(argument->descriptor, argument->base, argument->size); + + if (result < 0) { + return rb_fiber_scheduler_io_result(result, errno); + } + else if (result == 0) { + return rb_fiber_scheduler_io_result(total, 0); + } + else { + total += result; + + if (total >= argument->length) { + return rb_fiber_scheduler_io_result(total, 0); + } + + argument->base = argument->base + result; + argument->size = argument->size - result; + } + } } VALUE @@ -2383,82 +2546,72 @@ rb_io_buffer_read(VALUE self, VALUE io, size_t length, size_t offset) { VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { - VALUE result = rb_fiber_scheduler_io_read(scheduler, io, self, SIZET2NUM(length), SIZET2NUM(offset)); + VALUE result = rb_fiber_scheduler_io_read(scheduler, io, self, length, offset); if (!UNDEF_P(result)) { return result; } } - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - io_buffer_validate_range(data, offset, length); + io_buffer_validate_range(buffer, offset, length); int descriptor = rb_io_descriptor(io); void * base; size_t size; - io_buffer_get_bytes_for_writing(data, &base, &size); + io_buffer_get_bytes_for_writing(buffer, &base, &size); base = (unsigned char*)base + offset; + size = size - offset; struct io_buffer_read_internal_argument argument = { .descriptor = descriptor, .base = base, - .size = length, + .size = size, + .length = length, }; - return rb_thread_io_blocking_region(io_buffer_read_internal, &argument, descriptor); + return io_buffer_blocking_region(buffer, io_buffer_read_internal, &argument, descriptor); } /* - * call-seq: read(io, [length, [offset]]) -> self + * call-seq: read(io, [length, [offset]]) -> read length or -errno * - * Read at most +length+ bytes from +io+ into the buffer, starting at - * +offset+. + * Read at least +length+ bytes from the +io+, into the buffer starting at + * +offset+. If an error occurs, return <tt>-errno</tt>. * - * If +length+ is not given, read until the end of the buffer. + * If +length+ is not given or +nil+, it defaults to the size of the buffer + * minus the offset, i.e. the entire buffer. * - * If +offset+ is not given, read from the beginning of the buffer. + * If +length+ is zero, exactly one <tt>read</tt> operation will occur. * - * If +length+ is 0, read nothing. - * - * Example: + * If +offset+ is not given, it defaults to zero, i.e. the beginning of the + * buffer. * - * buffer = IO::Buffer.for('test') - * # => - * # <IO::Buffer 0x00007fca40087c38+4 SLICE> - * # 0x00000000 74 65 73 74 test - * buffer.read(File.open('/dev/urandom', 'rb'), 4) - * # => - * # <IO::Buffer 0x00007fca40087c38+4 SLICE> - * # 0x00000000 2a 0e 0e 0e *... + * IO::Buffer.for('test') do |buffer| + * p buffer + * # => + * # <IO::Buffer 0x00007fca40087c38+4 SLICE> + * # 0x00000000 74 65 73 74 test + * buffer.read(File.open('/dev/urandom', 'rb'), 2) + * p buffer + * # => + * # <IO::Buffer 0x00007f3bc65f2a58+4 EXTERNAL SLICE> + * # 0x00000000 05 35 73 74 .5st + * end */ static VALUE io_buffer_read(int argc, VALUE *argv, VALUE self) { - rb_check_arity(argc, 2, 3); + rb_check_arity(argc, 1, 3); VALUE io = argv[0]; - size_t length; - if (argc >= 2) { - if (rb_int_negative_p(argv[1])) { - rb_raise(rb_eArgError, "Length can't be negative!"); - } - - length = NUM2SIZET(argv[1]); - } - - size_t offset = 0; - if (argc >= 3) { - if (rb_int_negative_p(argv[2])) { - rb_raise(rb_eArgError, "Offset can't be negative!"); - } - - offset = NUM2SIZET(argv[2]); - } + size_t length, offset; + io_buffer_extract_length_offset(self, argc-1, argv+1, &length, &offset); return rb_io_buffer_read(self, io, length, offset); } @@ -2500,72 +2653,118 @@ rb_io_buffer_pread(VALUE self, VALUE io, rb_off_t from, size_t length, size_t of { VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { - VALUE result = rb_fiber_scheduler_io_pread(scheduler, io, OFFT2NUM(from), self, SIZET2NUM(length), SIZET2NUM(offset)); + VALUE result = rb_fiber_scheduler_io_pread(scheduler, io, from, self, length, offset); if (!UNDEF_P(result)) { return result; } } - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - io_buffer_validate_range(data, 0, length); + io_buffer_validate_range(buffer, offset, length); int descriptor = rb_io_descriptor(io); void * base; size_t size; - io_buffer_get_bytes_for_writing(data, &base, &size); + io_buffer_get_bytes_for_writing(buffer, &base, &size); struct io_buffer_pread_internal_argument argument = { .descriptor = descriptor, - .base = base, + + // Move the base pointer to the offset: + .base = (unsigned char*)base + offset, + + // And the size to the length of buffer we want to read: .size = length, + + // From the offset in the file we want to read from: .offset = from, }; - return rb_thread_io_blocking_region(io_buffer_pread_internal, &argument, descriptor); + return io_buffer_blocking_region(buffer, io_buffer_pread_internal, &argument, descriptor); } +/* + * call-seq: pread(io, from, length, [offset]) -> read length or -errno + * + * Read at most +length+ bytes from +io+ into the buffer, starting at + * +from+, and put it in buffer starting from specified +offset+. + * If an error occurs, return <tt>-errno</tt>. + * + * If +offset+ is not given, put it at the beginning of the buffer. + * + * Example: + * + * IO::Buffer.for('test') do |buffer| + * p buffer + * # => + * # <IO::Buffer 0x00007fca40087c38+4 SLICE> + * # 0x00000000 74 65 73 74 test + * + * # take 2 bytes from the beginning of urandom, + * # put them in buffer starting from position 2 + * buffer.pread(File.open('/dev/urandom', 'rb'), 0, 2, 2) + * p buffer + * # => + * # <IO::Buffer 0x00007f3bc65f2a58+4 EXTERNAL SLICE> + * # 0x00000000 05 35 73 74 te.5 + * end + */ static VALUE io_buffer_pread(int argc, VALUE *argv, VALUE self) { - rb_check_arity(argc, 3, 4); + rb_check_arity(argc, 2, 4); VALUE io = argv[0]; rb_off_t from = NUM2OFFT(argv[1]); - size_t length; - if (rb_int_negative_p(argv[2])) { - rb_raise(rb_eArgError, "Length can't be negative!"); - } - length = NUM2SIZET(argv[2]); - - size_t offset = 0; - if (argc >= 4) { - if (rb_int_negative_p(argv[3])) { - rb_raise(rb_eArgError, "Offset can't be negative!"); - } - - offset = NUM2SIZET(argv[3]); - } + size_t length, offset; + io_buffer_extract_length_offset(self, argc-2, argv+2, &length, &offset); return rb_io_buffer_pread(self, io, from, length, offset); } struct io_buffer_write_internal_argument { int descriptor; - const void *base; + + // The base pointer to write from: + const char *base; + // The size of the buffer: size_t size; + + // The minimum length to write: + size_t length; }; static VALUE io_buffer_write_internal(void *_argument) { + size_t total = 0; struct io_buffer_write_internal_argument *argument = _argument; - ssize_t result = write(argument->descriptor, argument->base, argument->size); - return rb_fiber_scheduler_io_result(result, errno); + + while (true) { + ssize_t result = write(argument->descriptor, argument->base, argument->size); + + if (result < 0) { + return rb_fiber_scheduler_io_result(result, errno); + } + else if (result == 0) { + return rb_fiber_scheduler_io_result(total, 0); + } + else { + total += result; + + if (total >= argument->length) { + return rb_fiber_scheduler_io_result(total, 0); + } + + argument->base = argument->base + result; + argument->size = argument->size - result; + } + } } VALUE @@ -2573,59 +2772,65 @@ rb_io_buffer_write(VALUE self, VALUE io, size_t length, size_t offset) { VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { - VALUE result = rb_fiber_scheduler_io_write(scheduler, io, self, SIZET2NUM(length), SIZET2NUM(offset)); + VALUE result = rb_fiber_scheduler_io_write(scheduler, io, self, length, offset); if (!UNDEF_P(result)) { return result; } } - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - io_buffer_validate_range(data, offset, length); + io_buffer_validate_range(buffer, offset, length); int descriptor = rb_io_descriptor(io); const void * base; size_t size; - io_buffer_get_bytes_for_reading(data, &base, &size); + io_buffer_get_bytes_for_reading(buffer, &base, &size); - base = (unsigned char *)base + offset; + base = (unsigned char*)base + offset; + size = size - offset; struct io_buffer_write_internal_argument argument = { .descriptor = descriptor, .base = base, - .size = length, + .size = size, + .length = length, }; - return rb_thread_io_blocking_region(io_buffer_write_internal, &argument, descriptor); + return io_buffer_blocking_region(buffer, io_buffer_write_internal, &argument, descriptor); } +/* + * call-seq: write(io, [length, [offset]]) -> written length or -errno + * + * Write at least +length+ bytes from the buffer starting at +offset+, into the +io+. + * If an error occurs, return <tt>-errno</tt>. + * + * If +length+ is not given or +nil+, it defaults to the size of the buffer + * minus the offset, i.e. the entire buffer. + * + * If +length+ is zero, exactly one <tt>write</tt> operation will occur. + * + * If +offset+ is not given, it defaults to zero, i.e. the beginning of the + * buffer. + * + * out = File.open('output.txt', 'wb') + * IO::Buffer.for('1234567').write(out, 3) + * + * This leads to +123+ being written into <tt>output.txt</tt> + */ static VALUE io_buffer_write(int argc, VALUE *argv, VALUE self) { - rb_check_arity(argc, 2, 3); + rb_check_arity(argc, 1, 3); VALUE io = argv[0]; - size_t length; - if (argc >= 2) { - if (rb_int_negative_p(argv[1])) { - rb_raise(rb_eArgError, "Length can't be negative!"); - } - - length = NUM2SIZET(argv[1]); - } - - size_t offset = 0; - if (argc >= 3) { - if (rb_int_negative_p(argv[2])) { - rb_raise(rb_eArgError, "Offset can't be negative!"); - } - - offset = NUM2SIZET(argv[2]); - } + size_t length, offset; + io_buffer_extract_length_offset(self, argc-1, argv+1, &length, &offset); return rb_io_buffer_write(self, io, length, offset); } @@ -2667,56 +2872,66 @@ rb_io_buffer_pwrite(VALUE self, VALUE io, rb_off_t from, size_t length, size_t o { VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { - VALUE result = rb_fiber_scheduler_io_pwrite(scheduler, io, OFFT2NUM(from), self, SIZET2NUM(length), SIZET2NUM(offset)); + VALUE result = rb_fiber_scheduler_io_pwrite(scheduler, io, from, self, length, offset); if (!UNDEF_P(result)) { return result; } } - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - io_buffer_validate_range(data, 0, length); + io_buffer_validate_range(buffer, offset, length); int descriptor = rb_io_descriptor(io); const void * base; size_t size; - io_buffer_get_bytes_for_reading(data, &base, &size); + io_buffer_get_bytes_for_reading(buffer, &base, &size); struct io_buffer_pwrite_internal_argument argument = { .descriptor = descriptor, - .base = base, + + // Move the base pointer to the offset: + .base = (unsigned char *)base + offset, + + // And the size to the length of buffer we want to read: .size = length, + + // And the offset in the file we want to write from: .offset = from, }; - return rb_thread_io_blocking_region(io_buffer_pwrite_internal, &argument, descriptor); + return io_buffer_blocking_region(buffer, io_buffer_pwrite_internal, &argument, descriptor); } +/* + * call-seq: pwrite(io, from, length, [offset]) -> written length or -errno + * + * Writes +length+ bytes from buffer into +io+, starting at + * +offset+ in the buffer. If an error occurs, return <tt>-errno</tt>. + * + * If +offset+ is not given, the bytes are taken from the beginning of the + * buffer. If the +offset+ is given and is beyond the end of the file, the + * gap will be filled with null (0 value) bytes. + * + * out = File.open('output.txt', File::RDWR) # open for read/write, no truncation + * IO::Buffer.for('1234567').pwrite(out, 2, 3, 1) + * + * This leads to +234+ (3 bytes, starting from position 1) being written into + * <tt>output.txt</tt>, starting from file position 2. + */ static VALUE io_buffer_pwrite(int argc, VALUE *argv, VALUE self) { - rb_check_arity(argc, 3, 4); + rb_check_arity(argc, 2, 4); VALUE io = argv[0]; rb_off_t from = NUM2OFFT(argv[1]); - size_t length; - if (rb_int_negative_p(argv[2])) { - rb_raise(rb_eArgError, "Length can't be negative!"); - } - length = NUM2SIZET(argv[2]); - - size_t offset = 0; - if (argc >= 4) { - if (rb_int_negative_p(argv[3])) { - rb_raise(rb_eArgError, "Offset can't be negative!"); - } - - offset = NUM2SIZET(argv[3]); - } + size_t length, offset; + io_buffer_extract_length_offset(self, argc-2, argv+2, &length, &offset); return rb_io_buffer_pwrite(self, io, from, length, offset); } @@ -2751,19 +2966,19 @@ memory_and(unsigned char * restrict output, unsigned char * restrict base, size_ static VALUE io_buffer_and(VALUE self, VALUE mask) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - struct rb_io_buffer *mask_data = NULL; - TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + struct rb_io_buffer *mask_buffer = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); - io_buffer_check_mask(mask_data); + io_buffer_check_mask(mask_buffer); - VALUE output = rb_io_buffer_new(NULL, data->size, io_flags_for_size(data->size)); - struct rb_io_buffer *output_data = NULL; - TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_data); + VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size)); + struct rb_io_buffer *output_buffer = NULL; + TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer); - memory_and(output_data->base, data->base, data->size, mask_data->base, mask_data->size); + memory_and(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size); return output; } @@ -2791,19 +3006,19 @@ memory_or(unsigned char * restrict output, unsigned char * restrict base, size_t static VALUE io_buffer_or(VALUE self, VALUE mask) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - struct rb_io_buffer *mask_data = NULL; - TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + struct rb_io_buffer *mask_buffer = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); - io_buffer_check_mask(mask_data); + io_buffer_check_mask(mask_buffer); - VALUE output = rb_io_buffer_new(NULL, data->size, io_flags_for_size(data->size)); - struct rb_io_buffer *output_data = NULL; - TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_data); + VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size)); + struct rb_io_buffer *output_buffer = NULL; + TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer); - memory_or(output_data->base, data->base, data->size, mask_data->base, mask_data->size); + memory_or(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size); return output; } @@ -2831,19 +3046,19 @@ memory_xor(unsigned char * restrict output, unsigned char * restrict base, size_ static VALUE io_buffer_xor(VALUE self, VALUE mask) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - struct rb_io_buffer *mask_data = NULL; - TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + struct rb_io_buffer *mask_buffer = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); - io_buffer_check_mask(mask_data); + io_buffer_check_mask(mask_buffer); - VALUE output = rb_io_buffer_new(NULL, data->size, io_flags_for_size(data->size)); - struct rb_io_buffer *output_data = NULL; - TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_data); + VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size)); + struct rb_io_buffer *output_buffer = NULL; + TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer); - memory_xor(output_data->base, data->base, data->size, mask_data->base, mask_data->size); + memory_xor(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size); return output; } @@ -2871,14 +3086,14 @@ memory_not(unsigned char * restrict output, unsigned char * restrict base, size_ static VALUE io_buffer_not(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - VALUE output = rb_io_buffer_new(NULL, data->size, io_flags_for_size(data->size)); - struct rb_io_buffer *output_data = NULL; - TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_data); + VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size)); + struct rb_io_buffer *output_buffer = NULL; + TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer); - memory_not(output_data->base, data->base, data->size); + memory_not(output_buffer->base, buffer->base, buffer->size); return output; } @@ -2897,7 +3112,7 @@ static inline void io_buffer_check_overlaps(struct rb_io_buffer *a, struct rb_io_buffer *b) { if (io_buffer_overlaps(a, b)) - rb_raise(rb_eIOBufferMaskError, "Mask overlaps source data!"); + rb_raise(rb_eIOBufferMaskError, "Mask overlaps source buffer!"); } static void @@ -2928,20 +3143,20 @@ memory_and_inplace(unsigned char * restrict base, size_t size, unsigned char * r static VALUE io_buffer_and_inplace(VALUE self, VALUE mask) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - struct rb_io_buffer *mask_data = NULL; - TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + struct rb_io_buffer *mask_buffer = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); - io_buffer_check_mask(mask_data); - io_buffer_check_overlaps(data, mask_data); + io_buffer_check_mask(mask_buffer); + io_buffer_check_overlaps(buffer, mask_buffer); void *base; size_t size; - io_buffer_get_bytes_for_writing(data, &base, &size); + io_buffer_get_bytes_for_writing(buffer, &base, &size); - memory_and_inplace(base, size, mask_data->base, mask_data->size); + memory_and_inplace(base, size, mask_buffer->base, mask_buffer->size); return self; } @@ -2974,20 +3189,20 @@ memory_or_inplace(unsigned char * restrict base, size_t size, unsigned char * re static VALUE io_buffer_or_inplace(VALUE self, VALUE mask) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - struct rb_io_buffer *mask_data = NULL; - TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + struct rb_io_buffer *mask_buffer = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); - io_buffer_check_mask(mask_data); - io_buffer_check_overlaps(data, mask_data); + io_buffer_check_mask(mask_buffer); + io_buffer_check_overlaps(buffer, mask_buffer); void *base; size_t size; - io_buffer_get_bytes_for_writing(data, &base, &size); + io_buffer_get_bytes_for_writing(buffer, &base, &size); - memory_or_inplace(base, size, mask_data->base, mask_data->size); + memory_or_inplace(base, size, mask_buffer->base, mask_buffer->size); return self; } @@ -3020,20 +3235,20 @@ memory_xor_inplace(unsigned char * restrict base, size_t size, unsigned char * r static VALUE io_buffer_xor_inplace(VALUE self, VALUE mask) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - struct rb_io_buffer *mask_data = NULL; - TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + struct rb_io_buffer *mask_buffer = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); - io_buffer_check_mask(mask_data); - io_buffer_check_overlaps(data, mask_data); + io_buffer_check_mask(mask_buffer); + io_buffer_check_overlaps(buffer, mask_buffer); void *base; size_t size; - io_buffer_get_bytes_for_writing(data, &base, &size); + io_buffer_get_bytes_for_writing(buffer, &base, &size); - memory_xor_inplace(base, size, mask_data->base, mask_data->size); + memory_xor_inplace(base, size, mask_buffer->base, mask_buffer->size); return self; } @@ -3066,12 +3281,12 @@ memory_not_inplace(unsigned char * restrict base, size_t size) static VALUE io_buffer_not_inplace(VALUE self) { - struct rb_io_buffer *data = NULL; - TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + struct rb_io_buffer *buffer = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); void *base; size_t size; - io_buffer_get_bytes_for_writing(data, &base, &size); + io_buffer_get_bytes_for_writing(buffer, &base, &size); memory_not_inplace(base, size); @@ -3084,8 +3299,8 @@ io_buffer_not_inplace(VALUE self) * IO::Buffer is a low-level efficient buffer for input/output. There are three * ways of using buffer: * - * * Create an empty buffer with ::new, fill it with data using #copy or - * #set_value, #set_string, get data with #get_string; + * * Create an empty buffer with ::new, fill it with buffer using #copy or + * #set_value, #set_string, get buffer with #get_string; * * Create a buffer mapped to some string with ::for, then it could be used * both for reading with #get_string or #get_value, and writing (writing will * change the source string, too); @@ -3117,7 +3332,7 @@ io_buffer_not_inplace(VALUE self) * * \Buffer from string: * - * string = 'data' + * string = 'buffer' * buffer = IO::Buffer.for(string) * # => * # #<IO::Buffer 0x00007f3f02be9b18+4 SLICE> @@ -3125,7 +3340,7 @@ io_buffer_not_inplace(VALUE self) * buffer * # => * # #<IO::Buffer 0x00007f3f02be9b18+4 SLICE> - * # 0x00000000 64 61 74 61 data + * # 0x00000000 64 61 74 61 buffer * * buffer.get_string(2) # read content starting from offset 2 * # => "ta" @@ -3140,7 +3355,7 @@ io_buffer_not_inplace(VALUE self) * * \Buffer from file: * - * File.write('test.txt', 'test data') + * File.write('test.txt', 'test buffer') * # => 9 * buffer = IO::Buffer.map(File.open('test.txt')) * # => @@ -3157,7 +3372,7 @@ io_buffer_not_inplace(VALUE self) * buffer.set_string('---', 1) * # => 3 -- bytes written * File.read('test.txt') - * # => "t--- data" + * # => "t--- buffer" * * <b>The class is experimental and the interface is subject to change.</b> */ @@ -3281,7 +3496,7 @@ Init_IO_Buffer(void) rb_define_method(rb_cIOBuffer, "get_string", io_buffer_get_string, -1); rb_define_method(rb_cIOBuffer, "set_string", io_buffer_set_string, -1); - // Binary data manipulations: + // Binary buffer manipulations: rb_define_method(rb_cIOBuffer, "&", io_buffer_and, 1); rb_define_method(rb_cIOBuffer, "|", io_buffer_or, 1); rb_define_method(rb_cIOBuffer, "^", io_buffer_xor, 1); @@ -113,7 +113,9 @@ remove_from_constant_cache(ID id, IC ic) st_table *ics = (st_table *)lookup_result; st_delete(ics, &ic_data, NULL); - if (ics->num_entries == 0) { + if (ics->num_entries == 0 && + // See comment in vm_track_constant_cache on why we need this check + id != vm->inserting_constant_cache_id) { rb_id_table_delete(vm->constant_cache, id); st_free_table(ics); } @@ -187,7 +189,11 @@ rb_iseq_free(const rb_iseq_t *iseq) ruby_xfree((void *)body->mark_bits.list); } + ruby_xfree(body->variable.original_iseq); + if (body->param.keyword != NULL) { + if (body->param.keyword->table != &body->local_table[body->param.keyword->bits_start - body->param.keyword->num]) + ruby_xfree((void *)body->param.keyword->table); ruby_xfree((void *)body->param.keyword->default_values); ruby_xfree((void *)body->param.keyword); } @@ -591,6 +597,19 @@ rb_iseq_pathobj_set(const rb_iseq_t *iseq, VALUE path, VALUE realpath) rb_iseq_pathobj_new(path, realpath)); } +// Make a dummy iseq for a dummy frame that exposes a path for profilers to inspect +rb_iseq_t * +rb_iseq_alloc_with_dummy_path(VALUE fname) +{ + rb_iseq_t *dummy_iseq = iseq_alloc(); + + ISEQ_BODY(dummy_iseq)->type = ISEQ_TYPE_TOP; + RB_OBJ_WRITE(dummy_iseq, &ISEQ_BODY(dummy_iseq)->location.pathobj, fname); + RB_OBJ_WRITE(dummy_iseq, &ISEQ_BODY(dummy_iseq)->location.label, fname); + + return dummy_iseq; +} + static rb_iseq_location_t * iseq_location_setup(rb_iseq_t *iseq, VALUE name, VALUE path, VALUE realpath, int first_lineno, const rb_code_location_t *code_location, const int node_id) { @@ -917,13 +936,20 @@ iseq_setup_coverage(VALUE coverages, VALUE path, const rb_ast_body_t *ast, int l return Qnil; } -rb_iseq_t * -rb_iseq_new_top(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath, const rb_iseq_t *parent) +static inline void +iseq_new_setup_coverage(VALUE path, const rb_ast_body_t *ast, int line_offset) { VALUE coverages = rb_get_coverages(); + if (RTEST(coverages)) { iseq_setup_coverage(coverages, path, ast, 0); } +} + +rb_iseq_t * +rb_iseq_new_top(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath, const rb_iseq_t *parent) +{ + iseq_new_setup_coverage(path, ast, 0); return rb_iseq_new_with_opt(ast, name, path, realpath, 0, parent, 0, ISEQ_TYPE_TOP, &COMPILE_OPTION_DEFAULT); @@ -932,6 +958,8 @@ rb_iseq_new_top(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath rb_iseq_t * rb_iseq_new_main(const rb_ast_body_t *ast, VALUE path, VALUE realpath, const rb_iseq_t *parent, int opt) { + iseq_new_setup_coverage(path, ast, 0); + return rb_iseq_new_with_opt(ast, rb_fstring_lit("<main>"), path, realpath, 0, parent, 0, ISEQ_TYPE_MAIN, opt ? &COMPILE_OPTION_DEFAULT : &COMPILE_OPTION_FALSE); @@ -1559,7 +1587,11 @@ rb_iseqw_to_iseq(VALUE iseqw) static VALUE iseqw_eval(VALUE self) { - return rb_iseq_eval(iseqw_check(self)); + const rb_iseq_t *iseq = iseqw_check(self); + if (0 == ISEQ_BODY(iseq)->iseq_size) { + rb_raise(rb_eTypeError, "attempt to evaluate dummy InstructionSequence"); + } + return rb_iseq_eval(iseq); } /* @@ -3358,7 +3390,7 @@ rb_vm_encoded_insn_data_table_init(void) const void * const *table = rb_vm_get_insns_address_table(); #define INSN_CODE(insn) ((VALUE)table[insn]) #else -#define INSN_CODE(insn) (insn) +#define INSN_CODE(insn) ((VALUE)(insn)) #endif st_data_t insn; encoded_insn_data = st_init_numtable_with_size(VM_INSTRUCTION_SIZE / 2); @@ -114,6 +114,7 @@ struct iseq_compile_data { struct iseq_compile_data_storage *storage_current; } insn; bool in_rescue; + bool in_masgn; int loopval_popped; /* used by NODE_BREAK */ int last_line; int label_no; diff --git a/lib/English.gemspec b/lib/English.gemspec index 274fb047b8..a08542bcda 100644 --- a/lib/English.gemspec +++ b/lib/English.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "english" - spec.version = "0.7.1" + spec.version = "0.7.2" spec.authors = ["Yukihiro Matsumoto"] spec.email = ["matz@ruby-lang.org"] diff --git a/lib/abbrev.gemspec b/lib/abbrev.gemspec index 72837ed2ab..c28b960c8c 100644 --- a/lib/abbrev.gemspec +++ b/lib/abbrev.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "abbrev" - spec.version = "0.1.0" + spec.version = "0.1.1" spec.authors = ["Akinori MUSHA"] spec.email = ["knu@idaemons.org"] diff --git a/lib/bundler.rb b/lib/bundler.rb index b20179934a..f83268e9cd 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -75,10 +75,12 @@ module Bundler autoload :StubSpecification, File.expand_path("bundler/stub_specification", __dir__) autoload :UI, File.expand_path("bundler/ui", __dir__) autoload :URICredentialsFilter, File.expand_path("bundler/uri_credentials_filter", __dir__) + autoload :URINormalizer, File.expand_path("bundler/uri_normalizer", __dir__) + autoload :SafeMarshal, File.expand_path("bundler/safe_marshal", __dir__) class << self def configure - @configured ||= configure_gem_home_and_path + @configure ||= configure_gem_home_and_path end def ui @@ -208,9 +210,10 @@ module Bundler end def frozen_bundle? - frozen = settings[:deployment] - frozen ||= settings[:frozen] - frozen + frozen = settings[:frozen] + return frozen unless frozen.nil? + + settings[:deployment] end def locked_gems @@ -453,7 +456,7 @@ EOF end def local_platform - return Gem::Platform::RUBY if settings[:force_ruby_platform] || Gem.platforms == [Gem::Platform::RUBY] + return Gem::Platform::RUBY if settings[:force_ruby_platform] Gem::Platform.local end @@ -496,7 +499,7 @@ EOF if File.file?(executable) && File.executable?(executable) executable elsif paths = ENV["PATH"] - quote = '"'.freeze + quote = '"' paths.split(File::PATH_SEPARATOR).find do |path| path = path[1..-2] if path.start_with?(quote) && path.end_with?(quote) executable_path = File.expand_path(executable, path) @@ -511,10 +514,8 @@ EOF end end - def load_marshal(data) - Marshal.load(data) - rescue TypeError => e - raise MarshalError, "#{e.class}: #{e.message}" + def safe_load_marshal(data) + load_marshal(data, :marshal_proc => SafeMarshal.proc) end def load_gemspec(file, validate = false) @@ -523,7 +524,7 @@ EOF @gemspec_cache[key] ||= load_gemspec_uncached(file, validate) # Protect against caching side-effected gemspecs by returning a # new instance each time. - @gemspec_cache[key].dup if @gemspec_cache[key] + @gemspec_cache[key]&.dup end def load_gemspec_uncached(file, validate = false) @@ -550,7 +551,7 @@ EOF def git_present? return @git_present if defined?(@git_present) - @git_present = Bundler.which("git") || Bundler.which("git.exe") + @git_present = Bundler.which("git#{RbConfig::CONFIG["EXEEXT"]}") end def feature_flag @@ -572,7 +573,7 @@ EOF @bin_path = nil @bundler_major_version = nil @bundle_path = nil - @configured = nil + @configure = nil @configured_bundle_path = nil @definition = nil @load = nil @@ -605,6 +606,12 @@ EOF private + def load_marshal(data, marshal_proc: nil) + Marshal.load(data, marshal_proc) + rescue TypeError => e + raise MarshalError, "#{e.class}: #{e.message}" + end + def eval_yaml_gemspec(path, contents) Kernel.require "psych" diff --git a/lib/bundler/bundler.gemspec b/lib/bundler/bundler.gemspec index a9c9fac462..da50b46225 100644 --- a/lib/bundler/bundler.gemspec +++ b/lib/bundler/bundler.gemspec @@ -29,8 +29,8 @@ Gem::Specification.new do |s| "source_code_uri" => "https://github.com/rubygems/rubygems/tree/master/bundler", } - s.required_ruby_version = ">= 2.3.0" - s.required_rubygems_version = ">= 2.5.2" + s.required_ruby_version = ">= 2.6.0" + s.required_rubygems_version = ">= 3.0.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 c5edfadd37..a3eb494db2 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -10,6 +10,7 @@ module Bundler AUTO_INSTALL_CMDS = %w[show binstubs outdated exec open console licenses clean].freeze PARSEABLE_COMMANDS = %w[check config help exec platform show version].freeze + EXTENSIONS = ["c", "rust"].freeze COMMAND_ALIASES = { "check" => "c", @@ -22,6 +23,8 @@ module Bundler }.freeze def self.start(*) + check_deprecated_ext_option(ARGV) if ARGV.include?("--ext") + super ensure Bundler::SharedHelpers.print_major_deprecations! @@ -153,6 +156,7 @@ module Bundler dependency listed in the gemspec file to the newly created Gemfile. D method_option "gemspec", :type => :string, :banner => "Use the specified .gemspec to create the Gemfile" + method_option "gemfile", :type => :string, :banner => "Use the specified name for the gemfile instead of 'Gemfile'" def init require_relative "cli/init" Init.new(options.dup).run @@ -292,6 +296,8 @@ module Bundler "Prefer updating only to next minor version" method_option "major", :type => :boolean, :banner => "Prefer updating to next major version (default)" + method_option "pre", :type => :boolean, :banner => + "Always choose the highest allowed version when updating gems, regardless of prerelease status" method_option "strict", :type => :boolean, :banner => "Do not allow any gem to be updated past latest --patch | --minor | --major" method_option "conservative", :type => :boolean, :banner => @@ -504,6 +510,7 @@ module Bundler subcommand "config", Config desc "open GEM", "Opens the source directory of the given bundled gem" + method_option "path", :type => :string, :lazy_default => "", :banner => "Open relative path of the gem source." def open(name) require_relative "cli/open" Open.new(options, name).run @@ -574,7 +581,7 @@ module Bundler method_option :edit, :type => :string, :aliases => "-e", :required => false, :banner => "EDITOR", :lazy_default => [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? }, :desc => "Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)" - method_option :ext, :type => :boolean, :default => false, :desc => "Generate the boilerplate for C extension code" + method_option :ext, :type => :string, :desc => "Generate the boilerplate for C extension code.", :enum => EXTENSIONS method_option :git, :type => :boolean, :default => true, :desc => "Initialize a git repo inside your library." method_option :mit, :type => :boolean, :desc => "Generate an MIT license file. Set a default with `bundle config set --global gem.mit true`." method_option :rubocop, :type => :boolean, :desc => "Add rubocop to the generated Rakefile and gemspec. Set a default with `bundle config set --global gem.rubocop true`." @@ -582,7 +589,7 @@ module Bundler method_option :test, :type => :string, :lazy_default => Bundler.settings["gem.test"] || "", :aliases => "-t", :banner => "Use the specified test framework for your library", :desc => "Generate a test directory for your library, either rspec, minitest or test-unit. Set a default with `bundle config set --global gem.test (rspec|minitest|test-unit)`." method_option :ci, :type => :string, :lazy_default => Bundler.settings["gem.ci"] || "", - :desc => "Generate CI configuration, either GitHub Actions, Travis CI, GitLab CI or CircleCI. Set a default with `bundle config set --global gem.ci (github|travis|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"] || "", :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 <your_username>`." @@ -668,10 +675,14 @@ module Bundler "If updating, prefer updating only to next minor version" method_option "major", :type => :boolean, :banner => "If updating, prefer updating to next major version (default)" + method_option "pre", :type => :boolean, :banner => + "If updating, always choose the highest allowed version, regardless of prerelease status" method_option "strict", :type => :boolean, :banner => "If updating, do not allow any gem to be updated past latest --patch | --minor | --major" method_option "conservative", :type => :boolean, :banner => "If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated" + method_option "bundler", :type => :string, :lazy_default => "> 0.a", :banner => + "Update the locked version of bundler" def lock require_relative "cli/lock" Lock.new(options).run @@ -749,6 +760,38 @@ module Bundler end end + def self.check_deprecated_ext_option(arguments) + # when deprecated version of `--ext` is called + # print out deprecation warning and pretend `--ext=c` was provided + if deprecated_ext_value?(arguments) + SharedHelpers.major_deprecation 2, "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been deprecated. Please select a language, e.g. `--ext=rust` to generate a Rust extension. This gem will now be generated as if `--ext=c` was used." + arguments[arguments.index("--ext")] = "--ext=c" + end + end + + def self.deprecated_ext_value?(arguments) + index = arguments.index("--ext") + next_argument = arguments[index+1] + + # it is ok when --ext is followed with valid extension value + # for example `bundle gem hello --ext c` + return false if EXTENSIONS.include?(next_argument) + + # deprecated call when --ext is called with no value in last position + # for example `bundle gem hello_gem --ext` + return true if next_argument.nil? + + # deprecated call when --ext is followed by other parameter + # for example `bundle gem --ext --no-ci hello_gem` + return true if next_argument.start_with?("-") + + # deprecated call when --ext is followed by gem name + # for example `bundle gem --ext hello_gem` + return true if next_argument + + false + end + private # Automatically invoke `bundle install` and resume if diff --git a/lib/bundler/cli/add.rb b/lib/bundler/cli/add.rb index 5bcf30d82d..08fa6547fb 100644 --- a/lib/bundler/cli/add.rb +++ b/lib/bundler/cli/add.rb @@ -40,7 +40,7 @@ module Bundler raise InvalidOption, "Please specify gems to add." if gems.empty? version.to_a.each do |v| - raise InvalidOption, "Invalid gem requirement pattern '#{v}'" unless Gem::Requirement::PATTERN =~ v.to_s + raise InvalidOption, "Invalid gem requirement pattern '#{v}'" unless Gem::Requirement::PATTERN.match?(v.to_s) end end end diff --git a/lib/bundler/cli/binstubs.rb b/lib/bundler/cli/binstubs.rb index 639c01ff39..fc2fad47a5 100644 --- a/lib/bundler/cli/binstubs.rb +++ b/lib/bundler/cli/binstubs.rb @@ -11,7 +11,7 @@ module Bundler def run Bundler.definition.validate_runtime! path_option = options["path"] - path_option = nil if path_option && path_option.empty? + path_option = nil if path_option&.empty? Bundler.settings.set_command_option :bin, path_option if options["path"] Bundler.settings.set_command_option_if_given :shebang, options["shebang"] installer = Installer.new(Bundler.root, Bundler.definition) @@ -40,7 +40,11 @@ module Bundler end if options[:standalone] - next Bundler.ui.warn("Sorry, Bundler can only be run via RubyGems.") if gem_name == "bundler" + if gem_name == "bundler" + Bundler.ui.warn("Sorry, Bundler can only be run via RubyGems.") unless options[:all] + next + end + Bundler.settings.temporary(:path => (Bundler.settings[:path] || Bundler.root)) do installer.generate_standalone_bundler_executable_stubs(spec, installer_opts) end diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb index 0d83a1c07e..d654406f65 100644 --- a/lib/bundler/cli/common.rb +++ b/lib/bundler/cli/common.rb @@ -111,6 +111,7 @@ module Bundler definition.gem_version_promoter.tap do |gvp| gvp.level = patch_level.first || :major gvp.strict = options[:strict] || options["filter-strict"] + gvp.pre = options[:pre] end end diff --git a/lib/bundler/cli/doctor.rb b/lib/bundler/cli/doctor.rb index 74444ad0ce..e299a5a8c2 100644 --- a/lib/bundler/cli/doctor.rb +++ b/lib/bundler/cli/doctor.rb @@ -73,12 +73,10 @@ module Bundler definition.specs.each do |spec| bundles_for_gem(spec).each do |bundle| bad_paths = dylibs(bundle).select do |f| - begin - Fiddle.dlopen(f) - false - rescue Fiddle::DLError - true - end + Fiddle.dlopen(f) + false + rescue Fiddle::DLError + true end if bad_paths.any? broken_links[spec] ||= [] diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 8c8ebe8ff3..7f1200f4a0 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -15,7 +15,7 @@ module Bundler "test-unit" => "3.0", }.freeze - attr_reader :options, :gem_name, :thor, :name, :target + attr_reader :options, :gem_name, :thor, :name, :target, :extension def initialize(options, gem_name, thor) @options = options @@ -28,7 +28,11 @@ module Bundler @name = @gem_name @target = SharedHelpers.pwd.join(gem_name) - validate_ext_name if options[:ext] + @extension = options[:ext] + + validate_ext_name if @extension + validate_rust_builder_rubygems_version if @extension == "rust" + travis_removal_info end def run @@ -64,12 +68,13 @@ module Bundler :author => git_author_name.empty? ? "TODO: Write your name" : git_author_name, :email => git_user_email.empty? ? "TODO: Write your email address" : git_user_email, :test => options[:test], - :ext => options[:ext], + :ext => extension, :exe => options[:exe], :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, } ensure_safe_gem_name(name, constant_array) @@ -132,8 +137,6 @@ module Bundler case config[:ci] when "github" templates.merge!("github/workflows/main.yml.tt" => ".github/workflows/main.yml") - when "travis" - templates.merge!("travis.yml.tt" => ".travis.yml") when "gitlab" templates.merge!("gitlab-ci.yml.tt" => ".gitlab-ci.yml") when "circle" @@ -188,14 +191,23 @@ module Bundler templates.merge!("exe/newgem.tt" => "exe/#{name}") if config[:exe] - if options[:ext] + if extension == "c" templates.merge!( - "ext/newgem/extconf.rb.tt" => "ext/#{name}/extconf.rb", + "ext/newgem/extconf-c.rb.tt" => "ext/#{name}/extconf.rb", "ext/newgem/newgem.h.tt" => "ext/#{name}/#{underscored_name}.h", "ext/newgem/newgem.c.tt" => "ext/#{name}/#{underscored_name}.c" ) end + if extension == "rust" + templates.merge!( + "Cargo.toml.tt" => "Cargo.toml", + "ext/newgem/Cargo.toml.tt" => "ext/#{name}/Cargo.toml", + "ext/newgem/extconf-rust.rb.tt" => "ext/#{name}/extconf.rb", + "ext/newgem/src/lib.rs.tt" => "ext/#{name}/src/lib.rs", + ) + end + if target.exist? && !target.directory? Bundler.ui.error "Couldn't create a new gem named `#{gem_name}` because there's an existing file named `#{gem_name}`." exit Bundler::BundlerError.all_errors[Bundler::GenericSystemCallError] @@ -270,7 +282,7 @@ module Bundler Bundler.ui.info hint_text("test") result = Bundler.ui.ask "Enter a test framework. rspec/minitest/test-unit/(none):" - if result =~ /rspec|minitest|test-unit/ + if /rspec|minitest|test-unit/.match?(result) test_framework = result else test_framework = false @@ -306,12 +318,11 @@ module Bundler "* CircleCI: https://circleci.com/\n" \ "* GitHub Actions: https://github.com/features/actions\n" \ "* GitLab CI: https://docs.gitlab.com/ee/ci/\n" \ - "* Travis CI: https://travis-ci.org/\n" \ "\n" Bundler.ui.info hint_text("ci") - result = Bundler.ui.ask "Enter a CI service. github/travis/gitlab/circle/(none):" - if result =~ /github|travis|gitlab|circle/ + result = Bundler.ui.ask "Enter a CI service. github/gitlab/circle/(none):" + if /github|gitlab|circle/.match?(result) ci_template = result else ci_template = false @@ -342,7 +353,7 @@ module Bundler Bundler.ui.info hint_text("linter") result = Bundler.ui.ask "Enter a linter. rubocop/standard/(none):" - if result =~ /rubocop|standard/ + if /rubocop|standard/.match?(result) linter_template = result else linter_template = false @@ -389,7 +400,7 @@ module Bundler end def ensure_safe_gem_name(name, constant_array) - if name =~ /^\d/ + if /^\d/.match?(name) Bundler.ui.error "Invalid gem name #{name} Please give a name which does not start with numbers." exit 1 end @@ -415,28 +426,39 @@ module Bundler thor.run(%(#{editor} "#{file}")) end + def rust_builder_required_rubygems_version + "3.3.11" + end + def required_ruby_version - if Gem.ruby_version < Gem::Version.new("2.4.a") then "2.3.0" - elsif Gem.ruby_version < Gem::Version.new("2.5.a") then "2.4.0" - elsif Gem.ruby_version < Gem::Version.new("2.6.a") then "2.5.0" - else - "2.6.0" - end + "2.6.0" end def rubocop_version - if Gem.ruby_version < Gem::Version.new("2.4.a") then "0.81.0" - elsif Gem.ruby_version < Gem::Version.new("2.5.a") then "1.12" - else - "1.21" - end + "1.21" end def standard_version - if Gem.ruby_version < Gem::Version.new("2.4.a") then "0.2.5" - elsif Gem.ruby_version < Gem::Version.new("2.5.a") then "1.0" - else - "1.3" + "1.3" + end + + # TODO: remove at next minor release + def travis_removal_info + if options[:ci] == "travis" + Bundler.ui.error "Support for Travis CI was removed from gem skeleton generator." + exit 1 + end + + if Bundler.settings["gem.ci"] == "travis" + Bundler.ui.error "Support for Travis CI was removed from gem skeleton generator, but it is present in bundle config. Please configure another provider using `bundle config set gem.ci SERVICE` (where SERVICE is one of github/gitlab/circle) or unset configuration using `bundle config unset gem.ci`." + exit 1 + end + 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 end end end diff --git a/lib/bundler/cli/info.rb b/lib/bundler/cli/info.rb index 0545ce8c75..36c7a58f12 100644 --- a/lib/bundler/cli/info.rb +++ b/lib/bundler/cli/info.rb @@ -33,7 +33,7 @@ module Bundler def default_gem_spec(gem_name) return unless Gem::Specification.respond_to?(:find_all_by_name) gem_spec = Gem::Specification.find_all_by_name(gem_name).last - return gem_spec if gem_spec && gem_spec.respond_to?(:default_gem?) && gem_spec.default_gem? + return gem_spec if gem_spec&.default_gem? end def spec_not_found(gem_name) diff --git a/lib/bundler/cli/init.rb b/lib/bundler/cli/init.rb index bc96507c29..246b9d6460 100644 --- a/lib/bundler/cli/init.rb +++ b/lib/bundler/cli/init.rb @@ -32,7 +32,7 @@ module Bundler file << spec.to_gemfile end else - File.open(File.expand_path("../templates/#{gemfile}", __dir__), "r") do |template| + File.open(File.expand_path("../templates/Gemfile", __dir__), "r") do |template| File.open(gemfile, "wb") do |destination| IO.copy_stream(template, destination) end @@ -45,7 +45,7 @@ module Bundler private def gemfile - @gemfile ||= Bundler.preferred_gemfile_name + @gemfile ||= options[:gemfile] || Bundler.preferred_gemfile_name end end end diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb index 1765621cb3..c71bcf159f 100644 --- a/lib/bundler/cli/install.rb +++ b/lib/bundler/cli/install.rb @@ -154,7 +154,7 @@ module Bundler end bin_option = options["binstubs"] - bin_option = nil if bin_option && bin_option.empty? + bin_option = nil if bin_option&.empty? Bundler.settings.set_command_option :bin, bin_option if options["binstubs"] Bundler.settings.set_command_option_if_given :shebang, options["shebang"] diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb index cbd3f5a6fe..cb3ed27138 100644 --- a/lib/bundler/cli/lock.rb +++ b/lib/bundler/cli/lock.rb @@ -22,12 +22,15 @@ module Bundler update = options[:update] conservative = options[:conservative] + bundler = options[:bundler] if update.is_a?(Array) # unlocking specific gems Bundler::CLI::Common.ensure_all_gems_in_lockfile!(update) update = { :gems => update, :conservative => conservative } - elsif update - update = { :conservative => conservative } if conservative + elsif update && conservative + update = { :conservative => conservative } + elsif update && bundler + update = { :bundler => bundler } end definition = Bundler.definition(update) diff --git a/lib/bundler/cli/open.rb b/lib/bundler/cli/open.rb index ea504344f3..8522ec92d6 100644 --- a/lib/bundler/cli/open.rb +++ b/lib/bundler/cli/open.rb @@ -2,23 +2,25 @@ module Bundler class CLI::Open - attr_reader :options, :name + attr_reader :options, :name, :path def initialize(options, name) @options = options @name = name + @path = options[:path] unless options[:path].nil? end def run + raise InvalidOption, "Cannot specify `--path` option without a value" if !@path.nil? && @path.empty? editor = [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? } return Bundler.ui.info("To open a bundled gem, set $EDITOR or $BUNDLER_EDITOR") unless editor return unless spec = Bundler::CLI::Common.select_spec(name, :regex_match) if spec.default_gem? Bundler.ui.info "Unable to open #{name} because it's a default gem, so the directory it would normally be installed to does not exist." else - path = spec.full_gem_path - Dir.chdir(path) do + root_path = spec.full_gem_path + Dir.chdir(root_path) do require "shellwords" - command = Shellwords.split(editor) + [path] + command = Shellwords.split(editor) << File.join([root_path, path].compact) Bundler.with_original_env do system(*command) end || Bundler.ui.info("Could not run '#{command.join(" ")}'") diff --git a/lib/bundler/cli/outdated.rb b/lib/bundler/cli/outdated.rb index e9f93fec39..68c701aefb 100644 --- a/lib/bundler/cli/outdated.rb +++ b/lib/bundler/cli/outdated.rb @@ -111,9 +111,7 @@ module Bundler end.compact if options[:parseable] - relevant_outdated_gems.each do |gems| - print_gems(gems) - end + print_gems(relevant_outdated_gems) else print_gems_table(relevant_outdated_gems) end @@ -196,7 +194,7 @@ module Bundler end current_version = "#{current_spec.version}#{current_spec.git_version}" - if dependency && dependency.specific? + if dependency&.specific? dependency_version = %(, requested #{dependency.requirement}) end diff --git a/lib/bundler/cli/platform.rb b/lib/bundler/cli/platform.rb index 73da8cf80e..32d68abbb1 100644 --- a/lib/bundler/cli/platform.rb +++ b/lib/bundler/cli/platform.rb @@ -8,12 +8,12 @@ module Bundler end def run - platforms, ruby_version = Bundler.ui.silence do - locked_ruby_version = Bundler.locked_gems && Bundler.locked_gems.ruby_version&.gsub(/p\d+\Z/, "") - gemfile_ruby_version = Bundler.definition.ruby_version && Bundler.definition.ruby_version.single_version_string - [Bundler.definition.platforms.map {|p| "* #{p}" }, - locked_ruby_version || gemfile_ruby_version] + ruby_version = if Bundler.locked_gems + Bundler.locked_gems.ruby_version&.gsub(/p\d+\Z/, "") + else + Bundler.definition.ruby_version&.single_version_string end + output = [] if options[:ruby] @@ -23,6 +23,8 @@ module Bundler output << "No ruby version specified" end else + platforms = Bundler.definition.platforms.map {|p| "* #{p}" } + output << "Your platform is: #{Gem::Platform.local}" output << "Your app has gems that work on these platforms:\n#{platforms.join("\n")}" diff --git a/lib/bundler/compact_index_client/cache.rb b/lib/bundler/compact_index_client/cache.rb index 2d83777139..0b43581c11 100644 --- a/lib/bundler/compact_index_client/cache.rb +++ b/lib/bundler/compact_index_client/cache.rb @@ -68,7 +68,7 @@ module Bundler def info_path(name) name = name.to_s - if name =~ /[^a-z0-9_-]/ + if /[^a-z0-9_-]/.match?(name) name += "-#{SharedHelpers.digest(:MD5).hexdigest(name).downcase}" info_roots.last.join(name) else diff --git a/lib/bundler/compact_index_client/updater.rb b/lib/bundler/compact_index_client/updater.rb index 5b430dfbe2..0f7bf9bb50 100644 --- a/lib/bundler/compact_index_client/updater.rb +++ b/lib/bundler/compact_index_client/updater.rb @@ -20,63 +20,64 @@ module Bundler def initialize(fetcher) @fetcher = fetcher - require_relative "../vendored_tmpdir" end def update(local_path, remote_path, retrying = nil) headers = {} - Bundler::Dir.mktmpdir("bundler-compact-index-") do |local_temp_dir| - local_temp_path = Pathname.new(local_temp_dir).join(local_path.basename) - - # first try to fetch any new bytes on the existing file - if retrying.nil? && local_path.file? - copy_file local_path, local_temp_path - - headers["If-None-Match"] = etag_for(local_temp_path) - headers["Range"] = - if local_temp_path.size.nonzero? - # Subtract a byte to ensure the range won't be empty. - # Avoids 416 (Range Not Satisfiable) responses. - "bytes=#{local_temp_path.size - 1}-" - else - "bytes=#{local_temp_path.size}-" - end - end + local_temp_path = local_path.sub(/$/, ".#{$$}") + local_temp_path = local_temp_path.sub(/$/, ".retrying") if retrying + local_temp_path = local_temp_path.sub(/$/, ".tmp") - response = @fetcher.call(remote_path, headers) - return nil if response.is_a?(Net::HTTPNotModified) + # first try to fetch any new bytes on the existing file + if retrying.nil? && local_path.file? + copy_file local_path, local_temp_path - content = response.body + headers["If-None-Match"] = etag_for(local_temp_path) + headers["Range"] = + if local_temp_path.size.nonzero? + # Subtract a byte to ensure the range won't be empty. + # Avoids 416 (Range Not Satisfiable) responses. + "bytes=#{local_temp_path.size - 1}-" + else + "bytes=#{local_temp_path.size}-" + end + end - etag = (response["ETag"] || "").gsub(%r{\AW/}, "") - correct_response = SharedHelpers.filesystem_access(local_temp_path) do - if response.is_a?(Net::HTTPPartialContent) && local_temp_path.size.nonzero? - local_temp_path.open("a") {|f| f << slice_body(content, 1..-1) } + response = @fetcher.call(remote_path, headers) + return nil if response.is_a?(Net::HTTPNotModified) - etag_for(local_temp_path) == etag - else - local_temp_path.open("wb") {|f| f << content } + content = response.body - etag.length.zero? || etag_for(local_temp_path) == etag - end - end + etag = (response["ETag"] || "").gsub(%r{\AW/}, "") + correct_response = SharedHelpers.filesystem_access(local_temp_path) do + if response.is_a?(Net::HTTPPartialContent) && local_temp_path.size.nonzero? + local_temp_path.open("a") {|f| f << slice_body(content, 1..-1) } - if correct_response - SharedHelpers.filesystem_access(local_path) do - FileUtils.mv(local_temp_path, local_path) - end - return nil + etag_for(local_temp_path) == etag + else + local_temp_path.open("wb") {|f| f << content } + + etag.length.zero? || etag_for(local_temp_path) == etag end + end - if retrying - raise MisMatchedChecksumError.new(remote_path, etag, etag_for(local_temp_path)) + if correct_response + SharedHelpers.filesystem_access(local_path) do + FileUtils.mv(local_temp_path, local_path) end + return nil + end - update(local_path, remote_path, :retrying) + if retrying + raise MisMatchedChecksumError.new(remote_path, etag, etag_for(local_temp_path)) end + + update(local_path, remote_path, :retrying) rescue Zlib::GzipFile::Error raise Bundler::HTTPError + ensure + FileUtils.remove_file(local_temp_path) if File.exist?(local_temp_path) end def etag_for(path) diff --git a/lib/bundler/current_ruby.rb b/lib/bundler/current_ruby.rb index f9987c4da8..f009b07ad7 100644 --- a/lib/bundler/current_ruby.rb +++ b/lib/bundler/current_ruby.rb @@ -22,6 +22,8 @@ module Bundler 2.7 3.0 3.1 + 3.2 + 3.3 ].freeze KNOWN_MAJOR_VERSIONS = KNOWN_MINOR_VERSIONS.map {|v| v.split(".", 2).first }.uniq.freeze diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index fe807605e0..564530a98c 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -16,7 +16,6 @@ module Bundler :locked_deps, :locked_gems, :platforms, - :requires, :ruby_version, :lockfile, :gemfiles @@ -77,9 +76,13 @@ module Bundler @lockfile = lockfile @lockfile_contents = String.new + @locked_bundler_version = nil - @locked_ruby_version = nil + @resolved_bundler_version = nil + + @locked_ruby_version = nil @new_platform = nil + @removed_platform = nil if lockfile && File.exist?(lockfile) @lockfile_contents = Bundler.read_file(lockfile) @@ -130,7 +133,7 @@ module Bundler end @unlocking ||= @unlock[:ruby] ||= (!@locked_ruby_version ^ !@ruby_version) - add_current_platform unless current_ruby_platform_locked? || Bundler.frozen_bundle? + add_current_platform unless Bundler.frozen_bundle? converge_path_sources_to_gemspec_sources @path_changes = converge_paths @@ -146,7 +149,7 @@ module Bundler @dependency_changes = converge_dependencies @local_changes = converge_locals - @requires = compute_requires + @missing_lockfile_dep = check_missing_lockfile_dep end def gem_version_promoter @@ -159,13 +162,6 @@ module Bundler resolve end - def resolve_prefering_local! - @prefer_local = true - @remote = true - sources.remote! - resolve - end - def resolve_with_cache! sources.cached! resolve @@ -177,6 +173,23 @@ module Bundler resolve end + def resolution_mode=(options) + if options["local"] + @remote = false + else + @remote = true + @prefer_local = options["prefer-local"] + end + end + + def setup_sources_for_resolve + if @remote == false + sources.cached! + else + sources.remote! + end + end + # For given dependency list returns a SpecSet with Gemspec of all the required # dependencies. # 1. The method first resolves the dependencies specified in Gemfile @@ -207,6 +220,7 @@ module Bundler rescue BundlerError => e @resolve = nil @resolver = nil + @resolution_packages = nil @specs = nil @gem_version_promoter = nil @@ -223,6 +237,14 @@ module Bundler end def current_dependencies + filter_relevant(dependencies) + end + + def current_locked_dependencies + filter_relevant(locked_dependencies) + end + + def filter_relevant(dependencies) dependencies.select do |d| d.should_include? && !d.gem_platforms([generic_local_platform]).empty? end @@ -262,20 +284,20 @@ module Bundler @resolve ||= if Bundler.frozen_bundle? Bundler.ui.debug "Frozen, using resolution from the lockfile" @locked_specs - elsif !unlocking? && nothing_changed? + elsif no_resolve_needed? if deleted_deps.any? - Bundler.ui.debug("Some dependencies were deleted, using a subset of the resolution from the lockfile") + Bundler.ui.debug "Some dependencies were deleted, using a subset of the resolution from the lockfile" SpecSet.new(filter_specs(@locked_specs, @dependencies - deleted_deps)) else - Bundler.ui.debug("Found no changes, using resolution from the lockfile") - if @locked_gems.may_include_redundant_platform_specific_gems? + Bundler.ui.debug "Found no changes, using resolution from the lockfile" + if @removed_platform || @locked_gems.may_include_redundant_platform_specific_gems? SpecSet.new(filter_specs(@locked_specs, @dependencies)) else @locked_specs end end else - Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}") + Bundler.ui.debug "Found changes from the lockfile, re-resolving dependencies because #{change_reason}" start_resolution end end @@ -295,11 +317,11 @@ module Bundler # Convert to \r\n if the existing lock has them # i.e., Windows with `git config core.autocrlf=true` - contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match("\r\n") + contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match?("\r\n") if @locked_bundler_version locked_major = @locked_bundler_version.segments.first - current_major = Bundler.gem_version.segments.first + current_major = bundler_version_to_lock.segments.first updating_major = locked_major < current_major end @@ -339,27 +361,16 @@ module Bundler end end + def bundler_version_to_lock + @resolved_bundler_version || Bundler.gem_version + end + def to_lock require_relative "lockfile_generator" LockfileGenerator.generate(self) end def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false) - msg = String.new - msg << "You are trying to install in deployment mode after changing\n" \ - "your Gemfile. Run `bundle install` elsewhere and add the\n" \ - "updated #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} to version control." - - unless explicit_flag - suggested_command = if Bundler.settings.locations("frozen").keys.&([:global, :local]).any? - "bundle config unset frozen" - elsif Bundler.settings.locations("deployment").keys.&([:global, :local]).any? - "bundle config unset deployment" - end - msg << "\n\nIf this is a development machine, remove the #{Bundler.default_gemfile} " \ - "freeze \nby running `#{suggested_command}`." if suggested_command - end - added = [] deleted = [] changed = [] @@ -373,32 +384,36 @@ module Bundler deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" } if deleted_deps.any? both_sources = Hash.new {|h, k| h[k] = [] } - @dependencies.each {|d| both_sources[d.name][0] = d } - - locked_dependencies.each do |d| - next if !Bundler.feature_flag.bundler_3_mode? && @locked_specs[d.name].empty? - - both_sources[d.name][1] = d - end + current_dependencies.each {|d| both_sources[d.name][0] = d } + current_locked_dependencies.each {|d| both_sources[d.name][1] = d } both_sources.each do |name, (dep, lock_dep)| next if dep.nil? || lock_dep.nil? - gemfile_source = dep.source || sources.default_source - lock_source = lock_dep.source || sources.default_source + gemfile_source = dep.source || default_source + lock_source = lock_dep.source || default_source next if lock_source.include?(gemfile_source) - gemfile_source_name = dep.source ? gemfile_source.identifier : "no specified source" - lockfile_source_name = lock_dep.source ? lock_source.identifier : "no specified source" + gemfile_source_name = dep.source ? gemfile_source.to_gemfile : "no specified source" + lockfile_source_name = lock_dep.source ? lock_source.to_gemfile : "no specified source" changed << "* #{name} from `#{lockfile_source_name}` to `#{gemfile_source_name}`" end reason = change_reason - msg << "\n\n#{reason.split(", ").map(&:capitalize).join("\n")}" unless reason.strip.empty? + msg = String.new + msg << "#{reason.capitalize.strip}, but the lockfile can't be updated because frozen mode is set" msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any? msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any? msg << "\n\nYou have changed in the Gemfile:\n" << changed.join("\n") if changed.any? - msg << "\n" + msg << "\n\nRun `bundle install` elsewhere and add the updated #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} to version control.\n" + + unless explicit_flag + suggested_command = unless Bundler.settings.locations("frozen").keys.include?(:env) + "bundle config set frozen false" + end + msg << "If this is a development machine, remove the #{Bundler.default_gemfile.relative_path_from(SharedHelpers.pwd)} " \ + "freeze by running `#{suggested_command}`." if suggested_command + end raise ProductionError, msg if added.any? || deleted.any? || changed.any? || !nothing_changed? end @@ -447,7 +462,9 @@ module Bundler end def remove_platform(platform) - return if @platforms.delete(Gem::Platform.new(platform)) + removed_platform = @platforms.delete(Gem::Platform.new(platform)) + @removed_platform ||= removed_platform + return if removed_platform raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}" end @@ -461,7 +478,11 @@ module Bundler private :sources def nothing_changed? - !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes + !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@missing_lockfile_dep && !@unlocking_bundler + end + + def no_resolve_needed? + !unlocking? && nothing_changed? end def unlocking? @@ -471,31 +492,26 @@ module Bundler private def resolver - @resolver ||= begin - last_resolve = converge_locked_specs - remove_ruby_from_platforms_if_necessary!(current_dependencies) - Resolver.new(source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve(last_resolve)) - end + @resolver ||= Resolver.new(resolution_packages, gem_version_promoter) end def expanded_dependencies - @expanded_dependencies ||= dependencies + metadata_dependencies + dependencies_with_bundler + metadata_dependencies end - def resolution_packages - @resolution_packages ||= begin - packages = Hash.new do |h, k| - h[k] = Resolver::Package.new(k, @platforms, @originally_locked_specs, @unlock[:gems]) - end - - expanded_dependencies.each do |dep| - name = dep.name - platforms = dep.gem_platforms(@platforms) + def dependencies_with_bundler + return dependencies unless @unlocking_bundler + return dependencies if dependencies.map(&:name).include?("bundler") - packages[name] = Resolver::Package.new(name, platforms, @originally_locked_specs, @unlock[:gems], :dependency => dep) - end + [Dependency.new("bundler", @unlocking_bundler)] + dependencies + end - packages + def resolution_packages + @resolution_packages ||= begin + last_resolve = converge_locked_specs + remove_ruby_from_platforms_if_necessary!(current_dependencies) + packages = Resolver::Base.new(source_requirements, expanded_dependencies, last_resolve, @platforms, :locked_specs => @originally_locked_specs, :unlock => @unlock[:gems], :prerelease => gem_version_promoter.pre?) + additional_base_requirements_for_resolve(packages, last_resolve) end end @@ -529,13 +545,15 @@ module Bundler break if incomplete_specs.empty? Bundler.ui.debug("The lockfile does not have all gems needed for the current platform though, Bundler will still re-resolve dependencies") - @resolve = start_resolution(:exclude_specs => incomplete_specs) + setup_sources_for_resolve + resolution_packages.delete(incomplete_specs) + @resolve = start_resolution specs = resolve.materialize(dependencies) still_incomplete_specs = specs.incomplete_specs if still_incomplete_specs == incomplete_specs - package = resolution_packages[incomplete_specs.first.name] + package = resolution_packages.get_package(incomplete_specs.first.name) resolver.raise_not_found! package end @@ -548,14 +566,16 @@ module Bundler specs end - def start_resolution(exclude_specs: []) - result = resolver.start(expanded_dependencies, resolution_packages, :exclude_specs => exclude_specs) + def start_resolution + result = resolver.start + + @resolved_bundler_version = result.find {|spec| spec.name == "bundler" }&.version SpecSet.new(SpecSet.new(result).for(dependencies, false, @platforms)) end def precompute_source_requirements_for_indirect_dependencies? - @remote && sources.non_global_rubygems_sources.all?(&:dependency_api_available?) && !sources.aggregate_global_source? + sources.non_global_rubygems_sources.all?(&:dependency_api_available?) && !sources.aggregate_global_source? end def pin_locally_available_names(source_requirements) @@ -585,6 +605,8 @@ module Bundler end def add_current_platform + return if current_ruby_platform_locked? + add_platform(local_platform) end @@ -606,6 +628,8 @@ module Bundler [@new_platform, "you added a new platform to your gemfile"], [@path_changes, "the gemspecs for path gems changed"], [@local_changes, "the gemspecs for git local gems changed"], + [@missing_lockfile_dep, "your lock file is missing \"#{@missing_lockfile_dep}\""], + [@unlocking_bundler, "an update to the version of Bundler itself was requested"], ].select(&:first).map(&:last).join(", ") end @@ -647,8 +671,8 @@ module Bundler Bundler.settings.local_overrides.map do |k, v| spec = @dependencies.find {|s| s.name == k } - source = spec && spec.source - if source && source.respond_to?(:local_override!) + source = spec&.source + if source&.respond_to?(:local_override!) source.unlock! if @unlock[:gems].include?(spec.name) locals << [source, source.local_override!(v)] end @@ -660,6 +684,26 @@ module Bundler !sources_with_changes.each {|source| @unlock[:sources] << source.name }.empty? end + def check_missing_lockfile_dep + all_locked_specs = @locked_specs.map(&:name) << "bundler" + + missing = @locked_specs.select do |s| + s.dependencies.any? {|dep| !all_locked_specs.include?(dep.name) } + end + + if missing.any? + @locked_specs.delete(missing) + + return missing.first.name + end + + return if @dependency_changes + + current_dependencies.find do |d| + @locked_specs[d.name].empty? && d.name != "bundler" + end&.name + end + def converge_paths sources.path_sources.any? do |source| specs_changed?(source) @@ -713,6 +757,8 @@ module Bundler dep.source = sources.get(dep.source) end + next if unlocking? + unless locked_dep = @locked_deps[dep.name] changes = true next @@ -759,26 +805,27 @@ module Bundler def converge_specs(specs) converged = [] - - deps = @dependencies.select do |dep| - specs[dep].any? {|s| s.satisfies?(dep) && (!dep.source || s.source.include?(dep.source)) } - end + deps = [] @specs_that_changed_sources = [] specs.each do |s| + name = s.name dep = @dependencies.find {|d| s.satisfies?(d) } + lockfile_source = s.source - # Replace the locked dependency's source with the equivalent source from the Gemfile - s.source = if dep && dep.source - gemfile_source = dep.source - lockfile_source = s.source + if dep + gemfile_source = dep.source || default_source @specs_that_changed_sources << s if gemfile_source != lockfile_source + deps << dep if !dep.source || lockfile_source.include?(dep.source) + @unlock[:gems] << name if lockfile_source.include?(dep.source) && lockfile_source != gemfile_source - gemfile_source + # Replace the locked dependency's source with the equivalent source from the Gemfile + s.source = gemfile_source else - sources.get_with_fallback(s.source) + # 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) end next if @unlock[:sources].include?(s.source.name) @@ -787,9 +834,9 @@ module Bundler if s.source.instance_of?(Source::Path) || s.source.instance_of?(Source::Gemspec) new_specs = begin s.source.specs - rescue PathError, GitError + rescue PathError # if we won't need the source (according to the lockfile), - # don't error if the path/git source isn't available + # don't error if the path source isn't available next if specs. for(requested_dependencies, false). none? {|locked_spec| locked_spec.source == s.source } @@ -798,15 +845,16 @@ module Bundler end new_spec = new_specs[s].first - - # If the spec is no longer in the path source, unlock it. This - # commonly happens if the version changed in the gemspec - next unless new_spec - - s.dependencies.replace(new_spec.dependencies) + if new_spec + s.dependencies.replace(new_spec.dependencies) + else + # If the spec is no longer in the path source, unlock it. This + # commonly happens if the version changed in the gemspec + @unlock[:gems] << name + end end - if dep.nil? && requested_dependencies.find {|d| s.name == d.name } + if dep.nil? && requested_dependencies.find {|d| name == d.name } @unlock[:gems] << s.name else converged << s @@ -830,7 +878,7 @@ module Bundler source_requirements = if precompute_source_requirements_for_indirect_dependencies? all_requirements = source_map.all_requirements all_requirements = pin_locally_available_names(all_requirements) if @prefer_local - { :default => sources.default_source }.merge(all_requirements) + { :default => default_source }.merge(all_requirements) else { :default => Source::RubygemsAggregate.new(sources, source_map) }.merge(source_map.direct_requirements) end @@ -838,12 +886,24 @@ module Bundler metadata_dependencies.each do |dep| source_requirements[dep.name] = sources.metadata_source end - source_requirements[:default_bundler] = source_requirements["bundler"] || sources.default_source - source_requirements["bundler"] = sources.metadata_source # needs to come last to override + + default_bundler_source = source_requirements["bundler"] || default_source + + if @unlocking_bundler + default_bundler_source.add_dependency_names("bundler") + else + source_requirements[:default_bundler] = default_bundler_source + source_requirements["bundler"] = sources.metadata_source # needs to come last to override + end + verify_changed_sources! source_requirements end + def default_source + sources.default_source + end + def verify_changed_sources! @specs_that_changed_sources.each do |s| if s.source.specs.search(s.name).empty? @@ -862,7 +922,8 @@ module Bundler if preserve_unknown_sections sections_to_ignore = LockfileParser.sections_to_ignore(@locked_bundler_version) sections_to_ignore += LockfileParser.unknown_sections_in_lockfile(current) - sections_to_ignore += LockfileParser::ENVIRONMENT_VERSION_SECTIONS + sections_to_ignore << LockfileParser::RUBY + sections_to_ignore << LockfileParser::BUNDLED unless @unlocking_bundler pattern = /#{Regexp.union(sections_to_ignore)}\n(\s{2,}.*\n)+/ whitespace_cleanup = /\n{2,}/ current = current.gsub(pattern, "\n").gsub(whitespace_cleanup, "\n\n").strip @@ -871,22 +932,13 @@ module Bundler current == proposed end - def compute_requires - dependencies.reduce({}) do |requires, dep| - next requires unless dep.should_include? - requires[dep.name] = Array(dep.autorequire || dep.name).map do |file| - # Allow `require: true` as an alias for `require: <name>` - file == true ? dep.name : file - end - requires + def additional_base_requirements_for_resolve(resolution_packages, last_resolve) + return resolution_packages unless @locked_gems && !sources.expired_sources?(@locked_gems.sources) + converge_specs(@originally_locked_specs - last_resolve).each do |locked_spec| + next if locked_spec.source.is_a?(Source::Path) + resolution_packages.base_requirements[locked_spec.name] = Gem::Requirement.new(">= #{locked_spec.version}") end - end - - def additional_base_requirements_for_resolve(last_resolve) - return [] unless @locked_gems && unlocking? && !sources.expired_sources?(@locked_gems.sources) - converge_specs(@originally_locked_specs - last_resolve).map do |locked_spec| - Dependency.new(locked_spec.name, ">= #{locked_spec.version}") - end.uniq + resolution_packages end def remove_ruby_from_platforms_if_necessary!(dependencies) @@ -894,6 +946,8 @@ module Bundler Bundler.local_platform == Gem::Platform::RUBY || !platforms.include?(Gem::Platform::RUBY) || (@new_platform && platforms.last == Gem::Platform::RUBY) || + @path_changes || + @dependency_changes || !@originally_locked_specs.incomplete_ruby_specs?(dependencies) remove_platform(Gem::Platform::RUBY) diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb index 695e5c12b2..5f17943629 100644 --- a/lib/bundler/dependency.rb +++ b/lib/bundler/dependency.rb @@ -7,9 +7,9 @@ require_relative "rubygems_ext" module Bundler class Dependency < Gem::Dependency attr_reader :autorequire - attr_reader :groups, :platforms, :gemfile, :path, :git, :github, :branch, :ref, :force_ruby_platform + attr_reader :groups, :platforms, :gemfile, :path, :git, :github, :branch, :ref - ALL_RUBY_VERSIONS = ((18..27).to_a + (30..31).to_a).freeze + ALL_RUBY_VERSIONS = ((18..27).to_a + (30..33).to_a).freeze PLATFORM_MAP = { :ruby => [Gem::Platform::RUBY, ALL_RUBY_VERSIONS], :mri => [Gem::Platform::RUBY, ALL_RUBY_VERSIONS], @@ -42,7 +42,7 @@ module Bundler @env = options["env"] @should_include = options.fetch("should_include", true) @gemfile = options["gemfile"] - @force_ruby_platform = options["force_ruby_platform"] + @force_ruby_platform = options["force_ruby_platform"] if options.key?("force_ruby_platform") @autorequire = Array(options["require"] || []) if options.key?("require") end @@ -50,7 +50,7 @@ module Bundler # Returns the platforms this dependency is valid for, in the same order as # passed in the `valid_platforms` parameter def gem_platforms(valid_platforms) - return [Gem::Platform::RUBY] if @force_ruby_platform + return [Gem::Platform::RUBY] if force_ruby_platform return valid_platforms if @platforms.empty? valid_platforms.select {|p| expanded_platforms.include?(GemHelpers.generic(p)) } diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index 547db16190..03c80a408c 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -41,7 +41,7 @@ module Bundler end def eval_gemfile(gemfile, contents = nil) - expanded_gemfile_path = Pathname.new(gemfile).expand_path(@gemfile && @gemfile.parent) + expanded_gemfile_path = Pathname.new(gemfile).expand_path(@gemfile&.parent) original_gemfile = @gemfile @gemfile = expanded_gemfile_path @gemfiles << expanded_gemfile_path @@ -277,8 +277,8 @@ module Bundler if repo_name =~ GITHUB_PULL_REQUEST_URL { "git" => "https://github.com/#{$1}.git", - "branch" => "refs/pull/#{$2}/head", - "ref" => nil, + "branch" => nil, + "ref" => "refs/pull/#{$2}/head", "tag" => nil, } else @@ -324,7 +324,7 @@ module Bundler if name.is_a?(Symbol) raise GemfileError, %(You need to specify gem names as Strings. Use 'gem "#{name}"' instead) end - if name =~ /\s/ + if /\s/.match?(name) raise GemfileError, %('#{name}' is not a valid gem name because it contains whitespace) end raise GemfileError, %(an empty gem name is not valid) if name.empty? diff --git a/lib/bundler/endpoint_specification.rb b/lib/bundler/endpoint_specification.rb index d315d1cc68..863544b1f9 100644 --- a/lib/bundler/endpoint_specification.rb +++ b/lib/bundler/endpoint_specification.rb @@ -26,10 +26,6 @@ module Bundler @platform end - def identifier - @__identifier ||= [name, version, platform.to_s] - end - # needed for standalone, load required_paths from local gemspec # after the gem is installed def require_paths diff --git a/lib/bundler/env.rb b/lib/bundler/env.rb index 8b2e292fd6..7b1152930e 100644 --- a/lib/bundler/env.rb +++ b/lib/bundler/env.rb @@ -122,7 +122,7 @@ module Bundler specs = Bundler.rubygems.find_name(name) out << [" #{name}", "(#{specs.map(&:version).join(",")})"] unless specs.empty? end - if (exe = caller.last.split(":").first) && exe =~ %r{(exe|bin)/bundler?\z} + if (exe = caller.last.split(":").first)&.match? %r{(exe|bin)/bundler?\z} shebang = File.read(exe).lines.first shebang.sub!(/^#!\s*/, "") unless shebang.start_with?(Gem.ruby, "/usr/bin/env ruby") diff --git a/lib/bundler/environment_preserver.rb b/lib/bundler/environment_preserver.rb index 70967522af..57013f5d50 100644 --- a/lib/bundler/environment_preserver.rb +++ b/lib/bundler/environment_preserver.rb @@ -2,7 +2,7 @@ module Bundler class EnvironmentPreserver - INTENTIONALLY_NIL = "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL".freeze + INTENTIONALLY_NIL = "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL" BUNDLER_KEYS = %w[ BUNDLE_BIN_PATH BUNDLE_GEMFILE @@ -16,7 +16,7 @@ module Bundler RUBYLIB RUBYOPT ].map(&:freeze).freeze - BUNDLER_PREFIX = "BUNDLER_ORIG_".freeze + BUNDLER_PREFIX = "BUNDLER_ORIG_" def self.from_env new(env_to_hash(ENV), BUNDLER_KEYS) diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index 983de3137c..ab2189f7f0 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -37,7 +37,6 @@ module Bundler 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(:suppress_install_using_messages) { bundler_3_mode? } settings_flag(:update_requires_all_flag) { bundler_4_mode? } settings_option(:default_cli_command) { bundler_3_mode? ? :cli_help : :install } diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index a073bae278..2119799f68 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -61,6 +61,16 @@ module Bundler end end + # This error is raised if HTTP authentication is correct, but lacks + # necessary permissions. + class AuthenticationForbiddenError < HTTPError + def initialize(remote_uri) + remote_uri = filter_uri(remote_uri) + super "Access token could not be authenticated for #{remote_uri}.\n" \ + "Make sure it's valid and has the necessary scopes configured." + end + end + # 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, @@ -70,7 +80,7 @@ module Bundler :HTTPRequestURITooLong, :HTTPUnauthorized, :HTTPUnprocessableEntity, :HTTPUnsupportedMediaType, :HTTPVersionNotSupported].freeze FAIL_ERRORS = begin - fail_errors = [AuthenticationRequiredError, BadAuthenticationError, FallbackError] + fail_errors = [AuthenticationRequiredError, BadAuthenticationError, AuthenticationForbiddenError, FallbackError] fail_errors << Gem::Requirement::BadRequirementError fail_errors.concat(NET_ERRORS.map {|e| Net.const_get(e) }) end.freeze @@ -102,11 +112,11 @@ module Bundler uri = Bundler::URI.parse("#{remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}.rz") if uri.scheme == "file" path = Bundler.rubygems.correct_for_windows_path(uri.path) - Bundler.load_marshal Bundler.rubygems.inflate(Gem.read_binary(path)) + Bundler.safe_load_marshal Bundler.rubygems.inflate(Gem.read_binary(path)) elsif cached_spec_path = gemspec_cached_path(spec_file_name) Bundler.load_gemspec(cached_spec_path) else - Bundler.load_marshal Bundler.rubygems.inflate(downloader.fetch(uri).body) + Bundler.safe_load_marshal Bundler.rubygems.inflate(downloader.fetch(uri).body) end rescue MarshalError raise HTTPError, "Gemspec #{spec} contained invalid data.\n" \ diff --git a/lib/bundler/fetcher/compact_index.rb b/lib/bundler/fetcher/compact_index.rb index b23176588f..8d30dec471 100644 --- a/lib/bundler/fetcher/compact_index.rb +++ b/lib/bundler/fetcher/compact_index.rb @@ -12,17 +12,15 @@ module Bundler method = instance_method(method_name) undef_method(method_name) define_method(method_name) do |*args, &blk| - begin - method.bind(self).call(*args, &blk) - rescue NetworkDownError, CompactIndexClient::Updater::MisMatchedChecksumError => e - raise HTTPError, e.message - rescue AuthenticationRequiredError - # Fail since we got a 401 from the server. - raise - rescue HTTPError => e - Bundler.ui.trace(e) - nil - end + method.bind(self).call(*args, &blk) + rescue NetworkDownError, CompactIndexClient::Updater::MisMatchedChecksumError => e + raise HTTPError, e.message + rescue AuthenticationRequiredError, BadAuthenticationError + # Fail since we got a 401 from the server. + raise + rescue HTTPError => e + Bundler.ui.trace(e) + nil end end @@ -42,7 +40,7 @@ module Bundler deps = begin parallel_compact_index_client.dependencies(remaining_gems) rescue TooManyRequestsError - @bundle_worker.stop if @bundle_worker + @bundle_worker&.stop @bundle_worker = nil # reset it. Not sure if necessary serial_compact_index_client.dependencies(remaining_gems) end @@ -51,7 +49,7 @@ module Bundler complete_gems.concat(deps.map(&:first)).uniq! remaining_gems = next_gems - complete_gems end - @bundle_worker.stop if @bundle_worker + @bundle_worker&.stop @bundle_worker = nil # reset it. Not sure if necessary gem_info diff --git a/lib/bundler/fetcher/dependency.rb b/lib/bundler/fetcher/dependency.rb index c52c32fb5b..18b606abb6 100644 --- a/lib/bundler/fetcher/dependency.rb +++ b/lib/bundler/fetcher/dependency.rb @@ -34,14 +34,10 @@ module Bundler returned_gems = spec_list.map(&:first).uniq specs(deps_list, full_dependency_list + returned_gems, spec_list + last_spec_list) - rescue MarshalError + rescue MarshalError, HTTPError, GemspecError Bundler.ui.info "" unless Bundler.ui.debug? # new line now that the dots are over Bundler.ui.debug "could not fetch from the dependency API, trying the full index" nil - rescue HTTPError, GemspecError - Bundler.ui.info "" unless Bundler.ui.debug? # new line now that the dots are over - Bundler.ui.debug "could not fetch from the dependency API\nit's suggested to retry using the full index via `bundle install --full-index`" - nil end def dependency_specs(gem_names) @@ -55,7 +51,7 @@ module Bundler gem_list = [] gem_names.each_slice(Source::Rubygems::API_REQUEST_SIZE) do |names| marshalled_deps = downloader.fetch(dependency_api_uri(names)).body - gem_list.concat(Bundler.load_marshal(marshalled_deps)) + gem_list.concat(Bundler.safe_load_marshal(marshalled_deps)) end gem_list end diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb index 0a668eb17c..3062899e0e 100644 --- a/lib/bundler/fetcher/downloader.rb +++ b/lib/bundler/fetcher/downloader.rb @@ -41,6 +41,8 @@ module Bundler when Net::HTTPUnauthorized raise BadAuthenticationError, uri.host if uri.userinfo raise AuthenticationRequiredError, uri.host + when Net::HTTPForbidden + raise AuthenticationForbiddenError, uri.host when Net::HTTPNotFound raise FallbackError, "Net::HTTPNotFound: #{filtered_uri}" else @@ -61,9 +63,6 @@ module Bundler req.basic_auth(user, password) end connection.request(uri, req) - rescue NoMethodError => e - raise unless ["undefined method", "use_ssl="].all? {|snippet| e.message.include? snippet } - raise LoadError.new("cannot load such file -- openssl") rescue OpenSSL::SSL::SSLError raise CertificateFailureError.new(uri) rescue *HTTP_ERRORS => e @@ -80,7 +79,7 @@ module Bundler private def validate_uri_scheme!(uri) - return if uri.scheme =~ /\Ahttps?\z/ + return if /\Ahttps?\z/.match?(uri.scheme) raise InvalidOption, "The request uri `#{uri}` has an invalid scheme (`#{uri.scheme}`). " \ "Did you mean `http` or `https`?" diff --git a/lib/bundler/fetcher/index.rb b/lib/bundler/fetcher/index.rb index 6bb9fcc193..c623647f01 100644 --- a/lib/bundler/fetcher/index.rb +++ b/lib/bundler/fetcher/index.rb @@ -15,8 +15,7 @@ module Bundler raise BadAuthenticationError, remote_uri if remote_uri.userinfo raise AuthenticationRequiredError, remote_uri when /403/ - raise BadAuthenticationError, remote_uri if remote_uri.userinfo - raise AuthenticationRequiredError, remote_uri + raise AuthenticationForbiddenError, remote_uri else raise HTTPError, "Could not fetch specs from #{display_uri} due to underlying error <#{e.message}>" end diff --git a/lib/bundler/force_platform.rb b/lib/bundler/force_platform.rb new file mode 100644 index 0000000000..249a24ecd1 --- /dev/null +++ b/lib/bundler/force_platform.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Bundler + module ForcePlatform + private + + # The `:force_ruby_platform` value used by dependencies for resolution, and + # by locked specifications for materialization is `false` by default, except + # for TruffleRuby. TruffleRuby generally needs to force the RUBY platform + # variant unless the name is explicitly allowlisted. + + def default_force_ruby_platform + return false unless RUBY_ENGINE == "truffleruby" + + !Gem::Platform::REUSE_AS_BINARY_ON_TRUFFLERUBY.include?(name) + end + end +end diff --git a/lib/bundler/friendly_errors.rb b/lib/bundler/friendly_errors.rb index d0d2a6679a..39afe8a071 100644 --- a/lib/bundler/friendly_errors.rb +++ b/lib/bundler/friendly_errors.rb @@ -36,9 +36,6 @@ module Bundler end when Thor::Error Bundler.ui.error error.message - when LoadError - raise error unless error.message =~ /cannot load such file -- openssl|openssl.so|libcrypto.so/ - Bundler.ui.error "\nCould not load OpenSSL. #{error.class}: #{error}\n#{error.backtrace.join("\n ")}" when Interrupt Bundler.ui.error "\nQuitting..." Bundler.ui.trace error @@ -98,7 +95,7 @@ module Bundler def serialized_exception_for(e) <<-EOS.gsub(/^ {8}/, "") #{e.class}: #{e.message} - #{e.backtrace && e.backtrace.join("\n ").chomp} + #{e.backtrace&.join("\n ")&.chomp} EOS end diff --git a/lib/bundler/gem_helper.rb b/lib/bundler/gem_helper.rb index 0bbd2d9b75..dcf759cded 100644 --- a/lib/bundler/gem_helper.rb +++ b/lib/bundler/gem_helper.rb @@ -21,7 +21,7 @@ module Bundler def gemspec(&block) gemspec = instance.gemspec - block.call(gemspec) if block + block&.call(gemspec) gemspec end end @@ -152,8 +152,7 @@ module Bundler def gem_push_host env_rubygems_host = ENV["RUBYGEMS_HOST"] - env_rubygems_host = nil if - env_rubygems_host && env_rubygems_host.empty? + env_rubygems_host = nil if env_rubygems_host&.empty? allowed_push_host || env_rubygems_host || "rubygems.org" end @@ -218,7 +217,7 @@ module Bundler SharedHelpers.chdir(base) do outbuf = IO.popen(cmd, :err => [:child, :out], &:read) status = $? - block.call(outbuf) if status.success? && block + block&.call(outbuf) if status.success? [outbuf, status] end end diff --git a/lib/bundler/gem_version_promoter.rb b/lib/bundler/gem_version_promoter.rb index d6ae65998f..d281f46eeb 100644 --- a/lib/bundler/gem_version_promoter.rb +++ b/lib/bundler/gem_version_promoter.rb @@ -8,11 +8,12 @@ module Bundler # to the resolution engine to select the best version. class GemVersionPromoter attr_reader :level + attr_accessor :pre # By default, strict is false, meaning every available version of a gem # is returned from sort_versions. The order gives preference to the # requested level (:patch, :minor, :major) but in complicated requirement - # cases some gems will by necessity by promoted past the requested level, + # cases some gems will by necessity be promoted past the requested level, # or even reverted to older versions. # # If strict is set to true, the results from sort_versions will be @@ -28,6 +29,7 @@ module Bundler def initialize @level = :major @strict = false + @pre = false end # @param value [Symbol] One of three Symbols: :major, :minor or :patch. @@ -66,23 +68,24 @@ module Bundler level == :minor end + # @return [bool] Convenience method for testing value of pre variable. + def pre? + pre == true + end + private def filter_dep_specs(specs, package) locked_version = package.locked_version + return specs if locked_version.nil? || major? specs.select do |spec| - if locked_version && !major? - gsv = spec.version - lsv = locked_version + gsv = spec.version - must_match = minor? ? [0] : [0, 1] + must_match = minor? ? [0] : [0, 1] - matches = must_match.map {|idx| gsv.segments[idx] == lsv.segments[idx] } - matches.uniq == [true] ? (gsv >= lsv) : false - else - true - end + all_match = must_match.all? {|idx| gsv.segments[idx] == locked_version.segments[idx] } + all_match && gsv >= locked_version end end @@ -90,7 +93,7 @@ module Bundler locked_version = package.locked_version result = specs.sort do |a, b| - unless locked_version && package.prerelease_specified? + unless package.prerelease_specified? || pre? a_pre = a.prerelease? b_pre = b.prerelease? @@ -100,11 +103,11 @@ module Bundler if major? a <=> b - elsif either_version_older_than_locked(a, b, locked_version) + elsif either_version_older_than_locked?(a, b, locked_version) a <=> b - elsif segments_do_not_match(a, b, :major) + elsif segments_do_not_match?(a, b, :major) b <=> a - elsif !minor? && segments_do_not_match(a, b, :minor) + elsif !minor? && segments_do_not_match?(a, b, :minor) b <=> a else a <=> b @@ -113,11 +116,11 @@ module Bundler post_sort(result, package.unlock?, locked_version) end - def either_version_older_than_locked(a, b, locked_version) + def either_version_older_than_locked?(a, b, locked_version) locked_version && (a.version < locked_version || b.version < locked_version) end - def segments_do_not_match(a, b, level) + def segments_do_not_match?(a, b, level) index = [:major, :minor].index(level) a.segments[index] != b.segments[index] end diff --git a/lib/bundler/index.rb b/lib/bundler/index.rb index 0301986ca9..b8c599f63a 100644 --- a/lib/bundler/index.rb +++ b/lib/bundler/index.rb @@ -13,8 +13,8 @@ module Bundler attr_reader :specs, :all_specs, :sources protected :specs, :all_specs - RUBY = "ruby".freeze - NULL = "\0".freeze + RUBY = "ruby" + NULL = "\0" def initialize @sources = [] diff --git a/lib/bundler/injector.rb b/lib/bundler/injector.rb index 81465cec19..cb644a7f69 100644 --- a/lib/bundler/injector.rb +++ b/lib/bundler/injector.rb @@ -2,7 +2,7 @@ module Bundler class Injector - INJECTED_GEMS = "injected gems".freeze + INJECTED_GEMS = "injected gems" def self.inject(new_deps, options = {}) injector = new(new_deps, options) @@ -235,7 +235,7 @@ module Bundler gemfile.each_with_index do |line, index| next unless !line.nil? && line.strip.start_with?(block_name) - if gemfile[index + 1] =~ /^\s*end\s*$/ + if /^\s*end\s*$/.match?(gemfile[index + 1]) gemfile[index] = nil gemfile[index + 1] = nil end diff --git a/lib/bundler/inline.rb b/lib/bundler/inline.rb index 6664d3ebc4..5c184f67a1 100644 --- a/lib/bundler/inline.rb +++ b/lib/bundler/inline.rb @@ -31,6 +31,7 @@ # def gemfile(install = false, options = {}, &gemfile) require_relative "../bundler" + Bundler.reset! opts = options.dup ui = opts.delete(:ui) { Bundler::UI::Shell.new } @@ -38,9 +39,8 @@ def gemfile(install = false, options = {}, &gemfile) Bundler.ui = ui raise ArgumentError, "Unknown options: #{opts.keys.join(", ")}" unless opts.empty? - begin + Bundler.with_unbundled_env do Bundler.instance_variable_set(:@bundle_path, Pathname.new(Gem.dir)) - old_gemfile = ENV["BUNDLE_GEMFILE"] Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile" Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins? @@ -65,11 +65,9 @@ def gemfile(install = false, options = {}, &gemfile) runtime = Bundler::Runtime.new(nil, definition) runtime.setup.require end - ensure - if old_gemfile - ENV["BUNDLE_GEMFILE"] = old_gemfile - else - ENV["BUNDLE_GEMFILE"] = "" - end + end + + if ENV["BUNDLE_GEMFILE"].nil? + ENV["BUNDLE_GEMFILE"] = "" end end diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index 1b17de5d4e..59b6a6ad22 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -90,7 +90,7 @@ module Bundler Gem::Specification.reset # invalidate gem specification cache so that installed gems are immediately available - lock unless Bundler.frozen_bundle? + lock Standalone.new(options[:standalone], @definition).generate if options[:standalone] end end @@ -136,11 +136,7 @@ module Bundler mode = Gem.win_platform? ? "wb:UTF-8" : "w" require "erb" - content = if RUBY_VERSION >= "2.6" - ERB.new(template, :trim_mode => "-").result(binding) - else - ERB.new(template, nil, "-").result(binding) - end + content = ERB.new(template, :trim_mode => "-").result(binding) File.write(binstub_path, content, :mode => mode, :perm => 0o777 & ~File.umask) if Gem.win_platform? || options[:all_platforms] @@ -183,11 +179,7 @@ module Bundler mode = Gem.win_platform? ? "wb:UTF-8" : "w" require "erb" - content = if RUBY_VERSION >= "2.6" - ERB.new(template, :trim_mode => "-").result(binding) - else - ERB.new(template, nil, "-").result(binding) - end + content = ERB.new(template, :trim_mode => "-").result(binding) File.write("#{bin_path}/#{executable}", content, :mode => mode, :perm => 0o755) if Gem.win_platform? || options[:all_platforms] @@ -226,12 +218,10 @@ module Bundler requested_path_gems = @definition.requested_specs.select {|s| s.source.is_a?(Source::Path) } path_plugin_files = requested_path_gems.map do |spec| - begin - Bundler.rubygems.spec_matches_for_glob(spec, "rubygems_plugin#{Bundler.rubygems.suffix_pattern}") - rescue TypeError - error_message = "#{spec.name} #{spec.version} has an invalid gemspec" - raise Gem::InvalidSpecificationException, error_message - end + Bundler.rubygems.spec_matches_for_glob(spec, "rubygems_plugin#{Bundler.rubygems.suffix_pattern}") + rescue TypeError + error_message = "#{spec.name} #{spec.version} has an invalid gemspec" + raise Gem::InvalidSpecificationException, error_message end.flatten Bundler.rubygems.load_plugin_files(path_plugin_files) Bundler.rubygems.load_env_plugins @@ -259,17 +249,13 @@ module Bundler # returns whether or not a re-resolve was needed def resolve_if_needed(options) + @definition.resolution_mode = options + if !@definition.unlocking? && !options["force"] && !Bundler.settings[:inline] && Bundler.default_lockfile.file? return false if @definition.nothing_changed? && !@definition.missing_specs? end - if options["local"] - @definition.resolve_with_cache! - elsif options["prefer-local"] - @definition.resolve_prefering_local! - else - @definition.resolve_remotely! - end + @definition.setup_sources_for_resolve true end diff --git a/lib/bundler/installer/parallel_installer.rb b/lib/bundler/installer/parallel_installer.rb index 5b6680e5e1..83a381f592 100644 --- a/lib/bundler/installer/parallel_installer.rb +++ b/lib/bundler/installer/parallel_installer.rb @@ -53,10 +53,6 @@ module Bundler @dependencies ||= all_dependencies.reject {|dep| ignorable_dependency? dep } end - def missing_lockfile_dependencies(all_spec_names) - dependencies.reject {|dep| all_spec_names.include? dep.name } - end - # Represents all dependencies def all_dependencies @spec.dependencies @@ -84,8 +80,6 @@ module Bundler end def call - check_for_corrupt_lockfile - if @rake do_install(@rake, 0) Gem::Specification.reset @@ -102,7 +96,7 @@ module Bundler handle_error if failed_specs.any? @specs ensure - worker_pool && worker_pool.stop + worker_pool&.stop end def check_for_unmet_dependencies @@ -116,43 +110,19 @@ module Bundler warning = [] warning << "Your lockfile doesn't include a valid resolution." - warning << "You can fix this by regenerating your lockfile or trying to manually editing the bad locked gems to a version that satisfies all dependencies." + warning << "You can fix this by regenerating your lockfile or manually editing the bad locked gems to a version that satisfies all dependencies." warning << "The unmet dependencies are:" unmet_dependencies.each do |spec, unmet_spec_dependencies| unmet_spec_dependencies.each do |unmet_spec_dependency| - warning << "* #{unmet_spec_dependency}, depended upon #{spec.full_name}, unsatisfied by #{@specs.find {|s| s.name == unmet_spec_dependency.name && !unmet_spec_dependency.matches_spec?(s.spec) }.full_name}" + found = @specs.find {|s| s.name == unmet_spec_dependency.name && !unmet_spec_dependency.matches_spec?(s.spec) } + warning << "* #{unmet_spec_dependency}, dependency of #{spec.full_name}, unsatisfied by #{found.full_name}" end end Bundler.ui.warn(warning.join("\n")) end - def check_for_corrupt_lockfile - missing_dependencies = @specs.map do |s| - [ - s, - s.missing_lockfile_dependencies(@specs.map(&:name)), - ] - end.reject {|a| a.last.empty? } - return if missing_dependencies.empty? - - warning = [] - warning << "Your lockfile was created by an old Bundler that left some things out." - if @size != 1 - warning << "Because of the missing DEPENDENCIES, we can only install gems one at a time, instead of installing #{@size} at a time." - @size = 1 - end - warning << "You can fix this by adding the missing gems to your Gemfile, running bundle install, and then removing the gems from your Gemfile." - warning << "The missing gems are:" - - missing_dependencies.each do |spec, missing| - warning << "* #{missing.map(&:name).join(", ")} depended upon by #{spec.name}" - end - - Bundler.ui.warn(warning.join("\n")) - end - private def failed_specs diff --git a/lib/bundler/installer/standalone.rb b/lib/bundler/installer/standalone.rb index 2756626f8a..2a8c9a432d 100644 --- a/lib/bundler/installer/standalone.rb +++ b/lib/bundler/installer/standalone.rb @@ -52,7 +52,7 @@ module Bundler def gem_path(path, spec) full_path = Pathname.new(path).absolute? ? path : File.join(spec.full_gem_path, path) - if spec.source.instance_of?(Source::Path) + if spec.source.instance_of?(Source::Path) && spec.source.path.absolute? full_path else Pathname.new(full_path).relative_path_from(Bundler.root.join(bundler_path)).to_s @@ -84,13 +84,17 @@ module Bundler def reverse_rubygems_kernel_mixin <<~END - kernel = (class << ::Kernel; self; end) - [kernel, ::Kernel].each do |k| - if k.private_method_defined?(:gem_original_require) - private_require = k.private_method_defined?(:require) - k.send(:remove_method, :require) - k.send(:define_method, :require, k.instance_method(:gem_original_require)) - k.send(:private, :require) if private_require + if Gem.respond_to?(:discover_gems_on_require=) + Gem.discover_gems_on_require = false + else + kernel = (class << ::Kernel; self; end) + [kernel, ::Kernel].each do |k| + if k.private_method_defined?(:gem_original_require) + private_require = k.private_method_defined?(:require) + k.send(:remove_method, :require) + k.send(:define_method, :require, k.instance_method(:gem_original_require)) + k.send(:private, :require) if private_require + end end end END diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb index 21a6f96f69..c9b161dc0e 100644 --- a/lib/bundler/lazy_specification.rb +++ b/lib/bundler/lazy_specification.rb @@ -1,8 +1,11 @@ # frozen_string_literal: true +require_relative "force_platform" + module Bundler class LazySpecification include MatchPlatform + include ForcePlatform attr_reader :name, :version, :dependencies, :platform attr_accessor :source, :remote, :force_ruby_platform @@ -13,11 +16,11 @@ module Bundler @dependencies = [] @platform = platform || Gem::Platform::RUBY @source = source - @specification = nil + @force_ruby_platform = default_force_ruby_platform end def full_name - if platform == Gem::Platform::RUBY + @full_name ||= if platform == Gem::Platform::RUBY "#{@name}-#{@version}" else "#{@name}-#{@version}-#{platform}" @@ -25,15 +28,15 @@ module Bundler end def ==(other) - identifier == other.identifier + full_name == other.full_name end def eql?(other) - identifier.eql?(other.identifier) + full_name.eql?(other.full_name) end def hash - identifier.hash + full_name.hash end ## @@ -76,51 +79,56 @@ module Bundler def materialize_for_installation source.local! - candidates = if source.is_a?(Source::Path) || !ruby_platform_materializes_to_ruby_platform? - target_platform = ruby_platform_materializes_to_ruby_platform? ? platform : local_platform + matching_specs = source.specs.search(use_exact_resolved_specifications? ? self : [name, version]) + return self if matching_specs.empty? - GemHelpers.select_best_platform_match(source.specs.search([name, version]), target_platform) + candidates = if use_exact_resolved_specifications? + matching_specs else - source.specs.search(self) - end + target_platform = ruby_platform_materializes_to_ruby_platform? ? platform : local_platform - return self if candidates.empty? + installable_candidates = GemHelpers.select_best_platform_match(matching_specs, target_platform) - __materialize__(candidates) - end + specification = __materialize__(installable_candidates, :fallback_to_non_installable => false) + return specification unless specification.nil? - def __materialize__(candidates) - @specification = begin - search = candidates.reverse.find do |spec| - spec.is_a?(StubSpecification) || - (spec.matches_current_ruby? && - spec.matches_current_rubygems?) - end - if search.nil? && Bundler.frozen_bundle? - search = candidates.last - else - search.dependencies = dependencies if search && search.full_name == full_name && (search.is_a?(RemoteSpecification) || search.is_a?(EndpointSpecification)) + if target_platform != platform + installable_candidates = GemHelpers.select_best_platform_match(matching_specs, platform) end - search + + installable_candidates end + + __materialize__(candidates) end - def respond_to?(*args) - super || @specification ? @specification.respond_to?(*args) : nil + # If in frozen mode, we fallback to a non-installable candidate because by + # doing this we avoid re-resolving and potentially end up changing the + # lock file, which is not allowed. In that case, we will give a proper error + # about the mismatch higher up the stack, right before trying to install the + # bad gem. + def __materialize__(candidates, fallback_to_non_installable: Bundler.frozen_bundle?) + search = candidates.reverse.find do |spec| + spec.is_a?(StubSpecification) || + (spec.matches_current_ruby? && + spec.matches_current_rubygems?) + end + if search.nil? && fallback_to_non_installable + search = candidates.last + else + search.dependencies = dependencies if search && search.full_name == full_name && (search.is_a?(RemoteSpecification) || search.is_a?(EndpointSpecification)) + end + search end def to_s - @__to_s ||= if platform == Gem::Platform::RUBY + @to_s ||= if platform == Gem::Platform::RUBY "#{name} (#{version})" else "#{name} (#{version}-#{platform})" end end - def identifier - @__identifier ||= [name, version, platform.to_s] - end - def git_version return unless source.is_a?(Bundler::Source::Git) " #{source.revision[0..6]}" @@ -128,16 +136,8 @@ module Bundler private - def to_ary - nil - end - - def method_missing(method, *args, &blk) - raise "LazySpecification has not been materialized yet (calling :#{method} #{args.inspect})" unless @specification - - return super unless respond_to?(method) - - @specification.send(method, *args, &blk) + def use_exact_resolved_specifications? + @use_exact_resolved_specifications ||= !source.is_a?(Source::Path) && ruby_platform_materializes_to_ruby_platform? end # diff --git a/lib/bundler/lockfile_generator.rb b/lib/bundler/lockfile_generator.rb index 23413dbdd6..f7ba51b3e6 100644 --- a/lib/bundler/lockfile_generator.rb +++ b/lib/bundler/lockfile_generator.rb @@ -45,7 +45,7 @@ module Bundler # gems with the same name, but different platform # are ordered consistently specs.sort_by(&:full_name).each do |spec| - next if spec.name == "bundler".freeze + next if spec.name == "bundler" out << spec.to_lock end end @@ -71,7 +71,7 @@ module Bundler end def add_bundled_with - add_section("BUNDLED WITH", Bundler::VERSION) + add_section("BUNDLED WITH", definition.bundler_version_to_lock.to_s) end def add_section(name, value) diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb index 871f53663c..7360a36752 100644 --- a/lib/bundler/lockfile_parser.rb +++ b/lib/bundler/lockfile_parser.rb @@ -4,15 +4,15 @@ module Bundler class LockfileParser attr_reader :sources, :dependencies, :specs, :platforms, :bundler_version, :ruby_version - BUNDLED = "BUNDLED WITH".freeze - DEPENDENCIES = "DEPENDENCIES".freeze - PLATFORMS = "PLATFORMS".freeze - RUBY = "RUBY VERSION".freeze - GIT = "GIT".freeze - GEM = "GEM".freeze - PATH = "PATH".freeze - PLUGIN = "PLUGIN SOURCE".freeze - SPECS = " specs:".freeze + BUNDLED = "BUNDLED WITH" + DEPENDENCIES = "DEPENDENCIES" + PLATFORMS = "PLATFORMS" + RUBY = "RUBY VERSION" + GIT = "GIT" + GEM = "GEM" + PATH = "PATH" + PLUGIN = "PLUGIN SOURCE" + SPECS = " specs:" OPTIONS = /^ ([a-z]+): (.*)$/i.freeze SOURCE = [GIT, GEM, PATH, PLUGIN].freeze @@ -26,6 +26,7 @@ module Bundler KNOWN_SECTIONS = SECTIONS_BY_VERSION_INTRODUCED.values.flatten.freeze ENVIRONMENT_VERSION_SECTIONS = [BUNDLED, RUBY].freeze + deprecate_constant(:ENVIRONMENT_VERSION_SECTIONS) def self.sections_in_lockfile(lockfile_contents) lockfile_contents.scan(/^\w[\w ]*$/).uniq @@ -63,7 +64,7 @@ module Bundler @state = nil @specs = {} - if lockfile.match(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/) + if lockfile.match?(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/) raise LockfileError, "Your #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} contains merge conflicts.\n" \ "Run `git checkout HEAD -- #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}` first to get a clean lock." end @@ -80,13 +81,13 @@ module Bundler @state = :ruby elsif line == BUNDLED @state = :bundled_with - elsif line =~ /^[^\s]/ + elsif /^[^\s]/.match?(line) @state = nil elsif @state send("parse_#{@state}", line) end end - @specs = @specs.values.sort_by(&:identifier) + @specs = @specs.values.sort_by(&:full_name) rescue ArgumentError => e Bundler.ui.debug(e) raise LockfileError, "Your lockfile is unreadable. Run `rm #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}` " \ @@ -199,7 +200,7 @@ module Bundler @current_spec.source = @current_source @current_source.add_dependency_names(name) - @specs[@current_spec.identifier] = @current_spec + @specs[@current_spec.full_name] = @current_spec elsif spaces.size == 6 version = version.split(",").map(&:strip) if version dep = Gem::Dependency.new(name, version) diff --git a/lib/bundler/man/bundle-add.1 b/lib/bundler/man/bundle-add.1 index ba4b76f7df..8549855b0d 100644 --- a/lib/bundler/man/bundle-add.1 +++ b/lib/bundler/man/bundle-add.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-ADD" "1" "October 2022" "" "" +.TH "BUNDLE\-ADD" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install diff --git a/lib/bundler/man/bundle-binstubs.1 b/lib/bundler/man/bundle-binstubs.1 index 152aec9dd0..40c338916a 100644 --- a/lib/bundler/man/bundle-binstubs.1 +++ b/lib/bundler/man/bundle-binstubs.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-BINSTUBS" "1" "October 2022" "" "" +.TH "BUNDLE\-BINSTUBS" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-binstubs\fR \- Install the binstubs of the listed gems diff --git a/lib/bundler/man/bundle-cache.1 b/lib/bundler/man/bundle-cache.1 index 9eb2e1d7cc..69b1e1e3dd 100644 --- a/lib/bundler/man/bundle-cache.1 +++ b/lib/bundler/man/bundle-cache.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-CACHE" "1" "October 2022" "" "" +.TH "BUNDLE\-CACHE" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application @@ -13,7 +13,7 @@ alias: \fBpackage\fR, \fBpack\fR . .SH "DESCRIPTION" -Copy all of the \fB\.gem\fR files needed to run the application into the \fBvendor/cache\fR directory\. In the future, when running [bundle install(1)][bundle\-install], use the gems in the cache in preference to the ones on \fBrubygems\.org\fR\. +Copy all of the \fB\.gem\fR files needed to run the application into the \fBvendor/cache\fR directory\. In the future, when running \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR, use the gems in the cache in preference to the ones on \fBrubygems\.org\fR\. . .SH "GIT AND PATH GEMS" The \fBbundle cache\fR command can also package \fB:git\fR and \fB:path\fR dependencies besides \.gem files\. This needs to be explicitly enabled via the \fB\-\-all\fR option\. Once used, the \fB\-\-all\fR option will be remembered\. @@ -22,7 +22,7 @@ The \fBbundle cache\fR command can also package \fB:git\fR and \fB:path\fR depen When using gems that have different packages for different platforms, Bundler supports caching of gems for other platforms where the Gemfile has been resolved (i\.e\. present in the lockfile) in \fBvendor/cache\fR\. This needs to be enabled via the \fB\-\-all\-platforms\fR option\. This setting will be remembered in your local bundler configuration\. . .SH "REMOTE FETCHING" -By default, if you run \fBbundle install(1)\fR](bundle\-install\.1\.html) after running bundle cache(1) \fIbundle\-cache\.1\.html\fR, bundler will still connect to \fBrubygems\.org\fR to check whether a platform\-specific gem exists for any of the gems in \fBvendor/cache\fR\. +By default, if you run \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR after running bundle cache(1) \fIbundle\-cache\.1\.html\fR, bundler will still connect to \fBrubygems\.org\fR to check whether a platform\-specific gem exists for any of the gems in \fBvendor/cache\fR\. . .P For instance, consider this Gemfile(5): diff --git a/lib/bundler/man/bundle-cache.1.ronn b/lib/bundler/man/bundle-cache.1.ronn index 46906d2b48..8112c2c551 100644 --- a/lib/bundler/man/bundle-cache.1.ronn +++ b/lib/bundler/man/bundle-cache.1.ronn @@ -10,7 +10,7 @@ alias: `package`, `pack` ## DESCRIPTION Copy all of the `.gem` files needed to run the application into the -`vendor/cache` directory. In the future, when running [bundle install(1)][bundle-install], +`vendor/cache` directory. In the future, when running [`bundle install(1)`](bundle-install.1.html), use the gems in the cache in preference to the ones on `rubygems.org`. ## GIT AND PATH GEMS @@ -29,7 +29,7 @@ bundler configuration. ## REMOTE FETCHING -By default, if you run `bundle install(1)`](bundle-install.1.html) after running +By default, if you run [`bundle install(1)`](bundle-install.1.html) after running [bundle cache(1)](bundle-cache.1.html), bundler will still connect to `rubygems.org` to check whether a platform-specific gem exists for any of the gems in `vendor/cache`. diff --git a/lib/bundler/man/bundle-check.1 b/lib/bundler/man/bundle-check.1 index f6aa6988c4..748a37e7d1 100644 --- a/lib/bundler/man/bundle-check.1 +++ b/lib/bundler/man/bundle-check.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-CHECK" "1" "October 2022" "" "" +.TH "BUNDLE\-CHECK" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems diff --git a/lib/bundler/man/bundle-clean.1 b/lib/bundler/man/bundle-clean.1 index 27e249cb64..af8f13cd89 100644 --- a/lib/bundler/man/bundle-clean.1 +++ b/lib/bundler/man/bundle-clean.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-CLEAN" "1" "October 2022" "" "" +.TH "BUNDLE\-CLEAN" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 08e08ecca9..4442f33105 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-CONFIG" "1" "October 2022" "" "" +.TH "BUNDLE\-CONFIG" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-config\fR \- Set bundler configuration options @@ -39,7 +39,7 @@ Bundler default config .IP "" 0 . .P -Executing \fBbundle config list\fR with will print a list of all bundler configuration for the current bundle, and where that configuration was set\. +Executing \fBbundle config list\fR will print a list of all bundler configuration for the current bundle, and where that configuration was set\. . .P Executing \fBbundle config get <name>\fR will print the value of that configuration setting, and where it was set\. @@ -284,9 +284,6 @@ The following is a list of all configuration keys and their purpose\. You can le \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 -\fBsuppress_install_using_messages\fR (\fBBUNDLE_SUPPRESS_INSTALL_USING_MESSAGES\fR): Avoid printing \fBUsing \.\.\.\fR messages during installation when the version of a gem has not changed\. -. -.IP "\(bu" 4 \fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR): The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\. . .IP "\(bu" 4 diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index 6f9edc9c39..adc273ec62 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -19,7 +19,7 @@ Bundler loads configuration settings in this order: 3. Global config (`~/.bundle/config`) 4. Bundler default config -Executing `bundle config list` with will print a list of all bundler +Executing `bundle config list` will print a list of all bundler configuration for the current bundle, and where that configuration was set. @@ -265,9 +265,6 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). * `ssl_verify_mode` (`BUNDLE_SSL_VERIFY_MODE`): The SSL verification mode Bundler uses when making HTTPS requests. Defaults to verify peer. -* `suppress_install_using_messages` (`BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES`): - Avoid printing `Using ...` messages during installation when the version of - a gem has not changed. * `system_bindir` (`BUNDLE_SYSTEM_BINDIR`): The location where RubyGems installs binstubs. Defaults to `Gem.bindir`. * `timeout` (`BUNDLE_TIMEOUT`): diff --git a/lib/bundler/man/bundle-console.1 b/lib/bundler/man/bundle-console.1 index c9463e372c..24fff46cec 100644 --- a/lib/bundler/man/bundle-console.1 +++ b/lib/bundler/man/bundle-console.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-CONSOLE" "1" "October 2022" "" "" +.TH "BUNDLE\-CONSOLE" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-console\fR \- Deprecated way to open an IRB session with the bundle pre\-loaded diff --git a/lib/bundler/man/bundle-doctor.1 b/lib/bundler/man/bundle-doctor.1 index dc5f5cf27e..57da8216cb 100644 --- a/lib/bundler/man/bundle-doctor.1 +++ b/lib/bundler/man/bundle-doctor.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-DOCTOR" "1" "October 2022" "" "" +.TH "BUNDLE\-DOCTOR" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-doctor\fR \- Checks the bundle for common problems diff --git a/lib/bundler/man/bundle-exec.1 b/lib/bundler/man/bundle-exec.1 index 1b3ad11395..852788db7a 100644 --- a/lib/bundler/man/bundle-exec.1 +++ b/lib/bundler/man/bundle-exec.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-EXEC" "1" "October 2022" "" "" +.TH "BUNDLE\-EXEC" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-exec\fR \- Execute a command in the context of the bundle @@ -74,13 +74,13 @@ Finally, \fBbundle exec\fR also implicitly modifies \fBGemfile\.lock\fR if the l By default, when attempting to \fBbundle exec\fR to a file with a ruby shebang, Bundler will \fBKernel\.load\fR that file instead of using \fBKernel\.exec\fR\. For the vast majority of cases, this is a performance improvement\. In a rare few cases, this could cause some subtle side\-effects (such as dependence on the exact contents of \fB$0\fR or \fB__FILE__\fR) and the optimization can be disabled by enabling the \fBdisable_exec_load\fR setting\. . .SS "Shelling out" -Any Ruby code that opens a subshell (like \fBsystem\fR, backticks, or \fB%x{}\fR) will automatically use the current Bundler environment\. If you need to shell out to a Ruby command that is not part of your current bundle, use the \fBwith_clean_env\fR method with a block\. Any subshells created inside the block will be given the environment present before Bundler was activated\. For example, Homebrew commands run Ruby, but don\'t work inside a bundle: +Any Ruby code that opens a subshell (like \fBsystem\fR, backticks, or \fB%x{}\fR) will automatically use the current Bundler environment\. If you need to shell out to a Ruby command that is not part of your current bundle, use the \fBwith_unbundled_env\fR method with a block\. Any subshells created inside the block will be given the environment present before Bundler was activated\. For example, Homebrew commands run Ruby, but don\'t work inside a bundle: . .IP "" 4 . .nf -Bundler\.with_clean_env do +Bundler\.with_unbundled_env do `brew install wget` end . @@ -89,13 +89,13 @@ end .IP "" 0 . .P -Using \fBwith_clean_env\fR is also necessary if you are shelling out to a different bundle\. Any Bundler commands run in a subshell will inherit the current Gemfile, so commands that need to run in the context of a different bundle also need to use \fBwith_clean_env\fR\. +Using \fBwith_unbundled_env\fR is also necessary if you are shelling out to a different bundle\. Any Bundler commands run in a subshell will inherit the current Gemfile, so commands that need to run in the context of a different bundle also need to use \fBwith_unbundled_env\fR\. . .IP "" 4 . .nf -Bundler\.with_clean_env do +Bundler\.with_unbundled_env do Dir\.chdir "/other/bundler/project" do `bundle exec \./script` end diff --git a/lib/bundler/man/bundle-exec.1.ronn b/lib/bundler/man/bundle-exec.1.ronn index 5f5e78ed12..05948095e2 100644 --- a/lib/bundler/man/bundle-exec.1.ronn +++ b/lib/bundler/man/bundle-exec.1.ronn @@ -84,20 +84,20 @@ the `disable_exec_load` setting. Any Ruby code that opens a subshell (like `system`, backticks, or `%x{}`) will automatically use the current Bundler environment. If you need to shell out to a Ruby command that is not part of your current bundle, use the -`with_clean_env` method with a block. Any subshells created inside the block +`with_unbundled_env` method with a block. Any subshells created inside the block will be given the environment present before Bundler was activated. For example, Homebrew commands run Ruby, but don't work inside a bundle: - Bundler.with_clean_env do + Bundler.with_unbundled_env do `brew install wget` end -Using `with_clean_env` is also necessary if you are shelling out to a different +Using `with_unbundled_env` is also necessary if you are shelling out to a different bundle. Any Bundler commands run in a subshell will inherit the current Gemfile, so commands that need to run in the context of a different bundle also -need to use `with_clean_env`. +need to use `with_unbundled_env`. - Bundler.with_clean_env do + Bundler.with_unbundled_env do Dir.chdir "/other/bundler/project" do `bundle exec ./script` end diff --git a/lib/bundler/man/bundle-gem.1 b/lib/bundler/man/bundle-gem.1 index b70cfbccd8..8339b727ce 100644 --- a/lib/bundler/man/bundle-gem.1 +++ b/lib/bundler/man/bundle-gem.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-GEM" "1" "October 2022" "" "" +.TH "BUNDLE\-GEM" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem @@ -31,41 +31,32 @@ The generated project skeleton can be customized with OPTIONS, as explained belo . .SH "OPTIONS" . -.TP -\fB\-\-exe\fR or \fB\-b\fR or \fB\-\-bin\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\-\-exe\fR or \fB\-b\fR or \fB\-\-bin\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)\. +.IP "\(bu" 4 +\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\. +.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\. . -.TP -\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\-\-no\-coc\fR: Do not create a \fBCODE_OF_CONDUCT\.md\fR (overrides \fB\-\-coc\fR specified in the global config)\. . -.TP -\fB\-\-ext\fR -Add boilerplate for C extension code to the generated project\. This behavior is disabled by default\. +.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\. . -.TP -\fB\-\-no\-ext\fR -Do not add C extension code (overrides \fB\-\-ext\fR specified in the global config)\. +.IP "\(bu" 4 +\fB\-\-no\-ext\fR: Do not add extension code (overrides \fB\-\-ext\fR specified in the global config)\. . -.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\. +.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\. . -.TP -\fB\-\-no\-mit\fR -Do not create a \fBLICENSE\.txt\fR (overrides \fB\-\-mit\fR specified in the global config)\. +.IP "\(bu" 4 +\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 "\(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: . .IP When Bundler is configured to generate tests, this defaults to Bundler\'s global config setting \fBgem\.test\fR\. @@ -76,9 +67,8 @@ When Bundler is configured to not generate tests, an interactive prompt will be .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\. . -.TP -\fB\-\-ci\fR, \fB\-\-ci=github\fR, \fB\-\-ci=travis\fR, \fB\-\-ci=gitlab\fR, \fB\-\-ci=circle\fR -Specify the continuous integration service that Bundler should use when generating the project\. Acceptable values are \fBgithub\fR, \fBtravis\fR, \fBgitlab\fR and \fBcircle\fR\. A configuration file will be generated in the project directory\. Given no option is specified: +.IP "\(bu" 4 +\fB\-\-ci\fR, \fB\-\-ci=github\fR, \fB\-\-ci=gitlab\fR, \fB\-\-ci=circle\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\. @@ -89,9 +79,8 @@ When Bundler is configured to not generate CI files, an interactive prompt will .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\. . -.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 "\(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: . .IP When Bundler is configured to add a linter, this defaults to Bundler\'s global config setting \fBgem\.linter\fR\. @@ -102,9 +91,10 @@ When Bundler is configured not to add a linter, an interactive prompt will be di .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\. . -.TP -\fB\-e\fR, \fB\-\-edit[=EDITOR]\fR -Open the resulting GEM_NAME\.gemspec in EDITOR, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\. +.IP "\(bu" 4 +\fB\-e\fR, \fB\-\-edit[=EDITOR]\fR: Open the resulting GEM_NAME\.gemspec in EDITOR, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\. +. +.IP "" 0 . .SH "SEE ALSO" . diff --git a/lib/bundler/man/bundle-gem.1.ronn b/lib/bundler/man/bundle-gem.1.ronn index 61c741fb24..46fa2f179f 100644 --- a/lib/bundler/man/bundle-gem.1.ronn +++ b/lib/bundler/man/bundle-gem.1.ronn @@ -41,12 +41,12 @@ configuration file using the following names: Do not create a `CODE_OF_CONDUCT.md` (overrides `--coc` specified in the global config). -* `--ext`: - Add boilerplate for C extension code to the generated project. This behavior +* `--ext=c`, `--ext=rust` + Add boilerplate for C or Rust (currently [magnus](https://docs.rs/magnus) based) extension code to the generated project. This behavior is disabled by default. * `--no-ext`: - Do not add C extension code (overrides `--ext` specified in the global + Do not add extension code (overrides `--ext` specified in the global config). * `--mit`: @@ -76,9 +76,9 @@ configuration file using the following names: the answer will be saved in Bundler's global config for future `bundle gem` use. -* `--ci`, `--ci=github`, `--ci=travis`, `--ci=gitlab`, `--ci=circle`: +* `--ci`, `--ci=github`, `--ci=gitlab`, `--ci=circle`: Specify the continuous integration service that Bundler should use when - generating the project. Acceptable values are `github`, `travis`, `gitlab` + generating the project. Acceptable values are `github`, `gitlab` and `circle`. A configuration file will be generated in the project directory. Given no option is specified: diff --git a/lib/bundler/man/bundle-help.1 b/lib/bundler/man/bundle-help.1 index bf378b0950..9787c2d49f 100644 --- a/lib/bundler/man/bundle-help.1 +++ b/lib/bundler/man/bundle-help.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-HELP" "1" "October 2022" "" "" +.TH "BUNDLE\-HELP" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-help\fR \- Displays detailed help for each subcommand diff --git a/lib/bundler/man/bundle-info.1 b/lib/bundler/man/bundle-info.1 index 9445aece25..2cced71520 100644 --- a/lib/bundler/man/bundle-info.1 +++ b/lib/bundler/man/bundle-info.1 @@ -1,16 +1,16 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-INFO" "1" "October 2022" "" "" +.TH "BUNDLE\-INFO" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-info\fR \- Show information for the given gem in your bundle . .SH "SYNOPSIS" -\fBbundle info\fR [GEM] [\-\-path] +\fBbundle info\fR [GEM_NAME] [\-\-path] . .SH "DESCRIPTION" -Print the basic information about the provided GEM such as homepage, version, path and summary\. +Given a gem name present in your bundle, print the basic information about it such as homepage, version, path and summary\. . .SH "OPTIONS" . diff --git a/lib/bundler/man/bundle-info.1.ronn b/lib/bundler/man/bundle-info.1.ronn index 47e457aa3c..cecdeb564f 100644 --- a/lib/bundler/man/bundle-info.1.ronn +++ b/lib/bundler/man/bundle-info.1.ronn @@ -3,13 +3,13 @@ bundle-info(1) -- Show information for the given gem in your bundle ## SYNOPSIS -`bundle info` [GEM] +`bundle info` [GEM_NAME] [--path] ## DESCRIPTION -Print the basic information about the provided GEM such as homepage, version, -path and summary. +Given a gem name present in your bundle, print the basic information about it + such as homepage, version, path and summary. ## OPTIONS diff --git a/lib/bundler/man/bundle-init.1 b/lib/bundler/man/bundle-init.1 index b80652d189..c7a9a155b5 100644 --- a/lib/bundler/man/bundle-init.1 +++ b/lib/bundler/man/bundle-init.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-INIT" "1" "October 2022" "" "" +.TH "BUNDLE\-INIT" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-init\fR \- Generates a Gemfile into the current working directory @@ -18,6 +18,10 @@ Init generates a default [\fBGemfile(5)\fR][Gemfile(5)] in the current working d \fB\-\-gemspec\fR Use the specified \.gemspec to create the [\fBGemfile(5)\fR][Gemfile(5)] . +.TP +\fB\-\-gemfile\fR +Use the specified name for the gemfile instead of \fBGemfile\fR +. .SH "FILES" Included in the default [\fBGemfile(5)\fR][Gemfile(5)] generated is the line \fB# frozen_string_literal: true\fR\. This is a magic comment supported for the first time in Ruby 2\.3\. The presence of this line results in all string literals in the file being implicitly frozen\. . diff --git a/lib/bundler/man/bundle-init.1.ronn b/lib/bundler/man/bundle-init.1.ronn index 9d3d97deea..7d3cede1f6 100644 --- a/lib/bundler/man/bundle-init.1.ronn +++ b/lib/bundler/man/bundle-init.1.ronn @@ -16,6 +16,8 @@ created [`Gemfile(5)`][Gemfile(5)]. * `--gemspec`: Use the specified .gemspec to create the [`Gemfile(5)`][Gemfile(5)] +* `--gemfile`: + Use the specified name for the gemfile instead of `Gemfile` ## FILES diff --git a/lib/bundler/man/bundle-inject.1 b/lib/bundler/man/bundle-inject.1 index bd440eee65..9e25c29085 100644 --- a/lib/bundler/man/bundle-inject.1 +++ b/lib/bundler/man/bundle-inject.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-INJECT" "1" "October 2022" "" "" +.TH "BUNDLE\-INJECT" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1 index 34bb58b53d..337683af06 100644 --- a/lib/bundler/man/bundle-install.1 +++ b/lib/bundler/man/bundle-install.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-INSTALL" "1" "October 2022" "" "" +.TH "BUNDLE\-INSTALL" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-install\fR \- Install the dependencies specified in your Gemfile diff --git a/lib/bundler/man/bundle-list.1 b/lib/bundler/man/bundle-list.1 index d82093ad4a..1680e6007a 100644 --- a/lib/bundler/man/bundle-list.1 +++ b/lib/bundler/man/bundle-list.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-LIST" "1" "October 2022" "" "" +.TH "BUNDLE\-LIST" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-list\fR \- List all the gems in the bundle diff --git a/lib/bundler/man/bundle-lock.1 b/lib/bundler/man/bundle-lock.1 index 65586c89c5..8722c44b3d 100644 --- a/lib/bundler/man/bundle-lock.1 +++ b/lib/bundler/man/bundle-lock.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-LOCK" "1" "October 2022" "" "" +.TH "BUNDLE\-LOCK" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-lock\fR \- Creates / Updates a lockfile without installing diff --git a/lib/bundler/man/bundle-open.1 b/lib/bundler/man/bundle-open.1 index b2327fa9f1..3513f0d09b 100644 --- a/lib/bundler/man/bundle-open.1 +++ b/lib/bundler/man/bundle-open.1 @@ -1,13 +1,13 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-OPEN" "1" "October 2022" "" "" +.TH "BUNDLE\-OPEN" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-open\fR \- Opens the source directory for a gem in your bundle . .SH "SYNOPSIS" -\fBbundle open\fR [GEM] +\fBbundle open\fR [GEM] [\-\-path=PATH] . .SH "DESCRIPTION" Opens the source directory of the provided GEM in your editor\. @@ -30,3 +30,23 @@ bundle open \'rack\' . .P Will open the source directory for the \'rack\' gem in your bundle\. +. +.IP "" 4 +. +.nf + +bundle open \'rack\' \-\-path \'README\.md\' +. +.fi +. +.IP "" 0 +. +.P +Will open the README\.md file of the \'rack\' gem source in your bundle\. +. +.SH "OPTIONS" +. +.TP +\fB\-\-path\fR +Specify GEM source relative path to open\. + diff --git a/lib/bundler/man/bundle-open.1.ronn b/lib/bundler/man/bundle-open.1.ronn index 497beac93f..a857f3a965 100644 --- a/lib/bundler/man/bundle-open.1.ronn +++ b/lib/bundler/man/bundle-open.1.ronn @@ -3,7 +3,7 @@ bundle-open(1) -- Opens the source directory for a gem in your bundle ## SYNOPSIS -`bundle open` [GEM] +`bundle open` [GEM] [--path=PATH] ## DESCRIPTION @@ -17,3 +17,11 @@ Example: bundle open 'rack' Will open the source directory for the 'rack' gem in your bundle. + + bundle open 'rack' --path 'README.md' + +Will open the README.md file of the 'rack' gem source in your bundle. + +## OPTIONS +* `--path`: + Specify GEM source relative path to open. diff --git a/lib/bundler/man/bundle-outdated.1 b/lib/bundler/man/bundle-outdated.1 index 896155212f..129ff00f58 100644 --- a/lib/bundler/man/bundle-outdated.1 +++ b/lib/bundler/man/bundle-outdated.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-OUTDATED" "1" "October 2022" "" "" +.TH "BUNDLE\-OUTDATED" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-outdated\fR \- List installed gems with newer versions available @@ -83,9 +83,10 @@ If the regular output shows the following: . .nf -* faker (newest 1\.6\.6, installed 1\.6\.5, requested ~> 1\.4) in groups "development, test" -* hashie (newest 3\.4\.6, installed 1\.2\.0, requested = 1\.2\.0) in groups "default" -* headless (newest 2\.3\.1, installed 2\.2\.3) in groups "test" +* Gem Current Latest Requested Groups +* faker 1\.6\.5 1\.6\.6 ~> 1\.4 development, test +* hashie 1\.2\.0 3\.4\.6 = 1\.2\.0 default +* headless 2\.2\.3 2\.3\.1 = 2\.2\.3 test . .fi . @@ -98,7 +99,8 @@ If the regular output shows the following: . .nf -* hashie (newest 3\.4\.6, installed 1\.2\.0, requested = 1\.2\.0) in groups "default" +* Gem Current Latest Requested Groups +* hashie 1\.2\.0 3\.4\.6 = 1\.2\.0 default . .fi . @@ -111,7 +113,8 @@ If the regular output shows the following: . .nf -* headless (newest 2\.3\.1, installed 2\.2\.3) in groups "test" +* Gem Current Latest Requested Groups +* headless 2\.2\.3 2\.3\.1 = 2\.2\.3 test . .fi . @@ -124,7 +127,8 @@ If the regular output shows the following: . .nf -* faker (newest 1\.6\.6, installed 1\.6\.5, requested ~> 1\.4) in groups "development, test" +* Gem Current Latest Requested Groups +* faker 1\.6\.5 1\.6\.6 ~> 1\.4 development, test . .fi . @@ -137,8 +141,8 @@ Filter options can be combined\. \fB\-\-filter\-minor\fR and \fB\-\-filter\-patc . .nf -* faker (newest 1\.6\.6, installed 1\.6\.5, requested ~> 1\.4) in groups "development, test" -* headless (newest 2\.3\.1, installed 2\.2\.3) in groups "test" +* Gem Current Latest Requested Groups +* faker 1\.6\.5 1\.6\.6 ~> 1\.4 development, test . .fi . diff --git a/lib/bundler/man/bundle-outdated.1.ronn b/lib/bundler/man/bundle-outdated.1.ronn index 04096ffbb6..27bf21ab9d 100644 --- a/lib/bundler/man/bundle-outdated.1.ronn +++ b/lib/bundler/man/bundle-outdated.1.ronn @@ -78,25 +78,28 @@ in the output. If the regular output shows the following: - * faker (newest 1.6.6, installed 1.6.5, requested ~> 1.4) in groups "development, test" - * hashie (newest 3.4.6, installed 1.2.0, requested = 1.2.0) in groups "default" - * headless (newest 2.3.1, installed 2.2.3) in groups "test" + * Gem Current Latest Requested Groups + * faker 1.6.5 1.6.6 ~> 1.4 development, test + * hashie 1.2.0 3.4.6 = 1.2.0 default + * headless 2.2.3 2.3.1 = 2.2.3 test `--filter-major` would only show: - * hashie (newest 3.4.6, installed 1.2.0, requested = 1.2.0) in groups "default" + * Gem Current Latest Requested Groups + * hashie 1.2.0 3.4.6 = 1.2.0 default `--filter-minor` would only show: - * headless (newest 2.3.1, installed 2.2.3) in groups "test" + * Gem Current Latest Requested Groups + * headless 2.2.3 2.3.1 = 2.2.3 test `--filter-patch` would only show: - * faker (newest 1.6.6, installed 1.6.5, requested ~> 1.4) in groups "development, test" + * Gem Current Latest Requested Groups + * faker 1.6.5 1.6.6 ~> 1.4 development, test Filter options can be combined. `--filter-minor` and `--filter-patch` would show: - * faker (newest 1.6.6, installed 1.6.5, requested ~> 1.4) in groups "development, test" - * headless (newest 2.3.1, installed 2.2.3) in groups "test" - + * Gem Current Latest Requested Groups + * faker 1.6.5 1.6.6 ~> 1.4 development, test Combining all three `filter` options would be the same result as providing none of them. diff --git a/lib/bundler/man/bundle-platform.1 b/lib/bundler/man/bundle-platform.1 index 41db8f72f0..5021c46b4c 100644 --- a/lib/bundler/man/bundle-platform.1 +++ b/lib/bundler/man/bundle-platform.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-PLATFORM" "1" "October 2022" "" "" +.TH "BUNDLE\-PLATFORM" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-platform\fR \- Displays platform compatibility information diff --git a/lib/bundler/man/bundle-plugin.1 b/lib/bundler/man/bundle-plugin.1 index 3a08bf8c46..ec30e1d0fd 100644 --- a/lib/bundler/man/bundle-plugin.1 +++ b/lib/bundler/man/bundle-plugin.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-PLUGIN" "1" "October 2022" "" "" +.TH "BUNDLE\-PLUGIN" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-plugin\fR \- Manage Bundler plugins diff --git a/lib/bundler/man/bundle-pristine.1 b/lib/bundler/man/bundle-pristine.1 index 5f562a2e07..af81c48d2b 100644 --- a/lib/bundler/man/bundle-pristine.1 +++ b/lib/bundler/man/bundle-pristine.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-PRISTINE" "1" "October 2022" "" "" +.TH "BUNDLE\-PRISTINE" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-pristine\fR \- Restores installed gems to their pristine condition diff --git a/lib/bundler/man/bundle-remove.1 b/lib/bundler/man/bundle-remove.1 index 128ac64f9f..d86cf134bd 100644 --- a/lib/bundler/man/bundle-remove.1 +++ b/lib/bundler/man/bundle-remove.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-REMOVE" "1" "October 2022" "" "" +.TH "BUNDLE\-REMOVE" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-remove\fR \- Removes gems from the Gemfile diff --git a/lib/bundler/man/bundle-show.1 b/lib/bundler/man/bundle-show.1 index 1d747fd5f4..aa91176bf2 100644 --- a/lib/bundler/man/bundle-show.1 +++ b/lib/bundler/man/bundle-show.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-SHOW" "1" "October 2022" "" "" +.TH "BUNDLE\-SHOW" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem diff --git a/lib/bundler/man/bundle-update.1 b/lib/bundler/man/bundle-update.1 index 15e0517737..e4e10ad23b 100644 --- a/lib/bundler/man/bundle-update.1 +++ b/lib/bundler/man/bundle-update.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-UPDATE" "1" "October 2022" "" "" +.TH "BUNDLE\-UPDATE" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-update\fR \- Update your gems to the latest available versions diff --git a/lib/bundler/man/bundle-version.1 b/lib/bundler/man/bundle-version.1 index 3721cf9c7a..5e3ed44600 100644 --- a/lib/bundler/man/bundle-version.1 +++ b/lib/bundler/man/bundle-version.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-VERSION" "1" "October 2022" "" "" +.TH "BUNDLE\-VERSION" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-version\fR \- Prints Bundler version information diff --git a/lib/bundler/man/bundle-viz.1 b/lib/bundler/man/bundle-viz.1 index 3508c09bcc..d5330ec754 100644 --- a/lib/bundler/man/bundle-viz.1 +++ b/lib/bundler/man/bundle-viz.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE\-VIZ" "1" "October 2022" "" "" +.TH "BUNDLE\-VIZ" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1 index c2e7e4c5c4..99c65a72b5 100644 --- a/lib/bundler/man/bundle.1 +++ b/lib/bundler/man/bundle.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BUNDLE" "1" "October 2022" "" "" +.TH "BUNDLE" "1" "August 2023" "" "" . .SH "NAME" \fBbundle\fR \- Ruby Dependency Management diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index 6fcffb9cc7..352fa0f545 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "GEMFILE" "5" "October 2022" "" "" +.TH "GEMFILE" "5" "August 2023" "" "" . .SH "NAME" \fBGemfile\fR \- A format for describing gem dependencies for Ruby programs @@ -85,6 +85,19 @@ ruby "3\.1\.2" . .IP "" 0 . +.P +If you wish to derive your Ruby version from a version file (ie \.ruby\-version), you can use the \fBfile\fR option instead\. +. +.IP "" 4 +. +.nf + +ruby file: "\.ruby\-version" +. +.fi +. +.IP "" 0 +. .SS "ENGINE" Each application \fImay\fR specify a Ruby engine\. If an engine is specified, an engine version \fImust\fR also be specified\. . @@ -702,7 +715,7 @@ If you wish to use Bundler to help install dependencies for a gem while it is be The \fBgemspec\fR method adds any runtime dependencies as gem requirements in the default group\. It also adds development dependencies as gem requirements in the \fBdevelopment\fR group\. Finally, it adds a gem requirement on your project (\fBpath: \'\.\'\fR)\. In conjunction with \fBBundler\.setup\fR, this allows you to require project files in your test code as you would if the project were installed as a gem; you need not manipulate the load path manually or require project files via relative paths\. . .P -The \fBgemspec\fR method supports optional \fB:path\fR, \fB:glob\fR, \fB:name\fR, and \fB:development_group\fR options, which control where bundler looks for the \fB\.gemspec\fR, the glob it uses to look for the gemspec (defaults to: "{,\fI,\fR/*}\.gemspec"), what named \fB\.gemspec\fR it uses (if more than one is present), and which group development dependencies are included in\. +The \fBgemspec\fR method supports optional \fB:path\fR, \fB:glob\fR, \fB:name\fR, and \fB:development_group\fR options, which control where bundler looks for the \fB\.gemspec\fR, the glob it uses to look for the gemspec (defaults to: \fB{,*,*/*}\.gemspec\fR), what named \fB\.gemspec\fR it uses (if more than one is present), and which group development dependencies are included in\. . .P When a \fBgemspec\fR dependency encounters version conflicts during resolution, the local version under development will always be selected \-\- even if there are remote versions that better match other requirements for the \fBgemspec\fR gem\. diff --git a/lib/bundler/man/gemfile.5.ronn b/lib/bundler/man/gemfile.5.ronn index a3affc30cc..6749c33f59 100644 --- a/lib/bundler/man/gemfile.5.ronn +++ b/lib/bundler/man/gemfile.5.ronn @@ -69,6 +69,11 @@ should be the Ruby version that the engine is compatible with. ruby "3.1.2" +If you wish to derive your Ruby version from a version file (ie .ruby-version), +you can use the `file` option instead. + + ruby file: ".ruby-version" + ### ENGINE Each application _may_ specify a Ruby engine. If an engine is specified, an @@ -514,7 +519,7 @@ paths. The `gemspec` method supports optional `:path`, `:glob`, `:name`, and `:development_group` options, which control where bundler looks for the `.gemspec`, the glob it uses to look -for the gemspec (defaults to: "{,*,*/*}.gemspec"), what named `.gemspec` it uses +for the gemspec (defaults to: `{,*,*/*}.gemspec`), what named `.gemspec` it uses (if more than one is present), and which group development dependencies are included in. When a `gemspec` dependency encounters version conflicts during resolution, the diff --git a/lib/bundler/mirror.rb b/lib/bundler/mirror.rb index a63b45b47d..9d437a0951 100644 --- a/lib/bundler/mirror.rb +++ b/lib/bundler/mirror.rb @@ -148,13 +148,11 @@ module Bundler class TCPSocketProbe def replies?(mirror) MirrorSockets.new(mirror).any? do |socket, address, timeout| - begin - socket.connect_nonblock(address) - rescue Errno::EINPROGRESS - wait_for_writtable_socket(socket, address, timeout) - rescue RuntimeError # Connection failed somehow, again - false - end + socket.connect_nonblock(address) + rescue Errno::EINPROGRESS + wait_for_writtable_socket(socket, address, timeout) + rescue RuntimeError # Connection failed somehow, again + false end end diff --git a/lib/bundler/plugin.rb b/lib/bundler/plugin.rb index 26458bd596..f3caff8963 100644 --- a/lib/bundler/plugin.rb +++ b/lib/bundler/plugin.rb @@ -15,7 +15,7 @@ module Bundler class UnknownSourceError < PluginError; end class PluginInstallError < PluginError; end - PLUGIN_FILE_NAME = "plugins.rb".freeze + PLUGIN_FILE_NAME = "plugins.rb" module_function diff --git a/lib/bundler/plugin/index.rb b/lib/bundler/plugin/index.rb index 6fbd036f38..a2d5eaa38a 100644 --- a/lib/bundler/plugin/index.rb +++ b/lib/bundler/plugin/index.rb @@ -146,7 +146,7 @@ module Bundler # @param [Boolean] is the index file global index def load_index(index_file, global = false) SharedHelpers.filesystem_access(index_file, :read) do |index_f| - valid_file = index_f && index_f.exist? && !index_f.size.zero? + valid_file = index_f&.exist? && !index_f.size.zero? break unless valid_file data = index_f.read diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb index 81ecafa470..c9ff12ce4b 100644 --- a/lib/bundler/plugin/installer.rb +++ b/lib/bundler/plugin/installer.rb @@ -83,8 +83,11 @@ module Bundler Bundler.configure_gem_home_and_path(Plugin.root) - definition = Definition.new(nil, deps, source_list, true) - install_definition(definition) + Bundler.settings.temporary(:deployment => false, :frozen => false) do + definition = Definition.new(nil, deps, source_list, true) + + install_definition(definition) + end end # Installs the plugins and deps from the provided specs and returns map of diff --git a/lib/bundler/remote_specification.rb b/lib/bundler/remote_specification.rb index 34d7fd116c..f626a3218e 100644 --- a/lib/bundler/remote_specification.rb +++ b/lib/bundler/remote_specification.rb @@ -29,12 +29,8 @@ module Bundler @platform = _remote_specification.platform end - def identifier - @__identifier ||= [name, version, @platform.to_s] - end - def full_name - if @platform == Gem::Platform::RUBY + @full_name ||= if @platform == Gem::Platform::RUBY "#{@name}-#{@version}" else "#{@name}-#{@version}-#{@platform}" @@ -106,7 +102,7 @@ module Bundler def _remote_specification @_remote_specification ||= @spec_fetcher.fetch_spec([@name, @version, @original_platform]) @_remote_specification || raise(GemspecError, "Gemspec data for #{full_name} was" \ - " missing from the server! Try installing with `--full-index` as a workaround.") + " missing from the server!") end def method_missing(method, *args, &blk) diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index c175ea4354..2ad35bc931 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -9,29 +9,37 @@ module Bundler class Resolver require_relative "vendored_pub_grub" require_relative "resolver/base" - require_relative "resolver/package" require_relative "resolver/candidate" require_relative "resolver/incompatibility" require_relative "resolver/root" include GemHelpers - def initialize(source_requirements, base, gem_version_promoter, additional_base_requirements) - @source_requirements = source_requirements - @base = Resolver::Base.new(base, additional_base_requirements) + def initialize(base, gem_version_promoter) + @source_requirements = base.source_requirements + @base = base @gem_version_promoter = gem_version_promoter end - def start(requirements, packages, exclude_specs: []) - exclude_specs.each do |spec| - remove_from_candidates(spec) - end + def start + @requirements = @base.requirements + @packages = @base.packages + + root, logger = setup_solver + + Bundler.ui.info "Resolving dependencies...", true + + solve_versions(:root => root, :logger => logger) + end + def setup_solver root = Resolver::Root.new(name_for_explicit_dependency_source) root_version = Resolver::Candidate.new(0) @all_specs = Hash.new do |specs, name| - specs[name] = source_for(name).specs.search(name).sort_by {|s| [s.version, s.platform.to_s] } + specs[name] = source_for(name).specs.search(name).reject do |s| + s.dependencies.any? {|d| d.name == name && !d.requirement.satisfied_by?(s.version) } # ignore versions that depend on themselves incorrectly + end.sort_by {|s| [s.version, s.platform.to_s] } end @sorted_versions = Hash.new do |candidates, package| @@ -42,48 +50,51 @@ module Bundler end end - root_dependencies = prepare_dependencies(requirements, packages) + root_dependencies = prepare_dependencies(@requirements, @packages) @cached_dependencies = Hash.new do |dependencies, package| dependencies[package] = if package.root? { root_version => root_dependencies } else Hash.new do |versions, version| - versions[version] = to_dependency_hash(version.dependencies, packages) + versions[version] = to_dependency_hash(version.dependencies.reject {|d| d.name == package.name }, @packages) end end end logger = Bundler::UI::Shell.new logger.level = debug? ? "debug" : "warn" + + [root, logger] + end + + def solve_versions(root:, logger:) solver = PubGrub::VersionSolver.new(:source => self, :root => root, :logger => logger) - before_resolution result = solver.solve - after_resolution result.map {|package, version| version.to_specs(package) }.flatten.uniq rescue PubGrub::SolveFailure => e incompatibility = e.incompatibility - names_to_unlock = [] - extended_explanation = nil + names_to_unlock, names_to_allow_prereleases_for, extended_explanation = find_names_to_relax(incompatibility) - while incompatibility.conflict? - cause = incompatibility.cause - incompatibility = cause.incompatibility + names_to_relax = names_to_unlock + names_to_allow_prereleases_for - incompatibility.terms.each do |term| - name = term.package.name - names_to_unlock << name if base_requirements[name] + if names_to_relax.any? + if names_to_unlock.any? + Bundler.ui.debug "Found conflicts with locked dependencies. Will retry with #{names_to_unlock.join(", ")} unlocked...", true - no_versions_incompat = [cause.incompatibility, cause.satisfier].find {|incompat| incompat.cause.is_a?(PubGrub::Incompatibility::NoVersions) } - next unless no_versions_incompat + @base.unlock_names(names_to_unlock) + end - extended_explanation = no_versions_incompat.extended_explanation + if names_to_allow_prereleases_for.any? + Bundler.ui.debug "Found conflicts with dependencies with prereleases. Will retrying considering prereleases for #{names_to_allow_prereleases_for.join(", ")}...", true + + @base.include_prereleases(names_to_allow_prereleases_for) end - end - if names_to_unlock.any? - @base.unlock_names(names_to_unlock) + root, logger = setup_solver + + Bundler.ui.debug "Retrying resolution...", true retry end @@ -97,6 +108,35 @@ module Bundler raise SolveFailure.new(explanation) end + def find_names_to_relax(incompatibility) + names_to_unlock = [] + names_to_allow_prereleases_for = [] + extended_explanation = nil + + while incompatibility.conflict? + cause = incompatibility.cause + incompatibility = cause.incompatibility + + incompatibility.terms.each do |term| + package = term.package + name = package.name + + if base_requirements[name] + names_to_unlock << name + elsif package.ignores_prereleases? + names_to_allow_prereleases_for << name + end + + no_versions_incompat = [cause.incompatibility, cause.satisfier].find {|incompat| incompat.cause.is_a?(PubGrub::Incompatibility::NoVersions) } + next unless no_versions_incompat + + extended_explanation = no_versions_incompat.extended_explanation + end + end + + [names_to_unlock.uniq, names_to_allow_prereleases_for.uniq, extended_explanation] + end + def parse_dependency(package, dependency) range = if repository_for(package).is_a?(Source::Gemspec) PubGrub::VersionRange.any @@ -117,19 +157,19 @@ module Bundler cause = PubGrub::Incompatibility::NoVersions.new(unsatisfied_term) name = package.name constraint = unsatisfied_term.constraint - requirement = Gem::Requirement.new(constraint.constraint_string.split(",")) + constraint_string = constraint.constraint_string + requirements = constraint_string.split(" OR ").map {|req| Gem::Requirement.new(req.split(",")) } - if name == "bundler" + if name == "bundler" && bundler_pinned_to_current_version? custom_explanation = "the current Bundler version (#{Bundler::VERSION}) does not satisfy #{constraint}" - extended_explanation = bundler_not_found_message(requirement) + extended_explanation = bundler_not_found_message(requirements) else - specs_matching_other_platforms = filter_matching_specs(@all_specs[name], requirement) + specs_matching_other_platforms = filter_matching_specs(@all_specs[name], requirements) platforms_explanation = specs_matching_other_platforms.any? ? " for any resolution platforms (#{package.platforms.join(", ")})" : "" custom_explanation = "#{constraint} could not be found in #{repository_for(package)}#{platforms_explanation}" - dependency = Dependency.new(name, requirement) - label = SharedHelpers.pretty_dependency(dependency) + label = "#{name} (#{constraint_string})" extended_explanation = other_specs_matching_message(specs_matching_other_platforms, label) if specs_matching_other_platforms.any? end @@ -144,24 +184,10 @@ module Bundler false end - def before_resolution - Bundler.ui.info "Resolving dependencies...", debug? - end - - def after_resolution - Bundler.ui.info "" - end - def incompatibilities_for(package, version) package_deps = @cached_dependencies[package] sorted_versions = @sorted_versions[package] package_deps[version].map do |dep_package, dep_constraint| - unless dep_constraint - # falsey indicates this dependency was invalid - cause = PubGrub::Incompatibility::InvalidDependency.new(dep_package, dep_constraint.constraint_string) - return [PubGrub::Incompatibility.new([PubGrub::Term.new(self_constraint, true)], :cause => cause)] - end - low = high = sorted_versions.index(version) # find version low such that all >= low share the same dep @@ -191,18 +217,25 @@ module Bundler self_constraint = PubGrub::VersionConstraint.new(package, :range => range) dep_term = PubGrub::Term.new(dep_constraint, false) + self_term = PubGrub::Term.new(self_constraint, true) custom_explanation = if dep_package.meta? && package.root? "current #{dep_package} version is #{dep_constraint.constraint_string}" end - PubGrub::Incompatibility.new([PubGrub::Term.new(self_constraint, true), dep_term], :cause => :dependency, :custom_explanation => custom_explanation) + PubGrub::Incompatibility.new([self_term, dep_term], :cause => :dependency, :custom_explanation => custom_explanation) end end def all_versions_for(package) name = package.name - results = @base[name] + @all_specs[name] + results = (@base[name] + filter_prereleases(@all_specs[name], package)).uniq {|spec| [spec.version.hash, spec.platform] } + + if name == "bundler" && !bundler_pinned_to_current_version? + bundler_spec = Gem.loaded_specs["bundler"] + results << bundler_spec if bundler_spec + end + locked_requirement = base_requirements[name] results = filter_matching_specs(results, locked_requirement) if locked_requirement @@ -213,7 +246,7 @@ module Bundler ruby_specs = select_best_platform_match(specs, Gem::Platform::RUBY) groups << Resolver::Candidate.new(version, :specs => ruby_specs) if ruby_specs.any? - next groups if platform_specs == ruby_specs + next groups if platform_specs == ruby_specs || package.force_ruby_platform? groups << Resolver::Candidate.new(version, :specs => platform_specs) @@ -227,6 +260,14 @@ module Bundler @source_requirements[name] || @source_requirements[:default] end + def default_bundler_source + @source_requirements[:default_bundler] + end + + def bundler_pinned_to_current_version? + !default_bundler_source.nil? + end + def name_for_explicit_dependency_source Bundler.default_gemfile.basename.to_s rescue StandardError @@ -265,8 +306,16 @@ module Bundler private - def filter_matching_specs(specs, requirement) - specs.select {| spec| requirement_satisfied_by?(requirement, spec) } + def filter_matching_specs(specs, requirements) + Array(requirements).flat_map do |requirement| + specs.select {| spec| requirement_satisfied_by?(requirement, spec) } + end + end + + def filter_prereleases(specs, package) + return specs unless package.ignores_prereleases? && specs.size > 1 + + specs.reject {|s| s.version.prerelease? } end def requirement_satisfied_by?(requirement, spec) @@ -289,16 +338,20 @@ module Bundler @base.base_requirements end - def remove_from_candidates(spec) - @base.delete(spec) - end - def prepare_dependencies(requirements, packages) to_dependency_hash(requirements, packages).map do |dep_package, dep_constraint| name = dep_package.name - next if dep_package.platforms.empty? + next [dep_package, dep_constraint] if name == "bundler" - next [dep_package, dep_constraint] unless versions_for(dep_package, dep_constraint.range).empty? + + versions = versions_for(dep_package, dep_constraint.range) + if versions.empty? && dep_package.ignores_prereleases? + @sorted_versions.delete(dep_package) + dep_package.consider_prereleases! + versions = versions_for(dep_package, dep_constraint.range) + end + next [dep_package, dep_constraint] unless versions.empty? + next unless dep_package.current_platform? raise_not_found!(dep_package) @@ -313,7 +366,8 @@ module Bundler def requirement_to_range(requirement) ranges = requirement.requirements.map do |(op, version)| - ver = Resolver::Candidate.new(version) + ver = Resolver::Candidate.new(version).generic! + platform_ver = Resolver::Candidate.new(version).platform_specific! case op when "~>" @@ -321,17 +375,17 @@ module Bundler bump = Resolver::Candidate.new(version.bump.to_s + ".A") PubGrub::VersionRange.new(:name => name, :min => ver, :max => bump, :include_min => true) when ">" - PubGrub::VersionRange.new(:min => ver) + PubGrub::VersionRange.new(:min => platform_ver) when ">=" PubGrub::VersionRange.new(:min => ver, :include_min => true) when "<" PubGrub::VersionRange.new(:max => ver) when "<=" - PubGrub::VersionRange.new(:max => ver, :include_max => true) + PubGrub::VersionRange.new(:max => platform_ver, :include_max => true) when "=" - PubGrub::VersionRange.new(:min => ver, :max => ver, :include_min => true, :include_max => true) + PubGrub::VersionRange.new(:min => ver, :max => platform_ver, :include_min => true, :include_max => true) when "!=" - PubGrub::VersionRange.new(:min => ver, :max => ver, :include_min => true, :include_max => true).invert + PubGrub::VersionRange.new(:min => ver, :max => platform_ver, :include_min => true, :include_max => true).invert else raise "bad version specifier: #{op}" end @@ -357,8 +411,9 @@ module Bundler end end - def bundler_not_found_message(conflict_dependency) - candidate_specs = filter_matching_specs(source_for(:default_bundler).specs.search("bundler"), conflict_dependency) + def bundler_not_found_message(conflict_dependencies) + candidate_specs = filter_matching_specs(default_bundler_source.specs.search("bundler"), conflict_dependencies) + if candidate_specs.any? target_version = candidate_specs.last.version new_command = [File.basename($PROGRAM_NAME), "_#{target_version}_", *ARGV].join(" ") diff --git a/lib/bundler/resolver/base.rb b/lib/bundler/resolver/base.rb index 78b798f4ec..e5c3763c3f 100644 --- a/lib/bundler/resolver/base.rb +++ b/lib/bundler/resolver/base.rb @@ -1,19 +1,45 @@ # frozen_string_literal: true +require_relative "package" + module Bundler class Resolver class Base - def initialize(base, additional_base_requirements) + attr_reader :packages, :requirements, :source_requirements + + def initialize(source_requirements, dependencies, base, platforms, options) + @source_requirements = source_requirements + @base = base - @additional_base_requirements = additional_base_requirements + + @packages = Hash.new do |hash, name| + hash[name] = Package.new(name, platforms, **options) + end + + @requirements = dependencies.map do |dep| + dep_platforms = dep.gem_platforms(platforms) + + # Dependencies scoped to external platforms are ignored + next if dep_platforms.empty? + + name = dep.name + + @packages[name] = Package.new(name, dep_platforms, **options.merge(:dependency => dep)) + + dep + end.compact end def [](name) @base[name] end - def delete(spec) - @base.delete(spec) + def delete(specs) + @base.delete(specs) + end + + def get_package(name) + @packages[name] end def base_requirements @@ -21,24 +47,59 @@ module Bundler end def unlock_names(names) - names.each do |name| - @base.delete_by_name(name) + indirect_pins = indirect_pins(names) + + if indirect_pins.any? + loosen_names(indirect_pins) + else + pins = pins(names) - @additional_base_requirements.reject! {|dep| dep.name == name } + if pins.any? + loosen_names(pins) + else + unrestrict_names(names) + end end + end - @base_requirements = nil + def include_prereleases(names) + names.each do |name| + get_package(name).consider_prereleases! + end end private + def indirect_pins(names) + names.select {|name| @base_requirements[name].exact? && @requirements.none? {|dep| dep.name == name } } + end + + def pins(names) + names.select {|name| @base_requirements[name].exact? } + end + + def loosen_names(names) + names.each do |name| + version = @base_requirements[name].requirements.first[1] + + @base_requirements[name] = Gem::Requirement.new(">= #{version}") + + @base.delete_by_name(name) + end + end + + def unrestrict_names(names) + names.each do |name| + @base_requirements.delete(name) + end + end + def build_base_requirements base_requirements = {} @base.each do |ls| req = Gem::Requirement.new(ls.version) base_requirements[ls.name] = req end - @additional_base_requirements.each {|d| base_requirements[d.name] = d.requirement } base_requirements end end diff --git a/lib/bundler/resolver/candidate.rb b/lib/bundler/resolver/candidate.rb index cf5691ccc7..e695ef08ee 100644 --- a/lib/bundler/resolver/candidate.rb +++ b/lib/bundler/resolver/candidate.rb @@ -26,9 +26,8 @@ module Bundler def initialize(version, specs: []) @spec_group = Resolver::SpecGroup.new(specs) - @platforms = specs.map(&:platform).sort_by(&:to_s).uniq @version = Gem::Version.new(version) - @ruby_only = @platforms == [Gem::Platform::RUBY] + @ruby_only = specs.map(&:platform).uniq == [Gem::Platform::RUBY] end def dependencies @@ -41,6 +40,18 @@ module Bundler @spec_group.to_specs(package.force_ruby_platform?) end + def generic! + @ruby_only = true + + self + end + + def platform_specific! + @ruby_only = false + + self + end + def prerelease? @version.prerelease? end @@ -53,27 +64,20 @@ module Bundler [@version, @ruby_only ? -1 : 1] end - def canonical? - !@spec_group.empty? - end - def <=>(other) return unless other.is_a?(self.class) - return @version <=> other.version unless canonical? && other.canonical? sort_obj <=> other.sort_obj end def ==(other) return unless other.is_a?(self.class) - return @version == other.version unless canonical? && other.canonical? sort_obj == other.sort_obj end def eql?(other) return unless other.is_a?(self.class) - return @version.eql?(other.version) unless canonical? || other.canonical? sort_obj.eql?(other.sort_obj) end @@ -83,9 +87,7 @@ module Bundler end def to_s - return @version.to_s if @platforms.empty? || @ruby_only - - "#{@version} (#{@platforms.join(", ")})" + @version.to_s end end end diff --git a/lib/bundler/resolver/package.rb b/lib/bundler/resolver/package.rb index 7d64632860..7499a75006 100644 --- a/lib/bundler/resolver/package.rb +++ b/lib/bundler/resolver/package.rb @@ -15,12 +15,13 @@ module Bundler class Package attr_reader :name, :platforms, :dependency, :locked_version - def initialize(name, platforms, locked_specs, unlock, dependency: nil) + def initialize(name, platforms, locked_specs:, unlock:, prerelease: false, dependency: nil) @name = name @platforms = platforms @locked_version = locked_specs[name].first&.version @unlock = unlock @dependency = dependency || Dependency.new(name, @locked_version) + @prerelease = @dependency.prerelease? || @locked_version&.prerelease? || prerelease ? :consider_first : :ignore end def to_s @@ -47,8 +48,16 @@ module Bundler @unlock.empty? || @unlock.include?(name) end + def ignores_prereleases? + @prerelease == :ignore + end + def prerelease_specified? - @dependency.prerelease? + @prerelease == :consider_first + end + + def consider_prereleases! + @prerelease = :consider_last end def force_ruby_platform? diff --git a/lib/bundler/ruby_dsl.rb b/lib/bundler/ruby_dsl.rb index 3b3a0583a5..d054969e8d 100644 --- a/lib/bundler/ruby_dsl.rb +++ b/lib/bundler/ruby_dsl.rb @@ -5,9 +5,15 @@ module Bundler def ruby(*ruby_version) options = ruby_version.last.is_a?(Hash) ? ruby_version.pop : {} ruby_version.flatten! + raise GemfileError, "Please define :engine_version" if options[:engine] && options[:engine_version].nil? raise GemfileError, "Please define :engine" if options[:engine_version] && options[:engine].nil? + if options[:file] + raise GemfileError, "Cannot specify version when using the file option" if ruby_version.any? + ruby_version << Bundler.read_file(options[:file]).strip + end + if options[:engine] == "ruby" && options[:engine_version] && ruby_version != Array(options[:engine_version]) raise GemfileEvalError, "ruby_version must match the :engine_version for MRI" diff --git a/lib/bundler/ruby_version.rb b/lib/bundler/ruby_version.rb index 9161c6afde..b5396abb6e 100644 --- a/lib/bundler/ruby_version.rb +++ b/lib/bundler/ruby_version.rb @@ -28,8 +28,8 @@ module Bundler end @gem_version = Gem::Requirement.create(@versions.first).requirements.first.last - @input_engine = engine && engine.to_s - @engine = engine && engine.to_s || "ruby" + @input_engine = engine&.to_s + @engine = engine&.to_s || "ruby" @engine_versions = (engine_version && Array(engine_version)) || @versions @engine_gem_version = Gem::Requirement.create(@engine_versions.first).requirements.first.last @patchlevel = patchlevel || (@gem_version.prerelease? ? "-1" : nil) @@ -107,7 +107,7 @@ module Bundler ruby_engine_version = RUBY_ENGINE == "ruby" ? ruby_version : RUBY_ENGINE_VERSION.dup patchlevel = RUBY_PATCHLEVEL.to_s - @ruby_version ||= RubyVersion.new(ruby_version, patchlevel, ruby_engine, ruby_engine_version) + @system ||= RubyVersion.new(ruby_version, patchlevel, ruby_engine, ruby_engine_version) end private diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb index 12d6789065..8981612706 100644 --- a/lib/bundler/rubygems_ext.rb +++ b/lib/bundler/rubygems_ext.rb @@ -16,6 +16,7 @@ require "rubygems/specification" require "rubygems/source" require_relative "match_metadata" +require_relative "force_platform" require_relative "match_platform" # Cherry-pick fixes to `Gem.ruby_version` to be useful for modern Bundler @@ -65,7 +66,9 @@ module Gem alias_method :rg_extension_dir, :extension_dir def extension_dir - @bundler_extension_dir ||= if source.respond_to?(:extension_dir_name) + # following instance variable is already used in original method + # and that is the reason to prefix it with bundler_ and add rubocop exception + @bundler_extension_dir ||= if source.respond_to?(:extension_dir_name) # rubocop:disable Naming/MemoizedInstanceVariableName unique_extension_dir = [source.extension_dir_name, File.basename(full_gem_path)].uniq.join("-") File.expand_path(File.join(extensions_dir, unique_extension_dir)) else @@ -153,12 +156,16 @@ module Gem end class Dependency + include ::Bundler::ForcePlatform + attr_accessor :source, :groups alias_method :eql?, :== def force_ruby_platform - false + return @force_ruby_platform if defined?(@force_ruby_platform) && !@force_ruby_platform.nil? + + @force_ruby_platform = default_force_ruby_platform end def encode_with(coder) @@ -198,9 +205,9 @@ module Gem protected def _requirements_sorted? - return @_are_requirements_sorted if defined?(@_are_requirements_sorted) + return @_requirements_sorted if defined?(@_requirements_sorted) strings = as_list - @_are_requirements_sorted = strings == strings.sort + @_requirements_sorted = strings == strings.sort end def _with_sorted_requirements @@ -277,6 +284,10 @@ module Gem without_gnu_nor_abi_modifiers end end + + if RUBY_ENGINE == "truffleruby" && !defined?(REUSE_AS_BINARY_ON_TRUFFLERUBY) + REUSE_AS_BINARY_ON_TRUFFLERUBY = %w[libv8 libv8-node sorbet-static].freeze + end end Platform.singleton_class.module_eval do @@ -338,11 +349,7 @@ module Gem end def glob_files_in_dir(glob, base_path) - if RUBY_VERSION >= "2.5" - Dir.glob(glob, :base => base_path).map! {|f| File.expand_path(f, base_path) } - else - Dir.glob(File.join(base_path.to_s.gsub(/[\[\]]/, '\\\\\\&'), glob)).map! {|f| File.expand_path(f) } - end + Dir.glob(glob, :base => base_path).map! {|f| File.expand_path(f, base_path) } end end end diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb index 13c2d25882..38035a00ac 100644 --- a/lib/bundler/rubygems_gem_installer.rb +++ b/lib/bundler/rubygems_gem_installer.rb @@ -109,8 +109,10 @@ module Bundler def strict_rm_rf(dir) Bundler.rm_rf dir - rescue Errno::ENOTEMPTY => e - raise DirectoryRemovalError.new(e.cause, "Could not delete previous installation of `#{dir}`") + rescue StandardError => e + raise unless File.exist?(dir) + + raise DirectoryRemovalError.new(e, "Could not delete previous installation of `#{dir}`") end def validate_bundler_checksum(checksum) diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb index d14075c96b..d8b7886af7 100644 --- a/lib/bundler/rubygems_integration.rb +++ b/lib/bundler/rubygems_integration.rb @@ -227,10 +227,14 @@ module Bundler def reverse_rubygems_kernel_mixin # Disable rubygems' gem activation system - kernel = (class << ::Kernel; self; end) - [kernel, ::Kernel].each do |k| - if k.private_method_defined?(:gem_original_require) - redefine_method(k, :require, k.instance_method(:gem_original_require)) + if Gem.respond_to?(:discover_gems_on_require=) + Gem.discover_gems_on_require = false + else + kernel = (class << ::Kernel; self; end) + [kernel, ::Kernel].each do |k| + if k.private_method_defined?(:gem_original_require) + redefine_method(k, :require, k.instance_method(:gem_original_require)) + end end end end @@ -243,7 +247,7 @@ module Bundler kernel = (class << ::Kernel; self; end) [kernel, ::Kernel].each do |kernel_class| redefine_method(kernel_class, :gem) do |dep, *reqs| - if executables && executables.include?(File.basename(caller.first.split(":").first)) + if executables&.include?(File.basename(caller.first.split(":").first)) break end @@ -449,7 +453,7 @@ module Bundler fetcher = gem_remote_fetcher fetcher.headers = { "X-Gemfile-Source" => remote.original_uri.to_s } if remote.original_uri string = fetcher.fetch_path(path) - Bundler.load_marshal(string) + Bundler.safe_load_marshal(string) rescue Gem::RemoteFetcher::FetchError # it's okay for prerelease to fail raise unless name == "prerelease_specs" diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index bd38353d3c..95cf78dd41 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -94,7 +94,7 @@ module Bundler definition_method :requires def lock(opts = {}) - return if @definition.nothing_changed? && !@definition.unlocking? + return if @definition.no_resolve_needed? @definition.lock(Bundler.default_lockfile, opts[:preserve_unknown_sections]) end diff --git a/lib/bundler/safe_marshal.rb b/lib/bundler/safe_marshal.rb new file mode 100644 index 0000000000..50aa0f60a6 --- /dev/null +++ b/lib/bundler/safe_marshal.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Bundler + module SafeMarshal + ALLOWED_CLASSES = [ + Array, + FalseClass, + Gem::Specification, + Gem::Version, + Hash, + String, + Symbol, + Time, + TrueClass, + ].freeze + + ERROR = "Unexpected class %s present in marshaled data. Only %s are allowed." + + PROC = proc do |object| + object.tap do + unless ALLOWED_CLASSES.include?(object.class) + raise TypeError, format(ERROR, object.class, ALLOWED_CLASSES.join(", ")) + end + end + end + + def self.proc + PROC + end + end +end diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index a76a792743..0af2236a45 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -43,7 +43,6 @@ module Bundler setup_makes_kernel_gem_public silence_deprecations silence_root_warning - suppress_install_using_messages update_requires_all_flag ].freeze @@ -219,7 +218,6 @@ module Bundler def path configs.each do |_level, settings| path = value_for("path", settings) - path = "vendor/bundle" if value_for("deployment", settings) && path.nil? path_system = value_for("path.system", settings) disabled_shared_gems = value_for("disable_shared_gems", settings) next if path.nil? && path_system.nil? && disabled_shared_gems.nil? @@ -227,7 +225,9 @@ module Bundler return Path.new(path, system_path) end - Path.new(nil, false) + path = "vendor/bundle" if self[:deployment] + + Path.new(path, false) end Path = Struct.new(:explicit_path, :system_path) do @@ -495,7 +495,7 @@ module Bundler uri = $2 suffix = $3 end - uri = "#{uri}/" unless uri.end_with?("/") + uri = URINormalizer.normalize_suffix(uri) require_relative "vendored_uri" uri = Bundler::URI(uri) unless uri.absolute? diff --git a/lib/bundler/setup.rb b/lib/bundler/setup.rb index 32e9b2d7c0..801fd5312a 100644 --- a/lib/bundler/setup.rb +++ b/lib/bundler/setup.rb @@ -12,7 +12,10 @@ if Bundler::SharedHelpers.in_bundle? Bundler.ui.error e.message Bundler.ui.warn e.backtrace.join("\n") if ENV["DEBUG"] if e.is_a?(Bundler::GemNotFound) - Bundler.ui.warn "Run `bundle install` to install missing gems." + suggested_cmd = "bundle install" + original_gemfile = Bundler.original_env["BUNDLE_GEMFILE"] + suggested_cmd += " --gemfile #{original_gemfile}" if original_gemfile + Bundler.ui.warn "Run `#{suggested_cmd}` to install missing gems." end exit e.status_code end diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 0a6afe0e5a..d1d4e1d07a 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -160,7 +160,7 @@ module Bundler " (was expecting #{old_deps.map(&:to_s)}, but the real spec has #{new_deps.map(&:to_s)})" raise APIResponseMismatchError, "Downloading #{spec.full_name} revealed dependencies not in the API or the lockfile (#{extra_deps.join(", ")})." \ - "\nEither installing with `--full-index` or running `bundle update #{spec.name}` should fix the problem." + "\nRunning `bundle update #{spec.name}` should fix the problem." end def pretty_dependency(dep) @@ -284,7 +284,7 @@ module Bundler Bundler::SharedHelpers.set_env "BUNDLE_BIN_PATH", exe_file Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", find_gemfile.to_s Bundler::SharedHelpers.set_env "BUNDLER_VERSION", Bundler::VERSION - Bundler::SharedHelpers.set_env "BUNDLER_SETUP", File.expand_path("setup", __dir__) + Bundler::SharedHelpers.set_env "BUNDLER_SETUP", File.expand_path("setup", __dir__) unless RUBY_VERSION < "2.7" end def set_path diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb index 69804a2e63..f7f5ea7865 100644 --- a/lib/bundler/source.rb +++ b/lib/bundler/source.rb @@ -100,7 +100,7 @@ module Bundler end def print_using_message(message) - if !message.include?("(was ") && Bundler.feature_flag.suppress_install_using_messages? + if !message.include?("(was ") Bundler.ui.debug message else Bundler.ui.info message diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb index 745ae05c5c..adbce5fce4 100644 --- a/lib/bundler/source/git.rb +++ b/lib/bundler/source/git.rb @@ -19,7 +19,7 @@ module Bundler # Stringify options that could be set as symbols %w[ref branch tag revision].each {|k| options[k] = options[k].to_s if options[k] } - @uri = options["uri"] || "" + @uri = URINormalizer.normalize_suffix(options["uri"] || "", :trailing_slash => false) @safe_uri = URICredentialsFilter.credential_filtered_uri(@uri) @branch = options["branch"] @ref = options["ref"] || options["branch"] || options["tag"] @@ -46,6 +46,14 @@ module Bundler out << " specs:\n" end + def to_gemfile + specifiers = %w[ref branch tag submodules glob].map do |opt| + "#{opt}: #{options[opt]}" if options[opt] + end + + uri_with_specifiers(specifiers) + end + def hash [self.class, uri, ref, branch, name, version, glob, submodules].hash end @@ -59,28 +67,32 @@ module Bundler alias_method :==, :eql? + def include?(other) + other.is_a?(Git) && uri == other.uri && + name == other.name && + glob == other.glob && + submodules == other.submodules + end + def to_s begin - at = if local? - path - elsif user_ref = options["ref"] - if ref =~ /\A[a-z0-9]{4,}\z/i - shortref_for_display(user_ref) - else - user_ref - end - elsif ref - ref - else - current_branch - end + at = humanized_ref || current_branch rev = "at #{at}@#{shortref_for_display(revision)}" rescue GitError "" end - specifiers = [rev, glob_for_display].compact + uri_with_specifiers([rev, glob_for_display]) + end + + def identifier + uri_with_specifiers([humanized_ref, cached_revision, glob_for_display]) + end + + def uri_with_specifiers(specifiers) + specifiers.compact! + suffix = if specifiers.any? " (#{specifiers.join(", ")})" @@ -148,7 +160,7 @@ module Bundler "#{current_branch} but Gemfile specifies #{branch}" end - changed = cached_revision && cached_revision != git_proxy.revision + changed = cached_revision && cached_revision != revision if !Bundler.settings[:disable_local_revision_check] && changed && !@unlocked && !git_proxy.contains?(cached_revision) raise GitError, "The Gemfile lock is pointing to revision #{shortref_for_display(cached_revision)} " \ @@ -173,6 +185,7 @@ module Bundler end def install(spec, options = {}) + return if Bundler.settings[:no_install] force = options[:force] print_using_message "Using #{version_message(spec, options[:previous_spec])} from #{self}" @@ -242,6 +255,20 @@ module Bundler private + def humanized_ref + if local? + path + elsif user_ref = options["ref"] + if /\A[a-z0-9]{4,}\z/i.match?(ref) + shortref_for_display(user_ref) + else + user_ref + end + elsif ref + ref + end + end + def serialize_gemspecs_in(destination) destination = destination.expand_path(Bundler.root) if destination.relative? Dir["#{destination}/#{@glob}"].each do |spec_path| @@ -295,7 +322,7 @@ module Bundler end def uri_hash - if uri =~ %r{^\w+://(\w+@)?} + if %r{^\w+://(\w+@)?}.match?(uri) # Downcase the domain component of the URI # and strip off a trailing slash, if one is present input = Bundler::URI.parse(uri).normalize.to_s.sub(%r{/$}, "") diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb index 5468536f86..fdb738e52e 100644 --- a/lib/bundler/source/git/git_proxy.rb +++ b/lib/bundler/source/git/git_proxy.rb @@ -28,10 +28,10 @@ module Bundler def initialize(command, path, extra_info = nil) @command = command - msg = String.new - msg << "Git error: command `#{command}` in directory #{path} has failed." + msg = String.new("Git error: command `#{command}`") + msg << " in directory #{path}" if path + msg << " has failed." msg << "\n#{extra_info}" if extra_info - msg << "\nIf this error persists you could try removing the cache directory '#{path}'" if path.exist? super msg end end @@ -47,7 +47,7 @@ module Bundler # All actions required by the Git source is encapsulated in this # object. class GitProxy - attr_accessor :path, :uri, :branch, :tag, :ref + attr_accessor :path, :uri, :branch, :tag, :ref, :explicit_ref attr_writer :revision def initialize(path, uri, options = {}, revision = nil, git = nil) @@ -56,17 +56,19 @@ module Bundler @branch = options["branch"] @tag = options["tag"] @ref = options["ref"] + @explicit_ref = branch || tag || ref @revision = revision @git = git + @commit_ref = nil end def revision - @revision ||= find_local_revision + @revision ||= allowed_with_path { find_local_revision } end def current_branch - @current_branch ||= allowed_with_path do - git("rev-parse", "--abbrev-ref", "HEAD", :dir => path).strip + @current_branch ||= with_path do + git_local("rev-parse", "--abbrev-ref", "HEAD", :dir => path).strip end end @@ -82,7 +84,7 @@ module Bundler end def full_version - @full_version ||= git("--version").sub(/git version\s*/, "").strip + @full_version ||= git_local("--version").sub(/git version\s*/, "").strip end def checkout @@ -90,18 +92,11 @@ module Bundler Bundler.ui.info "Fetching #{credential_filtered_uri}" - unless path.exist? - SharedHelpers.filesystem_access(path.dirname) do |p| - FileUtils.mkdir_p(p) - end - git_retry "clone", "--bare", "--no-hardlinks", "--quiet", *extra_clone_args, "--", configured_uri, path.to_s - return unless extra_ref - end + extra_fetch_needed = clone_needs_extra_fetch? + unshallow_needed = clone_needs_unshallow? + return unless extra_fetch_needed || unshallow_needed - fetch_args = extra_fetch_args - fetch_args.unshift("--unshallow") if path.join("shallow").exist? && full_clone? - - git_retry(*["fetch", "--force", "--quiet", "--no-tags", *fetch_args, "--", configured_uri, refspec].compact, :dir => path) + git_remote_fetch(unshallow_needed ? ["--unshallow"] : depth_args) end def copy_to(destination, submodules = false) @@ -123,7 +118,7 @@ module Bundler end end - git(*["fetch", "--force", "--quiet", *extra_fetch_args, path.to_s, revision_refspec].compact, :dir => destination) + git "fetch", "--force", "--quiet", *extra_fetch_args, :dir => destination if @commit_ref git "reset", "--hard", @revision, :dir => destination @@ -137,6 +132,52 @@ module Bundler private + def git_remote_fetch(args) + command = ["fetch", "--force", "--quiet", "--no-tags", *args, "--", configured_uri, refspec].compact + command_with_no_credentials = check_allowed(command) + + Bundler::Retry.new("`#{command_with_no_credentials}` at #{path}", [MissingGitRevisionError]).attempts do + out, err, status = capture(command, path) + return out if status.success? + + if err.include?("couldn't find remote ref") || err.include?("not our ref") + raise MissingGitRevisionError.new(command_with_no_credentials, path, commit || explicit_ref, credential_filtered_uri) + else + raise GitCommandError.new(command_with_no_credentials, path, err) + end + end + end + + def clone_needs_extra_fetch? + return true if path.exist? + + SharedHelpers.filesystem_access(path.dirname) do |p| + FileUtils.mkdir_p(p) + end + + command = ["clone", "--bare", "--no-hardlinks", "--quiet", *extra_clone_args, "--", configured_uri, path.to_s] + command_with_no_credentials = check_allowed(command) + + Bundler::Retry.new("`#{command_with_no_credentials}`", [MissingGitRevisionError]).attempts do + _, err, status = capture(command, nil) + return extra_ref if status.success? + + if err.include?("Could not find remote branch") || # git up to 2.49 + err.include?("Remote branch #{branch_option} not found") # git 2.49 or higher + raise MissingGitRevisionError.new(command_with_no_credentials, nil, explicit_ref, credential_filtered_uri) + else + raise GitCommandError.new(command_with_no_credentials, path, err) + end + end + end + + def clone_needs_unshallow? + return false unless path.join("shallow").exist? + return true if full_clone? + + @revision && @revision != head_revision + end + def extra_ref return false if not_pinned? return true unless full_clone? @@ -147,39 +188,41 @@ module Bundler def depth return @depth if defined?(@depth) - @depth = if legacy_locked_revision? || !supports_fetching_unreachable_refs? + @depth = if !supports_fetching_unreachable_refs? nil - elsif not_pinned? + elsif not_pinned? || pinned_to_full_sha? 1 elsif ref.include?("~") parsed_depth = ref.split("~").last parsed_depth.to_i + 1 - elsif abbreviated_ref? - nil - else - 1 end end def refspec - if fully_qualified_ref - "#{fully_qualified_ref}:#{fully_qualified_ref}" - elsif ref.include?("~") - parsed_ref = ref.split("~").first - "#{parsed_ref}:#{parsed_ref}" + if commit + @commit_ref = "refs/#{commit}-sha" + return "#{commit}:#{@commit_ref}" + end + + reference = fully_qualified_ref + + reference ||= if ref.include?("~") + ref.split("~").first elsif ref.start_with?("refs/") - "#{ref}:#{ref}" - elsif abbreviated_ref? - nil - else ref + else + "refs/*" end + + "#{reference}:#{reference}" end - def fully_qualified_ref - return @fully_qualified_ref if defined?(@fully_qualified_ref) + def commit + @commit ||= pinned_to_full_sha? ? ref : @revision + end - @fully_qualified_ref = if branch + def fully_qualified_ref + if branch "refs/heads/#{branch}" elsif tag "refs/tags/#{tag}" @@ -189,25 +232,17 @@ module Bundler end def not_pinned? - branch || tag || ref.nil? - end - - def abbreviated_ref? - ref =~ /\A\h+\z/ && ref !~ /\A\h{40}\z/ + branch_option || ref.nil? end - def legacy_locked_revision? - !@revision.nil? && @revision =~ /\A\h{7}\z/ + def pinned_to_full_sha? + ref =~ /\A\h{40}\z/ end def git_null(*command, dir: nil) check_allowed(command) - out, status = SharedHelpers.with_clean_git_env do - capture_and_ignore_stderr(*capture3_args_for(command, dir)) - end - - [URICredentialsFilter.credential_filtered_string(out, uri), status] + capture(command, dir, :ignore_err => true) end def git_retry(*command, dir: nil) @@ -219,17 +254,15 @@ module Bundler end def git(*command, dir: nil) - command_with_no_credentials = check_allowed(command) - - out, status = SharedHelpers.with_clean_git_env do - capture_and_filter_stderr(*capture3_args_for(command, dir)) + run_command(*command, :dir => dir) do |unredacted_command| + check_allowed(unredacted_command) end + end - filtered_out = URICredentialsFilter.credential_filtered_string(out, uri) - - raise GitCommandError.new(command_with_no_credentials, dir || SharedHelpers.pwd, filtered_out) unless status.success? - - filtered_out + def git_local(*command, dir: nil) + run_command(*command, :dir => dir) do |unredacted_command| + redact_and_check_presence(unredacted_command) + end end def has_revision_cached? @@ -241,16 +274,28 @@ module Bundler end def find_local_revision - allowed_with_path do - git("rev-parse", "--verify", branch || tag || ref || "HEAD", :dir => path).strip - end + return head_revision if explicit_ref.nil? + + find_revision_for(explicit_ref) + end + + def head_revision + verify("HEAD") + end + + def find_revision_for(reference) + verify(reference) rescue GitCommandError => e - raise MissingGitRevisionError.new(e.command, path, branch || tag || ref, credential_filtered_uri) + raise MissingGitRevisionError.new(e.command, path, reference, credential_filtered_uri) + end + + def verify(reference) + git("rev-parse", "--verify", reference, :dir => path).strip end # Adds credentials to the URI def configured_uri - if /https?:/ =~ uri + if /https?:/.match?(uri) remote = Bundler::URI(uri) config_auth = Bundler.settings[remote.to_s] || Bundler.settings[remote.host] remote.userinfo ||= config_auth @@ -286,23 +331,41 @@ module Bundler end def check_allowed(command) - require "shellwords" - command_with_no_credentials = URICredentialsFilter.credential_filtered_string("git #{command.shelljoin}", uri) + command_with_no_credentials = redact_and_check_presence(command) raise GitNotAllowedError.new(command_with_no_credentials) unless allow? command_with_no_credentials end - def capture_and_filter_stderr(*cmd) - require "open3" - return_value, captured_err, status = Open3.capture3(*cmd) - Bundler.ui.warn URICredentialsFilter.credential_filtered_string(captured_err, uri) unless captured_err.empty? - [return_value, status] + def redact_and_check_presence(command) + raise GitNotInstalledError.new unless Bundler.git_present? + + require "shellwords" + URICredentialsFilter.credential_filtered_string("git #{command.shelljoin}", uri) + end + + def run_command(*command, dir: nil) + command_with_no_credentials = yield(command) + + out, err, status = capture(command, dir) + + raise GitCommandError.new(command_with_no_credentials, dir || SharedHelpers.pwd, err) unless status.success? + + Bundler.ui.warn err unless err.empty? + + out end - def capture_and_ignore_stderr(*cmd) - require "open3" - return_value, _, status = Open3.capture3(*cmd) - [return_value, status] + def capture(cmd, dir, ignore_err: false) + SharedHelpers.with_clean_git_env do + require "open3" + out, err, status = Open3.capture3(*capture3_args_for(cmd, dir)) + + filtered_out = URICredentialsFilter.credential_filtered_string(out, uri) + return [filtered_out, status] if ignore_err + + filtered_err = URICredentialsFilter.credential_filtered_string(err, uri) + [filtered_out, filtered_err, status] + end end def capture3_args_for(cmd, dir) @@ -316,25 +379,35 @@ module Bundler end def extra_clone_args - return [] if full_clone? + args = depth_args + return [] if args.empty? - args = ["--depth", depth.to_s, "--single-branch"] + args += ["--single-branch"] args.unshift("--no-tags") if supports_cloning_with_no_tags? - args += ["--branch", branch || tag] if branch || tag + # If there's a locked revision, no need to clone any specific branch + # or tag, since we will end up checking out that locked revision + # anyways. + return args if @revision + + args += ["--branch", branch_option] if branch_option args end - def extra_fetch_args + def depth_args return [] if full_clone? ["--depth", depth.to_s] end - def revision_refspec - return if legacy_locked_revision? + def extra_fetch_args + extra_args = [path.to_s, *depth_args] + extra_args.push(@commit_ref) + extra_args + end - revision + def branch_option + branch || tag end def full_clone? diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb index 672ecfd13b..bdfcf8274a 100644 --- a/lib/bundler/source/path.rb +++ b/lib/bundler/source/path.rb @@ -11,7 +11,7 @@ module Bundler protected :original_path - DEFAULT_GLOB = "{,*,*/*}.gemspec".freeze + DEFAULT_GLOB = "{,*,*/*}.gemspec" def initialize(options) @options = options.dup @@ -224,13 +224,13 @@ module Bundler # Some gem authors put absolute paths in their gemspec # and we have to save them from themselves - spec.files = spec.files.map do |p| - next p unless p =~ /\A#{Pathname::SEPARATOR_PAT}/ - next if File.directory?(p) + spec.files = spec.files.map do |path| + next path unless /\A#{Pathname::SEPARATOR_PAT}/.match?(path) + next if File.directory?(path) begin - Pathname.new(p).relative_path_from(gem_dir).to_s + Pathname.new(path).relative_path_from(gem_dir).to_s rescue ArgumentError - p + path end end.compact diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index 3b640b55a9..af57acbbc2 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -7,12 +7,10 @@ module Bundler class Rubygems < Source autoload :Remote, File.expand_path("rubygems/remote", __dir__) - # Use the API when installing less than X gems - API_REQUEST_LIMIT = 500 # Ask for X gems per API request API_REQUEST_SIZE = 50 - attr_reader :remotes, :caches + attr_reader :remotes def initialize(options = {}) @options = options @@ -21,11 +19,14 @@ module Bundler @allow_remote = false @allow_cached = false @allow_local = options["allow_local"] || false - @caches = [cache_path, *Bundler.rubygems.gem_cache] Array(options["remotes"]).reverse_each {|r| add_remote(r) } end + def caches + @caches ||= [cache_path, *Bundler.rubygems.gem_cache] + end + def local_only! @specs = nil @allow_local = true @@ -122,6 +123,7 @@ module Bundler end end alias_method :name, :identifier + alias_method :to_gemfile, :identifier def specs @specs ||= begin @@ -145,7 +147,7 @@ module Bundler end if installed?(spec) && !force - print_using_message "Using #{version_message(spec)}" + print_using_message "Using #{version_message(spec, options[:previous_spec])}" return nil # no post-install message end @@ -163,8 +165,6 @@ module Bundler install_path = rubygems_dir bin_path = Bundler.system_bindir - Bundler.mkdir_p bin_path unless spec.executables.empty? || Bundler.rubygems.provides?(">= 2.7.5") - require_relative "../rubygems_gem_installer" installer = Bundler::RubyGemsGemInstaller.at( @@ -294,7 +294,7 @@ module Bundler end def dependency_api_available? - api_fetchers.any? + @allow_remote && api_fetchers.any? end protected @@ -328,9 +328,9 @@ module Bundler def cached_path(spec) global_cache_path = download_cache_path(spec) - @caches << global_cache_path if global_cache_path + caches << global_cache_path if global_cache_path - possibilities = @caches.map {|p| package_path(p, spec) } + possibilities = caches.map {|p| package_path(p, spec) } possibilities.find {|p| File.exist?(p) } end @@ -339,8 +339,7 @@ module Bundler end def normalize_uri(uri) - uri = uri.to_s - uri = "#{uri}/" unless uri =~ %r{/$} + uri = URINormalizer.normalize_suffix(uri.to_s) require_relative "../vendored_uri" uri = Bundler::URI(uri) raise ArgumentError, "The source must be an absolute URI. For example:\n" \ @@ -383,7 +382,6 @@ module Bundler idx = @allow_local ? installed_specs.dup : Index.new Dir["#{cache_path}/*.gem"].each do |gemfile| - next if gemfile =~ /^bundler\-[\d\.]+?\.gem/ s ||= Bundler.rubygems.spec_from_gem(gemfile) s.source = self idx << s @@ -404,12 +402,11 @@ module Bundler # gather lists from non-api sites fetch_names(index_fetchers, nil, idx, false) - # because ensuring we have all the gems we need involves downloading - # the gemspecs of those gems, if the non-api sites contain more than - # about 500 gems, we treat all sites as non-api for speed. - allow_api = idx.size < API_REQUEST_LIMIT && dependency_names.size < API_REQUEST_LIMIT - Bundler.ui.debug "Need to query more than #{API_REQUEST_LIMIT} gems." \ - " Downloading full index instead..." unless allow_api + # legacy multi-remote sources need special logic to figure out + # dependency names and that logic can be very costly if one remote + # uses the dependency API but others don't. So use full indexes + # consistently in that particular case. + allow_api = !multiple_remotes? fetch_names(api_fetchers, allow_api && dependency_names, idx, false) end diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb index 6ea2910d18..4419695b7f 100644 --- a/lib/bundler/source_list.rb +++ b/lib/bundler/source_list.rb @@ -101,10 +101,6 @@ module Bundler source_list_for(source).find {|s| equivalent_source?(source, s) } end - def get_with_fallback(source) - get(source) || default_source - end - def lock_sources lock_other_sources + lock_rubygems_sources end @@ -161,11 +157,17 @@ module Bundler end def map_sources(replacement_sources) - [@rubygems_sources, @path_sources, @git_sources, @plugin_sources].map do |sources| + rubygems, git, plugin = [@rubygems_sources, @git_sources, @plugin_sources].map do |sources| sources.map do |source| replacement_sources.find {|s| s == source } || source end end + + path = @path_sources.map do |source| + replacement_sources.find {|s| s == (source.is_a?(Source::Gemspec) ? source.as_path_source : source) } || source + end + + [rubygems, path, git, plugin] end def global_replacement_source(replacement_sources) @@ -206,7 +208,7 @@ module Bundler def warn_on_git_protocol(source) return if Bundler.settings["git.allow_insecure"] - if source.uri =~ /^git\:/ + if /^git\:/.match?(source.uri) Bundler.ui.warn "The git source `#{source.uri}` uses the `git` protocol, " \ "which transmits data without encryption. Disable this warning with " \ "`bundle config set --local git.allow_insecure true`, or switch to the `https` " \ diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb index a3d9218593..21630e3a3e 100644 --- a/lib/bundler/spec_set.rb +++ b/lib/bundler/spec_set.rb @@ -24,6 +24,7 @@ module Bundler name = dep[0].name platform = dep[1] + incomplete = false key = [name, platform] next if handled.key?(key) @@ -36,14 +37,19 @@ module Bundler specs_for_dep.first.dependencies.each do |d| next if d.type == :development + incomplete = true if d.name != "bundler" && lookup[d.name].empty? deps << [d, dep[1]] end - elsif check - @incomplete_specs += lookup[name] + else + incomplete = true + end + + if incomplete && check + @incomplete_specs += lookup[name].any? ? lookup[name] : [LazySpecification.new(name, nil, nil)] end end - specs + specs.uniq end def [](key) @@ -57,8 +63,8 @@ module Bundler @sorted = nil end - def delete(spec) - @specs.delete(spec) + def delete(specs) + specs.each {|spec| @specs.delete(spec) } @lookup = nil @sorted = nil end @@ -95,6 +101,10 @@ module Bundler end def incomplete_ruby_specs?(deps) + return false if @specs.empty? + + @incomplete_specs = [] + self.for(deps, true, [Gem::Platform::RUBY]) @incomplete_specs.any? @@ -190,12 +200,10 @@ module Bundler def specs_for_dependency(dep, platform) specs_for_name = lookup[dep.name] - if platform.nil? - matching_specs = specs_for_name.map {|s| s.materialize_for_installation if Gem::Platform.match_spec?(s) }.compact - GemHelpers.sort_best_platform_match(matching_specs, Bundler.local_platform) - else - GemHelpers.select_best_platform_match(specs_for_name, dep.force_ruby_platform ? Gem::Platform::RUBY : platform) - end + target_platform = dep.force_ruby_platform ? Gem::Platform::RUBY : (platform || Bundler.local_platform) + matching_specs = GemHelpers.select_best_platform_match(specs_for_name, target_platform) + matching_specs.map!(&:materialize_for_installation).compact! if platform.nil? + matching_specs end def tsort_each_child(s) diff --git a/lib/bundler/templates/Executable.bundler b/lib/bundler/templates/Executable.bundler index 77f90e735c..e290fe91eb 100644 --- a/lib/bundler/templates/Executable.bundler +++ b/lib/bundler/templates/Executable.bundler @@ -47,7 +47,7 @@ m = Module.new do def lockfile lockfile = case File.basename(gemfile) - when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") else "#{gemfile}.lock" end File.expand_path(lockfile) @@ -72,13 +72,7 @@ m = Module.new do bundler_gem_version = Gem::Version.new(version) - requirement = bundler_gem_version.approximate_recommendation - - return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") - - requirement += ".a" if bundler_gem_version.prerelease? - - requirement + bundler_gem_version.approximate_recommendation end def load_bundler! diff --git a/lib/bundler/templates/gems.rb b/lib/bundler/templates/gems.rb deleted file mode 100644 index d2403f18b2..0000000000 --- a/lib/bundler/templates/gems.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -# gem "rails" diff --git a/lib/bundler/templates/newgem/Cargo.toml.tt b/lib/bundler/templates/newgem/Cargo.toml.tt new file mode 100644 index 0000000000..f5a460c9bb --- /dev/null +++ b/lib/bundler/templates/newgem/Cargo.toml.tt @@ -0,0 +1,7 @@ +# This Cargo.toml is here to let externals tools (IDEs, etc.) know that this is +# a Rust project. Your extensions dependencies should be added to the Cargo.toml +# in the ext/ directory. + +[workspace] +members = ["./ext/<%= config[:name] %>"] +resolver = "2" diff --git a/lib/bundler/templates/newgem/Gemfile.tt b/lib/bundler/templates/newgem/Gemfile.tt index de82a63c5f..a0d2ac2826 100644 --- a/lib/bundler/templates/newgem/Gemfile.tt +++ b/lib/bundler/templates/newgem/Gemfile.tt @@ -9,6 +9,9 @@ gem "rake", "~> 13.0" <%- if config[:ext] -%> gem "rake-compiler" +<%- if config[:ext] == 'rust' -%> +gem "rb_sys", "~> 0.9.63" +<%- end -%> <%- end -%> <%- if config[:test] -%> diff --git a/lib/bundler/templates/newgem/README.md.tt b/lib/bundler/templates/newgem/README.md.tt index a60c7967ec..20eaac8a62 100644 --- a/lib/bundler/templates/newgem/README.md.tt +++ b/lib/bundler/templates/newgem/README.md.tt @@ -1,18 +1,20 @@ # <%= config[:constant_name] %> -Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/<%= config[:namespaced_path] %>`. To experiment with that code, run `bin/console` for an interactive prompt. +TODO: Delete this and the text below, and describe your gem -TODO: Delete this and the text above, and describe your gem +Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/<%= config[:namespaced_path] %>`. To experiment with that code, run `bin/console` for an interactive prompt. ## Installation +TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org. + Install the gem and add to the application's Gemfile by executing: - $ bundle add <%= config[:name] %> + $ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG If bundler is not being used to manage dependencies, install the gem by executing: - $ gem install <%= config[:name] %> + $ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG ## Usage diff --git a/lib/bundler/templates/newgem/Rakefile.tt b/lib/bundler/templates/newgem/Rakefile.tt index b02ada9b6c..b5a5c4e392 100644 --- a/lib/bundler/templates/newgem/Rakefile.tt +++ b/lib/bundler/templates/newgem/Rakefile.tt @@ -39,7 +39,17 @@ require "standard/rake" <% end -%> <% if config[:ext] -%> -<% default_task_names.unshift(:clobber, :compile) -%> +<% default_task_names.unshift(:compile) -%> +<% default_task_names.unshift(:clobber) unless config[:ext] == 'rust' -%> +<% if config[:ext] == 'rust' -%> +require "rb_sys/extensiontask" + +task build: :compile + +RbSys::ExtensionTask.new(<%= config[:name].inspect %>) do |ext| + ext.lib_dir = "lib/<%= config[:namespaced_path] %>" +end +<% else -%> require "rake/extensiontask" task build: :compile @@ -47,6 +57,7 @@ task build: :compile Rake::ExtensionTask.new("<%= config[:underscored_name] %>") do |ext| ext.lib_dir = "lib/<%= config[:namespaced_path] %>" end +<% end -%> <% end -%> <% if default_task_names.size == 1 -%> diff --git a/lib/bundler/templates/newgem/bin/console.tt b/lib/bundler/templates/newgem/bin/console.tt index 08dfaaef69..c91ee65f93 100644 --- a/lib/bundler/templates/newgem/bin/console.tt +++ b/lib/bundler/templates/newgem/bin/console.tt @@ -7,9 +7,5 @@ require "<%= config[:namespaced_path] %>" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. -# (If you use this, don't forget to add pry to your Gemfile!) -# require "pry" -# Pry.start - require "irb" IRB.start(__FILE__) diff --git a/lib/bundler/templates/newgem/circleci/config.yml.tt b/lib/bundler/templates/newgem/circleci/config.yml.tt index 79fd0dcc0f..f40f029bf1 100644 --- a/lib/bundler/templates/newgem/circleci/config.yml.tt +++ b/lib/bundler/templates/newgem/circleci/config.yml.tt @@ -3,8 +3,20 @@ jobs: build: docker: - image: ruby:<%= RUBY_VERSION %> +<%- if config[:ext] == 'rust' -%> + environment: + RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN: 'true' +<%- end -%> steps: - checkout +<%- if config[:ext] == 'rust' -%> + - run: + name: Install Rust/Cargo dependencies + command: apt-get update && apt-get install -y clang + - run: + name: Install a RubyGems version that can compile rust extensions + command: gem update --system '<%= ::Gem.rubygems_version %>' +<%- end -%> - run: name: Run the default task command: | diff --git a/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt b/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt new file mode 100644 index 0000000000..c64385486e --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt @@ -0,0 +1,15 @@ +[package] +name = <%= config[:name].inspect %> +version = "0.1.0" +edition = "2021" +authors = ["<%= config[:author] %> <<%= config[:email] %>>"] +<%- if config[:mit] -%> +license = "MIT" +<%- end -%> +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +magnus = { version = "0.6" } diff --git a/lib/bundler/templates/newgem/ext/newgem/extconf-c.rb.tt b/lib/bundler/templates/newgem/ext/newgem/extconf-c.rb.tt new file mode 100644 index 0000000000..0a0c5a3d09 --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/extconf-c.rb.tt @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require "mkmf" + +# Makes all symbols private by default to avoid unintended conflict +# with other gems. To explicitly export symbols you can use RUBY_FUNC_EXPORTED +# selectively, or entirely remove this flag. +append_cflags("-fvisibility=hidden") + +create_makefile(<%= config[:makefile_path].inspect %>) diff --git a/lib/bundler/templates/newgem/ext/newgem/extconf-rust.rb.tt b/lib/bundler/templates/newgem/ext/newgem/extconf-rust.rb.tt new file mode 100644 index 0000000000..e24566a17a --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/extconf-rust.rb.tt @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +require "mkmf" +require "rb_sys/mkmf" + +create_rust_makefile(<%= config[:makefile_path].inspect %>) diff --git a/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt b/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt deleted file mode 100644 index e918063ddf..0000000000 --- a/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -require "mkmf" - -create_makefile(<%= config[:makefile_path].inspect %>) diff --git a/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt b/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt index 8177c4d202..bcd5148569 100644 --- a/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt +++ b/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt @@ -2,7 +2,7 @@ VALUE rb_m<%= config[:constant_array].join %>; -void +RUBY_FUNC_EXPORTED void Init_<%= config[:underscored_name] %>(void) { rb_m<%= config[:constant_array].join %> = rb_define_module(<%= config[:constant_name].inspect %>); diff --git a/lib/bundler/templates/newgem/ext/newgem/src/lib.rs.tt b/lib/bundler/templates/newgem/ext/newgem/src/lib.rs.tt new file mode 100644 index 0000000000..ba234529a3 --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/src/lib.rs.tt @@ -0,0 +1,12 @@ +use magnus::{function, prelude::*, Error, Ruby}; + +fn hello(subject: String) -> String { + format!("Hello from Rust, {subject}!") +} + +#[magnus::init] +fn init(ruby: &Ruby) -> Result<(), Error> { + let module = ruby.<%= config[:constant_array].map {|c| "define_module(#{c.dump})?"}.join(".") %>; + module.define_singleton_method("hello", function!(hello, 1))?; + Ok(()) +} diff --git a/lib/bundler/templates/newgem/github/workflows/main.yml.tt b/lib/bundler/templates/newgem/github/workflows/main.yml.tt index 1ff4b58b7b..be58dd8156 100644 --- a/lib/bundler/templates/newgem/github/workflows/main.yml.tt +++ b/lib/bundler/templates/newgem/github/workflows/main.yml.tt @@ -18,10 +18,20 @@ jobs: steps: - uses: actions/checkout@v3 +<%- if config[:ext] == 'rust' -%> + - name: Set up Ruby & Rust + uses: oxidize-rb/actions/setup-ruby-and-rust@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + cargo-cache: true + rubygems: '<%= ::Gem.rubygems_version %>' +<%- else -%> - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true +<%- end -%> - name: Run the default task run: bundle exec rake diff --git a/lib/bundler/templates/newgem/gitignore.tt b/lib/bundler/templates/newgem/gitignore.tt index b1c9f9986c..9b40ba5a58 100644 --- a/lib/bundler/templates/newgem/gitignore.tt +++ b/lib/bundler/templates/newgem/gitignore.tt @@ -12,6 +12,9 @@ *.o *.a mkmf.log +<%- if config[:ext] == 'rust' -%> +target/ +<%- end -%> <%- end -%> <%- if config[:test] == "rspec" -%> diff --git a/lib/bundler/templates/newgem/gitlab-ci.yml.tt b/lib/bundler/templates/newgem/gitlab-ci.yml.tt index 42e00392de..d2e1f33736 100644 --- a/lib/bundler/templates/newgem/gitlab-ci.yml.tt +++ b/lib/bundler/templates/newgem/gitlab-ci.yml.tt @@ -2,9 +2,17 @@ default: image: ruby:<%= RUBY_VERSION %> before_script: +<%- if config[:ext] == 'rust' -%> + - apt-get update && apt-get install -y clang + - gem update --system '<%= ::Gem.rubygems_version %>' +<%- end -%> - gem install bundler -v <%= Bundler::VERSION %> - bundle install example_job: +<%- if config[:ext] == 'rust' -%> + variables: + RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN: 'true' +<%- end -%> script: - bundle exec rake diff --git a/lib/bundler/templates/newgem/newgem.gemspec.tt b/lib/bundler/templates/newgem/newgem.gemspec.tt index ceb2e9b28d..bb76680379 100644 --- a/lib/bundler/templates/newgem/newgem.gemspec.tt +++ b/lib/bundler/templates/newgem/newgem.gemspec.tt @@ -15,6 +15,9 @@ Gem::Specification.new do |spec| spec.license = "MIT" <%- end -%> spec.required_ruby_version = ">= <%= config[:required_ruby_version] %>" +<%- if config[:ext] == 'rust' -%> + spec.required_rubygems_version = ">= <%= config[:rust_builder_required_rubygems_version] %>" +<%- end -%> spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'" @@ -26,15 +29,19 @@ Gem::Specification.new do |spec| # The `git ls-files -z` loads the files in the RubyGem that have been added into git. spec.files = Dir.chdir(__dir__) do `git ls-files -z`.split("\x0").reject do |f| - (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)}) + (File.expand_path(f) == __FILE__) || + f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile]) end end spec.bindir = "exe" spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] -<%- if config[:ext] -%> +<%- if config[:ext] == 'c' -%> spec.extensions = ["ext/<%= config[:underscored_name] %>/extconf.rb"] <%- end -%> +<%- if config[:ext] == 'rust' -%> + spec.extensions = ["ext/<%= config[:underscored_name] %>/Cargo.toml"] +<%- end -%> # Uncomment to register a new dependency of your gem # spec.add_dependency "example-gem", "~> 1.0" diff --git a/lib/bundler/templates/newgem/travis.yml.tt b/lib/bundler/templates/newgem/travis.yml.tt deleted file mode 100644 index eab16addca..0000000000 --- a/lib/bundler/templates/newgem/travis.yml.tt +++ /dev/null @@ -1,6 +0,0 @@ ---- -language: ruby -cache: bundler -rvm: - - <%= RUBY_VERSION %> -before_install: gem install bundler -v <%= Bundler::VERSION %> diff --git a/lib/bundler/ui/rg_proxy.rb b/lib/bundler/ui/rg_proxy.rb index ef6def225b..b17ca65f53 100644 --- a/lib/bundler/ui/rg_proxy.rb +++ b/lib/bundler/ui/rg_proxy.rb @@ -12,7 +12,7 @@ module Bundler end def say(message) - @ui && @ui.debug(message) + @ui&.debug(message) end end end diff --git a/lib/bundler/uri_normalizer.rb b/lib/bundler/uri_normalizer.rb new file mode 100644 index 0000000000..ad08593256 --- /dev/null +++ b/lib/bundler/uri_normalizer.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Bundler + module URINormalizer + module_function + + # Normalizes uri to a consistent version, either with or without trailing + # slash. + # + # TODO: Currently gem sources are locked with a trailing slash, while git + # sources are locked without a trailing slash. This should be normalized but + # the inconsistency is there for now to avoid changing all lockfiles + # including GIT sources. We could normalize this on the next major. + # + def normalize_suffix(uri, trailing_slash: true) + if trailing_slash + uri.end_with?("/") ? uri : "#{uri}/" + else + uri.end_with?("/") ? uri.delete_suffix("/") : uri + end + end + end +end diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb index 984c1c3dcb..455319efe3 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb @@ -3,7 +3,9 @@ require_relative "connection_pool/version" class Bundler::ConnectionPool class Error < ::RuntimeError; end + class PoolShuttingDownError < ::Bundler::ConnectionPool::Error; end + class TimeoutError < ::Timeout::Error; end end @@ -67,7 +69,7 @@ class Bundler::ConnectionPool end end end - alias then with + alias_method :then, :with def checkout(options = {}) if ::Thread.current[@key] diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb index a7b1cf06a8..35d1d7cc35 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb @@ -49,7 +49,7 @@ class Bundler::ConnectionPool::TimedStack @resource.broadcast end end - alias << push + alias_method :<<, :push ## # Retrieves a connection from the stack. If a connection is available it is @@ -74,7 +74,7 @@ class Bundler::ConnectionPool::TimedStack return connection if connection to_wait = deadline - current_time - raise Bundler::ConnectionPool::TimeoutError, "Waited #{timeout} sec" if to_wait <= 0 + raise Bundler::ConnectionPool::TimeoutError, "Waited #{timeout} sec, #{length}/#{@max} available" if to_wait <= 0 @resource.wait(@mutex, to_wait) end end @@ -87,7 +87,7 @@ class Bundler::ConnectionPool::TimedStack # +:reload+ is +true+. def shutdown(reload: false, &block) - raise ArgumentError, "shutdown must receive a block" unless block_given? + raise ArgumentError, "shutdown must receive a block" unless block @mutex.synchronize do @shutdown_block = block diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb index 880170c06b..dd796d1021 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb @@ -30,7 +30,6 @@ class Bundler::ConnectionPool METHODS.include?(id) || with { |c| c.respond_to?(id, *args) } end - # rubocop:disable Style/MethodMissingSuper # rubocop:disable Style/MissingRespondToMissing if ::RUBY_VERSION >= "3.0.0" def method_missing(name, *args, **kwargs, &block) diff --git a/lib/bundler/vendor/fileutils/lib/fileutils.rb b/lib/bundler/vendor/fileutils/lib/fileutils.rb index 8f8faf30c8..211311c069 100644 --- a/lib/bundler/vendor/fileutils/lib/fileutils.rb +++ b/lib/bundler/vendor/fileutils/lib/fileutils.rb @@ -3,106 +3,184 @@ begin require 'rbconfig' rescue LoadError - # for make mjit-headers + # for make rjit-headers end +# Namespace for file utility methods for copying, moving, removing, etc. # -# = fileutils.rb +# == What's Here # -# Copyright (c) 2000-2007 Minero Aoki +# First, what’s elsewhere. \Module \Bundler::FileUtils: # -# This program is free software. -# You can distribute/modify this program under the same terms of ruby. +# - Inherits from {class Object}[rdoc-ref:Object]. +# - Supplements {class File}[rdoc-ref:File] +# (but is not included or extended there). # -# == module Bundler::FileUtils +# Here, module \Bundler::FileUtils provides methods that are useful for: # -# Namespace for several file utility methods for copying, moving, removing, etc. +# - {Creating}[rdoc-ref:FileUtils@Creating]. +# - {Deleting}[rdoc-ref:FileUtils@Deleting]. +# - {Querying}[rdoc-ref:FileUtils@Querying]. +# - {Setting}[rdoc-ref:FileUtils@Setting]. +# - {Comparing}[rdoc-ref:FileUtils@Comparing]. +# - {Copying}[rdoc-ref:FileUtils@Copying]. +# - {Moving}[rdoc-ref:FileUtils@Moving]. +# - {Options}[rdoc-ref:FileUtils@Options]. # -# === Module Functions +# === Creating # -# require 'bundler/vendor/fileutils/lib/fileutils' +# - ::mkdir: Creates directories. +# - ::mkdir_p, ::makedirs, ::mkpath: Creates directories, +# also creating ancestor directories as needed. +# - ::link_entry: Creates a hard link. +# - ::ln, ::link: Creates hard links. +# - ::ln_s, ::symlink: Creates symbolic links. +# - ::ln_sf: Creates symbolic links, overwriting if necessary. +# - ::ln_sr: Creates symbolic links relative to targets # -# Bundler::FileUtils.cd(dir, **options) -# Bundler::FileUtils.cd(dir, **options) {|dir| block } -# Bundler::FileUtils.pwd() -# Bundler::FileUtils.mkdir(dir, **options) -# Bundler::FileUtils.mkdir(list, **options) -# Bundler::FileUtils.mkdir_p(dir, **options) -# Bundler::FileUtils.mkdir_p(list, **options) -# Bundler::FileUtils.rmdir(dir, **options) -# Bundler::FileUtils.rmdir(list, **options) -# Bundler::FileUtils.ln(target, link, **options) -# Bundler::FileUtils.ln(targets, dir, **options) -# Bundler::FileUtils.ln_s(target, link, **options) -# Bundler::FileUtils.ln_s(targets, dir, **options) -# Bundler::FileUtils.ln_sf(target, link, **options) -# Bundler::FileUtils.cp(src, dest, **options) -# Bundler::FileUtils.cp(list, dir, **options) -# Bundler::FileUtils.cp_r(src, dest, **options) -# Bundler::FileUtils.cp_r(list, dir, **options) -# Bundler::FileUtils.mv(src, dest, **options) -# Bundler::FileUtils.mv(list, dir, **options) -# Bundler::FileUtils.rm(list, **options) -# Bundler::FileUtils.rm_r(list, **options) -# Bundler::FileUtils.rm_rf(list, **options) -# Bundler::FileUtils.install(src, dest, **options) -# Bundler::FileUtils.chmod(mode, list, **options) -# Bundler::FileUtils.chmod_R(mode, list, **options) -# Bundler::FileUtils.chown(user, group, list, **options) -# Bundler::FileUtils.chown_R(user, group, list, **options) -# Bundler::FileUtils.touch(list, **options) +# === Deleting # -# Possible <tt>options</tt> are: +# - ::remove_dir: Removes a directory and its descendants. +# - ::remove_entry: Removes an entry, including its descendants if it is a directory. +# - ::remove_entry_secure: Like ::remove_entry, but removes securely. +# - ::remove_file: Removes a file entry. +# - ::rm, ::remove: Removes entries. +# - ::rm_f, ::safe_unlink: Like ::rm, but removes forcibly. +# - ::rm_r: Removes entries and their descendants. +# - ::rm_rf, ::rmtree: Like ::rm_r, but removes forcibly. +# - ::rmdir: Removes directories. # -# <tt>:force</tt> :: forced operation (rewrite files if exist, remove -# directories if not empty, etc.); -# <tt>:verbose</tt> :: print command to be run, in bash syntax, before -# performing it; -# <tt>:preserve</tt> :: preserve object's group, user and modification -# time on copying; -# <tt>:noop</tt> :: no changes are made (usable in combination with -# <tt>:verbose</tt> which will print the command to run) +# === Querying # -# Each method documents the options that it honours. See also ::commands, -# ::options and ::options_of methods to introspect which command have which -# options. +# - ::pwd, ::getwd: Returns the path to the working directory. +# - ::uptodate?: Returns whether a given entry is newer than given other entries. # -# All methods that have the concept of a "source" file or directory can take -# either one file or a list of files in that argument. See the method -# documentation for examples. +# === Setting # -# There are some `low level' methods, which do not accept keyword arguments: +# - ::cd, ::chdir: Sets the working directory. +# - ::chmod: Sets permissions for an entry. +# - ::chmod_R: Sets permissions for an entry and its descendants. +# - ::chown: Sets the owner and group for entries. +# - ::chown_R: Sets the owner and group for entries and their descendants. +# - ::touch: Sets modification and access times for entries, +# creating if necessary. # -# Bundler::FileUtils.copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false) -# Bundler::FileUtils.copy_file(src, dest, preserve = false, dereference = true) -# Bundler::FileUtils.copy_stream(srcstream, deststream) -# Bundler::FileUtils.remove_entry(path, force = false) -# Bundler::FileUtils.remove_entry_secure(path, force = false) -# Bundler::FileUtils.remove_file(path, force = false) -# Bundler::FileUtils.compare_file(path_a, path_b) -# Bundler::FileUtils.compare_stream(stream_a, stream_b) -# Bundler::FileUtils.uptodate?(file, cmp_list) +# === Comparing # -# == module Bundler::FileUtils::Verbose +# - ::compare_file, ::cmp, ::identical?: Returns whether two entries are identical. +# - ::compare_stream: Returns whether two streams are identical. # -# This module has all methods of Bundler::FileUtils module, but it outputs messages -# before acting. This equates to passing the <tt>:verbose</tt> flag to methods -# in Bundler::FileUtils. +# === Copying # -# == module Bundler::FileUtils::NoWrite +# - ::copy_entry: Recursively copies an entry. +# - ::copy_file: Copies an entry. +# - ::copy_stream: Copies a stream. +# - ::cp, ::copy: Copies files. +# - ::cp_lr: Recursively creates hard links. +# - ::cp_r: Recursively copies files, retaining mode, owner, and group. +# - ::install: Recursively copies files, optionally setting mode, +# owner, and group. # -# This module has all methods of Bundler::FileUtils module, but never changes -# files/directories. This equates to passing the <tt>:noop</tt> flag to methods -# in Bundler::FileUtils. +# === Moving # -# == module Bundler::FileUtils::DryRun +# - ::mv, ::move: Moves entries. # -# This module has all methods of Bundler::FileUtils module, but never changes -# files/directories. This equates to passing the <tt>:noop</tt> and -# <tt>:verbose</tt> flags to methods in Bundler::FileUtils. +# === Options +# +# - ::collect_method: Returns the names of methods that accept a given option. +# - ::commands: Returns the names of methods that accept options. +# - ::have_option?: Returns whether a given method accepts a given option. +# - ::options: Returns all option names. +# - ::options_of: Returns the names of the options for a given method. +# +# == Path Arguments +# +# Some methods in \Bundler::FileUtils accept _path_ arguments, +# which are interpreted as paths to filesystem entries: +# +# - If the argument is a string, that value is the path. +# - If the argument has method +:to_path+, it is converted via that method. +# - If the argument has method +:to_str+, it is converted via that method. +# +# == About the Examples +# +# Some examples here involve trees of file entries. +# For these, we sometimes display trees using the +# {tree command-line utility}[https://en.wikipedia.org/wiki/Tree_(command)], +# which is a recursive directory-listing utility that produces +# a depth-indented listing of files and directories. +# +# We use a helper method to launch the command and control the format: +# +# def tree(dirpath = '.') +# command = "tree --noreport --charset=ascii #{dirpath}" +# system(command) +# end +# +# To illustrate: +# +# tree('src0') +# # => src0 +# # |-- sub0 +# # | |-- src0.txt +# # | `-- src1.txt +# # `-- sub1 +# # |-- src2.txt +# # `-- src3.txt +# +# == Avoiding the TOCTTOU Vulnerability +# +# For certain methods that recursively remove entries, +# there is a potential vulnerability called the +# {Time-of-check to time-of-use}[https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use], +# or TOCTTOU, vulnerability that can exist when: +# +# - An ancestor directory of the entry at the target path is world writable; +# such directories include <tt>/tmp</tt>. +# - The directory tree at the target path includes: +# +# - A world-writable descendant directory. +# - A symbolic link. +# +# To avoid that vulnerability, you can use this method to remove entries: +# +# - Bundler::FileUtils.remove_entry_secure: removes recursively +# if the target path points to a directory. +# +# Also available are these methods, +# each of which calls \Bundler::FileUtils.remove_entry_secure: +# +# - Bundler::FileUtils.rm_r with keyword argument <tt>secure: true</tt>. +# - Bundler::FileUtils.rm_rf with keyword argument <tt>secure: true</tt>. +# +# Finally, this method for moving entries calls \Bundler::FileUtils.remove_entry_secure +# if the source and destination are on different file systems +# (which means that the "move" is really a copy and remove): +# +# - Bundler::FileUtils.mv with keyword argument <tt>secure: true</tt>. +# +# \Method \Bundler::FileUtils.remove_entry_secure removes securely +# by applying a special pre-process: +# +# - If the target path points to a directory, this method uses methods +# {File#chown}[rdoc-ref:File#chown] +# and {File#chmod}[rdoc-ref:File#chmod] +# in removing directories. +# - The owner of the target directory should be either the current process +# or the super user (root). +# +# WARNING: You must ensure that *ALL* parent directories cannot be +# moved by other untrusted users. For example, parent directories +# should not be owned by untrusted users, and should not be world +# writable except when the sticky bit is set. +# +# For details of this security vulnerability, see Perl cases: +# +# - {CVE-2005-0448}[https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448]. +# - {CVE-2004-0452}[https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452]. # module Bundler::FileUtils - VERSION = "1.4.1" + VERSION = "1.7.0" def self.private_module_function(name) #:nodoc: module_function name @@ -110,7 +188,13 @@ module Bundler::FileUtils end # - # Returns the name of the current directory. + # Returns a string containing the path to the current directory: + # + # Bundler::FileUtils.pwd # => "/rdoc/fileutils" + # + # Bundler::FileUtils.getwd is an alias for Bundler::FileUtils.pwd. + # + # Related: Bundler::FileUtils.cd. # def pwd Dir.pwd @@ -120,19 +204,40 @@ module Bundler::FileUtils alias getwd pwd module_function :getwd + # Changes the working directory to the given +dir+, which + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]: + # + # With no block given, + # changes the current directory to the directory at +dir+; returns zero: + # + # Bundler::FileUtils.pwd # => "/rdoc/fileutils" + # Bundler::FileUtils.cd('..') + # Bundler::FileUtils.pwd # => "/rdoc" + # Bundler::FileUtils.cd('fileutils') + # + # With a block given, changes the current directory to the directory + # at +dir+, calls the block with argument +dir+, + # and restores the original current directory; returns the block's value: # - # Changes the current directory to the directory +dir+. + # Bundler::FileUtils.pwd # => "/rdoc/fileutils" + # Bundler::FileUtils.cd('..') { |arg| [arg, Bundler::FileUtils.pwd] } # => ["..", "/rdoc"] + # Bundler::FileUtils.pwd # => "/rdoc/fileutils" # - # If this method is called with block, resumes to the previous - # working directory after the block execution has finished. + # Keyword arguments: # - # Bundler::FileUtils.cd('/') # change directory + # - <tt>verbose: true</tt> - prints an equivalent command: # - # Bundler::FileUtils.cd('/', verbose: true) # change directory and report it + # Bundler::FileUtils.cd('..') + # Bundler::FileUtils.cd('fileutils') # - # Bundler::FileUtils.cd('/') do # change directory - # # ... # do something - # end # return to original directory + # Output: + # + # cd .. + # cd fileutils + # + # Bundler::FileUtils.chdir is an alias for Bundler::FileUtils.cd. + # + # Related: Bundler::FileUtils.pwd. # def cd(dir, verbose: nil, &block) # :yield: dir fu_output_message "cd #{dir}" if verbose @@ -146,11 +251,19 @@ module Bundler::FileUtils module_function :chdir # - # Returns true if +new+ is newer than all +old_list+. - # Non-existent files are older than any file. + # Returns +true+ if the file at path +new+ + # is newer than all the files at paths in array +old_list+; + # +false+ otherwise. + # + # Argument +new+ and the elements of +old_list+ + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]: # - # Bundler::FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \ - # system 'make hello.o' + # Bundler::FileUtils.uptodate?('Rakefile', ['Gemfile', 'README.md']) # => true + # Bundler::FileUtils.uptodate?('Gemfile', ['Rakefile', 'README.md']) # => false + # + # A non-existent file is considered to be infinitely old. + # + # Related: Bundler::FileUtils.touch. # def uptodate?(new, old_list) return false unless File.exist?(new) @@ -170,12 +283,39 @@ module Bundler::FileUtils private_module_function :remove_trailing_slash # - # Creates one or more directories. + # Creates directories at the paths in the given +list+ + # (a single path or an array of paths); + # returns +list+ if it is an array, <tt>[list]</tt> otherwise. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # With no keyword arguments, creates a directory at each +path+ in +list+ + # by calling: <tt>Dir.mkdir(path, mode)</tt>; + # see {Dir.mkdir}[rdoc-ref:Dir.mkdir]: + # + # Bundler::FileUtils.mkdir(%w[tmp0 tmp1]) # => ["tmp0", "tmp1"] + # Bundler::FileUtils.mkdir('tmp4') # => ["tmp4"] + # + # Keyword arguments: + # + # - <tt>mode: <i>mode</i></tt> - also calls <tt>File.chmod(mode, path)</tt>; + # see {File.chmod}[rdoc-ref:File.chmod]. + # - <tt>noop: true</tt> - does not create directories. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.mkdir(%w[tmp0 tmp1], verbose: true) + # Bundler::FileUtils.mkdir(%w[tmp2 tmp3], mode: 0700, verbose: true) + # + # Output: # - # Bundler::FileUtils.mkdir 'test' - # Bundler::FileUtils.mkdir %w(tmp data) - # Bundler::FileUtils.mkdir 'notexist', noop: true # Does not really create. - # Bundler::FileUtils.mkdir 'tmp', mode: 0700 + # mkdir tmp0 tmp1 + # mkdir -m 700 tmp2 tmp3 + # + # Raises an exception if any path points to an existing + # file or directory, or if for any reason a directory cannot be created. + # + # Related: Bundler::FileUtils.mkdir_p. # def mkdir(list, mode: nil, noop: nil, verbose: nil) list = fu_list(list) @@ -189,40 +329,56 @@ module Bundler::FileUtils module_function :mkdir # - # Creates a directory and all its parent directories. - # For example, + # Creates directories at the paths in the given +list+ + # (a single path or an array of paths), + # also creating ancestor directories as needed; + # returns +list+ if it is an array, <tt>[list]</tt> otherwise. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # With no keyword arguments, creates a directory at each +path+ in +list+, + # along with any needed ancestor directories, + # by calling: <tt>Dir.mkdir(path, mode)</tt>; + # see {Dir.mkdir}[rdoc-ref:Dir.mkdir]: + # + # Bundler::FileUtils.mkdir_p(%w[tmp0/tmp1 tmp2/tmp3]) # => ["tmp0/tmp1", "tmp2/tmp3"] + # Bundler::FileUtils.mkdir_p('tmp4/tmp5') # => ["tmp4/tmp5"] + # + # Keyword arguments: + # + # - <tt>mode: <i>mode</i></tt> - also calls <tt>File.chmod(mode, path)</tt>; + # see {File.chmod}[rdoc-ref:File.chmod]. + # - <tt>noop: true</tt> - does not create directories. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.mkdir_p(%w[tmp0 tmp1], verbose: true) + # Bundler::FileUtils.mkdir_p(%w[tmp2 tmp3], mode: 0700, verbose: true) # - # Bundler::FileUtils.mkdir_p '/usr/local/lib/ruby' + # Output: # - # causes to make following directories, if they do not exist. + # mkdir -p tmp0 tmp1 + # mkdir -p -m 700 tmp2 tmp3 # - # * /usr - # * /usr/local - # * /usr/local/lib - # * /usr/local/lib/ruby + # Raises an exception if for any reason a directory cannot be created. # - # You can pass several directories at a time in a list. + # Bundler::FileUtils.mkpath and Bundler::FileUtils.makedirs are aliases for Bundler::FileUtils.mkdir_p. + # + # Related: Bundler::FileUtils.mkdir. # def mkdir_p(list, mode: nil, noop: nil, verbose: nil) list = fu_list(list) fu_output_message "mkdir -p #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose return *list if noop - list.map {|path| remove_trailing_slash(path)}.each do |path| - # optimize for the most common case - begin - fu_mkdir path, mode - next - rescue SystemCallError - next if File.directory?(path) - end + list.each do |item| + path = remove_trailing_slash(item) stack = [] - until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/" + until File.directory?(path) || File.dirname(path) == path stack.push path path = File.dirname(path) end - stack.pop # root directory should exist stack.reverse_each do |dir| begin fu_mkdir dir, mode @@ -253,12 +409,39 @@ module Bundler::FileUtils private_module_function :fu_mkdir # - # Removes one or more directories. + # Removes directories at the paths in the given +list+ + # (a single path or an array of paths); + # returns +list+, if it is an array, <tt>[list]</tt> otherwise. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # With no keyword arguments, removes the directory at each +path+ in +list+, + # by calling: <tt>Dir.rmdir(path)</tt>; + # see {Dir.rmdir}[rdoc-ref:Dir.rmdir]: + # + # Bundler::FileUtils.rmdir(%w[tmp0/tmp1 tmp2/tmp3]) # => ["tmp0/tmp1", "tmp2/tmp3"] + # Bundler::FileUtils.rmdir('tmp4/tmp5') # => ["tmp4/tmp5"] + # + # Keyword arguments: + # + # - <tt>parents: true</tt> - removes successive ancestor directories + # if empty. + # - <tt>noop: true</tt> - does not remove directories. + # - <tt>verbose: true</tt> - prints an equivalent command: # - # Bundler::FileUtils.rmdir 'somedir' - # Bundler::FileUtils.rmdir %w(somedir anydir otherdir) - # # Does not really remove directory; outputs message. - # Bundler::FileUtils.rmdir 'somedir', verbose: true, noop: true + # Bundler::FileUtils.rmdir(%w[tmp0/tmp1 tmp2/tmp3], parents: true, verbose: true) + # Bundler::FileUtils.rmdir('tmp4/tmp5', parents: true, verbose: true) + # + # Output: + # + # rmdir -p tmp0/tmp1 tmp2/tmp3 + # rmdir -p tmp4/tmp5 + # + # Raises an exception if a directory does not exist + # or if for any reason a directory cannot be removed. + # + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def rmdir(list, parents: nil, noop: nil, verbose: nil) list = fu_list(list) @@ -279,26 +462,62 @@ module Bundler::FileUtils end module_function :rmdir + # Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link]. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # When +src+ is the path to an existing file + # and +dest+ is the path to a non-existent file, + # creates a hard link at +dest+ pointing to +src+; returns zero: + # + # Dir.children('tmp0/') # => ["t.txt"] + # Dir.children('tmp1/') # => [] + # Bundler::FileUtils.ln('tmp0/t.txt', 'tmp1/t.lnk') # => 0 + # Dir.children('tmp1/') # => ["t.lnk"] + # + # When +src+ is the path to an existing file + # and +dest+ is the path to an existing directory, + # creates a hard link at <tt>dest/src</tt> pointing to +src+; returns zero: # - # :call-seq: - # Bundler::FileUtils.ln(target, link, force: nil, noop: nil, verbose: nil) - # Bundler::FileUtils.ln(target, dir, force: nil, noop: nil, verbose: nil) - # Bundler::FileUtils.ln(targets, dir, force: nil, noop: nil, verbose: nil) + # Dir.children('tmp2') # => ["t.dat"] + # Dir.children('tmp3') # => [] + # Bundler::FileUtils.ln('tmp2/t.dat', 'tmp3') # => 0 + # Dir.children('tmp3') # => ["t.dat"] # - # In the first form, creates a hard link +link+ which points to +target+. - # If +link+ already exists, raises Errno::EEXIST. - # But if the +force+ option is set, overwrites +link+. + # When +src+ is an array of paths to existing files + # and +dest+ is the path to an existing directory, + # then for each path +target+ in +src+, + # creates a hard link at <tt>dest/target</tt> pointing to +target+; + # returns +src+: # - # Bundler::FileUtils.ln 'gcc', 'cc', verbose: true - # Bundler::FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs' + # Dir.children('tmp4/') # => [] + # Bundler::FileUtils.ln(['tmp0/t.txt', 'tmp2/t.dat'], 'tmp4/') # => ["tmp0/t.txt", "tmp2/t.dat"] + # Dir.children('tmp4/') # => ["t.dat", "t.txt"] # - # In the second form, creates a link +dir/target+ pointing to +target+. - # In the third form, creates several hard links in the directory +dir+, - # pointing to each item in +targets+. - # If +dir+ is not a directory, raises Errno::ENOTDIR. + # Keyword arguments: # - # Bundler::FileUtils.cd '/sbin' - # Bundler::FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked. + # - <tt>force: true</tt> - overwrites +dest+ if it exists. + # - <tt>noop: true</tt> - does not create links. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.ln('tmp0/t.txt', 'tmp1/t.lnk', verbose: true) + # Bundler::FileUtils.ln('tmp2/t.dat', 'tmp3', verbose: true) + # Bundler::FileUtils.ln(['tmp0/t.txt', 'tmp2/t.dat'], 'tmp4/', verbose: true) + # + # Output: + # + # ln tmp0/t.txt tmp1/t.lnk + # ln tmp2/t.dat tmp3 + # ln tmp0/t.txt tmp2/t.dat tmp4/ + # + # Raises an exception if +dest+ is the path to an existing file + # and keyword argument +force+ is not +true+. + # + # Bundler::FileUtils#link is an alias for Bundler::FileUtils#ln. + # + # Related: Bundler::FileUtils.link_entry (has different options). # def ln(src, dest, force: nil, noop: nil, verbose: nil) fu_output_message "ln#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose @@ -313,28 +532,103 @@ module Bundler::FileUtils alias link ln module_function :link - # - # Hard link +src+ to +dest+. If +src+ is a directory, this method links - # all its contents recursively. If +dest+ is a directory, links - # +src+ to +dest/src+. - # - # +src+ can be a list of files. - # - # If +dereference_root+ is true, this method dereference tree root. - # - # If +remove_destination+ is true, this method removes each destination file before copy. - # - # Bundler::FileUtils.rm_r site_ruby + '/mylib', force: true - # Bundler::FileUtils.cp_lr 'lib/', site_ruby + '/mylib' - # - # # Examples of linking several files to target directory. - # Bundler::FileUtils.cp_lr %w(mail.rb field.rb debug/), site_ruby + '/tmail' - # Bundler::FileUtils.cp_lr Dir.glob('*.rb'), '/home/aamine/lib/ruby', noop: true, verbose: true - # - # # If you want to link all contents of a directory instead of the - # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y, - # # use the following code. - # Bundler::FileUtils.cp_lr 'src/.', 'dest' # cp_lr('src', 'dest') makes dest/src, but this doesn't. + # Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link]. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # If +src+ is the path to a directory and +dest+ does not exist, + # creates links +dest+ and descendents pointing to +src+ and its descendents: + # + # tree('src0') + # # => src0 + # # |-- sub0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- sub1 + # # |-- src2.txt + # # `-- src3.txt + # File.exist?('dest0') # => false + # Bundler::FileUtils.cp_lr('src0', 'dest0') + # tree('dest0') + # # => dest0 + # # |-- sub0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- sub1 + # # |-- src2.txt + # # `-- src3.txt + # + # If +src+ and +dest+ are both paths to directories, + # creates links <tt>dest/src</tt> and descendents + # pointing to +src+ and its descendents: + # + # tree('src1') + # # => src1 + # # |-- sub0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- sub1 + # # |-- src2.txt + # # `-- src3.txt + # Bundler::FileUtils.mkdir('dest1') + # Bundler::FileUtils.cp_lr('src1', 'dest1') + # tree('dest1') + # # => dest1 + # # `-- src1 + # # |-- sub0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- sub1 + # # |-- src2.txt + # # `-- src3.txt + # + # If +src+ is an array of paths to entries and +dest+ is the path to a directory, + # for each path +filepath+ in +src+, creates a link at <tt>dest/filepath</tt> + # pointing to that path: + # + # tree('src2') + # # => src2 + # # |-- sub0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- sub1 + # # |-- src2.txt + # # `-- src3.txt + # Bundler::FileUtils.mkdir('dest2') + # Bundler::FileUtils.cp_lr(['src2/sub0', 'src2/sub1'], 'dest2') + # tree('dest2') + # # => dest2 + # # |-- sub0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- sub1 + # # |-- src2.txt + # # `-- src3.txt + # + # Keyword arguments: + # + # - <tt>dereference_root: false</tt> - if +src+ is a symbolic link, + # does not dereference it. + # - <tt>noop: true</tt> - does not create links. + # - <tt>remove_destination: true</tt> - removes +dest+ before creating links. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.cp_lr('src0', 'dest0', noop: true, verbose: true) + # Bundler::FileUtils.cp_lr('src1', 'dest1', noop: true, verbose: true) + # Bundler::FileUtils.cp_lr(['src2/sub0', 'src2/sub1'], 'dest2', noop: true, verbose: true) + # + # Output: + # + # cp -lr src0 dest0 + # cp -lr src1 dest1 + # cp -lr src2/sub0 src2/sub1 dest2 + # + # Raises an exception if +dest+ is the path to an existing file or directory + # and keyword argument <tt>remove_destination: true</tt> is not given. + # + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def cp_lr(src, dest, noop: nil, verbose: nil, dereference_root: true, remove_destination: false) @@ -346,27 +640,81 @@ module Bundler::FileUtils end module_function :cp_lr + # Creates {symbolic links}[https://en.wikipedia.org/wiki/Symbolic_link]. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # If +src+ is the path to an existing file: + # + # - When +dest+ is the path to a non-existent file, + # creates a symbolic link at +dest+ pointing to +src+: + # + # Bundler::FileUtils.touch('src0.txt') + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.ln_s('src0.txt', 'dest0.txt') + # File.symlink?('dest0.txt') # => true # - # :call-seq: - # Bundler::FileUtils.ln_s(target, link, force: nil, noop: nil, verbose: nil) - # Bundler::FileUtils.ln_s(target, dir, force: nil, noop: nil, verbose: nil) - # Bundler::FileUtils.ln_s(targets, dir, force: nil, noop: nil, verbose: nil) + # - When +dest+ is the path to an existing file, + # creates a symbolic link at +dest+ pointing to +src+ + # if and only if keyword argument <tt>force: true</tt> is given + # (raises an exception otherwise): # - # In the first form, creates a symbolic link +link+ which points to +target+. - # If +link+ already exists, raises Errno::EEXIST. - # But if the <tt>force</tt> option is set, overwrites +link+. + # Bundler::FileUtils.touch('src1.txt') + # Bundler::FileUtils.touch('dest1.txt') + # Bundler::FileUtils.ln_s('src1.txt', 'dest1.txt', force: true) + # FileTest.symlink?('dest1.txt') # => true # - # Bundler::FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby' - # Bundler::FileUtils.ln_s 'verylongsourcefilename.c', 'c', force: true + # Bundler::FileUtils.ln_s('src1.txt', 'dest1.txt') # Raises Errno::EEXIST. # - # In the second form, creates a link +dir/target+ pointing to +target+. - # In the third form, creates several symbolic links in the directory +dir+, - # pointing to each item in +targets+. - # If +dir+ is not a directory, raises Errno::ENOTDIR. + # If +dest+ is the path to a directory, + # creates a symbolic link at <tt>dest/src</tt> pointing to +src+: # - # Bundler::FileUtils.ln_s Dir.glob('/bin/*.rb'), '/home/foo/bin' + # Bundler::FileUtils.touch('src2.txt') + # Bundler::FileUtils.mkdir('destdir2') + # Bundler::FileUtils.ln_s('src2.txt', 'destdir2') + # File.symlink?('destdir2/src2.txt') # => true # - def ln_s(src, dest, force: nil, noop: nil, verbose: nil) + # If +src+ is an array of paths to existing files and +dest+ is a directory, + # for each child +child+ in +src+ creates a symbolic link <tt>dest/child</tt> + # pointing to +child+: + # + # Bundler::FileUtils.mkdir('srcdir3') + # Bundler::FileUtils.touch('srcdir3/src0.txt') + # Bundler::FileUtils.touch('srcdir3/src1.txt') + # Bundler::FileUtils.mkdir('destdir3') + # Bundler::FileUtils.ln_s(['srcdir3/src0.txt', 'srcdir3/src1.txt'], 'destdir3') + # File.symlink?('destdir3/src0.txt') # => true + # File.symlink?('destdir3/src1.txt') # => true + # + # Keyword arguments: + # + # - <tt>force: true</tt> - overwrites +dest+ if it exists. + # - <tt>relative: false</tt> - create links relative to +dest+. + # - <tt>noop: true</tt> - does not create links. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.ln_s('src0.txt', 'dest0.txt', noop: true, verbose: true) + # Bundler::FileUtils.ln_s('src1.txt', 'destdir1', noop: true, verbose: true) + # Bundler::FileUtils.ln_s('src2.txt', 'dest2.txt', force: true, noop: true, verbose: true) + # Bundler::FileUtils.ln_s(['srcdir3/src0.txt', 'srcdir3/src1.txt'], 'destdir3', noop: true, verbose: true) + # + # Output: + # + # ln -s src0.txt dest0.txt + # ln -s src1.txt destdir1 + # ln -sf src2.txt dest2.txt + # ln -s srcdir3/src0.txt srcdir3/src1.txt destdir3 + # + # Bundler::FileUtils.symlink is an alias for Bundler::FileUtils.ln_s. + # + # Related: Bundler::FileUtils.ln_sf. + # + def ln_s(src, dest, force: nil, relative: false, target_directory: true, noop: nil, verbose: nil) + if relative + return ln_sr(src, dest, force: force, noop: noop, verbose: verbose) + end fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose return if noop fu_each_src_dest0(src, dest) do |s,d| @@ -379,29 +727,95 @@ module Bundler::FileUtils alias symlink ln_s module_function :symlink - # - # :call-seq: - # Bundler::FileUtils.ln_sf(*args) - # - # Same as - # - # Bundler::FileUtils.ln_s(*args, force: true) + # Like Bundler::FileUtils.ln_s, but always with keyword argument <tt>force: true</tt> given. # def ln_sf(src, dest, noop: nil, verbose: nil) ln_s src, dest, force: true, noop: noop, verbose: verbose end module_function :ln_sf + # Like Bundler::FileUtils.ln_s, but create links relative to +dest+. + # + def ln_sr(src, dest, target_directory: true, force: nil, noop: nil, verbose: nil) + options = "#{force ? 'f' : ''}#{target_directory ? '' : 'T'}" + dest = File.path(dest) + srcs = Array(src) + link = proc do |s, target_dir_p = true| + s = File.path(s) + if target_dir_p + d = File.join(destdirs = dest, File.basename(s)) + else + destdirs = File.dirname(d = dest) + end + destdirs = fu_split_path(File.realpath(destdirs)) + if fu_starting_path?(s) + srcdirs = fu_split_path((File.realdirpath(s) rescue File.expand_path(s))) + base = fu_relative_components_from(srcdirs, destdirs) + s = File.join(*base) + else + srcdirs = fu_clean_components(*fu_split_path(s)) + base = fu_relative_components_from(fu_split_path(Dir.pwd), destdirs) + while srcdirs.first&. == ".." and base.last&.!=("..") and !fu_starting_path?(base.last) + srcdirs.shift + base.pop + end + s = File.join(*base, *srcdirs) + end + fu_output_message "ln -s#{options} #{s} #{d}" if verbose + next if noop + remove_file d, true if force + File.symlink s, d + end + case srcs.size + when 0 + when 1 + link[srcs[0], target_directory && File.directory?(dest)] + else + srcs.each(&link) + end + end + module_function :ln_sr + + # Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link]; returns +nil+. # - # Hard links a file system entry +src+ to +dest+. - # If +src+ is a directory, this method links its contents recursively. + # Arguments +src+ and +dest+ + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. # - # Both of +src+ and +dest+ must be a path name. - # +src+ must exist, +dest+ must not exist. + # If +src+ is the path to a file and +dest+ does not exist, + # creates a hard link at +dest+ pointing to +src+: # - # If +dereference_root+ is true, this method dereferences the tree root. + # Bundler::FileUtils.touch('src0.txt') + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.link_entry('src0.txt', 'dest0.txt') + # File.file?('dest0.txt') # => true # - # If +remove_destination+ is true, this method removes each destination file before copy. + # If +src+ is the path to a directory and +dest+ does not exist, + # recursively creates hard links at +dest+ pointing to paths in +src+: + # + # Bundler::FileUtils.mkdir_p(['src1/dir0', 'src1/dir1']) + # src_file_paths = [ + # 'src1/dir0/t0.txt', + # 'src1/dir0/t1.txt', + # 'src1/dir1/t2.txt', + # 'src1/dir1/t3.txt', + # ] + # Bundler::FileUtils.touch(src_file_paths) + # File.directory?('dest1') # => true + # Bundler::FileUtils.link_entry('src1', 'dest1') + # File.file?('dest1/dir0/t0.txt') # => true + # File.file?('dest1/dir0/t1.txt') # => true + # File.file?('dest1/dir1/t2.txt') # => true + # File.file?('dest1/dir1/t3.txt') # => true + # + # Keyword arguments: + # + # - <tt>dereference_root: true</tt> - dereferences +src+ if it is a symbolic link. + # - <tt>remove_destination: true</tt> - removes +dest+ before creating links. + # + # Raises an exception if +dest+ is the path to an existing file or directory + # and keyword argument <tt>remove_destination: true</tt> is not given. + # + # Related: Bundler::FileUtils.ln (has different options). # def link_entry(src, dest, dereference_root = false, remove_destination = false) Entry_.new(src, nil, dereference_root).traverse do |ent| @@ -412,16 +826,59 @@ module Bundler::FileUtils end module_function :link_entry + # Copies files. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # If +src+ is the path to a file and +dest+ is not the path to a directory, + # copies +src+ to +dest+: + # + # Bundler::FileUtils.touch('src0.txt') + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.cp('src0.txt', 'dest0.txt') + # File.file?('dest0.txt') # => true # - # Copies a file content +src+ to +dest+. If +dest+ is a directory, - # copies +src+ to +dest/src+. + # If +src+ is the path to a file and +dest+ is the path to a directory, + # copies +src+ to <tt>dest/src</tt>: # - # If +src+ is a list of files, then +dest+ must be a directory. + # Bundler::FileUtils.touch('src1.txt') + # Bundler::FileUtils.mkdir('dest1') + # Bundler::FileUtils.cp('src1.txt', 'dest1') + # File.file?('dest1/src1.txt') # => true # - # Bundler::FileUtils.cp 'eval.c', 'eval.c.org' - # Bundler::FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6' - # Bundler::FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', verbose: true - # Bundler::FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink + # If +src+ is an array of paths to files and +dest+ is the path to a directory, + # copies from each +src+ to +dest+: + # + # src_file_paths = ['src2.txt', 'src2.dat'] + # Bundler::FileUtils.touch(src_file_paths) + # Bundler::FileUtils.mkdir('dest2') + # Bundler::FileUtils.cp(src_file_paths, 'dest2') + # File.file?('dest2/src2.txt') # => true + # File.file?('dest2/src2.dat') # => true + # + # Keyword arguments: + # + # - <tt>preserve: true</tt> - preserves file times. + # - <tt>noop: true</tt> - does not copy files. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.cp('src0.txt', 'dest0.txt', noop: true, verbose: true) + # Bundler::FileUtils.cp('src1.txt', 'dest1', noop: true, verbose: true) + # Bundler::FileUtils.cp(src_file_paths, 'dest2', noop: true, verbose: true) + # + # Output: + # + # cp src0.txt dest0.txt + # cp src1.txt dest1 + # cp src2.txt src2.dat dest2 + # + # Raises an exception if +src+ is a directory. + # + # Bundler::FileUtils.copy is an alias for Bundler::FileUtils.cp. + # + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def cp(src, dest, preserve: nil, noop: nil, verbose: nil) fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose @@ -435,30 +892,105 @@ module Bundler::FileUtils alias copy cp module_function :copy - # - # Copies +src+ to +dest+. If +src+ is a directory, this method copies - # all its contents recursively. If +dest+ is a directory, copies - # +src+ to +dest/src+. - # - # +src+ can be a list of files. - # - # If +dereference_root+ is true, this method dereference tree root. - # - # If +remove_destination+ is true, this method removes each destination file before copy. - # - # # Installing Ruby library "mylib" under the site_ruby - # Bundler::FileUtils.rm_r site_ruby + '/mylib', force: true - # Bundler::FileUtils.cp_r 'lib/', site_ruby + '/mylib' - # - # # Examples of copying several files to target directory. - # Bundler::FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail' - # Bundler::FileUtils.cp_r Dir.glob('*.rb'), '/home/foo/lib/ruby', noop: true, verbose: true - # - # # If you want to copy all contents of a directory instead of the - # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y, - # # use following code. - # Bundler::FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes dest/src, - # # but this doesn't. + # Recursively copies files. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # The mode, owner, and group are retained in the copy; + # to change those, use Bundler::FileUtils.install instead. + # + # If +src+ is the path to a file and +dest+ is not the path to a directory, + # copies +src+ to +dest+: + # + # Bundler::FileUtils.touch('src0.txt') + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.cp_r('src0.txt', 'dest0.txt') + # File.file?('dest0.txt') # => true + # + # If +src+ is the path to a file and +dest+ is the path to a directory, + # copies +src+ to <tt>dest/src</tt>: + # + # Bundler::FileUtils.touch('src1.txt') + # Bundler::FileUtils.mkdir('dest1') + # Bundler::FileUtils.cp_r('src1.txt', 'dest1') + # File.file?('dest1/src1.txt') # => true + # + # If +src+ is the path to a directory and +dest+ does not exist, + # recursively copies +src+ to +dest+: + # + # tree('src2') + # # => src2 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt + # Bundler::FileUtils.exist?('dest2') # => false + # Bundler::FileUtils.cp_r('src2', 'dest2') + # tree('dest2') + # # => dest2 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt + # + # If +src+ and +dest+ are paths to directories, + # recursively copies +src+ to <tt>dest/src</tt>: + # + # tree('src3') + # # => src3 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt + # Bundler::FileUtils.mkdir('dest3') + # Bundler::FileUtils.cp_r('src3', 'dest3') + # tree('dest3') + # # => dest3 + # # `-- src3 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt + # + # If +src+ is an array of paths and +dest+ is a directory, + # recursively copies from each path in +src+ to +dest+; + # the paths in +src+ may point to files and/or directories. + # + # Keyword arguments: + # + # - <tt>dereference_root: false</tt> - if +src+ is a symbolic link, + # does not dereference it. + # - <tt>noop: true</tt> - does not copy files. + # - <tt>preserve: true</tt> - preserves file times. + # - <tt>remove_destination: true</tt> - removes +dest+ before copying files. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.cp_r('src0.txt', 'dest0.txt', noop: true, verbose: true) + # Bundler::FileUtils.cp_r('src1.txt', 'dest1', noop: true, verbose: true) + # Bundler::FileUtils.cp_r('src2', 'dest2', noop: true, verbose: true) + # Bundler::FileUtils.cp_r('src3', 'dest3', noop: true, verbose: true) + # + # Output: + # + # cp -r src0.txt dest0.txt + # cp -r src1.txt dest1 + # cp -r src2 dest2 + # cp -r src3 dest3 + # + # Raises an exception of +src+ is the path to a directory + # and +dest+ is the path to a file. + # + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil, dereference_root: true, remove_destination: nil) @@ -470,21 +1002,50 @@ module Bundler::FileUtils end module_function :cp_r + # Recursively copies files from +src+ to +dest+. + # + # Arguments +src+ and +dest+ + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. # - # Copies a file system entry +src+ to +dest+. - # If +src+ is a directory, this method copies its contents recursively. - # This method preserves file types, c.f. symlink, directory... - # (FIFO, device files and etc. are not supported yet) + # If +src+ is the path to a file, copies +src+ to +dest+: # - # Both of +src+ and +dest+ must be a path name. - # +src+ must exist, +dest+ must not exist. + # Bundler::FileUtils.touch('src0.txt') + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.copy_entry('src0.txt', 'dest0.txt') + # File.file?('dest0.txt') # => true # - # If +preserve+ is true, this method preserves owner, group, and - # modified time. Permissions are copied regardless +preserve+. + # If +src+ is a directory, recursively copies +src+ to +dest+: # - # If +dereference_root+ is true, this method dereference tree root. + # tree('src1') + # # => src1 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt + # Bundler::FileUtils.copy_entry('src1', 'dest1') + # tree('dest1') + # # => dest1 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt # - # If +remove_destination+ is true, this method removes each destination file before copy. + # The recursive copying preserves file types for regular files, + # directories, and symbolic links; + # other file types (FIFO streams, device files, etc.) are not supported. + # + # Keyword arguments: + # + # - <tt>dereference_root: true</tt> - if +src+ is a symbolic link, + # follows the link. + # - <tt>preserve: true</tt> - preserves file times. + # - <tt>remove_destination: true</tt> - removes +dest+ before copying files. + # + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false) if dereference_root @@ -502,9 +1063,25 @@ module Bundler::FileUtils end module_function :copy_entry + # Copies file from +src+ to +dest+, which should not be directories. + # + # Arguments +src+ and +dest+ + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # Examples: # - # Copies file contents of +src+ to +dest+. - # Both of +src+ and +dest+ must be a path name. + # Bundler::FileUtils.touch('src0.txt') + # Bundler::FileUtils.copy_file('src0.txt', 'dest0.txt') + # File.file?('dest0.txt') # => true + # + # Keyword arguments: + # + # - <tt>dereference: false</tt> - if +src+ is a symbolic link, + # does not follow the link. + # - <tt>preserve: true</tt> - preserves file times. + # - <tt>remove_destination: true</tt> - removes +dest+ before copying files. + # + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def copy_file(src, dest, preserve = false, dereference = true) ent = Entry_.new(src, nil, dereference) @@ -513,25 +1090,81 @@ module Bundler::FileUtils end module_function :copy_file + # Copies \IO stream +src+ to \IO stream +dest+ via + # {IO.copy_stream}[rdoc-ref:IO.copy_stream]. # - # Copies stream +src+ to +dest+. - # +src+ must respond to #read(n) and - # +dest+ must respond to #write(str). + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def copy_stream(src, dest) IO.copy_stream(src, dest) end module_function :copy_stream - # - # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different - # disk partition, the file is copied then the original file is removed. - # - # Bundler::FileUtils.mv 'badname.rb', 'goodname.rb' - # Bundler::FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', force: true # no error - # - # Bundler::FileUtils.mv %w(junk.txt dust.txt), '/home/foo/.trash/' - # Bundler::FileUtils.mv Dir.glob('test*.rb'), 'test', noop: true, verbose: true + # Moves entries. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # If +src+ and +dest+ are on different file systems, + # first copies, then removes +src+. + # + # May cause a local vulnerability if not called with keyword argument + # <tt>secure: true</tt>; + # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability]. + # + # If +src+ is the path to a single file or directory and +dest+ does not exist, + # moves +src+ to +dest+: + # + # tree('src0') + # # => src0 + # # |-- src0.txt + # # `-- src1.txt + # File.exist?('dest0') # => false + # Bundler::FileUtils.mv('src0', 'dest0') + # File.exist?('src0') # => false + # tree('dest0') + # # => dest0 + # # |-- src0.txt + # # `-- src1.txt + # + # If +src+ is an array of paths to files and directories + # and +dest+ is the path to a directory, + # copies from each path in the array to +dest+: + # + # File.file?('src1.txt') # => true + # tree('src1') + # # => src1 + # # |-- src.dat + # # `-- src.txt + # Dir.empty?('dest1') # => true + # Bundler::FileUtils.mv(['src1.txt', 'src1'], 'dest1') + # tree('dest1') + # # => dest1 + # # |-- src1 + # # | |-- src.dat + # # | `-- src.txt + # # `-- src1.txt + # + # Keyword arguments: + # + # - <tt>force: true</tt> - if the move includes removing +src+ + # (that is, if +src+ and +dest+ are on different file systems), + # ignores raised exceptions of StandardError and its descendants. + # - <tt>noop: true</tt> - does not move files. + # - <tt>secure: true</tt> - removes +src+ securely; + # see details at Bundler::FileUtils.remove_entry_secure. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.mv('src0', 'dest0', noop: true, verbose: true) + # Bundler::FileUtils.mv(['src1.txt', 'src1'], 'dest1', noop: true, verbose: true) + # + # Output: + # + # mv src0 dest0 + # mv src1.txt src1 dest1 + # + # Bundler::FileUtils.move is an alias for Bundler::FileUtils.mv. # def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil) fu_output_message "mv#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose @@ -565,13 +1198,34 @@ module Bundler::FileUtils alias move mv module_function :move + # Removes entries at the paths in the given +list+ + # (a single path or an array of paths) + # returns +list+, if it is an array, <tt>[list]</tt> otherwise. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # With no keyword arguments, removes files at the paths given in +list+: + # + # Bundler::FileUtils.touch(['src0.txt', 'src0.dat']) + # Bundler::FileUtils.rm(['src0.dat', 'src0.txt']) # => ["src0.dat", "src0.txt"] + # + # Keyword arguments: + # + # - <tt>force: true</tt> - ignores raised exceptions of StandardError + # and its descendants. + # - <tt>noop: true</tt> - does not remove files; returns +nil+. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.rm(['src0.dat', 'src0.txt'], noop: true, verbose: true) # - # Remove file(s) specified in +list+. This method cannot remove directories. - # All StandardErrors are ignored when the :force option is set. + # Output: # - # Bundler::FileUtils.rm %w( junk.txt dust.txt ) - # Bundler::FileUtils.rm Dir.glob('*.so') - # Bundler::FileUtils.rm 'NotExistFile', force: true # never raises exception + # rm src0.dat src0.txt + # + # Bundler::FileUtils.remove is an alias for Bundler::FileUtils.rm. + # + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def rm(list, force: nil, noop: nil, verbose: nil) list = fu_list(list) @@ -587,10 +1241,18 @@ module Bundler::FileUtils alias remove rm module_function :remove + # Equivalent to: + # + # Bundler::FileUtils.rm(list, force: true, **kwargs) + # + # Argument +list+ (a single path or an array of paths) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # See Bundler::FileUtils.rm for keyword arguments. # - # Equivalent to + # Bundler::FileUtils.safe_unlink is an alias for Bundler::FileUtils.rm_f. # - # Bundler::FileUtils.rm(list, force: true) + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def rm_f(list, noop: nil, verbose: nil) rm list, force: true, noop: noop, verbose: verbose @@ -600,24 +1262,55 @@ module Bundler::FileUtils alias safe_unlink rm_f module_function :safe_unlink + # Removes entries at the paths in the given +list+ + # (a single path or an array of paths); + # returns +list+, if it is an array, <tt>[list]</tt> otherwise. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # May cause a local vulnerability if not called with keyword argument + # <tt>secure: true</tt>; + # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability]. + # + # For each file path, removes the file at that path: + # + # Bundler::FileUtils.touch(['src0.txt', 'src0.dat']) + # Bundler::FileUtils.rm_r(['src0.dat', 'src0.txt']) + # File.exist?('src0.txt') # => false + # File.exist?('src0.dat') # => false # - # remove files +list+[0] +list+[1]... If +list+[n] is a directory, - # removes its all contents recursively. This method ignores - # StandardError when :force option is set. + # For each directory path, recursively removes files and directories: # - # Bundler::FileUtils.rm_r Dir.glob('/tmp/*') - # Bundler::FileUtils.rm_r 'some_dir', force: true + # tree('src1') + # # => src1 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt + # Bundler::FileUtils.rm_r('src1') + # File.exist?('src1') # => false # - # WARNING: This method causes local vulnerability - # if one of parent directories or removing directory tree are world - # writable (including /tmp, whose permission is 1777), and the current - # process has strong privilege such as Unix super user (root), and the - # system has symbolic link. For secure removing, read the documentation - # of remove_entry_secure carefully, and set :secure option to true. - # Default is <tt>secure: false</tt>. + # Keyword arguments: # - # NOTE: This method calls remove_entry_secure if :secure option is set. - # See also remove_entry_secure. + # - <tt>force: true</tt> - ignores raised exceptions of StandardError + # and its descendants. + # - <tt>noop: true</tt> - does not remove entries; returns +nil+. + # - <tt>secure: true</tt> - removes +src+ securely; + # see details at Bundler::FileUtils.remove_entry_secure. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.rm_r(['src0.dat', 'src0.txt'], noop: true, verbose: true) + # Bundler::FileUtils.rm_r('src1', noop: true, verbose: true) + # + # Output: + # + # rm -r src0.dat src0.txt + # rm -r src1 + # + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil) list = fu_list(list) @@ -633,13 +1326,22 @@ module Bundler::FileUtils end module_function :rm_r + # Equivalent to: # - # Equivalent to + # Bundler::FileUtils.rm_r(list, force: true, **kwargs) # - # Bundler::FileUtils.rm_r(list, force: true) + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. # - # WARNING: This method causes local vulnerability. - # Read the documentation of rm_r first. + # May cause a local vulnerability if not called with keyword argument + # <tt>secure: true</tt>; + # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability]. + # + # See Bundler::FileUtils.rm_r for keyword arguments. + # + # Bundler::FileUtils.rmtree is an alias for Bundler::FileUtils.rm_rf. + # + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def rm_rf(list, noop: nil, verbose: nil, secure: nil) rm_r list, force: true, noop: noop, verbose: verbose, secure: secure @@ -649,37 +1351,20 @@ module Bundler::FileUtils alias rmtree rm_rf module_function :rmtree + # Securely removes the entry given by +path+, + # which should be the entry for a regular file, a symbolic link, + # or a directory. # - # This method removes a file system entry +path+. +path+ shall be a - # regular file, a directory, or something. If +path+ is a directory, - # remove it recursively. This method is required to avoid TOCTTOU - # (time-of-check-to-time-of-use) local security vulnerability of rm_r. - # #rm_r causes security hole when: - # - # * Parent directory is world writable (including /tmp). - # * Removing directory tree includes world writable directory. - # * The system has symbolic link. + # Argument +path+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # - # To avoid this security hole, this method applies special preprocess. - # If +path+ is a directory, this method chown(2) and chmod(2) all - # removing directories. This requires the current process is the - # owner of the removing whole directory tree, or is the super user (root). + # Avoids a local vulnerability that can exist in certain circumstances; + # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability]. # - # WARNING: You must ensure that *ALL* parent directories cannot be - # moved by other untrusted users. For example, parent directories - # should not be owned by untrusted users, and should not be world - # writable except when the sticky bit set. + # Optional argument +force+ specifies whether to ignore + # raised exceptions of StandardError and its descendants. # - # WARNING: Only the owner of the removing directory tree, or Unix super - # user (root) should invoke this method. Otherwise this method does not - # work. - # - # For details of this security vulnerability, see Perl's case: - # - # * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448 - # * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452 - # - # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100]. + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def remove_entry_secure(path, force = false) unless fu_have_symlink? @@ -767,12 +1452,17 @@ module Bundler::FileUtils end private_module_function :fu_stat_identical_entry? + # Removes the entry given by +path+, + # which should be the entry for a regular file, a symbolic link, + # or a directory. # - # This method removes a file system entry +path+. - # +path+ might be a regular file, a directory, or something. - # If +path+ is a directory, remove it recursively. + # Argument +path+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # - # See also remove_entry_secure. + # Optional argument +force+ specifies whether to ignore + # raised exceptions of StandardError and its descendants. + # + # Related: Bundler::FileUtils.remove_entry_secure. # def remove_entry(path, force = false) Entry_.new(path).postorder_traverse do |ent| @@ -787,9 +1477,16 @@ module Bundler::FileUtils end module_function :remove_entry + # Removes the file entry given by +path+, + # which should be the entry for a regular file or a symbolic link. + # + # Argument +path+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # - # Removes a file +path+. - # This method ignores StandardError if +force+ is true. + # Optional argument +force+ specifies whether to ignore + # raised exceptions of StandardError and its descendants. + # + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def remove_file(path, force = false) Entry_.new(path).remove_file @@ -798,20 +1495,32 @@ module Bundler::FileUtils end module_function :remove_file + # Recursively removes the directory entry given by +path+, + # which should be the entry for a regular file, a symbolic link, + # or a directory. + # + # Argument +path+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # - # Removes a directory +dir+ and its contents recursively. - # This method ignores StandardError if +force+ is true. + # Optional argument +force+ specifies whether to ignore + # raised exceptions of StandardError and its descendants. + # + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def remove_dir(path, force = false) remove_entry path, force # FIXME?? check if it is a directory end module_function :remove_dir + # Returns +true+ if the contents of files +a+ and +b+ are identical, + # +false+ otherwise. + # + # Arguments +a+ and +b+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # - # Returns true if the contents of a file +a+ and a file +b+ are identical. + # Bundler::FileUtils.identical? and Bundler::FileUtils.cmp are aliases for Bundler::FileUtils.compare_file. # - # Bundler::FileUtils.compare_file('somefile', 'somefile') #=> true - # Bundler::FileUtils.compare_file('/dev/null', '/dev/urandom') #=> false + # Related: Bundler::FileUtils.compare_stream. # def compare_file(a, b) return false unless File.size(a) == File.size(b) @@ -828,19 +1537,19 @@ module Bundler::FileUtils module_function :identical? module_function :cmp + # Returns +true+ if the contents of streams +a+ and +b+ are identical, + # +false+ otherwise. # - # Returns true if the contents of a stream +a+ and +b+ are identical. + # Arguments +a+ and +b+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. + # + # Related: Bundler::FileUtils.compare_file. # def compare_stream(a, b) bsize = fu_stream_blksize(a, b) - if RUBY_VERSION > "2.4" - sa = String.new(capacity: bsize) - sb = String.new(capacity: bsize) - else - sa = String.new - sb = String.new - end + sa = String.new(capacity: bsize) + sb = String.new(capacity: bsize) begin a.read(bsize, sa) @@ -851,13 +1560,69 @@ module Bundler::FileUtils end module_function :compare_stream + # Copies a file entry. + # See {install(1)}[https://man7.org/linux/man-pages/man1/install.1.html]. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]; + # + # If the entry at +dest+ does not exist, copies from +src+ to +dest+: + # + # File.read('src0.txt') # => "aaa\n" + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.install('src0.txt', 'dest0.txt') + # File.read('dest0.txt') # => "aaa\n" + # + # If +dest+ is a file entry, copies from +src+ to +dest+, overwriting: + # + # File.read('src1.txt') # => "aaa\n" + # File.read('dest1.txt') # => "bbb\n" + # Bundler::FileUtils.install('src1.txt', 'dest1.txt') + # File.read('dest1.txt') # => "aaa\n" # - # If +src+ is not same as +dest+, copies it and changes the permission - # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+. - # This method removes destination before copy. + # If +dest+ is a directory entry, copies from +src+ to <tt>dest/src</tt>, + # overwriting if necessary: # - # Bundler::FileUtils.install 'ruby', '/usr/local/bin/ruby', mode: 0755, verbose: true - # Bundler::FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', verbose: true + # File.read('src2.txt') # => "aaa\n" + # File.read('dest2/src2.txt') # => "bbb\n" + # Bundler::FileUtils.install('src2.txt', 'dest2') + # File.read('dest2/src2.txt') # => "aaa\n" + # + # If +src+ is an array of paths and +dest+ points to a directory, + # copies each path +path+ in +src+ to <tt>dest/path</tt>: + # + # File.file?('src3.txt') # => true + # File.file?('src3.dat') # => true + # Bundler::FileUtils.mkdir('dest3') + # Bundler::FileUtils.install(['src3.txt', 'src3.dat'], 'dest3') + # File.file?('dest3/src3.txt') # => true + # File.file?('dest3/src3.dat') # => true + # + # Keyword arguments: + # + # - <tt>group: <i>group</i></tt> - changes the group if not +nil+, + # using {File.chown}[rdoc-ref:File.chown]. + # - <tt>mode: <i>permissions</i></tt> - changes the permissions. + # using {File.chmod}[rdoc-ref:File.chmod]. + # - <tt>noop: true</tt> - does not copy entries; returns +nil+. + # - <tt>owner: <i>owner</i></tt> - changes the owner if not +nil+, + # using {File.chown}[rdoc-ref:File.chown]. + # - <tt>preserve: true</tt> - preserve timestamps + # using {File.utime}[rdoc-ref:File.utime]. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.install('src0.txt', 'dest0.txt', noop: true, verbose: true) + # Bundler::FileUtils.install('src1.txt', 'dest1.txt', noop: true, verbose: true) + # Bundler::FileUtils.install('src2.txt', 'dest2', noop: true, verbose: true) + # + # Output: + # + # install -c src0.txt dest0.txt + # install -c src1.txt dest1.txt + # install -c src2.txt dest2 + # + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil, noop: nil, verbose: nil) @@ -917,11 +1682,8 @@ module Bundler::FileUtils private_module_function :apply_mask def symbolic_modes_to_i(mode_sym, path) #:nodoc: - mode = if File::Stat === path - path.mode - else - File.stat(path).mode - end + path = File.stat(path) unless File::Stat === path + mode = path.mode mode_sym.split(/,/).inject(mode & 07777) do |current_mode, clause| target, *actions = clause.split(/([=+-])/) raise ArgumentError, "invalid file mode: #{mode_sym}" if actions.empty? @@ -938,7 +1700,7 @@ module Bundler::FileUtils when "x" mask | 0111 when "X" - if FileTest.directory? path + if path.directory? mask | 0111 else mask @@ -978,37 +1740,78 @@ module Bundler::FileUtils end private_module_function :mode_to_s + # Changes permissions on the entries at the paths given in +list+ + # (a single path or an array of paths) + # to the permissions given by +mode+; + # returns +list+ if it is an array, <tt>[list]</tt> otherwise: + # + # - Modifies each entry that is a regular file using + # {File.chmod}[rdoc-ref:File.chmod]. + # - Modifies each entry that is a symbolic link using + # {File.lchmod}[rdoc-ref:File.lchmod]. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # Argument +mode+ may be either an integer or a string: + # + # - \Integer +mode+: represents the permission bits to be set: + # + # Bundler::FileUtils.chmod(0755, 'src0.txt') + # Bundler::FileUtils.chmod(0644, ['src0.txt', 'src0.dat']) + # + # - \String +mode+: represents the permissions to be set: + # + # The string is of the form <tt>[targets][[operator][perms[,perms]]</tt>, where: + # + # - +targets+ may be any combination of these letters: + # + # - <tt>'u'</tt>: permissions apply to the file's owner. + # - <tt>'g'</tt>: permissions apply to users in the file's group. + # - <tt>'o'</tt>: permissions apply to other users not in the file's group. + # - <tt>'a'</tt> (the default): permissions apply to all users. + # + # - +operator+ may be one of these letters: + # + # - <tt>'+'</tt>: adds permissions. + # - <tt>'-'</tt>: removes permissions. + # - <tt>'='</tt>: sets (replaces) permissions. + # + # - +perms+ (may be repeated, with separating commas) + # may be any combination of these letters: + # + # - <tt>'r'</tt>: Read. + # - <tt>'w'</tt>: Write. + # - <tt>'x'</tt>: Execute (search, for a directory). + # - <tt>'X'</tt>: Search (for a directories only; + # must be used with <tt>'+'</tt>) + # - <tt>'s'</tt>: Uid or gid. + # - <tt>'t'</tt>: Sticky bit. + # + # Examples: + # + # Bundler::FileUtils.chmod('u=wrx,go=rx', 'src1.txt') + # Bundler::FileUtils.chmod('u=wrx,go=rx', '/usr/bin/ruby') + # + # Keyword arguments: + # + # - <tt>noop: true</tt> - does not change permissions; returns +nil+. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.chmod(0755, 'src0.txt', noop: true, verbose: true) + # Bundler::FileUtils.chmod(0644, ['src0.txt', 'src0.dat'], noop: true, verbose: true) + # Bundler::FileUtils.chmod('u=wrx,go=rx', 'src1.txt', noop: true, verbose: true) + # Bundler::FileUtils.chmod('u=wrx,go=rx', '/usr/bin/ruby', noop: true, verbose: true) + # + # Output: + # + # chmod 755 src0.txt + # chmod 644 src0.txt src0.dat + # chmod u=wrx,go=rx src1.txt + # chmod u=wrx,go=rx /usr/bin/ruby + # + # Related: Bundler::FileUtils.chmod_R. # - # Changes permission bits on the named files (in +list+) to the bit pattern - # represented by +mode+. - # - # +mode+ is the symbolic and absolute mode can be used. - # - # Absolute mode is - # Bundler::FileUtils.chmod 0755, 'somecommand' - # Bundler::FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb) - # Bundler::FileUtils.chmod 0755, '/usr/bin/ruby', verbose: true - # - # Symbolic mode is - # Bundler::FileUtils.chmod "u=wrx,go=rx", 'somecommand' - # Bundler::FileUtils.chmod "u=wr,go=rr", %w(my.rb your.rb his.rb her.rb) - # Bundler::FileUtils.chmod "u=wrx,go=rx", '/usr/bin/ruby', verbose: true - # - # "a" :: is user, group, other mask. - # "u" :: is user's mask. - # "g" :: is group's mask. - # "o" :: is other's mask. - # "w" :: is write permission. - # "r" :: is read permission. - # "x" :: is execute permission. - # "X" :: - # is execute permission for directories only, must be used in conjunction with "+" - # "s" :: is uid, gid. - # "t" :: is sticky bit. - # "+" :: is added to a class given the specified mode. - # "-" :: Is removed from a given class given mode. - # "=" :: Is the exact nature of the class will be given a specified mode. - def chmod(mode, list, noop: nil, verbose: nil) list = fu_list(list) fu_output_message sprintf('chmod %s %s', mode_to_s(mode), list.join(' ')) if verbose @@ -1019,12 +1822,7 @@ module Bundler::FileUtils end module_function :chmod - # - # Changes permission bits on the named files (in +list+) - # to the bit pattern represented by +mode+. - # - # Bundler::FileUtils.chmod_R 0700, "/tmp/app.#{$$}" - # Bundler::FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}" + # Like Bundler::FileUtils.chmod, but changes permissions recursively. # def chmod_R(mode, list, noop: nil, verbose: nil, force: nil) list = fu_list(list) @@ -1044,15 +1842,68 @@ module Bundler::FileUtils end module_function :chmod_R + # Changes the owner and group on the entries at the paths given in +list+ + # (a single path or an array of paths) + # to the given +user+ and +group+; + # returns +list+ if it is an array, <tt>[list]</tt> otherwise: + # + # - Modifies each entry that is a regular file using + # {File.chown}[rdoc-ref:File.chown]. + # - Modifies each entry that is a symbolic link using + # {File.lchown}[rdoc-ref:File.lchown]. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. # - # Changes owner and group on the named files (in +list+) - # to the user +user+ and the group +group+. +user+ and +group+ - # may be an ID (Integer/String) or a name (String). - # If +user+ or +group+ is nil, this method does not change - # the attribute. + # User and group: # - # Bundler::FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby' - # Bundler::FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), verbose: true + # - Argument +user+ may be a user name or a user id; + # if +nil+ or +-1+, the user is not changed. + # - Argument +group+ may be a group name or a group id; + # if +nil+ or +-1+, the group is not changed. + # - The user must be a member of the group. + # + # Examples: + # + # # One path. + # # User and group as string names. + # File.stat('src0.txt').uid # => 1004 + # File.stat('src0.txt').gid # => 1004 + # Bundler::FileUtils.chown('user2', 'group1', 'src0.txt') + # File.stat('src0.txt').uid # => 1006 + # File.stat('src0.txt').gid # => 1005 + # + # # User and group as uid and gid. + # Bundler::FileUtils.chown(1004, 1004, 'src0.txt') + # File.stat('src0.txt').uid # => 1004 + # File.stat('src0.txt').gid # => 1004 + # + # # Array of paths. + # Bundler::FileUtils.chown(1006, 1005, ['src0.txt', 'src0.dat']) + # + # # Directory (not recursive). + # Bundler::FileUtils.chown('user2', 'group1', '.') + # + # Keyword arguments: + # + # - <tt>noop: true</tt> - does not change permissions; returns +nil+. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.chown('user2', 'group1', 'src0.txt', noop: true, verbose: true) + # Bundler::FileUtils.chown(1004, 1004, 'src0.txt', noop: true, verbose: true) + # Bundler::FileUtils.chown(1006, 1005, ['src0.txt', 'src0.dat'], noop: true, verbose: true) + # Bundler::FileUtils.chown('user2', 'group1', path, noop: true, verbose: true) + # Bundler::FileUtils.chown('user2', 'group1', '.', noop: true, verbose: true) + # + # Output: + # + # chown user2:group1 src0.txt + # chown 1004:1004 src0.txt + # chown 1006:1005 src0.txt src0.dat + # chown user2:group1 src0.txt + # chown user2:group1 . + # + # Related: Bundler::FileUtils.chown_R. # def chown(user, group, list, noop: nil, verbose: nil) list = fu_list(list) @@ -1068,15 +1919,7 @@ module Bundler::FileUtils end module_function :chown - # - # Changes owner and group on the named files (in +list+) - # to the user +user+ and the group +group+ recursively. - # +user+ and +group+ may be an ID (Integer/String) or - # a name (String). If +user+ or +group+ is nil, this - # method does not change the attribute. - # - # Bundler::FileUtils.chown_R 'www', 'www', '/var/www/htdocs' - # Bundler::FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', verbose: true + # Like Bundler::FileUtils.chown, but changes owner and group recursively. # def chown_R(user, group, list, noop: nil, verbose: nil, force: nil) list = fu_list(list) @@ -1127,12 +1970,50 @@ module Bundler::FileUtils end private_module_function :fu_get_gid + # Updates modification times (mtime) and access times (atime) + # of the entries given by the paths in +list+ + # (a single path or an array of paths); + # returns +list+ if it is an array, <tt>[list]</tt> otherwise. + # + # By default, creates an empty file for any path to a non-existent entry; + # use keyword argument +nocreate+ to raise an exception instead. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # Examples: + # + # # Single path. + # f = File.new('src0.txt') # Existing file. + # f.atime # => 2022-06-10 11:11:21.200277 -0700 + # f.mtime # => 2022-06-10 11:11:21.200277 -0700 + # Bundler::FileUtils.touch('src0.txt') + # f = File.new('src0.txt') + # f.atime # => 2022-06-11 08:28:09.8185343 -0700 + # f.mtime # => 2022-06-11 08:28:09.8185343 -0700 # - # Updates modification time (mtime) and access time (atime) of file(s) in - # +list+. Files are created if they don't exist. + # # Array of paths. + # Bundler::FileUtils.touch(['src0.txt', 'src0.dat']) # - # Bundler::FileUtils.touch 'timestamp' - # Bundler::FileUtils.touch Dir.glob('*.c'); system 'make' + # Keyword arguments: + # + # - <tt>mtime: <i>time</i></tt> - sets the entry's mtime to the given time, + # instead of the current time. + # - <tt>nocreate: true</tt> - raises an exception if the entry does not exist. + # - <tt>noop: true</tt> - does not touch entries; returns +nil+. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.touch('src0.txt', noop: true, verbose: true) + # Bundler::FileUtils.touch(['src0.txt', 'src0.dat'], noop: true, verbose: true) + # Bundler::FileUtils.touch(path, noop: true, verbose: true) + # + # Output: + # + # touch src0.txt + # touch src0.txt src0.dat + # touch src0.txt + # + # Related: Bundler::FileUtils.uptodate?. # def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil) list = fu_list(list) @@ -1290,14 +2171,9 @@ module Bundler::FileUtils def entries opts = {} - opts[:encoding] = ::Encoding::UTF_8 if fu_windows? + opts[:encoding] = fu_windows? ? ::Encoding::UTF_8 : path.encoding - files = if Dir.respond_to?(:children) - Dir.children(path, **opts) - else - Dir.entries(path(), **opts) - .reject {|n| n == '.' or n == '..' } - end + files = Dir.children(path, **opts) untaint = RUBY_VERSION < '2.7' files.map {|n| Entry_.new(prefix(), join(rel(), untaint ? n.untaint : n)) } @@ -1345,6 +2221,7 @@ module Bundler::FileUtils else File.chmod mode, path() end + rescue Errno::EOPNOTSUPP end def chown(uid, gid) @@ -1439,7 +2316,7 @@ module Bundler::FileUtils if st.symlink? begin File.lchmod mode, path - rescue NotImplementedError + rescue NotImplementedError, Errno::EOPNOTSUPP end else File.chmod mode, path @@ -1498,13 +2375,21 @@ module Bundler::FileUtils def postorder_traverse if directory? - entries().each do |ent| + begin + children = entries() + rescue Errno::EACCES + # Failed to get the list of children. + # Assuming there is no children, try to process the parent directory. + yield self + return + end + + children.each do |ent| ent.postorder_traverse do |e| yield e end end end - ensure yield self end @@ -1559,7 +2444,15 @@ module Bundler::FileUtils def join(dir, base) return File.path(dir) if not base or base == '.' return File.path(base) if not dir or dir == '.' - File.join(dir, base) + begin + File.join(dir, base) + rescue EncodingError + if fu_windows? + File.join(dir.encode(::Encoding::UTF_8), base.encode(::Encoding::UTF_8)) + else + raise + end + end end if File::ALT_SEPARATOR @@ -1590,15 +2483,15 @@ module Bundler::FileUtils end private_module_function :fu_each_src_dest - def fu_each_src_dest0(src, dest) #:nodoc: + def fu_each_src_dest0(src, dest, target_directory = true) #:nodoc: if tmp = Array.try_convert(src) tmp.each do |s| s = File.path(s) - yield s, File.join(dest, File.basename(s)) + yield s, (target_directory ? File.join(dest, File.basename(s)) : dest) end else src = File.path(src) - if File.directory?(dest) + if target_directory and File.directory?(dest) yield src, File.join(dest, File.basename(src)) else yield src, File.path(dest) @@ -1614,7 +2507,7 @@ module Bundler::FileUtils def fu_output_message(msg) #:nodoc: output = @fileutils_output if defined?(@fileutils_output) - output ||= $stderr + output ||= $stdout if defined?(@fileutils_label) msg = @fileutils_label + msg end @@ -1622,6 +2515,56 @@ module Bundler::FileUtils end private_module_function :fu_output_message + def fu_split_path(path) + path = File.path(path) + list = [] + until (parent, base = File.split(path); parent == path or parent == ".") + list << base + path = parent + end + list << path + list.reverse! + end + private_module_function :fu_split_path + + def fu_relative_components_from(target, base) #:nodoc: + i = 0 + while target[i]&.== base[i] + i += 1 + end + Array.new(base.size-i, '..').concat(target[i..-1]) + end + private_module_function :fu_relative_components_from + + def fu_clean_components(*comp) + comp.shift while comp.first == "." + return comp if comp.empty? + clean = [comp.shift] + path = File.join(*clean, "") # ending with File::SEPARATOR + while c = comp.shift + if c == ".." and clean.last != ".." and !(fu_have_symlink? && File.symlink?(path)) + clean.pop + path.chomp!(%r((?<=\A|/)[^/]+/\z), "") + else + clean << c + path << c << "/" + end + end + clean + end + private_module_function :fu_clean_components + + if fu_windows? + def fu_starting_path?(path) + path&.start_with?(%r(\w:|/)) + end + else + def fu_starting_path?(path) + path&.start_with?("/") + end + end + private_module_function :fu_starting_path? + # This hash table holds command options. OPT_TABLE = {} #:nodoc: internal use only (private_instance_methods & methods(false)).inject(OPT_TABLE) {|tbl, name| @@ -1631,50 +2574,49 @@ module Bundler::FileUtils public + # Returns an array of the string names of \Bundler::FileUtils methods + # that accept one or more keyword arguments: # - # Returns an Array of names of high-level methods that accept any keyword - # arguments. - # - # p Bundler::FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...] + # Bundler::FileUtils.commands.sort.take(3) # => ["cd", "chdir", "chmod"] # def self.commands OPT_TABLE.keys end + # Returns an array of the string keyword names: # - # Returns an Array of option names. - # - # p Bundler::FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"] + # Bundler::FileUtils.options.take(3) # => ["noop", "verbose", "force"] # def self.options OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s } end + # Returns +true+ if method +mid+ accepts the given option +opt+, +false+ otherwise; + # the arguments may be strings or symbols: # - # Returns true if the method +mid+ have an option +opt+. - # - # p Bundler::FileUtils.have_option?(:cp, :noop) #=> true - # p Bundler::FileUtils.have_option?(:rm, :force) #=> true - # p Bundler::FileUtils.have_option?(:rm, :preserve) #=> false + # Bundler::FileUtils.have_option?(:chmod, :noop) # => true + # Bundler::FileUtils.have_option?('chmod', 'secure') # => false # def self.have_option?(mid, opt) li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}" li.include?(opt) end + # Returns an array of the string keyword name for method +mid+; + # the argument may be a string or a symbol: # - # Returns an Array of option names of the method +mid+. - # - # p Bundler::FileUtils.options_of(:rm) #=> ["noop", "verbose", "force"] + # Bundler::FileUtils.options_of(:rm) # => ["force", "noop", "verbose"] + # Bundler::FileUtils.options_of('mv') # => ["force", "noop", "verbose", "secure"] # def self.options_of(mid) OPT_TABLE[mid.to_s].map {|sym| sym.to_s } end + # Returns an array of the string method names of the methods + # that accept the given keyword option +opt+; + # the argument must be a symbol: # - # Returns an Array of methods names which have the option +opt+. - # - # p Bundler::FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"] + # Bundler::FileUtils.collect_method(:preserve) # => ["cp", "copy", "cp_r", "install"] # def self.collect_method(opt) OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) } diff --git a/lib/bundler/vendor/pub_grub/LICENSE.txt b/lib/bundler/vendor/pub_grub/LICENSE.txt deleted file mode 100644 index 411840a4a0..0000000000 --- a/lib/bundler/vendor/pub_grub/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018 John Hawthorn - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/incompatibility.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/incompatibility.rb index 51e1fc3cdd..239eaf3401 100644 --- a/lib/bundler/vendor/pub_grub/lib/pub_grub/incompatibility.rb +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/incompatibility.rb @@ -71,9 +71,13 @@ module Bundler::PubGrub elsif terms.length == 1 term = terms[0] if term.positive? - "#{terms[0].to_s(allow_every: true)} is forbidden" + if term.constraint.any? + "#{term.package} cannot be used" + else + "#{term.to_s(allow_every: true)} cannot be used" + end else - "#{terms[0].invert} is required" + "#{term.invert} is required" end else if terms.all?(&:positive?) diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb index e895812bed..4bf61461b2 100644 --- a/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb @@ -19,7 +19,14 @@ module Bundler::PubGrub version = Gem::Version.new(version) @packages[name] ||= {} raise ArgumentError, "#{name} #{version} declared twice" if @packages[name].key?(version) - @packages[name][version] = deps + @packages[name][version] = clean_deps(name, version, deps) + end + + private + + # Exclude redundant self-referencing dependencies + def clean_deps(name, version, deps) + deps.reject {|dep_name, req| name == dep_name && Bundler::PubGrub::RubyGems.parse_range(req).include?(version) } end end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/version_constraint.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_constraint.rb index c222542435..b71f3eaf53 100644 --- a/lib/bundler/vendor/pub_grub/lib/pub_grub/version_constraint.rb +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_constraint.rb @@ -15,6 +15,11 @@ module Bundler::PubGrub package.hash ^ range.hash end + def ==(other) + package == other.package && + range == other.range + end + def eql?(other) package.eql?(other.package) && range.eql?(other.range) diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/version_range.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_range.rb index e384178973..8d73c3f7b5 100644 --- a/lib/bundler/vendor/pub_grub/lib/pub_grub/version_range.rb +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_range.rb @@ -19,7 +19,7 @@ module Bundler::PubGrub true end - def eql? + def eql?(other) other.empty? end @@ -65,6 +65,7 @@ module Bundler::PubGrub end EMPTY = Empty.new + Empty.singleton_class.undef_method(:new) def self.empty EMPTY @@ -88,7 +89,8 @@ module Bundler::PubGrub def eql?(other) if other.is_a?(VersionRange) - min.eql?(other.min) && + !other.empty? && + min.eql?(other.min) && max.eql?(other.max) && include_min.eql?(other.include_min) && include_max.eql?(other.include_max) @@ -397,7 +399,7 @@ module Bundler::PubGrub def constraints return ["any"] if any? - return ["= #{min}"] if min == max + return ["= #{min}"] if min.to_s == max.to_s c = [] c << "#{include_min ? ">=" : ">"} #{min}" if min diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/version_solver.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_solver.rb index ea5e455968..4caf6b355b 100644 --- a/lib/bundler/vendor/pub_grub/lib/pub_grub/version_solver.rb +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_solver.rb @@ -125,6 +125,7 @@ module Bundler::PubGrub package = next_package_to_try unsatisfied_term = solution.unsatisfied.find { |t| t.package == package } version = source.versions_for(package, unsatisfied_term.constraint.range).first + logger.debug { "attempting #{package} #{version}" } if version.nil? add_incompatibility source.no_versions_incompatibility_for(package, unsatisfied_term) @@ -148,9 +149,11 @@ module Bundler::PubGrub end unless conflict - logger.info { "selecting #{package} #{version}" } + logger.info { "selected #{package} #{version}" } solution.decide(package, version) + else + logger.info { "conflict: #{conflict.inspect}" } end package @@ -159,7 +162,7 @@ module Bundler::PubGrub def resolve_conflict(incompatibility) logger.info { "conflict: #{incompatibility}" } - new_incompatibility = false + new_incompatibility = nil while !incompatibility.failure? most_recent_term = nil @@ -201,7 +204,7 @@ module Bundler::PubGrub solution.backtrack(previous_level) if new_incompatibility - add_incompatibility(incompatibility) + add_incompatibility(new_incompatibility) end return incompatibility @@ -216,9 +219,14 @@ module Bundler::PubGrub new_terms << difference.invert end - incompatibility = Incompatibility.new(new_terms, cause: Incompatibility::ConflictCause.new(incompatibility, most_recent_satisfier.cause)) + new_incompatibility = Incompatibility.new(new_terms, cause: Incompatibility::ConflictCause.new(incompatibility, most_recent_satisfier.cause)) + + if incompatibility.to_s == new_incompatibility.to_s + logger.info { "!! failed to resolve conflicts, this shouldn't have happened" } + break + end - new_incompatibility = true + incompatibility = new_incompatibility partially = difference ? " partially" : "" logger.info { "! #{most_recent_term} is#{partially} satisfied by #{most_recent_satisfier.term}" } diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/version_union.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_union.rb index b66c603dfd..bbc10c3804 100644 --- a/lib/bundler/vendor/pub_grub/lib/pub_grub/version_union.rb +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_union.rb @@ -77,7 +77,7 @@ module Bundler::PubGrub return true end - if !my_range.max || (other_range.max && other_range.max < my_range.max) + if !my_range.max || other_range.empty? || (other_range.max && other_range.max < my_range.max) other_range = other_ranges.shift else my_range = my_ranges.shift @@ -119,7 +119,7 @@ module Bundler::PubGrub while my_range && other_range new_ranges << my_range.intersect(other_range) - if !my_range.max || (other_range.max && other_range.max < my_range.max) + if !my_range.max || other_range.empty? || (other_range.max && other_range.max < my_range.max) other_range = other_ranges.shift else my_range = my_ranges.shift @@ -148,7 +148,7 @@ module Bundler::PubGrub while !ranges.empty? ne = [] range = ranges.shift - while !ranges.empty? && ranges[0].min == range.max + while !ranges.empty? && ranges[0].min.to_s == range.max.to_s ne << range.max range = range.span(ranges.shift) end diff --git a/lib/bundler/vendor/tmpdir/lib/tmpdir.rb b/lib/bundler/vendor/tmpdir/lib/tmpdir.rb deleted file mode 100644 index 70d43e0c6b..0000000000 --- a/lib/bundler/vendor/tmpdir/lib/tmpdir.rb +++ /dev/null @@ -1,154 +0,0 @@ -# frozen_string_literal: true -# -# tmpdir - retrieve temporary directory path -# -# $Id$ -# - -require_relative '../../fileutils/lib/fileutils' -begin - require 'etc.so' -rescue LoadError # rescue LoadError for miniruby -end - -class Bundler::Dir < Dir - - @systmpdir ||= defined?(Etc.systmpdir) ? Etc.systmpdir : '/tmp' - - ## - # Returns the operating system's temporary file path. - - def self.tmpdir - tmp = nil - ['TMPDIR', 'TMP', 'TEMP', ['system temporary path', @systmpdir], ['/tmp']*2, ['.']*2].each do |name, dir = ENV[name]| - next if !dir - dir = File.expand_path(dir) - stat = File.stat(dir) rescue next - case - when !stat.directory? - warn "#{name} is not a directory: #{dir}" - when !stat.writable? - warn "#{name} is not writable: #{dir}" - when stat.world_writable? && !stat.sticky? - warn "#{name} is world-writable: #{dir}" - else - tmp = dir - break - end - end - raise ArgumentError, "could not find a temporary directory" unless tmp - tmp - end - - # Bundler::Dir.mktmpdir creates a temporary directory. - # - # The directory is created with 0700 permission. - # Application should not change the permission to make the temporary directory accessible from other users. - # - # The prefix and suffix of the name of the directory is specified by - # the optional first argument, <i>prefix_suffix</i>. - # - If it is not specified or nil, "d" is used as the prefix and no suffix is used. - # - If it is a string, it is used as the prefix and no suffix is used. - # - If it is an array, first element is used as the prefix and second element is used as a suffix. - # - # Bundler::Dir.mktmpdir {|dir| dir is ".../d..." } - # Bundler::Dir.mktmpdir("foo") {|dir| dir is ".../foo..." } - # Bundler::Dir.mktmpdir(["foo", "bar"]) {|dir| dir is ".../foo...bar" } - # - # The directory is created under Bundler::Dir.tmpdir or - # the optional second argument <i>tmpdir</i> if non-nil value is given. - # - # Bundler::Dir.mktmpdir {|dir| dir is "#{Bundler::Dir.tmpdir}/d..." } - # Bundler::Dir.mktmpdir(nil, "/var/tmp") {|dir| dir is "/var/tmp/d..." } - # - # If a block is given, - # it is yielded with the path of the directory. - # The directory and its contents are removed - # using Bundler::FileUtils.remove_entry before Bundler::Dir.mktmpdir returns. - # The value of the block is returned. - # - # Bundler::Dir.mktmpdir {|dir| - # # use the directory... - # open("#{dir}/foo", "w") { ... } - # } - # - # If a block is not given, - # The path of the directory is returned. - # In this case, Bundler::Dir.mktmpdir doesn't remove the directory. - # - # dir = Bundler::Dir.mktmpdir - # begin - # # use the directory... - # open("#{dir}/foo", "w") { ... } - # ensure - # # remove the directory. - # Bundler::FileUtils.remove_entry dir - # end - # - def self.mktmpdir(prefix_suffix=nil, *rest, **options) - base = nil - path = Tmpname.create(prefix_suffix || "d", *rest, **options) {|p, _, _, d| - base = d - mkdir(p, 0700) - } - if block_given? - begin - yield path.dup - ensure - unless base - stat = File.stat(File.dirname(path)) - if stat.world_writable? and !stat.sticky? - raise ArgumentError, "parent directory is world writable but not sticky" - end - end - Bundler::FileUtils.remove_entry path - end - else - path - end - end - - module Tmpname # :nodoc: - module_function - - def tmpdir - Bundler::Dir.tmpdir - end - - UNUSABLE_CHARS = "^,-.0-9A-Z_a-z~" - - class << (RANDOM = Random.new) - MAX = 36**6 # < 0x100000000 - def next - rand(MAX).to_s(36) - end - end - private_constant :RANDOM - - def create(basename, tmpdir=nil, max_try: nil, **opts) - origdir = tmpdir - tmpdir ||= tmpdir() - n = nil - prefix, suffix = basename - prefix = (String.try_convert(prefix) or - raise ArgumentError, "unexpected prefix: #{prefix.inspect}") - prefix = prefix.delete(UNUSABLE_CHARS) - suffix &&= (String.try_convert(suffix) or - raise ArgumentError, "unexpected suffix: #{suffix.inspect}") - suffix &&= suffix.delete(UNUSABLE_CHARS) - begin - t = Time.now.strftime("%Y%m%d") - path = "#{prefix}#{t}-#{$$}-#{RANDOM.next}"\ - "#{n ? %[-#{n}] : ''}#{suffix||''}" - path = File.join(tmpdir, path) - yield(path, n, opts, origdir) - rescue Errno::EEXIST - n ||= 0 - n += 1 - retry if !max_try or n < max_try - raise "cannot generate temporary name using `#{basename}' under `#{tmpdir}'" - end - path - end - end -end diff --git a/lib/bundler/vendor/uri/lib/uri.rb b/lib/bundler/vendor/uri/lib/uri.rb index 0e574dd2b1..976320f6bd 100644 --- a/lib/bundler/vendor/uri/lib/uri.rb +++ b/lib/bundler/vendor/uri/lib/uri.rb @@ -30,7 +30,7 @@ # class RSYNC < Generic # DEFAULT_PORT = 873 # end -# @@schemes['RSYNC'] = RSYNC +# register_scheme 'RSYNC', RSYNC # end # #=> Bundler::URI::RSYNC # @@ -70,7 +70,6 @@ # - Bundler::URI::REGEXP - (in uri/common.rb) # - Bundler::URI::REGEXP::PATTERN - (in uri/common.rb) # - Bundler::URI::Util - (in uri/common.rb) -# - Bundler::URI::Escape - (in uri/common.rb) # - Bundler::URI::Error - (in uri/common.rb) # - Bundler::URI::InvalidURIError - (in uri/common.rb) # - Bundler::URI::InvalidComponentError - (in uri/common.rb) @@ -101,3 +100,5 @@ require_relative 'uri/https' require_relative 'uri/ldap' require_relative 'uri/ldaps' require_relative 'uri/mailto' +require_relative 'uri/ws' +require_relative 'uri/wss' diff --git a/lib/bundler/vendor/uri/lib/uri/common.rb b/lib/bundler/vendor/uri/lib/uri/common.rb index 6539e1810f..914a4c7581 100644 --- a/lib/bundler/vendor/uri/lib/uri/common.rb +++ b/lib/bundler/vendor/uri/lib/uri/common.rb @@ -13,9 +13,12 @@ require_relative "rfc2396_parser" require_relative "rfc3986_parser" module Bundler::URI + include RFC2396_REGEXP + REGEXP = RFC2396_REGEXP Parser = RFC2396_Parser RFC3986_PARSER = RFC3986_Parser.new + Ractor.make_shareable(RFC3986_PARSER) if defined?(Ractor) # Bundler::URI::Parser.new DEFAULT_PARSER = Parser.new @@ -27,6 +30,7 @@ module Bundler::URI DEFAULT_PARSER.regexp.each_pair do |sym, str| const_set(sym, str) end + Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor) module Util # :nodoc: def make_components_hash(klass, array_hash) @@ -60,24 +64,42 @@ module Bundler::URI module_function :make_components_hash end - include REGEXP + module Schemes + end + private_constant :Schemes + + # + # Register the given +klass+ to be instantiated when parsing URLs with the given +scheme+. + # Note that currently only schemes which after .upcase are valid constant names + # can be registered (no -/+/. allowed). + # + def self.register_scheme(scheme, klass) + Schemes.const_set(scheme.to_s.upcase, klass) + end - @@schemes = {} # Returns a Hash of the defined schemes. def self.scheme_list - @@schemes + Schemes.constants.map { |name| + [name.to_s.upcase, Schemes.const_get(name)] + }.to_h end + INITIAL_SCHEMES = scheme_list + private_constant :INITIAL_SCHEMES + Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor) + # # Construct a Bundler::URI instance, using the scheme to detect the appropriate class # from +Bundler::URI.scheme_list+. # def self.for(scheme, *arguments, default: Generic) - if scheme - uri_class = @@schemes[scheme.upcase] || default - else - uri_class = default + const_name = scheme.to_s.upcase + + 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 ||= default return uri_class.new(scheme, *arguments) end @@ -278,6 +300,7 @@ module Bundler::URI 256.times do |i| TBLENCWWWCOMP_[-i.chr] = -('%%%02X' % i) end + TBLENCURICOMP_ = TBLENCWWWCOMP_.dup.freeze TBLENCWWWCOMP_[' '] = '+' TBLENCWWWCOMP_.freeze TBLDECWWWCOMP_ = {} # :nodoc: @@ -303,6 +326,33 @@ module Bundler::URI # # See Bundler::URI.decode_www_form_component, Bundler::URI.encode_www_form. def self.encode_www_form_component(str, enc=nil) + _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_, str, enc) + end + + # Decodes given +str+ of URL-encoded form data. + # + # This decodes + to SP. + # + # See Bundler::URI.encode_www_form_component, Bundler::URI.decode_www_form. + def self.decode_www_form_component(str, enc=Encoding::UTF_8) + _decode_uri_component(/\+|%\h\h/, str, enc) + end + + # Encodes +str+ using URL encoding + # + # This encodes SP to %20 instead of +. + def self.encode_uri_component(str, enc=nil) + _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCURICOMP_, str, enc) + end + + # Decodes given +str+ of URL-encoded data. + # + # This does not decode + to SP. + def self.decode_uri_component(str, enc=Encoding::UTF_8) + _decode_uri_component(/%\h\h/, str, enc) + end + + def self._encode_uri_component(regexp, table, str, enc) str = str.to_s.dup if str.encoding != Encoding::ASCII_8BIT if enc && enc != Encoding::ASCII_8BIT @@ -311,19 +361,16 @@ module Bundler::URI end str.force_encoding(Encoding::ASCII_8BIT) end - str.gsub!(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_) + str.gsub!(regexp, table) str.force_encoding(Encoding::US_ASCII) end + private_class_method :_encode_uri_component - # Decodes given +str+ of URL-encoded form data. - # - # This decodes + to SP. - # - # See Bundler::URI.encode_www_form_component, Bundler::URI.decode_www_form. - def self.decode_www_form_component(str, enc=Encoding::UTF_8) - raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/ =~ str - str.b.gsub(/\+|%\h\h/, TBLDECWWWCOMP_).force_encoding(enc) + 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) end + private_class_method :_decode_uri_component # Generates URL-encoded form data from given +enum+. # @@ -653,6 +700,7 @@ module Bundler::URI "utf-16"=>"utf-16le", "utf-16le"=>"utf-16le", } # :nodoc: + Ractor.make_shareable(WEB_ENCODINGS_) if defined?(Ractor) # :nodoc: # return encoding or nil diff --git a/lib/bundler/vendor/uri/lib/uri/file.rb b/lib/bundler/vendor/uri/lib/uri/file.rb index df42f8bcdd..8d75a9de7a 100644 --- a/lib/bundler/vendor/uri/lib/uri/file.rb +++ b/lib/bundler/vendor/uri/lib/uri/file.rb @@ -33,6 +33,9 @@ module Bundler::URI # If an Array is used, the components must be passed in the # order <code>[host, path]</code>. # + # A path from e.g. the File class should be escaped before + # being passed. + # # Examples: # # require 'bundler/vendor/uri/lib/uri' @@ -44,6 +47,9 @@ module Bundler::URI # :path => '/ruby/src'}) # uri2.to_s # => "file://host.example.com/ruby/src" # + # uri3 = Bundler::URI::File.build({:path => Bundler::URI::escape('/path/my file.txt')}) + # uri3.to_s # => "file:///path/my%20file.txt" + # def self.build(args) tmp = Util::make_components_hash(self, args) super(tmp) @@ -90,5 +96,5 @@ module Bundler::URI end end - @@schemes['FILE'] = File + register_scheme 'FILE', File end diff --git a/lib/bundler/vendor/uri/lib/uri/ftp.rb b/lib/bundler/vendor/uri/lib/uri/ftp.rb index 2252e405d5..48b4c6718d 100644 --- a/lib/bundler/vendor/uri/lib/uri/ftp.rb +++ b/lib/bundler/vendor/uri/lib/uri/ftp.rb @@ -262,5 +262,6 @@ module Bundler::URI return str end end - @@schemes['FTP'] = FTP + + register_scheme 'FTP', FTP end diff --git a/lib/bundler/vendor/uri/lib/uri/generic.rb b/lib/bundler/vendor/uri/lib/uri/generic.rb index f29ba6cf18..9ae6915826 100644 --- a/lib/bundler/vendor/uri/lib/uri/generic.rb +++ b/lib/bundler/vendor/uri/lib/uri/generic.rb @@ -564,16 +564,26 @@ module Bundler::URI end end - # Returns the user component. + # Returns the user component (without Bundler::URI decoding). def user @user end - # Returns the password component. + # Returns the password component (without Bundler::URI decoding). def password @password end + # Returns the user component after Bundler::URI decoding. + def decoded_user + Bundler::URI.decode_uri_component(@user) if @user + end + + # Returns the password component after Bundler::URI decoding. + def decoded_password + Bundler::URI.decode_uri_component(@password) if @password + end + # # Checks the host +v+ component for RFC2396 compliance # and against the Bundler::URI::Parser Regexp for :HOST. @@ -643,7 +653,7 @@ module Bundler::URI # def hostname v = self.host - /\A\[(.*)\]\z/ =~ v ? $1 : v + v&.start_with?('[') && v.end_with?(']') ? v[1..-2] : v end # Sets the host part of the Bundler::URI as the argument with brackets for IPv6 addresses. @@ -659,7 +669,7 @@ module Bundler::URI # it is wrapped with brackets. # def hostname=(v) - v = "[#{v}]" if /\A\[.*\]\z/ !~ v && /:/ =~ v + v = "[#{v}]" if !(v&.start_with?('[') && v&.end_with?(']')) && v&.index(':') self.host = v end @@ -1514,9 +1524,19 @@ module Bundler::URI proxy_uri = env["CGI_#{name.upcase}"] end elsif name == 'http_proxy' - unless proxy_uri = env[name] - if proxy_uri = env[name.upcase] - warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1 + if RUBY_ENGINE == 'jruby' && p_addr = ENV_JAVA['http.proxyHost'] + p_port = ENV_JAVA['http.proxyPort'] + if p_user = ENV_JAVA['http.proxyUser'] + p_pass = ENV_JAVA['http.proxyPass'] + proxy_uri = "http://#{p_user}:#{p_pass}@#{p_addr}:#{p_port}" + else + proxy_uri = "http://#{p_addr}:#{p_port}" + end + else + unless proxy_uri = env[name] + if proxy_uri = env[name.upcase] + warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1 + end end end else diff --git a/lib/bundler/vendor/uri/lib/uri/http.rb b/lib/bundler/vendor/uri/lib/uri/http.rb index 50d7e427a5..2c44810644 100644 --- a/lib/bundler/vendor/uri/lib/uri/http.rb +++ b/lib/bundler/vendor/uri/lib/uri/http.rb @@ -80,8 +80,46 @@ module Bundler::URI url = @query ? "#@path?#@query" : @path.dup url.start_with?(?/.freeze) ? url : ?/ + url end - end - @@schemes['HTTP'] = HTTP + # + # == Description + # + # Returns the authority for an HTTP uri, as defined in + # https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2. + # + # + # Example: + # + # Bundler::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').authority #=> "www.example.com" + # Bundler::URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').authority #=> "www.example.com:8000" + # Bundler::URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').authority #=> "www.example.com" + # + def authority + if port == default_port + host + else + "#{host}:#{port}" + end + end + + # + # == Description + # + # Returns the origin for an HTTP uri, as defined in + # https://datatracker.ietf.org/doc/html/rfc6454. + # + # + # Example: + # + # Bundler::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').origin #=> "http://www.example.com" + # Bundler::URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').origin #=> "http://www.example.com:8000" + # Bundler::URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').origin #=> "http://www.example.com" + # Bundler::URI::HTTPS.build(host: 'www.example.com', path: '/foo/bar').origin #=> "https://www.example.com" + # + def origin + "#{scheme}://#{authority}" + end + end + register_scheme 'HTTP', HTTP end diff --git a/lib/bundler/vendor/uri/lib/uri/https.rb b/lib/bundler/vendor/uri/lib/uri/https.rb index 4fd4e9af7b..e4556e3ecb 100644 --- a/lib/bundler/vendor/uri/lib/uri/https.rb +++ b/lib/bundler/vendor/uri/lib/uri/https.rb @@ -18,5 +18,6 @@ module Bundler::URI # A Default port of 443 for Bundler::URI::HTTPS DEFAULT_PORT = 443 end - @@schemes['HTTPS'] = HTTPS + + register_scheme 'HTTPS', HTTPS end diff --git a/lib/bundler/vendor/uri/lib/uri/ldap.rb b/lib/bundler/vendor/uri/lib/uri/ldap.rb index 6e9e1918f6..9811b6e2f5 100644 --- a/lib/bundler/vendor/uri/lib/uri/ldap.rb +++ b/lib/bundler/vendor/uri/lib/uri/ldap.rb @@ -257,5 +257,5 @@ module Bundler::URI end end - @@schemes['LDAP'] = LDAP + register_scheme 'LDAP', LDAP end diff --git a/lib/bundler/vendor/uri/lib/uri/ldaps.rb b/lib/bundler/vendor/uri/lib/uri/ldaps.rb index 0af35bb16b..c786168450 100644 --- a/lib/bundler/vendor/uri/lib/uri/ldaps.rb +++ b/lib/bundler/vendor/uri/lib/uri/ldaps.rb @@ -17,5 +17,6 @@ module Bundler::URI # A Default port of 636 for Bundler::URI::LDAPS DEFAULT_PORT = 636 end - @@schemes['LDAPS'] = LDAPS + + register_scheme 'LDAPS', LDAPS end diff --git a/lib/bundler/vendor/uri/lib/uri/mailto.rb b/lib/bundler/vendor/uri/lib/uri/mailto.rb index ff7ab7e114..ff2e30be86 100644 --- a/lib/bundler/vendor/uri/lib/uri/mailto.rb +++ b/lib/bundler/vendor/uri/lib/uri/mailto.rb @@ -15,7 +15,7 @@ module Bundler::URI # RFC6068, the mailto URL scheme. # class MailTo < Generic - include REGEXP + include RFC2396_REGEXP # A Default port of nil for Bundler::URI::MailTo. DEFAULT_PORT = nil @@ -289,5 +289,5 @@ module Bundler::URI alias to_rfc822text to_mailtext end - @@schemes['MAILTO'] = MailTo + register_scheme 'MAILTO', MailTo end diff --git a/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb b/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb index e48e164f4c..09c22c9906 100644 --- a/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb +++ b/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb @@ -116,7 +116,7 @@ module Bundler::URI # See also Bundler::URI::Parser.initialize_regexp. attr_reader :regexp - # Returns a split Bundler::URI against regexp[:ABS_URI]. + # Returns a split Bundler::URI against +regexp[:ABS_URI]+. def split(uri) case uri when '' @@ -257,8 +257,8 @@ module Bundler::URI end end - # Returns Regexp that is default self.regexp[:ABS_URI_REF], - # unless +schemes+ is provided. Then it is a Regexp.union with self.pattern[:X_ABS_URI]. + # Returns Regexp that is default +self.regexp[:ABS_URI_REF]+, + # unless +schemes+ is provided. Then it is a Regexp.union with +self.pattern[:X_ABS_URI]+. def make_regexp(schemes = nil) unless schemes @regexp[:ABS_URI_REF] @@ -277,7 +277,7 @@ module Bundler::URI # +str+:: # String to make safe # +unsafe+:: - # Regexp to apply. Defaults to self.regexp[:UNSAFE] + # Regexp to apply. Defaults to +self.regexp[:UNSAFE]+ # # == Description # @@ -309,7 +309,7 @@ module Bundler::URI # +str+:: # String to remove escapes from # +escaped+:: - # Regexp to apply. Defaults to self.regexp[:ESCAPED] + # Regexp to apply. Defaults to +self.regexp[:ESCAPED]+ # # == Description # @@ -322,8 +322,14 @@ module Bundler::URI end @@to_s = Kernel.instance_method(:to_s) - def inspect - @@to_s.bind_call(self) + if @@to_s.respond_to?(:bind_call) + def inspect + @@to_s.bind_call(self) + end + else + def inspect + @@to_s.bind(self).call + end end private @@ -491,8 +497,8 @@ module Bundler::URI ret = {} # for Bundler::URI::split - ret[:ABS_URI] = Regexp.new('\A\s*' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED) - ret[:REL_URI] = Regexp.new('\A\s*' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED) + ret[:ABS_URI] = Regexp.new('\A\s*+' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED) + ret[:REL_URI] = Regexp.new('\A\s*+' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED) # for Bundler::URI::extract ret[:URI_REF] = Regexp.new(pattern[:URI_REF]) diff --git a/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb b/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb index 2029cfd056..a85511c146 100644 --- a/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb +++ b/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb @@ -2,9 +2,8 @@ module Bundler::URI class RFC3986_Parser # :nodoc: # Bundler::URI defined in RFC3986 - # this regexp is modified not to host is not empty string - RFC3986_URI = /\A(?<Bundler::URI>(?<scheme>[A-Za-z][+\-.0-9A-Za-z]*):(?<hier-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*)@)?(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])+))?(?::(?<port>\d*))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*))*)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+)(?:\/\g<segment>)*)?)|(?<path-rootless>\g<segment-nz>(?:\/\g<segment>)*)|(?<path-empty>))(?:\?(?<query>[^#]*))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*))?)\z/ - RFC3986_relative_ref = /\A(?<relative-ref>(?<relative-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*)@)?(?<host>(?<IP-literal>\[(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:){,1}\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+)\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])+))?(?::(?<port>\d*))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*))*)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+)(?:\/\g<segment>)*)?)|(?<path-noscheme>(?<segment-nz-nc>(?:%\h\h|[!$&-.0-9;=@-Z_a-z~])+)(?:\/\g<segment>)*)|(?<path-empty>))(?:\?(?<query>[^#]*))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*))?)\z/ + RFC3986_URI = /\A(?<Bundler::URI>(?<scheme>[A-Za-z][+\-.0-9A-Za-z]*+):(?<hier-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*+)@)?(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h++\.[!$&-.0-;=A-Z_a-z~]++))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])*+))(?::(?<port>\d*+))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*+))*+)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])++)(?:\/\g<segment>)*+)?)|(?<path-rootless>\g<segment-nz>(?:\/\g<segment>)*+)|(?<path-empty>))(?:\?(?<query>[^#]*+))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*+))?)\z/ + RFC3986_relative_ref = /\A(?<relative-ref>(?<relative-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*+)@)?(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:){,1}\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h++\.[!$&-.0-;=A-Z_a-z~]++))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])++))?(?::(?<port>\d*+))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*+))*+)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])++)(?:\/\g<segment>)*+)?)|(?<path-noscheme>(?<segment-nz-nc>(?:%\h\h|[!$&-.0-9;=@-Z_a-z~])++)(?:\/\g<segment>)*+)|(?<path-empty>))(?:\?(?<query>[^#]*+))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*+))?)\z/ attr_reader :regexp def initialize @@ -79,8 +78,14 @@ module Bundler::URI end @@to_s = Kernel.instance_method(:to_s) - def inspect - @@to_s.bind_call(self) + if @@to_s.respond_to?(:bind_call) + def inspect + @@to_s.bind_call(self) + end + else + def inspect + @@to_s.bind(self).call + end end private @@ -95,7 +100,7 @@ module Bundler::URI QUERY: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*\z/, FRAGMENT: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*\z/, OPAQUE: /\A(?:[^\/].*)?\z/, - PORT: /\A[\x09\x0a\x0c\x0d ]*\d*[\x09\x0a\x0c\x0d ]*\z/, + PORT: /\A[\x09\x0a\x0c\x0d ]*+\d*[\x09\x0a\x0c\x0d ]*\z/, } end diff --git a/lib/bundler/vendor/uri/lib/uri/version.rb b/lib/bundler/vendor/uri/lib/uri/version.rb index f2bb0ebad2..84b08eee30 100644 --- a/lib/bundler/vendor/uri/lib/uri/version.rb +++ b/lib/bundler/vendor/uri/lib/uri/version.rb @@ -1,6 +1,6 @@ module Bundler::URI # :stopdoc: - VERSION_CODE = '001001'.freeze + VERSION_CODE = '001202'.freeze VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze # :startdoc: end diff --git a/lib/bundler/vendor/uri/lib/uri/ws.rb b/lib/bundler/vendor/uri/lib/uri/ws.rb index 58e08bf98e..10ae6f5834 100644 --- a/lib/bundler/vendor/uri/lib/uri/ws.rb +++ b/lib/bundler/vendor/uri/lib/uri/ws.rb @@ -79,6 +79,5 @@ module Bundler::URI end end - @@schemes['WS'] = WS - + register_scheme 'WS', WS end diff --git a/lib/bundler/vendor/uri/lib/uri/wss.rb b/lib/bundler/vendor/uri/lib/uri/wss.rb index 3827053c7b..e8db1ceabf 100644 --- a/lib/bundler/vendor/uri/lib/uri/wss.rb +++ b/lib/bundler/vendor/uri/lib/uri/wss.rb @@ -18,5 +18,6 @@ module Bundler::URI # A Default port of 443 for Bundler::URI::WSS DEFAULT_PORT = 443 end - @@schemes['WSS'] = WSS + + register_scheme 'WSS', WSS end diff --git a/lib/bundler/vendored_tmpdir.rb b/lib/bundler/vendored_tmpdir.rb deleted file mode 100644 index 43b4fa75fe..0000000000 --- a/lib/bundler/vendored_tmpdir.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -module Bundler; end -require_relative "vendor/tmpdir/lib/tmpdir" diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index a2a244c220..8ef7be935b 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "2.4.0.dev".freeze + VERSION = "2.4.19".freeze def self.bundler_major_version @bundler_major_version ||= VERSION.split(".").first.to_i diff --git a/lib/bundler/worker.rb b/lib/bundler/worker.rb index 5e4ee21c51..3ebd6f01db 100644 --- a/lib/bundler/worker.rb +++ b/lib/bundler/worker.rb @@ -87,14 +87,12 @@ module Bundler creation_errors = [] @threads = Array.new(@size) do |i| - begin - Thread.start { process_queue(i) }.tap do |thread| - thread.name = "#{name} Worker ##{i}" if thread.respond_to?(:name=) - end - rescue ThreadError => e - creation_errors << e - nil + Thread.start { process_queue(i) }.tap do |thread| + thread.name = "#{name} Worker ##{i}" if thread.respond_to?(:name=) end + rescue ThreadError => e + creation_errors << e + nil end.compact add_interrupt_handler unless @threads.empty? diff --git a/lib/cgi.rb b/lib/cgi.rb index 4cd6b3bd8e..7dc3a64941 100644 --- a/lib/cgi.rb +++ b/lib/cgi.rb @@ -288,7 +288,7 @@ # class CGI - VERSION = "0.3.6" + VERSION = "0.3.7" end require 'cgi/core' diff --git a/lib/cgi/cookie.rb b/lib/cgi/cookie.rb index 9498e2f9fa..1c4ef6a600 100644 --- a/lib/cgi/cookie.rb +++ b/lib/cgi/cookie.rb @@ -190,9 +190,10 @@ class CGI values ||= "" values = values.split('&').collect{|v| CGI.unescape(v,@@accept_charset) } if cookies.has_key?(name) - values = cookies[name].value + values + cookies[name].concat(values) + else + cookies[name] = Cookie.new(name, *values) end - cookies[name] = Cookie.new(name, *values) end cookies diff --git a/lib/cgi/util.rb b/lib/cgi/util.rb index 5a5c77ac97..ce77a0ccd5 100644 --- a/lib/cgi/util.rb +++ b/lib/cgi/util.rb @@ -178,7 +178,7 @@ module CGI::Util def escapeElement(string, *elements) elements = elements[0] if elements[0].kind_of?(Array) unless elements.empty? - string.gsub(/<\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?>/i) do + string.gsub(/<\/?(?:#{elements.join("|")})\b[^<>]*+>?/im) do CGI.escapeHTML($&) end else @@ -198,7 +198,7 @@ module CGI::Util def unescapeElement(string, *elements) elements = elements[0] if elements[0].kind_of?(Array) unless elements.empty? - string.gsub(/<\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?>/i) do + string.gsub(/<\/?(?:#{elements.join("|")})\b(?>[^&]+|&(?![gl]t;)\w+;)*(?:>)?/im) do unescapeHTML($&) end else diff --git a/lib/did_you_mean/version.rb b/lib/did_you_mean/version.rb index 9fd6f17034..5745ca1efd 100644 --- a/lib/did_you_mean/version.rb +++ b/lib/did_you_mean/version.rb @@ -1,3 +1,3 @@ module DidYouMean - VERSION = "1.6.2".freeze + VERSION = "1.6.3".freeze end diff --git a/lib/erb.gemspec b/lib/erb.gemspec index 94a8fd5c3e..d973cc10de 100644 --- a/lib/erb.gemspec +++ b/lib/erb.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |spec| spec.name = 'erb' spec.version = ERB.const_get(:VERSION, false) spec.authors = ['Masatoshi SEKI', 'Takashi Kokubun'] - spec.email = ['seki@ruby-lang.org', 'k0kubun@ruby-lang.org'] + spec.email = ['seki@ruby-lang.org', 'takashikkbn@gmail.com'] spec.summary = %q{An easy to use but powerful templating system for Ruby.} spec.description = %q{An easy to use but powerful templating system for Ruby.} diff --git a/lib/irb/cmd/debug.rb b/lib/irb/cmd/debug.rb index e6e812e075..7d39b9fa27 100644 --- a/lib/irb/cmd/debug.rb +++ b/lib/irb/cmd/debug.rb @@ -102,13 +102,14 @@ module IRB return false unless debug_gem # Discover debug/debug.so under extensions for Ruby 3.2+ - debug_so = Gem.paths.path.flat_map do |path| - Dir.glob("#{path}/extensions/**/#{File.basename(debug_gem)}/debug/debug.so") + ext_name = "/debug/debug.#{RbConfig::CONFIG['DLEXT']}" + ext_path = Gem.paths.path.flat_map do |path| + Dir.glob("#{path}/extensions/**/#{File.basename(debug_gem)}#{ext_name}") end.first # Attempt to forcibly load the bundled gem - if debug_so - $LOAD_PATH << debug_so.delete_suffix('/debug/debug.so') + if ext_path + $LOAD_PATH << ext_path.delete_suffix(ext_name) end $LOAD_PATH << "#{debug_gem}/lib" begin diff --git a/lib/irb/cmd/edit.rb b/lib/irb/cmd/edit.rb index 98fa07e49d..0103891cf4 100644 --- a/lib/irb/cmd/edit.rb +++ b/lib/irb/cmd/edit.rb @@ -18,13 +18,6 @@ module IRB args.strip.dump end end - - private - - def string_literal?(args) - sexp = Ripper.sexp(args) - sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal - end end def execute(*args) diff --git a/lib/irb/cmd/help.rb b/lib/irb/cmd/help.rb index 3ef59f5eea..2a135cdb14 100644 --- a/lib/irb/cmd/help.rb +++ b/lib/irb/cmd/help.rb @@ -16,6 +16,17 @@ module IRB module ExtendCommand class Help < Nop + class << self + def transform_args(args) + # Return a string literal as is for backward compatibility + if args.empty? || string_literal?(args) + args + else # Otherwise, consider the input as a String for convenience + args.strip.dump + end + end + end + category "Context" description "Enter the mode to look up RI documents." diff --git a/lib/irb/cmd/nop.rb b/lib/irb/cmd/nop.rb index 32074f2d9f..c616c054a8 100644 --- a/lib/irb/cmd/nop.rb +++ b/lib/irb/cmd/nop.rb @@ -26,6 +26,13 @@ module IRB @description = description if description @description end + + private + + def string_literal?(args) + sexp = Ripper.sexp(args) + sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal + end end if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.7.0" diff --git a/lib/irb/cmd/show_source.rb b/lib/irb/cmd/show_source.rb index b68aa257c2..ea700be4bf 100644 --- a/lib/irb/cmd/show_source.rb +++ b/lib/irb/cmd/show_source.rb @@ -65,11 +65,6 @@ module IRB end first_line end - - def string_literal?(args) - sexp = Ripper.sexp(args) - sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal - end end def execute(str = nil) diff --git a/lib/irb/version.rb b/lib/irb/version.rb index ea95e98f79..d1c0e54fdc 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -11,7 +11,7 @@ # module IRB # :nodoc: - VERSION = "1.6.0" + VERSION = "1.6.2" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2022-11-28" + @LAST_UPDATE_DATE = "2022-12-13" end diff --git a/lib/logger/version.rb b/lib/logger/version.rb index 57735f88db..f85c72eed3 100644 --- a/lib/logger/version.rb +++ b/lib/logger/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class Logger - VERSION = "1.5.2" + VERSION = "1.5.3" end diff --git a/lib/mkmf.rb b/lib/mkmf.rb index e94733c635..0fbc1cc2e5 100644 --- a/lib/mkmf.rb +++ b/lib/mkmf.rb @@ -1866,7 +1866,7 @@ SRC if pkgconfig = with_config("#{pkg}-config") and find_executable0(pkgconfig) # if and only if package specific config command is given elsif ($PKGCONFIG ||= - (pkgconfig = with_config("pkg-config", RbConfig::CONFIG["PKG_CONFIG"])) && + (pkgconfig = with_config("pkg-config") {config_string("PKG_CONFIG") || "pkg-config"}) && find_executable0(pkgconfig) && pkgconfig) and xsystem([*envs, $PKGCONFIG, "--exists", pkg]) # default to pkg-config command diff --git a/lib/net/http.rb b/lib/net/http.rb index 1c64dae4ff..387df4b8f4 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # = net/http.rb # @@ -34,72 +34,70 @@ module Net #:nodoc: # \Class \Net::HTTP provides a rich library that implements the client # in a client-server model that uses the \HTTP request-response protocol. - # For information about \HTTP, see + # For information about \HTTP, see: # # - {Hypertext Transfer Protocol}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol]. # - {Technical overview}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Technical_overview]. # - # Note: If you are performing only a few GET requests, consider using - # {OpenURI}[rdoc-ref:OpenURI]; - # otherwise, read on. - # - # == Synopsis - # - # If you are already familiar with \HTTP, this synopsis may be helpful. - # - # {Session}[rdoc-ref:Net::HTTP@Sessions] with multiple requests for - # {HTTP methods}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods]: - # - # Net::HTTP.start(hostname) do |http| - # # Session started automatically before block execution. - # http.get(path_or_uri, headers = {}) - # http.head(path_or_uri, headers = {}) - # http.post(path_or_uri, data, headers = {}) # Can also have a block. - # http.put(path_or_uri, data, headers = {}) - # http.delete(path_or_uri, headers = {Depth: 'Infinity'}) - # http.options(path_or_uri, headers = {}) - # http.trace(path_or_uri, headers = {}) - # http.patch(path_or_uri, data, headers = {}) # Can also have a block. - # # Session finished automatically at block exit. - # end - # - # {Session}[rdoc-ref:Net::HTTP@Sessions] with multiple requests for - # {WebDAV methods}[https://en.wikipedia.org/wiki/WebDAV#Implementation]: - # - # Net::HTTP.start(hostname) do |http| - # # Session started automatically before block execution. - # http.copy(path_or_uri, headers = {}) - # http.lock(path_or_uri, body, headers = {}) - # http.mkcol(path_or_uri, body = nil, headers = {}) - # http.move(path_or_uri, headers = {}) - # http.propfind(path_or_uri, body = nil, headers = {'Depth' => '0'}) - # http.proppatch(path_or_uri, body, headers = {}) - # http.unlock(path_or_uri, body, headers = {}) - # # Session finished automatically at block exit. - # end - # - # Each of the following methods automatically starts and finishes - # a {session}[rdoc-ref:Net::HTTP@Sessions] that sends a single request: - # - # # Return string response body. - # Net::HTTP.get(hostname, path, port = 80) - # Net::HTTP.get(uri, headers = {}, port = 80) - # - # # Write string response body to $stdout. - # Net::HTTP.get_print(hostname, path_or_uri, port = 80) - # Net::HTTP.get_print(uri, headers = {}, port = 80) - # - # # Return response as Net::HTTPResponse object. - # Net::HTTP.get_response(hostname, path_or_uri, port = 80) - # Net::HTTP.get_response(uri, headers = {}, port = 80) - # - # Net::HTTP.post(uri, data, headers = {}) - # Net::HTTP.post_form(uri, params) - # # == About the Examples # # :include: doc/net-http/examples.rdoc # + # == Strategies + # + # - If you will make only a few GET requests, + # consider using {OpenURI}[rdoc-ref:OpenURI]. + # - If you will make only a few requests of all kinds, + # consider using the various singleton convenience methods in this class. + # Each of the following methods automatically starts and finishes + # a {session}[rdoc-ref:Net::HTTP@Sessions] that sends a single request: + # + # # Return string response body. + # Net::HTTP.get(hostname, path) + # Net::HTTP.get(uri) + # + # # Write string response body to $stdout. + # Net::HTTP.get_print(hostname, path) + # Net::HTTP.get_print(uri) + # + # # Return response as Net::HTTPResponse object. + # Net::HTTP.get_response(hostname, path) + # Net::HTTP.get_response(uri) + # data = '{"title": "foo", "body": "bar", "userId": 1}' + # Net::HTTP.post(uri, data) + # params = {title: 'foo', body: 'bar', userId: 1} + # Net::HTTP.post_form(uri, params) + # + # - If performance is important, consider using sessions, which lower request overhead. + # This {session}[rdoc-ref:Net::HTTP@Sessions] has multiple requests for + # {HTTP methods}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods] + # and {WebDAV methods}[https://en.wikipedia.org/wiki/WebDAV#Implementation]: + # + # Net::HTTP.start(hostname) do |http| + # # Session started automatically before block execution. + # http.get(path) + # http.head(path) + # body = 'Some text' + # http.post(path, body) # Can also have a block. + # http.put(path, body) + # http.delete(path) + # http.options(path) + # http.trace(path) + # http.patch(path, body) # Can also have a block. + # http.copy(path) + # http.lock(path, body) + # http.mkcol(path, body) + # http.move(path) + # http.propfind(path, body) + # http.proppatch(path, body) + # http.unlock(path, body) + # # Session finished automatically at block exit. + # end + # + # The methods cited above are convenience methods that, via their few arguments, + # allow minimal control over the requests. + # For greater control, consider using {request objects}[rdoc-ref:Net::HTTPRequest]. + # # == URIs # # On the internet, a URI @@ -175,7 +173,7 @@ module Net #:nodoc: # {Request Fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields]. # A host may also accept other custom fields. # - # == Sessions + # == \HTTP Sessions # # A _session_ is a connection between a server (host) and a client that: # @@ -183,7 +181,7 @@ module Net #:nodoc: # - May contain any number of requests. # - Is ended by instance method Net::HTTP#finish. # - # See example sessions at the {Synopsis}[rdoc-ref:Net::HTTP@Synopsis]. + # See example sessions at {Strategies}[rdoc-ref:Net::HTTP@Strategies]. # # === Session Using \Net::HTTP.start # @@ -264,66 +262,55 @@ module Net #:nodoc: # # == Following Redirection # - # Each Net::HTTPResponse object belongs to a class for its response code. - # - # For example, all 2XX responses are instances of a Net::HTTPSuccess - # subclass, a 3XX response is an instance of a Net::HTTPRedirection - # subclass and a 200 response is an instance of the Net::HTTPOK class. For - # details of response classes, see the section "HTTP Response Classes" - # below. + # Each returned response is an instance of a subclass of Net::HTTPResponse. + # See the {response class hierarchy}[rdoc-ref:Net::HTTPResponse@Response+Subclasses]. # - # Using a case statement you can handle various types of responses properly: + # In particular, class Net::HTTPRedirection is the parent + # of all redirection classes. + # This allows you to craft a case statement to handle redirections properly: # - # def fetch(uri_str, limit = 10) + # def fetch(uri, limit = 10) # # You should choose a better exception. - # raise ArgumentError, 'too many HTTP redirects' if limit == 0 - # - # response = Net::HTTP.get_response(URI(uri_str)) - # - # case response - # when Net::HTTPSuccess then - # response - # when Net::HTTPRedirection then - # location = response['location'] - # warn "redirected to #{location}" + # raise ArgumentError, 'Too many HTTP redirects' if limit == 0 + # + # res = Net::HTTP.get_response(URI(uri)) + # case res + # when Net::HTTPSuccess # Any success class. + # res + # when Net::HTTPRedirection # Any redirection class. + # location = res['Location'] + # warn "Redirected to #{location}" # fetch(location, limit - 1) - # else - # response.value + # else # Any other class. + # res.value # end # end # - # print fetch('http://www.ruby-lang.org') + # fetch(uri) # # == Basic Authentication # # Basic authentication is performed according to - # [RFC2617](http://www.ietf.org/rfc/rfc2617.txt). - # - # uri = URI('http://example.com/index.html?key=value') + # {RFC2617}[http://www.ietf.org/rfc/rfc2617.txt]: # # req = Net::HTTP::Get.new(uri) - # req.basic_auth 'user', 'pass' - # - # res = Net::HTTP.start(uri.hostname, uri.port) {|http| + # req.basic_auth('user', 'pass') + # res = Net::HTTP.start(hostname) do |http| # http.request(req) - # } - # puts res.body + # end # # == Streaming Response Bodies # - # By default Net::HTTP reads an entire response into memory. If you are + # By default \Net::HTTP reads an entire response into memory. If you are # handling large files or wish to implement a progress bar you can instead # stream the body directly to an IO. # - # uri = URI('http://example.com/large_file') - # - # Net::HTTP.start(uri.host, uri.port) do |http| - # request = Net::HTTP::Get.new uri - # - # http.request request do |response| - # open 'large_file', 'w' do |io| - # response.read_body do |chunk| - # io.write chunk + # Net::HTTP.start(hostname) do |http| + # req = Net::HTTP::Get.new(uri) + # http.request(req) do |res| + # open('t.tmp', 'w') do |f| + # res.read_body do |chunk| + # f.write chunk # end # end # end @@ -331,56 +318,411 @@ module Net #:nodoc: # # == HTTPS # - # HTTPS is enabled for an HTTP connection by Net::HTTP#use_ssl=. + # HTTPS is enabled for an \HTTP connection by Net::HTTP#use_ssl=: # - # uri = URI('https://secure.example.com/some_path?query=string') - # - # Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http| - # request = Net::HTTP::Get.new uri - # response = http.request request # Net::HTTPResponse object + # Net::HTTP.start(hostname, :use_ssl => true) do |http| + # req = Net::HTTP::Get.new(uri) + # res = http.request(req) # end # - # Or if you simply want to make a GET request, you may pass in an URI - # object that has an HTTPS URL. Net::HTTP automatically turns on TLS - # verification if the URI object has a 'https' URI scheme. + # Or if you simply want to make a GET request, you may pass in a URI + # object that has an \HTTPS URL. \Net::HTTP automatically turns on TLS + # verification if the URI object has a 'https' URI scheme: + # + # uri # => #<URI::HTTPS https://jsonplaceholder.typicode.com/> + # Net::HTTP.get(uri) + # + # == Proxy Server + # + # An \HTTP object can have + # a {proxy server}[https://en.wikipedia.org/wiki/Proxy_server]. + # + # You can create an \HTTP object with a proxy server + # using method Net::HTTP.new or method Net::HTTP.start. + # + # The proxy may be defined either by argument +p_addr+ + # or by environment variable <tt>'http_proxy'</tt>. + # + # === Proxy Using Argument +p_addr+ as a \String # - # uri = URI('https://example.com/') - # Net::HTTP.get(uri) # => String + # When argument +p_addr+ is a string hostname, + # the returned +http+ has the given host as its proxy: # - # In previous versions of Ruby you would need to require 'net/https' to use - # HTTPS. This is no longer true. + # http = Net::HTTP.new(hostname, nil, 'proxy.example') + # http.proxy? # => true + # http.proxy_from_env? # => false + # http.proxy_address # => "proxy.example" + # # These use default values. + # http.proxy_port # => 80 + # http.proxy_user # => nil + # http.proxy_pass # => nil # - # == Proxies + # The port, username, and password for the proxy may also be given: # - # Net::HTTP will automatically create a proxy from the +http_proxy+ - # environment variable if it is present. To disable use of +http_proxy+, - # pass +nil+ for the proxy address. + # http = Net::HTTP.new(hostname, nil, 'proxy.example', 8000, 'pname', 'ppass') + # # => #<Net::HTTP jsonplaceholder.typicode.com:80 open=false> + # http.proxy? # => true + # http.proxy_from_env? # => false + # http.proxy_address # => "proxy.example" + # http.proxy_port # => 8000 + # http.proxy_user # => "pname" + # http.proxy_pass # => "ppass" # - # You may also create a custom proxy: + # === Proxy Using '<tt>ENV['http_proxy']</tt>' # - # proxy_addr = 'your.proxy.host' - # proxy_port = 8080 + # When environment variable <tt>'http_proxy'</tt> + # is set to a \URI string, + # the returned +http+ will have the server at that URI as its proxy; + # note that the \URI string must have a protocol + # such as <tt>'http'</tt> or <tt>'https'</tt>: # - # Net::HTTP.new('example.com', nil, proxy_addr, proxy_port).start { |http| - # # always proxy via your.proxy.addr:8080 - # } + # ENV['http_proxy'] = 'http://example.com' + # http = Net::HTTP.new(hostname) + # http.proxy? # => true + # http.proxy_from_env? # => true + # http.proxy_address # => "example.com" + # # These use default values. + # http.proxy_port # => 80 + # http.proxy_user # => nil + # http.proxy_pass # => nil + # + # The \URI string may include proxy username, password, and port number: + # + # ENV['http_proxy'] = 'http://pname:ppass@example.com:8000' + # http = Net::HTTP.new(hostname) + # http.proxy? # => true + # http.proxy_from_env? # => true + # http.proxy_address # => "example.com" + # http.proxy_port # => 8000 + # http.proxy_user # => "pname" + # http.proxy_pass # => "ppass" # - # See Net::HTTP.new for further details and examples such as proxies that - # require a username and password. + # === Filtering Proxies # - # == Compression + # With method Net::HTTP.new (but not Net::HTTP.start), + # you can use argument +p_no_proxy+ to filter proxies: # - # Net::HTTP automatically adds Accept-Encoding for compression of response - # bodies and automatically decompresses gzip and deflate responses unless a - # Range header was sent. + # - Reject a certain address: # - # Compression can be disabled through the Accept-Encoding: identity header. + # http = Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'proxy.example') + # http.proxy_address # => nil + # + # - Reject certain domains or subdomains: + # + # http = Net::HTTP.new('example.com', nil, 'my.proxy.example', 8000, 'pname', 'ppass', 'proxy.example') + # http.proxy_address # => nil + # + # - Reject certain addresses and port combinations: + # + # http = Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'proxy.example:1234') + # http.proxy_address # => "proxy.example" + # + # http = Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'proxy.example:8000') + # http.proxy_address # => nil + # + # - Reject a list of the types above delimited using a comma: + # + # http = Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'my.proxy,proxy.example:8000') + # http.proxy_address # => nil + # + # http = Net::HTTP.new('example.com', nil, 'my.proxy', 8000, 'pname', 'ppass', 'my.proxy,proxy.example:8000') + # http.proxy_address # => nil + # + # == Compression and Decompression + # + # \Net::HTTP does not compress the body of a request before sending. + # + # By default, \Net::HTTP adds header <tt>'Accept-Encoding'</tt> + # to a new {request object}[rdoc-ref:Net::HTTPRequest]: + # + # Net::HTTP::Get.new(uri)['Accept-Encoding'] + # # => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" + # + # This requests the server to zip-encode the response body if there is one; + # the server is not required to do so. + # + # \Net::HTTP does not automatically decompress a response body + # if the response has header <tt>'Content-Range'</tt>. + # + # Otherwise decompression (or not) depends on the value of header + # {Content-Encoding}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-encoding-response-header]: + # + # - <tt>'deflate'</tt>, <tt>'gzip'</tt>, or <tt>'x-gzip'</tt>: + # decompresses the body and deletes the header. + # - <tt>'none'</tt> or <tt>'identity'</tt>: + # does not decompress the body, but deletes the header. + # - Any other value: + # leaves the body and header unchanged. + # + # == What's Here + # + # This is a categorized summary of methods and attributes. + # + # === \Net::HTTP Objects + # + # - {::new}[rdoc-ref:Net::HTTP.new]: + # Creates a new instance. + # - {#inspect}[rdoc-ref:Net::HTTP#inspect]: + # Returns a string representation of +self+. + # + # === Sessions + # + # - {::start}[rdoc-ref:Net::HTTP.start]: + # Begins a new session in a new \Net::HTTP object. + # - {#started?}[rdoc-ref:Net::HTTP#started?] + # (aliased as {#active?}[rdoc-ref:Net::HTTP#active?]): + # Returns whether in a session. + # - {#finish}[rdoc-ref:Net::HTTP#finish]: + # Ends an active session. + # - {#start}[rdoc-ref:Net::HTTP#start]: + # Begins a new session in an existing \Net::HTTP object (+self+). + # + # === Connections + # + # - {:continue_timeout}[rdoc-ref:Net::HTTP#continue_timeout]: + # Returns the continue timeout. + # - {#continue_timeout=}[rdoc-ref:Net::HTTP#continue_timeout=]: + # Sets the continue timeout seconds. + # - {:keep_alive_timeout}[rdoc-ref:Net::HTTP#keep_alive_timeout]: + # Returns the keep-alive timeout. + # - {:keep_alive_timeout=}[rdoc-ref:Net::HTTP#keep_alive_timeout=]: + # Sets the keep-alive timeout. + # - {:max_retries}[rdoc-ref:Net::HTTP#max_retries]: + # Returns the maximum retries. + # - {#max_retries=}[rdoc-ref:Net::HTTP#max_retries=]: + # Sets the maximum retries. + # - {:open_timeout}[rdoc-ref:Net::HTTP#open_timeout]: + # Returns the open timeout. + # - {:open_timeout=}[rdoc-ref:Net::HTTP#open_timeout=]: + # Sets the open timeout. + # - {:read_timeout}[rdoc-ref:Net::HTTP#read_timeout]: + # Returns the open timeout. + # - {:read_timeout=}[rdoc-ref:Net::HTTP#read_timeout=]: + # Sets the read timeout. + # - {:ssl_timeout}[rdoc-ref:Net::HTTP#ssl_timeout]: + # Returns the ssl timeout. + # - {:ssl_timeout=}[rdoc-ref:Net::HTTP#ssl_timeout=]: + # Sets the ssl timeout. + # - {:write_timeout}[rdoc-ref:Net::HTTP#write_timeout]: + # Returns the write timeout. + # - {write_timeout=}[rdoc-ref:Net::HTTP#write_timeout=]: + # Sets the write timeout. + # + # === Requests + # + # - {::get}[rdoc-ref:Net::HTTP.get]: + # Sends a GET request and returns the string response body. + # - {::get_print}[rdoc-ref:Net::HTTP.get_print]: + # Sends a GET request and write the string response body to $stdout. + # - {::get_response}[rdoc-ref:Net::HTTP.get_response]: + # Sends a GET request and returns a response object. + # - {::post_form}[rdoc-ref:Net::HTTP.post_form]: + # Sends a POST request with form data and returns a response object. + # - {::post}[rdoc-ref:Net::HTTP.post]: + # Sends a POST request with data and returns a response object. + # - {#copy}[rdoc-ref:Net::HTTP#copy]: + # Sends a COPY request and returns a response object. + # - {#delete}[rdoc-ref:Net::HTTP#delete]: + # Sends a DELETE request and returns a response object. + # - {#get}[rdoc-ref:Net::HTTP#get]: + # Sends a GET request and returns a response object. + # - {#head}[rdoc-ref:Net::HTTP#head]: + # Sends a HEAD request and returns a response object. + # - {#lock}[rdoc-ref:Net::HTTP#lock]: + # Sends a LOCK request and returns a response object. + # - {#mkcol}[rdoc-ref:Net::HTTP#mkcol]: + # Sends a MKCOL request and returns a response object. + # - {#move}[rdoc-ref:Net::HTTP#move]: + # Sends a MOVE request and returns a response object. + # - {#options}[rdoc-ref:Net::HTTP#options]: + # Sends a OPTIONS request and returns a response object. + # - {#patch}[rdoc-ref:Net::HTTP#patch]: + # Sends a PATCH request and returns a response object. + # - {#post}[rdoc-ref:Net::HTTP#post]: + # Sends a POST request and returns a response object. + # - {#propfind}[rdoc-ref:Net::HTTP#propfind]: + # Sends a PROPFIND request and returns a response object. + # - {#proppatch}[rdoc-ref:Net::HTTP#proppatch]: + # Sends a PROPPATCH request and returns a response object. + # - {#put}[rdoc-ref:Net::HTTP#put]: + # Sends a PUT request and returns a response object. + # - {#request}[rdoc-ref:Net::HTTP#request]: + # Sends a request and returns a response object. + # - {#request_get}[rdoc-ref:Net::HTTP#request_get] + # (aliased as {#get2}[rdoc-ref:Net::HTTP#get2]): + # Sends a GET request and forms a response object; + # if a block given, calls the block with the object, + # otherwise returns the object. + # - {#request_head}[rdoc-ref:Net::HTTP#request_head] + # (aliased as {#head2}[rdoc-ref:Net::HTTP#head2]): + # Sends a HEAD request and forms a response object; + # if a block given, calls the block with the object, + # otherwise returns the object. + # - {#request_post}[rdoc-ref:Net::HTTP#request_post] + # (aliased as {#post2}[rdoc-ref:Net::HTTP#post2]): + # Sends a POST request and forms a response object; + # if a block given, calls the block with the object, + # otherwise returns the object. + # - {#send_request}[rdoc-ref:Net::HTTP#send_request]: + # Sends a request and returns a response object. + # - {#trace}[rdoc-ref:Net::HTTP#trace]: + # Sends a TRACE request and returns a response object. + # - {#unlock}[rdoc-ref:Net::HTTP#unlock]: + # Sends an UNLOCK request and returns a response object. + # + # === Responses + # + # - {:close_on_empty_response}[rdoc-ref:Net::HTTP#close_on_empty_response]: + # Returns whether to close connection on empty response. + # - {:close_on_empty_response=}[rdoc-ref:Net::HTTP#close_on_empty_response=]: + # Sets whether to close connection on empty response. + # - {:ignore_eof}[rdoc-ref:Net::HTTP#ignore_eof]: + # Returns whether to ignore end-of-file when reading a response body + # with <tt>Content-Length</tt> headers. + # - {:ignore_eof=}[rdoc-ref:Net::HTTP#ignore_eof=]: + # Sets whether to ignore end-of-file when reading a response body + # with <tt>Content-Length</tt> headers. + # - {:response_body_encoding}[rdoc-ref:Net::HTTP#response_body_encoding]: + # Returns the encoding to use for the response body. + # - {#response_body_encoding=}[rdoc-ref:Net::HTTP#response_body_encoding=]: + # Sets the response body encoding. + # + # === Proxies + # + # - {:proxy_address}[rdoc-ref:Net::HTTP#proxy_address]: + # Returns the proxy address. + # - {:proxy_address=}[rdoc-ref:Net::HTTP#proxy_address=]: + # Sets the proxy address. + # - {::proxy_class?}[rdoc-ref:Net::HTTP.proxy_class?]: + # Returns whether +self+ is a proxy class. + # - {#proxy?}[rdoc-ref:Net::HTTP#proxy?]: + # Returns whether +self+ has a proxy. + # - {#proxy_address}[rdoc-ref:Net::HTTP#proxy_address] + # (aliased as {#proxyaddr}[rdoc-ref:Net::HTTP#proxyaddr]): + # Returns the proxy address. + # - {#proxy_from_env?}[rdoc-ref:Net::HTTP#proxy_from_env?]: + # Returns whether the proxy is taken from an environment variable. + # - {:proxy_from_env=}[rdoc-ref:Net::HTTP#proxy_from_env=]: + # Sets whether the proxy is to be taken from an environment variable. + # - {:proxy_pass}[rdoc-ref:Net::HTTP#proxy_pass]: + # Returns the proxy password. + # - {:proxy_pass=}[rdoc-ref:Net::HTTP#proxy_pass=]: + # Sets the proxy password. + # - {:proxy_port}[rdoc-ref:Net::HTTP#proxy_port]: + # Returns the proxy port. + # - {:proxy_port=}[rdoc-ref:Net::HTTP#proxy_port=]: + # Sets the proxy port. + # - {#proxy_user}[rdoc-ref:Net::HTTP#proxy_user]: + # Returns the proxy user name. + # - {:proxy_user=}[rdoc-ref:Net::HTTP#proxy_user=]: + # Sets the proxy user. + # + # === Security + # + # - {:ca_file}[rdoc-ref:Net::HTTP#ca_file]: + # Returns the path to a CA certification file. + # - {:ca_file=}[rdoc-ref:Net::HTTP#ca_file=]: + # Sets the path to a CA certification file. + # - {:ca_path}[rdoc-ref:Net::HTTP#ca_path]: + # Returns the path of to CA directory containing certification files. + # - {:ca_path=}[rdoc-ref:Net::HTTP#ca_path=]: + # Sets the path of to CA directory containing certification files. + # - {:cert}[rdoc-ref:Net::HTTP#cert]: + # Returns the OpenSSL::X509::Certificate object to be used for client certification. + # - {:cert=}[rdoc-ref:Net::HTTP#cert=]: + # Sets the OpenSSL::X509::Certificate object to be used for client certification. + # - {:cert_store}[rdoc-ref:Net::HTTP#cert_store]: + # Returns the X509::Store to be used for verifying peer certificate. + # - {:cert_store=}[rdoc-ref:Net::HTTP#cert_store=]: + # Sets the X509::Store to be used for verifying peer certificate. + # - {:ciphers}[rdoc-ref:Net::HTTP#ciphers]: + # Returns the available SSL ciphers. + # - {:ciphers=}[rdoc-ref:Net::HTTP#ciphers=]: + # Sets the available SSL ciphers. + # - {:extra_chain_cert}[rdoc-ref:Net::HTTP#extra_chain_cert]: + # Returns the extra X509 certificates to be added to the certificate chain. + # - {:extra_chain_cert=}[rdoc-ref:Net::HTTP#extra_chain_cert=]: + # Sets the extra X509 certificates to be added to the certificate chain. + # - {:key}[rdoc-ref:Net::HTTP#key]: + # Returns the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. + # - {:key=}[rdoc-ref:Net::HTTP#key=]: + # Sets the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. + # - {:max_version}[rdoc-ref:Net::HTTP#max_version]: + # Returns the maximum SSL version. + # - {:max_version=}[rdoc-ref:Net::HTTP#max_version=]: + # Sets the maximum SSL version. + # - {:min_version}[rdoc-ref:Net::HTTP#min_version]: + # Returns the minimum SSL version. + # - {:min_version=}[rdoc-ref:Net::HTTP#min_version=]: + # Sets the minimum SSL version. + # - {#peer_cert}[rdoc-ref:Net::HTTP#peer_cert]: + # Returns the X509 certificate chain for the session's socket peer. + # - {:ssl_version}[rdoc-ref:Net::HTTP#ssl_version]: + # Returns the SSL version. + # - {:ssl_version=}[rdoc-ref:Net::HTTP#ssl_version=]: + # Sets the SSL version. + # - {#use_ssl=}[rdoc-ref:Net::HTTP#use_ssl=]: + # Sets whether a new session is to use Transport Layer Security. + # - {#use_ssl?}[rdoc-ref:Net::HTTP#use_ssl?]: + # Returns whether +self+ uses SSL. + # - {:verify_callback}[rdoc-ref:Net::HTTP#verify_callback]: + # Returns the callback for the server certification verification. + # - {:verify_callback=}[rdoc-ref:Net::HTTP#verify_callback=]: + # Sets the callback for the server certification verification. + # - {:verify_depth}[rdoc-ref:Net::HTTP#verify_depth]: + # Returns the maximum depth for the certificate chain verification. + # - {:verify_depth=}[rdoc-ref:Net::HTTP#verify_depth=]: + # Sets the maximum depth for the certificate chain verification. + # - {:verify_hostname}[rdoc-ref:Net::HTTP#verify_hostname]: + # Returns the flags for server the certification verification at the beginning of the SSL/TLS session. + # - {:verify_hostname=}[rdoc-ref:Net::HTTP#verify_hostname=]: + # Sets he flags for server the certification verification at the beginning of the SSL/TLS session. + # - {:verify_mode}[rdoc-ref:Net::HTTP#verify_mode]: + # Returns the flags for server the certification verification at the beginning of the SSL/TLS session. + # - {:verify_mode=}[rdoc-ref:Net::HTTP#verify_mode=]: + # Sets the flags for server the certification verification at the beginning of the SSL/TLS session. + # + # === Addresses and Ports + # + # - {:address}[rdoc-ref:Net::HTTP#address]: + # Returns the string host name or host IP. + # - {::default_port}[rdoc-ref:Net::HTTP.default_port]: + # Returns integer 80, the default port to use for HTTP requests. + # - {::http_default_port}[rdoc-ref:Net::HTTP.http_default_port]: + # Returns integer 80, the default port to use for HTTP requests. + # - {::https_default_port}[rdoc-ref:Net::HTTP.https_default_port]: + # Returns integer 443, the default port to use for HTTPS requests. + # - {#ipaddr}[rdoc-ref:Net::HTTP#ipaddr]: + # Returns the IP address for the connection. + # - {#ipaddr=}[rdoc-ref:Net::HTTP#ipaddr=]: + # Sets the IP address for the connection. + # - {:local_host}[rdoc-ref:Net::HTTP#local_host]: + # Returns the string local host used to establish the connection. + # - {:local_host=}[rdoc-ref:Net::HTTP#local_host=]: + # Sets the string local host used to establish the connection. + # - {:local_port}[rdoc-ref:Net::HTTP#local_port]: + # Returns the integer local port used to establish the connection. + # - {:local_port=}[rdoc-ref:Net::HTTP#local_port=]: + # Sets the integer local port used to establish the connection. + # - {:port}[rdoc-ref:Net::HTTP#port]: + # Returns the integer port number. + # + # === \HTTP Version + # + # - {::version_1_2?}[rdoc-ref:Net::HTTP.version_1_2?] + # (aliased as {::is_version_1_2?}[rdoc-ref:Net::HTTP.is_version_1_2?] + # and {::version_1_2}[rdoc-ref:Net::HTTP.version_1_2]): + # Returns true; retained for compatibility. + # + # === Debugging + # + # - {#set_debug_output}[rdoc-ref:Net::HTTP#set_debug_output]: + # Sets the output stream for debugging. # class HTTP < Protocol # :stopdoc: - VERSION = "0.3.1" - Revision = %q$Revision$.split[1] + VERSION = "0.4.1" HTTPVersion = '1.1' begin require 'zlib' @@ -452,6 +794,11 @@ module Net #:nodoc: # headers = {'Content-type' => 'application/json; charset=UTF-8'} # Net::HTTP.get(uri, headers) # + # Related: + # + # - Net::HTTP::Get: request class for \HTTP method +GET+. + # - Net::HTTP#get: convenience method for \HTTP method +GET+. + # def HTTP.get(uri_or_host, path_or_headers = nil, port = nil) get_response(uri_or_host, path_or_headers, port).body end @@ -460,7 +807,7 @@ module Net #:nodoc: # Net::HTTP.get_response(hostname, path, port = 80) -> http_response # Net::HTTP:get_response(uri, headers = {}, port = uri.port) -> http_response # - # Like Net::HTTP.get, but returns an Net::HTTPResponse object + # Like Net::HTTP.get, but returns a Net::HTTPResponse object # instead of the body string. def HTTP.get_response(uri_or_host, path_or_headers = nil, port = nil, &block) if path_or_headers && !path_or_headers.is_a?(Hash) @@ -479,16 +826,31 @@ module Net #:nodoc: end end - # Posts data to the specified URI object. + # Posts data to a host; returns a Net::HTTPResponse object. # - # Example: + # Argument +url+ must be a URL; + # argument +data+ must be a string: + # + # _uri = uri.dup + # _uri.path = '/posts' + # data = '{"title": "foo", "body": "bar", "userId": 1}' + # headers = {'content-type': 'application/json'} + # res = Net::HTTP.post(_uri, data, headers) # => #<Net::HTTPCreated 201 Created readbody=true> + # puts res.body + # + # Output: + # + # { + # "title": "foo", + # "body": "bar", + # "userId": 1, + # "id": 101 + # } # - # require 'net/http' - # require 'uri' + # Related: # - # Net::HTTP.post URI('http://www.example.com/api/search'), - # { "q" => "ruby", "max" => "50" }.to_json, - # "Content-Type" => "application/json" + # - Net::HTTP::Post: request class for \HTTP method +POST+. + # - Net::HTTP#post: convenience method for \HTTP method +POST+. # def HTTP.post(url, data, header = nil) start(url.hostname, url.port, @@ -497,22 +859,25 @@ module Net #:nodoc: } end - # Posts HTML form data to the specified URI object. - # The form data must be provided as a Hash mapping from String to String. - # Example: + # Posts data to a host; returns a Net::HTTPResponse object. # - # { "cmd" => "search", "q" => "ruby", "max" => "50" } + # Argument +url+ must be a URI; + # argument +data+ must be a hash: # - # This method also does Basic Authentication if and only if +url+.user exists. - # But userinfo for authentication is deprecated (RFC3986). - # So this feature will be removed. + # _uri = uri.dup + # _uri.path = '/posts' + # data = {title: 'foo', body: 'bar', userId: 1} + # res = Net::HTTP.post_form(_uri, data) # => #<Net::HTTPCreated 201 Created readbody=true> + # puts res.body # - # Example: - # - # require 'net/http' + # Output: # - # Net::HTTP.post_form URI('http://www.example.com/search.cgi'), - # { "q" => "ruby", "max" => "50" } + # { + # "title": "foo", + # "body": "bar", + # "userId": "1", + # "id": 101 + # } # def HTTP.post_form(url, params) req = Post.new(url) @@ -525,20 +890,29 @@ module Net #:nodoc: end # - # HTTP session management + # \HTTP session management # - # The default port to use for HTTP requests; defaults to 80. + # Returns integer +80+, the default port to use for \HTTP requests: + # + # Net::HTTP.default_port # => 80 + # def HTTP.default_port http_default_port() end - # The default port to use for HTTP requests; defaults to 80. + # Returns integer +80+, the default port to use for \HTTP requests: + # + # Net::HTTP.http_default_port # => 80 + # def HTTP.http_default_port 80 end - # The default port to use for HTTPS requests; defaults to 443. + # Returns integer +443+, the default port to use for HTTPS requests: + # + # Net::HTTP.https_default_port # => 443 + # def HTTP.https_default_port 443 end @@ -548,20 +922,39 @@ module Net #:nodoc: end # :call-seq: - # HTTP.start(address, port, p_addr, p_port, p_user, p_pass) {|http| ... } - # HTTP.start(address, port=nil, p_addr=:ENV, p_port=nil, p_user=nil, p_pass=nil, opt) {|http| ... } + # HTTP.start(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, opts) -> http + # HTTP.start(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, opts) {|http| ... } -> object # - # Creates a new \Net::HTTP object, - # opens a TCP connection and \HTTP session. + # Creates a new \Net::HTTP object, +http+, via \Net::HTTP.new: # - # Argument +address+ is the hostname or IP address of the server. + # - For arguments +address+ and +port+, see Net::HTTP.new. + # - For proxy-defining arguments +p_addr+ through +p_pass+, + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. + # - For argument +opts+, see below. + # + # With no block given: + # + # - Calls <tt>http.start</tt> with no block (see #start), + # which opens a TCP connection and \HTTP session. + # - Returns +http+. + # - The caller should call #finish to close the session: + # + # http = Net::HTTP.start(hostname) + # http.started? # => true + # http.finish + # http.started? # => false # # With a block given: # - # - Passes the object to the given block, - # which may make any number of requests to the host. - # - Closes the \HTTP session on block exit. - # - Returns the block's value. + # - Calls <tt>http.start</tt> with the block (see #start), which: + # + # - Opens a TCP connection and \HTTP session. + # - Calls the block, + # which may make any number of requests to the host. + # - Closes the \HTTP session and TCP connection on block exit. + # - Returns the block's value +object+. + # + # - Returns +object+. # # Example: # @@ -586,19 +979,7 @@ module Net #:nodoc: # "completed": false # } # - # With no block given, returns the \Net::HTTP object; - # the caller should call #finish to close the session. - # - # Other arguments: - # - # - +port+: Server port number. - # - +p_addr+: Proxy address. - # - +p_port+: Proxy port. - # - +p_user+: Proxy user name. - # - +p_pass+: Proxy password. - # - +opts+: Optional options hash. - # - # The options hash +opts+ sets certain values, + # If the last argument given is a hash, it is the +opts+ hash, # where each key is a method or accessor to be called, # and its value is the value to be set. # @@ -623,6 +1004,9 @@ module Net #:nodoc: # - #verify_mode # - #write_timeout # + # Note: If +port+ is +nil+ and <tt>opts[:use_ssl]</tt> is a truthy value, + # the value passed to +new+ is Net::HTTP.https_default_port, not +port+. + # def HTTP.start(address, *arg, &block) # :yield: +http+ arg.pop if opt = Hash.try_convert(arg[-1]) port, p_addr, p_port, p_user, p_pass = *arg @@ -649,25 +1033,34 @@ module Net #:nodoc: alias newobj new # :nodoc: end - # Creates a new Net::HTTP object without opening a TCP connection or - # HTTP session. + # Returns a new \Net::HTTP object +http+ + # (but does not open a TCP connection or \HTTP session). + # + # With only string argument +address+ given + # (and <tt>ENV['http_proxy']</tt> undefined or +nil+), + # the returned +http+: + # + # - Has the given address. + # - Has the default port number, Net::HTTP.default_port (80). + # - Has no proxy. + # + # Example: + # + # http = Net::HTTP.new(hostname) + # # => #<Net::HTTP jsonplaceholder.typicode.com:80 open=false> + # http.address # => "jsonplaceholder.typicode.com" + # http.port # => 80 + # http.proxy? # => false # - # The +address+ should be a DNS hostname or IP address, the +port+ is the - # port the server operates on. If no +port+ is given the default port for - # HTTP or HTTPS is used. + # With integer argument +port+ also given, + # the returned +http+ has the given port: # - # If none of the +p_+ arguments are given, the proxy host and port are - # taken from the +http_proxy+ environment variable (or its uppercase - # equivalent) if present. If the proxy requires authentication you must - # supply it by hand. See URI::Generic#find_proxy for details of proxy - # detection from the environment. To disable proxy detection set +p_addr+ - # to nil. + # http = Net::HTTP.new(hostname, 8000) + # # => #<Net::HTTP jsonplaceholder.typicode.com:8000 open=false> + # http.port # => 8000 # - # If you are connecting to a custom proxy, +p_addr+ specifies the DNS name - # or IP address of the proxy host, +p_port+ the port to use to access the - # proxy, +p_user+ and +p_pass+ the username and password if authorization - # is required to use the proxy, and p_no_proxy hosts which do not - # use the proxy. + # For proxy-defining arguments +p_addr+ through +p_no_proxy+, + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. # def HTTP.new(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_no_proxy = nil) http = super address, port @@ -681,7 +1074,7 @@ module Net #:nodoc: elsif p_addr == :ENV then http.proxy_from_env = true else - if p_addr && p_no_proxy && !URI::Generic.use_proxy?(p_addr, p_addr, p_port, p_no_proxy) + if p_addr && p_no_proxy && !URI::Generic.use_proxy?(address, address, port, p_no_proxy) p_addr = nil p_port = nil end @@ -694,10 +1087,10 @@ module Net #:nodoc: http end - # Creates a new Net::HTTP object for the specified server address, - # without opening the TCP connection or initializing the HTTP session. + # Creates a new \Net::HTTP object for the specified server address, + # without opening the TCP connection or initializing the \HTTP session. # The +address+ should be a DNS hostname or IP address. - def initialize(address, port = nil) + def initialize(address, port = nil) # :nodoc: @address = address @port = (port || HTTP.default_port) @ipaddr = nil @@ -734,6 +1127,11 @@ module Net #:nodoc: end end + # Returns a string representation of +self+: + # + # Net::HTTP.new(hostname).inspect + # # => "#<Net::HTTP jsonplaceholder.typicode.com:80 open=false>" + # def inspect "#<#{self.class} #{@address}:#{@port} open=#{started?}>" end @@ -741,83 +1139,184 @@ module Net #:nodoc: # *WARNING* This method opens a serious security hole. # Never use this method in production code. # - # Sets an output stream for debugging. + # Sets the output stream for debugging: # # http = Net::HTTP.new(hostname) - # http.set_debug_output $stderr - # http.start { .... } + # File.open('t.tmp', 'w') do |file| + # http.set_debug_output(file) + # http.start + # http.get('/nosuch/1') + # http.finish + # end + # puts File.read('t.tmp') + # + # Output: + # + # opening connection to jsonplaceholder.typicode.com:80... + # opened + # <- "GET /nosuch/1 HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: jsonplaceholder.typicode.com\r\n\r\n" + # -> "HTTP/1.1 404 Not Found\r\n" + # -> "Date: Mon, 12 Dec 2022 21:14:11 GMT\r\n" + # -> "Content-Type: application/json; charset=utf-8\r\n" + # -> "Content-Length: 2\r\n" + # -> "Connection: keep-alive\r\n" + # -> "X-Powered-By: Express\r\n" + # -> "X-Ratelimit-Limit: 1000\r\n" + # -> "X-Ratelimit-Remaining: 999\r\n" + # -> "X-Ratelimit-Reset: 1670879660\r\n" + # -> "Vary: Origin, Accept-Encoding\r\n" + # -> "Access-Control-Allow-Credentials: true\r\n" + # -> "Cache-Control: max-age=43200\r\n" + # -> "Pragma: no-cache\r\n" + # -> "Expires: -1\r\n" + # -> "X-Content-Type-Options: nosniff\r\n" + # -> "Etag: W/\"2-vyGp6PvFo4RvsFtPoIWeCReyIC8\"\r\n" + # -> "Via: 1.1 vegur\r\n" + # -> "CF-Cache-Status: MISS\r\n" + # -> "Server-Timing: cf-q-config;dur=1.3000000762986e-05\r\n" + # -> "Report-To: {\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=yOr40jo%2BwS1KHzhTlVpl54beJ5Wx2FcG4gGV0XVrh3X9OlR5q4drUn2dkt5DGO4GDcE%2BVXT7CNgJvGs%2BZleIyMu8CLieFiDIvOviOY3EhHg94m0ZNZgrEdpKD0S85S507l1vsEwEHkoTm%2Ff19SiO\"}],\"group\":\"cf-nel\",\"max_age\":604800}\r\n" + # -> "NEL: {\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}\r\n" + # -> "Server: cloudflare\r\n" + # -> "CF-RAY: 778977dc484ce591-DFW\r\n" + # -> "alt-svc: h3=\":443\"; ma=86400, h3-29=\":443\"; ma=86400\r\n" + # -> "\r\n" + # reading 2 bytes... + # -> "{}" + # read 2 bytes + # Conn keep-alive # def set_debug_output(output) warn 'Net::HTTP#set_debug_output called after HTTP started', uplevel: 1 if started? @debug_output = output end - # The DNS host name or IP address to connect to. + # Returns the string host name or host IP given as argument +address+ in ::new. attr_reader :address - # The port number to connect to. + # Returns the integer port number given as argument +port+ in ::new. attr_reader :port - # The local host used to establish the connection. + # Sets or returns the string local host used to establish the connection; + # initially +nil+. attr_accessor :local_host - # The local port used to establish the connection. + # Sets or returns the integer local port used to establish the connection; + # initially +nil+. attr_accessor :local_port - # The encoding to use for the response body. If Encoding, uses the - # specified encoding. If other true value, tries to detect the response - # body encoding. + # Returns the encoding to use for the response body; + # see #response_body_encoding=. attr_reader :response_body_encoding - # Set the encoding to use for the response body. If given a String, find - # the related Encoding. + # Sets the encoding to be used for the response body; + # returns the encoding. + # + # The given +value+ may be: + # + # - An Encoding object. + # - The name of an encoding. + # - An alias for an encoding name. + # + # See {Encoding}[rdoc-ref:Encoding]. + # + # Examples: + # + # http = Net::HTTP.new(hostname) + # http.response_body_encoding = Encoding::US_ASCII # => #<Encoding:US-ASCII> + # http.response_body_encoding = 'US-ASCII' # => "US-ASCII" + # http.response_body_encoding = 'ASCII' # => "ASCII" + # def response_body_encoding=(value) value = Encoding.find(value) if value.is_a?(String) @response_body_encoding = value end + # Sets whether to determine the proxy from environment variable + # '<tt>ENV['http_proxy']</tt>'; + # see {Proxy Using ENV['http_proxy']}[rdoc-ref:Net::HTTP@Proxy+Using+-27ENV-5B-27http_proxy-27-5D-27]. attr_writer :proxy_from_env + + # Sets the proxy address; + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_address + + # Sets the proxy port; + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_port + + # Sets the proxy user; + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_user + + # Sets the proxy password; + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_pass - # The IP address to connect to/used to connect to + # Returns the IP address for the connection. + # + # If the session has not been started, + # returns the value set by #ipaddr=, + # or +nil+ if it has not been set: + # + # http = Net::HTTP.new(hostname) + # http.ipaddr # => nil + # http.ipaddr = '172.67.155.76' + # http.ipaddr # => "172.67.155.76" + # + # If the session has been started, + # returns the IP address from the socket: + # + # http = Net::HTTP.new(hostname) + # http.start + # http.ipaddr # => "172.67.155.76" + # http.finish + # def ipaddr started? ? @socket.io.peeraddr[3] : @ipaddr end - # Set the IP address to connect to + # Sets the IP address for the connection: + # + # http = Net::HTTP.new(hostname) + # http.ipaddr # => nil + # http.ipaddr = '172.67.155.76' + # http.ipaddr # => "172.67.155.76" + # + # The IP address may not be set if the session has been started. def ipaddr=(addr) raise IOError, "ipaddr value changed, but session already started" if started? @ipaddr = addr end - # Number of seconds to wait for the connection to open. Any number - # may be used, including Floats for fractional seconds. If the HTTP - # object cannot open a connection in this many seconds, it raises a - # Net::OpenTimeout exception. The default value is 60 seconds. + # Sets or returns the numeric (\Integer or \Float) number of seconds + # to wait for a connection to open; + # initially 60. + # If the connection is not made in the given interval, + # an exception is raised. attr_accessor :open_timeout - # Number of seconds to wait for one block to be read (via one read(2) - # call). Any number may be used, including Floats for fractional - # seconds. If the HTTP object cannot read data in this many seconds, - # it raises a Net::ReadTimeout exception. The default value is 60 seconds. + # Returns the numeric (\Integer or \Float) number of seconds + # to wait for one block to be read (via one read(2) call); + # see #read_timeout=. attr_reader :read_timeout - # Number of seconds to wait for one block to be written (via one write(2) - # call). Any number may be used, including Floats for fractional - # seconds. If the HTTP object cannot write data in this many seconds, - # it raises a Net::WriteTimeout exception. The default value is 60 seconds. - # Net::WriteTimeout is not raised on Windows. + # Returns the numeric (\Integer or \Float) number of seconds + # to wait for one block to be written (via one write(2) call); + # see #write_timeout=. attr_reader :write_timeout - # Maximum number of times to retry an idempotent request in case of - # Net::ReadTimeout, IOError, EOFError, Errno::ECONNRESET, + # Sets the maximum number of times to retry an idempotent request in case of + # \Net::ReadTimeout, IOError, EOFError, Errno::ECONNRESET, # Errno::ECONNABORTED, Errno::EPIPE, OpenSSL::SSL::SSLError, # Timeout::Error. - # Should be a non-negative integer number. Zero means no retries. - # The default value is 1. + # The initial value is 1. + # + # Argument +retries+ must be a non-negative numeric value: + # + # http = Net::HTTP.new(hostname) + # http.max_retries = 2 # => 2 + # http.max_retries # => 2 + # def max_retries=(retries) retries = retries.to_int if retries < 0 @@ -826,59 +1325,113 @@ module Net #:nodoc: @max_retries = retries end + # Returns the maximum number of times to retry an idempotent request; + # see #max_retries=. attr_reader :max_retries - # Setter for the read_timeout attribute. + # Sets the read timeout, in seconds, for +self+ to integer +sec+; + # the initial value is 60. + # + # Argument +sec+ must be a non-negative numeric value: + # + # http = Net::HTTP.new(hostname) + # http.read_timeout # => 60 + # http.get('/todos/1') # => #<Net::HTTPOK 200 OK readbody=true> + # http.read_timeout = 0 + # http.get('/todos/1') # Raises Net::ReadTimeout. + # def read_timeout=(sec) @socket.read_timeout = sec if @socket @read_timeout = sec end - # Setter for the write_timeout attribute. + # Sets the write timeout, in seconds, for +self+ to integer +sec+; + # the initial value is 60. + # + # Argument +sec+ must be a non-negative numeric value: + # + # _uri = uri.dup + # _uri.path = '/posts' + # body = 'bar' * 200000 + # data = <<EOF + # {"title": "foo", "body": "#{body}", "userId": "1"} + # EOF + # headers = {'content-type': 'application/json'} + # http = Net::HTTP.new(hostname) + # http.write_timeout # => 60 + # http.post(_uri.path, data, headers) + # # => #<Net::HTTPCreated 201 Created readbody=true> + # http.write_timeout = 0 + # http.post(_uri.path, data, headers) # Raises Net::WriteTimeout. + # def write_timeout=(sec) @socket.write_timeout = sec if @socket @write_timeout = sec end - # Seconds to wait for 100 Continue response. If the HTTP object does not - # receive a response in this many seconds it sends the request body. The - # default value is +nil+. + # Returns the continue timeout value; + # see continue_timeout=. attr_reader :continue_timeout - # Setter for the continue_timeout attribute. + # Sets the continue timeout value, + # which is the number of seconds to wait for an expected 100 Continue response. + # If the \HTTP object does not receive a response in this many seconds + # it sends the request body. def continue_timeout=(sec) @socket.continue_timeout = sec if @socket @continue_timeout = sec end - # Seconds to reuse the connection of the previous request. - # If the idle time is less than this Keep-Alive Timeout, - # Net::HTTP reuses the TCP/IP socket used by the previous communication. - # The default value is 2 seconds. + # Sets or returns the numeric (\Integer or \Float) number of seconds + # to keep the connection open after a request is sent; + # initially 2. + # If a new request is made during the given interval, + # the still-open connection is used; + # otherwise the connection will have been closed + # and a new connection is opened. attr_accessor :keep_alive_timeout - # Whether to ignore EOF when reading response bodies with defined - # Content-Length headers. For backwards compatibility, the default is true. + # Sets or returns whether to ignore end-of-file when reading a response body + # with <tt>Content-Length</tt> headers; + # initially +true+. attr_accessor :ignore_eof - # Returns true if the HTTP session has been started. + # Returns +true+ if the \HTTP session has been started: + # + # http = Net::HTTP.new(hostname) + # http.started? # => false + # http.start + # http.started? # => true + # http.finish # => nil + # http.started? # => false + # + # Net::HTTP.start(hostname) do |http| + # http.started? + # end # => true + # http.started? # => false + # def started? @started end alias active? started? #:nodoc: obsolete + # Sets or returns whether to close the connection when the response is empty; + # initially +false+. attr_accessor :close_on_empty_response - # Returns true if SSL/TLS is being used with HTTP. + # Returns +true+ if +self+ uses SSL, +false+ otherwise. + # See Net::HTTP#use_ssl=. def use_ssl? @use_ssl end - # Turn on/off SSL. - # This flag must be set before starting session. - # If you change use_ssl value after session started, - # a Net::HTTP object raises IOError. + # Sets whether a new session is to use + # {Transport Layer Security}[https://en.wikipedia.org/wiki/Transport_Layer_Security]: + # + # Raises IOError if attempting to change during a session. + # + # Raises OpenSSL::SSL::SSLError if the port is not an HTTPS port. def use_ssl=(flag) flag = flag ? true : false if started? and @use_ssl != flag @@ -903,7 +1456,7 @@ module Net #:nodoc: :@verify_depth, :@verify_mode, :@verify_hostname, - ] + ] # :nodoc: SSL_ATTRIBUTES = [ :ca_file, :ca_path, @@ -920,64 +1473,67 @@ module Net #:nodoc: :verify_depth, :verify_mode, :verify_hostname, - ] + ] # :nodoc: - # Sets path of a CA certification file in PEM format. - # - # The file can contain several CA certificates. + # Sets or returns the path to a CA certification file in PEM format. attr_accessor :ca_file - # Sets path of a CA certification directory containing certifications in - # PEM format. + # Sets or returns the path of to CA directory + # containing certification files in PEM format. attr_accessor :ca_path - # Sets an OpenSSL::X509::Certificate object as client certificate. - # (This method is appeared in Michal Rokos's OpenSSL extension). + # Sets or returns the OpenSSL::X509::Certificate object + # to be used for client certification. attr_accessor :cert - # Sets the X509::Store to verify peer certificate. + # Sets or returns the X509::Store to be used for verifying peer certificate. attr_accessor :cert_store - # Sets the available ciphers. See OpenSSL::SSL::SSLContext#ciphers= + # Sets or returns the available SSL ciphers. + # See {OpenSSL::SSL::SSLContext#ciphers=}[rdoc-ref:OpenSSL::SSL::SSLContext#ciphers-3D]. attr_accessor :ciphers - # Sets the extra X509 certificates to be added to the certificate chain. - # See OpenSSL::SSL::SSLContext#extra_chain_cert= + # Sets or returns the extra X509 certificates to be added to the certificate chain. + # See {OpenSSL::SSL::SSLContext#add_certificate}[rdoc-ref:OpenSSL::SSL::SSLContext#add_certificate]. attr_accessor :extra_chain_cert - # Sets an OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. - # (This method is appeared in Michal Rokos's OpenSSL extension.) + # Sets or returns the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. attr_accessor :key - # Sets the SSL timeout seconds. + # Sets or returns the SSL timeout seconds. attr_accessor :ssl_timeout - # Sets the SSL version. See OpenSSL::SSL::SSLContext#ssl_version= + # Sets or returns the SSL version. + # See {OpenSSL::SSL::SSLContext#ssl_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#ssl_version-3D]. attr_accessor :ssl_version - # Sets the minimum SSL version. See OpenSSL::SSL::SSLContext#min_version= + # Sets or returns the minimum SSL version. + # See {OpenSSL::SSL::SSLContext#min_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#min_version-3D]. attr_accessor :min_version - # Sets the maximum SSL version. See OpenSSL::SSL::SSLContext#max_version= + # Sets or returns the maximum SSL version. + # See {OpenSSL::SSL::SSLContext#max_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#max_version-3D]. attr_accessor :max_version - # Sets the verify callback for the server certification verification. + # Sets or returns the callback for the server certification verification. attr_accessor :verify_callback - # Sets the maximum depth for the certificate chain verification. + # Sets or returns the maximum depth for the certificate chain verification. attr_accessor :verify_depth - # Sets the flags for server the certification verification at beginning of - # SSL/TLS session. - # + # Sets or returns the flags for server the certification verification + # at the beginning of the SSL/TLS session. # OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER are acceptable. attr_accessor :verify_mode - # Sets to check the server certificate is valid for the hostname. - # See OpenSSL::SSL::SSLContext#verify_hostname= + # Sets or returns whether to verify that the server certificate is valid + # for the hostname. + # See {OpenSSL::SSL::SSLContext#verify_hostname=}[rdoc-ref:OpenSSL::SSL::SSLContext#attribute-i-verify_mode]. attr_accessor :verify_hostname - # Returns the X.509 certificates the server presented. + # Returns the X509 certificate chain (an array of strings) + # for the session's socket peer, + # or +nil+ if none. def peer_cert if not use_ssl? or not @socket return nil @@ -985,14 +1541,26 @@ module Net #:nodoc: @socket.io.peer_cert end - # Opens a TCP connection and HTTP session. + # Starts an \HTTP session. # - # When this method is called with a block, it passes the Net::HTTP - # object to the block, and closes the TCP connection and HTTP session - # after the block has been executed. + # Without a block, returns +self+: # - # When called with a block, it returns the return value of the - # block; otherwise, it returns self. + # http = Net::HTTP.new(hostname) + # # => #<Net::HTTP jsonplaceholder.typicode.com:80 open=false> + # http.start + # # => #<Net::HTTP jsonplaceholder.typicode.com:80 open=true> + # http.started? # => true + # http.finish + # + # With a block, calls the block with +self+, + # finishes the session when the block exits, + # and returns the block's value: + # + # http.start do |http| + # http + # end + # # => #<Net::HTTP jsonplaceholder.typicode.com:80 open=false> + # http.started? # => false # def start # :yield: http raise IOError, 'HTTP session already opened' if @started @@ -1046,8 +1614,8 @@ module Net #:nodoc: write_timeout: @write_timeout, continue_timeout: @continue_timeout, debug_output: @debug_output) - buf = "CONNECT #{conn_address}:#{@port} HTTP/#{HTTPVersion}\r\n" - buf << "Host: #{@address}:#{@port}\r\n" + buf = +"CONNECT #{conn_address}:#{@port} HTTP/#{HTTPVersion}\r\n" \ + "Host: #{@address}:#{@port}\r\n" if proxy_user credential = ["#{proxy_user}:#{proxy_pass}"].pack('m0') buf << "Proxy-Authorization: Basic #{credential}\r\n" @@ -1128,8 +1696,15 @@ module Net #:nodoc: end private :on_connect - # Finishes the HTTP session and closes the TCP connection. - # Raises IOError if the session has not been started. + # Finishes the \HTTP session: + # + # http = Net::HTTP.new(hostname) + # http.start + # http.started? # => true + # http.finish # => nil + # http.started? # => false + # + # Raises IOError if not in a session. def finish raise IOError, 'HTTP session not yet started' unless started? do_finish @@ -1156,12 +1731,12 @@ module Net #:nodoc: @proxy_user = nil @proxy_pass = nil - # Creates an HTTP proxy class which behaves like Net::HTTP, but + # Creates an \HTTP proxy class which behaves like \Net::HTTP, but # performs all access via the specified proxy. # # This class is obsolete. You may pass these same parameters directly to - # Net::HTTP.new. See Net::HTTP.new for details of the arguments. - def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil) + # \Net::HTTP.new. See Net::HTTP.new for details of the arguments. + def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil) #:nodoc: return self unless p_addr Class.new(self) { @@ -1183,31 +1758,37 @@ module Net #:nodoc: end class << HTTP - # returns true if self is a class which was created by HTTP::Proxy. + # Returns true if self is a class which was created by HTTP::Proxy. def proxy_class? defined?(@is_proxy_class) ? @is_proxy_class : false end - # Address of proxy host. If Net::HTTP does not use a proxy, nil. + # Returns the address of the proxy host, or +nil+ if none; + # see Net::HTTP@Proxy+Server. attr_reader :proxy_address - # Port number of proxy host. If Net::HTTP does not use a proxy, nil. + # Returns the port number of the proxy host, or +nil+ if none; + # see Net::HTTP@Proxy+Server. attr_reader :proxy_port - # User name for accessing proxy. If Net::HTTP does not use a proxy, nil. + # Returns the user name for accessing the proxy, or +nil+ if none; + # see Net::HTTP@Proxy+Server. attr_reader :proxy_user - # User password for accessing proxy. If Net::HTTP does not use a proxy, - # nil. + # Returns the password for accessing the proxy, or +nil+ if none; + # see Net::HTTP@Proxy+Server. attr_reader :proxy_pass end - # True if requests for this connection will be proxied + # Returns +true+ if a proxy server is defined, +false+ otherwise; + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. def proxy? !!(@proxy_from_env ? proxy_uri : @proxy_address) end - # True if the proxy for this connection is determined from the environment + # Returns +true+ if the proxy server is defined in the environment, + # +false+ otherwise; + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. def proxy_from_env? @proxy_from_env end @@ -1216,12 +1797,13 @@ module Net #:nodoc: def proxy_uri # :nodoc: return if @proxy_uri == false @proxy_uri ||= URI::HTTP.new( - "http".freeze, nil, address, port, nil, nil, nil, nil, nil + "http", nil, address, port, nil, nil, nil, nil, nil ).find_proxy || false @proxy_uri || nil end - # The address of the proxy server, if one is configured. + # Returns the address of the proxy server, if defined, +nil+ otherwise; + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. def proxy_address if @proxy_from_env then proxy_uri&.hostname @@ -1230,7 +1812,8 @@ module Net #:nodoc: end end - # The port of the proxy server, if one is configured. + # Returns the port number of the proxy server, if defined, +nil+ otherwise; + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. def proxy_port if @proxy_from_env then proxy_uri&.port @@ -1239,7 +1822,8 @@ module Net #:nodoc: end end - # The username of the proxy server, if one is configured. + # Returns the user name of the proxy server, if defined, +nil+ otherwise; + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. def proxy_user if @proxy_from_env user = proxy_uri&.user @@ -1249,7 +1833,8 @@ module Net #:nodoc: end end - # The password of the proxy server, if one is configured. + # Returns the password of the proxy server, if defined, +nil+ otherwise; + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. def proxy_pass if @proxy_from_env pass = proxy_uri&.password @@ -1297,45 +1882,38 @@ module Net #:nodoc: public - # Retrieves data from +path+ on the connected-to host which may be an - # absolute path String or a URI to extract the path from. + # :call-seq: + # get(path, initheader = nil) {|res| ... } + # + # Sends a GET request to the server; + # returns an instance of a subclass of Net::HTTPResponse. # - # +initheader+ must be a Hash like { 'Accept' => '*/*', ... }, - # and it defaults to an empty hash. - # If +initheader+ doesn't have the key 'accept-encoding', then - # a value of "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" is used, - # so that gzip compression is used in preference to deflate - # compression, which is used in preference to no compression. - # Ruby doesn't have libraries to support the compress (Lempel-Ziv) - # compression, so that is not supported. The intent of this is - # to reduce bandwidth by default. If this routine sets up - # compression, then it does the decompression also, removing - # the header as well to prevent confusion. Otherwise - # it leaves the body as it found it. + # The request is based on the Net::HTTP::Get object + # created from string +path+ and initial headers hash +initheader+. # - # This method returns a Net::HTTPResponse object. + # With a block given, calls the block with the response body: # - # If called with a block, yields each fragment of the - # entity body in turn as a string as it is read from - # the socket. Note that in this case, the returned response - # object will *not* contain a (meaningful) body. + # http = Net::HTTP.new(hostname) + # http.get('/todos/1') do |res| + # p res + # end # => #<Net::HTTPOK 200 OK readbody=true> + # + # Output: + # + # "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false\n}" # - # +dest+ argument is obsolete. - # It still works but you must not use it. + # With no block given, simply returns the response object: # - # This method never raises an exception. + # http.get('/') # => #<Net::HTTPOK 200 OK readbody=true> # - # response = http.get('/index.html') + # Related: # - # # using block - # File.open('result.txt', 'w') {|f| - # http.get('/~foo/') do |str| - # f.write str - # end - # } + # - Net::HTTP::Get: request class for \HTTP method GET. + # - Net::HTTP.get: sends GET request, returns response body. # def get(path, initheader = nil, dest = nil, &block) # :yield: +body_segment+ res = nil + request(Get.new(path, initheader)) {|r| r.read_body dest, &block res = r @@ -1343,198 +1921,312 @@ module Net #:nodoc: res end - # Gets only the header from +path+ on the connected-to host. - # +header+ is a Hash like { 'Accept' => '*/*', ... }. - # - # This method returns a Net::HTTPResponse object. + # Sends a HEAD request to the server; + # returns an instance of a subclass of Net::HTTPResponse. # - # This method never raises an exception. + # The request is based on the Net::HTTP::Head object + # created from string +path+ and initial headers hash +initheader+: # - # response = nil - # Net::HTTP.start('some.www.server', 80) {|http| - # response = http.head('/index.html') - # } - # p response['content-type'] + # res = http.head('/todos/1') # => #<Net::HTTPOK 200 OK readbody=true> + # res.body # => nil + # res.to_hash.take(3) + # # => + # [["date", ["Wed, 15 Feb 2023 15:25:42 GMT"]], + # ["content-type", ["application/json; charset=utf-8"]], + # ["connection", ["close"]]] # def head(path, initheader = nil) request(Head.new(path, initheader)) end - # Posts +data+ (must be a String) to +path+. +header+ must be a Hash - # like { 'Accept' => '*/*', ... }. + # :call-seq: + # post(path, data, initheader = nil) {|res| ... } # - # This method returns a Net::HTTPResponse object. + # Sends a POST request to the server; + # returns an instance of a subclass of Net::HTTPResponse. # - # If called with a block, yields each fragment of the - # entity body in turn as a string as it is read from - # the socket. Note that in this case, the returned response - # object will *not* contain a (meaningful) body. + # The request is based on the Net::HTTP::Post object + # created from string +path+, string +data+, and initial headers hash +initheader+. # - # +dest+ argument is obsolete. - # It still works but you must not use it. + # With a block given, calls the block with the response body: # - # This method never raises exception. + # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' + # http = Net::HTTP.new(hostname) + # http.post('/todos', data) do |res| + # p res + # end # => #<Net::HTTPCreated 201 Created readbody=true> + # + # Output: + # + # "{\n \"{\\\"userId\\\": 1, \\\"id\\\": 1, \\\"title\\\": \\\"delectus aut autem\\\", \\\"completed\\\": false}\": \"\",\n \"id\": 201\n}" # - # response = http.post('/cgi-bin/search.rb', 'query=foo') + # With no block given, simply returns the response object: # - # # using block - # File.open('result.txt', 'w') {|f| - # http.post('/cgi-bin/search.rb', 'query=foo') do |str| - # f.write str - # end - # } + # http.post('/todos', data) # => #<Net::HTTPCreated 201 Created readbody=true> # - # You should set Content-Type: header field for POST. - # If no Content-Type: field given, this method uses - # "application/x-www-form-urlencoded" by default. + # Related: + # + # - Net::HTTP::Post: request class for \HTTP method POST. + # - Net::HTTP.post: sends POST request, returns response body. # def post(path, data, initheader = nil, dest = nil, &block) # :yield: +body_segment+ send_entity(path, data, initheader, dest, Post, &block) end - # Sends a PATCH request to the +path+ and gets a response, - # as an HTTPResponse object. + # :call-seq: + # patch(path, data, initheader = nil) {|res| ... } + # + # Sends a PATCH request to the server; + # returns an instance of a subclass of Net::HTTPResponse. + # + # The request is based on the Net::HTTP::Patch object + # created from string +path+, string +data+, and initial headers hash +initheader+. + # + # With a block given, calls the block with the response body: + # + # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' + # http = Net::HTTP.new(hostname) + # http.patch('/todos/1', data) do |res| + # p res + # end # => #<Net::HTTPOK 200 OK readbody=true> + # + # Output: + # + # "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false,\n \"{\\\"userId\\\": 1, \\\"id\\\": 1, \\\"title\\\": \\\"delectus aut autem\\\", \\\"completed\\\": false}\": \"\"\n}" + # + # With no block given, simply returns the response object: + # + # http.patch('/todos/1', data) # => #<Net::HTTPCreated 201 Created readbody=true> + # def patch(path, data, initheader = nil, dest = nil, &block) # :yield: +body_segment+ send_entity(path, data, initheader, dest, Patch, &block) end - def put(path, data, initheader = nil) #:nodoc: + # Sends a PUT request to the server; + # returns an instance of a subclass of Net::HTTPResponse. + # + # The request is based on the Net::HTTP::Put object + # created from string +path+, string +data+, and initial headers hash +initheader+. + # + # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' + # http = Net::HTTP.new(hostname) + # http.put('/todos/1', data) # => #<Net::HTTPOK 200 OK readbody=true> + # + def put(path, data, initheader = nil) request(Put.new(path, initheader), data) end - # Sends a PROPPATCH request to the +path+ and gets a response, - # as an HTTPResponse object. + # Sends a PROPPATCH request to the server; + # returns an instance of a subclass of Net::HTTPResponse. + # + # The request is based on the Net::HTTP::Proppatch object + # created from string +path+, string +body+, and initial headers hash +initheader+. + # + # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' + # http = Net::HTTP.new(hostname) + # http.proppatch('/todos/1', data) + # def proppatch(path, body, initheader = nil) request(Proppatch.new(path, initheader), body) end - # Sends a LOCK request to the +path+ and gets a response, - # as an HTTPResponse object. + # Sends a LOCK request to the server; + # returns an instance of a subclass of Net::HTTPResponse. + # + # The request is based on the Net::HTTP::Lock object + # created from string +path+, string +body+, and initial headers hash +initheader+. + # + # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' + # http = Net::HTTP.new(hostname) + # http.lock('/todos/1', data) + # def lock(path, body, initheader = nil) request(Lock.new(path, initheader), body) end - # Sends a UNLOCK request to the +path+ and gets a response, - # as an HTTPResponse object. + # Sends an UNLOCK request to the server; + # returns an instance of a subclass of Net::HTTPResponse. + # + # The request is based on the Net::HTTP::Unlock object + # created from string +path+, string +body+, and initial headers hash +initheader+. + # + # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' + # http = Net::HTTP.new(hostname) + # http.unlock('/todos/1', data) + # def unlock(path, body, initheader = nil) request(Unlock.new(path, initheader), body) end - # Sends a OPTIONS request to the +path+ and gets a response, - # as an HTTPResponse object. + # Sends an Options request to the server; + # returns an instance of a subclass of Net::HTTPResponse. + # + # The request is based on the Net::HTTP::Options object + # created from string +path+ and initial headers hash +initheader+. + # + # http = Net::HTTP.new(hostname) + # http.options('/') + # def options(path, initheader = nil) request(Options.new(path, initheader)) end - # Sends a PROPFIND request to the +path+ and gets a response, - # as an HTTPResponse object. + # Sends a PROPFIND request to the server; + # returns an instance of a subclass of Net::HTTPResponse. + # + # The request is based on the Net::HTTP::Propfind object + # created from string +path+, string +body+, and initial headers hash +initheader+. + # + # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' + # http = Net::HTTP.new(hostname) + # http.propfind('/todos/1', data) + # def propfind(path, body = nil, initheader = {'Depth' => '0'}) request(Propfind.new(path, initheader), body) end - # Sends a DELETE request to the +path+ and gets a response, - # as an HTTPResponse object. + # Sends a DELETE request to the server; + # returns an instance of a subclass of Net::HTTPResponse. + # + # The request is based on the Net::HTTP::Delete object + # created from string +path+ and initial headers hash +initheader+. + # + # http = Net::HTTP.new(hostname) + # http.delete('/todos/1') + # def delete(path, initheader = {'Depth' => 'Infinity'}) request(Delete.new(path, initheader)) end - # Sends a MOVE request to the +path+ and gets a response, - # as an HTTPResponse object. + # Sends a MOVE request to the server; + # returns an instance of a subclass of Net::HTTPResponse. + # + # The request is based on the Net::HTTP::Move object + # created from string +path+ and initial headers hash +initheader+. + # + # http = Net::HTTP.new(hostname) + # http.move('/todos/1') + # def move(path, initheader = nil) request(Move.new(path, initheader)) end - # Sends a COPY request to the +path+ and gets a response, - # as an HTTPResponse object. + # Sends a COPY request to the server; + # returns an instance of a subclass of Net::HTTPResponse. + # + # The request is based on the Net::HTTP::Copy object + # created from string +path+ and initial headers hash +initheader+. + # + # http = Net::HTTP.new(hostname) + # http.copy('/todos/1') + # def copy(path, initheader = nil) request(Copy.new(path, initheader)) end - # Sends a MKCOL request to the +path+ and gets a response, - # as an HTTPResponse object. + # Sends a MKCOL request to the server; + # returns an instance of a subclass of Net::HTTPResponse. + # + # The request is based on the Net::HTTP::Mkcol object + # created from string +path+, string +body+, and initial headers hash +initheader+. + # + # data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}' + # http.mkcol('/todos/1', data) + # http = Net::HTTP.new(hostname) + # def mkcol(path, body = nil, initheader = nil) request(Mkcol.new(path, initheader), body) end - # Sends a TRACE request to the +path+ and gets a response, - # as an HTTPResponse object. + # Sends a TRACE request to the server; + # returns an instance of a subclass of Net::HTTPResponse. + # + # The request is based on the Net::HTTP::Trace object + # created from string +path+ and initial headers hash +initheader+. + # + # http = Net::HTTP.new(hostname) + # http.trace('/todos/1') + # def trace(path, initheader = nil) request(Trace.new(path, initheader)) end - # Sends a GET request to the +path+. - # Returns the response as a Net::HTTPResponse object. + # Sends a GET request to the server; + # forms the response into a Net::HTTPResponse object. + # + # The request is based on the Net::HTTP::Get object + # created from string +path+ and initial headers hash +initheader+. + # + # With no block given, returns the response object: # - # When called with a block, passes an HTTPResponse object to the block. - # The body of the response will not have been read yet; - # the block can process it using HTTPResponse#read_body, - # if desired. + # http = Net::HTTP.new(hostname) + # http.request_get('/todos') # => #<Net::HTTPOK 200 OK readbody=true> # - # Returns the response. + # With a block given, calls the block with the response object + # and returns the response object: # - # This method never raises Net::* exceptions. + # http.request_get('/todos') do |res| + # p res + # end # => #<Net::HTTPOK 200 OK readbody=true> # - # response = http.request_get('/index.html') - # # The entity body is already read in this case. - # p response['content-type'] - # puts response.body + # Output: # - # # Using a block - # http.request_get('/index.html') {|response| - # p response['content-type'] - # response.read_body do |str| # read body now - # print str - # end - # } + # #<Net::HTTPOK 200 OK readbody=false> # def request_get(path, initheader = nil, &block) # :yield: +response+ request(Get.new(path, initheader), &block) end - # Sends a HEAD request to the +path+ and returns the response - # as a Net::HTTPResponse object. + # Sends a HEAD request to the server; + # returns an instance of a subclass of Net::HTTPResponse. # - # Returns the response. + # The request is based on the Net::HTTP::Head object + # created from string +path+ and initial headers hash +initheader+. # - # This method never raises Net::* exceptions. - # - # response = http.request_head('/index.html') - # p response['content-type'] + # http = Net::HTTP.new(hostname) + # http.head('/todos/1') # => #<Net::HTTPOK 200 OK readbody=true> # def request_head(path, initheader = nil, &block) request(Head.new(path, initheader), &block) end - # Sends a POST request to the +path+. + # Sends a POST request to the server; + # forms the response into a Net::HTTPResponse object. # - # Returns the response as a Net::HTTPResponse object. + # The request is based on the Net::HTTP::Post object + # created from string +path+, string +data+, and initial headers hash +initheader+. # - # When called with a block, the block is passed an HTTPResponse - # object. The body of that response will not have been read yet; - # the block can process it using HTTPResponse#read_body, if desired. + # With no block given, returns the response object: # - # Returns the response. + # http = Net::HTTP.new(hostname) + # http.post('/todos', 'xyzzy') + # # => #<Net::HTTPCreated 201 Created readbody=true> + # + # With a block given, calls the block with the response body + # and returns the response object: # - # This method never raises Net::* exceptions. + # http.post('/todos', 'xyzzy') do |res| + # p res + # end # => #<Net::HTTPCreated 201 Created readbody=true> # - # # example - # response = http.request_post('/cgi-bin/nice.rb', 'datadatadata...') - # p response.status - # puts response.body # body is already read in this case + # Output: # - # # using block - # http.request_post('/cgi-bin/nice.rb', 'datadatadata...') {|response| - # p response.status - # p response['content-type'] - # response.read_body do |str| # read body now - # print str - # end - # } + # "{\n \"xyzzy\": \"\",\n \"id\": 201\n}" # def request_post(path, data, initheader = nil, &block) # :yield: +response+ request Post.new(path, initheader), data, &block end + # Sends a PUT request to the server; + # returns an instance of a subclass of Net::HTTPResponse. + # + # The request is based on the Net::HTTP::Put object + # created from string +path+, string +data+, and initial headers hash +initheader+. + # + # http = Net::HTTP.new(hostname) + # http.put('/todos/1', 'xyzzy') + # # => #<Net::HTTPOK 200 OK readbody=true> + # def request_put(path, data, initheader = nil, &block) #:nodoc: request Put.new(path, initheader), data, &block end @@ -1544,16 +2236,25 @@ module Net #:nodoc: alias post2 request_post #:nodoc: obsolete alias put2 request_put #:nodoc: obsolete - - # Sends an HTTP request to the HTTP server. - # Also sends a DATA string if +data+ is given. + # Sends an \HTTP request to the server; + # returns an instance of a subclass of Net::HTTPResponse. # - # Returns a Net::HTTPResponse object. + # The request is based on the Net::HTTPRequest object + # created from string +path+, string +data+, and initial headers hash +header+. + # That object is an instance of the + # {subclass of Net::HTTPRequest}[rdoc-ref:Net::HTTPRequest@Request+Subclasses], + # that corresponds to the given uppercase string +name+, + # which must be + # an {HTTP request method}[https://en.wikipedia.org/wiki/HTTP#Request_methods] + # or a {WebDAV request method}[https://en.wikipedia.org/wiki/WebDAV#Implementation]. # - # This method never raises Net::* exceptions. + # Examples: # - # response = http.send_request('GET', '/index.html') - # puts response.body + # http = Net::HTTP.new(hostname) + # http.send_request('GET', '/todos/1') + # # => #<Net::HTTPOK 200 OK readbody=true> + # http.send_request('POST', '/todos', 'xyzzy') + # # => #<Net::HTTPCreated 201 Created readbody=true> # def send_request(name, path, data = nil, header = nil) has_response_body = name != 'HEAD' @@ -1561,20 +2262,35 @@ module Net #:nodoc: request r, data end - # Sends an HTTPRequest object +req+ to the HTTP server. + # Sends the given request +req+ to the server; + # forms the response into a Net::HTTPResponse object. # - # If +req+ is a Net::HTTP::Post or Net::HTTP::Put request containing - # data, the data is also sent. Providing data for a Net::HTTP::Head or - # Net::HTTP::Get request results in an ArgumentError. + # The given +req+ must be an instance of a + # {subclass of Net::HTTPRequest}[rdoc-ref:Net::HTTPRequest@Request+Subclasses]. + # Argument +body+ should be given only if needed for the request. # - # Returns an HTTPResponse object. + # With no block given, returns the response object: # - # When called with a block, passes an HTTPResponse object to the block. - # The body of the response will not have been read yet; - # the block can process it using HTTPResponse#read_body, - # if desired. + # http = Net::HTTP.new(hostname) + # + # req = Net::HTTP::Get.new('/todos/1') + # http.request(req) + # # => #<Net::HTTPOK 200 OK readbody=true> + # + # req = Net::HTTP::Post.new('/todos') + # http.request(req, 'xyzzy') + # # => #<Net::HTTPCreated 201 Created readbody=true> + # + # With a block given, calls the block with the response and returns the response: + # + # req = Net::HTTP::Get.new('/todos/1') + # http.request(req) do |res| + # p res + # end # => #<Net::HTTPOK 200 OK readbody=true> + # + # Output: # - # This method never raises Net::* exceptions. + # #<Net::HTTPOK 200 OK readbody=false> # def request(req, body = nil, &block) # :yield: +response+ unless started? diff --git a/lib/net/http/backward.rb b/lib/net/http/backward.rb index 691e41e4f1..b44577edbd 100644 --- a/lib/net/http/backward.rb +++ b/lib/net/http/backward.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # for backward compatibility # :enddoc: diff --git a/lib/net/http/exceptions.rb b/lib/net/http/exceptions.rb index 9c425cae16..ceec8f7b0a 100644 --- a/lib/net/http/exceptions.rb +++ b/lib/net/http/exceptions.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true module Net # Net::HTTP exception class. # You cannot use Net::HTTPExceptions directly; instead, you must use diff --git a/lib/net/http/generic_request.rb b/lib/net/http/generic_request.rb index d56835c76f..44e329a0c8 100644 --- a/lib/net/http/generic_request.rb +++ b/lib/net/http/generic_request.rb @@ -1,14 +1,18 @@ -# frozen_string_literal: false -# HTTPGenericRequest is the parent of the Net::HTTPRequest class. -# Do not use this directly; use a subclass of Net::HTTPRequest. +# frozen_string_literal: true # -# Mixes in the Net::HTTPHeader module to provide easier access to HTTP headers. +# \HTTPGenericRequest is the parent of the Net::HTTPRequest class. +# +# Do not use this directly; instead, use a subclass of Net::HTTPRequest. +# +# == About the Examples +# +# :include: doc/net-http/examples.rdoc # class Net::HTTPGenericRequest include Net::HTTPHeader - def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) + def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) # :nodoc: @method = m @request_has_body = reqbody @response_has_body = resbody @@ -19,7 +23,7 @@ class Net::HTTPGenericRequest raise ArgumentError, "no host component for URI" unless (hostname && hostname.length > 0) @uri = uri_or_path.dup host = @uri.hostname.dup - host << ":".freeze << @uri.port.to_s if @uri.port != @uri.default_port + host << ":" << @uri.port.to_s if @uri.port != @uri.default_port @path = uri_or_path.request_uri raise ArgumentError, "no HTTP request path given" unless @path else @@ -53,15 +57,47 @@ class Net::HTTPGenericRequest @body_data = nil end + # Returns the string method name for the request: + # + # Net::HTTP::Get.new(uri).method # => "GET" + # Net::HTTP::Post.new(uri).method # => "POST" + # attr_reader :method + + # Returns the string path for the request: + # + # Net::HTTP::Get.new(uri).path # => "/" + # Net::HTTP::Post.new('example.com').path # => "example.com" + # attr_reader :path + + # Returns the URI object for the request, or +nil+ if none: + # + # Net::HTTP::Get.new(uri).uri + # # => #<URI::HTTPS https://jsonplaceholder.typicode.com/> + # Net::HTTP::Get.new('example.com').uri # => nil + # attr_reader :uri - # Automatically set to false if the user sets the Accept-Encoding header. - # This indicates they wish to handle Content-encoding in responses - # themselves. + # Returns +false+ if the request's header <tt>'Accept-Encoding'</tt> + # has been set manually or deleted + # (indicating that the user intends to handle encoding in the response), + # +true+ otherwise: + # + # req = Net::HTTP::Get.new(uri) # => #<Net::HTTP::Get GET> + # req['Accept-Encoding'] # => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" + # req.decode_content # => true + # req['Accept-Encoding'] = 'foo' + # req.decode_content # => false + # req.delete('Accept-Encoding') + # req.decode_content # => false + # attr_reader :decode_content + # Returns a string representation of the request: + # + # Net::HTTP::Post.new(uri).inspect # => "#<Net::HTTP::Post POST>" + # def inspect "\#<#{self.class} #{@method}>" end @@ -76,21 +112,45 @@ class Net::HTTPGenericRequest super key, val end + # Returns whether the request may have a body: + # + # Net::HTTP::Post.new(uri).request_body_permitted? # => true + # Net::HTTP::Get.new(uri).request_body_permitted? # => false + # def request_body_permitted? @request_has_body end + # Returns whether the response may have a body: + # + # Net::HTTP::Post.new(uri).response_body_permitted? # => true + # Net::HTTP::Head.new(uri).response_body_permitted? # => false + # def response_body_permitted? @response_has_body end - def body_exist? + def body_exist? # :nodoc: warn "Net::HTTPRequest#body_exist? is obsolete; use response_body_permitted?", uplevel: 1 if $VERBOSE response_body_permitted? end + # Returns the string body for the request, or +nil+ if there is none: + # + # req = Net::HTTP::Post.new(uri) + # req.body # => nil + # req.body = '{"title": "foo","body": "bar","userId": 1}' + # req.body # => "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}" + # attr_reader :body + # Sets the body for the request: + # + # req = Net::HTTP::Post.new(uri) + # req.body # => nil + # req.body = '{"title": "foo","body": "bar","userId": 1}' + # req.body # => "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}" + # def body=(str) @body = str @body_stream = nil @@ -98,8 +158,24 @@ class Net::HTTPGenericRequest str end + # Returns the body stream object for the request, or +nil+ if there is none: + # + # req = Net::HTTP::Post.new(uri) # => #<Net::HTTP::Post POST> + # req.body_stream # => nil + # require 'stringio' + # req.body_stream = StringIO.new('xyzzy') # => #<StringIO:0x0000027d1e5affa8> + # req.body_stream # => #<StringIO:0x0000027d1e5affa8> + # attr_reader :body_stream + # Sets the body stream for the request: + # + # req = Net::HTTP::Post.new(uri) # => #<Net::HTTP::Post POST> + # req.body_stream # => nil + # require 'stringio' + # req.body_stream = StringIO.new('xyzzy') # => #<StringIO:0x0000027d1e5affa8> + # req.body_stream # => #<StringIO:0x0000027d1e5affa8> + # def body_stream=(input) @body = nil @body_stream = input @@ -136,15 +212,15 @@ class Net::HTTPGenericRequest return unless @uri if ssl - scheme = 'https'.freeze + scheme = 'https' klass = URI::HTTPS else - scheme = 'http'.freeze + scheme = 'http' klass = URI::HTTP end if host = self['host'] - host.sub!(/:.*/m, ''.freeze) + host.sub!(/:.*/m, '') elsif host = @uri.host else host = addr @@ -240,7 +316,7 @@ class Net::HTTPGenericRequest boundary ||= SecureRandom.urlsafe_base64(40) chunked_p = chunked? - buf = '' + buf = +'' params.each do |key, value, h={}| key = quote_string(key, charset) filename = @@ -325,7 +401,7 @@ class Net::HTTPGenericRequest if /[\r\n]/ =~ reqline raise ArgumentError, "A Request-Line must not contain CR or LF" end - buf = "" + buf = +'' buf << reqline << "\r\n" each_capitalized do |k,v| buf << "#{k}: #{v}\r\n" diff --git a/lib/net/http/header.rb b/lib/net/http/header.rb index 856fb0a16b..6660c8075a 100644 --- a/lib/net/http/header.rb +++ b/lib/net/http/header.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # # The \HTTPHeader module provides access to \HTTP headers. # @@ -179,6 +179,8 @@ # - #each_value: Passes each string field value to the block. # module Net::HTTPHeader + MAX_KEY_LENGTH = 1024 + MAX_FIELD_LENGTH = 65536 def initialize_http_header(initheader) #:nodoc: @header = {} @@ -189,6 +191,12 @@ module Net::HTTPHeader warn "net/http: nil HTTP header: #{key}", uplevel: 3 if $VERBOSE else value = value.strip # raise error for invalid byte sequences + if key.to_s.bytesize > MAX_KEY_LENGTH + raise ArgumentError, "too long (#{key.bytesize} bytes) header: #{key[0, 30].inspect}..." + end + if value.to_s.bytesize > MAX_FIELD_LENGTH + raise ArgumentError, "header #{key} has too long field value: #{value.bytesize}" + end if value.count("\r\n") > 0 raise ArgumentError, "header #{key} has field value #{value.inspect}, this cannot include CR/LF" end @@ -207,9 +215,7 @@ module Net::HTTPHeader # or +nil+ if there is no such key; # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: # - # res = Net::HTTP.start(hostname) do |http| - # http.get('/todos/1') - # end + # res = Net::HTTP.get_response(hostname, '/todos/1') # res['Connection'] # => "keep-alive" # res['Nosuch'] # => nil # @@ -293,9 +299,7 @@ module Net::HTTPHeader # or +nil+ if there is no such field; # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: # - # res = Net::HTTP.start(hostname) do |http| - # http.get('/todos/1') - # end + # res = Net::HTTP.get_response(hostname, '/todos/1') # res.get_fields('Connection') # => ["keep-alive"] # res.get_fields('Nosuch') # => nil # @@ -305,7 +309,7 @@ module Net::HTTPHeader @header[stringified_downcased_key].dup end - # :call-seq + # call-seq: # fetch(key, default_val = nil) {|key| ... } -> object # fetch(key, default_val = nil) -> value or default_val # @@ -314,9 +318,7 @@ module Net::HTTPHeader # ignores the +default_val+; # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: # - # res = Net::HTTP.start(hostname) do |http| - # http.get('/todos/1') - # end + # res = Net::HTTP.get_response(hostname, '/todos/1') # # # Field exists; block not called. # res.fetch('Connection') do |value| @@ -343,9 +345,7 @@ module Net::HTTPHeader # Calls the block with each key/value pair: # - # res = Net::HTTP.start(hostname) do |http| - # http.get('/todos/1') - # end + # res = Net::HTTP.get_response(hostname, '/todos/1') # res.each_header do |key, value| # p [key, value] if key.start_with?('c') # end @@ -372,20 +372,18 @@ module Net::HTTPHeader # Calls the block with each field key: # - # res = Net::HTTP.start(hostname) do |http| - # http.get('/todos/1') - # end + # res = Net::HTTP.get_response(hostname, '/todos/1') # res.each_key do |key| # p key if key.start_with?('c') # end # # Output: # - # "content-type" - # "connection" - # "cache-control" - # "cf-cache-status" - # "cf-ray" + # "content-type" + # "connection" + # "cache-control" + # "cf-cache-status" + # "cf-ray" # # Returns an enumerator if no block is given. # @@ -399,9 +397,7 @@ module Net::HTTPHeader # Calls the block with each capitalized field name: # - # res = Net::HTTP.start(hostname) do |http| - # http.get('/todos/1') - # end + # res = Net::HTTP.get_response(hostname, '/todos/1') # res.each_capitalized_name do |key| # p key if key.start_with?('C') # end @@ -427,9 +423,7 @@ module Net::HTTPHeader # Calls the block with each string field value: # - # res = Net::HTTP.start(hostname) do |http| - # http.get('/todos/1') - # end + # res = Net::HTTP.get_response(hostname, '/todos/1') # res.each_value do |value| # p value if value.start_with?('c') # end @@ -554,7 +548,7 @@ module Net::HTTPHeader result end - # :call-seq: + # call-seq: # set_range(length) -> length # set_range(offset, length) -> range # set_range(begin..length) -> range @@ -610,8 +604,15 @@ module Net::HTTPHeader alias range= set_range - # Returns an Integer object which represents the HTTP Content-Length: - # header field, or +nil+ if that field was not provided. + # Returns the value of field <tt>'Content-Length'</tt> as an integer, + # or +nil+ if there is no such field; + # see {Content-Length request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-length-request-header]: + # + # res = Net::HTTP.get_response(hostname, '/nosuch/1') + # res.content_length # => 2 + # res = Net::HTTP.get_response(hostname, '/todos/1') + # res.content_length # => nil + # def content_length return nil unless key?('Content-Length') len = self['Content-Length'].slice(/\d+/) or @@ -619,6 +620,20 @@ module Net::HTTPHeader len.to_i end + # Sets the value of field <tt>'Content-Length'</tt> to the given numeric; + # see {Content-Length response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-length-response-header]: + # + # _uri = uri.dup + # hostname = _uri.hostname # => "jsonplaceholder.typicode.com" + # _uri.path = '/posts' # => "/posts" + # req = Net::HTTP::Post.new(_uri) # => #<Net::HTTP::Post POST> + # req.body = '{"title": "foo","body": "bar","userId": 1}' + # req.content_length = req.body.size # => 42 + # req.content_type = 'application/json' + # res = Net::HTTP.start(hostname) do |http| + # http.request(req) + # end # => #<Net::HTTPCreated 201 Created readbody=true> + # def content_length=(len) unless len @header.delete 'content-length' @@ -627,20 +642,31 @@ module Net::HTTPHeader @header['content-length'] = [len.to_i.to_s] end - # Returns "true" if the "transfer-encoding" header is present and - # set to "chunked". This is an HTTP/1.1 feature, allowing - # the content to be sent in "chunks" without at the outset - # stating the entire content length. + # Returns +true+ if field <tt>'Transfer-Encoding'</tt> + # exists and has value <tt>'chunked'</tt>, + # +false+ otherwise; + # see {Transfer-Encoding response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#transfer-encoding-response-header]: + # + # res = Net::HTTP.get_response(hostname, '/todos/1') + # res['Transfer-Encoding'] # => "chunked" + # res.chunked? # => true + # def chunked? return false unless @header['transfer-encoding'] field = self['Transfer-Encoding'] (/(?:\A|[^\-\w])chunked(?![\-\w])/i =~ field) ? true : false end - # Returns a Range object which represents the value of the Content-Range: - # header field. - # For a partial entity body, this indicates where this fragment - # fits inside the full entity body, as range of byte offsets. + # Returns a Range object representing the value of field + # <tt>'Content-Range'</tt>, or +nil+ if no such field exists; + # see {Content-Range response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-range-response-header]: + # + # res = Net::HTTP.get_response(hostname, '/todos/1') + # res['Content-Range'] # => nil + # res['Content-Range'] = 'bytes 0-499/1000' + # res['Content-Range'] # => "bytes 0-499/1000" + # res.content_range # => 0..499 + # def content_range return nil unless @header['content-range'] m = %r<\A\s*(\w+)\s+(\d+)-(\d+)/(\d+|\*)>.match(self['Content-Range']) or @@ -649,32 +675,66 @@ module Net::HTTPHeader m[2].to_i .. m[3].to_i end - # The length of the range represented in Content-Range: header. + # Returns the integer representing length of the value of field + # <tt>'Content-Range'</tt>, or +nil+ if no such field exists; + # see {Content-Range response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-range-response-header]: + # + # res = Net::HTTP.get_response(hostname, '/todos/1') + # res['Content-Range'] # => nil + # res['Content-Range'] = 'bytes 0-499/1000' + # res.range_length # => 500 + # def range_length r = content_range() or return nil r.end - r.begin + 1 end - # Returns a content type string such as "text/html". - # This method returns nil if Content-Type: header field does not exist. + # Returns the {media type}[https://en.wikipedia.org/wiki/Media_type] + # from the value of field <tt>'Content-Type'</tt>, + # or +nil+ if no such field exists; + # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]: + # + # res = Net::HTTP.get_response(hostname, '/todos/1') + # res['content-type'] # => "application/json; charset=utf-8" + # res.content_type # => "application/json" + # def content_type - return nil unless main_type() - if sub_type() - then "#{main_type()}/#{sub_type()}" - else main_type() + main = main_type() + return nil unless main + + sub = sub_type() + if sub + "#{main}/#{sub}" + else + main end end - # Returns a content type string such as "text". - # This method returns nil if Content-Type: header field does not exist. + # Returns the leading ('type') part of the + # {media type}[https://en.wikipedia.org/wiki/Media_type] + # from the value of field <tt>'Content-Type'</tt>, + # or +nil+ if no such field exists; + # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]: + # + # res = Net::HTTP.get_response(hostname, '/todos/1') + # res['content-type'] # => "application/json; charset=utf-8" + # res.main_type # => "application" + # def main_type return nil unless @header['content-type'] self['Content-Type'].split(';').first.to_s.split('/')[0].to_s.strip end - # Returns a content type string such as "html". - # This method returns nil if Content-Type: header field does not exist - # or sub-type is not given (e.g. "Content-Type: text"). + # Returns the trailing ('subtype') part of the + # {media type}[https://en.wikipedia.org/wiki/Media_type] + # from the value of field <tt>'Content-Type'</tt>, + # or +nil+ if no such field exists; + # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]: + # + # res = Net::HTTP.get_response(hostname, '/todos/1') + # res['content-type'] # => "application/json; charset=utf-8" + # res.sub_type # => "json" + # def sub_type return nil unless @header['content-type'] _, sub = *self['Content-Type'].split(';').first.to_s.split('/') @@ -682,9 +742,14 @@ module Net::HTTPHeader sub.strip end - # Any parameters specified for the content type, returned as a Hash. - # For example, a header of Content-Type: text/html; charset=EUC-JP - # would result in type_params returning {'charset' => 'EUC-JP'} + # Returns the trailing ('parameters') part of the value of field <tt>'Content-Type'</tt>, + # or +nil+ if no such field exists; + # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]: + # + # res = Net::HTTP.get_response(hostname, '/todos/1') + # res['content-type'] # => "application/json; charset=utf-8" + # res.type_params # => {"charset"=>"utf-8"} + # def type_params result = {} list = self['Content-Type'].to_s.split(';') @@ -696,10 +761,12 @@ module Net::HTTPHeader result end - # Sets the content type in an HTTP header. - # The +type+ should be a full HTTP content type, e.g. "text/html". - # The +params+ are an optional Hash of parameters to add after the - # content type, e.g. {'charset' => 'iso-8859-1'}. + # Sets the value of field <tt>'Content-Type'</tt>; + # returns the new value; + # see {Content-Type request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-request-header]: + # + # req = Net::HTTP::Get.new(uri) + # req.set_content_type('application/json') # => ["application/json"] # # Net::HTTPHeader#content_type= is an alias for Net::HTTPHeader#set_content_type. def set_content_type(type, params = {}) @@ -708,18 +775,38 @@ module Net::HTTPHeader alias content_type= set_content_type - # Set header fields and a body from HTML form data. - # +params+ should be an Array of Arrays or - # a Hash containing HTML form data. - # Optional argument +sep+ means data record separator. + # Sets the request body to a URL-encoded string derived from argument +params+, + # and sets request header field <tt>'Content-Type'</tt> + # to <tt>'application/x-www-form-urlencoded'</tt>. + # + # The resulting request is suitable for HTTP request +POST+ or +PUT+. + # + # Argument +params+ must be suitable for use as argument +enum+ to + # {URI.encode_www_form}[rdoc-ref:URI.encode_www_form]. + # + # With only argument +params+ given, + # sets the body to a URL-encoded string with the default separator <tt>'&'</tt>: # - # Values are URL encoded as necessary and the content-type is set to - # application/x-www-form-urlencoded + # req = Net::HTTP::Post.new('example.com') # - # Example: - # http.form_data = {"q" => "ruby", "lang" => "en"} - # http.form_data = {"q" => ["ruby", "perl"], "lang" => "en"} - # http.set_form_data({"q" => "ruby", "lang" => "en"}, ';') + # req.set_form_data(q: 'ruby', lang: 'en') + # req.body # => "q=ruby&lang=en" + # req['Content-Type'] # => "application/x-www-form-urlencoded" + # + # req.set_form_data([['q', 'ruby'], ['lang', 'en']]) + # req.body # => "q=ruby&lang=en" + # + # req.set_form_data(q: ['ruby', 'perl'], lang: 'en') + # req.body # => "q=ruby&q=perl&lang=en" + # + # req.set_form_data([['q', 'ruby'], ['q', 'perl'], ['lang', 'en']]) + # req.body # => "q=ruby&q=perl&lang=en" + # + # With string argument +sep+ also given, + # uses that string as the separator: + # + # req.set_form_data({q: 'ruby', lang: 'en'}, '|') + # req.body # => "q=ruby|lang=en" # # Net::HTTPHeader#form_data= is an alias for Net::HTTPHeader#set_form_data. def set_form_data(params, sep = '&') @@ -731,53 +818,108 @@ module Net::HTTPHeader alias form_data= set_form_data - # Set an HTML form data set. - # +params+ :: The form data to set, which should be an enumerable. - # See below for more details. - # +enctype+ :: The content type to use to encode the form submission, - # which should be application/x-www-form-urlencoded or - # multipart/form-data. - # +formopt+ :: An options hash, supporting the following options: - # :boundary :: The boundary of the multipart message. If - # not given, a random boundary will be used. - # :charset :: The charset of the form submission. All - # field names and values of non-file fields - # should be encoded with this charset. - # - # Each item of params should respond to +each+ and yield 2-3 arguments, - # or an array of 2-3 elements. The arguments yielded should be: - # * The name of the field. - # * The value of the field, it should be a String or a File or IO-like. - # * An options hash, supporting the following options, only - # used for file uploads: - # :filename :: The name of the file to use. - # :content_type :: The content type of the uploaded file. - # - # Each item is a file field or a normal field. - # If +value+ is a File object or the +opt+ hash has a :filename key, - # the item is treated as a file field. - # - # If Transfer-Encoding is set as chunked, this sends the request using - # chunked encoding. Because chunked encoding is HTTP/1.1 feature, - # you should confirm that the server supports HTTP/1.1 before using - # chunked encoding. - # - # Example: - # req.set_form([["q", "ruby"], ["lang", "en"]]) - # - # req.set_form({"f"=>File.open('/path/to/filename')}, - # "multipart/form-data", - # charset: "UTF-8", - # ) - # - # req.set_form([["f", - # File.open('/path/to/filename.bar'), - # {filename: "other-filename.foo"} - # ]], - # "multipart/form-data", - # ) - # - # See also RFC 2388, RFC 2616, HTML 4.01, and HTML5 + # Stores form data to be used in a +POST+ or +PUT+ request. + # + # The form data given in +params+ consists of zero or more fields; + # each field is: + # + # - A scalar value. + # - A name/value pair. + # - An IO stream opened for reading. + # + # Argument +params+ should be an + # {Enumerable}[rdoc-ref:Enumerable@Enumerable+in+Ruby+Classes] + # (method <tt>params.map</tt> will be called), + # and is often an array or hash. + # + # First, we set up a request: + # + # _uri = uri.dup + # _uri.path ='/posts' + # req = Net::HTTP::Post.new(_uri) + # + # <b>Argument +params+ As an Array</b> + # + # When +params+ is an array, + # each of its elements is a subarray that defines a field; + # the subarray may contain: + # + # - One string: + # + # req.set_form([['foo'], ['bar'], ['baz']]) + # + # - Two strings: + # + # req.set_form([%w[foo 0], %w[bar 1], %w[baz 2]]) + # + # - When argument +enctype+ (see below) is given as + # <tt>'multipart/form-data'</tt>: + # + # - A string name and an IO stream opened for reading: + # + # require 'stringio' + # req.set_form([['file', StringIO.new('Ruby is cool.')]]) + # + # - A string name, an IO stream opened for reading, + # and an options hash, which may contain these entries: + # + # - +:filename+: The name of the file to use. + # - +:content_type+: The content type of the uploaded file. + # + # Example: + # + # req.set_form([['file', file, {filename: "other-filename.foo"}]] + # + # The various forms may be mixed: + # + # req.set_form(['foo', %w[bar 1], ['file', file]]) + # + # <b>Argument +params+ As a Hash</b> + # + # When +params+ is a hash, + # each of its entries is a name/value pair that defines a field: + # + # - The name is a string. + # - The value may be: + # + # - +nil+. + # - Another string. + # - An IO stream opened for reading + # (only when argument +enctype+ -- see below -- is given as + # <tt>'multipart/form-data'</tt>). + # + # Examples: + # + # # Nil-valued fields. + # req.set_form({'foo' => nil, 'bar' => nil, 'baz' => nil}) + # + # # String-valued fields. + # req.set_form({'foo' => 0, 'bar' => 1, 'baz' => 2}) + # + # # IO-valued field. + # require 'stringio' + # req.set_form({'file' => StringIO.new('Ruby is cool.')}) + # + # # Mixture of fields. + # req.set_form({'foo' => nil, 'bar' => 1, 'file' => file}) + # + # Optional argument +enctype+ specifies the value to be given + # to field <tt>'Content-Type'</tt>, and must be one of: + # + # - <tt>'application/x-www-form-urlencoded'</tt> (the default). + # - <tt>'multipart/form-data'</tt>; + # see {RFC 7578}[https://www.rfc-editor.org/rfc/rfc7578]. + # + # Optional argument +formopt+ is a hash of options + # (applicable only when argument +enctype+ + # is <tt>'multipart/form-data'</tt>) + # that may include the following entries: + # + # - +:boundary+: The value is the boundary string for the multipart message. + # If not given, the boundary is a random string. + # See {Boundary}[https://www.rfc-editor.org/rfc/rfc7578#section-4.1]. + # - +:charset+: Value is the character set for the form submission. + # Field names and values of non-file fields should be encoded with this charset. # def set_form(params, enctype='application/x-www-form-urlencoded', formopt={}) @body_data = params @@ -793,12 +935,24 @@ module Net::HTTPHeader end end - # Set the Authorization: header for "Basic" authorization. + # Sets header <tt>'Authorization'</tt> using the given + # +account+ and +password+ strings: + # + # req.basic_auth('my_account', 'my_password') + # req['Authorization'] + # # => "Basic bXlfYWNjb3VudDpteV9wYXNzd29yZA==" + # def basic_auth(account, password) @header['authorization'] = [basic_encode(account, password)] end - # Set Proxy-Authorization: header for "Basic" authorization. + # Sets header <tt>'Proxy-Authorization'</tt> using the given + # +account+ and +password+ strings: + # + # req.proxy_basic_auth('my_account', 'my_password') + # req['Proxy-Authorization'] + # # => "Basic bXlfYWNjb3VudDpteV9wYXNzd29yZA==" + # def proxy_basic_auth(account, password) @header['proxy-authorization'] = [basic_encode(account, password)] end @@ -808,6 +962,7 @@ module Net::HTTPHeader end private :basic_encode +# Returns whether the HTTP session is to be closed. def connection_close? token = /(?:\A|,)\s*close\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} @@ -815,6 +970,7 @@ module Net::HTTPHeader false end +# Returns whether the HTTP session is to be kept alive. def connection_keep_alive? token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} diff --git a/lib/net/http/net-http.gemspec b/lib/net/http/net-http.gemspec index a7f122fc0e..0021136793 100644 --- a/lib/net/http/net-http.gemspec +++ b/lib/net/http/net-http.gemspec @@ -2,9 +2,14 @@ name = File.basename(__FILE__, ".gemspec") version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir| - break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| - /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 - end rescue nil + file = File.join(__dir__, dir, "#{name.tr('-', '/')}.rb") + begin + break File.foreach(file, mode: "rb") do |line| + /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 + end + rescue SystemCallError + next + end end Gem::Specification.new do |spec| @@ -25,7 +30,7 @@ Gem::Specification.new do |spec| # 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 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{\A(?:(?:test|spec|features)/|\.git)}) } end spec.bindir = "exe" spec.require_paths = ["lib"] diff --git a/lib/net/http/proxy_delta.rb b/lib/net/http/proxy_delta.rb index a2f770ebdb..e7d30def64 100644 --- a/lib/net/http/proxy_delta.rb +++ b/lib/net/http/proxy_delta.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true module Net::HTTP::ProxyDelta #:nodoc: internal use only private diff --git a/lib/net/http/request.rb b/lib/net/http/request.rb index f3d32edb62..4a138572e9 100644 --- a/lib/net/http/request.rb +++ b/lib/net/http/request.rb @@ -1,10 +1,55 @@ -# frozen_string_literal: false +# frozen_string_literal: true -# This class is the base class for \Net::HTTP request classes; -# it wraps together the request path and the request headers. -# +# This class is the base class for \Net::HTTP request classes. # The class should not be used directly; -# instead you should use its subclasses. +# instead you should use its subclasses, listed below. +# +# == Creating a Request +# +# An request object may be created with either a URI or a string hostname: +# +# require 'net/http' +# uri = URI('https://jsonplaceholder.typicode.com/') +# req = Net::HTTP::Get.new(uri) # => #<Net::HTTP::Get GET> +# req = Net::HTTP::Get.new(uri.hostname) # => #<Net::HTTP::Get GET> +# +# And with any of the subclasses: +# +# req = Net::HTTP::Head.new(uri) # => #<Net::HTTP::Head HEAD> +# req = Net::HTTP::Post.new(uri) # => #<Net::HTTP::Post POST> +# req = Net::HTTP::Put.new(uri) # => #<Net::HTTP::Put PUT> +# # ... +# +# The new instance is suitable for use as the argument to Net::HTTP#request. +# +# == Request Headers +# +# A new request object has these header fields by default: +# +# req.to_hash +# # => +# {"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], +# "accept"=>["*/*"], +# "user-agent"=>["Ruby"], +# "host"=>["jsonplaceholder.typicode.com"]} +# +# See: +# +# - {Request header Accept-Encoding}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Accept-Encoding] +# and {Compression and Decompression}[rdoc-ref:Net::HTTP@Compression+and+Decompression]. +# - {Request header Accept}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#accept-request-header]. +# - {Request header User-Agent}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#user-agent-request-header]. +# - {Request header Host}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#host-request-header]. +# +# You can add headers or override default headers: +# +# # res = Net::HTTP::Get.new(uri, {'foo' => '0', 'bar' => '1'}) +# +# This class (and therefore its subclasses) also includes (indirectly) +# module Net::HTTPHeader, which gives access to its +# {methods for setting headers}[rdoc-ref:Net::HTTPHeader@Setters]. +# +# == Request Subclasses # # Subclasses for HTTP requests: # diff --git a/lib/net/http/requests.rb b/lib/net/http/requests.rb index 294b8e8841..5724164205 100644 --- a/lib/net/http/requests.rb +++ b/lib/net/http/requests.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true # HTTP/1.1 methods --- RFC2616 @@ -13,6 +13,8 @@ # http.request(req) # end # +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# # Properties: # # - Request body: optional. @@ -43,6 +45,8 @@ end # http.request(req) # end # +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# # Properties: # # - Request body: optional. @@ -75,6 +79,8 @@ end # http.request(req) # end # +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# # Properties: # # - Request body: yes. @@ -108,6 +114,8 @@ end # http.request(req) # end # +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# # Properties: # # - Request body: yes. @@ -134,6 +142,8 @@ end # http.request(req) # end # +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# # Properties: # # - Request body: optional. @@ -163,6 +173,8 @@ end # http.request(req) # end # +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# # Properties: # # - Request body: optional. @@ -192,6 +204,8 @@ end # http.request(req) # end # +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# # Properties: # # - Request body: no. @@ -224,6 +238,8 @@ end # http.request(req) # end # +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# # Properties: # # - Request body: yes. @@ -257,6 +273,8 @@ end # http.request(req) # end # +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# # Related: # # - Net::HTTP#propfind: sends +PROPFIND+ request, returns response object. @@ -278,6 +296,8 @@ end # http.request(req) # end # +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# # Related: # # - Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object. @@ -299,6 +319,8 @@ end # http.request(req) # end # +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# # Related: # # - Net::HTTP#mkcol: sends +MKCOL+ request, returns response object. @@ -320,6 +342,8 @@ end # http.request(req) # end # +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# # Related: # # - Net::HTTP#copy: sends +COPY+ request, returns response object. @@ -341,6 +365,8 @@ end # http.request(req) # end # +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# # Related: # # - Net::HTTP#move: sends +MOVE+ request, returns response object. @@ -362,6 +388,8 @@ end # http.request(req) # end # +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# # Related: # # - Net::HTTP#lock: sends +LOCK+ request, returns response object. @@ -383,6 +411,8 @@ end # http.request(req) # end # +# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. +# # Related: # # - Net::HTTP#unlock: sends +UNLOCK+ request, returns response object. diff --git a/lib/net/http/response.rb b/lib/net/http/response.rb index 83853ffd19..40de963868 100644 --- a/lib/net/http/response.rb +++ b/lib/net/http/response.rb @@ -1,6 +1,6 @@ -# frozen_string_literal: false +# frozen_string_literal: true -# This class is the base class for \Net::HTTP request classes. +# This class is the base class for \Net::HTTP response classes. # # == About the Examples # @@ -224,13 +224,32 @@ class Net::HTTPResponse # Accept-Encoding header from the user. attr_accessor :decode_content - # The encoding to use for the response body. If Encoding, use that encoding. - # If other true value, attempt to detect the appropriate encoding, and use - # that. + # Returns the value set by body_encoding=, or +false+ if none; + # see #body_encoding=. attr_reader :body_encoding - # Set the encoding to use for the response body. If given a String, find - # the related Encoding. + # Sets the encoding that should be used when reading the body: + # + # - If the given value is an Encoding object, that encoding will be used. + # - Otherwise if the value is a string, the value of + # {Encoding#find(value)}[rdoc-ref:Encoding.find] + # will be used. + # - Otherwise an encoding will be deduced from the body itself. + # + # Examples: + # + # http = Net::HTTP.new(hostname) + # req = Net::HTTP::Get.new('/') + # + # http.request(req) do |res| + # p res.body.encoding # => #<Encoding:ASCII-8BIT> + # end + # + # http.request(req) do |res| + # res.body_encoding = "UTF-8" + # p res.body.encoding # => #<Encoding:UTF-8> + # end + # def body_encoding=(value) value = Encoding.find(value) if value.is_a?(String) @body_encoding = value @@ -254,7 +273,7 @@ class Net::HTTPResponse def error! #:nodoc: message = @code - message += ' ' + @message.dump if @message + message = "#{message} #{@message.dump}" if @message raise error_type().new(message, self) end @@ -347,6 +366,7 @@ class Net::HTTPResponse @body = nil end @read = true + return if @body.nil? case enc = @body_encoding when Encoding, false, nil @@ -362,26 +382,26 @@ class Net::HTTPResponse @body end - # Returns the full entity body. + # Returns the string response body; + # note that repeated calls for the unmodified body return a cached string: # - # Calling this method a second or subsequent time will return the - # string already read. + # path = '/todos/1' + # Net::HTTP.start(hostname) do |http| + # res = http.get(path) + # p res.body + # p http.head(path).body # No body. + # end # - # http.request_get('/index.html') {|res| - # puts res.body - # } + # Output: # - # http.request_get('/index.html') {|res| - # p res.body.object_id # 538149362 - # p res.body.object_id # 538149362 - # } + # "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false\n}" + # nil # def body read_body() end - # Because it may be necessary to modify the body, Eg, decompression - # this method facilitates that. + # Sets the body of the response to the given value. def body=(value) @body = value end @@ -620,7 +640,7 @@ class Net::HTTPResponse end def stream_check - raise IOError, 'attempt to read body out of block' if @socket.closed? + raise IOError, 'attempt to read body out of block' if @socket.nil? || @socket.closed? end def procdest(dest, block) @@ -629,7 +649,7 @@ class Net::HTTPResponse if block Net::ReadAdapter.new(block) else - dest || '' + dest || +'' end end diff --git a/lib/net/http/responses.rb b/lib/net/http/responses.rb index 02a2fdaa4c..6f6fb8d055 100644 --- a/lib/net/http/responses.rb +++ b/lib/net/http/responses.rb @@ -3,192 +3,909 @@ # https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml module Net - # :stopdoc: class HTTPUnknownResponse < HTTPResponse HAS_BODY = true EXCEPTION_TYPE = HTTPError # end - class HTTPInformation < HTTPResponse # 1xx + + # Parent class for informational (1xx) HTTP response classes. + # + # An informational response indicates that the request was received and understood. + # + # References: + # + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.1xx]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response]. + # + class HTTPInformation < HTTPResponse HAS_BODY = false EXCEPTION_TYPE = HTTPError # end - class HTTPSuccess < HTTPResponse # 2xx + + # Parent class for success (2xx) HTTP response classes. + # + # A success response indicates the action requested by the client + # was received, understood, and accepted. + # + # References: + # + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.2xx]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success]. + # + class HTTPSuccess < HTTPResponse HAS_BODY = true EXCEPTION_TYPE = HTTPError # end - class HTTPRedirection < HTTPResponse # 3xx + + # Parent class for redirection (3xx) HTTP response classes. + # + # A redirection response indicates the client must take additional action + # to complete the request. + # + # References: + # + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.3xx]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection]. + # + class HTTPRedirection < HTTPResponse HAS_BODY = true EXCEPTION_TYPE = HTTPRetriableError # end - class HTTPClientError < HTTPResponse # 4xx + + # Parent class for client error (4xx) HTTP response classes. + # + # A client error response indicates that the client may have caused an error. + # + # References: + # + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.4xx]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors]. + # + class HTTPClientError < HTTPResponse HAS_BODY = true EXCEPTION_TYPE = HTTPClientException # end - class HTTPServerError < HTTPResponse # 5xx + + # Parent class for server error (5xx) HTTP response classes. + # + # A server error response indicates that the server failed to fulfill a request. + # + # References: + # + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.5xx]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors]. + # + class HTTPServerError < HTTPResponse HAS_BODY = true EXCEPTION_TYPE = HTTPFatalError # end - class HTTPContinue < HTTPInformation # 100 + # Response class for +Continue+ responses (status code 100). + # + # A +Continue+ response indicates that the server has received the request headers. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/100]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-100-continue]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100]. + # + class HTTPContinue < HTTPInformation HAS_BODY = false end - class HTTPSwitchProtocol < HTTPInformation # 101 + + # Response class for <tt>Switching Protocol</tt> responses (status code 101). + # + # The <tt>Switching Protocol<tt> response indicates that the server has received + # a request to switch protocols, and has agreed to do so. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/101]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-101-switching-protocols]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101]. + # + class HTTPSwitchProtocol < HTTPInformation HAS_BODY = false end - class HTTPProcessing < HTTPInformation # 102 + + # Response class for +Processing+ responses (status code 102). + # + # The +Processing+ response indicates that the server has received + # and is processing the request, but no response is available yet. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {RFC 2518}[https://www.rfc-editor.org/rfc/rfc2518#section-10.1]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102]. + # + class HTTPProcessing < HTTPInformation HAS_BODY = false end - class HTTPEarlyHints < HTTPInformation # 103 - RFC 8297 + + # Response class for <tt>Early Hints</tt> responses (status code 103). + # + # The <tt>Early Hints</tt> indicates that the server has received + # and is processing the request, and contains certain headers; + # the final response is not available yet. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103]. + # - {RFC 8297}[https://www.rfc-editor.org/rfc/rfc8297.html#section-2]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103]. + # + class HTTPEarlyHints < HTTPInformation HAS_BODY = false end - class HTTPOK < HTTPSuccess # 200 + # Response class for +OK+ responses (status code 200). + # + # The +OK+ response indicates that the server has received + # a request and has responded successfully. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-200-ok]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200]. + # + class HTTPOK < HTTPSuccess HAS_BODY = true end - class HTTPCreated < HTTPSuccess # 201 + + # Response class for +Created+ responses (status code 201). + # + # The +Created+ response indicates that the server has received + # and has fulfilled a request to create a new resource. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-201-created]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201]. + # + class HTTPCreated < HTTPSuccess HAS_BODY = true end - class HTTPAccepted < HTTPSuccess # 202 + + # Response class for +Accepted+ responses (status code 202). + # + # The +Accepted+ response indicates that the server has received + # and is processing a request, but the processing has not yet been completed. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-202-accepted]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202]. + # + class HTTPAccepted < HTTPSuccess HAS_BODY = true end - class HTTPNonAuthoritativeInformation < HTTPSuccess # 203 + + # Response class for <tt>Non-Authoritative Information</tt> responses (status code 203). + # + # The <tt>Non-Authoritative Information</tt> response indicates that the server + # is a transforming proxy (such as a Web accelerator) + # that received a 200 OK response from its origin, + # and is returning a modified version of the origin's response. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/203]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-203-non-authoritative-infor]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203]. + # + class HTTPNonAuthoritativeInformation < HTTPSuccess HAS_BODY = true end - class HTTPNoContent < HTTPSuccess # 204 + + # Response class for <tt>No Content</tt> responses (status code 204). + # + # The <tt>No Content</tt> response indicates that the server + # successfully processed the request, and is not returning any content. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-204-no-content]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204]. + # + class HTTPNoContent < HTTPSuccess HAS_BODY = false end - class HTTPResetContent < HTTPSuccess # 205 + + # Response class for <tt>Reset Content</tt> responses (status code 205). + # + # The <tt>Reset Content</tt> response indicates that the server + # successfully processed the request, + # asks that the client reset its document view, and is not returning any content. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/205]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-205-reset-content]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205]. + # + class HTTPResetContent < HTTPSuccess HAS_BODY = false end - class HTTPPartialContent < HTTPSuccess # 206 + + # Response class for <tt>Partial Content</tt> responses (status code 206). + # + # The <tt>Partial Content</tt> response indicates that the server is delivering + # only part of the resource (byte serving) + # due to a Range header in the request. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-206-partial-content]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206]. + # + class HTTPPartialContent < HTTPSuccess HAS_BODY = true end - class HTTPMultiStatus < HTTPSuccess # 207 - RFC 4918 + + # Response class for <tt>Multi-Status (WebDAV)</tt> responses (status code 207). + # + # The <tt>Multi-Status (WebDAV)</tt> response indicates that the server + # has received the request, + # and that the message body can contain a number of separate response codes. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {RFC 4818}[https://www.rfc-editor.org/rfc/rfc4918#section-11.1]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207]. + # + class HTTPMultiStatus < HTTPSuccess HAS_BODY = true end - class HTTPAlreadyReported < HTTPSuccess # 208 - RFC 5842 + + # Response class for <tt>Already Reported (WebDAV)</tt> responses (status code 208). + # + # The <tt>Already Reported (WebDAV)</tt> response indicates that the server + # has received the request, + # and that the members of a DAV binding have already been enumerated + # in a preceding part of the (multi-status) response, + # and are not being included again. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {RFC 5842}[https://www.rfc-editor.org/rfc/rfc5842.html#section-7.1]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208]. + # + class HTTPAlreadyReported < HTTPSuccess HAS_BODY = true end - class HTTPIMUsed < HTTPSuccess # 226 - RFC 3229 + + # Response class for <tt>IM Used</tt> responses (status code 226). + # + # The <tt>IM Used</tt> response indicates that the server has fulfilled a request + # for the resource, and the response is a representation of the result + # of one or more instance-manipulations applied to the current instance. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {RFC 3229}[https://www.rfc-editor.org/rfc/rfc3229.html#section-10.4.1]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226]. + # + class HTTPIMUsed < HTTPSuccess HAS_BODY = true end - class HTTPMultipleChoices < HTTPRedirection # 300 + # Response class for <tt>Multiple Choices</tt> responses (status code 300). + # + # The <tt>Multiple Choices</tt> response indicates that the server + # offers multiple options for the resource from which the client may choose. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/300]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-300-multiple-choices]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300]. + # + class HTTPMultipleChoices < HTTPRedirection HAS_BODY = true end HTTPMultipleChoice = HTTPMultipleChoices - class HTTPMovedPermanently < HTTPRedirection # 301 + + # Response class for <tt>Moved Permanently</tt> responses (status code 301). + # + # The <tt>Moved Permanently</tt> response indicates that links or records + # returning this response should be updated to use the given URL. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-301-moved-permanently]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301]. + # + class HTTPMovedPermanently < HTTPRedirection HAS_BODY = true end - class HTTPFound < HTTPRedirection # 302 + + # Response class for <tt>Found</tt> responses (status code 302). + # + # The <tt>Found</tt> response indicates that the client + # should look at (browse to) another URL. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-302-found]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302]. + # + class HTTPFound < HTTPRedirection HAS_BODY = true end HTTPMovedTemporarily = HTTPFound - class HTTPSeeOther < HTTPRedirection # 303 + + # Response class for <tt>See Other</tt> responses (status code 303). + # + # The response to the request can be found under another URI using the GET method. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-303-see-other]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303]. + # + class HTTPSeeOther < HTTPRedirection HAS_BODY = true end - class HTTPNotModified < HTTPRedirection # 304 + + # Response class for <tt>Not Modified</tt> responses (status code 304). + # + # Indicates that the resource has not been modified since the version + # specified by the request headers. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-304-not-modified]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304]. + # + class HTTPNotModified < HTTPRedirection HAS_BODY = false end - class HTTPUseProxy < HTTPRedirection # 305 + + # Response class for <tt>Use Proxy</tt> responses (status code 305). + # + # The requested resource is available only through a proxy, + # whose address is provided in the response. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-305-use-proxy]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305]. + # + class HTTPUseProxy < HTTPRedirection HAS_BODY = false end - # 306 Switch Proxy - no longer unused - class HTTPTemporaryRedirect < HTTPRedirection # 307 + + # Response class for <tt>Temporary Redirect</tt> responses (status code 307). + # + # The request should be repeated with another URI; + # however, future requests should still use the original URI. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-307-temporary-redirect]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307]. + # + class HTTPTemporaryRedirect < HTTPRedirection HAS_BODY = true end - class HTTPPermanentRedirect < HTTPRedirection # 308 + + # Response class for <tt>Permanent Redirect</tt> responses (status code 308). + # + # This and all future requests should be directed to the given URI. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-308-permanent-redirect]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308]. + # + class HTTPPermanentRedirect < HTTPRedirection HAS_BODY = true end - class HTTPBadRequest < HTTPClientError # 400 + # Response class for <tt>Bad Request</tt> responses (status code 400). + # + # The server cannot or will not process the request due to an apparent client error. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-400-bad-request]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400]. + # + class HTTPBadRequest < HTTPClientError HAS_BODY = true end - class HTTPUnauthorized < HTTPClientError # 401 + + # Response class for <tt>Unauthorized</tt> responses (status code 401). + # + # Authentication is required, but either was not provided or failed. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-401-unauthorized]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401]. + # + class HTTPUnauthorized < HTTPClientError HAS_BODY = true end - class HTTPPaymentRequired < HTTPClientError # 402 + + # Response class for <tt>Payment Required</tt> responses (status code 402). + # + # Reserved for future use. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/402]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-402-payment-required]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402]. + # + class HTTPPaymentRequired < HTTPClientError HAS_BODY = true end - class HTTPForbidden < HTTPClientError # 403 + + # Response class for <tt>Forbidden</tt> responses (status code 403). + # + # The request contained valid data and was understood by the server, + # but the server is refusing action. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-403-forbidden]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403]. + # + class HTTPForbidden < HTTPClientError HAS_BODY = true end - class HTTPNotFound < HTTPClientError # 404 + + # Response class for <tt>Not Found</tt> responses (status code 404). + # + # The requested resource could not be found but may be available in the future. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-404-not-found]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404]. + # + class HTTPNotFound < HTTPClientError HAS_BODY = true end - class HTTPMethodNotAllowed < HTTPClientError # 405 + + # Response class for <tt>Method Not Allowed</tt> responses (status code 405). + # + # The request method is not supported for the requested resource. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-405-method-not-allowed]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405]. + # + class HTTPMethodNotAllowed < HTTPClientError HAS_BODY = true end - class HTTPNotAcceptable < HTTPClientError # 406 + + # Response class for <tt>Not Acceptable</tt> responses (status code 406). + # + # The requested resource is capable of generating only content + # that not acceptable according to the Accept headers sent in the request. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-406-not-acceptable]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406]. + # + class HTTPNotAcceptable < HTTPClientError HAS_BODY = true end - class HTTPProxyAuthenticationRequired < HTTPClientError # 407 + + # Response class for <tt>Proxy Authentication Required</tt> responses (status code 407). + # + # The client must first authenticate itself with the proxy. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/407]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-407-proxy-authentication-re]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407]. + # + class HTTPProxyAuthenticationRequired < HTTPClientError HAS_BODY = true end - class HTTPRequestTimeout < HTTPClientError # 408 + + # Response class for <tt>Request Timeout</tt> responses (status code 408). + # + # The server timed out waiting for the request. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-408-request-timeout]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408]. + # + class HTTPRequestTimeout < HTTPClientError HAS_BODY = true end HTTPRequestTimeOut = HTTPRequestTimeout - class HTTPConflict < HTTPClientError # 409 + + # Response class for <tt>Conflict</tt> responses (status code 409). + # + # The request could not be processed because of conflict in the current state of the resource. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-409-conflict]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409]. + # + class HTTPConflict < HTTPClientError HAS_BODY = true end - class HTTPGone < HTTPClientError # 410 + + # Response class for <tt>Gone</tt> responses (status code 410). + # + # The resource requested was previously in use but is no longer available + # and will not be available again. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-410-gone]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410]. + # + class HTTPGone < HTTPClientError HAS_BODY = true end - class HTTPLengthRequired < HTTPClientError # 411 + + # Response class for <tt>Length Required</tt> responses (status code 411). + # + # The request did not specify the length of its content, + # which is required by the requested resource. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/411]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-411-length-required]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411]. + # + class HTTPLengthRequired < HTTPClientError HAS_BODY = true end - class HTTPPreconditionFailed < HTTPClientError # 412 + + # Response class for <tt>Precondition Failed</tt> responses (status code 412). + # + # The server does not meet one of the preconditions + # specified in the request headers. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-412-precondition-failed]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412]. + # + class HTTPPreconditionFailed < HTTPClientError HAS_BODY = true end - class HTTPPayloadTooLarge < HTTPClientError # 413 + + # Response class for <tt>Payload Too Large</tt> responses (status code 413). + # + # The request is larger than the server is willing or able to process. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-413-content-too-large]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413]. + # + class HTTPPayloadTooLarge < HTTPClientError HAS_BODY = true end HTTPRequestEntityTooLarge = HTTPPayloadTooLarge - class HTTPURITooLong < HTTPClientError # 414 + + # Response class for <tt>URI Too Long</tt> responses (status code 414). + # + # The URI provided was too long for the server to process. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/414]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-414-uri-too-long]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414]. + # + class HTTPURITooLong < HTTPClientError HAS_BODY = true end HTTPRequestURITooLong = HTTPURITooLong HTTPRequestURITooLarge = HTTPRequestURITooLong - class HTTPUnsupportedMediaType < HTTPClientError # 415 + + # Response class for <tt>Unsupported Media Type</tt> responses (status code 415). + # + # The request entity has a media type which the server or resource does not support. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/415]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-415-unsupported-media-type]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415]. + # + class HTTPUnsupportedMediaType < HTTPClientError HAS_BODY = true end - class HTTPRangeNotSatisfiable < HTTPClientError # 416 + + # Response class for <tt>Range Not Satisfiable</tt> responses (status code 416). + # + # The request entity has a media type which the server or resource does not support. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-416-range-not-satisfiable]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416]. + # + class HTTPRangeNotSatisfiable < HTTPClientError HAS_BODY = true end HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable - class HTTPExpectationFailed < HTTPClientError # 417 + + # Response class for <tt>Expectation Failed</tt> responses (status code 417). + # + # The server cannot meet the requirements of the Expect request-header field. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/417]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-417-expectation-failed]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417]. + # + class HTTPExpectationFailed < HTTPClientError HAS_BODY = true end + # 418 I'm a teapot - RFC 2324; a joke RFC + # See https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#418. + # 420 Enhance Your Calm - Twitter - class HTTPMisdirectedRequest < HTTPClientError # 421 - RFC 7540 + + # Response class for <tt>Misdirected Request</tt> responses (status code 421). + # + # The request was directed at a server that is not able to produce a response. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-421-misdirected-request]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421]. + # + class HTTPMisdirectedRequest < HTTPClientError HAS_BODY = true end - class HTTPUnprocessableEntity < HTTPClientError # 422 - RFC 4918 + + # Response class for <tt>Unprocessable Entity</tt> responses (status code 422). + # + # The request was well-formed but had semantic errors. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-422-unprocessable-content]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422]. + # + class HTTPUnprocessableEntity < HTTPClientError HAS_BODY = true end - class HTTPLocked < HTTPClientError # 423 - RFC 4918 + + # Response class for <tt>Locked (WebDAV)</tt> responses (status code 423). + # + # The requested resource is locked. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.3]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423]. + # + class HTTPLocked < HTTPClientError HAS_BODY = true end - class HTTPFailedDependency < HTTPClientError # 424 - RFC 4918 + + # Response class for <tt>Failed Dependency (WebDAV)</tt> responses (status code 424). + # + # The request failed because it depended on another request and that request failed. + # See {424 Failed Dependency (WebDAV)}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.4]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. + # + class HTTPFailedDependency < HTTPClientError HAS_BODY = true end - # 425 Unordered Collection - existed only in draft - class HTTPUpgradeRequired < HTTPClientError # 426 - RFC 2817 + + # 425 Too Early + # https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#425. + + # Response class for <tt>Upgrade Required</tt> responses (status code 426). + # + # The client should switch to the protocol given in the Upgrade header field. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/426]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-426-upgrade-required]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426]. + # + class HTTPUpgradeRequired < HTTPClientError HAS_BODY = true end - class HTTPPreconditionRequired < HTTPClientError # 428 - RFC 6585 + + # Response class for <tt>Precondition Required</tt> responses (status code 428). + # + # The origin server requires the request to be conditional. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/428]. + # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-3]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428]. + # + class HTTPPreconditionRequired < HTTPClientError HAS_BODY = true end - class HTTPTooManyRequests < HTTPClientError # 429 - RFC 6585 + + # Response class for <tt>Too Many Requests</tt> responses (status code 429). + # + # The user has sent too many requests in a given amount of time. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429]. + # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-4]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429]. + # + class HTTPTooManyRequests < HTTPClientError HAS_BODY = true end - class HTTPRequestHeaderFieldsTooLarge < HTTPClientError # 431 - RFC 6585 + + # Response class for <tt>Request Header Fields Too Large</tt> responses (status code 431). + # + # An individual header field is too large, + # or all the header fields collectively, are too large. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/431]. + # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-5]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431]. + # + class HTTPRequestHeaderFieldsTooLarge < HTTPClientError HAS_BODY = true end - class HTTPUnavailableForLegalReasons < HTTPClientError # 451 - RFC 7725 + + # Response class for <tt>Unavailable For Legal Reasons</tt> responses (status code 451). + # + # A server operator has received a legal demand to deny access to a resource or to a set of resources + # that includes the requested resource. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/451]. + # - {RFC 7725}[https://www.rfc-editor.org/rfc/rfc7725.html#section-3]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451]. + # + class HTTPUnavailableForLegalReasons < HTTPClientError HAS_BODY = true end # 444 No Response - Nginx @@ -196,43 +913,188 @@ module Net # 450 Blocked by Windows Parental Controls - Microsoft # 499 Client Closed Request - Nginx - class HTTPInternalServerError < HTTPServerError # 500 + # Response class for <tt>Internal Server Error</tt> responses (status code 500). + # + # An unexpected condition was encountered and no more specific message is suitable. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-500-internal-server-error]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500]. + # + class HTTPInternalServerError < HTTPServerError HAS_BODY = true end - class HTTPNotImplemented < HTTPServerError # 501 + + # Response class for <tt>Not Implemented</tt> responses (status code 501). + # + # The server either does not recognize the request method, + # or it lacks the ability to fulfil the request. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-501-not-implemented]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501]. + # + class HTTPNotImplemented < HTTPServerError HAS_BODY = true end - class HTTPBadGateway < HTTPServerError # 502 + + # Response class for <tt>Bad Gateway</tt> responses (status code 502). + # + # The server was acting as a gateway or proxy + # and received an invalid response from the upstream server. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-502-bad-gateway]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502]. + # + class HTTPBadGateway < HTTPServerError HAS_BODY = true end - class HTTPServiceUnavailable < HTTPServerError # 503 + + # Response class for <tt>Service Unavailable</tt> responses (status code 503). + # + # The server cannot handle the request + # (because it is overloaded or down for maintenance). + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-503-service-unavailable]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503]. + # + class HTTPServiceUnavailable < HTTPServerError HAS_BODY = true end - class HTTPGatewayTimeout < HTTPServerError # 504 + + # Response class for <tt>Gateway Timeout</tt> responses (status code 504). + # + # The server was acting as a gateway or proxy + # and did not receive a timely response from the upstream server. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-504-gateway-timeout]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504]. + # + class HTTPGatewayTimeout < HTTPServerError HAS_BODY = true end HTTPGatewayTimeOut = HTTPGatewayTimeout - class HTTPVersionNotSupported < HTTPServerError # 505 + + # Response class for <tt>HTTP Version Not Supported</tt> responses (status code 505). + # + # The server does not support the HTTP version used in the request. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/505]. + # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-505-http-version-not-suppor]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505]. + # + class HTTPVersionNotSupported < HTTPServerError HAS_BODY = true end - class HTTPVariantAlsoNegotiates < HTTPServerError # 506 + + # Response class for <tt>Variant Also Negotiates</tt> responses (status code 506). + # + # Transparent content negotiation for the request results in a circular reference. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/506]. + # - {RFC 2295}[https://www.rfc-editor.org/rfc/rfc2295#section-8.1]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506]. + # + class HTTPVariantAlsoNegotiates < HTTPServerError HAS_BODY = true end - class HTTPInsufficientStorage < HTTPServerError # 507 - RFC 4918 + + # Response class for <tt>Insufficient Storage (WebDAV)</tt> responses (status code 507). + # + # The server is unable to store the representation needed to complete the request. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/507]. + # - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.5]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507]. + # + class HTTPInsufficientStorage < HTTPServerError HAS_BODY = true end - class HTTPLoopDetected < HTTPServerError # 508 - RFC 5842 + + # Response class for <tt>Loop Detected (WebDAV)</tt> responses (status code 508). + # + # The server detected an infinite loop while processing the request. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/508]. + # - {RFC 5942}[https://www.rfc-editor.org/rfc/rfc5842.html#section-7.2]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508]. + # + class HTTPLoopDetected < HTTPServerError HAS_BODY = true end # 509 Bandwidth Limit Exceeded - Apache bw/limited extension - class HTTPNotExtended < HTTPServerError # 510 - RFC 2774 + + # Response class for <tt>Not Extended</tt> responses (status code 510). + # + # Further extensions to the request are required for the server to fulfill it. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/510]. + # - {RFC 2774}[https://www.rfc-editor.org/rfc/rfc2774.html#section-7]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510]. + # + class HTTPNotExtended < HTTPServerError HAS_BODY = true end - class HTTPNetworkAuthenticationRequired < HTTPServerError # 511 - RFC 6585 + + # Response class for <tt>Network Authentication Required</tt> responses (status code 511). + # + # The client needs to authenticate to gain network access. + # + # :include: doc/net-http/included_getters.rdoc + # + # References: + # + # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/511]. + # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-6]. + # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511]. + # + class HTTPNetworkAuthenticationRequired < HTTPServerError HAS_BODY = true end - # :startdoc: end class Net::HTTPResponse diff --git a/lib/net/http/status.rb b/lib/net/http/status.rb index 8db3f7d9e3..e70b47d9fb 100644 --- a/lib/net/http/status.rb +++ b/lib/net/http/status.rb @@ -4,7 +4,7 @@ require_relative '../http' if $0 == __FILE__ require 'open-uri' - IO.foreach(__FILE__) do |line| + File.foreach(__FILE__) do |line| puts line break if line.start_with?('end') end @@ -16,7 +16,7 @@ if $0 == __FILE__ next if ['(Unused)', 'Unassigned', 'Description'].include?(mes) puts " #{code} => '#{mes}'," end - puts "}" + puts "} # :nodoc:" end Net::HTTP::STATUS_CODES = { @@ -55,15 +55,16 @@ Net::HTTP::STATUS_CODES = { 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', - 413 => 'Payload Too Large', + 413 => 'Content Too Large', 414 => 'URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Range Not Satisfiable', 417 => 'Expectation Failed', 421 => 'Misdirected Request', - 422 => 'Unprocessable Entity', + 422 => 'Unprocessable Content', 423 => 'Locked', 424 => 'Failed Dependency', + 425 => 'Too Early', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', @@ -78,6 +79,6 @@ Net::HTTP::STATUS_CODES = { 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', - 510 => 'Not Extended', + 510 => 'Not Extended (OBSOLETED)', 511 => 'Network Authentication Required', -} +} # :nodoc: diff --git a/lib/net/https.rb b/lib/net/https.rb index d46721c82a..0f23e1fb13 100644 --- a/lib/net/https.rb +++ b/lib/net/https.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true =begin = net/https -- SSL/TLS enhancement for Net::HTTP. diff --git a/lib/open3/version.rb b/lib/open3/version.rb index 5a8e84b4ae..b6b6ee2c9c 100644 --- a/lib/open3/version.rb +++ b/lib/open3/version.rb @@ -1,3 +1,3 @@ module Open3 - VERSION = "0.1.1" + VERSION = "0.1.2" end diff --git a/lib/optparse.rb b/lib/optparse.rb index dc2b8a060f..53a4387bd8 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -425,7 +425,7 @@ # If you have any questions, file a ticket at http://bugs.ruby-lang.org. # class OptionParser - OptionParser::Version = "0.3.0" + OptionParser::Version = "0.3.1" # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze @@ -2084,10 +2084,23 @@ XXX f |= Regexp::IGNORECASE if /i/ =~ o f |= Regexp::MULTILINE if /m/ =~ o f |= Regexp::EXTENDED if /x/ =~ o - k = o.delete("imx") - k = nil if k.empty? + case o = o.delete("imx") + when "" + when "u" + s = s.encode(Encoding::UTF_8) + when "e" + s = s.encode(Encoding::EUC_JP) + when "s" + s = s.encode(Encoding::SJIS) + when "n" + f |= Regexp::NOENCODING + else + raise OptionParser::InvalidArgument, "unknown regexp option - #{o}" + end + else + s ||= all end - Regexp.new(s || all, f, k) + Regexp.new(s, f) end # diff --git a/lib/racc/info.rb b/lib/racc/info.rb index f19987e6b8..37bff7edba 100644 --- a/lib/racc/info.rb +++ b/lib/racc/info.rb @@ -11,7 +11,7 @@ #++ module Racc - VERSION = '1.6.1' + VERSION = '1.6.2' Version = VERSION Copyright = 'Copyright (c) 1999-2006 Minero Aoki' end diff --git a/lib/racc/statetransitiontable.rb b/lib/racc/statetransitiontable.rb index cae411c98b..d75fa1657a 100644 --- a/lib/racc/statetransitiontable.rb +++ b/lib/racc/statetransitiontable.rb @@ -216,7 +216,7 @@ module Racc end i = ii end - Regexp.compile(map, nil, 'n') + Regexp.compile(map, Regexp::NOENCODING) end def set_table(entries, dummy, tbl, chk, ptr) diff --git a/lib/random/formatter.rb b/lib/random/formatter.rb index 744853a4b7..4dea61c16c 100644 --- a/lib/random/formatter.rb +++ b/lib/random/formatter.rb @@ -1,9 +1,14 @@ # -*- coding: us-ascii -*- # frozen_string_literal: true -# == Random number formatter. +# == \Random number formatter. # -# Formats generated random numbers in many manners. +# Formats generated random numbers in many manners. When <tt>'random/formatter'</tt> +# is required, several methods are added to empty core module <tt>Random::Formatter</tt>, +# making them available as Random's instance and module methods. +# +# Standard library SecureRandom is also extended with the module, and the methods +# described below are available as a module methods in it. # # === Examples # @@ -11,34 +16,45 @@ # # require 'random/formatter' # +# prng = Random.new # prng.hex(10) #=> "52750b30ffbc7de3b362" # prng.hex(10) #=> "92b15d6c8dc4beb5f559" # prng.hex(13) #=> "39b290146bea6ce975c37cfc23" +# # or just +# Random.hex #=> "1aed0c631e41be7f77365415541052ee" # # Generate random base64 strings: # # prng.base64(10) #=> "EcmTPZwWRAozdA==" # prng.base64(10) #=> "KO1nIU+p9DKxGg==" # prng.base64(12) #=> "7kJSM/MzBJI+75j8" +# Random.base64(4) #=> "bsQ3fQ==" # # Generate random binary strings: # # prng.random_bytes(10) #=> "\016\t{\370g\310pbr\301" # prng.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337" +# Random.random_bytes(6) #=> "\xA1\xE6Lr\xC43" # # Generate alphanumeric strings: # # prng.alphanumeric(10) #=> "S8baxMJnPl" # prng.alphanumeric(10) #=> "aOxAg8BAJe" +# Random.alphanumeric #=> "TmP9OsJHJLtaZYhP" # # Generate UUIDs: # # prng.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" # prng.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" +# Random.uuid #=> "f14e0271-de96-45cc-8911-8910292a42cd" +# +# All methods are available in the standard library SecureRandom, too: +# +# SecureRandom.hex #=> "05b45376a30c67238eb93b16499e50cf" module Random::Formatter - # Random::Formatter#random_bytes generates a random binary string. + # Generate a random binary string. # # The argument _n_ specifies the length of the result string. # @@ -49,14 +65,16 @@ module Random::Formatter # # require 'random/formatter' # - # prng.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6" + # Random.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6" + # # or + # prng = Random.new # prng.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97" def random_bytes(n=nil) n = n ? n.to_int : 16 gen_random(n) end - # Random::Formatter#hex generates a random hexadecimal string. + # Generate a random hexadecimal string. # # The argument _n_ specifies the length, in bytes, of the random number to be generated. # The length of the resulting hexadecimal string is twice of _n_. @@ -68,13 +86,15 @@ module Random::Formatter # # require 'random/formatter' # - # prng.hex #=> "eb693ec8252cd630102fd0d0fb7c3485" + # Random.hex #=> "eb693ec8252cd630102fd0d0fb7c3485" + # # or + # prng = Random.new # prng.hex #=> "91dc3bfb4de5b11d029d376634589b61" def hex(n=nil) random_bytes(n).unpack1("H*") end - # Random::Formatter#base64 generates a random base64 string. + # Generate a random base64 string. # # The argument _n_ specifies the length, in bytes, of the random number # to be generated. The length of the result string is about 4/3 of _n_. @@ -86,7 +106,9 @@ module Random::Formatter # # require 'random/formatter' # - # prng.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A==" + # Random.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A==" + # # or + # prng = Random.new # prng.base64 #=> "6BbW0pxO0YENxn38HMUbcQ==" # # See RFC 3548 for the definition of base64. @@ -94,7 +116,7 @@ module Random::Formatter [random_bytes(n)].pack("m0") end - # Random::Formatter#urlsafe_base64 generates a random URL-safe base64 string. + # Generate a random URL-safe base64 string. # # The argument _n_ specifies the length, in bytes, of the random number # to be generated. The length of the result string is about 4/3 of _n_. @@ -112,7 +134,9 @@ module Random::Formatter # # require 'random/formatter' # - # prng.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg" + # Random.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg" + # # or + # prng = Random.new # prng.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg" # # prng.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ==" @@ -126,12 +150,14 @@ module Random::Formatter s end - # Random::Formatter#uuid generates a random v4 UUID (Universally Unique IDentifier). + # Generate a random v4 UUID (Universally Unique IDentifier). # # require 'random/formatter' # - # prng.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" - # prng.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" + # Random.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" + # Random.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" + # # or + # prng = Random.new # prng.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b" # # The version 4 UUID is purely random (except the version). @@ -139,7 +165,7 @@ module Random::Formatter # # The result contains 122 random bits (15.25 random bytes). # - # See RFC 4122 for details of UUID. + # See RFC4122[https://datatracker.ietf.org/doc/html/rfc4122] for details of UUID. # def uuid ary = random_bytes(16).unpack("NnnnnN") @@ -152,7 +178,7 @@ module Random::Formatter self.bytes(n) end - # Random::Formatter#choose generates a string that randomly draws from a + # Generate a string that randomly draws from a # source array of characters. # # The argument _source_ specifies the array of characters from which @@ -196,7 +222,7 @@ module Random::Formatter end ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9'] - # Random::Formatter#alphanumeric generates a random alphanumeric string. + # Generate a random alphanumeric string. # # The argument _n_ specifies the length, in characters, of the alphanumeric # string to be generated. @@ -208,7 +234,9 @@ module Random::Formatter # # require 'random/formatter' # - # prng.alphanumeric #=> "2BuBuLf3WfSKyQbR" + # Random.alphanumeric #=> "2BuBuLf3WfSKyQbR" + # # or + # prng = Random.new # prng.alphanumeric(10) #=> "i6K93NdqiH" def alphanumeric(n=nil) n = 16 if n.nil? diff --git a/lib/rdoc/store.rb b/lib/rdoc/store.rb index 9fc540d317..c793e49ed8 100644 --- a/lib/rdoc/store.rb +++ b/lib/rdoc/store.rb @@ -556,9 +556,7 @@ class RDoc::Store def load_cache #orig_enc = @encoding - File.open cache_path, 'rb' do |io| - @cache = Marshal.load io - end + @cache = marshal_load(cache_path) load_enc = @cache[:encoding] @@ -615,9 +613,7 @@ class RDoc::Store def load_class_data klass_name file = class_file klass_name - File.open file, 'rb' do |io| - Marshal.load io - end + marshal_load(file) rescue Errno::ENOENT => e error = MissingFileError.new(self, file, klass_name) error.set_backtrace e.backtrace @@ -630,14 +626,10 @@ class RDoc::Store def load_method klass_name, method_name file = method_file klass_name, method_name - File.open file, 'rb' do |io| - obj = Marshal.load io - obj.store = self - obj.parent = - find_class_or_module(klass_name) || load_class(klass_name) unless - obj.parent - obj - end + obj = marshal_load(file) + obj.store = self + obj.parent ||= find_class_or_module(klass_name) || load_class(klass_name) + obj rescue Errno::ENOENT => e error = MissingFileError.new(self, file, klass_name + method_name) error.set_backtrace e.backtrace @@ -650,11 +642,9 @@ class RDoc::Store def load_page page_name file = page_file page_name - File.open file, 'rb' do |io| - obj = Marshal.load io - obj.store = self - obj - end + obj = marshal_load(file) + obj.store = self + obj rescue Errno::ENOENT => e error = MissingFileError.new(self, file, page_name) error.set_backtrace e.backtrace @@ -976,4 +966,21 @@ class RDoc::Store @unique_modules end + private + def marshal_load(file) + File.open(file, 'rb') {|io| Marshal.load(io, MarshalFilter)} + end + + MarshalFilter = proc do |obj| + case obj + when true, false, nil, Array, Class, Encoding, Hash, Integer, String, Symbol, RDoc::Text + else + unless obj.class.name.start_with?("RDoc::") + raise TypeError, "not permitted class: #{obj.class.name}" + end + end + obj + end + private_constant :MarshalFilter + end diff --git a/lib/rdoc/version.rb b/lib/rdoc/version.rb index 04322a1970..31c1aa0276 100644 --- a/lib/rdoc/version.rb +++ b/lib/rdoc/version.rb @@ -5,6 +5,6 @@ module RDoc ## # RDoc version you are using - VERSION = '6.5.0' + VERSION = '6.5.1.1' end diff --git a/lib/reline/version.rb b/lib/reline/version.rb index 1bb1c02f3d..67a3d694bd 100644 --- a/lib/reline/version.rb +++ b/lib/reline/version.rb @@ -1,3 +1,3 @@ module Reline - VERSION = '0.3.1' + VERSION = '0.3.2' end diff --git a/lib/resolv-replace.gemspec b/lib/resolv-replace.gemspec index 6bc07dbe10..48f7108a8e 100644 --- a/lib/resolv-replace.gemspec +++ b/lib/resolv-replace.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "resolv-replace" - spec.version = "0.1.0" + spec.version = "0.1.1" spec.authors = ["Tanaka Akira"] spec.email = ["akr@fsij.org"] diff --git a/lib/resolv.gemspec b/lib/resolv.gemspec index db256dc1b0..f221010ab6 100644 --- a/lib/resolv.gemspec +++ b/lib/resolv.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "resolv" - spec.version = "0.2.2" + spec.version = "0.2.3" spec.authors = ["Tanaka Akira"] spec.email = ["akr@fsij.org"] diff --git a/lib/resolv.rb b/lib/resolv.rb index 61c9c7d5cf..eaea69bfd5 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -1624,6 +1624,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) @@ -1644,7 +1645,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 diff --git a/lib/mjit/c_pointer.rb b/lib/ruby_vm/mjit/c_pointer.rb index aadf80e804..a92c2140ae 100644 --- a/lib/mjit/c_pointer.rb +++ b/lib/ruby_vm/mjit/c_pointer.rb @@ -1,4 +1,4 @@ -module RubyVM::MJIT +module RubyVM::MJIT # :nodoc: all # Every class under this namespace is a pointer. Even if the type is # immediate, it shouldn't be dereferenced until `*` is called. module CPointer @@ -282,12 +282,12 @@ module RubyVM::MJIT # Dereference def * - byte = Fiddle::Pointer.new(@addr)[0, Fiddle::SIZEOF_CHAR].unpack('c').first + byte = Fiddle::Pointer.new(@addr)[0, Fiddle::SIZEOF_CHAR].unpack1('c') if @width == 1 bit = (1 & (byte >> @offset)) bit == 1 elsif @width <= 8 && @offset == 0 - bitmask = @width.times.map { |i| 1 << i }.sum + bitmask = @width.times.sum { |i| 1 << i } byte & bitmask else raise NotImplementedError.new("not-implemented bit field access: width=#{@width} offset=#{@offset}") diff --git a/lib/mjit/c_type.rb b/lib/ruby_vm/mjit/c_type.rb index 9e45d8d41c..9c965ad2fb 100644 --- a/lib/mjit/c_type.rb +++ b/lib/ruby_vm/mjit/c_type.rb @@ -2,7 +2,7 @@ require 'fiddle' require 'fiddle/pack' require_relative 'c_pointer' -module RubyVM::MJIT +module RubyVM::MJIT # :nodoc: all module CType module Struct # @param name [String] diff --git a/lib/mjit/compiler.rb b/lib/ruby_vm/mjit/compiler.rb index 972137e383..81022cd0a8 100644 --- a/lib/mjit/compiler.rb +++ b/lib/ruby_vm/mjit/compiler.rb @@ -13,7 +13,7 @@ # DISPATCH_ORIGINAL_INSN(): expanded in _mjit_compile_insn.erb # THROW_EXCEPTION(): specially defined for JIT # RESTORE_REGS(): specially defined for `leave` -class RubyVM::MJIT::Compiler +class RubyVM::MJIT::Compiler # :nodoc: all C = RubyVM::MJIT.const_get(:C, false) INSNS = RubyVM::MJIT.const_get(:INSNS, false) UNSUPPORTED_INSNS = [ @@ -75,6 +75,13 @@ class RubyVM::MJIT::Compiler src << "#undef GET_SELF\n" src << "#define GET_SELF() cfp_self\n" + # Generate merged ivar guards first if needed + if !status.compile_info.disable_ivar_cache && using_ivar?(iseq.body) + src << " if (UNLIKELY(!RB_TYPE_P(GET_SELF(), T_OBJECT))) {" + src << " goto ivar_cancel;\n" + src << " }\n" + end + # Simulate `opt_pc` in setup_parameters_complex. Other PCs which may be passed by catch tables # are not considered since vm_exec doesn't call jit_exec for catch tables. if iseq.body.param.flags.has_opt @@ -88,13 +95,6 @@ class RubyVM::MJIT::Compiler src << " }\n" end - # Generate merged ivar guards first if needed - if !status.compile_info.disable_ivar_cache && using_ivar?(iseq.body) - src << " if (UNLIKELY(!RB_TYPE_P(GET_SELF(), T_OBJECT))) {" - src << " goto ivar_cancel;\n" - src << " }\n" - end - compile_insns(0, 0, status, iseq.body, src) compile_cancel_handler(src, iseq.body, status) src << "#undef GET_SELF\n" @@ -536,7 +536,7 @@ class RubyVM::MJIT::Compiler when /\A\s+JUMP\((?<dest>[^)]+)\);\s+\z/ dest = Regexp.last_match[:dest] if insn.name == :opt_case_dispatch # special case... TODO: use another macro to avoid checking name - hash_offsets = C.rb_hash_values(operands[0]) + hash_offsets = C.rb_hash_values(operands[0]).uniq else_offset = cast_offset(operands[1]) base_pos = pos + insn_len @@ -817,9 +817,8 @@ class RubyVM::MJIT::Compiler # Interpret unsigned long as signed long (VALUE -> OFFSET) def cast_offset(offset) - bits = "%0#{8 * Fiddle::SIZEOF_VOIDP}d" % offset.to_s(2) - if bits[0] == '1' # negative - offset = -bits.chars.map { |i| i == '0' ? '1' : '0' }.join.to_i(2) - 1 + if offset >= 1 << 8 * Fiddle::SIZEOF_VOIDP - 1 # negative + offset -= 1 << 8 * Fiddle::SIZEOF_VOIDP end offset end diff --git a/lib/ruby_vm/mjit/hooks.rb b/lib/ruby_vm/mjit/hooks.rb new file mode 100644 index 0000000000..3fb1004111 --- /dev/null +++ b/lib/ruby_vm/mjit/hooks.rb @@ -0,0 +1,32 @@ +module RubyVM::MJIT::Hooks # :nodoc: all + C = RubyVM::MJIT.const_get(:C, false) + + def self.on_bop_redefined(_redefined_flag, _bop) + C.mjit_cancel_all("BOP is redefined") + end + + def self.on_cme_invalidate(_cme) + # to be used later + end + + def self.on_ractor_spawn + C.mjit_cancel_all("Ractor is spawned") + end + + def self.on_constant_state_changed(_id) + # to be used later + end + + def self.on_constant_ic_update(_iseq, _ic, _insn_idx) + # to be used later + end + + def self.on_tracing_invalidate_all(new_iseq_events) + # Stop calling all JIT-ed code. We can't rewrite existing JIT-ed code to trace_ insns for now. + # :class events are triggered only in ISEQ_TYPE_CLASS, but mjit_target_iseq_p ignores such iseqs. + # Thus we don't need to cancel JIT-ed code for :class events. + if new_iseq_events != C.RUBY_EVENT_CLASS + C.mjit_cancel_all("TracePoint is enabled") + end + end +end diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 0c0ca9c1ba..af86646a82 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. @@ -8,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "3.4.0.dev".freeze + VERSION = "3.4.19" end # Must be first since it unloads the prelude from 1.9.2 @@ -119,10 +120,6 @@ module Gem # to avoid deprecation warnings in Ruby 2.7. UNTAINT = RUBY_VERSION < "2.7" ? :untaint.to_sym : proc {} - # When https://bugs.ruby-lang.org/issues/17259 is available, there is no need to override Kernel#warn - KERNEL_WARN_IGNORES_INTERNAL_ENTRIES = RUBY_ENGINE == "truffleruby" || - (RUBY_ENGINE == "ruby" && RUBY_VERSION >= "3.0") - ## # An Array of Regexps that match windows Ruby platforms. @@ -185,6 +182,8 @@ module Gem @default_source_date_epoch = nil + @discover_gems_on_require = true + ## # Try to activate a gem containing +path+. Returns true if # activation succeeded or wasn't needed because it was already @@ -826,7 +825,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} def self.env_requirement(gem_name) @env_requirements_by_name ||= {} @env_requirements_by_name[gem_name] ||= begin - req = ENV["GEM_REQUIREMENT_#{gem_name.upcase}"] || ">= 0".freeze + req = ENV["GEM_REQUIREMENT_#{gem_name.upcase}"] || ">= 0" Gem::Requirement.create(req) end end @@ -857,8 +856,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} # Returns the version of the latest release-version of gem +name+ def self.latest_version_for(name) - spec = latest_spec_for name - spec && spec.version + latest_spec_for(name)&.version end ## @@ -1167,9 +1165,17 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} # RubyGems distributors (like operating system package managers) can # disable RubyGems update by setting this to error message printed to # end-users on gem update --system instead of actual update. + attr_accessor :disable_system_update_message ## + # Whether RubyGems should enhance builtin `require` to automatically + # check whether the path required is present in installed gems, and + # automatically activate them and add them to `$LOAD_PATH`. + + attr_accessor :discover_gems_on_require + + ## # Hash of loaded Gem::Specification keyed by name attr_reader :loaded_specs @@ -1295,9 +1301,8 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} ## # Location of Marshal quick gemspecs on remote repositories - MARSHAL_SPEC_DIR = "quick/Marshal.#{Gem.marshal_version}/".freeze + MARSHAL_SPEC_DIR = "quick/Marshal.#{Gem.marshal_version}/" - autoload :BundlerVersionFinder, File.expand_path("rubygems/bundler_version_finder", __dir__) autoload :ConfigFile, File.expand_path("rubygems/config_file", __dir__) autoload :Dependency, File.expand_path("rubygems/dependency", __dir__) autoload :DependencyList, File.expand_path("rubygems/dependency_list", __dir__) @@ -1350,7 +1355,16 @@ end Gem::Specification.load_defaults require_relative "rubygems/core_ext/kernel_gem" -require_relative "rubygems/core_ext/kernel_require" -require_relative "rubygems/core_ext/kernel_warn" + +path = File.join(__dir__, "rubygems/core_ext/kernel_require.rb") +# When https://bugs.ruby-lang.org/issues/17259 is available, there is no need to override Kernel#warn +if RUBY_ENGINE == "truffleruby" || + (RUBY_ENGINE == "ruby" && RUBY_VERSION >= "3.0") + file = "<internal:#{path}>" +else + require_relative "rubygems/core_ext/kernel_warn" + file = path +end +eval File.read(path), nil, file require ENV["BUNDLER_SETUP"] if ENV["BUNDLER_SETUP"] && !defined?(Bundler) diff --git a/lib/rubygems/available_set.rb b/lib/rubygems/available_set.rb index 58b601f6b0..2ab3e137da 100644 --- a/lib/rubygems/available_set.rb +++ b/lib/rubygems/available_set.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + class Gem::AvailableSet include Enumerable diff --git a/lib/rubygems/basic_specification.rb b/lib/rubygems/basic_specification.rb index dcc64e6409..5d198114e0 100644 --- a/lib/rubygems/basic_specification.rb +++ b/lib/rubygems/basic_specification.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + ## # BasicSpecification is an abstract class which implements some common code # used by both Specification and StubSpecification. diff --git a/lib/rubygems/bundler_version_finder.rb b/lib/rubygems/bundler_version_finder.rb index f6fad0bd83..5b34227d3a 100644 --- a/lib/rubygems/bundler_version_finder.rb +++ b/lib/rubygems/bundler_version_finder.rb @@ -21,7 +21,7 @@ module Gem::BundlerVersionFinder end def self.bundle_update_bundler_version - return unless File.basename($0) == "bundle".freeze + return unless File.basename($0) == "bundle" return unless "update".start_with?(ARGV.first || " ") bundler_version = nil update_index = nil @@ -47,7 +47,7 @@ module Gem::BundlerVersionFinder def self.lockfile_contents gemfile = ENV["BUNDLE_GEMFILE"] - gemfile = nil if gemfile && gemfile.empty? + gemfile = nil if gemfile&.empty? unless gemfile begin diff --git a/lib/rubygems/command.rb b/lib/rubygems/command.rb index badc21023a..07cf4e0e81 100644 --- a/lib/rubygems/command.rb +++ b/lib/rubygems/command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. @@ -201,11 +202,15 @@ class Gem::Command # respectively. def get_all_gem_names_and_versions get_all_gem_names.map do |name| - if /\A(.*):(#{Gem::Requirement::PATTERN_RAW})\z/ =~ name - [$1, $2] - else - [name] - end + extract_gem_name_and_version(name) + end + end + + def extract_gem_name_and_version(name) # :nodoc: + if /\A(.*):(#{Gem::Requirement::PATTERN_RAW})\z/ =~ name + [$1, $2] + else + [name] end end @@ -624,13 +629,17 @@ class Gem::Command # :stopdoc: - HELP = <<-HELP.freeze + HELP = <<-HELP RubyGems is a package manager for Ruby. Usage: gem -h/--help gem -v/--version - gem command [arguments...] [options...] + gem [global options...] command [arguments...] [options...] + + Global options: + -C PATH run as if gem was started in <PATH> + instead of the current working directory Examples: gem install rake diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb index 8d31d85b44..b3913d465a 100644 --- a/lib/rubygems/command_manager.rb +++ b/lib/rubygems/command_manager.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. @@ -43,6 +44,7 @@ class Gem::CommandManager :contents, :dependency, :environment, + :exec, :fetch, :generate_index, :help, @@ -82,7 +84,7 @@ class Gem::CommandManager # Return the authoritative instance of the command manager. def self.instance - @command_manager ||= new + @instance ||= new end ## @@ -97,7 +99,7 @@ class Gem::CommandManager # Reset the authoritative instance of the command manager. def self.reset - @command_manager = nil + @instance = nil end ## @@ -175,14 +177,20 @@ class Gem::CommandManager when "-v", "--version" then say Gem::VERSION terminate_interaction 0 + when "-C" then + args.shift + start_point = args.shift + if Dir.exist?(start_point) + Dir.chdir(start_point) { invoke_command(args, build_args) } + else + alert_error clean_text("#{start_point} isn't a directory.") + terminate_interaction 1 + end when /^-/ then alert_error clean_text("Invalid option: #{args.first}. See 'gem --help'.") terminate_interaction 1 else - cmd_name = args.shift.downcase - cmd = find_command cmd_name - cmd.deprecation_warning if cmd.deprecated? - cmd.invoke_with_build_args args, build_args + invoke_command(args, build_args) end end @@ -237,4 +245,11 @@ class Gem::CommandManager ui.backtrace e end end + + def invoke_command(args, build_args) + cmd_name = args.shift.downcase + cmd = find_command cmd_name + cmd.deprecation_warning if cmd.deprecated? + cmd.invoke_with_build_args args, build_args + end end diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb index accbd7e97d..68e020d94a 100644 --- a/lib/rubygems/commands/build_command.rb +++ b/lib/rubygems/commands/build_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../package" require_relative "../version_option" @@ -26,6 +27,9 @@ class Gem::Commands::BuildCommand < Gem::Command add_option "-C PATH", "Run as if gem build was started in <PATH> instead of the current working directory." do |value, options| options[:build_path] = value end + deprecate_option "-C", + version: "4.0", + extra_msg: "-C is a global flag now. Use `gem -C PATH build GEMSPEC_FILE [options]` instead" end def arguments # :nodoc: diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb index 17b1d11b19..344f31fba9 100644 --- a/lib/rubygems/commands/cert_command.rb +++ b/lib/rubygems/commands/cert_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../security" diff --git a/lib/rubygems/commands/check_command.rb b/lib/rubygems/commands/check_command.rb index 4d1f8782b1..b300b4f87a 100644 --- a/lib/rubygems/commands/check_command.rb +++ b/lib/rubygems/commands/check_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../version_option" require_relative "../validator" diff --git a/lib/rubygems/commands/cleanup_command.rb b/lib/rubygems/commands/cleanup_command.rb index 1ae84924c1..ee5e1252b6 100644 --- a/lib/rubygems/commands/cleanup_command.rb +++ b/lib/rubygems/commands/cleanup_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../dependency_list" require_relative "../uninstaller" diff --git a/lib/rubygems/commands/contents_command.rb b/lib/rubygems/commands/contents_command.rb index c5fdfca31e..f378441ca4 100644 --- a/lib/rubygems/commands/contents_command.rb +++ b/lib/rubygems/commands/contents_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../version_option" diff --git a/lib/rubygems/commands/dependency_command.rb b/lib/rubygems/commands/dependency_command.rb index 3f69a95e83..cef98e30d3 100644 --- a/lib/rubygems/commands/dependency_command.rb +++ b/lib/rubygems/commands/dependency_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../local_remote_options" require_relative "../version_option" diff --git a/lib/rubygems/commands/environment_command.rb b/lib/rubygems/commands/environment_command.rb index d95e1d0dbb..28dbf93c50 100644 --- a/lib/rubygems/commands/environment_command.rb +++ b/lib/rubygems/commands/environment_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" class Gem::Commands::EnvironmentCommand < Gem::Command diff --git a/lib/rubygems/commands/exec_command.rb b/lib/rubygems/commands/exec_command.rb new file mode 100644 index 0000000000..383c8c5b37 --- /dev/null +++ b/lib/rubygems/commands/exec_command.rb @@ -0,0 +1,249 @@ +# frozen_string_literal: true + +require_relative "../command" +require_relative "../dependency_installer" +require_relative "../gem_runner" +require_relative "../package" +require_relative "../version_option" + +class Gem::Commands::ExecCommand < Gem::Command + include Gem::VersionOption + + def initialize + super "exec", "Run a command from a gem", { + version: Gem::Requirement.default, + } + + add_version_option + add_prerelease_option "to be installed" + + add_option "-g", "--gem GEM", "run the executable from the given gem" do |value, options| + options[:gem_name] = value + end + + add_option(:"Install/Update", "--conservative", + "Prefer the most recent installed version, ", + "rather than the latest version overall") do |value, options| + options[:conservative] = true + end + end + + def arguments # :nodoc: + "COMMAND the executable command to run" + end + + def defaults_str # :nodoc: + "--version '#{Gem::Requirement.default}'" + end + + def description # :nodoc: + <<-EOF +The exec command handles installing (if necessary) and running an executable +from a gem, regardless of whether that gem is currently installed. + +The exec command can be thought of as a shortcut to running `gem install` and +then the executable from the installed gem. + +For example, `gem exec rails new .` will run `rails new .` in the current +directory, without having to manually run `gem install rails`. +Additionally, the exec command ensures the most recent version of the gem +is used (unless run with `--conservative`), and that the gem is not installed +to the same gem path as user-installed gems. + EOF + end + + def usage # :nodoc: + "#{program_name} [options --] COMMAND [args]" + end + + def execute + gem_paths = { "GEM_HOME" => Gem.paths.home, "GEM_PATH" => Gem.paths.path.join(File::PATH_SEPARATOR), "GEM_SPEC_CACHE" => Gem.paths.spec_cache_dir }.compact + + check_executable + + print_command + if options[:gem_name] == "gem" && options[:executable] == "gem" + set_gem_exec_install_paths + Gem::GemRunner.new.run options[:args] + return + elsif options[:conservative] + install_if_needed + else + install + activate! + end + + load! + ensure + ENV.update(gem_paths) if gem_paths + Gem.clear_paths + end + + private + + def handle_options(args) + args = add_extra_args(args) + check_deprecated_options(args) + @options = Marshal.load Marshal.dump @defaults # deep copy + parser.order!(args) do |v| + # put the non-option back at the front of the list of arguments + args.unshift(v) + + # stop parsing once we hit the first non-option, + # so you can call `gem exec rails --version` and it prints the rails + # version rather than rubygem's + break + end + @options[:args] = args + + options[:executable], gem_version = extract_gem_name_and_version(options[:args].shift) + options[:gem_name] ||= options[:executable] + + if gem_version + if options[:version].none? + options[:version] = Gem::Requirement.new(gem_version) + else + options[:version].concat [gem_version] + end + end + + if options[:prerelease] && !options[:version].prerelease? + if options[:version].none? + options[:version] = Gem::Requirement.default_prerelease + else + options[:version].concat [Gem::Requirement.default_prerelease] + end + end + end + + def check_executable + if options[:executable].nil? + raise Gem::CommandLineError, + "Please specify an executable to run (e.g. #{program_name} COMMAND)" + end + end + + def print_command + verbose "running #{program_name} with:\n" + opts = options.reject {|_, v| v.nil? || Array(v).empty? } + max_length = opts.map {|k, _| k.size }.max + opts.each do |k, v| + next if v.nil? + verbose "\t#{k.to_s.rjust(max_length)}: #{v}" + end + verbose "" + end + + def install_if_needed + activate! + rescue Gem::MissingSpecError + verbose "#{Gem::Dependency.new(options[:gem_name], options[:version])} not available locally, installing from remote" + install + activate! + end + + def set_gem_exec_install_paths + home = File.join(Gem.dir, "gem_exec") + + ENV["GEM_PATH"] = ([home] + Gem.path).join(File::PATH_SEPARATOR) + ENV["GEM_HOME"] = home + Gem.clear_paths + end + + def install + set_gem_exec_install_paths + + gem_name = options[:gem_name] + gem_version = options[:version] + + install_options = options.merge( + minimal_deps: false, + wrappers: true + ) + + suppress_always_install do + dep_installer = Gem::DependencyInstaller.new install_options + + request_set = dep_installer.resolve_dependencies gem_name, gem_version + + verbose "Gems to install:" + request_set.sorted_requests.each do |activation_request| + verbose "\t#{activation_request.full_name}" + end + + request_set.install install_options + end + + Gem::Specification.reset + rescue Gem::InstallError => e + alert_error "Error installing #{gem_name}:\n\t#{e.message}" + terminate_interaction 1 + rescue Gem::GemNotFoundException => e + show_lookup_failure e.name, e.version, e.errors, false + + terminate_interaction 2 + rescue Gem::UnsatisfiableDependencyError => e + show_lookup_failure e.name, e.version, e.errors, false, + "'#{gem_name}' (#{gem_version})" + + terminate_interaction 2 + end + + def activate! + gem(options[:gem_name], options[:version]) + Gem.finish_resolve + + verbose "activated #{options[:gem_name]} (#{Gem.loaded_specs[options[:gem_name]].version})" + end + + def load! + argv = ARGV.clone + ARGV.replace options[:args] + + exe = executable = options[:executable] + + contains_executable = Gem.loaded_specs.values.select do |spec| + spec.executables.include?(executable) + end + + if contains_executable.any? {|s| s.name == executable } + contains_executable.select! {|s| s.name == executable } + end + + if contains_executable.empty? + if (spec = Gem.loaded_specs[executable]) && (exe = spec.executable) + contains_executable << spec + else + alert_error "Failed to load executable `#{executable}`," \ + " are you sure the gem `#{options[:gem_name]}` contains it?" + terminate_interaction 1 + end + end + + if contains_executable.size > 1 + alert_error "Ambiguous which gem `#{executable}` should come from: " \ + "the options are #{contains_executable.map(&:name)}, " \ + "specify one via `-g`" + terminate_interaction 1 + end + + load Gem.activate_bin_path(contains_executable.first.name, exe, ">= 0.a") + ensure + ARGV.replace argv + end + + def suppress_always_install + name = :always_install + cls = ::Gem::Resolver::InstallerSet + method = cls.instance_method(name) + cls.remove_method(name) + cls.define_method(name) { [] } + + begin + yield + ensure + cls.remove_method(name) + cls.define_method(name, method) + end + end +end diff --git a/lib/rubygems/commands/fetch_command.rb b/lib/rubygems/commands/fetch_command.rb index 5eb45d259c..950d4fe30e 100644 --- a/lib/rubygems/commands/fetch_command.rb +++ b/lib/rubygems/commands/fetch_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../local_remote_options" require_relative "../version_option" diff --git a/lib/rubygems/commands/generate_index_command.rb b/lib/rubygems/commands/generate_index_command.rb index bc71e60ff0..ce580cfaf9 100644 --- a/lib/rubygems/commands/generate_index_command.rb +++ b/lib/rubygems/commands/generate_index_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../indexer" diff --git a/lib/rubygems/commands/help_command.rb b/lib/rubygems/commands/help_command.rb index 8bfb4458ff..20b99fa366 100644 --- a/lib/rubygems/commands/help_command.rb +++ b/lib/rubygems/commands/help_command.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true + require_relative "../command" class Gem::Commands::HelpCommand < Gem::Command # :stopdoc: - EXAMPLES = <<-EOF.freeze + EXAMPLES = <<-EOF Some examples of 'gem' usage. * Install 'rake', either from local directory or remote server: @@ -52,7 +53,7 @@ Some examples of 'gem' usage. gem update --system EOF - GEM_DEPENDENCIES = <<-EOF.freeze + GEM_DEPENDENCIES = <<-EOF A gem dependencies file allows installation of a consistent set of gems across multiple environments. The RubyGems implementation is designed to be compatible with Bundler's Gemfile format. You can see additional @@ -229,7 +230,7 @@ default. This may be overridden with the :development_group option: EOF - PLATFORMS = <<-'EOF'.freeze + PLATFORMS = <<-'EOF' RubyGems platforms are composed of three parts, a CPU, an OS, and a version. These values are taken from values in rbconfig.rb. You can view your current platform by running `gem environment`. diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb index c04c01f258..b0bc2908b4 100644 --- a/lib/rubygems/commands/install_command.rb +++ b/lib/rubygems/commands/install_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../install_update_options" require_relative "../dependency_installer" diff --git a/lib/rubygems/commands/list_command.rb b/lib/rubygems/commands/list_command.rb index 011873b99c..522c771f90 100644 --- a/lib/rubygems/commands/list_command.rb +++ b/lib/rubygems/commands/list_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../query_utils" diff --git a/lib/rubygems/commands/lock_command.rb b/lib/rubygems/commands/lock_command.rb index da636492c9..3a9512fe3f 100644 --- a/lib/rubygems/commands/lock_command.rb +++ b/lib/rubygems/commands/lock_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" class Gem::Commands::LockCommand < Gem::Command diff --git a/lib/rubygems/commands/mirror_command.rb b/lib/rubygems/commands/mirror_command.rb index b633cd3d81..b91a8db12d 100644 --- a/lib/rubygems/commands/mirror_command.rb +++ b/lib/rubygems/commands/mirror_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" unless defined? Gem::Commands::MirrorCommand diff --git a/lib/rubygems/commands/open_command.rb b/lib/rubygems/commands/open_command.rb index d5283f72dd..5a13074a1d 100644 --- a/lib/rubygems/commands/open_command.rb +++ b/lib/rubygems/commands/open_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../version_option" diff --git a/lib/rubygems/commands/outdated_command.rb b/lib/rubygems/commands/outdated_command.rb index 1785194389..08a9221a26 100644 --- a/lib/rubygems/commands/outdated_command.rb +++ b/lib/rubygems/commands/outdated_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../local_remote_options" require_relative "../spec_fetcher" diff --git a/lib/rubygems/commands/owner_command.rb b/lib/rubygems/commands/owner_command.rb index 4a0f7aa3e4..b51c9cf888 100644 --- a/lib/rubygems/commands/owner_command.rb +++ b/lib/rubygems/commands/owner_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../local_remote_options" require_relative "../gemcutter_utilities" @@ -15,7 +16,7 @@ The owner command lets you add and remove owners of a gem on a push server (the default is https://rubygems.org). Multiple owners can be added or removed at the same time, if the flag is given multiple times. -The supported user identifiers are dependant on the push server. +The supported user identifiers are dependent on the push server. For rubygems.org, both e-mail and handle are supported, even though the user identifier field is called "email". @@ -98,8 +99,10 @@ permission to. action = method == :delete ? "Removing" : "Adding" with_response response, "#{action} #{owner}" - rescue - # ignore + rescue Gem::WebauthnVerificationError => e + raise e + rescue StandardError + # ignore early exits to allow for completing the iteration of all owners end end end diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index 72db53ef37..64608a033f 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../package" require_relative "../installer" @@ -34,6 +35,11 @@ class Gem::Commands::PristineCommand < Gem::Command options[:extensions] = value end + add_option("--only-missing-extensions", + "Only restore gems with missing extensions") do |value, options| + options[:only_missing_extensions] = value + end + add_option("--only-executables", "Only restore executables") do |value, options| options[:only_executables] = value @@ -107,6 +113,10 @@ extensions will be restored. Gem::Specification.select do |spec| spec.extensions && !spec.extensions.empty? end + elsif options[:only_missing_extensions] + Gem::Specification.select do |spec| + spec.missing_extensions? + end else get_all_gem_names.sort.map do |gem_name| Gem::Specification.find_all_by_name(gem_name, options[:version]).reverse diff --git a/lib/rubygems/commands/push_command.rb b/lib/rubygems/commands/push_command.rb index 46b65f4e15..79ca3d59b0 100644 --- a/lib/rubygems/commands/push_command.rb +++ b/lib/rubygems/commands/push_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../local_remote_options" require_relative "../gemcutter_utilities" diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb index c6315acf8c..4fa201272e 100644 --- a/lib/rubygems/commands/query_command.rb +++ b/lib/rubygems/commands/query_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../query_utils" require_relative "../deprecate" diff --git a/lib/rubygems/commands/rdoc_command.rb b/lib/rubygems/commands/rdoc_command.rb index a998a9704c..e318a52914 100644 --- a/lib/rubygems/commands/rdoc_command.rb +++ b/lib/rubygems/commands/rdoc_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../version_option" require_relative "../rdoc" diff --git a/lib/rubygems/commands/search_command.rb b/lib/rubygems/commands/search_command.rb index 3f8f7e13f2..c7469e1fa8 100644 --- a/lib/rubygems/commands/search_command.rb +++ b/lib/rubygems/commands/search_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../query_utils" diff --git a/lib/rubygems/commands/server_command.rb b/lib/rubygems/commands/server_command.rb index 56be07c79d..f1dde4aa02 100644 --- a/lib/rubygems/commands/server_command.rb +++ b/lib/rubygems/commands/server_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" unless defined? Gem::Commands::ServerCommand diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index 37c9b4ada1..df1732b49e 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" ## @@ -107,7 +108,7 @@ class Gem::Commands::SetupCommand < Gem::Command end def check_ruby_version - required_version = Gem::Requirement.new ">= 2.3.0" + required_version = Gem::Requirement.new ">= 2.6.0" unless required_version.satisfied_by? Gem.ruby_version alert_error "Expected Ruby version #{required_version}, is #{Gem.ruby_version}" @@ -244,7 +245,7 @@ By default, this RubyGems will install gem as: def install_executables(bin_dir) prog_mode = options[:prog_mode] || 0755 - executables = { "gem" => "bin" } + executables = { "gem" => "exe" } executables.each do |tool, path| say "Installing #{tool} executable" if @verbose diff --git a/lib/rubygems/commands/signin_command.rb b/lib/rubygems/commands/signin_command.rb index 2660eee4f3..0f77908c5b 100644 --- a/lib/rubygems/commands/signin_command.rb +++ b/lib/rubygems/commands/signin_command.rb |
